From 6f3d00ae6d5926506b102b70a667088df422d4e7 Mon Sep 17 00:00:00 2001 From: kiliman Date: Tue, 12 May 2020 09:36:58 -0400 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=AA=93=20Add=20hack=20for=20italics?= =?UTF-8?q?=20by=20removing=20existing=20GSUB=20tables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.bat | 16 ++++++---- build.sh | 17 +++++++---- gsub.js | 49 ++++++++++++++++++++---------- index.js | 86 ++++++++++++++++++++++++++++------------------------- sample.html | 2 +- sample.js | 12 ++++++++ 6 files changed, 113 insertions(+), 69 deletions(-) diff --git a/build.bat b/build.bat index 320343f..b70c253 100644 --- a/build.bat +++ b/build.bat @@ -3,19 +3,25 @@ setlocal if not exist .\build\* mkdir build +set flags= +if "%1"=="--italics-hack" ( + set flags=%1 + shift +) rem only build passed in font -if not "%1%"=="" ( - call :build_font %1 +if not "%1"=="" ( + call :build_font %1 %flags% exit /b ) rem build all fonts -for /d %%d in (.\ligature\*) do call :build_font %%~nd +for /d %%d in (.\ligature\*) do call :build_font %%~nd %flags% exit /b :build_font set lig=%1 +set flags=%2 set otf=%lig:Lig-=-% if not exist .\original\%otf%.otf exit /b @@ -23,6 +29,6 @@ if not exist .\ligature\%lig%\glyphs\* exit /b @echo Building %lig% ttx -f .\original\%otf%.otf -node index.js %otf% -for %%f in (.\build\%lig%*.ttx) do ttx -f %%f +node index.js %otf% %flags% +ttx -f .\build\%lig%.ttx exit /b diff --git a/build.sh b/build.sh index a27374d..ea9765a 100755 --- a/build.sh +++ b/build.sh @@ -4,6 +4,7 @@ mkdir -p build build_font() { lig="$1" + flags="$2" otf=${lig/Lig-/-} if [ ! -e "./original/$otf.otf" ] @@ -17,20 +18,24 @@ build_font() { echo Building $1 ttx -f "./original/$otf.otf" - node index.js $otf + node index.js $otf $flags - for f in ./build/$1*.ttx ; do - ttx -f $f - done + ttx -f ./build/$1.ttx } +flags= +if [ "$1" = "--italics-hack" ] +then + flags=$1 + shift +fi if [ -n "$1" ] then # build specified font - build_font $1 + build_font $1 $flags else # build all available fonts for d in ./ligature/*/ ; do - build_font $(basename $d) + build_font $(basename $d) $flags done fi diff --git a/gsub.js b/gsub.js index 3e8f6ef..d8bfe3e 100644 --- a/gsub.js +++ b/gsub.js @@ -12,7 +12,7 @@ let lookupIndex = 0; let chainIndex = 0; const substLookupMap = {}; -const buildGsubTables = (_dom, ligature) => { +const buildGsubTables = (_dom, ligature, options) => { dom = _dom; if (/_/.test(ligature.glyph) === false) { @@ -35,6 +35,17 @@ const buildGsubTables = (_dom, ligature) => { // look for 'calt' feature featureListDom = xpath.select('FeatureList', gsubDom, true); + if (options.italicsHack) { + // remove all features from FeatureList + Array.from(featureListDom.childNodes).forEach((c) => + featureListDom.removeChild(c) + ); + + Array.from(xpath.select('ttFont/GSUB//FeatureIndex', dom)).forEach((c) => + c.parentNode.removeChild(c); + ); + } + let featureRecord; const featureTag = xpath.select( 'FeatureRecord/FeatureTag[@value="calt"]', @@ -48,7 +59,7 @@ const buildGsubTables = (_dom, ligature) => { true ); featureRecord = createElementWithAttributes('FeatureRecord', { - index: featureListCount + index: featureListCount, }); featureRecord.appendChild( createElementWithAttributes('FeatureTag', { value: 'calt' }) @@ -67,10 +78,17 @@ const buildGsubTables = (_dom, ligature) => { addFeatureToScriptList('latn', featureIndex); lookupListDom = xpath.select('LookupList', gsubDom, true); + if (options.italicsHack) { + // remove all lookups from LookupList + Array.from(lookupListDom.childNodes).forEach((c) => + lookupListDom.removeChild(c) + ); + } + lookupIndex = xpath.select('count(Lookup)', lookupListDom, true); const lookupDom = createElementWithAttributes('Lookup', { - index: lookupIndex++ + index: lookupIndex++, }); appendChildren( lookupDom, @@ -87,7 +105,7 @@ const buildGsubTables = (_dom, ligature) => { featureDom.appendChild( createElementWithAttributes('LookupListIndex', { index: featureLookupCount, - value: lookupDom.getAttribute('index') + value: lookupDom.getAttribute('index'), }) ); @@ -122,12 +140,11 @@ const finalizeGsubTables = () => { lookupListDom, false ); - substLookups.forEach(substLookup => { + substLookups.forEach((substLookup) => { substLookup.setAttribute('value', newIndex); }); lookup.setAttribute('index', newIndex++); lookupListDom.appendChild(lookup); - lookupListDom.appendChild(lookup); }); }; @@ -150,20 +167,20 @@ const addFeatureToScriptList = (tag, featureIndex) => { defaultLangSys.appendChild( createElementWithAttributes('FeatureIndex', { index: count, - value: featureIndex + value: featureIndex, }) ); } }; -const buildBacktrackFirst = glyphs => { +const buildBacktrackFirst = (glyphs) => { // backtrack = first glyph // input = first glphy // lookAhead = [1..n] return buildChainContext([glyphs[0]], [glyphs[0]], glyphs.slice(1)); }; -const buildLookAheadLast = glyphs => { +const buildLookAheadLast = (glyphs) => { // backtrack = none // input = first glyph // lookAhead = [1..n] + [n] @@ -186,12 +203,12 @@ const buildLigatureSubst = (glyphs, lookup) => { ); }; -const getLIGs = length => new Array(length).fill('LIG', 0, length); +const getLIGs = (length) => new Array(length).fill('LIG', 0, length); const buildChainContext = (backtrack, input, lookAhead, substitute) => { const chainDom = createElementWithAttributes('ChainContextSubst', { index: chainIndex++, - Format: 3 + Format: 3, }); buildCoverage(chainDom, 'BacktrackCoverage', backtrack); @@ -218,7 +235,7 @@ const buildCoverage = (chainDom, tagName, coverage) => { coverage.forEach((glyph, i) => { const coverageDom = createElementWithAttributes(tagName, { index: i++, - Format: 1 + Format: 1, }); coverageDom.appendChild( createElementWithAttributes('Glyph', { value: glyph }) @@ -235,7 +252,7 @@ const getSubstLookup = (input, output, lookupDom) => { if (!lookupDom) { lookupDom = createElementWithAttributes('Lookup', { - index: Object.keys(substLookupMap).length + 1 + index: Object.keys(substLookupMap).length + 1, }); lookupDom.appendChild( createElementWithAttributes('LookupType', { value: 1 }) @@ -251,7 +268,7 @@ const getSubstLookup = (input, output, lookupDom) => { if (!singleSubstDom) { singleSubstDom = createElementWithAttributes('SingleSubst', { index: 0, - Format: 2 + Format: 2, }); lookupDom.appendChild(singleSubstDom); } @@ -272,14 +289,14 @@ const getSubstLookup = (input, output, lookupDom) => { const createElementWithAttributes = (tagName, attributes) => { const element = dom.createElement(tagName); - Object.entries(attributes).forEach(attribute => { + Object.entries(attributes).forEach((attribute) => { element.setAttribute(attribute[0], attribute[1]); }); return element; }; const appendChildren = (node, ...children) => { - children.forEach(child => node.appendChild(child)); + children.forEach((child) => node.appendChild(child)); }; //const serialize = dom => new XMLSerializer().serializeToString(dom); diff --git a/index.js b/index.js index fd3e051..bf2bd13 100644 --- a/index.js +++ b/index.js @@ -17,8 +17,12 @@ const NodeType = {}; NodeType.TEXT_NODE = 3; let dom; +let italicsHack = false; function main() { fontName = process.argv[2]; + italicsHack = + process.argv[3] === '--italics-hack' && fontName.includes('Italic'); + ligFontName = fontName.split('-').join('Lig-'); const srcFileName = `./original/${fontName}.ttx`; @@ -29,12 +33,12 @@ function main() { const profiles = getProfiles(); // process settings (there may be more than one font to build) - profiles.forEach(profile => buildFont(profile)); + profiles.forEach((profile) => buildFont(profile, { italicsHack })); console.log('Done'); } -const buildFont = profile => { +const buildFont = (profile, options) => { // add suffix to dstFileName if present const dstFileName = `./build/${ligFontName}${profile.suffixWithLeadingHyphen}.ttx`; console.log( @@ -44,19 +48,19 @@ const buildFont = profile => { const ligatures = sortLigatures( fs .readdirSync(`./ligature/${ligFontName}/glyphs`) - .filter(file => /\.xml$/.test(file)) - .map(file => file.replace('.xml', '')) + .filter((file) => /\.xml$/.test(file)) + .map((file) => file.replace('.xml', '')) ) - .filter(name => !/\d+\.liga$/.test(name)) // skip alternates (ends with .#) - .filter(name => filterLigatures(name, profile.ligatures)) - .map(name => mapLigatures(name, profile.ligatures)) - .filter(entry => filterGlyphsExists(entry)); + .filter((name) => !/\d+\.liga$/.test(name)) // skip alternates (ends with .#) + .filter((name) => filterLigatures(name, profile.ligatures)) + .map((name) => mapLigatures(name, profile.ligatures)) + .filter((entry) => filterGlyphsExists(entry)); const ligaturesWithLIG = ligatures; //[...ligatures, { name: 'LIG', glyph: 'LIG' }]; processPatch('names', patchNames, dom, ligatures, profile); - processPatch('gsub', patchGsub, dom, ligatures); + processPatch('gsub', patchGsub, dom, ligatures, options); processPatch('charstrings', patchCharStrings, dom, ligaturesWithLIG); processPatch('glyphs', patchGlyphs, dom, ligaturesWithLIG); processPatch('hmtx', patchHmtx, dom); @@ -75,8 +79,8 @@ const getProfiles = () => { suffix: '', suffixWithLeadingSpace: '', suffixWithLeadingHyphen: '', - ligatures: [] - } + ligatures: [], + }, ]; } @@ -86,8 +90,8 @@ const getProfiles = () => { const content = fs.readFileSync(profilePath, 'utf-8'); content .split(/\r|\r\n|\n/g) - .filter(line => /^[#]/.test(line) === false && line.length > 0) - .forEach(line => { + .filter((line) => /^[#]/.test(line) === false && line.length > 0) + .forEach((line) => { const ch = line.trim()[0]; if (ch === '[') { let name = line.substr(1, line.indexOf(']') - 1); @@ -96,7 +100,7 @@ const getProfiles = () => { suffix: name === 'default' ? '' : name, suffixWithLeadingSpace: name === 'default' ? '' : ' ' + name, suffixWithLeadingHyphen: name === 'default' ? '' : '-' + name, - ligatures: [] + ligatures: [], }; profiles.push(profile); } else { @@ -113,12 +117,12 @@ const getProfiles = () => { const filterLigatures = (name, mappings) => { // loop through mappings and return if name applies or not // skip if setting is !name - return mappings.filter(entry => entry === '!' + name).length === 0; + return mappings.filter((entry) => entry === '!' + name).length === 0; }; const mapLigatures = (name, mappings) => { let mapping = { name, glyph: name }; - mappings.forEach(entry => { + mappings.forEach((entry) => { const [val1, val2] = entry.split('='); // handle lig=altliga if (val1 === name) { @@ -135,10 +139,10 @@ const filterGlyphsExists = ({ glyph }) => { return exists; }; -const sortLigatures = ligatures => { +const sortLigatures = (ligatures) => { // sort by most glyphs then alphabetically const sorted = ligatures - .map(ligature => { + .map((ligature) => { return { count: ligature.split('_').length + 1, ligature: ligature }; }) .sort( @@ -146,7 +150,7 @@ const sortLigatures = ligatures => { -compareProperty(a.count, b.count) || // sort by count descending compareProperty(a.ligature, b.ligature) // then by ligature alphabetically ) - .map(entry => entry.ligature); + .map((entry) => entry.ligature); return sorted; }; @@ -158,20 +162,20 @@ const compareProperty = (a, b) => { } }; -const loadXml = name => { +const loadXml = (name) => { const fileName = `./ligature/${ligFontName}/${name}.xml`; const xml = fs.readFileSync(fileName, 'utf-8'); return new DOMParser().parseFromString(xml); }; -const processPatch = (name, patchFunc, dom, ligatures, profile) => { +const processPatch = (name, patchFunc, dom, ligatures, profile, options) => { console.log(`Patching ${name}`); - patchFunc(dom, ligatures, profile); + patchFunc(dom, ligatures, profile, options); }; const PlatformId = { mac: 1, - win: 3 + win: 3, }; const NameId = { @@ -182,7 +186,7 @@ const NameId = { version: 5, postscriptName: 6, windowsFamilyName: 16, - fontStyleName: 17 + fontStyleName: 17, }; const patchNames = (dom, ligatures, profile) => { @@ -253,7 +257,7 @@ const patchGlyphs = (dom, ligatures) => { // only import glyphs specified let n = glyphsCount; // get ligature glyphs - ligatures.forEach(ligature => { + ligatures.forEach((ligature) => { targetGlyphs.appendChild( createElementWithAttributes('GlyphID', { id: n++, name: ligature.glyph }) ); @@ -262,18 +266,18 @@ const patchGlyphs = (dom, ligatures) => { setAttribute(dom, '/ttFont/maxp/numGlyphs', 'value', n); }; -const patchGsub = (dom, ligatures) => { +const patchGsub = (dom, ligatures, options) => { // don't patch if no ligatures other than LIG if (ligatures.length <= 1) return; - ligatures.forEach(ligature => { + ligatures.forEach((ligature) => { // build gsub tables - gsub.buildGsubTables(dom, ligature); + gsub.buildGsubTables(dom, ligature, options); }); gsub.finalizeGsubTables(); }; -const patchHmtx = dom => { +const patchHmtx = (dom) => { const mtxCount = xpath.select('count(/ttFont/hmtx/mtx)', dom, true); setAttribute(dom, '/ttFont/hhea/numberOfHMetrics', 'value', mtxCount); @@ -306,7 +310,7 @@ const patchCharStrings = (dom, ligatures) => { ); const targetGsubrs = xpath.select('/ttFont/CFF/GlobalSubrs', dom, true); const fingerprints = {}; - ligatures.forEach(ligature => { + ligatures.forEach((ligature) => { console.log( `* ${ligature.name}${ ligature.name === ligature.glyph ? '' : ' => ' + ligature.glyph @@ -318,11 +322,11 @@ const patchCharStrings = (dom, ligatures) => { const subrs = { sourcePath: '/Glyph/Subrs', - target: targetSubrs + target: targetSubrs, }; const gsubrs = { sourcePath: '/Glyph/GlobalSubrs', - target: targetGsubrs + target: targetGsubrs, }; patchCharStringSubrs(glyphDom, node, fingerprints, subrs, gsubrs); @@ -332,16 +336,16 @@ const patchCharStrings = (dom, ligatures) => { createElementWithAttributes('mtx', { name: ligature.glyph, width: glyphDom.getAttribute('width'), - lsb: glyphDom.getAttribute('lsb') + lsb: glyphDom.getAttribute('lsb'), }) ); const code = glyphDom.getAttribute('code'); if (code) { - Array.from(targetCmaps).forEach(node => { + Array.from(targetCmaps).forEach((node) => { node.appendChild( createElementWithAttributes('map', { code, - name: ligature.glyph + name: ligature.glyph, }) ); }); @@ -354,7 +358,7 @@ const patchCharStringSubrs = (glyphDom, node, fingerprints, subrs, gsubrs) => { const lines = node.childNodes[0].textContent.split(/\r|\r\n|\n/g); const newLines = []; - lines.forEach(line => { + lines.forEach((line) => { if (line.trim().length === 0) return; const matches = line.match(/(.*?)\{([0-9a-z]+)\} (callsubr|callgsubr)$/); if (matches != null) { @@ -403,21 +407,21 @@ const copyConfigAttribute = (dom, configDom, path, name) => { const createElementWithAttributes = (tagName, attributes) => { const element = dom.createElement(tagName); - Object.entries(attributes).forEach(attribute => { + Object.entries(attributes).forEach((attribute) => { element.setAttribute(attribute[0], attribute[1]); }); return element; }; //const dump = dom => console.log(serialize(dom)); -const serialize = dom => - new XMLSerializer().serializeToString(dom, false, node => { +const serialize = (dom) => + new XMLSerializer().serializeToString(dom, false, (node) => { if (node.nodeType === NodeType.TEXT_NODE) { if (regExWhitespace.test(node.data)) return null; const data = node.data .split(/\r|\r\n|\n/g) - .filter(s => /\S+/.test(s)) - .map(s => s.replace(/^\s+/g, '')) + .filter((s) => /\S+/.test(s)) + .map((s) => s.replace(/^\s+/g, '')) .join(os.EOL); return node.ownerDocument.createTextNode(data); diff --git a/sample.html b/sample.html index f55b981..5c8d5ad 100644 --- a/sample.html +++ b/sample.html @@ -5,7 +5,7 @@ } - +
Click Here diff --git a/sample.js b/sample.js index dee8ba7..5a23062 100644 --- a/sample.js +++ b/sample.js @@ -1,3 +1,5 @@ +// @ts-nocheck + const func = (a, b, c, d, e, f, s, n) => { // this is a comment if (a++ && b-- || c >= d || e <= f) { @@ -10,6 +12,16 @@ const func = (a, b, c, d, e, f, s, n) => { a |> b a <| b |> c -> d ==> e + // if (a++ && b-- || c >= d || e <= f) { + // } + // if (s === 'string' && n !== 999) { + // } + // if (a == b && c != d) + // a ?? b + // a <- b <-- c <== d + // a |> b + // a <| b |> c -> d ==> e + // italic ligatures // != == >= <= -> => ==> // === !== && || ++ -- From 321486d19ea3fe1916e7e13f68853f0da51d2c47 Mon Sep 17 00:00:00 2001 From: kiliman Date: Tue, 12 May 2020 12:05:21 -0400 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=90=9B=20Add=20init=20function=20to?= =?UTF-8?q?=20only=20apply=20the=20hack=20once=20instead=20of=20per=20liga?= =?UTF-8?q?ture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gsub.js | 46 +++++++++++++++++++++++++++------------------- index.js | 1 + 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/gsub.js b/gsub.js index d8bfe3e..7fe456e 100644 --- a/gsub.js +++ b/gsub.js @@ -12,6 +12,32 @@ let lookupIndex = 0; let chainIndex = 0; const substLookupMap = {}; +const initGsubTables = (_dom, options) => { + dom = _dom; + + gsubDom = xpath.select('ttFont/GSUB', dom, true); + + // look for 'calt' feature + featureListDom = xpath.select('FeatureList', gsubDom, true); + lookupListDom = xpath.select('LookupList', gsubDom, true); + + if (options.italicsHack) { + // remove all features from FeatureList + Array.from(featureListDom.childNodes).forEach((c) => { + featureListDom.removeChild(c); + }); + + Array.from(xpath.select('ttFont/GSUB//FeatureIndex', dom)).forEach((c) => { + c.parentNode.removeChild(c); + }); + + // remove all lookups from LookupList + Array.from(lookupListDom.childNodes).forEach((c) => + lookupListDom.removeChild(c) + ); + } +}; + const buildGsubTables = (_dom, ligature, options) => { dom = _dom; @@ -35,17 +61,6 @@ const buildGsubTables = (_dom, ligature, options) => { // look for 'calt' feature featureListDom = xpath.select('FeatureList', gsubDom, true); - if (options.italicsHack) { - // remove all features from FeatureList - Array.from(featureListDom.childNodes).forEach((c) => - featureListDom.removeChild(c) - ); - - Array.from(xpath.select('ttFont/GSUB//FeatureIndex', dom)).forEach((c) => - c.parentNode.removeChild(c); - ); - } - let featureRecord; const featureTag = xpath.select( 'FeatureRecord/FeatureTag[@value="calt"]', @@ -77,14 +92,6 @@ const buildGsubTables = (_dom, ligature, options) => { addFeatureToScriptList('DFLT', featureIndex); addFeatureToScriptList('latn', featureIndex); - lookupListDom = xpath.select('LookupList', gsubDom, true); - if (options.italicsHack) { - // remove all lookups from LookupList - Array.from(lookupListDom.childNodes).forEach((c) => - lookupListDom.removeChild(c) - ); - } - lookupIndex = xpath.select('count(Lookup)', lookupListDom, true); const lookupDom = createElementWithAttributes('Lookup', { @@ -301,5 +308,6 @@ const appendChildren = (node, ...children) => { //const serialize = dom => new XMLSerializer().serializeToString(dom); +exports.initGsubTables = initGsubTables; exports.buildGsubTables = buildGsubTables; exports.finalizeGsubTables = finalizeGsubTables; diff --git a/index.js b/index.js index bf2bd13..afec6c9 100644 --- a/index.js +++ b/index.js @@ -269,6 +269,7 @@ const patchGlyphs = (dom, ligatures) => { const patchGsub = (dom, ligatures, options) => { // don't patch if no ligatures other than LIG if (ligatures.length <= 1) return; + gsub.initGsubTables(dom, options); ligatures.forEach((ligature) => { // build gsub tables From e692fcfbf88625965c307cbd2f56ed71a5be9237 Mon Sep 17 00:00:00 2001 From: kiliman Date: Wed, 13 May 2020 08:23:09 -0400 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=94=A8=20Make=20italics-hack=20the=20?= =?UTF-8?q?default=20option=20with=20ability=20to=20turn=20it=20off?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.bat | 2 +- build.sh | 2 +- index.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.bat b/build.bat index b70c253..ff18de5 100644 --- a/build.bat +++ b/build.bat @@ -4,7 +4,7 @@ setlocal if not exist .\build\* mkdir build set flags= -if "%1"=="--italics-hack" ( +if "%1"=="--italics-hack-off" ( set flags=%1 shift ) diff --git a/build.sh b/build.sh index ea9765a..3ccf9d0 100755 --- a/build.sh +++ b/build.sh @@ -24,7 +24,7 @@ build_font() { } flags= -if [ "$1" = "--italics-hack" ] +if [ "$1" = "--italics-hack-off" ] then flags=$1 shift diff --git a/index.js b/index.js index afec6c9..0d3363f 100644 --- a/index.js +++ b/index.js @@ -21,7 +21,7 @@ let italicsHack = false; function main() { fontName = process.argv[2]; italicsHack = - process.argv[3] === '--italics-hack' && fontName.includes('Italic'); + fontName.includes('Italic') && process.argv[3] !== '--italics-hack-off'; ligFontName = fontName.split('-').join('Lig-');