Fix OpenType Context Substitution Format 3 and decomposition type handling for Arabic/emoji shaping#824
Open
fjobeir wants to merge 12 commits intoopentypejs:masterfrom
Open
Fix OpenType Context Substitution Format 3 and decomposition type handling for Arabic/emoji shaping#824fjobeir wants to merge 12 commits intoopentypejs:masterfrom
fjobeir wants to merge 12 commits intoopentypejs:masterfrom
Conversation
Replace contextSubstitutionFormat3 with implementation that uses lookupCoverageList and getLookupByIndex for proper GSUB lookup type 5 substFormat 3 handling. Some Arabic fonts (e.g. IBM Plex Sans Arabic) use this lookup format for contextual letter forms. https://claude.ai/code/session_0192MAXgejpjkKBgChuyTFcd
Fix OpenType Context Substitution Format 3 for Arabic font shaping
Each lookupRecord should only substitute the glyph at its sequenceIndex, not iterate all input lookups. This caused duplicate results (e.g. [54,54,54,54] instead of [54,54]). https://claude.ai/code/session_0192MAXgejpjkKBgChuyTFcd
Fix contextSubstitutionFormat3 producing duplicate substitutions
The chaining context substitution handler only supported type '12' (single substitution format 2) and threw on all other types. Some fonts (e.g. noto-emoji) use type '21' (multiple/decomposition substitution) within chaining context lookups. https://claude.ai/code/session_0192MAXgejpjkKBgChuyTFcd
Handle decomposition substitution type 21 in chainingSubstitutionFormat3
Same fix as contextSubstitutionFormat3: each lookupRecord should only substitute the glyph at its sequenceIndex, not iterate all input lookups. Fixes duplicate glyph output in emoji/flag shaping. https://claude.ai/code/session_0192MAXgejpjkKBgChuyTFcd
Fix chainingSubstitutionFormat3 to use lookupRecord.sequenceIndex
Context substitution format 3 only handled nested lookup type '12' (single substitution). Some fonts (e.g. noto-emoji) use type '21' (multiple/decomposition substitution) in context substitution lookup records. Without handling type '21', the second covered glyph was not processed by the context substitution, causing it to be re-matched and duplicated in the ccmp pipeline. https://claude.ai/code/session_0192MAXgejpjkKBgChuyTFcd
3 tasks
fjobeir
pushed a commit
to fjobeir/satori
that referenced
this pull request
Apr 3, 2026
Keep @shuding/opentype.js at 1.4.0-beta.0 (the published version). The opentype.js Context Substitution Format 3 fix is tracked separately in opentypejs/opentype.js#824. Restore webkit-text-stroke snapshots to match the published dependency. https://claude.ai/code/session_0192MAXgejpjkKBgChuyTFcd
3 tasks
Author
|
Hi @fdb would you please take a look whenever possible |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Replace the contextSubstitutionFormat3 implementation in src/features/featureQuery.mjs with one that uses lookupCoverageList and the standard lookup infrastructure (getLookupByIndex, getLookupMethod, getSubstitutionType) for proper GSUB lookup type 5, substFormat 3 handling. Additionally, fix both contextSubstitutionFormat3 and chainingSubstitutionFormat3 to use lookupRecord.sequenceIndex instead of iterating all input lookups, and add support for decomposition substitution type 21 in both functions.
Also adds a version field (1.4.0-beta.1) to package.json to support downstream dependency resolution.
Motivation and Context
Some Arabic fonts (e.g. IBM Plex Sans Arabic) use OpenType Context Substitution Format 3 (GSUB lookup type 5, substFormat 3) for contextual letter forms. The previous implementation bypassed the standard lookup infrastructure and could fail to correctly resolve substitutions. Additionally, both contextSubstitutionFormat3 and chainingSubstitutionFormat3 iterated all input lookups for each lookup record instead of targeting the specific glyph at sequenceIndex, causing duplicate glyph output. Fonts like noto-emoji that use nested decomposition substitution (type 21) within context/chaining lookups also failed because only type 12 was handled.
This is part of a broader effort to fix RTL (Arabic/Hebrew) text rendering in Next.js OG image generation, which uses Satori -> opentype.js for font shaping.
How Has This Been Tested?
Screenshots (if appropriate):
N/A — changes affect font shaping logic, validated via automated tests.
Types of changes
Checklist:
npm run testand all tests passed green (including code styling checks).