opentype.js icon indicating copy to clipboard operation
opentype.js copied to clipboard

Ligatures not working for specific font (LobsterTwo-Bold.otf)

Open lwollnikowski opened this issue 2 years ago • 11 comments
trafficstars

Expected Behavior

Opentype.js should output text with ligatures for getPath() conversion.

Current Behavior

Result of font.getPath() call results in text which doesn't have ligatures, even if font supports them.

I explicitly request 'liga' feature in getPath() call:

font.getPath(
  htmlBlock.text, 
  0, 
  0, 
  htmlBlock.fontSize, 
  {
    letterSpacing: htmlBlock.letterSpacing,
    features: {
      'liga': true,
    }
  }
);

Release 1.3.4 converts to path silently changing ligatures to standard letters.

I tried @Connum fork of opentype.js, branch which fixes #554 (PR: https://github.com/opentypejs/opentype.js/pull/564) - this throw error "Substitution type 11 is not supported in chaining substitution". Seems logical, this branch adds check:

if (substitutionType === '12') {
  <lookup code>
} else {
  throw new Error(`Substitution type ${substitutionType} is not supported in chaining substitution`);
}

around substitution parsing.

FontForge reports this font contains mutiple definitions of 'liga' lookups: image

To see the difference look at provided example, more informations about it in 'Steps to Reproduce' section.

Possible Solution

No idea, I'm newbie in font world :)

Steps to Reproduce (for bugs)

I provide minimal example - upper text is how browser renders this text (with ligatures), lower text is upper text converted by opentype.js conversion to path commands. Notice different letters 'd', 'u' and 'fft' ligature...

... or just look at this screenshot: image

Context

I try to use opentype.js as a tool to convert HTML text to SVG path elements. I treat opentype.js as 'magic box' which returns appropriate drawing commands.

Your Environment

  • Version used: 1.3.4 / branch: (https://github.com/opentypejs/opentype.js/pull/564)
  • Font used: LobsterTwo-Bold.otf (provided in ZIP file)
  • Browser Name and version: Chromium 78 / Chrome 103
  • Operating System and version (desktop or mobile): Ubuntu 22.04 LTS
  • Link to your project: minimalexample.zip

lwollnikowski avatar Mar 21 '23 10:03 lwollnikowski

Thanks for the report! So the issue at heart is that the only supported substitution type inside chained substitutions is '12' right now. I tried to understand the lookup-related code or at least locate the corresponding part of the OpenType spec, but I have to admit I'm at a bit of aloss here, unfortunately.

Connum avatar Mar 21 '23 15:03 Connum

Here are two people who touched those parts of the code in recent years and might be able to shed more light on this and at least lead us currently active developers in the right direction: @solomancode @st0nerhat

Connum avatar Mar 21 '23 15:03 Connum

If you remove the if/else statement checking for type 12, the dz and uz ligatures look like they are supposed to according to your image, but d and u then look like that no matter in which context.

if (substitutionType === '12') {
  // leave the code inside of this but remove the if/else statements
  // [...]
} else {
    throw new Error(`Substitution type ${substitutionType} is not supported in chaining substitution`);
}

Connum avatar Mar 21 '23 15:03 Connum

What confuses me even more is the fact that we're not using the backtrack and lookahead at all, apart from checking their length for the contextRulesMatch constant, or am I missing sonething?

Connum avatar Mar 21 '23 16:03 Connum

The exception being thrown here was added in PR #486. I believe it's having the desired effect which is crashing on an unsupported substitution instead of silently substituting incorrectly, which is what happens if the exception is commented out. Let me take a look at what substitution type '11' is.

CTOStone avatar Mar 21 '23 23:03 CTOStone

Btw, totally open to changing this to a warning instead.

CTOStone avatar Mar 21 '23 23:03 CTOStone

Ok the relevant table type is https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#11-single-substitution-format-1

CTOStone avatar Mar 21 '23 23:03 CTOStone

So yeah, this is just another gap in opentype.js's chained sequence context format 3 implementation. I don't think it would be particularly hard to add, given that it's the absolute simplest form of substitution, but nevertheless it is missing.

CTOStone avatar Mar 21 '23 23:03 CTOStone

getLookupMethod supports 11 lookup tables amongst others, should the if statement on line 165 be removed? The logic inside there should work as is for any of formats. getLookupMethod already throws if it's passed a format that it doesn't support. @Connum I guess there is a difference between what was there before #486 where it had the if but no else, and just removing the if entirely which is what you tested. Not sure if the display you were getting is correct though.

CTOStone avatar Mar 21 '23 23:03 CTOStone

Yeah, that's what I thought. However, it then always uses the replacement forms, because we're not actually checking the backtrack and lookahead context. Going on the commit message, I guess chaining substitution was never implemented correctly and it was more of a hotfix for some ligatures used in Arabic scripts.

Connum avatar Mar 22 '23 08:03 Connum

@st0nerhat This exception is totally fine for me, even more correct when I explicitly tell opentype.js to use 'liga' feature. Font contains those ligatures but opentype.js can't use them properly in current state.

I tested more solutions to my task (text -> path conversion). One more comparison:

HTML <p> tag (correct output) : image

Opentype.js (incorrect output, no ligatures): image

Opentype.js without substitutionType check (incorrect output, ligatures everywhere instead of specific combinations of letters) image

Typr.js (incorrect output, no ligatures): image

Typr.js + HarfBuzz.js [explanation] (correct output): image

I would call this piece of code "partially implemented" rather than "incorrect". Before contextRulesMatch there are some lookahead / backtrack calculations for Tashkeel Arabic characters, whatever they are.

lwollnikowski avatar Mar 24 '23 08:03 lwollnikowski