You are on page 1of 272

AHybridApproach To Medium- And Low-Resolution Font-Scaling And Its OOP Style Implementation

Diss. ETH No 10884

A Hybrid Approach To Medium- And Low-Resolution Font-Scaling And Its OOP Style Implementation
Adissertation submitted to the
Swiss Federal Institute of Technology Zrich

for the degree of


Doctor of Technical Sciences

presented by
Beat Stamm

Dipl.Phys.ETHZ born 18 March 1963 citizen of Basel, Switzerland

accepted on the recommendation of


Prof. Dr. J. Gutknecht, examiner Prof. Dr. R. D. Hersch, co-examiner

1994

Contents
0 Introduction 0.0 Bitmapped fonts and font-scaling 0.1 The raster tragedy at low resolution 0.2 The scope of this thesis 0.3 Guide to the reader 1 The Others: A Brief Overview of Existing Approaches 1.0 The MIT Project: A formal approach 1.1 The ETH Project: An interactive image-oriented approach 1.2 Adobe Type 1 Fonts: An approach with hints 1.3 Apple/Microsoft TrueType Fonts: An approach with instructions 2 The Foundations: An Object-Oriented Graphical Toolbox 2.0 Coordinates, pixels, and rectilinear regions 2.0.0 Vector displays 2.0.1 Raster displays 2.0.2 The grid-pointmodel 2.0.3 The checker-boardmodel 2.0.4 Aconflict of models 2.0.5 Conclusion 1: Coordinates vs. pixels 2.0.6 Conclusion 2: Rectilinearregions 2.0.7 Conclusion 3: Object-oriented programming 2.1 Concepts of object-oriented programmingand their notation 2.1.0 Encapsulation 2.1.1 Late binding 2.1.2 Extensibility&polymorphism 2.1.3 Notation &terminology 2.2 Structuring a graphics library into interchangeable components 2.2.0 Graphical objects 2.2.1 Renderingtools 2.2.2 Raster devices 2.2.3 Levels of abstraction 2.3 Assessment of the new approach for a graphical toolbox 3 The Input: A Simple Outline-Font Editor 3.0 Knots 1 1 3 6 7 9 9 11 12 15 18 18 18 18 19 21 22 24 27 29 30 30 31 33 37 40 40 41 45 47 49 52 52

vi

3.0.0 The entity of interaction 3.0.1 Differentways of marking 3.0.2 Cumulativemarking and ad-hoc constraints 3.0.3 Markingbeyond two dimensions 3.1 Steps beyond ordinary cut & paste 3.1.0 Ordinary cut &paste 3.1.1 Dragging 3.1.2 Inserting &draggingwith extended foci 3.1.3 Canceling 3.1.4 Split &merge 3.1.5 Contour orientation &seed-points 3.1.6 Zooming&other viewing attributes 3.1.7 Conveniencevs. non-modality 3.2 An experience with third order Bzier curves 3.2.0 Afont-designerspoint of view 3.2.1 Afont-scalers"point of view" 3.2.2 Conclusions 3.2.3 Adroitly defined Bziercurves 3.3 Assessment of the approach for an outline-font editor 4 The Backbone: A Formalism For Intelligent Outline-Fonts 4.0 The fundamental raster problem 4.0.0 Scaling- not a linear mapping? 4.0.1 Equality 4.0.2 Symmetry 4.0.3 Connectivity 4.1 An outline-oriented declarative high level language for fonts 4.1.0 Formal language vs. interactivity 4.1.1 High level vs. low level 4.1.2 Declarativevs. imperative 4.1.3 Outline-orientation vs. image-orientation 4.2 Semantic implications:The round-before-use rule 4.3 Formally defined regularity 4.3.0 Components 4.3.1 Attributes 4.3.2 Names &types 4.3.3 Instancing transformations 4.3.4 Hierarchy 4.3.5 Assemblyin pixel-space 4.3.6 Scopes

52 53 54 55 56 56 56 59 62 62 63 65 66 68 68 69 72 72 73 76 76 76 78 79 81 82 82 84 85 86 87 88 88 89 93 94 95 99 101

vii

4.3.7 Extensibility 4.4 Implied dynamic regularization 4.4.0 Stern regularityvs. artistic license 4.4.1 Curvedcontours 4.4.2 Near regularity 4.4.3 Non proportionality 4.4.4 Dynamicglyphs 4.4.5 Snapping into regularity 4.4.6 Well-behaveddegeneration 4.4.7 The lower limit 4.5 Steps beyond contours 4.5.0 Jaggies on the fringe 4.5.1 Half-bitting 4.5.2 Drop-outs &non-standard scan-conversion 4.5.3 Device independence &single pixels 4.6 Inter-character spacing & "wysiwyg" type-setting 4.6.0 From characters to words 4.6.1 Left and right side-bearings 4.6.2 Pair-kerning 4.6.3 "Wysiwyg" type-setting 4.7 Formal notation & graphical feed-back 4.7.0 Floating elements 4.7.1 Static feed-back 4.7.2 Dynamicfeed-back 4.7.3 Structural feed-back 5 The Implementation: An Extensible OOP Style Application 5.0 A font-scalers "point of view" 5.0.0 Hierarchy 5.0.1 Attributes 5.0.2 Polymorphous modeling 5.0.3 Inherited decoupling of tasks 5.1 A font-compilers "point of view" 5.1.0 Syntax &symbols 5.1.1 Terms &expressions 5.1.2 Once more: Polymorphous modeling 5.1.3 Names &scopes 5.2 A single recursive data structure for the font-scaler and -compiler 5.2.0 Aconflict of aims? 5.2.1 Compile once, scale many times

102 104 104 105 108 112 113 117 120 121 123 123 124 126 130 131 131 133 134 135 137 137 139 141 142 144 144 144 145 145 147 149 149 149 149 151 151 151 152

viii

5.2.2 Recursivedescent parsing &error handling 5.2.3 Persistent objects &intelligent riders 5.3 By way of assessment: Using Oberon-2 for the implementation 5.3.0 Abstractconcepts vs. concrete records and pointers 5.3.1 Canonical structures vs. multiple polymorphism 5.3.2 Polymorphism vs. wrapping 5.3.3 Forwarding,delegating&message records vs. methods 6 The Balance: A Competitive Prototype 6.0 The results: Demands met? 6.1 Artistic aspects 6.2 Technical aspects 6.3 Future research topics 7 Conclusions Appendix A: The Algorithms A.0 Graphical objects A.0.0 Lines A.0.1 Circles A.0.2 Ellipses &arcs A.0.3 Bzier&spline curves A.1 Rendering tools A.1.0 Recordingpixel boundaries A.1.1 Outlining &bounding boxes A.1.2 Fillingsimple objects A.1.3 Filling&windingnumbers A.2 Raster devices A.2.0 Copyingorthogonal rectangles A.2.1 Scalingorthogonal rectangles A.3 Spline-to-Bzier transformation A.3.0 The easy part A.3.1 Misleadingprecision A.3.2 The hard part A.3.3 Adequateresults for medium- and low-resolution Appendix B: The Binary Format of Unstructured Outline-Fonts Appendix C: The Language Definition for Intelligent Outline-Fonts Appendix D: A Self-Contained Example of a Font Definition References

156 158 162 162 164 166 167 170 170 173 177 178 182 183 183 183 187 191 195 200 200 201 205 209 218 218 225 231 231 233 234 236 237 238 240 251

ix

Abstract
The present thesis is dedicated to the scaling of fonts for medium- and particularly for low-resolutionraster-devices.We start out from the simple but fundamental observation that a scaling function, which inevitablyhas to quantize, is inherently non-linear. We call this the fundamental raster problem. From this non-linearity,and from the demand to preserve the regularityproperties under scaling, the necessity to decompose a fonts characters into components ensues inexorably.In contrast to other approaches, however, we do not stop decomposing on the level of glyphs,but we proceed with the level of contours, and down to the level of knots and single numbers. This is a consequence of the fundamental raster problem. This hierarchy of components unveils far-reaching opportunities. On the one hand, it determines the structural information that is indispensable to preserve regularity on all the levels of hierarchy. On the other hand, we can package individual intelligence into whatever level of hierarchy is most appropriate to do so. With a few intelligibleconcepts, we provide for dynamic regularization in a novel way. In contrast to commercial approaches, we do not just propose yet another font-description language, but we illustrate a strategy, how this font-description language is to be used particularly at lowresolution. The key ideas of this strategy are adroitly defined Bzier curves and the round-before-use rule. They are both consequences of the desire to define components in font-space, combined with the need to assemble them in pixel-space. As a result, we obtain intelligent outlines, whose semantics mirrors closelythe worldof typography. Our approach is a hybrid approach in more than one way. On the one hand, the bare shapes are drawn out of a graphical outline-font editor, the structural information is declared textually,whilegraphical representations of the compiled declarations are introduced back into the formal language, in order to provide feedback. In doing so we cope with the wish to acquire both shape and structure in a way which is most appropriate for the respective task. Besides outline-orientation, on the other hand, the extensibility of contours allows for a certain amount of image-orientation. In doing so we fulfill the demand to talk not only about the geometry of the glyphs and characters, but also about their digitized appearance. The graphical core of the font-scaler is based upon an object-oriented graphical tool-box. Besides its almost infinite extensibility,this tool-boxrealizes the strict decoupling of the actual rendering from the pure digitizing, and from the underlying raster-devices. It does so by encapsulating the intelli-

gence for particular rendering algorithms into active objects. These intelligent renderers are propagated to the font-scaler, which is relieved thereby from issues depending neither on the fonts geometry,nor on the characters appearance. The orthogonality of the tool-boxmakes it a highly valuable basis even for rather demanding general-purpose drawing programs. All the algorithms used in our approach are given in an Appendix. They contain many refinements to existingalgorithms that may be of interest to the practitioner in raster graphics. The implementation of the font-scaler, the proof-by-existence, is an application embedded into an extensible environment. With implementing a substantial part of the environment ourselves, we target several goals, not the least of which is to provide ideal premises for our application. As a result, we obtain most probably the simplest and concisest scaler for intelligent fonts ever entirely published.

xi

Zusammenfassung
Die vorliegendeArbeit ist dem Skalieren von Schriften fr Rastergerte mittlerer und niedriger Auflsung gewidmet. Wir gehen von der einfachen, aber grundlegenden Feststellung aus, dass eine Skalierungsfunktion, welche zwangslufigquantisieren muss, inherent nicht-linear ist. Wir nennen dies das fundamentale Rasterproblem. Aus dieser Nicht-Linearittund der Anforderung, die Regularittseigenschaften einer Schrift unter der Skalierung aufrecht zu erhalten, folgt unausweichlich die Notwendigkeit,die Buchstaben einer Schrift in Komponentenzu zerlegen.Im Gegensatz zu anderen Anstzen jedoch hren wir mit dem Zerlegen nicht auf der Ebene von Glyphen auf, sondern wir fahren auf der Ebene von Kontouren fort, bis hinunter zur Ebene von Knotenpunkten und einzelnen Zahlen. Dies ist eine Konsequenz des fundamentalen Rasterproblems. Diese Hierarchie von Komponenten deckt weitreichende Mglichkeiten auf. Einerseits bestimmt sie die zum Aufrechterhalten der Regularitt auf allen Ebenen der Hierarchie notwendige Strukturinformation. Andererseits knnen wir individuelle Intelligenz in die dafr am ehesten geeignete Hierarchieebene packen. Mit wenigen, verstndlichen Konzepten stellen wir dynamische Regularisierung auf eine neue Art zur Verfgung. Im Gegensatz zu kommerziellen Anstzen schlagen wir nicht einfach noch eine andere Schriftbeschreibungssprache vor, sondern wir illustrieren eine Strategie, wie diese Schriftbeschreibungssprache zur Skalierung von Schriften insbesondere bei niedriger Auflsunganzuwenden ist. Die Schlsselideen dieser Strategie sind geschickt definierte Bzier-Kurven und die Vor-Gebrauch-Runden Regel. Sie sind beide Konsequenzen des Wunsches, Komponenten im Schriftenraum zu definieren, kombiniert mit der Notwendigkeit, sie im Bildelementeraum zusammen zu setzen. Als Resultat erhalten wir intelligente Umrisse, deren Semantik die Welt der Typographiegenau widerspiegelt. Unser Ansatz ist ein Hybridansatz in mehr als einer Hinsicht. Einerseits werden die blossen Formen einem graphischen Umrissschrifteneditor entnommen, die Strukturinformation wird textuell deklariert, whrend die graphischen Darstellungen der bersetzten Deklarationen in den Text wiedereingefgt werden, um fr Rckmeldung zu sorgen. Damit, dass wir das so machen, erfllen wir den Wunsch, sowohl die Formen, als auch die Struktur auf diejenige Weise gewinnen zu knnen, welche fr die entsprechende Aufgabe am ehesten geeignet ist. Neben der Umriss-Orientierung erlaubt die Erweiterbarkeit der Kontouren andererseits ein gewisses Mass an Bild-Orientierung. Damit, dass wir das so machen, erfllen wir die Anforderung,nicht nur ber

xii

die Geometrie der Glyphen und Buchstaben sprechen zu knnen, sondern auch ber deren quantisierteErscheinung. Der graphische Kern des Schriftenskalierers basiert auf einem objekt-orientiertengraphischen Werkzeugkasten Neben dessen schier unendlichen Erwei. terbarkeit realisiert dieser Werkzeugkasten eine strikte Entkopplung des eigentlichen Wiedergabevom reinen Digitalisieren und von den darunterliegenden Rastergerten. Er macht dies durch Kapselung der Intelligenz fr besondere Wiedergabealgorithmenin aktiveObjekte. Diese intelligenten Wiedergabewerkzeuge werden zum Schriftenskalierer hin, welcher dabei von Fragestellungen, die weder von der Schriftgeometrie,noch vom Erscheinungsbild der Buchstaben abhngen, fortgepflanzt. Die Orthogonalitt des Werkzeugkastens macht diesen zu einer sehr wertvollen Basis sogar fr eher anspruchsvolle Allzweckzeichenprogramme.Alle in unserem Ansatz verwendeten Algorithmen sind in einem Appendixaufgefhrt. Sie enthalten viele Verfeinerungen zu existierenden Algorithmen,welche fr den Praktiker in Raster Graphik von Interesse sein knnen. Die Implementierung des Schriftenskalierers, der Existenzbeweis,ist eine in einer erweiterbaren Umgebung eingebettete Applikation.Mit der Eigenimplementation eines substantiellen Teils der Umgebung verfolgenwir verschiedene Ziele, nicht zuletzt ideale Voraussetzungen fr unsere Applikation zur Verfgungzu stellen. Als Resultat erhalten wir den vielleicht einfachsten und konzisesten Skalierer fr intelligente Schriften, der jemals vollstndig verffentlichtwurde.

0 Introduction
0.0 Bitmapped fonts and font-scaling Today, an increasing number of personal computers and workstations are equipped with bitmapped displays and laser printers. The flexibility of these raster devices makes the computer available to any kind of graphical output, without investing in special purpose peripherals. The ability to do graphics is overburdened to the respective software (or firmware).It actually amounts to nothing more than building up an image out of many small picture elements or pixels. With that, the computer has become available to notably type-settingand fonts. On a raster device, a bitmapped font comprises a particular choice of pixels that mimic as closely as possible an individual font style and type size, the latter denoting the maximum over all the characters vertical extensions (in units of length called points or pt, corresponding to about 1/ 72 of an inch or about 0.35 mm; for a more detailed definition cf. [Andr93 ]).

the maximum of the extensions in vertical direction The choice also depends on the device resolution, which denotes the number of pixels per unit length (for instance, dpi or dots per inch). A higher resolution corresponds to smaller pixels, of which more are needed, but it also permits to reproduce more details, and vice-versa. In order to make different fonts availableto computers, it looks as if all we needed to do is to define, which pixels make up for which font at what type size and deviceresolution. On a modern computer we may expect a graphicsoriented interactive softwaretool that would assist us in our endeavor. Such a pixel-fonteditor would let us specifythe pixelsusing a pointing devicesuch as a mouse, or allow us to cut-and-paste parts of characters - just all the conveniences of today's computer programs.

But there is one thing we have not reckoned with. Suppose we wanted to define all the pixels for a Times font; for two raster devices (a 72 dpi screen and a 300 dpi printer), four variants (book, italic, bold , italic-bold ), and 8 type sizes (from 6 to 36 pt). This requires raster data of just under one megabyte, or about 223 pixels. Assume that using our pixel-fonteditor we manage to specify one pixel per second (sometimes we may need more time for serious artwork, sometimes less, thanks to cut-and-paste etc.). With about 26 seconds to the minute, 26 minutes per hour, 8 hours a day, and roughly 28 days a year (no work on week-ends), we might make it - in just under one year of pixelediting. Even though our estimate should be too high by several orders of binary magnitude, we shall have to consider more than only two different device resolutions. Todayscommercial screens have a resolution of 64 to 100 dpi (which is considered low-resolution), matrix printers and facsimile devices range from maybe 100 to 300 dpi, and laser printers start at 300 dpi (which is commonly considered medium-resolution Furthermore, we may need more ). than eight type sizes, particularly in view of the zooming capabilities of more demanding type-setting programs. On top of that, there are about 6000 (!) fonts in use in the western world[Karow92b]. These figures show clearly that bitmapped fonts quickly exhaust manual definition. It is thus intelligible that we wish to overburden the labor to appropriate software.Now, naivelyseen the character , for instance, is always A an , whether it is somewhat larger or smaller, or whether the resolution is A higher or lower. If it were not an Aanymore, we would not be able to identify it as an A;the softwarewill have to make a different choice of pixels, depending on the type size and deviceresolution, but it is always an . A

a different choice of pixels mimics a 72 pt Times A, depending on the device resolution Therefore,all we seem to have to do is to capture the shape of the characters in a form which is available to the simple mathematical transformation of scaling. The shapes of the characters and fonts are the same at different resolutions. In the simplest case, we could think of an approximation of the shapes by a large number of very short straight lines, but fortunately, apart from straight lines, there are some curves that can be scaled very easily, too. Like

Introduction

this, the characters and fonts become representable in a generic form. The process of producing bitmapped fonts from a generic representation is called font-scaling. Ideally,the fonts are scaled on demand, and the scaling is performed in the twinklingof an eye, which is why it is called somewhat informally on-the-fly font-scaling.

0.1 The raster tragedy at low resolution From reading the preceding section the uninitiated reader might object with a question like: "So, what is the problem? All that your font-scalinghas to do is to overlay the shapes with a grid, reflecting the targeted type size and device resolution, and determine the pixels which are covered by the shape by at least 50%." In fact, we have met quite a few colleagues, with a solid background in engineering,who thought that all this boils down to is maybe to devise an efficientalgorithm for the "50%-rule", like the linear incremental algorithm for straight lines [Bresenham65]. With the legacy of what we shall present in Chapter 2 and AppendixA, we have the tools to scale, digitize,and fill straight lines and curves. The respective algorithms are implemented according to the rules of the engineering profession: they are correct, efficient,and concise. In the outline font editor to be presented in Chapter 3, these tools are used fruitfully to acquire characters and fonts in electronic form. Yet, if we apply these algorithms to scale a font down to screen resolution (cf. also 4.1.3), we are presented with results such as the one illustrated below.

A Times font, scaled naively down to 24 pt at 72 dpi Frankly speaking, this feels a bit like being awarded a penalty illegitimately. Unfortunately,we shall find ourselves ended up in a dead end road - despite undoubtedly correct navigationaldecisions - again (cf. particularly4.5.2sq). Lookingat what happens when we overlay the shapes with a grid and subsequently apply the "50%-rule", we begin to see that the problem is not primarily a lack of efficiency, but a much more fundamental one (cf. 4.0).

The shape of a Times H, overlaid by a grid that reflects 18 pt at 72 dpi (left), and applying the "50%-rule" (right) Clearly, in terms of pixels the two stems are unequal, although the original shapes are equal. Likewise, the serifs have become irregular - if they are still there at all - despite the regular original shapes that, in particular, are symmetric. Finally,the entire character disintegrated into two disjointed parts, even though the original shape is connected. If the algorithms are correct, then maybe the filling paradigm was a bit rash, that is, the rule to turn a pixel "on" if at least 50% thereof is covered by the shape. Maybe we should use the paradigm advocated in [Hersch88], that is, to turn a pixel "on" if at least its center lies in the interior of the shape. In fact, in the way in which we shall define coordinates and pixels(cf. 2.0.5), and therefore the digitizing(cf. A.0) and filling(cf. A.1) algorithms, we are actually applyingthis very same fillingparadigm, too.

The same initial situation as above, but applying the "pixel-center-rule" As we can see, the other rule does not help too much. Although the resulting

Introduction

choice of pixels happens to mimic a connected figure, it introduces still far more irregularitiesthan what is acceptable. One point should be emphasized right now, in hopes that this may convince the remaining skeptics. In the above example, even the stems fail to reproduce regularly,although they are nothing but orthogonal rectangles. To digitise and fill such rectangles, neither special purpose digitizingnor fillingalgorithms are necessary. Rather, a primitive of the raster devicecould be used instead (cf. 2.2.2). Thus, whoever should doubt the correctness of any of the algorithms given in A.0 and A.1, or of either of the filling paradigms, is encouraged to comprehend now that there is a lot more behind it. We attach a sequence of illustrations without further comments and invite the alert eye to identifyfurther "raster tragedies" (a term found in [Karow]).

72 pt at 300 dpi

36 pt at 300 dpi

24 pt at 300 dpi

18 pt at 300 dpi

12 pt at 300 dpi

9 pt at 300 dpi

6 pt at 300 dpi Apparently,we have stepped across the Nyquist limit long before we are anywhere near low-resolution font-scaling. The Nyquist limit gives a theoretical lower limit for the minimum sampling rate. It states that a signal can be sampled and reconstructed without loss or distortion if the sampling rate is at least twice the rate of the highest frequencyin the original signal [Bigelow85]. Practically,this has the followingconsequences: The fonts designed at our institute for medium resolution (cf. [Meier91, Meier93] and 3.3) have a font height of 300 to 400 units in font space (cf. 4.0.0), corresponding to a type size of 72 to 96 pt at 300 dpi resolution. In this representation, the thinnest strokes are 3 or 4 pixels wide. Now, if we scale the fonts by a factor of only 1/ 4, corresponding to a type size of 18 to 24 pt at 300 dpi (!), we are already at the Nyquist limit. In fact in the above illustration the 24 pt ehas a "drop-out" - a missing pixel in what at our institute would be considered a headline type size at printer resolution. Therefore,we may expect naive font-scalingto be bound to fail alreadyfor plain text at printer resolution, let alone screen resolution.

0.2 The scope of this thesis The illustrations in the preceding section show clearly that the problem of medium and low resolution font-scalingdiscloses itself in a multitude of seemingly different facets. Conventionally,now, we could compile a catalogue of problems to be tackled. Subsequently, we would devise an individual recipe for each of these problems, specifying how to cure the respective symptom. Finally, we would assess the resulting approach to medium and low resolution font-scalingat its abilityto meet our own demands. It may be a striking argument for the people from the sales department to be able to outdo competitors with the length of their own catalogue of features. We are convinced, however, that most of them - avoiding to say, all of

Introduction

them - fall into one or more of the followingfour categories: Regularity: Intelligent font-scaling provides invariance of translation (of stems, serifs, thicknesses of stroke, etc.), mirroring (of serifs, bowls, shoulders, etc.), and existence(of components, connections, features, etc.). Near regularity: Within the bounds of quantization, intelligent font-scaling avoids disproportionate enlargement of small divergences from sternly regular parts and measures (such as optical corrections and reference line overlaps). Constrained proportionality: Within the bounds of quantization and the constraints imposed by safeguarding components against disappearance, intelligent font-scalingpreserves proportionality of parts and measures. Digitized appearance: Last but not least, intelligent font-scaling allows to influence the inevitable patterns of jaggedness on the fringe of slanted and curved parts without allowingat the same time to refer to individual pixels directly. We understand that some of these terms may appear somewhat alien to the uninitiated reader, but we are convinced that they will become meaningful, at the latest by the end of Chapter 4. With that, the scope of this thesis is to devise and implement an algorithm for scaling fonts on-the-fly, for medium and particularly low resolution raster devices, provided the generic representation of the fonts contains enough information to permit avoidingany of the problems of the above categories.Furthermore, it is our personal goal to be comprehensive as far as the employed algorithms are concerned, which comprises the complete scaling algorithm as well as the digitizingand rendering algorithms for scan-convertingand filling straight lines and curves.In doing so we put our cards on the table in hopes that this may provide future approaches to both font-scaling in particular and two-dimensionalgraphics in general with a sound starting capital.

0.3 Guide to the reader Even to the uninitiated reader it should have become clear by now that what we are about to tackle is indeed a problem. It is not simply an industrious but uninspired piece of work, or maybe a laborious engineerstask. In view of the multitude of mishaps that can happen, scattered over different levels of abstraction, and in accordance with the goals set out in the preceding section, the present thesis has grown to respectable dimensions. Therefore,here is a short guide to the reader: Chapter 1 (The others: A brief overview of existing approaches) opens a

bracket around the main part. It briefly introduces both academic and industrial approaches to font-scaling. Chapter 2 (The foundations: an object-oriented graphical toolbox) gives the graphical basis of the project. Although today one may take versatile graphical capabilities for granted, the reader is invited to pay attention at least to sections 2.0 (Coordinates, pixels, and rectilinear regions) and 2.3 (Assessment of the new approach for a graphical toolbox). Chapter 3 (The input: A simple outline-font editor) illustrates an application of the graphical toolbox in our own programming environment. Readers uninterested in its particulars are encouraged at least not to miss out section 3.2 (An experience with third order Bzier curves). Chapter 4 (The backbone: A formalism for intelligent outline-fonts parti), cularly 4.4 (Implied dynamic regularisation), constitutes the core of our approach. Of this material at most the sections 4.6 (Inter-character spacing & "wysiwyg" Type-Setting) and 4.7 (Formal Notation & Graphical Feed-Back) may be left out in a first reading. Chapter 5 (The implementation: An extensible OOP style application), finally, illustrates the actual realization of the prototype. It may be a bit far away from the conceptual range of typography. Chapter 6 (The balance: A competitive prototype) closes the bracket around the main part. It compares two popular industrial approaches with our new approach. AppendixA(The algorithms lists and describes all the algorithms we have ) used in our font-scaler,including many refinements to existingalgorithms that may be of interest to the practitioner in raster graphics. Appendices B, C, and D (The format of the unstructured outline-fonts The , definition of the language for intelligent outline-Fonts and A self-contained , example of a font definition) are mostly for reference purposes. We hope that this helps the reader not to lose track of things or just to restrict the reading to the passages containing the major novelties.

1 The Others: ABrief Overview of Existing Approaches


1.0 The MIT Project: A formal approach with components The preceding chapter has conveyed a first glimpse at the extent of the low resolution font-scaling problem. It is the very scope of this thesis to contribute a solution to this problem, without introducing a completely new problem to begin with.But since it is not a new problem at all, there must be other approaches to it already.This is why the present chapter gives a brief overview of existingapproaches. We are concerned to make clear that this overview is nowhere near exhaustive - being so might easily amount on an entire thesis of its own. Rather, we would like to present a selection of widely differingapproaches, and we shall mention related approaches as appropriate. For historical reasons, and not the least because it pursues an approach with a certain similarity to ours, we mention the famous project launched at the Massachusetts Institute of Technology (MIT) some twenty years ago [Coueignoux75 In the early seventies, memory must have been prohibitively ]. expensive.Reportedly,the hardware available for the project comprised like 64 kB main memory and 1 MB of disk space. (Incidentally, our first experiences with computer programming took place in 1981 on a computer with 16 kB main memory and a tape connector, later to be replaced by a 140 kB floppy disk. Today, we would not be surprised anymore to have 64 MB main memory and 1 GB of disk space, instead.) Problems regarding these constraints run throughout the wholethesis by Coueignoux. However, this had a substantial benefit, namely to have to look for a compact font representation. One of the factors contributing to memory effectiveness is to store repeated occurrences of one and the same component only once, which at the same time eliminates the necessity to see to it that equal components be rendered equally: If there is only one instance of the stem or the serif, then all the characters built with that kind of stem or serif can refer to it.

Times "HILFE", exploded view

10

The Others:

The method according to which the components are put together is specified by means of quite comprehensive a grammarfor roman printed fonts, which is why we have classified it as a formal approach. The intuitive idea to decompose characters into their constituent glyphs (stems, serifs, etc.), if they are to retain a certain degree of regularity under scaling, is bound to fail in case the considered glyphs are not quite equal, but in a way "similar" to one another.

Times "ET", exploded view The top arm of the "E" ( ) may look like the ones of the "T" ( ), but it is not quite identical to them. Likewise, the bottom left serif of the "E" or the "T" ( ), when mirrored at y := - x ( ), is not the same as the one that is part of the nose of the "E" ( ), yet they look as if they were related to one another. Coueignouxwas of course aware of this fact, which is why he parameterized the glyphs or primitives, as he calls the two-dimensional building blocks. With that, one may understand the terminal nodes (or leaves)of the grammar (or blue-prints) to be modeled by a procedure (one for each character of the alphabet) that makes use of the parameterized glyphs. Still,said parametrization is not yet quite sufficient if the character decomposition is to keep at two-dimensional primitives only, which can be seen in the followingsituation.

Times "ceo" As clearly as above, these characters have a certain affinity to one another, such as their overall proportions or the thicknesses of stroke. In fact, in the classification introduced in [Adams89], they appear in the same group. Yet, to decompose them into equal glyphs gives rise to separation lines which are not related to the respective characters in a natural way. In spite of this draw-

A Brief Overview of Existing Approaches

11

back (which may explain why - according to [Karow89] - it never became a success) we further pursued the path that decomposes characters into their constituent parts. We shall see that this approach is not bound to fail provided the decomposition does not stop on the level of two-dimensional building blocks.

1.1 The ETH Project: An image-oriented approach Formal or descriptive approaches have a general drawback,which is the necessity to have to "program" fonts. The famous METAFONT [Knuth86] is a specimen of this category. Whether it is the early version that strokes the skeleton of characters using brushes of different sizes and orientations, or whether it is a later version that allowscharacters to be defined also by outlines, in the end each character is described by a program. This not only impedes bringing the wealth of existingfonts to computers, but it is likely to make this method unavailable to font design, since programming is anticipated to lie outside the type-designersusual field of activity. This is one of the reasons why Kohen has adopted quite different a line [Kohen87, Kohen88]. In his system for medium resolution font design the fonts are acquired by the use of a graphics editor that has been especially tailored to the demands of character design (cf. also Chapter 3). After an initial training phase, this editor was operated productively by professional type designers (cf. e.g. [Meier91]). The font design system comprised an algorithm for scaling fonts for medium resolution printers (300 dpi). Basically,it used a variant of the error diffusion algorithm introduced by [FloydSteinberg75 ], which is why we have classified it as an image-oriented approach. Alot of finesse must have been put in the way error diffusion is done, since the actual diffusion step is driven by the particular topology of the glyph at hand (cf. also A.2.1). Reportedly,this accounts for aesthetically pleasing digitized appearance, or local beauty, as Kohen calls it. Localbeauty regards questions like nicelyrastered arcs or half-bittingapplied to specific parts of glyphs.

some Times 24 pt characters, hand-tuned for 300 dpi (enlarged)

12

The Others:

Unfortunately,some of the vital parameters of the algorithm are not recorded for posterity,which is why we could not easilybase our own research on it. There are other reasons why we did not pursue the same path any further, though. Error diffusion algorithms are to a certain extent statistical in nature. However, our goal includes font scaling at low resolution, where characters consist of a few coarse pixels only, thus making statistical approaches questionable. Furthermore, they are at least an order of decimal magnitude slower than outline-oriented algorithms (cf. A.2.1 and 4.1.3). On top of that, they cannot easily model global consistencies, especiallyin case of curvilinear glyphs. Already Kohen was aware of that and presented in [KohenGutknecht89] a hybrid approach or, as he calls it, a combined algorithm.

1.2 Adobe Type 1 Fonts: An approach with hints By the start of the present decade, two companies successfully marketed commercial products that let personal computers scale fonts on demand down to screen resolution [Adobe90, Apple90]. Adobes Type 1 fonts emerged from a long tradition in page description [Adobe85], while ApplesTrueType fonts reportedly are the result of printer companies not wanting to pay Adobe royalties, and constitute the answer to Adobesinitial unwillingness to unveil the Type 1 Font Format [Fenton90]. Let us first look at AdobesType 1 fonts. (Type 3 fonts, PostScripts"user-defined fonts", are fonts without hints as below, nor any other "font-intelligence".) AType 1 font essentially is a program that uses a small subset of the original PostScript language, together with a few powerful extensions. To capture global information of a font or an entire family,a Type 1 font comprises various dictionaries that enlist the reference lines (descender, base, mean, cap, and ascender line (cf. 3.1.6, 3.3), together with their optical corrections (cf. 4.4.2).

The base and cap line, together with their optical corrections

A Brief Overview of Existing Approaches

13

Adobe calls the dictionaries BlueValues, OtherBlues, FamilyBlues, and FamilyOtherBlues, etc. In their terminology,family applies to our font scope, in contrast to the variant scope (regarding scopes cf. 4.3.6), while "other" specifies reference lines below the base line. The last distinction does not appear necessary to us. A Type 1 hint boils down to the fact that an entry may or may not be present in the dictionary, in which case a default value is assumed. The exact behavior is well-documented and possibly can even be re-configured.Likewise, the underlying algorithm may or may not be able to obey the hint, in which case it is simply ignored. In particular, rather than having the primitives for lines and curves only (lineto, curveto, etc.), Type 1 supports so-called hint commands giving information about the ranges of a zone occupied by a predominantly horizontal or verticalpart. Such a part may be rectilinear or curvilinear (cf. 4.4.1) and is what Type 1 calls a stem (hstem, vstem, etc.). For example, a vstem3 specifies the horizontal ranges of 3 verticalstem zones, which "is especially suited for controlling the stems and counters of characters such as a lower case m" [Adobe90]. However, Type 1 hint commands do not amount on a decomposition into components (i.e. horizontal stems or vertical stems etc.). Rather, the values specified together with the respective commands are in effect for the entire character. This means for the "E" below that either the serifs on the left and the crossbars on the right are expected to have the same thickness of stroke (which is called a hstem in this context), or the hints have to be (and can be) changed amidst the definition of a character outline path.

A Times "E" with (left) and without (right) equal thicknesses of horizontal stroke Also, e.g. a sans-serif "I" (which in its simplest case we would understand to be made of a single orthogonal rectangle) seems to require an assembly out of a verticalstem with two horizontal so-calledghost stems at either end, in order

14

The Others:

for the actual stems horizontal alignment with the blue values to work properly. This suggests to us a certain lack of flexibility of the approach, even in so far as the shapes are letter-like.(In fact, Adobe recommends to use Type 3 fonts for other shapes.) An approach similar to AdobesType 1 fonts is pursued by Agfa Compugraphic with their Intellifont format. Intellifonts are used in PCL5, the page description language that comes with HewlettPackardslaser printers, and therefore is possibly much more wide-spread than widely known. The Intellifont format starts out from the IKARUS format [Karow92c] and, as with Type 1 fonts, adds extra information to the bare outlines, but in contrast to Adobe, Agfa Compugraphic calls this extra information instructions . Analogousto Type 1 fonts, Intellifonts distinguish global instructions that are common to an entire font from local instructions that are specific to individual characters. On the global side, we are not surprised to be presented with reference lines and standard dimensions in x- and y-direction, roughly the equivalent of Adobesblue values, vstems, and hstems respectively,and related to the goals targeted by TrueTypescontrol-value-table(cf. 1.3). On the local side, however, Intellifonts differ from Type 1 fonts in that they emphasize by concept the role of local extremal points and, in general, the role of points that delimit the thickness of stroke. Agfa Compugraphic calls these points skeletal points and stores them in addition to the other support- or control-points.

A Times "B" with skeletal points In general, these skeletal points are scaled and rounded to the nearest grid line. Atree-likestructure, called association, is superimposed on these skeletal points. This permits to define the stem of the above "B" to be scaled and rounded before the serifs, or to have the counterforms correct dimensions (correct with respect to a giventype size and deviceresolution) take precedence over correct overallwidth of the character at issue (cf. 4.0.0).

A Brief Overview of Existing Approaches

15

1.3 Apple/Microsoft TrueTypeFonts: An approach with instructions In contrast to Type 1 fonts, flexibility is most definitely not a weakness of TrueType fonts, at least not a lack thereof. On the contrary, the vast flexibility of the TrueType approach may at best befuddle the uninitiated designer of digital fonts. Actually, TrueType fonts are fully-grown assembly language programs for a stack machine. Apart from basic arithmetical and conditional instructions, they comprise special-purpose instructions for explicitly fitting knots (on- or off-curve points, cf. 3.2.0sq) to grid lines under subordination to a wealth of parameters and tables. These parameters and tables are global to the interpreter and may therefore governprocesses that are global to an entire font or variant.They are collected in what TrueType calls the graphics state. The graphics state contains in particular the current values for the freedom vector ( ) and the projection vector ( ). These two vectors constrain the way knots are moved upon grid-fitting:Motion takes place along the freedom vector with the purpose to assume a distance that, simultaneously, is as close as possible to a givendistance (measured parallel to the projection vector) comprises an integral multiple of the targeted grid unit (measured on the freedom vector).

To fit diagonal strokes to the grid, the projection vector is perpendicular to the diagonal, while the freedom vector runs in parallel to the reference lines. The way in which said integral multiple is determined depends furthermore on one of severalround-states, which is part of the graphics state, too. The special-purpose instructions that invoke such a motion are called MDAP, MIAP, MDRP, and MIRP (Move Direct/Indirect Absolute/Relative Point). In this context, the term absolute denotes an absolute distance, while relative relates to a distance relative to a reference point (which may or may not have been moved already).This helps to avoid, amongst other, unequally

16

The Others:

rastered stem and crossbar widths (cf. 4.3.0). In the same context, the term direct means that the distance is measured directly(parallel to the projection vector), while indirect replaces the measured distance by the nearest one found in the CVT (Control-Value-Table) if it is "sufficientlynear" it (the latter being governedby the control-value-cut-in). With this replacement, all kinds of distances (stem or crossbar widths, serif heights, etc.) can be collected to groups of nearly equal distances in order to have them assume the same values under sufficiently coarse scaling (cf. 4.4.2). Both the control-value-table and the control-value-cut-in belong to the graphics state as well. At the lower limit (cf. 4.4.7), the single-width-value and the single-width-cut-in stand in for the same purpose. At least the last distinction does not appear necessary to us. The rounding can respect a minimum distance, such as to prevent coarsely scaled distances and simple glyphs like orthogonal rectangles from vanishing (cf. 4.3.1). For less obvious cases, a more general approach can be activated, called scan control mode, which analyses the rastered result against discontinuities (cf. 4.5.2sq). Cases not covered by this most general drop-out control mechanism can be cured by the possibility to specifyexceptions, called delta instructions that in the end even permit to patch individual pixelsfor a specif, ic type size and at a givendeviceresolution. The possibility to directly refer to individual pixels rises questions regarding deviceindependence, for the pixel size is a function of the targeted resolution. Further features in TrueType fonts take us more and more away from the conceptual range of a type-designer.Takingthe pick of the bunch, measured magnitudes may have to be forced to assume non-negativevalues (auto-flip), reference line overlaps and related optical corrections are covered by twilight zones and points (cf. 4.4.2), whilefor such simple things as side bearings TrueType introduces phantom points (cf. 4.6.1). To recapitulate, in the TrueType approach much of the font-intelligence lies in the way the instructions are used, which is in contrast to the Type 1 approach. Therefore,the TrueType approach may serve as a common and portable basis to different ways in which the font-intelligenceis provided. However, even if we disregard the assembly language approach at all, the conceptual range adopted by the TrueType approach is on a purely geometric level of abstraction, whilethe one by Type 1 is much more closelyrelated to the worldof font-design.(The absence of typographicalterms and notions applies particularlyto the F3 format by Sun Microsystems,which is why shall not go into the F3 format in more detail.) Reportedly,TrueTypesflexibility has to be paid for with higher production costs of a font [Karow92c], while Type 1s simplicity may make it better suited for automatic acquisition of hints [Hersch91], such

A Brief Overview of Existing Approaches

17

as the auto-hintingproposed in [Karow89, HerschBtrisey91b Btrisey93 , ]. At this point it may be appropriate to give the prospects for our own approach. Basically,we shall decompose a font into equal or symmetric component parts. In contrast to [Coueignoux75 components are not restricted to entire ], glyphs, but may be contours, knots, and even single numbers as well. This permits to define for the components a hierarchical structure that specifies how more complex components depend on simpler ones. To define the structure, we shall adopt a conceptual range similar to that of Type 1 fonts [Adobe90], but without concealing the implementation of the font engine. To adapt the components to the grid for a given type size and device resolution, we offera flexibility close to that of TrueType fonts [Apple90], but additionally we explain in full detail, how this flexibility is employed best for the grid adaptation. Finally, the particular implementation gives a means of talking about digitized appearance or local beauty as suggested by [Kohen88], but without foregoingthe efficiencyadvantage of an outline-oriented approach. In retrospect, we may therefore understand our own approach to be a hybrid approach in the sense that it combines the strengths of the approaches introduced in this section without inheriting at the same time their weaknesses as well.

2 The Foundations: An Object-Oriented Graphical Toolbox


2.0 Coordinates, pixels, and rectilinear regions 2.0.0 Vector displays The primary source of problems with the quantization of continuous graphical objects is the blue-eyedness by which beginners associate abstract mathematical coordinates with visible technical pixels. Therefore we first present a couple of our own experiences made with two inherently different ways of looking at coordinates and pixels. The conclusions drawn at the end of this section are a sound definition of coordinates and pixels, and the introduction of rectilinear regions. At the beginning of interactive computer graphics in the early 1960s, display devices were usually some form of line drawing or vector displays [NewmanSproull82 On such devices, vertices of polygons to be displayed ]. were kept in so-called display files or lists that were traversed,in turn, to direct the electron gun from one vertex to the next one. At the end of the list, directing the electron gun would simply start over again, and so forth.

a straight line drawn on a vector display

2.0.1 Raster displays In the late 1960s, display lists were eventuallyreplaced by refresh buffers containing - instead of vertices of polygons - a two-dimensional array of all the (discrete) points that the electron beam can display as separate but closely spaced tiny little dots [NewmanSproull82 In such devicesthe electron gun is ]. directed across the screen in horizontal rows from dot to dot, rather than

An Object-Oriented Graphical Toolbox

19

being directed from vertex to vertex. For each dot that the refresh buffer marks as a visibledot, the electron gun is turned on, and off for invisible dots. These devicesare essentially the raster displays that we are still using today. The obvious disadvantage of refresh buffers is that we cannot simply draw a straight line by storing its end points in a display list. Instead, we have to calculate and store in the refresh buffer a (discrete) choice of those dots that approximate the (analog) line as closelyas possible.

a straight line drawn on a raster device Of course, refresh buffers have obvious advantages as well. Now we can draw not only straight lines, but also circles and other curves, simply by calculating a different choice of dots. Another advantage of software controlled refresh buffers is that they allow us to display solid two-dimensional objects rather easily, simply by turning on adjacent (rows of) dots, as is the case in one of the followingillustrations. The graphical primitives have become available to software,rather than being restricted to hardware.

2.0.2 The grid-point model To calculate a choice of dots means first of all to associate abstract mathematical coordinates with these dots. Keeping in mind the way raster devices access the refresh buffer, it may appear quite natural to simply number consecutivelythe discrete geometrical points that the raster device can address. Doing so all the way through the complete choice of dots, row-by-row, and within each row, dot-by-dot,the numbering is equivalent to an integer grid of horizontal and vertical coordinates. Each (geometrical) point at which a horizontal and a verticalcoordinate line intersect constitutes the center of a (technical) dot. Upon devising our first algorithms for drawing lines and curves we intuitively understood the picture elements or pixels that make up for the visible result on a raster display to be the same as the dots just introduced. Since the dots by themselves are approximations of the infinitesimal points in geometry,

20

The Foundations:

we effectively associated the pixels with the points on the grid. This model to define coordinates and pixelsis usually called the grid-point model.

a straight line drawn in the grid-point model Based on the grid-point model we implemented a set of algorithms for drawing hair lines, circles, arbitrary ellipses and natural splines [Stamm89, Ch. 1]. However, if we now wanted to assemble a polygon out of a sequence of such hair lines, we would run into the "end-point-paranoia",that is, the question whether the last pixel of one line belongs to the next line already,or not. This may sound like a nit-pickerputting carefullythe dots on the is,since the pixels are intended to model geometrical points without physical extension. But in the grid-point model the points actually do have physical extension. Therefore,if we inverted such a polygon(e.g. to implement dragging,cf. 3.1.1), the respectivepixelswould be drawn twice, and thus remain invisible.

a triangle (left) inverted with missing vertices (right) in the grid-point model Likewise, if we used the calculated points not for drawing dots, but as entries into a table for parity filling(cf. A.1.2sq), we may get two identical or adjacent table entries at the same vertex. In the triangle below, this causes the fillingto leave most of the respective pixel span empty, whilehaving it leak out opposite to the vertex at issue. Conversely, if we decided not to enter the triangles vertices twice, we may get the pixel span in the middle, but at the expense of missing verticesat the top and at the bottom, like with the outlined triangle.

An Object-Oriented Graphical Toolbox

21

a triangle (left) and a polygon (right) filled with errors in the grid-point model Even worse, in the polygon above, this causes another vertex to be the source of a leak. The seemingly natural decision to simply number through all the displayable dots is causing the algorithms for calculating choices of dots a couple of rather unnatural roundabout ways.

2.0.3 The checker-board model By the time we started rendering genuinely two-dimensional objects, we were aware of the above problems with the grid-point model. Right from the very outset, we therefore interpreted the role of pixels differently.Rather than trying to hide the information that raster displays cannot approximate geometrical points any better than by tiny little dots, we accepted the fact that pixels are small but finite areas. Starting out from a grid of integer coordinates, the role of a pixel is no longer that of a dot on a grid-point, but that of filling the gap between adjacent pairs of horizontal and verticalgrid lines. This model to define coordinates and pixelsis usually called the checker-board model.

a circle, outlined 3 pixels thick (left) and filled completely (right) in the checker-board model

22

The Foundations:

Rendering thick lines and curves in the checker-board model can use roughly the same algorithms as drawing hair lines in the grid-point model. The main difference regards the way pixels are determined at the edge of such objects. Rather than calculating the dots that are as close to the analog edge as possible, this time we have to calculate, on each row of pixels, the first and the last pixel that the analog object coversby at least 50%. Based on the checker-boardmodel we implemented a set of algorithms for drawing thick lines and curves [Stamm89, Ch. 3]. Due to the chosen model for associating coordinates with pixels, and since the parameters that determine the geometryof the lines and curves were integers, in none of these algorithms we had to face problems like missing vertices or pixel spans, such as discussed above. It is therefore not surprising to us that this way of looking at pixels has become the "de facto" industry standard [Hersch]. However, if it comes to outlining, the checker-board model may be a choice that needs further explanation. We will come back to this right below.

2.0.4 A conflict of models With the above foundations in two-dimensional graphics we started devising our first outline-font editor. One of the editors features was to let the type designer view the characters both unfilled and filled, which is a vital asset for a high-qualitytype design tool. The efficiencyof our routines, together with the computing power of the personal workstations of those days, would permit to manipulate at interactivespeeds even filledcharacters. However, at this point another case of the "plus-minus-one-paranoia" thwarted our plans: the outlined characters appeared to be slightly bigger than their filledversions.

the same circle of radius 8 (middle), outlined in the grid-point model (left), and filled in the checker-board model (right)

An Object-Oriented Graphical Toolbox

23

The reason for this mishap is, we unconsciously mingled the grid-pointmodel with the checker-boardmodel. The routines for unfilled outlines were employing the grid-point model, while the routines for filled outlines were adhering to the checker-board model. Using a simple circle as an example, the consequences thereof are illustrated above. In the grid-point model, the outlined circle has a diameter of 2r + 1 pixels, hence its graphical bounding box exceeds the mathematically correct bounding box. In the checker-board model, the filled circle has a correct 2r pixels, and therefore also its bounding box is correct. For large radii, this may be totally irrelevant,but in view of font-scalingbeing targeted at low-resolution,a single pixel is too large an area to be ignored. In fact, even at the original resolution of the outline font characters, extending over several hundred (!) pixels on screen, our type designer Hans Ed. Meier would point out promptly that when switching the viewing attributes to "solid" (cf. 3.1.6) the characters seem to "shrink", which is unacceptable for a high-qualitytype design tool. By now we should have become aware of the fact that there are two different ways to define coordinates, and that there is rationale for both of them: In the grid-point model the coordinate points are associated with the pixels, hence they extend over a small but finite range.

A 3 by 4 array in the grid-point model (left), the coordinate point [2, 1] (right) The grid-point model may be the natural way to draw hair lines, that is, to approximate truly one-dimensional objects but, besides implementational difficulties,it does not yieldmathematically consistent bounding boxes. In the checker-board model the coordinate points are associated with the intersections of the infinitelythin grid lines, constraining the small but finite gaps filledup by the pixels.

The same array (left) and coordinate point (right) in the checker-board model

24

The Foundations:

The checker-boardmodel looks like the natural choice for truly two-dimensional objects and thick lines, but drawinghair lines is not that obvious in this model. Unfortunately, the two models are not compatible with one another. There is no one-to-onemapping that relates one model to the other, and vice versa. At this point the rash reader might argue astutely that our problem is only that of specifyingthe origin of a pixel, that is, whether it is at [1/ 2, 1/ 2] or [0, 0]. However, this does not solve the bounding box problem, but merely shift the entire coordinate plane by a vector [- 1/ 2, - 1/ 2], which we could accomplish as well with dislocating our computers monitor as a whole.

the same circle, outlined in the grid-point model, but with the pixelsorigin at [1/ 2, 1/ 2] (left), and at [0, 0] (right): Both circles exceed the mathematically correct bounding box This means that we have to make a choice for either model of the two, and that once we have made our choice, we have to comply with our model systematically,if not to say, painstakingly.

2.0.5 Conclusion 1: Coordinates vs. pixels The reason why the grid-point model fails for solid objects is the intention to describe mathematically precise (coordinate) points with imprecise technical pixels. As long as we do not decouple the notion of a point from the notion of a pixel, we are always tempted to associate with the coordinate lines a clearance of half a pixel or more. Like this, any subsequent notions of length, such as the basic dimensions of characters, stems, and serifs, or optical corrections to stroke thicknesses and reference line overlaps, are vague, if not to say, simply wrong at the quantization limit. Therefore,our first conclusion is to choose the checker-board model, and the followingdefinitions separate coordinates

An Object-Oriented Graphical Toolbox

25

from pixels: Acoordinate is an integer which denotes an infinitelythin horizontal or vertical line in the Euclidean plane.

the coordinates x = 0, 1, , 7, and y = 0, 1, , 5 Apoint is the intersection of a horizontal with a verticalcoordinate.

the point p = (x, y) = (2, 1) A pixel spans a part of the Euclidean plane which is bordered by a pair of adjacent verticaland horizontal coordinates.

the pixel a = [x, y, X, Y] = [2, 1, 3, 2] An orthogonal rectangle spans a part of the Euclidean plane which is bordered by a pair of verticaland horizontal but not necessarily adjacent coordinates.

26

The Foundations:

the orthogonal rectangle a = [x, y, X, Y] = [2, 1, 6, 4] This is the only way to define mathematically pure geometric entities! Looking at the real world, our definition does not appear that academic at all. The first popular graphical user interface uses the same definition in its toolbox, too [Apple85, later Petzold92]. From the above definitions, the width w and the height h of an orthogonal rectangle [x, y, X, Y] are straightforward,but most important. Mathematically, they are w := X - x and h := Y - y, and graphically,they are w and h pixelsrespectively;the mathematicaland the graphical notions of length or distance correspond. The orthogonal rectangle constitutes a generalization of pixels to arbitrary integral width and height. Conversely, if (X - x) = (Y - y) = 1, then the orthogonal rectangle spans exactly one pixel. Consequently, the orthogonal rectangle is a null rectangle,if (x = X) (y = Y), that is, its width or its height comprises not even a single pixel. This may sound academic, but this very conception of distance and its corresponding graphical appearance have proved most useful - at the latest for section 4.4, where we talk about dynamic regularization (characters components which may vanish under coarse scaling) and drop-outs (components which must not do so). Notice, finally, the superiority of the checker-board model: If we were to define orthogonal rectangles using the grid-point model, then the mathematical width X - x would span X - x + 1 pixels, which means

An Object-Oriented Graphical Toolbox

27

that null pixels would have to be defined by a negativemathematical length an idea from which we should disassociate ourselves clearly! To consolidate our understanding of orthogonal rectangles, the following definitions illustrate, in the order of appearance, the empty area, a single coordinate point, the union of two bounding boxes, and the result of clipping one area against another, := [, , - , - ] (x, y) := [x, y, x, y] a b := [Min(ax, bx), Min(ay, by), Max(aX, bX), Max(aY, bY)] a b := [Max(ax, bx), Max(ay, by), Min(aX, bX), Min(aY, bY)] givenany point (x, y) and any two orthogonal rectangles a = [ax, ay, aX, aY] and b = [bx, by, bX, bY].

2.0.6 Conclusion 2: Rectilinear regions For the objectivesof both outlining and fillingwith the same (mathematically correct) bounding box, we should be looking neither for the pixels that are closest to some analog edge, nor for pixel spans whose first and last pixelsare covered by at least 50%. Rather, we should be looking for an entity without physical extension. Therefore, our second conclusion is to calculate instead the (digital) boundaries between the interior and the exterior pixels that we would get as a result of fillinggenuinelytwo-dimensionalobjects according to the checker-board model. We use the term rectilinear regions for these boundaries and define them as follows: Arectilinear region is the entity which represents the (digital) boundary of a not necessarily simply connected set of orthogonal rectangles.

the rectilinear region defining the (digitized) shape of a circle

28

The Foundations:

In this sense, rectilinear regions constitute a natural generalization of orthogonal rectangles to arbitrary,multiply connected, digitizedshapes. Starting out from its rectilinear region, defining a filled two-dimensional object is trivialby concept, while outlining it requires us to make a choice; the choice to turn on interior pixels that are immediately adjacent to the digital boundary, rather than exterior pixels.

the rectilinear region defining the shape of a circle (middle), together with the outlined (left) and filled (right) region. This is quite a natural choice, though, for turning on exterior pixels adjacent to the boundary would simply increase the effect of "shrinking" upon switching the viewing attributes to "solid". It may be helpful to understand a rectilinear region as one or more closed polygons whose vertices are on grid points and whose edges are on grid lines. Subsequent verticesof such polygonsdifferfrom one another either in their xor in their y-coordinate,i.e. by a so-called "Manhattan move", but not in both. If both directions were allowed to differ simultaneously, the missing coordinate point would have to be interpolated for subsequent outlining or filling.

a simultaneous unit step in x- and y-direction (middle) is ambiguous: Without knowing the original shape, both interpolations(left and right) are correct

An Object-Oriented Graphical Toolbox

29

This can be done in two different ways: An ambiguity which may result e.g. in a coarselyscaled serif being rendered too large. The fact that the "Manhattan moves" are restricted to unit steps, i.e. moving "block" by "block", may be considered an implementational decision. In doing so, calculating the pixel boundaries can be done strictly incrementally, which may be simpler but less efficient(but cf. A.1.1). All in all, we have simply generalized "the conventional choice to make (integer) pixel boundaries intuitive"[NewmanSproull82 ].

2.0.7 Conclusion 3: Object-oriented programming We hope that one particular idea follows from reading between the lines of the preceding sections. We did not make all the above considerations in the very same orderly way before implementing the graphics toolbox to be used throughout the rest of our project. It would be a downright lie if we claimed to have done so. Rather, we took certain things for granted, notably the two different models for defining coordinates and pixels, or the idea that speed is the primary concern of the practitioner in raster graphics, and hence that performance justifies next to any amount of code. But eventually, a major rewrite of the graphics library became apparent. For use by professional typographers, a font-editor has to fill characters without the effect of "shrinking the outlines". Although this is not that difficult to "fix", we would rather package the solution to this problem in such a way that it can be used equally by straight lines and all the present and future curves. Likewise, we would rather have future curves be concerned only with calculating (parts of) rectilinear regions, but not the problem how to fill or outline these regions correctly.Last but not least, we would rather not have to record necessarily all the "Manhattan moves" that make up for a rectilinear region if this should be just for the sake of "interfacing"between finding pixel boundaries and fillingor outlining these boundaries correctly. What these problems have in common is the wish to better structure the respective computer program into re-usable and interchangeable component parts. Today, a popular way to do so is to use some form of object-oriented programming (OOP) style. Since we had already implemented all kinds of digitizing algorithms for solids, respecting the checker-board model and the pixel boundaries, and since we eventuallyknew how we should have done the outlining in the first place, given the pixel boundaries of all kinds of solids, our third conclusion is to give OOP style a chance to pay off. Assuming that the interested reader may be familiar with all kinds of prob-

30

The Foundations:

lems regarding digital typography,but not necessarily with their implementation in OOP style - let alone our notation to do so - the next section will give a brief introduction to OOP style, together with its notation, in hopes that it be generallyintelligible.The next section is followedby a description of the particular factorization chosen, and a critical assessment of our approach.

2.1 Concepts of object-oriented programming and their notation 2.1.0 Encapsulation Object-oriented programmingis one of todaysen voguewords, and OOP is its frequently traded abbreviation. While for some readers OOP may have become a bit hackneyed already, the somewhat more traditional programmers could still be afraid of missing the bus. The realityis probably somewhere inbetween these two extremes. OOP on its own is neither a giant leap forward, nor does it solve all the remaining problems of computer programming necessarily. Rather, we understand OOP as an evolutionary step beyond such well known paradigms as information hiding or structured programming ne, vertheless coming in quite handy sometimes. The followingaccount of this evolution should bring the reader, whom we assume to be familiar with the basics of a Pascal-likelanguage, to a rough understanding of modern hybrid languages - traditional imperative languages with OOP extensions. Computer programs are essentially recipes for processing machine-readable ingredients. These recipes are often termed algorithms while the ingredi, ents receive the common name data. The introduction of high-level programming languages (cf. also 4.1.1) started to bring computer programming closer to the conceptual range of the actual programmers. Alanguage like FORTRAN (FORmula TRANslator) was targeted at scientists who wished to make iterated formulae evaluations available to computers, while a language like COBOL (COmmon Business Oriented Language) was geared to the book-keeper who wished to automate payrolls or lists of accounts receivableand suchlike. Ever since the advent of the first high-levelprogramming languages, efforts were made to increase the languages expressiveness and to avert program errors due to the lack of the latter. The introduction of subroutines or procedures started to involveissues like existence or visibility of data. For the first time, data that are not relevant for the rest of the program could be hidden away from the latter by declaring them in a local scope. This may be the case with bound variablesor intermediate results of lengthycomputations. Subsequently, the module concept provided not only for separate compila-

An Object-Oriented Graphical Toolbox

31

tion, but also for the separation of the existence of data from their visibility. Now it was possible for data to exist without being visibleby other modules or compilation units. But not being visible means not being accessible, whether intentionally or by accident. By hiding the data in the implementation part of the module, clients did not have to know anymore the details of the inter-dependence of data, whileinvariant properties of the data could be guaranteed to the clients by the capsule called module in Modula-2 or unit in UCSD-Pascal. Such invariants may be as simple as asserting that an index points to the next free slot of a table, or as complex as the correspondences between the accounts payable and the accounts receivable. The notion of an abstract data type may be considered a preliminary pinnacle of this evolution. An abstract data type or ADT is a type whose internals are not unveiled to its clients directly,but indirectly only through a set of access procedures. With that, the advantages of encapsulationhave been made availableon the level of variables,rather than modules only,since now clients can own several instances of the same ADT. A simple example may be the implementation of a financial amount type as a very long integer with an extra byte indicating how many decimal digits thereof are actually decimal places; the invariant being that the number of decimal places is always non-negative, and hence the "dollar-and-cents"formatting can rely on this fact. Regarding the notation of encapsulation, we do not intend to make our own proposal, partly in view of the considerable number of already existing notations (Ada, C++, Modula-2,Oberon, UCSD-Pascal, and partly because ), we would rather expect the reader to be experienced enough to see, throughout the rest of this thesis, which parts of the examples are to be exported for public use or are better restricted to private use.

2.1.1 Late binding The preceding sub-section has focused on the ingredients of programs, bringing the restrictiveconstraints of asserting delicate invariants to considerable flexibility in the form of ADTs. But this is not the only path that leads to OOP. In the present sub-section, therefore, we shall look as well at the recipes for processing the ingredients, and how flexibility can be added there. Quite early on, programming languages had to face the problem that only a part of the recipe may be known upon designing a computer program. Suppose we wanted to do a program for numeric integration, for which numerous algorithms exist. Usually,these algorithms abstract from the particular function to be integrated by a stereotyped phrase like "Let f(x) be an integrable

32

The Foundations:

function In practice, this means that we have to commit ourselves to a par". ticular function to be integrated already upon implementing the integrating procedure, rather than being allowedto implement the actual integration only and supply the functions to be integrated to the integrating procedure later, in the form of a parameter. The designers of Algol-60 were certainly aware of this fact, which is why parameters could be passed to procedures by name (in contrast to by value), and therefore not only the names of variables, but as well those of functions. While this is a mathematically most satisfying solution, it is not always that simple to explain,let alone to realize it in practice in a compiler. It is therefore intelligible that in Pascal the general call-by-name parameter mechanism was downsized to the call-by-reference mechanism, but complemented by the possibility to supply functions and procedures as actual parameters. With that, the aforementioned demand - to decide later, which function to integrate could be met. It is known that the appetite grows with the eating. Hence it is not surprising that soon the wish emerged to bind such procedural parameters to a module or ADT for longer than just the integration of some function. This can prove useful when it comes to deal with exceptions, or for re-directinginputand output operations. (Incidentally, an interrupt handler is a closely related situation.) In Modula-2 this demand is reflected by the powerful concept of procedural variables (in contrast to parameters), and therefore procedure types. With that, one can both bind the same algorithm to several ADTs and bind different algorithms to the same ADT in turn. In Oberon, finally, cosmetic changes were made to the syntax of procedure types, making up for their growing use. As an example to explore the practical benefits of ADTs and procedure types, we might devisean ADT for the windowsof a simple windowingsystem. We observe that all windowsare essentially orthogonal rectangles, which may or may not overlap each other on screen. As a result of moving or resizing a window, some parts of some of the windowsmay become newly visible.Much like with the integration of functions, the algorithm to determine for which part of which window this applies is independent of the actual contents of the windowsat issue. Hence the functionality to display a windowscontents is an ideal candidate for installing a procedural variable into an ADT. Likewise, if a mouse is moved or one of its buttons has been pressed, or if a key on the keyboard has been pressed, a simple algorithm has to determine, which of the windowson screen is supposed to respond to this event, if any at all, but this functionality is again independent of the nature of the particular window. Therefore,in a first attempt we use a record, such as in Pascal, with some

An Object-Oriented Graphical Toolbox

33

of the record fields actually being procedure types as newly introduced: Window = RECORD x, y, X, Y: INTEGER { considered "read-only" or not exported at all }; displayContents: PROCEDURE (x, y, X, Y: INTEGER); handleMouse PROCEDURE (buttons: Buttons; x, y: INTEGER); : handleKeyboard: PROCEDURE (ch: CHAR); END; The module that implements the windowingsystem manages a collection of such records, together with procedures for opening and closing windows, and for installing the procedure variables. Later on, when we implement a text editor, we can define a procedure such as the one below
PROCEDURE DisplayContents(x, y, X, Y: INTEGER); BEGIN

"displaypart of windowscontents bordered by the rectangle [x, y, X, Y]" END { DisplayContents }; and use the relevant procedure from the windowingsystem to assign it to the window in which the text is being edited. If we do the same e.g. for a font editor, and so forth, we begin to see how the windowingsystem can assert invariant properties, such as to correctly reflect a given collection of windowson screen, even though only part of the recipes to do so are known when the windowing system is implemented. Late binding of the remaining parts of the recipes provides for this sort of independence of the windowingsystem from later additions.

2.1.2 Extensibility& Polymorphism The judicious practitioner probably will not jump to conclusions yet, but first subject the proposed methodology to further aptitude tests. What if we wanted our text- and font-editors to let us edit several texts and fonts at the same time, now that we have windowsto assist us organizingon screen their simultaneous visualization? Likewise, what if we wanted our editors to allow us several views onto the same text or font? For that purpose, the procedure that displays the windowscontents (e.g. the text at issue) would have to know, which text is at issue.

34

The Foundations:

Therefore,in a second attempt we add to the windowsprocedure types a parameter that specifies the window at issue: Window = RECORD x, y, X, Y: INTEGER; displayContents: PROCEDURE (self: Window;x, y, X, Y: INTEGER); END; and in our text-editor,we try the following"extension"of the window: TextWindow = RECORD original: Window { "inherit"all the fields of the original window }; text: POINTER TO Text { assume Text is another ADT }; pos: INTEGER { pos. in text corresponding to windows top-left corner }; END; in hopes that like this
PROCEDURE DisplayContents(self: TextWindow; x, y, X, Y: INTEGER); BEGIN

"displayself.text starting from self.pos and clipping to [x, y, X, Y]" END { DisplayContents }; we can finallylet the display procedure know, which text is at issue. Unfortunately,we are still quite far from it. On the one hand, the windowing system manages windows, but not text-windows (or any other particular window), on the other hand, we would most probably not be allowedto assign the above procedure to the respective field of the window, since its parameter list does not match that of the declaration. As is to be expected,we are not giving up that easily. Rather, we first illuminate two round-about ways that either appear to solve the problem or suggest its implementation by a compiler once we have decided for the ultimate solution. For the former, let us remember Pascals or Modula-2svariant records. If, in the windowingsystem, we declared

An Object-Oriented Graphical Toolbox

35

Specialisation = (none, textWindow, fontWindow, ); Window = RECORD x, y, X, Y: INTEGER; displayContents: PROCEDURE (self: Window;x, y, X, Y: INTEGER); CASE is: Specialisation OF | none: { nix }; | textWindow:text: POINTER TO Text; pos: INTEGER; | fontWindow: ; END; END; then, in the text-editor, we would not have the problem not of being allowed to assign
PROCEDURE DisplayContents(self: Window;x, y, X, Y: INTEGER); BEGIN IF self.is = textWindow THEN { make sure it is the right variant... }

"displayself.text starting from self.pos and clipping to [x, y, X, Y]" END; END { DisplayContents }; to the respectivefield,and we could have multiple views onto multiple texts. The price to be paid for, however, would be that for each specialization to be added to the windowingsystem, we would have to go down to the "basement" and modify the windowingsystem itself. Whatever our programming skills are (of course we would be more than ready to assume them to be above average, wouldntwe?), there is a good deal of truth in the saying: "If it works, dont touch it!" Variant records are neither extensible (in the sense that we could add the specializations without "touching the workingcode") nor safe (in the sense that we cannot inadvertently modify fields that are meaningful only for the font variant, although the particular variant at issue is a text-window). We almost hesitate to present the other round-about way now, but since it is extensible (in the above sense) and since it may give a hint at the actual implementation by a compiler (in hopes that this drives away any doubt regarding its efficiency),here it is. Suppose we declared, in the windowingsystem,

36

The Foundations:

Window = POINTER TO WindowDesc; WindowDesc= RECORD x, y, X, Y: INTEGER; displayContents: PROCEDURE (self: Window;x, y, X, Y: INTEGER); END; then, in our text-editor,we could try the following"extension"of the window, TextWindow = POINTER TO TextWindowDesc; TextWindowDesc = RECORD original: WindowDesc; text: POINTER TO Text; pos: INTEGER; END; which,with the following"hack",
PROCEDURE DisplayContents(self: Window;x, y, X, Y: INTEGER); VAR Self: TextWindow; BEGIN

Self := SYSTEM.VAL(TextWindow, self) { cast type, in hopes that it is... }; "displaySelf.text starting from Self.pos and clipping to [x, y, X, Y]" END { DisplayContents }; would let us both extend the basic windowingsystem later on, and let us bind the procedure variableto the respectivefield. This is the very point at which polymorphismstands in, or extensibility of records such as in Oberon. Polymorphism denotes the ability of variables to hold values of different but, in a well-definedway, related types. Less formally speaking, polymorphism lets us add variants to records much like variant records, but neither having to touch the module implementing the original records, nor having to "distort" the true nature of pointers. Under polymorphism, a specialization is assignment compatible with its more general original by concept, rather than "by hack". Therefore,wheneverthe formal parameter list expects a parameter of type Window, a variable of type TextWindow (or FontWindow)can be supplied as well, but not vice-versa. (In fact, if the assignment it guarded, the other direction is possible as well. Atype guard is a runtime test that asserts that the hidden discriminator field or type tag indicates at least the targeted specialization. At this stage of the introduction to OOP,

An Object-Oriented Graphical Toolbox

37

however, this is not particularly relevant, if not to say, at risk of confusing the novice. Therefore,we conclude our introduction with a couple of notational simplifications, in hopes that they contribute more to the expressivepower of a conventional imperative language than any advanced topic in OOP could for the time being.)

2.1.3 Notation & Terminology We are using our own notation partly because a universallyacknowledgednotation does not seem to be in sight yet, partly because we would like to ensure that the programming examples can be understood easily without the reader having to be familiar with the syntax of a particular hybrid language. In order to emphasize the practical side thereof, we introduce our notation with the above examples. The main purpose of OOP style is to encapsulate both data and algorithms in an ADT. For this purpose we replace Window = POINTER TO WindowDesc; WindowDesc= RECORD x, y, X, Y: INTEGER; displayContents: PROCEDURE (self: Window;x, y, X, Y: INTEGER); END; by Window = CLASS x, y, X, Y: INTEGER; METHOD DisplayContents(x, y, X, Y: INTEGER); END; which is a purely syntactical short-cut with exactly identical semantics. Regarding the extension of the ADT,

38

The Foundations:

TextWindow = POINTER TO TextWindowDesc; TextWindowDesc = RECORD original: WindowDesc; text: POINTER TO Text; pos: INTEGER; END; is replaced by TextWindow = CLASS [original = Window] text: POINTER TO Text; pos: INTEGER; END; which declares the TextWindow variant to be descended from the original Window. The piece of code in charge of displayingthe text-windows ontents, c
PROCEDURE DisplayContents(self: Window;x, y, X, Y: INTEGER); VAR Self: TextWindow; BEGIN

Self := SYSTEM.VAL(TextWindow, self) { in hopes that it is... }; "displaySelf.text starting from Self.pos and clipping to [x, y, X, Y]" END { DisplayContents }; finally,is bound to the TextWindow by formulating it as follows,
METHOD [self: TextWindow] DisplayContents(x, y, X, Y: INTEGER); BEGIN

"displayself.text starting from self.pos and clipping to [x, y, X, Y]" END { DisplayContents }; that is, without havingto assign a procedure variable"by hand". The last notational simplification follows closely,but not exactly, the syntax of Oberon-2stype-bound procedures [MssenbckWirth91]. By requiring the specification of the tuple [self, TextWindow], we let the programmer both specifythe type or class to which the procedure or method is to be bound, and let choose a name by which this method can refer to the particular instance of the type or class. This is the case when the above DisplayContents method accesses self.text or self.pos. The fields text or pos are often called instance variables, since they may, and need to, assume different values for different instan-

An Object-Oriented Graphical Toolbox

39

ces of TextWindow. (This relates to what is called the instance centered view of objects. In contrast, the same DisplayContents method is bound to all instances of the TextWindow class, which is more like what is called the class centered view of objects.) A similar, but not identical, situation arises from the tuple [original, Window] in the example further above. On the one hand, we have to let the programmer specifythe type or class that is to be extended or specialized, on the other hand, it may be that the method of the specialized variant would like to fall back on that of the original. This may be the case if the method of the original does this or that task in a standard way (e.g. display the windows background) and if the variant would like to re-use this piece of code prior to (or at some other point of) applying its particular way to implement said task (e.g. display the text-windowsext on top of the well-preparedbackground). t The class that is extended is usually called the superclass or base class, as opposed to the subclass or derived class, the usual names of the actual extension or variant. Fallingback on the code of the original is often called a supercall, referringto the fact that a call is made to the superclass. Notice,however, that code re-use is not bound to using methods and classes. A conventional call of a procedure from some library serves the same purpose just as well as that. The differenceis that derivedclasses inherit the code (or the behavior) of the base class automatically (that is, without having to bind methods explicitly), unless they define their own methods for realizingthis or that task. Defining ones own method for realizinga particular task is often called overwriting the method at issue. Finally,if no code is bound to a method of the base class, said method is called an abstract method, in contrast to a concrete method that contains code to be bound. Abstract methods merely serve as a means of defining (the signature of) a class. We are aware of the fact that the preceding introduction to the concepts of OOP was much of a crash course, partly because anything more than that would be beyond the scope of this thesis, partly because we would not want to presume being able to give a comprehensive course. We are convinced, however, that understanding classes to be modeling interchangeable and extensible building blocks of "data-plus-algorithms"will be more than sufficient.In the end, much of the difficulty lies in the question, what are these building blocks, but much less how the building blocks are implemented. (The reader may find a couple of implementational issues regardingmeta-programmingor delayed class instantiation in 5.2.3, making the OOP approach really extensible, while 5.3 discusses topics concerning the design of (future) hybrid languages. For the time being, it shall suffice to say that the building blocks of a graphics libraryare the subject of the next section.)

40

The Foundations:

2.2 Structuring a graphics library into interchangeable components 2.2.0 Graphical objects In a conventional graphics library we might find routines similar to the one belowfor outlining a circle:
PROCEDURE DrawCircle (xc, yc, r: INTEGER); VAR x, y: INTEGER; BEGIN

"use xc, yc, and r to determine initial values for x and y" "draw initial pixel at (x, y)" WHILE "not entire circle drawn" DO "use xc, yc, and r to determine next values for x and y" "draw next pixel at (x, y)" END; END { DrawCircle }; The recipes to determine the values for x and y are different for lines, polygons, circles, or more general curves (cf. A.0). However, their common ground is to calculate an "initial pixel", followedby a sequence of zero or more "next pixels". This predetermines the interface between barely digitizing graphical objects and concretelyrendering them, regardless of the underlying model for coordinates and pixels. Let us therefore use the conclusions drawn in 2.0.5sq by refining the above algorithm as follows. Rather than calculating "first"and "next"pixels, we compute the initial and any subsequent vertices of a rectilinear region. Particularly, we understand the circlesradius, together with its center point, to be the instance variables of a class, one of whose methods calculates the vertices of the rectilinear region. With that, all graphical objects can start out from the same base class, GraphicalObject = CLASS METHOD Digitize(); END; while a particular graphical object, such as the above circle, extends the base class with the followingderived class,

An Object-Oriented Graphical Toolbox

41

Circle= CLASS [super = GraphicalObject] xc, yc, r: INTEGER; END; replacing the abstract method of the base class with the concrete one below,
METHOD [self: Circle]Digitize(); VAR x, y: INTEGER; BEGIN

"use self.xc, self.yc, and self.r to determine initial values for x and y" "initial vertex of rectilinear region := (x, y)" WHILE "not entire circle drawn" DO "use self.xc, self.yc, and self.r to determine next values for x and y" "nextvertex of rectilinear region := (x, y)" END; END { Digitize }; and likewise for all other graphical objects. In this first refinement, it is up to the method of the particular derived class to produce concrete initial and next values for x and y, and likewise to determine the termination of the iteration. Since this will be discussed in full detail in A.0, we have not expanded the respective part of the pseudo-code any further. Conversely, the task to consume initial and next vertices is the very point at which the bare calculation of vertices is separated from concretely rendering them as outlined or filled objects. This refinement is introduced in the next sub-section.

2.2.1 Rendering tools Accordingto the spirit of program development by step-wise refinement, let us now look into the details of consuming the verticesof the rectilinear region as produced by the digitizemethod. We first recall the implementational decision that the "Manhattan moves" shall be restricted to unit steps, and we add that the orientation of closed regions shall be positive, i.e. orbiting interior pixelscounter-clockwise.These two decisions may be regarded as restrictions, but they constitute, so to speak, the greatest common denominator of combining outlining and fillingwith lines and curves. For the purpose of outlining, we illustrate in A.1.1 that, for an interior pixel, to be immediately adjacent to the pixel boundary is equivalent to being right

42

The Foundations:

on the left of the unit step on the positivelyoriented pixel boundary. Likewise, for the purpose of filling,we show in A.1.2sq that fillingalgorithms eventually resolvethe region to be filled into orthogonal rectangles of unit height, that is, rectangles that are bordered on the left and on the right by a verticalunit step.

the rectilinear region defining the shape of a circle (middle), together with the partially outlined (left) and filled (right) region. For both kinds of algorithms, we need to know not only the next values for x and y, as supplied by the digitizingmethod, but as well the previous pair of values, such as to know what means "on the left of the unit step" or what is a "verticalunit step". Furthermore, the fillingalgorithm will manage some form of fillingtable, possibly with an index that specifies the next slot to be filled,or similar. Although we do not expect these hints about the actual algorithms to be anywhere near clear to the reader yet, what should have become apparent by now is the fact that the process of rendering is an ideal case for a class. There are both data to be encapsulated, and there are collections of algorithms that process this data. Both the data and the algorithms will be different for the fillingor the outlining (or other rendering styles),but their functionality is the same. We propose the followingbase class for the resulting rendering tool: RenderingTool= CLASS METHOD Setup () { initialize instance variables }; METHOD JumpTo (x, y: INTEGER) { consume initial (x, y)-pair }; METHOD WalkTo (x, y: INTEGER) { consume next (x, y)-pair }; METHOD Flush () { finalize instance variables }; END; from which we can derive a class (cf. A.1.1 for further details) for outlining pixel boundaries, with methods JumpTo and WalkTo as below,

An Object-Oriented Graphical Toolbox

43

Quill = CLASS [super = RenderingTool] x, y: INTEGER { last coordinate pair digitized }; END;
METHOD [self: Quill] JumpTo (x, y: INTEGER); BEGIN

self.x, self.y := x, y; END { JumpTo };


METHOD [self: Quill] WalkTo (x, y: INTEGER); BEGIN

"draw pixel located on left side of unit step from (self.x, self.y) to (x, y)" self.x, self.y := x, y; END { WalkTo }; or another class (cf. A.1.2 for further details) InkSpot = CLASS [super = RenderingTool] y: INTEGER { last y-coordinate digitized }; OF Y: ARRAY INTEGER { information leap }; END; for fillingsimply connected convex rectilinear regions, with concrete methods as well for Setup and Flush. Now that the rendering of rectilinear regions is encapsulated in a class, we can pass instances of this class as a parameter to the digitizingmethod (compare this with the last examplein the previous sub-section),
METHOD [self: Circle]Digitize(with: RenderingTool); VAR x, y: INTEGER; BEGIN

"use self.xc, self.yc, and self.r to determine initial values for x and y" with.JumpTo(x, y); WHILE "not entire circle drawn" DO "use self.xc, self.yc, and self.r to determine next values for x and y" with.WalkTo(x, y); END; END { Digitize }; and, with the legacy of polymorphism, instances of any class that has been de-

44

The Foundations:

rived from the basic rendering tool! The rendering style has become an interchangeable parameter of the bare digitizing: NEW(circle); circle.xc, circle.yc, circle.r := , , ; NEW(quill); quill.Setup(); circle.Digitize(quill); quill.Flush(); NEW(inkDrop); inkDrop.Setup(); circle.Digitize(inkDrop); inkDrop.Flush(); The above short exampleproduces, in turn, an outlined circle and a filledone, with one and the same digitizingmethod. With the proposed methods for the rendering tool, the digitizingmethod can invokethe consumption of the vertices of the rectilinear regions it is producing without having to be concerned, what is actually hidden behind these methods. Although the previous two examples have shown the use of the methods JumpTo, WalkTo, Setup, and Flush, their exact role may not have become clear yet. Particularly,the need of a JumpTo method in addition to the Setup method is not obvious, since all that the Quills JumpTo method seems to be doing is to initialize the Quills instance variables (for details regarding this and other rendering tools, we encourage the interested reader to consult A.1). The reason for the latter is to allow to fill closed graphical objects that are concatenated out of several open ones simply by requesting all of them to digitizethemselves in turn. To do so, the fillingtable (or whateverthe required information leap should be, cf. A.1) is setup before the first of the constituent parts is requested to digitize itself, and flushed after the last one. In-between, each of the constituent parts can determine on its own, to which coordinate pair it has to jump before the first unit step is "walked" simply by invokingthe respective methods without having to be concerned, what these methods actually consist of, if anything at all: NEW(line); NEW(curve); NEW(inkDrop); inkDrop.Setup(); , , , ; line.x0, line.y0, line.x1, line.y1 := line.Digitize(inkDrop); , , , , , ; curve.x0, curve.y0, curve.x3, curve.y3 := curve.Digitize(inkDrop); inkDrop.Flush(); The above example fills a graphical object that is concatenated out of lines and curves (cf. 5.0.2). Notice finally that, taking earlier graphics interfaces as an example (e.g. Turtlegraphics in [Apple80]), we sometimes call the rendering tool a turtle, as we did alreadyin [Stamm92b].

An Object-Oriented Graphical Toolbox

45

2.2.2 Raster devices The last refinement regards the details of drawingpixels or fillingsmall orthogonal rectangles on raster devices.We understand a raster deviceto be a (possibly concrete physical) devicethat implements a finite part of the coordinate grid introduced in 2.0.5. This understanding is quite general, for it includes not only all kinds of screens and printers, but as well shadow bitmaps or even the windows on screen. The idea is to reduce these varieties of devices to a sheet of squared or graph paper. Turning on a pixel then means to conceptually fill in one of the little squares or checks, in accordance with the checkerboard model. Whileintelligibleand desirable, the actual realization of this concept is not as straightforwardas it may look. The videomemory of our display devicemay be organized in bit planes or in memory banks, or the video sub-system may not even be memory-mapped at all, but controlled by a graphical co-processor [Wilton87]. Similar idiosyncrasies exist with other raster devices, such as the ways to access matrix or laser printers. Yet, our rendering tools simply want to draw pixelsand filllittle rectangles on all of these devicesinvariably. What emerges for the third time is again what looks like an ideal case for encapsulating ingredients and recipes in a class raster device: RasterDevice= CLASS resolution: INTEGER { dots-per-inch, read-only }; width, height: INTEGER { pixels, read-only }; METHOD Fill (into: Area; this: Pattern; in: Mode); METHOD Copy (into: Area; this: RasterDevice;from: Area; in: Mode); END; With the above basic deviceclass we propose the minimal standards to which all derived classes should be able to adhere. By experience,it has proved to be an optimal balance between flexibility and complexity,at least as long as we stayed in the black-and-whiteenvironment of the institutes equipment [Eberle87, Heeb88, HeebNoack91 Details of particular interest are set out next. ]. The Area class encapsulates the lower left and upper right corner of an orthogonal rectangle, together with elementary operations thereon, such as the union (of two bounding boxes) or the intersection (for clipping one area against another). The Pattern class manages a small block of pixels which is conceptually tiled on the area to be filled,rather than fillingall the checks uniformly.This can simulate intermediate gray-levels on black-and-whitedevices or produce different hatchings as a means of distinction. Finally, the Mode

46

The Foundations:

parameter further specifies the way in which the checks are filled in. We have used the modes replace, paint, erase, mask, and invert which implement, in turn, the assignment, the logical OR, the asymmetrical difference, the logical AND, and the symmetrical difference (in terms of sets of pixels). Notice the generality of the Copy method. On the one hand, we allow the source and target areas to be different.This comprises the possibility to move an area of pixels from one place to another. Furthermore, it allowsto blow up rastered images, in particular by an integral factor (e.g. to examine rastered results more closely),and in general by a rational factor (e.g. to view images at printer resolution from within a document editor - at screen resolution). On the other hand, we allow the copies to be made from any raster device(derived from the base class) to any other raster device (again derived from the base class). This results in a dyadic copy operation which the present base class expects to be encapsulated in one of the operands only, even though the polymorphic operands are not necessarily of the same type. Although this opens the door to an area of problems which, to the best of our knowledge,objectoriented programming usually cannot solve satisfactorily, we are pleased to refer the reader to A.2.1 for a general implementation of the dyadic polymorphic copy operation. Now that all kinds of concrete raster devices can be encapsulated in classes, we can complete the introduction of our object-oriented graphical toolbox. In this last step, we turn (a reference to) the raster device into an instance variableof the rendering tool, to be setup by the Setup method, RenderingTool= CLASS on: RasterDevice{ read-only }; METHOD Setup (on: RasterDevice){ initialize instance variables }; { the remaining methods remain unchanged } END; and thanks to polymorphism, this can be any class that has been derived from the basic raster deviceclass. In doing so, we offerthe concrete rendering methods the full functionality of any raster device and at the same time encourage the rendering tool to do the "walking" and the "flushing" on the same raster device. The raster device, too, has become an interchangeable parameter of the bare rendering. The short example below produces an outlined circle, in turn, on the screen and on the printer. As far as device independence is concerned (the printers resolution will most probably not be the same as the screens) we refer the reader to the next sub-section.

An Object-Oriented Graphical Toolbox

47

NEW(circle); circle.xc, circle.yc, circle.r := , , ; NEW(screen) { plus whatever else it takes to initialize the screen }; NEW(printer) { same for printer }; NEW(quill); quill.Setup(screen); circle.Digitize(quill); quill.Flush(); quill.Setup(printer); circle.Digitize(quill); quill.Flush();

2.2.3 Levels of abstraction In the preceding sub-section we have successfully encapsulated most of the technical intricacies of all these raster devices. Subsequently, the rendering tools do not have to be concerned anymore how the pixels get onto the device at all. We call this level of device independencethe technical independence To . achieve a higher level of device independence, we have to take the devices resolution into account. Depending on this resolution, the concern is then which pixelsare supposed to get onto the device. We call this level of independence the logical independence . In our toolbox, this higher level of independence is split into two areas of responsibility. They are covered by the rendering tools and the graphical objects. Togetherwith the raster devices,this can be visualizedas follows: application program: real world model space

graphical object (scan-conversion): model space pixel boundaries

48

The Foundations:

rendering tool (outlining, filling): pixel boundaries pixel space

raster device: pixel space visible dots As we can see from the above illustration, to achieve logical independence is merely a matter of the graphical objects to determine the pixel boundaries as a function of the resolution. To do so, we pass as a parameter to the graphical objects digitize method the scaling factor that results from the ratio between the resolutions of model and devicespace. In order to cater for differingresolutions in x- and y-direction,we may wish to pass two scaling factors, or generalize the scaling factor to an affine mapping in terms of homogeneous coordinates. This generalization is quite inexpensiveand has some cunning applications in our outline-font editor (e.g. affinedragging,cf. 3.1.2). By way of recapitulation, we now repeat the final versions of the class interfaces coherently: GraphicalObject = CLASS METHOD Digitize(under: Mapping; with: RenderingTool); { plus methods for whatever represents functionalitycommon to all graphical objects, but with individual implementation, such as the objects bounding box, or for generic loading and storing, cf. 5.2.3 } END; RenderingTool= CLASS on: RasterDevice{ read-only }; METHOD Setup (on: RasterDevice){ initialize instance variables }; METHOD JumpTo (x, y: INTEGER) { consume initial (x, y)-pair }; METHOD WalkTo (x, y: INTEGER) { consume next (x, y)-pair }; METHOD Flush () { finalize instance variables }; END;

An Object-Oriented Graphical Toolbox

49

RasterDevice= CLASS resolution: INTEGER { dots-per-inch, read-only }; width, height: INTEGER { pixels, read-only }; METHOD Fill (into: Area; this: Pattern; in: Mode); METHOD Copy (into: Area; this: RasterDevice;from: Area; in: Mode); END; The graphical objects Digitize method scan-converts the continuous shape that it encapsulates, such as a line or a curve, into a pixel boundary or a sequence of "Manhattan moves", under subordination to an affine mapping. It feeds the pixel boundary into the rendering tools JumpTo and mainly WalkTo method; unit step by unit step (cf. A.0). The rendering tool decides which pixels are involvedfor outlining or filling,depending on the rendering style that it encapsulates. For the outlining tool, the method in charge is the WalkTo method (cf. A.1.1), while for the filling tool, this may be the WalkTo or the Flush method (information leap, cf. A.1.2 and A.1.3). Accordingly, the methods send single pixelsor entire pixel spans to the raster devicesSet and Fill methods. The raster device knows how to draw pixels and small orthogonal rectangles thereof on the screen or the printer, depending on the actual device it encapsulates. As a final remark we point out that, having structured our graphical toolbox into polymorphic factors, we get 3-way extensibility for free now. Derived classes for graphical objects, rendering tools, and raster devices are all the three fully interchangeable within their genealogicaltrees. With that, we have created a graphical toolbox featuring a functionality that corresponds to the Cartesian product graphical object (circle,polygon, rendering tool (outliner, filler, ) ) raster device (screen, printer, ) but for which the price to be paid for programming time, code size, and maintenance cost equals the sum of the prices of the individual factors only!

2.3 Assessment of the new approach for a graphical toolbox In view of the present thesis being targeted at scaling fonts, the critical reader may have started to wonder why at all we are putting that much emphasis on a particular way to define and implement the graphical prerequisites for fontscaling. It is clear that concepts or models should not be mixed unconscious-

50

The Foundations:

ly, or else we should be prepared to unpleasant surprises. Running into "offby-one"errors therefore may be dismissed somewhat condescendingly using uncomplimentary and generalizingcomments about "careless programming style" and similar. Such objections are certainly not entirely unfounded, but we are convinced that with our approach we take a few steps beyond. By workingon the pixelsboundaries, rather than on the pixelsthemselves, we get the followingthree points for free: Assemblinga polygonout of a sequence of straight lines, we do not have to face the "end-point-paranoia" any longer. Since the "end-points" are not "displayable" entities anymore, but the start- or end-coordinates of pixel boundaries, there is no chance to "invert" such "end-points" twice, hence there are no more missing vertices. Likewise, workingon pixel boundaries, there is always an even number of "Manhattan moves" between adjacent pairs of x- or y-coordinates, hence the parity can never be odd. Even moving up and down along "the same avenue",there are two moves that link "parallel streets". Finally, the bounding boxes are put down to those of pixel boundaries. Since pixel boundaries are purely mathematical entities without physical extension in two dimensions, and since the same pixel boundary applies to both a filled and an outlined object now, the bounding boxes are not only correct, but the graphical objects furthermore do not appear to "shrink" upon switchingthe viewing attributes from outlined to solid. There is no dedicated piece of code necessary to take care of the above singularities - not even a single if-statement! By wrapping the respective routines into an object-oriented interface, we get further benefits: The graphical objects being extensible, it is easy to add new graphical primitives to an existing system. Particularly, we can define a cluster object that realizes an arbitrary concatenation of lines and curves without having to re-implement any of the digitizing algorithms for the lines and curves, nor any of the outlining or fillingalgorithms (cf. 5.0.2). Besides, new primitives may realizethe digitizingin a non-standardway (cf. 4.5.2sq). The rendering tools being extensible,it is easy to supply different rendering styles as well. A simple "rendering" style just records all the "Manhattan moves" without rendering them at all (cf. A.1.0), which serves to assemble characters out of pre-fabricated components (cf. 4.3.0), and which would not be easy without strictly separating the rendering from the digitizing. Besides, alternative algorithms for outlining or filling(cf. 4.5.1) can be added to an existingsystem without any hitch. The raster devices being extensible, it is easy to introduce further devices.

An Object-Oriented Graphical Toolbox

51

Unlike conventional device drivers, this can happen at run-time, without having to "re-boot"the computer. Intelligent devicedrivers could encapsulate appropriate measures to compensate for the actual pixel shapes not being perfect squares as assumed in our model (cf. 2.0.5). Particularly,the difference between black-writingand white-writinglaser engines may be stupefyingto the rest of us (cf. [MacKay91]). While the rules of the engineering profession would suggest not to duplicate the code for digitizingor rendering, and therefore justifies the chosen factorization, by dint of polymorphism we are not deterred now from putting in further extensions - we can do so without touching the basic toolbox and without havingto fillin the missing factors of the Cartesian product (cf. 2.2.3). To recapitulate, working with pixel boundaries lets us compose complex graphical objects out of simpler ones and both outline and fill them without "off-by-one" errors or erroneous bounding boxes. Implementing the graphics library in extensible OOP style turns these wonderful theoretical considerations more or less one by one into practical solutions. Notice, therefore, that extensible OOP style is not primarily a means of straightening a bad software architecture, but for easing the implementation of a good one! In retrospective, it was a matter of "constructive laziness", so to speak, to understand the architecture of our graphics library to be separating the bare digitizingfrom the actual rendering "along the pixel boundaries", and to subsequently apply to the original graphics librarya little "cut-and-paste"and add a little "syntactic sugar", in order to turn a conventional graphics libraryinto a genuine construction kit for two-dimensional graphics. But even without this outstanding result, it is worthwhilespending quite some time and effort on the graphical foundations, for without taking care of the knowledge,where the pixels come from, we should not expect the characters to look after themselves.

3 The Input: ASimpleOutline-FontEditor


3.0 Knots 3.0.0 The entity of interaction In an outline-font editor we define and manipulate the shapes of characters by way of outlines. An outline is defined by a sequence of knots (support- or control-points, on- or off-curve points) and a contour class. Depending on the contour class (line, circle, polygon, natural spline, Bzier curve, this se), quence of knots is mapped unambiguously onto the geometryof the contour class.

various outlines defined by a sequence of knots From that point on, the digitizer of the graphical object at issue resolves the continuous geometrical shape into a sequence of integral coordinate pairs. These coordinate pairs are serialized into a rendering tool, which in turn determines the pixelsinvolvedto render the rectilinear region (cf. 2.2). The pixels which make up for the resulting image may be understood to depend on the knots by (possibly quite intricate) a function. In contrast to painting programs, however, we do not have to place the pixels individually, one by one. Rather, the computer frees us from the burden to do so by dint of precise mathematical laws. The entity of interactionremaining available to us is the knot. With a few knots, compared to a large number of pixels, we determine completely,what the shape should be. This very idea can be modeled easily in an outline-font editor. What the various outline classes have in common is a list of knots, a contour class, and

A Simple Outline-Font Editor

53

a method mapping the knots onto the geometry: Outline = CLASS knots: LIST OF Knot; contour: GraphicalObject; METHOD MapKnotsToContour(); END; With that, the manipulation of outlines has been reduced to the manipulation of knots, both as far as the concept and the implementation are concerned. The only thing which varies from contour to contour is the mapping method. Subsequently, any editing function operates invariablyon lists of knots, being burdened with neither the particular ways to specifythe geometryof the contours at issue, nor their implementation.

3.0.1 Different ways of marking Eventually, those parts of the outlines which are subject to subsequent cut and paste operations, have to be marked. Much like with modern text editors, we expectthe possibility to mark individual knots (instead of the characters of a text), entire contours (as opposed to words), or even the whole figure (in contrast to paragraphs). With text editors, the sequence of attributed characters (font and verticaloffset,or line spacing and formatting mode, etc.) implies an unambiguous mapping of a pair of coordinates to a logical position in the text, and vice-versa (unless the kerning yields negativeword gaps, cf. 4.6). The seeminglytwo-dimensional nature of a text editor is reducible to a purely onedimensional problem. With a graphics editor, this is not true anymore. Therefore,we implemented two different ways of marking knots and outlines. The one way to mark knots is called the specific marking. Thereby, the knot is pointed at with the mouse cursor, the respective button of the mouse is pressed, and the knot is displayed in marked state to provide optical feedback. Analogously,if the knot is pointed at with a so-calleddouble-clickof the mouse button, all knots of the same outline are displayed in marked state, that is, the entire outline is marked. With a triple-click,finally,we might wish to mark the wholefigure. The other way to mark knots is called the area marking. Thereby, an orthogonal rectangle is spanned while moving the mouse with the respective button pressed. As soon as the mouse button is released, those knots are marked which lie in the area defined by the rectangle. This delayof the feed-backis ne-

54

The Input:

cessary if we want to dispense with neither cumulative marking (cf. 3.0.2) nor the possibility to shrink the area whilebeing defined (wherebypreviouslymarked knots would have to be unmarked for correct feed-back).However, while being defined, the area is displayed with a gray pattern to provide temporary feed-back.

temporary and final feed-back of the area marking The area definition can be initiated with a double-click as well. Analogous to the specific marking, this results in marking entire contours if all their knots lie in the area spanned. Knots at the start or the end of connecting contours are not marked. Temporary feed-back is provided by a more intensive gray pattern. Finally,starting the area marking with a triple-clickcan be dispensed with without loss of generality. To see the advantages of both ways of marking, and to justify their existence, it is recommended actually to work with the outline-font editor. While the area marking is fast and convenient for marking larger numbers of knots, the rectangle spanned may comprise knots which are not supposed to be marked. Therefore,a specific marking may help. With text editors, in contrast, the area marked is but a (one-dimensional) range, hence a correct range cannot give raise to inadvertentlymarked characters.

3.0.2 Cumulative marking and ad-hoc constraints Specific marking and area marking are not enough to mark sets of knots which neither can be delimited by a single orthogonal rectangle, nor can be seized by a single specific marking. Especiallyif such parts of figures are subject to dragging(cf. 3.1.1), it is preferable not to have to dislocate independent parts separately.Rather, we would like to move these parts to the new location in one go. To be able to do so, subsequent markings are treated cumulatively.

A Simple Outline-Font Editor

55

Newly marked sets of knots are included in the set of knots which have been marked already,unless the figurehas been neutralized meanwhile. On the one hand, this type of behavior is maybe the one which demands the most re-adaptation, when compared to text editors, since it emphasizes the modal role of marked knots: Marking a knot, it is overlooked easily that another one (at the far end of the figure) may be marked as well, causing subsequent operations to do more than what may have been expected.It shall not be withheld, though, that similarly unexpected reactions can be caused with the text editor in the Oberon System as well (cf. 3.1.7). On the other hand, this behavior actually provides a means of defining ad-hoc constraints. In commercial drawing programs, the possibility to group and un-group loose parts can be found. While this may be helpful for technical illustrations, for creativedesign it is more of a hindrance than a help. With cumulative marking we can build groups ad-hoc without having to go through the somewhat cumbersome explicitgrouping. Togetherwith (affine) dragging (cf. 3.1.1sq) this constitutes the constrained motion of the marked knots.

3.0.3 Marking beyond two dimensions In the preceding sections we have seen that two-dimensional graphics entails different concepts and ways of marking knots and outlines. The following subtlety shows that these are not yet enough: Aparticular outline ends usually at the same point at which a connecting outline starts, and vice versa. The respective knots are located at one and the same coordinate pair. However, the outlines and knots being organized in lists, the editors response to a specific marking of a single knot will be to mark one of the two only. As a result, the one which we can get hold of seems to be "at the top", coveringthe other one "at the bottom", much like with overlapping windows. The covered knot can be reached independentlyneither by an area marking. The area marking would simply include either both knots or neither of them. This shows clearly that two-dimensional graphics enters the third dimension, even though for an infinitely small distance only. As a consequence, the specific marking contains two distinct strategies to diffuse this problem. The obvious strategy is to place the most recently marked outline always at the end of the list of outlines, before displaying the entire figure. This is a cheap but most effective list operation: If the entire figure is displayed by drawingone outline on top of the other, a marked outline will be displayed last, with all its knots visibly marked - an intuitive response of the editor. It is easy to see that this holds true also if several outlines are marked

56

The Input:

cumulatively,for the resulting list of outlines will be ordered partially. The other strategy refines the previous one for cases where outlines are made of two knots only.Both knots thus connect to other outlines, leavingus no chance to seize the outline if it happens to be "at the bottom" - unless we want to move away temporarily the other outlines. Therefore,outlines which already are "at the top" are moved "at the bottom" upon a second attempt to grasp the same starting or ending knot. Again, it is easy to see that this works as well if several outlines start or end at the same point. With this strategy, they can be reached cyclically. To recapitulate we repeat that the contour classes have been decoupled from the outlines in 3.0.0. Therefore,all of them can profit invariablyfrom the different ways and strategies of marking introduced meanwhile.Beinga bit extravagant with that respect is considered thus to be perfectly tenable. At the same time it does not deter novice users from using the program altogether, since it is not absolutely necessary to know all the different ways of marking. At the expense of a few extra manipulations, most goals can be achieved by followingthe line of least learning.

3.1 Steps beyond ordinary cut & paste 3.1.0 Ordinary cut & paste Now that the entity of interaction is defined, we can turn to operations thereon. Analogousto the text editor in the Oberon System [Reiser91], in our outline-font editor the marked parts can be deleted (cut), copied to a new location (paste), or "copied-over"from an old location. To do so in the text editor, we define a focus point that specifies the destination of the insert (copy, paste) operation. Likewise, in our outline-font editor we can define a crosshair that symbolizes the target of the insert operation. However, even though these ordinary cut and paste operations are indispensable, they are not really that much of a thrill until the role of the focus is extended (cf. 3.1.2).

3.1.1 Dragging With text editors, the location of an individual character (i.e. the entity of interaction) is fully defined by its relative position in the text (cf. 3.0.1). The characters side-bearings (cf. 4.6.1) and the line-spacingfree us from having to place the characters more individually.With graphics editors, the exact oppo-

A Simple Outline-Font Editor

57

site is the case. Often, the location of individual knots and contours needs to be changed, while the relative position of a contour in (the list of contours making up for) the character is irrelevant(but cf. 3.0.3). The frequent need to cut a part of the figure, in order to paste it right next to it, is combined in a single concept called dragging. While the respective button is pressed, the marked knots or contours follow immediately every single movement of the mouse. This particular behavior is known as the direct manipulation metaphor.

area marking of individual knots...

...followed by dragging We are convinced that a serious font editor does not constitute an advantage over paper and pencil until it enables us to manipulate the entities as immediately as possible. Furthermore, we would understand a less immediate response of the editor to be modal (cf. 3.1.7): We cannot see that we are in draggingmode until it may be too late.

58

The Input:

area marking of entire contours...

...followed by dragging Sometimes the marked knots or contours have to be dragged by a very small distance only, which may be hard to achieve with the mouse. Even worse, eventuallythe mouse reacts to the releasing of a button with a slight motion into the opposite direction, as a result of the conservation of momentum. To defuse this problem we have implemented the use of the arrow keys of the keyboardas an alternative to using the mouse. With the arrowkeys we can dislocate the marked knots and contours along one of the four points of the compass in units of the underlying raster device. Notice that, in order not to restrict freedom of artistic expression, our type-designer Hans Ed. Meier would object to have the motion of the mouse constrained to a grid which is coarser than the one imposed by the resolution of the underlyingraster device itself.

A Simple Outline-Font Editor

59

3.1.2 Inserting & dragging with extended foci Modern text editors assist us to place the focus at the start or end of a word, or even at the start of the entire paragraph. To do so, one may have to quickly press the respective mouse button twice (double-click), or point the mouse at the margin of the paragraph at issue. Whateverthe exact manipulation should be, it extends the role of the focus. Analogous to such text-editors, in our outline-font editor we have implemented the possibility to designate not only a simple focus, but also the center of a mirroring and the center of a scaling. The latter two can be extended further into an axis of mirroring or scaling respectively.Combined with inserting and dragging,this allowsfor a wholerange of advanced operations. To see this, we repeat that by now we have explained how to designate in different ways both the source and the destinationof an insert operation. But if we can copy the marked parts of a figureto some new location (i.e. under subordination to a translation), then we can insert as well a copy which has been subjected to a more general affine transformation such as a mirroring at a , point or at a straight line.

extended foci specify an insertion under subordinationto an affine transformation: mirroring at a straight line assists designing a Times "O" or... To implement affine copying, we simply replace the translation performed upon insertion by an explicitand more general mapping, according to the extended focus. Mirroringe.g. a point (x, y) at a straight line from (x0, y0) to (x1, y1), the mapping is: x x + y + ( - )x0 - y0 y x - y - x0 + ( + )y0 with x, y := x1 - x0, y1 - y0, and , , := x2 + y2, x2 - y2, 2xy.

60

The Input:

...mirroring at a point assists designing a Times "q" out of a "b"; its bottom serif can be replaced using ordinary cut & paste Analogouslyto copying, if not even with less effort, we can drag the marked parts under subordination to an affine transformation. The point to set about for the generalization is the same: If we can move the marked parts of a figure to some new location (i.e. drag under subordination to a translation), then we can rotate them around a point (like using a pair of compasses) or glide them along a straight line (as if we used a ruler), too.

extended foci specify dragging under subordinationto an affine transformation: moving along a straight line assists positioning glyphs of italic fonts To implement affine dragging, we simply pass the appropriate mapping to the digitizerof the respective parts of the figure.Upon dragging,this mapping depends on both the extended focus and the current location of the mouse (relative to the point where it started). Projecting e.g. the motion of the mouse from (xi, yi) to (xf, yf) to a straight line from (x0, y0) to (x1, y1), the mapping for an individual point (x, y) is:

A Simple Outline-Font Editor

61

x x + x2dx + xydy y y + xydx + y2dy

with dx, dy := xf - xi, yf - yi, and x, y, := x1 - x0, y1 - y0, x2 + y2.

Much like defining the center of a rotation, an extended focus can define the center of a scaling operation, too, even if the scaling is to be restricted to an arbitrary direction. Once the generalized dragging has been completed, the mapping is applied to the marked knots in order to make permanent the temporary manipulation. Notice that although the above equations may look somewhat costly,they contain integers only,and the matrix elements remain the same for all points to be mirrored or projected. One of the above cases of constrained draggingis used often enough that it deserves special treatment justifiably. The matter at issue is the motion along a straight line that runs in parallel to one of the coordinate axes. Much like with dragging by very short distances, we have implemented a short-cut for this case. The draggingbeing done with the middle mouse button, we can issue an interclick with either the left or the right mouse button for constraining the motion to the verticalor the horizontal direction respectively.The reader is referred to 3.1.7 as to what enhancement this constitutes when compared with the softwareto be replaced. A manipulation that may ease the design of italicized characters is the shearing. Althougha particular typefacemay not be suited for automatic italicizing,the result of shearing (whiledragging)may be a basis for the design of the italic variant.

shearing a Times "b" (left) may be a basis (middle) to design its italic variant (right) Characters are sheared by combining a rotatory draggingwith a horizontal (or vertical)motion constraint, to be issued by the same interclick as above.

62

The Input:

3.1.3 Canceling Just as with the text editors in the Oberon System [Reiser91], a typical insert or delete operation marks the knots and, before releasing the respective mouse button, issues an appropriate interclick. This allows for a simple way to cancel the operation, before it is too late, simply by pressing all the three mouse buttons simultaneously. A more sophisticated mechanism for undoing operations issued by mistake was dispensed with. The complete outline-font being brought into memory, and on top of that, the individual characters being copied for subsequent editing, one can always make use of this two-level cache to store ones labor in the desired "level of permanence". As far as the dragging is concerned, canceling does not entail complications. While dragging,the mapping passed to the marked parts differs from the one passed to the non-marked parts, as explained above. Like this we can always see immediately, what the marked parts are going to look like after the manipulation, without changing these parts. Thus, if the generalizeddragging is canceled, no special precautions are needed to undo what the draggingmay have caused already.Eventually,we have come to appreciate the possibility to cancel the draggingso much that we decided to put similar functionality into our windowingsystem. There, we can cancel the moving,resizing,or scrolling of any window now, from which in turn the outline-font editor profits as well. If dragging is not canceled, the temporary manipulation is made permanent by applying once only a single resultant mapping to the marked knots. This conceals an important subtlety: If individual scalings or shearings were applied to the marked parts, one after the other, we would be at risk of not obtaining the same result as when applying an equivalent single mapping, since the knots are lyingon an integer grid, while the mappings are rational or real. The willingreader may consider the implementational consequences of using a somewhat more light-weighteddraggingmechanism, not adhering to the direct manipulation metaphor.

3.1.4 Split & merge The truly two-dimensional nature of a font editor gives raise to functions not present in text editors. These functions relate to geometrical topics, such as the advanced inserting and dragging operations introduced in 3.1.2, or they address structural issues, like the ad-hoc constraints we have met in 3.0.2. The possibility to merge two contours of the same class, or to split a contour into two parts, belongs to the same categoryof functions.

A Simple Outline-Font Editor

63

To split a contour, the focus designates the knot at which the contour is to be broken in two parts. The targeted contour is interrupted at the pressing of a button which is available from a menu. To merge two contours, the ending and starting knots of the two parts are brought on top of one another. This spot is designated with the focus, and the two parts are spot-welded at the pressing of another button. Splitting and merging complete the set of manipulations availableon contours. They relieveus of carryingout equivalent functions using a large number of more elementary operations. With the rather simple user interface we avoid the need to un-group (until we can manipulate the parts at all) and (re-) group loose parts (after due manipulation), a somewhat cumbersome procedure found in competing approaches.

3.1.5 Contour orientation & seed-points It is undeniable that a serious font-designsystem allowsto display characters both outlined and filled. Filling characters requires to know, what is the interior of characters. Starting out from the outlines, this may or may not be obvious, since the outlines do not always separate the "inside" from the "outside":

all the regions marked with " " are "inside" To make clear what is the interior, the Medium Resolution Font-Design System [Kohen88] required the explicit specification of so-called seed-points ( ) for filling.Starting out from these seed-points, the respective region filling algorithm traced the contours, which previouslyhad to be digitized and stored in an array of linked lists, to eventuallydo the actual filling. Such a filling algorithm is not only slow and complex, but it furthermore

64

The Input:

bears a subtlety that may escape the layperson easily. The seed-points are intended to make a qualitative statement, distinguishing the interior from the exteriorof characters, while their positions within the characters are quantitative in nature. The subtlety now is to assert that the scaled seed-points refer to the interior of the scaled characters, regardless of any round-offs introduced by grid-fittingor by the particular digitizingalgorithms at issue (cf. Chapter 4). Fortunately, such a fillingalgorithm can be superseded by one that gets by without (quantitative) seed-points, but instead relies on the orientationof one or more closed contours, which is qualitativein nature to begin with. Essentially, a positivelyoriented closed contour (running counter-clockwise)encloses an interior part of the character, while a negativelyoriented one (running clockwise) encloses an exterior part. This even works if the closed contours overlap one another or intersect themselves, as long as they are closed at all. Today, the resulting winding number filling algorithm represents the state-ofthe-art [Hersch, Gonczarowski89 (for further details cf. A.1.3). ] The problem left was to manage the orientation from within the outlinefont editor. To migrate the existing fonts to our new editor, we equipped the editor with a function that determines the orientation of the contours and - if necessary - fixes it, assuming that the existing fonts characters are made of closed contours only. Lookingat the fonts we had, this was mostly the case. A few figures needed an extra straight line or similar trivialmeasures for proper "closure".

a character which is not properly "closed" (left) and its closed variant (right) To define new fonts with our outline-font editor, we have provided for the possibility to reverse the orientation of a contour. Whiledesigning a character on

A Simple Outline-Font Editor

65

screen, one may not (want to) be permanently aware of the contour orientation. Yet, once the character is designed, the orientation may be wrong. In order not to have to reverse it by hand, we have provided for the possibility to reverseindividual contours by command. Much like split and merge,this operation reveals the truly two-dimensional nature of a font editor and certainly would not make any sense in a text editor.

3.1.6 Zooming & other viewing attributes A couple of further features of our new outline-font editor are worthwhilebeing mentioned. Despite the measures taken so far to make accurate font editing as easy as possible, there is still some scope left for further improvement. One detail of rather inconspicuous nature implements a kind of local gravity within the immediate neighborhood of the knots. If the mouse is located within a clearance of 2 pixels relativeto a knot, then placing a focus will place the focus at the knots exact coordinates, and attempting a specific marking will actually mark the knot. This behavior implements a compromise between having a grid (which we are not allowed to do, cf. 3.1.1) and not having a grid (a situation which we may find hard to live with). Another feature of the outline-font editor is the zooming. Althoughit would have been trivial to realize logical zooming, the zooming provided is but a physical zooming. Physicalzooming essentially blows up all pixels by an integral factor before displayingthem. In contrast, logicalzooming scales the outlines, rather than scaling the pixels. But thereby, overshoots of natural splines between two closely spaced knots may be amplified up to the point where they become visible(cf. also 3.2.1).

physical (left) and logical (right) zooming (with and without knots): logical zooming unveils overshoots between two closely spaced knots (the natural spline neither starts horizontally, nor ends vertically) While logical zooming would have permitted to accurately create new fonts, targeted at high-resolution font-scaling,it would not automatically have made

66

The Input:

any preciser the old outline-fonts we had already,geared at medium- and lowresolution font-scaling.Be it as it may, zooming is targeted primarily at making font editing as pleasant as possible for a given resolution. On top of that, the interface objects being extensible windows, zooming can be encapsulated in the windowsmethods, without the application objects (i.e. the model, or the outlines) havingto be aware of this fact permanently. Yet another class of features are the various viewing attributes provided. Although it is indispensable to see the knots when it comes to manipulate them, and although it may be highlydesirable to see the auxiliarylines (base, mean or cap line, left or right side-bearing), eventually the unrestricted view onto a possibly filled character may be preferable.These viewing attributes are a property of the interface object (i.e. the window), not the application object (i.e. the model). Therefore, it goes without saying that we can see simultaneously any number of windows displaying the same figure with different attributes and zooming factors, limited only by the screen size and eventually by the performance of the machine. To emphasize it once more, the attribute which allows us to see a character as outline or as solid object, is merely a matter of choosing this or that turtle.

3.1.7 Convenience vs. non-modality In a broader sense, a mode denotes the way in which something is done. In a narrower sense, i.e. in the Oberon environment, particularly a hidden mode designates something which an interface object should avoid to do: to respond to one and the same user action in different ways, depending on an invisible or an easily overlooked state of the interface object, or even worse, to enforce a dialog [Reiser91]. It is not always that easy to see, whether or not the behavior of a particular program is modal or not, it may take quite a bit of experience - experience in the sense of workingwith programs which reportedlyare modal, or which claim to be mode-free. Not without amusement, we remember an example of a modal program. At that time, it used to be a rather famous word processor. We tried to explain it to an Italian friend of ours, who was about to write his thesis in German literature. Irrespectiveof the fact that the wordprocessors entire user dialogue was in Italian, too (which would be considered user-friendly),the followingscenario happened: At some stage, our friend activated inadvertentlya kind of a menu, which from that point on would cover the first few rows of the screen. For some reason or other, our friend decided not to make use of the options in the menu, but preferred to proceed with typing the text. Asubstantial portion

A Simple Outline-Font Editor

67

of the text being visible still, right underneath the menu, nothing would have suggested that this is impossible. However, the computer interpreted all the subsequent key strokes as commands, causing the heavenly hosts of menus to follow one on the heels of the others, which eventuallyprompted our friend to exclaimsomething like: "For goodness sake, what happened then!?" Upon typing [Ctrl]-[Q], our friend must have switched from text entry mode to command mode (the respective menu being labeled "Rapido",nota bene), which does the response to key strokes in a different way. We may believeto be able to do better today, but still, it is not always that easy to find an optimal balance between avoiding such unexpected reactions and providing convenient operation of the program. To see this we describe a particular response of the Oberon text editor which is both very convenient and highly irritating, depending on the temporal context. To delete text portions that would exceed the dimensions of the window, we may mark the start of the text to be deleted in one window, and the end of it in another, interfacingthe same text (in the same track, but below the first window). Upon the appropriate interclick in the second window, the text is deleted from the start in the upper window up to the end in the lower window, which is reflected in both windowscorrectly.This avoids us to have to delete extended portions of text in terms of small portions not exceeding the dimensions of a single window, which is very convenient. Now suppose we are editing a computer program, say, analyzingthe use of types or other global declarations. To do so, we can keep the declaration part in one window, whilehavingthe editor search identifiers declared above in the window right below. To make the searching mode-free, the respective identifier is marked and a command is issued, using the marked text as its parameter. Havingfound an occurrence of the identifier (the original identifier remaining marked), we may decide to edit the program which surrounds the identifier. In particular, we may wish to remove,say, a local variablewhich has become obsolete. However, this seemingly inconspicuous venture is awarded with removing all the text from the marked identifier in the upper window up to and including the variable we intended to get rid off in the lower window, which is rather irritating. All this goes to show that the question, whether or not something is a mode, or whether or not a program is modal, does not always allow for an unambiguous answer. Even the inevitablypersistent marking of text (if it were not persistent, much of the Oberon systems design philosophy would become unfounded) can unveil a certain degree of modality. We have met a situation like that already in 3.0.2, and we would like to give two more examples, illustrating our attempts to balance the absence of modes against convenience.

68

The Input:

In the first example,we believeto have been successful in our endeavors to avoid (or at least to weaken) modality. In the font tools to be replaced, it was possible already to constrain motion to the vertical or the horizontal direction. To have this function at all thus is not our own contribution. However, to use it, one had to switch the editor into verticalor horizontal mode by pressing the "v" or the "h" key respectively.This state was not made visible at all. Even worse, to "unlock" this constraint, it was necessary to know the exact number of strokes of the [Esc] key, deactivating apparently different degrees of modality. Yet, being trained to this mode of operation, the type-designerobjected to using a ruler as introduced in 3.1.2, for inconvenience. It is easier to issue a key stroke than to define an exactly verticalor horizontal ruler. Thus it is not without pride that we emphasize our solution to the dilemma convenience vs. non-modality . In the second example we were less successful in removing modality. The matter at issue is the indispensable definition of new contours. To make this function mode-free, we might consider a kind of multi-focus, as opposed to the extended foci introduced in 3.1.2, consisting of a sequence of foci to be specified one after the other. Asubsequent command could use these foci to define a new curve.During the specification of the foci, this would allow only for a rather vague idea of the future course of the curve, particularly in the case of Bzier curves. We therefore opted for a kind of rubber-banding, the rubber-bands being polygons, natural splines, or Bzier curves. During the specification of the knots, we can see already the exact course of the curve. However, the curve to be is a mode! Even though it may be highlighted by an icon in the title bar, possibly at the far end of the focus of our attention, we may forget to look at the icon or to switch the radio button to "spline mode" before contour definition. With that, we have tried to show that for reasons of convenience we have opted for a mode, albeit not such a serious one as the "v" and "h" keys above, for we can still change the curve class afterwards.

3.2 An experience with third order Bzier curves 3.2.0 A font-designers point of view Now that we had an outline-font editor with both natural spline and Bzier curves, we started to weigh up the advantages and the disadvantages of both curve classes. For reasons that could not be traced back ultimately,the curves provided by the softwareto be replaced were exclusively natural splines. Most probably, they reflected the preference of the type-designer:All the knots lie

A Simple Outline-Font Editor

69

on the curve (i.e. so-called on-curve points), or vice-versa, the curve passes through all the knots. Movingone or several of the knots yields a rather intuitive change of shape. In the end, this response is not that much of a surprise: The natural splines originatein ancient shipbuilding, which fitted wooden analogs to a framework of pivots. Thereby, naturally, the wood would follow the line of least deformation energy, hence their name, and most probably, hence their intuitive response as well. Yet, the deformation energybeing leveled out globally,this has some typical side-effects, which may question the naturality. On the one hand, any manipulation at a knot has non-local effects. It may change the shape of the entire natural spline curve, even though the amount of change will decrease along with increasing distance from the original point of change. On the other hand, it is hard to control the slope of a natural spline curve directly (or what on the raster display we perceive to be the slope, cf. A.3.1 and the illustration in 3.1.6). It may require two or even three rather closelyspaced knots to force the natural spline to a certain slope at its ends, which is required to make a smooth transition between curved and straight parts of the outlines, and still the slope may not be absolutely accurate. These problems do not exist with Bzier curves. Manipulations at single knots of third order Bzier curves have rather local effects, and their slope at either end can be controlled directly.However, this control is paid for with the disadvantage of knots which lie off the curve (i.e. so-called off-curve points), or vice-versa, the curve does not pass through all the knots. This may be at least confusing, since it makes it difficult to see, to which curve a particular knot belongs, and hence, where to set about in order to change the shape of the curve.On top of that, whiletryingto design characters by hand, their response to manipulations appeared rather unnatural to us, not only due to lack of experience. We often felt like moving the two off-curve points at the same time, but possibly in different directions (having a well-developedsense of symmetry, we would not have objected to try to use even two mice simultaneously). We will come back to this problem later in this section.

3.2.1 A font-scalers "point of view" Eventually,we managed to overlay the pair of natural splines making up for a quadrant of a Times "O", by a pair of Bziercurves.With both the natural splines and the Bziercurves, we applied a feature of our simple outline-font editor: To scale outlines whiledraggingthem (cf. 3.1.2). With these assets, for the first time we could see what coarse scaling does to the outlines.

70

The Input:

The outlines of a Times "O", using natural spline curves...

...and Bzier curves, scaled naively from 72 pt at 300 dpi down to 9 pt at 72 dpi, and re-normalized after scaling In the above sequence of curves, we observe first of all that the transition from high- to low-resolutionas done by Bzier curves is much more good-natured than the one by natural spline curves. Furthermore, natural splines may turn rather straight parts at high-resolution into overshoots and meanders at intermediate resolution. On top of that, pairs of knots which are spaced closely(in order to try to control the slope of the curve) eventually coincide, whereby

A Simple Outline-Font Editor

71

their original purpose ceases to be served.Bziercurves display none of these shortcomings. At worst, it may take considerably coarser scaling until the distance between the on- and off-curve point of a Bzier curve vanishes, which would cause the curve's topologyto change significantly(cf. 4.4.1). The reason for this dramatic difference between natural spline and Bzier curves is the following.For the natural spline curves, apart from defining their shape, the knots often bear no further meaning as far as the topology of the curve is concerned. For the Bziercurves, however, successive pairs of off-curve and on-curvepoints define directlythe slope of the curve.When used as in the above example,this means that either the x- or the y-coordinateof such a pair is the same, hence it will undergo the same transformation under scaling. On top of that, said coordinate simultaneously defines the locus of extremal extension. With that, the scaling is applied actually to the slope and the extremal points, rather than just any other meaningless knot. We begin to understand why commercial font-scalers seem to avoid natural splines. Acommon objection to Bziercurves is that their use as a substitute to natural splines triples the number of knots. This is true only if we convert natural splines to Bzier curves naively:Anatural spline is obtained by transforming a sequence of n+1 knots into a sequence of n polynomials. Each of these polynomials may be written equivalentlyin the standard or the Bernstein basis (i.e. as a Bzierpolynomial, cf. A.3.0). With that, each polynomial contributes 3 knots, adding up to 3n+1 knots, which for a large n indeed is about three times the n+1 knots of a natural spline. However, proper definition may keep the number of knots down at roughly the same number. In other words, this means that proper definition reduces the number of polynomials to one third! For example,for the above quadrant of the "O", 6 knots per natural spline are replaced by 4 knots per Bziercurve,and 5 polynomials are substituted by a single one. At the same time, using Bziercurves avoids us the numerics to convert a sequence of knots into a sequence of polynomials. The respective conversion is trivialfor Bziercurves and not necessary altogether when using recursivesubdivision as digitizingmethod. It may be objected furthermore that the way we scale outlines is a bit overly naive. In the above examples we have rounded all the knots to the nearest integer grid point before digitizingthe contours. This is not quite what we are going to do in our approach for font-scaling,but it helped us to develop the intuition for the matter at issue: It is not exactly correct to round all the knots of the splines, but it is neither compelling to be wrongfor all the knots of the Bzier curves. In fact, besides being a necessary precondition in many cases (cf. 4.2), rounding knots before digitizing the outlines provides several other advantages (cf. 4.4).

72

The Input:

3.2.2 Conclusions From the preceding two sections we conclude that the advantages of the Bzier curves would excel those of the natural spline curves, if it were not that cumbersome to provide them. One way to defuse the problem is to have the outline-font editor provide more assistance. Much like decoupling the curves from the outlines, we could decouple the knots' coordinates from their actual implementation. Therebywe could implement intelligent knots or constraints which are associated with the knots more permanently, rather than ad-hoc as in 3.0.2. Simple applications thereof are the restriction of the motion of offcurve-points to the direction of the slope, or the assertion of G1 continuity at the on-curve-points by relating the off-curve-pointsof successive Bzier polynomials to one another. In the end, this could result in a kind of simultaneous h- and v-lock of successive off-curve-points(cf. 3.1.2 and 3.1.7), much like with a joy-stick located at the imaginary corner of the two tangents (cf. 4.4.1). However, since the primary goal of the present work is to raster fonts, but not to acquire new ones, we opted in favor of another way out. The more demanding way is to try to provide an automatic transformation from spline to Bziercurves.Most of what is required to do so, seems to be rather easy (cf. A.3.0), and the rest (cf. A.3.2) amounts on varying the two off-curve points (variational calculus) in a well determined fashion (least-square-fit). Thus, rather than easing the manual work (most hopefully avoiding the need for two mice), we have the computer do the labor. In the end, computing science is (also) an engineering science; therefore, in order to prove the usefulness of the approach, it would be rather academic to assume the existence of a certain curve class without supplying the input.

3.2.3 Adroitly defined Bzier curves Beforetaking the plunge, here is an attempt to define what we are about to do: An adroitly defined Bzier curve is a G1 continuous sequence of third order Bzier polynomials, whose on-curve-pointsare located at the start and the end, at the local extrema, and optionally at the inflection points of the curve. Interestingly enough, when we tried to explain this definition to our typedesigner, Hans Ed. Meier, he appeared to understand the graphics thereof quicker than what it took us to convey the ideas of local extrema and inflection points. He would point out promptly, where to start new Bzier polynomials. This leads us to hope that our ideas are not that far-fetched.

A Simple Outline-Font Editor

73

A Times "S" defined with third order Bzier polynomialsthat start at local extrema and inflection points We are concerned to make clear that we cannot guarantee a single Bzierpolynomial per quadrant to be sufficient for correctlysubstituting all natural spline curves possible in one quadrant, but we have found these limited capabilities to be sufficient for the Times font used to give the proof by existence of our approach to medium- and low-resolutionfont-scaling (cf. also A.3.3). If it should be objected that these prerequisites may be too restrictiveto meet the highest demands, the path to pursue for defining curves is the one which preserves the possibility to talk directly about the topology of the curve. Hint: Consider the use of higher ordered Bziercurves or use one third order Bzier polynomial per octant, rather than per quadrant only,particularly if this leads to avoidinglong flats in diagonal parts of curvilinearglyphs(cf. 4.4.1).

3.3 Assessment of the approach for an outline-font editor In the beginning it looked as if there were no grounds for the present chapter. On the one hand, the Medium Resolution Font-Design System [Kohen88] was still operational on the personal computer Lilith [Wirth81], and the font-designer was well trained to it [Meier91]. On the other hand, our own first outlinefont editor on the personal workstation Ceres-3 [HeebNoack91 under Object ] Oberon [Mssenbck&al89 was not geared at designing new fonts. Rather, it ] was intended to let us look at existingfonts, maybe perform slight corrections to the outlines (cf. 3.1.5), and convert the fonts to formal notation (cf. 4.3). It was valuable to gain experience with the graphics library [Stamm89] when used for constructing a graphics editor. The conclusions drawn from these experiments resulted in [Stamm92b] and are coveredin Chapter 2 in here.

74

The Input:

But then two events occurred almost simultaneously. For reasons discussed in section 3.2, we decided to make increased use of third order Bziercurves for defining curvilinear glyphs. However, our first outline-font editor, not being extensible,would not let us integrate Bziercurves smoothly. At the same time, the last Lilith ceased to function, after having been in use for about ten years. With that, the font-design tools could not be used anymore. However, due to substantial incompatibilities between the Lilith and the Ceres, and their respectiveenvironments, we considered porting the tools from Lilith to Ceres a major challenge, yet scientificallya rather useless one: In the end, we are not assessed by what we have ported, but by what we have created. The alternative was to provide a new editor for outline-fonts, and a simple font-scaler for medium resolution devices.The editor should comprise all the functionality of the original tools, together with a sufficient degree of extensibility allowing for the integration of Bzier curves (and further contour classes). Right from the very outset, the editor was targeted at medium resolution font-design. Providing for high resolution, albeit trivial with our device independent toolbox now, would not have made the existingoutline-fonts any preciser. The result is a single module that replaces 12 modules, reducing the source code size of 264 kB to 86 kB. This is less than 33% of the original size! We would like to put this down to two different causes. On the one hand, we have illustrated in the first two sections of this chapter, how OOP style can detach the entity of interaction from the graphics primitives, and particularly, how algebra can be used quite profitably for advanced manipulations (affine copyingand dragging).Here is therefore a hint regarding the use of OOP: It is by no means compelling that, once we have determined OOP style to assist the structuring of programs, we are bound to do everythingin OOP style exclusively.To give an idea thereof, we mention that it was a matter of less than one hour to both figure out the linear mapping of the shearing and to integrate the shearing into the outline-font editor. We therefore cannot understand why programs with comparable functionality should be considerably larger. On the other hand, we find it hard to overemphasize the importance of the (programming) environment. Apart from elementary support modules for managing strings, lists, trigonometric functions and their inverses, or ADTs for rational numbers and mappings, and apart from our graphical toolbox,by programming environment we mean shadow bitmaps and windowsthat free the implementor of derived window classes from all those details which are irrelevant for the particular derived window class. Especiallyfor an interactive application, such as the present outline-font editor, it is indispensable to know one's environment, to know always what it does, and to know for sure that this is exactly what we want it to do, rather than what the implementor of

A Simple Outline-Font Editor

75

the base system could implement with the fewest possible statements. Here is therefore another hint regarding OOP: It is by no means compelling that an extensible environment is a good environment, if this assessment is based exclusively on the basis of being extensible or not. If the implementor of an extension has to re-invent most of the wheels, such as to update the mouse cursor on screen, then such an empty base system is a useless system, albeit extensible. Notice, finally that all of the above implied requirements are far from being geared exclusively at an outline-font editor, but rather constitute the stock-in-tradeof a versatileenvironment for program development. To conclude, we are concerned to make clear that the present chapter is by no means a comprehensive account on how to implement an interactive program, and in particular, a graphics editor. We do not intend to carry the coals to Newcastle, going over the model-view-control(MVC) decomposition again and again. Rather, taking the pick of the bunch, we have tried to highlight the points which make our approach different,both from a users and from a programmerspoint of view. (Section 2.1.1 may give some hints as to the windowing system that we have implemented actually, separating interface objects from application objects. The binary format of the (unstructured) outlinefonts, as produced by the editor, is given in AppendixB, while for the bare operation of the outline-font editor, the reader is referred to [Stamm93c].)

A snapshot of a typical outline-font editing session

4 The Backbone: AFormalism For Intelligent Outline-Fonts


4.0 The fundamental raster problem 4.0.0 Scaling - not a linear mapping? By way of introduction, let us review brieflythe achievements of the preceding chapters. In Chapter 2, we have provided for the foundations in raster graphics: A graphical toolbox that would let us outline and fill any sequence of lines and curves on any raster device. Since this comprises logical device independence, and therewith the scalability of lines and curves, we would expect to use our toolbox for scaling characters straight away. In Chapter 3, we have illustrated one way of providing the input for the font scaling: Asimple, yet versatile editor for drawing the characters outlines in terms of lines and curves. Together with our toolbox, therefore, we could scale fonts on-the-fly now (and actually,we did, cf. 4.1.3). In Chapter 0, however, we anticipated that matters are not as simple as that: The raster tragedies illustrated there give a phenomenological access to the problem of font-scaling at low resolution. The multitude of facets through which this problem discloses itself, though, does not hold out any hopes in a systematic transaction, in the sense of a most general and unified formulation. Yet there is a common denominator, from which we shall start out here. Without loss of generality,we use integer coordinates (cf. 2.0.5) in font space, which is our model space (cf. 2.2.3). If it should be objected that reals have to be used instead, or else we do not allow for subtle nuances, we multiply all coordinates in model space by a sufficiently large integer, such that, in this new model space, non-integral values cannot contribute to any further finegrainedness, at any deviceresolution, and for any type size to be used. Added to which comes the fact that on computers the so-called reals are nothing but floating point numbers, whose intricate behavior under addition or multiplication would have to be taken into account for a given floating point co-processor or emulator - the IEEE-754 norm notwithstanding. Notice,finally,that we can use integer coordinates regardless of the approach to be used (outlineoriented or image-oriented,cf. 4.1.3). Thus let x

A Formalism For Intelligent Outline-Fonts

77

denote one such coordinate, relating to such graphical entities as the sidebearings or the control-points, or to any other interesting locus on the boundary of a character. To scale this coordinate, we multiply it by a scaling factor . Again, without loss of generality,we assume a rational scaling factor = n/ d, where both n and d are integers. Here, too, there is no point in using reals; both model and device space are integral now, hence their ratio is, trivially, rational. We thus have x = nx/d wherebywe usually leave the set of integers, since the integers are not complete with respect to division. In the end, however, we have to come back to the set of integers, for we have to decide, which pixelsto turn on. To do so, we have to round the scaled integer in one way or other (symmetric or asymmetric; up, to-nearest, or down), and in some place or other (assume x denotes the x-coordinate of a controlpoint, then we can round this control-point before digitizing the respective line or curve,or we can decide in which way to round upon digitizingonly,but the problem of having to round at all occurs in either case). Denoting the rounding by [ we define the scaling function s(x), to which all coordinates ], obey, to be s(x) := [x] = [nx/d ] whose range and domain are both the set of integers. We conclude with the simple but utmost important observation that with this scaling function s(x), for any two integer coordinates x and y, and for any two rational numbers and , usually s(x + y) s(x) + s(y) and s(x) s(x) or, in general, s(x + y) s(x) + s(y)

78

The Backbone:

as a result of the inevitable rounding. In turn, additivity and proportionality, and therewith linearity, are lost. Our scaling function is not a linear mapping! Since we shall face individual facets of the whole gamut of consequences of this problem repeatedly, we call this common denominator the fundamental raster problem, to be investigatedsubsequently.

4.0.1 Equality We start our investigations with the analysis of s(x + y) s(x) + s(y), the lost additivity.Let left and right denote the (integer) x-coordinates of a plain upright stem of a character. Then there is always an integer width, such that width= right - left. If we apply our scaling function, however, we are likely to get s(width) = s(right - left) s(right) - s(left), that is, the scaled width is likely to be wrong. And if the width of this stem should be correct, Murphyslaw willing, the width of that one is sure to be off by one. Likewise, horizontal crossbars are prone to come out with uneven thicknesses, and serifs will be of varying lengths, and so forth. What should be established at the very outset, though, is that the problem occurs already with a plain upright stem, which has neither serifs, nor a slant. Such a stem is an orthogonal rectangle, as introduced in 2.0.5. With orthogonal rectangles, there is no need to digitize outlines or the like, hence there is nothing to be suspected to fail upon the digitizingitself. All we need to do is to scale (and round) the vertices of this rectangle, and issue a single call to device.Fill(rect) to render the stem. With the loss of additivity,we understand now, that unequal stems, although defined equally,were bound to happen. What is the lesson to be learned, then? Our scaling function is not linear, irrespective of the exact nature of the division (cf. 4.0.2) or the rounding (cf. 4.0.3). This causes implicitly equal components (glyphs, distances, to co) me out unequal. Hence we must make the implicit equality explicit.To do so, we have to structure the fonts characters into components, whateverthey will be (cf. 4.3.0). And we have to do so, again, regardless of the approach to be used (cf. 4.1.3). Then the characters and the components themselves can refer to these components repeatedly.Hereby, we introduce the concept of equality on pixel-level, and we providefor invariance of translation.

A Formalism For Intelligent Outline-Fonts

79

4.0.2 Symmetry By now, it may seem a bit of a commonplace to stress the loss of proportionality: If the naive approach did yield proportionally scaled characters to begin with, then we would not have anything left on which to investigate here. Yet there are subtleties concealed in the simple statement s(x) s(x), which may not be obvious to the unbiased reader. Two of them are subject to further investigation in here, and in 4.0.3, respectively(further aspects of proportionalityare postponed to 4.4.1 and 4.4.3). Let us consider s(x) s(x) for = -1 and any x. The subtlety here chiefly arises from the definition of the division of two integers, i.e. the question, what is the result of n DIV d on our machine or in our programming language? We observe that for our scaling function we have d > 0 always. As long as n 0, we would expect n DIV d = FLOOR(n/ d) = TRUNC(n/ d) justifiably.For n < 0, this depends on the machine and/or programming language,however. Popular implementations of Pascal or Modula-2offer n DIV d = TRUNC(n/ d) {integral part of real number n/ d } typically, but Oberon asserts the invariant n = (n DIV d)*d + (n MOD d), and therefore n DIV d = FLOOR(n/ d) {largest integer not greater than n/ d } for d > 0. When scaling the control-points of graphics, this has a great advantage: This definition of the division of two integers is continuous with respect to the origin [0, 0], and thereby, with respect to both coordinate axes y = 0 and x = 0. If we scale an orthogonal rectangle, and if the scaled width and height are again integers, then we simply scale the verticesof the rectangle, regardless of the number of quadrants (of the coordinate plane) involvedtherein. In other words, if the division can be made without loss of information, we can move the rectangle across the y- or x-axis freely, without their scaled width or height to change. This suits perfectlythe invariance of translation aimed at in 4.0.1.

80

The Backbone:

However, this definition of the division also has a great disadvantage: It is asymmetric with respect to the origin, or in general,with respect to the coordinate axes. In algebraic terms, and formulated somewhat provocatively, this would mean for an x in font-space that -x 0- x where the "-" on the left is unary, while the "-" on the right is binary. As soon as the coordinates are scaled by = n/ d, being located at some negativecoordinate is not the same anymore as measuring off an equivalent distance on the negativecoordinate axis. In terms of Oberon, and for simplicity for | x| = 1, this means that (- n) DIV d -( n DIV d) actually. In other words, we would have to distinguish the scaling of a figure representing a location (which may assume negative values) from the scaling of a figure denoting a length (which is a strictly non-negative entity) - rather confusing! Notice, besides, that there is no point in implementing "round-to-nearest" or ceiling when scaling with the asymmetric division. If we did, we would merely shift the scaled drawingas a whole,at the expense of extra computational steps: FLOOR(nx/ d) = n*x DIV d ROUND(nx/ d) = (2*n*x + d) DIV (2*d) CEILING(nx/ d) = (n*x + d - 1) DIV d Now, for the sake of additivityand notably subtractivity,we are tempted to opt for symmetric division, even though this may mean to setup certain glyphs entirely in the same quadrant, in order to avoid said discontinuities. But this is not enough to guarantee s(- x) = -s( x) ultimately: It is not sufficient for ensuring the symmetry of two contours, even if they are defined by sequences of control points which are symmetric with some respect. Readers familiar with Bresenham type algorithms will know, that scan-converting a straight line from P to Q may yield a slightly different sequence of coordinate pairs than when digitizingfrom Q to P (cf. 4.5.2), or el-

A Formalism For Intelligent Outline-Fonts

81

se we trade it in for asymmetries with respect to the coordinate axes, and viceversa. If we used the knowledgeabout the way our Bresenham type algorithm is realized, we would break with the layer of abstraction provided by the digitizing method! Again, in order not to anticipate the choice of the approach to be used (cf. 4.1.3), we point out that FloydSteinbergtype algorithms (cf. A.2.1) show predominant directions of processing, too. These may violate symmetry at even higher a degree due to their statistical nature - the algorithms stem from error diffusion. What is the lesson to be learned, then? Asymmetric dividing, such as "flooring",avoids discontinuities at the origin, and it is invariant under translation (provided there is no loss of information), but it leads to inconsistent notions of location and length. Symmetric dividingavoids conflictingnotions of location and length, but it is not invariant under translation. Yet not even symmetric dividing can ensure symmetry on pixel-level ultimately. We thus need more than the possibility to represent a negative number by "minus an unsigned number". We must have a means of making implicit symmetry of components explicit.Hereby, we introduce the concept of symmetry on pixellevel, and we providefor invariance of mirroring.

4.0.3 Connectivity The other subtlety concealed in s(x) s(x) stems from the case 0 < 1 x 1. Suppose x denotes the width of a stem and x the width of a and crossbar, or assume any other pair of lengths which are connected with one another by proportionality. Furthermore, let us consider it for coarse scaling, that is, for small type size at low resolution. At this scale, it is nothing unusual x 1. Now, if for stem widths to be representable by a single pixel only,i.e. the crossbars width should be just a fraction of the stems width,i.e. 0 < 1, and if s(x) does round-to-nearest, we are in the situation wheres(x) = 0 but s(x) 0. As a result of the inevitable rounding (i.e. the decision, which pixel to "turn on", or which "Manhattan move" to make), the crossbar or the serifs vanish, although the scaling factor does not! Unavoidable though the rounding is, we observe that we have not made a choice of its exact nature yet. So we consider round-up instead, however intuitive round-to-nearest may be. Undoubtedly, for the particular cases of vanishing crossbars and serifs, this would help. In general,though, the glyphs might get a tendency towards overweight. And in the end, we would replace one evil with another: Assume now that x denotes the height of the capitals and x the baseline overhang or the capsline overshoot. Such optical corrections are

82

The Backbone:

aspects of artistic license (cf. 4.4.0), which can be reproduced for large type size at high resolution only. For coarse scaling, even a single pixel would be too much, hence they must vanish, although the scaling factor does not. What is the lesson to be learned, then? Round-to-nearestintuitivelyprovides the best transition from rational to integer numbers, but it leads to vanishing components. Round-up prevents such drop-outs, but it leads to overweight, and it blows up subtle nuances beyond all measure. Round-down is out of question altogether by now, to a certain extent, it would simply combine the shortcomings of the former two proposals. Hence, we must have a way to distinguish components which may - or even should - vanish under coarse scaling, from components which must not do so - under no circumstance whatsoever. Hereby, we introduce the concept of connectivity on pixel-level, and we providefor invariance of existence.

4.1 An outline-oriented declarative high level language for fonts 4.1.0 Formal language vs. interactivity By way of transition, let us stress one thing. In the preceding section, we have seen the most relevant consequences of the linearity lost on pixel level. Each of these consequences entails a necessary structural contribution to naive generic fonts. But until now, we have left open the nature of both the font and its structure, as well as how the latter is added to the former. Thus we set out next, what are the alternatives, and upon what questions we have based our own decisions. When we started our project, a collection of most carefully designed outline-fonts were available in electronic form. From enlarged 300 dpi print-outs of these fonts, professional "type foundries" could digitize outline-fonts for use with high resolution devices straight away (e.g. ITC Syndor, URW Barbedor, and recently,URW Barbetwo;all in their proprietary format). Thus, as far as their shape was concerned (not the curves representation as splines, cf. 3.2), we had no reason to assume any need for major modification, and therefore, no inherent necessity for interactivitybeyond the simple outline-font editor. For chiefly, the present thesis assignment is to produce bitmap-fonts at good quality automatically and on-the-fly, starting out from an outline-font with sufficient structural information permitting to do so, but not the acquisition of new fonts. As far as structuring the font is concerned, we have to assert an acyclictopologyof components. If we do not, we are at risk of allowingcyclic dependen-

A Formalism For Intelligent Outline-Fonts

83

cies, which would have to be disrupted. Alternatively, we may understand the dependencies as the rules of a constraint based editor. In such editors, contradictory rules expressing mutual dependencies are "relaxed"by some iterative - and sometimes rather mysterious - process, whose intricate behavior is felt to be beyond predictability - unless we wish to break with the level of abstraction provided by the rules. In our opinion, it should always be possible to obey to a rule rigidly, or else the rule need not be imposed. With a formal approach partial ordering of the components follows triviallyfrom requiring the components to be declared before they are used. This restriction is not expected to be a hindrance: When watching our type designer at work, often we would observe him matching glyphs with "reference"stems and serifs defined already. In other words, he is applying contradiction-free rules to begin with, since his way of workingis "partially ordered". This is why we are convinced that the possibility of contradictory rules to be "relaxed"does not mirror closely the way in which a font designer reasons about his font design. We conclude that opting for a formal approach provides an acyclic topology at no extra expense. Afinal argument as to the feasibilityof a more interactive approach: Unlike IC design programs and similar, the components of our fonts are not going to be glyphs and contours only (e.g., in IC design programs, inverters and their connections), but necessarily knots and numbers, too (cf. 4.3.0). Therewith, an interactivesolution becomes not that obvious, let alone intuitive in its use: With our fonts, we cannot simply pick this or that glyph from some stock of glyphs and add it to the assembly. We would also need a means of representing graphicallythe simple but frequent facts, that e.g. the actual width of this glyph is the standard stem width plus an optical correction of so-and-so, or that this knot should be slightly below that point of reference, and so forth. Doing so may entail a wealth of arrows and other special symbols on top of the graphical representation, such that in the end, we are likely not to see the character for the marks anymore. And we would not content ourselves with clicking on the respective knot with the mouse, in order to pop-up a property sheet in which to fill in x = stemWidth + opticalCorrection, etc. This would be plain formal notation to begin with, covered up by a fancy graphical user-interface.We prefer not to be suspected of havingwhitewashedour ignorance. All this is to say that since there is no need for further interactivity,since a formal approach yields the desired topology at no extra expense, and since further interactivitywould be rather complex but without benefit for the endproduct, the use of a formal approach for adding structural information to existing fonts is the optimal choice within the scope of the present preconditions, and it leads to the simplest program.

84

The Backbone:

4.1.1 High level vs. low level We have decided to use a formal language for expressing the structure to be added to our existing fonts. Now we have to decide, whether this language should be a high level language,such as Oberon, or a low level language,such as NS32x32 assembly language. In order to accomplish our decision, let us confront the two concepts with one another briefly, and let us try to extrapolate, what these concepts mean in terms of fonts. In a high level programming language, we manipulate abstract entities, such as integers and floatingpoint numbers, or aggregatesthereof, such as arrays, records, and sets. The manipulations comprise elementary arithmetic, such as "+" and "-", or more complexoperations, such as what can be realized by some form of subroutine call. The advantage of a high level language is to be enabled to concentrate on the problem, that is, the algorithm and its data structure, rather than having to be concerned with the intricacies of the underlyingdevice. For a high level font language, therefore, we expect to manipulate such entities as glyphs and distances, or aggregatesthereof, such as compound distances and compound glyphs. As far as the manipulations are concerned, we expect to add and subtract such distances, or to move and mirror such glyphs.The objectiveof a high level language here is to be as close as possible to the world of type design, rather than having to be concerned with issues inherent to a particular digitizingor rendering algorithm. In a low level programming language, we manipulate concrete entities of the underlying device, such as bits and bytes in memory, or registers. The manipulations comprise not only elementary arithmetic, such as "add-with-carry" and "subtract-with-borrow",but - besides subroutine calls - also operations on memory addresses, necessarily. The advantage of a low level language may be to profit unrestrictedly from the very devicesfeatures, at the expense of being burdened with the devicesintricacies, which possibly do not have any relationship with the targeted algorithms and data structures at all. For a low level font language, likewise, we expect to manipulate entities aimed at the grid of the underlying device, if not the pixels themselves. Accordingly, the manipulations may comprise explicit instructions for fitting control-points to grid lines, if not to restore a pixel dropped under some exceptional circumstance. The objectiveof a low level language here may be to control the appearance of rastered glyphs unrestrictedly, at the expense of having to relate to particularities of the intrinsic digitizing or rendering algorithms, which possibly are quite far from the worldof type design and from the font to be defined.

A Formalism For Intelligent Outline-Fonts

85

To make our decision, we notice two things. On the one hand, the structural information to be added to the fonts is inherently on a very high level of abstraction to begin with: Equality,symmetry and connectivityare concepts way above the notion of glyphs and distances, irrespective of the approach to be used (cf. 4.1.3). On the other hand, the ultimate purpose of our project is to provide complete device independence - as established for single glyphs (i.e. graphical objects, cf. 2.2.0) - also for characters and entire fonts. This dictates to prohibit whatever means of referring to pixel-space. We infer that the obvious decision to be made is for a high level language for fonts, and that we have to understand the above needs of the moment as a paradigm, to be respected under all circumstances.

4.1.2 Declarative vs. imperative We are about to define a high level language for fonts. This raises the question of the language paradigm to be employed. Nowadays, several high level programming language paradigms are competing to enjoy the programmers favor. While early machine languages, and shortly afterwards formula translators, reflected strongly the operational way of thinking, recently such imperative programming has suffered partial ousting by declarative or functional programming, and notably by object-orientation. Some modern high-level languages advocate a hybrid approach, such as Oberon, in that they blend the strengths of individual paradigms, without inheriting blindly all their weaknesses, too. Upon deciding which paradigm is suited best to add structural information to fonts, we realized quickly,that we had better not formalizestructure as an end in itself: Equality, for instance, should be formulated as equality of components, rather than equality on its own, and likewise, symmetry or connectivity.Hence we start talking about structured fonts, that is, about geometrical shapes and their mutual relationship. In order to formulate structured fonts, we would like to say preferably, what the fonts are, and not, what the computer has to do such as to obtain them. On the one hand, this is much closer to the world of type design, and on the other hand, this is anticipated to prevent peculiarities - which may be intrinsic to particular digitizingor rendering algorithms - from stealing in unwelcomedly.At the same time, however, this sets the course for a declarative language. Beforeour decision is reinforced any further, it might be objected justifiably, why we did not use Oberon itself to program our fonts. The answer is simp-

86

The Backbone:

le enough. On the one hand, Oberons comprehensive generality would be very likely to distract from the proper goal: To define fonts, we would not need modules, nor polymorphism, or late binding. On the other hand, some of the main constructs of our font formalism (cf. 4.3) are not provided by Oberon: There are no constructors for tuples, such as coordinate pairs or lists thereof, and the domain of binary operators cannot be extended to vectors, contours, or glyphs. Not that we would demand operator overloading unreservedly,but paraphrasing it, as well as the tuple constructors, would make it look like assembly language,which we believenot to be the intention of Oberon. Besides enabling us to express ourselves as concisely as possible, implementing a special font language has another advantage to offer: Since it is going to be a declarativelanguage, a correct problem specification will constitute automatically a correct program - correctness proofs are not required. In a declarative program, we declare the (static) goal to be achieved, but not the (dynamic) process of how to achieve the goal actually. Hence we would never have to ask the question, "why doesnt this code work properly?",since there is nothing supposed to be "working" - it is perfectlyenough to be "existing". In view of its use by the world of type design, we are convinced that this further predisposes the choice of a declarative language.

4.1.3 Outline-orientationvs. image-orientation There is one more question influencing our declarativehigh level language for fonts: Is the language oriented mainly towards manipulating outlines, or chiefly towards processing images? With the availability of outline-fonts in electronic form, although unstructured, and the seemingly simple scalability of outlines, we were predisposed for an outline-oriented approach. The following facts helped us along, at least until just before the end of the project: [Kohen88] concludes, that outline-oriented algorithms are usually fast and simple, while image-oriented algorithms generallyproduce better typographical quality - at the expense of complexityand speed. The image-oriented Raster [Kohen88] program on the Lilith was slow, it could take it hours for rastering a single bitmap-font, yet screen fonts still had to undergo pixel-editing.For comparison purposes, we introduced into our simple outline-font editor for the Ceres-3 a command to scale and fillnaivelyan entire font, which,despite the re-directions introduced by the object-oriented toolbox, can achieve roughly the same results in a matter of seconds! This is an advantage of order 1000. Even though the progress made meanwhile in hardware should contribute a factor of order 10 - and

A Formalism For Intelligent Outline-Fonts

87

the Lilith was not slow for raster graphics - the resulting figure is plain enough. The present projects scope comprised explicitlyto scale the fonts on-thefly, that is, to produce an aesthetically pleasing bitmap-font out of a generic representation within a few seconds. This excludes lengthy processing from the outset. All this goes to show that outline-orientationis the obvious choice. Yet imageorientation, on the fringe(in both its literal and figurativesense!), is not out of place altogether. We will come back to this topic in 4.3.7 and 4.5.

4.2 Semantic implications: The round-before-use rule In 4.0 we convinced ourselves that it is necessary to provide equality, symmetry, and connectivity on the quantization level by dint of concepts. In 4.1 we motivated the choice of a declarative high level language to formulate structured fonts. However, the resulting high level language paradigm relates to font-space and forbids us every means of referring to pixel-space (with the only touch of an exception being the attribute for declaring invariance of existence, cf. 4.0.3, 4.3.1, and 4.4.7) Therefore,unless we want to keep passing the buck to higher and higher levelsof abstraction, we have to elaborate a concise rule on how this semantic gap is to be bridged. Directlytaking up 4.0.1 we have to structure the font into components, in order to make up for the lost linearityin pixel-space.Eventually,we have to assemble these components into entire characters. The crucial point now is to assemble the components in pixel-space.If we do not assemble them in pixelspace, but instead in font-space, we would not need to do components to begin with: Since font-space is unscaled, s(x) is idempotent, from which no loss of linearity results. If we do assemble the components in pixel-space,thus, we start out from components which have been scaled and rounded already,that is, before being used for subsequent assembly. Since all components can be put down to numbers ultimately, we establish the followingrule: All characteristic numbers, affected by the loss of linearity, are subject to rounding before use. This round-before-use rule is considerably more far-reaching than what its simplicity might suggest. Essentially, it means that we are going to define numbers in font-space, while their actual domain is pixel-space.Those readers, who are caught somewhat unawares by this rule, are stronglyrecommended to come back to it after reading sections 4.3 and 4.4, respectively.To give

88

The Backbone:

the prospects for what comes next, in section 4.3 we shall introduce our language, while section 4.4 will show, how the language can formulate both the inherent regularityof a font, and the regularityto be attained dynamicallyunder scaling.

4.3 Formally defined regularity 4.3.0 Components It is undeniably true that if a font-scalingalgorithm cannot render equal parts equally, then it is sufficient to factor out repeated occurrences of the same parts, turn them into components, render the components separately, and equal parts will come out equally. In doing so we use the same template, rather than attempting to raster the glyphs to come out in the same way. On the other hand, we learn from 4.0 that - although sufficient it may appear - it is necessary to structure a font into components. Consequently, we are following the very right path. Here is a remark addressed to all those engineers who have jumped to conclusions. While to them it may be obvious to decompose characters into glyphs (stems, serifs, 4.0.1 should have convinced all of us that more levels ), of structuring are inevitable,simply because the loss of linearity will catch us on all these levels. Disregarding this fact is likely to entail all sorts of stemwidth-, half-serif-width- r serif-thickness-controlfeatures, which we will see to o be obsolete. We are ready to give our taxonomy of components now, together with examples of their notation. The elementary structuring unit is the unsigned, immediate NUMBER. Example: 33 Numbers are defined in font-space, but they have their domain - depending on the actual scaling factor - in pixel-space(cf. 4.2). We construct a KNOT out of two numbers which refer to two orthogonal coordinate directions. Example: [33, 247] Knots may assume dually the role of control-points or vertices, and that of vectors: Every point in plane can be understood as a vector from the origin to

A Formalism For Intelligent Outline-Fonts

89

that point, and vice-versa. A sequence of knots constructs the essential ingredients of polygons and curves.Example: [[0, 0], [33, 0], [33, 247], [0, 247]] Once such a sequence of knots is attributed (cf. 4.3.1), it makes up for a CONTOUR, which is defined to be a part of the boundary of a glyph,or (the boundary of) a GLYPH itself. In other words, contours are one-dimensional entities, whileglyphsare two-dimensionalentities.

4.3.1 Attributes As for the contours and glyphs, we have just mentioned attributes. We introduce them for contours and glyphs to define the transition from a sequence of knots to some specific class of curves. With that, well-definedpolygons, natural splines, or third order Bzier curves result from a sequence of knots, without anything else to be added. Examples: POLYGON [[0, 0], [33, 0], [33, 247], [0, 247]] BEZIER [[30, 0], [7, 0], [0, 8], [0, 30]] Likewise, an attribute distinguishes closed polygons and curves, which may save one knot, and which yieldsdifferent natural splines. Examples: CLOSED POLYGON [[0, 0], [33, 0], [33, 247], [0, 247]] CLOSED SPLINE [[0, 0], [36, 0]] All in all, we have introduced by now three pre-definedcurve classes, and supplements may follow (cf. 4.3.7). But what is an attribute? An attribute is a quality or a distinguishing feature looked upon as necessarily belonging to something. This is an important observation: With an attribute, we define what the component in question is, but not what the computer might have to know about it, nor what it must do with it. At the same time we would like to delimit ourselves clearly from the popular terms hint and instruction. We understand a hint to be an indirect indication that suggests what might be the case. But the persisting vagueness of this term still permits some fact not to be the case, which does not go far enough. On the other hand, the imperative undertone of the term instruction

90

The Backbone:

does not quite blend in with the functional nature of our formal approach. Specifyingthe order in which the operations must be performed by the computer goes too far. As far as the knots are concerned, we did not need any attributes to encapsulate individual intelligence. The knots serve merely as a convenient means of aggregatinga pair of coordinates. If we still were to do so, analogously to the contours, the ensuing notation might look somewhat awkward. Example: POLYGON [ NORMAL [0, 0], SPECIAL [33, 0], UNUSUAL [33, 247], OTHER [0, 247] ] This kind of verboseness we would rather dispense with. Two attributes are defined in conjunction with numbers, then. Picking up the issue of connectivity on pixel-level (cf. 4.0.3), we introduce an attribute that governsthe rounding of critical numbers (cf. 4.2). Example: 30^ With this attribute the affectednumber persists coarse scaling, whileits omission permits its vanishing in pixel-space.In other approaches this is sometimes referred to as non-linear scaling, which relates to replacing the strictly linear function x by a non-linear function (x) to begin with.In our approach we would have,thus, (x) = Max(x, 1) maximizingthe values scaled linearlywith the constant threshold value of 1. x (x)

x linear scaling (left) vs. hyperbolic scaling (right)

Since the transition to the threshold value may be too abrupt, we conceptually

A Formalism For Intelligent Outline-Fonts

91

interpolate linear and constant scaling by a hyperbola,for which we have coined the term hyperbolic scaling [Stamm92a]. In the end, s(x) is just non-linear, anyhow. Before introducing the second attribute for numbers, we would expect the critical reader to come up with two questions. The first question is this: Why should the use of hyperbolic scaling, as an alternative to plain linear scaling, work just like this without entailing any resultant problems? Since s(x) is nonlinear to start with, we had to do components, which are scaled and rounded before they are assembled. In doing so, we abstract from the components genesis. We accept them just the way they have come out, whichevermethod it took to define them in the first place. With the knowledgeof 4.3.2 and 4.3.3 we shall see, furthermore, how both the glyphs themselves and the location of neighboring glyphscan refer to the same vitalspacing information, in order to abstract from placing glyphs next to one another. This explains why we are getting off scot-free. The second question is this: When referringto one unit in device-space,as we do upon maximizingx with 1, does this not mean to give up device-independence? Not really, because this attribute only distinguishes two alternative scaling functions a priori. Even though this 1 refers to device-space,it is welldefined without us making any further contribution, and irrespective of the resolution of the underlying device. All we actually do is to assert minimal dimensions at low resolution, much like rendering hairlines in a device-independent drawingprogram. Yet, these "device-dependentpixels"seem to shine through sometimes, as is illustrated in 4.4.7. The second attribute for numbers, finally,serves to bypass the round-before-use rule, in cases whereit is not necessary to obey it. Example: 7.0 With this attribute the affected number stays rational "before-use":Its main application is the definition of the (off-curve) control-points of Bzier curves. There, the (orthogonal) distances between the off-curve and the on-curve points are not meaningful for further assembly, hence they may remain rational (cf. 4.2). Example: BEZIER [[30^, 0], [7.0, 0], [0, 8.0], [0, 30^]] However, it is pointless to use this "point" attribute to define orthogonal rectangles, such as a plain upright stem. What would it help, if the stem width were scaled to, say, 2.4 units in pixel-space,if later on the digitizerof the stem's

92

The Backbone:

right edge has to round this figure down to 2 units anyhow,but instead, upon each step digitized?

A plain upright stem, scaled to rational (left) and rounded to integer (right) before digitizing:in terms of pixels, both results are equivalent, but "rational scaling" overburdens the rounding decision to each "Manhattan move", while rounding "before-use" requires deciding for the four vertices only. Readers unable to further consolidate their understanding of using these attributes, as in the exampleabove, are kindlyreferred to 4.4.1 and 4.4.4. Incidentally,we were asked more than once whether we could not "digitize without rounding" - a question which we found difficult to understand, at first: In our approach, by digitizingwe mean the process of quantizing a continuous shape into a finite sequence of integer valued "Manhattan moves",while rounding usually denotes the process of quantizing a real or rational number to an integer number; hence "digitizewithout rounding" would translate to "quantize without quantizing", which obviouslyis sheer nonsense. But gradually, we became aware of the fact that the questionable point is to do the quantizing step at a different stage than where, usually, it is done on the way from the continuous shapes (of the generic fonts) to the quantized sets of pixels (of the bit-mapped fonts). Hence "round-before-use"alternativelymay be understood as "quantize at the very right stage" (or what we think to be the "right stage"). Notice, in any case, that for the same reasons permitting us to use hyperbolic scaling instead of plain vanilla scaling, without entailing further difficulties, we can use "rational scaling" as a viablealternative.

A Formalism For Intelligent Outline-Fonts

93

4.3.2 Names & types It would be universallyacknowledged,that names and types are common concepts of high level languages. Identifying with a name the entities which are manipulated, we give an intuitive means of referringto our components. Associating with said entities the notion of a type, the compiler can prevent from impossible applications of the components. In contrast to general purpose high level languages, however, we do not have the necessity to define compound types out of elementary types. This keeps the language simple. Essentially, we use a word symbol to say, what type of component we are about to define, followed by its name, a rhetoric delimiter, and finally the component itself.Examples:
NUMBER

StemWidth = 33^ Height = 247


KNOT

Origin = [0, 0]
CONTOUR

XAxis = POLYGON [[0, 0], [1, 0]] YAxis = POLYGON [[0, 0], [0, 1]]
GLYPH

Stem = CLOSED POLYGON [[0, 0], [StemWidth, 0], [StemWidth, Height], [0, Height]] With the two numbers in this example, we understand how phase-control of reference lines or stem-width-control features are simplified to referencing named numbers. Henceforth, all the fonts characters, which need this particular stem, or which in some way or other depend on the vital distances stem width or cap height, can refer to these components by name. At this point it is well worth clarifyingthat using identifiers is not equivalent to using variables: The components declared in our language are not variables in the sense of imperative programming, since they cannot assume different values during font-scaling.Like this we avoid alterable states or modes of the underlying font machine, which, in the end, would require us to keep track of these modes, if we wanted to understand what the machine does. Rather, a value is bound to a symbol once only, saying what the respective com-

94

The Backbone:

ponent is. This comes close to symbols in algebra or LISP, and thereby fits perfectlythe declarativeor functional programming paradigm.

4.3.3 Instancing transformations Followingon from 4.0.1 and 4.0.2, we need multiple examples of one and the same component, but in different places, orientations, and maybe even in different sizes. Since our components are immutable, we do not transform them immediately. We rather interpret them as templates, if they represent the target to be defined, and we understand them as instances of the respective templates, if they denote the defining source. Upon creating a particular instance from a template, the instance to be may undergo a transformation, or a concatenation thereof, in pixel-space.These instancing transformationsare set out in detail next. The most obvious instancing transformation is the translation, although this may sound a bit ponderous in this context.Example: CrossBar @ [StemWidth, 123] This denotes a (digitized) CrossBar, which has been moved to the right by the same width as the (digitized) Stem, and up by the scaled 123. Whatever the (digitized) contour or glyph to be moved is, it will look the same at the new location. For numbers and knots it is enough to add or subtract another number or knot (cf. 4.3.4). Thus we have provided for invariance of translation also for contours and glyphs. The second instancing transformation is the mirroring. Both the central and the axial mirroring are provided, but since they are carried out entirely in pixel-space,this imposes some obvious restrictions on the center or the axis. Examples: Serif # XAxis Serif # YAxis Serif # Origin These denote a (digitized)Serif, which has been flipped, in turn, upside-down, back-to-front,and simultaneously upside-down and back-to-front.Irrespective of the (digitized) contour or glyph to be mirrored, the result will look the same; of course, up to orientation. Again, for numbers and knots it is enough to apply a unary minus (cf. 4.3.4), and thus we have provided for invariance of

A Formalism For Intelligent Outline-Fonts

95

mirroring also for contours and glyphs. The last instancing transformation, finally, is the scaling. While for contours and glyphs this was not found to be necessary, it serves to measure off numbers and knots proportionally in pixel-space.Example: Height %498 This denotes a number whose value is 498 thousandths of Height. We would prefer the symbol to be "per thousand", but this is not available on an ASCIIkeyboard.Specifying49.8 percent, instead, would cast the integer to a rational number (cf. 4.3.4), whilesupplying just 49 or 50 percent is not precise enough in a font-space that extends over a few hundred units. In any case its main application is to place crossbars and similar glyphs at their correct height, and to ease dynamic regularization of Bziercurves (cf. 4.4.1). Thus, this time it is the numbers and knots that require special treatment of yet another facet of improperly digitized proportions; we anticipate it to be a convenient feature also for the contours and glyphs, though, once we venture to tackle kanji fonts. Two comments are worth being added. First, the order of the instancing transformations is relevant for the contours and glyphs, since the respective isomorphic operators are not commutative: Movingthe component before it is mirrored is not the same as moving it after mirroring it. To eliminate any ambiguity in the ordering, we enforce it syntactically (cf. Appendix C). Last, the set of instancing transformations for contours and glyphs might be extended by further transformations, but since they would have to be carried out entirelyin pixel-space,there may be left none.

4.3.4 Hierarchy Now that we have defined all the ingredients, we can start assembling them. Essentially, a component is either an immediate component, or it is composed of other components, which themselves may be composed of other components, etc.. Beforesetting out what this means in detail for the components introduced in 4.3.0, we point out that this allows for arbitrary recursive depth. On the one hand, this represents one of the aspects of the hierarchy of a character or a font, on the other, it is rather awkward in view of LL[1] syntax analysis. With the components being named, there is a simple solution. We assemble the components in much the same way as numbers are combined to

96

The Backbone:

expressions in Pascal, Modula-2,or Oberon. In terms of EBNF: Expression Component = ["+"|"-"] Component {("+"|"-")Component}. = Immediate | Identifier | "(" Expression ")".

The production Expression is non-recursive,which not only eases syntax analysis, but also makes font definitions more obvious to type designers. By now, defining a component reduces to say that it is a such-and-such with a so-andso here and another one there, etc.. Example: Stem + Serif @ [StemWidth, 0] + Serif # XAxis @ [StemWidth, Height] + Notice that the first optional operator is unary, whilethe subsequent compulsory operators are binary. We proceed with the semantics of these operators. For NUMBERs, the semantics is the one which would be expected trivially, apart from the round-before-userule, of course. Thus - baseCorr + fullHeight- capCorr evaluates to -s( baseCorr) + s(fullHeight) - s(capCorr) preservingadditivityas needed. For KNOTs, the semantics is put down to that of numbers, which leaves nothing more to be said. For CONTOURs, the basic semantics is that of a concatenation, while the sign determines the orientation (cf. 3.1.5 and A.1.3). Thus POLYGON [ + BEZIER [ + POLYGON [ ] ] ]

= denotes a contour with straight ends and a curved middle part, while

A Formalism For Intelligent Outline-Fonts

97

POLYGON [ + SlantedEdge@ [StemWidth, 0] ] - POLYGON [ @ [ - SlantedEdge ] ]

defines a diagonal stem, which depends on two instances of the same slanted edge, but with different orientations, inevitably.For GLYPHs, finally,we adopt the semantics of sets as implemented in modern high-levelprogramming languages. In the end, glyphs may be understood as a set of unit squares or pixels, which can be both painted or erased (cf. 2.2.2). With that, we not only allow to put together the glyphs as expected,but also we allow to cut out holes and to cut off parts that jut out or tower up over the edge of some other component. From this particular semantics for the glyphsoperators ensues a subtlety that might escape a first glance, but which represents nevertheless the result of a good deal of scrutiny. The set difference being asymmetric, the following two compound glyphsfrom the blue-prints of an "N" are not equivalent: Stem = Stem =

Stem + Diagonal=

Stem - Excess=
Stem - Excess+ Diagonal=

Stem + Diagonal- Excess=

Assuming that for some reason or other the diagonal projects beyond the stem, then there is an excess to be cut off, which cannot be done until this very excess arises. Likewise, a unary minus would not make any sense at all: We cannot take something away which was not there before.With scraps of paper, some glue, and a pair of scissors, this is evident, but with sets in a computer program, this is less so. From our own experiences,now, we were tempted to implement a somewhat more symmetric set difference,such that the order of

98

The Backbone:

the operands does not matter. But this would not let us assemble a "W" at all, nor would it let us add serifs to simpler characters like an "A" or an "N" in a straightforward way, i.e. without "piling up" serifs in order to prevent them from being "drowned in the moat" (cf. A.1.3). Staying with the asymmetrical difference,however, means to make a soupon of a concession to imperative programming: to stipulate the order of execution, albeit conventional, is not pure functional programming. We believe, though, that the practical advantages of the cut-and-paste paradigm exceed these merelytheoretical concerns. Before concluding this sub-section, there are a few context rules to be discussed. While language design usually tries to avoid as many of them as possible, this should not be done at the expense of overextravagantsyntax. In our case we could have avoided overloading the basic operators plus and minus in favor of a wealth of symbols - which is why we preferred to impose a context rule that defines type compatibility : Components can be added to/subtracted from components of the same type only. Since the semantics of the basic operators is different for contours and glyphs inherently, we distinguish open contours (or CONTOURs) from closed contours (or GLYPHs) at all. But characters, in the end, are glyphs, and glyphs may be bordered by a concatenation of (open) contours. Hence we impose a context rule that defines type inclusion: Glyphs can be composed either of other glyphs or of a closed sequence of contours. With that, the followingexamples denote legalglyphdeclarations:
GLYPH

Stem = CLOSED POLYGON [[0, 0], [StemWidth, 0], [StemWidth, Height], [0, Height]] Serif = POLYGON [ [0, 34], [0, 0], [30, 0], [30, 4]] + BEZIER [[30, 4], [7, 4], [0, 12], [0, 34]] I = Stem + Serif @ [StemWidth, 0] + Serif # XAxis @ [StemWidth, Height] + Type inclusion represents another aspect of hierarchy: glyphs are on a higher level of abstraction than contours. A final context rule concerns the result of scaled numbers and knots. While the result of an expression of numbers with mixed attributes is evident (they are rounded before use unless the bypass applies), the result of the above example

A Formalism For Intelligent Outline-Fonts

99

Height %498 is not. The chief purpose of this instancing transformation is two-fold. On the one hand, we want to control proportionality in pixel-space,thus expectingthe result of Height %498 to be integer. On the other, we want to scale the Bzier curves' off-curve points by circumventing the round-before-use rule, thus expecting the result to be rational. On top of that, the nature of Height does not determine sufficientlythe nature of the result of Height %498: It may have to be rational irrespective of the attribute of Height. Hence we impose a context rule that defines type hierarchy: The domain of scaled numbers is integer if both operands are integer, else rational. The knots being aggregates of numbers, this rule is generalized easily to knots. With that, we can force the result to be rational, even if the argument to be scaled is not, simply by adding a decimal point Height %498. to the instancing number. At the same time we preserve its intuitive meaning in cases where the argument's attribute corresponds to that of the desired result.

4.3.5 Assembly in pixel-space Although we have mentioned assembly in pixel-spacealready in 4.2, and even though it may have shone through repeatedly since then, we have not shown explicitlywhat this means in terms of using our formalism. By now, we can allocate names to the components, and we can put glyphs in different places. What is left to do is to have both the glyph definitions and their instancing translations refer to the same vitalspacing information. If we do not, the loss of linearitywill strike us again, this time with the (negative) shapes between the glyphs. To see this, we pick up the example of 4.0.1, but we replace the roles of the numbers therein. Let left denote the right edge of the left stem of a "H", and right the left edge of the right stem. Then there is always an integer length, such that length = right - left denotes the length of the crossbar that links the two stems of the "H". Apply-

100

The Backbone:

ing our scaling function, now, we understand that we are likely to get s(length) = s(right - left) s(right) - s(left). As a result, the right stem may or may not be placed correctly.If it is not, it either overlaps the crossbar (the lesser evil: the space between the stems is too narrow), or it leaves out a gap (not permissible at all, and the space is too wide).

Trusting onesluck with assembling a "H" naively The solution is to make the location of the right stem depend on both the width of the left stem and the length of the crossbar. With that, the resulting translation will be the sum of the scaled stem width and the scaled crossbar length - in agreement with the glyphsbeing assembled. Example:
NUMBER

BarWidth= 7 BarLength = 122


GLYPH

Stem = CLOSED POLYGON [ StemWidth, Height ] CrossBar = CLOSED POLYGON [ BarLength, BarWidth ] H = Stem + CrossBar @ [StemWidth, 124] + Stem @ [StemWidth + BarLength, 0]

These considerations impose themselves likewise for all non-trivialglyphs. Yet, we not only solve the more apparent problem of placing the right stem seamlessly, but we also address another issue. Type designers are aware of the fact that the (negative) shapes of the background, the so-called counters, are at least as important as the (positive) glyphs themselves [Bigelow85]. This explains, why the right stem should depend on the length of the crossbar, rather than having the crossbars length determined by the location of the right stem. It is hard to overemphasize the importance of keeping the counters and glyphsbalance in mind for all character definitions to come.

A Formalism For Intelligent Outline-Fonts

101

4.3.6 Scopes The casualness of identifyingcomponents by names is one thing, the obligation to conceive yet another name, whenevermultiple references are made to one and the same template, is another. We may have seen it in the previous example already: On the one hand, both the crossbar itself, and the instancing of the second stem, have to refer to the bar length. On the other hand, this figure is totally irrelevant to the remaining components. For this reason, we may wish to restrict the domain of an identifier to a well-definedpart of our font definition, in accordance with its relevance to the rest of the font definition. Following popular high level languages closely, we introduce scopes to nest the declarations of the components. Example:
GLYPH H NUMBER Length= 122 GLYPH Bar = CLOSED POLYGON [ Length, BarWidth ] BEGIN

Stem + Bar @ [StemWidth, 124] + Stem @ [StemWidth + Length, 0]


END

In this example, we assume that the numbers BarWidth and StemWidth and the glyphStem are components of global importance, and therefore have been declared already. The number Length and the glyph Bar, in comparison, are declared local to the glyph H. In doing so, we limit their domain to the scope H: They can be referenced from within the H itself, but they are invisible to components declared outside the H. We are aware of the fact that scopes do not eliminate ultimately the necessity to conceive names, of course. However, they defuse substantially the burden which might be associated with this assignment. By restricting the domain of the identifiers to named scopes, we make the ponderous qualification of identifiers obsolete: Within its scope, a concise identifier leaves no further expressivenessto be desired. Scopes are well-suited not only for sub-structuring components, but also for providingsuper-structuring. The font scope reconciles common properties of an entire font (Times, Univers, Lucida, Syndor, ). Inside this font scope, the variant scopes bracket the properties shared by the constituent parts of the particular variant (book, medium, bold, italic, Within each variant sco). pe, the character scopes embrace their individual glyphs and the inter-character spacing. The additional syntaxinvolvedwith the introduction of these sco-

102

The Backbone:

pes is simple enough to be postponed to AppendixC legitimately.With that, we have become acquainted with the concept of scopes as an indispensable means of abstraction, and we can see how it fits perfectlythe concept of hierarchy.

4.3.7 Extensibility We take up again the attributes introduced in 4.3.1, in particular the attribute determining the nature of the (pre-defined)curve classes. At a first glance there is hardly any reason to doubt neither the usefulness nor the implementation of straight lines and polygons.Yet there are situations where we would rather have diagonal strokes - with their inevitablyjaggededges - digitizeddifferently. Likewise, after having praised duly the advantages of third order Bzier curves, it may seem rather unintelligible why this curve class should be supplemented by others. Nevertheless,we do not intend to foresee the unpredictable. Therefore,a custom curve class can be added to the pre-defined ones, if the set of pre-defined classes should prove too inflexible,or if the implementation of a pre-definedclass does not yieldthe desired digitizedappearance. The goal targeted thereby is twofold.The first goal is to abridge certain simple but frequent situations in our font definitions. One such situation is the definition of diagonal strokes per se (cf. illustration in 4.3.4). Example:
GLYPH Diagonal KNOT

Slant = [98, Height] Right = [StemWidth, 0]


CONTOUR

Edge = POLYGON [Origin, Slant] Base = POLYGON [Origin, Right]


BEGIN

Base + Edge @ Right - Base @ Slant - Edge


END

The purpose of defining diagonals this way is to have the jagged stair-cases happen in phase on either edge, which is commonly accepted as the preferable way to digitizediagonals. This recurrent situation can be abridged to CLOSED DIAGONAL [[0, 0], [StemWidth, 0], [98 + StemWidth, Height], [98, Height]]

A Formalism For Intelligent Outline-Fonts

103

if such a DIAGONAL can be made available to the font language on demand (cf. AppendixC). The astute reader might object justifiably that this amounts on a contour or glyph with parameters. This is undeniably true. We could think readily of syntactical extensions suitable for components with parameters. The other side of the coin is, however, that this would result in a parameter passing mechanism close to the call-by-namefashion, which at the same time is hard to explain and not simple to implement without disproportionate overhead. Furthermore, parameterized contours and glyphs would mean to have higher order functions: Contours and glyphs are, to a certain extent, nothing but lists of knots, hence a parameterized contour is a function returning a list. On top of that, such diagonals as in the above exampleare likely to occur in many different font definitions. To avoid this unnecessary duplication, we would have to integrate into our font formalism not only parameterized components, but additionally a fully-grown import/export mechanism. This is clearlyone of the things which Modula-2or Oberon are just much better at to begin with. The second goal is to give the possibility to talk also about digitized appearance, rather than about outlines only. Once the contours knots are scaled, and possibly rounded, control is passed to the digitizing,without us exerting any further influence on the process. On the one hand, this is done so for reasons of abstraction, but on the other, it may lead to unfortunate patterns of jaggedness or an aesthetically unsatisfying quantization in general. If certain glyphs are particularly prone to such mishaps, then a curve class - curing these symptoms with a somewhat unorthodox digitizingmethod - can be supplied to the font formalism. In doing so, we can restrict the use of expensive measures to indispensable situations, while enabling the respective imageoriented algorithm to fall back upon the outlines topology.We used this option notably to assert connectivityof curved glyphs with highly varying thicknesses of stroke (cf. 4.5.2). There, the strategyof the algorithm is driven by the sign of the curvature of the original outline. With that, we have another place where we are well advised not to upgrade our font formalism by things which can be realized as well, or maybe even better, in the programming language used for the implementation. There is a final point that deserves being mentioned. So far, we have defined essentially a sequence of control-points plus a curve-class attribute, from which the outline ensued inexorably.Yet, most of the intelligence was in the control-points and their constituent numbers: There is a lot more to it than what we have seen so far (cf. 4.4). With the extensibility made availableon the level of contour classes, we give the opportunity to move some of this overweightback to the contours. Consequently, we should start talking about con-

104

The Backbone:

tour-orientation, then. We strongly believe that not until both control-point orientation and contour-orientation assume roles of comparable importance, we may talk about outline-orientation .

4.4 Implied dynamic regularization 4.4.0 Stern regularity vs. artistic license Let us take a brief inventoryof the preceding sections. In 4.0 we depicted the loss of the implicit regularityto be a consequence of the fundamental raster problem. In 4.1 we decided to provide explicit regularity by defining formally the structure of a font and its characters, hence the term formally defined regularity. In 4.3 we have introduced our language for fonts, permitting to formulate regularityon all levelsof hierarchy,that is, from entire font families down to individual specifications of length. All in all, this may have given rise to the impression that regularity has been overemphasized until now. Indeed, quite often components, which seem to be identical to the untrained observer, unveil subtle nuances upon closer examination. Such divergences from stern regularity express artistic license. Whether or not they can be explained physiologically,such as is the case with optical corrections, they have to be respected by the font-scaler.Yet many of these details inherently cannot be represented by small type sizes, especially at low-resolution.Below a certain limit, a single unit in pixel-space is far too coarse to express this or that tiny difference.Because of that, the degree of regularitymust increase along with decreasing resolution and type size. This transition to small type size at low resolution is called therefore dynamic regularization. In the sections to follow we shall illustrate how we can use our formalism to provideincreasing regularityat smaller and smaller type sizes. Eventhough the reader should be aware of the demands of the regularization of glyphs,often it is not obvious why the respective glyph - when expressed in our formalism - should make such a good-natured transition from high- to low-resolution. It is therefore remarkable, that the few concepts introduced by our formalism are already sufficient to cope with all the phenomena of artistic license, exceptfor the reservations made in 4.3.7 as far as appearance is concerned. Liftingthe veil of secrecy by an inch, the regularityto be attained is contained implicitlyin a correct font definition, hence the term implied dynamic regularization.

A Formalism For Intelligent Outline-Fonts

105

4.4.1 Curved contours Let us start illustrating the use of our font language with the definition of curved contours. It may have become apparent to the alert eye that up until now, we were talking a bit too often about geometrical orthogonality.We have introduced orthogonal rectangles in 2.0.5, while2.0.6 has contributed the notion of rectilinear regions. In 4.3.0 we referred to pairs of orthogonal coordinate directions which are aggregated to points or knots, while 4.3.3 restricted mirrorings, in order to be carried out entirely in pixel-space,essentially to grid-lines and -points. Our entire worldseems to be subdivided into the categories horizontal and vertical, leavingno scope for curves. Appearances are deceptive, though. In 3.2 we stressed the importance to control directly the slope of a curve, while A.3 will contribute a numerical method to obtain Bzier curves whose slopes are either horizontal or vertical at the on-curve points. Now we just have to make use of this very advantage bought dearly.We set out severaldifferent ways to do so next. Apparently, we have to express explicitlythe tangents defined by neighboring pairs of on-curveand off-curve points. Rather than defining naively V3

V0 The standard form BEZIER [V0, V1, V2, V3] for a quadruple of knots V0 V3, we substitute the off-curve points V1 and V2 by V1 = V0 + (V1 - V0) =: V0 + T1 V2 = V3 - (V3 - V2) =: V3 - T2 respectively.This defines tangents T1 and T2, hence the name tangent form for defining a Bziercurve in this particular way.

106

The Backbone:

V3

V0 The tangent form BEZIER [V0, V0 + T1, V3 - T2, V3] Notice that usually T1 and T2 lie on grid-lines,in which case one of the coordinates of T1 and T2 will be 0, while the other one determines the (orthogonal) length of the tangent. Now, in order not to change the topology of the Bzier curve, we should define this length using the round-before-use bypass or the hyperbolic scaling attribute (cf. 4.3.1). If we do not, we allow the tangents to vanish under coarse scaling, which collapses the resulting Bzier curve to a straight line - supposedly not quite what we want. The hyperbolic scaling attribute, in contrast to the round-before-use bypass, tends to push the offcurve points towards an imaginary corner at the intersection of the (conceptual) extension of the tangents. This tendency may or may not be desirable eventually. For another way to define a Bzier curve, we take up the imaginary corner just mentioned and turn it into a real vertex. Denoting this vertex by V, we define C1 and C2 through V0 + T1 + C1 := V V3 - T2 - C2 := V respectively.These vectors complement the tangents, hence the name complementary tangent form for defining a Bzier curve in this particular way. Both tangent forms are roughly equivalent, but complementary tangents stress the role of Vto act as a reference point. In 4.3.1 we gave the example BEZIER [[30^, 0], [7.0, 0], [0, 8.0], [0, 30^]] which now we understand to be formulated in the complementary tangent form by letting V= [0, 0].

A Formalism For Intelligent Outline-Fonts

107

V3

V0 The complementary tangent form BEZIER [V0, V- C1, V+ C2, V3] With this example we see that said imaginary corner need not be declared explicitly.However, it is important to grasp that even if the corner is not explicit, it functions as an implicit reference point, and that this reference point bears an orthogonal relation to the on-curvepoints. The most general way to define a Bzier curve combines the complementary tangent form with the scaling transformation available upon instancing knots. Rather than by additivity,we relate the off-curve points to the on-curve points and the computed additional vertex by proportionality.Thus V1 =: V0 + (V- V0)%Prop1 V2 =: V3 - (V3 - V)%Prop2 defines proportionality factors Prop1 and Prop2 respectively,hence the term proportional tangent form for defining a Bziercurve in this particular way: V3 V

V0 The proportional tangent form BEZIER [V0, V0 + (V- V0)%Prop1, V3 - (V3 - V)%Prop2, V3]

108

The Backbone:

This means that, say, for Prop1 = 500 per thousandths, V1 is exactly half way between V0 and V, whicheverway it took to define V0 and V. V0, V3 and Vpossibly represent vital measures, hence they are subject to the round-before-use rule, whilethe (immediate) Prop1 and Prop2 figures would be casted to rational by adding a decimal point (cf. 4.3.4). The outstanding advantage of this notation may become clear in 4.4.4. only. For the time being we content ourselves with a closing remark: We have gone through all the trouble of defining Bziercurves in alternative ways - using the Bziercurves' degrees of freedom differently- only because of the linearitylost in pixel-space;in font-space these definitions are identical to the naive definition.

4.4.2 Near regularity There are several levelsof divergencefrom stern regularity.On a coarser level, we would classifythe most apparent phenomena of font metrics, such as the mutual proportions of glyphs, followed by the variations of the thickness of stroke, and the shape of the serifs - if there are any at all. On a considerably more fine grained level, we would fit in particularities of slightly differing serifs or bowls, and all kinds of optical corrections. These particularities may not be that apparent at a first glance, yet they represent a vast amount of professional artwork. What all the inconspicuous phenomena have in common is a near miss of the regular measures, hence the term near regularity. The problems with these near misses stem from yet another aspect of lost linearity,that is, s(x + x) s(x) + s(x) x . Associatingwith x the regular measure, and a small difference for x thereof with x, now we understand how subtle nuances at high resolution are enlarged disproportionately at low resolution: This is bound to happen wheneverx is just below a certain quantizing threshold, while (x + x) is still above it. Apparently,the solution consists of making explicit all these intentional but implicit divergences from regularity. Rather than defining the measures involvedby an immediate number, we have said measures refer to the sum of the regular measure plus or minus an optical correction:
NUMBER ActualMeasure = RegularMeasure OpticalCorrection

A Formalism For Intelligent Outline-Fonts

109

and likewise for knots. By dint of the round-before-userule, both RegularMeasure and OpticalCorrection will be scaled and rounded separately. But since OpticalCorrection is much smaller than RegularMeasure, the correction will vanish already at moderate resolution and average type size, whereby the resulting ActualMeasure assumes the regular measure. As with the various alternative ways to define Bziercurves, in font-space this explicitsum is identical to the immediate number - but not in pixel-space.We set out a selection of applications of such compound measures next. Let us start with looking at the dimensions of serifs. In the literature about the state of the art, we find such terms as serif thickness control and half-serif width control [Hersch93]. With our named numbers and the round-beforeuse rule, we can take those things for granted, but now, we can take a step beyond. By relating the half-serifwidth to the stem width, we can avoid the serifs being digitizedin disproportion with the stems.

The half-serifs width relative to that of stems: As designed for the Times font (left), and as regularized for small type size at low-resolution (right) The respectiveformulation in terms of our font language is sketched below:
NUMBER

StemWidth = 33^ HalfSerifWidth= StemWidth - 3 Height = 247 SerifThickness = 4^


GLYPH

Stem = CLOSED POLYGON [ StemWidth, Height ] Serif = CLOSED POLYGON [ HalfSerifWidth SerifThickness , ]

Although simple it may be, we consider this way of controlling stem widths and half-serifwidths a major advantage over competing approaches. In a second application we consider the positioning of glyphs relative to the reference lines (base, mean and cap line). Suppose we position a plain up-

110

The Backbone:

right stem. Clearly, with an appropriate use of named numbers, we can make the stem start exactly on the base line, and extend it precisely up to the cap line. But for curvilinear glyphs,matters are somewhat less straightforward.In order to give our eye the same impression of grayness as would do a rectilinear cross bar on the base line, the central part of a curvilinear glyph hangs slightlybelow the base line, whileits ends remain above.

The position of curvilinear glyphs relative to the reference lines: The lower half of a Times "O", as designed (left), and as regularized for small type size at low-resolution (right) Symmetricallyto overhangingthe base line, a curvilinearglyphovershoots the mean or cap line. Thus, to position curvilinear glyphs correctly,we just make the y-coordinates of the tangents of the respective Bzier curves comprise explicitlythe magnitudes of these overlaps.Example:
KNOT

Start = [0, BaseLine - Overhang] Tangent= [74.0, 0]


CONTOUR

Curvilinear = BEZIER [Start, Start + Tangent, ]

Under sufficientlycoarse scaling this contour will drop its discrete base line overhang and take its mark exactly on the standard base line, without us to intervene any further - it is so simple. In a third application we consider the thickness of stroke of curvilinear glyphs. For reasons similar to those which motivate the reference line overlaps, the maximum width of curvilinear glyphs may exceed that of a plain upright stem, while the minimum thickness may fall below that of a rectilinear cross bar.

A Formalism For Intelligent Outline-Fonts

111

The thickness of stroke of curvilinear glyphs relative to that of rectilinear ones: The first quadrant of a Times "O", overlaid by part of the "H", as designed (left), and as regularized for small type size at low-resolution (right) To get the upper hand thereof, we simply have the spacing of the tangents of the neighboring Bzier curves comprise explicitlythe magnitude of this divergence. Thus, under coarse scaling, the thicknesses of stroke of a curvilinear glyphwill align eventuallywith their regular counterparts. Incidentally,this leads to an area of problems which our method does not even allow to arise. Suppose we did not align the tangents with (integer) coordinate lines, then they might be placed on the grid in such an unfortunate way to be either just above, or right belowa quantizing unit. Like this, the resulting digital boundary either yields isolated or missing pixels, or it allows for long flats.

Trusting onesluck with naive arcs (half of a Times "O", 22 to 25 pt at 72 dpi) In both cases, nota bene, there would not be any obvious thing which we have done wrong, yet the appearance would be wrong definitely.Aligningthe tangents properly, on the other hand, avoids the problem or else the need to de-

112

The Backbone:

phase the arcs [Hersch87] to yieldthe desired discrete shape boundary. By now, the attentive reader may have started to grasp at what a correct font definition is directed. Even though it may seem a bit elaborate, it closely mimics the way a font designer reasons about digital font design: Designinga Times "O" on screen, he does not mean to make the strongest vertical part of the "O" 36 units wide, but a few units wider than the stem, whose width happens to be 33 units. Likewise, he would argue about the weakest horizontal part of the "O", which he does not intend to be 6 units wide, but a single unit narrowerthan the crossbar of width 7 units. Identical reasoning, finally,would lead him to the base line overhangs or the mean and cap line overshoots. To conclude this sub-section, we would like to append an afterthought. All these divergences shown so far represent a kind of delta numbers or knots. They further specifywhat is the value of a particular entity relativeto that of a standard one. On the one hand, this relativity might be fitted into the notion of linearity - somewhere between additivityand proportionality. On the other hand, these delta numbers and knots might be understood as an attempt to linearize the problem of dynamic regularization - to a certain extent like a Taylor series expansion to first order. In 4.4.6 the idea of delta numbers and knots will be picked up again, in view of the possibility to be generalized to contours and glyphs.Be that as it may,it is remarkable in any case that such a gamut of artistic license can be reconciled in such an easy way.

4.4.3 Non proportionality In the illustrations of the preceding subsections it may have shown through already: The spectrum of what dynamic regularization should be in a position to achieve does not just amount to a mere balancing out of inconspicuous divergences. Rather, at the other end of the spectrum, dynamic regularization has to cope with the requirements imposed by small type size at screen resolution. At 72 dpi, the cap height of a 12pp font is only just 8 pixels. For reasons of connectivity- if not so say, sheer decipherability - many regular measures therefore must be exaggeratedseverely under coarse scaling.

The characters "H" and "n", two typical examples from the Times font, 72pp at 300 dpi (left) and 12pp at 72 dpi (right)

A Formalism For Intelligent Outline-Fonts

113

There is a vast difference, which might be hard to surpass: Clearly, at high resolution we can distinguish three classes of serifs. The "n" by itself comes with a head serif which is different from its foot serifs, while these foot serifs are only similar to those of the "H", but not identical. Yet all these serifs are caricatured by a bare pixel at low resolution. Likewise, we can differentiate at least three thicknesses of stroke. The stem widths of the capital and the lowercase letters are not exactly the same, and the cross bar of the "H" is quite a bit thinner than its stems. Still,all these strokes are sketched uniformly. The common effectis that of balancing out components with substantially different dimensions. This is achieved implicitly,that is, without us having to specify explicitlya relation dedicated to make such requests. Even if we did relate the stem width to that of the cross bar, the effectof balancing out would be unavoidable (cf. 4.4.7). To convey an idea thereof, the ratio of 33 : 7 : 4 units in font space, relating to the stem width, the crossbar width, and the serif thickness in turn, ends up as 1 : 1 : 1 in pixel space. The cause of this effect is the prevention of drop-outs, whereby all small but regular measures are subject to hyperbolic scaling. By now, we would agree to know roughly,how and where to apply the respective attribute in our font language correctly, thus we dispense with further explanations. What is important to point out, however, is the fact that along with decreasing resolution and type size, the longer the more measures respond to hyperbolic scaling. With that, an increasing number of glyphs evades proportional scaling, hence the term non proportionality.

4.4.4 Dynamic glyphs In the preceding sub-sections, we have started to distinguish regular measures from small deltas thereof. In order to fulfilthe requirements of dynamic regularization,we have to see to it, on the one hand, that the delta numbers and knots vanish under coarse scaling (cf. 4.4.2). On the other hand, the regular measures must be safeguarded unconditionally against vanishing (cf. 4.4.3). Therefore,here we investigate on some of the effects on the glyphs when reconciling these two contrary tendencies with one another. We start out with the foot serifs of capital or lower case letters. Theircurved parts might be understood as assuming a role similar to the one of sealing up joints. Under scaling, these sealings behave as if they were soaked up by said joint, along with decreasing type size, and along with increasing disproportion of the joint itself.

114

The Backbone:

a sequence of Times "H" (72 pt at 300 dpi down to 12 pt at 72 dpi) In our font language,we imagine the serifs to be built out of a rectilinear base plate, on top of which the sealing material is piled up. Clearly, the base plate is defined by a polygon in such a way that the serifsthickness persists coarse scaling. For the sealing material we use the complementary tangent form of a Bzier curve, pinning down one of its ends at the end of the base plate, and hooking the other one into some point at the stem. Hook

Pin

Assembling a serif: A Bzier curve in the complementary tangent form seals the joint between the base plate and the stem With that, the remaining (off-curve) points are free to take their marks, respectively, anywhere at the upper edge of the base plate, and somewhere at that part of the stems edge belowsaid hook. Hook Hook

Pin

Pin

The off-curve points stay on top of the base plate and the right of the stem in the original design (left), and under dynamic regularization (right) Here is the corresponding blue-print:

A Formalism For Intelligent Outline-Fonts

115

GLYPH Serif NUMBER

SerifHeight = 30^
KNOT

Pin = [HalfSerifWidth SerifThickness] , Hook = [0, SerifThickness + SerifHeight]


BEGIN

POLYGON [Hook, [0, 0], [HalfSerifWidth 0], Pin] + , BEZIER [Pin, [7.0, SerifThickness], [0, SerifThickness + 8.0], Hook]
END

assuming the same HalfSerifWidthand SerifThickness as introduced in one of the preceding sub-sections. Matters are not that much different with glyphs of more general curvilinearity. For the topological equivalent of a quarter of a circle, i.e. a quadrant, we employ essentially a pair of concentric Bzier curves in one of their tangent forms. The idea behind this is to hook the tangents into pairs of horizontally or verticallyadjacent guard-rails. Much like with ordinary railroads, a pair of rails introduces a gauge. This gauge corresponds to the maximum or the minimum thickness of stroke, whose optical corrections we have become acquainted with in 4.4.2 already.We would thus knowhow to define this correctly. Now, if we use in particular the proportional tangent form, its computed additional vertex coincides with the intersection of a pair of orthogonal guardrails. capline

A pair of guard-rails, intersecting at the additional vertex of Bzier curves in the proportional tangent form, overlaid over the original design of the first quadrant of a Times "o" (left) and a regularized variant thereof (right) With that, we can reduce the topological equivalent of a full circle to its circumscribed orthogonal rectangle, i.e. its rectilinear convex hull, plus a couple of percentage figures. To do so, we just have to relate the remaining on-curve points to successive pairs of computed vertices by another scaling instancing

116

The Backbone:

transformation, too. With two such convex hulls, the outer of which having the base and mean line as its guard-rails, we can define the topological equivalent of a an entire circle,such as the "o" below:

The topological equivalentof a circle is reduced to a pair of orthogonal rectangles (left) which act as guard-rails under dynamic regularization (right) Along with decreasing device resolution and targeted type-size, this definition yields an increasingly regularized pair of outlines, which makes subsequent digitizingand rendering of curvilinearglyphsa straightforwardtask:

a sequence of Times "o" (72 pt at 300 dpi down to 12 pt at 72 dpi)

the same Times "o", enlarged, with grid (36, 24, 18, and 12 pt at 72 dpi)

A Formalism For Intelligent Outline-Fonts

117

Betweenthin (horizontal) and thick (vertical)strokes we observe the same balancing out as with the "H" in the previous sub-section. On top of that, slightly opposing inclinations of the inner and outer contours are brought eventually in a fullyupright position. Thus, if we have understood the dynamic regularization of sets of orthogonal rectangles, and if we keep in mind the Bziercurves' convex hull property, we can imagine how to define a whole gamut of curved contours and glyphs. Sometimes, the role of the computed vertex takes a step beyond being hooked on reference lines. This is the case with ears or tails, which start or end neither horizontally nor verticallyat high resolution, but which do so at low resolution.

a sequence of Times "ft" (72 pt at 300 dpi down to 12 pt at 72 dpi) Here, said vertex seems to glide on the respective reference line with an intended tendency to the right, along with the glyph's start or end being allowed to reach out for the reference line. As a result, the tail of the "t" almost falls flat on its face, whilethe "f" suffers similar attraction by the ascender line. In the examples we have seen now, the respective glyphs had to undergo substantial deformations as a result of coarse scaling, hence the term dynamic glyphs. But intuitively,these metamorphosesare actually obvious: Once we have determined that the verticalstroke of the "o" can be represented best by two pixels, for a giventype size and at a givenresolution, and once we know that the horizontal stroke has to be represented by one pixel, then we would wish naturally a continuous transition in-between("continuous" with respect to the given quantization, of course). This is precisely what we do with our Bziercurves when used as described in here.

4.4.5 Snapping into regularity In the previous sub-section we have seen glyphs which sort of uncurl along with decreasing resolution and type size. These particular glyphs eventually align with the respective reference lines. In general, we observe that more and more measures respond to being compound out of a regular measure and a delta thereof, hence the expression of snapping into regularity. In order not to

118

The Backbone:

lose control of this tendency, we just have to define correctly,at what it is to be directed. Here are a few examples showinghow to do so. In an earlier example we have met already a Times "n" at two highly different scaling factors. At high resolution, its head serif is inclined slightly, mimicking an upstroke of a broad-nibbed pen. At low resolution, the final state is more like a short horizontal line, right underneath the mean line.

a sequence of Times "n" (72 pt at 300 dpi down to 12 pt at 72 dpi) Much like with the foot serif illustrated earlier, we achieve this by sealing conceptually a joint. Here, the role of the base plate is assumed by an inclined parallelogram,whose upper edge is defined relativeto the mean line. The sealing curvestangents are aligned with the lower edge of the parallelogram, and the stem, respectively.The serif thickness, finally,is defined by the distance of the lower and the upper edge. mean line

Setting about the head serif of a Times "n" and the like Much like with the effects of near regularity, this is actually nothing but an intuitive way to define such a head serif: Its upper right part is just a little bit above the mean line, while the lower left part stays below - plainly an optical correction as others before (cf. AppendixD for further details). Sometimes, we may have to engineer ourselves a little luck. Lookingat the three cross-bars of an "E", for instance, we may want the middle cross-bar to take second place always.

A Formalism For Intelligent Outline-Fonts

119

a sequence of Times "E" as outlines of increasing regularity (above), and rendered (below) (72 pt at 300 dpi down to 12 pt at 72 dpi) The knack here is to prevent the length of the "E"snose, i.e. its middle cross bar, from aligning with the lengths of the "E"sarms, i.e. the top and the bottom cross bar. Obviously,we do so by using hyperbolic scaling, but this time we apply it to a part of a counter, rather than to a glyph. An example of particular finesse is that of diagonal stems. At low resolution, we want its jaggiesrastered in phase, whileat high resolution, we may have to represent a certain degree of tapering towards its upper end.

a sequence of Times "A" and "V" (72 pt at 300 dpi down to 9 pt at 72 dpi) We have discussed in 4.3.7 already, how to provide phase control for the diagonalsjaggies. Now we just have to understand the tapering as an optical correction relativeto the regular diagonal. Needless to say that a correctlydefined diagonal relates its width at its bottom and at its top to that of a regular diagonal or a plain upright stem. As a result, our method yieldsdynamic dropping of the discrete tapering, along with snapping the jaggiesinto phase. We close with an example which may be quite a challenge to the novice, but which by now we can base on dynamic glyphs snapping into regularity. Clearly, the circular head of the "g" is defined much like an "o", and likewise, the tail and the U-shaped junction. (The complete definition of the "g" is included in AppendixD.) Under coarse scaling, these glyphs will assume their simplest and most regular form dynamically.The overlappingof the "g"snose at the top is kept from vanishing like the different lengths of the "E"scross

120

The Backbone:

bars, which is why the nose will always stick out. Remains to define the leftmost tangents of the head, the tail, and the U-turn relativeto each other. With that, they will snap into verticalalignment - just like this.

a sequence of Times "g" (72 pt at 300 dpi down to 12 pt at 72 dpi)

4.4.6 Well-behaved degeneration The veil of secrecy is lifted fully now. With not exactly small a number of examples, we have intended to illustrate different aspects of dynamic regularization under coarse scaling. Yet, the gamut of facets may be comprehensive enough in order not to tell the method for the facets anymore. Let us try to subject it to recapitulation on a somewhat more global level. We would like to understand the metamorphosis, which the characters outlines undergo under implied dynamic regularization, as a family of curves. Each member of this familyis determined by its scaling factor, which by itself stems from the targeted resolution and type size. Sweepingthrough the scaling factors, the coarse factor defining the regularizedcase, we simplifya particular character from all its beauty down to a skeleton of topology,sketched at a unit thickness of stroke. On the way to this skeleton, many degrees of artistic freedom have to be dropped, one by one. In consideration of our past, we cannot help but relate this to quantum physics. There, degrees of freedom (energy)are frozen along with decreasing temperature. Conversely, we would understand increasingly fine grained scaling as external perturbations, suitable to lift degenerate eigenstates as does applying an electromagnetic field in a Stern-Gerlachtype experiment. In font-speak, this amounts on adding more and more details to the bare skeleton, along with increasing resolution and type size. Be that as it may, the way to said skeleton was not paved with as much straight linearity as that. The initial experiences with splines - even though they are called natural splines - were quite embarrassing, actually; with misleading consequences: If we are in for talking about delta numbers and knots, we should also be in for talking about delta contours and glyphs. The intention to do so was linked to two different goals. On the one hand, it is appeal-

A Formalism For Intelligent Outline-Fonts

121

ing academically to provide orthogonality, that is, to combine the delta concept with all levels of abstraction defined by our components. On the other hand, it might have permitted an intuitive means to say that this contour is related to that one by this inconspicuous difference.But since our approach, in the end, is control-point oriented, this would have restricted relativityof contours to relativityof knots - nota bene, an identical number thereof. Yet, on these premises, we would have wanted to say that, for instance, this belly of a "P" and that belly of an "R" both inherit their fundamental behavior from a more abstract belly. As a result, we might have advocated an object-oriented functional formalism for hierarchical outline-fonts - what a weird crossbreed! Within these perspectives, we happened to make our experiences with adroitly defined Bziercurves just at the right time. Their convex hull property and the invariance of their topology, together with the tangent form and the round-before-use rule, provide for well-behaved degeneration in an amazingly natural way. With that, we have dispensed with fancy features, making the intelligent outlines as simple as possible, but not any simpler. We are convinced that this is the major novelty of our approach. While conceived originally for low-resolutionfont-scaling,the resulting dynamic regularization of intelligent outline-fontsmay find its application as well in medium- and high-resolution font-scaling,as concluded brieflyin [Stamm93b].

4.4.7 The lower limit To conclude this section, we would like to plumb the depths of the lower limit. To our understanding of the problem, the theoretical lower limit of our approach is the point at which we cannot represent anymore the character's original topology with the given pixel size. If the pixels are too coarse to leave out the holes in characters like "o" or "g", that is, if we cannot even define the respective set of pixels manually, then there is no point in attempting to invent an algorithm which does so automatically. To give an impression of the extent of the problem, we show the results of our approach for small print at low resolution:

a sequence of Times "go", from 12 pt down to 6 pt at 72 dpi (original size) Clearly, already the 12pp "o" has lost all its artistic peculiarities: The thick-

122

The Backbone:

ness of stroke is balanced out completely, while the inclinations of the inner and the outer ovals have been brought into a fully upright position. But the "o" remains sort of an annulus down to 6pp. In contrast, the "g" preserves its topologyonly down to 10pp: At 8pp, its U-turn clicks its heels, and at 6pp, its head collapses to a solid spot. In the followingexample, this leads to total indistinguishability of the respectivecharacters, and with that, the unreadability of small print on screen:

a sequence of Times "es" (same type sizes as above, original size) Thus, at the lowest limit, both the "e" and the "s" are but a solid rectangle. In other words, we might say that dynamic regularization is powerfulenough to regularizeeven different topologies. Of course, we could apply increasingly the hyperbolic scaling attribute to the counters, too, rather than to the glyphs only,such as to assert persistence of the holes. However, this would not shift the lower limit: What is the point of being able to represent a 6pp "H" with the same topology as the one at 9pp or at 12pp, if at the same time this also corresponds to the same font-heightas the one at 9pp or at 12pp? In doing so, we would but fool ourselves. Remains to mention, that at the lower limit we start becoming able to make out the pixels"by touching" the regularizedoutlines.

Regularized outlines of Times "H" (72 pt at 72 dpi down to 6 pt) Although we cannot reallysee the pixels, we can tell roughlywhere they are going to be after digitizingand rendering - they are showing off. This may be understood as a restriction to device-independence,as pointed out in 4.3.1, but since we cannot do it any better by hand, this restriction would be of purely academic interest. Be that as it may, we hope to have illustrated clearlythat at the lower limit it is not that much of an overemphasis to talk predominantly about regularitycomprised implicitlyin a correct font definition.

A Formalism For Intelligent Outline-Fonts

123

4.5 Steps beyond contours 4.5.0 Jaggies on the fringe In the preceding two sections, we have elucidated in great detail what a good font representation should contain in order to express a fonts inherent regularity, and why at the same time this is sufficient to formulate the regularityto be attained under coarse scaling. Regardingthe automatic generation of bitmapped characters out of a generic font description, this is about all we can do on a high level of abstraction (cf. 4.1), and particularly,on the level of contours (cf. 4.3.7). Subsequently, the regularizedcontours have to be digitizedand filled.With the legacy of our versatile graphical toolbox (cf. Chapter 2), we might not expect this to entail further problems, but appearances are deceptive. When scan-converted, the boundaries of continuous graphical objects often look much like irregular staircases, rather than smooth contours.

a steep line (left) and the first quadrant of a circle (right), partly digitized Inherently, this jaggedness cannot be avoided, but neither should it be ignored altogether. The psychological barrier to be surmounted is probably once more the somewhat misleading term quantization error. It is not an error on its own to quantize continuous contours into sequences of "Manhattan moves". On the contrary, we would consider our scan-convertingalgorithms to be correct with respect to the rules of the engineering profession (cf. A.0). But some patterns of "Manhattan moves" are maybe less pleasing to the eye than others (their mathematical correctness notwithstanding), in which case we may wish to - if not to say, have to (cf. 4.5.2) - find alternative patterns. Therefore,this section introduces three areas of problems occurring on the fringe of glyphs, it suggests solutions that take us beyond bare contours, and it recommends inte-

124

The Backbone:

grating these solutions into the font-scaler by profiting from the extensibility of our font-scaler.(Further applications of extensibility,particularly regarding font-technologyintegration, may be found in [Stamm93a].)

4.5.1 Half-bitting The first area of problems on the fringe regards the aesthetics of the jagged edges. Sometimes the visibleeffects of jaggedness may be reduced without increasing the effective resolution. One way to do so is to use so-called half-bitting, a variant of anti-aliasingemployed on bi-level printers:

A diagonal stroke at 300 dpi, filled with (left) and without (right) half-bitting

A sector of the above diagonals (generously enlarged) For the human eye half-bittinghas the desired effectif the resolution is already high enough such that one cannot tell apart individual pixels easily, while remaining aware of the quantization at all. Commercial laser printers with a typical resolution of 300 to 600 dpi are primary candidates for this technique. Furthermore, toner dispersion assists the desired effect. On screens, though, the pixelsare too coarse for this technique, there are too few pixelsmaking up for a character, and the pixels do not disperse, while on high resolution printers ( 1200 dpi) half-bittingmay not be that indispensable. Hence, the application of half-bittingmay be restricted to medium resolution printers. The technique of half-bittingis not new at all, but an extensible font-scaler can encourage its integration. The idea is to package the respective intelligence into a tool that can be used either as a post-processor to the digitizable objects, or as pre-processor to the rendering tools (cf. 2.2.1sq).

A Formalism For Intelligent Outline-Fonts

125

What is produced by the digitizer...

...can be filled with half-bitting...

...if the digitized boundaries are transformed accordingly. The task of this tool is to record the most recent couple of "Manhattan moves". This information lead can be analyzed for predominant directions that are disrupted only by a single unit step across. Such elementary configurations are transformed into pieces of digital boundaries that reflect half-bitting. The transformed pieces have this step across advanced, followed by a step back, and finallyanother one across:

a single step across on a straight piece of the digital boundary (left) is replaced by one (middle) or more (right) meander(s) Subsequently, the transformed "Manhattan moves" are forwarded to the actual rendering tool in charge of the filling. Significant advantages result from the extensibilityof our font-scaler. The half-bitting mechanism is completely decoupled from both the digitizingand the actual rendering. Therefore, neither the scan-converting algorithms nor the fillingalgorithm are subject to change. Furthermore, it can be restricted to printers on which this kind of appearance-oriented filling is most effective, while avoiding it on low-resolution screens. Much like the scaling factor, the rendering tool is becoming an interchangeable parameter of the font-scaler.

126

The Backbone:

4.5.2 Drop-outs & non-standardscan-conversion In a second area of problems we would like to draw the attention on what can happen if two adjacent edges of glyphs are digitized independently of each other. As an example, we pick up diagonal stems, such as discussed in 4.3.7 already.

A sequence of diagonal stems, showing uneven thicknesses (third from the left) and drop-outs (third and second from the right) (24 pt down to 6 pt at 72 dpi) Even though the respective attribute safe-guards the stem widths against vanishing (on the level of the outlines), and even though the edges are perfectly parallel at low-resolutionand small type sizes, the resulting diagonal stems may show uneven thicknesses of stroke or missing pixels (so-called dropouts). The reason for this mishap is a subtle asymmetry inherent to Bresenham-type algorithms and has given reason enough to make symmetry a concept (cf. 4.0.2). When digitizing,the algorithm may have to decide what to do in case the continuous line passes exactly through the center of the pixel.

continuous line passes through the very pixel center

Doing so "down the left edge" and "up the right edge", the resulting sequence of "Manhattan moves" appear not to be the same (they are, of course, but only up to mirroring at a point). When putting these two continuous edges together, the respective "Manhattan moves" will cause an uneven stroke thickness or a drop-out, even though the minimal distance of 1 unit in pixel space is asserted between the continuous edges.

A Formalism For Intelligent Outline-Fonts

127

The "explanation"for uneven stroke thickness and drop-outs The interested reader is encouraged to comprehend that neither uneven stroke thicknesses, nor drop-outs, are consequences of computing pixel-boundaries, rather than pixelsthemselves (cf. 2.0.6) The solution is easy for diagonal stems, we just have to see to it that both the left and the right edge refer to the same edge template, and thereby repeat exactly the same pattern of jaggedness in parallel (cf. 4.3.7). But the solution is not always as easy as that for curvilinear glyphs.Much like simple straight lines also the curved edges may pass exactly through the center of a pixel, in which case the digitizing algorithm must make a decision which - strictly speaking - is undecidable. We do not even have to fetch the problems from that far for curvilinears, though. The attribute for hyperbolic scaling (cf. 4.3.1) can assert that under coarse scaling the distance between two knots does not fall below one unit in pixel-space.However, it cannot guarantee a minimal distance between any two points on neighboring contours, unless incidentally there are knots at these two points as well.

The regularized outlines of a Times "g", regularized for 24 pt at 72 dpi (left): At the bottom right part of the gs "loop", the outlines are getting closer than 1 unit in pixel-space, resulting in drop-outs (right)

128

The Backbone:

The pixel boundaries of the outer (left) and the inner (middle) contour of the gs "loop", their super-position showing where the pixels will miss (right)

A sequence of Times "eg", scaled from 72 pt down to 6 pt at 72 dpi, showing drop-outs at intermediatetype sizes (original size) Clearly, missing pixelsare intolerable, and equally clearly, a generic font representation prohibits whatevermeans of referringto pixels directly.Thus, a mechanism is needed to serve as a stop gap for such glyphs. Unlike TrueTypes exception instructions[Apple90], which in the end even permit to patch single pixels deliberately into the rastered glyphs, this mechanism should depend neither on a particular type size, nor on a givenresolution. Furthermore, it is important to restrict this measure to those glyphs which need it, and avoid burdening the other glyphs with it, too. Obviously, we cannot simply use a pair of equal "stairs" and space them apart by one unit in pixel-space,as was the case with diagonal stems. Hence, this is an ideal case for making use of the extensibilityof the font-scaler, and in particular of the contour-independence, with non-standardscan-conversion. The idea is to provide a contour class which consists of a pair of Bzier curves by itself, the inner and the outer outline of a curvilinear glyph. With that, the contour class has the knowledgethat the two outlines are related to one another. This contour class makes use of the standard scan-converterfor Bziercurves, but instead of serializingthe vertices of the scan-converted rectilinear region into the renderer, it records these digitized coordinate pairs. With that, the scan-converter can determine the locations at which pixels will be missing after rendering (filling).Since conceptually this is carried out in pixel-space,it realizes effectively a simple form of image processing. It means that at this point the scan-converterknows about both the digitized and the original shape of the glyph.This allowsto package into the particu-

A Formalism For Intelligent Outline-Fonts

129

lar contour class the intelligence to fix the resulting digitized image in accordance with the original shape of the contour. Subsequently, the digitizedcoordinate pairs are serialized into the renderer. The resulting drop-out control mechanism does not have to rely on a good guess as to which of two verticallyor horizontally adjacent pixels to use as a stop gap. Rather, it can fall back upon the original outlines topology.

A strategy to lift drop-outs: if the orientation of two subsequent "Manhattan moves" around a "corner" (middle) is unlike the orientation of the original outline (left), the respective "corner" ( ) is "flipped"( )

If the drop-out prevention mechanism had to make its decision, where to add in the missing pixels based upon the pixels (or their boundaries) only (left), it may (middle) or may not (right) come to the same solution

The same sequence of Times "eg" as above, applying intelligent drop-out prevention Togetherwith the option to restrict the measure to absolutely necessary places, we would consider this particular use of extensibilityto constitute a substantial advantage over competing approaches.

130

The Backbone:

4.5.3 Device independence& single pixels In a third area of problems, finally, we mention unfortunate quantizations with not quite as dramatic consequences as in the last sub-section, but which nevertheless distort the appearance of the glyphs at issue, and which - in terms of pixels - could be done better. Much like unfortunate quantization can cause missing pixels, it can as well produce extra pixels:

Althoughthe scan-conversion of the curve near the serifs is correct (left), we may wish the extra pixels not to be there (right) (Times "s", 20 pt at 72 dpi) The situation is a bit frustrating, indeed, now that we have gone through all the trouble of structuring a font in order to preserve regularityunder scaling, and of dynamicallyregularizinga font in order to adapt the outlines to a particular grid and for a particular type-size. It makes clear that an approach which purely relies on knots and contours - adroit Bzier curves notwithstanding cannot give the complete answer to low-resolution font-scaling,for it cannot talk about the appearance of the digitized contours. Fortunately,our approach is extensible,which in this case again lets us formulate a somewhat unorthodox digitizing method. Upon digitizingthe ends of the curved part of the above "s", this method would make sure to "punch out" the "pixel" at issue - the resolution and type-size permitting - in order to let the serifs contrast with the ends of curved part. Although this may sound like tinkering with the pixels, it is about what the type designer would do when tuning the pixel-fontsby hand. At this point we would like to stress that we cannot (and do not want to) realize total control over each and every pixel, for this is against the high-level

A Formalism For Intelligent Outline-Fonts

131

paradigm. If we should still wish to do so, e.g. for defining icons and the like, we can but use a pixel-fonteditor. Yet the past examples have illustrated, that there are things which can be specified in terms of pixels only. If we succeed in finding a rule, which particular patterns of the digital boundary should be replaced by what other patterns, then we have done it. To recapitulate, we point out that in this section we have presented one of the aspects of hybridness of our approach: Although it is basically outline-oriented, cases which cannot be coveredon that level of abstraction are "caught" on the level of image-orientation,but without referringto single pixelsdirectly. Some of these pixel-level issues vary from glyph to glyph,some apply universally to all glyphs. We consider this an outstanding achievement of our approach to be enabled to address pixel-level issues in a device independent way on either side of the separation line between the digitizingand the rendering.

4.6 Inter-character spacing & "wysiwyg"type-setting 4.6.0 From characters to words By way of transition, let us make a brief inventory of the preceding sections. The obvious consequence of the fundamental raster problem, the loss of regularity(equality,symmetry),is coveredby decomposing characters into components (glyph, contour, knot, number) and safeguarding their existence (thus asserting connectivity).Localaspects of dynamic regularization (optical corrections, snapping) are taken care of by explicitlyincluding in the characters blue-prints any divergence from stern regularity, and by rounding the blue-prints parts before they are assembled. Global aspects of dynamic regularization (caricaturing, balancing out) are a consequence of safeguarding components against disappearance; they are realized by preserving proportions within the constraints imposed by the safeguards. Finally, topics from appearance of the digitized contours to customized methods acting as a stopgap against drop-outs, are subsumed under the keyword extensibility. In order to use these fonts for typesetting, we have to compose words out of individual characters now. To do so, however, we cannot string together the respectivecharacters "just like that":

composingwords by placing the characters bounding boxes next to one another

132

The Backbone:

Even though the individual characters look nice, with respect to the given solution, the resulting type does not. The characters are too close together. It does not help if the characters bounding boxes are "simply" spaced out. Although adding the same amount of space between the characters may make it somewhat easier to read, the setting is far from lookingorderly. From the naive setting we see that the inter-character spacing depends on the shapes of the characters at issue. If the characters left or right side is rectilinear, spacing should be a little wider, particularlyin a sans-serif typeface,

characters with vertical sides need wider spacing: Times (left) and Syntax (right) or else the type looks (far) too tight. In contrast, if the characters sides are slanted, spacing should be a little narrower, if not to say, negativein case of a serif typeface,

characters with diagonal sides need narrower spacing: Times (left) and Syntax (right) or else the type looks too loose. Finally,if the characters sides are curvilinear, spacing should assume values in between for the type to look orderly.

characters with rounded sides need average spacing: Times (left) and Syntax (right)

A Formalism For Intelligent Outline-Fonts

133

4.6.1 Left and right side-bearings Adivision into simple categories such as the ones in the previous sub-section is nowhere near sufficient,but it may give an intuitive idea of what is required. Of course, if a character is e.g. rounded on one side, and verticalon the other, the respective categories apply separately to the characters sides. Still, the characters are shaped more individuallythan that, hence the amount of space to be added (or subtracted) on either side of a character should be an individual figure as well. Therefore, these figures can be specified by the type-designer in the outline-font editor (cf. 3.1.6, auxiliary lines). They are called left and right side-bearing.

some characters with their bounding boxes (left) and side bearings (right)

composingwords by placing the characters side-bearings next to one another The side-bearings are figures that are defined relative to the bounding box of the character. This definition fits perfectlyinto our approach that decomposes fonts and characters into components. All that is left to be done is to understand both the character itself and the two side-bearings as components. With that, composing a word has become equivalent to assembling the word out of character glyphs, using the instancing transformation for the translation. Example:
GLYPH Word =

W @ [WleftSidebearing 0] + , o @ [WleftSidebearing+ WbodyWidth+ WrightSidebearing + oLeftSidebearing, 0] + The reader is encouraged to understand the problem of composing words this

134

The Backbone:

way (cf. 4.2), although the functional nature of the font language causes this to look far too elaborate (already upon adding the second character to the composition). It goes without sayingthat we do not expect type-settersto compose words like this. Rather, the syntaxof the font language allows expressing the characters together with their side-bearings.Example:
CHARACTER W[WleftSidebearing WrightSidebearing] = W ,

In doing so, the font-scaler can store the scaled side-bearings and the scaled characters together in the same raster-font. With that, composing words (and eventuallylines and paragraphs) is overburdened to the type-settingprogram, wherethis task actually belongs (cf. 4.6.3). This simple solution to inter-character spacing has the advantage not to depend any further on the "internals" of the respective glyph definition. This does not exclude that both the glyph and its side-bearings depend on the same basic figures, such as an optical correction. Actually, the side-bearings may be expressions of numbers. In particular, this generalityallows the sidebearings to be safeguarded against their vanishing, applying the hyperbolic scaling attribute, and to prevent nearly equal side-bearings from being scaled to different discrete values. This assists keeping the inter-character spacing "correct"under coarse scaling much like with the characters counterforms.

4.6.2 Pair-kerning To whomsoeverthe chosen solution seems somewhat overly simple, and therefore has serious doubts about it, we can but admit to be right. Actually, the distance between two characters may depend simultaneously on the particular shapes of both characters at issue. In doing so we understand the character spacing to assume a role (regarding the entire word) similar to that of the counterform (regarding a single character). Therefore,in addition to the sidebearings, more sophisticated type-setting programs may wish to correct the spacing obtained through the side-bearings by what is called kerning. Kerning denotes the process of increasing or decreasing the spacing obtained through the side-bearings by a small amount that depends on both shapes involved. Ideally, this means to provide a two-dimensional table that specifies the amount of correction for each possible pair of characters. This is called therefore pair-kerning. It is clear that the demand for 216 pairs exhausts manual specification. An

A Formalism For Intelligent Outline-Fonts

135

idea how to determine inter-character spacing automatically would be to fit a rubber band around the character and compute the space between two characters as a function of the abilityof the rubber band to give way. Chances are that like this different character styles could be taken into account, such as e.g. Syntax vs. Helvetica.Informally,e.g. a Syntax "C" looks more like the left half of an "O" (it is said to be open), while a Helvetica"C" almost constitutes an "O" (for it is rather closed). Optically,the counter of the (open) Syntax "C" contributes to the inter-character space. This fact might be modeled by the rubber band, since around the Syntax "C" it would give way by a largeramount than around the Helvetica"C". Accordingly, the pair "Ch" could be set slightly narrowerin Syntax than in Helvetica. Similar ideas have been developed by [Btrisey93 and brought to great de] tail. But already in view of a complete solution to on-the-flypair-kerningintroduced in [Karow92a], we did not pursue the issue any further. The idea is explained to some detail in [Karow92b], but - tough luck for the inquisitive researcher - not quite to the same level of detail as in [Btrisey93 Basically, ]. the amount of space between characters is modeled by a viscous fluid that is allowed to immerse between the characters. Depending on the shape of the character, more or less of it will leak into the counterforms: The more these counterforms are shielded against the outside, the less fluid will immerse, and vice-versa. If the properties of this fluid are modeled skillfully, the amount of fluid can be related to the amount of space between the characters. The results of this approach set the standards regarding inter-character spacing on a rather high level!

4.6.3 "Wysiwyg" type-setting We conclude this section with considerations regarding scalable fonts and "wysiwyg" type-settingfrom a font-scaler's "point of view", in hopes that this is of any use to future implementers of respective programs. To make type-setting deviceindependent within a document editor, [Vetterli91] has introduced a model space for documents. Much like our font-space, this model space is an integer space, with a resolution of 36'000 units per mm (or 914'400 units per inch). To the best of our recollection, this particular choice originates in [Schr] and is now widely used in the Oberon system [GutknechtWirth92]. Two main objectives are tackled with this particular choice for document coordinates: The resolution is high enough to specify (fractions of) units in device space even for devices with a resolution of 200 dots per mm: 1/36'000 mm

136

The Backbone:

(27.8 nm) is way belowthe wavelengthof visiblelight (400 nm). Yet, at 800 this resolution, a 32-bitinteger can cover a range of over 119 m (130 yd). Many common device resolutions (both metric and English) can be modeled exactly in this model space: 914400(= 253252127) units per inch divide by 72, 96, or 300 dots per inch, as well as 50 or 100 dots per mm. This allowsfor a certain degree of bi-directionalitybetween device(i.e. window)coordinates and model coordinates. With these premises, and with fonts that are scaled systematically,we would not expect major problems anymore to implement a document editor that lets us see the text composed on screen the way we will get it from the printer ("whatyou see is what you get"). We know from 4.6.1 that words have to be composed in device resolution out of characters, for the words form an integrated whole which should be kept invariant under scaling. The widths of the scaled characters will differ from the scaled widths due to the linearity lost under scaling. Intuitively, though, we would expect these divergences to be per character of order 1/ 2 unit in device space, statistically distributed uniformly. Therefore,we naively hope to be able to even out accumulated differences together with the word gaps. The white space between two successive words expectedlyoften has to be spaced out anyhowin order to provideblock justification. Unfortunately,matters are not as simple as that: A24 pt font at 300 dpi is not exactly the same as a 12 pt font at 600 dpi. There are several reasons why small type sizes are wider than what we anticipate them to be: Small type sizes appear too light when scaled linearly: Our eyes are not perfectlylinear perceptors. Traditional hot-metal setting compensated this therefore by cutting the thicknesses of stroke of small types somewhat wider. Today, this compensation is known as optical scaling (typographically correct scaling might be a more appropriate term [Klassen93]). For the same reasons, the counterforms of the characters at small type sizes have to be widened, too. Likewise, to be physiologicallycorrect, intercharacter spacing is somewhat wider for small type sizes than what we would expect. This compensation is sometimes called optical spacing. At low resolution, the problem is amplified as soon as the stems and serifs are safe-guarded against their vanishing. Similarly,some of the side-bearings must not vanish under coarse scaling, hence they suffer exaggeration. The problem is aggravated if an editor does not allow negative kerning values. For "wysiwyg" type-setting,the formatting shown on screen should be the same as the one on the printer, but words set in small type sizes at low resolution will be too wide, hence the word gaps will be too small. In practice we

A Formalism For Intelligent Outline-Fonts

137

have found this to lead to negative word gaps almost systematically, which makes serious editing impossible. To avoid negativewordgaps, we might compose the words narrower on screen (which is harder to read, and it does not ease the editing), allow wider word gaps on the printer (which may cause rivers through the type area or the columns), allow the last wordof a line to leave the type area or the columns on screen (which endangers multi-column layout),or use smaller type sizes in the role of larger ones (which has at least an ophthalmic drawback), but whateverwe do is more or less unacceptable. Therefore,in our editor we have engineered ourselves a little luck with the device resolution. While the actual screen resolution of the Ceres-3 is 72 dpi [HeebNoack91 the wealth of fonts inherited from the Ceres-1/2 [Eberle87, ], Heeb88] - originallyscaled (and tuned [Meier91]) for 80 dpi - is used now 84 as if the screen had a resolution of 91.44 dpi (or 10000units). Like this, we can avoid negative word gaps, but what we see on screen is throughout a bit too large. However, in doing so we can just about fulfillthe guideline in [Bigelow85], that "screen fonts should be from 1.2 to 2 times as large as corresponding printed fonts". We conclude that although "wysiwyg" type-settingremains to be a bit of a botched job, ours at least tries to get the best out of it.

4.7 Formal notation & graphical feed-back 4.7.0 Floatingelements In 4.1.0 we have set the course for a formal approach. With the legacy of the next chapter, a formal outline-font representation can be compiled into a data structure, which is able to scale, digitize,and render "itself".However, before an outline-font can be defined formally, and after the outline-font has been rastered, the entities at issue are of purely graphical nature. It is therefore desirable to accompany the formal roundabout way by a certain degree of graphical feed-back. Ideally,we would want to place, for instance, a graphical representation of each newly compiled contour or glyph,say, next to the respective name in the source text. An extensible hybrid editor that encourages such an endeavor has been introduced in [Szyperski92a and we have modeled our own editor quite ], closelyafter it. In this editor, a text is understood to be essentially a sequence

138

The Backbone:

of generalizedattributed characters. These characters or floating elementsare activeobjects (in the sense of 2.1): They can display themselves on a raster device, or respond to events triggeredoff by the mouse. Starting out from an abstract base element, all kinds of concrete elements can be derived. Such derived elements can be used to assist formal font definition in many different ways. In this section, we give some examples on how they can bring graphical feed-backinto the formal notation. As a first example,we illustrate error marks and how they are used not only for ordinary programming, but equally profitablyfor font-programming.Being used to computer programming, we would all agree that it is very easy to make syntax errors "by accident". Be it an omitted closing parenthesis or a misspelled identifier, often such errors could be "improved" at the twinkling of an eye, if we were guided to the exact location of the error within the text. For this very reason, our font-compiler inserts sort of an icon into the text at the location of the syntax error and explains the errors nature in full words. When combined with text windowsthat "scroll" to the syntacticallyoffending text portion,

Formal notation with a marked syntax error ("origin" is "misspelled") this constitutes a simple measure for making formal notation easier to use.

A Formalism For Intelligent Outline-Fonts

139

4.7.1 Static feed-back While compiling contours and glyphs, the compiler can remember easily the source position of the contours and glyphsname. Once the respective component is compiled into (a piece of) an internal data structure (cf. 5.2.1), the compiler can have an icon inserted at the memorized source position.

An extract from a formal font definition interspersed with icons These icons serve as an anchor for triggeringoff different ways to provide graphical feed-back. They can be toggled to static graphical feed-back. Like this, we can see whether we have assembled correctly the character at issue, e.g. whether all the serifs are at the right places, and if so, whether they are orientated properly. At the same time, this permits us to see whether the characters side-bearings are acceptable, or not: Since the generalized characters respond to the basic editing operations in exactly the same way as do ordinary characters, we can readily compose a short sequence of icons simply by using the editors cut-and-paste facilities.

140

The Backbone:

Interspersing formal font definition with graphical representations of the glyphs (top) and using the editors ordinary cut-and-paste facilities to compose a sequence of icons for visualizinginter-character spacing (below)

A Formalism For Intelligent Outline-Fonts

141

4.7.2 Dynamic feed-back On top of static feed-back, the icons can be "interclicked"to other resolutions and type sizes, providingdynamic graphical feed-back. Like this, we can visualize the response of the characters to dynamic regularization, that is, their behavior under coarse scaling. Many of the illustrations given earlier in this chapter are actually examples thereof. Eventually, we may wish to see the individual pixels blown up, possibly with the regularized outlines or a grid inverted over the enlarged glyphs. In view of the increasing number of alternative ways to display the contours and glyphs,or in general,the large number of parameters to be setup, we will soon run out of mouse buttons and even combinations thereof. Therefore,a somewhat more sophisticated way to adjust all the nuts and bolts was implemented.

Various control panels showing the same Times "u" character with different parameters: outlined with knots (left), solid with inverted grid (right) The technique how to implement such control panels most easilyshall not be

142

The Backbone:

subject to closer analysis in this thesis. We put off the reader with the hint that they are assembled out of small text windows. In doing so, we not only reuse existing software components, but at the same time make data exchange between screen masks (which is what such panels are) and document windows (which is what we are using right at the moment for setting the present thesis) an intrinsic feature: Both the mask fields and the document windows are derivedfrom the same text window class. Before turning to yet another application of floating elements, one point should be made clear right now. Some of the above control panels show outlined characters with knots. These illustrations resemble closelythe one given in 3.3, showing a typical outline-font editing session. However, behind the scenes, there is a completely different structure. The knots in the above illustrations may be expressions or pairs of numbers, which by themselves may be expressions or immediate numbers attributed differently,while (in terms of the class hierarchy proposed in 5.2) the knots shown in 3.3 are always pairs of immediate numbers. Thus, although we may be mislead to do so, we cannot make the knots in the above control panels editable, for the same reasons as the ones given in 4.1.0 already. But we can couple the graphical feed-backto the formal notation as tightly as possible, giving a whole gamut of different ways to look at the glyphs - which is preciselywhat we have done with the outline-fonticons.

4.7.3 Structural feed-back Another class of floatingelements implements a simple way to providehierarchical editing. The text folders at issue stem from original ideas in [Mssenbck90] and [Szyperski92a particularly the idea to permit "lazy" ], structuring of texts without obstructing the user with the editing task (i.e. without introducing a higher degree of modality). Especially the handling of aliases (i.e. elements whose code is not available, but which nevertheless should be treated consistently as black boxes), is implemented much more polishedly in [Szyperski92a which is why we are not unhappy to dispense ], with further discussion. What is important to point out, however, is the application of text folders to font-programming.Embracing the "implementation" part of contours and glyphs,the folders reflect both semantically and "graphically"the hierarchical nature of the outline-fonts.

A Formalism For Intelligent Outline-Fonts

143

The blue-print of the example font, with closed and opened folders (left), unveiling various degrees of detail of a Times "g", along with the hierarchy of the glyph (right) With that, we hope to further ease the threat called formal font definition. Notice that we used such text folders most fruitfullyas well for editing computer programs, research papers, and last but not least, the present thesis. As far as our experiencesare concerned, they can but help not to lose track of things since they assist substantially the wish to have texts laid out both comprehensively and concisely.

5 The Implementation: An Extensible OOPStyle Application


5.0 A font-scalers "point of view" 5.0.0 Hierarchy In the preceding chapter, we have reconciled the fundamental raster problem with the wish to represent fonts in a way which is independent of the targeted type size and device resolution. This reconciliation has lead to a formal language for intelligent outline-fonts. With this formalism, we can express both the inherent regularityof outline-fonts, and the regularityto be attained under coarse scaling. It may be objected, that the implementation of this formalism entails too complex a program to be a viablealternative to competing approaches. However, in this chapter we shall show that - taking an appropriate OOP style factorization for granted - such objections are unfounded. Thereby, we hope not to bore the reader with great detail, such as the particular modularization (the essential part, anyhow,consists of a single module only). Instead, assuming some familiarity with elementary compiler construction, we prefer to illustrate some of the more unconventional aspects of the approach. With the hierarchy proposed in 4.3.4, a component essentially is either an immediate component, or it is composed of other components, which by themselves may be composed of other components, and so forth. Examples: rightStemEdge = leftStemEdge+ stemWidth thicknessOfStroke = regularThickness opticalCorrection apex = meanline + overshoot - thicknessOfStroke These examples reflect in detail the nature of three number parts, as introduced in 4.3.0. On the next higher level of hierarchy, pairs of numbers are aggregated to knots. Analogousto the numbers, the knots may be the result of the addition (or subtraction) of other knots, that is, in their dual role of vectors,respectively. On the second but highest level, contours combine sequences of knots with an attribute that relates to the geometry of the underlying graphical object. Open contours are concatenated to more complex contours, the sign de-

An Extensible OOP Style Application

145

noting their orientation, and eventuallyresult in a glyph (closed contour). On the top level, characters are created out of one or more glyphs or counters, the glyphsnegativeshapes. To sum up, each level of hierarchy is represented by a particular class of constituent parts (glyphs, contours, knots, and numbers). Each part is composed of one or more parts of the next lower level of hierarchy. Alternatively, each of these parts may be a combination of other parts of the same level of hierarchy. This distinction between terminal and non-terminal entities allows for any recursive depth of the characters blue-prints. Nevertheless, the fontscaler need not be aware of this recursiveness permanently, as will be shown in sub-section 5.0.2.

5.0.1 Attributes Terminal entities may occur in different varieties.In 4.3.1, attributes are introduced to define the particular variety:Contour attributes determine the variety of the contour (polygon,Bzier curve), while number attributes specifyfurther the numbers' response to coarse scaling (hyperbolic scaling, round-before-use bypass). What is important to think of is to provide for the possibility to introduce further attributes on demand. This not only extends the palette of primitives (cf. 4.5), but it may lead as well to the reconciliation of different approaches with one another (cf. [Stamm93a]), in hopes that thereby the respective advantages are combined. In the end, this allows for any number of attributes in the characters' blue-prints. The followingsub-section shows, why nevertheless the font-scalerneed not knowall the varietiessimultaneously.

5.0.2 Polymorphous modeling The preceding section calls for homogeneous treatment of all kinds of varieties of one and the same basic entity. This is a typical case for the application of polymorphism The functionality of the entities is the same, but their imple. mentation is not: Both third order Bziercurves and simple polygonsare contours suitable for defining the boundary of glyphs and characters, as may be natural splines and circular arcs, but their scan-conversion algorithms differ from contour class to contour class. Analogously,the response of numbers to scaling varies along with the attribute givento the individual number, but the common functionality- to be scalable numbers at all- does not. The homogeneous view onto varieties of the same component can be ex-

146

The Implementation:

panded smoothly to a variety which refers to a list of other components of the same basic class, essentially. By dint of polymorphism, these "other components" by themselves can be either one of the varietiesof the immediate component, or again one which refers to other components. With that, both alternative parts, and the assembly thereof, are unified - irrespectiveof the gamut of alternatives and the recursive depth of the assembly! Starting out from an abstract base class Component = CLASS END, the followingexample illustrates these facts in practice: Contour = CLASS [super = Component] { abstract base class for contours } METHOD Digitise(under: Mapping; with: RenderingTool); END; Polygon= CLASS [super = Contour] knots: LIST OF Knot; METHOD Digitize(under: Mapping; with: RenderingTool){ overwrite }; END; BezierCurve= CLASS [super = Contour] polynomials LIST OF BezierPolynomial; : METHOD Digitize(under: Mapping; with: RenderingTool){ overwrite }; END; Concatenation = CLASS [super = Contour] components: LIST OF Contour; METHOD Digitize(under: Mapping; with: RenderingTool){ overwrite }; END; This exampleforms a class hierarchy of contour components. The other levels of the component hierarchy, the glyphs, the knots, and especially the numbers, form analogous class hierarchies: Number = CLASS [super = Component] METHOD Value (scale: RationalNumber) : INTEGER; END; END; Integer = CLASS [super = Number] END; Hyperbolic= CLASS [super = Number] END; Sum = CLASS [super = Number] As to the problem, how far these analogies can be unified, cf. 5.2.0 and 5.3.0sq. In the end, a font will be modeled by a polymorphic directed acyclic graph (PolyDAG). The use of polymorphism and late binding provides us with two immedia-

An Extensible OOP Style Application

147

te benefits. First, the potentially arbitrary recursiveness is straightened out in a way which is transparent to the caller. The caller does not have to be aware of it permanently. Second, the gamut of varieties is completely homogenized, again transparently. This encourages the ad-hoc integration of independent solutions to artistic aspects of font-scaling. The course is set for the implementation of a concise, yet comprehensive font-scaler. In view of merely three variants (polygon, Bzier curve, concatenation), it might be objected that advocatingpolymorphism and late binding is bringing up one's big guns. For a moment, it may be hard to disagree, but consider how the distinction of different terminal entities, and the separation of terminal from non-terminal entities, would be realized in a more conventional programming language (Pascal, Modula-2,cf. 2.1.2): Most probably, we would resort to tagged variant records! Then, upon traversing the font, the font-scaler would have to inspect each of these records' "type-tag" in order to "select" the desired data of the record, and in order to decide what to do with it. This is precisely what polymorphism realizes anyhow, but without our explicit ado. Furthermore, using polymorphism is safer, since we do not have to assert "manually" the integrity of the tuple [tag, data]. On top of that, using late binding frees the font-scaler from the decision, what to do with the data, hence it is faster. All this goes to show that the bare necessity to implement different variants is a sufficient argument in favor of using polymorphism and late binding. With that, in the end, we get extensibilityfor free.

5.0.3 Inherited decouplingof tasks The outstanding result of the graphical toolbox introduced in Chapter 2 is a strict decoupling of intelligent rendering tools from both the graphical objects to be digitized,and from the raster devices on which the objects are to be rendered. This strict decoupling is inherited by the PolyDAG that models a font. With respect to a font-scaler,this provides both contour- and rendering-independence. The sketch of a class hierarchy given in the previous sub-section illustrates this heritage. However, as far as font-scaling is concerned, the signature of the Digitizemethod is not quite apt. Both parameters are subject to change. First, the mapping is not an arbitrary rational mapping. Rather, translations, mirrorings at points, and at orthogonal lines, are the only instancing transformations needed for contours and glyphs (cf. 4.3.3). These transformations are entirely integer mappings. Yet, the scaling factor proceeds to be a rational number. Therefore,the two are separated, although, at the expense of

148

The Implementation:

making the retrieval of the scaling factor a bit laborious, they could be combined in a single rational mapping. The axial mirroring furthermore reverts the orientation of the outlines. Again, the resulting parity could be retrieved from the final mapping, but it is more efficient to maintain it separately. Immersing in the recursive depths of the outline-font definitions, the font-scaler toggles, as appropriate, the parity flag, and concatenates instancing transformations, whileleavingthe scaling factor unchanged. Second, due to the subtlety pointed out in 4.3.4, the rendering cannot be solved with a turtle that adheres strictly to the protocol proposed in 2.2. The outlines are not only concatenated out of positively and negativelyoriented (open) contours, a situation for which a fillingalgorithm is givenin A.1.3. Rather, the outlines may be the addition and particularly the subtraction of glyphs (or closed contours) as well. This stipulates the order of execution,and in the end, it makes the intermediate storage of rectilinear regions an unavoidabletask. Therefore,extra methods were added to the turtles protocol: Renderer = CLASS [super = RenderingTool] METHOD InitGlyph(); METHOD TermGlyph(); METHOD MergeGlyph(sign: INTEGER); { sign = +1: set union, sign = -1: asymmetrical set difference } END; The fillingalgorithm derived from the above Renderer manages a stack of rectilinear regions (or glyphs).Essentially,its methods InitGlyphand TermGlyph bracket the application of an algorithm similar to the one given in A.1.3. In particular, InitGlyph creates a new stack element (glyph),and by the time the turtle has finished walkingalong a closed contour, TermGlyphdeposits this element on the stack. Once both operands (glyphs) of the set union or asymmetrical set difference are known, MergeGlyphpops the two top-most stack elements, performs the respective operation on the glyphs, and pushes the result back onto the stack. Like this, the result of lengthy expressions with any number of parenthetical sub-expressions is a stack with a single element. The Flush method uses this very last element to do the actual filling (cf. A.1.3). Readers somewhat familiar with compiler construction will recognizethe analogy of this filling algorithm with the code generation for stack machines (cf. also 5.2).

An Extensible OOP Style Application

149

5.1 A font-compilers "point of view" 5.1.0 Syntax & symbols By now, we have conveyed an idea of one way to model intelligent outlinefonts. Thereby, an important consideration was excluded altogether so far: The transition of the fonts formal representation to their internal representation, that is, their compilation. A compiler for outline-fonts is not concerned that much with different variants of contours and numbers. Rather, it deals with symbols and their differinglexical and syntactical analysis. Examples:
NUMBER Height = 247 KNOT Origin = [0, 0] CONTOUR XAxis = POLYGON [[0, 0], [1, 0]]

These examples reflect in detail the nature of three immediate components, as introduced in 4.3.0sq.

5.1.1 Terms & expressions Subsequently, such immediate components, as well as named parts and parenthetical sub-expressions, may become the terms of expressions as introduced in 4.3.4: Expression = ["+"|"-"] Term {("+"|"-") Term }. Term = Immediate | Identifier | "(" Expression ")". Now, what is important to grasp is that these expressions all follow the same scheme, irrespectiveof the particular component at issue (number, knot, contour). This degree of orthogonality, as far as the syntaxof the font-languageis concerned, is intentional. It makes the font language more regular, and with that, hopefully,easier to learn.

5.1.2 Once more: Polymorphous modeling The advantages of polymorphism gives raise to some hope that the compilers "point-of-view" can profit from these benefits as well. The common functionality to be factored out is the syntaxof the particular immediate symbols. Hen-

150

The Implementation:

ce we start out from a base class which has a method in charge of the syntactical analysis: Term = CLASS sign: INTEGER; METHOD Compile ( ); END; Immediate = CLASS [super = Term] { abstract base class for immediate numbers, knots, and contours } END; Identifier = CLASS [super = Term] name: ARRAYOF CHAR; METHOD Compile ( { overwrite }; ) END; Expression = CLASS [super = Term] components: LIST OF Term; METHOD Compile ( { overwrite }; ) END; This hierarchy mirrors closelythe syntactical scheme repeated in the previous sub-section. It requires derived classes only for particular immediate symbols, providingfor both the data and the compiling method: Number = CLASS [super = Immediate] value: INTEGER; METHOD Compile ( { overwrite }; ) END; Knot = CLASS [super = Immediate] x, y: Term { may be a Number or an Expression or any (!) other Term }; METHOD Compile ( { overwrite }; ) END; Contour = CLASS [super = Immediate] knots: LIST OF Term; METHOD Compile ( { overwrite }; ) END; Like this, the syntactical analysis is simplified substantially, since the class hierarchy now is arranged in a way that permits a vast amount of code to be reused.

An Extensible OOP Style Application

151

5.1.3 Names & scopes Clearly, the above class hierarchy mirrors exactly the respectiveEBNF-productions of the formalism. But it has a serious fault: The identifier class cannot model invariably named numbers, knots, or contours, without resorting to multiple polymorphism. What we need is a way to model both anonymous and named entities. This particular way of looking at the entities stems from our earliest prototype. At that time, the course was not yet set for a more formal approach. Rather, the idea was to provide simultaneously a formal and a graphical view onto one and the same model. Each formal change would be compiled into a data structure, which is used for the graphical display. Conversely, each graphical manipulation would be "de-compiled"back to formal notation. Therefore, the names had to be remembered, much like for symbolic debuggers. Eventually,we decided not to pursue this path any longer. Although challenging and most interesting, it would not have led us any closer to our primary goal, that is, font scaling at low-resolution.It is sufficient to maintain a separate list of scopes, each of which contains a list of tuples of the form [name, term], as a means of knowingwhereto search for an object referred to by a name; because of this decision, names have become quite futile for other purposes. With that, multiple polymorphism can be evaded for the time being, even though the resulting class hierarchy is not by any means beyond all doubt.

5.2 A single recursive data structure for the scaler and the compiler 5.2.0 A conflict of aims? The class hierarchies sketched in 5.0.2 and 5.1.2 are seen from different points of view. An attempt to reconcile the goals targeted by the font-scaler and the font-compiler presents us with a clear conflict of aims: The goals are orthogonal to one another. This fact can be pictured in an (orthogonal) diagram. In the diagram below, the horizontal axis depicts the scalers view, while the vertical axis gives the compilers view. Naming encodes the heritage: An ImmedNumber is both a number component and an immediate symbol. Likewise, an ExprContour (i.e. a concatenation of contours) is both a contour and an expression.Finally, both axes stem from the same root, a persistent part (cf. 5.2.3).

152

The Implementation:

Part Term

Number TermNumber sign percentage ImmedNumber hyperbolic round-bypass

Knot TermKnot sign percentage ImmedKnot ?

Contour TermContour sign instantiation ImmedContour Polygon Bzier ExprContour list of terms (contour terms)

Immediate

Expression

ExprNumber ExprKnot list of terms list of terms (number terms) (knot terms)

But what about the terms? Is a TermKnota Knot? Is an ExprKnota TermKnot or a Knot? What seemed rather straightforwardin 5.1.2 now is becoming a bit of a snag.

5.2.1 Compile once, scale many times We need a means of reflectingthe fact that several distinct terms can refer to one and the same part. After pursuing prototypes of the two "points of view" and weighingtheir pros and their cons, the followingcompromise started to emerge, not least because of the environment's limitation to single polymorphism, and because of the consideration that fonts most probably are going to be scaled more often than compiled: Part = CLASS [super = IO.Ref] END; Number = CLASS [super = Part] METHOD Value (scale: RationalNumber) : INTEGER; END; Knot = CLASS [super = Part] METHOD Value (scale: RationalNumber; VAR x, y: INTEGER); END; Contour = CLASS [super = Part] closed, closure: BOOLEAN; METHOD Digitize(under: Mapping; with: Renderer); END;

An Extensible OOP Style Application

153

ImmedNumber = CLASS [super = Number] value, places: INTEGER; hyper: BOOLEAN; END; ImmedKnot = CLASS [super = Knot] x, y: Number; END; ImmedContour = CLASS [super = Contour] knots: List.Struct; class: Graphic.Curve{ a kind of parent object}; scale: RationalNumber; cache: Graphic.StepRecorder; END; = CLASS [super = List.Elem] sign: INTEGER; operand: Part; END; TermNumber = CLASS [super = Term] percentOf: Number END; TermKnot = CLASS [super = Term] percentOf: Number END; TermContour = CLASS [super = Term] mirroredAt: ImmedContour; at: Knot; END; Term ExprNumber = CLASS [super = Number] terms: List.Struct END; ExprKnot = CLASS [super = Knot] terms: List.Struct END; ExprContour = CLASS [super = Contour] terms: List.Struct END; The absence of multiple polymorphism seems to introduce a couple of roundabout ways or indirections which are not necessary: The class hierarchy appears to be forced to ignore the benefits of polymorphous modeling with respect to the compilers viewpoint.Besides, there are more properties which ideally should be factored out as different classes (e.g. glyphs from contours, different immediate numbers, etc.). Unfortunately, an unpleasant restriction in the environments module loader [GutknechtWirth92] would not let us implement a cleaner solution, or else we would have to trade it in for an unmotivated splitting of the font-scaler into several modules, entailing unnecessary exports, and further methods dedicated to assert encapsulation. Theoretical and practical aspects thereof are discussed in 5.3. For the time being, it shall suffice to complete the above class hierarchy by characters, variants, and of course, a font:

154

The Implementation:

Character = CLASS [super = List.Elem] ch: CHAR; leftSidebearing, rightSidebearing: Number; contour: Contour; END; Variant= CLASS [super = List.Elem] name: DeskTop.Name{ "book", "bold", "italic", }; height, baseLine: Number; characters: List.Struct; END; Font = CLASS [super = DeskTop.Model] name: DeskTop.Name{ "Times", "Helvetica", "Syndor", }; variants: List.Struct; END; With this class hierarchy, the font-scaling algorithm essentially is reduced to the followingfour methods:
METHOD [self: ImmedContour] Digitize( under: Mapping; with: Renderer);

{ a (forwarding)digitizer } VAR i, I, i, x, y: INTEGER; knot: List.Elem; den: RationalNumber; Den: Mapping;


BEGIN IF self.scale # under.scale THEN { digitize anew }

Graphic.AssertArrayLen( self.class, self.knots.elems); i := 0; knot := self.knots.first; WHILE knot # NIL DO knot[Term].operand[Knot].Value( nder.scale,self.class.x[i],self.class.y[i]); u INC(i); knot := knot.next; END; den.Setup(1,under.scale.den); Den.Unity();Den.ScaleBy(den, den); self.cache.Setup(NIL,NIL, Raster.replace); self.class.Digitize(Den, self.cache); self.scale := under.scale; END; IF self.closure THEN with.InitGlyph()END; IF under.odd THEN i := self.cache.n-1; I := 0; ELSE i := 0; I := self.cache.n-1; END; i := SGN(I- i);

An Extensible OOP Style Application

155

under.Apply(self.cache.X[i], self.cache.Y[i], x, y); with.JumpTo(x, y); WHILE i # I DO INC(i, i); under.Apply(self.cache.X[i], self.cache.Y[i], x, y); with.WalkTo(x, y); END; IF self.closure THEN with.TermGlyph()END; END { Digitize };
METHOD [self: TermContour] Digitize(under: Mapping; with: Renderer);

{ a forwarder } VAR x0, y0, x1, y1: INTEGER;


BEGIN IF self.at # NIL THEN

self.at.Value(under.scale, x0, y0); under.At(x0, y0); END; IF self.mirroredAt # NIL THEN self.mirroredAt.Value(one, x0, y0, x1, y1); under.MirroredAt( 0, y0, x1, y1); x END; IF self.operand[Contour].closed THEN { else dealtwithin MergeGlyph } under.odd := under.odd # (self.sign < 0); END; self.operand[Contour].Draw( under, with) { forward to the real contour }; END { Digitize };
METHOD [self: ExprContour]Digitize(under: Mapping; with: Renderer);

{ an iterator } VAR elem: List.Elem;sign: INTEGER;


BEGIN

elem := self.terms.first; IF self.closure | self.closed THEN { dealing with (open) contours } IF self.closure THEN with.InitGlyph()END; WHILE elem # NIL DO elem[TermContour].Draw( nder, with); elem := elem.next; u END; IF self.closure THEN with.TermGlyph()END; ELSE { self.closed & self.closure dealing with glyphs } elem[TermContour].Draw( nder, with); elem := elem.next; u WHILE elem # NIL DO sign := elem[TermContour].sign; elem[TermContour].Draw( nder, with); elem := elem.next; u

156

The Implementation:

with.MergeGlyph( ign); s END; END; END { Digitize };


METHOD [self: Character] Digitize(under: Mapping; with: Renderer); BEGIN

self.contour.Draw(under, with); END { Digitize }; Notice that the font-scaler may or may not have to round the numbers to the nearest unit of quantization. This fact is equivalent to having the immediate number class, upon invokingits Value method (and thereby, the knots Value method), fit its "scaled" value to the nearest unit of the scaling factors denominator or not. Hence the remaining intelligence amounts on nothing more than forwardingthe digitizingrequest under the mapping 1/Den.

5.2.2 Recursive descent parsing & error handling In the preceding sub-section we have analyzed some important aspects of the proposed data structure right down to the last little detail. We have done so deliberately, in order not to give rise to the idea of pure coincidence. Therefore, now we can turn to the process of producing instances of this PolyDAG (that is, outline-fonts) in good spirits. Basically,the compilation of the formalism for intelligent outline-fonts follows closely the principles given in [Wirth86], which we have employed successfully already earlier [Stamm87]: In so-called LL(1) parsing, a text representing a computer program is both read and syntacticallyanalyzed from left to right once. The syntax analysis itself is performed on a stream of (syntactically relevant) symbols, which are scanned for by lexical analysis out of a stream of characters. To our understanding, this technique of recursive descent parsing constitutes a most natural approach to the problem. However, non-trivialproblems may arise when amidst the depths of recursion the parser should detect the computer program to be syntacticallyerroneous. At that point, sometimes even the best parser can but make a (more or less) good guess as to what went wrong, reporting an error message accordingly: The speculative element of LL(1) parsing may have lead the syntactical analysis to assume that it is analyzing something which differs totally from the programmers intentions. Even worse, the respective heuristics has to be

An Extensible OOP Style Application

157

used to recoverthe parser from such an error. Of course, we cannot propose a solution as to what the compiler should assume to be the error committed by the programmer. But the language to be compiled is simple enough, and the compiler itself is reasonably efficient, such that we prefer to abort compilation upon detecting the first syntactical error, rather than tryingto "re-synchronize"with the source text and risking to emit all kinds of meaningless follow-up errors. Unfortunately, this type of error handling leads to a substantial overhead in a strictly structured programming language, for effectively, it amounts on a "goto" statement (that is, go to the end of the compilation), unless we bother to test a flag after each (nested) procedure call, and the like. Inspired by our current activities in assisting the teaching of systems programming, our contribution to the problem is to formulate our compilation as a co-routine. Particularly,our approach defines a co-process, Process = CLASS METHOD Execute(); END; rather than a co-routine only,which permits a cleaner way to pass parameters to and from the co-process Compiler = CLASS [super = Co.Process] source: Text.Model; pos: INTEGER { input }; ch: CHAR; sym: Symbol;id: ARRAY CHAR; val: INTEGER { state }; OF destination: Font; errNum, errPos: INTEGER { output }; METHOD Execute() { overwrite }; END; than when resorting to an individual procedure and separate global variables. Once the compilation is wrapped into a co-process, the abortion of the compilation amounts on a (well-defined)transfer of control back to the main process. Internally, the base process class maintains a stack pointer, pointing into its own stack area. This area is allocated once, according to the estimates of stack usage of the attached co-routine. No provisions can be made to detect a co-process stack overflow, but reportedly, this is not possible for the Ceres-3 machine inherently [HeebNoack91 Supposedly, we would need some sup]. port from the underlying machine architecture, generating a run-time error as soon as an attempt is made to refer to an address outside the current stack

158

The Implementation:

segment, or beyond a dedicated stack-fenceregister [Szyperski92b ]. The last paragraph in this sub-section is dedicated to those readers who have ever believed to be real hackers: A co-routine transfer amounts on nothing but redirecting the value of the underlying machines stack pointer, in the end. Amazingly, this can be realized rather easily without resorting to assembly language:
PROCEDURE Transfer(from, to: Process) { SP SP }; PROCEDURE Xfer (from, to: Process); VAR stack: ARRAY [0 OF INTEGER { 32-bit }; 0] BEGIN { push FP }

from.sp := stack[1]; stack[1] := to.sp; END { pop FP };


BEGIN { FP := SP }

Xfer(from, to); END { SP := FP }; It goes without saying that the above piece of code applies to the parameter passing protocol of the particular high-levellanguage only, and that it is to be compiled with the compiler avoiding any attempts to check the validity of array indices.

5.2.3 Persistent objects & intelligent riders We are compiling a formalism for intelligent outline-fonts into a polymorphic directed acyclicgraph. Eventually,we wish to externalize(store to a disk file, or similar) and internalize (load from a disk file)this PolyDAG. Makinga PolyDAG objects persistent entails two areas of problems. On the one hand, the s DAG contains references (addresses) which lose their meaning upon externalization. On the other hand, the polymorphic nature allowsfor extensions (derivedclasses) which lose their identity in the same way. Much like a turtle serializes the output of a digitizerto a device, an analogous separation of riders (access mechanism) from files (the actual carriers) is implemented in Oberon [Reiser91] and made a canonical concept in Ethos [Szyperski92b For simple data structures, such riders convert integers or ]. strings to and from sequences of bytes. For our PolyDAGs, they furthermore represent the entities which one would expect to address the two aforemen-

An Extensible OOP Style Application

159

tioned areas of problems. In order to provide maximum simplicity with using persistent objects, we propose to derive all objects from the same base class, which can load and store itself through an intelligent rider: Type = CLASS { "meta"-class, wrapped aroundOberons type descriptor } END; Ref = CLASS { base class for persistent objects } METHOD Load (from: Rider); METHOD Store (to: Rider); END; Rider = CLASS { intelligentrider, wrapped aroundOberons [file,rider] tuple } { methods to open and close the rider, set and get the position, } METHOD Read (VAR ch: CHAR); METHOD Write (ch: CHAR); METHOD ReadNum (VAR int: INTEGER); METHOD WriteNum(int: INTEGER); { plus a good supply of further read/write methods } METHOD ReadType (VAR type: Type); METHOD WriteType (type: Type); METHOD ReadRef (VAR ref: Ref); METHOD WriteRef (ref: Ref); END; Starting out from these definitions, we shall proceed to illustrate, how this rider solves said areas of problems. For the assertion of referential transparency, that is, in order to re-establish the meaning of addresses (references) upon internalizing the DAG, the rider encapsulates a mapping mechanism. Under this mapping mechanism, addresses are translated to numbers (the same addresses to the same numbers) and vice-versa. Here are the respectivemethods:
METHOD [self: Rider]ReadRef (VAR ref: Ref); VAR num: INTEGER; type: Type; BEGIN

self.ReadNum(num); IF num = 0 THEN ref := NIL; RETURN END; IF num = self.refXlat.nums THEN { not yet in table include }

160

The Implementation:

self.ReadType(type); New(ref, type); self.refXlat.array[self.refXlat.nums] := ref; INC(self.refXlat.nums); ref.Load(self) { the actual contents }; END; ref := self.refXlat.array[num]; END { ReadRef };
METHOD [self: Rider]WriteRef (ref: Ref); VAR num: INTEGER; BEGIN IF ref = NIL THEN self.WriteNum(0);RETURN END;

"num := index of type in self.refXlat.array, or self.refXlat.nums"; self.WriteNum( num); IF num = self.refXlat.nums THEN { not yet in table include } self.WriteType(Old(ref)); self.refXlat.array[self.refXlat.nums] := ref; INC(self.refXlat.nums); ref.Store(self) { the actual contents }; END; END { WriteRef }; The concrete implementation of arrays is irrelevantfor the principle (cf. 5.3.0). Notice, however, that the tuple [array, nums] has to be referenceable: Since riders can be copied, we have to assert that all copies refer to the same instance of the mapping mechanism. On top of that, the possible recursiveness of PolyDAGs requires to update the respective tables before the actual objects are loaded or stored. For the support of a generic load/store mechanism, that is, in order not to let objects lose their identity upon externalization, the rider encapsulates a second mapping mechanism. Since we do not anticipate that many different types, both the externalization and the internalization use the same array of (references to) type descriptors. Independent of whether or not an object is a referenceable object, this permits to store possibly the fully qualified identifier denoting the class at issue. Here are the respectivemethods:
METHOD [self: Rider]ReadType (VAR type: Type); VAR num: INTEGER; moduleName, className: ARRAY CHAR; OF BEGIN

self.ReadNum(num); IF num = self.typeXlat.nums THEN { not yet in table include } self.ReadStrg( moduleName); self.ReadStrg( className);

An Extensible OOP Style Application

161

type := This(moduleName,className) { "delayed class instantiation"}; self.typeXlat.array[self.typeXlat.nums] := type; INC(self.typeXlat.nums); END; type := self.typeXlat.array[num]; END { ReadType };
METHOD [self: Rider]WriteType (type: Type); VAR num: INTEGER; moduleName, className: ARRAY CHAR; OF BEGIN

"num := index of type in self.typeXlat.array, or self.typeXlat.nums"; self.WriteNum( num); IF num = self.typeXlat.nums THEN { not yet in table include } Name(type, moduleName, className) { "class identity" }; self.WriteStrg(moduleName); self.WriteStrg(className); self.typeXlat.array[self.typeXlat.nums] := type; INC(self.typeXlat.nums); END; END { WriteType }; Again, the implementation of the array is irrelevant,but for the same reasons as above, the array has to be referenceable. The entire approach makes use of the routines New and Old, which serve to allocate a particular derived class, giventhe (reference to the) type descriptor, and to obtain an objects(reference to the) type descriptor. These routines understand the above Type to be a meta-class, (references of) which can be owned, hence the term meta-programming On the one hand, the above proce. dure effectively amounts on integrating the "meta-objects", that is, instances of the meta-class, into the mechanism for asserting referential transparency. On the other hand, it refines to a certain extent the concept of delayed module loading and linking, such as is necessarily present in the Oberon environment (or, in the form of DLLs, in Windows[Petzold92]), to the granularity of classes, hence we might talk about "delayed class instantiation". Amazinglyto us, these elementary prerequisites for a modern persistence model, although introduced in [Gutknecht86a], generalizedin [Pfister91], and realized in all its consequences in [Szyperski92b have never been institutio], nalized satisfactorily in the Oberon environment. Our own implementation, comprising the meta-programming and the intelligent riders with a good supply of read/write methods for further simple data types, compiles to just over 3 kB of object code. Notice that since it is a depth-first approach, it is not restricted to DAGs, but would work equally well with (cyclic) symbol tables for modern high-levellanguage compilers.

162

The Implementation:

5.3 By way of assessment: Using Oberon-2 for the implementation 5.3.0 Abstract concepts vs. concrete records and pointers In modern high-level programming languages we are often talking about structures. A structure denotes the way in which something is organized or put together. No doubt, the simplest structure is a single entity, such as an integer or a character. Homogeneous and heterogeneous aggregations thereof, i.e. arrays and records respectively,are the second but simplest structures. Makingno claim to be exhaustive,beyond that we will soon see rather general structures, such as lists or trees. While arrays and records are rather straightforward to implement, unfortunately more general structures are not: Lists and trees are at least recursive,making it impossible for the compiler to determine their size and memory addresses at compile-time. Typically, programming languages introduce place-holders to store only the address (pointer), but not the entire entity (record), in order to overcome this inherent problem. The address itself is determined on demand at runtime only (explicit allocator). At the same time this introduces the referenceabilityof entities, since severalinstances now can own a place-holder containing the same address. Understanding the records and pointers to be nodes and edges of graphs, this allowsfor infinite generality:Differentinstances can refer to the same entity,which is a necessary prerequisite for general graphs. Thereby, however, at least two concepts are mapped onto one and the same construct of the underlyingprogramming language: The allocation (implicit "static" on stack, explicit"dynamic"in heap) The access (direct "by-value" access, indirect "by-reference"access) The second concept is quite similar to the distinction of two conventional mechanisms of passing parameters to subroutines. Athird conceptual issue may arise from the way in which polymorphism is implemented. Oberon [Wirth89], presumably to give freedom of choice for the allocation and/or access method, implements both polymorphic records and pointers. Oberon-2 [MssenbckWirth91] goes so far as to allow procedures (methods) to be bound to record or pointer types. In either case, this may lead to ambiguities upon assignment. Assuming P and Q to denote record types (where Q may be a type extension of P) and p and q to denote pointer types (again, with q possibly an extension), then the followingassignments look identical, but their results are not: P := Q; p := q;

An Extensible OOP Style Application

163

The first assignment projects the contents of the object Q to that of the object P, wherebythe existence and the behavior of the assigned data may be changed. The second assignment merely copies two addresses, without affecting the nature of the data pointed to, but yieldinga second reference to the same data. (We believe the second variant to be generally less confusing, which is amongst others why our notation introduces the abbreviation CLASS.) Afourth conceptual inconsistency arises from the so-called open arrays as introduced in Oberon-2. An open array is an array whose (upper) bound, and therefore its size, is unknown at compile-time.Several algorithms in Appendix A illustrate occurrences of situations where determining the array bounds at run-time only may be very useful. Particularly,if a procedure accepts open array parameters, and if the procedure requires temporary variablesfor intermediate storage of these parameters, then the possibility to declare and use such an open array variable with the same bounds as the actual array parameter avoids having "to pass the local variable as an extra parameter" (which we have seen in FORTRAN programs for numerical mathematics). However, Oberon-2 requires open arrays - or what it calls open arrays - to be declared as OpenArray= POINTER TO ARRAYOF BaseType which at the same time introduces their access by reference and restricts their allocation to the heap. This entails two problems: Introducing the access of open arrays by reference falls in a similar category of problems as the assignment of polymorphic records and pointers. To copy "the contents" of the array (which is what we would want to do usually), we have to remember how the variable has been declared, and we have to give proof of our memory by giving the compiler the hint "^", indicating where it has to code a technically imposed indirection. Still,this neither allows to assign open array parameters to open array variables,nor to assign open arrays of different sizes to one another. Restrictingthe allocation of open arrays to the heap gives away the possibility to allocate a local open array variable on stack. This turns the open array concept into a farce in cases like the aforementioned application to numerical mathematics. Not only does this slow down performance unnecessarily,but furthermore it leaves no chance to collect garbage during extended periods of processing. Scaling a font for several type sizes in a row suffers from this problem. The only reason why we have still used Oberon-2sopen arrays,in spite of these drawbacks,is because it avoids us resorting to "dirty tricks" with assembly language.

164

The Implementation:

In the end, mapping severalabstract concepts onto the same concrete constructs may make programs harder to read, not least for ourselves. If we look at one of our programs again, we may not find it easy to tell, whether this pointer is now meant to be an indirection, or whether it is simply a necessary evil (yet the lesser one, permitting us to realize recursive data structures or intuitive assignments of active objects at all). Deplorably,we are not in a position to propose an enhancement - if there should be a simple one at all. But at least, we would like to ask the reader to keep in mind said mapping of concepts when subjecting to closer assessment the class hierarchy proposed in 5.2. One point deservingspecial attention is the Term class, which at the beginning of 5.2 seemed difficult to fit into the hierarchy. Recallthe role of a pointer as an edge of a graph. Expressions do refer to their subordinate component parts, thus the pointer is the appropriate construct. However, the expression would need, informally, a glyph so-and-so here, plus a such-and-such down there and another one up here. Formally, the expressions require attributed edges, similarly to more general purpose models of graphics. (The latter may need, say, a red circle here, plus a blue triangle there, and a green square somewhere in the background.) That is, now we need both, the referenceability introduced with edges (the proper indirection provided by pointers), and the possibility to combine edges with attributes (the upgrade of "immaterial" pointers to "material" entities). Now that this is not possible, the Term class stands in, hence its right to exist. The fact that the Term itself is nothing but a pointer stems from the wish to reuse predefined canonical data structures, which shall be dealt with in the next section. For the time being, we conclude with the moral of the story: The pointers and edges are not the same worry.

5.3.1 Canonical structures vs. multiple polymorphism In the previous section we have determined that the fairly abstract concept of a general structure or graph is mapped onto nothing but concrete records and pointers. To our knowledge,although the respective language constructs need not be called "record" or "pointer" respectively,this is the case in most of the popular programming languages. This should really make us think, for these languages invariablyclaim to be high-levellanguages. We are perfectlyaware of the fact that this is no small matter, though. Thus, we try to use polymorphism to gain both more abstraction and higher code-reusability.As a practical example we venture to implement generic

An Extensible OOP Style Application

165

lists. The experienced programmers may object somewhat condescendingly that they can do lists "on-the-fly", hence they may find it hard to understand why to bring up ones big guns. Havingwitnessed the introduction of the compiler presented in [GutknechtWirth92], though, we are perfectly aware of the possible "slips of the pen". On top of that, we can surely think of more creative pastimes than re-inventingthe wheeltoo many times. Starting out from a persistent object (cf. 5.2.3), a doubly-linkedlist is made of elements of the followingform: Elem = CLASS [super = IO.Ref] prev, next: Elem { to be exported read-only }; END; With such elements, we can define the actual list structure, with elementary but conventional operations thereon: Struct = CLASS [super = IO.Ref] first, last: Elem { to be exported read-only }; METHOD InsertAtStart(this: Elem); METHOD InsertAtEnd (this: Elem); METHOD InsertA(this, after: Elem); METHOD InsertB (this, before: Elem); METHOD Delete (this: Elem); METHOD SplitA (after: Elem; VAR off:Struct); METHOD SplitB (before: Elem; VAR off:Struct); METHOD Merge (with: Struct); METHOD Reverse (); END; Notice that, in doing so, we separate the structure from the contents. To a certain extent, such a list is nothing but an array of variable length and with flexible ordering of its elements. This is in contrast to defining a list recursively, wherebya list is looked upon as either an empty list, or a tuple consisting of one datum (to be enlisted) and a list ("the rest"). The recursive definition identifies the entire list by a single element, without giving a chance to talk with reasonable ease as well about the entire list itself. This may make it dangerous to refer to such a list if the list is still subject to change. Besides, arrays are not identified by one of their elements, either, which is why we believe the above way to define a list to be the more natural one. From that point on, so we would hope, all we have to do is to derive concrete list elements

166

The Implementation:

from the above abstract ones, and we can readily enlist number or contour terms. Effectively, we did so in our simple outline-font editor, but actually, we are still quite far from it. What the syntaxin 5.1.1 suggests is that an expression is a list of terms and that at the same time a term is an expression. This means e.g. for the ExprContour that it is to be derived from both the List.Struct and the Contour. Consequently, in order to become "enlistable", the Contour itself is derived from the List.Elem. With that, an expression contour has turned into a figment which simultaneously assumes the role of a list, that of one of its elements, and that of a contour. High time to break the vicious circle!

5.3.2 Polymorphism vs. wrapping The preceding sections and sub-sections may have produced the thought that we seem to miss multiple polymorphism wheneverwe try to marry a hierarchy of nodes with a canonical structure. The experiencesmade with the venture to implement such a structure, although still a bit immature, may be a hint that we are not quite pursuing the right avenue. A structure generally relates to more than one object, and objects are meant to provide encapsulation, hence we would be attempting to reconcile contrary intentions. The class hierarchy presented in section 5.2 has indicated it, and this subsection will explain it: We can re-use the code for the lists introduced in the preceding sub-section without multiple polymorphism. The reason stems from the distinction of the followingtwo concepts: Polymorphism denotes a relationship whereby a (derived) object "is-a" (base) object: "A circle is a graphical object." Wrappingemploys a relationship wherebya (any) object "has-a" (another) object: "A circle has a center point." In less obvious cases, these two kinds of relationships are confused easily. As far as our class hierarchy is concerned, this means that it is perfectly sufficient for the expression to have a list of constituent parts; the expression need not be a list. As soon as we look upon a list as a canonical structure, then the list manipulation methods should not need to be overwritten,or else the original formulation was not canonical. Therefore,there is no justifiable demand for derivinga "particular canonical" list. Instead, it is enough to wrap an expression class around the list class, from which point on the list class looks e.g. like an expression contour class. In contrast, the expression contour has to be a contour, or else it would be impossible for the contour term to refer to any contour whatsoever,that is, regardless of whether the contour at

An Extensible OOP Style Application

167

issue is an immediate contour or another expression contour. Opposed to that, in order to re-use the code for the lists, there is a legitimate reason for derivinglist elements: We wish to enlist particular concrete elements, but not merely abstract ones. Thus, a contour term is a term (which by itself is a list element), but it need not be a contour. It is sufficient to have or wrap a contour. Not quite incidentally,this "has-a"is actually a "has-a-reference", for expressions possibly reflect multiple references to the same constituent parts (cf. 5.3.0). That is, this indirection is a necessary precondition of the approach, but not a consequence of the absence of multiple polymorphism. Thus, the long and the short of it is that turning both nodes and edges into objects has disentangled sufficientlythe overall structure such as to be able to dispense with multiple polymorphism. If we still were to demand multiple polymorphism, we could employ mutual wrapping,at the risk of not anymore being able to easily tell the data for the wrapping.To conclude, we repeat the informal stimulus that for future high-levelprogramming languages we consider it worthwhileto think more intensivelyabout (generic)structures.

5.3.3 Forwarding, delegating, and message records vs. method procedures The previous sub-section has illustrated that wrapping can simplify complex relationships. Wrappingis concerned about what an object has, and eventually, what it refers to. But objects have both, existence and behavior. Now, any object owninganother object, and not willingto re-implement a particular behavior by itself, can re-use the code that "the other" object has implemented already,simply by forwarding any message to the object owned. Atypical example of this technique is the immediate contour. Rather than deriving(immediate) polygons and Bzier curves from a common immediate contour class, the actual (and only) immediate contour class refers to a graphical object. This graphical object serves as a base class for all curves that can be defined by an array of coordinate pairs. The immediate contour has a (reference to a) graphical object, but it need not be one by itself. Like this, the forwardingneeds to be implemented only once (cf. also 3.0.0, which actually implements forwardingin much the same way). There is just one snag: We have to be careful not to mix concepts when using wrapping as a roundabout way for non-existent language constructs. An example shall illustrate the point: To provide graphical feed-back of the formally defined contours and glyphs, the compiler inserts graphical illustrations into the source text upon successful compilation (cf. 4.7.1). It goes without saying that these illustrations are produced with the help of the scaling

168

The Implementation:

algorithm. Now, for the subtlety pointed out in 4.3.4, the scaling algorithm expects a somewhat specialized renderer (cf. 5.0.3). Notice that there is nothing to be said against this renderer being derived from the basic turtle, since it constitutes a specialization of the turtle. Just that, for open contours, there is no such subtlety, hence to illustrate them, a quill turtle (cf. A.1.1) would be enough. In order to re-use the code of the quill turtle, and equipped with single polymorphism, we tried the followingformulation: Sketcher = CLASS [super = Renderer] forwardee: Graphic.Quill END;
METHOD [self: Sketcher] WalkTo (x, y: INTEGER); BEGIN

self.forwardee.WalkTo(x, y); END { WalkTo }; in hopes that - since both the Renderer and the Graphic.Quill "is-a" RenderingTool- inheritance would take care of the rest. Far from it! The reason is that the Setup and WalkTo methods are addressed to different receivers.Particularly,the Setup method sets up a reference to the underlying raster device, and the WalkTo method expects this variable to be initialized, but now the two methods operate on variables that are owned by different instances of turtles (that is, self and self.forwardee respectively). The somewhat untidy workaround is to enforce the initialization of both variables(they are but references,anyhow),
METHOD [self: Sketcher] Setup (on: Raster.Device); BEGIN

super.Setup(on); self.forwardee.Setup(on) { setup "both super classes" }; END { Setup }; although, to our knowledge,the clean solution is delegating the message. Thereby, control would be transferred to the code of the forwardee (that is, to the parent object), but the data operated on by the forwardeecontinues to be that of the forwarder (that is, the object self). Notice that the call super.Setup(on) realizes proper delegation, just that the "delegatee"is self. Could it be that the class-instance model proves to rigid, at least for computer graphics? The interested reader may find an answer in [ConnerVanDam92 ]. Before concluding this sub-section, and while we are talking about forwarding, there is an aspect of forwardingof more practical concern. In 5.1.2 the hope emerged to let us derive all kinds of expression classes from the

An Extensible OOP Style Application

169

same base expression. Thereby, we might have been able to re-use a substantial amount of code which results from the various expressions that have to forward messages to their constituent parts. This is possible if the individual methods of a class are combined in a single handler which responds to a message that is contained in a message record. With that, messages can be forwarded anonymously to all the elements of a structure, that is, without the forwarderhaving to know, what it is forwardingin a particular case (much like the broadcasting mechanism described in [GutknechtWirth92]). Incidentally, we think that anonymous forwarding(or iterating on a structure) is one of the problems to be included in future investigations about (generic) structures: we would want to do something with all (or some of) the elements of the structure without sayingright from the beginning,what it is. The use of a handler procedure, instead of individual method procedures, is not free of disadvantages, either. First, it is rather tedious to assign the "parameters" of a message to a message record. Like this, the simple turtle.WalkTo(x, y) expands to the statement sequence walkMsg.x := x; walkMsg.y := y; turtle.Handle(walkMsg); which is a bit elaborate. Second, the handler of a leaf object will have to use a cascade of type tests in order to find out, what to do with the message
IF msg IS SetupMsg THEN ELSIF msg IS JumpToMsg THEN ELSIF msg IS WalkToMsg THEN ELSIF msg IS FlushMsg THEN END

which amounts on a sequential method lookup procedure. This is inefficient or - in case the discrimination is done with a message identification number instead of a type test - rather error prone [Mssenbck&al89 ]. Third, it would not have helped us anything at all, for in our case the forwardingis not totallyanonymous: the numbers and knots have to be added to one another, while the contours and glyphs eventuallyentail calls to the renderer. Thus, the long and the short of it is: Message records encourage anonymous forwarding,while method procedures ease single calls. Which of the schemes is used should depend not so much on personal likes and dislikes, but rather on the frequency of occurrence of anonymous forwardingand on that of single calls.

6 The Balance: ACompetitive Prototype


6.0 The results: Demands met? By way of recapitulation, let us briefly resume the main part of this thesis. Starting out from the foundations in two-dimensional graphics, and proceeding with its application to font-design,the central Chapter 4 has introduced a representation for intelligent outline-fonts that both permits to scale fonts onthe-fly, for medium- and particularly low-resolutionraster devices,and allows for a concise implementation. In order to answer the question, whether the demands made in section 0.2 have been met, we first illustrate the results of making an entire font availableto intelligent font-scaling.

A Competitive Prototype

171

172

The Balance:

Various Times alphabets at 72 dpi, from 72 pt down to 6 pt (enlarged) Clearly, down at 6 pt and 72 dpi, it is just about decipherable, but since there is not much we could do better by hand, we could not care less (cf. 4.4.7). At 10 pt and above, most of the characters look quite acceptable. If they do not, it may be a matter of taste whether,say, this pixel should be here or there or anywhere at all, in order for the arc at issue to be considered a nicelyrastered arc. We are concerned to make clear that in this context, nice is to be understood by the standards of an individual taste. Since this need not correspond to the rasterization obtained by applyingbut the rules of the engineering profession, we offerthe use of non-standard scan-conversion (cf. 4.5.2). Added to which comes the fact that using the Times font for the example does not mean to be using the Times font. Rather, it is the interpretation of our type-designer,Hans Ed. Meier, much like Adobes Type 1 Times is actually LinoTypesTimes Roman, licensed to Adobe, while Apple/MicrosoftsTrueType Times is in fact MonotypesTimes New Roman. These variants of the Times font unveil subtle differences that will disclose themselves most probably only at large type sizes and high resolution. Unless the goals targeted by dynamic regularization (cf. 4.4) should also comprise to even out artistic license of different interpretations, this may result in this or that divergenceshowing off on the pixel-level. Now, assuming the role of an engineer, we do not have the legitimation to play a particular font designsartistic qualities off against those of another design. We have to content ourselves with the attempt to provide an algorithm

A Competitive Prototype

173

that renders the font as faithfullyas possible. At best, we may say that we prefer Type 1 fonts over TrueType fonts, or vice-versa, as well as we concede anybody else to prefer either of these fonts over ours. Therefore,we would not be too unhappy to recommend the most demanding clients of fonts to stay with the hand-tuned screen-fonts of their preference, rather than debating on the undecidable. (In fact, URW considers it a viable concept to supply sets of hand-tuned screen fonts for resolutions below 240 dpi or type sizes of 4 pt and below, to be used by the more demanding clients of fonts as alternative to URW font-scaling software NIMBUS [Karow92c]. The only other reason for s which we would still want to use a bitmap-font editor is to define "fonts" with icons, electrical symbols, and similar.) Be it as it may, more than once Hans Ed. Meier - our expert in hand-tuning fonts - has pointed out that he would not have hand-tuned this or that character differently.This encourages us to answer the introductory question with a convinced "yes".

6.1 Artistic aspects Before starting the day of reckoning, we would like to point out that the present stage of the project is that of a prototype, representing the result of maybe three years worth of both research and development. This means amongst other that we are fighting with unequal weapons when comparing our prototype to commercial products, possibly produced and supported by the hosts of computer programmers. In quite a few cases we had to concentrate on essential ideas which unveil a certain degree of novelty, rather than implementing in great detail all kinds of alternatives and features. For instance, a question that we have been asked more than once is whether we have provided for the possibility to "turn off" the serifs below a certain type size and resolution, to which we invariablyanswered with a succinct "no". Equally invariablythis provoked at least irritation, backed up with the somewhat reproachful objection that with TrueType one could do so. Great, but if one can do it with TrueType, what would be new about putting it into our own approach as well? We ask the reader to keep this in mind when subjecting the overallresults to closer assessment. In section 0.2 we have claimed that most of the problems of medium- and low-resolutionfont-scaling fall into one or more of four categories. We repeat these categories now, in order to compare our approach with the approaches and concepts introduced by AdobesType 1 (cf. 1.2) and Apple/Microsofts TrueType (cf. 1.3). (Coueignouxsapproach was targeted uniquely at high-resolution, while Kohensapproach primarily envisaged medium-resolution.) For

174

The Balance:

each category, we briefly recapitulate our solution to the areas of problems, along with the conceptual range of font-scaling that the respective category solves. Regularity: The obvious consequence of the fundamental raster problem, the loss of regularity (equality, symmetry, connectivity),is covered by decomposing characters into components (glyph, contour, knot, number) and safeguarding their existence under coarse scaling. This lets our approach to font-scalingprovide invariance of translation (of stems, serifs, thicknesses of stroke, etc.), mirroring (of serifs, bowls, shoulders, etc.), and existence (of components, connections, features, etc.). Near regularity: Quantization imposes limitations that increase the unavailabilityof components to represent small divergencesfrom stern regularity, along with decreasing type size and resolution. By explicitlyincluding in the characters blue-prints such divergencesfrom regularity,and by rounding the blue-prints parts before they are assembled, these local aspects of dynamic regularization are put down to correct handling of regularity. This lets our approach to font-scaling avoid disproportionate enlargement of optical corrections and reference line overlaps, as well as halfserif widths that are much smaller than stem widths, and it evitates long flats and isolated pixelsin curvilinearglyphs. Constrained proportionality: Unconditionally safeguarding components against disappearance imposes further constraints that endanger the components proportions. By explicitlyincluding in the characters blue-prints such proportional relationships, these global aspects of dynamic regularization are put down to correct handling of regularity,too. This lets our approach to font-scaling caricature serifs and balance out thicknesses of stroke, along with decreasing type size and resolution, and within the constraints imposed by both the safeguards and the quantisation. Digitized appearance: In order to abstract from the contours and their digitized shapes, outline-oriented font-scalingapproaches concentrate on controlling the way in which knots are adapted to a grid,although the resulting patterns of jaggedness on the fringe of slanted and curved parts may respond delicatelyto slight variations of the knots. By consequently using object-oriented programming style, selectivesolutions to such pixel-level problems can be integrated without allowing to talk about pixels selectively. This not only lets our approach to font-scaling give answers to problems regarding digitized appearance, both on the level of the actual digitizingitself and on the level of rendering digitizedcoordinate pairs, but as well lets it avoid pixels that would miss as a result of particularly unfortunate patterns of jaggedness on the fringeof pairs of inner and outer contours.

A Competitive Prototype

175

It is appropriate to subject the above statements to further discussion, not the least because some of them assume rather strong positions. Decomposing characters and fonts into components is not new at all, which is why we have mentioned Coueignouxsapproach as well. Doing so not only for two-dimensional glyphs, but as well for one-dimensional contours or zero-dimensional knots and numbers, however, is new. We agree with [Karow89] that there are relatively few glyphs that can be separated, pre-rastered and subsequently assembled by pasting their bitmaps together (diacritical characters, vertical stems, foot serifs, etc.). But with our approach we can also pre-digitize contours and have them concatenated with other contours afterwards. This provides the required degree of regularity in cases where glyph separation is prone to destroy existing designs of typefaces (diagonal edges in Aor Z,head serifs in nor u,curve parts in Oor U, etc.). With pre-scaled and rounded knot or number components, finally, we can provide regularityin cases where using glyphs or contours would suggest to introduce regularities not present in the original design, thereby definitely destroying existing (traditional) type design. Knot components are useful as reference points (such as are known also to TrueType), while number components may replace all kinds of dictionaries (such as present in Type 1 fonts) or tables (such as found in TrueType). The importance of number components is backed up by two simple but powerful concepts. One of them, the concept of scopes, is not new at all, but introducing it to the worldof font languages most probably is. Particularlythe hierarchical arrangement of the variant and the character scope provides a natural way to distinguish numbers with global relevancefrom numbers with local relevance.Both Type 1 fonts and TrueType fonts realize similar distinctions by giving the respective dictionaries or tables different names. The other of the two concepts is the round-before-use rule, a direct consequence of the lost linearity. We have explained in detail that rounding critical numbers before adding them to one another is closely related to pasting pre-rastered bitmaps together. The seemingly incautious rule to round numbers "as soon as possible" is thus quite new, indeed. It has far-reachingadvantages regarding preservation of equality under scaling and dynamic regularization of components with nearly equal dimensions and locations. In the first case (cf. 4.0.1), s(x + y) s(x) + s(y) explains unequal stems and counterforms in such characters as e.g. an m.In Type 1 fonts, the remedy is a stem hint command, particularly the vstem3 command (cf. 1.2), while in TrueType the R (for relative) of the MDRP/MIRP instructions (cf. 1.3) makes up for the lost linearity. In the second case (cf. 4.4.2), s(x + x) s(x) + s(x) explains disproportionately enlar-

176

The Balance:

ged optical corrections and reference line overlaps. In Type 1 fonts, the remedy is a combination of stem hint commands with the dictionaries BlueValues, OtherBlues, FamilyBlues, and FamilyOtherBlues, while in TrueType the I (for indirect) of the MIAP/MIRP instructions, together with the Control-ValueTable, the Single-Width-Value the Control-Value-Cut-In, and the Single-Value, Cut-In of the Graphics State make up for the lost linearity.We consider it our own humble contribution to the worldof font-scalingto unifysuch a gamut of phenomena and to reconcile with one another such a multitude of remedies or features on the same simple denominator called number component. Much of the success of the nice and simple Type 1 approach lies in the way Adobe suggests to define curves. Actually, they are using third order Bzier curves and strongly recommend to use them in much the same way we do in 3.2.3. With that, Type 1's stem hint commands are equivalent to being specific about the distance of pairs of tangents of Bzier curves, or the gauge of the rails introduced in 4.4.4. Maybe this is the sort of concept one just has to come to after having engaged in the area of font-scaling for a sufficientlylong time. As far as the hint replacement or the ghost stems are concerned, however, we cannot help feelingthat our approach is quite a bit more flexible. Much of the speed of the TrueType approach lies in the simple font-scaling software(but cf. 6.2) and reportedly the use of second order Bziercurves. Second order Bzier curves let us easily talk about their tangents, too. In contrast to third order Bzier curves, however, they have only three knots, which makes it impossible to use a single second order polynomial per quadrant; the free parameters would allow for a single shape only. Furthermore, on-curve-pointsare usually not stored explicitlyin a TrueType font. On the one hand, this saves space, but on the other, the on-curve-pointshave to be interpolated on the basis of two consecutive off-curve-points.Doing so presupposes C1 continuity, while G1 continuity would be sufficient for smooth curves. (However, even though they do not bend the curve sharply, abrupt changes in curvature are at least debatable [Karow92c].) Be it as it may, we do not want to make into a dogma the curve class to be used for font-designor scaling. In the end, all the curve classes will give rise to more or rather less pleasing jaggies. But in order to comply with constrained proportionality, we would prefer the lines and especially the curves not to assume a different topologyafter scaling (cf. 3.2.1). Therefore,we simply need a means of talking about topological features (such as local extremal points, tangents, and inflection points) easily, together with the assertion that said topologywill remain invariant under scaling. The long and the short of it is the following remark: If second order Bzier curves are used with little understanding for the typographical problem at issue, such things as twilight zones

A Competitive Prototype

177

and points (cf. 1.3) are no surprise anymore. Here is therefore an issue which may be overlookedeasily. In Type 1 fonts, the font-scaling software contains the strategy,how to adapt the outlines to a grid of a givenresolution, whilefor TrueType fonts this strategyhas to be built into font production tools. Whoever wishes to produce TrueType fonts has to invent such a strategy,which may make it easier to produce Type 1 fonts, but possibly at the expense of flexibility and speed. However, neither of the two approaches unveil their strategies - for commercially intelligible reasons. We are convinced, therefore, that in this respect our approach is a particularly competitive prototype. One more issue for which none of the discussed commercial products offers a solution is the possibility to talk also about digitizedappearance, rather than contours and knots only. We have illustrated two examples in 4.5.1 and 4.5.2, and we can think of other benefits of extensibilityapplied to contours, knots, numbers, and renderers. In that area our approach is definitely a new one. There is latent danger, though, which should be averted. The ease with which derived contour classes for non-standard digitizing can be integrated into our approach should not lead to an abundance of mostly similar extensions, and in particular should not be misused to bypass the concept of device independence.

6.2 Technical aspects Our approach yields a small font-compiler and scaler: The entire module comprises less than 1000 Oberon-2 statements compiling to less than 16 kB of NS32x32 code. In fact, the bare font-scaler alone compiles to just over 6 kB! Typical extensions will contribute maybe two more pages of source code each, or around 2 kB of object code. Yet OOP style does not reduce performance considerably. Our prototype compiles a font of average complexity (Times) into 0.25 to 1 kB of font representation per character (without further data compression etc.), whilethe font-scaler (not particularly optimized) generates 15 to 30 bit-mapped characters per second (8 to 24 pt at 72 dpi, NS32532 at 25 MHz). Since we are talking about a prototype, the figures may not be particularly meaningful. For instance, the tiny code size is a result of carefullysplitting off an object-oriented graphical toolbox or a mechanism for generic loading and storing polymorphic DAGs. Such issues are considered general purpose and we may expectthem to be provided by the programming environment. But once it becomes a commercial application, this or that extension may have been

178

The Balance:

added, making the code size larger.Also, there is scope for optimization, such as integer-based algorithms for digitizingcubics (cf. A.0.3) or dedicated filling tools for small type sizes at low resolution (cf. A.1.2sq), which we have determined to contribute about a factor of 2. Yet, the figures give grounds for a rough comparison with AdobesType 1 (cf. 1.2) and ApplesTrueType (cf. 1.3). In [Karow92b], we find figures for a Times font, scaled to 10 pt at 300 dpi, which is why in the table below we have quoted our own approach with the lower end of the performance range (although the complexity of the bare scaling algorithm is the same for all type sizes): ETH Project II program code size scaling performance 16 kB 15 cps Adobe Type 1 70 kB 35 cps Apple TrueType 160 kB 60 cps

Clearly, ours is the slowest of the three, but equally clearly, it is also the smallest. As far as the performance is concerned, we have mentioned possible optimizations, which would take us close to the performance rated for the Type 1 approach. The redirections introduced by the object-oriented approach (digitizer/renderer/device separation), although we would not recommend to bypass them unreservedly, are reported to add up to another 80% (cf. carrier/rider separation in [Szyperski92b which would take us near the performan]), ce rated for the TrueType approach. Even if this meant to "hard-wire"this or that part of the graphics library into the bare font-scaler, taking the code size up to the rated 16 kB or beyond, but the performance anywhere near that of TrueType, our font-scaler would remain significantly conciser. Therefore, we dare concluding that our approach is a competitive prototype.

6.3 Future research topics There are a couple of topics to which we shall have to owe the reader a detailed solution, mostly because these topics are somehow related to - but not entirely within - the scope of this thesis. For instance, in the entire thesis we have not talked about how to scale italic fonts, but since our approach can reconcile different variants, and since it can render characters with diagonal strokes (A , etc.) acceptably, this is not expected to introduce any further , V problems. Likewise, it is easy to do bold fonts, if they are "sufficiently"bolder than their regular counterparts.

A Competitive Prototype

179

Times H, 72 pt down to 6 pt, regular (top) and bold (bottom) at 300 dpi (left) and 72 dpi (right) In the above example, the bold stems are 70% wider than the regular ones, or almost twice as wide. Definingthe boldStemWidthas regularStemWidth+ 23^, both regularStemWidthand 23^ will not vanish under scaling, and boldStemWidth assumes a minimum stem width of 2 pixels - our equivalent to forcebold in AdobesType 1 fonts. But what should be the stem width of a medium bold font at low resolution, such that we can still tell bold from medium, or medium from regular? If medium bold is just a little "bolder" than regular, and if the regular stems are one pixel wide only, then a little more than one pixel is still one pixel. It looks as if there is no inherent solution to this problem on bi-level screens, not even if we did it by hand. On a printer, we could eventuallyresort to half-bitting,but on screen there are too few pixels, and they are too coarse for such endeavors. The problem of rendering medium bold stems on bi-level screens is related to that of rendering slightlycurved stems, such as may be found in the font "Optima".

An attempt to do Optima-like stems, 72 pt down to 6 pt, at 300 dpi (left) and 72 dpi (right) Of course we can use half-bitting at medium-resolution, and dynamic regularization at low-resolution,in order to have such stems "snap" into orthogonal rectangles, but in-between they look more like ordinary dumbbells than like "Optima"-stems. Again, on a bi-level screen the pixelsare too coarse such that

180

The Balance:

half-bitting could let the visual system confuse intensity and size. Even though we did not have to introduce a dedicated mechanism, such as the flex mechanism in AdobesType 1 fonts, we are not yet satisfied with these results. The rash answer may be to use intermediate intensities of gray, rather than black and white only.The idea is roughlynot to decide for either black or white (cf. 0.1), but to use an intensity of gray, related to the percentage of the pixel covered by the shape. Since percentages below 100% will be the case on the fringeof the shape, this may reduce the visual effectof jaggedness on gray-level screens, much like what half-bittingachieves on bi-level printers. The interested reader may find detailed answers to gray-scalingissues in [Naiman91a] and particularly[Naiman91b]. Apart from technical drawbacks, such as memory consumption and display speed, the unreserved application of gray-scalingis likely to entail further problems. We can easily imagine that naive gray-value assignment - the gamma correction notwithstanding - reduces the contrast of originallysharp edges, such as stems and crossbars. But since the spatial frequency of the stems sort of make up for the "reading rhythm" of a text, this may also reduce legibility, not only jaggedness, since the stems will look as if the monitor were terribly out of focus. Therefore,we intuitivelyagree with [Bigelow85] to opt for a "hybrid"font in which the verticalstems remain bi-level, whilediagonal stems and curvilinear glyphs are improved by gray-valued edges. Particularlyin view of electronic text books that are bound to be read on screens, we would prefer to further investigate on the issue of gray-scaled fonts. (Incidentally, we have already been asked whether we could do "fuzzy" fonts with our approach; the term "fuzzy" intended to relate to gray-scaling. This suggests to us that whenever people do not have a precise understanding of what they are doing or talking about, they invariablycall it "fuzzy". Chances are that products, tagged with the buzz-word "fuzzy" in some way or other, even sell better than others.) A particularly subtle problem we have not covered is that of acute junctions (such as of diagonal stems in , , etc.). It falls into two categories.On A V the one hand, the type-designer usually applies an optical correction to the counterform of such junctions, in order not to let the surrounding of the junction appear too dark.

Some characters of the Syntax font with acute junctions

A Competitive Prototype

181

We could think of an extended contour class (cf. 4.3.7) that avoids us having to define the regularization thereof repeatedly.On the other hand, such acute angles seem to be particularly prone to toner dispersion on laser printers (for vivid reasons, this used to be called ink trap in hot-metal setting). Ultimately, this can be tackled only if more of the properties of the underlying device are taken into account than what is proposed in 2.2. The idea to pursue is to encapsulate pixel-level corrections in a (derived)rendering tool, and to supply to the actual scaling process whichevertool is most appropriate for a givendevice. (A related problem is that of black-writinglaser engines in contrast to white-writingones, cf. [MacKay91].) Last but not least, we can think of other applications of the conclusions drawn from the fundamental raster problem. Since the latter is purely geometric in nature, we would expect any device independent page description language to face the same irregularities as we have experiencedwith font-scaling. Particularly,if the targeted deviceshould be the screen, we are sensitized now that it is not a mere rounding mishap for the edge of a rectangle to "fall"between two grid lines. It may be a tough nut to crack, but we consider it worthwhile to avert the risk of such irregularities in a way that is transparent to the user of future device independent page description languages.

7 Conclusions
"Nichtwissen hilft" (Heinrich Rohrer) "Zuviel Wissen kann fr die Kreativitt schdlich sein" (Gerd Binnig) The above epigraphs may give rise to the impression that we are reaching for the stars now. But isnt there some truth in the motto: "Who works in research and always knows beforehand what he is going to do, will never discover something new", as Rohrer has put it on the occasion of a talk givenat ETH? In the view of Binnig "too much knowledgecan be damaging to creativity, particularly if the patterns of thought, as are applied to the knowledge, are considered invariant" [Binnig89]. (Heinrich Rohrer and Gerd Binnig were awarded the Nobel prize in Physics in 1986 for inventing the raster tunnel scanning microscope.) It is in this certain spirit of the inquisitive physicists constructive naivety in which we took up the challenge. Not letting ourselves be distracted by objections ("component decomposition has been determined unsuitable for font-scaling") or be impressed by know-all type of advice ("you should be using hints/instructions/grid-fitting as do") we threw ourselves into our work. Not being biased, we did not have to take somebodysside to begin with, and we hope to have been impartial upon comparing our prototype with competing approaches. Unlike commercially available products we have put our cards on the table, not expectinganybody to buy a pig in a poke, and we have pointed out follow-upresearch projects in cases where we owe the reader a detailed solution - for which reason whatsoever. As a result, we have most probably the shortest solution to the problem of medium- and low-resolution font-scaling. In this solution, the font language on its own is maybe not the most important contribution, it merely reflects in a concise and consistent way what an appropriate font representation should be. To a certain extent,this representation combines much of the flexibility of Apple/MicrosoftsTrueType fonts with the conceptual range of AdobesType 1 fonts or Agfa Compugraphics Intellifonts. Because of its simplicity we strongly believe that most of the formal part can become available to automated acquisition. Combined with the advantage of the extensibility,permitting our approach to address pixel-level problems without addressing pixels individually, in many respects it is therefore a hybrid approach to mediumand low-resolution font-scaling. We hope that our report on the subject has been clear and simple enough in order to have to owe the reader at most a question like: "So, what was the problem?"

Appendix A:The algorithms


A.0 Graphical objects A.0.0 Lines Most of the material to be presented in this appendix is not entirelynew. However, many prominent text-books unveil this or that difficultywith conveying it. Often, they appear to struggle needlessly with a somewhat careless notion of coordinates and pixels. Therefore,and not least to complete the picture, we give a selection of the algorithms for two-dimensional graphics employed in our approach. The current section is addressed at digitizing continuous graphical objects. In A.0.0 and A.0.1, we follow closelyan introduction to the Bresenham type methodology given in [Gutknecht86b]. To our knowledge, it constitutes the most painless access to the topic. To begin with,we discuss straight lines - the edges of polygons.Let (x0, y0) and (x1, y1) denote the (integer) coordinates of the start and the end of a straight line, Line= CLASS [super = GraphicalObject] x0, y0, x1, y1: INTEGER; END; and let us consider, in a first step, a steep line in the first quadrant. For such a line 0 dx dy, where dx := x1 - x0 and dy := y1 - y0. Assume the line has been digitizedup to some point (x, y) on the coordinate grid,and that the unit steps "walked" by the turtle on the grid represent the best possible approximation to the continuous line. This is always possible by putting (x, y) := (x0, y0).

a steep line, partly digitized

184

Appendix A:

At that point, either the (vertical)unit step from (x, y) to (x, y + 1), or the unit step from (x + 1, y) to (x + 1, y + 1) again represents the best possible approximation; the latter case being preceded by a (horizontal) unit step from (x, y) to (x + 1, y). In order to decide, on which of the two x-coordinatesto walk upwards,the equation of the continuous line xr := dx/ dy(yr - y0) + x0 is evaluated at yr := y + 1/ 2 (i.e. halfway between y and y + 1). If xr x + 1/ 2, then the choice is (still) x, else a unit step across is done beforehand and the choice becomes x + 1. Rather than evaluating the (quantitative) equation of the real line (xr), the latter is substituted into the (qualitative) decision. By elementary algebra,the above decision thereby transforms, in turn, to
dx/ dx/ dy(yr

- y0) + x0 x + 1/ 2
1/ 2 - y0) + x0 x + 1/ 2

{xr := dx/ dy(yr - y0) + x0 } {yr := y + 1/ 2 }

dy(y +

(y + 1/ 2 - y0)dx (x + 1/ 2 - x0)dy d(x, y) := (2y + 1 - 2y0)dx - (2x + 1 - 2x0)dy 0 which contains but integers and integer operations. If d(x, y) 0, then x is the right choice for the turtle to walk upwards, else it is x + 1. This leads to the followinginformal pattern for the digitizealgorithm: start with the choice (x, y) = (x0, y0) {here we knowthat x is the right choice } whiley has not arrivedat y1 do x is the right choice, hence walk one step upwards if d(x, y) > 0 then x is not the right choice anymore, hence walk one step across end {here we knowthat x is the right choice (again) } end Since dx dy, at most a single step across is sufficient to assert that d(x, y) 0 again after the step across. More formally,the iteration is performed under invariance of H d(x, y) 0, the condition never to make the wrongchoice. Furthermore, the choice is the right one at start, and each iteration makes a pro-

The algorithms

185

gress towards arrivingat the end. Therefore,the followingalgorithm is correct:


METHOD [self: Line]Digitize(with: RenderingTool); VAR x, y: INTEGER; BEGIN

x, y := self.x0, self.y0; with.JumpTo(x, y) { H }; WHILE y < self.y1 DO INC(y); with.WalkTo(x, y); IF d(x, y) > 0 THEN INC(x); with.WalkTo(x, y) END { H }; END; END { Digitize }; with comments indicating wherethe above invariant condition H holds. In a first refinement, we take into account that d(x, y) is linear in both x and y, hence incremental evaluation imposes itself: Instead of re-calculating d(x, y) anew wheneverit is used, we evaluate d := d(x0, y0) at start and update d with each change in x or y. This results in the followingrefined algorithm:
METHOD [self: Line]Digitize(with: RenderingTool); VAR x, y, d, dx, dy: INTEGER; BEGIN

dx, dy := self.x1 - self.x0, self.y1 - self.y0; x, y, d, dx, dy := self.x0, self.y0, dx - dy, 2*dx, 2*dy; with.JumpTo(x, y) { H }; WHILE y < self.y1 DO INC(y); with.WalkTo(x, y); INC(d, dx); IF d > 0 THEN INC(x); with.WalkTo(x, y); DEC(d, dy) END { H }; END; END { Digitize }; In order to prepare for the forthcoming generalization of the algorithm, let us rearrange it slightly.Admittedly,this particular way of asserting the invariant H may seem a little unorthodox, but its significant advantage should become apparent in the next refinement.
METHOD [self: Line]Digitize(with: RenderingTool); VAR x, y, d, dx, dy: INTEGER; BEGIN

dx, dy := self.x1 - self.x0, self.y1 - self.y0; x, y, d, dx, dy := self.x0, self.y0, dx - dy, 2*dx, 2*dy;

186

Appendix A:

with.JumpTo(x, y); WHILE y < self.y1 DO IF d > 0 THEN INC(x); with.WalkTo(x, y); DEC(d, dy) END; { H } INC(y); with.WalkTo(x, y); INC(d, dx); END; IF x < self.x1 THEN INC(x); with.WalkTo(x, y); DEC(d, dy) END { H }; END { Digitize }; In a second step, we refine the above algorithm such that it considers not only steep lines, but also flat ones, that is, any line in the first quadrant. The key idea of this refinement is the fact that the equation of the line does not make any assumptions as far as the slope of the line is concerned. The geometryof the line is the same for both steep and flat lines. Therefore,H states as well for flat lines that (still) we have not made the right choice for the x-coordinate on which the turtle is to walk upwards. The only difference to steep lines is that a single unit step across may not be sufficient to assert H. It is thus very easy to refine the above algorithm without any further algebra:
METHOD [self: Line]Digitize(with: RenderingTool); VAR x, y, d, dx, dy: INTEGER; BEGIN

dx, dy := self.x1 - self.x0, self.y1 - self.y0; x, y, d, dx, dy := self.x0, self.y0, dx - dy, 2*dx, 2*dy; with.JumpTo(x, y); WHILE y < self.y1 DO WHILE d > 0 DO INC(x); with.WalkTo(x, y); DEC(d, dy) END; { H } INC(y); with.WalkTo(x, y); INC(d, dx); END; WHILE x < self.x1 DO INC(x); with.WalkTo(x, y); DEC(d, dy) END; END { Digitize }; Two if-statements have been replaced by two while-statements. Before jumping to conclusions, the rash reader is encouraged to comprehend that the advantageof the above refinement is not paid for with a quadratic cost function. In a third step, finally,we refine the algorithm such that it considers any line in any of the four quadrants. The key idea for this refinement is the fact that the assertion of the invariant H is carried out independently from updating the coordinates x and y respectively.Thus, all that remains to be done is to reflect properly the particular quadrant when updating x and y, whilecontinuing to base the evaluation of H on the first quadrant. Again, no further algebra

The algorithms

187

is required for the refinement:


METHOD [self: Line]Digitize(with: RenderingTool); VAR x, y, d, dx, dy, inx, iny: INTEGER; BEGIN

dx, dy := self.x1 - self.x0, self.y1 - self.y0; dx, dy, inx, iny := ABS(dx), ABS(dy), SGN(dx), SGN(dy); x, y, d, dx, dy := self.x0, self.y0, dx - dy, 2*dx, 2*dy; with.JumpTo(x, y); WHILE y self.y1 DO WHILE d > 0 DO INC(x, inx); with.WalkTo(x, y); DEC(d, dy) END; { H } INC(y, iny); with.WalkTo(x, y); INC(d, dx); END; WHILE x self.x1 DO INC(x, inx); with.WalkTo(x, y); DEC(d, dy) END; END { Digitize }; Upon incrementing x and y, the constant stride 1 has been replaced by variables that hold strides which correspond to the quadrant at issue. This algorithm of amazing conciseness digitizes any line starting and ending on the (integer) grid! Since it uses only integers and the cheapest of their operations (add, subtract, and shift), it will do so very efficiently.For comparison purposes, the interested reader may wish to refer to the original approach presented in [Bresenham65].

A.0.1 Circles We continue the illustration of digitizing algorithms with the discussion of circles. Let (xc, yc) and r denote the (integer) coordinates of the center and the radius of a circle, Circle= CLASS [super = GraphicalObject] xc, yc, r: INTEGER; END; and let us consider, in a first step, the first quadrant of a circle located at the origin. Assume the circle has been digitized up to some point (x, y) on the coordinate grid, and that the unit steps "walked" by the turtle on the grid represent the best possible approximation to the continuous circle.

188

Appendix A:

the first quadrant of a circle, partly digitized Again, this assumption is always possible by putting (x, y) = (r, 0). Much like with the line, at that point the (vertical) unit step from (x, y) to (x, y + 1) may or may not represent again the best possible approximation. If it does not, it has to be preceded by a unit step from (x, y) to (x - 1, y), or more than one in the same direction. In order to decide, which of the x-coordinates to use to walk upwards, the equation of the continuous circle xr := (r2 - yr2)1/2 is evaluated at yr := y + 1/ 2 (i.e. halfway between y and y + 1). If xr > x - 1/ 2, then the choice is x, else one or more unit steps across are done beforehand. Again, the equation of the circle is not evaluated repeatedly, but instead, it is substituted into the above decision. A little algebra transforms this decision, in turn, to (r2 - yr2)1/2 > x - 1/ 2 (r2 - yr2) > x2 - x + 1/ 4 (r2 - y2 - y - 1/ 4) > x2 - x + 1/ 4 d(x, y) := 2(r2 - x2 - y2 + x - y) > 1 which again contains integers and integer operations only. To construct the algorithm to digitize circles, we are now prompted to use the very same pattern as the one for straight lines, but in contrast to the line, this time the function d(x, y) is quadratic in both x and y; its incremental evaluation requires second order forward differences (cf. A.0.3). With these premises, we use as a template the line algorithm refined during the second {xr := (r2 - yr2)1/2 } {unless radicands are 1 } {yr := y + 1/ 2 }

The algorithms

189

step (cf. A.0.0). To do so, we replace essentially the function d(x, y) that is part of the condition never to make the wrongchoice. Furthermore, we see that the choice is the right one at start by putting (x, y) = (r, 0), and that each iteration makes a progress towards arrivingat the end (x, y) = (0, r). Therefore,the followingalgorithm is correct:
METHOD [self: Circle]Digitize(with: RenderingTool); VAR x, y, d, dx, dy: INTEGER; BEGIN

x, y, d, dx, dy := self.r, 0, 2*self.r, 4*self.r, 0; with.JumpTo(x, y); WHILE y < self.r DO WHILE d 1 DO DEC(x); WalkTo(x, y); DEC(dx, 4); INC(d, dx) END; { H } INC(y); with.WalkTo(x, y); INC(dy, 4); DEC(d, dy); END; WHILE x > 0 DO DEC(x); WalkTo(x, y); DEC(dx, 4); INC(d, dx) END; END { Digitize }; It may seem to be a waste of (computing) time not to make the most of the circles symmetry, but on the one hand, consuming the pairs of coordinates may compare favorablyto any gain obtained from avoiding their re-computation, and on the other, doing a full quadrant at a time avoids the asymmetries of the termination condition at / 4 (cf. [Stamm89], 1.1). In a second step, we consider two ways to generalizethe algorithm to a full circle,located not necessarily at the origin.For the first way, again, we can make use of the fact that the assertion of the invariant H does not depend on the updating of the coordinates x and y. To digitize a quadrant of the circle located at (xc, yc), we simply have to initialize (x, y) to (self.xc + self.r, self.yc), instead of (self.r, 0). In contrast to the line algorithm, however, it is not overly simple to reflect properly the particular quadrant when updating x and y. In order to do a full circle sequentially - an implicit prerequisite for some of the rendering algorithms - we would need 8 parameters to parameterize the above code for the first quadrant (the start- and the end-point, together with unit vectors that correspond to the step up- and leftwards).The resulting code would look somewhat clumsy. For this reason, and in order not to use an array for intermediate storage of the first quadrant, we do not hesitate to replicate the code for the first quadrant. This constitutes the second way to generalizethe above algorithm. Without further algebra, the algorithm below can digitize sequentially a full circle located at any point.

190

Appendix A:

METHOD [self: Circle]Digitize(with: RenderingTool); VAR x, y, d, dx, dy: INTEGER; BEGIN

x, y, d, dx, dy := self.xc + self.r, self.yc, 2*self.r, 4*self.r, 0; with.JumpTo(x, y); WHILE y self.yc + self.r DO WHILE d 1 DO DEC(x); WalkTo(x, y); DEC(dx, 4); INC(d, dx) END; INC(y); with.WalkTo(x, y); INC(dy, 4); DEC(d, dy); END; WHILE x self.xc DO DEC(x); WalkTo(x, y); DEC(dx,4); INC(d,dx) END; d := - d; WHILE x self.xc - self.r DO WHILE d 1 DO DEC(y); WalkTo(x, y); DEC(dy, 4); INC(d, dy) END; DEC(x); with.WalkTo(x, y); INC(dx, 4); DEC(d, dx); END; WHILE y self.yc DO DEC(y); WalkTo(x, y); DEC(dy, 4); INC(d, dy) END; d := - d; WHILE y self.yc - self.r DO WHILE d 1 DO INC(x); WalkTo(x, y); DEC(dx, 4); INC(d, dx) END; DEC(y); with.WalkTo(x, y); INC(dy, 4); DEC(d, dy); END; WHILE x self.xc DO INC(x); WalkTo(x, y); DEC(dx,4); INC(d, dx) END; d := - d; WHILE x self.xc + self.r DO WHILE d 1 DO INC(y); WalkTo(x, y); DEC(dy, 4); INC(d, dy) END; INC(x); with.WalkTo(x, y); INC(dx, 4); DEC(d, dx); END; WHILE y self.yc DO INC(y); WalkTo(x, y); DEC(dy, 4); INC(d, dy) END; END { Digitize }; As far as conciseness is concerned, this algorithm certainly cannot beat the one for lines. However, it uses only integers and the cheapest of their operations, which makes it very efficient. For comparison purposes, and in particular for the proof, why a choice among two horizontally (or vertically)adjacent coordinate points is sufficient to decide for the best possible approximation to the continuous circle,the reader may refer to [Bresenham77].

The algorithms

191

A.0.2 Ellipses & arcs The preceding two sub-sections have discussed the digitizingof lines and circles in detail. They have done so irrespective of the mapping under which the respective objects have to digitize themselves. While applying a linear mapping to the end-points of a line is straightforward,the particular formulation for circles (the tuple [xc, yc, r]) proves somewhat unwieldyfor the same venture. In contrast to that, transforming Bzier and spline curves is simple again: It is a well-known fact that it suffices to apply the mapping to all their knots in order to transform them. Of course, in order to map the circle, we could understand the latter as a quadratic form xTQx = 1, with Qij = ij/ r2, and apply the mapping to Q accordingly.But this then means that we have to develop an algorithm for digitizing general ellipses, for this is to what circles will transform under an arbitrary linear mapping. (Actually, we have done so already in [Stamm89]. However, in there the underlyingcoordinate model was the grid-pointmodel, while now we prefer the checker-board model; which is why we would at least have to reviseour algorithm.) Now, general ellipses are not sufficientlyimportant for font-scaling to deserve an individual algorithm. They are included merely for completeness. Much like we propose to do with any curvilinearglyph(cf. 4.4.1), however, it is possible to approximate them in terms of cubic polynomials, which we need anyhow(cf. A.0.3). Within the bounds of quantization, we have determined experimentally that this approximation corresponds to the exact circles for all practical purposes in medium- and low-resolution font-scaling. On top of that, already our earlier algorithm for general ellipses required double precision reals, as will the one for third order polynomials. Therefore,we are not loosing too much by representing general ellipses in terms of Bzier curves. (For a formal argument, the point to set about is the error p(u) - (cos, sin), with = u/ 2.) We start with representing the first quadrant of a unit circle in terms of a cubic Bzierpolynomial.Acubic Bzierpolynomial is defined as follows: p(u) := V0(1 - u)3 + 3V1(1 - u)2u + 3V2(1 - u)u2 + V3u3. In order to represent the first quadrant of a circle,its knots Vi take up qualitatively the followingpositions:

192

Appendix A:

the first quadrant of a circle, represented as a Bzier curve Quantitatively,the Vi are, in turn, (1, 0), (1, l), (l, 1), (0, 1), remembering that we consider a unit circle (cf. also 3.2, 4.4.1, and A.3). Remains to determine the parameter l. To do so, on the one hand, we substitute the Vi into p(u), p(u, l) = (1, 0)(1 - u)3 + 3(1, l)(1 - u)2u + 3(l, 1)(1 - u)u2 + (0, 1)u3 and on the other hand, we know that for reasons of symmetry a particle that moves along p(u, l) will arriveat (xu, yu) = (1/ 21/2 , 1/ 21/2 ) for u = 1/ 2. Thus, solving any of the two linear equations p(1/ 2, l) = (xu, yu) = (1/ 21/2 , 1/ 21/2 ) e.g. the xu-component, yieldsthe missing parameter l xu = (1 - u)3 + 3(1 - u)2u + 3l(1 - u)u2
1/ 1/2 2

= 1/ 8 + 3/ 8 + l3/ 8

{xu = 1/ 21/2 , u = 1/ 2 }

l = 4/ 3(21/2 - 1) = 0.5522 to formulate the first quadrant of a circle in terms of a Bzierpolynomial. With that, the digitizing of general ellipses can be put down to that of Bzier curves. Let (xc, yc) denote the coordinates of the center of the ellipse, a and b its major and minor axis, and the angle by which the major axis is inclined with respect to the x-axis,

The algorithms

193

Ellipse = CLASS [super = GraphicalObject] xc, yc, a, b: INTEGER; phi: REAL; END; then the knowledgeof l leads to the followingalgorithm:
METHOD [self: Ellipse] Digitize(with: Turtle); VAR l, c, s: REAL; x, y: ARRAY 6 OF REAL; i, X, Y: INTEGER; b: Bezier; BEGIN

l, c, s := 4*(Math.sqrt(2) - 1)/3, Math.cos(self.phi), Math.sin(self.phi); x[0], x[1], x[2], x[3], x[4], x[5] := 1, 1, l, 0, - l, -1; y[0], y[1], y[2], y[3], y[4], y[5] := 0, l, 1, 1, 1, l; b.closed := TRUE; b.n := 12; FOR i := 0 TO 5 DO X := ROUND(self.a*c*x[i] - self.b*s*y[i]); Y := ROUND(self.a*s*x[i] + self.b*c*y[i]); b.x[i], b.x[i+6] := self.xc + X, self.xc - X; b.y[i], b.y[i+6] := self.yc + Y, self.yc - Y; END; b.Digitize(with); END { Digitize }; Followingthe line of least programming, we can do arbitrary circular and elliptical arcs with little extra algebra as well. The idea is the following:Acircular arc can be partitioned in a sequence of null or more full quadrants, possibly initiated by a fraction of a full quadrant, and maybe terminated by another fraction of a full quadrant. Clearly, all that remains to be learned is how to formulate a fraction of a full quadrant in terms of a Bzier curve.This particular situation is sketched below:

a fraction of a quadrant of a circle, represented as a Bzier curve

194

Appendix A:

Denoting the fraction of the full quadrant with the angle , the Vi are, in turn, (1, 0), (1, l), (cos, sin) + l(sin, -cos ), (cos, sin), validfor arcs of unit radius (cf. once more 4.4.1 and A.3). To determine the parameter l as a function of , again we substitute the Vi into p(u, l), on the one hand, p(u, l) = (1, 0)(1 - u)3 + 3(1, l)(1 - u)2u + 3((cos, sin) + l(sin, -cos ))(1 - u)u2 + (cos, sin)u3 and on the other hand, we assert that for reasons of symmetry and for u = 1/ 2, (xu, yu) = (cos/ 2, sin / 2). Hence we have to solve any of the two linear equations p(1/ 2, l) = (cos/ 2, sin / 2) for l. Using again the x-component, this leads to the missing parameter l as follows xu = (1 - u)3 + 3(1 - u)2u + 3(cos + lsin)(1 - u)u2 + cosu3 cos/ 2 = 1/ 8 + 3/ 8 + 3/ 8(cos + lsin) + 1/ 8cos cos/ 2 = 1/ 2 + 1/ 2cos + 3/ 8lsin
cos/ 2 = cos2(/ 2) + 3/ 4lsin / 2cos/ 2 cos = 2cos2(/ 2) - 1, sin = 2sin / 2cos/ 2 } xu = cos/ 2, u = 1/ 2 }

1 - cos/ 2 = 3/ 4lsin / 2
4/ 3tan
/

4 = l,

(1 - cos)/sin = tan / 2 }

which for = / 2 reduces to the previous formula for l. With that, and a minimal additional programming effort,the way should be paved for arbitrary circular and elliptical arcs. Likewise, Bzier curves can be employed to digitize circles defined by two diametrically opposed points on the circle's perimeter. This may prove useful in an interactive environment. It is thus high time to convey how to do the Bziercurves themselves.

The algorithms

195

A.0.3 Bzier & spline curves In the last sub-section on digitizingalgorithms, we illustrate an algorithm for digitizingcubic polynomials Polynomial= CLASS [super = GraphicalObject] x0, y0, x1, y1: INTEGER { start- and end-point }; xa, ya, xb, yb, xc, yc, xd, yd: REAL{ coefficients }; END; defined with real coefficients. They constitute the common denominator for third order Bzier curves and natural spline curves. The coefficientsa are chosen to be real because this is simply indispend sable if the polynomials are used for natural splines. For use with Bzier curves, particularly if their knots Vi are rounded before they are used (cf. 4.2, A.3), integers would do, a := V3 - 3V2 + 3V1 - V0 b := 3(V2 - 2V1 + V0) c := 3(V1 - V0) d := V0 or at most some form of rational numbers, if the round-before-use rule is overridden (cf. 4.3.1). The pattern which underlies the algorithm for digitizingcubic polynomials in parametric form p(t) := at3 + bt2 + ct + d differs substantially from the one employed for lines or circles. Parametric form means that we cannot simply give a particular y-coordinate and subsequently decide for this or that x-coordinateaccordingly.(Of course we could solve p y(t) = y for t and substitute the resulting t into p x(t), but this is too inefficient,since it requires iterations - albeit a square root only.) The key idea is to use forwarddifferencing,rather than explicitevaluation Horner's scheme notwithstanding -, for we are interested in evaluating the polynomial at closely and regularly spaced intervals. The first order forward differenceof the polynomial p(t) is defined as (1) (t) := p(t + 1) - p(t) = 3at2 + (3a + 2b)t + a + b + c

196

Appendix A:

which is a quadratic polynomial in t - the terms with the highest powers of t cancel. The second and the third order forwarddifferencesare (2) (t) := (1) (t + 1) - (1) (t) = 6at + 6a + 2b (3) (t) := (2) (t + 1) - (2) (t) = 6a analogously.The important fact now is that the third order forwarddifference (3) (t) is constant in t. With that, we can use the recurrence relations (i-1) (t + 1) = (i-1) (t) + (i) (t), for i > 0 and (0) (t) := p(t) to obtain lower ordered forwarddifferences out of higher ordered ones by addition. In a first step, this gives rise to the following algorithm using forwarddifferencing:
METHOD [self: Polynomial]Digitize(with: Turtle); VAR x, y, dx1, dy1, dx2, dy2, dx3, dy3: REAL; xi, yi: INTEGER; BEGIN

x:= self.x0; dx1:= self.xa+self.xb+self.xc; dx3:= 6*self.xa; dx2:= dx3+2*self.xb; y:= self.y0; dy1:= self.ya+self.yb+self.yc; dy3:= 6*self.ya; dy2:= dy3+2*self.yb; xi := self.x0; yi := self.y0; with.JumpTo(xi, yi); WHILE (xi self.x1) OR (yi self.y1) DO x := x + dx1; dx1 := dx1 + dx2; dx2 := dx2 + dx3; y := y + dy1; dy1 := dy1 + dy2; dy2 := dy2 + dy3; xi := FLOOR(x + 1/ 2); yi := FLOOR(y + 1/ 2); with.WalkTo(xi, yi); END; END { Digitize }; Notice that forward differencingis used already in A.0.0 and A.0.1 in order to evaluate H incrementally. In e.g. the circle algorithm, dx and dy are the first order forward differences, while the second order forward differences are the constants 4 in both x- and y-directions(up to a sign). In contrast to the line and circle algorithms, however, forwarddifferencing is used for the cubic polynomials to evaluate not a H function, but the polynomial itself.With that, the first order forwarddifferencescorrespond to the unit steps to be fed into the turtle. This step may be too large (but interpolation is at least ambiguous, cf. 2.0.6) or too small (the unit steps being quantized, the same integer step is likely to be obtained more than once, which is at least inefficient). Therefore,the above algorithm is nowhere near sufficient for practical purposes.

The algorithms

197

A most fortunate solution to this problem is advocated in [Gonczarowski89]. Since we shall base a further refinement on this approach, we repeat brieflyits derivation. The key idea is the following:In the above definitions of the forward differences it was assumed that the parameter t increases at uniform steps d = 1. For reasonably smooth natural spline curves, this is not totallyunfounded. Typically, natural splines are parameterized such that , 0 t 1, y1) - (x0, y0) (x where the upper limit is called the chord length. Like this, d = 1 roughlycorres ponds to (1) (t)1, within an order of (binary) magnitude or so. Still,adding this particular forwarddifference(1) (t) to p(t) may cause p(t) to take too short a step, in which case most probably we can double d, or too long a step, in which case we should halve d. In order to determine the consequences on the forwarddifferences in case the parameter step d is doubled or halved,we first rewritethe former as a function of d: (1, d) (t) := (0) (t + d) - (0) (t) = 3adt2 + (3ad2 + 2bd)t + ad3 + bd2 + cd (2, d) (t) := (1) (t + d) - (1) (t) = 6ad2t + 6ad3 + 2bd2 (3, d) := (2) (t + d) - (2) (t) = 6ad3 In this representation, it is a matter of straightforward substitution to derive the followingrecurrence relations for adapting the forwarddifferences to new parameter steps d:= 2d or d:= d/ 2 respectively: (1, 2d) (t) = 2(1, d) (t) + (2, d) (t) (1, d/2) (t) = 1/ 2((1, d) (t) - (2, d/2) (t)) (2, 2d) (t) = 4((2, d) (t) + (3, d) ) (2, d/2) (t) = 1/ 4(2, d) (t) - (3, d/2) (3, 2d) = 8(3, d) (3, d/2) = 1/ 8(3, d) With these recurrence relations, the above algorithm can be refined in a second step to use adaptive forward differencing, that is, adapting the forward differences on-the-fly, whenever (1) (t) should have caused too small or too large a step:
METHOD [self: Polynomial]Digitize(with: Turtle); VAR x, y, dx1, dy1, dx2, dy2, dx3, dy3: REAL; xi, yi: INTEGER; BEGIN

x:= self.x0; dx1:= self.xa+self.xb+self.xc; dx3:= 6*self.xa; dx2:= dx3+2*self.xb; y:= self.y0; dy1:= self.ya+self.yb+self.yc; dy3:= 6*self.ya; dy2:= dy3+2*self.yb; xi := self.x0; yi := self.y0; with.JumpTo(xi, yi);

198

Appendix A:

WHILE (xi self.x1) OR (yi self.y1) DO WHILE (ABS(dx1) < 1/4) & (ABS(dy1) < 1/4) DO { adapting step }

dx1 := 2*dx1+dx2; dx2 := 4*(dx2+dx3); dx3 := 8*dx3; dy1 := 2*dy1+dy2; dy2 := 4*(dy2+dy3); dy3 := 8*dy3; END; WHILE (ABS(dx1) > 1/2) OR (ABS(dy1) > 1/2) DO { adapting step } dx3 := dx3/8; dx2 := dx2/4- dx3; dx1 := (dx1- dx2)/2; dy3 := dy3/8; dy2 := dy2/4- dy3; dy1 := (dy1- dy2)/2; END; x := x + dx1; dx1 := dx1 + dx2; dx2 := dx2 + dx3; y := y + dy1; dy1 := dy1 + dy2; dy2 := dy2 + dy3; xi := FLOOR(x + 1/ 2); yi := FLOOR(y + 1/ 2); with.WalkTo(xi, yi); END; END { Digitize }; An immediate advantage of this refinement is the fact that its accuracy is independent of the parameter interval. It does not matter whether the polynomial results from a spline, parameterized as above, or a Bzier curve whose parameter u typicallycovers the interval [0, 1]. Especially for screen fonts, a few adaptation steps will suffice to re-parameterizethe polynomial. Before refining the above algorithm any further, we emphasize that the reals used in this algorithm have to be double precision (64-bit) reals necessarily.To see this, consider verticesVi of a Bziercurve on a modern screen or a medium resolution printer, hence coordinates of order 210 12. Now recall 2 the third order forward difference (3) (t) = 6a, with a = V3 - 3V2 + 3V1 - V0. Thus (3) (t) is of order 210 12 as well, and so is the chord length. With that, 2 12, the number of adaptation steps required until (1) (t) 1/ 2 is of order 10 (3) (t) drop to anything as low as order 2-24 . Together with which may make the actual polynomial itself, whose values are of order 210 12, this covers a 2 range of well above what the mantissa of 32-bit reals can represent; remembering that the higher ordered forward differences are added to the lower ordered ones. Therefore, we are well advised to use double precision reals, unless we are sure that the algorithm is going to be used for a rather limited range of coordinates only. Compared to the algorithms for digitizinglines and circles, the above algorithm for third order polynomials seems to treat the quantization effects rather roughly. The aforementioned algorithms used to evaluate a function relating to the continuous object at a real value yr = yi + 1/ 2. As mentioned earlier, we can but approximate this (unless we care to do lengthy iterations). Yet, as long as the curvature is not too high (with respect to the targeted devicere-

The algorithms

199

solution), there is a rather simple improvement. Assume the above algorithm has lead to a pair of y-coordinatesy and y + dy1, lyingon either side of a threshold yi + 1/ 2 respectively.Then a simple linear interpolation can take us considerably closer to the actual value xr(yi + 1/ 2) than any other improvement at the same price. Together with a few minor algorithmic changes, in a third steps this results in the followingrefinement:
METHOD [self: Polynomial]Digitize(with: Turtle); VAR x, y, X, Y, dx1, dy1, dx2, dy2, dx3, dy3: REAL; xi, yi Xi, Yi, xXi: INTEGER; BEGIN

x:= self.x0; dx1:= self.xa+self.xb+self.xc; dx3:= 6*self.xa; dx2:= dx3+2*self.xb; y:= self.y0; dy1:= self.ya+self.yb+self.yc; dy3:= 6*self.ya; dy2:= dy3+2*self.yb; xi := self.x0; yi := self.y0; with.JumpTo(xi, yi); WHILE ABS(self.x1 - xi) + ABS(self.y1 - yi) > 1 DO WHILE (ABS(dx1) < 1/4) & (ABS(dy1) < 1/4) DO { adapting step } dx1 := 2*dx1+dx2; dx2 := 4*(dx2+dx3); dx3 := 8*dx3; dy1 := 2*dy1+dy2; dy2 := 4*(dy2+dy3); dy3 := 8*dy3; END; WHILE (ABS(dx1) > 1/2) OR (ABS(dy1) > 1/2) DO { adapting step } dx3 := dx3/8; dx2 := dx2/4- dx3; dx1 := (dx1- dx2)/2; dy3 := dy3/8; dy2 := dy2/4- dy3; dy1 := (dy1- dy2)/2; END; X := x + dx1; Xi := FLOOR(X + 1/ 2); Y := y + dy1; Yi := FLOOR(Y + 1/ 2); IF (Xi xi) OR (Yi yi) THEN IF (Xi xi) & (Yi yi) THEN { linearly interpolate } xXi := FLOOR(dx1*((yi + Yi)/2 - y)/ dy1 + x + 1/ 2); IF xXi = xi THEN with.WalkTo(xi, Yi); ELSE { xXi = Xi } with.WalkTo(Xi, yi); END; END; with.WalkTo(Xi, Yi); END; xi := Xi; dx1 := dx1 + dx2; dx2 := dx2 + dx3; yi := Yi; dy1 := dy1 + dy2; dy2 := dy2 + dy3; END; IF ABS(self.x1- xi) + ABS(self.y1- yi) > 0 THEN { stop-gap for round-offs } with.WalkTo(self.x1, self.y1); END; END { Digitize };

200

Appendix A:

We have determined this to be sufficient for accurate digitizing in case a simultaneous unit step in both x- and y-directionhas been calculated, provided the Bzier polynomials are employed such as defined in 3.2.3. Otherwise, we may have to restrict the first order forwarddifferencesto take steps even smaller than 1/ 2, at the expense of extra computing time. In a further refinement, we might implement 64-bitfixed point binary arithmetic, as suggested in [Gonczarowski89 or rely on modern 64-bitprocessors ], to become more widespread. For screen fonts, we may be contenting ourselves with 32-bitarithmetic; they are only a couple of pixelswide. Meanwhile, several papers have been published, dedicated exclusively to the digitizingof spline curves, and addressing both speed and precision issues. The reader may refer to [Klassen91a], [Klassen91b], and [Rappoport91]. [Hersch91] presents a solution that better approximates the value xr(yi + 1/ 2), but at the computational expense of recursivelysubdividing the interval of the polynomial to be digitized.Be it as it may, though, this is not bound to take us any closer to the main goal of low-resolution font-scaling. Therefore, this concludes the section on digitizingalgorithms and lets us turn to rendering algorithms.

A.1 Rendering tools A.1.0 Recording pixel boundaries Upon revising our first graphics library, we did not intend to throw ourselves into an object-oriented interface; not at all if only for the sake of yet another OOP style approach, and not even when it started to look as if OOP style provided the most appropriate means of encapsulating a filling algorithm, at least. Only when we had convinced ourselves that it is possible to kill two birds with a single stone - the "second bird" being the possibility to overcome the "off-by-one" bounding boxes of outlined objects by concept, rather than by industriousness - the ice was broken. From that point on nothing was left standing in the way of a fullygrownobject-oriented graphical toolbox. The simplest purpose for which we have separated the pure digitizingfrom the actual rendering, though, is for recording the digitized coordinate pairs or the "Manhattan moves" - without any rendering at all. This may sound like a triviality, but we could become rather embarrassed if we were not enabled do so for assembling characters out of pre-rastered contours and glyphs. Already at this point, therefore, intercepting conventional drawing routines into digitizingand rendering tasks proves quite valuable. The simplest turtle used in our approach

The algorithms

201

StepRecorder = CLASS [super = RenderingTool] n: INTEGER; x, y: ARRAYOF INTEGER; END; stands in for this very purpose. Its "algorithm" is subsumed in the following methods:
METHOD [self: StepRecorder]Setup (on: Device); BEGIN

super.Setup(on); DIM(self.X, 16); DIM(self.Y, 16); self.n := 0; END { Setup };


METHOD [self: StepRecorder]JumpTo (x, y: INTEGER); BEGIN

self.WalkTo(x, y); END { JumpTo };


METHOD [self: StepRecorder]WalkTo (x, y: INTEGER); BEGIN IF self.n LEN(self.X) THEN extend self.X and self.Y to doublesize END;

self.X[self.n], self.Y[self.n] := x, y; INC(self.n); END { WalkTo }; Its efficiencydepends somewhat on the presence or absence of the possibility to declare and extend open arrays, such as hinted at in the WalkTo method. Also, memory usage can be reduced if the StepRecorderturtle records but the first coordinate pair, followedby a sequence of encodings of the four points of the compass, like the Freeman code: Since the turtle is supposed to do unit steps only,two bits (rather than two integers) are enough to record to which of its four immediate neighbors a particular step is leading. Remains to point out that such a StepRecorder turtle represents on its own a means of caching pre-rastered contours and glyphs(cf. 5.2).

A.1.1 Outlining & bounding boxes Recallingsection 2.0, the "off-by-one" bounding boxes are a problem to be blamed for the wrongnotion of pixels and coordinates. Furthermore, section 2.0

202

Appendix A:

has illustrated, how a circle with a correct bounding box should be outlined, and why. Starting out from the pixel boundary that has been digitized according to the checker-board model, the respective turtle has to place the pixels just inside the rectilinear region.Knowing the digital boundary to be positively oriented (cf. 3.1.5 and A.1.3), this is equivalent to placing the pixels on the left of the digital boundary.

"inside" is equivalentto placing the pixels on the "left" of the boundary To do so, we have to know about the orientation of the contour at least the direction of the current unit step. This is easy to accomplish if the turtle always remembers the last coordinate pair digitized.Togetherwith the current coordinate pair, this defines the required unit vector, and together with a second vector, obtained by turning left the first one by / 2, this spans the unit area of the desired pixel.

on the "left" of the boundary, spanning the desired area Rotating a vector may seem a bit elaborate, but the respective rotation itself is

The algorithms

203

trivial,as can be seen in the method WalkTo below, and all in all this avoids us havingto implement separately four cases for the four directions above. The rest of the algorithm for outlining rectilinear regions with correct bounding boxes essentially serves to assert that already the first call to the WalkTomethod can compute the direction of the current unit step. We encapsulate this algorithm to draw hairlines in a turtle Quill = CLASS [super = RenderingTool] x, y: INTEGER { last coordinate pair digitized }; END; and overwrite its methods as follows:
METHOD [self: Quill] JumpTo (x, y: INTEGER); BEGIN

self.x, self.y := x, y; END { JumpTo };


METHOD [self: Quill] WalkTo (x, y: INTEGER); VAR X, Y: INTEGER; span: Area; BEGIN

X, Y := self.x - y + self.y, self.y + x - self.x; span.Setup(Min(x, X), Min(y, Y), Max(x, X), Max(y, Y)); self.on.Fill(span); self.x, self.y := x, y; END { WalkTo }; We can see at least three refinements to the admittedly concise algorithm above. First, the raster devices permitting, it may be considerably more efficient not to draw unit areas, but single pixels. For these purposes, our raster devicescomprise the methods
METHOD [self: RasterDevice]Set (x, y: INTEGER; with: Pattern; in: Mode); METHOD [self: RasterDevice]Get (x, y: INTEGER) : BOOLEAN;

to set and get single pixels. The method Set is merely an optimization of the method Fill for the simple but frequent special case wherethe area to be filled is the unit area, while the method Get roughly performs the opposite operation (cf. A.2.1). Both methods have in common that the pair (x, y) denotes the lower left corner of the unit area involved.With these premises, the Quills WalkTomethod can be optimized as follows,

204

Appendix A:

METHOD [self: Quill] WalkTo (x, y: INTEGER); BEGIN self.on.Set((x + self.x - y + self.y) DIV 2, (y + self.y + x - self.x) DIV 2);

self.x, self.y := x, y; END { WalkTo }; in hopes that i DIV j is equivalent to FLOOR(i/ j) - which is the case (cf. 4.0.2). Second, the above WalkTo method will draw pixels twice upon doing a / 2 turn left. This is not just superfluous, but it will cause the impression of missing pixels if the fillingmode is invert. It can be avoided if the quill remembers not only the last coordinate pair digitized, but also the last unit vector computed. Initializing the last unit vector to the null vector, a simple vector product indicates always, whether the Quill is doing a turn left (in which case it must not draw a pixel), or not (in which case it is going in a straight line or around a turn right, hence it must draw a pixel - the case of a "round trip" not being anticipated for font-design).

a turn right, a straight move, a turn left, and a "round trip" Addingthe pair (dx, dy) to the Quillsinstance variables,denoting the last unit vectorcomputed, eventuallythis leads to the followingrefined methods:
METHOD [self: Quill] Setup (on: Device); BEGIN

super.Setup(on); self.x, self.y := - , - ; END { Setup };


METHOD [self: Quill] JumpTo (x, y: INTEGER); BEGIN IF (xself.x) OR (yself.y) THEN self.x, self.y, self.dx, self.dy:= x,y,0,0 END; END { JumpTo };

The algorithms

205

METHOD [self: Quill] WalkTo (x, y: INTEGER); VAR dx, dy: INTEGER; span: Area; BEGIN

dx, dy := x - self.x, y - self.y; IF self.dx*dy self.dy*dx THEN self.on.Set((x + self.x - dy) DIV 2, (y + self.y + dx) DIV 2); END; self.x, self.y, self.dx, self.dy := x, y, dx, dy; END { WalkTo }; Notice that in case we should be concerned about the cost of the refinement, we have the freedom of choice: Depending on whether we have to rely on the correct implementation of the invert mode, or whether we are dependent on maximum speed, we can opt for this or that particular realization of the quill, irrespective of the gamut of digitizing algorithms developed already. Even if there should be only one quill supplied, we are not left out in the cold. We simply implement our own quill and use it instantly instead of the one provided, not touching the original graphics library in the least. (Incidentally, solvingthe invert problem is related closelyto filteringout knees or elbows,or adding them in; whateverwe want,now we can centralize the respectiveintelligence in a rendering tool, rather than scattering it over all the digitizingalgorithms.) Third, picking up the issue of speed again, we may wish the quill to detect that it is drawing a sequence of verticallyor horizontally adjacent unit areas. Such cases may be drawn more efficientlyby a single rectangle of unit width or height respectively.The important point to be investigatedhere is the break even point: How many pixels can we draw on a particular (!) raster device before it is worthwhilefillinga single (thin) rectangle instead. To whateverresult the respective investigations might come, at least all graphical objects will be able to profit thereof, also the cubics (cf. A.0.3). Considering their complexity, the latter are most probably not the first place we would venture to set about when optimizing a graphics librarywith a conventional interface.

A.1.2 Filling simple objects For the purpose of technical illustrations - and even for serious font-scalingwe have to fillgeometrical objects which are often quite simple.

206

Appendix A:

simple solid objects (top left), thick lines and curves (top right), and parts of characters (bottom) Now that we know how to digitize such objects, it should be fairly straightforwardto fillthem as well. In fact, in this sub-section we shall offerproof thereof. So much the more we can but find it amazing that a lot has been written about filling algorithms, or scan-conversion algorithms specialized for filling purposes. Maybe this is because "( only a few computer graphics specialists ) have real experience with them [the scan-conversion algorithms] ( as )", [Hersch88] has put it. So much the less we hesitate to admit having spent therefore a good deal of scrutiny on the subject ourselves. As one of the results, unlike the respective literature, we talk about pixel boundaries, rather than pixels themselves. In doing so, we avoid a whole gamut of complications, as illustrated in Chapter 2. Of course we come to the same results as [Hersch88] or [Watt89] (the latter talking about "linear sequences of pixels with (or without) gaps", suited for either fillingor outlining), but the access is different,and we are convinced that - once understood - it is quite a bit more painless. The fact that all the above geometrical objects have in common with one another is to be simply connected regions which are convex in y-direction(or yconvex for short). Once they are digitized and filled, such rectilinear regions may be understood as a stack of orthogonal rectangles.

The algorithms

207

a coarsely scaled triangle, resolved into orthogonal rectangles of unit height Now that the fillingof single orthogonal rectangles is an elementary operation of the raster devices,all that the fillingalgorithm has to do is to resolvethe serialized digital boundaries into orthogonal rectangles of unit height. We start the discussion of filling algorithms therefore with an algorithm that can fill simply connected convex regions by "spilling a single drop of paint". We encapsulate this algorithm in a turtle InkSpot = CLASS [super = RenderingTool] y: INTEGER { last y-coordinate digitized }; Y: ARRAYOF INTEGER { information leap }; END; adhering to the interface established in 2.2. Because of the topologyof the objects under consideration, this turtle will perform either two verticalunit steps on a particular y-coordinate,or none at all. These two unit steps correspond to the left and the right end of one of the (unit height) orthogonal rectangles to be filled.

two unit steps delimit an orthogonal rectangle of unit height

208

Appendix A:

Since the filling cannot be accomplished unless the x-coordinates of both of these unit steps are known, we have to provide an information leap. This information leap consists of an array of integers and stores either of the two xcoordinates involved. The algorithm then consists of the simple distinction whether a vertical unit step on a particular y-coordinate has taken place already, or not. If so, both x-coordinatesare known, and the respective orthogonal rectangle can be filled. If not, the respective x-coordinateis stored in the array. For memory effectiveness (the programming language permitting), the array may be allocated at run-time, based upon the height of the targeted raster device(a so-called open array). Here are the respectivemethods of the turtle:
METHOD [self: InkSpot] Setup (on: Device); VAR i: INTEGER; BEGIN

super.Setup(on); DIM(self.Y, on.height); FOR i := 0 TO LEN(self.Y)-1 DO self.Y[i] := - END; END { Setup };


METHOD [self: InkSpot] JumpTo (x, y: INTEGER); BEGIN

self.y := y; END { JumpTo };


METHOD [self: InkSpot] WalkTo (x, y: INTEGER); VAR X, Y: INTEGER; span: Area; BEGIN IF y self.y THEN { it is a vertical unit step } Y := (self.y + y) DIV 2; X := self.Y[Y]; IF X = - THEN { it is the first vertical unit step from self.y or to y }

self.Y[Y] := x; ELSE { it is the second vertical unit step from self.y or to y } self.Y[Y] := - ; span.Setup(Min(x, X), Y, Max(x, X), Y + 1); self.on.Fill(span); END; self.y := y; END; END { WalkTo };

The algorithms

209

With this remarkably simple algorithm, all the above graphical objects can be filled. To do so, all their constituent contours are requested to digitize themselves with this particular turtle. Thereby, with none of the contours we face "odd parity"-typeproblems at the vertices. Besides the simplicity, the overall efficiencycompares favorablyto that of customized algorithms for drawing that is, filling- thick lines or arcs. It looks as if we could have the cake and eat it, too. This remark concludes the easier part of the section on rendering algorithms - or what we thought would suffice. In fact, it does not, hence an "advanced turtle" is introduced next.

A.1.3 Filling & winding numbers The turtle introduced in A.1.2 can fill simply connected y-convex regions with "a single drop of paint". Incidentally, by using invert mode, and maybe a shadow bitmap, it can fillthe two figures belowas well,

Times "H" and "O" as outlines (left), and filled (right) even though the "H" is not y-convex, or the "O" is not simply connected. The explanation for this fortunate circumstance is the fact that invertingis a symmetric operation. For the above characters this means that it is irrelevantwhether the letterforms or the counterforms are inverted first. When applied to fill the "O", this is easy to see:

XOR

XOR

When applied to fill the "H", the turtle might be requested to render the digitized boundaries e.g. in the followingorder,

210

Appendix A:

Various stages of digitizing a Times "H" (from left to right) which,if we could see it on the shadow bitmap, would correspond to the intermediate stages of fillingbelow,

Various stages of filling a Times "H" (from left to right) leading nonetheless to the correct result. The skeptical reader is encouraged to go through other scenarios, particularly other sequences of digitizing the contours of the "H". The result will always be the same. It may be objected justifiably that in the end this is a waste of time, since this procedure may lead to paint and erase the counterforms. This is true only for large type sizes at high resolution. As soon as we do screen fonts - the principal goal of the present work - the pixel spans to be filled will hardly exceed the machine word size. It is thus six of these and half a dozen of those whether the two spans of pixels to be filled belong to the two stems of the "H" (such as is the case in the lower part of the above example),or to its letter-and counterform respectively(as in the upper part of the example). In either case only a single memory word will be manipulated. For larger fonts, at least the resulting flickering can be made invisible, if the filling is done in the background. Be it as it may, there is much more serious a disadvantage than efficiency: Some characters are modeled with self-intersections,such as the ones below.

Times "&" and "8" as outlines (left), and filled (right)

The algorithms

211

To model these characters with non-disrupted splines may be the natural way to design them (as far as the naturality of curve classes is concerned, cf. 3.2). Therefore, we should find a way to fill them properly, at least in view of the outline-font editor (cf. also 3.1.5). The solution to this problem lies in the answer to the question, "what is an interior pixel?". PostScriptsfillingcommands may give a hint [Adobe85]. The fill command considers a pixel to be an interior pixel if its winding number is non-zero, hence the term non-zero winding number rule. The eofill command understands pixels with odd winding numbers to be interior pixels, hence the term even-odd winding number rule. Formally,the winding number n(, P) of a point P with respect to the contour (that is, the boundary of the region at issue), is defined in terms of a complex line integral. Intuitively,we may think of a rubber band that starts at P and ends at a particle which is moving along the contour . Like this, the winding number denotes the number of times that the rubber band winds around the point P, if the entire boundary of the region at issue is run through once. The sign of the winding number specifies the contours orientation: A positive sign stems from counter-clockwisemotion along the contour, whilea negativesign originates in clockwisemotion. In order to see the practical benefit from this still rather theoretical consideration, we look at the above characters again, together with the winding numbers taken for "all" points in the plane. This results in a relief map of windingnumbers.

Times "&" and "8", together with their winding number reliefs Notice that "incidentally"the InkSpot turtle fills the "&" and the "8" according to the even-oddwindingnumber rule when used with invert mode. From the above illustration we can see that all that an advanced filling algorithm has to do is to comprehend the winding number relief. To do so, the resulting turtle needs a more extensiveinformation leap than the InkSpot turtle. For each y-coordinate it has to know all the x-coordinates at which a verticalunit step has taken place, together with their orientation (up or down).

212

Appendix A:

A Times "&" (left), the vertical unit steps and their orientation on a single ycoordinate (right), a section of the winding number relief or "sierra" (below) Once the x-coordinates are sorted in ascending order, the winding number "sierra" is processed sequentially in order to determine spans of interior pixels - according to either of the above fillingrules. This is easy, for a unit step down on the digitized boundary corresponds to a unit step up in the winding number "sierra",and vice versa (processing the "sierra" from left to right). Since we do not want to tie ourselves down to a limit for the number of vertical unit steps per y-coordinate,the actual implementation uses a single array only and sorts the unit steps by y and x (much like sorting a telephone directory by last and first name), in hopes that this helps to even out memory usage. By now, it should have become apparent that it is advantageous to centralize all the delicate knowledgeabout winding numbers into an intelligent rendering tool. We encapsulate in a turtle Inkpot = CLASS [super = RenderingTool] y: INTEGER { last y-coordinate digitized }; n: INTEGER { number of entries "in information leap" }; xy: ARRAYOF INTEGER { information leap }; END; the algorithm that can fill arbitrary regions with "any amount of ink". The methods below illustrate, how to pack the coordinate pairs into (32-bit-) integers

The algorithms

213

such as not to burden the sorting algorithm with two sorting criteria.
METHOD [self: Inkpot] Setup (on: Device); BEGIN

super.Setup(on); DIM(self.xy, 16*on.height { heuristic } ); self.n := 0; END { Setup };


METHOD [self: Inkpot] JumpTo (x, y: INTEGER); BEGIN

self.y := y; END { JumpTo };


METHOD [self: Inkpot] WalkTo (x, y: INTEGER); VAR xy: INTEGER; BEGIN IF y self.y THEN { it is a vertical unit step } xy := ((y + self.y) DIV 2 + 214)*216 + (x + 214)*2 + (self.y - y + 1) DIV 2;

self.xy[self.n] := xy { unless array has to be extended}; INC(self.n); self.y := y; END; END { WalkTo };
METHOD [self: Inkpot] Flush (); VAR i, n, x, y, xy, X, Y: INTEGER; span: Area; BEGIN

QuickSort(self.xy, self.n); n := 0 { winding number }; FOR i := 0 TO self.n - 1 DO xy := self.xy[i]; X, Y := xy MOD 216 DIV 2 - 214, xy DIV 216 - 214; IF n = 0 THEN x, y := X, Y { start pixel span }; END; INC(n, (xy MOD 2)*2 - 1) { update winding number }; IF n = 0 THEN span.Setup(x, y, X, y + 1) { end pixel span }; self.on.Fill(span); END; END; END { Flush };

214

Appendix A:

This amazinglyconcise algorithm can fill any multiply connected region with concavities and self-intersections, provided its boundary is oriented properly: letterforms and counterforms in opposing directions. The orientation is packed into the last bit (self.y - y + 1) DIV 2

in the WalkTo method, and unpacked from the last bit (xy MOD 2)*2 - 1 in the Flush method respectively,in a way which avoids conditional branches. There are two alternatives to the processing of the winding number relief. Instead of implementing the non-zero winding number rule, we can implement the even-odd rule as well. To do so, we replace the updating of the current windingnumber n by n := (n + (xy MOD 2)*2 - 1) MOD 2, that is, by a "modulo-2-addition".Fillingwith the even-oddrule like this needs neither invert mode nor shadow bitmaps. Asecond alternative,and at the same time a third fillingrule, restricts interior pixels to those whose winding number is strictly positive.This alternative follows from understanding letterforms as sets of pixels that "are there" in contrast to counterforms interpreted as sets of pixels that are "taken away". It constitutes an intuitive way to cut off excesses or parts of glyphs which for some reason or other project beyond another glyph.However, neither the evenodd rule nor the non-zero winding number rule can determine the interior pixels of such a situation correctly,as can be seen below.

A Times "N" consists of a pair of thin vertical stems and a thick diagonal one (left), plus a couple of serifs (right)...

The algorithms

215

...but the bottom right part needs trimming.

Adding another diagonal stem and a thick vertical one acting as "virtual counterforms" achieves the trimming, but leaves a "moat" around the glyph We can see that the windingnumber reliefhas to be interpreted such that only strictly positive winding numbers contribute to the pixel spans to be filled. To the best of our recollection, this filling rule is specified not even in PostScript [Adobe85], hence we call it the above-zero winding number rule. The solution is easy, though, now that we have understood how to process reliefs of windingnumbers. As with the other two fillingrules, we have to keep track of the current winding number n. In order to know where to start or end pixel spans, however, this time it is not enough to detect that n is zero. Rather, we have to know that it is goingfrom zero to above-zero in the very next update of n, or conversely,that it is coming from above-zero down to zero. In other words, we do not have to detect a particular state, but a transition from or to this state. Depending on their intended use, we may wish to devise different derived Inkpot turtles, implementing the various filling rules, or use but the implementation below. Here are the necessary changes to the Flush method:

216

Appendix A:

METHOD [self: Inkpot] Flush (); VAR i, n, dn, x, y, xy, X, Y: INTEGER; span: Area; BEGIN

QuickSort(self.xy, self.n); n := 0 { winding number }; FOR i := 0 TO self.n - 1 DO xy := self.xy[i]; X, Y, dn := xy MOD 216 DIV 2 - 214, xy DIV 216 - 214, (xy MOD 2)*2 - 1; IF (n = 0) &(dn = 1) THEN { changes from zero to above-zero } x, y := X, Y { start pixel span }; END; INC(n, dn) { update winding number }; IF (n = 0) &(dn = -1) THEN { changes from above-zero down to zero } span.Setup(x, y, X, y + 1) { end pixel span }; self.on.Fill(span); END; END; END { Flush }; There are a few more points that deserve being mentioned. First, if the above fillingalgorithm is used in an interactive environment (cf. 3.1.6), it may occur that the figures to be filled are not always closed sequences of (open) contours. While editing, there may be holes at which typicallythe ink will "leak" out, filling the entire exterior. This is a well-known phenomenon of simple flood-filltype algorithms. In the proposed fillingalgorithm, it is less critical at worst, in case of a parity error caused by open contours, every other scan line was experiencedto remain empty. To make it even more immune to such mishaps, we can re-synchronize the algorithm, setting the current winding number n back to zero deliberatelywheneverthe current y-coordinateshould differunexpectedlyfrom the last one. This cure is both cheap and effective. Second, the amount of memory used by the xy-array may be considered rather lavish in view of its main use for small characters. Once we know that the particular filling algorithm is going to be employed for font-scaling purposes exclusively, the size of the array can be related to the expected font height and the number of vertical unit steps. On top of that, we might implement the one-dimensional array as a matrix, instead. The reason to do so is the cost function of the sorting algorithm. For Quicksort this is O(nlog(n)) for large n, hence h arrays of w verticalunit steps cost hO(wlog(w)), whileone huge array of hw unit steps costs O(hwlog(hw)). This looks more expensive,but beware of the complexityof the fast sorting algorithms: For a small enough n, even a plain bubblesort may be faster than quicksort. Be it as it may, once more we

The algorithms

217

are free to implement our own turtle, which may not be able to fill large figures, but instead excels the more general one in speed. Finally, there is a theoretical aspect whose practical consequence was brought up already in 4.3.4 and 5.0.3. To ease their understanding, both letterand counterforms were interpreted to be sets of pixels. These sets are added or subtracted to the final image as appropriate. However, internally these set operations are put down to integer operations now; adding and subtracting deltas from the current windingnumber n. Now, for sets usually a - b -b +a whilefor integer numbers a - b =-b +a always. In the end, our fillingalgorithm leads to a symmetric set difference in the sense that it does not matter in which order the letterforms and the counterforms are added to and subtracted from the final image. This sounds promising, for it frees us from unpleasant surprises as experienced in A.2.0 with re-ordering expressions of bit sets. But beware, it is not a true symmetric set differencein the sense a b b a. Furthermore, we have to engineer ourselves a little luck when adding the serifs to the trimmed stems. In order to prevent some of these glyphs from "being drowned in the moat" upon using the above-zero windingnumber rule, a pair of the respectiveglyphshas to be piled up on top of one another.

In order not to be "trimmed off", the left stem, the top left serif, and the top right serif have to be added to the final image of the Times "N" in pairs

218

Appendix A:

In doing so, we see to it that the parts to be filled always raise "above zero". (The interested reader may figure out how to assemble a "W", then.) For the simple outline-font editor introduced in Chapter 3, such intellectual acrobatics are not necessary, the non-zero windingnumber rule will do by far.

A.2 Raster devices A.2.0 Copying orthogonal rectangles Raster devices are most probably the last place wherewe would expectto have to set about when devisinga new approach to font scaling. All that we require the raster devices to be able to do, essentially, is to fill a rectangular area with a pattern, or to copy a rectangular area from one place to another, so what? Yet on several occasions we were coerced into (re-)implementing Oberons existing raster operations - not only due to the absence of abstract device classes: The replicate pattern operation lacked the pin-point, copying a pattern would not allow to display characters larger than 32 pixels, and copyinga block of pixels "took a prohibitively long time" until at least the version for black-and-whitescreens was found to be correct! Actually, the solutions to these simple and well-definedproblems would be straightforward engineers jobs - if it were not for those subtle pitfalls. Not the least because of those we give a full implementation of the copy method for shadow bitmaps and (memory mapped) black-and-whitedisplay devices, in hopes that this is of any help to future implementers of raster operations. For the present sub-section, let us assume that not only the target device, but also the source device is (at least) a memory device, that is, a black-andwhite device whose contents is memory mapped in some way or other. Let us assume as well that the area to be copied has the same size on both the source and the target device. As to differingsizes, we put off the reader to the next sub-section. (In doing so, we ignore the deviceresolution, in accordance with the abstraction provided by the raster devices, that is, technical device independence, cf. 2.2.3.) Assume furthermore that clipping already has been done by the time the routine belowis called, and that as a result of clipping there is actually an area of non-trivial dimensions to be copied. (Be it as it may, clipping is easy anyhow: if the target area into should not fit into the target devices dimensions or its current clipping rectangle, then we have to reduce it to what actually does fit, and apply the same reduction to the source area from simultaneously. This procedure then is followedby the same considerations with reversed roles of the source and target areas, thus adjusting both areas

The algorithms

219

up to two times each.) Assume, finally,that machine words have 32 bits, and so do integers, hence there are 4 bytes in one word, and integers are large enough to represent memory addresses. The central problem clearlyis this: We want to do as few memory accesses as absolutely necessary, hence we should avoid to read or write any of the memory words more than once. Now, usually into.x modulo the word size will not be the same as from.x modulo the word size. Therefore,what goes into a particular word of the target area will originate in two adjacent words of the source area. As taught in [Gutknecht87], this is done by masking the source words, combining them in a single word, and rotating the combined result. Assumingthe routines
PROCEDURE GET(addr: INTEGER) : WORD; PROCEDURE PUT(addr: INTEGER, value: WORD); PROCEDURE LSH(value: WORD; count: INTEGER) : WORD; PROCEDURE ROT(value: WORD; count: INTEGER) : WORD;

to be "available"in a module SYSTEM, or their equivalent to be part of the programming language,this central step can be implemented as follows PUT(dAdr, ROT(hi- m + lo*m, rot)); where dAdr, hi, lo, m, and rot denote the destination address, the higher and the lower of the source words read already,the mask for the central part, and the rotate counter. The WORD type is the equivalent of a set of 32 bits; in this notation, hi- m performs an asymmetrical set difference ("AND NOT"), while lo*m stands for a set intersection ("AND"), and finally hi- m + lo*m is the union of hi- m with lo*m ("OR"). Pitfall #0: The set operators in high-levellanguages such as Pascal, Modula-2, or Oberon, are overloaded operators. Therefore,the operator precedence is the same as the one defined for numbers, regardless of whether or not "-" stands for "plus a negative number" or for "and the complement of a set of bits". So beware of attempting to rewrite hi- m + lo*m as lo*m + hi- m, this is far from being equivalent! (We could just keep ourselves from obfuscating this expression with pairs of parenthesis: The result would look too similar to C, where overly cautious programmers seem to have to bracket everything,including such things as if (false) {...};.) To clarify our intentions, the algorithm below uses "\" to denote "AND NOT", " for "AND", and " for "OR". It " " should go without sayingthat both " and "\" take precedence over " " ". Pitfall #1: Shadow bitmaps most intuitivelyare "allocated" in a bottom-up

220

Appendix A:

fashion, that is, lower y-coordinates correspond to lower memory addresses, and vice versa. Conversely, the display devices universallyseem to be allocated top-down, that is, higher y-coordinates correspond to lower video memory addresses, and vice versa. Even worse, on some of the more antiquated commercial video sub-systems, increasing memory addresses correspond to every other y-coordinate (interlacing), or even to every fourth y-coordinateonly, the remaining lines being beamed during subsequent cycles of the electron gun. On top of that, garbage collectors can get rather irritated if we should try to force a pointer to "point to" the video memory - invariably,they attempt to collect the videomemory eventually. To address all of these problems we have used successfully an address lookup table, present in each instance of memory mapped raster devices.This lookup table is initialized individually such that the yth entry contains the memory address of the coordinate pair (0, y). With that, all kinds of physical devices can be addressed irrespective of interlacing and the like. Virtual devices (shadow bitmaps) furthermore host a dynamically allocated array of WORDs, to which the address lookup table is initialized. With that, virtual devices can be accessed and copied to or from physical devices regardless of their being "allocated"bottom-up or top-down. Pitfall #2: The algorithm to copy blocks of pixels operates most efficiently on a line-by-linebasis, since lines correspond to consecutive addresses in memory. In general, though, neither into.x nor into.X will fall on a word boundary. Therefore,possibly both at the start and at the end of each line we will have to restrict the central step to a margin. Fair enough, so we will mask out the margins separately, and restrict the number of general steps in-between appropriately. Just that as soon as into.x modulo the word size is not the same anymore as from.x modulo the word size, we are at risk of not having to read the same number of full words as the number of words to be written:

a source line (top) and a target line (bottom) with a differing number of full words This case is taken into account by a somewhat clumsy guard, reflecting the fact that we have to start early with reading the source words. For analogous considerations reading the source words may terminate late. Both cases may cause an attempt to read from an illegal address, which - depending on the

The algorithms

221

machine architecture - at worst can generate an illegal address trap. To the best of our recollection,even Oberon solves this problem "by avoidance"only. Pitfall #3: Masking out margins, and even computing the number of full words in-between the margins, can work out to be an easier-said-than-done sort of job. One part of the problem lies in the fact that upon shifting arrays of bits, "left" and "right" are not necessarily where we would expect them to be. This has to do with the byte- and bit-ordering:The NS32x32 uses little endian (low bit and byte first) consequently, while the M680x0 ordering is throughs outly big endian (high byte first, and within each byte, high bit first). Beware of the i80x86: its bit ordering is big endian, while its byte ordering is little endian, constituting the most inconsequent architecture in view of raster operations. Thus, upon generating bit masks, an i80x86 logical shift left (the equis valent of an unsigned multiplicationby a powerof two) translates to an NS32x32slogicalshift with a negative counter (that is, the equivalent of an unsigned division by a powerof two). The other part of the problem lies in the way the division of signed integers is defined. Oberon defines and implements the signed division i DIV j such that the invariant i = (i DIV j)*j + i MOD j is asserted (cf. 4.0.2). In particular for j > 0 and any i, this has proved most useful, for the remainder of a division by a positive number will always be non-negative (cf. any of the MOD 32 statements below). Be it as it may, though, keep in mind that for j > 0 the division i DIV j is equivalent to FLOOR(i/ j). Now that we are forearmed for the struggle with the own will of these things, we can turn to an algorithmic concern. If the two devices this and self are the same, and if the two areas from and into overlap,then it becomes relevant in which way we do the copying. Usually, four ways are distinguished (that is, at which of the four points of the compass the copy is targeted), but [Heeb] has pointed out that the distinction of two cases is sufficient: Copying from north to south, the processing starts at the bottom line of the source area and works upwards, in order not to overwrite the parts which still need to be copied. Vice-versa, if copying from south to north, the processing starts at the top line of the source area and works downwards.The order in which the copying is done on individual lines is irrelevant, as long as source and target do not take place on the same latitude. If yet this should be the case, then copyingshould start on the left if the target is the west, and on the right if the target is the east. Conversely, we may copy from left to right if the target is

222

Appendix A:

anywhere in the west, and vice-versa from right to left if the target is in the east. Thus, if into.yinto.w + into.x from.yfrom.w + from.x, then we copy bottom-up and from left to right, else top down and from right to left. In the end, this amounts on distinguishing between moving left or right in a linearized frame buffer. This symmetric problem hosts some asymmetries, some of which only seem to be asymmetries, but actually are symmetries. Clearly, to express full symmetry,we would want to address memory with post-increment upon moving left, while using pre-decrement when moving right. Surely,we will have to make concessions to the symmetry if we want to implement both cases in a single algorithm with variable increments. The seeming asymmetries stem from dealing carelessly with the signed division - once more. We content ourselves with an example and encourage the interested reader to symmetrize the remaining asymmetries independently. The problem is the following:To mask out the left margin upon moving left, we define lm := LSH([], into.x MOD 32) which takes the full set of bits and removes the first into.x MOD 32 of them. Performing a logical "AND" with lm has the desired effect of masking. However, what this actually means is to clear from the full set the same number of bits as from into.x down to FLOOR(into.x/32)*32, the next lower word boundary. Thus, we should have written lm := LSH([], into.x - FLOOR(into.x/32)*32) instead, since i MOD j = i - (i DIV j)*j, and i DIV j = FLOOR(i/ j). In this notation, the symmetric case is more obvious. When moving right, the left mask assumes the role of the right mask, and vice-versa. Thus, for our purposes lm := LSH([], -(CEIL(into.X/32)*32 - into.X)) clears the same number of bits as from into.X up to the next higher word boundary CEIL(into.X/32)*32), remembering that we are "shifting left" on the NS32x32, hence the shift counter has to be negative.Now,

The algorithms

223

x - CEIL(x/32)*32 = x - ((x + 31) DIV 32)*32 = (x + 31) - ((x + 31) DIV 32)*32- 31 {x := x + 31 } = x - (x DIV 32)*32- 31 {i MOD j = i - (i DIV j)*j } = x MOD 32 - 31 = (x + 31) MOD 32 - 31 = (x - 1) MOD 32 - 31, which is why in the actual program we have written lm := LSH([], (into.X-1) MOD 32 - 31) instead. The same argument explains the way in which the number of central iterations is computed, and the guard that gives the green light for starting early with reading the source. Finally,notice that a shift by - rot is not the same as by (- rot) MOD 32 although the rot itself has been subjected to modulo 32 already, and beware of being tempted to remove the MOD 32 altogether although the NS32x32 will treat the shifts and rotates modulo 32 anyway (!).
METHOD [self: Memory]Copy (into: Area; this: Memory;from: Area); VAR rot, iny, inx, w, dy, dx, sy, sx, dAdr, sAdr, i, j: INTEGER;

m, lm, rm, lo, hi: WORD; BEGIN { assume the above assumptions... } rot := (into.x - from.x) MOD 32; IF into.y*into.w + into.x from.y*from.w + from.x THEN { NE-to-SW } iny, inx, w := 1, 4, into.X DIV 32 - into.x DIV 32; dy, dx := into.y, into.x DIV 32*4; sy, sx := from.y, from.x DIV 32*4; IF into.x MOD 32 from.x MOD 32 THEN DEC(sx, 4) END { early }; m := LSH([], - rot); lm := LSH([], into.x MOD 32); rm := LSH([], into.X MOD 32); ELSE { SW-to-NE } iny, inx, w := -1, -4, (into.X-1) DIV 32 - (into.x-1) DIV 32; dy, dx := into.Y-1, (into.X-1) DIV 32*4; sy, sx := from.Y-1, (from.X-1) DIV 32*4; IF (into.X-1) MOD 32 (from.X-1) MOD 32 THEN INC(sx, 4) END; m := LSH([], (- rot) MOD 32); lm := LSH([], (into.X-1) MOD 32 - 31); rm := LSH([], (into.x-1) MOD 32 - 31); END;

224

Appendix A:

FOR i := 1 TO into.h DO { copy line-by-line }

dAdr := self.adr[dy] + dx; INC(dy, iny); sAdr := this.adr[sy] + sx; INC(sy, iny); lo := GET(sAdr); INC(sAdr, inx); hi := GET(sAdr); INC(sAdr, inx); lo := GET(dAdr)\ lm ROT(hi\ m lo rot) { "left" margin }; m, lm FOR j := 1 TO w DO { general central part } PUT(dAdr, lo); INC(dAdr, inx); lo := hi; hi := GET(sAdr); INC(sAdr, inx); lo := ROT(hi\ m lo rot); m, END; PUT(dAdr, GET(dAdr)\ rm lo { "right" margin }; rm) END; END { Copy }; If all these considerations are followedcarefully,this really can but result in an algorithm like the one above. Why not do that in the first place? Be it as it may, implementing this sort of thing in a high-levellanguage is more than an exercise of purely academic interest. Todayscompilers are very well able to use registers quite efficiently[Crelier92 With these compilers, efficiencycea]. ses to be a concern to do it in assembly language necessarily (which is confirmed e.g. in [Szyperski92b Furthermore, using a high-levellanguage may ]). encourage the separation and optimization of frequent special cases (such as small characters of 32 pixels or less) and similar, which in assembly language we might refrain from doing for fear of losing track of things or due to simple lack of maintainability. One thing left out deliberatelyso far is the drawingmode, as introduced in 2.2.2. At a first glance, it may seem somewhat elaborate to ask for paint or invert mode to be respected by the copy operation, but on the one hand, it has proved most useful in the outline-font editor, and on the other, it is rather easy to implement. All that is required to implement paint mode is to replace the PUT(dAdr, value) statements by PUT(dAdr, GET(dAdr) value), forgettingabout the GET(dAdr)\ lm and GET(dAdr)\ rm masking at the respective margins. Analogously,invert mode is realized in Oberon by using "/" instead of "+" ("", that is, so - once more - beware of the operator precedences). As far as the efficiency is concerned, the nested loops are replicated "case mode of" preferably.We simply do not want to afford the price of a case distinction within the inner loop. Now that we believe to know all the tricks, we conclude this sub-section

The algorithms

225

with pointing out that a stripped down version of the Copy routine should serve as a sound basis to implement the Fill method as well. For the Fill routine, quite a few things of the Copy method are not necessary, so it should be fairly easy to do it. Implementing the pin-point - considering the way in which filling patterns are defined in Oberon - is simply a matter of selecting the appropriate word out of an array of such words (i.e. the pattern), and rotate said word once, as a preparation step to the nested loops. Further stripping down will yield eventuallythe implementation of the Set and Get methods - we dispense with their detailed elucidation and turn to a more interesting topic.

A.2.1 Scaling orthogonal rectangles Low-resolutionfont-scalingis not inevitablya matter of scaling outlines. Making existingartworkusable on computers may start out from scanned images of characters, and the rendered result of digitizedoutlines will end up as raster images. Therefore,we may ask the question, why not to make this transition without the roundabout way via outlines, that is, why not to directly scale images instead. To answer this question (cf. 4.1.3), we need the respective foundation, which is one of the goals of the present sub-section. Another goal is to give sort of a solution to the covarianceproblem mentioned in 2.2.2. The problem is thus the following.We have a black-and-whitepicture of some size and we want a copy thereof at a reduced size. Basically,each pixel of the reduced copy originates in an orthogonal rectangle of pixels in the original picture. Therefore, it seems that all we need to do is to count the number of black pixelsin each of the originalsrectangles, and assign black to the target pixel if more than half of the original rectangles pixels are black, else white.

scaling rastered images: pixels of the reduced copy (right) originate in squares (or orthogonal rectangles) of pixels in the original image (left)

226

Appendix A:

(The generalizationto both reduction and enlargement is brieflymentioned at the end of this sub-section.) It is easy to see that this overly simplistic approach is far from being sufficient. For arbitrary rational scaling factors, the boundaries of the rectangles in the original picture usually will fall between two grid lines, as is the case in the above illustration. Furthermore, even if the boundaries did fall on grid lines, halftones would not be reproduced faithfully.This is why scaling an image is not a mere matter of counting source pixels.

Scaling a checkered pattern by a factor of 1/ 2 As shown in the above illustration, a 50% halftone would be mapped onto either black or white (depending on whether or not equality is included in the "at-least-50%-rule"), which is unacceptable for scaling halftoned images. The solution to this problem may be understood to stem from an interpretation of the role of d in the digitizingalgorithm for straight lines (cf. A.0.0). Dividingd, dx, and dy by dy, this algorithm turns into
METHOD [self: Line]Digitize(with: Turtle); VAR x, y: INTEGER; m, e: REAL; BEGIN

m := (self.x1 - self.x0)/( self.y1 - self.y0); x, y, e := self.x0, self.y0, m/2; with.JumpTo(x, y); WHILE y < self.y1 DO IF e > 1/2 THEN INC(x); with.WalkTo(x, y); e := e - 1 END; { H } INC(y); with.WalkTo(x, y); e := e + m; END; IF x < self.x1 THEN INC(x); with.WalkTo(x, y); e := e - 1 END; END { Digitize }; wherein m is the (inverse) slope of the straight line, and e takes over the role of d: It denotes the error between the digitized and the real line, evaluated halfway between y and y + 1. Thus, the geometrical interpretation of the invariant H (e 1/ 2) becomes never to make an error exceeding half a unit length. Conversely, if the error exceeds 1/ 2, then a unit step is taken into the

The algorithms

227

direction across, the error is adjusted, and the resulting amount carried forward is passed on to the "nextstep". This is the very idea to be picked up for scaling images. Errors are accumulated, and amounts carried forwardare passed on to the "next step". Applying this idea to the 1:2 scaling of the above checker-board,this means to forward to the "next pixel" the 50% error made when mapping a 2 2 checker-board onto either black or white: Mapping it onto black, we make it too dark, hence we should compensate this by making the "next pixel" too light, and viceversa. In contrast to outlines, forwardingan error can take place in two dimensions, however. This means that the "next pixel" can be a horizontally or vertically neighboring pixel; or actually the two of them in order to give both dimensions a fair chance to share the error. Based upon these considerations we can start constructing an algorithm for scaling images now. Remember that each pixel of the reduced image originates in an orthogonal rectangle of pixels of the original picture. Suppose we had counted the number of black pixels for each of these rectangles. Assume we had stored the corresponding gray values in a two-dimensional array gray (that is, the number of black pixels divided by the total number of pixels in the respective rectangle); one gray value for each pixel of the reduced image. To obtain a bi-level picture out of this image of gray values, an algorithm can start forwardingerrors at the "lower left corner" of the array, and systematically proceed doing so, until it ends up at the "upper right corner" of the array; with a central step e := gray[y, x]; IF e < 1/2 THEN gray[y, x] := 0 ELSE e := e - 1; gray[y, x] := 1 END; gray[y+1, x] := gray[y+1, x] + e/2 { share the error among the vertical }; gray[y, x+1] := gray[y, x+1] + e/2 { and the horizontal direction fairly }; to be performed on each array element. Instead of computing gray values explicitlyout of the number of black pixels counted, we understand each black pixel of the source area to be contributing to the target pixels gray value an amount equal to (the size of) the target area divided by (the size of) the source area. Much like with interpreting differentlythe role of d in the algorithm for straight lines, we now can interpret differentlythe role of gray by multiplying it by the source area fromA. Like this, each black pixel of the source area contributes to the target pixelsgray value an amount equal to the target area intoA. At the same time the test whether the target pixel is goingto be white or black changes to

228

Appendix A:

IF 2*e < fromA THEN ELSE END, blank solid

which eliminates the use of rational numbers altogether. For the final implementation we observe that once a bi-level value has been determined, the respective pixel can be set accordingly,and the corresponding array element is not needed any further. Therefore, for individual lines of the target image (originating in horizontal stripes of the source picture), we can combine the accumulation of gray values and the error forwarding such that we can get by on a one-dimensional array. Simultaneously, this frees us from having to worry about sampling boundaries that do not fall on the grid lines of the source picture, for any leftovers are overburdened systematicallyto the respectiveneighbors.
METHOD [self: Device] Copy (into: Area; this: Device; from: Area); VAR fromA, intoA, fromY, intoY, modY, fromX, intoX, modX, e: INTEGER; BEGIN { assume the above assumptions } FOR intoX := into.x TO into.X-1 DO gray[intoX] := 0 END;

fromA, intoA := from.h*from.w, into.w*into.h; fromY, intoY, modY := from.y, into.y, 0; WHILE fromY < from.Y DO fromX, intoX, modX := from.x, into.x, 0; WHILE fromX < from.X DO IF this.Get(fromX, fromY) THEN INC(gray[intoX], intoA) END; INC(fromX); INC(modX, into.w); IF modX from.w THEN INC(intoX); DEC(modX, from.w) END; END; INC(fromY); INC(modY, into.h); IF modY from.h THEN { accumulateda single line of gray values } FOR intoX := into.x TO into.X-1 DO { now forward the errors } e := gray[intoX]; IF 2*e < fromA THEN self.Set(intoX, intoY, shade[blank]); ELSE DEC(e, fromA); self.Set(intoX, intoY, shade[solid]); END; }; gray[intoX] := e DIV 2 { leave half the error to top neighbor INC(gray[intoX+1], e - e DIV 2) { add the rest to right one }; END; INC(intoY); DEC(modY, from.h); END; END; END { Copy };

The algorithms

229

Using incremental evaluation of the coordinates that refer to the target image, this results in the algorithm above. Depending on the availabilityand efficiency of open arrays, it may be advantageous to declare gray within the local scope of the Copy method. Like this, we can avoid, at the same time, global variables that do not belong to the global scope, and upper limits that eventually are felt to be imposed rather arbitrarily. The above algorithm is amazingly concise, considering the seeming complexity of the problem. We are concerned to make clear that it is far from being entirely new, though. Rather, it belongs to a categoryof algorithms subsumed under the term error diffusion, which captures quite intuitivelywhat the algorithms do: use errors carried forward and distribute them among several neighboring pixels, giving rise to further re-distribution, and so forth. To the best of our recollection, they originate in [FloydSteinberg75 who used not ], only directly neighboring pixels, but also indirect neighbors in order to propagate the error. Much emphasis (or heuristics) is put on assigning different weights to the share to be forwarded to different neighbors, e.g. the weights being inverselyproportional to the distances between the pixel at issue and its respective neighbor (cf. op. cit.), or depending on the topology of the glyphs being scaled [Kohen88]. In [Vetterli91], an algorithm for scaling rastered images is reported to produce severe errors, which we failed to reproduce (cf. illustrations at the end of the present sub-section). A few more points should not remain unmentioned. First of all, we may not only wish to reduce a rastered image, but we shall want to enlarge it as well. An obvious application of blowing up pixels is to assess the quality of any kind of raster algorithm - the stock-in-tradeof a practitioner in computer graphics. Integral zooming factors are easy to implement using a byte lookup table that is built-up lazy. Non-integral zooming factors can be realized by generalizing the above algorithm. The idea to be pursued is the fact that a single pixel of the source area now contributes to the gray values of an entire rectangle of pixels of the target area - with respect to reducing images, some roles have to be reversed. Second, the algorithm may not work if the devices are the same instances, and if the areas overlap. In case of unscaled copying, this problem is dealt with by starting the copy either at the lower left corner, or the upper right corner. In case of scaled copying,this is not as simple as that, though, for the target area may be completely included in the source area. This means that a part of the copy should be done in one way, and the rest in the other way correct error diffusion notwithstanding. This is a rather unlikely case, however, for the intended use of scaled image copyingis that of allowingcopies to be made between devices of different resolutions, in which case the instances

230

Appendix A:

are different to begin with. Third, we may understand the generalized copy method to be sort of a solution to the covarianceproblem mentioned in 2.2.2. Since it accesses devices via the abstract Get and Set methods, it effectively decouples from the exact nature of either of the involvedraster devices.This constitutes a primitive way to allow copies to be made between any two devices. Technically,such "abstract" methods are incorporated best into the abstract base class. This frees clients unwilling to re-invent the wheel from a futile burden, while giving the others a fair chance to overwrite the method in order to address their particular demands. In [Szyperski92b this amounts on a concept termed child] maps. Finally,picking up efficiencydemands, we have equipped the shadow bitmap class with an optimizied variant of the above scaling algorithm. The use of this algorithm is restricted to the cases where both this and self are (at least) shadow bitmaps, while delegating the other cases to the general but inefficientmethod of the abstract base class (that, come to think of it, is not so abstract anymore). Essentially,the optimization consists of by-passingthe abstract Get and Set methods. For scaling screen sized pictures by a factor of 1/ , this yields a speed-up of a factor of 10, which may give an idea why image 2 processing practitioners seem to avoid abstract interfaces to raster devices. The optimized image scaling algorithm works acceptably well for halftoned pictures, as is illustrated below.

A variant of Barnsleys maple leaf [PeitgenSaupe88] (left), scaled by a factor of 1/ 2 (middle) and 1/ 4 (right), the scaled images with the pixels enlarged by a factor of 2 and 4 respectively

The algorithms

231

The same procedure as above, applied to a standard picture for testing dithering algorithms It has become indispensable for scaling illustrations that have been especially prepared for printer resolution, for it enables our type-setting program to let us see on screen what we will get from the printer. When applied naively to the scaling of large bit-mapped characters, even the optimized version is a decimal order of magnitude slower than scaling outlines of characters naively(cf. 4.1.3), and it does not solve low-resolution font-scalingwithout further structuring information:

Naively scaled character images of the font Times, 14pp at 300 dpi (top) and 14pp at 72 dpi (bottom) Whileat 300 dpi the result may be just about acceptable for the less demanding, we are expectingfar too much of naive scaling algorithms when tackling screen fonts, albeit an image-orientedalgorithm.

A.3 Spline-to-Bzier transformation A.3.0 The easy part n) A natural spline is obtained out of a sequence of knots Ki (i = 0 by transforming the Ki into a sequence of cubical polynomials n-1) si(t) := a it3 + b it2 + cit + d i (i = 0

232

Appendix A:

(cf. e.g. [Schwarz86]). By elementary calculus, their local extrema in x- and ydirection are
2 dt sxi(t) = 3axit + 2bxit + cxi = 0 d/ s (t) = 3a t2 + 2b t + c = 0 dt yi yi yi yi d/

tik = (k = 1, 2) tik = (k = 3, 4).

If 0 tik i+1 - Ki the chord length, then s(tik ) is a local extremal point of K , the spline. These extremal points are used to define, in turn, the first (Vi0) and the last (Vi3) vertex of the third order Bzierpolynomials p i(u) := Vi0B0(u) + Vi1B1(u) + Vi2B2(u) + Vi3B3(u), that is, cubical polynomials written in terms of the Bernstein-Bzierbasis B0(u) := (1 - u)3 B1(u) := 3(1 - u)2u B2(u) := 3(1 - u)u2 B3(u) := u3. Since for the Bzierpolynomials p i(u) = 0 = Vi0 u p i(u) = 1 = Vi3 u d/ p i(u) = 0 = 3(Vi1 - Vi0) du u d/ du p i(u) = 1 = 3(V - V ), u i3 i2 the above calculus yields furthermore the directions Ti1 and Ti2 of the first derivativesat Vi0 and Vi3. This allowsfor the substitution Vi1 =: Vi0 + li1Ti1 Vi2 =: Vi3 - li2Ti2 (cf. the tangent forms in 4.4.1). Omitting the subscript, this parameterizes a Bzierpolynomial with l1 and l2 p(u, l1, l2) = V0B0(u) + (V0 + l1T1)B1(u) + (V3 - l2T2)B2(u) + V3B3(u) = V0H0(u) + l1T1B1(u) - l2T2B2(u) + V3H3(u), using H0(u) = B0(u) + B1(u) and H3(u) = B2(u) + B3(u) from the Hermite basis for the first and the last term. In other words, by now we have determined 6

The algorithms

233

out of 8 parameters (corresponding to the 4 vertices Vi0 i3) required to defiV ne a Bzierpolynomial. What is left to do for each Bzierpolynomial is to determine l1 and l2 such that p(u, l1, l2) corresponds to a givenspline curve s as closelyas possible. Notice that for our transformation of spline curves into Bzier curves we are not going to require C1 (or analytic) continuity. Commercial programs, which allow interactive editing of Bzier curves, often provide automatically this type of continuity by enforcing l1 to be equal to l2 of the previous Bzier polynomial and vice versa. In our case G1 (or geometric) continuity is enough.

A.3.1 Misleadingprecision Already at this stage, careless implementation is likely to entail unexpected surprises. The question "what is a candidate for the Bzier curves' on-curvepoints?" does not amount to nothing more than the test "if 0 tik i+1 - Ki K , then s(tik ) is an on-curve-point".Doing so without reservation may lead to closely spaced on-curve-points,together with an unacceptable overallnumber of knots. The reason for this mishap is the following.Workingon the (continuous) splines, rather than the (discrete) pixels shown in the outline-font editor, candidates are computed which do not exist in terms of pixels (i.e. which we cannot see, cf. 3.2.0). These artifacts are consequences of the meandering pointed out in 3.2.1 already, in conjunction with the need to use two closely spaced knots to define something of a slope. The cure is a heuristic one. Neighbors within the distance of less than one pixel are removed from the list of candidates to begin with. Then, start and end points replace their neighbors, if the neighbor is sufficientlyclose and its tangent is almost the same (T1T2/ T1T2 > 1 - ). Finally,inflection points (cf. A.3.2) are removed from the list of candidates under analogous conditions. Ideally,however, we should look at the pixels right from the beginning. If there are several pixels in a (horizontal or vertical)row, this may be the result of a continuous curve starting or ending perfectly horizontally or vertically, but it may be as well the result of a slight "overshoot". Yet, the font-designer would only see a rasterized curve when designing the character, albeit at a relatively high resolution. For short, being able to work on the continuous splines has mislead us to the assumption to be able to work with more precision.

234

Appendix A:

A.3.2 The hard part In view of our background in physics, in particular the Euler-Lagrangiandifferential equations of classical mechanics, applying variational calculus was the obvious path to pursue. To ease subsequent algebra, we have chosen a least-square-fit.Thus, for a regularlyspaced sequence of N+1 points si on the spline curve,the sum of the squares of the distances i, l1, l2) - si that is, p(u ,
2 N) F(l1, l2) := i i, l1, l2) - si (i = 0 p(u

must be minimal, or the total differential dF := k [/ lk F(l1, l2)]dlk = 0 (k = 1 2) must vanish altogether. Since dl1 and dl2 are independent of one another, this requires the partial derivatives [/ lk F(l1, l2)]dlk = 0 (k = 1, 2) to vanish separately.Because dlk 0, this requires that
/ lk F(l1, l2) 2 = / lk i i, l1, l2) - si p(u = / lk i[p 2(ui, l1, l2) - 2p(ui, l1, l2)si + si2] = 2i[(p(ui, l1, l2) - si)/ lk p(ui, l1, l2)] = 2i[(V0H0(ui) + l1T1B1(ui) - l2T2B2(ui) + V3H3(ui) - si)(-1) k Tk Bk (ui)] = 0 (k = 1, 2)

whereby in the last step we have expanded p(ui, l1, l2). Extracting the factors which do not dependent on the index of summation, and rearranging the terms, this yields a system of two linear equations with the two unknowns l1 and l2 + T12iB12l1 - T1T2iB1B2l2 = + T1isiB1 - V0T1iH0B1 - V3T1iB1H3 - T1T2iB1B2l1 + T22iB22l2 = - T2isiB2 + V0T2iH0B2 + V3T2iB2H3 for each Bzier polynomial, with all the Bk and Hk actually denoting Bk (ui) 3, and Hk (ui) respectively.Evaluating Bk (ui) (k = 0 where the Hk depend on the Bk ) at regular intervals ui := i/ N, this determines the left and right hand sides of the above equations

The algorithms

235

a11l1 + a12l2 = b1 a21l1 + a22l2 = b2 which finallycan be solved for l1 and l2 l1 = (a22b1 - a12b2)/( a11a22 - a21a12) l2 = (a11b2 - a21b1)/( a11a22 - a21a12). The evaluation can be simplified by making use of the symmetry property of the basis polynomials Bk (u) = B3-k (1 - u) and hence iBk (ui) = iB3-k (ui) in our case. Furthermore most of the sums do not depend on s, but are of the form iBk (ui)Bl(ui), of which only the special cases iBk (ui)B1(ui) are needed. A modern program for symbolic computation helped us to convert these cases into closed form, iB0(ui)B1(ui) = (2N6 - 7N4 + 7N2 - 2)/(28N5) iB1(ui)B1(ui) = 3(2N6 - 7N2 + 5)/(70N5) iB2(ui)B1(ui) = 3(3N6 + 7N2 - 10)/(140N5) iB3(ui)B1(ui) = (2N6 - 7N2 + 5)/(70N5) remembering that the sums extend from 0 to N. On top of that, we can use the fact that 1= 2= 1, particularly that T12 = T22 = 1 always (and T1T2 = 0 soT T metimes). Therefore,not only a12 = a21, but also a11 = a22. By elementary differential geometry, we can obtain not only the splines extremal points, but also its inflection points. They are located at the points wherethe curvature (s) := s(t) s"(t)/ 3 s(t) vanishes, the prime denoting d/ dt. Written in components s(t) = [x(t), y(t)], this is at the points where x(t)y"(t) = x"(t)y(t). For a cubical polynomial this is the case if 3(aybx - axby)t2 + 3(aycx - axcy)t + (bycx - bxcy) = 0. Using not only the splines extremal points, but also its inflection points (as a candidate for the starting or ending vertex of the third order Bzier polyno-

236

Appendix A:

mials) may lead to a better correspondence with the spline curve.For obvious reasons, we include the start and the end of the spline as well.

A.3.3 Adequate results for medium- and low-resolution To conclude, it should be mentioned that we can not obtain necessarily an identical Bzier curve, but merely a more or less good approximation, much like with traditional curve fitting.One point which has been withheld deliberately is the fact that for the spline we considered only the geometry of the described trajectory,ignoring the elapsed time (or the "elapsed parameter"). Physically, a different parametrization corresponds to a particle traveling along the trajectory at a different (and possibly non-uniform) velocity. Yet, by putting ui = i/ N, we assumed the Bzier polynomials to be parameterized uniformly. Reportedly, doing so is customary, but not optimal [Gonczarowski91 Gonczarowski93 To discuss the relevance of the problem, , ]. we first give an illustration of a typicalcase.

A Times "e" defined with natural spline curves (left) and with third order Bzier curves (right). The middle illustration depicts the degree of correspondence of the automatic conversion. The converted outlines are not that far away from the original,and even with a more appropriate parametrization, we should not expect to get perfect correspondence. Furthermore, the conversion tool being integrated in the outlinefont editor, we can always apply manual corrections, if we feel that this should be necessary. On top of that, the primary reason to convert the outlines at all is to ease font-scalingat low-resolution,whilethe above divergencesare barely noticeable at a type-size of way above 72 pt at 300 dpi (with the pixelsenlarged by a factor of 4).

Appendix B: The Binary Format of Unstructured Outline-Fonts


EBNF is used to define the binary format of the unstructured outline-fonts. Terminal symbols printed in italics denote numbers stored in machine-independent format. To our knowledge, this format originates in a proposal by [Odersky] and is fully defined in [Templ90]. Using this number format, and eliminating the seed-points (cf. 3.1.5), the amount of disk-space required to store the outline-fonts was reduced by about 33%. Font Tag Char Ch Contour Type Closed Knot = Tag underbase mean cap over chars {Char }. = F7X 1X. = Ch left right contours {Contour }. = CHAR. = Type Closed knots {Knot }. = {CHAR }0X {CHAR }0X. = BOOLEAN. = x y.

To ease formal definition of intelligent outline-fonts, our outline-font editor can produce textual output as well. Although it cannot add in the intelligence automatically, it can assist the "font-programmer"substantially by computing different tangent forms autonomously (cf. 4.4.1). With that, formal font definition ideallyreduces to naming a few values of the blue-prints.

Appendix C: The Language Definition for Intelligent Outline-Fonts


EBNF is used to specify the syntax, while plain English formulates the two context rules beyond the round-before-userule. FontDecl VariantDecl = "FONT" Ident {VariantDecl | CharacterDecl| GlyphDecl | ContourDecl | KnotDecl| NumberDecl}"END". = "VARIANT" Ident ["[" BodyHeight"," BaseLine"]"] {CharacterDecl| GlyphDecl| ContourDecl | KnotDecl| NumberDecl}"END". = "CHARACTER" (Natural | """ " "")["[" LeftSide"~" bearing "," RightSidebearing"]"] ("=" ExprGlyph| {GlyphDecl | ContourDecl | KnotDecl| NumberDecl} "BEGIN" ExprGlyph"END"). = "GLYPH" Ident ("=" ExprGlyph| {GlyphDecl | ContourDecl | KnotDecl| NumberDecl}"BEGIN" ExprGlyph "END"). = "CONTOUR" Ident ("=" ExprContour| {ContourDecl| KnotDecl| NumberDecl}"BEGIN" ExprContour "END"). = "KNOT" Ident ("=" ExprKnot| {KnotDecl | NumberDecl} "BEGIN" ExprKnot"END"). = "NUMBER" Ident ("=" ExprNumber| {NumberDecl} "BEGIN" ExprKnot"END"). = ["+"|"-"] GlyphGlyphInst {("+"|"-")GlyphGlyphInst}. = ["+"|"-"] Contour ContourInst {("+"|"-")Contour ContourInst}. = ["+"|"-"] Knot KnotInst {("+"|"-")Knot KnotInst}. = ["+"|"-"] Number NumberInst {("+"|"-")Number NumberInst}. = = = = ImmedGlyph | Ident | "(" ExprGlyph"). ImmedContour | Ident | "(" ExprContour"). ImmedKnot | Ident | "(" ExprKnot"). ImmedNumber | Ident | "(" ExprNumber").

CharacterDecl

GlyphDecl

ContourDecl

KnotDecl NumberDecl

ExprGlyph ExprContour ExprKnot ExprNumber

Glyph Contour Knot Number

The Language Definition for Intelligent Outline-Fonts

239

GlyphInst ContourInst KnotInst NumberInst ImmedGlyph ImmedContour ImmedKnot ImmedNumber

= = = =

["#" Contour] ["@" Knot]. ["#" Contour] ["@" Knot]. ["%" PartsPerThousand]. ["%" PartsPerThousand].

= ["CLOSED"] ("POLYGON" | "BEZIER" | "SPLINE" | Ident "." Ident) "[" ExprKnot{"," ExprKnot}"]". = ("POLYGON" | "BEZIER" | "SPLINE" | Ident "." Ident) "[" ExprKnot{"," ExprKnot} "]". = "[" ExprNumber"," ExprNumber"]". = (Natural | Rational) ["^"]. ExprNumber. ExprNumber. ExprNumber. ExprNumber. "0" "9". "A" | "a" "Z" "z". Numeric {Numeric}. Natural "." {Numeric}. Alpha{Alpha | Numeric}.

BodyHeight = BaseLine = LeftSidebearing = RightSidebearing = Numeric Alpha Natural Rational Ident = = = = =

Glyphs can be composed either of other glyphs or of a closed sequence of contours. The domain of scaled numbers is equal to the maximum of the operands domains. Comments can be enclosed between pairs of curly brackets ("{" and "}") anywherein the font definition, exceptwithin symbols subject to lexical analysis. Notice that the formal specification of the font language contains a good deal of repetition. This concerns situations where the structure of the underlying problem is the same, but the syntax is not: Expressions can be expressions of all numbers or all glyphs in much the same way, but the syntax of numbers differs from that of glyphs.We are not aware of a universallyacknowledged way to express such structural analogies in EBNF, though. Be it as it may, when seen from this point of view, the syntax is not as abundant as it may look at a first glance.

Appendix D: ASelf-ContainedExampleof a Font Definition


In sections 4.3 and 4.4 the font language and its application have been introduced in terms of short examples. The reason to do so was the intention not to disrupt the flow of text with lengthy but self-contained examples (which would have to repeat most of the "syntactic sugar" of their antecedents anyhow), or with fragments of formal language specification (which would constitute a rather incoherent definition of the font language). Therefore,in this appendix we give the complete definition of a Times font that contains the characters "H", "I", "N", "O", "e", "g", "n", and "u". The "I" and the "H" contain many of the capitals building blocks and illustrate their re-use, and with that how to define regularity. The "N" re-uses some of the components, and on top of that introduces a diagonal stem that shows how to trim slanted junctions. The "O", while highlysymmetric, furthermore illustrates how to deal with optical corrections to the thicknesses of stroke of curvilinear glyphs and to their positioning regarding the reference lines. The "n" is a key glyph for the lower case characters and has been discussed in detail. The "u" demonstrates the advantage to be able to re-use components without re-using glyphs.The asymmetric curvilinearglyph"e" introduces the use of extensions for non-standard digitizing.Finally,although undoubtedly a non-trivial example, in the end the "g" is no different than the other glyphs. Further comments are interspersed among the font definition where this was felt to be necessary. The complete alphabets do not introduce substantially different usages of the font language,their description therefore can be left out without loss of generality.

A Self-Contained Example of a Font Definition

241

FONT Times NUMBER

Height = 247^ StemW= 33^ StemW2= 12^ SerifW = StemW- 3 SerifH= 4^ XBarW = 7^ XBarY = 123 height = 177 stemW = 26^ stemW2= 9^ serifW = stemW + 2 serifH = SerifH
CONTOUR

Origin = POLYGON [[0,0]] xAxis = POLYGON [[0,0], [1,0]] yAxis = POLYGON [[0,0], [0,1]]
VARIANT Book [371,94] { body height and base line }

= POLYGON [[0,SerifH+ SerifW], [0,0], [SerifW,0], [SerifW,SerifH]] + BEZIER [[SerifW,SerifH], [7,SerifH], [0,SerifH+8], [0,SerifH+SerifW]] GLYPH BLSerif = Serif# yAxis GLYPH BRSerif = Serif GLYPH TLSerif = Serif# Origin GLYPH TRSerif = Serif# xAxis

GLYPH Serif

GLYPH Stem =

CLOSED POLYGON [[0,0], [StemW,0],[StemW,Height],[0,Height]]

GLYPH Stem2 =

CLOSED POLYGON [[0,0],[StemW2,0],[StemW2,Height],[0,Height]]

242

Appendix D:

GLYPH I

Stem + BRSerif @ [StemW,0]+ TRSerif @ [StemW,Height] + BLSerif + TLSerif @ [0,Height]

GLYPH H NUMBER XBarL = 122 GLYPH XBar =

CLOSED POLYGON [[0,0],[XBarL,0],[XBarL,XBarW],[0,XBarW]]


BEGIN

I + XBar @ [StemW,XBarY] + I @ [StemW+ XBarL,0]


END

GLYPH N NUMBER dx = 200 dsc = -4 DiagW = StemW+ 9

DiagX = -15 RightStemX= DiagX + dx + 4^ - StemW2 CONTOUR Edge = POLYGON [[0,Height],[dx,dsc]] GLYPH Diagonal = Edge + POLYGON [[dx,dsc],[dx + DiagW,dsc]] - Edge @ [DiagW,0] - POLYGON [[0,Height],[DiagW,Height]]
BEGIN

+ +

Diagonal@ [DiagX,0] + Stem2 @ [RightStemX,0] Diagonal@ [DiagX - DiagW,0] { trim leftover caused by Stem2} CLOSED POLYGON[[0,dsc],[DiagW,dsc],[DiagW,Height],[0,Height]] @ [RightStemX+ StemW2,0]{ trim leftover caused by Diagonal} Stem2 + BLSerif + BRSerif @ [StemW2,0]+ TLSerif @ [0,Height] (TLSerif@[0,Height]+TRSerif@[StemW2,Height])[RightStemX,0] @

END

A Self-Contained Example of a Font Definition

243

GLYPH O NUMBER

HalfHeight= Height%50.0 Width = Height - 19 HalfWidth= Width%50.0 OptCorrV = 3 x1 = OptCorrV x0 = x1 - StemW- OptCorrV OptCorrH= 1 Over = 2 y1 = Over y0 = y1 - XBarW + OptCorrH CONTOUR Quadrant KNOT c0v0 = [x1,y1] c0k0 = [x1,-HalfHeight]c0k3 = [-HalfWidth,y1] KNOT c1v0 = [x0,y0] c1k0 = [-HalfWidth,y0]c1k3 = [x0,-HalfHeight]
BEGIN

BEZIER [c0k0,c0k0 + (c0v0 - c0k0)%54.8, c0k3 + (c0v0 - c0k3)%63.2, c0k3] + BEZIER [c1k0, c1k0 + (c1v0 - c1k0)%75.3, c1k3 + (c1v0 - c1k3)%48.3, c1k3] END { Quadrant }
BEGIN

Quadrant @ [Width,Height]+ Quadrant # yAxis @ [0,Height] + Quadrant # Origin + Quadrant # xAxis @ [Width,0] END { O } = POLYGON [[0,serifH+ 39], [0,0], [serifW,0], [serifW,serifH]] + BEZIER [[serifW,serifH], 2,serifH],[0,serifH+ 12], [0,serifH+ 39]] [ GLYPH BLserif = serif # yAxis GLYPH BRserif = serif GLYPH TLserif = serif # Origin GLYPH TRserif = serif # xAxis
GLYPH serif

244

Appendix D:

KNOT serif2Start= [stemW,4]serif2End = [0,-45^] CONTOUR serif2 NUMBER depth = -22^ BEGIN

POLYGON [ serif2Start,[stemW-3,4],[-serifW-1,depth+serifH],[-serifW,depth]] + BEZIER [ [-serifW,depth], [-serifW + 4,depth + 2], [-serifW + 8,depth + 4], [-serifW + 14,depth + 4], [0,depth + 4], [0,depth - 12], serif2End ]
END

GLYPH stem2m

serif2 @ [0,height] + POLYGON [serif2End + [0,height],[0,0], [stemW,0],serif2Start+ [0,height]]


NUMBER nDist = 80 nArcWid = XBarW + 8 nArcH= nArcWid + 30^ KNOT nArcStart=[nDist+stemW,-nArcH+2] nArcEnd=[nDist,-nArcH]

CONTOUR nArc NUMBER xm = nDist - 30 overhang= 2 BEGIN

BEZIER [ nArcStart,[nDist + stemW,-35], [nDist+stemW-2,overhang],[xm+18,overhang],[xm-13^,overhang], [15^,-nArcH+ 15 + 9^], [-4,-nArcH + 2^]] + POLYGON [[-4,-nArcH + 2^], [0,-nArcH]] + BEZIER [ [0,-nArcH],[15^ - 3,-nArcH+ 15], [xm - 22,-nArcWid],[xm,-nArcWid],[nDist - 2,-nArcWid], [nDist,-nArcH + 5], nArcEnd]
END

A Self-Contained Example of a Font Definition

245

GLYPH stem2arc

= nArc @ [0,height] + POLYGON [ nArcEnd+ [0,height],[nDist,0],[nDist + stemW,0], nArcStart+ [0,height]]


BEGIN

GLYPH stem2

stem2 + BRserif@ [nDist + stemW,0]+ BLserif@ [nDist,0]


END

GLYPH n

stem2m + BRserif@ [stemW,0]+ BLserif+ stem2arc @ [stemW,0]

GLYPH u

(( + + +

serif2 @ [0,height] POLYGON [serif2End + [0,height],[stemW,0]- serif2Start] serif2 # Origin @ [stemW,0] POLYGON [[stemW,0]- serif2End,serif2Start+ [0,height]] ) + ( nArc @ [0,height] + POLYGON [nArcEnd+ [0,height],[stemW + nDist,0] - serif2Start] + serif2 # Origin @ [stemW + nDist,0] + POLYGON [[stemW + nDist,0] - serif2End,nArcStart+ [0,height]] ) @ [stemW,0] ) # Origin @ [0,height] { Although the definition of the above u is not obtained out of the n through central mirroring, most of its components - open (!) contours can be re-used. }

246

Appendix D:

GLYPH e NUMBER

overhang= 2 optCorr = 2 y0 = -overhang y5 = height + overhang y1 = y0 + 14^ y4 = y5 - XBarW y3 = y5 - 62 y2 = y3 - XBarW x0 = 0 x3 = height - 38 x1 = stemW x2 = x3 - stemW - optCorr
KNOT

c0v0 = [x3,y5] c0v3 = [x0,y5] c0v6 = [x0,y0] c0v9 = [x3 - 32.0,y0] c1v0 = [x2 - 3.0,y1] c1v3 = [x1,y1] c1v6 = [x1,y4] c1v9 = [x2,y4] c0k0 = [x3,y2] c0k3 = c0v0 + (c0v3 - c0v0)%41.0 c0k6 = c0v3 + (c0v6 - c0v3)%55.8 c0k9 = c0v6 + (c0v9 - c0v6)%60.7 c0k12 = [x3 + 1,50] c1k0 = [x3 - 4^,50 + 1] c1k3 = c1v0 + (c1v3 - c1v0)%36.6 c1k6 = c1v3 + (c1v6 - c1v3)%49.4 c1k9 = c1v6 + (c1v9 - c1v6)%52.9 c1k12 = [x2,y3]
BEGIN

GraphicExt.TwinCurveDesc [ c0k0, c0k0 + (c0v0 - c0k0)%49.3, c0k3 + (c0v0 - c0k3)%68.4, c0k3, c0k3 + (c0v3 c0k6 + (c0v3 - c0k6)%48.5, c0k6, c0k6 + (c0v6 c0k9 + (c0v6 - c0k9)%70.8, c0k9, c0k9 + (c0v9 c0k12 + (c0v9 - c0k12)%55.4, c0k12, c1k0, c1k0 + (c1v0 - c1k0)%48.5, c1k3 + (c1v0 - c1k3)%90.0, c1k3, c1k3 + (c1v3 c1k6 + (c1v3 - c1k6)%41.8, c1k6, c1k6 + (c1v6 c1k9 + (c1v6 - c1k9)%88.9, c1k9, c1k9 + (c1v9 c1k12 + (c1v9 - c1k12)%45.5, c1k12 ] - POLYGON [c0k0, [x1,y2], [x1,y3], c1k12, c0k0]
END

c0k3)%65.9, c0k6)%48.8, c0k9)%81.0,

c1k3)%80.8, c1k6)%32.1, c1k9)%72.5,

{ Although the definition of the above e may look like a tedious programming job, it is not as bad as that: Except for naming the critical numbers and adding a few attributes, it is produced automatically by the outline-font editor (cf. Appendix B). This explains the not overly meaningful identifiers such as c1k12 etc. GraphicExt.TwinCurveDesc is an extension that implements non-standarddigitizing (cf. 4.5.2). }

A Self-Contained Example of a Font Definition

247

GLYPH g

NUMBER

headHeight = 116 headWidth = headHeight + 8 headDx= 12 earDx = headDx+ headWidth + 16^ tailWidth = earDx + 8

GLYPH head NUMBER

x0 = 0 x1 = stemW x3 = headWidth x2 = x3 - stemW overhang= 2 y0 = -overhang y3 = headHeight + overhang y1 = y0 + XBarW - 2 y2 = y3 - XBarW + 2


KNOT

c0v0 = [x2,y2] c0v3 = [x2,y1] c0v6 = [x1,y1] c0v9 = [x1,y2] c0k0 = c0v9+(c0v0-c0v9)%47.2 c0k3 = c0v0+(c0v3-c0v0)%54.5 c0k6 = c0v3+(c0v6-c0v3)%47.2 c0k9 = c0v6+(c0v9-c0v6)%54.5 c1v0 = [x0,y3] c1v3 = [x0,y0] c1v6 = [x3,y0] c1v9 = [x3,y3] c1k0 = c1v9+(c1v0-c1v9)%42.7 c1k3 = c1v0+(c1v3-c1v0)%52.9 c1k6 = c1v3+(c1v6-c1v3)%42.7 c1k9 = c1v6+(c1v9-c1v6)%52.9
BEGIN

BEZIER [ c0k0, c0k0 + (c0v0 - c0k0)%39.5, c0k3 + (c0v0 - c0k3)%88.3, c0k3, c0k3 + (c0v3 - c0k3)%86.0, c0k6 + (c0v3 - c0k6)%26.5, c0k6, c0k6 + (c0v6 - c0k6)%39.5, c0k9 + (c0v6 - c0k9)%88.3, c0k9, c0k9 + (c0v9 - c0k9)%86.0, c0k0 + (c0v9 - c0k0)%26.5, c0k0] + BEZIER [ c1k0, c1k0 + (c1v0 - c1k0)%73.2, c1k3 + (c1v0 - c1k3)%47.6, c1k3, c1k3 + (c1v3 - c1k3)%44.6, c1k6 + (c1v3 - c1k6)%77.4, c1k6, c1k6 + (c1v6 - c1k6)%73.2, c1k9 + (c1v6 - c1k9)%47.6, c1k9, c1k9 + (c1v9 - c1k9)%44.6, c1k0 + (c1v9 - c1k0)%77.4, c1k0] END { head } { Again, except for naming critical numbers, the above headis produced automatically by the outline-font editor. See also the definitions of Oand e. }

248

Appendix D:

GLYPH tail NUMBER

y0 = -93 y1 = y0 + 16^ y2 = 1 y3 = y2 + 21^ y4 = height - headHeight x0 = 0 x1 = x0 + stemW - 4 x3 = tailWidth x2 = x3 - 13^ X0 = headDx+ 2 X1 = X0 + stemW - 7
KNOT

c0v0 = [X0,y3 + 14^] c0v3 = [X0,y2 + 2] c0k0 = [X1 + 20 - 3^,y4] c0k3 = c0v0 + (c0v3 - c0v0)%51.5 c0k6 = [x1 + 33 - 4^,y2 + 2] c1v0 = [0,-35.0] c1v3 = [0,y0] c1v6 = [x3,y0] c1v9 = [x3,y3] c1k0 = c0k6 c1k3 = c1v0 + (c1v3 - c1v0)%50.0 c1k6 = c1v3+(c1v6-c1v3)%37.5 c1k9 = c1v6+(c1v9-c1v6)%65.2 c1k12 = [106,y3] c2v0 = [X1,y3 + 1] c2v3 = [X1,{41}y3 + 19] c2k0=[48,y3+1] c2k3=c2v0+(c2v3-c2v0)%50.0 c2k6=[X1+20,y4] c3v0 = [x2,y2] c3v3 = [x2,y1] c3v6 = [x1,y1] c3v9 = [x1,-29.0] c3k0 = [96,y2] c3k3 = c3v0 + (c3v3 - c3v0)%44.9 c3k6 = c3v3+(c3v6-c3v3)%57.6 c3k9 = c3v6+(c3v9-c3v6)%54.2 c3k12 = [x1 + 33,y2 + 1]
CONTOUR start = GraphicExt.TwinCurveDesc [

c0k0, c0k0 + (c0v0 - c0k0)%41.9, c0k3 + (c0v0 - c0k3)%94.1, c0k3, c0k3 + (c0v3 - c0k3)%100.0, c0k6 + (c0v3 - c0k6)%40.5, c0k6, c2k0, c2k0 + (c2v0 - c2k0)%6.7, c2k3 + (c2v0 - c2k3)%122.2, c2k3, c2k3 + (c2v3 - c2k3)%111.1, c2k6 + (c2v3 - c2k6)%40.0, c2k6] CONTOUR loop = GraphicExt.TwinCurveDesc [ c1k0, c1k0 + (c1v0 - c1k0)%39.3, c1k3 + (c1v0 - c1k3)%93.1, c1k3, c1k3 + (c1v3 c1k6 + (c1v3 - c1k6)%31.7, c1k6, c1k6 + (c1v6 c1k9 + (c1v6 - c1k9)%52.0, c1k9, c1k9 + (c1v9 c1k12 + (c1v9 - c1k12)%48.1, c1k12, c3k0, c3k0 + (c3v0 - c3k0)%52.9, c3k3 + (c3v0 - c3k3)%82.9, c3k3, c3k3 + (c3v3 -

c1k3)%86.2, c1k6)%57.0, c1k9)%82.5,

c3k3)%69.8,

A Self-Contained Example of a Font Definition

249

c3k6 + (c3v3 - c3k6)%50.0, c3k6, c3k6 + (c3v6 - c3k6)%47.2, c3k9 + (c3v6 - c3k9)%73.1, c3k9, c3k9 + (c3v9 - c3k9)%86.4, c3k12 + (c3v9 - c3k12)%45.3, c3k12] CONTOUR gap = POLYGON [c2k0, c1k12, c3k0, c0k6, c2k0]
BEGIN

start - gap + loop END { tail } { Notice the gapcontour above, its negative orientation seems to cancel with the subtraction in the blue-print. The reason yet to do so is to avoid pixel-gaps between adjacent lines digitized in opposing directions ("Bresenham-problem").}
GLYPH ear KNOT

c0k0 = [-39^,-XBarW + 1] c0k1 = [-27^,-XBarW] c0k2 = [-18,-XBarW- 1] c0k3 = [0,-XBarW- 1] c0k4 = [0,0] c0k5 = [-64^,0]
BEGIN

CLOSED POLYGON [c0k0, c0k1, c0k2, c0k3, c0k4, c0k5]


END BEGIN

tail + head @ [headDx,height- headHeight] + ear @ [earDx,height-1] END { g }

CHARACTER H

[-18^,18^] = H { left and right sidebearings }

CHARACTER I

[-18^,18^] = I

CHARACTER N

[-18^,18^] = N

250

Appendix D:

CHARACTER O

[-28^,28^] = O

CHARACTER e

[-15^,15^] = e

CHARACTER g

[-6^,4] = g

CHARACTER n

[-6,7] = n

CHARACTER u

[-5,6] = u

END { Book } END { Times }

References
[Adams89] Debra A. Adams,abcdefg (a better constraint driven environment for font generation, Proceedings of the Raster Imaging and Digital Typography, pp. 54-70, Cambridge UniversityPress (1989). Adobe Systems Inc., PostScript Language Reference Manual, Addison-Wesley Publishing Co. (1985). AdobeSystems Inc., Adobe Type 1 Font Format Version 1.1, Addison-Wesley Publishing Co. (1990). Jacques Andr, Cration de fontes en typographie numrique, Documents dhabilitation, IRISA Rennes, France (1993). Apple Computer Inc., Apple Pascal Language Reference Manual, Cupertino CAU.S.A. (1980). Apple Computer Inc., Inside Macintosh Vol. 1, Addison-Wesley Publishing Co. (1985). Apple Computer Inc., The TrueType Font Format Specification, Cupertino CA(1990). Claude Btrisey, Gnration automatique de contraintes pour caractres typographiques l'aide d'un modle topologique Ph. D. Thesis No. 1156, EPF Lausanne , (1993). Charles Bigelow, Principles of Type Design for the Personal Workstations ATypI Congress, Kiel, Germany , (1985). Gerd Binnig,Aus dem Nichts - ber die Kreativitt von Natur und Mensch, Piper Verlag, Mnchen (1989). Jack E. Bresenham, Algorithm for computer control of a digital plotter, IBM Systems Journal, Vol. 4, No. 1, pp. 25-30, (1965). Jack E. Bresenham, A Linear Algorithm for Incremental Display of Circular Arcs, Communications of the ACM, Vol. 20, No. 2, pp. 100-106 Feb (1977). D. Brookshire Conner & Andries van Dam, Sharing Between Graphical Objects Using Delegation, Third Eurographics Workshop on Object-Oriented Graphics, pp. 63-82, Champry,Universityof Geneva(1992).

[Adobe85] [Adobe90] [Andr93 ]

[Apple80] [Apple85] [Apple90] [Btrisey93 ]

[Bigelow85]

[Binnig89] [Bresenham65]

[Bresenham77]

[ConnerVanDam92 ]

252

Philippe J. M. Coueignoux,Generation of Roman Printed Fonts, Ph. D. Thesis, MIT Boston MA(1975). [Crelier92 ] Rgis Crelier & al, The Oberon System Family, Techn. Rept. No. 174, Dept. of Comp. Sci., ETH Zrich (1992). [Eberle87] Johann Jakob Eberle, Development and Analysis of a Workstation Computer, Ph. D. Thesis No. 8431, ETH Zrich (1987). [Fenton90] Erfert Fenton, Battle Royal - Fonts, MacWorld, pp. 147-153,April (1990). [FloydSteinberg75 R. W. Floyd & L. Steinberg, An Adaptive Algorithm for ] Spatial Gray Scale, SID'75Digest(1975). [Gonczarowski89 ] Jakob Gonczarowski,Fast generation of unfilled and filled outline characters, Proceedings of the Raster Imaging and Digital Typography, pp. 97-110, Cambridge UniversityPress (1989). [Gonczarowski91 ] Jakob Gonczarowski,A Fast Approach to Auto-tracing (with Parametric Cubics), Proceedings of the Raster Imaging and Digital TypographyII, pp. 1-15, Cambridge UniversityPress (1991). [Gonczarowski93 ] Jakob Gonczarowski,Curve Techniques for Autotracing, in: Roger D. Hersch (ed.), Visual and TechnicalAspects of Type, pp. 126-147, Cambridge University Press (1993). [Gutknecht86a] Jrg Gutknecht, Separate Compilationin Modula-2: An Approach to Efficient Symbol Files, IEEE Software, Nov (1986). [Gutknecht86b] Jrg Gutknecht, Editorenbau Lecture notes of the , summer term (1986). [Gutknecht87] Jrg Gutknecht, Informatik III, Lecture notes of the winterterm (1987). [GutknechtWirth92] Jrg Gutknecht &Niklaus Wirth,Project Oberon - The Design of an Operating System and Compiler, ACMPress, Addison-Wesley Publishing Co. (1992). [Heeb] Beat Heeb (personal communication). [Heeb88] Beat Heeb, Design of the Processor-Board for the Ceres-2 Workstation, Techn. Rept. No. 93, Dept. of Comp. Sci., ETH Zrich (1988). [HeebNoack91 ] Beat Heeb &Immo Noack, Hardware Description of the Workstation Ceres-3, Techn. Rept. No. 168, Dept. of Comp. Sci., ETH Zrich (1991).

[Coueignoux75 ]

References

253

Roger D. Hersch (personal communication). Roger D. Hersch, Grid Adaptationsof Character Outline Descriptions, Font Design Systems Workshop, Sophia-Antipolis,France (1987). [Hersch88] Roger D. Hersch, Vertical Scan-Conversion for Filling Purposes, Proceedings of the CGI Geneva, Springer 88, Verlag (1988). [Hersch91] Roger D. Hersch, Efficient Rendering of Outline Characters, Proceedings of the SID Vol. 32 No. 1, pp. 55-58, (1991). [Hersch93] Roger D. Hersch, Font rasterization: the state of the art, in: Roger D. Hersch (ed.), Visual and TechnicalAspects of Type, pp. 78-109, CambridgeUniversityPress (1993). [HerschBtrisey89 Roger D. Hersch & Claude Btrisey, Flexible applica] tion of outline grid constraints, Proceedings of the Raster Imaging and DigitalTypography, pp. 242-250, Cambridge UniversityPress (1989). [HerschBtrisey91a Roger D. Hersch & Claude Btrisey, Advanced Grid ] Constraints: Performances and Limitations, Proceedings of the Raster Imaging and Digital TypographyII, pp. 190-204,CambridgeUniversityPress (1991). [HerschBtrisey91b Roger D. Hersch & Claude Btrisey, Model-based mat] ching and hinting of fonts, Proceedings SIGGRAPH'91, ACM Computer Graphics, Vol. 24, pp. 71-80, (1991). [Karow] Peter Karow, Intelligent FontScaling URW Unterneh, mensberatung, Hamburg, Germany(undated). [Karow89] Peter Karow, Automatic hinting for intelligent font scaling, Proceedings of the Raster Imaging and Digital Typography, pp. 232-241, Cambridge UniversityPress (1989). [Karow92a] Peter Karow, Advanced Typography, Advanced Session of the IIEEJ, pp. 251-254, Kogakuin University,Tokyo (1992). [Karow92b] Peter Karow, Schrifttechnologie - Methoden und Werkzeuge, Springer-Verlag, Berlin (1992). [Karow92c] Peter Karow, Digitale Schriften - Darstellung und Formate, Springer-Verlag, Berlin (1992). [Klassen91a] R. Victor Klassen, Drawing Antialiased Cubic Spline Curves, ACM Transactions on Graphics, Vol. 10, No. 1, pp. 92-108, January (1991).

[Hersch] [Hersch87]

254

R. Victor Klassen, Integer Forward Differencing of Cubic Polynomials: Analysis and Algorithms, ACM Transactions on Graphics, Vol. 10, No. 2, pp. 152-181, April (1991). [Klassen93] R. Victor Klassen, Variable Width Splines: A Possible Font Representation?, Electronic Publishing Vol. 6(3) (Proceedings of the RIDT III), pp. 183-194, John Wiley &Sons (1993). [Knuth86] Donald E. Knuth, The METAFONTbook, Addison-Wesley Publishing Co. (1986). [Kohen87] Eliyezer Kohen, A simple and efficient way to design middle resolution fonts, Proceedings of the workshop on font design systems, INRIA Rennes, France (1987). [Kohen88] Eliyezer Kohen, Two-Dimensional Graphics on Personal Workstations Ph. D. Thesis No. 8719, ETH Zrich , (1988). [KohenGutknecht89] Eliyezer Kohen & Jrg Gutknecht, An analysis of fontscaling algorithms presented at the RIDT'89, EPF Lau, sanne (1989). [MacKay91] Pierre MacKay, Looking at the Pixels, Proceedings of the Raster Imaging and Digital Typography II, pp. 205-215,CambridgeUniversityPress (1991). [Meier91] Hans Ed. Meier, Schriftgestaltung mit Hilfe des Computers, Techn. Rept. No. 167, Dept. of Comp. Sci., ETH Zrich (1991). [Meier93] Hans Ed. Meier, On the Design of Barbedor and Syndor, in: Roger D. Hersch (ed.), Visual and Technical Aspects of Type, pp. 148-164, Cambridge University Press (1993). [Mssenbck&al89 Hanspeter Mssenbck, Josef Templ, Robert Griese] mer, Object Oberon - An Object-Oriented Extension of Oberon, Techn. Rept. No. 109, Dept. of Comp. Sci., ETH Zrich (1991). [Mssenbck90] Hanspeter Mssenbck, She - A Simple Hypertext Editor for Programs, Techn. Rept. No. 145, Dept. of Comp. Sci., ETH Zrich (1990). [MssenbckWirth91] Hanspeter Mssenbck &Niklaus Wirth,The Programming Language Oberon-2, Structured Programming 12:4 (1991). [Naiman91a] Avi C. Naiman, CRT Spatial Non-Linearities and Lumi-

[Klassen91b]

References

255

nance Linearization Raster Imaging and Digital Typo, graphy II, pp. 43-53, Cambridge University Press (1991). [Naiman91b] Avi C. Naiman, The Use of Grayscale for Improved Character Presentation, Department of Computer Science, Toronto, Ontario, Ph. D. Thesis, Universityof Toronto (1991). [NewmanSproull82 William M. Newman & Robert F. Sproull, Principles of ] Interactive Computer-Graphics, McGraw-Hill Inc., Tokyo (1982). [Odersky] Martin Odersky(personal communication) [PeitgenSaupe88] Heinz-Otto Peitgen & Dietmar Saupe (editors), The Science of Fractal Images, Springer-Verlag, New York (1988). [PeschelWille87 ] Frank Peschel & Matthias Wille, Porting Medos-2 onto the Ceres Workstation, Techn. Rept. No. 78, Dept. of Comp. Sci., ETH Zrich (1987). [Petzold92] Charles Petzold, Programming WindowsTM 3.1, Microsoft Press, Redmond WAU.S.A. (1992). [Pfister91] Cuno Pfister (Ed.), Oberon Technical Release Notes, Techn. Rept. No. 156, Dept. of Comp. Sci., ETH Zrich (1991). [Rappoport91] Ari Rappoport, Rendering Curves and Surfaces with Hybrid Subdivision and Forward Differencing, ACM Transactions on Graphics, Vol. 10, No. 4, pp. 323-341, October (1991). [Reiser91] Martin Reiser, The Oberon System - User Guide and Programmers Manual, ACM-Press, Addison-Wesley Publishing Co. (1991). [Ruder67] Emil Ruder, Typographie, Verlag Arthur Niggli AG, Teufen(1967). [Schr] Hansrudolf Schr (personal communication) [Schwarz86] Hans Rudolf Schwarz, Numerische Mathematik, B. G. Teubner, Stuttgart (1986). [Stamm87] Beat Stamm, Assembler for MacIntosh Motorola MC68000, Diploma Thesis, Dept. of Mathematics & Physics,ETH Zrich (1987). [Stamm89] Beat Stamm, Algorithms for Drawing Thick Lines and Curves on Raster Devices, Techn. Rept. No. 107, Dept. of Comp. Sci., ETH Zrich (1989).

256

[Stamm92a]

[Stamm92b]

[Stamm93a]

[Stamm93b]

[Stamm93c] [Szyperski92a ]

[Szyperski92b ]

[Templ90]

[Vetterli91]

[Watt89] [Wilton87] [Wirth81] [Wirth86] [Wirth89]

Beat Stamm, A Formalism for Hierarchical OutlineFonts, Advanced Session of the IIEEJ, 247-250, Kogakuin University,Tokyo, Japan (1992). Beat Stamm, An Object-Oriented Graphical Tool Box, Third Eurographics Workshop on Object-Oriented Graphics, pp. 105-126, Champry, Universityof Geneva (1992). Beat Stamm, Object-Orientation and extensibility in a font-scaler, Electronic Publishing Vol. 6(3) (Proceedings of the RIDT III), pp. 159-170,John Wiley &Sons (1993). Beat Stamm, Dynamic regularization of intelligent outline-fonts, Electronic Publishing Vol. 6(3) (Proceedings of the RIDT III), pp. 219-230,John Wiley &Sons (1993). Beat Stamm, Kurzanleitung zum MetaFont-Programm, internal documentation (1993). Clemens A. Szyperski, Write-ing Applications: Designing an Extensible Text Editor as an Application Framework, Proceedings TOOLS'92, Dortmund, Germany. Prentice Hall,EnglewoodCliffs, Mar (1992). Clemens A. Szyperski, Insight ETHOS: On Object-Orientation in Operating Systems, Ph. D. Thesis No. 9884, ETH Zrich (1992). Josef Templ,SPARC-Oberon - Users Guide and Implementation, Techn. Rept. No. 133, Dept. of Comp. Sci., ETH Zrich (1990). Christian Vetterli, Opus: Entwurf und Realisierung eines erweiterbaren, objekt-orientierten Dokumentenverarbeitungssystems, Ph. D. Thesis No. 9456, ETH Zrich (1991). Alan Watt, Three-Dimensional Computer Graphics, Addison-WesleyPublishing Co. (1989). Richard Wilton,Programmers Guide To PC & PS/2 Video Systems, MicrosoftPress, Redmond WA(1987). Niklaus Wirth, The Personal Computer Lilith, Techn. Rept. No. 40, Dept. of Comp. Sci., ETH Zrich (1981). Niklaus Wirth, Compilerbau, Teubner Studienbcher Informatik, Stuttgart (1986). Niklaus Wirth, The Programming Language Oberon, Techn. Rept. No. 111, Dept. of Comp. Sci., ETH Zrich (1989).

Acknowledgements
I would like to thank Prof. Dr. Jrg Gutknecht, Swiss Federal Institute of Technology,Zrich, Switzerland,for his willingness to further pursue font-design and -scaling as a research topic and particularlythe liberal supervision, Prof. Dr. Roger D. Hersch, Swiss Federal Institute of Technology,Lausanne, Switzerland,for his encouraging and competent criticism throughout the project and for kindlyaccepting to be the co-supervisor, Dr. Eliyezer Kohen, Microsoft Corporation, Redmond WA, USA, for the well-prepared ground and for proposing me to continue the research already done, Hans Ed. Meier, Obstalden, Switzerland, for providing me with the typographical background and for patiently learning to work with the new outline-fonteditor, Hannes L. Marais, ETH, for his interest in carefullyreading and commenting the manuscript, Dr. Cuno Pfister, Oberon MicroSystems,Basel, Switzerland,and Prof. Dr. Clemens A. Szyperski, Queensland University of Technology, Brisbane, Australia, for many fruitful discussions on concepts and application of object-orientation, Dr. Robert Griesemer, ICSI, Berkeley CA, USA, and Prof. Dr. Toshi Minami, KogakuinUniversity,Tokyo, Japan, for supporting my coming out into the real worldof document processing and digital imaging, Matthias Hausner, ETH, and Dr. Beat Heeb, Oberon MicroSystems, Zrich, for helping me with WindowsOberonTM, Dr. Michael Monagan and Ralph Sommerer, ETH, for helping me with MapleTM, Philipp Heuberger and Martin Wunderli,ETH, for helping me with LaTE, Marco Bacchetta, Lugano,Switzerland,for a strategy how to insert missing pixels, the students Mathias Kiss for developing a subset of PostScript for Oberon, Erwin Huber for porting it to my new graphical toolbox and for introducing me to PostScript's filling rules, and Thomas Schumacher, Christian Sigrist, and Martin Ulmann for teaching me that even the most orthogonal environment is difficultif it is new, Dr. Kevin Gates, Dr. Gladys Monagan-Wong,and Ronald Wakefield, ETH, for helping me with some of the peculiar idiosyncrasies of the English language

the colleagues of the institute, Marc Brandis, Rgis Crelier, Andreas R. Disteli, Urs Hiestand, Stefan Ludwig, Jacques Supcik, and others, for sharing some of their precious time and knowledgewith me, the members of the department, Antoinette Frster, Heidi Glgn, Hanni Hilgarth,Rita Jenny,Immo Noack, Willi Rttener, Heidi Theiler,and Albert Weiss, for providinghelp in some way or other, all the others that I may have forgotten, but which nonetheless made their contribution to the result of this thesis, albeit just by showingvivid interest in it, and last but not least my parents for their everlastingmoral support.

Curriculum Vitae
18 March 1963 born in Basel, citizen of Basel &Riehen, BS, Switzerland son of Anny &Hans-Martin Stamm-Vollfrom Basel PrimarySchool in Basel Grammar School Holbeingymnasium in Basel Matriculation exam (Type D, contemporary languages) Studies in ExperimentalPhysics at the SwissFederal Institute of TechnologyETH in Zrich Programming financial application softwareat Gordon Watkins&AssociatesAG, Schlieren ZH Diploma in Physics (Dipl.Phys.ETH) Research &TeachingAssistant in the research group headed by Prof. Dr. Jrg Gutknecht Institute for Computer Systems SwissFederal Institute of TechnologyETH, Zrich Ph. D. in Computer Sciences (Dr. sc. techn. ETH) Starting as a Software Design Engineer at MicrosoftTypography Lead Developerof Visual TrueType MicrosoftCorporation, Redmond WA, U.S.A.

1969 - 1973 1973 - 1981 1981

1981 - 1987

1982 - 1986

1987

1988 - 1993

1994

1994

You might also like