Hinting tutorial/Example: Hinting L
From DejaVu
We will give a hinting example for an easy-to-hint letter: the uppercase L.
Contents |
Things you need to know first
The reference points
There are three reference points, referred to as rp_{0}, rp_{1} and rp_{2}.
These reference points keep track of where you are while hinting. They are used in calculating distances.
rp_{0} is the most important one, and is the only one used in this example.
The phantom points
There are more points in your glyph than you think, which aren't labelled in FontForge when point numbers are shown. These are four points at the boundaries of the glyph: one on the left edge, one on the right edge, one at the top, and one at the bottom.
Moving the left and right phantom points changes the advance width of the glyph, so you can make glyphs wider or smaller if it doesn't fit in the space anymore after hinting.
If there are n point numbers used for the outline of the glyph, the left phantom point has number n (as point numbers start from 0 on the outline, so n is one more than the largest number you see on the outline) and the right one n + 1.
Relative and absolute points
Relative points are points whose position depend on another point. Most points will thus be relative, and there are generally very few absolute points, since the location of most points depends on other points.
Using relative points will make sure the glyph keeps it's original shape. Since points move while hinting, relative points will make sure the same distance is kept compared to the new location. Absolute points don't care about movements of other points.
The example
The glyph we will hint is this one, also known as the uppercase L:
The complete hinting instructions are:
PUSHB_8 7 5 102 3 101 1 100 6 SRP0 MIRP[rp0,min,rnd,grey] MIRP[min,rnd,grey] MIRP[rp0,rnd,grey] MDRP[rnd,grey] IUP[x] PUSHB_5 1 201 3 200 0 SVTCA[y-axis] MDAP[rnd] MIRP[min,rnd,grey] MIRP[rnd,grey] IUP[y]
CVT values used
To make it easier to read, CVT values in this example are greater or equal to 100.
The used values in this font are (these values are different for each font of course, you can find the values for DejaVu here):
CVT value | What it represents |
---|---|
100 | X Offset (width between glyph edge and outline) |
101 | Vertical stem weight |
102 | Width of outline of L |
200 | Horizontal stem weight |
201 | Height of outline of L |
Step-by-step explanation
Horizontal hinting
A thing to know is that hinting instructions start in 'X-axis mode', meaning that projecion and freedom vector are set to the (horizontal) X-axis. Don't ask yourself what these vectors are, they will be explained later. It's sufficient for now to know that all distances are calculated horizontally and all points are moved horizontally in this mode.
Generally, in hinting all points are first moved horizontally, then vertically.
PUSHB_8 7 5 102 3 101 1 100 6
This pushes the numbers necessary for hinting the glyph in the X direction. (see the section about the stack for more information)
SRP0
SRP0 means "set reference point 0". It sets rp_{0} to the point with the number on top of the stack, in this case point 6 (which is the left phantom point).
It means that when you want an relative point at a certain distance, that distance is calculated from this point.
As a little help, we'll mark rp_{0} with a blue dot in our pictures:
MIRP[rp0,min,rnd,grey]
MIRP means "move indirect relative point". As explained above, the location of a relative point depends on another point. The distance will always be calculated from rp_{0}.
The "indirect" part means that the distance will be calculated from a given CVT value (distances for direct points are calculated from the original distance).
The MIRP instructions pops two parameters from the stack: first, the CVT value for the distance (in this case 100), and next the point number from the point that has to be moved (in this case 1).
The part [rp0,min,rnd,grey] gives extra parameters to the MIRP instruction. These have the following meaning:
- rp0: makes the point we are moving the new rp_{0} after the instruction.
- min: this makes sure that there is a minimum distance between rp_{0} and the point we are moving (in most fonts this distance is set to one pixel). This can for example be used to make sure that stems do not disappear when a distance would be rounded to zero.
- rnd: round the distance. If the CVT value returns for example a distance of 1.7 pixels, it will be rounded to 2 pixels.
- grey: the type of distance. In DejaVu fonts we almost always use "grey" for this parameter. Other options are "white" and "black".
So, in short, this instruction will move point 1 at a distance of the rounded value of CVT value 100 (and minimum one pixel) from point 6 (which was rp_{0}), and point 1 will be the new rp_{0} after it.
When the rp0, min or rnd aren't needed, you just have to leave them out, like is shown in the next instructions. (The grey, white or black parameter should always be given.
After this instruction, the glyph will look like this (we'll mark the points we've moved with a blue number):
MIRP[min,rnd,grey]
This is a similar instruction. Remember that the current rp_{0} has become point 1 in the first MIRP instruction, so now we're calculating the distance from that point and no longer point 6.
After this instruction, point number 3 will be moved the CVT distance 101 measured from point 1.
There's no rp0 parameter, so point 1 will stay rp_{0}.
The min parameter makes sure that the vertical stem will be at least 1 (so it wouldn't become invisible at small sizes).
MIRP[rp0,rnd,grey]
Another variation, this time we move point 5 the CVT distance 102 measured from point 1. Point 5 has become the new rp_{0}.
Our glyph now looks like this:
Next up we have a new instruction:
MDRP[rnd,grey]
MDRP is short for "move direct relative point". "Direct" means that we don't measure the distance with a value from the CVT table, but we use the distance between two points (rp_{0} and the point with the number that's on top of the stack) as they were before we started the hinting process.
Therefore we only pop one number on the stack to execute this instruction: the point we want to move.
This instruction is needed because the right edge of our "L" outline may have moved significantly and the right glyph boundary has to be adjusted accordingly, or we'd risk to have some spacing problems with the letter that follows our "L".
MDRP can take the same parameters as MIRP like MDRP[rp0,min,rnd,grey] (or black or white instead of grey). They mean exactly the same as with MIRP.
Our glyph is now almost completely hinted horizontally. However, we haven't touched the points 0, 2 and 4 at all. We have to move them as well, and we use the following instruction for that:
IUP[x]
IUP means "interpolate unused points". The x means that we want to move them horizontally. An unused point here means a point that wasn't moved horizontally by an instruction above.
The instruction uses interpolation: that means that it will move a point by finding the next and previous hinted points on the outline (for point 2, the next point is 3 and the previous is point 1), and it will adjust its position to match the ratio between itself and the previous point and itself and the next point as they were before the hinting (i.e. the distance 1-2 and 2-3 in our example).
Given that point 2 is on the same horizontal location as 3 before the hinting, that means that 2 will now be horizontally aligned to 3. Other points were moved similarly and we get this as the result:
We're now finished with the horizontal hinting, and can do a similar process vertically.
Vertical hinting
Pushing some more values on the stack:
PUSHB_5 1 201 3 200 0
First we need to tell the hinting interpreter that we're now switching to vertical hinting. This is done with this instruction:
SVTCA[y-axis]
SVTCA is short for "set vectors to control axis". As mentioned above, the vectors here are the "projection vector" and the "freedom vector". It's sufficient to know that the projection vector is for measuring distances, and the freedom vector is the direction in which we're moving the nodes. With this instruction we both set this to the Y-axis, so from now on we'll move the points vertically and no longer horizontally.
MDAP[rnd]
MDAP meand "move direct absolute point" and as the name suggests, it's similar to MDRP. The difference is that it doesn't measure from the reference point rp_{0}, but directly from the coordinates of the point it moves. In this case, it just makes sure that point 0 is placed on the baseline.
The function also sets the new rp_{0} to the point you've moved.
Next up there are two MIRP instructions:
MIRP[min,rnd,grey]
MIRP[rnd,grey]
And to end the vertical hinting we interpolate the unused points along the y-axis as well:
IUP[y]
We now end up with a completely hinted letter "L":