harfbuzz / harfbuzz

HarfBuzz text shaping engine
http://harfbuzz.github.io/
Other
4.13k stars 628 forks source link

cursive attachment backwards #1181

Closed mhosken closed 6 years ago

mhosken commented 6 years ago

In creating a font that uses contextually chained cursive attachment, I have discovered that how harfbuzz does cursive attachment is backwards in relation to how DirectWrite does it. in harfbuzz you specify the lookup on the first, non-moving glyph and it scans forward to find the next glyph to move into position. In DirectWrite you specify the lookup to run on the moving glyph and it scans backward to find a 'base' to attach it to. Now, I may have this wrong. My context was a left to right font using cursive attachment to give space to an attached mark type character that is implemented using a base glyph and cursive attachment. My workaround is to specify the lookup twice. Once on the 'base' for harfbuzz and once for the 'attachment' for DirectWrite. But this can get problematic and it would be good to fix this. Notice that for fonts that simply run a cursive attachment pass over a whole string, say Arabic, they aren't going to see much difference either way.

brawer commented 6 years ago

@mhosken, would you mind making a small test font (just 2-3 glyphs) for Unicode’s test suite for text rendering engines? If you send a pull request to add fonts/TestGPOSSix.ttf or so, I’ll add it to the test framework. By doing this, you’d help the various OpenType implementations to behave identically.

behdad commented 6 years ago

I'm not sure I understand. Is the mark glyph marked as mark in GDEF? IIUC, you are saying that, if you have IgnoreBase in lookup, it works with DirectWrite but not harfbuzz? Otherwise I'm not sure how you can make it work with one but not other.

Also, would be interesting to see behavior with lookup flag RTL set or not.

mhosken commented 6 years ago

Sorry my explanation was a bit cryptic. Let's try an example. Consider two glyphs 'a' and apostrophe and we want to contextual cursively attach the apostrophe after the a. In fea terms we might write:

lookup cursive_a {
  lookupflag IgnoreMarks;
  pos cursive  a <anchor 0 0> <anchor 1200 0>;
  pos cursive quotesingle <anchor 0 0> <anchor 30 0>;
} cursive_a;

lookup context_attach_hb {
  pos a' lookup cursive_a quotesingle;
} context_attach_hb;

lookup context_attach_dw {
  pos a quotesingle' lookup cursive_a;
} context_attach_dw;

If the font is to to work in harfbuzz, we would need to use lookup context_attach_hb wherease if we want the font to work in DirectWrite we need to use lookup context_attach_dw. In the first case the cursive_a lookup is executed on the first character of the pair. In the DirectWrite case it is executed on the second (being the glyph that moves).

Notice that all glyphs here are bases. Cursive attachment only works on base glyphs and my previous message may have confused folks into thinking I was trying to cursively attach marks, I am not.

behdad commented 6 years ago

Ok that makes more sense.

Only other question I have before I can implement this, is, can you test with RTL lookupflag on vs off?

mhosken commented 6 years ago

That's a non-trivial amount of work, and the testing is the essence of the fix. So I'm going to pass at this point and hand this off to you guys. It doesn't make much difference to me since I still have to make my font work with older versions of harfbuzz.

mhosken commented 6 years ago

BTW Just came across this propose change to the OT Spec GPOS lookup type 3 description:

Positioning adjustments from anchor alignment may be either horizontal or vertical. Note that the positioning effects in the text-layout direction (horizontal, for horizontal layout) work differently for than for the cross-stream direction (vertical, in horizontal layout):

Note that, if the rightToLeft lookup flag is set, then the last glyph in the connected sequence keeps its initial position in the cross-stream direction relative to the baseline, and the cross-stream positions of the preceding, connected glyphs are adjusted.

behdad commented 6 years ago

That's a non-trivial amount of work, and the testing is the essence of the fix. So I'm going to pass at this point and hand this off to you guys. It doesn't make much difference to me since I still have to make my font work with older versions of harfbuzz.

I'm just asking you to set one bit in the LookupFlags and test with DWrite again. How is that non-trivial amount of work?

behdad commented 6 years ago

Or give me your font...

mhosken commented 6 years ago

In this zip file: Shimenkan-0.401-test-dev-4e1e3cM.zip

Test sequences involve CV and either U+16F8F or U+16F90, for example: U+16F0A U+16F57 U+16F8F. The font currently renders that sequence correctly in DirectWrite but not in harfbuzz. To mess with the relative order of cursive attachment for such a sequence, run ttx and look at GPOS lookup index 17 and ChainContextPos index 2. If you change the PosLookupRecord and its SequenceIndex value of 1 to 0 the font will work for harfbuzz but will fail in DirectWrite.

Let's try messing with the RTL flag on the cursive lookup: Lookup index 4. Reverting the SequenceIndex to 0 and setting RTL results in a font that doesn't work for harfbuzz and doesn't work in DirectWrite.

Now changing the SequenceIndex to 1 and leaving the cursive lookup RTL we get a font that fails for harfbuzz and works in DirectWrite, although it introduces another bug.