Refactor OTL script

This commit is contained in:
Belleve Invis 2020-03-13 22:17:28 -07:00
parent 01cc069462
commit e259814b62
19 changed files with 623 additions and 559 deletions

11
.gitignore vendored
View file

@ -44,19 +44,18 @@ snapshot/iosevka*
snapshot/index.css snapshot/index.css
# Generated scripts # Generated scripts
.buildglyphs.all.patel
build-glyphs.js
parameters.js
support/glyph.js
support/point.js
support/anchor.js support/anchor.js
support/glyph.js
support/parameters.js
support/point.js
support/transform.js support/transform.js
support/simple-expand.js support/simple-expand.js
support/spiroexpand.js support/spiroexpand.js
support/spirokit.js support/spirokit.js
support/utils.js support/utils.js
gen/build-glyphs.js
meta/*.js meta/*.js
meta/feature/*.js otl/*.js
glyphs/*.js glyphs/*.js
package-lock.json package-lock.json

58
gen/build-font.js Normal file
View file

@ -0,0 +1,58 @@
const EmptyFont = require("./empty-font.js");
const buildGlyphs = require("./build-glyphs.js");
const { buildOtl } = require("../otl/index");
const { assignFontNames } = require("../meta/naming");
const { setFontMetrics } = require("../meta/aesthetics");
const regulateGlyphs = require("../support/regulate-glyph");
module.exports = function(para) {
const font = EmptyFont();
const gs = buildGlyphs(para);
assignFontNames(para, gs.metrics, font);
setFontMetrics(para, gs.metrics, font);
const otl = buildOtl(para, gs.glyphs, gs.glyphList, gs.unicodeGlyphs);
font.GSUB = otl.GSUB;
font.GPOS = otl.GPOS;
font.GDEF = otl.GDEF;
// Filtering
if (para.forceMonospace && para.spacing == 0) {
gs.glyphList = gs.glyphList.filter(g => !(g.advanceWidth > para.width));
}
// Regulate
const skew = Math.tan(((font.post.italicAngle || 0) / 180) * Math.PI);
const glyphList = regulateGlyphs(gs.glyphList, skew);
// finalize
const excludedCodePoints = new Set();
if (para.excludedCodePointRanges) {
for (const [start, end] of para.excludedCodePointRanges) {
for (let p = start; p <= end; p++) excludedCodePoints.add(p);
}
}
const { glyf, cmap } = extractGlyfAndCmap(glyphList, excludedCodePoints);
font.glyf = glyf;
font.cmap = cmap;
return font;
};
function extractGlyfAndCmap(glyphList, excludedCodePoints) {
const glyf = {};
const cmap = {};
for (let g of glyphList) {
glyf[g.name] = g;
if (!g.unicode) continue;
for (let u of g.unicode) {
if (!excludedCodePoints.has(u - 0)) cmap[u] = g.name;
}
}
return { glyf, cmap };
}

View file

@ -8,8 +8,6 @@ import '../support/fairify' as fairify
import [mix linreg clamp fallback TempFont includeGlyphPart compsiteMarkSet] from '../support/utils' import [mix linreg clamp fallback TempFont includeGlyphPart compsiteMarkSet] from '../support/utils'
import [calculateMetrics setFontMetrics MarksetDiv GenDivFrame] from '../meta/aesthetics' import [calculateMetrics setFontMetrics MarksetDiv GenDivFrame] from '../meta/aesthetics'
import [assignFontNames] from '../meta/naming'
import '../meta/features' as Features
$$include '../meta/macros.ptl' $$include '../meta/macros.ptl'
@ -25,7 +23,7 @@ define [tagged tag component] : begin
set component.tag tag set component.tag tag
return component return component
define [buildGlyphs para recursive recursiveCodes] : begin export all : define [buildGlyphs para recursive recursiveCodes] : begin
define variantSelector para.variantSelector define variantSelector para.variantSelector
local glyphList {} local glyphList {}
local glyphs {.} local glyphs {.}
@ -144,22 +142,3 @@ define [buildGlyphs para recursive recursiveCodes] : begin
return : object metrics glyphs glyphList unicodeGlyphs return : object metrics glyphs glyphList unicodeGlyphs
# Main build procedure
export as build : define [buildFont font para] : begin
define gs : buildGlyphs para
assignFontNames para gs.metrics font
setFontMetrics para gs.metrics font
local otl : Features.apply para gs.glyphs gs.glyphList gs.unicodeGlyphs
set font.GSUB otl.GSUB
set font.GPOS otl.GPOS
set font.GDEF otl.GDEF
if (para.forceMonospace && para.spacing == 0) : begin
set gs.glyphList : gs.glyphList.filter [lambda [g] : g.advanceWidth <= para.width]
set font.glyfMap gs.glyphs
set font.glyf gs.glyphList
return font

View file

@ -4,7 +4,7 @@ const fs = require("fs-extra");
const path = require("path"); const path = require("path");
const argv = require("yargs").argv; const argv = require("yargs").argv;
const buildGlyphs = require("./build-glyphs.js"); const buildFont = require("./build-font.js");
const EmptyFont = require("./empty-font.js"); const EmptyFont = require("./empty-font.js");
const parameters = require("../support/parameters"); const parameters = require("../support/parameters");
const formVariantData = require("../support/variant-data"); const formVariantData = require("../support/variant-data");
@ -20,21 +20,13 @@ main().catch(e => {
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
async function main() { async function main() {
const font = await buildFont(); const para = await getParameters(argv);
const font = buildFont(para);
if (argv.charmap) await saveCharMap(font); if (argv.charmap) await saveCharMap(font);
if (argv.o) await saveFont(font); if (argv.o) await saveOtd(font);
}
async function tryParseToml(str) {
try {
return toml.parse(await fs.readFile(str, "utf-8"));
} catch (e) {
throw new Error(
`Failed to parse configuration file ${str}.\nPlease validate whether there's syntax error.\n${e}`
);
}
} }
// Parameter preparation
async function getParameters(argv) { async function getParameters(argv) {
const PARAMETERS_TOML = path.resolve(__dirname, "../parameters.toml"); const PARAMETERS_TOML = path.resolve(__dirname, "../parameters.toml");
const PRIVATE_TOML = path.resolve(__dirname, "../private.toml"); const PRIVATE_TOML = path.resolve(__dirname, "../private.toml");
@ -71,13 +63,23 @@ async function getParameters(argv) {
return para; return para;
} }
// Font building async function tryParseToml(str) {
async function buildFont() { try {
const emptyFont = EmptyFont(); return toml.parse(await fs.readFile(str, "utf-8"));
const para = await getParameters(argv); } catch (e) {
const font = buildGlyphs.build(emptyFont, para); throw new Error(
font.parameters = para; `Failed to parse configuration file ${str}.\nPlease validate whether there's syntax error.\n${e}`
return font; );
}
}
// Save OTD
async function saveOtd(font) {
if (argv.o === "|") {
process.stdout.write(JSON.stringify(font));
} else {
await fs.writeFile(argv.o, JSON.stringify(font, null, " "));
}
} }
// Save char map // Save char map
@ -86,50 +88,18 @@ function objHashNonEmpty(obj) {
for (let k in obj) if (obj[k]) return true; for (let k in obj) if (obj[k]) return true;
return false; return false;
} }
async function saveCharMap(font) { async function saveCharMap(font) {
const charmap = font.glyf.map(function(glyph) { let charMap = [];
const isSpace = glyph.contours && glyph.contours.length ? 2 : 0; for (const gid in font.glyf) {
return [ const glyph = font.glyf[gid];
if (!glyph) continue;
const isSpace = glyph.contours && glyph.contours.length;
charMap.push([
glyph.name, glyph.name,
glyph.unicode, glyph.unicode,
glyph.advanceWidth === 0 ? (objHashNonEmpty(glyph.anchors) ? 1 : isSpace ? 2 : 0) : 0 glyph.advanceWidth === 0 ? (objHashNonEmpty(glyph.anchors) ? 1 : isSpace ? 2 : 0) : 0
]; ]);
});
await fs.writeFile(argv.charmap, JSON.stringify(charmap), "utf8");
}
async function saveFont(font) {
const skew = Math.tan(((font.post.italicAngle || 0) / 180) * Math.PI);
regulateGlyphs(font, skew);
// finalize
const excludedCodePoints = new Set();
if (font.parameters.excludedCodePointRanges) {
for (const [start, end] of font.parameters.excludedCodePointRanges) {
for (let p = start; p <= end; p++) excludedCodePoints.add(p);
}
}
const o_glyf = {};
const o_cmap = {};
for (let g of font.glyf) {
o_glyf[g.name] = g;
if (!g.unicode) continue;
for (let u of g.unicode) {
if (!excludedCodePoints.has(u - 0)) o_cmap[u] = g.name;
}
}
// Prepare OTD
const otd = Object.assign({}, font);
otd.glyf = o_glyf;
otd.cmap = o_cmap;
otd.glyfMap = null;
if (argv.o === "|") {
process.stdout.write(JSON.stringify(otd));
} else {
await fs.writeFile(argv.o, JSON.stringify(otd, null, " "));
} }
await fs.writeFile(argv.charmap, JSON.stringify(charMap), "utf8");
} }

View file

@ -18,7 +18,6 @@ glyph-block Symbol-Mosaic-NotDef : begin
sketch # space sketch # space
local df : DivFrame para.diversityI local df : DivFrame para.diversityI
set-width df.width set-width df.width
include df.markSet.e
save 'space' ' ' save 'space' ' '
save 'threePerEmsp' 0x2004 save 'threePerEmsp' 0x2004
save 'fourPerEmsp' 0x2005 save 'fourPerEmsp' 0x2005
@ -30,7 +29,6 @@ glyph-block Symbol-Mosaic-NotDef : begin
sketch # en-space sketch # en-space
local df : DivFrame 1 local df : DivFrame 1
set-width df.width set-width df.width
include df.markSet.e
save 'figureSpace' 0x2007 save 'figureSpace' 0x2007
save 'enquad' 0x2000 save 'enquad' 0x2000
save 'ensp' 0x2002 save 'ensp' 0x2002

View file

@ -1,30 +0,0 @@
define-operator "~>" 880 'right' : syntax-rules
`(@l ~> @r) `{.left @l .right @r}
define [flatten] : begin
local ans {}
foreach [term : items-of : {}.slice.call arguments 0] : begin
if (term <@ Array)
: then : set ans : ans.concat term
: else : ans.push term
return ans
export all : define [buildOPBD chain-rule lookupOrder commonList features lookups] : begin
local fwclose {'fwlcloseDoubleQuote' 'fwlcloseSingleQuote' 'dwlcjkSingleQuoteRight' 'dwlcjkDoubleQuoteRight' 'dwlparenRight'}
local hwclose {'closeDoubleQuote' 'closeSingleQuote' 'cjkSingleQuoteRight' 'cjkDoubleQuoteRight' 'opbdParenRight'}
local fwopen {'fwropenDoubleQuote' 'fwropenSingleQuote' 'dwrcjkSingleQuoteLeft' 'dwrcjkDoubleQuoteLeft' 'dwrparenLeft'}
local hwopen {'openDoubleQuote' 'openSingleQuote' 'cjkSingleQuoteLeft' 'cjkDoubleQuoteLeft' 'opbdParenLeft'}
local fwquoteopen {'fwropenDoubleQuote' 'fwropenSingleQuote'}
local hwquoteopen {'openDoubleQuote' 'openSingleQuote'}
local fwtrail {'dwlperiod' 'dwlcomma' 'dwlcjkperiod' 'dwlcjkcomma'}
local hwtrail {'period' 'comma' 'cjkperiod' 'cjkcomma'}
local fwmid {'dwccolon' 'dwcsemicolon'}
local hwmid {'colon' 'semicolon'}
commonList.push 'opbd'
set features.opbd {'opbd1'}
set lookups.opbd1
.type 'gsub_chaining'
.subtables : list
chain-rule [flatten fwtrail hwtrail fwopen hwopen fwmid hwmid] (fwopen ~> hwopen)
chain-rule (fwclose ~> hwclose) [flatten fwtrail hwtrail fwclose hwclose fwopen hwopen fwmid hwmid]

View file

@ -1,246 +0,0 @@
import 'topsort' as topsort
import 'object-assign' as objectAssign
import '../support/glyph' as Glyph
import '../support/transform' as Transform
import [buildLigations] from './feature/ligation'
import './feature/opbd' as buildOPBD
import './feature/ccmp' as buildCCMP
import [BuildCompatLigatures] from './feature/compat-ligature'
define GDEF_SIMPLE 1
define GDEF_LIGATURE 2
define GDEF_MARK 3
define-operator "~>" 880 'right' : syntax-rules
`(@l ~> @r) `{.left @l .right @r}
# GSUB
define [buildGSUB para glyphs glyphList markGlyphs] : begin
local commonList {}
local languages
.DFLT_DFLT {.features commonList}
.latn_DFLT {.features commonList}
.grek_DFLT {.features commonList}
.cyrl_DFLT {.features commonList}
.kana_DFLT {.features commonList}
.hani_DFLT {.features commonList}
local features {.}
local lookups {.}
local lookupOrder {}
# Chaining lookup builder
define [lookup-single name f t] : begin
local subtable {.}
foreach [j : range 0 f.length] : set subtable.(f.(j)) t.(j)
set lookups.(name) {.type 'gsub_single' .subtables {subtable}}
define [getsublookup left right] : piecewise
[not right] null
([typeof right] === "string") right
(right <@ Function) : getsublookup left [right left]
true : begin
local found null
foreach [{name lookup} : pairs-of lookups] : match lookup
{.type "gsub_single" .subtables {st}} : begin
local check true
foreach [j : range 0 left.length] : if (st.(left.(j)) !== right.(j)) : set check false
if check : set found name
if found : return found
local name "_lookup_\([Object.keys lookups].length)"
lookup-single name left right
return name
define [chain-rule] : begin
local terms : [{}.slice.call arguments 0].map (x -> [if x.left x (x ~> null)])
local subtable {.match {} .apply {} .inputBegins 0 .inputEnds 0}
local foundi false
local founde false
foreach [j : range 0 terms.length] : if (!foundi && terms.(j).right) : begin
set subtable.inputBegins j
set foundi true
foreach [j : range (terms.length - 1) downtill 0] : if (!founde && terms.(j).right) : begin
set subtable.inputEnds (j + 1)
set founde true
foreach [j : range 0 terms.length] : begin
local term terms.(j)
subtable.match.push term.left
local lutn : getsublookup term.left term.right
if lutn : subtable.apply.push {.at j .lookup lutn}
return subtable
# hwid-fwid, lnum-onum
define [MakePairFrature tag1 tag2] : begin
local mapTag2 {.}
local mapTag1 {.}
define reHidden : regex "^\\."
define reTag1 : new RegExp ("\\." + tag1 + "$")
foreach [glyph : items-of glyphList] : begin
if ([reTag1.test glyph.name] && ![reHidden.test glyph.name]) : do
local gnTag2 : glyph.name.replace reTag1 ('.' + tag2)
if (glyphs.(gnTag2)) : begin
set mapTag2.(glyph.name) gnTag2
set mapTag1.(gnTag2) glyph.name
set lookups.(tag2) {.type 'gsub_single' .subtables {mapTag2}}
set lookups.(tag1) {.type 'gsub_single' .subtables {mapTag1}}
set features.(tag2) {tag2}
set features.(tag1) {tag1}
commonList.push tag2 tag1
MakePairFrature 'lnum' 'onum'
if (!para.forceMonospace || para.spacing > 0) : MakePairFrature 'hwid' 'fwid'
# CCMP
buildCCMP chain-rule markGlyphs commonList features lookups
# Ligation
if para.enableLigation : do
define plm : objectAssign {.} para.defaultBuildup
if (para.ligation.caltBuildup && para.ligation.caltBuildup.length) : begin
set plm.calt para.ligation.caltBuildup
buildLigations chain-rule lookupOrder commonList features lookups plm glyphs
# CVxx/SSxx
if para.enableCvSs : begin
# cvxx
foreach [glyph : items-of glyphList] : if glyph.featureSelector : begin
local fs glyph.featureSelector
foreach [feature : items-of : Object.keys fs] : begin
if [not lookups.(feature)] : begin
set features.(feature) {feature}
set lookups.(feature) {.type 'gsub_single' .subtables{{.}}}
commonList.push feature
set lookups.(feature).subtables.0.(glyph.name) fs.(feature)
# ssxx
foreach [{name composition} : pairs-of para.variants] : begin
if (name.length === 4 && composition.__isComposite && [name.slice 0 2] === 'ss') : begin
commonList.push name
local tags {.}
foreach [{ch tag} : pairs-of composition.__cvmap] : set tags.(tag) true
set features.(name) : [Object.keys tags].filter (tag => tags.(tag))
# locl, SRB
local srbSubtable null
if para.isItalic
: then : set srbSubtable : object
cyrbe 'cyrbe.SRB'
cyrghe 'cyrghe.SRB'
cyrde 'cyrde.SRB'
cyrpe 'cyrpe.SRB'
cyrte 'cyrte.SRB'
: else : set srbSubtable : object
cyrbe 'cyrbe.SRB'
set lookups.locl_srb {.type 'gsub_single' .subtables {srbSubtable}}
set features.locl_srb {'locl_srb'}
# locl, BGR
local bgrSubtable : object
cyrve 'cyrve.BGR'
cyrghe 'cyrghe.italic'
cyrde 'g'
cyrzhe 'cyrzhe.BGR'
cyrze 'cyrze.BGR'
cyri 'u'
cyribreve 'ubreve'
cyrka 'k'
cyrEl 'Lambda'
cyrel 'turnv'
cyrpe 'n'
cyrte 'm'
cyrsha 'cyrsha.italic'
cyrshcha 'cyrshcha.italic'
cyryu 'cyryu.BGR'
cyrtse 'cyrtse.italic'
set lookups.locl_bgr {.type 'gsub_single' .subtables {bgrSubtable}}
set features.locl_bgr {'locl_bgr'}
set languages.'cyrl_SRB ' {.features [{'locl_srb'}.concat commonList]}
set languages.'cyrl_MKD ' {.features [{'locl_srb'}.concat commonList]}
set languages.'cyrl_BGR ' {.features [{'locl_bgr'}.concat commonList]}
return {.languages languages .features features .lookups lookups .lookupOrder [topsort lookupOrder]}
# GPOS
define [buildGPOS para glyphs glyphList markGlyphs] : begin
# mark and mkmk
define [createBaseInfo g th px py] : begin
local res {.}
local pushed false
foreach key [items-of : Object.keys g.anchors] : if (!th || th.(key)) : begin
set res.(key) : object
.x g.anchors.(key).(px || 'x')
.y g.anchors.(key).(py || 'y')
set pushed true
return : if pushed res nothing
define [createMTLookup lookupType anchorClasses] : begin
local subtable {.marks {.} .bases {.}}
local th {.}
foreach [ac : items-of anchorClasses] : set th.(ac) true
foreach glyph [items-of glyphList] : if glyph.anchors : begin
local anchorKeys : Object.keys glyph.anchors
local hasAnchor false
foreach [key : items-of anchorKeys] : if th.(key) : set hasAnchor true
if hasAnchor : begin
local isMarkGlyph false
local markKey nothing
foreach key [items-of anchorKeys] : if (glyph.anchors.(key).type == 'mark') : begin
set isMarkGlyph true
set markKey key
if isMarkGlyph
: then : begin
set subtable.marks.(glyph.name) : object
class markKey
x glyph.anchors.(markKey).x
y glyph.anchors.(markKey).y
if (lookupType == 'gpos_mark_to_mark'): begin
local r : createBaseInfo glyph th 'mbx' 'mby'
if r : set subtable.bases.(glyph.name) r
: else : if (lookupType == 'gpos_mark_to_base') : begin
local r : createBaseInfo glyph th 'x' 'y'
if r : set subtable.bases.(glyph.name) r
return {.type lookupType .subtables {subtable}}
local lookupSet {.}
local markLookupNames {}
local mkmkLookupNames {}
foreach [marktag : items-of {'above' 'below' 'overlay' 'slash' 'topright' 'bottomright' 'trailing' 'lf'}] : begin
set lookupSet.('lookup_mark_' + marktag) : createMTLookup 'gpos_mark_to_base' {marktag}
set lookupSet.('lookup_mkmk_' + marktag) : createMTLookup 'gpos_mark_to_mark' {marktag}
markLookupNames.push ('lookup_mark_' + marktag)
mkmkLookupNames.push ('lookup_mkmk_' + marktag)
return : object
languages
.DFLT_DFLT {.features {'mark0', 'mkmk0'}}
.latn_DFLT {.features {'mark0', 'mkmk0'}}
.grek_DFLT {.features {'mark0', 'mkmk0'}}
.cyrl_DFLT {.features {'mark0', 'mkmk0'}}
features
.mark0 markLookupNames
.mkmk0 mkmkLookupNames
lookups lookupSet
# GDEF
define [buildGDEF para glyphs glyphList markGlyphs] : begin
local GDEF {.glyphClassDef {.}}
foreach glyph [items-of glyphList] : begin
set GDEF.glyphClassDef.(glyph.name) : if [[regex '_'].test glyph.name] GDEF_LIGATURE GDEF_SIMPLE
if (glyph.anchors && [begin [local anchorKeys : Object.keys glyph.anchors] anchorKeys.length]) : begin
foreach key [items-of anchorKeys] : if (glyph.anchors.(key).type == 'mark') : begin
if [not markGlyphs.(key)] : set markGlyphs.(key) {}
markGlyphs.(key).push glyph.name
markGlyphs.all.push glyph.name
set GDEF.glyphClassDef.(glyph.name) GDEF_MARK
return GDEF
export : define [apply para glyphs glyphList unicodeGlyphs] : begin
local markGlyphs {.all {} }
local GPOS : buildGPOS para glyphs glyphList markGlyphs
local GDEF : buildGDEF para glyphs glyphList markGlyphs
local GSUB : buildGSUB para glyphs glyphList markGlyphs
# Build compatibility ligatures
if (para.spacing > 0 && para.compLig) : begin
BuildCompatLigatures glyphs glyphList unicodeGlyphs GSUB GDEF para.compLig
return [object GSUB GPOS GDEF]

View file

@ -1,5 +1,5 @@
import '../../support/glyph' as Glyph import '../support/glyph' as Glyph
import '../../support/transform' as Transform import '../support/transform' as Transform
define GDEF_SIMPLE 1 define GDEF_SIMPLE 1
define GDEF_LIGATURE 2 define GDEF_LIGATURE 2

54
otl/gpos-mark-mkmk.ptl Normal file
View file

@ -0,0 +1,54 @@
import [add-common-feature add-feature add-lookup] from "./table-util"
define [createBaseInfo g th px py] : begin
local res {.}
local pushed false
foreach key [items-of : Object.keys g.anchors] : if (!th || th.(key)) : begin
set res.(key) : object
.x g.anchors.(key).(px || 'x')
.y g.anchors.(key).(py || 'y')
set pushed true
return : if pushed res nothing
define [createMTLookup glyphList lookupType anchorClasses] : begin
local subtable {.marks {.} .bases {.}}
local th {.}
foreach [ac : items-of anchorClasses] : set th.(ac) true
foreach glyph [items-of glyphList] : if glyph.anchors : begin
local anchorKeys : Object.keys glyph.anchors
local hasAnchor false
foreach [key : items-of anchorKeys] : if th.(key) : set hasAnchor true
if hasAnchor : begin
local isMarkGlyph false
local markKey nothing
foreach key [items-of anchorKeys] : if (glyph.anchors.(key).type == 'mark') : begin
set isMarkGlyph true
set markKey key
if isMarkGlyph
: then : begin
set subtable.marks.(glyph.name) : object
class markKey
x glyph.anchors.(markKey).x
y glyph.anchors.(markKey).y
if (lookupType == 'gpos_mark_to_mark'): begin
local r : createBaseInfo glyph th 'mbx' 'mby'
if r : set subtable.bases.(glyph.name) r
: else : if (lookupType == 'gpos_mark_to_base') : begin
local r : createBaseInfo glyph th 'x' 'y'
if r : set subtable.bases.(glyph.name) r
return {.type lookupType .subtables {subtable}}
export : define [buildMarkMkmk sink glyphList] : begin
define mark : add-feature sink 'mark'
define mkmk : add-feature sink 'mkmk'
add-common-feature sink mark
add-common-feature sink mkmk
local markLookupNames {}
local mkmkLookupNames {}
foreach [marktag : items-of {'above' 'below' 'overlay' 'slash' 'topright' 'bottomright' 'trailing' 'lf'}] : begin
define lookupNameMark : 'lookup_mark_' + marktag
define lookupNameMkmk : 'lookup_mkmk_' + marktag
mark.lookups.push : add-lookup sink : createMTLookup glyphList 'gpos_mark_to_base' {marktag}
mkmk.lookups.push : add-lookup sink : createMTLookup glyphList 'gpos_mark_to_mark' {marktag}

View file

@ -1,11 +1,14 @@
import [add-common-feature add-feature add-lookup ChainRuleBuilder] from "./table-util"
define-operator "~>" 880 'right' : syntax-rules define-operator "~>" 880 'right' : syntax-rules
`(@l ~> @r) `{.left @l .right @r} `(@l ~> @r) `{.left @l .right @r}
export all : define [buildCCMP chain-rule markGlyphs commonList features lookups] : begin export : define [buildCCMP sink markGlyphs] : begin
commonList.push 'ccmp' define ccmp : add-feature sink 'ccmp'
set features.ccmp {'ccmp1' 'ccmp2'} define chain-rule : ChainRuleBuilder sink
let [groupA {'A' 'a' 'u' 'cyrA' 'cyra'}] : set lookups.ccmp1 define groupA {'A' 'a' 'u' 'cyrA' 'cyra'}
define lookupCcmp1 : add-lookup sink : object
.type 'gsub_chaining' .type 'gsub_chaining'
.subtables : list .subtables : list
chain-rule chain-rule
@ -20,7 +23,7 @@ export all : define [buildCCMP chain-rule markGlyphs commonList features lookups
chain-rule groupA markGlyphs.all markGlyphs.all ({'ogonekBelow'} ~> {'ogonekTR'}) chain-rule groupA markGlyphs.all markGlyphs.all ({'ogonekBelow'} ~> {'ogonekTR'})
chain-rule groupA markGlyphs.all markGlyphs.all markGlyphs.all ({'ogonekBelow'} ~> {'ogonekTR'}) chain-rule groupA markGlyphs.all markGlyphs.all markGlyphs.all ({'ogonekBelow'} ~> {'ogonekTR'})
set lookups.ccmp2 define lookupCcmp2 : add-lookup sink : object
.type 'gsub_ligature' .type 'gsub_ligature'
.subtables : list : object .subtables : list : object
psilivaria {'commaAbove' 'graveAbove'} psilivaria {'commaAbove' 'graveAbove'}
@ -29,3 +32,6 @@ export all : define [buildCCMP chain-rule markGlyphs commonList features lookups
dasiavaria {'revCommaAbove' 'graveAbove'} dasiavaria {'revCommaAbove' 'graveAbove'}
dasiaoxia {'revCommaAbove' 'acuteAbove'} dasiaoxia {'revCommaAbove' 'acuteAbove'}
dasiaperispomeni {'revCommaAbove' 'perispomeniAbove'} dasiaperispomeni {'revCommaAbove' 'perispomeniAbove'}
ccmp.lookups.push lookupCcmp1 lookupCcmp2
add-common-feature sink ccmp

28
otl/gsub-cv-ss.ptl Normal file
View file

@ -0,0 +1,28 @@
import [add-common-feature pick-feature add-feature-lookup pick-lookup] from "./table-util"
define [FeatureName tag] : tag + '_cvss'
define [LookupName tag] : 'lookup_cvss_' + tag
export : define [buildCVSS sink para glyphList] : begin
if [not para.enableCvSs] : return nothing
# cvxx
foreach [glyph : items-of glyphList] : if glyph.featureSelector : begin
local fs glyph.featureSelector
foreach [{tag to} : pairs-of fs] : begin
local feature : pick-feature sink [FeatureName tag]
add-common-feature sink feature
local lookup : pick-lookup sink [LookupName tag] {.type 'gsub_single' .subtables{{.}}}
add-feature-lookup feature [LookupName tag]
set lookup.subtables.0.(glyph.name) to
# ssxx
foreach [{name composition} : pairs-of para.variants] : begin
if (name.length === 4 && composition.__isComposite && [name.slice 0 2] === 'ss') : begin
local feature : pick-feature sink [FeatureName name]
add-common-feature sink feature
foreach [{ch tag} : pairs-of composition.__cvmap]
add-feature-lookup feature [LookupName tag]

View file

@ -1,3 +1,5 @@
import [add-common-feature add-feature ChainRuleBuilder] from "./table-util"
define-operator "~>" 880 'right' : syntax-rules define-operator "~>" 880 'right' : syntax-rules
`(@l ~> @r) `{.left @l .right @r} `(@l ~> @r) `{.left @l .right @r}
@ -7,7 +9,9 @@ define [just s] : lambda [t] : t.map : lambda [x] s
define preserved null define preserved null
define advance : lambda [t] : t.map : lambda [x] x define advance : lambda [t] : t.map : lambda [x] x
export : define [buildLigations chain-rule lookupOrder commonList features lookups plm glyphs] : foreach [ ligationFeatureName : items-of : Object.keys plm] : do export : define [buildLigations sink plm glyphs] : foreach [ {featureName mappedFeature} : pairs-of plm] : do
define chain-rule : ChainRuleBuilder sink
define arrowStick {'hyphen' 'equal'} define arrowStick {'hyphen' 'equal'}
define [stick style] : {'hyphen' 'equal'} ~> [lsx style] define [stick style] : {'hyphen' 'equal'} ~> [lsx style]
@ -59,8 +63,7 @@ export : define [buildLigations chain-rule lookupOrder commonList features looku
g.push v g.push v
* g * g
local mappedFeature : plm.(ligationFeatureName) || {} local ligationLookupName : 'lig_' + featureName + '-' + mappedFeature
local ligationLookupName : 'lig_' + ligationFeatureName + '-' + mappedFeature
define [hasLG ln] : [mappedFeature.indexOf ln] >= 0 define [hasLG ln] : [mappedFeature.indexOf ln] >= 0
@ -76,26 +79,23 @@ export : define [buildLigations chain-rule lookupOrder commonList features looku
(f <@ Function) : f left (f <@ Function) : f left
true : return f true : return f
commonList.push ligationFeatureName define feature : add-common-feature sink : add-feature sink featureName
local featLookups {}
local lastLookupName null local lastLookupName null
local [dedup ln0 obj] : begin local [dedup ln0 obj] : begin
local h : JSON.stringify obj local h : JSON.stringify obj
foreach [{name lookup} : pairs-of lookups] : begin foreach [{name lookup} : pairs-of sink.lookups] : begin
local h1 : JSON.stringify lookup local h1 : JSON.stringify lookup
if (h == h1) : return name if (h == h1) : return name
return ln0 return ln0
local [includeLookup obj] : begin local [includeLookup obj] : begin
local ln : dedup (ligationLookupName + featLookups.length) obj local ln : dedup (ligationLookupName + feature.lookups.length) obj
if [not lookups.(ln)] : set lookups.(ln) obj if [not sink.lookups.(ln)] : set sink.lookups.(ln) obj
featLookups.push ln feature.lookups.push ln
if lastLookupName : lookupOrder.push {lastLookupName ln} if lastLookupName : sink.lookupDep.push {lastLookupName ln}
set lastLookupName ln set lastLookupName ln
set features.(ligationFeatureName) featLookups
do "Operator centering" : if [hasLG "center-ops"] : begin do "Operator centering" : if [hasLG "center-ops"] : begin
define centerizeGroups : { asterisk_center caret_center tilde_center colon_center [if [hasLG 'dotoper'] period_center nothing]}.filter (x => x) define centerizeGroups : { asterisk_center caret_center tilde_center colon_center [if [hasLG 'dotoper'] period_center nothing]}.filter (x => x)

45
otl/gsub-locl.ptl Normal file
View file

@ -0,0 +1,45 @@
import [copy-language add-feature add-lookup] from "./table-util"
export : define [buildLOCL sink para] : begin
define cyrlSRB : copy-language sink 'cyrl_SRB ' 'cyrl_DFLT'
define cyrlMKD : copy-language sink 'cyrl_MKD ' 'cyrl_DFLT'
define cyrlBGR : copy-language sink 'cyrl_BGR ' 'cyrl_DFLT'
# SRB
define loclSRB : add-feature sink 'locl'
cyrlSRB.features.unshift loclSRB.name
cyrlMKD.features.unshift loclSRB.name
loclSRB.lookups.push : add-lookup sink : object
type 'gsub_single'
subtables : list : if para.isItalic
object
cyrbe 'cyrbe.SRB'
cyrghe 'cyrghe.SRB'
cyrde 'cyrde.SRB'
cyrpe 'cyrpe.SRB'
cyrte 'cyrte.SRB'
object
cyrbe 'cyrbe.SRB'
# BGR
define loclBGR : add-feature sink 'locl'
cyrlBGR.features.unshift loclBGR.name
loclBGR.lookups.push : add-lookup sink : object
type 'gsub_single'
subtables : list : object
cyrve 'cyrve.BGR'
cyrghe 'cyrghe.italic'
cyrde 'g'
cyrzhe 'cyrzhe.BGR'
cyrze 'cyrze.BGR'
cyri 'u'
cyribreve 'ubreve'
cyrka 'k'
cyrEl 'Lambda'
cyrel 'turnv'
cyrpe 'n'
cyrte 'm'
cyrsha 'cyrsha.italic'
cyrshcha 'cyrshcha.italic'
cyryu 'cyryu.BGR'
cyrtse 'cyrtse.italic'

24
otl/gsub-pairing.ptl Normal file
View file

@ -0,0 +1,24 @@
import [add-common-feature add-feature add-lookup] from "./table-util"
export : define [buildPairFeature sink tag1 tag2 glyphs glyphList] : begin
local mapTag2 {.}
local mapTag1 {.}
define reHidden : regex "^\\."
define reTag1 : new RegExp ("\\." + tag1 + "$")
foreach [glyph : items-of glyphList] : begin
if ([reTag1.test glyph.name] && ![reHidden.test glyph.name]) : do
local gnTag2 : glyph.name.replace reTag1 ('.' + tag2)
if (glyphs.(gnTag2)) : begin
set mapTag2.(glyph.name) gnTag2
set mapTag1.(gnTag2) glyph.name
define lookup1 : add-lookup sink {.type 'gsub_single' .subtables {mapTag1}}
define lookup2 : add-lookup sink {.type 'gsub_single' .subtables {mapTag2}}
define feature1 : add-feature sink tag1
feature1.lookups.push lookup1
define feature2 : add-feature sink tag2
feature2.lookups.push lookup2
add-common-feature sink feature1
add-common-feature sink feature2

74
otl/index.ptl Normal file
View file

@ -0,0 +1,74 @@
import 'topsort' as topsort
import 'object-assign' as objectAssign
import '../support/glyph' as Glyph
import '../support/transform' as Transform
import [CreateEmptyTable] from "./table-util"
import [buildLigations] from './gsub-ligation'
import [buildCCMP] from './gsub-ccmp'
import [buildPairFeature] from './gsub-pairing'
import [buildCVSS] from './gsub-cv-ss'
import [buildLOCL] from './gsub-locl'
import [buildMarkMkmk] from "./gpos-mark-mkmk"
import [BuildCompatLigatures] from './compat-ligature'
define GDEF_SIMPLE 1
define GDEF_LIGATURE 2
define GDEF_MARK 3
# GSUB
define [buildGSUB para glyphs glyphList markGlyphs] : begin
define gsub : CreateEmptyTable
set gsub.lookupDep {}
buildPairFeature gsub 'lnum' 'onum' glyphs glyphList
if (!para.forceMonospace || para.spacing > 0) : begin
buildPairFeature gsub 'hwid' 'fwid' glyphs glyphList
# CCMP
buildCCMP gsub markGlyphs
# Ligation
if para.enableLigation : do
define plm : objectAssign {.} para.defaultBuildup
if (para.ligation.caltBuildup && para.ligation.caltBuildup.length) : begin
set plm.calt para.ligation.caltBuildup
buildLigations gsub plm glyphs
buildCVSS gsub para glyphList
buildLOCL gsub para
set gsub.lookupOrder : topsort gsub.lookupDep
return gsub
# GPOS
define [buildGPOS para glyphs glyphList markGlyphs] : begin
define gpos : CreateEmptyTable
buildMarkMkmk gpos glyphList
return gpos
# GDEF
define [buildGDEF para glyphs glyphList markGlyphs] : begin
local GDEF {.glyphClassDef {.}}
foreach glyph [items-of glyphList] : begin
set GDEF.glyphClassDef.(glyph.name) : if [[regex '_'].test glyph.name] GDEF_LIGATURE GDEF_SIMPLE
if (glyph.anchors && [begin [local anchorKeys : Object.keys glyph.anchors] anchorKeys.length]) : begin
foreach key [items-of anchorKeys] : if (glyph.anchors.(key).type == 'mark') : begin
if [not markGlyphs.(key)] : set markGlyphs.(key) {}
markGlyphs.(key).push glyph.name
markGlyphs.all.push glyph.name
set GDEF.glyphClassDef.(glyph.name) GDEF_MARK
return GDEF
export : define [buildOtl para glyphs glyphList unicodeGlyphs] : begin
local markGlyphs {.all {} }
local GPOS : buildGPOS para glyphs glyphList markGlyphs
local GDEF : buildGDEF para glyphs glyphList markGlyphs
local GSUB : buildGSUB para glyphs glyphList markGlyphs
# Build compatibility ligatures
if (para.spacing > 0 && para.compLig) : begin
BuildCompatLigatures glyphs glyphList unicodeGlyphs GSUB GDEF para.compLig
return [object GSUB GPOS GDEF]

103
otl/table-util.ptl Normal file
View file

@ -0,0 +1,103 @@
export : define [CreateEmptyTable] {.languages {.} .features {.} .lookups {.}}
export : define [pick-language sink tag] : begin
if sink.languages.(tag) : return sink.languages.(tag)
define lang {.features {}}
set sink.languages.(tag) lang
return lang
export : define [copy-language sink tag tagFrom] : begin
define langFrom : pick-language sink tagFrom
define langTo : pick-language sink tag
foreach [feat : items-of langFrom.features] : langTo.features.push feat
return langTo
export : define [add-lang-feature lang fea] : begin
define index : lang.features.indexOf fea.name
if (index < 0) : lang.features.push fea.name
export : define [add-feature sink tag] : begin
define lookupArray {}
local n 0
while true : begin
if [not sink.features.(tag + '_' + n)] : begin
set sink.features.(tag + '_' + n) lookupArray
return {.name (tag + '_' + n) .lookups lookupArray}
set n : n + 1
export : define [pick-feature sink name] : begin
if sink.features.(name) : return { .name name .lookups sink.features.(name) }
define featObj { .name name .lookups {} }
set sink.features.(name) featObj.lookups
return featObj
export : define [add-feature-lookup fea lookupName] : begin
define index : fea.lookups.indexOf lookupName
if (index < 0) : fea.lookups.push lookupName
export : define [add-lookup sink data] : begin
local n 0
while true : begin
if [not sink.lookups.('_lut_' + n)] : begin
set sink.lookups.('_lut_' + n) data
return ('_lut_' + n)
set n : n + 1
export : define [pick-lookup sink name fallback] : begin
if sink.lookups.(name) : return sink.lookups.(name)
set sink.lookups.(name) fallback
return sink.lookups.(name)
export : define [add-common-feature sink fea] : begin
define dfltDflt : pick-language sink 'dflt_DFLT'
define latnDflt : pick-language sink 'latn_DFLT'
define grekDflt : pick-language sink 'grek_DFLT'
define cyrlDflt : pick-language sink 'cyrl_DFLT'
add-lang-feature dfltDflt fea
add-lang-feature latnDflt fea
add-lang-feature grekDflt fea
add-lang-feature cyrlDflt fea
return fea
export : define [ChainRuleBuilder sink] : begin
define [lookup-single f t] : begin
local subtable {.}
foreach [j : range 0 f.length] : set subtable.(f.(j)) t.(j)
return : add-lookup sink {.type 'gsub_single' .subtables {subtable}}
define [getsublookup left right] : piecewise
[not right] null
([typeof right] === "string") right
(right <@ Function) : getsublookup left [right left]
true : begin
local found null
foreach [{name lookup} : pairs-of sink.lookups] : match lookup
{.type "gsub_single" .subtables {st}} : begin
local check true
foreach [j : range 0 left.length] : if (st.(left.(j)) !== right.(j)) : set check false
if check : set found name
if found : return found
return : lookup-single left right
define [chain-rule] : begin
local terms : [{}.slice.call arguments 0].map (x -> [if x.left x {.left x .right null}])
local subtable {.match {} .apply {} .inputBegins 0 .inputEnds 0}
local foundi false
local founde false
foreach [j : range 0 terms.length] : if (!foundi && terms.(j).right) : begin
set subtable.inputBegins j
set foundi true
foreach [j : range (terms.length - 1) downtill 0] : if (!founde && terms.(j).right) : begin
set subtable.inputEnds (j + 1)
set founde true
foreach [j : range 0 terms.length] : begin
local term terms.(j)
subtable.match.push term.left
local lutn : getsublookup term.left term.right
if lutn : subtable.apply.push {.at j .lookup lutn}
return subtable
return chain-rule

View file

@ -67,11 +67,11 @@ function unlinkRef(g, dx, dy, glyf) {
return contours; return contours;
} }
function autoref(glyf, excludeUnicodes) { function autoref(gs, excludeUnicodeSet) {
suppressNaN(glyf); suppressNaN(gs);
for (let j = 0; j < glyf.length; j++) { for (let j = 0; j < gs.length; j++) {
const g = glyf[j]; const g = gs[j];
if (g.contours) { if (g.contours) {
for (let k = 0; k < g.contours.length; k++) { for (let k = 0; k < g.contours.length; k++) {
const contour = g.contours[k]; const contour = g.contours[k];
@ -81,44 +81,44 @@ function autoref(glyf, excludeUnicodes) {
} }
// Refl-referencify, forward. // Refl-referencify, forward.
for (let j = 0; j < glyf.length; j++) { for (let j = 0; j < gs.length; j++) {
if (!glyf[j].contours.length || (glyf[j].references && glyf[j].references.length)) continue; if (!gs[j].contours.length || (gs[j].references && gs[j].references.length)) continue;
for (let k = j + 1; k < glyf.length; k++) { for (let k = j + 1; k < gs.length; k++) {
if (glyf[j].contours.length === glyf[k].contours.length) { if (gs[j].contours.length === gs[k].contours.length) {
match(glyf[j], glyf[k], j); match(gs[j], gs[k], j);
} }
} }
} }
// referencify, backward // referencify, backward
for (let j = 0; j < glyf.length; j++) { for (let j = 0; j < gs.length; j++) {
if (glyf[j].cmpPriority < 0) continue; if (gs[j].cmpPriority < 0) continue;
if (!glyf[j].contours.length) continue; if (!gs[j].contours.length) continue;
if (glyf[j].references && glyf[j].references.length) continue; if (gs[j].references && gs[j].references.length) continue;
for (let k = glyf.length - 1; k >= 0; k--) { for (let k = gs.length - 1; k >= 0; k--) {
if (glyf[j].contours.length > glyf[k].contours.length) continue; if (gs[j].contours.length > gs[k].contours.length) continue;
if ( if (
glyf[j].contours.length === glyf[k].contours.length && gs[j].contours.length === gs[k].contours.length &&
!(glyf[k].references && glyf[k].references.length) !(gs[k].references && gs[k].references.length)
) { ) {
continue; continue;
} }
while (match(glyf[j], glyf[k], j)) "pass"; while (match(gs[j], gs[k], j)) "pass";
} }
} }
// unlink composite // unlink composite
for (let j = 0; j < glyf.length; j++) { for (let j = 0; j < gs.length; j++) {
if (!glyf[j].references || glyf[j].references.length === 0) continue; if (!gs[j].references || gs[j].references.length === 0) continue;
if ( if (
!glyf[j].flatten && !gs[j].flatten &&
glyf[j].contours.length === 0 && gs[j].contours.length === 0 &&
!(glyf[j].unicode && excludeUnicodes.has(glyf[j].unicode[0])) !(gs[j].unicode && excludeUnicodeSet.has(gs[j].unicode[0]))
) { ) {
continue; continue;
} }
glyf[j].contours = unlinkRef(glyf[j], 0, 0, glyf); gs[j].contours = unlinkRef(gs[j], 0, 0, gs);
glyf[j].references = []; gs[j].references = [];
} }
} }

View file

@ -75,17 +75,18 @@ function byGlyphPriority(a, b) {
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0; return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
} }
module.exports = function(font, skew) { module.exports = function(gs, skew) {
const excludeUnicode = new Set(); const excludeUnicode = new Set();
excludeUnicode.add(0x80); excludeUnicode.add(0x80);
for (let c = 0x2500; c <= 0x259f; c++) excludeUnicode.add(c); for (let c = 0x2500; c <= 0x259f; c++) excludeUnicode.add(c);
// autoref // autoref
font.glyf = font.glyf.map((g, j) => ((g.glyphOrder = j), g)).sort(byGlyphPriority); gs = gs.map((g, j) => ((g.glyphOrder = j), g)).sort(byGlyphPriority);
autoRef(font.glyf, excludeUnicode); autoRef(gs, excludeUnicode);
// regulate // regulate
for (let g of font.glyf) regulateGlyph(g, skew); for (let g of gs) regulateGlyph(g, skew);
// reorder // reorder
font.glyf = font.glyf.sort((a, b) => a.glyphOrder - b.glyphOrder); return gs.sort((a, b) => a.glyphOrder - b.glyphOrder);
}; };

View file

@ -682,8 +682,9 @@ const UtilScriptFiles = computed("util-script-files", async target => {
const ScriptFiles = computed.group("script-files", async (target, ext) => { const ScriptFiles = computed.group("script-files", async (target, ext) => {
const [gen, meta, glyphs, support] = await target.need( const [gen, meta, glyphs, support] = await target.need(
ScriptsUnder(ext, `gen`), ScriptsUnder(ext, `gen`),
ScriptsUnder(ext, `meta`),
ScriptsUnder(ext, `glyphs`), ScriptsUnder(ext, `glyphs`),
ScriptsUnder(ext, `meta`),
ScriptsUnder(ext, `otl`),
ScriptsUnder(ext, `support`) ScriptsUnder(ext, `support`)
); );
return [...gen, ...meta, ...glyphs, ...support]; return [...gen, ...meta, ...glyphs, ...support];
@ -693,7 +694,7 @@ const JavaScriptFromPtl = computed("scripts-js-from-ptl", async target => {
return ptl.map(x => x.replace(/\.ptl$/g, ".js")); return ptl.map(x => x.replace(/\.ptl$/g, ".js"));
}); });
const ScriptJS = file.glob(`{gen|glyphs|support|meta}/**/*.js`, async (target, path) => { const ScriptJS = file.glob(`{gen|glyphs|meta|otl|support}/**/*.js`, async (target, path) => {
const [jsFromPtl] = await target.need(JavaScriptFromPtl); const [jsFromPtl] = await target.need(JavaScriptFromPtl);
if (jsFromPtl.indexOf(path.full) >= 0) { if (jsFromPtl.indexOf(path.full) >= 0) {
const ptl = path.full.replace(/\.js$/g, ".ptl"); const ptl = path.full.replace(/\.js$/g, ".ptl");