This repository is an attempt at making the authoring of a mark feature a bit more accessible.
It consists of
- a Mark Feature Helper Robofont Extension
- an example project implementing the mark feature
The Mark Feature Helper looks like this:
The window basically is a little UI to the Adobe FDK’s markFeatureWriter script.
There are two requirements for writing a mark feature:
- base glyphs and marks with corresponding anchors
- a group that helps the mark feature writer understand which glyphs are combining marks.
This is not in the scope of this tool. There have been many attempts at automating anchor placement, and most likely your UFO already has anchors. If it doesn’t, check Mechanic for the approach you like best.
Some good extensions to help you here are:
- RoboFont Mark Positioning Tool by Bahman Eslami
- Anchor Dropper by Ryan Bugden
- Adjust Anchors by Adobe Type Tools
The top of the helper has three buttons to help with defining a COMBINING_MARKS group in your UFO.
-
[Show Existing Group]– filter the font overview to see the contents of this group, if it already exists. In our test project there is no such group – therefore the button is greyed out. -
[Filter Eligible Glyphs]– filter the font overview for glyphs that could make it into theCOMBINING_MARKSgroup. Basically, this will be all zero-width glyphs with attaching anchors (_top, or_above, for example). Be aware that this filter may find glyphs you really don’t want as combining marks. -
[Build Group From Selection]– this button adds theCOMBINING_MARKSgroup to your UFO, after you have carefully considered which glyphs to include.
-
After our UFO has a
COMBINING_MARKSgroup, the[Write Feature File]button will become active. When we click it, amark.feafile will be written next to the UFO file. -
Optionally, we can also write a
mkmk.feafile – the contents of this file makes combinations likex-acute-acute-acute-acutepossible. -
Another option is to trim casing tags – this is a very Adobe-ish approach and has to do with Latin being a bicameral script. More in the “ins and outs” section below.
Both mark.fea and mkmk.fea are written in the “meat without the sandwich” style typical for the FDK. To test/compile the features, they need to be wrapped in feature “fences”:
feature mark {
include(mark.fea);
} mark;
feature mkmk {
include(mkmk.fea);
} mkmk;
The reason for this is to allow re-writing the features without having to edit a big feature tree.
Writing a mark feature is a joyful activity, however there a number of caveats to consider.
With a simple mark.fea, our example font will look like this:
What happened?
- We entered the glyphs
AU+0301BU+0301CU+0301DU+0301aU+0301bU+0301xU+0301 - The mark feature is working as expected (in some cases), attaching the combining acute (U+0301) to the anchor on the base glyph
- The layout engine is smart and thinks – “Ah! A combination of A and Acute! I know this and will therefore present the glyph for Aacute (U+00C1)” (I think that’s Unicode normalization)
Looking at the mark feature, we understand why:
markClass acutecmb <anchor 0 472> @MC_aboveLC;
markClass acutecmb.cap <anchor 0 690> @MC_aboveUC;
lookup MARK_BASE_aboveLC {
pos base a <anchor 219 472> mark @MC_aboveLC;
pos base b <anchor 147 744> mark @MC_aboveLC;
pos base c <anchor 246 472> mark @MC_aboveLC;
} MARK_BASE_aboveLC;
lookup MARK_BASE_aboveUC {
pos base A <anchor 304 690> mark @MC_aboveUC;
pos base B <anchor 270 690> mark @MC_aboveUC;
pos base C <anchor 345 690> mark @MC_aboveUC;
} MARK_BASE_aboveUC;We have two mark lookups, and two mark classes, with the lowercase marks attaching to the lowercase base glyphs, and the uppercase marks attaching to the uppercase base glyphs. Since the uppercase marks don’t have Unicode code points, there is just no way of typing them.
How to solve this problem?
- This is where the “trim casing tags” flag comes in. In many Adobe projects, anchors are assigned as
aboveUCandaboveLC. This has the benefit that they can be used to combine accented glyphs within RoboFont. (aboveUCaccents go on top of the caps, andaboveLCaccents go on top of the lowercase). - Obviously, trimming casing tags is not necessary if you only use a single
top/_topanchor pair for both upper- and lowercase. - When trimming casing tags from
aboveUC/aboveLCwhile writing the mark feature file, we end up with a simplified feature like this:
markClass acutecmb <anchor 0 472> @MC_above;
markClass acutecmb.cap <anchor 0 690> @MC_above;
lookup MARK_BASE_above {
pos base A <anchor 304 690> mark @MC_above;
pos base B <anchor 270 690> mark @MC_above;
pos base C <anchor 345 690> mark @MC_above;
pos base a <anchor 219 472> mark @MC_above;
pos base b <anchor 147 744> mark @MC_above;
pos base c <anchor 246 472> mark @MC_above;
} MARK_BASE_above;
Only one mark lookup, and a single mark class with both lowercase and uppercase marks attaching to the lowercase and uppercase base glyphs. The result:
What happened?
- We entered the glyphs
AU+0301BU+0301CU+0301DU+0301 - The mark feature is working as expected, attaching the combining acute (U+0301) to the anchor on the base glyph
- Pre-composed accented are normalized, while novel combinations just attach the literal combining mark. Since the normalized accented capitals are most likely composed with alternate accents (shallower angle, slightly smaller), this results in a mixed appearance.
How to solve this problem?
- We need to substitute marks on the fly – so the correct accent is attached to the respective base glyph. This can be done in the
ccmpfeature:
# very simplified, extended version in attached example project
feature ccmp {
lookup CAP_ACCENT_SWAP {
sub [ A B C D ] acutecmb' by acutecmb.cap;
} CAP_ACCENT_SWAP;
} ccmp;Amazing – the caps now all have uniform acutes. Check the b – probably you’d want a flatter acute there as well, which will make the ccmp a bit more complicated.
Is that all? Not quite, there’s another caveat:
As we can see, the kerning between T and o is interrupted by the insertion of a combining mark. Fortunately, all we have to do is insert a lookupflag into the kerning like this:
feature kern {
lookup KRN {
lookupflag IgnoreMarks;
include(kern.fea);
} KRN;
} kern;If you’ve read this far, you are truly remarkable … 🫣






