Fixing rasterization issues at low resolutions

From outlines to pixels

The outline of a glyph is a mathematical description of the glyph's shape using lines and curves (Béziers).

Screenshot showing an outline and control points on the lower-case letter m.

The particular choice of curve is irrelevant. Both quadratic and cubic Bézier curves will do, as long as we have control points. The control points are needed to scale the glyph to the desired type size and resolution (or ppem size for short).

The screen is a regularly spaced grid of black and white dots (pixels).

To rasterize the glyph essentially means to turn on all pixels inside the scaled outline.

Screenshot showing the outlined letter with all pixels turned on.

Doing so naïvely results in the above raster tragedy: disjoined arches, unequal stems, missing serifs, etc.

What went wrong?

Control point coordinates are integers. There are no decimal places or fractional numbers.

  • In TrueType, the design grid (em-square) is typically 2048 by 2048 font units.
  • In Type 1, the design grid is 1000 by 1000 font units. Type 1 allows decimal places. But: 253.7 out of 1000 units is the same as 2537 out of 10000. Hence, for the sake of the argument, there are no decimal places.
  • The pixel grid is integer as well, there are no fractional pixels either. E.g., at 12 pt and 96 dpi, the em-square is 16 by 16 pixels. If the pixel grid were not integer, we would simply round it. On screen, we can't tell 12.2 pt from 12 anyhow.

Scaling the font

What's left to do to scale the outline is to multiply all coordinates by 16/2048, i.e., first multiply them by 16, then divide them by 2048.

The only thing that can (and will) go wrong is the division. Most integers don't divide. Therefore, the computer will have to round some numbers. This is the same as to decide for pixels. As a consequence of rounding, "things don't add up anymore." Something always has to give way. Some things (distances, proportions) must not give way.

Notice that if we were to use fractional coordinates and/or allow fractional point sizes, the computer still would have to decide for pixels. In the end, the problem with fractional numbers is the same, it is merely more difficult to understand.

Hinting, grid-fitting, instructing

The computer has to be told, which things must not give way. This is what is colloquially called hinting. More precisely, it is called grid-fitting or instructing, since we give the computer precise instructions how to fit the outlines to the grid before turning on the pixels. The purpose of hinting is to...

  • Preserve regularity of locations and distances
  • Preserve near-regularity of locations and distances
  • Preserve proportions
  • Control digitized appearance

Can't have the cake and eat it, too

We cannot have both the left and right edge of stem in the right place and have the correct stem weight (correct in terms of pixels). We have to make a trade-off chosing one of the following:

leftEdge + stemWeight = rightEdge
rightEdge - stemWeight = leftEdge
rightEdge - leftEdge = stemWeight

The trade-offs are the variables on the right. They will have to give way.

Control points galore

In TrueType, there is no concept of edges and stems. There are just control points.

Screenshot showing on-curve points as filled circles, off-curve points as unfilled circles.

There are on-curve points (Inline image of a filled circle.) and off-curve points (Inline image of an unfilled circle.). Both on- and off-curve points are called control points.

Screenshot showing on-curve points only.

VTT's "Show fewer points" hides the off-curve points.

We have to choose which control points we want to define the edge (Inline image of a small black square.).

x- and y-directions

Screenshot showing arrows pointing to control points on two edge curves.

A pair of control points defines a pair of edges, which in turn delimits a stem.

Screenshot showing the measure tool in Visual TrueType measuring the distance between two edges.

  • The "horizontal portion" of the distance (= the distance in x-direction, or dx) between these control points defines the weight of a vertical stem
  • The "vertical portion" of the distance (= the distance in y-direction, or dy) between these control points defines the weight of a horizontal stem

Screenshot showing measured horizontal and vertical distances.

VTT's "Measuring Tool"shows measurements for both distances, in x- and in y-directions.

Vertical is x, horizontal is y?

This may be a bit confusing:

Screenshot showing highlighted control points on the vertical stem of the letter m.

For a vertical stem, the relevant distance is in x (i.e. horizontal). We move the point in x-direction to fit it to the grid.

Screenshot showing highlighted control points on the horizontal stems of the letter m.

For a horizontal stem, the relevant distance is in y (i.e. vertical). We move the point in y-direction to fit it to the grid.

"Touched" points will be followed "accordingly"

Screenshot showing a distorted letter o with highlighted horizontal control points.

Suppose we have grid-fitted ("touched") the above points in y-direction to control the horizontal stems. Now what about the other ("untouched") points?

Screenshot showing an adjusted letter o with highlighted horizontal control points.

They follow "accordingly," due to a pair of IUP instructions ("interpolate-untouched-points") in x-and y-direction that VTT adds at the end of the other instructions.

Always touch the "extremes":

Screenshot showing a distorted letter o with question marks where the lower points are distorted.

If we don't "touch" all extremes, IUP can't interpolate all the "untouched" points between pairs of "touched" points, only shift them along with the "touched" points.

Screenshot showing a distorted letter o with exclamation marks where the lower points are distorted.

This is probably not what we want. But we may still see this intermediate stage while we're linking with "grid-fitting" turned on. Just turn it off, as desired.

So, the left and right edge of a stem is just a pair of control points. If we move these points, the other points will follow "accordingly". Now then, let's give priority to the left edge and the stem weight.

This is what the link tool is for. It defines a relationship between:

  • a parent point (on the edge from which we start out) and

  • a child point (on the edge which has to give way in the trade-off).

    Screenshot showing an arrow pointing from one point to another point, which is a visual tool linking a parent point and a child point.

Linking across the stems preserves the weights of the stems.

Screenshot showing the outlined letter m with arrows between pairs of points on the three stems.

Screenshot showing the outlined letter m filled with solid blocks insufficiently filling in the letter.

Linking across the serifs preserves the weight of the serifs.

Screenshot showing arrows indicating links between points on the ends of the serifs.

Screenshot showing the outlined letter m filled with solid blocks filling the letter including serif points but not curves.

A link across a black distance keeps a minimum distance of one pixel.

bottomOfSerif + serifWeight = topOfSerif

Linking across the arches (and more):

Screenshot showing the outlined letter m with multiple arrows indicating links between several pairs of points.

Screenshot showing the outlined letter m filled with solid blocks adjusted to the curves.

prevents arches from "drop-outs".

The trouble with the outlines:

Screenshot showing the left stem of of the letter m. Arrows indicate links between pairs of control points.

Notice the two links in x-direction across the left stem. We can see a stem, but the computer cannot. It merely sees a bunch of unrelated control points. We have to relate these control points by links or shifts, even though they are on the same edge of the stem.

But why are there two links, rather than four? IUP[X] will take care of the others if there is no gap between them.

Compare this to the vstem or hstem hints in Type 1.

There are really no stems, just control points:

How many links for the "Croatian d"?

Screenshot showing an outline with control points on the Croatian d.

Screenshot showing an outline of the Croatian d. Arrows indicate links for one parent point on the stem to multiple child points in the outline.

Fortunately, this case is rare.

Screenshot showing an outline of the Croatian d.

Arrows link one parent point on the stem to three child points. Two child points link as parents to other points.

Also, there are different ways to do it.

Linking across the counters:

Screenshot showing an outline the letter m with control points. Arrows indicate links between points in each stem and between points on adjacent stems.

Screenshot showing an outline the letter m filled with solid blocks.

regularly spaces the stems. A link can entail another link, which in turn can entail a third one etc.

A perfect 'm'?

The chain of links goes from the left to the right sidebearing in 7 steps. Each step may be off by ½ pixel. Therefore, the right sidebearing may be off by 3½ pixels.

Trade-off: proportion (width vs. height), advance width, wysiwyg.

At 96 dpi, 3½ pixels is 50% of the x-height (of 7 pixels). In contrast, at 600 dpi, 3½ pixels is only 8% of the x-height. How is that going to "line up" such as to see what you get?

Other linking strategies

  • We can link the left edges of the stems, rather than the counters, reducing the chain to 4 steps.
    Trade-off: counters, wysiwyg to a lesser degree.
  • We can link outside-in, not left-to-right, for unconditional wysiwyg. Trade-off: spacing of stems for an odd number of pixels.
  • For an extra bold m we may wish to reduce the middle stem to 1 pixel.
  • etc.
  • Compare this to the vstem3 hint in Type 1. We don't have to know what it does, nor can we do anything if it doesn't do what we want it to.

Other ways to go "too far"

Screenshot of capital i. An  arrow indicates a link between a point on the right edge of the stem to a point on the lower right serif. Screenshot of capital i with an additional link from a point on the lower right serif to a point on the lower left serif.
Screenshot of capital i with an additional link from the point on the lower left serif to a point on the left edge of the stem. Screenshot of capital i with an additional link from a point on the left edge of the stem to the original point on the right edge.

A link too many would create a circular chain of links. This means that nothing can give way. But we can't keep passing the buck around in a circle. Something always has to give way.

An interpolation to preserve proportions:

Screenshot showing an outline of capital B with nine arrows indicating links between pairs of control points.

Screenshot showing an outline of a capital B filled with solid blocks.

This looks fairly acceptable at 12 pt and 96 dpi.

Proportions of the counters or location of the middle crossbar:

Screenshot showing capital B at 10 points with the middle bar appearing as expected.Screenshot showing capital B at 8 points with the top bowl smaller than the bottom bowl.Screenshot showing capital B at 6 points with the middle bar right against the top stem and eliminating the top bowl.

It is still fine at 10, but not at 8 and 6 pt. At 8 and 6 pt, the cap height must have rounded down, while the location of the middle bar appears to have rounded up. The proportions don't add up to the capheight.

An interpolation to place the crossbar:

Screenshot showing the outline of capital B with parent and child connections.

Arrows indicate control of a point on the middle bar using interpolation between points on the top and bottom edges.

The interpolation doesn't preserve a distance from a parent to a child. The interpolation preserves the proportion of the distance from parent #1 to a child, relative to the total distance from parent #1 to parent #2. It does so in terms of pixels.

The crossbars are all set:

Screenshot showing capital B at 10 points. Top and bottom bowls are the same height.Screenshot showing capital B at 8 points. Top and bottom bowls are the same height.Screenshot showing capital B at 6 points. Top and bottom bowls are the same height.

  • We can interpolate the bottom edge of the middle crossbar and link to its top edge.
    Trade-off: This tends to push the crossbar up (but works fine for the B).
  • We can interpolate the top edge of the crossbar and link down.
    Trade-off: This tends to push the crossbar down (which may be better for the A).
  • Compare this to the hstem3 hint in Type 1.

While we're interpolating:

Screenshot showing the outline of capital B.

Arrows indicate control of a point on the right edge of the top bowl by interpolation between points on the left stem and the right edge of the bottom bowl.

One way to horizontally align the top bowl with the bottom one.

Proportionally placing strokes

Screenshot showing capital B at 10 points. Top and bottom bowls are horizontally aligned.Screenshot showing capital B at 8 points. Top and bottom bowls are horizontally aligned.Screenshot showing capital B at 6 points. Top and bottom bowls are horizontally aligned.

This works fine with an interpolation. But we might just as well link them. A link across a white or grey distance does not keep a minimum distance. Eventually, the distance rounds down to 0 pixels, and remains 0 for smaller sizes (consistency).

A bad interpolation:

Screenshot showing an outlined capital B.

Arrows indicate control of a point on the bottom bowl by extrapolation from a point on the left stem and a point on the right edge of the top bowl.

The interpolation doesn't work the other way round. We cannot extrapolate the position of the bottom bowl relative to the top bowl. The child point has to be between the parent points.

Placing children between parents:

Screenshot showing capital B at 8 points with visible crotch where top and bottom bowls meet.Screenshot showing a 7 point capital B with the crotch between the top and bottom bowls filled.Screenshot showing a 6 point capital B with visible crotch where top and bottom bowls meet.

At 8 and 6 pt, the point at the crotch between the two bowls is roughly in the center of the middle crossbar, while at 7 pt it isn't.

A good interpolation:

Screenshot showing the letter B with arrows pointing to horizontal parent and child control points.

Extremal points don't know where to go. We have to tell them. What were parent and child for the link become parents for the interpolation.

Interpolations to keep children inside:

Screenshot showing the letter B after interpolation.Screenshot showing the letter B with inconsistent bowl size.Screenshot showing the letter B with consistent bowl size.

What a difference a pixel makes. Notice that this last interpolation is relevant to many point sizes, although we're merely looking at one of them. The fact that the 6 pt B has rather odd bowls is another problem, quite specific to that point size.

Control value table entries to control near regularities:

Screenshot showing the letter B where both bowls are proportional.Screenshot showing the letter B where the upper bowl is smaller.Screenshot showing the letter B where the bowls are even and there are gaps at the inner curves.

The B at 12, 11, and 10 pt: By design, the round vertical strokes are a bit wider than the straight ones. We can't see this at 12 and 10 pt. In contrast, at 11 pt we can see a bit too much.

The role of the control value table

The round stroke is just over 1½ pixels, which rounds up to 2 pixels. The straight stroke is just under 1½ pixels, which rounds down to 1 pixel.

We need a common reference distance that both strokes can refer to. A control value is a dominant width or length of a group of features such as the stem width or the serif length. Control values are tabulated in the control value table (cvt). You'll have to populate the cvt by measuring the character's features.

Compare this to blue values and dictionaries in Type 1.

How do control values work?

The pre-program (prep) scales the control values for the desired ppem size. In the prep, we can adjust, at and below which ppem size the scaled cvt values for straight and round strokes should be the same, and similar. Links can then use the respective values from the cvt, rather than using the actual distance between the two control points. As a result, we have

leftEdge + stemWeightFromCvtForRoundStrokes = rightEdge   
leftEdge + stemWeightFromCvtForStraightStrokes = rightEdge

yielding the same stem weights for the set ppem size and below.

Which cvts to use?

Screenshot showing an outline of capital B. Arrows with badges indicate control point links that have associated CVTs.

Cvts are attributed by:

  • Character group (UC, LC, ...)
  • Link color (black, white, grey)
  • Link Direction (x, y, diagonal)
  • Feature (distance, straight or round stroke, left or right sidebearing, absolute or relative height, ...)

VTT will pick the best match for a given attribute and the actual distance. We can override this choice.

Using cvts

Screenshot showing the letter B with CVTs option 1.Screenshot showing the letter B with CVTs option 2.Screenshot showing the letter B with CVTs option 3.

Compare this to hint replacement commands in Type 1.

We just put the right cvts in the right places, but we have to put them there at all. Then we can override at what size a 1 pixel stem becomes 2 pixels. We can also override whether e.g. the round strokes should be tied to the straight ones or vice versa for more contrast at smaller sizes.

Other reason for cvts

  • Even strokes that appear to have the same weight sometimes don't.
  • Outlines may have been scanned in from hand-drawings.
  • Outlines may have been converted from other outline formats. For example, cubic Bézier curves (Type 1) don't convert to quadratic Bézier curves (TrueType) identically. The math can only approximate them, at the expense of the total number of control points (and font size).
  • Straight lines are still a problem. For example, going from Type 1 to TrueType, the conversion must scale from a 1000 by 1000 grid to a 2048 by 2048 grid. To do so it has to multiply all coordinates by 2048/1000. The problem is the division, again.
  • We'll use average stroke weight for actual cvt value.

Other places for cvts

Screenshot showing an outline of capital O. Arrows with badges indicate control point links that have associated CVTs.

The O looks fairly straightforward to do. The question mark merely indicates VTT didn't find a cvt for UC White X LSB.

Cvts for height guidelines

Screenshot showing the letters H and O with CVT values example 1.

Screenshot showing the letters H and O with CVT values example 2.

Screenshot showing the letters H and O with CVT values example 3.

Cvts for height guidelines are a bit tricky. We have to store the absolute value (base line, cap height) for the squares (like the H), and relative value (over- and undershoot) for the rounds (like the O). We would like to link from the cap height to the cap height overshoot and from the base line to the base line undershoot, using the same cvt both times. But the O has no control points on the cap height or on the base line. Therefore, we have to simulate this behaviour.

Anchoring control points on height guidelines

Screenshot showing an outline of capital O.

Anchor icons on control points indicate anchoring control points. Anchor icons with a badge indicate anchors that have associated CVTs.

VTT will pick a cvt for the height if it is attributed as "Grey-Y-Absolute (or Relative) Height" We can override this choice.

The 'O' with anchors and cvt numbers:

Screenshot showing the letters H and O with CVT values and anchors example 1.

Screenshot showing the letters H and O with CVT values and anchors example 2.

Screenshot showing the letters H and O with CVT values and anchors example 3.

In daily font production, you may start with the heights and then go from there with links and interpolates. But we first had to learn about things not adding up and how links and cvts can fix this.

We use cvts for squares as well. Like this we can tweak the cap height for coordination of the roman with the italic or the bold.

Compare this to the blue lines in Type 1.

Deltas for digitised appearance

Screenshot showing an outline of the letter O filled with pixels. Some pixel combinations cause the O to appear with corners.

Grid-fitting is

  • ppem size independent
  • pixel size independent

Links round strokes to the nearest number of pixels at any ppem size, and using cvts makes them come out the same width. Links do not specify a number of pixels nor address a specific pixel.

But after linking, we may be left with knees or elbows and other unfortunate pixel patterns. First, we should make sure no link with round-to-halfgrid etc. can control this pattern.

The final O:

Screenshot showing an outline of the letter O filled with pixels.

Arrowheads indicate where delta instructions have been applied. The pixels display O without any corners.

A delta can e.g. at 12 pt/96 dpi (16 ppem) move the off-curve point at the top 1/8 pixel to the left and another delta can move a point at the bottom 1/8 pixel to the right.

These deltas apply to

  • a specific ppem size
  • a specific pixel amount

We'll have to check and fix all sizes likely to be displayed on screen. We can insert a delta between links and interpolates or add a bitmap for a particular size if this saves many deltas.

Hand hinting vs. auto-hinting

Hand hinting requires a lot of work. While VTT makes it easier to learn and faster to do the work, we still have to learn all the various hinting strategies and do the work.

Auto-hinting would be nice to have, but would you trust a computer to have enough "taste" to make the right trade-offs, always?

What is the "right" trade-off, anyway?

Are the trade-offs always the same ones? Or are they different for different fonts, usages, or customers?

The future of VTT may look into more computer assistance in cases where it can make logical decisions or crunch numbers.

Summa summarum

The main difference between Type 1 and TrueType is:

  • A lot of situations you need not worry about in Type 1
  • A lot of situations you can't do anything about in Type 1
  • Which of the two should you put into an OpenType font? This depends on what you'll use it for.