aligrudi / neatroff_make

Neatroff top-level makefile
50 stars 15 forks source link

Odd kerning with Minion Pro #5

Closed vuori closed 3 years ago

vuori commented 3 years ago

I'm trying to figure out a kerning issue with the Adobe Minion Pro Opentype font and neatroff built from latest git: Vi and Vj appear too close together, but look correct in other programs. This appears to affect the whole Minion family, while the comparably-complex Adobe Garamond Premier Pro (also Opentype) doesn't seem to have problems.

Here is roff | pdf output: minion-kern-neatroff

from this file:

.fp - M MinionPro-Regular
.fp - MM MinionPro-Medium
.fp - G Garamond-Premier-Pro
.ft R
R: Vimperator Vjol
.sp
.ft M
Minion Pro: Vimperator Vjol
.br
.ft MM
Minion Pro Medium: Vimperator Vjol
.sp
.ft G
Garamond Premier Pro: Vimperator Vjol

The metrics file generated by mkfn has gpos kern:latn 2 V:+0+0-5+0 i and gpos kern:latn 2 V:+0+0-5+0 j. The Garamond has no V-i or V-j kern pairs, while R has -2 for V-i.

This is what the text looks like in Fontforge (apps such as Krita result in comparable layout): minion-kern-ff2

Any ideas what might be happening here? I can send you the font if needed.

aligrudi commented 3 years ago

vuori notifications@github.com wrote:

I'm trying to figure out a kerning issue with the Adobe Minion Pro Opentype font and neatroff built from latest git: Vi and Vj appear too close together, but look correct in other programs. This appears to affect the whole Minion family, while the comparably-complex Adobe Garamond Premier Pro (also Opentype) doesn't seem to have problems.

Here is roff | pdf output: minion-kern-neatroff

from this file:

..fp - M MinionPro-Regular
..fp - MM MinionPro-Medium
..fp - G Garamond-Premier-Pro
..ft R
R: Vimperator Vjol
..sp
..ft M
Minion Pro: Vimperator Vjol
..br
..ft MM
Minion Pro Medium: Vimperator Vjol
..sp
..ft G
Garamond Premier Pro: Vimperator Vjol

The metrics file generated by mkfn has gpos kern:latn 2 V:+0+0-5+0 i and gpos kern:latn 2 V:+0+0-5+0 j. The Garamond has no V-i or V-j kern pairs, while R has -2 for V-i.

This is what the text looks like in Fontforge (apps such as Krita result in comparable layout): minion-kern-ff2

Any ideas what might be happening here? I can send you the font if needed.

I can reproduce the problem.

The font description seems correct. In FontForge the amount of pairwise kerning between V and i is -47, which matches Neatmkfn's output (you can multiply device resolution by 10 in neatmkfn/gen.sh and execute make neat). Therefore, I think the problem is in neatroff/font.c; I'll check that.

Thanks, Ali

aligrudi commented 3 years ago

The problem is the following rule in the font description.

gpos kern:latn 2 @79:+0+0-86+0 @95

@N is group # N (these groups are defined in the font). The definition of these groups are as follows:

ggrp 79 6 V W Wcircumflex Wgrave Wacute Wdieresis ggrp 95 59 f i m n r germandbls igrave iacute icircumflex idieresis ntilde itilde imacron ibreve iogonek dotlessi ij nacute ncommaaccent ncaron eng racute rcommaaccent rcaron longs uni1EC9 uni1ECB f_b f_f_b f_f_h f_f_j f_f_k f_f_t f_h f_j f_k f_t longs_b longs_h longs_i longs_k longs_l longs_longs longs_longs_i longs_longs_l m.end n.end r.end m_uni0302 n_uni0302 f_f fi fl f_f_i f_f_l longs_t f_i f_l i.dot

The first number after ggrp is group identifier and the next is the number of glyphs in it.

This rule matches "Vi" in addition to the one you mentioned.

gpos kern:latn 2 V:+0+0-47+0 i

I guess the former is defined in an OpenType kern feature table and the latter in the OpenType kern table. Not sure what to do here; any idea?

vuori commented 3 years ago

Nice find. Here is the offending pair in Fontforge which looks like what neatroff is doing: minion-kern-ff3

It seems that the font actually has several GPOS pairwise positioning ("kern") adjustment tables: one contains glyph pairs and the remaining three have class pairs. However, there are no script/language variations that I can see which would affect the PairPos selection: all four tables are part of the same "lookup", they just have a different format—and weirdly, conflicting values.

ISO 14496-22 section 6.3.3.1 (page 217) says:

"Each LookupType has one or more subtable formats. The "best" format depends on the type of positioning operation and the resulting storage efficiency. When glyph information is best presented in more than one format, a single lookup may define more than one subtable, as long as all the subtables are of the same LookupType. For example, within a given lookup, a glyph index array format may best represent one set of target glyphs, whereas a glyph index range format may be better for another set."

So what the standard seems to say is "pick a format that suits you and just use that." Seems that most software picks the pairwise variant (it comes first in the file) and uses that. I have no idea what the font designer was trying to achieve here, and this conflict might just be a bug in the font. Perhaps the most expedient course here would be to stop the lookup at the first match, i.e. in the glyph-pair table?

I think this is the general thrust of the standard too. Section 6.2.5 (page 161):

"During text processing, a client applies a lookup to each glyph in the string before moving to the next lookup. A lookup is finished for a glyph after the client makes the substitution/positioning operation. To move to the "next" glyph, the client will typically skip all the glyphs that participated in the lookup operation: glyphs that were substituted/positioned as well as any other glyphs that formed a context for the operation. However, in the case of pair positioning operations (i.e., kerning), the "next" glyph in a sequence may be the second glyph of the positioned pair (see pair positioning lookup for details)."

vuori commented 3 years ago

Addendum: here is neatroff output with the classwise pairs removed:

image

V-i and V-j are spaced correctly with the edited Minion Pro. Garamond also has a similar structure with alternate lookup tables. It seems to have at least fewer overlaps between them, and the classwise tables have more entries. For example, V-x pair is not present in the glyph-pair table but appears in the class-pair table.

edit: the n-i pair in the word "Minion" also differs by one pixel and I think the version produced by the edited font is more balanced.

aligrudi commented 3 years ago

vuori notifications@github.com wrote:

Nice find. Here is the offending pair in Fontforge which looks like what neatroff is doing: minion-kern-ff3

It seems that the font actually has several GPOS pairwise positioning ("kern") adjustment tables: one contains glyphs pair and the remaining three with class pairs. However, there are no script/language variations that I can see which would affect the PairPos selection: all four tables are part of the same "lookup", they just have a different format—and weirdly, conflicting values.

ISO 14496-22 section 6.3.3.1 (page 217) says:

"Each LookupType has one or more subtable formats. The "best" format depends on the type of positioning operation and the resulting storage efficiency. When glyph information is best presented in more than one format, a single lookup may define more than one subtable, as long as all the subtables are of the same LookupType. For example, within a given lookup, a glyph index array format may best represent one set of target glyphs, whereas a glyph index range format may be better for another set."

So what the standard seems to say is "pick a format that suits you and just use that." Seems that most software picks the pairwise variant (it comes first in the file) and uses that. I have no idea what the font designer was trying to achieve here, and this conflict might just be a bug in the font. Perhaps the most expedient course here would be to stop the lookup at the first match, i.e. in the glyph-pair table?

I think this is the general thrust of the standard too. Section 6.2.5 (page 161):

"During text processing, a client applies a lookup to each glyph in the string before moving to the next lookup. A lookup is finished for a glyph after the client makes the substitution/positioning operation. To move to the "next" glyph, the client will typically skip all the glyphs that participated in the lookup operation: glyphs that were substituted/positioned as well as any other glyphs that formed a context for the operation. However, in the case of pair positioning operations (i.e., kerning), the "next" glyph in a sequence may be the second glyph of the positioned pair (see pair positioning lookup for details)."

I see. In Neatroff font descriptions, rules are ordered according to the index of their Lookup table. However, it does not show to which lookup each rule belongs. For GSUB rules, Neatroff simply applies each rule to the entire text and then moves to the next. For GPOS rules, it simply applies all possible rules to every glyph. This does not work if more than one rule in a Lookup matches a position; although uncommon, it does happen in some fonts, as you have found.

I cannot think of a clean solution. We can limit the number of GPOS rules that can be applied to each glyph, as the following patch shows, but that would prevent applying multiple GPOS rules from different Lookups.

diff --git a/font.c b/font.c index bee148c..fb2d07f 100644 --- a/font.c +++ b/font.c @@ -217,6 +217,7 @@ static void font_performgpos(struct font fn, int src, int slen, for (k = 0; k < gpos[r].len; k++) curs_dif += pats[k].yadv; }

Any ideas?

Thanks, Ali

vuori commented 3 years ago

Stopping on the first matched gpos rule overall sounds a bit drastic. How about applying only one rule per glyph and per type? So at most one SinglePos, one PairPos, one Cursive attachment etc. rule per glyph.

I'm no font expert, but I'd guess it's rare to legitimately have multiple Lookups that contain (for example) a PairPos table. Besides that I can't think of other methods than to amend the font description format to include the Lookup list index.

khaledhosny commented 3 years ago

When a glyph is matched in a lookup subtable, it is never matched again in any subsequent subtable of the same lookup. Fonts use this to implement the so called kerning exception (a glyph that is used in a class but sometimes need a different value from the rest of the class).

aligrudi commented 3 years ago

Khaled Hosny notifications@github.com wrote:

When a glyph is matched in a lookup subtable, it is never matched again in any subsequent subtable of the same lookup. Fonts use this to implement the so called kerning exception (a glyph that is used in a class but sometimes need a different value from the rest of the class).

Updated Neatmkfn and Neatroff. Does it fix the issue?

Thanks, Ali

vuori commented 3 years ago

Working great for my test case now: image

Spacing on Minion is now better overall. Garamond is unchanged (as expected). Thanks for the fix.