🪓 Add hack for italics by removing existing GSUB tables

This commit is contained in:
kiliman
2020-05-12 09:36:58 -04:00
parent 420eeefe59
commit 6f3d00ae6d
6 changed files with 113 additions and 69 deletions
+11 -5
View File
@@ -3,19 +3,25 @@ setlocal
if not exist .\build\* mkdir build if not exist .\build\* mkdir build
set flags=
if "%1"=="--italics-hack" (
set flags=%1
shift
)
rem only build passed in font rem only build passed in font
if not "%1%"=="" ( if not "%1"=="" (
call :build_font %1 call :build_font %1 %flags%
exit /b exit /b
) )
rem build all fonts 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 exit /b
:build_font :build_font
set lig=%1 set lig=%1
set flags=%2
set otf=%lig:Lig-=-% set otf=%lig:Lig-=-%
if not exist .\original\%otf%.otf exit /b if not exist .\original\%otf%.otf exit /b
@@ -23,6 +29,6 @@ if not exist .\ligature\%lig%\glyphs\* exit /b
@echo Building %lig% @echo Building %lig%
ttx -f .\original\%otf%.otf ttx -f .\original\%otf%.otf
node index.js %otf% node index.js %otf% %flags%
for %%f in (.\build\%lig%*.ttx) do ttx -f %%f ttx -f .\build\%lig%.ttx
exit /b exit /b
+11 -6
View File
@@ -4,6 +4,7 @@ mkdir -p build
build_font() { build_font() {
lig="$1" lig="$1"
flags="$2"
otf=${lig/Lig-/-} otf=${lig/Lig-/-}
if [ ! -e "./original/$otf.otf" ] if [ ! -e "./original/$otf.otf" ]
@@ -17,20 +18,24 @@ build_font() {
echo Building $1 echo Building $1
ttx -f "./original/$otf.otf" ttx -f "./original/$otf.otf"
node index.js $otf node index.js $otf $flags
for f in ./build/$1*.ttx ; do ttx -f ./build/$1.ttx
ttx -f $f
done
} }
flags=
if [ "$1" = "--italics-hack" ]
then
flags=$1
shift
fi
if [ -n "$1" ] if [ -n "$1" ]
then then
# build specified font # build specified font
build_font $1 build_font $1 $flags
else else
# build all available fonts # build all available fonts
for d in ./ligature/*/ ; do for d in ./ligature/*/ ; do
build_font $(basename $d) build_font $(basename $d) $flags
done done
fi fi
+33 -16
View File
@@ -12,7 +12,7 @@ let lookupIndex = 0;
let chainIndex = 0; let chainIndex = 0;
const substLookupMap = {}; const substLookupMap = {};
const buildGsubTables = (_dom, ligature) => { const buildGsubTables = (_dom, ligature, options) => {
dom = _dom; dom = _dom;
if (/_/.test(ligature.glyph) === false) { if (/_/.test(ligature.glyph) === false) {
@@ -35,6 +35,17 @@ const buildGsubTables = (_dom, ligature) => {
// look for 'calt' feature // look for 'calt' feature
featureListDom = xpath.select('FeatureList', gsubDom, true); 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; let featureRecord;
const featureTag = xpath.select( const featureTag = xpath.select(
'FeatureRecord/FeatureTag[@value="calt"]', 'FeatureRecord/FeatureTag[@value="calt"]',
@@ -48,7 +59,7 @@ const buildGsubTables = (_dom, ligature) => {
true true
); );
featureRecord = createElementWithAttributes('FeatureRecord', { featureRecord = createElementWithAttributes('FeatureRecord', {
index: featureListCount index: featureListCount,
}); });
featureRecord.appendChild( featureRecord.appendChild(
createElementWithAttributes('FeatureTag', { value: 'calt' }) createElementWithAttributes('FeatureTag', { value: 'calt' })
@@ -67,10 +78,17 @@ const buildGsubTables = (_dom, ligature) => {
addFeatureToScriptList('latn', featureIndex); addFeatureToScriptList('latn', featureIndex);
lookupListDom = xpath.select('LookupList', gsubDom, true); 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); lookupIndex = xpath.select('count(Lookup)', lookupListDom, true);
const lookupDom = createElementWithAttributes('Lookup', { const lookupDom = createElementWithAttributes('Lookup', {
index: lookupIndex++ index: lookupIndex++,
}); });
appendChildren( appendChildren(
lookupDom, lookupDom,
@@ -87,7 +105,7 @@ const buildGsubTables = (_dom, ligature) => {
featureDom.appendChild( featureDom.appendChild(
createElementWithAttributes('LookupListIndex', { createElementWithAttributes('LookupListIndex', {
index: featureLookupCount, index: featureLookupCount,
value: lookupDom.getAttribute('index') value: lookupDom.getAttribute('index'),
}) })
); );
@@ -122,12 +140,11 @@ const finalizeGsubTables = () => {
lookupListDom, lookupListDom,
false false
); );
substLookups.forEach(substLookup => { substLookups.forEach((substLookup) => {
substLookup.setAttribute('value', newIndex); substLookup.setAttribute('value', newIndex);
}); });
lookup.setAttribute('index', newIndex++); lookup.setAttribute('index', newIndex++);
lookupListDom.appendChild(lookup); lookupListDom.appendChild(lookup);
lookupListDom.appendChild(lookup);
}); });
}; };
@@ -150,20 +167,20 @@ const addFeatureToScriptList = (tag, featureIndex) => {
defaultLangSys.appendChild( defaultLangSys.appendChild(
createElementWithAttributes('FeatureIndex', { createElementWithAttributes('FeatureIndex', {
index: count, index: count,
value: featureIndex value: featureIndex,
}) })
); );
} }
}; };
const buildBacktrackFirst = glyphs => { const buildBacktrackFirst = (glyphs) => {
// backtrack = first glyph // backtrack = first glyph
// input = first glphy // input = first glphy
// lookAhead = [1..n] // lookAhead = [1..n]
return buildChainContext([glyphs[0]], [glyphs[0]], glyphs.slice(1)); return buildChainContext([glyphs[0]], [glyphs[0]], glyphs.slice(1));
}; };
const buildLookAheadLast = glyphs => { const buildLookAheadLast = (glyphs) => {
// backtrack = none // backtrack = none
// input = first glyph // input = first glyph
// lookAhead = [1..n] + [n] // 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 buildChainContext = (backtrack, input, lookAhead, substitute) => {
const chainDom = createElementWithAttributes('ChainContextSubst', { const chainDom = createElementWithAttributes('ChainContextSubst', {
index: chainIndex++, index: chainIndex++,
Format: 3 Format: 3,
}); });
buildCoverage(chainDom, 'BacktrackCoverage', backtrack); buildCoverage(chainDom, 'BacktrackCoverage', backtrack);
@@ -218,7 +235,7 @@ const buildCoverage = (chainDom, tagName, coverage) => {
coverage.forEach((glyph, i) => { coverage.forEach((glyph, i) => {
const coverageDom = createElementWithAttributes(tagName, { const coverageDom = createElementWithAttributes(tagName, {
index: i++, index: i++,
Format: 1 Format: 1,
}); });
coverageDom.appendChild( coverageDom.appendChild(
createElementWithAttributes('Glyph', { value: glyph }) createElementWithAttributes('Glyph', { value: glyph })
@@ -235,7 +252,7 @@ const getSubstLookup = (input, output, lookupDom) => {
if (!lookupDom) { if (!lookupDom) {
lookupDom = createElementWithAttributes('Lookup', { lookupDom = createElementWithAttributes('Lookup', {
index: Object.keys(substLookupMap).length + 1 index: Object.keys(substLookupMap).length + 1,
}); });
lookupDom.appendChild( lookupDom.appendChild(
createElementWithAttributes('LookupType', { value: 1 }) createElementWithAttributes('LookupType', { value: 1 })
@@ -251,7 +268,7 @@ const getSubstLookup = (input, output, lookupDom) => {
if (!singleSubstDom) { if (!singleSubstDom) {
singleSubstDom = createElementWithAttributes('SingleSubst', { singleSubstDom = createElementWithAttributes('SingleSubst', {
index: 0, index: 0,
Format: 2 Format: 2,
}); });
lookupDom.appendChild(singleSubstDom); lookupDom.appendChild(singleSubstDom);
} }
@@ -272,14 +289,14 @@ const getSubstLookup = (input, output, lookupDom) => {
const createElementWithAttributes = (tagName, attributes) => { const createElementWithAttributes = (tagName, attributes) => {
const element = dom.createElement(tagName); const element = dom.createElement(tagName);
Object.entries(attributes).forEach(attribute => { Object.entries(attributes).forEach((attribute) => {
element.setAttribute(attribute[0], attribute[1]); element.setAttribute(attribute[0], attribute[1]);
}); });
return element; return element;
}; };
const appendChildren = (node, ...children) => { const appendChildren = (node, ...children) => {
children.forEach(child => node.appendChild(child)); children.forEach((child) => node.appendChild(child));
}; };
//const serialize = dom => new XMLSerializer().serializeToString(dom); //const serialize = dom => new XMLSerializer().serializeToString(dom);
+45 -41
View File
@@ -17,8 +17,12 @@ const NodeType = {};
NodeType.TEXT_NODE = 3; NodeType.TEXT_NODE = 3;
let dom; let dom;
let italicsHack = false;
function main() { function main() {
fontName = process.argv[2]; fontName = process.argv[2];
italicsHack =
process.argv[3] === '--italics-hack' && fontName.includes('Italic');
ligFontName = fontName.split('-').join('Lig-'); ligFontName = fontName.split('-').join('Lig-');
const srcFileName = `./original/${fontName}.ttx`; const srcFileName = `./original/${fontName}.ttx`;
@@ -29,12 +33,12 @@ function main() {
const profiles = getProfiles(); const profiles = getProfiles();
// process settings (there may be more than one font to build) // 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'); console.log('Done');
} }
const buildFont = profile => { const buildFont = (profile, options) => {
// add suffix to dstFileName if present // add suffix to dstFileName if present
const dstFileName = `./build/${ligFontName}${profile.suffixWithLeadingHyphen}.ttx`; const dstFileName = `./build/${ligFontName}${profile.suffixWithLeadingHyphen}.ttx`;
console.log( console.log(
@@ -44,19 +48,19 @@ const buildFont = profile => {
const ligatures = sortLigatures( const ligatures = sortLigatures(
fs fs
.readdirSync(`./ligature/${ligFontName}/glyphs`) .readdirSync(`./ligature/${ligFontName}/glyphs`)
.filter(file => /\.xml$/.test(file)) .filter((file) => /\.xml$/.test(file))
.map(file => file.replace('.xml', '')) .map((file) => file.replace('.xml', ''))
) )
.filter(name => !/\d+\.liga$/.test(name)) // skip alternates (ends with .#) .filter((name) => !/\d+\.liga$/.test(name)) // skip alternates (ends with .#)
.filter(name => filterLigatures(name, profile.ligatures)) .filter((name) => filterLigatures(name, profile.ligatures))
.map(name => mapLigatures(name, profile.ligatures)) .map((name) => mapLigatures(name, profile.ligatures))
.filter(entry => filterGlyphsExists(entry)); .filter((entry) => filterGlyphsExists(entry));
const ligaturesWithLIG = ligatures; const ligaturesWithLIG = ligatures;
//[...ligatures, { name: 'LIG', glyph: 'LIG' }]; //[...ligatures, { name: 'LIG', glyph: 'LIG' }];
processPatch('names', patchNames, dom, ligatures, profile); processPatch('names', patchNames, dom, ligatures, profile);
processPatch('gsub', patchGsub, dom, ligatures); processPatch('gsub', patchGsub, dom, ligatures, options);
processPatch('charstrings', patchCharStrings, dom, ligaturesWithLIG); processPatch('charstrings', patchCharStrings, dom, ligaturesWithLIG);
processPatch('glyphs', patchGlyphs, dom, ligaturesWithLIG); processPatch('glyphs', patchGlyphs, dom, ligaturesWithLIG);
processPatch('hmtx', patchHmtx, dom); processPatch('hmtx', patchHmtx, dom);
@@ -75,8 +79,8 @@ const getProfiles = () => {
suffix: '', suffix: '',
suffixWithLeadingSpace: '', suffixWithLeadingSpace: '',
suffixWithLeadingHyphen: '', suffixWithLeadingHyphen: '',
ligatures: [] ligatures: [],
} },
]; ];
} }
@@ -86,8 +90,8 @@ const getProfiles = () => {
const content = fs.readFileSync(profilePath, 'utf-8'); const content = fs.readFileSync(profilePath, 'utf-8');
content content
.split(/\r|\r\n|\n/g) .split(/\r|\r\n|\n/g)
.filter(line => /^[#]/.test(line) === false && line.length > 0) .filter((line) => /^[#]/.test(line) === false && line.length > 0)
.forEach(line => { .forEach((line) => {
const ch = line.trim()[0]; const ch = line.trim()[0];
if (ch === '[') { if (ch === '[') {
let name = line.substr(1, line.indexOf(']') - 1); let name = line.substr(1, line.indexOf(']') - 1);
@@ -96,7 +100,7 @@ const getProfiles = () => {
suffix: name === 'default' ? '' : name, suffix: name === 'default' ? '' : name,
suffixWithLeadingSpace: name === 'default' ? '' : ' ' + name, suffixWithLeadingSpace: name === 'default' ? '' : ' ' + name,
suffixWithLeadingHyphen: name === 'default' ? '' : '-' + name, suffixWithLeadingHyphen: name === 'default' ? '' : '-' + name,
ligatures: [] ligatures: [],
}; };
profiles.push(profile); profiles.push(profile);
} else { } else {
@@ -113,12 +117,12 @@ const getProfiles = () => {
const filterLigatures = (name, mappings) => { const filterLigatures = (name, mappings) => {
// loop through mappings and return if name applies or not // loop through mappings and return if name applies or not
// skip if setting is !name // 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) => { const mapLigatures = (name, mappings) => {
let mapping = { name, glyph: name }; let mapping = { name, glyph: name };
mappings.forEach(entry => { mappings.forEach((entry) => {
const [val1, val2] = entry.split('='); const [val1, val2] = entry.split('=');
// handle lig=altliga // handle lig=altliga
if (val1 === name) { if (val1 === name) {
@@ -135,10 +139,10 @@ const filterGlyphsExists = ({ glyph }) => {
return exists; return exists;
}; };
const sortLigatures = ligatures => { const sortLigatures = (ligatures) => {
// sort by most glyphs then alphabetically // sort by most glyphs then alphabetically
const sorted = ligatures const sorted = ligatures
.map(ligature => { .map((ligature) => {
return { count: ligature.split('_').length + 1, ligature: ligature }; return { count: ligature.split('_').length + 1, ligature: ligature };
}) })
.sort( .sort(
@@ -146,7 +150,7 @@ const sortLigatures = ligatures => {
-compareProperty(a.count, b.count) || // sort by count descending -compareProperty(a.count, b.count) || // sort by count descending
compareProperty(a.ligature, b.ligature) // then by ligature alphabetically compareProperty(a.ligature, b.ligature) // then by ligature alphabetically
) )
.map(entry => entry.ligature); .map((entry) => entry.ligature);
return sorted; return sorted;
}; };
@@ -158,20 +162,20 @@ const compareProperty = (a, b) => {
} }
}; };
const loadXml = name => { const loadXml = (name) => {
const fileName = `./ligature/${ligFontName}/${name}.xml`; const fileName = `./ligature/${ligFontName}/${name}.xml`;
const xml = fs.readFileSync(fileName, 'utf-8'); const xml = fs.readFileSync(fileName, 'utf-8');
return new DOMParser().parseFromString(xml); return new DOMParser().parseFromString(xml);
}; };
const processPatch = (name, patchFunc, dom, ligatures, profile) => { const processPatch = (name, patchFunc, dom, ligatures, profile, options) => {
console.log(`Patching ${name}`); console.log(`Patching ${name}`);
patchFunc(dom, ligatures, profile); patchFunc(dom, ligatures, profile, options);
}; };
const PlatformId = { const PlatformId = {
mac: 1, mac: 1,
win: 3 win: 3,
}; };
const NameId = { const NameId = {
@@ -182,7 +186,7 @@ const NameId = {
version: 5, version: 5,
postscriptName: 6, postscriptName: 6,
windowsFamilyName: 16, windowsFamilyName: 16,
fontStyleName: 17 fontStyleName: 17,
}; };
const patchNames = (dom, ligatures, profile) => { const patchNames = (dom, ligatures, profile) => {
@@ -253,7 +257,7 @@ const patchGlyphs = (dom, ligatures) => {
// only import glyphs specified // only import glyphs specified
let n = glyphsCount; let n = glyphsCount;
// get ligature glyphs // get ligature glyphs
ligatures.forEach(ligature => { ligatures.forEach((ligature) => {
targetGlyphs.appendChild( targetGlyphs.appendChild(
createElementWithAttributes('GlyphID', { id: n++, name: ligature.glyph }) createElementWithAttributes('GlyphID', { id: n++, name: ligature.glyph })
); );
@@ -262,18 +266,18 @@ const patchGlyphs = (dom, ligatures) => {
setAttribute(dom, '/ttFont/maxp/numGlyphs', 'value', n); 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 // don't patch if no ligatures other than LIG
if (ligatures.length <= 1) return; if (ligatures.length <= 1) return;
ligatures.forEach(ligature => { ligatures.forEach((ligature) => {
// build gsub tables // build gsub tables
gsub.buildGsubTables(dom, ligature); gsub.buildGsubTables(dom, ligature, options);
}); });
gsub.finalizeGsubTables(); gsub.finalizeGsubTables();
}; };
const patchHmtx = dom => { const patchHmtx = (dom) => {
const mtxCount = xpath.select('count(/ttFont/hmtx/mtx)', dom, true); const mtxCount = xpath.select('count(/ttFont/hmtx/mtx)', dom, true);
setAttribute(dom, '/ttFont/hhea/numberOfHMetrics', 'value', mtxCount); 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 targetGsubrs = xpath.select('/ttFont/CFF/GlobalSubrs', dom, true);
const fingerprints = {}; const fingerprints = {};
ligatures.forEach(ligature => { ligatures.forEach((ligature) => {
console.log( console.log(
`* ${ligature.name}${ `* ${ligature.name}${
ligature.name === ligature.glyph ? '' : ' => ' + ligature.glyph ligature.name === ligature.glyph ? '' : ' => ' + ligature.glyph
@@ -318,11 +322,11 @@ const patchCharStrings = (dom, ligatures) => {
const subrs = { const subrs = {
sourcePath: '/Glyph/Subrs', sourcePath: '/Glyph/Subrs',
target: targetSubrs target: targetSubrs,
}; };
const gsubrs = { const gsubrs = {
sourcePath: '/Glyph/GlobalSubrs', sourcePath: '/Glyph/GlobalSubrs',
target: targetGsubrs target: targetGsubrs,
}; };
patchCharStringSubrs(glyphDom, node, fingerprints, subrs, gsubrs); patchCharStringSubrs(glyphDom, node, fingerprints, subrs, gsubrs);
@@ -332,16 +336,16 @@ const patchCharStrings = (dom, ligatures) => {
createElementWithAttributes('mtx', { createElementWithAttributes('mtx', {
name: ligature.glyph, name: ligature.glyph,
width: glyphDom.getAttribute('width'), width: glyphDom.getAttribute('width'),
lsb: glyphDom.getAttribute('lsb') lsb: glyphDom.getAttribute('lsb'),
}) })
); );
const code = glyphDom.getAttribute('code'); const code = glyphDom.getAttribute('code');
if (code) { if (code) {
Array.from(targetCmaps).forEach(node => { Array.from(targetCmaps).forEach((node) => {
node.appendChild( node.appendChild(
createElementWithAttributes('map', { createElementWithAttributes('map', {
code, 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 lines = node.childNodes[0].textContent.split(/\r|\r\n|\n/g);
const newLines = []; const newLines = [];
lines.forEach(line => { lines.forEach((line) => {
if (line.trim().length === 0) return; if (line.trim().length === 0) return;
const matches = line.match(/(.*?)\{([0-9a-z]+)\} (callsubr|callgsubr)$/); const matches = line.match(/(.*?)\{([0-9a-z]+)\} (callsubr|callgsubr)$/);
if (matches != null) { if (matches != null) {
@@ -403,21 +407,21 @@ const copyConfigAttribute = (dom, configDom, path, name) => {
const createElementWithAttributes = (tagName, attributes) => { const createElementWithAttributes = (tagName, attributes) => {
const element = dom.createElement(tagName); const element = dom.createElement(tagName);
Object.entries(attributes).forEach(attribute => { Object.entries(attributes).forEach((attribute) => {
element.setAttribute(attribute[0], attribute[1]); element.setAttribute(attribute[0], attribute[1]);
}); });
return element; return element;
}; };
//const dump = dom => console.log(serialize(dom)); //const dump = dom => console.log(serialize(dom));
const serialize = dom => const serialize = (dom) =>
new XMLSerializer().serializeToString(dom, false, node => { new XMLSerializer().serializeToString(dom, false, (node) => {
if (node.nodeType === NodeType.TEXT_NODE) { if (node.nodeType === NodeType.TEXT_NODE) {
if (regExWhitespace.test(node.data)) return null; if (regExWhitespace.test(node.data)) return null;
const data = node.data const data = node.data
.split(/\r|\r\n|\n/g) .split(/\r|\r\n|\n/g)
.filter(s => /\S+/.test(s)) .filter((s) => /\S+/.test(s))
.map(s => s.replace(/^\s+/g, '')) .map((s) => s.replace(/^\s+/g, ''))
.join(os.EOL); .join(os.EOL);
return node.ownerDocument.createTextNode(data); return node.ownerDocument.createTextNode(data);
+1 -1
View File
@@ -5,7 +5,7 @@
} }
</style> </style>
<!-- html comment --> <!-- html comment a => b && c++ -->
<div class="header"> <div class="header">
<a href="http://localhost">Click Here</a> <a href="http://localhost">Click Here</a>
+12
View File
@@ -1,3 +1,5 @@
// @ts-nocheck
const func = (a, b, c, d, e, f, s, n) => { const func = (a, b, c, d, e, f, s, n) => {
// this is a comment // this is a comment
if (a++ && b-- || c >= d || e <= f) { 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
a <| b |> c -> d ==> e 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 // italic ligatures
// != == >= <= -> => ==> // != == >= <= -> => ==>
// === !== && || ++ -- // === !== && || ++ --