diff --git a/gsub.js b/gsub.js index a97c3aa..330326b 100644 --- a/gsub.js +++ b/gsub.js @@ -1,6 +1,4 @@ const xpath = require('xpath'); -//const { XMLSerializer } = require('xmldom'); -//const format = require('xml-formatter'); const NodeType = {}; NodeType.TEXT_NODE = 3; @@ -15,9 +13,23 @@ let chainIndex = 0; const substLookupMap = {}; const buildGsubTables = (_dom, ligature) => { - const glyphs = ligature.name.replace(/\.liga$/, '').split('_'); - dom = _dom; + + if (/_/.test(ligature.glyph) === false) { + // not a ligature, so see if glyph exists in font (as substitute) + const glyphNode = xpath.select( + `ttFont/GlyphOrder/GlyphID[@name="${ligature.name}"]`, + dom, + true + ); + if (!glyphNode) { + return; + } + } + + const glyphs = ligature.name.replace(/\.liga$/, '').split('_'); + const isSingleGlyph = glyphs.length == 1; + gsubDom = xpath.select('ttFont/GSUB', dom, true); // look for 'calt' feature @@ -62,12 +74,10 @@ const buildGsubTables = (_dom, ligature) => { }); appendChildren( lookupDom, - createElementWithAttributes('LookupType', { value: 6 }), + createElementWithAttributes('LookupType', { value: isSingleGlyph ? 1 : 6 }), createElementWithAttributes('LookupFlag', { value: 0 }) ); - lookupListDom.appendChild(lookupDom); - // add contextual lookup to feature const featureLookupCount = xpath.select( 'count(LookupListIndex)', @@ -81,15 +91,22 @@ const buildGsubTables = (_dom, ligature) => { }) ); - lookupDom.appendChild(buildBacktrackFirst(glyphs)); - lookupDom.appendChild(buildLookAheadLast(glyphs)); - lookupDom.appendChild(buildLigatureSubst(glyphs, ligature.glyph)); - // remaining context - for (let n = glyphs.length - 2; n >= 0; n--) { + if (isSingleGlyph) { lookupDom.appendChild( - buildChainContext(getLIGs(n), [glyphs[n]], glyphs.slice(n + 1), 'LIG') + getSubstLookup(ligature.name, ligature.glyph, lookupDom) ); + } else { + lookupDom.appendChild(buildBacktrackFirst(glyphs)); + lookupDom.appendChild(buildLookAheadLast(glyphs)); + lookupDom.appendChild(buildLigatureSubst(glyphs, ligature.glyph)); + // remaining context + for (let n = glyphs.length - 2; n >= 0; n--) { + lookupDom.appendChild( + buildChainContext(getLIGs(n), [glyphs[n]], glyphs.slice(n + 1), 'LIG') + ); + } } + lookupListDom.appendChild(lookupDom); }; const finalizeGsubTables = () => { @@ -97,9 +114,8 @@ const finalizeGsubTables = () => { let newIndex = xpath.select('count(Lookup)', lookupListDom, true); // append added lookups - Object.entries(substLookupMap).forEach(lookupEntry => { + Object.entries(substLookupMap).forEach(([key, lookup]) => { // fixup existing lookups with new index - const lookup = lookupEntry[1]; const oldIndex = lookup.getAttribute('index'); const substLookups = xpath.select( `//ChainContextSubst/SubstLookupRecord/LookupListIndex[@value="${oldIndex}"]`, @@ -212,8 +228,10 @@ const buildCoverage = (chainDom, tagName, coverage) => { } }; -const getSubstLookup = (input, output) => { - let lookupDom = substLookupMap[output]; +const getSubstLookup = (input, output, lookupDom) => { + if (!lookupDom) { + lookupDom = substLookupMap[output]; + } if (!lookupDom) { lookupDom = createElementWithAttributes('Lookup', { @@ -225,12 +243,18 @@ const getSubstLookup = (input, output) => { lookupDom.appendChild( createElementWithAttributes('LookupFlag', { value: 0 }) ); - lookupDom.appendChild( - createElementWithAttributes('SingleSubst', { index: 0, Format: 2 }) - ); substLookupMap[output] = lookupDom; } - const singleSubstDom = xpath.select('SingleSubst', lookupDom, true); + + let singleSubstDom = xpath.select('SingleSubst', lookupDom, true); + + if (!singleSubstDom) { + singleSubstDom = createElementWithAttributes('SingleSubst', { + index: 0, + Format: 2 + }); + lookupDom.appendChild(singleSubstDom); + } // look for input subst const substitionDom = xpath.select( diff --git a/index.js b/index.js index 311be22..c17a9d0 100644 --- a/index.js +++ b/index.js @@ -58,9 +58,9 @@ const buildFont = profile => { //[...ligatures, { name: 'LIG', glyph: 'LIG' }]; processPatch('names', patchNames, dom, ligatures, profile); - processPatch('glyphs', patchGlyphs, dom, ligaturesWithLIG); processPatch('gsub', patchGsub, dom, ligatures); processPatch('charstrings', patchCharStrings, dom, ligaturesWithLIG); + processPatch('glyphs', patchGlyphs, dom, ligaturesWithLIG); processPatch('hmtx', patchHmtx, dom); console.log(`Writing ligature font file ${dstFileName}`); @@ -103,7 +103,6 @@ const getProfiles = () => { profiles.push(profile); } else { if (!profile) { - console.log(`Found ${ch} instead of [`); throw new Error('You must profile a profile name in []'); } profile.ligatures.push(line); @@ -113,22 +112,24 @@ const getProfiles = () => { return profiles; }; -const filterLigatures = (name, ligatures) => { - // loop through ligatures and return if name applies or not +const filterLigatures = (name, mappings) => { + // loop through mappings and return if name applies or not // skip if setting is !name - return ligatures.filter(ligature => ligature === '!' + name).length === 0; + return mappings.filter(entry => entry === '!' + name).length === 0; }; -const mapLigatures = (name, ligatures) => { - // return { ligature, glyph } - let entry = { name, glyph: name }; - ligatures.forEach(ligature => { - const n = ligature.indexOf('='); - if (n > 0 && ligature.substr(0, n) === name) { - entry.glyph = ligature.substr(n + 1); +const mapLigatures = (name, mappings) => { + let mapping = { name, glyph: name }; + mappings.forEach(entry => { + const [val1, val2] = entry.split('='); + // handle lig=altliga + if (val1 === name) { + mapping.glyph = val2; + } else if (val2 === name) { + mapping.name = val1; } }); - return entry; + return mapping; }; const filterGlyphsExists = ({ glyph }) => { @@ -266,7 +267,7 @@ const patchGsub = (dom, ligatures) => { if (ligatures.length <= 1) return; ligatures - .filter(ligature => /_/.test(ligature.glyph)) + //.filter(ligature => /_/.test(ligature.glyph)) .forEach(ligature => { // build gsub tables gsub.buildGsubTables(dom, ligature);