foliojs / fontkit

An advanced font engine for Node and the browser
1.47k stars 219 forks source link

missing ligature characters #187

Closed jossef closed 5 years ago

jossef commented 5 years ago

I have an existing ttf font where I wish to extract all Ligature mappings into this form:

{
    "calendar_today": "E935",
    "calendar_view_day": "E936",
    ...
}

I'm using this snippet:


const fontkit = require('fontkit');
let font = fontkit.openSync('./MaterialIcons-Regular.ttf');
let lookupList = font.GSUB.lookupList.toArray();
let lookupListIndexes = font.GSUB.featureList[0].feature.lookupListIndexes;

lookupListIndexes.forEach(index => {
    let subTable = lookupList[index].subTables[0];
    let ligatureSets = subTable.ligatureSets.toArray();

    ligatureSets.forEach(ligatureSet => {
        ligatureSet.forEach(ligature => {
            let character = font.stringsForGlyph(ligature.glyph)[0];
            let characterCode = character.charCodeAt(0).toString(16).toUpperCase();

            let ligatureText = ligature
                .components
                .map(x => font.stringsForGlyph(x)[0])
                .join('');

            console.log(`${ligatureText} -> ${characterCode}`);
        });
    });
});

However, I'm not getting the full Ligature name. output:

...
alendar_today -> E935
rop_portrait -> E3C5
ontact_phone -> E0CF
ontrol_point -> E3BA
hevron_right -> E5CC
...

Am I doing something wrong? Judging by the analysis with FontForge, the font's Ligature names are not missing any characters.

image

Pomax commented 5 years ago

Remember that the substitution table's coverage gives you the first glyph, and then the ligaturesets are for "everything after that". To quote the spec:

For example, if the Coverage table lists the glyph index for a lowercase “f,” then a LigatureSet table will define the “ffl,” “fl,” “ffi,” “fi,” and “ff” ligatures.

It does this by not "double-encoding" that first letter, so that first code point is left out of the actual substitution rules.

jossef commented 5 years ago

thanks!

posting my fixed snippet here https://stackoverflow.com/questions/54721774/extracting-ttf-font-ligature-mappings/54728584#54728584

const fontkit = require('fontkit');
let font = fontkit.openSync('./MaterialIcons-Regular.ttf');
let lookupList = font.GSUB.lookupList.toArray();
let lookupListIndexes = font.GSUB.featureList[0].feature.lookupListIndexes;

lookupListIndexes.forEach(index => {
    let subTable = lookupList[index].subTables[0];

    let leadingCharacters = [];
    subTable.coverage.rangeRecords.forEach((coverage) => {
        for (let i = coverage.start; i <= coverage.end; i++) {
            let character = font.stringsForGlyph(i)[0];
            leadingCharacters.push(character);
        }
    });

    let ligatureSets = subTable.ligatureSets.toArray();

    ligatureSets.forEach((ligatureSet, ligatureSetIndex) => {

        let leadingCharacter = leadingCharacters[ligatureSetIndex];

        ligatureSet.forEach(ligature => {
            let character = font.stringsForGlyph(ligature.glyph)[0];
            let characterCode = character.charCodeAt(0).toString(16).toUpperCase();

            let ligatureText = ligature
                .components
                .map(x => font.stringsForGlyph(x)[0])
                .join('');

            ligatureText = leadingCharacter + ligatureText;

            console.log(`${ligatureText} -> ${characterCode}`);
        });
    });
});