From e0c6872cf40896c7be36b11dcc744620f10adf1d Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Mon, 2 Sep 2019 13:46:59 +0900 Subject: Initial commit --- systems/knuth/dist/mf/mf.web | 23135 ++++++++++++++++++++++++++++++++++++ systems/knuth/dist/mf/mfbook.tex | 20160 +++++++++++++++++++++++++++++++ systems/knuth/dist/mf/trap.fot | 59 + systems/knuth/dist/mf/trap.log | 4266 +++++++ systems/knuth/dist/mf/trap.mf | 170 + systems/knuth/dist/mf/trap.pl | 490 + systems/knuth/dist/mf/trap.typ | 210 + systems/knuth/dist/mf/trapin.log | 179 + systems/knuth/dist/mf/trapman.tex | 400 + 9 files changed, 49069 insertions(+) create mode 100644 systems/knuth/dist/mf/mf.web create mode 100644 systems/knuth/dist/mf/mfbook.tex create mode 100644 systems/knuth/dist/mf/trap.fot create mode 100644 systems/knuth/dist/mf/trap.log create mode 100644 systems/knuth/dist/mf/trap.mf create mode 100644 systems/knuth/dist/mf/trap.pl create mode 100644 systems/knuth/dist/mf/trap.typ create mode 100644 systems/knuth/dist/mf/trapin.log create mode 100644 systems/knuth/dist/mf/trapman.tex (limited to 'systems/knuth/dist/mf') diff --git a/systems/knuth/dist/mf/mf.web b/systems/knuth/dist/mf/mf.web new file mode 100644 index 0000000000..ca3cb2518d --- /dev/null +++ b/systems/knuth/dist/mf/mf.web @@ -0,0 +1,23135 @@ +% This program is copyright (C) 1984 by D. E. Knuth; all rights are reserved. +% Copying of this file is authorized only if (1) you are D. E. Knuth, or if +% (2) you make absolutely no changes to your copy. (The WEB system provides +% for alterations via an auxiliary file; the master file should stay intact.) +% In other words, METAFONT is under essentially the same ground rules as TeX. + +% TeX is a trademark of the American Mathematical Society. +% METAFONT is a trademark of Addison-Wesley Publishing Company. + +% Version 0 was completed on July 28, 1984. +% Version 1 was completed on January 4, 1986; it corresponds to "Volume D". +% Version 1.1 trivially corrected the punctuation in one message (June 1986). +% Version 1.2 corrected an arithmetic overflow problem (July 1986). +% Version 1.3 improved rounding when elliptical pens are made (November 1986). +% Version 1.4 corrected scan_declared_variable timing (May 1988). +% Version 1.5 fixed negative halving in allocator when mem_min<0 (June 1988). +% Version 1.6 kept open_log_file from calling fatal_error (November 1988). +% Version 1.7 solved that problem a better way (December 1988). +% Version 1.8 introduced major changes for 8-bit extensions (September 1989). +% Version 1.9 improved skimping and was edited for style (December 1989). +% Version 2.0 fixed bug in addto; released with TeX version 3.0 (March 1990). +% Version 2.7 made consistent with TeX version 3.1 (September 1990). +% Version 2.71 fixed bug in draw, allowed unprintable filenames (March 1992). +% Version 2.718 fixed bug in (March 1995). +% Version 2.7182 fixed bugs related to "" (August 1996). +% Version 2.71828 suppressed autorounding in dangerous cases (June 2003). +% Version 2.718281 was a general cleanup with minor fixes (February 2008). +% Version 2.7182818 was similar (January 2014). + +% A reward of $327.68 will be paid to the first finder of any remaining bug. + +% Although considerable effort has been expended to make the METAFONT program +% correct and reliable, no warranty is implied; the author disclaims any +% obligation or liability for damages, including but not limited to +% special, indirect, or consequential damages arising out of or in +% connection with the use or performance of this software. This work has +% been a ``labor of love'' and the author hopes that users enjoy it. + +% Here is TeX material that gets inserted after \input webmac +\def\hang{\hangindent 3em\noindent\ignorespaces} +\def\textindent#1{\hangindent2.5em\noindent\hbox to2.5em{\hss#1 }\ignorespaces} +\font\ninerm=cmr9 +\let\mc=\ninerm % medium caps for names like SAIL +\def\PASCAL{Pascal} +\def\ph{\hbox{Pascal-H}} +\def\psqrt#1{\sqrt{\mathstrut#1}} +\def\k{_{k+1}} +\def\pct!{{\char`\%}} % percent sign in ordinary text +\font\tenlogo=logo10 % font used for the METAFONT logo +\font\logos=logosl10 +\font\eightlogo=logo8 +\def\MF{{\tenlogo META}\-{\tenlogo FONT}} +\def\<#1>{$\langle#1\rangle$} +\def\section{\mathhexbox278} +\let\swap=\leftrightarrow +\def\round{\mathop{\rm round}\nolimits} + +\def\(#1){} % this is used to make section names sort themselves better +\def\9#1{} % this is used for sort keys in the index via @@:sort key}{entry@@> + +\outer\def\N#1. \[#2]#3.{\MN#1.\vfil\eject % begin starred section + \def\rhead{PART #2:\uppercase{#3}} % define running headline + \message{*\modno} % progress report + \edef\next{\write\cont{\Z{\?#2]#3}{\modno}{\the\pageno}}}\next + \ifon\startsection{\bf\ignorespaces#3.\quad}\ignorespaces} +\let\?=\relax % we want to be able to \write a \? + +\def\title{{\eightlogo METAFONT}} +\def\topofcontents{\hsize 5.5in + \vglue -30pt plus 1fil minus 1.5in + \def\?##1]{\hbox to 1in{\hfil##1.\ }} + } +\def\botofcontents{\vskip 0pt plus 1fil minus 1.5in} +\pageno=3 +\def\glob{13} % this should be the section number of "" +\def\gglob{20, 26} % this should be the next two sections of "" + +@* \[1] Introduction. +This is \MF, a font compiler intended to produce typefaces of high quality. +The \PASCAL\ program that follows is the definition of \MF84, a standard +@:PASCAL}{\PASCAL@> +@!@:METAFONT84}{\MF84@> +version of \MF\ that is designed to be highly portable so that identical output +will be obtainable on a great variety of computers. The conventions +of \MF84 are the same as those of \TeX82. + +The main purpose of the following program is to explain the algorithms of \MF\ +as clearly as possible. As a result, the program will not necessarily be very +efficient when a particular \PASCAL\ compiler has translated it into a +particular machine language. However, the program has been written so that it +can be tuned to run efficiently in a wide variety of operating environments +by making comparatively few changes. Such flexibility is possible because +the documentation that follows is written in the \.{WEB} language, which is +at a higher level than \PASCAL; the preprocessing step that converts \.{WEB} +to \PASCAL\ is able to introduce most of the necessary refinements. +Semi-automatic translation to other languages is also feasible, because the +program below does not make extensive use of features that are peculiar to +\PASCAL. + +A large piece of software like \MF\ has inherent complexity that cannot +be reduced below a certain level of difficulty, although each individual +part is fairly simple by itself. The \.{WEB} language is intended to make +the algorithms as readable as possible, by reflecting the way the +individual program pieces fit together and by providing the +cross-references that connect different parts. Detailed comments about +what is going on, and about why things were done in certain ways, have +been liberally sprinkled throughout the program. These comments explain +features of the implementation, but they rarely attempt to explain the +\MF\ language itself, since the reader is supposed to be familiar with +{\sl The {\logos METAFONT\/}book}. +@.WEB@> +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> + +@ The present implementation has a long ancestry, beginning in the spring +of~1977, when its author wrote a prototype set of subroutines and macros +@^Knuth, Donald Ervin@> +that were used to develop the first Computer Modern fonts. +This original proto-\MF\ required the user to recompile a {\mc SAIL} program +whenever any character was changed, because it was not a ``language'' for +font design; the language was {\mc SAIL}. After several hundred characters +had been designed in that way, the author developed an interpretable language +called \MF, in which it was possible to express the Computer Modern programs +less cryptically. A complete \MF\ processor was designed and coded by the +author in 1979. This program, written in {\mc SAIL}, was adapted for use +with a variety of typesetting equipment and display terminals by Leo Guibas, +Lyle Ramshaw, and David Fuchs. +@^Guibas, Leonidas Ioannis@> +@^Ramshaw, Lyle Harold@> +@^Fuchs, David Raymond@> +Major improvements to the design of Computer Modern fonts were made in the +spring of 1982, after which it became clear that a new language would +better express the needs of letterform designers. Therefore an entirely +new \MF\ language and system were developed in 1984; the present system +retains the name and some of the spirit of \MF79, but all of the details +have changed. + +No doubt there still is plenty of room for improvement, but the author +is firmly committed to keeping \MF84 ``frozen'' from now on; stability +and reliability are to be its main virtues. + +On the other hand, the \.{WEB} description can be extended without changing +the core of \MF84 itself, and the program has been designed so that such +extensions are not extremely difficult to make. +The |banner| string defined here should be changed whenever \MF\ +undergoes any modifications, so that it will be clear which version of +\MF\ might be the guilty party when a problem arises. +@^extensions to \MF@> +@^system dependencies@> + +If this program is changed, the resulting system should not be called +`\MF\kern.5pt'; the official name `\MF\kern.5pt' by itself is reserved +for software systems that are fully compatible with each other. +A special test suite called the ``\.{TRAP} test'' is available for +helping to determine whether an implementation deserves to be +known as `\MF\kern.5pt' [cf.~Stanford Computer Science report CS1095, +January 1986]. + +@d banner=='This is METAFONT, Version 2.7182818' {printed when \MF\ starts} + +@ Different \PASCAL s have slightly different conventions, and the present +@!@:PASCAL H}{\ph@> +program expresses \MF\ in terms of the \PASCAL\ that was +available to the author in 1984. Constructions that apply to +this particular compiler, which we shall call \ph, should help the +reader see how to make an appropriate interface for other systems +if necessary. (\ph\ is Charles Hedrick's modification of a compiler +@^Hedrick, Charles Locke@> +for the DECsystem-10 that was originally developed at the University of +Hamburg; cf.\ {\sl SOFTWARE---Practice \AM\ Experience \bf6} (1976), +29--42. The \MF\ program below is intended to be adaptable, without +extensive changes, to most other versions of \PASCAL, so it does not fully +use the admirable features of \ph. Indeed, a conscious effort has been +made here to avoid using several idiosyncratic features of standard +\PASCAL\ itself, so that most of the code can be translated mechanically +into other high-level languages. For example, the `\&{with}' and `\\{new}' +features are not used, nor are pointer types, set types, or enumerated +scalar types; there are no `\&{var}' parameters, except in the case of files +or in the system-dependent |paint_row| procedure; +there are no tag fields on variant records; there are no |real| variables; +no procedures are declared local to other procedures.) + +The portions of this program that involve system-dependent code, where +changes might be necessary because of differences between \PASCAL\ compilers +and/or differences between +operating systems, can be identified by looking at the sections whose +numbers are listed under `system dependencies' in the index. Furthermore, +the index entries for `dirty \PASCAL' list all places where the restrictions +of \PASCAL\ have not been followed perfectly, for one reason or another. +@!@^system dependencies@> +@!@^dirty \PASCAL@> + +@ The program begins with a normal \PASCAL\ program heading, whose +components will be filled in later, using the conventions of \.{WEB}. +@.WEB@> +For example, the portion of the program called `\X\glob:Global +variables\X' below will be replaced by a sequence of variable declarations +that starts in $\section\glob$ of this documentation. In this way, we are able +to define each individual global variable when we are prepared to +understand what it means; we do not have to define all of the globals at +once. Cross references in $\section\glob$, where it says ``See also +sections \gglob, \dots,'' also make it possible to look at the set of +all global variables, if desired. Similar remarks apply to the other +portions of the program heading. + +Actually the heading shown here is not quite normal: The |program| line +does not mention any |output| file, because \ph\ would ask the \MF\ user +to specify a file name if |output| were specified here. +@:PASCAL H}{\ph@> +@^system dependencies@> + +@d mtype==t@&y@&p@&e {this is a \.{WEB} coding trick:} +@f mtype==type {`\&{mtype}' will be equivalent to `\&{type}'} +@f type==true {but `|type|' will not be treated as a reserved word} + +@p @t\4@>@@/ +program MF; {all file names are defined dynamically} +label @@/ +const @@/ +mtype @@/ +var @@/ +@# +procedure initialize; {this procedure gets things started properly} + var @@/ + begin @@/ + end;@# +@t\4@>@@/ +@t\4@>@@/ + +@ The overall \MF\ program begins with the heading just shown, after which +comes a bunch of procedure declarations and function declarations. +Finally we will get to the main program, which begins with the +comment `|start_here|'. If you want to skip down to the +main program now, you can look up `|start_here|' in the index. +But the author suggests that the best way to understand this program +is to follow pretty much the order of \MF's components as they appear in the +\.{WEB} description you are now reading, since the present ordering is +intended to combine the advantages of the ``bottom up'' and ``top down'' +approaches to the problem of understanding a somewhat complicated system. + +@ Three labels must be declared in the main program, so we give them +symbolic names. + +@d start_of_MF=1 {go here when \MF's variables are initialized} +@d end_of_MF=9998 {go here to close files and terminate gracefully} +@d final_end=9999 {this label marks the ending of the program} + +@= +start_of_MF@t\hskip-2pt@>, end_of_MF@t\hskip-2pt@>,@,final_end; + {key control points} + +@ Some of the code below is intended to be used only when diagnosing the +strange behavior that sometimes occurs when \MF\ is being installed or +when system wizards are fooling around with \MF\ without quite knowing +what they are doing. Such code will not normally be compiled; it is +delimited by the codewords `$|debug|\ldots|gubed|$', with apologies +to people who wish to preserve the purity of English. + +Similarly, there is some conditional code delimited by +`$|stat|\ldots|tats|$' that is intended for use when statistics are to be +kept about \MF's memory usage. The |stat| $\ldots$ |tats| code also +implements special diagnostic information that is printed when +$\\{tracingedges}>1$. +@^debugging@> + +@d debug==@{ {change this to `$\\{debug}\equiv\null$' when debugging} +@d gubed==@t@>@} {change this to `$\\{gubed}\equiv\null$' when debugging} +@f debug==begin +@f gubed==end +@# +@d stat==@{ {change this to `$\\{stat}\equiv\null$' when gathering + usage statistics} +@d tats==@t@>@} {change this to `$\\{tats}\equiv\null$' when gathering + usage statistics} +@f stat==begin +@f tats==end + +@ This program has two important variations: (1) There is a long and slow +version called \.{INIMF}, which does the extra calculations needed to +@.INIMF@> +initialize \MF's internal tables; and (2)~there is a shorter and faster +production version, which cuts the initialization to a bare minimum. +Parts of the program that are needed in (1) but not in (2) are delimited by +the codewords `$|init|\ldots|tini|$'. + +@d init== {change this to `$\\{init}\equiv\.{@@\{}$' in the production version} +@d tini== {change this to `$\\{tini}\equiv\.{@@\}}$' in the production version} +@f init==begin +@f tini==end + +@ If the first character of a \PASCAL\ comment is a dollar sign, +\ph\ treats the comment as a list of ``compiler directives'' that will +affect the translation of this program into machine language. The +directives shown below specify full checking and inclusion of the \PASCAL\ +debugger when \MF\ is being debugged, but they cause range checking and other +redundant code to be eliminated when the production system is being generated. +Arithmetic overflow will be detected in all cases. +@:PASCAL H}{\ph@> +@^system dependencies@> +@^overflow in arithmetic@> + +@= +@{@&$C-,A+,D-@} {no range check, catch arithmetic overflow, no debug overhead} +@!debug @{@&$C+,D+@}@+ gubed {but turn everything on when debugging} + +@ This \MF\ implementation conforms to the rules of the {\sl Pascal User +@:PASCAL}{\PASCAL@> +@^system dependencies@> +Manual} published by Jensen and Wirth in 1975, except where system-dependent +@^Wirth, Niklaus@> +@^Jensen, Kathleen@> +code is necessary to make a useful system program, and except in another +respect where such conformity would unnecessarily obscure the meaning +and clutter up the code: We assume that |case| statements may include a +default case that applies if no matching label is found. Thus, we shall use +constructions like +$$\vbox{\halign{\ignorespaces#\hfil\cr +|case x of|\cr +1: $\langle\,$code for $x=1\,\rangle$;\cr +3: $\langle\,$code for $x=3\,\rangle$;\cr +|othercases| $\langle\,$code for |x<>1| and |x<>3|$\,\rangle$\cr +|endcases|\cr}}$$ +since most \PASCAL\ compilers have plugged this hole in the language by +incorporating some sort of default mechanism. For example, the \ph\ +compiler allows `|others|:' as a default label, and other \PASCAL s allow +syntaxes like `\&{else}' or `\&{otherwise}' or `\\{otherwise}:', etc. The +definitions of |othercases| and |endcases| should be changed to agree with +local conventions. Note that no semicolon appears before |endcases| in +this program, so the definition of |endcases| should include a semicolon +if the compiler wants one. (Of course, if no default mechanism is +available, the |case| statements of \MF\ will have to be laboriously +extended by listing all remaining cases. People who are stuck with such +\PASCAL s have, in fact, done this, successfully but not happily!) +@:PASCAL H}{\ph@> + +@d othercases == others: {default for cases not listed explicitly} +@d endcases == @+end {follows the default case in an extended |case| statement} +@f othercases == else +@f endcases == end + +@ The following parameters can be changed at compile time to extend or +reduce \MF's capacity. They may have different values in \.{INIMF} and +in production versions of \MF. +@.INIMF@> +@^system dependencies@> + +@= +@!mem_max=30000; {greatest index in \MF's internal |mem| array; + must be strictly less than |max_halfword|; + must be equal to |mem_top| in \.{INIMF}, otherwise |>=mem_top|} +@!max_internal=100; {maximum number of internal quantities} +@!buf_size=500; {maximum number of characters simultaneously present in + current lines of open files; must not exceed |max_halfword|} +@!error_line=72; {width of context lines on terminal error messages} +@!half_error_line=42; {width of first lines of contexts in terminal + error messages; should be between 30 and |error_line-15|} +@!max_print_line=79; {width of longest text lines output; should be at least 60} +@!screen_width=768; {number of pixels in each row of screen display} +@!screen_depth=1024; {number of pixels in each column of screen display} +@!stack_size=30; {maximum number of simultaneous input sources} +@!max_strings=2000; {maximum number of strings; must not exceed |max_halfword|} +@!string_vacancies=8000; {the minimum number of characters that should be + available for the user's identifier names and strings, + after \MF's own error messages are stored} +@!pool_size=32000; {maximum number of characters in strings, including all + error messages and help texts, and the names of all identifiers; + must exceed |string_vacancies| by the total + length of \MF's own strings, which is currently about 22000} +@!move_size=5000; {space for storing moves in a single octant} +@!max_wiggle=300; {number of autorounded points per cycle} +@!gf_buf_size=800; {size of the output buffer, must be a multiple of 8} +@!file_name_size=40; {file names shouldn't be longer than this} +@!pool_name='MFbases:MF.POOL '; + {string of length |file_name_size|; tells where the string pool appears} +@.MFbases@> +@!path_size=300; {maximum number of knots between breakpoints of a path} +@!bistack_size=785; {size of stack for bisection algorithms; + should probably be left at this value} +@!header_size=100; {maximum number of \.{TFM} header words, times~4} +@!lig_table_size=5000; {maximum number of ligature/kern steps, must be + at least 255 and at most 32510} +@!max_kerns=500; {maximum number of distinct kern amounts} +@!max_font_dimen=50; {maximum number of \&{fontdimen} parameters} + +@ Like the preceding parameters, the following quantities can be changed +at compile time to extend or reduce \MF's capacity. But if they are changed, +it is necessary to rerun the initialization program \.{INIMF} +@.INIMF@> +to generate new tables for the production \MF\ program. +One can't simply make helter-skelter changes to the following constants, +since certain rather complex initialization +numbers are computed from them. They are defined here using +\.{WEB} macros, instead of being put into \PASCAL's |const| list, in order to +emphasize this distinction. + +@d mem_min=0 {smallest index in the |mem| array, must not be less + than |min_halfword|} +@d mem_top==30000 {largest index in the |mem| array dumped by \.{INIMF}; + must be substantially larger than |mem_min| + and not greater than |mem_max|} +@d hash_size=2100 {maximum number of symbolic tokens, + must be less than |max_halfword-3*param_size|} +@d hash_prime=1777 {a prime number equal to about 85\pct! of |hash_size|} +@d max_in_open=6 {maximum number of input files and error insertions that + can be going on simultaneously} +@d param_size=150 {maximum number of simultaneous macro parameters} +@^system dependencies@> + +@ In case somebody has inadvertently made bad settings of the ``constants,'' +\MF\ checks them using a global variable called |bad|. + +This is the first of many sections of \MF\ where global variables are +defined. + +@= +@!bad:integer; {is some ``constant'' wrong?} + +@ Later on we will say `\ignorespaces|if mem_max>=max_halfword then bad:=10|', +or something similar. (We can't do that until |max_halfword| has been defined.) + +@= +bad:=0; +if (half_error_line<30)or(half_error_line>error_line-15) then bad:=1; +if max_print_line<60 then bad:=2; +if gf_buf_size mod 8<>0 then bad:=3; +if mem_min+1100>mem_top then bad:=4; +if hash_prime>hash_size then bad:=5; +if header_size mod 4 <> 0 then bad:=6; +if(lig_table_size<255)or(lig_table_size>32510)then bad:=7; + +@ Labels are given symbolic names by the following definitions, so that +occasional |goto| statements will be meaningful. We insert the label +`|exit|' just before the `\ignorespaces|end|\unskip' of a procedure in +which we have used the `|return|' statement defined below; the label +`|restart|' is occasionally used at the very beginning of a procedure; and +the label `|reswitch|' is occasionally used just prior to a |case| +statement in which some cases change the conditions and we wish to branch +to the newly applicable case. Loops that are set up with the |loop| +construction defined below are commonly exited by going to `|done|' or to +`|found|' or to `|not_found|', and they are sometimes repeated by going to +`|continue|'. If two or more parts of a subroutine start differently but +end up the same, the shared code may be gathered together at +`|common_ending|'. + +Incidentally, this program never declares a label that isn't actually used, +because some fussy \PASCAL\ compilers will complain about redundant labels. + +@d exit=10 {go here to leave a procedure} +@d restart=20 {go here to start a procedure again} +@d reswitch=21 {go here to start a case statement again} +@d continue=22 {go here to resume a loop} +@d done=30 {go here to exit a loop} +@d done1=31 {like |done|, when there is more than one loop} +@d done2=32 {for exiting the second loop in a long block} +@d done3=33 {for exiting the third loop in a very long block} +@d done4=34 {for exiting the fourth loop in an extremely long block} +@d done5=35 {for exiting the fifth loop in an immense block} +@d done6=36 {for exiting the sixth loop in a block} +@d found=40 {go here when you've found it} +@d found1=41 {like |found|, when there's more than one per routine} +@d found2=42 {like |found|, when there's more than two per routine} +@d not_found=45 {go here when you've found nothing} +@d common_ending=50 {go here when you want to merge with another branch} + +@ Here are some macros for common programming idioms. + +@d incr(#) == #:=#+1 {increase a variable by unity} +@d decr(#) == #:=#-1 {decrease a variable by unity} +@d negate(#) == #:=-# {change the sign of a variable} +@d double(#) == #:=#+# {multiply a variable by two} +@d loop == @+ while true do@+ {repeat over and over until a |goto| happens} +@f loop == xclause + {\.{WEB}'s |xclause| acts like `\ignorespaces|while true do|\unskip'} +@d do_nothing == {empty statement} +@d return == goto exit {terminate a procedure call} +@f return == nil {\.{WEB} will henceforth say |return| instead of \\{return}} + +@* \[2] The character set. +In order to make \MF\ readily portable to a wide variety of +computers, all of its input text is converted to an internal eight-bit +code that includes standard ASCII, the ``American Standard Code for +Information Interchange.'' This conversion is done immediately when each +character is read in. Conversely, characters are converted from ASCII to +the user's external representation just before they are output to a +text file. +@^ASCII code@> + +Such an internal code is relevant to users of \MF\ only with respect to +the \&{char} and \&{ASCII} operations, and the comparison of strings. + +@ Characters of text that have been converted to \MF's internal form +are said to be of type |ASCII_code|, which is a subrange of the integers. + +@= +@!ASCII_code=0..255; {eight-bit numbers} + +@ The original \PASCAL\ compiler was designed in the late 60s, when six-bit +character sets were common, so it did not make provision for lowercase +letters. Nowadays, of course, we need to deal with both capital and small +letters in a convenient way, especially in a program for font design; +so the present specification of \MF\ has been written under the assumption +that the \PASCAL\ compiler and run-time system permit the use of text files +with more than 64 distinguishable characters. More precisely, we assume that +the character set contains at least the letters and symbols associated +with ASCII codes @'40 through @'176; all of these characters are now +available on most computer terminals. + +Since we are dealing with more characters than were present in the first +\PASCAL\ compilers, we have to decide what to call the associated data +type. Some \PASCAL s use the original name |char| for the +characters in text files, even though there now are more than 64 such +characters, while other \PASCAL s consider |char| to be a 64-element +subrange of a larger data type that has some other name. + +In order to accommodate this difference, we shall use the name |text_char| +to stand for the data type of the characters that are converted to and +from |ASCII_code| when they are input and output. We shall also assume +that |text_char| consists of the elements |chr(first_text_char)| through +|chr(last_text_char)|, inclusive. The following definitions should be +adjusted if necessary. +@^system dependencies@> + +@d text_char == char {the data type of characters in text files} +@d first_text_char=0 {ordinal number of the smallest element of |text_char|} +@d last_text_char=255 {ordinal number of the largest element of |text_char|} + +@= +@!i:integer; + +@ The \MF\ processor converts between ASCII code and +the user's external character set by means of arrays |xord| and |xchr| +that are analogous to \PASCAL's |ord| and |chr| functions. + +@= +@!xord: array [text_char] of ASCII_code; + {specifies conversion of input characters} +@!xchr: array [ASCII_code] of text_char; + {specifies conversion of output characters} + +@ Since we are assuming that our \PASCAL\ system is able to read and +write the visible characters of standard ASCII (although not +necessarily using the ASCII codes to represent them), the following +assignment statements initialize the standard part of the |xchr| array +properly, without needing any system-dependent changes. On the other +hand, it is possible to implement \MF\ with less complete character +sets, and in such cases it will be necessary to change something here. +@^system dependencies@> + +@= +xchr[@'40]:=' '; +xchr[@'41]:='!'; +xchr[@'42]:='"'; +xchr[@'43]:='#'; +xchr[@'44]:='$'; +xchr[@'45]:='%'; +xchr[@'46]:='&'; +xchr[@'47]:='''';@/ +xchr[@'50]:='('; +xchr[@'51]:=')'; +xchr[@'52]:='*'; +xchr[@'53]:='+'; +xchr[@'54]:=','; +xchr[@'55]:='-'; +xchr[@'56]:='.'; +xchr[@'57]:='/';@/ +xchr[@'60]:='0'; +xchr[@'61]:='1'; +xchr[@'62]:='2'; +xchr[@'63]:='3'; +xchr[@'64]:='4'; +xchr[@'65]:='5'; +xchr[@'66]:='6'; +xchr[@'67]:='7';@/ +xchr[@'70]:='8'; +xchr[@'71]:='9'; +xchr[@'72]:=':'; +xchr[@'73]:=';'; +xchr[@'74]:='<'; +xchr[@'75]:='='; +xchr[@'76]:='>'; +xchr[@'77]:='?';@/ +xchr[@'100]:='@@'; +xchr[@'101]:='A'; +xchr[@'102]:='B'; +xchr[@'103]:='C'; +xchr[@'104]:='D'; +xchr[@'105]:='E'; +xchr[@'106]:='F'; +xchr[@'107]:='G';@/ +xchr[@'110]:='H'; +xchr[@'111]:='I'; +xchr[@'112]:='J'; +xchr[@'113]:='K'; +xchr[@'114]:='L'; +xchr[@'115]:='M'; +xchr[@'116]:='N'; +xchr[@'117]:='O';@/ +xchr[@'120]:='P'; +xchr[@'121]:='Q'; +xchr[@'122]:='R'; +xchr[@'123]:='S'; +xchr[@'124]:='T'; +xchr[@'125]:='U'; +xchr[@'126]:='V'; +xchr[@'127]:='W';@/ +xchr[@'130]:='X'; +xchr[@'131]:='Y'; +xchr[@'132]:='Z'; +xchr[@'133]:='['; +xchr[@'134]:='\'; +xchr[@'135]:=']'; +xchr[@'136]:='^'; +xchr[@'137]:='_';@/ +xchr[@'140]:='`'; +xchr[@'141]:='a'; +xchr[@'142]:='b'; +xchr[@'143]:='c'; +xchr[@'144]:='d'; +xchr[@'145]:='e'; +xchr[@'146]:='f'; +xchr[@'147]:='g';@/ +xchr[@'150]:='h'; +xchr[@'151]:='i'; +xchr[@'152]:='j'; +xchr[@'153]:='k'; +xchr[@'154]:='l'; +xchr[@'155]:='m'; +xchr[@'156]:='n'; +xchr[@'157]:='o';@/ +xchr[@'160]:='p'; +xchr[@'161]:='q'; +xchr[@'162]:='r'; +xchr[@'163]:='s'; +xchr[@'164]:='t'; +xchr[@'165]:='u'; +xchr[@'166]:='v'; +xchr[@'167]:='w';@/ +xchr[@'170]:='x'; +xchr[@'171]:='y'; +xchr[@'172]:='z'; +xchr[@'173]:='{'; +xchr[@'174]:='|'; +xchr[@'175]:='}'; +xchr[@'176]:='~';@/ + +@ The ASCII code is ``standard'' only to a certain extent, since many +computer installations have found it advantageous to have ready access +to more than 94 printing characters. If \MF\ is being used +on a garden-variety \PASCAL\ for which only standard ASCII +codes will appear in the input and output files, it doesn't really matter +what codes are specified in |xchr[0..@'37]|, but the safest policy is to +blank everything out by using the code shown below. + +However, other settings of |xchr| will make \MF\ more friendly on +computers that have an extended character set, so that users can type things +like `\.^^Z' instead of `\.{<>}'. +People with extended character sets can +assign codes arbitrarily, giving an |xchr| equivalent to whatever +characters the users of \MF\ are allowed to have in their input files. +Appropriate changes to \MF's |char_class| table should then be made. +(Unlike \TeX, each installation of \MF\ has a fixed assignment of category +codes, called the |char_class|.) Such changes make portability of programs +more difficult, so they should be introduced cautiously if at all. +@^character set dependencies@> +@^system dependencies@> + +@= +for i:=0 to @'37 do xchr[i]:=' '; +for i:=@'177 to @'377 do xchr[i]:=' '; + +@ The following system-independent code makes the |xord| array contain a +suitable inverse to the information in |xchr|. Note that if |xchr[i]=xchr[j]| +where |i= +for i:=first_text_char to last_text_char do xord[chr(i)]:=@'177; +for i:=@'200 to @'377 do xord[xchr[i]]:=i; +for i:=0 to @'176 do xord[xchr[i]]:=i; + +@* \[3] Input and output. +The bane of portability is the fact that different operating systems treat +input and output quite differently, perhaps because computer scientists +have not given sufficient attention to this problem. People have felt somehow +that input and output are not part of ``real'' programming. Well, it is true +that some kinds of programming are more fun than others. With existing +input/output conventions being so diverse and so messy, the only sources of +joy in such parts of the code are the rare occasions when one can find a +way to make the program a little less bad than it might have been. We have +two choices, either to attack I/O now and get it over with, or to postpone +I/O until near the end. Neither prospect is very attractive, so let's +get it over with. + +The basic operations we need to do are (1)~inputting and outputting of +text, to or from a file or the user's terminal; (2)~inputting and +outputting of eight-bit bytes, to or from a file; (3)~instructing the +operating system to initiate (``open'') or to terminate (``close'') input or +output from a specified file; (4)~testing whether the end of an input +file has been reached; (5)~display of bits on the user's screen. +The bit-display operation will be discussed in a later section; we shall +deal here only with more traditional kinds of I/O. + +\MF\ needs to deal with two kinds of files. +We shall use the term |alpha_file| for a file that contains textual data, +and the term |byte_file| for a file that contains eight-bit binary information. +These two types turn out to be the same on many computers, but +sometimes there is a significant distinction, so we shall be careful to +distinguish between them. Standard protocols for transferring +such files from computer to computer, via high-speed networks, are +now becoming available to more and more communities of users. + +The program actually makes use also of a third kind of file, called a +|word_file|, when dumping and reloading base information for its own +initialization. We shall define a word file later; but it will be possible +for us to specify simple operations on word files before they are defined. + +@= +@!eight_bits=0..255; {unsigned one-byte quantity} +@!alpha_file=packed file of text_char; {files that contain textual data} +@!byte_file=packed file of eight_bits; {files that contain binary data} + +@ Most of what we need to do with respect to input and output can be handled +by the I/O facilities that are standard in \PASCAL, i.e., the routines +called |get|, |put|, |eof|, and so on. But +standard \PASCAL\ does not allow file variables to be associated with file +names that are determined at run time, so it cannot be used to implement +\MF; some sort of extension to \PASCAL's ordinary |reset| and |rewrite| +is crucial for our purposes. We shall assume that |name_of_file| is a variable +of an appropriate type such that the \PASCAL\ run-time system being used to +implement \MF\ can open a file whose external name is specified by +|name_of_file|. +@^system dependencies@> + +@= +@!name_of_file:packed array[1..file_name_size] of char;@;@/ + {on some systems this may be a \&{record} variable} +@!name_length:0..file_name_size;@/{this many characters are actually + relevant in |name_of_file| (the rest are blank)} + +@ The \ph\ compiler with which the present version of \MF\ was prepared has +extended the rules of \PASCAL\ in a very convenient way. To open file~|f|, +we can write +$$\vbox{\halign{#\hfil\qquad&#\hfil\cr +|reset(f,@t\\{name}@>,'/O')|&for input;\cr +|rewrite(f,@t\\{name}@>,'/O')|&for output.\cr}}$$ +The `\\{name}' parameter, which is of type `\ignorespaces|packed +array[@t\<\\{any}>@>] of text_char|', stands for the name of +the external file that is being opened for input or output. +Blank spaces that might appear in \\{name} are ignored. + +The `\.{/O}' parameter tells the operating system not to issue its own +error messages if something goes wrong. If a file of the specified name +cannot be found, or if such a file cannot be opened for some other reason +(e.g., someone may already be trying to write the same file), we will have +|@!erstat(f)<>0| after an unsuccessful |reset| or |rewrite|. This allows +\MF\ to undertake appropriate corrective action. +@:PASCAL H}{\ph@> +@^system dependencies@> + +\MF's file-opening procedures return |false| if no file identified by +|name_of_file| could be opened. + +@d reset_OK(#)==erstat(#)=0 +@d rewrite_OK(#)==erstat(#)=0 + +@p function a_open_in(var @!f:alpha_file):boolean; + {open a text file for input} +begin reset(f,name_of_file,'/O'); a_open_in:=reset_OK(f); +end; +@# +function a_open_out(var @!f:alpha_file):boolean; + {open a text file for output} +begin rewrite(f,name_of_file,'/O'); a_open_out:=rewrite_OK(f); +end; +@# +function b_open_out(var @!f:byte_file):boolean; + {open a binary file for output} +begin rewrite(f,name_of_file,'/O'); b_open_out:=rewrite_OK(f); +end; +@# +function w_open_in(var @!f:word_file):boolean; + {open a word file for input} +begin reset(f,name_of_file,'/O'); w_open_in:=reset_OK(f); +end; +@# +function w_open_out(var @!f:word_file):boolean; + {open a word file for output} +begin rewrite(f,name_of_file,'/O'); w_open_out:=rewrite_OK(f); +end; + +@ Files can be closed with the \ph\ routine `|close(f)|', which +@:PASCAL H}{\ph@> +@^system dependencies@> +should be used when all input or output with respect to |f| has been completed. +This makes |f| available to be opened again, if desired; and if |f| was used for +output, the |close| operation makes the corresponding external file appear +on the user's area, ready to be read. + +@p procedure a_close(var @!f:alpha_file); {close a text file} +begin close(f); +end; +@# +procedure b_close(var @!f:byte_file); {close a binary file} +begin close(f); +end; +@# +procedure w_close(var @!f:word_file); {close a word file} +begin close(f); +end; + +@ Binary input and output are done with \PASCAL's ordinary |get| and |put| +procedures, so we don't have to make any other special arrangements for +binary~I/O. Text output is also easy to do with standard \PASCAL\ routines. +The treatment of text input is more difficult, however, because +of the necessary translation to |ASCII_code| values. +\MF's conventions should be efficient, and they should +blend nicely with the user's operating environment. + +@ Input from text files is read one line at a time, using a routine called +|input_ln|. This function is defined in terms of global variables called +|buffer|, |first|, and |last| that will be described in detail later; for +now, it suffices for us to know that |buffer| is an array of |ASCII_code| +values, and that |first| and |last| are indices into this array +representing the beginning and ending of a line of text. + +@= +@!buffer:array[0..buf_size] of ASCII_code; {lines of characters being read} +@!first:0..buf_size; {the first unused position in |buffer|} +@!last:0..buf_size; {end of the line just input to |buffer|} +@!max_buf_stack:0..buf_size; {largest index used in |buffer|} + +@ The |input_ln| function brings the next line of input from the specified +field into available positions of the buffer array and returns the value +|true|, unless the file has already been entirely read, in which case it +returns |false| and sets |last:=first|. In general, the |ASCII_code| +numbers that represent the next line of the file are input into +|buffer[first]|, |buffer[first+1]|, \dots, |buffer[last-1]|; and the +global variable |last| is set equal to |first| plus the length of the +line. Trailing blanks are removed from the line; thus, either |last=first| +(in which case the line was entirely blank) or |buffer[last-1]<>" "|. +@^inner loop@> + +An overflow error is given, however, if the normal actions of |input_ln| +would make |last>=buf_size|; this is done so that other parts of \MF\ +can safely look at the contents of |buffer[last+1]| without overstepping +the bounds of the |buffer| array. Upon entry to |input_ln|, the condition +|first=max_buf_stack then + begin max_buf_stack:=last+1; + if max_buf_stack=buf_size then + @; + end; + buffer[last]:=xord[f^]; get(f); incr(last); + if buffer[last-1]<>" " then last_nonblank:=last; + end; + last:=last_nonblank; input_ln:=true; + end; +end; + +@ The user's terminal acts essentially like other files of text, except +that it is used both for input and for output. When the terminal is +considered an input file, the file variable is called |term_in|, and when it +is considered an output file the file variable is |term_out|. +@^system dependencies@> + +@= +@!term_in:alpha_file; {the terminal as an input file} +@!term_out:alpha_file; {the terminal as an output file} + +@ Here is how to open the terminal files +in \ph. The `\.{/I}' switch suppresses the first |get|. +@:PASCAL H}{\ph@> +@^system dependencies@> + +@d t_open_in==reset(term_in,'TTY:','/O/I') {open the terminal for text input} +@d t_open_out==rewrite(term_out,'TTY:','/O') + {open the terminal for text output} + +@ Sometimes it is necessary to synchronize the input/output mixture that +happens on the user's terminal, and three system-dependent +procedures are used for this +purpose. The first of these, |update_terminal|, is called when we want +to make sure that everything we have output to the terminal so far has +actually left the computer's internal buffers and been sent. +The second, |clear_terminal|, is called when we wish to cancel any +input that the user may have typed ahead (since we are about to +issue an unexpected error message). The third, |wake_up_terminal|, +is supposed to revive the terminal if the user has disabled it by +some instruction to the operating system. The following macros show how +these operations can be specified in \ph: +@:PASCAL H}{\ph@> +@^system dependencies@> + +@d update_terminal == break(term_out) {empty the terminal output buffer} +@d clear_terminal == break_in(term_in,true) {clear the terminal input buffer} +@d wake_up_terminal == do_nothing {cancel the user's cancellation of output} + +@ We need a special routine to read the first line of \MF\ input from +the user's terminal. This line is different because it is read before we +have opened the transcript file; there is sort of a ``chicken and +egg'' problem here. If the user types `\.{input cmr10}' on the first +line, or if some macro invoked by that line does such an \.{input}, +the transcript file will be named `\.{cmr10.log}'; but if no \.{input} +commands are performed during the first line of terminal input, the transcript +file will acquire its default name `\.{mfput.log}'. (The transcript file +will not contain error messages generated by the first line before the +first \.{input} command.) +@.mfput@> + +The first line is even more special if we are lucky enough to have an operating +system that treats \MF\ differently from a run-of-the-mill \PASCAL\ object +program. It's nice to let the user start running a \MF\ job by typing +a command line like `\.{MF cmr10}'; in such a case, \MF\ will operate +as if the first line of input were `\.{cmr10}', i.e., the first line will +consist of the remainder of the command line, after the part that invoked \MF. + +The first line is special also because it may be read before \MF\ has +input a base file. In such cases, normal error messages cannot yet +be given. The following code uses concepts that will be explained later. +(If the \PASCAL\ compiler does not support non-local |@!goto|\unskip, the +@^system dependencies@> +statement `|goto final_end|' should be replaced by something that +quietly terminates the program.) + +@= +if base_ident=0 then + begin write_ln(term_out,'Buffer size exceeded!'); goto final_end; +@.Buffer size exceeded@> + end +else begin cur_input.loc_field:=first; cur_input.limit_field:=last-1; + overflow("buffer size",buf_size); +@:METAFONT capacity exceeded buffer size}{\quad buffer size@> + end + +@ Different systems have different ways to get started. But regardless of +what conventions are adopted, the routine that initializes the terminal +should satisfy the following specifications: + +\yskip\textindent{1)}It should open file |term_in| for input from the + terminal. (The file |term_out| will already be open for output to the + terminal.) + +\textindent{2)}If the user has given a command line, this line should be + considered the first line of terminal input. Otherwise the + user should be prompted with `\.{**}', and the first line of input + should be whatever is typed in response. + +\textindent{3)}The first line of input, which might or might not be a + command line, should appear in locations |first| to |last-1| of the + |buffer| array. + +\textindent{4)}The global variable |loc| should be set so that the + character to be read next by \MF\ is in |buffer[loc]|. This + character should not be blank, and we should have |loc + +@p function init_terminal:boolean; {gets the terminal input started} +label exit; +begin t_open_in; +loop@+begin wake_up_terminal; write(term_out,'**'); update_terminal; +@.**@> + if not input_ln(term_in,true) then {this shouldn't happen} + begin write_ln(term_out); + write(term_out,'! End of file on the terminal... why?'); +@.End of file on the terminal@> + init_terminal:=false; return; + end; + loc:=first; + while (loc +which converts single-character strings into the ASCII code number of the +single character involved, while it converts other strings into integers +and builds a string pool file. Thus, when the string constant \.{"."} appears +in the program below, \.{WEB} converts it into the integer 46, which is the +ASCII code for a period, while \.{WEB} will convert a string like \.{"hello"} +into some integer greater than~255. String number 46 will presumably be the +single character `\..'\thinspace; but some ASCII codes have no standard visible +representation, and \MF\ may need to be able to print an arbitrary +ASCII character, so the first 256 strings are used to specify exactly what +should be printed for each of the 256 possibilities. + +Elements of the |str_pool| array must be ASCII codes that can actually be +printed; i.e., they must have an |xchr| equivalent in the local +character set. (This restriction applies only to preloaded strings, +not to those generated dynamically by the user.) + +Some \PASCAL\ compilers won't pack integers into a single byte unless the +integers lie in the range |-128..127|. To accommodate such systems +we access the string pool only via macros that can easily be redefined. +@^system dependencies@> + +@d si(#) == # {convert from |ASCII_code| to |packed_ASCII_code|} +@d so(#) == # {convert from |packed_ASCII_code| to |ASCII_code|} + +@= +@!pool_pointer = 0..pool_size; {for variables that point into |str_pool|} +@!str_number = 0..max_strings; {for variables that point into |str_start|} +@!packed_ASCII_code = 0..255; {elements of |str_pool| array} + +@ @= +@!str_pool:packed array[pool_pointer] of packed_ASCII_code; {the characters} +@!str_start : array[str_number] of pool_pointer; {the starting pointers} +@!pool_ptr : pool_pointer; {first unused position in |str_pool|} +@!str_ptr : str_number; {number of the current string being created} +@!init_pool_ptr : pool_pointer; {the starting value of |pool_ptr|} +@!init_str_ptr : str_number; {the starting value of |str_ptr|} +@!max_pool_ptr : pool_pointer; {the maximum so far of |pool_ptr|} +@!max_str_ptr : str_number; {the maximum so far of |str_ptr|} + +@ Several of the elementary string operations are performed using \.{WEB} +macros instead of \PASCAL\ procedures, because many of the +operations are done quite frequently and we want to avoid the +overhead of procedure calls. For example, here is +a simple macro that computes the length of a string. +@.WEB@> + +@d length(#)==(str_start[#+1]-str_start[#]) {the number of characters + in string number \#} + +@ The length of the current string is called |cur_length|: + +@d cur_length == (pool_ptr - str_start[str_ptr]) + +@ Strings are created by appending character codes to |str_pool|. +The |append_char| macro, defined here, does not check to see if the +value of |pool_ptr| has gotten too high; this test is supposed to be +made before |append_char| is used. + +To test if there is room to append |l| more characters to |str_pool|, +we shall write |str_room(l)|, which aborts \MF\ and gives an +apologetic error message if there isn't enough room. + +@d append_char(#) == {put |ASCII_code| \# at the end of |str_pool|} +begin str_pool[pool_ptr]:=si(#); incr(pool_ptr); +end +@d str_room(#) == {make sure that the pool hasn't overflowed} + begin if pool_ptr+# > max_pool_ptr then + begin if pool_ptr+# > pool_size then + overflow("pool size",pool_size-init_pool_ptr); +@:METAFONT capacity exceeded pool size}{\quad pool size@> + max_pool_ptr:=pool_ptr+#; + end; + end + +@ \MF's string expressions are implemented in a brute-force way: Every +new string or substring that is needed is simply copied into the string pool. + +Such a scheme can be justified because string expressions aren't a big +deal in \MF\ applications; strings rarely need to be saved from one +statement to the next. But it would waste space needlessly if we didn't +try to reclaim the space of strings that are going to be used only once. + +Therefore a simple reference count mechanism is provided: If there are +@^reference counts@> +no references to a certain string from elsewhere in the program, and +if there are no references to any strings created subsequent to it, +then the string space will be reclaimed. + +The number of references to string number |s| will be |str_ref[s]|. The +special value |str_ref[s]=max_str_ref=127| is used to denote an unknown +positive number of references; such strings will never be recycled. If +a string is ever referred to more than 126 times, simultaneously, we +put it in this category. Hence a single byte suffices to store each |str_ref|. + +@d max_str_ref=127 {``infinite'' number of references} +@d add_str_ref(#)==begin if str_ref[#]= +@!str_ref:array[str_number] of 0..max_str_ref; + +@ Here's what we do when a string reference disappears: + +@d delete_str_ref(#)== begin if str_ref[#]1 then decr(str_ref[#])@+else flush_string(#); + end + +@= +procedure flush_string(@!s:str_number); +begin if s0; +pool_ptr:=str_start[str_ptr]; +end; + +@ Once a sequence of characters has been appended to |str_pool|, it +officially becomes a string when the function |make_string| is called. +This function returns the identification number of the new string as its +value. + +@p function make_string : str_number; {current string enters the pool} +begin if str_ptr=max_str_ptr then + begin if str_ptr=max_strings then + overflow("number of strings",max_strings-init_str_ptr); +@:METAFONT capacity exceeded number of strings}{\quad number of strings@> + incr(max_str_ptr); + end; +str_ref[str_ptr]:=1; incr(str_ptr); str_start[str_ptr]:=pool_ptr; +make_string:=str_ptr-1; +end; + +@ The following subroutine compares string |s| with another string of the +same length that appears in |buffer| starting at position |k|; +the result is |true| if and only if the strings are equal. + +@p function str_eq_buf(@!s:str_number;@!k:integer):boolean; + {test equality of strings} +label not_found; {loop exit} +var @!j: pool_pointer; {running index} +@!result: boolean; {result of comparison} +begin j:=str_start[s]; +while jbuffer[k] then + begin result:=false; goto not_found; + end; + incr(j); incr(k); + end; +result:=true; +not_found: str_eq_buf:=result; +end; + +@ Here is a similar routine, but it compares two strings in the string pool, +and it does not assume that they have the same length. If the first string +is lexicographically greater than, less than, or equal to the second, +the result is respectively positive, negative, or zero. + +@p function str_vs_str(@!s,@!t:str_number):integer; + {test equality of strings} +label exit; +var @!j,@!k: pool_pointer; {running indices} +@!ls,@!lt:integer; {lengths} +@!l:integer; {length remaining to test} +begin ls:=length(s); lt:=length(t); +if ls<=lt then l:=ls@+else l:=lt; +j:=str_start[s]; k:=str_start[t]; +while l>0 do + begin if str_pool[j]<>str_pool[k] then + begin str_vs_str:=str_pool[j]-str_pool[k]; return; + end; + incr(j); incr(k); decr(l); + end; +str_vs_str:=ls-lt; +exit:end; + +@ The initial values of |str_pool|, |str_start|, |pool_ptr|, +and |str_ptr| are computed by the \.{INIMF} program, based in part +on the information that \.{WEB} has output while processing \MF. +@.INIMF@> +@^string pool@> + +@p @!init function get_strings_started:boolean; {initializes the string pool, + but returns |false| if something goes wrong} +label done,exit; +var @!k,@!l:0..255; {small indices or counters} +@!m,@!n:text_char; {characters input from |pool_file|} +@!g:str_number; {garbage} +@!a:integer; {accumulator for check sum} +@!c:boolean; {check sum has been checked} +begin pool_ptr:=0; str_ptr:=0; max_pool_ptr:=0; max_str_ptr:=0; str_start[0]:=0; +@; +@; +exit:end; +tini + +@ @d app_lc_hex(#)==l:=#; + if l<10 then append_char(l+"0")@+else append_char(l-10+"a") + +@= +for k:=0 to 255 do + begin if (@) then + begin append_char("^"); append_char("^"); + if k<@'100 then append_char(k+@'100) + else if k<@'200 then append_char(k-@'100) + else begin app_lc_hex(k div 16); app_lc_hex(k mod 16); + end; + end + else append_char(k); + g:=make_string; str_ref[g]:=max_str_ref; + end + +@ The first 128 strings will contain 95 standard ASCII characters, and the +other 33 characters will be printed in three-symbol form like `\.{\^\^A}' +unless a system-dependent change is made here. Installations that have +an extended character set, where for example |xchr[@'32]=@t\.{\'^^Z\'}@>|, +would like string @'32 to be the single character @'32 instead of the +three characters @'136, @'136, @'132 (\.{\^\^Z}). On the other hand, +even people with an extended character set will want to represent string +@'15 by \.{\^\^M}, since @'15 is ASCII's ``carriage return'' code; the idea is +to produce visible strings instead of tabs or line-feeds or carriage-returns +or bell-rings or characters that are treated anomalously in text files. + +Unprintable characters of codes 128--255 are, similarly, rendered +\.{\^\^80}--\.{\^\^ff}. + +The boolean expression defined here should be |true| unless \MF\ internal +code number~|k| corresponds to a non-troublesome visible symbol in the +local character set. +If character |k| cannot be printed, and |k<@'200|, then character |k+@'100| or +|k-@'100| must be printable; moreover, ASCII codes +|[@'60..@'71, @'136, @'141..@'146]| +must be printable. +@^character set dependencies@> +@^system dependencies@> + +@= + (k<" ")or(k>"~") + +@ When the \.{WEB} system program called \.{TANGLE} processes the \.{MF.WEB} +description that you are now reading, it outputs the \PASCAL\ program +\.{MF.PAS} and also a string pool file called \.{MF.POOL}. The \.{INIMF} +@.WEB@>@.INIMF@> +program reads the latter file, where each string appears as a two-digit decimal +length followed by the string itself, and the information is recorded in +\MF's string memory. + +@= +@!init @!pool_file:alpha_file; {the string-pool file output by \.{TANGLE}} +tini + +@ @d bad_pool(#)==begin wake_up_terminal; write_ln(term_out,#); + a_close(pool_file); get_strings_started:=false; return; + end +@= +name_of_file:=pool_name; {we needn't set |name_length|} +if a_open_in(pool_file) then + begin c:=false; + repeat @; + until c; + a_close(pool_file); get_strings_started:=true; + end +else bad_pool('! I can''t read MF.POOL.') +@.I can't read MF.POOL@> + +@ @= +begin if eof(pool_file) then bad_pool('! MF.POOL has no check sum.'); +@.MF.POOL has no check sum@> +read(pool_file,m,n); {read two digits of string length} +if m='*' then @ +else begin if (xord[m]<"0")or(xord[m]>"9")or@| + (xord[n]<"0")or(xord[n]>"9") then + bad_pool('! MF.POOL line doesn''t begin with two digits.'); +@.MF.POOL line doesn't...@> + l:=xord[m]*10+xord[n]-"0"*11; {compute the length} + if pool_ptr+l+string_vacancies>pool_size then + bad_pool('! You have to increase POOLSIZE.'); +@.You have to increase POOLSIZE@> + for k:=1 to l do + begin if eoln(pool_file) then m:=' '@+else read(pool_file,m); + append_char(xord[m]); + end; + read_ln(pool_file); g:=make_string; str_ref[g]:=max_str_ref; + end; +end + +@ The \.{WEB} operation \.{@@\$} denotes the value that should be at the +end of this \.{MF.POOL} file; any other value means that the wrong pool +file has been loaded. +@^check sum@> + +@= +begin a:=0; k:=1; +loop@+ begin if (xord[n]<"0")or(xord[n]>"9") then + bad_pool('! MF.POOL check sum doesn''t have nine digits.'); +@.MF.POOL check sum...@> + a:=10*a+xord[n]-"0"; + if k=9 then goto done; + incr(k); read(pool_file,n); + end; +done: if a<>@$ then bad_pool('! MF.POOL doesn''t match; TANGLE me again.'); +@.MF.POOL doesn't match@> +c:=true; +end + +@* \[5] On-line and off-line printing. +Messages that are sent to a user's terminal and to the transcript-log file +are produced by several `|print|' procedures. These procedures will +direct their output to a variety of places, based on the setting of +the global variable |selector|, which has the following possible +values: + +\yskip +\hang |term_and_log|, the normal setting, prints on the terminal and on the + transcript file. + +\hang |log_only|, prints only on the transcript file. + +\hang |term_only|, prints only on the terminal. + +\hang |no_print|, doesn't print at all. This is used only in rare cases + before the transcript file is open. + +\hang |pseudo|, puts output into a cyclic buffer that is used + by the |show_context| routine; when we get to that routine we shall discuss + the reasoning behind this curious mode. + +\hang |new_string|, appends the output to the current string in the + string pool. + +\yskip +\noindent The symbolic names `|term_and_log|', etc., have been assigned +numeric codes that satisfy the convenient relations |no_print+1=term_only|, +|no_print+2=log_only|, |term_only+2=log_only+1=term_and_log|. + +Three additional global variables, |tally| and |term_offset| and +|file_offset|, record the number of characters that have been printed +since they were most recently cleared to zero. We use |tally| to record +the length of (possibly very long) stretches of printing; |term_offset| +and |file_offset|, on the other hand, keep track of how many characters +have appeared so far on the current line that has been output to the +terminal or to the transcript file, respectively. + +@d no_print=0 {|selector| setting that makes data disappear} +@d term_only=1 {printing is destined for the terminal only} +@d log_only=2 {printing is destined for the transcript file only} +@d term_and_log=3 {normal |selector| setting} +@d pseudo=4 {special |selector| setting for |show_context|} +@d new_string=5 {printing is deflected to the string pool} +@d max_selector=5 {highest selector setting} + +@= +@!log_file : alpha_file; {transcript of \MF\ session} +@!selector : 0..max_selector; {where to print a message} +@!dig : array[0..22] of 0..15; {digits in a number being output} +@!tally : integer; {the number of characters recently printed} +@!term_offset : 0..max_print_line; + {the number of characters on the current terminal line} +@!file_offset : 0..max_print_line; + {the number of characters on the current file line} +@!trick_buf:array[0..error_line] of ASCII_code; {circular buffer for + pseudoprinting} +@!trick_count: integer; {threshold for pseudoprinting, explained later} +@!first_count: integer; {another variable for pseudoprinting} + +@ @= +selector:=term_only; tally:=0; term_offset:=0; file_offset:=0; + +@ Macro abbreviations for output to the terminal and to the log file are +defined here for convenience. Some systems need special conventions +for terminal output, and it is possible to adhere to those conventions +by changing |wterm|, |wterm_ln|, and |wterm_cr| here. +@^system dependencies@> + +@d wterm(#)==write(term_out,#) +@d wterm_ln(#)==write_ln(term_out,#) +@d wterm_cr==write_ln(term_out) +@d wlog(#)==write(log_file,#) +@d wlog_ln(#)==write_ln(log_file,#) +@d wlog_cr==write_ln(log_file) + +@ To end a line of text output, we call |print_ln|. + +@= +procedure print_ln; {prints an end-of-line} +begin case selector of +term_and_log: begin wterm_cr; wlog_cr; + term_offset:=0; file_offset:=0; + end; +log_only: begin wlog_cr; file_offset:=0; + end; +term_only: begin wterm_cr; term_offset:=0; + end; +no_print,pseudo,new_string: do_nothing; +end; {there are no other cases} +end; {note that |tally| is not affected} + +@ The |print_char| procedure sends one character to the desired destination, +using the |xchr| array to map it into an external character compatible with +|input_ln|. All printing comes through |print_ln| or |print_char|. + +@= +procedure print_char(@!s:ASCII_code); {prints a single character} +begin case selector of +term_and_log: begin wterm(xchr[s]); wlog(xchr[s]); + incr(term_offset); incr(file_offset); + if term_offset=max_print_line then + begin wterm_cr; term_offset:=0; + end; + if file_offset=max_print_line then + begin wlog_cr; file_offset:=0; + end; + end; +log_only: begin wlog(xchr[s]); incr(file_offset); + if file_offset=max_print_line then print_ln; + end; +term_only: begin wterm(xchr[s]); incr(term_offset); + if term_offset=max_print_line then print_ln; + end; +no_print: do_nothing; +pseudo: if tally + +@= +procedure print(@!s:integer); {prints string |s|} +var @!j:pool_pointer; {current character code position} +begin if (s<0)or(s>=str_ptr) then s:="???"; {this can't happen} +@.???@> +if (s<256)and(selector>pseudo) then print_char(s) +else begin j:=str_start[s]; + while j= +procedure slow_print(@!s:integer); {prints string |s|} +var @!j:pool_pointer; {current character code position} +begin if (s<0)or(s>=str_ptr) then s:="???"; {this can't happen} +@.???@> +if (s<256)and(selector>pseudo) then print_char(s) +else begin j:=str_start[s]; + while j= +wterm(banner); +if base_ident=0 then wterm_ln(' (no base preloaded)') +else begin slow_print(base_ident); print_ln; + end; +update_terminal; + +@ The procedure |print_nl| is like |print|, but it makes sure that the +string appears at the beginning of a new line. + +@= +procedure print_nl(@!s:str_number); {prints string |s| at beginning of line} +begin if ((term_offset>0)and(odd(selector)))or@| + ((file_offset>0)and(selector>=log_only)) then print_ln; +print(s); +end; + +@ An array of digits in the range |0..9| is printed by |print_the_digs|. + +@= +procedure print_the_digs(@!k:eight_bits); + {prints |dig[k-1]|$\,\ldots\,$|dig[0]|} +begin while k>0 do + begin decr(k); print_char("0"+dig[k]); + end; +end; + +@ The following procedure, which prints out the decimal representation of a +given integer |n|, has been written carefully so that it works properly +if |n=0| or if |(-n)| would cause overflow. It does not apply |mod| or |div| +to negative arguments, since such operations are not implemented consistently +by all \PASCAL\ compilers. + +@= +procedure print_int(@!n:integer); {prints an integer in decimal form} +var k:0..23; {index to current digit; we assume that $|n|<10^{23}$} +@!m:integer; {used to negate |n| in possibly dangerous cases} +begin k:=0; +if n<0 then + begin print_char("-"); + if n>-100000000 then negate(n) + else begin m:=-1-n; n:=m div 10; m:=(m mod 10)+1; k:=1; + if m<10 then dig[0]:=m + else begin dig[0]:=0; incr(n); + end; + end; + end; +repeat dig[k]:=n mod 10; n:=n div 10; incr(k); +until n=0; +print_the_digs(k); +end; + +@ \MF\ also makes use of a trivial procedure to print two digits. The +following subroutine is usually called with a parameter in the range |0<=n<=99|. + +@p procedure print_dd(@!n:integer); {prints two least significant digits} +begin n:=abs(n) mod 100; print_char("0"+(n div 10)); +print_char("0"+(n mod 10)); +end; + +@ Here is a procedure that asks the user to type a line of input, +assuming that the |selector| setting is either |term_only| or |term_and_log|. +The input is placed into locations |first| through |last-1| of the +|buffer| array, and echoed on the transcript file if appropriate. + +This procedure is never called when |interaction +term_offset:=0; {the user's line ended with \<\rm return>} +decr(selector); {prepare to echo the input} +if last<>first then for k:=first to last-1 do print(buffer[k]); +print_ln; buffer[last]:="%"; incr(selector); {restore previous status} +end; + +@* \[6] Reporting errors. +When something anomalous is detected, \MF\ typically does something like this: +$$\vbox{\halign{#\hfil\cr +|print_err("Something anomalous has been detected");|\cr +|help3("This is the first line of my offer to help.")|\cr +|("This is the second line. I'm trying to")|\cr +|("explain the best way for you to proceed.");|\cr +|error;|\cr}}$$ +A two-line help message would be given using |help2|, etc.; these informal +helps should use simple vocabulary that complements the words used in the +official error message that was printed. (Outside the U.S.A., the help +messages should preferably be translated into the local vernacular. Each +line of help is at most 60 characters long, in the present implementation, +so that |max_print_line| will not be exceeded.) + +The |print_err| procedure supplies a `\.!' before the official message, +and makes sure that the terminal is awake if a stop is going to occur. +The |error| procedure supplies a `\..' after the official message, then it +shows the location of the error; and if |interaction=error_stop_mode|, +it also enters into a dialog with the user, during which time the help +message may be printed. +@^system dependencies@> + +@ The global variable |interaction| has four settings, representing increasing +amounts of user interaction: + +@d batch_mode=0 {omits all stops and omits terminal output} +@d nonstop_mode=1 {omits all stops} +@d scroll_mode=2 {omits error stops} +@d error_stop_mode=3 {stops at every opportunity to interact} +@d print_err(#)==begin if interaction=error_stop_mode then wake_up_terminal; + print_nl("! "); print(#); +@.!\relax@> + end + +@= +@!interaction:batch_mode..error_stop_mode; {current level of interaction} + +@ @=interaction:=error_stop_mode; + +@ \MF\ is careful not to call |error| when the print |selector| setting +might be unusual. The only possible values of |selector| at the time of +error messages are + +\yskip\hang|no_print| (when |interaction=batch_mode| + and |log_file| not yet open); + +\hang|term_only| (when |interaction>batch_mode| and |log_file| not yet open); + +\hang|log_only| (when |interaction=batch_mode| and |log_file| is open); + +\hang|term_and_log| (when |interaction>batch_mode| and |log_file| is open). + +@= +if interaction=batch_mode then selector:=no_print@+else selector:=term_only + +@ A global variable |deletions_allowed| is set |false| if the |get_next| +routine is active when |error| is called; this ensures that |get_next| +will never be called recursively. +@^recursion@> + +The global variable |history| records the worst level of error that +has been detected. It has four possible values: |spotless|, |warning_issued|, +|error_message_issued|, and |fatal_error_stop|. + +Another global variable, |error_count|, is increased by one when an +|error| occurs without an interactive dialog, and it is reset to zero at +the end of every statement. If |error_count| reaches 100, \MF\ decides +that there is no point in continuing further. + +@d spotless=0 {|history| value when nothing has been amiss yet} +@d warning_issued=1 {|history| value when |begin_diagnostic| has been called} +@d error_message_issued=2 {|history| value when |error| has been called} +@d fatal_error_stop=3 {|history| value when termination was premature} + +@= +@!deletions_allowed:boolean; {is it safe for |error| to call |get_next|?} +@!history:spotless..fatal_error_stop; {has the source input been clean so far?} +@!error_count:-1..100; {the number of scrolled errors since the + last statement ended} + +@ The value of |history| is initially |fatal_error_stop|, but it will +be changed to |spotless| if \MF\ survives the initialization process. + +@= +deletions_allowed:=true; error_count:=0; {|history| is initialized elsewhere} + +@ Since errors can be detected almost anywhere in \MF, we want to declare the +error procedures near the beginning of the program. But the error procedures +in turn use some other procedures, which need to be declared |forward| +before we get to |error| itself. + +It is possible for |error| to be called recursively if some error arises +when |get_next| is being used to delete a token, and/or if some fatal error +occurs while \MF\ is trying to fix a non-fatal one. But such recursion +@^recursion@> +is never more than two levels deep. + +@= +procedure@?normalize_selector; forward;@t\2@>@/ +procedure@?get_next; forward;@t\2@>@/ +procedure@?term_input; forward;@t\2@>@/ +procedure@?show_context; forward;@t\2@>@/ +procedure@?begin_file_reading; forward;@t\2@>@/ +procedure@?open_log_file; forward;@t\2@>@/ +procedure@?close_files_and_terminate; forward;@t\2@>@/ +procedure@?clear_for_error_prompt; forward;@t\2@>@/ +@t\4\hskip-\fontdimen2\font@>@;@+@!debug@+procedure@?debug_help; + forward;@;@+gubed@;@/ +@t\4@>@ + +@ Individual lines of help are recorded in the array |help_line|, which +contains entries in positions |0..(help_ptr-1)|. They should be printed +in reverse order, i.e., with |help_line[0]| appearing last. + +@d hlp1(#)==help_line[0]:=#;@+end +@d hlp2(#)==help_line[1]:=#; hlp1 +@d hlp3(#)==help_line[2]:=#; hlp2 +@d hlp4(#)==help_line[3]:=#; hlp3 +@d hlp5(#)==help_line[4]:=#; hlp4 +@d hlp6(#)==help_line[5]:=#; hlp5 +@d help0==help_ptr:=0 {sometimes there might be no help} +@d help1==@+begin help_ptr:=1; hlp1 {use this with one help line} +@d help2==@+begin help_ptr:=2; hlp2 {use this with two help lines} +@d help3==@+begin help_ptr:=3; hlp3 {use this with three help lines} +@d help4==@+begin help_ptr:=4; hlp4 {use this with four help lines} +@d help5==@+begin help_ptr:=5; hlp5 {use this with five help lines} +@d help6==@+begin help_ptr:=6; hlp6 {use this with six help lines} + +@= +@!help_line:array[0..5] of str_number; {helps for the next |error|} +@!help_ptr:0..6; {the number of help lines present} +@!use_err_help:boolean; {should the |err_help| string be shown?} +@!err_help:str_number; {a string set up by \&{errhelp}} + +@ @= +help_ptr:=0; use_err_help:=false; err_help:=0; + +@ The |jump_out| procedure just cuts across all active procedure levels and +goes to |end_of_MF|. This is the only nontrivial |@!goto| statement in the +whole program. It is used when there is no recovery from a particular error. + +Some \PASCAL\ compilers do not implement non-local |goto| statements. +@^system dependencies@> +In such cases the body of |jump_out| should simply be +`|close_files_and_terminate|;\thinspace' followed by a call on some system +procedure that quietly terminates the program. + +@= +procedure jump_out; +begin goto end_of_MF; +end; + +@ Here now is the general |error| routine. + +@= +procedure error; {completes the job of error reporting} +label continue,exit; +var @!c:ASCII_code; {what the user types} +@!s1,@!s2,@!s3:integer; {used to save global variables when deleting tokens} +@!j:pool_pointer; {character position being printed} +begin if history; +incr(error_count); +if error_count=100 then + begin print_nl("(That makes 100 errors; please try again.)"); +@.That makes 100 errors...@> + history:=fatal_error_stop; jump_out; + end; +@; +exit:end; + +@ @= +loop@+begin continue: clear_for_error_prompt; prompt_input("? "); +@.?\relax@> + if last=first then return; + c:=buffer[first]; + if c>="a" then c:=c+"A"-"a"; {convert to uppercase} + @; + end + +@ It is desirable to provide an `\.E' option here that gives the user +an easy way to return from \MF\ to the system editor, with the offending +line ready to be edited. But such an extension requires some system +wizardry, so the present implementation simply types out the name of the +file that should be +edited and the relevant line number. +@^system dependencies@> + +There is a secret `\.D' option available when the debugging routines haven't +been commented~out. +@^debugging@> + +@= +case c of +"0","1","2","3","4","5","6","7","8","9": if deletions_allowed then + @; +@t\4\4@>@;@+@!debug "D":begin debug_help;goto continue;@+end;@+gubed@/ +"E": if file_ptr>0 then + begin print_nl("You want to edit file "); +@.You want to edit file x@> + slow_print(input_stack[file_ptr].name_field); + print(" at line "); print_int(line);@/ + interaction:=scroll_mode; jump_out; + end; +"H": @; +"I":@; +"Q","R","S":@; +"X":begin interaction:=scroll_mode; jump_out; + end; +othercases do_nothing +endcases;@/ +@ + +@ @= +begin print("Type to proceed, S to scroll future error messages,");@/ +@.Type to proceed...@> +print_nl("R to run without stopping, Q to run quietly,");@/ +print_nl("I to insert something, "); +if file_ptr>0 then print("E to edit your file,"); +if deletions_allowed then + print_nl("1 or ... or 9 to ignore the next 1 to 9 tokens of input,"); +print_nl("H for help, X to quit."); +end + +@ Here the author of \MF\ apologizes for making use of the numerical +relation between |"Q"|, |"R"|, |"S"|, and the desired interaction settings +|batch_mode|, |nonstop_mode|, |scroll_mode|. +@^Knuth, Donald Ervin@> + +@= +begin error_count:=0; interaction:=batch_mode+c-"Q"; +print("OK, entering "); +case c of +"Q":begin print("batchmode"); decr(selector); + end; +"R":print("nonstopmode"); +"S":print("scrollmode"); +end; {there are no other cases} +print("..."); print_ln; update_terminal; return; +end + +@ When the following code is executed, |buffer[(first+1)..(last-1)]| may +contain the material inserted by the user; otherwise another prompt will +be given. In order to understand this part of the program fully, you need +to be familiar with \MF's input stacks. + +@= +begin begin_file_reading; {enter a new syntactic level for terminal input} +if last>first+1 then + begin loc:=first+1; buffer[first]:=" "; + end +else begin prompt_input("insert>"); loc:=first; +@.insert>@> + end; +first:=last+1; cur_input.limit_field:=last; return; +end + +@ We allow deletion of up to 99 tokens at a time. + +@= +begin s1:=cur_cmd; s2:=cur_mod; s3:=cur_sym; OK_to_interrupt:=false; +if (last>first+1) and (buffer[first+1]>="0")and(buffer[first+1]<="9") then + c:=c*10+buffer[first+1]-"0"*11 +else c:=c-"0"; +while c>0 do + begin get_next; {one-level recursive call of |error| is possible} + @; + decr(c); + end; +cur_cmd:=s1; cur_mod:=s2; cur_sym:=s3; OK_to_interrupt:=true; +help2("I have just deleted some text, as you asked.")@/ +("You can now delete more, or insert, or whatever."); +show_context; goto continue; +end + +@ @= +begin if use_err_help then + begin @; + use_err_help:=false; + end +else begin if help_ptr=0 then + help2("Sorry, I don't know how to help in this situation.")@/ + @t\kern1em@>("Maybe you should try asking a human?"); + repeat decr(help_ptr); print(help_line[help_ptr]); print_ln; + until help_ptr=0; + end; +help4("Sorry, I already gave what help I could...")@/ + ("Maybe you should try asking a human?")@/ + ("An error might have occurred before I noticed any problems.")@/ + ("``If all else fails, read the instructions.''");@/ +goto continue; +end + +@ @= +j:=str_start[err_help]; +while jsi("%") then print(so(str_pool[j])) + else if j+1=str_start[err_help+1] then print_ln + else if str_pool[j+1]<>si("%") then print_ln + else begin incr(j); print_char("%"); + end; + incr(j); + end + +@ @= +if interaction>batch_mode then decr(selector); {avoid terminal output} +if use_err_help then + begin print_nl(""); + @; + end +else while help_ptr>0 do + begin decr(help_ptr); print_nl(help_line[help_ptr]); + end; +print_ln; +if interaction>batch_mode then incr(selector); {re-enable terminal output} +print_ln + +@ In anomalous cases, the print selector might be in an unknown state; +the following subroutine is called to fix things just enough to keep +running a bit longer. + +@p procedure normalize_selector; +begin if log_opened then selector:=term_and_log +else selector:=term_only; +if job_name=0 then open_log_file; +if interaction=batch_mode then decr(selector); +end; + +@ The following procedure prints \MF's last words before dying. + +@d succumb==begin if interaction=error_stop_mode then + interaction:=scroll_mode; {no more interaction} + if log_opened then error; + @!debug if interaction>batch_mode then debug_help;@;@+gubed@;@/ + history:=fatal_error_stop; jump_out; {irrecoverable error} + end + +@= +procedure fatal_error(@!s:str_number); {prints |s|, and that's it} +begin normalize_selector;@/ +print_err("Emergency stop"); help1(s); succumb; +@.Emergency stop@> +end; + +@ Here is the most dreaded error message. + +@= +procedure overflow(@!s:str_number;@!n:integer); {stop due to finiteness} +begin normalize_selector; +print_err("METAFONT capacity exceeded, sorry ["); +@.METAFONT capacity exceeded ...@> +print(s); print_char("="); print_int(n); print_char("]"); +help2("If you really absolutely need more capacity,")@/ + ("you can ask a wizard to enlarge me."); +succumb; +end; + +@ The program might sometime run completely amok, at which point there is +no choice but to stop. If no previous error has been detected, that's bad +news; a message is printed that is really intended for the \MF\ +maintenance person instead of the user (unless the user has been +particularly diabolical). The index entries for `this can't happen' may +help to pinpoint the problem. +@^dry rot@> + +@= +procedure confusion(@!s:str_number); + {consistency check violated; |s| tells where} +begin normalize_selector; +if history + help1("I'm broken. Please show this to someone who can fix can fix"); + end +else begin print_err("I can't go on meeting you like this"); +@.I can't go on...@> + help2("One of your faux pas seems to have wounded me deeply...")@/ + ("in fact, I'm barely conscious. Please fix it and try again."); + end; +succumb; +end; + +@ Users occasionally want to interrupt \MF\ while it's running. +If the \PASCAL\ runtime system allows this, one can implement +a routine that sets the global variable |interrupt| to some nonzero value +when such an interrupt is signalled. Otherwise there is probably at least +a way to make |interrupt| nonzero using the \PASCAL\ debugger. +@^system dependencies@> +@^debugging@> + +@d check_interrupt==begin if interrupt<>0 then pause_for_instructions; + end + +@= +@!interrupt:integer; {should \MF\ pause for instructions?} +@!OK_to_interrupt:boolean; {should interrupts be observed?} + +@ @= +interrupt:=0; OK_to_interrupt:=true; + +@ When an interrupt has been detected, the program goes into its +highest interaction level and lets the user have the full flexibility of +the |error| routine. \MF\ checks for interrupts only at times when it is +safe to do this. + +@p procedure pause_for_instructions; +begin if OK_to_interrupt then + begin interaction:=error_stop_mode; + if (selector=log_only)or(selector=no_print) then + incr(selector); + print_err("Interruption"); +@.Interruption@> + help3("You rang?")@/ + ("Try to insert some instructions for me (e.g.,`I show x'),")@/ + ("unless you just want to quit by typing `X'."); + deletions_allowed:=false; error; deletions_allowed:=true; + interrupt:=0; + end; +end; + +@ Many of \MF's error messages state that a missing token has been +inserted behind the scenes. We can save string space and program space +by putting this common code into a subroutine. + +@p procedure missing_err(@!s:str_number); +begin print_err("Missing `"); print(s); print("' has been inserted"); +@.Missing...inserted@> +end; + +@* \[7] Arithmetic with scaled numbers. +The principal computations performed by \MF\ are done entirely in terms of +integers less than $2^{31}$ in magnitude; thus, the arithmetic specified in this +program can be carried out in exactly the same way on a wide variety of +computers, including some small ones. +@^small computers@> + +But \PASCAL\ does not define the @!|div| +operation in the case of negative dividends; for example, the result of +|(-2*n-1) div 2| is |-(n+1)| on some computers and |-n| on others. +There are two principal types of arithmetic: ``translation-preserving,'' +in which the identity |(a+q*b)div b=(a div b)+q| is valid; and +``negation-preserving,'' in which |(-a)div b=-(a div b)|. This leads to +two \MF s, which can produce different results, although the differences +should be negligible when the language is being used properly. +The \TeX\ processor has been defined carefully so that both varieties +of arithmetic will produce identical output, but it would be too +inefficient to constrain \MF\ in a similar way. + +@d el_gordo == @'17777777777 {$2^{31}-1$, the largest value that \MF\ likes} + +@ One of \MF's most common operations is the calculation of +$\lfloor{a+b\over2}\rfloor$, +the midpoint of two given integers |a| and~|b|. The only decent way to do +this in \PASCAL\ is to write `|(a+b) div 2|'; but on most machines it is +far more efficient to calculate `|(a+b)| right shifted one bit'. + +Therefore the midpoint operation will always be denoted by `|half(a+b)|' +in this program. If \MF\ is being implemented with languages that permit +binary shifting, the |half| macro should be changed to make this operation +as efficient as possible. + +@d half(#)==(#) div 2 + +@ A single computation might use several subroutine calls, and it is +desirable to avoid producing multiple error messages in case of arithmetic +overflow. So the routines below set the global variable |arith_error| to |true| +instead of reporting errors directly to the user. +@^overflow in arithmetic@> + +@= +@!arith_error:boolean; {has arithmetic overflow occurred recently?} + +@ @= +arith_error:=false; + +@ At crucial points the program will say |check_arith|, to test if +an arithmetic error has been detected. + +@d check_arith==begin if arith_error then clear_arith;@+end + +@p procedure clear_arith; +begin print_err("Arithmetic overflow"); +@.Arithmetic overflow@> +help4("Uh, oh. A little while ago one of the quantities that I was")@/ + ("computing got too large, so I'm afraid your answers will be")@/ + ("somewhat askew. You'll probably have to adopt different")@/ + ("tactics next time. But I shall try to carry on anyway."); +error; arith_error:=false; +end; + +@ Addition is not always checked to make sure that it doesn't overflow, +but in places where overflow isn't too unlikely the |slow_add| routine +is used. + +@p function slow_add(@!x,@!y:integer):integer; +begin if x>=0 then + if y<=el_gordo-x then slow_add:=x+y + else begin arith_error:=true; slow_add:=el_gordo; + end +else if -y<=el_gordo+x then slow_add:=x+y + else begin arith_error:=true; slow_add:=-el_gordo; + end; +end; + +@ Fixed-point arithmetic is done on {\sl scaled integers\/} that are multiples +of $2^{-16}$. In other words, a binary point is assumed to be sixteen bit +positions from the right end of a binary computer word. + +@d quarter_unit == @'40000 {$2^{14}$, represents 0.250000} +@d half_unit == @'100000 {$2^{15}$, represents 0.50000} +@d three_quarter_unit == @'140000 {$3\cdot2^{14}$, represents 0.75000} +@d unity == @'200000 {$2^{16}$, represents 1.00000} +@d two == @'400000 {$2^{17}$, represents 2.00000} +@d three == @'600000 {$2^{17}+2^{16}$, represents 3.00000} + +@= +@!scaled = integer; {this type is used for scaled integers} +@!small_number=0..63; {this type is self-explanatory} + +@ The following function is used to create a scaled integer from a given decimal +fraction $(.d_0d_1\ldots d_{k-1})$, where |0<=k<=17|. The digit $d_i$ is +given in |dig[i]|, and the calculation produces a correctly rounded result. + +@p function round_decimals(@!k:small_number) : scaled; + {converts a decimal fraction} +var @!a:integer; {the accumulator} +begin a:=0; +while k>0 do + begin decr(k); a:=(a+dig[k]*two) div 10; + end; +round_decimals:=half(a+1); +end; + +@ Conversely, here is a procedure analogous to |print_int|. If the output +of this procedure is subsequently read by \MF\ and converted by the +|round_decimals| routine above, it turns out that the original value will +be reproduced exactly. A decimal point is printed only if the value is +not an integer. If there is more than one way to print the result with +the optimum number of digits following the decimal point, the closest +possible value is given. + +The invariant relation in the \&{repeat} loop is that a sequence of +decimal digits yet to be printed will yield the original number if and only if +they form a fraction~$f$ in the range $s-\delta\L10\cdot2^{16}f= +procedure print_scaled(@!s:scaled); {prints scaled real, rounded to five + digits} +var @!delta:scaled; {amount of allowable inaccuracy} +begin if s<0 then + begin print_char("-"); negate(s); {print the sign, if negative} + end; +print_int(s div unity); {print the integer part} +s:=10*(s mod unity)+5; +if s<>5 then + begin delta:=10; print_char("."); + repeat if delta>unity then + s:=s+@'100000-(delta div 2); {round the final digit} + print_char("0"+(s div unity)); s:=10*(s mod unity); delta:=delta*10; + until s<=delta; + end; +end; + +@ We often want to print two scaled quantities in parentheses, +separated by a comma. + +@= +procedure print_two(@!x,@!y:scaled); {prints `|(x,y)|'} +begin print_char("("); print_scaled(x); print_char(","); print_scaled(y); +print_char(")"); +end; + +@ The |scaled| quantities in \MF\ programs are generally supposed to be +less than $2^{12}$ in absolute value, so \MF\ does much of its internal +arithmetic with 28~significant bits of precision. A |fraction| denotes +a scaled integer whose binary point is assumed to be 28 bit positions +from the right. + +@d fraction_half==@'1000000000 {$2^{27}$, represents 0.50000000} +@d fraction_one==@'2000000000 {$2^{28}$, represents 1.00000000} +@d fraction_two==@'4000000000 {$2^{29}$, represents 2.00000000} +@d fraction_three==@'6000000000 {$3\cdot2^{28}$, represents 3.00000000} +@d fraction_four==@'10000000000 {$2^{30}$, represents 4.00000000} + +@= +@!fraction=integer; {this type is used for scaled fractions} + +@ In fact, the two sorts of scaling discussed above aren't quite +sufficient; \MF\ has yet another, used internally to keep track of angles +in units of $2^{-20}$ degrees. + +@d forty_five_deg==@'264000000 {$45\cdot2^{20}$, represents $45^\circ$} +@d ninety_deg==@'550000000 {$90\cdot2^{20}$, represents $90^\circ$} +@d one_eighty_deg==@'1320000000 {$180\cdot2^{20}$, represents $180^\circ$} +@d three_sixty_deg==@'2640000000 {$360\cdot2^{20}$, represents $360^\circ$} + +@= +@!angle=integer; {this type is used for scaled angles} + +@ The |make_fraction| routine produces the |fraction| equivalent of +|p/q|, given integers |p| and~|q|; it computes the integer +$f=\lfloor2^{28}p/q+{1\over2}\rfloor$, when $p$ and $q$ are +positive. If |p| and |q| are both of the same scaled type |t|, +the ``type relation'' |make_fraction(t,t)=fraction| is valid; +and it's also possible to use the subroutine ``backwards,'' using +the relation |make_fraction(t,fraction)=t| between scaled types. + +If the result would have magnitude $2^{31}$ or more, |make_fraction| +sets |arith_error:=true|. Most of \MF's internal computations have +been designed to avoid this sort of error. + +Notice that if 64-bit integer arithmetic were available, +we could simply compute |(@t$(2^{29}$@>*p+q)div (2*q)|. +But when we are restricted to \PASCAL's 32-bit arithmetic we +must either resort to multiple-precision maneuvering +or use a simple but slow iteration. The multiple-precision technique +would be about three times faster than the code adopted here, but it +would be comparatively long and tricky, involving about sixteen +additional multiplications and divisions. + +This operation is part of \MF's ``inner loop''; indeed, it will +consume nearly 10\pct! of the running time (exclusive of input and output) +if the code below is left unchanged. A machine-dependent recoding +will therefore make \MF\ run faster. The present implementation +is highly portable, but slow; it avoids multiplication and division +except in the initial stage. System wizards should be careful to +replace it with a routine that is guaranteed to produce identical +results in all cases. +@^system dependencies@> + +As noted below, a few more routines should also be replaced by machine-dependent +code, for efficiency. But when a procedure is not part of the ``inner loop,'' +such changes aren't advisable; simplicity and robustness are +preferable to trickery, unless the cost is too high. +@^inner loop@> + +@p function make_fraction(@!p,@!q:integer):fraction; +var @!f:integer; {the fraction bits, with a leading 1 bit} +@!n:integer; {the integer part of $\vert p/q\vert$} +@!negative:boolean; {should the result be negated?} +@!be_careful:integer; {disables certain compiler optimizations} +begin if p>=0 then negative:=false +else begin negate(p); negative:=true; + end; +if q<=0 then + begin debug if q=0 then confusion("/");@;@+gubed@;@/ +@:this can't happen /}{\quad \./@> + negate(q); negative:=not negative; + end; +n:=p div q; p:=p mod q; +if n>=8 then + begin arith_error:=true; + if negative then make_fraction:=-el_gordo@+else make_fraction:=el_gordo; + end +else begin n:=(n-1)*fraction_one; + @; + if negative then make_fraction:=-(f+n)@+else make_fraction:=f+n; + end; +end; + +@ The |repeat| loop here preserves the following invariant relations +between |f|, |p|, and~|q|: +(i)~|0<=p + +@= +f:=1; +repeat be_careful:=p-q; p:=be_careful+p; +if p>=0 then f:=f+f+1 +else begin double(f); p:=p+q; + end; +until f>=fraction_one; +be_careful:=p-q; +if be_careful+p>=0 then incr(f) + +@ The dual of |make_fraction| is |take_fraction|, which multiplies a +given integer~|q| by a fraction~|f|. When the operands are positive, it +computes $p=\lfloor qf/2^{28}+{1\over2}\rfloor$, a symmetric function +of |q| and~|f|. + +This routine is even more ``inner loopy'' than |make_fraction|; +the present implementation consumes almost 20\pct! of \MF's computation +time during typical jobs, so a machine-language or 64-bit +substitute is advisable. +@^inner loop@> @^system dependencies@> + +@p function take_fraction(@!q:integer;@!f:fraction):integer; +var @!p:integer; {the fraction so far} +@!negative:boolean; {should the result be negated?} +@!n:integer; {additional multiple of $q$} +@!be_careful:integer; {disables certain compiler optimizations} +begin @=0| and |q>=0|@>; +if f; +be_careful:=n-el_gordo; +if be_careful+p>0 then + begin arith_error:=true; n:=el_gordo-p; + end; +if negative then take_fraction:=-(n+p) +else take_fraction:=n+p; +end; + +@ @=0| and |q>=0|@>= +if f>=0 then negative:=false +else begin negate(f); negative:=true; + end; +if q<0 then + begin negate(q); negative:=not negative; + end; + +@ The invariant relations in this case are (i)~$\lfloor(qf+p)/2^k\rfloor +=\lfloor qf_0/2^{28}+{1\over2}\rfloor$, where $k$ is an integer and +$f_0$ is the original value of~$f$; (ii)~$2^k\L f<2^{k+1}$. +@^inner loop@> + +@= +p:=fraction_half; {that's $2^{27}$; the invariants hold now with $k=28$} +if q + +@p function take_scaled(@!q:integer;@!f:scaled):integer; +var @!p:integer; {the fraction so far} +@!negative:boolean; {should the result be negated?} +@!n:integer; {additional multiple of $q$} +@!be_careful:integer; {disables certain compiler optimizations} +begin @=0| and |q>=0|@>; +if f; +be_careful:=n-el_gordo; +if be_careful+p>0 then + begin arith_error:=true; n:=el_gordo-p; + end; +if negative then take_scaled:=-(n+p) +else take_scaled:=n+p; +end; + +@ @= +p:=half_unit; {that's $2^{15}$; the invariants hold now with $k=16$} +@^inner loop@> +if q=0 then negative:=false +else begin negate(p); negative:=true; + end; +if q<=0 then + begin debug if q=0 then confusion("/");@+gubed@;@/ +@:this can't happen /}{\quad \./@> + negate(q); negative:=not negative; + end; +n:=p div q; p:=p mod q; +if n>=@'100000 then + begin arith_error:=true; + if negative then make_scaled:=-el_gordo@+else make_scaled:=el_gordo; + end +else begin n:=(n-1)*unity; + @; + if negative then make_scaled:=-(f+n)@+else make_scaled:=f+n; + end; +end; + +@ @= +f:=1; +repeat be_careful:=p-q; p:=be_careful+p; +if p>=0 then f:=f+f+1 +else begin double(f); p:=p+q; + end; +until f>=unity; +be_careful:=p-q; +if be_careful+p>=0 then incr(f) + +@ Here is a typical example of how the routines above can be used. +It computes the function +$${1\over3\tau}f(\theta,\phi)= +{\tau^{-1}\bigl(2+\sqrt2\,(\sin\theta-{1\over16}\sin\phi) + (\sin\phi-{1\over16}\sin\theta)(\cos\theta-\cos\phi)\bigr)\over +3\,\bigl(1+{1\over2}(\sqrt5-1)\cos\theta+{1\over2}(3-\sqrt5\,)\cos\phi\bigr)},$$ +where $\tau$ is a |scaled| ``tension'' parameter. This is \MF's magic +fudge factor for placing the first control point of a curve that starts +at an angle $\theta$ and ends at an angle $\phi$ from the straight path. +(Actually, if the stated quantity exceeds 4, \MF\ reduces it to~4.) + +The trigonometric quantity to be multiplied by $\sqrt2$ is less than $\sqrt2$. +(It's a sum of eight terms whose absolute values can be bounded using +relations such as $\sin\theta\cos\theta\L{1\over2}$.) Thus the numerator +is positive; and since the tension $\tau$ is constrained to be at least +$3\over4$, the numerator is less than $16\over3$. The denominator is +nonnegative and at most~6. Hence the fixed-point calculations below +are guaranteed to stay within the bounds of a 32-bit computer word. + +The angles $\theta$ and $\phi$ are given implicitly in terms of |fraction| +arguments |st|, |ct|, |sf|, and |cf|, representing $\sin\theta$, $\cos\theta$, +$\sin\phi$, and $\cos\phi$, respectively. + +@p function velocity(@!st,@!ct,@!sf,@!cf:fraction;@!t:scaled):fraction; +var @!acc,@!num,@!denom:integer; {registers for intermediate calculations} +begin acc:=take_fraction(st-(sf div 16), sf-(st div 16)); +acc:=take_fraction(acc,ct-cf); +num:=fraction_two+take_fraction(acc,379625062); + {$2^{28}\sqrt2\approx379625062.497$} +denom:=fraction_three+take_fraction(ct,497706707)+take_fraction(cf,307599661); + {$3\cdot2^{27}\cdot(\sqrt5-1)\approx497706706.78$ and + $3\cdot2^{27}\cdot(3-\sqrt5\,)\approx307599661.22$} +if t<>unity then num:=make_scaled(num,t); + {|make_scaled(fraction,scaled)=fraction|} +if num div 4>=denom then velocity:=fraction_four +else velocity:=make_fraction(num,denom); +end; + +@ The following somewhat different subroutine tests rigorously if $ab$ is +greater than, equal to, or less than~$cd$, +given integers $(a,b,c,d)$. In most cases a quick decision is reached. +The result is $+1$, 0, or~$-1$ in the three respective cases. + +@d return_sign(#)==begin ab_vs_cd:=#; return; + end + +@p function ab_vs_cd(@!a,b,c,d:integer):integer; +label exit; +var @!q,@!r:integer; {temporary registers} +begin @=0|, |b,d>0|@>; +loop@+ begin q := a div d; r := c div b; + if q<>r then + if q>r then return_sign(1)@+else return_sign(-1); + q := a mod d; r := c mod b; + if r=0 then + if q=0 then return_sign(0)@+else return_sign(1); + if q=0 then return_sign(-1); + a:=b; b:=q; c:=d; d:=r; + end; {now |a>d>0| and |c>b>0|} +exit:end; + +@ @= +if a<0 then + begin negate(a); negate(b); + end; +if c<0 then + begin negate(c); negate(d); + end; +if d<=0 then + begin if b>=0 then + if ((a=0)or(b=0))and((c=0)or(d=0)) then return_sign(0) + else return_sign(1); + if d=0 then + if a=0 then return_sign(0)@+else return_sign(-1); + q:=a; a:=c; c:=q; q:=-b; b:=-d; d:=q; + end +else if b<=0 then + begin if b<0 then if a>0 then return_sign(-1); + if c=0 then return_sign(0) else return_sign(-1); + end + +@ We conclude this set of elementary routines with some simple rounding +and truncation operations that are coded in a machine-independent fashion. +The routines are slightly complicated because we want them to work +without overflow whenever $-2^{31}\L x<2^{31}$. + +@p function floor_scaled(@!x:scaled):scaled; + {$2^{16}\lfloor x/2^{16}\rfloor$} +var @!be_careful:integer; {temporary register} +begin if x>=0 then floor_scaled:=x-(x mod unity) +else begin be_careful:=x+1; + floor_scaled:=x+((-be_careful) mod unity)+1-unity; + end; +end; +@# +function floor_unscaled(@!x:scaled):integer; + {$\lfloor x/2^{16}\rfloor$} +var @!be_careful:integer; {temporary register} +begin if x>=0 then floor_unscaled:=x div unity +else begin be_careful:=x+1; floor_unscaled:=-(1+((-be_careful) div unity)); + end; +end; +@# +function round_unscaled(@!x:scaled):integer; + {$\lfloor x/2^{16}+.5\rfloor$} +var @!be_careful:integer; {temporary register} +begin if x>=half_unit then round_unscaled:=1+((x-half_unit) div unity) +else if x>=-half_unit then round_unscaled:=0 +else begin be_careful:=x+1; + round_unscaled:=-(1+((-be_careful-half_unit) div unity)); + end; +end; +@# +function round_fraction(@!x:fraction):scaled; + {$\lfloor x/2^{12}+.5\rfloor$} +var @!be_careful:integer; {temporary register} +begin if x>=2048 then round_fraction:=1+((x-2048) div 4096) +else if x>=-2048 then round_fraction:=0 +else begin be_careful:=x+1; + round_fraction:=-(1+((-be_careful-2048) div 4096)); + end; +end; + +@* \[8] Algebraic and transcendental functions. +\MF\ computes all of the necessary special functions from scratch, without +relying on |real| arithmetic or system subroutines for sines, cosines, etc. + +@ To get the square root of a |scaled| number |x|, we want to calculate +$s=\lfloor 2^8\!\sqrt x +{1\over2}\rfloor$. If $x>0$, this is the unique +integer such that $2^{16}x-s\L s^2<2^{16}x+s$. The following subroutine +determines $s$ by an iterative method that maintains the invariant +relations $x=2^{46-2k}x_0\bmod 2^{30}$, $0 +else begin k:=23; q:=2; + while x|\unskip} + begin decr(k); x:=x+x+x+x; + end; + if x; + until k=0; + square_rt:=half(q); + end; +end; + +@ @= +begin if x<0 then + begin print_err("Square root of "); +@.Square root...replaced by 0@> + print_scaled(x); print(" has been replaced by 0"); + help2("Since I don't take square roots of negative numbers,")@/ + ("I'm zeroing this one. Proceed, with fingers crossed."); + error; + end; +square_rt:=0; +end + +@ @= +double(x); double(y); +if x>=fraction_four then {note that |fraction_four=@t$2^{30}$@>|} + begin x:=x-fraction_four; incr(y); + end; +double(x); y:=y+y-q; double(q); +if x>=fraction_four then + begin x:=x-fraction_four; incr(y); + end; +if y>q then + begin y:=y-q; q:=q+2; + end +else if y<=0 then + begin q:=q-2; y:=y+q; + end; +decr(k) + +@ Pythagorean addition $\psqrt{a^2+b^2}$ is implemented by an elegant +iterative scheme due to Cleve Moler and Donald Morrison [{\sl IBM Journal +@^Moler, Cleve Barry@> +@^Morrison, Donald Ross@> +of Research and Development\/ \bf27} (1983), 577--581]. It modifies |a| and~|b| +in such a way that their Pythagorean sum remains invariant, while the +smaller argument decreases. + +@p function pyth_add(@!a,@!b:integer):integer; +label done; +var @!r:fraction; {register used to transform |a| and |b|} +@!big:boolean; {is the result dangerously near $2^{31}$?} +begin a:=abs(a); b:=abs(b); +if a0 then + begin if a; + if big then + if a= +loop@+ begin r:=make_fraction(b,a); + r:=take_fraction(r,r); {now $r\approx b^2/a^2$} + if r=0 then goto done; + r:=make_fraction(r,fraction_four+r); + a:=a+take_fraction(a+a,r); b:=take_fraction(b,r); + end; +done: + +@ Here is a similar algorithm for $\psqrt{a^2-b^2}$. +It converges slowly when $b$ is near $a$, but otherwise it works fine. + +@p function pyth_sub(@!a,@!b:integer):integer; +label done; +var @!r:fraction; {register used to transform |a| and |b|} +@!big:boolean; {is the input dangerously near $2^{31}$?} +begin a:=abs(a); b:=abs(b); +if a<=b then @ +else begin if a; + if big then a:=a+a; + end; +pyth_sub:=a; +end; + +@ @= +loop@+ begin r:=make_fraction(b,a); + r:=take_fraction(r,r); {now $r\approx b^2/a^2$} + if r=0 then goto done; + r:=make_fraction(r,fraction_four-r); + a:=a-take_fraction(a+a,r); b:=take_fraction(b,r); + end; +done: + +@ @= +begin if a + help2("Since I don't take square roots of negative numbers,")@/ + ("I'm zeroing this one. Proceed, with fingers crossed."); + error; + end; +a:=0; +end + +@ The subroutines for logarithm and exponential involve two tables. +The first is simple: |two_to_the[k]| equals $2^k$. The second involves +a bit more calculation, which the author claims to have done correctly: +|spec_log[k]| is $2^{27}$ times $\ln\bigl(1/(1-2^{-k})\bigr)= +2^{-k}+{1\over2}2^{-2k}+{1\over3}2^{-3k}+\cdots\,$, rounded to the +nearest integer. + +@= +@!two_to_the:array[0..30] of integer; {powers of two} +@!spec_log:array[1..28] of integer; {special logarithms} + +@ @= +@!k:integer; {all-purpose loop index} + +@ @= +two_to_the[0]:=1; +for k:=1 to 30 do two_to_the[k]:=2*two_to_the[k-1]; +spec_log[1]:=93032640; +spec_log[2]:=38612034; +spec_log[3]:=17922280; +spec_log[4]:=8662214; +spec_log[5]:=4261238; +spec_log[6]:=2113709; +spec_log[7]:=1052693; +spec_log[8]:=525315; +spec_log[9]:=262400; +spec_log[10]:=131136; +spec_log[11]:=65552; +spec_log[12]:=32772; +spec_log[13]:=16385; +for k:=14 to 27 do spec_log[k]:=two_to_the[27-k]; +spec_log[28]:=1; + +@ Here is the routine that calculates $2^8$ times the natural logarithm +of a |scaled| quantity; it is an integer approximation to $2^{24}\ln(x/2^{16})$, +when |x| is a given positive integer. + +The method is based on exercise 1.2.2--25 in {\sl The Art of Computer +Programming\/}: During the main iteration we have $1\L 2^{-30}x<1/(1-2^{1-k})$, +and the logarithm of $2^{30}x$ remains to be added to an accumulator +register called~$y$. Three auxiliary bits of accuracy are retained in~$y$ +during the calculation, and sixteen auxiliary bits to extend |y| are +kept in~|z| during the initial argument reduction. (We add +$100\cdot2^{16}=6553600$ to~|z| and subtract 100 from~|y| so that |z| will +not become negative; also, the actual amount subtracted from~|y| is~96, +not~100, because we want to add~4 for rounding before the final division by~8.) + +@p function m_log(@!x:scaled):scaled; +var @!y,@!z:integer; {auxiliary registers} +@!k:integer; {iteration counter} +begin if x<=0 then @ +else begin y:=1302456956+4-100; {$14\times2^{27}\ln2\approx1302456956.421063$} + z:=27595+6553600; {and $2^{16}\times .421063\approx 27595$} + while xfraction_four+4 do + @; + m_log:=y div 8; + end; +end; + +@ @= +begin z:=((x-1) div two_to_the[k])+1; {$z=\lceil x/2^k\rceil$} +while x= +begin print_err("Logarithm of "); +@.Logarithm...replaced by 0@> +print_scaled(x); print(" has been replaced by 0"); +help2("Since I don't take logs of non-positive numbers,")@/ + ("I'm zeroing this one. Proceed, with fingers crossed."); +error; m_log:=0; +end + +@ Conversely, the exponential routine calculates $\exp(x/2^8)$, +when |x| is |scaled|. The result is an integer approximation to +$2^{16}\exp(x/2^{24})$, when |x| is regarded as an integer. + +@p function m_exp(@!x:scaled):scaled; +var @!k:small_number; {loop control index} +@!y,@!z:integer; {auxiliary registers} +begin if x>174436200 then + {$2^{24}\ln((2^{31}-1)/2^{16})\approx 174436199.51$} + begin arith_error:=true; m_exp:=el_gordo; + end +else if x<-197694359 then m_exp:=0 + {$2^{24}\ln(2^{-1}/2^{16})\approx-197694359.45$} +else begin if x<=0 then + begin z:=-8*x; y:=@'4000000; {$y=2^{20}$} + end + else begin if x<=127919879 then z:=1023359037-8*x + {$2^{27}\ln((2^{31}-1)/2^{20})\approx 1023359037.125$} + else z:=8*(174436200-x); {|z| is always nonnegative} + y:=el_gordo; + end; + @; + if x<=127919879 then m_exp:=(y+8) div 16@+else m_exp:=y; + end; +end; + +@ The idea here is that subtracting |spec_log[k]| from |z| corresponds +to multiplying |y| by $1-2^{-k}$. + +A subtle point (which had to be checked) was that if $x=127919879$, the +value of~|y| will decrease so that |y+8| doesn't overflow. In fact, +$z$ will be 5 in this case, and |y| will decrease by~64 when |k=25| +and by~16 when |k=27|. + +@= +k:=1; +while z>0 do + begin while z>=spec_log[k] do + begin z:=z-spec_log[k]; + y:=y-1-((y-two_to_the[k-1]) div two_to_the[k]); + end; + incr(k); + end + +@ The trigonometric subroutines use an auxiliary table such that +|spec_atan[k]| contains an approximation to the |angle| whose tangent +is~$1/2^k$. + +@= +@!spec_atan:array[1..26] of angle; {$\arctan2^{-k}$ times $2^{20}\cdot180/\pi$} + +@ @= +spec_atan[1]:=27855475; +spec_atan[2]:=14718068; +spec_atan[3]:=7471121; +spec_atan[4]:=3750058; +spec_atan[5]:=1876857; +spec_atan[6]:=938658; +spec_atan[7]:=469357; +spec_atan[8]:=234682; +spec_atan[9]:=117342; +spec_atan[10]:=58671; +spec_atan[11]:=29335; +spec_atan[12]:=14668; +spec_atan[13]:=7334; +spec_atan[14]:=3667; +spec_atan[15]:=1833; +spec_atan[16]:=917; +spec_atan[17]:=458; +spec_atan[18]:=229; +spec_atan[19]:=115; +spec_atan[20]:=57; +spec_atan[21]:=29; +spec_atan[22]:=14; +spec_atan[23]:=7; +spec_atan[24]:=4; +spec_atan[25]:=2; +spec_atan[26]:=1; + +@ Given integers |x| and |y|, not both zero, the |n_arg| function +returns the |angle| whose tangent points in the direction $(x,y)$. +This subroutine first determines the correct octant, then solves the +problem for |0<=y<=x|, then converts the result appropriately to +return an answer in the range |-one_eighty_deg<=@t$\theta$@><=one_eighty_deg|. +(The answer is |+one_eighty_deg| if |y=0| and |x<0|, but an answer of +|-one_eighty_deg| is possible if, for example, |y=-1| and $x=-2^{30}$.) + +The octants are represented in a ``Gray code,'' since that turns out +to be computationally simplest. + +@d negate_x=1 +@d negate_y=2 +@d switch_x_and_y=4 +@d first_octant=1 +@d second_octant=first_octant+switch_x_and_y +@d third_octant=first_octant+switch_x_and_y+negate_x +@d fourth_octant=first_octant+negate_x +@d fifth_octant=first_octant+negate_x+negate_y +@d sixth_octant=first_octant+switch_x_and_y+negate_x+negate_y +@d seventh_octant=first_octant+switch_x_and_y+negate_y +@d eighth_octant=first_octant+negate_y + +@p function n_arg(@!x,@!y:integer):angle; +var @!z:angle; {auxiliary register} +@!t:integer; {temporary storage} +@!k:small_number; {loop counter} +@!octant:first_octant..sixth_octant; {octant code} +begin if x>=0 then octant:=first_octant +else begin negate(x); octant:=first_octant+negate_x; + end; +if y<0 then + begin negate(y); octant:=octant+negate_y; + end; +if x +else begin @; + @; + end; +end; + +@ @= +begin print_err("angle(0,0) is taken as zero"); +@.angle(0,0)...zero@> +help2("The `angle' between two identical points is undefined.")@/ + ("I'm zeroing this one. Proceed, with fingers crossed."); +error; n_arg:=0; +end + +@ @= +case octant of +first_octant:n_arg:=z; +second_octant:n_arg:=ninety_deg-z; +third_octant:n_arg:=ninety_deg+z; +fourth_octant:n_arg:=one_eighty_deg-z; +fifth_octant:n_arg:=z-one_eighty_deg; +sixth_octant:n_arg:=-z-ninety_deg; +seventh_octant:n_arg:=z-ninety_deg; +eighth_octant:n_arg:=-z; +end {there are no other cases} + +@ At this point we have |x>=y>=0|, and |x>0|. The numbers are scaled up +or down until $2^{28}\L x<2^{29}$, so that accurate fixed-point calculations +will be made. + +@= +while x>=fraction_two do + begin x:=half(x); y:=half(y); + end; +z:=0; +if y>0 then + begin while x; + end + +@ During the calculations of this section, variables |x| and~|y| +represent actual coordinates $(x,2^{-k}y)$. We will maintain the +condition |x>=y|, so that the tangent will be at most $2^{-k}$. +If $x<2y$, the tangent is greater than $2^{-k-1}$. The transformation +$(a,b)\mapsto(a+b\tan\phi,b-a\tan\phi)$ replaces $(a,b)$ by +coordinates whose angle has decreased by~$\phi$; in the special case +$a=x$, $b=2^{-k}y$, and $\tan\phi=2^{-k-1}$, this operation reduces +to the particularly simple iteration shown here. [Cf.~John E. Meggitt, +@^Meggitt, John E.@> +{\sl IBM Journal of Research and Development\/ \bf6} (1962), 210--226.] + +The initial value of |x| will be multiplied by at most +$(1+{1\over2})(1+{1\over8})(1+{1\over32})\cdots\approx 1.7584$; hence +there is no chance of integer overflow. + +@= +k:=0; +repeat double(y); incr(k); +if y>x then + begin z:=z+spec_atan[k]; t:=x; x:=x+(y div two_to_the[k+k]); y:=y-t; + end; +until k=15; +repeat double(y); incr(k); +if y>x then + begin z:=z+spec_atan[k]; y:=y-x; + end; +until k=26 + +@ Conversely, the |n_sin_cos| routine takes an |angle| and produces the sine +and cosine of that angle. The results of this routine are +stored in global integer variables |n_sin| and |n_cos|. + +@= +@!n_sin,@!n_cos:fraction; {results computed by |n_sin_cos|} + +@ Given an integer |z| that is $2^{20}$ times an angle $\theta$ in degrees, +the purpose of |n_sin_cos(z)| is to set +|x=@t$r\cos\theta$@>| and |y=@t$r\sin\theta$@>| (approximately), +for some rather large number~|r|. The maximum of |x| and |y| +will be between $2^{28}$ and $2^{30}$, so that there will be hardly +any loss of accuracy. Then |x| and~|y| are divided by~|r|. + +@p procedure n_sin_cos(@!z:angle); {computes a multiple of the sine and cosine} +var @!k:small_number; {loop control variable} +@!q:0..7; {specifies the quadrant} +@!r:fraction; {magnitude of |(x,y)|} +@!x,@!y,@!t:integer; {temporary registers} +begin while z<0 do z:=z+three_sixty_deg; +z:=z mod three_sixty_deg; {now |0<=z; +@; +r:=pyth_add(x,y); n_cos:=make_fraction(x,r); n_sin:=make_fraction(y,r); +end; + +@ In this case the octants are numbered sequentially. + +@= +case q of +0:do_nothing; +1:begin t:=x; x:=y; y:=t; + end; +2:begin t:=x; x:=-y; y:=t; + end; +3:negate(x); +4:begin negate(x); negate(y); + end; +5:begin t:=x; x:=-y; y:=-t; + end; +6:begin t:=x; x:=y; y:=-t; + end; +7:negate(y); +end {there are no other cases} + +@ The main iteration of |n_sin_cos| is similar to that of |n_arg| but +applied in reverse. The values of |spec_atan[k]| decrease slowly enough +that this loop is guaranteed to terminate before the (nonexistent) value +|spec_atan[27]| would be required. + +@= +k:=1; +while z>0 do + begin if z>=spec_atan[k] then + begin z:=z-spec_atan[k]; t:=x;@/ + x:=t+y div two_to_the[k]; + y:=y-t div two_to_the[k]; + end; + incr(k); + end; +if y<0 then y:=0 {this precaution may never be needed} + +@ And now let's complete our collection of numeric utility routines +by considering random number generation. +\MF\ generates pseudo-random numbers with the additive scheme recommended +in Section 3.6 of {\sl The Art of Computer Programming}; however, the +results are random fractions between 0 and |fraction_one-1|, inclusive. + +There's an auxiliary array |randoms| that contains 55 pseudo-random +fractions. Using the recurrence $x_n=(x_{n-55}-x_{n-24})\bmod 2^{28}$, +we generate batches of 55 new $x_n$'s at a time by calling |new_randoms|. +The global variable |j_random| tells which element has most recently +been consumed. + +@= +@!randoms:array[0..54] of fraction; {the last 55 random values generated} +@!j_random:0..54; {the number of unused |randoms|} + +@ To consume a random fraction, the program below will say `|next_random|' +and then it will fetch |randoms[j_random]|. The |next_random| macro +actually accesses the numbers backwards; blocks of 55~$x$'s are +essentially being ``flipped.'' But that doesn't make them less random. + +@d next_random==if j_random=0 then new_randoms + else decr(j_random) + +@p procedure new_randoms; +var @!k:0..54; {index into |randoms|} +@!x:fraction; {accumulator} +begin for k:=0 to 23 do + begin x:=randoms[k]-randoms[k+31]; + if x<0 then x:=x+fraction_one; + randoms[k]:=x; + end; +for k:=24 to 54 do + begin x:=randoms[k]-randoms[k-24]; + if x<0 then x:=x+fraction_one; + randoms[k]:=x; + end; +j_random:=54; +end; + +@ To initialize the |randoms| table, we call the following routine. + +@p procedure init_randoms(@!seed:scaled); +var @!j,@!jj,@!k:fraction; {more or less random integers} +@!i:0..54; {index into |randoms|} +begin j:=abs(seed); +while j>=fraction_one do j:=half(j); +k:=1; +for i:=0 to 54 do + begin jj:=k; k:=j-k; j:=jj; + if k<0 then k:=k+fraction_one; + randoms[(i*21)mod 55]:=j; + end; +new_randoms; new_randoms; new_randoms; {``warm up'' the array} +end; + +@ To produce a uniform random number in the range |0<=u=u>x| +or |0=u=x|, given a |scaled| value~|x|, we proceed as shown here. + +Note that the call of |take_fraction| will produce the values 0 and~|x| +with about half the probability that it will produce any other particular +values between 0 and~|x|, because it rounds its answers. + +@p function unif_rand(@!x:scaled):scaled; +var @!y:scaled; {trial value} +begin next_random; y:=take_fraction(abs(x),randoms[j_random]); +if y=abs(x) then unif_rand:=0 +else if x>0 then unif_rand:=y +else unif_rand:=-y; +end; + +@ Finally, a normal deviate with mean zero and unit standard deviation +can readily be obtained with the ratio method (Algorithm 3.4.1R in +{\sl The Art of Computer Programming\/}). + +@p function norm_rand:scaled; +var @!x,@!u,@!l:integer; {what the book would call $2^{16}X$, $2^{28}U$, + and $-2^{24}\ln U$} +begin repeat + repeat next_random; + x:=take_fraction(112429,randoms[j_random]-fraction_half); + {$2^{16}\sqrt{8/e}\approx 112428.82793$} + next_random; u:=randoms[j_random]; + until abs(x)=0; +norm_rand:=x; +end; + +@* \[9] Packed data. +In order to make efficient use of storage space, \MF\ bases its major data +structures on a |memory_word|, which contains either a (signed) integer, +possibly scaled, or a small number of fields that are one half or one +quarter of the size used for storing integers. + +If |x| is a variable of type |memory_word|, it contains up to four +fields that can be referred to as follows: +$$\vbox{\halign{\hfil#&#\hfil&#\hfil\cr +|x|&.|int|&(an |integer|)\cr +|x|&.|sc|\qquad&(a |scaled| integer)\cr +|x.hh.lh|, |x.hh|&.|rh|&(two halfword fields)\cr +|x.hh.b0|, |x.hh.b1|, |x.hh|&.|rh|&(two quarterword fields, one halfword + field)\cr +|x.qqqq.b0|, |x.qqqq.b1|, |x.qqqq|&.|b2|, |x.qqqq.b3|\hskip-100pt + &\qquad\qquad\qquad(four quarterword fields)\cr}}$$ +This is somewhat cumbersome to write, and not very readable either, but +macros will be used to make the notation shorter and more transparent. +The \PASCAL\ code below gives a formal definition of |memory_word| and +its subsidiary types, using packed variant records. \MF\ makes no +assumptions about the relative positions of the fields within a word. + +Since we are assuming 32-bit integers, a halfword must contain at least +16 bits, and a quarterword must contain at least 8 bits. +@^system dependencies@> +But it doesn't hurt to have more bits; for example, with enough 36-bit +words you might be able to have |mem_max| as large as 262142. + +N.B.: Valuable memory space will be dreadfully wasted unless \MF\ is compiled +by a \PASCAL\ that packs all of the |memory_word| variants into +the space of a single integer. Some \PASCAL\ compilers will pack an +integer whose subrange is `|0..255|' into an eight-bit field, but others +insist on allocating space for an additional sign bit; on such systems you +can get 256 values into a quarterword only if the subrange is `|-128..127|'. + +The present implementation tries to accommodate as many variations as possible, +so it makes few assumptions. If integers having the subrange +`|min_quarterword..max_quarterword|' can be packed into a quarterword, +and if integers having the subrange `|min_halfword..max_halfword|' +can be packed into a halfword, everything should work satisfactorily. + +It is usually most efficient to have |min_quarterword=min_halfword=0|, +so one should try to achieve this unless it causes a severe problem. +The values defined here are recommended for most 32-bit computers. + +@d min_quarterword=0 {smallest allowable value in a |quarterword|} +@d max_quarterword=255 {largest allowable value in a |quarterword|} +@d min_halfword==0 {smallest allowable value in a |halfword|} +@d max_halfword==65535 {largest allowable value in a |halfword|} + +@ Here are the inequalities that the quarterword and halfword values +must satisfy (or rather, the inequalities that they mustn't satisfy): + +@= +init if mem_max<>mem_top then bad:=10;@+tini@;@/ +if mem_max0)or(max_quarterword<127) then bad:=11; +if (min_halfword>0)or(max_halfword<32767) then bad:=12; +if (min_quarterwordmax_halfword) then bad:=13; +if (mem_min=max_halfword) then bad:=14; +if max_strings>max_halfword then bad:=15; +if buf_size>max_halfword then bad:=16; +if (max_quarterword-min_quarterword<255)or@| + (max_halfword-min_halfword<65535) then bad:=17; + +@ The operation of subtracting |min_halfword| occurs rather frequently in +\MF, so it is convenient to abbreviate this operation by using the macro +|ho| defined here. \MF\ will run faster with respect to compilers that +don't optimize the expression `|x-0|', if this macro is simplified in the +obvious way when |min_halfword=0|. Similarly, |qi| and |qo| are used for +input to and output from quarterwords. +@^system dependencies@> + +@d ho(#)==#-min_halfword + {to take a sixteen-bit item from a halfword} +@d qo(#)==#-min_quarterword {to read eight bits from a quarterword} +@d qi(#)==#+min_quarterword {to store eight bits in a quarterword} + +@ The reader should study the following definitions closely: +@^system dependencies@> + +@d sc==int {|scaled| data is equivalent to |integer|} + +@= +@!quarterword = min_quarterword..max_quarterword; {1/4 of a word} +@!halfword=min_halfword..max_halfword; {1/2 of a word} +@!two_choices = 1..2; {used when there are two variants in a record} +@!three_choices = 1..3; {used when there are three variants in a record} +@!two_halves = packed record@;@/ + @!rh:halfword; + case two_choices of + 1: (@!lh:halfword); + 2: (@!b0:quarterword; @!b1:quarterword); + end; +@!four_quarters = packed record@;@/ + @!b0:quarterword; + @!b1:quarterword; + @!b2:quarterword; + @!b3:quarterword; + end; +@!memory_word = record@;@/ + case three_choices of + 1: (@!int:integer); + 2: (@!hh:two_halves); + 3: (@!qqqq:four_quarters); + end; +@!word_file = file of memory_word; + +@ When debugging, we may want to print a |memory_word| without knowing +what type it is; so we print it in all modes. +@^dirty \PASCAL@>@^debugging@> + +@p @!debug procedure print_word(@!w:memory_word); + {prints |w| in all ways} +begin print_int(w.int); print_char(" ");@/ +print_scaled(w.sc); print_char(" "); print_scaled(w.sc div @'10000); print_ln;@/ +print_int(w.hh.lh); print_char("="); print_int(w.hh.b0); print_char(":"); +print_int(w.hh.b1); print_char(";"); print_int(w.hh.rh); print_char(" ");@/ +print_int(w.qqqq.b0); print_char(":"); print_int(w.qqqq.b1); print_char(":"); +print_int(w.qqqq.b2); print_char(":"); print_int(w.qqqq.b3); +end; +gubed + +@* \[10] Dynamic memory allocation. +The \MF\ system does nearly all of its own memory allocation, so that it +can readily be transported into environments that do not have automatic +facilities for strings, garbage collection, etc., and so that it can be in +control of what error messages the user receives. The dynamic storage +requirements of \MF\ are handled by providing a large array |mem| in +which consecutive blocks of words are used as nodes by the \MF\ routines. + +Pointer variables are indices into this array, or into another array +called |eqtb| that will be explained later. A pointer variable might +also be a special flag that lies outside the bounds of |mem|, so we +allow pointers to assume any |halfword| value. The minimum memory +index represents a null pointer. + +@d pointer==halfword {a flag or a location in |mem| or |eqtb|} +@d null==mem_min {the null pointer} + +@ The |mem| array is divided into two regions that are allocated separately, +but the dividing line between these two regions is not fixed; they grow +together until finding their ``natural'' size in a particular job. +Locations less than or equal to |lo_mem_max| are used for storing +variable-length records consisting of two or more words each. This region +is maintained using an algorithm similar to the one described in exercise +2.5--19 of {\sl The Art of Computer Programming}. However, no size field +appears in the allocated nodes; the program is responsible for knowing the +relevant size when a node is freed. Locations greater than or equal to +|hi_mem_min| are used for storing one-word records; a conventional +\.{AVAIL} stack is used for allocation in this region. + +Locations of |mem| between |mem_min| and |mem_top| may be dumped as part +of preloaded base files, by the \.{INIMF} preprocessor. +@.INIMF@> +Production versions of \MF\ may extend the memory at the top end in order to +provide more space; these locations, between |mem_top| and |mem_max|, +are always used for single-word nodes. + +The key pointers that govern |mem| allocation have a prescribed order: +$$\hbox{|null=mem_min= +@!mem : array[mem_min..mem_max] of memory_word; {the big dynamic storage area} +@!lo_mem_max : pointer; {the largest location of variable-size memory in use} +@!hi_mem_min : pointer; {the smallest location of one-word memory in use} + +@ Users who wish to study the memory requirements of specific applications can +use optional special features that keep track of current and +maximum memory usage. When code between the delimiters |@!stat| $\ldots$ +|tats| is not ``commented out,'' \MF\ will run a bit slower but it will +report these statistics when |tracing_stats| is positive. + +@= +@!var_used, @!dyn_used : integer; {how much memory is in use} + +@ Let's consider the one-word memory region first, since it's the +simplest. The pointer variable |mem_end| holds the highest-numbered location +of |mem| that has ever been used. The free locations of |mem| that +occur between |hi_mem_min| and |mem_end|, inclusive, are of type +|two_halves|, and we write |info(p)| and |link(p)| for the |lh| +and |rh| fields of |mem[p]| when it is of this type. The single-word +free locations form a linked list +$$|avail|,\;\hbox{|link(avail)|},\;\hbox{|link(link(avail))|},\;\ldots$$ +terminated by |null|. + +@d link(#) == mem[#].hh.rh {the |link| field of a memory word} +@d info(#) == mem[#].hh.lh {the |info| field of a memory word} + +@= +@!avail : pointer; {head of the list of available one-word nodes} +@!mem_end : pointer; {the last one-word node used in |mem|} + +@ If one-word memory is exhausted, it might mean that the user has forgotten +a token like `\&{enddef}' or `\&{endfor}'. We will define some procedures +later that try to help pinpoint the trouble. + +@p @t\4@>@@; +@t\4@>@ + +@ The function |get_avail| returns a pointer to a new one-word node whose +|link| field is null. However, \MF\ will halt if there is no more room left. +@^inner loop@> + +@p function get_avail : pointer; {single-word node allocation} +var @!p:pointer; {the new node being got} +begin p:=avail; {get top location in the |avail| stack} +if p<>null then avail:=link(avail) {and pop it off} +else if mem_end + end; + end; +link(p):=null; {provide an oft-desired initialization of the new node} +@!stat incr(dyn_used);@+tats@;{maintain statistics} +get_avail:=p; +end; + +@ Conversely, a one-word node is recycled by calling |free_avail|. + +@d free_avail(#)== {single-word node liberation} + begin link(#):=avail; avail:=#; + @!stat decr(dyn_used);@+tats@/ + end + +@ There's also a |fast_get_avail| routine, which saves the procedure-call +overhead at the expense of extra programming. This macro is used in +the places that would otherwise account for the most calls of |get_avail|. +@^inner loop@> + +@d fast_get_avail(#)==@t@>@;@/ + begin #:=avail; {avoid |get_avail| if possible, to save time} + if #=null then #:=get_avail + else begin avail:=link(#); link(#):=null; + @!stat incr(dyn_used);@+tats@/ + end; + end + +@ The available-space list that keeps track of the variable-size portion +of |mem| is a nonempty, doubly-linked circular list of empty nodes, +pointed to by the roving pointer |rover|. + +Each empty node has size 2 or more; the first word contains the special +value |max_halfword| in its |link| field and the size in its |info| field; +the second word contains the two pointers for double linking. + +Each nonempty node also has size 2 or more. Its first word is of type +|two_halves|\kern-1pt, and its |link| field is never equal to |max_halfword|. +Otherwise there is complete flexibility with respect to the contents +of its other fields and its other words. + +(We require |mem_max= +@!rover : pointer; {points to some node in the list of empties} + +@ A call to |get_node| with argument |s| returns a pointer to a new node +of size~|s|, which must be 2~or more. The |link| field of the first word +of this new node is set to null. An overflow stop occurs if no suitable +space exists. + +If |get_node| is called with $s=2^{30}$, it simply merges adjacent free +areas and returns the value |max_halfword|. + +@p function get_node(@!s:integer):pointer; {variable-size node allocation} +label found,exit,restart; +var @!p:pointer; {the node currently under inspection} +@!q:pointer; {the node physically after node |p|} +@!r:integer; {the newly allocated node, or a candidate for this honor} +@!t,@!tt:integer; {temporary registers} +@^inner loop@> +begin restart: p:=rover; {start at some free node in the ring} +repeat @; +p:=rlink(p); {move to the next node in the ring} +until p=rover; {repeat until the whole list has been traversed} +if s=@'10000000000 then + begin get_node:=max_halfword; return; + end; +if lo_mem_max+2; +overflow("main memory size",mem_max+1-mem_min); + {sorry, nothing satisfactory is left} +@:METAFONT capacity exceeded main memory size}{\quad main memory size@> +found: link(r):=null; {this node is now nonempty} +@!stat var_used:=var_used+s; {maintain usage statistics} +tats@;@/ +get_node:=r; +exit:end; + +@ The lower part of |mem| grows by 1000 words at a time, unless +we are very close to going under. When it grows, we simply link +a new node into the available-space list. This method of controlled +growth helps to keep the |mem| usage consecutive when \MF\ is +implemented on ``virtual memory'' systems. +@^virtual memory@> + +@= +begin if hi_mem_min-lo_mem_max>=1998 then t:=lo_mem_max+1000 +else t:=lo_mem_max+1+(hi_mem_min-lo_mem_max) div 2; + {|lo_mem_max+2<=tmem_min+max_halfword then t:=mem_min+max_halfword; +p:=llink(rover); q:=lo_mem_max; rlink(p):=q; llink(rover):=q;@/ +rlink(q):=rover; llink(q):=p; link(q):=empty_flag; node_size(q):=t-lo_mem_max;@/ +lo_mem_max:=t; link(lo_mem_max):=null; info(lo_mem_max):=null; +rover:=q; goto restart; +end + +@ @= +q:=p+node_size(p); {find the physical successor} +while is_empty(q) do {merge node |p| with node |q|} + begin t:=rlink(q); tt:=llink(q); +@^inner loop@> + if q=rover then rover:=t; + llink(t):=tt; rlink(tt):=t;@/ + q:=q+node_size(q); + end; +r:=q-s; +if r>p+1 then @; +if r=p then if rlink(p)<>p then + @; +node_size(p):=q-p {reset the size in case it grew} + +@ @= +begin node_size(p):=r-p; {store the remaining size} +rover:=p; {start searching here next time} +goto found; +end + +@ Here we delete node |p| from the ring, and let |rover| rove around. + +@= +begin rover:=rlink(p); t:=llink(p); +llink(rover):=t; rlink(t):=rover; +goto found; +end + +@ Conversely, when some variable-size node |p| of size |s| is no longer needed, +the operation |free_node(p,s)| will make its words available, by inserting +|p| as a new empty node just before where |rover| now points. + +@p procedure free_node(@!p:pointer; @!s:halfword); {variable-size node + liberation} +var @!q:pointer; {|llink(rover)|} +begin node_size(p):=s; link(p):=empty_flag; +@^inner loop@> +q:=llink(rover); llink(p):=q; rlink(p):=rover; {set both links} +llink(rover):=p; rlink(q):=p; {insert |p| into the ring} +@!stat var_used:=var_used-s;@+tats@;{maintain statistics} +end; + +@ Just before \.{INIMF} writes out the memory, it sorts the doubly linked +available space list. The list is probably very short at such times, so a +simple insertion sort is used. The smallest available location will be +pointed to by |rover|, the next-smallest by |rlink(rover)|, etc. + +@p @!init procedure sort_avail; {sorts the available variable-size nodes + by location} +var @!p,@!q,@!r: pointer; {indices into |mem|} +@!old_rover:pointer; {initial |rover| setting} +begin p:=get_node(@'10000000000); {merge adjacent free areas} +p:=rlink(rover); rlink(rover):=max_halfword; old_rover:=rover; +while p<>old_rover do @; +p:=rover; +while rlink(p)<>max_halfword do + begin llink(rlink(p)):=p; p:=rlink(p); + end; +rlink(p):=rover; llink(rover):=p; +end; +tini + +@ The following |while| loop is guaranteed to +terminate, since the list that starts at +|rover| ends with |max_halfword| during the sorting procedure. + +@= +if p= +rover:=lo_mem_stat_max+1; {initialize the dynamic memory} +link(rover):=empty_flag; +node_size(rover):=1000; {which is a 1000-word available node} +llink(rover):=rover; rlink(rover):=rover;@/ +lo_mem_max:=rover+1000; link(lo_mem_max):=null; info(lo_mem_max):=null;@/ +for k:=hi_mem_stat_min to mem_top do + mem[k]:=mem[lo_mem_max]; {clear list heads} +avail:=null; mem_end:=mem_top; +hi_mem_min:=hi_mem_stat_min; {initialize the one-word memory} +var_used:=lo_mem_stat_max+1-mem_min; dyn_used:=mem_top+1-hi_mem_min; + {initialize statistics} + +@ The procedure |flush_list(p)| frees an entire linked list of one-word +nodes that starts at a given position, until coming to |sentinel| or a +pointer that is not in the one-word region. Another procedure, +|flush_node_list|, frees an entire linked list of one-word and two-word +nodes, until coming to a |null| pointer. +@^inner loop@> + +@p procedure flush_list(@!p:pointer); {makes list of single-word nodes + available} +label done; +var @!q,@!r:pointer; {list traversers} +begin if p>=hi_mem_min then if p<>sentinel then + begin r:=p; + repeat q:=r; r:=link(r); @!stat decr(dyn_used);@+tats@/ + if rnull do + begin q:=p; p:=link(p); + if q +are debugging.) + +@= +@!debug @!free: packed array [mem_min..mem_max] of boolean; {free cells} +@t\hskip1em@>@!was_free: packed array [mem_min..mem_max] of boolean; + {previously free cells} +@t\hskip1em@>@!was_mem_end,@!was_lo_max,@!was_hi_min: pointer; + {previous |mem_end|, |lo_mem_max|, and |hi_mem_min|} +@t\hskip1em@>@!panicking:boolean; {do we want to check memory constantly?} +gubed + +@ @= +@!debug was_mem_end:=mem_min; {indicate that everything was previously free} +was_lo_max:=mem_min; was_hi_min:=mem_max; +panicking:=false; +gubed + +@ Procedure |check_mem| makes sure that the available space lists of +|mem| are well formed, and it optionally prints out all locations +that are reserved now but were free the last time this procedure was called. + +@p @!debug procedure check_mem(@!print_locs : boolean); +label done1,done2; {loop exits} +var @!p,@!q,@!r:pointer; {current locations of interest in |mem|} +@!clobbered:boolean; {is something amiss?} +begin for p:=mem_min to lo_mem_max do free[p]:=false; {you can probably + do this faster} +for p:=hi_mem_min to mem_end do free[p]:=false; {ditto} +@; +@; +@; +@; +if print_locs then @; +for p:=mem_min to lo_mem_max do was_free[p]:=free[p]; +for p:=hi_mem_min to mem_end do was_free[p]:=free[p]; + {|was_free:=free| might be faster} +was_mem_end:=mem_end; was_lo_max:=lo_mem_max; was_hi_min:=hi_mem_min; +end; +gubed + +@ @= +p:=avail; q:=null; clobbered:=false; +while p<>null do + begin if (p>mem_end)or(p + print_int(q); goto done1; + end; + free[p]:=true; q:=p; p:=link(q); + end; +done1: + +@ @= +p:=rover; q:=null; clobbered:=false; +repeat if (p>=lo_mem_max)or(p=lo_mem_max)or(rlink(p)lo_mem_max)or@| (llink(rlink(p))<>p) then clobbered:=true; + if clobbered then + begin print_nl("Double-AVAIL list clobbered at "); +@.Double-AVAIL list clobbered...@> + print_int(q); goto done2; + end; +for q:=p to p+node_size(p)-1 do {mark all locations free} + begin if free[q] then + begin print_nl("Doubly free location at "); +@.Doubly free location...@> + print_int(q); goto done2; + end; + free[q]:=true; + end; +q:=p; p:=rlink(p); +until p=rover; +done2: + +@ @= +p:=mem_min; +while p<=lo_mem_max do {node |p| should not be empty} + begin if is_empty(p) then + begin print_nl("Bad flag at "); print_int(p); +@.Bad flag...@> + end; + while (p<=lo_mem_max) and not free[p] do incr(p); + while (p<=lo_mem_max) and free[p] do incr(p); + end + +@ @= +begin print_nl("New busy locs:"); +@.New busy locs@> +for p:=mem_min to lo_mem_max do + if not free[p] and ((p>was_lo_max) or was_free[p]) then + begin print_char(" "); print_int(p); + end; +for p:=hi_mem_min to mem_end do + if not free[p] and + ((pwas_mem_end) or was_free[p]) then + begin print_char(" "); print_int(p); + end; +end + +@ The |search_mem| procedure attempts to answer the question ``Who points +to node~|p|?'' In doing so, it fetches |link| and |info| fields of |mem| +that might not be of type |two_halves|. Strictly speaking, this is +@^dirty \PASCAL@> +undefined in \PASCAL, and it can lead to ``false drops'' (words that seem to +point to |p| purely by coincidence). But for debugging purposes, we want +to rule out the places that do {\sl not\/} point to |p|, so a few false +drops are tolerable. + +@p @!debug procedure search_mem(@!p:pointer); {look for pointers to |p|} +var @!q:integer; {current position being searched} +begin for q:=mem_min to lo_mem_max do + begin if link(q)=p then + begin print_nl("LINK("); print_int(q); print_char(")"); + end; + if info(q)=p then + begin print_nl("INFO("); print_int(q); print_char(")"); + end; + end; +for q:=hi_mem_min to mem_end do + begin if link(q)=p then + begin print_nl("LINK("); print_int(q); print_char(")"); + end; + if info(q)=p then + begin print_nl("INFO("); print_int(q); print_char(")"); + end; + end; +@; +end; +gubed + +@* \[12] The command codes. +Before we can go much further, we need to define symbolic names for the internal +code numbers that represent the various commands obeyed by \MF. These codes +are somewhat arbitrary, but not completely so. For example, +some codes have been made adjacent so that |case| statements in the +program need not consider cases that are widely spaced, or so that |case| +statements can be replaced by |if| statements. A command can begin an +expression if and only if its code lies between |min_primary_command| and +|max_primary_command|, inclusive. The first token of a statement that doesn't +begin with an expression has a command code between |min_command| and +|max_statement_command|, inclusive. The ordering of the highest-numbered +commands (|commacomma +@d semicolon=83 {the operator `\.;', must be |comma+1|} +@d end_group=84 {end a group (\&{endgroup}), must be |semicolon+1|} +@d stop=85 {end a job (\&{end}, \&{dump}), must be |end_group+1|} +@d max_command_code=stop +@d outer_tag=max_command_code+1 {protection code added to command code} + +@= +@!command_code=1..max_command_code; + +@ Variables and capsules in \MF\ have a variety of ``types,'' +distinguished by the following code numbers: + +@d undefined=0 {no type has been declared} +@d unknown_tag=1 {this constant is added to certain type codes below} +@d vacuous=1 {no expression was present} +@d boolean_type=2 {\&{boolean} with a known value} +@d unknown_boolean=boolean_type+unknown_tag +@d string_type=4 {\&{string} with a known value} +@d unknown_string=string_type+unknown_tag +@d pen_type=6 {\&{pen} with a known value} +@d unknown_pen=pen_type+unknown_tag +@d future_pen=8 {subexpression that will become a \&{pen} at a higher level} +@d path_type=9 {\&{path} with a known value} +@d unknown_path=path_type+unknown_tag +@d picture_type=11 {\&{picture} with a known value} +@d unknown_picture=picture_type+unknown_tag +@d transform_type=13 {\&{transform} variable or capsule} +@d pair_type=14 {\&{pair} variable or capsule} +@d numeric_type=15 {variable that has been declared \&{numeric} but not used} +@d known=16 {\&{numeric} with a known value} +@d dependent=17 {a linear combination with |fraction| coefficients} +@d proto_dependent=18 {a linear combination with |scaled| coefficients} +@d independent=19 {\&{numeric} with unknown value} +@d token_list=20 {variable name or suffix argument or text argument} +@d structured=21 {variable with subscripts and attributes} +@d unsuffixed_macro=22 {variable defined with \&{vardef} but no \.{\AT!\#}} +@d suffixed_macro=23 {variable defined with \&{vardef} and \.{\AT!\#}} +@# +@d unknown_types==unknown_boolean,unknown_string, + unknown_pen,unknown_picture,unknown_path + +@= +procedure print_type(@!t:small_number); +begin case t of +vacuous:print("vacuous"); +boolean_type:print("boolean"); +unknown_boolean:print("unknown boolean"); +string_type:print("string"); +unknown_string:print("unknown string"); +pen_type:print("pen"); +unknown_pen:print("unknown pen"); +future_pen:print("future pen"); +path_type:print("path"); +unknown_path:print("unknown path"); +picture_type:print("picture"); +unknown_picture:print("unknown picture"); +transform_type:print("transform"); +pair_type:print("pair"); +known:print("known numeric"); +dependent:print("dependent"); +proto_dependent:print("proto-dependent"); +numeric_type:print("numeric"); +independent:print("independent"); +token_list:print("token list"); +structured:print("structured"); +unsuffixed_macro:print("unsuffixed macro"); +suffixed_macro:print("suffixed macro"); +othercases print("undefined") +endcases; +end; + +@ Values inside \MF\ are stored in two-word nodes that have a |name_type| +as well as a |type|. The possibilities for |name_type| are defined +here; they will be explained in more detail later. + +@d root=0 {|name_type| at the top level of a variable} +@d saved_root=1 {same, when the variable has been saved} +@d structured_root=2 {|name_type| where a |structured| branch occurs} +@d subscr=3 {|name_type| in a subscript node} +@d attr=4 {|name_type| in an attribute node} +@d x_part_sector=5 {|name_type| in the \&{xpart} of a node} +@d y_part_sector=6 {|name_type| in the \&{ypart} of a node} +@d xx_part_sector=7 {|name_type| in the \&{xxpart} of a node} +@d xy_part_sector=8 {|name_type| in the \&{xypart} of a node} +@d yx_part_sector=9 {|name_type| in the \&{yxpart} of a node} +@d yy_part_sector=10 {|name_type| in the \&{yypart} of a node} +@d capsule=11 {|name_type| in stashed-away subexpressions} +@d token=12 {|name_type| in a numeric token or string token} + +@ Primitive operations that produce values have a secondary identification +code in addition to their command code; it's something like genera and species. +For example, `\.*' has the command code |primary_binary|, and its +secondary identification is |times|. The secondary codes start at 30 so that +they don't overlap with the type codes; some type codes (e.g., |string_type|) +are used as operators as well as type identifications. + +@d true_code=30 {operation code for \.{true}} +@d false_code=31 {operation code for \.{false}} +@d null_picture_code=32 {operation code for \.{nullpicture}} +@d null_pen_code=33 {operation code for \.{nullpen}} +@d job_name_op=34 {operation code for \.{jobname}} +@d read_string_op=35 {operation code for \.{readstring}} +@d pen_circle=36 {operation code for \.{pencircle}} +@d normal_deviate=37 {operation code for \.{normaldeviate}} +@d odd_op=38 {operation code for \.{odd}} +@d known_op=39 {operation code for \.{known}} +@d unknown_op=40 {operation code for \.{unknown}} +@d not_op=41 {operation code for \.{not}} +@d decimal=42 {operation code for \.{decimal}} +@d reverse=43 {operation code for \.{reverse}} +@d make_path_op=44 {operation code for \.{makepath}} +@d make_pen_op=45 {operation code for \.{makepen}} +@d total_weight_op=46 {operation code for \.{totalweight}} +@d oct_op=47 {operation code for \.{oct}} +@d hex_op=48 {operation code for \.{hex}} +@d ASCII_op=49 {operation code for \.{ASCII}} +@d char_op=50 {operation code for \.{char}} +@d length_op=51 {operation code for \.{length}} +@d turning_op=52 {operation code for \.{turningnumber}} +@d x_part=53 {operation code for \.{xpart}} +@d y_part=54 {operation code for \.{ypart}} +@d xx_part=55 {operation code for \.{xxpart}} +@d xy_part=56 {operation code for \.{xypart}} +@d yx_part=57 {operation code for \.{yxpart}} +@d yy_part=58 {operation code for \.{yypart}} +@d sqrt_op=59 {operation code for \.{sqrt}} +@d m_exp_op=60 {operation code for \.{mexp}} +@d m_log_op=61 {operation code for \.{mlog}} +@d sin_d_op=62 {operation code for \.{sind}} +@d cos_d_op=63 {operation code for \.{cosd}} +@d floor_op=64 {operation code for \.{floor}} +@d uniform_deviate=65 {operation code for \.{uniformdeviate}} +@d char_exists_op=66 {operation code for \.{charexists}} +@d angle_op=67 {operation code for \.{angle}} +@d cycle_op=68 {operation code for \.{cycle}} +@d plus=69 {operation code for \.+} +@d minus=70 {operation code for \.-} +@d times=71 {operation code for \.*} +@d over=72 {operation code for \./} +@d pythag_add=73 {operation code for \.{++}} +@d pythag_sub=74 {operation code for \.{+-+}} +@d or_op=75 {operation code for \.{or}} +@d and_op=76 {operation code for \.{and}} +@d less_than=77 {operation code for \.<} +@d less_or_equal=78 {operation code for \.{<=}} +@d greater_than=79 {operation code for \.>} +@d greater_or_equal=80 {operation code for \.{>=}} +@d equal_to=81 {operation code for \.=} +@d unequal_to=82 {operation code for \.{<>}} +@d concatenate=83 {operation code for \.\&} +@d rotated_by=84 {operation code for \.{rotated}} +@d slanted_by=85 {operation code for \.{slanted}} +@d scaled_by=86 {operation code for \.{scaled}} +@d shifted_by=87 {operation code for \.{shifted}} +@d transformed_by=88 {operation code for \.{transformed}} +@d x_scaled=89 {operation code for \.{xscaled}} +@d y_scaled=90 {operation code for \.{yscaled}} +@d z_scaled=91 {operation code for \.{zscaled}} +@d intersect=92 {operation code for \.{intersectiontimes}} +@d double_dot=93 {operation code for improper \.{..}} +@d substring_of=94 {operation code for \.{substring}} +@d min_of=substring_of +@d subpath_of=95 {operation code for \.{subpath}} +@d direction_time_of=96 {operation code for \.{directiontime}} +@d point_of=97 {operation code for \.{point}} +@d precontrol_of=98 {operation code for \.{precontrol}} +@d postcontrol_of=99 {operation code for \.{postcontrol}} +@d pen_offset_of=100 {operation code for \.{penoffset}} + +@p procedure print_op(@!c:quarterword); +begin if c<=numeric_type then print_type(c) +else case c of +true_code:print("true"); +false_code:print("false"); +null_picture_code:print("nullpicture"); +null_pen_code:print("nullpen"); +job_name_op:print("jobname"); +read_string_op:print("readstring"); +pen_circle:print("pencircle"); +normal_deviate:print("normaldeviate"); +odd_op:print("odd"); +known_op:print("known"); +unknown_op:print("unknown"); +not_op:print("not"); +decimal:print("decimal"); +reverse:print("reverse"); +make_path_op:print("makepath"); +make_pen_op:print("makepen"); +total_weight_op:print("totalweight"); +oct_op:print("oct"); +hex_op:print("hex"); +ASCII_op:print("ASCII"); +char_op:print("char"); +length_op:print("length"); +turning_op:print("turningnumber"); +x_part:print("xpart"); +y_part:print("ypart"); +xx_part:print("xxpart"); +xy_part:print("xypart"); +yx_part:print("yxpart"); +yy_part:print("yypart"); +sqrt_op:print("sqrt"); +m_exp_op:print("mexp"); +m_log_op:print("mlog"); +sin_d_op:print("sind"); +cos_d_op:print("cosd"); +floor_op:print("floor"); +uniform_deviate:print("uniformdeviate"); +char_exists_op:print("charexists"); +angle_op:print("angle"); +cycle_op:print("cycle"); +plus:print_char("+"); +minus:print_char("-"); +times:print_char("*"); +over:print_char("/"); +pythag_add:print("++"); +pythag_sub:print("+-+"); +or_op:print("or"); +and_op:print("and"); +less_than:print_char("<"); +less_or_equal:print("<="); +greater_than:print_char(">"); +greater_or_equal:print(">="); +equal_to:print_char("="); +unequal_to:print("<>"); +concatenate:print("&"); +rotated_by:print("rotated"); +slanted_by:print("slanted"); +scaled_by:print("scaled"); +shifted_by:print("shifted"); +transformed_by:print("transformed"); +x_scaled:print("xscaled"); +y_scaled:print("yscaled"); +z_scaled:print("zscaled"); +intersect:print("intersectiontimes"); +substring_of:print("substring"); +subpath_of:print("subpath"); +direction_time_of:print("directiontime"); +point_of:print("point"); +precontrol_of:print("precontrol"); +postcontrol_of:print("postcontrol"); +pen_offset_of:print("penoffset"); +othercases print("..") +endcases; +end; + +@ \MF\ also has a bunch of internal parameters that a user might want to +fuss with. Every such parameter has an identifying code number, defined here. + +@d tracing_titles=1 {show titles online when they appear} +@d tracing_equations=2 {show each variable when it becomes known} +@d tracing_capsules=3 {show capsules too} +@d tracing_choices=4 {show the control points chosen for paths} +@d tracing_specs=5 {show subdivision of paths into octants before digitizing} +@d tracing_pens=6 {show details of pens that are made} +@d tracing_commands=7 {show commands and operations before they are performed} +@d tracing_restores=8 {show when a variable or internal is restored} +@d tracing_macros=9 {show macros before they are expanded} +@d tracing_edges=10 {show digitized edges as they are computed} +@d tracing_output=11 {show digitized edges as they are output} +@d tracing_stats=12 {show memory usage at end of job} +@d tracing_online=13 {show long diagnostics on terminal and in the log file} +@d year=14 {the current year (e.g., 1984)} +@d month=15 {the current month (e.g., 3 $\equiv$ March)} +@d day=16 {the current day of the month} +@d time=17 {the number of minutes past midnight when this job started} +@d char_code=18 {the number of the next character to be output} +@d char_ext=19 {the extension code of the next character to be output} +@d char_wd=20 {the width of the next character to be output} +@d char_ht=21 {the height of the next character to be output} +@d char_dp=22 {the depth of the next character to be output} +@d char_ic=23 {the italic correction of the next character to be output} +@d char_dx=24 {the device's $x$ movement for the next character, in pixels} +@d char_dy=25 {the device's $y$ movement for the next character, in pixels} +@d design_size=26 {the unit of measure used for |char_wd..char_ic|, in points} +@d hppp=27 {the number of horizontal pixels per point} +@d vppp=28 {the number of vertical pixels per point} +@d x_offset=29 {horizontal displacement of shipped-out characters} +@d y_offset=30 {vertical displacement of shipped-out characters} +@d pausing=31 {positive to display lines on the terminal before they are read} +@d showstopping=32 {positive to stop after each \&{show} command} +@d fontmaking=33 {positive if font metric output is to be produced} +@d proofing=34 {positive for proof mode, negative to suppress output} +@d smoothing=35 {positive if moves are to be ``smoothed''} +@d autorounding=36 {controls path modification to ``good'' points} +@d granularity=37 {autorounding uses this pixel size} +@d fillin=38 {extra darkness of diagonal lines} +@d turning_check=39 {controls reorientation of clockwise paths} +@d warning_check=40 {controls error message when variable value is large} +@d boundary_char=41 {the right boundary character for ligatures} +@d max_given_internal=41 + +@= +@!internal:array[1..max_internal] of scaled; + {the values of internal quantities} +@!int_name:array[1..max_internal] of str_number; + {their names} +@!int_ptr:max_given_internal..max_internal; + {the maximum internal quantity defined so far} + +@ @= +for k:=1 to max_given_internal do internal[k]:=0; +int_ptr:=max_given_internal; + +@ The symbolic names for internal quantities are put into \MF's hash table +by using a routine called |primitive|, which will be defined later. Let us +enter them now, so that we don't have to list all those names again +anywhere else. + +@= +primitive("tracingtitles",internal_quantity,tracing_titles);@/ +@!@:tracingtitles_}{\&{tracingtitles} primitive@> +primitive("tracingequations",internal_quantity,tracing_equations);@/ +@!@:tracing_equations_}{\&{tracingequations} primitive@> +primitive("tracingcapsules",internal_quantity,tracing_capsules);@/ +@!@:tracing_capsules_}{\&{tracingcapsules} primitive@> +primitive("tracingchoices",internal_quantity,tracing_choices);@/ +@!@:tracing_choices_}{\&{tracingchoices} primitive@> +primitive("tracingspecs",internal_quantity,tracing_specs);@/ +@!@:tracing_specs_}{\&{tracingspecs} primitive@> +primitive("tracingpens",internal_quantity,tracing_pens);@/ +@!@:tracing_pens_}{\&{tracingpens} primitive@> +primitive("tracingcommands",internal_quantity,tracing_commands);@/ +@!@:tracing_commands_}{\&{tracingcommands} primitive@> +primitive("tracingrestores",internal_quantity,tracing_restores);@/ +@!@:tracing_restores_}{\&{tracingrestores} primitive@> +primitive("tracingmacros",internal_quantity,tracing_macros);@/ +@!@:tracing_macros_}{\&{tracingmacros} primitive@> +primitive("tracingedges",internal_quantity,tracing_edges);@/ +@!@:tracing_edges_}{\&{tracingedges} primitive@> +primitive("tracingoutput",internal_quantity,tracing_output);@/ +@!@:tracing_output_}{\&{tracingoutput} primitive@> +primitive("tracingstats",internal_quantity,tracing_stats);@/ +@!@:tracing_stats_}{\&{tracingstats} primitive@> +primitive("tracingonline",internal_quantity,tracing_online);@/ +@!@:tracing_online_}{\&{tracingonline} primitive@> +primitive("year",internal_quantity,year);@/ +@!@:year_}{\&{year} primitive@> +primitive("month",internal_quantity,month);@/ +@!@:month_}{\&{month} primitive@> +primitive("day",internal_quantity,day);@/ +@!@:day_}{\&{day} primitive@> +primitive("time",internal_quantity,time);@/ +@!@:time_}{\&{time} primitive@> +primitive("charcode",internal_quantity,char_code);@/ +@!@:char_code_}{\&{charcode} primitive@> +primitive("charext",internal_quantity,char_ext);@/ +@!@:char_ext_}{\&{charext} primitive@> +primitive("charwd",internal_quantity,char_wd);@/ +@!@:char_wd_}{\&{charwd} primitive@> +primitive("charht",internal_quantity,char_ht);@/ +@!@:char_ht_}{\&{charht} primitive@> +primitive("chardp",internal_quantity,char_dp);@/ +@!@:char_dp_}{\&{chardp} primitive@> +primitive("charic",internal_quantity,char_ic);@/ +@!@:char_ic_}{\&{charic} primitive@> +primitive("chardx",internal_quantity,char_dx);@/ +@!@:char_dx_}{\&{chardx} primitive@> +primitive("chardy",internal_quantity,char_dy);@/ +@!@:char_dy_}{\&{chardy} primitive@> +primitive("designsize",internal_quantity,design_size);@/ +@!@:design_size_}{\&{designsize} primitive@> +primitive("hppp",internal_quantity,hppp);@/ +@!@:hppp_}{\&{hppp} primitive@> +primitive("vppp",internal_quantity,vppp);@/ +@!@:vppp_}{\&{vppp} primitive@> +primitive("xoffset",internal_quantity,x_offset);@/ +@!@:x_offset_}{\&{xoffset} primitive@> +primitive("yoffset",internal_quantity,y_offset);@/ +@!@:y_offset_}{\&{yoffset} primitive@> +primitive("pausing",internal_quantity,pausing);@/ +@!@:pausing_}{\&{pausing} primitive@> +primitive("showstopping",internal_quantity,showstopping);@/ +@!@:showstopping_}{\&{showstopping} primitive@> +primitive("fontmaking",internal_quantity,fontmaking);@/ +@!@:fontmaking_}{\&{fontmaking} primitive@> +primitive("proofing",internal_quantity,proofing);@/ +@!@:proofing_}{\&{proofing} primitive@> +primitive("smoothing",internal_quantity,smoothing);@/ +@!@:smoothing_}{\&{smoothing} primitive@> +primitive("autorounding",internal_quantity,autorounding);@/ +@!@:autorounding_}{\&{autorounding} primitive@> +primitive("granularity",internal_quantity,granularity);@/ +@!@:granularity_}{\&{granularity} primitive@> +primitive("fillin",internal_quantity,fillin);@/ +@!@:fillin_}{\&{fillin} primitive@> +primitive("turningcheck",internal_quantity,turning_check);@/ +@!@:turning_check_}{\&{turningcheck} primitive@> +primitive("warningcheck",internal_quantity,warning_check);@/ +@!@:warning_check_}{\&{warningcheck} primitive@> +primitive("boundarychar",internal_quantity,boundary_char);@/ +@!@:boundary_char_}{\&{boundarychar} primitive@> + +@ Well, we do have to list the names one more time, for use in symbolic +printouts. + +@= +int_name[tracing_titles]:="tracingtitles"; +int_name[tracing_equations]:="tracingequations"; +int_name[tracing_capsules]:="tracingcapsules"; +int_name[tracing_choices]:="tracingchoices"; +int_name[tracing_specs]:="tracingspecs"; +int_name[tracing_pens]:="tracingpens"; +int_name[tracing_commands]:="tracingcommands"; +int_name[tracing_restores]:="tracingrestores"; +int_name[tracing_macros]:="tracingmacros"; +int_name[tracing_edges]:="tracingedges"; +int_name[tracing_output]:="tracingoutput"; +int_name[tracing_stats]:="tracingstats"; +int_name[tracing_online]:="tracingonline"; +int_name[year]:="year"; +int_name[month]:="month"; +int_name[day]:="day"; +int_name[time]:="time"; +int_name[char_code]:="charcode"; +int_name[char_ext]:="charext"; +int_name[char_wd]:="charwd"; +int_name[char_ht]:="charht"; +int_name[char_dp]:="chardp"; +int_name[char_ic]:="charic"; +int_name[char_dx]:="chardx"; +int_name[char_dy]:="chardy"; +int_name[design_size]:="designsize"; +int_name[hppp]:="hppp"; +int_name[vppp]:="vppp"; +int_name[x_offset]:="xoffset"; +int_name[y_offset]:="yoffset"; +int_name[pausing]:="pausing"; +int_name[showstopping]:="showstopping"; +int_name[fontmaking]:="fontmaking"; +int_name[proofing]:="proofing"; +int_name[smoothing]:="smoothing"; +int_name[autorounding]:="autorounding"; +int_name[granularity]:="granularity"; +int_name[fillin]:="fillin"; +int_name[turning_check]:="turningcheck"; +int_name[warning_check]:="warningcheck"; +int_name[boundary_char]:="boundarychar"; + +@ The following procedure, which is called just before \MF\ initializes its +input and output, establishes the initial values of the date and time. +@^system dependencies@> +Since standard \PASCAL\ cannot provide such information, something special +is needed. The program here simply specifies July 4, 1776, at noon; but +users probably want a better approximation to the truth. + +Note that the values are |scaled| integers. Hence \MF\ can no longer +be used after the year 32767. + +@p procedure fix_date_and_time; +begin internal[time]:=12*60*unity; {minutes since midnight} +internal[day]:=4*unity; {fourth day of the month} +internal[month]:=7*unity; {seventh month of the year} +internal[year]:=1776*unity; {Anno Domini} +end; + +@ \MF\ is occasionally supposed to print diagnostic information that +goes only into the transcript file, unless |tracing_online| is positive. +Now that we have defined |tracing_online| we can define +two routines that adjust the destination of print commands: + +@= +procedure begin_diagnostic; {prepare to do some tracing} +begin old_setting:=selector; +if(internal[tracing_online]<=0)and(selector=term_and_log) then + begin decr(selector); + if history=spotless then history:=warning_issued; + end; +end; +@# +procedure end_diagnostic(@!blank_line:boolean); + {restore proper conditions after tracing} +begin print_nl(""); +if blank_line then print_ln; +selector:=old_setting; +end; + +@ Of course we had better declare another global variable, if the previous +routines are going to work. + +@= +@!old_setting:0..max_selector; + +@ We will occasionally use |begin_diagnostic| in connection with line-number +printing, as follows. (The parameter |s| is typically |"Path"| or +|"Cycle spec"|, etc.) + +@= +procedure print_diagnostic(@!s,@!t:str_number;@!nuline:boolean); +begin begin_diagnostic; +if nuline then print_nl(s)@+else print(s); +print(" at line "); print_int(line); +print(t); print_char(":"); +end; + +@ The 256 |ASCII_code| characters are grouped into classes by means of +the |char_class| table. Individual class numbers have no semantic +or syntactic significance, except in a few instances defined here. +There's also |max_class|, which can be used as a basis for additional +class numbers in nonstandard extensions of \MF. + +@d digit_class=0 {the class number of \.{0123456789}} +@d period_class=1 {the class number of `\..'} +@d space_class=2 {the class number of spaces and nonstandard characters} +@d percent_class=3 {the class number of `\.\%'} +@d string_class=4 {the class number of `\."'} +@d right_paren_class=8 {the class number of `\.)'} +@d isolated_classes==5,6,7,8 {characters that make length-one tokens only} +@d letter_class=9 {letters and the underline character} +@d left_bracket_class=17 {`\.['} +@d right_bracket_class=18 {`\.]'} +@d invalid_class=20 {bad character in the input} +@d max_class=20 {the largest class number} + +@= +@!char_class:array[ASCII_code] of 0..max_class; {the class numbers} + +@ If changes are made to accommodate non-ASCII character sets, they should +follow the guidelines in Appendix~C of {\sl The {\logos METAFONT\/}book}. +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> +@^system dependencies@> + +@= +for k:="0" to "9" do char_class[k]:=digit_class; +char_class["."]:=period_class; +char_class[" "]:=space_class; +char_class["%"]:=percent_class; +char_class[""""]:=string_class;@/ +char_class[","]:=5; +char_class[";"]:=6; +char_class["("]:=7; +char_class[")"]:=right_paren_class; +for k:="A" to "Z" do char_class[k]:=letter_class; +for k:="a" to "z" do char_class[k]:=letter_class; +char_class["_"]:=letter_class;@/ +char_class["<"]:=10; +char_class["="]:=10; +char_class[">"]:=10; +char_class[":"]:=10; +char_class["|"]:=10;@/ +char_class["`"]:=11; +char_class["'"]:=11;@/ +char_class["+"]:=12; +char_class["-"]:=12;@/ +char_class["/"]:=13; +char_class["*"]:=13; +char_class["\"]:=13;@/ +char_class["!"]:=14; +char_class["?"]:=14;@/ +char_class["#"]:=15; +char_class["&"]:=15; +char_class["@@"]:=15; +char_class["$"]:=15;@/ +char_class["^"]:=16; +char_class["~"]:=16;@/ +char_class["["]:=left_bracket_class; +char_class["]"]:=right_bracket_class;@/ +char_class["{"]:=19; +char_class["}"]:=19;@/ +for k:=0 to " "-1 do char_class[k]:=invalid_class; +for k:=127 to 255 do char_class[k]:=invalid_class; + +@* \[13] The hash table. +Symbolic tokens are stored and retrieved by means of a fairly standard hash +table algorithm called the method of ``coalescing lists'' (cf.\ Algorithm 6.4C +in {\sl The Art of Computer Programming\/}). Once a symbolic token enters the +table, it is never removed. + +The actual sequence of characters forming a symbolic token is +stored in the |str_pool| array together with all the other strings. An +auxiliary array |hash| consists of items with two halfword fields per +word. The first of these, called |next(p)|, points to the next identifier +belonging to the same coalesced list as the identifier corresponding to~|p|; +and the other, called |text(p)|, points to the |str_start| entry for +|p|'s identifier. If position~|p| of the hash table is empty, we have +|text(p)=0|; if position |p| is either empty or the end of a coalesced +hash list, we have |next(p)=0|. + +An auxiliary pointer variable called |hash_used| is maintained in such a +way that all locations |p>=hash_used| are nonempty. The global variable +|st_count| tells how many symbolic tokens have been defined, if statistics +are being kept. + +The first 256 locations of |hash| are reserved for symbols of length one. + +There's a parallel array called |eqtb| that contains the current equivalent +values of each symbolic token. The entries of this array consist of +two halfwords called |eq_type| (a command code) and |equiv| (a secondary +piece of information that qualifies the |eq_type|). + +@d next(#) == hash[#].lh {link for coalesced lists} +@d text(#) == hash[#].rh {string number for symbolic token name} +@d eq_type(#) == eqtb[#].lh {the current ``meaning'' of a symbolic token} +@d equiv(#) == eqtb[#].rh {parametric part of a token's meaning} +@d hash_base=257 {hashing actually starts here} +@d hash_is_full == (hash_used=hash_base) {are all positions occupied?} + +@= +@!hash_used:pointer; {allocation pointer for |hash|} +@!st_count:integer; {total number of known identifiers} + +@ Certain entries in the hash table are ``frozen'' and not redefinable, +since they are used in error recovery. + +@d hash_top==hash_base+hash_size {the first location of the frozen area} +@d frozen_inaccessible==hash_top {|hash| location to protect the frozen area} +@d frozen_repeat_loop==hash_top+1 {|hash| location of a loop-repeat token} +@d frozen_right_delimiter==hash_top+2 {|hash| location of a permanent `\.)'} +@d frozen_left_bracket==hash_top+3 {|hash| location of a permanent `\.['} +@d frozen_slash==hash_top+4 {|hash| location of a permanent `\./'} +@d frozen_colon==hash_top+5 {|hash| location of a permanent `\.:'} +@d frozen_semicolon==hash_top+6 {|hash| location of a permanent `\.;'} +@d frozen_end_for==hash_top+7 {|hash| location of a permanent \&{endfor}} +@d frozen_end_def==hash_top+8 {|hash| location of a permanent \&{enddef}} +@d frozen_fi==hash_top+9 {|hash| location of a permanent \&{fi}} +@d frozen_end_group==hash_top+10 + {|hash| location of a permanent `\.{endgroup}'} +@d frozen_bad_vardef==hash_top+11 {|hash| location of `\.{a bad variable}'} +@d frozen_undefined==hash_top+12 {|hash| location that never gets defined} +@d hash_end==hash_top+12 {the actual size of the |hash| and |eqtb| arrays} + +@= +@!hash: array[1..hash_end] of two_halves; {the hash table} +@!eqtb: array[1..hash_end] of two_halves; {the equivalents} + +@ @= +next(1):=0; text(1):=0; eq_type(1):=tag_token; equiv(1):=null; +for k:=2 to hash_end do + begin hash[k]:=hash[1]; eqtb[k]:=eqtb[1]; + end; + +@ @= +hash_used:=frozen_inaccessible; {nothing is used} +st_count:=0;@/ +text(frozen_bad_vardef):="a bad variable"; +text(frozen_fi):="fi"; +text(frozen_end_group):="endgroup"; +text(frozen_end_def):="enddef"; +text(frozen_end_for):="endfor";@/ +text(frozen_semicolon):=";"; +text(frozen_colon):=":"; +text(frozen_slash):="/"; +text(frozen_left_bracket):="["; +text(frozen_right_delimiter):=")";@/ +text(frozen_inaccessible):=" INACCESSIBLE";@/ +eq_type(frozen_right_delimiter):=right_delimiter; + +@ @= +if hash_end+max_internal>max_halfword then bad:=21; + +@ Here is the subroutine that searches the hash table for an identifier +that matches a given string of length~|l| appearing in |buffer[j.. +(j+l-1)]|. If the identifier is not found, it is inserted; hence it +will always be found, and the corresponding hash table address +will be returned. + +@p function id_lookup(@!j,@!l:integer):pointer; {search the hash table} +label found; {go here when you've found it} +var @!h:integer; {hash code} +@!p:pointer; {index in |hash| array} +@!k:pointer; {index in |buffer| array} +begin if l=1 then @; +@; +p:=h+hash_base; {we start searching here; note that |0<=h0 then if length(text(p))=l then + if str_eq_buf(text(p),j) then goto found; + if next(p)=0 then + @; + p:=next(p); + end; +found: id_lookup:=p; +end; + +@ @= +begin p:=buffer[j]+1; text(p):=p-1; goto found; +end + +@ @= +begin if text(p)>0 then + begin repeat if hash_is_full then + overflow("hash size",hash_size); +@:METAFONT capacity exceeded hash size}{\quad hash size@> + decr(hash_used); + until text(hash_used)=0; {search for an empty location in |hash|} + next(p):=hash_used; p:=hash_used; + end; +str_room(l); +for k:=j to j+l-1 do append_char(buffer[k]); +text(p):=make_string; str_ref[text(p)]:=max_str_ref; +@!stat incr(st_count);@+tats@;@/ +goto found; +end + +@ The value of |hash_prime| should be roughly 85\pct! of |hash_size|, and it +should be a prime number. The theory of hashing tells us to expect fewer +than two table probes, on the average, when the search is successful. +[See J.~S. Vitter, {\sl Journal of the ACM\/ \bf30} (1983), 231--258.] +@^Vitter, Jeffrey Scott@> + +@= +h:=buffer[j]; +for k:=j+1 to j+l-1 do + begin h:=h+h+buffer[k]; + while h>=hash_prime do h:=h-hash_prime; + end + +@ @= +for q:=1 to hash_end do + begin if equiv(q)=p then + begin print_nl("EQUIV("); print_int(q); print_char(")"); + end; + end + +@ We need to put \MF's ``primitive'' symbolic tokens into the hash +table, together with their command code (which will be the |eq_type|) +and an operand (which will be the |equiv|). The |primitive| procedure +does this, in a way that no \MF\ user can. The global value |cur_sym| +contains the new |eqtb| pointer after |primitive| has acted. + +@p @!init procedure primitive(@!s:str_number;@!c:halfword;@!o:halfword); +var @!k:pool_pointer; {index into |str_pool|} +@!j:small_number; {index into |buffer|} +@!l:small_number; {length of the string} +begin k:=str_start[s]; l:=str_start[s+1]-k; + {we will move |s| into the (empty) |buffer|} +for j:=0 to l-1 do buffer[j]:=so(str_pool[k+j]); +cur_sym:=id_lookup(0,l);@/ +if s>=256 then {we don't want to have the string twice} + begin flush_string(str_ptr-1); text(cur_sym):=s; + end; +eq_type(cur_sym):=c; equiv(cur_sym):=o; +end; +tini + +@ Many of \MF's primitives need no |equiv|, since they are identifiable +by their |eq_type| alone. These primitives are loaded into the hash table +as follows: + +@= +primitive("..",path_join,0);@/ +@!@:.._}{\.{..} primitive@> +primitive("[",left_bracket,0); eqtb[frozen_left_bracket]:=eqtb[cur_sym];@/ +@!@:[ }{\.{[} primitive@> +primitive("]",right_bracket,0);@/ +@!@:] }{\.{]} primitive@> +primitive("}",right_brace,0);@/ +@!@:]]}{\.{\char`\}} primitive@> +primitive("{",left_brace,0);@/ +@!@:][}{\.{\char`\{} primitive@> +primitive(":",colon,0); eqtb[frozen_colon]:=eqtb[cur_sym];@/ +@!@:: }{\.{:} primitive@> +primitive("::",double_colon,0);@/ +@!@::: }{\.{::} primitive@> +primitive("||:",bchar_label,0);@/ +@!@:::: }{\.{\char'174\char'174:} primitive@> +primitive(":=",assignment,0);@/ +@!@::=_}{\.{:=} primitive@> +primitive(",",comma,0);@/ +@!@:, }{\., primitive@> +primitive(";",semicolon,0); eqtb[frozen_semicolon]:=eqtb[cur_sym];@/ +@!@:; }{\.; primitive@> +primitive("\",relax,0);@/ +@!@:]]\\}{\.{\char`\\} primitive@> +@# +primitive("addto",add_to_command,0);@/ +@!@:add_to_}{\&{addto} primitive@> +primitive("at",at_token,0);@/ +@!@:at_}{\&{at} primitive@> +primitive("atleast",at_least,0);@/ +@!@:at_least_}{\&{atleast} primitive@> +primitive("begingroup",begin_group,0); bg_loc:=cur_sym;@/ +@!@:begin_group_}{\&{begingroup} primitive@> +primitive("controls",controls,0);@/ +@!@:controls_}{\&{controls} primitive@> +primitive("cull",cull_command,0);@/ +@!@:cull_}{\&{cull} primitive@> +primitive("curl",curl_command,0);@/ +@!@:curl_}{\&{curl} primitive@> +primitive("delimiters",delimiters,0);@/ +@!@:delimiters_}{\&{delimiters} primitive@> +primitive("display",display_command,0);@/ +@!@:display_}{\&{display} primitive@> +primitive("endgroup",end_group,0); + eqtb[frozen_end_group]:=eqtb[cur_sym]; eg_loc:=cur_sym;@/ +@!@:endgroup_}{\&{endgroup} primitive@> +primitive("everyjob",every_job_command,0);@/ +@!@:every_job_}{\&{everyjob} primitive@> +primitive("exitif",exit_test,0);@/ +@!@:exit_if_}{\&{exitif} primitive@> +primitive("expandafter",expand_after,0);@/ +@!@:expand_after_}{\&{expandafter} primitive@> +primitive("from",from_token,0);@/ +@!@:from_}{\&{from} primitive@> +primitive("inwindow",in_window,0);@/ +@!@:in_window_}{\&{inwindow} primitive@> +primitive("interim",interim_command,0);@/ +@!@:interim_}{\&{interim} primitive@> +primitive("let",let_command,0);@/ +@!@:let_}{\&{let} primitive@> +primitive("newinternal",new_internal,0);@/ +@!@:new_internal_}{\&{newinternal} primitive@> +primitive("of",of_token,0);@/ +@!@:of_}{\&{of} primitive@> +primitive("openwindow",open_window,0);@/ +@!@:open_window_}{\&{openwindow} primitive@> +primitive("randomseed",random_seed,0);@/ +@!@:random_seed_}{\&{randomseed} primitive@> +primitive("save",save_command,0);@/ +@!@:save_}{\&{save} primitive@> +primitive("scantokens",scan_tokens,0);@/ +@!@:scan_tokens_}{\&{scantokens} primitive@> +primitive("shipout",ship_out_command,0);@/ +@!@:ship_out_}{\&{shipout} primitive@> +primitive("skipto",skip_to,0);@/ +@!@:skip_to_}{\&{skipto} primitive@> +primitive("step",step_token,0);@/ +@!@:step_}{\&{step} primitive@> +primitive("str",str_op,0);@/ +@!@:str_}{\&{str} primitive@> +primitive("tension",tension,0);@/ +@!@:tension_}{\&{tension} primitive@> +primitive("to",to_token,0);@/ +@!@:to_}{\&{to} primitive@> +primitive("until",until_token,0);@/ +@!@:until_}{\&{until} primitive@> + +@ Each primitive has a corresponding inverse, so that it is possible to +display the cryptic numeric contents of |eqtb| in symbolic form. +Every call of |primitive| in this program is therefore accompanied by some +straightforward code that forms part of the |print_cmd_mod| routine +explained below. + +@= +add_to_command:print("addto"); +assignment:print(":="); +at_least:print("atleast"); +at_token:print("at"); +bchar_label:print("||:"); +begin_group:print("begingroup"); +colon:print(":"); +comma:print(","); +controls:print("controls"); +cull_command:print("cull"); +curl_command:print("curl"); +delimiters:print("delimiters"); +display_command:print("display"); +double_colon:print("::"); +end_group:print("endgroup"); +every_job_command:print("everyjob"); +exit_test:print("exitif"); +expand_after:print("expandafter"); +from_token:print("from"); +in_window:print("inwindow"); +interim_command:print("interim"); +left_brace:print("{"); +left_bracket:print("["); +let_command:print("let"); +new_internal:print("newinternal"); +of_token:print("of"); +open_window:print("openwindow"); +path_join:print(".."); +random_seed:print("randomseed"); +relax:print_char("\"); +right_brace:print("}"); +right_bracket:print("]"); +save_command:print("save"); +scan_tokens:print("scantokens"); +semicolon:print(";"); +ship_out_command:print("shipout"); +skip_to:print("skipto"); +step_token:print("step"); +str_op:print("str"); +tension:print("tension"); +to_token:print("to"); +until_token:print("until"); + +@ We will deal with the other primitives later, at some point in the program +where their |eq_type| and |equiv| values are more meaningful. For example, +the primitives for macro definitions will be loaded when we consider the +routines that define macros. +It is easy to find where each particular +primitive was treated by looking in the index at the end; for example, the +section where |"def"| entered |eqtb| is listed under `\&{def} primitive'. + +@* \[14] Token lists. +A \MF\ token is either symbolic or numeric or a string, or it denotes +a macro parameter or capsule; so there are five corresponding ways to encode it +@^token@> +internally: (1)~A symbolic token whose hash code is~|p| +is represented by the number |p|, in the |info| field of a single-word +node in~|mem|. (2)~A numeric token whose |scaled| value is~|v| is +represented in a two-word node of~|mem|; the |type| field is |known|, +the |name_type| field is |token|, and the |value| field holds~|v|. +The fact that this token appears in a two-word node rather than a +one-word node is, of course, clear from the node address. +(3)~A string token is also represented in a two-word node; the |type| +field is |string_type|, the |name_type| field is |token|, and the +|value| field holds the corresponding |str_number|. (4)~Capsules have +|name_type=capsule|, and their |type| and |value| fields represent +arbitrary values (in ways to be explained later). (5)~Macro parameters +are like symbolic tokens in that they appear in |info| fields of +one-word nodes. The $k$th parameter is represented by |expr_base+k| if it +is of type \&{expr}, or by |suffix_base+k| if it is of type \&{suffix}, or +by |text_base+k| if it is of type \&{text}. (Here |0<=k= +if text_base+param_size>max_halfword then bad:=22; + +@ A numeric token is created by the following trivial routine. + +@p function new_num_tok(@!v:scaled):pointer; +var @!p:pointer; {the new node} +begin p:=get_node(token_node_size); value(p):=v; +type(p):=known; name_type(p):=token; new_num_tok:=p; +end; + +@ A token list is a singly linked list of nodes in |mem|, where +each node contains a token and a link. Here's a subroutine that gets rid +of a token list when it is no longer needed. + +@p procedure@?token_recycle; forward;@t\2@>@;@/ +procedure flush_token_list(@!p:pointer); +var @!q:pointer; {the node being recycled} +begin while p<>null do + begin q:=p; p:=link(p); + if q>=hi_mem_min then free_avail(q) + else begin case type(q) of + vacuous,boolean_type,known:do_nothing; + string_type:delete_str_ref(value(q)); + unknown_types,pen_type,path_type,future_pen,picture_type, + pair_type,transform_type,dependent,proto_dependent,independent: + begin g_pointer:=q; token_recycle; + end; + othercases confusion("token") +@:this can't happen token}{\quad token@> + endcases;@/ + free_node(q,token_node_size); + end; + end; +end; + +@ The procedure |show_token_list|, which prints a symbolic form of +the token list that starts at a given node |p|, illustrates these +conventions. The token list being displayed should not begin with a reference +count. However, the procedure is intended to be fairly robust, so that if the +memory links are awry or if |p| is not really a pointer to a token list, +almost nothing catastrophic can happen. + +An additional parameter |q| is also given; this parameter is either null +or it points to a node in the token list where a certain magic computation +takes place that will be explained later. (Basically, |q| is non-null when +we are printing the two-line context information at the time of an error +message; |q| marks the place corresponding to where the second line +should begin.) + +The generation will stop, and `\.{\char`\ ETC.}' will be printed, if the length +of printing exceeds a given limit~|l|; the length of printing upon entry is +assumed to be a given amount called |null_tally|. (Note that +|show_token_list| sometimes uses itself recursively to print +variable names within a capsule.) +@^recursion@> + +Unusual entries are printed in the form of all-caps tokens +preceded by a space, e.g., `\.{\char`\ BAD}'. + +@= +procedure@?print_capsule; forward; @t\2@>@;@/ +procedure show_token_list(@!p,@!q:integer;@!l,@!null_tally:integer); +label exit; +var @!class,@!c:small_number; {the |char_class| of previous and new tokens} +@!r,@!v:integer; {temporary registers} +begin class:=percent_class; +tally:=null_tally; +while (p<>null) and (tally; + @; + class:=c; p:=link(p); + end; +if p<>null then print(" ETC."); +@.ETC@> +exit: +end; + +@ @= +c:=letter_class; {the default} +if (pmem_end) then + begin print(" CLOBBERED"); return; +@.CLOBBERED@> + end; +if p +else begin r:=info(p); + if r>=expr_base then @ + else if r<1 then + if r=0 then @ + else print(" IMPOSSIBLE") +@.IMPOSSIBLE@> + else begin r:=text(r); + if (r<0)or(r>=str_ptr) then print(" NONEXISTENT") +@.NONEXISTENT@> + else @; + end; + end + +@ @= +if name_type(p)=token then + if type(p)=known then @ + else if type(p)<>string_type then print(" BAD") +@.BAD@> + else begin print_char(""""); slow_print(value(p)); print_char(""""); + c:=string_class; + end +else if (name_type(p)<>capsule)or(type(p)independent) then + print(" BAD") +else begin g_pointer:=p; print_capsule; c:=right_paren_class; + end + +@ @= +begin if class=digit_class then print_char(" "); +v:=value(p); +if v<0 then + begin if class=left_bracket_class then print_char(" "); + print_char("["); print_scaled(v); print_char("]"); + c:=right_bracket_class; + end +else begin print_scaled(v); c:=digit_class; + end; +end + +@ Strictly speaking, a genuine token will never have |info(p)=0|. +But we will see later (in the |print_variable_name| routine) that +it is convenient to let |info(p)=0| stand for `\.{[]}'. + +@= +begin if class=left_bracket_class then print_char(" "); +print("[]"); c:=right_bracket_class; +end + +@ @= +begin if r + end +else if r + end +else begin print("(TEXT"); r:=r-(text_base); +@.TEXT@> + end; +print_int(r); print_char(")"); c:=right_paren_class; +end + +@ @= +begin c:=char_class[so(str_pool[str_start[r]])]; +if c=class then + case c of + letter_class:print_char("."); + isolated_classes:do_nothing; + othercases print_char(" ") + endcases; +slow_print(r); +end + +@ The following procedures have been declared |forward| with no parameters, +because the author dislikes \PASCAL's convention about |forward| procedures +with parameters. It was necessary to do something, because |show_token_list| +is recursive (although the recursion is limited to one level), and because +|flush_token_list| is syntactically (but not semantically) recursive. +@^recursion@> + +@= +procedure print_capsule; +begin print_char("("); print_exp(g_pointer,0); print_char(")"); +end; +@# +procedure token_recycle; +begin recycle_value(g_pointer); +end; + +@ @= +@!g_pointer:pointer; {(global) parameter to the |forward| procedures} + +@ Macro definitions are kept in \MF's memory in the form of token lists +that have a few extra one-word nodes at the beginning. + +The first node contains a reference count that is used to tell when the +list is no longer needed. To emphasize the fact that a reference count is +present, we shall refer to the |info| field of this special node as the +|ref_count| field. +@^reference counts@> + +The next node or nodes after the reference count serve to describe the +formal parameters. They consist of zero or more parameter tokens followed +by a code for the type of macro. + +@d ref_count==info {reference count preceding a macro definition or pen header} +@d add_mac_ref(#)==incr(ref_count(#)) {make a new reference to a macro list} +@d general_macro=0 {preface to a macro defined with a parameter list} +@d primary_macro=1 {preface to a macro with a \&{primary} parameter} +@d secondary_macro=2 {preface to a macro with a \&{secondary} parameter} +@d tertiary_macro=3 {preface to a macro with a \&{tertiary} parameter} +@d expr_macro=4 {preface to a macro with an undelimited \&{expr} parameter} +@d of_macro=5 {preface to a macro with + undelimited `\&{expr} |x| \&{of}~|y|' parameters} +@d suffix_macro=6 {preface to a macro with an undelimited \&{suffix} parameter} +@d text_macro=7 {preface to a macro with an undelimited \&{text} parameter} + +@p procedure delete_mac_ref(@!p:pointer); + {|p| points to the reference count of a macro list that is + losing one reference} +begin if ref_count(p)=null then flush_token_list(p) +else decr(ref_count(p)); +end; + +@ The following subroutine displays a macro, given a pointer to its +reference count. + +@p @t\4@>@@; +procedure show_macro(@!p:pointer;@!q,@!l:integer); +label exit; +var @!r:pointer; {temporary storage} +begin p:=link(p); {bypass the reference count} +while info(p)>text_macro do + begin r:=link(p); link(p):=null; + show_token_list(p,null,l,0); link(p):=r; p:=r; + if l>0 then l:=l-tally@+else return; + end; {control printing of `\.{ETC.}'} +@.ETC@> +tally:=0; +case info(p) of +general_macro:print("->"); +@.->@> +primary_macro,secondary_macro,tertiary_macro:begin print_char("<"); + print_cmd_mod(param_type,info(p)); print(">->"); + end; +expr_macro:print("->"); +of_macro:print("of->"); +suffix_macro:print("->"); +text_macro:print("->"); +end; {there are no other cases} +show_token_list(link(p),q,l-tally,0); +exit:end; + +@* \[15] Data structures for variables. +The variables of \MF\ programs can be simple, like `\.x', or they can +combine the structural properties of arrays and records, like `\.{x20a.b}'. +A \MF\ user assigns a type to a variable like \.{x20a.b} by saying, for +example, `\.{boolean} \.{x[]a.b}'. It's time for us to study how such +things are represented inside of the computer. + +Each variable value occupies two consecutive words, either in a two-word +node called a value node, or as a two-word subfield of a larger node. One +of those two words is called the |value| field; it is an integer, +containing either a |scaled| numeric value or the representation of some +other type of quantity. (It might also be subdivided into halfwords, in +which case it is referred to by other names instead of |value|.) The other +word is broken into subfields called |type|, |name_type|, and |link|. The +|type| field is a quarterword that specifies the variable's type, and +|name_type| is a quarterword from which \MF\ can reconstruct the +variable's name (sometimes by using the |link| field as well). Thus, only +1.25 words are actually devoted to the value itself; the other +three-quarters of a word are overhead, but they aren't wasted because they +allow \MF\ to deal with sparse arrays and to provide meaningful diagnostics. + +In this section we shall be concerned only with the structural aspects of +variables, not their values. Later parts of the program will change the +|type| and |value| fields, but we shall treat those fields as black boxes +whose contents should not be touched. + +However, if the |type| field is |structured|, there is no |value| field, +and the second word is broken into two pointer fields called |attr_head| +and |subscr_head|. Those fields point to additional nodes that +contain structural information, as we shall see. + +@d subscr_head_loc(#) == #+1 {where |value|, |subscr_head|, and |attr_head| are} +@d attr_head(#) == info(subscr_head_loc(#)) {pointer to attribute info} +@d subscr_head(#) == link(subscr_head_loc(#)) {pointer to subscript info} +@d value_node_size=2 {the number of words in a value node} + +@ An attribute node is three words long. Two of these words contain |type| +and |value| fields as described above, and the third word contains +additional information: There is an |attr_loc| field, which contains the +hash address of the token that names this attribute; and there's also a +|parent| field, which points to the value node of |structured| type at the +next higher level (i.e., at the level to which this attribute is +subsidiary). The |name_type| in an attribute node is `|attr|'. The +|link| field points to the next attribute with the same parent; these are +arranged in increasing order, so that |attr_loc(link(p))>attr_loc(p)|. The +final attribute node links to the constant |end_attr|, whose |attr_loc| +field is greater than any legal hash address. The |attr_head| in the +parent points to a node whose |name_type| is |structured_root|; this +node represents the null attribute, i.e., the variable that is relevant +when no attributes are attached to the parent. The |attr_head| node +has the fields of either +a value node, a subscript node, or an attribute node, depending on what +the parent would be if it were not structured; but the subscript and +attribute fields are ignored, so it effectively contains only the data of +a value node. The |link| field in this special node points to an attribute +node whose |attr_loc| field is zero; the latter node represents a collective +subscript `\.{[]}' attached to the parent, and its |link| field points to +the first non-special attribute node (or to |end_attr| if there are none). + +A subscript node likewise occupies three words, with |type| and |value| fields +plus extra information; its |name_type| is |subscr|. In this case the +third word is called the |subscript| field, which is a |scaled| integer. +The |link| field points to the subscript node with the next larger +subscript, if any; otherwise the |link| points to the attribute node +for collective subscripts at this level. We have seen that the latter node +contains an upward pointer, so that the parent can be deduced. + +The |name_type| in a parent-less value node is |root|, and the |link| +is the hash address of the token that names this value. + +In other words, variables have a hierarchical structure that includes +enough threads running around so that the program is able to move easily +between siblings, parents, and children. An example should be helpful: +(The reader is advised to draw a picture while reading the following +description, since that will help to firm up the ideas.) +Suppose that `\.x' and `\.{x.a}' and `\.{x[]b}' and `\.{x5}' +and `\.{x20b}' have been mentioned in a user's program, where +\.{x[]b} has been declared to be of \&{boolean} type. Let |h(x)|, |h(a)|, +and |h(b)| be the hash addresses of \.x, \.a, and~\.b. Then +|eq_type(h(x))=tag_token| and |equiv(h(x))=p|, where |p|~is a two-word value +node with |name_type(p)=root| and |link(p)=h(x)|. We have |type(p)=structured|, +|attr_head(p)=q|, and |subscr_head(p)=r|, where |q| points to a value +node and |r| to a subscript node. (Are you still following this? Use +a pencil to draw a diagram.) The lone variable `\.x' is represented by +|type(q)| and |value(q)|; furthermore +|name_type(q)=structured_root| and |link(q)=q1|, where |q1| points +to an attribute node representing `\.{x[]}'. Thus |name_type(q1)=attr|, +|attr_loc(q1)=collective_subscript=0|, |parent(q1)=p|, +|type(q1)=structured|, |attr_head(q1)=qq|, and |subscr_head(q1)=qq1|; +|qq| is a three-word ``attribute-as-value'' node with |type(qq)=numeric_type| +(assuming that \.{x5} is numeric, because |qq| represents `\.{x[]}' +with no further attributes), |name_type(qq)=structured_root|, +|attr_loc(qq)=0|, |parent(qq)=p|, and +|link(qq)=qq1|. (Now pay attention to the next part.) Node |qq1| is +an attribute node representing `\.{x[][]}', which has never yet +occurred; its |type| field is |undefined|, and its |value| field is +undefined. We have |name_type(qq1)=attr|, |attr_loc(qq1)=collective_subscript|, +|parent(qq1)=q1|, and |link(qq1)=qq2|. Since |qq2| represents +`\.{x[]b}', |type(qq2)=unknown_boolean|; also |attr_loc(qq2)=h(b)|, +|parent(qq2)=q1|, |name_type(qq2)=attr|, |link(qq2)=end_attr|. +(Maybe colored lines will help untangle your picture.) + Node |r| is a subscript node with |type| and |value| +representing `\.{x5}'; |name_type(r)=subscr|, |subscript(r)=5.0|, +and |link(r)=r1| is another subscript node. To complete the picture, +see if you can guess what |link(r1)| is; give up? It's~|q1|. +Furthermore |subscript(r1)=20.0|, |name_type(r1)=subscr|, +|type(r1)=structured|, |attr_head(r1)=qqq|, |subscr_head(r1)=qqq1|, +and we finish things off with three more nodes +|qqq|, |qqq1|, and |qqq2| hung onto~|r1|. (Perhaps you should start again +with a larger sheet of paper.) The value of variable `\.{x20b}' +appears in node~|qqq2=link(qqq1)|, as you can well imagine. +Similarly, the value of `\.{x.a}' appears in node |q2=link(q1)|, where +|attr_loc(q2)=h(a)| and |parent(q2)=p|. + +If the example in the previous paragraph doesn't make things crystal +clear, a glance at some of the simpler subroutines below will reveal how +things work out in practice. + +The only really unusual thing about these conventions is the use of +collective subscript attributes. The idea is to avoid repeating a lot of +type information when many elements of an array are identical macros +(for which distinct values need not be stored) or when they don't have +all of the possible attributes. Branches of the structure below collective +subscript attributes do not carry actual values except for macro identifiers; +branches of the structure below subscript nodes do not carry significant +information in their collective subscript attributes. + +@d attr_loc_loc(#)==#+2 {where the |attr_loc| and |parent| fields are} +@d attr_loc(#)==info(attr_loc_loc(#)) {hash address of this attribute} +@d parent(#)==link(attr_loc_loc(#)) {pointer to |structured| variable} +@d subscript_loc(#)==#+2 {where the |subscript| field lives} +@d subscript(#)==mem[subscript_loc(#)].sc {subscript of this variable} +@d attr_node_size=3 {the number of words in an attribute node} +@d subscr_node_size=3 {the number of words in a subscript node} +@d collective_subscript=0 {code for the attribute `\.{[]}'} + +@= +attr_loc(end_attr):=hash_end+1; parent(end_attr):=null; + +@ Variables of type \&{pair} will have values that point to four-word +nodes containing two numeric values. The first of these values has +|name_type=x_part_sector| and the second has |name_type=y_part_sector|; +the |link| in the first points back to the node whose |value| points +to this four-word node. + +Variables of type \&{transform} are similar, but in this case their +|value| points to a 12-word node containing six values, identified by +|x_part_sector|, |y_part_sector|, |xx_part_sector|, |xy_part_sector|, +|yx_part_sector|, and |yy_part_sector|. + +When an entire structured variable is saved, the |root| indication +is temporarily replaced by |saved_root|. + +Some variables have no name; they just are used for temporary storage +while expressions are being evaluated. We call them {\sl capsules}. + +@d x_part_loc(#)==# {where the \&{xpart} is found in a pair or transform node} +@d y_part_loc(#)==#+2 {where the \&{ypart} is found in a pair or transform node} +@d xx_part_loc(#)==#+4 {where the \&{xxpart} is found in a transform node} +@d xy_part_loc(#)==#+6 {where the \&{xypart} is found in a transform node} +@d yx_part_loc(#)==#+8 {where the \&{yxpart} is found in a transform node} +@d yy_part_loc(#)==#+10 {where the \&{yypart} is found in a transform node} +@# +@d pair_node_size=4 {the number of words in a pair node} +@d transform_node_size=12 {the number of words in a transform node} + +@= +@!big_node_size:array[transform_type..pair_type] of small_number; + +@ The |big_node_size| array simply contains two constants that \MF\ +occasionally needs to know. + +@= +big_node_size[transform_type]:=transform_node_size; +big_node_size[pair_type]:=pair_node_size; + +@ If |type(p)=pair_type| or |transform_type| and if |value(p)=null|, the +procedure call |init_big_node(p)| will allocate a pair or transform node +for~|p|. The individual parts of such nodes are initially of type +|independent|. + +@p procedure init_big_node(@!p:pointer); +var @!q:pointer; {the new node} +@!s:small_number; {its size} +begin s:=big_node_size[type(p)]; q:=get_node(s); +repeat s:=s-2; @; +name_type(q+s):=half(s)+x_part_sector; link(q+s):=null; +until s=0; +link(q):=p; value(p):=q; +end; + +@ The |id_transform| function creates a capsule for the +identity transformation. + +@p function id_transform:pointer; +var @!p,@!q,@!r:pointer; {list manipulation registers} +begin p:=get_node(value_node_size); type(p):=transform_type; +name_type(p):=capsule; value(p):=null; init_big_node(p); q:=value(p); +r:=q+transform_node_size; +repeat r:=r-2; +type(r):=known; value(r):=0; +until r=q; +value(xx_part_loc(q)):=unity; value(yy_part_loc(q)):=unity; +id_transform:=p; +end; + +@ Tokens are of type |tag_token| when they first appear, but they point +to |null| until they are first used as the root of a variable. +The following subroutine establishes the root node on such grand occasions. + +@p procedure new_root(@!x:pointer); +var @!p:pointer; {the new node} +begin p:=get_node(value_node_size); type(p):=undefined; name_type(p):=root; +link(p):=x; equiv(x):=p; +end; + +@ These conventions for variable representation are illustrated by the +|print_variable_name| routine, which displays the full name of a +variable given only a pointer to its two-word value packet. + +@p procedure print_variable_name(@!p:pointer); +label found,exit; +var @!q:pointer; {a token list that will name the variable's suffix} +@!r:pointer; {temporary for token list creation} +begin while name_type(p)>=x_part_sector do + @; +q:=null; +while name_type(p)>saved_root do + @; +r:=get_avail; info(r):=link(p); link(r):=q; +if name_type(p)=saved_root then print("(SAVED)"); +@.SAVED@> +show_token_list(r,null,el_gordo,tally); flush_token_list(r); +exit:end; + +@ @= +begin if name_type(p)=subscr then + begin r:=new_num_tok(subscript(p)); + repeat p:=link(p); + until name_type(p)=attr; + end +else if name_type(p)=structured_root then + begin p:=link(p); goto found; + end +else begin if name_type(p)<>attr then confusion("var"); +@:this can't happen var}{\quad var@> + r:=get_avail; info(r):=attr_loc(p); + end; +link(r):=q; q:=r; +found: p:=parent(p); +end + +@ @= +begin case name_type(p) of +x_part_sector: print_char("x"); +y_part_sector: print_char("y"); +xx_part_sector: print("xx"); +xy_part_sector: print("xy"); +yx_part_sector: print("yx"); +yy_part_sector: print("yy"); +capsule: begin print("%CAPSULE"); print_int(p-null); return; +@.CAPSULE@> + end; +end; {there are no other cases} +print("part "); p:=link(p-2*(name_type(p)-x_part_sector)); +end + +@ The |interesting| function returns |true| if a given variable is not +in a capsule, or if the user wants to trace capsules. + +@p function interesting(@!p:pointer):boolean; +var @!t:small_number; {a |name_type|} +begin if internal[tracing_capsules]>0 then interesting:=true +else begin t:=name_type(p); + if t>=x_part_sector then if t<>capsule then + t:=name_type(link(p-2*(t-x_part_sector))); + interesting:=(t<>capsule); + end; +end; + +@ Now here is a subroutine that converts an unstructured type into an +equivalent structured type, by inserting a |structured| node that is +capable of growing. This operation is done only when |name_type(p)=root|, +|subscr|, or |attr|. + +The procedure returns a pointer to the new node that has taken node~|p|'s +place in the structure. Node~|p| itself does not move, nor are its +|value| or |type| fields changed in any way. + +@p function new_structure(@!p:pointer):pointer; +var @!q,@!r:pointer; {list manipulation registers} +begin case name_type(p) of +root: begin q:=link(p); r:=get_node(value_node_size); equiv(q):=r; + end; +subscr: @; +attr: @; +othercases confusion("struct") +@:this can't happen struct}{\quad struct@> +endcases;@/ +link(r):=link(p); type(r):=structured; name_type(r):=name_type(p); +attr_head(r):=p; name_type(p):=structured_root;@/ +q:=get_node(attr_node_size); link(p):=q; subscr_head(r):=q; +parent(q):=r; type(q):=undefined; name_type(q):=attr; link(q):=end_attr; +attr_loc(q):=collective_subscript; new_structure:=r; +end; + +@ @= +begin q:=p; +repeat q:=link(q); +until name_type(q)=attr; +q:=parent(q); r:=subscr_head_loc(q); {|link(r)=subscr_head(q)|} +repeat q:=r; r:=link(r); +until r=p; +r:=get_node(subscr_node_size); +link(q):=r; subscript(r):=subscript(p); +end + +@ If the attribute is |collective_subscript|, there are two pointers to +node~|p|, so we must change both of them. + +@= +begin q:=parent(p); r:=attr_head(q); +repeat q:=r; r:=link(r); +until r=p; +r:=get_node(attr_node_size); link(q):=r;@/ +mem[attr_loc_loc(r)]:=mem[attr_loc_loc(p)]; {copy |attr_loc| and |parent|} +if attr_loc(p)=collective_subscript then + begin q:=subscr_head_loc(parent(p)); + while link(q)<>p do q:=link(q); + link(q):=r; + end; +end + +@ The |find_variable| routine is given a pointer~|t| to a nonempty token +list of suffixes; it returns a pointer to the corresponding two-word +value. For example, if |t| points to token \.x followed by a numeric +token containing the value~7, |find_variable| finds where the value of +\.{x7} is stored in memory. This may seem a simple task, and it +usually is, except when \.{x7} has never been referenced before. +Indeed, \.x may never have even been subscripted before; complexities +arise with respect to updating the collective subscript information. + +If a macro type is detected anywhere along path~|t|, or if the first +item on |t| isn't a |tag_token|, the value |null| is returned. +Otherwise |p| will be a non-null pointer to a node such that +|undefined +begin p:=info(t); t:=link(t); +if eq_type(p) mod outer_tag<>tag_token then abort_find; +if equiv(p)=null then new_root(p); +p:=equiv(p); pp:=p; +while t<>null do + begin @; + if t + else @; + t:=link(t); + end; +if type(pp)>=structured then + if type(pp)=structured then pp:=attr_head(pp)@+else abort_find; +if type(p)=structured then p:=attr_head(p); +if type(p)=undefined then + begin if type(pp)=undefined then + begin type(pp):=numeric_type; value(pp):=null; + end; + type(p):=type(pp); value(p):=null; + end; +find_variable:=p; +exit:end; + +@ Although |pp| and |p| begin together, they diverge when a subscript occurs; +|pp|~stays in the collective line while |p|~goes through actual subscript +values. + +@= +if type(pp)<>structured then + begin if type(pp)>structured then abort_find; + ss:=new_structure(pp); + if p=pp then p:=ss; + pp:=ss; + end; {now |type(pp)=structured|} +if type(p)<>structured then {it cannot be |>structured|} + p:=new_structure(p) {now |type(p)=structured|} + +@ We want this part of the program to be reasonably fast, in case there are +@^inner loop@> +lots of subscripts at the same level of the data structure. Therefore +we store an ``infinite'' value in the word that appears at the end of the +subscript list, even though that word isn't part of a subscript node. + +@= +begin n:=value(t); +pp:=link(attr_head(pp)); {now |attr_loc(pp)=collective_subscript|} +q:=link(attr_head(p)); save_word:=mem[subscript_loc(q)]; +subscript(q):=el_gordo; s:=subscr_head_loc(p); {|link(s)=subscr_head(p)|} +repeat r:=s; s:=link(s); +until n<=subscript(s); +if n=subscript(s) then p:=s +else begin p:=get_node(subscr_node_size); link(r):=p; link(p):=s; + subscript(p):=n; name_type(p):=subscr; type(p):=undefined; + end; +mem[subscript_loc(q)]:=save_word; +end + +@ @= +begin n:=info(t); +ss:=attr_head(pp); +repeat rr:=ss; ss:=link(ss); +until n<=attr_loc(ss); +if n + +Parameter |p| points to the root information of the variable; +parameter |t| points to a list of one-word nodes that represent +suffixes, with |info=collective_subscript| for subscripts. + +@p @t\4@>@@;@/ +@t\4@>@@; +@t\4@>@@; +@t\4@>@@; +@t\4@>@@; +procedure flush_variable(@!p,@!t:pointer;@!discard_suffixes:boolean); +label exit; +var @!q,@!r:pointer; {list manipulation} +@!n:halfword; {attribute to match} +begin while t<>null do + begin if type(p)<>structured then return; + n:=info(t); t:=link(t); + if n=collective_subscript then + begin r:=subscr_head_loc(p); q:=link(r); {|q=subscr_head(p)|} + while name_type(q)=subscr do + begin flush_variable(q,t,discard_suffixes); + if t=null then + if type(q)=structured then r:=q + else begin link(r):=link(q); free_node(q,subscr_node_size); + end + else r:=q; + q:=link(r); + end; + end; + p:=attr_head(p); + repeat r:=p; p:=link(p); + until attr_loc(p)>=n; + if attr_loc(p)<>n then return; + end; +if discard_suffixes then flush_below_variable(p) +else begin if type(p)=structured then p:=attr_head(p); + recycle_value(p); + end; +exit:end; + +@ The next procedure is simpler; it wipes out everything but |p| itself, +which becomes undefined. + +@= +procedure flush_below_variable(@!p:pointer); +var @!q,@!r:pointer; {list manipulation registers} +begin if type(p)<>structured then + recycle_value(p) {this sets |type(p)=undefined|} +else begin q:=subscr_head(p); + while name_type(q)=subscr do + begin flush_below_variable(q); r:=q; q:=link(q); + free_node(r,subscr_node_size); + end; + r:=attr_head(p); q:=link(r); recycle_value(r); + if name_type(p)<=saved_root then free_node(r,value_node_size) + else free_node(r,subscr_node_size); + {we assume that |subscr_node_size=attr_node_size|} + repeat flush_below_variable(q); r:=q; q:=link(q); free_node(r,attr_node_size); + until q=end_attr; + type(p):=undefined; + end; +end; + +@ Just before assigning a new value to a variable, we will recycle the +old value and make the old value undefined. The |und_type| routine +determines what type of undefined value should be given, based on +the current type before recycling. + +@p function und_type(@!p:pointer):small_number; +begin case type(p) of +undefined,vacuous:und_type:=undefined; +boolean_type,unknown_boolean:und_type:=unknown_boolean; +string_type,unknown_string:und_type:=unknown_string; +pen_type,unknown_pen,future_pen:und_type:=unknown_pen; +path_type,unknown_path:und_type:=unknown_path; +picture_type,unknown_picture:und_type:=unknown_picture; +transform_type,pair_type,numeric_type:und_type:=type(p); +known,dependent,proto_dependent,independent:und_type:=numeric_type; +end; {there are no other cases} +end; + +@ The |clear_symbol| routine is used when we want to redefine the equivalent +of a symbolic token. It must remove any variable structure or macro +definition that is currently attached to that symbol. If the |saving| +parameter is true, a subsidiary structure is saved instead of destroyed. + +@p procedure clear_symbol(@!p:pointer;@!saving:boolean); +var @!q:pointer; {|equiv(p)|} +begin q:=equiv(p); +case eq_type(p) mod outer_tag of +defined_macro,secondary_primary_macro,tertiary_secondary_macro, + expression_tertiary_macro: if not saving then delete_mac_ref(q); +tag_token:if q<>null then + if saving then name_type(q):=saved_root + else begin flush_below_variable(q); free_node(q,value_node_size); + end;@; +othercases do_nothing +endcases;@/ +eqtb[p]:=eqtb[frozen_undefined]; +end; + +@* \[16] Saving and restoring equivalents. +The nested structure provided by \&{begingroup} and \&{endgroup} +allows |eqtb| entries to be saved and restored, so that temporary changes +can be made without difficulty. When the user requests a current value to +be saved, \MF\ puts that value into its ``save stack.'' An appearance of +\&{endgroup} ultimately causes the old values to be removed from the save +stack and put back in their former places. + +The save stack is a linked list containing three kinds of entries, +distinguished by their |info| fields. If |p| points to a saved item, +then + +\smallskip\hang +|info(p)=0| stands for a group boundary; each \&{begingroup} contributes +such an item to the save stack and each \&{endgroup} cuts back the stack +until the most recent such entry has been removed. + +\smallskip\hang +|info(p)=q|, where |1<=q<=hash_end|, means that |mem[p+1]| holds the former +contents of |eqtb[q]|. Such save stack entries are generated by \&{save} +commands. + +\smallskip\hang +|info(p)=hash_end+q|, where |q>0|, means that |value(p)| is a |scaled| +integer to be restored to internal parameter number~|q|. Such entries +are generated by \&{interim} commands. + +\smallskip\noindent +The global variable |save_ptr| points to the top item on the save stack. + +@d save_node_size=2 {number of words per non-boundary save-stack node} +@d saved_equiv(#)==mem[#+1].hh {where an |eqtb| entry gets saved} +@d save_boundary_item(#)==begin #:=get_avail; info(#):=0; + link(#):=save_ptr; save_ptr:=#; + end + +@=@!save_ptr:pointer; {the most recently saved item} + +@ @=save_ptr:=null; + +@ The |save_variable| routine is given a hash address |q|; it salts this +address in the save stack, together with its current equivalent, +then makes token~|q| behave as though it were brand new. + +Nothing is stacked when |save_ptr=null|, however; there's no way to remove +things from the stack when the program is not inside a group, so there's +no point in wasting the space. + +@p procedure save_variable(@!q:pointer); +var @!p:pointer; {temporary register} +begin if save_ptr<>null then + begin p:=get_node(save_node_size); info(p):=q; link(p):=save_ptr; + saved_equiv(p):=eqtb[q]; save_ptr:=p; + end; +clear_symbol(q,(save_ptr<>null)); +end; + +@ Similarly, |save_internal| is given the location |q| of an internal +quantity like |tracing_pens|. It creates a save stack entry of the +third kind. + +@p procedure save_internal(@!q:halfword); +var @!p:pointer; {new item for the save stack} +begin if save_ptr<>null then + begin p:=get_node(save_node_size); info(p):=hash_end+q; + link(p):=save_ptr; value(p):=internal[q]; save_ptr:=p; + end; +end; + +@ At the end of a group, the |unsave| routine restores all of the saved +equivalents in reverse order. This routine will be called only when there +is at least one boundary item on the save stack. + +@p procedure unsave; +var @!q:pointer; {index to saved item} +@!p:pointer; {temporary register} +begin while info(save_ptr)<>0 do + begin q:=info(save_ptr); + if q>hash_end then + begin if internal[tracing_restores]>0 then + begin begin_diagnostic; print_nl("{restoring "); + slow_print(int_name[q-(hash_end)]); print_char("="); + print_scaled(value(save_ptr)); print_char("}"); + end_diagnostic(false); + end; + internal[q-(hash_end)]:=value(save_ptr); + end + else begin if internal[tracing_restores]>0 then + begin begin_diagnostic; print_nl("{restoring "); + slow_print(text(q)); print_char("}"); + end_diagnostic(false); + end; + clear_symbol(q,false); + eqtb[q]:=saved_equiv(save_ptr); + if eq_type(q) mod outer_tag=tag_token then + begin p:=equiv(q); + if p<>null then name_type(p):=root; + end; + end; + p:=link(save_ptr); free_node(save_ptr,save_node_size); save_ptr:=p; + end; +p:=link(save_ptr); free_avail(save_ptr); save_ptr:=p; +end; + +@* \[17] Data structures for paths. +When a \MF\ user specifies a path, \MF\ will create a list of knots +and control points for the associated cubic spline curves. If the +knots are $z_0$, $z_1$, \dots, $z_n$, there are control points +$z_k^+$ and $z_{k+1}^-$ such that the cubic splines between knots +$z_k$ and $z_{k+1}$ are defined by B\'ezier's formula +@:Bezier}{B\'ezier, Pierre Etienne@> +$$\eqalign{z(t)&=B(z_k,z_k^+,z_{k+1}^-,z_{k+1};t)\cr +&=(1-t)^3z_k+3(1-t)^2tz_k^++3(1-t)t^2z_{k+1}^-+t^3z_{k+1}\cr}$$ +for |0<=t<=1|. + +There is a 7-word node for each knot $z_k$, containing one word of +control information and six words for the |x| and |y| coordinates +of $z_k^-$ and $z_k$ and~$z_k^+$. The control information appears +in the |left_type| and |right_type| fields, which each occupy +a quarter of the first word in the node; they specify properties +of the curve as it enters and leaves the knot. There's also a +halfword |link| field, which points to the following knot. + +If the path is a closed contour, knots 0 and |n| are identical; +i.e., the |link| in knot |n-1| points to knot~0. But if the path +is not closed, the |left_type| of knot~0 and the |right_type| of knot~|n| +are equal to |endpoint|. In the latter case the |link| in knot~|n| points +to knot~0, and the control points $z_0^-$ and $z_n^+$ are not used. + +@d left_type(#) == mem[#].hh.b0 {characterizes the path entering this knot} +@d right_type(#) == mem[#].hh.b1 {characterizes the path leaving this knot} +@d endpoint=0 {|left_type| at path beginning and |right_type| at path end} +@d x_coord(#) == mem[#+1].sc {the |x| coordinate of this knot} +@d y_coord(#) == mem[#+2].sc {the |y| coordinate of this knot} +@d left_x(#) == mem[#+3].sc {the |x| coordinate of previous control point} +@d left_y(#) == mem[#+4].sc {the |y| coordinate of previous control point} +@d right_x(#) == mem[#+5].sc {the |x| coordinate of next control point} +@d right_y(#) == mem[#+6].sc {the |y| coordinate of next control point} +@d knot_node_size=7 {number of words in a knot node} + +@ Before the B\'ezier control points have been calculated, the memory +space they will ultimately occupy is taken up by information that can be +used to compute them. There are four cases: + +\yskip +\textindent{$\bullet$} If |right_type=open|, the curve should leave +the knot in the same direction it entered; \MF\ will figure out a +suitable direction. + +\yskip +\textindent{$\bullet$} If |right_type=curl|, the curve should leave the +knot in a direction depending on the angle at which it enters the next +knot and on the curl parameter stored in |right_curl|. + +\yskip +\textindent{$\bullet$} If |right_type=given|, the curve should leave the +knot in a nonzero direction stored as an |angle| in |right_given|. + +\yskip +\textindent{$\bullet$} If |right_type=explicit|, the B\'ezier control +point for leaving this knot has already been computed; it is in the +|right_x| and |right_y| fields. + +\yskip\noindent +The rules for |left_type| are similar, but they refer to the curve entering +the knot, and to \\{left} fields instead of \\{right} fields. + +Non-|explicit| control points will be chosen based on ``tension'' parameters +in the |left_tension| and |right_tension| fields. The +`\&{atleast}' option is represented by negative tension values. +@:at_least_}{\&{atleast} primitive@> + +For example, the \MF\ path specification +$$\.{z0..z1..tension atleast 1..\{curl 2\}z2..z3\{-1,-2\}..tension + 3 and 4..p},$$ +where \.p is the path `\.{z4..controls z45 and z54..z5}', will be represented +by the six knots +\def\lodash{\hbox to 1.1em{\thinspace\hrulefill\thinspace}} +$$\vbox{\halign{#\hfil&&\qquad#\hfil\cr +|left_type|&\\{left} info&|x_coord,y_coord|&|right_type|&\\{right} info\cr +\noalign{\yskip} +|endpoint|&\lodash$,\,$\lodash&$x_0,y_0$&|curl|&$1.0,1.0$\cr +|open|&\lodash$,1.0$&$x_1,y_1$&|open|&\lodash$,-1.0$\cr +|curl|&$2.0,-1.0$&$x_2,y_2$&|curl|&$2.0,1.0$\cr +|given|&$d,1.0$&$x_3,y_3$&|given|&$d,3.0$\cr +|open|&\lodash$,4.0$&$x_4,y_4$&|explicit|&$x_{45},y_{45}$\cr +|explicit|&$x_{54},y_{54}$&$x_5,y_5$&|endpoint|&\lodash$,\,$\lodash\cr}}$$ +Here |d| is the |angle| obtained by calling |n_arg(-unity,-two)|. +Of course, this example is more complicated than anything a normal user +would ever write. + +These types must satisfy certain restrictions because of the form of \MF's +path syntax: +(i)~|open| type never appears in the same node together with |endpoint|, +|given|, or |curl|. +(ii)~The |right_type| of a node is |explicit| if and only if the +|left_type| of the following node is |explicit|. +(iii)~|endpoint| types occur only at the ends, as mentioned above. + +@d left_curl==left_x {curl information when entering this knot} +@d left_given==left_x {given direction when entering this knot} +@d left_tension==left_y {tension information when entering this knot} +@d right_curl==right_x {curl information when leaving this knot} +@d right_given==right_x {given direction when leaving this knot} +@d right_tension==right_y {tension information when leaving this knot} +@d explicit=1 {|left_type| or |right_type| when control points are known} +@d given=2 {|left_type| or |right_type| when a direction is given} +@d curl=3 {|left_type| or |right_type| when a curl is desired} +@d open=4 {|left_type| or |right_type| when \MF\ should choose the direction} + +@ Here is a diagnostic routine that prints a given knot list +in symbolic form. It illustrates the conventions discussed above, +and checks for anomalies that might arise while \MF\ is being debugged. + +@= +procedure print_path(@!h:pointer;@!s:str_number;@!nuline:boolean); +label done,done1; +var @!p,@!q:pointer; {for list traversal} +begin print_diagnostic("Path",s,nuline); print_ln; +@.Path at line...@> +p:=h; +repeat q:=link(p); +if (p=null)or(q=null) then + begin print_nl("???"); goto done; {this won't happen} +@.???@> + end; +@; +p:=q; +if (p<>h)or(left_type(h)<>endpoint) then + @; +until p=h; +if left_type(h)<>endpoint then print("cycle"); +done:end_diagnostic(true); +end; + +@ @= +print_two(x_coord(p),y_coord(p)); +case right_type(p) of +endpoint: begin if left_type(p)=open then print("{open?}"); {can't happen} +@.open?@> + if (left_type(q)<>endpoint)or(q<>h) then q:=null; {force an error} + goto done1; + end; +explicit: @; +open: @; +curl,given: @; +othercases print("???") {can't happen} +@.???@> +endcases;@/ +if left_type(q)<=explicit then print("..control?") {can't happen} +@.control?@> +else if (right_tension(p)<>unity)or(left_tension(q)<>unity) then + @; +done1: + +@ Since |n_sin_cos| produces |fraction| results, which we will print as if they +were |scaled|, the magnitude of a |given| direction vector will be~4096. + +@= +begin print_nl(" .."); +if left_type(p)=given then + begin n_sin_cos(left_given(p)); print_char("{"); + print_scaled(n_cos); print_char(","); + print_scaled(n_sin); print_char("}"); + end +else if left_type(p)=curl then + begin print("{curl "); print_scaled(left_curl(p)); print_char("}"); + end; +end + +@ @= +begin print("..tension "); +if right_tension(p)<0 then print("atleast"); +print_scaled(abs(right_tension(p))); +if right_tension(p)<>left_tension(q) then + begin print(" and "); + if left_tension(q)<0 then print("atleast"); + print_scaled(abs(left_tension(q))); + end; +end + +@ @= +begin print("..controls "); print_two(right_x(p),right_y(p)); print(" and "); +if left_type(q)<>explicit then print("??") {can't happen} +@.??@> +else print_two(left_x(q),left_y(q)); +goto done1; +end + +@ @= +if (left_type(p)<>explicit)and(left_type(p)<>open) then + print("{open?}") {can't happen} +@.open?@> + +@ A curl of 1 is shown explicitly, so that the user sees clearly that +\MF's default curl is present. + +@= +begin if left_type(p)=open then print("??"); {can't happen} +@.??@> +if right_type(p)=curl then + begin print("{curl "); print_scaled(right_curl(p)); + end +else begin n_sin_cos(right_given(p)); print_char("{"); + print_scaled(n_cos); print_char(","); print_scaled(n_sin); + end; +print_char("}"); +end + +@ If we want to duplicate a knot node, we can say |copy_knot|: + +@p function copy_knot(@!p:pointer):pointer; +var @!q:pointer; {the copy} +@!k:0..knot_node_size-1; {runs through the words of a knot node} +begin q:=get_node(knot_node_size); +for k:=0 to knot_node_size-1 do mem[q+k]:=mem[p+k]; +copy_knot:=q; +end; + +@ The |copy_path| routine makes a clone of a given path. + +@p function copy_path(@!p:pointer):pointer; +label exit; +var @!q,@!pp,@!qq:pointer; {for list manipulation} +begin q:=get_node(knot_node_size); {this will correspond to |p|} +qq:=q; pp:=p; +loop@+ begin left_type(qq):=left_type(pp); + right_type(qq):=right_type(pp);@/ + x_coord(qq):=x_coord(pp); y_coord(qq):=y_coord(pp);@/ + left_x(qq):=left_x(pp); left_y(qq):=left_y(pp);@/ + right_x(qq):=right_x(pp); right_y(qq):=right_y(pp);@/ + if link(pp)=p then + begin link(qq):=q; copy_path:=q; return; + end; + link(qq):=get_node(knot_node_size); qq:=link(qq); pp:=link(pp); + end; +exit:end; + +@ Similarly, there's a way to copy the {\sl reverse\/} of a path. This procedure +returns a pointer to the first node of the copy, if the path is a cycle, +but to the final node of a non-cyclic copy. The global +variable |path_tail| will point to the final node of the original path; +this trick makes it easier to implement `\&{doublepath}'. + +All node types are assumed to be |endpoint| or |explicit| only. + +@p function htap_ypoc(@!p:pointer):pointer; +label exit; +var @!q,@!pp,@!qq,@!rr:pointer; {for list manipulation} +begin q:=get_node(knot_node_size); {this will correspond to |p|} +qq:=q; pp:=p; +loop@+ begin right_type(qq):=left_type(pp); left_type(qq):=right_type(pp);@/ + x_coord(qq):=x_coord(pp); y_coord(qq):=y_coord(pp);@/ + right_x(qq):=left_x(pp); right_y(qq):=left_y(pp);@/ + left_x(qq):=right_x(pp); left_y(qq):=right_y(pp);@/ + if link(pp)=p then + begin link(q):=qq; path_tail:=pp; htap_ypoc:=q; return; + end; + rr:=get_node(knot_node_size); link(rr):=qq; qq:=rr; pp:=link(pp); + end; +exit:end; + +@ @= +@!path_tail:pointer; {the node that links to the beginning of a path} + +@ When a cyclic list of knot nodes is no longer needed, it can be recycled by +calling the following subroutine. + +@= +procedure toss_knot_list(@!p:pointer); +var @!q:pointer; {the node being freed} +@!r:pointer; {the next node} +begin q:=p; +repeat r:=link(q); free_node(q,knot_node_size); q:=r; +until q=p; +end; + +@* \[18] Choosing control points. +Now we must actually delve into one of \MF's more difficult routines, +the |make_choices| procedure that chooses angles and control points for +the splines of a curve when the user has not specified them explicitly. +The parameter to |make_choices| points to a list of knots and +path information, as described above. + +A path decomposes into independent segments at ``breakpoint'' knots, +which are knots whose left and right angles are both prespecified in +some way (i.e., their |left_type| and |right_type| aren't both open). + +@p @t\4@>@@; +procedure make_choices(@!knots:pointer); +label done; +var @!h:pointer; {the first breakpoint} +@!p,@!q:pointer; {consecutive breakpoints being processed} +@@; +begin check_arith; {make sure that |arith_error=false|} +if internal[tracing_choices]>0 then + print_path(knots,", before choices",true); +@; +@; +p:=h; +repeat @; +until p=h; +if internal[tracing_choices]>0 then + print_path(knots,", after choices",true); +if arith_error then @; +end; + +@ @= +begin print_err("Some number got too big"); +@.Some number got too big@> +help2("The path that I just computed is out of range.")@/ + ("So it will probably look funny. Proceed, for a laugh."); +put_get_error; arith_error:=false; +end + +@ Two knots in a row with the same coordinates will always be joined +by an explicit ``curve'' whose control points are identical with the +knots. + +@= +p:=knots; +repeat q:=link(p); +if x_coord(p)=x_coord(q) then if y_coord(p)=y_coord(q) then + if right_type(p)>explicit then + begin right_type(p):=explicit; + if left_type(p)=open then + begin left_type(p):=curl; left_curl(p):=unity; + end; + left_type(q):=explicit; + if right_type(q)=open then + begin right_type(q):=curl; right_curl(q):=unity; + end; + right_x(p):=x_coord(p); left_x(q):=x_coord(p);@/ + right_y(p):=y_coord(p); left_y(q):=y_coord(p); + end; +p:=q; +until p=knots + +@ If there are no breakpoints, it is necessary to compute the direction +angles around an entire cycle. In this case the |left_type| of the first +node is temporarily changed to |end_cycle|. + +@d end_cycle=open+1 + +@= +h:=knots; +loop@+ begin if left_type(h)<>open then goto done; + if right_type(h)<>open then goto done; + h:=link(h); + if h=knots then + begin left_type(h):=end_cycle; goto done; + end; + end; +done: + +@ If |right_type(p)= +q:=link(p); +if right_type(p)>=given then + begin while (left_type(q)=open)and(right_type(q)=open) do q:=link(q); + @; + end; +p:=q + +@ Before we can go further into the way choices are made, we need to +consider the underlying theory. The basic ideas implemented in |make_choices| +are due to John Hobby, who introduced the notion of ``mock curvature'' +@^Hobby, John Douglas@> +at a knot. Angles are chosen so that they preserve mock curvature when +a knot is passed, and this has been found to produce excellent results. + +It is convenient to introduce some notations that simplify the necessary +formulas. Let $d_{k,k+1}=\vert z\k-z_k\vert$ be the (nonzero) distance +between knots |k| and |k+1|; and let +$${z\k-z_k\over z_k-z_{k-1}}={d_{k,k+1}\over d_{k-1,k}}e^{i\psi_k}$$ +so that a polygonal line from $z_{k-1}$ to $z_k$ to $z\k$ turns left +through an angle of~$\psi_k$. We assume that $\vert\psi_k\vert\L180^\circ$. +The control points for the spline from $z_k$ to $z\k$ will be denoted by +$$\eqalign{z_k^+&=z_k+ + \textstyle{1\over3}\rho_k e^{i\theta_k}(z\k-z_k),\cr + z\k^-&=z\k- + \textstyle{1\over3}\sigma\k e^{-i\phi\k}(z\k-z_k),\cr}$$ +where $\rho_k$ and $\sigma\k$ are nonnegative ``velocity ratios'' at the +beginning and end of the curve, while $\theta_k$ and $\phi\k$ are the +corresponding ``offset angles.'' These angles satisfy the condition +$$\theta_k+\phi_k+\psi_k=0,\eqno(*)$$ +whenever the curve leaves an intermediate knot~|k| in the direction that +it enters. + +@ Let $\alpha_k$ and $\beta\k$ be the reciprocals of the ``tension'' of +the curve at its beginning and ending points. This means that +$\rho_k=\alpha_k f(\theta_k,\phi\k)$ and $\sigma\k=\beta\k f(\phi\k,\theta_k)$, +where $f(\theta,\phi)$ is \MF's standard velocity function defined in +the |velocity| subroutine. The cubic spline $B(z_k^{\phantom+},z_k^+, +z\k^-,z\k^{\phantom+};t)$ +has curvature +@^curvature@> +$${2\sigma\k\sin(\theta_k+\phi\k)-6\sin\theta_k\over\rho_k^2d_{k,k+1}} +\qquad{\rm and}\qquad +{2\rho_k\sin(\theta_k+\phi\k)-6\sin\phi\k\over\sigma\k^2d_{k,k+1}}$$ +at |t=0| and |t=1|, respectively. The mock curvature is the linear +@^mock curvature@> +approximation to this true curvature that arises in the limit for +small $\theta_k$ and~$\phi\k$, if second-order terms are discarded. +The standard velocity function satisfies +$$f(\theta,\phi)=1+O(\theta^2+\theta\phi+\phi^2);$$ +hence the mock curvatures are respectively +$${2\beta\k(\theta_k+\phi\k)-6\theta_k\over\alpha_k^2d_{k,k+1}} +\qquad{\rm and}\qquad +{2\alpha_k(\theta_k+\phi\k)-6\phi\k\over\beta\k^2d_{k,k+1}}.\eqno(**)$$ + +@ The turning angles $\psi_k$ are given, and equation $(*)$ above +determines $\phi_k$ when $\theta_k$ is known, so the task of +angle selection is essentially to choose appropriate values for each +$\theta_k$. When equation~$(*)$ is used to eliminate $\phi$~variables +from $(**)$, we obtain a system of linear equations of the form +$$A_k\theta_{k-1}+(B_k+C_k)\theta_k+D_k\theta\k=-B_k\psi_k-D_k\psi\k,$$ +where +$$A_k={\alpha_{k-1}\over\beta_k^2d_{k-1,k}}, +\qquad B_k={3-\alpha_{k-1}\over\beta_k^2d_{k-1,k}}, +\qquad C_k={3-\beta\k\over\alpha_k^2d_{k,k+1}}, +\qquad D_k={\beta\k\over\alpha_k^2d_{k,k+1}}.$$ +The tensions are always $3\over4$ or more, hence each $\alpha$ and~$\beta$ +will be at most $4\over3$. It follows that $B_k\G{5\over4}A_k$ and +$C_k\G{5\over4}D_k$; hence the equations are diagonally dominant; +hence they have a unique solution. Moreover, in most cases the tensions +are equal to~1, so that $B_k=2A_k$ and $C_k=2D_k$. This makes the +solution numerically stable, and there is an exponential damping +effect: The data at knot $k\pm j$ affects the angle at knot~$k$ by +a factor of~$O(2^{-j})$. + +@ However, we still must consider the angles at the starting and ending +knots of a non-cyclic path. These angles might be given explicitly, or +they might be specified implicitly in terms of an amount of ``curl.'' + +Let's assume that angles need to be determined for a non-cyclic path +starting at $z_0$ and ending at~$z_n$. Then equations of the form +$$A_k\theta_{k-1}+(B_k+C_k)\theta_k+D_k\theta_{k+1}=R_k$$ +have been given for $00$ and $C_0B_1-A_1D_0>0$ when $\gamma_0\G0$, +hence the linear equations remain nonsingular. + +Similar considerations apply at the right end, when the final angle $\phi_n$ +may or may not need to be determined. It is convenient to let $\psi_n=0$, +hence $\theta_n=-\phi_n$. We either have an explicit equation $\theta_n=E_n$, +or we have +$$\bigl((3-\beta_n)\chi_n+\alpha_{n-1}\bigr)\theta_{n-1}+ +(\beta_n\chi_n+3-\alpha_{n-1})\theta_n=0,\qquad + \chi_n={\beta_n^2\gamma_n\over\alpha_{n-1}^2}.$$ + +When |make_choices| chooses angles, it must compute the coefficients of +these linear equations, then solve the equations. To compute the coefficients, +it is necessary to compute arctangents of the given turning angles~$\psi_k$. +When the equations are solved, the chosen directions $\theta_k$ are put +back into the form of control points by essentially computing sines and +cosines. + +@ OK, we are ready to make the hard choices of |make_choices|. +Most of the work is relegated to an auxiliary procedure +called |solve_choices|, which has been introduced to keep +|make_choices| from being extremely long. + +@= +@; +@; +solve_choices(p,q,n) + +@ It's convenient to precompute quantities that will be needed several +times later. The values of |delta_x[k]| and |delta_y[k]| will be the +coordinates of $z\k-z_k$, and the magnitude of this vector will be +|delta[k]=@t$d_{k,k+1}$@>|. The path angle $\psi_k$ between $z_k-z_{k-1}$ +and $z\k-z_k$ will be stored in |psi[k]|. + +@= +@!delta_x,@!delta_y,@!delta:array[0..path_size] of scaled; {knot differences} +@!psi:array[1..path_size] of angle; {turning angles} + +@ @= +@!k,@!n:0..path_size; {current and final knot numbers} +@!s,@!t:pointer; {registers for list traversal} +@!delx,@!dely:scaled; {directions where |open| meets |explicit|} +@!sine,@!cosine:fraction; {trig functions of various angles} + +@ @= +k:=0; s:=p; n:=path_size; +repeat t:=link(s); +delta_x[k]:=x_coord(t)-x_coord(s); +delta_y[k]:=y_coord(t)-y_coord(s); +delta[k]:=pyth_add(delta_x[k],delta_y[k]); +if k>0 then + begin sine:=make_fraction(delta_y[k-1],delta[k-1]); + cosine:=make_fraction(delta_x[k-1],delta[k-1]); + psi[k]:=n_arg(take_fraction(delta_x[k],cosine)+ + take_fraction(delta_y[k],sine), + take_fraction(delta_y[k],cosine)- + take_fraction(delta_x[k],sine)); + end; +@:METAFONT capacity exceeded path size}{\quad path size@> +incr(k); s:=t; +if k=path_size then overflow("path size",path_size); +if s=q then n:=k; +until (k>=n)and(left_type(s)<>end_cycle); +if k=n then psi[n]:=0@+else psi[k]:=psi[1] + +@ When we get to this point of the code, |right_type(p)| is either +|given| or |curl| or |open|. If it is |open|, we must have +|left_type(p)=end_cycle| or |left_type(p)=explicit|. In the latter +case, the |open| type is converted to |given|; however, if the +velocity coming into this knot is zero, the |open| type is +converted to a |curl|, since we don't know the incoming direction. + +Similarly, |left_type(q)| is either |given| or |curl| or |open| or +|end_cycle|. The |open| possibility is reduced either to |given| or to |curl|. + +@= +if left_type(q)=open then + begin delx:=right_x(q)-x_coord(q); dely:=right_y(q)-y_coord(q); + if (delx=0)and(dely=0) then + begin left_type(q):=curl; left_curl(q):=unity; + end + else begin left_type(q):=given; left_given(q):=n_arg(delx,dely); + end; + end; +if (right_type(p)=open)and(left_type(p)=explicit) then + begin delx:=x_coord(p)-left_x(p); dely:=y_coord(p)-left_y(p); + if (delx=0)and(dely=0) then + begin right_type(p):=curl; right_curl(p):=unity; + end + else begin right_type(p):=given; right_given(p):=n_arg(delx,dely); + end; + end + +@ Linear equations need to be solved whenever |n>1|; and also when |n=1| +and exactly one of the breakpoints involves a curl. The simplest case occurs +when |n=1| and there is a curl at both breakpoints; then we simply draw +a straight line. + +But before coding up the simple cases, we might as well face the general case, +since we must deal with it sooner or later, and since the general case +is likely to give some insight into the way simple cases can be handled best. + +When there is no cycle, the linear equations to be solved form a tri-diagonal +system, and we can apply the standard technique of Gaussian elimination +to convert that system to a sequence of equations of the form +$$\theta_0+u_0\theta_1=v_0,\quad +\theta_1+u_1\theta_2=v_1,\quad\ldots,\quad +\theta_{n-1}+u_{n-1}\theta_n=v_{n-1},\quad +\theta_n=v_n.$$ +It is possible to do this diagonalization while generating the equations. +Once $\theta_n$ is known, it is easy to determine $\theta_{n-1}$, \dots, +$\theta_1$, $\theta_0$; thus, the equations will be solved. + +The procedure is slightly more complex when there is a cycle, but the +basic idea will be nearly the same. In the cyclic case the right-hand +sides will be $v_k+w_k\theta_0$ instead of simply $v_k$, and we will start +the process off with $u_0=v_0=0$, $w_0=1$. The final equation will be not +$\theta_n=v_n$ but $\theta_n+u_n\theta_1=v_n+w_n\theta_0$; an appropriate +ending routine will take account of the fact that $\theta_n=\theta_0$ and +eliminate the $w$'s from the system, after which the solution can be +obtained as before. + +When $u_k$, $v_k$, and $w_k$ are being computed, the three pointer +variables |r|, |s|,~|t| will point respectively to knots |k-1|, |k|, +and~|k+1|. The $u$'s and $w$'s are scaled by $2^{28}$, i.e., they are +of type |fraction|; the $\theta$'s and $v$'s are of type |angle|. + +@= +@!theta:array[0..path_size] of angle; {values of $\theta_k$} +@!uu:array[0..path_size] of fraction; {values of $u_k$} +@!vv:array[0..path_size] of angle; {values of $v_k$} +@!ww:array[0..path_size] of fraction; {values of $w_k$} + +@ Our immediate problem is to get the ball rolling by setting up the +first equation or by realizing that no equations are needed, and to fit +this initialization into a framework suitable for the overall computation. + +@= +@t\4@>@@; +procedure solve_choices(@!p,@!q:pointer;@!n:halfword); +label found,exit; +var @!k:0..path_size; {current knot number} +@!r,@!s,@!t:pointer; {registers for list traversal} +@@; +begin k:=0; s:=p; +loop@+ begin t:=link(s); + if k=0 then @ + else case left_type(s) of + end_cycle,open:@; + curl:@; + given:@; + end; {there are no other cases} + r:=s; s:=t; incr(k); + end; +found:@; +exit:end; + +@ On the first time through the loop, we have |k=0| and |r| is not yet +defined. The first linear equation, if any, will have $A_0=B_0=0$. + +@= +case right_type(s) of +given: if left_type(t)=given then @ + else @; +curl: if left_type(t)=curl then @ + else @; +open: begin uu[0]:=0; vv[0]:=0; ww[0]:=fraction_one; + end; {this begins a cycle} +end {there are no other cases} + +@ The general equation that specifies equality of mock curvature at $z_k$ is +$$A_k\theta_{k-1}+(B_k+C_k)\theta_k+D_k\theta\k=-B_k\psi_k-D_k\psi\k,$$ +as derived above. We want to combine this with the already-derived equation +$\theta_{k-1}+u_{k-1}\theta_k=v_{k-1}+w_{k-1}\theta_0$ in order to obtain +a new equation +$\theta_k+u_k\theta\k=v_k+w_k\theta_0$. This can be done by dividing the +equation +$$(B_k-u_{k-1}A_k+C_k)\theta_k+D_k\theta\k=-B_k\psi_k-D_k\psi\k-A_kv_{k-1} + -A_kw_{k-1}\theta_0$$ +by $B_k-u_{k-1}A_k+C_k$. The trick is to do this carefully with +fixed-point arithmetic, avoiding the chance of overflow while retaining +suitable precision. + +The calculations will be performed in several registers that +provide temporary storage for intermediate quantities. + +@= +@!aa,@!bb,@!cc,@!ff,@!acc:fraction; {temporary registers} +@!dd,@!ee:scaled; {likewise, but |scaled|} +@!lt,@!rt:scaled; {tension values} + +@ @= +begin @; +@; +uu[k]:=take_fraction(ff,bb); +@; +if left_type(s)=end_cycle then + @; +end + +@ Since tension values are never less than 3/4, the values |aa| and +|bb| computed here are never more than 4/5. + +@= +if abs(right_tension(r))=unity then + begin aa:=fraction_half; dd:=2*delta[k]; + end +else begin aa:=make_fraction(unity,3*abs(right_tension(r))-unity); + dd:=take_fraction(delta[k], + fraction_three-make_fraction(unity,abs(right_tension(r)))); + end; +if abs(left_tension(t))=unity then + begin bb:=fraction_half; ee:=2*delta[k-1]; + end +else begin bb:=make_fraction(unity,3*abs(left_tension(t))-unity); + ee:=take_fraction(delta[k-1], + fraction_three-make_fraction(unity,abs(left_tension(t)))); + end; +cc:=fraction_one-take_fraction(uu[k-1],aa) + +@ The ratio to be calculated in this step can be written in the form +$$\beta_k^2\cdot\\{ee}\over\beta_k^2\cdot\\{ee}+\alpha_k^2\cdot + \\{cc}\cdot\\{dd},$$ +because of the quantities just calculated. The values of |dd| and |ee| +will not be needed after this step has been performed. + +@= +dd:=take_fraction(dd,cc); lt:=abs(left_tension(s)); rt:=abs(right_tension(s)); +if lt<>rt then {$\beta_k^{-1}\ne\alpha_k^{-1}$} + if lt= +acc:=-take_fraction(psi[k+1],uu[k]); +if right_type(r)=curl then + begin ww[k]:=0; + vv[k]:=acc-take_fraction(psi[1],fraction_one-ff); + end +else begin ff:=make_fraction(fraction_one-ff,cc); {this is + $B_k/(C_k+B_k-u_{k-1}A_k)<5$} + acc:=acc-take_fraction(psi[k],ff); + ff:=take_fraction(ff,aa); {this is $A_k/(C_k+B_k-u_{k-1}A_k)$} + vv[k]:=acc-take_fraction(vv[k-1],ff); + if ww[k-1]=0 then ww[k]:=0 + else ww[k]:=-take_fraction(ww[k-1],ff); + end + +@ When a complete cycle has been traversed, we have $\theta_k+u_k\theta\k= +v_k+w_k\theta_0$, for |1<=k<=n|. We would like to determine the value of +$\theta_n$ and reduce the system to the form $\theta_k+u_k\theta\k=v_k$ +for |0<=k= +begin aa:=0; bb:=fraction_one; {we have |k=n|} +repeat decr(k); +if k=0 then k:=n; +aa:=vv[k]-take_fraction(aa,uu[k]); +bb:=ww[k]-take_fraction(bb,uu[k]); +until k=n; {now $\theta_n=\\{aa}+\\{bb}\cdot\theta_n$} +aa:=make_fraction(aa,fraction_one-bb); +theta[n]:=aa; vv[0]:=aa; +for k:=1 to n-1 do vv[k]:=vv[k]+take_fraction(aa,ww[k]); +goto found; +end + +@ @d reduce_angle(#)==if abs(#)>one_eighty_deg then + if #>0 then #:=#-three_sixty_deg@+else #:=#+three_sixty_deg + +@= +begin theta[n]:=left_given(s)-n_arg(delta_x[n-1],delta_y[n-1]); +reduce_angle(theta[n]); +goto found; +end + +@ @= +begin vv[0]:=right_given(s)-n_arg(delta_x[0],delta_y[0]); +reduce_angle(vv[0]); +uu[0]:=0; ww[0]:=0; +end + +@ @= +begin cc:=right_curl(s); lt:=abs(left_tension(t)); rt:=abs(right_tension(s)); +if (rt=unity)and(lt=unity) then + uu[0]:=make_fraction(cc+cc+unity,cc+two) +else uu[0]:=curl_ratio(cc,rt,lt); +vv[0]:=-take_fraction(psi[1],uu[0]); ww[0]:=0; +end + +@ @= +begin cc:=left_curl(s); lt:=abs(left_tension(s)); rt:=abs(right_tension(r)); +if (rt=unity)and(lt=unity) then + ff:=make_fraction(cc+cc+unity,cc+two) +else ff:=curl_ratio(cc,lt,rt); +theta[n]:=-make_fraction(take_fraction(vv[n-1],ff), + fraction_one-take_fraction(ff,uu[n-1])); +goto found; +end + +@ The |curl_ratio| subroutine has three arguments, which our previous notation +encourages us to call $\gamma$, $\alpha^{-1}$, and $\beta^{-1}$. It is +a somewhat tedious program to calculate +$${(3-\alpha)\alpha^2\gamma+\beta^3\over + \alpha^3\gamma+(3-\beta)\beta^2},$$ +with the result reduced to 4 if it exceeds 4. (This reduction of curl +is necessary only if the curl and tension are both large.) +The values of $\alpha$ and $\beta$ will be at most~4/3. + +@= +function curl_ratio(@!gamma,@!a_tension,@!b_tension:scaled):fraction; +var @!alpha,@!beta,@!num,@!denom,@!ff:fraction; {registers} +begin alpha:=make_fraction(unity,a_tension); +beta:=make_fraction(unity,b_tension);@/ +if alpha<=beta then + begin ff:=make_fraction(alpha,beta); ff:=take_fraction(ff,ff); + gamma:=take_fraction(gamma,ff);@/ + beta:=beta div @'10000; {convert |fraction| to |scaled|} + denom:=take_fraction(gamma,alpha)+three-beta; + num:=take_fraction(gamma,fraction_three-alpha)+beta; + end +else begin ff:=make_fraction(beta,alpha); ff:=take_fraction(ff,ff); + beta:=take_fraction(beta,ff) div @'10000; {convert |fraction| to |scaled|} + denom:=take_fraction(gamma,alpha)+(ff div 1365)-beta; + {$1365\approx 2^{12}/3$} + num:=take_fraction(gamma,fraction_three-alpha)+beta; + end; +if num>=denom+denom+denom+denom then curl_ratio:=fraction_four +else curl_ratio:=make_fraction(num,denom); +end; + +@ We're in the home stretch now. + +@= +for k:=n-1 downto 0 do theta[k]:=vv[k]-take_fraction(theta[k+1],uu[k]); +s:=p; k:=0; +repeat t:=link(s);@/ +n_sin_cos(theta[k]); st:=n_sin; ct:=n_cos;@/ +n_sin_cos(-psi[k+1]-theta[k+1]); sf:=n_sin; cf:=n_cos;@/ +set_controls(s,t,k);@/ +incr(k); s:=t; +until k=n + +@ The |set_controls| routine actually puts the control points into +a pair of consecutive nodes |p| and~|q|. Global variables are used to +record the values of $\sin\theta$, $\cos\theta$, $\sin\phi$, and +$\cos\phi$ needed in this calculation. + +@= +@!st,@!ct,@!sf,@!cf:fraction; {sines and cosines} + +@ @= +procedure set_controls(@!p,@!q:pointer;@!k:integer); +var @!rr,@!ss:fraction; {velocities, divided by thrice the tension} +@!lt,@!rt:scaled; {tensions} +@!sine:fraction; {$\sin(\theta+\phi)$} +begin lt:=abs(left_tension(q)); rt:=abs(right_tension(p)); +rr:=velocity(st,ct,sf,cf,rt); +ss:=velocity(sf,cf,st,ct,lt); +if (right_tension(p)<0)or(left_tension(q)<0) then @; +right_x(p):=x_coord(p)+take_fraction( + take_fraction(delta_x[k],ct)-take_fraction(delta_y[k],st),rr); +right_y(p):=y_coord(p)+take_fraction( + take_fraction(delta_y[k],ct)+take_fraction(delta_x[k],st),rr); +left_x(q):=x_coord(q)-take_fraction( + take_fraction(delta_x[k],cf)+take_fraction(delta_y[k],sf),ss); +left_y(q):=y_coord(q)-take_fraction( + take_fraction(delta_y[k],cf)-take_fraction(delta_x[k],sf),ss); +right_type(p):=explicit; left_type(q):=explicit; +end; + +@ The boundedness conditions $\\{rr}\L\sin\phi\,/\sin(\theta+\phi)$ and +$\\{ss}\L\sin\theta\,/\sin(\theta+\phi)$ are to be enforced if $\sin\theta$, +$\sin\phi$, and $\sin(\theta+\phi)$ all have the same sign. Otherwise +there is no ``bounding triangle.'' + +@= +if((st>=0)and(sf>=0))or((st<=0)and(sf<=0)) then + begin sine:=take_fraction(abs(st),cf)+take_fraction(abs(sf),ct); + if sine>0 then + begin sine:=take_fraction(sine,fraction_one+unity); {safety factor} + if right_tension(p)<0 then + if ab_vs_cd(abs(sf),fraction_one,rr,sine)<0 then + rr:=make_fraction(abs(sf),sine); + if left_tension(q)<0 then + if ab_vs_cd(abs(st),fraction_one,ss,sine)<0 then + ss:=make_fraction(abs(st),sine); + end; + end + +@ Only the simple cases remain to be handled. + +@= +begin aa:=n_arg(delta_x[0],delta_y[0]);@/ +n_sin_cos(right_given(p)-aa); ct:=n_cos; st:=n_sin;@/ +n_sin_cos(left_given(q)-aa); cf:=n_cos; sf:=-n_sin;@/ +set_controls(p,q,0); return; +end + +@ @= +begin right_type(p):=explicit; left_type(q):=explicit; +lt:=abs(left_tension(q)); rt:=abs(right_tension(p)); +if rt=unity then + begin if delta_x[0]>=0 then right_x(p):=x_coord(p)+((delta_x[0]+1) div 3) + else right_x(p):=x_coord(p)+((delta_x[0]-1) div 3); + if delta_y[0]>=0 then right_y(p):=y_coord(p)+((delta_y[0]+1) div 3) + else right_y(p):=y_coord(p)+((delta_y[0]-1) div 3); + end +else begin ff:=make_fraction(unity,3*rt); {$\alpha/3$} + right_x(p):=x_coord(p)+take_fraction(delta_x[0],ff); + right_y(p):=y_coord(p)+take_fraction(delta_y[0],ff); + end; +if lt=unity then + begin if delta_x[0]>=0 then left_x(q):=x_coord(q)-((delta_x[0]+1) div 3) + else left_x(q):=x_coord(q)-((delta_x[0]-1) div 3); + if delta_y[0]>=0 then left_y(q):=y_coord(q)-((delta_y[0]+1) div 3) + else left_y(q):=y_coord(q)-((delta_y[0]-1) div 3); + end +else begin ff:=make_fraction(unity,3*lt); {$\beta/3$} + left_x(q):=x_coord(q)-take_fraction(delta_x[0],ff); + left_y(q):=y_coord(q)-take_fraction(delta_y[0],ff); + end; +return; +end + +@* \[19] Generating discrete moves. +The purpose of the next part of \MF\ is to compute discrete approximations +to curves described as parametric polynomial functions $z(t)$. +We shall start with the low level first, because an efficient ``engine'' +is needed to support the high-level constructions. + +Most of the subroutines are based on variations of a single theme, +namely the idea of {\sl bisection}. Given a Bernshte{\u\i}n polynomial +@^Bernshte{\u\i}n, Serge{\u\i} Natanovich@> +$$B(z_0,z_1,\ldots,z_n;t)=\sum_k{n\choose k}t^k(1-t)^{n-k}z_k,$$ +we can conveniently bisect its range as follows: + +\smallskip +\textindent{1)} Let $z_k^{(0)}=z_k$, for |0<=k<=n|. + +\smallskip +\textindent{2)} Let $z_k^{(j+1)}={1\over2}(z_k^{(j)}+z\k^{(j)})$, for +|0<=k +@^Riesenfeld, Richard Franklin@> +and Machine Intelligence\/ \bf PAMI-2} (1980), 35--46]: Assuming that +$\vert Z\k-Z_k\vert\L M$ before bisection, we want to prove that +$\vert Z\k-Z_k\vert\L\lceil M/2\rceil$ afterward. First we show that +$\vert Z\k^{(j)}-Z_k^{(j)}\vert\L M$ for all $j$ and~$k$, by induction +on~$j$; this follows from the fact that +$$\bigl\vert\\{half}(a+b)-\\{half}(b+c)\bigr\vert\L + \max\bigl(\vert a-b\vert,\vert b-c\vert\bigr)$$ +holds for both of the rounding rules $\\{half}(x)=\lfloor x/2\rfloor$ +and $\\{half}(x)={\rm sign}(x)\lfloor\vert x/2\vert\rfloor$. +(If $\vert a-b\vert$ and $\vert b-c\vert$ are equal, then +$a+b$ and $b+c$ are both even or both odd. The rounding errors either +cancel or round the numbers toward each other; hence +$$\eqalign{\bigl\vert\\{half}(a+b)-\\{half}(b+c)\bigr\vert +&\L\textstyle\bigl\vert{1\over2}(a+b)-{1\over2}(b+c)\bigr\vert\cr +&=\textstyle\bigl\vert{1\over2}(a-b)+{1\over2}(b-c)\bigr\vert +\L\max\bigl(\vert a-b\vert,\vert b-c\vert\bigr),\cr}$$ +as required. A simpler argument applies if $\vert a-b\vert$ and +$\vert b-c\vert$ are unequal.) Now it is easy to see that +$\vert Z_1^{(j+1)}-Z_1^{(j)}\vert\L\bigl\lfloor{1\over2} +\vert Z_2^{(j)}-Z_1^{(j)}\vert+{1\over2}\bigr\rfloor +\L\bigl\lfloor{1\over2}(M+1)\bigr\rfloor=\lceil M/2\rceil$. + +Another interesting fact about bisection is the identity +$$Z_1'+\cdots+Z_n'+Z_1''+\cdots+Z_n''=2(Z_1+\cdots+Z_n+E),$$ +where $E$ is the sum of the rounding errors in all of the halving +operations ($\vert E\vert\L n(n-1)/4$). + +@ We will later reduce the problem of digitizing a complex cubic +$z(t)=B(z_0,z_1,z_2,z_3;t)$ to the following simpler problem: +Given two real cubics +$x(t)=B(x_0,x_1,x_2,x_3;t)$ +and $y(t)=B(y_0,y_1,y_2,y_3;t)$ that are monotone nondecreasing, +determine the set of integer points +$$P=\bigl\{\bigl(\lfloor x(t)\rfloor,\lfloor y(t)\rfloor\bigr) +\bigm\vert 0\L t\L 1\bigr\}.$$ +Well, the problem isn't actually quite so clean as this; when the path +goes very near an integer point $(a,b)$, computational errors may +make us think that $P$ contains $(a-1,b)$ while in reality it should +contain $(a,b-1)$. Furthermore, if the path goes {\sl exactly\/} +through the integer points $(a-1,b-1)$ and +$(a,b)$, we will want $P$ to contain one +of the two points $(a-1,b)$ or $(a,b-1)$, so that $P$ can be described +entirely by ``rook moves'' upwards or to the right; no diagonal +moves from $(a-1,b-1)$ to~$(a,b)$ will be allowed. + +Thus, the set $P$ we wish to compute will merely be an approximation +to the set described in the formula above. It will consist of +$\lfloor x(1)\rfloor-\lfloor x(0)\rfloor$ rightward moves and +$\lfloor y(1)\rfloor-\lfloor y(0)\rfloor$ upward moves, intermixed +in some order. Our job will be to figure out a suitable order. + +The following recursive strategy suggests itself, when we recall that +$x(0)=x_0$, $x(1)=x_3$, $y(0)=y_0$, and $y(1)=y_3$: + +\smallskip +If $\lfloor x_0\rfloor=\lfloor x_3\rfloor$ then take +$\lfloor y_3\rfloor-\lfloor y_0\rfloor$ steps up. + +Otherwise if $\lfloor y_0\rfloor=\lfloor y_3\rfloor$ then take +$\lfloor x_3\rfloor-\lfloor x_0\rfloor$ steps to the right. + +Otherwise bisect the current cubics and repeat the process on both halves. + +\yskip\noindent +This intuitively appealing formulation does not quite solve the problem, +because it may never terminate. For example, it's not hard to see that +no steps will {\sl ever\/} be taken if $(x_0,x_1,x_2,x_3)=(y_0,y_1,y_2,y_3)$! +However, we can surmount this difficulty with a bit of care; so let's +proceed to flesh out the algorithm as stated, before worrying about +such details. + +The bisect-and-double strategy discussed above suggests that we represent +$(x_0,x_1,x_2,x_3)$ by $(X_1,X_2,X_3)$, where $X_k=2^l(x_k-x_{k-1})$ +for some~$l$. Initially $l=16$, since the $x$'s are |scaled|. +In order to deal with other aspects of the algorithm we will want to +maintain also the quantities $m=\lfloor x_3\rfloor-\lfloor x_0\rfloor$ +and $R=2^l(x_0\bmod 1)$. Similarly, +$(y_0,y_1,y_2,y_3)$ will be represented by $(Y_1,Y_2,Y_3)$, +$n=\lfloor y_3\rfloor-\lfloor y_0\rfloor$, +and $S=2^l(y_0\bmod 1)$. The algorithm now takes the following form: + +\smallskip +If $m=0$ then take $n$ steps up. + +Otherwise if $n=0$ then take $m$ steps to the right. + +Otherwise bisect the current cubics and repeat the process on both halves. + +\smallskip\noindent +The bisection process for $(X_1,X_2,X_3,m,R,l)$ reduces, in essence, +to the following formulas: +$$\vbox{\halign{$#\hfil$\cr +X_2'=\\{half}(X_1+X_2),\quad +X_2''=\\{half}(X_2+X_3),\quad +X_3'=\\{half}(X_2'+X_2''),\cr +X_1'=X_1,\quad +X_1''=X_3',\quad +X_3''=X_3,\cr +R'=2R,\quad +T=X_1'+X_2'+X_3'+R',\quad +R''=T\bmod 2^{l+1},\cr +m'=\lfloor T/2^{l+1}\rfloor,\quad +m''=m-m'.\cr}}$$ + +@ When $m=n=1$, the computation can be speeded up because we simply +need to decide between two alternatives, (up,\thinspace right) +versus (right,\thinspace up). There appears to be no simple, direct +way to make the correct decision by looking at the values of +$(X_1,X_2,X_3,R)$ and +$(Y_1,Y_2,Y_3,S)$; but we can streamline the bisection process, and +we can use the fact that only one of the two descendants needs to +be examined after each bisection. Furthermore, we observed earlier +that after several levels of bisection the $X$'s and $Y$'s will be nearly +equal; so we will be justified in assuming that the curve is essentially a +straight line. (This, incidentally, solves the problem of infinite +recursion mentioned earlier.) + +It is possible to show that +$$m=\bigl\lfloor(X_1+X_2+X_3+R+E)\,/\,2^l\bigr\rfloor,$$ +where $E$ is an accumulated rounding error that is at most +$3\cdot(2^{l-16}-1)$ in absolute value. We will make sure that +the $X$'s are less than $2^{28}$; hence when $l=30$ we must +have |m<=1|. This proves that the special case $m=n=1$ is +bound to be reached by the time $l=30$. Furthermore $l=30$ is +a suitable time to make the straight line approximation, +if the recursion hasn't already died out, because the maximum +difference between $X$'s will then be $<2^{14}$; this corresponds +to an error of $<1$ with respect to the original scaling. +(Stating this another way, each bisection makes the curve two bits +closer to a straight line, hence 14 bisections are sufficient for +28-bit accuracy.) + +In the case of a straight line, the curve goes first right, then up, +if and only if $(T-2^l)(2^l-S)>(U-2^l)(2^l-R)$, where +$T=X_1+X_2+X_3+R$ and $U=Y_1+Y_2+Y_3+S$. For the actual curve +essentially runs from $(R/2^l,S/2^l)$ to $(T/2^l,U/2^l)$, and +we are testing whether or not $(1,1)$ is above the straight +line connecting these two points. (This formula assumes that $(1,1)$ +is not exactly on the line.) + +@ We have glossed over the problem of tie-breaking in ambiguous +cases when the cubic curve passes exactly through integer points. +\MF\ finesses this problem by assuming that coordinates +$(x,y)$ actually stand for slightly perturbed values $(x+\xi,y+\eta)$, +where $\xi$ and~$\eta$ are infinitesimals whose signs will determine +what to do when $x$ and/or~$y$ are exact integers. The quantities +$\lfloor x\rfloor$ and~$\lfloor y\rfloor$ in the formulas above +should actually read $\lfloor x+\xi\rfloor$ and $\lfloor y+\eta\rfloor$. + +If $x$ is a |scaled| value, we have $\lfloor x+\xi\rfloor=\lfloor x\rfloor$ +if $\xi>0$, and $\lfloor x+\xi\rfloor=\lfloor x-2^{-16}\rfloor$ if +$\xi<0$. It is convenient to represent $\xi$ by the integer |xi_corr|, +defined to be 0~if $\xi>0$ and 1~if $\xi<0$; then, for example, the +integer $\lfloor x+\xi\rfloor$ can be computed as +|floor_unscaled(x-xi_corr)|. Similarly, $\eta$ is conveniently +represented by~|eta_corr|. + +In our applications the sign of $\xi-\eta$ will always be the same as +the sign of $\xi$. Therefore it turns out that the rule for straight +lines, as stated above, should be modified as follows in the case of +ties: The line goes first right, then up, if and only if +$(T-2^l)(2^l-S)+\xi>(U-2^l)(2^l-R)$. And this relation holds iff +$|ab_vs_cd|(T-2^l,2^l-S,U-2^l,2^l-R)-|xi_corr|\ge0$. + +These conventions for rounding are symmetrical, in the sense that the +digitized moves obtained from $(x_0,x_1,x_2,x_3,y_0,y_1,y_2,y_3,\xi,\eta)$ +will be exactly complementary to the moves that would be obtained from +$(-x_3,-x_2,-x_1,-x_0,-y_3,-y_2,-y_1,-y_0,-\xi,-\eta)$, if arithmetic +is exact. However, truncation errors in the bisection process might +upset the symmetry. We can restore much of the lost symmetry by adding +|xi_corr| or |eta_corr| when halving the data. + +@ One further possibility needs to be mentioned: The algorithm +will be applied only to cubic polynomials $B(x_0,x_1,x_2,x_3;t)$ that +are nondecreasing as $t$~varies from 0 to~1; this condition turns +out to hold if and only if $x_0\L x_1$ and $x_2\L x_3$, and either +$x_1\L x_2$ or $(x_1-x_2)^2\L(x_1-x_0)(x_3-x_2)$. If bisection were +carried out with perfect accuracy, these relations would remain +invariant. But rounding errors can creep in, hence the bisection +algorithm can produce non-monotonic subproblems from monotonic +initial conditions. This leads to the potential danger that $m$ or~$n$ +could become negative in the algorithm described above. + +For example, if we start with $(x_1-x_0,x_2-x_1,x_3-x_2)= +(X_1,X_2,X_3)=(7,-16,39)$, the corresponding polynomial is +monotonic, because $16^2<7\cdot39$. But the bisection algorithm +produces the left descendant $(7,-5,3)$, which is nonmonotonic; +its right descendant is~$(0,-1,3)$. + +\def\xt{{\tilde x}} +Fortunately we can prove that such rounding errors will never cause +the algorithm to make a tragic mistake. At every stage we are working +with numbers corresponding to a cubic polynomial $B(\xt_0, +\xt_1,\xt_2,\xt_3)$ that approximates some +monotonic polynomial $B(x_0,x_1,x_2,x_3)$. The accumulated errors are +controlled so that $\vert x_k-\xt_k\vert<\epsilon=3\cdot2^{-16}$. +If bisection is done at some stage of the recursion, we have +$m=\lfloor\xt_3\rfloor-\lfloor\xt_0\rfloor>0$, and the algorithm +computes a bisection value $\bar x$ such that $m'=\lfloor\bar x\rfloor- +\lfloor\xt_0\rfloor$ +and $m''=\lfloor\xt_3\rfloor-\lfloor\bar x\rfloor$. We want to prove +that neither $m'$ nor $m''$ can be negative. Since $\bar x$ is an +approximation to a value in the interval $[x_0,x_3]$, we have +$\bar x>x_0-\epsilon$ and $\bar x +\xt_0-2\epsilon$ and $\bar x<\xt_3+2\epsilon$. +If $m'$ is negative we must have $\xt_0\bmod 1<2\epsilon$; +if $m''$ is negative we must have $\xt_3\bmod 1>1-2\epsilon$. +In either case the condition $\lfloor\xt_3\rfloor-\lfloor\xt_0\rfloor>0$ +implies that $\xt_3-\xt_0>1-2\epsilon$, hence $x_3-x_0>1-4\epsilon$. +But it can be shown that if $B(x_0,x_1,x_2,x_3;t)$ is a monotonic +cubic, then $B(x_0,x_1,x_2,x_3;{1\over2})$ is always between +$.06[x_0,x_3]$ and $.94[x_0,x_3]$; and it is impossible for $\bar x$ +to be within~$\epsilon$ of such a number. Contradiction! +(The constant .06 is actually $(2-\sqrt3\,)/4$; the worst case +occurs for polynomials like $B(0,2-\sqrt3,1-\sqrt3,3;t)$.) + +@ OK, now that a long theoretical preamble has justified the +bisection-and-doubling algorithm, we are ready to proceed with +its actual coding. But we still haven't discussed the +form of the output. + +For reasons to be discussed later, we shall find it convenient to +record the output as follows: Moving one step up is represented by +appending a `1' to a list; moving one step right is represented by +adding unity to the element at the end of the list. Thus, for example, +the net effect of ``(up, right, right, up, right)'' is to append +$(3,2)$. + +The list is kept in a global array called |move|. Before starting the +algorithm, \MF\ should check that $\\{move\_ptr}+\lfloor y_3\rfloor +-\lfloor y_0\rfloor\L\\{move\_size}$, so that the list won't exceed +the bounds of this array. + +@= +@!move:array[0..move_size] of integer; {the recorded moves} +@!move_ptr:0..move_size; {the number of items in the |move| list} + +@ When bisection occurs, we ``push'' the subproblem corresponding +to the right-hand subinterval onto the |bisect_stack| while +we continue to work on the left-hand subinterval. Thus, the |bisect_stack| +will hold $(X_1,X_2,X_3,R,m,Y_1,Y_2,Y_3,S,n,l)$ values for +subproblems yet to be tackled. + +At most 15 subproblems will be on the stack at once (namely, for +$l=15$,~16, \dots,~29); but the stack is bigger than this, because +it is used also for more complicated bisection algorithms. + +@d stack_x1==bisect_stack[bisect_ptr] {stacked value of $X_1$} +@d stack_x2==bisect_stack[bisect_ptr+1] {stacked value of $X_2$} +@d stack_x3==bisect_stack[bisect_ptr+2] {stacked value of $X_3$} +@d stack_r==bisect_stack[bisect_ptr+3] {stacked value of $R$} +@d stack_m==bisect_stack[bisect_ptr+4] {stacked value of $m$} +@d stack_y1==bisect_stack[bisect_ptr+5] {stacked value of $Y_1$} +@d stack_y2==bisect_stack[bisect_ptr+6] {stacked value of $Y_2$} +@d stack_y3==bisect_stack[bisect_ptr+7] {stacked value of $Y_3$} +@d stack_s==bisect_stack[bisect_ptr+8] {stacked value of $S$} +@d stack_n==bisect_stack[bisect_ptr+9] {stacked value of $n$} +@d stack_l==bisect_stack[bisect_ptr+10] {stacked value of $l$} +@d move_increment=11 {number of items pushed by |make_moves|} + +@= +@!bisect_stack:array[0..bistack_size] of integer; +@!bisect_ptr:0..bistack_size; + +@ @= +if 15*move_increment>bistack_size then bad:=31; + +@ The |make_moves| subroutine is given |scaled| values $(x_0,x_1,x_2,x_3)$ +and $(y_0,y_1,y_2,y_3)$ that represent monotone-nondecreasing polynomials; +it makes $\lfloor x_3+\xi\rfloor-\lfloor x_0+\xi\rfloor$ rightward moves +and $\lfloor y_3+\eta\rfloor-\lfloor y_0+\eta\rfloor$ upward moves, as +explained earlier. (Here $\lfloor x+\xi\rfloor$ actually stands for +$\lfloor x/2^{16}-|xi_corr|\rfloor$, if $x$ is regarded as an integer +without scaling.) The unscaled integers $x_k$ and~$y_k$ should be less +than $2^{28}$ in magnitude. + +It is assumed that $|move_ptr| + \lfloor y_3+\eta\rfloor - +\lfloor y_0+\eta\rfloor < |move_size|$ when this procedure is called, +so that the capacity of the |move| array will not be exceeded. + +The variables |r| and |s| in this procedure stand respectively for +$R-|xi_corr|$ and $S-|eta_corr|$ in the theory discussed above. + +@p procedure make_moves(@!xx0,@!xx1,@!xx2,@!xx3,@!yy0,@!yy1,@!yy2,@!yy3: + scaled;@!xi_corr,@!eta_corr:small_number); +label continue, done, exit; +var @!x1,@!x2,@!x3,@!m,@!r,@!y1,@!y2,@!y3,@!n,@!s,@!l:integer; + {bisection variables explained above} +@!q,@!t,@!u,@!x2a,@!x3a,@!y2a,@!y3a:integer; {additional temporary registers} +begin if (xx3 +l:=16; bisect_ptr:=0;@/ +x1:=xx1-xx0; x2:=xx2-xx1; x3:=xx3-xx2; +if xx0>=xi_corr then r:=(xx0-xi_corr) mod unity +else r:=unity-1-((-xx0+xi_corr-1) mod unity); +m:=(xx3-xx0+r) div unity;@/ +y1:=yy1-yy0; y2:=yy2-yy1; y3:=yy3-yy2; +if yy0>=eta_corr then s:=(yy0-eta_corr) mod unity +else s:=unity-1-((-yy0+eta_corr-1) mod unity); +n:=(yy3-yy0+s) div unity;@/ +if (xx3-xx0>=fraction_one)or(yy3-yy0>=fraction_one) then + @; +loop@+ begin continue:@; + if bisect_ptr=0 then return; + @; + end; +exit: end; + +@ @= +bisect_ptr:=bisect_ptr-move_increment;@/ +x1:=stack_x1; x2:=stack_x2; x3:=stack_x3; r:=stack_r; m:=stack_m;@/ +y1:=stack_y1; y2:=stack_y2; y3:=stack_y3; s:=stack_s; n:=stack_n;@/ +l:=stack_l + +@ Our variables |(x1,x2,x3)| correspond to $(X_1,X_2,X_3)$ in the notation +of the theory developed above. We need to keep them less than $2^{28}$ +in order to avoid integer overflow in weird circumstances. +For example, data like $x_0=-2^{28}+2^{16}-1$ and $x_1=x_2=x_3=2^{28}-1$ +would otherwise be problematical. Hence this part of the code is +needed, if only to thwart malicious users. + +@= +begin x1:=half(x1+xi_corr); x2:=half(x2+xi_corr); x3:=half(x3+xi_corr); +r:=half(r+xi_corr);@/ +y1:=half(y1+eta_corr); y2:=half(y2+eta_corr); y3:=half(y3+eta_corr); +s:=half(s+eta_corr);@/ +l:=15; +end + +@ @= +if m=0 then @ +else if n=0 then @ +else if m+n=2 then @ +else begin incr(l); stack_l:=l;@/ + stack_x3:=x3; stack_x2:=half(x2+x3+xi_corr); x2:=half(x1+x2+xi_corr); + x3:=half(x2+stack_x2+xi_corr); stack_x1:=x3;@/ + r:=r+r+xi_corr; t:=x1+x2+x3+r;@/ + q:=t div two_to_the[l]; stack_r:=t mod two_to_the[l];@/ + stack_m:=m-q; m:=q;@/ + stack_y3:=y3; stack_y2:=half(y2+y3+eta_corr); y2:=half(y1+y2+eta_corr); + y3:=half(y2+stack_y2+eta_corr); stack_y1:=y3;@/ + s:=s+s+eta_corr; u:=y1+y2+y3+s;@/ + q:=u div two_to_the[l]; stack_s:=u mod two_to_the[l];@/ + stack_n:=n-q; n:=q;@/ + bisect_ptr:=bisect_ptr+move_increment; goto continue; + end + +@ @= +while n>0 do + begin incr(move_ptr); move[move_ptr]:=1; decr(n); + end + +@ @= +move[move_ptr]:=move[move_ptr]+m + +@ @= +begin r:=two_to_the[l]-r; s:=two_to_the[l]-s;@/ +while l<30 do + begin x3a:=x3; x2a:=half(x2+x3+xi_corr); x2:=half(x1+x2+xi_corr); + x3:=half(x2+x2a+xi_corr); + t:=x1+x2+x3; r:=r+r-xi_corr;@/ + y3a:=y3; y2a:=half(y2+y3+eta_corr); y2:=half(y1+y2+eta_corr); + y3:=half(y2+y2a+eta_corr); + u:=y1+y2+y3; s:=s+s-eta_corr;@/ + if t + else begin @; goto done; + end + else if u; goto done; + end; + incr(l); + end; +r:=r-xi_corr; s:=s-eta_corr; +if ab_vs_cd(x1+x2+x3,s,y1+y2+y3,r)-xi_corr>=0 then @ + else @; +done: +end + +@ @= +begin x1:=x3; x2:=x2a; x3:=x3a; r:=r-t; +y1:=y3; y2:=y2a; y3:=y3a; s:=s-u; +end + +@ @= +begin incr(move[move_ptr]); incr(move_ptr); move[move_ptr]:=1; +end + +@ @= +begin incr(move_ptr); move[move_ptr]:=2; +end + +@ After |make_moves| has acted, possibly for several curves that move toward +the same octant, a ``smoothing'' operation might be done on the |move| array. +This removes optical glitches that can arise even when the curve has been +digitized without rounding errors. + +The smoothing process replaces the integers $a_0\ldots a_n$ in +|move[b..t]| by ``smoothed'' integers $a_0'\ldots a_n'$ defined as +follows: +$$a_k'=a_k+\delta\k-\delta_k;\qquad +\delta_k=\cases{+1,&if $1=3 then + begin k:=b+2; aa:=move[k-1]; aaa:=move[k-2]; + repeat a:=move[k]; + if abs(a-aa)>1 then + @; + incr(k); aaa:=aa; aa:=a; + until k=t; + end; +end; + +@ @= +if a>aa then + begin if aaa>=aa then if a>=move[k+1] then + begin incr(move[k-1]); move[k]:=a-1; + end; + end +else begin if aaa<=aa then if a<=move[k+1] then + begin decr(move[k-1]); move[k]:=a+1; + end; + end + +@* \[20] Edge structures. +Now we come to \MF's internal scheme for representing what the user can +actually ``see,'' the edges between pixels. Each pixel has an integer +weight, obtained by summing the weights on all edges to its left. \MF\ +represents only the nonzero edge weights, since most of the edges are +weightless; in this way, the data storage requirements grow only linearly +with respect to the number of pixels per point, even though two-dimensional +data is being represented. (Well, the actual dependence on the underlying +resolution is order $n\log n$, but the the $\log n$ factor is buried in our +implicit restriction on the maximum raster size.) The sum of all edge +weights in each row should be zero. + +The data structure for edge weights must be compact and flexible, +yet it should support efficient updating and display operations. We +want to be able to have many different edge structures in memory at +once, and we want the computer to be able to translate them, reflect them, +and/or merge them together with relative ease. + +\MF's solution to this problem requires one single-word node per +nonzero edge weight, plus one two-word node for each row in a contiguous +set of rows. There's also a header node that provides global information +about the entire structure. + +@ Let's consider the edge-weight nodes first. The |info| field of such +nodes contains both an $m$~value and a weight~$w$, in the form +$8m+w+c$, where $c$ is a constant that depends on data found in the header. +We shall consider $c$ in detail later; for now, it's best just to think +of it as a way to compensate for the fact that $m$ and~$w$ can be negative, +together with the fact that an |info| field must have a value between +|min_halfword| and |max_halfword|. The $m$ value is an unscaled $x$~coordinate, +so it satisfies $\vert m\vert< +4096$; the $w$ value is always in the range $1\L\vert w\vert\L3$. We can +unpack the data in the |info| field by fetching |ho(info(p))= +info(p)-min_halfword| and dividing this nonnegative number by~8; +the constant~$c$ will be chosen so that the remainder of this division +is $4+w$. Thus, for example, a remainder of~3 will correspond to +the edge weight $w=-1$. + +Every row of an edge structure contains two lists of such edge-weight +nodes, called the |sorted| and |unsorted| lists, linked together by their +|link| fields in the normal way. The difference between them is that we +always have |info(p)<=info(link(p))| in the |sorted| list, but there's no +such restriction on the elements of the |unsorted| list. The reason for +this distinction is that it would take unnecessarily long to maintain +edge-weight lists in sorted order while they're being updated; but when we +need to process an entire row from left to right in order of the +$m$~values, it's fairly easy and quick to sort a short list of unsorted +elements and to merge them into place among their sorted cohorts. +Furthermore, the fact that the |unsorted| list is empty can sometimes be +used to good advantage, because it allows us to conclude that a particular +row has not changed since the last time we sorted it. + +The final |link| of the |sorted| list will be |sentinel|, which points to +a special one-word node whose |info| field is essentially infinite; this +facilitates the sorting and merging operations. The final |link| of the +|unsorted| list will be either |null| or |void|, where |void=null+1| +is used to avoid redisplaying data that has not changed: +A |void| value is stored at the head of the +unsorted list whenever the corresponding row has been displayed. + +@d zero_w=4 +@d void==null+1 + +@= +info(sentinel):=max_halfword; {|link(sentinel)=null|} + +@ The rows themselves are represented by row header nodes that +contain four link fields. Two of these four, |sorted| and |unsorted|, +point to the first items of the edge-weight lists just mentioned. +The other two, |link| and |knil|, point to the headers of the two +adjacent rows. If |p| points to the header for row number~|n|, then +|link(p)| points up to the header for row~|n+1|, and |knil(p)| points +down to the header for row~|n-1|. This double linking makes it +convenient to move through consecutive rows either upward or downward; +as usual, we have |link(knil(p))=knil(link(p))=p| for all row headers~|p|. + +The row associated with a given value of |n| contains weights for +edges that run between the lattice points |(m,n)| and |(m,n+1)|. + +@d knil==info {inverse of the |link| field, in a doubly linked list} +@d sorted_loc(#)==#+1 {where the |sorted| link field resides} +@d sorted(#)==link(sorted_loc(#)) {beginning of the list of sorted edge weights} +@d unsorted(#)==info(#+1) {beginning of the list of unsorted edge weights} +@d row_node_size=2 {number of words in a row header node} + +@ The main header node |h| for an edge structure has |link| and |knil| +fields that link it above the topmost row and below the bottommost row. +It also has fields called |m_min|, |m_max|, |n_min|, and |n_max| that +bound the current extent of the edge data: All |m| values in edge-weight +nodes should lie between |m_min(h)-4096| and |m_max(h)-4096|, inclusive. +Furthermore the topmost row header, pointed to by |knil(h)|, +is for row number |n_max(h)-4096|; the bottommost row header, pointed to by +|link(h)|, is for row number |n_min(h)-4096|. + +The offset constant |c| that's used in all of the edge-weight data is +represented implicitly in |m_offset(h)|; its actual value is +$$\hbox{|c=min_halfword+zero_w+8*m_offset(h)|.}$$ +Notice that it's possible to shift an entire edge structure by an +amount $(\Delta m,\Delta n)$ by adding $\Delta n$ to |n_min(h)| and |n_max(h)|, +adding $\Delta m$ to |m_min(h)| and |m_max(h)|, and subtracting +$\Delta m$ from |m_offset(h)|; +none of the other edge data needs to be modified. Initially the |m_offset| +field is~4096, but it will change if the user requests such a shift. +The contents of these five fields should always be positive and less than +8192; |n_max| should, in fact, be less than 8191. Furthermore +|m_min+m_offset-4096| and |m_max+m_offset-4096| must also lie strictly +between 0 and 8192, so that the |info| fields of edge-weight nodes will +fit in a halfword. + +The header node of an edge structure also contains two somewhat unusual +fields that are called |last_window(h)| and |last_window_time(h)|. When this +structure is displayed in window~|k| of the user's screen, after that +window has been updated |t| times, \MF\ sets |last_window(h):=k| and +|last_window_time(h):=t|; it also sets |unsorted(p):=void| for all row +headers~|p|, after merging any existing unsorted weights with the sorted +ones. A subsequent display in the same window will be able to avoid +redisplaying rows whose |unsorted| list is still |void|, if the window +hasn't been used for something else in the meantime. + +A pointer to the row header of row |n_pos(h)-4096| is provided in +|n_rover(h)|. Most of the algorithms that update an edge structure +are able to get by without random row references; they usually +access rows that are neighbors of each other or of the current |n_pos| row. +Exception: If |link(h)=h| (so that the edge structure contains +no rows), we have |n_rover(h)=h|, and |n_pos(h)| is irrelevant. + +@d zero_field=4096 {amount added to coordinates to make them positive} +@d n_min(#)==info(#+1) {minimum row number present, plus |zero_field|} +@d n_max(#)==link(#+1) {maximum row number present, plus |zero_field|} +@d m_min(#)==info(#+2) {minimum column number present, plus |zero_field|} +@d m_max(#)==link(#+2) {maximum column number present, plus |zero_field|} +@d m_offset(#)==info(#+3) {translation of $m$ data in edge-weight nodes} +@d last_window(#)==link(#+3) {the last display went into this window} +@d last_window_time(#)==mem[#+4].int {after this many window updates} +@d n_pos(#)==info(#+5) {the row currently in |n_rover|, plus |zero_field|} +@d n_rover(#)==link(#+5) {a row recently referenced} +@d edge_header_size=6 {number of words in an edge-structure header} +@d valid_range(#)==(abs(#-4096)<4096) {is |#| strictly between 0 and 8192?} +@d empty_edges(#)==link(#)=# {are there no rows in this edge header?} + +@p procedure init_edges(@!h:pointer); {initialize an edge header to null values} +begin knil(h):=h; link(h):=h;@/ +n_min(h):=zero_field+4095; n_max(h):=zero_field-4095; +m_min(h):=zero_field+4095; m_max(h):=zero_field-4095; +m_offset(h):=zero_field;@/ +last_window(h):=0; last_window_time(h):=0;@/ +n_rover(h):=h; n_pos(h):=0;@/ +end; + +@ When a lot of work is being done on a particular edge structure, we plant +a pointer to its main header in the global variable |cur_edges|. +This saves us from having to pass this pointer as a parameter over and +over again between subroutines. + +Similarly, |cur_wt| is a global weight that is being used by several +procedures at once. + +@= +@!cur_edges:pointer; {the edge structure of current interest} +@!cur_wt:integer; {the edge weight of current interest} + +@ The |fix_offset| routine goes through all the edge-weight nodes of +|cur_edges| and adds a constant to their |info| fields, so that +|m_offset(cur_edges)| can be brought back to |zero_field|. (This +is necessary only in unusual cases when the offset has gotten too +large or too small.) + +@p procedure fix_offset; +var @!p,@!q:pointer; {list traversers} +@!delta:integer; {the amount of change} +begin delta:=8*(m_offset(cur_edges)-zero_field); +m_offset(cur_edges):=zero_field; +q:=link(cur_edges); +while q<>cur_edges do + begin p:=sorted(q); + while p<>sentinel do + begin info(p):=info(p)-delta; p:=link(p); + end; + p:=unsorted(q); + while p>void do + begin info(p):=info(p)-delta; p:=link(p); + end; + q:=link(q); + end; +end; + +@ The |edge_prep| routine makes the |cur_edges| structure ready to +accept new data whose coordinates satisfy |ml<=m<=mr| and |nl<=n<=nr-1|, +assuming that |-4096m_max(cur_edges) then m_max(cur_edges):=mr; +if not valid_range(m_min(cur_edges)+m_offset(cur_edges)-zero_field) or@| + not valid_range(m_max(cur_edges)+m_offset(cur_edges)-zero_field) then + fix_offset; +if empty_edges(cur_edges) then {there are no rows} + begin n_min(cur_edges):=nr+1; n_max(cur_edges):=nr; + end; +if nl; +if nr>n_max(cur_edges) then + @; +end; + +@ @= +begin delta:=n_min(cur_edges)-nl; n_min(cur_edges):=nl; +p:=link(cur_edges); +repeat q:=get_node(row_node_size); sorted(q):=sentinel; unsorted(q):=void; +knil(p):=q; link(q):=p; p:=q; decr(delta); +until delta=0; +knil(p):=cur_edges; link(cur_edges):=p; +if n_rover(cur_edges)=cur_edges then n_pos(cur_edges):=nl-1; +end + +@ @= +begin delta:=nr-n_max(cur_edges); n_max(cur_edges):=nr; +p:=knil(cur_edges); +repeat q:=get_node(row_node_size); sorted(q):=sentinel; unsorted(q):=void; +link(p):=q; knil(q):=p; p:=q; decr(delta); +until delta=0; +link(p):=cur_edges; knil(cur_edges):=p; +if n_rover(cur_edges)=cur_edges then n_pos(cur_edges):=nr+1; +end + +@ The |print_edges| subroutine gives a symbolic rendition of an edge +structure, for use in `\&{show}' commands. A rather terse output +format has been chosen since edge structures can grow quite large. + +@= +@t\4@>@@;@/ +procedure print_edges(@!s:str_number;@!nuline:boolean;@!x_off,@!y_off:integer); +var @!p,@!q,@!r:pointer; {for list traversal} +@!n:integer; {row number} +begin print_diagnostic("Edge structure",s,nuline); +p:=knil(cur_edges); n:=n_max(cur_edges)-zero_field; +while p<>cur_edges do + begin q:=unsorted(p); r:=sorted(p); + if(q>void)or(r<>sentinel) then + begin print_nl("row "); print_int(n+y_off); print_char(":"); + while q>void do + begin print_weight(q,x_off); q:=link(q); + end; + print(" |"); + while r<>sentinel do + begin print_weight(r,x_off); r:=link(r); + end; + end; + p:=knil(p); decr(n); + end; +end_diagnostic(true); +end; + +@ @= +procedure print_weight(@!q:pointer;@!x_off:integer); +var @!w,@!m:integer; {unpacked weight and coordinate} +@!d:integer; {temporary data register} +begin d:=ho(info(q)); w:=d mod 8; m:=(d div 8)-m_offset(cur_edges); +if file_offset>max_print_line-9 then print_nl(" ") +else print_char(" "); +print_int(m+x_off); +while w>zero_w do + begin print_char("+"); decr(w); + end; +while wh do + begin pp:=get_node(row_node_size); link(qq):=pp; knil(pp):=qq; + @; + p:=link(p); qq:=pp; + end; +link(qq):=hh; knil(hh):=qq; +copy_edges:=hh; +end; + +@ @= +r:=sorted(p); rr:=sorted_loc(pp); {|link(rr)=sorted(pp)|} +while r<>sentinel do + begin ss:=get_avail; link(rr):=ss; rr:=ss; info(rr):=info(r);@/ + r:=link(r); + end; +link(rr):=sentinel;@/ +r:=unsorted(p); rr:=temp_head; +while r>void do + begin ss:=get_avail; link(rr):=ss; rr:=ss; info(rr):=info(r);@/ + r:=link(r); + end; +link(rr):=r; unsorted(pp):=link(temp_head) + +@ Another trivial routine flips |cur_edges| about the |x|-axis +(i.e., negates all the |y| coordinates), assuming that at least +one row is present. + +@p procedure y_reflect_edges; +var @!p,@!q,@!r:pointer; {list manipulation registers} +begin p:=n_min(cur_edges); +n_min(cur_edges):=zero_field+zero_field-1-n_max(cur_edges); +n_max(cur_edges):=zero_field+zero_field-1-p; +n_pos(cur_edges):=zero_field+zero_field-1-n_pos(cur_edges);@/ +p:=link(cur_edges); q:=cur_edges; {we assume that |p<>q|} +repeat r:=link(p); link(p):=q; knil(q):=p; q:=p; p:=r; +until q=cur_edges; +last_window_time(cur_edges):=0; +end; + +@ It's somewhat more difficult, yet not too hard, to reflect about the |y|-axis. + +@p procedure x_reflect_edges; +var @!p,@!q,@!r,@!s:pointer; {list manipulation registers} +@!m:integer; {|info| fields will be reflected with respect to this number} +begin p:=m_min(cur_edges); +m_min(cur_edges):=zero_field+zero_field-m_max(cur_edges); +m_max(cur_edges):=zero_field+zero_field-p; +m:=(zero_field+m_offset(cur_edges))*8+zero_w+min_halfword+zero_w+min_halfword; +m_offset(cur_edges):=zero_field; +p:=link(cur_edges); +repeat @; +@; +p:=link(p); +until p=cur_edges; +last_window_time(cur_edges):=0; +end; + +@ We want to change the sign of the weight as we change the sign of the +|x|~coordinate. Fortunately, it's easier to do this than to negate +one without the other. + +@= +q:=unsorted(p); +while q>void do + begin info(q):=m-info(q); q:=link(q); + end + +@ Reversing the order of a linked list is best thought of as the process of +popping nodes off one stack and pushing them on another. In this case we +pop from stack~|q| and push to stack~|r|. + +@= +q:=sorted(p); r:=sentinel; +while q<>sentinel do + begin s:=link(q); link(q):=r; r:=q; info(r):=m-info(q); q:=s; + end; +sorted(p):=r + +@ Now let's multiply all the $y$~coordinates of a nonempty edge structure +by a small integer $s>1$: + +@p procedure y_scale_edges(@!s:integer); +var @!p,@!q,@!pp,@!r,@!rr,@!ss:pointer; {list manipulation registers} +@!t:integer; {replication counter} +begin if (s*(n_max(cur_edges)+1-zero_field)>=4096) or@| + (s*(n_min(cur_edges)-zero_field)<=-4096) then + begin print_err("Scaled picture would be too big"); +@.Scaled picture...big@> + help3("I can't yscale the picture as requested---it would")@/ + ("make some coordinates too large or too small.")@/ + ("Proceed, and I'll omit the transformation."); + put_get_error; + end +else begin n_max(cur_edges):=s*(n_max(cur_edges)+1-zero_field)-1+zero_field; + n_min(cur_edges):=s*(n_min(cur_edges)-zero_field)+zero_field; + @; + last_window_time(cur_edges):=0; + end; +end; + +@ @= +p:=cur_edges; +repeat q:=p; p:=link(p); +for t:=2 to s do + begin pp:=get_node(row_node_size); link(q):=pp; knil(p):=pp; + link(pp):=p; knil(pp):=q; q:=pp; + @; + end; +until link(p)=cur_edges + +@ Scaling the $x$~coordinates is, of course, our next task. + +@p procedure x_scale_edges(@!s:integer); +var @!p,@!q:pointer; {list manipulation registers} +@!t:0..65535; {unpacked |info| field} +@!w:0..7; {unpacked weight} +@!delta:integer; {amount added to scaled |info|} +begin if (s*(m_max(cur_edges)-zero_field)>=4096) or@| + (s*(m_min(cur_edges)-zero_field)<=-4096) then + begin print_err("Scaled picture would be too big"); +@.Scaled picture...big@> + help3("I can't xscale the picture as requested---it would")@/ + ("make some coordinates too large or too small.")@/ + ("Proceed, and I'll omit the transformation."); + put_get_error; + end +else if (m_max(cur_edges)<>zero_field)or(m_min(cur_edges)<>zero_field) then + begin m_max(cur_edges):=s*(m_max(cur_edges)-zero_field)+zero_field; + m_min(cur_edges):=s*(m_min(cur_edges)-zero_field)+zero_field; + delta:=8*(zero_field-s*m_offset(cur_edges))+min_halfword; + m_offset(cur_edges):=zero_field;@/ + @; + last_window_time(cur_edges):=0; + end; +end; + +@ The multiplications cannot overflow because we know that |s<4096|. + +@= +q:=link(cur_edges); +repeat p:=sorted(q); +while p<>sentinel do + begin t:=ho(info(p)); w:=t mod 8; info(p):=(t-w)*s+w+delta; p:=link(p); + end; +p:=unsorted(q); +while p>void do + begin t:=ho(info(p)); w:=t mod 8; info(p):=(t-w)*s+w+delta; p:=link(p); + end; +q:=link(q); +until q=cur_edges + +@ Here is a routine that changes the signs of all the weights, without +changing anything else. + +@p procedure negate_edges(@!h:pointer); +label done; +var @!p,@!q,@!r,@!s,@!t,@!u:pointer; {structure traversers} +begin p:=link(h); +while p<>h do + begin q:=unsorted(p); + while q>void do + begin info(q):=8-2*((ho(info(q))) mod 8)+info(q); q:=link(q); + end; + q:=sorted(p); + if q<>sentinel then + begin repeat info(q):=8-2*((ho(info(q))) mod 8)+info(q); q:=link(q); + until q=sentinel; + @; + end; + p:=link(p); + end; +last_window_time(h):=0; +end; + +@ \MF\ would work even if the code in this section were omitted, because +a list of edge-and-weight data that is sorted only by +|m| but not~|w| turns out to be good enough for correct operation. +However, the author decided not to make the program even trickier than +it is already, since |negate_edges| isn't needed very often. +The simpler-to-state condition, ``keep the |sorted| list fully sorted,'' +is therefore being preserved at the cost of extra computation. + +@= +u:=sorted_loc(p); q:=link(u); r:=q; s:=link(r); {|q=sorted(p)|} +loop@+ if info(s)>info(r) then + begin link(u):=q; + if s=sentinel then goto done; + u:=r; q:=s; r:=q; s:=link(r); + end + else begin t:=s; s:=link(t); link(t):=q; q:=t; + end; +done: link(r):=sentinel + +@ The |unsorted| edges of a row are merged into the |sorted| ones by +a subroutine called |sort_edges|. It uses simple insertion sort, +followed by a merge, because the unsorted list is supposedly quite short. +However, the unsorted list is assumed to be nonempty. + +@p procedure sort_edges(@!h:pointer); {|h| is a row header} +label done; +var @!k:halfword; {key register that we compare to |info(q)|} +@!p,@!q,@!r,@!s:pointer; +begin r:=unsorted(h); unsorted(h):=null; +p:=link(r); link(r):=sentinel; link(temp_head):=r; +while p>void do {sort node |p| into the list that starts at |temp_head|} + begin k:=info(p); q:=temp_head; + repeat r:=q; q:=link(r); + until k<=info(q); + link(r):=p; r:=link(p); link(p):=q; p:=r; + end; +@; +end; + +@ In this step we use the fact that |sorted(h)=link(sorted_loc(h))|. + +@= +begin r:=sorted_loc(h); q:=link(r); p:=link(temp_head); +loop@+ begin k:=info(p); + while k>info(q) do + begin r:=q; q:=link(r); + end; + link(r):=p; s:=link(p); link(p):=q; + if s=sentinel then goto done; + r:=p; p:=s; + end; +done:end + +@ The |cull_edges| procedure ``optimizes'' an edge structure by making all +the pixel weights either |w_out| or~|w_in|. The weight will be~|w_in| after the +operation if and only if it was in the closed interval |[w_lo,w_hi]| +before, where |w_lo<=w_hi|. Either |w_out| or |w_in| is zero, while the other is +$\pm1$, $\pm2$, or $\pm3$. The parameters will be such that zero-weight +pixels will remain of weight zero. (This is fortunate, +because there are infinitely many of them.) + +The procedure also computes the tightest possible bounds on the resulting +data, by updating |m_min|, |m_max|, |n_min|, and~|n_max|. + +@p procedure cull_edges(@!w_lo,@!w_hi,@!w_out,@!w_in:integer); +label done; +var @!p,@!q,@!r,@!s:pointer; {for list manipulation} +@!w:integer; {new weight after culling} +@!d:integer; {data register for unpacking} +@!m:integer; {the previous column number, including |m_offset|} +@!mm:integer; {the next column number, including |m_offset|} +@!ww:integer; {accumulated weight before culling} +@!prev_w:integer; {value of |w| before column |m|} +@!n,@!min_n,@!max_n:pointer; {current and extreme row numbers} +@!min_d,@!max_d:pointer; {extremes of the new edge-and-weight data} +begin min_d:=max_halfword; max_d:=min_halfword; +min_n:=max_halfword; max_n:=min_halfword;@/ +p:=link(cur_edges); n:=n_min(cur_edges); +while p<>cur_edges do + begin if unsorted(p)>void then sort_edges(p); + if sorted(p)<>sentinel then + @; + p:=link(p); incr(n); + end; +@; +last_window_time(cur_edges):=0; +end; + +@ The entire |sorted| list is returned to available memory in this step; +a new list is built starting (temporarily) at |temp_head|. +Since several edges can occur at the same column, we need to be looking +ahead of where the actual culling takes place. This means that it's +slightly tricky to get the iteration started and stopped. + +@= +begin r:=temp_head; q:=sorted(p); ww:=0; m:=1000000; prev_w:=0; +loop@+ begin if q=sentinel then mm:=1000000 + else begin d:=ho(info(q)); mm:=d div 8; ww:=ww+(d mod 8)-zero_w; + end; + if mm>m then + begin @; + if q=sentinel then goto done; + end; + m:=mm; + if ww>=w_lo then if ww<=w_hi then w:=w_in + else w:=w_out + else w:=w_out; + s:=link(q); free_avail(q); q:=s; + end; +done: link(r):=sentinel; sorted(p):=link(temp_head); +if r<>temp_head then @; +end + +@ @= +if w<>prev_w then + begin s:=get_avail; link(r):=s; + info(s):=8*m+min_halfword+zero_w+w-prev_w; + r:=s; prev_w:=w; + end + +@ @= +begin if min_n=max_halfword then min_n:=n; +max_n:=n; +if min_d>info(link(temp_head)) then min_d:=info(link(temp_head)); +if max_d= +if min_n>max_n then @ +else begin n:=n_min(cur_edges); n_min(cur_edges):=min_n; + while min_n>n do + begin p:=link(cur_edges); link(cur_edges):=link(p); + knil(link(p)):=cur_edges; + free_node(p,row_node_size); incr(n); + end; + n:=n_max(cur_edges); n_max(cur_edges):=max_n; + n_pos(cur_edges):=max_n+1; n_rover(cur_edges):=cur_edges; + while max_n= +begin p:=link(cur_edges); +while p<>cur_edges do + begin q:=link(p); free_node(p,row_node_size); p:=q; + end; +init_edges(cur_edges); +end + + +@ The last and most difficult routine for transforming an edge structure---and +the most interesting one!---is |xy_swap_edges|, which interchanges the +r\^^Doles of rows and columns. Its task can be viewed as the job of +creating an edge structure that contains only horizontal edges, linked +together in columns, given an edge structure that contains only +vertical edges linked together in rows; we must do this without changing +the implied pixel weights. + +Given any two adjacent rows of an edge structure, it is not difficult to +determine the horizontal edges that lie ``between'' them: We simply look +for vertically adjacent pixels that have different weight, and insert +a horizontal edge containing the difference in weights. Every horizontal +edge determined in this way should be put into an appropriate linked +list. Since random access to these linked lists is desirable, we use +the |move| array to hold the list heads. If we work through the given +edge structure from top to bottom, the constructed lists will not need +to be sorted, since they will already be in order. + +The following algorithm makes use of some ideas suggested by John Hobby. +@^Hobby, John Douglas@> +It assumes that the edge structure is non-null, i.e., that |link(cur_edges) +<>cur_edges|, hence |m_max(cur_edges)>=m_min(cur_edges)|. + +@p procedure xy_swap_edges; {interchange |x| and |y| in |cur_edges|} +label done; +var @!m_magic,@!n_magic:integer; {special values that account for offsets} +@!p,@!q,@!r,@!s:pointer; {pointers that traverse the given structure} +@@; +begin @; +@; +@; +repeat q:=knil(p);@+if unsorted(q)>void then sort_edges(q); +@; +p:=q; n_magic:=n_magic-8; +until knil(p)=cur_edges; +free_node(p,row_node_size); {now all original rows have been recycled} +@; +end; + +@ Here we don't bother to keep the |link| entries up to date, since the +procedure looks only at the |knil| fields as it destroys the former +edge structure. + +@= +p:=get_node(row_node_size); sorted(p):=sentinel; unsorted(p):=null;@/ +knil(p):=cur_edges; knil(link(cur_edges)):=p; {the new bottom row} +p:=get_node(row_node_size); sorted(p):=sentinel; +knil(p):=knil(cur_edges); {the new top row} + +@ The new lists will become |sorted| lists later, so we initialize +empty lists to |sentinel|. + +@= +m_spread:=m_max(cur_edges)-m_min(cur_edges); {this is |>=0| by assumption} +if m_spread>move_size then overflow("move table size",move_size); +@:METAFONT capacity exceeded move table size}{\quad move table size@> +for j:=0 to m_spread do move[j]:=sentinel + +@ @= +@!m_spread:integer; {the difference between |m_max| and |m_min|} +@!j,@!jj:0..move_size; {indices into |move|} +@!m,@!mm:integer; {|m| values at vertical edges} +@!pd,@!rd:integer; {data fields from edge-and-weight nodes} +@!pm,@!rm:integer; {|m| values from edge-and-weight nodes} +@!w:integer; {the difference in accumulated weight} +@!ww:integer; {as much of |w| that can be stored in a single node} +@!dw:integer; {an increment to be added to |w|} + +@ At the point where we test |w<>0|, variable |w| contains +the accumulated weight from edges already passed in +row~|p| minus the accumulated weight from edges already passed in row~|q|. + +@= +r:=sorted(p); free_node(p,row_node_size); p:=r;@/ +pd:=ho(info(p)); pm:=pd div 8;@/ +r:=sorted(q); rd:=ho(info(r)); rm:=rd div 8; w:=0; +loop@+ begin if pm0 then + @; + if pd; + end + else begin if r=sentinel then goto done; {|rd=pd=ho(max_halfword)|} + dw:=-((rd mod 8)-zero_w); + @; + end; + m:=mm; w:=w+dw; + end; +done: + +@ @= +r:=link(r); rd:=ho(info(r)); rm:=rd div 8 + +@ @= +s:=link(p); free_avail(p); p:=s; pd:=ho(info(p)); pm:=pd div 8 + +@ Certain ``magic'' values are needed to make the following code work, +because of the various offsets in our data structure. For now, let's not +worry about their precise values; we shall compute |m_magic| and |n_magic| +later, after we see what the code looks like. + +@ @= +if m<>mm then + begin if mm-m_magic>=move_size then confusion("xy"); +@:this can't happen xy}{\quad xy@> + extras:=(abs(w)-1) div 3; + if extras>0 then + begin if w>0 then xw:=+3@+else xw:=-3; + ww:=w-extras*xw; + end + else ww:=w; + repeat j:=m-m_magic; + for k:=1 to extras do + begin s:=get_avail; info(s):=n_magic+xw; + link(s):=move[j]; move[j]:=s; + end; + s:=get_avail; info(s):=n_magic+ww; + link(s):=move[j]; move[j]:=s;@/ + incr(m); + until m=mm; + end + +@ @= +@!extras:integer; {the number of additional nodes to make weights |>3|} +@!xw:-3..3; {the additional weight in extra nodes} +@!k:integer; {loop counter for inserting extra nodes} + +@ At the beginning of this step, |move[m_spread]=sentinel|, because no +horizontal edges will extend to the right of column |m_max(cur_edges)|. + +@= +move[m_spread]:=0; j:=0; +while move[j]=sentinel do incr(j); +if j=m_spread then init_edges(cur_edges) {all edge weights are zero} +else begin mm:=m_min(cur_edges); + m_min(cur_edges):=n_min(cur_edges); + m_max(cur_edges):=n_max(cur_edges)+1; + m_offset(cur_edges):=zero_field; + jj:=m_spread-1; + while move[jj]=sentinel do decr(jj); + n_min(cur_edges):=j+mm; n_max(cur_edges):=jj+mm; q:=cur_edges; + repeat p:=get_node(row_node_size); link(q):=p; knil(p):=q; + sorted(p):=move[j]; unsorted(p):=null; incr(j); q:=p; + until j>jj; + link(q):=cur_edges; knil(cur_edges):=q; + n_pos(cur_edges):=n_max(cur_edges)+1; n_rover(cur_edges):=cur_edges; + last_window_time(cur_edges):=0; + end; + +@ The values of |m_magic| and |n_magic| can be worked out by trying the +code above on a small example; if they work correctly in simple cases, +they should work in general. + +@= +m_magic:=m_min(cur_edges)+m_offset(cur_edges)-zero_field; +n_magic:=8*n_max(cur_edges)+8+zero_w+min_halfword + +@ Now let's look at the subroutine that merges the edges from a given +edge structure into |cur_edges|. The given edge structure loses all its +edges. + +@p procedure merge_edges(@!h:pointer); +label done; +var @!p,@!q,@!r,@!pp,@!qq,@!rr:pointer; {list manipulation registers} +@!n:integer; {row number} +@!k:halfword; {key register that we compare to |info(q)|} +@!delta:integer; {change to the edge/weight data} +begin if link(h)<>h then + begin if (m_min(h)m_max(cur_edges))or@| + (n_min(h)n_max(cur_edges)) then + edge_prep(m_min(h)-zero_field,m_max(h)-zero_field, + n_min(h)-zero_field,n_max(h)-zero_field+1); + if m_offset(h)<>m_offset(cur_edges) then + @; + n:=n_min(cur_edges); p:=link(cur_edges); pp:=link(h); + while n; + pp:=link(pp); p:=link(p); + until pp=h; + end; +end; + +@ @= +begin pp:=link(h); delta:=8*(m_offset(cur_edges)-m_offset(h)); +repeat qq:=sorted(pp); +while qq<>sentinel do + begin info(qq):=info(qq)+delta; qq:=link(qq); + end; +qq:=unsorted(pp); +while qq>void do + begin info(qq):=info(qq)+delta; qq:=link(qq); + end; +pp:=link(pp); +until pp=h; +end + +@ The |sorted| and |unsorted| lists are merged separately. After this +step, row~|pp| will have no edges remaining, since they will all have +been merged into row~|p|. + +@= +qq:=unsorted(pp); +if qq>void then + if unsorted(p)<=void then unsorted(p):=qq + else begin while link(qq)>void do qq:=link(qq); + link(qq):=unsorted(p); unsorted(p):=unsorted(pp); + end; +unsorted(pp):=null; qq:=sorted(pp); +if qq<>sentinel then + begin if unsorted(p)=void then unsorted(p):=null; + sorted(pp):=sentinel; r:=sorted_loc(p); q:=link(r); {|q=sorted(p)|} + if q=sentinel then sorted(p):=qq + else loop@+begin k:=info(qq); + while k>info(q) do + begin r:=q; q:=link(r); + end; + link(r):=qq; rr:=link(qq); link(qq):=q; + if rr=sentinel then goto done; + r:=qq; qq:=rr; + end; + end; +done: + +@ The |total_weight| routine computes the total of all pixel weights +in a given edge structure. It's not difficult to prove that this is +the sum of $(-w)$ times $x$ taken over all edges, +where $w$ and~$x$ are the weight and $x$~coordinates stored in an edge. +It's not necessary to worry that this quantity will overflow the +size of an |integer| register, because it will be less than~$2^{31}$ +unless the edge structure has more than 174,762 edges. However, we had +better not try to compute it as a |scaled| integer, because a total +weight of almost $12\times 2^{12}$ can be produced by only four edges. + +@p function total_weight(@!h:pointer):integer; {|h| is an edge header} +var @!p,@!q:pointer; {variables that traverse the given structure} +@!n:integer; {accumulated total so far} +@!m:0..65535; {packed $x$ and $w$ values, including offsets} +begin n:=0; p:=link(h); +while p<>h do + begin q:=sorted(p); + while q<>sentinel do + @; + q:=unsorted(p); + while q>void do + @; + p:=link(p); + end; +total_weight:=n; +end; + +@ It's not necessary to add the offsets to the $x$ coordinates, because +an entire edge structure can be shifted without affecting its total weight. +Similarly, we don't need to subtract |zero_field|. + +@= +begin m:=ho(info(q)); n:=n-((m mod 8)-zero_w)*(m div 8); +q:=link(q); +end + +@ So far we've done lots of things to edge structures assuming that +edges are actually present, but we haven't seen how edges get created +in the first place. Let's turn now to the problem of generating new edges. + +\MF\ will display new edges as they are being computed, if |tracing_edges| +is positive. In order to keep such data reasonably compact, only the +points at which the path makes a $90^\circ$ or $180^\circ$ turn are listed. + +The tracing algorithm must remember some past history in order to suppress +unnecessary data. Three variables |trace_x|, |trace_y|, and |trace_yy| +provide this history: The last coordinates printed were |(trace_x,trace_y)|, +and the previous edge traced ended at |(trace_x,trace_yy)|. Before anything +at all has been traced, |trace_x=-4096|. + +@= +@!trace_x:integer; {$x$~coordinate most recently shown in a trace} +@!trace_y:integer; {$y$~coordinate most recently shown in a trace} +@!trace_yy:integer; {$y$~coordinate most recently encountered} + +@ Edge tracing is initiated by the |begin_edge_tracing| routine, +continued by the |trace_a_corner| routine, and terminated by the +|end_edge_tracing| routine. + +@p procedure begin_edge_tracing; +begin print_diagnostic("Tracing edges","",true); +print(" (weight "); print_int(cur_wt); print_char(")"); trace_x:=-4096; +end; +@# +procedure trace_a_corner; +begin if file_offset>max_print_line-13 then print_nl(""); +print_char("("); print_int(trace_x); print_char(","); print_int(trace_yy); +print_char(")"); trace_y:=trace_yy; +end; +@# +procedure end_edge_tracing; +begin if trace_x=-4096 then print_nl("(No new edges added.)") +@.No new edges added@> +else begin trace_a_corner; print_char("."); + end; +end_diagnostic(true); +end; + +@ Just after a new edge weight has been put into the |info| field of +node~|r|, in row~|n|, the following routine continues an ongoing trace. + +@p procedure trace_new_edge(@!r:pointer;@!n:integer); +var @!d:integer; {temporary data register} +@!w:-3..3; {weight associated with an edge transition} +@!m,@!n0,@!n1:integer; {column and row numbers} +begin d:=ho(info(r)); w:=(d mod 8)-zero_w; m:=(d div 8)-m_offset(cur_edges); +if w=cur_wt then + begin n0:=n+1; n1:=n; + end +else begin n0:=n; n1:=n+1; + end; {the edges run from |(m,n0)| to |(m,n1)|} +if m<>trace_x then + begin if trace_x=-4096 then + begin print_nl(""); trace_yy:=n0; + end + else if trace_yy<>n0 then print_char("?") {shouldn't happen} + else trace_a_corner; + trace_x:=m; trace_a_corner; + end +else begin if n0<>trace_yy then print_char("!"); {shouldn't happen} + if ((n0trace_yy))or((n0>n1)and(trace_yn1 then + begin m0:=round_unscaled(x0); m1:=round_unscaled(x1); + delx:=x1-x0; dely:=y1-y0; + yt:=n0*unity-half_unit; y0:=y0-yt; y1:=y1-yt; + if n0 + else @; + n_rover(cur_edges):=p; n_pos(cur_edges):=n+zero_field; + end; +end; + +@ Here we are careful to cancel any effect of rounding error. + +@= +begin base:=8*m_offset(cur_edges)+min_halfword+zero_w-cur_wt; +if m0<=m1 then edge_prep(m0,m1,n0,n1)@+else edge_prep(m1,m0,n0,n1); +@; +y0:=unity-y0; +loop@+ begin r:=get_avail; link(r):=unsorted(p); unsorted(p):=r;@/ + tx:=take_fraction(delx,make_fraction(y0,dely)); + if ab_vs_cd(delx,y0,dely,tx)<0 then decr(tx); + {now $|tx|=\lfloor|y0|\cdot|delx|/|dely|\rfloor$} + info(r):=8*round_unscaled(x0+tx)+base;@/ + y1:=y1-unity; + if internal[tracing_edges]>0 then trace_new_edge(r,n); + if y1= +begin base:=8*m_offset(cur_edges)+min_halfword+zero_w+cur_wt; +if m0<=m1 then edge_prep(m0,m1,n1,n0)@+else edge_prep(m1,m0,n1,n0); +decr(n0); @; +loop@+ begin r:=get_avail; link(r):=unsorted(p); unsorted(p):=r;@/ + tx:=take_fraction(delx,make_fraction(y0,dely)); + if ab_vs_cd(delx,y0,dely,tx)<0 then incr(tx); + {now $|tx|=\lceil|y0|\cdot|delx|/|dely|\rceil$, since |dely<0|} + info(r):=8*round_unscaled(x0-tx)+base;@/ + y1:=y1+unity; + if internal[tracing_edges]>0 then trace_new_edge(r,n); + if y1>=0 then goto done1; + p:=knil(p); y0:=y0+unity; decr(n); + end; +done1: end + +@ @= +n:=n_pos(cur_edges)-zero_field; p:=n_rover(cur_edges); +if n<>n0 then + if nm1-m0 then confusion("0");@+gubed@;@/ +@:this can't happen 0}{\quad 0@> +@; +fast_case_up:@; +fast_case_down:@; +slow_case_up:@; +slow_case_down:@; +done: n_pos(cur_edges):=n+zero_field; n_rover(cur_edges):=p; +end; + +@ The current octant code appears in a global variable. If, for example, +we have |octant=third_octant|, it means that a curve traveling in a north to +north-westerly direction has been rotated for the purposes of internal +calculations so that the |move| data travels in an east to north-easterly +direction. We want to unrotate as we update the edge structure. + +@= +@!octant:first_octant..sixth_octant; {the current octant of interest} + +@ @= +case octant of +first_octant:begin dx:=8; edge_prep(m0,m1,n0,n1); goto fast_case_up; + end; +second_octant:begin dx:=8; edge_prep(n0,n1,m0,m1); goto slow_case_up; + end; +third_octant:begin dx:=-8; edge_prep(-n1,-n0,m0,m1); negate(n0); + goto slow_case_up; + end; +fourth_octant:begin dx:=-8; edge_prep(-m1,-m0,n0,n1); negate(m0); + goto fast_case_up; + end; +fifth_octant:begin dx:=-8; edge_prep(-m1,-m0,-n1,-n0); negate(m0); + goto fast_case_down; + end; +sixth_octant:begin dx:=-8; edge_prep(-n1,-n0,-m1,-m0); negate(n0); + goto slow_case_down; + end; +seventh_octant:begin dx:=8; edge_prep(n0,n1,-m1,-m0); goto slow_case_down; + end; +eighth_octant:begin dx:=8; edge_prep(m0,m1,-n1,-n0); goto fast_case_down; + end; +end; {there are only eight octants} + +@ @= +@; +if delta>0 then + begin k:=0; + edge_and_weight:=8*(m0+m_offset(cur_edges))+min_halfword+zero_w-cur_wt; + repeat edge_and_weight:=edge_and_weight+dx*move[k]; + fast_get_avail(r); link(r):=unsorted(p); info(r):=edge_and_weight; + if internal[tracing_edges]>0 then trace_new_edge(r,n); + unsorted(p):=r; p:=link(p); incr(k); incr(n); + until k=delta; + end; +goto done + +@ @= +n0:=-n0-1; @; +if delta>0 then + begin k:=0; + edge_and_weight:=8*(m0+m_offset(cur_edges))+min_halfword+zero_w+cur_wt; + repeat edge_and_weight:=edge_and_weight+dx*move[k]; + fast_get_avail(r); link(r):=unsorted(p); info(r):=edge_and_weight; + if internal[tracing_edges]>0 then trace_new_edge(r,n); + unsorted(p):=r; p:=knil(p); incr(k); decr(n); + until k=delta; + end; +goto done + +@ @= +edge_and_weight:=8*(n0+m_offset(cur_edges))+min_halfword+zero_w-cur_wt; +n0:=m0; k:=0; @; +repeat j:=move[k]; +while j>0 do + begin fast_get_avail(r); link(r):=unsorted(p); info(r):=edge_and_weight; + if internal[tracing_edges]>0 then trace_new_edge(r,n); + unsorted(p):=r; p:=link(p); decr(j); incr(n); + end; +edge_and_weight:=edge_and_weight+dx; incr(k); +until k>delta; +goto done + +@ @= +edge_and_weight:=8*(n0+m_offset(cur_edges))+min_halfword+zero_w+cur_wt; +n0:=-m0-1; k:=0; @; +repeat j:=move[k]; +while j>0 do + begin fast_get_avail(r); link(r):=unsorted(p); info(r):=edge_and_weight; + if internal[tracing_edges]>0 then trace_new_edge(r,n); + unsorted(p):=r; p:=knil(p); decr(j); decr(n); + end; +edge_and_weight:=edge_and_weight+dx; incr(k); +until k>delta; +goto done + +@ All the hard work of building an edge structure is undone by the following +subroutine. + +@= +procedure toss_edges(@!h:pointer); +var @!p,@!q:pointer; {for list manipulation} +begin q:=link(h); +while q<>h do + begin flush_list(sorted(q)); + if unsorted(q)>void then flush_list(unsorted(q)); + p:=q; q:=link(q); free_node(p,row_node_size); + end; +free_node(h,edge_header_size); +end; + +@* \[21] Subdivision into octants. +When \MF\ digitizes a path, it reduces the problem to the special +case of paths that travel in ``first octant'' directions; i.e., +each cubic $z(t)=\bigl(x(t),y(t)\bigr)$ being digitized will have the property +that $0\L y'(t)\L x'(t)$. This assumption makes digitizing simpler +and faster than if the direction of motion has to be tested repeatedly. + +When $z(t)$ is cubic, $x'(t)$ and $y'(t)$ are quadratic, hence the four +polynomials $x'(t)$, $y'(t)$, $x'(t)-y'(t)$, and $x'(t)+y'(t)$ cross +through~0 at most twice each. If we subdivide the given cubic at these +places, we get at most nine subintervals in each of which +$x'(t)$, $y'(t)$, $x'(t)-y'(t)$, and $x'(t)+y'(t)$ all have a constant +sign. The curve can be transformed in each of these subintervals so that +it travels entirely in first octant directions, if we reflect $x\swap-x$, +$y\swap-y$, and/or $x\swap y$ as necessary. (Incidentally, it can be +shown that a cubic such that $x'(t)=16(2t-1)^2+2(2t-1)-1$ and +$y'(t)=8(2t-1)^2+4(2t-1)$ does indeed split into nine subintervals.) + +@ The transformation that rotates coordinates, so that first octant motion +can be assumed, is defined by the |skew| subroutine, which sets global +variables |cur_x| and |cur_y| to the values that are appropriate in a +given octant. (Octants are encoded as they were in the |n_arg| subroutine.) + +This transformation is ``skewed'' by replacing |(x,y)| by |(x-y,y)|, +once first octant motion has been established. It turns out that +skewed coordinates are somewhat better to work with when curves are +actually digitized. + +@d set_two_end(#)==cur_y:=#;@+end +@d set_two(#)==begin cur_x:=#; set_two_end + +@p procedure skew(@!x,@!y:scaled;@!octant:small_number); +begin case octant of +first_octant: set_two(x-y)(y); +second_octant: set_two(y-x)(x); +third_octant: set_two(y+x)(-x); +fourth_octant: set_two(-x-y)(y); +fifth_octant: set_two(-x+y)(-y); +sixth_octant: set_two(-y+x)(-x); +seventh_octant: set_two(-y-x)(x); +eighth_octant: set_two(x+y)(-y); +end; {there are no other cases} +end; + +@ Conversely, the following subroutine sets |cur_x| and +|cur_y| to the original coordinate values of a point, given an octant +code and the point's coordinates |(x,y)| after they have been mapped into +the first octant and skewed. + +@= +procedure unskew(@!x,@!y:scaled;@!octant:small_number); +begin case octant of +first_octant: set_two(x+y)(y); +second_octant: set_two(y)(x+y); +third_octant: set_two(-y)(x+y); +fourth_octant: set_two(-x-y)(y); +fifth_octant: set_two(-x-y)(-y); +sixth_octant: set_two(-y)(-x-y); +seventh_octant: set_two(y)(-x-y); +eighth_octant: set_two(x+y)(-y); +end; {there are no other cases} +end; + +@ @= +@!cur_x,@!cur_y:scaled; + {outputs of |skew|, |unskew|, and a few other routines} + +@ The conversion to skewed and rotated coordinates takes place in +stages, and at one point in the transformation we will have negated the +$x$ and/or $y$ coordinates so as to make curves travel in the first +{\sl quadrant}. At this point the relevant ``octant'' code will be +either |first_octant| (when no transformation has been done), +or |fourth_octant=first_octant+negate_x| (when $x$ has been negated), +or |fifth_octant=first_octant+negate_x+negate_y| (when both have been +negated), or |eighth_octant=first_octant+negate_y| (when $y$ has been +negated). The |abnegate| routine is sometimes needed to convert +from one of these transformations to another. + +@p procedure abnegate(@!x,@!y:scaled; + @!octant_before,@!octant_after:small_number); +begin if odd(octant_before)=odd(octant_after) then cur_x:=x + else cur_x:=-x; +if (octant_before>negate_y)=(octant_after>negate_y) then cur_y:=y + else cur_y:=-y; +end; + +@ Now here's a subroutine that's handy for subdivision: Given a +quadratic polynomial $B(a,b,c;t)$, the |crossing_point| function +returns the unique |fraction| value |t| between 0 and~1 at which +$B(a,b,c;t)$ changes from positive to negative, or returns +|t=fraction_one+1| if no such value exists. If |a<0| (so that $B(a,b,c;t)$ +is already negative at |t=0|), |crossing_point| returns the value zero. + +@d no_crossing==begin crossing_point:=fraction_one+1; return; + end +@d one_crossing==begin crossing_point:=fraction_one; return; + end +@d zero_crossing==begin crossing_point:=0; return; + end + +@p function crossing_point(@!a,@!b,@!c:integer):fraction; +label exit; +var @!d:integer; {recursive counter} +@!x,@!xx,@!x0,@!x1,@!x2:integer; {temporary registers for bisection} +begin if a<0 then zero_crossing; +if c>=0 then + begin if b>=0 then + if c>0 then no_crossing + else if (a=0)and(b=0) then no_crossing + else one_crossing; + if a=0 then zero_crossing; + end +else if a=0 then if b<=0 then zero_crossing; +@; +exit:end; + +@ The general bisection method is quite simple when $n=2$, hence +|crossing_point| does not take much time. At each stage in the +recursion we have a subinterval defined by |l| and~|j| such that +$B(a,b,c;2^{-l}(j+t))=B(x_0,x_1,x_2;t)$, and we want to ``zero in'' on +the subinterval where $x_0\G0$ and $\min(x_1,x_2)<0$. + +It is convenient for purposes of calculation to combine the values +of |l| and~|j| in a single variable $d=2^l+j$, because the operation +of bisection then corresponds simply to doubling $d$ and possibly +adding~1. Furthermore it proves to be convenient to modify +our previous conventions for bisection slightly, maintaining the +variables $X_0=2^lx_0$, $X_1=2^l(x_0-x_1)$, and $X_2=2^l(x_1-x_2)$. +With these variables the conditions $x_0\ge0$ and $\min(x_1,x_2)<0$ are +equivalent to $\max(X_1,X_1+X_2)>X_0\ge0$. + +The following code maintains the invariant relations +$0\L|x0|<\max(|x1|,|x1|+|x2|)$, +$\vert|x1|\vert<2^{30}$, $\vert|x2|\vert<2^{30}$; +it has been constructed in such a way that no arithmetic overflow +will occur if the inputs satisfy +$a<2^{30}$, $\vert a-b\vert<2^{30}$, and $\vert b-c\vert<2^{30}$. + +@= +d:=1; x0:=a; x1:=a-b; x2:=b-c; +repeat x:=half(x1+x2); +if x1-x0>x0 then + begin x2:=x; double(x0); double(d); + end +else begin xx:=x1+x-x0; + if xx>x0 then + begin x2:=x; double(x0); double(d); + end + else begin x0:=x0-xx; + if x<=x0 then if x+x2<=x0 then no_crossing; + x1:=x; d:=d+d+1; + end; + end; +until d>=fraction_one; +crossing_point:=d-fraction_one + +@ Octant subdivision is applied only to cycles, i.e., to closed paths. +A ``cycle spec'' is a data structure that contains specifications of +@!@^cycle spec@> +cubic curves and octant mappings for the cycle that has been subdivided +into segments belonging to single octants. It is composed entirely of +knot nodes, similar to those in the representation of paths; but the +|explicit| type indications have been replaced by positive numbers +that give further information. Additional |endpoint| data is also +inserted at the octant boundaries. + +Recall that a cubic polynomial is represented by four control points +that appear in adjacent nodes |p| and~|q| of a knot list. The |x|~coordinates +are |x_coord(p)|, |right_x(p)|, |left_x(q)|, and |x_coord(q)|; the +|y|~coordinates are similar. We shall call this ``the cubic following~|p|'' +or ``the cubic between |p| and~|q|'' or ``the cubic preceding~|q|.'' + +Cycle specs are circular lists of cubic curves mixed with octant +boundaries. Like cubics, the octant boundaries are represented in +consecutive knot nodes |p| and~|q|. In such cases |right_type(p)= +left_type(q)=endpoint|, and the fields |right_x(p)|, |right_y(p)|, +|left_x(q)|, and |left_y(q)| are replaced by other fields called +|right_octant(p)|, |right_transition(p)|, |left_octant(q)|, and +|left_transition(q)|, respectively. For example, when the curve direction +moves from the third octant to the fourth octant, the boundary nodes say +|right_octant(p)=third_octant|, |left_octant(q)=fourth_octant|, +and |right_transition(p)=left_transition(q)=diagonal|. A |diagonal| +transition occurs when moving between octants 1~\AM~2, 3~\AM~4, 5~\AM~6, or +7~\AM~8; an |axis| transition occurs when moving between octants 8~\AM~1, +2~\AM~3, 4~\AM~5, 6~\AM~7. (Such transition information is redundant +but convenient.) Fields |x_coord(p)| and |y_coord(p)| will contain +coordinates of the transition point after rotation from third octant +to first octant; i.e., if the true coordinates are $(x,y)$, the +coordinates $(y,-x)$ will appear in node~|p|. Similarly, a fourth-octant +transformation will have been applied after the transition, so +we will have |x_coord(q)=@t$-x$@>| and |y_coord(q)=y|. + +The cubic between |p| and |q| will contain positive numbers in the +fields |right_type(p)| and |left_type(q)|; this makes cubics +distinguishable from octant boundaries, because |endpoint=0|. +The value of |right_type(p)| will be the current octant code, +during the time that cycle specs are being constructed; it will +refer later to a pen offset position, if the envelope of a cycle is +being computed. A cubic that comes from some subinterval of the $k$th +step in the original cyclic path will have |left_type(q)=k|. + +@d right_octant==right_x {the octant code before a transition} +@d left_octant==left_x {the octant after a transition} +@d right_transition==right_y {the type of transition} +@d left_transition==left_y {ditto, either |axis| or |diagonal|} +@d axis=0 {a transition across the $x'$- or $y'$-axis} +@d diagonal=1 {a transition where $y'=\pm x'$} + +@ Here's a routine that prints a cycle spec in symbolic form, so that it +is possible to see what subdivision has been made. The point coordinates +are converted back from \MF's internal ``rotated'' form to the external +``true'' form. The global variable~|cur_spec| should point to a knot just +after the beginning of an octant boundary, i.e., such that +|left_type(cur_spec)=endpoint|. + +@d print_two_true(#)==unskew(#,octant); print_two(cur_x,cur_y) + +@p procedure print_spec(@!s:str_number); +label not_found,done; +var @!p,@!q:pointer; {for list traversal} +@!octant:small_number; {the current octant code} +begin print_diagnostic("Cycle spec",s,true); +@.Cycle spec at line...@> +p:=cur_spec; octant:=left_octant(p); print_ln; +print_two_true(x_coord(cur_spec),y_coord(cur_spec)); +print(" % beginning in octant `"); +loop@+ begin print(octant_dir[octant]); print_char("'"); + loop@+ begin q:=link(p); + if right_type(p)=endpoint then goto not_found; + @; + p:=q; + end; +not_found: if q=cur_spec then goto done; + p:=q; octant:=left_octant(p); print_nl("% entering octant `"); + end; +@.entering the nth octant@> +done: print_nl(" & cycle"); end_diagnostic(true); +end; + +@ Symbolic octant direction names are kept in the |octant_dir| array. + +@= +@!octant_dir:array[first_octant..sixth_octant] of str_number; + +@ @= +octant_dir[first_octant]:="ENE"; +octant_dir[second_octant]:="NNE"; +octant_dir[third_octant]:="NNW"; +octant_dir[fourth_octant]:="WNW"; +octant_dir[fifth_octant]:="WSW"; +octant_dir[sixth_octant]:="SSW"; +octant_dir[seventh_octant]:="SSE"; +octant_dir[eighth_octant]:="ESE"; + +@ @= +begin print_nl(" ..controls "); +print_two_true(right_x(p),right_y(p)); +print(" and "); +print_two_true(left_x(q),left_y(q)); +print_nl(" .."); +print_two_true(x_coord(q),y_coord(q)); +print(" % segment "); print_int(left_type(q)-1); +end + +@ A much more compact version of a spec is printed to help users identify +``strange paths.'' + +@p procedure print_strange(@!s:str_number); +var @!p:pointer; {for list traversal} +@!f:pointer; {starting point in the cycle} +@!q:pointer; {octant boundary to be printed} +@!t:integer; {segment number, plus 1} +begin if interaction=error_stop_mode then wake_up_terminal; +print_nl(">"); +@.>\relax@> +@; +@; +t:=0; +repeat if left_type(p)<>endpoint then + begin if left_type(p)<>t then + begin t:=left_type(p); print_char(" "); print_int(t-1); + end; + if q<>null then + begin @; + print_char(" "); print(octant_dir[left_octant(q)]); q:=null; + end; + end +else if q=null then q:=p; +p:=link(p); +until p=f; +print_char(" "); print_int(left_type(p)-1); +if q<>null then @; +print_err(s); +end; + +@ If the segment numbers on the cycle are $t_1$, $t_2$, \dots, $t_m$, +and if |m<=max_quarterword|, +we have $t_{k-1}\L t_k$ except for at most one value of~$k$. If there are +no exceptions, $f$ will point to $t_1$; otherwise it will point to the +exceptional~$t_k$. + +There is at least one segment number (i.e., we always have $m>0$), because +|print_strange| is never called upon to display an entirely ``dead'' cycle. + +@= +p:=cur_spec; t:=max_quarterword+1; +repeat p:=link(p); +if left_type(p)<>endpoint then + begin if left_type(p)= +p:=cur_spec; q:=p; +repeat p:=link(p); +if left_type(p)=endpoint then q:=p; +until p=f + +@ When two octant boundaries are adjacent, the path is simply changing direction +without moving. Such octant directions are shown in parentheses. + +@= +if left_type(link(q))=endpoint then + begin print(" ("); print(octant_dir[left_octant(q)]); q:=link(q); + while left_type(link(q))=endpoint do + begin print_char(" "); print(octant_dir[left_octant(q)]); q:=link(q); + end; + print_char(")"); + end + +@ The |make_spec| routine is what subdivides paths into octants: +Given a pointer |cur_spec| to a cyclic path, |make_spec| mungs the path data +and returns a pointer to the corresponding cyclic spec. +All ``dead'' cubics (i.e., cubics that don't move at all from +their starting points) will have been removed from the result. +@!@^dead cubics@> + +The idea of |make_spec| is fairly simple: Each cubic is first +subdivided, if necessary, into pieces belonging to single octants; +then the octant boundaries are inserted. But some of the details of +this transformation are not quite obvious. + +If |autorounding>0|, the path will be adjusted so that critical tangent +directions occur at ``good'' points with respect to the pen called |cur_pen|. + +The resulting spec will have all |x| and |y| coordinates at most +$2^{28}-|half_unit|-1-|safety_margin|$ in absolute value. The pointer +that is returned will start some octant, as required by |print_spec|. + +@p @t\4@>@@; +function make_spec(@!h:pointer; + @!safety_margin:scaled;@!tracing:integer):pointer; + {converts a path to a cycle spec} +label continue,done; +var @!p,@!q,@!r,@!s:pointer; {for traversing the lists} +@!k:integer; {serial number of path segment, or octant code} +@!chopped:integer; {positive if data truncated, + negative if data dangerously large} +@@; +begin cur_spec:=h; +if tracing>0 then + print_path(cur_spec,", before subdivision into octants",true); +max_allowed:=fraction_one-half_unit-1-safety_margin; +@; +quadrant_subdivide; {subdivide each cubic into pieces belonging to quadrants} +if (internal[autorounding]>0)and(chopped=0) then xy_round; +octant_subdivide; {complete the subdivision} +if (internal[autorounding]>unity)and(chopped=0) then diag_round; +@; +@; +while left_type(cur_spec)<>endpoint do cur_spec:=link(cur_spec); +if tracing>0 then + if (internal[autorounding]<=0)or(chopped<>0) then + print_spec(", after subdivision") + else if internal[autorounding]>unity then + print_spec(", after subdivision and double autorounding") + else print_spec(", after subdivision and autorounding"); +make_spec:=cur_spec; +end; + +@ The |make_spec| routine has an interesting side effect, namely to set +the global variable |turning_number| to the number of times the tangent +vector of the given cyclic path winds around the origin. + +Another global variable |cur_spec| points to the specification as it is +being made, since several subroutines must go to work on it. + +And there are two global variables that affect the rounding +decisions, as we'll see later; they are called |cur_pen| and |cur_path_type|. +The latter will be |double_path_code| if |make_spec| is being +applied to a double path. + +@d double_path_code=0 {command modifier for `\&{doublepath}'} +@d contour_code=1 {command modifier for `\&{contour}'} +@d also_code=2 {command modifier for `\&{also}'} + +@= +@!cur_spec:pointer; {the principal output of |make_spec|} +@!turning_number:integer; {another output of |make_spec|} +@!cur_pen:pointer; {an implicit input of |make_spec|, used in autorounding} +@!cur_path_type:double_path_code..contour_code; {likewise} +@!max_allowed:scaled; {coordinates must be at most this big} + +@ First we do a simple preprocessing step. The segment numbers inserted +here will propagate to all descendants of cubics that are split into +subintervals. These numbers must be nonzero, but otherwise they are +present merely for diagnostic purposes. The cubic from |p| to~|q| +that represents ``time interval'' |(t-1)..t| usually has |left_type(q)=t|, +except when |t| is too large to be stored in a quarterword. + +@d procrustes(#)==@+if abs(#)>=dmax then + if abs(#)>max_allowed then + begin chopped:=1; + if #>0 then #:=max_allowed@+else #:=-max_allowed; + end + else if chopped=0 then chopped:=-1 + +@= +p:=cur_spec; k:=1; chopped:=0; dmax:=half(max_allowed); +repeat procrustes(left_x(p)); procrustes(left_y(p)); +procrustes(x_coord(p)); procrustes(y_coord(p)); +procrustes(right_x(p)); procrustes(right_y(p));@/ +p:=link(p); left_type(p):=k; +if k0 then + begin print_err("Curve out of range"); +@.Curve out of range@> + help4("At least one of the coordinates in the path I'm about to")@/ + ("digitize was really huge (potentially bigger than 4095).")@/ + ("So I've cut it back to the maximum size.")@/ + ("The results will probably be pretty wild."); + put_get_error; + end + +@ We may need to get rid of constant ``dead'' cubics that clutter up +the data structure and interfere with autorounding. + +@= +procedure remove_cubic(@!p:pointer); {removes the cubic following~|p|} +var @!q:pointer; {the node that disappears} +begin q:=link(p); right_type(p):=right_type(q); link(p):=link(q);@/ +x_coord(p):=x_coord(q); y_coord(p):=y_coord(q);@/ +right_x(p):=right_x(q); right_y(p):=right_y(q);@/ +free_node(q,knot_node_size); +end; + +@ The subdivision process proceeds by first swapping $x\swap-x$, if +necessary, to ensure that $x'\G0$; then swapping $y\swap-y$, if necessary, +to ensure that $y'\G0$; and finally swapping $x\swap y$, if necessary, +to ensure that $x'\G y'$. + +Recall that the octant codes have been defined in such a way that, for +example, |third_octant=first_octant+negate_x+switch_x_and_y|. The program +uses the fact that |negate_xnegate_y|. Octant codes are always greater than zero. + +The first step is to subdivide on |x| and |y| only, so that horizontal +and vertical autorounding can be done before we compare $x'$ to $y'$. + +@= +@t\4@>@@; +procedure quadrant_subdivide; +label continue,exit; +var @!p,@!q,@!r,@!s,@!pp,@!qq:pointer; {for traversing the lists} +@!first_x,@!first_y:scaled; {unnegated coordinates of node |cur_spec|} +@!del1,@!del2,@!del3,@!del,@!dmax:scaled; {proportional to the control + points of a quadratic derived from a cubic} +@!t:fraction; {where a quadratic crosses zero} +@!dest_x,@!dest_y:scaled; {final values of |x| and |y| in the current cubic} +@!constant_x:boolean; {is |x| constant between |p| and |q|?} +begin p:=cur_spec; first_x:=x_coord(cur_spec); first_y:=y_coord(cur_spec); +repeat continue: q:=link(p); +@; +@; +p:=q; +until p=cur_spec; +exit:end; + +@ All three subdivision processes are similar, so it's possible to +get the general idea by studying the first one (which is the simplest). +The calculation makes use of the fact that the derivatives of +Bernshte{\u\i}n polynomials satisfy +$B'(z_0,z_1,\ldots,z_n;t)=nB(z_1-z_0,\ldots,z_n-z_{n-1};t)$. + +When this routine begins, |right_type(p)| is |explicit|; we should +set |right_type(p):=first_octant|. However, no assignment is made, +because |explicit=first_octant|. The author apologizes for using +such trickery here; it is really hard to do redundant computations +just for the sake of purity. + +@= +if q=cur_spec then + begin dest_x:=first_x; dest_y:=first_y; + end +else begin dest_x:=x_coord(q); dest_y:=y_coord(q); + end; +del1:=right_x(p)-x_coord(p); del2:=left_x(q)-right_x(p); +del3:=dest_x-left_x(q); +@; +if del=0 then constant_x:=true +else begin constant_x:=false; + if del<0 then @; + t:=crossing_point(del1,del2,del3); + if t; + end + +@ If |del1=del2=del3=0|, it's impossible to obey the title of this +section. We just set |del=0| in that case. +@^inner loop@> + +@= +if del1<>0 then del:=del1 +else if del2<>0 then del:=del2 +else del:=del3; +if del<>0 then + begin dmax:=abs(del1); + if abs(del2)>dmax then dmax:=abs(del2); + if abs(del3)>dmax then dmax:=abs(del3); + while dmax= +begin negate(x_coord(p)); negate(right_x(p)); +negate(left_x(q));@/ +negate(del1); negate(del2); negate(del3);@/ +negate(dest_x); +right_type(p):=first_octant+negate_x; +end + +@ When a cubic is split at a |fraction| value |t|, we obtain two cubics +whose B\'ezier control points are obtained by a generalization of the +bisection process: The formula +`$z_k^{(j+1)}={1\over2}(z_k^{(j)}+z\k^{(j)})$' becomes +`$z_k^{(j+1)}=t[z_k^{(j)},z\k^{(j)}]$'. + +It is convenient to define a \.{WEB} macro |t_of_the_way| such that +|t_of_the_way(a)(b)| expands to |a-(a-b)*t|, i.e., to |t[a,b]|. + +If |0<=t<=1|, the quantity |t[a,b]| is always between |a| and~|b|, even in +the presence of rounding errors. Our subroutines +also obey the identity |t[a,b]+t[b,a]=a+b|. + +@d t_of_the_way_end(#)==#,t@=)@> +@d t_of_the_way(#)==#-take_fraction@=(@>#-t_of_the_way_end + +@= +procedure split_cubic(@!p:pointer;@!t:fraction; + @!xq,@!yq:scaled); {splits the cubic after |p|} +var @!v:scaled; {an intermediate value} +@!q,@!r:pointer; {for list manipulation} +begin q:=link(p); r:=get_node(knot_node_size); link(p):=r; link(r):=q;@/ +left_type(r):=left_type(q); right_type(r):=right_type(p);@# +v:=t_of_the_way(right_x(p))(left_x(q)); +right_x(p):=t_of_the_way(x_coord(p))(right_x(p)); +left_x(q):=t_of_the_way(left_x(q))(xq); +left_x(r):=t_of_the_way(right_x(p))(v); +right_x(r):=t_of_the_way(v)(left_x(q)); +x_coord(r):=t_of_the_way(left_x(r))(right_x(r));@# +v:=t_of_the_way(right_y(p))(left_y(q)); +right_y(p):=t_of_the_way(y_coord(p))(right_y(p)); +left_y(q):=t_of_the_way(left_y(q))(yq); +left_y(r):=t_of_the_way(right_y(p))(v); +right_y(r):=t_of_the_way(v)(left_y(q)); +y_coord(r):=t_of_the_way(left_y(r))(right_y(r)); +end; + +@ Since $x'(t)$ is a quadratic equation, it can cross through zero +at~most twice. When it does cross zero, we make doubly sure that the +derivative is really zero at the splitting point, in case rounding errors +have caused the split cubic to have an apparently nonzero derivative. +We also make sure that the split cubic is monotonic. + +@= +begin split_cubic(p,t,dest_x,dest_y); r:=link(p); +if right_type(r)>negate_x then right_type(r):=first_octant +else right_type(r):=first_octant+negate_x; +if x_coord(r)x_coord(r) then right_x(p):=x_coord(r); + {we always have |x_coord(p)<=right_x(p)|} +negate(x_coord(r)); right_x(r):=x_coord(r); +negate(left_x(q)); negate(dest_x);@/ +del2:=t_of_the_way(del2)(del3); + {now |0,del2,del3| represent $x'$ on the remaining interval} +if del2>0 then del2:=0; +t:=crossing_point(0,-del2,-del3); +if t +else begin if x_coord(r)>dest_x then + begin x_coord(r):=dest_x; left_x(r):=-x_coord(r); right_x(r):=x_coord(r); + end; + if left_x(q)>dest_x then left_x(q):=dest_x + else if left_x(q)= +begin split_cubic(r,t,dest_x,dest_y); s:=link(r); +if x_coord(s)x_coord(s) then left_x(q):=-x_coord(s) +else negate(left_x(q)); +negate(x_coord(s)); right_x(s):=x_coord(s); +end + +@ The process of subdivision with respect to $y'$ is like that with respect +to~$x'$, with the slight additional complication that two or three cubics +might now appear between |p| and~|q|. + +@= +pp:=p; +repeat qq:=link(pp); +abnegate(x_coord(qq),y_coord(qq),right_type(qq),right_type(pp)); +dest_x:=cur_x; dest_y:=cur_y;@/ +del1:=right_y(pp)-y_coord(pp); del2:=left_y(qq)-right_y(pp); +del3:=dest_y-left_y(qq); +@; +if del<>0 then {they weren't all zero} + begin if del<0 then @; + t:=crossing_point(del1,del2,del3); + if t; + end +else @; +pp:=qq; +until pp=q; +if constant_x then @ + +@ @= +begin negate(y_coord(pp)); negate(right_y(pp)); +negate(left_y(qq));@/ +negate(del1); negate(del2); negate(del3);@/ +negate(dest_y); +right_type(pp):=right_type(pp)+negate_y; +end + +@ @= +begin split_cubic(pp,t,dest_x,dest_y); r:=link(pp); +if right_type(r)>negate_y then right_type(r):=right_type(r)-negate_y +else right_type(r):=right_type(r)+negate_y; +if y_coord(r)y_coord(r) then right_y(pp):=y_coord(r); + {we always have |y_coord(pp)<=right_y(pp)|} +negate(y_coord(r)); right_y(r):=y_coord(r); +negate(left_y(qq)); negate(dest_y);@/ +if x_coord(r)dest_x then x_coord(r):=dest_x; +if left_x(r)>x_coord(r) then + begin left_x(r):=x_coord(r); + if right_x(pp)>x_coord(r) then right_x(pp):=x_coord(r); + end; +if right_x(r)0 then del2:=0; +t:=crossing_point(0,-del2,-del3); +if t +else begin if y_coord(r)>dest_y then + begin y_coord(r):=dest_y; left_y(r):=-y_coord(r); right_y(r):=y_coord(r); + end; + if left_y(qq)>dest_y then left_y(qq):=dest_y + else if left_y(qq)= +begin split_cubic(r,t,dest_x,dest_y); s:=link(r);@/ +if y_coord(s)y_coord(s) then left_y(qq):=-y_coord(s) +else negate(left_y(qq)); +negate(y_coord(s)); right_y(s):=y_coord(s); +if x_coord(s)dest_x then x_coord(s):=dest_x; +if left_x(s)>x_coord(s) then + begin left_x(s):=x_coord(s); + if right_x(r)>x_coord(s) then right_x(r):=x_coord(s); + end; +if right_x(s)= +if constant_x then {|p=pp|, |q=qq|, and the cubic is dead} + begin if q<>p then + begin remove_cubic(p); {remove the dead cycle and recycle node |q|} + if cur_spec<>q then goto continue + else begin cur_spec:=p; return; + end; {the final cubic was dead and is gone} + end; + end +else if not odd(right_type(pp)) then {the $x$ coordinates were negated} + @ + +@ A similar correction to octant codes deserves to be made when |x| is +constant and |y| is decreasing. + +@= +begin pp:=p; +repeat qq:=link(pp); +if right_type(pp)>negate_y then {the $y$ coordinates were negated} + begin right_type(pp):=right_type(pp)+negate_x; + negate(x_coord(pp)); negate(right_x(pp)); negate(left_x(qq)); + end; +pp:=qq; +until pp=q; +end + +@ Finally, the process of subdividing to make $x'\G y'$ is like the other +two subdivisions, with a few new twists. We skew the coordinates at this time. + +@= +procedure octant_subdivide; +var @!p,@!q,@!r,@!s:pointer; {for traversing the lists} +@!del1,@!del2,@!del3,@!del,@!dmax:scaled; {proportional to the control + points of a quadratic derived from a cubic} +@!t:fraction; {where a quadratic crosses zero} +@!dest_x,@!dest_y:scaled; {final values of |x| and |y| in the current cubic} +begin p:=cur_spec; +repeat q:=link(p);@/ +x_coord(p):=x_coord(p)-y_coord(p); +right_x(p):=right_x(p)-right_y(p); +left_x(q):=left_x(q)-left_y(q);@/ +@; +p:=q; +until p=cur_spec; +end; + +@ @= +@; +@; +if del<>0 then {they weren't all zero} + begin if del<0 then @; + t:=crossing_point(del1,del2,del3); + if t; + end + +@ @= +if q=cur_spec then + begin unskew(x_coord(q),y_coord(q),right_type(q)); + skew(cur_x,cur_y,right_type(p)); dest_x:=cur_x; dest_y:=cur_y; + end +else begin abnegate(x_coord(q),y_coord(q),right_type(q),right_type(p)); + dest_x:=cur_x-cur_y; dest_y:=cur_y; + end; +del1:=right_x(p)-x_coord(p); del2:=left_x(q)-right_x(p); +del3:=dest_x-left_x(q) + +@ The swapping here doesn't simply interchange |x| and |y| values, +because the coordinates are skewed. It turns out that this is easier +than ordinary swapping, because it can be done in two assignment statements +rather than three. + +@ @= +begin y_coord(p):=x_coord(p)+y_coord(p); negate(x_coord(p));@/ +right_y(p):=right_x(p)+right_y(p); negate(right_x(p));@/ +left_y(q):=left_x(q)+left_y(q); negate(left_x(q));@/ +negate(del1); negate(del2); negate(del3);@/ +dest_y:=dest_x+dest_y; negate(dest_x);@/ +right_type(p):=right_type(p)+switch_x_and_y; +end + +@ A somewhat tedious case analysis is carried out here to make sure that +nasty rounding errors don't destroy our assumptions of monotonicity. + +@= +begin split_cubic(p,t,dest_x,dest_y); r:=link(p); +if right_type(r)>switch_x_and_y then right_type(r):=right_type(r)-switch_x_and_y +else right_type(r):=right_type(r)+switch_x_and_y; +if y_coord(r)dest_y then y_coord(r):=dest_y; +if x_coord(p)+y_coord(r)>dest_x+dest_y then + y_coord(r):=dest_x+dest_y-x_coord(p); +if left_y(r)>y_coord(r) then + begin left_y(r):=y_coord(r); + if right_y(p)>y_coord(r) then right_y(p):=y_coord(r); + end; +if right_y(r)dest_x+dest_y then + x_coord(r):=dest_x+dest_y-y_coord(r); +left_x(r):=x_coord(r); +if right_x(p)>x_coord(r) then right_x(p):=x_coord(r); + {we always have |x_coord(p)<=right_x(p)|} +y_coord(r):=y_coord(r)+x_coord(r); right_y(r):=right_y(r)+x_coord(r);@/ +negate(x_coord(r)); right_x(r):=x_coord(r);@/ +left_y(q):=left_y(q)+left_x(q); negate(left_x(q));@/ +dest_y:=dest_y+dest_x; negate(dest_x); +if right_y(r)0 then del2:=0; +t:=crossing_point(0,-del2,-del3); +if t +else begin if x_coord(r)>dest_x then + begin x_coord(r):=dest_x; left_x(r):=-x_coord(r); right_x(r):=x_coord(r); + end; + if left_x(q)>dest_x then left_x(q):=dest_x + else if left_x(q)= +begin split_cubic(r,t,dest_x,dest_y); s:=link(r);@/ +if y_coord(s)dest_y then y_coord(s):=dest_y; +if x_coord(r)+y_coord(s)>dest_x+dest_y then + y_coord(s):=dest_x+dest_y-x_coord(r); +if left_y(s)>y_coord(s) then + begin left_y(s):=y_coord(s); + if right_y(r)>y_coord(s) then right_y(r):=y_coord(s); + end; +if right_y(s)dest_x+dest_y then x_coord(s):=dest_x+dest_y-y_coord(s) +else begin if x_coord(s)x_coord(s) then + begin left_y(q):=left_y(q)+x_coord(s); left_x(q):=-x_coord(s);@+end +else begin left_y(q):=left_y(q)+left_x(q); negate(left_x(q));@+end; +y_coord(s):=y_coord(s)+x_coord(s); right_y(s):=right_y(s)+x_coord(s);@/ +negate(x_coord(s)); right_x(s):=x_coord(s);@/ +if right_y(s)= +procedure make_safe; +var @!k:0..max_wiggle; {runs through the list of inputs} +@!all_safe:boolean; {does everything look OK so far?} +@!next_a:scaled; {|after[k]| before it might have changed} +@!delta_a,@!delta_b:scaled; {|after[k+1]-after[k]| and |before[k+1]-before[k]|} +begin before[cur_rounding_ptr]:=before[0]; {wrap around} +node_to_round[cur_rounding_ptr]:=node_to_round[0]; +repeat after[cur_rounding_ptr]:=after[0]; all_safe:=true; next_a:=after[0]; +for k:=0 to cur_rounding_ptr-1 do + begin delta_b:=before[k+1]-before[k]; + if delta_b>=0 then delta_a:=after[k+1]-next_a + else delta_a:=next_a-after[k+1]; + next_a:=after[k+1]; + if (delta_a<0)or(delta_a>abs(delta_b+delta_b)) then + begin all_safe:=false; after[k]:=before[k]; + if k=cur_rounding_ptr-1 then after[0]:=before[0] + else after[k+1]:=before[k+1]; + end; + end; +until all_safe; +end; + +@ The global arrays used by |make_safe| are accompanied by an array of +pointers into the current knot list. + +@= +@!before,@!after:array[0..max_wiggle] of scaled; {data for |make_safe|} +@!node_to_round:array[0..max_wiggle] of pointer; {reference back to the path} +@!cur_rounding_ptr:0..max_wiggle; {how many are being used} +@!max_rounding_ptr:0..max_wiggle; {how many have been used} + +@ @= +max_rounding_ptr:=0; + +@ New entries go into the tables via the |before_and_after| routine: + +@= +procedure before_and_after(@!b,@!a:scaled;@!p:pointer); +begin if cur_rounding_ptr=max_rounding_ptr then + if max_rounding_ptr +after[cur_rounding_ptr]:=a; before[cur_rounding_ptr]:=b; +node_to_round[cur_rounding_ptr]:=p; incr(cur_rounding_ptr); +end; + +@ A global variable called |cur_gran| is used instead of |internal[ +granularity]|, because we want to work with a number that's guaranteed to +be positive. + +@= +@!cur_gran:scaled; {the current granularity (which normally is |unity|)} + +@ The |good_val| function computes a number |a| that's as close as +possible to~|b|, with the property that |a+o| is a multiple of +|cur_gran|. + +If we assume that |cur_gran| is even (since it will in fact be a multiple +of |unity| in all reasonable applications), we have the identity +|good_val(-b-1,-o)=-good_val(b,o)|. + +@= +function good_val(@!b,@!o:scaled):scaled; +var @!a:scaled; {accumulator} +begin a:=b+o; +if a>=0 then a:=a-(a mod cur_gran)-o +else a:=a+((-(a+1)) mod cur_gran)-cur_gran+1-o; +if b-a +modulo |cur_gran| is relevant in our applications, so the result turns +out to be essentially symmetric in |u| and~|v|.) + +@= +function compromise(@!u,@!v:scaled):scaled; +begin compromise:=half(good_val(u+u,-u-v)); +end; + +@ Here, then, is the procedure that rounds $x$ coordinates as described; +it does the same for $y$ coordinates too, independently. + +@= +procedure xy_round; +var @!p,@!q:pointer; {list manipulation registers} +@!b,@!a:scaled; {before and after values} +@!pen_edge:scaled; {offset that governs rounding} +@!alpha:fraction; {coefficient of linear transformation} +begin cur_gran:=abs(internal[granularity]); +if cur_gran=0 then cur_gran:=unity; +p:=cur_spec; cur_rounding_ptr:=0; +repeat q:=link(p); +@; +p:=q; +until p=cur_spec; +if cur_rounding_ptr>0 then @; +p:=cur_spec; cur_rounding_ptr:=0; +repeat q:=link(p); +@; +p:=q; +until p=cur_spec; +if cur_rounding_ptr>0 then @; +end; + +@ When |x| has been negated, the |octant| codes are even. We allow +for an error of up to .01 pixel (i.e., 655 |scaled| units) in the +derivative calculations at transition nodes. + +@= +if odd(right_type(p))<>odd(right_type(q)) then + begin if odd(right_type(q)) then b:=x_coord(q)@+else b:=-x_coord(q); + if (abs(x_coord(q)-right_x(q))<655)or@| + (abs(x_coord(q)+left_x(q))<655) then + @ + else a:=b; + if abs(a)>max_allowed then + if a>0 then a:=max_allowed@+else a:=-max_allowed; + before_and_after(b,a,q); + end + +@ When we study the data representation for pens, we'll learn that the +|x|~coordinate of the current pen's west edge is +$$\hbox{|y_coord(link(cur_pen+seventh_octant))|},$$ +and that there are similar ways to address other important offsets. + +@d north_edge(#)==y_coord(link(#+fourth_octant)) +@d south_edge(#)==y_coord(link(#+first_octant)) +@d east_edge(#)==y_coord(link(#+second_octant)) +@d west_edge(#)==y_coord(link(#+seventh_octant)) + +@= +begin if cur_pen=null_pen then pen_edge:=0 +else if cur_path_type=double_path_code then + pen_edge:=compromise(east_edge(cur_pen),west_edge(cur_pen)) +else if odd(right_type(q)) then pen_edge:=west_edge(cur_pen) +else pen_edge:=east_edge(cur_pen); +a:=good_val(b,pen_edge); +end + +@ The monotone transformation computed here with fixed-point arithmetic is +guaranteed to take consecutive |before| values $(b,b')$ into consecutive +|after| values $(a,a')$, even in the presence of rounding errors, +as long as $\vert b-b'\vert<2^{28}$. + +@= +begin make_safe; +repeat decr(cur_rounding_ptr); +if (after[cur_rounding_ptr]<>before[cur_rounding_ptr])or@| + (after[cur_rounding_ptr+1]<>before[cur_rounding_ptr+1]) then + begin p:=node_to_round[cur_rounding_ptr]; + if odd(right_type(p)) then + begin b:=before[cur_rounding_ptr]; a:=after[cur_rounding_ptr]; + end + else begin b:=-before[cur_rounding_ptr]; a:=-after[cur_rounding_ptr]; + end; + if before[cur_rounding_ptr]=before[cur_rounding_ptr+1] then + alpha:=fraction_one + else alpha:=make_fraction(after[cur_rounding_ptr+1]-after[cur_rounding_ptr],@| + before[cur_rounding_ptr+1]-before[cur_rounding_ptr]); + repeat x_coord(p):=take_fraction(alpha,x_coord(p)-b)+a; + right_x(p):=take_fraction(alpha,right_x(p)-b)+a; + p:=link(p); left_x(p):=take_fraction(alpha,left_x(p)-b)+a; + until p=node_to_round[cur_rounding_ptr+1]; + end; +until cur_rounding_ptr=0; +end + +@ When |y| has been negated, the |octant| codes are |>negate_y|. Otherwise +these routines are essentially identical to the routines for |x| coordinates +that we have just seen. + +@= +if (right_type(p)>negate_y)<>(right_type(q)>negate_y) then + begin if right_type(q)<=negate_y then b:=y_coord(q)@+else b:=-y_coord(q); + if (abs(y_coord(q)-right_y(q))<655)or@| + (abs(y_coord(q)+left_y(q))<655) then + @ + else a:=b; + if abs(a)>max_allowed then + if a>0 then a:=max_allowed@+else a:=-max_allowed; + before_and_after(b,a,q); + end + +@ @= +begin if cur_pen=null_pen then pen_edge:=0 +else if cur_path_type=double_path_code then + pen_edge:=compromise(north_edge(cur_pen),south_edge(cur_pen)) +else if right_type(q)<=negate_y then pen_edge:=south_edge(cur_pen) +else pen_edge:=north_edge(cur_pen); +a:=good_val(b,pen_edge); +end + +@ @= +begin make_safe; +repeat decr(cur_rounding_ptr); +if (after[cur_rounding_ptr]<>before[cur_rounding_ptr])or@| + (after[cur_rounding_ptr+1]<>before[cur_rounding_ptr+1]) then + begin p:=node_to_round[cur_rounding_ptr]; + if right_type(p)<=negate_y then + begin b:=before[cur_rounding_ptr]; a:=after[cur_rounding_ptr]; + end + else begin b:=-before[cur_rounding_ptr]; a:=-after[cur_rounding_ptr]; + end; + if before[cur_rounding_ptr]=before[cur_rounding_ptr+1] then + alpha:=fraction_one + else alpha:=make_fraction(after[cur_rounding_ptr+1]-after[cur_rounding_ptr],@| + before[cur_rounding_ptr+1]-before[cur_rounding_ptr]); + repeat y_coord(p):=take_fraction(alpha,y_coord(p)-b)+a; + right_y(p):=take_fraction(alpha,right_y(p)-b)+a; + p:=link(p); left_y(p):=take_fraction(alpha,left_y(p)-b)+a; + until p=node_to_round[cur_rounding_ptr+1]; + end; +until cur_rounding_ptr=0; +end + +@ Rounding at diagonal tangents takes place after the subdivision into +octants is complete, hence after the coordinates have been skewed. +The details are somewhat tricky, because we want to round to points +whose skewed coordinates are halfway between integer multiples of +the granularity. Furthermore, both coordinates change when they are +rounded; this means we need a generalization of the |make_safe| routine, +ensuring safety in both |x| and |y|. + +In spite of these extra complications, we can take comfort in the fact +that the basic structure of the routine is the same as before. + +@= +procedure diag_round; +var @!p,@!q,@!pp:pointer; {list manipulation registers} +@!b,@!a,@!bb,@!aa,@!d,@!c,@!dd,@!cc:scaled; {before and after values} +@!pen_edge:scaled; {offset that governs rounding} +@!alpha,@!beta:fraction; {coefficients of linear transformation} +@!next_a:scaled; {|after[k]| before it might have changed} +@!all_safe:boolean; {does everything look OK so far?} +@!k:0..max_wiggle; {runs through before-and-after values} +@!first_x,@!first_y:scaled; {coordinates before rounding} +begin p:=cur_spec; cur_rounding_ptr:=0; +repeat q:=link(p); +@; +p:=q; +until p=cur_spec; +if cur_rounding_ptr>0 then @; +end; + +@ We negate the skewed |x| coordinates in the before-and-after table when +the octant code is greater than |switch_x_and_y|. + +@= +if right_type(p)<>right_type(q) then + begin if right_type(q)>switch_x_and_y then b:=-x_coord(q) + else b:=x_coord(q); + if abs(right_type(q)-right_type(p))=switch_x_and_y then + if (abs(x_coord(q)-right_x(q))<655)or(abs(x_coord(q)+left_x(q))<655) then + @ + else a:=b + else a:=b; + before_and_after(b,a,q); + end + +@ In octants whose code number is even, $x$~has been +negated; we want to round ambiguous cases downward instead of upward, +so that the rounding will be consistent with octants whose code +number is odd. This downward bias can be achieved by +subtracting~1 from the first argument of |good_val|. + +@d diag_offset(#)==x_coord(knil(link(cur_pen+#))) + +@= +begin if cur_pen=null_pen then pen_edge:=0 +else if cur_path_type=double_path_code then @ +else if right_type(q)<=switch_x_and_y then pen_edge:=diag_offset(right_type(q)) +else pen_edge:=-diag_offset(right_type(q)); +if odd(right_type(q)) then a:=good_val(b,pen_edge+half(cur_gran)) +else a:=good_val(b-1,pen_edge+half(cur_gran)); +end + +@ (It seems a shame to compute these compromise offsets repeatedly. The +author would have stored them directly in the pen data structure, if the +granularity had been constant.) + +@= +case right_type(q) of +first_octant,second_octant:pen_edge:=compromise(diag_offset(first_octant),@| + -diag_offset(fifth_octant)); +fifth_octant,sixth_octant:pen_edge:=-compromise(diag_offset(first_octant),@| + -diag_offset(fifth_octant)); +third_octant,fourth_octant:pen_edge:=compromise(diag_offset(fourth_octant),@| + -diag_offset(eighth_octant)); +seventh_octant,eighth_octant:pen_edge:=-compromise(diag_offset(fourth_octant),@| + -diag_offset(eighth_octant)); +end {there are no other cases} + +@ @= +begin p:=node_to_round[0]; first_x:=x_coord(p); first_y:=y_coord(p); +@; +for k:=0 to cur_rounding_ptr-1 do + begin a:=after[k]; b:=before[k]; + aa:=after[k+1]; bb:=before[k+1]; + if (a<>b)or(aa<>bb) then + begin p:=node_to_round[k]; pp:=node_to_round[k+1]; + @; + if b=bb then alpha:=fraction_one + else alpha:=make_fraction(aa-a,bb-b); + if d=dd then beta:=fraction_one + else beta:=make_fraction(cc-c,dd-d); + repeat x_coord(p):=take_fraction(alpha,x_coord(p)-b)+a; + y_coord(p):=take_fraction(beta,y_coord(p)-d)+c; + right_x(p):=take_fraction(alpha,right_x(p)-b)+a; + right_y(p):=take_fraction(beta,right_y(p)-d)+c; + p:=link(p); left_x(p):=take_fraction(alpha,left_x(p)-b)+a; + left_y(p):=take_fraction(beta,left_y(p)-d)+c; + until p=pp; + end; + end; +end + +@ In node |p|, the coordinates |(b,d)| will be rounded to |(a,c)|; +in node |pp|, the coordinates |(bb,dd)| will be rounded to |(aa,cc)|. +(We transform the values from node |pp| so that they agree with the +conventions of node |p|.) + +If |aa<>bb|, we know that |abs(right_type(p)-right_type(pp))=switch_x_and_y|. + +@= +if aa=bb then + begin if pp=node_to_round[0] then + unskew(first_x,first_y,right_type(pp)) + else unskew(x_coord(pp),y_coord(pp),right_type(pp)); + skew(cur_x,cur_y,right_type(p)); + bb:=cur_x; aa:=bb; dd:=cur_y; cc:=dd; + if right_type(p)>switch_x_and_y then + begin b:=-b; a:=-a; + end; + end +else begin if right_type(p)>switch_x_and_y then + begin bb:=-bb; aa:=-aa; b:=-b; a:=-a; + end; + if pp=node_to_round[0] then dd:=first_y-bb@+else dd:=y_coord(pp)-bb; + if odd(aa-bb) then + if right_type(p)>switch_x_and_y then cc:=dd-half(aa-bb+1) + else cc:=dd-half(aa-bb-1) + else cc:=dd-half(aa-bb); + end; +d:=y_coord(p); +if odd(a-b) then + if right_type(p)>switch_x_and_y then c:=d-half(a-b-1) + else c:=d-half(a-b+1) +else c:=d-half(a-b) + +@ @= +before[cur_rounding_ptr]:=before[0]; {cf.~|make_safe|} +node_to_round[cur_rounding_ptr]:=node_to_round[0]; +repeat after[cur_rounding_ptr]:=after[0]; all_safe:=true; next_a:=after[0]; +for k:=0 to cur_rounding_ptr-1 do + begin a:=next_a; b:=before[k]; next_a:=after[k+1]; + aa:=next_a; bb:=before[k+1]; + if (a<>b)or(aa<>bb) then + begin p:=node_to_round[k]; pp:=node_to_round[k+1]; + @; + if (aa2*(bb-b))or(cc-c>2*(dd-d)) then + begin all_safe:=false; after[k]:=before[k]; + if k=cur_rounding_ptr-1 then after[0]:=before[0] + else after[k+1]:=before[k+1]; + end; + end; + end; +until all_safe + +@ Here we get rid of ``dead'' cubics, i.e., polynomials that don't move at +all when |t|~changes, since the subdivision process might have introduced +such things. If the cycle reduces to a single point, however, we are left +with a single dead cubic that will not be removed until later. + +@= +p:=cur_spec; +repeat continue: q:=link(p); +if p<>q then + begin if x_coord(p)=right_x(p) then + if y_coord(p)=right_y(p) then + if x_coord(p)=left_x(q) then + if y_coord(p)=left_y(q) then + begin unskew(x_coord(q),y_coord(q),right_type(q)); + skew(cur_x,cur_y,right_type(p)); + if x_coord(p)=cur_x then if y_coord(p)=cur_y then + begin remove_cubic(p); {remove the cubic following |p|} + if q<>cur_spec then goto continue; + cur_spec:=p; q:=p; + end; + end; + end; +p:=q; +until p=cur_spec; + +@ Finally we come to the last steps of |make_spec|, when boundary nodes +are inserted between cubics that move in different octants. The main +complication remaining arises from consecutive cubics whose octants +are not adjacent; we should insert more than one octant boundary +at such sharp turns, so that the envelope-forming routine will work. + +For this purpose, conversion tables between numeric and Gray codes for +octants are desirable. + +@= +@!octant_number:array[first_octant..sixth_octant] of 1..8; +@!octant_code:array[1..8] of first_octant..sixth_octant; + +@ @= +octant_code[1]:=first_octant; +octant_code[2]:=second_octant; +octant_code[3]:=third_octant; +octant_code[4]:=fourth_octant; +octant_code[5]:=fifth_octant; +octant_code[6]:=sixth_octant; +octant_code[7]:=seventh_octant; +octant_code[8]:=eighth_octant; +for k:=1 to 8 do octant_number[octant_code[k]]:=k; + +@ The main loop for boundary insertion deals with three consecutive +nodes |p,q,r|. + +@= +turning_number:=0; +p:=cur_spec; q:=link(p); +repeat r:=link(q); +if (right_type(p)<>right_type(q))or(q=r) then + @; +p:=q; q:=r; +until p=cur_spec; + +@ The |new_boundary| subroutine comes in handy at this point. It inserts +a new boundary node just after a given node |p|, using a given octant code +to transform the new node's coordinates. The ``transition'' fields are +not computed here. + +@= +procedure new_boundary(@!p:pointer;@!octant:small_number); +var @!q,@!r:pointer; {for list manipulation} +begin q:=link(p); {we assume that |right_type(q)<>endpoint|} +r:=get_node(knot_node_size); link(r):=q; link(p):=r; +left_type(r):=left_type(q); {but possibly |left_type(q)=endpoint|} +left_x(r):=left_x(q); left_y(r):=left_y(q); +right_type(r):=endpoint; left_type(q):=endpoint; +right_octant(r):=octant; left_octant(q):=right_type(q); +unskew(x_coord(q),y_coord(q),right_type(q)); +skew(cur_x,cur_y,octant); x_coord(r):=cur_x; y_coord(r):=cur_y; +end; + +@ The case |q=r| occurs if and only if |p=q=r=cur_spec|, when we want to turn +$360^\circ$ in eight steps and then remove a solitary dead cubic. +The program below happens to work in that case, but the reader isn't +expected to understand why. + +@= +begin new_boundary(p,right_type(p)); s:=link(p); +o1:=octant_number[right_type(p)]; o2:=octant_number[right_type(q)]; +case o2-o1 of +1,-7,7,-1: goto done; +2,-6: clockwise:=false; +3,-5,4,-4,5,-3: @; +6,-2: clockwise:=true; +0:clockwise:=rev_turns; +end; {there are no other cases} +@; +done: if q=r then + begin q:=link(q); r:=q; p:=s; link(s):=q; left_octant(q):=right_octant(q); + left_type(q):=endpoint; free_node(cur_spec,knot_node_size); cur_spec:=q; + end; +@; +end + +@ @= +@!o1,@!o2:small_number; {octant numbers} +@!clockwise:boolean; {should we turn clockwise?} +@!dx1,@!dy1,@!dx2,@!dy2:integer; {directions of travel at a cusp} +@!dmax,@!del:integer; {temporary registers} + +@ A tricky question arises when a path jumps four octants. We want the +direction of turning to be counterclockwise if the curve has changed +direction by $180^\circ$, or by something so close to $180^\circ$ that +the difference is probably due to rounding errors; otherwise we want to +turn through an angle of less than $180^\circ$. This decision needs to +be made even when a curve seems to have jumped only three octants, since +a curve may approach direction $(-1,0)$ from the fourth octant, then +it might leave from direction $(+1,0)$ into the first. + +The following code solves the problem by analyzing the incoming +direction |(dx1,dy1)| and the outgoing direction |(dx2,dy2)|. + +@= +begin @; +unskew(dx1,dy1,right_type(p)); del:=pyth_add(cur_x,cur_y);@/ +dx1:=make_fraction(cur_x,del); dy1:=make_fraction(cur_y,del); + {$\cos\theta_1$ and $\sin\theta_1$} +unskew(dx2,dy2,right_type(q)); del:=pyth_add(cur_x,cur_y);@/ +dx2:=make_fraction(cur_x,del); dy2:=make_fraction(cur_y,del); + {$\cos\theta_2$ and $\sin\theta_2$} +del:=take_fraction(dx1,dy2)-take_fraction(dx2,dy1); {$\sin(\theta_2-\theta_1)$} +if del>4684844 then clockwise:=false +else if del<-4684844 then clockwise:=true + {$2^{28}\cdot\sin 1^\circ\approx4684844.68$} +else clockwise:=rev_turns; +end + +@ Actually the turnarounds just computed will be clockwise, +not counterclockwise, if +the global variable |rev_turns| is |true|; it is usually |false|. + +@= +@!rev_turns:boolean; {should we make U-turns in the English manner?} + +@ @= +rev_turns:=false; + +@ @= +dx1:=x_coord(s)-left_x(s); dy1:=y_coord(s)-left_y(s); +if dx1=0 then if dy1=0 then + begin dx1:=x_coord(s)-right_x(p); dy1:=y_coord(s)-right_y(p); + if dx1=0 then if dy1=0 then + begin dx1:=x_coord(s)-x_coord(p); dy1:=y_coord(s)-y_coord(p); + end; {and they {\sl can't} both be zero} + end; +dmax:=abs(dx1);@+if abs(dy1)>dmax then dmax:=abs(dy1); +while dmaxdmax then dmax:=abs(dy2); +while dmax= +loop@+ begin if clockwise then + if o1=1 then o1:=8@+else decr(o1) + else if o1=8 then o1:=1@+else incr(o1); + if o1=o2 then goto done; + new_boundary(s,octant_code[o1]); + s:=link(s); left_octant(s):=right_octant(s); + end + +@ Now it remains to insert the redundant +transition information into the |left_transition| +and |right_transition| fields between adjacent octants, in the octant +boundary nodes that have just been inserted between |link(p)| and~|q|. +The turning number is easily computed from these transitions. + +@= +p:=link(p); +repeat s:=link(p); +o1:=octant_number[right_octant(p)]; o2:=octant_number[left_octant(s)]; +if abs(o1-o2)=1 then + begin if o2= +@!y_corr,@!xy_corr,@!z_corr:array[first_octant..sixth_octant] of 0..1; +@!x_corr:array[first_octant..sixth_octant] of -1..1; + +@ Here |xy_corr| is 1 if and only if the $x$ component of a skewed coordinate +is to be decreased by an infinitesimal amount; |y_corr| is similar, but for +the $y$ components. The other tables are set up so that the condition +$$(x+y+|half_unit|)\bmod|unity|\G(y+|half_unit|)\bmod|unity|$$ +is properly perturbed to the condition +$$(x+y+|half_unit|-|x_corr|-|y_corr|)\bmod|unity|\G + (y+|half_unit|-|y_corr|)\bmod|unity|+|z_corr|.$$ + +@= +x_corr[first_octant]:=0; y_corr[first_octant]:=0; +xy_corr[first_octant]:=0;@/ +x_corr[second_octant]:=0; y_corr[second_octant]:=0; +xy_corr[second_octant]:=1;@/ +x_corr[third_octant]:=-1; y_corr[third_octant]:=1; +xy_corr[third_octant]:=0;@/ +x_corr[fourth_octant]:=1; y_corr[fourth_octant]:=0; +xy_corr[fourth_octant]:=1;@/ +x_corr[fifth_octant]:=0; y_corr[fifth_octant]:=1; +xy_corr[fifth_octant]:=1;@/ +x_corr[sixth_octant]:=0; y_corr[sixth_octant]:=1; +xy_corr[sixth_octant]:=0;@/ +x_corr[seventh_octant]:=1; y_corr[seventh_octant]:=0; +xy_corr[seventh_octant]:=1;@/ +x_corr[eighth_octant]:=-1; y_corr[eighth_octant]:=1; +xy_corr[eighth_octant]:=0;@/ +for k:=1 to 8 do z_corr[k]:=xy_corr[k]-x_corr[k]; + +@ Here's a procedure that handles the details of rounding at the +endpoints: Given skewed coordinates |(x,y)|, it sets |(m1,n1)| +to the corresponding rounded lattice points, taking the current +|octant| into account. Global variable |d1| is also set to 1 if +$(x+y+{1\over2})\bmod1\G(y+{1\over2})\bmod1$. + +@p procedure end_round(@!x,@!y:scaled); +begin y:=y+half_unit-y_corr[octant]; +x:=x+y-x_corr[octant]; +m1:=floor_unscaled(x); n1:=floor_unscaled(y); +if x-unity*m1>=y-unity*n1+z_corr[octant] then d1:=1@+else d1:=0; +end; + +@ The outputs |(m1,n1,d1)| of |end_round| will sometimes be moved +to |(m0,n0,d0)|. + +@= +@!m0,@!n0,@!m1,@!n1:integer; {lattice point coordinates} +@!d0,@!d1:0..1; {displacement corrections} + +@ We're ready now to fill the pixels enclosed by a given cycle spec~|h|; +the knot list that represents the cycle is destroyed in the process. +The edge structure that gets all the resulting data is |cur_edges|, +and the edges are weighted by |cur_wt|. + +@p procedure fill_spec(@!h:pointer); +var @!p,@!q,@!r,@!s:pointer; {for list traversal} +begin if internal[tracing_edges]>0 then begin_edge_tracing; +p:=h; {we assume that |left_type(h)=endpoint|} +repeat octant:=left_octant(p); +@; +if q<>p then + begin @; + @; + move_to_edges(m0,n0,m1,n1); + end; +p:=link(q); +until p=h; +toss_knot_list(h); +if internal[tracing_edges]>0 then end_edge_tracing; +end; + +@ @= +q:=p; +while right_type(q)<>endpoint do q:=link(q) + +@ @= +end_round(x_coord(p),y_coord(p)); m0:=m1; n0:=n1; d0:=d1;@/ +end_round(x_coord(q),y_coord(q)) + +@ Finally we perform the five-step process that was explained at +the very beginning of this part of the program. + +@= +if n1-n0>=move_size then overflow("move table size",move_size); +@:METAFONT capacity exceeded move table size}{\quad move table size@> +move[0]:=d0; move_ptr:=0; r:=p; +repeat s:=link(r);@/ +make_moves(x_coord(r),right_x(r),left_x(s),x_coord(s),@| + y_coord(r)+half_unit,right_y(r)+half_unit,left_y(s)+half_unit, + y_coord(s)+half_unit,@| xy_corr[octant],y_corr[octant]); +r:=s; +until r=q; +move[move_ptr]:=move[move_ptr]-d1; +if internal[smoothing]>0 then smooth_moves(0,move_ptr) + +@* \[23] Polygonal pens. +The next few parts of the program deal with the additional complications +associated with ``envelopes,'' leading up to an algorithm that fills a +contour with respect to a pen whose boundary is a convex polygon. The +mathematics underlying this algorithm is based on simple aspects of the +theory of tracings developed by Leo Guibas, Lyle Ramshaw, and Jorge +Stolfi [``A kinetic framework for computational geometry,'' +{\sl Proc.\ IEEE Symp.\ Foundations of Computer Science\/ \bf24} (1983), +100--111]. +@^Guibas, Leonidas Ioannis@> +@^Ramshaw, Lyle Harold@> +@^Stolfi, Jorge@> + +If the vertices of the polygon are $w_0$, $w_1$, \dots, $w_{n-1}$, $w_n=w_0$, +in counterclockwise order, the convexity condition requires that ``left +turns'' are made at each vertex when a person proceeds from $w_0$ to +$w_1$ to $\cdots$ to~$w_n$. The envelope is obtained if we offset a given +curve $z(t)$ by $w_k$ when that curve is traveling in a direction +$z'(t)$ lying between the directions $w_k-w_{k-1}$ and $w\k-w_k$. +At times~$t$ when the curve direction $z'(t)$ increases past +$w\k-w_k$, we temporarily stop plotting the offset curve and we insert +a straight line from $z(t)+w_k$ to $z(t)+w\k$; notice that this straight +line is tangent to the offset curve. Similarly, when the curve direction +decreases past $w_k-w_{k-1}$, we stop plotting and insert a straight +line from $z(t)+w_k$ to $z(t)+w_{k-1}$; the latter line is actually a +``retrograde'' step, which won't be part of the final envelope under +\MF's assumptions. The result of this construction is a continuous path +that consists of alternating curves and straight line segments. The +segments are usually so short, in practice, that they blend with the +curves; after all, it's possible to represent any digitized path as +a sequence of digitized straight lines. + +The nicest feature of this approach to envelopes is that it blends +perfectly with the octant subdivision process we have already developed. +The envelope travels in the same direction as the curve itself, as we +plot it, and we need merely be careful what offset is being added. +Retrograde motion presents a problem, but we will see that there is +a decent way to handle it. + +@ We shall represent pens by maintaining eight lists of offsets, +one for each octant direction. The offsets at the boundary points +where a curve turns into a new octant will appear in the lists for +both octants. This means that we can restrict consideration to +segments of the original polygon whose directions aim in the first +octant, as we have done in the simpler case when envelopes were not +required. + +An example should help to clarify this situation: Consider the +quadrilateral whose vertices are $w_0=(0,-1)$, $w_1=(3,-1)$, +$w_2=(6,1)$, and $w_3=(1,2)$. A curve that travels in the first octant +will be offset by $w_1$ or $w_2$, unless its slope drops to zero +en route to the eighth octant; in the latter case we should switch to $w_0$ as +we cross the octant boundary. Our list for the first octant will +contain the three offsets $w_0$, $w_1$,~$w_2$. By convention we will +duplicate a boundary offset if the angle between octants doesn't +explicitly appear; in this case there is no explicit line of slope~1 +at the end of the list, so the full list is +$$w_0\;w_1\;w_2\;w_2\;=\;(0,-1)\;(3,-1)\;(6,1)\;(6,1).$$ +With skewed coordinates $(u-v,v)$ instead of $(u,v)$ we obtain the list +$$w_0\;w_1\;w_2\;w_2\;\mapsto\;(1,-1)\;(4,-1)\;(5,1)\;(5,1),$$ +which is what actually appears in the data structure. In the second +octant there's only one offset; we list it twice (with coordinates +interchanged, so as to make the second octant look like the first), +and skew those coordinates, obtaining +$$\tabskip\centering +\halign to\hsize{$\hfil#\;\mapsto\;{}$\tabskip=0pt& + $#\hfil$&\quad in the #\hfil\tabskip\centering\cr +w_2\;w_2&(-5,6)\;(-5,6)\cr +\noalign{\vskip\belowdisplayskip +\vbox{\noindent\strut as the list of transformed and skewed offsets to use +when curves travel in the second octant. Similarly, we will have\strut} +\vskip\abovedisplayskip} +w_2\;w_2&(7,-6)\;(7,-6)&third;\cr +w_2\;w_2\;w_3\;w_3&(-7,1)\;(-7,1)\;(-3,2)\;(-3,2)&fourth;\cr +w_3\;w_3&(1,-2)\;(1,-2)&fifth;\cr +w_3\;w_3\;w_0\;w_0&(-1,1)\;(-1,1)\;(1,0)\;(1,0)&sixth;\cr +w_0\;w_0&(1,0)\;(1,0)&seventh;\cr +w_0\;w_0&(-1,1)\;(-1,1)&eighth.\cr}$$ +Notice that $w_1$ is considered here to be internal to the first octant; +it's not part of the eighth. We could equally well have taken $w_0$ out +of the first octant list and put it into the eighth; then the first octant +list would have been +$$w_1\;w_1\;w_2\;w_2\;\mapsto\;(4,-1)\;(4,-1)\;(5,1)\;(5,1)$$ +and the eighth octant list would have been +$$w_0\;w_0\;w_1\;\mapsto\;(-1,1)\;(-1,1)\;(2,1).$$ + +Actually, there's one more complication: The order of offsets is reversed +in even-numbered octants, because the transformation of coordinates has +reversed counterclockwise and clockwise orientations in those octants. +The offsets in the fourth octant, for example, are really $w_3$, $w_3$, +$w_2$,~$w_2$, not $w_2$, $w_2$, $w_3$,~$w_3$. + +@ In general, the list of offsets for an octant will have the form +$$w_0\;\;w_1\;\;\ldots\;\;w_n\;\;w_{n+1}$$ +(if we renumber the subscripts in each list), where $w_0$ and $w_{n+1}$ +are offsets common to the neighboring lists. We'll often have $w_0=w_1$ +and/or $w_n=w_{n+1}$, but the other $w$'s will be distinct. Curves +that travel between slope~0 and direction $w_2-w_1$ will use offset~$w_1$; +curves that travel between directions $w_k-w_{k-1}$ and $w\k-w_k$ will +use offset~$w_k$, for $1 + +If |p|~points to such a node, and if the +offset list for, say, the fourth octant has entries $w_0$, $w_1$, \dots, +$w_n$,~$w_{n+1}$, then |info(p+fourth_octant)| will equal~$n$, and +|link(p+fourth_octant)| will point to the offset node containing~$w_0$. +Memory location |p+fourth_octant| is said to be the {\sl header\/} of +the pen-offset list for the fourth octant. Since this is an even-numbered +octant, $w_0$ is the offset that goes with the fifth octant, and +$w_{n+1}$ goes with the third. + +The elements of the offset list themselves are doubly linked 3-word nodes, +containing coordinates in their |x_coord| and |y_coord| fields. +The two link fields are called |link| and |knil|; if |w|~points to +the node for~$w_k$, then |link(w)| and |knil(w)| point respectively +to the nodes for $w\k$ and~$w_{k-1}$. If |h| is the list header, +|link(h)| points to the node for~$w_0$ and |knil(link(h))| to the +node for~$w_{n+1}$. + +The tenth word of a pen header node contains the maximum absolute value of +an $x$ or $y$ coordinate among all of the unskewed pen offsets. + +The |link| field of a pen header node should be |null| if and only if +the pen is a single point. + +@d pen_node_size=10 +@d coord_node_size=3 +@d max_offset(#)==mem[#+9].sc + +@ The |print_pen| subroutine illustrates these conventions by +reconstructing the vertices of a polygon from \MF's complicated +internal offset representation. + +@= +procedure print_pen(@!p:pointer;@!s:str_number;@!nuline:boolean); +var @!nothing_printed:boolean; {has there been any action yet?} +@!k:1..8; {octant number} +@!h:pointer; {offset list head} +@!m,@!n:integer; {offset indices} +@!w,@!ww:pointer; {pointers that traverse the offset list} +begin print_diagnostic("Pen polygon",s,nuline); +nothing_printed:=true; print_ln; +for k:=1 to 8 do + begin octant:=octant_code[k]; h:=p+octant; n:=info(h); w:=link(h); + if not odd(k) then w:=knil(w); {in even octants, start at $w_{n+1}$} + for m:=1 to n+1 do + begin if odd(k) then ww:=link(w)@+else ww:=knil(w); + if (x_coord(ww)<>x_coord(w))or(y_coord(ww)<>y_coord(w)) then + @; + w:=ww; + end; + end; +if nothing_printed then + begin w:=link(p+first_octant); print_two(x_coord(w)+y_coord(w),y_coord(w)); + end; +print_nl(" .. cycle"); end_diagnostic(true); +end; + +@ @= +begin if nothing_printed then nothing_printed:=false +else print_nl(" .. "); +print_two_true(x_coord(ww),y_coord(ww)); +end + +@ A null pen polygon, which has just one vertex $(0,0)$, is +predeclared for error recovery. It doesn't need a proper +reference count, because the |toss_pen| procedure below +will never delete it from memory. +@^reference counts@> + +@= +ref_count(null_pen):=null; link(null_pen):=null;@/ +info(null_pen+1):=1; link(null_pen+1):=null_coords; +for k:=null_pen+2 to null_pen+8 do mem[k]:=mem[null_pen+1]; +max_offset(null_pen):=0;@/ +link(null_coords):=null_coords; +knil(null_coords):=null_coords;@/ +x_coord(null_coords):=0; +y_coord(null_coords):=0; + +@ Here's a trivial subroutine that inserts a copy of an offset +on the |link| side of its clone in the doubly linked list. + +@p procedure dup_offset(@!w:pointer); +var @!r:pointer; {the new node} +begin r:=get_node(coord_node_size); +x_coord(r):=x_coord(w); +y_coord(r):=y_coord(w); +link(r):=link(w); knil(link(w)):=r; +knil(r):=w; link(w):=r; +end; + +@ The following algorithm is somewhat more interesting: It converts a +knot list for a cyclic path into a pen polygon, ignoring everything +but the |x_coord|, |y_coord|, and |link| fields. If the given path +vertices do not define a convex polygon, an error message is issued +and the null pen is returned. + +@p function make_pen(@!h:pointer):pointer; +label done,done1,not_found,found; +var @!o,@!oo,@!k:small_number; {octant numbers---old, new, and current} +@!p:pointer; {top-level node for the new pen} +@!q,@!r,@!s,@!w,@!hh:pointer; {for list manipulation} +@!n:integer; {offset counter} +@!dx,@!dy:scaled; {polygon direction} +@!mc:scaled; {the largest coordinate} +begin @; +if mc>=fraction_one-half_unit then goto not_found; +p:=get_node(pen_node_size); q:=hh; max_offset(p):=mc; ref_count(p):=null; +if link(q)<>q then link(p):=null+1; +for k:=1 to 8 do @; +goto found; +not_found:p:=null_pen; @; +found: if internal[tracing_pens]>0 then print_pen(p," (newly created)",true); +make_pen:=p; +end; + +@ @= +if mc>=fraction_one-half_unit then + begin print_err("Pen too large"); +@.Pen too large@> + help2("The cycle you specified has a coordinate of 4095.5 or more.")@/ + ("So I've replaced it by the trivial path `(0,0)..cycle'.");@/ + end +else begin print_err("Pen cycle must be convex"); +@.Pen cycle must be convex@> + help3("The cycle you specified either has consecutive equal points")@/ + ("or turns right or turns through more than 360 degrees.")@/ + ("So I've replaced it by the trivial path `(0,0)..cycle'.");@/ + end; +put_get_error + +@ There should be exactly one node whose octant number is less than its +predecessor in the cycle; that is node~|hh|. + +The loop here will terminate in all cases, but the proof is somewhat tricky: +If there are at least two distinct $y$~coordinates in the cycle, we will have +|o>4| and |o<=4| at different points of the cycle. Otherwise there are +at least two distinct $x$~coordinates, and we will have |o>2| somewhere, +|o<=2| somewhere. + +@= +q:=h; r:=link(q); mc:=abs(x_coord(h)); +if q=r then + begin hh:=h; right_type(h):=0; {this trick is explained below} + if mc; + right_type(q):=octant; oo:=octant_number[octant]; + if o>oo then + begin if hh<>null then goto not_found; {$>360^\circ$} + hh:=q; + end; + o:=oo; + if (q=h)and(hh<>null) then goto done; + q:=r; r:=s; + end; + done:end + + +@ We want the octant for |(-dx,-dy)| to be +exactly opposite the octant for |(dx,dy)|. + +@= +if dx>0 then octant:=first_octant +else if dx=0 then + if dy>0 then octant:=first_octant@+else octant:=first_octant+negate_x +else begin negate(dx); octant:=first_octant+negate_x; + end; +if dy<0 then + begin negate(dy); octant:=octant+negate_y; + end +else if dy=0 then + if octant>first_octant then octant:=first_octant+negate_x+negate_y; +if dx= +begin octant:=octant_code[k]; n:=0; h:=p+octant; +loop@+ begin r:=get_node(coord_node_size); + skew(x_coord(q),y_coord(q),octant); x_coord(r):=cur_x; y_coord(r):=cur_y; + if n=0 then link(h):=r + else @; + w:=r; + if right_type(q)<>octant then goto done1; + q:=link(q); incr(n); + end; +done1: @; +if n>=max_quarterword then overflow("pen polygon size",max_quarterword); +@:METAFONT capacity exceeded pen polygon size}{\quad pen polygon size@> +info(h):=n; +end + +@ Now |w| points to the node that was inserted most recently, and +|k| is the current octant number. + +@= +if odd(k) then + begin link(w):=r; knil(r):=w; + end +else begin knil(w):=r; link(r):=w; + end + +@ We have inserted |n+1| nodes; it remains to duplicate the nodes at the +ends, if slopes 0 and~$\infty$ aren't already represented. At the end of +this section the total number of offset nodes should be |n+2| +(since we call them $w_0$, $w_1$, \dots,~$w_{n+1}$). + +@= +r:=link(h); +if odd(k) then + begin link(w):=r; knil(r):=w; + end +else begin knil(w):=r; link(r):=w; link(h):=w; r:=w; + end; +if (y_coord(r)<>y_coord(link(r)))or(n=0) then + begin dup_offset(r); incr(n); + end; +r:=knil(r); +if x_coord(r)<>x_coord(knil(r)) then dup_offset(r) +else decr(n) + +@ Conversely, |make_path| goes back from a pen to a cyclic path that +might have generated it. The structure of this subroutine is essentially +the same as |print_pen|. + +@p @t\4@>@@; +function make_path(@!pen_head:pointer):pointer; +var @!p:pointer; {the most recently copied knot} +@!k:1..8; {octant number} +@!h:pointer; {offset list head} +@!m,@!n:integer; {offset indices} +@!w,@!ww:pointer; {pointers that traverse the offset list} +begin p:=temp_head; +for k:=1 to 8 do + begin octant:=octant_code[k]; h:=pen_head+octant; n:=info(h); w:=link(h); + if not odd(k) then w:=knil(w); {in even octants, start at $w_{n+1}$} + for m:=1 to n+1 do + begin if odd(k) then ww:=link(w)@+else ww:=knil(w); + if (x_coord(ww)<>x_coord(w))or(y_coord(ww)<>y_coord(w)) then + @; + w:=ww; + end; + end; +if p=temp_head then + begin w:=link(pen_head+first_octant); + p:=trivial_knot(x_coord(w)+y_coord(w),y_coord(w)); link(temp_head):=p; + end; +link(p):=link(temp_head); make_path:=link(temp_head); +end; + +@ @= +begin unskew(x_coord(ww),y_coord(ww),octant); +link(p):=trivial_knot(cur_x,cur_y); p:=link(p); +end + +@ @= +function trivial_knot(@!x,@!y:scaled):pointer; +var @!p:pointer; {a new knot for explicit coordinates |x| and |y|} +begin p:=get_node(knot_node_size); +left_type(p):=explicit; right_type(p):=explicit;@/ +x_coord(p):=x; left_x(p):=x; right_x(p):=x;@/ +y_coord(p):=y; left_y(p):=y; right_y(p):=y;@/ +trivial_knot:=p; +end; + +@ That which can be created can be destroyed. + +@d add_pen_ref(#)==incr(ref_count(#)) +@d delete_pen_ref(#)==if ref_count(#)=null then toss_pen(#) + else decr(ref_count(#)) + +@= +procedure toss_pen(@!p:pointer); +var @!k:1..8; {relative header locations} +@!w,@!ww:pointer; {pointers to offset nodes} +begin if p<>null_pen then + begin for k:=1 to 8 do + begin w:=link(p+k); + repeat ww:=link(w); free_node(w,coord_node_size); w:=ww; + until w=link(p+k); + end; + free_node(p,pen_node_size); + end; +end; + +@ The |find_offset| procedure sets |(cur_x,cur_y)| to the offset associated +with a given direction~|(x,y)| and a given pen~|p|. If |x=y=0|, the +result is |(0,0)|. If two different offsets apply, one of them is +chosen arbitrarily. + +@p procedure find_offset(@!x,@!y:scaled; @!p:pointer); +label done,exit; +var @!octant:first_octant..sixth_octant; {octant code for |(x,y)|} +@!s:-1..+1; {sign of the octant} +@!n:integer; {number of offsets remaining} +@!h,@!w,@!ww:pointer; {list traversal registers} +begin @; +if odd(octant_number[octant]) then s:=-1@+else s:=+1; +h:=p+octant; w:=link(link(h)); ww:=link(w); n:=info(h); +while n>1 do + begin if ab_vs_cd(x,y_coord(ww)-y_coord(w),@| + y,x_coord(ww)-x_coord(w))<>s then goto done; + w:=ww; ww:=link(w); decr(n); + end; +done:unskew(x_coord(w),y_coord(w),octant); +exit:end; + +@ @= +if x>0 then octant:=first_octant +else if x=0 then + if y<=0 then + if y=0 then + begin cur_x:=0; cur_y:=0; return; + end + else octant:=first_octant+negate_x + else octant:=first_octant +else begin x:=-x; + if y=0 then octant:=first_octant+negate_x+negate_y + else octant:=first_octant+negate_x; + end; +if y<0 then + begin octant:=octant+negate_y; y:=-y; + end; +if x>=y then x:=x-y +else begin octant:=octant+switch_x_and_y; x:=y-x; y:=y-x; + end + +@* \[24] Filling an envelope. +We are about to reach the culmination of \MF's digital plotting routines: +Almost all of the previous algorithms will be brought to bear on \MF's +most difficult task, which is to fill the envelope of a given cyclic path +with respect to a given pen polygon. + +But we still must complete some of the preparatory work before taking such +a big plunge. + +@ Given a pointer |c| to a nonempty list of cubics, +and a pointer~|h| to the header information of a pen polygon segment, +the |offset_prep| routine changes the list into cubics that are +associated with particular pen offsets. Namely, the cubic between |p| +and~|q| should be associated with the |k|th offset when |right_type(p)=k|. + +List |c| is actually part of a cycle spec, so it terminates at the +first node whose |right_type| is |endpoint|. The cubics all have +monotone-nondecreasing $x(t)$ and $y(t)$. + +@p @t\4@>@@; +procedure offset_prep(@!c,@!h:pointer); +label done,not_found; +var @!n:halfword; {the number of pen offsets} +@!p,@!q,@!r,@!lh,@!ww:pointer; {for list manipulation} +@!k:halfword; {the current offset index} +@!w:pointer; {a pointer to offset $w_k$} +@@; +begin p:=c; n:=info(h); lh:=link(h); {now |lh| points to $w_0$} +while right_type(p)<>endpoint do + begin q:=link(p); + @; + @; + end; +end; + +@ @= +repeat r:=link(p); +if x_coord(p)=right_x(p) then if y_coord(p)=right_y(p) then + if x_coord(p)=left_x(r) then if y_coord(p)=left_y(r) then + if x_coord(p)=x_coord(r) then if y_coord(p)=y_coord(r) then + begin remove_cubic(p); + if r=q then q:=p; + r:=p; + end; +p:=r; +until p=q + +@ The splitting process uses a subroutine like |split_cubic|, but +(for ``bulletproof'' operation) we check to make sure that the +resulting (skewed) coordinates satisfy $\Delta x\G0$ and $\Delta y\G0$ +after splitting; |make_spec| has made sure that these relations hold +before splitting. (This precaution is surely unnecessary, now that +|make_spec| is so much more careful than it used to be. But who +wants to take a chance? Maybe the hardware will fail or something.) + +@= +procedure split_for_offset(@!p:pointer;@!t:fraction); +var @!q:pointer; {the successor of |p|} +@!r:pointer; {the new node} +begin q:=link(p); split_cubic(p,t,x_coord(q),y_coord(q)); r:=link(p); +if y_coord(r)y_coord(q) then y_coord(r):=y_coord(q); +if x_coord(r)x_coord(q) then x_coord(r):=x_coord(q); +end; + +@ If the pen polygon has |n| offsets, and if $w_k=(u_k,v_k)$ is the $k$th +of these, the $k$th pen slope is defined by the formula +$$s_k={v\k-v_k\over u\k-u_k},\qquad\hbox{for $0= +if n<=1 then right_type(p):=1 {this case is easy} +else begin @; + @; + if dx=0 then @ + else begin @; + @; + end; +not_found: end + +@ The slope of a cubic $B(z_0,z_1,z_2,z_3;t)=\bigl(x(t),y(t)\bigr)$ can be +calculated from the quadratic polynomials +${1\over3}x'(t)=B(x_1-x_0,x_2-x_1,x_3-x_2;t)$ and +${1\over3}y'(t)=B(y_1-y_0,y_2-y_1,y_3-y_2;t)$. +Since we may be calculating slopes from several cubics +split from the current one, it is desirable to do these calculations +without losing too much precision. ``Scaled up'' values of the +derivatives, which will be less tainted by accumulated errors than +derivatives found from the cubics themselves, are maintained in +local variables |x0|, |x1|, and |x2|, representing $X_0=2^l(x_1-x_0)$, +$X_1=2^l(x_2-x_1)$, and $X_2=2^l(x_3-x_2)$; similarly |y0|, |y1|, and~|y2| +represent $Y_0=2^l(y_1-y_0)$, $Y_1=2^l(y_2-y_1)$, and $Y_2=2^l(y_3-y_2)$. +To test whether the slope of the cubic is $\ge s$ or $\le s$, we will test +the sign of the quadratic ${1\over3}2^l\bigl(y'(t)-sx'(t)\bigr)$ if $s\le1$, +or ${1\over3}2^l\bigl(y'(t)/s-x'(t)\bigr)$ if $s>1$. + +@= +@!x0,@!x1,@!x2,@!y0,@!y1,@!y2:integer; {representatives of derivatives} +@!t0,@!t1,@!t2:integer; {coefficients of polynomial for slope testing} +@!du,@!dv,@!dx,@!dy:integer; {for slopes of the pen and the curve} +@!max_coef:integer; {used while scaling} +@!x0a,@!x1a,@!x2a,@!y0a,@!y1a,@!y2a:integer; {intermediate values} +@!t:fraction; {where the derivative passes through zero} +@!s:fraction; {slope or reciprocal slope} + +@ @= +x0:=right_x(p)-x_coord(p); {should be |>=0|} +x2:=x_coord(q)-left_x(q); {likewise} +x1:=left_x(q)-right_x(p); {but this might be negative} +y0:=right_y(p)-y_coord(p); y2:=y_coord(q)-left_y(q); +y1:=left_y(q)-right_y(p); +max_coef:=abs(x0); {we take |abs| just to make sure} +if abs(x1)>max_coef then max_coef:=abs(x1); +if abs(x2)>max_coef then max_coef:=abs(x2); +if abs(y0)>max_coef then max_coef:=abs(y0); +if abs(y1)>max_coef then max_coef:=abs(y1); +if abs(y2)>max_coef then max_coef:=abs(y2); +if max_coef=0 then goto not_found; +while max_coefs_{k-1}$. +Then, in a sense, we're halfway done, since one of the two inequalities +in $(*)$ is satisfied, and the other couldn't be satisfied for +any other value of~|k|. + +The |fin_offset_prep| subroutine solves the stated subproblem. +It has a boolean parameter called |rising| that is |true| in +case~(i), |false| in case~(ii). When |rising=false|, parameters +|x0| through |y2| represent the negative of the derivative of +the cubic following |p|; otherwise they represent the actual derivative. +The |w| parameter should point to offset~$w_k$. + +@= +procedure fin_offset_prep(@!p:pointer;@!k:halfword;@!w:pointer; + @!x0,@!x1,@!x2,@!y0,@!y1,@!y2:integer;@!rising:boolean;@!n:integer); +label exit; +var @!ww:pointer; {for list manipulation} +@!du,@!dv:scaled; {for slope calculation} +@!t0,@!t1,@!t2:integer; {test coefficients} +@!t:fraction; {place where the derivative passes a critical slope} +@!s:fraction; {slope or reciprocal slope} +@!v:integer; {intermediate value for updating |x0..y2|} +begin loop + begin right_type(p):=k; + if rising then + if k=n then return + else ww:=link(w) {a pointer to $w\k$} + else if k=1 then return + else ww:=knil(w); {a pointer to $w_{k-1}$} + @; + t:=crossing_point(t0,t1,t2); + if t>=fraction_one then return; + @; + if rising then incr(k)@+else decr(k); + w:=ww; + end; +exit:end; + +@ @= +du:=x_coord(ww)-x_coord(w); dv:=y_coord(ww)-y_coord(w); +if abs(du)>=abs(dv) then {$s_{k-1}\le1$ or $s_k\le1$} + begin s:=make_fraction(dv,du); + t0:=take_fraction(x0,s)-y0; + t1:=take_fraction(x1,s)-y1; + t2:=take_fraction(x2,s)-y2; + end +else begin s:=make_fraction(du,dv); + t0:=x0-take_fraction(y0,s); + t1:=x1-take_fraction(y1,s); + t2:=x2-take_fraction(y2,s); + end + +@ The curve has crossed $s_k$ or $s_{k-1}$; its initial segment satisfies +$(*)$, and it might cross again and return towards $s_{k-1}$ or $s_k$, +respectively, yielding another solution of $(*)$. + +@= +begin split_for_offset(p,t); right_type(p):=k; p:=link(p);@/ +v:=t_of_the_way(x0)(x1); x1:=t_of_the_way(x1)(x2); +x0:=t_of_the_way(v)(x1);@/ +v:=t_of_the_way(y0)(y1); y1:=t_of_the_way(y1)(y2); +y0:=t_of_the_way(v)(y1);@/ +t1:=t_of_the_way(t1)(t2); +if t1>0 then t1:=0; {without rounding error, |t1| would be |<=0|} +t:=crossing_point(0,-t1,-t2); +if t= +dx:=x0; dy:=y0; +if dx=0 then if dy=0 then + begin dx:=x1; dy:=y1; + if dx=0 then if dy=0 then + begin dx:=x2; dy:=y2; + end; + end + +@ The next step is to bracket the initial slope between consecutive +slopes of the pen polygon. The most important invariant relation in the +following loop is that |dy/dx>=@t$s_{k-1}$@>|. + +@= +k:=1; w:=link(lh); +loop@+ begin if k=n then goto done; + ww:=link(w); + if ab_vs_cd(dy,abs(x_coord(ww)-x_coord(w)),@| + dx,abs(y_coord(ww)-y_coord(w)))>=0 then + begin incr(k); w:=ww; + end + else goto done; + end; +done: + +@ Finally we want to reduce the general problem to situations that +|fin_offset_prep| can handle. If |k=1|, we already are in the desired +situation. Otherwise we can split the cubic into at most three parts +with respect to $s_{k-1}$, and apply |fin_offset_prep| to each part. + +@= +if k=1 then t:=fraction_one+1 +else begin ww:=knil(w); @; + t:=crossing_point(-t0,-t1,-t2); + end; +if t>=fraction_one then fin_offset_prep(p,k,w,x0,x1,x2,y0,y1,y2,true,n) +else begin split_for_offset(p,t); r:=link(p);@/ + x1a:=t_of_the_way(x0)(x1); x1:=t_of_the_way(x1)(x2); + x2a:=t_of_the_way(x1a)(x1);@/ + y1a:=t_of_the_way(y0)(y1); y1:=t_of_the_way(y1)(y2); + y2a:=t_of_the_way(y1a)(y1);@/ + fin_offset_prep(p,k,w,x0,x1a,x2a,y0,y1a,y2a,true,n); x0:=x2a; y0:=y2a; + t1:=t_of_the_way(t1)(t2); + if t1<0 then t1:=0; + t:=crossing_point(0,t1,t2); + if t; + fin_offset_prep(r,k-1,ww,-x0,-x1,-x2,-y0,-y1,-y2,false,n); + end + +@ @= +begin split_for_offset(r,t);@/ +x1a:=t_of_the_way(x1)(x2); x1:=t_of_the_way(x0)(x1); +x0a:=t_of_the_way(x1)(x1a);@/ +y1a:=t_of_the_way(y1)(y2); y1:=t_of_the_way(y0)(y1); +y0a:=t_of_the_way(y1)(y1a);@/ +fin_offset_prep(link(r),k,w,x0a,x1a,x2,y0a,y1a,y2,true,n); +x2:=x0a; y2:=y0a; +end + +@ @= +fin_offset_prep(p,n,knil(knil(lh)),-x0,-x1,-x2,-y0,-y1,-y2,false,n) + +@ OK, it's time now for the biggie. The |fill_envelope| routine generalizes +|fill_spec| to polygonal envelopes. Its outer structure is essentially the +same as before, except that octants with no cubics do contribute to +the envelope. + +@p @t\4@>@@; +@t\4@>@@; +procedure fill_envelope(@!spec_head:pointer); +label done, done1; +var @!p,@!q,@!r,@!s:pointer; {for list traversal} +@!h:pointer; {head of pen offset list for current octant} +@!www:pointer; {a pen offset of temporary interest} +@@; +begin if internal[tracing_edges]>0 then begin_edge_tracing; +p:=spec_head; {we assume that |left_type(spec_head)=endpoint|} +repeat octant:=left_octant(p); h:=cur_pen+octant; +@; +@; +offset_prep(p,h); {this may clobber node~|q|, if it becomes ``dead''} +@; +@; +p:=link(q); +until p=spec_head; +if internal[tracing_edges]>0 then end_edge_tracing; +toss_knot_list(spec_head); +end; + +@ In even-numbered octants we have reflected the coordinates an odd number +of times, hence clockwise and counterclockwise are reversed; this means that +the envelope is being formed in a ``dual'' manner. For the time being, let's +concentrate on odd-numbered octants, since they're easier to understand. +After we have coded the program for odd-numbered octants, the changes needed +to dualize it will not be so mysterious. + +It is convenient to assume that we enter an odd-numbered octant with +an |axis| transition (where the skewed slope is zero) and leave at a +|diagonal| one (where the skewed slope is infinite). Then all of the +offset points $z(t)+w(t)$ will lie in a rectangle whose lower left and +upper right corners are the initial and final offset points. If this +assumption doesn't hold we can implicitly change the curve so that it does. +For example, if the entering transition is diagonal, we can draw a +straight line from $z_0+w_{n+1}$ to $z_0+w_0$ and continue as if the +curve were moving rightward. The effect of this on the envelope is simply +to ``doubly color'' the region enveloped by a section of the pen that +goes from $w_0$ to $w_1$ to $\cdots$ to $w_{n+1}$ to~$w_0$. The additional +straight line at the beginning (and a similar one at the end, where it +may be necessary to go from $z_1+w_{n+1}$ to $z_1+w_0$) can be drawn by +the |line_edges| routine; we are thereby saved from the embarrassment that +these lines travel backwards from the current octant direction. + +Once we have established the assumption that the curve goes from +$z_0+w_0$ to $z_1+w_{n+1}$, any further retrograde moves that might +occur within the octant can be essentially ignored; we merely need to +keep track of the rightmost edge in each row, in order to compute +the envelope. + +Envelope moves consist of offset cubics intermixed with straight line +segments. We record them in a separate |env_move| array, which is +something like |move| but it keeps track of the rightmost position of the +envelope in each row. + +@= +@!env_move:array[0..move_size] of integer; + +@ @= +w:=link(h);@+if left_transition(p)=diagonal then w:=knil(w); +@!stat if internal[tracing_edges]>unity then + @; +tats@;@/ +ww:=link(h); www:=ww; {starting and ending offsets} +if odd(octant_number[octant]) then www:=knil(www)@+else ww:=knil(ww); +if w<>ww then skew_line_edges(p,w,ww); +end_round(x_coord(p)+x_coord(ww),y_coord(p)+y_coord(ww)); +m0:=m1; n0:=n1; d0:=d1;@/ +end_round(x_coord(q)+x_coord(www),y_coord(q)+y_coord(www)); +if n1-n0>=move_size then overflow("move table size",move_size) +@:METAFONT capacity exceeded move table size}{\quad move table size@> + +@ @= +begin print_nl("@@ Octant "); print(octant_dir[octant]); +@:]]]\AT!_Octant}{\.{\AT! Octant...}@> +print(" ("); print_int(info(h)); print(" offset"); +if info(h)<>1 then print_char("s"); +print("), from "); +print_two_true(x_coord(p)+x_coord(w),y_coord(p)+y_coord(w));@/ +ww:=link(h);@+if right_transition(q)=diagonal then ww:=knil(ww); +print(" to "); +print_two_true(x_coord(q)+x_coord(ww),y_coord(q)+y_coord(ww)); +end + +@ A slight variation of the |line_edges| procedure comes in handy +when we must draw the retrograde lines for nonstandard entry and exit +conditions. + +@= +procedure skew_line_edges(@!p,@!w,@!ww:pointer); +var @!x0,@!y0,@!x1,@!y1:scaled; {from and to} +begin if (x_coord(w)<>x_coord(ww))or(y_coord(w)<>y_coord(ww)) then + begin x0:=x_coord(p)+x_coord(w); y0:=y_coord(p)+y_coord(w);@/ + x1:=x_coord(p)+x_coord(ww); y1:=y_coord(p)+y_coord(ww);@/ + unskew(x0,y0,octant); {unskew and unrotate the coordinates} + x0:=cur_x; y0:=cur_y;@/ + unskew(x1,y1,octant);@/ + @!stat if internal[tracing_edges]>unity then + begin print_nl("@@ retrograde line from "); +@:]]]\AT!_retro_}{\.{\AT! retrograde line...}@> + @.retrograde line...@> + print_two(x0,y0); print(" to "); print_two(cur_x,cur_y); print_nl(""); + end;@+tats@;@/ + line_edges(x0,y0,cur_x,cur_y); {then draw a straight line} + end; +end; + +@ The envelope calculations require more local variables than we needed +in the simpler case of |fill_spec|. At critical points in the computation, +|w| will point to offset $w_k$; |m| and |n| will record the current +lattice positions. The values of |move_ptr| after the initial and before +the final offset adjustments are stored in |smooth_bot| and |smooth_top|, +respectively. + +@= +@!m,@!n:integer; {current lattice position} +@!mm0,@!mm1:integer; {skewed equivalents of |m0| and |m1|} +@!k:integer; {current offset number} +@!w,@!ww:pointer; {pointers to the current offset and its neighbor} +@!smooth_bot,@!smooth_top:0..move_size; {boundaries of smoothing} +@!xx,@!yy,@!xp,@!yp,@!delx,@!dely,@!tx,@!ty:scaled; + {registers for coordinate calculations} + +@ @= +if odd(octant_number[octant]) then + begin @; + r:=p; right_type(q):=info(h)+1; + loop@+ begin if r=q then smooth_top:=move_ptr; + while right_type(r)<>k do + @; + if r=p then smooth_bot:=move_ptr; + if r=q then goto done; + move[move_ptr]:=1; n:=move_ptr; s:=link(r);@/ + make_moves(x_coord(r)+x_coord(w),right_x(r)+x_coord(w), + left_x(s)+x_coord(w),x_coord(s)+x_coord(w),@| + y_coord(r)+y_coord(w)+half_unit,right_y(r)+y_coord(w)+half_unit, + left_y(s)+y_coord(w)+half_unit,y_coord(s)+y_coord(w)+half_unit,@| + xy_corr[octant],y_corr[octant]);@/ + @; + r:=s; + end; +done: @; + end +else dual_moves(h,p,q); +right_type(q):=endpoint + +@ @= +k:=0; w:=link(h); ww:=knil(w); +mm0:=floor_unscaled(x_coord(p)+x_coord(w)-xy_corr[octant]); +mm1:=floor_unscaled(x_coord(q)+x_coord(ww)-xy_corr[octant]); +for n:=0 to n1-n0 do env_move[n]:=mm0; +env_move[n1-n0]:=mm1; move_ptr:=0; m:=mm0 + +@ At this point |n| holds the value of |move_ptr| that was current +when |make_moves| began to record its moves. + +@= +repeat m:=m+move[n]-1; +if m>env_move[n] then env_move[n]:=m; +incr(n); +until n>move_ptr + +@ Retrograde lines (when |k| decreases) do not need to be recorded in +|env_move| because their edges are not the furthest right in any row. + +@= +begin xx:=x_coord(r)+x_coord(w); yy:=y_coord(r)+y_coord(w)+half_unit; +@!stat if internal[tracing_edges]>unity then + begin print_nl("@@ transition line "); print_int(k); print(", from "); +@:]]]\AT!_trans_}{\.{\AT! transition line...}@> +@.transition line...@> + print_two_true(xx,yy-half_unit); + end;@+tats@;@/ +if right_type(r)>k then + begin incr(k); w:=link(w); + xp:=x_coord(r)+x_coord(w); yp:=y_coord(r)+y_coord(w)+half_unit; + if yp<>yy then + @; + end +else begin decr(k); w:=knil(w); + xp:=x_coord(r)+x_coord(w); yp:=y_coord(r)+y_coord(w)+half_unit; + end; +stat if internal[tracing_edges]>unity then + begin print(" to "); + print_two_true(xp,yp-half_unit); + print_nl(""); + end;@+tats@;@/ +m:=floor_unscaled(xp-xy_corr[octant]); +move_ptr:=floor_unscaled(yp-y_corr[octant])-n0; +if m>env_move[move_ptr] then env_move[move_ptr]:=m; +end + +@ In this step we have |xp>=xx| and |yp>=yy|. + +@= +begin ty:=floor_scaled(yy-y_corr[octant]); dely:=yp-yy; yy:=yy-ty; +ty:=yp-y_corr[octant]-ty; +if ty>=unity then + begin delx:=xp-xx; yy:=unity-yy; + loop@+ begin tx:=take_fraction(delx,make_fraction(yy,dely)); + if ab_vs_cd(tx,dely,delx,yy)+xy_corr[octant]>0 then decr(tx); + m:=floor_unscaled(xx+tx); + if m>env_move[move_ptr] then env_move[move_ptr]:=m; + ty:=ty-unity; + if ty= +debug if (m<>mm1)or(move_ptr<>n1-n0) then confusion("1");@+gubed@;@/ +@:this can't happen /}{\quad 1@> +move[0]:=d0+env_move[0]-mm0; +for n:=1 to move_ptr do + move[n]:=env_move[n]-env_move[n-1]+1; +move[move_ptr]:=move[move_ptr]-d1; +if internal[smoothing]>0 then smooth_moves(smooth_bot,smooth_top); +move_to_edges(m0,n0,m1,n1); +if right_transition(q)=axis then + begin w:=link(h); skew_line_edges(q,knil(w),w); + end + +@ We've done it all in the odd-octant case; the only thing remaining +is to repeat the same ideas, upside down and/or backwards. + +The following code has been split off as a subprocedure of |fill_envelope|, +because some \PASCAL\ compilers cannot handle procedures as large as +|fill_envelope| would otherwise be. + +@= +procedure dual_moves(@!h,@!p,@!q:pointer); +label done,done1; +var @!r,@!s:pointer; {for list traversal} +@@; +begin @; +r:=p; {recall that |right_type(q)=endpoint=0| now} +loop@+ begin if r=q then smooth_top:=move_ptr; + while right_type(r)<>k do + @; + if r=p then smooth_bot:=move_ptr; + if r=q then goto done; + move[move_ptr]:=1; n:=move_ptr; s:=link(r);@/ + make_moves(x_coord(r)+x_coord(w),right_x(r)+x_coord(w), + left_x(s)+x_coord(w),x_coord(s)+x_coord(w),@| + y_coord(r)+y_coord(w)+half_unit,right_y(r)+y_coord(w)+half_unit, + left_y(s)+y_coord(w)+half_unit,y_coord(s)+y_coord(w)+half_unit,@| + xy_corr[octant],y_corr[octant]); + @; + r:=s; + end; +done:@; +end; + +@ In the dual case the normal situation is to arrive with a |diagonal| +transition and to leave at the |axis|. The leftmost edge in each row +is relevant instead of the rightmost one. + +@= +k:=info(h)+1; ww:=link(h); w:=knil(ww);@/ +mm0:=floor_unscaled(x_coord(p)+x_coord(w)-xy_corr[octant]); +mm1:=floor_unscaled(x_coord(q)+x_coord(ww)-xy_corr[octant]); +for n:=1 to n1-n0+1 do env_move[n]:=mm1; +env_move[0]:=mm0; move_ptr:=0; m:=mm0 + +@ @= +repeat if mmove_ptr + +@ Dual retrograde lines occur when |k| increases; the edges of such lines +are not the furthest left in any row. + +@= +begin xx:=x_coord(r)+x_coord(w); yy:=y_coord(r)+y_coord(w)+half_unit; +@!stat if internal[tracing_edges]>unity then + begin print_nl("@@ transition line "); print_int(k); print(", from "); +@:]]]\AT!_trans_}{\.{\AT! transition line...}@> +@.transition line...@> + print_two_true(xx,yy-half_unit); + end;@+tats@;@/ +if right_type(r)yy then + @; + end +else begin incr(k); w:=link(w); + xp:=x_coord(r)+x_coord(w); yp:=y_coord(r)+y_coord(w)+half_unit; + end; +stat if internal[tracing_edges]>unity then + begin print(" to "); + print_two_true(xp,yp-half_unit); + print_nl(""); + end;@+tats@;@/ +m:=floor_unscaled(xp-xy_corr[octant]); +move_ptr:=floor_unscaled(yp-y_corr[octant])-n0; +if m=xx| and |yp>=yy|; but this time we are interested in the {\sl +smallest\/} |m| that belongs to a given |move_ptr| position, instead of +the largest~|m|. + +@= +begin ty:=floor_scaled(yy-y_corr[octant]); dely:=yp-yy; yy:=yy-ty; +ty:=yp-y_corr[octant]-ty; +if ty>=unity then + begin delx:=xp-xx; yy:=unity-yy; + loop@+ begin if m0 then decr(tx); + m:=floor_unscaled(xx+tx); + ty:=ty-unity; incr(move_ptr); + if ty= +debug if (m<>mm1)or(move_ptr<>n1-n0) then confusion("2");@+gubed@;@/ +@:this can't happen /}{\quad 2@> +move[0]:=d0+env_move[1]-mm0; +for n:=1 to move_ptr do + move[n]:=env_move[n+1]-env_move[n]+1; +move[move_ptr]:=move[move_ptr]-d1; +if internal[smoothing]>0 then smooth_moves(smooth_bot,smooth_top); +move_to_edges(m0,n0,m1,n1); +if right_transition(q)=diagonal then + begin w:=link(h); skew_line_edges(q,w,knil(w)); + end + +@* \[25] Elliptical pens. +To get the envelope of a cyclic path with respect to an ellipse, \MF\ +calculates the envelope with respect to a polygonal approximation to +the ellipse, using an approach due to John Hobby (Ph.D. thesis, +Stanford University, 1985). +@^Hobby, John Douglas@> +This has two important advantages over trying to obtain the ``exact'' +envelope: + +\yskip\textindent{1)}It gives better results, because the polygon has been +designed to counteract problems that arise from digitization; the +polygon includes sub-pixel corrections to an exact ellipse that make +the results essentially independent of where the path falls on the raster. +For example, the exact envelope with respect to a pen of diameter~1 +blackens a pixel if and only if the path intersects a circle of diameter~1 +inscribed in that pixel; the resulting pattern has ``blots'' when the path +is travelling diagonally in unfortunate raster positions. A much better +result is obtained when pixels are blackened only when the path intersects +an inscribed {\sl diamond\/} of diameter~1. Such a diamond is precisely +the polygon that \MF\ uses in the special case of a circle whose diameter is~1. + +\yskip\textindent{2)}Polygonal envelopes of cubic splines are cubic +splines, hence it isn't necessary to introduce completely different +routines. By contrast, exact envelopes of cubic splines with respect +to circles are complicated curves, more difficult to plot than cubics. + +@ Hobby's construction involves some interesting number theory. +If $u$ and~$v$ are relatively prime integers, we divide the +set of integer points $(m,n)$ into equivalence classes by saying +that $(m,n)$ belongs to class $um+vn$. Then any two integer points +that lie on a line of slope $-u/v$ belong to the same class, because +such points have the form $(m+tv,n-tu)$. Neighboring lines of slope $-u/v$ +that go through integer points are separated by distance $1/\psqrt{u^2+v^2}$ +from each other, and these lines are perpendicular to lines of slope~$v/u$. +If we start at the origin and travel a distance $k/\psqrt{u^2+v^2}$ in +direction $(u,v)$, we reach the line of slope~$-u/v$ whose points +belong to class~$k$. + +For example, let $u=2$ and $v=3$. Then the points $(0,0)$, $(3,-2)$, +$\ldots$ belong to class~0; the points $(-1,1)$, $(2,-1)$, $\ldots$ belong +to class~1; and the distance between these two lines is $1/\sqrt{13}$. +The point $(2,3)$ itself belongs to class~13, hence its distance from +the origin is $13/\sqrt{13}=\sqrt{13}$ (which we already knew). + +Suppose we wish to plot envelopes with respect to polygons with +integer vertices. Then the best polygon for curves that travel in +direction $(v,-u)$ will contain the points of class~$k$ such that +$k/\psqrt{u^2+v^2}$ is as close as possible to~$d$, where $d$ is the +maximum distance of the given ellipse from the line $ux+vy=0$. + +The |fillin| correction assumes that a diagonal line has an +apparent thickness $$2f\cdot\min(\vert u\vert,\vert v\vert)/\psqrt{u^2+v^2}$$ +greater than would be obtained with truly square pixels. (If a +white pixel at an exterior corner is assumed to have apparent +darkness $f_1$ and a black pixel at an interior corner is assumed +to have apparent darkness $1-f_2$, then $f=f_1-f_2$ is the |fillin| +parameter.) Under this assumption we want to choose $k$ so that +$\bigl(k+2f\cdot\min(\vert u\vert,\vert v\vert)\bigr)\big/\psqrt{u^2+v^2}$ +is as close as possible to $d$. + +Integer coordinates for the vertices work nicely because the thickness of +the envelope at any given slope is independent of the position of the +path with respect to the raster. It turns out, in fact, that the same +property holds for polygons whose vertices have coordinates that are +integer multiples of~$1\over2$, because ellipses are symmetric about +the origin. It's convenient to double all dimensions and require the +resulting polygon to have vertices with integer coordinates. For example, +to get a circle of {\sl diameter}~$r$, we shall compute integer +coordinates for a circle of {\sl radius}~$r$. The circle of radius~$r$ +will want to be represented by a polygon that contains the boundary +points $(0,\pm r)$ and~$(\pm r,0)$; later we will divide everything +by~2 and get a polygon with $(0,\pm{1\over2}r)$ and $(\pm{1\over2}r,0)$ +on its boundary. + +@ In practice the important slopes are those having small values of +$u$ and~$v$; these make regular patterns in which our eyes quickly +spot irregularities. For example, horizontal and vertical lines +(when $u=0$ and $\vert v\vert=1$, or $\vert u\vert=1$ and $v=0$) +are the most important; diagonal lines (when $\vert u\vert=\vert v\vert=1$) +are next; and then come lines with slope $\pm2$ or $\pm1/2$. + +The nicest way to generate all rational directions having small +numerators and denominators is to generalize the Stern--Brocot tree +[cf.~{\sl Concrete Mathematics}, section 4.5] +@^Brocot, Achille@> +@^Stern, Moritz Abraham@> +to a ``Stern--Brocot wreath'' as follows: Begin with four nodes +arranged in a circle, containing the respective directions +$(u,v)=(1,0)$, $(0,1)$, $(-1,0)$, and~$(0,-1)$. Then between pairs of +consecutive terms $(u,v)$ and $(u',v')$ of the wreath, insert the +direction $(u+u',v+v')$; continue doing this until some stopping +criterion is fulfilled. + +It is not difficult to verify that, regardless of the stopping +criterion, consecutive directions $(u,v)$ and $(u',v')$ of this +wreath will always satisfy the relation $uv'-u'v=1$. Such pairs +of directions have a nice property with respect to the equivalence +classes described above. Let $l$ be a line of equivalent integer points +$(m+tv,n-tu)$ with respect to~$(u,v)$, and let $l'$ be a line of +equivalent integer points $(m'+tv',n'-tu')$ with respect to~$(u',v')$. +Then $l$ and~$l'$ intersect in an integer point $(m'',n'')$, because +the determinant of the linear equations for intersection is $uv'-u'v=1$. +Notice that the class number of $(m'',n'')$ with respect to $(u+u',v+v')$ +is the sum of its class numbers with respect to $(u,v)$ and~$(u',v')$. +Moreover, consecutive points on~$l$ and~$l'$ belong to classes that +differ by exactly~1 with respect to $(u+u',v+v')$. + +This leads to a nice algorithm in which we construct a polygon having +``correct'' class numbers for as many small-integer directions $(u,v)$ +as possible: Assuming that lines $l$ and~$l'$ contain points of the +correct class for $(u,v)$ and~$(u',v')$, respectively, we determine +the intersection $(m'',n'')$ and compute its class with respect to +$(u+u',v+v')$. If the class is too large to be the best approximation, +we move back the proper number of steps from $(m'',n'')$ toward smaller +class numbers on both $l$ and~$l'$, unless this requires moving to points +that are no longer in the polygon; in this way we arrive at two points that +determine a line~$l''$ having the appropriate class. The process continues +recursively, until it cannot proceed without removing the last remaining +point from the class for $(u,v)$ or the class for $(u',v')$. + +@ The |make_ellipse| subroutine produces a pointer to a cyclic path +whose vertices define a polygon suitable for envelopes. The control +points on this path will be ignored; in fact, the fields in knot nodes +that are usually reserved for control points are occupied by other +data that helps |make_ellipse| compute the desired polygon. + +Parameters |major_axis| and |minor_axis| define the axes of the ellipse; +and parameter |theta| is an angle by which the ellipse is rotated +counterclockwise. If |theta=0|, the ellipse has the equation +$(x/a)^2+(y/b)^2=1$, where |a=major_axis/2| and |b=minor_axis/2|. +In general, the points of the ellipse are generated in the complex plane +by the formula $e^{i\theta}(a\cos t+ib\sin t)$, as $t$~ranges over all +angles. Notice that if |major_axis=minor_axis=d|, we obtain a circle +of diameter~|d|, regardless of the value of |theta|. + +The method sketched above is used to produce the elliptical polygon, +except that the main work is done only in the halfplane obtained from +the three starting directions $(0,-1)$, $(1,0)$,~$(0,1)$. Since the ellipse +has circular symmetry, we use the fact that the last half of the polygon +is simply the negative of the first half. Furthermore, we need to compute only +one quarter of the polygon if the ellipse has axis symmetry. + +@p function make_ellipse(@!major_axis,@!minor_axis:scaled; + @!theta:angle):pointer; +label done,done1,found; +var @!p,@!q,@!r,@!s:pointer; {for list manipulation} +@!h:pointer; {head of the constructed knot list} +@!alpha,@!beta,@!gamma,@!delta:integer; {special points} +@!c,@!d:integer; {class numbers} +@!u,@!v:integer; {directions} +@!symmetric:boolean; {should the result be symmetric about the axes?} +begin @; +@; +if symmetric then + @; +@; +make_ellipse:=h; +end; + +@ A special data structure is used only with |make_ellipse|: The +|right_x|, |left_x|, |right_y|, and |left_y| fields of knot nodes +are renamed |right_u|, |left_v|, |right_class|, and |left_length|, +in order to store information that simplifies the necessary computations. + +If |p| and |q| are consecutive knots in this data structure, the +|x_coord| and |y_coord| fields of |p| and~|q| contain current vertices +of the polygon; their values are integer multiples +of |half_unit|. Both of these vertices belong to equivalence class +|right_class(p)| with respect to the direction +$\bigl($|right_u(p),left_v(q)|$\bigr)$. The number of points of this class +on the line from vertex~|p| to vertex~|q| is |1+left_length(q)|. +In particular, |left_length(q)=0| means that |x_coord(p)=x_coord(q)| +and |y_coord(p)=y_coord(q)|; such duplicate vertices will be +discarded during the course of the algorithm. + +The contents of |right_u(p)| and |left_v(q)| are integer multiples +of |half_unit|, just like the coordinate fields. Hence, for example, +the point $\bigl($|x_coord(p)-left_v(q),y_coord(p)+right_u(p)|$\bigr)$ +also belongs to class number |right_class(p)|. This point is one +step closer to the vertex in node~|q|; it equals that vertex +if and only if |left_length(q)=1|. + +The |left_type| and |right_type| fields are not used, but |link| +has its normal meaning. + +To start the process, we create four nodes for the three directions +$(0,-1)$, $(1,0)$, and $(0,1)$. The corresponding vertices are +$(-\alpha,-\beta)$, $(\gamma,-\beta)$, $(\gamma,\beta)$, and +$(\alpha,\beta)$, where $(\alpha,\beta)$ is a half-integer approximation +to where the ellipse rises highest above the $x$-axis, and where +$\gamma$ is a half-integer approximation to the maximum $x$~coordinate +of the ellipse. The fourth of these nodes is not actually calculated +if the ellipse has axis symmetry. + +@d right_u==right_x {|u| value for a pen edge} +@d left_v==left_x {|v| value for a pen edge} +@d right_class==right_y {equivalence class number of a pen edge} +@d left_length==left_y {length of a pen edge} + +@= +@; +p:=get_node(knot_node_size); q:=get_node(knot_node_size); +r:=get_node(knot_node_size); +if symmetric then s:=null@+else s:=get_node(knot_node_size); +h:=p; link(p):=q; link(q):=r; link(r):=s; {|s=null| or |link(s)=null|} +@; +x_coord(p):=-alpha*half_unit; +y_coord(p):=-beta*half_unit; +x_coord(q):=gamma*half_unit;@/ +y_coord(q):=y_coord(p); x_coord(r):=x_coord(q);@/ +right_u(p):=0; left_v(q):=-half_unit;@/ +right_u(q):=half_unit; left_v(r):=0;@/ +right_u(r):=0; +right_class(p):=beta; right_class(q):=gamma; right_class(r):=beta;@/ +left_length(q):=gamma+alpha; +if symmetric then + begin y_coord(r):=0; left_length(r):=beta; + end +else begin y_coord(r):=-y_coord(p); left_length(r):=beta+beta;@/ + x_coord(s):=-x_coord(p); y_coord(s):=y_coord(r);@/ + left_v(s):=half_unit; left_length(s):=gamma-alpha; + end + +@ One of the important invariants of the pen data structure is that +the points are distinct. We may need to correct the pen specification +in order to avoid this. (The result of \&{pencircle} will always be at +least one pixel wide and one pixel tall, although \&{makepen} is +capable of producing smaller pens.) + +@= +if beta=0 then beta:=1; +if gamma=0 then gamma:=1; +if gamma<=abs(alpha) then + if alpha>0 then alpha:=gamma-1 + else alpha:=1-gamma + +@ If $a$ and $b$ are the semi-major and semi-minor axes, +the given ellipse rises highest above the $x$-axis at the point +$\bigl((a^2-b^2)\sin\theta\cos\theta/\rho\bigr)+i\rho$, where +$\rho=\sqrt{(a\sin\theta)^2+(b\cos\theta)^2}$. It reaches +furthest to the right of~the $y$-axis at the point +$\sigma+i(a^2-b^2)\sin\theta\cos\theta/\sigma$, where +$\sigma=\sqrt{(a\cos\theta)^2+(b\sin\theta)^2}$. + +@= +if (major_axis=minor_axis)or(theta mod ninety_deg=0) then + begin symmetric:=true; alpha:=0; + if odd(theta div ninety_deg) then + begin beta:=major_axis; gamma:=minor_axis; + n_sin:=fraction_one; n_cos:=0; {|n_sin| and |n_cos| are used later} + end + else begin beta:=minor_axis; gamma:=major_axis; theta:=0; + end; {|n_sin| and |n_cos| aren't needed in this case} + end +else begin symmetric:=false; + n_sin_cos(theta); {set up $|n_sin|=\sin\theta$ and $|n_cos|=\cos\theta$} + gamma:=take_fraction(major_axis,n_sin); + delta:=take_fraction(minor_axis,n_cos); + beta:=pyth_add(gamma,delta); + alpha:=take_fraction(take_fraction(major_axis, + make_fraction(gamma,beta)),n_cos)@| + -take_fraction(take_fraction(minor_axis, + make_fraction(delta,beta)),n_sin); + alpha:=(alpha+half_unit) div unity; + gamma:=pyth_add(take_fraction(major_axis,n_cos), + take_fraction(minor_axis,n_sin)); + end; +beta:=(beta+half_unit) div unity; +gamma:=(gamma+half_unit) div unity + +@ Now |p|, |q|, and |r| march through the list, always representing +three consecutive vertices and two consecutive slope directions. +When a new slope is interpolated, we back up slightly, until +further refinement is impossible; then we march forward again. +The somewhat magical operations performed in this part of the +algorithm are justified by the theory sketched earlier. +Complications arise only from the need to keep zero-length lines +out of the final data structure. + +@= +loop@+ begin u:=right_u(p)+right_u(q); v:=left_v(q)+left_v(r); + c:=right_class(p)+right_class(q);@/ + @; + delta:=c-d; {we want to move |delta| steps back + from the intersection vertex~|q|} + if delta>0 then + begin if delta>left_length(r) then delta:=left_length(r); + if delta>=left_length(q) then + @ + else @; + end + else p:=q; + @; + end; +done: + +@ The appearance of a zero-length line means that we should advance |p| +past it. We must not try to straddle a missing direction, because the +algorithm works only on consecutive pairs of directions. + +@= +loop@+ begin q:=link(p); + if q=null then goto done; + if left_length(q)=0 then + begin link(p):=link(q); right_class(p):=right_class(q); + right_u(p):=right_u(q); free_node(q,knot_node_size); + end + else begin r:=link(q); + if r=null then goto done; + if left_length(r)=0 then + begin link(p):=r; free_node(q,knot_node_size); p:=r; + end + else goto found; + end; + end; +found: + +@ The `\&{div} 8' near the end of this step comes from +the fact that |delta| is scaled by~$2^{15}$ and $d$~by~$2^{16}$, +while |take_fraction| removes a scale factor of~$2^{28}$. +We also make sure that $d\G\max(\vert u\vert,\vert v\vert)$, so that +the pen will always include a circular pen of diameter~1 as a subset; +then it won't be possible to get disconnected path envelopes. + +@= +delta:=pyth_add(u,v); +if major_axis=minor_axis then d:=major_axis {circles are easy} +else begin if theta=0 then + begin alpha:=u; beta:=v; + end + else begin alpha:=take_fraction(u,n_cos)+take_fraction(v,n_sin); + beta:=take_fraction(v,n_cos)-take_fraction(u,n_sin); + end; + alpha:=make_fraction(alpha,delta); + beta:=make_fraction(beta,delta); + d:=pyth_add(take_fraction(major_axis,alpha), + take_fraction(minor_axis,beta)); + end; +alpha:=abs(u); beta:=abs(v); +if alpha0 then + d:=d-take_fraction(internal[fillin],make_fraction(beta+beta,delta)); +d:=take_fraction((d+4) div 8,delta); alpha:=alpha div half_unit; +if d=delta| from vertex~|q| to +to vertex~|r|, orthogonal to direction $\bigl($|right_u(q),left_v(r)|$\bigr)$. +The best line to direction $(u,v)$ should replace the line from +|p| to~|q|; this new line will have the same length as the old. + +@= +begin delta:=left_length(q);@/ +right_class(p):=c-delta; right_u(p):=u; left_v(q):=v;@/ +x_coord(q):=x_coord(q)-delta*left_v(r); +y_coord(q):=y_coord(q)+delta*right_u(q);@/ +left_length(r):=left_length(r)-delta; +end + +@ Here is the main case, now that we have dealt with the exception: +We insert a new line of length |delta| for direction |(u,v)|, decreasing +each of the adjacent lines by |delta| steps. + +@= +begin s:=get_node(knot_node_size); link(p):=s; link(s):=q;@/ +x_coord(s):=x_coord(q)+delta*left_v(q); +y_coord(s):=y_coord(q)-delta*right_u(p);@/ +x_coord(q):=x_coord(q)-delta*left_v(r); +y_coord(q):=y_coord(q)+delta*right_u(q);@/ +left_v(s):=left_v(q); right_u(s):=u; left_v(q):=v;@/ +right_class(s):=c-delta;@/ +left_length(s):=left_length(q)-delta; left_length(q):=delta; +left_length(r):=left_length(r)-delta; +end + +@ Only the coordinates need to be copied, not the class numbers and other stuff. +At this point either |link(p)| or |link(link(p))| is |null|. + +@= +begin s:=null; q:=h; +loop@+ begin r:=get_node(knot_node_size); link(r):=s; s:=r;@/ + x_coord(s):=x_coord(q); y_coord(s):=-y_coord(q); + if q=p then goto done1; + q:=link(q); + if y_coord(q)=0 then goto done1; + end; +done1: if (link(p)<>null) then free_node(link(p),knot_node_size); +link(p):=s; beta:=-y_coord(h); +while y_coord(p)<>beta do p:=link(p); +q:=link(p); +end + +@ Now we use a somewhat tricky fact: The pointer |q| will be null if and +only if the line for the final direction $(0,1)$ has been removed. If +that line still survives, it should be combined with a possibly +surviving line in the initial direction $(0,-1)$. + +@= +if q<>null then + begin if right_u(h)=0 then + begin p:=h; h:=link(h); free_node(p,knot_node_size);@/ + x_coord(q):=-x_coord(h); + end; + p:=q; + end +else q:=p; +r:=link(h); {now |p=q|, |x_coord(p)=-x_coord(h)|, |y_coord(p)=-y_coord(h)|} +repeat s:=get_node(knot_node_size); link(p):=s; p:=s;@/ +x_coord(p):=-x_coord(r); y_coord(p):=-y_coord(r); r:=link(r); +until r=q; +link(p):=h + +@* \[26] Direction and intersection times. +A path of length $n$ is defined parametrically by functions $x(t)$ and +$y(t)$, for |0<=t<=n|; we can regard $t$ as the ``time'' at which the path +reaches the point $\bigl(x(t),y(t)\bigr)$. In this section of the program +we shall consider operations that determine special times associated with +given paths: the first time that a path travels in a given direction, and +a pair of times at which two paths cross each other. + +@ Let's start with the easier task. The function |find_direction_time| is +given a direction |(x,y)| and a path starting at~|h|. If the path never +travels in direction |(x,y)|, the direction time will be~|-1|; otherwise +it will be nonnegative. + +Certain anomalous cases can arise: If |(x,y)=(0,0)|, so that the given +direction is undefined, the direction time will be~0. If $\bigl(x'(t), +y'(t)\bigr)=(0,0)$, so that the path direction is undefined, it will be +assumed to match any given direction at time~|t|. + +The routine solves this problem in nondegenerate cases by rotating the path +and the given direction so that |(x,y)=(1,0)|; i.e., the main task will be +to find when a given path first travels ``due east.'' + +@p function find_direction_time(@!x,@!y:scaled;@!h:pointer):scaled; +label exit,found,not_found,done; +var @!max:scaled; {$\max\bigl(\vert x\vert,\vert y\vert\bigr)$} +@!p,@!q:pointer; {for list traversal} +@!n:scaled; {the direction time at knot |p|} +@!tt:scaled; {the direction time within a cubic} +@@; +begin @; +n:=0; p:=h; +loop@+ begin if right_type(p)=endpoint then goto not_found; + q:=link(p); + @; + p:=q; n:=n+unity; + end; +not_found: find_direction_time:=-unity; return; +found: find_direction_time:=n+tt; +exit:end; + +@ @= +if abs(x)0 then y:=fraction_one@+else y:=-fraction_one; + end +else if x=0 then + begin find_direction_time:=0; return; + end +else begin y:=make_fraction(y,abs(x)); + if x>0 then x:=fraction_one@+else x:=-fraction_one; + end + +@ Since we're interested in the tangent directions, we work with the +derivative $${1\over3}B'(x_0,x_1,x_2,x_3;t)= +B(x_1-x_0,x_2-x_1,x_3-x_2;t)$$ instead of +$B(x_0,x_1,x_2,x_3;t)$ itself. The derived coefficients are also scaled up +in order to achieve better accuracy. + +The given path may turn abruptly at a knot, and it might pass the critical +tangent direction at such a time. Therefore we remember the direction |phi| +in which the previous rotated cubic was traveling. (The value of |phi| will be +undefined on the first cubic, i.e., when |n=0|.) + +@= +tt:=0; +@; +if y1=0 then if x1>=0 then goto found; +if n>0 then + begin @; + if p=h then goto not_found; + end; +if (x3<>0)or(y3<>0) then phi:=n_arg(x3,y3); +@ + +@ @= +@!x1,@!x2,@!x3,@!y1,@!y2,@!y3:scaled; {multiples of rotated derivatives} +@!theta,@!phi:angle; {angles of exit and entry at a knot} +@!t:fraction; {temp storage} + +@ @= +x1:=right_x(p)-x_coord(p); x2:=left_x(q)-right_x(p); +x3:=x_coord(q)-left_x(q);@/ +y1:=right_y(p)-y_coord(p); y2:=left_y(q)-right_y(p); +y3:=y_coord(q)-left_y(q);@/ +max:=abs(x1); +if abs(x2)>max then max:=abs(x2); +if abs(x3)>max then max:=abs(x3); +if abs(y1)>max then max:=abs(y1); +if abs(y2)>max then max:=abs(y2); +if abs(y3)>max then max:=abs(y3); +if max=0 then goto found; +while max= +theta:=n_arg(x1,y1); +if theta>=0 then if phi<=0 then if phi>=theta-one_eighty_deg then goto found; +if theta<=0 then if phi>=0 then if phi<=theta+one_eighty_deg then goto found + +@ In this step we want to use the |crossing_point| routine to find the +roots of the quadratic equation $B(y_1,y_2,y_3;t)=0$. +Several complications arise: If the quadratic equation has a double root, +the curve never crosses zero, and |crossing_point| will find nothing; +this case occurs iff $y_1y_3=y_2^2$ and $y_1y_2<0$. If the quadratic +equation has simple roots, or only one root, we may have to negate it +so that $B(y_1,y_2,y_3;t)$ crosses from positive to negative at its first root. +And finally, we need to do special things if $B(y_1,y_2,y_3;t)$ is +identically zero. + +@ @= +if x1<0 then if x2<0 then if x3<0 then goto done; +if ab_vs_cd(y1,y3,y2,y2)=0 then + @; +if y1<=0 then + if y1<0 then + begin y1:=-y1; y2:=-y2; y3:=-y3; + end + else if y2>0 then + begin y2:=-y2; y3:=-y3; + end; +@; +done: + +@ The quadratic polynomial $B(y_1,y_2,y_3;t)$ begins |>=0| and has at most +two roots, because we know that it isn't identically zero. + +It must be admitted that the |crossing_point| routine is not perfectly accurate; +rounding errors might cause it to find a root when $y_1y_3>y_2^2$, or to +miss the roots when $y_1y_3= +t:=crossing_point(y1,y2,y3); +if t>fraction_one then goto done; +y2:=t_of_the_way(y2)(y3); +x1:=t_of_the_way(x1)(x2); +x2:=t_of_the_way(x2)(x3); +x1:=t_of_the_way(x1)(x2); +if x1>=0 then we_found_it; +if y2>0 then y2:=0; +tt:=t; t:=crossing_point(0,-y2,-y3); +if t>fraction_one then goto done; +x1:=t_of_the_way(x1)(x2); +x2:=t_of_the_way(x2)(x3); +if t_of_the_way(x1)(x2)>=0 then + begin t:=t_of_the_way(tt)(fraction_one); we_found_it; + end + +@ @= +begin if ab_vs_cd(y1,y2,0,0)<0 then + begin t:=make_fraction(y1,y1-y2); + x1:=t_of_the_way(x1)(x2); + x2:=t_of_the_way(x2)(x3); + if t_of_the_way(x1)(x2)>=0 then we_found_it; + end +else if y3=0 then + if y1=0 then + @=0|@> + else if x3>=0 then + begin tt:=unity; goto found; + end; +goto done; +end + +@ At this point we know that the derivative of |y(t)| is identically zero, +and that |x1<0|; but either |x2>=0| or |x3>=0|, so there's some hope of +traveling east. + +@=0|...@>= +begin t:=crossing_point(-x1,-x2,-x3); +if t<=fraction_one then we_found_it; +if ab_vs_cd(x1,x3,x2,x2)<=0 then + begin t:=make_fraction(x1,x1-x2); we_found_it; + end; +end + +@ The intersection of two cubics can be found by an interesting variant +of the general bisection scheme described in the introduction to |make_moves|.\ +Given $w(t)=B(w_0,w_1,w_2,w_3;t)$ and $z(t)=B(z_0,z_1,z_2,z_3;t)$, +we wish to find a pair of times $(t_1,t_2)$ such that $w(t_1)=z(t_2)$, +if an intersection exists. First we find the smallest rectangle that +encloses the points $\{w_0,w_1,w_2,w_3\}$ and check that it overlaps +the smallest rectangle that encloses +$\{z_0,z_1,z_2,z_3\}$; if not, the cubics certainly don't intersect. +But if the rectangles do overlap, we bisect the intervals, getting +new cubics $w'$ and~$w''$, $z'$~and~$z''$; the intersection routine first +tries for an intersection between $w'$ and~$z'$, then (if unsuccessful) +between $w'$ and~$z''$, then (if still unsuccessful) between $w''$ and~$z'$, +finally (if thrice unsuccessful) between $w''$ and~$z''$. After $l$~successful +levels of bisection we will have determined the intersection times $t_1$ +and~$t_2$ to $l$~bits of accuracy. + +\def\submin{_{\rm min}} \def\submax{_{\rm max}} +As before, it is better to work with the numbers $W_k=2^l(w_k-w_{k-1})$ +and $Z_k=2^l(z_k-z_{k-1})$ rather than the coefficients $w_k$ and $z_k$ +themselves. We also need one other quantity, $\Delta=2^l(w_0-z_0)$, +to determine when the enclosing rectangles overlap. Here's why: +The $x$~coordinates of~$w(t)$ are between $u\submin$ and $u\submax$, +and the $x$~coordinates of~$z(t)$ are between $x\submin$ and $x\submax$, +if we write $w_k=(u_k,v_k)$ and $z_k=(x_k,y_k)$ and $u\submin= +\min(u_0,u_1,u_2,u_3)$, etc. These intervals of $x$~coordinates +overlap if and only if $u\submin\L x\submax$ and +$x\submin\L u\submax$. Letting +$$U\submin=\min(0,U_1,U_1+U_2,U_1+U_2+U_3),\; + U\submax=\max(0,U_1,U_1+U_2,U_1+U_2+U_3),$$ +we have $2^lu\submin=2^lu_0+U\submin$, etc.; the condition for overlap +reduces to +$$X\submin-U\submax\L 2^l(u_0-x_0)\L X\submax-U\submin.$$ +Thus we want to maintain the quantity $2^l(u_0-x_0)$; similarly, +the quantity $2^l(v_0-y_0)$ accounts for the $y$~coordinates. The +coordinates of $\Delta=2^l(w_0-z_0)$ must stay bounded as $l$ increases, +because of the overlap condition; i.e., we know that $X\submin$, +$X\submax$, and their relatives are bounded, hence $X\submax- +U\submin$ and $X\submin-U\submax$ are bounded. + +@ Incidentally, if the given cubics intersect more than once, the process +just sketched will not necessarily find the lexicographically smallest pair +$(t_1,t_2)$. The solution actually obtained will be smallest in ``shuffled +order''; i.e., if $t_1=(.a_1a_2\ldots a_{16})_2$ and +$t_2=(.b_1b_2\ldots b_{16})_2$, then we will minimize +$a_1b_1a_2b_2\ldots a_{16}b_{16}$, not +$a_1a_2\ldots a_{16}b_1b_2\ldots b_{16}$. +Shuffled order agrees with lexicographic order if all pairs of solutions +$(t_1,t_2)$ and $(t_1',t_2')$ have the property that $t_1= +@!tol_step:0..6; {either 0 or 3, usually} + +@ We shall use an explicit stack to implement the recursive bisection +method described above. In fact, the |bisect_stack| array is available for +this purpose. It will contain numerous 5-word packets like +$(U_1,U_2,U_3,U\submin,U\submax)$, as well as 20-word packets comprising +the 5-word packets for $U$, $V$, $X$, and~$Y$. + +The following macros define the allocation of stack positions to +the quantities needed for bisection-intersection. + +@d stack_1(#)==bisect_stack[#] {$U_1$, $V_1$, $X_1$, or $Y_1$} +@d stack_2(#)==bisect_stack[#+1] {$U_2$, $V_2$, $X_2$, or $Y_2$} +@d stack_3(#)==bisect_stack[#+2] {$U_3$, $V_3$, $X_3$, or $Y_3$} +@d stack_min(#)==bisect_stack[#+3] + {$U\submin$, $V\submin$, $X\submin$, or $Y\submin$} +@d stack_max(#)==bisect_stack[#+4] + {$U\submax$, $V\submax$, $X\submax$, or $Y\submax$} +@d int_packets=20 {number of words to represent $U_k$, $V_k$, $X_k$, and $Y_k$} +@# +@d u_packet(#)==#-5 +@d v_packet(#)==#-10 +@d x_packet(#)==#-15 +@d y_packet(#)==#-20 +@d l_packets==bisect_ptr-int_packets +@d r_packets==bisect_ptr +@d ul_packet==u_packet(l_packets) {base of $U'_k$ variables} +@d vl_packet==v_packet(l_packets) {base of $V'_k$ variables} +@d xl_packet==x_packet(l_packets) {base of $X'_k$ variables} +@d yl_packet==y_packet(l_packets) {base of $Y'_k$ variables} +@d ur_packet==u_packet(r_packets) {base of $U''_k$ variables} +@d vr_packet==v_packet(r_packets) {base of $V''_k$ variables} +@d xr_packet==x_packet(r_packets) {base of $X''_k$ variables} +@d yr_packet==y_packet(r_packets) {base of $Y''_k$ variables} +@# +@d u1l==stack_1(ul_packet) {$U'_1$} +@d u2l==stack_2(ul_packet) {$U'_2$} +@d u3l==stack_3(ul_packet) {$U'_3$} +@d v1l==stack_1(vl_packet) {$V'_1$} +@d v2l==stack_2(vl_packet) {$V'_2$} +@d v3l==stack_3(vl_packet) {$V'_3$} +@d x1l==stack_1(xl_packet) {$X'_1$} +@d x2l==stack_2(xl_packet) {$X'_2$} +@d x3l==stack_3(xl_packet) {$X'_3$} +@d y1l==stack_1(yl_packet) {$Y'_1$} +@d y2l==stack_2(yl_packet) {$Y'_2$} +@d y3l==stack_3(yl_packet) {$Y'_3$} +@d u1r==stack_1(ur_packet) {$U''_1$} +@d u2r==stack_2(ur_packet) {$U''_2$} +@d u3r==stack_3(ur_packet) {$U''_3$} +@d v1r==stack_1(vr_packet) {$V''_1$} +@d v2r==stack_2(vr_packet) {$V''_2$} +@d v3r==stack_3(vr_packet) {$V''_3$} +@d x1r==stack_1(xr_packet) {$X''_1$} +@d x2r==stack_2(xr_packet) {$X''_2$} +@d x3r==stack_3(xr_packet) {$X''_3$} +@d y1r==stack_1(yr_packet) {$Y''_1$} +@d y2r==stack_2(yr_packet) {$Y''_2$} +@d y3r==stack_3(yr_packet) {$Y''_3$} +@# +@d stack_dx==bisect_stack[bisect_ptr] {stacked value of |delx|} +@d stack_dy==bisect_stack[bisect_ptr+1] {stacked value of |dely|} +@d stack_tol==bisect_stack[bisect_ptr+2] {stacked value of |tol|} +@d stack_uv==bisect_stack[bisect_ptr+3] {stacked value of |uv|} +@d stack_xy==bisect_stack[bisect_ptr+4] {stacked value of |xy|} +@d int_increment=int_packets+int_packets+5 {number of stack words per level} + +@= +if int_packets+17*int_increment>bistack_size then bad:=32; + +@ Computation of the min and max is a tedious but fairly fast sequence of +instructions; exactly four comparisons are made in each branch. + +@d set_min_max(#)== + if stack_1(#)<0 then + if stack_3(#)>=0 then + begin if stack_2(#)<0 then stack_min(#):=stack_1(#)+stack_2(#) + else stack_min(#):=stack_1(#); + stack_max(#):=stack_1(#)+stack_2(#)+stack_3(#); + if stack_max(#)<0 then stack_max(#):=0; + end + else begin stack_min(#):=stack_1(#)+stack_2(#)+stack_3(#); + if stack_min(#)>stack_1(#) then stack_min(#):=stack_1(#); + stack_max(#):=stack_1(#)+stack_2(#); + if stack_max(#)<0 then stack_max(#):=0; + end + else if stack_3(#)<=0 then + begin if stack_2(#)>0 then stack_max(#):=stack_1(#)+stack_2(#) + else stack_max(#):=stack_1(#); + stack_min(#):=stack_1(#)+stack_2(#)+stack_3(#); + if stack_min(#)>0 then stack_min(#):=0; + end + else begin stack_max(#):=stack_1(#)+stack_2(#)+stack_3(#); + if stack_max(#)0 then stack_min(#):=0; + end + +@ It's convenient to keep the current values of $l$, $t_1$, and $t_2$ in +the integer form $2^l+2^lt_1$ and $2^l+2^lt_2$. The |cubic_intersection| +routine uses global variables |cur_t| and |cur_tt| for this purpose; +after successful completion, |cur_t| and |cur_tt| will contain |unity| +plus the |scaled| values of $t_1$ and~$t_2$. + +The values of |cur_t| and |cur_tt| will be set to zero if |cubic_intersection| +finds no intersection. The routine gives up and gives an approximate answer +if it has backtracked +more than 5000 times (otherwise there are cases where several minutes +of fruitless computation would be possible). + +@d max_patience=5000 + +@= +@!cur_t,@!cur_tt:integer; {controls and results of |cubic_intersection|} +@!time_to_go:integer; {this many backtracks before giving up} +@!max_t:integer; {maximum of $2^{l+1}$ so far achieved} + +@ The given cubics $B(w_0,w_1,w_2,w_3;t)$ and +$B(z_0,z_1,z_2,z_3;t)$ are specified in adjacent knot nodes |(p,link(p))| +and |(pp,link(pp))|, respectively. + +@p procedure cubic_intersection(@!p,@!pp:pointer); +label continue, not_found, exit; +var @!q,@!qq:pointer; {|link(p)|, |link(pp)|} +begin time_to_go:=max_patience; max_t:=2; +@; +loop@+ begin continue: + if delx-tol<=stack_max(x_packet(xy))-stack_min(u_packet(uv)) then + if delx+tol>=stack_min(x_packet(xy))-stack_max(u_packet(uv)) then + if dely-tol<=stack_max(y_packet(xy))-stack_min(v_packet(uv)) then + if dely+tol>=stack_min(y_packet(xy))-stack_max(v_packet(uv)) then + begin if cur_t>=max_t then + begin if max_t=two then {we've done 17 bisections} + begin cur_t:=half(cur_t+1); cur_tt:=half(cur_tt+1); return; + end; + double(max_t); appr_t:=cur_t; appr_tt:=cur_tt; + end; + @; + goto continue; + end; + if time_to_go>0 then decr(time_to_go) + else begin while appr_t; + end; +exit:end; + +@ The following variables are global, although they are used only by +|cubic_intersection|, because it is necessary on some machines to +split |cubic_intersection| up into two procedures. + +@= +@!delx,@!dely:integer; {the components of $\Delta=2^l(w_0-z_0)$} +@!tol:integer; {bound on the uncertainty in the overlap test} +@!uv,@!xy:0..bistack_size; {pointers to the current packets of interest} +@!three_l:integer; {|tol_step| times the bisection level} +@!appr_t,@!appr_tt:integer; {best approximations known to the answers} + +@ We shall assume that the coordinates are sufficiently non-extreme that +integer overflow will not occur. +@^overflow in arithmetic@> + +@= +q:=link(p); qq:=link(pp); bisect_ptr:=int_packets;@/ +u1r:=right_x(p)-x_coord(p); u2r:=left_x(q)-right_x(p); +u3r:=x_coord(q)-left_x(q); set_min_max(ur_packet);@/ +v1r:=right_y(p)-y_coord(p); v2r:=left_y(q)-right_y(p); +v3r:=y_coord(q)-left_y(q); set_min_max(vr_packet);@/ +x1r:=right_x(pp)-x_coord(pp); x2r:=left_x(qq)-right_x(pp); +x3r:=x_coord(qq)-left_x(qq); set_min_max(xr_packet);@/ +y1r:=right_y(pp)-y_coord(pp); y2r:=left_y(qq)-right_y(pp); +y3r:=y_coord(qq)-left_y(qq); set_min_max(yr_packet);@/ +delx:=x_coord(p)-x_coord(pp); dely:=y_coord(p)-y_coord(pp);@/ +tol:=0; uv:=r_packets; xy:=r_packets; three_l:=0; cur_t:=1; cur_tt:=1 + +@ @= +stack_dx:=delx; stack_dy:=dely; stack_tol:=tol; stack_uv:=uv; stack_xy:=xy; +bisect_ptr:=bisect_ptr+int_increment;@/ +double(cur_t); double(cur_tt);@/ +u1l:=stack_1(u_packet(uv)); u3r:=stack_3(u_packet(uv)); +u2l:=half(u1l+stack_2(u_packet(uv))); +u2r:=half(u3r+stack_2(u_packet(uv))); +u3l:=half(u2l+u2r); u1r:=u3l; +set_min_max(ul_packet); set_min_max(ur_packet);@/ +v1l:=stack_1(v_packet(uv)); v3r:=stack_3(v_packet(uv)); +v2l:=half(v1l+stack_2(v_packet(uv))); +v2r:=half(v3r+stack_2(v_packet(uv))); +v3l:=half(v2l+v2r); v1r:=v3l; +set_min_max(vl_packet); set_min_max(vr_packet);@/ +x1l:=stack_1(x_packet(xy)); x3r:=stack_3(x_packet(xy)); +x2l:=half(x1l+stack_2(x_packet(xy))); +x2r:=half(x3r+stack_2(x_packet(xy))); +x3l:=half(x2l+x2r); x1r:=x3l; +set_min_max(xl_packet); set_min_max(xr_packet);@/ +y1l:=stack_1(y_packet(xy)); y3r:=stack_3(y_packet(xy)); +y2l:=half(y1l+stack_2(y_packet(xy))); +y2r:=half(y3r+stack_2(y_packet(xy))); +y3l:=half(y2l+y2r); y1r:=y3l; +set_min_max(yl_packet); set_min_max(yr_packet);@/ +uv:=l_packets; xy:=l_packets; +double(delx); double(dely);@/ +tol:=tol-three_l+tol_step; double(tol); three_l:=three_l+tol_step + +@ @= +not_found: if odd(cur_tt) then + if odd(cur_t) then @ + else begin incr(cur_t); + delx:=delx+stack_1(u_packet(uv))+stack_2(u_packet(uv)) + +stack_3(u_packet(uv)); + dely:=dely+stack_1(v_packet(uv))+stack_2(v_packet(uv)) + +stack_3(v_packet(uv)); + uv:=uv+int_packets; {switch from |l_packets| to |r_packets|} + decr(cur_tt); xy:=xy-int_packets; {switch from |r_packets| to |l_packets|} + delx:=delx+stack_1(x_packet(xy))+stack_2(x_packet(xy)) + +stack_3(x_packet(xy)); + dely:=dely+stack_1(y_packet(xy))+stack_2(y_packet(xy)) + +stack_3(y_packet(xy)); + end +else begin incr(cur_tt); tol:=tol+three_l; + delx:=delx-stack_1(x_packet(xy))-stack_2(x_packet(xy)) + -stack_3(x_packet(xy)); + dely:=dely-stack_1(y_packet(xy))-stack_2(y_packet(xy)) + -stack_3(y_packet(xy)); + xy:=xy+int_packets; {switch from |l_packets| to |r_packets|} + end + +@ @= +begin cur_t:=half(cur_t); cur_tt:=half(cur_tt); +if cur_t=0 then return; +bisect_ptr:=bisect_ptr-int_increment; three_l:=three_l-tol_step; +delx:=stack_dx; dely:=stack_dy; tol:=stack_tol; uv:=stack_uv; xy:=stack_xy;@/ +goto not_found; +end + +@ The |path_intersection| procedure is much simpler. +It invokes |cubic_intersection| in lexicographic order until finding a +pair of cubics that intersect. The final intersection times are placed in +|cur_t| and~|cur_tt|. + +@p procedure path_intersection(@!h,@!hh:pointer); +label exit; +var @!p,@!pp:pointer; {link registers that traverse the given paths} +@!n,@!nn:integer; {integer parts of intersection times, minus |unity|} +begin @; +tol_step:=0; +repeat n:=-unity; p:=h; + repeat if right_type(p)<>endpoint then + begin nn:=-unity; pp:=hh; + repeat if right_type(pp)<>endpoint then + begin cubic_intersection(p,pp); + if cur_t>0 then + begin cur_t:=cur_t+n; cur_tt:=cur_tt+nn; return; + end; + end; + nn:=nn+unity; pp:=link(pp); + until pp=hh; + end; + n:=n+unity; p:=link(p); + until p=h; +tol_step:=tol_step+3; +until tol_step>3; +cur_t:=-unity; cur_tt:=-unity; +exit:end; + +@ @= +if right_type(h)=endpoint then + begin right_x(h):=x_coord(h); left_x(h):=x_coord(h); + right_y(h):=y_coord(h); left_y(h):=y_coord(h); right_type(h):=explicit; + end; +if right_type(hh)=endpoint then + begin right_x(hh):=x_coord(hh); left_x(hh):=x_coord(hh); + right_y(hh):=y_coord(hh); left_y(hh):=y_coord(hh); right_type(hh):=explicit; + end; + +@* \[27] Online graphic output. +\MF\ displays images on the user's screen by means of a few primitive +operations that are defined below. These operations have deliberately been +kept simple so that they can be implemented without great difficulty on a +wide variety of machines. Since \PASCAL\ has no traditional standards for +graphic output, some system-dependent code needs to be written in order to +support this aspect of \MF; but the necessary routines are usually quite +easy to write. +@^system dependencies@> + +In fact, there are exactly four such routines: + +\yskip\hang +|init_screen| does whatever initialization is necessary to +support the other operations; it is a boolean function that returns +|false| if graphic output cannot be supported (e.g., if the other three +routines have not been written, or if the user doesn't have the +right kind of terminal). + +\yskip\hang +|blank_rectangle| updates a buffer area in memory so that +all pixels in a specified rectangle will be set to the background color. + +\yskip\hang +|paint_row| assigns values to specified pixels in a row of +the buffer just mentioned, based on ``transition'' indices explained below. + +\yskip\hang +|update_screen| displays the current screen buffer; the +effects of |blank_rectangle| and |paint_row| commands may or may not +become visible until the next |update_screen| operation is performed. +(Thus, |update_screen| is analogous to |update_terminal|.) + +\yskip\noindent +The \PASCAL\ code here is a minimum version of |init_screen| and +|update_screen|, usable on \MF\ installations that don't +support screen output. If |init_screen| is changed to return |true| +instead of |false|, the other routines will simply log the fact +that they have been called; they won't really display anything. +The standard test routines for \MF\ use this log information to check +that \MF\ is working properly, but the |wlog| instructions should be +removed from production versions of \MF. + +@p function init_screen:boolean; +begin init_screen:=false; +end; +@# +procedure update_screen; {will be called only if |init_screen| returns |true|} +begin @!init wlog_ln('Calling UPDATESCREEN');@+tini {for testing only} +end; + +@ The user's screen is assumed to be a rectangular area, |screen_width| +pixels wide and |screen_depth| pixels deep. The pixel in the upper left +corner is said to be in column~0 of row~0; the pixel in the lower right +corner is said to be in column |screen_width-1| of row |screen_depth-1|. +Notice that row numbers increase from top to bottom, contrary to \MF's +other coordinates. + +Each pixel is assumed to have two states, referred to in this documentation +as |black| and |white|. The background color is called |white| and the +other color is called |black|; but any two distinct pixel values +can actually be used. For example, the author developed \MF\ on a +system for which |white| was black and |black| was bright green. + +@d white=0 {background pixels} +@d black=1 {visible pixels} + +@= +@!screen_row=0..screen_depth; {a row number on the screen} +@!screen_col=0..screen_width; {a column number on the screen} +@!trans_spec=array[screen_col] of screen_col; {a transition spec, see below} +@!pixel_color=white..black; {specifies one of the two pixel values} + +@ We'll illustrate the |blank_rectangle| and |paint_row| operations by +pretending to declare a screen buffer called |screen_pixel|. This code +is actually commented out, but it does specify the intended effects. + +@= +@{@!screen_pixel:array[screen_row,screen_col] of pixel_color;@+@} + +@ The |blank_rectangle| routine simply whitens all pixels that lie in +columns |left_col| through |right_col-1|, inclusive, of rows +|top_row| through |bot_row-1|, inclusive, given four parameters that satisfy +the relations +$$\hbox{|0<=left_col<=right_col<=screen_width|,\quad + |0<=top_row<=bot_row<=screen_depth|.}$$ +If |left_col=right_col| or |top_row=bot_row|, nothing happens. + +The commented-out code in the following procedure is for illustrative +purposes only. +@^system dependencies@> + +@p procedure blank_rectangle(@!left_col,@!right_col:screen_col; + @!top_row,@!bot_row:screen_row); +var @!r:screen_row; +@!c:screen_col; +begin @{@+for r:=top_row to bot_row-1 do + for c:=left_col to right_col-1 do + screen_pixel[r,c]:=white;@+@}@/ +@!init wlog_cr; {this will be done only after |init_screen=true|} +wlog_ln('Calling BLANKRECTANGLE(',left_col:1,',', + right_col:1,',',top_row:1,',',bot_row:1,')');@+tini +end; + +@ The real work of screen display is done by |paint_row|. But it's not +hard work, because the operation affects only +one of the screen rows, and it affects only a contiguous set of columns +in that row. There are four parameters: |r|~(the row), +|b|~(the initial color), +|a|~(the array of transition specifications), +and |n|~(the number of transitions). The elements of~|a| will satisfy +$$0\L a[0] + +@p procedure paint_row(@!r:screen_row;@!b:pixel_color;var @!a:trans_spec; + @!n:screen_col); +var @!k:screen_col; {an index into |a|} +@!c:screen_col; {an index into |screen_pixel|} +begin @{ k:=0; c:=a[0]; +repeat incr(k); + repeat screen_pixel[r,c]:=b; incr(c); + until c=a[k]; + b:=black-b; {$|black|\swap|white|$} + until k=n;@+@}@/ +@!init wlog('Calling PAINTROW(',r:1,',',b:1,';'); + {this is done only after |init_screen=true|} +for k:=0 to n do + begin wlog(a[k]:1); if k<>n then wlog(','); + end; +wlog_ln(')');@+tini +end; + +@ The remainder of \MF's screen routines are system-independent calls +on the four primitives just defined. + +First we have a global boolean variable that tells if |init_screen| +has been called, and another one that tells if |init_screen| has +given a |true| response. + +@= +@!screen_started:boolean; {have the screen primitives been initialized?} +@!screen_OK:boolean; {is it legitimate to call |blank_rectangle|, + |paint_row|, and |update_screen|?} + +@ @d start_screen==begin if not screen_started then + begin screen_OK:=init_screen; screen_started:=true; + end; + end + +@= +screen_started:=false; screen_OK:=false; + +@ \MF\ provides the user with 16 ``window'' areas on the screen, in each +of which it is possible to produce independent displays. + +It should be noted that \MF's windows aren't really independent +``clickable'' entities in the sense of multi-window graphic workstations; +\MF\ simply maps them into subsets of a single screen image that is +controlled by |init_screen|, |blank_rectangle|, |paint_row|, and +|update_screen| as described above. Implementations of \MF\ on a +multi-window workstation probably therefore make use of only two +windows in the other sense: one for the terminal output and another +for the screen with \MF's 16 areas. Henceforth we shall +use the term window only in \MF's sense. + +@= +@!window_number=0..15; + +@ A user doesn't have to use any of the 16 windows. But when a window is +``opened,'' it is allocated to a specific rectangular portion of the screen +and to a specific rectangle with respect to \MF's coordinates. The relevant +data is stored in global arrays |window_open|, |left_col|, |right_col|, +|top_row|, |bot_row|, |m_window|, and |n_window|. + +The |window_open| array is boolean, and its significance is obvious. The +|left_col|, \dots, |bot_row| arrays contain screen coordinates that +can be used to blank the entire window with |blank_rectangle|. And the +other two arrays just mentioned handle the conversion between +actual coordinates and screen coordinates: \MF's pixel in column~$m$ +of row~$n$ will appear in screen column |m_window+m| and in screen row +|n_window-n|, provided that these lie inside the boundaries of the window. + +Another array |window_time| holds the number of times this window has +been updated. + +@= +@!window_open:array[window_number] of boolean; + {has this window been opened?} +@!left_col:array[window_number] of screen_col; + {leftmost column position on screen} +@!right_col:array[window_number] of screen_col; + {rightmost column position, plus~1} +@!top_row:array[window_number] of screen_row; + {topmost row position on screen} +@!bot_row:array[window_number] of screen_row; + {bottommost row position, plus~1} +@!m_window:array[window_number] of integer; + {offset between user and screen columns} +@!n_window:array[window_number] of integer; + {offset between user and screen rows} +@!window_time:array[window_number] of integer; + {it has been updated this often} + +@ @= +for k:=0 to 15 do + begin window_open[k]:=false; window_time[k]:=0; + end; + +@ Opening a window isn't like opening a file, because you can open it +as often as you like, and you never have to close it again. The idea is +simply to define special points on the current screen display. + +Overlapping window specifications may cause complex effects that can +be understood only by scrutinizing \MF's display algorithms; thus it +has been left undefined in the \MF\ user manual, although the behavior +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> +is in fact predictable. + +Here is a subroutine that implements the command `\&{openwindow}~|k| +\&{from}~$(\\{r0},\\{c0})$ \&{to}~$(\\{r1},\\{c1})$ \&{at}~$(x,y)$'. + +@p procedure open_a_window(@!k:window_number;@!r0,@!c0,@!r1,@!c1:scaled; + @!x,@!y:scaled); +var @!m,@!n:integer; {pixel coordinates} +begin @; +window_open[k]:=true; incr(window_time[k]);@/ +left_col[k]:=c0; right_col[k]:=c1; top_row[k]:=r0; bot_row[k]:=r1;@/ +@; +start_screen; +if screen_OK then + begin blank_rectangle(c0,c1,r0,r1); update_screen; + end; +end; + +@ A window whose coordinates don't fit the existing screen size will be +truncated until they do. + +@= +if r0<0 then r0:=0@+else r0:=round_unscaled(r0); +r1:=round_unscaled(r1); +if r1>screen_depth then r1:=screen_depth; +if r1screen_depth then r0:=r1@+else r1:=r0; +if c0<0 then c0:=0@+else c0:=round_unscaled(c0); +c1:=round_unscaled(c1); +if c1>screen_width then c1:=screen_width; +if c1screen_width then c0:=c1@+else c1:=c0 + +@ Three sets of coordinates are rampant, and they must be kept straight! +(i)~\MF's main coordinates refer to the edges between pixels. (ii)~\MF's +pixel coordinates (within edge structures) say that the pixel bounded by +$(m,n)$, $(m,n+1)$, $(m+1,n)$, and~$(m+1,n+1)$ is in pixel row number~$n$ +and pixel column number~$m$. (iii)~Screen coordinates, on the other hand, +have rows numbered in increasing order from top to bottom, as mentioned +above. +@^coordinates, explained@> + +The program here first computes integers $m$ and $n$ such that +pixel column~$m$ of pixel row~$n$ will be at the upper left corner +of the window. Hence pixel column |m-c0| of pixel row |n+r0| +will be at the upper left corner of the screen. + +@= +m:=round_unscaled(x); n:=round_unscaled(y)-1;@/ +m_window[k]:=c0-m; n_window[k]:=r0+n + +@ Now here comes \MF's most complicated operation related to window +display: Given the number~|k| of an open window, the pixels of positive +weight in |cur_edges| will be shown as |black| in the window; all other +pixels will be shown as |white|. + +@p procedure disp_edges(@!k:window_number); +label done,found; +var @!p,@!q:pointer; {for list manipulation} +@!already_there:boolean; {is a previous incarnation in the window?} +@!r:integer; {row number} +@@; +begin if screen_OK then + if left_col[k]; + p:=link(cur_edges); r:=n_window[k]-(n_min(cur_edges)-zero_field); + while (p<>cur_edges)and(r>=top_row[k]) do + begin if r; + p:=link(p); decr(r); + end; + update_screen; + incr(window_time[k]); + last_window(cur_edges):=k; last_window_time(cur_edges):=window_time[k]; + end; +end; + +@ Since it takes some work to display a row, we try to avoid recomputation +whenever we can. + +@= +begin if unsorted(p)>void then sort_edges(p) +else if unsorted(p)=void then if already_there then goto done; +unsorted(p):=void; {this time we'll paint, but maybe not next time} +@; +paint_row(r,b,row_transition,n); +done: end + +@ The transition-specification parameter to |paint_row| is always the same +array. + +@= +@!row_transition:trans_spec; {an array of |black|/|white| transitions} + +@ The job remaining is to go through the list |sorted(p)|, unpacking the +|info| fields into |m| and weight, then making |black| the pixels whose +accumulated weight~|w| is positive. + +@= +@!n:screen_col; {the highest active index in |row_transition|} +@!w,@!ww:integer; {old and new accumulated weights} +@!b:pixel_color; {status of first pixel in the row transitions} +@!m,@!mm:integer; {old and new screen column positions} +@!d:integer; {edge-and-weight without |min_halfword| compensation} +@!m_adjustment:integer; {conversion between edge and screen coordinates} +@!right_edge:integer; {largest edge-and-weight that could affect the window} +@!min_col:screen_col; {the smallest screen column number in the window} + +@ Some precomputed constants make the display calculations faster. + +@= +m_adjustment:=m_window[k]-m_offset(cur_edges);@/ +right_edge:=8*(right_col[k]-m_adjustment);@/ +min_col:=left_col[k] + +@ @= +n:=0; ww:=0; m:=-1; w:=0; +q:=sorted(p); row_transition[0]:=min_col; +loop@+ begin if q=sentinel then d:=right_edge + else d:=ho(info(q)); + mm:=(d div 8)+m_adjustment; + if mm<>m then + begin @; + m:=mm; w:=ww; + end; + if d>=right_edge then goto found; + ww:=ww+(d mod 8)-zero_w; + q:=link(q); + end; +found:@; + +@ Now |m| is a screen column |= +if w<=0 then + begin if ww>0 then if m>min_col then + begin if n=0 then + if already_there then + begin b:=white; incr(n); + end + else b:=black + else incr(n); + row_transition[n]:=m; + end; + end +else if ww<=0 then if m>min_col then + begin if n=0 then b:=black; + incr(n); row_transition[n]:=m; + end + +@ If the entire row is |white| in the window area, we can omit painting it +when |already_there| is false, since it has already been blanked out in +that case. + +When the following code is invoked, |row_transition[n]| will be +strictly less than |right_col[k]|. + +@= +if already_there or(ww>0) then + begin if n=0 then + if ww>0 then b:=black + else b:=white; + incr(n); row_transition[n]:=right_col[k]; + end +else if n=0 then goto done + +@* \[28] Dynamic linear equations. +\MF\ users define variables implicitly by stating equations that should be +satisfied; the computer is supposed to be smart enough to solve those equations. +And indeed, the computer tries valiantly to do so, by distinguishing five +different types of numeric values: + +\smallskip\hang +|type(p)=known| is the nice case, when |value(p)| is the |scaled| value +of the variable whose address is~|p|. + +\smallskip\hang +|type(p)=dependent| means that |value(p)| is not present, but |dep_list(p)| +points to a {\sl dependency list\/} that expresses the value of variable~|p| +as a |scaled| number plus a sum of independent variables with |fraction| +coefficients. + +\smallskip\hang +|type(p)=independent| means that |value(p)=64s+m|, where |s>0| is a ``serial +number'' reflecting the time this variable was first used in an equation; +also |0<=m<64|, and each dependent variable +that refers to this one is actually referring to the future value of +this variable times~$2^m$. (Usually |m=0|, but higher degrees of +scaling are sometimes needed to keep the coefficients in dependency lists +from getting too large. The value of~|m| will always be even.) + +\smallskip\hang +|type(p)=numeric_type| means that variable |p| hasn't appeared in an +equation before, but it has been explicitly declared to be numeric. + +\smallskip\hang +|type(p)=undefined| means that variable |p| hasn't appeared before. + +\smallskip\noindent +We have actually discussed these five types in the reverse order of their +history during a computation: Once |known|, a variable never again +becomes |dependent|; once |dependent|, it almost never again becomes +|independent|; once |independent|, it never again becomes |numeric_type|; +and once |numeric_type|, it never again becomes |undefined| (except +of course when the user specifically decides to scrap the old value +and start again). A backward step may, however, take place: Sometimes +a |dependent| variable becomes |independent| again, when one of the +independent variables it depends on is reverting to |undefined|. + +@d s_scale=64 {the serial numbers are multiplied by this factor} +@d new_indep(#)== {create a new independent variable} + begin if serial_no>el_gordo-s_scale then + overflow("independent variables",serial_no div s_scale); +@:METAFONT capacity exceeded independent variables}{\quad independent variables@> + type(#):=independent; serial_no:=serial_no+s_scale; + value(#):=serial_no; + end + +@= +@!serial_no:integer; {the most recent serial number, times |s_scale|} + +@ @=new_indep(q+s) + +@ But how are dependency lists represented? It's simple: The linear combination +$\alpha_1v_1+\cdots+\alpha_kv_k+\beta$ appears in |k+1| value nodes. If +|q=dep_list(p)| points to this list, and if |k>0|, then |value(q)= +@t$\alpha_1$@>| (which is a |fraction|); |info(q)| points to the location +of $v_1$; and |link(p)| points to the dependency list +$\alpha_2v_2+\cdots+\alpha_kv_k+\beta$. On the other hand if |k=0|, +then |value(q)=@t$\beta$@>| (which is |scaled|) and |info(q)=null|. +The independent variables $v_1$, \dots,~$v_k$ have been sorted so that +they appear in decreasing order of their |value| fields (i.e., of +their serial numbers). \ (It is convenient to use decreasing order, +since |value(null)=0|. If the independent variables were not sorted by +serial number but by some other criterion, such as their location in |mem|, +the equation-solving mechanism would be too system-dependent, because +the ordering can affect the computed results.) + +The |link| field in the node that contains the constant term $\beta$ is +called the {\sl final link\/} of the dependency list. \MF\ maintains +a doubly-linked master list of all dependency lists, in terms of a permanently +allocated node +in |mem| called |dep_head|. If there are no dependencies, we have +|link(dep_head)=dep_head| and |prev_dep(dep_head)=dep_head|; +otherwise |link(dep_head)| points to the first dependent variable, say~|p|, +and |prev_dep(p)=dep_head|. We have |type(p)=dependent|, and |dep_list(p)| +points to its dependency list. If the final link of that dependency list +occurs in location~|q|, then |link(q)| points to the next dependent +variable (say~|r|); and we have |prev_dep(r)=q|, etc. + +@d dep_list(#)==link(value_loc(#)) + {half of the |value| field in a |dependent| variable} +@d prev_dep(#)==info(value_loc(#)) + {the other half; makes a doubly linked list} +@d dep_node_size=2 {the number of words per dependency node} + +@= serial_no:=0; +link(dep_head):=dep_head; prev_dep(dep_head):=dep_head; +info(dep_head):=null; dep_list(dep_head):=null; + +@ Actually the description above contains a little white lie. There's +another kind of variable called |proto_dependent|, which is +just like a |dependent| one except that the $\alpha$ coefficients +in its dependency list are |scaled| instead of being fractions. +Proto-dependency lists are mixed with dependency lists in the +nodes reachable from |dep_head|. + +@ Here is a procedure that prints a dependency list in symbolic form. +The second parameter should be either |dependent| or |proto_dependent|, +to indicate the scaling of the coefficients. + +@= +procedure print_dependency(@!p:pointer;@!t:small_number); +label exit; +var @!v:integer; {a coefficient} +@!pp,@!q:pointer; {for list manipulation} +begin pp:=p; +loop@+ begin v:=abs(value(p)); q:=info(p); + if q=null then {the constant term} + begin if (v<>0)or(p=pp) then + begin if value(p)>0 then if p<>pp then print_char("+"); + print_scaled(value(p)); + end; + return; + end; + @; + if type(q)<>independent then confusion("dep"); +@:this can't happen dep}{\quad dep@> + print_variable_name(q); v:=value(q) mod s_scale; + while v>0 do + begin print("*4"); v:=v-2; + end; + p:=link(p); + end; +exit:end; + +@ @= +if value(p)<0 then print_char("-") +else if p<>pp then print_char("+"); +if t=dependent then v:=round_fraction(v); +if v<>unity then print_scaled(v) + +@ The maximum absolute value of a coefficient in a given dependency list +is returned by the following simple function. + +@p function max_coef(@!p:pointer):fraction; +var @!x:fraction; {the maximum so far} +begin x:=0; +while info(p)<>null do + begin if abs(value(p))>x then x:=abs(value(p)); + p:=link(p); + end; +max_coef:=x; +end; + +@ One of the main operations needed on dependency lists is to add a multiple +of one list to the other; we call this |p_plus_fq|, where |p| and~|q| point +to dependency lists and |f| is a fraction. + +If the coefficient of any independent variable becomes |coef_bound| or +more, in absolute value, this procedure changes the type of that variable +to `|independent_needing_fix|', and sets the global variable |fix_needed| +to~|true|. The value of $|coef_bound|=\mu$ is chosen so that +$\mu^2+\mu<8$; this means that the numbers we deal with won't +get too large. (Instead of the ``optimum'' $\mu=(\sqrt{33}-1)/2\approx +2.3723$, the safer value 7/3 is taken as the threshold.) + +The changes mentioned in the preceding paragraph are actually done only if +the global variable |watch_coefs| is |true|. But it usually is; in fact, +it is |false| only when \MF\ is making a dependency list that will soon +be equated to zero. + +Several procedures that act on dependency lists, including |p_plus_fq|, +set the global variable |dep_final| to the final (constant term) node of +the dependency list that they produce. + +@d coef_bound==@'4525252525 {|fraction| approximation to 7/3} +@d independent_needing_fix=0 + +@= +@!fix_needed:boolean; {does at least one |independent| variable need scaling?} +@!watch_coefs:boolean; {should we scale coefficients that exceed |coef_bound|?} +@!dep_final:pointer; {location of the constant term and final link} + +@ @= +fix_needed:=false; watch_coefs:=true; + +@ The |p_plus_fq| procedure has a fourth parameter, |t|, that should be +set to |proto_dependent| if |p| is a proto-dependency list. In this +case |f| will be |scaled|, not a |fraction|. Similarly, the fifth parameter~|tt| +should be |proto_dependent| if |q| is a proto-dependency list. + +List |q| is unchanged by the operation; but list |p| is totally destroyed. + +The final link of the dependency list or proto-dependency list returned +by |p_plus_fq| is the same as the original final link of~|p|. Indeed, the +constant term of the result will be located in the same |mem| location +as the original constant term of~|p|. + +Coefficients of the result are assumed to be zero if they are less than +a certain threshold. This compensates for inevitable rounding errors, +and tends to make more variables `|known|'. The threshold is approximately +$10^{-5}$ in the case of normal dependency lists, $10^{-4}$ for +proto-dependencies. + +@d fraction_threshold=2685 {a |fraction| coefficient less than this is zeroed} +@d half_fraction_threshold=1342 {half of |fraction_threshold|} +@d scaled_threshold=8 {a |scaled| coefficient less than this is zeroed} +@d half_scaled_threshold=4 {half of |scaled_threshold|} + +@= +function p_plus_fq(@!p:pointer;@!f:integer;@!q:pointer; + @!t,@!tt:small_number):pointer; +label done; +var @!pp,@!qq:pointer; {|info(p)| and |info(q)|, respectively} +@!r,@!s:pointer; {for list manipulation} +@!threshold:integer; {defines a neighborhood of zero} +@!v:integer; {temporary register} +begin if t=dependent then threshold:=fraction_threshold +else threshold:=scaled_threshold; +r:=temp_head; pp:=info(p); qq:=info(q); +loop@+ if pp=qq then + if pp=null then goto done + else @ + else if value(pp) + else begin link(r):=p; r:=p; p:=link(p); pp:=info(p); + end; +done: if t=dependent then + value(p):=slow_add(value(p),take_fraction(value(q),f)) +else value(p):=slow_add(value(p),take_scaled(value(q),f)); +link(r):=p; dep_final:=p; p_plus_fq:=link(temp_head); +end; + +@ @= +begin if tt=dependent then v:=value(p)+take_fraction(f,value(q)) +else v:=value(p)+take_scaled(f,value(q)); +value(p):=v; s:=p; p:=link(p); +if abs(v)=coef_bound then if watch_coefs then + begin type(qq):=independent_needing_fix; fix_needed:=true; + end; + link(r):=s; r:=s; + end; +pp:=info(p); q:=link(q); qq:=info(q); +end + +@ @= +begin if tt=dependent then v:=take_fraction(f,value(q)) +else v:=take_scaled(f,value(q)); +if abs(v)>half(threshold) then + begin s:=get_node(dep_node_size); info(s):=qq; value(s):=v; + if abs(v)>=coef_bound then if watch_coefs then + begin type(qq):=independent_needing_fix; fix_needed:=true; + end; + link(r):=s; r:=s; + end; +q:=link(q); qq:=info(q); +end + +@ It is convenient to have another subroutine for the special case +of |p_plus_fq| when |f=1.0|. In this routine lists |p| and |q| are +both of the same type~|t| (either |dependent| or |proto_dependent|). + +@p function p_plus_q(@!p:pointer;@!q:pointer;@!t:small_number):pointer; +label done; +var @!pp,@!qq:pointer; {|info(p)| and |info(q)|, respectively} +@!r,@!s:pointer; {for list manipulation} +@!threshold:integer; {defines a neighborhood of zero} +@!v:integer; {temporary register} +begin if t=dependent then threshold:=fraction_threshold +else threshold:=scaled_threshold; +r:=temp_head; pp:=info(p); qq:=info(q); +loop@+ if pp=qq then + if pp=null then goto done + else @ + else if value(pp)= +begin v:=value(p)+value(q); +value(p):=v; s:=p; p:=link(p); pp:=info(p); +if abs(v)=coef_bound then if watch_coefs then + begin type(qq):=independent_needing_fix; fix_needed:=true; + end; + link(r):=s; r:=s; + end; +q:=link(q); qq:=info(q); +end + +@ A somewhat simpler routine will multiply a dependency list +by a given constant~|v|. The constant is either a |fraction| less than +|fraction_one|, or it is |scaled|. In the latter case we might be forced to +convert a dependency list to a proto-dependency list. +Parameters |t0| and |t1| are the list types before and after; +they should agree unless |t0=dependent| and |t1=proto_dependent| +and |v_is_scaled=true|. + +@p function p_times_v(@!p:pointer;@!v:integer; + @!t0,@!t1:small_number;@!v_is_scaled:boolean):pointer; +var @!r,@!s:pointer; {for list manipulation} +@!w:integer; {tentative coefficient} +@!threshold:integer; +@!scaling_down:boolean; +begin if t0<>t1 then scaling_down:=true@+else scaling_down:=not v_is_scaled; +if t1=dependent then threshold:=half_fraction_threshold +else threshold:=half_scaled_threshold; +r:=temp_head; +while info(p)<>null do + begin if scaling_down then w:=take_fraction(v,value(p)) + else w:=take_scaled(v,value(p)); + if abs(w)<=threshold then + begin s:=link(p); free_node(p,dep_node_size); p:=s; + end + else begin if abs(w)>=coef_bound then + begin fix_needed:=true; type(info(p)):=independent_needing_fix; + end; + link(r):=p; r:=p; value(p):=w; p:=link(p); + end; + end; +link(r):=p; +if v_is_scaled then value(p):=take_scaled(value(p),v) +else value(p):=take_fraction(value(p),v); +p_times_v:=link(temp_head); +end; + +@ Similarly, we sometimes need to divide a dependency list +by a given |scaled| constant. + +@= +function p_over_v(@!p:pointer;@!v:scaled; + @!t0,@!t1:small_number):pointer; +var @!r,@!s:pointer; {for list manipulation} +@!w:integer; {tentative coefficient} +@!threshold:integer; +@!scaling_down:boolean; +begin if t0<>t1 then scaling_down:=true@+else scaling_down:=false; +if t1=dependent then threshold:=half_fraction_threshold +else threshold:=half_scaled_threshold; +r:=temp_head; +while info(p)<>null do + begin if scaling_down then + if abs(v)<@'2000000 then w:=make_scaled(value(p),v*@'10000) + else w:=make_scaled(round_fraction(value(p)),v) + else w:=make_scaled(value(p),v); + if abs(w)<=threshold then + begin s:=link(p); free_node(p,dep_node_size); p:=s; + end + else begin if abs(w)>=coef_bound then + begin fix_needed:=true; type(info(p)):=independent_needing_fix; + end; + link(r):=p; r:=p; value(p):=w; p:=link(p); + end; + end; +link(r):=p; value(p):=make_scaled(value(p),v); +p_over_v:=link(temp_head); +end; + +@ Here's another utility routine for dependency lists. When an independent +variable becomes dependent, we want to remove it from all existing +dependencies. The |p_with_x_becoming_q| function computes the +dependency list of~|p| after variable~|x| has been replaced by~|q|. + +This procedure has basically the same calling conventions as |p_plus_fq|: +List~|q| is unchanged; list~|p| is destroyed; the constant node and the +final link are inherited from~|p|; and the fourth parameter tells whether +or not |p| is |proto_dependent|. However, the global variable |dep_final| +is not altered if |x| does not occur in list~|p|. + +@p function p_with_x_becoming_q(@!p,@!x,@!q:pointer;@!t:small_number):pointer; +var @!r,@!s:pointer; {for list manipulation} +@!v:integer; {coefficient of |x|} +@!sx:integer; {serial number of |x|} +begin s:=p; r:=temp_head; sx:=value(x); +while value(info(s))>sx do + begin r:=s; s:=link(s); + end; +if info(s)<>x then p_with_x_becoming_q:=p +else begin link(temp_head):=p; link(r):=link(s); v:=value(s); + free_node(s,dep_node_size); + p_with_x_becoming_q:=p_plus_fq(link(temp_head),v,q,t,dependent); + end; +end; + +@ Here's a simple procedure that reports an error when a variable +has just received a known value that's out of the required range. + +@= +procedure val_too_big(@!x:scaled); +begin if internal[warning_check]>0 then + begin print_err("Value is too large ("); print_scaled(x); print_char(")"); +@.Value is too large@> + help4("The equation I just processed has given some variable")@/ + ("a value of 4096 or more. Continue and I'll try to cope")@/ + ("with that big value; but it might be dangerous.")@/ + ("(Set warningcheck:=0 to suppress this message.)"); + error; + end; +end; + +@ When a dependent variable becomes known, the following routine +removes its dependency list. Here |p| points to the variable, and +|q| points to the dependency list (which is one node long). + +@= +procedure make_known(@!p,@!q:pointer); +var @!t:dependent..proto_dependent; {the previous type} +begin prev_dep(link(q)):=prev_dep(p); +link(prev_dep(p)):=link(q); t:=type(p); +type(p):=known; value(p):=value(q); free_node(q,dep_node_size); +if abs(value(p))>=fraction_one then val_too_big(value(p)); +if internal[tracing_equations]>0 then if interesting(p) then + begin begin_diagnostic; print_nl("#### "); +@:]]]\#\#\#\#_}{\.{\#\#\#\#}@> + print_variable_name(p); print_char("="); print_scaled(value(p)); + end_diagnostic(false); + end; +if cur_exp=p then if cur_type=t then + begin cur_type:=known; cur_exp:=value(p); + free_node(p,value_node_size); + end; +end; + +@ The |fix_dependencies| routine is called into action when |fix_needed| +has been triggered. The program keeps a list~|s| of independent variables +whose coefficients must be divided by~4. + +In unusual cases, this fixup process might reduce one or more coefficients +to zero, so that a variable will become known more or less by default. + +@= +procedure fix_dependencies; +label done; +var @!p,@!q,@!r,@!s,@!t:pointer; {list manipulation registers} +@!x:pointer; {an independent variable} +begin r:=link(dep_head); s:=null; +while r<>dep_head do + begin t:=r; + @; + r:=link(q); + if q=dep_list(t) then make_known(t,q); + end; +while s<>null do + begin p:=link(s); x:=info(s); free_avail(s); s:=p; + type(x):=independent; value(x):=value(x)+2; + end; +fix_needed:=false; +end; + +@ @d independent_being_fixed=1 {this variable already appears in |s|} + +@= +r:=value_loc(t); {|link(r)=dep_list(t)|} +loop@+ begin q:=link(r); x:=info(q); + if x=null then goto done; + if type(x)<=independent_being_fixed then + begin if type(x)28 then single_dependency:=const_dependency(0) +else begin q:=get_node(dep_node_size); + value(q):=two_to_the[28-m]; info(q):=p;@/ + link(q):=const_dependency(0); single_dependency:=q; + end; +end; + +@ We sometimes need to make an exact copy of a dependency list. + +@p function copy_dep_list(@!p:pointer):pointer; +label done; +var @!q:pointer; {the new dependency list} +begin q:=get_node(dep_node_size); dep_final:=q; +loop@+ begin info(dep_final):=info(p); value(dep_final):=value(p); + if info(dep_final)=null then goto done; + link(dep_final):=get_node(dep_node_size); + dep_final:=link(dep_final); p:=link(p); + end; +done:copy_dep_list:=q; +end; + +@ But how do variables normally become known? Ah, now we get to the heart of the +equation-solving mechanism. The |linear_eq| procedure is given a |dependent| +or |proto_dependent| list,~|p|, in which at least one independent variable +appears. It equates this list to zero, by choosing an independent variable +with the largest coefficient and making it dependent on the others. The +newly dependent variable is eliminated from all current dependencies, +thereby possibly making other dependent variables known. + +The given list |p| is, of course, totally destroyed by all this processing. + +@p procedure linear_eq(@!p:pointer;@!t:small_number); +var @!q,@!r,@!s:pointer; {for link manipulation} +@!x:pointer; {the variable that loses its independence} +@!n:integer; {the number of times |x| had been halved} +@!v:integer; {the coefficient of |x| in list |p|} +@!prev_r:pointer; {lags one step behind |r|} +@!final_node:pointer; {the constant term of the new dependency list} +@!w:integer; {a tentative coefficient} +begin @; +x:=info(q); n:=value(x) mod s_scale;@/ +@; +if internal[tracing_equations]>0 then @; +@; +@; +if fix_needed then fix_dependencies; +end; + +@ @= +q:=p; r:=link(p); v:=value(q); +while info(r)<>null do + begin if abs(value(r))>abs(v) then + begin q:=r; v:=value(r); + end; + r:=link(r); + end + +@ Here we want to change the coefficients from |scaled| to |fraction|, +except in the constant term. In the common case of a trivial equation +like `\.{x=3.14}', we will have |v=-fraction_one|, |q=p|, and |t=dependent|. + +@= +s:=temp_head; link(s):=p; r:=p; +repeat if r=q then + begin link(s):=link(r); free_node(r,dep_node_size); + end +else begin w:=make_fraction(value(r),v); + if abs(w)<=half_fraction_threshold then + begin link(s):=link(r); free_node(r,dep_node_size); + end + else begin value(r):=-w; s:=r; + end; + end; +r:=link(s); +until info(r)=null; +if t=proto_dependent then value(r):=-make_scaled(value(r),v) +else if v<>-fraction_one then value(r):=-make_fraction(value(r),v); +final_node:=r; p:=link(temp_head) + +@ @= +if interesting(x) then + begin begin_diagnostic; print_nl("## "); print_variable_name(x); +@:]]]\#\#_}{\.{\#\#}@> + w:=n; + while w>0 do + begin print("*4"); w:=w-2; + end; + print_char("="); print_dependency(p,dependent); end_diagnostic(false); + end + +@ @= +prev_r:=dep_head; r:=link(dep_head); +while r<>dep_head do + begin s:=dep_list(r); q:=p_with_x_becoming_q(s,x,p,type(r)); + if info(q)=null then make_known(r,q) + else begin dep_list(r):=q; + repeat q:=link(q); + until info(q)=null; + prev_r:=q; + end; + r:=link(prev_r); + end + +@ @= +if n>0 then @; +if info(p)=null then + begin type(x):=known; + value(x):=value(p); + if abs(value(x))>=fraction_one then val_too_big(value(x)); + free_node(p,dep_node_size); + if cur_exp=x then if cur_type=independent then + begin cur_exp:=value(x); cur_type:=known; + free_node(x,value_node_size); + end; + end +else begin type(x):=dependent; dep_final:=final_node; new_dep(x,p); + if cur_exp=x then if cur_type=independent then cur_type:=dependent; + end + +@ @= +begin s:=temp_head; link(temp_head):=p; r:=p; +repeat if n>30 then w:=0 +else w:=value(r) div two_to_the[n]; +if (abs(w)<=half_fraction_threshold)and(info(r)<>null) then + begin link(s):=link(r); + free_node(r,dep_node_size); + end +else begin value(r):=w; s:=r; + end; +r:=link(s); +until info(s)=null; +p:=link(temp_head); +end + +@ The |check_mem| procedure, which is used only when \MF\ is being +debugged, makes sure that the current dependency lists are well formed. + +@= +q:=dep_head; p:=link(q); +while p<>dep_head do + begin if prev_dep(p)<>q then + begin print_nl("Bad PREVDEP at "); print_int(p); +@.Bad PREVDEP...@> + end; + p:=dep_list(p); r:=inf_val; + repeat if value(info(p))>=value(r) then + begin print_nl("Out of order at "); print_int(p); +@.Out of order...@> + end; + r:=info(p); q:=p; p:=link(q); + until r=null; + end + +@* \[29] Dynamic nonlinear equations. +Variables of numeric type are maintained by the general scheme of +independent, dependent, and known values that we have just studied; +and the components of pair and transform variables are handled in the +same way. But \MF\ also has five other types of values: \&{boolean}, +\&{string}, \&{pen}, \&{path}, and \&{picture}; what about them? + +Equations are allowed between nonlinear quantities, but only in a +simple form. Two variables that haven't yet been assigned values are +either equal to each other, or they're not. + +Before a boolean variable has received a value, its type is |unknown_boolean|; +similarly, there are variables whose type is |unknown_string|, |unknown_pen|, +|unknown_path|, and |unknown_picture|. In such cases the value is either +|null| (which means that no other variables are equivalent to this one), or +it points to another variable of the same undefined type. The pointers in the +latter case form a cycle of nodes, which we shall call a ``ring.'' +Rings of undefined variables may include capsules, which arise as +intermediate results within expressions or as \&{expr} parameters to macros. + +When one member of a ring receives a value, the same value is given to +all the other members. In the case of paths and pictures, this implies +making separate copies of a potentially large data structure; users should +restrain their enthusiasm for such generality, unless they have lots and +lots of memory space. + +@ The following procedure is called when a capsule node is being +added to a ring (e.g., when an unknown variable is mentioned in an expression). + +@p function new_ring_entry(@!p:pointer):pointer; +var q:pointer; {the new capsule node} +begin q:=get_node(value_node_size); name_type(q):=capsule; +type(q):=type(p); +if value(p)=null then value(q):=p@+else value(q):=value(p); +value(p):=q; +new_ring_entry:=q; +end; + +@ Conversely, we might delete a capsule or a variable before it becomes known. +The following procedure simply detaches a quantity from its ring, +without recycling the storage. + +@= +procedure ring_delete(@!p:pointer); +var @!q:pointer; +begin q:=value(p); +if q<>null then if q<>p then + begin while value(q)<>p do q:=value(q); + value(q):=value(p); + end; +end; + +@ Eventually there might be an equation that assigns values to all of the +variables in a ring. The |nonlinear_eq| subroutine does the necessary +propagation of values. + +If the parameter |flush_p| is |true|, node |p| itself needn't receive a +value; it will soon be recycled. + +@p procedure nonlinear_eq(@!v:integer;@!p:pointer;@!flush_p:boolean); +var @!t:small_number; {the type of ring |p|} +@!q,@!r:pointer; {link manipulation registers} +begin t:=type(p)-unknown_tag; q:=value(p); +if flush_p then type(p):=vacuous@+else p:=q; +repeat r:=value(q); type(q):=t; +case t of +boolean_type: value(q):=v; +string_type: begin value(q):=v; add_str_ref(v); + end; +pen_type: begin value(q):=v; add_pen_ref(v); + end; +path_type: value(q):=copy_path(v); +picture_type: value(q):=copy_edges(v); +end; {there ain't no more cases} +q:=r; +until q=p; +end; + +@ If two members of rings are equated, and if they have the same type, +the |ring_merge| procedure is called on to make them equivalent. + +@p procedure ring_merge(@!p,@!q:pointer); +label exit; +var @!r:pointer; {traverses one list} +begin r:=value(p); +while r<>p do + begin if r=q then + begin @; + return; + end; + r:=value(r); + end; +r:=value(p); value(p):=value(q); value(q):=r; +exit:end; + +@ @= +begin print_err("Redundant equation");@/ +@.Redundant equation@> +help2("I already knew that this equation was true.")@/ + ("But perhaps no harm has been done; let's continue.");@/ +put_get_error; +end + +@* \[30] Introduction to the syntactic routines. +Let's pause a moment now and try to look at the Big Picture. +The \MF\ program consists of three main parts: syntactic routines, +semantic routines, and output routines. The chief purpose of the +syntactic routines is to deliver the user's input to the semantic routines, +while parsing expressions and locating operators and operands. The +semantic routines act as an interpreter responding to these operators, +which may be regarded as commands. And the output routines are +periodically called on to produce compact font descriptions that can be +used for typesetting or for making interim proof drawings. We have +discussed the basic data structures and many of the details of semantic +operations, so we are good and ready to plunge into the part of \MF\ that +actually controls the activities. + +Our current goal is to come to grips with the |get_next| procedure, +which is the keystone of \MF's input mechanism. Each call of |get_next| +sets the value of three variables |cur_cmd|, |cur_mod|, and |cur_sym|, +representing the next input token. +$$\vbox{\halign{#\hfil\cr + \hbox{|cur_cmd| denotes a command code from the long list of codes + given earlier;}\cr + \hbox{|cur_mod| denotes a modifier of the command code;}\cr + \hbox{|cur_sym| is the hash address of the symbolic token that was + just scanned,}\cr + \hbox{\qquad or zero in the case of a numeric or string + or capsule token.}\cr}}$$ +Underlying this external behavior of |get_next| is all the machinery +necessary to convert from character files to tokens. At a given time we +may be only partially finished with the reading of several files (for +which \&{input} was specified), and partially finished with the expansion +of some user-defined macros and/or some macro parameters, and partially +finished reading some text that the user has inserted online, +and so on. When reading a character file, the characters must be +converted to tokens; comments and blank spaces must +be removed, numeric and string tokens must be evaluated. + +To handle these situations, which might all be present simultaneously, +\MF\ uses various stacks that hold information about the incomplete +activities, and there is a finite state control for each level of the +input mechanism. These stacks record the current state of an implicitly +recursive process, but the |get_next| procedure is not recursive. + +@= +@!cur_cmd: eight_bits; {current command set by |get_next|} +@!cur_mod: integer; {operand of current command} +@!cur_sym: halfword; {hash address of current symbol} + +@ The |print_cmd_mod| routine prints a symbolic interpretation of a +command code and its modifier. +It consists of a rather tedious sequence of print +commands, and most of it is essentially an inverse to the |primitive| +routine that enters a \MF\ primitive into |hash| and |eqtb|. Therefore almost +all of this procedure appears elsewhere in the program, together with the +corresponding |primitive| calls. + +@= +procedure print_cmd_mod(@!c,@!m:integer); +begin case c of +@t\4@>@@/ +othercases print("[unknown command code!]") +endcases; +end; + +@ Here is a procedure that displays a given command in braces, in the +user's transcript file. + +@d show_cur_cmd_mod==show_cmd_mod(cur_cmd,cur_mod) + +@p procedure show_cmd_mod(@!c,@!m:integer); +begin begin_diagnostic; print_nl("{"); +print_cmd_mod(c,m); print_char("}"); +end_diagnostic(false); +end; + +@* \[31] Input stacks and states. +The state of \MF's input mechanism appears in the input stack, whose +entries are records with five fields, called |index|, |start|, |loc|, +|limit|, and |name|. The top element of this stack is maintained in a +global variable for which no subscripting needs to be done; the other +elements of the stack appear in an array. Hence the stack is declared thus: + +@= +@!in_state_record = record + @!index_field: quarterword; + @!start_field,@!loc_field, @!limit_field, @!name_field: halfword; + end; + +@ @= +@!input_stack : array[0..stack_size] of in_state_record; +@!input_ptr : 0..stack_size; {first unused location of |input_stack|} +@!max_in_stack: 0..stack_size; {largest value of |input_ptr| when pushing} +@!cur_input : in_state_record; {the ``top'' input state} + +@ We've already defined the special variable |@!loc==cur_input.loc_field| +in our discussion of basic input-output routines. The other components of +|cur_input| are defined in the same way: + +@d index==cur_input.index_field {reference for buffer information} +@d start==cur_input.start_field {starting position in |buffer|} +@d limit==cur_input.limit_field {end of current line in |buffer|} +@d name==cur_input.name_field {name of the current file} + +@ Let's look more closely now at the five control variables +(|index|,~|start|,~|loc|,~|limit|,~|name|), +assuming that \MF\ is reading a line of characters that have been input +from some file or from the user's terminal. There is an array called +|buffer| that acts as a stack of all lines of characters that are +currently being read from files, including all lines on subsidiary +levels of the input stack that are not yet completed. \MF\ will return to +the other lines when it is finished with the present input file. + +(Incidentally, on a machine with byte-oriented addressing, it would be +appropriate to combine |buffer| with the |str_pool| array, +letting the buffer entries grow downward from the top of the string pool +and checking that these two tables don't bump into each other.) + +The line we are currently working on begins in position |start| of the +buffer; the next character we are about to read is |buffer[loc]|; and +|limit| is the location of the last character present. We always have +|loc<=limit|. For convenience, |buffer[limit]| has been set to |"%"|, so +that the end of a line is easily sensed. + +The |name| variable is a string number that designates the name of +the current file, if we are reading a text file. It is 0 if we +are reading from the terminal for normal input, or 1 if we are executing a +\&{readstring} command, or 2 if we are reading a string that was +moved into the buffer by \&{scantokens}. + +@ Additional information about the current line is available via the +|index| variable, which counts how many lines of characters are present +in the buffer below the current level. We have |index=0| when reading +from the terminal and prompting the user for each line; then if the user types, +e.g., `\.{input font}', we will have |index=1| while reading +the file \.{font.mf}. However, it does not follow that |index| is the +same as the input stack pointer, since many of the levels on the input +stack may come from token lists. + +The global variable |in_open| is equal to the |index| +value of the highest non-token-list level. Thus, the number of partially read +lines in the buffer is |in_open+1|, and we have |in_open=index| +when we are not reading a token list. + +If we are not currently reading from the terminal, +we are reading from the file variable |input_file[index]|. We use +the notation |terminal_input| as a convenient abbreviation for |name=0|, +and |cur_file| as an abbreviation for |input_file[index]|. + +The global variable |line| contains the line number in the topmost +open file, for use in error messages. If we are not reading from +the terminal, |line_stack[index]| holds the line number for the +enclosing level, so that |line| can be restored when the current +file has been read. + +If more information about the input state is needed, it can be +included in small arrays like those shown here. For example, +the current page or segment number in the input file might be +put into a variable |@!page|, maintained for enclosing levels in +`\ignorespaces|@!page_stack:array[1..max_in_open] of integer|\unskip' +by analogy with |line_stack|. +@^system dependencies@> + +@d terminal_input==(name=0) {are we reading from the terminal?} +@d cur_file==input_file[index] {the current |alpha_file| variable} + +@= +@!in_open : 0..max_in_open; {the number of lines in the buffer, less one} +@!open_parens : 0..max_in_open; {the number of open text files} +@!input_file : array[1..max_in_open] of alpha_file; +@!line : integer; {current line number in the current source file} +@!line_stack : array[1..max_in_open] of integer; + +@ However, all this discussion about input state really applies only to the +case that we are inputting from a file. There is another important case, +namely when we are currently getting input from a token list. In this case +|index>max_in_open|, and the conventions about the other state variables +are different: + +\yskip\hang|loc| is a pointer to the current node in the token list, i.e., +the node that will be read next. If |loc=null|, the token list has been +fully read. + +\yskip\hang|start| points to the first node of the token list; this node +may or may not contain a reference count, depending on the type of token +list involved. + +\yskip\hang|token_type|, which takes the place of |index| in the +discussion above, is a code number that explains what kind of token list +is being scanned. + +\yskip\hang|name| points to the |eqtb| address of the control sequence +being expanded, if the current token list is a macro not defined by +\&{vardef}. Macros defined by \&{vardef} have |name=null|; their name +can be deduced by looking at their first two parameters. + +\yskip\hang|param_start|, which takes the place of |limit|, tells where +the parameters of the current macro or loop text begin in the |param_stack|. + +\yskip\noindent The |token_type| can take several values, depending on +where the current token list came from: + +\yskip +\indent|forever_text|, if the token list being scanned is the body of +a \&{forever} loop; + +\indent|loop_text|, if the token list being scanned is the body of +a \&{for} or \&{forsuffixes} loop; + +\indent|parameter|, if a \&{text} or \&{suffix} parameter is being scanned; + +\indent|backed_up|, if the token list being scanned has been inserted as +`to be read again'. + +\indent|inserted|, if the token list being scanned has been inserted as +part of error recovery; + +\indent|macro|, if the expansion of a user-defined symbolic token is being +scanned. + +\yskip\noindent +The token list begins with a reference count if and only if |token_type= +macro|. +@^reference counts@> + +@d token_type==index {type of current token list} +@d token_state==(index>max_in_open) {are we scanning a token list?} +@d file_state==(index<=max_in_open) {are we scanning a file line?} +@d param_start==limit {base of macro parameters in |param_stack|} +@d forever_text=max_in_open+1 {|token_type| code for loop texts} +@d loop_text=max_in_open+2 {|token_type| code for loop texts} +@d parameter=max_in_open+3 {|token_type| code for parameter texts} +@d backed_up=max_in_open+4 {|token_type| code for texts to be reread} +@d inserted=max_in_open+5 {|token_type| code for inserted texts} +@d macro=max_in_open+6 {|token_type| code for macro replacement texts} + +@ The |param_stack| is an auxiliary array used to hold pointers to the token +lists for parameters at the current level and subsidiary levels of input. +This stack grows at a different rate from the others. + +@= +@!param_stack:array [0..param_size] of pointer; + {token list pointers for parameters} +@!param_ptr:0..param_size; {first unused entry in |param_stack|} +@!max_param_stack:integer; + {largest value of |param_ptr|} + +@ Thus, the ``current input state'' can be very complicated indeed; there +can be many levels and each level can arise in a variety of ways. The +|show_context| procedure, which is used by \MF's error-reporting routine to +print out the current input state on all levels down to the most recent +line of characters from an input file, illustrates most of these conventions. +The global variable |file_ptr| contains the lowest level that was +displayed by this procedure. + +@= +@!file_ptr:0..stack_size; {shallowest level shown by |show_context|} + +@ The status at each level is indicated by printing two lines, where the first +line indicates what was read so far and the second line shows what remains +to be read. The context is cropped, if necessary, so that the first line +contains at most |half_error_line| characters, and the second contains +at most |error_line|. Non-current input levels whose |token_type| is +`|backed_up|' are shown only if they have not been fully read. + +@p procedure show_context; {prints where the scanner is} +label done; +var @!old_setting:0..max_selector; {saved |selector| setting} +@@/ +begin file_ptr:=input_ptr; input_stack[file_ptr]:=cur_input; + {store current state} +loop@+begin cur_input:=input_stack[file_ptr]; {enter into the context} + @; + if file_state then + if (name>2) or (file_ptr=0) then goto done; + decr(file_ptr); + end; +done: cur_input:=input_stack[input_ptr]; {restore original state} +end; + +@ @= +if (file_ptr=input_ptr) or file_state or + (token_type<>backed_up) or (loc<>null) then + {we omit backed-up token lists that have already been read} + begin tally:=0; {get ready to count characters} + old_setting:=selector; + if file_state then + begin @; + @; + end + else begin @; + @; + end; + selector:=old_setting; {stop pseudoprinting} + @; + end + +@ This routine should be changed, if necessary, to give the best possible +indication of where the current line resides in the input file. +For example, on some systems it is best to print both a page and line number. +@^system dependencies@> + +@= +if name<=1 then + if terminal_input and(file_ptr=0) then print_nl("<*>") + else print_nl("") +else if name=2 then print_nl("") +else begin print_nl("l."); print_int(line); + end; +print_char(" ") + +@ @= +case token_type of +forever_text: print_nl(" "); +loop_text: @; +parameter: print_nl(" "); +backed_up: if loc=null then print_nl(" ") + else print_nl(" "); +inserted: print_nl(" "); +macro: begin print_ln; + if name<>null then slow_print(text(name)) + else @; + print("->"); + end; +othercases print_nl("?") {this should never happen} +@.?\relax@> +endcases + +@ The parameter that corresponds to a loop text is either a token list +(in the case of \&{forsuffixes}) or a ``capsule'' (in the case of \&{for}). +We'll discuss capsules later; for now, all we need to know is that +the |link| field in a capsule parameter is |void| and that +|print_exp(p,0)| displays the value of capsule~|p| in abbreviated form. + +@= +begin print_nl("null then + if link(p)=void then print_exp(p,0) {we're in a \&{for} loop} + else show_token_list(p,null,20,tally); +print(")> "); +end + +@ The first two parameters of a macro defined by \&{vardef} will be token +lists representing the macro's prefix and ``at point.'' By putting these +together, we get the macro's full name. + +@= +begin p:=param_stack[param_start]; +if p=null then show_token_list(param_stack[param_start+1],null,20,tally) +else begin q:=p; + while link(q)<>null do q:=link(q); + link(q):=param_stack[param_start+1]; + show_token_list(p,null,20,tally); + link(q):=null; + end; +end + +@ Now it is necessary to explain a little trick. We don't want to store a long +string that corresponds to a token list, because that string might take up +lots of memory; and we are printing during a time when an error message is +being given, so we dare not do anything that might overflow one of \MF's +tables. So `pseudoprinting' is the answer: We enter a mode of printing +that stores characters into a buffer of length |error_line|, where character +$k+1$ is placed into \hbox{|trick_buf[k mod error_line]|} if +|k(error_line, +tally+1+error_line-half_error_line)|. At the end of the +pseudoprinting, the values of |first_count|, |tally|, and +|trick_count| give us all the information we need to print the two lines, +and all of the necessary text is in |trick_buf|. + +Namely, let |l| be the length of the descriptive information that appears +on the first line. The length of the context information gathered for that +line is |k=first_count|, and the length of the context information +gathered for line~2 is $m=\min(|tally|, |trick_count|)-k$. If |l+k<=h|, +where |h=half_error_line|, we print |trick_buf[0..k-1]| after the +descriptive information on line~1, and set |n:=l+k|; here |n| is the +length of line~1. If $l+k>h$, some cropping is necessary, so we set |n:=h| +and print `\.{...}' followed by +$$\hbox{|trick_buf[(l+k-h+3)..k-1]|,}$$ +where subscripts of |trick_buf| are circular modulo |error_line|. The +second line consists of |n|~spaces followed by |trick_buf[k..(k+m-1)]|, +unless |n+m>error_line|; in the latter case, further cropping is done. +This is easier to program than to explain. + +@= +@!i:0..buf_size; {index into |buffer|} +@!l:integer; {length of descriptive information on line 1} +@!m:integer; {context information gathered for line 2} +@!n:0..error_line; {length of line 1} +@!p: integer; {starting or ending place in |trick_buf|} +@!q: integer; {temporary index} + +@ The following code tells the print routines to gather +the desired information. + +@d begin_pseudoprint== + begin l:=tally; tally:=0; selector:=pseudo; + trick_count:=1000000; + end +@d set_trick_count== + begin first_count:=tally; + trick_count:=tally+1+error_line-half_error_line; + if trick_count= +if trick_count=1000000 then set_trick_count; + {|set_trick_count| must be performed} +if tallyerror_line then print("...") + +@ But the trick is distracting us from our current goal, which is to +understand the input state. So let's concentrate on the data structures that +are being pseudoprinted as we finish up the |show_context| procedure. + +@= +begin_pseudoprint; +if limit>0 then for i:=start to limit-1 do + begin if i=loc then set_trick_count; + print(buffer[i]); + end + +@ @= +begin_pseudoprint; +if token_type<>macro then show_token_list(start,loc,100000,0) +else show_macro(start,loc,100000) + +@ Here is the missing piece of |show_token_list| that is activated when the +token beginning line~2 is about to be shown: + +@=set_trick_count + +@* \[32] Maintaining the input stacks. +The following subroutines change the input status in commonly needed ways. + +First comes |push_input|, which stores the current state and creates a +new level (having, initially, the same properties as the old). + +@d push_input==@t@> {enter a new input level, save the old} + begin if input_ptr>max_in_stack then + begin max_in_stack:=input_ptr; + if input_ptr=stack_size then overflow("input stack size",stack_size); +@:METAFONT capacity exceeded input stack size}{\quad input stack size@> + end; + input_stack[input_ptr]:=cur_input; {stack the record} + incr(input_ptr); + end + +@ And of course what goes up must come down. + +@d pop_input==@t@> {leave an input level, re-enter the old} + begin decr(input_ptr); cur_input:=input_stack[input_ptr]; + end + +@ Here is a procedure that starts a new level of token-list input, given +a token list |p| and its type |t|. If |t=macro|, the calling routine should +set |name|, reset~|loc|, and increase the macro's reference count. + +@d back_list(#)==begin_token_list(#,backed_up) {backs up a simple token list} + +@p procedure begin_token_list(@!p:pointer;@!t:quarterword); +begin push_input; start:=p; token_type:=t; +param_start:=param_ptr; loc:=p; +end; + +@ When a token list has been fully scanned, the following computations +should be done as we leave that level of input. +@^inner loop@> + +@p procedure end_token_list; {leave a token-list input level} +label done; +var @!p:pointer; {temporary register} +begin if token_type>=backed_up then {token list to be deleted} + if token_type<=inserted then + begin flush_token_list(start); goto done; + end + else delete_mac_ref(start); {update reference count} +while param_ptr>param_start do {parameters must be flushed} + begin decr(param_ptr); + p:=param_stack[param_ptr]; + if p<>null then + if link(p)=void then {it's an \&{expr} parameter} + begin recycle_value(p); free_node(p,value_node_size); + end + else flush_token_list(p); {it's a \&{suffix} or \&{text} parameter} + end; +done: pop_input; check_interrupt; +end; + +@ The contents of |cur_cmd,cur_mod,cur_sym| are placed into an equivalent +token by the |cur_tok| routine. +@^inner loop@> + +@p @t\4@>@@;@/ +function cur_tok:pointer; +var @!p:pointer; {a new token node} +@!save_type:small_number; {|cur_type| to be restored} +@!save_exp:integer; {|cur_exp| to be restored} +begin if cur_sym=0 then + if cur_cmd=capsule_token then + begin save_type:=cur_type; save_exp:=cur_exp; + make_exp_copy(cur_mod); p:=stash_cur_exp; link(p):=null; + cur_type:=save_type; cur_exp:=save_exp; + end + else begin p:=get_node(token_node_size); + value(p):=cur_mod; name_type(p):=token; + if cur_cmd=numeric_token then type(p):=known + else type(p):=string_type; + end +else begin fast_get_avail(p); info(p):=cur_sym; + end; +cur_tok:=p; +end; + +@ Sometimes \MF\ has read too far and wants to ``unscan'' what it has +seen. The |back_input| procedure takes care of this by putting the token +just scanned back into the input stream, ready to be read again. +If |cur_sym<>0|, the values of |cur_cmd| and |cur_mod| are irrelevant. + +@p procedure back_input; {undoes one token of input} +var @!p:pointer; {a token list of length one} +begin p:=cur_tok; +while token_state and(loc=null) do end_token_list; {conserve stack space} +back_list(p); +end; + +@ The |back_error| routine is used when we want to restore or replace an +offending token just before issuing an error message. We disable interrupts +during the call of |back_input| so that the help message won't be lost. + +@p procedure back_error; {back up one token and call |error|} +begin OK_to_interrupt:=false; back_input; OK_to_interrupt:=true; error; +end; +@# +procedure ins_error; {back up one inserted token and call |error|} +begin OK_to_interrupt:=false; back_input; token_type:=inserted; +OK_to_interrupt:=true; error; +end; + +@ The |begin_file_reading| procedure starts a new level of input for lines +of characters to be read from a file, or as an insertion from the +terminal. It does not take care of opening the file, nor does it set |loc| +or |limit| or |line|. +@^system dependencies@> + +@p procedure begin_file_reading; +begin if in_open=max_in_open then overflow("text input levels",max_in_open); +@:METAFONT capacity exceeded text input levels}{\quad text input levels@> +if first=buf_size then overflow("buffer size",buf_size); +@:METAFONT capacity exceeded buffer size}{\quad buffer size@> +incr(in_open); push_input; index:=in_open; +line_stack[index]:=line; start:=first; +name:=0; {|terminal_input| is now |true|} +end; + +@ Conversely, the variables must be downdated when such a level of input +is finished: + +@p procedure end_file_reading; +begin first:=start; line:=line_stack[index]; +if index<>in_open then confusion("endinput"); +@:this can't happen endinput}{\quad endinput@> +if name>2 then a_close(cur_file); {forget it} +pop_input; decr(in_open); +end; + +@ In order to keep the stack from overflowing during a long sequence of +inserted `\.{show}' commands, the following routine removes completed +error-inserted lines from memory. + +@p procedure clear_for_error_prompt; +begin while file_state and terminal_input and@| + (input_ptr>0)and(loc=limit) do end_file_reading; +print_ln; clear_terminal; +end; + +@ To get \MF's whole input mechanism going, we perform the following +actions. + +@= +begin input_ptr:=0; max_in_stack:=0; +in_open:=0; open_parens:=0; max_buf_stack:=0; +param_ptr:=0; max_param_stack:=0; +first:=1; +start:=1; index:=0; line:=0; name:=0; +force_eof:=false; +if not init_terminal then goto final_end; +limit:=last; first:=last+1; {|init_terminal| has set |loc| and |last|} +end; + +@* \[33] Getting the next token. +The heart of \MF's input mechanism is the |get_next| procedure, which +we shall develop in the next few sections of the program. Perhaps we +shouldn't actually call it the ``heart,'' however; it really acts as \MF's +eyes and mouth, reading the source files and gobbling them up. And it also +helps \MF\ to regurgitate stored token lists that are to be processed again. + +The main duty of |get_next| is to input one token and to set |cur_cmd| +and |cur_mod| to that token's command code and modifier. Furthermore, if +the input token is a symbolic token, that token's |hash| address +is stored in |cur_sym|; otherwise |cur_sym| is set to zero. + +Underlying this simple description is a certain amount of complexity +because of all the cases that need to be handled. +However, the inner loop of |get_next| is reasonably short and fast. + +@ Before getting into |get_next|, we need to consider a mechanism by which +\MF\ helps keep errors from propagating too far. Whenever the program goes +into a mode where it keeps calling |get_next| repeatedly until a certain +condition is met, it sets |scanner_status| to some value other than |normal|. +Then if an input file ends, or if an `\&{outer}' symbol appears, +an appropriate error recovery will be possible. + +The global variable |warning_info| helps in this error recovery by providing +additional information. For example, |warning_info| might indicate the +name of a macro whose replacement text is being scanned. + +@d normal=0 {|scanner_status| at ``quiet times''} +@d skipping=1 {|scanner_status| when false conditional text is being skipped} +@d flushing=2 {|scanner_status| when junk after a statement is being ignored} +@d absorbing=3 {|scanner_status| when a \&{text} parameter is being scanned} +@d var_defining=4 {|scanner_status| when a \&{vardef} is being scanned} +@d op_defining=5 {|scanner_status| when a macro \&{def} is being scanned} +@d loop_defining=6 {|scanner_status| when a \&{for} loop is being scanned} + +@= +@!scanner_status:normal..loop_defining; {are we scanning at high speed?} +@!warning_info:integer; {if so, what else do we need to know, + in case an error occurs?} + +@ @= +scanner_status:=normal; + +@ The following subroutine +is called when an `\&{outer}' symbolic token has been scanned or +when the end of a file has been reached. These two cases are distinguished +by |cur_sym|, which is zero at the end of a file. + +@p function check_outer_validity:boolean; +var @!p:pointer; {points to inserted token list} +begin if scanner_status=normal then check_outer_validity:=true +else begin deletions_allowed:=false; + @; + if scanner_status>skipping then + @ + else begin print_err("Incomplete if; all text was ignored after line "); +@.Incomplete if...@> + print_int(warning_info);@/ + help3("A forbidden `outer' token occurred in skipped text.")@/ + ("This kind of error happens when you say `if...' and forget")@/ + ("the matching `fi'. I've inserted a `fi'; this might work."); + if cur_sym=0 then help_line[2]:=@| + "The file ended while I was skipping conditional text."; + cur_sym:=frozen_fi; ins_error; + end; + deletions_allowed:=true; check_outer_validity:=false; + end; +end; + +@ @= +if cur_sym<>0 then + begin p:=get_avail; info(p):=cur_sym; + back_list(p); {prepare to read the symbolic token again} + end + +@ @= +begin runaway; {print the definition-so-far} +if cur_sym=0 then print_err("File ended") +@.File ended while scanning...@> +else begin print_err("Forbidden token found"); +@.Forbidden token found...@> + end; +print(" while scanning "); +help4("I suspect you have forgotten an `enddef',")@/ +("causing me to read past where you wanted me to stop.")@/ +("I'll try to recover; but if the error is serious,")@/ +("you'd better type `E' or `X' now and fix your file.");@/ +case scanner_status of +@t\4@>@@; +end; {there are no other cases} +ins_error; +end + +@ As we consider various kinds of errors, it is also appropriate to +change the first line of the help message just given; |help_line[3]| +points to the string that might be changed. + +@= +flushing: begin print("to the end of the statement"); + help_line[3]:="A previous error seems to have propagated,"; + cur_sym:=frozen_semicolon; + end; +absorbing: begin print("a text argument"); + help_line[3]:="It seems that a right delimiter was left out,"; + if warning_info=0 then cur_sym:=frozen_end_group + else begin cur_sym:=frozen_right_delimiter; + equiv(frozen_right_delimiter):=warning_info; + end; + end; +var_defining, op_defining: begin print("the definition of "); + if scanner_status=op_defining then slow_print(text(warning_info)) + else print_variable_name(warning_info); + cur_sym:=frozen_end_def; + end; +loop_defining: begin print("the text of a "); slow_print(text(warning_info)); + print(" loop"); + help_line[3]:="I suspect you have forgotten an `endfor',"; + cur_sym:=frozen_end_for; + end; + +@ The |runaway| procedure displays the first part of the text that occurred +when \MF\ began its special |scanner_status|, if that text has been saved. + +@= +procedure runaway; +begin if scanner_status>flushing then + begin print_nl("Runaway "); + case scanner_status of + absorbing: print("text?"); + var_defining,op_defining: print("definition?"); + loop_defining: print("loop?"); + end; {there are no other cases} + print_ln; show_token_list(link(hold_head),null,error_line-10,0); + end; +end; + +@ We need to mention a procedure that may be called by |get_next|. + +@p procedure@?firm_up_the_line; forward; + +@ And now we're ready to take the plunge into |get_next| itself. + +@d switch=25 {a label in |get_next|} +@d start_numeric_token=85 {another} +@d start_decimal_token=86 {and another} +@d fin_numeric_token=87 + {and still another, although |goto| is considered harmful} + +@p procedure get_next; {sets |cur_cmd|, |cur_mod|, |cur_sym| to next token} +@^inner loop@> +label restart, {go here to get the next input token} + exit, {go here when the next input token has been got} + found, {go here when the end of a symbolic token has been found} + switch, {go here to branch on the class of an input character} + start_numeric_token,start_decimal_token,fin_numeric_token,done; + {go here at crucial stages when scanning a number} +var @!k:0..buf_size; {an index into |buffer|} +@!c:ASCII_code; {the current character in the buffer} +@!class:ASCII_code; {its class number} +@!n,@!f:integer; {registers for decimal-to-binary conversion} +begin restart: cur_sym:=0; +if file_state then +@ +else @; +@; +exit:end; + +@ When a symbolic token is declared to be `\&{outer}', its command code +is increased by |outer_tag|. +@^inner loop@> + +@= +cur_cmd:=eq_type(cur_sym); cur_mod:=equiv(cur_sym); +if cur_cmd>=outer_tag then + if check_outer_validity then cur_cmd:=cur_cmd-outer_tag + else goto restart + +@ A percent sign appears in |buffer[limit]|; this makes it unnecessary +to have a special test for end-of-line. +@^inner loop@> + +@= +begin switch: c:=buffer[loc]; incr(loc); class:=char_class[c]; +case class of +digit_class: goto start_numeric_token; +period_class: begin class:=char_class[buffer[loc]]; + if class>period_class then goto switch + else if class + end; +space_class: goto switch; +percent_class: begin @; + check_interrupt; + goto switch; + end; +string_class: @; +isolated_classes: begin k:=loc-1; goto found; + end; +invalid_class: @; +othercases do_nothing {letters, etc.} +endcases;@/ +k:=loc-1; +while char_class[buffer[loc]]=class do incr(loc); +goto found; +start_numeric_token:@; +start_decimal_token:@; +fin_numeric_token:@; +found: cur_sym:=id_lookup(k,loc-k); +end + +@ We go to |restart| instead of to |switch|, because we might enter +|token_state| after the error has been dealt with +(cf.\ |clear_for_error_prompt|). + +@= +begin print_err("Text line contains an invalid character"); +@.Text line contains...@> +help2("A funny symbol that I can't read has just been input.")@/ +("Continue, and I'll forget that it ever happened.");@/ +deletions_allowed:=false; error; deletions_allowed:=true; +goto restart; +end + +@ @= +begin if buffer[loc]="""" then cur_mod:="" +else begin k:=loc; buffer[limit+1]:=""""; + repeat incr(loc); + until buffer[loc]=""""; + if loc>limit then @; + if (loc=k+1) and (length(buffer[k])=1) then cur_mod:=buffer[k] + else begin str_room(loc-k); + repeat append_char(buffer[k]); incr(k); + until k=loc; + cur_mod:=make_string; + end; + end; +incr(loc); cur_cmd:=string_token; return; +end + +@ We go to |restart| after this error message, not to |switch|, +because the |clear_for_error_prompt| routine might have reinstated +|token_state| after |error| has finished. + +@= +begin loc:=limit; {the next character to be read on this line will be |"%"|} +print_err("Incomplete string token has been flushed"); +@.Incomplete string token...@> +help3("Strings should finish on the same line as they began.")@/ + ("I've deleted the partial string; you might want to")@/ + ("insert another by typing, e.g., `I""new string""'.");@/ +deletions_allowed:=false; error; deletions_allowed:=true; goto restart; +end + +@ @= +n:=c-"0"; +while char_class[buffer[loc]]=digit_class do + begin if n<4096 then n:=10*n+buffer[loc]-"0"; + incr(loc); + end; +if buffer[loc]="." then if char_class[buffer[loc+1]]=digit_class then goto done; +f:=0; goto fin_numeric_token; +done: incr(loc) + +@ @= +k:=0; +repeat if k<17 then {digits for |k>=17| cannot affect the result} + begin dig[k]:=buffer[loc]-"0"; incr(k); + end; +incr(loc); +until char_class[buffer[loc]]<>digit_class; +f:=round_decimals(k); +if f=unity then + begin incr(n); f:=0; + end + +@ @= +if n<4096 then cur_mod:=n*unity+f +else begin print_err("Enormous number has been reduced"); +@.Enormous number...@> + help2("I can't handle numbers bigger than about 4095.99998;")@/ + ("so I've changed your constant to that maximum amount.");@/ + deletions_allowed:=false; error; deletions_allowed:=true; + cur_mod:=@'1777777777; + end; +cur_cmd:=numeric_token; return + +@ Let's consider now what happens when |get_next| is looking at a token list. +@^inner loop@> + +@= +if loc>=hi_mem_min then {one-word token} + begin cur_sym:=info(loc); loc:=link(loc); {move to next} + if cur_sym>=expr_base then + if cur_sym>=suffix_base then + @ + else begin cur_cmd:=capsule_token; + cur_mod:=param_stack[param_start+cur_sym-(expr_base)]; + cur_sym:=0; return; + end; + end +else if loc>null then + @ +else begin {we are done with this token list} + end_token_list; goto restart; {resume previous level} + end + +@ @= +begin if cur_sym>=text_base then cur_sym:=cur_sym-param_size; + {|param_size=text_base-suffix_base|} +begin_token_list(param_stack[param_start+cur_sym-(suffix_base)],parameter); +goto restart; +end + +@ @= +begin if name_type(loc)=token then + begin cur_mod:=value(loc); + if type(loc)=known then cur_cmd:=numeric_token + else begin cur_cmd:=string_token; add_str_ref(cur_mod); + end; + end +else begin cur_mod:=loc; cur_cmd:=capsule_token; + end; +loc:=link(loc); return; +end + +@ All of the easy branches of |get_next| have now been taken care of. +There is one more branch. + +@= +if name>2 then @ +else begin if input_ptr>0 then + {text was inserted during error recovery or by \&{scantokens}} + begin end_file_reading; goto restart; {resume previous level} + end; + if selectornonstop_mode then + begin if limit=start then {previous line was empty} + print_nl("(Please type a command or say `end')"); +@.Please type...@> + print_ln; first:=start; + prompt_input("*"); {input on-line into |buffer|} +@.*\relax@> + limit:=last; buffer[limit]:="%"; + first:=limit+1; loc:=start; + end + else fatal_error("*** (job aborted, no legal end found)"); +@.job aborted@> + {nonstop mode, which is intended for overnight batch processing, + never waits for on-line input} + end + +@ The global variable |force_eof| is normally |false|; it is set |true| +by an \&{endinput} command. + +@= +@!force_eof:boolean; {should the next \&{input} be aborted early?} + +@ @= +begin incr(line); first:=start; +if not force_eof then + begin if input_ln(cur_file,true) then {not end of file} + firm_up_the_line {this sets |limit|} + else force_eof:=true; + end; +if force_eof then + begin print_char(")"); decr(open_parens); + update_terminal; {show user that file has been read} + force_eof:=false; + end_file_reading; {resume previous level} + if check_outer_validity then goto restart@+else goto restart; + end; +buffer[limit]:="%"; first:=limit+1; loc:=start; {ready to read} +end + +@ If the user has set the |pausing| parameter to some positive value, +and if nonstop mode has not been selected, each line of input is displayed +on the terminal and the transcript file, followed by `\.{=>}'. +\MF\ waits for a response. If the response is null (i.e., if nothing is +typed except perhaps a few blank spaces), the original +line is accepted as it stands; otherwise the line typed is +used instead of the line in the file. + +@p procedure firm_up_the_line; +var @!k:0..buf_size; {an index into |buffer|} +begin limit:=last; +if internal[pausing]>0 then if interaction>nonstop_mode then + begin wake_up_terminal; print_ln; + if start"); {wait for user response} +@.=>@> + if last>first then + begin for k:=first to last-1 do {move line down in buffer} + buffer[k+start-first]:=buffer[k]; + limit:=start+last-first; + end; + end; +end; + +@* \[34] Scanning macro definitions. +\MF\ has a variety of ways to tuck tokens away into token lists for later +use: Macros can be defined with \&{def}, \&{vardef}, \&{primarydef}, etc.; +repeatable code can be defined with \&{for}, \&{forever}, \&{forsuffixes}. +All such operations are handled by the routines in this part of the program. + +The modifier part of each command code is zero for the ``ending delimiters'' +like \&{enddef} and \&{endfor}. + +@d start_def=1 {command modifier for \&{def}} +@d var_def=2 {command modifier for \&{vardef}} +@d end_def=0 {command modifier for \&{enddef}} +@d start_forever=1 {command modifier for \&{forever}} +@d end_for=0 {command modifier for \&{endfor}} + +@= +primitive("def",macro_def,start_def);@/ +@!@:def_}{\&{def} primitive@> +primitive("vardef",macro_def,var_def);@/ +@!@:var_def_}{\&{vardef} primitive@> +primitive("primarydef",macro_def,secondary_primary_macro);@/ +@!@:primary_def_}{\&{primarydef} primitive@> +primitive("secondarydef",macro_def,tertiary_secondary_macro);@/ +@!@:secondary_def_}{\&{secondarydef} primitive@> +primitive("tertiarydef",macro_def,expression_tertiary_macro);@/ +@!@:tertiary_def_}{\&{tertiarydef} primitive@> +primitive("enddef",macro_def,end_def); eqtb[frozen_end_def]:=eqtb[cur_sym];@/ +@!@:end_def_}{\&{enddef} primitive@> +@# +primitive("for",iteration,expr_base);@/ +@!@:for_}{\&{for} primitive@> +primitive("forsuffixes",iteration,suffix_base);@/ +@!@:for_suffixes_}{\&{forsuffixes} primitive@> +primitive("forever",iteration,start_forever);@/ +@!@:forever_}{\&{forever} primitive@> +primitive("endfor",iteration,end_for); eqtb[frozen_end_for]:=eqtb[cur_sym];@/ +@!@:end_for_}{\&{endfor} primitive@> + +@ @= +macro_def:if m<=var_def then + if m=start_def then print("def") + else if m0 then + begin @; + if cur_cmd=terminator then + @ + else if cur_cmd=macro_special then + @; + end; + link(p):=cur_tok; p:=link(p); + end; +done: link(p):=tail_end; flush_node_list(subst_list); +scan_toks:=link(hold_head); +end; + +@ @= +begin q:=subst_list; +while q<>null do + begin if info(q)=cur_sym then + begin cur_sym:=value(q); cur_cmd:=relax; goto found; + end; + q:=link(q); + end; +found:end + +@ @= +if cur_mod>0 then incr(balance) +else begin decr(balance); + if balance=0 then goto done; + end + +@ Four commands are intended to be used only within macro texts: \&{quote}, +\.{\#\AT!}, \.{\AT!}, and \.{\AT!\#}. They are variants of a single command +code called |macro_special|. + +@d quote=0 {|macro_special| modifier for \&{quote}} +@d macro_prefix=1 {|macro_special| modifier for \.{\#\AT!}} +@d macro_at=2 {|macro_special| modifier for \.{\AT!}} +@d macro_suffix=3 {|macro_special| modifier for \.{\AT!\#}} + +@= +primitive("quote",macro_special,quote);@/ +@!@:quote_}{\&{quote} primitive@> +primitive("#@@",macro_special,macro_prefix);@/ +@!@:]]]\#\AT!_}{\.{\#\AT!} primitive@> +primitive("@@",macro_special,macro_at);@/ +@!@:]]]\AT!_}{\.{\AT!} primitive@> +primitive("@@#",macro_special,macro_suffix);@/ +@!@:]]]\AT!\#_}{\.{\AT!\#} primitive@> + +@ @= +macro_special: case m of + macro_prefix: print("#@@"); + macro_at: print_char("@@"); + macro_suffix: print("@@#"); + othercases print("quote") + endcases; + +@ @= +begin if cur_mod=quote then get_next +else if cur_mod<=suffix_count then cur_sym:=suffix_base-1+cur_mod; +end + +@ Here is a routine that's used whenever a token will be redefined. If +the user's token is unredefinable, the `|frozen_inaccessible|' token is +substituted; the latter is redefinable but essentially impossible to use, +hence \MF's tables won't get fouled up. + +@p procedure get_symbol; {sets |cur_sym| to a safe symbol} +label restart; +begin restart: get_next; +if (cur_sym=0)or(cur_sym>frozen_inaccessible) then + begin print_err("Missing symbolic token inserted"); +@.Missing symbolic token...@> + help3("Sorry: You can't redefine a number, string, or expr.")@/ + ("I've inserted an inaccessible symbol so that your")@/ + ("definition will be completed without mixing me up too badly."); + if cur_sym>0 then + help_line[2]:="Sorry: You can't redefine my error-recovery tokens." + else if cur_cmd=string_token then delete_str_ref(cur_mod); + cur_sym:=frozen_inaccessible; ins_error; goto restart; + end; +end; + +@ Before we actually redefine a symbolic token, we need to clear away its +former value, if it was a variable. The following stronger version of +|get_symbol| does that. + +@p procedure get_clear_symbol; +begin get_symbol; clear_symbol(cur_sym,false); +end; + +@ Here's another little subroutine; it checks that an equals sign +or assignment sign comes along at the proper place in a macro definition. + +@p procedure check_equals; +begin if cur_cmd<>equals then if cur_cmd<>assignment then + begin missing_err("=");@/ +@.Missing `='@> + help5("The next thing in this `def' should have been `=',")@/ + ("because I've already looked at the definition heading.")@/ + ("But don't worry; I'll pretend that an equals sign")@/ + ("was present. Everything from here to `enddef'")@/ + ("will be the replacement text of this macro."); + back_error; + end; +end; + +@ A \&{primarydef}, \&{secondarydef}, or \&{tertiarydef} is rather easily +handled now that we have |scan_toks|. In this case there are +two parameters, which will be \.{EXPR0} and \.{EXPR1} (i.e., +|expr_base| and |expr_base+1|). + +@p procedure make_op_def; +var @!m:command_code; {the type of definition} +@!p,@!q,@!r:pointer; {for list manipulation} +begin m:=cur_mod;@/ +get_symbol; q:=get_node(token_node_size); +info(q):=cur_sym; value(q):=expr_base;@/ +get_clear_symbol; warning_info:=cur_sym;@/ +get_symbol; p:=get_node(token_node_size); +info(p):=cur_sym; value(p):=expr_base+1; link(p):=q;@/ +get_next; check_equals;@/ +scanner_status:=op_defining; q:=get_avail; ref_count(q):=null; +r:=get_avail; link(q):=r; info(r):=general_macro; +link(r):=scan_toks(macro_def,p,null,0); +scanner_status:=normal; eq_type(warning_info):=m; +equiv(warning_info):=q; get_x_next; +end; + +@ Parameters to macros are introduced by the keywords \&{expr}, +\&{suffix}, \&{text}, \&{primary}, \&{secondary}, and \&{tertiary}. + +@= +primitive("expr",param_type,expr_base);@/ +@!@:expr_}{\&{expr} primitive@> +primitive("suffix",param_type,suffix_base);@/ +@!@:suffix_}{\&{suffix} primitive@> +primitive("text",param_type,text_base);@/ +@!@:text_}{\&{text} primitive@> +primitive("primary",param_type,primary_macro);@/ +@!@:primary_}{\&{primary} primitive@> +primitive("secondary",param_type,secondary_macro);@/ +@!@:secondary_}{\&{secondary} primitive@> +primitive("tertiary",param_type,tertiary_macro);@/ +@!@:tertiary_}{\&{tertiary} primitive@> + +@ @= +param_type:if m>=expr_base then + if m=expr_base then print("expr") + else if m=suffix_base then print("suffix") + else print("text") + else if m@@; +@t\4@>@@; +procedure scan_def; +var @!m:start_def..var_def; {the type of definition} +@!n:0..3; {the number of special suffix parameters} +@!k:0..param_size; {the total number of parameters} +@!c:general_macro..text_macro; {the kind of macro we're defining} +@!r:pointer; {parameter-substitution list} +@!q:pointer; {tail of the macro token list} +@!p:pointer; {temporary storage} +@!base:halfword; {|expr_base|, |suffix_base|, or |text_base|} +@!l_delim,@!r_delim:pointer; {matching delimiters} +begin m:=cur_mod; c:=general_macro; link(hold_head):=null;@/ +q:=get_avail; ref_count(q):=null; r:=null;@/ +@; +k:=n; +if cur_cmd=left_delimiter then + @; +if cur_cmd=param_type then + @; +check_equals; +p:=get_avail; info(p):=c; link(q):=p; +@; +scanner_status:=normal; get_x_next; +end; + +@ We don't put `|frozen_end_group|' into the replacement text of +a \&{vardef}, because the user may want to redefine `\.{endgroup}'. + +@= +if m=start_def then link(p):=scan_toks(macro_def,r,null,n) +else begin q:=get_avail; info(q):=bg_loc; link(p):=q; + p:=get_avail; info(p):=eg_loc; + link(q):=scan_toks(macro_def,r,p,n); + end; +if warning_info=bad_vardef then flush_token_list(value(bad_vardef)) + +@ @= +@!bg_loc,@!eg_loc:1..hash_end; + {hash addresses of `\.{begingroup}' and `\.{endgroup}'} + +@ @= +if m=start_def then + begin get_clear_symbol; warning_info:=cur_sym; get_next; + scanner_status:=op_defining; n:=0; + eq_type(warning_info):=defined_macro; equiv(warning_info):=q; + end +else begin p:=scan_declared_variable; + flush_variable(equiv(info(p)),link(p),true); + warning_info:=find_variable(p); flush_list(p); + if warning_info=null then @; + scanner_status:=var_defining; n:=2; + if cur_cmd=macro_special then if cur_mod=macro_suffix then {\.{\AT!\#}} + begin n:=3; get_next; + end; + type(warning_info):=unsuffixed_macro-2+n; value(warning_info):=q; + end {|suffixed_macro=unsuffixed_macro+1|} + +@ @= +begin print_err("This variable already starts with a macro"); +@.This variable already...@> +help2("After `vardef a' you can't say `vardef a.b'.")@/ + ("So I'll have to discard this definition."); +error; warning_info:=bad_vardef; +end + +@ @= +name_type(bad_vardef):=root; link(bad_vardef):=frozen_bad_vardef; +equiv(frozen_bad_vardef):=bad_vardef; eq_type(frozen_bad_vardef):=tag_token; + +@ @= +repeat l_delim:=cur_sym; r_delim:=cur_mod; get_next; +if (cur_cmd=param_type)and(cur_mod>=expr_base) then base:=cur_mod +else begin print_err("Missing parameter type; `expr' will be assumed"); +@.Missing parameter type@> + help1("You should've had `expr' or `suffix' or `text' here."); + back_error; base:=expr_base; + end; +@; +check_delimiter(l_delim,r_delim); +get_next; +until cur_cmd<>left_delimiter + +@ @= +repeat link(q):=get_avail; q:=link(q); info(q):=base+k;@/ +get_symbol; p:=get_node(token_node_size); value(p):=base+k; info(p):=cur_sym; +if k=param_size then overflow("parameter stack size",param_size); +@:METAFONT capacity exceeded parameter stack size}{\quad parameter stack size@> +incr(k); link(p):=r; r:=p; get_next; +until cur_cmd<>comma + +@ @= +begin p:=get_node(token_node_size); +if cur_mod=min_command|. In other words, |get_x_next| expands +macros and removes conditionals or iterations or input instructions that +might be present. + +It follows that |get_x_next| might invoke itself recursively. In fact, +there is massive recursion, since macro expansion can involve the +scanning of arbitrarily complex expressions, which in turn involve +macro expansion and conditionals, etc. +@^recursion@> + +Therefore it's necessary to declare a whole bunch of |forward| +procedures at this point, and to insert some other procedures +that will be invoked by |get_x_next|. + +@p procedure@?scan_primary; forward;@t\2@> +procedure@?scan_secondary; forward;@t\2@> +procedure@?scan_tertiary; forward;@t\2@> +procedure@?scan_expression; forward;@t\2@> +procedure@?scan_suffix; forward;@t\2@>@/ +@t\4@>@@;@/ +procedure@?get_boolean; forward;@t\2@> +procedure@?pass_text; forward;@t\2@> +procedure@?conditional; forward;@t\2@> +procedure@?start_input; forward;@t\2@> +procedure@?begin_iteration; forward;@t\2@> +procedure@?resume_iteration; forward;@t\2@> +procedure@?stop_iteration; forward;@t\2@> + +@ An auxiliary subroutine called |expand| is used by |get_x_next| +when it has to do exotic expansion commands. + +@p procedure expand; +var @!p:pointer; {for list manipulation} +@!k:integer; {something that we hope is |<=buf_size|} +@!j:pool_pointer; {index into |str_pool|} +begin if internal[tracing_commands]>unity then if cur_cmd<>defined_macro then + show_cur_cmd_mod; +case cur_cmd of +if_test:conditional; {this procedure is discussed in Part 36 below} +fi_or_else:@; +input:@; +iteration:if cur_mod=end_for then + @ + else begin_iteration; {this procedure is discussed in Part 37 below} +repeat_loop: @; +exit_test: @; +relax: do_nothing; +expand_after: @; +scan_tokens: @; +defined_macro:macro_call(cur_mod,null,cur_sym); +end; {there are no other cases} +end; + +@ @= +begin print_err("Extra `endfor'"); +@.Extra `endfor'@> +help2("I'm not currently working on a for loop,")@/ + ("so I had better not try to end anything.");@/ +error; +end + +@ The processing of \&{input} involves the |start_input| subroutine, +which will be declared later; the processing of \&{endinput} is trivial. + +@= +primitive("input",input,0);@/ +@!@:input_}{\&{input} primitive@> +primitive("endinput",input,1);@/ +@!@:end_input_}{\&{endinput} primitive@> + +@ @= +input: if m=0 then print("input")@+else print("endinput"); + +@ @= +if cur_mod>0 then force_eof:=true +else start_input + +@ We'll discuss the complicated parts of loop operations later. For now +it suffices to know that there's a global variable called |loop_ptr| +that will be |null| if no loop is in progress. + +@= +begin while token_state and(loc=null) do end_token_list; {conserve stack space} +if loop_ptr=null then + begin print_err("Lost loop"); +@.Lost loop@> + help2("I'm confused; after exiting from a loop, I still seem")@/ + ("to want to repeat it. I'll try to forget the problem.");@/ + error; + end +else resume_iteration; {this procedure is in Part 37 below} +end + +@ @= +begin get_boolean; +if internal[tracing_commands]>unity then show_cmd_mod(nullary,cur_exp); +if cur_exp=true_code then + if loop_ptr=null then + begin print_err("No loop is in progress"); +@.No loop is in progress@> + help1("Why say `exitif' when there's nothing to exit from?"); + if cur_cmd=semicolon then error@+else back_error; + end + else @ +else if cur_cmd<>semicolon then + begin missing_err(";");@/ +@.Missing `;'@> + help2("After `exitif ' I expect to see a semicolon.")@/ + ("I shall pretend that one was there."); back_error; + end; +end + +@ Here we use the fact that |forever_text| is the only |token_type| that +is less than |loop_text|. + +@= +begin p:=null; +repeat if file_state then end_file_reading +else begin if token_type<=loop_text then p:=start; + end_token_list; + end; +until p<>null; +if p<>info(loop_ptr) then fatal_error("*** (loop confusion)"); +@.loop confusion@> +stop_iteration; {this procedure is in Part 37 below} +end + +@ @= +begin get_next; +p:=cur_tok; get_next; +if cur_cmd= +begin get_x_next; scan_primary; +if cur_type<>string_type then + begin disp_err(null,"Not a string"); +@.Not a string@> + help2("I'm going to flush this expression, since")@/ + ("scantokens should be followed by a known string."); + put_get_flush_error(0); + end +else begin back_input; + if length(cur_exp)>0 then @; + end; +end + +@ @= +begin begin_file_reading; name:=2; +k:=first+length(cur_exp); +if k>=max_buf_stack then + begin if k>=buf_size then + begin max_buf_stack:=buf_size; + overflow("buffer size",buf_size); +@:METAFONT capacity exceeded buffer size}{\quad buffer size@> + end; + max_buf_stack:=k+1; + end; +j:=str_start[cur_exp]; limit:=k; +while first + +@p procedure get_x_next; +var @!save_exp:pointer; {a capsule to save |cur_type| and |cur_exp|} +begin get_next; +if cur_cmd=min_command; + unstash_cur_exp(save_exp); {that restores |cur_type| and |cur_exp|} + end; +end; + +@ Now let's consider the |macro_call| procedure, which is used to start up +all user-defined macros. Since the arguments to a macro might be expressions, +|macro_call| is recursive. +@^recursion@> + +The first parameter to |macro_call| points to the reference count of the +token list that defines the macro. The second parameter contains any +arguments that have already been parsed (see below). The third parameter +points to the symbolic token that names the macro. If the third parameter +is |null|, the macro was defined by \&{vardef}, so its name can be +reconstructed from the prefix and ``at'' arguments found within the +second parameter. + +What is this second parameter? It's simply a linked list of one-word items, +whose |info| fields point to the arguments. In other words, if |arg_list=null|, +no arguments have been scanned yet; otherwise |info(arg_list)| points to +the first scanned argument, and |link(arg_list)| points to the list of +further arguments (if any). + +Arguments of type \&{expr} are so-called capsules, which we will +discuss later when we concentrate on expressions; they can be +recognized easily because their |link| field is |void|. Arguments of type +\&{suffix} and \&{text} are token lists without reference counts. + +@ After argument scanning is complete, the arguments are moved to the +|param_stack|. (They can't be put on that stack any sooner, because +the stack is growing and shrinking in unpredictable ways as more arguments +are being acquired.) Then the macro body is fed to the scanner; i.e., +the replacement text of the macro is placed at the top of the \MF's +input stack, so that |get_next| will proceed to read it next. + +@= +@t\4@>@@; +@t\4@>@@; +@t\4@>@@; +procedure macro_call(@!def_ref,@!arg_list,@!macro_name:pointer); + {invokes a user-defined control sequence} +label found; +var @!r:pointer; {current node in the macro's token list} +@!p,@!q:pointer; {for list manipulation} +@!n:integer; {the number of arguments} +@!l_delim,@!r_delim:pointer; {a delimiter pair} +@!tail:pointer; {tail of the argument list} +begin r:=link(def_ref); add_mac_ref(def_ref); +if arg_list=null then n:=0 +else @; +if internal[tracing_macros]>0 then + @; +@; +@; +end; + +@ @= +begin begin_diagnostic; print_ln; print_macro_name(arg_list,macro_name); +if n=3 then print("@@#"); {indicate a suffixed macro} +show_macro(def_ref,null,100000); +if arg_list<>null then + begin n:=0; p:=arg_list; + repeat q:=info(p); + print_arg(q,n,0); + incr(n); p:=link(p); + until p=null; + end; +end_diagnostic(false); +end + +@ @= +procedure print_macro_name(@!a,@!n:pointer); +var @!p,@!q:pointer; {they traverse the first part of |a|} +begin if n<>null then slow_print(text(n)) +else begin p:=info(a); + if p=null then slow_print(text(info(info(link(a))))) + else begin q:=p; + while link(q)<>null do q:=link(q); + link(q):=info(link(a)); + show_token_list(p,null,1000,0); + link(q):=null; + end; + end; +end; + +@ @= +procedure print_arg(@!q:pointer;@!n:integer;@!b:pointer); +begin if link(q)=void then print_nl("(EXPR") +else if (btext_macro) then print_nl("(SUFFIX") +else print_nl("(TEXT"); +print_int(n); print(")<-"); +if link(q)=void then print_exp(q,1) +else show_token_list(q,null,1000,0); +end; + +@ @= +begin n:=1; tail:=arg_list; +while link(tail)<>null do + begin incr(n); tail:=link(tail); + end; +end + +@ @= +cur_cmd:=comma+1; {anything |<>comma| will do} +while info(r)>=expr_base do + begin @; + r:=link(r); + end; +if cur_cmd=comma then + begin print_err("Too many arguments to "); +@.Too many arguments...@> + print_macro_name(arg_list,macro_name); print_char(";"); + print_nl(" Missing `"); slow_print(text(r_delim)); +@.Missing `)'...@> + print("' has been inserted"); + help3("I'm going to assume that the comma I just read was a")@/ + ("right delimiter, and then I'll begin expanding the macro.")@/ + ("You might want to delete some tokens before continuing."); + error; + end; +if info(r)<>general_macro then @; +r:=link(r) + +@ At this point, the reader will find it advisable to review the explanation +of token list format that was presented earlier, paying special attention to +the conventions that apply only at the beginning of a macro's token list. + +On the other hand, the reader will have to take the expression-parsing +aspects of the following program on faith; we will explain |cur_type| +and |cur_exp| later. (Several things in this program depend on each other, +and it's necessary to jump into the circle somewhere.) + +@= +if cur_cmd<>comma then + begin get_x_next; + if cur_cmd<>left_delimiter then + begin print_err("Missing argument to "); +@.Missing argument...@> + print_macro_name(arg_list,macro_name); + help3("That macro has more parameters than you thought.")@/ + ("I'll continue by pretending that each missing argument")@/ + ("is either zero or null."); + if info(r)>=suffix_base then + begin cur_exp:=null; cur_type:=token_list; + end + else begin cur_exp:=0; cur_type:=known; + end; + back_error; cur_cmd:=right_delimiter; goto found; + end; + l_delim:=cur_sym; r_delim:=cur_mod; + end; +@; +if cur_cmd<>comma then @; +found: @ + +@ @= +if (cur_cmd<>right_delimiter)or(cur_mod<>l_delim) then + if info(link(r))>=expr_base then + begin missing_err(","); +@.Missing `,'@> + help3("I've finished reading a macro argument and am about to")@/ + ("read another; the arguments weren't delimited correctly.")@/ + ("You might want to delete some tokens before continuing."); + back_error; cur_cmd:=comma; + end + else begin missing_err(text(r_delim)); +@.Missing `)'@> + help2("I've gotten to the end of the macro parameter list.")@/ + ("You might want to delete some tokens before continuing."); + back_error; + end + +@ A \&{suffix} or \&{text} parameter will have been scanned as +a token list pointed to by |cur_exp|, in which case we will have +|cur_type=token_list|. + +@= +begin p:=get_avail; +if cur_type=token_list then info(p):=cur_exp +else info(p):=stash_cur_exp; +if internal[tracing_macros]>0 then + begin begin_diagnostic; print_arg(info(p),n,info(r)); end_diagnostic(false); + end; +if arg_list=null then arg_list:=p +else link(tail):=p; +tail:=p; incr(n); +end + +@ @= +if info(r)>=text_base then scan_text_arg(l_delim,r_delim) +else begin get_x_next; + if info(r)>=suffix_base then scan_suffix + else scan_expression; + end + +@ The parameters to |scan_text_arg| are either a pair of delimiters +or zero; the latter case is for undelimited text arguments, which +end with the first semicolon or \&{endgroup} or \&{end} that is not +contained in a group. + +@= +procedure scan_text_arg(@!l_delim,@!r_delim:pointer); +label done; +var @!balance:integer; {excess of |l_delim| over |r_delim|} +@!p:pointer; {list tail} +begin warning_info:=l_delim; scanner_status:=absorbing; +p:=hold_head; balance:=1; link(hold_head):=null; +loop@+ begin get_next; + if l_delim=0 then @ + else @; + link(p):=cur_tok; p:=link(p); + end; +done: cur_exp:=link(hold_head); cur_type:=token_list; +scanner_status:=normal; +end; + +@ @= +begin if cur_cmd=right_delimiter then + begin if cur_mod=l_delim then + begin decr(balance); + if balance=0 then goto done; + end; + end +else if cur_cmd=left_delimiter then if cur_mod=r_delim then incr(balance); +end + +@ @= +begin if end_of_statement then {|cur_cmd=semicolon|, |end_group|, or |stop|} + begin if balance=1 then goto done + else if cur_cmd=end_group then decr(balance); + end +else if cur_cmd=begin_group then incr(balance); +end + +@ @= +begin if info(r)suffix_macro then + if (cur_cmd=equals)or(cur_cmd=assignment) then get_x_next; + end; +case info(r) of +primary_macro:scan_primary; +secondary_macro:scan_secondary; +tertiary_macro:scan_tertiary; +expr_macro:scan_expression; +of_macro:@; +suffix_macro:@; +text_macro:scan_text_arg(0,0); +end; {there are no other cases} +back_input; @; +end + +@ @= +begin scan_expression; p:=get_avail; info(p):=stash_cur_exp; +if internal[tracing_macros]>0 then + begin begin_diagnostic; print_arg(info(p),n,0); end_diagnostic(false); + end; +if arg_list=null then arg_list:=p@+else link(tail):=p; +tail:=p;incr(n); +if cur_cmd<>of_token then + begin missing_err("of"); print(" for "); +@.Missing `of'@> + print_macro_name(arg_list,macro_name); + help1("I've got the first argument; will look now for the other."); + back_error; + end; +get_x_next; scan_primary; +end + +@ @= +begin if cur_cmd<>left_delimiter then l_delim:=null +else begin l_delim:=cur_sym; r_delim:=cur_mod; get_x_next; + end; +scan_suffix; +if l_delim<>null then + begin if(cur_cmd<>right_delimiter)or(cur_mod<>l_delim) then + begin missing_err(text(r_delim)); +@.Missing `)'@> + help2("I've gotten to the end of the macro parameter list.")@/ + ("You might want to delete some tokens before continuing."); + back_error; + end; + get_x_next; + end; +end + +@ Before we put a new token list on the input stack, it is wise to clean off +all token lists that have recently been depleted. Then a user macro that ends +with a call to itself will not require unbounded stack space. + +@= +while token_state and(loc=null) do end_token_list; {conserve stack space} +if param_ptr+n>max_param_stack then + begin max_param_stack:=param_ptr+n; + if max_param_stack>param_size then + overflow("parameter stack size",param_size); +@:METAFONT capacity exceeded parameter stack size}{\quad parameter stack size@> + end; +begin_token_list(def_ref,macro); name:=macro_name; loc:=r; +if n>0 then + begin p:=arg_list; + repeat param_stack[param_ptr]:=info(p); incr(param_ptr); p:=link(p); + until p=null; + flush_list(arg_list); + end + +@ It's sometimes necessary to put a single argument onto |param_stack|. +The |stack_argument| subroutine does this. + +@p procedure stack_argument(@!p:pointer); +begin if param_ptr=max_param_stack then + begin incr(max_param_stack); + if max_param_stack>param_size then + overflow("parameter stack size",param_size); +@:METAFONT capacity exceeded parameter stack size}{\quad parameter stack size@> + end; +param_stack[param_ptr]:=p; incr(param_ptr); +end; + +@* \[36] Conditional processing. +Let's consider now the way \&{if} commands are handled. + +Conditions can be inside conditions, and this nesting has a stack +that is independent of other stacks. +Four global variables represent the top of the condition stack: +|cond_ptr| points to pushed-down entries, if~any; |cur_if| tells whether +we are processing \&{if} or \&{elseif}; |if_limit| specifies +the largest code of a |fi_or_else| command that is syntactically legal; +and |if_line| is the line number at which the current conditional began. + +If no conditions are currently in progress, the condition stack has the +special state |cond_ptr=null|, |if_limit=normal|, |cur_if=0|, |if_line=0|. +Otherwise |cond_ptr| points to a two-word node; the |type|, |name_type|, and +|link| fields of the first word contain |if_limit|, |cur_if|, and +|cond_ptr| at the next level, and the second word contains the +corresponding |if_line|. + +@d if_node_size=2 {number of words in stack entry for conditionals} +@d if_line_field(#)==mem[#+1].int +@d if_code=1 {code for \&{if} being evaluated} +@d fi_code=2 {code for \&{fi}} +@d else_code=3 {code for \&{else}} +@d else_if_code=4 {code for \&{elseif}} + +@= +@!cond_ptr:pointer; {top of the condition stack} +@!if_limit:normal..else_if_code; {upper bound on |fi_or_else| codes} +@!cur_if:small_number; {type of conditional being worked on} +@!if_line:integer; {line where that conditional began} + +@ @= +cond_ptr:=null; if_limit:=normal; cur_if:=0; if_line:=0; + +@ @= +primitive("if",if_test,if_code);@/ +@!@:if_}{\&{if} primitive@> +primitive("fi",fi_or_else,fi_code); eqtb[frozen_fi]:=eqtb[cur_sym];@/ +@!@:fi_}{\&{fi} primitive@> +primitive("else",fi_or_else,else_code);@/ +@!@:else_}{\&{else} primitive@> +primitive("elseif",fi_or_else,else_if_code);@/ +@!@:else_if_}{\&{elseif} primitive@> + +@ @= +if_test,fi_or_else: case m of + if_code:print("if"); + fi_code:print("fi"); + else_code:print("else"); + othercases print("elseif") + endcases; + +@ Here is a procedure that ignores text until coming to an \&{elseif}, +\&{else}, or \&{fi} at level zero of $\&{if}\ldots\&{fi}$ +nesting. After it has acted, |cur_mod| will indicate the token that +was found. + +\MF's smallest two command codes are |if_test| and |fi_or_else|; this +makes the skipping process a bit simpler. + +@p procedure pass_text; +label done; +var l:integer; +begin scanner_status:=skipping; l:=0; warning_info:=line; +loop@+ begin get_next; + if cur_cmd<=fi_or_else then + if cur_cmd; + end; +done: scanner_status:=normal; +end; + +@ @= +if cur_cmd=string_token then delete_str_ref(cur_mod) + +@ When we begin to process a new \&{if}, we set |if_limit:=if_code|; then +if \&{elseif} or \&{else} or \&{fi} occurs before the current \&{if} +condition has been evaluated, a colon will be inserted. +A construction like `\.{if fi}' would otherwise get \MF\ confused. + +@= +begin p:=get_node(if_node_size); link(p):=cond_ptr; type(p):=if_limit; +name_type(p):=cur_if; if_line_field(p):=if_line; +cond_ptr:=p; if_limit:=if_code; if_line:=line; cur_if:=if_code; +end + +@ @= +begin p:=cond_ptr; if_line:=if_line_field(p); +cur_if:=name_type(p); if_limit:=type(p); cond_ptr:=link(p); +free_node(p,if_node_size); +end + +@ Here's a procedure that changes the |if_limit| code corresponding to +a given value of |cond_ptr|. + +@p procedure change_if_limit(@!l:small_number;@!p:pointer); +label exit; +var q:pointer; +begin if p=cond_ptr then if_limit:=l {that's the easy case} +else begin q:=cond_ptr; + loop@+ begin if q=null then confusion("if"); +@:this can't happen if}{\quad if@> + if link(q)=p then + begin type(q):=l; return; + end; + q:=link(q); + end; + end; +exit:end; + +@ The user is supposed to put colons into the proper parts of conditional +statements. Therefore, \MF\ has to check for their presence. + +@p procedure check_colon; +begin if cur_cmd<>colon then + begin missing_err(":");@/ +@.Missing `:'@> + help2("There should've been a colon after the condition.")@/ + ("I shall pretend that one was there.");@; + back_error; + end; +end; + +@ A condition is started when the |get_x_next| procedure encounters +an |if_test| command; in that case |get_x_next| calls |conditional|, +which is a recursive procedure. +@^recursion@> + +@p procedure conditional; +label exit,done,reswitch,found; +var @!save_cond_ptr:pointer; {|cond_ptr| corresponding to this conditional} +@!new_if_limit:fi_code..else_if_code; {future value of |if_limit|} +@!p:pointer; {temporary register} +begin @;@+save_cond_ptr:=cond_ptr; +reswitch: get_boolean; new_if_limit:=else_if_code; +if internal[tracing_commands]>unity then + @; +found: check_colon; +if cur_exp=true_code then + begin change_if_limit(new_if_limit,save_cond_ptr); + return; {wait for \&{elseif}, \&{else}, or \&{fi}} + end; +@; +done: cur_if:=cur_mod; if_line:=line; +if cur_mod=fi_code then @ +else if cur_mod=else_if_code then goto reswitch +else begin cur_exp:=true_code; new_if_limit:=fi_code; get_x_next; goto found; + end; +exit:end; + +@ In a construction like `\&{if} \&{if} \&{true}: $0=1$: \\{foo} +\&{else}: \\{bar} \&{fi}', the first \&{else} +that we come to after learning that the \&{if} is false is not the +\&{else} we're looking for. Hence the following curious logic is needed. + +@= +loop@+ begin pass_text; + if cond_ptr=save_cond_ptr then goto done + else if cur_mod=fi_code then @; + end + + +@ @= +begin begin_diagnostic; +if cur_exp=true_code then print("{true}")@+else print("{false}"); +end_diagnostic(false); +end + +@ The processing of conditionals is complete except for the following +code, which is actually part of |get_x_next|. It comes into play when +\&{elseif}, \&{else}, or \&{fi} is scanned. + +@= +if cur_mod>if_limit then + if if_limit=if_code then {condition not yet evaluated} + begin missing_err(":"); +@.Missing `:'@> + back_input; cur_sym:=frozen_colon; ins_error; + end + else begin print_err("Extra "); print_cmd_mod(fi_or_else,cur_mod); +@.Extra else@> +@.Extra elseif@> +@.Extra fi@> + help1("I'm ignoring this; it doesn't match any if."); + error; + end +else begin while cur_mod<>fi_code do pass_text; {skip to \&{fi}} + @; + end + +@* \[37] Iterations. +To bring our treatment of |get_x_next| to a close, we need to consider what +\MF\ does when it sees \&{for}, \&{forsuffixes}, and \&{forever}. + +There's a global variable |loop_ptr| that keeps track of the \&{for} loops +that are currently active. If |loop_ptr=null|, no loops are in progress; +otherwise |info(loop_ptr)| points to the iterative text of the current +(innermost) loop, and |link(loop_ptr)| points to the data for any other +loops that enclose the current one. + +A loop-control node also has two other fields, called |loop_type| and +|loop_list|, whose contents depend on the type of loop: + +\yskip\indent|loop_type(loop_ptr)=null| means that |loop_list(loop_ptr)| +points to a list of one-word nodes whose |info| fields point to the +remaining argument values of a suffix list and expression list. + +\yskip\indent|loop_type(loop_ptr)=void| means that the current loop is +`\&{forever}'. + +\yskip\indent|loop_type(loop_ptr)=p>void| means that |value(p)|, +|step_size(p)|, and |final_value(p)| contain the data for an arithmetic +progression. + +\yskip\noindent In the latter case, |p| points to a ``progression node'' +whose first word is not used. (No value could be stored there because the +link field of words in the dynamic memory area cannot be arbitrary.) + +@d loop_list_loc(#)==#+1 {where the |loop_list| field resides} +@d loop_type(#)==info(loop_list_loc(#)) {the type of \&{for} loop} +@d loop_list(#)==link(loop_list_loc(#)) {the remaining list elements} +@d loop_node_size=2 {the number of words in a loop control node} +@d progression_node_size=4 {the number of words in a progression node} +@d step_size(#)==mem[#+2].sc {the step size in an arithmetic progression} +@d final_value(#)==mem[#+3].sc {the final value in an arithmetic progression} + +@= +@!loop_ptr:pointer; {top of the loop-control-node stack} + +@ @= +loop_ptr:=null; + +@ If the expressions that define an arithmetic progression in +a \&{for} loop don't have known numeric values, the |bad_for| +subroutine screams at the user. + +@p procedure bad_for(@!s:str_number); +begin disp_err(null,"Improper "); {show the bad expression above the message} +@.Improper...replaced by 0@> +print(s); print(" has been replaced by 0"); +help4("When you say `for x=a step b until c',")@/ + ("the initial value `a' and the step size `b'")@/ + ("and the final value `c' must have known numeric values.")@/ + ("I'm zeroing this one. Proceed, with fingers crossed."); +put_get_flush_error(0); +end; + +@ Here's what \MF\ does when \&{for}, \&{forsuffixes}, or \&{forever} +has just been scanned. (This code requires slight familiarity with +expression-parsing routines that we have not yet discussed; but it seems +to belong in the present part of the program, even though the author +didn't write it until later. The reader may wish to come back to it.) + +@p procedure begin_iteration; +label continue,done,found; +var @!m:halfword; {|expr_base| (\&{for}) or |suffix_base| (\&{forsuffixes})} +@!n:halfword; {hash address of the current symbol} +@!p,@!q,@!s,@!pp:pointer; {link manipulation registers} +begin m:=cur_mod; n:=cur_sym; s:=get_node(loop_node_size); +if m=start_forever then + begin loop_type(s):=void; p:=null; get_x_next; goto found; + end; +get_symbol; p:=get_node(token_node_size); info(p):=cur_sym; value(p):=m;@/ +get_x_next; +if (cur_cmd<>equals)and(cur_cmd<>assignment) then + begin missing_err("=");@/ +@.Missing `='@> + help3("The next thing in this loop should have been `=' or `:='.")@/ + ("But don't worry; I'll pretend that an equals sign")@/ + ("was present, and I'll look for the values next.");@/ + back_error; + end; +@; +found:@; +@; +resume_iteration; +end; + +@ @= +if cur_cmd<>colon then + begin missing_err(":");@/ +@.Missing `:'@> + help3("The next thing in this loop should have been a `:'.")@/ + ("So I'll pretend that a colon was present;")@/ + ("everything from here to `endfor' will be iterated."); + back_error; + end + +@ We append a special |frozen_repeat_loop| token in place of the +`\&{endfor}' at the end of the loop. This will come through \MF's scanner +at the proper time to cause the loop to be repeated. + +(If the user tries some shenanigan like `\&{for} $\ldots$ \&{let} \&{endfor}', +he will be foiled by the |get_symbol| routine, which keeps frozen +tokens unchanged. Furthermore the |frozen_repeat_loop| is an \&{outer} +token, so it won't be lost accidentally.) + +@ @= +q:=get_avail; info(q):=frozen_repeat_loop; +scanner_status:=loop_defining; warning_info:=n; +info(s):=scan_toks(iteration,p,q,0); scanner_status:=normal;@/ +link(s):=loop_ptr; loop_ptr:=s + +@ @= +eq_type(frozen_repeat_loop):=repeat_loop+outer_tag; +text(frozen_repeat_loop):=" ENDFOR"; + +@ The loop text is inserted into \MF's scanning apparatus by the +|resume_iteration| routine. + +@p procedure resume_iteration; +label not_found,exit; +var @!p,@!q:pointer; {link registers} +begin p:=loop_type(loop_ptr); +if p>void then {|p| points to a progression node} + begin cur_exp:=value(p); + if @ then goto not_found; + cur_type:=known; q:=stash_cur_exp; {make |q| an \&{expr} argument} + value(p):=cur_exp+step_size(p); {set |value(p)| for the next iteration} + end +else if punity then @; +return; +not_found:stop_iteration; +exit:end; + +@ @= +((step_size(p)>0)and(cur_exp>final_value(p)))or@| + ((step_size(p)<0)and(cur_exp= +begin begin_diagnostic; print_nl("{loop value="); +@.loop value=n@> +if (q<>null)and(link(q)=void) then print_exp(q,1) +else show_token_list(q,null,50,0); +print_char("}"); end_diagnostic(false); +end + +@ A level of loop control disappears when |resume_iteration| has decided +not to resume, or when an \&{exitif} construction has removed the loop text +from the input stack. + +@p procedure stop_iteration; +var @!p,@!q:pointer; {the usual} +begin p:=loop_type(loop_ptr); +if p>void then free_node(p,progression_node_size) +else if pnull do + begin p:=info(q); + if p<>null then + if link(p)=void then {it's an \&{expr} parameter} + begin recycle_value(p); free_node(p,value_node_size); + end + else flush_token_list(p); {it's a \&{suffix} or \&{text} parameter} + p:=q; q:=link(q); free_avail(p); + end; + end; +p:=loop_ptr; loop_ptr:=link(p); flush_token_list(info(p)); +free_node(p,loop_node_size); +end; + +@ Now that we know all about loop control, we can finish up +the missing portion of |begin_iteration| and we'll be done. + +The following code is performed after the `\.=' has been scanned in +a \&{for} construction (if |m=expr_base|) or a \&{forsuffixes} construction +(if |m=suffix_base|). + +@= +loop_type(s):=null; q:=loop_list_loc(s); link(q):=null; {|link(q)=loop_list(s)|} +repeat get_x_next; +if m<>expr_base then scan_suffix +else begin if cur_cmd>=colon then if cur_cmd<=comma then goto continue; + scan_expression; + if cur_cmd=step_token then if q=loop_list_loc(s) then + @; + cur_exp:=stash_cur_exp; + end; +link(q):=get_avail; q:=link(q); info(q):=cur_exp; cur_type:=vacuous; +continue: until cur_cmd<>comma; +done: + +@ @= +begin if cur_type<>known then bad_for("initial value"); +pp:=get_node(progression_node_size); value(pp):=cur_exp;@/ +get_x_next; scan_expression; +if cur_type<>known then bad_for("step size"); +step_size(pp):=cur_exp; +if cur_cmd<>until_token then + begin missing_err("until");@/ +@.Missing `until'@> + help2("I assume you meant to say `until' after `step'.")@/ + ("So I'll look for the final value and colon next."); + back_error; + end; +get_x_next; scan_expression; +if cur_type<>known then bad_for("final value"); +final_value(pp):=cur_exp; loop_type(s):=pp; goto done; +end + +@* \[38] File names. +It's time now to fret about file names. Besides the fact that different +operating systems treat files in different ways, we must cope with the +fact that completely different naming conventions are used by different +groups of people. The following programs show what is required for one +particular operating system; similar routines for other systems are not +difficult to devise. +@^system dependencies@> + +\MF\ assumes that a file name has three parts: the name proper; its +``extension''; and a ``file area'' where it is found in an external file +system. The extension of an input file is assumed to be +`\.{.mf}' unless otherwise specified; it is `\.{.log}' on the +transcript file that records each run of \MF; it is `\.{.tfm}' on the font +metric files that describe characters in the fonts \MF\ creates; it is +`\.{.gf}' on the output files that specify generic font information; and it +is `\.{.base}' on the base files written by \.{INIMF} to initialize \MF. +The file area can be arbitrary on input files, but files are usually +output to the user's current area. If an input file cannot be +found on the specified area, \MF\ will look for it on a special system +area; this special area is intended for commonly used input files. + +Simple uses of \MF\ refer only to file names that have no explicit +extension or area. For example, a person usually says `\.{input} \.{cmr10}' +instead of `\.{input} \.{cmr10.new}'. Simple file +names are best, because they make the \MF\ source files portable; +whenever a file name consists entirely of letters and digits, it should be +treated in the same way by all implementations of \MF. However, users +need the ability to refer to other files in their environment, especially +when responding to error messages concerning unopenable files; therefore +we want to let them use the syntax that appears in their favorite +operating system. + +@ \MF\ uses the same conventions that have proved to be satisfactory for +\TeX. In order to isolate the system-dependent aspects of file names, the +@^system dependencies@> +system-independent parts of \MF\ are expressed in terms +of three system-dependent +procedures called |begin_name|, |more_name|, and |end_name|. In +essence, if the user-specified characters of the file name are $c_1\ldots c_n$, +the system-independent driver program does the operations +$$|begin_name|;\,|more_name|(c_1);\,\ldots\,;\,|more_name|(c_n); +\,|end_name|.$$ +These three procedures communicate with each other via global variables. +Afterwards the file name will appear in the string pool as three strings +called |cur_name|\penalty10000\hskip-.05em, +|cur_area|, and |cur_ext|; the latter two are null (i.e., +|""|), unless they were explicitly specified by the user. + +Actually the situation is slightly more complicated, because \MF\ needs +to know when the file name ends. The |more_name| routine is a function +(with side effects) that returns |true| on the calls |more_name|$(c_1)$, +\dots, |more_name|$(c_{n-1})$. The final call |more_name|$(c_n)$ +returns |false|; or, it returns |true| and $c_n$ is the last character +on the current input line. In other words, +|more_name| is supposed to return |true| unless it is sure that the +file name has been completely scanned; and |end_name| is supposed to be able +to finish the assembly of |cur_name|, |cur_area|, and |cur_ext| regardless of +whether $|more_name|(c_n)$ returned |true| or |false|. + +@= +@!cur_name:str_number; {name of file just scanned} +@!cur_area:str_number; {file area just scanned, or \.{""}} +@!cur_ext:str_number; {file extension just scanned, or \.{""}} + +@ The file names we shall deal with for illustrative purposes have the +following structure: If the name contains `\.>' or `\.:', the file area +consists of all characters up to and including the final such character; +otherwise the file area is null. If the remaining file name contains +`\..', the file extension consists of all such characters from the first +remaining `\..' to the end, otherwise the file extension is null. +@^system dependencies@> + +We can scan such file names easily by using two global variables that keep track +of the occurrences of area and extension delimiters: + +@= +@!area_delimiter:pool_pointer; {the most recent `\.>' or `\.:', if any} +@!ext_delimiter:pool_pointer; {the relevant `\..', if any} + +@ Input files that can't be found in the user's area may appear in a standard +system area called |MF_area|. +This system area name will, of course, vary from place to place. +@^system dependencies@> + +@d MF_area=="MFinputs:" +@.MFinputs@> + +@ Here now is the first of the system-dependent routines for file name scanning. +@^system dependencies@> + +@p procedure begin_name; +begin area_delimiter:=0; ext_delimiter:=0; +end; + +@ And here's the second. +@^system dependencies@> + +@p function more_name(@!c:ASCII_code):boolean; +begin if c=" " then more_name:=false +else begin if (c=">")or(c=":") then + begin area_delimiter:=pool_ptr; ext_delimiter:=0; + end + else if (c=".")and(ext_delimiter=0) then ext_delimiter:=pool_ptr; + str_room(1); append_char(c); {contribute |c| to the current string} + more_name:=true; + end; +end; + +@ The third. +@^system dependencies@> + +@p procedure end_name; +begin if str_ptr+3>max_str_ptr then + begin if str_ptr+3>max_strings then + overflow("number of strings",max_strings-init_str_ptr); +@:METAFONT capacity exceeded number of strings}{\quad number of strings@> + max_str_ptr:=str_ptr+3; + end; +if area_delimiter=0 then cur_area:="" +else begin cur_area:=str_ptr; incr(str_ptr); + str_start[str_ptr]:=area_delimiter+1; + end; +if ext_delimiter=0 then + begin cur_ext:=""; cur_name:=make_string; + end +else begin cur_name:=str_ptr; incr(str_ptr); + str_start[str_ptr]:=ext_delimiter; cur_ext:=make_string; + end; +end; + +@ Conversely, here is a routine that takes three strings and prints a file +name that might have produced them. (The routine is system dependent, because +some operating systems put the file area last instead of first.) +@^system dependencies@> + +@= +procedure print_file_name(@!n,@!a,@!e:integer); +begin slow_print(a); slow_print(n); slow_print(e); +end; + +@ Another system-dependent routine is needed to convert three internal +\MF\ strings +to the |name_of_file| value that is used to open files. The present code +allows both lowercase and uppercase letters in the file name. +@^system dependencies@> + +@d append_to_name(#)==begin c:=#; incr(k); + if k<=file_name_size then name_of_file[k]:=xchr[c]; + end + +@p procedure pack_file_name(@!n,@!a,@!e:str_number); +var @!k:integer; {number of positions filled in |name_of_file|} +@!c: ASCII_code; {character being packed} +@!j:pool_pointer; {index into |str_pool|} +begin k:=0; +for j:=str_start[a] to str_start[a+1]-1 do append_to_name(so(str_pool[j])); +for j:=str_start[n] to str_start[n+1]-1 do append_to_name(so(str_pool[j])); +for j:=str_start[e] to str_start[e+1]-1 do append_to_name(so(str_pool[j])); +if k<=file_name_size then name_length:=k@+else name_length:=file_name_size; +for k:=name_length+1 to file_name_size do name_of_file[k]:=' '; +end; + +@ A messier routine is also needed, since base file names must be scanned +before \MF's string mechanism has been initialized. We shall use the +global variable |MF_base_default| to supply the text for default system areas +and extensions related to base files. +@^system dependencies@> + +@d base_default_length=18 {length of the |MF_base_default| string} +@d base_area_length=8 {length of its area part} +@d base_ext_length=5 {length of its `\.{.base}' part} +@d base_extension=".base" {the extension, as a \.{WEB} constant} + +@= +@!MF_base_default:packed array[1..base_default_length] of char; + +@ @= +MF_base_default:='MFbases:plain.base'; +@.MFbases@> +@.plain@> +@^system dependencies@> + +@ @= +if base_default_length>file_name_size then bad:=41; + +@ Here is the messy routine that was just mentioned. It sets |name_of_file| +from the first |n| characters of |MF_base_default|, followed by +|buffer[a..b]|, followed by the last |base_ext_length| characters of +|MF_base_default|. + +We dare not give error messages here, since \MF\ calls this routine before +the |error| routine is ready to roll. Instead, we simply drop excess characters, +since the error will be detected in another way when a strange file name +isn't found. +@^system dependencies@> + +@p procedure pack_buffered_name(@!n:small_number;@!a,@!b:integer); +var @!k:integer; {number of positions filled in |name_of_file|} +@!c: ASCII_code; {character being packed} +@!j:integer; {index into |buffer| or |MF_base_default|} +begin if n+b-a+1+base_ext_length>file_name_size then + b:=a+file_name_size-n-1-base_ext_length; +k:=0; +for j:=1 to n do append_to_name(xord[MF_base_default[j]]); +for j:=a to b do append_to_name(buffer[j]); +for j:=base_default_length-base_ext_length+1 to base_default_length do + append_to_name(xord[MF_base_default[j]]); +if k<=file_name_size then name_length:=k@+else name_length:=file_name_size; +for k:=name_length+1 to file_name_size do name_of_file[k]:=' '; +end; + +@ Here is the only place we use |pack_buffered_name|. This part of the program +becomes active when a ``virgin'' \MF\ is trying to get going, just after +the preliminary initialization, or when the user is substituting another +base file by typing `\.\&' after the initial `\.{**}' prompt. The buffer +contains the first line of input in |buffer[loc..(last-1)]|, where +|loc" "|. + +@= +function open_base_file:boolean; +label found,exit; +var @!j:0..buf_size; {the first space after the file name} +begin j:=loc; +if buffer[loc]="&" then + begin incr(loc); j:=loc; buffer[last]:=" "; + while buffer[j]<>" " do incr(j); + pack_buffered_name(0,loc,j-1); {try first without the system file area} + if w_open_in(base_file) then goto found; + pack_buffered_name(base_area_length,loc,j-1); + {now try the system base file area} + if w_open_in(base_file) then goto found; + wake_up_terminal; + wterm_ln('Sorry, I can''t find that base;',' will try PLAIN.'); +@.Sorry, I can't find...@> + update_terminal; + end; + {now pull out all the stops: try for the system \.{plain} file} +pack_buffered_name(base_default_length-base_ext_length,1,0); +if not w_open_in(base_file) then + begin wake_up_terminal; + wterm_ln('I can''t find the PLAIN base file!'); +@.I can't find PLAIN...@> +@.plain@> + open_base_file:=false; return; + end; +found:loc:=j; open_base_file:=true; +exit:end; + +@ Operating systems often make it possible to determine the exact name (and +possible version number) of a file that has been opened. The following routine, +which simply makes a \MF\ string from the value of |name_of_file|, should +ideally be changed to deduce the full name of file~|f|, which is the file +most recently opened, if it is possible to do this in a \PASCAL\ program. +@^system dependencies@> + +This routine might be called after string memory has overflowed, hence +we dare not use `|str_room|'. + +@p function make_name_string:str_number; +var @!k:1..file_name_size; {index into |name_of_file|} +begin if (pool_ptr+name_length>pool_size)or(str_ptr=max_strings) then + make_name_string:="?" +else begin for k:=1 to name_length do append_char(xord[name_of_file[k]]); + make_name_string:=make_string; + end; +end; +function a_make_name_string(var @!f:alpha_file):str_number; +begin a_make_name_string:=make_name_string; +end; +function b_make_name_string(var @!f:byte_file):str_number; +begin b_make_name_string:=make_name_string; +end; +function w_make_name_string(var @!f:word_file):str_number; +begin w_make_name_string:=make_name_string; +end; + +@ Now let's consider the ``driver'' +routines by which \MF\ deals with file names +in a system-independent manner. First comes a procedure that looks for a +file name in the input by taking the information from the input buffer. +(We can't use |get_next|, because the conversion to tokens would +destroy necessary information.) + +This procedure doesn't allow semicolons or percent signs to be part of +file names, because of other conventions of \MF. The manual doesn't +use semicolons or percents immediately after file names, but some users +no doubt will find it natural to do so; therefore system-dependent +changes to allow such characters in file names should probably +be made with reluctance, and only when an entire file name that +includes special characters is ``quoted'' somehow. +@^system dependencies@> + +@p procedure scan_file_name; +label done; +begin begin_name; +while buffer[loc]=" " do incr(loc); +loop@+begin if (buffer[loc]=";")or(buffer[loc]="%") then goto done; + if not more_name(buffer[loc]) then goto done; + incr(loc); + end; +done: end_name; +end; + +@ The global variable |job_name| contains the file name that was first +\&{input} by the user. This name is extended by `\.{.log}' and `\.{.gf}' and +`\.{.base}' and `\.{.tfm}' in the names of \MF's output files. + +@= +@!job_name:str_number; {principal file name} +@!log_opened:boolean; {has the transcript file been opened?} +@!log_name:str_number; {full name of the log file} + +@ Initially |job_name=0|; it becomes nonzero as soon as the true name is known. +We have |job_name=0| if and only if the `\.{log}' file has not been opened, +except of course for a short time just after |job_name| has become nonzero. + +@=job_name:=0; log_opened:=false; + +@ Here is a routine that manufactures the output file names, assuming that +|job_name<>0|. It ignores and changes the current settings of |cur_area| +and |cur_ext|. + +@d pack_cur_name==pack_file_name(cur_name,cur_area,cur_ext) + +@p procedure pack_job_name(@!s:str_number); {|s = ".log"|, |".gf"|, + |".tfm"|, or |base_extension|} +begin cur_area:=""; cur_ext:=s; +cur_name:=job_name; pack_cur_name; +end; + +@ Actually the main output file extension is usually something like +|".300gf"| instead of just |".gf"|; the additional number indicates the +resolution in pixels per inch, based on the setting of |hppp| when +the file is opened. + +@= +@!gf_ext:str_number; {default extension for the output file} + +@ If some trouble arises when \MF\ tries to open a file, the following +routine calls upon the user to supply another file name. Parameter~|s| +is used in the error message to identify the type of file; parameter~|e| +is the default extension if none is given. Upon exit from the routine, +variables |cur_name|, |cur_area|, |cur_ext|, and |name_of_file| are +ready for another attempt at file opening. + +@p procedure prompt_file_name(@!s,@!e:str_number); +label done; +var @!k:0..buf_size; {index into |buffer|} +begin if interaction=scroll_mode then wake_up_terminal; +if s="input file name" then print_err("I can't find file `") +@.I can't find file x@> +else print_err("I can't write on file `"); +@.I can't write on file x@> +print_file_name(cur_name,cur_area,cur_ext); print("'."); +if e=".mf" then show_context; +print_nl("Please type another "); print(s); +@.Please type...@> +if interaction +clear_terminal; prompt_input(": "); @; +if cur_ext="" then cur_ext:=e; +pack_cur_name; +end; + +@ @= +begin begin_name; k:=first; +while (buffer[k]=" ")and(k +pack_job_name(".log"); +while not a_open_out(log_file) do @; +log_name:=a_make_name_string(log_file); +selector:=log_only; log_opened:=true; +@; +input_stack[input_ptr]:=cur_input; {make sure bottom level is in memory} +print_nl("**"); +@.**@> +l:=input_stack[0].limit_field-1; {last position of first line} +for k:=1 to l do print(buffer[k]); +print_ln; {now the transcript file contains the first line of input} +selector:=old_setting+2; {|log_only| or |term_and_log|} +end; + +@ Sometimes |open_log_file| is called at awkward moments when \MF\ is +unable to print error messages or even to |show_context|. +The |prompt_file_name| routine can result in a |fatal_error|, but the |error| +routine will not be invoked because |log_opened| will be false. + +The normal idea of |batch_mode| is that nothing at all should be written +on the terminal. However, in the unusual case that +no log file could be opened, we make an exception and allow +an explanatory message to be seen. + +Incidentally, the program always refers to the log file as a `\.{transcript +file}', because some systems cannot use the extension `\.{.log}' for +this file. + +@= +begin selector:=term_only; +prompt_file_name("transcript file name",".log"); +end + +@ @= +begin wlog(banner); +slow_print(base_ident); print(" "); +print_int(round_unscaled(internal[day])); print_char(" "); +months:='JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC'; +m:=round_unscaled(internal[month]); +for k:=3*m-2 to 3*m do wlog(months[k]); +print_char(" "); print_int(round_unscaled(internal[year])); print_char(" "); +m:=round_unscaled(internal[time]); +print_dd(m div 60); print_char(":"); print_dd(m mod 60); +end + +@ Here's an example of how these file-name-parsing routines work in practice. +We shall use the macro |set_output_file_name| when it is time to +crank up the output file. + +@d set_output_file_name== + begin if job_name=0 then open_log_file; + pack_job_name(gf_ext); + while not b_open_out(gf_file) do + prompt_file_name("file name for output",gf_ext); + output_file_name:=b_make_name_string(gf_file); + end + +@= +@!gf_file: byte_file; {the generic font output goes here} +@!output_file_name: str_number; {full name of the output file} + +@ @=output_file_name:=0; + +@ Let's turn now to the procedure that is used to initiate file reading +when an `\.{input}' command is being processed. + +@p procedure start_input; {\MF\ will \.{input} something} +label done; +begin @; +if cur_ext="" then cur_ext:=".mf"; +pack_cur_name; +loop@+ begin begin_file_reading; {set up |cur_file| and new level of input} + if a_open_in(cur_file) then goto done; + if cur_area="" then + begin pack_file_name(cur_name,MF_area,cur_ext); + if a_open_in(cur_file) then goto done; + end; + end_file_reading; {remove the level that didn't work} + prompt_file_name("input file name",".mf"); + end; +done: name:=a_make_name_string(cur_file); str_ref[cur_name]:=max_str_ref; +if job_name=0 then + begin job_name:=cur_name; open_log_file; + end; {|open_log_file| doesn't |show_context|, so |limit| + and |loc| needn't be set to meaningful values yet} +if term_offset+length(name)>max_print_line-2 then print_ln +else if (term_offset>0)or(file_offset>0) then print_char(" "); +print_char("("); incr(open_parens); slow_print(name); update_terminal; +if name=str_ptr-1 then {we can conserve string pool space now} + begin flush_string(name); name:=cur_name; + end; +@; +end; + +@ Here we have to remember to tell the |input_ln| routine not to +start with a |get|. If the file is empty, it is considered to +contain a single blank line. +@^system dependencies@> + +@= +begin line:=1; +if input_ln(cur_file,false) then do_nothing; +firm_up_the_line; +buffer[limit]:="%"; first:=limit+1; loc:=start; +end + +@ @= +while token_state and(loc=null) do end_token_list; +if token_state then + begin print_err("File names can't appear within macros"); +@.File names can't...@> + help3("Sorry...I've converted what follows to tokens,")@/ + ("possibly garbaging the name you gave.")@/ + ("Please delete the tokens and insert the name again.");@/ + error; + end; +if file_state then scan_file_name +else begin cur_name:=""; cur_ext:=""; cur_area:=""; + end + +@* \[39] Introduction to the parsing routines. +We come now to the central nervous system that sparks many of \MF's activities. +By evaluating expressions, from their primary constituents to ever larger +subexpressions, \MF\ builds the structures that ultimately define fonts of type. + +Four mutually recursive subroutines are involved in this process: We call them +$$\hbox{|scan_primary|, |scan_secondary|, |scan_tertiary|, +and |scan_expression|.}$$ +@^recursion@> +Each of them is parameterless and begins with the first token to be scanned +already represented in |cur_cmd|, |cur_mod|, and |cur_sym|. After execution, +the value of the primary or secondary or tertiary or expression that was +found will appear in the global variables |cur_type| and |cur_exp|. The +token following the expression will be represented in |cur_cmd|, |cur_mod|, +and |cur_sym|. + +Technically speaking, the parsing algorithms are ``LL(1),'' more or less; +backup mechanisms have been added in order to provide reasonable error +recovery. + +@= +@!cur_type:small_number; {the type of the expression just found} +@!cur_exp:integer; {the value of the expression just found} + +@ @= +cur_exp:=0; + +@ Many different kinds of expressions are possible, so it is wise to have +precise descriptions of what |cur_type| and |cur_exp| mean in all cases: + +\smallskip\hang +|cur_type=vacuous| means that this expression didn't turn out to have a +value at all, because it arose from a \&{begingroup}$\,\ldots\,$\&{endgroup} +construction in which there was no expression before the \&{endgroup}. +In this case |cur_exp| has some irrelevant value. + +\smallskip\hang +|cur_type=boolean_type| means that |cur_exp| is either |true_code| +or |false_code|. + +\smallskip\hang +|cur_type=unknown_boolean| means that |cur_exp| points to a capsule +node that is in +a ring of equivalent booleans whose value has not yet been defined. + +\smallskip\hang +|cur_type=string_type| means that |cur_exp| is a string number (i.e., an +integer in the range |0<=cur_exp= +function stash_cur_exp:pointer; +var @!p:pointer; {the capsule that will be returned} +begin case cur_type of +unknown_types,transform_type,pair_type,dependent,proto_dependent, + independent:p:=cur_exp; +othercases begin p:=get_node(value_node_size); name_type(p):=capsule; + type(p):=cur_type; value(p):=cur_exp; + end +endcases;@/ +cur_type:=vacuous; link(p):=void; stash_cur_exp:=p; +end; + +@ The inverse of |stash_cur_exp| is the following procedure, which +deletes an unnecessary capsule and puts its contents into |cur_type| +and |cur_exp|. + +The program steps of \MF\ can be divided into two categories: those in +which |cur_type| and |cur_exp| are ``alive'' and those in which they are +``dead,'' in the sense that |cur_type| and |cur_exp| contain relevant +information or not. It's important not to ignore them when they're alive, +and it's important not to pay attention to them when they're dead. + +There's also an intermediate category: If |cur_type=vacuous|, then +|cur_exp| is irrelevant, hence we can proceed without caring if |cur_type| +and |cur_exp| are alive or dead. In such cases we say that |cur_type| +and |cur_exp| are {\sl dormant}. It is permissible to call |get_x_next| +only when they are alive or dormant. + +The \\{stash} procedure above assumes that |cur_type| and |cur_exp| +are alive or dormant. The \\{unstash} procedure assumes that they are +dead or dormant; it resuscitates them. + +@= +procedure unstash_cur_exp(@!p:pointer); +begin cur_type:=type(p); +case cur_type of +unknown_types,transform_type,pair_type,dependent,proto_dependent, + independent: cur_exp:=p; +othercases begin cur_exp:=value(p); + free_node(p,value_node_size); + end +endcases;@/ +end; + +@ The following procedure prints the values of expressions in an +abbreviated format. If its first parameter |p| is null, the value of +|(cur_type,cur_exp)| is displayed; otherwise |p| should be a capsule +containing the desired value. The second parameter controls the amount of +output. If it is~0, dependency lists will be abbreviated to +`\.{linearform}' unless they consist of a single term. If it is greater +than~1, complicated structures (pens, pictures, and paths) will be displayed +in full. +@.linearform@> + +@= +@t\4@>@@; +@t\4@>@@; +procedure print_exp(@!p:pointer;@!verbosity:small_number); +var @!restore_cur_exp:boolean; {should |cur_exp| be restored?} +@!t:small_number; {the type of the expression} +@!v:integer; {the value of the expression} +@!q:pointer; {a big node being displayed} +begin if p<>null then restore_cur_exp:=false +else begin p:=stash_cur_exp; restore_cur_exp:=true; + end; +t:=type(p); +if t; +if restore_cur_exp then unstash_cur_exp(p); +end; + +@ @= +case t of +vacuous:print("vacuous"); +boolean_type:if v=true_code then print("true")@+else print("false"); +unknown_types,numeric_type:@; +string_type:begin print_char(""""); slow_print(v); print_char(""""); + end; +pen_type,future_pen,path_type,picture_type:@; +transform_type,pair_type:if v=null then print_type(t) + else @; +known:print_scaled(v); +dependent,proto_dependent:print_dp(t,v,verbosity); +independent:print_variable_name(p); +othercases confusion("exp") +@:this can't happen exp}{\quad exp@> +endcases + +@ @= +begin print_char("("); q:=v+big_node_size[t]; +repeat if type(v)=known then print_scaled(value(v)) +else if type(v)=independent then print_variable_name(v) +else print_dp(type(v),dep_list(v),verbosity); +v:=v+2; +if v<>q then print_char(","); +until v=q; +print_char(")"); +end + +@ Values of type \&{picture}, \&{path}, and \&{pen} are displayed verbosely +in the log file only, unless the user has given a positive value to +\\{tracingonline}. + +@= +if verbosity<=1 then print_type(t) +else begin if selector=term_and_log then + if internal[tracing_online]<=0 then + begin selector:=term_only; + print_type(t); print(" (see the transcript file)"); + selector:=term_and_log; + end; + case t of + pen_type:print_pen(v,"",false); + future_pen:print_path(v," (future pen)",false); + path_type:print_path(v,"",false); + picture_type:begin cur_edges:=v; print_edges("",false,0,0); + end; + end; {there are no other cases} + end + +@ @= +procedure print_dp(@!t:small_number;@!p:pointer;@!verbosity:small_number); +var @!q:pointer; {the node following |p|} +begin q:=link(p); +if (info(q)=null) or (verbosity>0) then print_dependency(p,t) +else print("linearform"); +@.linearform@> +end; + +@ The displayed name of a variable in a ring will not be a capsule unless +the ring consists entirely of capsules. + +@= +begin print_type(t); +if v<>null then + begin print_char(" "); + while (name_type(v)=capsule) and (v<>p) do v:=value(v); + print_variable_name(v); + end; +end + +@ When errors are detected during parsing, it is often helpful to +display an expression just above the error message, using |exp_err| +or |disp_err| instead of |print_err|. + +@d exp_err(#)==disp_err(null,#) {displays the current expression} + +@= +procedure disp_err(@!p:pointer;@!s:str_number); +begin if interaction=error_stop_mode then wake_up_terminal; +print_nl(">> "); +@.>>@> +print_exp(p,1); {``medium verbose'' printing of the expression} +if s<>"" then + begin print_nl("! "); print(s); +@.!\relax@> + end; +end; + +@ If |cur_type| and |cur_exp| contain relevant information that should +be recycled, we will use the following procedure, which changes |cur_type| +to |known| and stores a given value in |cur_exp|. We can think of |cur_type| +and |cur_exp| as either alive or dormant after this has been done, +because |cur_exp| will not contain a pointer value. + +@= +procedure flush_cur_exp(@!v:scaled); +begin case cur_type of +unknown_types,transform_type,pair_type,@|dependent,proto_dependent,independent: + begin recycle_value(cur_exp); free_node(cur_exp,value_node_size); + end; +pen_type: delete_pen_ref(cur_exp); +string_type:delete_str_ref(cur_exp); +future_pen,path_type: toss_knot_list(cur_exp); +picture_type:toss_edges(cur_exp); +othercases do_nothing +endcases;@/ +cur_type:=known; cur_exp:=v; +end; + +@ There's a much more general procedure that is capable of releasing +the storage associated with any two-word value packet. + +@= +procedure recycle_value(@!p:pointer); +label done; +var @!t:small_number; {a type code} +@!v:integer; {a value} +@!vv:integer; {another value} +@!q,@!r,@!s,@!pp:pointer; {link manipulation registers} +begin t:=type(p); +if t; +dependent,proto_dependent:@; +independent:@; +token_list,structured:confusion("recycle"); +@:this can't happen recycle}{\quad recycle@> +unsuffixed_macro,suffixed_macro:delete_mac_ref(value(p)); +end; {there are no other cases} +type(p):=undefined; +end; + +@ @= +if v<>null then + begin q:=v+big_node_size[t]; + repeat q:=q-2; recycle_value(q); + until q=v; + free_node(v,big_node_size[t]); + end + +@ @= +begin q:=dep_list(p); +while info(q)<>null do q:=link(q); +link(prev_dep(p)):=link(q); +prev_dep(link(q)):=prev_dep(p); +link(q):=null; flush_node_list(dep_list(p)); +end + +@ When an independent variable disappears, it simply fades away, unless +something depends on it. In the latter case, a dependent variable whose +coefficient of dependence is maximal will take its place. +The relevant algorithm is due to Ignacio~A. Zabala, who implemented it +as part of his Ph.D. thesis (Stanford University, December 1982). +@^Zabala Salelles, Ignacio Andr\'es@> + +For example, suppose that variable $x$ is being recycled, and that the +only variables depending on~$x$ are $y=2x+a$ and $z=x+b$. In this case +we want to make $y$ independent and $z=.5y-.5a+b$; no other variables +will depend on~$y$. If $\\{tracingequations}>0$ in this situation, +we will print `\.{\#\#\# -2x=-y+a}'. + +There's a slight complication, however: An independent variable $x$ +can occur both in dependency lists and in proto-dependency lists. +This makes it necessary to be careful when deciding which coefficient +is maximal. + +Furthermore, this complication is not so slight when +a proto-dependent variable is chosen to become independent. For example, +suppose that $y=2x+100a$ is proto-dependent while $z=x+b$ is dependent; +then we must change $z=.5y-50a+b$ to a proto-dependency, because of the +large coefficient `50'. + +In order to deal with these complications without wasting too much time, +we shall link together the occurrences of~$x$ among all the linear +dependencies, maintaining separate lists for the dependent and +proto-dependent cases. + +@= +begin max_c[dependent]:=0; max_c[proto_dependent]:=0;@/ +max_link[dependent]:=null; max_link[proto_dependent]:=null;@/ +q:=link(dep_head); +while q<>dep_head do + begin s:=value_loc(q); {now |link(s)=dep_list(q)|} + loop@+ begin r:=link(s); + if info(r)=null then goto done; + if info(r)<>p then s:=r + else begin t:=type(q); link(s):=link(r); info(r):=q; + if abs(value(r))>max_c[t] then + @ + else begin link(r):=max_link[t]; max_link[t]:=r; + end; + end; + end; +done: q:=link(r); + end; +if (max_c[dependent]>0)or(max_c[proto_dependent]>0) then + @; +end + +@ The code for independency removal makes use of three two-word arrays. + +@= +@!max_c:array[dependent..proto_dependent] of integer; + {max coefficient magnitude} +@!max_ptr:array[dependent..proto_dependent] of pointer; + {where |p| occurs with |max_c|} +@!max_link:array[dependent..proto_dependent] of pointer; + {other occurrences of |p|} + +@ @= +begin if max_c[t]>0 then + begin link(max_ptr[t]):=max_link[t]; max_link[t]:=max_ptr[t]; + end; +max_c[t]:=abs(value(r)); max_ptr[t]:=r; +end + +@ @= +begin if (max_c[dependent] div @'10000 >= + max_c[proto_dependent]) then + t:=dependent +else t:=proto_dependent; +@; +t:=dependent+proto_dependent-t; {complement |t|} +if max_c[t]>0 then {we need to pick up an unchosen dependency} + begin link(max_ptr[t]):=max_link[t]; max_link[t]:=max_ptr[t]; + end; +if t<>dependent then @ +else @; +flush_node_list(s); +if fix_needed then fix_dependencies; +check_arith; +end + +@ Let |s=max_ptr[t]|. At this point we have $|value|(s)=\pm|max_c|[t]$, +and |info(s)| points to the dependent variable~|pp| of type~|t| from +whose dependency list we have removed node~|s|. We must reinsert +node~|s| into the dependency list, with coefficient $-1.0$, and with +|pp| as the new independent variable. Since |pp| will have a larger serial +number than any other variable, we can put node |s| at the head of the +list. + +@= +s:=max_ptr[t]; pp:=info(s); v:=value(s); +if t=dependent then value(s):=-fraction_one@+else value(s):=-unity; +r:=dep_list(pp); link(s):=r; +while info(r)<>null do r:=link(r); +q:=link(r); link(r):=null; +prev_dep(q):=prev_dep(pp); link(prev_dep(pp)):=q; +new_indep(pp); +if cur_exp=pp then if cur_type=t then cur_type:=independent; +if internal[tracing_equations]>0 then @ + +@ Now $(-v)$ times the formerly independent variable~|p| is being replaced +by the dependency list~|s|. + +@= +if interesting(p) then + begin begin_diagnostic; print_nl("### "); +@:]]]\#\#\#_}{\.{\#\#\#}@> + if v>0 then print_char("-"); + if t=dependent then vv:=round_fraction(max_c[dependent]) + else vv:=max_c[proto_dependent]; + if vv<>unity then print_scaled(vv); + print_variable_name(p); + while value(p) mod s_scale>0 do + begin print("*4"); value(p):=value(p)-2; + end; + if t=dependent then print_char("=")@+else print(" = "); + print_dependency(s,t); + end_diagnostic(false); + end + +@ Finally, there are dependent and proto-dependent variables whose +dependency lists must be brought up to date. + +@= +for t:=dependent to proto_dependent do + begin r:=max_link[t]; + while r<>null do + begin q:=info(r); + dep_list(q):=p_plus_fq(dep_list(q),@| + make_fraction(value(r),-v),s,t,dependent); + if dep_list(q)=dep_final then make_known(q,dep_final); + q:=r; r:=link(r); free_node(q,dep_node_size); + end; + end + +@ @= +for t:=dependent to proto_dependent do + begin r:=max_link[t]; + while r<>null do + begin q:=info(r); + if t=dependent then {for safety's sake, we change |q| to |proto_dependent|} + begin if cur_exp=q then if cur_type=dependent then + cur_type:=proto_dependent; + dep_list(q):=p_over_v(dep_list(q),unity,dependent,proto_dependent); + type(q):=proto_dependent; value(r):=round_fraction(value(r)); + end; + dep_list(q):=p_plus_fq(dep_list(q),@| + make_scaled(value(r),-v),s,proto_dependent,proto_dependent); + if dep_list(q)=dep_final then make_known(q,dep_final); + q:=r; r:=link(r); free_node(q,dep_node_size); + end; + end + +@ Here are some routines that provide handy combinations of actions +that are often needed during error recovery. For example, +`|flush_error|' flushes the current expression, replaces it by +a given value, and calls |error|. + +Errors often are detected after an extra token has already been scanned. +The `\\{put\_get}' routines put that token back before calling |error|; +then they get it back again. (Or perhaps they get another token, if +the user has changed things.) + +@= +procedure flush_error(@!v:scaled);@+begin error; flush_cur_exp(v);@+end; +@# +procedure@?back_error; forward;@t\2@>@/ +procedure@?get_x_next; forward;@t\2@>@/ +@# +procedure put_get_error;@+begin back_error; get_x_next;@+end; +@# +procedure put_get_flush_error(@!v:scaled);@+begin put_get_error; + flush_cur_exp(v);@+end; + +@ A global variable called |var_flag| is set to a special command code +just before \MF\ calls |scan_expression|, if the expression should be +treated as a variable when this command code immediately follows. For +example, |var_flag| is set to |assignment| at the beginning of a +statement, because we want to know the {\sl location\/} of a variable at +the left of `\.{:=}', not the {\sl value\/} of that variable. + +The |scan_expression| subroutine calls |scan_tertiary|, +which calls |scan_secondary|, which calls |scan_primary|, which sets +|var_flag:=0|. In this way each of the scanning routines ``knows'' +when it has been called with a special |var_flag|, but |var_flag| is +usually zero. + +A variable preceding a command that equals |var_flag| is converted to a +token list rather than a value. Furthermore, an `\.{=}' sign following an +expression with |var_flag=assignment| is not considered to be a relation +that produces boolean expressions. + + +@= +@!var_flag:0..max_command_code; {command that wants a variable} + +@ @= +var_flag:=0; + +@* \[40] Parsing primary expressions. +The first parsing routine, |scan_primary|, is also the most complicated one, +since it involves so many different cases. But each case---with one +exception---is fairly simple by itself. + +When |scan_primary| begins, the first token of the primary to be scanned +should already appear in |cur_cmd|, |cur_mod|, and |cur_sym|. The values +of |cur_type| and |cur_exp| should be either dead or dormant, as explained +earlier. If |cur_cmd| is not between |min_primary_command| and +|max_primary_command|, inclusive, a syntax error will be signalled. + +@= +procedure scan_primary; +label restart, done, done1, done2; +var @!p,@!q,@!r:pointer; {for list manipulation} +@!c:quarterword; {a primitive operation code} +@!my_var_flag:0..max_command_code; {initial value of |var_flag|} +@!l_delim,@!r_delim:pointer; {hash addresses of a delimiter pair} +@@; +begin my_var_flag:=var_flag; var_flag:=0; +restart:check_arith; +@; +case cur_cmd of +left_delimiter:@; +begin_group:@; +string_token:@; +numeric_token:@; +nullary:@; +unary,type_name,cycle,plus_or_minus:@; +primary_binary:@; +str_op:@; +internal_quantity:@; +capsule_token:make_exp_copy(cur_mod); +tag_token:@; +othercases begin bad_exp("A primary"); goto restart; +@.A primary expression...@> + end +endcases;@/ +get_x_next; {the routines |goto done| if they don't want this} +done: if cur_cmd=left_bracket then + if cur_type>=known then @; +end; + +@ Errors at the beginning of expressions are flagged by |bad_exp|. + +@p procedure bad_exp(@!s:str_number); +var save_flag:0..max_command_code; +begin print_err(s); print(" expression can't begin with `"); +print_cmd_mod(cur_cmd,cur_mod); print_char("'"); +help4("I'm afraid I need some sort of value in order to continue,")@/ + ("so I've tentatively inserted `0'. You may want to")@/ + ("delete this zero and insert something else;")@/ + ("see Chapter 27 of The METAFONTbook for an example."); +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> +back_input; cur_sym:=0; cur_cmd:=numeric_token; cur_mod:=0; ins_error;@/ +save_flag:=var_flag; var_flag:=0; get_x_next; +var_flag:=save_flag; +end; + +@ @= +debug if panicking then check_mem(false);@+gubed@;@/ +if interrupt<>0 then if OK_to_interrupt then + begin back_input; check_interrupt; get_x_next; + end + +@ @= +begin l_delim:=cur_sym; r_delim:=cur_mod; get_x_next; scan_expression; +if (cur_cmd=comma) and (cur_type>=known) then + @ +else check_delimiter(l_delim,r_delim); +end + +@ The |stash_in| subroutine puts the current (numeric) expression into a field +within a ``big node.'' + +@p procedure stash_in(@!p:pointer); +var @!q:pointer; {temporary register} +begin type(p):=cur_type; +if cur_type=known then value(p):=cur_exp +else begin if cur_type=independent then + @ + else begin mem[value_loc(p)]:=mem[value_loc(cur_exp)]; + {|dep_list(p):=dep_list(cur_exp)| and |prev_dep(p):=prev_dep(cur_exp)|} + link(prev_dep(p)):=p; + end; + free_node(cur_exp,value_node_size); + end; +cur_type:=vacuous; +end; + +@ In rare cases the current expression can become |independent|. There +may be many dependency lists pointing to such an independent capsule, +so we can't simply move it into place within a big node. Instead, +we copy it, then recycle it. + +@ @= +begin q:=single_dependency(cur_exp); +if q=dep_final then + begin type(p):=known; value(p):=0; free_node(q,dep_node_size); + end +else begin type(p):=dependent; new_dep(p,q); + end; +recycle_value(cur_exp); +end + +@ @= +begin p:=get_node(value_node_size); type(p):=pair_type; name_type(p):=capsule; +init_big_node(p); q:=value(p); stash_in(x_part_loc(q));@/ +get_x_next; scan_expression; +if cur_type + help4("I thought you were giving me a pair `(x,y)'; but")@/ + ("after finding a nice xpart `x' I found a ypart `y'")@/ + ("that isn't of numeric type. So I've changed y to zero.")@/ + ("(The y that I didn't like appears above the error message.)"); + put_get_flush_error(0); + end; +stash_in(y_part_loc(q)); +check_delimiter(l_delim,r_delim); +cur_type:=pair_type; cur_exp:=p; +end + +@ The local variable |group_line| keeps track of the line +where a \&{begingroup} command occurred; this will be useful +in an error message if the group doesn't actually end. + +@= +@!group_line:integer; {where a group began} + +@ @= +begin group_line:=line; +if internal[tracing_commands]>0 then show_cur_cmd_mod; +save_boundary_item(p); +repeat do_statement; {ends with |cur_cmd>=semicolon|} +until cur_cmd<>semicolon; +if cur_cmd<>end_group then + begin print_err("A group begun on line "); +@.A group...never ended@> + print_int(group_line); + print(" never ended"); + help2("I saw a `begingroup' back there that hasn't been matched")@/ + ("by `endgroup'. So I've inserted `endgroup' now."); + back_error; cur_cmd:=end_group; + end; +unsave; {this might change |cur_type|, if independent variables are recycled} +if internal[tracing_commands]>0 then show_cur_cmd_mod; +end + +@ @= +begin cur_type:=string_type; cur_exp:=cur_mod; +end + +@ Later we'll come to procedures that perform actual operations like +addition, square root, and so on; our purpose now is to do the parsing. +But we might as well mention those future procedures now, so that the +suspense won't be too bad: + +\smallskip +|do_nullary(c)| does primitive operations that have no operands (e.g., +`\&{true}' or `\&{pencircle}'); + +\smallskip +|do_unary(c)| applies a primitive operation to the current expression; + +\smallskip +|do_binary(p,c)| applies a primitive operation to the capsule~|p| +and the current expression. + +@=do_nullary(cur_mod) + +@ @= +begin c:=cur_mod; get_x_next; scan_primary; do_unary(c); goto done; +end + +@ A numeric token might be a primary by itself, or it might be the +numerator of a fraction composed solely of numeric tokens, or it might +multiply the primary that follows (provided that the primary doesn't begin +with a plus sign or a minus sign). The code here uses the facts that +|max_primary_command=plus_or_minus| and +|max_primary_command-1=numeric_token|. If a fraction is found that is less +than unity, we try to retain higher precision when we use it in scalar +multiplication. + +@= +@!num,@!denom:scaled; {for primaries that are fractions, like `1/2'} + +@ @= +begin cur_exp:=cur_mod; cur_type:=known; get_x_next; +if cur_cmd<>slash then + begin num:=0; denom:=0; + end +else begin get_x_next; + if cur_cmd<>numeric_token then + begin back_input; + cur_cmd:=slash; cur_mod:=over; cur_sym:=frozen_slash; + goto done; + end; + num:=cur_exp; denom:=cur_mod; + if denom=0 then @ + else cur_exp:=make_scaled(num,denom); + check_arith; get_x_next; + end; +if cur_cmd>=min_primary_command then + if cur_cmdplus_or_minus|} + begin p:=stash_cur_exp; scan_primary; + if (abs(num)>=abs(denom))or(cur_type= +begin print_err("Division by zero"); +@.Division by zero@> +help1("I'll pretend that you meant to divide by 1."); error; +end + +@ @= +begin c:=cur_mod; get_x_next; scan_expression; +if cur_cmd<>of_token then + begin missing_err("of"); print(" for "); print_cmd_mod(primary_binary,c); +@.Missing `of'@> + help1("I've got the first argument; will look now for the other."); + back_error; + end; +p:=stash_cur_exp; get_x_next; scan_primary; do_binary(p,c); goto done; +end + +@ @= +begin get_x_next; scan_suffix; old_setting:=selector; selector:=new_string; +show_token_list(cur_exp,null,100000,0); flush_token_list(cur_exp); +cur_exp:=make_string; selector:=old_setting; cur_type:=string_type; +goto done; +end + +@ If an internal quantity appears all by itself on the left of an +assignment, we return a token list of length one, containing the address +of the internal quantity plus |hash_end|. (This accords with the conventions +of the save stack, as described earlier.) + +@= +begin q:=cur_mod; +if my_var_flag=assignment then + begin get_x_next; + if cur_cmd=assignment then + begin cur_exp:=get_avail; + info(cur_exp):=q+hash_end; cur_type:=token_list; goto done; + end; + back_input; + end; +cur_type:=known; cur_exp:=internal[q]; +end + +@ The most difficult part of |scan_primary| has been saved for last, since +it was necessary to build up some confidence first. We can now face the task +of scanning a variable. + +As we scan a variable, we build a token list containing the relevant +names and subscript values, simultaneously following along in the +``collective'' structure to see if we are actually dealing with a macro +instead of a value. + +The local variables |pre_head| and |post_head| will point to the beginning +of the prefix and suffix lists; |tail| will point to the end of the list +that is currently growing. + +Another local variable, |tt|, contains partial information about the +declared type of the variable-so-far. If |tt>=unsuffixed_macro|, the +relation |tt=type(q)| will always hold. If |tt=undefined|, the routine +doesn't bother to update its information about type. And if +|undefined= +@!pre_head,@!post_head,@!tail:pointer; + {prefix and suffix list variables} +@!tt:small_number; {approximation to the type of the variable-so-far} +@!t:pointer; {a token} +@!macro_ref:pointer; {reference count for a suffixed macro} + +@ @= +begin fast_get_avail(pre_head); tail:=pre_head; post_head:=null; tt:=vacuous; +loop@+ begin t:=cur_tok; link(tail):=t; + if tt<>undefined then + begin @; + if tt>=unsuffixed_macro then + @; + end; + get_x_next; tail:=t; + if cur_cmd=left_bracket then + @; + if cur_cmd>max_suffix_token then goto done1; + if cur_cmd; +end + +@ @= +begin link(tail):=null; +if tt>unsuffixed_macro then {|tt=suffixed_macro|} + begin post_head:=get_avail; tail:=post_head; link(tail):=t;@/ + tt:=undefined; macro_ref:=value(q); add_mac_ref(macro_ref); + end +else @; +end + +@ @= +begin get_x_next; scan_expression; +if cur_cmd<>right_bracket then + @ +else begin if cur_type<>known then bad_subscript; + cur_cmd:=numeric_token; cur_mod:=cur_exp; cur_sym:=0; + end; +end + +@ The left bracket that we thought was introducing a subscript might have +actually been the left bracket in a mediation construction like `\.{x[a,b]}'. +So we don't issue an error message at this point; but we do want to back up +so as to avoid any embarrassment about our incorrect assumption. + +@= +begin back_input; {that was the token following the current expression} +back_expr; cur_cmd:=left_bracket; cur_mod:=0; cur_sym:=frozen_left_bracket; +end + +@ Here's a routine that puts the current expression back to be read again. + +@p procedure back_expr; +var @!p:pointer; {capsule token} +begin p:=stash_cur_exp; link(p):=null; back_list(p); +end; + +@ Unknown subscripts lead to the following error message. + +@p procedure bad_subscript; +begin exp_err("Improper subscript has been replaced by zero"); +@.Improper subscript...@> +help3("A bracketed subscript must have a known numeric value;")@/ + ("unfortunately, what I found was the value that appears just")@/ + ("above this error message. So I'll try a zero subscript."); +flush_error(0); +end; + +@ Every time we call |get_x_next|, there's a chance that the variable we've +been looking at will disappear. Thus, we cannot safely keep |q| pointing +into the variable structure; we need to start searching from the root each time. + +@= +@^inner loop@> +begin p:=link(pre_head); q:=info(p); tt:=undefined; +if eq_type(q) mod outer_tag=tag_token then + begin q:=equiv(q); + if q=null then goto done2; + loop@+ begin p:=link(p); + if p=null then + begin tt:=type(q); goto done2; + end; + if type(q)<>structured then goto done2; + q:=link(attr_head(q)); {the |collective_subscript| attribute} + if p>=hi_mem_min then {it's not a subscript} + begin repeat q:=link(q); + until attr_loc(q)>=info(p); + if attr_loc(q)>info(p) then goto done2; + end; + end; + end; +done2:end + +@ How do things stand now? Well, we have scanned an entire variable name, +including possible subscripts and/or attributes; |cur_cmd|, |cur_mod|, and +|cur_sym| represent the token that follows. If |post_head=null|, a +token list for this variable name starts at |link(pre_head)|, with all +subscripts evaluated. But if |post_head<>null|, the variable turned out +to be a suffixed macro; |pre_head| is the head of the prefix list, while +|post_head| is the head of a token list containing both `\.{\AT!}' and +the suffix. + +Our immediate problem is to see if this variable still exists. (Variable +structures can change drastically whenever we call |get_x_next|; users +aren't supposed to do this, but the fact that it is possible means that +we must be cautious.) + +The following procedure prints an error message when a variable +unexpectedly disappears. Its help message isn't quite right for +our present purposes, but we'll be able to fix that up. + +@p procedure obliterated(@!q:pointer); +begin print_err("Variable "); show_token_list(q,null,1000,0); +print(" has been obliterated"); +@.Variable...obliterated@> +help5("It seems you did a nasty thing---probably by accident,")@/ + ("but nevertheless you nearly hornswoggled me...")@/ + ("While I was evaluating the right-hand side of this")@/ + ("command, something happened, and the left-hand side")@/ + ("is no longer a variable! So I won't change anything."); +end; + +@ If the variable does exist, we also need to check +for a few other special cases before deciding that a plain old ordinary +variable has, indeed, been scanned. + +@= +if post_head<>null then @; +q:=link(pre_head); free_avail(pre_head); +if cur_cmd=my_var_flag then + begin cur_type:=token_list; cur_exp:=q; goto done; + end; +p:=find_variable(q); +if p<>null then make_exp_copy(p) +else begin obliterated(q);@/ + help_line[2]:="While I was evaluating the suffix of this variable,"; + help_line[1]:="something was redefined, and it's no longer a variable!"; + help_line[0]:="In order to get back on my feet, I've inserted `0' instead."; + put_get_flush_error(0); + end; +flush_node_list(q); goto done + +@ The only complication associated with macro calling is that the prefix +and ``at'' parameters must be packaged in an appropriate list of lists. + +@= +begin p:=get_avail; info(pre_head):=link(pre_head); link(pre_head):=p; +info(p):=t; macro_call(value(q),pre_head,null); get_x_next; goto restart; +end + +@ If the ``variable'' that turned out to be a suffixed macro no longer exists, +we don't care, because we have reserved a pointer (|macro_ref|) to its +token list. + +@= +begin back_input; p:=get_avail; q:=link(post_head); +info(pre_head):=link(pre_head); link(pre_head):=post_head; +info(post_head):=q; link(post_head):=p; info(p):=link(q); link(q):=null; +macro_call(macro_ref,pre_head,null); decr(ref_count(macro_ref)); +get_x_next; goto restart; +end + +@ Our remaining job is simply to make a copy of the value that has been +found. Some cases are harder than others, but complexity arises solely +because of the multiplicity of possible cases. + +@= +@t\4@>@@; +procedure make_exp_copy(@!p:pointer); +label restart; +var @!q,@!r,@!t:pointer; {registers for list manipulation} +begin restart: cur_type:=type(p); +case cur_type of +vacuous,boolean_type,known:cur_exp:=value(p); +unknown_types:cur_exp:=new_ring_entry(p); +string_type:begin cur_exp:=value(p); add_str_ref(cur_exp); + end; +pen_type:begin cur_exp:=value(p); add_pen_ref(cur_exp); + end; +picture_type:cur_exp:=copy_edges(value(p)); +path_type,future_pen:cur_exp:=copy_path(value(p)); +transform_type,pair_type:@; +dependent,proto_dependent:encapsulate(copy_dep_list(dep_list(p))); +numeric_type:begin new_indep(p); goto restart; + end; +independent: begin q:=single_dependency(p); + if q=dep_final then + begin cur_type:=known; cur_exp:=0; free_node(q,dep_node_size); + end + else begin cur_type:=dependent; encapsulate(q); + end; + end; +othercases confusion("copy") +@:this can't happen copy}{\quad copy@> +endcases; +end; + +@ The |encapsulate| subroutine assumes that |dep_final| is the +tail of dependency list~|p|. + +@= +procedure encapsulate(@!p:pointer); +begin cur_exp:=get_node(value_node_size); type(cur_exp):=cur_type; +name_type(cur_exp):=capsule; new_dep(cur_exp,p); +end; + +@ The most tedious case arises when the user refers to a +\&{pair} or \&{transform} variable; we must copy several fields, +each of which can be |independent|, |dependent|, |proto_dependent|, +or |known|. + +@= +begin if value(p)=null then init_big_node(p); +t:=get_node(value_node_size); name_type(t):=capsule; type(t):=cur_type; +init_big_node(t);@/ +q:=value(p)+big_node_size[cur_type]; r:=value(t)+big_node_size[cur_type]; +repeat q:=q-2; r:=r-2; install(r,q); +until q=value(p); +cur_exp:=t; +end + +@ The |install| procedure copies a numeric field~|q| into field~|r| of +a big node that will be part of a capsule. + +@= +procedure install(@!r,@!q:pointer); +var p:pointer; {temporary register} +begin if type(q)=known then + begin value(r):=value(q); type(r):=known; + end +else if type(q)=independent then + begin p:=single_dependency(q); + if p=dep_final then + begin type(r):=known; value(r):=0; free_node(p,dep_node_size); + end + else begin type(r):=dependent; new_dep(r,p); + end; + end + else begin type(r):=type(q); new_dep(r,copy_dep_list(dep_list(q))); + end; +end; + +@ Expressions of the form `\.{a[b,c]}' are converted into +`\.{b+a*(c-b)}', without checking the types of \.b~or~\.c, +provided that \.a is numeric. + +@= +begin p:=stash_cur_exp; get_x_next; scan_expression; +if cur_cmd<>comma then + begin @; + unstash_cur_exp(p); + end +else begin q:=stash_cur_exp; get_x_next; scan_expression; + if cur_cmd<>right_bracket then + begin missing_err("]");@/ +@.Missing `]'@> + help3("I've scanned an expression of the form `a[b,c',")@/ + ("so a right bracket should have come next.")@/ + ("I shall pretend that one was there.");@/ + back_error; + end; + r:=stash_cur_exp; make_exp_copy(q);@/ + do_binary(r,minus); do_binary(p,times); do_binary(q,plus); get_x_next; + end; +end + +@ Here is a comparatively simple routine that is used to scan the +\&{suffix} parameters of a macro. + +@= +procedure scan_suffix; +label done; +var @!h,@!t:pointer; {head and tail of the list being built} +@!p:pointer; {temporary register} +begin h:=get_avail; t:=h; +loop@+ begin if cur_cmd=left_bracket then + @; + if cur_cmd=numeric_token then p:=new_num_tok(cur_mod) + else if (cur_cmd=tag_token)or(cur_cmd=internal_quantity) then + begin p:=get_avail; info(p):=cur_sym; + end + else goto done; + link(t):=p; t:=p; get_x_next; + end; +done: cur_exp:=link(h); free_avail(h); cur_type:=token_list; +end; + +@ @= +begin get_x_next; scan_expression; +if cur_type<>known then bad_subscript; +if cur_cmd<>right_bracket then + begin missing_err("]");@/ +@.Missing `]'@> + help3("I've seen a `[' and a subscript value, in a suffix,")@/ + ("so a right bracket should have come next.")@/ + ("I shall pretend that one was there.");@/ + back_error; + end; +cur_cmd:=numeric_token; cur_mod:=cur_exp; +end + +@* \[41] Parsing secondary and higher expressions. +After the intricacies of |scan_primary|\kern-1pt, +the |scan_secondary| routine is +refreshingly simple. It's not trivial, but the operations are relatively +straightforward; the main difficulty is, again, that expressions and data +structures might change drastically every time we call |get_x_next|, so a +cautious approach is mandatory. For example, a macro defined by +\&{primarydef} might have disappeared by the time its second argument has +been scanned; we solve this by increasing the reference count of its token +list, so that the macro can be called even after it has been clobbered. + +@= +procedure scan_secondary; +label restart,continue; +var @!p:pointer; {for list manipulation} +@!c,@!d:halfword; {operation codes or modifiers} +@!mac_name:pointer; {token defined with \&{primarydef}} +begin restart:if(cur_cmdmax_primary_command) then + bad_exp("A secondary"); +@.A secondary expression...@> +scan_primary; +continue: if cur_cmd<=max_secondary_command then + if cur_cmd>=min_secondary_command then + begin p:=stash_cur_exp; c:=cur_mod; d:=cur_cmd; + if d=secondary_primary_macro then + begin mac_name:=cur_sym; add_mac_ref(c); + end; + get_x_next; scan_primary; + if d<>secondary_primary_macro then do_binary(p,c) + else begin back_input; binary_mac(p,c,mac_name); + decr(ref_count(c)); get_x_next; goto restart; + end; + goto continue; + end; +end; + +@ The following procedure calls a macro that has two parameters, +|p| and |cur_exp|. + +@p procedure binary_mac(@!p,@!c,@!n:pointer); +var @!q,@!r:pointer; {nodes in the parameter list} +begin q:=get_avail; r:=get_avail; link(q):=r;@/ +info(q):=p; info(r):=stash_cur_exp;@/ +macro_call(c,q,n); +end; + +@ The next procedure, |scan_tertiary|, is pretty much the same deal. + +@= +procedure scan_tertiary; +label restart,continue; +var @!p:pointer; {for list manipulation} +@!c,@!d:halfword; {operation codes or modifiers} +@!mac_name:pointer; {token defined with \&{secondarydef}} +begin restart:if(cur_cmdmax_primary_command) then + bad_exp("A tertiary"); +@.A tertiary expression...@> +scan_secondary; +if cur_type=future_pen then materialize_pen; +continue: if cur_cmd<=max_tertiary_command then + if cur_cmd>=min_tertiary_command then + begin p:=stash_cur_exp; c:=cur_mod; d:=cur_cmd; + if d=tertiary_secondary_macro then + begin mac_name:=cur_sym; add_mac_ref(c); + end; + get_x_next; scan_secondary; + if d<>tertiary_secondary_macro then do_binary(p,c) + else begin back_input; binary_mac(p,c,mac_name); + decr(ref_count(c)); get_x_next; goto restart; + end; + goto continue; + end; +end; + +@ A |future_pen| becomes a full-fledged pen here. + +@p procedure materialize_pen; +label common_ending; +var @!a_minus_b,@!a_plus_b,@!major_axis,@!minor_axis:scaled; {ellipse variables} +@!theta:angle; {amount by which the ellipse has been rotated} +@!p:pointer; {path traverser} +@!q:pointer; {the knot list to be made into a pen} +begin q:=cur_exp; +if left_type(q)=endpoint then + begin print_err("Pen path must be a cycle"); +@.Pen path must be a cycle@> + help2("I can't make a pen from the given path.")@/ + ("So I've replaced it by the trivial path `(0,0)..cycle'."); + put_get_error; cur_exp:=null_pen; goto common_ending; + end +else if left_type(q)=open then + @; +cur_exp:=make_pen(q); +common_ending: toss_knot_list(q); cur_type:=pen_type; +end; + +@ We placed the three points $(0,0)$, $(1,0)$, $(0,1)$ into a \&{pencircle}, +and they have now been transformed to $(u,v)$, $(A+u,B+v)$, $(C+u,D+v)$; +this gives us enough information to deduce the transformation +$(x,y)\mapsto(Ax+Cy+u,Bx+Dy+v)$. + +Given ($A,B,C,D)$ we can always find $(a,b,\theta,\phi)$ such that +$$\eqalign{A&=a\cos\phi\cos\theta-b\sin\phi\sin\theta;\cr +B&=a\cos\phi\sin\theta+b\sin\phi\cos\theta;\cr +C&=-a\sin\phi\cos\theta-b\cos\phi\sin\theta;\cr +D&=-a\sin\phi\sin\theta+b\cos\phi\cos\theta.\cr}$$ +In this notation, the unit circle $(\cos t,\sin t)$ is transformed into +$$\bigl(a\cos(\phi+t)\cos\theta-b\sin(\phi+t)\sin\theta,\; +a\cos(\phi+t)\sin\theta+b\sin(\phi+t)\cos\theta\bigr)\;+\;(u,v),$$ +which is an ellipse with semi-axes~$(a,b)$, rotated by~$\theta$ and +shifted by~$(u,v)$. To solve the stated equations, we note that it is +necessary and sufficient to solve +$$\eqalign{A-D&=(a-b)\cos(\theta-\phi),\cr +B+C&=(a-b)\sin(\theta-\phi),\cr} +\qquad +\eqalign{A+D&=(a+b)\cos(\theta+\phi),\cr +B-C&=(a+b)\sin(\theta+\phi);\cr}$$ +and it is easy to find $a-b$, $a+b$, $\theta-\phi$, and $\theta+\phi$ +from these formulas. + +The code below uses |(txx,tyx,txy,tyy,tx,ty)| to stand for +$(A,B,C,D,u,v)$. + +@= +begin tx:=x_coord(q); ty:=y_coord(q); +txx:=left_x(q)-tx; tyx:=left_y(q)-ty; +txy:=right_x(q)-tx; tyy:=right_y(q)-ty; +a_minus_b:=pyth_add(txx-tyy,tyx+txy); a_plus_b:=pyth_add(txx+tyy,tyx-txy); +major_axis:=half(a_minus_b+a_plus_b); minor_axis:=half(abs(a_plus_b-a_minus_b)); +if major_axis=minor_axis then theta:=0 {circle} +else theta:=half(n_arg(txx-tyy,tyx+txy)+n_arg(txx+tyy,tyx-txy)); +free_node(q,knot_node_size); +q:=make_ellipse(major_axis,minor_axis,theta); +if (tx<>0)or(ty<>0) then @; +end + +@ @= +begin p:=q; +repeat x_coord(p):=x_coord(p)+tx; y_coord(p):=y_coord(p)+ty; p:=link(p); +until p=q; +end + +@ Finally we reach the deepest level in our quartet of parsing routines. +This one is much like the others; but it has an extra complication from +paths, which materialize here. + +@d continue_path=25 {a label inside of |scan_expression|} +@d finish_path=26 {another} + +@= +procedure scan_expression; +label restart,done,continue,continue_path,finish_path,exit; +var @!p,@!q,@!r,@!pp,@!qq:pointer; {for list manipulation} +@!c,@!d:halfword; {operation codes or modifiers} +@!my_var_flag:0..max_command_code; {initial value of |var_flag|} +@!mac_name:pointer; {token defined with \&{tertiarydef}} +@!cycle_hit:boolean; {did a path expression just end with `\&{cycle}'?} +@!x,@!y:scaled; {explicit coordinates or tension at a path join} +@!t:endpoint..open; {knot type following a path join} +begin my_var_flag:=var_flag; +restart:if(cur_cmdmax_primary_command) then + bad_exp("An"); +@.An expression...@> +scan_tertiary; +continue: if cur_cmd<=max_expression_command then + if cur_cmd>=min_expression_command then + if (cur_cmd<>equals)or(my_var_flag<>assignment) then + begin p:=stash_cur_exp; c:=cur_mod; d:=cur_cmd; + if d=expression_tertiary_macro then + begin mac_name:=cur_sym; add_mac_ref(c); + end; + if (d + else begin get_x_next; scan_tertiary; + if d<>expression_tertiary_macro then do_binary(p,c) + else begin back_input; binary_mac(p,c,mac_name); + decr(ref_count(c)); get_x_next; goto restart; + end; + end; + goto continue; + end; +exit:end; + +@ The reader should review the data structure conventions for paths before +hoping to understand the next part of this code. + +@= +begin cycle_hit:=false; +@; +continue_path: @; +if cur_cmd=cycle then @ +else begin scan_tertiary; + @; + end; +@; +if cur_cmd>=min_expression_command then + if cur_cmd<=ampersand then if not cycle_hit then goto continue_path; +finish_path: +@; +end + +@ @= +begin unstash_cur_exp(p); +if cur_type=pair_type then p:=new_knot +else if cur_type=path_type then p:=cur_exp +else return; +q:=p; +while link(q)<>p do q:=link(q); +if left_type(p)<>endpoint then {open up a cycle} + begin r:=copy_knot(p); link(q):=r; q:=r; + end; +left_type(p):=open; right_type(q):=open; +end + +@ A pair of numeric values is changed into a knot node for a one-point path +when \MF\ discovers that the pair is part of a path. + +@p@t\4@>@@; +function new_knot:pointer; {convert a pair to a knot with two endpoints} +var @!q:pointer; {the new node} +begin q:=get_node(knot_node_size); left_type(q):=endpoint; +right_type(q):=endpoint; link(q):=q;@/ +known_pair; x_coord(q):=cur_x; y_coord(q):=cur_y; +new_knot:=q; +end; + +@ The |known_pair| subroutine sets |cur_x| and |cur_y| to the components +of the current expression, assuming that the current expression is a +pair of known numerics. Unknown components are zeroed, and the +current expression is flushed. + +@= +procedure known_pair; +var @!p:pointer; {the pair node} +begin if cur_type<>pair_type then + begin exp_err("Undefined coordinates have been replaced by (0,0)"); +@.Undefined coordinates...@> + help5("I need x and y numbers for this part of the path.")@/ + ("The value I found (see above) was no good;")@/ + ("so I'll try to keep going by using zero instead.")@/ + ("(Chapter 27 of The METAFONTbook explains that")@/ +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> + ("you might want to type `I ???' now.)"); + put_get_flush_error(0); cur_x:=0; cur_y:=0; + end +else begin p:=value(cur_exp); + @; + flush_cur_exp(0); + end; +end; + +@ @= +if type(x_part_loc(p))=known then cur_x:=value(x_part_loc(p)) +else begin disp_err(x_part_loc(p), + "Undefined x coordinate has been replaced by 0"); +@.Undefined coordinates...@> + help5("I need a `known' x value for this part of the path.")@/ + ("The value I found (see above) was no good;")@/ + ("so I'll try to keep going by using zero instead.")@/ + ("(Chapter 27 of The METAFONTbook explains that")@/ +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> + ("you might want to type `I ???' now.)"); + put_get_error; recycle_value(x_part_loc(p)); cur_x:=0; + end; +if type(y_part_loc(p))=known then cur_y:=value(y_part_loc(p)) +else begin disp_err(y_part_loc(p), + "Undefined y coordinate has been replaced by 0"); + help5("I need a `known' y value for this part of the path.")@/ + ("The value I found (see above) was no good;")@/ + ("so I'll try to keep going by using zero instead.")@/ + ("(Chapter 27 of The METAFONTbook explains that")@/ + ("you might want to type `I ???' now.)"); + put_get_error; recycle_value(y_part_loc(p)); cur_y:=0; + end + +@ At this point |cur_cmd| is either |ampersand|, |left_brace|, or |path_join|. + +@= +if cur_cmd=left_brace then + @; +d:=cur_cmd; +if d=path_join then @ +else if d<>ampersand then goto finish_path; +get_x_next; +if cur_cmd=left_brace then + @ +else if right_type(q)<>explicit then + begin t:=open; x:=0; + end + +@ The |scan_direction| subroutine looks at the directional information +that is enclosed in braces, and also scans ahead to the following character. +A type code is returned, either |open| (if the direction was $(0,0)$), +or |curl| (if the direction was a curl of known value |cur_exp|), or +|given| (if the direction is given by the |angle| value that now +appears in |cur_exp|). + +There's nothing difficult about this subroutine, but the program is rather +lengthy because a variety of potential errors need to be nipped in the bud. + +@p function scan_direction:small_number; +var @!t:given..open; {the type of information found} +@!x:scaled; {an |x| coordinate} +begin get_x_next; +if cur_cmd=curl_command then @ +else @; +if cur_cmd<>right_brace then + begin missing_err("}");@/ +@.Missing `\char`\}'@> + help3("I've scanned a direction spec for part of a path,")@/ + ("so a right brace should have come next.")@/ + ("I shall pretend that one was there.");@/ + back_error; + end; +get_x_next; scan_direction:=t; +end; + +@ @= +begin get_x_next; scan_expression; +if (cur_type<>known)or(cur_exp<0) then + begin exp_err("Improper curl has been replaced by 1"); +@.Improper curl@> + help1("A curl must be a known, nonnegative number."); + put_get_flush_error(unity); + end; +t:=curl; +end + +@ @= +begin scan_expression; +if cur_type>pair_type then @ +else known_pair; +if (cur_x=0)and(cur_y=0) then t:=open +else begin t:=given; cur_exp:=n_arg(cur_x,cur_y); + end; +end + +@ @= +begin if cur_type<>known then + begin exp_err("Undefined x coordinate has been replaced by 0"); +@.Undefined coordinates...@> + help5("I need a `known' x value for this part of the path.")@/ + ("The value I found (see above) was no good;")@/ + ("so I'll try to keep going by using zero instead.")@/ + ("(Chapter 27 of The METAFONTbook explains that")@/ +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> + ("you might want to type `I ???' now.)"); + put_get_flush_error(0); + end; +x:=cur_exp; +if cur_cmd<>comma then + begin missing_err(",");@/ +@.Missing `,'@> + help2("I've got the x coordinate of a path direction;")@/ + ("will look for the y coordinate next."); + back_error; + end; +get_x_next; scan_expression; +if cur_type<>known then + begin exp_err("Undefined y coordinate has been replaced by 0"); + help5("I need a `known' y value for this part of the path.")@/ + ("The value I found (see above) was no good;")@/ + ("so I'll try to keep going by using zero instead.")@/ + ("(Chapter 27 of The METAFONTbook explains that")@/ + ("you might want to type `I ???' now.)"); + put_get_flush_error(0); + end; +cur_y:=cur_exp; cur_x:=x; +end + +@ At this point |right_type(q)| is usually |open|, but it may have been +set to some other value by a previous operation. We must maintain +the value of |right_type(q)| in cases such as +`\.{..\{curl2\}z\{0,0\}..}'. + +@= +begin t:=scan_direction; +if t<>open then + begin right_type(q):=t; right_given(q):=cur_exp; + if left_type(q)=open then + begin left_type(q):=t; left_given(q):=cur_exp; + end; {note that |left_given(q)=left_curl(q)|} + end; +end + +@ Since |left_tension| and |left_y| share the same position in knot nodes, +and since |left_given| is similarly equivalent to |left_x|, we use +|x| and |y| to hold the given direction and tension information when +there are no explicit control points. + +@= +begin t:=scan_direction; +if right_type(q)<>explicit then x:=cur_exp +else t:=explicit; {the direction information is superfluous} +end + +@ @= +begin get_x_next; +if cur_cmd=tension then @ +else if cur_cmd=controls then @ +else begin right_tension(q):=unity; y:=unity; back_input; {default tension} + goto done; + end; +if cur_cmd<>path_join then + begin missing_err("..");@/ +@.Missing `..'@> + help1("A path join command should end with two dots."); + back_error; + end; +done:end + +@ @= +begin get_x_next; y:=cur_cmd; +if cur_cmd=at_least then get_x_next; +scan_primary; +@; +if y=at_least then negate(cur_exp); +right_tension(q):=cur_exp; +if cur_cmd=and_command then + begin get_x_next; y:=cur_cmd; + if cur_cmd=at_least then get_x_next; + scan_primary; + @; + if y=at_least then negate(cur_exp); + end; +y:=cur_exp; +end + +@ @d min_tension==three_quarter_unit + +@= +if (cur_type<>known)or(cur_exp + help1("The expression above should have been a number >=3/4."); + put_get_flush_error(unity); + end + +@ @= +begin right_type(q):=explicit; t:=explicit; get_x_next; scan_primary;@/ +known_pair; right_x(q):=cur_x; right_y(q):=cur_y; +if cur_cmd<>and_command then + begin x:=right_x(q); y:=right_y(q); + end +else begin get_x_next; scan_primary;@/ + known_pair; x:=cur_x; y:=cur_y; + end; +end + +@ @= +begin if cur_type<>path_type then pp:=new_knot +else pp:=cur_exp; +qq:=pp; +while link(qq)<>pp do qq:=link(qq); +if left_type(pp)<>endpoint then {open up a cycle} + begin r:=copy_knot(pp); link(qq):=r; qq:=r; + end; +left_type(pp):=open; right_type(qq):=open; +end + +@ If a person tries to define an entire path by saying `\.{(x,y)\&cycle}', +we silently change the specification to `\.{(x,y)..cycle}', since a cycle +shouldn't have length zero. + +@= +begin cycle_hit:=true; get_x_next; pp:=p; qq:=p; +if d=ampersand then if p=q then + begin d:=path_join; right_tension(q):=unity; y:=unity; + end; +end + +@ @= +begin if d=ampersand then + if (x_coord(q)<>x_coord(pp))or(y_coord(q)<>y_coord(pp)) then + begin print_err("Paths don't touch; `&' will be changed to `..'"); +@.Paths don't touch@> + help3("When you join paths `p&q', the ending point of p")@/ + ("must be exactly equal to the starting point of q.")@/ + ("So I'm going to pretend that you said `p..q' instead."); + put_get_error; d:=path_join; right_tension(q):=unity; y:=unity; + end; +@; +if d=ampersand then @ +else begin @; + link(q):=pp; left_y(pp):=y; + if t<>open then + begin left_x(pp):=x; left_type(pp):=t; + end; + end; +q:=qq; +end + +@ @= +if right_type(q)=open then + if (left_type(q)=curl)or(left_type(q)=given) then + begin right_type(q):=left_type(q); right_given(q):=left_given(q); + end + +@ @= +if right_type(pp)=open then + if (t=curl)or(t=given) then + begin right_type(pp):=t; right_given(pp):=x; + end + +@ @= +begin if left_type(q)=open then if right_type(q)=open then + begin left_type(q):=curl; left_curl(q):=unity; + end; +if right_type(pp)=open then if t=open then + begin right_type(pp):=curl; right_curl(pp):=unity; + end; +right_type(q):=right_type(pp); link(q):=link(pp);@/ +right_x(q):=right_x(pp); right_y(q):=right_y(pp); +free_node(pp,knot_node_size); +if qq=pp then qq:=q; +end + +@ @= +if cycle_hit then + begin if d=ampersand then p:=q; + end +else begin left_type(p):=endpoint; + if right_type(p)=open then + begin right_type(p):=curl; right_curl(p):=unity; + end; + right_type(q):=endpoint; + if left_type(q)=open then + begin left_type(q):=curl; left_curl(q):=unity; + end; + link(q):=p; + end; +make_choices(p); +cur_type:=path_type; cur_exp:=p + +@ Finally, we sometimes need to scan an expression whose value is +supposed to be either |true_code| or |false_code|. + +@= +procedure get_boolean; +begin get_x_next; scan_expression; +if cur_type<>boolean_type then + begin exp_err("Undefined condition will be treated as `false'"); +@.Undefined condition...@> + help2("The expression shown above should have had a definite")@/ + ("true-or-false value. I'm changing it to `false'.");@/ + put_get_flush_error(false_code); cur_type:=boolean_type; + end; +end; + +@* \[42] Doing the operations. +The purpose of parsing is primarily to permit people to avoid piles of +parentheses. But the real work is done after the structure of an expression +has been recognized; that's when new expressions are generated. We +turn now to the guts of \MF, which handles individual operators that +have come through the parsing mechanism. + +We'll start with the easy ones that take no operands, then work our way +up to operators with one and ultimately two arguments. In other words, +we will write the three procedures |do_nullary|, |do_unary|, and |do_binary| +that are invoked periodically by the expression scanners. + +First let's make sure that all of the primitive operators are in the +hash table. Although |scan_primary| and its relatives made use of the +\\{cmd} code for these operators, the \\{do} routines base everything +on the \\{mod} code. For example, |do_binary| doesn't care whether the +operation it performs is a |primary_binary| or |secondary_binary|, etc. + +@= +primitive("true",nullary,true_code);@/ +@!@:true_}{\&{true} primitive@> +primitive("false",nullary,false_code);@/ +@!@:false_}{\&{false} primitive@> +primitive("nullpicture",nullary,null_picture_code);@/ +@!@:null_picture_}{\&{nullpicture} primitive@> +primitive("nullpen",nullary,null_pen_code);@/ +@!@:null_pen_}{\&{nullpen} primitive@> +primitive("jobname",nullary,job_name_op);@/ +@!@:job_name_}{\&{jobname} primitive@> +primitive("readstring",nullary,read_string_op);@/ +@!@:read_string_}{\&{readstring} primitive@> +primitive("pencircle",nullary,pen_circle);@/ +@!@:pen_circle_}{\&{pencircle} primitive@> +primitive("normaldeviate",nullary,normal_deviate);@/ +@!@:normal_deviate_}{\&{normaldeviate} primitive@> +primitive("odd",unary,odd_op);@/ +@!@:odd_}{\&{odd} primitive@> +primitive("known",unary,known_op);@/ +@!@:known_}{\&{known} primitive@> +primitive("unknown",unary,unknown_op);@/ +@!@:unknown_}{\&{unknown} primitive@> +primitive("not",unary,not_op);@/ +@!@:not_}{\&{not} primitive@> +primitive("decimal",unary,decimal);@/ +@!@:decimal_}{\&{decimal} primitive@> +primitive("reverse",unary,reverse);@/ +@!@:reverse_}{\&{reverse} primitive@> +primitive("makepath",unary,make_path_op);@/ +@!@:make_path_}{\&{makepath} primitive@> +primitive("makepen",unary,make_pen_op);@/ +@!@:make_pen_}{\&{makepen} primitive@> +primitive("totalweight",unary,total_weight_op);@/ +@!@:total_weight_}{\&{totalweight} primitive@> +primitive("oct",unary,oct_op);@/ +@!@:oct_}{\&{oct} primitive@> +primitive("hex",unary,hex_op);@/ +@!@:hex_}{\&{hex} primitive@> +primitive("ASCII",unary,ASCII_op);@/ +@!@:ASCII_}{\&{ASCII} primitive@> +primitive("char",unary,char_op);@/ +@!@:char_}{\&{char} primitive@> +primitive("length",unary,length_op);@/ +@!@:length_}{\&{length} primitive@> +primitive("turningnumber",unary,turning_op);@/ +@!@:turning_number_}{\&{turningnumber} primitive@> +primitive("xpart",unary,x_part);@/ +@!@:x_part_}{\&{xpart} primitive@> +primitive("ypart",unary,y_part);@/ +@!@:y_part_}{\&{ypart} primitive@> +primitive("xxpart",unary,xx_part);@/ +@!@:xx_part_}{\&{xxpart} primitive@> +primitive("xypart",unary,xy_part);@/ +@!@:xy_part_}{\&{xypart} primitive@> +primitive("yxpart",unary,yx_part);@/ +@!@:yx_part_}{\&{yxpart} primitive@> +primitive("yypart",unary,yy_part);@/ +@!@:yy_part_}{\&{yypart} primitive@> +primitive("sqrt",unary,sqrt_op);@/ +@!@:sqrt_}{\&{sqrt} primitive@> +primitive("mexp",unary,m_exp_op);@/ +@!@:m_exp_}{\&{mexp} primitive@> +primitive("mlog",unary,m_log_op);@/ +@!@:m_log_}{\&{mlog} primitive@> +primitive("sind",unary,sin_d_op);@/ +@!@:sin_d_}{\&{sind} primitive@> +primitive("cosd",unary,cos_d_op);@/ +@!@:cos_d_}{\&{cosd} primitive@> +primitive("floor",unary,floor_op);@/ +@!@:floor_}{\&{floor} primitive@> +primitive("uniformdeviate",unary,uniform_deviate);@/ +@!@:uniform_deviate_}{\&{uniformdeviate} primitive@> +primitive("charexists",unary,char_exists_op);@/ +@!@:char_exists_}{\&{charexists} primitive@> +primitive("angle",unary,angle_op);@/ +@!@:angle_}{\&{angle} primitive@> +primitive("cycle",cycle,cycle_op);@/ +@!@:cycle_}{\&{cycle} primitive@> +primitive("+",plus_or_minus,plus);@/ +@!@:+ }{\.{+} primitive@> +primitive("-",plus_or_minus,minus);@/ +@!@:- }{\.{-} primitive@> +primitive("*",secondary_binary,times);@/ +@!@:* }{\.{*} primitive@> +primitive("/",slash,over); eqtb[frozen_slash]:=eqtb[cur_sym];@/ +@!@:/ }{\.{/} primitive@> +primitive("++",tertiary_binary,pythag_add);@/ +@!@:++_}{\.{++} primitive@> +primitive("+-+",tertiary_binary,pythag_sub);@/ +@!@:+-+_}{\.{+-+} primitive@> +primitive("and",and_command,and_op);@/ +@!@:and_}{\&{and} primitive@> +primitive("or",tertiary_binary,or_op);@/ +@!@:or_}{\&{or} primitive@> +primitive("<",expression_binary,less_than);@/ +@!@:< }{\.{<} primitive@> +primitive("<=",expression_binary,less_or_equal);@/ +@!@:<=_}{\.{<=} primitive@> +primitive(">",expression_binary,greater_than);@/ +@!@:> }{\.{>} primitive@> +primitive(">=",expression_binary,greater_or_equal);@/ +@!@:>=_}{\.{>=} primitive@> +primitive("=",equals,equal_to);@/ +@!@:= }{\.{=} primitive@> +primitive("<>",expression_binary,unequal_to);@/ +@!@:<>_}{\.{<>} primitive@> +primitive("substring",primary_binary,substring_of);@/ +@!@:substring_}{\&{substring} primitive@> +primitive("subpath",primary_binary,subpath_of);@/ +@!@:subpath_}{\&{subpath} primitive@> +primitive("directiontime",primary_binary,direction_time_of);@/ +@!@:direction_time_}{\&{directiontime} primitive@> +primitive("point",primary_binary,point_of);@/ +@!@:point_}{\&{point} primitive@> +primitive("precontrol",primary_binary,precontrol_of);@/ +@!@:precontrol_}{\&{precontrol} primitive@> +primitive("postcontrol",primary_binary,postcontrol_of);@/ +@!@:postcontrol_}{\&{postcontrol} primitive@> +primitive("penoffset",primary_binary,pen_offset_of);@/ +@!@:pen_offset_}{\&{penoffset} primitive@> +primitive("&",ampersand,concatenate);@/ +@!@:!!!}{\.{\&} primitive@> +primitive("rotated",secondary_binary,rotated_by);@/ +@!@:rotated_}{\&{rotated} primitive@> +primitive("slanted",secondary_binary,slanted_by);@/ +@!@:slanted_}{\&{slanted} primitive@> +primitive("scaled",secondary_binary,scaled_by);@/ +@!@:scaled_}{\&{scaled} primitive@> +primitive("shifted",secondary_binary,shifted_by);@/ +@!@:shifted_}{\&{shifted} primitive@> +primitive("transformed",secondary_binary,transformed_by);@/ +@!@:transformed_}{\&{transformed} primitive@> +primitive("xscaled",secondary_binary,x_scaled);@/ +@!@:x_scaled_}{\&{xscaled} primitive@> +primitive("yscaled",secondary_binary,y_scaled);@/ +@!@:y_scaled_}{\&{yscaled} primitive@> +primitive("zscaled",secondary_binary,z_scaled);@/ +@!@:z_scaled_}{\&{zscaled} primitive@> +primitive("intersectiontimes",tertiary_binary,intersect);@/ +@!@:intersection_times_}{\&{intersectiontimes} primitive@> + +@ @= +nullary,unary,primary_binary,secondary_binary,tertiary_binary, + expression_binary,cycle,plus_or_minus,slash,ampersand,equals,and_command: + print_op(m); + +@ OK, let's look at the simplest \\{do} procedure first. + +@p procedure do_nullary(@!c:quarterword); +var @!k:integer; {all-purpose loop index} +begin check_arith; +if internal[tracing_commands]>two then + show_cmd_mod(nullary,c); +case c of +true_code,false_code:begin cur_type:=boolean_type; cur_exp:=c; + end; +null_picture_code:begin cur_type:=picture_type; + cur_exp:=get_node(edge_header_size); init_edges(cur_exp); + end; +null_pen_code:begin cur_type:=pen_type; cur_exp:=null_pen; + end; +normal_deviate:begin cur_type:=known; cur_exp:=norm_rand; + end; +pen_circle:@; +job_name_op: begin if job_name=0 then open_log_file; + cur_type:=string_type; cur_exp:=job_name; + end; +read_string_op:@; +end; {there are no other cases} +check_arith; +end; + +@ @= +begin cur_type:=future_pen; cur_exp:=get_node(knot_node_size); +left_type(cur_exp):=open; right_type(cur_exp):=open; +link(cur_exp):=cur_exp;@/ +x_coord(cur_exp):=0; y_coord(cur_exp):=0;@/ +left_x(cur_exp):=unity; left_y(cur_exp):=0;@/ +right_x(cur_exp):=0; right_y(cur_exp):=unity;@/ +end + +@ @= +begin if interaction<=nonstop_mode then + fatal_error("*** (cannot readstring in nonstop modes)"); +begin_file_reading; name:=1; prompt_input(""); +str_room(last-start); +for k:=start to last-1 do append_char(buffer[k]); +end_file_reading; cur_type:=string_type; cur_exp:=make_string; +end + +@ Things get a bit more interesting when there's an operand. The +operand to |do_unary| appears in |cur_type| and |cur_exp|. + +@p @t\4@>@@; +procedure do_unary(@!c:quarterword); +var @!p,@!q:pointer; {for list manipulation} +@!x:integer; {a temporary register} +begin check_arith; +if internal[tracing_commands]>two then + @; +case c of +plus:if cur_typepicture_type then bad_unary(plus); +minus:@; +@t\4@>@@; +end; {there are no other cases} +check_arith; +end; + +@ The |nice_pair| function returns |true| if both components of a pair +are known. + +@= +function nice_pair(@!p:integer;@!t:quarterword):boolean; +label exit; +begin if t=pair_type then + begin p:=value(p); + if type(x_part_loc(p))=known then + if type(y_part_loc(p))=known then + begin nice_pair:=true; return; + end; + end; +nice_pair:=false; +exit:end; + +@ @= +procedure print_known_or_unknown_type(@!t:small_number;@!v:integer); +begin print_char("("); +if tpair_type then print_type(t) + else if nice_pair(v,pair_type) then print("pair") + else print("unknown pair") +else print("unknown numeric"); +print_char(")"); +end; + +@ @= +procedure bad_unary(@!c:quarterword); +begin exp_err("Not implemented: "); print_op(c); +@.Not implemented...@> +print_known_or_unknown_type(cur_type,cur_exp); +help3("I'm afraid I don't know how to apply that operation to that")@/ + ("particular type. Continue, and I'll simply return the")@/ + ("argument (shown above) as the result of the operation."); +put_get_error; +end; + +@ @= +begin begin_diagnostic; print_nl("{"); print_op(c); print_char("(");@/ +print_exp(null,0); {show the operand, but not verbosely} +print(")}"); end_diagnostic(false); +end + +@ Negation is easy except when the current expression +is of type |independent|, or when it is a pair with one or more +|independent| components. + +It is tempting to argue that the negative of an independent variable +is an independent variable, hence we don't have to do anything when +negating it. The fallacy is that other dependent variables pointing +to the current expression must change the sign of their +coefficients if we make no change to the current expression. + +Instead, we work around the problem by copying the current expression +and recycling it afterwards (cf.~the |stash_in| routine). + +@= +case cur_type of +pair_type,independent: begin q:=cur_exp; make_exp_copy(q); + if cur_type=dependent then negate_dep_list(dep_list(cur_exp)) + else if cur_type=pair_type then + begin p:=value(cur_exp); + if type(x_part_loc(p))=known then negate(value(x_part_loc(p))) + else negate_dep_list(dep_list(x_part_loc(p))); + if type(y_part_loc(p))=known then negate(value(y_part_loc(p))) + else negate_dep_list(dep_list(y_part_loc(p))); + end; {if |cur_type=known| then |cur_exp=0|} + recycle_value(q); free_node(q,value_node_size); + end; +dependent,proto_dependent:negate_dep_list(dep_list(cur_exp)); +known:negate(cur_exp); +picture_type:negate_edges(cur_exp); +othercases bad_unary(minus) +endcases + +@ @= +procedure negate_dep_list(@!p:pointer); +label exit; +begin loop@+begin negate(value(p)); + if info(p)=null then return; + p:=link(p); + end; +exit:end; + +@ @= +not_op: if cur_type<>boolean_type then bad_unary(not_op) + else cur_exp:=true_code+false_code-cur_exp; + +@ @d three_sixty_units==23592960 {that's |360*unity|} +@d boolean_reset(#)==if # then cur_exp:=true_code@+else cur_exp:=false_code + +@= +sqrt_op,m_exp_op,m_log_op,sin_d_op,cos_d_op,floor_op, + uniform_deviate,odd_op,char_exists_op:@t@>@;@/ + if cur_type<>known then bad_unary(c) + else case c of + sqrt_op:cur_exp:=square_rt(cur_exp); + m_exp_op:cur_exp:=m_exp(cur_exp); + m_log_op:cur_exp:=m_log(cur_exp); + sin_d_op,cos_d_op:begin n_sin_cos((cur_exp mod three_sixty_units)*16); + if c=sin_d_op then cur_exp:=round_fraction(n_sin) + else cur_exp:=round_fraction(n_cos); + end; + floor_op:cur_exp:=floor_scaled(cur_exp); + uniform_deviate:cur_exp:=unif_rand(cur_exp); + odd_op: begin boolean_reset(odd(round_unscaled(cur_exp))); + cur_type:=boolean_type; + end; + char_exists_op:@; + end; {there are no other cases} + +@ @= +angle_op:if nice_pair(cur_exp,cur_type) then + begin p:=value(cur_exp); + x:=n_arg(value(x_part_loc(p)),value(y_part_loc(p))); + if x>=0 then flush_cur_exp((x+8)div 16) + else flush_cur_exp(-((-x+8)div 16)); + end + else bad_unary(angle_op); + +@ If the current expression is a pair, but the context wants it to +be a path, we call |pair_to_path|. + +@= +procedure pair_to_path; +begin cur_exp:=new_knot; cur_type:=path_type; +end; + +@ @= +x_part,y_part:if (cur_type<=pair_type)and(cur_type>=transform_type) then + take_part(c) + else bad_unary(c); +xx_part,xy_part,yx_part,yy_part: if cur_type=transform_type then take_part(c) + else bad_unary(c); + +@ In the following procedure, |cur_exp| points to a capsule, which points to +a big node. We want to delete all but one part of the big node. + +@= +procedure take_part(@!c:quarterword); +var @!p:pointer; {the big node} +begin p:=value(cur_exp); value(temp_val):=p; type(temp_val):=cur_type; +link(p):=temp_val; free_node(cur_exp,value_node_size); +make_exp_copy(p+2*(c-x_part)); +recycle_value(temp_val); +end; + +@ @= +name_type(temp_val):=capsule; + +@ @= +char_op: if cur_type<>known then bad_unary(char_op) + else begin cur_exp:=round_unscaled(cur_exp) mod 256; cur_type:=string_type; + if cur_exp<0 then cur_exp:=cur_exp+256; + if length(cur_exp)<>1 then + begin str_room(1); append_char(cur_exp); cur_exp:=make_string; + end; + end; +decimal: if cur_type<>known then bad_unary(decimal) + else begin old_setting:=selector; selector:=new_string; + print_scaled(cur_exp); cur_exp:=make_string; + selector:=old_setting; cur_type:=string_type; + end; +oct_op,hex_op,ASCII_op: if cur_type<>string_type then bad_unary(c) + else str_to_num(c); + +@ @= +procedure str_to_num(@!c:quarterword); {converts a string to a number} +var @!n:integer; {accumulator} +@!m:ASCII_code; {current character} +@!k:pool_pointer; {index into |str_pool|} +@!b:8..16; {radix of conversion} +@!bad_char:boolean; {did the string contain an invalid digit?} +begin if c=ASCII_op then + if length(cur_exp)=0 then n:=-1 + else n:=so(str_pool[str_start[cur_exp]]) +else begin if c=oct_op then b:=8@+else b:=16; + n:=0; bad_char:=false; + for k:=str_start[cur_exp] to str_start[cur_exp+1]-1 do + begin m:=so(str_pool[k]); + if (m>="0")and(m<="9") then m:=m-"0" + else if (m>="A")and(m<="F") then m:=m-"A"+10 + else if (m>="a")and(m<="f") then m:=m-"a"+10 + else begin bad_char:=true; m:=0; + end; + if m>=b then + begin bad_char:=true; m:=0; + end; + if n<32768 div b then n:=n*b+m@+else n:=32767; + end; + @=4096|@>; + end; +flush_cur_exp(n*unity); +end; + +@ @= +if bad_char then + begin exp_err("String contains illegal digits"); +@.String contains illegal digits@> + if c=oct_op then + help1("I zeroed out characters that weren't in the range 0..7.") + else help1("I zeroed out characters that weren't hex digits."); + put_get_error; + end; +if n>4095 then + begin print_err("Number too large ("); print_int(n); print_char(")"); +@.Number too large@> + help1("I have trouble with numbers greater than 4095; watch out."); + put_get_error; + end + +@ The length operation is somewhat unusual in that it applies to a variety +of different types of operands. + +@= +length_op: if cur_type=string_type then flush_cur_exp(length(cur_exp)*unity) + else if cur_type=path_type then flush_cur_exp(path_length) + else if cur_type=known then cur_exp:=abs(cur_exp) + else if nice_pair(cur_exp,cur_type) then + flush_cur_exp(pyth_add(value(x_part_loc(value(cur_exp))),@| + value(y_part_loc(value(cur_exp))))) + else bad_unary(c); + +@ @= +function path_length:scaled; {computes the length of the current path} +var @!n:scaled; {the path length so far} +@!p:pointer; {traverser} +begin p:=cur_exp; +if left_type(p)=endpoint then n:=-unity@+else n:=0; +repeat p:=link(p); n:=n+unity; +until p=cur_exp; +path_length:=n; +end; + +@ The turning number is computed only with respect to null pens. A different +pen might affect the turning number, in degenerate cases, because autorounding +will produce a slightly different path, or because excessively large coordinates +might be truncated. + +@= +turning_op:if cur_type=pair_type then flush_cur_exp(0) + else if cur_type<>path_type then bad_unary(turning_op) + else if left_type(cur_exp)=endpoint then + flush_cur_exp(0) {not a cyclic path} + else begin cur_pen:=null_pen; cur_path_type:=contour_code; + cur_exp:=make_spec(cur_exp, + fraction_one-half_unit-1-el_gordo,0); + flush_cur_exp(turning_number*unity); {convert to |scaled|} + end; + +@ @d type_test_end== flush_cur_exp(true_code) + else flush_cur_exp(false_code); + cur_type:=boolean_type; + end +@d type_range_end(#)==(cur_type<=#) then type_test_end +@d type_range(#)==begin if (cur_type>=#) and type_range_end +@d type_test(#)==begin if cur_type=# then type_test_end + +@= +boolean_type: type_range(boolean_type)(unknown_boolean); +string_type: type_range(string_type)(unknown_string); +pen_type: type_range(pen_type)(future_pen); +path_type: type_range(path_type)(unknown_path); +picture_type: type_range(picture_type)(unknown_picture); +transform_type,pair_type: type_test(c); +numeric_type: type_range(known)(independent); +known_op,unknown_op: test_known(c); + +@ @= +procedure test_known(@!c:quarterword); +label done; +var @!b:true_code..false_code; {is the current expression known?} +@!p,@!q:pointer; {locations in a big node} +begin b:=false_code; +case cur_type of +vacuous,boolean_type,string_type,pen_type,future_pen,path_type,picture_type, + known: b:=true_code; +transform_type,pair_type:begin p:=value(cur_exp); q:=p+big_node_size[cur_type]; + repeat q:=q-2; + if type(q)<>known then goto done; + until q=p; + b:=true_code; +done: end; +othercases do_nothing +endcases; +if c=known_op then flush_cur_exp(b) +else flush_cur_exp(true_code+false_code-b); +cur_type:=boolean_type; +end; + +@ @= +cycle_op: begin if cur_type<>path_type then flush_cur_exp(false_code) + else if left_type(cur_exp)<>endpoint then flush_cur_exp(true_code) + else flush_cur_exp(false_code); + cur_type:=boolean_type; + end; + +@ @= +make_pen_op: begin if cur_type=pair_type then pair_to_path; + if cur_type=path_type then cur_type:=future_pen + else bad_unary(make_pen_op); + end; +make_path_op: begin if cur_type=future_pen then materialize_pen; + if cur_type<>pen_type then bad_unary(make_path_op) + else begin flush_cur_exp(make_path(cur_exp)); cur_type:=path_type; + end; + end; +total_weight_op: if cur_type<>picture_type then bad_unary(total_weight_op) + else flush_cur_exp(total_weight(cur_exp)); +reverse: if cur_type=path_type then + begin p:=htap_ypoc(cur_exp); + if right_type(p)=endpoint then p:=link(p); + toss_knot_list(cur_exp); cur_exp:=p; + end + else if cur_type=pair_type then pair_to_path + else bad_unary(reverse); + +@ Finally, we have the operations that combine a capsule~|p| +with the current expression. + +@p @t\4@>@@; +procedure do_binary(@!p:pointer;@!c:quarterword); +label done,done1,exit; +var @!q,@!r,@!rr:pointer; {for list manipulation} +@!old_p,@!old_exp:pointer; {capsules to recycle} +@!v:integer; {for numeric manipulation} +begin check_arith; +if internal[tracing_commands]>two then + @; +@; +@; +case c of +plus,minus:@; +@t\4@>@@; +end; {there are no other cases} +recycle_value(p); free_node(p,value_node_size); {|return| to avoid this} +exit:check_arith; @; +end; + +@ @= +procedure bad_binary(@!p:pointer;@!c:quarterword); +begin disp_err(p,""); +exp_err("Not implemented: "); +@.Not implemented...@> +if c>=min_of then print_op(c); +print_known_or_unknown_type(type(p),p); +if c>=min_of then print("of")@+else print_op(c); +print_known_or_unknown_type(cur_type,cur_exp);@/ +help3("I'm afraid I don't know how to apply that operation to that")@/ + ("combination of types. Continue, and I'll return the second")@/ + ("argument (see above) as the result of the operation."); +put_get_error; +end; + +@ @= +begin begin_diagnostic; print_nl("{("); +print_exp(p,0); {show the operand, but not verbosely} +print_char(")"); print_op(c); print_char("(");@/ +print_exp(null,0); print(")}"); end_diagnostic(false); +end + +@ Several of the binary operations are potentially complicated by the +fact that |independent| values can sneak into capsules. For example, +we've seen an instance of this difficulty in the unary operation +of negation. In order to reduce the number of cases that need to be +handled, we first change the two operands (if necessary) +to rid them of |independent| components. The original operands are +put into capsules called |old_p| and |old_exp|, which will be +recycled after the binary operation has been safely carried out. + +@= +if old_p<>null then + begin recycle_value(old_p); free_node(old_p,value_node_size); + end; +if old_exp<>null then + begin recycle_value(old_exp); free_node(old_exp,value_node_size); + end + +@ A big node is considered to be ``tarnished'' if it contains at least one +independent component. We will define a simple function called `|tarnished|' +that returns |null| if and only if its argument is not tarnished. + +@= +case type(p) of +transform_type,pair_type: old_p:=tarnished(p); +independent: old_p:=void; +othercases old_p:=null +endcases; +if old_p<>null then + begin q:=stash_cur_exp; old_p:=p; make_exp_copy(old_p); + p:=stash_cur_exp; unstash_cur_exp(q); + end; + +@ @= +case cur_type of +transform_type,pair_type:old_exp:=tarnished(cur_exp); +independent:old_exp:=void; +othercases old_exp:=null +endcases; +if old_exp<>null then + begin old_exp:=cur_exp; make_exp_copy(old_exp); + end + +@ @= +function tarnished(@!p:pointer):pointer; +label exit; +var @!q:pointer; {beginning of the big node} +@!r:pointer; {current position in the big node} +begin q:=value(p); r:=q+big_node_size[type(p)]; +repeat r:=r-2; +if type(r)=independent then + begin tarnished:=void; return; + end; +until r=q; +tarnished:=null; +exit:end; + +@ @= +if (cur_typepair_type then bad_binary(p,c) + else begin q:=value(p); r:=value(cur_exp); + add_or_subtract(x_part_loc(q),x_part_loc(r),c); + add_or_subtract(y_part_loc(q),y_part_loc(r),c); + end + else if type(p)=pair_type then bad_binary(p,c) + else add_or_subtract(p,null,c) + +@ The first argument to |add_or_subtract| is the location of a value node +in a capsule or pair node that will soon be recycled. The second argument +is either a location within a pair or transform node of |cur_exp|, +or it is null (which means that |cur_exp| itself should be the second +argument). The third argument is either |plus| or |minus|. + +The sum or difference of the numeric quantities will replace the second +operand. Arithmetic overflow may go undetected; users aren't supposed to +be monkeying around with really big values. +@^overflow in arithmetic@> + +@= +@t\4@>@@; +procedure add_or_subtract(@!p,@!q:pointer;@!c:quarterword); +label done,exit; +var @!s,@!t:small_number; {operand types} +@!r:pointer; {list traverser} +@!v:integer; {second operand value} +begin if q=null then + begin t:=cur_type; + if t; + end +else begin if c=minus then negate_dep_list(v); + @; + end; +exit:end; + +@ @= +r:=dep_list(p); +while info(r)<>null do r:=link(r); +value(r):=slow_add(value(r),v); +if q=null then + begin q:=get_node(value_node_size); cur_exp:=q; cur_type:=type(p); + name_type(q):=capsule; + end; +dep_list(q):=dep_list(p); type(q):=type(p); +prev_dep(q):=prev_dep(p); link(prev_dep(p)):=q; +type(p):=known; {this will keep the recycler from collecting non-garbage} + +@ We prefer |dependent| lists to |proto_dependent| ones, because it is +nice to retain the extra accuracy of |fraction| coefficients. +But we have to handle both kinds, and mixtures too. + +@= +if type(p)=known then + @ +else begin s:=type(p); r:=dep_list(p); + if t=dependent then + begin if s=dependent then + if max_coef(r)+max_coef(v); + end + +@ @= +begin while info(v)<>null do v:=link(v); +value(v):=slow_add(value(p),value(v)); +end + +@ @= +if q<>null then dep_finish(v,q,t) +else begin cur_type:=t; dep_finish(v,null,t); + end + +@ Here's the current situation: The dependency list |v| of type |t| +should either be put into the current expression (if |q=null|) or +into location |q| within a pair node (otherwise). The destination (|cur_exp| +or |q|) formerly held a dependency list with the same +final pointer as the list |v|. + +@= +procedure dep_finish(@!v,@!q:pointer;@!t:small_number); +var @!p:pointer; {the destination} +@!vv:scaled; {the value, if it is |known|} +begin if q=null then p:=cur_exp@+else p:=q; +dep_list(p):=v; type(p):=t; +if info(v)=null then + begin vv:=value(v); + if q=null then flush_cur_exp(vv) + else begin recycle_value(p); type(q):=known; value(q):=vv; + end; + end +else if q=null then cur_type:=t; +if fix_needed then fix_dependencies; +end; + +@ Let's turn now to the six basic relations of comparison. + +@= +less_than,less_or_equal,greater_than,greater_or_equal,equal_to,unequal_to: + begin@t@>@; + if (cur_type>pair_type)and(type(p)>pair_type) then + add_or_subtract(p,null,minus) {|cur_exp:=(p)-cur_exp|} + else if cur_type<>type(p) then + begin bad_binary(p,c); goto done; + end + else if cur_type=string_type then + flush_cur_exp(str_vs_str(value(p),cur_exp)) + else if (cur_type=unknown_string)or(cur_type=unknown_boolean) then + @ + else if (cur_type=pair_type)or(cur_type=transform_type) then + @ + else if cur_type=boolean_type then flush_cur_exp(cur_exp-value(p)) + else begin bad_binary(p,c); goto done; + end; + @; +done: end; + +@ @= +if cur_type<>known then + begin if cur_type + put_get_flush_error(false_code); + end +else case c of + less_than: boolean_reset(cur_exp<0); + less_or_equal: boolean_reset(cur_exp<=0); + greater_than: boolean_reset(cur_exp>0); + greater_or_equal: boolean_reset(cur_exp>=0); + equal_to: boolean_reset(cur_exp=0); + unequal_to: boolean_reset(cur_exp<>0); + end; {there are no other cases} + cur_type:=boolean_type + +@ When two unknown strings are in the same ring, we know that they are +equal. Otherwise, we don't know whether they are equal or not, so we +make no change. + +@= +begin q:=value(cur_exp); +while (q<>cur_exp)and(q<>p) do q:=value(q); +if q=p then flush_cur_exp(0); +end + +@ @= +begin q:=value(p); r:=value(cur_exp); +rr:=r+big_node_size[cur_type]-2; +loop@+ begin add_or_subtract(q,r,minus); + if type(r)<>known then goto done1; + if value(r)<>0 then goto done1; + if r=rr then goto done1; + q:=q+2; r:=r+2; + end; +done1:take_part(x_part+half(r-value(cur_exp))); +end + +@ Here we use the sneaky fact that |and_op-false_code=or_op-true_code|. + +@= +and_op,or_op: if (type(p)<>boolean_type)or(cur_type<>boolean_type) then + bad_binary(p,c) + else if value(p)=c+false_code-and_op then cur_exp:=value(p); + +@ @= +times: if (cur_type + else if (nice_pair(p,type(p))and(cur_type>pair_type)) + or(nice_pair(cur_exp,cur_type)and(type(p)>pair_type)) then + begin hard_times(p); return; + end + else bad_binary(p,times); + +@ @= +begin if type(p)=known then + begin v:=value(p); free_node(p,value_node_size); + end +else begin v:=cur_exp; unstash_cur_exp(p); + end; +if cur_type=known then cur_exp:=take_scaled(cur_exp,v) +else if cur_type=pair_type then + begin p:=value(cur_exp); + dep_mult(x_part_loc(p),v,true); + dep_mult(y_part_loc(p),v,true); + end +else dep_mult(null,v,true); +return; +end + +@ @= +procedure dep_mult(@!p:pointer;@!v:integer;@!v_is_scaled:boolean); +label exit; +var @!q:pointer; {the dependency list being multiplied by |v|} +@!s,@!t:small_number; {its type, before and after} +begin if p=null then q:=cur_exp +else if type(p)<>known then q:=p +else begin if v_is_scaled then value(p):=take_scaled(value(p),v) + else value(p):=take_fraction(value(p),v); + return; + end; +t:=type(q); q:=dep_list(q); s:=t; +if t=dependent then if v_is_scaled then + if ab_vs_cd(max_coef(q),abs(v),coef_bound-1,unity)>=0 then t:=proto_dependent; +q:=p_times_v(q,v,s,t,v_is_scaled); dep_finish(q,p,t); +exit:end; + +@ Here is a routine that is similar to |times|; but it is invoked only +internally, when |v| is a |fraction| whose magnitude is at most~1, +and when |cur_type>=pair_type|. + +@p procedure frac_mult(@!n,@!d:scaled); {multiplies |cur_exp| by |n/d|} +var @!p:pointer; {a pair node} +@!old_exp:pointer; {a capsule to recycle} +@!v:fraction; {|n/d|} +begin if internal[tracing_commands]>two then + @; +case cur_type of +transform_type,pair_type:old_exp:=tarnished(cur_exp); +independent:old_exp:=void; +othercases old_exp:=null +endcases; +if old_exp<>null then + begin old_exp:=cur_exp; make_exp_copy(old_exp); + end; +v:=make_fraction(n,d); +if cur_type=known then cur_exp:=take_fraction(cur_exp,v) +else if cur_type=pair_type then + begin p:=value(cur_exp); + dep_mult(x_part_loc(p),v,false); + dep_mult(y_part_loc(p),v,false); + end +else dep_mult(null,v,false); +if old_exp<>null then + begin recycle_value(old_exp); free_node(old_exp,value_node_size); + end +end; + +@ @= +begin begin_diagnostic; print_nl("{("); print_scaled(n); print_char("/"); +print_scaled(d); print(")*("); print_exp(null,0); print(")}"); +end_diagnostic(false); +end + +@ The |hard_times| routine multiplies a nice pair by a dependency list. + +@= +procedure hard_times(@!p:pointer); +var @!q:pointer; {a copy of the dependent variable |p|} +@!r:pointer; {the big node for the nice pair} +@!u,@!v:scaled; {the known values of the nice pair} +begin if type(p)=pair_type then + begin q:=stash_cur_exp; unstash_cur_exp(p); p:=q; + end; {now |cur_type=pair_type|} +r:=value(cur_exp); u:=value(x_part_loc(r)); v:=value(y_part_loc(r)); +@; +dep_mult(x_part_loc(r),u,true); dep_mult(y_part_loc(r),v,true); +end; + +@ @= +type(y_part_loc(r)):=type(p); +new_dep(y_part_loc(r),copy_dep_list(dep_list(p)));@/ +type(x_part_loc(r)):=type(p); +mem[value_loc(x_part_loc(r))]:=mem[value_loc(p)]; +link(prev_dep(p)):=x_part_loc(r); +free_node(p,value_node_size) + +@ @= +over: if (cur_type<>known)or(type(p) + else begin if cur_type=known then cur_exp:=make_scaled(cur_exp,v) + else if cur_type=pair_type then + begin p:=value(cur_exp); + dep_div(x_part_loc(p),v); + dep_div(y_part_loc(p),v); + end + else dep_div(null,v); + end; + return; + end; + +@ @= +procedure dep_div(@!p:pointer;@!v:scaled); +label exit; +var @!q:pointer; {the dependency list being divided by |v|} +@!s,@!t:small_number; {its type, before and after} +begin if p=null then q:=cur_exp +else if type(p)<>known then q:=p +else begin value(p):=make_scaled(value(p),v); return; + end; +t:=type(q); q:=dep_list(q); s:=t; +if t=dependent then + if ab_vs_cd(max_coef(q),unity,coef_bound-1,abs(v))>=0 then t:=proto_dependent; +q:=p_over_v(q,v,s,t); dep_finish(q,p,t); +exit:end; + +@ @= +begin exp_err("Division by zero"); +@.Division by zero@> +help2("You're trying to divide the quantity shown above the error")@/ + ("message by zero. I'm going to divide it by one instead."); +put_get_error; +end + +@ @= +pythag_add,pythag_sub: if (cur_type=known)and(type(p)=known) then + if c=pythag_add then cur_exp:=pyth_add(value(p),cur_exp) + else cur_exp:=pyth_sub(value(p),cur_exp) + else bad_binary(p,c); + +@ The next few sections of the program deal with affine transformations +of coordinate data. + +@= +rotated_by,slanted_by,scaled_by,shifted_by,transformed_by, + x_scaled,y_scaled,z_scaled: @t@>@;@/ + if (type(p)=path_type)or(type(p)=future_pen)or(type(p)=pen_type) then + begin path_trans(p,c); return; + end + else if (type(p)=pair_type)or(type(p)=transform_type) then big_trans(p,c) + else if type(p)=picture_type then + begin edges_trans(p,c); return; + end + else bad_binary(p,c); + +@ Let |c| be one of the eight transform operators. The procedure call +|set_up_trans(c)| first changes |cur_exp| to a transform that corresponds to +|c| and the original value of |cur_exp|. (In particular, |cur_exp| doesn't +change at all if |c=transformed_by|.) + +Then, if all components of the resulting transform are |known|, they are +moved to the global variables |txx|, |txy|, |tyx|, |tyy|, |tx|, |ty|; +and |cur_exp| is changed to the known value zero. + +@= +procedure set_up_trans(@!c:quarterword); +label done,exit; +var @!p,@!q,@!r:pointer; {list manipulation registers} +begin if (c<>transformed_by)or(cur_type<>transform_type) then + @; +@; +exit:end; + +@ @= +@!txx,@!txy,@!tyx,@!tyy,@!tx,@!ty:scaled; {current transform coefficients} + +@ @= +begin p:=stash_cur_exp; cur_exp:=id_transform; cur_type:=transform_type; +q:=value(cur_exp); +case c of +@@; +end; {there are no other cases} +disp_err(p,"Improper transformation argument"); +@.Improper transformation argument@> +help3("The expression shown above has the wrong type,")@/ + ("so I can't transform anything using it.")@/ + ("Proceed, and I'll omit the transformation."); +put_get_error; +done: recycle_value(p); free_node(p,value_node_size); +end + +@ @= +q:=value(cur_exp); r:=q+transform_node_size; +repeat r:=r-2; +if type(r)<>known then return; +until r=q; +txx:=value(xx_part_loc(q)); +txy:=value(xy_part_loc(q)); +tyx:=value(yx_part_loc(q)); +tyy:=value(yy_part_loc(q)); +tx:=value(x_part_loc(q)); +ty:=value(y_part_loc(q)); +flush_cur_exp(0) + +@ @= +rotated_by:if type(p)=known then + @; +slanted_by:if type(p)>pair_type then + begin install(xy_part_loc(q),p); goto done; + end; +scaled_by:if type(p)>pair_type then + begin install(xx_part_loc(q),p); install(yy_part_loc(q),p); goto done; + end; +shifted_by:if type(p)=pair_type then + begin r:=value(p); install(x_part_loc(q),x_part_loc(r)); + install(y_part_loc(q),y_part_loc(r)); goto done; + end; +x_scaled:if type(p)>pair_type then + begin install(xx_part_loc(q),p); goto done; + end; +y_scaled:if type(p)>pair_type then + begin install(yy_part_loc(q),p); goto done; + end; +z_scaled:if type(p)=pair_type then + @; +transformed_by:do_nothing; + +@ @= +begin n_sin_cos((value(p) mod three_sixty_units)*16); +value(xx_part_loc(q)):=round_fraction(n_cos); +value(yx_part_loc(q)):=round_fraction(n_sin); +value(xy_part_loc(q)):=-value(yx_part_loc(q)); +value(yy_part_loc(q)):=value(xx_part_loc(q)); +goto done; +end + +@ @= +begin r:=value(p); +install(xx_part_loc(q),x_part_loc(r)); +install(yy_part_loc(q),x_part_loc(r)); +install(yx_part_loc(q),y_part_loc(r)); +if type(y_part_loc(r))=known then negate(value(y_part_loc(r))) +else negate_dep_list(dep_list(y_part_loc(r))); +install(xy_part_loc(q),y_part_loc(r)); +goto done; +end + +@ Procedure |set_up_known_trans| is like |set_up_trans|, but it +insists that the transformation be entirely known. + +@= +procedure set_up_known_trans(@!c:quarterword); +begin set_up_trans(c); +if cur_type<>known then + begin exp_err("Transform components aren't all known"); +@.Transform components...@> + help3("I'm unable to apply a partially specified transformation")@/ + ("except to a fully known pair or transform.")@/ + ("Proceed, and I'll omit the transformation."); + put_get_flush_error(0); + txx:=unity; txy:=0; tyx:=0; tyy:=unity; tx:=0; ty:=0; + end; +end; + +@ Here's a procedure that applies the transform |txx..ty| to a pair of +coordinates in locations |p| and~|q|. + +@= +procedure trans(@!p,@!q:pointer); +var @!v:scaled; {the new |x| value} +begin v:=take_scaled(mem[p].sc,txx)+take_scaled(mem[q].sc,txy)+tx; +mem[q].sc:=take_scaled(mem[p].sc,tyx)+take_scaled(mem[q].sc,tyy)+ty; +mem[p].sc:=v; +end; + +@ The simplest transformation procedure applies a transform to all +coordinates of a path. The |null_pen| remains unchanged if it isn't +being shifted. + +@= +procedure path_trans(@!p:pointer;@!c:quarterword); +label exit; +var @!q:pointer; {list traverser} +begin set_up_known_trans(c); unstash_cur_exp(p); +if cur_type=pen_type then + begin if max_offset(cur_exp)=0 then if tx=0 then if ty=0 then return; + flush_cur_exp(make_path(cur_exp)); cur_type:=future_pen; + end; +q:=cur_exp; +repeat if left_type(q)<>endpoint then + trans(q+3,q+4); {that's |left_x| and |left_y|} +trans(q+1,q+2); {that's |x_coord| and |y_coord|} +if right_type(q)<>endpoint then + trans(q+5,q+6); {that's |right_x| and |right_y|} +q:=link(q); +until q=cur_exp; +exit:end; + +@ The next simplest transformation procedure applies to edges. +It is simple primarily because \MF\ doesn't allow very general +transformations to be made, and because the tricky subroutines +for edge transformation have already been written. + +@= +procedure edges_trans(@!p:pointer;@!c:quarterword); +label exit; +begin set_up_known_trans(c); unstash_cur_exp(p); cur_edges:=cur_exp; +if empty_edges(cur_edges) then return; {the empty set is easy to transform} +if txx=0 then if tyy=0 then + if txy mod unity=0 then if tyx mod unity=0 then + begin xy_swap_edges; txx:=txy; tyy:=tyx; txy:=0; tyx:=0; + if empty_edges(cur_edges) then return; + end; +if txy=0 then if tyx=0 then + if txx mod unity=0 then if tyy mod unity=0 then + @; +print_err("That transformation is too hard"); +@.That transformation...@> +help3("I can apply complicated transformations to paths,")@/ + ("but I can only do integer operations on pictures.")@/ + ("Proceed, and I'll omit the transformation."); +put_get_error; +exit:end; + +@ @= +begin if (txx=0)or(tyy=0) then + begin toss_edges(cur_edges); + cur_exp:=get_node(edge_header_size); init_edges(cur_exp); + end +else begin if txx<0 then + begin x_reflect_edges; txx:=-txx; + end; + if tyy<0 then + begin y_reflect_edges; tyy:=-tyy; + end; + if txx<>unity then x_scale_edges(txx div unity); + if tyy<>unity then y_scale_edges(tyy div unity); + @; + end; +return; +end + +@ @= +tx:=round_unscaled(tx); ty:=round_unscaled(ty); +if (m_min(cur_edges)+tx<=0)or(m_max(cur_edges)+tx>=8192)or@| + (n_min(cur_edges)+ty<=0)or(n_max(cur_edges)+ty>=8191)or@| + (abs(tx)>=4096)or(abs(ty)>=4096) then + begin print_err("Too far to shift"); +@.Too far to shift@> + help3("I can't shift the picture as requested---it would")@/ + ("make some coordinates too large or too small.")@/ + ("Proceed, and I'll omit the transformation."); + put_get_error; + end +else begin if tx<>0 then + begin if not valid_range(m_offset(cur_edges)-tx) then fix_offset; + m_min(cur_edges):=m_min(cur_edges)+tx; + m_max(cur_edges):=m_max(cur_edges)+tx; + m_offset(cur_edges):=m_offset(cur_edges)-tx; + last_window_time(cur_edges):=0; + end; + if ty<>0 then + begin n_min(cur_edges):=n_min(cur_edges)+ty; + n_max(cur_edges):=n_max(cur_edges)+ty; + n_pos(cur_edges):=n_pos(cur_edges)+ty; + last_window_time(cur_edges):=0; + end; + end + +@ The hard cases of transformation occur when big nodes are involved, +and when some of their components are unknown. + +@= +@t\4@>@@; +procedure big_trans(@!p:pointer;@!c:quarterword); +label exit; +var @!q,@!r,@!pp,@!qq:pointer; {list manipulation registers} +@!s:small_number; {size of a big node} +begin s:=big_node_size[type(p)]; q:=value(p); r:=q+s; +repeat r:=r-2; +if type(r)<>known then @; +until r=q; +@; +exit:end; {node |p| will now be recycled by |do_binary|} + +@ @= +begin set_up_known_trans(c); make_exp_copy(p); r:=value(cur_exp); +if cur_type=transform_type then + begin bilin1(yy_part_loc(r),tyy,xy_part_loc(q),tyx,0); + bilin1(yx_part_loc(r),tyy,xx_part_loc(q),tyx,0); + bilin1(xy_part_loc(r),txx,yy_part_loc(q),txy,0); + bilin1(xx_part_loc(r),txx,yx_part_loc(q),txy,0); + end; +bilin1(y_part_loc(r),tyy,x_part_loc(q),tyx,ty); +bilin1(x_part_loc(r),txx,y_part_loc(q),txy,tx); +return; +end + +@ Let |p| point to a two-word value field inside a big node of |cur_exp|, +and let |q| point to a another value field. The |bilin1| procedure +replaces |p| by $p\cdot t+q\cdot u+\delta$. + +@= +procedure bilin1(@!p:pointer;@!t:scaled;@!q:pointer;@!u,@!delta:scaled); +var @!r:pointer; {list traverser} +begin if t<>unity then dep_mult(p,t,true); +if u<>0 then + if type(q)=known then delta:=delta+take_scaled(value(q),u) + else begin @; + dep_list(p):=p_plus_fq(dep_list(p),u,dep_list(q),proto_dependent,type(q)); + end; +if type(p)=known then value(p):=value(p)+delta +else begin r:=dep_list(p); + while info(r)<>null do r:=link(r); + delta:=value(r)+delta; + if r<>dep_list(p) then value(r):=delta + else begin recycle_value(p); type(p):=known; value(p):=delta; + end; + end; +if fix_needed then fix_dependencies; +end; + +@ @= +if type(p)<>proto_dependent then + begin if type(p)=known then new_dep(p,const_dependency(value(p))) + else dep_list(p):=p_times_v(dep_list(p),unity,dependent,proto_dependent,true); + type(p):=proto_dependent; + end + +@ @= +set_up_trans(c); +if cur_type=known then @ +else begin pp:=stash_cur_exp; qq:=value(pp); + make_exp_copy(p); r:=value(cur_exp); + if cur_type=transform_type then + begin bilin2(yy_part_loc(r),yy_part_loc(qq), + value(xy_part_loc(q)),yx_part_loc(qq),null); + bilin2(yx_part_loc(r),yy_part_loc(qq), + value(xx_part_loc(q)),yx_part_loc(qq),null); + bilin2(xy_part_loc(r),xx_part_loc(qq), + value(yy_part_loc(q)),xy_part_loc(qq),null); + bilin2(xx_part_loc(r),xx_part_loc(qq), + value(yx_part_loc(q)),xy_part_loc(qq),null); + end; + bilin2(y_part_loc(r),yy_part_loc(qq), + value(x_part_loc(q)),yx_part_loc(qq),y_part_loc(qq)); + bilin2(x_part_loc(r),xx_part_loc(qq), + value(y_part_loc(q)),xy_part_loc(qq),x_part_loc(qq)); + recycle_value(pp); free_node(pp,value_node_size); + end; + +@ Let |p| be a |proto_dependent| value whose dependency list ends +at |dep_final|. The following procedure adds |v| times another +numeric quantity to~|p|. + +@= +procedure add_mult_dep(@!p:pointer;@!v:scaled;@!r:pointer); +begin if type(r)=known then + value(dep_final):=value(dep_final)+take_scaled(value(r),v) +else begin dep_list(p):= + p_plus_fq(dep_list(p),v,dep_list(r),proto_dependent,type(r)); + if fix_needed then fix_dependencies; + end; +end; + +@ The |bilin2| procedure is something like |bilin1|, but with known +and unknown quantities reversed. Parameter |p| points to a value field +within the big node for |cur_exp|; and |type(p)=known|. Parameters +|t| and~|u| point to value fields elsewhere; so does parameter~|q|, +unless it is |null| (which stands for zero). Location~|p| will be +replaced by $p\cdot t+v\cdot u+q$. + +@= +procedure bilin2(@!p,@!t:pointer;@!v:scaled;@!u,@!q:pointer); +var @!vv:scaled; {temporary storage for |value(p)|} +begin vv:=value(p); type(p):=proto_dependent; +new_dep(p,const_dependency(0)); {this sets |dep_final|} +if vv<>0 then add_mult_dep(p,vv,t); {|dep_final| doesn't change} +if v<>0 then add_mult_dep(p,v,u); +if q<>null then add_mult_dep(p,unity,q); +if dep_list(p)=dep_final then + begin vv:=value(dep_final); recycle_value(p); + type(p):=known; value(p):=vv; + end; +end; + +@ @= +begin make_exp_copy(p); r:=value(cur_exp); +if cur_type=transform_type then + begin bilin3(yy_part_loc(r),tyy,value(xy_part_loc(q)),tyx,0); + bilin3(yx_part_loc(r),tyy,value(xx_part_loc(q)),tyx,0); + bilin3(xy_part_loc(r),txx,value(yy_part_loc(q)),txy,0); + bilin3(xx_part_loc(r),txx,value(yx_part_loc(q)),txy,0); + end; +bilin3(y_part_loc(r),tyy,value(x_part_loc(q)),tyx,ty); +bilin3(x_part_loc(r),txx,value(y_part_loc(q)),txy,tx); +end + +@ Finally, in |bilin3| everything is |known|. + +@= +procedure bilin3(@!p:pointer;@!t,@!v,@!u,@!delta:scaled); +begin if t<>unity then delta:=delta+take_scaled(value(p),t) +else delta:=delta+value(p); +if u<>0 then value(p):=delta+take_scaled(v,u) +else value(p):=delta; +end; + +@ @= +concatenate: if (cur_type=string_type)and(type(p)=string_type) then cat(p) + else bad_binary(p,concatenate); +substring_of: if nice_pair(p,type(p))and(cur_type=string_type) then + chop_string(value(p)) + else bad_binary(p,substring_of); +subpath_of: begin if cur_type=pair_type then pair_to_path; + if nice_pair(p,type(p))and(cur_type=path_type) then + chop_path(value(p)) + else bad_binary(p,subpath_of); + end; + +@ @= +procedure cat(@!p:pointer); +var @!a,@!b:str_number; {the strings being concatenated} +@!k:pool_pointer; {index into |str_pool|} +begin a:=value(p); b:=cur_exp; str_room(length(a)+length(b)); +for k:=str_start[a] to str_start[a+1]-1 do append_char(so(str_pool[k])); +for k:=str_start[b] to str_start[b+1]-1 do append_char(so(str_pool[k])); +cur_exp:=make_string; delete_str_ref(b); +end; + +@ @= +procedure chop_string(@!p:pointer); +var @!a,@!b:integer; {start and stop points} +@!l:integer; {length of the original string} +@!k:integer; {runs from |a| to |b|} +@!s:str_number; {the original string} +@!reversed:boolean; {was |a>b|?} +begin a:=round_unscaled(value(x_part_loc(p))); +b:=round_unscaled(value(y_part_loc(p))); +if a<=b then reversed:=false +else begin reversed:=true; k:=a; a:=b; b:=k; + end; +s:=cur_exp; l:=length(s); +if a<0 then + begin a:=0; + if b<0 then b:=0; + end; +if b>l then + begin b:=l; + if a>l then a:=l; + end; +str_room(b-a); +if reversed then + for k:=str_start[s]+b-1 downto str_start[s]+a do append_char(so(str_pool[k])) +else for k:=str_start[s]+a to str_start[s]+b-1 do append_char(so(str_pool[k])); +cur_exp:=make_string; delete_str_ref(s); +end; + +@ @= +procedure chop_path(@!p:pointer); +var @!q:pointer; {a knot in the original path} +@!pp,@!qq,@!rr,@!ss:pointer; {link variables for copies of path nodes} +@!a,@!b,@!k,@!l:scaled; {indices for chopping} +@!reversed:boolean; {was |a>b|?} +begin l:=path_length; a:=value(x_part_loc(p)); b:=value(y_part_loc(p)); +if a<=b then reversed:=false +else begin reversed:=true; k:=a; a:=b; b:=k; + end; +@l|@>; +q:=cur_exp; +while a>=unity do + begin q:=link(q); a:=a-unity; b:=b-unity; + end; +if b=a then @ +else @; +left_type(pp):=endpoint; right_type(qq):=endpoint; link(qq):=pp; +toss_knot_list(cur_exp); +if reversed then + begin cur_exp:=link(htap_ypoc(pp)); toss_knot_list(pp); + end +else cur_exp:=pp; +end; + +@ @l|@>= +if a<0 then + if left_type(cur_exp)=endpoint then + begin a:=0; if b<0 then b:=0; + end + else repeat a:=a+l; b:=b+l; + until a>=0; {a cycle always has length |l>0|} +if b>l then if left_type(cur_exp)=endpoint then + begin b:=l; if a>l then a:=l; + end + else while a>=l do + begin a:=a-l; b:=b-l; + end + +@ @= +begin pp:=copy_knot(q); qq:=pp; +repeat q:=link(q); rr:=qq; qq:=copy_knot(q); link(rr):=qq; b:=b-unity; +until b<=0; +if a>0 then + begin ss:=pp; pp:=link(pp); + split_cubic(ss,a*@'10000,x_coord(pp),y_coord(pp)); pp:=link(ss); + free_node(ss,knot_node_size); + if rr=ss then + begin b:=make_scaled(b,unity-a); rr:=pp; + end; + end; +if b<0 then + begin split_cubic(rr,(b+unity)*@'10000,x_coord(qq),y_coord(qq)); + free_node(qq,knot_node_size); + qq:=link(rr); + end; +end + +@ @= +begin if a>0 then + begin qq:=link(q); + split_cubic(q,a*@'10000,x_coord(qq),y_coord(qq)); q:=link(q); + end; +pp:=copy_knot(q); qq:=pp; +end + +@ The |pair_value| routine changes the current expression to a +given ordered pair of values. + +@= +procedure pair_value(@!x,@!y:scaled); +var @!p:pointer; {a pair node} +begin p:=get_node(value_node_size); flush_cur_exp(p); cur_type:=pair_type; +type(p):=pair_type; name_type(p):=capsule; init_big_node(p); +p:=value(p);@/ +type(x_part_loc(p)):=known; value(x_part_loc(p)):=x;@/ +type(y_part_loc(p)):=known; value(y_part_loc(p)):=y;@/ +end; + +@ @= +point_of,precontrol_of,postcontrol_of: begin if cur_type=pair_type then + pair_to_path; + if (cur_type=path_type)and(type(p)=known) then + find_point(value(p),c) + else bad_binary(p,c); + end; +pen_offset_of: begin if cur_type=future_pen then materialize_pen; + if (cur_type=pen_type)and nice_pair(p,type(p)) then + set_up_offset(value(p)) + else bad_binary(p,pen_offset_of); + end; +direction_time_of: begin if cur_type=pair_type then pair_to_path; + if (cur_type=path_type)and nice_pair(p,type(p)) then + set_up_direction_time(value(p)) + else bad_binary(p,direction_time_of); + end; + +@ @= +procedure set_up_offset(@!p:pointer); +begin find_offset(value(x_part_loc(p)),value(y_part_loc(p)),cur_exp); +pair_value(cur_x,cur_y); +end; +@# +procedure set_up_direction_time(@!p:pointer); +begin flush_cur_exp(find_direction_time(value(x_part_loc(p)), + value(y_part_loc(p)),cur_exp)); +end; + +@ @= +procedure find_point(@!v:scaled;@!c:quarterword); +var @!p:pointer; {the path} +@!n:scaled; {its length} +@!q:pointer; {successor of |p|} +begin p:=cur_exp;@/ +if left_type(p)=endpoint then n:=-unity@+else n:=0; +repeat p:=link(p); n:=n+unity; +until p=cur_exp; +if n=0 then v:=0 +else if v<0 then + if left_type(p)=endpoint then v:=0 + else v:=n-1-((-v-1) mod n) +else if v>n then + if left_type(p)=endpoint then v:=n + else v:=v mod n; +p:=cur_exp; +while v>=unity do + begin p:=link(p); v:=v-unity; + end; +if v<>0 then @; +@; +end; + +@ @= +begin q:=link(p); split_cubic(p,v*@'10000,x_coord(q),y_coord(q)); p:=link(p); +end + +@ @= +case c of +point_of: pair_value(x_coord(p),y_coord(p)); +precontrol_of: if left_type(p)=endpoint then pair_value(x_coord(p),y_coord(p)) + else pair_value(left_x(p),left_y(p)); +postcontrol_of: if right_type(p)=endpoint then pair_value(x_coord(p),y_coord(p)) + else pair_value(right_x(p),right_y(p)); +end {there are no other cases} + +@ @= +intersect: begin if type(p)=pair_type then + begin q:=stash_cur_exp; unstash_cur_exp(p); + pair_to_path; p:=stash_cur_exp; unstash_cur_exp(q); + end; + if cur_type=pair_type then pair_to_path; + if (cur_type=path_type)and(type(p)=path_type) then + begin path_intersection(value(p),cur_exp); + pair_value(cur_t,cur_tt); + end + else bad_binary(p,intersect); + end; + +@* \[43] Statements and commands. +The chief executive of \MF\ is the |do_statement| routine, which +contains the master switch that causes all the various pieces of \MF\ +to do their things, in the right order. + +In a sense, this is the grand climax of the program: It applies all the +tools that we have worked so hard to construct. In another sense, this is +the messiest part of the program: It necessarily refers to other pieces +of code all over the place, so that a person can't fully understand what is +going on without paging back and forth to be reminded of conventions that +are defined elsewhere. We are now at the hub of the web. + +The structure of |do_statement| itself is quite simple. The first token +of the statement is fetched using |get_x_next|. If it can be the first +token of an expression, we look for an equation, an assignment, or a +title. Otherwise we use a \&{case} construction to branch at high speed to +the appropriate routine for various and sundry other types of commands, +each of which has an ``action procedure'' that does the necessary work. + +The program uses the fact that +$$\hbox{|min_primary_command=max_statement_command=type_name|}$$ +to interpret a statement that starts with, e.g., `\&{string}', +as a type declaration rather than a boolean expression. + +@p @t\4@>@@; +@t\4@>@@; +procedure do_statement; {governs \MF's activities} +begin cur_type:=vacuous; get_x_next; +if cur_cmd>max_primary_command then @ +else if cur_cmd>max_statement_command then + @ +else @; +if cur_cmd; +error_count:=0; +end; + +@ The only command codes |>max_primary_command| that can be present +at the beginning of a statement are |semicolon| and higher; these +occur when the statement is null. + +@= +begin if cur_cmd + print_cmd_mod(cur_cmd,cur_mod); print_char("'"); + help5("I was looking for the beginning of a new statement.")@/ + ("If you just proceed without changing anything, I'll ignore")@/ + ("everything up to the next `;'. Please insert a semicolon")@/ + ("now in front of anything that you don't want me to delete.")@/ + ("(See Chapter 27 of The METAFONTbook for an example.)");@/ +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> + back_error; get_x_next; + end; +end + +@ The help message printed here says that everything is flushed up to +a semicolon, but actually the commands |end_group| and |stop| will +also terminate a statement. + +@= +begin print_err("Extra tokens will be flushed"); +@.Extra tokens will be flushed@> +help6("I've just read as much of that statement as I could fathom,")@/ +("so a semicolon should have been next. It's very puzzling...")@/ +("but I'll try to get myself back together, by ignoring")@/ +("everything up to the next `;'. Please insert a semicolon")@/ +("now in front of anything that you don't want me to delete.")@/ +("(See Chapter 27 of The METAFONTbook for an example.)");@/ +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> +back_error; scanner_status:=flushing; +repeat get_next; +@; +until end_of_statement; {|cur_cmd=semicolon|, |end_group|, or |stop|} +scanner_status:=normal; +end + +@ If |do_statement| ends with |cur_cmd=end_group|, we should have +|cur_type=vacuous| unless the statement was simply an expression; +in the latter case, |cur_type| and |cur_exp| should represent that +expression. + +@= +begin if internal[tracing_commands]>0 then show_cur_cmd_mod; +case cur_cmd of +type_name:do_type_declaration; +macro_def:if cur_mod>var_def then make_op_def + else if cur_mod>end_def then scan_def; +@t\4@>@@; +end; {there are no other cases} +cur_type:=vacuous; +end + +@ The most important statements begin with expressions. + +@= +begin var_flag:=assignment; scan_expression; +if cur_cmd + else if cur_type<>vacuous then + begin exp_err("Isolated expression"); +@.Isolated expression@> + help3("I couldn't find an `=' or `:=' after the")@/ + ("expression that is shown above this error message,")@/ + ("so I guess I'll just ignore it and carry on."); + put_get_error; + end; + flush_cur_exp(0); cur_type:=vacuous; + end; +end + +@ @= +begin if internal[tracing_titles]>0 then + begin print_nl(""); slow_print(cur_exp); update_terminal; + end; +if internal[proofing]>0 then + @; +end + +@ Equations and assignments are performed by the pair of mutually recursive +@^recursion@> +routines |do_equation| and |do_assignment|. These routines are called when +|cur_cmd=equals| and when |cur_cmd=assignment|, respectively; the left-hand +side is in |cur_type| and |cur_exp|, while the right-hand side is yet +to be scanned. After the routines are finished, |cur_type| and |cur_exp| +will be equal to the right-hand side (which will normally be equal +to the left-hand side). + +@= +@t\4@>@@; +@t\4@>@@; +procedure@?do_assignment; forward;@t\2@>@/ +procedure do_equation; +var @!lhs:pointer; {capsule for the left-hand side} +@!p:pointer; {temporary register} +begin lhs:=stash_cur_exp; get_x_next; var_flag:=assignment; scan_expression; +if cur_cmd=equals then do_equation +else if cur_cmd=assignment then do_assignment; +if internal[tracing_commands]>two then @; +if cur_type=unknown_path then if type(lhs)=pair_type then + begin p:=stash_cur_exp; unstash_cur_exp(lhs); lhs:=p; + end; {in this case |make_eq| will change the pair to a path} +make_eq(lhs); {equate |lhs| to |(cur_type,cur_exp)|} +end; + +@ And |do_assignment| is similar to |do_equation|: + +@= +procedure do_assignment; +var @!lhs:pointer; {token list for the left-hand side} +@!p:pointer; {where the left-hand value is stored} +@!q:pointer; {temporary capsule for the right-hand value} +begin if cur_type<>token_list then + begin exp_err("Improper `:=' will be changed to `='"); +@.Improper `:='@> + help2("I didn't find a variable name at the left of the `:=',")@/ + ("so I'm going to pretend that you said `=' instead.");@/ + error; do_equation; + end +else begin lhs:=cur_exp; cur_type:=vacuous;@/ + get_x_next; var_flag:=assignment; scan_expression; + if cur_cmd=equals then do_equation + else if cur_cmd=assignment then do_assignment; + if internal[tracing_commands]>two then @; + if info(lhs)>hash_end then + @ + else @; + flush_node_list(lhs); + end; +end; + +@ @= +begin begin_diagnostic; print_nl("{("); print_exp(lhs,0); +print(")=("); print_exp(null,0); print(")}"); end_diagnostic(false); +end + +@ @= +begin begin_diagnostic; print_nl("{"); +if info(lhs)>hash_end then slow_print(int_name[info(lhs)-(hash_end)]) +else show_token_list(lhs,null,1000,0); +print(":="); print_exp(null,0); print_char("}"); end_diagnostic(false); +end + +@ @= +if cur_type=known then internal[info(lhs)-(hash_end)]:=cur_exp +else begin exp_err("Internal quantity `"); +@.Internal quantity...@> + slow_print(int_name[info(lhs)-(hash_end)]); + print("' must receive a known value"); + help2("I can't set an internal quantity to anything but a known")@/ + ("numeric value, so I'll have to ignore this assignment."); + put_get_error; + end + +@ @= +begin p:=find_variable(lhs); +if p<>null then + begin q:=stash_cur_exp; cur_type:=und_type(p); recycle_value(p); + type(p):=cur_type; value(p):=null; make_exp_copy(p); + p:=stash_cur_exp; unstash_cur_exp(q); make_eq(p); + end +else begin obliterated(lhs); put_get_error; + end; +end + + +@ And now we get to the nitty-gritty. The |make_eq| procedure is given +a pointer to a capsule that is to be equated to the current expression. + +@= +procedure make_eq(@!lhs:pointer); +label restart,done, not_found; +var @!t:small_number; {type of the left-hand side} +@!v:integer; {value of the left-hand side} +@!p,@!q:pointer; {pointers inside of big nodes} +begin restart: t:=type(lhs); +if t<=pair_type then v:=value(lhs); +case t of +@t\4@>@@; +end; {all cases have been listed} +@; +done:check_arith; recycle_value(lhs); free_node(lhs,value_node_size); +end; + +@ @= +disp_err(lhs,""); exp_err("Equation cannot be performed ("); +@.Equation cannot be performed@> +if type(lhs)<=pair_type then print_type(type(lhs))@+else print("numeric"); +print_char("="); +if cur_type<=pair_type then print_type(cur_type)@+else print("numeric"); +print_char(")");@/ +help2("I'm sorry, but I don't know how to make such things equal.")@/ + ("(See the two expressions just above the error message.)"); +put_get_error + +@ @= +boolean_type,string_type,pen_type,path_type,picture_type: + if cur_type=t+unknown_tag then + begin nonlinear_eq(v,cur_exp,false); unstash_cur_exp(cur_exp); goto done; + end + else if cur_type=t then + @; +unknown_types:if cur_type=t-unknown_tag then + begin nonlinear_eq(cur_exp,lhs,true); goto done; + end + else if cur_type=t then + begin ring_merge(lhs,cur_exp); goto done; + end + else if cur_type=pair_type then if t=unknown_path then + begin pair_to_path; goto restart; + end; +transform_type,pair_type:if cur_type=t then + @; +known,dependent,proto_dependent,independent:if cur_type>=known then + begin try_eq(lhs,null); goto done; + end; +vacuous:do_nothing; + +@ @= +begin if cur_type<=string_type then + begin if cur_type=string_type then + begin if str_vs_str(v,cur_exp)<>0 then goto not_found; + end + else if v<>cur_exp then goto not_found; + @; goto done; + end; +print_err("Redundant or inconsistent equation"); +@.Redundant or inconsistent equation@> +help2("An equation between already-known quantities can't help.")@/ + ("But don't worry; continue and I'll just ignore it."); +put_get_error; goto done; +not_found: print_err("Inconsistent equation"); +@.Inconsistent equation@> +help2("The equation I just read contradicts what was said before.")@/ + ("But don't worry; continue and I'll just ignore it."); +put_get_error; goto done; +end + +@ @= +begin p:=v+big_node_size[t]; q:=value(cur_exp)+big_node_size[t]; +repeat p:=p-2; q:=q-2; try_eq(p,q); +until p=v; +goto done; +end + +@ The first argument to |try_eq| is the location of a value node +in a capsule that will soon be recycled. The second argument is +either a location within a pair or transform node pointed to by +|cur_exp|, or it is |null| (which means that |cur_exp| itself +serves as the second argument). The idea is to leave |cur_exp| unchanged, +but to equate the two operands. + +@= +procedure try_eq(@!l,@!r:pointer); +label done,done1; +var @!p:pointer; {dependency list for right operand minus left operand} +@!t:known..independent; {the type of list |p|} +@!q:pointer; {the constant term of |p| is here} +@!pp:pointer; {dependency list for right operand} +@!tt:dependent..independent; {the type of list |pp|} +@!copied:boolean; {have we copied a list that ought to be recycled?} +begin @; +@; +if info(p)=null then @ +else begin linear_eq(p,t); + if r=null then if cur_type<>known then if type(cur_exp)=known then + begin pp:=cur_exp; cur_exp:=value(cur_exp); cur_type:=known; + free_node(pp,value_node_size); + end; + end; +end; + +@ @= +t:=type(l); +if t=known then + begin t:=dependent; p:=const_dependency(-value(l)); q:=p; + end +else if t=independent then + begin t:=dependent; p:=single_dependency(l); negate(value(p)); + q:=dep_final; + end +else begin p:=dep_list(l); q:=p; + loop@+ begin negate(value(q)); + if info(q)=null then goto done; + q:=link(q); + end; + done: link(prev_dep(l)):=link(q); prev_dep(link(q)):=prev_dep(l); + type(l):=known; + end + +@ @= +begin if abs(value(p))>64 then {off by .001 or more} + begin print_err("Inconsistent equation");@/ +@.Inconsistent equation@> + print(" (off by "); print_scaled(value(p)); print_char(")"); + help2("The equation I just read contradicts what was said before.")@/ + ("But don't worry; continue and I'll just ignore it."); + put_get_error; + end +else if r=null then @; +free_node(p,dep_node_size); +end + +@ @= +if r=null then + if cur_type=known then + begin value(q):=value(q)+cur_exp; goto done1; + end + else begin tt:=cur_type; + if tt=independent then pp:=single_dependency(cur_exp) + else pp:=dep_list(cur_exp); + end +else if type(r)=known then + begin value(q):=value(q)+value(r); goto done1; + end + else begin tt:=type(r); + if tt=independent then pp:=single_dependency(r) + else pp:=dep_list(r); + end; +if tt<>independent then copied:=false +else begin copied:=true; tt:=dependent; + end; +@; +if copied then flush_node_list(pp); +done1: + +@ @= +watch_coefs:=false; +if t=tt then p:=p_plus_q(p,pp,t) +else if t=proto_dependent then + p:=p_plus_fq(p,unity,pp,proto_dependent,dependent) +else begin q:=p; + while info(q)<>null do + begin value(q):=round_fraction(value(q)); q:=link(q); + end; + t:=proto_dependent; p:=p_plus_q(p,pp,t); + end; +watch_coefs:=true; + +@ Our next goal is to process type declarations. For this purpose it's +convenient to have a procedure that scans a $\langle\,$declared +variable$\,\rangle$ and returns the corresponding token list. After the +following procedure has acted, the token after the declared variable +will have been scanned, so it will appear in |cur_cmd|, |cur_mod|, +and~|cur_sym|. + +@= +function scan_declared_variable:pointer; +label done; +var @!x:pointer; {hash address of the variable's root} +@!h,@!t:pointer; {head and tail of the token list to be returned} +@!l:pointer; {hash address of left bracket} +begin get_symbol; x:=cur_sym; +if cur_cmd<>tag_token then clear_symbol(x,false); +h:=get_avail; info(h):=x; t:=h;@/ +loop@+ begin get_x_next; + if cur_sym=0 then goto done; + if cur_cmd<>tag_token then if cur_cmd<>internal_quantity then + if cur_cmd=left_bracket then @ + else goto done; + link(t):=get_avail; t:=link(t); info(t):=cur_sym; + end; +done: if eq_type(x) mod outer_tag<>tag_token then clear_symbol(x,false); +if equiv(x)=null then new_root(x); +scan_declared_variable:=h; +end; + +@ If the subscript isn't collective, we don't accept it as part of the +declared variable. + +@= +begin l:=cur_sym; get_x_next; +if cur_cmd<>right_bracket then + begin back_input; cur_sym:=l; cur_cmd:=left_bracket; goto done; + end +else cur_sym:=collective_subscript; +end + +@ Type declarations are introduced by the following primitive operations. + +@= +primitive("numeric",type_name,numeric_type);@/ +@!@:numeric_}{\&{numeric} primitive@> +primitive("string",type_name,string_type);@/ +@!@:string_}{\&{string} primitive@> +primitive("boolean",type_name,boolean_type);@/ +@!@:boolean_}{\&{boolean} primitive@> +primitive("path",type_name,path_type);@/ +@!@:path_}{\&{path} primitive@> +primitive("pen",type_name,pen_type);@/ +@!@:pen_}{\&{pen} primitive@> +primitive("picture",type_name,picture_type);@/ +@!@:picture_}{\&{picture} primitive@> +primitive("transform",type_name,transform_type);@/ +@!@:transform_}{\&{transform} primitive@> +primitive("pair",type_name,pair_type);@/ +@!@:pair_}{\&{pair} primitive@> + +@ @= +type_name: print_type(m); + +@ Now we are ready to handle type declarations, assuming that a +|type_name| has just been scanned. + +@= +procedure do_type_declaration; +var @!t:small_number; {the type being declared} +@!p:pointer; {token list for a declared variable} +@!q:pointer; {value node for the variable} +begin if cur_mod>=transform_type then t:=cur_mod@+else t:=cur_mod+unknown_tag; +repeat p:=scan_declared_variable; +flush_variable(equiv(info(p)),link(p),false);@/ +q:=find_variable(p); +if q<>null then + begin type(q):=t; value(q):=null; + end +else begin print_err("Declared variable conflicts with previous vardef"); +@.Declared variable conflicts...@> + help2("You can't use, e.g., `numeric foo[]' after `vardef foo'.")@/ + ("Proceed, and I'll ignore the illegal redeclaration."); + put_get_error; + end; +flush_list(p); +if cur_cmd; +until end_of_statement; +end; + +@ @= +begin print_err("Illegal suffix of declared variable will be flushed"); +@.Illegal suffix...flushed@> +help5("Variables in declarations must consist entirely of")@/ + ("names and collective subscripts, e.g., `x[]a'.")@/ + ("Are you trying to use a reserved word in a variable name?")@/ + ("I'm going to discard the junk I found here,")@/ + ("up to the next comma or the end of the declaration."); +if cur_cmd=numeric_token then + help_line[2]:="Explicit subscripts like `x15a' aren't permitted."; +put_get_error; scanner_status:=flushing; +repeat get_next; +@; +until cur_cmd>=comma; {either |end_of_statement| or |cur_cmd=comma|} +scanner_status:=normal; +end + +@ \MF's |main_control| procedure just calls |do_statement| repeatedly +until coming to the end of the user's program. +Each execution of |do_statement| concludes with +|cur_cmd=semicolon|, |end_group|, or |stop|. + +@p procedure main_control; +begin repeat do_statement; +if cur_cmd=end_group then + begin print_err("Extra `endgroup'"); +@.Extra `endgroup'@> + help2("I'm not currently working on a `begingroup',")@/ + ("so I had better not try to end anything."); + flush_error(0); + end; +until cur_cmd=stop; +end; + +@ @= +primitive("end",stop,0);@/ +@!@:end_}{\&{end} primitive@> +primitive("dump",stop,1);@/ +@!@:dump_}{\&{dump} primitive@> + +@ @= +stop:if m=0 then print("end")@+else print("dump"); + +@* \[44] Commands. +Let's turn now to statements that are classified as ``commands'' because +of their imperative nature. We'll begin with simple ones, so that it +will be clear how to hook command processing into the |do_statement| routine; +then we'll tackle the tougher commands. + +Here's one of the simplest: + +@= +random_seed: do_random_seed; + +@ @= +procedure do_random_seed; +begin get_x_next; +if cur_cmd<>assignment then + begin missing_err(":="); +@.Missing `:='@> + help1("Always say `randomseed:='."); + back_error; + end; +get_x_next; scan_expression; +if cur_type<>known then + begin exp_err("Unknown value will be ignored"); +@.Unknown value...ignored@> + help2("Your expression was too random for me to handle,")@/ + ("so I won't change the random seed just now.");@/ + put_get_flush_error(0); + end +else @; +end; + +@ @= +begin init_randoms(cur_exp); +if selector>=log_only then + begin old_setting:=selector; selector:=log_only; + print_nl("{randomseed:="); print_scaled(cur_exp); print_char("}"); + print_nl(""); selector:=old_setting; + end; +end + +@ And here's another simple one (somewhat different in flavor): + +@= +mode_command: begin print_ln; interaction:=cur_mod; + @; + if log_opened then selector:=selector+2; + get_x_next; + end; + +@ @= +primitive("batchmode",mode_command,batch_mode); +@!@:batch_mode_}{\&{batchmode} primitive@> +primitive("nonstopmode",mode_command,nonstop_mode); +@!@:nonstop_mode_}{\&{nonstopmode} primitive@> +primitive("scrollmode",mode_command,scroll_mode); +@!@:scroll_mode_}{\&{scrollmode} primitive@> +primitive("errorstopmode",mode_command,error_stop_mode); +@!@:error_stop_mode_}{\&{errorstopmode} primitive@> + +@ @= +mode_command: case m of + batch_mode: print("batchmode"); + nonstop_mode: print("nonstopmode"); + scroll_mode: print("scrollmode"); + othercases print("errorstopmode") + endcases; + +@ The `\&{inner}' and `\&{outer}' commands are only slightly harder. + +@= +protection_command: do_protection; + +@ @= +primitive("inner",protection_command,0);@/ +@!@:inner_}{\&{inner} primitive@> +primitive("outer",protection_command,1);@/ +@!@:outer_}{\&{outer} primitive@> + +@ @= +protection_command: if m=0 then print("inner")@+else print("outer"); + +@ @= +procedure do_protection; +var @!m:0..1; {0 to unprotect, 1 to protect} +@!t:halfword; {the |eq_type| before we change it} +begin m:=cur_mod; +repeat get_symbol; t:=eq_type(cur_sym); + if m=0 then + begin if t>=outer_tag then eq_type(cur_sym):=t-outer_tag; + end + else if tcomma; +end; + +@ \MF\ never defines the tokens `\.(' and `\.)' to be primitives, but +plain \MF\ begins with the declaration `\&{delimiters} \.{()}'. Such a +declaration assigns the command code |left_delimiter| to `\.{(}' and +|right_delimiter| to `\.{)}'; the |equiv| of each delimiter is the +hash address of its mate. + +@= +delimiters: def_delims; + +@ @= +procedure def_delims; +var l_delim,r_delim:pointer; {the new delimiter pair} +begin get_clear_symbol; l_delim:=cur_sym;@/ +get_clear_symbol; r_delim:=cur_sym;@/ +eq_type(l_delim):=left_delimiter; equiv(l_delim):=r_delim;@/ +eq_type(r_delim):=right_delimiter; equiv(r_delim):=l_delim;@/ +get_x_next; +end; + +@ Here is a procedure that is called when \MF\ has reached a point +where some right delimiter is mandatory. + +@= +procedure check_delimiter(@!l_delim,@!r_delim:pointer); +label exit; +begin if cur_cmd=right_delimiter then if cur_mod=l_delim then return; +if cur_sym<>r_delim then + begin missing_err(text(r_delim));@/ +@.Missing `)'@> + help2("I found no right delimiter to match a left one. So I've")@/ + ("put one in, behind the scenes; this may fix the problem."); + back_error; + end +else begin print_err("The token `"); slow_print(text(r_delim)); +@.The token...delimiter@> + print("' is no longer a right delimiter"); + help3("Strange: This token has lost its former meaning!")@/ + ("I'll read it as a right delimiter this time;")@/ + ("but watch out, I'll probably miss it later."); + error; + end; +exit:end; + +@ The next four commands save or change the values associated with tokens. + +@= +save_command: repeat get_symbol; save_variable(cur_sym); get_x_next; + until cur_cmd<>comma; +interim_command: do_interim; +let_command: do_let; +new_internal: do_new_internal; + +@ @= +procedure@?do_statement; forward;@t\2@>@/ +procedure do_interim; +begin get_x_next; +if cur_cmd<>internal_quantity then + begin print_err("The token `"); +@.The token...quantity@> + if cur_sym=0 then print("(%CAPSULE)") + else slow_print(text(cur_sym)); + print("' isn't an internal quantity"); + help1("Something like `tracingonline' should follow `interim'."); + back_error; + end +else begin save_internal(cur_mod); back_input; + end; +do_statement; +end; + +@ The following procedure is careful not to undefine the left-hand symbol +too soon, lest commands like `{\tt let x=x}' have a surprising effect. + +@= +procedure do_let; +var @!l:pointer; {hash location of the left-hand symbol} +begin get_symbol; l:=cur_sym; get_x_next; +if cur_cmd<>equals then if cur_cmd<>assignment then + begin missing_err("="); +@.Missing `='@> + help3("You should have said `let symbol = something'.")@/ + ("But don't worry; I'll pretend that an equals sign")@/ + ("was present. The next token I read will be `something'."); + back_error; + end; +get_symbol; +case cur_cmd of +defined_macro,secondary_primary_macro,tertiary_secondary_macro, + expression_tertiary_macro: add_mac_ref(cur_mod); +othercases do_nothing +endcases;@/ +clear_symbol(l,false); eq_type(l):=cur_cmd; +if cur_cmd=tag_token then equiv(l):=null +else equiv(l):=cur_mod; +get_x_next; +end; + +@ @= +procedure do_new_internal; +begin repeat if int_ptr=max_internal then + overflow("number of internals",max_internal); +@:METAFONT capacity exceeded number of int}{\quad number of internals@> +get_clear_symbol; incr(int_ptr); +eq_type(cur_sym):=internal_quantity; equiv(cur_sym):=int_ptr; +int_name[int_ptr]:=text(cur_sym); internal[int_ptr]:=0; +get_x_next; +until cur_cmd<>comma; +end; + +@ The various `\&{show}' commands are distinguished by modifier fields +in the usual way. + +@d show_token_code=0 {show the meaning of a single token} +@d show_stats_code=1 {show current memory and string usage} +@d show_code=2 {show a list of expressions} +@d show_var_code=3 {show a variable and its descendents} +@d show_dependencies_code=4 {show dependent variables in terms of independents} + +@= +primitive("showtoken",show_command,show_token_code);@/ +@!@:show_token_}{\&{showtoken} primitive@> +primitive("showstats",show_command,show_stats_code);@/ +@!@:show_stats_}{\&{showstats} primitive@> +primitive("show",show_command,show_code);@/ +@!@:show_}{\&{show} primitive@> +primitive("showvariable",show_command,show_var_code);@/ +@!@:show_var_}{\&{showvariable} primitive@> +primitive("showdependencies",show_command,show_dependencies_code);@/ +@!@:show_dependencies_}{\&{showdependencies} primitive@> + +@ @= +show_command: case m of + show_token_code:print("showtoken"); + show_stats_code:print("showstats"); + show_code:print("show"); + show_var_code:print("showvariable"); + othercases print("showdependencies") + endcases; + +@ @= +show_command:do_show_whatever; + +@ The value of |cur_mod| controls the |verbosity| in the |print_exp| routine: +If it's |show_code|, complicated structures are abbreviated, otherwise +they aren't. + +@= +procedure do_show; +begin repeat get_x_next; scan_expression; +print_nl(">> "); +@.>>@> +print_exp(null,2); flush_cur_exp(0); +until cur_cmd<>comma; +end; + +@ @= +procedure disp_token; +begin print_nl("> "); +@.>\relax@> +if cur_sym=0 then @ +else begin slow_print(text(cur_sym)); print_char("="); + if eq_type(cur_sym)>=outer_tag then print("(outer) "); + print_cmd_mod(cur_cmd,cur_mod); + if cur_cmd=defined_macro then + begin print_ln; show_macro(cur_mod,null,100000); + end; {this avoids recursion between |show_macro| and |print_cmd_mod|} +@^recursion@> + end; +end; + +@ @= +begin if cur_cmd=numeric_token then print_scaled(cur_mod) +else if cur_cmd=capsule_token then + begin g_pointer:=cur_mod; print_capsule; + end +else begin print_char(""""); slow_print(cur_mod); print_char(""""); + delete_str_ref(cur_mod); + end; +end + +@ The following cases of |print_cmd_mod| might arise in connection +with |disp_token|, although they don't necessarily correspond to +primitive tokens. + +@= +left_delimiter,right_delimiter: begin if c=left_delimiter then print("lef") + else print("righ"); + print("t delimiter that matches "); slow_print(text(m)); + end; +tag_token:if m=null then print("tag")@+else print("variable"); +defined_macro: print("macro:"); +secondary_primary_macro,tertiary_secondary_macro,expression_tertiary_macro: + begin print_cmd_mod(macro_def,c); print("'d macro:"); + print_ln; show_token_list(link(link(m)),null,1000,0); + end; +repeat_loop:print("[repeat the loop]"); +internal_quantity:slow_print(int_name[m]); + +@ @= +procedure do_show_token; +begin repeat get_next; disp_token; +get_x_next; +until cur_cmd<>comma; +end; + +@ @= +procedure do_show_stats; +begin print_nl("Memory usage "); +@.Memory usage...@> +@!stat print_int(var_used); print_char("&"); print_int(dyn_used); +if false then@+tats@t@>@;@/ +print("unknown"); +print(" ("); print_int(hi_mem_min-lo_mem_max-1); +print(" still untouched)"); print_ln; +print_nl("String usage "); +print_int(str_ptr-init_str_ptr); print_char("&"); +print_int(pool_ptr-init_pool_ptr); +print(" ("); +print_int(max_strings-max_str_ptr); print_char("&"); +print_int(pool_size-max_pool_ptr); print(" still untouched)"); print_ln; +get_x_next; +end; + +@ Here's a recursive procedure that gives an abbreviated account +of a variable, for use by |do_show_var|. + +@= +procedure disp_var(@!p:pointer); +var @!q:pointer; {traverses attributes and subscripts} +@!n:0..max_print_line; {amount of macro text to show} +begin if type(p)=structured then @ +else if type(p)>=unsuffixed_macro then @ +else if type(p)<>undefined then + begin print_nl(""); print_variable_name(p); print_char("="); + print_exp(p,0); + end; +end; + +@ @= +begin q:=attr_head(p); +repeat disp_var(q); q:=link(q); +until q=end_attr; +q:=subscr_head(p); +while name_type(q)=subscr do + begin disp_var(q); q:=link(q); + end; +end + +@ @= +begin print_nl(""); print_variable_name(p); +if type(p)>unsuffixed_macro then print("@@#"); {|suffixed_macro|} +print("=macro:"); +if file_offset>=max_print_line-20 then n:=5 +else n:=max_print_line-file_offset-15; +show_macro(value(p),null,n); +end + +@ @= +procedure do_show_var; +label done; +begin repeat get_next; +if cur_sym>0 then if cur_sym<=hash_end then + if cur_cmd=tag_token then if cur_mod<>null then + begin disp_var(cur_mod); goto done; + end; +disp_token; +done:get_x_next; +until cur_cmd<>comma; +end; + +@ @= +procedure do_show_dependencies; +var @!p:pointer; {link that runs through all dependencies} +begin p:=link(dep_head); +while p<>dep_head do + begin if interesting(p) then + begin print_nl(""); print_variable_name(p); + if type(p)=dependent then print_char("=") + else print(" = "); {extra spaces imply proto-dependency} + print_dependency(dep_list(p),type(p)); + end; + p:=dep_list(p); + while info(p)<>null do p:=link(p); + p:=link(p); + end; +get_x_next; +end; + +@ Finally we are ready for the procedure that governs all of the +show commands. + +@= +procedure do_show_whatever; +begin if interaction=error_stop_mode then wake_up_terminal; +case cur_mod of +show_token_code:do_show_token; +show_stats_code:do_show_stats; +show_code:do_show; +show_var_code:do_show_var; +show_dependencies_code:do_show_dependencies; +end; {there are no other cases} +if internal[showstopping]>0 then + begin print_err("OK"); +@.OK@> + if interaction= +primitive("contour",thing_to_add,contour_code);@/ +@!@:contour_}{\&{contour} primitive@> +primitive("doublepath",thing_to_add,double_path_code);@/ +@!@:double_path_}{\&{doublepath} primitive@> +primitive("also",thing_to_add,also_code);@/ +@!@:also_}{\&{also} primitive@> +primitive("withpen",with_option,pen_type);@/ +@!@:with_pen_}{\&{withpen} primitive@> +primitive("withweight",with_option,known);@/ +@!@:with_weight_}{\&{withweight} primitive@> +primitive("dropping",cull_op,drop_code);@/ +@!@:dropping_}{\&{dropping} primitive@> +primitive("keeping",cull_op,keep_code);@/ +@!@:keeping_}{\&{keeping} primitive@> + +@ @= +thing_to_add:if m=contour_code then print("contour") + else if m=double_path_code then print("doublepath") + else print("also"); +with_option:if m=pen_type then print("withpen") + else print("withweight"); +cull_op:if m=drop_code then print("dropping") + else print("keeping"); + +@ @= +function scan_with:boolean; +var @!t:small_number; {|known| or |pen_type|} +@!result:boolean; {the value to return} +begin t:=cur_mod; cur_type:=vacuous; get_x_next; scan_expression; +result:=false; +if cur_type<>t then @ +else if cur_type=pen_type then result:=true +else @; +scan_with:=result; +end; + +@ @= +begin exp_err("Improper type"); +@.Improper type@> +help2("Next time say `withweight ';")@/ + ("I'll ignore the bad `with' clause and look for another."); +if t=pen_type then + help_line[1]:="Next time say `withpen ';"; +put_get_flush_error(0); +end + +@ @= +begin cur_exp:=round_unscaled(cur_exp); +if (abs(cur_exp)<4)and(cur_exp<>0) then result:=true +else begin print_err("Weight must be -3, -2, -1, +1, +2, or +3"); +@.Weight must be...@> + help1("I'll ignore the bad `with' clause and look for another."); + put_get_flush_error(0); + end; +end + +@ One of the things we need to do when we've parsed an \&{addto} or +similar command is set |cur_edges| to the header of a supposed \&{picture} +variable, given a token list for that variable. + +@= +procedure find_edges_var(@!t:pointer); +var @!p:pointer; +begin p:=find_variable(t); cur_edges:=null; +if p=null then + begin obliterated(t); put_get_error; + end +else if type(p)<>picture_type then + begin print_err("Variable "); show_token_list(t,null,1000,0); +@.Variable x is the wrong type@> + print(" is the wrong type ("); print_type(type(p)); print_char(")"); + help2("I was looking for a ""known"" picture variable.")@/ + ("So I'll not change anything just now."); put_get_error; + end +else cur_edges:=value(p); +flush_node_list(t); +end; + +@ @= +add_to_command: do_add_to; + +@ @= +procedure do_add_to; +label done, not_found; +var @!lhs,@!rhs:pointer; {variable on left, path on right} +@!w:integer; {tentative weight} +@!p:pointer; {list manipulation register} +@!q:pointer; {beginning of second half of doubled path} +@!add_to_type:double_path_code..also_code; {modifier of \&{addto}} +begin get_x_next; var_flag:=thing_to_add; scan_primary; +if cur_type<>token_list then + @ +else begin lhs:=cur_exp; add_to_type:=cur_mod;@/ + cur_type:=vacuous; get_x_next; scan_expression; + if add_to_type=also_code then @ + else @; + end; +end; + +@ @= +begin exp_err("Not a suitable variable"); +@.Not a suitable variable@> +help4("At this point I needed to see the name of a picture variable.")@/ + ("(Or perhaps you have indeed presented me with one; I might")@/ + ("have missed it, if it wasn't followed by the proper token.)")@/ + ("So I'll not change anything just now."); +put_get_flush_error(0); +end + +@ @= +begin find_edges_var(lhs); +if cur_edges=null then flush_cur_exp(0) +else if cur_type<>picture_type then + begin exp_err("Improper `addto'"); +@.Improper `addto'@> + help2("This expression should have specified a known picture.")@/ + ("So I'll not change anything just now."); put_get_flush_error(0); + end +else begin merge_edges(cur_exp); flush_cur_exp(0); + end; +end + +@ @= +begin if cur_type=pair_type then pair_to_path; +if cur_type<>path_type then + begin exp_err("Improper `addto'"); +@.Improper `addto'@> + help2("This expression should have been a known path.")@/ + ("So I'll not change anything just now."); + put_get_flush_error(0); flush_token_list(lhs); + end +else begin rhs:=cur_exp; w:=1; cur_pen:=null_pen; + while cur_cmd=with_option do + if scan_with then + if cur_type=known then w:=cur_exp + else @; + @; + delete_pen_ref(cur_pen); + end; +end + +@ We could say `|add_pen_ref(cur_pen)|; |flush_cur_exp(0)|' after changing +|cur_pen| here. But that would have no effect, because the current expression +will not be flushed. Thus we save a bit of code (at the risk of being too +tricky). + +@= +begin delete_pen_ref(cur_pen); cur_pen:=cur_exp; +end + +@ @= +find_edges_var(lhs); +if cur_edges=null then toss_knot_list(rhs) +else begin lhs:=null; cur_path_type:=add_to_type; + if left_type(rhs)=endpoint then + if cur_path_type=double_path_code then @ + else @ + else if cur_path_type=double_path_code then lhs:=htap_ypoc(rhs); + cur_wt:=w; rhs:=make_spec(rhs,max_offset(cur_pen),internal[tracing_specs]); + @; + if max_offset(cur_pen)=0 then fill_spec(rhs) + else fill_envelope(rhs); + if lhs<>null then + begin rev_turns:=true; + lhs:=make_spec(lhs,max_offset(cur_pen),internal[tracing_specs]); + rev_turns:=false; + if max_offset(cur_pen)=0 then fill_spec(lhs) + else fill_envelope(lhs); + end; +not_found: end + +@ @= +if link(rhs)=rhs then @ +else begin p:=htap_ypoc(rhs); q:=link(p);@/ + right_x(path_tail):=right_x(q); right_y(path_tail):=right_y(q); + right_type(path_tail):=right_type(q); + link(path_tail):=link(q); free_node(q,knot_node_size);@/ + right_x(p):=right_x(rhs); right_y(p):=right_y(rhs); + right_type(p):=right_type(rhs); + link(p):=link(rhs); free_node(rhs,knot_node_size);@/ + rhs:=p; + end + +@ @= +begin right_x(rhs):=x_coord(rhs); right_y(rhs):=y_coord(rhs); +left_x(rhs):=x_coord(rhs); left_y(rhs):=y_coord(rhs); +left_type(rhs):=explicit; right_type(rhs):=explicit; +end + +@ @= +begin print_err("Not a cycle"); +@.Not a cycle@> +help2("That contour should have ended with `..cycle' or `&cycle'.")@/ + ("So I'll not change anything just now."); put_get_error; +toss_knot_list(rhs); goto not_found; +end + +@ @= +if turning_number<=0 then + if cur_path_type<>double_path_code then if internal[turning_check]>0 then + if (turning_number<0)and(link(cur_pen)=null) then negate(cur_wt) + else begin if turning_number=0 then + if (internal[turning_check]<=unity)and(link(cur_pen)=null) then goto done + else print_strange("Strange path (turning number is zero)") +@.Strange path...@> + else print_strange("Backwards path (turning number is negative)"); +@.Backwards path...@> + help3("The path doesn't have a counterclockwise orientation,")@/ + ("so I'll probably have trouble drawing it.")@/ + ("(See Chapter 27 of The METAFONTbook for more help.)"); +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> + put_get_error; + end; +done: + +@ @= +ship_out_command: do_ship_out; +display_command: do_display; +open_window: do_open_window; +cull_command: do_cull; + +@ @= +@t\4@>@@; +procedure do_ship_out; +label exit; +var @!c:integer; {the character code} +begin get_x_next; var_flag:=semicolon; scan_expression; +if cur_type<>token_list then + if cur_type=picture_type then cur_edges:=cur_exp + else begin @; + return; + end +else begin find_edges_var(cur_exp); cur_type:=vacuous; + end; +if cur_edges<>null then + begin c:=round_unscaled(internal[char_code]) mod 256; + if c<0 then c:=c+256; + @; + if internal[proofing]>=0 then ship_out(c); + end; +flush_cur_exp(0); +exit:end; + +@ @= +procedure do_display; +label not_found,common_ending,exit; +var @!e:pointer; {token list for a picture variable} +begin get_x_next; var_flag:=in_window; scan_primary; +if cur_type<>token_list then + @ +else begin e:=cur_exp; cur_type:=vacuous; + get_x_next; scan_expression; + if cur_type<>known then goto common_ending; + cur_exp:=round_unscaled(cur_exp); + if cur_exp<0 then goto not_found; + if cur_exp>15 then goto not_found; + if not window_open[cur_exp] then goto not_found; + find_edges_var(e); + if cur_edges<>null then disp_edges(cur_exp); + return; + not_found: cur_exp:=cur_exp*unity; + common_ending: exp_err("Bad window number"); +@.Bad window number@> + help1("It should be the number of an open window."); + put_get_flush_error(0); flush_token_list(e); + end; +exit:end; + +@ The only thing difficult about `\&{openwindow}' is that the syntax +allows the user to go astray in many ways. The following subroutine +helps keep the necessary program reasonably short and sweet. + +@= +function get_pair(@!c:command_code):boolean; +var @!p:pointer; {a pair of values that are known (we hope)} +@!b:boolean; {did we find such a pair?} +begin if cur_cmd<>c then get_pair:=false +else begin get_x_next; scan_expression; + if nice_pair(cur_exp,cur_type) then + begin p:=value(cur_exp); + cur_x:=value(x_part_loc(p)); cur_y:=value(y_part_loc(p)); + b:=true; + end + else b:=false; + flush_cur_exp(0); get_pair:=b; + end; +end; + +@ @= +procedure do_open_window; +label not_found,exit; +var @!k:integer; {the window number in question} +@!r0,@!c0,@!r1,@!c1:scaled; {window coordinates} +begin get_x_next; scan_expression; +if cur_type<>known then goto not_found; +k:=round_unscaled(cur_exp); +if k<0 then goto not_found; +if k>15 then goto not_found; +if not get_pair(from_token) then goto not_found; +r0:=cur_x; c0:=cur_y; +if not get_pair(to_token) then goto not_found; +r1:=cur_x; c1:=cur_y; +if not get_pair(at_token) then goto not_found; +open_a_window(k,r0,c0,r1,c1,cur_x,cur_y); return; +not_found:print_err("Improper `openwindow'"); +@.Improper `openwindow'@> +help2("Say `openwindow k from (r0,c0) to (r1,c1) at (x,y)',")@/ + ("where all quantities are known and k is between 0 and 15."); +put_get_error; +exit:end; + +@ @= +procedure do_cull; +label not_found,exit; +var @!e:pointer; {token list for a picture variable} +@!keeping:drop_code..keep_code; {modifier of |cull_op|} +@!w,@!w_in,@!w_out:integer; {culling weights} +begin w:=1; +get_x_next; var_flag:=cull_op; scan_primary; +if cur_type<>token_list then + @ +else begin e:=cur_exp; cur_type:=vacuous; keeping:=cur_mod; + if not get_pair(cull_op) then goto not_found; + while (cur_cmd=with_option)and(cur_mod=known) do + if scan_with then w:=cur_exp; + @; + find_edges_var(e); + if cur_edges<>null then + cull_edges(floor_unscaled(cur_x+unity-1),floor_unscaled(cur_y),w_out,w_in); + return; + not_found: print_err("Bad culling amounts"); +@.Bad culling amounts@> + help1("Always cull by known amounts that exclude 0."); + put_get_error; flush_token_list(e); + end; +exit:end; + +@ @= +if cur_x>cur_y then goto not_found; +if keeping=drop_code then + begin if (cur_x>0)or(cur_y<0) then goto not_found; + w_out:=w; w_in:=0; + end +else begin if (cur_x<=0)and(cur_y>=0) then goto not_found; + w_out:=0; w_in:=w; + end + +@ The \&{everyjob} command simply assigns a nonzero value to the global variable +|start_sym|. + +@= +every_job_command: begin get_symbol; start_sym:=cur_sym; get_x_next; + end; + +@ @= +@!start_sym:halfword; {a symbolic token to insert at beginning of job} + +@ @= +start_sym:=0; + +@ Finally, we have only the ``message'' commands remaining. + +@d message_code=0 +@d err_message_code=1 +@d err_help_code=2 + +@= +primitive("message",message_command,message_code);@/ +@!@:message_}{\&{message} primitive@> +primitive("errmessage",message_command,err_message_code);@/ +@!@:err_message_}{\&{errmessage} primitive@> +primitive("errhelp",message_command,err_help_code);@/ +@!@:err_help_}{\&{errhelp} primitive@> + +@ @= +message_command: if m= +message_command: do_message; + +@ @= +procedure do_message; +var @!m:message_code..err_help_code; {the type of message} +begin m:=cur_mod; get_x_next; scan_expression; +if cur_type<>string_type then + begin exp_err("Not a string"); +@.Not a string@> + help1("A message should be a known string expression."); + put_get_error; + end +else case m of + message_code:begin print_nl(""); slow_print(cur_exp); + end; + err_message_code:@; + err_help_code:@; + end; {there are no other cases} +flush_cur_exp(0); +end; + +@ The global variable |err_help| is zero when the user has most recently +given an empty help string, or if none has ever been given. + +@= +begin if err_help<>0 then delete_str_ref(err_help); +if length(cur_exp)=0 then err_help:=0 +else begin err_help:=cur_exp; add_str_ref(err_help); + end; +end + +@ If \&{errmessage} occurs often in |scroll_mode|, without user-defined +\&{errhelp}, we don't want to give a long help message each time. So we +give a verbose explanation only once. + +@= +@!long_help_seen:boolean; {has the long \&{errmessage} help been used?} + +@ @=long_help_seen:=false; + +@ @= +begin print_err(""); slow_print(cur_exp); +if err_help<>0 then use_err_help:=true +else if long_help_seen then help1("(That was another `errmessage'.)") +else begin if interaction + ("and deduce the truth by inspired guesses."); + end; +put_get_error; use_err_help:=false; +end + +@* \[45] Font metric data. +\TeX\ gets its knowledge about fonts from font metric files, also called +\.{TFM} files; the `\.T' in `\.{TFM}' stands for \TeX, +but other programs know about them too. One of \MF's duties is to +write \.{TFM} files so that the user's fonts can readily be +applied to typesetting. +@:TFM files}{\.{TFM} files@> +@^font metric files@> + +The information in a \.{TFM} file appears in a sequence of 8-bit bytes. +Since the number of bytes is always a multiple of~4, we could +also regard the file as a sequence of 32-bit words, but \MF\ uses the +byte interpretation. The format of \.{TFM} files was designed by +Lyle Ramshaw in 1980. The intent is to convey a lot of different kinds +@^Ramshaw, Lyle Harold@> +of information in a compact but useful form. + +@= +@!tfm_file:byte_file; {the font metric output goes here} +@!metric_file_name: str_number; {full name of the font metric file} + +@ The first 24 bytes (6 words) of a \.{TFM} file contain twelve 16-bit +integers that give the lengths of the various subsequent portions +of the file. These twelve integers are, in order: +$$\vbox{\halign{\hfil#&$\null=\null$#\hfil\cr +|lf|&length of the entire file, in words;\cr +|lh|&length of the header data, in words;\cr +|bc|&smallest character code in the font;\cr +|ec|&largest character code in the font;\cr +|nw|&number of words in the width table;\cr +|nh|&number of words in the height table;\cr +|nd|&number of words in the depth table;\cr +|ni|&number of words in the italic correction table;\cr +|nl|&number of words in the lig/kern table;\cr +|nk|&number of words in the kern table;\cr +|ne|&number of words in the extensible character table;\cr +|np|&number of font parameter words.\cr}}$$ +They are all nonnegative and less than $2^{15}$. We must have |bc-1<=ec<=255|, +|ne<=256|, and +$$\hbox{|lf=6+lh+(ec-bc+1)+nw+nh+nd+ni+nl+nk+ne+np|.}$$ +Note that a font may contain as many as 256 characters (if |bc=0| and |ec=255|), +and as few as 0 characters (if |bc=ec+1|). + +Incidentally, when two or more 8-bit bytes are combined to form an integer of +16 or more bits, the most significant bytes appear first in the file. +This is called BigEndian order. +@!@^BigEndian order@> + +@ The rest of the \.{TFM} file may be regarded as a sequence of ten data +arrays having the informal specification +$$\def\arr$[#1]#2${\&{array} $[#1]$ \&{of} #2} +\tabskip\centering +\halign to\displaywidth{\hfil\\{#}\tabskip=0pt&$\,:\,$\arr#\hfil + \tabskip\centering\cr +header&|[0..lh-1]@t\\{stuff}@>|\cr +char\_info&|[bc..ec]char_info_word|\cr +width&|[0..nw-1]fix_word|\cr +height&|[0..nh-1]fix_word|\cr +depth&|[0..nd-1]fix_word|\cr +italic&|[0..ni-1]fix_word|\cr +lig\_kern&|[0..nl-1]lig_kern_command|\cr +kern&|[0..nk-1]fix_word|\cr +exten&|[0..ne-1]extensible_recipe|\cr +param&|[1..np]fix_word|\cr}$$ +The most important data type used here is a |@!fix_word|, which is +a 32-bit representation of a binary fraction. A |fix_word| is a signed +quantity, with the two's complement of the entire word used to represent +negation. Of the 32 bits in a |fix_word|, exactly 12 are to the left of the +binary point; thus, the largest |fix_word| value is $2048-2^{-20}$, and +the smallest is $-2048$. We will see below, however, that all but two of +the |fix_word| values must lie between $-16$ and $+16$. + +@ The first data array is a block of header information, which contains +general facts about the font. The header must contain at least two words, +|header[0]| and |header[1]|, whose meaning is explained below. Additional +header information of use to other software routines might also be +included, and \MF\ will generate it if the \.{headerbyte} command occurs. +For example, 16 more words of header information are in use at the Xerox +Palo Alto Research Center; the first ten specify the character coding +scheme used (e.g., `\.{XEROX TEXT}' or `\.{TEX MATHSY}'), the next five +give the font family name (e.g., `\.{HELVETICA}' or `\.{CMSY}'), and the +last gives the ``face byte.'' + +\yskip\hang|header[0]| is a 32-bit check sum that \MF\ will copy into +the \.{GF} output file. This helps ensure consistency between files, +since \TeX\ records the check sums from the \.{TFM}'s it reads, and these +should match the check sums on actual fonts that are used. The actual +relation between this check sum and the rest of the \.{TFM} file is not +important; the check sum is simply an identification number with the +property that incompatible fonts almost always have distinct check sums. +@^check sum@> + +\yskip\hang|header[1]| is a |fix_word| containing the design size of the +font, in units of \TeX\ points. This number must be at least 1.0; it is +fairly arbitrary, but usually the design size is 10.0 for a ``10 point'' +font, i.e., a font that was designed to look best at a 10-point size, +whatever that really means. When a \TeX\ user asks for a font `\.{at} +$\delta$ \.{pt}', the effect is to override the design size and replace it +by $\delta$, and to multiply the $x$ and~$y$ coordinates of the points in +the font image by a factor of $\delta$ divided by the design size. {\sl +All other dimensions in the\/ \.{TFM} file are |fix_word|\kern-1pt\ +numbers in design-size units.} Thus, for example, the value of |param[6]|, +which defines the \.{em} unit, is often the |fix_word| value $2^{20}=1.0$, +since many fonts have a design size equal to one em. The other dimensions +must be less than 16 design-size units in absolute value; thus, +|header[1]| and |param[1]| are the only |fix_word| entries in the whole +\.{TFM} file whose first byte might be something besides 0 or 255. +@^design size@> + +@ Next comes the |char_info| array, which contains one |@!char_info_word| +per character. Each word in this part of the file contains six fields +packed into four bytes as follows. + +\yskip\hang first byte: |@!width_index| (8 bits)\par +\hang second byte: |@!height_index| (4 bits) times 16, plus |@!depth_index| + (4~bits)\par +\hang third byte: |@!italic_index| (6 bits) times 4, plus |@!tag| + (2~bits)\par +\hang fourth byte: |@!remainder| (8 bits)\par +\yskip\noindent +The actual width of a character is \\{width}|[width_index]|, in design-size +units; this is a device for compressing information, since many characters +have the same width. Since it is quite common for many characters +to have the same height, depth, or italic correction, the \.{TFM} format +imposes a limit of 16 different heights, 16 different depths, and +64 different italic corrections. + +Incidentally, the relation $\\{width}[0]=\\{height}[0]=\\{depth}[0]= +\\{italic}[0]=0$ should always hold, so that an index of zero implies a +value of zero. The |width_index| should never be zero unless the +character does not exist in the font, since a character is valid if and +only if it lies between |bc| and |ec| and has a nonzero |width_index|. + +@ The |tag| field in a |char_info_word| has four values that explain how to +interpret the |remainder| field. + +\def\hangg#1 {\hang\hbox{#1 }} +\yskip\hangg|tag=0| (|no_tag|) means that |remainder| is unused.\par +\hangg|tag=1| (|lig_tag|) means that this character has a ligature/kerning +program starting at location |remainder| in the |lig_kern| array.\par +\hangg|tag=2| (|list_tag|) means that this character is part of a chain of +characters of ascending sizes, and not the largest in the chain. The +|remainder| field gives the character code of the next larger character.\par +\hangg|tag=3| (|ext_tag|) means that this character code represents an +extensible character, i.e., a character that is built up of smaller pieces +so that it can be made arbitrarily large. The pieces are specified in +|@!exten[remainder]|.\par +\yskip\noindent +Characters with |tag=2| and |tag=3| are treated as characters with |tag=0| +unless they are used in special circumstances in math formulas. For example, +\TeX's \.{\\sum} operation looks for a |list_tag|, and the \.{\\left} +operation looks for both |list_tag| and |ext_tag|. + +@d no_tag=0 {vanilla character} +@d lig_tag=1 {character has a ligature/kerning program} +@d list_tag=2 {character has a successor in a charlist} +@d ext_tag=3 {character is extensible} + +@ The |lig_kern| array contains instructions in a simple programming language +that explains what to do for special letter pairs. Each word in this array is a +|@!lig_kern_command| of four bytes. + +\yskip\hang first byte: |skip_byte|, indicates that this is the final program + step if the byte is 128 or more, otherwise the next step is obtained by + skipping this number of intervening steps.\par +\hang second byte: |next_char|, ``if |next_char| follows the current character, + then perform the operation and stop, otherwise continue.''\par +\hang third byte: |op_byte|, indicates a ligature step if less than~128, + a kern step otherwise.\par +\hang fourth byte: |remainder|.\par +\yskip\noindent +In a kern step, an +additional space equal to |kern[256*(op_byte-128)+remainder]| is inserted +between the current character and |next_char|. This amount is +often negative, so that the characters are brought closer together +by kerning; but it might be positive. + +There are eight kinds of ligature steps, having |op_byte| codes $4a+2b+c$ where +$0\le a\le b+c$ and $0\le b,c\le1$. The character whose code is +|remainder| is inserted between the current character and |next_char|; +then the current character is deleted if $b=0$, and |next_char| is +deleted if $c=0$; then we pass over $a$~characters to reach the next +current character (which may have a ligature/kerning program of its own). + +If the very first instruction of the |lig_kern| array has |skip_byte=255|, +the |next_char| byte is the so-called right boundary character of this font; +the value of |next_char| need not lie between |bc| and~|ec|. +If the very last instruction of the |lig_kern| array has |skip_byte=255|, +there is a special ligature/kerning program for a left boundary character, +beginning at location |256*op_byte+remainder|. +The interpretation is that \TeX\ puts implicit boundary characters +before and after each consecutive string of characters from the same font. +These implicit characters do not appear in the output, but they can affect +ligatures and kerning. + +If the very first instruction of a character's |lig_kern| program has +|skip_byte>128|, the program actually begins in location +|256*op_byte+remainder|. This feature allows access to large |lig_kern| +arrays, because the first instruction must otherwise +appear in a location |<=255|. + +Any instruction with |skip_byte>128| in the |lig_kern| array must satisfy +the condition +$$\hbox{|256*op_byte+remainder=0|, unless $M$ is absent; +in the latter case we can have $TR^kB$ for both even and odd values of~|k|. +The width of the extensible character is the width of $R$; and the +height-plus-depth is the sum of the individual height-plus-depths of the +components used, since the pieces are butted together in a vertical list. + +@d ext_top(#)==exten[#].b0 {|top| piece in a recipe} +@d ext_mid(#)==exten[#].b1 {|mid| piece in a recipe} +@d ext_bot(#)==exten[#].b2 {|bot| piece in a recipe} +@d ext_rep(#)==exten[#].b3 {|rep| piece in a recipe} + +@ The final portion of a \.{TFM} file is the |param| array, which is another +sequence of |fix_word| values. + +\yskip\hang|param[1]=slant| is the amount of italic slant, which is used +to help position accents. For example, |slant=.25| means that when you go +up one unit, you also go .25 units to the right. The |slant| is a pure +number; it is the only |fix_word| other than the design size itself that is +not scaled by the design size. +@^design size@> + +\hang|param[2]=space| is the normal spacing between words in text. +Note that character @'40 in the font need not have anything to do with +blank spaces. + +\hang|param[3]=space_stretch| is the amount of glue stretching between words. + +\hang|param[4]=space_shrink| is the amount of glue shrinking between words. + +\hang|param[5]=x_height| is the size of one ex in the font; it is also +the height of letters for which accents don't have to be raised or lowered. + +\hang|param[6]=quad| is the size of one em in the font. + +\hang|param[7]=extra_space| is the amount added to |param[2]| at the +ends of sentences. + +\yskip\noindent +If fewer than seven parameters are present, \TeX\ sets the missing parameters +to zero. + +@d slant_code=1 +@d space_code=2 +@d space_stretch_code=3 +@d space_shrink_code=4 +@d x_height_code=5 +@d quad_code=6 +@d extra_space_code=7 + +@ So that is what \.{TFM} files hold. One of \MF's duties is to output such +information, and it does this all at once at the end of a job. +In order to prepare for such frenetic activity, it squirrels away the +necessary facts in various arrays as information becomes available. + +Character dimensions (\&{charwd}, \&{charht}, \&{chardp}, and \&{charic}) +are stored respectively in |tfm_width|, |tfm_height|, |tfm_depth|, and +|tfm_ital_corr|. Other information about a character (e.g., about +its ligatures or successors) is accessible via the |char_tag| and +|char_remainder| arrays. Other information about the font as a whole +is kept in additional arrays called |header_byte|, |lig_kern|, +|kern|, |exten|, and |param|. + +@d undefined_label==lig_table_size {an undefined local label} + +@= +@!bc,@!ec:eight_bits; {smallest and largest character codes shipped out} +@!tfm_width:array[eight_bits] of scaled; {\&{charwd} values} +@!tfm_height:array[eight_bits] of scaled; {\&{charht} values} +@!tfm_depth:array[eight_bits] of scaled; {\&{chardp} values} +@!tfm_ital_corr:array[eight_bits] of scaled; {\&{charic} values} +@!char_exists:array[eight_bits] of boolean; {has this code been shipped out?} +@!char_tag:array[eight_bits] of no_tag..ext_tag; {|remainder| category} +@!char_remainder:array[eight_bits] of 0..lig_table_size; {the |remainder| byte} +@!header_byte:array[1..header_size] of -1..255; + {bytes of the \.{TFM} header, or $-1$ if unset} +@!lig_kern:array[0..lig_table_size] of four_quarters; {the ligature/kern table} +@!nl:0..32767-256; {the number of ligature/kern steps so far} +@!kern:array[0..max_kerns] of scaled; {distinct kerning amounts} +@!nk:0..max_kerns; {the number of distinct kerns so far} +@!exten:array[eight_bits] of four_quarters; {extensible character recipes} +@!ne:0..256; {the number of extensible characters so far} +@!param:array[1..max_font_dimen] of scaled; {\&{fontinfo} parameters} +@!np:0..max_font_dimen; {the largest \&{fontinfo} parameter specified so far} +@!nw,@!nh,@!nd,@!ni:0..256; {sizes of \.{TFM} subtables} +@!skip_table:array[eight_bits] of 0..lig_table_size; {local label status} +@!lk_started:boolean; {has there been a lig/kern step in this command yet?} +@!bchar:integer; {right boundary character} +@!bch_label:0..lig_table_size; {left boundary starting location} +@!ll,@!lll:0..lig_table_size; {registers used for lig/kern processing} +@!label_loc:array[0..256] of -1..lig_table_size; {lig/kern starting addresses} +@!label_char:array[1..256] of eight_bits; {characters for |label_loc|} +@!label_ptr:0..256; {highest position occupied in |label_loc|} + +@ @= +for k:=0 to 255 do + begin tfm_width[k]:=0; tfm_height[k]:=0; tfm_depth[k]:=0; tfm_ital_corr[k]:=0; + char_exists[k]:=false; char_tag[k]:=no_tag; char_remainder[k]:=0; + skip_table[k]:=undefined_label; + end; +for k:=1 to header_size do header_byte[k]:=-1; +bc:=255; ec:=0; nl:=0; nk:=0; ne:=0; np:=0;@/ +internal[boundary_char]:=-unity; +bch_label:=undefined_label;@/ +label_loc[0]:=-1; label_ptr:=0; + +@ @= +function tfm_check(@!m:small_number):scaled; +begin if abs(internal[m])>=fraction_half then + begin print_err("Enormous "); print(int_name[m]); +@.Enormous charwd...@> +@.Enormous chardp...@> +@.Enormous charht...@> +@.Enormous charic...@> +@.Enormous designsize...@> + print(" has been reduced"); + help1("Font metric dimensions must be less than 2048pt."); + put_get_error; + if internal[m]>0 then tfm_check:=fraction_half-1 + else tfm_check:=1-fraction_half; + end +else tfm_check:=internal[m]; +end; + +@ @= +if cec then ec:=c; +char_exists[c]:=true; +gf_dx[c]:=internal[char_dx]; gf_dy[c]:=internal[char_dy]; +tfm_width[c]:=tfm_check(char_wd); +tfm_height[c]:=tfm_check(char_ht); +tfm_depth[c]:=tfm_check(char_dp); +tfm_ital_corr[c]:=tfm_check(char_ic) + +@ Now let's consider \MF's special \.{TFM}-oriented commands. + +@= +tfm_command: do_tfm_command; + +@ @d char_list_code=0 +@d lig_table_code=1 +@d extensible_code=2 +@d header_byte_code=3 +@d font_dimen_code=4 + +@= +primitive("charlist",tfm_command,char_list_code);@/ +@!@:char_list_}{\&{charlist} primitive@> +primitive("ligtable",tfm_command,lig_table_code);@/ +@!@:lig_table_}{\&{ligtable} primitive@> +primitive("extensible",tfm_command,extensible_code);@/ +@!@:extensible_}{\&{extensible} primitive@> +primitive("headerbyte",tfm_command,header_byte_code);@/ +@!@:header_byte_}{\&{headerbyte} primitive@> +primitive("fontdimen",tfm_command,font_dimen_code);@/ +@!@:font_dimen_}{\&{fontdimen} primitive@> + +@ @= +tfm_command: case m of + char_list_code:print("charlist"); + lig_table_code:print("ligtable"); + extensible_code:print("extensible"); + header_byte_code:print("headerbyte"); + othercases print("fontdimen") + endcases; + +@ @= +function get_code:eight_bits; {scans a character code value} +label found; +var @!c:integer; {the code value found} +begin get_x_next; scan_expression; +if cur_type=known then + begin c:=round_unscaled(cur_exp); + if c>=0 then if c<256 then goto found; + end +else if cur_type=string_type then if length(cur_exp)=1 then + begin c:=so(str_pool[str_start[cur_exp]]); goto found; + end; +exp_err("Invalid code has been replaced by 0"); +@.Invalid code...@> +help2("I was looking for a number between 0 and 255, or for a")@/ + ("string of length 1. Didn't find it; will use 0 instead."); +put_get_flush_error(0); c:=0; +found: get_code:=c; +end; + +@ @= +procedure set_tag(@!c:halfword;@!t:small_number;@!r:halfword); +begin if char_tag[c]=no_tag then + begin char_tag[c]:=t; char_remainder[c]:=r; + if t=lig_tag then + begin incr(label_ptr); label_loc[label_ptr]:=r; label_char[label_ptr]:=c; + end; + end +else @; +end; + +@ @= +begin print_err("Character "); +if (c>" ")and(c<127) then print(c) +else if c=256 then print("||") +else begin print("code "); print_int(c); + end; +print(" is already "); +@.Character c is already...@> +case char_tag[c] of +lig_tag: print("in a ligtable"); +list_tag: print("in a charlist"); +ext_tag: print("extensible"); +end; {there are no other cases} +help2("It's not legal to label a character more than once.")@/ + ("So I'll not change anything just now."); +put_get_error; end + +@ @= +procedure do_tfm_command; +label continue,done; +var @!c,@!cc:0..256; {character codes} +@!k:0..max_kerns; {index into the |kern| array} +@!j:integer; {index into |header_byte| or |param|} +begin case cur_mod of +char_list_code: begin c:=get_code; + {we will store a list of character successors} + while cur_cmd=colon do + begin cc:=get_code; set_tag(c,list_tag,cc); c:=cc; + end; + end; +lig_table_code: @; +extensible_code: @; +header_byte_code, font_dimen_code: begin c:=cur_mod; get_x_next; + scan_expression; + if (cur_type<>known)or(cur_exp + help2("I was looking for a known, positive number.")@/ + ("For safety's sake I'll ignore the present command."); + put_get_error; + end + else begin j:=round_unscaled(cur_exp); + if cur_cmd<>colon then + begin missing_err(":"); +@.Missing `:'@> + help1("A colon should follow a headerbyte or fontinfo location."); + back_error; + end; + if c=header_byte_code then @ + else @; + end; + end; +end; {there are no other cases} +end; + +@ @= +begin lk_started:=false; +continue: get_x_next; +if(cur_cmd=skip_to)and lk_started then + @; +if cur_cmd=bchar_label then + begin c:=256; cur_cmd:=colon;@+end +else begin back_input; c:=get_code;@+end; +if(cur_cmd=colon)or(cur_cmd=double_colon)then + @; +if cur_cmd=lig_kern_token then @ +else begin print_err("Illegal ligtable step"); +@.Illegal ligtable step@> + help1("I was looking for `=:' or `kern' here."); + back_error; next_char(nl):=qi(0); op_byte(nl):=qi(0); rem_byte(nl):=qi(0);@/ + skip_byte(nl):=stop_flag+1; {this specifies an unconditional stop} + end; +if nl=lig_table_size then overflow("ligtable size",lig_table_size); +@:METAFONT capacity exceeded ligtable size}{\quad ligtable size@> +incr(nl); +if cur_cmd=comma then goto continue; +if skip_byte(nl-1)= +primitive("=:",lig_kern_token,0); +@!@:=:_}{\.{=:} primitive@> +primitive("=:|",lig_kern_token,1); +@!@:=:/_}{\.{=:\char'174} primitive@> +primitive("=:|>",lig_kern_token,5); +@!@:=:/>_}{\.{=:\char'174>} primitive@> +primitive("|=:",lig_kern_token,2); +@!@:=:/_}{\.{\char'174=:} primitive@> +primitive("|=:>",lig_kern_token,6); +@!@:=:/>_}{\.{\char'174=:>} primitive@> +primitive("|=:|",lig_kern_token,3); +@!@:=:/_}{\.{\char'174=:\char'174} primitive@> +primitive("|=:|>",lig_kern_token,7); +@!@:=:/>_}{\.{\char'174=:\char'174>} primitive@> +primitive("|=:|>>",lig_kern_token,11); +@!@:=:/>_}{\.{\char'174=:\char'174>>} primitive@> +primitive("kern",lig_kern_token,128); +@!@:kern_}{\&{kern} primitive@> + +@ @= +lig_kern_token: case m of +0:print("=:"); +1:print("=:|"); +2:print("|=:"); +3:print("|=:|"); +5:print("=:|>"); +6:print("|=:>"); +7:print("|=:|>"); +11:print("|=:|>>"); +othercases print("kern") +endcases; + +@ Local labels are implemented by maintaining the |skip_table| array, +where |skip_table[c]| is either |undefined_label| or the address of the +most recent lig/kern instruction that skips to local label~|c|. In the +latter case, the |skip_byte| in that instruction will (temporarily) +be zero if there were no prior skips to this label, or it will be the +distance to the prior skip. + +We may need to cancel skips that span more than 127 lig/kern steps. + +@d cancel_skips(#)==ll:=#; + repeat lll:=qo(skip_byte(ll)); skip_byte(ll):=stop_flag; ll:=ll-lll; + until lll=0 +@d skip_error(#)==begin print_err("Too far to skip"); +@.Too far to skip@> + help1("At most 127 lig/kern steps can separate skipto1 from 1::."); + error; cancel_skips(#); + end + +@= +begin c:=get_code; +if nl-skip_table[c]>128 then + begin skip_error(skip_table[c]); skip_table[c]:=undefined_label; + end; +if skip_table[c]=undefined_label then skip_byte(nl-1):=qi(0) +else skip_byte(nl-1):=qi(nl-skip_table[c]-1); +skip_table[c]:=nl-1; goto done; +end + +@ @= +begin if cur_cmd=colon then + if c=256 then bch_label:=nl + else set_tag(c,lig_tag,nl) +else if skip_table[c]128 then + begin skip_error(ll); goto continue; + end; + skip_byte(ll):=qi(nl-ll-1); ll:=ll-lll; + until lll=0; + end; +goto continue; +end + +@ @= +begin next_char(nl):=qi(c); skip_byte(nl):=qi(0); +if cur_mod<128 then {ligature op} + begin op_byte(nl):=qi(cur_mod); rem_byte(nl):=qi(get_code); + end +else begin get_x_next; scan_expression; + if cur_type<>known then + begin exp_err("Improper kern"); +@.Improper kern@> + help2("The amount of kern should be a known numeric value.")@/ + ("I'm zeroing this one. Proceed, with fingers crossed."); + put_get_flush_error(0); + end; + kern[nk]:=cur_exp; + k:=0;@+while kern[k]<>cur_exp do incr(k); + if k=nk then + begin if nk=max_kerns then overflow("kern",max_kerns); +@:METAFONT capacity exceeded kern}{\quad kern@> + incr(nk); + end; + op_byte(nl):=kern_flag+(k div 256); + rem_byte(nl):=qi((k mod 256)); + end; +lk_started:=true; +end + +@ @d missing_extensible_punctuation(#)== + begin missing_err(#); +@.Missing `\char`\#'@> + help1("I'm processing `extensible c: t,m,b,r'."); back_error; + end + +@= +begin if ne=256 then overflow("extensible",256); +@:METAFONT capacity exceeded extensible}{\quad extensible@> +c:=get_code; set_tag(c,ext_tag,ne); +if cur_cmd<>colon then missing_extensible_punctuation(":"); +ext_top(ne):=qi(get_code); +if cur_cmd<>comma then missing_extensible_punctuation(","); +ext_mid(ne):=qi(get_code); +if cur_cmd<>comma then missing_extensible_punctuation(","); +ext_bot(ne):=qi(get_code); +if cur_cmd<>comma then missing_extensible_punctuation(","); +ext_rep(ne):=qi(get_code); +incr(ne); +end + +@ @= +repeat if j>header_size then overflow("headerbyte",header_size); +@:METAFONT capacity exceeded headerbyte}{\quad headerbyte@> +header_byte[j]:=get_code; incr(j); +until cur_cmd<>comma + +@ @= +repeat if j>max_font_dimen then overflow("fontdimen",max_font_dimen); +@:METAFONT capacity exceeded fontdimen}{\quad fontdimen@> +while j>np do + begin incr(np); param[np]:=0; + end; +get_x_next; scan_expression; +if cur_type<>known then + begin exp_err("Improper font parameter"); +@.Improper font parameter@> + help1("I'm zeroing this one. Proceed, with fingers crossed."); + put_get_flush_error(0); + end; +param[j]:=cur_exp; incr(j); +until cur_cmd<>comma + +@ OK: We've stored all the data that is needed for the \.{TFM} file. +All that remains is to output it in the correct format. + +An interesting problem needs to be solved in this connection, because +the \.{TFM} format allows at most 256~widths, 16~heights, 16~depths, +and 64~italic corrections. If the data has more distinct values than +this, we want to meet the necessary restrictions by perturbing the +given values as little as possible. + +\MF\ solves this problem in two steps. First the values of a given +kind (widths, heights, depths, or italic corrections) are sorted; +then the list of sorted values is perturbed, if necessary. + +The sorting operation is facilitated by having a special node of +essentially infinite |value| at the end of the current list. + +@= +value(inf_val):=fraction_four; + +@ Straight linear insertion is good enough for sorting, since the lists +are usually not terribly long. As we work on the data, the current list +will start at |link(temp_head)| and end at |inf_val|; the nodes in this +list will be in increasing order of their |value| fields. + +Given such a list, the |sort_in| function takes a value and returns a pointer +to where that value can be found in the list. The value is inserted in +the proper place, if necessary. + +At the time we need to do these operations, most of \MF's work has been +completed, so we will have plenty of memory to play with. The value nodes +that are allocated for sorting will never be returned to free storage. + +@d clear_the_list==link(temp_head):=inf_val + +@p function sort_in(@!v:scaled):pointer; +label found; +var @!p,@!q,@!r:pointer; {list manipulation registers} +begin p:=temp_head; +loop@+ begin q:=link(p); + if v<=value(q) then goto found; + p:=q; + end; +found: if vd$ +such that the covering found by this algorithm would be different. + +In particular, |min_cover(0)| returns the number of distinct values in the +current list and sets |perturbation| to the minimum distance between +adjacent values. + +@p function min_cover(@!d:scaled):integer; +var @!p:pointer; {runs through the current list} +@!l:scaled; {the least element covered by the current interval} +@!m:integer; {lower bound on the size of the minimum cover} +begin m:=0; p:=link(temp_head); perturbation:=el_gordo; +while p<>inf_val do + begin incr(m); l:=value(p); + repeat p:=link(p); + until value(p)>l+d; + if value(p)-l= +@!perturbation:scaled; {quantity related to \.{TFM} rounding} +@!excess:integer; {the list is this much too long} + +@ The smallest |d| such that a given list can be covered with |m| intervals +is determined by the |threshold| routine, which is sort of an inverse +to |min_cover|. The idea is to increase the interval size rapidly until +finding the range, then to go sequentially until the exact borderline has +been discovered. + +@p function threshold(@!m:integer):scaled; +var @!d:scaled; {lower bound on the smallest interval size} +begin excess:=min_cover(0)-m; +if excess<=0 then threshold:=0 +else begin repeat d:=perturbation; + until min_cover(d+d)<=m; + while min_cover(d)>m do d:=perturbation; + threshold:=d; + end; +end; + +@ The |skimp| procedure reduces the current list to at most |m| entries, +by changing values if necessary. It also sets |info(p):=k| if |value(p)| +is the |k|th distinct value on the resulting list, and it sets +|perturbation| to the maximum amount by which a |value| field has +been changed. The size of the resulting list is returned as the +value of |skimp|. + +@p function skimp(@!m:integer):integer; +var @!d:scaled; {the size of intervals being coalesced} +@!p,@!q,@!r:pointer; {list manipulation registers} +@!l:scaled; {the least value in the current interval} +@!v:scaled; {a compromise value} +begin d:=threshold(m); perturbation:=0; +q:=temp_head; m:=0; p:=link(temp_head); +while p<>inf_val do + begin incr(m); l:=value(p); info(p):=m; + if value(link(p))<=l+d then + @; + q:=p; p:=link(p); + end; +skimp:=m; +end; + +@ @= +begin repeat p:=link(p); info(p):=m; +decr(excess);@+if excess=0 then d:=0; +until value(link(p))>l+d; +v:=l+half(value(p)-l); +if value(p)-v>perturbation then perturbation:=value(p)-v; +r:=q; +repeat r:=link(r); value(r):=v; +until r=p; +link(q):=p; {remove duplicate values from the current list} +end + +@ A warning message is issued whenever something is perturbed by +more than 1/16\thinspace pt. + +@p procedure tfm_warning(@!m:small_number); +begin print_nl("(some "); print(int_name[m]); +@.some charwds...@> +@.some chardps...@> +@.some charhts...@> +@.some charics...@> +print(" values had to be adjusted by as much as "); +print_scaled(perturbation); print("pt)"); +end; + +@ Here's an example of how we use these routines. +The width data needs to be perturbed only if there are 256 distinct +widths, but \MF\ must check for this case even though it is +highly unusual. + +An integer variable |k| will be defined when we use this code. +The |dimen_head| array will contain pointers to the sorted +lists of dimensions. + +@= +clear_the_list; +for k:=bc to ec do if char_exists[k] then + tfm_width[k]:=sort_in(tfm_width[k]); +nw:=skimp(255)+1; dimen_head[1]:=link(temp_head); +if perturbation>=@'10000 then tfm_warning(char_wd) + +@ @= +@!dimen_head:array[1..4] of pointer; {lists of \.{TFM} dimensions} + +@ Heights, depths, and italic corrections are different from widths +not only because their list length is more severely restricted, but +also because zero values do not need to be put into the lists. + +@= +clear_the_list; +for k:=bc to ec do if char_exists[k] then + if tfm_height[k]=0 then tfm_height[k]:=zero_val + else tfm_height[k]:=sort_in(tfm_height[k]); +nh:=skimp(15)+1; dimen_head[2]:=link(temp_head); +if perturbation>=@'10000 then tfm_warning(char_ht); +clear_the_list; +for k:=bc to ec do if char_exists[k] then + if tfm_depth[k]=0 then tfm_depth[k]:=zero_val + else tfm_depth[k]:=sort_in(tfm_depth[k]); +nd:=skimp(15)+1; dimen_head[3]:=link(temp_head); +if perturbation>=@'10000 then tfm_warning(char_dp); +clear_the_list; +for k:=bc to ec do if char_exists[k] then + if tfm_ital_corr[k]=0 then tfm_ital_corr[k]:=zero_val + else tfm_ital_corr[k]:=sort_in(tfm_ital_corr[k]); +ni:=skimp(63)+1; dimen_head[4]:=link(temp_head); +if perturbation>=@'10000 then tfm_warning(char_ic) + +@ @= +value(zero_val):=0; info(zero_val):=0; + +@ Bytes 5--8 of the header are set to the design size, unless the user has +some crazy reason for specifying them differently. +@^design size@> + +Error messages are not allowed at the time this procedure is called, +so a warning is printed instead. + +The value of |max_tfm_dimen| is calculated so that +$$\hbox{|make_scaled(16*max_tfm_dimen,internal[design_size])|} + < \\{three\_bytes}.$$ + +@d three_bytes==@'100000000 {$2^{24}$} + +@p procedure fix_design_size; +var @!d:scaled; {the design size} +begin d:=internal[design_size]; +if (d=fraction_half) then + begin if d<>0 then + print_nl("(illegal design size has been changed to 128pt)"); +@.illegal design size...@> + d:=@'40000000; internal[design_size]:=d; + end; +if header_byte[5]<0 then if header_byte[6]<0 then + if header_byte[7]<0 then if header_byte[8]<0 then + begin header_byte[5]:=d div @'4000000; + header_byte[6]:=(d div 4096) mod 256; + header_byte[7]:=(d div 16) mod 256; + header_byte[8]:=(d mod 16)*16; + end; +max_tfm_dimen:=16*internal[design_size]-1-internal[design_size] div @'10000000; +if max_tfm_dimen>=fraction_half then max_tfm_dimen:=fraction_half-1; +end; + +@ The |dimen_out| procedure computes a |fix_word| relative to the +design size. If the data was out of range, it is corrected and the +global variable |tfm_changed| is increased by~one. + +@p function dimen_out(@!x:scaled):integer; +begin if abs(x)>max_tfm_dimen then + begin incr(tfm_changed); + if x>0 then x:=max_tfm_dimen@+else x:=-max_tfm_dimen; + end; +x:=make_scaled(x*16,internal[design_size]); +dimen_out:=x; +end; + +@ @= +@!max_tfm_dimen:scaled; {bound on widths, heights, kerns, etc.} +@!tfm_changed:integer; {the number of data entries that were out of bounds} + +@ If the user has not specified any of the first four header bytes, +the |fix_check_sum| procedure replaces them by a ``check sum'' computed +from the |tfm_width| data relative to the design size. +@^check sum@> + +@p procedure fix_check_sum; +label exit; +var @!k:eight_bits; {runs through character codes} +@!b1,@!b2,@!b3,@!b4:eight_bits; {bytes of the check sum} +@!x:integer; {hash value used in check sum computation} +begin if header_byte[1]<0 then if header_byte[2]<0 then + if header_byte[3]<0 then if header_byte[4]<0 then + begin @; + header_byte[1]:=b1; header_byte[2]:=b2; + header_byte[3]:=b3; header_byte[4]:=b4; return; + end; +for k:=1 to 4 do if header_byte[k]<0 then header_byte[k]:=0; +exit:end; + +@ @= +b1:=bc; b2:=ec; b3:=bc; b4:=ec; tfm_changed:=0; +for k:=bc to ec do if char_exists[k] then + begin x:=dimen_out(value(tfm_width[k]))+(k+4)*@'20000000; {this is positive} + b1:=(b1+b1+x) mod 255; + b2:=(b2+b2+x) mod 253; + b3:=(b3+b3+x) mod 251; + b4:=(b4+b4+x) mod 247; + end + +@ Finally we're ready to actually write the \.{TFM} information. +Here are some utility routines for this purpose. + +@d tfm_out(#)==write(tfm_file,#) {output one byte to |tfm_file|} + +@p procedure tfm_two(@!x:integer); {output two bytes to |tfm_file|} +begin tfm_out(x div 256); tfm_out(x mod 256); +end; +@# +procedure tfm_four(@!x:integer); {output four bytes to |tfm_file|} +begin if x>=0 then tfm_out(x div three_bytes) +else begin x:=x+@'10000000000; {use two's complement for negative values} + x:=x+@'10000000000; + tfm_out((x div three_bytes) + 128); + end; +x:=x mod three_bytes; tfm_out(x div unity); +x:=x mod unity; tfm_out(x div @'400); +tfm_out(x mod @'400); +end; +@# +procedure tfm_qqqq(@!x:four_quarters); {output four quarterwords to |tfm_file|} +begin tfm_out(qo(x.b0)); tfm_out(qo(x.b1)); tfm_out(qo(x.b2)); +tfm_out(qo(x.b3)); +end; + +@ @= +if job_name=0 then open_log_file; +pack_job_name(".tfm"); +while not b_open_out(tfm_file) do + prompt_file_name("file name for font metrics",".tfm"); +metric_file_name:=b_make_name_string(tfm_file); +@; +@; +@; +@; +@!stat if internal[tracing_stats]>0 then + @;@;@+tats@/ +print_nl("Font metrics written on "); slow_print(metric_file_name); +print_char("."); +@.Font metrics written...@> +b_close(tfm_file) + +@ Integer variables |lh|, |k|, and |lk_offset| will be defined when we use +this code. + +@= +k:=header_size; +while header_byte[k]<0 do decr(k); +lh:=(k+3) div 4; {this is the number of header words} +if bc>ec then bc:=1; {if there are no characters, |ec=0| and |bc=1|} +@; +tfm_two(6+lh+(ec-bc+1)+nw+nh+nd+ni+nl+lk_offset+nk+ne+np); + {this is the total number of file words that will be output} +tfm_two(lh); tfm_two(bc); tfm_two(ec); tfm_two(nw); tfm_two(nh); +tfm_two(nd); tfm_two(ni); tfm_two(nl+lk_offset); tfm_two(nk); tfm_two(ne); +tfm_two(np); +for k:=1 to 4*lh do + begin if header_byte[k]<0 then header_byte[k]:=0; + tfm_out(header_byte[k]); + end + +@ @= +for k:=bc to ec do + if not char_exists[k] then tfm_four(0) + else begin tfm_out(info(tfm_width[k])); {the width index} + tfm_out((info(tfm_height[k]))*16+info(tfm_depth[k])); + tfm_out((info(tfm_ital_corr[k]))*4+char_tag[k]); + tfm_out(char_remainder[k]); + end; +tfm_changed:=0; +for k:=1 to 4 do + begin tfm_four(0); p:=dimen_head[k]; + while p<>inf_val do + begin tfm_four(dimen_out(value(p))); p:=link(p); + end; + end + +@ We need to output special instructions at the beginning of the +|lig_kern| array in order to specify the right boundary character +and/or to handle starting addresses that exceed 255. The |label_loc| +and |label_char| arrays have been set up to record all the +starting addresses; we have $-1=|label_loc|[0]<|label_loc|[1]\le\cdots +\le|label_loc|[|label_ptr]|$. + +@= +bchar:=round_unscaled(internal[boundary_char]); +if(bchar<0)or(bchar>255)then + begin bchar:=-1; lk_started:=false; lk_offset:=0;@+end +else begin lk_started:=true; lk_offset:=1;@+end; +@; +if bch_label= +k:=label_ptr; {pointer to the largest unallocated label} +if label_loc[k]+lk_offset>255 then + begin lk_offset:=0; lk_started:=false; {location 0 can do double duty} + repeat char_remainder[label_char[k]]:=lk_offset; + while label_loc[k-1]=label_loc[k] do + begin decr(k); char_remainder[label_char[k]]:=lk_offset; + end; + incr(lk_offset); decr(k); + until lk_offset+label_loc[k]<256; + {N.B.: |lk_offset=256| satisfies this when |k=0|} + end; +if lk_offset>0 then + while k>0 do + begin char_remainder[label_char[k]] + :=char_remainder[label_char[k]]+lk_offset; + decr(k); + end + +@ @= +for k:=0 to 255 do if skip_table[k] + cancel_skips(skip_table[k]); + end; +if lk_started then {|lk_offset=1| for the special |bchar|} + begin tfm_out(255); tfm_out(bchar); tfm_two(0); + end +else for k:=1 to lk_offset do {output the redirection specs} + begin ll:=label_loc[label_ptr]; + if bchar<0 then + begin tfm_out(254); tfm_out(0); + end + else begin tfm_out(255); tfm_out(bchar); + end; + tfm_two(ll+lk_offset); + repeat decr(label_ptr); + until label_loc[label_ptr]= +for k:=0 to ne-1 do tfm_qqqq(exten[k]); +for k:=1 to np do + if k=1 then + if abs(param[1])0 then tfm_four(el_gordo) + else tfm_four(-el_gordo); + end + else tfm_four(dimen_out(param[k])); +if tfm_changed>0 then + begin if tfm_changed=1 then print_nl("(a font metric dimension") +@.a font metric dimension...@> + else begin print_nl("("); print_int(tfm_changed); +@.font metric dimensions...@> + print(" font metric dimensions"); + end; + print(" had to be decreased)"); + end + +@ @= +begin wlog_ln(' '); +if bch_label|. +\MF\ uses this when sending a \&{special} string whose length exceeds~255. + +\yskip\hang|@!xxx4| 242 |k[4]| |x[k]|. Like |xxx1|, but |k| can be +ridiculously large; |k| mustn't be negative. + +\yskip\hang|yyy| 243 |y[4]|. This command is undefined in general; +it functions as a 5-byte |no_op| unless special \.{GF}-reading programs +are being used. \MF\ puts |scaled| numbers into |yyy|'s, as a +result of \&{numspecial} commands; the intent is to provide numeric +parameters to \\{xxx} commands that immediately precede. + +\yskip\hang|@!no_op| 244. No operation, do nothing. Any number of |no_op|'s +may occur between \.{GF} commands, but a |no_op| cannot be inserted between +a command and its parameters or between two parameters. + +\yskip\hang|char_loc| 245 |c[1]| |dx[4]| |dy[4]| |w[4]| |p[4]|. +This command will appear only in the postamble, which will be explained shortly. + +\yskip\hang|@!char_loc0| 246 |c[1]| |@!dm[1]| |w[4]| |p[4]|. +Same as |char_loc|, except that |dy| is assumed to be zero, and the value +of~|dx| is taken to be |65536*dm|, where |0<=dm<256|. + +\yskip\hang|pre| 247 |i[1]| |k[1]| |x[k]|. +Beginning of the preamble; this must come at the very beginning of the +file. Parameter |i| is an identifying number for \.{GF} format, currently +131. The other information is merely commentary; it is not given +special interpretation like \\{xxx} commands are. (Note that \\{xxx} +commands may immediately follow the preamble, before the first |boc|.) + +\yskip\hang|post| 248. Beginning of the postamble, see below. + +\yskip\hang|post_post| 249. Ending of the postamble, see below. + +\yskip\noindent Commands 250--255 are undefined at the present time. + +@d gf_id_byte=131 {identifies the kind of \.{GF} files described here} + +@ \MF\ refers to the following opcodes explicitly. + +@d paint_0=0 {beginning of the \\{paint} commands} +@d paint1=64 {move right a given number of columns, then + black${}\swap{}$white} +@d boc=67 {beginning of a character} +@d boc1=68 {short form of |boc|} +@d eoc=69 {end of a character} +@d skip0=70 {skip no blank rows} +@d skip1=71 {skip over blank rows} +@d new_row_0=74 {move down one row and then right} +@d max_new_row=164 {the largest \\{new\_row} command is |new_row_164|} +@d xxx1=239 {for \&{special} strings} +@d xxx3=241 {for long \&{special} strings} +@d yyy=243 {for \&{numspecial} numbers} +@d char_loc=245 {character locators in the postamble} +@d pre=247 {preamble} +@d post=248 {postamble beginning} +@d post_post=249 {postamble ending} + +@ The last character in a \.{GF} file is followed by `|post|'; this command +introduces the postamble, which summarizes important facts that \MF\ has +accumulated. The postamble has the form +$$\vbox{\halign{\hbox{#\hfil}\cr + |post| |p[4]| |@!ds[4]| |@!cs[4]| |@!hppp[4]| |@!vppp[4]| + |@!min_m[4]| |@!max_m[4]| |@!min_n[4]| |@!max_n[4]|\cr + $\langle\,$character locators$\,\rangle$\cr + |post_post| |q[4]| |i[1]| 223's$[{\G}4]$\cr}}$$ +Here |p| is a pointer to the byte following the final |eoc| in the file +(or to the byte following the preamble, if there are no characters); +it can be used to locate the beginning of \\{xxx} commands +that might have preceded the postamble. The |ds| and |cs| parameters +@^design size@> @^check sum@> +give the design size and check sum, respectively, which are exactly the +values put into the header of the \.{TFM} file that \MF\ produces (or +would produce) on this run. Parameters |hppp| and |vppp| are the ratios of +pixels per point, horizontally and vertically, expressed as |scaled| integers +(i.e., multiplied by $2^{16}$); they can be used to correlate the font +with specific device resolutions, magnifications, and ``at sizes.'' Then +come |min_m|, |max_m|, |min_n|, and |max_n|, which bound the values that +registers |m| and~|n| assume in all characters in this \.{GF} file. +(These bounds need not be the best possible; |max_m| and |min_n| may, on the +other hand, be tighter than the similar bounds in |boc| commands. For +example, some character may have |min_n=-100| in its |boc|, but it might +turn out that |n| never gets lower than |-50| in any character; then +|min_n| can have any value |<=-50|. If there are no characters in the file, +it's possible to have |min_m>max_m| and/or |min_n>max_n|.) + +@ Character locators are introduced by |char_loc| commands, +which specify a character residue~|c|, character escapements (|dx,dy|), +a character width~|w|, and a pointer~|p| +to the beginning of that character. (If two or more characters have the +same code~|c| modulo 256, only the last will be indicated; the others can be +located by following backpointers. Characters whose codes differ by a +multiple of 256 are assumed to share the same font metric information, +hence the \.{TFM} file contains only residues of character codes modulo~256. +This convention is intended for oriental languages, when there are many +character shapes but few distinct widths.) +@^oriental characters@>@^Chinese characters@>@^Japanese characters@> + +The character escapements (|dx,dy|) are the values of \MF's \&{chardx} +and \&{chardy} parameters; they are in units of |scaled| pixels; +i.e., |dx| is in horizontal pixel units times $2^{16}$, and |dy| is in +vertical pixel units times $2^{16}$. This is the intended amount of +displacement after typesetting the character; for \.{DVI} files, |dy| +should be zero, but other document file formats allow nonzero vertical +escapement. + +The character width~|w| duplicates the information in the \.{TFM} file; it +is a |fix_word| value relative to the design size, and it should be +independent of magnification. + +The backpointer |p| points to the character's |boc|, or to the first of +a sequence of consecutive \\{xxx} or |yyy| or |no_op| commands that +immediately precede the |boc|, if such commands exist; such ``special'' +commands essentially belong to the characters, while the special commands +after the final character belong to the postamble (i.e., to the font +as a whole). This convention about |p| applies also to the backpointers +in |boc| commands, even though it wasn't explained in the description +of~|boc|. @^backpointers@> + +Pointer |p| might be |-1| if the character exists in the \.{TFM} file +but not in the \.{GF} file. This unusual situation can arise in \MF\ output +if the user had |proofing<0| when the character was being shipped out, +but then made |proofing>=0| in order to get a \.{GF} file. + +@ The last part of the postamble, following the |post_post| byte that +signifies the end of the character locators, contains |q|, a pointer to the +|post| command that started the postamble. An identification byte, |i|, +comes next; this currently equals~131, as in the preamble. + +The |i| byte is followed by four or more bytes that are all equal to +the decimal number 223 (i.e., @'337 in octal). \MF\ puts out four to seven of +these trailing bytes, until the total length of the file is a multiple of +four bytes, since this works out best on machines that pack four bytes per +word; but any number of 223's is allowed, as long as there are at least four +of them. In effect, 223 is a sort of signature that is added at the very end. +@^Fuchs, David Raymond@> + +This curious way to finish off a \.{GF} file makes it feasible for +\.{GF}-reading programs to find the postamble first, on most computers, +even though \MF\ wants to write the postamble last. Most operating +systems permit random access to individual words or bytes of a file, so +the \.{GF} reader can start at the end and skip backwards over the 223's +until finding the identification byte. Then it can back up four bytes, read +|q|, and move to byte |q| of the file. This byte should, of course, +contain the value 248 (|post|); now the postamble can be read, so the +\.{GF} reader can discover all the information needed for individual characters. + +Unfortunately, however, standard \PASCAL\ does not include the ability to +@^system dependencies@> +access a random position in a file, or even to determine the length of a file. +Almost all systems nowadays provide the necessary capabilities, so \.{GF} +format has been designed to work most efficiently with modern operating systems. +But if \.{GF} files have to be processed under the restrictions of standard +\PASCAL, one can simply read them from front to back. This will +be adequate for most applications. However, the postamble-first approach +would facilitate a program that merges two \.{GF} files, replacing data +from one that is overridden by corresponding data in the other. + +@* \[47] Shipping characters out. +The |ship_out| procedure, to be described below, is given a pointer to +an edge structure. Its mission is to describe the positive pixels +in \.{GF} form, outputting a ``character'' to |gf_file|. + +Several global variables hold information about the font file as a whole:\ +|gf_min_m|, |gf_max_m|, |gf_min_n|, and |gf_max_n| are the minimum and +maximum \.{GF} coordinates output so far; |gf_prev_ptr| is the byte number +following the preamble or the last |eoc| command in the output; +|total_chars| is the total number of characters (i.e., |boc..eoc| segments) +shipped out. There's also an array, |char_ptr|, containing the starting +positions of each character in the file, as required for the postamble. If +character code~|c| has not yet been output, |char_ptr[c]=-1|. + +@= +@!gf_min_m,@!gf_max_m,@!gf_min_n,@!gf_max_n:integer; {bounding rectangle} +@!gf_prev_ptr:integer; {where the present/next character started/starts} +@!total_chars:integer; {the number of characters output so far} +@!char_ptr:array[eight_bits] of integer; {where individual characters started} +@!gf_dx,@!gf_dy:array[eight_bits] of integer; {device escapements} + +@ @= +gf_prev_ptr:=0; total_chars:=0; + +@ The \.{GF} bytes are output to a buffer instead of being sent +byte-by-byte to |gf_file|, because this tends to save a lot of +subroutine-call overhead. \MF\ uses the same conventions for |gf_file| +as \TeX\ uses for its \\{dvi\_file}; hence if system-dependent +changes are needed, they should probably be the same for both programs. + +The output buffer is divided into two parts of equal size; the bytes found +in |gf_buf[0..half_buf-1]| constitute the first half, and those in +|gf_buf[half_buf..gf_buf_size-1]| constitute the second. The global +variable |gf_ptr| points to the position that will receive the next +output byte. When |gf_ptr| reaches |gf_limit|, which is always equal +to one of the two values |half_buf| or |gf_buf_size|, the half buffer that +is about to be invaded next is sent to the output and |gf_limit| is +changed to its other value. Thus, there is always at least a half buffer's +worth of information present, except at the very beginning of the job. + +Bytes of the \.{GF} file are numbered sequentially starting with 0; +the next byte to be generated will be number |gf_offset+gf_ptr|. + +@= +@!gf_index=0..gf_buf_size; {an index into the output buffer} + +@ Some systems may find it more efficient to make |gf_buf| a |packed| +array, since output of four bytes at once may be facilitated. +@^system dependencies@> + +@= +@!gf_buf:array[gf_index] of eight_bits; {buffer for \.{GF} output} +@!half_buf:gf_index; {half of |gf_buf_size|} +@!gf_limit:gf_index; {end of the current half buffer} +@!gf_ptr:gf_index; {the next available buffer address} +@!gf_offset:integer; {|gf_buf_size| times the number of times the + output buffer has been fully emptied} + +@ Initially the buffer is all in one piece; we will output half of it only +after it first fills up. + +@= +half_buf:=gf_buf_size div 2; gf_limit:=gf_buf_size; gf_ptr:=0; +gf_offset:=0; + +@ The actual output of |gf_buf[a..b]| to |gf_file| is performed by calling +|write_gf(a,b)|. It is safe to assume that |a| and |b+1| will both be +multiples of 4 when |write_gf(a,b)| is called; therefore it is possible on +many machines to use efficient methods to pack four bytes per word and to +output an array of words with one system call. +@^system dependencies@> + +@= +procedure write_gf(@!a,@!b:gf_index); +var k:gf_index; +begin for k:=a to b do write(gf_file,gf_buf[k]); +end; + +@ To put a byte in the buffer without paying the cost of invoking a procedure +each time, we use the macro |gf_out|. + +@d gf_out(#)==@+begin gf_buf[gf_ptr]:=#; incr(gf_ptr); + if gf_ptr=gf_limit then gf_swap; + end + +@= +procedure gf_swap; {outputs half of the buffer} +begin if gf_limit=gf_buf_size then + begin write_gf(0,half_buf-1); gf_limit:=half_buf; + gf_offset:=gf_offset+gf_buf_size; gf_ptr:=0; + end +else begin write_gf(half_buf,gf_buf_size-1); gf_limit:=gf_buf_size; + end; +end; + +@ Here is how we clean out the buffer when \MF\ is all through; |gf_ptr| +will be a multiple of~4. + +@= +if gf_limit=half_buf then write_gf(half_buf,gf_buf_size-1); +if gf_ptr>0 then write_gf(0,gf_ptr-1) + +@ The |gf_four| procedure outputs four bytes in two's complement notation, +without risking arithmetic overflow. + +@= +procedure gf_four(@!x:integer); +begin if x>=0 then gf_out(x div three_bytes) +else begin x:=x+@'10000000000; + x:=x+@'10000000000; + gf_out((x div three_bytes) + 128); + end; +x:=x mod three_bytes; gf_out(x div unity); +x:=x mod unity; gf_out(x div @'400); +gf_out(x mod @'400); +end; + +@ Of course, it's even easier to output just two or three bytes. + +@= +procedure gf_two(@!x:integer); +begin gf_out(x div @'400); gf_out(x mod @'400); +end; +@# +procedure gf_three(@!x:integer); +begin gf_out(x div unity); gf_out((x mod unity) div @'400); +gf_out(x mod @'400); +end; + +@ We need a simple routine to generate a \\{paint} +command of the appropriate type. + +@= +procedure gf_paint(@!d:integer); {here |0<=d<65536|} +begin if d<64 then gf_out(paint_0+d) +else if d<256 then + begin gf_out(paint1); gf_out(d); + end +else begin gf_out(paint1+1); gf_two(d); + end; +end; + +@ And |gf_string| outputs one or two strings. If the first string number +is nonzero, an \\{xxx} command is generated. + +@= +procedure gf_string(@!s,@!t:str_number); +var @!k:pool_pointer; +@!l:integer; {length of the strings to output} +begin if s<>0 then + begin l:=length(s); + if t<>0 then l:=l+length(t); + if l<=255 then + begin gf_out(xxx1); gf_out(l); + end + else begin gf_out(xxx3); gf_three(l); + end; + for k:=str_start[s] to str_start[s+1]-1 do gf_out(so(str_pool[k])); + end; +if t<>0 then for k:=str_start[t] to str_start[t+1]-1 do gf_out(so(str_pool[k])); +end; + +@ The choice between |boc| commands is handled by |gf_boc|. + +@d one_byte(#)== #>=0 then if #<256 + +@= +procedure gf_boc(@!min_m,@!max_m,@!min_n,@!max_n:integer); +label exit; +begin if min_mgf_max_n then gf_max_n:=max_n; +if boc_p=-1 then if one_byte(boc_c) then + if one_byte(max_m-min_m) then if one_byte(max_m) then + if one_byte(max_n-min_n) then if one_byte(max_n) then + begin gf_out(boc1); gf_out(boc_c);@/ + gf_out(max_m-min_m); gf_out(max_m); + gf_out(max_n-min_n); gf_out(max_n); return; + end; +gf_out(boc); gf_four(boc_c); gf_four(boc_p);@/ +gf_four(min_m); gf_four(max_m); gf_four(min_n); gf_four(max_n); +exit: end; + +@ Two of the parameters to |gf_boc| are global. + +@= +@!boc_c,@!boc_p:integer; {parameters of the next |boc| command} + +@ Here is a routine that gets a \.{GF} file off to a good start. + +@d check_gf==@t@>@+if output_file_name=0 then init_gf + +@= +procedure init_gf; +var @!k:eight_bits; {runs through all possible character codes} +@!t:integer; {the time of this run} +begin gf_min_m:=4096; gf_max_m:=-4096; gf_min_n:=4096; gf_max_n:=-4096; +for k:=0 to 255 do char_ptr[k]:=-1; +@; +set_output_file_name; +gf_out(pre); gf_out(gf_id_byte); {begin to output the preamble} +old_setting:=selector; selector:=new_string; print(" METAFONT output "); +print_int(round_unscaled(internal[year])); print_char("."); +print_dd(round_unscaled(internal[month])); print_char("."); +print_dd(round_unscaled(internal[day])); print_char(":");@/ +t:=round_unscaled(internal[time]); +print_dd(t div 60); print_dd(t mod 60);@/ +selector:=old_setting; gf_out(cur_length); +gf_string(0,make_string); decr(str_ptr); +pool_ptr:=str_start[str_ptr]; {flush that string from memory} +gf_prev_ptr:=gf_offset+gf_ptr; +end; + +@ @= +if internal[hppp]<=0 then gf_ext:=".gf" +else begin old_setting:=selector; selector:=new_string; print_char("."); + print_int(make_scaled(internal[hppp],59429463)); + {$2^{32}/72.27\approx59429463.07$} + print("gf"); gf_ext:=make_string; selector:=old_setting; + end + +@ With those preliminaries out of the way, |ship_out| is not especially +difficult. + +@= +procedure ship_out(@!c:eight_bits); +label done; +var @!f:integer; {current character extension} +@!prev_m,@!m,@!mm:integer; {previous and current pixel column numbers} +@!prev_n,@!n:integer; {previous and current pixel row numbers} +@!p,@!q:pointer; {for list traversal} +@!prev_w,@!w,@!ww:integer; {old and new weights} +@!d:integer; {data from edge-weight node} +@!delta:integer; {number of rows to skip} +@!cur_min_m:integer; {starting column, relative to the current offset} +@!x_off,@!y_off:integer; {offsets, rounded to integers} +begin check_gf; f:=round_unscaled(internal[char_ext]);@/ +x_off:=round_unscaled(internal[x_offset]); +y_off:=round_unscaled(internal[y_offset]); +if term_offset>max_print_line-9 then print_ln +else if (term_offset>0)or(file_offset>0) then print_char(" "); +print_char("["); print_int(c); +if f<>0 then + begin print_char("."); print_int(f); + end; +update_terminal; +boc_c:=256*f+c; boc_p:=char_ptr[c]; char_ptr[c]:=gf_prev_ptr;@/ +if internal[proofing]>0 then @; +@; +gf_out(eoc); gf_prev_ptr:=gf_offset+gf_ptr; incr(total_chars); +print_char("]"); update_terminal; {progress report} +if internal[tracing_output]>0 then + print_edges(" (just shipped out)",true,x_off,y_off); +end; + +@ @= +begin if x_off<>0 then + begin gf_string("xoffset",0); gf_out(yyy); gf_four(x_off*unity); + end; +if y_off<>0 then + begin gf_string("yoffset",0); gf_out(yyy); gf_four(y_off*unity); + end; +end + +@ @= +prev_n:=4096; p:=knil(cur_edges); n:=n_max(cur_edges)-zero_field; +while p<>cur_edges do + begin @; + p:=knil(p); decr(n); + end; +if prev_n=4096 then @ +else if prev_n+y_off= +begin gf_boc(0,0,0,0); +if gf_max_m<0 then gf_max_m:=0; +if gf_min_n>0 then gf_min_n:=0; +end + +@ In this loop, |prev_w| represents the weight at column |prev_m|, which is +the most recent column reflected in the output so far; |w| represents the +weight at column~|m|, which is the most recent column in the edge data. +Several edges might cancel at the same column position, so we need to +look ahead to column~|mm| before actually outputting anything. + +@= +if unsorted(p)>void then sort_edges(p); +q:=sorted(p); w:=0; prev_m:=-fraction_one; {$|fraction_one|\approx\infty$} +ww:=0; prev_w:=0; m:=prev_m; +repeat if q=sentinel then mm:=fraction_one +else begin d:=ho(info(q)); mm:=d div 8; ww:=ww+(d mod 8)-zero_w; + end; +if mm<>m then + begin if prev_w<=0 then + begin if w>0 then @; + end + else if w<=0 then @; + m:=mm; + end; +w:=ww; q:=link(q); +until mm=fraction_one; +if w<>0 then {this should be impossible} + print_nl("(There's unbounded black in character shipped out!)"); +@.There's unbounded black...@> +if prev_m-m_offset(cur_edges)+x_off>gf_max_m then + gf_max_m:=prev_m-m_offset(cur_edges)+x_off + + +@ @= +begin if prev_m=-fraction_one then @ +else gf_paint(m-prev_m); +prev_m:=m; prev_w:=w; +end + +@ @= +begin gf_paint(m-prev_m); prev_m:=m; prev_w:=w; +end + +@ @= +begin if prev_n=4096 then + begin gf_boc(m_min(cur_edges)+x_off-zero_field, + m_max(cur_edges)+x_off-zero_field,@| + n_min(cur_edges)+y_off-zero_field,n+y_off); + cur_min_m:=m_min(cur_edges)-zero_field+m_offset(cur_edges); + end +else if prev_n>n+1 then @ +else @; +gf_paint(m-cur_min_m); {skip to column $m$, painting white} +done:prev_n:=n; +end + +@ @= +begin delta:=m-cur_min_m; +if delta>max_new_row then gf_out(skip0) +else begin gf_out(new_row_0+delta); goto done; + end; +end + +@ @= +begin delta:=prev_n-n-1; +if delta<@'400 then + begin gf_out(skip1); gf_out(delta); + end +else begin gf_out(skip1+1); gf_two(delta); + end; +end + +@ Now that we've finished |ship_out|, let's look at the other commands +by which a user can send things to the \.{GF} file. + +@= +special_command: do_special; + +@ @= +primitive("special",special_command,string_type);@/ +@!@:special_}{\&{special} primitive@> +primitive("numspecial",special_command,known);@/ +@!@:num_special_}{\&{numspecial} primitive@> + +@ @= +procedure do_special; +var @!m:small_number; {either |string_type| or |known|} +begin m:=cur_mod; get_x_next; scan_expression; +if internal[proofing]>=0 then + if cur_type<>m then @ + else begin check_gf; + if m=string_type then gf_string(cur_exp,0) + else begin gf_out(yyy); gf_four(cur_exp); + end; + end; +flush_cur_exp(0); +end; + +@ @= +begin exp_err("Unsuitable expression"); +@.Unsuitable expression@> +help1("The expression shown above has the wrong type to be output."); +put_get_error; +end + +@ @= +begin check_gf; gf_string("title ",cur_exp); +@.title@> +end + +@ @= +special_command:if m=known then print("numspecial") + else print("special"); + +@ @= +begin cur_exp:=round_unscaled(cur_exp) mod 256; +if cur_exp<0 then cur_exp:=cur_exp+256; +boolean_reset(char_exists[cur_exp]); cur_type:=boolean_type; +end + +@ At the end of the program we must finish things off by writing the postamble. +The \.{TFM} information should have been computed first. + +An integer variable |k| and a |scaled| variable |x| will be declared for +use by this routine. + +@= +begin gf_out(post); {beginning of the postamble} +gf_four(gf_prev_ptr); gf_prev_ptr:=gf_offset+gf_ptr-5; {|post| location} +gf_four(internal[design_size]*16); +for k:=1 to 4 do gf_out(header_byte[k]); {the check sum} +gf_four(internal[hppp]); +gf_four(internal[vppp]);@/ +gf_four(gf_min_m); gf_four(gf_max_m); +gf_four(gf_min_n); gf_four(gf_max_n); +for k:=0 to 255 do if char_exists[k] then + begin x:=gf_dx[k] div unity; + if (gf_dy[k]=0)and(x>=0)and(x<256)and(gf_dx[k]=x*unity) then + begin gf_out(char_loc+1); gf_out(k); gf_out(x); + end + else begin gf_out(char_loc); gf_out(k); + gf_four(gf_dx[k]); gf_four(gf_dy[k]); + end; + x:=value(tfm_width[k]); + if abs(x)>max_tfm_dimen then + if x>0 then x:=three_bytes-1@+else x:=1-three_bytes + else x:=make_scaled(x*16,internal[design_size]); + gf_four(x); gf_four(char_ptr[k]); + end; +gf_out(post_post); gf_four(gf_prev_ptr); gf_out(gf_id_byte);@/ +k:=4+((gf_buf_size-gf_ptr) mod 4); {the number of 223's} +while k>0 do + begin gf_out(223); decr(k); + end; +@; +print_nl("Output written on "); slow_print(output_file_name); +@.Output written...@> +print(" ("); print_int(total_chars); print(" character"); +if total_chars<>1 then print_char("s"); +print(", "); print_int(gf_offset+gf_ptr); print(" bytes)."); +b_close(gf_file); +end + +@* \[48] Dumping and undumping the tables. +After \.{INIMF} has seen a collection of macros, it +can write all the necessary information on an auxiliary file so +that production versions of \MF\ are able to initialize their +memory at high speed. The present section of the program takes +care of such output and input. We shall consider simultaneously +the processes of storing and restoring, +so that the inverse relation between them is clear. +@.INIMF@> + +The global variable |base_ident| is a string that is printed right +after the |banner| line when \MF\ is ready to start. For \.{INIMF} this +string says simply `\.{(INIMF)}'; for other versions of \MF\ it says, +for example, `\.{(preloaded base=plain 1984.2.29)}', showing the year, +month, and day that the base file was created. We have |base_ident=0| +before \MF's tables are loaded. + +@= +@!base_ident:str_number; + +@ @= +base_ident:=0; + +@ @= +base_ident:=" (INIMF)"; + +@ @= +@!init procedure store_base_file; +var @!k:integer; {all-purpose index} +@!p,@!q: pointer; {all-purpose pointers} +@!x: integer; {something to dump} +@!w: four_quarters; {four ASCII codes} +begin @; +@; +@; +@; +@; +@; +@; +end; +tini + +@ Corresponding to the procedure that dumps a base file, we also have a function +that reads~one~in. The function returns |false| if the dumped base is +incompatible with the present \MF\ table sizes, etc. + +@d off_base=6666 {go here if the base file is unacceptable} +@d too_small(#)==begin wake_up_terminal; + wterm_ln('---! Must increase the ',#); +@.Must increase the x@> + goto off_base; + end + +@p @t\4@>@@; +function load_base_file:boolean; +label off_base,exit; +var @!k:integer; {all-purpose index} +@!p,@!q: pointer; {all-purpose pointers} +@!x: integer; {something undumped} +@!w: four_quarters; {four ASCII codes} +begin @; +@; +@; +@; +@; +load_base_file:=true; return; {it worked!} +off_base: wake_up_terminal; + wterm_ln('(Fatal base file error; I''m stymied)'); +@.Fatal base file error@> +load_base_file:=false; +exit:end; + +@ Base files consist of |memory_word| items, and we use the following +macros to dump words of different types: + +@d dump_wd(#)==begin base_file^:=#; put(base_file);@+end +@d dump_int(#)==begin base_file^.int:=#; put(base_file);@+end +@d dump_hh(#)==begin base_file^.hh:=#; put(base_file);@+end +@d dump_qqqq(#)==begin base_file^.qqqq:=#; put(base_file);@+end + +@= +@!base_file:word_file; {for input or output of base information} + +@ The inverse macros are slightly more complicated, since we need to check +the range of the values we are reading in. We say `|undump(a)(b)(x)|' to +read an integer value |x| that is supposed to be in the range |a<=x<=b|. + +@d undump_wd(#)==begin get(base_file); #:=base_file^;@+end +@d undump_int(#)==begin get(base_file); #:=base_file^.int;@+end +@d undump_hh(#)==begin get(base_file); #:=base_file^.hh;@+end +@d undump_qqqq(#)==begin get(base_file); #:=base_file^.qqqq;@+end +@d undump_end_end(#)==#:=x;@+end +@d undump_end(#)==(x>#) then goto off_base@+else undump_end_end +@d undump(#)==begin undump_int(x); if (x<#) or undump_end +@d undump_size_end_end(#)==too_small(#)@+else undump_end_end +@d undump_size_end(#)==if x># then undump_size_end_end +@d undump_size(#)==begin undump_int(x); + if x<# then goto off_base; undump_size_end + +@ The next few sections of the program should make it clear how we use the +dump/undump macros. + +@= +dump_int(@$);@/ +dump_int(mem_min);@/ +dump_int(mem_top);@/ +dump_int(hash_size);@/ +dump_int(hash_prime);@/ +dump_int(max_in_open) + +@ Sections of a \.{WEB} program that are ``commented out'' still contribute +strings to the string pool; therefore \.{INIMF} and \MF\ will have +the same strings. (And it is, of course, a good thing that they do.) +@.WEB@> +@^string pool@> + +@= +x:=base_file^.int; +if x<>@$ then goto off_base; {check that strings are the same} +undump_int(x); +if x<>mem_min then goto off_base; +undump_int(x); +if x<>mem_top then goto off_base; +undump_int(x); +if x<>hash_size then goto off_base; +undump_int(x); +if x<>hash_prime then goto off_base; +undump_int(x); +if x<>max_in_open then goto off_base + +@ @d dump_four_ASCII== + w.b0:=qi(so(str_pool[k])); w.b1:=qi(so(str_pool[k+1])); + w.b2:=qi(so(str_pool[k+2])); w.b3:=qi(so(str_pool[k+3])); + dump_qqqq(w) + +@= +dump_int(pool_ptr); +dump_int(str_ptr); +for k:=0 to str_ptr do dump_int(str_start[k]); +k:=0; +while k+4= +undump_size(0)(pool_size)('string pool size')(pool_ptr); +undump_size(0)(max_strings)('max strings')(str_ptr); +for k:=0 to str_ptr do + begin undump(0)(pool_ptr)(str_start[k]); str_ref[k]:=max_str_ref; + end; +k:=0; +while k+4= +sort_avail; var_used:=0; +dump_int(lo_mem_max); dump_int(rover); +p:=mem_min; q:=rover; x:=0; +repeat for k:=p to q+1 do dump_wd(mem[k]); +x:=x+q+2-p; var_used:=var_used+q-p; +p:=q+node_size(q); q:=rlink(q); +until q=rover; +var_used:=var_used+lo_mem_max-p; dyn_used:=mem_end+1-hi_mem_min;@/ +for k:=p to lo_mem_max do dump_wd(mem[k]); +x:=x+lo_mem_max+1-p; +dump_int(hi_mem_min); dump_int(avail); +for k:=hi_mem_min to mem_end do dump_wd(mem[k]); +x:=x+mem_end+1-hi_mem_min; +p:=avail; +while p<>null do + begin decr(dyn_used); p:=link(p); + end; +dump_int(var_used); dump_int(dyn_used); +print_ln; print_int(x); +print(" memory locations dumped; current usage is "); +print_int(var_used); print_char("&"); print_int(dyn_used) + +@ @= +undump(lo_mem_stat_max+1000)(hi_mem_stat_min-1)(lo_mem_max); +undump(lo_mem_stat_max+1)(lo_mem_max)(rover); +p:=mem_min; q:=rover; +repeat for k:=p to q+1 do undump_wd(mem[k]); +p:=q+node_size(q); +if (p>lo_mem_max)or((q>=rlink(q))and(rlink(q)<>rover)) then goto off_base; +q:=rlink(q); +until q=rover; +for k:=p to lo_mem_max do undump_wd(mem[k]); +undump(lo_mem_max+1)(hi_mem_stat_min)(hi_mem_min); +undump(null)(mem_top)(avail); mem_end:=mem_top; +for k:=hi_mem_min to mem_end do undump_wd(mem[k]); +undump_int(var_used); undump_int(dyn_used) + +@ A different scheme is used to compress the hash table, since its lower region +is usually sparse. When |text(p)<>0| for |p<=hash_used|, we output three +words: |p|, |hash[p]|, and |eqtb[p]|. The hash table is, of course, densely +packed for |p>=hash_used|, so the remaining entries are output in~a~block. + +@= +dump_int(hash_used); st_count:=frozen_inaccessible-1-hash_used; +for p:=1 to hash_used do if text(p)<>0 then + begin dump_int(p); dump_hh(hash[p]); dump_hh(eqtb[p]); incr(st_count); + end; +for p:=hash_used+1 to hash_end do + begin dump_hh(hash[p]); dump_hh(eqtb[p]); + end; +dump_int(st_count);@/ +print_ln; print_int(st_count); print(" symbolic tokens") + +@ @= +undump(1)(frozen_inaccessible)(hash_used); p:=0; +repeat undump(p+1)(hash_used)(p); undump_hh(hash[p]); undump_hh(eqtb[p]); +until p=hash_used; +for p:=hash_used+1 to hash_end do + begin undump_hh(hash[p]); undump_hh(eqtb[p]); + end; +undump_int(st_count) + +@ We have already printed a lot of statistics, so we set |tracing_stats:=0| +to prevent them from appearing again. + +@= +dump_int(int_ptr); +for k:=1 to int_ptr do + begin dump_int(internal[k]); dump_int(int_name[k]); + end; +dump_int(start_sym); dump_int(interaction); dump_int(base_ident); +dump_int(bg_loc); dump_int(eg_loc); dump_int(serial_no); dump_int(69069); +internal[tracing_stats]:=0 + +@ @= +undump(max_given_internal)(max_internal)(int_ptr); +for k:=1 to int_ptr do + begin undump_int(internal[k]); + undump(0)(str_ptr)(int_name[k]); + end; +undump(0)(frozen_inaccessible)(start_sym); +undump(batch_mode)(error_stop_mode)(interaction); +undump(0)(str_ptr)(base_ident); +undump(1)(hash_end)(bg_loc); +undump(1)(hash_end)(eg_loc); +undump_int(serial_no);@/ +undump_int(x);@+if (x<>69069)or eof(base_file) then goto off_base + +@ @= +selector:=new_string; +print(" (preloaded base="); print(job_name); print_char(" "); +print_int(round_unscaled(internal[year])); print_char("."); +print_int(round_unscaled(internal[month])); print_char("."); +print_int(round_unscaled(internal[day])); print_char(")"); +if interaction=batch_mode then selector:=log_only +else selector:=term_and_log; +str_room(1); base_ident:=make_string; str_ref[base_ident]:=max_str_ref;@/ +pack_job_name(base_extension); +while not w_open_out(base_file) do + prompt_file_name("base file name",base_extension); +print_nl("Beginning to dump on file "); +@.Beginning to dump...@> +slow_print(w_make_name_string(base_file)); flush_string(str_ptr-1); +print_nl(""); slow_print(base_ident) + +@ @= +w_close(base_file) + +@* \[49] The main program. +This is it: the part of \MF\ that executes all those procedures we have +written. + +Well---almost. We haven't put the parsing subroutines into the +program yet; and we'd better leave space for a few more routines that may +have been forgotten. + +@p @@; +@@; +@ + +@ We've noted that there are two versions of \MF84. One, called \.{INIMF}, +@.INIMF@> +has to be run first; it initializes everything from scratch, without +reading a base file, and it has the capability of dumping a base file. +The other one is called `\.{VIRMF}'; it is a ``virgin'' program that needs +@.VIRMF@> +to input a base file in order to get started. \.{VIRMF} typically has +a bit more memory capacity than \.{INIMF}, because it does not need the +space consumed by the dumping/undumping routines and the numerous calls on +|primitive|, etc. + +The \.{VIRMF} program cannot read a base file instantaneously, of course; +the best implementations therefore allow for production versions of \MF\ that +not only avoid the loading routine for \PASCAL\ object code, they also have +a base file pre-loaded. This is impossible to do if we stick to standard +\PASCAL; but there is a simple way to fool many systems into avoiding the +initialization, as follows:\quad(1)~We declare a global integer variable +called |ready_already|. The probability is negligible that this +variable holds any particular value like 314159 when \.{VIRMF} is first +loaded.\quad(2)~After we have read in a base file and initialized +everything, we set |ready_already:=314159|.\quad(3)~Soon \.{VIRMF} +will print `\.*', waiting for more input; and at this point we +interrupt the program and save its core image in some form that the +operating system can reload speedily.\quad(4)~When that core image is +activated, the program starts again at the beginning; but now +|ready_already=314159| and all the other global variables have +their initial values too. The former chastity has vanished! + +In other words, if we allow ourselves to test the condition +|ready_already=314159|, before |ready_already| has been +assigned a value, we can avoid the lengthy initialization. Dirty tricks +rarely pay off so handsomely. +@^dirty \PASCAL@> +@^system dependencies@> + +On systems that allow such preloading, the standard program called \.{MF} +should be the one that has \.{plain} base preloaded, since that agrees +with {\sl The {\logos METAFONT\/}book}. Other versions, e.g., \.{CMMF}, +should also be provided for commonly used bases such as \.{cmbase}. +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> +@.cmbase@> +@.plain@> + +@= +@!ready_already:integer; {a sacrifice of purity for economy} + +@ Now this is really it: \MF\ starts and ends here. + +The initial test involving |ready_already| should be deleted if the +\PASCAL\ runtime system is smart enough to detect such a ``mistake.'' +@^system dependencies@> + +@p begin @!{|start_here|} +history:=fatal_error_stop; {in case we quit during initialization} +t_open_out; {open the terminal for output} +if ready_already=314159 then goto start_of_MF; +@@; +if bad>0 then + begin wterm_ln('Ouch---my internal constants have been clobbered!', + '---case ',bad:1); +@.Ouch...clobbered@> + goto final_end; + end; +initialize; {set global variables to their starting values} +@!init if not get_strings_started then goto final_end; +init_tab; {initialize the tables} +init_prim; {call |primitive| for each primitive} +init_str_ptr:=str_ptr; init_pool_ptr:=pool_ptr;@/ +max_str_ptr:=str_ptr; max_pool_ptr:=pool_ptr; fix_date_and_time; +tini@/ +ready_already:=314159; +start_of_MF: @; +@; +history:=spotless; {ready to go!} +if start_sym>0 then {insert the `\&{everyjob}' symbol} + begin cur_sym:=start_sym; back_input; + end; +main_control; {come to life} +final_cleanup; {prepare for death} +end_of_MF: close_files_and_terminate; +final_end: ready_already:=0; +end. + +@ Here we do whatever is needed to complete \MF's job gracefully on the +local operating system. The code here might come into play after a fatal +error; it must therefore consist entirely of ``safe'' operations that +cannot produce error messages. For example, it would be a mistake to call +|str_room| or |make_string| at this time, because a call on |overflow| +might lead to an infinite loop. +@^system dependencies@> + +This program doesn't bother to close the input files that may still be open. + +@= +procedure close_files_and_terminate; +var @!k:integer; {all-purpose index} +@!lh:integer; {the length of the \.{TFM} header, in words} +@!lk_offset:0..256; {extra words inserted at beginning of |lig_kern| array} +@!p:pointer; {runs through a list of \.{TFM} dimensions} +@!x:scaled; {a |tfm_width| value being output to the \.{GF} file} +begin +@!stat if internal[tracing_stats]>0 then + @;@;@+tats@/ +wake_up_terminal; @; +if log_opened then + begin wlog_cr; + a_close(log_file); selector:=selector-2; + if selector=term_only then + begin print_nl("Transcript written on "); +@.Transcript written...@> + slow_print(log_name); print_char("."); + end; + end; +end; + +@ We want to finish the \.{GF} file if and only if it has already been started; +this will be true if and only if |gf_prev_ptr| is positive. +We want to produce a \.{TFM} file if and only if |fontmaking| is positive. +The \.{TFM} widths must be computed if there's a \.{GF} file, even if +there's going to be no \.{TFM}~file. + +We reclaim all of the variable-size memory at this point, so that +there is no chance of another memory overflow after the memory capacity +has already been exceeded. + +@= +if (gf_prev_ptr>0)or(internal[fontmaking]>0) then + begin @; + @; + fix_design_size; fix_check_sum; + if internal[fontmaking]>0 then + begin @; + internal[fontmaking]:=0; {avoid loop in case of fatal error} + @; + end; + if gf_prev_ptr>0 then @; + end + +@ @= +rover:=lo_mem_stat_max+1; link(rover):=empty_flag; lo_mem_max:=hi_mem_min-1; +if lo_mem_max-rover>max_halfword then lo_mem_max:=max_halfword+rover; +node_size(rover):=lo_mem_max-rover; llink(rover):=rover; rlink(rover):=rover; +link(lo_mem_max):=null; info(lo_mem_max):=null + +@ The present section goes directly to the log file instead of using +|print| commands, because there's no need for these strings to take +up |str_pool| memory when a non-{\bf stat} version of \MF\ is being used. + +@= +if log_opened then + begin wlog_ln(' '); + wlog_ln('Here is how much of METAFONT''s memory',' you used:'); +@.Here is how much...@> + wlog(' ',max_str_ptr-init_str_ptr:1,' string'); + if max_str_ptr<>init_str_ptr+1 then wlog('s'); + wlog_ln(' out of ', max_strings-init_str_ptr:1);@/ + wlog_ln(' ',max_pool_ptr-init_pool_ptr:1,' string characters out of ', + pool_size-init_pool_ptr:1);@/ + wlog_ln(' ',lo_mem_max-mem_min+mem_end-hi_mem_min+2:1,@| + ' words of memory out of ',mem_end+1-mem_min:1);@/ + wlog_ln(' ',st_count:1,' symbolic tokens out of ', + hash_size:1);@/ + wlog_ln(' ',max_in_stack:1,'i,',@| + int_ptr:1,'n,',@| + max_rounding_ptr:1,'r,',@| + max_param_stack:1,'p,',@| + max_buf_stack+1:1,'b stack positions out of ',@| + stack_size:1,'i,', + max_internal:1,'n,', + max_wiggle:1,'r,', + param_size:1,'p,', + buf_size:1,'b'); + end + +@ We get to the |final_cleanup| routine when \&{end} or \&{dump} has +been scanned. + +@= +procedure final_cleanup; +label exit; +var c:small_number; {0 for \&{end}, 1 for \&{dump}} +begin c:=cur_mod; +if job_name=0 then open_log_file; +while input_ptr>0 do + if token_state then end_token_list@+else end_file_reading; +while loop_ptr<>null do stop_iteration; +while open_parens>0 do + begin print(" )"); decr(open_parens); + end; +while cond_ptr<>null do + begin print_nl("(end occurred when ");@/ +@.end occurred...@> + print_cmd_mod(fi_or_else,cur_if); + {`\.{if}' or `\.{elseif}' or `\.{else}'} + if if_line<>0 then + begin print(" on line "); print_int(if_line); + end; + print(" was incomplete)"); + if_line:=if_line_field(cond_ptr); + cur_if:=name_type(cond_ptr); loop_ptr:=cond_ptr; + cond_ptr:=link(cond_ptr); free_node(loop_ptr,if_node_size); + end; +if history<>spotless then + if ((history=warning_issued)or(interaction + selector:=term_and_log; + end; +if c=1 then + begin @!init store_base_file; return;@+tini@/ + print_nl("(dump is performed only by INIMF)"); return; +@.dump...only by INIMF@> + end; +exit:end; + +@ @= +@!init procedure init_prim; {initialize all the primitives} +begin +@; +end; +@# +procedure init_tab; {initialize other tables} +var @!k:integer; {all-purpose index} +begin @@; +end; +tini + +@ When we begin the following code, \MF's tables may still contain garbage; +the strings might not even be present. Thus we must proceed cautiously to get +bootstrapped in. + +But when we finish this part of the program, \MF\ is ready to call on the +|main_control| routine to do its work. + +@= +begin @; +if (base_ident=0)or(buffer[loc]="&") then + begin if base_ident<>0 then initialize; {erase preloaded base} + if not open_base_file then goto final_end; + if not load_base_file then + begin w_close(base_file); goto final_end; + end; + w_close(base_file); + while (loc; +if loc"\" then start_input; {\&{input} assumed} +end + +@* \[50] Debugging. +Once \MF\ is working, you should be able to diagnose most errors with +the \.{show} commands and other diagnostic features. But for the initial +stages of debugging, and for the revelation of really deep mysteries, you +can compile \MF\ with a few more aids, including the \PASCAL\ runtime +checks and its debugger. An additional routine called |debug_help| +will also come into play when you type `\.D' after an error message; +|debug_help| also occurs just before a fatal error causes \MF\ to succumb. +@^debugging@> +@^system dependencies@> + +The interface to |debug_help| is primitive, but it is good enough when used +with a \PASCAL\ debugger that allows you to set breakpoints and to read +variables and change their values. After getting the prompt `\.{debug \#}', you +type either a negative number (this exits |debug_help|), or zero (this +goes to a location where you can set a breakpoint, thereby entering into +dialog with the \PASCAL\ debugger), or a positive number |m| followed by +an argument |n|. The meaning of |m| and |n| will be clear from the +program below. (If |m=13|, there is an additional argument, |l|.) +@.debug \#@> + +@d breakpoint=888 {place where a breakpoint is desirable} + +@= +@!debug procedure debug_help; {routine to display various things} +label breakpoint,exit; +var @!k,@!l,@!m,@!n:integer; +begin loop begin wake_up_terminal; + print_nl("debug # (-1 to exit):"); update_terminal; +@.debug \#@> + read(term_in,m); + if m<0 then return + else if m=0 then + begin goto breakpoint;@\ {go to every label at least once} + breakpoint: m:=0; @{'BREAKPOINT'@}@\ + end + else begin read(term_in,n); + case m of + @t\4@>@@; + othercases print("?") + endcases; + end; + end; +exit:end; +gubed + +@ @= +1: print_word(mem[n]); {display |mem[n]| in all forms} +2: print_int(info(n)); +3: print_int(link(n)); +4: begin print_int(eq_type(n)); print_char(":"); print_int(equiv(n)); + end; +5: print_variable_name(n); +6: print_int(internal[n]); +7: do_show_dependencies; +9: show_token_list(n,null,100000,0); +10: slow_print(n); +11: check_mem(n>0); {check wellformedness; print new busy locations if |n>0|} +12: search_mem(n); {look for pointers to |n|} +13: begin read(term_in,l); print_cmd_mod(n,l); + end; +14: for k:=0 to n do print(buffer[k]); +15: panicking:=not panicking; + +@* \[51] System-dependent changes. +This section should be replaced, if necessary, by any special +modifications of the program +that are necessary to make \MF\ work at a particular installation. +It is usually best to design your change file so that all changes to +previous sections preserve the section numbering; then everybody's version +will be consistent with the published program. More extensive changes, +which introduce new sections, can be inserted here; then only the index +itself will get a new section number. +@^system dependencies@> + +@* \[52] Index. +Here is where you can find all uses of each identifier in the program, +with underlined entries pointing to where the identifier was defined. +If the identifier is only one letter long, however, you get to see only +the underlined entries. {\sl All references are to section numbers instead of +page numbers.} + +This index also lists error messages and other aspects of the program +that you might want to look up some day. For example, the entry +for ``system dependencies'' lists all sections that should receive +special attention from people who are installing \MF\ in a new +operating environment. A list of various things that can't happen appears +under ``this can't happen''. +Approximately 25 sections are listed under ``inner loop''; these account +for more than 60\pct! of \MF's running time, exclusive of input and output. diff --git a/systems/knuth/dist/mf/mfbook.tex b/systems/knuth/dist/mf/mfbook.tex new file mode 100644 index 0000000000..c1692a6f52 --- /dev/null +++ b/systems/knuth/dist/mf/mfbook.tex @@ -0,0 +1,20160 @@ +% This manual is copyright (C) 1986 by the American Mathematical Society. +% All rights are reserved! +% The file is distributed only for people to see its examples of TeX input, +% not for use in the preparation of books like The METAFONTbook. +% Permission for any other use of this file must be obtained in writing +% from the copyright holder and also from the publisher (Addison-Wesley). +\let\MFmanual=\! +\loop\iftrue + \errmessage{This manual is copyrighted and should not be TeXed}\repeat +\pausing1 \input manmac +\ifproofmode\message{Proof mode is on!}\pausing1\fi + % halftitle +\titlepage +\pageno=-1985 +\null\bigskip +\line{\titlefont The {\manual ()*+,-.*}book} +\vfill +\ifproofmode +\rightline{The fine print in the upper right-hand} +\rightline{corner of each page is a draft of intended} +\rightline{index entries; it won't appear in the real book.} +\rightline{Some index entries will be in |typewriter type|} +\rightline{and/or enclosed in \<$\ldots$>, etc;} +\rightline{such typographic distinctions aren't shown here.} +\rightline{An index entry often extends for several pages;} +\rightline{the actual scope will be determined later.} +\rightline{Please note things that should be indexed but aren't.} +\medskip +\rightline{Apology: The xeroxed illustrations are often hard to see;} +\rightline{they will be done professionally in the real book.} +\fi +\eject +\titlepage\null\vfill\eject % blank page + % title +\pageno=-1 % the front matter is numbered with roman numerals +\font\auth=cmssdc10 scaled\magstep4 % used only on the title page +\font\elevenbf=cmbx10 scaled\magstephalf % ditto +\font\elevenit=cmti10 scaled\magstephalf % ditto +\font\elevenrm=cmr10 scaled\magstephalf % ditto +\titlepage +\null\bigskip +\line{\titlefont The {\manual ()*+,-.*}book} +^^{Knuth, Donald Ervin} +^^{Bibby, Duane Robert} +\vskip 2pc +\baselineskip 13pt \elevenbf +\halign to\hsize{#\hfil\tabskip 0pt plus 1fil&#\hfil\tabskip0pt\cr +\kern2.5mm\auth DONALD \kern+0pt E. \kern+0pt KNUTH& + \elevenit Stanford University\cr +\noalign{\vskip 11pc} +&\elevenit I\kern.7ptllustrations by\cr +&DU\kern-1ptANE BIBBY\cr +\noalign{\vfill} +&\setbox0=\hbox{\manual77}% +\setbox2=\hbox to\wd0{\hss\manual6\hss}% +\raise2.3mm\box2\kern-\wd0\box0\cr % A-W logo +&ADDISON\kern.1em--WESLEY\cr +%&PUBLISHING COMP\kern-.13emANY\kern-1.5mm\cr +\noalign{\vskip.5pc \global\elevenrm} +&Upper Saddle River, NJ\cr +&Boston\enspace$\cdot$\enspace Indianapolis\cr +&San Francisco\enspace$\cdot$\enspace New York\cr +&Toronto\enspace$\cdot$\enspace Montr\'eal\cr +&London\enspace$\cdot$\enspace Munich\cr +&Paris\enspace$\cdot$\enspace Madrid\cr +&Capetown\enspace$\cdot$\enspace Sydney\enspace$\cdot$\enspace Tokyo\cr +&Singapore\enspace$\cdot$\enspace Mexico City\cr} +\kern24pt +\eject + % copyright +\titlepage +\eightpoint +\vbox to 8pc{} +\noindent\strut +%The quotation on page xxx is copyright $\copyright$ 19xx by Xxxx, +%and used by permission. +%\medskip +%\noindent +This manual describes \MF\ Version 2.0. Some +of the advanced features mentioned here are absent from earlier versions. +\medskip +\noindent +The joke on page 8 is due to Richard S. ^{Palais}. +\medskip +\noindent +The ^{Wilkins} quotation on page 283 was suggested by Georgia K. M. ^{Tobin}. +\medskip +\noindent +{\manual opqrstuq} is a trademark of Addison\kern.1em--Wesley + Publishing Company. +\medskip +\noindent +\TeX\ is a trademark of the American Mathematical Society. +\bigskip\medskip +\noindent +{\bf Library of Congress cataloging in publication data} +\medskip +{\tt\halign{#\hfil\cr +Knuth, Donald Ervin, 1938-\cr +\ \ \ The METAFONTbook.\cr +\noalign{\medskip} +\ \ \ (Computers \& Typesetting ; C)\cr +\ \ \ Includes index.\cr +\ \ \ 1.~METAFONT (Computer system).\ \ 2.~Type and type-\cr +founding--Data processing.\ \ I.~Title.\ \ II.~Series:\cr +Knuth, Donald Ervin, 1938-\ \ \ \ .\ \ Computers \&\cr +typesetting ; C.\cr +Z250.8.M46K58\ \ 1986\ \ \ \ \ \ \ \ \ 686.2\char13 24\ \ \ \ \ \ 85-28675\cr +ISBN 0-201-13445-4\cr +ISBN 0-201-13444-6 (soft)\cr}} +\vfill +\noindent +%{\sl \kern-1pt Incorporates the final corrections made in 1995, +% and a few dozen more.} +{\sl \kern-1pt Incorporates all corrections known in 2013.} +\smallskip +\noindent +Internet page {\tt http://www-cs-faculty.stanford.edu/\char`\~ + knuth/abcde.html} +contains current information about this book and related books. +\smallskip +\noindent +Copyright $\copyright$ 1986 by the American Mathematical Society +\smallskip +\noindent +This book is published jointly by the American Mathematical Society +and Addison\kern.1em--Wesley Publishing Company. +All rights reserved. No part of this publication may be reproduced, stored in +a retrieval system, or transmitted, in any form or by any means, +electronic, mechanical, photocopying, recording, or otherwise, without +the prior written permission of the publishers. Printed in the United +States of America. % Published simultaneously in Canada. +\medskip +\noindent +%ISBN 0-201-13444-6\par % paperback +%ISBN 0-201-13445-4\par % hardcover +ISBN-13 \enspace 978-0-201-13445-2\par\noindent +ISBN-10 \enspace\phantom{978-}0-201-13445-4\par\noindent +ISBN-13 \enspace 978-0-201-13444-5 (soft)\par\noindent +ISBN-10 \enspace\phantom{978-}0-201-13444-6 (soft)\par +%11 12 13 14 15 16--CRS--07 06 05 04 03 02 % paperback +%7 8 9 10 11 12 13--CRS--07 06 05 04 03 02 01 % hardcover +\smallskip\noindent +Text printed in the United States + at Courier Westford in Westford, Massachusetts.\par\noindent +Eighth Printing, February 2012\par\noindent +Twelfth Printing, February 2012 (soft) +^^{Knuth, Donald Ervin} +\eject + % dedication +\titlepage +\vbox to 8pc{} +\rightline{\strut\eightssi To Hermann Zapf:} +^^{Zapf, Hermann} +\vskip2pt +\rightline{\eightssi Whose strokes are the best} +\vfill +\eject + % blank page +\titlepage +\null\vfill +\eject + % the preface +\titlepage +\def\rhead{Preface} +\vbox to 8pc{ +\rightline{\titlefont Preface}\vss} +{\topskip 9pc % this makes equal sinkage throughout the Preface +\vskip-\parskip +\tenpoint +\noindent\hang\hangafter-2 +\smash{\lower12pt\hbox to 0pt{\hskip-\hangindent\cmman G\hfill}}\hskip-16pt +{\sc ENERATION} {\sc OF} {\sc LETTERFORMS} \strut by mathematical means +was first tried in the fifteenth century; it became popular in the +sixteenth and seventeenth centuries; and it was abandoned (for good +reasons) during the eighteenth century. Perhaps the twentieth century +will turn out to be the right time for this idea to make a comeback, +now that mathematics has advanced and computers are able to +do the calculations. + +Modern printing equipment based on raster lines---in which metal ``type'' +has been replaced by purely combinatorial patterns of zeroes and ones +that specify the desired position of ink in a discrete way---makes +mathematics and computer science increasingly relevant to printing. +We now have the ability to give a completely precise definition of letter +shapes that will produce essentially equivalent results on all raster-based +machines. Moreover, the shapes can be defined in terms of variable +parameters; computers can ``draw'' new fonts of characters +in seconds, making it possible for designers to perform valuable experiments +that were previously unthinkable. + +\MF\ is a system for the design of alphabets suited to raster-based +devices that print or display text. The characters that you are reading +were all designed with \MF\!, in a completely precise way; and they +were developed rather hastily by the author of the system, who is a rank +amateur at such things. It seems clear that further work with \MF\ has +the potential of producing typefaces of real ^{beauty}. This manual has +been written for people who would like to help advance the art of +mathematical type design. + +A top-notch designer of typefaces needs to have an unusually good eye +and a highly developed sensitivity to the nuances of shapes. +A top-notch user of computer languages needs to have an unusual +talent for abstract reasoning and a highly developed ability to +express intuitive ideas in formal terms. Very few people have both +of these unusual combinations of skills; hence the best products of +\MF\ will probably be collaborative efforts between two +people who complement each other's abilities. Indeed, this situation +isn't very different from the way types have been created for many +generations, except that the r\^ole of ``punch-cutter'' is now being +played by skilled computer specialists instead of by skilled +metalworkers. + +A \MF\ user writes a ``program'' for each letter or symbol of a typeface. +These programs are different from ordinary computer programs, +because they are essentially {\sl declarative\/} rather than imperative. +In the \MF\ language you explain where the major components of a +desired shape are to be located, and how they relate to each other, +but you don't have to work out the details of exactly where the lines +cross, etc.; the computer takes over the work of solving equations as it +deduces the consequences of your specifications. One of the advantages of +\MF\ is that it provides a discipline according to which the principles +of a particular alphabet design can be stated precisely. The underlying +intelligence does not remain hidden in the mind of the designer; it is +spelled out in the programs. Thus consistency can readily be obtained +where consistency is desirable, and a font can readily be extended to +new symbols that are compatible with the existing ones. + +It would be nice if a system like \MF\ were to simplify the task of type +design to the point where beautiful new alphabets could be created in a +few hours. This, alas, is impossible; an enormous amount of subtlety lies +behind the seemingly simple letter shapes that we see every day, and the +designers of high-quality typefaces have done their work so well that we +don't notice the underlying complexity. One of the disadvantages of \MF\ +is that a person can easily use it to produce poor alphabets, cheaply and +in great quantity. Let us hope that such experiments will have educational +value as they reveal why the subtle tricks of the trade are important, but +let us also hope that they will not cause bad workmanship to proliferate. +Anybody can now produce a book in which all of the type is home-made, but +a person or team of persons should expect to spend a year or more on the +project if the type is actually supposed to look right. \MF\ won't put +today's type designers out of work; on the contrary, it will tend to make +them heroes and heroines, as more and more people come to appreciate their +skills. + +Although there is no royal road to type design, there are some things that +can, in fact, be done well with \MF\ in an afternoon. Geometric designs +are rather easy; and it doesn't take long to make modifications to letters +or symbols that have previously been expressed in \MF\ form. Thus, +although comparatively few users of \MF\ will have the courage to do an +entire alphabet from scratch, there will be many who will enjoy +customizing someone else's design. + +This book is not a text about mathematics or about computers. But if +you know the rudiments of those subjects (namely, contemporary high school +mathematics, together with the knowledge of how to use the text +editing or word processing facilities on your computing machine), +you should be able to use \MF\ with little difficulty after reading +what follows. Some parts of the exposition in the text are more obscure +than others, however, since the author has tried to satisfy experienced +\MF ers as well as beginners and casual users with a single manual. +Therefore a special symbol has been used to warn about esoterica: When you +see the sign +$$\vbox{\hbox{\dbend}\vskip 11pt}$$ +at the beginning of a paragraph, watch out for a ``^{dangerous bend}'' +in the train of thought---don't read such a paragraph unless you need to. +You will be able to use \MF\ reasonably well, even to design characters like +the dangerous-bend symbol itself, without reading the fine print in such +advanced sections. + +Some of the paragraphs in this manual are so far out that they are rated +$$\vcenter{\hbox{\dbend\kern1pt\dbend}\vskip 11pt}\;;$$ +everything that was said about single dangerous-bend signs goes double +for these. You should probably have at least a month's experience with +\MF\ before you attempt to fathom such doubly dangerous depths +of the system; in fact, most people will never need to know \MF\ +in this much detail, even if they use it every day. After all, it's +possible to fry an egg without knowing anything about biochemistry. +Yet the whole story is here in case you're curious. \ (About \MF\!, not eggs.) + +The reason for such different levels of complexity is that people change +as they grow accustomed to any powerful tool. When you first try to use +\MF\!, you'll find that some parts of it are very easy, while other things +will take some getting used to. At first you'll probably try to control +the shapes too rigidly, by overspecifying data that has been copied from +some other medium. But later, after you have begun to get a feeling for +what the machine can do well, you'll be a different person, and you'll be +willing to let \MF\ help contribute to your designs as they are being +developed. As you gain more and more experience working with this unusual +apprentice, your perspective will continue to change and you will +run into different sorts of challenges. That's the way it is with any +powerful tool: There's always more to learn, and there are always better +ways to do what you've done before. At every stage in the development +you'll want a slightly different sort of manual. You may even want to +write one yourself. By paying attention to the dangerous bend signs in +this book you'll be better able to focus on the level that interests you +at a particular time. + +Computer system manuals usually make dull reading, but take heart: +This one contains {\sc ^{JOKES}} every once in a while. You might actually +enjoy reading it. \ (However, most of the jokes can only be appreciated +properly if you understand a technical point that is being made---so +read {\sl carefully}.) + +Another noteworthy characteristic of this book is that it doesn't +always tell the ^{truth}. When certain concepts of \MF\ are introduced +informally, general rules will be stated; afterwards you will find that the +rules aren't strictly true. In general, the later chapters contain more +reliable information than the earlier ones do. The author feels that this +technique of deliberate lying will actually make it easier for you to +learn the ideas. Once you understand a simple but false rule, it will not +be hard to supplement that rule with its exceptions. + +In order to help you internalize what you're reading, +{\sc ^{EXERCISES}} are sprinkled through this manual. It is generally intended +that every reader should try every exercise, except for questions that appear +in the ``dangerous bend'' areas. If you can't solve a problem, you +can always look up the answer. +But please, try first to solve it by yourself; then you'll learn more +and you'll learn faster. Furthermore, if you think you do know the solution, +you should turn to Appendix~A and check it out, just to make sure. + +\bigskip +\hrule +\line{\vrule\hss\vbox{\medskip\ninepoint +\leftskip=\parindent \rightskip=\parindent +\noindent\strut W{\sc ARNING}: Type design can be hazardous to your other +interests. Once you get hooked, you will develop intense feelings about +letterforms; the medium will intrude on the messages that you read. And you +will perpetually be thinking of improvements to the fonts that you see +everywhere, especially those of your own design. +\strut\medskip}\hss\vrule} +\hrule + +\bigskip + +The \MF\ language described here has very little in common with the +author's previous attempt at a language for alphabet design, because +five years of experience with the old system has made it clear that a +completely different approach is preferable. Both languages have +been called \MF; but henceforth the old language should be called +\MF\kern.05em79, and its use should rapidly fade away. Let's keep the name +\MF\ for the language described here, since it is so much better, and +since it will never change again. ^^{MF79} + +I wish to thank the hundreds of people who have helped me to formulate +this ``definitive edition'' of \MF\!, based on their experiences with +preliminary versions of the system. In particular, John ^{Hobby} +discovered many of the algorithms that have made the new language +possible. My work at Stanford has been generously supported by the +^{National Science Foundation}, the ^{Office of Naval Research}, the ^{IBM +Corporation}, and the ^{System Development Foundation}. I also wish to +thank the ^{American Mathematical Society} for its encouragement and for +publishing the {\sl ^{TUGboat}\/} newsletter (see Appendix~J\null). +Above all, I deeply thank my wife, Jill, for the inspiration, ^^{Knuth, Jill} +understanding, comfort, and support she has given me for more than +25~years, especially during the eight years that I have been +working intensively on mathematical typography. + +\medskip +\line{{\sl Stanford, California}\hfil--- D. E. K.}^^{Knuth, Don} +\line{\sl September 1985\hfil} + +} % end of the special \topskip +\endchapter + +It is hoped that Divine Justice may find +some suitable affliction for the malefactors +who invent variations upon the alphabet of our fathers.~.\thinspace.\thinspace. +The type-founder, worthy mechanic, has asserted himself +with an overshadowing individuality, +defacing with his monstrous creations and revivals +every publication in the land. +\author AMBROSE ^{BIERCE}, {\sl The Opinionator.~Alphab\^etes\/} % + (1911) % vol 10 of his collected works, p69 + % probably written originally in 1898 or 1899 + +\bigskip + +Can the new process yield a result that, say, +a Club of Bibliophiles would recognise as a work of art +comparable to the choice books they have in their cabinets? +\author STANLEY ^{MORISON}, {\sl Typographic Design in Relation to + Photographic Composition\/} (1958) % pp 4--5 + +\eject + % the table of contents +\titlepage +\vbox to 8pc{ +\rightline{\titlefont Contents} +\vfill} +^^{Contents of this manual, table} +\def\rhead{Contents} +\tenpoint +\begingroup +\countdef\counter=255 +\def\diamondleaders{\global\advance\counter by 1 + \ifodd\counter \kern-10pt \fi + \leaders\hbox to 20pt{\ifodd\counter \kern13pt \else\kern3pt \fi + .\hss}} +\baselineskip 15pt plus 5pt +\def\\#1. #2. #3.{\line{\strut + \hbox to\parindent{\bf\hbox to 1em{\hss#1}\hss}% + \rm#2\diamondleaders\hfil\hbox to 2em{\hss#3}}} +\\1. The Name of the Game. 1. +\\2. Coordinates. 5. +\\3. Curves. 13. +\\4. Pens. 21. +\\5. Running \MF\!\null. 31. +\\6. How \MF\ Reads What You Type. 49. +\\7. Variables. 53. +\\8. Algebraic Expressions. 59. +\\9. Equations. 75. +\\10. Assignments. 87. +\\11. Magnification and Resolution. 91. +\\12. Boxes. 101. +\\13. Drawing, Filling, and Erasing. 109. +\\14. Paths. 123. +\\15. Transformations. 141. +\\16. Calligraphic Effects. 147. +\\17. Grouping. 155. +\\18. Definitions (also called Macros). 159. +\\19. Conditions and Loops. 169. +\\20. More about Macros. 175. +\\21. Random Numbers. 183. +\\22. Strings. 187. +\\23. Online Displays. 191. +\eject +\vbox to 8pc{} +\\24. Discreteness and Discretion. 195. +\\25. Summary of Expressions. 209. +\\26. Summary of the Language. 217. +\\27. Recovery from Errors. 223. +\null +\leftline{\indent\bf Appendices} +\\A. Answers to All the Exercises. 233. +\\B. Basic Operations. 257. +\\C. Character Codes. 281. +\\D. Dirty Tricks. 285. +\\E. Examples. 301. +\\F. Font Metric Information. 315. +\\G. Generic Font Files. 323. +\\H. Hardcopy Proofs. 327. +\\I\hskip 1pt. Index. 345. +\\J\hskip 1pt. Joining the \TeX\ Community. 361. +\null % 17 lines so far to balance the 23 on the other page +\null % 18 +\null % 19 +\null % 20 +\null % 21 +\null % 22 +\null % 23 +\eject +\endgroup + \beginchapter Chapter 1. The Name of\\the Game + +\pageno=1 % This is page number 1, number 1, +This is a book about a computer system called \MF\!, \kern1pt just as +\kern-1pt {\sl The \TeX +book\/} is about \TeX. \MF\ and \TeX\ are good friends who intend to live +together for a long time. Between them they take care of the two most +fundamental tasks of typesetting: \TeX\ puts characters into the proper +positions on a page, while \MF\ determines the shapes of the characters +themselves. ^^{TeX} ^^{METAFONT, the name} + +Why is the system called \MF\thinspace? The `-{\manual FONT}\thinspace' +part is easy to understand, because sets of related characters that are +used in typesetting are traditionally known as fonts of type. The +`{\manual META}-' part is more interesting: It indicates that we are +interested in making high-level descriptions that transcend any of the +individual fonts being described. + +Newly coined words beginning with `meta-' generally reflect our contemporary +inclination to view things from outside or above, at a more abstract level than +before, with what we feel is a more mature understanding. We now have +metapsychology (the study of how the mind relates to its containing body), +metahistory (the study of principles that control the course of events), +metamathematics (the study of mathematical reasoning), metafiction +(literary works that explicitly acknowledge their own forms), and so on. +A metamathematician proves metatheorems (theorems about theorems); +a computer scientist often works with metalanguages (languages for +describing languages). Similarly, a ^{meta-font} is a schematic description +of the shapes in a family of related fonts; the letterforms change +appropriately as their underlying parameters change. + +Meta-design is much more difficult than design; it's easier to draw something +than to explain how to draw it. One of the problems is that different sets +of potential specifications can't easily be envisioned all at once. +Another is that a computer has to be told absolutely everything. +However, once we have successfully explained how to draw something +in a sufficiently general manner, the same explanation will work for +related shapes, in different circumstances; so~the time spent in formulating +a precise explanation turns out to be worth it. + +Typefaces intended for text are normally seen small, and our eyes can read +them best when the letters have been designed specifically for the size at +which they are actually used. Although it is tempting to get 7-point fonts +by simply making a 70\% reduction from the 10-point size, this shortcut +leads to a serious degradation of quality. Much better results can be +obtained by incorporating parametric variations into a meta-design. In +fact, there are advantages to built-in variability even when you want to +produce only one font of type in a single size, because it allows you to +postpone making decisions about many aspects of your design. If you leave +certain things undefined, treating them as parameters instead of +``freezing'' the specifications at an early stage, the computer will be +able to draw lots of examples with different settings of the parameters, +and you will be able to see the results of all those experiments at the final +size. This will greatly increase your ability to edit and fine-tune the font. + +If meta-fonts are so much better than plain old ordinary fonts, why weren't +they developed long ago? The main reason is that computers did not exist until +recently. People find it difficult and dull to carry out calculations with +a multiplicity of parameters, while today's machines do such tasks with ease. +The introduction of parameters is a natural outgrowth of automation. + +OK, let's grant that meta-fonts sound good, at least in theory. There's still +the practical problem about how to achieve them. How can we actually +specify shapes that depend on unspecified parameters? + +If only one parameter is varying, it's fairly easy to solve the problem in +a visual way, by overlaying a series of drawings that show graphically how +the shape changes. For example, if the parameter varies from 0 to~1, we +might prepare five sketches, corresponding to the parameter values 0, +$1\over4$, $1\over2$, $3\over4$, and~1. If these sketches follow a +consistent pattern, we can readily ^{interpolate} to find the shape for a +value like~$2\over3$ that lies between two of the given ones. We might +even try extrapolating to parameter values like 1$1\over4$. + +But if there are two or more independent parameters, a purely visual solution +becomes too cumbersome. We must go to a verbal approach, using some sort +of language to describe the desired drawings. Let's imagine, for example, +that we want to explain the shape of a certain letter `a' to a friend in +a distant country, using only a telephone for communication; our friend +is supposed to be able to reconstruct exactly the shape we have in mind. +Once we figure out a sufficiently natural way to do that, for a particular +fixed shape, it isn't much of a trick to go further and make our verbal +description more general, by including variable parameters instead of +restricting ourselves to constants. + +An analogy to cooking might make this point clearer. Suppose you have just +baked a delicious berry pie, and your friends ask you to tell them the +^{recipe} so that they can bake one too. If you have developed your cooking +skills entirely by intuition, you might find it difficult to record exactly +what you did. But there is a traditional language of recipes in which you +could communicate the steps you followed; and if you take careful measurements, +you might find that you used, say, 1$1\over4$ cups of sugar. The next step, +if you were instructing a computer-controlled cooking machine, would be to +go to a meta-recipe in which you use, say, $.25x$ cups of sugar for $x$ +cups of berries; or $.3x+.2y$ cups for $x$~cups of boysenberries and +$y$~cups of blackberries. + +In other words, going from design to meta-design is essentially like +going from arithmetic to elementary algebra. Numbers are replaced +by simple formulas that involve unknown quantities. We will see +many examples of this. + +A \MF\ definition of a complete typeface generally consists of three +main parts. First there is a rather mundane set of subroutines that take care +of necessary administrative details, such as assigning code numbers +to individual characters; each character must also +be positioned properly inside an invisible ``box,'' so that typesetting +systems will produce the correct spacing. Next comes a more interesting +collection of subroutines, designed to draw the basic strokes characteristic +of the typeface (e.g., the serifs, bowls, arms, arches, and so on). +These subroutines will typically be described in terms of their own special +parameters, so that they can produce a variety of related strokes; +a serif subroutine will, for example, be able to draw serifs of +different lengths, although all of the serifs it draws should have the +same ``feeling.'' Finally, there are routines for each of the characters. +If the subroutines in the first and second parts have been chosen well, +the routines of the third part will be fairly high-level descriptions +that don't concern themselves unnecessarily with details; for example, it +may be possible to substitute a different serif-drawing subroutine without +changing any of the programs that use that subroutine, thereby obtaining +a typeface of quite a different flavor. [A particularly striking example +of this approach has been worked out by John~D. ^{Hobby} and ^{Gu} Guoan +in ``A Chinese Meta-Font,'' {\sl TUGboat\/ \bf5} (1984), 119--136. By +changing a set of 13 basic stroke subroutines, they were able to draw 128 +sample ^{Chinese characters} in three different styles (Song, Long Song, +and Bold), using the same programs for the characters.] + +A well-written \MF\ program will express the designer's intentions more +clearly than mere drawings ever can, because the language of algebra has +simple ``idioms'' that make it possible to elucidate many visual relationships. +Thus, \MF\ programs can be used to communicate knowledge +about type design, just as recipes convey the expertise of a chef. But +algebraic formulas are not easy to understand in isolation; \MF\ descriptions +are meant to be read with an accompanying illustration, just as the +constructions in geometry textbooks are accompanied by diagrams. +Nobody is ever expected to read the text of a \MF\ program and say, +``Ah, what a beautiful letter!'' But with one or more enlarged pictures +of the letter, based on one or more settings of the parameters, a reader +of the \MF\ program should be able to say, ``Ah, I~understand how this +beautiful letter was drawn!'' We shall see that the \MF\ system makes it +fairly easy to obtain annotated proof drawings that you can hold in your +hand as you are working with a program. + +Although \MF\ is intended to provide a relatively painless way to describe +meta-fonts, you can, of course, use it also to describe unvarying shapes that +have no ``meta-ness'' at all. Indeed, you need not even use it to produce +fonts; the system will happily draw geometric designs that have no relation +to the characters or glyphs of any alphabet or script. The author +occasionally uses \MF\ simply as a pocket calculator, to do elementary +arithmetic in an interactive way. A computer doesn't mind if its +programs are put to purposes that don't match their names. + +\endchapter + +[Tinguely] made some large, brightly coloured open reliefs, +juxtaposing stationary and mobile shapes. +He later gave them names like\/ % +{\rm Meta-^{Kandinsky}}\kern-1pt\ and\/ {\rm Meta-^{Herbin}}\kern-.5pt, +to clarify the ideas and attitudes % +that lay at the root of their conception. +\author K. G. PONTUS ^{HULT\'EN}, {\sl Jean ^{Tinguely}: M\'eta\/} (1972) + % translated from German by Mary Whittall, 1975, p46 + +\bigskip + +The idea of a meta-font should now be clear. But what good is it? +The ability to manipulate lots of parameters may be interesting and fun, +but does anybody really need a 6\/{\manual\seventh}\kern1pt-point font +that is one fourth of the way between Baskerville and Helvetica? +\author DONALD E. ^{KNUTH}, {\sl The Concept of a Meta-Font\/} (1982) + % Visible Language 16, p19 + +\eject + \beginchapter Chapter 2. Coordinates + +If we want to tell a computer how to draw a particular shape, we need a way to +explain where the key points of that shape are supposed to be. +\MF\ uses standard {\sl ^{Cartesian} ^{coordinates}\/} for this purpose: +The location of a point is defined by specifying its $x$~coordinate, which +is the number of units to the right of some reference point, and its +$y$~coordinate, which is the number of units upward from the reference +point. First we determine the horizontal (left/right) component of a +point's position, then we determine the vertical (up/down) component. +\MF's world is two-dimensional, so two coordinates are enough.% +^^{x coordinate} ^^{y coordinate} + +For example, let's consider the following six points: +\displayfig 2a (4.75pc) +\MF's names for the positions of these points are +\begindisplay +$(x_1,y_1)=(0,100)$;&$(x_2,y_2)=(100,100)$;&$(x_3,y_3)=(200,100)$;\cr +$(x_4,y_4)=(0,\hfill0)$;&$(x_5,y_5)=(100,\hfill0)$;& + $(x_6,y_6)=(200,\hfill0)$.\cr +\enddisplay +Point 4 is the same as the reference point, since both of its coordinates +are zero; to get to point~$3=(200,100)$, you start at the reference point +and go 200~steps right and 100~up; and so on. + +\exercise Which of the six example points is closest to the point $(60,30)$? +\answer Point $5=(100,0)$ is closer than any of the others. \ (See +the diagram below.) + +\exercise True or false: All points that lie on a given horizontal straight +line have the same $x$~coordinate. +\answer \decreasehsize 15pc +\rightfig A2a (13pc x 5pc) ^9pt +False. But they all do have the same $y$~coordinate. + +\exercise Explain where the point $(-5,15)$ is located. +\answer 5 units to the {\sl left\/} of the reference point, and 15 units up. + +\exercise What are the coordinates of a point that lies exactly +60~units below point~6 in the diagram above? +(``Below'' means ``down the page,'' not ``under the page.'') +\answer \restorehsize $(200,-60)$. + +In a typical application of \MF\!, you prepare a rough sketch of the shape +you plan to define, on a piece of ^{graph paper}, and you label important +points on that sketch with any convenient numbers. Then you write a \MF\ +program that explains (i)~the coordinates of those key points, and +(ii)~the lines or curves that are supposed to go between them. + +\MF\ has its own internal graph paper, which forms a so-called ^{raster} +or ^{grid} consisting of square ``^{pixels}.'' ^^{pel, see pixel} +The output of \MF\ will \hbox{specify} that certain of the pixels are ``black'' +and that the others are ``white''; thus, the computer essentially converts +shapes into binary patterns like the designs a~person can make when doing +needlepoint with two colors of yarn. + +Coordinates are lengths, but we haven't discussed yet what the units of +length actually are. It's important to choose convenient units, +and \MF's coordinates are given in units of pixels. The little squares +illustrated on the previous page, which correspond to differences +of 10~units in an $x$~coordinate or a $y$~coordinate, therefore represent +$10\times10$ arrays of pixels, and the rectangle enclosed by our six +example points contains 20,000 pixels altogether.\footnote*{We +sometimes use the term ``pixel'' to mean a square picture element, +but sometimes we use it to signify a one-dimensional unit of length. +A square pixel is one pixel-unit wide and one pixel-unit tall.} + +Coordinates don't have to be whole numbers. You can refer, for example, +to point $(31.5,42.5)$, which lies smack in the middle of the pixel +whose corners are at $(31,42)$, $(31,43)$, $(32,42)$, and~$(32,43)$. +The computer works internally with coordinates that are integer multiples +of ${1\over65536}\approx0.00002$ of the width of a pixel, so it is +capable of making very fine distinctions. But \MF\ will never make +a pixel half black; it's all or nothing, as far as the output is concerned. + +The fineness of a grid is usually called its {\sl ^{resolution}}, and +resolution is usually expressed in pixel units per inch (in America) +or pixel units per millimeter (elsewhere). For example, the type you +are now reading was prepared by \MF\ with a resolution of slightly +more than 700 pixels to the inch, but with slightly fewer than 30 pixels +per~mm. For the time being we shall assume that the pixels are so tiny +that the operation of rounding to whole pixels is unimportant; +later we will consider the important questions that arise when \MF\ is +producing low-resolution output. + +It's usually desirable to write \MF\ programs that can manufacture fonts +at many different resolutions, so that a variety of low-resolution printing +devices will be able to make proofs that are compatible with a variety of +high-resolution devices. Therefore the key points in \MF\ programs are rarely +specified in terms of pure numbers like `100'\thinspace; we generally make +the coordinates relative to some other resolution-dependent quantity, so +that changes will be easy to make. For example, it would have been better +to use a definition something like the following, for the six points +considered earlier: +\begindisplay +$(x_1,y_1)=(0,b)$;&$(x_2,y_2)=(a,b)$;&$(x_3,y_3)=(2a,b)$;\cr +$(x_4,y_4)=(0,0)$;&$(x_5,y_5)=(a,0)$;&$(x_6,y_6)=(2a,0)$;\cr +\enddisplay +then the quantities $a$ and $b$ can be defined in some way appropriate to +the desired resolution. We had $a=b=100$ in our previous example, but +such constant values leave us with little or no flexibility. + +Notice the quantity `$2a$' in the definitions of $x_3$ and $x_6$; \MF\ +understands enough algebra to know that this means twice the value of~$a$, +whatever $a$~is. We observed in Chapter~1 that simple uses of algebra give +\MF\ its meta-ness. Indeed, it is interesting to note from a historical +standpoint that ^{Cartesian} coordinates are named after Ren\'e +^{Descartes}, not because he invented the idea of coordinates, but because +he showed how to get much more out of that idea by applying algebraic +methods. People had long since been using coordinates for such things as +latitudes and longitudes, but Descartes observed that by putting unknown +quantities into the coordinates it became possible to describe infinite +sets of related points, and to deduce properties of curves that were +extremely difficult to work out using geometrical methods alone. + +So far we have specified some points, but we haven't actually done +anything with them. Let's suppose that we want to draw a straight line +from point~1 to point~6, obtaining +\displayfig 2b (5pc) +One way to do this with \MF\ is to say +\begindisplay +@draw@ $(x_1,y_1)\to(x_6,y_6)$. +\enddisplay +The `$\to$' ^^{..} here tells the computer to connect two points. + +It turns out that we often want to write formulas like `$(x_1,y_1)$', so +it will be possible to save lots of time if we have a special abbreviation +for such things. Henceforth we shall use the notation $z_1$ to stand for +$(x_1,y_1)$; and in general, ^^{z convention} +$z_k$ with an arbitrary subscript will stand for the point $(x_k,y_k)$. +The `@draw@' command above can therefore be written more simply as +\begindisplay +^@draw@ $z_1\to z_6$. +\enddisplay +Adding two more straight lines by saying, `@draw@ $z_2\to z_5$' and +`@draw@ $z_3\to z_4$', we obtain a design that is slightly reminiscent of +the ^{Union Jack}: +\displayfig 2c (5.5pc) +We shall call this a ^{hex symbol}, because it has six endpoints. Notice +that the straight lines here have some thickness, and they are rounded at +the ends as if they had been drawn with a felt-tip pen having a circular +nib. \MF\ provides many ways to control the thicknesses of lines and to +vary the terminal shapes, but we shall discuss such things in later +chapters because our main concern right now is to learn about coordinates. + +If the hex symbol is scaled down so that its height parameter $b$ +is exactly equal to the height of the letters in this paragraph, +it looks like this: `\thinspace{\manual\hexa}\thinspace'. Just for fun, +let's try to typeset ten of them in a row: +\begindisplay +{\manual\hexa\hexa\hexa\hexa\hexa\hexa\hexa\hexa\hexa\hexa} +\enddisplay +How easy it is to do this!\footnote*{Now that authors have +for the first time the power to invent new symbols with great ease, and to +have those characters printed in their manuscripts on a wide variety of +typesetting devices, we must face the question of how much experimentation +is desirable. Will font freaks abuse this toy by overdoing it? Is it wise +to introduce new symbols by the thousands? Such questions are beyond +the scope of this book; but it is easy to imagine an epidemic of +fontomania occurring, once people realize how much fun it is to design +their own characters, hence it may be necessary to perform fontal +lobotomies.} % This joke due to Richard Palais, commenting on draft in 1979 + +Let's look a bit more closely at this new character. +The {\manual\hexa} is a bit too tall, because it extends above points +1, 2, and~3 when the thickness of the lines is taken into account; +similarly, it sinks a bit too much below the baseline (i.e., below +the line $y=0$ that contains points 4, 5, and~6). In order to correct +this, we want to move the key points slightly. For example, point~$z_1$ +should not be exactly at $(0,b)$; we ought to arrange things so that +the top of the pen is at $(0,b)$ when the center of the pen is at~$z_1$. +We can express this condition for the top three points as follows: +\begindisplay +$"top"\,z_1=(0,b)$;&$"top"\,z_2=(a,b)$;&$"top"\,z_3=(2a,b)$;\cr +\noalign{\vskip\belowdisplayskip +\leftline{similarly, the remedy for points 4, 5, and 6 is to specify + the equations} +\vskip\abovedisplayskip} +$"bot"\,z_4=(0,0)$;&$"bot"\,z_5=(a,0)$;&$"bot"\,z_6=(2a,0)$.\cr +\enddisplay +The resulting squashed-in character is +\displayfig 2d (4.5pc) +(shown here with the original weight `\thinspace{\manual\hexb}\thinspace' +and also in a bolder version `\thinspace{\manual\hexc}\thinspace'). + +\exercise Ten of these bold hexes produce `\thinspace{\manual +\hexc\hexc\hexc\hexc\hexc\hexc\hexc\hexc\hexc\hexc}\thinspace'; notice that +adjacent symbols overlap each other. The reason is that each character +has width $2a$, hence point~3 of one character coincides with point~1 +of the next. Suppose that we actually want the characters to be +completely confined to a rectangular box of width~$2a$, so that +adjacent characters come just shy of touching (\thinspace{\manual +\hexd\hexd\hexd\hexd\hexd\hexd\hexd\hexd\hexd\hexd}\thinspace). +Try to guess how the point-defining equations above could be modified +to make this happen, assuming that +\MF\ has operations `"lft"' and `"rt"' analogous to `"top"' and `"bot"'. +\answer $"top"\,"lft"\,z_1=(0,b)$; \ $"top"\,z_2=(a,b)$; \ +$"top"\,"rt"\,z_3=(2a-1,b)$; \ $"bot"\,"lft"\,z_4=(0,0)$; \ +$"bot"\,z_5=(a,0)$; \ $"bot"\,"rt"\,z_6=(2a-1,0)$. +Adjacent characters will be separated by exactly one column of white +pixels, if the character is $2a$ pixels wide, because the right edge of +black pixels is specified here to have the $x$~coordinate $2a-1$. + +Pairs of coordinates can be thought of as ``^{vectors}'' or ``displacements'' +as well as points. For example, $(15,8)$ can be regarded as a command to +go right~15 and up~8; then point $(15,8)$ is the position we get to after +starting at the reference point and obeying the command $(15,8)$. This +interpretation works out nicely when we consider addition of vectors: +If we move according to the vector $(15,8)$ and then move according to +$(7,-3)$, the result is the same as if we move $(15,8)+(7,-3)= +(15+7,8-3)=(22,5)$. The sum of two vectors $z_1=(x_1,y_1)$ and $z_2= +(x_2,y_2)$ is the vector $z_1+z_2=(x_1+x_2,y_1+y_2)$ obtained by adding +$x$ and $y$ components separately. This vector represents the result of +moving by vector $z_1$ and then moving by vector $z_2$; alternatively, +$z_1+z_2$ represents the point you get~to by starting at point~$z_1$ +^^{addition of vectors} +and moving by vector~$z_2$. + +\exercise Consider the four fundamental vectors $(0,1)$, $(1,0)$, +$(0,-1)$, and $(-1,0)$. Which of them corresponds to moving one pixel unit +(a)~to the right? (b)~to the left? (c)~down? (d)~up? +\answer $"right"=(1,0)$; $"left"=(-1,0)$; $"down"=(0,-1)$; $"up"=(0,1)$. + +Vectors can be subtracted as well as added; the value of $z_1-z_2$ is simply +$(x_1-x_2,y_1-y_2)$. Furthermore it is natural to multiply a vector +by a single number~$c$: The quantity $c$~times $(x,y)$, which is written +$c(x,y)$, equals $(cx,cy)$. Thus, for example, $2z=2(x,y)=(2x,2y)$ turns +out to be equal to $z+z$. ^^{multiplication of vector by scalar} +In the special case $c=-1$, we write $-(x,y)=(-x,-y)$. ^^{negation of vectors} + +Now we come to an important notion, based on the fact that subtraction +is the opposite of addition. {\sl If $z_1$ and $z_2$ are any two points, +then $z_2-z_1$ is the vector that corresponds to moving from $z_1$ to~$z_2$.} +The reason is simply that $z_2-z_1$ is what we must add to~$z_1$ in order +to get~$z_2$: i.e., $z_1+(z_2-z_1)=z_2$. We shall call this the +{\sl ^{vector subtraction principle}}. ^^{subtraction of vectors} +It is used frequently in \MF\ programs when the designer wants to specify the +direction and/or distance of one point from another. + +\MF\ programs often use another idea to express relations between points. +Suppose we start at point~$z_1$ and travel in a straight line from there +in the direction of point~$z_2$, but we don't go all the way. There's a +special notation for this, using square brackets: ^^{bracket notation} +\begindisplay \advance\baselineskip by 3pt +${1\over3}[z_1,z_2]$ is the point one-third of the way from $z_1$ to $z_2$,\cr +${1\over2}[z_1,z_2]$ is the point midway between $z_1$ and $z_2$,\cr +$.8[z_1,z_2]$ is the point eight-tenths of the way from $z_1$ to $z_2$,\cr +\enddisplay +and, in general, $t[z_1,z_2]$ stands for the point that lies a fraction +$t$ of the way from $z_1$ to~$z_2$. We call this the operation of {\sl +^{mediation}\/} between points, or (informally) the ``^{of-the-way +function}.'' If the fraction~$t$ increases from 0 to~1, the expression +$t[z_1,z_2]$ traces out a straight line from $z_1$ to~$z_2$. According to +the vector subtraction principle, we must move $z_2-z_1$ in order to go all +the way from $z_1$ to~$z_2$, hence the point $t$~of~the~way between them is +\begindisplay +$t[z_1,z_2]\;=\;z_1+t(z_2-z_1)$. +\enddisplay +This is a general formula by which we can calculate $t[z_1,z_2]$ for any +given values of $t$, $z_1$, and~$z_2$. But \MF\ has this formula built~in, +so we can use the bracket notation explicitly. + +For example, let's go back to our first six example points, and suppose +that we want to refer to the point that's 2/5 of the way from +$z_2=(100,100)$ to $z_6=(200,0)$. In \MF\ we can write this simply as +$.4[z_2,z_6]$. And if we need to compute the exact coordinates for some +reason, we can always work them out from the general formula, getting +$z_2+.4(z_6-z_2)=(100,100)+.4\bigl((200,0)-(100,100)\bigr)=(100,100) ++.4(100,-100)=(100,100)+(40,-40)=(140,60)$. + +\exercise True or false: The direction vector from $(5,-2)$ to $(2,3)$ +is $(-3,5)$. +\answer True; this is $(2,3)-(5,-2)$. + +\exercise Explain what the notation `$0[z_1,z_2]$' means, if anything. +What about `$1[z_1,z_2]$'? And `$2[z_1,z_2]$'? And `$(-.5)[z_1,z_2]$'? +\answer $0[z_1,z_2]=z_1$, because we move none of the way towards~$z_2$; +similarly $1[z_1,z_2]$ simplifies to~$z_2$, because we move all of the +way. If we keep going in the same direction until we've gone twice as far +as the distance from $z_1$ to~$z_2$, we get to $2[z_1,z_2]$. But if we +start at point~$z_1$ and face~$z_2$, then back up exactly half the distance +between them, we wind up at $(-.5)[z_1,z_2]$. + +\exercise True or false, for mathematicians: (a)~${1\over2}[z_1,z_2]= +{1\over2}(z_1+z_2)$; \ (b)~${1\over3}[z_1,z_2]={1\over3}z_1+{2\over3}z_2$; +\ (c)~$t[z_1,z_2]=(1-t)[z_2,z_1]$. +\answer (a)~True; both are equal to $z_1+{1\over2}(z_2-z_1)$. +(b)~False, but close; the right-hand side should be +${2\over3}z_1+{1\over3}z_2$. (c)~True; both are equal to $(1-t)z_1+tz_2$. + +\setbox0=\vtop{\kern -6pt + \rightline{\rlap{\vbox to 250\apspix{ + \setbox2=\vbox{\kern-1pt + \hbox{\tenex\char'77} % vertical arrow extension module + \kern-1pt} + \offinterlineskip + \vbox{\hbox{\tenex\char'170}\kern-1pt} % arrowhead at top + \cleaders\copy2\vfill + \kern3pt + \hbox to\wd2{\hss$b$\hss} + \kern3pt + \cleaders\copy2\vfill + \vbox{\kern-1pt\hbox{\tenex\char'171}\kern0pt} % arrowhead at bottom + }}\kern 30\apspix + \vbox{%\kern-.2pt \hrule \kern-.2pt + \hbox{%\kern-.2pt \vrule \kern-.2pt + \kern30\apspix\figbox{2e}{150\apspix}{250\apspix}\vbox +% \kern30\apspix\kern-.2pt\vrule \kern-.2pt} +% \kern-.2pt \hrule \kern-.2pt}\quad} + \kern30\apspix} + }\quad} + \kern2pt + \rightline{\hbox to 30\apspix{\kern-.2pt\vrule height 7pt depth 2pt + \hfil$s$\hfil\vrule\kern-.2pt}% + \hbox to 150\apspix{\leftarrowfill$\,a\,$\rightarrowfill}% + \hbox to 30\apspix{\kern-.2pt\vrule height 7pt depth 2pt + \hfil$s$\hfil\vrule\kern-.2pt}\quad}} +\dp0=0pt + +\hangindent-300\apspix \hangafter-13 +Let's conclude \strut\vadjust{\box0}% +this chapter by using mediation +to help specify the five points in the stick-figure `{\manual\Aa}' +shown enlarged at the right. The distance between points 1 and~5 +should be~$a$, and point~3 should be $b$ pixels above the baseline; +these values $a$ and~$b$ have been predetermined by some method +that doesn't concern us here, and so has a ``^{sidebar}'' parameter~$s$ +that specifies the horizontal distance of points 1 and~5 from the +edges of the type. We shall assume that we don't know for sure what +the height of the bar line should be; point~2 should be somewhere on the +straight line from point~1 to point~3, and point~4 should be in the +corresponding place between 5 and~3, but we want to try several +possibilities before we make a decision. + +The width of the character will be $s+a+s$, and we can specify points +$z_1$ and $z_5$ by the equations +\begindisplay +$"bot"\,z_1=(s,0)$;\qquad $z_5=z_1+(a,0)$. +\enddisplay +There are other ways to do the job, but these formulas clearly express +our intention to have the bottom of the pen at the baseline, $s$ pixels +to the right of the reference point, when the pen is at~$z_1$, +and to have $z_5$ exactly $a$~pixels to the right of~$z_1$. +Next, we can say +\begindisplay +$z_3=\bigl({1\over2}[x_1,x_5],b\bigr)$; +\enddisplay +this means that the $x$ coordinate of point 3 should be halfway between +the $x$~coordinates of points 1 and~5, and that $y_3=b$. Finally, let's say +\begindisplay +$z_2="alpha"[z_1,z_3]$;\qquad $z_4="alpha"[z_5,z_3]$; +\enddisplay +the parameter "alpha" is a number between 0 and~1 that governs the +position of the bar line, and it will be supplied later. When "alpha" +has indeed received a value, we can say +\begindisplay +@draw@ $z_1\to z_3$;\qquad @draw@ $z_3\to z_5$;\qquad @draw@ $z_2\to z_4$. +\enddisplay +\MF\ will draw the characters `{\manual\sevenAs}' when "alpha" varies +from 0.2 to 0.5 in steps of 0.05 and when $a=150$, $b=250$, $s=30$. +The illustration on the previous page has $"alpha"=(3-\sqrt5\,)/2\approx +0.38197$; this value makes the ratio of the area below the bar to the area +above it equal to $(\sqrt5+1)/2\approx1.61803$, the so-called ``^{golden +ratio}'' of classical Greek mathematics. + +\danger (Are you sure you should be reading this paragraph? The +``^{dangerous bend}'' sign here is meant to warn you about material that +ought to be skipped on first reading. And maybe also on second reading. +The reader-beware paragraphs sometimes refer to concepts that aren't +explained until later chapters.) + +\dangerexercise Why is it better to define $z_3$ as $\bigl({1\over2}[x_1, +x_5],b\bigr)$, rather than to work out the explicit coordinates +$z_3=(s+{1\over2}a,\,b)$ that are implied by the other equations? +\answer There are several reasons. (1)~The equations in a \MF\ program +should represent the programmer's intentions as directly as possible; +it's hard to understand those intentions if you are shown only +their ultimate consequences, since it's not easy to reconstruct algebraic +manipulations that have gone on behind the scenes. (2)~It's easier and +safer to let the computer do algebraic calculations, rather than +to do them by hand. (3)~If the specifications for $z_1$ and $z_5$ change, +the formula $\bigl({1\over2}[x_1,x_5],b\bigr)$ +still gives a reasonable value for~$z_3$. It's +almost always good to anticipate the need for subsequent modifications.\par +However, the stated formula for $z_3$ isn't the only reasonable way to +proceed. We could, for example, give two equations +\begindisplay +$x_3-x_1=x_5-x_3$;\qquad $y_3=b$; +\enddisplay +the first of these states that the horizontal distance from 1 to 3 is +the same as the horizontal distance from 3 to~5. We'll see later that +\MF\ is able to solve a wide variety of equations. + +\ninepoint % all dangerous from here +\ddangerexercise Given $z_1$, $z_3$, and $z_5$ as above, explain how +to define $z_2$ and~$z_4$ so that all of the following conditions hold +simultaneously: +\enddanger + +\smallskip +\item\bull the line from $z_2$ to $z_4$ slopes upward at a $20^\circ$ angle; + +\item\bull the $y$ coordinate of that line's midpoint is 2/3 of the +way from $y_3$ to $y_1$; + +\item\bull $z_2$ and $z_4$ are on the respective lines $z_1\to z_3$ and +$z_3\to z_5$. + +\smallskip\noindent +(If you solve this exercise, you deserve an `{\manual\Az}'.) +\answer The following four equations suffice to define the four +unknown quantities $x_2$, $y_2$, $x_4$, and $y_4$: +$z_4-z_2="whatever"\ast{\rm dir}\,20$; +${1\over2}[y_2,y_4]={2\over3}[y_3,y_1]$; +$z_2="whatever"[z_1,z_3]$; +$z_4="whatever"[z_3,z_5]$. ^^"whatever" ^^{dir} + +\endchapter + +Here, where we reach the sphere of mathematics, +we are among processes which seem to some +the most inhuman of all human activities +and the most remote from poetry. +Yet it is here that the artist has the fullest scope for his imagination. +\author HAVELOCK ^{ELLIS}, {\sl The Dance of Life\/} (1923) % pp 138--139 + +\bigskip + +To anyone who has lived in a modern American city (except Boston) +at least one of the underlying ideas of ^{Descartes}' analytic geometry +will seem ridiculously evident. Yet, as remarked, +it took mathematicians all of two thousand years +to arrive at this simple thing. +\author ERIC TEMPLE ^{BELL}, {\sl Mathematics: Queen and Servant of % + Science\/} (1951) % p123 + +\eject + \beginchapter Chapter 3. Curves + +Albrecht ^{D\"urer} and other Renaissance men attempted to establish +mathematical principles of type design, but the letters they came up with +were not especially beautiful. Their methods failed because they +restricted themselves to ``ruler and compass'' constructions, which cannot +adequately express the nuances of good calligraphy. \MF\ gets around this +problem by using more powerful mathematical techniques, which provide the +necessary flexibility without really being too complicated. The purpose of +the present chapter is to explain the simple principles by which a +computer is able to draw ``pleasing'' ^{curves}. + +The basic idea is to start with four points $(z_1,z_2,z_3,z_4)$ and to +^^{four-point method for curves} +construct the three ^{midpoints} $z_{12}={1\over2}[z_1,z_2]$, +$z_{23}={1\over2}[z_2,z_3]$, $z_{34}={1\over2}[z_3,z_4]$: +\displayfig 3a (5pc) +Then take those three midpoints $(z_{12},z_{23},z_{34})$ and construct +two second-order midpoints $z_{123}={1\over2}[z_{12},z_{23}]$ and +$z_{234}={1\over2}[z_{23},z_{34}]$; finally, construct the third-order +midpoint $z_{1234}={1\over2}[z_{123},z_{234}]$: +\displayfig 3b (5pc) +This point $z_{1234}$ is one of the points of the curve determined by +$(z_1,z_2,z_3,z_4)$. To get the remaining points of that curve, +repeat the same construction on $(z_1,z_{12},z_{123},z_{1234})$ and +on $(z_{1234},z_{234},z_{34},z_4)$, ad infinitum: +\displayfig 3c (4.5pc) +The process converges quickly, and the preliminary scaffolding +(which appears above the limiting curve in our example) is ultimately discarded. +The limiting curve has the following important properties: + +\smallskip +\item\bull It begins at $z_1$, heading in the direction from $z_1$ to $z_2$. + +\item\bull It ends at $z_4$, heading in the direction from $z_3$ to $z_4$. + +\item\bull It stays entirely within the so-called convex hull of $z_1$, +$z_2$, $z_3$, and $z_4$; i.e., all points of the curve lie ``between'' the +defining points. + +\danger The recursive midpoint rule for curve-drawing was discovered in 1959 +by Paul ^{de Casteljau}, who showed that the curve could be described +algebraically by the remarkably simple formula +\begindisplay +$z(t)\;=\;(1-t)^3z_1+3(1-t)^2t\,z_2+3(1-t)t^2z_3+t^3z_4$, +\enddisplay +as the parameter $t$ varies from 0 to 1. This polynomial of degree~3 in~$t$ +is called a {\sl ^{Bernshte{\u\i}n polynomial}}, because Serge\u\i~N. +^{Bernshte{\u\i}n} introduced such functions in 1912 as part of his +pioneering work on approximation theory. Curves traced out by Bernshte{\u\i}n +polynomials of degree~3 are often called {\sl B\'ezier cubics}, after +Pierre ^{B\'ezier} who realized their importance for computer-aided design +during the 1960s. + +\danger It is interesting to observe that the Bernshte\u\i n polynomial +of degree~1, i.e., the function $z(t)=(1-t)\,z_1+t\,z_2$, is precisely the +^{mediation} operator $t[z_1,z_2]$ that we discussed in the previous chapter. +Indeed, if the geometric construction we have just seen is changed to +use $t$-of-the-way points instead of midpoints (i.e., if $z_{12}= +t[z_1,z_2]$ and $z_{23}=t[z_2,z_3]$, etc.), then $z_{1234}$ turns out +to be precisely $z(t)$ in the formula above. + +No matter what four points $(z_1,z_2,z_3,z_4)$ are given, the construction +on the previous page defines a curved line that runs from $z_1$ to~$z_4$. +This curve is not always interesting or beautiful; for example, if all +four of the given points lie on a straight line, the entire ``curve'' +that they define will also be contained in that same line. We obtain +rather different curves from the same four starting points if we +number the points differently: +\displayfig 3d (7.05pc) +Some discretion is evidently advisable when the $z$'s are chosen. But the +four-point method is good enough to obtain satisfactory approximations to +any curve we want, provided that we break the desired curve into short +enough segments and give four suitable control points for each segment. +It turns out, in fact, that we can usually get by with only a few segments. +For example, the four-point method can produce an approximate +quarter-circle with less than 0.06\% error; it never yields an exact +circle, but the differences between four such quarter-circles and a true +circle are imperceptible. + +All of the curves that \MF\ draws are based on four points, as just +described. But it isn't necessary for a user to specify all of those +points, because the computer is usually able to figure out good values of +$z_2$ and $z_3$ by itself. Only the endpoints $z_1$ and~$z_4$, through +which the curve is actually supposed to pass, are usually mentioned +explicitly in a \MF\ program. + +For example, let's return to the six points that were used to introduce the +ideas of coordinates in Chapter~2. We said `@draw@ $z_1\to z_6$' in that +chapter, in order to draw a straight line from point~$z_1$ to point~$z_6$. +In general, if three or more points are listed instead of two, \MF\ will draw a +^^{..} smooth curve through all the points. For example, the commands +`@draw@ $z_4\to z_1\to z_2\to z_6$' and `@draw@ $z_5\to z_4\to z_1 +\to z_3\to z_6\to z_5$' will produce the respective results +\displayfig 3e (7.75pc) +(Unlabeled points in these diagrams are ^{control points} that \MF\ has +supplied automatically so that it can use the four-point scheme to draw +curves between each pair of adjacent points on the specified paths.) + +Notice that the curve is not smooth at $z_5$ in the right-hand example, +because $z_5$~appears at both ends of that particular path. In order to +get a completely smooth curve that returns to its starting point, you can +say `@draw@ $z_5\to z_4\to z_1\to z_3\to z_6\to \cycle$' instead: +\displayfig 3f (7.25pc) +The word `^{cycle}' at the end of a path refers to the starting point +of that path. +\MF\ believes that this ^{bean-like shape} +is the nicest way to connect the given points in the given cyclic order; +but of course there are many decent curves that satisfy the specifications, +and you may have another one in mind. You can obtain finer control +by giving hints to the machine in various ways. For example, the +bean curve can be ``pulled tighter'' between $z_1$ and~$z_3$ if you say +\begindisplay +@draw@ $z_5\to z_4\to z_1\to\tension1.2\to z_3\to z_6\to \cycle$; +\enddisplay +the so-called ^{tension} between points is normally 1, and an increase +to 1.2 yields +\displayfig 3g (5.75pc) + +\danger An asymmetric effect can be obtained by increasing the tension +only at point~1 but not at points 3~or~4; the shape +\displayfig 3h (6.5pc) +comes from +%\begindisplay +%@draw@ $z_5\to z_4\to\tension1\and1.5\to z_1\to +% \tension1.5\and1\to z_3$\cr +%\hskip6em$\to z_6\to \cycle$. +%\enddisplay +`@draw@ $z_5\to z_4\to\tension1\and1.5\to z_1\to + \tension1.5\and1\to z_3\to z_6\to \cycle$'. +The effect of tension has been achieved in this example by moving two of +the anonymous control points closer to point~1. + +It's possible to control a curve in another way, by telling \MF\ what +direction to travel at some or all of the points. Such directions are +given inside curly braces; for example, +\begindisplay +@draw@ $z_5\to z_4\{"left"\}\to z_1\to z_3\to z_6\{"left"\}\to\cycle$ +\enddisplay +says that the curve should be traveling leftward at points 4 and 6. The +resulting curve is perfectly straight from $z_6$ to~$z_5$ to~$z_4$: +\displayfig 3i (5.8pc) +We will see later that `"left"' is an abbreviation for the vector $(-1,0)$, +which stands for one unit of travel in a leftward direction. Any desired +direction can be specified by enclosing a vector in $\{\ldots\}$'s; for +example, the command `@draw@ $z_4\to z_2\{z_3-z_4\}\to z_3$' will draw a +curve from $z_4$ to~$z_2$ to~$z_3$ such that the tangent direction at +$z_2$ is parallel to the line $z_4\to z_3$, because $z_3-z_4$ is the +vector that represents travel from $z_4$ to~$z_3$: +\displayfig 3j (4.7pc) +The same result would have been obtained from a command such as `@draw@ +$z_4\to z_2 \{10(z_3-z_4)\}\to z_3$', because the vector $10(z_3-z_4)$ has +the same direction as $z_3-z_4$. \MF\ ignores the magnitudes of vectors +when they are simply being used to specify directions. + +\exercise What do you think will be the result of +`@draw@ $z_4\to z_2\{z_4-z_3\}\to z_3$', when points $z_2$, $z_3$,~$z_4$ +are the same as they have been in the last several examples? +\answer The direction at $z_2$ is parallel to the line $z_4\to z_3$, but +the vector $z_4-z_3$ specifies a direction towards $z_4$, which is +$180^\circ$ different from the direction $z_3-z_4$ that was discussed in +the text. Thus, we have a difficult specification to meet, and \MF\ draws +a pretzel-shaped curve that loops around in a way that's too ugly to show +here. The first part of the path, from $z_4$ to $z_2$, is mirror symmetric +about the line~$z_1\to z_5$ that bisects $z_4\to z_2$, so it starts out in a +south-by-southwesterly direction; the second part is mirror symmetric about +the vertical line that bisects $z_2\to z_3$, so when the curve ends at~$z_3$ +it's traveling roughly northwest. The moral is: Don't specify a direction +that runs opposite to (i.e., is the negative of) the one you really want. + +\exercise Explain how to get \MF\ to draw the wiggly shape +\displayfig 3k (5pc) +in which the curve aims directly at point 2 when it's at point~6, but +directly away from point~2 when it's at point~4. [{\sl Hint:\/} No +tension changes are needed; it's merely necessary to specify directions +at $z_4$ and~$z_6$.] +\answer @draw@ $z_5\to z_4\{z_4-z_2\}\to z_1\to z_3\to z_6\{z_2-z_6\} +\to\cycle$. + +\MF\ allows you to change the shape of a curve at its endpoints by +specifying different amounts of ``^{curl}.'' For example, the two commands +\begindisplay +@draw@ $z_4\{\curl0\}\to z_2\{z_3-z_4\}\to\{\curl0\}\,z_3$;\cr +@draw@ $z_4\{\curl2\}\to z_2\{z_3-z_4\}\to\{\curl2\}\,z_3$\cr +\enddisplay +give the respective curves +\displayfig 3l (5pc) +which can be compared with the one shown earlier when no special curl was +requested. \ (The specification `$\curl1$' is assumed at an endpoint +if no explicit curl or direction has been mentioned, just as +`$\tension1$' is implied between points when no tension has +been explicitly given.) \ Chapter 14 explains more about~this. + +It's possible to get curved lines instead of straight lines even when +only two points are named, if a direction has been prescribed at one or +both of the points. For example, +\begindisplay +@draw@ $z_4\{z_2-z_4\}\to\{"down"\}\,z_6$\cr +\enddisplay +asks \MF\ for a curve that starts traveling towards $z_2$ but finishes +in a downward direction: +\displayfig 3m (4pc) + +\danger Here are some of the curves that \MF\ draws between two points, when +it is asked to move outward from the left-hand point at an angle of +$60^\circ$, and to approach the right-hand point at various angles: +\displayfig 3aa (2.6cm) +This diagram was produced by the \MF\ program ^^@for@ ^^@step@ ^^@until@ ^^"cm" +\begindisplay +@for@ $d=0$ @step@ 10 @until@ 120:\cr +\indent @draw@ $(0,0)\{{\rm dir}\,60\}\to\{{\rm dir}\,{-d}\}(6"cm",0)$; + @endfor@;\cr +\enddisplay +the `^{dir}' function specifies a direction measured in degrees +counterclockwise from a horizontal rightward line, hence `${\rm dir}\,{-d}$' +gives a direction that is $d^\circ$ below the horizon. The lowest curves +in the illustration correspond to small values of $d$, and the highest +curves correspond to values near $120^\circ$. + +\danger A car that drives along the upper paths in the diagram above +is always turning to the right, but in the lower paths it comes to a +point where it needs to turn to the left in order to reach its destination +from the specified direction. +The place where a path changes its curvature from right to left or +vice versa is called an ``^{inflection point}.'' \MF\ introduces +inflection points when it seems better to change the curvature than +to make a sharp turn; indeed, when $d$ is negative there is no way to +avoid points of inflection, and the curves for small positive~$d$ ought to +be similar to those obtained when $d$~has small negative values. The program +\begindisplay +@for@ $d=0$ @step@ $-10$ @until@ $-90$:\cr +\indent @draw@ $(0,0)\{{\rm dir}\,60\}\to\{{\rm dir}\,{-d}\}(6"cm",0)$; + @endfor@\cr +\enddisplay +shows what \MF\ does when $d$ is negative: +\displayfig 3bb (2.8cm) + +\danger It is sometimes desirable to avoid points of inflection, when $d$ is +positive, and to require the curve to remain inside the triangle +determined by its initial and final directions. This can be achieved +^^{...} +by using three dots instead of two when you specify a curve: The program +\begindisplay +@for@ $d=0$ @step@ 10 @until@ 120:\cr +\indent @draw@ $(0,0)\{{\rm dir}\,60\}\ldots\{{\rm dir}\,{-d}\}(6"cm",0)$; + @endfor@\cr +\enddisplay +generates the curves +\displayfig 3cc (2.6cm) +which are the same as before except that inflection points do not occur +for the small values of~$d$. The `$\ldots$' specification keeps the +curve ``^{bounded}'' inside the triangle that is defined by the endpoints +and directions; but it has no effect when there is +no such triangle. More precisely, suppose that the curve goes from $z_0$ +to~$z_1$; if there's a point~$z$ such that the initial direction is from +$z_0$ to~$z$ and the final direction is from $z$ to~$z_1$, then the curve +specified by `$\ldots$' will stay entirely within the triangle whose +corners are $z_0$, $z_1$, and~$z$. But if there's no such triangle +(e.g., if $d<0$ or $d>120$ in our example program), both `$\ldots$' +and~`$\to$' will produce the same curves. + +In this chapter we have seen lots of different ways to get \MF\ to draw +curves. And there's one more way, which subsumes all of the others. +If changes to tensions, curls, directions, and/or boundedness +aren't enough to produce the sort of curve that a person wants, it's +always possible as a last resort to specify all four of the points in the +four-point method. For example, the command +\begindisplay +@draw@ $z_4\to\controls z_1\and z_2\to z_6$ +\enddisplay +will draw the following curve from $z_4$ to $z_6$:^^{controls} +\displayfig 3n (5pc) + + +\endchapter + +And so I think I have omitted nothing +% Et ainsi ie pense n'auoir rien omis des elemens, +that is necessary to an understanding of curved lines. +% qui sont necessaires pour la connoissance des lignes courbes. +\author REN\'E ^{DESCARTES}, {\sl La G\'eom\'etrie\/} (1637) % p369 + +\bigskip + +Rules or substitutes for the artist's hand must necessarily be inadequate, +although, when set down by such men as +^{D\"urer}, ^{Tory}, ^{Da Vinci}, ^{Serlio}, and others, +they probably do establish canons of proportion and construction +which afford a sound basis upon which to present new expressions. +\author FREDERIC W. ^{GOUDY}, {\sl Typologia\/} (1940) % p 138f + +\eject + \beginchapter Chapter 4. Pens + +Our examples so far have involved straight lines or curved lines that look +as if they were drawn by a felt-tip ^{pen}, where the ^{nib} of that pen +was perfectly round. A mathematical ``line'' has no thickness, so it's +invisible; but when we plot circular dots at each point of an infinitely +thin line, we get a visible line that has constant thickness. + +Lines of constant thickness have their uses, but \MF\ also provides +several other kinds of scrivener's tools, and we shall take a look at some +of them in this chapter. We'll see not only that the sizes and shapes of +pen nibs can be varied, but also that characters can be built up in such a +way that the outlines of each stroke are precisely controlled. + +\def\kk{\kern2pt } % kidney-bean kern +First let's consider the simplest extensions of what we have seen before. +The letter `{\manual\Aa}' of Chapter~2 and the kidney-^{bean} +`\kk{\manual\beana}\kk' of Chapter~3 were drawn with circular pen nibs of +diameter $0.4\pt$, where `pt' stands for a printer's point;\footnote*{$ +1\,{\rm in}=2.54\,{\rm cm}=72.27\pt$ exactly, as explained in +{\sl The \TeX book}.} $0.4\pt$ is the standard thickness of a ruled line +`$\,\vcenter{\hrule width 2em}\,$' drawn by \TeX. Such a penpoint can be +specified by telling \MF\ to +\begindisplay +\pickup @pencircle@ ^{scaled} $0.4"pt"$; +\enddisplay +\MF\ will use the pen it has most recently picked up ^^@pickup@ +whenever it is asked to `^@draw@' anything. A ^@pencircle@ is a +circular pen whose diameter is the width of one pixel. Scaling it +by $0.4"pt"$ will change it to the size that corresponds +to $0.4\pt$ in the output, because ^"pt" is the number of pixels +in $1\pt$. If the key points $(z_1,z_2,z_3,z_4,z_5,z_6)$ of Chapters 2 and~3 +have already been defined, the \MF\ commands +\begindisplay +\pickup @pencircle@ scaled $0.8"pt"$;\cr +@draw@ $z_5\to z_4\to z_1\to z_3\to z_6\to \cycle$\cr +\enddisplay +will produce a bean shape twice as thick as before: `\kk{\manual\beanb}\kk' +instead of `\kk{\manual\beana}\kk'. + +More interesting effects arise when we use non-circular pen nibs. For example, +the command +\begindisplay +\pickup @pencircle@ ^{xscaled} $0.8"pt"$ ^{yscaled} $0.2"pt"$ +\enddisplay +picks up a pen whose tip has the shape of an ellipse, $0.8\pt$ wide and +$0.2\pt$ tall; magnified 10 times, it looks like this: +`$\,\vcenter{\hbox{\manual\niba}}\,$'. +\ (The operation of ``xscaling'' multiplies $x$~coordinates by a specified +amount but leaves $y$~coordinates unchanged, and the operation of +``yscaling'' is similar.) \ Using such a pen, the `\kk{\manual\beana}\kk' +becomes `\kk{\manual\beanc}\kk', and `{\manual\Aa}' becomes `{\manual\Ab}'. +Furthermore, +\begindisplay +\pickup @pencircle@ xscaled $0.8"pt"$ yscaled $0.2"pt"$ ^{rotated} 30 +\enddisplay +takes that ellipse and rotates it $30^\circ$ counterclockwise, obtaining the nib +`$\vcenter{\hbox{\manual\nibb}}$'; this changes `\kk{\manual\beanc}\kk' into +`\kk{\manual\beand}\kk' and `{\manual\Ab}' into `{\manual\Ac}'. An +enlarged view of the bean shape shows more clearly what is going on: +\displayfig 4a (7pc) +The right-hand example was obtained by eliminating the clause +`yscaled~$0.2"pt"$'; this makes the pen almost razor thin, only +one pixel tall before rotation. + +\exercise Describe the pen shapes defined by +(a)~@pencircle@ xscaled~$0.2"pt"$ yscaled~$0.8"pt"$; +\ (b)~@pencircle@ scaled~$0.8"pt"$ rotated~30; +\ (c)~@pencircle@ xscaled~.25 scaled~$0.8"pt"$. +\answer (a)~An ellipse $0.8\pt$ tall and $0.2\pt$ wide +(`$\,\vcenter{\hbox{\manual\nibc}}\,$'); +\ (b)~a~circle of diameter $0.8\pt$ (rotation doesn't change a circle!); +\ (c)~same as~(a). + +\exercise We've seen many examples of `^@draw@' +used with two or more points. What do you think \MF\ will do +if you ask it to perform the following commands? +\begindisplay +@draw@ $z_1$;\ @draw@ $z_2$; \ @draw@ $z_3$; \ @draw@ $z_4$; + \ @draw@ $z_5$; \ @draw@ $z_6$. +\enddisplay +\answer Six individual points will be drawn, instead of lines or curves. +These points will be drawn with the current pen. However, for technical +reasons explained in Chapter~24, the @draw@ command does its best work when it +is moving the pen; the pixels you get at the endpoints of curves are +not always what you would expect, especially at low resolutions. It is +usually best to say `^@drawdot@' instead of `@draw@' when you are drawing +only ^{one point}. + +\def\hidecoords(#1,#2){\hbox to 0pt{\hss$\scriptstyle(#1,#2)$\hss}} +\setbox0=\vtop{\kern 42pt + \rightline{\vbox{\hbox to 208\apspix{\hidecoords(0,h)\hfil + \hidecoords(w\mkern-2mu,h)} + \kern3pt + \figbox{4b}{208\apspix}{216\apspix}\vbox + \kern-3pt + \hbox to 208\apspix{\hidecoords(0,0)\hfil + \hidecoords(w\mkern-2mu,0)}}\quad}} +\dp0=0pt + +\hangindent-125pt \hangafter4 +\indent\strut\vadjust{\box0}% +Let's turn now to the design of a real letter that has already appeared +many times in this manual, namely the `\thinspace{\manual ^{T}}\thinspace' of +`\MF'. All seven of ^^{METAFONT logo} the distinct letters in `\MF' will +be used to illustrate various ideas as we get into the details of the +language; we might as well start with~`\thinspace{\manual T}\thinspace', +because it occurs twice, and (especially) because it's the simplest. An +enlarged version of this letter is shown at the right of this paragraph, +including the locations of its four key points $(z_1,z_2,z_3,z_4)$ and its +^{bounding box}. Typesetting systems like \TeX\ are based on the +assumption that each character fits in a rectangular ^{box}; we shall +discuss boxes in detail later, but for now we will be content simply to +know that such boundaries do exist.\footnote*{Strictly speaking, the +bounding box doesn't actually have to ``bound'' the black pixels of a +character; for example, the `\thinspace{\manual q}\thinspace' protrudes +slightly below the baseline at point~4, and italic letters frequently +extend rather far to the right of their boxes. However, \TeX\ positions +all characters by lumping boxes together as if they were pieces of metal +type that contain all of the ink.} Numbers $h$ and~$w$ ^^"h" ^^"w" will +have been computed so that the corners of the box are at positions +$(0,0)$, $(0,h)$, $(w,0)$, and~$(w,h)$ as shown. + +\hangindent-125pt +\hangafter\prevgraf \advance\hangafter by -16 % 4+12 (12 lines for the figure) +Each of the letters in `\MF' is drawn with a pen whose nib is an unrotated +ellipse, 90\% as tall as it is wide. In the 10-point size, which is used +for the main text of this book, the pen is $2/3\pt$ wide, so it has +been specified by the command +\begindisplay +\pickup @pencircle@ scaled $2\over3$"pt" yscaled $9\over10$ +\enddisplay +or something equivalent to this. + +We shall assume that a special value `$o$' has been computed so that the +bottom of the vertical stroke in `\thinspace{\manual T}\thinspace' should +descend exactly $o$~pixels below the baseline; ^^"o" this is called the +amount of ``^{overshoot}.'' Given $h$, $w$, and~$o$, it is a simple matter +to define the four key points and to draw the +`\thinspace{\manual T}\thinspace': ^^"top" ^^"lft" ^^"rt" ^^"bot" +\begindisplay +$"top"\,"lft"\,z_1=(0,h)$; \quad $"top"\,"rt"\,z_2=(w,h)$;\cr +$"top"\,z_3=(.5w,h)$; \quad $"bot"\,z_4=(.5w,-o)$;\cr +@draw@ $z_1\to z_2$; \quad @draw@ $z_3\to z_4$.\cr +\enddisplay + +\danger Sometimes it is easier and/or clearer to define the $x$ and~$y$ +^{coordinates} separately. For example, the key points of +the~`\thinspace{\manual j}\thinspace' +could also be specified thus: +\begindisplay +$"lft"\,x_1=0$;&$w-x_2=x_1$;&$x_3=x_4=.5w$;\cr +$"top"\,y_1=h$;&$"bot"\,y_4=-o$;&$y_1=y_2=y_3$.\cr +\enddisplay +The equation $w-x_2=x_1$ expresses the fact that $x_2$ is just as far from +the right edge of the bounding box as $x_1$ is from the left edge. + +\danger What exactly does `"top"\!' mean in a \MF\ equation? If the +currently-picked-up pen extends $l$~pixels to the left of its center, +$r$~pixels to the right, $t$~pixels upward and $b$~downward, then +\begindisplay +$"top"\,z=z+(0,t)$,\kern-1em&$"bot"\,z=z-(0,b)$,\kern-1em& +$"lft"\,z=z-(l,0)$,\kern-1em&$"rt"\,z=z+(r,0)$,\cr +\noalign{\vskip\belowdisplayskip +\vbox{\noindent\strut +when $z$ is a pair of coordinates. But---as the previous paragraph +shows, if you study it carefully---we also have +\strut}\vskip\abovedisplayskip} +$"top"\,y=y+t$,&$"bot"\,y=y-b$,& +$"lft"\,x=x-l$,&$"rt"\,x=x+r$,\cr +\enddisplay +when $x$ and $y$ are single values instead of coordinate pairs. +You shouldn't apply `"top"\!' or `"bot"\!' to $x$~coordinates, +nor `"lft"\!' or `"rt"\!' to $y$~coordinates. + +\dangerexercise True or false: $"top"\,"bot"\,z=z$, whenever $z$ +is a pair of coordinates. +\answer True, for all of the pens discussed so far. But false in general, +since we will see later that pens might extend further upward than +downward; i.e., $t$~might be unequal to~$b$ in the equations for +"top" and "bot". + +\setbox0=\vtop{\kern -12pt + \rightline{\vbox{\hbox to 288\apspix{\hidecoords(0,h)\hfil + \hidecoords(w\mkern-2mu,h)} + \kern3pt + \figbox{4c}{288\apspix}{216\apspix}\vbox + \kern-3pt + \hbox to 288\apspix{\hidecoords(0,0)\hfil + \hidecoords(w\mkern-2mu,0)}}\quad}} +\dp0=0pt +\begingroup\decreasehsize 165pt +\dangerexercise An enlarged \strut\vadjust{\box0}% +picture of \MF's `{\manual h}' shows that it has five key points. Assuming ^^{M} +that special values $ss$ and~"ygap" have been precomputed and that the equations +\begindisplay +$x_1=ss=w-x_5$;\quad$y_3-y_1="ygap"$\cr +\enddisplay +have already been given, what further equations and `@draw@' ^^{METAFONT +logo} commands will complete the specification of this letter? \ (The +value of~$w$ will be greater for~`\thinspace{\manual h}\thinspace' than it was +for~`\thinspace{\manual j}\thinspace'; it +stands for the pixel width of whatever character is currently being drawn.) +\answer $x_2=x_1$; $x_3={1\over2}[x_2,x_4]$; $x_4=x_5$; $"bot"\,y_1=-o$; +$"top"\,y_2=h+o$; $y_4=y_2$; $y_5=y_1$; @draw@ $z_1\to z_2$; +@draw@ $z_2\to z_3$; @draw@ $z_3\to z_4$; @draw@ $z_4\to z_5$. +We will learn later that the four @draw@ commands can be replaced by +\begindisplay +@draw@ $z_1\dashto z_2\dashto z_3\dashto z_4\dashto z_5$; +\enddisplay +in fact, this will make \MF\ run slightly faster. ^^{--} + +\endgroup % end of the diminished \hsize + +\MF's ability to `@draw@' allows it to produce character shapes that are +satisfactory for many applications, but the shapes are inherently limited +by the fact that the simulated pen nib must stay the same through an +entire stroke. Human penpushers are able to get richer effects by +using different amounts of pressure and/or by rotating the pen as they draw. + +We can obtain finer control over the characters we produce if we specify +their outlines, instead of working only with key points that lie somewhere +in the middle. In fact, \MF\ works internally with outlines, and the +computer finds it much easier to fill a region with solid black than to +figure out what pixels are blackened by a moving pen. There's a `^@fill@' +command that does region filling; for example, the solid ^{bean} shape +\displayfig 4d (6.5pc) +can be obtained from our six famous example points by giving the command +\begindisplay +@fill@ $z_5\to z_4\to z_1\to z_3\to z_6\to \cycle$. +\enddisplay +The filled region is essentially what would be cut out by an +infinitely sharp ^{knife} blade if it traced over the given curve while +cutting a piece of thin film. A @draw@ command needs to add thickness to +its curve, because the result would otherwise be invisible; but a @fill@ +command adds no thickness. + +The curve in a @fill@ command must end with `^{cycle}', because an +entire region must be filled. It wouldn't make sense to say, e.g., +`@fill@ $z_1\to z_2$'. The cycle being filled shouldn't cross itself, +either; \MF\ would have lots of trouble trying to figure out how to +obey a command like `@fill@ $z_1\to z_6\to z_3\to z_4\to\cycle$'. + +\dangerexercise Chapter 3 discusses the curve $z_5\to z_4\to z_1\to +z_3\to z_6\to z_5$, which isn't smooth at~$z_5$. Since this curve +doesn't end with `cycle', you can't use it in a @fill@ command. +But it does define a closed region. How can \MF\ be instructed +to fill that region? +\answer Either say `@fill@ $z_5\to z_4\to z_1\to z_3\to z_6\to z_5\to +\cycle$', which doubles point~$z_5$ and abandons smoothness there, +or `@fill@ $z_5\{\curl1\}\to z_4\to z_1\to z_3\to z_6\to +\{\curl1\}\cycle$'. In the latter case you can omit either one of +the ^{curl} specifications, but not both. + +The black ^{triangle} `{\manual\char'170}' that appears in the statement of +exercises in this book was drawn with the command +\begindisplay +@fill@ $z_1\dashto z_2\dashto z_3\dashto\cycle$ +\enddisplay +after appropriate corner points $z_1$, $z_2$, and $z_3$ had been specified. +In this case the outline of the region to be filled was specified in terms +of the symbol `$\dashto$' instead of `$\to$'; ^^{--}^^{..} +this is a convention we haven't discussed before. Each `$\dashto$' +introduces a straight line segment, which is independent of the rest of +^^{polygonal path} +the path that it belongs to; thus it is quite different from `$\to$', which +specifies a possibly curved line segment that connects smoothly with neighboring +points and lines of a path. In this case `$\dashto$' was used so that the +triangular region would have straight edges and sharp corners. We might say +informally that `$\to$' means ``Connect the points with a nice curve,'' +while `$\dashto$' means ``Connect the points with a straight line.'' + +\setbox0=\vtop{\kern -9pt + \rightline{\vbox{\hbox to 180\apspix{\hidecoords(0,h)\hfil + \hidecoords(w\mkern-2mu,h)} + \kern3pt + \figbox{4e}{180\apspix}{225\apspix}\vbox + \kern-3pt + \hbox to 180\apspix{\hidecoords(0,0)\hfil + \hidecoords(w\mkern-2mu,0)}}\quad}} +\dp0=0pt +\begingroup\decreasehsize 111pt + +\danger \strut\vadjust{\box0}% +The corner points $z_1$, $z_2$, and $z_3$ were defined carefully +so that the triangle would be {\sl^{equilateral}}, i.e., so that all three +of its sides would have the same length. Since an equilateral triangle +has $60^\circ$ angles, the following equations did the job: +\begindisplay +$x_1=x_2=w-x_3=s$;\cr +$y_3=.5h$;\cr +$z_1-z_2=(z_3-z_2)$ ^{rotated} 60.\cr +\enddisplay +Here $w$ and $h$ represent the character's width and height, and $s$~is +the distance of the triangle from the left and right edges of the type. + +\endgroup % end of the diminished \hsize + +\danger The @fill@ command has a companion called ^@unfill@, which changes +pixels from black to white inside a given region. For example, the solid +bean shape on the previous page can be changed to +\displayfig 4f (6.5pc) +if we say also `@unfill@ ${1\over4}[z_4,z_2]\to{3\over4}[z_4,z_2]\to\cycle$; +\ @unfill@ ${1\over4}[z_6,z_2]\to{3\over4}[z_6,z_2]\to\cycle$'. +This example shows, incidentally, that \MF\ converts a two-point specification +like `$z_1\to z_2\to\cycle$' into a more-or-less circular path, even though +two points by themselves define only a straight line. + +\dangerexercise Let $z_0$ be the point $(.8[x_1,x_2],.5[y_1,y_4])$, +and introduce six new points by letting $z'_k=.2[z_k,z_0]$ for $k=1,$ 2, +\dots,~6. Explain how to obtain the shape +\displayfig 4g (7.0pc) +in which the interior region is defined by $z'_1\ldots z'_6$ instead of +by $z_1\ldots z_6$. +\answer After the six original points have been defined, say +\begindisplay +@fill@ $z_5\to z_4\to z_1\to z_3\to z_6\to\cycle$;\cr +$z_0=(.8[x_1,x_2],.5[y_1,y_4])$;\cr +@for@ $k=1$ @upto@ 6: $z_k'=.2[z_k,z_0]$; @endfor@\cr +@unfill@ $z_5'\to z_4'\to z_1'\to z_3'\to z_6'\to\cycle$.\cr +\enddisplay + +The ability to fill between outlines makes it possible to pretend that we +have ^{broad-edge pens} that change in direction and pressure as they +glide over the paper, if we consider the separate paths traced out by the +pen's left edge and right edge. For example, the stroke +\displayfig 4h (3.5pc) +can be regarded as drawn by a pen that starts at the left, inclined +at a $30^\circ$ angle; as the pen moves, it turns gradually until its +^^{angle of pen} edge is strictly vertical by the time it reaches the +right end. The pen motion was horizontal at positions 2 and~3. This stroke +was actually obtained by the command +\begindisplay +@fill@ $z_{1l}\to z_{2l}\{"right"\}\to\{"right"\}\,z_{3l}$\cr +$\hskip4em\dashto z_{3r}\{"left"\}\to\{"left"\}\,z_{2r}\to z_{1r}$\cr +$\hskip4em\dashto\cycle$; +\enddisplay +i.e., \MF\ was asked to fill a region bounded by a ``left path'' from +$z_{1l}$ to $z_{2l}$ to $z_{3l}$, followed by a straight line ^^{--} +to~$z_{3r}$, then a reversed ``right path'' from $z_{3r}$ to $z_{2r}$ to +$z_{1r}$, and finally a straight line back to the starting point~$z_{1l}$. + +Key positions of the ``pen'' are represented in this example by sets of +three points, like $(z_{1l},z_1,z_{1r})$, which stand for the pen's left edge, +its midpoint, and its right edge. The midpoint doesn't actually occur in the +specification of the outline, but we'll see examples of its usefulness. +The relationships between such triples of points are established by a +`^"penpos"' command, which states the breadth of the pen and its angle of +inclination at a particular position. For example, positions 1, 2, and~3 +in the stroke above were established by saying +\begindisplay +$\penpos1(1.2"pt",30)$;& +$\penpos2(1.0"pt",45)$;& +$\penpos3(0.8"pt",90)$;\cr +\enddisplay +this made the pen $1.2\pt$ broad and tipped $30^\circ$ with respect to +the horizontal at position~1, etc. In general the idea is to specify +`$\penpos k(b,d)$', +where $k$ is the position number or position name, $b$ is the breadth (in +pixels), and $d$~is the angle (in degrees). Pen angles are measured +counterclockwise from the horizontal. Thus, an angle of~0 makes the right +edge of the pen exactly $b$~pixels to the right of the left edge; an angle +of~90 makes the right pen edge exactly $b$~pixels above the left; an angle +of~$-90$ makes it exactly $b$~pixels below. An angle of 45 makes the right +edge $b/{\sqrt2}$ pixels above and $b/{\sqrt2}$ pixels to the right of the +left edge; an angle of~$-45$ makes it $b/{\sqrt2}$ pixels below and +$b/{\sqrt2}$ to the right. When the pen angle is between $90^\circ$ and +$180^\circ$, the ``right'' edge actually lies to the left of the ``left'' +edge. In terms of ^{compass directions} on a conventional map, an angle +of~$0^\circ$ points due East, while $90^\circ$ points North and $-90^\circ$ +points South. The angle corresponding to Southwest is $-135^\circ$, +also known as $+225^\circ$. + +\exercise What angle corresponds to the direction North-Northwest? +\answer ${1\over2}\bigl["North",{1\over2}["North","West"]\bigr]= +{1\over2}\bigl[90,{1\over2}[90,180]\bigr]={1\over2}[90,135]=112.5$. + +\begingroup \decreasehsize 9pc +\exercise \xdef\circlex{4.\number\exno}% +\rightfig 4i (7pc x 7pc) ^20pt +What are the pen angles at positions 1, 2, 3, and~4 in +the circular shape shown here? [{\sl Hint:\/} Each angle is a multiple +of $30^\circ$. Note that $z_{3r}$ lies to the left of $z_{3l}$.] +\answer $30^\circ$, $60^\circ$, $210^\circ$, and $240^\circ$. Since it's +possible to add or subtract $360^\circ$ without changing the meaning, +the answers $-330^\circ$, $-300^\circ$, $-150^\circ$, and $-120^\circ$ +are also correct. + +\exercise What are the coordinates of $z_{1l}$ and $z_{1r}$ after the +command `$\penpos1(10,-90)$', if $z_1=(25,25)$? +\answer $z_{1l}=(25,30)$, $z_{1r}=(25,20)$. + +\endgroup % end of the diminished \hsize +\danger The statement `$\penpos k(b,d)$' is simply an abbreviation for +two equations, `$z_k={1\over2}[z_{kl},z_{kr}]$' and +`$z_{kr}=z_{kl}+(b,0)$ ^{rotated}~$d\,$'. You might want to use other +equations to define the relationship between $z_{kl}$, $z_k$, and +$z_{kr}$, instead of giving a "penpos" command, if an alternative +formulation turns out to be more convenient. + +After `"penpos"' has specified the relations between three points, we still +don't know exactly where they are; we only know their positions relative +to each other. Another equation or two is needed in order to fix the +horizontal and vertical locations of each triple. For example, the three +"penpos" commands that led to the pen stroke on the previous page were +accompanied by the equations +\begindisplay +$z_1=(0,2"pt")$;&$z_2=(4"pt",0)$;&$x_3=9"pt"$;&$y_{3l}=y_{2r}$; +\enddisplay +these made the information complete. There should be one $x$~equation and +one $y$~equation for each position; or you can use a $z$~equation, which +defines both $x$ and~$y$ simultaneously. + +It's a nuisance to write long-winded @fill@ commands when broad-edge +pens are being simulated in this way, so \MF\ provides a convenient +abbreviation: You can write simply +\begindisplay +^@penstroke@ $z_{1e}\to z_{2e}\{"right"\}\to\{"right"\}z_{3e}$ +\enddisplay +instead of the command `\thinspace@fill@ $z_{1l}\to +z_{2l}\{"right"\}\to\{"right"\}\,z_{3l} \dashto +z_{3r}\{"left"\}\to\{"left"\}\,z_{2r}\to z_{1r}\dashto\cycle$' that was +stated earlier. The letter `$e$' ^^"e" stands for the pen's edge. A @penstroke@ +command fills the region `$p.l\dashto \reverse p.r\dashto\cycle$', where +$p.l$ and~$p.r$ are the left and right paths formed by changing each~`$e$' +into `$l$' or~`$r$', respectively. + +\danger The @penstroke@ abbreviation can be used to draw cyclic paths +as well as ordinary ones. For example, the circle in exercise \circlex\ +was created by saying simply `@penstroke@ $z_{1e}\to z_{2e}\to z_{3e}\to +z_{4e}\to\cycle$'. This type of penstroke essentially expands into +\begindisplay +@fill@ $z_{1r}\to z_{2r}\to z_{3r}\to z_{4r}\to\cycle$;\cr +@unfill@ $z_{1l}\to z_{2l}\to z_{3l}\to z_{4l}\to\cycle$;\cr +\enddisplay +or the operations `@fill@' and `@unfill@' are reversed, if points +$(z_{1r},z_{2r}, z_{3r},z_{4r})$ are on the inside and +$(z_{1l},z_{2l},z_{3l},z_{4l})$ are on the outside. + +\dangerexercise The circle of exercise \circlex\ was actually drawn with +a slightly more complicated @penstroke@ command than just claimed: The +edges of the curve were forced to be vertical at +positions 1 and~3, horizontal at 2 and~4. How did the author do this? +\answer He said `@penstroke@ +$z_{1e}\{"up"\}\to z_{2e}\{"left"\}\to z_{3e}\{"down"\} + \to z_{4e}\{"right"\}\to\cycle$'. + +\setbox0=\vtop{\kern 21pt + \rightline{\vbox{\hbox to 126\apspix{\hidecoords(0,h)\hfil + \hidecoords(w\mkern-2mu,h)} + \kern6pt + \figbox{4j}{126\apspix}{252\apspix}\vbox + \kern-3pt + \hbox to 126\apspix{\hidecoords(0,0)\hfil + \hidecoords(w\mkern-2mu,0)}}\qquad}} +\dp0=0pt + +\hangindent-100pt \hangafter2 +\indent\strut\vadjust{\box0}% +Here's an example of how this new sort of pen can be used to draw a +sans-serif letter `{\manual\IOI}'. As usual, we assume ^^{I} +that two variables, $h$ and~$w$, have been set up to give the height and +width of the character in pixels. We shall also assume that there's a +"stem" parameter, which specifies the nominal pen breadth. The breadth +decreases to .9"stem" in the middle of the stroke, and the +pen angle changes from $15^\circ$ to~$10^\circ$: +\begindisplay +$\penpos1("stem",15)$; \ $\penpos2(.9"stem",12)$;\cr +$\penpos3("stem",10)$; \ $x_1=x_2=x_3=.5w$;\cr +$y_1=h$; \ $y_2=.55h$; \ $y_3=0$;\cr +$x_{2l}:={1\over6}[x_{2l},x_2]$;\cr +@penstroke@ $z_{1e}\to z_{2e}\{down\}\to z_{3e}$.\cr +\enddisplay +Setting $x_1=x_2=x_3=.5w$ centers the stroke; setting $y_1=h$ and $y_3=0$ +makes it sit in the type box, protruding just slightly at the top and bottom. + +The second-last line of this program is something that we haven't seen +before: It resets $x_{2l}$ to a value 1/6 of the way towards the center +of the pen, thereby making the stroke ^{taper} a bit at the left. +The `$:=$' operation is called an {\sl^{assignment}\/}; we shall +^^{:=} study the differences between `$:=$' and~`$=$' in Chapter~10. + +\danger It is important to note that these simulated pens +have a serious limitation compared to the way a real calligrapher's pen +works: The left and right edges of a "penpos"-made pen must never cross, +hence it is necessary to turn the pen when going around a curve. +Consider, for example, the following two curves: +\displayfig 4k (6pc) +The left-hand circle was drawn with a broad-edge pen of fixed breadth, +held at a fixed angle; consequently the left edge of the pen was responsible +for the outer boundary on the left, but the inner boundary on the right. +\ (This curve was produced by saying `\pickup @pencircle@ xscaled~0.8"pt" +rotated~25; @draw@ $z_1\to z_2\to\cycle$'.) \ The right-hand shape +was produced by `$\penpos1(0.8"pt",25)$; $\penpos2(0.8"pt",25)$; +@penstroke@ $z_{1e}\to z_{2e}\to\cycle$'; important chunks of the shape +are missing at the crossover points, because they don't lie on either of +the circles $z_{1l}\to z_{2l}\to\cycle$ or $z_{1r}\to z_{2r}\to\cycle$. + +\danger To conclude this chapter we shall improve the ^{hex} character +{\manual\hexb} of Chapter~2, which is too dark in the middle because it has +been drawn with a pen of uniform thickness. The main trouble with unvarying +pens is that they tend to produce black blotches where two strokes meet, +unless the pens are comparatively thin or unless the strokes are nearly +perpendicular. We want to thin out the lines at the center just enough +to cure the darkness problem, without destroying the illusion that the lines +still seem (at first glance) to have uniform thickness. + +\setbox0=\vtop{\kern 69pt + \rightline{\vbox{\hbox to 200\apspix{\hidecoords(0,h)\hfil + \hidecoords(w\mkern-2mu,h)} + \kern3pt + \figbox{4l}{200\apspix}{100\apspix}\vbox + \kern-3pt + \hbox to 200\apspix{\hidecoords(0,0)\hfil + \hidecoords(w\mkern-2mu,0)}}\quad}} +\dp0=0pt + +\danger \strut\vadjust{\box0}% +It isn't difficult to produce `\thinspace +{\manual\hexe\hexe\hexe\hexe\hexe\hexe\hexe\hexe\hexe\hexe}\thinspace' +instead of `\thinspace +{\manual\hexb\hexb\hexb\hexb\hexb\hexb\hexb\hexb\hexb\hexb}\thinspace' +when we work with dynamic pens: +\begindisplay +\pickup @pencircle@ scaled $b$;\cr +$"top"\,z_1=(0,h)$; \ $"top"\,z_2=(.5w,h)$; \ $"top"\,z_3=(w,h)$;\cr +$"bot"\,z_4=(0,0)$; \ $"bot"\,z_5=(.5w,0)$; \ $"bot"\,z_6=(w,0)$; \ + @draw@ $z_2\to z_5$;\cr +$z_{1'}=.25[z_1,z_6]$; \ $z_{6'}=.75[z_1,z_6]$; \ +$z_{3'}=.25[z_3,z_4]$; \ $z_{4'}=.75[z_3,z_4]$;\cr +$"theta"_1:=\angle(z_6-z_1)+90$;\cr +$"theta"_3:=\angle(z_4-z_3)+90$;\cr +$\penpos{1'}(b,"theta"_1)$; \ $\penpos{6'}(b,"theta"_1)$;\cr +$\penpos{3'}(b,"theta"_3)$; \ $\penpos{4'}(b,"theta"_3)$;\cr +$\penpos7(.6b,"theta"_1)$; \ $\penpos8(.6b,"theta"_3)$;\cr +$z_7=z_8=.5[z_1,z_6]$;\cr +@draw@ $z_1\to z_{1'}$; \ @draw@ $z_{6'}\to z_6$;\cr +@draw@ $z_3\to z_{3'}$; \ @draw@ $z_{4'}\to z_4$;\cr +@penstroke@ $z_{1'e}\{z_{6'}-z_{1'}\}\to z_{7e}\to\{z_{6'}-z_{1'}\}z_{6'e}$;\cr +@penstroke@ $z_{3'e}\{z_{4'}-z_{3'}\}\to z_{8e}\to\{z_{4'}-z_{3'}\}z_{4'e}$.\cr +\enddisplay +Here $b$ is the diameter of the pen at the terminal points; +`^{angle}' computes the direction angle of a given vector. +Adding $90^\circ$ to a direction angle gives a ^{perpendicular} +direction (see the definitions of $"theta"_1$ and~$"theta"_3$). +It isn't necessary to take anything off of the vertical stroke $z_2\to z_5$, +because the two diagonal strokes fill more than the width of the vertical +stroke at the point where they intersect. + +\setbox0=\vtop{\kern -30pt + \rightline{\vbox{\hbox to 200\apspix{\hidecoords(0,h)\hfil + \hidecoords(w\mkern-2mu,h)} + \kern6pt +% \figbox{4m}{200\apspix}{100\apspix}\vbox + \figbox{4m}{200\apspix}{105\apspix}\vbox + \kern0pt + \hbox to 200\apspix{\hidecoords(0,0)\hfil + \hidecoords(w\mkern-2mu,0)}}\quad}} +\dp0=0pt + +\begingroup \decreasehsize 125pt +\dangerexercise \strut\vadjust{\box0}% +Modify the hex character so that its ends are cut +sharply and confined to the bounding box, as shown. +\answer We use angles ^{perpendicular} to $(w,h)$ and $(w,-h)$ at the +diagonal endpoints: +\begindisplay +$x_{1l}=x_{4l}=0$;\cr +$x_2=x_5=.5w$;\cr +$x_{3r}=x_{6r}=w$;\cr +$y_{1r}=y_2=y_{3l}=h$;\cr +$y_{4r}=y_5=y_{6l}=0$;\cr +$z_{1'}=.25[z_1,z_6]$; \ $z_{6'}=.75[z_1,z_6]$;\cr +$theta_1:=\angle(w,-h)+90$;\cr +$\penpos1(b,theta_1)$; \ $\penpos6(b,theta_1)$;\cr +$z_7=.5[z_1,z_6]$; \ $\penpos7(.6b,theta_1)$;\cr +$\penpos{1'}(b,theta_1)$; \ $\penpos{6'}(b,theta_1)$;\cr +@penstroke@ $z_{1e}\to z_{1'e}\{z_{6'}-z_{1'}\}\to z_{7e}\to +\{z_{6'}-z_{1'}\}z_{6'e}\to z_{6e}$;\cr +$z_{3'}=.25[z_3,z_4]$; \ $z_{4'}=.75[z_3,z_4]$;\cr +$theta_3:=\angle(-w,-h)+90$;\cr +$\penpos3(b,theta_3)$; \ $\penpos4(b,theta_3)$;\cr +$z_8=.5[z_1,z_6]$; \ $\penpos8(.6b,theta_3)$;\cr +$\penpos{3'}(b,theta_3)$; \ $\penpos{4'}(b,theta_3)$;\cr +@penstroke@ $z_{3e}\to z_{3'e}\{z_{4'}-z_{3'}\}\to z_{8e}\to +\{z_{4'}-z_{3'}\}z_{4'e}\to z_{4e}$;\cr +$\penpos2(b,0)$; \ $\penpos5(b,0)$; \ @penstroke@ $z_{2e}\to z_{5e}$.\cr +\enddisplay + +\endgroup % end of the diminished \hsize + +\endchapter + +It is very important that the nib be cut ``sharp,'' +and as often as its edge wears blunt it must be resharpened. +It is impossible to make ``clean cut'' strokes with a blunt pen. +\author EDWARD ^{JOHNSTON}, {\sl Writing \& Illuminating, % + \& Lettering\/} (1906) + +\bigskip + +I might compare the high-speed computing machine +to a remarkably large and awkward pencil +which takes a long time to sharpen and +cannot be held in the fingers in the usual manner so that it +gives the illusion of responding to my thoughts, +but is fitted with a rather delicate engine +and will write like a mad thing +provided I am willing to let it dictate pretty much +the subjects on which it writes. +\author R. H. ^{BRUCK}, {\sl Computational Aspects of Certain + Combinatorial Problems\/} (1956) % AMS Symp Appl Math 6, p31 + +\eject + \beginchapter Chapter 5. Running\\\MF + +It's high time now for you to stop reading and to start playing with the +computer, since \MF\ is an interactive system that is best learned by +trial and error. \ (In fact, one of the nicest things about computer graphics +is that errors are often more interesting and more fun than ``successes.'') + +You probably will have to ask somebody how to deal with the idiosyncrasies +of your particular version of the system, even though \MF\ itself works in +essentially the same way on all machines; different computer terminals and +different hardcopy devices make it necessary to have somewhat different +interfaces. In~this chapter we shall assume that you have a computer +terminal with a reasonably high-resolution graphics display; that you have +access to a (possibly low-resolution) output device; and that you can +rather easily get that device to work with newly created fonts. + +OK, are you ready to run the program? First you need to log in, of course; +then start \MF\!, which is usually called ^|mf| for short. Once you've figured +out how to do it, you'll be welcomed by a message something like +$$\def\\{{\rm\ }} % take a wee bit off of the \tt spaces +\vtop{\line{\indent \tt +This\\is\\METAFONT,\\Version\\2.0\\(preloaded\\base=plain 89.11.8)} +\leftline{\indent \tt **}}$$ +The `^|**|' is \MF's way of asking you for an input file name. +% Incidentally, 89.11.8 was Hermann's 71st birthday. + +Now type `|\relax|'---that's ^{backslash}, |r|, |e|, |l|, |a|, |x|---and +hit ^\ (or~whatever stands for ``end-of-line'' on your keyboard). +\MF\ is all geared up for action, ready to make a big font; but you're +saying that it's all right to take things easy, since this is going to +be a real simple run. The backslash means that \MF\ should not read a file, +it should get instructions from the keyboard; the `^|relax|' means +``do nothing.'' + +The machine will respond by typing a single asterisk: `^|*|'. This means +it's ready to accept instructions (not the name of a file). Type the +following, just for fun: +\begintt +drawdot (35,70); showit; +\endtt +and \---don't forget to type the semicolons along with the other +stuff. A more-or-less circular dot should now appear on your screen! And +you should also be prompted with another asterisk. +Type +\begintt +drawdot (65,70); showit; +\endtt +and \, to get another dot. \ (Henceforth we won't keep mentioning +the necessity of \ing after each line of keyboard input.) \ Finally, +type +\begintt +draw (20,40)..(50,25)..(80,40); showit; shipit; end. +\endtt +This draws a curve through three given points, displays the result, +^^|showit| ^^|shipit| ^^|end| +ships it to an output file, and stops. \MF\ should respond with `|[0]|', +meaning that it has shipped out a character whose number is zero, in the +``font'' just made; and it should also tell you that it has created +an output file called `|mfput.2602gf|'. \ (The name ^|mfput| is used when +you haven't specified any better name in response to the ^|**| at the +beginning. The suffix |2602|^|gf| stands for ``^{generic font} at +2602 pixels per inch.'' The data in |mfput.2602gf| can be converted into +fonts suitable for a wide assortment of typographical output devices; +since it doesn't match the font file conventions of any name-brand +manufacturer, we call it generic.) + +This particular file won't make a very interesting font, +because it contains only one character, and because it probably doesn't +have the correct resolution for your output device. However, it does +have the right resolution for hardcopy proofs of characters; your next +step should therefore be to convert the data of |mfput.2602gf| into a +picture, suitable for framing. There should be a program called +^|GFtoDVI| on your computer. Apply it to |mfput.2602gf|, thereby +obtaining a file called |mfput.dvi| ^^|dvi| that can be printed. +Your friendly local computer hackers will tell you how to run +|GFtoDVI| and how to print |mfput.dvi|; then you'll have a marvelous +souvenir of your very first encounter with \MF\!. \looseness=-1 + +\smallskip +Once you have made a complete test run as just described, you will +know how to get through the whole cycle, so you'll be ready to tackle +a more complex project. Our next experiment will therefore be +to work from a file, instead of typing the input online. + +Use your favorite text editor to create a file called |io.mf| that +contains the following 23 lines of text (no more, no less): +$$\halign{\hbox to\parindent{\hfil\sevenrm#\ \ }&#\hfil\cr +1&|mode_setup;|\cr\noalign{^^@mode\_setup@} +2&| em#:=10pt#; cap#:=7pt#;|\cr +3&| thin#:=1/3pt#; thick#:=5/6pt#;|\cr +4&| o#:=1/5pt#;|\cr +5&|define_pixels(em,cap);|\cr +6&|define_blacker_pixels(thin,thick);|\cr +7&|define_corrected_pixels(o);|\cr +8&| curve_sidebar=round 1/18em;|\cr +9&|beginchar("O",0.8em#,cap#,0); "The letter O";|\cr +10&| penpos1(thick,10); penpos2(.1[thin,thick],90-10);|\cr +11&| penpos3(thick,180+10); penpos4(thin,270-10);|\cr +12&| x1l=w-x3l=curve_sidebar; x2=x4=.5w;|\cr +13&| y1=.49h; y2l=-o; y3=.51h; y4l=h+o;|\cr +14&| penstroke z1e{down}..z2e{right}|\cr +15&| ..z3e{up}..z4e{left}..cycle;|\cr +16&| penlabels(1,2,3,4); endchar;|\cr +17&|def test_I(expr code,trial_stem,trial_width) =|\cr +18&| stem#:=trial_stem*pt#; define_blacker_pixels(stem);|\cr +19&| beginchar(code,trial_width*em#,cap#,0); "The letter I";|\cr +20&| penpos1(stem,15); penpos2(.9stem,12); penpos3(stem,10);|\cr +21&| x1=x2=x3=.5w; y1=h; y2=.55h; y3=0; x2l:=1/6[x2l,x2];|\cr +22&| penstroke z1e..z2e{down}..z3e;|\cr +23&| penlabels(1,2,3); endchar; enddef;|\cr}$$ +(But don't type the numbers at the left of these lines; they're +only for reference.) + +This example file is dedicated to ^{Io}, the Greek goddess of input +and output. It's a trifle long, but you'll be able to get worthwhile +experience by typing it; so go ahead and type it now. For your own +good. And think about what you're typing, as you go; the example +introduces several important features of \MF\ that you can learn +as you're creating the file. + +Here's a brief explanation of what you've just typed: Line~1 contains a +command that usually appears near the beginning of every \MF\ file; +it tells the computer to get ready to work in whatever ``mode'' is +currently desired. \ (A file like |io.mf| can be used to generate +proofsheets as well as to make fonts for a variety of devices at a +variety of magnifications, and `@mode\_setup@' is what adapts \MF\ +to the task at hand.) \ Lines 2--8 define parameters that will be used +to draw the letters in the font. Lines 9--16 give a complete program +for the letter `O'; and lines 17--23 give a program that will draw +the letter~`I' in a number of related ways. + +It all looks pretty frightening at first glance, but a closer look +shows that Io is not so mysterious once we penetrate her disguise. +Let's spend a few minutes studying the file in more detail. + +Lines 2--4 define dimensions that are independent of the mode; the `|#|' +^^{sharpsign} signs are meant to imply ``sharp'' or ``true'' ^{units of +measure}, which remain the same whether we are making a font at high or +low resolution. For example, one `|pt#|' is a true printer's point, one +72.27th of an inch. This is quite different from the `^"pt"' we have +discussed in previous chapters, because `"pt"' is the number of pixels +that happen to correspond to a printer's point when the current resolution +is taken into account. The value of `|pt#|' never changes, but +@mode\_setup@ establishes the appropriate value of `"pt"'. + +The ^{assignments} `|em#:=10pt#|' and `|cap#:=7pt#|' in line~2 mean that +the Io font has two parameters, called "em" and "cap", whose mode-independent +values are 10 and~7 points, respectively. The statement ^^@define\_pixels@ +`|define_pixels(em,cap)|' on line~5 converts these values into pixel +units. For example, if we are working at the comparatively low resolution +of 3~pixels per~pt, the values of "em" and "cap" after the computer has +performed the instructions on line~5 will be $"em"=30$ and $"cap"=21$. +\ (We will see later that the widths of characters in this font are +expressed in terms of ems, and that "cap" is the height of the capital +letters. A change to line~2 will therefore affect the widths and/or heights +of all the letters.) + +Similarly, the Io font has parameters called "thin" and "thick", defined +on line~3 and converted to pixel units in line~6. These are used to control +the breadth of a simulated pen when it draws the letter~O. Experience has +shown that \MF\ produces better results on certain output devices if +pixel-oriented pens are made slightly broader than the true dimensions would +imply, because black pixels sometimes tend to ``burn off'' in the process +of printing. The command on line~6, `|define_blacker_pixels|', +^^@define\_blacker\_pixels@ adds a correction based on the device for which +the font is being prepared. For example, if the resolution is 3~pixels +per point, the value of "thin" when converted from true units to pixels +by @define\_pixels@ would be~1, but @define\_blacker\_pixels@ might set +"thin" to a value closer to~2. + +The `|o|' parameter ^^"o" on line 4 represents the amount by which curves will +^{overshoot} their boundaries. This is converted to pixels in yet another +way on line~7, so as to avoid yet another problem that arises in low-resolution +printing. The author apologizes for letting such real-world considerations +intrude into a textbook example; let's not get bogged down in fussy details +now, since these refinements will be explained in Chapter~11 after we have +mastered the basics. + +For now, the important point is simply that a typeface +design usually involves parameters that represent physical lengths. The +true, ``sharped'' forms of these parameters need to be converted to +``unsharped'' pixel-oriented quantities, and best results are obtained when +such conversions are done carefully. After \MF\ has obeyed line~7 of the +example, the pixel-oriented parameters "em", "cap", "thin", "thick", +and~"o" are ready to be used as we draw letters of the font. + +Line 8 defines a quantity called "curve\_sidebar" ^^{sidebar} that will +measure the distance of the left and right edges of the `O' from the +bounding box. It is computed by ^{rounding} ${1\over18}"em"$ to the nearest +integer number of pixels. For example, if $"em"=30$ then ${30\over18}= +{5\over3}$ yields the rounded value $"curve\_sidebar"=2$; there will be +two all-white columns of pixels at the left and right of the `O', +when we work at this particular resolution. + +Before we go any further, we ought to discuss the strange collection +of words and pseudo-words in the file |io.mf|. Which of the terms +`|mode_setup|', `|em|', `|curve_sidebar|' and so forth are part of +the \MF\ language, and which of them are made up specifically for +the Io example? Well, it turns out that almost {\sl nothing\/} in this +example is written in the pure \MF\ language that the computer understands! +\MF\ is really a low-level language that has been designed to allow easy +adaptation to many different styles of programming, and |io.mf| +illustrates just one of countless ways to use it. Most of the terms +in |io.mf| are conventions of ``^{plain} \MF\!,'' which is a collection +of subroutines found in Appendix~B\null. \MF's primitive capabilities are +not meant to be used directly, because that would force a particular style +on all users. A ``base file'' is generally loaded into the computer +at the beginning of a run, so that a standard set of conventions is +readily available. \MF's welcoming message, quoted at the +beginning of this chapter, says `|preloaded| |base=plain|'; it +means that the primitive \MF\ language has been extended to include the +features of the plain base file. This book is not only about \MF; it also +explains how to use the conventions of \MF's plain base. Similarly, {\sl +The \TeX book\/} describes a standard extension of \TeX\ called ``plain +\TeX\ format''; ^^{TeX} the ``plain'' extensions of \TeX\ and \MF\ are +completely analogous to each other. + +The notions of @mode\_setup@, @define\_pixels@, @beginchar@, "penpos", +and many other things found in |io.mf| are aspects +of plain \MF\ but they are not hardwired into \MF\ itself. Appendix~B +defines all of these things, as well as the relations between ``sharped'' +and ``unsharped'' variables. Even the fact that $z_1$ stands for +$(x_1,y_1)$ is defined in Appendix~B\null; \MF\ does not have this built~in. +You are free to define even fancier bases as you gain more experience, +but the plain base is a suitable starting point for a novice. + +\danger If you have important applications that make use of a different +base file, it's possible to create a version of \MF\ that has any desired +base preloaded. Such a program is generally called by a special name, +since the nickname `^|mf|' is reserved for the version that includes the +standard plain base assumed in this book. For example, the author has made +a special version called `^|cmmf|' just for the ^{Computer Modern} typefaces +he has been developing, so that the Computer Modern base file does not +have to be loaded each time he makes a new experiment. + +\danger There's a simple way to change the base file from the one that has +been preloaded: If the first character you type in response to `^|**|' is +an ^{ampersand} (\thinspace`|&|'\thinspace), \MF\ will replace its memory +with a specified base file before proceeding. If, for example, there is a +base file called `|cm.base|' but not a special program called `|cmmf|', +you can substitute the Computer Modern base for the plain base in |mf| by +typing `|&cm|' at the very beginning of a run. If you are working with a +program that doesn't have the plain base preloaded, the first experiment +in this chapter won't work as described, but you can do it by starting +with `|&plain \relax|' instead of just `|\relax|'. These conventions are +exactly the same as those of \TeX. + +Our Ionian example uses the following words that are not part of plain +\MF: "em", "cap", "thin", "thick", "o", "curve\_sidebar", "test\_I", "code", +"trial\_stem", "trial\_width", and "stem". If you change these to some other +words or symbols---for example, if you replace `|thin|' and `|thick|' by +`|t|' and `|T|' respectively, in lines 3, 6, 10, and~11---the results will +be unchanged, unless your substitutions just happen to clash with something +that plain \MF\ has already pre\"empted. In general, the best policy is to +choose descriptive terms for the quantities in your programs, since they +are not likely to conflict with reserved pseudo-words like "penpos" and +@endchar@. + +We have already noted that lines 9--16 of the file represent a program +for the letter `O'. The main part of this program, in lines 10--15, +uses the ideas of Chapter~4, but we haven't seen the stuff in lines 9 +and~16 before. Plain \MF\ makes it convenient to define letters by starting +each one with +\begindisplay +$@beginchar@\kern1pt($\, \, \, \);^^@beginchar@ +\enddisplay +here \ is either a quoted single character like |"O"| or a number that +represents the character's position in the final font. The other three +quantities \, \, and \ say how big the ^{bounding box} +is, so that typesetting systems like \TeX\ will be able to use the character. +These three dimensions must be given in device-independent units, i.e., +in ``^{sharped}'' form. + +\exercise What are the height and width of the bounding box described +in the @beginchar@ command on line~9 of |io.mf|, given the parameter +values defined on line~2? Give your answer in terms of printer's points. +\answer The width is |0.8em#|, and an |em#| is 10 true points, so the +box will be exactly $8\pt$ wide in device-independent units. The +height will be $7\pt$. \ (And the depth below the baseline will be $0\pt$.) + +Each @beginchar@ operation assigns values to special variables called +$w$, $h$, and~$d$, ^^"w" ^^"h" ^^"d" which represent the respective +width, height, and depth of the current character's bounding box, +^{rounded} to the nearest integer number of pixels. Our example file +uses $w$ and~$h$ to help establish the locations of several pen positions +(see lines 12, 13, and~21 of |io.mf|). + +\exercise Continuing the previous exercise, what will be the values of +$w$ and~$h$ if there are exactly 3.6 pixels per point? +\answer $8\times3.6=28.8$ rounds to the value $w=29$; similarly, $h=25$. +\ (And $d=0$.) + +There's a quoted phrase |"The| |letter| |O"| at the end of line~9; this is +simply a title that will be used in printouts. + +The `|endchar|' ^^@endchar@ on line 16 finishes the character that was +begun on line~9, by writing it to an output file and possibly displaying +it on your screen. We will want +to see the positions of the control points $z_1$, $z_2$, +$z_3$, and~$z_4$ that are used in its design, together with the auxiliary +points $(z_{1l},z_{2l},z_{3l},z_{4l})$ and $(z_{1r},z_{2r},z_{3r},z_{4r})$ +that come with the "penpos" conventions; the statement `|penlabels(1,2,3,4)|' +^^"penlabels" takes care of labeling these points on the proofsheets. + +So much for the letter O. Lines 17--23 are analogous to what we've seen +before, except that there's a new wrinkle: They contain a little program +^^@def@ enclosed by `|def...enddef|', which means that a +{\sl^{subroutine}\/} is being defined. In other words, those lines set up +a whole bunch of \MF\ commands that we will want to execute several times +with minor variations. The subroutine is called "test\_I" and it has three +parameters called "code", "trial\_stem", and "trial\_width" (see line~17). +The idea is that we'll want to draw several different versions of an `I', +having different stem widths and character widths; but we want to type the +program only once. Line~18 defines "stem"\0 and "stem", given a value of +"trial\_stem"; and lines 19--23 complete the program for the letter~I +(copying it from Chapter~4). + +\smallskip +Oops---we've been talking much too long about |io.mf|. It's time to stop +rambling and to begin Experiment~2 in earnest, because it will be much +more fun to see what the computer actually does with that file. + +Are you brave enough to try Experiment 2? Sure. +Get \MF\ going again, but this time when the machine says `^|**|' you should +say `|io|', since that's the name of the file you have prepared so +laboriously. \ (The file could also be specified by giving its full name +`|io.mf|', but \MF\ automatically adds `|.mf|' ^^|mf| ^^{file names} when +no suffix has been given explicitly.) + +If all goes well, the computer should now flash its lights a bit +and---presto---a big `{\manual\IOO}' should be drawn on your screen. +But if your luck is as good as the author's, something will probably go wrong +the first time, most likely because of a typographic error in the file. +A \MF\ program contains lots of data with comparatively little redundancy, +so a single error can make a drastic change in the meaning. Check that +you've typed everything perfectly: Be sure to notice the difference between +the letter~`|l|' and the numeral~`|1|' (especially in line~12, where it +says `|x1l|', not `|x11|' or~`|xll|'); be sure to distinguish between +the letter~`|O|' and the numeral~`|0|' (especially in line~9); be sure to +type the ``underline'' characters in words like `|mode_setup|'. We'll see +later that \MF\ can recover gracefully from most errors, but your job for +now is to make sure that you've got |io.mf| correct. + +Once you have a working file, the computer will draw you an `{\manual\IOO}' +and it will also say something like this: +\begintt +(io.mf +The letter O [79]) +* +\endtt +What does this mean? Well, `|(io.mf|' means that it has started to read your +file, and `|The| |letter|~|O|' was printed when the title was found in +line~9. Then when \MF\ got to the |endchar| on line~16, it said +`|[79]|' to tell you that it had just output character number~79. +\ (This is the ^{ASCII} code for the letter~|O|; Appendix~C lists all +of these codes, if you need to know them.) The `|)|' after `|[79]|' +means that \MF\ subsequently finished reading the file, and the `^|*|' +means that it wants another instruction. + +Hmmm. The file contains programs for both I and O; why did we get only +an~O? Answer: Because lines 17--23 simply define the subroutine "test\_I"; +they don't actually {\sl do\/} anything with that subroutine. We need to +activate "test\_I" if we're going to see what it does. So let's type +\begintt +test_I("I",5/6,1/3); +\endtt +this invokes the subroutine, with $"code"=\null$|"I"|, +$"trial\_stem"={5\over6}$, and $"trial\_width"={1\over3}$. The computer will +now draw an~I corresponding to these values,\footnote*{Unless, of course, +there was a typing error in lines 17--23, where "test\_I" is defined.} and +it will prompt us for another command. + +It's time to type `^|end|' now, after which \MF\ should tell us that it has +completed this run and made an output file called `|io.2602gf|'. Running this +file through ^|GFtoDVI| as in Experiment~1 will produce two proofsheets, +showing the `{\manual\IOO}' and the `{\manual\IOI}' we have created. +The output won't be shown here, but you can see the results by doing +the experiment personally. + +Look at those proofsheets now, because they provide instructive examples +of the simulated broad-edge pen constructions introduced in Chapter~4. +Compare the `{\manual\IOO}' with the program that drew it: Notice that +the $\penpos2$ in line~10 makes the curve slightly thicker at the ^^"penpos" +bottom than at the top; that the equation `$x_{1l}=w-x_{3l}="curve\_sidebar"$' +in line~12 makes the right edge of the curve as far from the right of the +bounding box as the left edge is from the left; that line~13 places point~1 +slightly lower than point~3. The proofsheet for `{\manual\IOI}' should look +very much like the corresponding illustration near the end of Chapter~4, +but it will be somewhat larger. + +\danger Your proof copy of the `{\manual\IOO}' should show twelve dots +for key points; but only ten of them will be labeled, because there isn't +room enough to put labels on points 2 and~4. The missing ^{labels} usually +^^{overflow labels} appear in the upper right corner, where it might say, e.g., +`|4|~|=|~|4l|~|+|~|(-1,-5.9)|'; this +means that point $z_4$ is one pixel to the left and 5.9 pixels down +from point~$z_{4l}$, which is labeled. \ (Some implementations omit this +information, because there isn't always room for it.) + +The proofsheets obtained in Experiment~2 show the key points and the +bounding boxes, but this extra information can interfere with our +perception of the character shape itself. There's a simple way to +get proofs that allow a viewer to criticize the results from an aesthetic +rather than a logical standpoint; the creation of such proofs will be the +goal of our next experiment. + +Here's how to do Experiment~3: Start \MF\ as usual, then type +\begintt +\mode=smoke; input io +\endtt +in response to the `^|**|'. This will input file |io.mf| again, +after establishing ``smoke'' mode. \ (As in Experiment~1, the command line +begins with `|\|' so that the computer knows you aren't starting with +the name of a file.) \ Then complete the run exactly ^^{backslash} +as in Experiment~2, by typing `|test_I("I",5/6,1/3);| |end|'; +and apply |GFtoDVI| to the resulting file |io.2602gf|. + +This time the proofsheets will contain the same characters as before, but +they will be darker and without labeled points. The bounding boxes will +be indicated only by small markings at the corners; you can put these +boxes next to each other and tack the results up on the wall, then stand +back to see how the characters will look when set by a high-resolution +typesetter. \ (This way of working is called ^"smoke" mode because it's +analogous to the ``smoke proofs'' that punch-cutters traditionally used to +test their handiwork. They held the newly cut type over a candle flame so +that it would be covered with carbon; then they pressed it on paper to +make a clean impression of the character, in order to see whether changes +were needed.) + +\danger Incidentally, many systems allow you to invoke \MF\ by typing +a one-line command like `|mf|~|io|' in the case of Experiment~2; you +don't have to wait for the `|**|' before giving a file name. Similarly, +the one-liners `|mf|~|\relax|' and `|mf|~|\mode=smoke;| |input|~|io|' can be +used on many systems at the beginning of Experiments 1 and~3. You might want +to try this, to see if it works on your computer; or you might ask +somebody if there's a similar shortcut. + +Experiments 1, 2, and 3 have demonstrated how to make proof drawings of +test characters, but they don't actually produce new fonts that can be +used in typesetting. For this, we move onward to Experiment~4, in which +we put ourselves in the position of a person who is just starting to +design a new typeface. Let's imagine that we're happy with the~O of +|io.mf|, and that we want a ``sans serif'' I in the general style produced +by "test\_I", but we aren't sure about how thick the stem of the~I +should be in order to make it blend properly with the~O. Moreover, we aren't +sure how much white space to leave at the sides of the~I. So~we want to do +some typesetting experiments, using a sequence of different I's. + +The ideal way to do this would be to produce a high-resolution test font and to +view the output at its true size. But this may be too expensive, because fine +printing equipment is usually available only for large production runs. +The next-best alternative is to use a low-resolution printer but to magnify +the output, so that the resolution is effectively increased. We shall adopt +the latter strategy, because it gives us a chance to learn about +^{magnification} as well as fontmaking. + +After starting \MF\ again, you can begin Experiment 4 by typing +\begintt +\mode=localfont; mag=4; input io +\endtt +in response to the `|**|'. The ^{plain base} at your installation is supposed +to recognize ^|localfont| as the name of the mode that makes fonts for your +``standard'' output device. The equation `|mag=4|' means that this run will +produce a font that is magnified fourfold; i.e., the results will be +4~times bigger than usual. + +The computer will read |io.mf| as before, but this time it won't display an~`O'; +characters are normally not displayed in fontmaking modes, because we usually +want the computer to run as fast as possible when it's generating a font +that has already been designed. All you'll see is `|(io.mf| |[79])|', +followed by~`^|*|'. Now the fun starts: You should type +\begintt +code=100; +for s=7 upto 10: + for w=5 upto 8: + test_I(incr code,s/10,w/20); +endfor endfor end. +\endtt +(Here `^|upto|' must be typed as a single word.) \ We'll learn about +repeating things with `^|for||...|^|endfor|' in Chapter~19. This little +program produces 16 versions of the letter~I, with stem widths of +$7\over10$, $8\over10$, $9\over10$, and~${10\over10}\pt$, and with +character widths of $5\over20$, $6\over20$, $7\over20$, and~${8\over20}\, +\rm em$. The sixteen trial characters will appear in positions 101 through~116 +of the font; it turns out that these are the ^{ASCII} codes for lowercase +letters |e| through~|t| inclusive. \ (Other codes would have been used if +`|code|' had been started at a value different from~100. The construction +`|incr|~|code|' increases the value of |code| by~1 and produces the new value; +thus, each use of |test_I| has a different code number.) ^^"incr" + +This run of \MF\ will not only produce a generic font |io.nnngf|, it will also +create a file called |io.tfm|, the ``^{font metric file}'' that tells +^^{output of METAFONT} ^^|tfm| +typesetting systems like \TeX\ how to make use of the new font. The remaining +part of Experiment~4 will be to put \TeX\ to work: We shall make some test +patterns from the new font, in order to determine which `I' is best. + +You may need to ask a local system wizard for help at this point, because +it may be necessary to move the file |io.tfm| to some special place where +\TeX\ and the other typesetting software can find it. Furthermore, you'll +need to run a program that converts |io.nnngf| to the font format used by your +local output device. But with luck, these will both be fairly simple +operations, and a new font called `|io|' will effectively be installed +on your system. This font will contain seventeen letters, namely an |O| and +sixteen |I|'s, where the |I|'s happen to be in the positions normally occupied +by |e|, |f|, \dots,~|t|. Furthermore, the font will be magnified fourfold. + +\danger The magnification of the font will be reflected in its file name. +For example, if "localfont" mode is for a device with 200 pixels per inch, +the |io| font at 4$\times$ magnification will be called `|io.800gf|'. + +You can use \TeX\ to typeset from this font like any other, but for the +purposes of Experiment~4 it's best to use a special \TeX\ package that has +been specifically designed for font testing. All you need to do is to +run \TeX---which is just like running \MF\!, except that you call it `|tex|' +instead of `|mf|'; and you simply type `^|testfont|' in reply to \TeX's +`|**|'. \ (The |testfont| routine should be available on your system; if +not, you or somebody else can type it in, by copying the relevant material +from Appendix~H\null.) \ You will then be asked for the name of the font +you wish to test. Type +\begintt +io scaled 4000 +\endtt +(which means the |io| font magnified by 4, in \TeX's jargon), +since this is what \MF\ just created. The machine will now ask you for +a test command, and you should reply +\begintt +\mixture +\endtt +to get the ``^{mixture}'' test. \ (Don't forget the ^{backslash}.) \ +You'll be asked for a ^{background letter}, a starting letter, and an +ending letter; type `|O|', `|e|', and `|t|', respectively. This will +produce sixteen lines of typeset output, in which the first line contains +a mixture of |O| with~|e|, the second contains a mixture of |O|~with~|f|, +and so on. To complete Experiment~4, type `|\end|' to \TeX, and print the +file |testfont.dvi| ^^|dvi| that \TeX\ gives you. + +\setbox0=\hbox{\kern.5pt I\kern.5pt} \def\\{\copy0} +If all goes well, you'll have sixteen lines that say `O\\OO\\\\OOO\\\\\\O\\', +but with a different I on each line. In order to choose the line that looks +best, without being influenced by neighboring lines, it's convenient to take +two sheets of blank paper and use them to mask out all of the lines +except the one you're studying. Caution: These letters are four times +larger than the size at which the final font is meant to be viewed, +so you should look at the samples from afar. Xerographic reductions may +introduce distortions that will give misleading results. Sometimes when +you stare at things like this too closely, they all look wrong, or +they all look right; first impressions are usually more significant +than the results of logical reflection. At any rate, you should be able +to come up with an informed judgment about what values to use for the +stem width and the character width of a decent `I'; these can then be +incorporated into the program, the `|def|' and `|enddef|' parts of +|io.mf| can be removed, and you can go on to design other characters +that go with your I and~O. Furthermore you can always go back and make +editorial changes after you see your letters in more contexts. + +\ddangerexercise The goddess Io was known in Egypt as ^{Isis}. +Design an `{\manual\IOS}' for her. +\answer Here's one way, using a variable "slab" to control the +\rightfig A5a ({200\apspix} x 252\apspix) ^-71pt +^^{S} pen breadth at the ends of the stroke: +\begintt +slab#:=.8pt#; define_blacker_pixels(slab); +beginchar("S",5/9em#,cap#,0); "The letter S"; +penpos1(slab,70); penpos2(.5slab,80); +penpos3(.5[slab,thick],200); penpos5(.5[slab,thick],210); +penpos6(.7slab,80); +penpos7(.25[slab,thick],72); +x1=x5; y1r=.94h+o; +x2=x4=x6=.5w; y2r=h+o; y4=.54h; y6l=-o; +x3r=.04em; y3=.5[y4,y2]; +x5l=w-.03em; y5=.5[y4,y6]; +.5[x7l,x7]=.04em; y7l=.12h-o; +path trial; trial=z3{down}..z4..{down}z5; +pair dz; dz=direction 1 of trial; +penpos4(thick,angle dz-90); +penstroke z1e..z2e{left}..z3e{down} + ..z4e{dz}..z5e{down}..z6e{left}..z7e; +penlabels(1,2,3,4,5,6,7); endchar; +\endtt +Notice that the pen angle at point 4 has been found by letting \MF\ +^^{direction} construct a ^{trial path} through the center points, +then using the ^{perpendicular} direction. The letters work reasonably +well at their true size: `{\manual\IOS\IOO} {\manual\IOI\IOO} +{\manual\IOI\IOS} {\manual\IOI\IOS\IOI\IOS}.' + +Well, this isn't a book about type design; the example of |io.mf| is +simply intended to illustrate how a type designer might want to operate, +and to provide a run-through of the complete process from design of +type to its use in a document. We must go back now to the world of +computerese, and study a few more practical details about the use of \MF\!. + +This has been a long chapter, but take heart: There's only one more +experiment to do, and then you will know enough about \MF\ to run it +fearlessly by yourself forever after. The only thing you are still missing +is some information about how to cope with error messages. Sometimes +\MF\ stops and asks you what to do next. Indeed, this may have already +happened, and you may have panicked. + +Error messages can be terrifying when you aren't prepared for them; +but they can be fun when you have the right attitude. Just remember that +you really haven't hurt the computer's feelings, and that nobody will +hold the errors against you. Then you'll find that running \MF\ might +actually be a creative experience instead of something to dread. + +The first step in Experiment 5 is to plant some intentional mistakes +in the input file. Make a copy of |io.mf| and call it |badio.mf|; then +change line~1 of |badio.mf| to +\begintt +mode setup; % an intentional error! +\endtt +(thereby omitting the underline character in |mode_setup|). +Also change the first semicolon (\thinspace`|;|'\thinspace) on line~2 +to a colon (\thinspace`|:|'\thinspace); +change `|thick,10|' to `|thick,l0|' on line~10 (i.e., replace the numeral~`|1|' +by the letter~`|l|'\thinspace); and change `|thin|' to `|thinn|' on line~11. +These four changes introduce typical typographic errors, and it will be +instructive to see if they lead to any disastrous consequences. + +Now start \MF\ up again; but instead of cooperating with the computer, type +`|mumble|' in reply to the~`|**|'. \ (As long as you're going to make +intentional mistakes, you might as well make some dillies.) \ +\MF\ will say that it can't find any file called |mumble.mf|, +and it will ask you for another name. Just hit \ this time; +you'll see that you had better give the name of a real file. +So type `|badio|' and wait for \MF\ to find one of the {\sl faux pas\/} +in that messed-up travesty. + +Ah yes, the machine will soon stop, after typing something like this: +\begintt +>> mode.setup +! Isolated expression. + + ; +l.1 mode setup; + % an intentional error! +? +\endtt +\MF\ begins its error messages with `|!|', and it sometimes precedes them +with one or two related mathematical expressions that are displayed on +lines starting with `^|>>|'. Each error message is also followed by lines +of context that show what the computer was reading at the time of the +error. Such context lines occur in pairs; the top line of the pair (e.g., +`|mode| |setup;|'\thinspace) shows what \MF\ has looked at so far, and +where it came from (`|l.1|', i.e., line number~1); the bottom line (here +`|%|~|an| |intentional| |error!|'\thinspace) shows what \MF\ has yet to +read. In this case there are two pairs of context lines; the top pair +refers to a semicolon that \MF\ has read once but will be reading again, +because it didn't belong with the preceding material. + +You don't have to take out pencil and paper in order to write down the +error messages that you get before they disappear from view, since \MF\ +always writes a ``^{transcript}'' or ``^{log file}'' that records what +happened during each session. For example, you should now have a file +called |io.log| containing the transcript of Experiment~4, as well as a file +|mfput.log| that contains the transcript of Experiment~1. \ (The old +transcript of Experiment~2 was probably overwritten when you did +Experiment~3, and again when you did Experiment~4, because all three +transcripts were called |io.log|.) \ At the end of Experiment~5 you'll +have a file |badio.log| that will serve as a helpful reminder of +what errors need to be fixed up. + +The `^|?|' that appears after the context display means that \MF\ wants +advice about what to do next. If you've never seen an error message before, +or if you've forgotten what sort of response is expected, you can type +`|?|' now (go ahead and try it!); \MF\ will respond as follows: +\begintt +Type to proceed, S to scroll future error messages, +R to run without stopping, Q to run quietly, +I to insert something, E to edit your file, +1 or ... or 9 to ignore the next 1 to 9 tokens of input, +H for help, X to quit. +\endtt +This is your menu of options. You may choose to continue in various ways: + +\smallskip\item{1.} +Simply type \. \MF\ will resume its processing, after +attempting to recover from the error as best it can. + +\smallbreak\item{2.} Type `|S|'. \MF\ will proceed without +pausing for instructions if further errors arise. Subsequent error messages +will flash by on your terminal, possibly faster than you can read them, and +they will appear in your log file where you can scrutinize them at your +leisure. Thus, `|S|'~is sort of like typing \ to every message. + +\smallbreak\item{3.} Type `|R|'. This is like `|S|' but even stronger, +since it tells \MF\ not to stop for any reason, not even if a file name +can't be found. + +\smallbreak\item{4.} Type `|Q|'. This is like `|R|' but even more so, +since it tells \MF\ not only to proceed without stopping but also to +suppress all further output to your terminal. It is a fast, but somewhat +reckless, way to proceed (intended for running \MF\ with no operator in +attendance). + +\smallbreak\item{5.} Type `|I|', followed by some text that you want to +insert. \MF\ will read this text before encountering what it +would ordinarily see ^^{inserting text online} +^^{online interaction, see interaction} ^^{interacting with MF} +next. + +\smallbreak\item{6.} Type a small number (less than 100). \MF\ will +delete this many ^{tokens} from whatever it is +about to read next, and it will pause again to give you another chance to +look things over. ^^{deleting tokens} +\ (A~``token'' is a name, number, or symbol that \MF\ reads as a unit; +e.g., `|mode|' and `|setup|' and `|;|' are the first three tokens +of |badio.mf|, but `|mode_setup|' is the first token of |io.mf|. +Chapter~6 explains this concept precisely.) + +\smallbreak\item{7.} Type `|H|'. This is what you should do now and whenever +you are faced with an error message that you haven't seen for a~while. \MF\ +has two messages built in for each perceived error: a formal one and an +informal one. The formal message is printed first (e.g., `|!|~|Isolated| +|expression.|'\thinspace); the informal one is printed if you request +more help by typing `|H|', and it also appears in your log file if you +are scrolling error messages. The informal message tries to complement the +formal one by explaining what \MF\ thinks the trouble is, and often +by suggesting a strategy for recouping your losses.^^{help messages} + +\smallbreak\item{8.} Type `|X|'. This stands for ``exit.'' It causes \MF\ +to stop working on your job, after putting the finishing touches on your +|log| file and on any characters that have already been output to your |gf| +and/or |tfm| files. The current (incomplete) character will not be output. + +\smallbreak\item{9.} Type `|E|'. This is like `|X|', but it also prepares +the computer to edit the file that \MF\ is currently reading, at the +current position, so that you can conveniently make a change before +trying again. + +\smallbreak\noindent +After you type `|H|' (or `|h|', which also works), you'll get a message +that tries to explain the current problem: The mathematical quantity just +read by \MF\ (i.e., |mode.setup|) was not followed by `|=|' or `|:=|', so +there was nothing for the computer to do with it. Chapter~6 explains that +a ^{space} between tokens (e.g., `|mode|~|setup|'\thinspace) is equivalent to +a ^{period} between tokens (e.g., `|mode.setup|'\thinspace). The correct +spelling `|mode_setup|' would be recognized as a preloaded subroutine of +plain \MF\!, but plain \MF\ doesn't have any built-in meaning for +|mode.setup|. Hence |mode.setup| appears as a sort of orphan, and \MF\ +realizes that something is amiss. + +In this case, it's OK to go ahead and type \, because we really +don't need to do the operations of @mode\_setup@ when no special mode +has been selected. \MF\ will continue by forgetting the isolated expression, +and it will ignore the rest of line~1 because everything after a +^^{percent} `|%|'~sign is always ignored. \ (This is another thing that +will be explained in Chapter~6; it's a handy way to put ^{comments} +into your \MF\ programs.) \ The changes that were made to line~1 of |badio.mf| +therefore have turned out to be relatively harmless. But \MF\ will +almost immediately encounter the mutilated semicolon in line~2: +\begintt +! Extra tokens will be flushed. + + : +l.2 em#:=10pt#: + cap#:=7pt#; +? +\endtt +What does this mean? Type `|H|' to find out. \MF\ has no idea what to +do with a `|:|' at this place in the file, so it plans to recover by +``^{flushing}'' or getting rid of everything it sees, until coming to a +semicolon. It would be a bad idea to type \ now, since you'd lose +the important assignment `|cap#:=7pt#|', and that would lead to worse errors. + +You might type `|X|' or `|E|' at this point, to exit from \MF\ and to fix +the errors in lines 1 and~2 before trying again. But it's usually best +to keep going, trying to detect and correct as many mistakes as possible +in each run, since that increases your productivity while +decreasing your computer bills. An experienced \MF\ user will quit +after an error only if the error is unfixable, or if there's almost no +chance that additional errors are present. + +The solution in this case is to proceed in two steps: First type `|1|', +which tells \MF\ to delete the next token (the unwanted `|:|'); then type +`|I;|', which inserts a semicolon. This semicolon protects the rest of line~2 +from being flushed away, +so all will go well until \MF\ reaches another garbled line. + +The next error message is more elaborate, because it is detected while +\MF\ is trying to carry out a "penpos" command; "penpos" is not a +primitive operation (it is defined in plain \MF), hence a lot more +context is given: +\begintt +>> l0 +! Improper transformation argument. + + ; +penpos->...(EXPR3),0)rotated(EXPR4); + x(SUFFIX2)=0.5(x(SUFF... +l.10 penpos1(thick,l0) + ; penpos2(.1[thin,thick],90-10); +? +\endtt +At first, such error messages will appear to be complete nonsense to you, +because much of what you see is low-level \MF\ code that you never wrote. But +you can overcome this hangup by getting a feeling for the way \MF\ operates. + +The bottom line shows how much progress \MF\ has made so far in the |badio| +file: It has read `|penpos1(thick,l0)|' but not yet the semicolon, on line~10. +The "penpos" routine expands into a long list of tokens; indeed, this list +is so long that it can't all be shown on two lines, and the appearances of +`^|...|' indicate that the definition of "penpos" has been truncated here. +Parameter values are often inserted into the expansion of a high-level +routine; in this case, for example, `|(EXPR3)|' and `|(EXPR4)|' correspond +to the respective parameters `|thick|' and `|l0|', and `|(SUFFIX2)|' +corresponds to~`|1|'. ^^|EXPR| ^^|SUFFIX| +\MF\ detected an error just after encountering the phrase `|rotated(EXPR4)|'; +the value of |(EXPR4)| was an undefined quantity (namely `|l0|', +which \MF\ treats as the subscripted variable~`$l_0$'\thinspace), and +^{rotation} is permitted only when a known numeric value has been supplied. +Rotations are particular instances of what \MF\ calls {\sl^{transformations}\/}; +hence \MF\ describes this particular error by saying that an ``improper +transformation argument'' was present. + +When you get a multiline error message like this, the best clues about the +source of the trouble are usually on the bottom line (since that is what +you typed) and on the top line (since that is what triggered the error +message). Somewhere in there you can usually spot the problem. + +If you type `|H|' now, you'll find that +\MF\ has simply decided to continue without doing the requested rotation. +Thus, if you respond by typing \, \MF\ will go on as if the program +had said `|penpos1(thick,0)|'. Comparatively little harm has been done; +but there's actually a way to fix the error perfectly before proceeding: +Insert the correct rotation by typing +\begintt +I rotated 10 +\endtt +and \MF\ will rotate by 10 degrees as if `|l0|' had been `|10|'. + +What happens next in Experiment 5? \MF\ will hiccup on the remaining +bug that we planted in the file. This time, however, the typo will +not be discovered until much later, because there's nothing wrong +with line~11 as it stands. \ (The variable |thinn| is not defined, +but undefined quantities are no problem unless you're doing something +complicated like rotation. Indeed, \MF\ programs typically +consist of equations in which there are lots of unknowns; +variables get more and more defined as time goes on. Hence spelling +errors cannot possibly be detected until the last minute.) \ +Finally comes the moment of truth, when |badio| tries to draw a +path through an unknown point; and you will get an error message +that's even scarier than the previous one: +\begintt +>> 0.08682thinn+144 +! Undefined x coordinate has been replaced by 0. + + { + ...FFIX0){up}..z4(SUFFIX0){ + left}..cycle; ENDFOR +penstroke->...ath_.e:=(TEXT0);endfor + .if.cycle.path_.l:cyc... + + ; +l.15 ... ..z3e{up}..z4e{left}..cycle; +|quad +? +\endtt +Wow; what's this? The expansion of @penstroke@ involves a ``@for@ loop,'' +and the error was detected in the midst of it. The +expression `|0.08682thinn+144|' just above the error message implies that +the culprit in this case was a misspelled `|thin|'. If that hadn't been +enough information, you could have gleaned another clue from the fact that +`|z4(SUFFIX0)|' has just been read; |(SUFFIX0)| is the current loop value +and `||' indicates that the value in question is `|l|', hence +$z_{4l}$ is under suspicion. \ (Sure enough, the undefined $x$~coordinate +that provoked this error can be shown to be $x_{4l}=0.08682"thinn"+144$.) + +In any event the mistake on line~11 has propagated too far to be fixable, +so you're justified in typing `|X|' or~`|E|' at this point. But type~`|S|' +instead, just for fun: This tells \MF\ to plunge ahead, correcting all +remaining errors as best it can. \ (There will be a few more problems, +since several variables still depend on `|thinn|'.) \ \MF\ will draw a +very strange letter~O before it gets to the end of the file. Then you +should type `|end|' to terminate the run. + +If you try to edit |badio.mf| again, you'll notice that line~2 still +contains ^^{editing} a colon instead of a semicolon. The fact that you +told \MF\ to delete the colon and to insert additional material doesn't +mean that your file has changed in any way. However, the transcript file +|badio.log| has a record of all the errors, so it's a handy reference when +you want to correct mistakes. \ (Why not look at +|badio.log| now, and |io.log| too, in order to get familiar with log files?) + +\dangerexercise Suppose you were doing Experiment 3 with |badio| instead +of~|io|, so you began by saying `|\mode=smoke|; |input| |badio|'. Then you +would want to recover from the error on line~1 by inserting a correct +@mode\_setup@ command, instead of by simply \ing, because +@mode\_setup@ is what really establishes "smoke" mode. Unfortunately if you +try typing `|I|~|mode_setup|' in response to the ``isolated expression'' +error, it doesn't work. What should you type instead? +\answer After an ``isolated expression,'' \MF\ thinks it is at the end of +a statement or command, so it expects to see a semicolon next. You should +type, e.g., `|I;|~|mode_setup|' to keep \MF\ happy. + +By doing the five experiments in this chapter you have learned at first hand +(1)~how to produce proofsheets of various kinds, including ``smoke proofs''; +(2)~how to make a new font and test it; (3)~how to keep calm when \MF\ +issues stern warnings. Congratulations! You're on the threshold of being able to +do lots more. As you read the following chapters, the best strategy +will be for you to continue making trial runs, using experiments +of your own design. + +\exercise However, this has been an extremely long chapter, +so you should go outside now and get some {\sl real\/} exercise. +\answer Yes. + +\endchapter + +Let us learn how Io's frenzy came--- +She telling her disasters manifold. +\author \AE SCHYLUS, ^^{Aeschylus} % + {\sl Prometheus Bound\/} (c.\thinspace470 B.C.) % verse 801 + % This is the translation by Morshead + +\bigskip + +To the student who wishes to use graphical methods as a tool, +it can not be emphasized too strongly that practice in the use of that tool +is as essential as a knowledge of how to use it. +The oft-repeated pedagogical phrase, ``we learn by doing,'' is applicable here. +\author THEODORE ^{RUNNING}, {\sl Graphical Mathematics\/} (1927) % p viii + +\eject + \beginchapter Chapter 6. How \MF\\Reads What You\\Type + +So far in this book we've seen lots of things that \MF\ can do, but we haven't +discussed what \MF\ can't do. We have looked at many examples of commands that +\MF\ can understand, but we haven't dwelt on the fact that the computer will +find many phrases unintelligible. It's time now to adopt a more systematic +approach and to study the exact rules of \MF's language. Then we'll know what +makes sense to the machine, and we'll also know how to avoid ungrammatical +utterances. + +A \MF\ program consists of one or more lines of text, where each line is made +up of letters, numbers, punctuation marks, and other symbols that appear on +a standard computer keyboard. A total of 95 different characters can be +employed, namely a blank space plus the 94 visible symbols of standard ^{ASCII}. +\ (Appendix~C describes the American Standard Code for Information +Interchange, popularly known as ``ASCII,'' under which code numbers 33 +through~126 have been assigned to 94 specific symbols. This particular +coding scheme is not important to a \MF\ programmer; the only relevant thing +is that 94 different nonblank symbols can be used.) + +\MF\ converts each line of text into a series of {\sl ^{tokens}}, and a +programmer should understand exactly how this conversion takes place. +Tokens are the individual lexical units that govern the computer's +activities. They are the basic building blocks from which meaningful +sequences of instructions can be constructed. We discussed tokens briefly +at the end of the previous chapter; now we shall consider them in detail. +Line~9 of the file |io.mf| in that chapter is a typical example of what +the machine might encounter: +\begintt +beginchar("O",0.8em#,cap#,0); "The letter O"; +\endtt +When \MF\ reads these ASCII characters it finds sixteen tokens: +\begindisplay \chardef\"=`\" \openup 2pt +\ttok{beginchar}\quad\ttok{(}\quad\ttok{\"O\"}\quad + \ttok{,}\quad\ttok{0.8}\quad\ttok{em}\quad\ttok{\#}\quad\ttok{,}\cr +\ttok{cap}\quad\ttok{\#}\quad\ttok{,}\quad\ttok{0}\quad + \ttok{)}\quad\ttok{;}\quad\ttok{\"The letter O\"}\quad\ttok{;}\cr +\enddisplay +Two of these, |"O"| and |"The| |letter| |O"|, are called {\sl^{string tokens}\/} +because they represent strings of characters. Two of them, `|0.8|' and `|0|', +are called {\sl^{numeric tokens}\/} because they represent numbers. The +other twelve---`|beginchar|', `|(|', etc.---are called {\sl^{symbolic +tokens}\/}; such tokens can change their meaning while a \MF\ program runs, +but string tokens and numeric tokens always have a predetermined significance. +Notice that clusters of letters like `|beginchar|' are treated as a unit; +the same holds with respect to letters mixed with ^{underline} characters, +as in `|mode_setup|'. Indeed, +the rules we are about to study will explain that clusters of other +characters like `|0.8|' and `|:=|' are also considered to be +indecomposable tokens. \MF\ has a definite way of deciding where one +token stops and another one begins. + +It's often convenient to discuss ^{grammatical rules} by formulating them in +a special notation that was introduced about 1960 by John ^{Backus} and +Peter ^{Naur}. Parts of speech are represented by named quantities in +^{angle brackets}, and {\sl^{syntax rules}\/} are used to express the ways +in which those quantities can be built~up from simpler units. For example, +here are three syntax rules that completely describe the possible forms of +numeric tokens: +\def\\#1{\thinspace{\tt#1}\thinspace} +\beginsyntax +\is\\0\alt\\1\alt\\2\alt\\3\alt\\4\alt\\5\alt\\6% + \alt\\7\alt\\8\alt\\9 +\is\alt +\is\alt[.] + \alt\\. +\endsyntax +The first rule says that a \ is either `|0|' or `|1|' or +$\cdots$ or `|9|'; thus it must be one of the ten numerals. The next +rule says that a \ is either a \ or a +\ followed by a \; thus it must be a sequence +of one or more digits. Finally, a \ has one of three forms, +exemplified respectively by `|15|', `|.05|', and `|3.14159|'. + +Syntax rules explain only the surface structure of a language, not the +underlying meanings of things. For example, the rules above tell us that +`|15|' is a \, but they don't imply that `|15|' has +any connection with the number fifteen. Therefore syntax rules are +generally accompanied by rules of {\sl^{semantics}}, which ascribe +meanings to the strings of symbols that meet the conditions of the syntax. +In the case of numeric tokens, the principles of ordinary decimal notation +define the semantics, except that \MF\ deals only with numbers in a +limited range: A numeric token must be less than 4096, and its value is +always rounded to the nearest multiple of $1\over65536$. Thus, for example, +`|.1|'~does not mean $1\over10$, it means $6554\over65536$ (which is +slightly greater than $1\over10$). It turns out that the tokens +`|.099999|' and `|0.10001|' both have exactly the same meaning as +^^{numeric tokens, rounded values} ^^{numeric tokens, maximum value} +`|.1|', because all three tokens represent the value $6554\over65536$. + +\dangerexercise Are the following pairs of numeric tokens equivalent +to each other, when they appear in \MF\ programs? +\ (a)~|0| and |0.00001|; \ (b)~|0.00001| and |0.00002|; +\ (c)~|0.00002| and |0.00003|; \ (d)~|04095.999999| and |10000|? +\answer (a) No, the second token represents $1\over65536$. \ (A token has +the same meaning as~`|0|' ^^{zero} if and only if its decimal value +is strictly less than $2^{-17}=.00000\,76293\,94531\,25$.) \ (b)~Yes; both +tokens represent $1\over65536$, because 1~is the nearest integer to both +$.00001\times65536=.65536$ and $0.00002\times65536=1.31072$. \ (c)~No, +|0.00003| represents $2\over65536$. \ (d)~Yes, they both mean ``^{enormous +number} that needs to be reduced''; \MF\ complains in both +cases and substitutes the largest legal numeric token. \ (Rounding +4095.999999 to the nearest multiple of $1\over65536$ yields 4096, +which is too big.) + +\MF\ converts each line of text into a sequence of tokens by repeating +the following rules until no more characters remain on the line: +\smallskip +\hang\textindent{1)}If the next character is a ^{space}, or if it's a ^{period} +(\thinspace`|.|'\thinspace) that isn't ^^{decimal point} followed by a +decimal digit or a period, ignore it and move on. + +\hang\textindent{2)}If the next character is a ^{percent sign} +(\thinspace`|%|'\thinspace), ignore it and also ignore everything else +that remains on the current line. \ (Percent signs therefore allow you to +write ^{comments} that are unseen by \MF\!.) + +\hang\textindent{3)}If the next character is a ^{decimal digit} or a period +that's followed by a decimal digit, the next token is a numeric token, +consisting of the longest sequence of contiguous characters starting at +the current place that satisfies the syntax for \ above. + +\hang\textindent{4)}If the next character is a ^{double-quote mark} (\thinspace +`|"|'\thinspace), the next token is a string token, consisting of all +characters from the current place to the next double-quote, inclusive. +\ (There must be at least one more double-quote remaining on the line, +otherwise \MF\ will complain about an ``^{incomplete string}.'') \ A string +token represents the sequence of characters between the double-quotes. + +\hang\textindent{5)}If the next character is a ^{parenthesis} (\thinspace +`|(|' or `|)|'\thinspace), a comma (\thinspace`|,|'\thinspace), or a +semicolon (\thinspace`|;|'\thinspace), the next token is a symbolic token +consisting of that single character. + +\hang\textindent{6)}Otherwise the next token is a symbolic token consisting +of the next character together with all immediately following characters +that appear in the same row of the following +^^{table of character classes} table: +\begindisplay \displayindent=0pt +|ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz|\hidewidth\cr +|<=>:|\|\cr +|`'|\cr +|+-|\cr +|/*\|\cr +|!?|\cr +|#&@$|\cr +|^~|\cr +|[|\cr +|]|\cr +|{}|\cr +|.|&(see rules 1, 3, 6)\cr +|, ; ( )|&(see rule 5; these characters are ``loners'')\cr +|"|&(see rule 4 for details about string tokens)\cr +|0123456789|&(see rule 3 for details about numeric tokens)\cr +|%|&(see rule 2 for details about comments)\cr +\enddisplay + +\noindent +The best way to learn the six rules about tokens is to work the following +exercise, after which you'll be able to read any input file just as the +computer does. + +\exercise What tokens does \MF\ find in the (ridiculous) line +\begindisplay +|xx3.1.6..[[a+-bc_d.e] ]"a %" <|\||>(($1. 5"+-""" % weird?| +\enddisplay +\answer \cstok{xx}, \cstok{3.1} (a numeric token), \cstok{.6} (another +numeric token), \cstok{..}, \cstok{[[}, \cstok{a}, \cstok{+-}, +\cstok{bc\_d}, \cstok{e}, \cstok{]}, \cstok{]}, {\chardef\"=`\"\cstok{\"a +\%\"} (a string token), \cstok{<\|>}, \cstok{(} (see rule~5), \cstok{(}, +\cstok{\$}, \cstok{1} (a numeric token), \cstok{5} (likewise numeric), +\cstok{\"+-\"} (a string token), and \cstok{\"\"}} (a string token that +denotes an empty sequence of characters). +All of these tokens are symbolic unless otherwise mentioned. \ (Notice that +four of the spaces and two of the periods were deleted by rule~1. +One way to verify that \MF\ finds precisely these tokens is to prepare a +test file that says `|isolated| |expression;|' on its first line and that +contains the stated text on its second line. Then respond to \MF's +error message by repeatedly typing `|1|', so that one token is deleted +at a time.) + +\exercise Criticize the following statement: \MF\ ignores all spaces in the +input. +\answer The statement is basically true but potentially misleading. You can +insert any number of spaces {\sl between\/} tokens without changing the +meaning of a program, but you cannot insert a space in the {\sl middle\/} +of any token without changing something. You can delete spaces between +tokens {\sl unless\/} that would ``glue'' two adjacent tokens together. + +\dangerexercise True or false: If the syntax for \ were +changed to include a fourth alternative, `\|.|', the meaning +of \MF\ programs would not change in any way. +\answer False. It may seem that this new sort of numeric token would be +recognized only in cases where the period is not followed by a digit, +hence the period would be dropped anyway by rule~1. However, the new rule +would have disastrous consequences in a line like `|draw| |z1..z2|'! + + +\endchapter + +Yet wee with all our seeking could see no tokens. + % of any such Wall. +\author PHILEMON ^{HOLLAND}, {\sl ^{Camden}'s Brittania\/} (1610) +% OED says page 518, but I couldn't find it there in the 1637 edition + +\bigskip + +Unpropitious tokens interfered. +\author WILLIAM ^{COWPER}, {\sl ^{Homer}'s Iliad\/} (1791) % Book 4 verse 455 + +\eject + \beginchapter Chapter 7. Variables + +One of \MF's most important concepts is the notion of a +{\sl^{variable}\/}---something that can take on a variety of different +values. Indeed, this is one of the most important concepts in all of +mathematics, and variables play a prominent r\^ole in almost all +computer languages. The basic idea is that a program manipulates data, +and the data values are stored in little compartments of a computer's +memory. Each little compartment is a variable, and we refer to an item +of data by giving its compartment a name. + +For example, the |io.mf| program for the letter {\manual\IOO} in Chapter~5 +contains lots of variables. Some of these, like `|x1l|' and `|y1|', represent +coordinates. Others, like `|up|', represent +directions. The variables `|em#|' and `|thin#|' stand for physical, +machine-independent distances; the analogous variables `|em|' and `|thin|' +stand for the corresponding machine-dependent distances in units of pixels. + +These examples indicate that different variables are often related to each +other. There's an implicit connection between `|em#|' and `|em|', +between `|x1|' and `|y1|'; the `"penpos"' convention +sets up relationships between `|x1l|', `|x1|', and `|x1r|'. By choosing +the names of variables carefully, programmers can make their programs +much easier to understand, because the relationships between variables +can be made to correspond to the ^^{data structure} structure +of their names. + +In the previous chapter we discussed tokens, the atomic elements from which +all \MF\ programs are made. We learned that there are three kinds of +tokens: numeric (representing numbers), string (representing text), and +symbolic (representing everything else). Symbolic tokens have no +intrinsic meaning; any symbolic token can stand for whatever a programmer +wants it to represent. + +Some symbolic tokens do, however, have predefined {\sl^{primitive}\/} +meanings, when \MF\ begins its operations. For example, `|+|' stands +initially for ``plus,'' and `|;|' stands for ``finish the current +statement and move on to the next part of the program.'' It is customary +to let such tokens retain their primitive meanings, but any symbolic token +can actually be assigned a new meaning as a program is performed. For +example, the definition of `|test_I|' in |io.mf| makes that token stand +for a {\sl^{macro}}, i.e., a subroutine. We'll see later that you can +instruct \MF\ to `|let| |plus=+|', after which `|plus|' will act just +like `|+|' did. + +\MF\ divides symbolic tokens into two categories, depending on their +current meaning. If the symbolic token currently stands for one of \MF's +primitive operations, or if it has been defined to be a macro, it is +called a {\sl^{spark}\/}; otherwise it is called a {\sl^{tag}}. Almost +all symbolic tokens are tags, because only a few are defined to be sparks; +however, \MF\ programs typically involve lots of sparks, because sparks +are what make things happen. The symbolic tokens on the first five lines +of |io.mf| include the following sparks: +\begintt +mode_setup ; := / define_pixels ( , ) +\endtt +and the following tags: +\begintt +em # pt cap thin thick o +\endtt +(some of which appear several times). Tags are used to designate variables, +but sparks cannot be used within a variable's name. + +Some variables, like `|em#|', have names that are made from more than one token; +in fact, the variable `|x1l|' is named by three tokens, one of which is +numeric. \MF\ has been designed so that it is easy to make compound names +that correspond to the relations between variables. Conventional programming +languages like ^{Pascal} would refer to `|x1l|' by the more +cumbersome notation `|x[1].l|'; it turns out that `|x[1].l|' is an +acceptable way to designate the variable |x1l| in a \MF\ program, but the +shorthand form `|x1l|' is a great convenience because such variables +are used frequently. + +Here are the formal rules of syntax by which \MF\ understands the names of +variables: +\def\\#1{\thinspace{\tt#1}\thinspace} +\beginsyntax +\is +\is\alt\alt +\is\alt\\{\char`\[}\\] +\endsyntax +First comes a tag, like `|x|'; then comes a {\sl^{suffix}\/} to the tag, +like `|1l|'. +The suffix might be empty, or it might consist of one or more subscripts +or tags that are tacked on to the original tag. A {\sl^{subscript}\/} is +a numeric index that permits you to construct ^{arrays} of related +variables. The subscript is either a single numeric token, or it is a formula +enclosed in square ^{brackets}; in the latter case the formula should produce a +^^|[| numeric value. For example, `|x[1]|' and `|x[k]|' and `|x[3-2k]|' all mean +^^|]| the same thing as `|x1|', if\/ |k|~is a variable whose value is~1. But +`|x.k|' is not the same; it is the tag~`|x|' suffixed by the tag~`|k|', +not the tag~`|x|' subscripted by the value of variable~|k|. + +\danger The variables `|x1|' and `|x01|' and `|x1.00|' are identical. +Since any numeric token can be used as a subscript, fractional indices +are possible; for example, `|x1.5|' is the same as `|x[3/2]|'. Notice, +however, that `|B007|' and `|B.007|' are {\sl not\/} the same variable, +because the latter has a fractional subscript. + +\danger \MF\ makes each \ as long as possible. In other words, +a \ is always extended if it is followed by a \ +or a~\. + +\dangerexercise Explain how to type a reference to the doubly subscripted +variable `|a[1][5]|' without using square brackets. +\answer You can put a space between the subscripts, as in `|a1|~|5|'. \ +(We'll see later that a ^{backslash} acts as a null symbol, +hence `|a1\5|' is another solution.) + +\dangerexercise Is it possible to refer to {\sl any\/} variable without +using square brackets? +\answer No; |a[-1]| can't be accessed without using |[| and |]|. The +only other form of \ is \, which can't be +negative. \ (Well, strictly speaking, you could say `|let|~|?=[;| +|let|~|??=]|' and then refer to `|a?-1??|'; but that's cheating.) + +\ddangerexercise Jonathan H. ^{Quick} (a student) used `|a.plus1|' as the name +of a variable at the beginning of his program; later he said `|let| +|plus=+|'. How could he refer to the variable `|a.plus1|' after that? +\answer Assuming that `|+|' was still a spark when he said `|let|~|plus=+|', +he can't refer to the variable `|a.plus1|' unless he changes the meaning of +|plus| again to make it a~tag. \ (We will eventually learn a way to do this +without permanently clobbering |plus|, as follows: `^|begingroup| ^|save| +|plus;| |a.plus1| ^|endgroup|'.) + +\danger \MF\ has several special variables called {\sl^{internal +quantities}\/} that are intimately wired-in to the computer's behavior. +For example, there's an internal quantity called `^|fontmaking|' that controls +whether or not a |tfm| file is produced; another one called `^|tracingtitles|' +governs whether or not titles like |"The| |letter|~|O"| appear on your +terminal; still another one called `^|smoothing|' affects the digitization of +curves. \ (A complete list of \MF's internal quantities appears in +Chapter~25.) \ The name of an internal quantity acts like a tag, but +internal quantities cannot be suffixed or subscripted. +Thus, the syntax rule for \ +should actually be replaced by a slightly more complicated pair of rules: +\beginsyntax +\is\alt +\is\alt +\endsyntax + +\dangerexercise True or false: Every \ is a legal \. +\answer False. After `|newinternal x;|' you can't say +`|x|\' in a \. + +\ddanger The `|[|' and `|]|' that appear in the syntax for \ +stand for any symbolic tokens whose current meanings are the same as +\MF's primitive meanings of left and right bracket, respectively; +those tokens don't necessarily have to be brackets. Conversely, if the +meanings of the tokens `|[|' and `|]|' have been changed, brackets cannot +be used to delimit subscripts. Similar remarks apply to all of the +symbolic tokens in all of the syntax rules from now on. \MF\ doesn't look +at the form of a token; it considers only a token's current meaning. + +The examples of \MF\ programs in this book have used two different +typographic conventions. Sometimes we refer to variables by using +^{italic type} and/or genuine subscripts, e.g., `"em"' and `$x_{2r}$'; +but sometimes we refer to those same variables by using a ^{typewriter}-like +style of type, e.g., `|em|' and~`|x2r|'. In general, the typewriter style +is used when we are mainly concerned with the way a programmer is supposed +to type something that will appear on the terminal or in a file; but fancier +typography is used when we are focusing on the meaning of a program rather +than its ASCII representation. It should be clear how to convert the fancier +form into tokens that \MF\ can actually understand. + +\danger In general, we shall use italic type only for tags (e.g., "em", +"x", "r"), while boldface and roman type will be used for sparks +(e.g., @draw@, @fill@, cycle, rotated, sqrt). Tags that consist of special +characters instead of letters will sometimes get special treatment; +for example, |em#| and |z2'| might be rendered $"em"\0$ and $z'_2$, +respectively. + +The variables we've discussed so far have almost always had numbers as their +values, but in fact \MF's variables are allowed to assume values of eight +different ^{types}. A variable can be of type +\nobreak\smallskip +\item\bull^{boolean}, representing the values `^{true}' or `^{false}'; +\item\bull^{string}, representing sequences of ASCII characters; +\item\bull^{path}, representing a (possibly curved) line; +\item\bull^{pen}, representing the shape of a pen nib; +\item\bull^{picture}, representing an entire pattern of pixels; +\item\bull^{transform}, representing the operations of scaling, rotating, + shifting, reflecting, and/or slanting; +\item\bull^{pair}, representing two numbers (e.g., a point or a vector); +\item\bull^{numeric}, representing a single number. +\smallskip\noindent +If you want a variable to represent something besides a number, you must +first give a {\sl^{type declaration}\/} ^^{declarations} that states +what the type will be. But if you refer to a variable whose type has not +been declared, \MF\ won't complain, unless you try to use it in a way that +demands a value that isn't numeric. + +Type declarations are easy. You simply name one of the eight types, +then you list the variables that you wish to declare for that type. +For example, the declaration +\begindisplay +@pair@ "right", "left", $a.p$ +\enddisplay +says that "right" and "left" and $a.p$ will be variables of type @pair@, +so that equations like +\begindisplay +$"right"=-"left"=2a.p=(1,0)$ +\enddisplay +can be given later. These equations, incidentally, define the values +$"right"=(1,0)$, $"left"=(-1,0)$, and $a.p=(.5,0)$. \ (Plain \MF\ +has the stated values of "right" and "left" already built~in.) + +The rules for declarations are slightly trickier when subscripts are +involved, because \MF\ insists that all variables whose names are identical +except for subscript values must have the same type. It's possible to +set things up so that, for example, $a$~is numeric, $a.p$ is a pair, +$a.q$ is a pen, $a.r$ is a path, and $a_1$ is a string; but if $a_1$ +is a string, then all other variables $a_2$, $a_3$, etc., must also be +strings. In order to enforce this restriction, \MF\ allows only +``collective'' subscripts, represented by empty brackets `^|[]|', +to appear in type declarations. ^^{collective subscripts} For example, +\begintt +path r, r[], x[]arc, f[][] +\endtt +declares $r$ and all variables of the forms $r[i]$, $x[i]"arc"$, +and $f[i][j]$ to be path variables. This declaration doesn't affect +the types or values of other variables like $r[\,]"arc"$; it affects +only the variables that are specifically mentioned. + +Declarations destroy all previous values of the variables being defined. +For example, the path declaration above makes $r$ and $r[i]$ and $x[i]"arc"$ +and $f[i][j]$ undefined, even if those variables previously had paths +as their values. The idea is that all such variables will start out with a +clean slate so that they can receive appropriate new values based on +subsequent equations. ^^{value, disappearance of} + +\exercise Numeric variables don't need to be declared. Therefore is there +ever any reason for saying `|numeric| |x|'\thinspace? +\answer Yes, because it removes any existing value that $x$ may have +had, of whatever type; otherwise you couldn't safely use $x$ in a +numeric equation. It's wise to declare numeric variables when you're +not sure about their former status, and when you're sure that you don't +care what their previous value was. A numeric declaration together with a +comment also provides useful documentation. \ (Incidentally, `|numeric|~|x|' +doesn't affect other variables like `|x2|' or `|x.x|' that might be present.) + +\danger The formal syntax rules for type declarations explain these +grammatical conventions precisely. If the symbolic token that begins a +declared variable was previously a spark, it loses its former meaning and +immediately becomes a tag. +\beginsyntax +\is +\is[boolean]\alt[string]\alt[path]\alt[pen] + \alt[picture]\alt[transform]\alt[pair]\alt[numeric] +\is + \alt[,] +\is +\is\alt + \alt\\{\char`\[}\\] +\endsyntax + +\dangerexercise Find three errors in the supposed declaration +`|transform| |t42,24t,,t,path|'. +\answer (a)~The `|42|' is illegal because subscripts must be collective. +\ (b)~The `|24|' is illegal because a \ must start with +a \, not a numeric token. \ (c)~There's nothing wrong with +the consecutive commas; the second comma begins a \, so +it loses its former meaning and becomes a tag. Thus \MF\ tries to declare +the variable `|,t,path|'. However, `|path|' cannot appear in a suffix, +since it's a spark. \ (Yes, this is admittedly tricky. Computers follow rules.) + +\endchapter + +Beings low in the scale of nature are +more variable than those which are higher. +\author CHARLES ^{DARWIN}, {\sl On the Origin of Species\/} (1859) % p149 + +\bigskip + +Among the variables, {\rm Beta ({\cmman\char'14\/}) Persei}, or\/ {\rm^{Algol}}, +is perhaps the most interesting, as its period is short. +\author J. NORMAN ^{LOCKYER}, {\sl Elements of Astronomy\/} (1870) + % American edition, p40 + +\eject + \beginchapter Chapter 8. Algebraic\\Expressions + +\MF\ programmers express themselves algebraically by writing algebraic +formulas called {\sl^{expressions}}. The formulas are algebraic in the +sense that they involve variables as well as constants. By combining +variables and constants with appropriate mathematical operations, a +programmer can specify an amazing variety of things with comparative ease. + +We have already seen many examples of expressions; our goal now is to make +a more systematic study of what is possible. The general idea is that an +expression is either a ^{variable} (e.g., `$x_1$'\thinspace) or a +^{constant} (e.g., `20'\thinspace), or it consists of an ^{operator} +(e.g., `$+$'\thinspace) together with its ^{operands} (e.g., +`$x_1+20$'\thinspace). The operands are, in turn, expressions built~up in +the same way, perhaps enclosed in ^{parentheses}. For example, +`$(x_1+20)/(x_2-20)$' is an expression that stands for the quotient of two +subexpressions. It is possible to concoct extremely complicated algebraic +expressions, but even the most intricate constructions are built from +simple parts in simple ways. + +Mathematicians spent hundreds of years developing good ways to write formulas; +then computer scientists came along and upset all the time-honored traditions. +The main reason for making a change was the fact that computers find it +difficult to deal with two-dimensional constructions like +\begindisplay +$\displaystyle{x_1+20\over x_2-20}+\sqrt{a^2-{2\over3}\sqrt b}.$ +\enddisplay +One-dimensional sequences of tokens are much easier to input and to decode; +hence programming languages generally put such formulas all on one line, +^^{sqrt} by inserting parentheses, brackets, and asterisks as follows: +\begintt +(x[1]+20)/(x[2]-20)+sqrt(a**2-(2/3)*sqrt(b)). +\endtt +\MF\ will understand this formula, but it also accepts a notation that +is shorter and closer to the standard conventions of mathematics: +\begintt +(x1+20)/(x2-20)+sqrt(a**2-2/3sqrt b). +\endtt +We observed in the previous chapter that \MF\ allows you to write `|x2|' +instead of `|x[2]|'; similarly, you can write `|2x|' instead of `|2*x|' +and `|2/3x|' instead of `|(2/3)*x|'. Such operations are extremely common +in \MF\ programs, hence the language has been set up to facilitate them. +On the other hand, \MF\ doesn't free you from all the inconveniences of +computer languages; you must still write `|x*k|' for the ^{product} of +$x$ times~$k$, and `|x[k]|' for the variable $x$~subscripted by~$k$, +in order to avoid confusion with the suffixed variable `|x.k|'. + +We learned in the previous chapter that there are eight types of +variables: numeric, boolean, string, and so~on. The same types apply +to expressions; \MF\ deals not only with numeric expressions but also +with boolean expressions, string expressions, and the others. For example, +`$(0,0)\to(x_1,y_1)$' +is a path-valued expression, formed by applying the operator `$\to$' to the +subexpressions `$(0,0)$' and `$(x_1,y_1)$'; these subexpressions, in turn, +have values of type ``pair,'' and they have been built up from values of +type ``numeric.'' Each operation produces a result whose type can be +determined from the types of the operands; furthermore, the simplest +expressions (variables and constants) always have a definite type. +Therefore the machine always knows what type of quantity it is dealing +with, after it has evaluated an expression. + +If an expression contains several operators, \MF\ has to decide which +^^{order of operations} +operation should be done first. For example, in the expression `$a-b+c$' +it is important to compute `$a-b$' first, then to add~$c$; if `$b+c$' were +computed first, the result `$a-(b+c)$' would be quite different from the +usual conventions of mathematics. On the other hand, mathematicians +usually expect `$b/c$' to be computed first in an expression like +`$a-b/c$'; multiplications and divisions are usually performed before +additions and subtractions, unless the contrary is specifically indicated +by parentheses as in `$(a-b)/c$'. The general rule is to evaluate +subexpressions in parentheses first, then to do operations in order of +their ``^{precedence}''; if two operations have the same precedence, the +left one is done first. For example, `$a-b/c$' is equivalent to +`$a-(b/c)$' because division takes precedence over subtraction; but +`$a-b+c$' is equivalent to `$(a-b)+c$' because left-to-right order is +used on operators of equal precedence. + +It's convenient to think of operators as if they are tiny ^{magnets} that +attract their operands; the magnets for `$\ast$' and `/' are stronger +than the magnets for `$+$' and `$-$', so they stick to their operands more +tightly and we want to perform them first. + +\MF\ distinguishes four (and only four) levels of precedence. The +strongest magnets are those that join `2' to~`$x$' and `sqrt' to `$b$' +in expressions like `$2x$' and `sqrt$\,b$'. The next strongest are +multiplicative operators like `$\ast$' and~`/'; then come the additive +operators like `$+$' and~`$-$'. The weakest magnets are operators like +`$\to$' or `$<$'. For example, the expression +\begindisplay +$a+{\rm sqrt}\,b/2x, obtaining the +value~`|>>|~|4|'. Isn't that amazing? Here are some more things to try: +\begindemo +\demohead +1.2-2.3&-1.1\cr +1.3-2.4&-1.09999\cr +1.3*1000&1300.00305\cr +2.4*1000&2399.9939\cr +3/8&0.375\cr +.375*1000&375\cr +1/3&0.33333\cr +1/3*3&0.99998\cr +0.99999&0.99998\cr +1-epsilon&0.99998\cr +1/(1/3)&3.00005\cr +1/3.00005&0.33333\cr +.1*10&1.00006\cr +1+4epsilon&1.00006\cr +\enddemo +These examples illustrate the small errors that occur because \MF\ does +``fixed binary'' ^{arithmetic} using integer multiples of $1\over65536$. +The result of $1.3-2.4$ is not quite the same as $-1.1$, because |1.3| is +a little bit larger than~$13\over10$ and |2.4| is a little smaller +than~$24\over10$. Small errors get magnified when they are multiplied by +1000, but even after magnification the discrepancies are negligible because +they are just tiny fractions of a pixel. You may be surprised that +1/3~times~3 comes out to be .99998 instead of .99999; the truth is that both +|0.99999| and |0.99998| represent the same value, namely $65535\over65536$; \MF\ +displays this value as |0.99998| because it is closer to .99998 than to +.99999. Plain \MF\ defines ^"epsilon" to be $1\over65536$, the smallest +representable number that is greater than zero; therefore |1-epsilon| +is $65535\over65536$, and |1+4epsilon| is $65540\over65536$. +\begindemo +\demohead +4096&4095.99998\werror\cr +infinity&4095.99998\cr +1000*1000&32767.99998\werror\cr +infinity+epsilon&4096\cr +100*100&10000\cr +.1(100*100)&1000.06104\cr +(100*100)/3&3333.33333\cr +\enddemo +\MF\ will complain that an `|Enormous| ^^{enormous number} |number| |has| +|been| |reduced|' when you try to introduce constants that are 4096 or~more. +Plain \MF\ defines ^"infinity" to be $4096-"epsilon"$, which is the largest +legal numeric token. On the other hand, it turns out that larger numbers +can actually arise when an expression is being evaluated; \MF\ doesn't +worry about this unless the resulting magnitude is at least 32768. + +\dangerexercise If you try `|100*100/3|' instead of `|(100*100)/3|', you +get `|3333.33282|'. Why? +\answer The fraction |100/3| is evaluated first (because such divisions +take precedence); the rounding error in this fraction is then magnified by~100. + +\ddanger Sometimes \MF\ will compute things more accurately than you would +expect from the examples above, because many of its internal calculations +are done with multiples of $2^{-28}$ instead of $2^{-16}$. For example, +if $t=3$ the result of `|1/3t|' will be exactly~1 +(not 0.99998); the same thing happens if you write `|1/3(3)|'. + +Now let's try some more complicated expressions, using undefined +variables as well as constants. \ (Are you actually trying these +examples, or are you just reading the book? It's far better to type +them yourself and to watch what happens; in fact, you're also allowed +to type things that {\sl aren't\/} in the book!) +\begindemo +\demohead +b+a&a+b\cr +a+b&a+b\cr +b+a-2b&a-b\cr +2(a-b+.5)&2a-2b+1\cr +.5(b-a)&-0.5a+0.5b\cr +.5[a,b]&0.5a+0.5b\cr +1/3[a,b]&0.66667a+0.33333b\cr +0[a,b]&a\cr +a[2,3]&a+2\cr +t[a,a+1]&t+a\cr +a*b&b\werror\cr +1/b&b\werror\cr +\enddemo +\MF\ has a preferred way to arrange variables in order when they are added +together; therefore `$a+b$' and `$b+a$' give the same result. Notice that +the ^{mediation} construction `$.5[a,b]$' specifies a number that's halfway +between $a$ and~$b$, as explained in Chapter~2. \MF\ does not allow you to +^{multiply} two unknown numeric quantities together, nor can you ^{divide} by an +unknown numeric; all of the unknown expressions that \MF\ works with must be +``^{linear forms},'' i.e., they must be sums of variables with constant +coefficients, plus an optional constant. \ (You might want to try typing +`|t[a,b]|' now, in order to see what error message is given.) +\begindemo +\demohead +sqrt 2&1.41422\cr +sqrt 100&10\cr +sqrt 100*100&1000\cr +sqrt(100*100)&100\cr +sqrt 100(100)&100\cr +sqrt sqrt 100(100)&10\cr +sqrt .01&0.09998\cr +0.09998**2&0.01\cr +2**1/2&1.41422\cr +sqrt 2**2&2\cr +sqrt -1&0\werror\cr +sqrt a&a\werror\cr +\enddemo +Since ^|sqrt| has more ``magnetism'' than |*|, the formula |sqrt|~|100*100| +^^{square roots} +is evaluated as |(sqrt|~|100)*100|; but in `|sqrt|~|100(100)|' the +|100(100)| is computed first. The reason is that `|(sqrt|~|100)(100)|' isn't +a legal expression, so the operations in `|sqrt|~|100(100)|' must be carried +out from right to left. If you are unsure about the order of evaluation, +^^|**| you can always insert parentheses; but you'll find that \MF's rules of +precedence are fairly natural as you gain experience. + +\exercise Is `|sqrt|~|2**2|' computed as `|(sqrt|~|2)**2|' or as +`|sqrt(2**2)|'\thinspace? +\answer A |sqrt| takes precedence over any operation with two operands, hence +the machine computes `|(sqrt|~|2)**2|'; \MF\ was somewhat lucky that the +answer turned out to be exactly~2. \ (The |sqrt| operation computes the +nearest multiple of $1\over65536$, and the rounding error in this quantity +is magnified when it is squared. If you try |sqrt|~|3**2|, you'll get +|3.00002|; also |sqrt|~|2**4| turns out to be |4.00002|.) \ Incidentally, +the ^|**| operation of plain \MF\ has the same precedence as |*| and~|/|; +hence `|x*y**2|' means the same as `|(x*y)**2|', and `|-x**2|' means +`|(-x)**2|', contrary to the conventions of {\eightrm ^{FORTRAN}}. + +Some \MF\ expressions have `^{true}' or `^{false}' values, instead of numbers; +we will see later that they can be used to adapt \MF\ programs to special +conditions. +\begindemo +\demohead +0<1&true\cr +0=1&false\cr +a+1>a&true\cr +a>=b&false\werror\cr +"abc"<="b"&true\cr +"B">"a!"&false\cr +"b">"a?"&true\cr +(1,2)<>(0,4)&true\cr +(1,2)<(0,4)&false\cr +(1,a)>(0,b)&true\cr +numeric a&true\cr +known a&false\cr +not pen a&true\cr +known "a" and numeric 1&true\cr +(0>1) or (a1 or a=|', `^|<=|', and `^|<>|' stand respectively for the +^{relations} ^{greater-than-or-equal-to}, ^{less-than-or-equal-to}, and +^{unequal-to}. When strings are compared, \MF\ uses the order of words in +a dictionary, except that it uses ASCII code to define ordering of individual +characters; thus, all uppercase letters are considered to be less than all +lowercase letters. \ (See Appendix~C\null.) \ When pairs of numbers are +compared, \MF\ considers only the $x$~coordinates, unless the $x$~coordinates +are equal; in the latter case it compares the $y$~coordinates. The type +of an expression can be ascertained by an expression like `|pair|~|a|', +which is true if and only if |a|~is a pair. ^^{pair} ^^{numeric} ^^{pen} +The expression `|known|~|a|' ^^{known} is true if and only if the value +of~|a| is fully known. + +\dangerexercise What causes the error messages in `|0>1|~|or|~|a$', ^^|<| ^^|>| +\MF\thinspace\ tries to evaluate this expression by putting things in +parentheses as follows: `$(0>(1\mathbin{\bf or}a))a$' is indeterminate because $a$~is unknown; \MF\ treats this as +false. Finally `${\rm false}.) \ ^^{xpart} ^^{ypart} ^^{shifted} ^^"right" ^^{slanted} +^^{zscaled} ^^{dotprod} ^^"z" The operations exhibited here are almost +all self-evident. When a point or vector is ^{rotated}, it is moved +counterclockwise about $(0,0)$ through a given number +of degrees. \MF\ computes the rotated coordinates by using +^{sines} and ^{cosines} in an appropriate way; you don't have to +remember the formulas! Although you cannot use `|*|' to multiply +a pair by a pair, you can use `^{zscaled}' to get the effect of +^{complex number} multiplication: Since $(1+2i)$ times $(3+4i)$ is +$-5+10i$, we have $(1,2)\mathbin{\rm zscaled}(3,4)=(-5,10)$. +There's also a ^{multiplication} that converts pairs into numbers: +$(a,b)\mathbin{\rm dotprod}(c,d\mkern1mu)=ac+bd$. This is the +``^{dot product},'' often written `$(a,b)\cdot(c,d\mkern1mu)$' in +mathematics texts; it turns out to be equal to $a\pyth+b$ times +$c\pyth+d$ times the cosine of the angle between the vectors $(a,b)$ and +$(c,d)$. Since cosd$\,90^\circ=0$, two vectors are +^{perpendicular} to each other if and only if their dot ^{product} is zero. + +\danger There are operations on strings, paths, and the other types too; +we shall study such things carefully in later chapters. For now, it will +suffice to give a few examples, keeping in mind that the file |expr.mf| +defines |s| with any subscript to be a ^{string}, while |p| with any subscript +is a path. Furthermore $s_1$ has been given the value |"abra"|, while +$p_1$ is `$(0,0)\to(3,3)$' and $p_2$ is `$(0,0)\to(3,3)\to\cycle$'. +\begindemo +\demohead +s2&unknown string s2\cr +s1\&"cad"\&s1&"abracadabra"\cr +length s1&4\cr +length p1&1\cr +length p2&2\cr +cycle p1&false\cr +cycle p2&true\cr +substring (0,2) of s1&"ab"\cr +substring (2,infinity) of s1&"ra"\cr +point 0 of p1&(0,0)\cr +point 1 of p1&(3,3)\cr +point .5 of p1&(1.5,1.5)\cr +point infinity of p1&(3,3)\cr +point .5 of p2&(3,0)\cr +point 1.5 of p2&(0,3)\cr +point 2 of p2&(0,0)\cr +point 2+epsilon of p2&(0.00009,-0.00009)\cr +point -epsilon of p2&(-0.00009,0.00009)\cr +point -1 of p1&(0,0)\cr +direction 0 of p1&(1,1)\cr +direction 0 of p2&(4,-4)\cr +direction 1 of p2&(-4,4)\cr +\enddemo +^^{point} ^^{direction} +The ^{length} of a path is the number of `$\to$' steps that it contains; +the construction `^|cycle|~\' can be used to tell whether or not a +particular path is cyclic. If you say just `|p1|' you get to see +path~$p_1$ with its ^{control points}: +\begintt +(0,0)..controls (1,1) and (2,2) + ..(3,3) +\endtt +Similarly, `|p2|' is +\begintt +(0,0)..controls (2,-2) and (5,1) + ..(3,3)..controls (1,5) and (-2,2) + ..cycle +\endtt +and `|subpath| |(0,1)| |of| |p2|' is analogous to a ^{substring}:^^{subpath} +\begintt +(0,0)..controls (2,-2) and (5,1) + ..(3,3) +\endtt +The expression `point $t$ of $p_2$' gives the position of a point that +moves along path~$p_2$, starting with the initial point $(0,0)$ at $t=0$, +then reaching point $(3,3)$ at $t=1$, etc.; + the value at $t=1/2$ is the +third-order midpoint obtained by the construction of Chapter~3, using +intermediate control points $(2,-2)$ and $(5,1)$. +Since $p_2$ is a cyclic path of length~2, +point $(t+2)$ of~$p_2$ is the same as point~$t$. Path $p_1$ is not +cyclic, so its points turn out to be identical to point~0 when $t<0$, +and identical to point~1 when $t>1$. The expression `direction~$t$ +of~\' is similar to `point~$t$ of \'; it yields a vector for the +direction of travel at time~$t$. + +{\ninepoint +\medbreak +\parshape 14 3pc 12pc 3pc 12pc +0pc 15pc 0pc 15pc 0pc 15pc 0pc 15pc 0pc 15pc 0pc 15pc +0pc 15pc 0pc 15pc 0pc 15pc 0pc 15pc 0pc 15pc 0pc 29pc +\noindent +\hbox to0pt{\hskip-3pc\dbend\hfill}% +\rightfig 8a (12pc x 12pc) ^16pt +Paths are not necessarily traversed at constant speed. For example, +the diagram at the right shows point $t$ of~$p_2$ at twenty equally +spaced values of~$t$. +\MF\ moves faster in this case at time~1.0 than at time 1.2; but the +points are spread out fairly well, so the concept of fractional +time can be useful. The diagram shows, incidentally, that +path~$p_2$ is not an especially good approximation to +a circle; there is no left-right symmetry, although the curve from point~1 +to point~2 is a mirror image of the curve from point~0 to point~1. +This lack of circularity is not surprising, since +$p_2$ was defined by simply specifying two points, $(0,0)$ and~$(3,3)$; +at least four points are needed to get a path that is convincingly round. +\parfillskip=0pt\par} + +\ddanger The ^{ampersand} operation `|&|' can be used to splice paths +together in much the same way as it concatenates strings. For example, if +you type `|p2|~|&|~|p1|', you get the path of length~3 that is obtained by +breaking the cyclic connection at the end of path~$p_2$ and attaching~$p_1$: +\begintt +(0,0)..controls (2,-2) and (5,1) + ..(3,3)..controls (1,5) and (-2,2) + ..(0,0)..controls (1,1) and (2,2) + ..(3,3) +\endtt +Concatenated paths must have identical endpoints at the junction. + +\ddanger You can even ``slow down the clock'' by concatenating subpaths +that have non-integer time specifications. For example, here's what you +get if you ask for `|subpath|~|(0,.5)| |of|~|p2| |&| |subpath| |(.5,2)| +|of|~|p2| |&| |cycle|': +\begintt +(0,0)..controls (1,-1) and (2.25,-0.75) + ..(3,0)..controls (3.75,0.75) and (4,2) + ..(3,3)..controls (1,5) and (-2,2) + ..cycle +\endtt +When $t$ goes from 0 to 1 in subpath $(0,.5)$ of $p_2$, you get the same +points as when $t$ goes from 0 to~.5 in $p_2$; when $t$ goes from 0 to 1 +in subpath $(.5,2)$ of~$p_2$, you get the same points as when $t$ goes +from .5 to~1 in~$p_2$; but when $t$ goes from 1 to~2 in subpath +$(.5,2)$ of~$p_2$, it's the same as the segment from 1 to~2 in~$p_2$. + +\danger Let's conclude this chapter by discussing the exact rules of +^{precedence} by which \MF\ decides what operations to do first. The +informal notion of ``magnetism'' gives a good intuitive picture of what +happens, but syntax rules express things unambiguously in borderline cases. + +\danger The four levels of precedence correspond to four kinds of formulas, +which are called primaries, secondaries, tertiaries, and +expressions. A {\sl^{primary}\/} is a~variable or a constant or a +tightly bound unit like `|2x|' or `|sqrt 2|'; a {\sl^{secondary}\/} +is~a primary or a sequence of primaries connected by multiplicative +operators like `|*|' or `|scaled|'; a {\sl^{tertiary}\/} is a secondary +or a sequence of secondaries connected by additive operators like `|+|' +or `|++|'; an {\sl^{expression}\/} is a tertiary or a sequence of +tertiaries connected by external operators like `|<|' or `|..|'. For example, +the expression +\begintt +a+b/2>3c*sqrt4d +\endtt +is composed of the primaries `|a|', `|b|', `|2|', `|3c|', and `|sqrt4d|'; +the last of these is a primary containing `|4d|' as a primary within itself. +The subformulas `|a|', `|b/2|', and `|3c*sqrt4d|' are secondaries; the +subformulas `|a+b/2|' and `|3c*sqrt4d|' are tertiaries. + +\danger If an expression is enclosed in parentheses, it becomes a primary +that can be used to build up larger secondaries, tertiaries, etc. + +\danger The full syntax for expressions is quite long, but most of it +falls into a simple pattern. If $\alpha$, $\beta$, and~$\gamma$ are +any ``types''---numeric, boolean, string, etc.---then \<$\alpha$ variable> +refers to a variable of type~$\alpha$, \<$\beta$ primary> refers to a +primary of type~$\beta$, and so on. Almost all of the syntax rules fit into +the following general framework: +\beginsyntax +<$\alpha$ primary>\is<$\alpha$ variable>\alt<$\alpha$ constant>% + \alt[(]<$\alpha$ expression>[)] + \alt<$\beta$ primary> +<$\alpha$ secondary>\is\<$\alpha$ primary> + \alt<$\beta$ secondary><$\gamma$ primary>\kern-1pt +<$\alpha$ tertiary>\is\<$\alpha$ secondary> + \alt<$\beta$ tertiary><$\gamma$ secondary> +<$\alpha$ expression>\is<$\alpha$ tertiary> + \alt<$\beta$ expression><$\gamma$ tertiary> +\endsyntax +These schematic rules don't give the whole story, but they do give the +general structure of the plot. + +\danger Chapter 25 spells out all of the syntax rules for all types of +expressions. We shall consider only a portion of the numeric and pair +cases here, in order to have a foretaste of the complete menu: +\def\\#1{\thinspace{\tt#1}\thinspace} +\beginsyntax +\is + \alt[\char'133]% + [,][\char'135] + \alt[length] + \alt[length] + \alt[length] + \alt[angle] + \alt[xpart] + \alt[ypart] + \alt +\is + \alt + \alt[(][)] + \alt[normaldeviate] +\is[/] + \alt +\is[sqrt]\alt[sind]\alt[cosd]\alt[mlog]\alt[mexp] + \alt[floor]\alt[uniformdeviate]\alt +\is + \alt +\is + \alt +\is[*]\alt[/] +\is + \alt + \alt +\is[+]\alt[-] +\is[++]\alt[+-+] +\is +\endsyntax +All of the finicky details about ^{fractions} and such things are made +explicit by this syntax. For example, we can use the rules to deduce that +`|sind-1/3x-2|' is interpreted as `|(sind(-(1/3x)))-2|'; notice that the +first minus sign in this formula is considered to be a ``scalar multiplication +operator,'' which comes in at the primary level, while the second one denotes +subtraction and enters in the construction of \. The +^{mediation} or ``^{of-the-way}'' operation `$t[a,b]$' is handled at the +primary level. + +\danger Several operations that haven't been discussed yet do not appear +in the syntax above, but they fit into the same general pattern; for example, +we will see later that `^|ASCII|\' and `^|xxpart|\' are additional cases of the syntax for \. +On the other hand, several operations that we have discussed in this chapter +do not appear in the syntax, because they are not primitives of \MF\ itself; +they are defined in the plain \MF\ base (Appendix B\null). For example, +`^|ceiling|' is analogous to `|floor|', and `^|**|' is analogous to~`|*|'. +Chapter~20 explains how \MF\ allows extensions to its built-in syntax, +so that additional operations can be added at will. + +\dangerexercise How does \MF\ interpret `|2|~|2|'\thinspace? +\ (There's a space between the 2's.) +\answer It's impossible to make an expression from `\ +\', because the rule for \ +specifically prohibits this. \MF\ will recognize the first `|2|' as +a \, which is ultimately regarded as a \; the other `|2|' will probably be an extra token that is +flushed away after an error message has been given. + +\ddangerexercise According to |expr.mf|, the value of `|1/2/3/4|' is +|0.66667|; the value of `|a/2/3/4|' is |0.375a|. Explain why. +\answer If a numeric token is followed by `|/|\' but +not preceded by `\|/|', the syntax allows it to become part of +an expression only by using the first case of \. Therefore `|1/2/3/4|' must be treated as `|(1/2)/(3/4)|', +and `|a/2/3/4|' must be treated as `|a/(2/3)/4|'. + +\danger The rules of \ are similar to those for +\, so it's convenient to learn them both at the same time. +\beginsyntax +\is + \alt[(][,][)] + \alt[(][)] + \alt[\char'133]% + [,][\char'135] + \alt[point][of] + \alt +\is + \alt + \alt[*] + \alt +\is[rotated] + \alt[scaled] + \alt[shifted] + \alt[slanted] + \alt[transformed] + \alt[xscaled] + \alt[yscaled] + \alt[zscaled] +\is + \alt +\is +\endsyntax + +\dangerexercise Try to guess the syntax rules for \, +\, $\langle$string tertiary$\rangle$, and \, based solely on the examples that have appeared in this +chapter. \ [{\sl Hint:}\/ The `|&|' operation has the same precedence +as `|..|'.] +\answer \\is\\parbreak +\qquad\alt\\parbreak +\def\\#1{\thinspace{\tt#1}\thinspace}% +\qquad\alt\\(\\\)\parbreak +\qquad\alt\\{substring}\\\{of}\\parbreak +\\is\\parbreak +\\is\\parbreak +\\is\\parbreak +\qquad\alt\\\{\char`\&}\\par +\medskip\noindent +(The full syntax in Chapter~25 includes several more varieties of +\ that haven't been hinted at yet.) + +\endchapter + +A maiden was sitting there who was lovely as any picture, +% ein bildsch\"one Jungfrau, +nay, so beautiful that no words can express it. +% nein so sch\"on, dass es nicht so sagen ist. +\author JAKOB and WILHELM ^{GRIMM}, {\sl Fairy Tales\/} (1815) + % Kinder- und hausm\"archen, vol 2, #166; translated by Margaret Hunt + % in Strong Hans (Der starke Hans), about 4/5 of the way through + % This quote and the next were found by online computer search at SAIL + % in the files GRIMM[lib,doc] and WUTHER[lib,doc] + +\bigskip + +He looked astonished at the expression. + % my face assumed... middle of chapter 13 +\author EMILY ^{BRONT\"E}, {\sl Wuthering Heights\/} (1847) + +\eject + \beginchapter Chapter 9. Equations + +The variables in a \MF\ program receive their values by appearing in +{\sl^{equations}}, which express relationships that the programmer +wants to achieve. We've seen in the previous chapter that algebraic +expressions provide a rich language for dealing with both numerical +and graphical relationships. Thus it is possible to express a great +variety of design objectives in precise form by stating that certain +algebraic expressions should be equal to each other. + +The most important things a \MF\ programmer needs to know about +equations are (1)~how to translate intuitive design concepts into +formal equations, and (2)~how to translate formal equations into +intuitive design concepts. In other words, it's important to be able +to {\sl write\/} equations, and it's also important to be able to +{\sl read\/} equations that you or somebody else has written. This +is not nearly as difficult as it might seem at first. The best way +to learn~(1) is to get a lot of practice with~(2) and to generalize +from specific examples. Therefore we shall begin this chapter by +translating a lot of equations into ``simple English.'' + +\newdimen\longesteq +\setbox0=\hbox{\indent$z_{12}-z_{11}=z_{14}-z_{13}$\quad} +\longesteq=\wd0 +\def\\#1\\{\medbreak\noindent + \hbox to\longesteq{\indent#1\hfil}% + \hangindent\longesteq\ignorespaces} +\medskip +\noindent\hbox to\longesteq{\indent\kern-1pt\sl Equation\hfil}% +\kern-1pt{\sl Translation}\smallskip + +\\$a=3.14$\\ +The value of $a$ should be 3.14. + +\\$3.14=a$\\ +The number 3.14 should be the value of $a$. \ (This means the same +thing as `$a=3.14$'; the left and right sides of an equation can be +interchanged without affecting the meaning of that equation in any way.) + +\\$"mode"="smoke"$\\ +The value of ^"mode" should be equal to the value of ^"smoke". \ +(Plain \MF\ assigns a special meaning to `"smoke"', so that if +^@mode\_setup@ is invoked when $"mode"="smoke"$ the computer will +prepare ``smoke proofs'' as explained in Chapter~5 and Appendix~H.) + +\\$y_3=0$\\ +The $y$ coordinate of point 3 should be zero; i.e., point~3 should +be at the ^{baseline}. \ (Point~3 is also known as~$z_3$, which is an +abbreviation for the pair of coordinates $(x_3,y_3)$, if you are +using the conventions of plain \MF\!.) + +\\$x_9=0$\\ +The $x$ coordinate of point 9 should be zero; i.e., point~9 should +be at the left edge of the type box that encloses the current character. + +\\$x_{1l}="curve\_sidebar"$\\ +The $x$ coordinate of point $1l$ should be equal to the value of the +variable called "curve\_sidebar". This puts $z_{1l}$ a certain +distance from the left edge~of the type. + +\\$x_1=x_2$\\ +Points 1 and 2 should have the same $x$ coordinate; i.e., they should +have the same horizontal position, so that one will lie directly +above or below the other. + +\\$y_4=y_5+1$\\ +Point 4 should be one pixel higher than point~5. +\ (However, points 4 and~5 might be far apart; this equation +says nothing about the relation between $x_4$ and~$x_5$.) + +\\$y_6=y_7+2"mm"$\\ +Point 6 should be two millimeters higher than point~7. \ (Plain \MF's +^@mode\_setup@ routine sets variable ^"mm" to the number of pixels in a +millimeter, based on the resolution determined by "mode" and "mag".) + +\\$x_4=w-.01"in"$\\ +Point 4 should be one-hundredth of an inch inside the right edge of +the type. \ (Plain \MF's ^@beginchar@ routine sets variable~^"w" equal +to the width of whatever character is currently being drawn, expressed in +pixels.) + +\\$y_4=.5h$\\ +Point 4 should be halfway between the baseline and the top of the type. +\ (Plain \MF's @beginchar@ sets ^"h" to the height of the +current character, in pixels.) + +\\$y_6=-d$\\ +Point 6 should be below the baseline, at the bottom edge of the type. +\ (Each character has a ``^{bounding box}'' that runs from $(0,h)$ +at the upper left and $(w,h)$ at the upper right to $(0,-d)$ and~$(w,-d)$ +at the lower left and lower right; variable~^"d" represents the depth of +the type. The values of $w$, $h$, and~$d$ might change from character to +character, since the individual pieces of type in a computer-produced +font need not have the same size.) + +\\$y_8=.5[h,-d]$\\ +Point 8 should be halfway between the top and bottom edges of the type. + +\\$w-x_5={2\over3}x_6$\\ +The distance from point 5 to the right edge of the type should be +two-thirds of the distance from point~6 to the left edge of the~type. +\ (Since $w$ is at the right edge, $w-x_5$ is the ^{distance} from +point~5 to the right edge.) + +\\$z_0=(0,0)$\\ +Point 0 should be at the ^{reference point} of the current character, +i.e., it should be on the baseline at the left edge of the type. +This equation is an abbreviation for two equations, `$x_0=0$' and `$y_0=0$', +because an equation between pairs of coordinates implies that the $x$ +and~$y$ coordinates must both agree. \ (Incidentally, plain \MF\ +defines a variable called ^"origin" whose value is $(0,0)$; hence +this equation could also have been written `$z_0="origin"$'.) + +\\$z_9=(w,h)$\\ +Point 9 should be at the upper right corner of the current character's +bounding box. + +\\$"top"\,z_8=(.5w,h)$\\ +If the pen that has currently been ``picked up'' is placed at point~8, +its top edge should be at the top edge of the type. Furthermore, +$x_8$~should be $.5w$; i.e., point~8 should be centered between the +left and right edges of the type. \ (Chapter~4 contains further +examples of `^"top"', as well as the corresponding operations +`"bot"', `"lft"', and `"rt"'.) + +\\$z_4={3\over7}[z_5,z_6]$\\ +Point 4 should be three-sevenths of the way from point~5 to point~6. + +\\$z_{12}-z_{11}=z_{14}-z_{13}$\\ +The ^{vector} that moves from point 11 to point~12 should be the same +as the vector that moves from point~13 to point~14. In other words, +point~12 should have the same direction and distance from point~11 +as point~14 has from point~13. + +\\\smash{\vtop{\hbox{$z_3-z_2=$} + \hbox{\quad$(z_4\!-\!z_2)$\thinspace rotated\thinspace 15}}}\\ +Points 3 and 4 should be at the same distance from point~2, but +the direction to point~3 should be 15~degrees counterclockwise from +the direction to point~4. + +\exercise Translate the following equations into ``simple English'': +\ (a)~$x_7-9=x_1$; \ (b)~$z_7=(x_4,.5[y_4,y_5])$; \ +(c)~$"lft"\,z_{21}="rt"\,z_{20}+1$. +\answer (a)~Point 1 should lie nine pixels to the left of point~7, +considering horizontal positions only; no information is given about the +vertical positions $y_1$ or $y_7$. \ (b)~Point~7 should sit directly +above or below point~4, and its distance up from the baseline should be +halfway between that of points 4 and~5. \ (c)~The left edge of the +currently-picked-up pen, when that pen is centered at point~21, should be +one pixel to the right of its right edge when at point~20. \ (Thus there +should be one clear pixel of white space between the images of the +pen at points 20 and~21.) + +\exercise Now see if your knowledge of equation reading gives you the +ability to write equations that correspond to the following objectives: +\ (a)~Point~13 should be just as far below the baseline as point~11 is +above the baseline. \ (b)~Point~10 should be one millimeter to the right +of, and one pixel below, point~12. \ (c)~Point~43 should be one-third of +the way from the top left corner of the type to the bottom right corner +of the type. +\answer (a) $y_{13}=-y_{11}$ (or $-y_{13}=y_{11}$, or $y_{13}+y_{11}=0$). +\ (b)~$z_{10}=z_{12}+("mm",-1)$. \ (c)~$z_{43}={1\over3}[(0,h),(w,-d)]$. + +Let's return now to the six example points $(z_1,z_2,z_3,z_4,z_5,z_6)$ +that were used so often in Chapters 2 and~3. Changing the notation +slightly, we might say that the points are +\begindisplay +$(x_1,y_1)=(0,h)$;&$(x_2,y_2)=(.5w,h)$;&$(x_3,y_3)=(w,h)$;\cr +$(x_4,y_4)=(0,0)$;&$(x_5,y_5)=(.5w,0)$;&$(x_6,y_6)=(w,0)$.\cr +\enddisplay +There are many ways to specify these points by writing a series of +equations. For example, the six equations just given would do fine; +or the short names $z_1$ through~$z_6$ could be used instead of the +long names $(x_1,y_1)$ through~$(x_6,y_6)$. But there are several +other ways to specify those points and at the same time to ``explain'' +the relations they have to each other. One way is to define the +$x$ and~$y$ coordinates separately: +\begindisplay +$x_1=x_4=0;\qquad x_2=x_5=.5w;\qquad x_3=x_6=w;$\cr +$y_1=y_2=y_3=h;\qquad y_4=y_5=y_6=0$.\cr +\enddisplay +\MF\ allows you to state several equations at once, by using more than +^^{=} one equality sign; for example, `$y_1=y_2=y_3=h$' stands for three +equations, `$y_1=y_2$', `$y_2=y_3$', and `$y_3=h$'. + +In order to define the coordinates of six points, it's necessary to +write twelve equations, because each equation contributes to the +definition of one value, and because six points have twelve coordinates +in all. However, an equation between pairs of coordinates counts as +two equations between single numbers; that's why we were able to get by +with only six `$=$'~signs in the first set of equations, while twelve +were used in the second. + +Let's look at yet another way to specify those six points, by giving +equations for their positions relative to each other: +\begindisplay +$z_1-z_4=z_2-z_5=z_3-z_6$\cr +$z_2-z_1=z_3-z_2=z_5-z_4=z_6-z_5$\cr +$z_4="origin"$; \ $z_3=(w,h)$.\cr +\enddisplay +^^"origin" First we say that the vectors from $z_4$ to~$z_1$, +from $z_5$ to~$z_2$, and from $z_6$ to~$z_3$, are equal to each other; +then we say the same thing for the vectors from $z_1$ to~$z_2$, +$z_2$ to~$z_3$, $z_4$ to~$z_5$, and $z_5$ to~$z_6$. Finally the +corner points $z_4$ and $z_3$ are given explicitly. That's a total +of seven equations between pairs of coordinates, so it should be +more than enough to define the six points of interest. + +However, it turns out that those seven equations are not enough! +For example, the six points +\begindisplay +$z_1=z_4=(0,0)$; \ $z_2=z_5=(.5w,.5h)$; \ $z_3=z_6=(w,h)$ +\enddisplay +also satisfy the same equations. A closer look explains why: +The two formulas +\begindisplay +$z_1-z_4=z_2-z_5$\qquad and\qquad $z_2-z_1=z_5-z_4$ +\enddisplay +actually say exactly the same thing. \ (Add $z_5-z_1$ to both sides +of the first equation and you get `$z_5-z_4=z_2-z_1$'.) \ Similarly, +$z_2-z_5=z_3-z_6$ is the same as $z_3-z_2=z_6-z_5$. Two of the +seven equations give no new information, so we really have specified +only five equations; that isn't enough. An additional relation +such as `$z_1=(0,h)$' is needed to make the solution unique. + +\dangerexercise (For mathematicians.) \ Find a solution to the seven +equations such that $z_1=z_2$. Also find another solution in which +$z_1=z_6$. +\answer (a) $z_1=z_2=z_3=(w,h)$; $z_4=z_5=z_6=(0,0)$. +\ (b)~$z_1=z_6=(.5w,.5h)$; $z_2=(.75w,.75h)$; $z_3=(w,h)$; +$z_4=(0,0)$; $z_5=(.25w,.25h)$. + +At the beginning of a \MF\ program, variables have no values, +except that plain \MF\ has assigned special values to variables +like "smoke" and "origin". Furthermore, when you begin a new +character with @beginchar@, any previous values that may have been +assigned to $x$ or $y$ variables are obliterated and forgotten. +Values are gradually established as the computer reads equations and +tries to solve them, together with any other equations that have already +appeared in the program. + +It takes ten equations to define the values of ten variables. +If you have given only nine equations it may turn out that none of +the ten variables has yet been determined; for example, the +nine equations +\begindisplay +$g_0=g_1=g_2=g_3=g_4=g_5=g_6=g_7=g_8=g_9$ +\enddisplay +don't tell us any of the $g$ values. However, the further equation +\begindisplay +$g_0+g_1=1$ +\enddisplay +will cause \MF\ to deduce that all ten of the $g$'s are equal to $1\over2$. + +\MF\ always computes the values of as many variables as possible, based +on the equations it has seen so far. For example, after the two equations +\begindisplay +$a+b+2c=3$;\cr +$a-b-2c=1$\cr +\enddisplay +the machine will know that $a=2$ (because the sum of these two equations is +`$2a=4$'); but all it will know about $b$ and~$c$ is that $b+2c=1$. + +At any point in a program a variable is said to be either ``^{known}'' +or ``^{unknown},'' depending on whether or not its value can be +deduced uniquely from the equations that have been stated so far. +The sample expressions in Chapter~8 indicate that \MF\ can compute a +variety of things with unknown variables; but sometimes a quantity +must be known before it can be used. For example, \MF\ can multiply +an unknown numeric or pair variable by a known numeric value, but it +cannot multiply two unknowns. + +Equations can be given in any order, except that you might sometimes +need to put certain equations first in order to make critical +values known in the others. For example, \MF\ will find the +solution $(a,b,c)=(2,7,-3)$ to the equations `$a+b+2c=3$; +$a-b-2c=1$; $b+c=4$' if you give those equations in any other order, +like `$b+c=4$; $a-b-2c=1$; $a+b+2c=3$'. But if the equations had +been `$a+b+2c=3$; $a-b-2c=1$; $a\ast(b+c)=8$', you would not have +been able to give the last one first, because \MF\ would have refused +to multiply the unknown quantity~$a$ by another unknown quantity $b+c$. +Here are the main things that \MF\ can do with unknown quantities: +\begindisplay +$-\$\cr +$\+\$\cr +$\-\$\cr +$\\ast\$\cr +$\\ast\$\cr +$\/\$\cr +$\[\,\]$\cr +$\[\,\]$\cr +\enddisplay +Some of the operations of plain \MF\!, defined in Appendix~B\null, also work +with unknown quantities. For example, it's possible to say +^"top"\thinspace\, ^"bot"\thinspace\, +^"lft"\thinspace\, ^"rt"\thinspace\, and even +\begindisplay +@penpos@\(\,\thinspace\). +\enddisplay + +\danger A \MF\ program can say `\$[a,b\mkern1mu]$' when $a-b$ is +known, and variable~$a$ can be compared to variable~$b$ in boolean +expressions ^^{comparison} like `$a0$, \MF\ chooses an +independent variable~$v_k$ for which $c_k$ is maximum, and rewrites +the equation in the form +\begindisplay +{\tt\#\#} $v_k=(\alpha-c_1v_1-\cdots-c_{k-1}v_{k-1}-c_{k+1}v_{k+1}- + \cdots-c_mv_m)/c_k$. +\enddisplay +Variable $v_k$ becomes dependent (if $m>1$) or known (if $m=1$). + +\danger Inconsistent equations are equations that have no solutions. +For example, if you say `$0=1$', \MF\ will issue an error message +^^{off by x} +saying that the equation is ``off by~1.'' A less blatant inconsistency +arises if you say, e.g., `$a=b+1$; $b=c+1$; $c=a+1$'; this last equation +is off by three, for the former equations imply that $c=b-1=a-2$. +The computer will simply ignore an inconsistent equation when you +resume processing after such an error. + +\danger Redundant equations are equations that say nothing new. +For example, `$0=0$' is redundant, and so is `$a=b+c$' if you have +previously said that $c=a-b$. \MF\ stops with an error message if +you give it a redundant equation between two numeric expressions, +because this usually indicates an oversight in the program. However, +no error is reported when an equation between pairs leads to one or +two redundant equations between numerics. For example, the equation +`$z_3=(0,h)$' will not trigger an error message when the program +has previously established that $x_3=0$ or that $y_3=h$ or both. + +\danger Sometimes you might have to work a little bit to put an equation +into a form that \MF\ can handle. For example, you can't say +\begindisplay +$x/y=2$ +\enddisplay +when $y$ is independent or dependent, because \MF\ allows ^{division} +only by known quantities. The alternative +\begindisplay +$x=2y$ +\enddisplay +says the same thing and causes the computer no difficulties; +furthermore it is a correct equation even when $y=0$. + +\ddanger \MF's ability to remember previous equations is limited to +``linear'' dependencies ^^{linear dependencies} as explained above. +A mathematician might want to introduce the condition $x\ge0$ by giving an +equation such as `$x=\mathop{\rm abs}x$'; but \MF\ is incapable +of dealing with such a constraint. Similarly, \MF\ can't cope with +an equation like `$x=\mathop{\rm floor}x$', which states that +$x$~is an integer. Systems of equations that involve the ^{absolute +value} and/or ^{floor} operation can be extremely difficult to solve, +and \MF\ doesn't pretend to be a mathematical genius. + +\ddanger The rules given earlier explain how an independent variable +can become dependent or known; conversely, it's possible for a +dependent variable to become independent again, in unusual circumstances. +For example, suppose that the equation $a+b+2c=3$ in our example above +had been followed by the equation $d=b+c+a/4$. Then there would be +two dependent variables, +\begintt +## c=-0.5b-0.5a+1.5 +## d=0.5b-0.25a+1.5 +\endtt +Now suppose that the next statement is `|numeric|~|a|', meaning that the +old value of variable~$a$ should be discarded. \MF\ can't simply delete +an independent variable that has things depending on it, so it +chooses a dependent variable to take $a$'s place; the computer prints out +\begintt +### 0.5a=-0.5b-c+1.5 +\endtt +^^{hash hash hash} meaning that $0.5a$ will be replaced by $-c-{1\over2}b ++{3\over2}$ in all dependencies, before $a$ is discarded. Variable $c$ is +now independent again; `^@showdependencies@' will reveal that the only +dependent variable is now $d$, which equals $0.75b+0.5c+0.75$. \ (This +is correct, for if the variable~$a$ is eliminated from the two given +equations we obtain $4d=3b+2c+3$.) \ The variable chosen for independence +is one that has the greatest coefficient of dependency with respect +to the variable that will disappear. + +\danger A designer often wants to stipulate that a certain point lies on +a certain line. ^^{line, point to be on} This can be done easily by +using a special feature of plain \MF\ called `^"whatever"', which +stands for an anonymous numeric variable that has a different unknown +value each time you use it. For example, +\begindisplay +$z_1="whatever"[z_2,z_3]$ +\enddisplay +states that point 1 appears somewhere on the straight line that passes +through points 2 and~3. \ (The expression $t[z_2,z_3]$ represents that +entire straight line, as $t$ runs through all values from $-\infty$ to +$+\infty$. We want $z_1$ to be equal to $t[z_2,z_3]$ for some value of~$t$, +but we don't care what value it is.) \ The expression `"whatever"$[z_2,z_3]$' +is legal whenever the difference $z_2-z_3$ is known; it's usually used +only when $z_2$ and $z_3$ are both known, i.e., when both points have been +determined by prior equations. + +\danger Here are a few more examples of equations that involve +`"whatever"', together with their translations into English. These +equations are more fun than the ``tame'' ones we considered at the +beginning of this chapter, because they show off more of the +computer's amazing ability to deduce explicit values from implicit +statements. + +\ninepoint % it's all dangerous from here on! +\setbox0=\hbox{\indent$z_7-z_6="whatever"\ast(z_3-z_2)$\quad} +\longesteq=\wd0 +\noindent\hbox to\longesteq{\indent\kern-1pt\sl Equation\hfil}% +\kern-1pt{\sl Translation}\smallskip + +\\$z_5-z_4="whatever"\ast\mathop{\rm dir}30$\\ +The angle between points 4 and~5 will be $30^\circ$ above the horizon. +\ (This equation can also be written `$z_4=z_5+"whatever"\ast\mathop{\rm +dir}30$', which states that point~4 is obtained by starting at point~5 +and moving by some unspecified multiple of ^{dir}$\,30$.) + +\\$z_7-z_6="whatever"\ast(z_3-z_2)$\\ +The line from point~6 to point~7 should be ^{parallel} to the +line from point~2 to point~3. + +\\$\penpos8("whatever",60)$\\ +The simulated pen angle at point~8 should be 60 degrees; the breadth +of the pen is unspecified, so it will be determined by other equations. + +\dangerexercise If $z_1$, $z_2$, $z_3$, and $z_4$ are known points, +how can you tell \MF\ to compute the point $z$ that lies on the +^{intersection} of the lines $z_1\to z_2$ and $z_3\to z_4$? +\answer $z="whatever"[z_1,z_2]$; $z="whatever"[z_3,z_4]$. \ (Incidentally, +it's interesting to watch this computation in action. Run \MF\ with +|\tracingequations:=|\allowbreak|tracingonline:=1| and say, for example, +\begintt +z=whatever[(1,5),(8,19)]; z=whatever[(0,17),(6,1)]; +\endtt +the solution appears as if by magic. +If you use |alpha| and |beta| in place of the whatevers, the machine will +also calculate values for "alpha" and "beta".) + +\dangerexercise Given five points $z_1$, $z_2$, $z_3$, $z_4$, and $z_5$, +explain how to compute $z$ on the line $z_1\to z_2$ such that the line +$z\to z_3$ is parallel to the line $z_4\to z_5$. +\answer $z="whatever"[z_1,z_2]$; $z-z_3="whatever"\ast(z_5-z_4)$. + +\dangerexercise What \MF\ equation says that the line between points +11 and~12 is {\sl^{perpendicular}\/} to the line between points 13 and~14? +\answer $z_{11}-z_{12}="whatever"\ast(z_{13}-z_{14})$ ^{rotated} 90, +assuming that $z_{13}-z_{14}$ is known. \ (It's also possible to say +`$(z_{11}-z_{12})\mathbin{\rm dotprod} (z_{13}-z_{14})=0$', ^^{dotprod} +although this risks overflow if the coordinates are large.) + +\dangerexercise (For mathematicians.) \ Given three points $z_1$, $z_2$, +and $z_3$, explain how to compute the distance from $z_1$ to the straight +line through $z_2$ and $z_3$. +\answer One solution constructs the point $z_4$ on $z_2\to z_3$ such +that $z_4\to z_1$ is perpendicular to $z_2\to z_3$, using ideas like +those in the previous two exercises: `$z_4="whatever"[z_2,z_3]$; +$z_4-z_1="whatever"\ast(z_3-z_2)$ rotated 90'. Then the requested distance +^^{abs} ^^{ypart}^^{angle} +is ${\rm length}(z_4-z_1)$. But there's a slicker solution: Just calculate +$$\hbox{abs ypart$((z_1-z_2)\mathbin{\rm rotated}-{\rm angle}(z_3-z_2))$.}$$ + +\ddangerexercise (For mathematicians.) \ Given three points $z_1$, +$z_2$, $z_3$, and a length~$l$, explain how to compute the two points +on the line $z_2\to z_3$ that are at distance~$l$ from $z_1$. \ (Assume +that $l$~is greater than the distance from $z_1$ to the line.) +\answer It would be nice to say simply `$z="whatever"[z_2,z_3]$' and +then to be able to say either `length$(z-z_1)=l$' or `$z-z_1=(l,0)$ +rotated "whatever"'; but neither of the second equations is legal. \ +(Indeed, there couldn't possibly be a legal solution that has this general +flavor, because any such solution would determine a unique $z$, while +there are two points to be determined.) \ The best way seems to be to +compute $z_4$ as in the previous exercise, ^^{pythagorean subtraction} and +then to let +$v=(l\mathbin{+{-}+}\mathop{\rm length} (z_4-z_1))\ast\mathop{\rm +unitvector}(z_3-z_2)$; ^^{unitvector} ^^{length} +the desired points are then $z_4+v$ and $z_4-v$. + +\ddangerexercise The applications of "whatever" that we have seen so far +have been in equations between {\sl pairs\/} of numeric values, not +in equations between simple numerics. Explain why an equation like +`$a+2b="whatever"$' would be useless. +\answer Such an equation tells us nothing new about $a$ or $b$. Indeed, +each use of "whatever" introduces a new independent variable, and +each new independent variable ``uses up'' one equation, since we need +$n$ equations to determine the values of $n$~unknowns. On the other hand +an equation between pairs counts as two equations; so there's a net +gain of one, when "whatever" appears in an equation between pairs. + +\danger All of the equations so far in this chapter have been between numeric +expressions or pair expressions. But \MF\ actually allows equations +between any of the eight types of quantities. For example, you can write +\begintt +s1="go"; s1&s1=s2 +\endtt +if $s_1$ and $s_2$ are string variables; this makes $s_1=\null$|"go"| +and $s_2=\null$|"gogo"|. Moreover, the subsequent equations +\begintt +s3=s4; s5=s6; s3=s5; s4=s1&"sh" +\endtt +will make it possible for the machine to deduce that $s_6=\null$|"gosh"|. + +\danger But nonnumeric equations are not as versatile as numeric +ones, because \MF\ does not perform operations on unknown quantities +^^{unknown quantities, nonnumeric} +of other types. For example, the equation +\begintt +"h"&s7="heck" +\endtt +cannot be used to define $s_7=\null$|"eck"|, because the ^{concatenation} +operator~|&| works only with strings that are already known. + +\ddanger After the declaration `|string| |s[]|' and the equations +`|s1=s2=s3|', the statement `|show|~|s0|' will produce the result +`|unknown| |string| |s0|'; but `|show|~|s1|' will produce `|unknown| +|string| |s2|'. Similarly, `|show|~|s2|' and `|show|~|s3|' will produce +`|unknown| |string| |s3|' and `|unknown| |string| |s1|', respectively. In +general, when several nonnumeric variables have been equated, they will +point to each other in some cyclic order. + +\endchapter + +Let ``X'' equal my father's signature. +\author FRED ^{ALLEN}, {\sl Vogues\/} (1924) % NYT review of show, Mar 28'24 + % quoted in Much Ado About Me, p288 + +\bigskip + +ALL ANIMALS ARE EQUAL +BUT SOME ANIMALS ARE MORE EQUAL THAN OTHERS +\author GEORGE ^{ORWELL}, {\sl Animal Farm\/} (1945) % Chapter 10 + +\eject + \beginchapter Chapter 10. Assignments + +Variables usually get values by appearing in equations, as described in +the preceding chapter. But there's also another way, in which `^|:=|' +is used instead of~`|=|'. For example, the |io.mf| program in Chapter~5 +said +\begintt +stem# := trial_stem * pt# +\endtt +when it wanted to define the value of |stem#|. + +The ^{colon-equal} operator `|:=|' means ``discard the previous value of +the variable and assign a new one''; we call this an {\sl^{assignment}\/} +operation. It was convenient for |io.mf| to define |stem#| with an +assignment instead of an equation, because |stem#| was getting several +different values within a single font. The alternative would have been to say +\begintt +numeric stem#; stem# = trial_stem * pt# +\endtt +(thereby specifically undefining the previous value of |stem#| before using +it in an equation); this is more cumbersome. + +The variable at the left of `|:=|' might appear also in the expression on +the right. For example, +\begintt +code := code + 1 +\endtt +means ``increase the value of "code" by 1.'' This assignment would make no +sense as an equation, since `$"code"="code"+1$' is inconsistent. The former +value of "code" is still relevant on the right-hand side when `$"code"+1$' +is evaluated in this example, because old values are not discarded until +the last minute; they are retained until just before a new assignment is made. + +\dangerexercise Is it possible to achieve the effect of `$"code":="code"+1$' +by using equations and @numeric@ declarations but not assignments? +\answer Yes, but it must be done in two steps: `@numeric@ "newcode"; +$"newcode"="code"+1$; @numeric@ "code"; $"code"="newcode"$'. + +Assignments are permitted only when the quantity at the left of the `|:=|' +is a variable. For example, you can't say `|code+1:=code|'. More +significantly, things like `|(x,y):=(0,0)|' are not permitted, although +you can say `|w:=(0,0)|' if~$w$~has been declared to be a variable of +type @pair@. This means that a statement like `|z1:=z2|' is illegal, because +it's an abbreviation for the inadmissible construction `|(x1,y1):=(x2,y2)|'; +we must remember that |z1| is not really a variable, it's a pair of variables. + +The restriction in the previous paragraph is not terribly significant, because +assignments play a relatively minor r\^ole in \MF\ programs. The best +programming strategy is usually to specify equations instead of +assignments, because equations indicate the relationships between +variables in a declarative ^^{declarative versus imperative} ^^{imperative +versus declarative} manner. A person who makes too many assignments is +still locked into the habits of old-style ``imperative'' programming +languages in which it is necessary to tell the computer exactly how to do +everything; \MF's equation +mechanism liberates us from that more complicated style of programming, +because it lets the computer take over the job of solving equations. + +The use of assignments often imposes a definite order on the statements of +a program, because the value of a variable is different before and after +an assignment takes place. Equations are simpler than assignments because +they can usually be written down in any order that comes naturally to you. + +Assignments do have their uses; otherwise \MF\ wouldn't bother with +`|:=|' at all. But experienced \MF\ programmers introduce assignments +sparingly---only when there's a good reason for doing so---because +equations are generally easier to write and more enlightening to read. + +\danger \MF's ^{internal quantities} like "tracingequations" always have +known numeric values, so there's no way to change them except by giving +assignments. The computer experiment in Chapter~9 began with +\begintt +\tracingequations:=tracingonline:=1; +\endtt +this illustrates the fact that multiple assignments are possible, just +like multiple equations. Here is the complete syntax for equations +and assignments: +\beginsyntax +\is[=] +\is[:=] +\is\alt\alt +\endsyntax +Notice that the syntax permits mixtures like `$a+b=c:=d+e$'; this is +the same as the assignment `$c:=d+e$' and the equation `$a+b=c$'. + +\ddanger In a mixed equation/assignment like `$a+b=b:=b+1$', the old +value of~$b$ is used to evaluate the expressions. For example, if $b$ equals~3 +before that statement, the result will be the same as `$a+3=b:=3+1$'; +therefore $b$ will be set to~4 and $a$~will be set to~1. + +\dangerexercise Suppose that you want variable $x_3$ to become ``like new,'' +^^{variables, reinitializing} ^^{reinitializing} ^^{independent variables} +completely independent of any value that it formerly had; but you don't +want to destroy the values of~$x_1$ and~$x_2$. You can't say `^@numeric@ +$x[\,]$', because that would obliterate all the $x_k$'s. What can you do +instead? \checkequals\xwhat\exno +\answer The assignment `$x_3:=\null$^"whatever"' does exactly what you want. + +\ddangerexercise Apply \MF\ to the short program +\begindisplay +@string@ $s[\,]$; \ $s_1=s_2=s_3=s_4$; \ $s_5=s_6$; \ $s_2:=s_5$; \ + @showvariable@ $s$; +\enddisplay +and explain the results you get. +\answer The result shows that $s_1=s_3=s_4$ and $s_2=s_5=s_6$ now: +\begintt +s[]=unknown string +s1=unknown string s3 +s2=unknown string s6 +s3=unknown string s4 +s4=unknown string s1 +s5=unknown string s2 +s6=unknown string s5 +\endtt +(The assignment $s_2:=s_5$ broke $s_2$'s former relationship with $s_1$, +$s_3$, and $s_4$.) + +\ddanger If other variables depend on $v$ when $v$ is assigned a new value, +the other variables do not change to reflect the new assignment; they still +act as if they depended on the previous (unknown) value of~$v$. For example, +if the equations `$2u=3v=w$' are followed by the assignment `$w:=6$', the +values of $u$ and~$v$ won't become known, but \MF\ will still remember the +fact that $v=.66667u$. \ (This is not a new rule; it's a consequence of +the rules already stated. When an independent variable is discarded, a +dependent variable may become independent in its place, as described in +Chapter~9.) + +\ddangerexercise Apply \MF\ to the program +\begindisplay +$"tracingequations":="tracingonline":=1$;\cr +$a=1$; \ $a:=a+b$; \ $a:=a+b$; \ $a:=a+b$;\cr +@show@ $a,b$;\cr +\enddisplay +and explain the results you get. +\answer The results are +\begindisplay +|## a=1|\cr +|## a=b+1|&(after the first assignment)\cr +|## b=0.5a-0.5|&(after the second assignment)\cr +|### -1.5a=-%CAPSULEnnnn-0.5|&(after the third assignment)\cr +|## a=%CAPSULEnnnn|&(after the third, see below)\cr +|>> a|&(after `@show@'; variable $a$ is independent)\cr +|>> 0.33333a-0.33333|&(this is the final value of $b$)\cr +\enddisplay +^^|CAPSULE| Let $a_k$ denote the value of $a$ after $k$ assignments were made. +Thus, $a_0=1$, and $a_1$ was dependent on the independent variable~$b$. +Then $a_1$ was discarded and $b$ became dependent on the independent +variable~$a_2$. The right-hand side of the third assignment was +therefore $a_2+b$. At the time $a_2$ was about to be discarded, \MF\ +had two dependencies $b=0.5a_2-0.5$ and $\kappa=1.5a_2-0.5$, where +$\kappa$ was a nameless ``^{capsule}'' inside of the computer, representing +the new value to be assigned. Since $\kappa$ had a higher coefficient +of dependency than~$b$, \MF\ chose to make $\kappa$ an independent variable, +after which $-1.5a_2$ was replaced by $-\kappa-0.5$ in all dependencies; hence +$b$ was equal to $0.33333\kappa-0.33333$. After the third +assignment was finished, $\kappa$ disappeared and $a_3$ became independent +in its place. \ (The line `|##| |a=%CAPSULEnnnn|' means that $a$~was +temporarily dependent on $\kappa$, before $\kappa$ was discarded. If +the equation $a=\kappa$ had happened to make $\kappa$ dependent on~$a$, rather +than vice versa, no ^^{hash hash} `|##|' line would have been printed; +such lines are omitted when a capsule or part of a capsule has been made +dependent, unless you have made ^"tracingcapsules"$\null>0$.) + +\endchapter + +At first his assignment had pleased, +but as hour after hour passed +with growing weariness, +he chafed more and more. +\author C. E. ^{MULFORD}, {\sl Hopalong Cassidy\/} (1910) % Chap 17 p154 + +\bigskip + +\ ::= \ := +\ ::= \ $\vert$ \\ +\ ::= \\ $\vert$ +\\ +\author PETER ^{NAUR} et al., {\sl Report % + on the Algorithmic language ALGOL 60\/} (1960) % section 4.2.1 + +\eject + \beginchapter Chapter 11. Magnification\\and\\Resolution + +A single \MF\ program can produce fonts of type for many different kinds +of printing equipment, if the programmer has set things up so that the +^{resolution} can be varied. The ``plain \MF\thinspace'' base file described +in Appendix~B establishes a set of conventions that make such variability +quite simple; the purpose of the present chapter is to explain those +conventions. + +For concreteness let's assume that our computer has two output devices. +One of them, called ^"cheapo", has a resolution of 200 pixels per +inch (approximately 8 per millimeter); the other, called ^"luxo", +has a resolution of 2000 pixels per inch. We would like to write \MF\ +programs that are able to produce fonts for both devices. For example, +if the file |newface.mf| contains a program for a new typeface, we'd +like to generate a low-resolution font by invoking \MF\ with +\begintt +\mode=cheapo; input newface +\endtt +and the same file should also produce a high-resolution font if we start with +\begintt +\mode=luxo; input newface +\endtt +instead. Other people with different printing equipment should also be +able to use |newface.mf| with their own favorite ^"mode" values. + +The way to do this with plain \MF\ is to call ^@mode\_setup@ near the +beginning of |newface.mf|; this routine establishes the values of +variables like ^"pt" and ^"mm", which represent the respective numbers of +pixels in a point and a millimeter. For example, when $"mode"= "cheapo"$, +the values will be $"pt"=2.7674$ and $"mm"=7.87402$; when $"mode"="luxo"$, +they will be $"pt"=27.674$ and $"mm"=78.74017$. The |newface.mf| program +should be written in terms of such variables, so that the pixel patterns +for characters will be about 10~times narrower and 10~times shorter in +"cheapo" mode than they are in "luxo" mode. For example, a line that's +drawn from $(0,0)$ to $(3"mm",0)$ will produce a line that's about 23.6 +pixels long in "cheapo" mode, and about 236.2 pixels long in "luxo" mode; +the former line will appear to be 3\thinspace mm long when printed by +"cheapo", while the latter will look 3\thinspace mm long when printed by +"luxo". + +A further complication occurs when a typeface is being ^{magnified}; in such +cases the font does not correspond to its normal size. For example, we might +want to have a set of fonts for "cheapo" that are twice as big as usual, +so that users can make transparencies for overhead projectors. \ (Such +output could also be reduced to 50\% of its size as printed, +on suitable reproduction equipment, thereby increasing the effective +resolution from 200 to 400.) \ \TeX\ allows entire jobs to be magnified +by a factor of~2 if the user says `|\magnification=2000|'; individual +fonts can also be magnified in a \TeX\ job by saying, e.g., ^^{TeX} +`|\font\f=newface| |scaled| |2000|'. The standard way to produce a font +with two-fold magnification using the conventions of plain \MF\ is to say, e.g., +\begintt +\mode=cheapo; mag=2; input newface; +\endtt +this will make $"pt"=5.5348$ and $"mm"=15.74803$. + +The @mode\_setup@ routine looks to see if ^"mag" has a known value; +if not, it sets $"mag"=1$. Similarly, if "mode" is unknown, +^^"proof" @mode\_setup@ sets $"mode"="proof"$. + +Plain \MF\ also computes the values of several other dimension-oriented +values in addition to "pt" and "mm", corresponding to the dimensions +that are understood by \TeX. Here is the complete list: +\begindisplay \openup 1pt +"pt"&printer's point&($\rm72.27\,pt=1\,in$)\cr +^"pc"&pica&($\rm1\,pc=12\,pt$)\cr +^"in"&inch&($\rm1\,in=2.54\,cm$)\cr +^"bp"&big point&($\rm72\,bp=1\,in$)\cr +^"cm"¢imeter&($\rm100\,cm=1\,meter$)\cr +"mm"&millimeter&($\rm10\,mm=1\,cm$)\cr +^"dd"&didot point&($\rm1157\,dd=1238\,pt$)\cr +^"cc"&cicero&($\rm1\,cc=12\,dd$)\cr +\enddisplay +In each case the values are rounded to the nearest $1\over65536$th of a pixel. + +Although such standard physical ^{dimensions} are available, they haven't +been used very much in traditional typefaces; designers usually specify +other units like `"em"' or `"x\_height"' in order to define the sizes +of letters, and such quantities generally have ad hoc values that vary +from font to font. Plain \MF\ makes it easy to introduce ^{ad hoc +dimensions} that will vary with the resolution and the magnification just +as "pt" and "mm" do; all you have to do is define ``^{sharped}'' +dimensions that have the same name as your pixel-oriented dimensions, but +with `|#|' ^^{hash} tacked on as a suffix. For example, $"em"\0$ and +$"x\_height"\0$ (typed `|em#|' and `|x_height#|'\thinspace) would be the +^{sharped dimensions} corresponding to "em" and "x\_height". Plain \MF\ has +already defined the quantities $"pt"\0$, $"pc"\0$, $"in"\0$, $"bp"\0$, +$"cm"\0$, $"mm"\0$, $"dd"\0$, and $"cc"\0$ for the standard units named above. + +Sharped dimensions like $"em"\0$ and $"x\_height"\0$ should always be +defined in terms of resolution-independent dimension variables like $"pt"\0$, +$"in"\0$, etc., so that their values do not change in any way when "mode" +and "mag" are varied. The `|#|' sign implies unchangeability. +After @mode\_setup@ has been called, +the pixel-oriented dimensions can be calculated by simply saying +\begindisplay +^@define\_pixels@("em", "x\_height"). +\enddisplay +This statement is an abbreviation for +\begindisplay +$"em":="em"\0\ast"hppp"$;&$"x\_height":="x\_height"\0\ast"hppp"$ +\enddisplay +where ^"hppp" is an internal variable of \MF\ that represents the number +of pixels per point in the horizontal dimension. Any number of ad hoc +dimensions can be listed in a single @define\_pixels@ statement. +Notice that `\#' is not an operator that could convert "em" to $"em"\0$; +rounding errors would be mode-dependent. + +Chapter 5's demonstration program |io.mf| contains several examples of ad hoc +dimensions defined in this way, and it also contains the statement +\begindisplay +^@define\_blacker\_pixels@("thin", "thick"); +\enddisplay +what's this? Well, Appendix B makes that statement an abbreviation for +\begindisplay +$"thin":="thin"\0\ast"hppp"+"blacker"$;& +$"thick":="thick"\0\ast"hppp"+"blacker"$;\cr +\enddisplay +in other words, the sharped dimensions are being unsharped in this case +by converting them to pixels and then adding `"blacker"'. The variable +^"blacker" is a special correction intended to help adapt a font to the +idiosyncrasies of the current output device; @mode\_setup@ uses the value +of "mode" to establish the value of "blacker". For example, "cheapo" mode +might want $"blacker"=0.65$, while "luxo" mode might give best results +when $"blacker"=0.1$. The general convention is to add "blacker" to +pixel-oriented variables that determine the breadth of pens and the +thickness of stems, so that the letters will be slightly darker on machines +that otherwise would make them appear too light. Different machines treat +pixels quite differently, because they are often based on quite different +physical principles. For example, the author once worked with an extremely +high-resolution device that tended to shrink stem lines rather drastically +when it used a certain type of photographic paper, and it was necessary +to set $"blacker"=4$ to get proper results on that machine; another +high-resolution device seems to want "blacker" to be only~$0.2$. Experimentation +is necessary to tune \MF's output to particular devices, but the author's +experience suggests strongly that such a correction is worthwhile. When +^^"proof" $"mode"="proof"$ or ^"smoke", the value of "blacker" is taken to +be zero, since the output in these modes is presumably undistorted. + +\exercise Does `$"mode"="cheapo"$; $"mag"=10$' produce exactly the same +font as `$"mode"="luxo"$', under the assumptions of this chapter? +\answer Almost, but not quite. The values of standard dimension variables +like "pt" and "mm" will be identical in both setups, as will the values of +ad~hoc dimension variables like "em" and "x\_height". But pen-oriented +dimensions that are defined via @define\_blacker\_pixels@ will be slightly +different, because "cheapo" mode has $"blacker"=0.65$ while "luxo" mode +has $"blacker"=0.1$ (since the "luxo" printer has different physical +characteristics). Similarly, @define\_corrected\_pixels@ (which we are +just about to discuss) will produce slightly different results in the two +given modes. + +\danger Line 7 of |io.mf| says `^@define\_corrected\_pixels@($o$)', and +this is yet a third way of converting from true physical dimensions to +pixel-oriented values. According to Appendix~B\null, variable~$o$ is +defined by the assignment +\begindisplay +$o:=\round(o\0\ast"hppp"\ast"o\_correction")+"eps"$ +\enddisplay +^^{round} ^^"eps" ^^"o" +where ^"o\_correction", like "blacker", is a magic number that depends on +the output device for which fonts are being made. On a high-resolution +device like "luxo", the appropriate value for the "o\_correction" factor +is~1; but on a low-resolution device like "cheapo", the author has obtained +more satisfactory results with $"o\_correction"=0.4$. The reason is that +`$o$' is used to specify the number of pixels by which certain features +of characters ``^{overshoot}'' the baseline or some other line to which +they are visually related. High-resolution curves look better when they +overshoot in this way, but low-resolution curves do not; therefore it is +usually wise to curtail the amount of overshoot by applying the +"o\_correction" factor. In "proof" and "smoke" modes the factor is +equal to 1.0, since these modes correspond to high resolution. + +%\danger Plain \MF\ also provides a fourth way to define unsharped +%dimensions from sharped ones, if you want the unsharped dimensions +%to be rounded to the nearest integer number of pixels: Just say +%`^@define\_whole\_pixels@'. For example, +%\begindisplay +%@define\_whole\_pixels@("foo") +%\enddisplay +%stands for `$"foo":=\round("foo"\0\ast"hppp")$'. + +\ddanger The properties of output devices are modeled also by a +parameter that's called ^"fillin", which represents the amount by which +diagonal strokes tend to be darker than horizontal or vertical strokes. +More precisely, let us say that a ``^{corner}'' pixel is one whose color +matches the color of five of its neighbors but not the other three, where the +three exceptions include one horizontal neighbor, one vertical neighbor, +and the diagonal neighbor between them. If a white corner pixel has +apparent darkness $f_1$ and if a black corner pixel has apparent darkness +$1-f_2$, then the "fillin" is $f_1-f_2$. \ (A ``true'' raster image would +have $f_1=f_2=0$, but physical properties often cause pixels to influence +their neighbors.) + +\ddanger Each output device for which you will be generating fonts should +be represented by a symbolic ^"mode" name in the implementation of \MF\ +that you are using. Since these mode names vary from place to place, they +are not standard aspects of the \MF\ language; for example, it is doubtful +whether the hypothetical "cheapo" and "luxo" modes discussed in this +chapter actually exist anywhere. The plain \MF\ base is intended to be +extended to additional modes in a disciplined way, as described at the +end of Appendix~B. + +\ddanger It's easy to create a new symbolic mode, using plain \MF's +`^@mode\_def@\kern.75pt' convention. For example, the "luxo" mode we have been +talking about could be defined by saying +\begindisplay +@mode\_def@ "luxo" $=$\cr +\quad$"pixels\_per\_inch":=2000$;&|%| high res, almost 30 per point\cr +\quad$"blacker":=.1$;&|%| make pens a teeny bit blacker\cr +\quad$"o\_correction":=1$;&|%| keep the full overshoot\cr +\quad$"fillin":=0.1$;&|%| compensate for darkened corners\cr +\quad$"proofing":=0$;&|%| no, we're not making proofs\cr +\quad$"fontmaking":=1$;&|%| yes, we are making a font\cr +\quad$"tracingtitles":=1$; \ @enddef@;&|%| yes, show titles online\cr +\enddisplay +The name of the mode should be a single symbolic token. The resolution +should be specified by assigning a value to "pixels\_per\_inch"; all other +dimension values ("pt", "mm", etc.)\ will be computed from this one by +@mode\_setup@. A mode definition should also assign values to the +internal variables "blacker", "o\_correction", and "fillin" (which describe +the device characteristics), as well as ^"proofing", ^"fontmaking", and +^"tracingtitles" (which affect the amount of output that will be produced). +In general, "proofing" and "fontmaking" are usually +set to 0 and~1, respectively, in modes that are intended for font +production rather than initial font design; "tracingtitles" is usually +0~for low-resolution fonts (which are generated quickly), but 1~for +high-resolution fonts (which go more slowly), because detailed online +progress reports are desirable when comparatively long jobs are running. + +\ddanger Besides the seven mandatory quantities `"pixels\_per\_inch"', +\dots, `"tracingtitles"' just discussed, a mode definition might assign +a value to `^"aspect\_ratio"'. In the normal case when no +"aspect\_ratio" is specified, it means that the fonts to be output +are assumed to have square pixels. But if, for +example, the @mode\_def@ sets $"aspect\_ratio":=5/4$, +it means that the output pixels +are assumed to be ^{nonsquare} in the ratio of 5 to~4; i.e., +5~vertical pixel units are equal to 4~horizontal pixel units. The +pixel-oriented dimensions of plain \MF\ are given in terms of horizontal +pixel units, so an aspect ratio of 5/4 together with 2000 pixels per inch +would mean that there are 2500 vertical pixel units per inch; a square +inch would consist of 2500 rows of pixels, with 2000 pixels in each row. \ +(Stating this another way, each pixel would be $1\over2000$ inches wide and +$1\over2500$ inches high.) \ In such a case, plain \MF\ will set the +^"currenttransform" variable so that all @draw@ and @fill@ commands +stretch the curves by a factor of 5/4 in the vertical dimension; this +compensates for the nonsquare pixels, so the typeface designer doesn't have to +be aware of the fact that pixels aren't square. + +%\ddanger A mode definition might also do other things besides setting +%the values of numeric variables like "blacker" or "aspect\_ratio". +%For example, the @mode\_def@ for "smoke" in Appendix~B includes the +%statements `@grayfont@ black; @let@ $"makebox"="maketicks"$'; +%this changes the style of proofsheets that you get in ^"smoke" mode. + +Let's look now at a concrete example, so that it will be clear how the +ideas of device-independent font design can be implemented in practice. +We shall study a file |logo.mf| that generates the seven letters of +\MF's ^{logo}. There also are ``^{parameter}'' files |logo10.mf|, |logo9.mf|, +etc., which use |logo.mf| to produce fonts in various sizes. For +example, a font containing the 10-point characters `\thinspace\MF\thinspace' +could be generated for the hypothetical "luxo" printer by running \MF\ with +the command line +\begintt +\mode=luxo; input logo10 +\endtt +if "luxo" mode really existed. + +The main purpose of |logo10.mf| is to establish the ``sharped'' values of +several ad hoc dimensions; then it inputs |logo.mf|, which does the +rest of the work. Here is the entire file |logo10.mf|: +\begintt +% 10-point METAFONT logo|smallskip +font_size 10pt#; % the "design size" of this font +ht#:=6pt#; % height of characters +xgap#:=0.6pt#; % horizontal adjustment +u#:=4/9pt#; % unit width +s#:=0; % extra space at the left and the right +o#:=1/9pt#; % overshoot +px#:=2/3pt#; % horizontal thickness of pen +input logo % now generate the font +end % and stop. +\endtt +Similar files |logo9.mf| and |logo8.mf| will produce 9-point +`\thinspace{\manual hijklmnj}\thinspace' and \hbox{8-point} +`\thinspace{\manual opqrstuq}\thinspace'; the letters get a little +wider in relation to their height, and the inter-character spacing +gets significantly wider, as the size gets smaller: +\begintt +% 9-point METAFONT logo % 8-point METAFONT logo|smallskip +font_size 9pt#; font_size 8pt#; +ht#:=.9*6pt#; ht#:=.8*6pt#; +xgap#:=.9*0.6pt#; xgap#:=.8*0.6pt#; +u#:=.91*4/9pt#; u#:=.82*4/9pt#; +s#:=.08pt#; s#:=.2pt#; +o#:=1/10pt#; o#:=1/12pt#; +px#:=.9*2/3pt#; px#:=.8*2/3pt#; +input logo input logo +end end +\endtt +It is interesting to compare the font generated by |logo10.mf| to the +font generated by |logo8.mf| with |mag=10/8|: Both fonts will have +the same values of "ht", "xgap", and "px", when the magnification has been +taken into account. But the magnified 8-point font has a slightly larger +value of "u" and a positive value of "s"; this changes +`\thinspace\MF\thinspace' to `\thinspace{\manual/0123451}\thinspace'. + +\danger Every font has a ``^{design size},'' which is a more-or-less +arbitrary number that reflects the size of type it is intended to blend +with. ^^{TeX} Users of \TeX\ select magnified fonts in two ways, either +by specifying an ``at size'' or by specifying a scale factor (times 1000). +For example, the 8-point \MF\ logo can be used at 10/8 magnification by +referring either to `|logo8| |at|~|10pt|' or to `|logo8| |scaled|~|1250|' +in a \TeX\ document. When an ``^{at size}'' is specified, the amount of +magnification is the stated size divided by the design~size. A typeface +designer can specify the design size by using plain \MF's `^@font\_size@' +command as illustrated on the previous page. \ (If no design size is +specified, \MF\ will set it to $128\pt$, by default.) + +The file |logo.mf| itself begins by defining three more ad hoc dimensions +in terms of the parameters that were set by the parameter file; these +dimensions will be used in several of the programs for individual letters. +Then |logo.mf| makes the conversion to pixel units: +\begintt +% Routines for the METAFONT logo +% (logo10.mf is a typical parameter file) +mode_setup; +ygap#:=(ht#/13.5u#)*xgap#; % vertical adjustment +leftstemloc#:=2.5u#+s#; % position of left stems +barheight#:=.45ht#; % height of bar lines +define_pixels(s,u,xgap,ygap,leftstemloc,barheight); +py#:=.9px#; define_blacker_pixels(px,py); % pen dimensions +pickup pencircle xscaled px yscaled py; logo_pen:=savepen; +define_corrected_pixels(o); +\endtt +There's nothing new here except the use of `^"savepen"' in the +second-last line; this, as we will see in Chapter~16, makes the +currently-picked-up pen available for repeated use in the +subsequent program. + +After the initial definitions just shown, |logo.mf| continues with +programs for each of the seven letters. For example, +here is the program for `{\manual ^{E}}', which illustrates the +\rightfig 11a ({224\apspix} x {216\apspix}) ^-11pt +use of $u\0$, $s\0$, $"ht"\0$, "leftstemloc", "barheight", "xgap", +and "logo\_pen": +\begintt +beginchar("E",14u#+2s#,ht#,0); +pickup logo_pen; +x1=x2=x3=leftstemloc; +x4=x6=w-x1+o; x5=x4-xgap; +y1=y6; y2=y5; y3=y4; +bot y1=0; top y3=h; +y2=barheight; +draw z6--z1--z3--z4; draw z2--z5; +labels(1,2,3,4,5,6); +endchar; +\endtt +We have seen the essentials of the {\manual M} and the {\manual T} in +Chapter~4; programs for the other letters will appear later. + +\exercise The ad hoc dimensions $"ht"\0$, $"xgap"\0$, $u\0$, $s\0$, +$o\0$, and $"px"\0$ defined in the parameter files all affect the letter +`{\manual E}' defined by this program. For each of these dimensions, +tell what would happen to the `{\manual E}' if that dimension were +increased slightly while all the others stayed the same. +\answer Increasing $"ht"\0$ would make the letter shape and the bounding +box taller; increasing $"xgap"\0$ would move point~5 to the left, thereby +making the middle bar shorter; increasing $u\0$ would make the shape and +its bounding box wider; increasing $s\0$ would widen the bounding box +at both sides without changing the letter shape; increasing $o\0$ would +move points 4,~5, and~6 to the right; increasing $"px"\0$ would make +the pen thicker (preserving the top edge of the upper bar, the bottom +edge of the lower bar, and the center of the middle bar and the stem). + +\dangerexercise Guess the program for `{\manual l}' (which is ^^{F} +almost the same as `{\manual i}'\thinspace). +\answer The only possible surprise is the position of $y_1$, +which should match similar details in the `{\manual h}' +and the~`\kern1pt{\manual j}\kern1pt' of Chapter~4: +\begintt +beginchar("F",14*u#+2s#,ht#,0); pickup logo_pen; +x1=x2=x3=leftstemloc; x4=w-x1+o; x5=x4-xgap; +y2=y5; y3=y4; bot y1=-o; top y3=h; y2=barheight; +draw z1--z3--z4; draw z2--z5; +labels(1,2,3,4,5); endchar; +\endtt + +\dangerexercise Write the complete programs for `{\manual h}' ^^{M} ^^{T} +and `\kern1pt{\manual j}\kern1pt', based on the information in Chapter~4, +but using the style of the program for `{\manual E}' above. The character +widths should be $18u\0+2s\0$ and $13u\0+2s\0$, respectively. +\checkequals\metaT\exno +\answer The quantity called "ss" in Chapter~4 is now "leftstemloc". +\begintt +beginchar("M",18*u#+2s#,ht#,0); pickup logo_pen; +x1=x2=leftstemloc; x4=x5=w-x1; x3=w-x3; +y1=y5; y2=y4; bot y1=-o; top y2=h+o; y3=y1+ygap; +draw z1--z2--z3--z4--z5; +labels(1,2,3,4,5); endchar;|smallskip +beginchar("T",13*u#+2s#,ht#,0); pickup logo_pen; +lft x1=0; x2=w-x1; x3=x4=.5w; +y1=y2=y3; top y1=h; bot y4=-o; +draw z1--z2; draw z3--z4; +labels(1,2,3,4); endchar; +\endtt + +\danger The file |logo.mf| also contains the following cryptic instructions, +which cause the letter pairs `\kern1pt{\manual jk}' and `{\manual lm}' to +be typeset closer together than their bounding boxes would imply: +\begintt +ligtable "T": "A" kern -.5u#; +ligtable "F": "O" kern -u#;|smallskip +\endtt +Without these corrections `\MF\kern1pt' would be ^^{kerning} ^^@kern@ +`{\manual hij\/kl\/mnj}\kern1pt'. Uppercase letters are often subject to +such spacing corrections, especially in logos; \TeX\ will adjust the spacing +if the typeface designer has supplied ^@ligtable@ information like this. + +\danger Finally, |logo.mf| closes with four more commands, which provide +further information about how to typeset with this font: +\begintt +font_quad 18u#+2s#; +font_normal_space 6u#+2s#; +font_normal_stretch 3u#; +font_normal_shrink 2u#; +\endtt +A ^@font\_quad@ is the unit of measure that a \TeX\ user calls one `|em|' +when this font is selected. The normal space, stretch, and shrink parameters +^^@font\_normal\_space@ ^^@font\_normal\_stretch@ ^^@font\_normal\_shrink@ +define the interword spacing when text is being typeset in this font. +Actually a font like |logo10| is rarely used to typeset anything except +the one word, `\MF\kern1pt'; but the spacing parameters have been +included just in case somebody wants to typeset a sentence like +`{\manual kn illiji jmhkjm ml hmnjknk mljin kji nmnlkj jmllii}'. + +\danger An optional `^|=|' or `^|:=|' sign may be typed after `@font\_size@', +`@font\_quad@', etc., in case you think the file looks better that way. + +\danger Notice that ``sharped'' units must be given in the ^@ligtable@ +kerning commands and in the definition of device-independent +parameters like @font\_size@ +and @font\_quad@. Appendix~F discusses the complete rules of @ligtable@ +and other commands by which \MF\ programs can send important information +to typesetting systems like \TeX. Adding these extra bits of information +to a \MF\ program after a font has been designed is something like +adding an index to a book after that book has been written and proofread. + +\ddangerexercise What's the longest English word that can be typeset +with the font |logo9|? +\answer `{\manual nmnkjmnihinj\/}'; possibly also `{\manual hijklmmjnmji}'; +and Georgia ^{Tobin} suggests that `{\manual knjiinlimllhinj\/}' +might be a legal term. + +\ninepoint % nothing but danger from here on, folks +\danger Let's summarize the general contents of |logo.mf|, now that we +have seen it all, because it provides an example of a complete typeface +description (even though there are only seven letters):\enddanger + +\smallskip +\item\bull The file begins by defining ad hoc dimensions and converting +them to pixel units, using @mode\_setup@, @define\_pixels@, etc. + +\smallskip +\item\bull Then come programs for individual letters. \ (These programs +are often preceded by macro definitions for subroutines that occur several +times. For example, we will see later that the `{\manual k}' and the +`{\manual m}' of the logo are drawn with the help of a subroutine that makes +half of a superellipse; the definition of this macro actually comes near +the beginning of |logo.mf|, just before the programs for the letters.) + +\smallskip +\item\bull Finally there are special commands like ^@ligtable@ and +^@font\_quad@, to define parameters of the font that are helpful +when typesetting. + +\smallskip +\item\bull The file is accompanied by parameter files that define +ad hoc dimensions for different incarnations of the typeface. + +\smallskip\noindent +We could make lots of different parameter files, which would produce +lots of different (but related) variations on the \MF\ logo; thus, |logo.mf| +defines a ``^{meta-font}'' in the sense of Chapter~1. + +\dangerexercise What changes would be necessary to generalize the |logo| +routines so that the bar-line height is not always 45 per~cent of the +character height? +\answer Delete the line of |logo.mf| that defines |barheight#|, and +insert that line into each of the parameter files |logo10.mf|, |logo9.mf|, +|logo8.mf|. Then other bar-line heights are possible by providing new +parameter files; another degree of ``meta-ness'' has therefore been added +to the meta-font. + +\danger ^{Assignments} (\thinspace`|:=|'\thinspace) have been used instead +of equations (\thinspace`|=|'\thinspace) in the parameter files |logo10.mf|, +|logo9.mf|, and |logo8.mf|, as well as +in the opening lines of |io.mf| in Chapter~5; this contradicts the +advice in Chapter~10, where we are told to stick to equations unless +assignments are absolutely necessary. The author has found it convenient +to develop the habit of using assignments whenever ad hoc dimensions +are being defined, because he often makes experimental files in which +the ad hoc dimensions are changed several times. For example, it's a good +idea to test a particular letter with respect to a variety of different +parameter settings when that letter is first being designed; such +experiments can be done easily by copying the ad hoc parameter definitions +from parameter files into a test file, provided that the parameters +have been defined with assignments instead of equations. + +\danger \TeX\ users have found it convenient to have fonts in a series +of magnifications that form a geometric series. A font is said +to be scaled by `^{magstep}~1' if it has been magnified by~1.2; +it is scaled by `magstep~2' if it has been magnified by $1.2\times1.2=1.44$; +it is scaled by `magstep~3' if it has been magnified by $1.2\times1.2\times1.2= +1.728$; and so on. Thus, if a job uses a font that is scaled by magstep~2, +and if that entire job is magnified by magstep~1, the font actually used +for printing will be scaled by magstep~3. The additive nature of magsteps +makes it more likely that fonts will exist at the desired sizes when +jobs are magnified. Plain \MF\ supports this convention by allowing +constructions like +\begintt +\mode=cheapo; mag=magstep 2; input logo9 +\endtt +if you want to generate the 9-point \MF\ logo for the "cheapo" printer, +magnified by 1.44 (i.e., by magstep~2). You can also write `|magstep|~|0.5|' +^^{TeX} for what \TeX\ calls `|\magstephalf|'; this magnifies by $\sqrt{1.2}$. + +\ddanger The sharped forms of dimensions are actually represented by plain +\MF\ in terms of printer's points, so that `$"pt"\0$' turns out to be +equal to~1. However, it is best for programmers not to make use of this +fact; a program ought to say, e.g., `$"em"\0:=10"pt"\0$', even though +the `$"pt"\0$' in this construction is redundant, and even though the +computer would run a few microseconds faster without it. + +\ddangerexercise Suppose you want to simulate a low-resolution printer +on a high resolution device; for concreteness, let's say that +"luxo" is supposed to produce the output of "cheapo", with each black +"cheapo" pixel replaced by a $10\times10$ square of black "luxo" pixels. +Explain how to do this to the |logo10| font, by making appropriate +changes to |logo.mf|. Your output file should be called |cheaplogo10.2000gf|. +\answer (This is tricky.) \ Insert the lines +\begintt +if known pixmag: begingroup interim hppp:=pixmag*hppp; + special "title cheapo simulation" endgroup; + extra_endchar:="currentpicture:=currentpicture scaled pixmag;" + & "w:=w*pixmag;" & extra_endchar; fi +\endtt +right after `|mode_setup|' in |logo.mf|, and also include the line +\begintt +if known pixmag: hppp:=pixmag*hppp; vppp:=pixmag*vppp; fi +\endtt +at the very end of that file. Then run \MF\ with +\begintt +\mode="cheapo"; input cheaplogo10 +\endtt +where the file `|cheaplogo10.mf|' says simply `|pixmag=10;| |input| |logo10|'. +\ (The interim "hppp" setting and the ^@special@ command are +used to fool \MF\ into giving the appropriate extension to the +^|gf| file name. Incidentally, you could print with this font on "cheapo" +at ten-fold magnification if you told \TeX\ to use the font `|cheaplogo10| +|scaled| |10000|'; but on "luxo" you would simply call this font +`|cheaplogo10|'.) + +\endchapter + +A great Temptation must be withstood with great Resolution. +\author WILLIAM ^{BURKITT}, {\sl Expository Notes on the New Testament\/} % + (c.\thinspace1700) % commenting on Matt 4:10 + % I examined only the fifth edition (1712), title page says `New-Testament' + % Another edition printed at New Haven in 1794 says `should' not `must'! + +\bigskip + +What some invent, the rest enlarge. +\author JONATHAN ^{SWIFT}, {\sl Journal of a Modern Lady\/} (1729) % line 145 + +\eject + \beginchapter Chapter 12. Boxes + +\looseness=-1 +Let's pause now to take a closer look at the ``bounding boxes'' that enclose +individual characters. In olden days, metal type was cast on a +rectangular body in which each piece of type had the same vertical +extent, although the type widths would vary from character to character. +Nowadays we are free of the mechanical constraints imposed by metal type, +but the former metaphors are still useful: A~typesetting system like ^^{TeX} +\TeX\ imagines that each character fits into a rectangular box, and words are +typeset by putting such boxes snugly next to each other. + +% Here are some macros borrowed from The TeXbook +\def\dolist{\afterassignment\dodolist\let\next= }% +\def\dodolist{\ifx\next\endlist \let\next\relax + \else \\\let\next\dolist \fi + \next} +\def\endlist{\endlist} +\def\\{\expandafter\if\space\next\ \else \setbox0=\hbox{\next}\maketypebox\fi} +\def\demobox#1{\setbox0=\hbox{\dolist#1\endlist}% + \copy0\kern-\wd0\makelightbox} + The main difference +between the old conventions and the new~ones is that type boxes are now +allowed to vary in height as well as in width. For example, when \TeX\ +typesets `A~line~of~type.' it puts boxes together that essentially look +like this: `\thinspace\demobox{A line of type.}\thinspace'. \ (The `A' +appears in a box `\thinspace\setbox0\hbox{A}\maketypebox\thinspace' that +sits on a given baseline, while the `y' appears in a box +`\thinspace\setbox0\hbox{y}\maketypebox\thinspace' that descends below the +baseline.) \ \TeX\ never looks inside a box to see what character actually +appears there; \TeX's job is to put boxes together in the right places +on a page, based only on the box sizes. It is a typeface designer's job +to decide how big the boxes should be and to create the characters inside +the boxes. + +Boxes are two-dimensional objects, but we ascribe three dimensions to them +because the vertical component is divided into two quantities, the +{\sl^{height}\/} (above the ^{baseline}) and the {\sl^{depth}\/} +(below the baseline). The horizontal dimension is, of course, called +the {\sl^{width}}. Here is a picture of a typical box, showing its +so-called ^{reference point} and baseline: + +{\eightpoint +\setbox0=\hbox{$\uparrow$} +\setbox1=\hbox to \wd0{$\hss\mid\hss$} % with luck, they'll line up +\setbox2=\vbox{\copy0 + \nointerlineskip \kern-.5pt \copy1 + \nointerlineskip \kern-.5pt \copy1 + \moveleft 1em\hbox{height} + \copy1 \nointerlineskip \kern-.5pt + \copy1 \nointerlineskip \kern-.5pt + \hbox{$\downarrow$} + \kern.2pt} +\setbox3=\vbox{\kern.2pt\copy0 + \moveleft 1em\hbox{depth} + \hbox{$\downarrow$} + \kern0pt} +\setbox4=\vtop{\kern-3pt % this cancels the null text above the samplebox + \hbox{\samplebox{\ht2}{\ht3}{6em}{}% + \kern-6em + \raise3pt\hbox to 6em{\hss Baseline\hss}} + \kern3pt + \arrows{6em}{width}} +\medskip\indent +\setbox0=\hbox{$\vcenter{}$}% \ht0 is the axis height +\lower\ht0\hbox{Reference point$-$\kern-.2em$\rightarrow$\kern2pt}% +\raise\ht2\box4 +\kern1.5em +\raise\ht2\vtop{\kern0pt\box2\nointerlineskip\box3}} + +\medskip\noindent +The example characters in previous chapters have all had zero depth, but +we will soon be seeing examples in which both height and depth are relevant. + +A character shape need not fit inside the boundaries of its box. Indeed, +{\it italic\/} and {\sl slanted\/} letters are put into ordinary boxes +just as if they were not slanted, so they frequently stick out at the right. +For example, the letter `g\/' in the font you are now reading (^|cmr10|) +can be compared with the `{\sl g\/}' in the corresponding slanted +font (^|cmsl10|): +\begindisplay +\vbox to 40pt{\ifproofmode\hrule\vfill + \hsize=2.5in \baselineskip 6pt \fiverm\noindent + (A figure will be inserted here; too bad you can't see it now. + It shows two g's, as claimed. In fact, the same figure appeared + on page 63 of The TeXbook.) + \vfill\hrule\fi} +\enddisplay +The slanted `{\sl g\/}' has been drawn as if its box were skewed right at the +top and left at the bottom, keeping the baseline fixed; but \TeX\ is told +in both cases that the box is $5\pt$ wide, $4.3055\pt$ high, and $1.9444\pt$ +deep. Slanted letters will be spaced properly in spite of the fact that their +boxes have been straightened up, because the letters will match correctly +at the baseline. + +\danger Boxes also have a fourth dimension called the {\sl^{italic +correction}}, which gives \TeX\ additional information about whether or +not a letter protrudes at the right. For example, the italic correction +for an unslanted `g\/' in |cmr10| is $0.1389\pt$, while the corresponding +slanted letter in |cmsl10| has an italic correction of $0.8565\pt$. The +italic correction is added to a box's width when math formulas like ${\rm +g}^2$ or ${\sl g}^2$ are being typeset, and also in other cases as +explained in {\sl The \TeX book}. + +Plain \MF's ^@beginchar@ command establishes the width, height, and depth +of a box. These dimensions should be given in terms of ``^{sharped}'' +quantities that do not vary with the resolution or magnification, because +the size of a character's type box should not depend in any way on the device +that will be used to output that character. It is important to be able to +define documents that will not change even though the technology for printing +those documents is continually evolving. \MF\ can be used to produce fonts for +new devices by introducing new ``modes,'' as we have seen in Chapter~11, +but the new fonts should still give the same box dimensions to each character. +Then the device-independent files output by \TeX\ will not have to be +changed in any way when they are printed or displayed with the help of +new equipment. + +The three dimensions in a @beginchar@ command are given in reverse +alphabetical order: First comes the width, then the height, then the depth. +The @beginchar@ routine converts these quantities into pixel units +and assigns them to the three variables ^"w", ^"h", and~^"d". In fact, +@beginchar@ rounds these dimensions to the nearest whole number of +pixels; hence $w$, $h$, and~$d$ will always be integers. + +\MF's pixels are like squares on ^{graph paper}, with pixel boundaries +at points with integer coordinates. The left edge of the type box lies +on the line $x=0$, and the right edge lies on the line $x=w$; we have +$y=h$ on the top edge and $y=-d$ on the bottom edge. There are $w$ pixels +in each row and $h+d$ in each column, so there are exactly $wh+wd$ pixels +inside the type box. + +Since $w$, $h$, and $d$ are integers, they probably do not exactly match +the box dimensions that are assumed by device-independent typesetting +systems like \TeX. Some characters will be a fraction of a pixel too wide; +others will be a fraction of a pixel too narrow. However, it's still possible +to obtain satisfactory results if the pixel boxes are stacked together +based on their $w$ values and if the accumulated error is removed in the +spaces between words, provided that the box positions do not ^{drift} +too far away from their true device-independent locations. A designer should +strive to obtain letterforms that work well together when they are placed +together in boxes that are an integer number of pixels wide. + +\ddanger You might not like the value of $w$ that @beginchar@ computes by +rounding the device-independent width to the nearest pixel boundary. +For example, you might want to make the letter~`m' one pixel wider, at +certain resolutions, so that its three stems are equally spaced or so that +it will go better with your `n'. In such a case you can assign a new value +to~$w$, at any time between @beginchar@ and ^@endchar@. This new value +will not affect the device-independent box width assumed by \TeX, but it +should be respected by the software that typesets ^|dvi| files using your font. + +\def\hidecoords(#1,#2){\hbox to 0pt{\hss$\scriptstyle(#1,#2)$\hss}} +\setbox0=\vtop{\kern -94pt + \rightline{\vbox{\hbox to 140\apspix{\hidecoords(0,h)\hfil + \hidecoords(w\mkern-2mu,h)} + \kern3pt + \figbox{12a}{140\apspix}{360\apspix}\vbox + \kern-3pt + \hbox to 140\apspix{\hidecoords(0,-d)\hfil + \hidecoords(w\mkern-2mu,-d)}}\quad}} +\dp0=0pt + +Here's an example of a character that has nonzero width, height, and depth; +it's the left ^{parenthesis} in ^{Computer Modern} fonts like |cmr10|. +Computer Modern typefaces are generated by \MF\ programs that involve +lots of parameters, so this example also illustrates the principles of +``^{meta-design}'': Many different varieties of left parentheses can be +drawn by this one program. But let's focus our attention first on the +comparatively simple way in which the box dimensions are established and +used, before looking into the details of how a meta-parenthesis has +actually been specified. +% "hair", "thin", "thick" are actually "vair", "hair", "stem" in the code +\def\xs(#1,#2){\{(z_{#1}-z_{#2})\,{\rm xscaled}\,3\}}% +\begindisplay +|"Left parenthesis"|;\cr +@numeric@ $"ht"\0$, $"dp"\0$;\cr +$"ht"\0="body\_height"\0$; \ $.5["ht"\0,-"dp"\0]="axis"\0$;\cr +@beginchar@\kern1pt(|"("|$,7u\0,"ht"\0,"dp"\0)$;\cr +@italcorr@ $"ht"\0\ast"slant"-.5u\0$;\cr +@pickup@ "fine.nib";\cr +$\penpos1("hair"-"fine",0)$;\strut\vadjust{\box0}\cr +$\penpos2(.75["thin","thick"]-"fine",0)$;\cr +$\penpos3("hair"-"fine",0)$;\cr +$\mathop{"rt"}x_{1r}=\mathop{"rt"}x_{3r}= w-u$; \ + $\mathop{"lft"}x_{2l}=x_1-4u$;\cr +$\mathop{"top"}y_1=h$; \ + $y_2=.5[y_1,y_3]="axis"$;\cr +@filldraw@ $z_{1l}\xs(2l,1l)\ldots z_{2l}$\cr +\qquad$\ldots\xs(3l,2l)z_{3l}$\cr +\qquad$\dashto z_{3r}\xs(2r,3r)\ldots z_{2r}$\cr +\qquad$\ldots\xs(1r,2r)z_{1r}\dashto\cycle$;\cr +@penlabels@$(1,2,3)$; \ @endchar@;\cr +\enddisplay + +The width of this left parenthesis is $7u\0$, where $u\0$ +is an ad hoc parameter that figures in all the widths of the Computer +Modern characters. The height and depth have been calculated in such a way +that the top and bottom of the bounding box are equally distant from an +imaginary line called the {\sl^{axis}}, which is important in mathematical +typesetting. \ (For example, \TeX\ puts the bar line at the axis +in fractions like $1\over2$; many symbols like `$+$' and `$=$', as well as +parentheses, are centered on the axis line.) \ Our example program puts the +axis midway between the top and bottom of the type by saying that +`$.5["ht"\0,-"dp"\0]="axis"\0$'. We also place the top at position +`$"ht"\0="body\_height"\0$'\thinspace; here $"body\_height"\0$ is the +height of the tallest characters in the entire typeface. +It turns out that $"body\_height"\0$ is exactly $7.5"pt"\0$ in |cmr10|, and +$"axis"\0=2.5"pt"\0$; hence $"dp"\0=2.5"pt"\0$, +and the parenthesis is exactly $10\pt$ tall. + +The program for `(' uses a ^@filldraw@ command, which we haven't +seen before in this book; it's basically a combination of @fill@ +and @draw@, where the filling is done with the currently-picked-up pen. +Some of the Computer Modern fonts have characters with ``^{soft}'' edges +while others have ``^{crisp}'' edges; the difference is due to the pen that +is used to @filldraw@ the shapes. This pen is a circle whose diameter +is called ^"fine"; when "fine" is fairly large, @filldraw@ will produce +rounded corners, but when $"fine"=0$ (as it is in |cmr10|) the corners +will be sharp. +% (actually it isn't zero in cmr10, but this makes a better example) + +The statement `$\penpos1("hair"-"fine",0)$' makes the breadth of a +simulated broad-edge pen equal to $"hair"-"fine"$ at position~1; i.e., +the distance between $z_{1l}$ and $z_{1r}$ will be $"hair"-"fine"$. +We will be filling a region between $z_{1l}$ and $z_{1r}$ with a +circle-shaped pen nib whose diameter is "fine"; the center of that +nib will pass through $z_{1l}$ and $z_{1r}$, hence the pen will +effectively add ${1\over2}"fine"$ to the breadth of the stroke at +either side. The overall breadth at position~1 will therefore be +${1\over2}"fine"+("hair"-"fine")+{1\over2}"fine"\;=\;"hair"$. +(Computer Modern's ``^{hairline} thickness'' parameter, which governs +the breadth of the thinnest strokes, is called "hair".) \ Similarly, +the statement `$\penpos2(.75["thin","thick"]-"fine",0)$' makes the +overall breadth of the pen at position~2 equal to $.75["thin","thick"]$, +which is $3\over4$ of the way between two other parameters that govern +stroke breadths in Computer Modern routines. If "fine" is increased while +"hair", "thin", and "thick" stay the same, the effect will simply be to +produce more rounded corners at positions 1 and~3, with little or no effect +on the rest of the shape, provided that "fine" doesn't get so large +that it exceeds "hair". + +\def\paren #1 #2 #3 #4 #5 #6 #7 #8 #9 +{\vbox{\dimen0=#3\apspix \hsize=7\dimen0 + \centerline{\tt#1} + \medskip \kern3pt \kern270\apspix \kern-#4\apspix + \dimen2=#4\apspix \advance\dimen2 by -#5\apspix + \figbox{#2}{7\dimen0}{2\dimen2}\vbox + \kern-2\dimen2 \kern#4\apspix \kern90\apspix \kern-3pt \medskip + \tabskip 0pt plus 1fil + \halign to\hsize{$##$\cr + u=\hfil#3\cr + "ht"=\hfil#4\cr + "axis"=\hfil#5\cr + "fine"=\hfil#6\cr + "hair"=\hfil#7\cr + "thin"=\hfil#8\cr + "thick"=\hfil#9\cr}}} + +Here, for example, are five different left parentheses, drawn by our example +program with various settings of the parameters: +$$\line{\paren cmr10 12a 20 270 90 0 8 9 25 + \hfil\paren cmbx10 12b 23 270 90 0 13 17 41 + \hfil\paren cmvtt10 12c 21 250 110 22 22 25 25 + \hfil\paren cmssdc10 12d 19 270 95 8 23 40 40 + \hfil\paren cmti10 12e 18.4 270 90 7 8 11 23 }$$ +Parameter values are shown here in ^"proof"\kern-1pt\ mode pixel units, +36 to the point. \ (Thus, for example, the value of $u\0$ in |cmr10| is +${20\over36}"pt"\0$.) \ Since |cmbx10| is a ``bold extended'' font, +its unit width~$u$ is slightly larger than the unit width of |cmr10|, +and its pen widths (especially "thick") are significantly larger. +The ``variable-width typewriter'' font |cmvtt10| has soft edges and +strokes of almost uniform thickness, because "fine" and "hair" are almost +as large as "thin" and "thick". This font also has a raised axis and a smaller +height. An intermediate situation occurs in |cmssdc10|, a ``sans serif +demibold condensed'' font that is similar to the type used in the chapter titles +of this book; $"thick"="thin"$ in this font, but hairlines are noticeably +thinner, and "fine" provides slightly rounded corners. The ``text italic'' +font |cmti10| has rounded ends, and the character shape has been ^{slanted} +by .25; this means that each point $(x,y)$ has been moved to position +$(x+.25y,y)$, in the path that is filled by @filldraw@. + +\danger The vertical line just to the right of the italic left parenthesis +shows the ^{italic correction} of that character, i.e., the fourth box +dimension mentioned earlier. This quantity was defined by the statement +`^@italcorr@ $"ht"\0\ast"slant"-.5u\0$' in our program; here ^"slant" is +a parameter of Computer Modern that is zero in all the unslanted fonts, +but $"slant"=.25$ in the case of |cmti10|. The expression following +@italcorr@ should always be given in sharped units. If the value is +negative, the italic correction will be zero; otherwise the italic +correction will be the stated amount. + +\danger The author has obtained satisfactory results by making the italic +correction roughly equal to $.5u$ plus the maximum amount by which the +character sticks out to the right of its box. For example, the top right +end of the left parenthesis will be nearly at position $(w-u,"ht")$ before +slanting, so its $x$~coordinate after slanting will be $w-u+"ht"\ast"slant"$; +this will be the rightmost point of the +character, if we assume that $"slant"\ge0$. Adding $.5u$, subtracting~$w$, +and rewriting in terms of sharped units gives the stated formula. Notice +that when $"slant"=0$ the statement reduces to `@italcorr@ $-.5u\0$'; +this means that unslanted left parentheses will have an italic correction +of zero. + +\dangerexercise Write a program for right parentheses, to go with these +left parentheses. +\answer The changes are straightforward, except for the italic correction +(for which a rough estimate like the one shown here is good enough): +\def\xs(#1,#2){\{(z_{#1}-z_{#2})\,{\rm xscaled}\,3\}}% +\begindisplay +|"Right parenthesis"|;\cr +@numeric@ $"ht"\0,"dp"\0$; \ + $"ht"\0="body\_height"\0$; \ + $.5["ht"\0,-"dp"\0]="axis"\0$;\cr +@beginchar@\kern1pt(|")"|$,7u\0,"ht"\0,"dp"\0)$; + \ @italcorr@ $"axis"\0\ast"slant"-.5u\0$;\cr +@pickup@ "fine.nib"; \ $\penpos1("hair"-"fine",0)$;\cr +$\penpos2(.75["thin","thick"]-"fine",0)$; \ $\penpos3("hair"-"fine",0)$;\cr +$\mathop{"lft"}x_{1l}=\mathop{"lft"}x_{3l}=u$; \ + $\mathop{"rt"}x_{2r}=x_1+4u$; \ +$\mathop{"top"}y_1=h$; \ + $y_2=.5[y_1,y_3]="axis"$;\cr +@filldraw@ $z_{1l}\xs(2l,1l)\ldots z_{2l}\ldots\xs(3l,2l)z_{3l}$\cr +\qquad$\dashto z_{3r}\xs(2r,3r) + \ldots z_{2r}\ldots\xs(1r,2r)z_{1r}\dashto\cycle$;\cr +@penlabels@$(1,2,3)$; \ @endchar@;\cr +\enddisplay +We will see in Chapter 15 that it's possible to guarantee perfect symmetry +between left and right parentheses by using picture transformations. + +The reader should bear in mind that the conventions of plain \MF\ and of +Computer Modern are not hardwired into the \MF\ language; they are merely +examples of how a person might use the system, and other typefaces may well +be better served by quite different approaches. Our program for left +parentheses makes use of @beginchar@, @endchar@, @italcorr@, @penlabels@, +@pickup@, "penpos", "lft", "rt", "top", "z", and @filldraw@, all of which +are defined somewhat arbitrarily in Appendix~B as part of the plain base; +it also uses the quantities "u", "body\_height", "axis", "fine", "hair", +"thin", "thick", and "slant", all of which are arbitrary parameters that +the author decided to introduce in his programs for Computer Modern. Once +you understand how to use arbitrary conventions like these, you will be +able to modify them to suit your own purposes. + +\exercise (For people who know \TeX.) \ It's fairly clear that the width of +a type box is important for typesetting, but what use does \TeX\ make of +the height and depth? +\answer When horizontal lines are being typeset, \TeX\ keeps track of the +maximum height and maximum depth of all boxes on the line; this determines +whether or not extra space is needed between baselines. The height and depth +are also used to position an accent above or below a character, and to +place symbols in mathematical formulas. Sometimes +boxes are also stacked~up vertically, in which case their heights and depths +are just as important as their widths are for horizontal setting. + +\ddanger The primitive commands by which \MF\ actually learns the dimensions +of each box are rarely used directly, since they are intended to be embedded +in higher-level commands like @beginchar@ and @italcorr@. But if you must +know how things are done at the low level, here is the secret: There are +four internal quantities called ^"charwd", ^"charht", ^"chardp", and ^"charic", +whose values at the time of every ^@shipout@ command are assumed to be the +box dimensions for the character being shipped out, in units of printer's +points. \ (See +the definitions of @beginchar@ and @italcorr@ in Appendix~B for examples +of how these quantities can be manipulated.) + +\ninepoint % all dangerous from here on +\ddanger Besides "charwd" and its cousins, \MF\ also has four other +internal variables whose values are recorded at the time of every +@shipout@:\enddanger + +\smallskip\textindent\bull^"charcode" is rounded to the nearest integer +and then converted to a number between 0 and~255, by adding or subtracting +multiples of~256 if necessary; this ``$c$~code'' is the ^{location} of the +^^{c code} character within its font. + +\smallskip\textindent\bull^"charext" is rounded to the nearest integer; +the resulting number is a secondary code that can be used to distinguish +between two or more characters with equal $c$ codes. \ (\TeX\ ignores +"charext" and assumes that each font contains at most 256 characters; but +extensions to \TeX\ for ^{oriental} languages can use "charext" to handle +much larger fonts.) + +\smallskip\textindent\bull^"chardx" and "chardy" represent horizontal and +vertical {\sl escapement\/} in units of pixels. \ (Some typesetting +systems use both of these device-dependent amounts to change their current +position on a page, just after typesetting each character. Other systems, +like the ^|dvi| software associated with \TeX, assume that $"chardy"=0$ +but use "chardx" as the horizontal escapement whenever a horizontal +movement by "chardx" does not cause the subsequent position to ^{drift} +too far from the device-independent position defined by accumulated +"charwd" values. Plain \MF's @endchar@ routine keeps $"chardy"=0$, but +sets $"chardx":=w$ just before shipping a character to the output. This +explains why a change to~^"w" will affect the spacing between adjacent +letters, as discussed earlier.) \looseness=-1 + +\ddanger Two characters with the same $c$ code +should have the same box dimensions and escapements; otherwise +the second character will override the specifications of the first. The boolean +expression `^{charexists}~$c$' can be used to determine whether or not +a character with a particular $c$~code has already been shipped out. + +\danger Let's conclude this chapter by contemplating a \MF\ program that +generates the ``^{dangerous bend}'' symbol, since that symbol appears so +often in this book. It's a custom-made character intended to be used only at +the very beginnings of paragraphs in which the baselines of the text are +exactly $11\pt$ apart. Therefore it extends below its baseline by $11\pt$; +but it is put into a box of depth zero, because \TeX\ would otherwise +think that the first line of the paragraph contains an extremely deep +character, and such depth would cause the second line to be moved down. +$$\def\comment{\hfill{\tt\%} } +\halign{\hbox to\hsize{\indent#\hfil}\cr +$"baselinedistance"\0:=11"pt"\0$; \ ^@define\_pixels@("baselinedistance");\cr +$"heavyline"\0:=50/36"pt"\0$; \ ^@define\_blacker\_pixels@("heavyline");\cr +$@beginchar@\kern1pt(127,25u\0,"h\_height"\0+"border"\0,0)$; \ + |"Dangerous bend symbol"|;\cr +\pickup @pencircle@ scaled "rulethickness"; + \ $\mathop{"top"}y_1={25\over27}h$; \ $\mathop{"lft"}x_4=0$;\cr +$x_1+x_1=x_{1a}+x_{1b}=x_{4b}+x_{2a}=x_4+x_2=x_{4a}+x_{2b}=x_{3b}+x_{3a}= + x_3+x_3=w$;\cr +$x_{4a}=x_{4b}=x_4+u$; \ $x_{3b}=x_{1a}=x_1-2u$;\cr +$y_4+y_4=y_{4a}+y_{4b}=y_{3b}+y_{1a}=y_3+y_1=y_{3a}+y_{1b}=y_{2b}+y_{2a}= + y_2+y_2=0$;\cr +$y_{1a}=y_{1b}=y_1-{2\over27}h$; \ $y_{4b}=y_{2a}= + y_4+{4\over27}h$;\cr +@draw@ $z_{1a}\to z_1\to z_{1b}\ddashto z_{2a}\to z_2\to z_{2b}\ddashto$\cr +\indent $z_{3a}\to z_3\to z_{3b}\ddashto z_{4a}\to z_4\to z_{4b} + \ddashto \rm cycle$;\comment the signboard\cr +$x_{10}=x_{11}=x_{12}=x_{13}=.5w-u$; + \ $x_{14}=x_{15}=x_{16}=x_{17}=w-x_{10}$;\cr +$y_{10}=y_{14}={28\over27}h$; \ $\mathop{"bot"}y_{13}=-"baselinedistance"$;\cr +$z_{11}=(z_{10}\to z_{13})\;{\rm intersectionpoint}\; + (z_{1a}\{z_{1a}-z_{4b}\}\to z_1\{"right"\})$;\cr +$y_{15}=y_{11}$; \ $y_{16}=y_{12}=-y_{11}$; \ $y_{17}=y_{20}=y_{21}=y_{13}$;\cr +@draw@ $z_{11}\dashto z_{10}\dashto z_{14}\dashto z_{15}$; + @draw@ $z_{12}\dashto z_{13}$; + @draw@ $z_{16}\dashto z_{17}$; \comment the signpost\cr +$x_{20}=w-x_{21}$; \ $x_{21}-x_{20}=16u$; + \ @draw@ $z_{20}\dashto z_{21}$; \comment ground level\cr +$x_{36}=w-x_{31}$; \ $x_{36}-x_{31}=8u$; + \ $x_{32}=x_{33}=x_{36}$; \ $x_{31}=x_{34}=x_{35}$;\cr +$y_{31}=-y_{36}={12\over27}h$; \ $y_{32}=-y_{35}={9\over27}h$; + \ $y_{33}=-y_{34}={3\over27}h$;\cr +\pickup @pencircle@ scaled "heavyline";\cr +@draw@ $z_{32}\{z_{32}-z_{31}\}\to z_{33}\ddashto + z_{34}\to z_{35}\{z_{36}-z_{35}\}$; + \comment the dangerous bend\cr +\pickup ^@penrazor@ xscaled "heavyline" + ^{rotated} (^{angle}$(z_{32}-z_{31})+90$);\cr +@draw@ $z_{31}\dashto z_{32}$; + \ @draw@ $z_{35}\dashto z_{36}$; \comment upper and lower bars\cr +^@labels@$(1a,1b,2a,2b,3a,3b,4a,4b,@range@ 1 @thru@ 36)$; \ @endchar@; +^^@range@^^@thru@\cr +}$$ + +\setbox0=\vtop{\kern -5pt + \figbox{12f}{500\apspix}{4.2in}\vbox} +\dp0=0pt +\vskip 18pt + +{\tolerance=2000 \hbadness=2000 \spaceskip=.3333em plus .25em minus .12em +\hangindent 515\apspix +\noindent\hbox to 515\apspix{\box0\hfil}% +This program has several noteworthy points of~interest: +(1)~The first parameter to ^@beginchar@ here is 127, not a +string; this puts the character into font ^{location}~127. \ (2)~A sequence +of equations like `$a=w-b$; $a'=w-b'$' can conveniently be shortened to +`$a+b=a'+b'=w$'. \ (3)~Three hyphens `$\ddashto$' is an abbreviation for a +line with ``infinite'' tension, ^^{---} i.e., an almost straight line that +connects smoothly to its curved neighbors. \ (4)~An `intersectionpoint' +operation finds out where ^^{intersectionpoint} +two paths cross; we'll learn more about this in Chapter~14.\par} + +\endchapter + +Well, we are in the same box. +\author RIDER ^{HAGGARD}, {\sl Dawn\/} (1884) % beginning of chapter 47 + +\bigskip + +A story, too, +may be boxed. +\author DOROTHY ^{COLBURN}, {\sl Newspaper Nomenclature\/} (1927) + % American Speech v2 Feb 27 p240 + +\eject + \beginchapter Chapter 13. Drawing, Filling,\\and Erasing + +The pictures that \MF\ produces are made up of tiny pixels that are either +``on'' or ``off''; therefore you might imagine that the computer works +behind the scenes with some sort of ^{graph paper}, and that it darkens some +of the squares whenever you tell it to @draw@ a line or to @fill@ a region. + +\newdimen\tinypix \setbox0=\hbox{\sixrm0} \tinypix=5pt +\newdimen\pixcorr \pixcorr=\tinypix \advance\pixcorr by-\wd0 +\def\spread#1{\if#1!\let\next\relax\else#1\kern\pixcorr\let\next\spread\fi + \next} +\def\beginpixdisplay{$$\advance\abovedisplayskip by 2pt + \advance\belowdisplayskip by-2pt + \baselineskip=\tinypix + \halign\bgroup\sixrm\indent\spread##!\hfil\cr} +\MF's internal graph paper is actually more sophisticated than this. +Pixels aren't simply ``on'' or ``off'' when \MF\ is working on a picture; +they can be ``doubly on'' or ``triply off.'' Each pixel contains a +small {\sl integer\/} value, and when a character is finally shipped out +to a font the black pixels are those whose value is greater than zero. +For example, the two commands +\begindisplay +^@fill@ $(0,3)\dashto(9,3)\dashto(9,6)\dashto(0,6)\dashto\cycle$;\cr +@fill@ $(3,0)\dashto(3,9)\dashto(6,9)\dashto(6,0)\dashto\cycle$ +\enddisplay +yield the following $9\times9$ pattern of pixel values: +\beginpixdisplay +000111000\cr +000111000\cr +000111000\cr +111222111\cr +111222111\cr +111222111\cr +000111000\cr +000111000\cr +000111000\cr +\enddisplay +Pixels that have been filled twice now have a value of 2. + +When a simple region is ``filled,'' its pixel values are all increased by~1; +when it is ``unfilled,'' they are all decreased by~1. The command +\begindisplay +^@unfill@ $(1,4)\dashto(8,4)\dashto(8,5)\dashto(1,5)\dashto\cycle$ +\enddisplay +will therefore change the pattern above to +\beginpixdisplay +000111000\cr +000111000\cr +000111000\cr +111222111\cr +100111001\cr +111222111\cr +000111000\cr +000111000\cr +000111000\cr +\enddisplay +The pixels in the center have not been erased (i.e., they will still be +black if this picture is output to a font), because they still have a +positive value. + +Incidentally, this example illustrates the fact that the edges between +\MF's pixels are lines that have integer ^{coordinates}, just as the +squares on graph paper do. For example, the lower left `{\sixrm0}' in +the $9\times9$ array above corresponds to the pixel whose boundary is +`$(0,0)\dashto(1,0)\dashto(1,1)\dashto(0,1)\dashto\cycle$'. The $(x,y)$ +coordinates of the points inside this pixel lie between 0 and~1. + +\exercise What are the $(x,y)$ coordinates of the four corners of the +{\sl middle\/} pixel in the $9\times9$ array? +\answer $(4,4)$, $(4,5)$, $(5,5)$, $(5,4)$. \ (Therefore the command +\begindisplay +@unfill@ $(4,4)\dashto(4,5)\dashto(5,5)\dashto(5,4)\dashto\cycle$ +\enddisplay +will decrease the value of this pixel by 1.) + +\exercise What picture would have been obtained if the @unfill@ command +had been given {\sl before\/} the two @fill@ commands in the examples +above? +\answer The result would be exactly the same; @fill@ and @unfill@ commands +can be given in any order. \ (After an initial @unfill@ command, some +pixel values will be $-1$, the others will be zero.) + +\exercise Devise an @unfill@ command that will produce the pixel values +\beginpixdisplay +000111000\cr +000101000\cr +000101000\cr +111212111\cr +100101001\cr +111212111\cr +000101000\cr +000101000\cr +000111000\cr +\enddisplay +when it is used just after the @fill@ and @unfill@ commands already given. +\answer @unfill@ $(4,1)\dashto(4,8)\dashto(5,8)\dashto(5,1)\dashto\cycle$. + +A ``simple'' region is one whose boundary does not intersect itself; more +complicated effects occur when the boundary lines cross. For example, +\begindisplay +@fill@ $(0,1)\dashto(9,1)\dashto(9,4)\dashto(4,4)\dashto$\cr +\indent$(4,0)\dashto(6,0)\dashto + (6,3)\dashto(8,3)\dashto(8,2)\dashto(0,2)\dashto\cycle$\cr +\enddisplay +produces the pixel pattern +\beginpixdisplay +000011111\cr +000011001\cr +111122111\cr +000011000\cr +\enddisplay +Notice that some pixels receive the value 2, because they're ``^{doubly +filled}.'' There's also a ``^{hole}'' where the pixel values remain zero, +even though they are surrounded by filled pixels; the pixels in that hole +are not considered to be in the region, but the doubly filled pixels +are considered to be in the region twice. + +\exercise Show that the first $9\times9$ cross pattern on the previous +page can be generated by a single @fill@ command. \ (The nine pixel +values in the center should be~2, as if two separate regions had been +filled, even though you are doing only one @fill@.) +\answer Here are two of the many solutions: +\begindisplay +@fill@ $(0,3)\dashto(9,3)\dashto(9,6)\dashto(6,6)\dashto(6,9)\dashto$\cr +\indent $(3,9)\dashto(3,0)\dashto(6,0)\dashto(6,6)\dashto(0,6)\dashto\cycle$;\cr +@fill@ $(0,3)\dashto(9,3)\dashto(9,6)\dashto(0,6)\dashto(0,3)\dashto$\cr +\indent $(3,3)\dashto(3,0)\dashto(6,0)\dashto(6,9)\dashto(3,9)\dashto + (3,3)\dashto\cycle$.\cr +\enddisplay +(It turns out that {\sl any\/} pixel pattern can be obtained by a single, +sufficiently hairy @fill@ command. But unnatural commands are usually also +inefficient and unreadable.) + +\exercise What do you think is the result of `@fill@ $(0,0)\dashto(1,0)\dashto +(1,1)\dashto(0,1)\dashto(0,0)\dashto(1,0)\dashto(1,1)\dashto(0,1)\dashto +\cycle$'\thinspace? +\answer The value of the enclosed pixel is increased by 2. \ (We'll see later +that there's a simpler way to do this.) + +A @fill@ command can produce even stranger effects when its boundary lines +cross in only one place. If you say, for example, +\begindisplay +@fill@ $(0,2)\dashto(4,2)\dashto(4,4)\dashto(2,4)\dashto(2,0) + \dashto(0,0)\dashto\cycle$ +\enddisplay +\MF\ will produce the $4\times4$ pattern +\setbox0=\hbox to\tinypix{\hss + $\scriptscriptstyle{\hbox to3pt{}\over}$\hss\kern\pixcorr} +\dp0=0pt +\beginpixdisplay +0011\cr +0011\cr +!\copy0\copy0 \spread00\cr +!\copy0\copy0 \spread00\cr +\enddisplay +where `$\hbox to3pt{}\over$' stands for the value $-1$. Furthermore the +machine will report that you have a ``^{strange path}'' whose ``^{turning +number}'' is zero! What does this mean? Basically, it means that your +path loops around on itself something like a figure~8; this causes a +breakdown in \MF's usual rules for distinguishing the ``inside'' and +``outside'' of a curve. + +\danger Every cyclic path has a {\sl turning number\/} that can be understood +as follows. Imagine that you are driving a car along the path and that you +have a digital compass that tells in what direction you're heading. For +example, if the path is +\begindisplay +$(0,0)\dashto(2,0)\dashto(2,2)\dashto(0,2)\dashto\cycle$ +\enddisplay +you begin driving in direction $0^\circ$, then you make four left turns. +After the first turn, your compass heading is $90^\circ$; after the +second, it is $180^\circ$; and after the third it is $270^\circ$. \ (The +compass direction increases when you turn left and decreases when you turn +right; therefore it now reads $270^\circ$, not $-90^\circ$.) \ At the +end of this cycle the compass will read $360^\circ$, and if you go around +again the reading will be $720^\circ$. Similarly, if you had traversed the +path +\begindisplay +$(0,0)\dashto(0,2)\dashto(2,2)\dashto(2,0)\dashto\cycle$ +\enddisplay +(which is essentially the same, but in the opposite direction), your compass +heading would have started at $90^\circ$ and ended at $-270^\circ$; +in this case each circuit would have {\sl decreased\/} the reading +by~$360^\circ$. It is clear that a drive around any cyclic path will +change the compass heading by some multiple of~$360^\circ$, since you +end in the same direction you started. The turning number of a path is +defined to be $t$ if the compass heading changes by exactly $t$~times +$360^\circ$ when the path is traversed. Thus, the two example cycles we have +just discussed have turning numbers of $+1$ and $-1$, respectively; and +the ``strange path'' on the previous page that produced both positive and +negative pixel values does indeed have a turning number of~0. + +\danger Here's how \MF\ actually implements a @fill@ command, assuming that +the cyclic path being filled has a {\sl positive\/} turning number: +The path is first ``^{digitized},'' if necessary, so that it lies entirely on +the edges of pixels; in other words, it is distorted slightly so that it +is confined to the lines between pixels on graph paper. \ (Our examples so +far in this chapter have not needed any such adjustments.) \ Then each +individual pixel value is increased by~$j$ and decreased by~$k$ if an +infinite horizontal line to the left of that pixel intersects the +digitized path $j$~times when the path is traveling downward and $k$~times +when it is traveling upward. For example, let's look more closely at the +non-simple path on the previous page that enclosed a hole: +$$\def\\#1{\hbox to 11pt{\hss$#1$\hss}} +\def\up{\hbox to0pt{\hss\lower3pt\vbox to 11pt{ + \hbox{\tenex\char'77}\vss\hbox{\tenex\char'170}\kern0pt}\hss}} +\def\down{\hbox to0pt{\hss\lower3pt\vbox to 11pt{ + \hbox{\tenex\char'171}\vss\hbox{\tenex\char'77}\kern0pt}\hss}} +\def\under{\smash{\rlap{\lower3.2pt\vbox{\hrule width 11pt}}}} +\def\over{\smash{\rlap{\raise7.8pt\vbox{\hrule width 11pt}}}} +\halign{\indent#\cr +\\a\\a\\a\\a\over\down\\b\over\\b\over\under\\b\over\under\\b\over\\b\up\cr +\under\\a\under\\a\under\\a\under\\a\down\under\\b\under\\b\up + \under\\c\under\\c\down\\d\up\cr +\down\under\\e\under\\e\under\\e\under\\e\down\under\\f\under\\f\up + \under\\g\under\\g\under\\g\up\cr +\\a\\a\\a\\a\down\under\\b\under\\b\up\\h\\h\\h\cr}$$ +Pixel $d$ has $j=2$ descending edges and $k=1$ ascending edges to its left, +so its net value increases by $j-k=1$; pixels~$g$ are similar. +Pixels~$c$ have $j=k=1$, so they lie in a ``hole'' that is unfilled; +pixels~$f$ have $j=2$ and $k=0$, so they are doubly filled. This rule +works because, intuitively, the inside of a region lies at the {\sl left\/} +of a path whose turning number is positive. + +\dangerexercise True or false: When the turning number of a cyclic path is +positive, a @fill@ command increases each individual pixel value by $l-m$, +if an infinite horizontal line to the {\sl right\/} of that pixel intersects +the digitized path $l$~times when the path is traveling upward and $m$~times +when it is traveling downward. \ (For example, pixels~$e$ have $l=2$ and +$m=1$; pixels~$c$ have $l=m=1$.) +\answer True; $j-k=l-m$, since $k+l=j+m$. \ (What comes up must go down.) + +\danger When the turning number is negative, a similar rule applies, +except that the pixel values are {\sl decreased\/} by~$j$ and {\sl +increased\/} by~$k$; in this case the inside of the region lies at the +{\sl right\/} of the path. + +\danger But when the turning number is zero, the inside of the region +lies sometimes at the left, sometimes at the right. \MF\ uses the rule +for positive turning number and reports that the path is ``strange.'' +You can avoid this error message by setting `$"turningcheck":=0$'; +^^"turningcheck" in this case the rule for positive turning number is +always used for filling, even when the turning number is negative. + +Plain \MF's ^@draw@ command is different from @fill@ in two important ways. +First, it uses the currently-picked-up pen, thereby ``thickening'' the path. +Second, it does not require that the path be cyclic. There is also a third +difference, which needs to be mentioned although it is not quite as important: +A @draw@ command may increase the value of certain pixels by more than~1, +even if the shape being drawn is fairly simple. For example, the pixel pattern +{\parindent=0pt +\beginpixdisplay +0000000000000000000000000000000000000000000000000000000000000000000000\cr +0000001111122222111110000000000000000000000000011111111000000000000000\cr +0000111111111211111111100000000000000000000011111111111111000000000000\cr +0001111111111011111111110000000000000000001111111111111111110000000000\cr +0001111111111011111111110000000000000000111111111111111111111100000000\cr +0011111111110001111111111000000000000001111111111111111111111110000000\cr +0011111111110001111111111000000000000011111111111111111111111111000000\cr +0011111111110001111111111000000000000111111111111111111111111111100000\cr +0111111111100000111111111100000000001111111111111111111111111111110000\cr +0111111111100000111111111100000000001111111111111111111111111111110000\cr +0111111111100000111111111100000000011111111111111111111111111111111000\cr +0111111111100000111111111100000000011111111111111111111111111111111000\cr +0111111111100000111111111100000000111111111111111112111111111111111100\cr +0111111111100000111111111100000000111111111111111112111111111111111100\cr +0111111111100000111111111100000001111111111111111122111111111111111110\cr +0111111111100000111111111100000001111111111111211121111211111111111110\cr +0111111111100000111111111100000001111111111111112122221111111111111110\cr +0111111111100000111111111100000001111111111111111100111111111111111110\cr +0111111111100000111111111100000001111111111111112000011111111111111110\cr +0111111111100000111111111100000001111111111112211000011211111111111110\cr +0111111111100000111111111100000000111111111111110000001111111111111100\cr +0111111111100000111111111100000000111111111111110000001111111111111100\cr +0111111111100000111111111100000000011111111111100000000111111111111000\cr +0111111111100000111111111100000000001111111111000000000011111111110000\cr +0111111111100000111111111100000000000011111100000000000000111111000000\cr +0000000000000000000000000000000000000000000000000000000000000000000000\cr +\enddisplay}% +was produced by two @draw@ commands. The left-hand shape came from +\begindisplay +\pickup ^@penrazor@ scaled 10;\quad \% a pen of width 10 and height 0\cr +@draw@ $(6,1)\{"up"\}\to(13.5,25)\to\{"down"\}(21,1)$;\cr +\enddisplay +it's not difficult to imagine why some of the top pixels get the value~2 +here because an actual razor-thin pen would cover those pixels twice as it +follows the given path. But the right-hand shape, which came from +\begindisplay +\pickup @pencircle@ scaled 16; \ @draw@ $(41,9)\to(51,17)\to(61,9)$ +\enddisplay +is harder to explain; there seems to be no rhyme or reason to the pattern +of 2's in that case. \MF's method for drawing curves with thick pens is +too complicated to explain here, so we shall just regard it as a curious +process that occasionally shoots out extra spurts of ink in the interior +of the shape that it's filling. Sometimes a pixel value even gets as high +as 3~or more; but if we ignore such anomalies and simply consider the set +of pixels that receive a positive value, we find that a reasonable shape +has been drawn. + +The left-parenthesis example in Chapter 12 illustrates the ^@filldraw@ +command, which is like @fill@ in that it requires a cyclic path, and like +@draw@ in that it uses the current pen. Pixel values are increased inside +the region that you would obtain by drawing the specified path with the current +pen and then filling in the interior. Some of the pixel values in this +region may increase by 2~or more. The turning number of the path +should be nonzero. + +Besides @fill@, @draw@, and @filldraw@, you can also say `^@drawdot@', +as illustrated at the beginning of Chapter~5. In this case you should specify +only a single point; the currently-picked-up pen will be used to increase +pixel values by~1 around that point. Chapter~24 explains that this gives +slightly better results than if you were to draw a one-point path. + +\danger There's also an ^@undraw@ command, analogous to @unfill@; it +decreases pixel values by the same amount that @draw@ would increase them. +Furthermore---as you might expect---^@unfilldraw@ and ^@undrawdot@ are the +respective opposites of @filldraw@ and @drawdot@. + +\danger If you try to use @unfill@ and/or @undraw@ in connection with +@fill@ and/or @draw@, you'll soon discover that something else is +necessary. Plain \MF\ has a ^@cullit@ command that replaces all +negative pixel values by~0 and all positive pixel values by~1. This +``^{culling}'' operation makes it possible to erase unwanted sections +of a picture in spite of the vagaries of @draw@ and @undraw@, and in spite of +the fact that overlapping regions may be doubly filled. + +\danger The command `^@erase@ @fill@ $c$' is an abbreviation for +`@cullit@; @unfill@~$c$; @cullit@'; this zeros out the pixel values inside +the cyclic path~$c$, and sets other pixel values to~1 if they were positive +before erasing took place. \ (It works because the initial @cullit@ makes +all the values 0 or~1, then the @unfill@ changes the values inside~$c$ +to 0 or negative. The final @cullit@ gets rid of the negative values, +so that they won't detract from future filling and drawing.) \ You can +also use `@draw@', `@filldraw@', or `@drawdot@' with `@erase@'; for example, +`@erase@ @draw@~$p$' is an abbreviation for `@cullit@; @undraw@~$p$; +@cullit@', which uses the currently-picked-up pen as if it were an +eraser applied to path~$p$. + +{\ninepoint +\medbreak +\parshape 7 3pc 17pc 3pc 17pc +0pc 20pc 0pc 20pc 0pc 20pc 0pc 20pc 0pc 29pc +\noindent +\hbox to0pt{\hskip-3pc\dbend\hfill}% +\rightfig 13a ({166.66667\apspix} x {133.33333\apspix}) ^9pt +The cube at the right of this paragraph illustrates one of the effects that +is easily obtained by erasing. First the eight points are defined, and +the ``back'' square is drawn; then two lines of the ``front'' square are +erased, using a somewhat thicker pen; finally the remaining lines are +drawn with the ordinary pen: +\begindisplay +$s\0:=5"pt"\0$; \ @define\_pixels@$(s)$; \ |%| side of the square\cr +$z_1=(0,0)$; \ $z_2=(s,0)$; \ $z_3=(0,s)$; $z_4=(s,s)$;\cr +^@for@ $k=1$ @upto@ 4: $z_{k+4}=z_k+({2\over3}s,{1\over3}s)$; \ @endfor@\cr +\pickup @pencircle@ scaled $.4"pt"$; \ +@draw@ $z_5\dashto z_6\dashto z_8\dashto z_7\dashto \cycle$;\cr +\pickup @pencircle@ scaled $1.6"pt"$; \ +@erase@ @draw@ $z_2\dashto z_4\dashto z_3$;\cr +\pickup @pencircle@ scaled $.4"pt"$; \ +@draw@ $z_1\dashto z_2\dashto z_4\dashto z_3\dashto \cycle$;\cr +@for@ $k=1$ @upto@ 4: @draw@ $z_k\dashto z_{k+4}$; \ @endfor@.\cr +\enddisplay +At its true size the resulting ^{cube} looks like this: +`\thinspace{\manual\cubea}\thinspace'.\par} + +\dangerexercise Modify the draw-and-erase construction in the preceding +paragraph so that you get the {\sl^{impossible cube}\/} +`\thinspace{\manual\cubeb}\thinspace' instead. +\answer The tricky part is to remember that `@erase@ @draw@ $z_i\dashto z_j$' +will erase pixels near $z_i$ and $z_j$. Therefore if $z_3\dashto z_4$ is +drawn before $z_4\dashto z_2$, we can't erase $z_4\dashto z_2$ without losing +some of $z_3\dashto z_4$; it's necessary to erase only part of one line. +One way to solve the problem is to do the following, after defining the +points and picking up the pen as before: +\begindisplay +@draw@ $z_3\dashto z_4$; \ @draw@ $z_5\dashto z_6$;\cr +^@cullit@; \ \pickup @pencircle@ scaled $1.6"pt"$;\cr +^@undraw@ $z_7\dashto {1\over2}[z_7,z_5]$; \ + @undraw@ $z_2\dashto {1\over2}[z_2,z_4]$;\cr +@cullit@; \ \pickup @pencircle@ scaled $.4"pt"$;\cr +@draw@ $z_3\dashto z_1\dashto z_2\dashto z_4$; \ + @draw@ $z_5\dashto z_7\dashto z_8\dashto z_6$;\cr +@for@ $k=1$ @upto@ 4: \ @draw@ $z_k\dashto z_{k+4}$; \ @endfor@.\cr +\enddisplay +(Note that it would not be quite enough to erase only from $z_7$ to +${1\over3}[z_7,z_5]$!)\par +It's also possible to solve this problem without partial erasing, if we +use additional features of \MF\ that haven't been explained yet. Let's +consider only the job of drawing $z_7\dashto z_5\dashto z_6$ and +$z_3\dashto z_4\dashto z_2$, since the other eight lines can easily be +added later. Alternative Solution~1 uses picture operations: +\begindisplay +@pen@ "eraser"; \ $"eraser"=@pencircle@$ scaled $1.6"pt"$;\cr +@draw@ $z_3\dashto z_4$; \ +@erase@ @draw@ $z_7\dashto z_5$ ^@withpen@ "eraser"; \ +@draw@ $z_7\dashto z_5$;\cr +@picture@ "savedpicture"; \ $"savedpicture"="currentpicture"$; \ ^@clearit@;\cr +@draw@ $z_6\dashto z_5$; \ +@erase@ @draw@ $z_2\dashto z_4$ ^@withpen@ "eraser"; \ +@draw@ $z_2\dashto z_4$;\cr +^@addto@ "currentpicture" @also@ "savedpicture".\cr +\enddisplay +Alternative Solution 2 is trickier, but still instructive; it uses +`^@withweight@' options and the fact that @draw@ does not increase any +pixel values by more than the stated weight when the path is a straight +line: +\begindisplay +@draw@ $z_3\dashto z_4$; \ +^@undraw@ $z_7\dashto z_5$ @withpen@ "eraser";\cr +@draw@ $z_7\dashto z_5$ @withweight@ 2; \ +^@cullit@ @withweight@ 2;\cr +@draw@ $z_6\dashto z_5$; \ +^@undraw@ $z_2\dashto z_4$ @withpen@ "eraser";\cr +@draw@ $z_2\dashto z_4$ @withweight@ 2;\cr +\enddisplay +(These alternative solutions were suggested by Bruce ^{Leban}.) + +\dangerexercise Write a \MF\ program to produce the symbol +`{\manual\bicentennial}'. \ [{\sl Hints:\/} The character is $10\pt$ +wide, $7\pt$ high, and $2\pt$ deep. The starlike path can be defined by +five points connected by ``tense'' lines as follows: +\begindisplay +@pair@ "center"; \ $"center"=(.5w,2"pt")$;\cr +@numeric@ "radius"; \ $"radius"=5"pt"$;\cr +@for@ $k=0$ @upto@ 4: \ $z_k="center"+("radius",0)$ + ^{rotated}$(90+{360\over5}k)$; \ @endfor@\cr +@def@ :: = ^^{tension} $\to\tension 5\to$ @enddef@;\cr +@path@ "star"; \ $"star"=z_0::z_2::z_4::z_1::z_3::\cycle$;\cr +\enddisplay +You probably want to work with ^{subpaths} of ^"star" instead of drawing the +whole path at once, in order to give the illusion that the curves cross over +and under each other.] +\answer Here's an analog of the first solution to the previous +exercise: +\begindisplay +@beginchar@\kern1pt(|"*"|$,10"pt"\0,7"pt"\0,2"pt"\0)$;\cr +@pair@ "center"; \dots \\cr +\pickup @pencircle@ scaled $.4"pt"$; \ @draw@ "star";\cr +@cullit@; \ \pickup @pencircle@ scaled $1.6"pt"$;\cr +@for@ $k=0$ @upto@ 4: + \ @undraw@ subpath$(k+.55,k+.7)$ @of@ "star"; \ @endfor@\cr +@cullit@; \ \pickup @pencircle@ scaled $.4"pt"$;\cr +@for@ $k=0$ @upto@ 4: \ @draw@ subpath$(k+.47,k+.8)$ @of@ "star"; \ @endfor@\cr +@labels@(0,1,2,3,4); \ @endchar@.\cr +\enddisplay +However, as in the previous case, there's an Alternate Solution~1 +by Bruce ^{Leban} that is preferable because it doesn't depend +on magic constants like .55 and~.47: +\begindisplay +@beginchar@ $\ldots$ \ $\ldots$ scaled $.4"pt"$;\cr +@picture@ "savedpicture"; \ $"savedpicture"=@nullpicture@$;\cr +@pen@ "eraser"; \ $"eraser":=@pencircle@$ scaled $1.6"pt"$;\cr +@for@ $k=0$ @upto@ 4:\cr +\indent @draw@ subpath$(k,k+1)$ @of@ "star"; @cullit@;\cr +\indent @undraw@ subpath$(k+3,k+4)$ @of@ "star" @withpen@ "eraser"; @cullit@;\cr +\indent @addto@ "savedpicture" @also@ "currentpicture"; @clearit@; @endfor@\cr +$"currentpicture":="savedpicture"$; \ @labels@(0,1,2,3,4); \ @endchar@.\cr +\enddisplay + +\dangerexercise What does the command `@fill@ "star"' do, if "star" is the +path defined above? +\answer It increases pixel values by 1 in the five lobes of the star, and by~2 +in the central pentagon-like region. + +\decreasehsize 6pc +\dangerexercise Devise a ^{macro} called `^@overdraw@' such that the command +\rightfig 13aa (50pt x 100pt) ^11pt +`@overdraw@~$c$' will erase the inside of region~$c$ and will then draw the +boundary of~$c$ with the currently-picked-up pen, assuming that $c$~is a +cyclic path that doesn't intersect itself. \ (Your macro could be used, for +example, in the program +\begindisplay +@path@ $S$; \ $S=((0,1)\to(2,0)\to(4,2)\to$\cr +\indent$(2,5.5)\to(0,8)\to(2,10)\to(3.5,9))$ scaled $9"pt"$;\cr +@for@ $k=0$ @upto@ 35: @overdraw@ ^"fullcircle" scaled 3"mm"\cr +\indent shifted ^{point} $k/35\ast \mathop{\rm length} S$ @of@ $S$; + @endfor@\cr +\enddisplay +to create the curious ^{S} shown here.) +\answer @def@ @overdraw@ @expr@ $c$ = @erase@ @fill@ $c$; @draw@ $c$ @enddef@. + +\restorehsize +\ddangerexercise The ^{M\"obius} Watchband Corporation has a logo that +looks like this: +\displayfig 13bb (.5in) +Explain how to produce it (or something very similar) with \MF\!. +\answer First we need to generalize the ^@overdraw@ macro of the previous +exercise so that it applies to arbitrary cycles~$c$, even those that are +self-intersecting: +\begindisplay +@def@ @overdraw@ @expr@ $c$ = ^@begingroup@\cr +\indent@picture@ "region"; $"region":=@nullpicture@$;\cr +\indent^@interim@ $"turningcheck":=0$; ^@addto@ "region" @contour@ $c$;\cr +\indent^@cull@ "region" @dropping@ $(0,0)$;\cr +\indent^@cullit@; @addto@ "currentpicture" ^@also@ $-"region"$; @cullit@;\cr +\indent@draw@ $c$ ^@endgroup@ @enddef@;\cr +\enddisplay +(This code uses operations defined later in this chapter; it erases the +"region" of pixels that would be made nonzero by the command `@fill@~$c$'.) +\ The watchband is now formed by overdrawing its links, one at a time, +doing first the ones that are underneath: +\begindisplay +@beginchar@$("M",1.25"in"\0,.5"in"\0,0)$; \ + \pickup @pencircle@ scaled .4"pt";\cr +$z_1=(20,-13)$; \ $z_2=(30,-6)$; \ $z_3=(20,1)$; $z_4=(4,-7)$;\cr +\indent $z_5=(-12,-13)$; \ $z_6=(-24,-4)$; \ $z_7=(-15,6)$;\cr +@path@ $M$; $M=("origin"\to z1\to z2\to z3\to z4\to z5\to z6\to z7\to$\cr +\indent$"origin"\to -z7\to -z6\to -z5\to -z4\to -z3\to -z2\to -z1\to\cycle)$\cr +^^"origin" \indent\indent scaled $(h/26)$ shifted $(.5w,.5h)$;\cr +@def@ @link@(@expr@ $n$) =\cr +\indent @overdraw@ subpath ${1\over3}(n,n+1)$ of $M\;\dashto$\cr +\indent\indent subpath ${1\over3}(n+25,n+24)$ of $M\;\dashto\;\cycle\;$ + @enddef@;\cr +@for@ $k=1$ @upto@ 12: @link@$(k+11)$; @link@$(12-k)$; @endfor@ +@endchar@;\cr +\enddisplay + +\danger Chapter 7 points out that variables can be of type `^@picture@', +and Chapter~8 mentions that expressions can be of type `@picture@', but +we still haven't seen any examples of picture variables or picture +expressions. Plain \MF\ keeps the currently-worked-on picture in a +picture variable called ^"currentpicture", and you can copy it by +equating it to a picture variable of your own. For example, if you +say `@picture@ $v[\,]$' at the beginning of your program, you can write +equations like +\begindisplay +$v_1="currentpicture"$; +\enddisplay +this makes $v_1$ equal to the picture that has been drawn so far; i.e., +it gives $v_1$ the same array of pixel values that "currentpicture" now has. + +\begingroup\def\dbend{{\manual\char0}} % reverse-video dangerous bend sign +\danger Pictures can be added or subtracted; for example, $v_1+v_2$ +^^{sum of pictures} ^^{negative of a picture} ^^{inverse video} +stands for the picture whose pixel values are the sums of the pixel +values of $v_1$ and~$v_2$. The ``^{reverse-video} ^{dangerous bend}'' sign that +heads this paragraph was made by substituting the following code for +the `@endchar@' in the program at the end of Chapter~12: +\begindisplay +@picture@ "dbend"; \ $"dbend"="currentpicture"$;\cr +@endchar@; \ |%| end of the normal dangerous bend sign\cr +@beginchar@$(0,25u\0,"h\_height"\0+"border"\0,0)$;\cr +@fill@ $(0,-11"pt")\dashto(w,-11"pt")\dashto(w,h)\dashto(0,h)\dashto\cycle$;\cr +$"currentpicture":="currentpicture"-"dbend"$;\cr +@endchar@;\ |%| end of the reversed dangerous bend sign\cr +\enddisplay +^^{black/white reversal} The pixel values in "dbend" are all zero or more; +thus the pixels with a positive value, after "dbend" has been subtracted from +a filled rectangle, will be those that are inside the rectangle +but zero in "dbend". + +\endgroup % back to normal \dbend + +\danger We will see in Chapter 15 that pictures can also be shifted, +reflected, and rotated by multiples of $90^\circ$. For example, +the statement `$"currentpicture":="currentpicture"$~shifted~3"right"' +shifts the entire current picture three pixels to the right. + +\danger There's a ``constant'' picture called ^@nullpicture@, whose pixel +values are all zero; +plain \MF\ defines `^@clearit@' to be an abbreviation for the +assignment `"currentpicture":=@nullpicture@'. The current picture is +cleared automatically by every ^@beginchar@ and ^@mode\_setup@ command, +so you usually don't have to say `@clearit@' in your own programs. + +\danger Here's the formal syntax for picture expressions. Although \MF\ has +comparatively few built-in operations that deal with entire pictures, +the operations that do exist have the same syntax as the similar operations +we have seen applied to numbers and pairs. +\beginsyntax +\is + \alt[nullpicture] + \alt[(][)] + \alt +\is + \alt +\is + \alt +\is +\endsyntax + +\danger The ``total weight'' of a picture is the sum of all its pixel +values, divided by 65536; you can compute this numeric quantity by +saying +\begindisplay +^|totalweight| \. +\enddisplay +\MF\ divides by 65536 in order to avoid overflow in case of huge pictures. +If the totalweight function returns a number whose absolute +value is less than~.5, as it almost always is, you can safely divide that number +by ^"epsilon" to obtain the integer sum of all pixel values +(since $"epsilon"=1/65536$). + +\danger Let's turn to the computer again and try to evaluate some simple +picture expressions interactively, using the general routine |expr.mf| +of Chapter~8. When \MF\ says `|gimme|', you can type +\begintt +hide(fill unitsquare) currentpicture +\endtt +and the machine will respond as follows: +\begintt +>> Edge structure at line 5: +row 0: 0+ 1- || +\endtt +What does this mean? Well, `^@hide@' is plain \MF's sneaky way to insert +a command or sequence of commands into the middle of an expression; such +commands are executed before the rest of the expression is looked at. In +this case the command `@fill@ "unitsquare"' sets one pixel value of the +current picture to~1, because ^"unitsquare" is plain \MF's abbreviation +for the path $(0,0)\dashto(1,0)\dashto(1,1)\dashto(0,1)\dashto\cycle$. The +value of "currentpicture" is displayed as `|row|~|0:| |0+|~|1-|', because +this means +``in row~0, the pixel value increases at $x=0$ and decreases at $x=1$.'' + +\danger \MF\ represents pictures internally by remembering only the vertical +^{edges} where pixel values change. For example, the picture just displayed +has just two edges, both in row~0, i.e., both in the row between $y$~coordinates +0 and~1. \ (Row~$k$ contains vertical edges whose $x$~coordinates are integers +and whose $y$~coordinates run between $k$ and $k+1$.) \ The fact that edges +are represented, rather than entire arrays of pixels, makes it possible for +\MF\ to operate efficiently at high resolutions, because the number of edges +in a picture is essentially proportional to the ^{resolution} while the total +number of pixels is proportional to the resolution {\sl squared}. A ten-fold +increase in resolution therefore calls for only a ten-fold (rather than a +hundred-fold) increase in memory space and execution time. + +\def\pixpat#1#2#3#4{\vcenter{\sixrm\baselineskip=\tinypix + \hbox{#1\kern\pixcorr#2}\hbox{#3\kern\pixcorr#4}}} +\ddanger Continuing our computer experiments, let's declare a picture variable +and fill a few more pixels: +\begintt +hide(picture V; fill unitsquare scaled 2; V=currentpicture) V +\endtt +The resulting picture has pixel values $\pixpat1121\,$, +and its edges are shown thus: +\begintt +>> Edge structure at line 5: +row 1: 0+ 2- || +row 0: 0+ 2- 0+ 1- || +\endtt +If we now type `|-V|', the result is similar but with the signs changed: +\begintt +>> Edge structure at line 5: +row 1: 0- 2+ || +row 0: 0- 2+ 0- 1+ || +\endtt +(You should be doing the experiments as you read this.) \ A more interesting +picture transformation occurs if we ask for `|V|~|rotated-90|'; the picture +$\pixpat2111$ appears below the baseline, hence the following edges are shown: +\begintt +>> Edge structure at line 5: +row -1: || 0++ 1- 2- +row -2: || 0+ 2- +\endtt +Here `^|++|' denotes an edge where the weight increases by 2. The edges appear +^^|+++| {\sl after\/} ^{vertical line}s `\|' in this case, while they appeared +{\sl before\/} vertical lines in the previous examples; this means that \MF\ +has sorted the edges by their $x$~coordinates. Each @fill@ or @draw@ instruction +contributes new edges to a picture, and unsorted edges accumulate until +\MF\ needs to look at them in left-to-right order. \ (Type +\begintt +V rotated-90 rotated 90 +\endtt +to see what $V$ itself looks like when its edges have been sorted.) \ The +expression +\begintt +V + V rotated 90 shifted 2right +\endtt +produces an edge structure with both sorted and unsorted edges: +\begintt +>> Edge structure at line 5: +row 1: 0+ 2- || 0+ 2- +row 0: 0+ 2- 0+ 1- || 0+ 1+ 2-- +\endtt +In general, addition of pictures is accomplished by simply combining the +unsorted and sorted edges of each row separately. + +\ddangerexercise Guess what will happen if you type `|hide(cullit)| +|currentpicture|' now; and verify your guess by actually doing the experiment. +\answer The pixel pattern $\pixpat1121$ is culled to $\pixpat1111\,$, +and \MF\ needs to sort the edges as it does this; so the result is simply +\begintt +row 1: || 0+ 2- +row 0: || 0+ 2- +\endtt + +\ddangerexercise Guess (and verify) what will happen when you type the +expression +\begintt +(V + V + V rotated 90 shifted 2right + - V rotated-90 shifted 2up) rotated 90. +\endtt +[You must type this monstrous formula all on one line, even though it's too +long to fit on a single line in this book.] +\answer The pixel pattern is $\pixpat1121+\pixpat1121+\pixpat1112-\pixpat2111 +=\pixpat1243$ before the final rotation, with the reference point at the +lower left corner of the~4; after rotation it is $\pixpat2314\,$, with the +reference point at the lower {\sl right\/} corner of the~4. Rotation causes +\MF\ to sort the edges, but the transition values per edge are never +more than $\pm3$. You weren't expected to know about this limit of $\pm3$, +but it accounts for what is actually reported: +\begintt +row 1: || -2++ -1+ 0--- +row 0: || -2+ -1+++ 0--- 0- +\endtt + +\ddanger If you ask for `|V| |rotated| |45|', \MF\ will complain that +$45^\circ$ rotation is too hard. \ (Try it.) \ After all, square pixels +can't be ^{rotated} unless the angle of rotation is a multiple of $90^\circ$. +On the other hand, `|V|~|scaled-1|' does work; you get +\begintt +>> Edge structure at line 5: +row -1: 0- -2+ 0- -1+ || +row -2: 0- -2+ || +\endtt + +\ddangerexercise Why is `|V| |scaled-1|' different from `|-V|'\thinspace? +\answer `|V| |scaled-1|' should be the same as `|V| |rotated| |180|', +because transformations apply to coordinates rather than to pixel values. +\ (Note, incidentally, that the reflections `|V|~^|xscaled-1|' and +`|V|~^|yscaled-1|' both work, and that `|V|~|scaled-1|' is the same as +`|V|~|xscaled-1| |yscaled-1|'.) + +\ddangerexercise Experiment with `|V| |shifted| |(1.5,3.14159)|' and +^^{shifted} explain what happens. +\answer The result is the same as `|V| |shifted| |(2,3)|'; the coordinates +of a shift are rounded to the nearest integers when a picture is being shifted. + +\ddangerexercise Guess and verify the result of `|V| |scaled| |2|'. +\answer |row 3: 0+ 4- |\|\parbreak +|row 2: 0+ 4- |\|\parbreak +|row 1: 0+ 4- 0+ 2- |\|\parbreak +|row 0: 0+ 4- 0+ 2- |\|\par\nobreak +\smallskip\noindent +(Scaling of pictures must be by an integer.) + +\ddangerexercise Why does the machine always speak of an ^{edge structure} +`|at| |line|~|5|'\thinspace? +\answer \MF\ is currently executing instructions after having read +as far as line~5 of the file |expr.mf|. + +\ddanger That completes our computer experiments. But before you log off, +you might want to try typing `|totalweight V/epsilon|', just to verify +that the sum of all pixel values in~$V$ is~5. + +\danger The commands we have discussed so far in this chapter---@fill@, +@draw@, @filldraw@, @unfill@, etc.---are not really primitives of \MF; +they are macros of plain \MF\!, defined in Appendix~B\null. Let's look now +at the low-level operations on pictures that \MF\ actually performs +behind the scenes. Here is the syntax: +\beginsyntax +\is\alt +\is[addto][also] + \alt[addto][contour] + \alt[addto][doublepath] +\is\alt +\is[withpen]% + \alt[withweight]\kern-3.5pt +\is[cull] + \alt[withweight] +\is[keeping]\alt[dropping] +\endsyntax +The \ in these commands should contain a known picture; +the command modifies that picture, and assigns the resulting new value +to the variable. + +\danger The first form of \, `@addto@ $V$ @also@~$P$', +has essentially the same meaning as `$V:=V+P$'. But the @addto@ statement +is more efficient, because it destroys the old value of~$V$ as it adds~$P$; +this saves both time and space. Earlier in this chapter we discussed +the ^{reverse-video} ^{dangerous bend}, which was said to have been +formed by the statement `$"currentpicture":="currentpicture"-"dbend"$'. +That was a little white lie; the actual command was +`@addto@ "currentpicture" @also@ $-"dbend"$'. + +\danger The details of the other forms of `@addto@' are slightly more +complex, but (informally) they work like this, when $V="currentpicture"$ +and $q=\null$^"currentpen": +\begindisplay +Plain \MF&Corresponding \MF\ primitives\cr +\noalign{\smallskip} +^@fill@ $c$&@addto@ $V$ @contour@ $c$\cr +^@unfill@ $c$&@addto@ $V$ @contour@ $c$ @withweight@ $-1$\cr +^@draw@ $p$&@addto@ $V$ @doublepath@ $p$ @withpen@ $q$\cr +^@undraw@ $p$&@addto@ $V$ @doublepath@ $p$ @withpen@ $q$ @withweight@ $-1$\cr +^@filldraw@ $c$&@addto@ $V$ @contour@ $c$ @withpen@ $q$\cr +^@unfilldraw@ $c$&@addto@ $V$ @contour@ $c$ @withpen@ $q$ @withweight@ $-1$\cr +\enddisplay + +\ddanger The second form of \ is `@addto@ $V$ @contour@ $p$', +followed by optional clauses that say either `@withpen@~$q$' or +`@withweight@~$w$'. In this case $p$~must be a cyclic path; each pen~$q$ +must be known; and each weight~$w$ must be either $-3$,~$-2$, $-1$, $+1$, +$+2$, or~$+3$, when rounded to the nearest integer. If more than one pen or +weight is given, the last specification overrides all previous ones. If no +pen is given, the pen is assumed to be `@nullpen@'; if no weight is given, +the weight is assumed to be~$+1$. Thus, the second form of \ +basically identifies a picture variable~$V$, a cyclic path~$p$, a pen~$q$, +and a weight~$w$; and it has the following meaning, assuming that +"turningcheck" is $\le0$: If~$q$~is the null pen, path~$p$ is digitized +and each pixel value is increased by $(j-k)w$, where $j$ and~$k$ are the +respective numbers of downward and upward path edges lying to the left +of the pixel (as explained earlier in this chapter). If $q$ is not the +null pen, the action is basically the same except that $p$ is converted to +another path that ``^{envelope}s'' $p$ with respect to the shape of~$q$; +this modified path is digitized and filled as before. \ (The modified path +may cross itself in unusual ways, producing strange squirts of ink as +illustrated earlier. But it will be well behaved if path~$p$ defines a +^{convex} region, i.e., if a car that drives counterclockwise +around $p$ never turns toward the right at any time.) + +\ddanger If $"turningcheck">0$ when an `$@addto@\ldots@contour@$' command +^^"turningcheck" is being performed, the action is the same as just +described, provided that path~$p$ has a positive ^{turning number}. +However, if $p$'s turning number is negative, the action depends on +whether or not pen~$q$ is simple or complex; a complex pen is one whose +boundary contains at least two points. If the turning number is negative +and the pen is simple, the weight~$w$ is changed to~$-w$. If the turning +number is negative and the pen is complex, you get an error message about +a ``^{backwards path}.'' Finally, if the turning number is zero, you get +an error message about a ``^{strange path},'' unless the pen is simple and +$"turningcheck"<=1$. Plain \MF\ sets $"turningcheck":=2$; the ^@filldraw@ +macro in Appendix~B avoids the ``backwards path'' error by explicitly +reversing a path whose turning number is negative. + +\danger We mentioned that the command `@fill@ $(0,2)\dashto(4,2)\dashto +(4,4)\dashto(2,4)\dashto(2,0)\dashto(0,0)\dashto\cycle$' causes \MF\ +to complain about a strange path; let's take a closer look at the +error message that you get: +\begintt +> 0 ENE 1 NNE 2 (NNW WNW) WSW 3 SSW 4 WSW 5 (WNW NNW) NNE 0 +! Strange path (turning number is zero). +\endtt +What does this mean? The numbers represent ``^time'' on the cyclic path, +from the starting point at time~0, to the next key point at time~1, +and so on, finally returning to the starting point. Code names like +`^|ENE|' stand for ^{compass directions} like ``East by North East''; +\MF\ decides in which of eight ``^{octants}'' each part of a path travels, +and |ENE| stands for all directions between the angles~$0^\circ$ +and~$45^\circ$, inclusive. Thus, this particular strange path starts in +octant |ENE| at time~0, then it turns to octant ^|NNE| after time~1. +An octant name is parenthesized when the path turns through that octant +without moving; thus, for example, octants ^|NNW| and ^|WNW| are bypassed +on the way to octant ^|WSW|. It's possible to compute the turning number +from the given ^^|SSW| sequence of octants; therefore, if you don't think +your path is really strange, the abbreviated octant codes should reveal +where \MF\ has decided to take an unexpected turn. \ (Chapter~27 explains +more about strange paths.) + +\ddanger The third form of \ is `@addto@ $V$ @doublepath@~$p$', +followed by optional clauses that define a pen~$q$ and a weight~$w$ as in +the second case. If $p$ is not a cyclic path, this case reduces to the +second case, with $p$ replaced by the doubled-up path +`$p\mathbin{\&}\mathop{\rm reverse}p \mathbin{\&}\cycle$' (unless $p$ +consists of only a single point, when the new path is simply +`$p\to\cycle$'\thinspace). On the other hand if $p$ is a cyclic +path, this case reduces to {\sl two\/} addto commands of the second type, +in one of which $p$ is reversed; "turningcheck" is ignored during both of +those commands. + +\danger An anomalous result may occur in the statement `@draw@~$p$' +or, more generally, in `@addto@~$V$ @doublepath@~$p$ @withpen@~$q$' when +$p$~is a very small cyclic path and the current pen~$q$ is very large: Pixels +that would be covered by the pen regardless of where it is placed on~$p$ +might retain their original value. If this unusual circumstance hits you, +the cure is simply to include the additional statement `@draw@~$z$' or +`@addto@~$V$ @doublepath@~$z$ @withpen@~$q$', where $z$ is any point +of~$p$, since this will cover all of the potentially uncovered pixels. + +\danger The ^@cull@ command transforms a picture variable so that +all of its pixel values are either 0 or a specified weight~$w$, where $w$~is +determined as in an @addto@ command. A pair of numbers $(a,b)$ is given, +where $a$ must be less than or equal to~$b$. To cull ``@keeping@ $(a,b)$'' +means that each new pixel value is $w$ if and only if the corresponding +old pixel value~$v$ was included in the range $a\le v\le b$; to cull +``@dropping@ $(a,b)$'' means that each new pixel value is $w$ if and only +if the corresponding old pixel value~$v$ was {\sl not\/} in that range. +Thus, for example, `^@cullit@' is an abbreviation for +\begindisplay \advance\belowdisplayskip by -4pt +@cull@ "currentpicture" @keeping@ $(1,"infinity")$ +\enddisplay +or for +\begindisplay \advance\abovedisplayskip by -4pt +@cull@ "currentpicture" @dropping@ $(-"infinity",0)$ +\enddisplay +(which both mean the same thing). A more complicated example is +\begindisplay +@cull@ $V_5$ @dropping@ $(-3,2)$ @withweight@ $-2$; +\enddisplay +this changes the pixel values of $V_5$ to $-2$ if they were $-4$ or less, +or if they were 3 or~more; pixel values between $-3$ and $+2$, inclusive, +are zeroed. + +\danger A cull command must not change pixel values from zero to nonzero. +For example, \MF\ doesn't let you say `@cull@ $V_1$ @keeping@ $(0,0)$', +since that would give a value of~1 to infinitely many pixels. + +\dangerexercise What is the effect of the following sequence of commands? +\begindisplay +@picture@ $V[\,]$;\cr +$V_1=V_2="currentpicture"$;\cr +@cull@ $V_1$ @dropping@ $(0,0)$;\cr +@cull@ $V_2$ @dropping@ $(-1,1)$;\cr +$"currentpicture":=V_1-V_2$;\cr +\enddisplay +\answer The pixel values of "currentpicture" become 1 if they were $\pm1$, +otherwise they become~0. + +\dangerexercise Given two picture variables $V_1$ and $V_2$, all of whose +pixel values are known to be either 0 or~1, explain how to replace $V_1$ by +(a)~$V_1\cap V_2$; \ (b)~$V_1\cup V_2$; \ (c)~$V_1\oplus V_2$. \ [The +{\sl^{intersection}\/} $V_1\cap V_2$ has 1's where $V_1$ and $V_2$ both are~1; +the {\sl^{union}\/} $V_1\cup V_2$ has 0's where $V_1$ and $V_2$ both are~0; +the {\sl^{symmetric difference}\/} or {\sl^{selective complement}\/} ^^{xor} +$V_1\oplus V_2$ has 1's where $V_1$ and $V_2$ are unequal.] +\answer (a) @addto@ $V_1$ @also@ $V_2$; @cull@ $V_1$ +@keeping@ $(2,2)$. \ (b) Same, but cull keeping $(1,2)$. +\ (c)~Same, but cull keeping $(1,1)$. + +\ddangerexercise Explain how to test whether or not two picture variables +are equal. +\answer Subtract one from the other, and cull the result dropping $(0,0)$; +then test to see if the total weight is zero. + +\ddangerexercise Look at the definitions of @fill@, @draw@, etc., in +Appendix~B and determine the effect of the following statements: +\begindisplay +\llap{a) }@draw@ $p$ @withpen@ $q$;\cr +\llap{b) }@draw@ $p$ @withweight@ 3;\cr +\llap{c) }@undraw@ $p$ @withweight@ $w$;\cr +\llap{d) }@fill@ $c$ @withweight@ $-2$ @withpen@ $q$;\cr +\llap{e) }@erase@ @fill@ $c$ @withweight@ 2 @withpen@ "currentpen";\cr +\llap{f) }@cullit@ @withweight@ 2.\cr +\enddisplay +\answer (a)~Same as `@draw@ $p$', but using $q$ instead of the +currently-picked-up pen. \ (b)~Same effect as `@draw@~$p$; @draw@~$p$; +@draw@~$p$' (but faster). \ (c)~Same as `@draw@~$p$ @withweight@~$w$', +because @undraw@'s `@withweight@~$-1$' is overridden. +\ (d)~Same as `@unfilldraw@~$c$; @unfilldraw@~$c$', + but using $q$ instead of "currentpen". +\ (e)~Same as `@erase@ @filldraw@~$c$', because the `@withweight@~2' is +overridden. \ [Since @erase@ has culled all weights to 0 or~1, there's +no need to ``doubly erase.''] +\ (f)~Same effect as `@cullit@; @addto@ "currentpicture" @also@ +"currentpicture"' (but faster). + +\ddangerexercise Devise a ^@safefill@ macro such that `@safefill@ $c$' increases +the pixel values of "currentpicture" by~1 in all pixels whose value would +be changed by the command `@fill@~$c$'. \ (Unlike @fill@, the @safefill@ command +never stops with a ``^{strange path}'' error; furthermore, it never increases +a pixel value by more than~1, nor does it decrease any pixel values, even +when the cycle~$c$ is quite wild.) +\answer @vardef@ @safefill@ @expr@ $c$ $=$ ^@save@ "region";\parbreak +\quad@picture@ "region"; "region"=@nullpicture@;\parbreak +\quad^@interim@ ^"turningcheck"$\null:=0$;\parbreak +\quad @addto@ "region" @contour@ $c$; \ + @cull@ "region" @dropping@ $(0,0)$;\parbreak +\quad @addto@ "currentpicture" @also@ "region" @enddef@. + +\ddangerexercise Explain how to replace a character by its ``^{outline}'': +All black pixels whose four closest neighbors are also +black should be changed to white, because they are in the interior. +\ (Diagonally adjacent neighbors don't count.) +\answer @cull@ "currentpicture" @keeping@ $(1,"infinity")$;\parbreak +@picture@ $v$; \ $v:="currentpicture"$;\parbreak +@cull@ "currentpicture" @keeping@ $(1,1)$ @withweight@ 3;\parbreak +@addto@ "currentpicture" @also@ + $v\;-\;v$ shifted "right"\parbreak +\qquad $\null-\;v$ shifted "left" + $\null-\;v$ shifted "up" + $\null-\;v$ shifted "down";\parbreak +@cull@ "currentpicture" @keeping@ $(1,4)$. + +\ddangerexercise In John ^{Conway}'s ``Game of ^{Life},'' pixels are said to +be either alive or dead. Each pixel is in contact with eight neighbors. +The live pixels in the $(n+1)$st generation are those who were dead and +had exactly three live neighbors in the $n$th generation, or those +who were alive and had exactly two or three live neighbors in the $n$th +generation. Write a short \MF\ program that displays successive +generations on your screen. +\answer (We assume that "currentpicture" initially has some configuration +in which all pixel values are zero or one; one means ``alive.'') +\begindisplay +@picture@ $v$; @def@ "c" $=$ "currentpicture" @enddef@;\cr +@forever@: \ $v:=c$; \ @showit@;\cr +\quad @addto@ $c$ @also@ $c$ shifted "left" $+$ "c" shifted "right";\cr +\quad @addto@ $c$ @also@ $c$ shifted "up" $+$ "c" shifted "down";\cr +\quad @addto@ $c$ @also@ $c-v$; \ @cull@ $c$ @keeping@ $(5,7)$; \ @endfor@.\cr +\enddisplay +(It is wise not to waste too much computer time watching this program.) + +\endchapter + +Blot out, correct, insert, refine, +Enlarge, diminish, interline; +Be mindful, when Invention fails, +To scratch your Head, and bite your Nails. +\author JONATHAN ^{SWIFT}, {\sl On Poetry: A Rapsody\/} (1733) % lines 87--90 +% Rapsody: stet! + +\bigskip + +The understanding that can be gained from computer drawings +is more valuable than mere production. +\author IVAN E. ^{SUTHERLAND}, {\sl Sketchpad\/} (1963) % chapter 9, section E + +\eject + \beginchapter Chapter 14. Paths + +The ^{boundaries} of regions to be filled, and the ^{trajectories} of +moving pens, are ``^{paths}'' that can be specified by the general methods +introduced in Chapter~3. \MF\ allows variables and expressions to be of +type @path@, so that a designer can build new paths from old ones in many +ways. Our purpose in this chapter will be to complete what Chapter~3 +began; we shall look first at some special features of plain \MF\ that +facilitate the creation of paths, then we shall go into the details of +everything that \MF\ knows about pathmaking. + +A few handy paths have been predefined in Appendix~B as part of plain \MF\!, +because they turn out to be useful in a variety of applications. For example, +^"quartercircle" is a path that represents one-fourth of a ^{circle} of +diameter~1; it runs from point $(0.5,0)$ to point~$(0,0.5)$. +The \MF\ program +\begindisplay +@beginchar@\kern1pt(|"a"|$,5"pt"\0,5"pt"\0,0)$;\cr +@pickup@ @pencircle@ scaled $(.4"pt"+"blacker")$;\cr +@draw@ "quartercircle" scaled 10"pt"; \ @endchar@;\cr +\enddisplay +therefore produces the character `\kern1pt{\manual\circa}' in position +`{\tt a}' of a font. + +\exercise +Write a program that puts a {\sl filled\/} quarter-circle +`\kern1pt{\manual\circb}' into font position~`{\tt b}'. +\answer @beginchar@\kern1pt(|"b"|$,5"pt"\0,5"pt"\0,0)$;\parbreak +@fill@ $((0,0)\dashto"quartercircle"\dashto{\rm cycle})$ +scaled 10"pt"; \ @endchar@. + +\exercise +Why are the `\kern1pt{\manual\circa}' and `\kern1pt{\manual\circb}' +characters of these examples only $5\,$pt wide and $5\,$pt high, although +they are made with the path `"quartercircle" scaled 10"pt"'? +\answer A "quartercircle" corresponds to a circle whose diameter +is~1; the radius is~$1\over2$. + +\dangerexercise +Use a {\sl rotated\/} quarter-circle to produce `{\manual\circc}\kern1pt' +in font position `{\tt c}'. +\answer @beginchar@\kern1pt(|"c"|$,5"pt"\0,5"pt"\0,0)$;\parbreak +@pickup@ @pencircle@ scaled $(.4"pt"+"blacker")$;\parbreak +@draw@ "quartercircle" rotated 90 scaled 10"pt" shifted $(5"pt",0)$; + \ @endchar@. + +\dangerexercise +Use "quartercircle" to produce `\kern1pt{\manual\circd}\kern1pt' +in font position `{\tt d}'. +\answer @beginchar@\kern1pt(|"d"|$,5"pt"\0\ast\rmsqrt2,5"pt"\0,0)$;\parbreak +@pickup@ @pencircle@ scaled $(.4"pt"+"blacker")$;\parbreak +@draw@ $((0,0)\dashto"quartercircle"\dashto{\rm cycle})$ + rotated 45 scaled 10"pt" shifted $(.5w,0)$;\parbreak +@endchar@. + +Plain \MF\ also provides a path called ^"halfcircle" that gives you +`{\manual\circc\circa}'; this path is actually made from two +quarter-circles, by defining +\begindisplay +"halfcircle" $=$ "quartercircle" \& $"quartercircle"\,{\rm rotated}\,90$. +\enddisplay +And of course there's also ^"fullcircle", a complete circle of unit diameter: +\begindisplay +"fullcircle" $=$ "halfcircle" \& $"halfcircle"\,{\rm rotated}\,180$ \& cycle. +\enddisplay +You can draw a circle of diameter $D$ centered at $(x,y)$ by saying +\begindisplay +@draw@ "fullcircle" scaled $D$ shifted $(x,y)$; +\enddisplay +similarly,\kern-.4pt\ `@draw@ "fullcircle" \kern-.5pt +xscaled \kern-1pt$A$ yscaled \kern-1pt$B$' +yields an ^{ellipse} with axes $A$~and~$B$\kern-1.3pt.\kern-.5pt + +Besides circles and parts of circles, there's also a standard square path +called "unitsquare"; this is a cycle that runs from $(0,0)$ to $(1,0)$ +to $(1,1)$ to $(0,1)$ and back to~$(0,0)$. For example, the command +`@fill@ "unitsquare"' adds~1 to a single pixel value, as discussed in +the previous chapter. + +\exercise Use "fullcircle" and "unitsquare" to produce the characters +`{\manual\circe}' and `{\manual\circf}' in font positions `{\tt e}' +and~`{\tt f}', respectively. These characters should be $10\,$pt wide +and $10\,$pt tall, and their centers should be $2.5\,$pt above +the baseline. +\answer @beginchar@\kern1pt(|"e"|$,10"pt"\0,7.5"pt"\0,2.5"pt"\0)$;\parbreak +@pickup@ @pencircle@ scaled $(.4"pt"+"blacker")$;\parbreak +@for@ $D=.2w,.6w,w$: \ +@draw@ "fullcircle" scaled $D$ shifted $(.5w,.5[-d,h])$;\parbreak +@endfor@ @endchar@. +\par\medskip\noindent +The program for `{\manual\circf}' is similar, but `"fullcircle" +scaled~$D$' is replaced by +\begindisplay +"unitsquare" shifted $-(.5,.5)$ rotated 45 scaled $(D/\rmsqrt2)$. +\enddisplay + +\hrule +\medskip +\line{\figbox{14a}{220\apspix}{690\apspix}\vbox \hfil +\vbox{\hsize=18pc \def\\{\vskip1.5pt} \parindent=0pt +\eightpoint +\obeylines +\leavevmode @path@ $"branch"[\,]$, "trunk"; +\\ +$"branch"_1= "flex"((0,660),(-9,633),(-22,610))$ + \quad\& "flex"$((-22,610),(-3,622),(17,617))$ + \quad\& "flex"$((17,617),(7,637),(0,660))$ \& cycle; +\\ +$"branch"_2="flex"((30,570),(10,590),(-1,616))$ + \quad\& "flex"$((-1,616),(-11,592),(-29,576),(-32,562))$ + \quad\& "flex"$((-32,562),(-10,577),(30,570))$ \& cycle; +\\ +$"branch"_3="flex"((-1,570),(-17,550),(-40,535))$ + \quad\& "flex"$((-40,535),(-45,510),(-60,477))$ + \quad\& "flex"$((-60,477),(-20,510),(40,512))$ + \quad\& "flex"$((40,512),(31,532),(8,550),(-1,570))$ \& cycle; +\\ +$"branch"_4="flex"((0,509),(-14,492),(-32,481))$ + \quad\& "flex"$((-32,481),(-42,455),(-62,430))$ + \quad\& "flex"$((-62,430),(-20,450),(42,448))$ + \quad\& "flex"$((42,448),(38,465),(4,493),(0,509))$ \& cycle; +\\ +$"branch"_5="flex"((-22,470),(-23,435),(-44,410))$ + \quad\& "flex"$((-44,410),(-10,421),(35,420))$ + \quad\& "flex"$((35,420),(15,455),(-22,470))$ \& cycle; +\\ +$"branch"_6="flex"((18,375),(9,396),(5,420))$ + \quad\& "flex"$((5,420),(-5,410),(-50,375),(-50,350))$ + \quad\& "flex"$((-50,350),(-25,375),(18,375))$ \& cycle; +\\ +$"branch"_7="flex"((0,400),(-13,373),(-30,350))$ + \quad\& "flex"$((-30,350),(0,358),(30,350))$ + \quad\& "flex"$((30,350),(13,373),(0,400))$ \& cycle; +\\ +$"branch"_8="flex"((50,275),(45,310),(3,360))$ + \quad\& "flex"$((3,360),(-20,330),(-70,300),(-100,266))$ + \quad\& "flex"$((-100,266),(-75,278),(-60,266))$ + \quad\& "flex"$((-60,266),(0,310),(50,275))$ \& cycle; +\\ +$"branch"_9="flex"((10,333),(-15,290),(-43,256))$ + \quad\& "flex"$((-43,256),(8,262),(58,245))$ + \quad\& "flex"$((58,245),(34,275),(10,333))$ \& cycle; +\\ +$"branch"_{10}="flex"((8,262),(-21,249),(-55,240))$ + \quad\& "flex"$((-55,240),(-51,232),(-53,220))$ + \quad\& "flex"$((-53,220),(-28,229),(27,235))$ + \quad\& "flex"$((27,235),(16,246),(8,262))$ \& cycle; +\\ +$"branch"_{11}="flex"((0,250),(-25,220),(-70,195))$ + \quad\& "flex"$((-70,195),(-78,180),(-90,170))$ + \quad\& "flex"$((-90,170),(-5,188),(74,183))$ + \quad\& "flex"$((74,183),(34,214),(0,250))$ \& cycle; +\\ +$"branch"_{12}="flex"((8,215),(-35,175),(-72,155))$ + \quad\& "flex"$((-72,155),(-75,130),(-92,110),(-95,88))$ + \quad\& "flex"$((-95,88),(-65,117),(-54,104))$ + \quad\& "flex"$((-54,104),(10,151),(35,142))$ + \qquad$\to"flex"((42,130),(60,123),(76,124))$ + \quad\& "flex"$((76,124),(62,146),(26,180),(8,215))$ \& cycle; +\\ +$"trunk"=(0,660)\ddashto(-12,70)\to\{\curl 5\}(-28,-8)$ + \quad\& "flex"$((-28,-8),(-16,-4),(-10,-11))$ + \quad\& "flex"$((-10,-11),(0,-5),(14,-10))$ + \quad\& "flex"$((14,-10),(20,-6),(29,-11))$ + \quad\& $(29,-11)\{\curl 4\}\to(10,100)\ddashto{\rm cycle}$; +}} + +Sometimes it's necessary to draw rather complicated curves, and plain \MF\ +provides a `^"flex"' operation that can simplify this task. The +construction `$"flex"(z_1,z_2,z_3)$' stands for the path +`$z_1\to z_2\{z_3-z_1\}\to z_3$', +and similarly `$"flex"(z_1,z_2,z_3,z_4)$' stands for +`$z_1\to z_2\{z_4-z_1\}\to z_3\{z_4-z_1\}\to z_4$'; in general +\begindisplay +$"flex"(z_1,z_2,\ldots,z_{n-1},z_n)$ +\enddisplay +is an abbreviation for the path +\begindisplay +$z_1\to z_2\{z_n-z_1\}\to\;\cdots\;\to z_{n-1}\{z_n-z_1\}\to z_n$. +\enddisplay +The idea is to specify two endpoints, $z_1$ and $z_n$, together with +one or more intermediate points where the path is traveling in the +same direction as the straight line from $z_1$ to~$z_n$; these +intermediate points are easy to see on a typical curve, so they +are natural candidates for key points. + +For example, the command +\begindisplay +@fill@ \ $"flex"(z_1,z_2,z_3)$ \& $"flex"(z_3,z_4,z_5)$\cr +\indent\& $"flex"(z_5,z_6,z_7)$ \& $"flex"(z_7,z_8,z_9,z_1)$ \& cycle\cr +\enddisplay +will fill the shape +\displayfig 14b (7pc) +after the points $z_1$, \dots, $z_9$ have been suitably defined. This +shape occurs as the fourth branch from the top of ``^{El Palo Alto},'' +a tree that is often used to symbolize ^{Stanford University}. The thirteen +paths on the opposite page were defined by simply sketching the tree on +a piece of graph paper, then reading off approximate values of key +points ``by eye'' while typing the code into a computer. \ (A good radio +or television program helps to stave off boredom when you're typing +a bunch of data like this.) \ The entire +figure involves a total of 47~flexes, most of which are pretty mundane; +but $"branch"_{12}$ does contain an interesting subpath of the form +\begindisplay +$"flex"(z_1,z_2,z_3)\to"flex"(z_4,z_5,z_6)$, +\enddisplay +which is an abbreviation for +\begindisplay +$z_1\to z_2\{z_3-z_1\}\to z_3\to z_4\to z_5\{z_6-z_4\}\to z_6$. +\enddisplay +Since $z_3\ne z_4$ in this example, a smooth curve runs through all six +points, although two different flexes are involved. + +\hangindent -1in \hangafter-2 +Once the paths have been defined, +\rightfig 14aa (.5in x 1.25in) ^-8pt +it's easy to use them to make +symbols like the white-on-black medallion shown here: +\begindisplay +@beginchar@\kern1pt(|"T"|$,.5"in"\0,1.25"in"\0,0)$;\cr +\;\cr +@fill@ "superellipse"$((w,.5h),(.5w,h),(0,.5h),(.5w,0),.8)$;\cr +$"branch"_0="trunk"$;\cr +@for@ $n=0$ @upto@ 12:\cr +\quad ^@unfill@ $"branch"[n]$ shifted $(150,50)$ scaled $(w/300)$;\cr +@endfor@ @endchar@;\cr +\enddisplay +The oval shape that encloses this tree is a ^"superellipse", which is +another special kind of path provided by plain \MF\!\null. To get a general +shape of this kind, you can write +\begindisplay +"superellipse"$("right\_point","top\_point","left\_point","bottom\_point", + "superness")$ +\enddisplay +where `"superness"' controls the amount by which the curve differs from a +true ^{ellipse}. For example, here are four superellipses, drawn with varying +amounts of ^{superness}, using a +@pencircle@ xscaled~0.7"pt" yscaled 0.2"pt" rotated~30: +\displayfig 14c (150\apspix) +The "superness" should be between 0.5 (when you get a diamond) and 1.0 +(when you get a square); values in the vicinity of 0.75 are usually preferred. +The zero symbol `{\tt 0}' in this book's typewriter font was +drawn as a superellipse of superness $2^{-.5}\approx.707$, which +corresponds to a normal ellipse; the uppercase letter `{\tt O}' was +drawn with superness $2^{-.25}\approx.841$, to help distinguish it +from the zero. The ambiguous symbol `{\cmman0}' (which is not in the +font, but \MF\ can of course draw it) lies between these two extremes; its +superness is 0.77. + +\ddanger A mathematical superellipse satisfies the equation $\vert +x/a\vert^\beta+\vert y/b\vert^\beta=1$, for some exponent $\beta$. It has +extreme points $(\pm a,0)$ and $(0,\pm b)$, as well as the ``corner'' +points $(\pm\sigma a,\pm\sigma b)$, where $\sigma=2^{-1/\beta}$ is the +superness. The tangent to the curve at $(\sigma a,\sigma b)$ runs in the +direction $(-a,b)$, hence it is parallel to a line from $(a,0)$ to +$(0,b)$. Gabriel ^{Lam\'e} invented the superellipse in 1818, and +Piet ^{Hein} popularized the special case +$\beta=2.5$ [see Martin ^{Gardner}, {\sl Mathematical +Carnival\/} (New York: Knopf, 1975), 240--254]; this special case +corresponds to a superness of $2^{-.4}\approx.7578582832552$. Plain \MF's +"superellipse" routine does not produce a perfect superellipse, nor does +^"fullcircle" yield a true circle, but the results are close enough for +practical purposes. + +\ddangerexercise Try "superellipse" with superness values less than 0.5 +or greater than~1.0; explain why you get weird shapes in such cases. +\answer There are inflection points, because there are no bounding triangles +for the `$\ldots$' operations in the "superellipse" macro of Appendix~B, +unless $.5\le s\le1$. + +Let's look now at the symbols that are used between key points, when we +specify a path. There are five such tokens in plain \MF: +\begindisplay +$\to$&free curve;\cr +$\ldots$&bounded curve;\cr +$\dashto$&straight line;\cr +$\ddashto$&``tense'' line;\cr +\&&splice.\cr +\enddisplay +^^{..}^^{...}^^{--}^^{---}^^{ampersand} +In general, when you write `$z_0\to z_1\to\\to z_{n-1}\to z_n$', +\MF\ will compute the path of length~$n$ that represents its idea of the +``most pleasing curve'' through the given points $z_0$ through~$z_n$. +The symbol `$\ldots$' is essentially the same as `$\to$'\thinspace, except +that it confines the path to a bounding triangle whenever possible, as +explained in Chapter~3. A straight line segment `$z_{k-1}\dashto z_k$' +usually causes the path to change course abruptly at $z_{k-1}$ and $z_k$. +By contrast, a segment specified by `$z_{k-1}\ddashto z_k$' will be a +straight line that blends smoothly with the neighboring curves; i.e., the +path will enter $z_{k-1}$ and leave~$z_k$ in the direction of +$z_k-z_{k-1}$. \ (The "trunk" of El Palo Alto makes use of this option, +and we have also used it to draw the signboard of the dangerous bend +symbol at the end of Chapter~12.) \ Finally, the `\&' operation joins two +independent paths together at a common point, just as `\&' concatenates +two strings together. + +Here, for example, is a somewhat silly path that illustrates all five +basic types of joinery: +\displayfig 14d (120\apspix) +\begindisplay +$z_0=(0,100)$; \ $z_1=(50,0)$; \ $z_2=(180,0)$;\cr +@for@ $n=3$ @upto@ 9: $z[n]=z[n-3]+(200,0)$; \ @endfor@\cr +@draw@ $z_0\to z_1\ddashto z_2\ldots\{"up"\}z_3$\cr +\qquad\& $z_3\to z_4\dashto z_5\ldots\{"up"\}z_6$\cr +\qquad\& $z_6\ldots z_7\ddashto z_8\to\{"up"\}z_9$.\cr +\enddisplay + +\danger The `$\ldots$' operation is usually used only when one or both of the +adjacent directions have been specified (like `$\{"up"\}$' in this example). +Plain \MF's ^"flex" construction actually uses `$\ldots$'\thinspace, +not `$\to$' as stated earlier, because this avoids inflection points in +certain situations. + +\danger A path like `$z_0\ddashto z_1\ddashto z_2$' is almost indistinguishable +from the broken line `$z_0\dashto z_1\dashto z_2$', except that if you +enlarge the former path you will see that its lines aren't perfectly +straight; they bend just a little, so that the curve is ``smooth'' at +$z_1$ although there's a rather sharp turn there. \ (This means that +the ^{autorounding} operations discussed in Chapter~24 will apply.) \ +For example, the path $(0,3)\ddashto(0,0)\ddashto(3,0)$ is equivalent to +\begindisplay +$(0,3)\to \controls\,(-0.0002,2.9998)\and (-0.0002,0.0002)$\cr +$\quad\to(0,0)\to \controls\,(0.0002,-0.0002) \and (2.9998,-0.0002)\to(3,0)$\cr +\enddisplay +while $(0,3)\dashto(0,0)\dashto(3,0)$ consists of two perfectly straight +segments: +\begindisplay +$(0,3)\to \controls\,(0,2)\and (0,1)$\cr +$\quad\to(0,0)\to \controls\,(1,0) \and (2,0)\to(3,0)$.\cr +\enddisplay + +\dangerexercise Plain \MF's ^"unitsquare" path is defined to be +`$(0,0)\dashto(1,0)\dashto(1,1)\dashto(0,1)\dashto\cycle$'. +Explain how the same path could have been defined using only `$\to$' and~`\&', +not `$\dashto$' or explicit directions. +\answer $(0,0)\to(1,0)\;\&\;(1,0)\to(1,1)\;\&\;(1,1)\to(0,1) +\;\&\;(0,1)\to(0,0)\;\&\;\cycle$. Incidentally, if each `\&' in this path +is changed to `$\to$', we get a path that goes through the same points; +but it is a path of length~8 that comes to a complete stop at each +corner. In other words, the path remains motionless between times $1\le t\le2$, +$3\le t\le4$, etc. This length-8 path therefore behaves somewhat strangely +with respect to the `^{directiontime}' operation. It's better to use `\&' +than to repeat points of a path. + +\ddanger Sometimes it's desirable to take a path and change all its +connecting links to `$\ddashto$', regardless of what they were originally; +the key points are left unchanged. Plain \MF\ has a ^"tensepath" operation +that does this. For example, "tensepath"~"unitsquare"~$=$ +$(0,0)\ddashto(1,0)\ddashto(1,1)\ddashto(0,1)\ddashto\cycle$. + +When \MF\ is deciding what curves should be drawn in place of `$\to$' or +`$\ldots$', it has to give special consideration to the beginning and +ending points, so that the path will start and finish as gracefully as +possible. The solution that usually works out best is to make the first +and last path segments very nearly the same as arcs of circles; an +unadorned path of length~2 like `$z_0\to z_1\to z_2$' will therefore turn +out to be a good approximation to the unique circular arc that passes +through $(z_0,z_1,z_2)$, except in extreme cases. You can change this +default behavior at the endpoints either by specifying an explicit +direction or by specifying an amount of ``^{curl}.'' If you call for +curliness less than~1, the path will decrease its curvature in the +vicinity of the endpoint (i.e., it will begin to turn less sharply); if +you specify curliness greater than~1, the curvature will increase. +\ (See the definition of El Palo Alto's "trunk", earlier in this chapter.) + +Here, for example, are some pairs of parentheses that were drawn using +various amounts of curl. In each case the shape was drawn by a statement +of the form `@penstroke@ $z_{0e}\{\curl c\}\to z_{1e}\to\{\curl c\}z_{2e}$'; +different values of $c$ produce different-looking parentheses:\def\\{\kern1pt} +\begindisplay +curl value\hidewidth&\hfil0&\hfil1&\hfil2&\hfil4&\kern-10pt"infinity"\cr +yields\quad&\cmman 1\\2&\cmman 3\\4&\cmman 5\\6&\cmman 7\\8&\cmman 9\\:\cr +\enddisplay +(The parentheses of Computer Modern typefaces are defined by the +somewhat more general scheme described in Chapter~12; explicit directions are +specified at the endpoints, instead of curls, because this produces +better results in unusual cases when the characters are extremely +tall or extremely wide.) + +\danger The amount of curl should not be negative. When the curl is +very large, \MF\ doesn't actually make an extremely sharp turn at the endpoint; +instead, it changes the rest of the path so that there is comparatively +little curvature at the neighboring point. + +\danger Chapter 3 points out that we can change \MF's default curves +by specifying nonstandard ``^{tension}'' between points, or even by +specifying explicit control points to be used in the four-point method. +Let us now study the full syntax of path expressions, so that we +can come to a complete understanding of the paths that \MF\ is able to make. +Here are the general rules: +\beginsyntax +\is\alt + \alt[(][)] + \alt[reverse] + \alt[subpath][of] +\is\alt + \alt +\is\alt +\is\alt + \alt + \alt[cycle] +\is\kern-5pt\null + \alt +\is +\is + \alt[\char'173][curl][\char'175] + \alt[\char'173][\char'175] + \alt[\char'173][,][\char'175] +\is[\&]\alt[..]\alt[..][..]\alt[..][..] +\is[tension] + \alt[tension][and] +\is + \alt[atleast] +\is[controls] + \alt[controls][and] +\endsyntax +The operations `$\ldots$' and `$\dashto$' and `$\ddashto$' are conspicuously +absent from this syntax; that is because Appendix~B defines them as macros: +\begindisplay +$\ldots$&is an abbreviation for `$\to\tension\atleast1\to$'\thinspace;\cr +$\dashto$&is an abbreviation for `$\{\curl1\}\to\{\curl1\}$'\thinspace;\cr +$\ddashto$&is an abbreviation for `$\to\tension"infinity"\to$'\thinspace.\cr +\enddisplay + +\danger These syntax rules specify a wide variety of possibilities, even though +they don't mention `$\dashto$' and such things explicitly, so we shall +now spend a little while looking carefully at their implications. +A path expression essentially has the form +\begindisplay +$p_0\quad j_1\quad p_1\quad j_2\quad\cdots\quad j_n\quad p_n$ +\enddisplay +where each $p_k$ is a tertiary expression of type pair or path, and where +each $j_k$ is a ``path join.'' A path join begins and ends with a +``direction specifier,'' and has a ``basic path join'' in the middle. +A direction specifier can be empty, or it can be `$\{\curl c\}$' +for some $c\ge0$, or it can be a direction vector enclosed in braces. +For example, `$\{"up"\}$' specifies an upward direction, because plain +\MF\ defines ^"up" to be the pair $(0,1)$. This same direction could be +specified by `$\{(0,1)\}$' or `$\{(0,10)\}$', or without parentheses as +`$\{0,1\}$'. If a specified direction vector turns out to be $(0,0)$, +\MF\ behaves as if no direction had been specified; i.e., `$\{0,0\}$' +is equivalent to `\'. An empty direction specifier is implicitly +filled in by rules that we shall discuss later. + +\danger A basic path join has three essential forms: \ (1)~`\&' simply +concatenates two paths, which must share a common endpoint. +\ (2)~`$\to\tension\alpha\and\beta\to$' means that a curve should be +defined, having respective ``tensions'' $\alpha$ and~$\beta$. +Both $\alpha$ and~$\beta$ must be equal to 3/4 or~more; +we shall discuss ^{tension} later in this chapter. +\ (3)~`$\to\controls u\and v\to$' defines a curve with intermediate +control points $u$ and~$v$. + +\danger Special abbreviations are also allowed, so that the long forms +of basic path joins can usually be avoided: `$\to$' by itself stands for +`$\to\tension 1\and1\to$'\thinspace, + while `$\to\tension\alpha\to$' stands for +`$\to\tension\alpha\and\alpha\to$'\thinspace, + and `$\to\controls u\to$' stands for +`$\to\controls u\and u\to$'\thinspace. + +\danger Our examples so far have always constructed paths from points; +but the syntax shows that it's also possible to write, e.g., +`$p_0\to p_1\to p_2$' when the $p$'s themselves are paths. What does +this mean? Well, every such path will already have been changed into a +sequence of curves with explicit control points; \MF\ expands such +paths into the corresponding sequence of points and basic path joins +of type~(3). For example, `$((0,0)\to(3,0))\to(3,3)$' is essentially +the same as `$(0,0)\to\controls\,(1,0)\and(2,0)\to(3,0)\to(3,3)$', +because `$(0,0)\to(3,0)$' is the path +`$(0,0)\to\controls\,(1,0)\and(2,0)\to(3,0)$'. +If a cycle is expanded into a subpath in this way, its cyclic +nature will be lost; its last point will simply be a copy of its first point. + +\danger Now let's consider the rules by which empty direction specifiers +can inherit specifications from their environment. +An empty direction specifier at the beginning or end of a path, or just next +to the `\&' operator, is effectively replaced by `$\{\curl1\}$'. +This rule should be interpreted properly with respect to cyclic paths, which +have no beginning or end; for example, `$z_0\to z_1\,\&\,z_1\to z_2\to\cycle$' +is equivalent to `$z_0\to z_1\{\curl1\}\&\{\curl1\}z_1\to z_2\to\cycle$'. + +\danger If there's a nonempty direction specifier after a point but not +before it, the nonempty one is copied into both places. Thus, for example, +`$\to z\{w\}$' is treated as if it were `$\to\{w\}z\{w\}$'. If there's +a nonempty direction specifier before a point but not after it, the +nonempty one is duplicated in a similar way. A~basic path join +`$\to\controls u\and v\to$' specifies explicit control points that +override any direction specifiers that may immediately surround it. + +\danger An empty direction specifier next to an explicit control point +inherits the direction of the adjacent path segment. More precisely, +`$\to z\to\controls u\and v\to$' is treated as if it were +`$\to\{u-z\}z\to\controls u\and v\to$' if $u\ne z$, or as if it were +`$\to\{\curl1\}z\to\controls u\and v\to$' if $u=z$. Similarly, +`$\to\controls u\and v\to z\to$' is treated as if $z$ were followed by +$\{z-v\}$ if $z\ne v$, by $\{\curl1\}$ otherwise. + +\ddanger After the previous three rules have been applied, we might still +be left with cases in which there are points surrounded on both sides +by empty direction specifiers. \MF\ must choose appropriate directions +at such points, and it does so by applying the following algorithm +due to John ^{Hobby} [{\sl Discrete and Computational Geometry\/ \bf1} +(1986), 123--140]: Given a sequence +\begindisplay +$z_0\{d_0\}\to\tension\alpha_0\and\beta_1\to z_1 + \to\tension\alpha_1\and\beta_2\to z_2$\cr +$\hskip5em\\;z_{n-1}\to\tension\alpha_{n-1}\and\beta_n\to\{d_n\}z_n$\cr +\enddisplay +for which interior directions need to be determined, we will regard the +$z$'s as if they were complex numbers. Let $l_k=\vert z_k-z_{k-1}\vert$ be +the distance from $z_{k-1}$ to $z_k$, and let +$\psi_k=\arg\bigl((z_{k+1}-z_k)/(z_k-z_{k-1} )\bigr)$ be the turning angle +at~$z_k$. We wish to find direction vectors $w_0$, $w_1$, \dots,~$w_n$ so +that the given sequence can effectively be replaced by +\begindisplay +$z_0\{w_0\}\to\tension\alpha_0\and\beta_1\to\{w_1\}z_1 + \{w_1\}\to\tension\alpha_1\and\beta_2\to\{w_2\}z_2$\cr +$\hskip5em\\;z_{n-1}\{w_{n-1}\}\to + \tension\alpha_{n-1}\and\beta_n\to\{w_n\}z_n$.\cr +\enddisplay +Since only the directions of the $w$'s are significant, not the magnitudes, +it suffices to determine the angles $\theta_k=\arg\bigl(w_k/(z_{k+1}-z_k +)\bigr)$. For convenience, we also let $\phi_k=\arg\bigl((z_k-z_{k-1})/w_k +\bigr)$, so that +$$\line{\indent$\theta_k+\phi_k+\psi_k\;=\;0$.\hfil$(\ast)$}$$ +Hobby's paper introduces the notion of ``^{mock curvature}'' according to +which the following equations should hold at interior points: +$$\line{\indent$\beta_k^2l_k^{-1}\bigl(\alpha_{k-1}^{-1}(\theta_{k-1} ++\phi_k)-3\phi_k\bigr)=\alpha_k^2l_{k+1}^{-1}\bigl(\beta_{k+1}^{-1} +(\theta_k+\phi_{k+1})-3\theta_k\bigr)$.\hfil$({\ast}{\ast})$}$$ +We also need to consider boundary conditions. If $d_0$ is an explicit +direction vector~$w_0$, we know $\theta_0$; otherwise $d_0$ is +`$\curl\gamma_0$' and we set up the equation +$$\line{\indent$\alpha_0^2\bigl(\beta_1^{-1}(\theta_0+\phi_1)-3\theta_0\bigr) +=\gamma_0\beta_1^2\bigl(\alpha_0^{-1}(\theta_0+\phi_1)-3\phi_1\bigr)$. +\hfil$({\ast}{\ast}{\ast})$}$$ +If $d_n$ is an explicit vector~$w_n$, we know $\phi_n$; otherwise +$d_n$ is `$\curl\gamma_n$' and we set +$$\line{\indent$\beta_n^2\bigl(\alpha_{n-1}^{-1}(\theta_{n-1}+\phi_n)-3\phi_n +\bigr)=\gamma_n\alpha_{n-1}^2\bigl(\beta_n^{-1}(\theta_{n-1}+\phi_n)-3 +\theta_{n-1}\bigr)$.\hfil$({\ast}{\ast}{\ast}')$}$$ +It can be shown that the conditions $\alpha_k\ge3/4$, $\beta_k\ge +3/4$, $\gamma_k\ge0$ imply that there is a unique solution to the +system of equations consisting of $(\ast)$ and $({\ast}{\ast})$ for $0 and +explain what \MF\ does with the specification `$(0,0)\to(3,3)\to\cycle +\{\curl1\}$'. +\answer It treats this as `$((0,0)\to(3,3)\to\cycle)\{\curl1\}$'; i.e., +the part up to and including `cycle' is treated as a subpath +(cf.~`|p2|' in Chapter~8). The cycle is broken, after which we have +`$(0,0)\to\controls\,(2,-2)\and(5,1)\to(3,3)\to\controls\,(1,5)\and +(-2,2)\to(0,0)\{\curl1\}$'. Finally the `$\{\curl1\}$' is dropped, +because all control points are known. \ (The syntax by itself isn't +really enough to answer this question, as you probably realize. +You also need to be told that the computation of directions and +control points is performed whenever \MF\ uses the last two +alternatives in the definition of \.) + +\danger Now let's come back to simpler topics relating to paths. +Once a path has been specified, there are lots of things you can +do with it, besides drawing and filling and suchlike. For example, +if $p$ is a path, you can reverse its direction by saying `reverse~$p$'; +the ^{reverse} of `$z_0\to\controls u\and v\to z_1$' is +`$z_1\to\controls v\and u\to z_0$'. + +\dangerexercise True or false: length reverse $p$ $=$ length $p$, +for all paths~$p$. +\answer True. The length of a path is the number of +`$z_k\to\controls u_k\and v_{k+1}\to z_{k+1}$' segments that it contains, +after all control points have been chosen. + +\danger It's convenient to associate ``^{time}'' with paths, +by imagining that we move along a path of length~$n$ as time passes +from 0 to~$n$. \ (Chapter~8 has already illustrated this notion, with +respect to an almost-but-not-quite-circular path called~|p2|; it's a good idea +to review the discussion of paths and ^{subpaths} in Chapter~8 now before +you read further.) \ Given a path +\begindisplay +$p=z_0\to\controls u_0\and v_1\to z_1\,\\,z_{n-1}\to + \controls u_{n-1}\and v_n\to z_n$ +\enddisplay +and a number $t$, \MF\ determines `point $t$ of $p$' as follows: +If $t\le0$, the result is~$z_0$; if $t\ge n$, the result is~$z_n$; +otherwise if $k\le t\,z_{n-1}\to + \controls u_{n-1}\and v_n\to\cycle$ +\enddisplay +and a number $t$, \MF\ determines `point $t$ of $c$' in essentially the +same way, except that $t$ is first reduced modulo~$n$ so as to lie +in the range $0\le tt_2$, subpath~$(t_1,t_2)$ of~$p$~$=$ +reverse subpath~$(t_2,t_1)$ of~$p$. Henceforth we shall assume that +$t_1\le t_2$. \ (2)~If $t_1=t_2$, subpath~$(t_1,t_2)$ of~$p$~$=$ +point~$t_1$ of~$p$, a path of length zero. Henceforth we shall assume that +$t_1n$ and $p$ is not a cycle, subpath~$(t_1,t_2)$ of~$p$~$=$ + subpath~$\bigl(\min(t_1,n),n\bigr)$ of~$p$. +Henceforth we shall assume that $0\le t_11$, subpath~$(t_1,t_2)$ of~$p$~$=$ + subpath~$(t_1,1)$ of~$p$~\& subpath~$(1,t_2)$ of~$p$. +Henceforth we shall assume that $0\le t_1\,z_{n-1}\to + \controls u_{n-1}\and v_n\to z_n$. +\enddisplay +If $t0$, precontrol~$t$ of~$p$ is the last control point in +subpath~$(0,t)$ of~$p$; if $t\le 0$, precontrol~$t$ of~$p$ is~$z_0$. +In particular, if $t$ is an integer, postcontrol~$t$ of~$p$ is $u_t$ +for $0\le t> (xpart t,ypart t,xxpart t,xypart t,yxpart t,yypart t) +\endtt +just as it would type `{\tt>> (xpart u,ypart u)}' for a completely +unknown variable~$u$ of type @pair@. You can access individual components +of a transform by referring to `^{xpart}~$t$', `^{ypart}~$t$', +^^{xypart}^^{yxpart}^^{yypart} +`^{xxpart}~$t$', etc. + +\outer\def\begindemo#1{$$\advance\baselineskip by2pt + \catcode`\"=\other + \halign\bgroup\indent\hbox to #1{\tt##\hfil}&\tt##\hfil\cr + \noalign{\vskip-2pt}} +\outer\def\enddemo{\egroup$$} +\def\demohead{\it\kern-2pt You type&\it\kern-1pt And the result is\cr + \noalign{\nobreak\vskip2pt}} + +\danger Once again, we can learn best by computer experiments with the +|expr| file (cf.~Chapter~8); this time the idea is to play with transforms: +\begindemo{175pt} +\demohead +identity&(0,0,1,0,0,1)\cr +identity shifted (a,b)&(a,b,1,0,0,1)\cr +identity scaled s&(0,0,s,0,0,s)\cr +identity xscaled s&(0,0,s,0,0,1)\cr +identity yscaled s&(0,0,1,0,0,s)\cr +identity slanted s&(0,0,1,s,0,1)\cr +identity rotated 90&(0,0,0,-1,1,0)\cr +identity rotated 30&(0,0,0.86603,-0.5,0.5,0.86603)\cr +identity rotatedaround ((2,3),90)&(5,1,0,-1,1,0)\cr +(x,y) rotatedaround ((2,3),90)&(-y+5,x+1)\cr +(x,y) reflectedabout ((0,0),(0,1))&(-x,y)\cr +(x,y) reflectedabout ((0,0),(1,1))&(y,x)\cr +(x,y) reflectedabout ((5,0),(0,10))&(-0.8y-0.6x+8,0.6y-0.8x+4)\cr +\enddemo + +\dangerexercise Guess the result of `|(x,y) reflectedabout ((0,0),(1,0))|'. +\answer |(x,-y)|. + +\dangerexercise What transform takes $(x,y)$ into $(-x,-y)$? +\answer $(x,y)$ rotated 180, or $(x,y)$ scaled $-1$. + +\dangerexercise True or false:\quad $\bigl(-(x,y)\bigr)$ transformed $t$ +$=$ $-\bigl((x,y)$ transformed $t\bigr)$. +\answer True if and only if ${\rm xpart}\,t={\rm ypart}\,t=0$. If the +stated equation holds for at least one pair $(x,y)$, it holds for all $(x,y)$. +According to the syntax of Chapter~8, \MF\ interprets `$-(x,y)$ transformed~$t$' +as $\bigl(-(x,y)\bigr)$ transformed~$t$. \ (Incidentally, mathematicians +call \MF's transformers ``^{affine transformations},'' and the special case in +which the xpart and ypart are zero is called ``^{homogeneous}.'') + +\danger In order to have some transform variables to work with, it's necessary +to `^{hide}' some declarations and commands before giving the next |expr|s: +\begindemo{175pt} +\demohead +hide(transform t[]) t1&(xpart t1,ypart t1,xxpart...)\cr +hide(t1=identity zscaled(1,2)) t1&(0,0,1,-2,2,1)\cr +hide(t2=t1 shifted (1,2)) t2&(1,2,1,-2,2,1)\cr +t2 xscaled s&(s,2,s,-2s,2,1)\cr +unknown t2&false\cr +transform t2&true\cr +t1=t2&false\cr +t1 transformed \\cr +\ transformed \\cr +\ transformed \\cr +\enddisplay +but \MF\ will balk at `\ transformed \'. This is +not the most lenient rule that could have been implemented, but it +does have the virtue of being easily remembered. + +\dangerexercise If $z_1$ and $z_2$ are unknown pairs, you can't +say `$z_1$ shifted~$z_2$', because `shifted~$z_2$' is an unknown +transform. What can you legally say instead? +\answer $z_1+z_2$. + +\begingroup\def\dbend{{\manual\char126}} % lefty dangerous bend sign +\dangerexercise Suppose "dbend" is a picture variable that contains +a normal dangerous bend sign, as in the ``reverse-video'' example +of Chapter~13. Explain how to transform it into the ^{left-handed +dangerous bend} that heads this paragraph. +\answer @beginchar@$(126,25u\0,"hheight"\0+"border"\0,0)$; +|"Dangerous left bend"|;\parbreak +$"currentpicture":="dbend"$ reflectedabout $\bigl((.5w,0),(.5w,h)\bigr)$; \ +@endchar@;\medskip\noindent +The same idea can be used to create right ^{parentheses} as perfect mirror +images of left parentheses, etc., if the parentheses aren't slanted. + +\endgroup + +\danger The next three lines illustrate the fact that you can specify +a transform completely by specifying the images of three points: +\begindemo{175pt} +\demohead +hide((0,0)transformed t4=(1,2)) t4&(1,2,xxpart t4,xypart t4,...)\cr +hide((1,0)transformed t4=(4,5)) t4&(1,2,3,xypart t4,3,yypart t4)\cr +hide((1,4)transformed t4=(0,0)) t4&(1,2,3,-1,3,-1.25)\cr +\enddemo +The points at which the transform is given shouldn't all lie on +a straight line. + +\danger Now let's use transformation to make a little ^{ornament}, based +on a `{\manual\oneu\kern1pt}' shape replicated four times: +\qquad\xleaders\hbox{$\vcenter{\hbox{\manual\fouru}}$}\hfill\ + +\vskip-6mm +\displayfig 15a (396\apspix) + +\begingroup\ninepoint\noindent The following program merits careful study: +$$\halign{\hbox to\parindent{\hfil\sevenrm#\ \ \ }&#\hfil\cr +1&@beginchar@\kern1pt(|"4"|$,11"pt"\0,11"pt"\0,0)$;\cr +2&@pickup@ @pencircle@ scaled 3/4"pt" yscaled 1/3 rotated 30;\cr +3&@transform@ $t$;\cr +4&$t="identity"$ ^{rotatedaround}$\bigl((.5w,.5h),-90\bigr)$;\cr +5&$x_2=.35w$; \ $x_3=.6w$;\cr +6&$y_2=.1h$; \ $"top"\,y_3=.4h$;\cr +7&@path@ $p$; \ $p=z_2\{"right"\}\ldots\{"up"\}z_3$;\cr +8&$"top"\,z_1$ $=$ point .5 of $p$ transformed $t$;\cr +9&@draw@ $z_1\ldots p$;\cr +10&@addto@ "currentpicture" @also@ "currentpicture" transformed $t$;\cr +11&@addto@ "currentpicture" @also@ "currentpicture" + transformed ($t$ transformed $t$);\cr +12&@labels@$(1,2,3)$; \ @endchar@;\cr +}$$ +^^@addto@ +Lines 3 and 4 compute the transform that moves each +`{\manual\oneu\kern1pt}' to its clockwise neighbor. Lines 5--7 compute the +right half of the `{\manual\oneu\kern1pt}'. Line~8 is the most +interesting: It puts point $z_1$ on the rotated path. Line~9 draws the +`{\manual\oneu\kern1pt}', line~10 changes it into two, and line~11 changes +two into four. The parentheses on line~11 could have been omitted, but it +is much faster to transform a transform than to transform a picture. + +\endgroup + +\ddanger \MF\ will transform a ^{picture} expression only when $t_{xx}$, +$t_{xy}$, $t_{yx}$, and~$t_{yy}$ are integers and either $t_{xy}=t_{yx}=0$ +or $t_{xx}=t_{yy}=0$; furthermore, the values of $t_x$ and~$t_y$ are +rounded to the nearest integers. Otherwise the transformation would not +take pixel boundaries into pixel boundaries. + +\ddangerexercise Explain how to rotate the ornament by $45^\circ$. +\qquad\xleaders\hbox{\kern1pt$\vcenter{\hbox{\manual\fourc}}$}\hfill\ +\answer Change line 9 to +\begindisplay +@draw@ $(z_1\ldots p)$ rotatedaround$\bigl((.5w,.5h),-45\bigr)$\cr +\quad @withpen@ @pencircle@ scaled 3/4"pt" yscaled 1/3 rotated $-15$;\cr +\enddisplay + +Plain \MF\ maintains a special variable called ^"currenttransform", +behind the scenes. Every ^@fill@ and ^@draw@ command is affected by this +variable; for example, the statement `@fill@~$p$' actually fills the +interior of the path +\begindisplay +$p$ transformed "currenttransform" +\enddisplay +instead of $p$ itself. We haven't mentioned this before, because +"currenttransform" is usually equal to "identity"; but nonstandard +settings of "currenttransform" can be used for special effects that +are occasionally desired. For example, it's possible to change +`\MF\kern1pt' to `{\manual 89:;<=>:}\kern3pt' by simply saying +\begindisplay +$"currenttransform":="identity"$ slanted 1/4 +\enddisplay +and executing the programs of |logo.mf| that are described in Chapter~11; +no other changes to those programs are necessary. + +It's worth noting that the pen nib used to draw `{\manual 89:;<=>:}\kern3pt' +was not slanted when "currenttransform" was changed; only the ``tracks'' of +the pen, the paths in @draw@ commands, were modified. Thus the slanted image +was not simply obtained by slanting the unslanted image. + +\ddanger When fonts are being made for devices with ^{nonsquare pixels}, +plain \MF\ will set "currenttransform" to `"identity" yscaled +^"aspect\_ratio"', and ^@pickup@ will similarly yscale the pen nibs +that are used for drawing. In this case the slanted +`{\manual 89:;<=>:}\kern3pt' letters should be drawn with +\begindisplay +$"currenttransform":="identity"$ slanted 1/4 yscaled "aspect\_ratio". +\enddisplay + +\ddangerexercise Our program for +`\kern1pt\lower2.5pt\hbox{\manual\fouru}\kern1pt' doesn't work when pixels +aren't square. Fix it so that it handles a general "aspect\_ratio". +\answer Replace line 10 by +\begindisplay +@pickup@ @pencircle@ scaled 3/4"pt" yscaled 1/3 rotated $-60$;\cr +@draw@ ($z_1\ldots p$) transformed $t$;\cr +\enddisplay + +\endchapter + +Change begets change. Nothing propagates so fast. +\author CHARLES ^{DICKENS}, {\sl Martin Chuzzlewit\/} (1843) + % opening lines of chapter 18 + +\bigskip + +There are some that never know how to change. +\author MARK ^{TWAIN}, {\sl Joan of Arc\/} (1896) + % book 2, chapter 26, second page + +\eject + \beginChapter Chapter 16. Calligraphic\\Effects + +^{Pens} were introduced in Chapter 4, and we ought to make a systematic study +of what \MF\ can do with them before we spill any more ink. The purpose +of this chapter will be to explore the uses of ``fixed'' pen nibs---i.e., +variables and expressions of type ^@pen@---rather than to consider +the creation of shapes by means of outlines or penstrokes. + +When you say `^@pickup@ ^\', the macros of plain \MF\ do +several things for you: They create a representation of the specified +pen~nib, and assign it to a pen variable called ^"currentpen"; then they +store away information about the top, bottom, left, and right extents of +that pen, for use in ^"top", ^"bot", ^"lft", and ^"rt" operations. +A ^@draw@ or ^@drawdot@ or ^@filldraw@ command will make use of +"currentpen" to modify the current picture. + +You can also say `@pickup@ \'; in this case the numeric +expression designates the code number of a previously picked-up pen +that was saved by `^"savepen"'. For example, the |logo.mf| file in Chapter~11 +begins by picking up the pen that's used to draw `\MF\kern1pt', then +it says `$"logo\_pen":="savepen"$'. Every character program later in that +file begins with the command `@pickup@ "logo\_pen"', which is a fast +operation because it doesn't require the generation of a new +pen representation inside the computer. + +\danger Caution: Every time you use "savepen", it produces a new integer +value and stashes away another pen for later use. If you keep doing this, +\MF's memory will become cluttered with the representations of pens +that you may never need again. The command `^@clear\_pen\_memory@' +discards all previously saved pens and lets \MF\ start afresh. + +\danger But what is a \? Good question. So far in this book, +almost everything that we've picked up was a pencircle followed by +some sequence of transformations; for example, the "logo\_pen" of +Chapter~11 was `@pencircle@ xscaled~"px" yscaled~"py"'. Chapter~13 +also made brief mention of another kind of pen, when it said +\begindisplay +@pickup@ ^@penrazor@ scaled 10; +\enddisplay +this command picks up an infinitely thin pen that runs from point +$(-5,0)$ to point $(5,0)$ with respect to its center. Later in this +chapter we shall make use of pens like +\begindisplay +^@pensquare@ xscaled 30 yscaled 3 rotated 30; +\enddisplay +this pen has a rectangular boundary measuring 30 pixels $\times$ 3 pixels, +inclined at an angle of $30^\circ$ to the baseline. + +\danger You can define pens of any ^{convex polygon}al shape by saying +`^@makepen@~$p$', where $p$ is a cyclic path. It turns out that \MF\ +looks only at the key points of~$p$, not the control points, so we may +as well assume that $p$ has the form $z_0\dashto z_1\dashto\\dashto +\cycle$. This path must have the property that it turns left at every +key point (i.e., $z_{k+1}$ must lie to the left of the line from $z_{k-1}$ +to~$z_k$, for all~$k$), unless the cycle contains fewer than three key +points; furthermore the path must have a ^{turning number} of~1 (i.e., +it must not make more than one counterclockwise loop). Plain \MF's +@penrazor@ stands for +`@makepen@ $\bigl((-.5,0)\dashto(.5,0)\dashto \cycle\bigr)$', +and @pensquare@ is an abbreviation for +`@makepen@ $\bigl("unitsquare"$ shifted $-(.5,.5)\bigr)$'. +But @pencircle@ is not defined via @makepen@; it is a +primitive operation of \MF. It represents a true ^{circle} of diameter~1, +% no need for `\MF\!.' here +passing through the points $(\pm.5,0)$ and $(0,\pm.5)$. + +\danger The complete syntax for pen expressions is rather short, because +you can't really do all that much with pens. But it also contains a +surprise: +\beginsyntax +\is + \alt[(][)] + \alt[nullpen] +\is[pencircle] + \alt[makepen] +\is +\is + \alt + \alt +\is + \alt +\is +\endsyntax +The constant `^@nullpen@' is just the single point $(0,0)$, which is +invisible---unless you use it in ^@filldraw@, which then reduces to +^@fill@. \ (A ^@beginchar@ command initializes "currentpen" to @nullpen@, +in order to reduce potentially dangerous dependencies between the programs +for different characters.) \ +The surprise in these rules is the notion of a ``^{future pen},'' +which stands for a path or an ellipse that has not yet been converted +into \MF's internal representation of a true pen. The conversion process +is rather complicated, so \MF\ procrastinates until being sure that no +more transformations are going to be made. A true pen is formed at the +tertiary level, when future pens are no longer permitted in the syntax. + +\danger The distinction between pens and future pens would make no +difference to a user, except for another surprising fact: All of \MF's +pens are convex polygons, even the pens that are made from @pencircle@ +and its variants! Thus, for example, the pen you get from an +untransformed pencircle is identical to the pen you get by specifying +the ^{diamond-shaped nib} +\begindisplay +@makepen@$\,\bigl((.5,0)\dashto(0,.5)\dashto(-.5,0)\dashto + (0,-.5)\dashto\cycle\bigr)$. +\enddisplay +And the pens you get from `@pencircle@ scaled 20' and `@pencircle@ +xscaled~30 yscaled~20' are polygons with 32 and 40 sides, respectively: +\displayfig 16a\&b (220\apspix) +The vertices of the polygons, shown as heavy dots in this illustration, +all have ``half-integer'' coordinates; i.e., each coordinate is either +an integer or an integer plus 1/2. Every polygon that comes from a +@pencircle@ is symmetric under $180^\circ$ rotation; furthermore, +there will be reflective left/right and top/bottom symmetry if the future +pen is a circle, or if it's an ellipse that has not been rotated. + +\danger This conversion to polygons explains why future pens must, in +general, be distinguished from ordinary ones. For example, the extra +parentheses in `(@pencircle@ xscaled~30) yscaled~20' will yield +a result quite different from the elliptical polygon just illustrated. +The parentheses force conversion of `@pencircle@ xscaled~30' from +future pen to pen, and this polygon turns out to be +\begindisplay +$(12.5,-0.5) \dashto (15,0) \dashto (12.5,0.5)$\cr +\qquad$\dashto (-12.5,0.5) \dashto +(-15,0) \dashto (-12.5,-0.5) \dashto\cycle$,\cr +\enddisplay +an approximation to a $30\times1$ ellipse. Then yscaling by 20 yields +\displayfig 16c (220\apspix) + +\danger Why does \MF\ work with polygonal approximations to circles, +instead of true circles? That's another good question. The main reason is +that suitably chosen polygons give better results than the real thing, +when ^{digitization} is taken into account. For example, suppose we want +to draw a straight line of slope 1/2 that's exactly one pixel thick, from +$(0,y)$ to $(200,y+100)$. The image of a perfectly circular pen of +diameter~1 that travels along this line has outlines that run from +$(0,y\pm\alpha)$ to $(200,y+100\pm\alpha)$, where +$\alpha=\sqrt5/4\approx0.559$. If we digitize these outlines and fill the +region between them, we find that for some values of~$y$ (e.g., $y=0.1$) +the result is a repeating pixel pattern like +`\smash{\hbox{$\vcenter{\offinterlineskip +\setbox4=\hbox{\manual R} +\hbox{\hphantom{$\,\ldots\,$}\kern5\wd4\copy4$\,\ldots\,$} +\hbox{\hphantom{$\,\ldots\,$}\kern3\wd4\copy4\copy4} +\hbox{\hphantom{$\,\ldots\,$}\kern\wd4\copy4\copy4} +\hbox{\smash{$\,\ldots\,$}\copy4}}$}}'; but for other values of~$y$ (e.g., +$y=0.3$) the repeating pattern of pixels is \vbox to11pt{}50 percent darker: +`\smash{\raise2pt\hbox{$\vcenter{\offinterlineskip +\setbox4=\hbox{\manual R} +\hbox{\hphantom{$\,\ldots\,$}\kern4\wd4\copy4\copy4$\,\ldots\,$} +\hbox{\hphantom{$\,\ldots\,$}\kern2\wd4\copy4\copy4\copy4} +\hbox{\hphantom{$\,\ldots\,$}\copy4\copy4\copy4} +\hbox{\smash{$\,\ldots\,$}\copy4}}$}}'. Similarly, some diagonal +lines of slope~1 digitize to be twice as dark as others, when a truly +circular pen is considered. But the diamond-shaped nib that \MF\ uses +for a pencircle of diameter~1 does not have this defect; all straight +lines of the same slope will digitize to lines of uniform darkness. +Moreover, curved lines drawn with the diamond nib always yield one pixel per +column when they move more-or-less horizontally (with slopes between $+1$ +and $-1$), and they always yield one pixel per row when they move vertically. +By contrast, the outlines of curves drawn with circular pens produce +occasional ``blots.'' Circles and ellipses of all diameters can profitably +be replaced by polygons whose sub-pixel corrections to the ideal shape +will produce better digitizations; \MF\ does this in accordance with the +interesting theory developed by John~D. ^{Hobby} in his Ph.D. +dissertation (Stanford University, 1985). + +\ddanger It's much easier to compute the outlines of a polygonal pen that +follows a given curve than to figure out the corresponding outlines of +a truly circular pen; thus polygons win over circles with respect +to both quality and speed. When a curve is traveling in a +direction between the edge vectors $z_{k+1}-z_k$ and~$z_k-z_{k-1}$ of +a polygonal pen, the curve's outline will be offset from its center +by~$z_k$. If you want fine control over this curve-drawing process, +\MF\ provides the primitive operation `^{penoffset}~$w$ of~$p$', where +$w$~is a vector and $p$~is a pen. If $w=(0,0)$, the result is $(0,0)$; +if the direction of~$w$ lies strictly between $z_{k+1}-z_k$ and $z_k +-z_{k-1}$, the result is~$z_k$; and if $w$ has the same direction as +$z_{k+1}-z_k$ for some~$k$, the result is either $z_k$ or~$z_{k+1}$, +whichever \MF\ finds most convenient to compute. + +\ddangerexercise Explain how to use penoffset to find the point or +points at the ``top'' of a pen (i.e., the point or points with largest +$y$~coordinate). +\answer If there are two points $z_k$ and $z_{k+1}$ with maximum +$y$~coordinate, the value of `penoffset $(-"infinity","epsilon")$ of~$p$' +will be~$z_k$ and `penoffset $(-"infinity",-"epsilon")$ of~$p$' will +be~$z_{k+1}$; `penoffset~"left" of~$p$' will be one or the other. If +there's only one top point, all three of these formulas will produce it. +\ (Actually \MF\ also allows pens to be made with three or more +vertices in a straight line. If there are more than two top vertices, +you can use penoffset to discover the first and the last, as above; +furthermore, if you really want to find them all, ^@makepath@ will produce +a path from which they can be deduced in a straightforward manner.) + +\ddanger The primitive operation `^@makepath@ $p$', where $p$ is +a (polygonal) pen whose vertices are $z_0$, $z_1$, \dots,~$z_{n-1}$, +produces the path `$z_0\to\controls z_0\and z_1\to z_1\to\\to +z_{n-1}\to\controls z_{n-1}\and z_0\to\cycle$', which is one of the +paths that might have generated~$p$. This gives access to all the +offsets of a pen. + +\ddanger When a @pencircle@ is transformed by any of the operations +in Chapter~15, it changes into an ellipse of some sort, since all of +\MF's transformations preserve ellipse-hood. The diameter of the +ellipse in each direction~$\theta$ is decreased by $2\min\bigl( +\vert\sin\theta\vert,\vert\cos\theta\vert\bigr)$ times the current +value of~^"fillin", before converting to a polygon; this helps to +compensate for the variation in thickness of diagonal strokes with +respect to horizontal or vertical strokes, on certain output devices. +\ (\MF\ uses "fillin" only when creating polygons from ellipses, +but users can of course refer to "fillin" within their own routines +for drawing strokes.) \ The final polygon will never be perfectly flat +like ^@penrazor@, even if you say `xscaled~0' and/or `yscaled~0'; +its center will always be surrounded at least by the basic diamond nib +that corresponds to a circle of diameter~1. + +\dangerexercise Run \MF\ on the |expr| file of Chapter~8 and look at +what is typed when you ask for `|pencircle|' and `|pencircle| +|scaled|~|1.1|'. \ (The first will exhibit the diamond nib, while +the second will show a polygon that's equivalent to @pensquare@.) \ +Continue experimenting until you find the ``threshold'' diameter where +\MF\ decides to switch between these two polygons. +\answer `@pencircle@ scaled 1.06060' is the diamond but +`@pencircle@ scaled 1.06061' is~the square. \ (This assumes that +^"fillin"$\null=0$. If, for example, $"fillin"=.1$, the change doesn't +occur until the diameter is 1.20204.) \ The next change is at diameter +1.5, which gives a diamond twice the size of the first. + +\danger \MF's polygonal pens work well for drawing lines and curves, +but this pleasant fact has an unpleasant corollary: They do not always +digitize well at the ^{endpoints}, where curves start and stop. The +reason for this is explored further in Chapter~24; polygon vertices that +give nice uniform stroke widths might also be ``ambiguous'' points that +cause difficulties when we consider rounding to the raster. Therefore a +special ^@drawdot@ routine is provided for drawing one-point paths. +It is sometimes advantageous to apply @drawdot@ to the first and last +points of a path~$p$, after having said `^@draw@~$p$'; this can +fatten up the endpoints slightly, making them look more consistent with +each other. + +\danger Plain \MF\ also provides two routines that can be used to clean~up +endpoints in a different way: The command `^@cutoff@$\,(z,\theta)$' +removes half of the ^"currentpen" image at point~$z$, namely all points +of the pen that lie in directions between $(\theta-90)^\circ$ and +$(\theta+90)^\circ$ from the center point. And the command `^@cutdraw@~$p$' +is an abbreviation for the following three commands: +\begindisplay +@draw@ $p$; \ @cutoff@\thinspace(point 0 of $p$, $180+\null$angle +direction 0 of $p$);\cr +@cutoff@\thinspace(point "infinity" of $p$, angle +direction "infinity" of $p$).\cr +\enddisplay +The effect is to draw a curve whose ends are clipped perpendicular to the +starting and ending directions. For example, the command +\begindisplay +@cutdraw@ $z_4\to\controls z_1\and z_2\to z_6$ +\enddisplay +produces the following curve, which invites comparison with the corresponding +uncut version at the end of Chapter~3: +\displayfig 16d (5pc) + +\decreasehsize 48mm +\danger Here's another example of @cutoff@, in which the endpoints of +\rightfig 16e ({208\apspix} x {216\apspix}) ^15pt +\MF's~`^{T}' have been cropped at $10^\circ$ angles to the +perpendicular of the stroke direction: +\begintt +pickup logo_pen; +top lft z1=(0,h); top rt z2=(w,h); +top z3=(.5w,h); z4=(.5w,0); +draw z1--z2; +cutoff(z1,170); cutoff(z2,-10); +draw z3--z4; cutoff(z4,-80). +\endtt + +\restorehsize +\ddanger The @cutoff@ macro of Appendix~B deals with several things +that we've been studying recently, so it will be instructive to look +at it now (slightly simplified): +\begindisplay +@def@ @cutoff@\thinspace(@expr@ $z,"theta"$) $=$\cr +\quad$"cut\_pic":=@nullpicture@$;\cr +\quad^@addto@ "cut\_pic" @doublepath@ $z$ @withpen@ "currentpen";\cr +\quad@addto@ "cut\_pic" @contour@ + $((0,-1)\dashto(1,-1)\dashto(1,1)\dashto(0,1)\dashto\cycle)$\cr +\qquad scaled $1.42(1+\max(-"pen\_lft","pen\_rt","pen\_top",-"pen\_bot"))$\cr +\qquad rotated "theta" shifted "z";\cr +\quad^@cull@ "cut\_pic" @keeping@ $(2,2)$ @withweight@ $-1$;\cr +\quad@addto@ "currentpicture" @also@ "cut\_pic" @enddef@.\cr +\enddisplay +The main work is done in a separate ^{picture} variable called "cut\_pic", +so that neighboring strokes won't be affected. First "cut\_pic" is set to +the full digitized pen image (by making a ^@doublepath@ from a single +point). Then a rectangle that includes the cutoff region is added in; +^"pen\_lft", "pen\_rt", "pen\_top", and "pen\_bot" are the quantities used +to compute the functions ^"lft", ^"rt", ^"top", and ^"bot", so they bound +the size of the pen. The culling operation produces the intersection of +pen and rectangle, which is finally subtracted from "currentpicture". + +\ddanger We shall conclude this chapter by studying two examples of how +\MF's pen-and-curve-drawing facilities can combine in interesting ways. +First, let's examine two ``^{tilde}'' characters +\displayfig 16f\&g (50\apspix) +which were both created by a single command of the form +\begindisplay +@draw@ $z_1\to\controls z_2\and z_3\to z_4$. +\enddisplay +The left example was done with a ^@pencircle@ xscaled .8"pt" yscaled .2"pt" +rotated~50, and the right example was exactly the same but with ^@pensquare@. +The control points $z_2$ and~$z_3$ that made this work were defined by +\begindisplay +$y_2-y_1=y_4-y_3=3(y_4-y_1)$;\cr +$z_2-z_1=z_4-z_3="whatever"\ast{\rm dir}\,50$.\cr % partly redundant +\enddisplay +The second pair of equations is an old calligrapher's trick, namely to start +and finish a~stroke in the direction of the pen you're holding. +The first pair of equations is a mathematician's trick, based on the +fact that the ^{Bernshte{\u\i}n polynomial} $t[0,3,-2,1]$ goes from +0~to~1 to~0~to~1 as $t$ goes from 0 to~.25 to~.75~to~1. + +\ddanger Next, let's try to draw a fancy ^{serif} with +the same two pens, holding them at a $20^\circ$~angle instead of a +$50^\circ$~angle. Here are two examples +\displayfig 16h\&i (195\apspix) +that can be created by `^@filldraw@' commands: +\begindisplay +@filldraw@ $z_1\to\controls z_2\to z_3$\cr +\qquad$\dashto("flex"(z_3,.5[z_3,z_4]+"dishing",z_4))$ + shifted$\,(0,-"epsilon")$\cr +\qquad$\dashto z_4\to\controls z_5\to z_6\dashto\cycle$.\cr +\enddisplay +The ^"dishing" parameter causes a slight rise between $z_3$ and~$z_4$; +the ^"flex" has been lowered by ^"epsilon" in order to avoid the danger +of ``^{strange paths},'' which might otherwise be caused by tiny loops +at $z_3$ or~$z_4$. But the most interesting thing about this example +is the use of double control points, $z_2$ and~$z_5$, in two of the +path segments. \ (Recall that `$\controls z_2$' means the same thing +^^{controls} as `$\controls z_2\and z_2$'.) \ These points were determined +by the equations +\begindisplay +$x_2=x_1$; \ $z_2=z_3+"whatever"\ast{\rm dir}\,20$;\cr +$x_5=x_6$; \ $z_5=z_4+"whatever"\ast{\rm dir}\,{-20}$;\cr +\enddisplay +thus, they make the strokes vertical at $z_1$ and $z_6$, parallel to the +pen angle at~$z_3$, and parallel to the complementary angle at~$z_4$. + + +\endchapter + +The pen, probably more than any other tool, +has had the strongest influence upon lettering +in respect of serif design .\thinspace.\thinspace. +It is probable that the letters [of the Trajan column] +were painted before they were incised, +and though their main structure is attributed to the pen +and their ultimate design to the technique of the chisel, +they undoubtedly owe much of their freedom +to the influence of the brush. +\author L. C. ^{EVETTS}, {\sl Roman Lettering\/} (1938) % pp 3 and 13 + +\bigskip + +Remember that it takes time, patience, critical practice +and knowledge to learn any art or craft. +No ``art experience'' is going to result from any busy work +for a few hours experimenting with the edged pen. +.\thinspace.\thinspace. Take as much time as you require, +and do not become impatient. +If it takes a month to get it, +then be happy that it takes only a month. +\author LLOYD ^{REYNOLDS}, {\sl Italic Calligraphy \& Handwriting\/} (1969) + +\eject + \beginchapter Chapter 17. Grouping + +We have now covered all the visual, graphic aspects of \MF---its +points, paths, pens, and pictures; but we still don't know everything +about \MF's organizational, administrative aspects---its programs. +The next few chapters of this book therefore concentrate on +how to put programs together effectively. + +A \MF\ program is a sequence of statements separated by semicolons and +followed by `^@end@'. More precisely, the syntax rules +\beginsyntax +\is[end] +\is\alt[;] +\endsyntax +define a \ in terms of a \. + +But what are ^{statements}? Well, they are of various kinds. An ``equation'' +states that two expressions are supposed to be equal. An ``assignment'' +assigns the value of an expression to a variable. A ``declaration'' +states that certain variables will have a certain type. +A ``definition'' defines a macro. A ``title'' gives a descriptive name to +the character that is to follow. A ``command'' orders \MF\ to do some +specific operation, immediately. The ``^{empty statement}'' tells \MF\ to +do absolutely nothing. And a ``^{compound statement}'' is a list of other +statements treated as a ^{group}. +\beginsyntax +\is\alt\alt + \alt\alt\alt<command>\alt<empty> + \alt[begingroup] <statement list> <statement> [endgroup] +\endsyntax +We've given the syntax for \<equation> and \<assignment> in Chapter~10; +the syntax for \<declaration> appeared in Chapter~7; \<definition> and +\<title> and \<command> will appear in later chapters. Our main concern +just now is with the final type of \<statement>, where @begingroup@ +and @endgroup@ bind other statements into a unit, just as parentheses +add structure to the elements of an algebraic expression. + +The main purpose of grouping is to protect the values of variables in +one part of the program from being clobbered in another. A symbolic token +can be given a new meaning inside a group, without changing the +meaning it had outside that group. \ (Recall that \MF\ deals with +three basic kinds of tokens, as discussed in Chapter~6; it is impossible +to change the meaning of a numeric token or a string token, but +symbolic tokens can change meanings~freely.) + +There are two ways to protect the values of variables in a group. One +is called a \<save command>, and the other is called an \<interim command>: +\beginsyntax +<save command>\is[save]<symbolic token list> +<symbolic token list>\is<symbolic token> + \alt<symbolic token list>[,]<symbolic token> +<interim command>\is\kern-1.5pt[interim]% + <internal quantity>[:=]<right-hand side>\kern-1pt +\endsyntax +The symbolic tokens in a @save@ command all lose their current meanings, but +those old meanings are put into a safe place and restored at the end of +the current group. Each token becomes undefined, as if it had never +appeared before. For example, the command +\begindisplay +@save@ $x,y$ +\enddisplay +effectively causes all previously known variables like $x_1$ and $y_{5r}$ to +become inaccessible; the variable $x_1$ could now appear in a new equation, +where it would have no connection with its out-of-group value. You could +also give the silly command +\begindisplay +@save@ @save@; +\enddisplay +this would make the token `|save|' itself into a ^\<tag> instead of a +^\<spark>, so you couldn't use it to save anything else until the group ended. + +\danger An @interim@ command is more restrictive than a @save@, since it +applies only to an ^\<internal quantity>. \ (Recall that internal +quantities are special variables like "tracingequations" that take numeric +values only; a complete list of all the standard internal quantities can +be found in Chapter~25, but that list isn't exhaustive because you can +define new ones for your own use.) \ \MF\ treats an interim command just +like an ordinary assignment, except that it undoes the assignment when the +group~ends. + +\danger If you save something two or more times in the same group, +the first saved value takes precedence. For example, in the construction +\begindisplay +@begingroup@\cr +\noalign{\vskip-3pt}\dots\cr +@interim@ $"autorounding":=0$; \ @save@ $x$;\cr +\noalign{\vskip-3pt}\dots\cr +@interim@ $"autorounding":=1$; \ @save@ $x$;\cr +\noalign{\vskip-3pt}\dots\cr +@endgroup@\cr +\enddisplay +the values of "autorounding" and $x$ after the end of the group will be +their previous values just before the statement `@interim@ $"autorounding":=0$'. +(Incidentally, these might not be the values they had upon entry to the group.) + +\danger Tokens and internal quantities regain their old meanings and +values at the end of a group only if they were explicitly saved in a +@save@ or @interim@ command. All other changes in meaning and/or value +will survive outside the group. + +\danger The ^@beginchar@ operation of plain \MF\ includes a @begingroup@, +and ^@endchar@ includes @endgroup@. Thus, for example, interim assignments +can be made in a program for one character without any effect on other +characters. + +\danger A \<save command> that's not in a group simply clears the meanings +of the symbolic tokens specified; their old meanings are not actually saved, +because they never will have to be restored. An \<interim command> +outside a group acts just like a normal assignment. + +\danger If you set the internal quantity ^"tracingrestores" to a positive +value, \MF\ will make a note in your transcript file whenever it is +restoring the former value of a symbolic token or internal quantity. +This can be useful when you're debugging a program that doesn't seem +to make sense. + +Groups can also be used within algebraic expressions. This is +the other important reason for grouping; it allows \MF\ to do arbitrarily +complicated things while in the middle of other calculations, thereby +greatly increasing the power of macro definitions (which we shall study +in the next chapter). A {\sl^{group expression}\/} has the general form +\begindisplay +{\tt begingroup}\thinspace\<statement list>\thinspace\<expression> +\thinspace{\tt endgroup} +\enddisplay +and it fits into the syntax of expressions at the primary level. The +meaning of a group expression is: ``Perform the list of statements, +then evaluate the expression, then restore anything that was saved +in this group.'' + +\danger Group expressions belong in the syntax rules for each type +of expression, but they were not mentioned in previous chapters because +it would have been unnecessarily distracting. Thus, for example, the syntax for +\<numeric primary> actually includes the additional alternative +\begindisplay +|begingroup|\thinspace\<statement list>\<numeric expression>% + \thinspace|endgroup|. +\enddisplay +The same goes for \<pair primary>, \<picture primary>, etc.; Chapter~25 +has the complete rules of syntax for all types of expressions. + +\dangerexercise What is the value of the expression +\begintt +begingroup x:=x+1; x endgroup + begingroup x:=2x; x endgroup +\endtt +if $x$ initially has the value $a$? What would the value have been if +the two group expressions had appeared in the opposite order? +Verify your answers using the |expr| routine of Chapter~8. +\answer $(a+1)+(2a+2)=3a+3$ and $(2a)+(2a+1)=4a+1$, respectively. +The final value of~$x$ in the first case is $2a+2$, hence $a=.5x-1$; +|expr| will report the answer as |1.5x| (in terms of $x$'s new value), +since it has not been told about `$a$'. In the second case |expr| will, +similarly, say |2x-1|.\par +This example shows that $\alpha+\beta$ is not necessarily equal +to ^^{commutativity} $\beta+\alpha$, when $\alpha$ and~$\beta$ involve +group expressions. \MF\ evaluates expressions strictly from left to +right, performing the statements within groups as they appear. + +\dangerexercise Appendix B defines ^"whatever" to be an abbreviation for +the group expression `@begingroup@ @save@ ?; ? @endgroup@'. Why +does this work? \checkequals\Xwhat\exno +\answer The save instruction gives `?' a fresh meaning, hence `?' is +a numeric variable unconnected to any other variables. When the group +ends and `?' is restored to its old meaning, the value of the group +expression no longer has a name. \ (It's called a ``^{capsule}'' if +you try to @show@ it.) \ Therefore the value of the group expression +is a new, nameless variable, as desired. + +\ddangerexercise What is the value of `@begingroup@ @save@ ?; \ +$(?,?)$ @endgroup@'\thinspace? +\answer It's a nameless pair whose xpart and ypart are equal; thus it +is essentially equivalent to `$"whatever"\ast(1,1)$'. + +\ddangerexercise According to exercise 10.\xwhat, the assignment +`$x_3:="whatever"$' will make the numeric variable $x_3$ behave like new, +without affecting other variables like $x_2$. Devise a similar stratagem +that works for arrays of @picture@ variables. +\answer `$v_3:=@begingroup@$ @save@ ?; @picture@ ?; ?\ @endgroup@' +refreshes the picture variable~$v_3$ without changing other variables +like~$v_2$. This construction works also for pairs, pens, strings, etc. + +\endchapter + +It is often difficult +to account for some beginners grouping right away +and others proving almost hopeless. +\author A. G. ^{FULTON}, {\sl Notes on Rifle Shooting\/} (1913) + % according to OED Supplement, but this pamphlet has vanished from their files! + +\bigskip + +Rock bands prefer San Francisco groupies to New York groupies. +\author ELLEN ^{WILLIS}, {\sl But Now I'm Gonna Move\/} (1971) + % New Yorker, 23 Oct 71, p170 + +\eject + \beginchapter Chapter 18. Definitions\\(also called Macros) + +You can often save time writing \MF\ programs by letting single tokens +stand for sequences of other tokens that are used repeatedly. For example, +Appendix~B defines `$\ddashto$' to be an abbreviation for ^^{---} +`$\to\tension"infinity"\to$', and this definition is preloaded as +part of the plain \MF\ base. Programs that use such definitions are not +only easier to write, they're also easier to read. But Appendix~B +doesn't contain every definition that every programmer might want; +the present chapter therefore explains how you can make ^{definitions} +of your own. + +In the simplest case, you just say +\begindisplay +@def@ \<symbolic token> $=$ \<replacement text> @enddef@ +\enddisplay +and the symbolic token will henceforth expand into the tokens of the +replacement text. For example, Appendix~B says +\begintt +def --- = ..tension infinity.. enddef; +\endtt +it makes `$z_1\ddashto z_2$' become `$z_1\to\tension"infinity"\to z_2$'. +The ^{replacement text} can be any sequence of tokens not including +`@enddef@\kern1pt'; or it can include entire subdefinitions like +`@def@~$\ldots$~@enddef@\kern1pt', according to certain rules +that we shall explain later. + +Definitions get more interesting when they include {\sl^{parameters}}, +which are replaced by {\sl^{arguments}\/} when the definition is expanded. +For example, Appendix~B also says +\begintt +def rotatedaround(expr z,theta) = + shifted -z rotated theta shifted z enddef; +\endtt +this means that an expression like `$z_1$ ^{rotatedaround}$\,(z_2,30)$' will +expand into `$z_1$ shifted~$-z_2$ rotated~30 shifted~$z_2$'. + +The parameters `|z|' and `|theta|' in this definition could have been any +symbolic tokens whatever; there's no connection between them and +appearances of `|z|' and `|theta|' outside the definition. \ (For example, +`|z|'~would ordinarily stand for `|(x,y)|', but it's just a simple token +here.) \ The definition could even have been written with ``primitive'' +tokens as parameters, like +\begintt +def rotatedaround(expr;,+) = + shifted-; rotated+shifted; enddef; +\endtt +the effect would be exactly the same. \ (Of course, there's no point in +doing such a thing unless you are purposely trying to make your +definition inscrutable.) + +When `|rotatedaround|' is used, the arguments that are substituted for |z| +and |theta| are first evaluated and put into ``^{capsules},'' so that they +will behave like primary expressions. Thus, for example, `$z_1$ +rotatedaround$\,(z_2+z_3,30)$' will not expand into `$z_1$ shifted~$-z_2+z_3$ +rotated~30 shifted~$z_2+z_3$'---which means something entirely different---but +rather into `$z_1$ shifted~$-\alpha$ rotated~30 shifted~$\alpha$', where +$\alpha$ is a nameless internal variable that contains the value of +$z_2+z_3$. + +\danger A capsule value cannot be changed, so an @expr@ parameter should not +^^{:=} appear at the left of the ^{assignment} operator `$:=$'. + +\danger Macros are great when they work, but complicated macros sometimes +surprise their creators. \MF\ provides ``tracing'' facilities so that you +can see what the computer thinks it's doing, when you're trying to +diagnose the reasons for unexpected behavior. If you say +`^"tracingmacros"$\null:=1$', the transcript file of your run will record +every macro that is subsequently expanded, followed by the values of its +arguments as soon as they have been computed. +For example, `rotatedaround$\,("up",30)$' might produce the +^^|EXPR0| following lines of diagnostic information: +\begintt +rotatedaround(EXPR0)(EXPR1)->shifted-(EXPR0)rotated(EXPR1)sh +ifted(EXPR0) +(EXPR0)<-(0,1) +(EXPR1)<-30 +\endtt + +\danger Here's another example from Appendix B\null. It illustrates the +usefulness of ^{group expressions} in macro definitions: +\begindisplay +@def@ ^{reflectedabout}$\,(@expr@\ p,q)$ $=$\cr +\quad transformed @begingroup@\cr +\qquad ^@save@ $T$; \ ^@transform@ $T$;\cr +\qquad $p$ transformed $T$ $=$ $p$;\cr +\qquad $q$ transformed $T$ $=$ $q$;\cr +\qquad ^{xxpart} $T$ $=$ $-$^{yypart} $T$;\cr +\qquad ^{xypart} $T$ $=$ ^{yxpart} $T$;\cr +\qquad $T$ @endgroup@ @enddef@;\cr +\enddisplay +thus a new transform, $T$, is computed in the midst of another expression, +and the macro `reflectedabout($p,q$)' essentially expands into +`transformed $T$'. + +Some macros, like `rotatedaround', are meant for general-purpose use. +But it's also convenient to write ^{special-purpose macros} that simplify +the development of particular typefaces. For example, let's consider the +\MF\ logo from this standpoint. The program for `{\manual E}' in +Chapter~11 starts with +\begintt +beginchar("E",14u#+2s#,ht#,0); pickup logo_pen; +\endtt +and the programs for `{\manual M}', `\kern1pt{\manual T}\kern1pt', +etc., all have almost the same beginning. Therefore we might as +well put the following definition near the top of the file |logo.mf|: +\begintt +def beginlogochar(expr code, unit_width) = + beginchar(code,unit_width*u#+2s#,ht#,0); + pickup logo_pen enddef; +\endtt +Then we can start the `{\manual E}' by saying simply ^^|beginlogochar| +\begintt +beginlogochar("E",14); +\endtt +similar simplifications apply to all seven letters. Notice from +this example that macros can be used inside macros (since `|beginchar|' +and `|pickup|' are themselves macros, defined in Appendix~B\null); once you +have defined a macro, you have essentially extended the \MF\ language. +Notice also that ^@expr@ parameters can be expressions of any type; +for example, |"E"| is a string, and the first parameter of +`rotatedaround' is a pair. + +\decreasehsize 48mm +Chapter 11 didn't give the programs for `{\manual A}' or `{\manual O}'. +\rightfig 18a ({240\apspix} x {216\apspix}) ^15pt +It turns out that those programs can be simplified if we write +them in terms of an auxiliary subroutine called `|super_half|'. +For example, here is how the `{\manual O}' is made: +\begintt +beginlogochar("O",15); +x1=x4=.5w; top y1=h+o; bot y4=-o; +x2=w-x3=1.5u+s; y2=y3=barheight; +super_half(2,1,3); +super_half(2,4,3); +labels(1,2,3,4); endchar; +\endtt + +\restorehsize\medbreak\noindent +The |super_half| routine is supposed to draw half of a ^{superellipse}, +through three points whose subscripts are specified. + +\restorehsize +We could define |super_half| as a macro with three @expr@ parameters, +referring to the first point as `|z[i]|', say; but there's a better way. +Parameters to macros can be classified as suffixes, by saying ^@suffix@ +instead of @expr@. In this case +the actual arguments may be any ^\<suffix>, i.e., any sequence of +subscripts and tags that complete the name of a variable as explained +in Chapter~7. Here's what |super_half| looks like, using this idea: +\begintt +def super_half(suffix i,j,k) = + draw z.i{0,y.j-y.i} + ... (.8[x.j,x.i],.8[y.i,y.j]){z.j-z.i} + ... z.j{x.k-x.i,0} + ... (.8[x.j,x.k],.8[y.k,y.j]){z.k-z.j} + ... z.k{0,y.k-y.j} enddef; +\endtt + +\exercise Would the program for `{\manual O}' still work if the two calls of +|super_half| had been `|super_half(3,1,2)|' and `|super_half(3,4,2)|'\thinspace? +\answer Yes; the direction at |z.j| will be either "left" or "right". + +\exercise Guess the program for \MF's `{\manual A}', which has the +same width as `{\manual O}'. +\answer |beginlogochar("A",15);| +\rightfig A18a ({240\apspix} x {216\apspix}) ^3pt \parbreak +|x1=.5w;|\parbreak +|x2=x4=leftstemloc;|\parbreak +|x3=x5=w-x2;|\parbreak +|top y1=h+o;|\parbreak +|y2=y3=barheight;|\parbreak +|bot y4=bot y5=-o;|\parbreak +|draw z4--z2--z3--z5;|\parbreak +|super_half(2,1,3);|\parbreak +|labels(1,2,3,4,5);|\parbreak +|endchar;|\par\smallskip\noindent +Notice that all three calls of |super_half| in |logo.mf| are of the form +`"super\_half"$(2,j,3)$'. But it would not be good style to eliminate +parameters $i$ and~$k$, even though |super_half| is a ^{special-purpose} +subroutine; that would make it too too special. + +\danger Besides parameters of type @expr@ and @suffix@, \MF\ also +allows a third type called ^@text@. In this case the actual argument +is any sequence of tokens, and this sequence is not evaluated +beforehand; a text argument is simply copied in place of the +corresponding parameter. This makes it possible to write macros that +deal with lists of things. For example, Appendix~B's `@define\_pixels@' +macro is defined thus: +\begintt +def define_pixels(text t) = + forsuffixes a=t: a := a# * hppp; endfor enddef; +\endtt +this means that `|define_pixels(em,cap)|' will expand into +\begintt +forsuffixes a=em,cap: a := a# * hppp; endfor +\endtt +which, in turn, expands into the tokens `|em|~|:=|~|em#|~|*|~|hppp;| +|cap|~|:=|~|cap#|~|*|~|hppp;|' as we will see in Chapter~19. + +\danger Let's look now at a subroutine for drawing ^{serifs}, since +this typifies the sort of special-purpose macro one expects to see +in the design of a meta-typeface. Serifs can take many forms, +so we must choose from myriads of possibilities. We shall consider +two rather different approaches, one based on outline-filling and the +other based on the use of a fixed pen nib. In both cases it will be +necessary to omit some of the refinements that would be desirable +in a complete typeface design, to keep the examples from +getting too complicated. + +\danger +\parshape 13 +3pc 13pc +3pc 13pc +0pc 16pc +0pc 16pc +0pc 16pc +0pc 16pc +0pc 16pc +0pc 16pc +0pc 16pc +0pc 16pc +0pc 16pc +0pc 16pc +0pc 29pc +Our first example is a serif routine that +constructs six points $z_{\$a}$, $z_{\$b}$, \dots,~$z_{\$\mkern-1muf}$ around a +\rightfig 18b (48mm x 40mm) ^26pt +given triple of ``^{penpos}'' points $z_{\$l}$, $z_{\$}$, $z_{\$r}$; here +\$ is a suffix that's a parameter to the "serif" macro. Other parameters +are: "breadth", the distance between the parallel lines that run from +$z_{\$l}$ to $z_{\$a}$ and from $z_{\$r}$ to $z_{\$\mkern-1muf}$; "theta", the +direction angle of those two lines; "left\_jut", the distance from +$z_{\$l}$ to $z_{\$b}$; and "right\_jut", the distance from $z_{\$r}$ to +$z_{\$e}$. \ (The serif ``juts out'' by the amounts of the +^{jut} parameters.) \ There's also a "serif\_edge" macro, which constructs +the path shown. The routines refer to three variables that are assumed to +apply to all serifs: "slab", the vertical distance from $z_{\$b}$~% +and~$z_{\$e}$ to $z_{\$c}$~and~$z_{\$d}$; "bracket", the vertical distance +from $z_{\$a}$~and~$z_{\$\mkern-1muf}$ to $z_{\$l}$~and~$z_{\$r}$; and +"serif\_darkness", a fraction that controls how much of the triangular +regions $(z_{\$a},z_{\$l},z_{\$b})$ and $(z_{\$\mkern-1muf},z_{\$r},z_{\$e})$ +^^{]]} will be filled in. +\begindisplay +@def@ "serif"\thinspace(@suffix@ \$)(@expr@ + $"breadth","theta","left\_jut","right\_jut")=$\cr +\quad $\penpos\$("breadth"/{\rm abs\,sind}\,"theta",0)$;\cr +\quad $z_{\$a}-z_{\$l}=z_{\$\mkern-1muf}-z_{\$r}= + ("bracket"/{\rm abs\,sind}\,"theta")\ast {\rm dir}\,"theta"$;\cr +\quad $y_{\$c}=y_{\$d}$; \ $y_{\$b}=y_{\$e}=y_\$$; \ + $y_{\$b}-y_{\$c}=@if@\;"theta"<0:\;{-}\;@fi@\;"slab"$;\cr +\quad $x_{\$b}=x_{\$c}=x_{\$l}-"left\_jut"$; \ + $x_{\$d}=x_{\$e}=x_{\$r}+"right\_jut"$;\cr +\quad @labels@$(\$a,\$b,\$c,\$d,\$e,\$\mkern-1muf)$ @enddef@;\cr +\noalign{\smallskip} +@def@ "serif\_edge" @suffix@ \$ =\cr +\quad $\bigl("serif\_bracket"(\$a,\$l,\$b)\dashto z_{\$c}$\cr +\qquad $\dashto z_{\$d}\dashto {\rm reverse}\, + "serif\_bracket"(\$\mkern-1muf,\$r,\$e)\bigr)$ @enddef@;\cr +\noalign{\smallskip} +@def@ "serif\_bracket"(@suffix@ $i,j,k$) $=$\cr +\quad $\bigl(z.i\{z.j-z.i\} + \ldots"serif\_darkness"[z.j,.5[z.i,z.k]\,]\{z.k-z.i\}$\cr +\qquad$\ldots z.k\{z.k-z.j\}\bigr)$ @enddef@;\cr +\enddisplay + +\dangerexercise Under what circumstances will the "serif\_edge" +go through points $z_{\$l}$ and $z_{\$r}$? +\answer If $"bracket"=0$ or $"serif\_darkness"=0$. \ (It's probably +not a good idea to make $"serif\_darkness"=0$, because this would lead to +an extreme case of the `$\ldots$' triangle, ^^{...} which might not +be numerically stable in the presence of rounding errors.) +Another case, not really desirable, is $"left\_jut"="right\_jut"=0$. + + +\dangerexercise Should this "serif" macro be used before +points $z_{\$l}$, $z_\$$, and $z_{\$r}$ have been defined, or should those +points be defined first? +\answer That's a strange question. The "serif" routine includes a +"penpos" that defines $z_{\$l}$, $z_\$$, and $z_{\$r}$ relative +to each other, and it defines the other six points relative to them. +Outside the routine the user ought to specify just one $x$~coordinate +and one $y$~coordinate, in order to position all of the points. +This can be done either before or after "serif" is called, but +\MF\ has an easier job if it's done beforehand. + +\danger Here are two sample letters that show how these serif routines +might be used. The programs assume that the font has several additional +ad~hoc parameters: $u$,~a~unit of character width; "ht",~the character +height; "thin" and "thick", the two stroke weights; and "jut", the amount +by which serifs protrude on a ``normal'' letter like `H'. + +\begingroup\ninepoint\noindent +\displayfig 18c (252\apspix) +$$\halign to\hsize\bgroup\indent#\hfil\tabskip1em plus1fil minus1fil + &\tabskip0pt\hfil\%\ #\cr +@beginchar@\kern1pt(|"A"|$,13u\0,ht\0,0)$;\cr +$z_1=(.5w,1.05h)$;&top point\cr +$x_{4l}=w-x_{5r}=u$; \ $y_{4l}=y_{5r}="slab"$;&bottom points\cr +@numeric@ $"theta"[\,]$;\cr +$"theta"_4={\rm angle}(z_1-z_{4l})$;&left stroke angle\cr +$"theta"_5={\rm angle}(z_1-z_{5r})$;&right stroke angle\cr +$"serif"(4,"thin","theta"_4,.6"jut","jut")$;&left serifs\cr +$"serif"(5,"thick","theta"_5,"jut",.6"jut")$;&right serifs\cr +$z_0=z_{4r}+"whatever"\ast{\rm dir}\,"theta"_4$\cr +\qquad$=z_{5l}+"whatever"\ast{\rm dir}\,"theta"_5$;&inside top point\cr +@fill@ $z_1\dashto "serif\_edge"_4\dashto z_0$&the left stroke\cr +\qquad$\&\;z_0\dashto "serif\_edge"_5\dashto z_1\;\&\;\cycle$;&the + right stroke\cr +$\penpos2("whatever","theta"_4)$;\cr +$\penpos3("whatever","theta"_5)$;\cr +$y_{2r}=y_{3r}=.5[y_4,y_0]$;&crossbar height\cr +$y_{2l}=y_{3l}=y_{2r}-"thin"$;&crossbar thickness\cr +$z_2="whatever"[z_1,z_{4r}]$;\cr +$z_3="whatever"[z_1,z_{5l}]$;\cr +@penstroke@ $z_{2e}\dashto z_{3e}$;&the crossbar\cr +@penlabels@$(0,1,2,3,4,5)$; \ @endchar@;\cr +\noalign{\medskip} +@beginchar@\kern1pt(|"I"|$,6u\0,ht\0,0)$;\cr +$x_1=x_2=.5w$;\cr +$y_1=h-y_2$; \ $y_2="slab"$;\cr +"serif"$(1,"thick",-90,1.1jut,1.1jut)$;&upper serifs\cr +"serif"$(2,"thick",90,1.1jut,1.1jut)$;&lower serifs\cr +@fill@ $"serif\_edge"_2\dashto{\rm reverse}\,"serif\_edge"_1\dashto\cycle$; + &the stroke\cr +@penlabels@$(1,2)$; \ @endchar@;\cr +\enddisplay +The illustration was prepared with $"thin"=.5"pt"$, $"thick"=1.1"pt"$, +$u=.6"pt"$, $"ht"=7"pt"$, $"slab"=.25"pt"$, $"jut"=.9"pt"$, $"bracket"="pt"$, +and $"serif\_darkness"=1/3$. +\par\endgroup + +\dangerexercise Could the equations defining $y_1$ and $y_2$ in the program +for~|"I"| have been replaced by `$y_{1c}=h$' and `$y_{2c}=0$'? +\answer Yes; see the previous exercise. \ (But in the program for |"A"| +it's necessary to define $y_{4l}$ and $y_{5r}$, so that $"theta"_4$ +and~$"theta"_5$ can be calculated.) + +\dangerexercise Write the program for an |"H"| to go with these letters. +\answer \rightfig A18b (48mm x 43mm) ^10pt +@beginchar@\kern1pt(|"H"|$,13u\0,"ht"\0,0)$;\parbreak +$x_1=x_2=x_5=3u$;\parbreak +$x_3=x_4=x_6=w-x_1$;\parbreak +$y_{1c}=y_{3c}=h$; \ $y_{2c}=y_{4c}=0$;\parbreak +$"serif"(1,"thick",-90,"jut","jut")$;\parbreak +$"serif"(2,"thick",90,"jut","jut")$;\parbreak +$"serif"(3,"thick",-90,"jut","jut")$;\parbreak +$"serif"(4,"thick",90,"jut","jut")$;\parbreak +@fill@ $"serif\_edge"_2$\parbreak +\quad$\dashto{\rm reverse}\,"serif\_edge"_1\dashto\cycle$;\parbreak +@fill@ $"serif\_edge"_4$\parbreak +\quad$\dashto{\rm reverse}\,"serif\_edge"_3\dashto\cycle$;\parbreak +$\penpos5("thin",90)$; \ $\penpos6("thin",90)$;\parbreak +$y_5=y_6=.52h$; \ @penstroke@ $z_{5e}\dashto z_{6e}$;\parbreak +@penlabels@$(1,2,3,4,5,6)$; \ @endchar@. + +\ddanger A second approach to serifs can be based on the example at +the end of Chapter~16. In this case we assume that "broad\_pen" is +a `@pensquare@ xscaled~"px" yscaled~"py" rotated~"phi"' for +some $"px">"py"$ and some small angle~"phi". Thicker strokes will +be made by using this pen to fill a larger region; the serif routine +is given the distance "xx" between $z_{\$l}$ and $z_{\$r}$. +There's a pair variable called "dishing" that +controls the curvature between $z_{\$c}$ and~$z_{\$d}$. Top and +bottom serifs are similar, but they are sufficiently different that it's +easier to write separate macros for each case. +\begindisplay +@def@ "bot\_serif"(@suffix@ \$)(@expr@ $"xx","theta", + "left\_jut","right\_jut")=$\cr +\quad $\penpos\$("xx",0)$; \ + $z_{\$a}-z_{\$l}=z_{\$\mkern-1muf}-z_{\$r}= + ("bracket"/{\rm abs\,sind\,}"theta")\ast{\rm dir}\,"theta"$;\cr +\quad $y_{\$c}="top"\,y_{\$l}$; \ $y_{\$d}=y_{\$r}$; \ + $x_{\$c}=x_{\$l}-"left\_jut"$; \ $x_{\$d}=x_{\$r}+"right\_jut"$;\cr +\quad $z_{\$b}=z_{\$l}+"whatever"\ast{\rm dir}\,"theta" + =z_{\$c}+"whatever"\ast{\rm dir}\,"phi"$;\cr +\quad $z_{\$e}=z_{\$r}+"whatever"\ast{\rm dir}\,"theta" + =z_{\$d}+"whatever"\ast{\rm dir}\,{-"phi"}$;\cr +\quad @labels@$(\$a,\$b,\$c,\$d,\$e,\$\mkern-1muf)$ @enddef@;\cr +\noalign{\smallskip} +@def@ "bot\_serif\_edge" @suffix@ \$ $=$\cr +\quad $\bigl(z_{\$a}\to\controls z_{\$b}\to z_{\$c}$\cr +\qquad $\dashto("flex"(z_{\$c},.5[z_{\$c},z_{\$d}]+"dishing", + z_{\$d}))$ shifted $(0,-"epsilon")$\cr +\qquad $\dashto z_{\$d}\to\controls z_{\$e}\to z_{\$\mkern-1muf} + \bigr)$ @enddef@;\cr +\enddisplay +\displayfig 18d (272\apspix) +\begindisplay +@beginchar@\kern1pt(|"A"|$,13u\0,"ht"\0,0)$; \ @pickup@ "broad\_pen";\cr +$z_1=(.5w,"top"\,h)$; \ $"lft"\,x_{4l}=w-"rt"\,x_{5r}=1.2u$; \ + $y_{4l}=y_{5r}=0$;\cr +@numeric@ $"theta"[\,]$; \ $"theta"_4={\rm angle}(z_1-z_{4l})$; \ + $"theta"_5={\rm angle}(z_1-z_{5r})$;\cr +@numeric@ "xxx"; +\hbox spread-8pt{% +$"px"\ast{\rm sind}("theta"_5-"phi")+"xxx"\ast{\rm sind}\,"theta"_5 + = "px"\ast{\rm cosd}\,"phi"+"xx"$};\cr +$"bot\_serif"(4,0,"theta"_4,.8"jut",.8"jut")$; \ +$"bot\_serif"(5,"xxx","theta"_5,.6"jut",.8"jut")$;\cr +$z_0=z_{4r}+"whatever"\ast{\rm dir}\,"theta"_4 + =z_{5l}+"whatever"\ast{\rm dir}\,"theta"_5$;\cr +@filldraw@ $z_1\dashto "bot\_serif\_edge"_4 + \dashto z_0\;\&\;z_0\dashto "bot\_serif\_edge"_5 + \dashto z_1\;\&\;\cycle$;\cr +$"top"\,y_2="top"\,y_3=.45"bot"\,y_0$; \ + $z_2="whatever"[z_1,z_{4r}]$; \ $z_3="whatever"[z_1,z_{5l}]$;\cr +@draw@ $z_2\dashto z_3$; \ @penlabels@$(0,1,2,3,4,5)$; @endchar@;\cr +\noalign{\medskip} +@beginchar@\kern1pt(|"I"|$,6u\0,"ht"\0,0)$; \ @pickup@ "broad\_pen";\cr +$x_1=x_2=.5w$; \ $y_1=h$; \ $y_2=0$;\cr +$"top\_serif"(1,"xx",-90,1.1"jut",1.1"jut")$; \ +$"bot\_serif"(2,"xx",90,1.1"jut",1.1"jut")$;\cr +@filldraw@ $"bot\_serif\_edge"_2\dashto + {\rm reverse}\,"top\_serif\_edge"_1\dashto\cycle$;\cr +@penlabels@$(1,2)$; \ @endchar@;\cr +\enddisplay +In the illustration, $"px"=.8"pt"$, $"py"=.2"pt"$, $"phi"=20$, +$"xx"=.3"pt"$, $u=.6"pt"$, $"ht"=7"pt"$, $"jut"=.9"pt"$, $"bracket"="pt"$, +and $"dishing"=(.25"pt",0)$ rotated~20. + +\ddangerexercise Write the missing code for "top\_serif" and +"top\_serif\_edge". +\answer @def@ "top\_serif"(@suffix@ \$)(@expr@ $"xx","theta", + "left\_jut","right\_jut")=$\parbreak +\quad $\penpos\$("xx",0)$; \ +$z_{\$a}-z_{\$l}=z_{\$\mkern-1muf}-z_{\$r}= + ("bracket"/{\rm abs\,sind\,}"theta")\ast{\rm dir}\,"theta"$;\parbreak +\quad $y_{\$c}=y_{\$d}=y_\$$; \ + $x_{\$c}=x_{\$l}-"left\_jut"$; \ $x_{\$d}=x_{\$r}+"right\_jut"$;\parbreak +\quad $z_{\$b}=z_{\$l}+"whatever"\ast{\rm dir}\,"theta" + =z_{\$c}+"whatever"\ast{\rm dir}\,{-"phi"}$;\parbreak +\quad $z_{\$e}=z_{\$r}+"whatever"\ast{\rm dir}\,"theta" + =z_{\$d}+"whatever"\ast{\rm dir}\,"phi"$;\parbreak +\quad @labels@$(\$a,\$b,\$c,\$d,\$e,\$\mkern-1muf)$ @enddef@;\par +\smallskip\indent +@def@ "top\_serif\_edge" @suffix@ \$ $=$\parbreak +\quad $\bigl(z_{\$a}\to\controls z_{\$b}\to z_{\$c}$\parbreak +\qquad $\dashto("flex"(z_{\$c},.5[z_{\$c},z_{\$d}]-"dishing", + z_{\$d}))$ shifted $(0,+"epsilon")$\parbreak +\qquad $\dashto z_{\$d}\to\controls z_{\$e}\to z_{\$\mkern-1muf} + \bigr)$ @enddef@; + +\ddangerexercise (For mathematicians.) \ +Explain the equation for "xxx" in the program for~|"A"|. +\answer Assuming that $"py"=0$, the effective right stroke weight would be +$"px"\cdot\sin(\theta_5-\phi)$ if it were drawn with one stroke of "broad\_pen", +and $"xxx"\cdot\sin\theta_5$ is the additional weight corresponding to separate +strokes "xxx" apart. The right-hand side of the equation is the same +calculation in the case of vertical strokes ($\theta=90^\circ$), when the +stroke weight of |"I"| is considered. \ (Since a similar calculation +needs to be done for the letters K, V, W, X, Y, and Z, it would be a good +idea to embed these details in another macro.) + +\ddangerexercise Write the program for an |"H"| to go with these letters. +\answer \rightfig A18c (48mm x 45mm) ^10pt +@beginchar@\kern1pt(|"H"|$,13u\0,"ht"\0,0)$;\parbreak +$x_1=x_2=x_5=3u$;\parbreak +$x_3=x_4=x_6=w-x_1$;\parbreak +$y_1=y_3=h$; \ $y_2=y_4=0$;\parbreak +$"top\_serif"(1,"xx",-90,"jut","jut")$;\parbreak +$"bot\_serif"(2,"xx",90,"jut","jut")$;\parbreak +$"top\_serif"(3,"xx",-90,"jut","jut")$;\parbreak +$"bot\_serif"(4,"xx",90,"jut","jut")$;\parbreak +@filldraw@ $"bot\_serif\_edge"_2$\parbreak +\quad$\dashto{\rm reverse}\,"top\_serif\_edge"_1\dashto\cycle$;\parbreak +@fill@ $"bot\_serif\_edge"_4$\parbreak +\quad$\dashto{\rm reverse}\,"top\_serif\_edge"_3\dashto\cycle$;\parbreak +$y_5=y_6=.52h$; \ @draw@ $z_5\dashto z_6$;\parbreak +@penlabels@$(1,2,3,4,5,6)$; \ @endchar@. + +\danger A close look at the "serif\_edge" routines in these examples +will reveal that some parentheses are curiously lacking: We said +`@def@ "serif\_edge" @suffix@~\$' instead of +`@def@ "serif\_edge"(@suffix@~\$)', and we used the macro by saying +`$"serif\_edge"_5$' instead of +`$"serif\_edge"(5)$'. The reason is that \MF\ allows the final parameter +of a macro to be without delimiters; this is something that could not +have been guessed from a study of previous examples. It is time now +to stop looking at specific cases and to start examining the complete +set of rules for macro definitions. Here is the syntax: +\beginsyntax +<definition>\is<definition heading><is><replacement text>[enddef] +<is>\is[=]\alt[:=] +<definition heading>\is[def]<symbolic token><parameter heading> + \alt<vardef heading> + \alt<leveldef heading> +<parameter heading>\is<delimited parameters><undelimited parameters> +<delimited parameters>\is<empty> + \alt<delimited parameters>[(]<parameter type><parameter tokens>[)] +<parameter type>\is[expr] + \alt[suffix] + \alt[text] +<parameter tokens>\is<symbolic token> + \alt<parameter tokens>[,]<symbolic token> +<undelimited parameters>\is<empty> + \alt[primary]<symbolic token> + \alt[secondary]<symbolic token> + \alt[tertiary]<symbolic token> + \alt[expr]<symbolic token> + \alt[expr]<symbolic token>[of]<symbolic token> + \alt[suffix]<symbolic token> + \alt[text]<symbolic token> +\endsyntax +(We'll discuss ^\<vardef heading> and ^\<leveldef heading> in Chapter~20.) +\ The basic idea is that we name the macro to be defined, then we name +zero or more delimited parameters (i.e., parameters in parentheses), +then we name zero or more undelimited parameters. Then comes an `$=$'~sign, +followed by the replacement text, and @enddef@. The `$=$'~sign might also +be~`$:=$'\thinspace; both mean the same thing. + +\danger Delimited parameters are of type @expr@, @suffix@, or @text@; +two or more parameters of the same type may be listed together, separated +by commas. For example, `(@expr@~$a,b$)' means exactly the same thing as +`(@expr@~$a$)(@expr@~$b$)'. Undelimited parameters have eight possible +forms, as shown in the syntax. + +\ninepoint % all dangerous from here on + +\danger The \<replacement text> is simply filed away for future use, +not interpreted, when \MF\ reads a definition. But a few tokens are +treated specially:\enddanger\nobreak + +\medskip +\item\bull @def@, ^@vardef@, ^@primarydef@, ^@secondarydef@, and +^@tertiarydef@ are considered to introduce definitions inside definitions. + +\smallskip +\item\bull @enddef@ ends the replacement text, unless it matches a +previous @def@-like token (as listed in the preceding rule). + +\smallskip +\item\bull Each \<symbolic token> that stands for a parameter, by +virtue of its appearance in the \<parameter heading> or \<leveldef +heading>, is changed to a special in\-ternal ``parameter +token'' wherever it occurs in the +replacement text. Whenever this special token is subsequently encountered, +\MF\ will substitute the appropriate argument. + +\smallskip +\item\bull ^@quote@ disables any special interpretation of the immediately +following token. A~`@quote@' doesn't survive in the replacement text +(unless, of course, it has been quoted). + +\dangerexercise Check your understanding of these rules by +figuring out what the replacement text is, in the following weird definition: +\begintt +def foo(text t) expr e of p := + def t = e enddef; quote def quote t = p enddef +\endtt +\answer The replacement text contains ten tokens, +\begindisplay +\ttok{def}\quad\<t>\quad\ttok{=}\quad\<e>\quad\ttok{enddef} +\quad\ttok{;}\quad\ttok{def}\quad\ttok{t}\quad\ttok{=}\quad\<p> +\enddisplay +where \<t>, \<e>, and \<p> are placeholders for argument insertion. +When this macro is expanded with $"tracingmacros">0$, \MF\ will type +\begintt +foo(TEXT0)<expr>of<primary>->def(TEXT0)=(EXPR1)enddef;def.t=(EXPR2) +\endtt +followed by the arguments |(TEXT0)|, |(EXPR1)|, and |(EXPR2)|. + +\danger \MF\ does not expand macros when it reads a \<definition>; +but at almost all other times it will replace a defined token by the +corresponding replacement text, after finding all the arguments. +The replacement text will then be read as if it had been present +in the program all along. + +\danger How does \MF\ determine the arguments to a macro? Well, +it knows what kinds of arguments to expect, based on the parameter +heading. Let's consider delimited arguments first:\enddanger\nobreak + +\medskip +\item\bull A delimited +@expr@ argument should be of the form `(\<expression>)'; the expression +is evaluated and put into a special ``^{capsule}'' token that will be +substituted for the parameter wherever it appears in the replacement text. + +\smallskip +\item\bull A delimited @suffix@ argument should be of the form +`(\<suffix>)'; subscripts that occur in the suffix are evaluated +and replaced by numeric tokens. The result is a list of zero or more +tokens that will be substituted for the parameter wherever it appears +in the replacement text. + +\smallskip +\item\bull A delimited @text@ argument should be of the form +`(\<text>)', where \<text> is any sequence of tokens that is balanced +with respect to the delimiters surrounding it. This sequence of tokens +will be substituted for the parameter wherever it appears in the +replacement text. + +\smallskip +\item\bull When there are two or more delimited parameters, you can +separate the arguments by commas instead of putting parentheses around +each one. For example, three delimited arguments could be written +either as `$(a)(b)(c)$' or `$(a,b)(c)$' or `$(a)(b,c)$' or `$(a,b,c)$'. +However, this abbreviation doesn't work after text arguments, which +must be followed by~`)' because text arguments can include commas. + +\ddanger Chapter 8 points out that you can use other ^{delimiters} +besides parentheses. In general, a comma following a delimited +@expr@ or @suffix@ argument is equivalent to two tokens `)\thinspace(', +corresponding to whatever delimiters enclose that comma. + +\ddangerexercise After `|def| |f(expr| |a)(text| |b,c)=...enddef|' +and `|delimiters|~|{{|~|}}|', what are the arguments in +`|f{{x,(,}}((}}))|'? +\answer According to the rule just stated, the first comma is an +abbreviation for `|}}|~|{{|'. Hence the first argument is a capsule +containing the value of~$x$; the second is the text `|(,|'\thinspace; +the third is the text `|(}})|'. + +\danger The rules for undelimited arguments are similar. An +undelimited @primary@, @secondary@, @tertiary@, or @expr@ is the +longest syntactically correct ^\<primary>, ^\<secondary>, ^\<tertiary>, +or ^\<expression> that immediately follows the delimited arguments. +An undelimited `@expr@~$x$~^{of}~$y$' specifies two arguments, found +by taking the longest syntactically correct \<expression>~of~\<primary>. +In each of these cases, the expression might also be preceded by an +optional `^{=}' or~`^{:=}'. An undelimited @suffix@ is the longest +\<suffix> that immediately follows the delimited arguments; \MF\ also +allows `(\<suffix>)' in this case, but not `=\<suffix>' or `:=\<suffix>'. +An undelimited @text@ essentially runs to the end of the current +statement; more precisely, it runs to the first `;'\ or `^@endgroup@' or +`^@end@' that is not part of a ^{group} within the argument. + +\danger Appendix B contains lots of macros that illustrate these +rules. For example, +\begindisplay +@def@ ^@fill@ @expr@ $c$ $=$ @addto@ "currentpicture" @contour@ $c$ @enddef@;\cr +@def@ ^@erase@ @text@ $t$ $=$ @cullit@; \ $t$ @withweight@ $-1$; + @cullit@ @enddef@;\cr +\enddisplay +these are slight simplifications of the real definitions, but they retain the +basic ideas. The command `@erase@~@fill@~$p$' causes `@fill@~$p$' to be +the @text@ argument to~@erase@, after which `$p$' becomes the @expr@ +argument to~@fill@. + +\ddangerexercise The `@pickup@' macro in Appendix B starts with +`@def@~@pickup@~@secondary@~$q$'; why is the argument a secondary +instead of an expression? +\answer This snares ^{future pen}s before they're converted to pens, because +@pickup@ wants to yscale by "aspect\_ratio" before ellipses change to +polygons. + +\ddangerexercise Explain why the following `^"hide"' macro allows you to +hide any sequence of statements in the midst of an expression: +\begindisplay +@def@ "hide"(@text@ $t)="gobble"@begingroup@\,t;$ @endgroup@ @enddef@;\cr +@def@ "gobble" @primary@ $g=@enddef@$;\cr +\enddisplay +\answer The construction `"hide"\thinspace(\<statement list>)' expands into +`"gobble" @begingroup@ \<statement list>; @endgroup@', so the +argument to "gobble" must be evaluated. The @begingroup@ causes \MF\ +to start executing statements. When that has been done, the final +statement turns out to be \<empty>, so the argument to "gobble" +turns out to be a ^{vacuous} expression (cf.\ Chapter~25). Finally, +"gobble"'s replacement text is empty, so the hidden text has indeed +disappeared. \ (The "hide" macro in Appendix~B is actually a bit +more efficient, but a bit trickier.) + +\endchapter + +DEFINI\/$'$\kern-.5ptTION, {\rm s. \ [definitio}, Latin.{\rm]} +1. A short description of a thing by its properties. +\author SAMUEL ^{JOHNSON}, {\sl A Dictionary of the English Language\/} (1755) + +\bigskip + +DEFINI\/$''$\kern-.5ptTION, {\rm n. \ [{\sl L.} definitio}. See\/ {\rm Define.]} +1. A brief description of a thing by its properties; +as a\/ {\rm definition} \kern-.5pt of wit or of a circle. +\author NOAH~^{WEBSTER}, {\sl An~American~% + Dictionary~of~the~English~Language\/}~(1828) + +\eject + \beginchapter Chapter 19. Conditions\\and Loops + +If decisions never had to be made, life would be much easier, and so would +programming. But sometimes it is necessary to choose between alternatives, +and \MF\ allows programs to take different paths depending on the circumstances. +You just say something like +\begindisplay +@if@ not "decisions": \ $"life":="programming":="easier"("much")$\cr +@elseif@ $"choice"=a$: \ "program\_a"\cr +@else@: \ "program\_b" \ @fi@\cr +\enddisplay +which reduces, for example, to `"program\_b"' if and only if +$"decisions"=@true@$ and $"choice"\ne a$. The normal left-to-right +order of program interpretation can also be modified by specifying +``^{loops},'' which tell the computer to read certain tokens repeatedly, +with minor variations, until some ^{condition} becomes true. We have +seen many examples of these mechanisms already; the purpose of the +present chapter is to discuss the entire range of possibilities. + +\MF's conditions and loops are different from those in most other +programming languages, because the conditional or iterated code does +not have to fit into the syntactic structure. For example, you can +write strange things like +\begintt +p = (if b: 0,0)..(1,5 else: u,v fi) +\endtt +where the conditional text `$0,0)\to(1,5$' makes no sense by itself, +although it becomes meaningful when read in context. In this respect +conditions and loops behave like macros. They specify rules of +token transformation that can be said to take place in \MF's ``^{mouth}'' +before the tokens are actually digested in the computer's ``^{stomach}.'' + +The first conditional example above has three alternatives, in the form +\begindisplay +@if@ \<boolean$_1$>: \<text$_1$> \ +@elseif@ \<boolean$_2$>: \<text$_2$> \ +@else@: \<text$_3$> \ @fi@ +\enddisplay +and the second example has just two; there can be any number of +`^@elseif@\kern1pt' clauses before `^@else@:'. Only one of the conditional +texts will survive, namely the first one whose condition is true; +`@else@:'\ is always true. You can also omit `@else@:'\ +entirely, in which case `@else@:\thinspace\<empty>' is implied just before +the closing `^@fi@'. For example, plain \MF's @mode\_setup@ routine +includes the conditional~command +\begindisplay +@if@ unknown "mag": \ $"mag":=1$; \ @fi@ +\enddisplay +whose effect is to set "mag" equal to 1 if it hasn't already received +a value; in this case there's only one alternative. + +\exercise Would it be wrong to put the `;' after the `@fi@' in the example +just given? +\answer Then \MF's ``stomach'' would see `;' if "mag" is known, but there +would be no change if "mag" is unknown. An extra semicolon is harmless, +since \MF\ statements can be \<empty>. But it's wise to get in the habit +of putting `;' before @fi@, because it saves a wee bit of time and because +`;' definitely belongs before ^@endfor@. + +\danger The informal rules just stated can, of course, be expressed more +formally as rules of syntax: +\beginsyntax +<condition>\is[if]<boolean expression>[:]<conditional text><alternatives>[fi] +<alternatives>\is<empty> + \alt[else][:]<conditional text> + \alt[elseif]<boolean expression>[:]<conditional text><alternatives> +\endsyntax +Every conditional construction begins with `^@if@\kern1pt' and ends with +`@fi@'. The conditional texts are any sequences of tokens that are +balanced with respect to `@if@\kern1pt' and~`@fi@'; furthermore, +`@elseif@\kern1pt' and `@else@' can occur in a conditional text only when +enclosed by `@if@\kern1pt' and~`@fi@'. + +\danger Each `@if@\kern1pt' and `@elseif@\kern1pt' must be followed by a +\<boolean expression>, i.e., by an expression whose value is either +`@true@' or `@false@'. ^{Boolean expressions} are named after George +^{Boole}, the founder of algebraic approaches to logic. Chapter~7 points +out that variables can be of type ^@boolean@, and numerous examples of +boolean expressions appear in Chapter~8. It's time now to be more +systematic, so that we will know the facts about boolean expressions just +as we have become well-versed in numeric expressions, pair expressions, +picture expressions, path expressions, transform expressions, and pen +expressions. Here are the relevant syntax rules: +\beginsyntax +<boolean primary>\is<boolean variable> + \alt[true]\alt[false] + \alt[(]<boolean expression>[)] + \alt[begingroup]<statement list><boolean expression>[endgroup] + \alt[known]<primary>\alt[unknown]<primary> + \alt<type><primary>\alt[cycle]<primary> + \alt[odd]<numeric primary> + \alt[not]<boolean primary> +<boolean secondary>\is<boolean primary> + \alt<boolean secondary>[and]<boolean primary> +<boolean tertiary>\is<boolean secondary> + \alt<boolean tertiary>[or]<boolean secondary> +<boolean expression>\is<boolean tertiary> + \alt<numeric expression><relation><numeric tertiary> + \alt<pair expression><relation><pair tertiary> + \alt<transform expression><relation><transform tertiary> + \alt<boolean expression><relation><boolean tertiary> + \alt<string expression><relation><string tertiary> +<relation>\is[\char'74]\alt[\char'74=]\alt[>]\alt[>=]\alt[=]\alt[\char'74>] +\endsyntax +Most of these operations were already explained in Chapter~8, so it's only +necessary to mention the more subtle points now. A ^\<primary> of any +type can be tested to see whether it has a specific type, and whether it +has a known or unknown value based on the equations so far. In these tests, +a ^\<future pen primary> is considered to be of type ^@pen@. The test +`cycle~$p$' is true if and only if $p$~is a cyclic path. The `odd' function +first rounds its argument to an integer, then tests to see if the integer +is odd. The `not' function changes true to false and vice versa. The `and' +function yields true only if both arguments are true; the `or' function +yields true unless both arguments are false. Relations on pairs, transforms, +or strings are decided by the first unequal component from left to right. +\ (A ^{transform} is considered to be a 6-tuple as in Chapter~15.) \ + +\dangerexercise What do you think: Is @false@ $>$ @true@? +\answer No; that would be shocking. + +\dangerexercise Could `(odd $n$) and not (odd $-n$)' possibly be true? +\answer Yes, if and only if $n-{1\over2}$ is a nonnegative even integer. +\ (Because ambiguous values are rounded upwards.) + +\dangerexercise Could `(cycle $p$) and not (known $p$)' possibly be true? +\answer No. + +\dangerexercise Define an `even' macro such that `even~$n$' is true if +and only if round$(n)$ is an even integer. \ [{\sl Hint:\/} There's a +slick answer.] +\answer @def@ even $=$ not odd @enddef@. + +\ddanger Boolean expressions beginning with a ^\<type> should not come +at the very beginning of a statement, because \MF\ will think that +a ^\<declaration> is coming up instead of an \<expression>. Thus, for +example, if $b$~is a boolean variable, the equation `$@path@\,p=b$' +should be rewritten either as `$b=@path@\,p$' or as `$(@path@\,p)=b$'. + +\ddanger A boolean expression like `$x=y$' that involves the ^{equality} +relation looks very much like an ^{equation}. \MF\ will consider `$=$' +to be a \<relation> unless the expression to its left occurs at the +very beginning of a ^\<statement> or the very beginning of a ^\<right-hand +side>. If you want to change an equation into a relation, +just insert parentheses, as in `$(x=y)=b$' or `$b=(x=y)$'. + +\ddanger After a ^\<path join>, the token `^{cycle}' is not considered +to be the beginning of a \<boolean primary>. \ (Cf.\ Chapter~14.) + +\ddanger The boolean expression `^@path@ $((0,0))$' is false, even +though `$((0,0))$' meets Chapter~14's syntax rules for +\<path primary>, via (\<path expression>) and +(\<path tertiary>) and +(\<pair tertiary>). A ^{pair expression} is not considered to be +of type @path@ unless the path interpretation is mandatory. + +\ddangerexercise Evaluate `length $((3,4))$' and `length $((3,4)\{0,0\})$' +and `length reverse~$(3,4)$'. +\answer The first is~5, because the pair is not considered to be a path. +The second and third are~0, because the pair is forced to become a path. + +OK, that covers all there is to be said about conditions. What about +loops? It's easiest to explain loops by giving the syntax first: +\beginsyntax +<loop>\is<loop header>[:]<loop text>[endfor] +<loop header>\is[for]<symbolic token><is><for list> + \alt[for]<symbolic token><is><progression> + \alt[forsuffixes]<symbolic token><is><suffix list> + \alt[forever] +<is>\is[=]\alt[:=] +<for list>\is<expression>\alt<empty> + \alt<for list>[,]<expression>\alt<for list>[,]<empty> +<suffix list>\is<suffix> + \alt<suffix list>[,]<suffix> +<progression>\is<initial value>[step]<step size>[until]<limit value> +<initial value>\is<numeric expression> +<step size>\is<numeric expression> +<limit value>\is<numeric expression> +<exit clause>\is[exitif]<boolean expression>[;] +\endsyntax +As in macro definitions, `$=$' and `$:=$' are interchangeable here. + +This syntax shows that loops can be of four kinds, which we might +indicate schematically as follows: +\begindisplay +@for@ $x=\epsilon_1,\epsilon_2,\epsilon_3$: text($x$) @endfor@\cr +\noalign{\vskip 1pt plus 1pt} +@for@ $x=\nu_1$ @step@ $\nu_2$ @until@ $\nu_3$: text($x$) @endfor@\cr +\noalign{\vskip 1pt plus 1pt} +@forsuffixes@ $s=\sigma_1,\sigma_2,\sigma_3$: text($s$) @endfor@\cr +\noalign{\vskip 1pt plus 1pt} +@forever@: text @endfor@\cr +\enddisplay +The first case expands to +`text($\epsilon_1$) text($\epsilon_2$) text($\epsilon_3$)'; the +$\epsilon$'s here are expressions of any type, not necessarily ``known,'' +and they are evaluated and put into ^{capsules} before being substituted +for~$x$. The $\epsilon$'s might also be empty, in which case +text($\epsilon$) is omitted. +The second case is more complicated, and it will be explained carefully +below; simple cases like `1~@step@~2 @until@~7' are equivalent to +short lists like `$1,3,5,7$'. The third case expands to +`text($\sigma_1$) text($\sigma_2$) text($\sigma_3$)'; the $\sigma$'s here +are arbitrary suffixes (possibly empty), in which subscripts will have been +evaluated and changed to numeric tokens before being substituted for~$s$. +The final case expands into the sequence `text~text~text~$\ldots$', +ad~infinitum; there's an escape from this (and from the other three kinds +of loop) if an \<exit clause> appears in the text, as explained below. + +Notice that if the loop text is a single statement that's supposed to +be repeated several times, you should put a `^{;}' just before the +@endfor@, not just after it; \MF's loops do not insert ^{semicolons} +automatically, because they are intended to be used in the midst of +expressions as well as with statements that are being iterated. + +Plain \MF\ defines `^@upto@' as an abbreviation for `@step@~1~@until@', +and `^@downto@' as an abbreviation for `@step@~$-1$~@until@'. Therefore +you can say, e.g., `\thinspace@for@ $x=1$ @upto@~9:\thinspace' instead of +`\thinspace@for@ $x=1,2,3,4,5,6,7,8,9$:\thinspace'. + +\danger When you say `@for@ $x=\nu_1$ @step@ $\nu_2$ @until@~$\nu_3$', +\MF\ evaluates the three numeric expressions, which must have known values. +Then it reads the loop text. If $\nu_2>0$ and $\nu_1>\nu_3$, or if +$\nu_2<0$ and $\nu_1<\nu_3$, the loop is not performed at all. Otherwise +text($\nu_1$) is performed, $\nu_1$ is replaced by $\nu_1+\nu_2$, and +the same process is repeated with the new value of $\nu_1$. + +\dangerexercise Read the rules in the previous paragraph carefully, then +explain for what values of~$x$ the loop is performed if you say +(a)~`\thinspace@for@~$x=1$ @step@~2 @until@~0'\thinspace. \ +(b)~`\thinspace@for@~$x=1$ @step@~$-2$ @until@~0\thinspace'. \ +(c)~`\thinspace@for@~$x=1$ @step@~0 @until@~0\thinspace'. \ +(d)~`\thinspace@for@~$x=0$ @step@~.1 @until@~1\thinspace'. +\answer (a) The loop text is never executed. \ +(b)~It's executed only once, for $x=1$. \ +(c)~It's executed infinitely often, for $x=1,1,1,\ldots\,$. \ +(d)~Since ten times \MF's internal representation of +.1 is slightly larger than 1, the answer +is not what you probably expect! The loop text is executed for +$x=0$,~0.1, 0.20001, 0.30002, 0.40002, 0.50003, 0.60004, 0.70004, 0.80005, +and 0.90005 only. \ (If you want the values $(0,.1,.2,\ldots,1)$, say +`\thinspace@for@ $"xx"=0$ @upto@~10: $x:="xx"/10$; \<text> @endfor@' instead.) + +\danger A \<loop text> is rather like the \<replacement text> of a macro. +It is any sequence of tokens that is balanced with respect to +un^{quote}d appearances of @for@/@forsuffixes@/@forever@ and @endfor@ +delimiters. \MF\ reads the entire loop text quickly and stores it away +before trying to perform it or to expand macros within it. All occurrences +of the controlled \<symbolic token> in the loop text are changed to +special internal parameter tokens that mean ``insert an argument here,'' +where the argument is of type @expr@ in the case of @for@, of +type @suffix@ in the case of @forsuffixes@. This rule implies, in +particular, that the symbolic token has no connection with similarly +named variables elsewhere in the program. + +\dangerexercise What values are shown by the following program? +\begintt +n=0; for n=1: m=n; endfor show m,n; end. +\endtt +\answer $m=1$, $n=0$. + +\danger The ^"flex" routine described in Chapter~14 provides an interesting +example of how loops can be used inside of macros inside of expressions: +\begindisplay +@pair@ $"z\_"\,[\,]$, $"dz\_"$; \ @numeric@ "n\_"\thinspace; + &\% private variables\cr +@def@ "flex"(@text@ $t$) $=$&\% $t$ is a list of pairs\cr +\quad^"hide"$\bigl(\,"n\_":=0$;\cr +\qquad @for@ $z=t$: $"z\_"\,[{\rm incr}\,"n\_"]:=z$; @endfor@\cr +\qquad $"dz\_":="z\_"\,["n\_"]-"z\_"\,[1]\,\bigr)$\cr +\quad $"z\_"\,[1]$ @for@ $k=2$ @upto@ $"n\_"-1$: + $\ldots"z\_"\,[k]\{"dz\_"\}$ @endfor@\hidewidth\cr +\qquad $\ldots"z\_"\,["n\_"]$ @enddef@;\cr +\enddisplay +The first loop stores the given pairs temporarily in an array, and it also +counts how many there are; this calculation is ``hidden.'' Then +the actual flex-path is contributed to the program with the help of +a second loop. \ (Appendix~B uses the convention that symbolic tokens +ending in `^{\_}' should not appear in a user's program; this often +makes it unnecessary to `^@save@' tokens.) + +\danger When \MF\ encounters the construction `^@exitif@ \<boolean +expression>;', it evaluates the boolean expression. If the +expression is true, the (innermost) loop being iterated is terminated +abruptly. Otherwise, nothing special happens. + +\dangerexercise Define an `^@exitunless@' macro such that +`@exitunless@ \<boolean expression>;' will exit the current loop +if the boolean expression is false. +\answer @def@ @exitunless@ @expr@ $b$ $=$ @exitif@ not $b$ @enddef@. +\ (The simpler alternative `@def@ @exitunless@ $=$ @exitif@ not +@enddef@\kern1pt' wouldn't work, since `not' applies only to the following +\<boolean primary>.) + +\ddangerexercise Write a \MF\ program that sets $p[k]$ to the $k$th +^{prime number}, for $1\le k\le30$. Thus, $p[1]$ should be~2, +$p[2]=3$, etc. +\answer |numeric p[]; boolean n_is_prime; p[1]=2; k:=1;|\parbreak +|for n=3 step 2 until infinity:|\parbreak +| n_is_prime:=true;|\parbreak +| for j=2 upto k: if n mod p[j]=0: n_is_prime:=false; fi|\parbreak +| exitif n/p[j]<p[j]; endfor|\parbreak +| if n_is_prime: p[incr k]:=n; exitif k=30; fi|\parbreak +| endfor fi|\parbreak +^^@show@^^@str@ +|show for k=1 upto 30: str p[k]&"="&decimal p[k], endfor "done" end.| + +\ddangerexercise When you run \MF\ on the file `|expr.mf|' of +Chapter~8, you get into a `^@forever@' loop that can be stopped +if you type, e.g., `|0|~|end|'. But what can you type to get out +of the loop without ending the run? \ (The goal is to make +\MF\ type~`|*|', without incurring any error messages.) +\answer `|0; exitif true;|'. + +\endchapter + +If? thou Protector of this damned Strumpet, +Talk'st thou to me of Ifs: thou art a Traytor, +Off with his Head. +\author WILLIAM ^{SHAKESPEARE}, {\sl Richard the Third\/} (1593) + +\bigskip + +% When ye pray, +Use not vain repetitions. +\author {\sl ^{Matthew} 6\thinspace:\thinspace7\/} (c.~70 A.D.) + +\eject + \beginchapter Chapter 20. More\\About\\Macros + +Chapter 18 gave the basic facts about macro definitions, but it didn't +tell the whole story. It's time now for the Ultimate Truth to be revealed. + +\ninepoint +\danger But this whole chapter consists of ``dangerous bend'' paragraphs, +since the subject matter will be appreciated best by people who have +worked with \MF\ for a little while. +We shall discuss the following topics:\enddanger +\smallskip +\item\bull Definitions that begin with `@vardef@\kern1pt'; these embed macros +into the variables of a program and extend the unary operators of +\MF\ expressions. + +\item\bull Definitions that begin with `@primarydef@\kern.3pt', +`@secondarydef@\kern.3pt', or `@tertiarydef@\kern.3pt'; these extend the +binary operators of \MF\ expressions. + +\item\bull Other primitives of \MF\ that expand into sequences of tokens +in a macro-like way, including `@input@' and `@scantokens@'. + +\item\bull Rules that explain when tokens are subject to expansion +and when they aren't. + +\danger First let's consider the \<vardef heading> that was left +undefined in Chapter~18. The ordinary macros discussed in that chapter +begin with +\begindisplay +@def@ \<symbolic token>\<parameter heading> +\enddisplay +and then comes `$=$', etc. You can also begin a definition by saying +\begindisplay +^@vardef@ \<declared variable>\<parameter heading> +\enddisplay +instead; in this case the ^\<declared variable> might consist of +several tokens, and you are essentially defining a variable whose +``value'' is of type ``macro.'' For example, suppose you decide to say +\begindisplay +@pair@ $a.p$; \ @pen@ $a.q$; \ @path@ $a.r$; \ +@vardef@ $a.s=\ldots$ @enddef@; +\enddisplay +then $a.p$, $a.q$, and $a.r$ will be variables of types @pair@, @pen@, +and @path@, but $a.s$ will expand into a sequence of tokens. \ +(The language {\eightrm^{SIMULA67}} demonstrated that it is advantageous +to include procedures as parts of variable data structures; \MF\ does an +analogous thing with macros.) + +\danger After a definition like `@def@ $t=\ldots$', the token $t$ becomes +a ``^{spark}''; i.e., you can't use it in a suffix. But after +`@vardef@ $t=\ldots$', the token~$t$ remains a ``^{tag},'' because +macro expansion will take place only when $t$~is the first token in +a variable name. Some of the definitions in Appendix~B are vardefs +instead of defs for just that reason; for example, +\begindisplay +@vardef@ dir @primary@ $d$ $=$ "right" rotated $d$ @enddef@ +\enddisplay +allows a user to have variable names like `|p5dir|'. + +\danger A variable is syntactically a primary expression, and \MF\ would +get unnecessarily confused if the replacement texts of vardef macros +were very different from primary expressions. Therefore, the +tokens `^@begingroup@' +and `^@endgroup@' are automatically inserted at the beginning and end +of every vardef replacement text. If you say `^@showvariable@~$a$' +just after making the declarations and definition above, the machine +will reply as follows: +\begintt +a.p=pair +a.q=unknown pen +a.r=unknown path +a.s=macro:->begingroup...endgroup +\endtt + +\danger The `^{incr}' macro of Appendix B increases its argument by~1 +and produces the increased value as its result. The inserted `@begingroup@' +and `@endgroup@' come in handy here: +\begindisplay +@vardef@ incr @suffix@ \$ $=$ $\$:=\$+1$; \ \$ @enddef@. +\enddisplay +Notice that the argument is a ^@suffix@, not an @expr@, because +every variable name is a special case of a ^\<suffix>, and because +an ^@expr@ parameter should never appear to the left ^^{:=} of~`$:=$'. +Incidentally, according to the rules for ^{undelimited suffix parameters} +in Chapter~18, you're allowed to say either `incr~$v$' or `incr$(v)$' when +applying incr to~$v$. + +\danger There's another kind of vardef, in which the variable name being +defined can have any additional suffix when it is used; this suffix is +treated as an argument to the macro. In this case you write +\begindisplay +@vardef@ \<declared variable>|@#| \<parameter heading> +\enddisplay +^^{at sharp} and you can use |@#| in the replacement text (where it +behaves like any other @suffix@ parameter). For example, Appendix~B says +\begindisplay +@vardef@ $z$|@#| $=$ $(x$|@#|$,y$|@#|) @enddef@; +\enddisplay +this is the magic definition that makes `$z_{3r}$' equivalent to +`$(x_{3r},y_{3r})$', etc. In fact, we now know that `|z3r|' actually +expands into eleven tokens: +\begintt +begingroup (x3r, y3r) endgroup +\endtt + +\ddangerexercise True or false: After `|vardef| |a@#| |suffix| |b| |=| +$\ldots$~|enddef|', the suffix argument~|b| will always be empty. +\answer False; consider `|a1(2)|'. + +\ddanger Plain \MF\ includes a ^"solve" macro that uses ^{binary search} +to find numerical solutions to ^{nonlinear equations}, which are too +difficult to resolve in the ordinary way. ^^{equations, nonlinear} +To use "solve", you first define a macro $f$ such that $f(x)$ is either +@true@ or @false@; then you say +\begindisplay +"solve" $f("true\_x","false\_x")$ +\enddisplay +where "true\_x" and "false\_x" are values such that $f("true\_x")=@true@$ +and $f("false\_x")=@false@$. The resulting value~$x$ will be at the cutting +edge between truth and falsity, in the sense that $x$~will be within a +given ^"tolerance" of values for which $f$ yields both outcomes. +\begindisplay +@vardef@ "solve"|@#|(@expr@ $"true\_x","false\_x"$) $=$\cr +\quad $"tx\_":="true\_x"$; \ $"fx\_":="false\_x"$;\cr +\quad^@forever@: $"x\_":=.5["tx\_","fx\_"]$; \ + ^@exitif@ abs$("tx\_"-"fx\_")\le"tolerance"$;\cr +\quad @if@ |@#|$("x\_"):\ "tx\_" \ @else@:\ "fx\_"\ @fi@$ + :=\ "x\_"\thinspace; @endfor@;\cr +\quad "x\_" @enddef@;\cr +\enddisplay + +\ddanger For example, the "solve" routine makes it possible to solve the +following interesting problem posed by Richard ^{Southall}: Given +points $z_1$,~$z_2$, $z_3$,~$z_4$ such that $x_1<x_2<x_3<x_4$ and +$y_1<y_2=y_3>y_4$, find the point~$z$ between $z_2$ and~$z_3$ such that +\MF\ will choose to travel "right" at~$z$ in the path +\begindisplay +$z_1\,\{z_2-z_1\}\to z\to\{z_4-z_3\}\,z_4$. +\enddisplay +If we try $z=z_2$, \MF\ will choose a direction at $z$ that has a positive +(upward) $y$-component; but at $z=z_3$, \MF's chosen direction will have a +negative (downward) $y$-component. Somewhere in between is a ``^{nice}'' +value of~$z$ for which the curve will not rise above the line $y=y_2$. +What is this~$z$? +\displayfig 20a (115\apspix) +Chapter 14 gives equations from which $z$ could be computed, in principle, +but those equations involve trigonometry in a complicated fashion. +It's nice to know that we can find~$z$ rather easily in spite of those +complexities: +\begindisplay +@vardef@ "upward"(@expr@ $x$) $=$\cr +\quad ypart direction 1 of $\bigl(z_1\{z_2-z_1\} + \to(x,y_2)\to\{z_4-z_3\}z_4\bigr)>0$ @enddef@;\cr +$z=\bigl("solve"\,"upward"(x_2,x_3),y_2\bigr)$.\cr +\enddisplay + +\ddangerexercise It might happen in unusual cases that $"upward"(x)$ +is @false@ for all $x_2\le x\le x_3$, hence "solve" is being invoked +under invalid assumptions. What result does it give~then? +\answer A value very close to $z_2$. + +\ddangerexercise Use "solve" to find $\root3\of{10}$, and compare +the answer to the ^{cube root} obtained in the normal way. +\answer |vardef lo_cube(expr x)=x*x*x<10 enddef;|\parbreak +|show solve lo_cube(0,10), 10**1/3; end.|\par\nobreak\medskip\noindent +^^{**} With the default ^"tolerance" of 0.1, +this will show the respective values |2.14844| and |2.1544|. +A more general routine could also be written, with `10' as a parameter: +\begintt +vardef lo_cube[](expr x)=x*x*x<@ enddef; +show solve lo_cube10(0,10); +\endtt +if we ask for minimum tolerance ($"tolerance":="epsilon"$), the +result is |2.15445|; the true value is $\approx 2.15443469$. + +\ddanger The syntax for \<declared variable> in Chapter~7 allows for +^{collective subscripts} as well as tags in the name of the variable +being declared. Thus, you can say +\begindisplay +@vardef@ $a[\,]b[\,]=\ldots$ @enddef@; +\enddisplay +what does this mean? Well, it means that all variables like |a1b2| +are macros with a common replacement text. Every vardef has two +^^{at} ^^{sharp at} +implicit suffix parameters, `|#@|' and~`|@|', which can be used in +the replacement text to discover what subscripts have actually been +used. Parameter~`|@|' is the final token of the variable name +(`|2|' in this example); parameter `|#@|' is everything preceding +the final token (in this case `|a1b|'). These notations are supposed to +be memorable because `|@|' is where you're ``at,'' while `|#@|' is +everything before and `|@#|' is everything after. + +\ddangerexercise After `|vardef| |p[]dir=(#@dx,#@dy)| |enddef|', what's +the expansion of `|p5dir|'\thinspace? +\answer |begingroup(p5dx,p5dy)endgroup|. + +\ddangerexercise Explain how it's possible to retrieve the first subscript +in the replacement text of |vardef|~|a[]b[]| (thereby obtaining, +for example, `|1|' instead of `|a1b|'). +\answer Say `|first#@|' after defining `|vardef| |first.a[]@#=@| |enddef|'. +\ (There are other solutions, e.g., using substrings of ^@str@~|#@|, +but this one is perhaps the most instructive.) + +\ddangerexercise Say `^|showvariable| |incr,z|' to \MF\ and explain +^^{incr} ^^{z} the machine's reply. +\answer The machine answers thus: +\begintt +incr=macro:<suffix>-> + begingroup(SUFFIX2):=(SUFFIX2)+1;(SUFFIX2)endgroup +z@#=macro:->begingroup(x(SUFFIX2),y(SUFFIX2))endgroup +\endtt +Parameters to a macro are numbered sequentially, starting with zero, +and classified as either ^|(EXPR|$_n$|)|, ^|(SUFFIX|$_n$|)|, or +^|(TEXT|$_n$|)|. In a vardef, |(SUFFIX0)| and |(SUFFIX1)| are always +reserved for the implicit parameters |#@| and~|@|; |(SUFFIX2)| will +be |@#|, if it is used in the parameter heading, otherwise it will be the +^^{at sharp} ^^{at} ^^{sharp at} first explicit parameter, if +it happens to be a suffix parameter. + +\ddanger A vardef wipes out all type declarations and macro definitions +for variables whose name begins with the newly defined macro variable name. +For example, `|vardef|~|a|' causes variables like |a.p| +and |a1b2| to disappear silently; `|vardef|~|a.s|' wipes out +|a.s.p|, etc. Moreover, after `|vardef|~|a|' is +in effect, you are not allowed to say `|pair|~|a.p|' or `|vardef|~|a[]|', +since such variables would be inaccessible. + +\ddanger The syntax for \<definition> in Chapter 18 was incomplete, +because $\langle$vardef heading$\rangle$ and \<leveldef heading> were +omitted. Here are the missing rules: +\beginsyntax +<vardef heading>\is[vardef]<declared variable><parameter heading> + \alt[vardef]<declared variable>[\char'100\#]<parameter heading> +<leveldef heading>\is<leveldef><parameter><symbolic token><parameter> +<leveldef>\is[primarydef]\alt[secondarydef]\alt[tertiarydef] +<parameter>\is<symbolic token> +\endsyntax +The new things here are @primarydef@, @secondarydef@, and @tertiarydef@, +which permit you to extend \MF's repertoire of binary operators. For example, +the `dotprod' operator is defined as follows in Appendix~B: +\begindisplay +@primarydef@ $w$ dotprod $z$ $=$\cr +\quad $({\rm xpart}\,w\ast{\rm xpart}\,z\;+\; + {\rm ypart}\,w\ast{\rm ypart}\,z)$ @enddef@.\cr +\enddisplay +\MF's syntax for expressions has effectively gained a new rule +\beginsyntax +<numeric secondary>\is<pair secondary>[dotprod]<pair primary> +\endsyntax +in addition to the other forms of \<numeric secondary>, because of this +primarydef. + +\ddanger The names `@primarydef@\kern1pt', `@secondarydef@\kern1pt', +and `@tertiarydef@\kern1pt' may +seem off by one, because they define operators at one level higher up: +A primarydef defines a binary operator that forms a secondary expression +from a secondary and a primary; such operators are at the same level +as `$\ast$' and `rotated'. +A secondarydef defines a binary operator that forms a tertiary expression +from a tertiary and a secondary; such operators are at the same level +as~`$+$'~and~`or'. +A tertiarydef defines a binary operator that forms an expression +from an expression and a tertiary; such operators are at the same level +as~`$<$'~and~`\&'. + +\ddanger Plain \MF's `^{intersectionpoint}' macro is defined by a +@secondarydef@ because it is analogous to `^{intersectiontimes}', which +occurs at the same level (namely the secondary~$\rightarrow$~tertiary level): +\begindisplay +@secondarydef@ $p$ intersectionpoint $q$ $=$\cr +\quad @begingroup@ ^@save@ $"x\_","y\_"$; \ + $("x\_","y\_")=p$ intersectiontimes $q$;\cr +\quad @if@ $"x\_"<0$: ^@errmessage@(|"The paths don't intersect"|); + \ $(0,0)$\cr +\quad @else@: .5[point "x\_" of $p$, + point "y\_" of $q$] @fi@ @endgroup@ @enddef@.\cr +\enddisplay +Notice that ^@begingroup@ and ^@endgroup@ are necessary here; they aren't +inserted automatically as they would have been in a @vardef@. + +\ddangerexercise Define a `^{transum}' macro operation that yields +the ^{sum} of two ^{transforms}. \ (If $t_3=t_1$~transum~$t_2$, then +$z$~transformed~$t_3=z$~transformed~$t_1+z$~transformed~$t_2$, +for~all~pairs~$z$.) +\answer |secondarydef t transum tt =|\parbreak +| begingroup save T; transform T;|\parbreak +| for z=origin,up,right:|^^"origin"\parbreak +| z transformed t + z transformed tt = z transformed T; endfor|\parbreak +| T endgroup enddef.| + +\ddanger \looseness=-1 +Now we've covered all the types of \<definition>, and it's time to +take stock and think about the total picture. \MF's ^{mastication} process +converts an input file into a long sequence of tokens, as explained in +Chapter~6, and its digestive processes work strictly on those tokens. +When a symbolic token is about to be digested, \MF\ looks up the token's +current meaning, and in certain cases \MF\ will expand that token into +a sequence of other tokens before continuing; this ``^{expansion process}'' +applies to macros and to @if@ and~@for@, as well as to certain other +special primitives that we shall consider momentarily. Expansion +continues until an unexpandable token is found; then the ^{digestion process} +can continue. Sometimes, however, the expansion is not carried out; for +example, after \MF\ has digested a @def@ token, it stops all expansion until +just after it reaches the corresponding @enddef@. A complete list of +all occasions when tokens are not expanded appears later in this chapter. + +\ddanger Let's consider all the tokens that cause expansion to occur, +whenever expansion hasn't been inhibited:\enddanger + +\nobreak\medskip +\textindent\bull Macros. When a macro is expanded, \MF\ first reads and +evaluates the arguments (if any), as already explained. +\ (Expansion continues while @expr@ and @suffix@ arguments are +being evaluated, but it is suppressed within @text@ arguments.) \ +Then \MF\ replaces the macro and its arguments by the replacement text. + +\smallbreak +\textindent\bull ^{Conditions}. When `^@if@\kern1pt' is expanded, \MF\ +reads and evaluates the boolean expression, then skips ahead, if necessary, +until coming to either `^@fi@' or a condition that's true; then it will +continue to read the next token. When `^@elseif@\kern1pt' or `^@else@' +or `@fi@' is expanded, a conditional text has just ended, so \MF\ +skips to the closing `@fi@' and the expansion is empty. + +\smallbreak +\textindent\bull ^{Loops}. When `^@for@' or `^@forsuffixes@' or +`^@forever@' is expanded, \MF\ reads the specifications up to the colon, +then reads the loop text (without expansion) up to the @endfor@. +Finally it rereads the loop text repeatedly, with expansion. When +`^@exitif@\kern1pt' is expanded, \MF\ evaluates the following boolean +expression and throws away the semicolon; if the expression proves +to be true, the current loop is terminated. + +\smallbreak +\textindent\bull ^@scantokens@ \<string primary>. When `@scantokens@' +is expanded, \MF\ evaluates the following primary expression, which +should be of type @string@. This string is converted to tokens by the +rules of Chapter~6, as if +it had been input from a file containing just one line of text. + +\smallbreak +\textindent\bull ^@input@ ^\<filename>. When `@input@' is expanded, +the expansion is null, but \MF\ prepares to read from the specified +file before looking at any more tokens from its current source. +A \<filename> is subject to special restrictions explained on the +next page. + +\smallbreak +\textindent\bull ^@endinput@. When `@endinput@' is expanded, the +expansion is null. But the next time \MF\ gets to the end of an +input line, it will stop reading from the file containing that line. + +\smallbreak +\textindent\bull ^@expandafter@. When `@expandafter@' is expanded, +\MF\ first reads one more token, without expanding it; let's +call this token~$t$. Then \MF\ reads the token that comes after~$t$ +(and possibly more tokens, if that token takes an argument), +replacing it by its expansion. Finally, \MF\ puts~$t$ back in front +of that expansion. + +\nobreak\smallskip +\textindent\bull ^^{backslash} |\|. When `|\|' is expanded, the +expansion is null, i.e., empty. + +\ddanger The syntax for \<filename> is not standard in \MF\!, because +different operating systems have different conventions. You should +ask your local system wizards for details on just how they have +decided to implement ^{file names}. The situation is complicated by +the fact that \MF's process of converting to tokens is irreversible; +for example, `|x01|' and `|x1.0|' both yield identical sequences +of tokens. Therefore \MF\ doesn't even try to convert a file name +to tokens; an ^|input| operation must appear only in a text file, not +in a list of tokens like the replacement text of a macro! \ (You can get +around this restriction by saying +\begindisplay +^@scantokens@ |"input foo"| +\enddisplay +or, more generally, +\begindisplay +^@scantokens@ (|"input "| \& "fname") +\enddisplay +if "fname" is a string variable containing the \<filename> you want to +input.) \ Although file names have nonstandard syntax, a sequence of six +or fewer ordinary letters and/or digits should be a +file name that works in essentially the same way on all installations of +\MF\!\null. Uppercase letters are considered to be distinct from their +lowercase counterparts, on many systems. + +\ddanger Here now is the promised list of all cases when expandable +tokens are not expanded. Some of the situations involve primitives +that haven't been discussed yet, but we'll get to them eventually. +Expansion is suppressed at the following times:\enddanger + +\nobreak\medskip\item\bull +When tokens are being deleted during error recovery (see Chapter~5). + +\smallskip\item\bull +When tokens are being skipped because conditional text is being ignored. + +\smallskip\item\bull +When \MF\ is reading the definition of a macro. + +\smallskip\item\bull +When \MF\ is reading a loop text, or the symbolic token that +immediately follows @for@ or @forsuffixes@. + +\smallskip\item\bull +When \MF\ is reading the @text@ argument of a macro. + +\smallskip\item\bull +When \MF\ is reading the initial symbolic token of a \<declared variable> +in a type declaration. + +\smallskip\item\bull +When \MF\ is reading the symbolic tokens to be defined by ^@delimiters@, +^@inner@, ^@let@, ^@newinternal@, or ^@outer@. + +\smallskip\item\bull +When \MF\ is reading the symbolic tokens to be shown by ^@showtoken@ +or ^@showvariable@. + +\smallskip\item\bull +When \MF\ is reading the symbolic tokens to be saved by ^@save@. + +\smallskip\item\bull +When \MF\ is reading the token after ^@expandafter@, ^@everyjob@, +or the `$=$' following @let@. + +\medskip\noindent +The expansion process is not suppressed while reading the suffix that +follows the initial token of a \<declared variable>, not even in a +\<vardef heading>. + +\endchapter + +% quam oppressis, qui novas res moliebantur, ... +The two lieutenants, +Fonteius Capito in Germany, +% in Germania, Fonteio Capitone; +and Claudius Macro in Africa, +% in Africa, Clodio Macro, legatis. +who opposed his advancement, +were put down. +\author ^{SUETONIUS}, % + {\sl Sergius Sulpicius Galba\/} (c.\thinspace125 A.D.) % chapter 11 +% from the translation by Alexander Thomson +% (he says Macer, not Macro, but other translators call this man Macro) + +\bigskip + +By introducing macro instructions in the source language, +the designer can bring about the same ease of programming +as could be achieved by giving the computer +a more powerful operation list than it really has. +But naturally, one does not get the same advantages +in terms of economy of memory space and computer time +as would be obtained if the more powerful instructions +were really built into the machine. +\author O. ^{DOPPING}, {\sl Computers \& Data Processing\/} (1970) % ch19 p312 + +\eject + \beginchapter Chapter 21. Random\\Numbers + +\newcount\n \n=93 \def\nextn{\global\advance\n1 \rand\char\n}% +\def\threenextn{\nextn&\nextn&\nextn}% +It's fun to play games with +{\nextn\nextn\nextn\kern-.23333pt\nextn\nextn\kern-.55555pt\nextn\nextn\nextn} +by writing programs that incorporate +an element of ^{chance}. You can generate unpredictable shapes, and +you can add patternless perturbations to break up the rigid symmetry that +is usually associated with mathematical constructions. + Musicians who use computers to +synthesize their compositions have found that ^{music} has more ``life'' if +its rhythms are slightly irregular and offbeat; perfect 1--2--3--4 pulses +sound pretty dull by contrast. The same phenomenon might prove to +be true in typography. + +\MF\ allows you to introduce controlled indeterminacy in two ways: +(1)~`^{uniformdeviate}~$t$' gives a number~$u$ that's randomly distributed +between 0 and~$t$; \ (2)~`^{normaldeviate}' gives a ^{random number}~$x$ +that has the so-called normal distribution with mean zero and variance one. + +\danger More precisely, if $t>0$ and $u=\null$uniformdeviate~$t$, we will +have $0\le u<t$, and for each fraction $0\le p\le1$ we will have +$0\le u<pt$ with approximate probability~$p$. If $t<0$, the results are +similar but negated, with $0\ge u>t$. Finally if $t=0$, we always have +$u=0$; this is the only case where $u=t$ is possible. + +\danger A normaldeviate, $x$, will be positive about half the time and +negative about half the time. Its distribution is ``^{bell-shaped}'' in +the sense that a particular value $x$ occurs with probability roughly +proportional to $e^{-x^2/2}$; the graph of this function looks something +like a bell. The probability is about 68\% that $\vert x\vert<1$, +about 95\% that $\vert x\vert<2$, and about 99.7\% that $\vert x\vert<3$. +It's a pretty safe bet that $\vert x\vert<4$. + +Instead of relying on mathematical formulas to explain this random +behavior, we can actually see the results graphically by letting \MF\ +draw some ``^{scatter plots}.'' Consider the following program, which +draws a $10\pt\times10\pt$ square and puts 100 little dots inside it: +\begindisplay +@beginchar@$\,(@incr@ "code",10"pt"\0,10"pt"\0,0)$;\cr +@pickup@ @pencircle@ scaled .3"pt"; \ @draw@ "unitsquare" scaled $w$;\cr +@pickup@ @pencircle@ scaled 1"pt";\cr +@for@ $k=1$ @upto@ 100:\cr +\quad @drawdot@(uniformdeviate $w,\,$uniformdeviate $w$); + \ @endfor@ @endchar@.\cr +\enddisplay +The resulting ``characters,'' if we repeat the experiment ten times, +\n=-1 look like~this: +\begindisplay +\threenextn&\threenextn&\threenextn&\nextn\rm\quad. +\enddisplay +And if we replace `uniformdeviate $w$' by `$.5w+w/6\ast\null$normaldeviate', +we get +\begindisplay +\threenextn&\threenextn&\threenextn&\nextn\rm\quad. +\enddisplay +Finally, if we say `@drawdot@(uniformdeviate $w,\,.5w+w/6\ast\null +$normaldeviate)' the results are a mixture of the other two cases: +\begindisplay +\threenextn&\threenextn&\threenextn&\nextn\rm\quad. +\enddisplay + +\exercise Consider the program fragment `@if@ uniformdeviate$\,1\kern-1pt< +\kern-1pt1/3$:\ +"case\_a" @else@:~"case\_b"~@fi@'\kern-.2pt. True or false: +"case\_b" will occur about three times as often as "case\_a". +\answer False; about twice as often (2/3 versus 1/3). + +\exercise \MF's uniformdeviate operator usually doesn't give you an integer. +Explain how to generate random integers between 1 and~$n$, in such a way +that each value will be about equally likely. +\answer |1+floor uniformdeviate n|. + +\exercise What does the formula `(uniformdeviate 1)[$z_1,z_2$]' represent? +\answer A random point on the straight line segment from $z_1$ to $z_2$. +\ (The point $z_1$ itself will occur with probability about 1/65536; +but point $z_2$ will never occur.) + +\exercise Guess what the following program will produce: +\begintt +beginchar(incr code,100pt#,10pt#,0); +for n:=0 upto 99: + fill unitsquare xscaled 1pt yscaled uniformdeviate h + shifted (n*pt,0); endfor endchar. +\endtt +\answer A random ``^{skyline}'' texture, $100\pt$ wide $\times$ $10\pt$ tall: +{\rand\char127} The density decreases uniformly as you go up in altitude. + +\dangerexercise And what does this puzzle program draw? +\begintt +beginchar(incr code,24pt#,10pt#,0); +numeric count[]; +pickup pencircle scaled 1pt; +for n:=1 upto 100: + x:=.5w+w/6*normaldeviate; + y:=floor(x/pt); + if unknown count[y]: count[y]:=-1; fi + drawdot(x,pt*incr count[y]); endfor endchar. +\endtt +\answer A more-or-less bell-shaped ^{histogram}: {\rand\char126} + +\danger Let's try now to put more ``life'' in the \MF\ ^{logo}, by +asking Lady Luck to add small perturbations to each of the key points. +First we define "noise", +\begindisplay +@vardef@ "noise" $=$ normaldeviate$\null\ast"craziness"$ @enddef@; +\enddisplay +the ^"craziness" parameter will control the degree of haphazard variation. +\rightfig 21a ({240\apspix} x {216\apspix}) ^-20pt +Then we can write the following program for the logo's `{\manual n}': +\begindisplay +@beginlogochar@\thinspace(|"N"|$,15)$;\cr +$x_1="leftstemloc"+"noise"$;\cr +$x_2="leftstemloc"+"noise"$;\cr +$w-x_4="leftstemloc"+"noise"$;\cr +$w-x_5="leftstemloc"+"noise"$;\cr +$"bot"\,y_1="noise"-"o"$;\cr +$"top"\,y_2=h+o+"noise"$;\cr +$y_3=y_4+"ygap"+"noise"$;\cr +$"bot"\,y_4="noise"-"o"$;\cr +$"top"\,y_5=h+o+"noise"$;\cr +$z_3="whatever"[z_4,z_5]$;\cr +@draw@ $z_1\dashto z_2\dashto z_3$; \ +@draw@ $z_4\dashto z_5$; \ @labels@$(1,2,3,4,5)$; \ @endchar@. +\enddisplay +The illustration here was drawn with $"craziness"=0$, so there was no noise. + +\danger Three trials of the $9\pt$ `{\manual n}' with $"craziness"=.1"pt"$ +gave the following results: +\displayfig 21b\&c\&d (195\apspix) +And here's what happens if you do similar things to all the +letters of \MF\!, with "craziness" decreasing from $.45"pt"$ to zero in +steps of $.05"pt"$: +\begindisplay \global\advance\n by 8 +% we haven't room for craziness .5! +%\nextn\nextn\nextn\kern-.23333pt\nextn\nextn\kern-.55555pt\nextn\nextn\nextn\cr +\nextn\nextn\nextn\kern-.23333pt\nextn\nextn\kern-.55555pt\nextn\nextn\nextn\cr +\nextn\nextn\nextn\kern-.23333pt\nextn\nextn\kern-.55555pt\nextn\nextn\nextn\cr +\nextn\nextn\nextn\kern-.23333pt\nextn\nextn\kern-.55555pt\nextn\nextn\nextn\cr +\nextn\nextn\nextn\kern-.23333pt\nextn\nextn\kern-.55555pt\nextn\nextn\nextn\cr +\nextn\nextn\nextn\kern-.23333pt\nextn\nextn\kern-.55555pt\nextn\nextn\nextn\cr +\nextn\nextn\nextn\kern-.23333pt\nextn\nextn\kern-.55555pt\nextn\nextn\nextn\cr +\nextn\nextn\nextn\kern-.23333pt\nextn\nextn\kern-.55555pt\nextn\nextn\nextn\cr +\nextn\nextn\nextn\kern-.23333pt\nextn\nextn\kern-.55555pt\nextn\nextn\nextn\cr +\nextn\nextn\nextn\kern-.23333pt\nextn\nextn\kern-.55555pt\nextn\nextn\nextn\cr +{\manual METAFONT}\cr +\enddisplay + +\danger Every time you run a program that refers to random numbers, +you'll get different results, because \MF\ uses the date and time of day +to change its generator. This unpredictable behavior +is normally what you want, but it can be troublesome if your +program draws a lovely shape that you'd like to see again. +Or perhaps one of your runs will uncover a program bug; you won't be able to +diagnose the problem, because it probably won't recur! +The solution is to say +\begindisplay +^@randomseed@ $:=$ \<numeric expression> +\enddisplay +and to remember the value of that numeric expression. \ (The value +will automatically be recorded in the transcript file of your run.) \ +You will get the same sequence of uniform and normal deviates on +any two runs that begin with the same @randomseed@, because \MF's +numbers are only ``pseudo-random.'' + +\endchapter + +% En musiker, som jag k\"ande, roade sig med +A musician whom I knew amused himself +% att st\"amma sitt piano hur som helst utan rim och reson. +%by tuning his piano arbitrarily, without any rhyme or reason. +by tuning his piano haphazardly, without any rhyme or reason. +% D\"arefter spelade han Beethovens Sonate path\'etique utantill. +Afterwards he played ^{Beethoven}'s\/ {\rm Sonate Path\'etique} by heart. +% Det var en otrolig fr\"ojd att h\"ora ett gammlt stycke leva upp igen. +It was an unbelievable delight to hear an old piece come back to life. +% Jag hade h\"ort denna sonat spelas under tjugo \aa r, st\"andigt +%^^{beauty} I had heard this sonata for twenty years, +^^{beauty} How often had I previously heard this sonata, always the same way, +% utan hopp att se den utvecklas; fixerad, of\"orm\"ogen att n\aa\ l\"angre. +%never dreaming that it was capable of being developed further. +never dreaming that it was capable of being developed further! +% utan hopp att se den utvecklas; fixerad, of\"orm\"ogen att n\aa\ l\"angre. +\author AUGUST ^{STRINDBERG}, {\sl Chance in Artistic Creation} (1894) +% The original was in French, but I was unable to locate anything +% but the above Swedish translation, from Modern Museet catalog 28 (1962) +% Nye Konstriktningar! eller Slumpen i det konstn\"arliga skapandet +% [New Directions in Art! or, Chance in Artistic Creation] +% 2009.10.21 here's the original French!: +% J'ai connu un musicien qui se plaisait \'a accorder son piano +% au petit bonheur, sans rime ni raison. Puis il jouait par c{\oe}ur +% la {\it Sonate path\'etique\/} de Beethoven, et c'\'etait une +% jouissance incroyable d'entendre ce vieux morceau se rajeunir. +% Que de fois je 'lavais entendu ex\'ecuter devant moi, cetta sonate, +% toujours le m\^eme, fix\'ee, sans esp\'erance de la voir se +% d\'evelopper en d'autres sons, jamais, incapable d'une \'evolution\thinspace! + +\bigskip + +[Education] must lead us from chance and arbitrariness +to rational clarity and intellectual order. +\author L. ^{MIES VAN DER ROHE}, {\sl Inaugural Address\/} (1938) + +\eject + \beginchapter Chapter 22. Strings + +\MF\ is not a word processor, but a \MF\ programmer can process words and +other short strings of symbols in rudimentary ways. Strings can help +explain what a program is doing; for example, the |io.mf| file of +Chapter~5 mentions |"The|~|letter|~|O"| as a title that should appear +on proofsheets, and it also says |"O"| in order to identify the +position of a character in the output font. + +Chapter 6 points out that a \<string token> is any sequence of +characters enclosed in double-quote (|"|) marks, except that you're +not allowed to use the double-quote character itself in this way. +If you need that character, plain \MF\ provides it in a string of +length~1 called ^"ditto". Thus +\begintt +"A string expression can contain a `" & ditto & "' mark" +\endtt +even though a \<string token> cannot. + +A string expression can be used all by itself as a statement, just as +if it were an equation or declaration or command. Such a statement is called +a ^\<title>, provided that it is immediately followed by a~`|;|'. +If ^"tracingtitles"$\null>0$ when a title is encountered, \MF\ +will type the title on the user's terminal. If ^"proofing"$\null>0$ +when a title is encountered, \MF\ will copy the title into the output +file, so that it can be put onto proofsheets by postprocessors such +as the ^|GFtoDVI| program described in Appendix~H. + +\danger Appendix H explains how to specify the strings that are used as +^{labels} for the key points on proofsheets. + +\ddanger Here's the full syntax for string expressions. All of the +activity except for ^{concatenation} (`\&') ^^{ampersand} takes +place at the primary level: +\beginsyntax +<string primary>\is<string token> + \alt<string variable> + \alt[(]<string expression>[)] + \alt[begingroup]<statement list><string expression>[endgroup] + \alt[jobname] + \alt[readstring] + \alt[str]<suffix> + \alt[char]<numeric primary> + \alt[decimal]<numeric primary> + \alt[substring]<pair primary>[of]<string primary> +<string secondary>\is<string primary> +<string tertiary>\is<string secondary> +<string expression>\is<string tertiary> + \alt<string expression>[\&]<string tertiary> +\endsyntax +The new features here are |jobname|, |readstring|, |str|, |char|, +|decimal|, and |substring|; we shall consider each of them in turn. + +\ddanger The name of your job (\kern1pt@jobname@) is the name of the first +file you input, provided that the first line of instructions to \MF\ +(the `^|**|' line or ^{command line}) causes input of some file. +Otherwise the job name is ^|mfput|, as in Experiment~1 of Chapter~5. + +\ddanger When you say `^@readstring@', \MF\ stops and waits for the user +to type a line at the terminal. The value of @readstring@ is the contents +of this line, with trailing spaces eliminated. +\ (You probably should use the @message@ command first, to give the +user a clue about what to type; for example, see the |expr.mf| file +of Chapter~8, which gets its input expressions via @readstring@. +The ^@stop@ macro of Appendix~B makes use of the fact that @readstring@ +halts the computer; it doesn't actually look at the string.) + +\ddanger An arbitrary ^\<suffix> is converted to a string by ^@str@, +using the method by which \MF\ displays suffix arguments in +diagnostic typeouts. Negative subscripts are enclosed in +square brackets; spaces or dots are inserted between tokens whose +characters belong to the same class (according to the table in +Chapter~6). For example, if $n=1$ then `@str@~$x[n]a$' is |"x1a"|; +`@str@~$x\,n\,a$' is |"x.n.a"|. + +\ddanger The result of `^@char@~$n$' is a string of length~1, +representing the character whose ^{ASCII} code is~$n$. +\ (Appendix~C explains this code.) \ The value of~$n$ is first +rounded to the nearest integer, then multiples of~256 are +added or subtracted if necessary until $0\le n<256$; this +defines @char@~$n$ in all cases. + +\ddanger The ^{decimal representation} of a known numeric value~$x$ +is available in string form as `@decimal@~$x$'. If $x$ is negative, +the first character of this string will be~`|-|'. If $x$ is not +an integer, a decimal point will be included, followed by as +many digits as are necessary to characterize the value. \ (These +conventions are the same as those illustrated in the example +outputs of Chapter~8.) + +\ddanger The rules for ^{substring} are like the rules for ^{subpath} +in Chapter~14. \MF\ thinks of a string as if its characters were +written in the squares of a piece of ^{graph paper}, between +coordinates $x=0$ and $x=n$, where $n$~is the length of the string. +In simple cases, substring$\,(a,b)$ +then refers to the characters between $x=a$ and~$x=b$. The +rules for the general case are slightly more involved: If $b<a$, +the result will be the ^{reverse} of substring$\,(b,a)$. +Otherwise $a$ and~$b$ are replaced respectively by +$\max\bigl(0,\min(n,\round a)\bigr)$ and +$\max\bigl(0,\min(n,\round b)\bigr)$; this leads to the simple +case $0\le a\le b\le n$ described above, when the resulting +string has length $b-a$. + +\ddanger Strings can be converted into numbers, although Chapter~8 +didn't mention this fact in its syntax for \<numeric primary>. The +primitive operations are +\begindisplay +{\tt ASCII}\thinspace\<string primary>\alt +\thinspace{\tt oct}\thinspace\<string primary>\alt +\thinspace{\tt hex}\thinspace\<string primary> +\enddisplay +where `^{ASCII}' returns the ASCII code of the first character of the +string, `^{oct}' computes an integer from a string representing +^{octal notation} (radix~8), and `^{hex}' computes an integer from +a string representing ^{hexadecimal notation} (radix~16). For example, +\begindisplay +ASCII |"100"| $=$ 49;\qquad oct |"100"| $=$ 64;\qquad hex |"100"| $=$ 256. +\enddisplay +Several exceptional conditions need to be mentioned: +(1)~ASCII~|""|~$=-1$; otherwise ASCII yields an integer between 0 and~255. +\ (2)~The characters in the string argument to `oct' must all be +digits in the range |0|--|7|. +\ (3)~The characters in the string argument to `hex' must all be +digits in the range |0|--|9|, |A|--|F|, or |a|--|f|. +\ (4)~The number that results from `oct' or `hex' must be less than 4096. +Thus, `oct~|"7777"|' and `hex~|"FFF"|' are the maximum legal values. + +\ddangerexercise Under what circumstances is (a) ASCII @char@ $n=n$? +\ (b)~@char@~ASCII~$s=s$? +\answer (a) Iff $n$ is an integer between 0 and 255. +(b) Iff $s$ is a string of length~1. + +\ddangerexercise Why are there primitive operations to convert from +strings to numbers assuming octal notation and hexadecimal notation, +but not assuming decimal notation? +\answer Whoever says that there's no such primitive operation has +forgotten about @scantokens@. + +\ddangerexercise Write an "octal" macro that converts a nonnegative +integer to an octal string. +\answer |vardef octal primary n =|\parbreak +| save m,s; m:=abs round n; string s; s=decimal(m mod 8);|\parbreak +| forever: m:=m div 8; exitif m=0;|\parbreak +| s:=decimal(m mod 8) & s; endfor|\parbreak +| s enddef;|\par\nobreak\medskip\noindent +`|str[m mod 8]|' could also be used instead of `|decimal(m mod 8)|'. + +\ddanger A ^\<message command> allows you to communicate directly +or indirectly with the user. It has the general syntax +\beginsyntax +<message command>\is<message op><string expression> +<message op>\is[message]\alt[errmessage]\alt[errhelp] +\endsyntax +If you say `@message@~$s$', the characters of $s$ will be typed on the +terminal, at the beginning of a new line; `@errmessage@~$s$' is +similar, but the string will be preceded by |"! "| and followed +by~|"."|, followed by lines of context as in \MF's normal error messages. +If the user asks for ^{help} after an @errmessage@ error, +the most recent @errhelp@ string will be typed (unless it was empty). + +\ddanger \MF\ doesn't allow you to have an array of different +macros $m[i]$; but you can have an array of strings that have +macro-like behavior, via ^@scantokens@. The ^@mode\_def@ construction +of Appendix~B exploits this idea. + +\endchapter + +Many other useful Practises +mecanicks perform by this Theo. +as the finding the length of strings. +\author WILLIAM ^{ALINGHAM}, {\sl Geometry Epitomized\/} (1695) + % p51 acc to OED; but British Library doesn't have this edition! + % they have 1701 `An epitome of geometry', as does Yale + +\bigskip + +Forgive me, if my trembling Pen displays +What never yet was sung in mortal Lays. +But how shall I attempt such arduous String? +\author JAMES ^{THOMSON}, {\sl The Castle of Indolence\/} (1748) + % canto 1 verse 31 + +\eject + \beginchapter Chapter 23. Online\\Displays + +How do you get pictures to appear on your screen? Plain \MF\ provides +the `^@showit@' command, which displays the ^"currentpicture". +Furthermore you can ask for `^@screenchars@'; this automatically +does a @showit@ at the time of each ^@endchar@. And you can see all +the action by asking for `^@screenstrokes@'; this automatically +does a @showit@ after every @draw@ or @fill@. + +\ddanger The above-described features of plain \MF\ are implemented +from low-level primitive commands, by macros that appear in Appendix~B\null. +At the lowest level, \MF\ obeys commands such as `@display@ +"currentpicture" @inwindow@~1'; there's also an `@openwindow@' +command that defines a correspondence between \MF\ coordinates and +screen coordinates. The syntax is +\beginsyntax +<display command>\is[display]<picture variable>[inwindow]<window> +<window>\is<numeric expression> +<openwindow command>\is[openwindow]<window><window spec> +<window spec>\is<screen place>[at]<pair expression> +<screen place>\is[from]<screen coordinates>[to]<screen coordinates> +<screen coordinates>\is<pair expression> +\endsyntax +A \<window> is an integer between 0 and 15, inclusive; it represents +one of sixteen ``windows'' or ``portholes'' that \MF\ provides +between its pictures and the outside world. The \<window> mentioned +in a @display@ command must previously have been ``opened'' by +an @openwindow@ command. + +\ddanger \MF's windows should not be confused with the so-called +windows provided by many modern operating systems. If you have +such a system, you'll probably find that all of \MF's pictorial +output appears in one operating-system window, and all of its +terminal I/O appears in another, and you might be running other +jobs (like the system editor) in another. \MF's windows are not so +fancy as this; they are just internal subwindows of one big +picture window. + +\ddanger The command `@openwindow@ $k$ @from@ $(r_0,c_0)$ @to@ $(r_1,c_1)$ +@at@~$(x,y)$' associates a rectangular area of the user's screen +(or of the user's big picture window) with pixels in \MF's coordinate +system. All of the numbers in this command (namely $k$, $r_0$,~$c_0$, +$r_1$,~$c_1$, $x$, and~$y$) are rounded to the nearest integer if they +aren't integers already. Furthermore $r_0$ is replaced by +$\max\bigl(0,\min("maxr",r_0)\bigr)$ and $r_1$ is replaced by +$\max\bigl(r_0,\min("maxr",r_1)\bigr)$, where "maxr" is the maximum +number of rows on the screen; similar adjustments are made to $c_0$ +and~$c_1$. The two $(r,c)$ values are row and column +numbers on the screen; the topmost row is conventionally taken to be +row zero, and the leftmost column is taken to be column zero. +\ (These conventions for screen coordinates are quite different from +the normal ^{Cartesian} coordinate system used everywhere else +in \MF\!, but somehow they seem appropriate when applied to screens.) \ +Point~$(x,y)$ of \MF's raster will be equated to the upper left +corner of the rectangle, i.e., to the upper left corner of the pixel +in screen column~$c_0$ of screen row~$r_0$. The window itself +occupies $r_1-r_0$ rows and $c_1-c_0$ columns. It follows that +the pixel in column~$c_1$ of row~$r_1$ is not in the window itself, +but it is the screen pixel diagonally just below and to the right of the +lower right corner of the window. + +\ddangerexercise What are the \MF\ coordinates of the boundary of +such a window? +\answer Point $(x,y)$ is the upper left corner, ${(x+c_1-c_0,y)}$ is the +upper right corner, ${(x,y-r_1+r_0)}$ is the lower left corner, and +${(x+c_1-c_0,y-r_1+r_0)}$ is the lower right corner. \ (Pixels +outside this rectangle will not be displayed.) + +\danger If you run \MF\ on a system that doesn't support general +bitmap displays, the @display@ and @openwindow@ commands will do +nothing. You'll have to look at hardcopy output, off\/line. +\ (But your \MF\ might run a bit faster.) + +\ddanger The syntax for @display@ insists that you display a +\<picture variable>, not a \<picture expression>; thus, you +can't `@display@ ^@nullpicture@'. Plain \MF\ defines a special +variable ^"blankpicture" that's entirely blank, just so that +you can easily display nothing whenever you like. + +\ddanger A window may be opened any number of times, hence moved +to different locations on the screen. Opening a window blanks the +corresponding screen rectangle as if you had displayed "blankpicture". + +\ddanger The effect of overlapping windows is undefined, because \MF\ +does not always repaint pixels that have remained unchanged between +displays. + +\ddanger Changes to a picture do not change the displays that were +generated from it, until you give another display command explicitly. +Thus, the images emblazoned on your screen might not exist any longer +in \MF's picture memory. + +\ddanger Plain \MF\ has an `@openit@' macro that opens +^"currentwindow"; this variable "currentwindow" is always zero +unless you change it yourself. The @showit@ macro displays +"currentpicture" in "currentwindow"; and it's also designed +to call @openit@---but only the very first time @showit@ is invoked. +This means that the screen normally won't be touched until the moment you +first try to display something. + +\ddanger Appendix E explains how to manage a more elaborate scheme +in which six windows can be used to show how ^{meta-characters} vary +under six different font-parameter settings. The author ^^{Knuth} +used such a six-window system when developing the Computer Modern +typefaces; here is a typical example of what appeared on his +^^{a} terminal when the letter~`a' was being refined: +\displayfig 23 (68mm) + +\ddangerexercise The @openit@ macro in Appendix~B specifies $(-50,300)$ +as the upper left corner point of the window used for showing +all the pictures. This might clip off the bottom of a large character, +if your screen is limited to, say, 360 rows. How could you change +@openit@ so that the character images will be raised 20 rows higher +than they would be in the standard setting? +\answer Redefine @openit@ so that it puts the top left at $(-50,280)$. + +\ddangerexercise Design a `^@new\_window@' routine that allocates +windows 1, 2, \dots,~15. If the user says `|new_window $(u,v)|', +where |$|~is any suffix and |u,v| are pairs of coordinates for +two opposite corners of a rectangle, your macro should map that +rectangle to the next available screen rectangle and open it as +window number |window$|. The allocation should be left to right, +top to bottom; assume that the screen is an infinite rectangle, +^"screen\_cols" wide. +\answer (This routine is due to John ^{Hobby}.) +\begintt +newinternal n_windows; % the number of windows allocated so far +newinternal screen_bot; % the first untouched screen row +pair screen_corner; % the upper left corner of next window +def wipescreen = % do this to initialize or reinitialize + for i:=1 upto n_windows: display blankpicture inwindow i; endfor + n_windows := screen_bot := 0; screen_corner := origin enddef; +wipescreen; +vardef new_window@#(expr u,v) = save r,c,up_lft; pair up_lft; + if n_windows=15: errmessage "No more windows left" + else: window@# := incr n_windows; + up_lft = (min(xpart u,xpart v), max(ypart u, ypart v)); + (r,c) = (u+v-2up_lft) rotated 90; + if ypart screen_corner + c > screen_cols: + screen_corner:=(screen_bot,0); fi + openwindow window@# from screen_corner + to screen_corner+(r,c) at up_lft; + screen_bot := max(screen_bot,xpart screen_corner + r); + screen_corner := screen_corner + (0,c) fi; enddef; +\endtt + +\endchapter + +Editing will be done on-line with a display scope and keyboard. +\author RICHARD L. ^{VENEZKY}, in {\sl American Documentation\/} (1968) + % p72; ``Storage, Retrieval, and Editing of Information for a Dictionary'' + +\bigskip + +In future I might be obliged to turn for material to the tube. +\author IGOR ^{STRAVINSKY}, in {\sl Harper's\/} (1970) % April, p112 + % that year he wrote a regular column called Performing Arts + +\eject + \beginchapter Chapter 24. Discreteness\\and Discretion + +Pixel patterns are indistinguishable from continuous curves, when the +pixels are small enough. After all, the human eye is composed of +discrete receptors, and visible light has a finite wavelength. +Our hypothetical ^"luxo" printer of Chapter~11, with its resolution +of 2000 pixels per inch, would surely be able to produce printed +pages of high quality, if it existed; the physical properties of ink +would smooth out all the tiny bumps, obliterating all the evidence that +the letterforms had been digitized. However, it will always be less +expensive to work with devices of lower resolution, and we want the output +of \MF\ to look as good as possible on the machines that we can afford to +buy. The purpose of this chapter is to discuss the principles of +``discreet ^{rounding},'' i.e., to consider the tasteful application of +mathematical techniques by which \MF\ can be made to produce satisfactory +shapes even when the resolution is rather coarse. + +The technical material in this chapter is entirely marked with danger +signs, since careful rounding tends to make \MF\ programs more complex; a +novice user will not wish to worry about such details. On the other hand, +an expert \MF er will take pains to round things properly even when +preparing high-resolution fonts, since the subtle refinements we are about +to discuss will often lead to significantly better letterforms. + +We should realize before we begin that it would be a mistake to +set our hopes too high. Mechanically generated letters that are untouched +by human hands and unseen by human eyes can never be expected to compete +with alphabets that are carefully crafted to look best on a particular +device. There's no substitute for actually looking at the letters +and changing their pixels until the result looks right. Therefore our +goal should not be to make ^{hand-tuning} obsolete; it should rather be +to make hand-tuning tolerable. Let us try to create meta-designs so +that we would never want to change more than a few pixels per character, +say half a dozen, regardless of the resolution. At low resolutions, six +pixels will of course be a significant percentage of the whole, and at higher +resolutions six well-considered pixel changes can still lead to worthwhile +improvements. The point is that if our design comes close enough, a +person with a good bitmap-editing program will be able to optimize an +entire font in less than an hour. This is an attainable goal, if rounding +is done judiciously. + +\danger \MF\ tries to adjust curves automatically, so that they are +well adapted to the ^{raster}, if the internal quantities ^"autorounding" +and/or ^"smoothing" have positive values. \ (Plain \MF\ sets +$"autorounding":=2$ and $"smoothing":=1$, so you generally get these +features unless you turn them off yourself.) \ But all the examples in +this chapter will be generated with $"autorounding":="smoothing":=0$ +unless otherwise mentioned, because this will keep \MF's automatic +mechanisms from interfering with our experiments. We shall discuss the +pros and cons of automatic rounding after we have explored the general +problem in more detail. + +\danger The first thing we need to understand about rounding is \MF's +procedure for ^{digitizing} a path. A path of length~$n$ can be regarded +as a trajectory~$z(t)$ that is traced out as $t$~varies from 0 to~$n$. In +these terms, the corresponding digitized path is most easily described by +the formula `round~$z(t)$' for $0\le t\le n$; each $z(t)$ is rounded to +the nearest point with integer coordinates. For example, if a path goes +through point~$(3.1,5.7)$, its digitization will go through point~$(3,6)$. +The digitized trajectory makes discrete jumps at certain values of $t$, +when round~$z(t)$ hops from one point to another; the two points will be +one pixel apart, and we can imagine that the digitized path traverses the +horizontal or vertical edge between them when it jumps. + +\danger When an ordinary region is being filled, this rule for +digitizing paths boils down to a simple criterion that's easy to +visualize: {\sl A pixel belongs to the digitized region if and only if +its center point lies inside the original undigitized path.} For example, +two versions of Chapter~5's Ionian `{\manual\IOO}' are shown here +at a resolution of 200 pixels per inch, using the characteristics +of ^"lowres" mode in Appendix~B: +\displayfig 24a\&b (190\apspix) +The heavy broken lines are digitized paths, and the pixels inside these +ragged boundaries are those whose centers lie in the shaded regions. + +\danger The `{\manual\IOO}' on the left has digitized well; but the +one on the right has problems, because it was based on curves that +were generated without taking the raster into account. The difference +between these two letters is entirely due to line~8 of the program +in Chapter~5, which says +\begindisplay +"curve\_sidebar" $=$ round $1/18"em"$; +\enddisplay +this equation determines the position of the leftmost and rightmost +edges of the `{\manual\IOO}' before digitization, and it leads to +the nice digitized form in the left-hand example. Without the word +`^{round}', we get the inferior right-hand example, which was +obtained by exactly the same \MF\ program except that "curve\_sidebar" +was set to $1/18"em"$ exactly. One little token---which changed an exact +calculation to an approximate, rounded calculation---made all the difference! + +\danger Curves that are placed in arbitrary positions on +a raster can lead to digital disasters, even though the curves themselves +aren't bad. For example, suppose we take the right-hand example above +and shift it just 0.05 and 0.10 pixels to the right: +\displayfig 24c\&d (190\apspix) +The first shift of 0.05 pixels causes a tiny ^{pimple} to appear +at the right edge; after another small shift the pimple has grown into a +mole, and the left edge has become too ^{flat}. \looseness=-1 + +\danger A designer who is asked to make a digital `O' that is 22 pixels +wide will certainly have pixels in mind when making the design. Therefore +it's not surprising that our program to generate a digital~`O' should +pay attention to actual pixel positions by rounding "curve\_sidebar" as +in this example. We have distorted the infinite-resolution curve +slightly so that it will digitize well, before digitizing it. + +\danger A path $z(t)$ will digitize well if the digitization process doesn't +change it too much; thus, we want $z(t)$ to be essentially the same as +round$\,z(t)$, at all the important places. But what places are ``important''? +Experience shows that the most critical points are those where the path +travels horizontally or vertically, i.e., where it runs parallel to +the raster lines. It's best to arrange things so that a curve becomes +parallel to the raster lines just when it touches or nearly touches those +lines; then it will appear to have the right curvature after digitization. +The worst case occurs when a curve becomes parallel to the raster just +when it's halfway between raster lines; then it gets a pimple or a flat spot. + +\ddanger Diagonal slopes, where a curve has a $\pm45^\circ$ tangent angle, +are also potential sources of unwanted pimples and flats. Similarly, at +higher resolutions it is sometimes possible to detect small glitches +when a curve travels with slopes of $\pm1/2$ or $\pm2/1$. Rational +slopes $m/n$ where $m$ and~$n$ are small integers turn out to be +somewhat dangerous. But diagonals are of secondary importance; horizontal +and vertical slopes lead to more severe problems. + +\danger These considerations suggest a simple general principle for adapting +the outlines of shapes to be digitized: {\sl If you know that the outline +will have a vertical tangent at some point, round the $x$~coordinate to an +integer and leave the $y$~coordinate unchanged. If you know that the +outline will have a horizontal tangent at some point, round the +$y$~coordinate to an integer and leave the $x$~coordinate unchanged.} + +\ddanger Incidentally, the horizontal tangent points in our `{\manual\IOO}' +examples were taken care~of by the fact that `^@define\_corrected\_pixels@' +makes the ^{overshoot} parameter~$o$ nearly an integer, together with +the fact that ^@beginchar@ makes $h$ an integer. If the $y$~coordinates +had not been rounded at the horizontal tangent points, +our bad examples would have looked even worse. + +\danger Before we go further into the study of rounding, we had better +face up to a technicality that's sometimes important: We said that the +pixels of a digitized region are those whose centers lie inside the +undigitized region; but this rule is vague about what happens when the +centers happen to fall precisely on the undigitized boundary. Similarly, +when we said that round$\,z(t)$ jumps from one point to an adjacent point, +we ignored the fact that a curve such as $z(t)=(t,t)$ actually +jumps from $(0,0)$ to $(1,1)$ when it is rounded as $t$ passes 1/2; +those points are not adjacent. +\MF\ skirts both of these problems in an interesting way: +It shifts all of its paths to the +right by an infinitesimal amount~$\delta$, and it also shifts them +upward by an even smaller +infinitesimal amount~$\delta\epsilon$, so that no path actually +touches a pixel center. Here $\delta$ and~$\epsilon$ are positive numbers +that are chosen to be so small that their actual values don't matter. +For example, the path $z(t)=(t,t)$ becomes $(t+\delta,t+\delta\epsilon)$, +which jumps from $(0,0)$ to $(1,0)$ to $(1,1)$ because it momentarily +rounds to $(1,0)$ when $t=1/2-2\delta\epsilon$. + +\danger Points of the form $(m+1/2,n+1/2)$, where $m$ and $n$ are integers, +lie in the centers of their pixels. They are called ``ambiguous'' points +because we can't round them to the nearest integer neighbor without +deciding which of four adjacent points is to be considered the nearest. +If we imagine taking a curved outline and shifting it slowly to the +right, the digitized image makes abrupt transitions when the outline +passes over an ^{ambiguous point}. When a path comes near an ambiguous +point, the path is farthest away from its digitization. Thus the +ambiguous points are points of instability, and digitizing works best +when paths don't get too close to them. + +\danger Let's consider now what happens when we ^@draw@ with a pen, +instead of filling an outline. It may seem that the simplest possible @draw@ +command would be something like this: +\begindisplay +@pickup@ @pencircle@; \ @draw@ $(0,0)\to(10,0)$; +\enddisplay +what could be easier? But a closer look shows that this is actually +about the worst case that could be imagined! A circular pen of +diameter~1 that goes from $(0,0)$ to $(10,0)$ has upper and lower +boundaries that go from $(0,\pm1/2)$ to $(10,\pm1/2)$, +and both of these boundaries run smack through lots of +ambiguous points. \MF\ has to decide whether to fill the row of pixels +with $0\le y\le1$ or the lower row with $-1\le y\le0$, neither of which is +centered on the given line. According to the rule stated earlier, \MF\ +shifts the path very slightly to the right and very, very slightly up; +thus the pixels actually filled are bounded by +$(0,0)\dashto(10,0)\dashto(10,1)\dashto(0,1)\dashto\cycle$. + +\dangerexercise Continuing this example, what pixels would have been +filled if the path had been `$(0,0)\to(10,-"epsilon")$'\thinspace? +\answer The entire path now has negative $y$~coordinates except at +point~$(0,0)$, so the outline of the filled region is + $(0,-1)\dashto(10,-1)\dashto(10,0)\dashto(0,0)\dashto(0,1) +\dashto\cycle$. \ $\bigl($Notice that the +digitized outline actually goes up to $(0,1)$ before coming straight down +again. This fills no pixels, but \MF\ correctly puts ``cancelling'' edges +from $(0,0)$ to $(0,1)$ and back to $(0,0)$ into its edge structure, because the +point $(0,.5)$ is on the boundary and rounds to $(0,1).\bigr)$ + +\danger In general when we @draw@ with a fixed pen, good digitizations +depend on where the edges of the pen happen to fall, not on the +path followed by the pen's center. Thus, for example, if the path we're +drawing has a vertical tangent at point~$z_1$, we don't necessarily +want $x_1$~to be an integer; we want "lft"$\,x_1$ and "rt"$\,x_1$ +to be integers. If there's a horizontal tangent at~$z_2$, we want +"top"$\,y_2$ and "bot"$\,y_2$ to be integers. The pens created by +^@pencircle@ always have the property that $("lft"\,x)-("rt"\,x)$ +and $("top"\,y)-("bot"\,y)$ are integers; hence both edges will +be in good or bad positions simultaneously. + +\danger Suppose that we want $x_1$ to be approximately equal to~$\alpha$, +and we also want it to be at a good place for vertical tangents with respect +to the pen that has currently been picked up. One way to define $x_1$ is to say +\begindisplay +$"lft"\,x_1=\round("lft"\,\alpha)$; +\enddisplay +this does the right thing, because it makes "lft"$\,x_1$ an integer and +it also makes $x_1\approx\alpha$. Similarly, to make~$y_2\approx\beta$ +good for horizontal tangents, we can say +\begindisplay +$"top"\,y_2=\round("top"\,\beta)$. +\enddisplay +Such operations occur frequently in practice, so plain \MF\ provides +^^"good.x" ^^"good.y" ^^{gumdrop} convenient abbreviations: We can say simply +\begindisplay +$x_1="good.x"\,\alpha$; \ $y_2="good.y"\,\beta$ +\enddisplay +instead of using indirect equations for $"lft"\,x_1$ and $"top"\,y_2$. + +\danger Let's look one last time at the letters of the \MF\ logo, in +order to make them round properly. Chapter~11 describes a file ^|logo.mf| +that draws the seven characters, but we can improve the results by +making pixel-oriented refinements. In the first place, we can replace +the command +\begindisplay +@define\_pixels@($s,u,"xgap","ygap","leftstemloc","barheight"$) +\enddisplay +by something better: Looking at the uses of these ad hoc dimensions, +we see that ^"xgap" and ^"ygap" ought to be integers; ^"leftstemloc" +should be a "good.x" value for "logo\_pen"; and ^"barheight" should +be a "good.y" value. Therefore we say +\begindisplay +^@define\_pixels@$(s,u)$;\cr +^@define\_whole\_pixels@$("xgap","ygap")$;\cr +^@define\_good\_x\_pixels@$("leftstemloc")$;\cr +^@define\_good\_y\_pixels@$("barheight")$;\cr +\enddisplay +these commands, provided by plain \MF\!, will do the right thing. +\ (The "logo\_pen" should be picked up before the last two commands are +given.) \ These few changes, and a change to the `{\manual m}', suffice to +fix all the letters except `\kern1pt{\manual j}\kern1pt'. + +\dangerexercise The program for \MF's `{\manual m}' ^^{O} +appears in Chapter~18. What changes would you suggest to make +it digitize well? +\answer The horizontal tangents are already taken care of by the equations +$"top"\,y_1=h+o$ and $"bot"\,y_4=-o$, so nothing needs to be done there. +We should, however, say +\begindisplay +$x_2=w-x_3="good.x"(1.5u+s)$ +\enddisplay +so that vertical tangents will occur in good places. Since $w$~is an +integer, and since the "logo\_pen" has left-right symmetry, +$w-x_3$ will be good if and only if $x_3$ is. + +\danger The `\kern1pt{\manual j}\kern1pt' ^^{T} presents a new problem, +because we want it to be symmetric between left and right. If the pen +breadth is odd, we want the character width~$w$ to be odd, so that there +will be as many pixels to the left of the stem as there are to the right. +If the pen breadth is even, we want $w$ to be even. Therefore we have a +50-50 chance of being unhappy with the value of~$w$ that is computed by +^@beginchar@. + +\dangerexercise Prove that the value of $w$ is satisfactory for +`\kern1pt{\manual j}\kern1pt' with respect to the "logo\_pen" if and +only if $.5w$ is a good $x$~value for vertical strokes. +\answer Let $b$ be the pen breadth. Then $.5w$ is a good $x$ value if and only +if $"lft"\,.5w$ is an integer; but $"lft"\,.5w=.5w-.5b$, and this is an +integer if and only if $w-b$ is even. + +\danger If $w$ is not a good value, we want to replace it by either +$w+1$ or~$w-1$, whichever is closer to the device-independent width +from which $w$ was rounded. For example, if $w$ was rounded to 22 from +the ideal width~21.7, we want to change it to 21 rather than~23. +Plain \MF's ^@change\_width@ routine does this. Hence we have the +following program for `\kern1pt{\manual j}\kern1pt', in place of the +\rightfig 4b ({208\apspix} x {216\apspix}) ^-18pt +simpler version found in exercise 11.\metaT: +\begindisplay +@beginlogochar@(|"T"|$,13)$;\cr +@if@ $.5w<>"good.x"\,.5w$: @change\_width@; @fi@\cr +$"lft"\,x_1=-"eps"$;\cr +$x_2=w-x_1$;\cr +$x_3=x_4=.5w$;\cr +$y_1=y_2=y_3$; \ $"top"\,y_1=h$; \ $"bot"\,y_4=-o$;\cr +@draw@ $z_1\dashto z_2$; \ @draw@ $z_3\dashto z_4$;\cr +@labels@$(1,2,3,4)$; \ @endchar@.\cr +\enddisplay +\decreasehsize 44mm +Chapter 4 said that `\kern1pt{\manual j}\kern1pt' was the simplest of the +seven logo letters, but it has turned out to be the trickiest. + +\restorehsize +\ddanger This program has one unexplained feature. Why was $"lft"\,x_1$ +set to $-"eps"$ instead of zero? The answer requires an understanding +of the pen polygons discussed in Chapter~16. The edges of those polygons +are highly likely to pass through ambiguous points when the center of +the pen has integer or half-integer coordinates. \MF\ shifts paths slightly +to the right and up, in order to resolve ambiguities; therefore if +ambiguous points occur at the left and right edges of the +`\kern1pt{\manual j}\kern1pt', some pixels will be lost at the left but +gained at the right. The constant ^"eps" is 0.00049, which is small but +positive enough that \MF\ will surely notice it. Subtracting "eps" +from~$x_1$ and adding "eps" to~$x_2$ avoids ambiguous edge points and +keeps the result symmetric. + +\ddanger Since the ^{overshoot} `$o$' is always "eps" more than an +integer, it is unnecessary to do anything similar at point~$z_4$; +the equation `$"bot"\,y_4=-o$' is sufficient. + +\ddanger Point $z_3$ in the middle of the `{\manual h}' ^^{M} is in +a satisfactory position because $"bot"\,y_3="ygap"-"o"$. +If $"bot"\,y_3$ were exactly an integer, the~`{\manual h}' would often turn +out to be unsymmetric, because of ambiguous points on the boundary +at~$z_3$. + +\ddangerexercise True or false: If "currentpen" is @pencircle@ xscaled "px" +yscaled~"py", the command `@draw@ $(-"epsilon",0)\to(+"epsilon",0)$' +will produce an image that has both left-right and top-bottom symmetry. +\ (Assume that "autorounding"="smoothing"=0.) +\answer There are no ambiguous points on the outlines of this stroke, +except perhaps on the top and bottom edges; the latter can occur only if +$\round"py"$ is odd. Hence there is always left-right symmetry, but +top-bottom symmetry might fail because of a missing row at the bottom +(e.g., when $"px"="py"=3$). In a case like the `\kern1pt{\manual j}\kern1pt' +we do have both symmetries, because $y_1$ and $x_4$ are in good positions. + +\ddangerexercise The polygon for `^@pencircle@ scaled 3' is an octagon +whose vertices are at the points $(\pm0.5,\pm1.5)$ and $(\pm1.5,\pm0.5)$. +Prove that if you `^@draw@~$(x,y)$' with this pen, the result never has +both top-bottom and left-right symmetry. +\answer No matter where you place the octagon so that it isn't touching +any ambiguous points, exactly seven ambiguous points are inside it; hence +every one-point ^@draw@ fills exactly seven pixels. \ (In fact, +you always get one of the patterns +$\vcenter{\vbox{\offinterlineskip\manual + \hbox{\kern\Blankpix RR}\hbox{RRR}\hbox{\kern\Blankpix RR}\kern1pt}}$, +$\vcenter{\vbox{\offinterlineskip\manual + \hbox{\kern\Blankpix R}\hbox{RRR}\hbox{RRR}\kern1pt}}$, +$\vcenter{\vbox{\offinterlineskip\manual + \hbox{RR}\hbox{RRR}\hbox{RR}\kern1pt}}$, or +$\vcenter{\vbox{\offinterlineskip\manual + \hbox{RRR}\hbox{RRR}\hbox{\kern\Blankpix R}\kern1pt}}$.) + +\ddanger Rounding can also help to position points at which we don't +have horizontal or vertical tangents. For example, consider the +``^{sharp sign}'' or ``^{hash mark}'' character that's drawn by the +\rightfig 24e ({300\apspix} x {320\apspix}) ^-60pt +following program: +\begindisplay +$u\0:={10\over18}"pt"\0$; \ @define\_pixels@$(u)$;\cr +@beginchar@$\,(0,15u\0,{250\over36}"pt"\0,{70\over36}"pt"\0)$;\cr +@pickup@ @pencircle@\cr +\qquad scaled $(.4"pt"+"blacker")$;\cr +$"lft"\,x_1=\round u-"eps"$;\cr +$x_3=x_1$;\cr +$x_2=x_4=w-x_1$;\cr +$y_1=y_2="good.y"(.5[-d,h]+"pt")$;\cr +$y_3=y_4=h-d-y_1$;\cr +@draw@ $z_1\dashto z_2$; \ @draw@ $z_3\dashto z_4$;\cr +$"lft"\,x_6=\round 3u$;\cr +$x_7=w-x_6$;\cr +$x_8="good.x"\,.5w$;\cr +$x_5-x_6=x_7-x_8$;\cr +$"top"\,y_5="top"\,y_7=h+"eps"$;\cr +$"bot"\,y_6="bot"\,y_8=-d-"eps"$;\cr +@draw@ $z_5\dashto z_6$; \ @draw@ $z_7\dashto z_8$;\cr +@labels@(^@range@ 1 ^@thru@ 8);\cr +@endchar@.\cr +\enddisplay +If we digitize this character according to ^"lowres" mode at 200 +pixels per inch, we get the following results: +\begindisplay +\vbox{\manual\offinterlineskip\halign{#\hfil\cr +SSSSSSSSSSSRSSSSSRSSSSS\cr +SSSSSSSSSSRRSSSSRRSSSSS\cr +SSSSSSSSSSRRSSSSRRSSSSS\cr +SSSSSSSSSSRRSSSSRRSSSSS\cr +SSSSSSSSSSRRSSSRRSSSSSS\cr +SSSSSSSSSRRSSSSRRSSSSSS\cr +SSSSSSSSSRRSSSSRRSSSSSS\cr +SSSSSSSSSRRSSSSRRSSSSSS\cr +SSRRRRRRRRRRRRRRRRRRRSS\cr +SSRRRRRRRRRRRRRRRRRRRSS\cr +SSSSSSSSRRSSSSRRSSSSSSS\cr +SSSSSSSSRRSSSSRRSSSSSSS\cr +SSSSSSSRRSSSSRRSSSSSSSS\cr +SSSSSSSRRSSSSRRSSSSSSSS\cr +SSRRRRRRRRRRRRRRRRRRRSS\cr +SSRRRRRRRRRRRRRRRRRRRSS\cr +SSSSSSRRSSSSRRSSSSSSSSS\cr +SSSSSSRRSSSSRRSSSSSSSSS\cr +SSSSSSRRSSSSRRSSSSSSSSS\cr +SSSSSSRRSSSRRSSSSSSSSSS\cr +SSSSSRRSSSSRRSSSSSSSSSS\cr +SSSSSRRSSSSRRSSSSSSSSSS\cr +SSSSSRRSSSSRRSSSSSSSSSS\cr +SSSSSRSSSSSRSSSSSSSSSSS\cr +}}\qquad +\vbox{\manual\offinterlineskip\halign{#\hfil\cr +SSSSSSSSSSRRSSSSRRSSSSS\cr +SSSSSSSSSSRRSSSSRRSSSSS\cr +SSSSSSSSSSRRSSSSRRSSSSS\cr +SSSSSSSSSRRSSSSRRSSSSSS\cr +SSSSSSSSSRRSSSSRRSSSSSS\cr +SSSSSSSSSRRSSSSRRSSSSSS\cr +SSSSSSSSSRRSSSSRRSSSSSS\cr +SSSSSSSSSRRSSSSRRSSSSSS\cr +SSRRRRRRRRRRRRRRRRRRRSS\cr +SSRRRRRRRRRRRRRRRRRRRSS\cr +SSSSSSSSRRSSSSRRSSSSSSS\cr +SSSSSSSSRRSSSSRRSSSSSSS\cr +SSSSSSSRRSSSSRRSSSSSSSS\cr +SSSSSSSRRSSSSRRSSSSSSSS\cr +SSRRRRRRRRRRRRRRRRRRRSS\cr +SSRRRRRRRRRRRRRRRRRRRSS\cr +SSSSSSRRSSSSRRSSSSSSSSS\cr +SSSSSSRRSSSSRRSSSSSSSSS\cr +SSSSSSRRSSSSRRSSSSSSSSS\cr +SSSSSSRRSSSSRRSSSSSSSSS\cr +SSSSSSRRSSSSRRSSSSSSSSS\cr +SSSSSRRSSSSRRSSSSSSSSSS\cr +SSSSSRRSSSSRRSSSSSSSSSS\cr +SSSSSRRSSSSRRSSSSSSSSSS\cr +}}\qquad +\vbox{\manual\offinterlineskip\halign{#\hfil\cr +SSSSSSSSSSRRSSSSRRSSSSS\cr +SSSSSSSSSSRRSSSSRRSSSSS\cr +SSSSSSSSSSRRSSSSRRSSSSS\cr +SSSSSSSSSSRRSSSSRRSSSSS\cr +SSSSSSSSSSRRSSSSRRSSSSS\cr +SSSSSSSSSRRSSSSRRSSSSSS\cr +SSSSSSSSSRRSSSSRRSSSSSS\cr +SSSSSSSSSRRSSSSRRSSSSSS\cr +SSRRRRRRRRRRRRRRRRRRRSS\cr +SSRRRRRRRRRRRRRRRRRRRSS\cr +SSSSSSSSRRSSSSRRSSSSSSS\cr +SSSSSSSSRRSSSSRRSSSSSSS\cr +SSSSSSSRRSSSSRRSSSSSSSS\cr +SSSSSSSRRSSSSRRSSSSSSSS\cr +SSRRRRRRRRRRRRRRRRRRRSS\cr +SSRRRRRRRRRRRRRRRRRRRSS\cr +SSSSSSRRSSSSRRSSSSSSSSS\cr +SSSSSSRRSSSSRRSSSSSSSSS\cr +SSSSSSRRSSSSRRSSSSSSSSS\cr +SSSSSRRSSSSRRSSSSSSSSSS\cr +SSSSSRRSSSSRRSSSSSSSSSS\cr +SSSSSRRSSSSRRSSSSSSSSSS\cr +SSSSSRRSSSSRRSSSSSSSSSS\cr +SSSSSRRSSSSRRSSSSSSSSSS\cr +}} +\enddisplay +The left-hand example was obtained by omitting the `round' and `"good.x"' +instructions in the equations for $x_6$ and~$x_8$. This meant that points +$z_6$ and $z_8$ fell into different, possibly unlucky, raster positions, +so the two diagonal strokes digitized differently even though they +came from essentially identical undigitized lines. The middle example +was produced by the given program without changes. And the right-hand +example was produced by drawing the diagonals in a more complicated way: +The commands `@draw@~$z_5\dashto z_6$; @draw@~$z_7\dashto z_8$;' were +replaced by +\begindisplay +$y_{15}=y_1$; \ $z_{15}="whatever"[z_5,z_6]$; \ + $y_{36}=y_3$; \ $z_{36}="whatever"[z_5,z_6]$;\cr +$y_{27}=y_2$; \ $z_{27}="whatever"[z_7,z_8]$; \ + $y_{48}=y_4$; \ $z_{48}="whatever"[z_7,z_8]$;\cr +\noalign{\smallskip} +@draw@ $z_5\dashto("good.x"(x_{15}+.5),y_1)\dashto("good.x"(x_{15}-.5),y_1)$\cr +\qquad\qquad$\dashto("good.x"(x_{36}+.5),y_3)\dashto("good.x"(x_{36}-.5),y_3) + \dashto z_6$;\cr +@draw@ $z_7\dashto("good.x"(x_{27}+.5),y_2)\dashto("good.x"(x_{27}-.5),y_2)$\cr +\qquad\qquad$\dashto("good.x"(x_{48}+.5),y_4)\dashto("good.x"(x_{48}-.5),y_4) + \dashto z_8$;\cr +\enddisplay +The idea here was to control the goodness of the points where the +diagonals intersect the horizontal bar lines, and to hide one of the +``^{jaggies}'' inside each bar line. If we do the same three experiments +but triple the resolution, we get similar results but the differences are +not quite so obvious: +\begindisplay +\vbox{\manual\offinterlineskip\halign{#\hfil\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPQQQQQQQQQQQQQQQQPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQ\cr +QQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQ\cr +QQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPQQQQQQQQQQQQQQQQPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +}}\qquad +\vbox{\manual\offinterlineskip\halign{#\hfil\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPQQQQQQQQQQQQQQQQQPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQ\cr +QQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQ\cr +QQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPQQQQQQQQQQQQQQQQQPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +}}\qquad +\vbox{\manual\offinterlineskip\halign{#\hfil\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPQQQQQQQQQQQQQQQQQPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQ\cr +QQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQ\cr +QQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQQPPQQQQQQQQQQQQQQQQQPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +}} +\enddisplay + +\danger When letters are drawn by filling outlines, the left and right +outlines are digitized independently; therefore corresponding outlines +should usually be offset from each other by an integer amount whenever +possible. For example, suppose that the letter~`^{n}' is being drawn +with commands like +\begindisplay +$\penpos2("stem",0)$; \ $\penpos4("stem",0)$ +\enddisplay +to specify the stroke widths at the base of the two ^{stems}. +We will therefore have $x_{2r}-x_{2l}=x_{4r}-x_{4l}="stem"$. If +"stem" is not an integer, say $"stem"=2.7$, we might have +$x_{2l}=2.1$, $x_{2r}=4.8$, $x_{4l}=9.6$, $x_{4r}=12.3$; +then $x_{2r}-x_{2l}$ will digitize to $5-2=3$, so the left stem +will be three pixels wide, but the right stem will be only +$12-10=2$ pixels wide. We could get around this problem by +insisting that either $x_{2l}$ or~$x_{2r}$ be an integer, +and that either $x_{4l}$ or~$x_{4r}$ be an integer; then both stems +would be three pixels wide. But other quantities calculated from "stem" +(e.g., the breadth of diagonal strokes) would then be based on a +value of~2.7 instead of the stem width~3 that an observer of the +font actually perceives. Therefore it is best to make "stem" an integer. +The proper way to do this is generally to say +\begindisplay +^@define\_whole\_blacker\_pixels@("stem"); +\enddisplay +this command computes "stem" from $"stem"\0$ by the formula +\begindisplay +$"stem":=max\bigl(1,\,\round("stem"\0\ast"hppp"+"blacker")\bigr)$. +\enddisplay +(Notice that this rounding operation is not allowed to reduce "stem" +to zero at low resolutions.) + +\danger Even when the "stem" width is an integer in the `n' example, +we probably want to arrange things so that $x_{2l}$, $x_{2r}$, $x_{4l}$, +and~$x_{4r}$ are integers, because this will give the least distortion +under digitization. Suppose, however, that it's most convenient to define +the pen position at the center of the stroke instead of at the edge; i.e., +the program would say just `$x_2=\alpha$' if rounding were not taken into +account. How should $x_2$ be defined, when we want $x_{2l}$ to be an +integer? We could say +\begindisplay +$x_2=\alpha$; \ $x_{2l}:=\round x_{2l}$; \ $x_{2r}:=\round x_{2r}$; \ +$x_2:=.5[x_{2l},x_{2r}]$ +\enddisplay +but that's too complicated; moreover, it will fail if any other +variables depend on $x_2$, $x_{2l}$, or $x_{2r}$, because such +dependencies are forgotten when new values are assigned. +In the case of fixed pens we solved this problem by saying +`$x_2="good.x"\,\alpha$'; but the "good.x" function doesn't know +about "stem". One solution is to say +\begindisplay +$x_{2l}=\round(\alpha-.5"stem")$, +\enddisplay +or equivalently, `$x_{2r}=\round(\alpha+.5"stem")$'. This does the +job all right, but it isn't completely satisfying. It requires +knowledge of the breadth that was specified in the $\penpos2$ command, +and it works only when the penpos angle is~0. If the penpos command +is changed, the corresponding equation for rounding must be +changed too. There's another solution that's more general and more +attractive once you get used to it: +\begindisplay +$x_{2l}=\round\bigl(x_{2l}-(x_2-\alpha)\bigr)$. +\enddisplay +Why does this work? The argument to `^{round}' must be a known value, +but both $x_{2l}$ and~$x_2$ are unknown. Fortunately, their difference +$x_{2l}-x_2$ is known, because of the $\penpos2$ command. The +rounding operation makes $x_2\approx\alpha$ because it makes $x_{2l}$ +approximately equal to the value of $x_{2l}$ minus the difference +between $x_2$ and~$\alpha$. + +\ddangerexercise The generality of this technique can be appreciated +by considering the following more difficult problem that the author +faced while designing a~`^{w}': Suppose you want $x_1-x_2$ to be +an integer and $x_3\approx x_4$, and suppose that $x_2$, $x_3-x_1$, +and~$x_4+x_1$ are known; but $x_1$ is unknown, hence $x_3$ and~$x_4$ +are also unknown. According to our general idea, we want to specify an +equation of the form `$x_1-x_2=\round(x_1-x_2+f)$', where $x_1-x_2+f$ +is known and $f$~is a formula that should be approximately zero. +In this case $x_3-x_4$ is approximately zero, and $(x_3-x_1)-(x_4+x_1)$ +is known; what value of~$f$ should we choose? +\answer $f=.5(x_4-x_3)$; the desired equation is +`$x_1-x_2=\round\bigl(x_1-x_2+.5(x_4-x_3)\bigr)$'. + +\ddanger In many fonts, such as the one you are now reading, +curved lines swell out so that the thick parts of~`^{o}' are actually +a bit broader than the stems of~`n'. Therefore the ^{Computer Modern} +font routines discussed in Appendix~E have two parameters, +$"stem"\0$ and $"curve"\0$, to govern the stroke thickness. +For example, the font ^|cmr9| used in the present paragraph has +$"stem"\0=2/3"pt"\0$ and $"curve"\0=7/9"pt"\0$. Both of these should +be integers, hence the ^@font\_setup@ macro in Appendix~E +dutifully says +\begindisplay +@define\_whole\_blacker\_pixels@$("stem","curve")$. +\enddisplay +Although this looks good on paper, it can cause problems at certain +low resolutions, because the rounding operation might make ^"stem" and +^"curve" rather different from each other even though $"stem"\0$ and +$"curve"\0$ are fairly close. For example, the resolution might be +just at the value where |cmr9|'s "stem" turns out to be only~2 +but "curve" is~3. Curves shouldn't be that much darker than stems; +they would look too splotchy. Therefore plain \MF\ +has a `^@lowres\_fix@' subroutine, and Appendix~E says +\begindisplay +@lowres\_fix@("stem","curve") 1.2 +\enddisplay +after "stem" and "curve" have been defined as above. In this particular +case @lowres\_fix@ will reset $"curve":="stem"$ if it turns out that the +ratio $"curve"/"stem"$ is greater than 1.2 times the ratio +$"curve"\0/"stem"\0$. Since $"curve"\0/"stem"\0=7/6$ in the case of |cmr9|, +this means that the ratio $"curve"/"stem"$ after rounding is allowed +to be at most~1.4; if $"curve"=3$ and $"stem"=2$, the "curve" parameter +will be lowered to~2. In general the command +\begindisplay +@lowres\_fix@($d_1,d_2,\ldots,d_n$) $r$ +\enddisplay +will set $d_n:=\cdots d_2:=d_1$ if $\max(d_1,d_2,\ldots,d_n)/\! +\min(d_1,d_2,\ldots,d_n)$ is greater than +$r\cdot\max(d_1\0,d_2\0,\ldots,d_n\0)/\!\min(d_1\0,d_2\0,\ldots,d_n\0)$. + +\ddangerexercise +\parshape 12 +3pc 201pt +3pc 201pt +0pc 237pt +0pc 237pt +0pc 237pt +0pc 237pt +0pc 237pt +0pc 237pt +0pc 237pt +0pc 237pt +0pc 237pt +0pc 29pc +\rightfig 4e ({180\apspix} x {225\apspix}) ^15pt +Good digitization can also require attention to the shapes of the +digitized angles where straight lines meet. The purpose of +the present exercise is to illustrate the relevant ideas by +studying the `\kern1pt{\manual\char'170}' symbol, for which a program +appears in Chapter~4. If that program is used without change to produce +low-resolution ^{triangle}s, the results might turn out to be unsatisfactory +because, for example, point~3 at the right of the triangle +might digitize into a snubnosed or asymmetric shape. +If $y_3$ is an integer, the triangle will be top-bottom symmetric, but +the right-hand tip will be two pixels tall and this will look too blunt. +Therefore we should choose~$y_3$ to be an integer plus~1/2. +Given this value of~$y_3$, what will be the shape of the rightmost +four columns of the digitized tip, as $x_3$ varies? +\answer Let $x_3=n+{1\over2}+\theta$, where $n$ is an integer and +$0\le\theta<1$. By drawing lines of slope~$30^\circ$ from the pixel +centers, we find that there are three cases for the rightmost four +columns: +\begindisplay +Case A, +$\vcenter{\vbox{\offinterlineskip\manual + \hbox{RR}\hbox{RRRR}\hbox{RR}\kern1pt}}$;\qquad +Case B, +$\vcenter{\vbox{\offinterlineskip\manual + \hbox{R}\hbox{RR}\hbox{RRRR}\hbox{RR}\hbox{R}\kern1pt}}$;\qquad +Case C, +$\vcenter{\vbox{\offinterlineskip\manual + \hbox{R}\hbox{RRR}\hbox{RRRR}\hbox{RRR}\hbox{R}\kern1pt}}$. +\enddisplay +Case A occurs for $0\le\theta<2\sqrt3-3$; Case B occurs for +$2\sqrt3-3\le\theta<\sqrt3-1$; Case~C occurs for +$\sqrt3-1\le\theta<1$. The tip in Case~A looks a bit too sharp, +and Case~C looks too blunt, so Case~B seems best. This case occurs +when $x_3$ is near an integer, so it's OK to let $x_3$ be an integer. + +\ddangerexercise Continuing the previous exercise, assume that $x_1$ +is an integer. What value of~$y_1$ will make the upper tip of the +triangle look like +`\thinspace$\vcenter{\vbox{\offinterlineskip\manual + \hbox{R}\hbox{RR}\hbox{RRRR}\kern1pt}}$' after digitization? +\answer Let $y_1=n+\theta$. If $\theta$ lies between +${1\over2}\sqrt3-{1\over2}$ and ${1\over6}\sqrt3+{1\over2}$, +the top row after digitization will contain two black pixels. +If $\theta$ lies between ${1\over6}\sqrt3+{1\over2}$ and +${5\over6}\sqrt3-{1\over2}$, we get the desired shape. Otherwise +we get `\thinspace$\vcenter{\vbox{\offinterlineskip\manual + \hbox{R}\hbox{RRR}\hbox{RRRR}\kern1pt}}$'. + +\ddangerexercise Concluding the previous exercise, modify the +program of Chapter 4 so that the upper tip and +the upper part of the right tip both digitize to the shape +`\thinspace$\vcenter{\vbox{\offinterlineskip\manual + \hbox{R}\hbox{RR}\hbox{RRRR}\kern1pt}}$'. +\answer (We choose $\theta={1\over2}\sqrt3$ in the previous exercise, since +^^{floor} this is the midpoint of the desirable interval.) The equations +are changed to +\begindisplay +$x_1=x_2=w-x_3=\round s$;\cr +$y_3=.5+{\rm floor}\,.5h$;\cr +$z_1-z_2=(z_3-z_2)$ rotated 60;\cr +$y_1:=.5\rmsqrt3+\round(y_1-.5\rmsqrt3)$;\cr +$y_2:=h-y_1$;\cr +\enddisplay +and then we @fill@ $z_1\dashto z_2\dashto z_3\dashto\cycle$ as before. + +\ddanger So far in this chapter we've assumed that pixels are square. But +sometimes ^^{nonsquare} we need to prepare output for devices with +general rectangular pixels, and this adds an extra dimension of +complexity to rounding. Plain \MF\ sets things up so that +^"currenttransform" multiplies all $y$~coordinates by +^"aspect\_ratio", when paths are filled or drawn, or when pens are +picked up. Furthermore the ^"top" and ^"bot" functions divide the +amount of offset by "aspect\_ratio". This means that \MF\ programs +can still be written as if pixels were square; +the normal `angle' and `direction' functions, etc., can be used. +But the good places for rounding horizontal tangents are not at +integer values of~$y$ in general, they are actually at values that +will become integers after multiplication by the aspect ratio. + +\ddanger The ^"vround" function rounds its argument to the nearest +$y$~coordinate that corresponds to a pixel boundary in the +general case. Thus if $"aspect\_ratio"=1$, "vround" simply rounds +to the nearest integer, just like `round'; but if, say, +$"aspect\_ratio"=4/3$, then "vround" will round to the nearest +multiple of~$3/4$. Plain \MF\ uses "vround" instead of `round' +when it computes an ^{overshoot} correction, and also when ^@beginchar@ +computes the values of ^"h" and~^"d". The ^"good.y" function produces +a good $y$~value that takes "aspect\_ratio" properly into account. + +\ddangerexercise Without looking at Appendix B\null, try to guess how +the "vround" and "good.y" macros are defined. +\answer @vardef@ "vround" @primary@ $v$ $=$\parbreak +\qquad floor$(v\ast"aspect\_ratio"+.5)/"aspect\_ratio"$ @enddef@;\parbreak +@vardef@ "good.y" @primary@ $y$ $=$\parbreak +\qquad "vround"$(y+"pen\_top")-"pen\_top"$ @enddef@. + +\ddangerexercise What are the ``ambiguous points'' when pixels +are not square? +\answer $\bigl(m+1/2,(n+1/2)/"aspect\_ratio"\bigr)$. These are the points +that "currenttransform" maps into pixel centers. + +\ddanger The \MF\ ^{logo} as we have described it so far will round +properly with respect to arbitrary aspect ratios if we make only +a few more refinements. The value of "ygap" should be vrounded +instead of rounded, so we initialize it by saying +\begindisplay +^@define\_whole\_vertical\_pixels@("ygap"). +\enddisplay +Furthermore we should say +\begindisplay +$"ho"\0:="o"\0$; \ ^@define\_horizontal\_corrected\_pixels@("ho"); +\enddisplay +and "ho" should replace ^"o" in the equations for $x_4$ in the programs +for `{\manual i}' and~`{\manual l}'. ^^{E} ^^{F} +Everything else should work satisfactorily as it stands. + +\ddanger Appendix B includes macros ^"good.top", ^"good.bot", ^"good.lft", +and ^"good.rt" that take pairs as arguments. If you say, for example, +`$z_3="good.top"(\alpha,\beta)$' it means that $z_3$ will be near +$(\alpha,\beta)$ and that when $z_3$ is modified by ^"currenttransform" +the top point of ^"currentpen" placed at the transformed point will +be in a good raster position. + +\danger \MF's `^"autorounding"' feature tries to adjust curves to the +raster for you, but it is a mixed blessing. Here's how it works: +If the internal quantity "autorounding" is positive, the $x$~coordinates +of all paths that are filled or drawn are rounded to good raster positions +wherever there's a vertical tangent; and the $y$~coordinates +are rounded to good raster positions wherever there's a horizontal +tangent. The rest of the curve is distorted appropriately, as if +the raster were stretching or shrinking slightly. If $"autorounding">1$, +you get even more changes: Paths are perturbed slightly at $\pm45^\circ$ +tangent directions, so that second-order ^{pimples} and flat spots don't +appear there. + +\danger For example, if we return to the Ionian `{\manual\IOO}' with +which we began this chapter, let's suppose that "curve\_sidebar" was left +unrounded. We saw that the result was bad when "autorounding" was~0; +when $"autorounding"=1$ and~2 we get this: +\displayfig 24f\&g (190\apspix) +The stroke has gotten a lot thinner at the sides, by comparison with +the original design (which, incidentally, can be seen in the illustrations +below). Although autorounding has produced a fairly recognizable O~shape, +the character of the original has been lost, especially in the case +$"autorounding"=2$; indeed, the inner outline has been brought towards the +center, in the upper left and lower right sectors, and this has made the +digitized inner boundary perfectly symmetric! + +\ddanger There's an internal quantity called ^"granularity", normally +equal to~1, which affects autorounding by effectively scaling~up +the raster size. If, for example, $"granularity"=4$, the autorounded +$x$~coordinates and $y$~coordinates will become multiples of~4 instead +of simply integers. The illustrations above were produced by +setting $"granularity"=10$ and $"mag"=10$; this made the +effects of autorounding visible. The granularity should always be an integer. + +\ddanger Besides "autorounding", there's a `smoothing' feature +that becomes active when ^"smoothing"$\null>0$. The basic idea is +to try to make the edges of a curve follow a regular progression +instead of wobbling. A complete discussion of the smoothing algorithm +is beyond the scope of this manual, but an example should make the +general idea clear: Let's use the letters $R$ and~$D$ to stand for +single-pixel steps to the right and down, respectively. If a digitized +path goes `"RDDRDRDDD"', say, the number of downward steps per +rightward step is first decreasing, then increasing; the "smoothing" +process changes this to `"RDDRDDRDD"'. If smoothing is applied to the +Ionian `{\manual\IOO}' shapes above, nothing happens; but if we go back +to the original obtained with $"autorounding"=0$, we get a few changes: +\displayfig 24b\&h (190\apspix) +Three pixels have been added by "smoothing" in the right-hand illustration; +e.g., a pattern "RDRDDDDRDD" has become "RDDRDDDRDD". + +\danger If you do your own rounding, it turns out that autorounding +and smoothing usually change very few pixels, if any; thus your +safest strategy is probably to turn them off in such cases. If you +define your strokes by outlines, autorounding and smoothing +apply independently to the left and right edges, so they may +hurt as often as they help; again, they should probably be turned off. +But if you are drawing with fixed pens, autorounding generally +works well and saves a lot of fuss. If the pens are circles or +nearly circles, smoothing is also helpful; but if the pens are +more ``calligraphic,'' they are supposed to produce nonsmooth +edges occasionally, so you had better set $"smoothing":=0$. + +\ddanger If you ``^{slant}'' a font by modifying "currenttransform" +as described in Chapter~15, positions of horizontal tangency will +remain the same. But positions of vertical tangency will change +drastically, and they will probably not fall in known parts +of your design. This means, for example, that autorounding will be +helpful in a slanted pen-generated font like the +`{\manual 89:;<=>:}\kern2pt' logo. However, the author ^^{Knuth} +found that the outline-generated letters of ^{Computer Modern} +{\it^{italic}\/} came out better with $"autorounding"=0$, because +autorounding tended to make some characters too dark and others too light. + +\ninepoint + +\ddanger The effect of autorounding can be studied numerically +if you set ^"tracingspecs" to a positive value; this displays \MF's +internal calculations as it finds horizontal, vertical, and diagonal +tangent points. \ (\MF\ prepares to digitize paths by first +subdividing each B\'ezier segment into pieces that travel in only one +``^{octant}'' direction.) \ For example, if $"autorounding"=0$ +and $"tracingspecs"=1$, and if "curve\_sidebar" is left unrounded, +the file |io.log| will contain the following information about the +outer curve of the `{\manual\IOO}': +\beginlines \advance\hsize.71pt +|Path at line 15, before subdivision into octants:| +|(1.53745,9.05345)..controls (1.53745,4.00511) and (5.75409,-0.00049)| +| ..(10.85147,-0.00049)..controls (16.2217,-0.00049) and (20.46255,4.51297)|% + \kern.5em\null +| ..(20.46255,9.94655)..controls (20.46255,14.99713) and (16.23842,19.00049)| + \kern-.71pt +| ..(11.13652,19.00049)..controls (5.77066,19.00049) and (1.53745,14.48491)|% + \kern.5em\null +| ..cycle| +\smallskip +|Cycle spec at line 15, after subdivision:| +|(1.53745,9.05345) % beginning in octant `SSE'| +| ..controls (1.53745,6.58786) and (2.54324,4.371)| +| ..(4.16621,2.74803) % segment 0| +|% entering octant `ESE'| +| ..controls (5.8663,1.04794) and (8.24362,-0.00049)| +| ..(10.85147,-0.00049) % segment 0| +|% entering octant `ENE'| +\endlines +$\ldots$ and so on; there are lots more numbers! What does this all mean? +^^|ENE|^^|ESE|^^|SSE|^^{compass directions} +Well, the first segment of the curve, from $(1.53745,9.05345)$ to +$(10.85147,-0.00049)$, +has been subdivided into two parts at the place where the slope is $-1$. +The first of these parts travels basically `South by South East' and +the second travels `East by South East'. The other three segments are +subdivided in a similar way (not shown here). If you try the same +experiment but with $"autorounding"=1$, some rather different numbers +emerge: \looseness=-1 + +\goodbreak +\beginlines +|Cycle spec at line 15, after subdivision and autorounding:| +|(2,9.05348) % beginning in octant `SSE'| +| ..controls (2,6.50526) and (3.02194,4.22272)| +| ..(4.6577,2.58696) % segment 0| +|% entering octant `ESE'| +| ..controls (6.2624,0.98225) and (8.45786,0)| +| ..(10.85873,0) % segment 0| +|% entering octant `ENE'| +\endlines +Point $(1.53745,9.05345)$, where there was a vertical tangent, has been +rounded to $(2,9.05348)$; point $(10.85147,-.00049)$, where there was +a horizontal tangent, has been rounded to $(10.85873,0)$; the intermediate +control points have been adjusted accordingly. \ (Rounding of $x$~coordinates +has been done separately from $y$~coordinates.) \ Finally, with +$"autorounding"=2$, additional adjustments are made so that the +$45^\circ$ transition point will occur at what \MF\ thinks is a good spot: +\beginlines +|Cycle spec at line 15, after subdivision and double autorounding:| +|(2,9.05348) % beginning in octant `SSE'| +| ..controls (2,6.6761) and (3.07103,4.42897)| +| ..(4.78537,2.71463) % segment 0| +|% entering octant `ESE'| +| ..controls (6.46927,1.03073) and (8.62749,0)| +| ..(10.85873,0) % segment 0| +|% entering octant `ENE'| +\endlines +(Notice that $4.78537+2.71463=7.50000$; when the slope +is~$-1$ at a transition point $(x,y)$, the curve stays as far away as +possible from ambiguous points near the transition if $x+y+.5$ is an integer.) + +\endchapter + +\rightline{\vbox{\offinterlineskip\manual\halign{#\hfil\cr +SRRRRRRRRRSSSSSSSSSSSRRRRRRSSSSSSSRRRRRRRRRSSSSSSS\cr +SSRRRSSSSSRRSSSSSSSRRSSSSSSRRSSSSSSRRRSSSSRRSSSSSS\cr +SSSRRSSSSSSRRSSSSSRRSSSSSSSSRRSSSSSSRRSSSSSRRSSSSS\cr +SSSRRSSSSSSRRSSSSRRSSSSSSSSSSRRSSSSSRRSSSSSRRSSSSS\cr +SSSRRSSSSSSRRSSSRRSSSSSSSSSSSSRRSSSSRRSSSSSRRSSSSS\cr +SSSRRSSSSSSRRSSSRRSSSSSSSSSSSSRRSSSSRRSSSSSRRSSSSS\cr +SSSRRSSSSSSRRSSRRSSSSSSSSSSSSSSRRSSSRRSSSSSRRSSSSS\cr +SSSRRSSSSSRRSSSRRSSSSSSSSSSSSSSRRSSSRRSSSSRRSSSSSS\cr +SSSRRSSRRRRSSSSRRSSSSSSSSSSSSSSRRSSSRRRRRRSSSSSSSS\cr +SSSRRSSSSSSSSSSRRSSSSSSSSSSSSSSRRSSSRRSSRRRSSSSSSS\cr +SSSRRSSSSSSSSSSRRSSSSSSSSSSSSSSRRSSSRRSSSRRRSSSSSS\cr +SSSRRSSSSSSSSSSRRSSSSSSSSSSSSSSRRSSSRRSSSSRRRSSSSS\cr +SSSRRSSSSSSSSSSSRRSSSSSSSSSSSSRRSSSSRRSSSSSRRRSSSS\cr +SSSRRSSSSSSSSSSSRRSSSSSSSSSSSSRRSSSSRRSSSSSSRRSSSS\cr +SSSRRSSSSSSSSSSSSRRSSSSSSSSSSRRSSSSSRRSSSSSSRRRSSS\cr +SSSRRSSSSSSSSSSSSSRRSSSSSSSSRRSSSSSSRRSSSSSSSRRRSS\cr +SSRRRRSSSSSSSSSSSSSRRSSSSSSRRSSSSSSRRRRSSSSSSSRRRS\cr +SRRRRRRSSSSSSSSSSSSSSRRRRRRSSSSSSSRRRRRRSSSSSSSRRR\cr +\kern23\Blankpix RRR\cr +\kern24\Blankpix RRR\cr +\kern25\Blankpix RRR\cr +\kern26\Blankpix RRRR\cr +}}} +\author PIERRE ^{LE B\'E}, {\sl B\'ele Pr\'erie\/} (1601) + % (an extract from his third alphabet) + +\bigskip +\bigskip + +\rightline{\vbox{\offinterlineskip\manual\halign{#\hfil\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQPPPPPPPPPPPPPPQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQPPPPPPPPPPPPPQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQPPPPPPPPPPPPQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQPPPPPPPPPPPQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQPPPPPPPPPPPQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQPPPPPPPPPPPQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQPPPPPPPPPPQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQPPPPPPPPPPQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQPPPPPPPPPPQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQPPPPPPPPPPQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQPPPPPPPPPPQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQPPPPPPPPPPQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQPPPPPPPPPPPQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQPPPPPPPPPPQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQPPPPPPPPPPPQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQPPPPPPPPPPPQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQPPPPPPPPPPPQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQPPPPPPPPPPPPPQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQPPPPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQQQQQQQ\cr +}\kern7\blankpix}\vbox{\offinterlineskip\manual\halign{#\hfil\cr +\kern19\blankpix PPPPPPPPPP\cr +QQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQQQQQ\cr +QQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQQQ\cr +QQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQQ\cr +QQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQ\cr +QQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQ\cr +QQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQ\cr +QQQQQQQPPPPPPPPPPPPPPQQQQQQPPPPPPPPPPPPPPQQQQQQQ\cr +QQQQQQQPPPPPPPPPPPPQQQQQQQQQQPPPPPPPPPPPPQQQQQQQ\cr +QQQQQQPPPPPPPPPPPPQQQQQQQQQQQQPPPPPPPPPPPPQQQQQQ\cr +QQQQQQPPPPPPPPPPPQQQQQQQQQQQQQQPPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPPQQQQQQQQQQQQQQQQPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPPQQQQQQQQQQQQQQQQPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQQQ\cr +QQQQPPPPPPPPPPPQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQQQ\cr +QQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQQ\cr +QQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQQ\cr +QQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQQ\cr +QQQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQQ\cr +QQQPPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPQQQ\cr +QQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQ\cr +QQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQ\cr +QQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQ\cr +QQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQ\cr +QQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQ\cr +QQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQ\cr +QQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQ\cr +QQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQ\cr +QQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQ\cr +QQQPPPPPPPPPPQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPQQQ\cr +QQQPPPPPPPPPPQQQQQQQQQQQQPQQQQQQQQQPPPPPPPPPPQQQ\cr +QQQPPPPPPPPPPQQQQQQQQQQQPPQQQQQQQQQPPPPPPPPPPQQQ\cr +QQQPPPPPPPPPPQQQQQQQQQQPPPPQQQQQQQQPPPPPPPPPPQQQ\cr +QQQPPPPPPPPPPPQQQQQQQQPPPPPPQQQQQQQPPPPPPPPPQQQQ\cr +QQQQPPPPPPPPPPQQQQQQQPPPPPPPPQQQQQQPPPPPPPPPQQQQ\cr +QQQQPPPPPPPPPPQQQQQQPPPPPPPPPQQQQQQPPPPPPPPPQQQQ\cr +QQQQPPPPPPPPPPQQQQQPPPPPPPPPPPQQQQQPPPPPPPPQQQQQ\cr +QQQQPPPPPPPPPPQQQQQQQPPPPPPPPPPQQQQPPPPPPPPQQQQQ\cr +QQQQPPPPPPPPPPPQQQQQQQPPPPPPPPPPQQQPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQPPPPPPPPPQQQPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPPQQQQQQQQPPPPPPPPQQQPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPPQQQQQQQQQPPPPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQQPPPPPPPPPPPQQQQQQQQQPPPPPPPPPPPPPPPQQQQQQQ\cr +QQQQQQPPPPPPPPPPPPQQQQQQQQQPPPPPPPPPPPPPQQQQQQQQ\cr +QQQQQQQPPPPPPPPPPPPQQQQQQQQQPPPPPPPPPPPPQQQQQQQQ\cr +QQQQQQQPPPPPPPPPPPPPPQQQQQQQQPPPPPPPPPPQQQQQQQQQ\cr +QQQQQQQQPPPPPPPPPPPPPPPQQQQQQPPPPPPPPPQQQQQQQQQQ\cr +QQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQ\cr +QQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQ\cr +QQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQ\cr +QQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPQQPPPPPPPPQQQQQQ\cr +QQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPQQPPPPPPPPPQQQQQ\cr +QQQQQQQQQQQQQQQQPPPPPPPPPPPPPPQQQQPPPPPPPPPPQQQQ\cr +\kern19\blankpix PPPPPPP\kern9\blankpix PPPPPPPPPPP\cr +\kern36\blankpix PPPPPPPPP\cr +\kern37\blankpix PPPPPPP\cr +\kern37\blankpix PPPPPP\cr +\kern38\blankpix PPPP\cr +\kern39\blankpix PP\cr +\kern39\blankpix P\cr +}}\vbox{\offinterlineskip\manual\halign{#\hfil\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQPPPPPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQPPPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQPPPPPPPPPPPQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQPPPPPPPPPPPQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQPPPPPPPPPPQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQPPPPPPPPPPQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQPPPPPPPPPPQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQPPPPPPPPPPQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQPPPPPPPPPPQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQPPPPPPPPPPQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQPPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQPPPPPPPPPPPPQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQPPPPPPPPPPPPPQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPPPPPPPPPQQQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPPPPPPQPPPPPPPPPQQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQPPPPPPPPPPQQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQPPPPPPPPPPPQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQPPPPPPPPPPQQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQPPPPPPPPPPPQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQPPPPPPPPPPQQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQPPPPPPPPPPPQQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQPPPPPPPPPPPQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQPPPPPPPPPPPQQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQPPPPPPPPPPPQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQPPPPPPPPPPPQQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQPPPPPPPPPPPQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQPPPPPPPPPPPQQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQPPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQPPPPPPPPPPPQQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQPPPPPPPPPPPQQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQPPPPPPPPPPPQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQPPPPPPPPPPPQQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQPPPPPPPPPPPQQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQPPPPPPPPPPPPQQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQPPPPPPPPPPPPQ\cr +QQQQQPPPPPPPPPPQQQQQQQQQQQQQQQQPPPPPPPPPPPPQ\cr +}\kern7\blankpix}} +\author MATTHEW ^{CARTER}, {\sl Bell Centennial\/} (1978) + % from the 6pt `name and number' font as he digitized it by hand + % reference: Type & Technology Manuscript No.1, Cooper Union (1982) + +\eject + \beginChapter Chapter 25. Summary of\\Expressions + +We've seen that \MF\ can handle a wide variety of algebraic ^{expressions}; +now it's time to consolidate what we have learned. The purpose of this +chapter and the one that follows is to present a precise and concise +summary of everything that \MF\ knows how to do. + +We shall be concerned here solely with \MF's {\sl^{primitive}\/} operations, +rather than with the higher-level features of the plain \MF\ base that +comprise the bulk of typical programs. Therefore novice users should put +off reading Chapters 25 and~26 until they feel a need to know what +goes on at the more mundane levels inside the computer. Appendix~B contains +a summary of the features of plain \MF\!, together with a ready-reference guide +to the things that most people want to know about \MF\ usage. + +\ninepoint\medskip +The remainder of this chapter is set in small type, like that of the +present paragraph, since it is analogous to material that is marked +``doubly dangerous'' in other chapters. Instead of using dangerous +bend signs repeatedly, let us simply agree that Chapters 25 and~26 are +dangerous by definition. + +Chapter 8 introduced the general idea of expressions and the four-fold +``primary, secondary, tertiary, expression'' ^{hierarchy} on which +their syntax is based. \MF's variables can have any of eight types: +@boolean@, @numeric@, @pair@, @path@, @pen@, @picture@, @string@, +and @transform@. Its expressions can actually have nine different +types, although the ninth one---``^{vacuous}''---is not particularly +interesting since it has only one possible value. Here is the overall +syntax: +\beginsyntax +<primary>\is<boolean primary>\alt<numeric primary> + \alt<pair primary>\alt<path primary> + \alt<pen primary>\alt<future pen primary> + \alt<picture primary>\alt<string primary> + \alt<transform primary>\alt<vacuous primary>\endgraf\medskip +<secondary>\is<boolean secondary>\alt<numeric secondary> + \alt<pair secondary>\alt<path secondary> + \alt<pen secondary>\alt<future pen secondary> + \alt<picture secondary>\alt<string secondary> + \alt<transform secondary>\alt<vacuous secondary>\endgraf\medskip +<tertiary>\is<boolean tertiary>\alt<numeric tertiary> + \alt<pair tertiary>\alt<path tertiary> + \alt<pen tertiary>\alt<picture tertiary> + \alt<string tertiary>\alt<transform tertiary> + \alt<vacuous tertiary>\endgraf\medskip +<expression>\is<boolean expression>\alt<numeric expression> + \alt<pair expression>\alt<path expression> + \alt<pen expression>\alt<picture expression> + \alt<string expression>\alt<transform expression> + \alt<vacuous expression> +\endsyntax +We shall discuss the different types of expressions in alphabetic order; +thus, if you are dying to know what a ``vacuous'' expression is, +you should skip to the end of the chapter. \looseness=-1 + +\medbreak +\textindent\bull +Boolean expressions were discussed in Chapter 19. The full syntax has +one more operation, `charexists', that was not mentioned there: +\beginsyntax +<boolean primary>\is<boolean variable>\alt<boolean argument> + \alt[true]\alt[false] + \alt[(]<boolean expression>[)] + \alt[begingroup]<statement list><boolean expression>[endgroup] + \alt[known]<primary>\alt[unknown]<primary> + \alt<type><primary>\alt[cycle]<primary> + \alt[odd]<numeric primary> + \alt[charexists]<numeric primary> + \alt[not]<boolean primary> +<boolean secondary>\is<boolean primary> + \alt<boolean secondary>[and]<boolean primary> +<boolean tertiary>\is<boolean secondary> + \alt<boolean tertiary>[or]<boolean secondary> +<boolean expression>\is<boolean tertiary> + \alt<numeric expression><relation><numeric tertiary> + \alt<pair expression><relation><pair tertiary> + \alt<transform expression><relation><transform tertiary> + \alt<boolean expression><relation><boolean tertiary> + \alt<string expression><relation><string tertiary> +<relation>\is[\char'74]\alt[\char'74=]\alt[>]\alt[>=]\alt[=]\alt[\char'74>] +\endsyntax +The expression `charexists $x$' is true if and only if a ^@shipout@ +command has previously been done with ^"charcode"$\null=x$. \ (The value +of~$x$ is first rounded to an integer, and reduced to the range +$0\le x<256$ by adding or subtracting multiples of~256.) + +In these rules, tokens like `|true|' that appear in typewriter type stand for +any ^{tokens} whose current meaning is the same as the meaning of `|true|' +when \MF\ starts from scratch; the particular token `|true|'---whose +meaning may indeed change as a program runs---is not really involved. + +The special tokens `|(|' and~`|)|' in these rules do not refer to +^{parentheses}; they refer to any matching pair of ^{delimiters} defined +by a ^@delimiters@ command. + +A \<boolean variable> denotes a ^\<variable> whose type is @boolean@; a +$\langle$numeric variable$\rangle$ is a \<variable> whose type is +@numeric@; and so~on. The syntax for \<variable> was discussed in +Chapter~7. A \<boolean argument> is an ^@expr@ ^{argument} to a macro, +where the value of the expression is of type @boolean@; +@expr@ arguments are put into special ``^{capsule}'' +tokens as explained in Chapter~18. + +\medbreak +\textindent\bull +Numeric expressions have the richest syntax of all, because they form the +nucleus of the entire \MF\ language: +\beginsyntax +<numeric atom>\is<numeric variable>\alt<numeric argument> + \alt<numeric token primary> + \alt<internal quantity> + \alt[normaldeviate] + \alt[(]<numeric expression>[)] + \alt[begingroup]<statement list><numeric expression>[endgroup] +<numeric token primary>\is<numeric token>[/]<numeric token> + \alt<numeric token not followed by `{\tt/}$\langle$numeric token$\rangle$'> +<numeric primary>\is<numeric atom> + \alt<numeric atom>[\char'133]<numeric expression>% + [,]<numeric expression>[\char'135] + \alt[length]<numeric primary>\alt[length]<pair primary> + \alt[length]<path primary>\alt[length]<string primary> + \alt[ASCII]<string primary>\alt[oct]<string primary>\alt[hex]<string primary> + \alt<pair part><pair primary>\alt<transform part><transform primary> + \alt[angle]<pair primary> + \alt[turningnumber]<path primary>\alt[totalweight]<picture primary> + \alt<numeric operator><numeric primary> + \alt[directiontime]<pair expression>[of]<path primary> +<pair part>\is[xpart]\alt[ypart] +<transform part>\is<pair part>\alt[xxpart]\alt[xypart]\alt[yxpart]\alt[yypart] +<numeric operator>\is[sqrt]\alt[sind]\alt[cosd]\alt[mlog]\alt[mexp] + \alt[floor]\alt[uniformdeviate]\alt<scalar multiplication operator> +<scalar multiplication operator>\is<plus or minus> + \alt<numeric token primary not followed by {\tt+} or {\tt-} or a numeric token> +<numeric secondary>\is<numeric primary> + \alt<numeric secondary><times or over><numeric primary> +<times or over>\is[*]\alt[/] +<numeric tertiary>\is<numeric secondary> + \alt<numeric tertiary><plus or minus><numeric secondary> + \alt<numeric tertiary><Pythagorean plus or minus><numeric secondary> +<plus or minus>\is[+]\alt[-] +<Pythagorean plus or minus>\is[++]\alt[+-+] +<numeric expression>\is<numeric tertiary> +\endsyntax +Each of the operations mentioned in this syntax has already been explained +somewhere in this book; Appendix~I tells where. + +\medbreak +This is a good time to list all of the internal quantities that are +initially present in \MF: +\begindisplay +^"tracingtitles"&show titles online when they appear\cr +^"tracingequations"\hidewidth&show each variable when it becomes known\cr +^"tracingcapsules"\hidewidth&show capsules as well as variables\cr +^"tracingchoices"&show the control points chosen for paths\cr +^"tracingspecs"&show subdivision of paths into octants before digitizing\cr +^"tracingpens"&show vertices of pens as they are made from future pens\cr +^"tracingcommands"\hidewidth + &show commands and operations before they're performed\cr +^"tracingrestores"&show when a symbol or internal quantity is restored\cr +^"tracingmacros"&show macros before they are expanded\cr +^"tracingedges"&show digitized edges as they are computed\cr +^"tracingoutput"&show digitized edges as they are output\cr +^"tracingonline"&show long diagnostics on the terminal and in the log\cr +^"tracingstats"&log the memory usage at end of job\cr +^"pausing"&show lines on the terminal before they are read\cr +^"showstopping"&stop after each @show@ command\cr +^"fontmaking"&produce font metric output\cr +^"proofing"&produce proof mode output\cr +^"turningcheck"&reorient clockwise paths, flag strange ones\cr +^"warningcheck"&advise when a variable value gets large\cr +^"smoothing"&remove certain glitches from digitized curves\cr +^"autorounding"&move paths to ``good'' tangent points\cr +^"granularity"&the pixel size for "autorounding"\cr +^"fillin"&the extra darkness of diagonals (to be counteracted)\cr +^"year"&the current year (e.g., 1986)\cr +^"month"&the current month (e.g., 3 $\equiv$ March)\cr +^"day"&the current day of the month\cr +^"time"&the number of minutes past midnight when job started\cr +^"charcode"&the number of the next character to be output\cr +^"charext"&the extension code of the next character to be output\cr +^"charwd"&the width of the next character to be output, in points\cr +^"charht"&the height of the next character to be output, in points\cr +^"chardp"&the depth of the next character to be output, in points\cr +^"charic"&the italic correction of the next character, in points\cr +^"chardx"&the device's $x$ movement for the next character, in pixels\cr +^"chardy"&the device's $y$ movement for the next character, in pixels\cr +^"designsize"&the approximate size of the current typeface, in points\cr +^"hppp"&the number of horizontal pixels per point\cr +^"vppp"&the number of vertical pixels per point\cr +^"xoffset"&the horizontal displacement of shipped-out characters\cr +^"yoffset"&the vertical displacement of shipped-out characters\cr +^"boundarychar"&the right boundary character for ligatures and kerns\cr +\enddisplay +All of these quantities are numeric. They are initially zero at the +start of a job, except for "year", "month", "day", and "time", which +are initialized to the time the run began; furthermore, "boundarychar" is +initially~$-1$. A "granularity" of zero is equivalent to $"granularity"=1$. +A preloaded base file like plain \MF\ will usually give nonzero values to +several other internal quantities on this list. + +\medbreak +\textindent\bull +Now we come to expressions of type @pair@, which are the second most +important elements of \MF\ programs: +\beginsyntax +<pair primary>\is<pair variable>\alt<pair argument> + \alt[(]<numeric expression>[,]<numeric expression>[)] + \alt[(]<pair expression>[)] + \alt[begingroup]<statement list><pair expression>[endgroup] + \alt<numeric atom>[\char'133]<pair expression>[,]<pair expression>[\char'135] + \alt<scalar multiplication operator><pair primary> + \alt[point]<numeric expression>[of]<path primary> + \alt[precontrol]<numeric expression>[of]<path primary> + \alt[postcontrol]<numeric expression>[of]<path primary> + \alt[penoffset]<pair expression>[of]<pen primary> + \alt[penoffset]<pair expression>[of]<future pen primary> +<pair secondary>\is<pair primary> + \alt<pair secondary><times or over><numeric primary> + \alt<numeric secondary>[*]<pair primary> + \alt<pair secondary><transformer> +<transformer>\is[rotated]<numeric primary> + \alt[scaled]<numeric primary> + \alt[shifted]<pair primary> + \alt[slanted]<numeric primary> + \alt[transformed]<transform primary> + \alt[xscaled]<numeric primary> + \alt[yscaled]<numeric primary> + \alt[zscaled]<pair primary> +<pair tertiary>\is<pair secondary> + \alt<pair tertiary><plus or minus><pair secondary> + \alt<path tertiary>[intersectiontimes]<path secondary> +<pair expression>\is<pair tertiary> +\endsyntax +A pair is a special case of a path (namely, it's a path of length zero); +Chapter 19 explains that \MF\ doesn't change the type from pair to path +unless there is no other way to meet the syntax rules. + +\medbreak +\textindent\bull +Speaking of paths, they come next in our survey: +\beginsyntax +<path primary>\is<pair primary>\alt<path variable>\alt<path argument> + \alt[(]<path expression>[)] + \alt[begingroup]<statement list><path expression>[endgroup] + \alt[makepath]<pen primary>\alt[makepath]<future pen primary> + \alt[reverse]<path primary> + \alt[subpath]<pair expression>[of]<path primary> +<path secondary>\is<pair secondary>\alt<path primary> + \alt<path secondary><transformer> +<path tertiary>\is<pair tertiary>\alt<path secondary> +<path expression>\is<pair expression>\alt<path tertiary> + \alt<path subexpression><direction specifier> + \alt<path subexpression><path join>[cycle] +<path subexpression>\is<path expression not ending with direction specifier>\kern-5pt\null + \alt<path subexpression><path join><path tertiary> +<path join>\is<direction specifier><basic path join><direction specifier> +<direction specifier>\is<empty> + \alt[\char'173][curl]<numeric expression>[\char'175] + \alt[\char'173]<pair expression>[\char'175] + \alt[\char'173]<numeric expression>[,]<numeric expression>[\char'175] +<basic path join>\is[\&] + \alt[..] + \alt[..]<tension>[..] + \alt[..]<controls>[..] +<tension>\is[tension]<tension amount> + \alt[tension]<tension amount>[and]<tension amount> +<tension amount>\is<numeric primary> + \alt[atleast]<numeric primary> +<controls>\is[controls]<pair primary> + \alt[controls]<pair primary>[and]<pair primary> +\endsyntax +Chapter 14 tells all about path creation. + +\medbreak +\textindent\bull +Pens and future pens coexist as follows: +\beginsyntax +<pen primary>\is<pen variable>\alt<pen argument> + \alt[nullpen] + \alt[(]<pen expression>[)] + \alt[begingroup]<statement list><pen expression>[endgroup] +<future pen primary>\is[pencircle] + \alt[makepen]<path primary> +<pen secondary>\is<pen primary> +<future pen secondary>\is<future pen primary> + \alt<future pen secondary><transformer> + \alt<pen secondary><transformer> +<pen tertiary>\is<pen secondary> + \alt<future pen secondary> +<pen expression>\is<pen tertiary> +\endsyntax +See Chapter 16 for a thorough discussion of pen usage. + +\medbreak +\textindent\bull +Pictures can be null, added, or subtracted: +\beginsyntax +<picture primary>\is<picture variable>\alt<picture argument> + \alt[nullpicture] + \alt[(]<picture expression>[)] + \alt[begingroup]<statement list><picture expression>[endgroup] + \alt<plus or minus><picture primary> +<picture secondary>\is<picture primary> + \alt<picture secondary><transformer> +<picture tertiary>\is<picture secondary> + \alt<picture tertiary><plus or minus><picture secondary> +<picture expression>\is<picture tertiary> +\endsyntax +Chapter 13 is the definitive reference for picture operations. + +\medbreak +\textindent\bull +Strings are still fresh in our minds from Chapter 22, but we should +repeat the syntax again for completeness here. +\beginsyntax +<string primary>\is<string variable>\alt<string argument> + \alt<string token> + \alt[jobname] + \alt[readstring] + \alt[(]<string expression>[)] + \alt[begingroup]<statement list><string expression>[endgroup] + \alt[str]<suffix> + \alt[char]<numeric primary> + \alt[decimal]<numeric primary> + \alt[substring]<pair primary>[of]<string primary> +<string secondary>\is<string primary> +<string tertiary>\is<string secondary> +<string expression>\is<string tertiary> + \alt<string expression>[\&]<string tertiary> +\endsyntax +There's nothing more to say about strings. + +\medbreak +\textindent\bull +Chapter 15 explains transforms, but gives no formal syntax. The rules are: +\beginsyntax +<transform primary>\is<transform variable>\alt<transform argument> + \alt[(]<transform expression>[)] + \alt[begingroup]<statement list><transform expression>[endgroup] +<transform secondary>\is<transform primary> + \alt<transform secondary><transformer> +<transform tertiary>\is<transform secondary> +<transform expression>\is<transform tertiary> +\endsyntax +Note that ^"identity" doesn't appear here; it is a variable defined +in Appendix~B\null, not a primitive of the language. + +\medbreak +\textindent\bull +Finally, we come to the new kind of expression, which wasn't mentioned +in previous chapters because it is so trivial. +\beginsyntax +<vacuous primary>\is<vacuous argument> + \alt<compound> + \alt[(]<vacuous expression>[)] + \alt[begingroup]<statement list><vacuous expression>[endgroup] +<vacuous secondary>\is<vacuous primary> +<vacuous tertiary>\is<vacuous secondary> +<vacuous expression>\is<vacuous tertiary> +\endsyntax +A \<compound> is defined in Chapter 26. + +\ddangerexercise Construct minimal examples of each of the +nine types of expression (boolean, numeric, \dots,~vacuous). +You should use only ``^{sparks}'' in your constructions, not \<tag> +tokens or capsules; in particular, variables are not permitted +(otherwise this exercise would be too easy). Your expressions should +be as short as possible in the sense of {\sl fewest tokens\/}; the number +of keystrokes needed to type them is irrelevant. +\answer By looking at the syntax rules, we find, for example, +\begindisplay +\<boolean expression>&|true|\cr +\<numeric expression>&|0|\cr +\<pair expression>&|(0,0)|\cr +\<path expression>&|makepath pencircle|\cr +\<pen expression>&|nullpen|\cr +\<picture expression>&|nullpicture|\cr +\<string expression>&|""|\cr +\<transform expression>&Impossible!\cr +\<vacuous expression>&|begingroup endgroup|\cr +\enddisplay +Every \<transform expression> includes either a variable or a capsule. +Incidentally, there are some amusing alternative 5-token solutions for +\<pair expression>: +\begintt +postcontrol 0 of makepath nullpen +makepath pencircle intersectiontimes makepath nullpen +\endtt + +\endchapter + +This is of you very well remembred, +and well and sommaryly rehersed. +\author THOMAS ^{MORE}, {\sl A Dialogue Concernynge Heresyes\/} (1529) + % Bk 2, Ch 1 + % p178 ll C7--8 in 1557 edition, where the spelling is slightly different + +\bigskip + +Below the tomato blobs was a band of white with vertical black stripes, +to which he could assign no meaning whatever, +till some one else came by, murmuring: +``What expression he gets with his foreground!'' +.\thinspace.\thinspace. % +Ah, they were all Expressionists now, he had heard, on the Continent. +So it was coming here too, was it? +\author JOHN ^{GALSWORTHY}, {\sl To Let\/} (1921) % Chapter 1, p13 + +\eject + \beginchapter Chapter 26. Summary of\\the Language + +The grand tour of \MF's syntax that was begun in the previous chapter +is concluded in this one, so that a complete reference guide is +available for people who need to know the details. +\ (Another summary appears in Appendix~B.) + +\ninepoint\medskip +\MF\ actually has a few features that didn't seem to be worth mentioning +in earlier chapters, so they will be introduced here as part of our +exhaustive survey. If there is any disagreement between something that +was said previously and something that will be said below, the facts +in the present chapter should be regarded as better approximations +to the ^{truth}. + +We shall study \MF's digestive processes, i.e., what \MF\ does in +response to the tokens that arrive in its ``stomach.'' +^^{anatomy of METAFONT} +Chapter~6 describes the process by which input files are converted to +lists of tokens in \MF's ``mouth,'' and Chapters 18--20 explain how +expandable tokens are converted to unexpandable ones in \MF's ``gullet'' +by a process similar to regurgitation. In particular, conditions and +loops are handled by the expansion mechanism, and we need not +discuss them further. When unexpandable tokens +finally reach \MF's gastro-intestinal tract, the real activities +begin; expressions are evaluated, equations are solved, variables are +declared, and commands are executed. In this chapter we shall discuss the +primitive operations that actually draw pictures and produce output. + +Let's start by looking at the full syntax for \<program> and for +\<statement>: +\beginsyntax +<program>\is<statement list>[end]\alt<statement list>[dump] +<statement list>\is<empty>\alt<statement>[;]<statement list> +<statement>\is<empty>\alt<title> + \alt<equation>\alt<assignment> + \alt<declaration>\alt<definition> + \alt<compound>\alt<command> +<title>\is<string expression> +<compound>\is[begingroup]<statement list><non-title statement>[endgroup] +<command>\is<save command> + \alt<interim command> + \alt<newinternal command> + \alt<randomseed command> + \alt<let command> + \alt<delimiters command> + \alt<protection command> + \alt<everyjob command> + \alt<show command> + \alt<message command> + \alt<mode command> + \alt<picture command> + \alt<display command> + \alt<openwindow command> + \alt<shipout command> + \alt<special command> + \alt<font metric command> +\endsyntax +The \<empty> statement does nothing, but it is very handy because you can +always feel safe when you put extra semicolons between statements. +A \<title> does almost nothing, but it provides useful documentation +as explained in Chapter~22. +The syntax of \<equation> and \<assignment> can be found in Chapter~10; +\<declaration> is in Chapter~7; \<definition> is in Chapters 18 and~20. +We shall concentrate in this chapter on the various types of {\sl +^{commands}}, especially on those that haven't been mentioned before. +\beginsyntax +<save command>\is[save]<symbolic token list> +<symbolic token list>\is<symbolic token> + \alt<symbolic token list>[,]<symbolic token> +<interim command>\is\kern-1.5pt[interim]% + <internal quantity>[:=]<right-hand side>\kern-1pt +\endsyntax +The @save@ and @interim@ commands cause values to be restored at the end +of the current group, as discussed in Chapter~17. +\beginsyntax +<newinternal command>\is[newinternal]<symbolic token list> +\endsyntax +Each of the symbolic tokens specified in a @newinternal@ command will +henceforth behave exactly as an \<internal quantity>, initially zero. +Thus, they can be used in @interim@ commands; they are ^{tags} but not +^{external tags} (see Chapter~7). Since \MF\ can access internal +quantities quickly, you can use them to gain efficiency. +\beginsyntax +<randomseed command>\is[randomseed][:=]<numeric expression> +\endsyntax +The @randomseed@ command specifies a ``seed'' value that defines +the pseudo-random numbers to be delivered by +`uniformdeviate' and `normaldeviate' (cf.~Chapter~21). +The default value, if you don't specify your own seed, is +^^"day" ^^"time" $"day"+"time"\ast"epsilon"$. +\beginsyntax +<let command>\is[let]<symbolic token><is><symbolic token> +<is>\is[=]\alt[:=] +\endsyntax +The @let@ command changes the current meaning of the left-hand token +to the current meaning of the right-hand token. For example, +after `@let@ $"diamonds"=@forever@$', the token "diamonds" will +introduce loops. If the left-hand token was the first token of +any variable names, those variables all disappear. If the right-hand +token was the first token in any variable names, those variables +remain unchanged, and the left-hand token becomes +an unknown, independent variable. \ (The purpose of @let@ is to redefine +primitive meanings or macro meanings, not to equate variables in any way.) +\ If the right-hand symbol is one of a pair of matching delimiters, +the subsequent behavior of the left-hand symbol is undefined. +For example, it's a bad idea to say `@let@~$[\,[=($;~@let@~$]\,]=)$'. +\beginsyntax +<delimiters command>\is[delimiters]<symbolic token><symbolic token> +\endsyntax +The @delimiters@ command gives new meanings to the two symbolic tokens; +henceforth they will match each other (and only each other). For example, +Appendix~B says `@delimiters@~()'; without this command, parentheses +would be ordinary symbolic tokens. Any distinct symbolic tokens can be +defined to act as delimiters, and many different pairs of delimiters +can be in use simultaneously. +\beginsyntax +<protection command>\is[outer]<symbolic token list> + \alt[inner]<symbolic token list> +\endsyntax +A ``^{forbidden}'' stamp is added to or removed from symbolic tokens +by an @outer@ or @inner@ command, without changing the essential meanings +of those tokens. A token that has been called @outer@ should not appear +when \MF\ is skipping over tokens at high speed; the program will stop +and insert an appropriate delimiter, if an @outer@ token is sensed in +the wrong place, since such tokens are supposed to occur only at +``quiet'' times. \ (Unquiet times occur when \MF\ is skipping tokens +because of a false ^{condition}, or because it is reading the ^{replacement +text} of a macro or the ^{loop text} of a loop, or because it is scanning +the ^{text argument} to a macro, or because it is ^{flushing} erroneous +tokens that were found at the end of a statement.) \ Without such +protection, a missing right delimiter could cause \MF\ to eat up your +whole program before any error was detected; @outer@ tokens keep such +errors localized. An @inner@ command undoes the effect of @outer@; so +does `@let@', and so does any other command or definition that changes the +meaning of a symbolic token. All tokens are initially @inner@. +\beginsyntax +<everyjob command>\is[everyjob]<symbolic token> +\endsyntax +The command `@everyjob@$\,S$' tells \MF\ that token $S$ should be inserted +first, just before the input file is read, when a job starts. \ (This +is meaningful only in a base file that will be loaded or preloaded +at the beginning of a run; it is analogous to \TeX's |\everyjob| command.) +\beginsyntax +<show command>\is[show]<expression list> + \alt[showvariable]<symbolic token list> + \alt[showtoken]<symbolic token list> + \alt[showdependencies] + \alt[showstats] +\endsyntax +A simple @show@ command displays the value of each expression, in turn. +Paths, pens, and pictures are shown only in the transcript file, unless +^"tracingonline" is positive. The @showvariable@ command gives the +structure of all variables that begin with a given external tag, +together with their values in an abbreviated form; this allows you to see +which of its subscripts and attributes have occurred. For example, if you're +using plain \MF\ conventions, `@showvariable@~$x,y$' will show all +coordinates that have been defined since the last @beginchar@. The @showtoken@ +command gives the current meaning of a token, so that you can tell whether +it is primitive or not, @outer@ or not. (If @showvariable@ is applied to +a spark instead of a tag, it gives the same information as @showtoken@.) +\ Every unknown numeric variable that's currently dependent is shown by +@showdependencies@ (except that unknown capsules are shown only +when ^"tracingcapsules" is positive). And finally, @showstats@ gives +information about \MF's current memory usage. +Each of these commands will stop and say `|!|~^|OK.|', if the internal +quantity "showstopping" has a positive value; this gives you a chance +to enter more @show@ commands ^{interactive}ly, while you're trying to +debug a program. +\beginsyntax +<message command>\is<message op><string expression> +<message op>\is[message]\alt[errmessage]\alt[errhelp] +\endsyntax +Communication with the user is possible via @message@, @errmessage@, +and @errhelp@, as discussed in Chapter~22. +\beginsyntax +<mode command>\is[batchmode]\alt[nonstopmode] + \alt[scrollmode]\alt[errorstopmode] +\endsyntax +The four ``mode commands'' control the amount of interaction during error +recovery, just as in~\TeX. A job starts in @errorstopmode@, and you can +also resurrect this mode by ^{interrupting} \MF; @scrollmode@, +@nonstopmode@, and @batchmode@ are the modes you get into by hitting +`|S|', `|R|', or `|Q|', respectively, in response to error messages +(cf.~Chapter~5). +\beginsyntax +<picture command>\is<addto command>\alt<cull command> +<addto command>\is[addto]<picture variable>[also]<picture expression> + \alt[addto]<picture variable>[contour]<path expression><with list> + \alt[addto]<picture variable>[doublepath]<path expression><with list> +<with list>\is<empty>\alt<with list><with clause> +<with clause>\is[withpen]<pen expression>% + \alt[withweight]<numeric expression>\kern-3.5pt +<cull command>\is[cull]<picture variable><keep or drop><pair expression> + \alt<cull command>[withweight]<numeric expression> +<keep or drop>\is[keeping]\alt[dropping] +\endsyntax +The @addto@ and @cull@ commands are the principal means of making +changes to pictures; they are discussed fully in Chapter~13. +\beginsyntax +<display command>\is[display]<picture variable>[inwindow]<window> +<window>\is<numeric expression> +<openwindow command>\is[openwindow]<window><window spec> +<window spec>\is<screen place>[at]<pair expression> +<screen place>\is[from]<screen coordinates>[to]<screen coordinates> +<screen coordinates>\is<pair expression> +\endsyntax +Chapter~23 explains how to display stuff on your screen via @display@ +and @openwindow@\kern-1pt. +\beginsyntax +<shipout command>\is[shipout]<picture expression> +\endsyntax +You may have wondered how \MF\ actually gets pictorial information into +a font. Here at last is the answer: `@shipout@~$v$' puts the pixels of +positive weight, as defined by the picture expression~$v$, into a ^{generic +font} output file, where they will be the bitmap image associated with +character number $"charcode"\bmod256+"charext"\ast256$. The pixels of~$v$ +are shifted by $("xoffset","yoffset")$ as they are shipped out. +\ (However, no output is done if ^"proofing"$\null<0$. The values of +^"xoffset", ^"yoffset", ^"charcode", and ^"charext" are first rounded to +integers, if necessary.) \ This command also saves the values of +^"charwd", ^"charht", ^"chardp", ^"charic", ^"chardx", and "chardy"; they +will be associated with the current "charcode" when ^{font metric +information} is produced. \ (See Appendices F and~G for the basic +principles of font metric information and generic font files.) +\beginsyntax +<special command>\is[special]<string expression> + \alt[numspecial]<numeric expression> +\endsyntax +The @special@ and @numspecial@ commands send alphabetic and numeric +information +to the generic font output file, if "proofing" is nonnegative. +For example, the labels on proofsheets are specified in this +way by macros of plain \MF\!\null. Appendices G and~H provide further details. + +\medbreak +We have now discussed every kind of command but one; and the remaining +one is even more special than the \<special command>, so we had better +defer its discussion to an appendix. Appendix~F will complete the syntax +by defining \<font metric command>. For now, we merely need to know that +font metric commands specify fussy font facts; examples are the kerning and +`@font\_normal\_space@' statements in the \MF\ logo program of Chapter~11. + +\medbreak +And there's one more loose end to tie up, going back to the very +first syntax rule in this chapter: The token `^@dump@' can be +substituted for `^@end@', if a special version of \MF\ called +`^|INIMF|' is being used. This writes a file containing the macros +defined so far, together with the current values of variables and +the current meanings of symbolic tokens, so +that they can be loaded as a base file. \ (It is analogous to +\TeX's |\dump| command.) \ Base files are discussed at the end of +Appendix~B. + +\ddangerexercise Run \MF\ with the input ^^@outer@ ^^@delimiters@ ^^@showtoken@ +\begintt +\newinternal a; +let b=a; outer a,b,c; +let c=b; delimiters a::; +showtoken a,b,c; end +\endtt +and explain the computer's responses. +\answer The responses are +\begintt +> a=left delimiter that matches :: +> b=(outer) a +> c=a +\endtt +because: $a$ has been redefined from internal quantity to delimiter; +$b$~is still an internal quantity (named~$a$), and it has been stamped +@outer@; $c$~denotes the same internal quantity, but it hasn't got outerness. + +\endchapter + +Our life is frittered away by detail. +An honest man has hardly need +to count more than his ten fingers, +or in extreme cases he may add his ten toes, +and lump the rest. Simplicity, simplicity, simplicity! +I say, let your affairs be as two or three, +and not a hundred or a thousand .\thinspace.\thinspace. +Simplify, simplify. +\author HENRY DAVID ^{THOREAU}, {\sl Walden\/} (1854) % 1st ed, ch2, graf15 + +\bigskip + +The awesome memory of thy ever attentive computer +accepts all words as ^{truth}. +Think, therefore, in analytical, modular steps, +for the truth or untruth spoken through thy fingertips +will be acted upon unerringly. +\author HERMANN ^{ZAPF}, {\sl The Ten Commandments of % + Photo\kern1pt-\kern-1ptTypesetting\/} (1982) % 2nd Commandment + +\eject + \beginchapter Chapter 27. Recovery\\from\\Errors + +OK, everything you need to know about \MF\ has been explained---unless you +happen to be fallible. If you don't plan to make any errors, don't bother to +read this chapter. Otherwise you might find it helpful to make use of some +of the ways that \MF\ tries to pinpoint bugs in your programs. + +In the trial runs you did when reading Chapter 5, you learned the general +form of ^{error messages}, and you also learned the various ways in which +you can respond to \MF's complaints. With practice, you will be able to +correct most errors ``online,'' as soon as \MF\ has detected them, by +inserting and deleting a few things. On the other hand, some errors are +more devastating than others; one error might cause some other perfectly +valid construction to be loused~up. Furthermore, \MF\ doesn't always +diagnose your errors correctly, since the number of ways to misunderstand +the rules is vast; \MF\ is a rather simple-minded computer program that +doesn't readily comprehend the human point of view. In fact, there will be times +when you and \MF\ disagree about something that you feel makes perfectly +good sense. This chapter tries to help avoid a breakdown in communication +by explaining how to learn \MF's reasons for its actions. + +Ideally you'll be in a mellow mood when you approach \MF\!, and you will +regard any error +messages as amusing puzzles---``Why did the machine do +that?''---rather than as personal insults. +\MF\ knows how to issue more than a hundred different sorts of error messages, +and you probably never will encounter all of them, because some types of +mistakes are very hard to make. + +Let's go back to the `^|badio.mf|' example file of Chapter~5, since it +has more to teach us. If you have a better memory than the author, you'll +recall that the first error message was +\begintt +>> mode.setup +! Isolated expression. +<to be read again> + ; +l.1 mode setup; + % an intentional error! +? +\endtt +In Chapter 5 we just charged ahead at this point, but it would be more +^^{!} ^^{to be read again} +normal for a mature \MF er to think ``Shucks, I meant to type +`|mode_setup|', but I forgot the underscore. Luckily this didn't cause +any harm; \MF\ just found an ^{isolated expression}, `"mode.setup"', which +it will ignore. So let me now insert the correct command, `@mode\_setup@'.'' + +Good thinking; so you type `|I| |mode_setup|', right? Wrong~$\ldots$~sorry. +Lots of error messages occur before \MF\ has read a ^{semicolon} in +preparation for another ^{statement}; the important clue in this case +comes from the two lines +\begintt +<to be read again> + ; +\endtt +which tell us that the semicolon is still pending. So the correct +response would have been to type `|I;|~|mode_setup|' instead. Without +the semicolon, you get what appears at first to be a horrible mess: +\begintt +! Extra tokens will be flushed. +<to be read again> + warningcheck +mode_setup->warningcheck + :=0;if.unknown.mode:mode=proof;fi... +<insert> mode_setup +|quad +<to be read again> + ; +l.1 mode setup; + % an intentional error! +? +\endtt +But relax, there's a simple way out. The help message says +^^|Extra tokens will be flushed| ^^{flushing} +`Please insert a ^{semicolon} now in front of anything that you +don't want me to delete'; all you have to do is type `|I;|' and +the net effect will be the same as if you had correctly inserted a semicolon +before |mode_setup| in the first place. + +The moral of this story is: {\sl When you insert a new statement during +error recovery, you frequently need to put a semicolon just ahead of~it.} +But if you forget, \MF\ gives you another chance. + +After proceeding through |badio| with the interactions suggested in +^^|Undefined coordinate| ^^{misspelling} ^^{typographic errors} +Chap\-ter~5, we will come again to the error +\begintt +>> 0.08682thinn+144 +! Undefined x coordinate has been replaced by 0. +\endtt +(This is where the erroneous `|thinn|' was detected.) \ The help message for +this error has some curious advice: +\begintt +(Chapter 27 of The METAFONTbook explains that +you might want to type `I ???' now.) +\endtt +Chapter 27? That's us! What happens if we do type `|I ???|' now? We get +\begintt +x4l=0.08682thinn+144 +y4=-0.4924thinn+259.0005 +x4r=-0.08682thinn+144 +y4r=-0.9848thinn+259.0005 +! OK. +\endtt +It is now abundantly clear that `|thin|' was misspelled. Plain \MF\ +defines `^|???|' to be a macro that shows all of the current +dependencies between numeric variables and stops with `^|OK|'; +this is useful because a badly typed variable name might have become a +^{dependent variable} instead of an ^{independent variable}, in which +case it would be revealed by `|???|' but not by the error message. + +One more example of online error correction should suffice to make +the general strategy clear. Suppose you accidentally type square brackets +instead of parentheses; the computer will scream: +\begintt +! A primary expression can't begin with `['. +<inserted text> + 0 +<to be read again> + [ +<*> show round[ + 1 + sqrt43]; +? +\endtt +(By coincidence, the help message for this particular error also refers to +Chapter~27.) \ When \MF\ needs to see an expression, because of the tokens +it has already digested, it will try to insert `|0|' in order to keep going. +In this case we can see that zero isn't what we intended; so we type +`|7|' to delete the next seven tokens, and the computer comes back with +\begintt +<*> show round[1 + sqrt43] + ; +? +\endtt +Now `|I (1 + sqrt43)|' will insert the correct formula, and the program will +be able to continue happily as if there were no mistake. + +\exercise Why was `|7|' the right number of tokens to delete? +\answer We want to delete +\begindisplay +\ttok{0}\quad\ttok{[}\quad\ttok{1}\quad\ttok{+}\quad\ttok{sqrt}\quad +\ttok{43}\quad\ttok{]} +\enddisplay +from the sequence of tokens that \MF\ is about to read next, in order to +get rid of the right bracket, which we can see is going to be just as +erroneous as the left bracket was. However, there is another way to +proceed (and indeed, this alternative would be preferable to counting +tokens, if the bracketed expression were longer): We could simply +^^{delimiter} ^^|Missing token has been inserted| +delete 2~tokens, then `|I(|'. This would produce another error stop, +\begintt +! Missing `)' has been inserted. +<to be read again> + ] +<*> show round[1 + sqrt43] + ; +? h +I found no right delimiter to match a left one. So I've +put one in, behind the scenes; this may fix the problem. +? +\endtt +after which it's easy to delete the `|]|' and continue successfully. + +\dangerexercise If the user hadn't deleted or inserted anything, but had +just plunged ahead, \MF\ would have come up with another error: +\begintt +>> 0 +! Extra tokens will be flushed. +<to be read again> + [ +<to be read again> + (7.55743) +<to be read again> + ] +<*> show round[1 + sqrt43] + ; +? +\endtt +Explain what happened. What should be done next? +\answer \MF\ looked ahead, to see if the expression being evaluated +was going to be something like `|round 0[1+sqrt43,x]|'. But when it +found no comma, it put back several tokens so that they could be +read again. \ (The subexpression |1+sqrt43| had already been evaluated, +so a ``^{capsule}'' for its value, 7.55743, was inserted among the +tokens to be reread.) \ The expression ended with `0', and `round~0' was +shown. Then \MF\ found extra tokens following the @show@ command; a +semicolon should have come next. To continue, the user should just plunge +ahead recklessly once again, letting \MF\ delete those unwanted tokens. + +It's wise to remember that the first error in your program may well spawn +spurious ``errors'' later on, because anomalous commands can inflict +serious injury on \MF's ability to cope with the subsequent material. +But most of the time you will find that a single run through the +machine will locate all of the places in which your input conflicts +with \MF's rules. + +\danger Sometimes an error is so bad that \MF\ is forced to quit +prematurely. For example, if you are running in ^@batchmode@ or +^@nonstopmode@, \MF\ makes an ``^{emergency stop}'' if it needs +input from the terminal; this happens when a necessary file can't +be opened, or when no ^@end@ was found in the input. +Here are some of the messages you might get just before +\MF\ gives up the ghost: \enddanger + +{\ninepoint +\def\fatal#1. {\medbreak{\tt#1.}\par\nobreak\smallskip\noindent\ignorespaces} +\fatal +Fatal base file error; I'm stymied. +^^|Fatal base file error| +This means that the preloaded base you have specified cannot be used, +because it was prepared for a different version of \MF\!. +\fatal +That makes 100 errors; please try again. +\MF\ has scrolled past 100 errors since the last statement ended, so +it's probably in an~endless ^{loop}. ^^{infinite loop} +\fatal +I can't go on meeting you like this. +^^|I can't go on| +A previous error has gotten \MF\ out of whack. Fix it and try again. +\fatal +This can't happen. +^^|This can't happen| +Something is wrong with the \MF\ you are using. Complain fiercely. +\goodbreak} + +\danger There's also a dreadful message that \MF\ issues only with +great reluctance. But it can happen: +\begintt +METAFONT capacity exceeded, sorry. +\endtt +^^|METAFONT capacity exceeded| +This, alas, means that you have tried to stretch \MF\ too far. The +message will tell you what part of \MF's memory has become overloaded; +one of the following nineteen things will be mentioned: +\begindisplay +|number of strings|\qquad(strings and names of symbolic tokens and files)\cr +|pool size|\qquad(the characters in such strings)\cr +|main memory size|\qquad(pairs, paths, pens, pictures, token lists, + transforms, etc.)\cr +|hash size|\qquad(symbolic token names)\cr +|input stack size|\qquad(simultaneous input sources)\cr +|number of internals|\qquad(internal quantities)\cr +|rounding table size|\qquad(transitions between octants in cycles)\cr +|parameter stack size|\qquad(macro parameters)\cr +|buffer size|\qquad(characters in lines being read from files)\cr +|text input levels|\qquad(@input@ files and error insertions)\cr +|path size|\qquad(key points per path)\cr +|move table size|\qquad(rows of picture being simultaneously accessed)\cr +|pen polygon size|\qquad(pen offsets per octant)\cr +|ligtable size|\qquad(accumulated @ligtable@ instructions)\cr +|kern|\qquad(distinct kern amounts)\cr +|extensible|\qquad(built-up characters)\cr +|headerbyte|\qquad(largest @headerbyte@ address)\cr +|fontdimen|\qquad(largest @fontdimen@ address)\cr +|independent variables|\qquad(distinct numeric variables)\cr +\enddisplay +The current amount of memory available will also be shown. + +\danger If you have a job that doesn't overflow \MF's capacity, yet +you want to see just how closely you have approached the limits, +just set ^"tracingstats" to a positive value before the end of your +job. The log file will then conclude with a report on your actual +usage of the first nine things named above (i.e., the number of strings, +\dots, the buffer size), in that order. ^^{stack positions} +Furthermore, the @showstats@ command can be used to discover the current +string memory and main ^{memory usage} at any time during a run. +The main memory statistics are broken into two +parts; `|490&5950|' means, for example, that 490 words are being used +for ``large'' things like pens, capsules, and +transforms, while 5950 words are being used for ``small'' things like +tokens and edges. + +\danger What can be done if \MF's capacity is exceeded? All of the +above-listed components of the capacity can be increased, except the memory +for kerns and extensible characters, provided +that your computer is large enough; in fact, the space necessary to +increase one component can usually be obtained by decreasing some +other component, without increasing the total size of \MF\!\null. +If you have an especially important application, you may be able +to convince your local system people to provide you with a special +\MF\ whose capacities have been hand-tailored to your needs. +But before taking such a drastic step, be sure that you are using +\MF\ properly. If you have specified a gigantic picture that has +lots of transitions between black and white pixels, you should +change your approach, because \MF\ has to remember every change between +adjacent pixel values in every currently accessible picture. +If you keep saving different pens, you might be wasting memory as +discussed in Chapter~16. If you have built up an enormous macro library, +you should realize that \MF\ has to remember all of the replacement texts +that you define; therefore if memory space is in short supply, you should +load only the macros that you need. + +\danger Some erroneous \MF\ programs will overflow any finite +memory capacity. For example, after `|def recurse=(recurse)enddef|', the +^^{recursion} use of |recurse| will immediately bomb out: +\begintt +! METAFONT capacity exceeded, sorry [input stack size=30]. +recurse->(recurse + ) +recurse->(recurse + ) +recurse->(recurse + ) +... +\endtt +The same sort of error will obviously occur no matter how much you increase +\MF's input stack size. + +\danger Most implementations of \MF\ allow you to ^{interrupt} the program +in some way. This makes it possible to diagnose the causes of ^{infinite +loops}, if the machine doesn't stop because of memory limitations. +\MF\ switches to ^@errorstopmode@ when interrupted; hence +you have a chance to insert commands into the input: You can abort the +run, or you can ^@show@ or change the current contents of variables, +etc. In such cases you will probably want to ``^{hide}'' your diagnostic +commands, for example by typing +\begintt +I hide(showstopping:=1; alpha:=2; show x) +\endtt +so that you don't mess up the expression \MF\ is currently evaluating. +Interruption can also give you a feeling for where \MF\ is spending most +of its time, if you happen to be using an inefficient macro, since random +interrupts will tend to occur in whatever place \MF\ visits most often. + +\danger \MF's second most frustrating error messages are its occasional +claims that you have ``^{strange}'' paths. Sometimes a glance at your +output will make it clear that you did indeed specify a path that +crossed over itself, something like a figure-8; but sometimes a path +that looks fine to you will be rejected by the computer. In such +cases you need to decipher \MF's octant codes, which look scary at +first although they turn out to be helpful when you get used to them. +For example, let's reconsider |branch4| of ^{El Palo Alto}, from +the program in Chapter~14: +\begintt +branch4= + flex((0,509),(-14,492),(-32,481)) + &flex((-32,481),(-42,455),(-62,430)) + &flex((-62,430),(-20,450),(42,448)) + &flex((42,448),(38,465),(4,493),(0,509)) + &cycle; +\endtt +If the number |450| in the third ^{flex} had been |452| instead, +\MF\ would have stopped and told you this: +\begintt +> 0 SSW WSW 1 2 SSW 3 WSW 4 (WNW NNW) NNE ENE 5 ESE 6 (ENE) + NNE NNW 7 WNW NNW 8 NNE 0 (NNW WNW WSW) +! Strange path (turning number is zero). +<to be read again> + ; +<for(4)> ...]shifted(150,50)scaled(w/300); + ENDFOR +p.4,l.94 endfor + endchar; +? +\endtt +The `|for(4)|' in the fifth-last line implies that |branch4| is +at fault, because it says that the ^@for@ loop index is~4; +but the ^{octant} codes like `^|SSW|' are your only clues about why +|branch4| is considered strange. \ (A simpler example appeared +in Chapter~13, which you might want to review now.) \ +^^{compass directions} ^^|SSE|^^|ESE|^^|WSW|^^|WNW|^^|NNE|^^|NNW|^^|ENE| +You probably also have a proofmode diagram: +\displayfig 27a (34mm) +Starting at time~0, and at the point $(0,509)$, the path goes South by +Southwest, then West by Southwest until time~2 (the end of the first flex). +Then it goes |SSW| again, and |WSW| again (that's the second flex). +But at time~4, the path makes a sharp turn through the directions +|WNW| and |NNW|, {\sl without moving\/} (because these octant codes are in +parentheses). Aha! That's where the path was supposed to turn +^{counterclockwise}, through |SSW| and~|SSE| and~|ESE|; \MF\ turned clockwise +because it was the shortest way to go. The path actually makes a little +loop at time~4, between the end of the second flex and the beginning of +the third. Therefore its turning number is indeed zero, and the path is +strange by definition. + +\dangerexercise At what point do the second and third flexes cross, +in this example? +\answer The little program +\begintt +path p,q; p=flex((-32,481),(-42,455),(-62,430)); +q=flex((-62,430),(-20,452),(42,448)); +show p intersectiontimes q, p intersectionpoint q, + angle -direction 2 of p, angle direction 0 of q; end +\endtt +gives the following results: +\begintt +>> (1.88403,0.07692) +>> (-59.32149,432.59523) +>> 43.14589 +>> 45.47263 +\endtt +(Actually, the paths would also cross if |452| were |451|, but +it's such a close call that \MF\ doesn't call the path strange; +\MF\ prefers to turn ^{counterclockwise} when the amount of turn +is close enough to $180^\circ$, even if it's slightly more.) + +\danger There are three main ways to avoid problems with strange paths. +One is to stay away from paths that turn so abruptly. Or you can displace the +paths by "epsilon", as in the serif example at the end of Chapter~16. +\ (Displacing by ^"eps" would be even safer.) \ Or you can discipline +yourself to fill all cycles counterclockwise, so that you can set +^"turningcheck"$\null:=0$; this means that \MF\ won't check for +strange paths, but that's OK because tiny little loops won't hurt anything +if you are filling cycles in the correct direction. + +\ddanger Sometimes the octant codes of a strange path are shown backwards, +because the system may have tried to reverse the path to get rid of +its strangeness. + +Sooner or later---hopefully sooner---you'll get \MF\ to process your +whole file without stopping once to complain. But maybe the output +still won't be right; the mere fact that \MF\ didn't stop doesn't mean +that you can avoid looking at proofsheets. At this stage it's usually easy to +see how to fix typographic errors by correcting the input; hardcopy proofs +such as those discussed in Appendix~H usually clear up obvious mistakes, +especially if you have remembered to label the key points in your constructions. + +But your output may contain seemingly inexplicable errors. +If you can't find out what went wrong, try the old trick of simplifying +your program: Remove all the things that do work, until you obtain +the shortest possible input file that fails in the same way as the +original. The shorter the file, the easier it will be for you or somebody +else to pinpoint the problem. + +\danger One of the important tricks for shortening a buggy program is to +assign a positive value to ^"tracingspecs", because this will put all the +key points and control points of a problematic path into your log file. \ +(See the example at the end of Chapter~24, ``before subdivision.'') \ If +something is wrong with the treatment of some path, you can copy the +path's description from the log file and use it directly in \MF\ input, +thereby avoiding all the complexity of equations that might have been +involved in that path's original creation. + +\danger We've just talked about "tracingstats" and "tracingspecs"; +\MF\ is able to produce lots of other kinds of tracing. For example, +Chapter~22 discusses ^"tracingtitles", +Chapter~18 discusses ^"tracingmacros", Chapter~17 discusses +^"tracingrestores", and Chapter~9 discusses ^"tracingequations". +You can also invoke ^"tracingchoices", which shows all paths before and +after their ^{control points} are chosen according to the rules +in Chapter~14; or ^"tracingpens", which shows the pen polygons that +arise when a future pen becomes a full-fledged @pen@; or ^"tracingoutput", +which shows every picture that's shipped out, using edge-transitions +to represent the pixel values as illustrated in Chapter~13. Each of +these types of tracing is enabled by assigning a positive value to the +corresponding internal quantity; for example, you can simply set +$"tracingpens":=1$ (or~^@interim@ $"tracingpens":=1$) +if you want the data about pens. + +\danger If ^"tracingcommands"$\null=1$, \MF\ shows every ^{command} +just before it is carried out. If $"tracingcommands"=2$, \MF\ also shows +every ^{ex\-pand\-able token} just before it is expanded (except that +macros are separate, they're traced only when $"tracingmacros">0$). And if +$"tracingcommands"=3$, \MF\ also shows every ^{algebraic} ^{operation} +just before it is evaluated. Thus you can get ``stream of +consciousness'' information about everything \MF\ is doing. + +\begingroup\ninepoint +\danger ^{Digitized output} can be monitored by setting ^"tracingedges"% +$\null=1$. For example, if we ask \MF\ to draw the Ionian `{\manual\IOO}' +of Chapter~5 at a resolution of 100~pixels per inch (^"lowres" mode +with ^"mag"$\null=.5$), "tracingedges" will report as follows:\enddanger +\beginlines +|Tracing edges at line 15: (weight 1)| +|(1,5)(1,2)(2,2)(2,1)(3,1)(3,0)(8,0)(8,1)(9,1)(9,2)(10,2)(10,8)(9,8)| +|(9,9)(8,9)(8,10)(3,10)(3,9)(2,9)(2,8)(1,8)(1,5).| +\smallskip +|Tracing edges at line 15: (weight -1)| +|(3,5)(3,2)(4,2)(4,1)(7,1)(7,2)(8,2)(8,8)(7,8)(7,9)(4,9)(4,8)(3,8)(3,5).| +\endlines +By following these edges (and negating their weights on the inner boundary) +we find that the character at this low resolution is symmetric: +\begindisplay +\vbox{\offinterlineskip\manual\halign{#\hfil\cr +SSSRRRRRSSS\cr +SSRRSSSRRSS\cr +SRRSSSSSRRS\cr +SRRSSSSSRRS\cr +SRRSSSSSRRS\cr +SRRSSSSSRRS\cr +SRRSSSSSRRS\cr +SRRSSSSSRRS\cr +SSRRSSSRRSS\cr +SSSRRRRRSSS\cr}} +\enddisplay + +\endgroup +\ddanger Further information about digitization comes out when +$"tracingedges">1$, if fixed pens are used to ^@draw@ or ^@filldraw@ a +shape. In this case detailed information is presented about the activity +in each ^{octant} direction; straight line ``^{transition}'' edges are +also reported whenever \MF\ changes from one ^{penoffset} to another. + +\ddanger The "tracing"$\ldots$ commands put all of their output into your log +file, unless the ^"tracingonline" parameter is positive; in the latter +case, all diagnostic information goes to the terminal as well as to the +log file. Plain \MF\ has a ^@tracingall@ macro that turns on the +maximum amount of tracing of all kinds. It not only sets~up +"tracingcommands", "tracingedges", "tracingspecs", and so on, +it also sets $"tracingonline":=1$, and it sets ^"showstopping"$\null:=1$ so +that you can do interactive debugging via ^@show@ commands. This is the works. +There's also ^@loggingall@, which is like @tracingall@ except that it +doesn't touch "tracingonline" or "showstopping". You can say ^@interact@ +if you want just $"tracingonline":="showstopping":=1$. Finally, there's +^@tracingnone@, which shuts off every form of tracing after you've had enough. + +\ddanger Some production versions of \MF\ have been streamlined for +speed. These implementations don't look at the value of ^"tracingstats", +nor do you get extra information when $"tracingedges">1$, +because \MF\ runs faster when it doesn't have +to maintain statistics or keep tabs on whether tracing is required. +If you want all of \MF's diagnostic tools, you should be sure to +use the right version. + +\ddanger If you set ^"pausing"$\null:=1$, \MF\ will give you a chance to edit +each line of input as it is read from the file. In this way you can +make temporary patches (e.g., you can insert @show@$\ldots$ commands) +while troubleshooting, without changing the actual contents +of the file, and you can keep \MF\ running at human speed. + +Final hint: When working on a large font, it's best to prepare +only a few characters at a time. Set up a ``{test}'' file and a ``{master}'' +file, and do your work in the test file. \ (Appendix~E suggests a +convenient way to prepare control files that supply parameters to individual +test characters as well as to the whole font.) \ +After the characters come out looking right, you can append them to the +master file; and you can run the master file through \MF\ occasionally, +in order to see how the font is shaping up. Characters can always be +moved back to the test file if you have to fix some unexpected problems. + +\ddangerexercise Final exercise: Find all of the ^{lies} in this +manual, and all of the ^{jokes}. +\answer If this exercise isn't just a joke, the title of this +appendix is a lie. \ (When you've solved this exercise you might also +try to find all the lies and/or jokes that are the same in both +this book and {\sl The \TeX book}.) + +\line{Final exhortation: G{\sc O} {\sc FORTH} now and create +{\sl masterpieces of digital typography!\/}} + +\endchapter + +% Advierto tambien que en quanto \^a los rumbos del camino +With respect to the directions of the route +% puedo haver tenido alguna equivocacion. +I may have made some errors. +\author FRAY PEDRO ^{FONT}, {\sl Diary\/} (1776) + % opening remarks + +\bigskip + +The road to wisdom? Well, it's plain +and simple to express: +\tabskip\centering\halign to\hsize{#\hfil\tabskip=0pt\cr% + Err\cr% + and err\cr% + and err again\cr% + but less\cr% + and less\cr% + and less.\cr}% +\author PIET ^{HEIN}, {\sl Grooks\/} (1966) % p34 + +\eject + \beginchapter Appendix A. Answers to\\All the\\Exercises + +The preface to this manual points out the wisdom of trying to figure out +each exercise before you look up the answer here. But these answers are intended +to be read, since they occasionally provide additional information that +you are best equipped to understand when you have just worked on a problem. + +\immediate\closeout\ans % this makes the answers file ready +\ninepoint +\input answers + +\endchapter + +Looke into this Businesse thorowly, +And call these foule Offendors to their Answeres. +\author WILLIAM ^{SHAKESPEARE}, % + {\sl Second Part of Henry the Sixth\/} (1594) % Act 2 Sc 1 ll 198--199 + +\bigskip + +If you can't solve a problem, +you can always look up the answer. +But please, try first to solve it by yourself; +then you'll learn more and you'll learn faster. +\author DONALD E. ^{KNUTH}, {\sl The % + {\manual \char`\\]\char`\^\char`\_efg\char`\^}\kern1ptbook\/} (1986) + +\eject + \beginchapter Appendix B. Basic\\Operations + +This appendix defines the macros of the plain \MF\ base. Let's begin +^^{table, useful} +with an informal ^{inventory} of all the features that are available. + +\def\bb{$\,\left\{\vcenter\bgroup\halign\bgroup\hfil##\hfil\cr} +\def\ee{\crcr\egroup\egroup\right\}\,$} +\def\\{\hfil\break} +\begingroup\lineskip=3pt plus .5pt + +\medbreak\textindent\bull {\it ^{Boolean} things:\/} \ +|true|, |false|; \ \ \bb|known|\cr|unknown|\cr|cycle|\ee\<expression>;\\ +\lower2pt\vbox to 7pt{}% +\smash{\raise3pt\hbox{{\tt odd} \<numeric>; \ \ {\tt charexists} \<numeric>;}}\\ +\bb|boolean|\cr|numeric|\cr|pair|\cr|path|\cr +|pen|\cr|picture|\cr|string|\cr|transform|\ee\<expression>; \ +\bb\<boolean>\cr\<numeric>\cr\<pair>\cr\<string>\cr\<transform>\ee + \bb|<|\cr|<=|\cr|=|\cr|<>|\cr|>=|\cr|>|\ee + \bb\<boolean>\cr\<numeric>\cr\<pair>\cr\<string>\cr\<transform>\ee;\\ +\raise3pt\hbox{\strut}% +|not| \<boolean>; \ \<boolean> |and| \<boolean>; \ \<boolean> |or| \<boolean>. + +\medbreak\textindent\bull {\it ^{Numeric} things:\/} \ +|tracingtitles|, \dots, |yoffset| (see Chapter~25);\\ +|eps|, |epsilon|, |infinity|; \ |tolerance|, |join_radius|, |displaying|; \ +\<constant>;\\ +\bb|sqrt|\cr|sind|\cr|cosd|\cr|mlog|\cr|mexp|\ee\<numeric>; \ +\bb|floor|\cr|round|\cr|hround|\cr|vround|\cr|ceiling|\ee\<numeric>; \ +\bb|lft|\cr|rt|\cr|top|\cr|bot|\cr|good.x|\cr|good.y|\ee\<numeric>;\\ +\bb|xpart|\cr|ypart|\ee\bb\<pair>\cr\<transform>\ee; \ +\bb|xxpart|\cr|xypart|\cr|yxpart|\cr|yypart|\ee\<transform>; \ +\bb|ASCII|\cr|oct|\cr|hex|\ee\<string>;\\ +|normaldeviate|; \ |uniformdeviate| \<numeric>; \ |whatever|;\\ +\lower6pt\null +|angle| \<pair>; \ |turningnumber| \<cycle>; \ |totalweight| \<picture>;\\ +\bb|+|\cr\noalign{\kern-2pt}|-|\cr\noalign{\kern-2pt}\<constant>\ee\<numeric>; \ +\bb|incr|\cr|decr|\ee\<variable>; \ +|byte|\bb\<numeric>\cr\<string>\ee;\\ +\<numeric>\bb|+|\cr|-|\ee\<numeric>; \ +\<numeric>\bb|++|\cr|+-+|\ee\<numeric>;\\ +\vbox to24pt{}% +\smash{\<numeric>\bb\tt*\cr\tt/\cr\tt**\ee\<numeric>}; \ +\<numeric>\bb|mod|\cr|div|\ee\<numeric>;\\ +\<pair> |dotprod| \<pair>; \ +\bb|max|\cr|min|\ee|(|\<numerics>|)|; \ +\bb|abs|\cr|length|\ee\bb\<numeric>\cr\<pair>\cr\<path>\cr\<string>\ee;\\ +\<numeric>|[|\<numeric>|,|\<numeric>|]|; \ +|solve|\<function>|(|\<numeric>|,|\<numeric>|)|;\\ +|directiontime| \<pair> |of| \<path>. + +\medbreak\textindent\bull {\it ^{Pair} things:\/} \ +|left|, |right|, |up|, |down|, |origin|; \ +|(|\<numeric>|,|\<numeric>|)|;\\ +|z|\<suffix>; \ |dir| \<numeric>; \ |unitvector| \<pair>; \ |round| \<pair>;\\ +\bb|lft|\cr|rt|\cr|top|\cr|bot|\ee\<pair>; \ +\bb|good.lft|\cr|good.rt|\cr|good.top|\cr|good.bot|\ee\<pair>; \ +\bb|point|\cr|precontrol|\cr|postcontrol|\cr|direction|\ee% + \<numeric> |of| \<path>;\\ +\bb|+|\cr\noalign{\kern-2pt}|-|\cr\noalign{\kern-2pt}\<constant>\ee\<pair>; \ +\<pair>\bb|+|\cr|-|\ee\<pair>; \ +\<numeric>|[|\<pair>|,|\<pair>|]|;\\ +\<numeric>|*|\<pair>; \ +\<pair>\bb|*|\cr|/|\ee\<numeric>; \ +\<pair>\<transformer>;\\ +\<path>\bb|intersectionpoint|\cr|intersectiontimes|\ee\<path>; \ +\bb|max|\cr|min|\ee|(|\<pairs>|)|;\\ +\raise3pt\hbox{\strut}% +|penoffset| \<pair> |of| \<pen>; \ +|directionpoint| \<pair> |of| \<path>. + +\medbreak\textindent\bull {\it ^{Path} things:\/} \ +|quartercircle|, |halfcircle|, |fullcircle|;\\ +|unitsquare|; \ +|flex(|\<pairs>|)|; \ +|makepath| \<pen>;\\ +|superellipse(|\<pair>|,|\<pair>|,|\<pair>|,|\<pair>|,|\<numeric>|)|;\\ +|reverse| \<path>; \ +|counterclockwise| \<path>; \ +|tensepath| \<path>;\\ +\<path>\<transformer>; \ +|interpath(|\<numeric>|,|\<path>|,|\<path>|)|;\\ +\bb\<pair>\cr\<path>\ee +\bb|{|\<pair>|}|\cr|{|\<curl>|}|\cr\<empty>\ee +\bb\strut|..|\cr|...|\cr|..|\<tension>|..|\cr|..|\<controls>|..|\cr + |--|\cr|---|\cr|&|\cr|softjoin|\ee +\bb|{|\<pair>|}|\cr|{|\<curl>|}|\cr\<empty>\ee +\bb\<pair>\cr\<path>\cr|cycle|\ee;\\ +|subpath| \<pair> |of| \<path>. + +\medbreak\textindent\bull {\it ^{Pen} things:\/} \ +|pencircle|, |pensquare|, |penrazor|, |penspeck|;\\ +|nullpen|; \ |currentpen|; \ +|makepen| \<path>; \ +\<pen>\<transformer>. + +\medbreak\textindent\bull {\it ^{Picture} things:\/} \ +|nullpicture|, |blankpicture|; \ |unitpixel|;\\ +|currentpicture|; \ +\bb|+|\cr|-|\ee\<picture>; \ +\<picture>\bb|+|\cr|-|\ee\<picture>;\\ +\<picture>\<transformer>. + +\medbreak\textindent\bull {\it ^{String} things:\/} \ +|"constant"|; \ |ditto|; \ |jobname|; \ |readstring|;\\ +|str|\<suffix>; \ +|decimal| \<numeric>; \ +|char| \<numeric>;\\ +\<string> |&| \<string>; \ +\bb|max|\cr|min|\ee|(|\<strings>|)|; \ +|substring| \<pair> |of| \<string>. + +\medbreak\textindent\bull {\it ^{Transform} things:\/} \ +|identity|; \ |currenttransform|;\\ +|inverse| \<transform>; \ +\<transform>\<transformer>. + +\advance\lineskip by 3pt +\advance\medskipamount by 3pt +\medbreak\textindent\bull {\it ^{Transformers}:\/} \ +|transformed| \<transform>;\\ +\bb|rotated|\cr|slanted|\ee\<numeric>; \ +\bb|scaled|\cr|xscaled|\cr|yscaled|\ee\<numeric>; \ +\bb|shifted|\cr|zscaled|\ee\<pair>;\\ +|reflectedabout(|\<pair>|,|\<pair>|)|; \ +|rotatedaround(|\<pair>|,|\<numeric>|)|. + +\medbreak\textindent\bull {\it ^{Conditions}:\/}\\ +|if| \<boolean>|: |\<text> \bb|elseif|\<boolean>|: |\<text>\ee$^{\ge0}$% +\bb|else:| \<text>\cr\<empty>\ee|fi|. + +\smallbreak\textindent\bull {\it ^{Loops}:\/} \ |forever:| \<text> |endfor|;\\ +|for| $\nu$ \bb|=|\cr|:=|\ee +\bb\<numeric> |upto| \<numeric>\cr + \<numeric> |downto| \<numeric>\cr + \<numeric>\thinspace|step|\thinspace + \<numeric>\thinspace|until|\thinspace\<numeric>\ee +|:| \<text$(\nu)$> |endfor|;\\ +|for| $\epsilon$ \bb|=|\cr|:=|\ee + \<expressions>|:| \<text$(\epsilon)$> |endfor|;\\ +|forsuffixes| $\sigma$ \bb|=|\cr|:=|\ee + \<suffixes>|:| \<text$(\sigma)$> |endfor|;\\ +|exitif| \<boolean>|;| ; \quad +|exitunless| \<boolean>|;| . + +\medbreak\textindent\bull {\it ^{Diagnostic things}:\/} \ +|???|; \ |interact|; \ +|hide(|\<statements>|)|;\\ +|loggingall|, |tracingall|, |tracingnone|. + +\textindent\bull {\it ^{Starting a job}:\/} \ +|\mode=|\<modename>; \ |mag=|\bb\<numeric>\cr|magstep|\<numeric>\ee;\\ +|screenchars|; \ |screenstrokes|; \ |imagerules|; \ |gfcorners|; \ +|nodisplays|;\\ +|notransforms|; \ |input| \<filename>. + +\medbreak\textindent\bull {\it ^{Conversion to pixel units}:\/} \ +|mode_setup|; \ |fix_units|;\\ +|pixels_per_inch|, |blacker|, |fillin|, |o_correction|;\\ +|mm#|, |cm#|, |pt#|, |pc#|, |dd#|, |cc#|, |bp#|, |in#|;\\ +|mm|, |cm|, |pt|, |pc|, |dd|, |cc|, |bp|, |in|;\\ +|mode_def|; \ |extra_setup|;\\ +\bb|define_pixels|\cr +|define_whole_pixels|\cr +|define_whole_vertical_pixels|\cr +|define_good_x_pixels|\cr +|define_good_y_pixels|\cr +|define_blacker_pixels|\cr +|define_whole_blacker_pixels|\cr +|define_whole_vertical_blacker_pixels|\cr +|define_corrected_pixels|\cr +|define_horizontal_corrected_pixels|\cr +|lowres_fix|\ee|(|\<names>|)|. + +\advance\lineskip by-4pt +\advance\medskipamount by-4pt +\medbreak\textindent\bull {\it Character and font administration:\/}\\ +|beginchar(|\<code>|,|\<width$\0$>|,|\<height$\0$>|,|\<depth$\0$>|)|; \ \ +|extra_beginchar|;\\ +|italcorr| \<numeric$\0$>; \ |change_width|; \ |endchar|; \ \ +|extra_endchar|;\\ +\bb|font_size|\cr|font_slant|\cr|font_normal_space|\cr + |font_normal_stretch|\cr|font_normal_shrink|\cr|font_x_height|\cr + |font_quad|\cr|font_extra_space|\ee +\bb|=|\cr\noalign{\kern-2pt}|:=|\cr\noalign{\kern-2pt}\<empty>\ee +\<numeric$\0$>; \ +\bb|ligtable|\<ligs/kerns>\cr|charlist|\<codes>\cr|extensible|\<codes>\cr + |fontdimen|\<info>\cr|headerbytes|\<info>\ee;\\ +\bb|font_identifier|\cr|font_coding_scheme|\ee +\smash{\bb\tt=\cr\noalign{\kern-2pt}\tt:=\cr\noalign{\kern-2pt}\<empty>\ee}% +\<string>. + +\medbreak\textindent\bull {\it ^{Drawing}:\/} \ +|penpos|\<suffix>|(|\<length>|,|\<angle>|)|; \ +|penstroke| \<path($e$)>;\\ +|pickup|\bb\<pen>\cr\<saved pen number>\ee; \ +\<pen number>|:=savepen|; \ |clear_pen_memory|;\\ +\lower6pt\null|pen_lft|, |pen_rt|, |pen_top|, |pen_bot|;\\ +\bb|fill|\cr|unfill|\cr|filldraw|\cr|unfilldraw|\ee\<cycle>; \ +\bb|draw|\cr|undraw|\cr|cutdraw|\ee\<path>; \ +\bb|drawdot|\cr|undrawdot|\ee\<pair>;\\ +\vbox to 10pt{}|erase| \<picture command>; \ +|cutoff(|\<pair>|,|\<angle>|)|;\\ +|addto| \<picture variable> |also| \<picture>;\\ +|addto| \<picture variable>\bb|contour| \<cycle>\cr|doublepath| \<path>\ee + $\hbox{\bb|withpen|\<pen>\cr|withweight|\<numeric>\ee}^ + {\smash{\lower3pt\hbox{$\scriptstyle\ge0$}}}\!$;\\ +|cull| \<picture variable>\bb|keeping|\cr|dropping|\ee\<pair>% + \bb|withweight|\<numeric>\cr\<empty>\ee. + +\medbreak\textindent\bull {\it ^{Screen display}:\/} \ +|currentwindow|; \ +|screen_rows|, |screen_cols|;\\ +|openwindow| \<numeric> |from| \<screen pair> |to| \<screen pair> + |at| \<pair>;\\ +|display| \<picture variable> |inwindow| \<numeric>. + +\advance\lineskip by 4pt +\advance\medskipamount by 2pt +\medbreak\textindent\bull {\it ^{Statements}:\/} \ +\<empty>; \ \<string>; \ |begingroup| \<statements> |endgroup|;\\ +\bb\<boolean>\cr\<numeric>\cr\<pair>\cr\<path>\cr + \<pen>\cr\<picture>\cr\<string>\cr\<transform>\ee +$\hbox{\bb\bb|=|\cr|:=|\ee + \bb\<boolean>\cr\<numeric>\cr\<pair>\cr\<path>\cr + \<pen>\cr\<picture>\cr\<string>\cr\<transform>\ee\ee}^ + {\smash{\lower6pt\hbox{$\scriptstyle\ge1$}}}\!\!$; \qquad +\bb|boolean|\cr|numeric|\cr|pair|\cr|path|\cr +|pen|\cr|picture|\cr|string|\cr\thinspace|transform|\thinspace\ee\<names>;\\ +|save| \<names>; \ +|interim| \<internal> |:=| \<numeric>; \ +|let| \<name>\bb|=|\cr|:=|\ee\<name>;\\ +\bb|def|\cr|vardef|\ee\<name>\<parameters>\bb|=|\cr|:=|\ee + \<text>\thinspace|enddef|;\\ +\vbox to24pt{}\bb|primarydef|\cr|secondarydef|\cr|tertiarydef|\ee + \ $\alpha$ \<name> $\beta$ \bb|=|\cr|:=|\ee + \<text$(\alpha,\beta)$>\thinspace|enddef|;\\ +\strut|showit|; \ |shipit|; \ |cullit|; \ |openit|; \ +|clearit|; \ |clearxy|; \ |clearpen|;\\ +|stop| \<string>; \ +|show| \<expressions>; \ +\bb|message|\cr|errmessage|\cr|errhelp|\ee\<string>;\\ +\bb|showvariable|\cr|showtoken|\ee\<names>; \ +\bb|showdependencies|\cr|showstats|\ee;\\ +\strut see also Chapter 26 for some more exotic commands. + +\advance\lineskip by -1pt +\advance\medskipamount by 1pt +\medbreak\textindent\bull {\it ^{Proofsheet} information:\/}\\ +\bb|labels|\cr|penlabels|\ee + \bb|top|\cr|lft|\cr|rt|\cr|bot|\cr\<empty>\ee + \bb|nodot|\cr\<empty>\ee + |(|\<suffixes>|)|;\\ +|makelabel|\bb|top|\cr|lft|\cr|rt|\cr|bot|\cr\<empty>\ee + \bb|nodot|\cr\<empty>\ee + |(|\<string>|,|\<pair>|)|; \ +\bb|titlefont|\cr|labelfont|\cr|grayfont|\cr|slantfont|\ee \<name>;\\ +\bb|proofrule|\cr|screenrule|\ee|(|\<pair>|,|\<pair>|)|; \ +|makegrid(|\<pairs>|)(|\<pairs>|)|;\\ +|proofrulethickness| \<numeric>; \ |proofoffset| \<pair>. + +\medbreak\textindent\bull {\it Hacks:\/} \ |gobble|, |gobbled|, |killtext|; \ +|capsule_def|; \ |numtok|. + +\medbreak +\endgroup % end of special \lineskip +\ninepoint +\danger The remainder of this appendix contains an edited transcript +of the ``plain ^{base file},'' which is a set of macros that come with +normal implementations of \MF\!\null. These macros serve three basic purposes: +\ (1)~They make \MF\ usable, because \MF's primitive capabilities +operate at a very low level. A~``virgin'' \MF\ system that has no +macros is like a newborn baby that has an immense amount to learn about +the real world; but it is capable of learning fast. \ (2)~The plain +\MF\ macros provide a basis for more elaborate and powerful bases +tailored to individual tastes and applications. You can do a lot with +plain \MF\!, but pretty soon you'll want to do even more. \ (3)~The macros +also serve to illustrate how additional bases can be designed. \enddanger + +Somewhere in your computer system you should be able to find a file called +^|plain.mf| that contains what has been preloaded into the +running \MF\ system that you use. That file should match the +code discussed below, except that it might do some things in an +equivalent but slightly more efficient manner. + +When we come to macros whose use has not yet been explained---for +example, somehow |softjoin| and |stop| never made it +into Chapters 1 through~27---we shall consider them from a user's +viewpoint. But most of the comments that follow are addressed to a +potential base-file designer. + +A special program called ^|INIMF| is used to install \MF; |INIMF| is +just like \MF\ except that it is able to `^@dump@' a base file +suitable for preloading. This operation requires additional program +space, so |INIMF| generally has less memory available +than you would expect to find in a production version of \MF\!. + +\subsection Getting started. A base file has to have a ^@delimiters@ +command near the beginning, since |INIMF| doesn't have any delimiters +built~in. The first few lines usually also give the base file a name and +version number as shown here. +\beginlines +|% This is the plain METAFONT base that's described in The METAFONTbook.| +|% N.B.: Please change "base_version" whenever this file is modified!| +|% And don't modify the file under any circumstances.| +|string base_name, base_version; base_name="plain"; base_version="2.71";| +\smallskip +^|message|| "Preloading the plain base, version " & base_version;| +\smallskip +|delimiters (); % this makes parentheses behave like parentheses| +\endlines + +Next we define some of the simplest macros, which provide ``syntactic sugar'' +for commonly occurring idioms. ^^{blash blash} +For example, `@stop@ |"hello"|' displays `|hello|' on the terminal and waits +until \<return> is typed. +\beginlines +|def |^|upto|| = step 1 until enddef; def |^|downto|| = step -1 until enddef;| +|def |^|exitunless|| expr c = exitif not c enddef;| +|let |^|relax|| = \; % ignore the word `relax', as in TeX| +|let \\ = \; % double relaxation is like single| +|def |^|]]|| = ] ] enddef; % right brackets should be loners| +|def |^|--|| = {curl 1}..{curl 1} enddef;| +|def |^|---|| = .. tension infinity .. enddef;| +|def |^|...|| = .. tension atleast 1 .. enddef;| +\smallskip +|def |^|gobble|| primary g = enddef; def |^|killtext|| text t = enddef;| +|primarydef g |^|gobbled|| gg = enddef;| +|def |^|hide||(text t) = exitif numeric begingroup t; endgroup; enddef;| +|def |^|???|| = hide(interim showstopping:=1; showdependencies) enddef;| +|def |^|stop|| expr s = message s; gobble readstring enddef;| +\endlines +(Chapter 20 points out that `|\|' is an expandable token that expands +into nothing. Plain \MF\ allows also `|\\|', because there's a +formatting program called ^|MFT| that uses~|\\| to insert extra spacing +in a ^{pretty-printed} listing.) \ The ``clever'' code for @hide@ +is based on the fact that a ^{vacuous} expression is not numeric; +hence no loop is exited, ^^@exitif@ and the computer doesn't mind the +fact that we may not be in a loop at all. + +The values of ^{internal quantities} are next on the agenda: +\beginlines +^|smoothing||:=1; |^|autorounding||:=2; % this adjusts curves to the raster| +^|turningcheck||:=2; % this will warn about a "strange path"| +^|granularity||:=1; % this says that pixels are pixels| +\smallskip +|def |^|interact|| = % prepares to make "show" commands stop| +| hide(showstopping:=1; tracingonline:=1) enddef;| +|def |^|loggingall|| = % puts tracing info into the log| +| tracingcommands:=3; tracingedges:=2; tracingtitles:=1;| +| tracingequations:=1; tracingcapsules:=1; tracingspecs:=1;| +| tracingpens:=1; tracingchoices:=1; tracingstats:=1;| +| tracingoutput:=1; tracingmacros:=1; tracingrestores:=1;| +| enddef;| +|def |^|tracingall|| = % turns on every form of tracing| +| tracingonline:=1; showstopping:=1; loggingall enddef;| +|def |^|tracingnone|| = % turns off every form of tracing| +| tracingcommands:=0; tracingonline:=0; showstopping:=0;| +| tracingedges:=0; tracingtitles:=0; tracingequations:=0;| +| tracingcapsules:=0; tracingspecs:=0; tracingpens:=0;| +| tracingchoices:=0; tracingstats:=0; tracingoutput:=0;| +| tracingmacros:=0; tracingrestores:=0; enddef;| +\endlines +The user can say @interact@ in the midst of a statement; but +@loggingall@, @tracingall@, and @tracingnone@ should come +between statements. \ (You don't need a ^{semicolon} after them, +because they come equipped with their own closing `|;|'.) + +\subsection Math routines. The second major part of |plain.mf| +contains the definitions of basic constants and mathematical +macros that extend the primitive capabilities of \MF's expressions. +\beginlines +|% numeric constants| +|newinternal eps,epsilon,infinity;| +^|eps|| := .00049; % this is a pretty small positive number| +^|epsilon|| := 1/256/256; % but this is the smallest| +^|infinity|| := 4095.99998; % and this is the largest| +\smallbreak +|% pair constants| +|pair right,left,up,down,origin;| +^|origin||=(0,0); |^|up||=-|^|down||=(0,1); |^|right||=-|^|left||=(1,0);| +\smallbreak +|% path constants| +|path quartercircle,halfcircle,fullcircle,unitsquare;| +^|quartercircle||=(right{up}..(right+up)/sqrt2..up{left}) scaled .5;| +^|halfcircle||=quartercircle & quartercircle rotated 90;| +^|fullcircle||=halfcircle & halfcircle rotated 180 & cycle;| +^|unitsquare||=(0,0)--(1,0)--(1,1)--(0,1)--cycle;| +\smallbreak +|% transform constants| +|transform identity;| +|for z=origin,right,up: z transformed |^|identity|| = z; endfor| +\smallbreak +|% picture constants| +|picture blankpicture,unitpixel;| +^|blankpicture||=nullpicture; % `display blankpicture...'| +^|unitpixel||=nullpicture; addto unitpixel contour unitsquare;| +\smallbreak +|% string constants| +|string ditto; |^|ditto|| = char 34; % ASCII double-quote mark| +\smallbreak +|% pen constants| +|def capsule_def(suffix s) primary u = def s = u enddef enddef;| +|capsule_def(pensquare) makepen(unitsquare shifted -(.5,.5));| +|capsule_def(penrazor) makepen((-.5,0)--(.5,0)--cycle);| +|pen penspeck; penspeck=pensquare scaled eps;| +\endlines +The ^@pensquare@ and ^@penrazor@ constants are defined here in a +surprisingly roundabout way, just so that they can be ^{future pens} +instead of pens. \MF\ can transform a future pen much faster than a +pen, since pens have a complex internal data structure, so this +trick saves time. But how does it work? Well, a variable cannot +be a future pen, but a ^{capsule} can; hence @pensquare@ and @penrazor@ +are defined, via ^@capsule\_def@, to be macros that expand into single capsules. +Incidentally, ^@penspeck@ is an extremely tiny little pen that is used by the +@drawdot@ macro. Since it is not intended to be transformed, +we are better off making it a pen; then it's immediately ready for use. + +Now that the basic constants have been defined, we turn to +mathematical operations. There's one operation that has no arguments: +\beginlines +|% nullary operators| +|vardef |^|whatever|| = save ?; ? enddef;| +\endlines +The reasoning behind this is discussed in exercise 17.\Xwhat. + +Operations that take one argument are introduced next. +\beginlines +|% unary operators| +|let |^|abs|| = length;| +\smallskip +|vardef |^|round|| primary u =| +| if numeric u: floor(u+.5)| +| elseif pair u: (hround xpart u, vround ypart u)| +| else: u fi enddef;| +\smallskip +|vardef |^|hround|| primary x = floor(x+.5) enddef;| +|vardef |^|vround|| primary y = floor(y.o_+.5)_o_ enddef;| +\smallskip +|vardef |^|ceiling|| primary x = -floor(-x) enddef;| +\smallbreak +|vardef |^|byte|| primary s = if string s: ASCII fi s enddef;| +\smallbreak +|vardef |^|dir|| primary d = right rotated d enddef;| +\smallskip +|vardef |^|unitvector|| primary z = z/abs z enddef;| +\smallbreak +|vardef |^|inverse|| primary T =| +| transform T_; T_ transformed T = identity; T_ enddef;| +\smallbreak +|vardef |^|counterclockwise|| primary c =| +| if turningcheck>0:| +| interim |^|autorounding||:=0;| +| if |^|turningnumber|| c <= 0: reverse fi fi c enddef;| +\smallbreak +|vardef |^|tensepath|| expr r =| +| for k=0 upto length r - 1: point k of r --- endfor| +| if cycle r: cycle else: point infinity of r fi enddef;| +\endlines +Notice that the variable `|T_|' was not saved by the "inverse" +function. The plain base routines gain ^{efficiency} by +using ``^{private}'' tokens that are assumed to be distinct +from any of the user's tokens; these private tokens always +end with the ^{underscore} character,~`|_|'. If ordinary user programs +never contain such token names, no surprises will occur, +provided that different macro designers who combine their routines are +careful that their private names are not in conflict. + +The private tokens `|o_|' and `|_o_|' used in |vround| stand +for `|*aspect_ratio|' and `|/aspect_ratio|', respectively, +as we shall see shortly. + +Now we define `mod' and `div', being careful to do this in such a way that +the identities $a(x\;\hbox{mod}\;y)=(ax)\;\hbox{mod}\;(ay)$ and +$(ax)\;\hbox{div}\;(ay)=x\;\hbox{div}\;y$ are valid. +\beginlines +|% binary operators| +|primarydef x |^|mod|| y = (x-y*floor(x/y)) enddef;| +|primarydef x |^|div|| y = floor(x/y) enddef;| +|primarydef w |^|dotprod|| z = (xpart w * xpart z + ypart w * ypart z) enddef;| +\endlines + +The `|**|' operator is designed to be most efficient when it's used +for squaring. A separate `^|takepower|' routine is used for exponents +other than~2, so that \MF\ doesn't have to skip over lots of tokens +in the common case. The |takepower| routine is careful to give the +correct answer in expressions like `|(-2)**(-3)|' and `|0**0|'. +\beginlines +|primarydef x |^|**|| y = if y=2: x*x else: takepower y of x fi enddef;| +|def takepower expr y of x =| +| if x>0: mexp(y*mlog x)| +| elseif (x=0) and (y>0): 0| +| else: 1| +| if y=floor y:| +| if y>=0: for n=1 upto y: *x endfor| +| else: for n=-1 downto y: /x endfor fi| +| else: hide(errmessage "Undefined power: " & decimal x&"**"&decimal y)| +| fi fi enddef;| +\endlines + +\MF's primitive path operations have been defined in such a way that the +following higher-level operations are easy: +\beginlines +|vardef |^|direction|| expr t of p =| +| postcontrol t of p - precontrol t of p enddef;| +\smallskip +|vardef |^|directionpoint|| expr z of p =| +| a_:=|^|directiontime|| z of p;| +| if a_<0: errmessage("The direction doesn't occur"); fi| +| point a_ of p enddef;| +\smallskip +|secondarydef p |^|intersectionpoint|| q =| +| begingroup save x_,y_; (x_,y_)=p |^|intersectiontimes|| q;| +| if x_<0: errmessage("The paths don't intersect"); (0,0)| +| else: .5[point x_ of p, point y_ of q] fi endgroup| +|enddef;| +\weakendlines +The private token `|a_|' will be declared as an ^{internal quantity}. +Internal quantities are more ^{efficient} than ordinary numeric variables. + +Plain \MF's `^{softjoin}' operation provides a way to hook paths together +without the abrupt change of direction implied by~`|&|'. Assuming that +the final point of~$p$ is the first point of~$q$, the path `$p$~softjoin~$q$' +begins on~$p$ until coming within "join\_radius" of this common point; +then it curves over and finishes~$q$ in essentially the same way. +The internal quantity ^"join\_radius" should be set to the desired +value before softjoin is applied. \ (This routine is due to N.~N. ^{Billawala}.) +\beginlines +|tertiarydef p softjoin q =| +| begingroup c_:=|^|fullcircle|| scaled 2join_radius shifted point 0 of q;| +| a_:=ypart(c_ intersectiontimes p); b_:=ypart(c_ intersectiontimes q);| +| if a_<0:point 0 of p{direction 0 of p} else: subpath(0,a_) of p fi| +| ... if b_<0:{direction infinity of q}point infinity of q| +| else: subpath(b_,infinity) of q fi endgroup enddef;| +|newinternal join_radius,a_,b_; path c_;| +\endlines + +The remaining math operators don't fall into the ordinary patterns; something +is unusual about each of them. First we have `|incr|' and `|decr|', which apply +only to variables; they have the side effect of changing the variable's value. +\beginlines +% special operators +|vardef |^|incr|| suffix $ = $:=$+1; $ enddef;| +|vardef |^|decr|| suffix $ = $:=$-1; $ enddef;| +\weakendlines +You can say either `|incr|~|x|' or `|incr|~|(x)|', within +an expression; but `|incr|~|x|' by itself is not a valid statement. + +To reflect about a line, we compute a ^{transform} on the fly: +\beginlines +|def |^|reflectedabout||(expr w,z) = % reflects about the line w..z| +| transformed| +| begingroup transform T_;| +| w transformed T_ = w; z transformed T_ = z;| +| xxpart T_ = -yypart T_; xypart T_ = yxpart T_; % T_ is a reflection| +| T_ endgroup enddef;| +\smallskip +|def |^|rotatedaround||(expr z, d) = % rotates d degrees around z| +| shifted -z rotated d shifted z enddef;| +|let |^|rotatedabout|| = rotatedaround; % for roundabout people| +\endlines + +Now we come to an interesting trick: The user writes something like +`min$(a,b)$' or `max$(a,b,c,d)$', and +\MF's notation for macro calls makes it easy to separate the first argument +from the rest---assuming that at least two arguments are present. +\beginlines +|vardef |^|max||(expr u)(text t) = % t is a list of numerics, pairs, or strings| +| save u_; setu_ u; for uu = t: if uu>u_: u_:=uu; fi endfor| +| u_ enddef;| +\smallskip +|vardef |^|min||(expr u)(text t) = % t is a list of numerics, pairs, or strings| +| save u_; setu_ u; for uu = t: if uu<u_: u_:=uu; fi endfor| +| u_ enddef;| +\smallskip +|def setu_ primary u =| +| if pair u: pair u_ elseif string u: string u_ fi;| +| u_=u enddef;| +\weakendlines +^^"setu\_" Appendix D discusses some variations on this theme. + +The ^|flex| routine defines part of a path whose directions at the +endpoints will depend on the environment, because this path is not +enclosed in parentheses. +\beginlines +|def flex(text t) = % t is a list of pairs| +| hide(n_:=0; for z=t: z_[incr n_]:=z; endfor| +| dz_:=z_[n_]-z_1)| +| z_1 for k=2 upto n_-1: ...z_[k]{dz_} endfor ...z_[n_] enddef;| +|newinternal n_; pair z_[],dz_;| +\endlines + +The five parameters to `superellipse' are the right, the top, the left, +the bottom, and the superness. +\beginlines +|def |^|superellipse||(expr r,t,l,b,s)=| +| r{up}...(s[xpart t,xpart r],s[ypart r,ypart t]){t-r}...| +| t{left}...(s[xpart t,xpart l],s[ypart l,ypart t]){l-t}...| +| l{down}...(s[xpart b,xpart l],s[ypart l,ypart b]){b-l}...| +| b{right}...(s[xpart b,xpart r],s[ypart r,ypart b]){r-b}...cycle enddef;| +\endlines + +Chapter~14 illustrates the `interpath' routine, which interpolates +between paths to find a path that would be written `$a[p,q]$' if +\MF's macro notation were more general. +\beginlines +|vardef |^|interpath||(expr a,p,q) =| +| for t=0 upto length p-1: a[point t of p, point t of q]| +| ..controls a[postcontrol t of p, postcontrol t of q]| +| and a[precontrol t+1 of p, precontrol t+1 of q] .. endfor| +| if cycle p: cycle| +| else: a[point infinity of p, point infinity of q] fi enddef;| +\endlines + +Finally we come to the "solve" macro, which has already been presented +in Chapter~20. Appendix~D gives further illustrations of its use. +\beginlines +|vardef |^|solve||@#(expr true_x,false_x)= % @#(true_x)=true, @#(false_x)=false| +| tx_:=true_x; fx_:=false_x;| +| forever: x_:=.5[tx_,fx_]; exitif abs(tx_-fx_)<=tolerance;| +| if @#(x_): tx_ else: fx_ fi :=x_; endfor| +| x_ enddef; % now x_ is near where @# changes from true to false| +|newinternal |^|tolerance||, tx_,fx_,x_; tolerance:=.1;| +\finalendlines + +\subsection Conversion to pixels. The next main subdivision of |plain.mf| +contains macros and constants that help convert dimensions from +device-independent ``sharped'' or ``true'' units into the pixel units +corresponding to a particular device. First comes a subroutine that +computes eight basic units, assuming that the number +^^{mm} ^^{cm} ^^{pt} ^^{pc} ^^{dd} ^^{cc} ^^{bp} ^^{in} ^^@fix\_units@ +of ^"pixels\_per\_inch" is known: +\beginlines +|def fix_units = % define the conversion factors, given pixels_per_inch| +| mm:=pixels_per_inch/25.4; cm:=pixels_per_inch/2.54;| +| pt:=pixels_per_inch/72.27; pc:=pixels_per_inch/6.0225;| +| dd:=1238/1157pt; cc:=12dd;| +| bp:=pixels_per_inch/72; in:=pixels_per_inch;| +| hppp:=pt; % horizontal pixels per point| +| vppp:=aspect_ratio*hppp; % vertical pixels per point| +| enddef;| +\endlines + +^{Sharped units} are actually expressed in terms of points, but a virtuous +user will not write programs that exploit this fact. +\beginlines +|mm#=2.84528; pt#=1; dd#=1.07001; bp#=1.00375;| +|cm#=28.45276; pc#=12; cc#=12.84010; in#=72.27;| +\endlines + +A particular device is supposed to be modeled by four parameters, called +^"pixels\_per\_inch", ^"blacker", ^"o\_correction", and ^"fillin", as discussed +in Chapter~11. Appropriate +values will be assigned to these internal quantities by @mode\_setup@. +\beginlines +|newinternal pixels_per_inch; % the given resolution| +|newinternal blacker, o_correction; % device-oriented corrections| +\endlines +(The fourth parameter, "fillin", is already an internal quantity of \MF\!.) + +Here are the ten principal ways to convert from +^^{define\_pixels (and nine others)} sharped units to pixels: +\beginlines +|def define_pixels(text t) =| +| forsuffixes $=t: $:=$.#*hppp; endfor enddef;| +|def define_whole_pixels(text t) =| +| forsuffixes $=t: $:=hround($.#*hppp); endfor enddef;| +|def define_whole_vertical_pixels(text t) =| +| forsuffixes $=t: $:=vround($.#*hppp); endfor enddef;| +|def define_good_x_pixels(text t) =| +| forsuffixes $=t: $:=good.x($.#*hppp); endfor enddef;| +|def define_good_y_pixels(text t) =| +| forsuffixes $=t: $:=good.y($.#*hppp); endfor enddef;| +|def define_blacker_pixels(text t) =| +| forsuffixes $=t: $:=$.#*hppp+blacker; endfor enddef;| +|def define_whole_blacker_pixels(text t) =| +| forsuffixes $=t: $:=hround($.#*hppp+blacker);| +| if $<=0: $:=1; fi endfor enddef;| +|def define_whole_vertical_blacker_pixels(text t) =| +| forsuffixes $=t: $:=vround($.#*hppp+blacker);| +| if $<=0: $:=1_o_; fi endfor enddef;| +|def define_corrected_pixels(text t) =| +| forsuffixes $=t: $:=vround($.#*hppp*o_correction)+eps; endfor enddef;| +|def define_horizontal_corrected_pixels(text t) =| +| forsuffixes $=t: $:=hround($.#*hppp*o_correction)+eps; endfor enddef;| +\endlines + +Chapter 24 discusses the ^@lowres\_fix@ routine, which helps to correct +anomalies that may have occurred when sharped dimensions were rounded +to whole pixels. +\beginlines +|def lowres_fix(text t) expr ratio =| +| begingroup save min,max,first;| +| forsuffixes $=t:| +| if unknown min: min=max=first=$; min#=max#=$.#;| +| elseif $.#<min#: min:=$; min#:=$.#;| +| elseif $.#>max#: max:=$; max#:=$.#; fi endfor| +| if max/min>ratio*max#/min#: forsuffixes $=t: $:=first; endfor fi| +| endgroup enddef;| +\finalendlines + +\subsection Modes of operation. The standard way to create a font with +plain \MF\ is to start~up the program by saying +\begindisplay +|\mode=|\<mode name>|; mag=|\<magnification>|; input |% + \<font file name> +\enddisplay +in response to \MF's initial `^|**|'. +The ^|mag| is omitted if the magnification is~1, and the ^|mode| is omitted +if |mode=proof|. Additional commands like `|screenchars|' might be +given before the `^|input|'; we shall discuss them later. If you are +using another base file, called say the `|super|' base, this whole +command line should be preceded by `|&super|'. The mode name should have +been predeclared in your base file, by the |mode_def| routine below. +If, however, you need a special mode that isn't in the base, you can put +its commands into a file (e.g., `|specmode.mf|') and invoke it +by saying +\begindisplay +^|\smode||="specmode"; mag=|$\,\cdots$ +\enddisplay +instead of giving a predeclared mode name. + +Here is the ^@mode\_setup@ routine, +which is usually one of the first macros to be called in a \MF\ program: +\beginlines +|def mode_setup =| +| warningcheck:=0;| +| if unknown mode: mode=proof; fi| +| numeric aspect_ratio; transform currenttransform;| +| scantokens if string mode:("input "&mode) else: mode_name[mode] fi;| +| if unknown mag: mag=1; fi| +| if unknown aspect_ratio: aspect_ratio=1; fi| +| displaying:=proofing;| +| pixels_per_inch:=pixels_per_inch*mag;| +| if aspect_ratio=1: let o_=\; let _o_=\| +| else: def o_=*aspect_ratio enddef; def _o_=/aspect_ratio enddef fi;| +| fix_units;| +| scantokens extra_setup; % the user's special last-minute adjustments| +| currenttransform:=| +| if unknown currenttransform: identity else: currenttransform fi| +| yscaled aspect_ratio;| +| clearit;| +| pickup pencircle scaled (.4pt+blacker);| +| warningcheck:=1; enddef;| +|def |^|smode|| = string mode; mode enddef;| +|string extra_setup, mode_name[];| +|extra_setup=""; % usually there's nothing special to do| +|newinternal |^|displaying||; % if positive, endchar will `showit'| +\endlines +^^"extra\_setup" ^^"mode\_name" +The first `^@scantokens@' in @mode\_setup@ either reads a special +file or calls a macro that expands into commands defining the mode. +Notice that "aspect\_ratio" is always cleared to an undefined value +when these commands are performed; you can't simply give a value to +"aspect\_ratio" when you set "mode" and~"mag". If the aspect ratio +isn't assigned a definite value by the mode routine, it will become unity, +and the `|o_|' and `|_o_|' operations will be omitted from subsequent +calculations. Notice also that the mode commands might do something special +to "mag", since "mag" isn't examined until after the mode routine has +acted. The "currenttransform" might also be given a special value. \MF's +^"warningcheck" is temporarily disabled during these computations, since +there might be more than 4096 pixels per inch. After @mode\_setup@ is +finished, the "currentpicture" will be null, the "currenttransform" +will take the "aspect\_ratio" into account, and the "currentpen" will be a +circular nib with the standard default thickness of $0.4\pt$. \ (You should +save this pen if you want to use it in a character, because @beginchar@ +will clear it away.) + +Plain \TeX\ has a convention for magnifying fonts in terms of ``magsteps,'' +where magstep~$m=1.2^m$. A geometric progression of font sizes is +convenient, because scaling by magstep~$m$ followed by magstep~$n$ is +^^{mexp} equivalent to scaling by magstep~$m+n$. +\beginlines +|vardef |^|magstep|| primary m = mexp(46.67432m) enddef;| +\endlines + +When a mode is defined (e.g., `|proof|'), a numeric variable of that +name is created and assigned a unique number (e.g.,~1). Then an +^{underscore} character is appended, and a macro is defined for the +resulting name (e.g., `|proof_|'). The "mode\_name" array is used to +convert between number and name (e.g., "mode\_name"$_1=\null$|"proof_"|). +\beginlines +|def mode_def suffix $ =| +| $:=incr number_of_modes;| +| mode_name[$]:=str$ & "_";| +| |^|expandafter|| |^|quote|| def scantokens mode_name[$] enddef;| +|newinternal number_of_modes;| +\endlines +(This ^@mode\_def@ strategy was suggested by Bruce ^{Leban}.) + +Three basic modes are now defined, starting with two for proofing: +\beginlines +|% proof mode: for initial design of characters| +|mode_def |^|proof|| =| +| proofing:=2; % yes, we're making full proofs| +| fontmaking:=0; % no, we're not making a font| +| tracingtitles:=1; % yes, show titles online| +| pixels_per_inch:=2601.72; % that's 36 pixels per pt| +| blacker:=0; % no additional blackness| +| fillin:=0; % no compensation for fillin| +| o_correction:=1; % no reduction in overshoot| +| enddef;| +\smallbreak +|% smoke mode: for label-free proofs to mount on the wall| +|mode_def |^|smoke|| =| +| proof_; % same as proof mode, except:| +| proofing:=1; % yes, we're making unlabeled proofs| +| extra_setup:=extra_setup&"grayfont black"; % with solid black pixels| +| let makebox=maketicks; % make the boxes less obtrusive| +| enddef;| +\weakendlines +Notice that "smoke" mode saves a lot of fuss by calling on `|proof_|'; +this is the macro that was defined by the first @mode\_def@. + +A typical mode for font generation appears next. ^^"fontmaking" +\beginlines +|% lowres mode: for certain devices that print 200 pixels per inch| +|mode_def |^|lowres|| =| +| proofing:=0; % no, we're not making proofs| +| fontmaking:=1; % yes, we are making a font| +| tracingtitles:=0; % no, don't show titles at all| +| pixels_per_inch:=200; % that's pretty low resolution| +| blacker:=.65; % make pens a bit blacker| +| fillin:=.2; % compensate for diagonal fillin| +| o_correction:=.4; % but don't overshoot as much| +| enddef;| +\smallskip +|localfont:=lowres; % the mode most commonly used to make fonts| +\endlines +Installations of \MF\ typically have several more predefined modes, and they +generally set "localfont" to something else. Such alterations should +not be made in the master file |plain.mf|; they should appear in a separate +file, as discussed below. + +\subsection Drawing and filling. Now we come to the macros that provide +an interface between the user and \MF's primitive picture commands. +^^"currentpen" ^^"currentpicture" ^^"currenttransform" +First, some important program variables are introduced: +\beginlines +|pen currentpen;| +|path currentpen_path;| +|picture currentpicture;| +|transform currenttransform;| +|def t_ = transformed currenttransform enddef;| +\endlines + +The key macros are ^@fill@, ^@draw@, ^@filldraw@, and ^@drawdot@. +\beginlines +|def fill expr c = addto_currentpicture contour c.t_ enddef;| +|def addto_currentpicture = addto currentpicture enddef;| +|def draw expr p =| +| addto_currentpicture doublepath p.t_ withpen currentpen enddef;| +|def filldraw expr c = fill counterclockwise c withpen currentpen enddef;| +|def drawdot expr z = if unknown currentpen_path: def_pen_path_ fi| +| addto_currentpicture contour| +| currentpen_path shifted (z.t_) withpen penspeck enddef;| +|def def_pen_path_ =| +| hide(currentpen_path=tensepath makepath currentpen) enddef;| +\endlines + +And they have negative counterparts: +\beginlines +|def |^|unfill|| expr c = fill c withweight -1 enddef;| +|def |^|undraw|| expr p = draw p withweight -1 enddef;| +|def |^|unfilldraw|| expr c = filldraw c withweight -1 enddef;| +|def |^|undrawdot|| expr z = drawdot z withweight -1 enddef;| +|def |^|erase|| text t = begingroup interim default_wt_:=-1;| +| cullit; t withweight -1; cullit; endgroup enddef;| +|newinternal default_wt_; default_wt_:=1;| +\endlines + +It's more difficult to cut off the ends of a stroke, but the +following macros (discussed near the end of Chapter~16) do the job: +\beginlines +|def |^|cutdraw|| expr p = % caution: you may need autorounding=0| +| cutoff(point 0 of p, 180+angle direction 0 of p);| +| cutoff(point infinity of p, angle direction infinity of p);| +| culldraw p enddef;| +\smallbreak +|def |^|culldraw|| expr p = addto pic_ doublepath p.t_ withpen currentpen;| +| cull pic_ dropping(-infinity,0) withweight default_wt_;| +| addto_currentpicture also pic_; pic_:=nullpicture; killtext enddef;| +|vardef |^|cutoff||(expr z,theta) =| +| interim autorounding := 0; interim smoothing := 0;| +| addto pic_ doublepath z.t_ withpen currentpen;| +| addto pic_ contour| +| (cut_ scaled (1+max(-pen_lft,pen_rt,pen_top,-pen_bot))| +| rotated theta shifted z)t_;| +| cull pic_ keeping (2,2) withweight -default_wt_;| +| addto currentpicture also pic_; pic_:=nullpicture enddef;| +|picture pic_; pic_:=nullpicture;| +|path cut_; cut_ = ((0,-1)--(1,-1)--(1,1)--(0,1)--cycle) scaled 1.42;| +\weakendlines +The use of ^"default\_wt\_" here makes `^@erase@ @cutdraw@' work. The +private variable "pic\_" is usually kept equal to @nullpicture@ in +order to conserve memory space. + +Picking up a pen not only sets "currentpen", it also establishes +the values of ^"pen\_lft", ^"pen\_rt", ^"pen\_top", and ^"pen\_bot", +which are used by "lft", "rt", "top", and "bot". +\beginlines +|def |^|pickup|| secondary q =| +| if numeric q: numeric_pickup_ else: pen_pickup_ fi q enddef;| +|def numeric_pickup_ primary q =| +| if unknown pen_[q]: errmessage "Unknown pen"; clearpen| +| else: currentpen:=pen_[q];| +| pen_lft:=pen_lft_[q]; pen_rt:=pen_rt_[q];| +| pen_top:=pen_top_[q]; pen_bot:=pen_bot_[q];| +| currentpen_path:=pen_path_[q] fi; enddef;| +|def pen_pickup_ primary q =| +| currentpen:=q yscaled aspect_ratio;| +| pen_lft:=xpart penoffset down of currentpen;| +| pen_rt:=xpart penoffset up of currentpen;| +| pen_top:=(ypart penoffset left of currentpen)_o_;| +| pen_bot:=(ypart penoffset right of currentpen)_o_;| +| path currentpen_path; enddef;| +|newinternal pen_lft,pen_rt,pen_top,pen_bot,pen_count_;| +\endlines +And saving a pen saves all the relevant values for later retrieval. +\beginlines +|vardef |^|savepen|| = pen_[incr pen_count_]=currentpen;| +| pen_lft_[pen_count_]=pen_lft;| +| pen_rt_[pen_count_]=pen_rt;| +| pen_top_[pen_count_]=pen_top;| +| pen_bot_[pen_count_]=pen_bot;| +| pen_path_[pen_count_]=currentpen_path;| +| pen_count_ enddef;| +\smallbreak +|def |^|clearpen|| = currentpen:=nullpen;| +| pen_lft:=pen_rt:=pen_top:=pen_bot:=0;| +| path currentpen_path; enddef;| +\smallbreak +|def clear_pen_memory =|^^@clear\_pen\_memory@ +| pen_count_:=0;| +| numeric pen_lft_[],pen_rt_[],pen_top_[],pen_bot_[];| +| pen currentpen,pen_[];| +| path currentpen_path, pen_path_[];| +| enddef;| +\endlines + +The four basic pen-edge functions offer no surprises: +^^"lft"^^"rt"^^"top"^^"bot" +\beginlines +|vardef lft primary x = x + if pair x: (pen_lft,0) else: pen_lft fi enddef;| +|vardef rt primary x = x + if pair x: (pen_rt,0) else: pen_rt fi enddef;| +|vardef top primary y = y + if pair y: (0,pen_top) else: pen_top fi enddef;| +|vardef bot primary y = y + if pair y: (0,pen_bot) else: pen_bot fi enddef;| +\endlines +There are six functions that ^{round} to good positions for pen placement. +\beginlines +|vardef |^|good.x|| primary x = hround(x+pen_lft)-pen_lft enddef;| +|vardef |^|good.y|| primary y = vround(y+pen_top)-pen_top enddef;| +|vardef |^|good.lft|| primary z = save z_; pair z_;| +| (z_+(pen_lft,0))t_=round((z+(pen_lft,0))t_); z_ enddef;| +|vardef |^|good.rt|| primary z = save z_; pair z_;| +| (z_+(pen_rt,0))t_=round((z+(pen_rt,0))t_); z_ enddef;| +|vardef |^|good.top|| primary z = save z_; pair z_;| +| (z_+(0,pen_top))t_=round((z+(0,pen_top))t_); z_ enddef;| +|vardef |^|good.bot|| primary z = save z_; pair z_;| +| (z_+(0,pen_bot))t_=round((z+(0,pen_bot))t_); z_ enddef;| +\endlines + +So much for fixed pens. When pen-like strokes are defined by +outlines, the ^"penpos" macro is of primary importance. Since "penpos" +may be used quite frequently, we might as well write out the $x$ and~$y$ +coordinates explicitly instead of using the (somewhat slower) $z$~convention: +\beginlines +|vardef penpos@#(expr b,d) =| +| (x@#r-x@#l,y@#r-y@#l)=(b,0) rotated d;| +| x@#=.5(x@#l+x@#r); y@#=.5(y@#l+y@#r) enddef;| +\endlines + +Simulated pen strokes are provided by the convenient ^@penstroke@ command. +\beginlines +|def penstroke text t =| +| forsuffixes e = l,r: path_.e:=t; endfor| +| if cycle path_.l: cyclestroke_| +| else: fill path_.l -- reverse path_.r -- cycle fi enddef;| +|def cyclestroke_ =| +| begingroup interim turningcheck:=0;| +| addto pic_ contour path_.l.t_ withweight 1;| +| addto pic_ contour path_.r.t_ withweight -1;| +| cull pic_ dropping origin withweight default_wt_;| +| addto_currentpicture also pic_;| +| pic_:=nullpicture endgroup enddef;| +|path path_.l,path_.r;| +\finalendlines + +\subsection Proof labels and rules. The next main section of |plain.mf| +is devoted to macros for the annotations on proofsheets. These macros +are discussed in Appendix~H\null, and they use the ^@special@ and ^@numspecial@ +commands discussed in Appendix~G. + +Labels are generated at the lowest level by @makelabel@\kern1pt:^^"lcode\_" +\beginlines +|vardef |^|makelabel||@#(expr s,z) = % puts string s at point z| +| if known z: special lcode_@# & s;| +| numspecial xpart(z.t_); numspecial ypart(z.t_) fi enddef;| +\smallskip +|string lcode_,lcode_.top,lcode_.lft,lcode_.rt,lcode_.bot,| +| lcode_.top.nodot,lcode_.lft.nodot,lcode_.rt.nodot,lcode_.bot.nodot;| +|lcode_.top=" 1"; lcode_.lft=" 2"; lcode_.rt=" 3"; lcode_.bot=" 4";| +|lcode_=" 0"; % change to " /" to avoid listing in overflow column| +|lcode_.top.nodot=" 5"; lcode_.lft.nodot=" 6";| +|lcode_.rt.nodot=" 7"; lcode_.bot.nodot=" 8";| +\endlines + +Users generally don't invoke @makelabel@ directly, because there's a convenient +shorthand. For example, `@labels@$(1,2,3)$' expands into +`@makelabel@\kern1pt(|"1"|$,z_1$); @makelabel@\kern1pt(|"2"|$,z_2$); +@makelabel@\kern1pt(|"3"|$,z_3$)'. +\ (But nothing happens if ^"proofing"$\null\le1$.) +\beginlines +|vardef |^|labels||@#(text t) =| +| if proofing>1: forsuffixes $=t: makelabel@#(str$,z$); endfor fi enddef;| +|vardef |^|penlabels||@#(text t) =| +| if proofing>1: forsuffixes $$=l,,r: forsuffixes $=t:| +| makelabel@#(str$.$$,z$.$$); endfor endfor fi enddef;| +\endlines +When there are lots of purely numeric labels, you can say, e.g., +\begindisplay +@labels@(1, @range@ 5 @thru@ 9, @range@ 100 @thru@ 124, 223) +\enddisplay +which is equivalent to `@labels@$(1,5,6,7,8,9,100,101,\ldots,124,223)$'. +Labels are omitted from the proofsheets if the corresponding $z$ value +isn't known, so it doesn't hurt (much) to include unused subscript numbers +in a range. +\beginlines +|def |^|range|| expr x = numtok[x] enddef;| +|def |^|numtok|| suffix x=x enddef;| +|tertiarydef m |^|thru|| n =| +| m for x=m+1 step 1 until n: , numtok[x] endfor enddef;| +\weakendlines +(This @range@ abbreviation will work in any ^@forsuffixes@ list; +and in a `@for@' list you can even omit the word `@range@'. +But you might fill~up \MF's main memory if too many values are involved.) + +A straight line will be drawn on the proofsheet by @proofrule@. +Although @makelabel@ takes the current transform into account, +@proofrule@ does not. There's also a corresponding routine `@screenrule@' +that puts a straight line in the current picture, so that design +guidelines will be visible on your screen: +\beginlines +|def |^|proofrule||(expr w,z) =| +| special "rule"; numspecial xpart w; numspecial ypart w;| +| numspecial xpart z; numspecial ypart z enddef;| +|def |^|screenrule||(expr w,z) =| +| addto currentpicture doublepath w--z withpen rulepen enddef;| +|pen rulepen; rulepen = pensquare scaled 2;| +\endlines +(The ^"rulepen" is two pixels wide, because screen rules are usually +drawn exactly over raster lines. A two-pixel-wide pen straddles the pixel +edges so that you can ``see'' the correct line position. If +a two-pixel-wide line proves to be too dark, you can redefine +"rulepen" to be simply ^@pensquare@; then \MF\ will draw the +thinnest possible screen rule, but it will be +a half-pixel too high and a half-pixel too far to the right.) + +You can produce lots of proof rules with ^@makegrid@, which connects +an arbitrary list of $x$~coordinates with an arbitrary list +of $y$~coordinates: +\beginlines +|def makegrid(text xlist,ylist) =| +| xmin_ := min(xlist); xmax_ := max(xlist);| +| ymin_ := min(ylist); ymax_ := max(ylist);| +| for x=xlist: proofrule((x,ymin_), (x,ymax_)); endfor| +| for y=ylist: proofrule((xmin_,y), (xmax_,y)); endfor| +| enddef;| +\endlines + +Finally we have a few macros that allow further communication with +the hardcopy proof-drawing routine of Appendix~H\null. You can change the +fonts, the thickness of proof rules, and the position of the image +on its page. +\beginlines +|vardef |^|titlefont|| suffix $ = special "titlefont "&str$ enddef;| +|vardef |^|labelfont|| suffix $ = special "labelfont "&str$ enddef;| +|vardef |^|grayfont|| suffix $ = special "grayfont "&str$ enddef;| +|vardef |^|slantfont|| suffix $ = special "slantfont "&str$ enddef;| +|def |^|proofoffset|| primary z = % shift proof output by z| +| special "offset"; numspecial xpart z; numspecial ypart z enddef;| +|vardef |^|proofrulethickness|| expr x =| +| special "rulethickness"; numspecial x enddef;| +\finalendlines + +\subsection Character and font administration. +After this elaborate preparation, we're finally ready +to consider the @beginchar@$\,\ldots\,$@endchar@ +framework for the individual characters of a font. Each ^@beginchar@ begins +a group, which should end at the next ^@endchar@. Then @beginchar@ +stores the given character code and device-independent +box dimensions in \MF's internal variables ^"charcode", ^"charwd", +^"charht", and ^"chardp". Then it computes the device-dependent box +dimensions ^"w", ^"h", and~^"d". Finally it +clears the $z$ variables, the current picture, and the +current pen. +\beginlines +|def beginchar(expr c,w_sharp,h_sharp,d_sharp) =| +| begingroup| +| charcode:=if known c: byte c else: 0 fi;| +| charwd:=w_sharp; charht:=h_sharp; chardp:=d_sharp;| +| w:=hround(charwd*hppp); h:=vround(charht*hppp); d:=vround(chardp*hppp);| +| charic:=0; clearxy; clearit; clearpen; scantokens extra_beginchar;| +| enddef;| +\endlines +The ^{italic correction} is normally zero, unless the user gives an +`^@italcorr@' command; even then, the correction stays zero unless +the given value is positive: +\beginlines +|def italcorr expr x_sharp = if x_sharp>0: charic:=x_sharp fi enddef;| +\endlines +When we want to change the pixel width $w$ from even to odd or vice +versa, the ^@change\_width@ macro does the right thing. +\beginlines +|def change_width =| +| w:=w if w>charwd*hppp:- else:+ fi 1 enddef;| +\endlines +(The user might also decide to change $w$ in some other way.) \ The +current value of~$w$ at the time of @endchar@ will be the +``official'' pixel width of the character, ^"chardx", that is +shipped to the |gf| output file. +\beginlines +|def endchar =| +| scantokens extra_endchar;| +| if proofing>0: makebox(proofrule); fi| +| chardx:=w; % desired width of the character in pixels| +| shipit;| +| if displaying>0: makebox(screenrule); showit; fi| +| endgroup enddef;| +\endlines +Extensions to these routines can be provided by putting commands in the +string variables ^"extra\_beginchar" and ^"extra\_endchar". +\beginlines +|string extra_beginchar, extra_endchar;| +|extra_beginchar=extra_endchar="";| +\endlines + +A ``^{bounding box}'' that surrounds the character according to the +specifications given in @beginchar@ is produced by ^@makebox@, which +takes into account the possibility that pixels might not be square. +An extra line is drawn to mark the width of the character with its +^{italic correction} included, if this correction is nonzero. +\beginlines +|def makebox(text r) =| +| for y=0,h.o_,-d.o_: r((0,y),(w,y)); endfor % horizontals| +| for x=0,w: r((x,-d.o_),(x,h.o_)); endfor % verticals| +| if charic<>0: r((w+charic*hppp,h.o_),(w+charic*hppp,.5h.o_)); fi| +| enddef;| +\endlines + +The ^@maketicks@ routine is an alternative to @makebox@ that draws less +conspicuous lines. This makes it easier to visualize a character's +appearance near the edges of its bounding box. +\beginlines +|def maketicks(text r) =| +| for y=0,h.o_,-d.o_: r((0,y),(10,y)); r((w-10,y),(w,y)); endfor| +| for x=0,w: r((x,10-d.o_),(x,-d.o_)); r((x,h.o_-10),(x,h.o_)); endfor| +| if charic<>0: r((w+charic*hppp,h.o_-10),(w+charic*hppp,h.o_)); fi| +| enddef;| +\endlines + +Overall information about the font as a whole is generally supplied +^^@font\_size\_etc@ +by the following commands, which are explained in Appendix~F\null. +\beginlines +|def font_size expr x = designsize:=x enddef;| +|def font_slant expr x = fontdimen 1: x enddef;| +|def font_normal_space expr x = fontdimen 2: x enddef;| +|def font_normal_stretch expr x = fontdimen 3: x enddef;| +|def font_normal_shrink expr x = fontdimen 4: x enddef;| +|def font_x_height expr x = fontdimen 5: x enddef;| +|def font_quad expr x = fontdimen 6: x enddef;| +|def font_extra_space expr x = fontdimen 7: x enddef;| +\smallskip +|def font_identifier expr x = font_identifier_:=x enddef;| +|def font_coding_scheme expr x = font_coding_scheme_:=x enddef;| +|string font_identifier_, font_coding_scheme_;| +|font_identifier_=font_coding_scheme_="UNSPECIFIED";| +\finalendlines +\bigskip + +\subsection The endgame. What have we left out? A few miscellaneous +things still need to be handled. First, we almost forgot to define the +^"z"~convention for points: +\beginlines +|vardef z@#=(x@#,y@#) enddef;| +\endlines +Then we need to do something rudimentary about \MF's ``windows.'' +^^"screen\_rows" ^^"screen\_cols" +\beginlines +|newinternal screen_rows, screen_cols, currentwindow;| +|screen_rows:=400; % these values should be corrected,| +|screen_cols:=500; % by reading in a separate file after plain.mf| +\smallskip +|def |^|openit|| = openwindow currentwindow| +| from origin to (screen_rows,screen_cols) at (-50,300) enddef;| +|def |^|showit|| = openit; let showit=showit_; showit enddef; % first time only| +|def showit_ = display currentpicture inwindow currentwindow enddef;| +\endlines +Plain \MF\ has several other terse commands like `@openit@' and `@showit@': +\beginlines +|def |^|clearxy|| = save x,y enddef;| +|def |^|clearit|| = currentpicture:=nullpicture enddef;| +|def |^|shipit|| = shipout currentpicture enddef;| +|def |^|cullit|| = cull currentpicture dropping (-infinity,0) enddef;| +\endlines + +\medbreak +The next several macros are handy things to put on your ^{command line} +when you are starting a \MF\ job (i.e., just before `|input| \<font file +name>'): + +\smallskip\item\bull |screenchars|. Say this when you're making a font +but want the characters to be displayed just before they are shipped out. +\item\bull |screenstrokes|. Say this when you're in "proof" mode +and want to see each stroke as it's added to the current picture. +\item\bull |imagerules|. Say this when you want to include the bounding box +in the current character, before you begin to draw it. +\item\bull |gfcorners|. Say this when you expect to make proofsheets +with large pixels, from a low-resolution font. +\item\bull |nodisplays|. Say this to save computer time when you don't +want "proof" mode to display each character automatically. +\item\bull |notransforms|. Say this to save computer time when you know +that the current transform is the identity. +\beginlines +|def |^|screenchars|| = % endchar should `showit'| +| extra_endchar:=extra_endchar&"showit;" enddef;| +\smallskip +|def |^|screenstrokes|| = % every stroke should `showit'| +| def addto_currentpicture text t=| +| addto currentpicture t; showit enddef; enddef;| +\smallbreak +|def |^|imagerules|| = % a box should be part of the character image| +| extra_beginchar:=extra_beginchar & "makebox(screenrule);" enddef;| +\smallbreak +|def |^|gfcorners|| = % `maketicks' should send rules to the gf file| +| extra_setup:=extra_setup & "let makebox=maketicks;proofing:=1;" enddef;| +\smallbreak +|def |^|nodisplays|| = % endchar shouldn't `showit'| +| extra_setup:=extra_setup & "displaying:=0;" enddef;| +\smallskip +|def |^|notransforms|| = % currenttransform should not be used| +| let t_ = \ enddef;| +\endlines + +We make `^@bye@' synonymous with `^@end@', just in case \TeX\ users expect +\MF\ programs to end like \TeX\ documents do. +\beginlines +|let bye = end; outer end,bye;| +\endlines + +And finally, we provide the default environment that a user gets when +^^@clear\_pen\_memory@ ^^@mode\_setup@ +simple experiments like those at the beginning of Chapter~5 are desired. +\beginlines +|clear_pen_memory; % initialize the `savepen' mechanism| +|mode_setup; % establish proof mode as the default| +|numeric |^|mode||,|^|mag||; % but leave mode and mag undefined| +\weakendlines +Whew! That's the end of the |plain.mf| file. + +\subsection Adapting to local conditions. In order to make plain \MF\ +programs interchangeable between different computers, everybody should use +the same |plain.mf| base. But there are some things that clearly should +be customized at each installation: + +\smallskip\item\bull Additional modes should be defined, so that fonts +can be made for whatever output devices are of interest. + +\item\bull The proper ^"localfont" mode should be established. + +\item\bull The correct numbers should be assigned to +^"screen\_rows" and ^"screen\_cols". + +\smallbreak +\noindent +Here's an example of a supplementary file `|local.mf|' +that would be appropriate for a computer center with the +hypothetical "cheapo" and "luxo" printers described in Chapter~11. +We assume that "cheapo" mode is to be identical to "lowres" mode, +except that the "cheapo" fonts should be generated with a {\sl negative\/} +value of ^"fillin" (because "cheapo" tends to make diagonal lines lighter +than normal, not heavier). The terminal screens are assumed to be +768 pixels wide and 512 pixels high. +\beginlines +|% A file to be loaded after "plain.mf".| +|base_version:=base_version&"/drofnats";| +\smallskip +|screen_rows:=512; screen_cols:=768;| +\smallskip +|mode_def cheapo = % cheapo mode: to generate fonts for cheapo| +| lowres_; % do as in lowres mode, except:| +| fillin:=-.1; % compensate for lighter diagonals| +| enddef;| +\smallskip +|mode_def luxo = % luxo mode: to generate fonts for luxo| +| proofing:=0; % no, we're not making proofs| +| fontmaking:=1; % yes, we are making a font| +| tracingtitles:=1; % yes, show titles online| +| pixels_per_inch:=2000; % almost 30 pixels per pt| +| blacker:=.2; % make pens a teeny bit blacker| +| fillin:=.1; % but compensate for heavy diagonals| +| o_correction:=1; % and keep the full overshoot| +| enddef;| +\smallskip +|localfont:=cheapo;| +\weakendlines +The macro `^@bye@' might also be redefined, as suggested at the close +of Appendix~F. + +To prepare a preloaded base file at this installation, a suitably +privileged person should run ^|INIMF| in the following way: +\begintt +This is METAFONT, Version 2.0 (INIMF) 8 NOV 1989 10:09 +**plain +(plain.mf +Preloading the plain base, version 2.0) +*input local +(local.mf) +*dump +Beginning to dump on file plain.base +\endtt +^^{dump}(The stuff after `|**|' or `|*|' is typed by the user; everything +else is typed by the system. A few more messages actually come out.) + +Notice that |local.mf| does not include any new macros or features that a +programmer could use in a special way. Therefore it doesn't make plain +\MF\ incompatible with implementations at other computing centers. + +Changes and/or extensions to the |plain.mf| macros should never be made, +unless the resulting base file is clearly distinguished from the standard +plain base. But new, differently named bases are welcome. +For example, the author prepared a special base for the +^{Computer Modern} fonts, so that they could be generated without first +reading the same 700 lines of macro definitions each time. To load +this base at high speed, he can type `|&cm|' after \MF's initial +`|**|'. ^^{asterisk} ^^{ampersand} (Or, on some machines, he has a special +version called `|cmmf|' in which the new base is already present.) + +\endchapter + +None but the Base, in baseness doth delight. +\author MICHAEL ^{DRAYTON}, {\sl Robert, Duke of Normandy\/} (1605) + % line 262; quite different in the 1596 version + +\bigskip + +So far all was plain sailing, as the saying is; +but Mr.\thinspace Till knew that his main difficulties were yet to come. +\author FRANCIS E. ^{PAGET}, {\sl Milford Malvoisin\/} (1842) % p209 + % in the 22nd paragraph of Chapter 9 + +\eject + \beginchapter Appendix C. Character\\Codes + +Different computers tend to have different ways of representing the +characters in files of text, but \MF\ gives the same results on +all machines, because it converts everything to a standard internal +code when it reads a file. \MF\ also converts back from its internal +representation to the appropriate external code, when it writes +a file of text; therefore most users need not be aware of the fact +that the ^{codes} have actually switched back and forth inside the machine. + +The purpose of this appendix is to define \MF's internal code, +which has the same characteristics on all implementations of \MF\!\null. +The existence of such a code is important, because it +makes \MF\ programs portable. +\MF's scheme is based on the American Standard Code for +Information Interchange, known popularly as ``^{ASCII}.'' There are +128 codes, numbered 0~to~127; we conventionally express the numbers +in ^{oct}al notation, from |oct"000"| to |oct"177"|, or in +^{hex}adecimal notation, from |hex"00"| to |hex"7F"|. Thus, the value of +|ASCII"b"| is normally called |oct"142"| or |hex"62"|, not 98. In the +^{ASCII} scheme, codes |oct"000"| through |oct"037"| and +code |oct"177"| are~assigned to special functions; for example, +code |oct"007"| is called |BEL|, and it means ``Ring the bell.'' +The other 95 codes are assigned to visible symbols and to the +blank space character. Here is a +chart that shows ASCII codes in such a way that octal and hexadecimal +equivalents can easily be read off: +\beginchart{\global\count255='41\postdisplaypenalty=0\tentt + \def\chartstrut{\lower4.3pt\vbox to13.6pt{}}} +&\oct{00x}&&NUL&&SOH&&STX&&ETX&&EOT&&ENQ&&ACK&&BEL&&\oddline0 +&\oct{01x}&&BS&&HT&&LF&&VT&&FF&&CR&&SO&&SI&\evenline +&\oct{02x}&&DLE&&DC1&&DC2&&DC3&&DC4&&NAK&&SYN&&ETB&&\oddline1 +&\oct{03x}&&CAN&&EM&&SUB&&ESC&&FS&&GS&&RS&&US&\evenline +&\oct{04x}&& &&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\oddline2 +&\oct{05x}&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\:&\evenline +&\oct{06x}&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\oddline3 +&\oct{07x}&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\:&\evenline +&\oct{10x}&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\oddline4 +&\oct{11x}&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\:&\evenline +&\oct{12x}&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\oddline5 +&\oct{13x}&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\:&\evenline +&\oct{14x}&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\oddline6 +&\oct{15x}&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\:&\evenline +&\oct{16x}&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\oddline7 +&\oct{17x}&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&DEL&\evenline +\endchart + +Ever since ASCII was established in the early 1960s, people have had +different ideas about what to do with positions |oct"000"| thru |oct"037"| and +|oct"177"|, because most of the functions assigned to those codes are +appropriate only for special purposes like file transmission, not for +applications to printing or to interactive computing. +Manufacturers soon started producing line printers that were capable of +generating 128 characters, 33~of~which were tailored to the special needs +of particular customers; part of the advantage of a standard code was +therefore lost. +An extended ASCII code intended for text editing and interactive computing + was developed at several universities about 1965, and +for many years there have been terminals in use at Stanford, MIT, +Carnegie-Mellon, and elsewhere that have 120 or~121 symbols, not just~95. +For example, the author ^^{Knuth} developed \MF\ on a keyboard that +includes the symbols `{\tentex\char'32}', `{\tentex\char'34}', +`{\tentex\char'35}', and `{\tentex\char'30}', which are easier to use than +the character pairs `{\tentex^{<>}}', `{\tentex^{<=}}', `{\tentex^{>=}}', +and `{\tentex^{:=}}'. The full character set looks like this: +\beginchart{\tentex\postdisplaypenalty=0} +\normalchart +\endchart +\MF\ can also be configured to accept any or all of the character codes +128--255. +However, \MF\ programs that make use of anything in addition to the 95 +standard ASCII characters cannot be expected to run on other systems, so +the use of extended character sets is discouraged. + +A possible middle ground has been suggested, based on the fact that +it's easy to write a +program that converts extended-character files into standard files by +substituting `|<>|' for `{\tentex\char'32}', etc. In the author's +implementation at Stanford, the symbols +`{\tentex\char'32}', `{\tentex\char'34}', `{\tentex\char'35}', +and `{\tentex\char'30}' are considered to be in the same class as +`{\tentex<}', `{\tentex=}', `{\tentex:}', and `{\tentex>}' when tokens are +formed (see Chapter~6). Tokens like `{\tentex\char'32=}' +and `{\tentex<\char'35}' are therefore distinct, although +they both become `{\tentex<>=}' after +conversion. As long as such tokens are avoided, +the author's programs can easily be expurgated into a +portable form for general distribution. \ (Another feasible approach would +have been to convert nonstandard codes to character pairs during \MF's +input process; that would have been slightly less efficient.) + +Computers with non-ASCII character sets should specify a correspondence +between 95 distinct characters and the standard ASCII codes |oct"040"| +thru |oct"176"|. \MF\ programs written on any such machines will be +completely interchangeable with each other. + +\endchapter + +If any shall suggest, that some of the Enquiries here insisted upon +(as particularly those about the Letters of the Alphabet) +do seem too minute and trivial, for any prudent Man +to bestow his serious thoughts and time about. +Such Persons may know, that the discovery +of the true nature and Cause of any the most minute thing, +doth promote real Knowledge, +and therefore cannot be unfit for any Mans endeauours, +who is willing to contribute to the advancement of Learning. +\author JOHN ^{WILKINS}, {\sl Towards a Real Character\/} (1668) + % preface to the reader + +\bigskip + +Clearly even the simple A.B.C.~is a thing of mystery. +Like all codes, it should not be trifled with, +but it is to be feared that in modern times +it has not always been respected. +\author STANLEY ^{MORISON}, {\sl On Type Faces\/} (1923) + % from the introduction, near the beginning +\eject + \beginchapter Appendix D. Dirty Tricks + +Any powerful computer language can be used in ways that go considerably +beyond what the language designer originally had in mind, especially +when macro expansion is possible. Sometimes the unexpected constructions +are just amusing; sometimes they are disgustingly arcane. But +sometimes they turn out to be quite useful, and they graduate from ``tricks'' +to the status of ``techniques.'' \ (For example, several of the macros +now in Appendix~B started out as suggestions for Appendix~D\null.) \ +In any case, gurus of a language always like to explore its limits. +The depths of \MF\ have hardly been plumbed, but this appendix probably +reached a new low at the time it was written. + +Acknowledgment: More than half of the ideas in this appendix are due to +John ^{Hobby}, who has been a tireless and inspiring co-worker during the +entire development of the new \MF\ system. + +\advance\medskipamount by -.5pt +\advance\abovedisplayskip by -.5pt +\advance\belowdisplayskip by -.5pt + +\ninepoint\medskip +\setbox0=\hbox spread-7\fontdimen4\font % that removes all the shrinkability + {\strut Please don't read this material until you've had} +\setbox1=\hbox to\wd0{\strut plenty of experience with plain \MF\!.} +\line{\leaders\hbox{\dbend\kern2pt}\hfil\vtop{\box0\box1}} +\nointerlineskip +\noindent\strut After you have read and understood the secrets below, you'll +know all sorts of devious combinations of \MF\ commands, +and you will often be tempted to write inscrutable macros. Always remember, +however, that there's usually a simpler and better way to do something +than the first way that pops into your head. You may not have to +resort to any subterfuge at all, since \MF\ is able to do lots of things +in a straightforward way. Try for simple solutions first. + +\subsection Macro madness. If you need to write complicated ^{macros}, you'll +need to be familiar with the fine points in Chapter~20. \MF's symbolic tokens +are divided into two main categories, ``expandable'' and ``unexpandable''; +the former category includes all macros and @if@$\,\ldots\,$@fi@ tests and +@for@$\,\ldots\,$@endfor@ loops, as well as special operations like @input@, +while the latter category includes the primitive operators and commands +listed in Chapters 25 and~26. The expansion of expandable tokens takes place +in \MF's ``^{mouth},'' but primitive statements (including equations, +declarations, and the various types of commands) are done in \MF's +``^{stomach}.'' There's a communication between the two, since the stomach +evaluates expressions that are needed as arguments to the mouth's macros; +any statement can be embedded in a group expression, so arbitrarily +complicated things can be done as part of the ^{expansion} process. + +Let's begin by considering a toy problem that is treated at the beginning +of Appendix~D in {\sl The \TeX book}, in case some readers are interested in +comparing \TeX\ to \MF\!\null. Given a numeric variable $n\ge0$, we wish to +define a macro |asts| whose replacement text consists of precisely +$n$~asterisks. This task is somewhat tricky because expansion is suppressed +when a replacement text is being read; we want to use a ^@for@ loop, but +loops are special cases of expansion. In other words, +\begintt +def asts = for x=1 upto n: * endfor enddef +\endtt +defines |asts| to be a macro with a @for@ loop in its replacement text; +\advance\belowdisplayskip by -1.5pt +in practice, |asts| would behave as if it contained $n$ asterisks (using +possibly different values of~$n$), but +we have not solved the stated problem. The alternative +\begintt +def makedef primary n = + def asts = for x=1 upto n: * endfor enddef enddef; +makedef n +\endtt +``freezes'' the present value of $n$; but this doesn't solve the problem either. + +\advance\medskipamount by .5pt +\advance\abovedisplayskip by .5pt +\advance\belowdisplayskip by 2pt + +\goodbreak + +One solution is to build up the definition by adding one asterisk at a time, +using ^@expandafter@ as follows: +\begintt +def asts = enddef; +for x=1 upto n: + expandafter def expandafter asts expandafter = asts * enddef; +endfor. +\endtt +The three @expandafter@s provide a ``finger'' into the replacement text, +before @def@ suppresses expansion; without them the replacement text +would turn out to be `|asts|~|*|', causing infinite recursion. + +This solution involves a running time proportional to $n^2$, so the +reader might wonder why a simpler approach like +\begintt +expandafter def expandafter asts expandafter = + for x = 1 upto n: * endfor enddef +\endtt +wasn't suggested? The reason is that this doesn't work, unless $n=0$! +A @for@ loop isn't entirely expanded by @expandafter@; only \MF's first +step in loop expansion is carried out. Namely, the loop text is read, +and a special ^{inaccessible} token `^|ENDFOR|' is placed at its end. +Later on when \MF's mouth encounters `|ENDFOR|' (which incidentally is an +expandable token, but it wasn't listed in Chapter~20), the loop text is +re-inserted into the input stream, unless of course the loop has finished. +The special |ENDFOR| is an `^@outer@' token, hence it should not +appear in replacement texts; \MF\ will therefore stop with a ``^{forbidden +token}'' error if you try the above with $n\ge1$. +^^@inner@ You might try to defeat the outerness by saying +\begintt +for x=1: inner endfor; +\endtt +but \MF\ won't let you. And even if this had worked, it wouldn't have +solved the problem; it would simply have put |ENDFOR| into the +replacement text of |asts|, because expansion is inhibited when the +replacement text is being read. + +There's another way to solve the problem that seems to have running +time proportional to~$n$ rather than~$n^2$: +\begintt +scantokens("def asts=" for x=1 upto n: & "* " endfor) enddef; +\endtt +^^{*} but actually \MF's string ^{concatenation} +operation takes time proportional to the length of the strings it +deals with, so the running time is still order~$n^2$. +Furthermore, the ^{string} operations in \MF\ are rather primitive, +because this isn't a major aspect of the language; so it turns out that +this approach uses order~$n^2$ storage cells in the string pool, although +they are recycled later. Even if the ^{pool size} were infinite, \MF's +``^{buffer size}'' would be exceeded for large~$n$, because ^@scantokens@ +puts the string into the input buffer before scanning it. + +Is there a solution of order $n$? Yes, of course. For example, +\begintt +def a=a* enddef; +for x=0 upto n: + if x=n: def a=quote quote def asts = enddef; fi + expandafter endfor a enddef; +showtoken asts. +\endtt +(The first `^@quote@' is removed by the @for@, hence one will survive until +$a$~is redefined. If you don't understand this program, try ^^{debugging tricks} +running it with $n=3$; insert an isolated expression `|0;|' just before +the~`|if|', and look at the lines of context that are shown when +\MF\ gives you four error messages.) \ The only flaw in this method is +that it uses~up $n$ cells of stack space; \MF's ^{input stack size} +may have to be increased, if $n$ is bigger than 25~or~so. + +\smallbreak +The asterisk problem is just a puzzle; let's turn now to a genuine +application. Suppose we want to define a macro called `"ten"' ^^{Derek} +whose replacement text is the contents of the parameter file ^|logo10.mf| +in Chapter~11, up to but {\sl not\/} including the last two lines of +that file. Those last two lines say +\begintt +input logo % now generate the font +end % and stop. +\endtt +The "ten" macro will make it possible to set up the 10-point parameters +repeatedly (perhaps alternating with 9-point parameters in a "nine" macro); +Appendix~E explains how to create a meta-design tool via such macros. + +One idea would be to try to input the entire file |logo10.mf| as the +replacement text for "ten". We could nullify the effect of the last three +unwanted tokens by saying +\begintt +save input,logo,end; +forsuffixes s=input,logo,end: let s=\; endfor +\endtt +just before "ten" is used. To get the entire file as a replacement text, +we can try one of the approaches that worked in the asterisk problem, say +\begintt +expandafter def expandafter ten expandafter = input logo10 enddef. +\endtt +But this first attempt runs awry if we haven't already redefined `^@end@'; +Appendix~B makes `@end@' an `^@outer@' token, preventing its appearance +in replacement texts. So we say `^@inner@ @end@' +and try again, only to discover an unwritten law that somehow never +came up in Chapters 20 or~26: +\begintt +Runaway definition? +font_size10pt#;ht#:=6pt#;xgap#:=0.6pt#;u#:=4/9pt#;s#:=0;o#:=1/ ETC. +! File ended while scanning the definition of ten. +<inserted text> + enddef +l.2 ...fter ten expandafter = input logo10 + enddef; +\endtt +^^|Runaway| ^^|File ended...| +The ^{end of a file} is invisible; but it's treated like an `@outer@' +token, in the sense that a file should never end when \MF\ is passing +rapidly over text. + +Therefore this whole approach is doomed to failure. We'll have to find a +way to stop the replacement text before the file ends. +OK, we'll redefine `@input@' so that it means `@enddef@\kern1pt', and +redefine "logo" so that it means `^@endinput@'. +\begintt +let INPUT = input; let input = enddef; let logo = endinput; +expandafter def expandafter ten expandafter = INPUT logo10; +showtoken ten. +\endtt +It works! By the way, the line with three expandafters can be replaced by +a more elegant construction that uses @scantokens@ as follows: +\begintt +scantokens "def ten=" INPUT logo10; +\endtt +This does the job because \MF\ always looks ahead and expands the token +immediately following an expression that is being evaluated. \ (The +expression in this case is the string |"def|~|ten="|, which is an argument +to @scantokens@. The token that immediately follows an expression +almost always needs to be examined in order to be sure that the expression +has ended, so \MF\ always examines it.) \ Curiously, the @expandafter@ +alternative causes "ten"'s replacement text to begin with the tokens +`|font_size10pt#;ht#:=...|', while the @scantokens@ way makes it start +with `|designsize:=(10);ht#:=...|'. Do you see why? In the second case, +expansion continued until an unexpandable token (`|designsize|') was +found, so the |font_size| macro was changed into its replacement text; but +@expandafter@ just expanded `|INPUT|'. + +Now let's make the problem a bit harder. Suppose we know that `|input|' +comes at the end of where we want to read, but we don't know that `|logo|' +will follow. We know that some program file name will be there, but +it might not be for the logo font. Furthermore, let's assume that `|end|' +might not be present; therefore we can't simply redefine it to be @enddef@. +In this case we can make `|input|' into a right ^{delimiter}, and +read the file as a {\sl delimited ^{text argument}\/}; ^^{argument} +that will give us enough time to insert other tokens, which will +terminate the input and flush the unwanted file name. But the construction +is more complex: +\begintt +let INPUT = input; delimiters begintext input; +def makedef(expr name)(text t) = + expandafter def scantokens name = t enddef; + endinput flushfilename enddef; +def flushfilename suffix s = enddef; +makedef("ten") expandafter begintext INPUT logo10; +showtoken ten. +\endtt +This example merits careful study, perhaps with `^@tracingall@' to +show exactly how \MF\ proceeds. We have assumed that the unknown file +name can be parsed as a suffix; this solves the problem that a file cannot +end inside of a @text@ parameter or a false condition. \ (If we knew that +`@end@' were present, we could have replaced `|endinput|~|flushfilename|' by +`|if|~|false:|' and redefined `|end|' to be `|fi|'.) + +Let's turn now to a simpler problem. \MF\ allows you to consider the +`^{and}' of two Boolean expressions, but it always evaluates both +expressions. This is problematical in situations like +\begintt +if pair x and (x>(0,0)): A else: B fi +\endtt +because the expression `|x>(0,0)|' will stop with an error message +unless $x$ is of type @pair@. The obvious way to avoid this error, +\begintt +if pair x: if x>(0,0): A else: B fi else: B fi +\endtt +is cumbersome and requires |B| to appear twice. What we want is a +``^{conditional and}'' operation in which the second Boolean expression is +evaluated only if the first one turns out to be true; then we can safely write +\begintt +if pair x cand (x>(0,0)): A else: B fi. +\endtt +Similarly we might want ``^{conditional or}'' in which the second operand is +evaluated only if the first is false, for situations like +\begintt +if unknown x cor (x<0): A else: B fi. +\endtt +Such ^|cand| and ^|cor| macros can be defined as follows: +\begintt +def cand(text q) = startif true q else: false fi enddef; +def cor(text q) = startif true true else: q fi enddef; +tertiarydef p startif true = if p: enddef; +\endtt +the ^{text arguments} are now evaluated only when necessary. We have essentially +^^@if@ replaced the original line by +\begintt +if if pair x: x>(0,0) else: false fi: A else: B fi. +\endtt +This construction has one catch; namely, the right-hand operands of |cand| and +|cor| must be explicitly enclosed in delimiters. But delimiters are only a +minor nuisance, because the operands to `and' and `or' usually need them +anyway. It would be impossible to make |cand| and |cor| obey the normal +expression ^{hierarchy}; when macros make primary/secondary/tertiary +distinctions, they evaluate their arguments, and such evaluation is +precisely what |cand| and |cor| want to avoid. + +If these |cand| and |cor| macros were changed so that they took +{\sl undelimited\/} text arguments, the text argument wouldn't stop at a colon. +We could, however, use such modified macros with ^{group delimiters} +instead. For example, after +\begintt +let {{ = begingroup; let }} = endgroup; +def cand text q = startif true q else: false fi enddef +\endtt +we could write things like +\begintt +if {{pair x cand x>(0,0)}}: A else: B fi. +\endtt +(Not that this buys us anything; it just illustrates a property of +undelimited text arguments.) \ Group delimiters +are not valid delimiters of {\sl delimited\/} text arguments. + +Speaking of group delimiters, the gratuitous ^@begingroup@ and ^@endgroup@ +tokens added by ^@vardef@ are usually helpful, but they can be a nuisance. +For example, suppose we want to write a |zz|~macro such that +`|zz1..zz2..zz3|' expands into +\begintt +z1{dz1}..z2{dz2}..z3{dz3} +\endtt +It would be trivial to do this with @def@: +\begintt +def zz suffix $ = z${dz$} enddef; +\endtt +but this makes |zz| a ``^{spark}.'' Let's suppose that we want to use +@vardef@, so that |zz| will be usable in suffixes of variable names. +Additional @begingroup@ and @endgroup@ delimiters will mess up the +syntax for paths, so we need to get rid of them. Here's one way to +finesse the problem: +\begintt +vardef zz@# = + endgroup gobbled true z@#{dz@#} gobble begingroup enddef. +\endtt +The ^|gobbled| and ^|gobble| functions of Appendix~B will remove the ^{vacuous +expressions} `@begingroup@~@endgroup@' at the beginning and end of +the replacement text. + +(The initial @begingroup@ @endgroup@ won't be gobbled if the vardef is +being read as a primary instead of as a secondary, tertiary, or +expression. But in such cases you probably don't mind having @begingroup@ +present.) + +\subsection Fortuitous loops. +The `^{max}' and `^{min}' macros in Appendix~B make use of the fact +that commas are like `|)(|' in argument lists. Although the +definition heading is +\begintt +def max(expr x)(text t) +\endtt +we can write `max$(a,b,c)$' and this makes $x=a$ and $t=\null$`$b,c$'. +Of course, a person isn't supposed to say `max$(a)(b)(c)$'. + +Here are two more applications of the idea: We want `^{inorder}$(a,b,c)$' +to be true if and only if $a\le b\le c$; and we want +`^@equally\_spaced@$(x_1,x_2,x_3)\,"dx"$' to produce the equations +`$x_2-x_1=x_3-x_2=dx$'. +\begintt +def inorder(expr x)(text t) = + ((x for u=t: <= u) + and (u endfor gobbled true true)) enddef; +def equally_spaced(expr x)(text t) expr dx = + x for u=t: - u = u endfor gobbled true + - dx enddef. +\endtt +Isn't this fun? \ (Look closely.) + +There is a problem, however, if we try to use these macros with +loops in the arguments. Consider the expressions +\begintt +inorder(for n=1 upto 10: a[n], endfor infinity), +inorder(a[1] for n=2 upto 10: ,a[n] endfor), +inorder(a[1],a[2] for n=3 upto 10: ,a[n] endfor); +\endtt +the first two give error messages, but the third one works! +The reason is that, in the first two cases, +the @for@ loop begins to be expanded before \MF\ begins to read the +^{text argument}, hence ^|ENDFOR| rears its ugly head again. +We can avoid this problem by rewriting the macros in a more complicated +way that doesn't try to single out the first argument~$x$: +\begintt +def inorder(text t) = + expandafter startinorder for u=t: + <= u endgroup and begingroup u endfor + gobbled true true endgroup) enddef; +def startinorder text t = + (begingroup true enddef; +def equally_spaced(text t) expr dx = + if pair dx: (whatever,whatever) else: whatever fi + for u=t: - u = u endfor gobbled true + - dx enddef; +\endtt +Two separate tricks have been used here: (1)~The `^@endgroup@' within +`inorder' will stop an undelimited text argument; this gets rid +of the unwanted `|<=|~|u|' at the beginning. (2)~A throwaway variable, +`^"whatever"', nullifies an unwanted equation at the beginning of +`@equally\_spaced@'. With the new definitions, +all three of the expressions above will be understood, +and so will things like +\begintt +equally_spaced(for n=1 upto 10: x[n], endfor whatever) dx. +\endtt +Furthermore the single-argument cases now work: +`inorder($a$)' will always be true, and +`@equally\_spaced@($x)\,"dx"$' will produce no new equations. + +If we want to improve ^{max} and ^{min} in the same way, so that a person can +specify loop arguments like +\begintt +max(a[1] for n=2 upto 10: ,a[n] endfor) +\endtt +and so that `max($a)=a$' in the case of a single argument, we have to +work harder, because max and min treat their first argument in quite +a special way; they need to apply the special macro ^"setu\_", which defines +the type of the auxiliary variable "u\_". The fastest way to solve this +problem is probably to use a token whose meaning changes during the +first time through the loop: +\begintt +vardef max(text t) = + let switch_ = firstset_; + for u=t: switch_ u>u_: u_ := u ;fi endfor + u_ enddef; +vardef min(text t) = + let switch_ = firstset_; + for u=t: switch_ u<u_: u_ := u ;fi endfor + u_ enddef; +def firstset_ primary u = + setu_ u; let switch_ = if; if false: enddef. +\endtt +Incidentally, the author's ^^{Knuth} first programs for max and min +contained an interesting bug. They started with `@save@ "u\_"', and they +tried to recognize the first time through the loop by testing if "u\_" was +unknown. This failed because "u\_" could be constantly unknown in +well-defined cases like max$(x,x+1,x+2)$. + +\subsection Types. Our programs for |inorder|, |equally_spaced|, and +|max| are careful not to make unnecessary assumptions about the type +of an expression. The `round' and `byte' functions in Appendix~B +are further examples of macros that change behavior based +on the types of their @expr@ arguments. Let's look more closely at +applications of type testing. \looseness=-1 + +When the author was developing macros for plain \MF\!, his first +``correct'' solution for |max| had the following form: +\begintt +vardef max(text t) = + save u_; boolean u_; + for u=t: if boolean u_: setu_ u + elseif u_<u: u_ := u fi; endfor + u_ enddef. +\endtt +This was interesting because it showed that there was no need to +set "u\_" to true or false; the simple fact that it was boolean +was enough to indicate the first time through the loop. \ (A slightly +different "setu\_" macro was used at that time.) + +We might want to generalize the `^{scaled}' operation of \MF\ so that +`scaled~$(x,y)$' is shorthand for `^{xscaled}~$x$ ^{yscaled}~$y$'. +That's pretty easy: +\begintt +let SCALED = scaled; +def scaled primary z = + if pair z: xscaled xpart z yscaled ypart z + else: SCALED z fi enddef; +\endtt +It's better to keep the primitive operation `|SCALED| |z|' here than to replace +it by the slower variant `|xscaled| |z| |yscaled| |z|'. + +\MF\ allows you to compare booleans, numerics, pairs, strings, and +transforms for equality; but it doesn't allow the expression +`$p=q$' where $p$ and~$q$ are paths or pens or pictures. Let's +write a general ^{equality test} macro ^^{==} such that `$p==q$' +will be true if and only if $p$ and $q$ are known and equal, +whatever their type. +\begintt +tertiarydef p == q = + if unknown p or unknown q: false + elseif boolean p and boolean q: p=q + elseif numeric p and numeric q: p=q + elseif pair p and pair q: p=q + elseif string p and string q: p=q + elseif transform p and transform q: p=q + elseif path p and path q: + if (cycle p = cycle q) and (length p = length q) + and (point 0 of p = point 0 of q): patheq p of q + else: false fi + elseif pen p and pen q: (makepath p == makepath q) + elseif picture p and picture q: piceq p of q + elseif vacuous p and vacuous q: true + else: false fi enddef; +vardef vacuous primary p = + not(boolean p or numeric p or pair p or path p + or pen p or picture p or string p or transform p) enddef; +vardef patheq expr p of q = + save t; boolean t; t=true; + for k=1 upto length p: + t := (postcontrol k-1 of p = postcontrol k-1 of q) + and (precontrol k of p = precontrol k of q) + and (point k of p = point k of q); + exitunless t; endfor + t enddef; +vardef piceq expr p of q = + save t; picture t; + t=p; addto t also -q; + cull t dropping origin; + (totalweight t=0) enddef; +\endtt +If $p$ and $q$ are numeric or pair expressions, we could relax the condition +that they both be known by saying `@if@ known $p-q$: $p=q$ @else@:~@false@ @fi@'; +transforms could be handled similarly by testing each of their six parts. +But there's no way to tell if booleans, paths, etc., have been equated +when they're both unknown, without the risk of irrevocably changing the +values of other variables. + +\subsection ^{Nonlinear equations}. \MF\ has a built-in solution mechanism +for linear equations, but it balks at nonlinear ones. +You might be able to solve a set of nonlinear equations yourself by +means of algebra or calculus, but in difficult cases it is probably +simplest to use the `^"solve"' macro of plain \MF\!\null. This makes it +possible to solve $n$~equations in $n$~unknowns, provided that at most +one of the equations is nonlinear when one of the unknowns is fixed. + +The general technique will be illustrated here in the case $n=3$. +Let us try to find numbers $a$, $b$, and~$c$ such that +$$\eqalign{-2a+3b/c&=c-3;\cr + ac+2b&=c^3-20;\cr + a^3+b^3&=c^2.\cr}$$ +When $c$ is fixed, the first two equations are linear in $a$ and~$b$. +We make an inequality out of the remaining equation by changing `$=$' +to~`$<$', then we embed the system in a boolean-valued function: +\begintt +vardef f(expr c) = save a,b; + -2a + 3b/c = c - 3; + a*c + 2b = c*c*c - 20; + a*a*a + b*b*b < c*c enddef; +c = solve f(1,7); +-2a + 3b/c = c - 3; +a*c + 2b = c*c*c - 20; +show a, b, c. +\endtt +If we set ^"tolerance"$\null="epsilon"$ (which is the minimum value +that avoids infinite looping in the "solve" routine), the values +$a=1$, $b=2$, and $c=3$ are shown (so it is obvious that the example +was rigged). If "tolerance" has its default value~0.1, we get +$a=1.05061$, $b=2.1279$, $c=3.01563$; this would probably be close +enough for practical purposes, assuming that the numbers represent +pixels. \ (Increasing the tolerance saves time because it +decreases the number of iterations within "solve"; you have to +balance time versus necessary accuracy.) + +The only tricky thing about this use of solve was the choice of the +numbers 1 and~7 in `$f(1,7)$'. In typical applications we'll usually have +obvious values of the unknown where $f$ will be true and false, +but a bit of experimentation was necessary for the problem considered +here. In fact, +it turns out that $f(-3)$ is~true and $f(-1)$ is false, in this +particular system; setting $c="solve"\,f(-3,-1)$ leads to +another solution: $a=7.51442$, $b=-7.48274$, $c=-2.3097$. Furthermore, +it's interesting to observe that this system has no solution with +$c$ between $-1$ and~$+1$, even though $f(+1)$ is true and +$f(-1)$ is false! When $c\rightarrow0$, the quantity $a^3+b^3$ +approaches $-\infty$ when $c$~is negative, $+\infty$ when $c$~is +positive. An attempt to `"solve" $f(1,-1)$' will divide by zero and +come up with several arithmetic overflows. + +\hangindent=-42mm \hangafter=-7 +Let's consider now a real application instead of a contrived example. +\rightfig Da (34mm x 24mm) ^20pt +We wish to find the vertices of a ^{parallelogram} +$z_{1l}$,~$z_{1r}$, $z_{0l}$,~$z_{0r}$, such that +\begindisplay +$x_{1l}=a$; \ \ $y_{1r}=b$; \ \ $z_{0r}=(c,d)$;\cr +length$(z_{1r}-z_{1l})$ $=$ length$(z_{0r}-z_{0l})$ $=$ "stem",\cr +\enddisplay +and such that the lines $z_{1r}\dashto z_{1l}$ and +$z_{1r}\dashto z_{0r}$ meet at a given angle~$\phi$. We can consider +the common angle~$\theta$ of $z_{1r}-z_{1l}$ and $z_{0r}-z_{0l}$ to be +the ``nonlinear'' unknown, so the equations to be solved can be +written +\begindisplay +$\penpos1("stem",\theta)$; \ \ $\penpos0("stem",\theta)$;\cr +$x_{1l}=a$; \ \ $y_{1r}=b$; \ \ $z_{0r}=(c,d)$;\cr +angle$(z_{1r}-z_{0r})\,=\,\theta+\phi$.\cr +\enddisplay +When $\theta$ has a given value, all but the last of these equations +are linear; hence we can solve them by turning the crank in our general method: +\begintt +vardef f(expr theta) = save x,y; + penpos1(stem,theta); penpos0(stem,theta); + x1l=a; y1r=b; z0r=(c,d); + angle(z1r-z0r)<theta+phi enddef; +theta=solve f(90,0); +penpos1(stem,theta); penpos0(stem,theta); +x1l=a; y1r=b; z0r=(c,d); +show z1l,z1r,z0l,z0r,theta,angle(z1r-z0r). +\endtt +For example, if $a=1$, $b=28$, $c=14$, $d=19$, $"stem"=5$, and $\phi=80$, +we get +\begindisplay \def\qquad{\hskip1.5em} +$(1,23.703)$&$(3.557,28)$&$(11.443,14.703)$&$(14,19)$&59.25&139.25 +\enddisplay +as answers when $"tolerance"="epsilon"$, and +\begindisplay \def\qquad{\hskip1.5em} +$(1,23.702)$&$(3.554,28)$&$(11.446,14.702)$&$(14,19)$&59.28&139.25 +\enddisplay +when $"tolerance"=0.1$. +The function $f$ prescribed by the general method +can often be simplified; for example, in this case we can remove +redundancies and get just +\begintt +vardef f(expr theta) = save x,y; + penpos1(stem,theta); x1l=a; y1r=b; + angle(z1r-(c,d))<theta+phi enddef. +\endtt +The problem just solved can be called the ``^{d} problem,'' because it arose in +connection with N.~N. ^{Billawala}'s meta-design of a ^{black-letter} +`{\manual?}', and because it appears in Appendix~D. + +\subsection Nonlinear interpolation. +Suppose a designer has empirically determined good values of some quantity +$f(x)$ for several values of~$x$; for example, $f(x)$ might be a +stroke weight or a serif length or an amount of overshoot, etc. These +empirical values can be generalized and incorporated into a ^{meta-design} +if we are able to ^{interpolate} between the original $x$'s, obtaining +$f(x)$ at intermediate points. + +Suppose the data points are known for $x=x_1<x_2<\cdots<x_n$. We can +represent $f(x)$ by its graph, which we can assume is well approximated +by the \MF\ path defined by +\begindisplay +$F\,=\,\bigl(x_1,f(x_1)\bigr)\to\bigl(x_2,f(x_2)\bigr)\to +\<etc.>\to\bigl(x_n,f(x_n)\bigr)$ +\enddisplay +if $f(x)$ is a reasonable ^{function}. Therefore interpolation can be +done by using path intersection (!): +\begintt +vardef interpolate expr F of x = save t; t = + if x < xpart point 0 of F: extrap_error 0 + elseif x > xpart point infinity of F: extrap_error infinity + else: xpart(F intersectiontimes verticalline x) fi; + ypart point t of F enddef; +def extrap_error = hide(errhelp "The extreme value will be used."; + errmessage "`interpolate' has been asked to extrapolate"; + errhelp "") enddef; +vardef verticalline primary x = + (x,-infinity)--(x,infinity) enddef; +\endtt +For example, if $f(1)=1$, $f(3)=2$, and $f(15)=4$, this interpolation +scheme gives `interpolate $(1,1)\to(3,2)\to(15,4)$ of~7' the value 3.37. + +\subsection Drawing with ^{overlays}. Let's leave numerical computations +now and go back into the realm of pictures. Bruce ^{Leban} has suggested +an extension of plain \MF's `^@clearit@/^@showit@/^@shipit@' commands +by which `^@fill@' and `^@draw@' essentially operate on imaginary sheets of +clear plastic. A new command `^@keepit@' places a fresh sheet of plastic +on top of whatever has already been drawn, thereby preserving the covered image +against subsequent erasures. + +We can implement @keepit@ by introducing a new picture variable +^"totalpicture", and new boolean variables ^"totalnull", ^"currentnull", +then defining macros as follows: +\begintt +def clearit = currentpicture:=totalpicture:=nullpicture; + currentnull:=totalnull:=true; enddef; +def keepit = cull currentpicture keeping (1,infinity); + addto totalpicture also currentpicture; + currentpicture:=nullpicture; + totalnull:=currentnull; currentnull:=true; enddef; +def addto_currentpicture = + currentnull:=false; addto currentpicture enddef; +def mergeit (text do) = + if totalnull: do currentpicture + elseif currentnull: do totalpicture + else: begingroup save v; picture v; v:=currentpicture; + cull v keeping (1,infinity); addto v also totalpicture; + do v endgroup fi enddef; +def shipit = mergeit(shipout) enddef; +def showit_ = mergeit(show_) enddef; +def show_ suffix v = display v inwindow currentwindow enddef; +\endtt +The "totalnull" and "currentnull" bookkeeping isn't strictly necessary, +but it contributes greatly to the efficiency of this scheme if the +extra generality of @keepit@ is not actually being used. +The `$v$' computations in @mergeit@ involve copying the accumulated +picture before displaying it or shipping it out; this takes time, +and it almost doubles the amount of memory needed, so we try to avoid it +when possible. + +\subsection Filing pictures. If you want to store a picture in a file +and read it in to some other \MF\ job, you face two problems: +(1)~\MF's @shipout@ command implicitly culls the picture, so that only +binary data is left. Pixel values $>0$ are distinguished from pixel +values $<=0$, but no other information about those values will survive. +\ (2)~The result of ^@shipout@ can be used in another \MF\ job only if +you have an auxiliary program that converts from binary ^|gf| format +to a \MF\ source program; \MF\ can write |gf| files, but it can't +read them. + +These problems can be resolved by using \MF's ^{transcript} or ^{log file} +as the output medium, instead of using the |gf| file. For example, let's +consider first the use of ^"tracingedges". Suppose we say +\begindisplay +"tracingedges" $:=$ 1;\cr +\<any sequence of @fill@, @draw@, or @filldraw@ commands>\cr +@message@ |"Tracing edges completed."|; \ $"tracingedges":=0$;\cr +\enddisplay +then the log file will contain lines such as the following: +\beginlines +|Tracing edges at line 15: (weight 1)| +|(1,5)(1,2)(2,2)(2,1)(3,1)(3,0)(8,0)(8,1)(9,1)(9,2)(10,2)(10,8)(9,8)| +|(9,9)(8,9)(8,10)(3,10)(3,9)(2,9)(2,8)(1,8)(1,5).| +\smallskip +|Tracing edges at line 15: (weight -1)| +|(3,5)(3,2)(4,2)(4,1)(7,1)(7,2)(8,2)(8,8)(7,8)(7,9)(4,9)(4,8)(3,8)(3,5).| +\smallskip +|Tracing edges at line 18: (weight -1)| +|(No new edges added.)| +\smallskip +|Tracing edges completed.| +\endlines +Let us write macros so that these lines are acceptable input to \MF\!. +\begintt +def Tracing=begingroup save :,[,],Tracing,edges,at,weight,w; + delimiters []; let Tracing = endfill; interim turningcheck := 0; + vardef at@#(expr wt) = save (,); w := wt; + let ( = lp; let ) = rp; fill[gobble begingroup enddef; + let edges = \; let weight = \; let : = \; enddef; +def lp = [ enddef; +def rp = ] -- enddef; +vardef No@# = origin enddef; +def endfill = cycle] withweight w endgroup; enddef; +def completed = endgroup; enddef; +\endtt +^^"turningcheck" ^^@save@ ^^@delimiters@ +The precise form of edge-traced output, with its limited vocabulary +and its restricted use of parentheses and commas, has been exploited here. + +With slight changes to this code, you can get weird effects. +For example, if the definition of |rp| is changed to `|]..tension 4..|', +^^{tension} and if `|scaled|~|5pt|' is inserted before `|withweight|', +the image will be an ``^{almost digitized}'' character: +\displayfig Daa (18.5mm) +(The bumps at the left here are due to the repeated points `|(1,5)|' and +`|(3,5)|' in the original data. You can remove them by adding an extra +pass, first tracing the edges that are output by the {\sl unmodified\/} +|Tracing| macros.) + +Although the effects of @fill@ and @draw@ can be captured by +"tracingedges", other operations like ^{culling} are not traced. +Let us therefore consider the more general picture representation +that \MF\ produces when ^"tracingoutput" is positive, or when you +ask it to ^@show@ a picture (see Chapter~13). The macros on the next +page will recreate a picture from input of the form +\begintt +beginpicture +row 1: 1+ -2- || 0+ 2- +row 0: || 0+ 2++ 5--- +row -2: 0- -2+ || +endpicture +\endtt +where the middle three lines have been copied verbatim from a transcript +file. \ (The task would be easier if the token `|-|' didn't have +to perform two different functions!) +\begintt +let neg_ = -; let colon_ = :; +def beginpicture = + begingroup save row, ||, :, ---, --, +, ++, +++, v, xx, yy, done; + picture v; v := nullpicture; interim turningcheck := 0; + let --- = mmm_; let -- = mm_; + let + = p_; let ++ = pp_; let +++ = ppp_; + let row = pic_row; let || = relax; let : = pic_colon; : enddef; +def pic_row primary y = done; yy := y; enddef; +def pic_colon primary x = + if known x colon_ ; xx := x; pic_edge fi enddef; +def pic_edge = + let - = m_; + addto v contour unitsquare xscaled xx shifted(0,yy) enddef; +def mmm_ = withweight 3; let - = neg_; : enddef; +def mm_ = withweight 2; let - = neg_; : enddef; +def m_ = withweight 1; let - = neg_; : enddef; +def p_ = withweight neg_1; let - = neg_; : enddef; +def pp_ = withweight neg_2; let - = neg_; : enddef; +def ppp_ = withweight neg_3; let - = neg_; : enddef; +transform xy_swap; xy_swap = identity rotated 90 xscaled -1; +def endpicture = done; + v transformed xy_swap transformed xy_swap endgroup enddef; +\endtt +The reader will find it instructive to study these macros closely. +When `|done|' appears, it is an unknown primary, so |pic_colon| +will not attempt to generate another edge. Each new edge also +inserts a cancelling edge at $x=0$. The two applications ^^"xy\_swap" +of |xy_swap| at the end will clear away all redundant edges. (Double +swapping is a bit faster than the operation `|rotated-90| |rotated|~|90|' +that was used for this purpose in Chapter~13.) + +\subsection Fattening a pen. Let's move on to another aspect of +\MF\ by considering +an operation on ^{pen} ^{polygons}: Given a @pen@ value~$p$, +the task is to construct a pen `^@taller@~$p$' that is one pixel +taller. For example, if $p$ is the ^{diamond} nib +`$(0.5,0)\dashto(0,0.5)\dashto(-0.5,0)\dashto(0,-0.5)\dashto\cycle$', +the taller nib will be +\begindisplay +$(0.5,0.5)\dashto(0,1)\dashto(-0.5,0.5)\dashto(-0.5,-0.5)\dashto(0,-1) + \dashto(0.5,-0.5)\dashto\cycle$; +\enddisplay +if $p$ is a tilted ^{penrazor} `$(-x,-y)\dashto(x,y)\dashto\cycle$', +the taller nib will be +\begindisplay +$(-x,-y-0.5)\dashto(x,y-0.5)\dashto(x,y+0.5)\dashto(-x,-y+0.5)\dashto\cycle$, +\enddisplay +assuming that $x>0$. The macro itself turns out to be fairly simple, but +it makes instructive use of ^{path} and pen operations. + +We want to split the pen into two parts, a ``bottom'' half and a ``top'' +half; the bottom half should be shifted down by .5~pixels, and the +top half should be shifted up. The dividing points between halves occur +at the leftmost and rightmost vertices of the pen. Hmmm; a potential problem +arises if there are two or more leftmost or rightmost points; for example, +what if we try to make `@taller@ @taller@~$p$'? Fortunately \MF\ doesn't +mind if a pen polygon has three or more consecutive vertices that +lie on a line, hence we can safely choose {\sl any\/} leftmost +point and any rightmost point. + +The next question is, ``How should we find leftmost and rightmost +points?'' We will, of course, use ^@makepath@ to find the set of all +vertices; so we could simply traverse the path and find the minimum +and maximum $x$~coordinates. However, it will be faster (and more fun) +to use either ^{directiontime} or ^{penoffset} for this purpose. +Let's try directiontime first: +\begintt +vardef taller primary p = + save r, n, t, T; path r; + r = tensepath makepath p; n = length r; + t = round directiontime up of r; + T = round directiontime down of r; + if t>T: t := t-n; fi + makepen(subpath(T-n,t) of r shifted .5down + --subpath(t,T) of r shifted .5up -- cycle) enddef; +\endtt +The result of @makepath@ has control points equal to their adjacent +vertices, so it could not be used with directiontime. +\ (If any key point is equal to its precontrol or postcontrol, +the ``^{velocity}'' of the path is zero at that point; directiontime +assumes that all directions occur whenever the velocity drops to zero.) \ +Therefore we have used `^@tensepath@'. +This almost works, once we realize that the values +of $t$ and~$T$ sometimes need to be rounded to integers. But it +fails for pens like @penspeck@ that have points very close together, +since @tensepath@ is no better than an unadulterated @makepath@ in such cases. +Furthermore, even if we could define a nice path from~$p$ (for example +by scaling it up), we would run into problems of +numerical instability, in cases like @penrazor@ where +the pen polygon takes a $180^\circ$ turn. Razor-thin pens cannot be recognized +easily, because they might have more than two vertices; for example, +rotations of future pens such as +`@makepen@($"left"\to"origin"\to"right"\to\cycle$)' are problematical. + +We can obtain a more robust result by using penoffset, because +this operation makes use of the convexity of the polygon. The +``fastest'' solution looks like this: +\begintt +vardef taller primary p = + save q, r, n, t, T; pen q; q = p; + path r; r = makepath q; n = length r; + t = round xpart(r intersectiontimes penoffset up of q); + T = round xpart(r intersectiontimes penoffset down of q); + if t>T: t := t-n; fi + makepen(subpath(T-n,t) of r shifted .5down + --subpath(t,T) of r shifted .5up -- cycle) enddef; +\endtt +^^{intersectiontimes} ^^{subpath} +(The argument $p$ is copied into $q$, in case it's a ^{future pen}; +this means that the conversion of future pen to pen need be +done only once instead of three times.) + +\subsection ^{Bernshte{\u\i}n} polynomials. And now, for our last trick, +let's try to extend \MF's syntax so that it will accept generalized +^{mediation} formulas of the form `$t[u_1,\ldots,u_n]$' for all $n\ge2$. +\ (This notation was introduced for $n=3$ and~4 in Chapter~14, when we were +considering fractional subpaths.) \ If $n>2$, the identity +\begindisplay +$t[\,u_1,\ldots,u_n]\;=\;t\bigl[t[u_1,\ldots,u_{n-1}],t[u_2,\ldots,u_n]\,\bigr]$ +\enddisplay +defines $t[u_1,\ldots,u_n]$ recursively, and it can be shown that the +alternative definition +\begindisplay +$t[\,u_1,\ldots,u_n]\;=\;t\bigl[t[u_1,u_2],\ldots,t[u_{n-1},u_n]\,\bigr]$ +\enddisplay +gives the same result. \ (Indeed, we have +\begindisplay +$\displaystyle t[u_1,\ldots,u_n]\;=\;\sum_{k=1}^n{n-1\choose k-1} + (1-t)^{n-k}t^{k-1}u_k,$ +\enddisplay +a Bernshte{\u\i}n polynomial of order $n-1$.) + +Our problem is to change the meaning of \MF's ^{brackets} so that +expressions like `$1/2[a,b,c,d]$' will evaluate to `$.125a+.375b+.375c ++.125d$' in accordance with the formulas just given, but we don't want +to mess up the other primitive uses of brackets in contexts like +`|x[n]|' and `|path|~|p[][]a|'. We also want to be able to use +brackets inside of brackets. + +The reader is challenged to try solving this problem before looking at +the weird solution that follows. Perhaps there is a simpler way? +\begintt +def lbrack = hide(delimiters []) lookahead [ enddef; +let [[[ = [; let ]]] = ]; let [ = lbrack; +def lookahead(text t) = + hide(let [ = lbrack; + for u=t, hide(n_ := 0; let switch_ = first_): switch_ u; endfor) + if n_<3: [[[t]]] else: Bernshtein n_ fi enddef; +def first_ primary u = + if numeric u: numeric u_[[[]]]; store_ u + elseif pair u: pair u_[[[]]]; store_ u fi; + let switch_ = store_ enddef; +def store_ primary u = u_[[[incr n_]]] := u enddef; +primarydef t Bernshtein nn = + begingroup for n=nn downto 2: + for k=1 upto n-1: u_[[[k]]]:=t[[[u_[[[k]]],u_[[[k+1]]] ]]]; + endfor endfor u_[[[1]]] endgroup enddef; +\endtt +The most subtle thing about this code is the way it uses the `empty' +option of a ^\<for list> to dispense with ^{empty text arguments}. +Since \MF\ evaluates all the expressions of a ^@for@ loop before +reading the loop text, and since `|n_|' and `|u_|' are used here +only when no recursion is taking place, it is unnecessary to ^{save} +their values even when brackets are nested inside of brackets. + +Of course this trick slows \MF\ down tremendously, whenever brackets +appear, so it is just of academic interest. But it seems to work +in all cases except with respect to formulas that involve `^|]]|' +(two consecutive brackets); the latter token, which plain \MF\ expands +to `|]|~|]|', is not expanded when |lookahead| reads its ^{text +argument}, hence the user must remember to insert a space between +consecutive brackets. \looseness=-1 + +\endchapter + +Their tricks an' craft hae put me daft, +They've taen me in, an' a' that. +\author ROBERT ^{BURNS}, {\sl The Jolly Beggar\/} (1799) % air 7 + +\bigskip + +Ebery house hab him dutty carner. +\author ^{ANDERSON} and ^{CUNDALL}, {\sl Jamaica Proverbs and Sayings\/} (1927) + % #755 in 2nd edition; was not in the first (1910) edition + +\eject + \beginchapter Appendix E. Examples + +We've seen lots of examples of individual letters or parts of letters; +let's concentrate now on the problem of getting things all together. +The next two pages contain the entire contents of an example file +`|logo.mf|', which generates the letters of the \MF\ ^{logo}. The file +is short, because only seven letters are involved, and because those letters +were intentionally done in a style that would be easy for the system they name. +But the file is complete, and it illustrates in simplified form all +the essential aspects of larger fonts: Ad~hoc dimensions are +converted to pixels; subroutines are defined; programs for +individual letters appear; intercharacter and interword +spacing conventions are nailed down. Furthermore, the character programs +are careful to draw letters that will be +well adapted to the raster, even if pixels on the output device are +not square. % It's all there, in one short program. + +We've been studying the `\MF' letters off and on since Chapter~4, making +our examples slightly more complex as more of the language has been +encountered. Finally we're ready to pull out all the stops and look at the +real, professional-quality |logo.mf|, which incorporates all the best +suggestions that have appeared in the text and in answers to the exercises. + +It's easy to generate a font with |logo.mf|, by proceeding as explained +in Chapter~11. For example, the |logo10| font that produces `\MF' in +10-point size can be created for a low-resolution printer by running +\MF\ with the ^{command line} +\begintt +\mode=lowres; input logo10 +\endtt +where the ^{parameter file} |logo10.mf| appears in that chapter. Furthermore +the slanted version `{\manual 89:;<=>:}\kern3pt' can be created by +inputting the parameter file |logosl10.mf|, which says simply +\begintt +% 10-point slanted METAFONT logo +slant := 1/4; +input logo10 +\endtt +The ^"slant" parameter affects ^"currenttransform" as explained in +Chapter~15. + +There isn't a great deal of ``^{meta-ness}'' in the |logo.mf| design, +because only a few forms of the \MF\ logo are needed. However, some +interesting variations are possible; for example, if we use the +parameter files +\begindisplay \def\qquad{\hskip4em\relax} \advance\belowdisplayskip by 3pt +|font_size 30pt#;|&|font_size 10pt#;|\cr +|ht#:=25pt#;|&|ht#:=6pt#;|\cr +|xgap#:=1.5pt#;|&|xgap#:=2pt#;|\cr +|u#:=3/9pt#;|&|u#:=4/3pt#;|\cr +|s#:=1/3pt#;|&|s#:=-2/3pt#;|\cr +|o#:=2/9pt#;|&|o#:=1/9pt#;|\cr +|px#:=1pt#;|&|px#:=1/3pt#;|\cr +|slant:=-1/9;|\cr +\enddisplay +we get \kern2pt{\manual BCDGHIJD} and \kern4pt{\manual KLUVWvwU}, +\kern4pt\ respectively. + +\goodbreak\begingroup\obeylines\everypar{\strut}\parindent=0pt +|% Routines for the METAFONT logo, as found in The METAFONTbook| +|% (logo10.mf is a typical parameter file)| +\medskip +|mode_setup;| +|if unknown slant: slant:=0 else: currenttransform:=| +| identity slanted slant yscaled aspect_ratio fi;| +\medskip +|ygap#:=(ht#/13.5u#)*xgap#; % vertical adjustment| +|ho#:=o#; % horizontal overshoot| +|leftstemloc#:=2.5u#+s#; % position of left stem| +|barheight#:=.45ht#; % height of bar lines| +|py#:=.9px#; % vertical pen thickness| +\medskip +|define_pixels(s,u);| +|define_whole_pixels(xgap);| +|define_whole_vertical_pixels(ygap);| +|define_blacker_pixels(px,py);| +|pickup pencircle xscaled px yscaled py;| +|logo_pen:=savepen;| +|define_good_x_pixels(leftstemloc);| +|define_good_y_pixels(barheight);| +|define_corrected_pixels(o);| +|define_horizontal_corrected_pixels(ho);| +\medskip +|def beginlogochar(expr code, unit_width) =| +| beginchar(code,unit_width*u#+2s#,ht#,0);| +| pickup logo_pen enddef;| +\medskip +|def super_half(suffix i,j,k) =| +| draw z.i{0,y.j-y.i}| +| ... (.8[x.j,x.i],.8[y.i,y.j]){z.j-z.i}| +| ... z.j{x.k-x.i,0}| +| ... (.8[x.j,x.k],.8[y.k,y.j]){z.k-z.j}| +| ... z.k{0,y.k-y.j} enddef;| +\medskip +|beginlogochar("M",18);| +|x1=x2=leftstemloc; x4=x5=w-x1; x3=w-x3;| +|y1=y5; y2=y4; bot y1=-o;| +|top y2=h+o; y3=y1+ygap;| +|draw z1--z2--z3--z4--z5;| +|labels(1,2,3,4,5); endchar;| +\medskip +|beginlogochar("E",14);| +|x1=x2=x3=leftstemloc;| +|x4=x6=w-x1+ho; x5=x4-xgap;| +|y1=y6; y2=y5; y3=y4;| +|bot y1=0; top y3=h; y2=barheight;| +\nointerlineskip +\smash{\vbox{ + \rightline{\figbox{Eb}{224\apspix}{216\apspix}\vbox} % F + \kern12pt + \rightline{\figbox{A18a}{240\apspix}{216\apspix}\vbox} % A + \kern12pt + \rightline{\figbox{Ea}{208\apspix}{216\apspix}\vbox} % T + \kern3.5pt}} +\endgroup\eject\begingroup\obeylines\everypar{\strut}\parindent=0pt +\smash{\vtop{\kern0pt\kern6pt + \rightline{\figbox{18a}{240\apspix}{216\apspix}\vbox} % O + \kern12pt + \rightline{\figbox{4c}{288\apspix}{216\apspix}\vbox} % M + \kern12pt + \rightline{\figbox{11a}{224\apspix}{216\apspix}\vbox} % E + \kern12pt + \rightline{\figbox{21a}{240\apspix}{216\apspix}\vbox} % N + }}\nointerlineskip +|draw z6--z1--z3--z4; draw z2--z5;| +|labels(1,2,3,4,5,6); endchar;| +\medskip +|beginlogochar("T",13);| +|italcorr ht#*slant + .5u#;| +|if .5w<>good.x .5w: change_width; fi| +|lft x1=-eps; x2=w-x1; x3=x4=.5w;| +|y1=y2=y3; top y1=h; bot y4=-o;| +|draw z1--z2; draw z3--z4;| +|labels(1,2,3,4); endchar;| +\medskip +|beginlogochar("A",15);| +|x1=.5w; x2=x4=leftstemloc; x3=x5=w-x2;| +|top y1=h+o; y2=y3=barheight;| +|bot y4=bot y5=-o;| +|draw z4--z2--z3--z5; super_half(2,1,3);| +|labels(1,2,3,4,5); endchar;| +\medskip +|beginlogochar("F",14);| +|x1=x2=x3=leftstemloc;| +|x4=w-x1+ho; x5=x4-xgap;| +|y2=y5; y3=y4; bot y1=-o;| +|top y3=h; y2=barheight;| +|draw z1--z3--z4; draw z2--z5;| +|labels(1,2,3,4,5); endchar;| +\medskip +|beginlogochar("O",15);| +|x1=x4=.5w; top y1=h+o; bot y4=-o;| +|x2=w-x3=good.x(1.5u+s); y2=y3=barheight;| +|super_half(2,1,3); super_half(2,4,3);| +|labels(1,2,3,4); endchar;| +\medskip +|beginlogochar("N",15);| +|x1=x2=leftstemloc; x3=x4=x5=w-x1;| +|bot y1=bot y4=-o;| +|top y2=top y5=h+o; y3=y4+ygap;| +|draw z1--z2--z3; draw z4--z5;| +|labels(1,2,3,4,5); endchar;| +\medskip +|ligtable "T": "A" kern -.5u#;| +|ligtable "F": "O" kern -u#;| +\medskip +|font_quad:=18u#+2s#;| +|font_normal_space:=6u#+2s#;| +|font_normal_stretch:=3u#;| +|font_normal_shrink:=2u#;| +|font_identifier:="MFLOGO" if slant<>0: & "SL" fi;| +|font_coding_scheme:="AEFMNOT only";| +\endgroup\goodbreak + +Everything in |logo.mf| has already been explained previously in this +book except for the very last two lines, which define a `^@font\_identifier@' +and a `^@font\_coding\_scheme@'. These are optional bits of information +that are discussed in Appendix~F\null. Furthermore an ^{italic correction} +has been specified for the letter `{\manual T}', since it's the final +letter of `\kern-1.4pt{\manual 89:;<=>:\/}'. + +\medskip\ninepoint +The program for a complete typeface will differ from the program for +this simple logo font primarily in degree; there will be lots more +parameters, lots more subroutines, lots more characters, lots more +ligatures and kerns and whatnot. But there will probably also be +more administrative machinery, designed to facilitate the creation, +testing, and modification of characters, since a large enterprise +requires good organization. The remainder of this appendix is +devoted to an example of how this might be done: We shall discuss +the additional kinds of routines that the author ^^{Knuth} found +helpful while he was developing the ^{Computer Modern} family +of typefaces. + +The complete, unexpurgated programs for Computer Modern appear in {\sl +Computers \& Typesetting}, Volume~E\null; but since they have evolved +over a long period of time, they are rather complex. We shall simplify +the details so that it will be easier to grasp the important issues +without being distracted by irrelevant technicalities. + +The simple logo fonts discussed above are generated by two types +of files: There are parameter files like |logo10.mf|, and there is +a program file |logo.mf|. The Computer Modern fonts, being more +extensive, are generated by four types of files: There are +{\sl^{parameter files}\/} like `|cmr10.mf|', which specify the +ad hoc dimensions for particular sizes and styles of type; there are +{\sl^{driver files}\/} like `|roman.mf|', which serve as chief +executives of the font-generation process; there are +{\sl^{program files}\/} like `|punct.mf|', which contain programs +for individual characters; and there's a {\sl^{base file}\/} called +`|cmbase.mf|', which contains the subroutines and other macros used +throughout the system. + +Our logo example could have been cast in this more general mold by moving +the character programs into a program file `|METAFON.mf|', and by moving +most of the opening material into a base file `|logobase.mf|' +that looks like this: +\beginlines +|% Base file for the METAFONT logo| +|logobase:=1; % when logobase is known, this file has been input| +\smallskip +|def font_setup =| +| if unknown slant: slant:=0 else: currenttransform:=| +%| identity slanted slant yscaled aspect_ratio fi;| +\qquad\smash{\vdots}\qquad\vbox to10pt{}% + \raise1pt\hbox{(the previous code is unchanged)} +| define_corrected_pixels(o);| +| define_horizontal_corrected_pixels(ho); enddef;| +\endlines +followed by the definitions of |beginlogochar| and |super_half|. +Then we're left with a driver file |logo.mf| that looks like this: +\beginlines +|% Driver file for the METAFONT logo| +|if unknown logobase: input logobase fi| +\smallskip +|mode_setup; font_setup; % establish pixel-oriented units| +|input METAFON % generate the characters| +\smallskip +|ligtable "T": "A" kern -.5u#;| +\weakendlines +and so on, concluding as before. + +In general, a parameter file calls on a driver file, which calls on +one or more program files; the base file contains predefined macros +shared by all. There may be several driver files, each using a +different combination of program files; for example, Computer Modern +has `|roman.mf|' and `|italic.mf|', % a little white lie, multiplied below +both of which call on |punct.mf| to generate punctuation marks, +although they use different program files to generate the lowercase +alphabets. Characters are partitioned into program files so that +they can be shared by different drivers. + +Parameter files in Computer Modern don't quite follow the conventions +of\/ |logo10.mf|. Here, for example, are the +opening and closing lines of ^|cmr10.mf|: +\beginlines +|% Computer Modern Roman 10 point| +|if unknown cmbase: input cmbase fi| +\smallskip +|font_identifier "CMR"; font_size 10pt#;| +\smallskip +|u#:=20/36pt#; % unit width| +|serif_fit:=0pt#; % extra sidebar near serifs| +|letter_fit:=0pt#; % extra space added to all sidebars| +\vskip-3pt +\qquad\vdots +|serifs:=true; % should serifs and bulbs be attached?| +|monospace:=false; % should all characters have the same width?| +\smallskip +|generate roman % switch to the driver file| +\endlines +The main differences are: \ (1) There's special code at the beginning, to +make sure that |cmbase.mf| has been loaded. The base file includes +several things that are needed right away; for example, |cmbase| declares +the variables `"serifs"' and `^"monospace"' to be of type @boolean@, +so that boolean-valued parameter assignments like `$"serifs":=@true@$' +will be legal. \ (2)~The @font\_identifier@ is defined in the parameter file, +not in the driver file. \ (3)~The last line says `^@generate@' instead of +`@input@'; the base file defines @generate@ to be the same as @input@, +but other meanings are assigned by utility routines that we'll study later. +\ (4)~The final `^@end@' is no longer present in the parameter file. + +The |roman.mf| driver looks like this (vastly simplified): +^^@font\_slant@ ^^@font\_quad@ ^^@font\_normal\_space@ +^^@font\_normal\_stretch@ ^^@font\_normal\_shrink@ +\beginlines +|% The Computer Modern Roman family of fonts| +\smallskip +|mode_setup; font_setup;| +\smallskip +|input romanu; % upper case (majuscules)| +|input romanl; % lower case (minuscules)| +|input romand; % numerals| +|input punct; % punctuation marks| +\smallskip +|font_slant slant;| +|if monospace: font_quad 18u#;| +| font_normal_space 9u#; % no stretching or shrinking| +|else: font_quad 18u#+4letter_fit#;| +| font_normal_space 6u#+2letter_fit#; % interword spacing| +| font_normal_stretch 3u#; % with ``glue''| +| font_normal_shrink 2u#;| +| input romlig; % f ligatures| +| |^|ligtable|| "f": "i" =: oct"014", "f" =: oct"013", "l" =: oct"015",| +| "'" kern u#, "?" kern u#, "!" kern u#;| +| ligtable oct"013": "i" =: oct"016", "l" =: oct"017", % ffi and ffl| +| "'" kern u#, "?" kern u#, "!" kern u#;| +| ligtable "-": "-" =: oct"173"; % en dash| +| ligtable oct"173": "-" =: oct"174"; % em dash| +| ligtable "`": "`" =: oct"134"; % open quotes| +| ligtable "'": "'" =: oct"042", % close quotes| +| "?" kern 2u#, "!" kern 2u#;| +|fi; |^|bye.| +\endlines +In a ^{monospaced} font like ^|cmtt10|, all characters will be exactly +$9u\0$ wide. Both |cmr10| and~|cmtt10| use the |roman| driver, but +|roman| omits the ligatures and changes the interword spacing +when it is producing monospaced fonts. + +The program files of Computer Modern have slightly different conventions +from those of plain \MF\!\null. Here, for example, are the ^^{.} ^^{em dash} +programs for two of the simplest ^{punctuation marks}: +\beginlines +|cmchar "Period";| +|numeric dot_diam#; dot_diam# = if monospace: 5/4 fi dot_size#;| +|define_whole_blacker_pixels(dot_diam);| +|beginchar(".",5u#,dot_diam#,0);| +|adjust_fit(0,0); pickup fine.nib;| +|pos1(dot_diam,0); pos2(dot_diam,90);| +|x1l=good.x(x1l+.5w-x1); bot y2l=0; z1=z2; dot(1,2); % dot| +|penlabels(1,2); endchar;| +\medskip +\leftline{\hskip3pc\figbox{Ec\&Ed}{3in}{360\apspix}\vbox} +\smallskip +|iff not monospace: cmchar "Em dash";| +|beginchar(oct"174",18u#,x_height#,0);| +|italcorr .61803x_height#*slant + .5u#;| +|adjust_fit(letter_fit#,letter_fit#);| +|pickup crisp.nib; pos1(vair,90); pos2(vair,90);| +|y1r=y2r=good.y(y1r+.61803h-y1); lft x1=-eps; rt x2=w+eps;| +|filldraw stroke z1e--z2e; % crossbar| +|penlabels(1,2); endchar;| +\endlines +The new structural features in these programs are: (1)~`^@cmchar@', +which appears at the very beginning of each character program; +(2)~`^@iff@~\<boolean expression>:', which precedes @cmchar@ if +the character is to be generated only when the boolean expression +is true; (3)~`^@adjust\_fit@', which can change the amount of white space +at the character's left and/or right; (4)~pens called `"fine.nib"' and +`"crisp.nib"'; (5)~new macros `"pos"', `"dot"', and `"stroke"', +discussed further below. + +The base file |cmbase.mf| begins as follows: +\beginlines +|% The base file for Computer Modern (a supplement to plain.mf)| +\smallskip +|cmbase:=1; % when cmbase is known, this file has been input| +\smallskip +|let cmchar = relax; % `cmchar' should precede each character| +|let generate = input; % `generate' should follow the parameters| +\smallskip +|newinternal slant, superness,| $\cdots$ | % purely numeric parameters| +|boolean serifs, monospace,| $\cdots$ | % boolean parameters| +\endlines +These few lines are straightforward enough. Although |cmchar| is defined +to be the same as ^|relax|, which does nothing, the definition of +|cmchar| will be changed by certain utility programs below; this will +prove to be a convenience when characters are designed, tested, and maintained. + +The next few lines of |cmbase| are trickier. They implement the `@iff@\kern1pt' +feature, which bypasses unwanted characters at high speed. +\beginlines +|let semi_ = ;; let colon_ = :; let endchar_ = endchar;| +|def iff expr b =| +| if b: let next_ = use_it else: let next_ = lose_it fi;| +| next_ enddef;| +|def use_it = let : = restore_colon; enddef;| +|def restore_colon = let : = colon_; enddef;| +|def lose_it = let endchar = fi; inner cmchar; let ; = fix_ semi_| +| if false enddef;| +|def fix_ = let ; = semi_; let endchar = endchar_; outer cmchar; enddef;| +|def always_iff = let : = endgroup; killboolean enddef;| +|def killboolean text t = use_it enddef;| +|outer cmchar;| +\weakendlines +^^@always\_if@ ^^@inner@ ^^@outer@ +(The |lose_it| routine assumes that every character program will end +with `|endchar;|'.) + +The most interesting part of |cmbase| is probably the way it allows the +``^{side\-bearings}'' of each character to be fine-tuned. The amount of +space at the left and right edges of the character's ``^{bounding box}'' +can be adjusted without actually shifting the picture, and without +changing the width that was specified in @beginchar@. Here's how it works: +After a @beginchar@ command and an optional @italcorr@, each Computer +Modern character program is supposed to say +\begindisplay +@adjust\_fit@(\<left sidebearing adjustment>,\thinspace + \<right sidebearing adjustment>); +\enddisplay +sidebearing adjustments are given in true, ``sharped'' units. +The ^@adjust\_fit@ routine essentially adds extra space at the left +and right, corresponding to the sidebearing adjustments. An ad-hoc +dimension called ``^"letter\_fit"$\0$'' is also added to all sidebearings, +behind the scenes. + +Our example program for the |"."|\ says simply `@adjust\_fit@$(0,0)$'; +this means that only "letter\_fit" is added. The program for em-dash +says `@adjust\_fit@$("letter\_fit"\0,\allowbreak"letter\_fit"\0)$', hence +the sidebearings are increased by 2"letter\_fit" at each side. +The total character width of the em-dash comes to $18u\0+ +4"letter\_fit"\0$ (which is indeed one em, the value of ^@font\_quad@ +specified in the |roman| driver file). + +The program for lowercase `^{b}' in file |romanl.mf| says +`@adjust\_fit@$("serif\_fit"\0,0)$'; this adds the ^"serif\_fit" +parameter at the left, to compensate for the possible appearance +of a serif at the left of this character. The "serif\_fit" is +zero in |cmr10|, but it has a negative value in a ^{sans-serif} font, +and a positive value when serifs are extralong. + +The nice thing about @adjust\_fit@ is that it's an ``add-on'' +specification that doesn't affect the rest of the character design. +The program can still be written as if 0~were the left edge and +$w$~were the right edge; afterwards the fit can be adjusted without +changing the program or the shapes. + +There are two versions of @adjust\_fit@, one for normal fonts +and one for ^{mono\-space} fonts. Both of them are slightly complicated +by something called ^"shrink\_fit", which will be explained later; +for the moment, let's just imagine that $"shrink\_fit"=0$. Here is the +routine for the normal case: +\beginlines +|def normal_adjust_fit(expr left_adjustment,right_adjustment) =| +| l := -hround(left_adjustment*hppp)-letter_fit;| +| interim xoffset := -l;| +| charwd := charwd+2letter_fit#+left_adjustment+right_adjustment;| +| r := l+hround(charwd*hppp)-shrink_fit;| +| w := r-hround(right_adjustment*hppp)-letter_fit;| +| enddef;| +\endlines +Variables ^"l" and ^"r" are set to the actual pixel boundaries of the +character; thus, plain \MF's bounding box has $0\le x\le w$, but +Computer Modern's has $l\le x\le r$. ^{Rounding} has been done +very carefully so that the sidebearings will have consistent +relationships across an entire font. Notice that ^"w"~has been +recalculated; this means that @adjust\_fit@ can affect the digitization, +but---we hope---in a beneficial way. + +In a monospaced font, the @adjust\_fit@ routine changes the +unit-width parameter, ^"u", so that the total width after adjustment +comes out to be constant. Similar adjustments are made to parameters +like ^"jut", the nominal serif length. The width of all characters +in a monospaced font will be $"mono\_charwd"\0$ in true units, +^"mono\_charwd" in pixels. The italic correction of all +characters will be $"mono\_charic"\0$. +\beginlines +|def mono_adjust_fit(expr left_adjustment,right_adjustment) =| +| numeric expansion_factor; mono_charwd# = 2letter_fit#| +| + expansion_factor*(charwd+left_adjustment+right_adjustment);| +| forsuffixes $=u,jut,| $\cdots$ |:| +| $ := $.#*expansion_factor*hppp; endfor| +| l := -hround(left_adjustment*expansion_factor*hppp)-letter_fit;| +| interim xoffset := -l;| +| r := l+mono_charwd-shrink_fit;| +| w := r-hround(right_adjustment*expansion_factor*hppp)-letter_fit;| +| charwd := mono_charwd#; charic := mono_charic#;| +| enddef;| +\weakendlines +It took the author ^^{Knuth} umpteen trials to get this routine right. + +The ^"xoffset" calculations in @adjust\_fit@ are enough to shift the +character by the proper amount when it's being ^{shipped out}. We just +have to take care of getting the correct character width in pixels, +and |cmbase| does this by setting +^^"extra\_endchar" +\beginlines +|extra_endchar := extra_endchar&"r:=r+shrink_fit;w:=r-l;";| +\endlines + +No other changes to plain \MF's ^@endchar@ routine are needed; but we do +need to redefine ^|makebox| and ^|maketicks|, in order to show the +adjusted bounding box. It's convenient to change |makebox| so that it also +slants the box, in a slanted font, and so that it draws vertical lines +one unit apart as aids to the designer; several more horizontal lines +are also drawn: +\beginlines +|def makebox(text rule) =| +| for y=0,asc_height,body_height,x_height,bar_height,| +| -desc_depth,-body_depth: rule((l,y)t_,(r,y)t_); endfor % horizontals| +| for x=l,r: rule((x,-body_depth)t_,(x,body_height)t_); endfor % verticals| +| for x=u*(1+floor(l/u)) step u until r-1:| +| rule((x,-body_depth)t_,(x,body_height)t_); endfor % more verticals| +| if charic<>0:| +| rule((r+charic*pt,h.o_),(r+charic*pt,.5h.o_)); fi % italic correction| +| enddef;| +\smallskip +|def maketicks(text rule) =| +| for y=0,h.o_,-d.o_:| +| rule((l,y),(l+10,y)); rule((r-10,y),(r,y)); endfor % horizontals| +| for x=l,r: rule((x,10-d.o_),(x,-d.o_));| +| rule((x,h.o_-10),(x,h.o_)); endfor % verticals| +| if charic<>0:| +| rule((r+charic*pt,h.o_-10),(r+charic*pt,h.o_)); fi % italic correction| +| enddef;| +\weakendlines +(Examples of the new |makebox| routine appear in the illustrations for +period and em-dash earlier in this appendix, and also in Chapter~23.) + +\smallskip +Plain \MF's ^@change\_width@ routine must also be generalized: +\beginlines +|def change_width = if not monospace: % change width by +1 or -1| +| if r+shrink_fit-l = floor(charwd*hppp): w := w+1; r := r+1;| +| else: w := w-1; r := r-1; fi fi enddef;| +\endlines + +The Computer Modern ^@font\_setup@ routine is invoked at the beginning of +each driver file. This is what converts sharped units to pixels; +@font\_setup@ also computes additional quantities that are important to the +font as a whole. It's a long macro, but here are its important features: +\beginlines +|def font_setup =| +| define_pixels(u,jut,| $\cdots$ |);| +| define_whole_pixels(letter_fit,fine,crisp,| $\cdots$ |);| +| define_whole_vertical_pixels(body_height,cap_height,| $\cdots$ |);| +| define_whole_blacker_pixels(hair,stem,curve,| $\cdots$ |);| +| define_whole_vertical_blacker_pixels(vair,slab,| $\cdots$ |);| +| define_corrected_pixels(o,| $\cdots$ |);| +\smallbreak +| if monospace: mono_charwd# := 9u#; define_whole_pixels(mono_charwd);| +| mono_charic# := max(0,body_height#*slant);| +| let adjust_fit = mono_adjust_fit;| +| else: let adjust_fit = normal_adjust_fit; fi| +| lowres_fix(stem,curve) 1.2;| +^^@lowres\_fix@ \smallbreak +| |\<Initialize pen nibs, see below> +\smallbreak +| |^|currenttransform||:=identity slanted slant| +| yscaled aspect_ratio scaled |^|granularity||;| +| shrink_fit := 1+hround(2letter_fit#*hppp)-2letter_fit;| +| if not string mode: if mode <= smoke: shrink_fit := 0; fi fi| +| enddef;| +\endlines +If $"letter\_fit"\0=0$, the `^"shrink\_fit"' is set to~1; otherwise +"shrink\_fit" is 0, 1, or~2, depending on how "letter\_fit" has +rounded to an integer. This amount is essentially subtracted from~^"w" +before each character in the font has been drawn. Experience shows that +this trick greatly improves the readability of fonts at ^{medium} +and ^{low resolutions}. + +Many of the Computer Modern characters are drawn with ^@filldraw@, which +is a mixture of outline-filling and fixed-pen drawing. Several macros +are included in |cmbase| to facilitate filldrawing, especially +`^"pos"' and `^"stroke"': +\beginlines +|vardef pos@#(expr b,d) =| +| (x@#r-x@#l,y@#r-y@#l)=(b-currentbreadth,0) rotated d;| +| x@#=.5(x@#l+x@#r); y@#=.5(y@#l+y@#r) enddef;| +\smallbreak +|vardef stroke text t =| +| forsuffixes e=l,r: path_.e:=t; endfor| +| path_.l -- reverse path_.r -- cycle enddef;| +\endlines +Thus "pos" is like ^"penpos", except that it subtracts ^"currentbreadth" +from the overall breadth. \ (Cf.~the program for left parentheses in +Chapter~12.) \ The "stroke" routine is a simplified alternative to +@penstroke@, such that @penstroke@ is equivalent to `@fill@~"stroke"' +if the specified path isn't a cycle. + +The value of "currentbreadth" is maintained by redefining plain \MF's +`^"numeric\_pickup\_"' macro so that it includes the new line +\beginlines +| if known breadth_[q]: currentbreadth:=breadth_[q]; fi| +\endlines +The ^@clear\_pen\_memory@ macro is redefined so that its second line now says +\beginlines +| numeric pen_lft_[],pen_rt_[],pen_top_[],pen_bot_[],breadth_[];| +\endlines +relevant entries of the "breadth\_" array will be defined by @font\_setup@, +as we'll see soon. + +The example programs for period and em-dash say `@pickup@ "fine.nib"' and +`@pickup@ "crisp.nib"'. These nibs are initialized by @font\_setup@ in +the following way: +\beginlines +| clear_pen_memory;| +| forsuffixes $ = fine,crisp,| $\cdots$ |:| +| $.breadth := $;| +| pickup if $=0: nullpen else: pencircle scaled $; $ := $-eps fi;| +| $.nib := |^|savepen||; breadth_[$.nib] := $;| +| forsuffixes $$ = lft,rt,top,bot: shiftdef($.$$,$$ 0); endfor endfor| +\weakendlines +If, for example, we have $"fine"=4$, this code sets $"fine.breadth":=4$, +$"fine.nib":=1$, $"fine":=4-"eps"$, and $"breadth\_"[1]:=4-"eps"$. +\ (A small amount~^"eps" has been subtracted so that "pos" will +usually find $b-"currentbreadth">0$.) \ Furthermore, four subroutines +^"fine.lft", "fine.rt", "fine.top", and "fine.bot" are defined, so +that it's easy to refer to the edges of "fine.nib" when it has not been +picked up. These four subroutines are created by a slightly +tricky ^|shiftdef| macro: +\beginlines +|def shiftdef(suffix $)(expr delta) =| +| vardef $ primary x = x+delta enddef enddef;| +\endlines + +OK, we've just about covered everything in |cmbase| that handles the +extra administrative complexity inherent in a large-scale design. +The rest of the base file simply contains subroutines like +^"serif" and ^"dot", for recurring features of the characters themselves. +Such subroutines needn't be shown here. + +To make a binary file called ^|cm.base|, there's a trivial file `|cm.mf|': +\beginlines +|% This file creates `cm.base', assuming that plain.base is preloaded| +|input cmbase; |^|dump.| +\endlines + +\medbreak +Besides parameter files, driver files, program files, and the base file, +the Computer Modern routines also include a number of {\sl^{utility files}\/} +that provide a convenient environment for designing new characters and +improving old ones. We'll conclude this appendix by studying the contents +of those utility files. + +Let's suppose, for example, that test proofs have revealed problems +with the characters `k' and `S', so we want to fix them. Instead of +working with the font as a whole, we can copy the programs for those two +characters (and only those two) into a temporary file called `^|test.mf|'. +Then we can run \MF\ on the file `^|rtest.mf|', which says the following: +\beginlines +|% try all characters on `test.mf' using the parameters of cmr10| +|if unknown cmbase: input cmbase fi| +|mode_setup;| +\smallskip +|def generate suffix t = enddef;| +|input cmr10; font_setup;| +\smallbreak +|let echar = endchar;| +|def endchar = echar; stop "done with char "&decimal charcode&". " enddef;| +|let iff = always_iff;| +\smallskip +|input test; bye| +\endlines +This will produce proofs of `k' and `S', using the |cmr10| parameters. +Notice the simple trick by which |rtest| is able to stay in charge +after inputting |cmr10|, without letting the |roman| driver come into +action: `|generate|' is redefined so that it becomes innocuous. +Furthermore |rtest| changes ^|endchar| so that \MF\ will ^{stop} and +display each character before moving~on to the next. The `^|iff|' +convention is changed to `|always_iff|', so that every test character will +^^@always\_iff@ be tested even if the boolean expression is undefined; +this makes it easier to copy from program files +into the test file and back again, since the |iff| indications do not +have to be touched. + +If you invoke \MF\ with `|\mode=lowres;| |input| |rtest|', you'll generate +a low-resolution font called |rtest| with the parameters of |cmr10|, +but containing only the characters in the test file. If you leave out +the mode, you get proof mode as usual. + +There are similar pseudo-drivers |ttest.mf| (for |cmtt10| instead of |cmr10|), +|btest.mf| (for |cmbx10|), etc.; these make it possible to try the +test characters with many different parameter settings. There's also +|ztest.mf|, which inputs parameters from a temporary file `|z.mf|' that +contains special parameters of interest at the moment. \ (If file +|z.mf| does not exist, you'll get a chance to specify another +parameter file, online.) \looseness=-1 + +A more elaborate ^{pseudo-driver file} called `|6test.mf|' allows you +to test up to six parameter settings simultaneously, and to see the +results all at once on your screen, as illustrated in Chapter~23. +Here is the program that does the necessary magic: +\beginlines +|% try all characters on `test.mf' using six different sets of parameters| +|if unknown cmbase: input cmbase fi| +|mag=.5; % the user can override this equation| +|mode_setup; let mode_setup=\;| +\smallskip +|boolean running;| +|def abort = hide(scrollmode; running := false) enddef;| +|def pause = stop "done with char "&decimal charcode&". " enddef;| +|let iff = always_iff;| +|def ligtable text t=enddef;| +|def charlist text t=enddef;| +|def extensible text t=enddef;| +\smallbreak +|string currenttitle;| +|let semi = ;; let echar = endchar; let endchar = enddef;| +|def cmchar expr s = currenttitle := s;| +| let ; = testchar semi quote def chartext = enddef;| +|def testchar = semi let ; = semi;| +| running := true; errorstopmode;| +| for k=1 upto 6:| +| if running: if known params[k]: scantokens params[k]; font_setup;| +| currentwindow:=k;| +| currenttitle & ", " & fontname[k];| +| chartext echar; fi fi endfor| +| pause; enddef;| +\smallbreak +|string params[],fontname[];| +|params[1] = "roman_params"; fontname[1] = "cmr10";| +|params[2] = "sans_params"; fontname[2] = "cmssbx10";| +|params[3] = "ital_params"; fontname[3] = "cmti10";| +|params[4] = "tt_params"; fontname[4] = "cmtt10";| +|params[5] = "bold_params"; fontname[5] = "cmb10";| +|params[6] = "quote_params"; fontname[6] = "cmssqi8";| +\smallbreak +|w_rows = floor 1/2 screen_rows; w_cols = floor 1/3 screen_cols;| +|def open(expr k,i,j)=| +| openwindow k from ((i-1)*w_rows,(j-1)*w_cols) to (i*w_rows,j*w_cols)| +| at (-10,140) enddef;| +|def openit =| +| open(1,1,1); open(2,1,2); open(3,1,3);| +| open(4,2,1); open(5,2,2); open(6,2,3); enddef;| +\smallbreak +|begingroup delimiters begintext generate;| +| def makedef(expr s)(text t) =| +| expandafter def scantokens s = t enddef; flushtext enddef;| +| def flushtext suffix t = enddef;| +| for k=1 upto 6: if known params[k]:| +| makedef(params[k])| +| expandafter expandafter expandafter begintext| +| scantokens ("input "&fontname[k]); fi endfor| +|endgroup;| +\smallskip +|input test; bye| +\endlines +^^@errorstopmode@ ^^@scrollmode@ ^^@quote@ ^^@openwindow@ ^^@openit@ +^^"currentwindow" ^^@expandafter@ ^^@scantokens@ +Parameters are moved from parameter files into macros, using a trick +discussed near the beginning of Appendix~D\null. Then ^@cmchar@ is redefined +so that the entire text of each character-to-be-tested will be embedded +in another macro called "chartext". Each instance of "chartext" is +repeatedly applied to each of the six font setups. + +An error that occurs with the first or second set of parameters may be +so bad that you won't want to see what happens with the third, fourth, +fifth, and sixth sets. For example, when |test.mf| contains characters +that are being newly designed, some equations might have been omitted +or mistyped, so the results will be ludicrous. In this case you can +^{interrupt} the program and type `|I|~^|abort|'. The |6test| routine +has an |abort| macro that will stop at the end of the current font setup +and move directly to the next character, without trying any of the +remaining parameter combinations. + +It's possible to include material in |test.mf| that isn't part of +a character program. For example, you might want to redefine a subroutine +in the base file. Only the character programs themselves (i.e., the +sequences of tokens between `@cmchar@' and `@endchar@;') are subject to +six-fold repetition. + +Some large characters may not appear in full, because there might not be +room for them on the screen at the stated magnification. You can make +everything smaller by running \MF\ with, say, `|\mag=1/3; input 6test|'. +The computer will stop with an error message, saying that the equation +`|mag=.5|' is ^{inconsistent}; but you can safely proceed, because you +will have the magnification you want. + +\endchapter + +An ensampull yn doyng ys more commendabull +\indent{\cmman\char'15}en ys techyng o{\cmman\char'15}er prechyng. +\author JOHN ^{MIRK}, {\sl The Festyuall\/} (c.\thinspace1400) + % from MS page 123b; p216 in Erbe's transcription + +\bigskip + +Old people love to give good advice, +% Les vieillards aiment \`a donner de bons pr\'eceptes, +to console themselves for no longer being able to give bad examples. +% pour se consoler de n'\^etre plus en \'etat de donner de mauvais exemples. +\author ^{LA ROCHEFOUCAULD}, {\sl Maximes\/} (1665) + +\eject + \beginchapter Appendix F. Font Metric\\Information + +The \TeX\ typesetting system assumes that some ``intelligence'' has been +built into the fonts it uses. In other words, information stored with +^^{TeX} \TeX's fonts will have important effects on \TeX's behavior. This +has two consequences: (a)~Typesetting is quite flexible, since few +conventions are frozen into \TeX\ itself. (b)~Font designers must work +a little harder, since they have to tell \TeX\ what to do. The purpose +of this appendix is to explain how you, as a font designer, can cope +with~(b) in order to achieve spectacular successes with~(a). + +The information used by \TeX\ is embedded in compact binary files called +\TeX\ Font Metric (^|tfm|) files. Although the `|t|' in `|tfm|' stands +for \TeX, this is an artifact of history, because other formatting systems +can work with |tfm| files too. The files should have been called just `|fm|', +but it's too late now. + +\MF\ is able to produce two different kinds of binary output files. +One, a `|gf|' file, contains digitized character shapes and some additional +information needed by programs that drive printing devices; such files +are discussed in Appendix~G\null. The other type of output is a |tfm| file, +which contains font information used by formatting routines like \TeX; +such files are our present concern. You get a |tfm| file if and only +if \MF's internal quantity `^"fontmaking"' is positive at the end +of your job. \ (Plain \MF's @mode\_setup@ routine usually sets +"fontmaking" to an appropriate value automatically.) + +\medskip\ninepoint +The |tfm| file contains some information about each character, some +information about combinations of characters, and some information +about the font as a whole. We shall consider these three kinds +of information in turn. All of the font metric data that refers to +physical dimensions should be expressed in device-independent, +``^{sharp}'' units; when a particular font is produced with different +modes or magnifications, all its |tfm| files should be identical. + +A formatting program like \TeX\ needs to know the size of each character's +``^{bounding} ^{box}.'' For example, when \TeX\ typesets a word like +`box', it places the first letter `b' into a little box in such a way that +the \MF\ pixel whose lower left corner is at $(0,0)$ will appear +on the baseline of the current line being typeset, at the left edge +of the box. \ (We assume for simplicity that ^"xoffset" and ^"yoffset" +were zero when `b' was shipped out.) \ The second letter,~`o', is placed +in a second little box adjacent to the first one, so we obviously must tell +\TeX\ how wide to make the `b'. + +In fact, \TeX\ also wants to know the height and depth of each letter. +This affects the placing of ^{accents}, if you wish to typeset +`\d{\~b}\kern.28pt\d{\~o}\kern-.28pt\d{\~x}\d{\~y}', and it also +avoids overlap when adjacent lines contain boxes that go unusually +far above or below the baselines. + +A total of four dimensions is given for each character, in sharp +units (i.e., in units of printer's points): + +\smallskip +\item\bull ^"charwd", the width of the bounding box. +\item\bull ^"charht", the height (above the baseline) of the bounding box. +\item\bull ^"chardp", the depth (below the baseline) of the bounding box. +This is a {\sl positive\/} number if the character descends below the +baseline, even though the corresponding $y$ values are negative. +\item\bull ^"charic", the character's ``^{italic correction}.'' \TeX\ +adds this amount to the width of the box (at the right-hand side) +in two cases: (a)~When the user specifies an italic correction explicitly, +by typing |\/| immediately after the character. (b)~When an ^{isolated} +character is used in math mode, unless it has a subscript but no +superscript. For example, the italic correction is applied to `$P$' in +the formulas `$P(x)$' and `$P^2$', but not in the formula `$P_n$'; +it is applied to position the superscript but not the subscript +in `$P_n^2$'. +\smallskip\noindent +In plain \MF\ programs, you specify "charwd", "charht", and "chardp" +in a ^@beginchar@ command, and you specify "charic" (if it's positive) +in an ^@italcorr@ command. But @beginchar@ and @italcorr@ are macros, +not primitives of \MF\!\null. What really happens is that \MF\ records the +value of its internal quantities "charwd", "charht", "chardp", and "charic" +at the time of a ^@shipout@ command. These values (and all other +dimensions to be mentioned below) must be less than $2048"pt"\0$ in +absolute value. + +A font contains at most 256 character codes; the ^{charexists} operator +can be used to tell which codes have already appeared. If two or more +characters are shipped out with the same code number (possibly with +different ^"charext" values), the "charwd", +"charht", "chardp", and "charic" of the final one are assumed to +apply to them all. + +At most 15 different nonzero heights, 15 different nonzero depths, +and 63 different nonzero italic corrections may appear in a single +font. If these limits are exceeded, \MF\ will change one or more +values, by as little as possible, until the restriction holds. +A warning message is issued if such changes are necessary; for example, +^^|some char values| +`|(some| |charht| |values| |had| |to| |be| |adjusted| |by| |as| |much| +|as| |0.12pt)|' means that~you had too many different nonzero heights, but +\MF\ found a way to reduce the number to at most~15 by changing some of +them; none of them had to be changed by more than 0.12 points. No warning +is actually given unless the maximum amount of perturbation exceeds +${1\over16}\pt$. + +\medbreak +The next kind of information that \TeX\ wants is concerned with +pairs of adjacent characters that are typeset from the same font. +For example, \TeX\ moves the~`x' slightly closer to the~`o' in the +word `box', and it moves the~`o' slightly away from the~`b', because +of information stored in the |tfm| file for the font you're now reading. +This space adjustment is called {\sl^{kerning}}. Otherwise (if the +three characters had simply been placed next to each other according +to their "charwd" values) the word would have been `b{}o{}x', which +looks slightly worse. Similarly, there's a difference between +`difference' and `dif{\null}ference', because the |tfm| file tells \TeX\ +to substitute the ligature `ff' when there are two f's in a row. + +Ligature information and kerning information is specified in short +``^{ligtable programs}'' of a particularly simple form. Here's an example +that illustrates most of the features (although it is not a serious +example of typographic practice): +\beginlines +^|ligtable|| "f": "f" =: oct"013", "i" |\||=: oct"020", skipto 1;| +|ligtable "o": "b": "p": "e" kern .5u#, "o" kern .5u#, "x" kern-.5u#,| +| 1:: "!" kern u#;| +\endlines +This sequence of instructions can be paraphrased as follows: +\smallskip +\hangindent 3pc +Dear \TeX, when you're typesetting an~`f' with this font, and when the +following character also belongs to this font, look at it closely because +you might need to do something special: If that following character is +another~`f', replace the two f's by character code |oct"013"| +[namely `\char'13'\kern.5pt]; +if it's an `i', retain the `f' but replace the `i' by character code +|oct"020"| [a dotless `\char'20'\kern.5pt]; +otherwise skip down to label `|1::|' for further instructions. +When you're typesetting an `o' or~`b' or~`p', if the next input to \TeX\ is +`e' or~`o', add a half unit +of space between the letters; if it's an `x', subtract a half unit; if it's an +exclamation point, add a full unit. The last instruction applies also +to exclamation points following~`f' (because of the label `|1::|'). +\smallskip\noindent +When a character code appears in front of a colon, the colon ``labels'' +the starting place for that character's ligature and kerning program, +which continues to the end of the ligtable statement. A double colon denotes +a ``local label''; a |skipto| instruction advances to the next matching local +label, which must appear before 128 ligtable steps intervene. The special +label \|\||:| can be used to initiate ligtable instructions for an invisible +``left boundary character'' that is implicitly present just before every +word; an invisible ``right boundary character'' equal to ^"boundarychar" is +also implicitly present just after every word, if "boundarychar" lies between +0 and~255. + +The general syntax for ligtable programs is pretty easy to guess from +these examples, but we ought to exhibit it for completeness: +\beginsyntax \chardef\\=`\| +<ligtable command>\is[ligtable]<ligtable program><optional skip> +<ligtable program>\is<ligtable step>\alt<ligtable program>[,]<ligtable step> +<optional skip>\is[,] [skipto]<code>\alt<empty> +<ligtable step>\is<code><ligature op><code> + \alt<code>[kern]<numeric expression> + \alt<label><ligtable step> +<ligature op>\is[=:]\alt[\\=:]\alt[\\=:>]\alt[=:\\]\alt[=:\\>]% + \alt[\\=:\\]\alt[\\=:\\>]\alt[\\=:\\>>] +<label>\is<code>[:]\alt<code>[::]\alt[\\\\:] +<code>\is<numeric expression>\alt<string expression> +\endsyntax +A \<code> should have a numeric value between 0 and 255, inclusive, +after having been rounded to the nearest integer; or it should be a +string of length~1, in which case it denotes the corresponding +^{ASCII} code (Appendix~C\null). For example, |"A"| and |64.61| both +specify the code value 65. Vertical bars to the left or right of `|=:|' +tell \TeX\ to retain the original left and/or right character that invoked a +ligature. Additional `|>|' signs tell \TeX\ to advance its focus of attention +instead of doing any further ligtable operations at the current +character position. + +{\sl Caution:\/} Novices often go overboard on kerning. Things usually +work out best if you kern by at most half of what looks right to you +at first, since kerning should not be noticeable by its presence +(only by its absence). Kerning that looks right in a logo or in a +headline display often interrupts the rhythm of reading when it appears +in ordinary textual material. + +You can improve \TeX's efficiency by ordering the steps of a ligtable +program so that the most frequent alternatives come first. +\TeX\ will stop reading the program when it finds the first ``hit.'' + +\medbreak +Several characters of a font can be linked together in a series +by means of a ^@charlist@ command. For example, +\begintt +charlist oct"000": oct"020": oct"022": oct"040": oct"060" +\endtt +is used in the font ^|cmex10| to specify the left parentheses that +\TeX\ uses in displayed math formulas, in increasing order of size. +\TeX\ follows charlists to make variable-size delimiters and +variable-width ^{accents}, as well as to link text-size operators +like `$\sum$' to the display-size `$\displaystyle\sum$'. + +\TeX\ builds up large delimiters by using ``^{extensible}'' characters, +which are specified by giving top, middle, bottom, and repeatable +characters in an ^@extensible@ command. For example, the extensible +left ^{parentheses} in |cmex10| are defined by +\begintt +extensible oct"060": oct"060", 0, oct"100", oct"102"; +\endtt +this says that character code |oct"060"| specifies an extensible +delimiter constructed from itself as the top piece, from character number +|oct"100"| as the bottom piece, and from character number |oct"102"| as +the piece that should be repeated as often as necessary to reach +a desired size. In this particular example there is no middle +piece, but characters like curly braces have a middle piece as well. +A zero value in the top, middle, or bottom position means that +no character should be used in that part of the construction; +but a zero value in the final position means that character number zero +is the repeater. The width of an extensible character is taken to +be the width of the repeater. \looseness=-1 + +The first eight different sizes of parentheses available to \TeX\ in +|cmex10|, when the user asks for `|\left(|', look like this: +\begindisplay +$\bigl(\quad\Bigl(\quad\biggl(\quad\Biggl(\quad +\mathopen{\hbox{$\left(\vbox to20.5pt{}\right.\nulldelimiterspace=0pt$}}\quad +\mathopen{\hbox{$\left(\vbox to23.5pt{}\right.\nulldelimiterspace=0pt$}}\quad +\mathopen{\hbox{$\left(\vbox to26.5pt{}\right.\nulldelimiterspace=0pt$}}\quad +\mathopen{\hbox{$\left(\vbox to29.5pt{}\right.\nulldelimiterspace=0pt$}}$ +\enddisplay +According to what we know from the examples of @charlist@ and @extensible@ +above, the first four of these are the characters in positions +|oct"000"|, |oct"020"|, |oct"022"|, and |oct"040"|. The other four have +character |oct"060"| on top; character |oct"100"| is at the bottom; +and there are respectively zero, one, two, and three occurrences +of character |oct"102"| in the middle. + +Here is the formal syntax: +\beginsyntax +<charlist command>\is[charlist]<labeled code> +<labeled code>\is<code> + \alt<label><labeled code> +<extensible command>\is[extensible]<label><four codes> +<four codes>\is<code>[,]<code>[,]<code>[,]<code> +\endsyntax +Notice that a \<label> can appear in a ligtable, charlist, or extensible +command. These appearances are mutually exclusive: No code may be used +more than once as a label. Thus, for example, a character with a +ligature/kerning program cannot also be extensible, nor can it be +in a charlist (except as the final item). + +\medbreak +The last type of information that appears in a |tfm| file applies to +the font as a whole. Two kinds of data are involved, bytes and +numerics; and they are specified in ``headerbyte'' and ``fontdimen'' +commands, according to the following general syntax: +\beginsyntax +<headerbyte command>\is[headerbyte]<numeric expression>[:]<byte list> +<fontdimen command>\is[fontdimen]<numeric expression>[:]<numeric list> +<byte list>\is<code> + \alt<byte list>[,]<code> +<numeric list>\is<numeric expression> + \alt<numeric list>[,]<numeric expression> +\endsyntax +We shall defer discussion of header bytes until later, because they +are usually unnecessary. But @fontdimen@ commands are important. +Numeric parameters of a font can be specified by saying, e.g., +\begintt +fontdimen 3: 2.5, 6.5, 0, 4x +\endtt +which means that parameters 3--6 are to be 2.5, 6.5, 0, and $4x$, +respectively. These are the parameters that \TeX\ calls |\fontdimen3| +thru |\fontdimen6|. \ (Parameter numbering is old-fashioned: +There is no |\fontdimen0|.) + +The first seven fontdimen parameters have special significance, so plain +\MF\ has seven macros to specify them symbolically, one at a time: + +\smallskip +\item\bull^@font\_slant@ (|\fontdimen1|) is the amount of ^{slant} +per point; \TeX\ uses this information when raising or lowering an +accent character. +\item\bull^@font\_normal\_space@ (|\fontdimen2|) is the interword spacing. +If the value is zero, all characters of this +font will be considered to be ``^{isolated}'' in math mode, so the +^{italic correction} will be added more often than otherwise. +\item\bull^@font\_normal\_stretch@ (|\fontdimen3|) is the ^{stretchability} +of interword spacing, as explained in {\sl The \TeX book}. +\item\bull^@font\_normal\_shrink@ (|\fontdimen4|) is the ^{shrinkability} +of interword spacing, as explained in {\sl The \TeX book}. +\item\bull^@font\_x\_height@ (|\fontdimen5|) is the height of characters +for which accents are correctly positioned. An accent over a character +will be raised by the difference between the character's "charht" +and this value. The ^{x-height} is also the unit of height that +\TeX\ calls one `|ex|'. +\item\bull^@font\_quad@ (|\fontdimen6|) is the unit of width that +\TeX\ calls one `|em|'. +\item\bull^@font\_extra\_space@ (|\fontdimen7|) is the additional amount +added to the normal interword space between sentences, depending +on the ``spacefactor'' as defined in {\sl The \TeX book}. +\smallskip\noindent +Parameters are zero unless otherwise specified. + +Math symbol fonts for \TeX\ are required to have at least 22 fontdimen +parameters, instead of the usual seven; math extension fonts need at least~13. +Appendix~G of {\sl The \TeX book\/} explains the precise significance +of these additional parameters, which control such things as the +placement of superscripts and subscripts. + +\medbreak +The {\sl^{design size}\/} of a font is not one of the fontdimen +parameters; it's an internal quantity of \MF\ that is actually output +among the header bytes as explained below. When a \TeX\ user asks +for a font `|at|' a certain size, the font is scaled by the ratio +between the ``^{at size}'' and the design size. For example, +|cmr10| has a design size of $10\pt$; if a \TeX\ user requests +`|cmr10|~|at|~|15pt|', the result is the same as `|cmr10|~|scaled|~|1500|' +(or, in plain \MF\ terms, |cmr10| with |mag=1.5|). + +What does the design size really mean? It's an imprecise notion, +because there need be no connection between the design size and any specific +measurement in a font. Typographers have always been vague when +they speak about ``10~point'' fonts, because some fonts look larger +than others even though the horizontal and vertical dimensions are the same. +It's something like dress sizes or shoe sizes. + +In general, the design size is a statement about the approximate size +of the type. Type with a larger design size generally looks bigger +than type with a smaller design size. Two fonts with the same design +size are supposed to work well together; for example, |cmr9| and +|cmtt9| both have $9\pt$ design size, although the uppercase letters of +|cmtt9| are quite a bit smaller (`|A|' versus `A'). + +The "designsize" must be at least $1"pt"\0$. And, as with all |tfm| +dimensions, it must be less than $2048"pt"\0$. Any other value is +changed to $128"pt"\0$. + +\MF\ looks at the value of ^"designsize" only when the job ends, so you +needn't set it before characters are shipped out. At the end of a job, +when the |tfm| file is being written, \MF\ checks to make sure that every +dimension of the font is less than 16 times the design size in absolute +value, because this limitation is imposed by the |tfm| file format. Thus, +for example, if the design size is $10\pt$, you cannot have a character +whose width or height is $160\pt$ or more. If one or more dimensions prove +to be too big, \MF\ will tell you how many of them had to be changed. + +\medbreak +The ^@headerbyte@ command is similar to @fontdimen@, but it gives +8-bit \<code> data instead of numeric information. For example, +\begintt +headerbyte 33: 0, 214, 0, "c" +\endtt +says that bytes 33--36 of the |tfm| file header will be 0, 214, +0, and~99. The first four header bytes (numbers 1--4) are automatically +set to a ^{check sum}, unless you have specified other values for +at least one of those bytes. \ (This check sum will match a similar +value in the |gf|~file, so that other typesetting software can check +the consistency of the different files they use.) \ Similarly, +the next four header bytes (numbers 5--8) are set automatically to +the design size times $2^{20}$, unless you have specified something +else. \looseness=-1 + +\TeX\ looks only at the first eight header bytes, so you needn't use the +header\-byte command if you are simply producing a font for +standard \TeX. But other software that reads |tfm| files may have +a need for more header information. For example, the original +|tfm| format (developed by Lyle ^{Ramshaw} at ^{Xerox} Palo Alto +Research Center) included ^@font\_coding\_scheme@ information +in bytes 9--48 of the header, and ^@font\_identifier@ information in +bytes 49--68. The design size of certain fonts was also packed into +byte~72. Each font in the ``Xerox world'' is uniquely identified by +its font identifier and its design size, rather than by its font file name. + +The ``font coding scheme'' is merely a comment that can be used +to help understand large collections of fonts; it's usually a nice thing +to know. Some of the coding scheme names in common use are +\begindisplay +|TeX text|&|TeX math italic|\cr +|TeX typewriter text|&|TeX math symbols|\cr +|XEROX text|&|TeX math extension|\cr +|ASCII|&|TeX extended ASCII|\cr +|PI|&|GRAPHIC|\cr +\enddisplay +The coding-scheme string should not include parentheses. + +Here are macros that can be used, if desired, to convert plain +\MF's @font\_identifier@ and @font\_coding\_scheme@ into the format +^^{substring} ^^{BCPL strings} +required by Ramshaw's original |tfm| files: +\beginlines +|def BCPL_string(expr s,n) = % string s becomes an n-byte BCPL string| +| for l:=if length(s)>=n: n-1 else: length(s) fi: l| +| for k:=1 upto l: , substring (k-1,k) of s endfor| +| for k:=l+2 upto n: , 0 endfor endfor enddef;| +\smallskip +^|inner|| end;| +|def bye = if fontmaking>0:| +| headerbyte 9: BCPL_string(font_coding_scheme_,40);| +| special "codingscheme " & font_coding_scheme_;| +| headerbyte 49: BCPL_string(font_identifier_,20);| +| special "identifier " & font_identifier_;| +| headerbyte 72: max(0, 254 - round 2designsize); fi| +| end enddef;| +^|outer|| bye,end;| +\endlines +These macros could be included among the ^|local.mf| extensions to +|plain.mf| at particular installations. When a user says `^@bye@' instead +of `^@end@', the additional headerbyte documentation will then be +automatically inserted into the |tfm| file. + +\medbreak +Let us now conclude this appendix by summarizing what we've learned. +A \MF\ programmer can provide various types of information about how +to typeset with a font, by using font metric commands. Simple versions +of these commands, sufficient for simple fonts, are standard operations +in plain \MF; examples have appeared in Chapter~11 +and the beginning of Appendix~E\null. The general cases are handled by +five types of font metric commands: +\beginsyntax +<font metric command>\is<ligtable command> + \alt<charlist command> + \alt<extensible command> + \alt<fontdimen command> + \alt<headerbyte command> +\endsyntax +This completes the syntax of \MF\ that was left slightly unfinished +in Chapter~26. + +\endchapter + +Such things induced me to untangle the chaos +% Voil\`a ce qui m'a engag\'e \`a d\'ebrouiller ce chaos, +by introducing order where it had never been before: +% en mettant dans cette partie un ordre qui n'y avoit jamais r\'egn\'e : +I think I may say I have had the good fortune to succeed +% je crois avoir eu le bonheur d'y r\'eussir +with an exactness \& a precision leaving nothing more to be desired, +% avec une justesse \& une pr\'ecision qui ne laissent rien \`a desirer, +by the invention of\/ {\rm Typographic points}. +% par l'invention des \it Points typographiques. +\author PIERRE ^{FOURNIER}, {\sl Manuel Typographique\/} (1764) % p129 + +\bigskip + +One should absorb the color of life, +but one should never remember its details. +Details are always vulgar. +\author OSCAR ^{WILDE}, {\sl The Picture of Dorian Gray\/} (1890) + % middle of ch6 in original edition [Lippincott's vol 46]; ch8 subsequently + +\eject + \beginchapter Appendix G. Generic\\Font\\Files + +\MF's main output goes into a ^|gf| or ``Generic Font'' file, so-called +because it can easily be translated into any other digital font format, +although it does not match the specifications of any ``name brand'' +manufacturer. The purpose of this appendix is to explain exactly what +kinds of information go into the |gf| file, and under what circumstances +\MF\ puts things there. + +\ninepoint\medskip +A |gf| file is a compact binary representation of a digitized font, +containing all the information needed by ``^{device driver}'' +software that produces printed documents from \TeX's ^|dvi| files. The +exact internal representation scheme of |gf| files doesn't concern us +here, but we ought to know what type of data is encoded. + +\smallskip +The first thing in a |gf| file is a string that explains its origin. +\MF\ writes strings of the form +\begintt +METAFONT output 1986.06.24:1635 +\endtt +based on the values of the internal quantities ^"day", ^"month", +^"year", and ^"time" when the |gf| file was started. \ (In this case +$"day"=24$, $"month"=6$, $"year"=1986$, % my 25th wedding anniversary +and $"time"=16\times60+35=995$.) + +After the opening string, the |gf| file contains a sequence of +``special'' commands interspersed with shipped-out character images. +^{Special commands} are intended to provide a loophole for future +extensions to \MF's set of primitives, so that \MF\ itself will not +have to change. Some specials are predefined, but others will +undoubtedly be created in years to come. \ (\TeX\ has an analogous +|\special| command, which puts an arbitrary string into a |dvi| file.) + +A special command gets into the |gf| file when you say `^@special@ +\<string>' or `^@numspecial@ \<numeric>' at a time when +^"proofing"$\null\ge0$. A @special@ string should come before +@numspecial@, and it +should either be a keyword all by itself or it should consist of a keyword +followed by a space followed by additional information. Keywords that +specify operations requiring numeric arguments should be followed by +numbers produced by @numspecial@. For example, the `^@proofrule@' macro +in Appendix~B expands into a sequence of five special commands, +\begindisplay +@special@ |"rule"|;\cr +@numspecial@ $x_1$; \ @numspecial@ $y_1$;\cr +@numspecial@ $x_2$; \ @numspecial@ $y_2$;\cr +\enddisplay +this represents a rule on the proofsheet that runs from point $(x_1,y_1)$ +to point $(x_2,y_2)$. If you say `|grayfont gray5|', the ^@grayfont@ +macro in Appendix~B expands to `@special@ |"grayfont gray5"|'. +Software that reads |gf| files will examine all of the special strings, +until coming to a space or to the end of the string. If the resulting +keyword isn't known to the program, the special string will be ignored, +together with all numspecials that immediately follow. But when the +keyword is known, the program will be able to determine the corresponding +arguments. For example, the |GFtoDVI| program described in Appendix~H +knows about the plain \MF\ keywords `|rule|' and `|grayfont|'. + +\MF\ might also create @special@ commands on its own initiative, but only +when "proofing" is strictly greater than zero. There are +two cases: (1)~When a ^\<title> statement occurs, +the special string `|"title "|\thinspace\&\thinspace\<title>' +is output. \ (This is how the phrase `|The letter O|' got onto your +proofsheets in the experiments of Chapter~5.) \ (2)~Just before a +character image is shipped out, \MF\ implicitly executes the following +sequence of instructions: +\begindisplay +@if@ round $"xoffset"\ne0$: \ @special@ |"xoffset"|; \ + @numspecial@ round ^"xoffset"; @fi@\cr +@if@ round $"yoffset"\ne0$: \ @special@ |"yoffset"|; \ + @numspecial@ round ^"yoffset"; @fi@\cr +\enddisplay + +A ^@shipout@ command sends a digitized picture to the |gf| +file, if $"proofing"\ge0$, but nothing is output if $"proofing"<0$. +Furthermore the current values of ^"charwd", ^"charht", ^"chardp", ^"charic", +^"chardx", and ^"chardy" are stored away for the current ^"charcode"; +these values are stored in all cases, regardless of the value of "proofing". +The current character code is henceforth said to ``exist.'' ^^@charexists@ + +When a ^{picture} is shipped out, its pixels of positive value are +considered to be ``black,'' and all other pixels are considered to be +``white.'' The pattern of blacks and whites is encoded in such a way +that doubling the resolution approximately doubles the length of the +|gf| output, in most cases. + +\MF\ reports its progress by typing `|[|$c$|]|' on the terminal +when character code~$c$ is being shipped out. \ (The `^|[|' is typed +before output conversion begins, and the `^|]|' is typed after; hence you +can see how much time output takes.) \ If "charext" is nonzero, after +being rounded to an integer, the typed message is `|[|$c.x$|]|' instead; +for example, `[65.3]' refers to character~65 with extension code~3. + +\TeX\ allows only 256 characters per font, but extensions of \TeX\ +intended for ^{oriental} languages will presumably use the "charext" +feature. All characters with the same code share the same width, +height, and depth, but they can correspond to distinct graphics if they have +different extension codes. + +\medbreak +A @special@ command generally refers to the picture that follows it, +rather than the picture that precedes~it. Special commands before the +first digitized picture might, however, give instructions about +the font as a whole. Special commands that follow the final picture +invariably refer to the font as a whole. \ (For example, the +`^@bye@' macro at the end of Appendix~F creates two special +strings that will appear after the final character of a font.) + +\medbreak +No |gf| file will be written unless a character is shipped out or a +special command is performed at a time when $"proofing"\ge0$, or unless a +title statement is encountered at a time when $"proofing">0$. When one of +these things first happens, the |gf| file receives its name. If no +^@input@ commands have yet occurred, \MF\ will set the job name to +`^|mfput|'; otherwise the job name will already have been determined. The +full name of the |gf| file will be +`\<jobname>|.|\<resolution>\thinspace|gf|', where the \<resolution> is +based on the current value of~^"hppp". \ (If $"hppp"\le0$, the resolution +will be omitted; otherwise it will be converted to an equivalent number of +pixels per inch, in the horizontal dimension.) \ Subsequent @input@ +operations or changes to~"hppp" will not change the ^^{file name} +name of the |gf| file. + +\medbreak +The end of a |gf| file contains a bunch of numeric data needed for +typesetting. First come the ^{design size} and the ^{check sum}; +these match precisely the data in the |tfm| file, unless the header +bytes of the |tfm| have explicitly been set to something else. +Then come the values of "hppp" and "vppp". \ (These are the values +at the end of the job, so "hppp" might not agree with the \<resolution> +value in the |gf| file name.) + +Finally, the |gf| file gets the ^"charwd", ^"chardx", and ^"chardy" +of each existing character code. The values of "chardx" and "chardy" +represent desired ``escapements'' when characters are typeset on a +particular device (cf.\ Chapter~12). The "charwd" values are identical to +the widths in the |tfm| file. + +\medbreak +The check sum is based entirely on the "charwd" data; two fonts +with the same character widths will have the same check sum, but +two fonts with different character widths will almost never have +the same check sum. + +The purpose of check sums can be understood by considering the following +scenario: A font named |cmr10| might be generated by \MF\ at any time, +producing a |tfm| file called |cmr10.tfm| and a |gf| file called, +say, |cmr10.300gf|. A document named |doc|, which uses |cmr10|, +might be generated by \TeX\ at any time, producing a |dvi| file +called |doc.dvi|; \TeX\ had to read |cmr10.tfm| in order to +produce this |dvi| file. Now on some future date, a ``^{device driver}'' +program will be used to print |doc.dvi|, using the font +|cmr10.300gf|. Meanwhile, the font may have changed. +If the current |gf| file doesn't match the |tfm| file that was assumed +by \TeX, mysterious glitches will probably occur in the printed document, +because |dvi| information is kept concise by the assumption that the +device driver knows the |tfm| widths of all characters. Potential +problems are kept to a minimum if \TeX\ puts the assumed design size +and check sum of each font into the |dvi| files it produces; +a device driver can then issue a warning message when it finds a +|gf| file that is inconsistent with \TeX's assumptions. + +\endchapter + +But if our\/ {\rm Letter-Cutter} \kern-1pt will have no Forge, +yet he must of necessity accommodate himself % +with a\/ {\rm Vice, Hand-Vice, Hammers,} +\leavevmode{\rm Files, Small} \kern-1pt and\/ {\rm Fine Files} (commonly % + called\/ \kern1pt{\rm Watch-makers Files}) +of these he saves all, as they wear out. +\author JOSEPH ^{MOXON}, {\sl Mechanick Exercises\/} (1683) + % part 12, section 1 + +\bigskip + +The natural definition lists all possible generic characters. +% 189. NATURALIS Character (186) notas omnes (92--113) +% genericas possibiles (167) allegat; +\author CAROLUS ^{LINN\AE US}, {\sl Philosophia Botanica\/} (1751) + % this translation due to Frans A. Stafleu + +\eject + \beginchapter Appendix H. Hardcopy Proofs + +A font cannot be proved correct like a mathematical theorem; a font must +be seen to be believed. Moreover, if some characters of a font are faulty, +the best way to fix them is to look at diagrams that indicate what went wrong. +Therefore \MF\ is incomplete by itself; additional programs are needed to +convert the output of \MF\ into graphic form. + +The purpose of this appendix is to discuss two such auxiliary programs, +which serve as examples of many others that could be devised. The first +of these, called ^|GFtoDVI|\null, takes |gf| files and converts them into +^|dvi| files, which can be printed just like the output of \TeX. Each +character image in the |gf| file will have a printed page to itself, with +labeled points and with bounding boxes just as in the illustrations +we have seen throughout this book. \ (Indeed, the illustrations in this +book were produced by |GFtoDVI|\null.) \ The second auxiliary program to +be discussed below is \TeX\ itself; we shall look at a set of \TeX\ macros +designed to facilitate font testing. + +\ninepoint +\subsection Large scale proofs. The |gf| files produced by plain \MF\ +when it is in ^"proof" mode or ^"smoke" mode can be converted to +annotated diagrams by running them through |GFtoDVI|\null, as we know from +the experiments in Chapter~5. It's also possible to study low-resolution +characters with |GFtoDVI|\null, especially if plain \MF's +`^|gfcorners|' feature has been used. ^^{low resolution proofs} +We shall now take a thorough look at what |GFtoDVI| can do. + +All communication from \MF\ to |GFtoDVI| comes through the |gf| file and +from options that you might type when you run |GFtoDVI|\null. If there are +no ``^{special}'' commands in the |gf| file (cf.~Appendix~G\null), each page +of |GFtoDVI|'s output will show just the ``black'' pixels of a character; +furthermore there will be a title line at the top of the page, showing +the date and time of the \MF\ run, together with the character code +number and extension code (if they are nonzero). The black pixels are +typeset via characters of a so-called ``^{gray font},'' described in +detail below; by changing the gray font you can produce a variety of +different outputs from a single |gf| file. + +To get other things on your proof sheets, ``special'' commands must +appear in the |gf| file. For example, \MF\ will automatically output +a |title| command, if $"proofing">0$, as explained in Appendix~G\null; +|GFtoDVI| will typeset this title on the title line of the next character +image that follows the command. If there are several title statements, +they all will appear; they are supposed to fit on a single line. + +The most important special commands tell |GFtoDVI| to create labeled +points on the character diagram. When you say, for example, +`^@labels@$(1,2)$' in a plain \MF\ program, at a time when +^"proofing"$\null>1$, the macros of Appendix~B will convert this to the +special commands +\begindisplay +@special@ |" 01"|; \ ^@numspecial@ $x_1$; \ @numspecial@ $y_1$;\cr +@special@ |" 02"|; \ @numspecial@ $x_2$; \ @numspecial@ $y_2$;\cr +\enddisplay +|GFtoDVI| will then put a dot labeled `|1|' at point $(x_1,y_1)$ +and a dot labeled `|2|' at~$(x_2,y_2)$. + +Labels are placed in one of four positions relative to their dots---% +either at the top, the left, the right, or the bottom. |GFtoDVI| will +ordinarily try to place all labels so that they don't interfere with +each other, and so that they stay clear of other dots. + But if you want to exercise fine control over the placement +yourself, you can say, for example, `@labels@."top"$(1a,2a)$'; in this +case the specified labels will appear above their dots, regardless of whether or +not other labels and/or dots are thereby overprinted. The |gf| file +^^{labels.top} in this case will contain +\begindisplay +@special@ |" 11a"|; \ @numspecial@ $x_{1a}$; \ @numspecial@ $y_{1a}$;\cr +@special@ |" 12a"|; \ @numspecial@ $x_{2a}$; \ @numspecial@ $y_{2a}$.\cr +\enddisplay +|GFtoDVI| looks at the character following a leading blank space to +determine what sort of labeling convention is desired; the subsequent +characters are the text of the label. + +The command `@labels@."top"$(1a,2a)$' in plain \MF\ is just an +abbreviation for `^@makelabel@."top"(|"1a"|$,z_{1a}$); +@makelabel@."top"(|"2a"|$,z_{2a}$)', when $"proofing">1$; the @makelabel@ +macro is really the fundamental one, and you should use it directly if you +want more unusual effects. Suppose, for example, you just want to +put a dot but no label at point~$z_5$; then you can say +`@makelabel@(|""|$,z_5$)'. And suppose you want to put a label to the +left of point~$z_5$ but with no dot; you can say +`@makelabel@."lft".^"nodot"(|"5"|$,z_5$)'. Furthermore you could say +`@makelabel@."lft".^"nodot"(|"5"|$,z_5-(2,3)$)' to move that label left +by~2 pixels and down by~3 pixels, thereby getting the effect of a label +that is diagonally adjacent to its dot. Labels without dots can also +be used to put words on a diagram. + +|GFtoDVI| recognizes nine varieties of labels in all, based on the +first two characters of the special string command: +\smallskip +\item\bull@makelabel@ (special |" 0"|): choose the label position automatically. +\item\bull@makelabel@."top" (special |" 1"|): center the label just above +the dot. +\item\bull@makelabel@."lft" (special |" 2"|): place the label just left of +the dot. +\item\bull@makelabel@."rt" (special |" 3"|): place the label just right of +the dot. +\item\bull@makelabel@."bot" (special |" 4"|): center the label just below +the dot. +\item\bull@makelabel@."top"."nodot" (special |" 5"|): like "top", but omit +the dot. +\item\bull@makelabel@."lft"."nodot" (special |" 6"|): like "lft", but omit +the dot. +\item\bull@makelabel@."rt"."nodot" (special |" 7"|): like "rt", but omit +the dot. +\item\bull@makelabel@."bot"."nodot" (special |" 8"|): like "bot", but omit +the dot. +\smallskip\noindent +The first case is called {\sl autolabeling\/}; this is the normal command. +Autolabeling always places a dot, whether or not that dot overlaps other dots, +but you don't always get a label. Autolabels are typeset only after +all explicit labels have been established; then |GFtoDVI| tries to +place as many of the remaining labels as possible. + +If there's no place to put an autolabel, an ``^{overflow equation}'' is +put in the upper right corner of the proofsheet. For example, the +overflow equation `|5 = 5r + (-4.9,0)|' means that there was no room +for label~|5|, whose dot is 4.9 pixels to the left of the dot for~|5r| +(which is labeled). + +You can avoid overflow equations by sending |GFtoDVI| the special command +|" /"| instead of |" 0"|; ^^{/} this is a variant of autolabeling that +does everything as usual except that the label will simply be forgotten if +it can't be placed. To do this with plain \MF\!, set +`$"lcode\_":=\null$|" /"|' near the beginning of your program; ^"lcode\_" +is the string that @makelabel@ uses to specify autolabeling. + +The next most important kind of annotation for proofs is a straight line +or ``^{rule}.'' Plain \MF's command for this is `^@proofrule@$(z_1,z_2)$', +which expands to +\begindisplay +@special@ |"rule"|; \ @numspecial@ $x_1$; \ @numspecial@ $y_1$;\cr +\qquad @numspecial@ $x_2$; \ @numspecial@ $y_2$.\cr +\enddisplay +|GFtoDVI| has trouble drawing diagonal rules, because standard ^|dvi| +format includes no provision for drawing straight lines unless they are +vertical or horizontal. Therefore you might get an error message +unless $x_1=x_2$ (vertical rule) or $y_1=y_2$ (horizontal rule). +However, a limited escape from this restriction is available via a +``^{slant font},'' by which |GFtoDVI| is able to typeset diagonal lines +as sequences of characters. Only one slope is permitted per job, +but this is better than nothing (see below). + +To control the weight of proof rules, you say, e.g., `^@proofrulethickness@ +1.5$"mm"\0$' in a plain \MF\ program; this expands to +\begindisplay +@special@ |"rulethickness"|; \ @numspecial@ $1.5"mm"\0$. +\enddisplay +Each horizontal or vertical rule is drawn as if by a pen of the current +rulethickness, hence you can get different weights of lines in a single +diagram. If the current rulethickness is negative, no rule will appear; if +it is zero, a default rulethickness based on a parameter of the gray font +will be used; if it is positive, the stated thickness will be increased if +necessary until it equals an integer number of pixels, and that value will +be used to draw the rule. At the beginning of each character the current +rulethickness is zero. + +You can reposition an entire diagram on its page by saying `^@proofoffset@ +$(x,y)$'; this expands to +\begindisplay +@special@ |"offset"|; \ @numspecial@ $x$; \ @numspecial@ $y$ +\enddisplay +and it tells |GFtoDVI| to shift everything except the title line on the +next character image, $x$~pixels to the right and $y$~pixels upward. + +|GFtoDVI| uses four fonts to typeset its output: (1) The {\sl +{title font}\/} is used for the top line on each page. +(2)~The {\sl{label font}\/} is used for all labels. +(3)~The {\sl{gray font}\/} is used for dots and for black pixels. +(4)~The {\sl{slant font}\/} is used for diagonal rules. +Appropriate default fonts will be used at each installation unless +you substitute specific fonts yourself, by using the @special@ commands +^@titlefont@, ^@labelfont@, ^@grayfont@, or ^@slantfont@. +|GFtoDVI| also understands special strings like `|"grayfontarea +/usr/dek"|', which can be used to specify a nonstandard file area +or directory name for the gray font. Furthermore the |gf| file might +^^{grayfontarea} ^^{labelfontat} +say, e.g., +\begindisplay +@special@ |"labelfontat"|; @numspecial@ 20 +\enddisplay +if you want the label font to be loaded at $20\pt$ instead of its ^{design +size}. The area name and the at size must be given after the font name +itself; in other words, `|"grayfont"|' cancels a previous +`|"grayfontarea"|'. + +The four fonts used by |GFtoDVI| must be established before the first +character bitmap appears in the |gf| file. This means that the special font +commands must be given before the first ^@shipout@ or ^@endchar@ in your +program; but they shouldn't appear until after ^@mode\_setup@, so that +your |gf| file will have the correct name. If it's inconvenient to +specify the fonts that way, you can change them at run time when +you use |GFtoDVI|\null: Just type `^|/|' following the name of the |gf| file +that's being input, and you will be asked to type special strings online. +For example, the run-time dialog might look like this: +\begintt +This is GFtoDVI, Version 2.0 +GF file name: io.2602gf/ +Special font substitution: labelfont cmbx10 +OK; any more? grayfont black +OK; any more? +\endtt +After the final carriage return, |GFtoDVI| does its normal thing, +ignoring font specifications in the file that conflict with those +just given. + +\subsection ^{Gray fonts}. A proof diagram constructed by |GFtoDVI| can +be regarded as an array of rectangles, where each rectangle is either +blank or filled with a special symbol that we shall call `{\manual R}'. A +blank rectangle represents a white pixel, while {\manual R} represents a +black pixel. Additional labels and reference lines are often superimposed +on this array of rectangles; hence it is usually best to choose a symbol +{\manual R} that has a somewhat gray appearance, although any symbol can +actually be used. + +In order to construct such proofs, |GFtoDVI| needs to work with +a special type of font known as a ``gray font''; it's possible to +obtain a wide variety of different sorts of proofs by using different +sorts of gray fonts. The next few paragraphs explain exactly what gray +fonts are supposed to contain, in case you want to design your own. + +The simplest gray font contains only two characters, namely {\manual R} +and another symbol that is used for dots that identify key points. +If proofs with relatively large pixels are desired, a two-character +gray font is all that's needed. However, if the pixel size is to be +relatively small, practical considerations make a two-character +font too inefficient, since it requires the typesetting of tens +of thousands of tiny little characters; printing-device drivers +rarely work very well when they are presented with data that is +so different from ordinary text. Therefore a gray font with small +pixels usually has a number of characters that replicate {\manual R} in +such a way that comparatively few characters actually need to be +typeset. + +Since many printing devices are not able to cope with +arbitrarily large or complex characters, it is not possible for a +single gray font to work well on all machines. In fact, +{\manual R} must have a width that is an integer multiple of the printing +device's units of horizontal and vertical positioning, +since rounding the positions of grey +characters would otherwise produce unsightly streaks on proof output. +Thus, there is no way to make the gray font as device-independent as +normal fonts of type can be. + +This understood, we can now take a look at what |GFtoDVI| expects to +see in a gray font. The character~{\manual R} always appears in position~1. It +must have positive height~$h$ and positive width~$w$; its depth +and italic correction are ignored. + +Positions 2--120 of a gray font are reserved for special combinations of\/ +{\manual R}'s and blanks, stacked on top of each other. None of these +character codes need be present in the font; but if they are, the slots +must be occupied by characters of width~$w$ that have certain +configurations of\/ {\manual R}'s and blanks, prescribed for each character +position. For example, position~3 of the font should either contain no +character at all, or it should contain a character consisting of two +{\manual R}'s, one above the other; one of these {\manual R}'s should rest +on the baseline, and the other should appear immediately below. + +It will be convenient to use a horizontal notation like `{\manual RSRRS}' +to stand for a vertical stack of\/ {\manual R}'s and blanks. The convention +will be that the stack is built from bottom to top, and the topmost +rectangle should sit on the baseline. Thus, `{\manual RSRRS}' stands +actually for a character of height~$h$ and depth~$4h$ that looks like this: +\begindisplay +\vbox{\offinterlineskip\halign{\manual#\hfil\cr +\phantom{R}\cr +R\rm\smash{\hbox{\raise.5pt\hbox{$\longleftarrow$ baseline}}}\cr +R\cr +\phantom{R}\cr +R\cr +}} +\enddisplay +We use a horizontal notation in this discussion instead of a vertical one +because column vectors take too much space, and because the horizontal +notation corresponds to binary numbers in a convenient way. + +Positions 1--63 of a gray font are reserved for the patterns {\manual R}, +{\manual RS}, {\manual RR}, {\manual RSS}, {\manual RSR}, and so~on up to +{\manual RRRRRR}, just as in the normal binary notation of the numbers +1--63, with {\manual R}'s substituted for 1's and blanks for 0's. +Positions 64--70 are reserved for the special patterns {\manual RSSSSSS}, +{\manual RRSSSSS}, {\manual RRRSSSS}, {\manual RRRRSSS}, {\manual +RRRRRSS}, {\manual RRRRRRS}, {\manual RRRRRRR} of length seven; positions +71--78 are, similarly, reserved for the length-eight patterns {\manual +RSSSSSSS} through {\manual RRRRRRRR}. The length-nine patterns {\manual +RSSSSSSSS} through {\manual RRRRRRRRR} are assigned to positions 79--87, +the length-ten patterns to positions 88--97, the length-eleven patterns to +positions 98--108, and the length-twelve patterns to positions 109--120. + +Position 0 of a gray font is reserved for the ``dot'' character, which +should have positive height~$h'$ and positive width~$w'$. When |GFtoDVI| +wants to put a dot at some place $(x,y)$ on the figure, it positions +the dot character so that its reference point is at $(x,y)$. The +dot will be considered to occupy a rectangle whose corners are at +$(x\pm w',y\pm h')$; the rectangular +box for a label will butt up against the rectangle enclosing the dot. + +All other character positions of a gray font (namely, positions 121--255) +are unreserved, in the sense that they have no predefined meaning. +But |GFtoDVI| may access them via the ^@charlist@ feature of +|tfm| files, starting with any of the characters in positions +1--120. In such a case each succeeding character in a list should be +equivalent to two of its predecessors, horizontally adjacent to each other. +For example, in +\begindisplay +@charlist@ 53: 121: 122: 123 +\enddisplay +character 121 will stand for two 53's, character 122 for two 121's (i.e., +four 53's), and character 123 for two 122's (i.e., eight 53's). Since +position~53 contains the pattern {\manual RRSRSR}, character~123 in this example +would have height~$h$, depth~$5h$, and width~$8w$, and it would stand for +the pattern +\begindisplay +\vbox{\offinterlineskip\halign{\manual#\hfil\cr +RRRRRRRR\cr +\phantom{SSSSSSSS}\rm + \smash{\hbox{\raise.5pt\hbox{$\longleftarrow$ baseline}}}\cr +RRRRRRRR\cr +\phantom{SSSSSSSS}\cr +RRRRRRRR\cr +RRRRRRRR\cr +}} +\enddisplay +Such a pattern is, of course, rather unlikely to occur in a |gf| file, +but |GFtoDVI| would be able to use if it were present. Designers +of gray fonts should provide characters only for patterns that they think +will occur often enough to make the doubling worthwhile. For example, +the character in position 120 ({\manual RRRRRRRRRRRR}), or whatever is the +tallest stack of\/ {\manual R}'s present in the font, is a natural candidate for +repeated doubling. + +Here's how |GFtoDVI| decides what characters of the gray font will be used, +given a configuration of black and white pixels: If there are no black +pixels, stop. Otherwise look at the top row that contains at least one +black pixel, and the eleven rows that follow. For each such column, +find the largest~$k$ such that $1\leq k\leq120$ and the gray font contains +character~$k$ and the pattern assigned to position~$k$ appears in the +given column. Typeset character $k$ (unless no such character exists) +and erase the corresponding black pixels; use doubled characters, +if they are present in the gray font, if two or more consecutive equal +characters need to be typeset. Repeat the same process on the remaining +configuration, until all the black pixels have been erased. + +If all characters in positions 1--63 are present, this process is guaranteed to +take care of at least six rows each time; and with characters 64--120 as well, +it usually takes care of twelve, since all patterns that contain at most +one ``run'' of\/ {\manual R}'s are present. + +Some of the ^@fontdimen@ parameters discussed in Appendix~F are important +in gray fonts. The ^@font\_slant@ value~$s$, if nonzero, will cause +|GFtoDVI| to skew its output; in this case the character {\manual R} will +presumably be a parallelogram with a corresponding slant, rather than the +usual rectangle. \MF's coordinate $(x,y)$ will appear in physical position +$(xw+yhs,yh)$ on the proofsheets. \ (This is appropriate for proofing unslanted +fonts whose pixels will become slanted by mechanical obliquing.) + +Parameter @fontdimen@ 8 of a gray font specifies the thickness of rules +that go on the proofs. If this parameter is zero, \TeX's default +rule thickness (0.4\thinspace pt) will be used. +The other parameters of a gray font are ignored by |GFtoDVI|\null, but +it is conventional to set ^@font\_normal\_space@ and ^@font\_quad@ to~$w$, +^@font\_x\_height@ to~$h$. + +For best results the designer of a gray font should choose $w$ and~$h$ +so that the user's |dvi|-to-hardcopy software will not make any +rounding errors. Furthermore, the dot should be an even number~$2m$ of +pixels in diameter, and the rule thickness should work out to an +even number~$2n$ of pixels; then the dots and rules will be centered on +the correct positions, in the common case of integer coordinates. Gray fonts +are almost always intended for particular output devices, even though +`|dvi|' stands for ``device independent''; we use |dvi| files for \MF\ +proofs chiefly because software to print |dvi| files is already in place. + +The \MF\ program for a fairly versatile gray font generator, called +`^|grayf.mf|', appears on the next few pages. It should be invoked by a +parameter file that establishes values of several quantities: +\smallskip +\item\bull If ^"large\_pixels" is of type @boolean@, only 15 characters +will be generated; otherwise there will be 123. +\item\bull If ^"pix\_picture" is of type @picture@, it should be the +desired pixel image `{\manual R}', and in this case ^"pix\_wd" and +^"pix\_ht" should be the width and height in pixels. Otherwise a default +gray pixel pattern will be used. +\item\bull If ^"rep" is known, it should be a positive integer; the default +pixel pattern will be replicated so that the final +proofs will be "rep" times bigger than usual, and the pattern will be clipped +slightly at the edges so that discrete pixels can be seen plainly. +\item\bull If ^"lightweight" is of type @boolean@, the default pixel +pattern will be only half as dark as usual. +\item\bull If ^"dotsize" is known, it should be the diameter of the +special dot character, in pixel units. +\item\bull The ^@font\_identifier@ should be specified. +\smallskip\noindent +(The "rep" and "lightweight" options are ignored if "pix\_picture" is +explicitly given.) \ +Since gray fonts are inherently device-dependent, we do not start +with ``sharp'' dimensions as in normal fonts; we go backwards and +compute the sharp units from pixel units. + +The name of each gray font should include the name of the device for +which it is intended. \ (A ``favorite'' proof device can also be chosen +at each installation, for which the alternate font names `^|gray|' +and `^|black|' are valid; these installation-dependent fonts are the +defaults for "proof" mode and "smoke" mode.) + +Here, for example, is a suitable parameter file `|graycheap.mf|', which +generates a vanilla-flavored gray font for the hypothetical "cheapo" +printer: +\beginlines +|% Gray font for Cheapo with proofsheet resolution 50 pixels per inch| +\smallskip +|if mode<>cheapo: errmessage "This file is for cheapo only"; fi| +\smallskip +|font_identifier "GRAYCHEAP";| +|input grayf| +\endlines +(The proofsheet resolution will be 50 pixels per inch, because "cheapo" has +200 pixels per inch, and the default "pix\_picture" in |grayf| +will be four pixels square in this case.) \ If the default pixel pattern +turns out to be such a dark gray that the labels and rules are obscured, +the statement `|boolean lightweight|' should be added. A solid black font +with slightly higher-resolution images can be generated by the following +file `|blackcheap.mf|': +\beginlines +|% Black font for Cheapo with proofsheet resolution 66.7 pixels per inch| +\smallskip +|if mode<>cheapo: errmessage "This file is for cheapo only"; fi| +\smallskip +|picture pix_picture; pix_wd := pix_ht := 3;| +|pix_picture := unitpixel scaled 3;| +\smallskip +|font_identifier "BLACKCHEAP";| +|input grayf| +\endlines +And here is a file `|graycheap5.mf|' that generates a gray font suitable +for studying large proofs of low-resolution characters: +\beginlines +|% Gray font for Cheapo with proofsheet resolution 10 pixels per inch| +\smallskip +|if mode<>cheapo: errmessage "This file is for cheapo only"; fi| +\smallskip +|rep=5; boolean large_pixels;| +\smallskip +|font_identifier "GRAYCHEAP";| +|input grayf| +\endlines + +Now let's look at the program file `|grayf.mf|' itself. It begins with +a simple test to ensure that "mag" and "rep" are positive integers, if +they're known; then comes some less obvious code that handles +magnification in a nonstandard way: +\beginlines +|% More-or-less general gray font generator| +|% See Appendix H of The METAFONTbook for how to use it| +\smallskip +|forsuffixes m = mag,rep:| +| if unknown m: m := 1;| +| elseif (m<1) or (m<>floor m):| +| errmessage "Sorry, " & str m & " must be a positive integer";| +| m := 1; fi endfor| +\smallbreak +|mg := mag; mag := 1; mode_setup;| +|if mg>1: hppp := hppp*mg; vppp := vppp*mg;| +| extra_endchar:=| +| "if charcode>0:currentpicture:=currentpicture scaled mg;fi"| +| & extra_endchar; fi;| +\endlines +This circumlocution is the easiest way to guarantee that the ^|tfm| file +will be completely unaffected by magnification. + +The next part of |grayf| computes the pixel representation, "pix\_picture". +\beginlines +|if picture pix_picture: rep := 1;| +| cull pix_picture keeping (1,infinity);| +|else: for z=(0,2),(1,0),(2,3),(3,1):| +| fill unitsquare shifted z; endfor| +| if not boolean lightweight:| +| addto currentpicture also| +| currentpicture rotated 90 xscaled -1; fi| +| if unknown scale: scale := max(1,round(pixels_per_inch/300)); fi| +| pix_wd := pix_ht := 4scale;| +| if rep>1: picture pix;| +| currentpicture := currentpicture shifted-(1,1); pix := currentpicture;| +| for r=1 upto rep-1: addto currentpicture also pix shifted(4r,0); endfor| +| cullit; pix := currentpicture;| +| for r=1 upto rep-1: addto currentpicture also pix shifted(0,4r); endfor| +| unfill unitsquare xscaled 4rep yscaled 2 shifted-(1,1);| +| unfill unitsquare yscaled 4rep xscaled 2 shifted-(1,1); cullit; fi| +| picture pix_picture; pix_picture := currentpicture scaled scale;| +| pix_wd := pix_ht := 4scale*rep; fi| +\weakendlines +The lightweight pattern has 4 of every 16 pixels turned on; the normal +pattern has twice as many. + +Character 0 is the dot, which is quite simple: +\beginlines +|def # = *72.27/pixels_per_inch enddef;| +|if unknown dotsize: dotsize := 2.5pix_wd/rep; fi| +\smallskip +|beginchar(0,1.2dotsize#,1.2dotsize#,0);| +|fill fullcircle scaled dotsize scaled mg; endchar;| +\endlines + +The special coding scheme of gray fonts is implemented next: +\beginlines +|numeric a[]; newinternal b,k;| +|def next_binary =| +| k := 0; forever: if k>b: a[incr b] := 0; fi| +| exitif a[k]=0; a[k] := 0; k := k+1; endfor| +| a[k] := 1 enddef;| +|def next_special_binary =| +| if a[0]=1: for k=0 upto b: a[k] := 0; endfor a[incr b]| +| else: k := 0; forever: exitif a[incr k]=1; endfor| +| a[k-1] fi := 1 enddef;| +\smallbreak +|def make_char =| +| clearit; next_binary;| +| for k=0 upto b: if a[k]=1:| +| addto currentpicture also pix_picture shifted(0,-k*pix_ht); fi endfor| +| charcode := charcode+1; chardp := b*charht;| +| scantokens extra_endchar; shipout currentpicture enddef;| +\endlines + +Now we are ready to generate all the pixel characters. +^^@charlist@^^"chardx"^^"charwd"^^"charht" +\beginlines +|charwd := pix_wd#; charht := pix_ht#; chardx := pix_wd*mg;| +|b := -1;| +\smallskip +|if boolean large_pixels:| +| for k=1 upto 7: make_char; charlist k:k+120; endfor| +| charcode := 120; b := -1;| +| addto pix_picture also pix_picture shifted (chardx,0);| +| charwd := 2charwd; chardx := 2chardx;| +| for k=1 upto 7: make_char; endfor| +|else: for k=1 upto 63: make_char; endfor| +| let next_binary = next_special_binary;| +| for k=64 upto 120: make_char; endfor| +| for k=121,122: charcode := k;| +| addto currentpicture also currentpicture shifted (chardx,0);| +| charwd := 2charwd; chardx := 2chardx;| +| scantokens extra_endchar; shipout currentpicture; endfor| +| charlist 120:121:122; fi| +\endlines + +The program closes by establishing fontwide parameters: +\beginlines +|font_coding_scheme "GFGRAY";| +|font_size 8(pix_wd#);| +|font_normal_space pix_wd#;| +|font_x_height pix_ht#;| +|font_quad pix_wd#;| +|fontdimen 8: if known rulethickness: rulethickness| +| else: pix_wd#/(2rep) fi;| +|bye.| +\weakendlines +(The extra complications of an ^"aspect\_ratio" or a slant have not +been addressed.) + +\subsection ^{Slant fonts}. |GFtoDVI| also makes use of another special +type of font, if it is necessary to typeset slanted rules. The format of +such so-called ``slant fonts'' is quite a bit simpler than the format of +gray fonts. + +A slant font contains exactly $n$ characters, in positions 1 to~$n$, +for some positive integer~$n$. +The character in position~$k$ represents a slanted line $k$ units +tall, starting at the baseline. These lines all have a fixed slant ratio~$s$. +The vertical ``unit'' is usually chosen to be an integral number of pixels, +small enough so that it suffices to draw rules that are an +integer number of units high; in fact, it should probably be no larger +than the thickness of the rules being drawn. + +The following simple algorithm is used to typeset a rule that is $m$ units +high: Compute $q=\lceil m/n\rceil$; then typeset $q$~characters of +approximately equal size, namely $(m\bmod q)$ copies of character number +$\lceil m/q\rceil$ and $q-(m\bmod q)$ copies of character number +$\lfloor m/q\rfloor$. For example, if $n=15$ and $m=100$, we have $q=7$; +a 100-unit-high rule will be composed of 7~pieces, using characters +14,~14, 14, 14, 14, 15,~15. + +|GFtoDVI| looks at the ^"charht" of character $n$ only, so the |tfm| file +need not be accurate about the heights of the other characters. \ (This is +fortunate, since ^|tfm| format allows at most 15 different nonzero +heights per font.) + +The ^"charwd" of character~$k$ should be $k/n$ times $s$ times the "charht" +of~$n$. + +The ^@font\_slant@ parameter should be $s$. It is customary to +set the parameter @fontdimen@~8 to the thickness of +the slanted rules, but |GFtoDVI| doesn't look at it. + +Here's an example of a slant-font parameter file, `|slantcheap6|', +for the "cheapo" printer and a slant of 1/6: +\beginlines +|% Slant font for Cheapo with slope 1/6| +\smallskip +|if mode<>cheapo: errmessage "This file is for cheapo only"; fi| +\smallskip +|s=1/6; % the slant ratio| +|n=30; % the number of characters| +|r#=.4pt#; % thickness of the rules| +|u=1; % vertical unit| +\smallskip +|font_identifier "SLANTCHEAP6";| +|input slant| +\endlines +The corresponding program file `|slant.mf|' looks like this: +\beginlines +|% More-or-less general slant font generator for GFtoDVI| +|% The calling file should set the font_identifier and| +|% n = number of characters| +|% s = slant ratio| +|% r# = rule thickness (in sharp units)| +|% u = vertical unit (in pixels)| +\smallskip +|if unknown mag: mag := 1;| +|elseif (mag<1) or (mag<>floor mag):| +| errmessage "Sorry, mag must be a positive integer"; mag := 1; fi| +\smallbreak +|mg := mag; mag := 1; mode_setup; u# := u*72.27/pixels_per_inch;| +|pixels_per_inch := pixels_per_inch*mg; fix_units;| +\smallbreak +|define_whole_pixels(u); define_blacker_pixels(r);| +|pickup pencircle scaled r; ruler := savepen;| +\smallbreak +|for k=1 upto n:| +| beginchar(k,k*u#*s,n*u#,0);| +| pickup ruler; draw origin--(k*u*s,k*u); % draw the line| +| unfill (lft-1,bot -1)--(rt 1,bot -1)| +| --(rt 1,0)--(lft-1,0)--cycle; % clip the ends| +| unfill ((lft -1,0)--(rt 1,0)| +| --(rt 1,top 1)--(lft -1,top 1)--cycle) shifted (k*u*s,k*u);| +| endchar; endfor| +\smallbreak +|font_size 16pt#;| +|font_slant s;| +|fontdimen 8: r#;| +|font_coding_scheme "GFSLANT";| +|bye.| +\endlines + +\subsection Font samples. The real test of a font is its appearance +at the final size, after it has actually been typeset. The \TeX\ +typesetting system can be used with the following example macro file +`^|testfont.tex|' (in addition to plain \TeX\ format) to +put a new font through its paces. + +We shall comment on typical uses of |testfont| as we examine its parts. +At the beginning, |testfont.tex| turns off several of \TeX's normal features. +\beginlines +|% A testbed for font evaluation| +\smallskip +|\tracinglostchars=0 % missing characters are OK| +|\tolerance=1000 % and so are loose lines| +|\raggedbottom % pages can be short| +|\nopagenumbers % and they won't be numbered| +|\parindent=0pt % nor will paragraphs be indented| +|\hyphenpenalty=200 % hyphens are discouraged| +|\doublehyphendemerits=30000 % and two in a row are terrible| +\smallskip +|\newlinechar=`@ % we want to type multiline messages| +|\chardef\other=12 % and redefine "catcodes"| +\smallskip +|\newcount\m \newcount\n \newcount\p \newdimen\dim % temporary variables| +\endlines + +Then there are macros to print the time and date---an extremely valuable +thing to have on any proofsheet. +\beginlines +|\def\today{\ifcase\month\or| +| January\or February\or March\or April\or May\or June\or| +| July\or August\or September\or October\or November\or December\fi| +| \space\number\day, \number\year}| +|\def\hours{\n=\time \divide\n 60| +| \m=-\n \multiply\m 60 \advance\m \time| +| \twodigits\n\twodigits\m}| +|\def\twodigits#1{\ifnum #1<10 0\fi \number#1}| +\endlines + +An online ``menu'' of the available test routines will be typed at your +terminal if you request |\help|. +\beginlines +|{\catcode`\|\||=0 \catcode`\\=\other % use |\|| as the escape, temporarily| +\||gdef|\||help{|\||message{%| +|\init switches to another font;@%| +|\end or \bye finishes the run;@%| +|\table prints the font layout in tabular format;@%| +|\text prints a sample text, assuming TeX text font conventions;@%| +|\sample combines \table and \text;@%| +|\mixture mixes a background character with a series of others;@%| +|\alternation interleaves a background character with a series;@%| +|\alphabet prints all lowercase letters within a given background;@%| +|\ALPHABET prints all uppercase letters within a given background;@%| +|\series prints a series of letters within a given background;@%| +|\lowers prints a comprehensive test of lowercase;@%| +|\uppers prints a comprehensive test of uppercase;@%| +|\digits prints a comprehensive test of numerals;@%| +|\math prints a comprehensive test of TeX math italic;@%| +|\names prints a text that mixes upper and lower case;@%| +|\punct prints a punctuation test;@%| +|\bigtest combines many of the above routines;@%| +|\help repeats this message;@%| +|and you can use ordinary TeX commands (e.g., to \input a file).}}}| +\endlines + +The program prompts you for a font name. If the font is in your local +directory instead of a system directory, you might have to +specify the directory name as part of the font name. You should +also specify scaling if the font has been magnified, as in the example +of Chapter~5. Several fonts can be tested during a single run, if you +say `|\init|' before `|\end|'. +\beginlines +|\def\init{\message{@Name of the font to test = }| +| \read-1 to\fontname \startfont| +| \message{Now type a test command (\string\help\space for help):}}| +|\def\startfont{\font\testfont=\fontname \spaceskip=0pt| +| \leftline{\sevenrm Test of \fontname\unskip\ on \today\ at \hours}| +| \medskip| +| \testfont \setbaselineskip| +| \ifdim\fontdimen6\testfont<10pt \rightskip=0pt plus 20pt| +| \else\rightskip=0pt plus 2em \fi| +| \spaceskip=\fontdimen2\testfont % space between words (\raggedright)| +| \xspaceskip=\fontdimen2\testfont| +| \advance\xspaceskip by\fontdimen7\testfont}| +\endlines +The specified font will be called |\testfont|. As soon as you have +specified it, |\init| calls on |\startfont|, which puts a title line +on the page; then it chooses what it hopes will be a good distance between +baselines, and gets ready to typeset text with ``^{ragged right}'' margins. +\ (The code above improves on plain \TeX's ^|\raggedright|.) + +The baselineskip distance is taken to be $6\pt$ plus the height of the +tallest character plus the depth of the deepest character. This is the +distance between baselines for ``series'' tests, but it is decreased +by $4\pt$ when the sample text is set. If you +want to change the baseline distance chosen by |testfont|, +you can just say, e.g., `|\baselineskip=11pt|'. +\beginlines +|\def\setbaselineskip{\setbox0=\hbox{\n=0| +|\loop\char\n \ifnum \n<255 \advance\n 1 \repeat} % 256 chars in \box0| +|\baselineskip=6pt \advance\baselineskip\ht0 \advance\baselineskip\dp0 }| +\endlines + +When |testfont| prompts you for a ``^{background character}'' +or a ``^{starting character}'' or an ``^{ending character},'' you +can type the character you want (assuming ASCII code); +or you can say, e.g., `|#35|' to get character code number 35. +Codes 0--32 and 127--255 have to be specified with the `|#|' option, +on non-fancy installations of \TeX, +and so does code 35 (which is the ASCII code of `|#|' itself). +\beginlines +|\def\setchar#1{{\escapechar-1\message{\string#1 character = }%| +| \def\do##1{\catcode`##1=\other}\dospecials| +| \read-1 to\next| +| \expandafter\finsetchar\next\next#1}}| +|\def\finsetchar#1#2\next#3{\global\chardef#3=`#1| +| \ifnum #3=`\# \global\chardef#3=#2 \fi}| +|\def\promptthree{\setchar\background| +| \setchar\starting \setchar\ending}| +\endlines +(The \TeX\ hackery here is a bit subtle, because special characters +like `|\|' and `|$|' must temporarily lose their special significance.) + +Suppose the background character is `|o|' and the starting and ending +characters are respectively `|p|' and~`|q|'. Then the ^|\mixture| +operation will typeset `|opooppooopppop|' and `|oqooqqoooqqqoq|'; +the ^|\alternation| operation will typeset `|opopopopopopopopo|' and +`|oqoqoqoqoqoqoqoqo|'. Other patterns could be added in a similar way. +\beginlines +|\def\mixture{\promptthree \domix\mixpattern}| +|\def\alternation{\promptthree \domix\altpattern}| +|\def\mixpattern{\0\1\0\0\1\1\0\0\0\1\1\1\0\1}| +|\def\altpattern{\0\1\0\1\0\1\0\1\0\1\0\1\0\1\0\1\0}| +|\def\domix#1{\par\chardef\0=\background \n=\starting| +| \loop \chardef\1=\n #1\endgraf| +| \ifnum \n<\ending \advance\n 1 \repeat}| +\endlines + +The |\series| operation puts the background character between all the +others (e.g., `|opoqo|'). Special series containing the lowercase +letters of \TeX\ text fonts (including `\char'31', `\char'32', +`\char'33', and ~\char'34') and the uppercase letters (including +`\char'35', `\char'36', and~`\char'37') are provided. +Although |\mixture| and |\alternation| show you the effects of +ligatures and kerning, |\series| does not. +\beginlines +|\def\!{\discretionary{\background}{\background}{\background}}| +|\def\series{\promptthree \!\doseries\starting\ending\par}| +|\def\doseries#1#2{\n=#1\loop\char\n\!\ifnum\n<#2\advance\n 1 \repeat}| +|\def\complower{\!\doseries{`a}{`z}\doseries{'31}{'34}\par}| +|\def\compupper{\!\doseries{`A}{`Z}\doseries{'35}{'37}\par}| +|\def\compdigs{\!\doseries{`0}{`9}\par}| +|\def\alphabet{\setchar\background\complower}| +|\def\ALPHABET{\setchar\background\compupper}| +\endlines +(A long series might fill more than one line; \TeX's |\discretionary| +break operation is used here so that the background character will end +the line and be repeated at the beginning of the next.) + +A ``comprehensive'' test uses a series of background characters +against a series of others. The series will consist of lowercase +letters (`^|\lowers|'), uppercase letters (`^|\uppers|'), or +numerals (`^|\digits|'). +\beginlines +|\def\lowers{\docomprehensive\complower{`a}{`z}{'31}{'34}}| +|\def\uppers{\docomprehensive\compupper{`A}{`Z}{'35}{'37}}| +|\def\digits{\docomprehensive\compdigs{`0}{`4}{`5}{`9}}| +|\def\docomprehensive#1#2#3#4#5{\par\chardef\background=#2| +| \loop{#1} \ifnum\background<#3\m=\background\advance\m 1| +| \chardef\background=\m \repeat \chardef\background=#4| +| \loop{#1} \ifnum\background<#5\m=\background\advance\m 1| +| \chardef\background=\m \repeat}| +\endlines + +The ^|\names| test puts uppercase letters and accents +together with lowercase letters. The accents will look funny +if the test font doesn't have them in plain \TeX's favorite positions. +\beginlines +|\def\names{ {\AA}ngel\aa\ Beatrice Claire| +| Diana \'Erica Fran\c{c}oise Ginette H\'el\`ene Iris| +| Jackie K\=aren {\L}au\.ra Mar{\'\i}a N\H{a}ta{\l}{\u\i}e {\O}ctave| +| Pauline Qu\^eneau Roxanne Sabine T\~a{\'\j}a Ur\v{s}ula| +| Vivian Wendy Xanthippe Yv{\o}nne Z\"azilie\par}| +\endlines + +Punctuation marks are tested in juxtaposition with different +sorts of letters, by the `^|\punct|' macro: +\beginlines +|\def\punct{\par\dopunct{min}\dopunct{pig}\dopunct{hid}| +| \dopunct{HIE}\dopunct{TIP}\dopunct{fluff}| +| \$1,234.56 + 7/8 = 9\% @ \#0\par}| +|\def\dopunct#1{#1,\ #1:\ #1;\ `#1'\| +| ?||`#1?\ !||`#1!\ (#1)\ [#1]\ #1*\ #1.\par}| +\endlines + +Mixtures and alternations and series are excellent ways to discover +that letters are too dark, too light, or too tightly spaced. But +a font also has to be readable; in fact, this is the number one +objective. So |testfont| provides a sample `^|\text|'. One of the sentences +is optional, because it contains lots of accents and unusual letters; +you can omit it from the text by saying `^|\omitaccents|'. +Furthermore, you can type your own text, online, or you can input one from +^^{Stanfords} ^^{Kafka} ^^{AEsop} +a file, instead of using this canned example. +\beginlines +|\def\text{{\advance\baselineskip-4pt| +|\setbox0=\hbox{abcdefghijklmnopqrstuvwxyz}| +|\ifdim\hsize>2\wd0 \ifdim 15pc>2\wd0 \hsize=15pc \else\hsize=2\wd0 \fi\fi| +|On November 14, 1885, Senator \& Mrs.~Leland Stanford called together at| +|their San Francisco mansion the 24~prominent men who had been chosen as| +|the first trustees of The Leland Stanford Junior University. They| +|handed to the board the Founding Grant of the University, which they had| +|executed three days before. This document---with various amendments,| +|legislative acts, and court decrees---remains as the University's| +|charter. In bold, sweeping language it stipulates that the objectives of| +|the University are ``to qualify students for personal success and direct| +|usefulness in life; and to promote the publick welfare by exercising an| +|influence in behalf of humanity and civilization, teaching the blessings| +|of liberty regulated by law, and inculcating love and reverence for the| +|great principles of government as derived from the inalienable rights of| +|man to life, liberty, and the pursuit of happiness.'' \moretext| +|(!||`THE DAZED BROWN FOX QUICKLY GAVE 12345--67890 JUMPS!)\par}}| +|\def\moretext{?||`But aren't Kafka's Schlo{\ss} and {\AE}sop's {\OE}uvres| +|often na{\"\i}ve vis-\`a-vis the d{\ae}monic ph{\oe}nix's official| +|r\^ole in fluffy souffl\'es? }| +|\def\omitaccents{\let\moretext=\relax}| +\endlines + +Now comes one of the hardest parts of the file, from the \TeX\ +standpoint: The |\table| macro prints a font diagram, omitting +groups of sixteen characters that are entirely absent from the font. +The format of this table is the same as that used in Appendix~F +of {\sl The \TeX book}. When the font contains unusually large characters +that ought to be vertically centered, you should say `^|\centerlargechars|' +before `|\table|'. \ (A \TeX\ math symbol font or math extension font +would use this feature.) +\beginlines +|\def\oct#1{\hbox{\rm\'{}\kern-.2em\it#1\/\kern.05em}} % octal constant| +|\def\hex#1{\hbox{\rm\H{}\tt#1}} % hexadecimal constant| +|\def\setdigs#1"#2{\gdef\h{#2}% \h=hex prefix; \0\1=corresponding octal| +| \m=\n \divide\m by 64 \xdef\0{\the\m}%| +| \multiply\m by-64 \advance\m by\n \divide\m by 8 \xdef\1{\the\m}}| +|\def\testrow{\setbox0=\hbox{\penalty 1\def\\{\char"\h}%| +| \\0\\1\\2\\3\\4\\5\\6\\7\\8\\9\\A\\B\\C\\D\\E\\F%| +| \global\p=\lastpenalty}} % \p=1 if none of the characters exist| +|\def\oddline{\cr| +| \noalign{\nointerlineskip}| +| \multispan{19}\hrulefill&| +| \setbox0=\hbox{\lower 2.3pt\hbox{\hex{\h x}}}\smash{\box0}\cr| +| \noalign{\nointerlineskip}}| +|\newif\ifskipping| +|\def\evenline{\loop\skippingfalse| +| \ifnum\n<256 \m=\n \divide\m 16 \chardef\next=\m| +| \expandafter\setdigs\meaning\next \testrow| +| \ifnum\p=1 \skippingtrue \fi\fi| +| \ifskipping \global\advance\n 16 \repeat| +| \ifnum\n=256 \let\next=\endchart\else\let\next=\morechart\fi| +| \next}| +|\def\morechart{\cr\noalign{\hrule\penalty5000}| +| \chartline \oddline \m=\1 \advance\m 1 \xdef\1{\the\m}| +| \chartline \evenline}| +|\def\chartline{&\oct{\0\1x}&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&\:&&}| +|\def\chartstrut{\lower4.5pt\vbox to14pt{}}| +|\def\table{$$\global\n=0| +| \halign to\hsize\bgroup| +| \chartstrut##\tabskip0pt plus10pt&| +| &\hfil##\hfil&\vrule##\cr| +| \lower6.5pt\null| +| &&&\oct0&&\oct1&&\oct2&&\oct3&&\oct4&&\oct5&&\oct6&&\oct7&\evenline}| +|\def\endchart{\cr\noalign{\hrule}| +| \raise11.5pt\null&&&\hex 8&&\hex 9&&\hex A&&\hex B&| +| &\hex C&&\hex D&&\hex E&&\hex F&\cr\egroup$$\par}| +|\def\:{\setbox0=\hbox{\noboundary\char\n\noboundary}%| +| \ifdim\ht0>7.5pt\reposition| +| \else\ifdim\dp0>2.5pt\reposition\fi\fi| +| \box0\global\advance\n 1 }| +|\def\reposition{\setbox0=\vbox{\kern2pt\box0}\dim=\dp0| +| \advance\dim 2pt \dp0=\dim}| +|\def\centerlargechars{| +| \def\reposition{\setbox0=\hbox{$\vcenter{\kern2pt\box0\kern2pt}$}}}| +\endlines + +Two of the most important combinations of tests are treated now: +^|\sample| prints the |\table| and the |text|; ^|\bigtest| gives +you the works, plus a mysterious word that is traditional in type +specimens: ^^{hamburgefonstiv} +\beginlines +|\def\sample{\table\text}| +\smallskip +|\def\bigtest{\sample| +| hamburgefonstiv HAMBURGEFONSTIV\par| +| \names \punct \lowers \uppers \digits}| +\endlines + +Finally, there's a |\math| +routine useful for checking out the spacing in the ^{math} ^{italic} +fonts used by plain \TeX; |\mathsy| does a similar thing for the +uppercase letters in a math symbols font. +\beginlines +|\def\math{\textfont1=\testfont \skewchar\testfont=\skewtrial| +| \mathchardef\Gamma="100 \mathchardef\Delta="101| +| \mathchardef\Theta="102 \mathchardef\Lambda="103 \mathchardef\Xi="104| +| \mathchardef\Pi="105 \mathchardef\Sigma="106 \mathchardef\Upsilon="107| +| \mathchardef\Phi="108 \mathchardef\Psi="109 \mathchardef\Omega="10A| +| \def\ii{i} \def\jj{j}| +| \def\\##1{|\||##1|\||+}\mathtrial| +| \def\\##1{##1_2+}\mathtrial| +| \def\\##1{##1^2+}\mathtrial| +| \def\\##1{##1/2+}\mathtrial| +| \def\\##1{2/##1+}\mathtrial| +| \def\\##1{##1,{}+}\mathtrial| +| \def\\##1{d##1+}\mathtrial| +| \let\ii=\imath \let\jj=\jmath \def\\##1{\hat##1+}\mathtrial}| +|\newcount\skewtrial \skewtrial='177| +|\def\mathtrial{$\\A \\B \\C \\D \\E \\F \\G \\H \\I \\J \\K \\L \\M \\N| +| \\O \\P \\Q \\R \\S \\T \\U \\V \\W \\X \\Y \\Z \\a \\b \\c \\d \\e \\f| +| \\g \\h \\\ii \\\jj \\k \\l \\m \\n \\o \\p \\q \\r \\s \\t \\u \\v \\w| +| \\x \\y \\z \\\alpha \\\beta \\\gamma \\\delta \\\epsilon \\\zeta| +| \\\eta \\\theta \\\iota \\\kappa \\\lambda \\\mu \\\nu \\\xi \\\pi| +| \\\rho \\\sigma \\\tau \\\upsilon \\\phi \\\chi \\\psi \\\omega| +| \\\vartheta \\\varpi \\\varphi \\\Gamma \\\Delta \\\Theta \\\Lambda| +| \\\Xi \\\Pi \\\Sigma \\\Upsilon \\\Phi \\\Psi \\\Omega| +| \\\partial \\\ell \\\wp$\par}| +|\def\mathsy{\begingroup\skewtrial='060 % for math symbol font tests| +| \def\mathtrial{$\\A \\B \\C \\D \\E \\F \\G \\H \\I \\J \\K \\L| +| \\M \\N \\O \\P \\Q \\R \\S \\T \\U \\V \\W \\X \\Y \\Z$\par}| +| \math\endgroup}| +\endlines + +The last line of |testfont| is +\beginlines +|\ifx\noinit!\else\init\fi| +\endlines +and it means ``automatically call `^|\init|' unless `|\noinit|' is +an exclamation point.'' Why this? Well, +you might have your own test file from which you'd like to use the +facilities of |testfont|, without typing commands online. +If your file says `|\let\noinit!| |\input testfont|' \TeX\ will +read in |testfont| but the routine will not prompt you for a file name. +The file can then continue to test one or more fonts by saying, e.g., +\beginlines +|\def\fontname{cmbx10 }\startfont\sample\vfill\eject| +|\def\fontname{cmti10 scaled \magstep3}\startfont\sample\vfill\eject| +\endlines +thereby defining ^|\fontname| directly, and using ^|\startfont| +to do the initialization instead of\/ |\init|. + +\medbreak +To conclude this appendix, let's look at the listing of a file +that can be used to test special constructions in math fonts +with the conventions of plain \TeX: +\beginlines +|\raggedright \rightskip=2em plus 5em minus 2em| +\smallbreak +|$\hbar \not\equiv B$, but $\sqrt C \mapsto \sqrt x$,| +|$Z \hookrightarrow W$, $Z \hookleftarrow W$,| +|$Z \longmapsto W$, $Z \bowtie W$, $Z \models W$,| +|$Z \Longrightarrow W$, $Z \longrightarrow W$,| +|$Z \longleftarrow W$, $Z \Longleftarrow W$,| +|$Z \longleftrightarrow W$, $Z \Longleftrightarrow W$,| +|$\overbrace{\hbox{very long things for testing}}$,| +|$\underbrace{\hbox{very long things for testing}}$,| +|$Z \choose W$, $Z \brack W$, $Z \brace W$, $Z \sqrt W$,| +|$Z \cong W$, $Z \notin W$, $Z \rightleftharpoons W$,| +|$\widehat Z$, $\widehat{ZW}$, $\widehat{Z+W}$,| +|$\widetilde Z$, $\widetilde{ZW}$, $\widetilde{Z+W}$.| +\smallbreak +|\def\sizetest#1#2{$$| +| \Bigggl{#1}\bigggl{#1}\Biggl{#1}\biggl{#1}\Bigl{#1}\bigl{#1}\left#1| +| \bullet| +| \right#2\bigr{#2}\Bigr{#2}\biggr{#2}\Biggr{#2}\bigggr{#2}\Bigggr{#2}$$}| +|\def\biggg#1{{\hbox{$\left#1\vbox to20.5pt{}\right.$}}}| +|\def\bigggl{\mathopen\biggg} \def\bigggr{\mathclose\biggg}| +|\def\Biggg#1{{\hbox{$\left#1\vbox to23.5pt{}\right.$}}}| +|\def\Bigggl{\mathopen\Biggg} \def\Bigggr{\mathclose\Biggg}| +\smallbreak +|\sizetest () \sizetest [] \sizetest \lgroup\rgroup| +|\sizetest \lmoustache\rmoustache \sizetest \vert\Vert| +|\sizetest \arrowvert\Arrowvert \sizetest \uparrow\downarrow| +|\sizetest \updownarrow\Updownarrow \sizetest \Uparrow\Downarrow| +|\sizetest \bracevert{\delimiter"342} \sizetest \backslash/| +|\sizetest \langle\rangle \sizetest \lbrace\rbrace| +|\sizetest \lceil\rceil \sizetest \lfloor\rfloor| +\smallbreak +|$$\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{\sqrt{-1}}}}}}}}}$$| +\smallbreak +|\def\dobig{\do\bigvee \do\bigwedge \do\bigotimes \do\bigoplus \do\bigodot| +| \do\bigcap \do\bigcup \do\biguplus \do\bigsqcup| +| \do\int \do\ointop \do\smallint \do\prod \do\coprod \do\sum}| +|\def\do#1{#1_a^b A} $\dobig$ $$\dobig$$| +\smallbreak +|\bye| +\endlines + +\endchapter + +Be sure of it: Giue me the Occular proofe. +\author WILLIAM ^{SHAKESPEARE}, {\sl Othello\/} (1604) % act 3 sc 3 l 360 + +\bigskip + +The figure itself appears here +as a very necessary adjunct to the verbalization. +In Euclid's presentation we cannot wholly follow the argumentation +without the figure, and unless we are strong enough +to imagine the figure in our mind's eye, we would also be reduced +to supplying our own figure if the author had not done it for us. +Notice also that the language of the proof has a +formal and severely restricted quality about it. +This is not the language of history, nor of drama, +nor of day to day life; +this is language that has been sharpened and refined so as to serve +the precise needs of a precise but limited intellectual goal. +\author P. J. ^{DAVIS} and R. ^{HERSH}, {\sl Proof\/} (1981) + % The Mathematical Experience (Birkh\"auser), near p150 + +\eject + \beginchapter Appendix I. Index + +\maxdepth=4pt +The author has tried to provide as complete an index as possible, so that +people will be able to find things that are tucked away in obscure +corners of this long book. Therefore the index itself is rather long. A +short summary of the simpler aspects of \MF\ appears at the beginning of +Appendix~B; a summary of the standard character classes for tokens +can be found at the end of Chapter~6; a summary of other special things +appears under `tables' below. + +\medskip\ninepoint +Page numbers are \underbar{underlined} in the index when they represent +the definition or the main source of information about whatever is being +indexed. \ (Underlined entries are the most definitive, but not +necessarily the easiest for a beginner to understand.) \ A page number is +given in italics (e.g., `{\it123\/}') when that page contains an instructive +example of how the concept in question might be used. Sometimes both +underlining and italics are appropriate. When an index entry refers to a +page containing a relevant exercise, the answer to that exercise (in +Appendix~A) might divulge further information; an answer page is not +indexed here unless it refers to a topic that isn't included in the +statement of the relevant exercise. + +\smallskip +Index entries for quoted symbols like `T' refer to example programs +that draw the symbols in question. + +\smallskip +Symbolic tokens that are preceded by an asterisk (*) in this index are +primitives of \MF; i.e., they are built in. It may be dangerous to +redefine them. + +\begindoublecolumns +\eightpoint \baselineskip=9.9pt % shooting for 53 lines/page +\parskip=0pt plus .8pt +\newdimen\sqht \sqht=2.4pt % \square +\newbox\astbox \setbox\astbox=\hbox to0pt{\hss\lower1pt\hbox{*}} +\raggedright \tolerance=5000 \hbadness=5000 \parfillskip 0pt plus 3em +\ttglue=.4em +\def\<#1>{\leavevmode\hbox{$\mkern-2mu\langle$#1\/$\rangle$}} +\let\oldcstok=\cstok +\def\cstok{\leavevmode\kern-2pt\oldcstok} +\def\vdots{\vbox{\baselineskip 3pt\kern2pt\hbox{.}\hbox{.}\hbox{.}}} +\def\ddots{\mathinner{\mskip1mu\raise5pt\vbox{\kern2pt\hbox{.}}\mskip2mu + \raise3pt\hbox{.}\mskip2mu\raise1pt\hbox{.}\mskip1mu}} +\def\see{{\sl see\/}~\ignorespaces} +\def\also{\hfil\penalty50\hfilneg{\sl see~also\/}~\ignorespaces} +\let\oldttv=\ttverbatim +\let\+=\relax +\def\sub{\penalty100 \vskip -\parskip \quad} +\def\LaTeX{L\kern -.36em\raise.6ex\hbox{\sixrm A}\kern-.15em\TeX} +\def\MF{{\manual opqrstuq}} +\def\ttverbatim{\oldttv \catcode`\*=\other \catcode`\,=\other + \catcode`\.=\other \catcode`\;=\other \catcode`\@=\other \catcode`\+=\other} +\let\comma=, \let\period=. \let\asterisk=* \let\semicolon=; +\def\backup{\leavevmode\kern-1pt}\catcode`\-=11 +\catcode`\*=\active \def*{\leavevmode\copy\astbox} +\catcode`\,=\active \def,{\eightrm\comma} +\catcode`\;=\active \def;{\/\eightrm\semicolon} +\catcode`\.=\active \def.{\eightrm\period\par\hangindent 2em } +\catcode`\+=\active \def+#1{\ifcat0\noexpand#1$\setbox0\hbox{#1}\dp0=0pt + \underline{\box0}$\let\next=+% + \else\let\next=#1\fi \next} +\catcode`\@=\active \let@=\eightit +\parindent=0pt +\hyphenpenalty=10000 \exhyphenpenalty=10000 +\def\newletter{\medbreak\hangindent 2em} +\hangindent 2em +|6test.mf|, 312--313. +|#| (hash mark), \see sharped dimensions. +`\#', 200--201. +|##| (traced equation), 80--83, 239. +|###| (removed independent variable), 83. +|####| (deduced equation), 81. +*|#@| (prefix of at point), +177, @251. +|%| (percent sign), 43, +50. +*|&| (ampersand), 213--214, \see concatenation. +\sub for preloaded bases, +35, 279. +|'| (apostrophe or prime), @25, @55, @81. +|"| (double-quote mark), +50--+51. +|""| (empty string), +188, 236, 254, @276, @294, @328. +`(', 103--105, 128, 318. +|(| (left parenthesis), 59, +60, 61, @62--@63, 71--73, 165, 210--215. +|((|, 51. +|)| (right parenthesis), 59, +60, 61, @62--@63, 71--73, 165, 210--215. +|))|, 51. +*|[| (left bracket), 9--10, +54, 55, 60, +72, 80, 211--212, 298--299, 324. +|[[|, 61. +|[]| (collective subscript), +56, 177, @273. +|[1]| (progress report), 37, +324. +*|]| (right bracket), 9--10, +54, 55, 60, +72, 80, 211--212, 298--299, 324. +|]]|, 61, @162, +262, 299. +*|{| (left brace), 16--18, 60, +129, 213. +|{{|, 61, 289. +*|}| (right brace), 16--18, 60, +129, 213. +|}}|, 61, 289. +*|+| (plus sign), @62, @63, +72, 80, 211. +*|++| (Pythagorean addition), @+66, @67, 72, 211. +\sub (double edge), 117, 296--297. +|+++| (triple edge), 296--297. +*|+-+| (Pythagorean subtraction), @+66, 72, 211, @238. +*|-| (minus sign), @62, @63, +72, 80, 211, 297. +|--| (straight join), @24--@26, 127--129, @234, +262. +\sub (double edge), 117, 296--297. +|---| (tense join), @107, 127--129, +262. +\sub (triple edge), 296--297. +|->| (macro expansion), 44, 160, 249, 251. +`---' (em dash), 306. +|_| (underline), 49, +51, 173, 265, 270. +|*| (asterisk), 285--286. +\sub as prompt character, 31, 37, 279. +\sub \llap{\char`\*}as times sign, @59, @62--@64, +72, +73, 80, 211--212. +|**|, as command-line prompt, 31--32, 35--40, 187, 269, 279. +\sub as exponentiation sign, @59, @64, 72, @237, @251, +265. +|/| (slash), 328, 329. +\sub \llap{\char`\*}as divided-by sign, @59, @62, @63, +72, 80, 82, 210--211. +\| (vertical line), 117, 297. +*|\| (backslash), +179, @236, @262. +\sub at beginning of command line, @31, @38, 40. +|\\|, +262. +*|<| (less than sign), @64, @65, +170, 210, 237. +*|<=| (less than or equal to), @64, 65, +170, 210, 282. +|<-| (argument value), 160. +*|<>| (unequal to), @64, 65, +170, 210, 282. +\<> (angle brackets), 49--50. +*|=| (equals sign), @5, @6, @23, @64, @75--@85, +88, 97, 165, 167, +170, + 171, 210, 218. +|==|, 292. +*|=:| (ligature replacement), @305, @306, @316, +317. +*\||=:|, @316, +317. +*\||=:>|, +317. +*|=:|\|, +317. +*|=:|\||>|, +317. +*\||=:|\|, +317. +*\||=:|\||>|, +317. +*\||=:|\||>>|, +317. +\leavevmode{\tt \rlap/=} (unequals sign), 282. +*|>| (greater than sign), @64, +170, 210, 237. +|>>| (shown value), 41, 62. +*|>=| (greater than or equal to), @64, 65, +170, 210, 282. +*|,| (comma), 57, 72, 73, 129, 155, 165--167, 171, 317, 318. +|,,|\thinspace, 51. +|.|~(period), 43, +50, 51. +`\char`\.', 306. +*|..| (free join), @7, @15--@19, @24, 127--133, 213. +|...| (bounded join), 18--19, 127, 248, +262. +|...| (truncation of displayed context), 44. +*|;| (semicolon), 155, 169, 171, 172, 187, 217, 223--224, 263, 312. +|;;|\thinspace, 51. +*|:| (colon), 169, 171, 317--319. +*|::| (local label), +317. +*\|\||:| (left boundary label), +317. +*|:=| (gets), @28, @33, 87, +88, 97, @98, 155--156, 159, 165, 167, 171, + 176, 218, 282. +|?|, @41, +42--+43. +|???|, @224, +262. +|!| (exclamation point), 41, 189. +*|@| (at point), +177, @251. +*|@#| (suffix of at point), @176, +177, +178, 251, @273--@274. +\newletter +`a', 192. +`A', 163, 164, 248, 302--303. +|abort|, 312--313. +|abs| (absolute value), @66, 82, @238, +264. +accents, 315, 317. +accuracy, 50, @62--@69, @143, 237. +ad hoc dimensions, 92, @95. +Adams, John, 359. +addition of pictures, 115, @117, @245. +addition of vectors, 9, @68. +*|addto|, +118--+119, @144, @151, @242--@245. +\<addto command>, +220. +|adjust_fit|, 306--308. +{\AE}schylus, 47. +{\AE}sopus, 340. +affine transformations, 247. +algebraic operations, 59--73, 209--215, 230. +Algol, 57, 89. +Alingham, William, 189. +Allen, Fred (= Sullivan, John Florence), 85. +almost digitized character, 296. +*|also|, +118, 220, @242--@245. +|\alternation|, 338. +alternatives, 169. +|always_iff|, +307, @311--@312. +ambiguous points, 150, 198--200, 204. +American Mathematical Society, ii, ix. +anatomy of \MF, 169, 179, 217, 285, +344. +*|and|, @65, +129, +170, 210, 213, 288--289. +Anderson, Izett William, 299. +*|angle|, @29, @67, +72, @107, @135, 211, @238. +angle brackets, 49--50. +angle of pen, 21--22, 26--28, 152, 164. +arccosine, arcsine, arctangent, \see |angle|. +arguments, 159--160, +166--+167, 210, 288. +arithmetic, 59--63. +arrays, 54--57. +ASCII, 49, 188, 281--283, 317. +*|ASCII|, 72, +188, 211. +|aspect_ratio|, 94, 145, 204, 269, 335. +\<assignment>, +88. +assignments, @28, @33, 87--89, @98, 159. +*|at|, +191, 220, @252, @277, @312. +at size, 96, 319. +*|atleast|, 129, +132, 213, @262. +*|autorounding|, 127, 195, +204--+205, @206, 212, @262, @264, 271--272. +axis, 103. +\newletter +`b', 308. +background character, @40, 338--339. +Backus, John Warner, 49. +backwards path, 119. +|badio.mf|, 41, 223. +|barheight|, 96, 161, 199, 302--303. +base file, 34--35, 261, 278--279, +304, 307. +baseline, 75--77, +101. +\<basic path join>, 129, +213. +*|batchmode|, +219, 226. +BCPL strings, 320. +bean-like shape, 15--16, 21--22, 24--25. +beauty, v, 185. +Beethoven, Ludwig van, 185. +|beginchar|, 35, 76, @96, 102--103, 107, 115, 148, 156, 197, 199, 204, + +275, 316. +*|begingroup|, +155--+157, 175, 178, 210--215, 217, @236, @243, @275, @289. +|beginlogochar|, 160, 302. +Bell, Eric Temple, 11. +bell-shaped distribution, 251. +Bernshte{\u\i}n, Serge{\u\i} \thinspace Natanovich, 14. +\sub polynomials, 14, 133, 152, 246, 298--299. +B\'ezier, Pierre Etienne, 14. +Bibby, Duane Robert, i. +Bierce, Ambrose Gwinnett, ix. +|\bigtest|, +341. +Billawala, Nazneen Noorudin, 266, 294. +binary search, 176--177, @293--@294. +black, 270, 332--333. +black-letter, 294. +black/white reversal, 115. +|blacker|, 93--94, 268, +270--+271. +|blankpicture|, 192, +263. +Boole, George, 170. +*|boolean|, 55, +56. +\<boolean expression>, 170, +210. +Boolean expressions, 170, 257. +\<boolean primary>, 170, +210. +\<boolean secondary>, 170, +210. +\<boolean tertiary>, 170, +210. +|bot|, @23, 80, 147, 151, 204, +273. +boundaries, 24--29, 123--125. +*|boundarychar|, 212, 317. +bounded curves, 19, 132. +bounding box, 22, 35, 76, +101--+107, 276, 307, 315. +bounding triangle, 19, 132. +box, \see bounding box. +|bp| (big point), 92, +267, 268. +braces, 16--18, 60, +129, 213. +bracket notation, \see mediation. +brackets, 9--10, +54, 55, 60, +72, 80, 211--212, 298--299, 324. +broad-edge pens, 26--29, 151--152, 162--165. +Bront\"e, Emily Jane, 73. +Bruck, Richard Hubert, 29. +buffer size, 226, 286. +built-up symbols, 318. +Burkitt, William, 99. +Burns, Robert, 299. +|bye|, +278, 279, @306, +321, 324. +|byte|, +264, @275. +\<byte list>, +318. +\newletter +$c$ code, 106, 324. +Camden, William, 51. +Campbell, John Campbell, 359. +|cand|, 288--289. +|CAPSULE|, 239. +|capsule_def|, 264. +capsules, 159, 166, 172, 210, 239, 247, 254, 264. +Carter, Matthew, 207. +Cartesian coordinates, 5--6, 191. +|cc| (cicero), 92, +267, 268. +|ceiling|, @65, 66, 72, +264. +|\centerlargechars|, 340, +341. +chance, 183--185. +|change_width|, @199, +276, +309. +*|char|, 187, +188, 214, @263. +*|charcode|, 106, 210, 212, +220, @275, 324. +*|chardp|, 106, 212, 220, @275, +315--+316, 324. +*|chardx|, 106, 212, 220, @276, +324, @334. +*|chardy|, 212, +324. +*|charexists|, +106, 210, 316, 324. +*|charext|, 106, 212, +220, 316, 324. +*|charht|, 106, 212, 220, @275, +315--+316, 324, @334, @335. +*|charic|, 106, 212, 220, @275, +315--+316, 324. +*|charlist|, @317, +318, 331, @334, @335. +\<charlist command>, +318. +*|charwd|, 106, 212, 220, @275, +315--+316, 324, @334, @335. +|cheapo|, 91--93, 99, 278--279, 332--333. +check sums, 320, 324, +325. +Chinese characters, 3, 106, 324. +circles, 123--124, 148. +|clear_pen_memory|, 147, +273, @278, @310. +|clearit|, 115, @242, @275, +277, 295. +|clearpen|, +272, @275. +|clearxy|, @275, +277. +|cm| (centimeter), @18, 92, +267, 268. +|cm.base|, 35, 279, 311. +|cmchar|, @306, +307, 312--313. +|cmex10|, 317--318. +|cmmf|, 35, 279. +|cmr9|, 203, 320. +|cmr10|, 101, 305--306, 319. +|cmr10.mf|, 305. +|cmsl10|, 101. +|cmtt10|, 306. +\<code>, +317. +codes, 281--283. +Colburn, Dorothy, 107. +collective subscripts, 56, 177. +\<command>, +217. +command line, 38, 187, 269, 277, 301. +commands, 155, 217--220, 230, 321. +comments, 43, 50--51. +commutativity, 247. +comparison, @65--@66, 80, 170. +compass directions, 26, 119, 206--207, 228--229. +complex numbers, 69. +\<compound>, +217. +compound statement, +155, 217. +Computer Modern, 35, 103--105, 203, 206, 279, 304--313. +concatenation, of paths, @70--@71, @123, 127, 129, +130, @245. +\sub of strings, @69, 84--85, +187, @278, @286, @312. +\<condition>, +169. +conditional and/or, 288--289. +conditions, 169--171, 179, 219, 259. +constants, 59, @62, 263--264. +contents of this manual, table, x--xi. +*|contour|, +118--+119, 220. +control points, @13--@19, 70--71, 133, 229. +*|controls|, @19, 70--71, +129--+130, 133, @152, 213. +\<controls>, 129, +213. +conversion to pixel units, 259, +268. +convex polygons, @119, 147, 297--298. +Conway, John Horton, 121. +coordinates, 5--11, 23, 109, 191, 193. +|cor|, 288--289. +corner pixels, 93--94. +*|cosd|, @67, 72, 211. +cosines, 67, 69. +counterclockwise, 111, 119, 229, 255. +|counterclockwise|, +264. +Cowper, William, 51. +|craziness|, 184--185. +crispness, 103--104. +cube roots, 177. +cubes, 113. +*|cull|, 118, +120, @151, @243--@245. +\<cull command>, 118, +220. +|culldraw|, @271, +272. +culling, 113, 120, @151, @242--@245, 296. +|cullit|, @113, 120, @242, @243, +277. +Cundall, Frank, 299. +*|curl|, @17, +128--+131, 213, 234. +|currentbreadth|, 310--311. +|currentnull|, 295. +|currentpen|, 118, 147, 150, 204, +271--+272. +|currentpicture|, 114, @115, @116, 118, 120, 191, +271--+272, 295. +|currenttransform|, 94, @145, 204, +269, 271, 301, 310. +|currentwindow|, 192, @312. +curves, 13--19, \see paths. +cusps, 136. +|cutdraw|, @151, +271--+272. +|cutoff|, @150, +272. +*|cycle|, @15, @16, @24--@28, @69, +129--+131, 170, 171, 210, 213. +\newletter +|d|, 35, @76, 102, 204, +275. +`d', 294. +da Vinci, Leonardo, 19. +dangerous bend, vii, 11, 106--107, 115, 143. +Darwin, Charles Robert, 57. +data structures, 53--57. +Davis, Philip Jacob, 343. +*|day|, +212, 218, 323. +|dd| (didot point), 92, +267, 268. +de Casteljau, Paul de Faget, 14. +debugging tricks, 229--231, 286. +*|decimal|, +187--+188, 214. +\<decimal digit>, +50. +decimal point, 50--51. +decimal representation, 188. +\<declaration>, 56, +171. +\<declaration list>, +57. +declarations, 56--57. +declarative versus imperative, 87. +\<declared suffix>, +57. +\<declared variable>, +57, 175. +|decr|, +266. +*|def|, @36, @159--@162, +165--+167. +|default_wt_|, 271--272. +|define_blacker_pixels|, @33, 92--93, @106, +268, 302. +|define_corrected_pixels|, 93, 197, +268, 302. +|define_good_x_pixels|, 199, +268, 302. +|define_good_y_pixels|, 199, +268, 302. +|define_horizontal_corrected_pixels|, @204, +268, 302. +|define_pixels|, @33, 92, @106, 199, +268, 302. +|define_whole_blacker_pixels|, 202, +268. +|define_whole_pixels|, 199, +268, 302. +|define_whole_vertical_blacker_pixels|, +268. +|define_whole_vertical_pixels|, @204, +268, 302. +\<definition>, +165. +\<definition heading>, +165. +definitions, 159--167, 175--180. +deleting tokens, 42--43, 225. +\<delimited parameters>, +165. +delimiters, 61, 167, 210, 254, 288--289. +*|delimiters|, 61, 180, 210, +218, @221, @262, @296, @299, @313. +\<delimiters command>, +218. +dependent variables, +81--+83, 88, 224. +depth, 101. +Derek, Bo, 287. +Descartes, Ren\'e, 6, 11, 19. +design size, 96, +319--+320, 324, 329. +*|designsize|, 212, 320. +device drivers, 323, 325. +diagnostic aids, 229--231, 259, 286. +diamond-shaped nib, 148--149, 297. +Dickens, Charles John Huffam, 145. +difference of pictures, 115, @244. +digestion process, 179, 217--221. +\<digit string>, +50. +digitization, 111, 149, 195--207, 230. +|\digits|, 339. +dimensions, 92, +267. +|dir|, @18, @67, @68, @83--@84, @135, @163--@164, 175, @233, +264. +|direction|, @69, 70, @135, @235, +265. +\<direction specifier>, 129, +213. +|directionpoint|, @135, +265. +*|directiontime|, @135, @+136, 211, 245, 265, @295. +|dishing|, 152, 164. +*|display|, +191--+192, 220. +\<display command>, +220. +|displaying|, 269, 276, 278. +distance, 76, 84. +|ditto|, @187, +263. +|div|, +265. +division, @59, @62, @63, 80, 82. +\sub of numeric tokens, 61, 73. +Dopping, Olle, 181. +|dot|, 306, 311. +dot product, 69. +|dotprod|, @69, 178, @238, 265. +|dotsize|, 332, 334. +double-quote mark, 50--51, 187. +*|doublepath|, 118, +119, @151, 220. +doubly filled pixels, 110--112. +|down|, @32, +263. +|downto|, 172, +262. +|draw|, @7, @15--@19, 21, 112, 118--120, 145, 147, 150, 198, 230, +271, 295. +\sub one point, 22, 150, 200, 253. +|drawdot|, @31, 113, 147, +150, 234, +271. +Drayton, Michael, 279. +drift, 102, 106. +driver files, 304--306. +*|dropping|, 118, +120, 220. +D\"urer, Albrecht, 13, 19. +*|dump|, 217, +221, 262, @279, @311. +|.dvi|, 32, 40, 103, 106, 323, 327, 328. +\newletter +|e|, 27--29, 273. +`E', 96--97, 204, 302--303. +edge structure, 116--117, 296--297. +edges, 116. +editing, 46. +efficiency, 39, 99, 141, 144, 147, 228, 230, 234, 244, 264, 265, 277, + 291, 297, 298. +El Palo Alto, 124--126, 139, 228--229. +ellipses, 123, 126. +Ellis, Henry Havelock, 11. +*|else|, +169--+170, 179. +*|elseif|, +169--+170, 179. +em dash, 306. +emergency stops, 226. +empty option in {\bf for\/} list, 171, @299. +empty statement, 155, 217. +empty text argument, 299. +*|end|, @31, @37, 155, 167, 217, 221, 226, 278, 287, 305, @321. +end of a file, 287. +|endchar|, @36, 102, 156, 191, +276, 309, 311, 329. +*|enddef|, @94, @159--@164, 165, @175--@178. +*|endfor|, @18, @39, +171--+172, @173, 250, @290. +|ENDFOR|, 45, 286, 290. +*|endgroup|, +155--+157, 167, 175, 178, 210--215, 217, @236, @243, + @276, @289, @290. +ending character, @40, 338--339. +*|endinput|, +179, @287--@288. +endpoints, 128, 150--151. +|ENE|, 119, 206--207, 228. +enormous number, 63, 236. +envelopes, 118--119, 150, 230. +|eps|, 93, @199--@200, 229, +263, @310--@311. +|epsilon|, @62--@69, 115, @135, 152, 229, +263. +equality test, general, 292. +equality versus equation, 171. +|equally_spaced|, 290. +\<equation>, +88. +equations, @5, @6, @23, @75--@85, 88, @141, 171. +\sub nonlinear, 84--85, 176--177, @292--@294. +equilateral triangle, 25, 203. +|erase|, @113, 120, 167, +271, 272. +*|errhelp|, +189, 219, @294. +*|errmessage|, @178, +189, 219, @294. +error messages, 41--46, 223--228. +*|errorstopmode|, +219, 227, @313. +|ESE|, 206--207, 228--229. +*|everyjob|, 180, +219. +\<everyjob command>, +219. +Evetts, Leonard Charles, 153. +exercises, viii, 5--231. +\<exit clause>, +171. +*|exitif|, 171, +173, @176, 179, @262. +|exitunless|, 173, +262. +expandable tokens, 179, 230. +*|expandafter|, +179, 180, @270, @286--@290, @313. +expansion process, +179--+180, @285--@291. +exponential, \see |mexp|. +*|expr|, @160, @162, 165, @166, 167, @176, 210. +|(EXPR|$_n$|)|, 44, 160, 249, 251. +|expr.mf|, +61, 62--71, 116--117, 132, 135--137, 142--143, 150, 173. +\<expression>, 167, +209. +expressions, 59--73, 209--215. +*|extensible|, 318. +\<extensible command>, +318. +external tags, 55, 218. +|extra_beginchar|, 275--276, @278. +|extra_endchar|, 276, @277, @309. +|extra_setup|, 269, @270, @278. +|!| |Extra| |tokens| |will| |be| |flushed|, 43--44, 224--225. +\newletter +`F', 97, 204, 302--303. +*|false|, 55, @64--@65, 170, 210. +faster operation, 39, 99, 141, 144, 147, 228, 230, 234, 244, 264, 265, 277, + 291, 297, 298. +|Fatal| |base| |file| |error|, 226. +fatter pens, 297--298. +*|fi|, +169--+170, 179. +|!| |File| |ended...|, 287. +file names, 36, 39, +180, 324, 329. +\<filename>, 179--180. +|fill|, @24--@27, 109--112, @116, 118--121, 145, 167, +271, 295. +|filldraw|, @103--@105, 112--113, 118--119, 147, 148, @152, @164, + 230, +271, @306, 310. +*|fillin|, +93--+94, 150, 212, 247, 268, 278--279. +|fine|, 103--104, 306--307, 310--311. +|fine.lft|, 311. +|fix_units|, +267. +flat spots, 196--197. +|flex|, @124--@125, 127, @152, 173, 228--229, +267. +*|floor|, @65, 66, 72, 83, 211, @253. +flushing, 43--44, 219, 224--225. +Font, Fray Pedro, 139, 231. +\<font metric command>, +321. +font metric information, 39, 220, 315--321. +|font_coding_scheme|, +277, @303, 304, +320--+321. +|font_extra_space|, +277, 319. +|font_identifier|, +277, @303, 304, @305, 320, @332--@333. +|font_normal_shrink|, @97, +276, @305, 319. +|font_normal_space|, @97, +276, @305, 319, 332. +|font_normal_stretch|, @97, +276, @305, 319. +|font_quad|, @97, +277, 308, 319, 332. +|font_setup|, 203, 305, 309--312. +|font_size|, @95, 96, +276. +|font_slant|, +276, @305, 319, 331, @335--@336. +|font_x_height|, +277, 319, 332. +*|fontdimen|, @276--@277, +318--+319, 331--332, @335. +\<fontdimen command>, +318. +*|fontmaking|, 54, @94, 211, @270, +315. +|\fontname|, 342. +*|for|, @18, @39, @113, +171--+173, 179, 228, @285--@291, @299. +\<for list>, +171, 299. +forbidden tokens, +173, 218--219, 286. +*|forever|, @61, +171--+173, @176, 179. +*|forsuffixes|, +171--+172. +{\sevenrm FORTRAN} language, 237. +\<four codes>, +318. +four-point method for curves, 13--14, 133. +Fournier, Simon Pierre, 321. % Harry Carter says S.P. is right, not P.S.! +fractions, 61, @62--@63, +72, 73. +*|from| 191, 220, @252, @277, @312. +|fullcircle|, @114, 123--124, 126, @135--@137, +263, @266. +Fulton, A\period\ G\period, 157. +function values by interpolation, 294--295. +\<future pen primary>, 148, +214. +\<future pen secondary>, 148, +214. +future pens, 148--149, 170, 249, 264, 298. +\newletter +Galsworthy, John, 215. +Gardner, Martin, 126. +|generate|, 305, 307, 311, 313. +|gf|, 32, 241, 295, 323--325. +|gfcorners|, 277, +278, 327. +|GFtoDVI|, 32, 37, 187, 327--336. +|gimme|, 61--62. +Giotto de Bondone, 139. +|gobble|, @167, +262, @289. +|gobbled|, +262, @289--@290. +golden ratio, 11. +|good.bot|, 204, +273. +|good.lft|, 204, +273. +|good.rt|, 204, +273. +|good.top|, 204, +273. +|good.x|, @198, @268, +273. +|good.y|, @198, 204, @268, +273. +Goudy, Frederic William, 19. +grammatical rules, 49--50. +*|granularity|, +205, 212, 262, 310. +graph paper, 5, 102, 109, 188. +|gray|, 332. +gray fonts, 327, 330--335. +|grayf.mf|, 332--335. +|grayfont|, 270, +275, 323, 329. +|grayfontarea|, 329. +|grayfontat|, 329. +greater than or equal to, 65. +greatest integer function, \see floor. +grid, 5, 109, 275. +Grimm, Jakob Ludwig Karl, 73. +Grimm, Wilhelm Karl, 73. +group delimiters, 289. +group expressions, 157, 160. +groups, 155--157, 167. +Gu Guoan, 3. +\newletter +|h|, @22--@25, 35--36, @76--@78, 102, 204, +275. +`H', 163, 165. +Haggard, Sir Henry Rider, 107. +hairlines, 104--105. +|halfcircle|, 123, @136, +263. +hamburgefonstiv, 341. +hand tuning, 195. +*|headerbyte|, 318, +320--+321. +\<headerbyte command>, +318. +hearts, 134. +height, 101. +Hein, Piet, 126, 231. +help messages, 43--45, 189, 224--225. +Herbin, Auguste, 3. +Hersh, Reuben, 343. +*|hex|, +188, 211, 281. +hex symbol, 7--8, 28--29. +hexadecimal notation, 188. +|hide|, @116, @143, 167, @173, @227, +262. +hierarchy of operators, 60--61, 71--73, 137, 209, 289. +histogram, 251. +Hobby, John Douglas, viii, 3, 130, 131, 149, 252, 285. +holes, 110. +Holland, Philemon, 51. +Homerus, 51. +homogeneous transforms, 247. +*|hppp|, 92--93, 212, 267, 268, 324. +|hround|, +264, @268. +Hult\'en, Karl Gunnar Pontus, 3. +\newletter +`I', 28, 32, 39, 163, 164. +|!| |I| |can't| |go| |on|, 226. +IBM Corporation, ix. +|identity|, @141--@145, 215, +263. +*|if|, +169--+170, 179, 289. +|iff|, @306, +307, 311. +|imagerules|, 277, +278. +imperative versus declarative, 87. +impossible cube, 113. +|in| (inch), 92, +267, 268. +inaccessible token, 286. +incomplete string, 50--51. +inconsistent equations, 82, 313. +|incr|, @39, 176--177, +266. +independent variables, +81--+83, 88, 224, 226. +infinite loops, 172, 226--227. +|infinity|, @62--@69, +263, @266. +inflection points, 18--19. +|INIMF|, 221, 262, 279. +|\init|, 342. +\<initial value>, +171. +*|inner|, 180, +218--+219, 286--287, @307, @321. +|inorder|, 290. +*|input|, +179, 180, @269, @287--@288, 324. +input stack size, 226, 287. +inserting text online, 42, 45, 61, 188, 223--225. +integers, 65--66. +|interact|, 230, +262. +interacting with \MF, 42--45, 61, 188--189, 191--193, 219, 223--225. +*|interim|, +155--+156, 230, @243, @244, @271, @272. +\<interim command>, 155, +218. +internal quantities, 54--55, 88, 218, 262. +\sub table, 211--212. +\<internal quantity>, 156, 218, 265. +|interpath|, 134, +267. +interpolation, 2, 134, 294--295. +interrupting \MF, 219, 227--228, 313. +intersection, of lines, 84. +\sub of paths, 136--137. +\sub of pictures, 120. +|intersectionpoint|, @107, @137, @138, 178, +265. +*|intersectiontimes|, +136, @178, 213, @265, @294, @298. +|inverse|, @143, +264. +inverse video, 115, 118. +*|inwindow|, +191, @277. +Io, 33, 40, 47. +\<is>, 165, 171, +218. +Isis, 40. +|!| |Isolated expression|, 223. +isolated math characters, 316, 319. +|italcorr|, @103--@105, +275, @303, @306, @316. +italic corrections, 102, 105, 275, 276, 304, 315--316, 319. +italic type, 55, 206, 341. +\newletter +jaggies, 201. +*|jobname|, +187, 214, 324. +Johnson, Samuel, 167. +Johnston, Edward, 29. +|join_radius|, 266. +jokes, viii, 231. +Journal of Algorithms, 137--139. +|jut|, 162, 308. +\newletter +Kafka, Franz, 340. +Kandinski\u\i, Vasili\u\i\ Vasil'evich, 3. +\<keep or drop>, +118, 120. +*|keeping|, 118, +120, 220. +|keepit|, 295. +*|kern|, @97, @316, +317. +kerning, 97, 316--317. +|killtext|, +262, @272. +knife, 24. +*|known|, @65, 79--82, 143, +170, 210. +Knuth, Donald Ervin, i, ii, ix, 3, 134, 192, 206, 255, 282, 291, 304, 308, 345, + 361. +Knuth, Nancy Jill Carter, ix, 134, 137. +\newletter +|l|, 308--309. +La Rochefoucauld, Fran\c cois VI, 313. +\<label>, +317. +\<labeled code>, +318. +|labelfont|, +275, 329. +|labelfontarea|, 329. +|labelfontat|, 329. +|labels|, @107, +274--+275, 327--328. +labels in font metric information, 317--318. +labels on "proofmode" output, 37, 187, 274--275. +|labels.top|, 328. +Lam\'e, Gabriel, 126. +|large_pixels|, 332. +|lcode_|, 274, 328. +le B\'e, Pierre, 207. +least integer function, \see ceiling. +Leban, Bruce Philip, 242, 243, 270, 295. +|left|, @16, +263. +left-handed dangerous bend, 143. +|leftstemloc|, 96, 199, 302. +*|length|, @66, @69, 72, 211, 238. +less than or equal to, 65. +*|let|, 53, 180, +218, @287--@289, @299, @311. +\<let command>, +218. +|letter_fit|, 307--308. +\<leveldef>, +178. +\<leveldef heading>, 165, +178. +|lft|, @23, @77, 80, 147, 151, +273. +lies, viii, 231. +Life, 121. +\<ligature op>, +317. +ligatures, 305--306, 315--317. +|lightweight|, 332. +*|ligtable|, @97, @305, +316--+317. +\<ligtable command>, +317. +\<ligtable program>, +317. +\<ligtable step>, +317. +\<limit value>, +171. +line, point to be on, 83--84. +linear dependencies, 82--83. +linear forms, 64, 82. +Linn\'e, Carl von (= Linn\ae us, Carolus), 325. +|local.mf|, 278--279, 321. +|localfont|, 39, 271, 278, @279. +locations of characters within a font, 106--107, 281--283, 320. +Lockyer, Sir Joseph Norman, 57. +log file, 42, 46, 62, 230, 295--297. +logarithm, \see |mlog|. +|loggingall|, 230, +263. +logo of \MF, ii, 22--23, 95--99, 160--161, 184--185, 199--200, 204, 301--304. +|logo.mf|, 95--98, 199, 302--303. +logos, {\it i}, 97, @114, @137--@139. +|logo10.mf|, 95, 287, 301, 304. +\<loop>, +171. +\<loop header>, +171. +loop text, 171--172, 219, 286. +loops, 169, 179, 226--227, 259, 290--291, 299. +low-resolution proofs, 99, 327. +|\lowers|, 339. +|lowres|, 196, 201, 230, +270. +|lowres_fix|, 203, +268, 310. +|luxo|, 91--94, 99, 195, 278--279. +\newletter +`M', 23, 97, 200, 302--303. +macros, @36--@37, 53, 114, 159--167, 175--179, 285--299. +|mag|, @39, +91--+93, 98, 169, 230, 269, 278, 333--334. +magnets, 60--61. +magnification, 38--40, 91--99. +|magstep|, 98, +270. +|makebox|, 270, +276, 309. +|makegrid|, +275. +|makelabel|, +274, 328. +*|makepath|, +150, 213, 247, @298. +*|makepen|, +147--+148, 214, @264. +|maketicks|, 270, +276, 309. +mastication, 169, 179, 285. +|\math|, 341. +Matthew, Saint, 173. +|max|, @65, +266, 290--291. +maximum, 65. +mediation, 9--11, 14, @63, @68, 72, 80, 133, 298--299. +memory usage, 226--227. +*|message|, @61, +189, @262. +\<message command>, 189, +219. +\<message op>, 189, +219. +meta-design, 1--3, 103--105, 294. +meta-font, 1--3, 98, 192, 301--304. +meta-ness, 3, 301. +\MF, the logo, ii, 22--23, 95--99, 160--161, 184--185, 199--200, + 204, 301--304. +\sub the name, 1--3. +|METAFONT| |capacity| |exceeded|, 226--227. +\MF\kern1pt79, viii. +*|mexp|, @+67, 72, 211, @265, @270. +|mf|, 31, 35. +|.mf|, 36. +|mfput|, 31--32, 187, 324. +|MFT|, 262. +midpoints, 9, 13. +Mies van der Rohe, Ludwig, 185. +|min|, @65, +266, 290--291. +minimum, 65. +Mirk, John, 313. +|!| |Missing| |`)'| |has| |been| |inserted|, 254. +misspelling, 45, 224. +|\mixture|, @40, +338. +*|mlog|, @+67, 72, 211, @265. +|mm| (millimeter), @76, 91--92, +267, 268. +M\"obius, August Ferdinand, 114. +mock curvature, 131. +|mod|, @66, +265. +|mode|, @38--@39, @75, 91--94, 269, 278. +\<mode command>, +219. +|mode_def|, 94, 189, @+270, @278--@279. +|mode_name|, 269. +|mode_setup|, @32--@34, 75, 76, 91--94, @96, 115, 169, +269, 278, @304, @305, + 329. +|mono_charwd|, 308. +|monospace|, 305--308. +*|month|, +212, 323. +More, Sir Thomas, 215. +Morison, Stanley, ix, 283. +mouth, 169, 179, 285. +Moxon, Joseph, 325. +Mulford, Clarence Edward, 89. +multiplication, @59, @62--@64, 69, 79--80, 82. +\sub of vector by scalar, 9. +music, 183, 185. +\newletter +`n', 201--203. +`N', 184--185, 303. +|\names|, 339. +National Science Foundation, ix. +Naur, Peter, 49, 89. +negation, of pictures, 115. +\sub of vectors, 9. +|new_window|, 193. +*|newinternal|, 180, +218. +\<newinternal command>, +218. +nice tangent points, 177. +|NNE|, 119, 228. +|NNW|, 26, 119, 228--229. +|nodisplays|, 277, +278. +|nodot|, 274, 328. +nonlinear equations, 84--85, 176--177, @292--@294. +nonsquare pixels, 94, 145, 204. +*|nonstopmode|, +219, 226. +*|normaldeviate|, @68, 72, @183--@185, 210. +*|not|, @65, +170, 210. +|notransforms|, 277, +278. +*|nullpen|, +148, 214, @272. +*|nullpicture|, +115, 192, 214, @272, @277. +*|numeric|, 55, +56, @65, 88. +\<numeric atom>, 72, +210. +\<numeric expression>, 72, +211. +numeric expressions, 72--73, 257. +\<numeric list>, +318. +\<numeric operator>, 72, +211. +\<numeric primary>, 72, +211. +\<numeric secondary>, 72, 178, +211. +\<numeric tertiary>, 72, +211. +\<numeric token>, +50, 236. +\<numeric token primary>, 72, +210. +numeric tokens, 49--50, 166. +\sub maximum value, 50. +\sub rounded fractional values, 50. +|numeric_pickup_|, +272, 310. +*|numspecial|, 220, @274, +323--+324, @327--@329. +|numtok|, @+274. +\newletter +|o|, @23, @34, +93, 197, 200, 204, 302. +`o', 203. +`O', 32--37, 199, 303. +|o_correction|, 93--94, 268. +*|oct|, +188, 211, 281. +octal notation, 188. +octants, 119, 206--207, 228--230. +*|odd|, +170, 210, 250. +*|of|, 73, 129, 165--167, 187, 211--214. +of-the-way function, \see mediation. +off by $x$, 82. +Office of Naval Research, ix. +|offset|, 275, 379. +|!| |OK|, 219, 224. +|\omitaccents|, 340. +one-point {\bf draw}, 22, 150, 200, 253. +online interaction, 42--45, 61, 188--189, 191--193, 219, 223--225. +|openit|, +277, 312. +*|openwindow|, +191--+193, 220, @277, @312--@313. +\<openwindow command>, 191, +220. +operands, 59. +operators, 59, 230. +\<optional skip>, +317. +*|or|, @65, +170, 210, 237, 288--289. +order of operations, 60--61, 137, 247, 289. +oriental characters, 3, 106, 324. +|origin|, @77--@78, @243, @251, +263. +ornament, 144--145. +Orwell, George (= Blair, Eric Arthur), 85. +*|outer|, 180, +218--+219, 221, 286--287, @307, @321. +outlines, 121. +output of \MF, 39, 42, 315--325. +|overdraw|, 114, 243. +overflow labels, 37, 328. +overlays, 295. +overshoot, 23, 34, 93, 197, 200, 204. +\newletter +`P', 207. +Paget, Francis Edward, 279. +*|pair|, 55, +56, 65. +\<pair expression>, 73, +213. +pair expressions, 73, 171, 258. +\<pair part>, +211. +\<pair primary>, 73, +212. +\<pair secondary>, 73, +212. +\<pair tertiary>, 73, +213. +Palais, Richard Sheldon, ii. +parallel lines, 84. +parallelogram, 293--294. +\<parameter>, +178. +parameter files, 301, 304. +\<parameter heading>, +165. +\<parameter tokens>, +165. +\<parameter type>, +165. +parameters, v, 1--3. +\sub to fonts, 95, 103--104, 305. +\sub to macros, 159--167, 175--178. +parentheses, 51, 59, +60, 61, 71, 128, 210--215, 247. +Pascal language, 54. +*|path|, 55, +56, 171. +\<path expression>, 129, +213. +path expressions, 129--134, 258. +\<path join>, 129--130, 171, +213. +\<path primary>, 129, +213. +\<path secondary>, 129, +213. +\<path tertiary>, 129, +213. +paths, 13--19, 123--139. +*|pausing|, 211, +231. +|pc| (pica), 92, +267, 268. +pels, \see pixels. +*|pen|, 55, +56, @65, 170. +\<pen expression>, 147, 148, +214. +pen expressions, 147--148, 258, 298. +\<pen primary>, 148, +214. +\<pen secondary>, 148, +214. +\<pen tertiary>, 148, +214. +|pen_bot|, 151, +272. +|pen_lft|, 151, +272. +|pen_rt|, 151, +272. +|pen_top|, 151, +272. +*|pencircle|, @21--@23, @28, @29, +147--+149, @150--@152, 198, 200, 214. +|penlabels|, 36, +274. +*|penoffset|, +150, 212, 230, @298. +|penpos|, @26--@29, 37, @103, @162, +273, 310. +|penrazor|, @107, @112, 147, 150, +264, 297. +pens, 21--39, 147--152, 297--298. +|penspeck|, +264, @271. +|pensquare|, 147, 152, +264, 275. +|penstroke|, 27--29, @138, +273. +perpendicular, 29, 69, 84, 235. +|pickup|, @21--@23, 145, 147, +272. +*|picture|, 55, +56, @114. +\<picture command>, 118, +220. +\<picture expression>, 115, +214. +picture expressions, 115, 258. +\sub transformation of, 144, 297. +\<picture primary>, 115, +214. +\<picture secondary>, 115, +214. +\<picture tertiary>, 115, +214. +pictures, 109--121. +pimples, 196--197, 204. +|pix_ht|, 332, @333. +|pix_picture|, 332, @333. +|pix_wd|, 332, @333. +pixels, 5, 109, 259, 324. +|pixels_per_inch|, 267, 268. +plain \MF\ base, 34, +257--279. +|plain.mf|, 261--278. +\<plus or minus>, 72, +211. +*|point|, @69--@70, 73, @114, +133, 212, @267. +polygonal path, 24, 297. +pool size, 226, 286. +|pos|, 310. +*|postcontrol|, +134, 212, @267. +|posttension|, 136. +precedence, 60--61, 71--73, 137, 289. +*|precontrol|, +134, 212, @267. +|pretension|, 136. +pretty-printed \MF\ programs, 262. +*|primary|, 165, 167. +\<primary>, 71, 170, +209. +*|primarydef|, 166, @+178. +prime numbers, 173. +primitives, 53, 209, 345. +private tokens, 173, 265, 270. +product, @59, @62--@64, 69, 79--80, 82. +\sub of vector by scalar, 9. +\<program>, 155, +217. +program files, 304, 306. +\<progression>, +171. +|proof| mode, 92, 93, 104, +270, 327. +*|proofing|, @94, 187, 211, 220, @270, 274, +323--+324, 327. +|proofoffset|, +275, 329. +|proofrule|, +274, 323, 328--329. +|proofrulethickness|, +275, 329. +proofsheets, 37, 261, 327--343. +\<protection command>, +218. +pseudo-driver files, 311--313. +|pt| (printer's point), @21--@23, @33, 91--92, +267, 268. +|\punct|, 339. +punctuation marks, 306. +Pythagorean addition, @+66, @67, 72, 211. +\<Pythagorean plus or minus>, 72, +211. +Pythagorean subtraction, @+66, 72, 211, @238. +\newletter +`Q', 207. +|quartercircle|, 123, +263. +Quick, Jonathan Horatio, 54, 137. +*|quote|, +166, 172, @270, @286, @312. +\newletter +|r|, 308--309. +`R', 207. +|\raggedright|, 338. +Ramshaw, Lyle Harold, 320. +random numbers, 183--185. +*|randomseed|, 185, 218. +\<randomseed command>, +218. +|range|, @107, @138, @200, +274. +raster, 5, 91, 109, 195. +*|readstring|, @61, +187--+188, 214. +recipes, 2. +recursion, 227. +redundant equations, 82. +reference point, 77, +101. +|reflectedabout|, @138, 141, @142, 160, +266. +reinitializing a variable, 88, 157. +\<relation>, 170, +210. +relations, @64--@65, 170--171. +|relax|, @31, +262, @307. +remainder, 66. +|rep|, 332, 335. +replacement text, 159, +166, 219. +resolution, 6, 38--39, 91--99, 116. +\<return> key, 31. +*|reverse|, 129, +132, 213. +reverse video, 115, 118. +Reynolds, Lloyd Jay, 153. +|right|, @26, @68, +263. +\<right-hand side>, +88, 171. +*|rotated|, @21--@22, @25, 27, 44, @68, 73, @107, @114, @117, +141, 212, @238. +|rotatedabout|, +266. +|rotatedaround|, @138, 141, @142, @144, 159--160, +266. +|round|, @66, 196, 202, +264, @273. +rounding, 34--35, 50, 195--207, 308. +|rt|, @23, @77, 80, @103, 147, 151, +273. +|rtest.mf|, 311. +|rule|, 234, 328. +|rulepen|, +274, 275. +rules on proofsheets, 328--329. +|rulethickness|, 275, 329. +runaway, 287. +Running, Theodore Rudolph, 47. +Ruskin, John, 139. +\newletter +`S', 40, 114. +|safefill|, 121. +|\sample|, 341. +sans-serif, 105, 305, 308. +*|save|, +155--+156, @160, 173, @178, 180, 218, @236, @244, @296, 299. +\<save command>, 155, +218. +|savepen|, @96, 147, +272, @310. +\<scalar multiplication operator>, 72, +211. +*|scaled|, @21--@23, @68, 73, +141, 212, 244, 291. +*|scantokens|, @61, +179, @180, 189, 251, @269, @270, @286--@288, @313. +scatter plots, 183. +\<screen coordinates>, 191, +220. +\<screen place>, 191, +220. +|screen_cols|, 193, 277, @278. +|screen_rows|, 277, @278. +|screenchars|, 191, +277. +|screenrule|, 274, 278. +|screenstrokes|, 191, +277. +*|scrollmode|, @61, +219, @313. +*|secondary|, 165, 167. +\<secondary>, 71, +209. +*|secondarydef|, 166, @178. +selective complement, 120. +semantics, 50. +semicolons, 155, 169, 171, 172, 187, 217, 223--224, 263, 312. +|serif_fit|, 308. +serifs, 152, 162--165, 308. +Serlio, Sebastiano, 19. +|setu_|, 266, 291. +Shakespeare, William, 173, 255, 343. +sharped dimensions, @32--@35, 91--99, 102--103, 268, 315. +|shiftdef|, 311. +*|shifted|, @68, 73, @117, +141, 213. +|shipit|, @31, @276, +277, 295. +*|shipout|, 106, 210, +220, @277, @295, 316, 324, 329. +\<shipout command>, +220. +*|show|, 142, +219, @227, 230, @250, 296. +\<show command>, +219. +*|showdependencies|, 81, 83, +219, @262. +|showit|, @31, 191, @276, +277, 295. +*|showstats|, +219. +*|showstopping|, 211, @227, 230, @262. +*|showtoken|, 180, +219, @221. +*|showvariable|, 175, 177, 180, +219. +|shrink_fit|, 308--310. +shrinkability, 319. +shuffled binary numbers, 137. +sidebearings, 10, 34--35, 307--308. +{\sevenrm SIMULA67} language, 175. +*|sind|, @67, 72, 211. +*|skipto|, @316, +317. +skyline, 251. +|slant|, 105, 206, 301--303, 310, 319. +slant fonts, 329, 335--336. +*|slanted|, @68, 73, 105, +141, 213. +|slantfont|, +275, 329. +|slantfontarea|, 329. +|slantfontat|, 329. +|smode|, 269. +|smoke| mode, 38, 75, 93, +270, 327. +*|smoothing|, 55, 195, 205--206, 212, @262. +|softjoin|, 262, +266. +|solve|, 176--177, +267, @292--@294. +|(some| |charht| |values...)|, 316. +Southall, Richard Francis, 176. +spaces, 43, 50, 236. +sparks, +53--+55, 156, 175, 215, 219, 289. +*|special|, 220, @240--@241, @274, +323--+324, @327--@329. +\<special command>, +220. +special-purpose macros, 160, 248. +*|sqrt|, @59, @64, 72, 211. +square roots, 66, \also |sqrt|. +|SSE|, 206--207, 228--229. +|SSW|, 119, 228--229. +stack positions, 227. +Stanford, Amasa Leland, 340. +Stanford, Jane Elizabeth Lathrop, 340. +Stanford University, 125, 340. +star, 114. +|\startfont|, +337, 338, @342. +starting a job, 39, 95, 259, 277. +starting character, @40, 338--339. +\<statement>, 155, 171, +217. +\<statement list>, 155, +217. +statements, 155, 217--221. +\sub summary, 260--261. +stems, 201--203. +*|step|, @18, 171. +\<step size>, +171. +stomach, 169, 217, 285. +|stop|, +262, @311--@312. +stopping \MF, \see |end|. +*|str|, +187--+188, 214, @250, @251. +strange paths, 110--111, 119, 121, 136, 152, 228--229. +Stravinski{\u\i}, Igor' F\"edorovich, 193. +stretchability, 319. +Strindberg, Johan August, 185. +*|string|, 55, +56, 69. +\<string expression>, 73, 187, +214. +string expressions, 187--189, 258, 286. +\<string primary>, 187, +214. +\<string secondary>, 187, +214. +\<string tertiary>, 187, +214. +string tokens, 49--51. +|stroke|, @306, 310. +*|subpath|, @70, @71, 114, 129, +133, 134, 188, 213, @298. +subroutines, \see macros. +\<subscript>, +54. +subscripts, 54--57. +*|substring|, @69, 187, +188, 214, @320. +subtraction, of pictures, 115, @244. +\sub of vectors, 9. +Suetonius Tranquillus, Gaius, 181. +*|suffix|, @161, 165, @176. +\<suffix>, +54, 161, 176, 188. +\<suffix list>, +171, 236. +|(SUFFIX|$_n$|)|, 44, 251. +sum, of pictures, 115, @117, @245. +\sub of transforms, 178. +\sub of vectors, 9. +|superellipse|, @126, @138, +267. +superellipses, 126, 161. +|superness|, 126. +Sutherland, Ivan Edward, 121. +Swift, Jonathan, 99, 121. +\<symbolic token list>, 155, +218. +symbolic tokens, 49--51. +symmetric difference, 120. +syntax rules, 49--50. +System Development Foundation, ix. +\newletter +`T', 22--23, 97, 151, 199--200, 302--303. +tables of \MF\ trivia: +\sub character classes, 51. +\sub character codes, 281--282. +\sub expandable tokens, 179--180. +\sub |fontdimen| parameters, 319. +\sub internal quantities, 211--212. +\sub language features, 257--261. +\sub proof\/ label options, 328. +\sub types, 55. +\sub units of measure, 92. +tags, +53--+55, 156, 175, 218--219. +|takepower|, +265. +taller pens, 297--298. +tapered stroke, 28. +|tensepath|, 128, +264, @298. +*|tension|, @15--@16, @114, +129--+132, 136, @296. +\<tension>, 129, +213. +\<tension amount>, 129, +213. +*|tertiary|, 165, 167. +\<tertiary>, 71, @137, +209. +*|tertiarydef|, 166, +178, @266. +|test.mf|, 311--312. +|testfont.tex|, 40, 336--342. +\TeX, 1, 34, 40, 91, 96, 98, 101--102, 315, 336--343, 361. +*|text|, @161, +165--+167. +|\text|, 340. +|(TEXT|$_n$|)|, 45, 249, 251. +text arguments, 219, 288--290, 299. +|.tfm|, 39, 315--317, 333, 335. +|!| |This| |can't| |happen|, 226. +Thomson, James, 189. +Thoreau, Henry David, 221. +|thru|, @107, @138, @200, +274. +tilde, 152. +*|time|, +212, 218, 323. +time in paths, 119, 133--137. +\<times or over>, 72, +211. +Tinguely, Jean, 3. +\<title>, +187, 217--218, 323. +|title|, 323, 327. +|titlefont|, +275, 329. +|titlefontarea|, 329. +|titlefontat|, 329. +*|to|, 191, 220, @252, @277, @312. +|<to| |be| |read| |again>|, 223. +Tobin, Georgia Kay Mase, ii, 240. +tokens, 42--43, +49--+51, 210. +|tolerance|, 176, 251, 267, 293. +|top|, @23, @77, 80, @103, 147, 151, 204, +273. +Tory, Geoffroy, 19. +|totalnull|, 295. +*|totalweight|, +115, 211, @292. +|tracingall|, 230, +263, 288. +*|tracingcapsules|, 211, 219, 239. +*|tracingchoices|, 211, +229. +*|tracingcommands|, 211, +230. +*|tracingedges|, 211, +230, @295--@296. +*|tracingequations|, 80--83, 211, 229. +*|tracingmacros|, +160, 211, 229. +|tracingnone|, 230, +263. +*|tracingonline|, @61, 80, 211, 219, +230. +*|tracingoutput|, 211, +229--+230, 296. +*|tracingpens|, 211, +229, 230. +*|tracingrestores|, +156, 211, 229. +*|tracingspecs|, 206--207, 211, +229. +*|tracingstats|, 211, +227, 230. +*|tracingtitles|, 55, @94, +187, 211, 229. +Trajanus, 153. +trajectories, \see paths. +transcript file, 42, 46, 62, 230, 295--297. +*|transform|, 55, +56, 57, 141--143, @160, 266. +\<transform expression>, +215. +transform expressions, 141--143, 170, 178, 258. +\<transform part>, +211. +\<transform primary>, +215. +\<transform secondary>, +215. +\<transform tertiary>, +215. +transformations, 44, 141--145. +*|transformed|, 73, 141--145, 213. +\<transformer>, 73, +213. +transition lines, 230. +|transum|, 178. +trial path, 235. +triangle, 24--25, 203. +trigonometric functions, 67, 69, 131, 177. +*|true|, 55, @64--@65, 170, 210. +truth, viii, 217, 221. +{\sl TUGboat}, ix, 361. +turning numbers, 110, +111, 112, 119, 136, 147. +*|turningcheck|, 112, +119, 212, 229, @244, 262, 296. +*|turningnumber|, 111, 211, 257, @264. +Twain, Mark (= Clemens, Samuel Langhorne), 145. +\<type>, +56, 171. +type declarations, 56. +types, 55. +typewriter type, 55, 105. +typographic errors, 45, 224. +\newletter +|u|, 103--104, 305--308. +|!| |Undefined| |coordinate|, 224. +undelimited arguments, +167. +\<undelimited parameters>, +165. +undelimited suffix parameters, +167, 176, 265, 270. +underline characters, 49, +51, 173, 265, 270. +|undraw|, 113, 118, 120, 242, +271. +|undrawdot|, 113, +271. +unequal to, 65. +|unfill|, @25, 27, 109--110, 118, @126, +271. +|unfilldraw|, 113, 118, +271. +*|uniformdeviate|, @68, 72, +183, 184, 211. +union, 120. +Union Jack, 7. +|unitpixel|, +263, @333. +units of measure, 33, 91--99, 267--268. +\sub table, 92. +|unitsquare|, @116, 128, 132, 136, +263. +|unitvector|, @238, +264. +*|unknown|, 79--82, 143, +170, 210. +unknown quantities, nonnumeric, 84--85. +\sub numeric, 79--83. +*|until|, @18, 171. +|up|, @32, @129, +263. +|\uppers|, 339. +|upto|, @39, 172, +262. +utility files, 311--313. +\newletter +\<vacuous expression>, +215. +vacuous expressions, 209, +215, 250, 262, 289, 292. +\<vacuous primary>, +215. +\<vacuous secondary>, +215. +\<vacuous tertiary>, +215. +valentine, 134. +values, disappearance of, 56, 88, 156--157. +*|vardef|, 166, @175--@178, 289. +\<vardef heading>, +178. +\<variable>, 54, +55, 210. +variables, 53--57, 59. +\sub reinitializing, 88, 157. +vector subtraction principle, 9. +vectors, 9--10, 77. +velocity zero, 136, 298. +Venezky, Richard Lawrence, 193. +*|vppp|, 212, 267, 324. +|vround|, @204, @+264, @268. +\newletter +|w|, @22--@25, 35--36, @76--@78, 102--103, 106, +275--+276, 308--310. +`w', 202. +*|warningcheck|, 212, @269, 270. +Warren, Mercy Otis, 359. +Webster, Noah, 167. +|whatever|, @83--@84, @138, 157, @233, 239, +264, @290. +width, 101. +Wilde, Oscar Fingal O'Flahertie Wills, 321. +Wilkins, John, ii, 283. +Willis, Ellen Jane, 157. +\<window>, 191, +220. +\<window spec>, 191, +220. +\<with clause>, +118, 120. +*|withpen|, 118, 220, @242. +*|withweight|, 118, 220, @242, @297. +|WNW|, 119, 228--229. +|WSW|, 119, 228--229. +\newletter +$x$ coordinates, @5--@7. +x-height, 319. +Xerox Corporation, 320. +|xgap|, 95--96, 199. +*|xoffset|, 212, +220, @309, 315, 324. +xor, 120. +*|xpart|, @68, 72, @138, 142, 211. +*|xscaled|, @21--@23, @68, 73, +141, 213, 244, 291. +*|xxpart|, 72, 142, @160, 211. +|xy_swap|, 297. +*|xypart|, 142, @160, 211. +\newletter +$y$ coordinates, @5--@7. +*|year|, +212, 323. +|ygap|, 96, 199. +*|yoffset|, 212, +220, 315, 324. +*|ypart|, @68, 72, 142, 211, 238. +*|yscaled|, @21--@23, @68, 73, +141, 213, 244, 291. +*|yxpart|, 142, @160, 211. +*|yypart|, 142, @160, 211. +\newletter +|z| convention, 7, @68, 69, 251, +277. +Zapf, Hermann, iii, 221. +zero, 236. +*|zscaled|, @68--@69, 73, +141, 213. +|ztest.mf|, 312. +\enddoublecolumns +\endchapter + +The more we search, +the More are we Deceived. +\author MERCY OTIS ^{WARREN}, ^^{Adams} {\sl To Mr.\thinspace Adams\/} (1773) + % in Mass. Historical Soc. Collections, vol73 (1917), p402; line 32 + +\bigskip + +A heavy weight is now to be removed from my conscience. +So essential did I consider an Index to be to every book, +that I proposed to bring a Bill into Parliament +to deprive an author who publishes a book without an Index +of the privilege of copyright; and, moreover, +to subject him, for his offence, to a pecuniary penalty. +Yet, from difficulties started by my printers, +my own books have hitherto been without an Index. +\author LORD ^{CAMPBELL}, {\sl Lives of the Chief Justices % + of England}, vol.\thinspace 3 (1857) % end of the preface + +\eject + \beginchapter Appendix J. Joining the\\\TeX\ Community + +This appendix is about grouping of another kind: \TeX\ and \MF\ users from +around the world have banded together to form the \TeX\ Users Group (TUG), +in order to exchange information about common problems and solutions. + +A newsletter/journal called {\sl TUGboat\/} has been published +since 1980, featuring articles about all aspects of \TeX\ and \MF\!\null. +^^{TeX} TUG has a network of ``site coordinators'' who serve as focal points of +communication for people with the same computer configurations. +Occasional short courses are given, +to provide concentrated training in special topics; videotapes of +these courses are available for rental. +Meetings of the entire TUG membership are held at least once a year. +You can buy \MF\ T-shirts at these meetings. + +Information about membership in TUG and subscription to {\sl TUGboat\/} +is available from + +\smallskip +{\obeylines +\TeX\ Users Group +|email: TUG@tug.org| +|internet: http://www.tug.org| +} + +\endchapter + +TUG is established to serve members having a common interest +in \TeX, a system for typesetting technical text, +and in {\manual \char`\\]\char`\^\char`\_efg\char`\^}\!, % + a system for font design. +\author T\kern-.15em\lower.5ex\hbox{E}\kern-.005em X % + USERS GROUP, {\sl Bylaws, Article II\/} (1983) % TUGboat 4 (1983) p60 + +\bigskip + +Don't delay, subscribe today! That address again is +\TeX\ Users Group +email: {\eighttt TUG\char`\@ tug.org} +internet: {\eighttt http://www.tug.org/} +\author DONALD E. ^{KNUTH}, {\sl The \TeX book\/} (1996) % Appendix J + +\eject + \end diff --git a/systems/knuth/dist/mf/trap.fot b/systems/knuth/dist/mf/trap.fot new file mode 100644 index 0000000000..6bceb9f67e --- /dev/null +++ b/systems/knuth/dist/mf/trap.fot @@ -0,0 +1,59 @@ +This is METAFONT, Version 2.7182818 (INIMF) +** &trap trap +(trap.mf +\*//*\trap\pass2! +! Missing symbolic token inserted. +<inserted text> + INACCESSIBLE +l.21 ...t next=\; delimiters ^~7 + ! fi +! Extra tokens will be flushed. +<to be read again> + ! +l.21 ... next=\; delimiters ^~7! + fi +! Forbidden token found while scanning to the end of the statement. +<inserted text> + ; +<to be read again> + \ +l.22 next\ + ; % the second pass will now compute silently; the ... + +hello again^^_ +>> pen (see the transcript file) +>> path (see the transcript file) +>> path (see the transcript file) +>> path (see the transcript file) +f<expr>->let)=];let[=(;show._ +(EXPR0)<-xx +{let} +{let} +{show} +>> _1 +{showdependencies} +xpart '=-ypart '-0.66667 +{qq:=pen} +{showstats} +Memory usage 1462&588 (104 still untouched) +String usage 41&161 (815&7634 still untouched) +{[repeat the loop]} +! A group begun on line 163 never ended. +<to be read again> + endtext +l.163 ... showstats; bye endtext + +{endgroup} + ) +(end occurred when else on line 93 was incomplete) +(end occurred when if on line 36 was incomplete) +(end occurred when elseif on line 21 was incomplete) +(see the transcript file for additional information) +(illegal design size has been changed to 128pt) +(some chardp values had to be adjusted by as much as 0.5pt) +(local label 0:: was missing) +(local label 5:: was missing) +(4 font metric dimensions had to be decreased) +Font metrics written on trap.tfm. +Output written on trap.72270gf (5 characters, 1028 bytes). +Transcript written on trap.log. diff --git a/systems/knuth/dist/mf/trap.log b/systems/knuth/dist/mf/trap.log new file mode 100644 index 0000000000..611ce589e2 --- /dev/null +++ b/systems/knuth/dist/mf/trap.log @@ -0,0 +1,4266 @@ +This is METAFONT, Version 2.7182818 (preloaded base=trap 2014.1.7) 7 JAN 2014 18:11 +** &trap trap +(trap.mf +{if} +{known(0)} +{not(true)} +{false} +{known("")} +{true} + +/*\`'@#->begingroup.message(SUFFIX1)&str(SUFFIX0)&jobname&char.ASCII'`&s +tr(SUFFIX2)!endgroup +(SUFFIX0)<-/*\ +(SUFFIX1)<-`' +(SUFFIX2)<-pass2 +{begingroup} +{message} + +`'->begingroup'`endgroup +(SUFFIX0)<- +(SUFFIX1)<-`' +{begingroup} + +'`->"\*/" +{endgroup} +{("\*/")&("/*\")} +{jobname} +{("\*//*\")&("trap")} + +'`->"\*/" +{ASCII("\*/")} +{char(92)} +{("\*//*\trap")&("\")} +{("\*//*\trap\")&("pass2!")} + +\*//*\trap\pass2! +{endgroup} +{outer} +{let} +{delimiters} + +! Missing symbolic token inserted. +<inserted text> + INACCESSIBLE +l.21 ...t next=\; delimiters ^~7 + ! fi +Sorry: You can't redefine a number, string, or expr. +I've inserted an inaccessible symbol so that your +definition will be completed without mixing me up too badly. + +! Extra tokens will be flushed. +<to be read again> + ! +l.21 ... next=\; delimiters ^~7! + fi +I've just read as much of that statement as I could fathom, +so a semicolon should have been next. It's very puzzling... +but I'll try to get myself back together, by ignoring +everything up to the next `;'. Please insert a semicolon +now in front of anything that you don't want me to delete. +(See Chapter 27 of The METAFONTbook for an example.) + +! Forbidden token found while scanning to the end of the statement. +<inserted text> + ; +<to be read again> + \ +l.22 next\ + ; % the second pass will now compute silently; the ... +A previous error seems to have propagated, +causing me to read past where you wanted me to stop. +I'll try to recover; but if the error is serious, +you'd better type `E' or `X' now and fix your file. + +{\} +{batchmode} + +! An expression can't begin with `endgroup'. +<inserted text> + 0 +<to be read again> + endgroup +l.23 batchmode; ^~7,endgroup + pausing:=1; exitif p exitif bool... +I'm afraid I need some sort of value in order to continue, +so I've tentatively inserted `0'. You may want to +delete this zero and insert something else; +see Chapter 27 of The METAFONTbook for an example. + +! Missing ` INACCESSIBLE' has been inserted. +<to be read again> + endgroup +l.23 batchmode; ^~7,endgroup + pausing:=1; exitif p exitif bool... +I found no right delimiter to match a left one. So I've +put one in, behind the scenes; this may fix the problem. + +! Extra `endgroup'. +<recently read> endgroup + +l.23 batchmode; ^~7,endgroup + pausing:=1; exitif p exitif bool... +I'm not currently working on a `begingroup', +so I had better not try to end anything. + +{pausing:=1} +{exitif} +{exitif} +{pencircle} +{endfor} +! Extra `endfor'. +l.23 ...ean pen pencircle endfor + +I'm not currently working on a for loop, +so I had better not try to end anything. + +{scantokens} +{begingroup} +{message} +{char(0)} +{("^^@")&("watch this")} +^^@watch this +{-(1)} +{char(-1)} +{("pair p[],';")&("^^ff")} +{endgroup} +{pen(future pen)} +{boolean(true)} +{true} +! No loop is in progress. +<to be read again> + pair +<scantokens> pair + p[],';^^ff +<to be read again> + path +l.25 path + p[][]p,w,qw; qw=(1,-2)..(2,-1)..(2.5,0.5)..(1,2)..(... +Why say `exitif' when there's nothing to exit from? + +>> p +! Undefined condition will be treated as `false'. +<to be read again> + pair +<scantokens> pair + p[],';^^ff +<to be read again> + path +l.25 path + p[][]p,w,qw; qw=(1,-2)..(2,-1)..(2.5,0.5)..(1,2)..(... +The expression shown above should have had a definite +true-or-false value. I'm changing it to `false'. + +{false} +! Missing `;' has been inserted. +<to be read again> + pair +<scantokens> pair + p[],';^^ff +<to be read again> + path +l.25 path + p[][]p,w,qw; qw=(1,-2)..(2,-1)..(2.5,0.5)..(1,2)..(... +After `exitif <boolean exp>' I expect to see a semicolon. +I shall pretend that one was there. + +{pair} +! Text line contains an invalid character. +<scantokens> pair p[],';^^ff + +<to be read again> + path +l.25 path + p[][]p,w,qw; qw=(1,-2)..(2,-1)..(2.5,0.5)..(1,2)..(... +A funny symbol that I can't read has just been input. +Continue, and I'll forget that it ever happened. + +{path} +{-(2)} +{-(1)} +{turningnumber((xpart ',ypart '))} +Path at line 25, before choices: +(1,-2){curl 1} + ..(2,-1) + ..(2.5,0.5) + ..(1,2) + ..{curl 1}(0,2.5) + +Path at line 25, after choices: +(1,-2)..controls (1.37755,-1.71404) and (1.71404,-1.37755) + ..(2,-1)..controls (2.33353,-0.55965) and (2.59729,-0.04124) + ..(2.5,0.5)..controls (2.36812,1.23369) and (1.6712,1.65662) + ..(1,2)..controls (0.66821,2.16974) and (0.33485,2.33641) + ..(0,2.5) + +{(unknown path qw)=(path)} +{numeric} +! Enormous number has been reduced. +l.26 ...[$] ]]=10000000000000000 + ; "this string constant is in... +I can't handle numbers bigger than about 4095.99998; +so I've changed your constant to that maximum amount. + +{(p[[ [-1] ]])=(4095.99998)} +## p[[ [-1] ]]=4095.99998 +! Incomplete string token has been flushed. +l.26 ...g constant is incomplete + +Strings should finish on the same line as they began. +I've deleted the partial string; you might want to +insert another by typing, e.g., `I"new string"'. + +{string} +! Declared variable conflicts with previous vardef. +<to be read again> + , +l.27 string foo[]p, + p~if true:[]; p~000=char34&char200&char34; +You can't use, e.g., `numeric foo[]' after `vardef foo'. +Proceed, and I'll ignore the illegal redeclaration. + +{if} +{true} +{true} +{char(34)} +{char(200)} +{(""")&("^^c8")} +{char(34)} +{(""^^c8")&(""")} +{(unknown string p~0)=(""^^c8"")} +{boolean} +{fi} +{showvariable} +boolean.boolean=unknown boolean +! OK. +l.28 ...n; showvariable boolean; + def\\= =end enddef; + +{def} +{picture} +{show} +{scantokens} +{length("^^c8")} +>> 1 +! OK. +<recently read> ; + +l.29 ...w length scantokens p~0; + + +{pen} +! Illegal suffix of declared variable will be flushed. +<to be read again> + [ +<to be read again> + "a" +l.30 pen p~[]~,q["a" + ,qq; p~1~=q=pencircle scaled mexp(-3016.5... +Variables in declarations must consist entirely of +names and collective subscripts, e.g., `x[]a'. +Are you trying to use a reserved word in a variable name? +I'm going to discard the junk I found here, +up to the next comma or the end of the declaration. + +{pencircle} +{-(3016.57654)} +{mexp(-3016.57654)} +{(future pen)scaled(0)} +Pen polygon at line 30 (newly created): +(0.5,0) + .. (0,0.5) + .. (-0.5,0) + .. (0,-0.5) + .. cycle + +{(unknown pen q)=(pen)} +{(unknown pen p~1~)=(pen)} +{transform} +! Illegal suffix of declared variable will be flushed. +<to be read again> + 0 +l.31 transform p,pp0 + ; if p=p:qq=makepen((1,0)..cycle) xscaled... +Variables in declarations must consist entirely of +names and collective subscripts, e.g., `x[]a'. +Explicit subscripts like `x15a' aren't permitted. +I'm going to discard the junk I found here, +up to the next comma or the end of the declaration. + +{if} +{((xpart p,ypart p,xxpart p,xypart p,yxpart p,yypart p))=((xpart p,ypart + p,xxpart p,xypart p,yxpart p,yypart p))} +{true} +Path at line 31, before choices: +(1,0) + ..cycle + +Path at line 31, after choices: +(1,0)..controls (1,0) and (1,0) + ..cycle + +{makepen(path)} +{hex("1000")} +! Number too large (4096). +<to be read again> + ; +l.31 ...cle) xscaled hex "1000"; + fi +I have trouble with numbers greater than 4095; watch out. + +{(future pen)xscaled(4096)} +! Pen too large. +<to be read again> + ; +l.31 ...cle) xscaled hex "1000"; + fi +The cycle you specified has a coordinate of 4095.5 or more. +So I've replaced it by the trivial path `(0,0)..cycle'. + +Pen polygon at line 31 (newly created): +(0,0) + .. cycle + +{(unknown pen qq)=(pen)} +{fi} +Path at line 32, before choices: +(0,0) + ..(1,0) + ..(0,1) + ..(0,0) + ..(1,0) + ..(0,1) + ..cycle + +Path at line 32, after choices: +(0,0)..controls (0.29056,-0.29056) and (0.75859,-0.30772) + ..(1,0)..controls (1.51964,0.66237) and (0.66237,1.51964) + ..(0,1)..controls (-0.30772,0.75859) and (-0.29056,0.29056) + ..(0,0)..controls (0.29056,-0.29056) and (0.75859,-0.30772) + ..(1,0)..controls (1.51964,0.66237) and (0.66237,1.51964) + ..(0,1)..controls (-0.30772,0.75859) and (-0.29056,0.29056) + ..cycle + +{makepen(path)} +! Pen cycle must be convex. +<to be read again> + ; +l.32 ...)..(1,0)..(0,1)..cycle); + +The cycle you specified either has consecutive equal points +or turns right or turns through more than 360 degrees. +So I've replaced it by the trivial path `(0,0)..cycle'. + +Pen polygon at line 32 (newly created): +(0,0) + .. cycle + +{qq:=pen} +{vardef} +! Missing parameter type; `expr' will be assumed. +<to be read again> + ) +l.33 ...ext suffix a,b endtext() + )suffix@=show #@; p.a.b() end... +You should've had `expr' or `suffix' or `text' here. + +{expandafter} +{\} +{let} + +\\->=end +{outer} +{pencircle} +{(future pen)scaled(4.5)} +{(future pen)yscaled(2)} +Pen polygon at line 34 (newly created): +(0.5,-4.5) + .. (1,-4) + .. (2,-2.5) + .. (2.5,0) + .. (2,2.5) + .. (1,4) + .. (0.5,4.5) + .. (-0.5,4.5) + .. (-1,4) + .. (-2,2.5) + .. (-2.5,0) + .. (-2,-2.5) + .. (-1,-4) + .. (-0.5,-4.5) + .. cycle + +{qq:=pen} +{((6,12))-((xpart p7,ypart p7))} +{((0,1))transformed((xpart p,ypart p,xxpart p,xypart p,yxpart p,yypart p +))} +{(x)-(x)} +{(2)/(0)} +>> 2 +! Division by zero. +<to be read again> + , +l.35 ...)transformed p=(2/(x-x), + 3/0)transformed p; +You're trying to divide the quantity shown above the error +message by zero. I'm going to divide it by one instead. + +! Division by zero. +l.35 ...ansformed p=(2/(x-x),3/0 + )transformed p; +I'll pretend that you meant to divide by 1. + +{((2,3))transformed((xpart p,ypart p,xxpart p,xypart p,yxpart p,yypart p +))} +{((linearform,linearform))=((linearform,linearform))} +## yxpart p=-yypart p +## xxpart p=-xypart p +{((-xpart p7+6,-ypart p7+12))=((linearform,linearform))} +## ypart p7=-ypart p-yypart p+12 +## xpart p7=-xpart p-xypart p+6 +{\} +{if} +{string(unknown string p~[-1])} +{true} +{(p0.1 0.2)-(p0.1 0.2)} +! The token `endtext' is no longer a right delimiter. +l.36 ...1.2-p.1.199999,1 endtext + transformed p; +Strange: This token has lost its former meaning! +I'll read it as a right delimiter this time; +but watch out, I'll probably miss it later. + +{((0,1))transformed((xpart p,ypart p,-xypart p,xypart p,-yypart p,yypart + p))} +{((linearform,linearform))=((linearform,linearform))} +## ypart p=-yypart p+6 +#### ypart p7=6 +## xpart p=-xypart p+3 +#### xpart p7=3 +{(unknown path p1 2p)=((3,6))} +{showstopping:=0} +{showvariable} +p=(-xypart p+3,-yypart p+6,-xypart p,xypart p,-yypart p,yypart p) +p[]=pair +p[][]=numeric +p[][]p=unknown path +p[][]p~=macro:(SUFFIX2)(SUFFIX3)(EXPR4)<suffix>->begingroup ETC. +p[]~=unknown boolean +p~=path +p~[]=unknown string +p~[]~=unknown pen +p~[]~[][]=unknown picture +p~[-1]=unknown string p~[-1] +p~0=""^^c8"" +p~1~=pen +p[[ [] ]]=numeric +p[[ [-1] ]]=4095.99998 +p0.1 0.2=p0.1 0.2 +p1 2p=path +p7=(3,6) +{((-xypart p+3,-yypart p+6,-xypart p,xypart p,-yypart p,yypart p))=((-xy +part p+3,-yypart p+6,-xypart p,xypart p,-yypart p,yypart p))} +{let} +{let} +{xxpart((-xypart p+3,-yypart p+6,-xypart p,xypart p,-yypart p,yypart p)) +} +{(-xypart p)+(0.002)} +{yxpart((-xypart p+3,-yypart p+6,-xypart p,xypart p,-yypart p,yypart p)) +} +{((xpart p2,ypart p2))-((xpart p1,ypart p1))} +{(1)*((linearform,linearform))} +{((xpart p1,ypart p1))+((linearform,linearform))} +{(y)+(0.00002)} +{yypart((-xypart p+3,-yypart p+6,-xypart p,xypart p,-yypart p,yypart p)) +} +{xypart((-xypart p+3,-yypart p+6,-xypart p,xypart p,-yypart p,yypart p)) +} +{((5,y))=((yypart p,xypart p))} +## y=xypart p +## yypart p=5 +#### ypart p=1 +#### yxpart p=-5 +{((5,xypart p+0.00002))=((5,xypart p))} +{((xpart p2,ypart p2))=((5,xypart p))} +## ypart p2=xypart p +## xpart p2=5 +{((-xypart p+0.002,-5))=((5,xypart p))} +## xypart p=-5 +#### ypart p2=-5 +#### y=-5 +#### xpart p=8 +#### xxpart p=5 +! Inconsistent equation (off by -0.002). +<to be read again> + ; +l.38 ...,y)=(yypart p,xypart p); + +The equation I just read contradicts what was said before. +But don't worry; continue and I'll just ignore it. + +Path at line 39, before choices: +(0,0)..controls (15,4) and (-15,-12) + ..(4,0) + ..cycle + +Path at line 39, after choices: +(0,0)..controls (15,4) and (-15,-12) + ..(4,0)..controls (17.52783,8.54388) and (-15.45978,-4.12262) + ..cycle + +{reverse(path)} +{(path)transformed((8,1,5,-5,-5,5))} +{(path)=(unknown path p2 3p)} + +p1 2p~(SUFFIX2)(SUFFIX3)(EXPR4)<suffix>->begingroup.show(SUFFIX0);p(SUFF +IX2)(SUFFIX3)((EXPR4)endgroup +(SUFFIX0)<-p1 2p +(SUFFIX1)<-~ +(SUFFIX2)<- +(SUFFIX3)<-2 3p~ +! A primary expression can't begin with `right delimiter that matches (' +. +<inserted text> + 0 +<to be read again> + ) +l.40 ...000000001]2p~(,[2]3p~,-) + =p~1~2[pausing]; +I'm afraid I need some sort of value in order to continue, +so I've tentatively inserted `0'. You may want to +delete this zero and insert something else; +see Chapter 27 of The METAFONTbook for an example. + +{-(0)} +(EXPR4)<-0 +(SUFFIX5)<- +{begingroup} +{show} +>> Path at line 40: +(3,6) + + +p2 3p~(SUFFIX2)(SUFFIX3)(EXPR4)<suffix>->begingroup.show(SUFFIX0);p(SUFF +IX2)(SUFFIX3)((EXPR4)endgroup +(SUFFIX0)<-p2 3p +(SUFFIX1)<-~ +! Missing `,' has been inserted. +<to be read again> + (0) +p1 2p~->...IX2)(SUFFIX3)((EXPR4) + endgroup +<to be read again> + = +l.40 ...00000001]2p~(,[2]3p~,-)= + p~1~2[pausing]; +I've finished reading a macro argument and am about to +read another; the arguments weren't delimited correctly. +You might want to delete some tokens before continuing. + +(SUFFIX2)<- +! Missing `,' has been inserted. +<to be read again> + (0) +p1 2p~->...IX2)(SUFFIX3)((EXPR4) + endgroup +<to be read again> + = +l.40 ...00000001]2p~(,[2]3p~,-)= + p~1~2[pausing]; +I've finished reading a macro argument and am about to +read another; the arguments weren't delimited correctly. +You might want to delete some tokens before continuing. + +(SUFFIX3)<- +! Missing `)' has been inserted. +<to be read again> + endgroup +<to be read again> + = +l.40 ...00000001]2p~(,[2]3p~,-)= + p~1~2[pausing]; +I've gotten to the end of the macro parameter list. +You might want to delete some tokens before continuing. + +(EXPR4)<-0 +(SUFFIX5)<- +{begingroup} +{show} +>> Path at line 40: +(8,1)..controls (-48.68579,57.68579) and (52.91974,-43.91974) + ..(28,-19)..controls (-7,16) and (63,-54) + ..cycle + +>> (8,1,5,-5,-5,5) +! Isolated expression. +<to be read again> + ( +p2 3p~->...;p(SUFFIX2)(SUFFIX3)( + (EXPR4)endgroup +<to be read again> + endgroup +<to be read again> + = +l.40 ...00000001]2p~(,[2]3p~,-)= + p~1~2[pausing]; +I couldn't find an `=' or `:=' after the +expression that is shown above this error message, +so I guess I'll just ignore it and carry on. + +! Extra tokens will be flushed. +<to be read again> + ( +p2 3p~->...;p(SUFFIX2)(SUFFIX3)( + (EXPR4)endgroup +<to be read again> + endgroup +<to be read again> + = +l.40 ...00000001]2p~(,[2]3p~,-)= + p~1~2[pausing]; +I've just read as much of that statement as I could fathom, +so a semicolon should have been next. It's very puzzling... +but I'll try to get myself back together, by ignoring +everything up to the next `;'. Please insert a semicolon +now in front of anything that you don't want me to delete. +(See Chapter 27 of The METAFONTbook for an example.) + +{endgroup} +{endgroup} +{(vacuous)=(unknown picture p~1~2 1)} +>> vacuous +>> unknown picture p~1~2 1 +! Equation cannot be performed (vacuous=unknown picture). +<to be read again> + ; +l.40 ...2]3p~,-)=p~1~2[pausing]; + +I'm sorry, but I don't know how to make such things equal. +(See the two expressions just above the error message.) + +{vardef} +Runaway definition? +if.p(SUFFIX1)(SUFFIX1)=(SUFFIX1)(SUFFIX1)p.fi +! Forbidden token found while scanning the definition of p~[]. +<inserted text> + enddef +<to be read again> + ; +l.41 ...iary t:=if p@ @=@ @p fi; + vardef p[][]p~[]=BAD; inner ;; +I suspect you have forgotten an `enddef', +causing me to read past where you wanted me to stop. +I'll try to recover; but if the error is serious, +you'd better type `E' or `X' now and fix your file. + +{vardef} +! This variable already starts with a macro. +l.41 ...@p fi; vardef p[][]p~[]= + BAD; inner ;; +After `vardef a' you can't say `vardef a.b'. +So I'll have to discard this definition. + +Runaway definition? +BAD +! Forbidden token found while scanning the definition of a bad variable. +<inserted text> + enddef +<to be read again> + ; +l.41 ...i; vardef p[][]p~[]=BAD; + inner ;; +I suspect you have forgotten an `enddef', +causing me to read past where you wanted me to stop. +I'll try to recover; but if the error is serious, +you'd better type `E' or `X' now and fix your file. + +{inner} +{show} +{-(2)} + +p~[-2]@#<tertiary>->begingroup.if.p(SUFFIX1)(SUFFIX1)=(SUFFIX1)(SUFFIX1) +p.fi.endgroup +(SUFFIX0)<-p~ +(SUFFIX1)<-[-2] +(SUFFIX2)<-~ +! A tertiary expression can't begin with `['. +<inserted text> + 0 +<to be read again> + [ +<to be read again> + (3000) +<to be read again> + , +l.42 show p~[-2]~[3000, + x]++4000>path p3; showvariable p,P; +I'm afraid I need some sort of value in order to continue, +so I've tentatively inserted `0'. You may want to +delete this zero and insert something else; +see Chapter 27 of The METAFONTbook for an example. + +{(x)-(3000)} +{(0)*(x-3000)} +{(3000)+(0)} +{(3000)++(4000)} +(EXPR3)<-4999.99998 +{begingroup} +{if} +{(p[-2][-2])=(-2)} +>> p[-2][-2]+2 +! Unknown relation will be considered false. +<to be read again> + [-2] +p~[-2]->...1)=(SUFFIX1)(SUFFIX1) + p.fi.endgroup +<to be read again> + > +l.42 show p~[-2]~[3000,x]++4000> + path p3; showvariable p,P; +Oh dear. I can't decide if the expression above is positive, +negative, or zero. So this comparison test won't be `true'. + +{false} +! Missing `:' has been inserted. +<to be read again> + [-2] +p~[-2]->...1)=(SUFFIX1)(SUFFIX1) + p.fi.endgroup +<to be read again> + > +l.42 show p~[-2]~[3000,x]++4000> + path p3; showvariable p,P; +There should've been a colon after the condition. +I shall pretend that one was there. + +{endgroup} +{path((xpart p3,ypart p3))} +{(vacuous)>(false)} +>> vacuous +>> false +! Not implemented: (vacuous)>(boolean). +<to be read again> + ; +l.42 ...~[3000,x]++4000>path p3; + showvariable p,P; +I'm afraid I don't know how to apply that operation to that +combination of types. Continue, and I'll return the second +argument (see above) as the result of the operation. + +>> false +{showvariable} +p=(8,1,5,-5,-5,5) +p[]=pair +p[][]=numeric +p[][]p=unknown path +p[][]p~=macro:(SUFFIX2)(SUFFIX3)(EXPR4)<suffix>->begingroup ETC. +p[]~=unknown boolean +p~=path +p~[]@#=macro:<tertiary>->begingroup.if.p(SUFFIX1)(SUFFIX1) ETC. +p[[ [] ]]=numeric +p[[ [-1] ]]=4095.99998 +p[-2][-2]=p[-2][-2] +p0.1 0.2=p0.1 0.2 +p1=(xpart p1,ypart p1) +p1 2p=path +p2=(5,-5) +p2 3p=path +p3=(xpart p3,ypart p3) +p7=(3,6) +> P=tag +{numeric} +{(2)*(alpha)} +{(p3~)=(2alpha)} +## alpha=0.5p3~ +{(1)/(-1)} +{(3)*(beta)} +{(p[-1]~)=(3beta)} +## beta=0.33333p[-1]~ +{begingroup} +{save} +{showvariable} +> p=tag +{(3)*(0.33333(SAVED)p[-1]~)} +{((SAVED)p[-1]~)=(1)} +## (SAVED)p[-1]~=1 +#### beta=0.33333 +{restoring p} +{endgroup} +{showvariable} +p=(8,1,5,-5,-5,5) +p[]=pair +p[][]=numeric +p[][]p=unknown path +p[][]p~=macro:(SUFFIX2)(SUFFIX3)(EXPR4)<suffix>->begingroup ETC. +p[]~=numeric +p~=path +p~[]@#=macro:<tertiary>->begingroup.if.p(SUFFIX1)(SUFFIX1) ETC. +p[[ [] ]]=numeric +p[[ [-1] ]]=4095.99998 +p[-2][-2]=p[-2][-2] +p[-1]~=1 +p0.1 0.2=p0.1 0.2 +p1=(xpart p1,ypart p1) +p1 2p=path +p2=(5,-5) +p2 3p=path +p3=(xpart p3,ypart p3) +p3~=p3~ +p7=(3,6) +{def} +{def} +! Missing `=' has been inserted. +<to be read again> + false +l.45 ...enddef;def!primary!false + ):!fi enddef; +The next thing in this `def' should have been `=', +because I've already looked at the definition heading. +But don't worry; I'll pretend that an equals sign +was present. Everything from here to `enddef' +will be the replacement text of this macro. + +{def} +{(path)scaled(-1)} +Path at line 46, before choices: +(1,-2)..controls (1.37755,-1.71404) and (1.71404,-1.37755) + ..(2,-1)..controls (2.33353,-0.55965) and (2.59729,-0.04124) + ..(2.5,0.5)..controls (2.36812,1.23369) and (1.6712,1.65662) + ..(1,2)..controls (0.66821,2.16974) and (0.33485,2.33641) + ..(0,2.5) + ..(-1,2)..controls (-1.37755,1.71404) and (-1.71404,1.37755) + ..(-2,1)..controls (-2.33353,0.55965) and (-2.59729,0.04124) + ..(-2.5,-0.5)..controls (-2.36812,-1.23369) and (-1.6712,-1.65662) + ..(-1,-2)..controls (-0.66821,-2.16974) and (-0.33485,-2.33641) + ..(0,-2.5) + ..cycle + +Path at line 46, after choices: +(1,-2)..controls (1.37755,-1.71404) and (1.71404,-1.37755) + ..(2,-1)..controls (2.33353,-0.55965) and (2.59729,-0.04124) + ..(2.5,0.5)..controls (2.36812,1.23369) and (1.6712,1.65662) + ..(1,2)..controls (0.66821,2.16974) and (0.33485,2.33641) + ..(0,2.5)..controls (-0.37186,2.68167) and (-0.668,2.25146) + ..(-1,2)..controls (-1.37755,1.71404) and (-1.71404,1.37755) + ..(-2,1)..controls (-2.33353,0.55965) and (-2.59729,0.04124) + ..(-2.5,-0.5)..controls (-2.36812,-1.23369) and (-1.6712,-1.65662) + ..(-1,-2)..controls (-0.66821,-2.16974) and (-0.33485,-2.33641) + ..(0,-2.5)..controls (0.37186,-2.68167) and (0.668,-2.25146) + ..cycle + +{makepen(path)} +Pen polygon at line 46 (newly created): +(1,-2) + .. (2,-1) + .. (2.5,0.5) + .. (1,2) + .. (0,2.5) + .. (-1,2) + .. (-2,1) + .. (-2.5,-0.5) + .. (-1,-2) + .. (0,-2.5) + .. cycle + +{qq:=pen} +{primarydef} +{secondarydef} + +//<expr>-> + +//<expr>-> +{pencircle} +{length(path)} +{(future pen)slanted(1)} +{((3,6))-((5,-5))} + +_aa__<secondary>->if(true +{(0.1)*(15)} +{odd(1.50009)} +{not(false)} +{known((8,1,5,-5,-5,5))} +{(true)and(true)} +(EXPR0)<-true +{if} +{true} + +!<primary>->false):(EXPR0)fi +(EXPR0)<-(5,-5) +{false} +{(true)or(false)} +{true} +{fi} +{-((5,-5))} +{-((5,-5))} +{+(1)} +{-(1)} +>> -1 +! Improper curl has been replaced by 1. +<to be read again> + ) +l.51 {curl- +1) + ..tension atleast1..cycle sqrt2++sqrt2***[[]]; +A curl must be a known, nonnegative number. + +! Missing `}' has been inserted. +<to be read again> + ) +l.51 {curl- +1) + ..tension atleast1..cycle sqrt2++sqrt2***[[]]; +I've scanned a direction spec for part of a path, +so a right brace should have come next. +I shall pretend that one was there. + +Path at line 51, before choices: +(-5,5) + +Path at line 51, after choices: +(-5,5) + +Path at line 51, before choices: +(3,6)..controls (5,-5) and (-5,5) + ..(-5,5)..tension atleast1 + ..{2896.30943,-2896.30934}cycle + +Path at line 51, after choices: +(3,6)..controls (5,-5) and (-5,5) + ..(-5,5)..controls (-3.29726,7.86205) and (0.64516,8.35484) + ..cycle + +! Missing `)' has been inserted. +<to be read again> + sqrt +l.51 ...ion atleast1..cycle sqrt + 2++sqrt2***[[]]; +I found no right delimiter to match a left one. So I've +put one in, behind the scenes; this may fix the problem. + +{((-2,11))subpath(path)} +{reverse(path)} +{makepen(path)} + +**->[[show(EXPR0)*(EXPR1)]] +(EXPR0)<-future pen +(EXPR1)<-future pen +{begingroup} +{show} +{(future pen)*(future pen)} +>> future pen +>> future pen +! Not implemented: (future pen)*(future pen). +<to be read again> + ]] +<to be read again> + sqrt +l.51 ...ion atleast1..cycle sqrt + 2++sqrt2***[[]]; +I'm afraid I don't know how to apply that operation to that +combination of types. Continue, and I'll return the second +argument (see above) as the result of the operation. + +! Pen path must be a cycle. +<to be read again> + ]] +<to be read again> + sqrt +l.51 ...ion atleast1..cycle sqrt + 2++sqrt2***[[]]; +I can't make a pen from the given path. +So I've replaced it by the trivial path `(0,0)..cycle'. + +>> Pen polygon at line 51: +(0,0) + .. cycle + +{endgroup} +(EXPR0)<-vacuous +{sqrt(2)} +{sqrt(2)} +{(1.41422)++(1.41422)} +{begingroup} +{endgroup} + +***->expandafter(EXPR1)scantokens"**oct"(EXPR0) +(EXPR0)<-2 +(EXPR1)<-vacuous +{expandafter} +{scantokens} +{oct(2)} +>> 2 +! Not implemented: oct(known numeric). +<to be read again> + ; +l.51 ...cle sqrt2++sqrt2***[[]]; + +I'm afraid I don't know how to apply that operation to that +particular type. Continue, and I'll simply return the +argument (shown above) as the result of the operation. + + +**->[[show(EXPR0)*(EXPR1)]] +(EXPR0)<-vacuous +(EXPR1)<-2 +{begingroup} +{show} +{(vacuous)*(2)} +>> vacuous +>> 2 +! Not implemented: (vacuous)*(known numeric). +<to be read again> + ]] +<to be read again> + ; +l.51 ...cle sqrt2++sqrt2***[[]]; + +I'm afraid I don't know how to apply that operation to that +combination of types. Continue, and I'll return the second +argument (see above) as the result of the operation. + +>> 2 +{endgroup} +(EXPR0)<-vacuous +{begingroup} +{interim} +{-(20.5)} +{charcode:=-20.5} +{proofing:=-20.5} +{-(2048)} +{chardp:=-2048} +{shipout} +{nullpicture} +! Enormous chardp has been reduced. +<to be read again> + ]] +l.52 ...48;shipout nullpicture]] + ; +Font metric dimensions must be less than 2048pt. + +{restoring proofing=0} +{endgroup} +{if} +{-(275.50002)} +{charexists(-275.50002)} +{known(unknown path p0 0p)} +{(true)>(false)} +{known(path)} +{(true)=(true)} +{true} +{randomseed} +! Missing `:=' has been inserted. +<to be read again> + charcode +l.53 ... p~: randomseed charcode + ; fi +Always say `randomseed:=<numeric expression>'. + +{randomseed:=-20.5} +{fi} +{randomseed} +>> "goof" +! Unknown value will be ignored. +<to be read again> + ; +l.54 randomseed:="goof"; + a[($,18++1+-+18),(2,3)]=b[(3,2),(1,$); +Your expression was too random for me to handle, +so I won't change the random seed just now. + +{(18)++(1)} +{(18.02776)+-+(18)} +{((2,3))-((-1,1))} +{(a)*((3,2))} +{((-1,1))+((3a,2a))} +! Missing `]' has been inserted. +<to be read again> + ; +l.54 ...8),(2,3)]=b[(3,2),(1,$); + +I've scanned an expression of the form `a[b,c', +so a right bracket should have come next. +I shall pretend that one was there. + +{((1,-1))-((3,2))} +{(b)*((-2,-3))} +{((3,2))+((-2b,-3b))} +{((3a-1,2a+1))=((-2b+3,-3b+2))} +## b=-0.66667a+0.33333 +## a=2 +#### b=-1 +{show} +{(^)+(1)} +{(~)+(2)} +{-(1)} +{((^+1,~+2))slanted(-1)} +{-(2)} +{((linearform,~+2))yscaled(-2)} +{-((3,4))} +{((linearform,-2~-4))zscaled((-3,-4))} +>> (-5~-3^-13,10~-4^+16) +{((xpart pp,ypart pp,xxpart pp,xypart pp,yxpart pp,yypart pp))xscaled(9) +} +{((8,1,5,-5,-5,5))transformed((9xpart pp,ypart pp,9xxpart pp,9xypart pp, +yxpart pp,yypart pp))} +>> (9xpart pp+72xxpart pp+9xypart pp,ypart pp+8yxpart pp+yypart pp,45xxp +art pp-45xypart pp,-45xxpart pp+45xypart pp,5yxpart pp-5yypart pp,-5yxpa +rt pp+5yypart pp) +{((xpart pp,ypart pp,xxpart pp,xypart pp,yxpart pp,yypart pp))shifted((1 +,2))} +{((8,1,5,-5,-5,5))transformed((8,1,5,-5,-5,5))} +{((xpart pp+1,ypart pp+2,xxpart pp,xypart pp,yxpart pp,yypart pp))transf +ormed((43,-34,50,-50,-50,50))} +>> (50xpart pp-50ypart pp-7,-50xpart pp+50ypart pp+16,50xxpart pp-50yxpa +rt pp,50xypart pp-50yypart pp,-50xxpart pp+50yxpart pp,-50xypart pp+50yy +part pp) +{(2)-(1)} +{(_0)*(1)} +{(1)+(_0)} +{-(_0+1)} +>> -_0-1 +{show} +{normaldeviate} +{-(-2048)} +{uniformdeviate(2048)} +{angle((-0.41438,761.67789))} +{cosd(90.03117)} +{(200)*(-0.00055)} +{mexp(-0.10986)} +{sqrt(0.99957)} +{mlog(0.99979)} +{sind(-0.05469)} +{floor(-0.00096)} +>> -1 +{string} +{(unknown string s2)=(unknown string s4)} +{(unknown string s1)=(unknown string s2)} +{(unknown string s3)=(unknown string s5)} +{(unknown string s2)=(unknown string s4)} +! Redundant equation. +<to be read again> + ; +l.58 ... s1=s2=s4; s3=s5; s1=s2; + if s1<=s4<>(s1<>s3):show[[ch... +I already knew that this equation was true. +But perhaps no harm has been done; let's continue. + +{if} +{(unknown string s2)<=(unknown string s1)} +{(unknown string s2)<>(unknown string s5)} +>> unknown string s2 +>> unknown string s5 +! Unknown relation will be considered false. +<to be read again> + ) +l.58 ...=s2; if s1<=s4<>(s1<>s3) + :show[[char34=s2:=s3]]fi; +The quantities shown above have not been equated. + +{(true)<>(false)} +{true} +{show} +{begingroup} +{char(34)} +{s2:=unknown string s5} +{(""")=(unknown string s2)} +{endgroup} +{fi} +>> vacuous +{(0.1)point(path)} +{begingroup} +{pencircle} +Pen polygon at line 59 (newly created): +(0.5,0) + .. (0,0.5) + .. (-0.5,0) + .. (0,-0.5) + .. cycle + +{endgroup} +{((3.24413,0.64801))penoffset(pen)} +{((0,-0.5))rotated(540)} +{((0,0.5))/(0.33333)} +{-(1.5)} +! Paths don't touch; `&' will be changed to `..'. +<to be read again> + ) +l.60 ...tcontrol-1.5of(p~&cycle) + -precontrol1/2of p~(p~)=s1:=s... +When you join paths `p&q', the ending point of p +must be exactly equal to the starting point of q. +So I'm going to pretend that you said `p..q' instead. + +Path at line 60, before choices: +(0,0)..controls (15,4) and (-15,-12) + ..(4,0) + ..cycle + +Path at line 60, after choices: +(0,0)..controls (15,4) and (-15,-12) + ..(4,0)..controls (17.52783,8.54388) and (-15.45978,-4.12262) + ..cycle + +{(-1.5)postcontrol(path)} +{(0.5)precontrol(path)} +{((-2.75,-5))-((3.75,-1))} +! Missing `of' has been inserted for directiontime. +<to be read again> + ( +l.60 ...cle)-precontrol1/2of p~( + p~)=s1:=s4:=s4; +I've got the first argument; will look now for the other. + +{((-6.5,-4))directiontime(path)} +{decimal(0.5)} +{((0,1.50003))substring("0.5")} +{s4:=unknown string s1} +{s1:=unknown string s4} +{("0.")=(unknown string s1)} +{path} +{length(" ")} +! Missing `,' has been inserted. +<to be read again> + } +l.61 ...p~[]; p~1=p2{length" "} + &cycle; p~1=p2=p~0; p2..contr... +I've got the x coordinate of a path direction; +will look for the y coordinate next. + +! An expression can't begin with `}'. +<inserted text> + 0 +<to be read again> + } +l.61 ...p~[]; p~1=p2{length" "} + &cycle; p~1=p2=p~0; p2..contr... +I'm afraid I need some sort of value in order to continue, +so I've tentatively inserted `0'. You may want to +delete this zero and insert something else; +see Chapter 27 of The METAFONTbook for an example. + +Path at line 61, before choices: +(5,-5){4096,0} + ..{4096,0}cycle + +Path at line 61, after choices: +(5,-5)..controls (5,-5) and (5,-5) + ..cycle + +{(unknown path p~1)=(path)} +{((5,-5))=(unknown path p~0)} +{(path)=(path)} +! Redundant or inconsistent equation. +<to be read again> + ; +l.61 ..." "}&cycle; p~1=p2=p~0; + p2..controls-p2..cycle=p~2;(p7 +An equation between already-known quantities can't help. +But don't worry; continue and I'll just ignore it. + +{-((5,-5))} +Path at line 61, before choices: +(5,-5)..controls (-5,5) and (-5,5) + ..cycle + +Path at line 61, after choices: +(5,-5)..controls (-5,5) and (-5,5) + ..cycle + +{(path)=(unknown path p~2)} +{length(path)} +Path at line 62, before choices: +(3,6)..tension 1.2 + ..(5,-5)..controls (5,-5) and (5,-5) + ..(5,-5) + ..(5,-5)..controls (-5,5) and (-5,5) + ..(5,-5){0,4096}..tension 1 and atleast1 + ..cycle + +Path at line 62, after choices: +(3,6)..controls (-3.01212,4.82085) and (-1.0424,-6.01257) + ..(5,-5)..controls (5,-5) and (5,-5) + ..(5,-5)..controls (5,-5) and (5,-5) + ..(5,-5)..controls (-5,5) and (-5,5) + ..(5,-5)..controls (5,0.02791) and (4.99951,6.39217) + ..cycle + +>> x +! Improper tension has been set to 1. +<to be read again> + .. +l.63 x.. + {curl1}-p7{curl hex "IsBad"}..tension.75and.74999..p... +The expression above should have been a number >=3/4. + +{-((3,6))} +{hex("IsBad")} +>> "IsBad" +! String contains illegal digits. +<to be read again> + } +l.63 ...l1}-p7{curl hex "IsBad"} + ..tension.75and.74999..p2{0,1... +I zeroed out characters that weren't hex digits. + +>> 0.74998 +! Improper tension has been set to 1. +<to be read again> + .. +l.63 ...}..tension.75and.74999.. + p2{0,1}&p2{_,'}..cycle:=p +The expression above should have been a number >=3/4. + +>> _ +! Undefined x coordinate has been replaced by 0. +<to be read again> + , +l.63 ...and.74999..p2{0,1}&p2{_, + '}..cycle:=p +I need a `known' x value for this part of the path. +The value I found (see above) was no good; +so I'll try to keep going by using zero instead. +(Chapter 27 of The METAFONTbook explains that +you might want to type `I ???' now.) + +>> (xpart ',ypart ') +! Undefined y coordinate has been replaced by 0. +<to be read again> + } +l.63 ...d.74999..p2{0,1}&p2{_,'} + ..cycle:=p +I need a `known' y value for this part of the path. +The value I found (see above) was no good; +so I'll try to keep going by using zero instead. +(Chapter 27 of The METAFONTbook explains that +you might want to type `I ???' now.) + +Path at line 63, before choices: +(3,6)..controls (-3.01212,4.82085) and (-1.0424,-6.01257) + ..(5,-5)..controls (5,-5) and (5,-5) + ..(5,-5)..controls (5,-5) and (5,-5) + ..(5,-5)..controls (-5,5) and (-5,5) + ..(5,-5)..controls (5,0.02791) and (4.99951,6.39217) + ..(3,6) + ..{curl 1}(-3,-6){curl 2989}..tension 0.75 and 1 + ..{0,4096}(5,-5){curl 1} + ..cycle + +Path at line 63, after choices: +(3,6)..controls (-3.01212,4.82085) and (-1.0424,-6.01257) + ..(5,-5)..controls (5,-5) and (5,-5) + ..(5,-5)..controls (5,-5) and (5,-5) + ..(5,-5)..controls (-5,5) and (-5,5) + ..(5,-5)..controls (5,0.02791) and (4.99951,6.39217) + ..(3,6)..controls (-2.44821,4.93144) and (-5.41408,-1.00029) + ..(-3,-6)..controls (-3.70045,-12.19485) and (5,-11.61679) + ..(5,-5)..controls (12.45406,-3.75087) and (10.41669,7.45464) + ..cycle + +>> path +! Improper `:=' will be changed to `='. +l.63 ...p2{0,1}&p2{_,'}..cycle:= + p +I didn't find a variable name at the left of the `:=', +so I'm going to pretend that you said `=' instead. + +{(path)=(unknown path p~4)} +{-(9)} +{((3.5001,7.00002))subpath(path)} +{((-9,9))subpath(path)} +{(path)=(unknown path p~6)} +{show} +>> Path at line 65: +(-2.5,2.5)..controls (-2.49893,2.49893) and (0.00107,-0.00107) + ..(5,-5)..controls (5,0.02791) and (4.99951,6.39217) + ..(3,6)..controls (-2.44821,4.93144) and (-5.41408,-1.00029) + ..(-3,-6)..controls (-3.70045,-12.19485) and (5,-11.61679) + ..(5,-5)..controls (5.0001,-4.99998) and (5.00021,-4.99997) + ..(5.00032,-4.99995) + +{((1,2))directiontime(path)} +>> 1 +{-(1.00002)} +{((1,-1.00002))directiontime(path)} +>> 3.24937 +Path at line 66, before choices: +(0,0)..controls (1,1) and (0,1) + ..(1,0) + +Path at line 66, after choices: +(0,0)..controls (1,1) and (0,1) + ..(1,0) + +{(unknown path p~3)=(path)} +{show} +Path at line 66, before choices: +(5,-5){-2896.30943,-2896.30934} + ..{1831.78674,3663.57385}cycle + +Path at line 66, after choices: +(5,-5)..controls (5,-5) and (5,-5) + ..cycle + +>> Path at line 66: +(5,-5)..controls (5,-5) and (5,-5) + ..cycle + +{((0.31416,1))subpath(path)} +{((1,1))directiontime(path)} +{(1)-(0.31416)} +{(0.27097)*(0.68584)} +{(0.31416)+(0.18584)} +>> 0.5 +{((0,0.25))subpath(path)} +{((0.25,1))subpath(path)} +Path at line 68, before choices: +(0,0)..controls (0.25,0.25) and (0.375,0.4375) + ..(0.4375,0.5625)..controls (0.625,0.9375) and (0.25,0.75) + ..(1,0) + +Path at line 68, after choices: +(0,0)..controls (0.25,0.25) and (0.375,0.4375) + ..(0.4375,0.5625)..controls (0.625,0.9375) and (0.25,0.75) + ..(1,0) + +! Missing `endtext' has been inserted. +<to be read again> + ; +l.68 ...~3)shifted begintext1,0; + +I found no right delimiter to match a left one. So I've +put one in, behind the scenes; this may fix the problem. + +{(path)shifted((1,0))} +{(unknown path p~5)=(path)} +{(2/3)*((xpart ',ypart '))} +{((0.66667xpart ',0.66667ypart '))zscaled((xpart ',ypart '))} +>> (0,0,xpart ',-ypart ',ypart ',xpart ') +! Transform components aren't all known. +<to be read again> + { +l.69 p~3:=2/3'zscaled'{ + p~3}..controls(2,2/3(3))and penoffset(... +I'm unable to apply a partially specified transformation +except to a fully known pair or transform. +Proceed, and I'll omit the transformation. + +>> 0.66667xpart ' +! Undefined x coordinate has been replaced by 0. +<to be read again> + { +l.69 p~3:=2/3'zscaled'{ + p~3}..controls(2,2/3(3))and penoffset(... +I need a `known' x value for this part of the path. +The value I found (see above) was no good; +so I'll try to keep going by using zero instead. +(Chapter 27 of The METAFONTbook explains that +you might want to type `I ???' now.) + +>> 0.66667ypart ' +! Undefined y coordinate has been replaced by 0. +<to be read again> + { +l.69 p~3:=2/3'zscaled'{ + p~3}..controls(2,2/3(3))and penoffset(... +I need a `known' y value for this part of the path. +The value I found (see above) was no good; +so I'll try to keep going by using zero instead. +(Chapter 27 of The METAFONTbook explains that +you might want to type `I ???' now.) + +>> path +! Undefined coordinates have been replaced by (0,0). +<to be read again> + } +l.69 p~3:=2/3'zscaled'{p~3} + ..controls(2,2/3(3))and penoffset(... +I need x and y numbers for this part of the path. +The value I found (see above) was no good; +so I'll try to keep going by using zero instead. +(Chapter 27 of The METAFONTbook explains that +you might want to type `I ???' now.) + +{(2/3)*(3)} +{(1/2)*(x)} +{((0.5x,-5))penoffset((0,1))} +>> (0.5x,-5) +>> (0,1) +! Not implemented: penoffset(unknown pair)of(pair). +<to be read again> + ( +l.69 ...enoffset(1/2x,y)of(0,1)( + 1,0); +I'm afraid I don't know how to apply that operation to that +combination of types. Continue, and I'll return the second +argument (see above) as the result of the operation. + +! Missing `..' has been inserted. +<to be read again> + ( +l.69 ...enoffset(1/2x,y)of(0,1)( + 1,0); +A path join command should end with two dots. + +Path at line 69, before choices: +(0,0)..controls (2,2) and (0,1) + ..(1,0) + +Path at line 69, after choices: +(0,0)..controls (2,2) and (0,1) + ..(1,0) + +{p~3:=path} +{show} +{reverse(path)} +{(path)intersectiontimes(path)} +>> (0.17227,0.28339) +{(0.17227)point(path)} +>> (0.71329,0.78188) +{(1)-(0.28339)} +{(0.71661)point(path)} +>> (0.7133,0.78189) +{show} +{(path)shifted((0.01,0))} +{(path)intersectiontimes(path)} +{xpart((1.47693,1.18973))} +{(1.47693)point(path)} +{(path)shifted((0.01,0))} +{(path)intersectiontimes(path)} +{ypart((1.47693,1.18973))} +{(1.18973)point(path)} +{((1.50499,0.71521))-((1.495,0.71521))} +>> (0.01,0) +{begingroup} +{interim} +{tracingedges:=1} +{-(1)} +{-(1.00002)} +{(-1)+-+(-1.00002)} +! Pythagorean subtraction 1+-+1.00002 has been replaced by 0. +l.73 ...es:=1; e[-1+-+ -1.00001] + =nullpicture; addto e1 also[[ +Since I don't take square roots of negative numbers, +I'm zeroing this one. Proceed, with fingers crossed. + +{nullpicture} +{(unknown picture e0)=(picture)} +{addto} +{begingroup} +{addto} +{(path)scaled(3)} +Path at line 74, before subdivision into octants: +(3,0)..controls (3.75,0.75) and (4.125,1.3125) + ..(4.3125,1.6875)..controls (4.875,2.8125) and (3.75,2.25) + ..(6,0)..controls (3.75,2.25) and (4.875,2.8125) + ..(4.3125,1.6875)..controls (4.125,1.3125) and (3.75,0.75) + ..cycle + +Cycle spec at line 74, after subdivision: +(3,0) % beginning in octant `NNE' + ..controls (3.75,0.75) and (4.125,1.3125) + ..(4.3125,1.6875) % segment 0 + ..controls (4.5,2.0625) and (4.5,2.25) + ..(4.5,2.25) % segment 1 +% entering octant `NNW' +% entering octant `WNW' +% entering octant `WSW' +% entering octant `SSW' +% entering octant `SSE' + ..controls (4.5,2.25) and (4.5,1.5) + ..(6,0) % segment 1 +% entering octant `ESE' +% entering octant `ENE' +% entering octant `NNE' +% entering octant `NNW' + ..controls (4.5,1.5) and (4.5,2.25) + ..(4.5,2.25) % segment 2 +% entering octant `WNW' +% entering octant `WSW' +% entering octant `SSW' + ..controls (4.5,2.25) and (4.5,2.0625) + ..(4.3125,1.6875) % segment 2 + ..controls (4.125,1.3125) and (3.75,0.75) + ..(3,0) % segment 3 +% entering octant `SSE' +% entering octant `ESE' +% entering octant `ENE' + & cycle + +Tracing edges at line 74: (weight 1) +(4,0)(4,1)(5,1)(5,3)(4,3)(4,1)(5,1)(5,0)(6,0)(6,1)(5,1)(5,3) +(4,3)(4,1)(3,1)(3,0). + +{(unknown picture e1)=(unknown picture e2)} +{(picture)=(unknown picture e1)} +{cull} +{nullpicture} +{endgroup} +{show} +{-(4095)} +{(picture)shifted((4089,-4095))} +>> Edge structure at line 75: +row -4093: | 4093+ 4094- +row -4094: | 4093+ 4094- +row -4095: | 4092+ 4093- 4094+ 4095- + +{-(4095)} +{(picture)shifted((-4095,4092))} +{-(3)} +{(picture)shifted((-3,0))} +>> Edge structure at line 75: +row 4094: -4094+ -4093- -4094+ -4093- | +row 4093: -4094+ -4093- -4094+ -4093- | +row 4092: -4095+ -4092- -4093+ -4094- | + +{-(4095)} +{(picture)shifted((4089,-4095))} +! Too far to shift. +<to be read again> + ]] +l.76 e2 shifted(4089,-4095)]] + ;addto e1 also e2 shifted(-2,$)... +I can't shift the picture as requested---it would +make some coordinates too large or too small. +Proceed, and I'll omit the transformation. + +>> Edge structure at line 76: +row 2: 4+ 5- 4+ 5- | +row 1: 4+ 5- 4+ 5- | +row 0: 3+ 6- 5+ 4- | + +{restoring tracingedges=32767.99998} +{endgroup} +{addto} +{-(2)} +{(picture)shifted((-2,-1))} +{-(4)} +{(picture)shifted((-4,-1))} +{e1:=picture} +{addto} +{(picture)rotated(89.999)} +! That transformation is too hard. +<to be read again> + + +l.77 ...e0 also e1rotated89.999+ + e1scaled$; show e0 xscaled-10... +I can apply complicated transformations to paths, +but I can only do integer operations on pictures. +Proceed, and I'll omit the transformation. + +{(picture)scaled(-1)} +{(picture)+(picture)} +{show} +{-(10)} +{(picture)xscaled(-10)} +{(picture)yscaled(2)} +{(picture)xscaled(82)} +! Scaled picture would be too big. +<to be read again> + yscaled +l.78 yscaled + 683;addto e1 doublepath (0,9) withweight-3 withwe... +I can't xscale the picture as requested---it would +make some coordinates too large or too small. +Proceed, and I'll omit the transformation. + +{(picture)yscaled(683)} +! Scaled picture would be too big. +<to be read again> + ; +l.78 yscaled683; + addto e1 doublepath (0,9) withweight-3 withwe... +I can't yscale the picture as requested---it would +make some coordinates too large or too small. +Proceed, and I'll omit the transformation. + +>> Edge structure at line 78: +row 5: -40- -50+ -40- -50+ | +row 4: -40- -50+ -40- -50+ | +row 3: -30+ 0- -10+ -20- -40- -50+ -40- -50+ | -10+ 0- +row 2: -30+ 0- -10+ -20- -40- -50+ -40- -50+ | -10+ 0- +row 1: 20- 10+ 20- 10+ -20+ -10- -20+ -10- -30- -60+ -50- -40+ | + -10+ -10+ 0- 0- 10+ 20- +row 0: 20- 10+ 20- 10+ -20+ -10- -20+ -10- -30- -60+ -50- -40+ | + -10+ -10+ 0- 0- 10+ 20- +row -1: 20- 10+ 20- 10+ -20+ -10- -20+ -10- | -20+ -10- 0+ 0+ 10- + 10- +row -2: 20- 10+ 20- 10+ -20+ -10- -20+ -10- | -20+ -10- 0+ 0+ 10- + 10- +row -3: 30- 0+ 10- 20+ | 0+ 10- +row -4: 30- 0+ 10- 20+ | 0+ 10- + +{addto} +{-(3)} +{turningnumber(path)} +! Weight must be -3, -2, -1, +1, +2, or +3. +<to be read again> + withpen +l.79 withpen + pencircle xscaled(oct"180"++1) rotated-angle(64... +I'll ignore the bad `with' clause and look for another. + +{pencircle} +{oct("180")} +>> "180" +! String contains illegal digits. +<to be read again> + ++ +l.79 ...ircle xscaled(oct"180"++ + 1) rotated-angle(64,$) shifte... +I zeroed out characters that weren't in the range 0..7. + +{(64)++(1)} +{(future pen)xscaled(64.00781)} +{angle((64,-1))} +{-(-0.89517)} +{(future pen)rotated(0.89517)} +{(future pen)shifted((9,8))} +Pen polygon at line 80 (newly created): +(9,7.5) + .. (37.5,8) + .. (41,8.5) + .. (9,8.5) + .. (-19.5,8) + .. (-23,7.5) + .. cycle + +Path at line 80, before choices: +(-1,-1){curl 1} + ..(1,0) + ..(1,1) + ..(-1,0) + ..{curl 1}cycle + +Path at line 80, after choices: +(-1,-1)..controls (-0.18568,-1.14464) and (0.62712,-0.73824) + ..(1,0)..controls (1.16457,0.32584) and (1.21637,0.71506) + ..(1,1)..controls (0.46898,1.69931) and (-0.7587,1.2167) + ..(-1,0)..controls (-1.06546,-0.33012) and (-1.06546,-0.66988) + ..cycle + +{makepen(path)} +{(future pen)xscaled(4095.49998)} +Pen polygon at line 80 (newly created): +(4095.49998,0) + .. (4095.49998,1) + .. (-4095.49998,0) + .. (-4095.49998,-1) + .. cycle + +Path at line 80, before subdivision into octants: +(0,9)..controls (0,9) and (0,9) + ..cycle + +! Curve out of range. +<to be read again> + ; +l.80 ...cycle)xscaled4095.49999; + +At least one of the coordinates in the path I'm about to +digitize was really huge (potentially bigger than 4095). +So I've cut it back to the maximum size. +The results will probably be pretty wild. + +Cycle spec at line 80, after subdivision: +(0,0) % beginning in octant `ENE' +% entering octant `NNE' +% entering octant `NNW' +% entering octant `WNW' +% entering octant `WSW' +% entering octant `SSW' +% entering octant `SSE' +% entering octant `ESE' + & cycle + +Tracing edges at line 80: (weight -3) +@ Octant ENE (2 offsets), from (-4095.49998,-1) to (4095.49998,0) +@ transition line 0, from (-4095.49998,-1) to (-4095.49998,-1) +@ transition line 1, from (-4095.49998,-1) to (4095.49998,0) +@ transition line 2, from (4095.49998,0) to (4095.49998,0) +(0,-1) +@ Octant NNE (1 offset), from (4095.49998,0) to (4095.49998,1) +@ transition line 2, from (4095.49998,0) to (4095.49998,0) +@ transition line 1, from (4095.49998,0) to (4095.49998,1) +(0,0)(4095,0) +@ Octant NNW (0 offsets), from (4095.49998,1) to (4095.49998,1) +@ transition line 0, from (4095.49998,1) to (4095.49998,1) +@ Octant WNW (0 offsets), from (4095.49998,1) to (4095.49998,1) +@ transition line 1, from (4095.49998,1) to (4095.49998,1) +@ Octant WSW (2 offsets), from (4095.49998,1) to (-4095.49998,0) +@ transition line 0, from (4095.49998,1) to (4095.49998,1) +@ transition line 1, from (4095.49998,1) to (-4095.49998,0) +@ transition line 2, from (-4095.49998,0) to (-4095.49998,0) +(4095,1)(0,1) +@ Octant SSW (1 offset), from (-4095.49998,0) to (-4095.49998,-1) +@ transition line 2, from (-4095.49998,0) to (-4095.49998,0) +@ transition line 1, from (-4095.49998,0) to (-4095.49998,-1) +(0,0)(-4095,0) +@ Octant SSE (0 offsets), from (-4095.49998,-1) to (-4095.49998,-1) +@ transition line 0, from (-4095.49998,-1) to (-4095.49998,-1) +@ Octant ESE (0 offsets), from (-4095.49998,-1) to (-4095.49998,-1) +@ transition line 1, from (-4095.49998,-1) to (-4095.49998,-1) +(-4095,-1). + +{show} +>> Edge structure at line 81: +row 1: | 0+ 1- +row 0: 0--- 4095+++ -2+ -1- -2+ -1- | 0+ 1- +row -1: -4095--- 0+++ -2+ -1- -2+ -1- | -1+ 0- 1+ 2- +row -2: -3+ 0- -1+ -2- | + +{totalweight(picture)} +>> -0.37476 +{charcode:=5} +{chardp:=5} +{-(1.5)} +{xoffset:=-1.5} +{shipout} +[5] +Edge structure at line 81 (just shipped out): +row 1: | -1+ 0- +row 0: | -3+ -3+ -2- -2- -1--- -1+ 0- 4094+++ +row -1: | -4096--- -3+ -3+ -2- -2- -2+ -1- -1+++ 0+ 1- +row -2: | -4+ -3- -2+ -1- + +{showstats} +Memory usage 1084&202 (749 still untouched) +String usage 24&92 (858&11309 still untouched) +{addto} +{(path)yscaled(0.01666)} +Path at line 82, before subdivision into octants: +(0,0)..controls (15,0.06665) and (-15,-0.19995) + ..(4,0)..controls (-15,-0.19995) and (15,0.06665) + ..cycle + +Cycle spec at line 82, after subdivision: +(0,0) % beginning in octant `ENE' + ..controls (1.74272,0.00775) and (2.87802,0.01099) + ..(3.55333,0.01099) % segment 0 +% entering octant `ESE' + ..controls (4.12251,0.01099) and (4.36491,0.00868) + ..(4.36877,0.00482) % segment 0 +% entering octant `SSE' + ..controls (4.3688,0.00479) and (4.36882,0.00476) + ..(4.36882,0.00473) % segment 0 +% entering octant `SSW' + ..controls (4.36882,0.0047) and (4.3688,0.00467) + ..(4.36877,0.00464) % segment 0 +% entering octant `WSW' + ..controls (4.34875,-0.01538) and (-1.88759,-0.07579) + ..(-2.48894,-0.07579) % segment 0 +% entering octant `WNW' + ..controls (-2.5191,-0.07579) and (-2.5351,-0.07564) + ..(-2.53542,-0.07532) % segment 0 +% entering octant `NNW' +% entering octant `NNE' +% entering octant `ENE' + ..controls (-2.53209,-0.07199) and (-0.88672,-0.05144) + ..(4,0) % segment 0 +% entering octant `NNE' +% entering octant `NNW' +% entering octant `WNW' +% entering octant `WSW' + ..controls (-0.88672,-0.05144) and (-2.5321,-0.072) + ..(-2.53542,-0.07532) % segment 1 +% entering octant `SSW' +% entering octant `SSE' +% entering octant `ESE' + ..controls (-2.53511,-0.07562) and (-2.5191,-0.07579) + ..(-2.48894,-0.07579) % segment 1 +% entering octant `ENE' + ..controls (-1.88759,-0.07579) and (4.34877,-0.01537) + ..(4.36877,0.00464) % segment 1 +% entering octant `NNE' + ..controls (4.3688,0.00467) and (4.36882,0.0047) + ..(4.36882,0.00473) % segment 1 +% entering octant `NNW' + ..controls (4.36882,0.00476) and (4.3688,0.00479) + ..(4.36877,0.00482) % segment 1 +% entering octant `WNW' + ..controls (4.36491,0.00868) and (4.12251,0.01099) + ..(3.55333,0.01099) % segment 1 +% entering octant `WSW' + ..controls (2.87802,0.01099) and (1.74272,0.00775) + ..(0,0) % segment 1 +% entering octant `SSW' +% entering octant `SSE' +% entering octant `ESE' + & cycle + +Tracing edges at line 82: (weight 1) +(No new edges added.) + +{(picture)yscaled(0)} +{e3:=picture} +{autorounding:=2} +{addto} +Path at line 83, before choices: +(0.5,0){curl 1} + ..{curl 1}(3.5,1.5) + +Path at line 83, after choices: +(0.5,0)..controls (1.5,0.5) and (2.5,1) + ..(3.5,1.5) + +Path at line 83, before subdivision into octants: +(0.5,0)..controls (1.5,0.5) and (2.5,1) + ..(3.5,1.5)..controls (2.5,1) and (1.5,0.5) + ..cycle + +Cycle spec at line 83, after subdivision and double autorounding: +(0.5,0) % beginning in octant `ENE' + ..controls (1.5,0.5) and (2.5,1) + ..(3.5,1.5) % segment 0 +% entering octant `NNE' +% entering octant `NNW' +% entering octant `WNW' +% entering octant `WSW' + ..controls (2.5,1) and (1.5,0.5) + ..(0.5,0) % segment 1 +% entering octant `SSW' +% entering octant `SSE' +% entering octant `ESE' + & cycle + +Tracing edges at line 83: (weight 2) +(2,0)(2,1)(4,1)(4,2)(4,1)(2,1)(2,0). + +{tracingspecs:=0} +Path at line 84, before choices: +(1,1) + ..cycle + +Path at line 84, after choices: +(1,1)..controls (1,1) and (1,1) + ..cycle + +{makepen(path)} +{(future pen)yscaled(1.5)} +Pen polygon at line 84 (newly created): +(1,1.5) + .. cycle + +{q:=pen} +{((-1,yy))rotated((8,1,5,-5,-5,5))} +>> (8,1,5,-5,-5,5) +! Improper transformation argument. +<to be read again> + { +l.85 p~8=(($,yy)rotated p{ + 0,1}..{0,$}(1,0){0,$}..cycle)scaled... +The expression shown above has the wrong type, +so I can't transform anything using it. +Proceed, and I'll omit the transformation. + +>> yy +! Undefined y coordinate has been replaced by 0. +<to be read again> + { +l.85 p~8=(($,yy)rotated p{ + 0,1}..{0,$}(1,0){0,$}..cycle)scaled... +I need a `known' y value for this part of the path. +The value I found (see above) was no good; +so I'll try to keep going by using zero instead. +(Chapter 27 of The METAFONTbook explains that +you might want to type `I ???' now.) + +Path at line 85, before choices: +(-1,0){0,4096} + ..{0,-4096}(1,0){0,-4096} + ..{0,4096}cycle + +Path at line 85, after choices: +(-1,0)..controls (-1,1.33333) and (1,1.33333) + ..(1,0)..controls (1,-1.33333) and (-1,-1.33333) + ..cycle + +{(path)scaled(2)} +{(path)shifted((1000.49,9))} +{(unknown path p~8)=(path)} +{turningcheck:=1} +{addto} +>> (8,1,5,-5,-5,5) +! Improper type. +<to be read again> + withpen +l.86 ...n q withweight p withpen + cycle p; +Next time say `withweight <known numeric expression>'; +I'll ignore the bad `with' clause and look for another. + +{cycle((8,1,5,-5,-5,5))} +>> false +! Improper type. +<to be read again> + ; +l.86 ...eight p withpen cycle p; + +Next time say `withpen <known pen expression>'; +I'll ignore the bad `with' clause and look for another. + +Tracing edges at line 86: (weight 1) +@ Octant NNE (0 offsets), from (999,10.5) to (999.15451,11.65451) +@ transition line 1, from (999.15451,11.65451) to (999.15451,11.65451) +(999,11) +@ Octant ENE (0 offsets), from (999.15451,11.65451) to (1001,12) +@ transition line 0, from (999.15451,11.65451) to (999.15451,11.65451) +@ Octant ESE (0 offsets), from (1001,12) to (1002.8455,11.6545) +@ transition line 1, from (1002.8455,11.6545) to (1002.8455,11.6545) +@ Octant SSE (0 offsets), from (1002.8455,11.6545) to (1003,10.5) +@ transition line 0, from (1002.8455,11.6545) to (1002.8455,11.6545) +(999,12)(1003,12) +@ Octant SSW (0 offsets), from (1003,10.5) to (1002.84549,9.34549) +@ transition line 1, from (1002.84549,9.34549) to (1002.84549,9.34549) +@ Octant WSW (0 offsets), from (1002.84549,9.34549) to (1001,9) +@ transition line 0, from (1002.84549,9.34549) to (1002.84549,9.34549) +@ Octant WNW (0 offsets), from (1001,9) to (999.1545,9.3455) +@ transition line 1, from (999.1545,9.3455) to (999.1545,9.3455) +@ Octant NNW (0 offsets), from (999.1545,9.3455) to (999,10.5) +@ transition line 0, from (999.1545,9.3455) to (999.1545,9.3455) +(1003,9)(999,9)(999,11). + +Tracing edges at line 86: (weight 1) +@ Octant SSE (0 offsets), from (999,10.5) to (999.15451,9.34549) +@ transition line 0, from (999,10.5) to (999,10.5) +(999,11) +@ Octant ESE (0 offsets), from (999.15451,9.34549) to (1001,9) +@ transition line 1, from (1001,9) to (1001,9) +@ Octant ENE (0 offsets), from (1001,9) to (1002.8455,9.3455) +@ transition line 0, from (1001,9) to (1001,9) +@ Octant NNE (0 offsets), from (1002.8455,9.3455) to (1003,10.5) +@ transition line 1, from (1003,10.5) to (1003,10.5) +(999,9)(1003,9) +@ Octant NNW (0 offsets), from (1003,10.5) to (1002.84549,11.65451) +@ transition line 0, from (1003,10.5) to (1003,10.5) +@ Octant WNW (0 offsets), from (1002.84549,11.65451) to (1001,12) +@ transition line 1, from (1001,12) to (1001,12) +@ Octant WSW (0 offsets), from (1001,12) to (999.1545,11.6545) +@ transition line 0, from (1001,12) to (1001,12) +@ Octant SSW (0 offsets), from (999.1545,11.6545) to (999,10.5) +@ transition line 1, from (999,10.5) to (999,10.5) +(1003,12)(999,12)(999,11). + +{begingroup} +{interim} +{(xx)=(0.1)} +## xx=0.1 +{autorounding:=0.1} +{addto} +Tracing edges at line 87: (weight -2) +@ Octant NNE (0 offsets), from (999,10.5) to (999.38197,11.42706) +@ transition line 1, from (999.38197,11.42706) to (999.38197,11.42706) +@ Octant ENE (0 offsets), from (999.38197,11.42706) to (1001,12) +@ transition line 0, from (999.38197,11.42706) to (999.38197,11.42706) +(999,11) +@ Octant ESE (0 offsets), from (1001,12) to (1002.61804,11.42705) +@ transition line 1, from (1002.61804,11.42705) to (1002.61804,11.42705) +(999,12)(1003,12) +@ Octant SSE (0 offsets), from (1002.61804,11.42705) to (1003,10.5) +@ transition line 0, from (1002.61804,11.42705) to (1002.61804,11.42705) +@ Octant SSW (0 offsets), from (1003,10.5) to (1002.61803,9.57294) +@ transition line 1, from (1002.61803,9.57294) to (1002.61803,9.57294) +@ Octant WSW (0 offsets), from (1002.61803,9.57294) to (1001,9) +@ transition line 0, from (1002.61803,9.57294) to (1002.61803,9.57294) +@ Octant WNW (0 offsets), from (1001,9) to (999.38196,9.57295) +@ transition line 1, from (999.38196,9.57295) to (999.38196,9.57295) +(1003,9)(999,9) +@ Octant NNW (0 offsets), from (999.38196,9.57295) to (999,10.5) +@ transition line 0, from (999.38196,9.57295) to (999.38196,9.57295) +(999,11). + +{restoring autorounding=2} +{endgroup} +{-(14.5)} +{charext:=-14.5} +{chardp:=-14.5} +{chardx:=-14.5} +{shipout} +{-(picture)} +{(picture)-(picture)} +{-(picture)} +{turningnumber(path)} +{(6)*(-1)} +{(picture)shifted((0,-6))} +{(picture)+(picture)} +[5.-14] +Edge structure at line 88 (just shipped out): +row 11: | 998- 998+ 998++ 1002-- 1002- 1002+ +row 10: | 998- 998+ 998++ 1002-- 1002- 1002+ +row 9: | 998- 998+ 998++ 1002-- 1002- 1002+ +row 5: | 998- 998+ 998++ 1002-- 1002- 1002+ +row 4: | 998- 998+ 998++ 1002-- 1002- 1002+ +row 3: | 998- 998+ 998++ 1002-- 1002- 1002+ +row 2: | 3+ 3+ 3+ 3+ 4- 4- 4- 4- +row 1: | -1+ -1+ 0- 0- 1+ 2- 3+ 3+ 3+ 3+ 4- 4- 4- 4- +row 0: | -3+ -3+ -3+ -2- -2- -2- -1+ -1+ 0- 0- 0+ 0+ 1- 1- 2+ 2+ + 3- 3- 4+ 4+ 5- 5- +row -1: | -3+ -3+ -2- -2- -2+ -2+ -1- -1- 0+ 0+ 0+ 1- 1- 1- +row -2: | -4+ -3- -2+ -2+ -1- -1- +row -4: | 3+ 3+ 4- 4- +row -5: | 3+ 3+ 4- 4- +row -6: | 2+ 3- 4+ 5- + +Path at line 89, before choices: +(0,0){curl 1} + ..(1,0.5) + ..(5,1.5) + ..(7,2.5) + ..(12,3.5) + ..{curl 1}(13,4) + +Path at line 89, after choices: +(0,0)..controls (0.3153,0.20053) and (0.6504,0.36807) + ..(1,0.5)..controls (2.29114,0.98723) and (3.72412,0.97192) + ..(5,1.5)..controls (5.68967,1.78545) and (6.30771,2.22081) + ..(7,2.5)..controls (8.58867,3.1407) and (10.3946,2.90645) + ..(12,3.5)..controls (12.3506,3.62962) and (12.68594,3.7973) + ..(13,4) + +{(unknown path p~9)=(path)} +{addto} +Tracing edges at line 89: (weight 1) +(1,0)(1,1)(5,1)(5,2)(7,2)(7,3)(12,3)(12,4)(12,3)(7,3)(7,2)(5,2) +(5,1)(1,1)(1,0). + +{smoothing:=1} +{addto} +Tracing edges at line 90: (weight 1) +(1,0)(1,1)(4,1)(4,2)(8,2)(8,3)(12,3)(12,4)(12,3)(8,3)(8,2)(4,2) +(4,1)(1,1)(1,0). + +{addto} +{-(4095)} +Path at line 91, before choices: +(-4095,0){curl 1}..tension 0.75 and 999 + ..{curl 1}(0,2) + +Path at line 91, after choices: +(-4095,0)..controls (-2275,0.88889) and (-1.36636,1.99933) + ..(0,2) + +Tracing edges at line 91: (weight 1) +(-3071,0)(-3071,1)(-1024,1)(-1024,2)(-1024,1)(-3071,1)(-3071,0). + +{show} +{-(90)} +{(picture)rotated(-90)} +>> Edge structure at line 91: + +{(picture)+(picture)} +{(90)*(-1)} +{(picture)rotated(-90)} +{(picture)rotated(90)} +>> Edge structure at line 91: +row 2: | 4+++ 4+ 5--- 5- +row 1: | 0+++ 0+ 1--- 1- 2++ 3-- 4+++ 4+ 5--- 5- +row 0: | -2+++ -2+++ -1--- -1--- 0+++ 0+ 2--- 2- 3++ 4-- 5++ 6-- +row -1: | -2+++ -2+ 0--- 0- 1+++ 1+++ 2--- 2--- +row -2: | -3++ -2-- -1+++ -1+ 0--- 0- + +{if} +{if} +{elseif} +! Missing `:' has been inserted. +<inserted text> + : +<to be read again> + elseif +l.92 if "a" if "ab">"b" elseif + path reverse (3,4): >="aa":foo... + +{("ab")>("b")} +{false} +{reverse((3,4))} +{path(path)} +{true} +{("a")>=("aa")} +{false} +{if} +{((xpart ',ypart '))-((1,yy))} +{((xpart '-1,linearform))<((xpart ',ypart '))} +{true} +{fi} +{else} +! Extra else. +l.93 ... if '-(1,yy)<': :fi else + def dup text t=[[t;save endd... +I'm ignoring this; it doesn't match any if. + +{def} +{def} +{def} + +||<tertiary>->show.substring(EXPR0)of("a" +(EXPR0)<-(2,-1) +{show} +{("a")&("bc")} +{((2,-1))substring("abc")} +>> "ba" +{tertiarydef} +{def} +{show} +{((23.3,4.5))subpath(path)} +Path at line 100, before choices: +(0,0){curl 2} + ..(13,4)..controls (12.84297,3.89865) and (12.68063,3.80606) + ..(12.51372,3.72261){curl 3} + ..{curl 4}(-1,-1){curl 4} + ..{curl 2}cycle + +Path at line 100, after choices: +(0,0)..controls (-40.15552,-36.7088) and (58.71173,33.50317) + ..(13,4)..controls (12.84297,3.89865) and (12.68063,3.80606) + ..(12.51372,3.72261)..controls (8.00914,2.1484) and (3.50458,0.5742) + ..(-1,-1)..controls (-0.66667,-0.66667) and (-0.33333,-0.33333) + ..cycle + +>> Path at line 100: +(0,0)..controls (-40.15552,-36.7088) and (58.71173,33.50317) + ..(13,4)..controls (12.84297,3.89865) and (12.68063,3.80606) + ..(12.51372,3.72261)..controls (8.00914,2.1484) and (3.50458,0.5742) + ..(-1,-1)..controls (-0.66667,-0.66667) and (-0.33333,-0.33333) + ..cycle + +{numspecial} +{(2)+(3)} + +++->[[dup.showtoken(EXPR0);]];def.x.expr.z.of(EXPR1)=z.enddef;texts(x=(( +EXPR1)+0)(EXPR1)+(EXPR1))("xx",foo((EXPR0)))=0]] +(EXPR0)<-path +(EXPR1)<-5 +{begingroup} + +dup<text>->[[(TEXT0);save +(TEXT0)<-showtoken(path) +{begingroup} +{showtoken} +> (path) +{save} +{restoring ;} +{endgroup} +{def} +! Missing symbolic token inserted. +<inserted text> + INACCESSIBLE +++->...]];def.x.expr.z.of(EXPR1) + =z.enddef;texts(x=((EXPR1)+0)... +<to be read again> + ; +l.100 ...cle;numspecial p~++2+3; + [[ +Sorry: You can't redefine a number, string, or expr. +I've inserted an inaccessible symbol so that your +definition will be completed without mixing me up too badly. + + +texts(TEXT0)(TEXT1)<expr>->for.n:=,for.n"yy":n,length.if.false:endfor(TE +XT1),(TEXT0),:if.string.n:forsuffixes.n=foo1,[foo(n)],':show(TEXT0),(TEX +T1)|(n;exitif.not('<='+((EXPR2),yy))endfor.for.m= :+endfor.for.m=alpha.s +tep-1.1 3$:+m.endfor.fi.endfor +(TEXT0)<-x=((5)+0)(5)+(5) +(TEXT1)<-"xx",foo((path)) +(EXPR2)<-0 +{for} +{for} +! Missing `=' has been inserted. +<to be read again> + "yy" +texts->for.n:=,for.n"yy" + :n,length.if.false:endfor(TEXT1),(TEX... +<to be read again> + ]] +<to be read again> + ; +l.100 ...cle;numspecial p~++2+3; + [[ +The next thing in this loop should have been `=' or `:='. +But don't worry; I'll pretend that an equals sign +was present, and I'll look for the values next. + +{loop value="yy"} +{if} +{false} +{false} +! Incomplete if; all text was ignored after line 100. +<inserted text> + fi +<to be read again> + ENDFOR +<for("yy")> ....if.false: ENDFOR + +texts->...length.if.false:endfor + (TEXT1),(TEXT0),:if.string.n:... +<to be read again> + ]] +<to be read again> + ; +l.100 ...cle;numspecial p~++2+3; + [[ +A forbidden `outer' token occurred in skipped text. +This kind of error happens when you say `if...' and forget +the matching `fi'. I've inserted a `fi'; this might work. + +{[repeat the loop]} +{length("xx")} + +foo(TEXT2)->begingroup(TEXT2)endgroup +(SUFFIX0)<- +(SUFFIX1)<-foo +(TEXT2)<-(path) +{begingroup} +{endgroup} + +x<expr>of<primary>->(EXPR0) +{(5)+(0)} +(EXPR0)<-5 +! Missing `of' has been inserted for x. +<to be read again> + (5) +<argument> x=((5)+0)(5) + +(5) +texts->...:endfor(TEXT1),(TEXT0) + ,:if.string.n:forsuffixes.n=f... +<to be read again> + ]] +<to be read again> + ; +l.100 ...cle;numspecial p~++2+3; + [[ +I've got the first argument; will look now for the other. + +(EXPR1)<-5 +{(5)+(5)} +{loop value="yy"} +{if} +{string("yy")} +{true} +{forsuffixes} +! Missing symbolic token inserted. +<inserted text> + INACCESSIBLE +<for("yy")> ...orsuffixes(EXPR0) + =foo1,[foo((EXPR0))],':show.x... +texts->...3$:+m.endfor.fi.endfor + +<to be read again> + ]] +<to be read again> + ; +l.100 ...cle;numspecial p~++2+3; + [[ +Sorry: You can't redefine a number, string, or expr. +I've inserted an inaccessible symbol so that your +definition will be completed without mixing me up too badly. + + +foo(TEXT2)->begingroup(TEXT2)endgroup +(SUFFIX0)<- +(SUFFIX1)<-foo +(TEXT2)<-("yy") +{begingroup} +{endgroup} +>> "yy" +! Improper subscript has been replaced by zero. +<for("yy")> ...o1,[foo((EXPR0))] + ,':show.x=((5)+0)(5)+(5),"xx"... +texts->...3$:+m.endfor.fi.endfor + +<to be read again> + ]] +<to be read again> + ; +l.100 ...cle;numspecial p~++2+3; + [[ +A bracketed subscript must have a known numeric value; +unfortunately, what I found was the value that appears just +above this error message. So I'll try a zero subscript. + +{loop value=foo1} +{show} + +x<expr>of<primary>->(EXPR0) +{(5)+(0)} +(EXPR0)<-5 +! Missing `of' has been inserted for x. +<to be read again> + (5) +<for(foo1)> show.x=((5)+0)(5) + +(5),"xx",foo((path))|(("yy");ex... +<for("yy")> ...'+((0),yy))endfor + .for.m= :+endfor.for.m=alpha.... +texts->...3$:+m.endfor.fi.endfor + +<to be read again> + ]] +<to be read again> + ; +l.100 ...cle;numspecial p~++2+3; + [[ +I've got the first argument; will look now for the other. + +(EXPR1)<-5 +{(5)+(5)} +>> 10 +>> "xx" +foo(TEXT2)->begingroup(TEXT2)endgroup +(SUFFIX0)<- +(SUFFIX1)<-foo +(TEXT2)<-(path) +{begingroup} +{endgroup} + +|<suffix>->,(SUFFIX0) +! Missing `)' has been inserted. +<to be read again> + ("yy") +<for(foo1)> ...o((path))|(("yy") + ;exitif.not('<='+((0),yy)) EN... +<for("yy")> ...'+((0),yy))endfor + .for.m= :+endfor.for.m=alpha.... +texts->...3$:+m.endfor.fi.endfor + +<to be read again> + ]] +<to be read again> + ; +l.100 ...cle;numspecial p~++2+3; + [[ +I've gotten to the end of the macro parameter list. +You might want to delete some tokens before continuing. + +(SUFFIX0)<- +>> Path at line 100: +(0,0)..controls (15,4) and (-15,-12) + ..(4,0) + +>> "yy" +{exitif} +{((xpart ',ypart '))+((0,yy))} +{((xpart ',ypart '))<=((xpart ',linearform))} +>> -yy +! Unknown relation will be considered false. +<to be read again> + ) +<for(foo1)> ...ot('<='+((0),yy)) + ENDFOR +<for("yy")> ...'+((0),yy))endfor + .for.m= :+endfor.for.m=alpha.... +texts->...3$:+m.endfor.fi.endfor + +<to be read again> + ]] +<to be read again> + ; +l.100 ...cle;numspecial p~++2+3; + [[ +Oh dear. I can't decide if the expression above is positive, +negative, or zero. So this comparison test won't be `true'. + +{[repeat the loop]} +{loop value=0} +{not(false)} +{true} +{for} +{for} +>> 0.5p3~ +! Improper initial value has been replaced by 0. +<to be read again> + step +<for("yy")> ....for.m=alpha.step + -1.1 3$:+m.endfor.fi ENDFOR +texts->...3$:+m.endfor.fi.endfor + +<to be read again> + ]] +<to be read again> + ; +l.100 ...cle;numspecial p~++2+3; + [[ +When you say `for x=a step b until c', +the initial value `a' and the step size `b' +and the final value `c' must have known numeric values. +I'm zeroing this one. Proceed, with fingers crossed. + +{-(1.1)} +! Missing `until' has been inserted. +<to be read again> + 3 +<for("yy")> ...=alpha.step-1.1 3 + $:+m.endfor.fi ENDFOR +texts->...3$:+m.endfor.fi.endfor + +<to be read again> + ]] +<to be read again> + ; +l.100 ...cle;numspecial p~++2+3; + [[ +I assume you meant to say `until' after `step'. +So I'll look for the final value and colon next. + +{(3)*(-1)} +{loop value=0} +{[repeat the loop]} +{loop value=-1.1} +{+(0)} +{[repeat the loop]} +{loop value=-2.20001} +{(0)+(-1.1)} +{[repeat the loop]} +{fi} +{[repeat the loop]} +{loop value=2} +{if} +{string(2)} +{false} +{[repeat the loop]} +{loop value=path} +{if} +{string(path)} +{false} +{[repeat the loop]} +{loop value=10} +{if} +{string(10)} +{false} +{[repeat the loop]} +{(-1.1)+(-2.20001)} +{endgroup} +{begingroup} +{let} +{save} +{def} +{def} + +texts->input +{input} +(trap.mf +if->endinput? +{endinput} +{if} +{known(0)} +{not(true)} +{false} +) +! Incomplete if; all text was ignored after line 2. +<inserted text> + fi +l.101 ...input enddef;texts trap + ]]; +The file ended while I was skipping conditional text. +This kind of error happens when you say `if...' and forget +the matching `fi'. I've inserted a `fi'; this might work. + +{restoring \} +{restoring if} +{endgroup} + +dup<text>->[[(TEXT0);save +Runaway text? +[[def.texts.secondary.x=primarydef.y++y=x@y.enddef;showtoken ETC. +! Forbidden token found while scanning a text argument. +<inserted text> + endgroup +<to be read again> + \ +l.102 ...showtoken++;x enddef]]\ + ; +It seems that a right delimiter was left out, +causing me to read past where you wanted me to stop. +I'll try to recover; but if the error is serious, +you'd better type `E' or `X' now and fix your file. + +(TEXT0)<-[[def.texts.secondary.x=primarydef.y++y=x@y.enddef;showtoken++; +x.enddef]] +{begingroup} +{begingroup} +{def} +{endgroup} +{save} +! Missing symbolic token inserted. +<inserted text> + INACCESSIBLE +<to be read again> + \ +l.102 ...showtoken++;x enddef]]\ + ; +Sorry: You can't redefine my error-recovery tokens. +I've inserted an inaccessible symbol so that your +definition will be completed without mixing me up too badly. + +{\} +{proofing:=1} + +texts<secondary>->primarydef.y++y=(EXPR0)@y.enddef;showtoken++;(EXPR0) +(EXPR0)<-"a" +{primarydef} +{showtoken} +> ++=primarydef'd macro: +("a")@(EXPR1) +{("a")&("b")} +ab +texts<secondary>->primarydef.y++y=(EXPR0)@y.enddef;showtoken++;(EXPR0) +{(1.00002)*(a1)} +{-(1.00002a1)} +(EXPR0)<--1.00002a1 +{primarydef} +{showtoken} +> ++=primarydef'd macro: +(-1.00002a1)@(EXPR1) +{(a2)+(a3)} +{(linearform)+(a4)} +{(linearform)+(a5)} +{(linearform)+(a6)} +{-(linearform)} +{(-1.00002a1)=(linearform)} +## a1=0.99998a6+0.99998a5+0.99998a4+0.99998a3+0.99998a2 +{-((a3,a2))} +{((-a3,-a2))/(0.99998)} +{(a4)+(a5)} +{begingroup} +{showdependencies} +a1=0.99998a6+0.99998a5+0.99998a4+0.99998a3+0.99998a2 +alpha=0.5p3~ +{endgroup} +{(linearform)+(a6)} +{(a3)+(a4)} +{(linearform)+(a5)} +{(linearform)+(a6)} +{-((linearform,linearform))} +{((-1.00002a3,-1.00002a2))=((linearform,linearform))} +## a2=0.99998a6+0.99998a5+0.99998a4+0.99998a3 +## a3=0.99998a6+0.99998a5+0.99998a4 +{restoring INACCESSIBLE} +{endgroup} +{(1.00002)*(0.25a4*4)} +{(0.25a4*4)+(1)} +{(0.25a5*4)+(0.25a6*4)} +{(0.25a4*4+1)=(linearform)} +## a4*4=0.99998a6*4+0.99998a5*4-3.99994 +{(0.9)*(0.5p3~)} +{(0.45p3~)+(7)} +{alpha:=0.45p3~+7} +## alpha=0.45p3~+7 +{showdependencies} +alpha=0.45p3~+7 +a4=0.25a6*4+0.25a5*4-0.99998 +a3=0.49998a6*4+0.49998a5*4-0.99997 +a2=0.99997a6*4+0.99997a5*4-1.99992 +a1=1.99992a6*4+1.99992a5*4-3.99982 +{(0.66667)-(0.25a6*4)} +{(0.25a6*4)=(-0.25a6*4+0.66667)} +## a6*4=1.33334 +{(0.25a5*4)=(0.33333)} +## a5*4=1.33331 +#### a4=-0.33333 +#### a3=0.33333 +#### a2=0.66664 +#### a1=1.33328 +{cull} +{(4)*(0.33333)} +{chardp:=27} +{charcode:=27} +{openwindow} +! Improper `openwindow'. +<to be read again> + ; +l.107 ...) to (0,0) at "whoops"; + addto p; shipout p; cull p; +Say `openwindow k from (r0,c0) to (r1,c1) at (x,y)', +where all quantities are known and k is between 0 and 15. + +{addto} +>> (8,1,5,-5,-5,5) +! Not a suitable variable. +<to be read again> + ; +l.107 ...) at "whoops"; addto p; + shipout p; cull p; +At this point I needed to see the name of a picture variable. +(Or perhaps you have indeed presented me with one; I might +have missed it, if it wasn't followed by the proper token.) +So I'll not change anything just now. + +{shipout} +! Variable p is the wrong type (transform). +<to be read again> + ; +l.107 ...s"; addto p; shipout p; + cull p; +I was looking for a "known" picture variable. +So I'll not change anything just now. + +{cull} +>> (8,1,5,-5,-5,5) +! Not a suitable variable. +<to be read again> + ; +l.107 ...o p; shipout p; cull p; + +At this point I needed to see the name of a picture variable. +(Or perhaps you have indeed presented me with one; I might +have missed it, if it wasn't followed by the proper token.) +So I'll not change anything just now. + +{openwindow} +{-(0.5)} + +Calling BLANKRECTANGLE(0,0,0,0) +Calling UPDATESCREEN +{special} +>> (8,1,5,-5,-5,5) +! Unsuitable expression. +<to be read again> + ; +l.108 ...$) at (0,0); special p; + numspecial "p"; +The expression shown above has the wrong type to be output. + +{numspecial} +>> "p" +! Unsuitable expression. +<to be read again> + ; +l.108 ...cial p; numspecial "p"; + +The expression shown above has the wrong type to be output. + +{openwindow} +{(32)*(1024)} +! Arithmetic overflow. +l.109 ....49999 from (0,32*1024) + to (1,31*1057) at (0,0); shi... +Uh, oh. A little while ago one of the quantities that I was +computing got too large, so I'm afraid your answers will be +somewhat askew. You'll probably have to adopt different +tactics next time. But I shall try to carry on anyway. + +{(31)*(1057)} + +Calling BLANKRECTANGLE(100,100,0,1) +Calling UPDATESCREEN +{shipout} +[27.-14] +Edge structure at line 109 (just shipped out): +row 0: | -3++ -2-- -1++ 4094-- +row -1: | -4096++ -3-- -2++ -1-- + +{openwindow} +Path at line 110, before choices: +(0,0) + +Path at line 110, after choices: +(0,0) + +{length(path)} + +Calling BLANKRECTANGLE(0,2,0,4) +Calling UPDATESCREEN +{openwindow} +{length((-1,0))} +{-(5)} + +Calling BLANKRECTANGLE(1,10,1,3) +Calling UPDATESCREEN +{display} +{nullpicture} +>> picture +! Not a suitable variable. +<to be read again> + ; +l.112 display nullpicture; + display p inwindow 3; display p in... +At this point I needed to see the name of a picture variable. +(Or perhaps you have indeed presented me with one; I might +have missed it, if it wasn't followed by the proper token.) +So I'll not change anything just now. + +{display} +>> 3 +! Bad window number. +<to be read again> + ; +l.112 ...; display p inwindow 3; + display p inwindow 6; +It should be the number of an open window. + +{display} +! Variable p is the wrong type (transform). +<to be read again> + ; +l.112 ...; display p inwindow 6; + +I was looking for a "known" picture variable. +So I'll not change anything just now. + +{display} + +Calling BLANKRECTANGLE(1,10,1,3) +Calling PAINTROW(2,1;4,5,6,10) +Calling UPDATESCREEN +{cull} +! Enormous number has been reduced. +l.113 ...opping (0.1,4095.999999 + ) withweight 3.5 +I can't handle numbers bigger than about 4095.99998; +so I've changed your constant to that maximum amount. + +! Weight must be -3, -2, -1, +1, +2, or +3. +<to be read again> + withweight +l.114 withweight + -3.5; display e0 inwindow 5.5; addto e0 also ... +I'll ignore the bad `with' clause and look for another. + +{-(3.5)} +! Bad culling amounts. +<to be read again> + ; +l.114 withweight-3.5; + display e0 inwindow 5.5; addto e0 also ... +Always cull by known amounts that exclude 0. + +{display} + +Calling BLANKRECTANGLE(1,10,1,3) +Calling PAINTROW(2,1;4,5,6,8,9,10) +Calling PAINTROW(1,1;6,7,8,9) +Calling UPDATESCREEN +{addto} +>> (8,1,5,-5,-5,5) +! Improper `addto'. +<to be read again> + ; +l.114 ...w 5.5; addto e0 also p; + addto e0 contour 0; +This expression should have specified a known picture. +So I'll not change anything just now. + +{addto} +>> 0 +! Improper `addto'. +<to be read again> + ; +l.114 ... p; addto e0 contour 0; + +This expression should have been a known path. +So I'll not change anything just now. + +{display} + +Calling BLANKRECTANGLE(0,2,0,4) +Calling PAINTROW(2,1;0,1) +Calling PAINTROW(1,1;0,1) +Calling PAINTROW(0,1;1,2) +Calling UPDATESCREEN +{addto} +! Not a cycle. +<to be read again> + ; +l.115 ...; addto e0 contour p~9; + +That contour should have ended with `..cycle' or `&cycle'. +So I'll not change anything just now. + +{display} +{(3)+(3)} + +Calling BLANKRECTANGLE(1,10,1,3) +Calling PAINTROW(2,1;4,5,6,10) +Calling UPDATESCREEN +{display} +{begingroup} +{vardef} +{endgroup} +! Variable e0e0 has been obliterated. +<to be read again> + ; +l.116 ...vardef e[]e=enddef;6]]; + +It seems you did a nasty thing---probably by accident, +but nevertheless you nearly hornswoggled me... +While I was evaluating the right-hand side of this +command, something happened, and the left-hand side +is no longer a variable! So I won't change anything. + +{addto} +{display} +Calling PAINTROW(1,1;0,1,2) +Calling PAINTROW(0,0;0,1,2) +Calling UPDATESCREEN +{ligtable} +{display} +{display} +Calling UPDATESCREEN +{show} +{begingroup} +{interim} +{tracingcommands:=0} + +lig(TEXT0)(TEXT1)->ligtable0::for*=1step1until60:0kern.boundarychar+*,en +dfor.skipto0;ligtable(TEXT0):(TEXT0)(TEXT1)0,skipto255;boundarychar:=bou +ndarychar+51.29999 +(TEXT0)<-"g" +(TEXT1)<-=:| + +lig(TEXT0)(TEXT1)->ligtable0::for*=1step1until60:0kern.boundarychar+*,en +dfor.skipto0;ligtable(TEXT0):(TEXT0)(TEXT1)0,skipto255;boundarychar:=bou +ndarychar+51.29999 +(TEXT0)<-"h":"i" +(TEXT1)<-|=: +! Character h is already in a ligtable. +<to be read again> + : +<argument> "h": + "i" +lig->...;ligtable(TEXT0):(TEXT0) + (TEXT1)0,skipto255;boundarych... +l.119 ...=:|); lig("h":"i")(|=:) + ; +It's not legal to label a character more than once. +So I'll not change anything just now. + + +lig(TEXT0)(TEXT1)->ligtable0::for*=1step1until60:0kern.boundarychar+*,en +dfor.skipto0;ligtable(TEXT0):(TEXT0)(TEXT1)0,skipto255;boundarychar:=bou +ndarychar+51.29999 +(TEXT0)<-"j" +(TEXT1)<-|=:> + +lig(TEXT0)(TEXT1)->ligtable0::for*=1step1until60:0kern.boundarychar+*,en +dfor.skipto0;ligtable(TEXT0):(TEXT0)(TEXT1)0,skipto255;boundarychar:=bou +ndarychar+51.29999 +(TEXT0)<-"k" +(TEXT1)<-=:|> + +lig(TEXT0)(TEXT1)->ligtable0::for*=1step1until60:0kern.boundarychar+*,en +dfor.skipto0;ligtable(TEXT0):(TEXT0)(TEXT1)0,skipto255;boundarychar:=bou +ndarychar+51.29999 +(TEXT0)<-"l" +(TEXT1)<-|=:|> +{restoring tracingcommands=2.1} +{endgroup} +>> vacuous +{begingroup} +{let} +{vardef} +{endgroup} +! Variable c.a1 has been obliterated. +<to be read again> + ; +l.121 ...;vardef b=enddef;1]] ]; + ligtable"m":0=:0,skipto5; +It seems you did a nasty thing---probably by accident, +but nevertheless you nearly hornswoggled me... +While I was evaluating the suffix of this variable, +something was redefined, and it's no longer a variable! +In order to get back on my feet, I've inserted `0' instead. + +{b1:=0} +! Variable b1 has been obliterated. +<to be read again> + ; +l.121 ...;vardef b=enddef;1]] ]; + ligtable"m":0=:0,skipto5; +It seems you did a nasty thing---probably by accident, +but nevertheless you nearly hornswoggled me... +While I was evaluating the right-hand side of this +command, something happened, and the left-hand side +is no longer a variable! So I won't change anything. + +{ligtable} +! A statement can't begin with `skipto'. +<to be read again> + !! +l.122 !! + ; errhelp 0; errmessage "Be like Jane"; +I was looking for the beginning of a new statement. +If you just proceed without changing anything, I'll ignore +everything up to the next `;'. Please insert a semicolon +now in front of anything that you don't want me to delete. +(See Chapter 27 of The METAFONTbook for an example.) + +! Extra tokens will be flushed. +<to be read again> + !! +l.122 !! + ; errhelp 0; errmessage "Be like Jane"; +I've just read as much of that statement as I could fathom, +so a semicolon should have been next. It's very puzzling... +but I'll try to get myself back together, by ignoring +everything up to the next `;'. Please insert a semicolon +now in front of anything that you don't want me to delete. +(See Chapter 27 of The METAFONTbook for an example.) + +{errhelp} +>> 0 +! Not a string. +<to be read again> + ; +l.122 !!; errhelp 0; + errmessage "Be like Jane"; +A message should be a known string expression. + +{errmessage} +! Be like Jane. +<to be read again> + ; +l.122 ...message "Be like Jane"; + +This error message was generated by an `errmessage' +command, so I can't give any explicit help. +Pretend that you're Miss Marple: Examine all clues, +and deduce the truth by inspired guesses. + +{errhelp} +{errmessage} +! . +<to be read again> + ; +l.123 ...e%%%lp%"; errmessage""; + errhelp ""; errmessage "Anot... +He% +lp + + +{errhelp} +{errmessage} +! Another. +<to be read again> + ; +l.123 ...; errmessage "Another"; + +(That was another `errmessage'.) + +{headerbyte} +>> 0 +! Improper location. +<to be read again> + ; +l.124 headerbyte 0; + headerbyte(48.5)substring(-9,9)of"long"; ... +I was looking for a known, positive number. +For safety's sake I'll ignore the present command. + +{headerbyte} +! Missing `:' has been inserted. +<to be read again> + substring +l.124 ...aderbyte(48.5)substring + (-9,9)of"long"; for\=0:\ +A colon should follow a headerbyte or fontinfo location. + +{-(9)} +{((-9,9))substring("long")} +>> "long" +! Invalid code has been replaced by 0. +<to be read again> + ; +l.124 ...ubstring(-9,9)of"long"; + for\=0:\ +I was looking for a number between 0 and 255, or for a +string of length 1. Didn't find it; will use 0 instead. + +{for} +Runaway loop? +! Forbidden token found while scanning the text of a for loop. +<inserted text> + endfor +<to be read again> + \ +l.124 ...-9,9)of"long"; for\=0:\ + +I suspect you have forgotten an `endfor', +causing me to read past where you wanted me to stop. +I'll try to recover; but if the error is serious, +you'd better type `E' or `X' now and fix your file. + +{loop value=0} +{[repeat the loop]} +{\} +{headerbyte} +{(2)*(0.33333)} +{fontdimen} +{(2)*(0.33333)} +>> "q" +! Improper font parameter. +<to be read again> + ; +l.125 ..."; fontdimen 9:2a6,"q"; + fontdimen 1:2048; +I'm zeroing this one. Proceed, with fingers crossed. + +{fontdimen} +{fontmaking:=1} +{extensible} +! Missing `:' has been inserted. +<to be read again> + 5 +l.126 ...king:=1; extensible 5 5 + ,"c"255.5,"d"; charlist 0:5:"... +I'm processing `extensible c: t,m,b,r'. + +! Missing `,' has been inserted. +<to be read again> + 255.5 +l.126 ...extensible 5 5,"c"255.5 + ,"d"; charlist 0:5:"a":"d"; +I'm processing `extensible c: t,m,b,r'. + +>> 255.5 +! Invalid code has been replaced by 0. +<to be read again> + , +l.126 ...xtensible 5 5,"c"255.5, + "d"; charlist 0:5:"a":"d"; +I was looking for a number between 0 and 255, or for a +string of length 1. Didn't find it; will use 0 instead. + +{charlist} +! Character code 5 is already extensible. +<to be read again> + : +l.126 ...,"d"; charlist 0:5:"a": + "d"; +It's not legal to label a character more than once. +So I'll not change anything just now. + +{ligtable} +! Too far to skip. +l.127 ligtable255:255:: + "a"=:"b","d" kern -2048,"c":0:99.5:"e"... +At most 127 lig/kern steps can separate skipto1 from 1::. + +{-(2048)} +! Character code 0 is already in a charlist. +<to be read again> + : +l.127 ...","d" kern -2048,"c":0: + 99.5:"e"|=:|"f",0kern'; +It's not legal to label a character more than once. +So I'll not change anything just now. + +>> (xpart ',ypart ') +! Improper kern. +<to be read again> + ; +l.127 ...99.5:"e"|=:|"f",0kern'; + +The amount of kern should be a known numeric value. +I'm zeroing this one. Proceed, with fingers crossed. + +{ligtable} +! Character code 5 is already extensible. +<to be read again> + : +l.128 ligtable 5: + 0; def clear(text x)=interim x:=$ enddef; cl... +It's not legal to label a character more than once. +So I'll not change anything just now. + +! Illegal ligtable step. +<to be read again> + ; +l.128 ligtable 5:0; + def clear(text x)=interim x:=$ enddef; cl... +I was looking for `=:' or `kern' here. + +{def} + +clear(TEXT0)->interim(TEXT0):=$ +(TEXT0)<-hppp +{interim} +{hppp:=-1} +{(0)=(0)} +! Redundant equation. +<to be read again> + ; +l.128 ...f; clear(hppp); vppp=0; + +I already knew that this equation was true. +But perhaps no harm has been done; let's continue. + +{begingroup} + +clear(TEXT0)->interim(TEXT0):=$ +(TEXT0)<-tracingmacros +{interim} +{tracingmacros:=-1} +{interim} +{tracingcommands:=-1} +Runaway text? +tracingoutput +! Forbidden token found while scanning a text argument. +<inserted text> + ) +<to be read again> + \ +l.129 ...); clear(tracingoutput\ + ; +It seems that a right delimiter was left out, +causing me to read past where you wanted me to stop. +I'll try to recover; but if the error is serious, +you'd better type `E' or `X' now and fix your file. + +{restoring proofing=1} +{restoring tracingoutput=1} +{restoring tracingcommands=2.1} +{restoring tracingmacros=1} +{endgroup} +{def} +{def} +{addto} +Path at line 139, before choices: +(0,0){4096,0} + ..{4096,0}(1,0){0,-4096} + ..{0,4096}cycle + +Path at line 139, after choices: +(0,0)..controls (0.33333,0) and (0.66667,0) + ..(1,0)..controls (1,-0.66667) and (0,-0.66667) + ..cycle + +> 0 ENE 1 (ESE SSE) SSW WSW WNW NNW 0 (NNE) +! Backwards path (turning number is negative). +<to be read again> + ; +l.139 ....{0,1}cycle withpen qq; + +The path doesn't have a counterclockwise orientation, +so I'll probably have trouble drawing it. +(See Chapter 27 of The METAFONTbook for more help.) + +Tracing edges at line 139: (weight 1) +@ Octant ENE (2 offsets), from (2.5,-0.5) to (1.5,-2) +@ retrograde line from (2.5,-0.5) to (0.5,-2) +(3,0)(3,-1)(1,-1) +@ transition line 0, from (0.5,-2) to (0.5,-2) +@ transition line 1, from (1.5,-2) to (2.5,-1.5) +@ transition line 2, from (2.5,-1.5) to (3.5,-0.5) +(1,-2)(3,-2)(3,-1)(4,-1) +@ retrograde line from (3.5,-0.5) to (1.5,-2) +(4,0)(4,-1)(2,-1) +@ Octant ESE (2 offsets), from (1.5,-2) to (-1,0) +@ retrograde line from (1.5,-2) to (-1,0) +(2,-2)(1,-2)(1,-1)(0,-1) +@ transition line 3, from (-1,0) to (0.5,-1.5) +@ transition line 2, from (0.5,-1.5) to (1.5,-2) +@ transition line 1, from (1.5,-2) to (1.5,-2) +(0,0)(0,-1)(1,-1) +@ retrograde line from (1.5,-2) to (-1,0) +(1,-2)(1,-1)(0,-1) +@ Octant SSE (0 offsets), from (-1,0) to (-1,0) +@ transition line 0, from (-1,0) to (-1,0) +@ Octant SSW (2 offsets), from (-1,0) to (-0.82407,0.67593) +@ retrograde line from (-1,0) to (-0.5,1.5) +(0,0)(-1,0)(-1,1)(0,1) +@ transition line 3, from (-0.5,1.5) to (-0.5,1.5) +@ transition line 2, from (-0.5,1.5) to (-1,0) +@ transition line 1, from (-1.06265,-0.39499) to (-0.56265,1.10501) +@ transition line 2, from (-0.82407,0.67593) to (-1.32407,-0.82407) +@ transition line 1, from (-1.32407,-0.82407) to (-1.32407,-0.82407) +(0,2)(0,1)(-1,1) +@ retrograde line from (-1.32407,-0.82407) to (-0.82407,0.67593) +(-1,-1) +@ Octant WSW (2 offsets), from (-0.82407,0.67593) to (1,2) +@ retrograde line from (-0.82407,0.67593) to (1.17593,2.17593) +(-1,1)(0,1) +@ transition line 0, from (1.17593,2.17593) to (1.17593,2.17593) +@ transition line 1, from (1.17593,2.17593) to (0.17593,1.67593) +@ transition line 2, from (0,1.5) to (-1,0.5) +(0,2) +@ retrograde line from (-1,0.5) to (1,2) +(0,1) +@ Octant WNW (2 offsets), from (1,2) to (3.32407,0.17593) +@ retrograde line from (1,2) to (3.5,0) +(0,2)(2,2)(2,1)(3,1) +@ transition line 3, from (3.5,0) to (2,1.5) +@ transition line 2, from (1.82407,1.67593) to (0.82407,2.17593) +@ transition line 1, from (0.82407,2.17593) to (0.82407,2.17593) +(3,0)(3,1)(2,1) +@ retrograde line from (0.82407,2.17593) to (3.32407,0.17593) +(2,2)(2,1)(3,1) +@ Octant NNW (0 offsets), from (3.32407,0.17593) to (3,1) +@ transition line 0, from (3.32407,0.17593) to (3.32407,0.17593) +(3,0) +@ Octant NNE (2 offsets), from (3,1) to (2.5,-0.5) +@ retrograde line from (3,1) to (2.5,-0.5) +(3,1) +@ transition line 3, from (2.5,-0.5) to (2.5,-0.5) +@ transition line 2, from (2.5,-0.5) to (3,1) +@ transition line 1, from (3,1) to (3,1) +(3,0) +@ retrograde line from (3,1) to (2.5,-0.5) +(3,1)(3,0). + + +f(SUFFIX0)(EXPR1)(EXPR2)(TEXT3)->numeric.w;show(EXPR1);addto(SUFFIX0)con +tour(0,0)..(2,0)..(1,$)..(1,1)..cycle.withpen.qq;addto(SUFFIX0)doublepat +h(0,0){1,1}..{2,1}(2,1)withpen.qq;addto(SUFFIX0)doublepath(($,$){1,0}..( +1,1){1,0})scaled0.5withpen.nullpen;cull(SUFFIX0)keeping(4,4)withweight1. +5; +! Missing `]' has been inserted. +<to be read again> + , +l.140 f(e[3, + w); g(e3,transformed p,penoffset-(1,1.3)of(pencir... +I've seen a `[' and a subscript value, in a suffix, +so a right bracket should have come next. +I shall pretend that one was there. + +(SUFFIX0)<-e3 +(EXPR1)<-unknown path w +! Missing argument to f. +<to be read again> + ; +l.140 f(e[3,w); + g(e3,transformed p,penoffset-(1,1.3)of(pencir... +That macro has more parameters than you thought. +I'll continue by pretending that each missing argument +is either zero or null. + +(EXPR2)<-0 +! Missing argument to f. +<to be read again> + ; +l.140 f(e[3,w); + g(e3,transformed p,penoffset-(1,1.3)of(pencir... +That macro has more parameters than you thought. +I'll continue by pretending that each missing argument +is either zero or null. + +(TEXT3)<- +{numeric} +{show} +>> unknown path %CAPSULE1615 +{addto} +Path at line 140, before choices: +(0,0) + ..(2,0) + ..(1,-1) + ..(1,1) + ..cycle + +Path at line 140, after choices: +(0,0)..controls (0.56189,-0.5286) and (1.43811,0.5286) + ..(2,0)..controls (2.70831,-0.66634) and (1.66634,-1.70831) + ..(1,-1)..controls (0.4714,-0.43811) and (1.5286,0.43811) + ..(1,1)..controls (0.33366,1.70831) and (-0.70831,0.66634) + ..cycle + +> 0 ESE ENE ESE 1 SSE SSW WSW WNW NNW 2 NNE NNW 3 WNW WSW SSW SSE ESE 0 +! Strange path (turning number is zero). +<to be read again> + ; +f->.....(1,1)..cycle.withpen.qq; + addto(SUFFIX0)doublepath(0,0)... +<to be read again> + ; +l.140 f(e[3,w); + g(e3,transformed p,penoffset-(1,1.3)of(pencir... +The path doesn't have a counterclockwise orientation, +so I'll probably have trouble drawing it. +(See Chapter 27 of The METAFONTbook for more help.) + +Tracing edges at line 140: (weight 1) +@ Octant ENE (2 offsets), from (0.26321,-2.6526) to (1.73679,-2.34741) +@ transition line 0, from (0.26321,-2.6526) to (0.26321,-2.6526) +@ transition line 1, from (1.73679,-2.34741) to (2.73679,-1.84741) +@ transition line 2, from (2.73679,-1.84741) to (3.73679,-0.84741) +(1,-3)(1,-2)(3,-2) +@ retrograde line from (3.73679,-0.84741) to (1.73679,-2.34741) +(3,-1) +@ Octant ESE (2 offsets), from (1.73679,-2.34741) to (-0.25465,-0.56029) +@ retrograde line from (1.73679,-2.34741) to (-0.76321,-0.34741) +(3,-2)(1,-2)(1,-1)(-1,-1) +@ transition line 3, from (-0.76321,-0.34741) to (0.73679,-1.84741) +@ transition line 2, from (0.73679,-1.84741) to (1.73679,-2.34741) +@ transition line 1, from (2.05333,-2.41772) to (1.05333,-1.91772) +@ transition line 2, from (1.24535,-2.06029) to (2.24535,-2.56029) +@ transition line 1, from (2.24535,-2.56029) to (2.24535,-2.56029) +(-1,0)(-1,-1)(0,-1)(0,-2)(2,-2) +@ retrograde line from (2.24535,-2.56029) to (-0.25465,-0.56029) +(2,-3)(2,-2)(1,-2) +@ Octant SSE (0 offsets), from (-0.25465,-0.56029) to (0,-1.13196) +@ transition line 0, from (-0.25465,-0.56029) to (-0.25465,-0.56029) +@ Octant SSW (2 offsets), from (0,-1.13196) to (0.25,-0.25) +@ retrograde line from (0,-1.13196) to (0.5,0.36804) +(1,-1)(0,-1) +@ transition line 3, from (0.5,0.36804) to (0.5,0.36804) +@ transition line 2, from (0.5,0.36804) to (0,-1.13196) +@ transition line 1, from (-0.04594,-1.41693) to (0.45406,0.08307) +@ transition line 2, from (0.25,-0.25) to (-0.25,-1.75) +@ transition line 1, from (-0.25,-1.75) to (-0.25,-1.75) +(0,0) +@ retrograde line from (-0.25,-1.75) to (0.25,-0.25) +(0,-2) +@ Octant WSW (2 offsets), from (0.25,-0.25) to (1.63197,1) +@ retrograde line from (0.25,-0.25) to (2.25,1.25) +(0,0)(1,0) +@ transition line 0, from (2.25,1.25) to (2.25,1.25) +@ transition line 1, from (2.25,1.25) to (1.25,0.75) +@ transition line 2, from (1.03186,0.59352) to (2.03186,1.09352) +@ transition line 1, from (1.63197,1) to (0.63197,0.5) +@ transition line 2, from (0.63197,0.5) to (-0.36803,-0.5) +(1,1) +@ retrograde line from (-0.36803,-0.5) to (1.63197,1) +(1,0) +@ Octant WNW (2 offsets), from (1.63197,1) to (3.56024,-0.74529) +@ retrograde line from (1.63197,1) to (4.13197,-1) +(1,1)(2,1)(2,0)(4,0) +@ transition line 3, from (4.13197,-1) to (2.63197,0.5) +@ transition line 2, from (2.63197,0.5) to (1.63197,1) +@ transition line 1, from (1.29863,1.07983) to (2.29863,0.57983) +@ transition line 2, from (2.06024,0.75471) to (1.06024,1.25471) +@ transition line 1, from (1.06024,1.25471) to (1.06024,1.25471) +(4,-1)(4,0)(3,0) +@ retrograde line from (1.06024,1.25471) to (3.56024,-0.74529) +(3,1)(2,1)(2,0)(3,0) +@ Octant NNW (0 offsets), from (3.56024,-0.74529) to (3.3474,-0.23679) +@ transition line 0, from (3.56024,-0.74529) to (3.56024,-0.74529) +(3,-1) +@ Octant NNE (2 offsets), from (3.3474,-0.23679) to (3.65259,1.23679) +@ retrograde line from (3.3474,-0.23679) to (2.8474,-1.73679) +(3,0) +@ transition line 3, from (2.8474,-1.73679) to (2.8474,-1.73679) +@ transition line 2, from (2.8474,-1.73679) to (3.3474,-0.23679) +@ transition line 1, from (3.65259,1.23679) to (3.65259,1.23679) +(3,-2) +@ Octant NNW (0 offsets), from (3.65259,1.23679) to (3.43976,1.74529) +@ transition line 0, from (3.65259,1.23679) to (3.65259,1.23679) +(3,1)(4,1) +@ Octant WNW (2 offsets), from (3.43976,1.74529) to (0.36803,4) +@ transition line 3, from (3.43976,1.74529) to (1.93976,3.24529) +@ transition line 2, from (1.70137,3.42017) to (0.70137,3.92017) +@ transition line 1, from (0.36803,4) to (0.36803,4) +(4,2)(3,2)(3,3)(2,3) +@ Octant WSW (2 offsets), from (0.36803,4) to (-2.25,2.25) +@ transition line 0, from (0.36803,4) to (0.36803,4) +@ transition line 1, from (-0.03186,3.90648) to (-1.03186,3.40648) +@ transition line 2, from (-1.25,3.25) to (-2.25,2.25) +(2,4)(-1,4)(-1,3)(-2,3) +@ Octant SSW (2 offsets), from (-2.25,2.25) to (-3,0.13196) +@ transition line 3, from (-2.25,2.25) to (-2.25,2.25) +@ transition line 2, from (-2.45406,1.91695) to (-2.95406,0.41695) +@ transition line 1, from (-3,0.13196) to (-3,0.13196) +(-2,2)(-3,2) +@ Octant SSE (0 offsets), from (-3,0.13196) to (-2.74533,-0.43973) +@ transition line 0, from (-3,0.13196) to (-3,0.13196) +@ Octant ESE (2 offsets), from (-2.74533,-0.43973) to (0.26321,-2.6526) +@ transition line 3, from (-2.74533,-0.43973) to (-1.24533,-1.93973) +@ transition line 2, from (-1.05331,-2.08229) to (-0.05331,-2.58229) +@ transition line 1, from (0.26321,-2.6526) to (0.26321,-2.6526) +(-3,-1)(-2,-1)(-2,-2)(0,-2)(0,-3). + +{addto} +Path at line 140, before choices: +(0,0){2896.30943,2896.30934} + ..{3663.57385,1831.78674}(2,1) + +Path at line 140, after choices: +(0,0)..controls (0.53566,0.53566) and (1.32689,0.66344) + ..(2,1) + +Tracing edges at line 140: (weight 1) +@ Octant ENE (2 offsets), from (0,-2.5) to (4,0) +@ transition line 0, from (0,-2.5) to (0,-2.5) +@ transition line 1, from (0,-2.5) to (1,-2) +@ transition line 2, from (1.607,-1.57747) to (0.607,-2.07747) +@ transition line 1, from (2,-1.5) to (3,-1) +@ transition line 2, from (3,-1) to (4,0) +(2,-2)(2,-1)(4,-1) +@ Octant NNE (2 offsets), from (4,0) to (4.5,1.5) +@ transition line 3, from (4,0) to (4,0) +@ transition line 2, from (4,0) to (4.5,1.5) +@ transition line 1, from (4.5,1.5) to (4.5,1.5) +(4,1)(5,1) +@ Octant NNW (0 offsets), from (4.5,1.5) to (4.5,1.5) +@ transition line 0, from (4.5,1.5) to (4.5,1.5) +@ Octant WNW (2 offsets), from (4.5,1.5) to (2,3.5) +@ transition line 3, from (4.5,1.5) to (3,3) +@ transition line 2, from (3,3) to (2,3.5) +@ transition line 1, from (2,3.5) to (2,3.5) +(5,2)(4,2)(4,3)(2,3) +@ Octant WSW (2 offsets), from (2,3.5) to (-2,1) +@ transition line 0, from (2,3.5) to (2,3.5) +@ transition line 1, from (0.607,2.92253) to (-0.393,2.42253) +@ transition line 2, from (-1,2) to (-2,1) +(2,4)(2,3)(0,3)(0,2)(-1,2) +@ Octant SSW (2 offsets), from (-2,1) to (-2.5,-0.5) +@ transition line 3, from (-2,1) to (-2,1) +@ transition line 2, from (-2,1) to (-2.5,-0.5) +@ transition line 1, from (-2.5,-0.5) to (-2.5,-0.5) +(-1,1)(-2,1) +@ Octant SSE (0 offsets), from (-2.5,-0.5) to (-2.5,-0.5) +@ transition line 0, from (-2.5,-0.5) to (-2.5,-0.5) +@ Octant ESE (2 offsets), from (-2.5,-0.5) to (0,-2.5) +@ transition line 3, from (-2.5,-0.5) to (-1,-2) +@ transition line 2, from (-1,-2) to (0,-2.5) +@ transition line 1, from (0,-2.5) to (0,-2.5) +(-2,-1)(-1,-1)(-1,-2). + +{addto} +Path at line 140, before choices: +(-1,-1){4096,0} + ..{4096,0}(1,1) + +Path at line 140, after choices: +(-1,-1)..controls (0.10457,-1) and (-0.10457,1) + ..(1,1) + +{(path)scaled(0.5)} +{nullpen} +Tracing edges at line 140: (weight 1) +(0,0)(0,1)(0,0). + +{cull} + +g(SUFFIX0)->show(SUFFIX0) +(SUFFIX0)<-e3 +! Too many arguments to g; + Missing `)' has been inserted. +l.140 f(e[3,w); g(e3, + transformed p,penoffset-(1,1.3)of(pencir... +I'm going to assume that the comma I just read was a +right delimiter, and then I'll begin expanding the macro. +You might want to delete some tokens before continuing. + +{show} +{(picture)transformed((8,1,5,-5,-5,5))} +>> Edge structure at line 140: + +{-((1,1.3))} +{pencircle} +{(future pen)scaled(20)} +{-(0.5)} +{(future pen)yscaled(-0.5)} +Pen polygon at line 140 (newly created): +(1.5,-5) + .. (4.5,-4.5) + .. (6,-4) + .. (8,-3) + .. (9.5,-1.5) + .. (10,-0.5) + .. (10,0.5) + .. (9.5,1.5) + .. (8,3) + .. (6,4) + .. (4.5,4.5) + .. (1.5,5) + .. (-1.5,5) + .. (-4.5,4.5) + .. (-6,4) + .. (-8,3) + .. (-9.5,1.5) + .. (-10,0.5) + .. (-10,-0.5) + .. (-9.5,-1.5) + .. (-8,-3) + .. (-6,-4) + .. (-4.5,-4.5) + .. (-1.5,-5) + .. cycle + +{((-1,-1.3))penoffset(pen)} +>> (-9.5,1.5) +Path at line 141, before choices: +(0,0)..controls (1,1) and (-1,1) + ..(3,0) + +Path at line 141, after choices: +(0,0)..controls (1,1) and (-1,1) + ..(3,0) + +{((0,1))directiontime(path)} +>> 0.33333 +{(3.14159)point(path)} +{((3.14159,4))subpath(path)} +{((7.68668,2.72437))intersectiontimes(path)} +>> (0,0) +Path at line 143, before choices: +(-1,1.1){curl 1} + ..{curl 1}(1,-1) + +Path at line 143, after choices: +(-1,1.1)..controls (-0.33333,0.40001) and (0.33333,-0.3) + ..(1,-1) + +{(-1)precontrol((0,0))} +{(path)intersectiontimes((0,0))} +>> (-1,-1) +{addto} +{-(4094.99998)} +{-(0.00002)} +Path at line 144, before choices: +(-4094.99998,0){curl 1} + ..{curl 1}(4094.99998,-0.00002) + +Path at line 144, after choices: +(-4094.99998,0)..controls (-1365,0) and (1365,-0.00002) + ..(4094.99998,-0.00002) + +{pencircle} +{(0)penoffset(future pen)} +Pen polygon at line 145 (newly created): +(0.5,0) + .. (0,0.5) + .. (-0.5,0) + .. (0,-0.5) + .. cycle + +>> 0 +>> pen +! Not implemented: penoffset(known numeric)of(pen). +<to be read again> + ; +l.145 pencircle; + addto e3 also e3 shifted (0,257); ,"flushed ... +I'm afraid I don't know how to apply that operation to that +combination of types. Continue, and I'll return the second +argument (see above) as the result of the operation. + +Tracing edges at line 145: (weight 1) +@ Octant ESE (1 offset), from (-4095.49998,0) to (4094.99998,-0.50002) +@ transition line 2, from (-4095.49998,0) to (-4094.99998,-0.5) +@ transition line 1, from (4094.99998,-0.50002) to (4094.99998,-0.50002) +(-4095,0) +@ Octant ENE (1 offset), from (4094.99998,-0.50002) to (4095.49998,-0.00 +002) +@ transition line 0, from (4094.99998,-0.50002) to (4094.99998,-0.50002) +@ transition line 1, from (4094.99998,-0.50002) to (4095.49998,-0.00002) +(-4095,-1)(4095,-1) +@ Octant NNE (0 offsets), from (4095.49998,-0.00002) to (4095.49998,-0.0 +0002) +@ transition line 1, from (4095.49998,-0.00002) to (4095.49998,-0.00002) +@ Octant NNW (0 offsets), from (4095.49998,-0.00002) to (4095.49998,-0.0 +0002) +@ transition line 0, from (4095.49998,-0.00002) to (4095.49998,-0.00002) +@ Octant WNW (1 offset), from (4095.49998,-0.00002) to (-4094.99998,0.5) +@ transition line 2, from (4095.49998,-0.00002) to (4094.99998,0.49998) +@ transition line 1, from (-4094.99998,0.5) to (-4094.99998,0.5) +(4095,0)(-4095,0) +@ Octant WSW (1 offset), from (-4094.99998,0.5) to (-4095.49998,0) +@ transition line 0, from (-4094.99998,0.5) to (-4094.99998,0.5) +@ transition line 1, from (-4094.99998,0.5) to (-4095.49998,0) +(-4095,1) +@ Octant SSW (0 offsets), from (-4095.49998,0) to (-4095.49998,0) +@ transition line 1, from (-4095.49998,0) to (-4095.49998,0) +@ Octant SSE (0 offsets), from (-4095.49998,0) to (-4095.49998,0) +@ transition line 0, from (-4095.49998,0) to (-4095.49998,0) +(-4095,0). + +{addto} +{(picture)shifted((0,257))} +! A statement can't begin with `,'. +<to be read again> + , +l.145 ...o e3 shifted (0,257); , + "flushed with pride"; numeric... +I was looking for the beginning of a new statement. +If you just proceed without changing anything, I'll ignore +everything up to the next `;'. Please insert a semicolon +now in front of anything that you don't want me to delete. +(See Chapter 27 of The METAFONTbook for an example.) + +! Extra tokens will be flushed. +<to be read again> + , +l.145 ...o e3 shifted (0,257); , + "flushed with pride"; numeric... +I've just read as much of that statement as I could fathom, +so a semicolon should have been next. It's very puzzling... +but I'll try to get myself back together, by ignoring +everything up to the next `;'. Please insert a semicolon +now in front of anything that you don't want me to delete. +(See Chapter 27 of The METAFONTbook for an example.) + +{numeric} +{def} + +f(EXPR0)(EXPR1)(EXPR2)->showdependencies;tracingcapsules:=1;showdependen +cies;show1/3(3,6)*(((EXPR0)+(EXPR1))+((EXPR1)-(EXPR0))),(1,1)/sqrt2zscal +ed((EXPR0)+1,(EXPR0)+2)-((EXPR0)+1,(EXPR0)+2)rotated45,(0,1)zscaled(1,(E +XPR1)+2)-(1,(EXPR1)+2)rotated90 +{(xx)+(1)} +{(xx+1)/(0.3)} +(EXPR0)<-3.3333xx+3.3333 +{(yy)-(1)} +{(yy-1)/(0.5)} +(EXPR1)<-2yy-2 +(EXPR2)<-(xx,0) +{showdependencies} +alpha=0.45p3~+7 +{tracingcapsules:=1} +{showdependencies} +xpart %CAPSULE1359=xx +%CAPSULE1367=2yy-2 +%CAPSULE1375 = 3.3333xx+3.3333 +alpha=0.45p3~+7 +{show} +{(1/3)*((3,6))} +{(3.3333xx+3.3333)+(2yy-2)} +{(2yy-2)-(3.3333xx+3.3333)} +{(linearform)+(linearform)} +{((1,2))*(4yy-4)} +>> (4yy-4,8yy-8) +{sqrt(2)} +{((1,1))/(1.41422)} +{(3.3333xx+3.3333)+(1)} +{(3.3333xx+3.3333)+(2)} +{((0.7071,0.7071))zscaled((3.3333xx+4.3333,3.3333xx+5.3333))} +{(3.3333xx+3.3333)+(1)} +{(3.3333xx+3.3333)+(2)} +{((3.3333xx+4.3333,3.3333xx+5.3333))rotated(45)} +{((-0.7071,4.71399xx+6.83531))-((-0.7071,4.71399xx+6.83531))} +>> (0,0) +{(2yy-2)+(2)} +{((0,1))zscaled((1,2yy))} +{(2yy-2)+(2)} +{((1,2yy))rotated(90)} +{((-2yy,1))-((-2yy,1))} +>> (0,0) +{(1000)*(o3)} +{(o1)-(o2)} +{(4000)*(linearform)} +{(1000o3)-(linearform)} +{(4000)*(o2)} +{(linearform)+(4000o2)} +{(linearform)+(9)} +{(0.01)*(o3)} +{-(0.01o3)} +{(3)*(ooo)} +{(-0.01o3)+(3ooo)} +{begingroup} +{(o2)+(o4)} +{(linearform)+(o5)} +{(linearform)-(20)} +{(9/10)*(linearform)} +{(oo)=(linearform)} +## oo=0.9o5+0.9o4+0.9o2-18 +{(0.5)*(o2)} +{(o1)+(0.5o2)} +{(8/9)*(linearform)} +{(o5)=(linearform)} +## o5=0.44444o2+0.88889o1 +{(o4)=(linearform)} +## o4=0.44444o2+0.88889o1 +{(0.0001)*(o2)} +{-(0.0001o2)} +{(o6)=(-0.0001o2)} +## o6=-0.0001o2 +{showdependencies} +o6=-0.0001o2 +o4=0.44444o2+0.88889o1 +o5=0.44444o2+0.88889o1 +oo=1.7o2+1.6o1-18 +%CAPSULE382 = 3ooo-0.01o3 +xpart %CAPSULE1049 = 8000o2-4000o1+1000o3+9 +xpart %CAPSULE1383=xpart ' +ypart %CAPSULE1383=ypart ' +alpha=0.45p3~+7 +{numeric} +### 4000o1 = -xpart %CAPSULE1049+8000o2+1000o3+9 +### -4.87383o2 = -oo-0.0004xpart %CAPSULE1049+0.39673o3-17.99643 +#### o6=-0.00027 +### -0.04366o3 = -o4+0.46689oo+8.40439 +### -o4 = -o5 +### 0.22894o5 = -%CAPSULE382+0.10689oo+3ooo+1.92412 +{begingroup} +{pair} +### -alfa=-xpart %CAPSULE1494 +{endgroup} +{xpart((xpart %CAPSULE1494,0))} +### -xpart %CAPSULE17=-%CAPSULE1176 +{endgroup} +{(%CAPSULE382)+(%CAPSULE1176)} +### -%CAPSULE382=-%CAPSULE1893+%CAPSULE1176 +### -%CAPSULE1893=-ypart %CAPSULE1049 +{begingroup} +{save} +{(1)-(p$)} +{restoring p} +### p$=-ypart %CAPSULE604+1 +{endgroup} +{(2/3)*((-ypart %CAPSULE604+1,ypart %CAPSULE604))} +### ypart %CAPSULE604=-xpart %CAPSULE604+1 +### -0.66667xpart %CAPSULE604=-xpart %CAPSULE1889 +{-((xpart %CAPSULE1889,-xpart %CAPSULE1889+0.66667))} +### xpart %CAPSULE1889=-xpart %CAPSULE1172 +{((xpart %CAPSULE1049,ypart %CAPSULE1049))=((xpart %CAPSULE1172,-xpart % +CAPSULE1172-0.66667))} +## xpart %CAPSULE1172=-ypart %CAPSULE1049-0.66667 +## ypart %CAPSULE1049=-xpart %CAPSULE1049-0.66667 +### -xpart %CAPSULE1049=-xpart %CAPSULE1172 +{((xpart ',ypart '))=((xpart %CAPSULE1172,-xpart %CAPSULE1172-0.66667))} +## xpart %CAPSULE1172=-ypart '-0.66667 +## xpart '=-ypart '-0.66667 +{begingroup} +{(0.5)*(ooo)} +{begingroup} +{numeric} +### -ooo=-%CAPSULE1494 +{endgroup} +{(%CAPSULE1494)+(1)} +### -%CAPSULE1494=-%CAPSULE1350+1 +{(1/2)*(%CAPSULE1350)} +### -0.5%CAPSULE1350=-%CAPSULE1367 +{-(%CAPSULE1367)} +### %CAPSULE1367=-%CAPSULE1893 +{(2)*(%CAPSULE1893)} +### -2%CAPSULE1893=-%CAPSULE1885 +{(-0.5%CAPSULE1885-0.5)=(%CAPSULE1885)} +## %CAPSULE1885=-0.33333 +{(oo)=(-0.33333)} +## oo=-0.33333 +{endgroup} +{(4000)*(-0.33333)} +{yoffset:=-1333.31299} +{xoffset:=-1333.31299} +{for} +{sqrt(-1)} +! Square root of -1 has been replaced by 0. +l.153 for @=angle(sqrt$, + mlog$):charext:=uniformdeviate$;charh... +Since I don't take square roots of negative numbers, +I'm zeroing this one. Proceed, with fingers crossed. + +{mlog(-1)} +! Logarithm of -1 has been replaced by 0. +l.153 for @=angle(sqrt$,mlog$) + :charext:=uniformdeviate$;charh... +Since I don't take logs of non-positive numbers, +I'm zeroing this one. Proceed, with fingers crossed. + +{angle((0,0))} +! angle(0,0) is taken as zero. +l.153 for @=angle(sqrt$,mlog$): + charext:=uniformdeviate$;charh... +The `angle' between two identical points is undefined. +I'm zeroing this one. Proceed, with fingers crossed. + +Runaway loop? +charext:=uniformdeviate$;charht:=2048;granularity:=-8; ETC. +! Forbidden token found while scanning the text of a for loop. +<inserted text> + endfor +<to be read again> + endtext +l.163 ... showstats; bye endtext + +I suspect you have forgotten an `endfor', +causing me to read past where you wanted me to stop. +I'll try to recover; but if the error is serious, +you'd better type `E' or `X' now and fix your file. + +{loop value=0} +{uniformdeviate(-1)} +{charext:=-0.00507} +{charht:=2048} +{-(8)} +{granularity:=-8} +{addto} +{-(100)} +{-(99)} +Path at line 163, before choices: +(0,-100)..tension 500 + ..(100,-99)..tension 3000 + ..cycle + +Path at line 163, after choices: +(0,-100)..controls (-36.92659,-103.51175) and (136.98943,-101.7726) + ..(100,-99)..controls (99.9889,-98.99916) and (0.01108,-99.99895) + ..cycle + +Tracing edges at line 163: (weight 1) +(-3,-101)(-3,-102)(71,-102)(71,-101)(99,-101)(99,-100)(103,-100) +(103,-99)(48,-99)(48,-100)(-7,-100)(-7,-101). + +{tracingoutput:=0} +{shipout} +! Enormous charht has been reduced. +<to be read again> + ; +<for(0)> ...=(EXPR0);shipout.e3; + special"bye";interim.char99="... +<inserted text> endfor + +<to be read again> + endtext +l.163 ... showstats; bye endtext + +Font metric dimensions must be less than 2048pt. + +[109] +{special} +{interim} +! The token `char' isn't an internal quantity. +<to be read again> + char +<for(0)> ...al"bye";interim.char + 99="c";true=false;[[clear(tra... +<inserted text> endfor + +<to be read again> + endtext +l.163 ... showstats; bye endtext + +Something like `tracingonline' should follow `interim'. + +{char(99)} +{("c")=("c")} +! Redundant equation. +<to be read again> + ; +<for(0)> ...;interim.char99="c"; + true=false;[[clear(tracingcom... +<inserted text> endfor + +<to be read again> + endtext +l.163 ... showstats; bye endtext + +I already knew that this equation was true. +But perhaps no harm has been done; let's continue. + +{true} +{false} +{(true)=(false)} +! Inconsistent equation. +<to be read again> + ; +<for(0)> ...ar99="c";true=false; + [[clear(tracingcommands);char... +<inserted text> endfor + +<to be read again> + endtext +l.163 ... showstats; bye endtext + +The equation I just read contradicts what was said before. +But don't worry; continue and I'll just ignore it. + +{begingroup} + +clear(TEXT0)->interim(TEXT0):=$ +(TEXT0)<-tracingcommands +{interim} +{tracingcommands:=-1} +! Enormous charht has been reduced. +<to be read again> + ; +<for(0)> ...shipout+nullpicture; + "careful"for.for=(EXPR0)step2... +<inserted text> endfor + +<to be read again> + endtext +l.163 ... showstats; bye endtext + +Font metric dimensions must be less than 2048pt. + +[0] +careful METAFONT METAFONT METAFONT METAFONT METAFONT METAFONT METAFONT M +ETAFONT METAFONT METAFONT METAFONT METAFONT METAFONT METAFONT METAFONT M +ETAFONT METAFONT METAFONT METAFONT METAFONT METAFONT METAFONT METAFONT M +ETAFONT METAFONT METAFONT METAFONT +{restoring tracingcommands=2.1} +{endgroup} +{scrollmode} + +{char(31)} +{("hello again")&("^^_")} +hello again^^_ +{save} +### -0.45p3~=-alpha+7 +{-(0.043)} +{fillin:=-0.043} +{def} +{begingroup} +{tracingspecs:=1} +{show} +{nullpen} + +>> Pen polygon at line 163: +(0,0) + .. cycle + +{makepath(pen)} + +>> Path at line 163: +(1,-2)..controls (1,-2) and (2,-1) + ..(2,-1)..controls (2,-1) and (2.5,0.5) + ..(2.5,0.5)..controls (2.5,0.5) and (1,2) + ..(1,2)..controls (1,2) and (0,2.5) + ..(0,2.5)..controls (0,2.5) and (-1,2) + ..(-1,2)..controls (-1,2) and (-2,1) + ..(-2,1)..controls (-2,1) and (-2.5,-0.5) + ..(-2.5,-0.5)..controls (-2.5,-0.5) and (-1,-2) + ..(-1,-2)..controls (-1,-2) and (0,-2.5) + ..(0,-2.5)..controls (0,-2.5) and (1,-2) + ..cycle + +{(pen)rotated(1)} +Pen polygon at line 163 (newly created): +(0.97366,1.51723) + .. cycle + +{makepath(pen)} + +>> Path at line 163: +(0.97366,1.51723)..controls (0.97366,1.51723) and (0.97366,1.51723) + ..cycle + +{pencircle} +{makepath(future pen)} +Pen polygon at line 163 (newly created): +(0.5,-0.5) + .. (0.5,0.5) + .. (-0.5,0.5) + .. (-0.5,-0.5) + .. cycle + +{(path)rotated(-1)} + +>> Path at line 163: +(0.4912,-0.50865)..controls (0.4912,-0.50865) and (0.50865,0.4912) + ..(0.50865,0.4912)..controls (0.50865,0.4912) and (-0.4912,0.50865) + ..(-0.4912,0.50865)..controls (-0.4912,0.50865) and (-0.50865,-0.4912) + ..(-0.50865,-0.4912)..controls (-0.50865,-0.4912) and (0.4912,-0.50865) + ..cycle + +{addto} +Path at line 163, before choices: +(0,2){0,-4096} + ..{0,-4096}(0,1){0,-4096} + ..{4096,0}(3,0){4096,0} + ..{4096,0}(4,0){4096,0} + ..{0,-4096}cycle + +Path at line 163, after choices: +(0,2)..controls (0,1.66667) and (0,1.33333) + ..(0,1)..controls (0,-0.20886) and (1.63324,0) + ..(3,0)..controls (3.33333,0) and (3.66667,0) + ..(4,0)..controls (13.63031,0) and (0,10.64238) + ..cycle + +Path at line 163, before choices: +(0,0) + ..(5,2.9) + ..(4,3) + ..cycle + +Path at line 163, after choices: +(0,0)..controls (0.9363,-8.0181) and (11.81119,0.07109) + ..(5,2.9)..controls (4.68596,3.03043) and (4.33879,3.02028) + ..(4,3)..controls (1.86353,2.87216) and (-0.21431,1.83528) + ..cycle + +{makepen(path)} +Pen polygon at line 163 (newly created): +(5,2.9) + .. (4,3) + .. (0,0) + .. cycle + +Path at line 163, before subdivision into octants: +(0,2)..controls (0,1.66667) and (0,1.33333) + ..(0,1)..controls (0,-0.20886) and (1.63324,0) + ..(3,0)..controls (3.33333,0) and (3.66667,0) + ..(4,0)..controls (13.63031,0) and (0,10.64238) + ..cycle + +Cycle spec at line 163, after subdivision and double autorounding: +(1.5,0.00272) % beginning in octant `SSE' + ..controls (1.5,-0.49254) and (1.63411,-0.82727) + ..(1.8592,-1.05237) % segment 1 +% entering octant `ESE' + ..controls (2.23936,-1.43253) and (2.87909,-1.5) + ..(3.57066,-1.5) % segment 1 +% entering octant `ENE' + ..controls (3.95114,-1.5) and (4.3473,-1.47957) + ..(4.72458,-1.47957) % segment 1 + ..controls (5.08287,-1.47957) and (5.44116,-1.47957) + ..(5.79944,-1.47957) % segment 2 + ..controls (7.35368,-1.47957) and (8.34424,-1.12393) + ..(8.90538,-0.56279) % segment 3 +% entering octant `NNE' + ..controls (9.31982,-0.14835) and (9.5,0.37819) + ..(9.5,0.95631) % segment 3 +% entering octant `NNW' + ..controls (9.5,2.00116) and (8.61821,3.4318) + ..(7.41951,4.6305) % segment 3 +% entering octant `WNW' + ..controls (6.32909,5.72092) and (5.11603,6.5) + ..(4.2104,6.5) % segment 3 +% entering octant `WSW' + ..controls (3.64668,6.5) and (3.12122,6.31134) + ..(2.68588,5.876) % segment 3 +% entering octant `SSW' + ..controls (1.97163,5.16176) and (1.5,3.78351) + ..(1.5,1.485) % segment 3 + ..controls (1.5,0.99092) and (1.5,0.49681) + ..(1.5,0.00272) % segment 0 + & cycle + +Tracing edges at line 163: (weight 1) +@ Octant SSE (0 offsets), from (1.5,0.00272) to (1.8592,-1.05237) +@ transition line 0, from (1.5,0.00272) to (1.5,0.00272) +(2,0) +@ Octant ESE (0 offsets), from (1.8592,-1.05237) to (3.57066,-1.5) +@ transition line 1, from (3.57066,-1.5) to (3.57066,-1.5) +@ Octant ENE (2 offsets), from (3.57066,-1.5) to (13.90538,2.3372) +@ transition line 0, from (3.57066,-1.5) to (3.57066,-1.5) +@ transition line 1, from (8.41534,-0.93503) to (13.41534,1.96497) +@ transition line 2, from (13.90538,2.3372) to (13.90538,2.3372) +(2,-1)(9,-1)(9,0)(11,0)(11,1)(13,1) +@ Octant NNE (0 offsets), from (13.90538,2.3372) to (14.5,3.85631) +@ transition line 1, from (14.5,3.85631) to (14.5,3.85631) +(13,2)(14,2) +@ Octant NNW (0 offsets), from (14.5,3.85631) to (12.41951,7.53049) +@ transition line 0, from (14.5,3.85631) to (14.5,3.85631) +(14,6)(13,6)(13,7)(12,7) +@ Octant WNW (2 offsets), from (12.41951,7.53049) to (8.2104,9.5) +@ transition line 3, from (12.41951,7.53049) to (12.41951,7.53049) +@ transition line 2, from (9.38113,9.39124) to (8.38113,9.49124) +@ transition line 1, from (8.2104,9.5) to (8.2104,9.5) +(12,8)(11,8)(11,9)(8,9) +@ Octant WSW (2 offsets), from (8.2104,9.5) to (2.68588,5.876) +@ transition line 0, from (8.2104,9.5) to (8.2104,9.5) +@ transition line 1, from (6.92668,9.08539) to (2.92668,6.08539) +@ transition line 2, from (2.68588,5.876) to (2.68588,5.876) +(8,10)(8,9)(6,9)(6,8)(5,8)(5,7)(3,7) +@ Octant SSW (0 offsets), from (2.68588,5.876) to (1.5,0.00272) +@ transition line 1, from (1.5,0.00272) to (1.5,0.00272) +(3,6)(2,6)(2,0). + +Path at line 163, before subdivision into octants: +(0,2)..controls (0,10.64238) and (13.63031,0) + ..(4,0)..controls (3.66667,0) and (3.33333,0) + ..(3,0)..controls (1.63324,0) and (0,-0.20886) + ..(0,1)..controls (0,1.33333) and (0,1.66667) + ..cycle + +Cycle spec at line 163, after subdivision and double autorounding: +(2.68588,5.876) % beginning in octant `ENE' + ..controls (3.12122,6.31134) and (3.64668,6.5) + ..(4.2104,6.5) % segment 0 +% entering octant `ESE' + ..controls (5.11603,6.5) and (6.32909,5.72092) + ..(7.41953,4.63048) % segment 0 +% entering octant `SSE' + ..controls (8.61824,3.43176) and (9.5,2.00116) + ..(9.5,0.95631) % segment 0 +% entering octant `SSW' + ..controls (9.5,0.37819) and (9.31982,-0.14835) + ..(8.90538,-0.56279) % segment 0 +% entering octant `WSW' + ..controls (8.34425,-1.12392) and (7.35368,-1.47957) + ..(5.79944,-1.47957) % segment 0 + ..controls (5.44116,-1.47957) and (5.08287,-1.47957) + ..(4.72458,-1.47957) % segment 1 + ..controls (4.3473,-1.47957) and (3.95114,-1.5) + ..(3.57066,-1.5) % segment 2 +% entering octant `WNW' + ..controls (2.87909,-1.5) and (2.23936,-1.43253) + ..(1.85919,-1.05235) % segment 2 +% entering octant `NNW' + ..controls (1.6341,-0.82726) and (1.5,-0.49254) + ..(1.5,0.00272) % segment 2 +% entering octant `NNE' + ..controls (1.5,0.49681) and (1.5,0.99092) + ..(1.5,1.485) % segment 3 + ..controls (1.5,3.78351) and (1.97165,5.16177) + ..(2.68588,5.876) % segment 0 + & cycle + +Tracing edges at line 163: (weight 1) +@ Octant ENE (2 offsets), from (7.68588,8.776) to (4.2104,6.5) +@ retrograde line from (7.68588,8.776) to (2.68588,5.876) +(7,9)(7,8)(5,8)(5,7)(4,7) +@ transition line 0, from (2.68588,5.876) to (2.68588,5.876) +@ transition line 1, from (2.68588,5.876) to (7.68588,8.776) +@ transition line 2, from (8.13123,9.12088) to (3.13123,6.22089) +@ transition line 1, from (4.2104,6.5) to (9.2104,9.4) +@ transition line 2, from (9.2104,9.4) to (9.2104,9.4) +(4,6)(4,7)(6,7)(6,8)(8,8) +@ retrograde line from (9.2104,9.4) to (4.2104,6.5) +(8,9)(8,8)(6,8) +@ Octant ESE (0 offsets), from (4.2104,6.5) to (7.41953,4.63048) +@ transition line 1, from (7.41953,4.63048) to (7.41953,4.63048) +(6,7)(4,7)(4,6)(6,6) +@ Octant SSE (0 offsets), from (7.41953,4.63048) to (9.5,0.95631) +@ transition line 0, from (7.41953,4.63048) to (7.41953,4.63048) +(6,5)(8,5)(8,3)(9,3) +@ Octant SSW (0 offsets), from (9.5,0.95631) to (8.90538,-0.56279) +@ transition line 1, from (8.90538,-0.56279) to (8.90538,-0.56279) +@ Octant WSW (2 offsets), from (8.90538,-0.56279) to (7.57066,1.5) +@ retrograde line from (8.90538,-0.56279) to (12.90538,2.43721) +(9,-1)(9,0)(10,0)(10,1)(12,1) +@ transition line 0, from (12.90538,2.43721) to (12.90538,2.43721) +@ transition line 1, from (12.90538,2.43721) to (8.90538,-0.56279) +@ transition line 2, from (8.6665,-0.76936) to (12.6665,2.23064) +@ transition line 1, from (7.57066,1.5) to (3.57066,-1.5) +@ transition line 2, from (3.57066,-1.5) to (3.57066,-1.5) +(12,2)(8,2)(8,1)(6,1)(6,0)(5,0) +@ retrograde line from (3.57066,-1.5) to (7.57066,1.5) +(5,-1)(5,0)(6,0)(6,1)(8,1) +@ Octant WNW (2 offsets), from (7.57066,1.5) to (6.85919,1.84764) +@ retrograde line from (7.57066,1.5) to (8.57066,1.4) +(8,2) +@ transition line 3, from (8.57066,1.4) to (8.57066,1.4) +@ transition line 2, from (8.57066,1.4) to (7.57066,1.5) +@ transition line 1, from (6.95764,1.52585) to (7.95764,1.42584) +@ transition line 2, from (6.85919,1.84764) to (5.85919,1.94765) +@ transition line 1, from (5.85919,1.94765) to (5.85919,1.94765) +(8,1) +@ retrograde line from (5.85919,1.94765) to (6.85919,1.84764) +@ Octant NNW (0 offsets), from (6.85919,1.84764) to (6.5,2.90271) +@ transition line 0, from (6.85919,1.84764) to (6.85919,1.84764) +(8,2)(7,2) +@ Octant NNE (0 offsets), from (6.5,2.90271) to (7.68588,8.776) +@ transition line 1, from (7.68588,8.776) to (7.68588,8.776) +(7,9). + +{tracingonline:=1} + +f<expr>->let)=];let[=(;show._ +(EXPR0)<-xx +{let} +{let} +{show} +>> _1 +{showdependencies} +xpart '=-ypart '-0.66667 +{qq:=pen} +{showstats} +Memory usage 1462&588 (104 still untouched) +String usage 41&161 (815&7634 still untouched) +{[repeat the loop]} +! A group begun on line 163 never ended. +<to be read again> + endtext +l.163 ... showstats; bye endtext + +I saw a `begingroup' back there that hasn't been matched +by `endgroup'. So I've inserted `endgroup' now. + +{endgroup} + ) +(end occurred when else on line 93 was incomplete) +(end occurred when if on line 36 was incomplete) +(end occurred when elseif on line 21 was incomplete) +Here is how much of METAFONT's memory you used: + 68 strings out of 883 + 3752 string characters out of 11386 + 2897 words of memory out of 3001 + 289 symbolic tokens out of 2100 + 8i,43n,14r,8p,167b stack positions out of 30i,100n,300r,150p,500b + +(illegal design size has been changed to 128pt) +(some chardp values had to be adjusted by as much as 0.5pt) +(local label 0:: was missing) +(local label 5:: was missing) +(4 font metric dimensions had to be decreased) +(You used 3w,2h,16d,2i,312l,301k,1e,10p metric file positions + out of 256w,16h,16d,64i,5000l,500k,256e,50p) + +Font metrics written on trap.tfm. +Output written on trap.72270gf (5 characters, 1028 bytes). diff --git a/systems/knuth/dist/mf/trap.mf b/systems/knuth/dist/mf/trap.mf new file mode 100644 index 0000000000..adf4d7bd48 --- /dev/null +++ b/systems/knuth/dist/mf/trap.mf @@ -0,0 +1,170 @@ +% This is a diabolical test file for MF84. Don't get stuck. +if not known inimf: .inimf.=.0. % next lines are skipped if format loaded + inimf; nonstopmode; tracingtitles:=day; showstopping:=year; hppp:=1000; + << == >> ::: || `` '' -- !! ?? ## && @@ $$ [[ ]] {{ }} . (( 5.5.5 )) ++ ".."; + begingroup save =; let=,; save,; newinternal $=,; let ):=, endgroup; + let year=month; showvariable errorstopmode,readstring,2,"2",,,(,),<<,year; + tracingrestores:=tracingcommands:=.00000762939453125; % that's 2^(-17) + if tracingcommands>0:tracingcommands:=if not cycle "":1.1 forever;fi; + tracingcommands:=2.1 exitif tracingcommands>2 endfor; showtoken |=:|>; + tracingedges:=1/.00001; tracingequations:=$+1; p~=tracingedges+.00001; + interim tracingspecs:=tracingpens:=tracingchoices:=tracingstats:= + warningcheck:=tracingoutput:=tracingmacros:=1; $:=ASCII""; $:=x; p~:=p~; + delimiters (); delimiters begintext endtext; vardef foo(text t)=t enddef; + def lig(text t,|)=ligtable0::for *=1step1until60:0kern boundarychar+*,endfor + skipto0;ligtable t:t|0,skipto255;boundarychar:=boundarychar+51.29999enddef; + foo begintext interim proofing:=(-.5; shipout nullpicture; special"3" endtext; + for n=tracingpens step 1 until proofing:fi endfor showstats; let!!=skipto; + path p~; p~=(0,0)..controls (15,4) and (-15,-12)..(4,0); everyjob /*\;; + vardef /*\`'@#=message @ & str#@ &jobname&char ASCII'`&str@#!enddef; + let next=dump; vardef `'='` enddef; def '`="\*/" enddef; +elseif known"": `'pass[2.]; outer\; let next=\; delimiters ^~7! fi +next\; % the second pass will now compute silently; the first pass will halt +batchmode; ^~7,endgroup pausing:=1; exitif p exitif boolean pen pencircle endfor +scantokens begingroup message char0&"watch this"; "pair p[],';"&char-1endgroup +path p[][]p,w,qw; qw=(1,-2)..(2,-1)..(2.5,0.5)..(1,2)..(turningnumber',2.5); +numeric p[][]; p[[ [$] ]]=10000000000000000; "this string constant is incomplete +string foo[]p,p~if true:[]; p~000=char34&char200&char34; +boolean p[]~,boolean fi.boolean; showvariable boolean; def\\= =end enddef; +picture e[]e[], e[], p~[]~[][]; show length scantokens p~0; +pen p~[]~,q["a",qq; p~1~=q=pencircle scaled mexp(-3016.57654); +transform p,pp0; if p=p:qq=makepen((1,0)..cycle) xscaled hex "1000";fi +qq:=makepen((0,0)..(1,0)..(0,1)..(0,0)..(1,0)..(0,1)..cycle); +vardef p[][]p~ begintext suffix a,b endtext())suffix@=show #@; p.a.b() enddef; +expandafter let\endtext\\; outer endtext,\,;;qq:=pencircle scaled 4.5 yscaled 2; +(6,12)-p7=(0,1)transformed p=(2/(x-x),3/0)transformed p; +p1\2p=p007=begintext if string p~[$]: p.1.2-p.1.199999,1 endtext transformed p; +showstopping:=0;showvariable p; p=p; let [[=begingroup; let ]]=endgroup; +(xxpart p+.002,yxpart p)=1[p1,p2]=(5,y+.00001)=(5,y)=(yypart p,xypart p); +reverse(p~..cycle) transformed p=p2. 3.p; +p[000000000001]2p~(,[2]3p~,-)=p~1~2[pausing]; +vardef p~[]@# tertiary t:=if p@ @=@ @p fi; vardef p[][]p~[]=BAD; inner ;; +show p~[-2]~[3000,x]++4000>path p3; showvariable p,P; +numeric p[]~; p3~=2alpha; p[1/$]~=3beta; +begingroup save p; showvariable p; 3beta=1]]; showvariable p; +def//expr;=enddef;def!primary!false):!fi enddef; +def _aa__ secondary _a_=if(true enddef; qq:=makepen(qw..(qw scaled$)..cycle); +primarydef _**__=[[show _*__]] enddef; +secondarydef _***__=expandafter __ scantokens"**oct"_ enddef; +// //pencircle slanted length p~**makepen reverse subpath p7-p2 of + (p7{p2}..controls _aa__ not odd.1(15) and known p or !p2and-p2..{1,1}(-p2 + {curl- +1)..tension atleast1..cycle sqrt2++sqrt2***[[]]; +[[interim proofing:=charcode:=-20.5;chardp:=-2048;shipout nullpicture]]; +if charexists -275.50002>known p0 0p=known p~: randomseed charcode; fi +randomseed:="goof"; a[($,18++1+-+18),(2,3)]=b[(3,2),(1,$); +show (^+1,~+2) slanted-1 yscaled-2 zscaled-(3,4), p transformed(pp xscaled 9), + pp shifted (1,2) transformed(p transformed p), -_[0][1,2]; show +floor sind mlog sqrt mexp200cosd angle(normaldeviate,uniformdeviate-chardp); +string s[]; s1=s2=s4; s3=s5; s1=s2; if s1<=s4<>(s1<>s3):show[[char34=s2:=s3]]fi; +substring penoffset point.1of.p~of[[pencircle]]rotated1080/2/1/3of decimal + directiontime postcontrol-1.5of(p~&cycle)-precontrol1/2of p~(p~)=s1:=s4:=s4; +path p~[]; p~1=p2{length" "}&cycle; p~1=p2=p~0; p2..controls-p2..cycle=p~2;(p7 +..tension1.2..p~[length p~2]..p~2&{0,1}p2..tension1and atleast1..cycle)..tension + x..{curl1}-p7{curl hex "IsBad"}..tension.75and.74999..p2{0,1}&p2{_,'}..cycle:=p + ~4; subpath(-9,9)of subpath(3.5001,7.00001)of p~4=p~6; +show p~6, directiontime(1,2) of p~6, directiontime(1,-1.00001) of p~6; +p~3=(0,0)..controls (1,1) and (0,1)..(1,0); show p2..p2{p7}&{$,$}cycle, + (directiontime(1,1) of subpath(.314159,1) of p~3)[.314159,1]; +p~5=(subpath(0,.25)of p~3&subpath(.25,1)of p~3)shifted begintext1,0; +p~3:=2/3'zscaled'{p~3}..controls(2,2/3(3))and penoffset(1/2x,y)of(0,1)(1,0); +show p~3 intersectiontimes reverse p~3, point.17227 of p~3, point1-.28339of p~3; +show point xpart(p~5 intersectiontimes p~5 shifted (.01,0))of p~5- + point ypart(p~5 intersectiontimes p~5 shifted (.01,0))of p~5; +[[interim tracingedges:=1; e[-1+-+ -1.00001]=nullpicture; addto e1 also[[ +addto e0 doublepath p~5 scaled 3 withpen q; e0=e1=e2; cull e1 dropping (0,.1); +nullpicture]];show e1 shifted(4089,-4095), e2 shifted(-4095,4092)shifted (-3,0), +e2 shifted(4089,-4095)]];addto e1 also e2 shifted(-2,$); e1:=e1 shifted(-4,$); +addto e0 also e1rotated89.999+e1scaled$; show e0 xscaled-10 yscaled2 xscaled82 +yscaled683;addto e1 doublepath (0,9) withweight-3 withweight turningnumber p~6 + withpen pencircle xscaled(oct"180"++1) rotated-angle(64,$) shifted (9,8) + withpen makepen(($,$)..(1,0)..(1,1)..($,0)..($,$)&cycle)xscaled4095.49999; +show e1, totalweight e1; chardp:=charcode:=5; xoffset:=-1.5; shipout e1; +showstats; addto e2doublepath p~ yscaled1/60; e3:=e2 yscaled 0; +autorounding:=2; addto e3doublepath(.5,0)..(3.5,1.5)withweight2; +tracingspecs:=0; q:=makepen((1,1)..cycle) yscaled 1.5; +p~8=(($,yy)rotated p{0,1}..{0,$}(1,0){0,$}..cycle)scaled2shifted(1000.49,9); +turningcheck:=1;addto e2doublepath p~8 withpen q withweight p withpen cycle p; +[[interim autorounding:=xx=.1; addto e2 contour p~8 withpen q withweight2]]; +chardx:=chardp:=charext:=-14.5;shipout-(-e0-e2)+e2shifted(0,6turningnumber p~8); +p~9=(0,0)..(1,.5)..(5,1.5)..(7,2.5)..(12,3.5)..(13,4);addto e3 doublepath p~9; +smoothing:=1; addto e3 doublepath p~9; addto e3 doublepath (-4095,0)..tension +3/4 and 999..(0,2); show e3 rotated-90, (e0+e0) rotated90$ rotated90; +if "a" if "ab">"b" elseif path reverse (3,4): >="aa":foo elseif fi "bar" +else if '-(1,yy)<': :fi else def dup text t=[[t;save enddef;def|suffix$=,$ +enddef; def||tertiary p=show substring p of("a" enddef;||(2,$)&"bc"); +tertiarydef x++y=[[dup showtoken x;]];def quote x expr z of y=z enddef; + texts(quote x=(y+0)y+y)("xx",foo(x))=0]] enddef; def texts(text t,tt)expr?= + for n:=,for n"yy":n,length if false:endfor tt,t,:if string n:forsuffixes n= + foo1,[foo(n)],':show t,tt|(n;exitif not('<='+(?,yy)) endfor for m= :+endfor + for m=alpha step-1.1 3$: +m endfor fi endfor enddef; show (0,0){curl2}.. + subpath(23.3,4.5)of p~9{curl3}..($,$){curl4}..cycle;numspecial p~++2+3;[[ +let?=if;save if,\;def if=endinput?enddef;def texts=input enddef;texts trap ]]; +dup[[def texts secondary x=primarydef y++y=x@y enddef; showtoken++;x enddef]]\; +proofing:=1;texts:="a"&"b";% strings "yy" and "ab" no longer appear in memory +texts-1.00001a1=-(a2+a3+a4+a5+a6);-(a3,a2)/.99999=-(a4+a5+[[showdependencies; +a6]],a3+a4+a5+a6)]]; 1.00001a4+1=a5+a6; alpha:=.9alpha+7; showdependencies; +a5=a6=2/3-a6; cull e1 dropping($,4a5)withweight1.5; charcode:=chardp:=27; +openwindow 3 from (0,0) to (0,0) at "whoops"; addto p; shipout p; cull p; +openwindow -.5 from ($,$) to ($,$) at (0,0); special p; numspecial "p"; +openwindow 15.49999 from (0,32*1024) to (1,31*1057) at (0,0); shipout e1; +openwindow 5 from (0,length((0,0){0,0})) to (4,2) at ($,1); +openwindow 6 from (length($,0),1) to (3,10) at (-5,2); +display nullpicture; display p inwindow 3; display p inwindow 6; +display e1 inwindow 6; cull e0 dropping (0.1,4095.999999) withweight 3.5 +withweight-3.5; display e0 inwindow 5.5; addto e0 also p; addto e0 contour 0; +display e0 inwindow 5.49999; addto e0 contour p~9; +display e1 inwindow 3+3; display e0e0 inwindow[[vardef e[]e=enddef;6]]; +addto e0 also e1; display e0 inwindow 5; ligtable||:255|=:|>>0,skipto0; +display e1 inwindow 15; display e1 inwindow 6; +show [[interim tracingcommands:=0; lig("g")(=:|); lig("h":"i")(|=:); +lig("j")(|=:>);lig("k")(=:|>); lig("l")(|=:|>)]]; +b1:=c.a[ [[let c=++;vardef b=enddef;1]] ]; ligtable"m":0=:0,skipto5; +!!; errhelp 0; errmessage "Be like Jane"; +errhelp "He%%%lp%"; errmessage""; errhelp ""; errmessage "Another"; +headerbyte 0; headerbyte(48.5)substring(-9,9)of"long"; for\=0:\ +headerbyte 9:2a6,"q"; fontdimen 9:2a6,"q"; fontdimen 1:2048; +fontmaking:=1; extensible 5 5,"c"255.5,"d"; charlist 0:5:"a":"d"; +ligtable255:255::"a"=:"b","d" kern -2048,"c":0:99.5:"e"|=:|"f",0kern'; +ligtable 5:0; def clear(text x)=interim x:=$ enddef; clear(hppp); vppp=0; +[[clear(tracingmacros); clear(tracingcommands); clear(tracingoutput\; + clear(proofing);designsize:=.99999;charcode:=ASCII char-418.5;vppp:=designsize; + def dp expr d = charcode:=charcode+1; chardp:=d; shipout nullpicture enddef; + dp 13; dp 12; dp 0; dp 21; dp -2; dp 17; dp 11; dp 3; charic:=-1000; dp -1; + dp 25; dp 31; dp 19; dp 7; charwd:=256; chardy:=6; dp 23; dp 30]]; +def f(suffix@@)(expr a,b)(text t)=numeric w; show a; % wipes out the old w +addto @@ contour (0,0)..(2,0)..(1,$)..(1,1)..cycle withpen qq; % strange path +addto @@ doublepath (0,0){1,1}..{2,1}(2,1) withpen qq; % carefully chosen +addto @@ doublepath(($,$){1,0}..(1,1){1,0})scaled.5 withpen nullpen; +cull @@ keeping (4,4) withweight1.5; enddef; def g(suffix$)=show $ enddef; +addto e0 contour (0,0){1,0}..{1,0}(1,0){0,$}..{0,1}cycle withpen qq; +f(e[3,w); g(e3,transformed p,penoffset-(1,1.3)of(pencircle scaled20 yscaled-.5), + directiontime (0,1) of ((0,0)..controls(1,1)and($,1)..(3,0)), + point 3.14159 of p~9 intersectiontimes subpath (3.14159,4) of p~9, + (($,1.1)..(1,$)) intersectiontimes precontrol$ of (0,0); +addto e3 doublepath(-4094.99998,0)..(4094.99998,-.00001) withpen penoffset 0 of +pencircle; addto e3 also e3 shifted (0,257); ,"flushed with pride"; numeric xx; +def f(expr x,y,z)=showdependencies;tracingcapsules:=1;showdependencies;show + 1/3(3,6)*((x+y)+(y-x)), (1,1)/sqrt2 zscaled (x+1,x+2) - (x+1,x+2) rotated 45, + (0,1) zscaled (1,y+2)-(1,y+2) rotated 90 enddef; f((xx+1)/.3,(yy-1)/.5,(xx,0)); +'=(1000o3-4000(o1-o2)+4000o2+9,-.01o3+3ooo+ + [[oo=9/10(o2+o4+o5-20);o4=o5=8/9(o1+.5o2); o6=-.0001o2;showdependencies; + numeric o[];xpart(alfa,[[pair alfa;0]])]])=-2/3[[save p;(p$,1-p$)]]; +xoffset:=yoffset:=4000[[oo=.5ooo=2*-1/2(ooo+[[numeric ooo;1]]);oo]]; +for @=angle(sqrt$,mlog$):charext:=uniformdeviate$;charht:=2048;granularity:=-8; +addto e3 contour (0,-100)..tension 500..(100,-99)..tension 3000..cycle; +tracingoutput:=@; shipout e3; special "bye"; interim char 99 = "c"; true=false; +[[clear(tracingcommands); charcode:=ASCII char 269-13; shipout+nullpicture; +"careful" quote for for = @ step 200 until 2*2600: &" METAFONT" endfor;]]; +scrollmode; "hello again"&char31; save p; fillin:=-.043; +def f expr x=let )=]; let [=(; show _ enddef; begingroup tracingspecs:=1; +show nullpen, makepath.qq, makepath(q rotated1), makepath pencircle rotated $; +addto e0 doublepath (0,2){0,$}..{0,$}(0,1)..{1,0}(3,0)..(4,0){1,0}..cycle + withpen makepen((0,0)..(5,2.9)..(4,3)..cycle); tracingonline:=1; f xx[1); +showdependencies; qq:=q; showstats; bye endtext +% things not tested: +% interaction (error insertion/deletion, interrupts, \pausing, files not there) +% date, time; initialization of random number generator without randomseed +% system-dependent parsing of file names, areas, extensions +% certain error messages, especially fatal ones +% things that can't happen in INIMF +% unusual cases of fixed-point arithmetic diff --git a/systems/knuth/dist/mf/trap.pl b/systems/knuth/dist/mf/trap.pl new file mode 100644 index 0000000000..bae32a41fb --- /dev/null +++ b/systems/knuth/dist/mf/trap.pl @@ -0,0 +1,490 @@ +(CODINGSCHEME Q) +(DESIGNSIZE R 128.0) +(COMMENT DESIGNSIZE IS IN POINTS) +(COMMENT OTHER SIZES ARE MULTIPLES OF DESIGNSIZE) +(CHECKSUM O 5546464252) +(FONTDIMEN + (SLANT R 2047.999999) + (SPACE R 0.0) + (STRETCH R 0.0) + (SHRINK R 0.0) + (XHEIGHT R 0.0) + (QUAD R 0.0) + (EXTRASPACE R 0.0) + (PARAMETER D 8 R 0.0) + (PARAMETER D 9 R 0.005208) + (PARAMETER D 10 R 0.0) + ) +(BOUNDARYCHAR O 377) +(LIGTABLE + (COMMENT THIS PART OF THE PROGRAM IS NEVER USED! + ) + (LABEL BOUNDARYCHAR) + (/LIG/>> O 377 O 0) + (KRN O 0 R 0.0) + (KRN O 0 R 0.0078125) + (KRN O 0 R 0.015625) + (KRN O 0 R 0.0234375) + (KRN O 0 R 0.03125) + (KRN O 0 R 0.0390625) + (KRN O 0 R 0.046875) + (KRN O 0 R 0.0546875) + (KRN O 0 R 0.0625) + (KRN O 0 R 0.0703125) + (KRN O 0 R 0.078125) + (KRN O 0 R 0.0859375) + (KRN O 0 R 0.09375) + (KRN O 0 R 0.1015625) + (KRN O 0 R 0.109375) + (KRN O 0 R 0.1171875) + (KRN O 0 R 0.125) + (KRN O 0 R 0.1328125) + (KRN O 0 R 0.140625) + (KRN O 0 R 0.1484375) + (KRN O 0 R 0.15625) + (KRN O 0 R 0.1640625) + (KRN O 0 R 0.171875) + (KRN O 0 R 0.1796875) + (KRN O 0 R 0.1875) + (KRN O 0 R 0.1953125) + (KRN O 0 R 0.203125) + (KRN O 0 R 0.2109375) + (KRN O 0 R 0.21875) + (KRN O 0 R 0.2265625) + (KRN O 0 R 0.234375) + (KRN O 0 R 0.2421875) + (KRN O 0 R 0.25) + (KRN O 0 R 0.2578125) + (KRN O 0 R 0.265625) + (KRN O 0 R 0.2734375) + (KRN O 0 R 0.28125) + (KRN O 0 R 0.2890625) + (KRN O 0 R 0.296875) + (KRN O 0 R 0.3046875) + (KRN O 0 R 0.3125) + (KRN O 0 R 0.3203125) + (KRN O 0 R 0.328125) + (KRN O 0 R 0.3359375) + (KRN O 0 R 0.34375) + (KRN O 0 R 0.3515625) + (KRN O 0 R 0.359375) + (KRN O 0 R 0.3671875) + (KRN O 0 R 0.375) + (KRN O 0 R 0.3828125) + (KRN O 0 R 0.390625) + (KRN O 0 R 0.3984375) + (KRN O 0 R 0.40625) + (KRN O 0 R 0.4140625) + (KRN O 0 R 0.421875) + (KRN O 0 R 0.4296875) + (KRN O 0 R 0.4375) + (KRN O 0 R 0.4453125) + (KRN O 0 R 0.453125) + (KRN O 0 R 0.4609375) + (SKIP D 1) + (LABEL C g) + (LIG/ C g O 0) + (STOP) + (KRN O 0 R 0.400782) + (KRN O 0 R 0.408594) + (KRN O 0 R 0.416407) + (KRN O 0 R 0.424219) + (KRN O 0 R 0.432032) + (KRN O 0 R 0.439844) + (KRN O 0 R 0.447657) + (KRN O 0 R 0.455469) + (KRN O 0 R 0.463282) + (KRN O 0 R 0.471094) + (KRN O 0 R 0.478907) + (KRN O 0 R 0.486719) + (KRN O 0 R 0.494532) + (KRN O 0 R 0.502344) + (KRN O 0 R 0.510157) + (KRN O 0 R 0.517969) + (KRN O 0 R 0.525782) + (KRN O 0 R 0.533594) + (KRN O 0 R 0.541407) + (KRN O 0 R 0.549219) + (KRN O 0 R 0.557032) + (KRN O 0 R 0.564844) + (KRN O 0 R 0.572657) + (KRN O 0 R 0.580469) + (KRN O 0 R 0.588282) + (KRN O 0 R 0.596094) + (KRN O 0 R 0.603907) + (KRN O 0 R 0.611719) + (KRN O 0 R 0.619532) + (KRN O 0 R 0.627344) + (KRN O 0 R 0.635157) + (KRN O 0 R 0.642969) + (KRN O 0 R 0.650782) + (KRN O 0 R 0.658594) + (KRN O 0 R 0.666407) + (KRN O 0 R 0.674219) + (KRN O 0 R 0.682032) + (KRN O 0 R 0.689844) + (KRN O 0 R 0.697657) + (KRN O 0 R 0.705469) + (KRN O 0 R 0.713282) + (KRN O 0 R 0.721094) + (KRN O 0 R 0.728907) + (KRN O 0 R 0.736719) + (KRN O 0 R 0.744532) + (KRN O 0 R 0.752344) + (KRN O 0 R 0.760157) + (KRN O 0 R 0.767969) + (KRN O 0 R 0.775782) + (KRN O 0 R 0.783594) + (KRN O 0 R 0.791407) + (KRN O 0 R 0.799219) + (KRN O 0 R 0.807032) + (KRN O 0 R 0.814844) + (KRN O 0 R 0.822657) + (KRN O 0 R 0.830469) + (KRN O 0 R 0.838282) + (KRN O 0 R 0.846094) + (KRN O 0 R 0.853907) + (KRN O 0 R 0.861719) + (SKIP D 1) + (LABEL C h) + (LABEL C i) + (/LIG C i O 0) + (STOP) + (KRN O 0 R 0.801562) + (KRN O 0 R 0.809375) + (KRN O 0 R 0.817187) + (KRN O 0 R 0.825) + (KRN O 0 R 0.832812) + (KRN O 0 R 0.840625) + (KRN O 0 R 0.848437) + (KRN O 0 R 0.85625) + (KRN O 0 R 0.864062) + (KRN O 0 R 0.871875) + (KRN O 0 R 0.879687) + (KRN O 0 R 0.8875) + (KRN O 0 R 0.895312) + (KRN O 0 R 0.903125) + (KRN O 0 R 0.910937) + (KRN O 0 R 0.91875) + (KRN O 0 R 0.926562) + (KRN O 0 R 0.934375) + (KRN O 0 R 0.942187) + (KRN O 0 R 0.95) + (KRN O 0 R 0.957812) + (KRN O 0 R 0.965625) + (KRN O 0 R 0.973437) + (KRN O 0 R 0.98125) + (KRN O 0 R 0.989062) + (KRN O 0 R 0.996875) + (KRN O 0 R 1.004687) + (KRN O 0 R 1.0125) + (KRN O 0 R 1.020312) + (KRN O 0 R 1.028125) + (KRN O 0 R 1.035937) + (KRN O 0 R 1.04375) + (KRN O 0 R 1.051562) + (KRN O 0 R 1.059375) + (KRN O 0 R 1.067187) + (KRN O 0 R 1.075) + (KRN O 0 R 1.082812) + (KRN O 0 R 1.090625) + (KRN O 0 R 1.098437) + (KRN O 0 R 1.10625) + (KRN O 0 R 1.114062) + (KRN O 0 R 1.121875) + (KRN O 0 R 1.129687) + (KRN O 0 R 1.1375) + (KRN O 0 R 1.145312) + (KRN O 0 R 1.153125) + (KRN O 0 R 1.160937) + (KRN O 0 R 1.16875) + (KRN O 0 R 1.176562) + (KRN O 0 R 1.184375) + (KRN O 0 R 1.192187) + (KRN O 0 R 1.2) + (KRN O 0 R 1.207812) + (KRN O 0 R 1.215625) + (KRN O 0 R 1.223437) + (KRN O 0 R 1.23125) + (KRN O 0 R 1.239062) + (KRN O 0 R 1.246875) + (KRN O 0 R 1.254687) + (KRN O 0 R 1.2625) + (SKIP D 1) + (LABEL C j) + (/LIG> C j O 0) + (SKIP D 123) + (KRN O 0 R 1.202344) + (KRN O 0 R 1.210156) + (KRN O 0 R 1.217969) + (KRN O 0 R 1.225781) + (KRN O 0 R 1.233594) + (KRN O 0 R 1.241406) + (KRN O 0 R 1.249219) + (KRN O 0 R 1.257031) + (KRN O 0 R 1.264844) + (KRN O 0 R 1.272656) + (KRN O 0 R 1.280469) + (KRN O 0 R 1.288281) + (KRN O 0 R 1.296094) + (KRN O 0 R 1.303906) + (KRN O 0 R 1.311719) + (KRN O 0 R 1.319531) + (KRN O 0 R 1.327344) + (KRN O 0 R 1.335156) + (KRN O 0 R 1.342969) + (KRN O 0 R 1.350781) + (KRN O 0 R 1.358594) + (KRN O 0 R 1.366406) + (KRN O 0 R 1.374219) + (KRN O 0 R 1.382031) + (KRN O 0 R 1.389844) + (KRN O 0 R 1.397656) + (KRN O 0 R 1.405469) + (KRN O 0 R 1.413281) + (KRN O 0 R 1.421094) + (KRN O 0 R 1.428906) + (KRN O 0 R 1.436719) + (KRN O 0 R 1.444531) + (KRN O 0 R 1.452344) + (KRN O 0 R 1.460156) + (KRN O 0 R 1.467969) + (KRN O 0 R 1.475781) + (KRN O 0 R 1.483594) + (KRN O 0 R 1.491406) + (KRN O 0 R 1.499219) + (KRN O 0 R 1.507031) + (KRN O 0 R 1.514844) + (KRN O 0 R 1.522656) + (KRN O 0 R 1.530469) + (KRN O 0 R 1.538281) + (KRN O 0 R 1.546094) + (KRN O 0 R 1.553906) + (KRN O 0 R 1.561719) + (KRN O 0 R 1.569531) + (KRN O 0 R 1.577344) + (KRN O 0 R 1.585156) + (KRN O 0 R 1.592969) + (KRN O 0 R 1.600781) + (KRN O 0 R 1.608594) + (KRN O 0 R 1.616406) + (KRN O 0 R 1.624219) + (KRN O 0 R 1.632031) + (KRN O 0 R 1.639844) + (KRN O 0 R 1.647656) + (KRN O 0 R 1.655469) + (KRN O 0 R 1.663281) + (SKIP D 1) + (LABEL C k) + (LIG/> C k O 0) + (SKIP D 62) + (KRN O 0 R 1.603125) + (KRN O 0 R 1.610937) + (KRN O 0 R 1.61875) + (KRN O 0 R 1.626562) + (KRN O 0 R 1.634375) + (KRN O 0 R 1.642187) + (KRN O 0 R 1.65) + (KRN O 0 R 1.657812) + (KRN O 0 R 1.665625) + (KRN O 0 R 1.673437) + (KRN O 0 R 1.68125) + (KRN O 0 R 1.689062) + (KRN O 0 R 1.696875) + (KRN O 0 R 1.704687) + (KRN O 0 R 1.7125) + (KRN O 0 R 1.720312) + (KRN O 0 R 1.728125) + (KRN O 0 R 1.735937) + (KRN O 0 R 1.74375) + (KRN O 0 R 1.751562) + (KRN O 0 R 1.759375) + (KRN O 0 R 1.767187) + (KRN O 0 R 1.775) + (KRN O 0 R 1.782812) + (KRN O 0 R 1.790625) + (KRN O 0 R 1.798437) + (KRN O 0 R 1.80625) + (KRN O 0 R 1.814062) + (KRN O 0 R 1.821875) + (KRN O 0 R 1.829687) + (KRN O 0 R 1.8375) + (KRN O 0 R 1.845312) + (KRN O 0 R 1.853125) + (KRN O 0 R 1.860937) + (KRN O 0 R 1.86875) + (KRN O 0 R 1.876562) + (KRN O 0 R 1.884375) + (KRN O 0 R 1.892187) + (KRN O 0 R 1.9) + (KRN O 0 R 1.907812) + (KRN O 0 R 1.915625) + (KRN O 0 R 1.923437) + (KRN O 0 R 1.93125) + (KRN O 0 R 1.939062) + (KRN O 0 R 1.946875) + (KRN O 0 R 1.954687) + (KRN O 0 R 1.9625) + (KRN O 0 R 1.970312) + (KRN O 0 R 1.978125) + (KRN O 0 R 1.985937) + (KRN O 0 R 1.99375) + (KRN O 0 R 2.001562) + (KRN O 0 R 2.009375) + (KRN O 0 R 2.017187) + (KRN O 0 R 2.025) + (KRN O 0 R 2.032812) + (KRN O 0 R 2.040625) + (KRN O 0 R 2.048437) + (KRN O 0 R 2.05625) + (KRN O 0 R 2.064062) + (STOP) + (LABEL C l) + (/LIG/> C l O 0) + (SKIP D 1) + (LABEL C m) + (LIG O 0 O 0) + (STOP) + (LIG C a C b) + (KRN C d R -15.999999) + (LABEL C c) + (LABEL C d) + (/LIG/ C e C f) + (KRN O 0 R 0.0) + (STOP) + (COMMENT THIS PART OF THE PROGRAM IS NEVER USED! + ) + ) +(CHARACTER O 0 + (CHARWD R 2.0) + (CHARHT R 15.999999) + (CHARDP R 0.234375) + (CHARIC R -7.8125) + (NEXTLARGER O 5) + ) +(CHARACTER O 5 + (CHARWD R 0.0) + (CHARDP R -0.113281) + (VARCHAR + (TOP O 5) + (MID C c) + (REP C d) + ) + ) +(CHARACTER O 33 + (CHARWD R 0.0) + (CHARDP R 0.2109375) + ) +(CHARACTER O 137 + (CHARWD R 0.0) + (CHARDP R 0.1015625) + ) +(CHARACTER O 140 + (CHARWD R 0.0) + (CHARDP R 0.089844) + ) +(CHARACTER C a + (CHARWD R 0.0) + (NEXTLARGER C d) + ) +(CHARACTER C b + (CHARWD R 0.0) + (CHARDP R 0.1640625) + ) +(CHARACTER C c + (CHARWD R 0.0) + (CHARDP R -0.011719) + (COMMENT + (/LIG/ C e C f) + (KRN O 0 R 0.0) + ) + ) +(CHARACTER C d + (CHARWD R 0.0) + (CHARDP R 0.1328125) + (COMMENT + (/LIG/ C e C f) + (KRN O 0 R 0.0) + ) + ) +(CHARACTER C e + (CHARWD R 0.0) + (CHARDP R 0.089844) + ) +(CHARACTER C f + (CHARWD R 0.0) + (CHARDP R 0.0234375) + ) +(CHARACTER C g + (CHARWD R 0.0) + (CHARDP R -0.011719) + (CHARIC R -7.8125) + (COMMENT + (LIG/ C g O 0) + ) + ) +(CHARACTER C h + (CHARWD R 0.0) + (CHARDP R 0.1953125) + (CHARIC R -7.8125) + (COMMENT + (/LIG C i O 0) + ) + ) +(CHARACTER C i + (CHARWD R 0.0) + (CHARDP R 0.2421875) + (CHARIC R -7.8125) + (COMMENT + (/LIG C i O 0) + ) + ) +(CHARACTER C j + (CHARWD R 0.0) + (CHARDP R 0.1484375) + (CHARIC R -7.8125) + (COMMENT + (/LIG> C j O 0) + (LIG C a C b) + (KRN C d R -15.999999) + (/LIG/ C e C f) + (KRN O 0 R 0.0) + ) + ) +(CHARACTER C k + (CHARWD R 0.0) + (CHARDP R 0.0546875) + (CHARIC R -7.8125) + (COMMENT + (LIG/> C k O 0) + (LIG C a C b) + (KRN C d R -15.999999) + (/LIG/ C e C f) + (KRN O 0 R 0.0) + ) + ) +(CHARACTER C l + (CHARWD R 2.0) + (CHARDP R 0.1796875) + (CHARIC R -7.8125) + (COMMENT + (/LIG/> C l O 0) + (LIG C a C b) + (KRN C d R -15.999999) + (/LIG/ C e C f) + (KRN O 0 R 0.0) + ) + ) +(CHARACTER C m + (CHARWD R 2.0) + (CHARHT R 15.999999) + (CHARDP R 0.234375) + (CHARIC R -7.8125) + (COMMENT + (LIG O 0 O 0) + ) + ) +(CHARACTER O 354 + (CHARWD R 0.0) + (CHARDP R -15.999999) + ) diff --git a/systems/knuth/dist/mf/trap.typ b/systems/knuth/dist/mf/trap.typ new file mode 100644 index 0000000000..159c5ce7fc --- /dev/null +++ b/systems/knuth/dist/mf/trap.typ @@ -0,0 +1,210 @@ +This is GFtype, Version 3.1 +Options selected: Mnemonic output = true; pixel output = true. +' METAFONT output 2014.01.07:1811' + +35: beginning of char 5: -4096<=m<=4094 -2<=n<=1 +(initially n=1) paint (4095)1 +64: skip0 0 (n=0) paint (4093)1 +69: skip0 0 (n=-1) paint (4096)1 +74: skip0 0 (n=-2) paint (4092)1(1)1 +81: eoc +(The character is too large to be displayed in full.) +.<--This pixel's lower left corner is at (-4096,2) in METAFONT coordinates + + + + +.<--This pixel's upper left corner is at (-4096,-2) in METAFONT coordinates + +82: beginning of char 5 with extension -14: -4<=m<=1002 -6<=n<=11 +(previous character with the same code started at byte 35) +(initially n=11) paint (1002)4 +111: skip0 0 (n=10) paint (1002)4 +116: skip0 0 (n=9) paint (1002)4 +121: skip1 3 (n=5) paint (1002)4 +127: skip0 0 (n=4) paint (1002)4 +132: skip0 0 (n=3) paint (1002)4 +137: newrow 7 (n=2) paint 1 +139: newrow 3 (n=1) paint 1(1)1(1)1 +145: newrow 1 (n=0) paint 1(1)2(1)1(1)1 +153: newrow 1 (n=-1) paint 2(1)1 +157: newrow 0 (n=-2) paint 1(1)1 +161: skip1 1 (n=-4) paint (7)1 +165: newrow 7 (n=-5) paint 1 +167: newrow 6 (n=-6) paint 1(1)1 +171: eoc +(The character is too large to be displayed in full.) +.<--This pixel's lower left corner is at (-4,12) in METAFONT coordinates + + + + + + + + + + * + * * * + * ** * * + ** * +* * + + * + * + * * +.<--This pixel's upper left corner is at (-4,-6) in METAFONT coordinates + +172: yyy -216270 (-3.30002) +177: xxx 'title ab' +187: xxx 'xoffset' +196: yyy -65536 (-1) +201: beginning of char 27 with extension -14: -4096<=m<=4094 -1<=n<=0 +(initially n=0) paint (4093)1(1)4095 +234: newrow 0 (n=-1) paint 4093(1)1 +240: eoc +(The character is too large to be displayed in full.) +.<--This pixel's lower left corner is at (-4096,1) in METAFONT coordinates + +******************************************************************************** +.<--This pixel's upper left corner is at (-4096,-1) in METAFONT coordinates + +241: xxx 'xoffset' +250: yyy -87359488 (-1333) +255: xxx 'yoffset' +264: yyy -87359488 (-1333) +269: beginning of char 109: -5428<=m<=2762 -1435<=n<=-1077 +(initially n=-1077) paint (0)8190 +298: skip2 256 (n=-1334) paint (0)8190 +305: skip1 98 (n=-1433) paint (4143)55 +311: skip0 0 (n=-1434) paint (4088)106 +317: skip0 0 (n=-1435) paint (4092)74 +323: eoc +(The character is too large to be displayed in full.) +.<--This pixel's lower left corner is at (-5428,-1076) in METAFONT coordinates +******************************************************************************** + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +.<--This pixel's upper left corner is at (-5428,-1156) in METAFONT coordinates + +324: xxx 'bye' +329: xxx 'xoffset' +338: yyy -87359488 (-1333) +343: xxx 'yoffset' +352: yyy -87359488 (-1333) +357: beginning of char 0: 0<=m<=0 0<=n<=0 +(initially n=0) +363: eoc +(The character is entirely blank.) + +364: xxx 'title careful METAFONT METAFONT METAFONT METAFONT METAFONT METAF +ONT METAFONT METAFONT METAFONT METAFONT METAFONT METAFONT METAFONT METAFONT ME +TAFONT METAFONT METAFONT METAFONT METAFONT METAFONT METAFONT METAFONT METAFONT + METAFONT METAFONT METAFONT METAFONT' +624: xxx 'title hello again?' +624: ! non-ASCII character in xxx command! + +Postamble starts at byte 644, after special info at byte 364. +design size = 134217728 (128pt) +check sum = 765094058 +hppp = -65536 (-1) +vppp = 65535 (0.99998) +min m = -5428, max m = 4094 +min n = -1435, max n = 11 +Character 0: dx -950272 (-14.5), dy 393216 (6), width 2097152 (-256), loc 324 +Character 5: dx -950272 (-14.5), width 0 (0), loc 82 +Character 27: dx -950272 (-14.5), width 0 (0), loc 172 +Character 95: dx -950272 (-14.5), width 0 (0), loc -1 +Character 96: dx -950272 (-14.5), width 0 (0), loc -1 +Character 97: dx -950272 (-14.5), width 0 (0), loc -1 +Character 98: dx -950272 (-14.5), width 0 (0), loc -1 +Character 99: dx -950272 (-14.5), width 0 (0), loc -1 +Character 100: dx -950272 (-14.5), width 0 (0), loc -1 +Character 101: dx -950272 (-14.5), width 0 (0), loc -1 +Character 102: dx -950272 (-14.5), width 0 (0), loc -1 +Character 103: dx -950272 (-14.5), width 0 (0), loc -1 +Character 104: dx -950272 (-14.5), width 0 (0), loc -1 +Character 105: dx -950272 (-14.5), width 0 (0), loc -1 +Character 106: dx -950272 (-14.5), width 0 (0), loc -1 +Character 107: dx -950272 (-14.5), width 0 (0), loc -1 +Character 108: dx -950272 (-14.5), dy 393216 (6), width 2097152 (-256), loc -1 +Character 109: dx -950272 (-14.5), dy 393216 (6), width 2097152 (-256), loc 241 +Character 236: dx 0 (0), width 0 (0), loc -1 +The file had 5 characters altogether. diff --git a/systems/knuth/dist/mf/trapin.log b/systems/knuth/dist/mf/trapin.log new file mode 100644 index 0000000000..2fbafd0e6b --- /dev/null +++ b/systems/knuth/dist/mf/trapin.log @@ -0,0 +1,179 @@ +This is METAFONT, Version 2.7182818 (INIMF) 7 JAN 2014 17:51 +**\input trap +(trap.mf +>> << == >> ::: ||`` ''--!! ??## && @@ $$[[]]{{ }}((5.5 0.5)) +>> ".." +! Not implemented: (unknown numeric)++(string). +<to be read again> + ; +l.4 ...}} . (( 5.5.5 )) ++ ".."; + +I'm afraid I don't know how to apply that operation to that +combination of types. Continue, and I'll return the second +argument (see above) as the result of the operation. + +.. +! Missing `=' has been inserted. +<to be read again> + , +l.5 begingroup save =; let=, + ; save,; newinternal $=,; let )... +You should have said `let symbol = something'. +But don't worry; I'll pretend that an equals sign +was present. The next token I read will be `something'. + +> errorstopmode=errorstopmode +> readstring=readstring +> 2 +> "2" +> ,=, +> (=tag +> )=, +<< == >> ::: ||`` ''--!! ??## && @@ $$[[]]{{ }}(([][]))=numeric +<< == >> ::: ||`` ''--!! ??## && @@ $$[[]]{{ }}((5.5 0.5))=<< == >> ::: +||`` ''--!! ??## && @@ $$[[]]{{ }}((5.5 0.5)) +> year=month +! OK. +l.6 ...ring,2,"2",,,(,),<<,year; + + +! Missing `:' has been inserted. +<to be read again> + ; +l.8 ...not cycle "":1.1 forever; + fi; +The next thing in this loop should have been a `:'. +So I'll pretend that a colon was present; +everything from here to `endfor' will be iterated. + +{fi} +{exitif} +{[repeat the loop]} +{false} +{fi} +{exitif} +{[repeat the loop]} +{(2.1)>(2)} +{true} +{tracingcommands:=2.1} +{showtoken} +> |=:|>=|=:|> +! OK. +l.9 ... endfor; showtoken |=:|>; + + +! Arithmetic overflow. +l.10 tracingedges:=1/.00001 + ; tracingequations:=$+1; p~=trac... +Uh, oh. A little while ago one of the quantities that I was +computing got too large, so I'm afraid your answers will be +somewhat askew. You'll probably have to adopt different +tactics next time. But I shall try to carry on anyway. + +{tracingedges:=32767.99998} +{(0)+(1)} +{tracingequations:=1} +{(32767.99998)+(0.00002)} +! Arithmetic overflow. +l.10 ... p~=tracingedges+.00001; + +Uh, oh. A little while ago one of the quantities that I was +computing got too large, so I'm afraid your answers will be +somewhat askew. You'll probably have to adopt different +tactics next time. But I shall try to carry on anyway. + +{(p~)=(32767.99998)} +## p~=32767.99998 +{interim} +{tracingmacros:=1} +{tracingoutput:=1} +{warningcheck:=1} +{tracingstats:=1} +{tracingchoices:=1} +{tracingpens:=1} +{tracingspecs:=1} +{ASCII("")} +{$:=-1} +{$:=x} +>> x +! Internal quantity `$' must receive a known value. +<to be read again> + ; +l.12 ...os:=1; $:=ASCII""; $:=x; + p~:=p~; +I can't set an internal quantity to anything but a known +numeric value, so I'll have to ignore this assignment. + +{p~:=32767.99998} +## p~=32767.99998 +! Value is too large (32767.99998). +l.12 ...:=ASCII""; $:=x; p~:=p~; + +The equation I just processed has given some variable +a value of 4096 or more. Continue and I'll try to cope +with that big value; but it might be dangerous. +(Set warningcheck:=0 to suppress this message.) + +{delimiters} +{delimiters} +{vardef} +{def} + +foo(TEXT2)->begingroup(TEXT2)endgroup +(SUFFIX0)<- +(SUFFIX1)<-foo +(TEXT2)<-interim.proofing:=(-0.5;shipout.nullpicture;special"3" +{begingroup} +{interim} +{-(0.5)} +! Missing `)' has been inserted. +<to be read again> + ; +<argument> ...m.proofing:=(-0.5; + shipout.nullpicture;special"3" +foo->begingroup(TEXT2) + endgroup +l.16 ...ture; special"3" endtext + ; +I found no right delimiter to match a left one. So I've +put one in, behind the scenes; this may fix the problem. + +{proofing:=-0.5} +{shipout} +{nullpicture} +{special} +{restoring proofing=0} +{endgroup} +{for} +{showstats} +Memory usage 291&41 (1922 still untouched) +String usage 26&83 (891&11438 still untouched) +! OK. +l.17 ...ing:fi endfor showstats; + let!!=skipto; + +{let} +{path} +{-(15)} +{-(12)} +Path at line 18, before choices: +(0,0)..controls (15,4) and (-15,-12) + ..(4,0) + +Path at line 18, after choices: +(0,0)..controls (15,4) and (-15,-12) + ..(4,0) + +{(unknown path p~)=(path)} +{everyjob} +{vardef} +{let} +{vardef} +{def} +{elseif} + ) +Beginning to dump on file trap.base + (preloaded base=trap 2014.1.7) +1117 strings of total length 20614 +395 memory locations dumped; current usage is 317&67 +265 symbolic tokens diff --git a/systems/knuth/dist/mf/trapman.tex b/systems/knuth/dist/mf/trapman.tex new file mode 100644 index 0000000000..7968c6e44e --- /dev/null +++ b/systems/knuth/dist/mf/trapman.tex @@ -0,0 +1,400 @@ +% The TRAP manual: How to validate MF --- last updated by D E Knuth on 4 Dec 89 +\font\eighttt= cmtt8 +\font\eightrm= cmr8 +\font\titlefont=cmssdc10 at 40pt +\let\mc=\eightrm +\font\logo=manfnt % font used for the METAFONT logo +\def\MF{{\logo META}\-{\logo FONT}} +\rm +\let\mainfont=\tenrm + +\def\.#1{\hbox{\tt#1}} +\def\\#1{\hbox{\it#1\/\hskip.05em}} % italic type for identifiers + +\parskip 2pt plus 1pt +\baselineskip 12pt plus .25pt + +\def\verbatim#1{\begingroup \frenchspacing + \def\do##1{\catcode`##1=12 } \dospecials + \parskip 0pt \parindent 0pt + \catcode`\ =\active \catcode`\^^M=\active + \tt \def\par{\ \endgraf} \obeylines \obeyspaces + \input #1 \endgroup} +% a blank line will be typeset at the end of the file; +% if you're unlucky it will appear on a page by itself! +{\obeyspaces\global\let =\ } + +\output{\shipout\box255\global\advance\pageno by 1} % for the title page only +\null +\vfill +\centerline{\titlefont A Torture Test} +\vskip8pt +\centerline{\titlefont for \logo ()*+,-.*} +\vskip 24pt +\centerline{by Donald E. Knuth} +\centerline{Stanford University} +\vskip 6pt +\centerline{({\sl Version 2, January 1990\/})} +\vfill +\centerline{\vbox{\hsize 4in +\noindent Programs that claim to be implementations of \MF84 are +supposed to be able to process the test routine contained in this +report, producing the outputs contained in this report.}} +\vskip 24pt +{\baselineskip 9pt +\eightrm\noindent +The preparation of this report was supported in part by the National Science +Foundation under grants IST-8201926 and MCS-8300984, +and by the System Development Foundation. +{\logo opqrstuq} is a trademark of Addison-Wesley Publishing Company. + + +}\pageno=0\eject + +\output{\shipout\vbox{ % for subsequent pages + \baselineskip0pt\lineskip0pt + \hbox to\hsize{\strut + \ifodd\pageno \hfil\eightrm\firstmark\hfil + \mainfont\the\pageno + \else\mainfont\the\pageno\hfil + \eightrm\firstmark\hfil\fi} + \vskip 10pt + \box255} + \global\advance\pageno by 1} +\let\runninghead=\mark +\outer\def\section#1.{\noindent{\bf#1.}\quad + \runninghead{\uppercase{#1} }\ignorespaces} + +\section Introduction. +People often think that their programs are ``debugged'' when large applications +have been run successfully. But system programmers know that a typical large +application tends to use at most about 50 per cent of the instructions +in a typical compiler. Although the other half of the code---which tends +to be the ``harder half''---might be riddled with errors, the system seems +to be working quite impressively until an unusual case shows up on the +next day. And on the following day another error manifests itself, and so on; +months or years go by before certain parts of the compiler are even +activated, much less tested in combination with other portions of the system, +if user applications provide the only tests. + +How then shall we go about testing a compiler? Ideally we would like to +have a formal proof of correctness, certified by a computer. +This would give us a lot of confidence, +although of course the formal verification program might itself be incorrect. +A more serious drawback of automatic verification is that the formal +specifications of the compiler are likely to be wrong, since they aren't +much easier to write than the compiler itself. Alternatively, we can +substitute an informal proof of correctness: The programmer writes his or +her code in a structured manner and checks that appropriate relations +remain invariant, etc. This helps greatly to reduce errors, but it cannot +be expected to remove them completely; the task of checking a large +system is sufficiently formidable that human beings cannot do it without +making at least a few slips here and there. + +Thus, we have seen that test programs are unsatisfactory if they are simply +large user applications; yet some sort of test program is needed because +proofs of correctness aren't adequate either. People have proposed schemes +for constructing test data automatically from a program text, but such +approaches run the risk of circularity, since they cannot assume that a +given program has the right structure. + +I have been having good luck with a somewhat different approach, +first used in 1960 to debug an {\mc ALGOL} compiler. The idea is to +construct a test file that is about as different from a typical user +application as could be imagined. Instead of testing things that people +normally want to do, the file tests complicated things that people would +never dare to think of, and it embeds these complexities in still +more arcane constructions. Instead of trying to make the compiler do the +right thing, the goal is to make it fail (until the bugs have all been found). + +To write such a fiendish test routine, one simply gets into a nasty frame +of mind and tries to do everything in the unexpected way. Parameters +that are normally positive are set negative or zero; borderline cases +are pushed to the limit; deliberate errors are made in hopes that the +compiler will not be able to recover properly from them. + +A user's application tends to exercise 50\%\ of a compiler's logic, +but my first fiendish tests tend to improve this to about 90\%. As the +next step I generally make use of frequency-counting software to identify +the instructions that have still not been called upon. Then I add ever more +fiendishness to the test routine, until more than 99\%\ of the code +has been used at least once. (The remaining bits are things that +can occur only if the source program is really huge, or if certain +fatal errors are detected; or they are cases so similar to other well-tested +things that there can be little doubt of their validity.) + +Of course, this is not guaranteed to work. But my experience in 1960 was +that only two bugs were ever found in that {\mc ALGOL} compiler after it +correctly translated that original fiendish test. And one of those bugs +was actually present in the results of the test; I simply had failed to +notice that the output was incorrect. Similar experiences occurred later +during the 60s and 70s, with respect to a few assemblers, compilers, +and simulators that I wrote. + +This method of debugging, combined with the methodology of structured +programming and informal proofs (otherwise known as careful desk checking), +leads to greater reliability of production software than any other +method I know. Therefore I have used it in developing \MF84, and the +main bulk of this report is simply a presentation of the test program +that was used to get the bugs out of \MF. + +Such a test file is useful also after a program has been debugged, since +it can be used to give some assurance that subsequent modifications don't +mess things up. + +The test file is called \.{TRAP.MF}, because of my warped sense of humor: +\MF's companion system, \TeX, has a similar test file called \.{TRIP}, and I +couldn't help thinking about Billy Goat Gruff and the story of ``trip, +trap, trip, trap.'' + +The contents of this test file are so remote from what people actually +do with \MF, I feel apologetic if I have to explain the correct +translation of \.{TRAP.MF}; nobody really cares about most of the +nitty-gritty rules that are involved. Yet I believe \.{TRAP} exemplifies +the sort of test program that has outstanding diagnostic ability, as +explained above. + +If somebody claims to have a correct implementation of \MF, I will not +believe it until I see that \.{TRAP.MF} is translated properly. +I propose, in fact, that a program must meet two criteria before it +can justifiably be called \MF: (1)~The person who wrote it must be +happy with the way it works at his or her installation; and (2)~the +program must produce the correct results from \.{TRAP.MF}. + +\MF\ is in the public domain, and its algorithms are published; +I've done this since I do not want to discourage its use by placing +proprietary restrictions on the software. However, I don't want +faulty imitations to masquerade as \MF\ processors, since users +want \MF\ to produce identical results on different machines. +Hence I am planning to do whatever I can to suppress any systems that +call themselves \MF\ without meeting conditions (1) and~(2). +I have copyrighted the programs so that I have some chance to forbid +unauthorized copies; I explicitly authorize copying of correct +\MF\ implementations, and not of incorrect ones! + +The remainder of this report consists of appendices, whose contents ought +to be described briefly here: + +Appendix A explains in detail how to carry out a test of \MF, given +a tape that contains copies of the other appendices. + +Appendix B is \.{TRAP.MF}, the fiendish test file that has already +been mentioned. People who think that they understand \MF\ are challenged +to see if they know what \MF\ is supposed to do with this file. +People who know only a little about \MF\ might still find it +interesting to study Appendix~B, just to get some insights into the +methodology advocated here. + +Appendix C is \.{TRAPIN.LOG}, a correct transcript file \.{TRAP.LOG} +that results if \.{INIMF} is applied to \.{TRAP.MF}. (\.{INIMF} is +the name of a version of \MF\ that does certain initializations; +this run of \.{INIMF} also creates a binary base file called \.{TRAP.BASE}.) + +Appendix D is a correct transcript file \.{TRAP.LOG} that results if +\.{INIMF} or any other version of \MF\ is applied to \.{TRAP.MF} +with base file \.{TRAP.BASE}. + +Appendix E is \.{TRAP.TYP}, the symbolic version of a correct output +file \.{TRAP.72270GF} that was produced at the same time as the \.{TRAP.LOG} +file of Appendix~D. + +Appendix F is \.{TRAP.PL}, the symbolic version of a correct output +file \.{TRAP.TFM} that was produced at the same time as the \.{TRAP.LOG} +file of Appendix~D. + +Appendix G is \.{TRAP.FOT}, an abbreviated version of Appendix D that +appears on the user's terminal during the run that produces \.{TRAP.LOG}, +\.{TRAP.72270GF}, and \.{TRAP.TFM}. + +The debugging of \MF\ and the testing of the adequacy of \.{TRAP.MF} +could not have been done nearly as well as reported here except for +the magnificent software support provided by my colleague David R. Fuchs. +In particular, he extended our local Pascal compiler so that +frequency counting and a number of other important features were added +to its online debugging abilities. + +The method of testing advocated here has one chief difficulty that deserves +comment: I had to verify by hand that \MF\ did the right things +to \.{TRAP.MF}. This took many hours, and perhaps I have missed +something (as I did in 1960); I must confess that I have not checked +every single number in Appendices D, E, and~F. However, I'm willing to pay +$\$$81.92 to the first finder of any remaining bug in \MF, and I will +be surprised if that bug doesn't show up also in one of these appendices. + +\vfill\eject + +\section Appendix A: How to test \MF. + +\item{0.} Let's assume that you have a tape containing \.{TRAP.MF}, +\.{TRAPIN.LOG}, \.{TRAP.LOG}, \.{TRAP.TYP}, \.{TRAP.PL}, and \.{TRAP.FOT}, +as in Appendices B, C, D, E, F, and~G. Furthermore, let's suppose that you +have a working \.{WEB} system, and that you have working programs +\.{TFtoPL} and \.{GFtype}, as described in the \TeX ware and \MF ware reports. + +\item{1.} Prepare a version of \.{INIMF}. (This means that your \.{WEB} +change file should have {\bf init} and {\bf tini} defined to be null.) +The {\bf debug} and {\bf gubed} macros should be null, in order to +activate special printouts that occur when $\\{tracingedges}>1.0$. +The {\bf stat} and {\bf tats} macros should also be null, so that +statistics are kept. Set \\{mem\_top} and \\{mem\_max} to 3000 +(or to \\{mem\_min} plus 3000, if \\{mem\_min} isn't zero), +for purposes of this test version. +Also set $\\{error\_line}=64$, $\\{half\_error\_line}=32$, +$\\{max\_print\_line}=72$, $\\{screen\_width}=100$, and +$\\{screen\_depth}=200$; these parameters affect many of the lines of +the test output, so your job will be much easier if you use the same +settings that were used to produce Appendix~E. Also (if possible) set +$\\{gf\_buf\_size}=8$, since this tests more parts of the program. +You probably should also use the ``normal'' settings of other parameters +found in \.{MF.WEB} (e.g., $\\{max\_internal}=100$, $\\{buf\_size}=500$, +etc.), since these show up in a few lines of the test output. Finally, +change \MF's screen-display routines by putting the following simple lines +in the change file: +$$\vbox{\halign{\tt#\hfil\cr +\char`\@x Screen routines:\cr +begin init\char`\_screen:=false;\cr +\char`\@y\cr +begin init\char`\_screen:=true; + \char`\{screen instructions will be logged\char`\}\cr +\char`\@z\cr}}$$ +None of the other screen routines (\\{update\_screen}, \\{blank\_rectangle}, +\\{paint\_row}) should be changed in any way; the effect will be to have +\MF's actions recorded in the transcript files instead of on the screen, +in a machine-independent way. + +\item{2.} Run the \.{INIMF} prepared in step 1. In response to the first +`\.{**}' prompt, type carriage return (thus getting another `\.{**}'). +Then type `\.{\char`\\input trap}'. You should get an output that matches +the file \.{TRAPIN.LOG} (Appendix~C). Don't be alarmed by the error +messages that you see, unless they are different from those in Appendix~C. + +\def\sp{{\char'40}} +\item{3.} Run \.{INIMF} again. This time type `\.{\sp\&trap\sp\sp trap\sp}'. +(The spaces in this input help to check certain parts of \MF\ that +aren't otherwise used.) You should get outputs \.{TRAP.LOG}, \.{TRAP.72270GF}, +and \.{TRAP.TFM}. +Furthermore, your terminal should receive output that matches \.{TRAP.FOT} +(Appendix~G). During the middle part of this test, however, the terminal +will not be getting output, because \.{batchmode} is being +tested; don't worry if nothing seems to be happening for a while---nothing +is supposed to. + +\item{4.} Compare the \.{TRAP.LOG} file from step 3 with the ``master'' +\.{TRAP.LOG} file of step~0. (Let's hope you put that master file in a +safe place so that it wouldn't be clobbered.) There should be perfect +agreement between these files except in the following respects: + +\itemitem{a)} The dates and possibly the file names will +naturally be different. + +\itemitem{b)} If you had different values for \\{stack\_size}, \\{buf\_size}, +etc., the corresponding capacity values will be different when they +are printed out at the end. + +\itemitem{c)} Help messages may be different; indeed, the author encourages +non-English help messages in versions of \MF\ for people who don't +understand English as well as some other language. + +\itemitem{d)} The total number and length of strings at the end and/or +``still untouched'' may well be different. + +\itemitem{e)} If your \MF\ uses a different memory allocation or +packing scheme, the memory usage statistics may change. + +\itemitem{f)} If you use a different storage allocation scheme, the +capsule numbers will probably be different, but the order of variables +should be unchanged when dependent variables are shown. \MF\ should also +choose the same variables to be dependent. + +\itemitem{g)} If your computer handles integer division of negative operands +in a nonstandard way, you may get results that are rounded differently. +Although \TeX\ is careful to be machine-independent in this regard, +\MF\ is not, because integer divisions are present in so many places. + +\item{5.} Use \.{GFtype} to convert your file \.{TRAP.72270GF} to a file +\.{TRAP.TYP}. (Both of \.{GFtype}'s options, i.e., mnemonic output and image +output, should be enabled so that you get the maximum amount of output.) +The resulting file should agree with the master \.{TRAP.TYP} file of step~0, +assuming that your \.{GFtype} has the ``normal'' values of compile-time +constants ($\\{top\_pixel}=69$, etc.). + +\item{6.} Use \.{TFtoPL} to convert your file \.{TRAP.TFM} to a file +\.{TRAP.PL}. The resulting file should agree with the master \.{TRAP.PL} +file of step~0. + +\item{7.} You might also wish to test \.{TRAP} with other versions of +\MF\ (i.e., \.{VIRMF} or a production version with another base file +preloaded). It should work unless \MF's primitives have been redefined in +the base file. However, this step isn't essential, since all the code of +\.{VIRMF} appears in \.{INIMF}; you probably won't catch any more errors +this way, unless they would already become obvious from normal use of +the~system. + +\vfill\eject + +\section Appendix B: The \.{TRAP.MF} file. +The contents of the test routine are prefixed here with line numbers, for +ease in comparing this file with the error messages printed later; the +line numbers aren't actually present. +\runninghead{APPENDIX B: \.{TRAP.MF} (CONTINUED)} + +\vskip 8pt +\begingroup\count255=0 +\everypar{\global\advance\count255 by 1 + \hbox to 20pt{\sevenrm\hfil\the\count255\ \ }} +\verbatim{trap.mf} +\endgroup +\vfill\eject + +\section Appendix C: The \.{TRAPIN.LOG} file. +When \.{INIMF} makes the \.{TRAP.BASE} file, it also creates a file called +\.{TRAP.LOG} that looks like this. +\runninghead{APPENDIX C: \.{TRAPIN.LOG} (CONTINUED)} + +\vskip8pt +\verbatim{trapin.log} +\vfill\eject + +\section Appendix D: The \.{TRAP.LOG} file. +Here is the major output of the \.{TRAP} test; it is generated by running +\.{INIMF} and loading \.{TRAP.BASE}, then reading \.{TRAP.MF}. +\runninghead{APPENDIX D: \.{TRAP.LOG} (CONTINUED)} + +{\let\tt=\eighttt\leftskip 1in\baselineskip 9pt plus .1pt minus .1pt +\vskip8pt +\verbatim{trap.log} +} +\vfill\eject + +\section Appendix E: The \.{TRAP.TYP} file. +Here is another major component of the test. It shows the output of \.{GFtype} +applied to the file \.{TRAP.72270GF} that is created at the same time +Appendix D was produced. +\runninghead{APPENDIX E: \.{TRAP.TYP} (CONTINUED)} + +{\let\tt=\eighttt\leftskip 1in\baselineskip 9pt plus .1pt minus .1pt +\vskip8pt +\verbatim{trap.typ} +} +\vfill\eject + +\section Appendix F: The \.{TRAP.PL} file. +In this case we have the output of \.{TFtoPL} +applied to the file \.{TRAP.TFM} that is created at the same time +Appendix D was produced. +\runninghead{APPENDIX F: \.{TRAP.PL} (CONTINUED)} + +{\let\tt=\eighttt\leftskip 1in\baselineskip 9pt plus .1pt minus .1pt +\vskip8pt +\verbatim{trap.pl} +} +\vfill\eject + +\section Appendix G: The \.{TRAP.FOT} file. +This shows what appeared on the terminal while Appendix D was being produced. +\runninghead{APPENDIX G: \.{TRAP.FOT} (CONTINUED)} + +\vskip8pt +\verbatim{trap.fot} + +\vfill\end -- cgit v1.2.3