
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
// see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM09/AppendixF.html
// and /System/Library/Frameworks/CoreText.framework/Versions/A/Headers/SFNTLayoutTypes.h on a Mac
const features = {
allTypographicFeatures: {
code: 0,
exclusive: false,
allTypeFeatures: 0
},
ligatures: {
code: 1,
exclusive: false,
requiredLigatures: 0,
commonLigatures: 2,
rareLigatures: 4,
// logos: 6
rebusPictures: 8,
diphthongLigatures: 10,
squaredLigatures: 12,
abbrevSquaredLigatures: 14,
symbolLigatures: 16,
contextualLigatures: 18,
historicalLigatures: 20
},
cursiveConnection: {
code: 2,
exclusive: true,
unconnected: 0,
partiallyConnected: 1,
cursive: 2
},
letterCase: {
code: 3,
exclusive: true
},
// upperAndLowerCase: 0 # deprecated
// allCaps: 1 # deprecated
// allLowerCase: 2 # deprecated
// smallCaps: 3 # deprecated
// initialCaps: 4 # deprecated
// initialCapsAndSmallCaps: 5 # deprecated
verticalSubstitution: {
code: 4,
exclusive: false,
substituteVerticalForms: 0
},
linguisticRearrangement: {
code: 5,
exclusive: false,
linguisticRearrangement: 0
},
numberSpacing: {
code: 6,
exclusive: true,
monospacedNumbers: 0,
proportionalNumbers: 1,
thirdWidthNumbers: 2,
quarterWidthNumbers: 3
},
smartSwash: {
code: 8,
exclusive: false,
wordInitialSwashes: 0,
wordFinalSwashes: 2,
// lineInitialSwashes: 4
// lineFinalSwashes: 6
nonFinalSwashes: 8
},
diacritics: {
code: 9,
exclusive: true,
showDiacritics: 0,
hideDiacritics: 1,
decomposeDiacritics: 2
},
verticalPosition: {
code: 10,
exclusive: true,
normalPosition: 0,
superiors: 1,
inferiors: 2,
ordinals: 3,
scientificInferiors: 4
},
fractions: {
code: 11,
exclusive: true,
noFractions: 0,
verticalFractions: 1,
diagonalFractions: 2
},
overlappingCharacters: {
code: 13,
exclusive: false,
preventOverlap: 0
},
typographicExtras: {
code: 14,
exclusive: false,
// hyphensToEmDash: 0
// hyphenToEnDash: 2
slashedZero: 4
},
// formInterrobang: 6
// smartQuotes: 8
// periodsToEllipsis: 10
mathematicalExtras: {
code: 15,
exclusive: false,
// hyphenToMinus: 0
// asteristoMultiply: 2
// slashToDivide: 4
// inequalityLigatures: 6
// exponents: 8
mathematicalGreek: 10
},
ornamentSets: {
code: 16,
exclusive: true,
noOrnaments: 0,
dingbats: 1,
piCharacters: 2,
fleurons: 3,
decorativeBorders: 4,
internationalSymbols: 5,
mathSymbols: 6
},
characterAlternatives: {
code: 17,
exclusive: true,
noAlternates: 0
},
// user defined options
designComplexity: {
code: 18,
exclusive: true,
designLevel1: 0,
designLevel2: 1,
designLevel3: 2,
designLevel4: 3,
designLevel5: 4
},
styleOptions: {
code: 19,
exclusive: true,
noStyleOptions: 0,
displayText: 1,
engravedText: 2,
illuminatedCaps: 3,
titlingCaps: 4,
tallCaps: 5
},
characterShape: {
code: 20,
exclusive: true,
traditionalCharacters: 0,
simplifiedCharacters: 1,
JIS1978Characters: 2,
JIS1983Characters: 3,
JIS1990Characters: 4,
traditionalAltOne: 5,
traditionalAltTwo: 6,
traditionalAltThree: 7,
traditionalAltFour: 8,
traditionalAltFive: 9,
expertCharacters: 10,
JIS2004Characters: 11,
hojoCharacters: 12,
NLCCharacters: 13,
traditionalNamesCharacters: 14
},
numberCase: {
code: 21,
exclusive: true,
lowerCaseNumbers: 0,
upperCaseNumbers: 1
},
textSpacing: {
code: 22,
exclusive: true,
proportionalText: 0,
monospacedText: 1,
halfWidthText: 2,
thirdWidthText: 3,
quarterWidthText: 4,
altProportionalText: 5,
altHalfWidthText: 6
},
transliteration: {
code: 23,
exclusive: true,
noTransliteration: 0
},
// hanjaToHangul: 1
// hiraganaToKatakana: 2
// katakanaToHiragana: 3
// kanaToRomanization: 4
// romanizationToHiragana: 5
// romanizationToKatakana: 6
// hanjaToHangulAltOne: 7
// hanjaToHangulAltTwo: 8
// hanjaToHangulAltThree: 9
annotation: {
code: 24,
exclusive: true,
noAnnotation: 0,
boxAnnotation: 1,
roundedBoxAnnotation: 2,
circleAnnotation: 3,
invertedCircleAnnotation: 4,
parenthesisAnnotation: 5,
periodAnnotation: 6,
romanNumeralAnnotation: 7,
diamondAnnotation: 8,
invertedBoxAnnotation: 9,
invertedRoundedBoxAnnotation: 10
},
kanaSpacing: {
code: 25,
exclusive: true,
fullWidthKana: 0,
proportionalKana: 1
},
ideographicSpacing: {
code: 26,
exclusive: true,
fullWidthIdeographs: 0,
proportionalIdeographs: 1,
halfWidthIdeographs: 2
},
unicodeDecomposition: {
code: 27,
exclusive: false,
canonicalComposition: 0,
compatibilityComposition: 2,
transcodingComposition: 4
},
rubyKana: {
code: 28,
exclusive: false,
// noRubyKana: 0 # deprecated - use rubyKanaOff instead
// rubyKana: 1 # deprecated - use rubyKanaOn instead
rubyKana: 2
},
CJKSymbolAlternatives: {
code: 29,
exclusive: true,
noCJKSymbolAlternatives: 0,
CJKSymbolAltOne: 1,
CJKSymbolAltTwo: 2,
CJKSymbolAltThree: 3,
CJKSymbolAltFour: 4,
CJKSymbolAltFive: 5
},
ideographicAlternatives: {
code: 30,
exclusive: true,
noIdeographicAlternatives: 0,
ideographicAltOne: 1,
ideographicAltTwo: 2,
ideographicAltThree: 3,
ideographicAltFour: 4,
ideographicAltFive: 5
},
CJKVerticalRomanPlacement: {
code: 31,
exclusive: true,
CJKVerticalRomanCentered: 0,
CJKVerticalRomanHBaseline: 1
},
italicCJKRoman: {
code: 32,
exclusive: false,
// noCJKItalicRoman: 0 # deprecated - use CJKItalicRomanOff instead
// CJKItalicRoman: 1 # deprecated - use CJKItalicRomanOn instead
CJKItalicRoman: 2
},
caseSensitiveLayout: {
code: 33,
exclusive: false,
caseSensitiveLayout: 0,
caseSensitiveSpacing: 2
},
alternateKana: {
code: 34,
exclusive: false,
alternateHorizKana: 0,
alternateVertKana: 2
},
stylisticAlternatives: {
code: 35,
exclusive: false,
noStylisticAlternates: 0,
stylisticAltOne: 2,
stylisticAltTwo: 4,
stylisticAltThree: 6,
stylisticAltFour: 8,
stylisticAltFive: 10,
stylisticAltSix: 12,
stylisticAltSeven: 14,
stylisticAltEight: 16,
stylisticAltNine: 18,
stylisticAltTen: 20,
stylisticAltEleven: 22,
stylisticAltTwelve: 24,
stylisticAltThirteen: 26,
stylisticAltFourteen: 28,
stylisticAltFifteen: 30,
stylisticAltSixteen: 32,
stylisticAltSeventeen: 34,
stylisticAltEighteen: 36,
stylisticAltNineteen: 38,
stylisticAltTwenty: 40
},
contextualAlternates: {
code: 36,
exclusive: false,
contextualAlternates: 0,
swashAlternates: 2,
contextualSwashAlternates: 4
},
lowerCase: {
code: 37,
exclusive: true,
defaultLowerCase: 0,
lowerCaseSmallCaps: 1,
lowerCasePetiteCaps: 2
},
upperCase: {
code: 38,
exclusive: true,
defaultUpperCase: 0,
upperCaseSmallCaps: 1,
upperCasePetiteCaps: 2
},
languageTag: { // indices into ltag table
code: 39,
exclusive: true
},
CJKRomanSpacing: {
code: 103,
exclusive: true,
halfWidthCJKRoman: 0,
proportionalCJKRoman: 1,
defaultCJKRoman: 2,
fullWidthCJKRoman: 3
}
};
const feature = (name, selector) => [features[name].code, features[name][selector]];
const OTMapping = {
rlig: feature('ligatures', 'requiredLigatures'),
clig: feature('ligatures', 'contextualLigatures'),
dlig: feature('ligatures', 'rareLigatures'),
hlig: feature('ligatures', 'historicalLigatures'),
liga: feature('ligatures', 'commonLigatures'),
hist: feature('ligatures', 'historicalLigatures'), // ??
smcp: feature('lowerCase', 'lowerCaseSmallCaps'),
pcap: feature('lowerCase', 'lowerCasePetiteCaps'),
frac: feature('fractions', 'diagonalFractions'),
dnom: feature('fractions', 'diagonalFractions'), // ??
numr: feature('fractions', 'diagonalFractions'), // ??
afrc: feature('fractions', 'verticalFractions'),
// aalt
// abvf, abvm, abvs, akhn, blwf, blwm, blws, cfar, cjct, cpsp, falt, isol, jalt, ljmo, mset?
// ltra, ltrm, nukt, pref, pres, pstf, psts, rand, rkrf, rphf, rtla, rtlm, size, tjmo, tnum?
// unic, vatu, vhal, vjmo, vpal, vrt2
// dist -> trak table?
// kern, vkrn -> kern table
// lfbd + opbd + rtbd -> opbd table?
// mark, mkmk -> acnt table?
// locl -> languageTag + ltag table
case: feature('caseSensitiveLayout', 'caseSensitiveLayout'), // also caseSensitiveSpacing
ccmp: feature('unicodeDecomposition', 'canonicalComposition'), // compatibilityComposition?
cpct: feature('CJKVerticalRomanPlacement', 'CJKVerticalRomanCentered'), // guess..., probably not given below
valt: feature('CJKVerticalRomanPlacement', 'CJKVerticalRomanCentered'),
swsh: feature('contextualAlternates', 'swashAlternates'),
cswh: feature('contextualAlternates', 'contextualSwashAlternates'),
curs: feature('cursiveConnection', 'cursive'), // ??
c2pc: feature('upperCase', 'upperCasePetiteCaps'),
c2sc: feature('upperCase', 'upperCaseSmallCaps'),
init: feature('smartSwash', 'wordInitialSwashes'), // ??
fin2: feature('smartSwash', 'wordFinalSwashes'), // ??
medi: feature('smartSwash', 'nonFinalSwashes'), // ??
med2: feature('smartSwash', 'nonFinalSwashes'), // ??
fin3: feature('smartSwash', 'wordFinalSwashes'), // ??
fina: feature('smartSwash', 'wordFinalSwashes'), // ??
pkna: feature('kanaSpacing', 'proportionalKana'),
half: feature('textSpacing', 'halfWidthText'), // also HalfWidthCJKRoman, HalfWidthIdeographs?
halt: feature('textSpacing', 'altHalfWidthText'),
hkna: feature('alternateKana', 'alternateHorizKana'),
vkna: feature('alternateKana', 'alternateVertKana'),
// hngl: feature 'transliteration', 'hanjaToHangulSelector' # deprecated
ital: feature('italicCJKRoman', 'CJKItalicRoman'),
lnum: feature('numberCase', 'upperCaseNumbers'),
onum: feature('numberCase', 'lowerCaseNumbers'),
mgrk: feature('mathematicalExtras', 'mathematicalGreek'),
// nalt: not enough info. what type of annotation?
// ornm: ditto, which ornament style?
calt: feature('contextualAlternates', 'contextualAlternates'), // or more?
vrt2: feature('verticalSubstitution', 'substituteVerticalForms'), // oh... below?
vert: feature('verticalSubstitution', 'substituteVerticalForms'),
tnum: feature('numberSpacing', 'monospacedNumbers'),
pnum: feature('numberSpacing', 'proportionalNumbers'),
sups: feature('verticalPosition', 'superiors'),
subs: feature('verticalPosition', 'inferiors'),
ordn: feature('verticalPosition', 'ordinals'),
pwid: feature('textSpacing', 'proportionalText'),
hwid: feature('textSpacing', 'halfWidthText'),
qwid: feature('textSpacing', 'quarterWidthText'), // also QuarterWidthNumbers?
twid: feature('textSpacing', 'thirdWidthText'), // also ThirdWidthNumbers?
fwid: feature('textSpacing', 'proportionalText'), //??
palt: feature('textSpacing', 'altProportionalText'),
trad: feature('characterShape', 'traditionalCharacters'),
smpl: feature('characterShape', 'simplifiedCharacters'),
jp78: feature('characterShape', 'JIS1978Characters'),
jp83: feature('characterShape', 'JIS1983Characters'),
jp90: feature('characterShape', 'JIS1990Characters'),
jp04: feature('characterShape', 'JIS2004Characters'),
expt: feature('characterShape', 'expertCharacters'),
hojo: feature('characterShape', 'hojoCharacters'),
nlck: feature('characterShape', 'NLCCharacters'),
tnam: feature('characterShape', 'traditionalNamesCharacters'),
ruby: feature('rubyKana', 'rubyKana'),
titl: feature('styleOptions', 'titlingCaps'),
zero: feature('typographicExtras', 'slashedZero'),
ss01: feature('stylisticAlternatives', 'stylisticAltOne'),
ss02: feature('stylisticAlternatives', 'stylisticAltTwo'),
ss03: feature('stylisticAlternatives', 'stylisticAltThree'),
ss04: feature('stylisticAlternatives', 'stylisticAltFour'),
ss05: feature('stylisticAlternatives', 'stylisticAltFive'),
ss06: feature('stylisticAlternatives', 'stylisticAltSix'),
ss07: feature('stylisticAlternatives', 'stylisticAltSeven'),
ss08: feature('stylisticAlternatives', 'stylisticAltEight'),
ss09: feature('stylisticAlternatives', 'stylisticAltNine'),
ss10: feature('stylisticAlternatives', 'stylisticAltTen'),
ss11: feature('stylisticAlternatives', 'stylisticAltEleven'),
ss12: feature('stylisticAlternatives', 'stylisticAltTwelve'),
ss13: feature('stylisticAlternatives', 'stylisticAltThirteen'),
ss14: feature('stylisticAlternatives', 'stylisticAltFourteen'),
ss15: feature('stylisticAlternatives', 'stylisticAltFifteen'),
ss16: feature('stylisticAlternatives', 'stylisticAltSixteen'),
ss17: feature('stylisticAlternatives', 'stylisticAltSeventeen'),
ss18: feature('stylisticAlternatives', 'stylisticAltEighteen'),
ss19: feature('stylisticAlternatives', 'stylisticAltNineteen'),
ss20: feature('stylisticAlternatives', 'stylisticAltTwenty')
};
// salt: feature 'stylisticAlternatives', 'stylisticAltOne' # hmm, which one to choose
// Add cv01-cv99 features
for (let i = 1; i <= 99; i++) {
OTMapping[`cv${`00${i}`.slice(-2)}`] = [features.characterAlternatives.code, i];
}
// create inverse mapping
let AATMapping = {};
for (let ot in OTMapping) {
let aat = OTMapping[ot];
if (AATMapping[aat[0]] == null) {
AATMapping[aat[0]] = {};
}
AATMapping[aat[0]][aat[1]] = ot;
}
// Maps an array of OpenType features to AAT features
// in the form of {featureType:{featureSetting:true}}
export function mapOTToAAT(features) {
let res = {};
for (let k in features) {
let r;
if (r = OTMapping[k]) {
if (res[r[0]] == null) {
res[r[0]] = {};
}
res[r[0]][r[1]] = features[k];
}
}
return res;
}
// Maps strings in a [featureType, featureSetting]
// to their equivalent number codes
function mapFeatureStrings(f) {
let [type, setting] = f;
if (isNaN(type)) {
var typeCode = features[type] && features[type].code;
} else {
var typeCode = type;
}
if (isNaN(setting)) {
var settingCode = features[type] && features[type][setting];
} else {
var settingCode = setting;
}
return [typeCode, settingCode];
}
// Maps AAT features to an array of OpenType features
// Supports both arrays in the form of [[featureType, featureSetting]]
// and objects in the form of {featureType:{featureSetting:true}}
// featureTypes and featureSettings can be either strings or number codes
export function mapAATToOT(features) {
let res = {};
if (Array.isArray(features)) {
for (let k = 0; k < features.length; k++) {
let r;
let f = mapFeatureStrings(features[k]);
if (r = AATMapping[f[0]] && AATMapping[f[0]][f[1]]) {
res[r] = true;
}
}
} else if (typeof features === 'object') {
for (let type in features) {
let feature = features[type];
for (let setting in feature) {
let r;
let f = mapFeatureStrings([type, setting]);
if (feature[setting] && (r = AATMapping[f[0]] && AATMapping[f[0]][f[1]])) {
res[r] = true;
}
}
}
}
return Object.keys(res);
}