summaryrefslogtreecommitdiff
path: root/tex/context/base/font-otf.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/font-otf.lua')
-rw-r--r--tex/context/base/font-otf.lua3416
1 files changed, 3416 insertions, 0 deletions
diff --git a/tex/context/base/font-otf.lua b/tex/context/base/font-otf.lua
new file mode 100644
index 000000000..c6a100566
--- /dev/null
+++ b/tex/context/base/font-otf.lua
@@ -0,0 +1,3416 @@
+if not modules then modules = { } end modules ['font-otf'] = {
+ version = 1.001,
+ comment = "companion to font-ini.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- otfdata zit in tfmdata / check
+
+--~ function string:split_at_space()
+--~ local t = { }
+--~ for s in self:gmatch("(%S+)") do
+--~ t[#t+1] = s
+--~ end
+--~ return t
+--~ end
+
+-- beware, the node related functions need to return head, current -- todo
+-- we may move marks to components so that parsing is faster
+
+-- using for i=1,#t do ... t[i] ... end is much faster than using ipairs
+-- copying some functions is faster than sharing code chunks esp here
+
+--[[ldx--
+<p>This module is sparesely documented because it is a moving target.
+The table format of the reader changes and we experiment a lot with
+different methods for supporting features.</p>
+
+<p>As with the <l n='afm'/> code, we may decide to store more information
+in the <l n='otf'/> table.</p>
+--ldx]]--
+
+fonts = fonts or { }
+fonts.otf = fonts.otf or { }
+fonts.otf.version = 1.56 -- incrementing this number one up will force a re-cache
+fonts.otf.tables = fonts.otf.tables or { }
+fonts.otf.meanings = fonts.otf.meanings or { }
+fonts.otf.enhance_data = false
+fonts.otf.syncspace = true
+fonts.otf.features = { }
+fonts.otf.features.aux = { }
+fonts.otf.features.data = { }
+fonts.otf.features.list = { } -- not (yet) used, oft fonts have gpos/gsub lists
+fonts.otf.features.default = { }
+fonts.otf.trace_features = false
+fonts.otf.trace_replacements = false
+fonts.otf.trace_contexts = false
+fonts.otf.trace_anchors = false
+fonts.otf.trace_ligatures = false
+fonts.otf.trace_kerns = false
+fonts.otf.notdef = false
+fonts.otf.cache = containers.define("fonts", "otf", fonts.otf.version, true)
+
+--[[ldx--
+<p>We start with a lot of tables and related functions.</p>
+--ldx]]--
+
+fonts.otf.tables.scripts = {
+ ['dflt'] = 'Default',
+
+ ['arab'] = 'Arabic',
+ ['armn'] = 'Armenian',
+ ['bali'] = 'Balinese',
+ ['beng'] = 'Bengali',
+ ['bopo'] = 'Bopomofo',
+ ['brai'] = 'Braille',
+ ['bugi'] = 'Buginese',
+ ['buhd'] = 'Buhid',
+ ['byzm'] = 'Byzantine Music',
+ ['cans'] = 'Canadian Syllabics',
+ ['cher'] = 'Cherokee',
+ ['copt'] = 'Coptic',
+ ['cprt'] = 'Cypriot Syllabary',
+ ['cyrl'] = 'Cyrillic',
+ ['deva'] = 'Devanagari',
+ ['dsrt'] = 'Deseret',
+ ['ethi'] = 'Ethiopic',
+ ['geor'] = 'Georgian',
+ ['glag'] = 'Glagolitic',
+ ['goth'] = 'Gothic',
+ ['grek'] = 'Greek',
+ ['gujr'] = 'Gujarati',
+ ['guru'] = 'Gurmukhi',
+ ['hang'] = 'Hangul',
+ ['hani'] = 'CJK Ideographic',
+ ['hano'] = 'Hanunoo',
+ ['hebr'] = 'Hebrew',
+ ['ital'] = 'Old Italic',
+ ['jamo'] = 'Hangul Jamo',
+ ['java'] = 'Javanese',
+ ['kana'] = 'Hiragana and Katakana',
+ ['khar'] = 'Kharosthi',
+ ['khmr'] = 'Khmer',
+ ['knda'] = 'Kannada',
+ ['lao' ] = 'Lao',
+ ['latn'] = 'Latin',
+ ['limb'] = 'Limbu',
+ ['linb'] = 'Linear B',
+ ['math'] = 'Mathematical Alphanumeric Symbols',
+ ['mlym'] = 'Malayalam',
+ ['mong'] = 'Mongolian',
+ ['musc'] = 'Musical Symbols',
+ ['mymr'] = 'Myanmar',
+ ['nko' ] = "N'ko",
+ ['ogam'] = 'Ogham',
+ ['orya'] = 'Oriya',
+ ['osma'] = 'Osmanya',
+ ['phag'] = 'Phags-pa',
+ ['phnx'] = 'Phoenician',
+ ['runr'] = 'Runic',
+ ['shaw'] = 'Shavian',
+ ['sinh'] = 'Sinhala',
+ ['sylo'] = 'Syloti Nagri',
+ ['syrc'] = 'Syriac',
+ ['tagb'] = 'Tagbanwa',
+ ['tale'] = 'Tai Le',
+ ['talu'] = 'Tai Lu',
+ ['taml'] = 'Tamil',
+ ['telu'] = 'Telugu',
+ ['tfng'] = 'Tifinagh',
+ ['tglg'] = 'Tagalog',
+ ['thaa'] = 'Thaana',
+ ['thai'] = 'Thai',
+ ['tibt'] = 'Tibetan',
+ ['ugar'] = 'Ugaritic Cuneiform',
+ ['xpeo'] = 'Old Persian Cuneiform',
+ ['xsux'] = 'Sumero-Akkadian Cuneiform',
+ ['yi' ] = 'Yi'
+}
+
+fonts.otf.tables.languages = {
+ ['dflt'] = 'Default',
+
+ ['aba'] = 'Abaza',
+ ['abk'] = 'Abkhazian',
+ ['ady'] = 'Adyghe',
+ ['afk'] = 'Afrikaans',
+ ['afr'] = 'Afar',
+ ['agw'] = 'Agaw',
+ ['als'] = 'Alsatian',
+ ['alt'] = 'Altai',
+ ['amh'] = 'Amharic',
+ ['ara'] = 'Arabic',
+ ['ari'] = 'Aari',
+ ['ark'] = 'Arakanese',
+ ['asm'] = 'Assamese',
+ ['ath'] = 'Athapaskan',
+ ['avr'] = 'Avar',
+ ['awa'] = 'Awadhi',
+ ['aym'] = 'Aymara',
+ ['aze'] = 'Azeri',
+ ['bad'] = 'Badaga',
+ ['bag'] = 'Baghelkhandi',
+ ['bal'] = 'Balkar',
+ ['bau'] = 'Baule',
+ ['bbr'] = 'Berber',
+ ['bch'] = 'Bench',
+ ['bcr'] = 'Bible Cree',
+ ['bel'] = 'Belarussian',
+ ['bem'] = 'Bemba',
+ ['ben'] = 'Bengali',
+ ['bgr'] = 'Bulgarian',
+ ['bhi'] = 'Bhili',
+ ['bho'] = 'Bhojpuri',
+ ['bik'] = 'Bikol',
+ ['bil'] = 'Bilen',
+ ['bkf'] = 'Blackfoot',
+ ['bli'] = 'Balochi',
+ ['bln'] = 'Balante',
+ ['blt'] = 'Balti',
+ ['bmb'] = 'Bambara',
+ ['bml'] = 'Bamileke',
+ ['bos'] = 'Bosnian',
+ ['bre'] = 'Breton',
+ ['brh'] = 'Brahui',
+ ['bri'] = 'Braj Bhasha',
+ ['brm'] = 'Burmese',
+ ['bsh'] = 'Bashkir',
+ ['bti'] = 'Beti',
+ ['cat'] = 'Catalan',
+ ['ceb'] = 'Cebuano',
+ ['che'] = 'Chechen',
+ ['chg'] = 'Chaha Gurage',
+ ['chh'] = 'Chattisgarhi',
+ ['chi'] = 'Chichewa',
+ ['chk'] = 'Chukchi',
+ ['chp'] = 'Chipewyan',
+ ['chr'] = 'Cherokee',
+ ['chu'] = 'Chuvash',
+ ['cmr'] = 'Comorian',
+ ['cop'] = 'Coptic',
+ ['cos'] = 'Corsican',
+ ['cre'] = 'Cree',
+ ['crr'] = 'Carrier',
+ ['crt'] = 'Crimean Tatar',
+ ['csl'] = 'Church Slavonic',
+ ['csy'] = 'Czech',
+ ['dan'] = 'Danish',
+ ['dar'] = 'Dargwa',
+ ['dcr'] = 'Woods Cree',
+ ['deu'] = 'German',
+ ['dgr'] = 'Dogri',
+ ['div'] = 'Divehi',
+ ['djr'] = 'Djerma',
+ ['dng'] = 'Dangme',
+ ['dnk'] = 'Dinka',
+ ['dri'] = 'Dari',
+ ['dun'] = 'Dungan',
+ ['dzn'] = 'Dzongkha',
+ ['ebi'] = 'Ebira',
+ ['ecr'] = 'Eastern Cree',
+ ['edo'] = 'Edo',
+ ['efi'] = 'Efik',
+ ['ell'] = 'Greek',
+ ['eng'] = 'English',
+ ['erz'] = 'Erzya',
+ ['esp'] = 'Spanish',
+ ['eti'] = 'Estonian',
+ ['euq'] = 'Basque',
+ ['evk'] = 'Evenki',
+ ['evn'] = 'Even',
+ ['ewe'] = 'Ewe',
+ ['fan'] = 'French Antillean',
+ ['far'] = 'Farsi',
+ ['fin'] = 'Finnish',
+ ['fji'] = 'Fijian',
+ ['fle'] = 'Flemish',
+ ['fne'] = 'Forest Nenets',
+ ['fon'] = 'Fon',
+ ['fos'] = 'Faroese',
+ ['fra'] = 'French',
+ ['fri'] = 'Frisian',
+ ['frl'] = 'Friulian',
+ ['fta'] = 'Futa',
+ ['ful'] = 'Fulani',
+ ['gad'] = 'Ga',
+ ['gae'] = 'Gaelic',
+ ['gag'] = 'Gagauz',
+ ['gal'] = 'Galician',
+ ['gar'] = 'Garshuni',
+ ['gaw'] = 'Garhwali',
+ ['gez'] = "Ge'ez",
+ ['gil'] = 'Gilyak',
+ ['gmz'] = 'Gumuz',
+ ['gon'] = 'Gondi',
+ ['grn'] = 'Greenlandic',
+ ['gro'] = 'Garo',
+ ['gua'] = 'Guarani',
+ ['guj'] = 'Gujarati',
+ ['hai'] = 'Haitian',
+ ['hal'] = 'Halam',
+ ['har'] = 'Harauti',
+ ['hau'] = 'Hausa',
+ ['haw'] = 'Hawaiin',
+ ['hbn'] = 'Hammer-Banna',
+ ['hil'] = 'Hiligaynon',
+ ['hin'] = 'Hindi',
+ ['hma'] = 'High Mari',
+ ['hnd'] = 'Hindko',
+ ['ho'] = 'Ho',
+ ['hri'] = 'Harari',
+ ['hrv'] = 'Croatian',
+ ['hun'] = 'Hungarian',
+ ['hye'] = 'Armenian',
+ ['ibo'] = 'Igbo',
+ ['ijo'] = 'Ijo',
+ ['ilo'] = 'Ilokano',
+ ['ind'] = 'Indonesian',
+ ['ing'] = 'Ingush',
+ ['inu'] = 'Inuktitut',
+ ['iri'] = 'Irish',
+ ['irt'] = 'Irish Traditional',
+ ['isl'] = 'Icelandic',
+ ['ism'] = 'Inari Sami',
+ ['ita'] = 'Italian',
+ ['iwr'] = 'Hebrew',
+ ['jan'] = 'Japanese',
+ ['jav'] = 'Javanese',
+ ['jii'] = 'Yiddish',
+ ['jud'] = 'Judezmo',
+ ['jul'] = 'Jula',
+ ['kab'] = 'Kabardian',
+ ['kac'] = 'Kachchi',
+ ['kal'] = 'Kalenjin',
+ ['kan'] = 'Kannada',
+ ['kar'] = 'Karachay',
+ ['kat'] = 'Georgian',
+ ['kaz'] = 'Kazakh',
+ ['keb'] = 'Kebena',
+ ['kge'] = 'Khutsuri Georgian',
+ ['kha'] = 'Khakass',
+ ['khk'] = 'Khanty-Kazim',
+ ['khm'] = 'Khmer',
+ ['khs'] = 'Khanty-Shurishkar',
+ ['khv'] = 'Khanty-Vakhi',
+ ['khw'] = 'Khowar',
+ ['kik'] = 'Kikuyu',
+ ['kir'] = 'Kirghiz',
+ ['kis'] = 'Kisii',
+ ['kkn'] = 'Kokni',
+ ['klm'] = 'Kalmyk',
+ ['kmb'] = 'Kamba',
+ ['kmn'] = 'Kumaoni',
+ ['kmo'] = 'Komo',
+ ['kms'] = 'Komso',
+ ['knr'] = 'Kanuri',
+ ['kod'] = 'Kodagu',
+ ['koh'] = 'Korean Old Hangul',
+ ['kok'] = 'Konkani',
+ ['kon'] = 'Kikongo',
+ ['kop'] = 'Komi-Permyak',
+ ['kor'] = 'Korean',
+ ['koz'] = 'Komi-Zyrian',
+ ['kpl'] = 'Kpelle',
+ ['kri'] = 'Krio',
+ ['krk'] = 'Karakalpak',
+ ['krl'] = 'Karelian',
+ ['krm'] = 'Karaim',
+ ['krn'] = 'Karen',
+ ['krt'] = 'Koorete',
+ ['ksh'] = 'Kashmiri',
+ ['ksi'] = 'Khasi',
+ ['ksm'] = 'Kildin Sami',
+ ['kui'] = 'Kui',
+ ['kul'] = 'Kulvi',
+ ['kum'] = 'Kumyk',
+ ['kur'] = 'Kurdish',
+ ['kuu'] = 'Kurukh',
+ ['kuy'] = 'Kuy',
+ ['kyk'] = 'Koryak',
+ ['lad'] = 'Ladin',
+ ['lah'] = 'Lahuli',
+ ['lak'] = 'Lak',
+ ['lam'] = 'Lambani',
+ ['lao'] = 'Lao',
+ ['lat'] = 'Latin',
+ ['laz'] = 'Laz',
+ ['lcr'] = 'L-Cree',
+ ['ldk'] = 'Ladakhi',
+ ['lez'] = 'Lezgi',
+ ['lin'] = 'Lingala',
+ ['lma'] = 'Low Mari',
+ ['lmb'] = 'Limbu',
+ ['lmw'] = 'Lomwe',
+ ['lsb'] = 'Lower Sorbian',
+ ['lsm'] = 'Lule Sami',
+ ['lth'] = 'Lithuanian',
+ ['ltz'] = 'Luxembourgish',
+ ['lub'] = 'Luba',
+ ['lug'] = 'Luganda',
+ ['luh'] = 'Luhya',
+ ['luo'] = 'Luo',
+ ['lvi'] = 'Latvian',
+ ['maj'] = 'Majang',
+ ['mak'] = 'Makua',
+ ['mal'] = 'Malayalam Traditional',
+ ['man'] = 'Mansi',
+ ['map'] = 'Mapudungun',
+ ['mar'] = 'Marathi',
+ ['maw'] = 'Marwari',
+ ['mbn'] = 'Mbundu',
+ ['mch'] = 'Manchu',
+ ['mcr'] = 'Moose Cree',
+ ['mde'] = 'Mende',
+ ['men'] = "Me'en",
+ ['miz'] = 'Mizo',
+ ['mkd'] = 'Macedonian',
+ ['mle'] = 'Male',
+ ['mlg'] = 'Malagasy',
+ ['mln'] = 'Malinke',
+ ['mlr'] = 'Malayalam Reformed',
+ ['mly'] = 'Malay',
+ ['mnd'] = 'Mandinka',
+ ['mng'] = 'Mongolian',
+ ['mni'] = 'Manipuri',
+ ['mnk'] = 'Maninka',
+ ['mnx'] = 'Manx Gaelic',
+ ['moh'] = 'Mohawk',
+ ['mok'] = 'Moksha',
+ ['mol'] = 'Moldavian',
+ ['mon'] = 'Mon',
+ ['mor'] = 'Moroccan',
+ ['mri'] = 'Maori',
+ ['mth'] = 'Maithili',
+ ['mts'] = 'Maltese',
+ ['mun'] = 'Mundari',
+ ['nag'] = 'Naga-Assamese',
+ ['nan'] = 'Nanai',
+ ['nas'] = 'Naskapi',
+ ['ncr'] = 'N-Cree',
+ ['ndb'] = 'Ndebele',
+ ['ndg'] = 'Ndonga',
+ ['nep'] = 'Nepali',
+ ['new'] = 'Newari',
+ ['ngr'] = 'Nagari',
+ ['nhc'] = 'Norway House Cree',
+ ['nis'] = 'Nisi',
+ ['niu'] = 'Niuean',
+ ['nkl'] = 'Nkole',
+ ['nko'] = "N'ko",
+ ['nld'] = 'Dutch',
+ ['nog'] = 'Nogai',
+ ['nor'] = 'Norwegian',
+ ['nsm'] = 'Northern Sami',
+ ['nta'] = 'Northern Tai',
+ ['nto'] = 'Esperanto',
+ ['nyn'] = 'Nynorsk',
+ ['oci'] = 'Occitan',
+ ['ocr'] = 'Oji-Cree',
+ ['ojb'] = 'Ojibway',
+ ['ori'] = 'Oriya',
+ ['oro'] = 'Oromo',
+ ['oss'] = 'Ossetian',
+ ['paa'] = 'Palestinian Aramaic',
+ ['pal'] = 'Pali',
+ ['pan'] = 'Punjabi',
+ ['pap'] = 'Palpa',
+ ['pas'] = 'Pashto',
+ ['pgr'] = 'Polytonic Greek',
+ ['pil'] = 'Pilipino',
+ ['plg'] = 'Palaung',
+ ['plk'] = 'Polish',
+ ['pro'] = 'Provencal',
+ ['ptg'] = 'Portuguese',
+ ['qin'] = 'Chin',
+ ['raj'] = 'Rajasthani',
+ ['rbu'] = 'Russian Buriat',
+ ['rcr'] = 'R-Cree',
+ ['ria'] = 'Riang',
+ ['rms'] = 'Rhaeto-Romanic',
+ ['rom'] = 'Romanian',
+ ['roy'] = 'Romany',
+ ['rsy'] = 'Rusyn',
+ ['rua'] = 'Ruanda',
+ ['rus'] = 'Russian',
+ ['sad'] = 'Sadri',
+ ['san'] = 'Sanskrit',
+ ['sat'] = 'Santali',
+ ['say'] = 'Sayisi',
+ ['sek'] = 'Sekota',
+ ['sel'] = 'Selkup',
+ ['sgo'] = 'Sango',
+ ['shn'] = 'Shan',
+ ['sib'] = 'Sibe',
+ ['sid'] = 'Sidamo',
+ ['sig'] = 'Silte Gurage',
+ ['sks'] = 'Skolt Sami',
+ ['sky'] = 'Slovak',
+ ['sla'] = 'Slavey',
+ ['slv'] = 'Slovenian',
+ ['sml'] = 'Somali',
+ ['smo'] = 'Samoan',
+ ['sna'] = 'Sena',
+ ['snd'] = 'Sindhi',
+ ['snh'] = 'Sinhalese',
+ ['snk'] = 'Soninke',
+ ['sog'] = 'Sodo Gurage',
+ ['sot'] = 'Sotho',
+ ['sqi'] = 'Albanian',
+ ['srb'] = 'Serbian',
+ ['srk'] = 'Saraiki',
+ ['srr'] = 'Serer',
+ ['ssl'] = 'South Slavey',
+ ['ssm'] = 'Southern Sami',
+ ['sur'] = 'Suri',
+ ['sva'] = 'Svan',
+ ['sve'] = 'Swedish',
+ ['swa'] = 'Swadaya Aramaic',
+ ['swk'] = 'Swahili',
+ ['swz'] = 'Swazi',
+ ['sxt'] = 'Sutu',
+ ['syr'] = 'Syriac',
+ ['tab'] = 'Tabasaran',
+ ['taj'] = 'Tajiki',
+ ['tam'] = 'Tamil',
+ ['tat'] = 'Tatar',
+ ['tcr'] = 'TH-Cree',
+ ['tel'] = 'Telugu',
+ ['tgn'] = 'Tongan',
+ ['tgr'] = 'Tigre',
+ ['tgy'] = 'Tigrinya',
+ ['tha'] = 'Thai',
+ ['tht'] = 'Tahitian',
+ ['tib'] = 'Tibetan',
+ ['tkm'] = 'Turkmen',
+ ['tmn'] = 'Temne',
+ ['tna'] = 'Tswana',
+ ['tne'] = 'Tundra Nenets',
+ ['tng'] = 'Tonga',
+ ['tod'] = 'Todo',
+ ['trk'] = 'Turkish',
+ ['tsg'] = 'Tsonga',
+ ['tua'] = 'Turoyo Aramaic',
+ ['tul'] = 'Tulu',
+ ['tuv'] = 'Tuvin',
+ ['twi'] = 'Twi',
+ ['udm'] = 'Udmurt',
+ ['ukr'] = 'Ukrainian',
+ ['urd'] = 'Urdu',
+ ['usb'] = 'Upper Sorbian',
+ ['uyg'] = 'Uyghur',
+ ['uzb'] = 'Uzbek',
+ ['ven'] = 'Venda',
+ ['vit'] = 'Vietnamese',
+ ['wa' ] = 'Wa',
+ ['wag'] = 'Wagdi',
+ ['wcr'] = 'West-Cree',
+ ['wel'] = 'Welsh',
+ ['wlf'] = 'Wolof',
+ ['xbd'] = 'Tai Lue',
+ ['xhs'] = 'Xhosa',
+ ['yak'] = 'Yakut',
+ ['yba'] = 'Yoruba',
+ ['ycr'] = 'Y-Cree',
+ ['yic'] = 'Yi Classic',
+ ['yim'] = 'Yi Modern',
+ ['zhh'] = 'Chinese Hong Kong',
+ ['zhp'] = 'Chinese Phonetic',
+ ['zhs'] = 'Chinese Simplified',
+ ['zht'] = 'Chinese Traditional',
+ ['znd'] = 'Zande',
+ ['zul'] = 'Zulu'
+}
+
+fonts.otf.tables.features = {
+ ['aalt'] = 'Access All Alternates',
+ ['abvf'] = 'Above-Base Forms',
+ ['abvm'] = 'Above-Base Mark Positioning',
+ ['abvs'] = 'Above-Base Substitutions',
+ ['afrc'] = 'Alternative Fractions',
+ ['akhn'] = 'Akhands',
+ ['blwf'] = 'Below-Base Forms',
+ ['blwm'] = 'Below-Base Mark Positioning',
+ ['blws'] = 'Below-Base Substitutions',
+ ['c2pc'] = 'Petite Capitals From Capitals',
+ ['c2sc'] = 'Small Capitals From Capitals',
+ ['calt'] = 'Contextual Alternates',
+ ['case'] = 'Case-Sensitive Forms',
+ ['ccmp'] = 'Glyph Composition/Decomposition',
+ ['cjct'] = 'Conjunct Forms',
+ ['clig'] = 'Contextual Ligatures',
+ ['cpsp'] = 'Capital Spacing',
+ ['cswh'] = 'Contextual Swash',
+ ['curs'] = 'Cursive Positioning',
+ ['dflt'] = 'Default Processing',
+ ['dist'] = 'Distances',
+ ['dlig'] = 'Discretionary Ligatures',
+ ['dnom'] = 'Denominators',
+ ['expt'] = 'Expert Forms',
+ ['falt'] = 'Final glyph Alternates',
+ ['fin2'] = 'Terminal Forms #2',
+ ['fin3'] = 'Terminal Forms #3',
+ ['fina'] = 'Terminal Forms',
+ ['frac'] = 'Fractions',
+ ['fwid'] = 'Full Width',
+ ['half'] = 'Half Forms',
+ ['haln'] = 'Halant Forms',
+ ['halt'] = 'Alternate Half Width',
+ ['hist'] = 'Historical Forms',
+ ['hkna'] = 'Horizontal Kana Alternates',
+ ['hlig'] = 'Historical Ligatures',
+ ['hngl'] = 'Hangul',
+ ['hojo'] = 'Hojo Kanji Forms',
+ ['hwid'] = 'Half Width',
+ ['init'] = 'Initial Forms',
+ ['isol'] = 'Isolated Forms',
+ ['ital'] = 'Italics',
+ ['jalt'] = 'Justification Alternatives',
+ ['jp04'] = 'JIS2004 Forms',
+ ['jp78'] = 'JIS78 Forms',
+ ['jp83'] = 'JIS83 Forms',
+ ['jp90'] = 'JIS90 Forms',
+ ['kern'] = 'Kerning',
+ ['lfbd'] = 'Left Bounds',
+ ['liga'] = 'Standard Ligatures',
+ ['ljmo'] = 'Leading Jamo Forms',
+ ['lnum'] = 'Lining Figures',
+ ['locl'] = 'Localized Forms',
+ ['mark'] = 'Mark Positioning',
+ ['med2'] = 'Medial Forms #2',
+ ['medi'] = 'Medial Forms',
+ ['mgrk'] = 'Mathematical Greek',
+ ['mkmk'] = 'Mark to Mark Positioning',
+ ['mset'] = 'Mark Positioning via Substitution',
+ ['nalt'] = 'Alternate Annotation Forms',
+ ['nlck'] = 'NLC Kanji Forms',
+ ['nukt'] = 'Nukta Forms',
+ ['numr'] = 'Numerators',
+ ['onum'] = 'Old Style Figures',
+ ['opbd'] = 'Optical Bounds',
+ ['ordn'] = 'Ordinals',
+ ['ornm'] = 'Ornaments',
+ ['palt'] = 'Proportional Alternate Width',
+ ['pcap'] = 'Petite Capitals',
+ ['pnum'] = 'Proportional Figures',
+ ['pref'] = 'Pre-base Forms',
+ ['pres'] = 'Pre-base Substitutions',
+ ['pstf'] = 'Post-base Forms',
+ ['psts'] = 'Post-base Substitutions',
+ ['pwid'] = 'Proportional Widths',
+ ['qwid'] = 'Quarter Widths',
+ ['rand'] = 'Randomize',
+ ['rkrf'] = 'Rakar Forms',
+ ['rlig'] = 'Required Ligatures',
+ ['rphf'] = 'Reph Form',
+ ['rtbd'] = 'Right Bounds',
+ ['rtla'] = 'Right-To-Left Alternates',
+ ['ruby'] = 'Ruby Notation Forms',
+ ['salt'] = 'Stylistic Alternates',
+ ['sinf'] = 'Scientific Inferiors',
+ ['size'] = 'Optical Size',
+ ['smcp'] = 'Small Capitals',
+ ['smpl'] = 'Simplified Forms',
+ ['ss01'] = 'Sylistic Set 1',
+ ['ss02'] = 'Sylistic Set 2',
+ ['ss03'] = 'Sylistic Set 3',
+ ['ss04'] = 'Sylistic Set 4',
+ ['ss05'] = 'Sylistic Set 5',
+ ['ss06'] = 'Sylistic Set 6',
+ ['ss07'] = 'Sylistic Set 7',
+ ['ss08'] = 'Sylistic Set 8',
+ ['ss09'] = 'Sylistic Set 9',
+ ['ss10'] = 'Sylistic Set 10',
+ ['ss11'] = 'Sylistic Set 11',
+ ['ss12'] = 'Sylistic Set 12',
+ ['ss13'] = 'Sylistic Set 13',
+ ['ss14'] = 'Sylistic Set 14',
+ ['ss15'] = 'Sylistic Set 15',
+ ['ss16'] = 'Sylistic Set 16',
+ ['ss17'] = 'Sylistic Set 17',
+ ['ss18'] = 'Sylistic Set 18',
+ ['ss19'] = 'Sylistic Set 19',
+ ['ss20'] = 'Sylistic Set 20',
+ ['subs'] = 'Subscript',
+ ['sups'] = 'Superscript',
+ ['swsh'] = 'Swash',
+ ['titl'] = 'Titling',
+ ['tjmo'] = 'Trailing Jamo Forms',
+ ['tnam'] = 'Traditional Name Forms',
+ ['tnum'] = 'Tabular Figures',
+ ['trad'] = 'Traditional Forms',
+ ['twid'] = 'Third Widths',
+ ['unic'] = 'Unicase',
+ ['valt'] = 'Alternate Vertical Metrics',
+ ['vatu'] = 'Vattu Variants',
+ ['vert'] = 'Vertical Writing',
+ ['vhal'] = 'Alternate Vertical Half Metrics',
+ ['vjmo'] = 'Vowel Jamo Forms',
+ ['vkna'] = 'Vertical Kana Alternates',
+ ['vkrn'] = 'Vertical Kerning',
+ ['vpal'] = 'Proportional Alternate Vertical Metrics',
+ ['vrt2'] = 'Vertical Rotation',
+ ['zero'] = 'Slashed Zero'
+}
+
+fonts.otf.tables.baselines = {
+ ['hang'] = 'Hanging baseline',
+ ['icfb'] = 'Ideographic character face bottom edge baseline',
+ ['icft'] = 'Ideographic character face tope edige baseline',
+ ['ideo'] = 'Ideographic em-box bottom edge baseline',
+ ['idtp'] = 'Ideographic em-box top edge baseline',
+ ['math'] = 'Mathmatical centered baseline',
+ ['romn'] = 'Roman baseline'
+}
+
+function fonts.otf.tables.to_tag(id)
+ return stringformat("%4s",id:lower())
+end
+
+function fonts.otf.meanings.resolve(tab,id)
+ if tab and id then
+ id = id:lower()
+ return tab[id] or tab[id:gsub(" ","")] or tab['dflt'] or ''
+ else
+ return "unknown"
+ end
+end
+
+function fonts.otf.meanings.script(id)
+ return fonts.otf.meanings.resolve(fonts.otf.tables.scripts,id)
+end
+function fonts.otf.meanings.language(id)
+ return fonts.otf.meanings.resolve(fonts.otf.tables.languages,id)
+end
+function fonts.otf.meanings.feature(id)
+ return fonts.otf.meanings.resolve(fonts.otf.tables.features,id)
+end
+function fonts.otf.meanings.baseline(id)
+ return fonts.otf.meanings.resolve(fonts.otf.tables.baselines,id)
+end
+
+--[[ldx--
+<p>Here we go.</p>
+--ldx]]--
+
+fonts.otf.enhance = { }
+
+function fonts.otf.load(filename,format,sub,featurefile)
+ local name = file.basename(file.removesuffix(filename))
+ if featurefile then
+ name = name .. "@" .. file.removesuffix(file.basename(featurefile))
+ end
+ local data = containers.read(fonts.otf.cache, name)
+ if not data then
+ local ff
+ if sub and sub ~= "" then
+ ff = fontforge.open(filename,sub)
+ else
+ ff = fontforge.open(filename)
+ end
+ if ff then
+ logs.report("load otf","loading: " .. filename)
+ if featurefile then
+ featurefile = input.find_file(texmf.instance,file.addsuffix(featurefile,'fea'),"FONTFEATURES")
+ if featurefile and featurefile ~= "" then
+ logs.report("load otf", "featurefile: " .. featurefile)
+ fontforge.apply_featurefile(ff, featurefile)
+ end
+ end
+ data = fontforge.to_table(ff)
+ fontforge.close(ff)
+ if data then
+ logs.report("load otf","enhance: before")
+ fonts.otf.enhance.before(data,filename)
+ logs.report("load otf","enhance: analyze")
+ fonts.otf.enhance.analyze(data,filename)
+ logs.report("load otf","enhance: after")
+ fonts.otf.enhance.after(data,filename)
+ logs.report("load otf","save in cache")
+ data = containers.write(fonts.otf.cache, name, data)
+ else
+ logs.error("load otf","loading failed")
+ end
+ end
+ end
+ if data then
+ local map = data.map.map
+ local backmap = data.map.backmap
+ local unicodes = data.luatex.unicodes
+ local glyphs = data.glyphs
+ -- maybe handy some day, not used
+ data.name_to_unicode = function (n) return unicodes[n] end
+ data.name_to_index = function (n) return map[unicodes[n]] end
+ data.index_to_name = function (i) return glyphs[i].name end
+ data.unicode_to_name = function (u) return glyphs[map[u]].name end
+ data.index_to_unicode = function (u) return backmap[u] end
+ data.unicode_to_index = function (u) return map[u] end
+ end
+ return data
+end
+
+function fonts.otf.enhance.analyze(data,filename)
+ local t = {
+ filename = file.basename(filename),
+ version = fonts.otf.version,
+ creator = "context mkiv",
+ unicodes = fonts.otf.analyze_unicodes(data),
+ gposfeatures = fonts.otf.analyze_features(data.gpos),
+ gsubfeatures = fonts.otf.analyze_features(data.gsub),
+ marks = fonts.otf.analyze_class(data,'mark'),
+ }
+ t.subtables, t.name_to_type, t.internals, t.always_valid, t.ignore_flags = fonts.otf.analyze_subtables(data)
+ data.luatex = t
+end
+
+function fonts.otf.enhance.before(data,filename)
+ local private = 0xE000
+ local uni_to_int = data.map.map
+ local int_to_uni = data.map.backmap
+ for index, glyph in pairs(data.glyphs) do
+ if index > 0 and glyph.unicodeenc == -1 then
+ while uni_to_int[private] do
+ private = private + 1
+ end
+ uni_to_int[private] = index
+ int_to_uni[index] = private
+ glyph.unicodeenc = private
+ logs.report("load otf",string.format("enhance: glyph %s at index %s is moved to private unicode slot %s",glyph.name,index,private))
+ end
+ end
+ if data.ttf_tables then
+ for _, v in ipairs(data.ttf_tables) do
+ if v.data then v.data = "deleted" end
+ --~ if v.data then v.data = v.data:gsub("\026","\\026") end -- does not work out well
+ end
+ end
+end
+
+function fonts.otf.enhance.after(data,filename)
+ local unicodes = data.luatex.unicodes
+ for index, glyph in pairs(data.glyphs) do
+ if glyph.kerns then
+ local mykerns = { }
+ for k,v in ipairs(glyph.kerns) do
+ local mkl = mykerns[v.lookup]
+ if not mkl then
+ mkl = { [unicodes[v.char]] = v.off }
+ mykerns[v.lookup] = mkl
+ else
+ mkl[unicodes[v.char]] = v.off
+ end
+ end
+ glyph.mykerns = mykerns
+ end
+ end
+end
+
+function fonts.otf.analyze_class(data,class)
+ local classes = { }
+ for index, glyph in pairs(data.glyphs) do
+ if glyph.class == class then
+ classes[glyph.unicodeenc] = true
+ end
+ end
+ return classes
+end
+
+function fonts.otf.analyze_subtables(data)
+ local subtables, name_to_type, internals, always_valid, ignore_flags = { }, { }, { }, { }, { }
+ local function collect(g)
+ if g then
+ for k,v in ipairs(g) do
+ if v.features then
+ local ignored = { false, false, false }
+ if v.flags.ignorecombiningmarks then ignored[1] = 'mark' end
+ if v.flags.ignorebasechars then ignored[2] = 'base' end
+ if v.flags.ignoreligatures then ignored[3] = 'ligature' end
+ if v.subtables then
+ local type = v.type
+ for _, feature in ipairs(v.features) do
+ local ft = feature.tag:lower()
+ subtables[ft] = subtables[ft] or { }
+ for _, script in ipairs(feature.scripts) do
+ local ss = script.script
+ ss = ss:lower()
+ ss = ss:strip()
+ sft = subtables[ft]
+ sft[ss] = sft[ss] or { }
+ local sfts = sft[ss]
+ for _, language in ipairs(script.langs) do
+ language = language:lower()
+ language = language:strip()
+ sfts[language] = sfts[language] or { }
+ local sftsl = sfts[language]
+ local lookups, valid = sftsl.lookups or { }, sftsl.valid or { }
+ for n, subtable in ipairs(v.subtables) do
+ local stl = subtable.name
+ if stl then
+ lookups[#lookups+1] = stl
+ valid[stl] = true
+ name_to_type[stl] = type
+ ignore_flags[stl] = ignored
+ end
+ end
+ sftsl.lookups, sftsl.valid = lookups, valid
+ end
+ end
+ end
+ end
+ else
+ -- we have an internal feature, say ss_l_83 that resolves to
+ -- subfeatures like ss_l_83_s which we find in the glyphs
+ name_to_type[v.name] = v.type
+ local lookups, valid = { }, { }
+ for n, subtable in ipairs(v.subtables) do
+ local stl = subtable.name
+ if stl then
+ lookups[#lookups+1] = stl
+ valid[stl] = true
+ -- name_to_type[stl] = type
+ always_valid[stl] = true
+ end
+ end
+ internals[v.name] = {
+ lookups = lookups,
+ valid = valid
+ }
+ always_valid[v.name] = true -- bonus
+ end
+ end
+ end
+ end
+ collect(data.gsub)
+ collect(data.gpos)
+ return subtables, name_to_type, internals, always_valid, ignore_flags
+end
+
+function fonts.otf.analyze_unicodes(data)
+ local unicodes = { }
+ for _, blob in pairs(data.glyphs) do
+ if blob.name then
+ unicodes[blob.name] = blob.unicodeenc or 0
+ end
+ end
+ unicodes['space'] = unicodes['space'] or 32 -- handly later on
+ return unicodes
+end
+
+function fonts.otf.analyze_features(g)
+ if g then
+ local t, done = { }, { }
+ for k,v in ipairs(g) do
+ local f = v.features
+ if f then
+ for k, v in ipairs(f) do
+ -- scripts and tag
+ local tag = v.tag
+ if not done[tag] then
+ t[#t+1] = tag
+ done[tag] = true
+ end
+ end
+ end
+ end
+ if #t > 0 then
+ return t
+ end
+ end
+ return nil
+end
+
+function fonts.otf.valid_subtable(otfdata,language,script,kind)
+ local t = otfdata.luatex.subtables
+ return t[kind] and t[kind][script] and t[kind][script][language] and t[kind][script][language].lookups
+end
+
+function fonts.otf.features.register(name,default)
+ fonts.otf.features.list[#fonts.otf.features.list+1] = name
+ fonts.otf.features.default[name] = default
+end
+
+function fonts.otf.set_features(tfmdata)
+ local shared = tfmdata.shared
+ local otfdata = shared.otfdata
+ shared.features = fonts.define.check(shared.features,fonts.otf.features.default)
+ local features = shared.features
+ if not table.is_empty(features) then
+ local gposlist = otfdata.luatex.gposfeatures
+ local gsublist = otfdata.luatex.gsubfeatures
+ local mode = tfmdata.mode or fonts.mode
+ local fi = fonts.initializers[mode]
+ if fi and fi.otf then
+ function initialize(list) -- using tex lig and kerning
+ if list then
+ for _, f in ipairs(list) do
+ local value = features[f]
+ if value and fi.otf[f] then -- brr
+ if fonts.otf.trace_features then
+ logs.report("define otf",string.format("initializing feature %s to %s for mode %s for font %s",f,tostring(value),mode or 'unknown', tfmdata.name or 'unknown'))
+ end
+ fi.otf[f](tfmdata,value) -- can set mode (no need to pass otf)
+ mode = tfmdata.mode or fonts.mode
+ fi = fonts.initializers[mode]
+ end
+ end
+ end
+ end
+ initialize(fonts.triggers)
+ initialize(gsublist)
+ initialize(gposlist)
+ end
+ local fm = fonts.methods[mode]
+ if fm and fm.otf then
+ function register(list) -- node manipulations
+ if list then
+ for _, f in ipairs(list) do
+ if features[f] and fm.otf[f] then -- brr
+ if not shared.processors then -- maybe also predefine
+ shared.processors = { fm.otf[f] }
+ else
+ shared.processors[#shared.processors+1] = fm.otf[f]
+ end
+ end
+ end
+ end
+ end
+ register(fonts.triggers)
+ register(gsublist)
+ register(gposlist)
+ end
+ end
+end
+
+function fonts.otf.otf_to_tfm(specification)
+ local name = specification.name
+ local sub = specification.sub
+ local filename = specification.filename
+ local format = specification.format
+ local features = specification.features.normal
+ local cache_id = specification.hash
+ local tfmdata = containers.read(fonts.tfm.cache,cache_id)
+ if not tfmdata then
+ local otfdata = fonts.otf.load(filename,format,sub,features and features.featurefile)
+ if not table.is_empty(otfdata) then
+ tfmdata = fonts.otf.copy_to_tfm(otfdata)
+ if not table.is_empty(tfmdata) then
+ tfmdata.shared = tfmdata.shared or { }
+ tfmdata.unique = tfmdata.unique or { }
+ tfmdata.shared.otfdata = otfdata
+ tfmdata.shared.features = features
+ fonts.otf.set_features(tfmdata)
+ end
+ end
+ containers.write(fonts.tfm.cache,cache_id,tfmdata)
+ end
+ return tfmdata
+end
+
+function fonts.otf.features.prepare_base_kerns(tfmdata,kind,value) -- todo what kind of kerns, currently all
+ if value then
+ local otfdata = tfmdata.shared.otfdata
+ local charlist = otfdata.glyphs
+ local unicodes = otfdata.luatex.unicodes
+ local somevalid = fonts.otf.some_valid_feature(otfdata,tfmdata.language,tfmdata.script,kind)
+ for _, chr in pairs(tfmdata.characters) do
+ local d = charlist[chr.index]
+ if d and d.kerns then
+ local t, done = chr.kerns or { }, false
+ for _, v in pairs(d.kerns) do
+ if somevalid[v.lookup] then
+ local k = unicodes[v.char]
+ if k > 0 then
+ t[k], done = v.off, true
+ end
+ end
+ end
+ if done then
+ chr.kerns = t
+ end
+ end
+ end
+ end
+end
+
+function fonts.otf.copy_to_tfm(data)
+ if data then
+ local tfm = { characters = { }, parameters = { } }
+ local unicodes = data.luatex.unicodes
+ local characters = tfm.characters
+ local force = fonts.otf.notdef
+ for k,v in pairs(data.map.map) do
+ -- k = unicode, v = slot
+ local d = data.glyphs[v]
+ -- if d then
+ if d and (force or d.name) then
+ local t = { }
+ t.index = v
+ t.unicode = k
+ t.name = d.name or ".notdef"
+ t.boundingbox = d.boundingbox or nil
+ t.width = d.width or 0
+ t.height = d.boundingbox[4] or 0
+ t.depth = - d.boundingbox[2] or 0
+ t.class = d.class
+ if d.class == "mark" then
+ t.width = -t.width
+ end
+ characters[k] = t
+ end
+ end
+ tfm.units = data.units_per_em or 1000
+ -- we need a runtime lookup because of running from cdrom or zip
+ tfm.filename = input.findbinfile(texmf.instance,data.luatex.filename,"") or data.luatex.filename
+ tfm.fullname = data.fullname or data.fontname
+ tfm.cidinfo = data.cidinfo
+ -- if not tfm.cidinfo.registry then tfm.cidinfo.registry = "" end
+ tfm.cidinfo.registry = tfm.cidinfo.registry or ""
+ tfm.name = file.removesuffix(file.basename(tfm.filename))
+ tfm.type = "real"
+ tfm.stretch = 0 -- stretch
+ tfm.slant = 0 -- slant
+ tfm.direction = 0
+ tfm.boundarychar_label = 0
+ tfm.boundarychar = 65536
+ tfm.designsize = ((data.designsize or 100)/10)*65536
+ local spaceunits = 500
+ tfm.spacer = "500 units"
+ data.isfixedpitch = data.pfminfo and data.pfminfo.panose and data.pfminfo.panose["proportion"] == "Monospaced"
+ data.charwidth = nil
+ if data.pfminfo then
+ data.charwidth = data.pfminfo.avgwidth
+ end
+ if data.isfixedpitch then
+ if data.glyphs[unicodes['space']] then
+ spaceunits, tfm.spacer = data.glyphs[unicodes['space']].width, "space"
+ elseif data.glyphs[unicodes['emdash']] then
+ spaceunits, tfm.spacer = data.glyphs[unicodes['emdash']].width, "emdash"
+ elseif data.charwidth then
+ spaceunits, tfm.spacer = data.charwidth, "charwidth"
+ end
+ elseif data.glyphs[unicodes['space']] then
+ spaceunits, tfm.spacer = data.glyphs[unicodes['space']].width, "space"
+ elseif data.glyphs[unicodes['emdash']] then
+ spaceunits, tfm.spacer = data.glyphs[unicodes['emdash']].width/2, "emdash/2"
+ elseif data.charwidth then
+ spaceunits, tfm.spacer = data.charwidth, "charwidth"
+ end
+ spaceunits = tonumber(spaceunits)
+ tfm.parameters[1] = 0 -- slant
+ tfm.parameters[2] = spaceunits -- space
+ tfm.parameters[3] = 500 -- space_stretch
+ tfm.parameters[4] = 333 -- space_shrink
+ tfm.parameters[5] = 400 -- x_height
+ tfm.parameters[6] = 1000 -- quad
+ tfm.parameters[7] = 0 -- extra_space (todo)
+ if spaceunits < 200 then
+ -- todo: warning
+ end
+ tfm.italicangle = data.italicangle
+ tfm.ascender = math.abs(data.ascent or 0)
+ tfm.descender = math.abs(data.descent or 0)
+ if data.italicangle then -- maybe also in afm _
+ tfm.parameters[1] = tfm.parameters[1] - math.round(math.tan(data.italicangle*math.pi/180))
+ end
+ if data.isfixedpitch then
+ tfm.parameters[3] = 0
+ tfm.parameters[4] = 0
+ elseif fonts.otf.syncspace then
+ tfm.parameters[3] = spaceunits/2 -- space_stretch
+ tfm.parameters[4] = spaceunits/3 -- space_shrink
+ end
+ if data.pfminfo and data.pfminfo.os2_xheight and data.pfminfo.os2_xheight > 0 then
+ tfm.parameters[5] = data.pfminfo.os2_xheight
+ elseif data.glyphs[unicodes['x']] and data.glyphs[unicodes['x']].height then
+ tfm.parameters[5] = data.glyphs[unicodes['x']].height
+ end
+ return tfm
+ else
+ return nil
+ end
+end
+
+function fonts.tfm.read_from_open_type(specification)
+ local tfmtable = fonts.otf.otf_to_tfm(specification)
+ if tfmtable then
+ tfmtable.name = specification.name
+ tfmtable.sub = specification.sub
+ tfmtable = fonts.tfm.scale(tfmtable, specification.size)
+ tfmtable.file = file.basename(specification.filename)
+ tfmtable.format = specification.format
+ end
+ return tfmtable
+end
+
+-- scripts
+
+fonts.otf.default_language = 'latn'
+fonts.otf.default_script = 'dflt'
+
+function fonts.otf.valid_feature(otfdata,language,script) -- return hash is faster
+ local language = language or fonts.otf.default_language
+ local script = script or fonts.otf.default_script
+ if not (script and language) then
+ return boolean.alwaystrue
+ else
+ language = string.padd(language:lower(),4)
+ script = string.padd(script:lower (),4)
+ local t = { }
+ for k,v in pairs(otfdata.luatex.subtables) do
+ local vv = v[script]
+ if vv and vv[language] then
+ t[k] = vv[language].valid
+ end
+ end
+ local always = otfdata.luatex.always_valid -- for the moment not per feature
+ return function(kind,tag) -- is the kind test needed
+ return always[tag] or kind and t[kind] and t[kind][tag]
+ end
+ end
+end
+function fonts.otf.some_valid_feature(otfdata,language,script,kind)
+ local language = language or fonts.otf.default_language
+ local script = script or fonts.otf.default_script
+ if not (script and language) then
+ return boolean.alwaystrue
+ else
+ language = string.padd(language:lower(),4)
+ script = string.padd(script:lower (),4)
+ local t = otfdata.luatex.subtables[kind]
+ if t and t[script] and t[script][language] and t[script][language].valid then
+ return t[script][language].valid
+ else
+ return { }
+ end
+--~ return (t and t[script][language] and t[script][language].valid) or { }
+ end
+end
+
+function fonts.otf.features.aux.resolve_ligatures(tfmdata,ligatures,kind)
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local chars = tfmdata.characters
+ local changed = tfmdata.changed or { }
+ local done = { }
+ kind = kind or "unknown"
+ while true do
+ local ok = false
+ for k,v in pairs(ligatures) do
+ local lig = v[1]
+ if not done[lig] then
+ local ligs = lig:split(" ")
+ if #ligs == 2 then
+ local c, f, s = chars[v[2]], ligs[1], ligs[2]
+ local uf, us = unicodes[f], unicodes[s]
+ if changed[uf] or changed[us] then
+ if fonts.otf.trace_features then
+ logs.report("define otf",string.format("%s: %s (%s) + %s (%s) ignored",kind,f,uf,s,us))
+ end
+ else
+ local first, second = chars[uf], us
+ if first and second then
+ if not first.ligatures then first.ligatures = { } end
+ first.ligatures[second] = {
+ char = unicodes[c.name],
+ type = 0
+ }
+ if fonts.otf.trace_features then
+ logs.report("define otf",string.format("%s: %s (%s) + %s (%s) = %s (%s)",kind,f,uf,s,us,c.name,unicodes[c.name]))
+ end
+ end
+ end
+ ok, done[lig] = true, c.name
+ end
+ end
+ end
+ if ok then
+ for d,n in pairs(done) do
+ local pattern = "^(" .. d .. ") "
+ for k,v in pairs(ligatures) do
+ v[1] = v[1]:gsub(pattern, function(str)
+ return n .. " "
+ end)
+ end
+ end
+ else
+ break
+ end
+ end
+end
+
+function fonts.otf.features.prepare_base_substitutions(tfmdata,kind,value) -- we can share some code with the node features
+ if value then
+ local ligatures = { }
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local trace = fonts.otf.trace_features
+ local chars = tfmdata.characters
+ local somevalid = fonts.otf.some_valid_feature(otfdata,tfmdata.language,tfmdata.script,kind)
+ tfmdata.changed = tfmdata.changed or { }
+ local changed = tfmdata.changed
+ for k,c in pairs(chars) do
+ local o = otfdata.glyphs[c.index]
+ if o and o.lookups then
+ for lookup,ps in pairs(o.lookups) do
+--~ if valid(kind,lookup) then -- can be optimized for #p = 1
+if somevalid[lookup] then -- can be optimized for #p = 1
+ for i=1,#ps do
+ local p = ps[i]
+ local t = p.type
+ if t == 'substitution' then
+ local ps = p.specification
+ if ps and ps.variant then
+ local pv = ps.variant
+ if pv then
+ local upv = unicodes[pv]
+ if upv and chars[upv] then
+ if trace then
+ logs.report("define otf",string.format("%s: %s (%s) => %s (%s)",kind,chars[k].name,k,chars[upv].name,upv))
+ end
+ chars[k] = chars[upv]
+ changed[k] = true
+ end
+ end
+ end
+ elseif t == 'alternate' then
+ local pa = p.specification if pa and pa.components then
+ local pc = pa.components:match("(%S+)")
+ if pc then
+ local upc = unicodes[pc]
+ if upc and chars[upc] then
+ if trace then
+ logs.report("define otf",string.format("%s: %s (%s) => %s (%s)",kind,chars[k].name,k,chars[upc].name,upc))
+ end
+ chars[k] = chars[upc]
+ changed[k] = true
+ end
+ end
+ end
+ elseif t == 'ligature' and not changed[k] then
+ local pl = p.specification
+ if pl and pl.components then
+ if trace then
+ logs.report("define otf",string.format("%s: %s => %s (%s)",kind,pl.components,chars[k].name,k))
+ end
+ ligatures[#ligatures+1] = { pl.components, k }
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ fonts.otf.features.aux.resolve_ligatures(tfmdata,ligatures,kind)
+ else
+ tfmdata.ligatures = tfmdata.ligatures or { }
+ end
+end
+
+function fonts.initializers.base.otf.liga(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'liga',value) end
+function fonts.initializers.base.otf.dlig(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'dlig',value) end
+function fonts.initializers.base.otf.rlig(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'rlig',value) end
+function fonts.initializers.base.otf.hlig(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'hlig',value) end
+function fonts.initializers.base.otf.pnum(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'pnum',value) end
+function fonts.initializers.base.otf.onum(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'onum',value) end
+function fonts.initializers.base.otf.tnum(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'tnum',value) end
+function fonts.initializers.base.otf.lnum(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'lnum',value) end
+function fonts.initializers.base.otf.zero(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'zero',value) end
+function fonts.initializers.base.otf.smcp(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'smcp',value) end
+function fonts.initializers.base.otf.cpsp(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'cpsp',value) end
+function fonts.initializers.base.otf.c2sc(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'c2sc',value) end
+function fonts.initializers.base.otf.ornm(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'ornm',value) end
+function fonts.initializers.base.otf.aalt(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'aalt',value) end
+
+fonts.otf.features.data.tex = {
+ { "endash", "hyphen hyphen" },
+ { "emdash", "hyphen hyphen hyphen" },
+ { "quotedblleft", "quoteleft quoteleft" },
+ { "quotedblright", "quoteright quoteright" },
+ { "quotedblleft", "grave grave" },
+ { "quotedblright", "quotesingle quotesingle" },
+ { "quotedblbase", "comma comma" }
+}
+
+--~ 0x201C 0x2018 0x2018
+--~ 0x201D 0x2019 0x2019
+--~ 0x201E 0X002C 0x002C
+
+function fonts.initializers.base.otf.texligatures(tfm,value)
+ local otfdata = tfm.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local ligatures = { }
+ for _,v in pairs(fonts.otf.features.data.tex) do
+ if type(v[1]) == "string" then
+ local c = unicodes[v[1]]
+ if c then
+ ligatures[#ligatures+1] = { v[2], c }
+ end
+ else
+ ligatures[#ligatures+1] = { v[2], v[1] }
+ end
+ end
+ fonts.otf.features.aux.resolve_ligatures(tfm,ligatures)
+end
+
+function fonts.initializers.base.otf.texquotes(tfm,value)
+ tfm.characters[0x0022] = table.fastcopy(tfm.characters[0x201D])
+ tfm.characters[0x0027] = table.fastcopy(tfm.characters[0x2019])
+ tfm.characters[0x0060] = table.fastcopy(tfm.characters[0x2018])
+end
+
+fonts.initializers.base.otf.trep = fonts.initializers.base.otf.texquotes
+fonts.initializers.base.otf.tlig = fonts.initializers.base.otf.texligatures
+
+table.insert(fonts.triggers,"texquotes")
+table.insert(fonts.triggers,"texligatures")
+table.insert(fonts.triggers,"tlig")
+
+-- Here comes the real thing ... node processing! The next session prepares
+-- things. The main features (unchained by rules) have their own caches,
+-- while the private ones cache locally.
+
+do
+
+ fonts.otf.features.prepare = { }
+
+ -- also share vars
+
+ function fonts.otf.features.prepare.feature(tfmdata,kind,value) -- check BASE VS NODE
+ if value then
+ tfmdata.unique = tfmdata.unique or { }
+ tfmdata.shared = tfmdata.shared or { }
+ local shared = tfmdata.shared
+ shared.featuredata = shared.featuredata or { }
+ shared.featuredata[kind] = shared.featuredata[kind] or { }
+ shared.featurecache = shared.featurecache or { }
+ shared.featurecache[kind] = false -- signal
+ local otfdata = shared.otfdata
+ local lookuptable = fonts.otf.valid_subtable(otfdata,tfmdata.language,tfmdata.script,kind)
+ shared.lookuptable = shared.lookuptable or { }
+ shared.lookuptable[kind] = lookuptable
+ if lookuptable then
+ shared.processes = shared.processes or { }
+ shared.processes[kind] = shared.processes[kind] or { }
+ local processes = shared.processes[kind]
+ local types = otfdata.luatex.name_to_type
+ local flags = otfdata.luatex.ignore_flags
+ local preparers = fonts.otf.features.prepare
+ local process = fonts.otf.features.process
+ for noflookups, lookupname in ipairs(lookuptable) do
+ local lookuptype = types[lookupname]
+ local prepare = preparers[lookuptype]
+ if prepare then
+ local processdata = prepare(tfmdata,kind,lookupname)
+ if processdata then
+ local processflags = flags[lookupname] or {false,false,false}
+ processes[#processes+1] = { process[lookuptype], lookupname, processdata, processflags }
+ end
+ end
+ end
+ end
+ end
+ end
+
+ -- helper: todo, we don't need to store non local ones for chains so we can pass the
+ -- validator as parameter
+
+ function fonts.otf.features.collect_ligatures(tfmdata,kind,internal) -- ligs are spread all over the place
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = tfmdata.shared.otfdata.luatex.unicodes -- actually the char index is ok too
+ local trace = fonts.otf.trace_features
+ local ligatures = { }
+ local function collect(lookup,o,ps)
+ for i=1,#ps do
+ local p = ps[i]
+ if p.specification and p.type == 'ligature' then
+ if trace then
+ logs.report("define otf",string.format("feature %s ligature %s => %s",kind,p.specification.components,o.name))
+ end
+ local t = ligatures[lookup]
+ if not t then
+ t = { }
+ ligatures[lookup] = t
+ end
+ local first = true
+ for s in p.specification.components:gmatch("(%S+)") do
+ local u = unicodes[s]
+ if first then
+ if not t[u] then
+ t[u] = { { } }
+ end
+ t = t[u]
+ first = false
+ else
+ if not t[1][u] then
+ t[1][u] = { { } }
+ end
+ t = t[1][u]
+ end
+ end
+ t[2] = o.unicodeenc
+ end
+ end
+ end
+ if internal then
+ local always = otfdata.luatex.always_valid
+ for _,o in pairs(otfdata.glyphs) do
+ if o.lookups then
+ for lookup, ps in pairs(o.lookups) do
+ if always[lookup] then
+ collect(lookup,o,ps)
+ end
+ end
+ end
+ end
+ else -- check if this valid is still ok
+ local valid = fonts.otf.valid_feature(otfdata,tfmdata.language,tfmdata.script)
+ for _,o in pairs(otfdata.glyphs) do
+ if o.lookups then
+ for lookup, ps in pairs(o.lookups) do
+ if valid(kind,lookup) then
+ collect(lookup,o,ps)
+ end
+ end
+ end
+ end
+ end
+ return ligatures
+ end
+
+ -- gsub_single -> done
+ -- gsub_multiple -> done
+ -- gsub_alternate -> done
+ -- gsub_ligature -> done
+ -- gsub_context -> todo
+ -- gsub_contextchain -> done
+ -- gsub_reversechain -> todo
+
+ -- we used to share code in the following functions but that was relatively
+ -- due to extensive calls to functions (easily hundreds of thousands per
+ -- document)
+
+ function fonts.otf.features.prepare.gsub_single(tfmdata,kind,lookupname)
+ local featuredata = tfmdata.shared.featuredata[kind]
+ local substitutions = featuredata[lookupname]
+ if not substitutions then
+ substitutions = { }
+ featuredata[lookupname] = substitutions
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local trace = fonts.otf.trace_features
+ for _, o in pairs(otfdata.glyphs) do
+ local lookups = o.lookups
+ if lookups then
+ for lookup,ps in pairs(lookups) do
+ if lookup == lookupname then
+ for i=1,#ps do
+ local p = ps[i]
+ if p.specification and p.type == 'substitution' then
+ local old, new = o.unicodeenc, unicodes[p.specification.variant]
+ substitutions[old] = new
+ if trace then
+ logs.report("define otf",string.format("%s:%s substitution %s => %s",kind,lookupname,old,new))
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ return substitutions
+ end
+
+ function fonts.otf.features.prepare.gsub_multiple(tfmdata,kind,lookupname)
+ local featuredata = tfmdata.shared.featuredata[kind]
+ local substitutions = featuredata[lookupname]
+ if not substitutions then
+ substitutions = { }
+ featuredata[lookupname] = substitutions
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local trace = fonts.otf.trace_features
+ for _,o in pairs(otfdata.glyphs) do
+ local lookups = o.lookups
+ if lookups then
+ for lookup,ps in pairs(lookups) do
+ if lookup == lookupname then
+ for i=1,#ps do
+ local p = ps[i]
+ if p.specification and p.type == 'multiple' then
+ local old, new = o.unicodeenc, { }
+ substitutions[old] = new
+ for pc in p.specification.components:gmatch("(%S+)") do
+ new[#new+1] = unicodes[pc]
+ end
+ if trace then
+ logs.report("define otf",string.format("%s:%s multiple %s => %s",kind,lookupname,old,table.concat(new," ")))
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ return substitutions
+ end
+
+ function fonts.otf.features.prepare.gsub_alternate(tfmdata,kind,lookupname)
+ -- todo: configurable preference list
+ local featuredata = tfmdata.shared.featuredata[kind]
+ local substitutions = featuredata[lookupname]
+ if not substitutions then
+ featuredata[lookupname] = { }
+ substitutions = featuredata[lookupname]
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local trace = fonts.otf.trace_features
+ for _,o in pairs(otfdata.glyphs) do
+ local lookups = o.lookups
+ if lookups then
+ for lookup,ps in pairs(lookups) do
+ if lookup == lookupname then
+ for i=1,#ps do
+ local p = ps[i]
+ if p.specification and p.type == 'alternate' then
+ local old = o.unicodeenc
+ local t = { }
+ for pc in p.specification.components:gmatch("(%S+)") do
+ t[#t+1] = unicodes[pc]
+ end
+ substitutions[old] = t
+ if trace then
+ logs.report("define otf",string.format("%s:%s alternate %s => %s",kind,lookupname,old,table.concat(substitutions,"|")))
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ return substitutions
+ end
+
+ function fonts.otf.features.prepare.gsub_ligature(tfmdata,kind,lookupname)
+ -- we collect them for all lookups, this saves loops, we only use the
+ -- lookupname for testing, we need to check if this leads to redundant
+ -- collections
+ local ligatures = tfmdata.shared.featuredata[kind]
+ if not ligatures[lookupname] then
+ ligatures = fonts.otf.features.collect_ligatures(tfmdata,kind)
+ tfmdata.shared.featuredata[kind] = ligatures
+ end
+ return ligatures[lookupname]
+ end
+
+ function fonts.otf.features.prepare.contextchain(tfmdata,kind,lookupname)
+ local featuredata = tfmdata.shared.featuredata[kind]
+ local contexts = featuredata[lookupname]
+ if not contexts then
+ featuredata[lookupname] = { }
+ contexts = featuredata[lookupname]
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local internals = otfdata.luatex.internals
+ local flags = otfdata.luatex.ignore_flags
+ local types = otfdata.luatex.name_to_type
+ otfdata.luatex.covers = otfdata.luatex.covers or { }
+ local cache = otfdata.luatex.covers
+ local characters = tfmdata.characters
+ local function uncover(covers)
+ if covers then
+ local result = { }
+ for n, c in ipairs(covers) do
+ local cc = cache[c]
+ if not cc then
+ local t = { }
+ for s in c:gmatch("(%S+)") do
+ t[unicodes[s]] = true
+ end
+ cache[c] = t
+ result[n] = t
+ else
+ result[n] = cc
+ end
+ end
+ return result
+ else
+ return { }
+ end
+ end
+ local lookupdata = otfdata.lookups[lookupname]
+ if not lookupdata then
+ texio.write_nl(string.format("error, missing lookupdata table %s",lookupname))
+ elseif lookupdata.rules then
+ for nofrules, rule in ipairs(lookupdata.rules) do
+ local coverage = rule.coverage
+ if coverage and coverage.current then
+ local current = uncover(coverage.current)
+ local before = uncover(coverage.before)
+ local after = uncover(coverage.after)
+ if current[1] then
+ local lookups, lookuptype = rule.lookups, 'self'
+ -- for the moment only lookup index 1
+ if lookups then
+ if #lookups > 1 then
+ logs.report("otf process","WARNING: more than one lookup in rule")
+ end
+ lookuptype = types[lookups[1]]
+ end
+ for unic, _ in pairs(current[1]) do
+ local t = contexts[unic]
+ if not t then
+ contexts[unic] = { lookups={}, flags=flags[lookupname] }
+ t = contexts[unic].lookups
+ end
+ t[#t+1] = { nofrules, lookuptype, current, before, after, lookups }
+ end
+ end
+ end
+ end
+ end
+ end
+ return contexts
+ end
+
+ fonts.otf.features.prepare.gsub_context = fonts.otf.features.prepare.contextchain
+ fonts.otf.features.prepare.gsub_contextchain = fonts.otf.features.prepare.contextchain
+ fonts.otf.features.prepare.gsub_reversecontextchain = fonts.otf.features.prepare.contextchain
+
+ -- ruled->lookup=ks_latn_l_27_c_4 => internal[ls_l_84] => valid[ls_l_84_s]
+
+ -- gpos_mark2base -> done
+ -- gpos_mark2ligature -> done
+ -- gpos_mark2mark -> done
+ -- gpos_single -> not done
+ -- gpos_pair -> not done
+ -- gpos_cursive -> not done
+ -- gpos_context -> not done
+ -- gpos_contextchain -> not done
+
+ function fonts.otf.features.prepare.anchors(tfmdata,kind,lookupname) -- tracing
+ local featuredata = tfmdata.shared.featuredata[kind]
+ local anchors = featuredata[lookupname]
+ if not anchors then
+ featuredata[lookupname] = { }
+ anchors = featuredata[lookupname]
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local validanchors = { }
+ local glyphs = otfdata.glyphs
+ if otfdata.anchor_classes then
+ for k,v in ipairs(otfdata.anchor_classes) do
+ if v.lookup == lookupname then
+ validanchors[v.name] = true
+ end
+ end
+ end
+ for _,o in pairs(glyphs) do
+ local oanchor = o.anchors
+ if oanchor then
+ local t, ok = { }, false
+ for type, anchors in pairs(oanchor) do -- types
+ local tt = false
+ for name, anchor in pairs(anchors) do
+ if validanchors[name] then
+ if not tt then
+ tt = { [name] = anchor }
+ t[type] = tt
+ ok = true
+ else
+ tt[name] = anchor
+ end
+ end
+ end
+ end
+ if ok then
+ anchors[o.unicodeenc] = t
+ end
+ end
+ end
+ end
+ return anchors
+ end
+
+ fonts.otf.features.prepare.gpos_mark2base = fonts.otf.features.prepare.anchors
+ fonts.otf.features.prepare.gpos_mark2ligature = fonts.otf.features.prepare.anchors
+ fonts.otf.features.prepare.gpos_mark2mark = fonts.otf.features.prepare.anchors
+ fonts.otf.features.prepare.gpos_cursive = fonts.otf.features.prepare.anchors
+ fonts.otf.features.prepare.gpos_context = fonts.otf.features.prepare.contextchain
+ fonts.otf.features.prepare.gpos_contextchain = fonts.otf.features.prepare.contextchain
+
+ function fonts.otf.features.prepare.gpos_single(tfmdata,kind,lookupname)
+ logs.report("otf define","gpos_single not yet supported")
+ end
+
+ function fonts.otf.features.prepare.gpos_pair(tfmdata,kind,lookupname)
+ local featuredata = tfmdata.shared.featuredata[kind]
+ local kerns = featuredata[lookupname]
+ if not kerns then
+ featuredata[lookupname] = { }
+ kerns = featuredata[lookupname]
+ local otfdata = tfmdata.shared.otfdata
+ local unicodes = otfdata.luatex.unicodes
+ local glyphs = otfdata.glyphs
+ for k,o in pairs(glyphs) do
+ local list = o.lookups
+ if list then
+ local one = o.unicodeenc
+ for lookup,ps in pairs(list) do
+ if lookup == lookupname then
+ for i=1,#ps do
+ local p = ps[i]
+ if p.type == 'pair' then
+ local specification = p.specification
+ local two = unicodes[specification.paired]
+ local krn = kerns[one]
+ if krn then
+ krn[two] = specification.offsets
+ else
+ kerns[one] = { two = specification.offsets }
+ end
+ if fonts.otf.trace_features then
+ logs.report("define otf",string.format("feature %s kern pair %s - %s",kind,one,two))
+ end
+ end
+ end
+ end
+ end
+ else
+ list = o.kerns
+ if list then
+ local one = o.unicodeenc
+ for lookup,ps in pairs(list) do
+ if lookup == lookupname then
+ for i=1,#ps do
+ local p = ps[i]
+ local char = p.char
+ if char then
+ local two = unicodes[char]
+ local krn = kerns[one]
+ if krn then
+ krn[two] = p.off
+ else
+ kerns[one] = { two = p.off }
+ end
+ if fonts.otf.trace_features then
+ logs.report("define otf",string.format("feature %s kern pair %s - %s",kind,one,two))
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ return kerns
+ end
+
+ fonts.otf.features.prepare.gpos_contextchain = fonts.otf.features.prepare.contextchain
+
+end
+
+-- can be generalized: one loop in main
+
+do
+
+ prepare = fonts.otf.features.prepare.feature
+
+ function fonts.initializers.node.otf.afrc(tfm,value) return prepare(tfm,'afrc',value) end
+ function fonts.initializers.node.otf.akhn(tfm,value) return prepare(tfm,'akhn',value) end
+ function fonts.initializers.node.otf.c2pc(tfm,value) return prepare(tfm,'c2pc',value) end
+ function fonts.initializers.node.otf.c2sc(tfm,value) return prepare(tfm,'c2sc',value) end
+ function fonts.initializers.node.otf.calt(tfm,value) return prepare(tfm,'calt',value) end
+ function fonts.initializers.node.otf.case(tfm,value) return prepare(tfm,'case',value) end
+ function fonts.initializers.node.otf.ccmp(tfm,value) return prepare(tfm,'ccmp',value) end
+ function fonts.initializers.node.otf.clig(tfm,value) return prepare(tfm,'clig',value) end
+ function fonts.initializers.node.otf.cpsp(tfm,value) return prepare(tfm,'cpsp',value) end
+ function fonts.initializers.node.otf.cswh(tfm,value) return prepare(tfm,'cswh',value) end
+ function fonts.initializers.node.otf.curs(tfm,value) return prepare(tfm,'curs',value) end
+ function fonts.initializers.node.otf.dlig(tfm,value) return prepare(tfm,'dlig',value) end
+ function fonts.initializers.node.otf.dnom(tfm,value) return prepare(tfm,'dnom',value) end
+ function fonts.initializers.node.otf.expt(tfm,value) return prepare(tfm,'expt',value) end
+ function fonts.initializers.node.otf.fin2(tfm,value) return prepare(tfm,'fin2',value) end
+ function fonts.initializers.node.otf.fin3(tfm,value) return prepare(tfm,'fin3',value) end
+ function fonts.initializers.node.otf.fina(tfm,value) return prepare(tfm,'fina',value) end
+ function fonts.initializers.node.otf.frac(tfm,value) return prepare(tfm,'frac',value) end
+ function fonts.initializers.node.otf.haln(tfm,value) return prepare(tfm,'haln',value) end
+ function fonts.initializers.node.otf.hist(tfm,value) return prepare(tfm,'hist',value) end
+ function fonts.initializers.node.otf.hkna(tfm,value) return prepare(tfm,'hkna',value) end
+ function fonts.initializers.node.otf.hlig(tfm,value) return prepare(tfm,'hlig',value) end
+ function fonts.initializers.node.otf.hngl(tfm,value) return prepare(tfm,'hngl',value) end
+ function fonts.initializers.node.otf.init(tfm,value) return prepare(tfm,'init',value) end
+ function fonts.initializers.node.otf.isol(tfm,value) return prepare(tfm,'isol',value) end
+ function fonts.initializers.node.otf.ital(tfm,value) return prepare(tfm,'ital',value) end
+ function fonts.initializers.node.otf.jp78(tfm,value) return prepare(tfm,'jp78',value) end
+ function fonts.initializers.node.otf.jp83(tfm,value) return prepare(tfm,'jp83',value) end
+ function fonts.initializers.node.otf.jp90(tfm,value) return prepare(tfm,'jp90',value) end
+ function fonts.initializers.node.otf.kern(tfm,value) return prepare(tfm,'kern',value) end
+ function fonts.initializers.node.otf.liga(tfm,value) return prepare(tfm,'liga',value) end
+ function fonts.initializers.node.otf.lnum(tfm,value) return prepare(tfm,'lnum',value) end
+ function fonts.initializers.node.otf.locl(tfm,value) return prepare(tfm,'locl',value) end
+ function fonts.initializers.node.otf.mark(tfm,value) return prepare(tfm,'mark',value) end
+ function fonts.initializers.node.otf.med2(tfm,value) return prepare(tfm,'med2',value) end
+ function fonts.initializers.node.otf.medi(tfm,value) return prepare(tfm,'medi',value) end
+ function fonts.initializers.node.otf.mgrk(tfm,value) return prepare(tfm,'mgrk',value) end
+ function fonts.initializers.node.otf.mkmk(tfm,value) return prepare(tfm,'mkmk',value) end
+ function fonts.initializers.node.otf.nalt(tfm,value) return prepare(tfm,'nalt',value) end
+ function fonts.initializers.node.otf.nlck(tfm,value) return prepare(tfm,'nlck',value) end
+ function fonts.initializers.node.otf.nukt(tfm,value) return prepare(tfm,'nukt',value) end
+ function fonts.initializers.node.otf.numr(tfm,value) return prepare(tfm,'numr',value) end
+ function fonts.initializers.node.otf.onum(tfm,value) return prepare(tfm,'onum',value) end
+ function fonts.initializers.node.otf.ordn(tfm,value) return prepare(tfm,'ordn',value) end
+ function fonts.initializers.node.otf.ornm(tfm,value) return prepare(tfm,'ornm',value) end
+ function fonts.initializers.node.otf.pnum(tfm,value) return prepare(tfm,'pnum',value) end
+ function fonts.initializers.node.otf.pref(tfm,value) return prepare(tfm,'pref',value) end
+ function fonts.initializers.node.otf.pres(tfm,value) return prepare(tfm,'pres',value) end
+ function fonts.initializers.node.otf.pstf(tfm,value) return prepare(tfm,'pstf',value) end
+ function fonts.initializers.node.otf.rlig(tfm,value) return prepare(tfm,'rlig',value) end
+ function fonts.initializers.node.otf.rphf(tfm,value) return prepare(tfm,'rphf',value) end
+ function fonts.initializers.node.otf.salt(tfm,value) return prepare(tfm,'salt',value) end
+ function fonts.initializers.node.otf.sinf(tfm,value) return prepare(tfm,'sinf',value) end
+ function fonts.initializers.node.otf.smcp(tfm,value) return prepare(tfm,'smcp',value) end
+ function fonts.initializers.node.otf.smpl(tfm,value) return prepare(tfm,'smpl',value) end
+ function fonts.initializers.node.otf.ss01(tfm,value) return prepare(tfm,'ss01',value) end
+ function fonts.initializers.node.otf.ss02(tfm,value) return prepare(tfm,'ss02',value) end
+ function fonts.initializers.node.otf.ss03(tfm,value) return prepare(tfm,'ss03',value) end
+ function fonts.initializers.node.otf.ss04(tfm,value) return prepare(tfm,'ss04',value) end
+ function fonts.initializers.node.otf.ss05(tfm,value) return prepare(tfm,'ss05',value) end
+ function fonts.initializers.node.otf.ss06(tfm,value) return prepare(tfm,'ss06',value) end
+ function fonts.initializers.node.otf.ss07(tfm,value) return prepare(tfm,'ss07',value) end
+ function fonts.initializers.node.otf.ss08(tfm,value) return prepare(tfm,'ss08',value) end
+ function fonts.initializers.node.otf.ss09(tfm,value) return prepare(tfm,'ss09',value) end
+ function fonts.initializers.node.otf.subs(tfm,value) return prepare(tfm,'subs',value) end
+ function fonts.initializers.node.otf.sups(tfm,value) return prepare(tfm,'sups',value) end
+ function fonts.initializers.node.otf.swsh(tfm,value) return prepare(tfm,'swsh',value) end
+ function fonts.initializers.node.otf.titl(tfm,value) return prepare(tfm,'titl',value) end
+ function fonts.initializers.node.otf.tnam(tfm,value) return prepare(tfm,'tnam',value) end
+ function fonts.initializers.node.otf.tnum(tfm,value) return prepare(tfm,'tnum',value) end
+ function fonts.initializers.node.otf.trad(tfm,value) return prepare(tfm,'trad',value) end
+ function fonts.initializers.node.otf.unic(tfm,value) return prepare(tfm,'unic',value) end
+ function fonts.initializers.node.otf.zero(tfm,value) return prepare(tfm,'zero',value) end
+
+end
+
+do
+
+ local glyph = node.id('glyph')
+ local glue = node.id('glue')
+ local kern_node = node.new("kern")
+ local glue_node = node.new("glue")
+ local glyph_node = node.new("glyph")
+
+ local fontdata = fonts.tfm.id
+ local has_attribute = node.has_attribute
+ local set_attribute = node.set_attribute
+ local state = attributes.numbers['state'] or 100
+ local marknumber = attributes.numbers['mark'] or 200
+ local format = string.format
+ local report = logs.report
+
+ fonts.otf.features.process = { }
+
+ -- we share aome vars here, after all, we have no nested lookups and
+ -- less code
+
+ local tfmdata = false
+ local otfdata = false
+ local characters = false
+ local marks = false
+ local glyphs = false
+ local currentfont = false
+
+ function fonts.otf.features.process.feature(head,font,kind,attribute)
+ tfmdata = fontdata[font]
+ otfdata = tfmdata.shared.otfdata
+ characters = tfmdata.characters
+ marks = otfdata.luatex.marks
+ glyphs = otfdata.glyphs
+ currentfont = font
+ local lookuptable = tfmdata.shared.lookuptable[kind]
+ if lookuptable then
+ local types = otfdata.luatex.name_to_type
+ local start, done, ok = head, false, false
+ local processes = tfmdata.shared.processes[kind]
+ if #processes == 1 then
+ local p = processes[1]
+ while start do
+ if start.id == glyph and start.font == font and (not attribute or has_attribute(start,state,attribute)) then
+ -- we can make the p vars also global to this closure
+ local pp = p[3] -- all lookups
+ local pc = pp[start.char]
+ if pc then
+ start, ok = p[1](start,kind,p[2],pc,pp,p[4])
+ done = done or ok
+ if start then start = start.next end
+ else
+ start = start.next
+ end
+ else
+ start = start.next
+ end
+ end
+ else
+ while start do
+ if start.id == glyph and start.font == font and (not attribute or has_attribute(start,state,attribute)) then
+ for i=1,#processes do local p = processes[i]
+ local pp = p[3]
+ local pc = pp[start.char]
+ if pc then
+ start, ok = p[1](start,kind,p[2],pc,pp,p[4])
+ if ok then
+ done = true
+ break
+ elseif not start then
+ break
+ end
+ end
+ end
+ if start then start = start.next end
+ else
+ start = start.next
+ end
+ end
+ end
+ return head, done
+ else
+ return head, false
+ end
+ end
+
+ -- todo: components / else subtype 0 / maybe we should be able to force this
+
+ function toligature(start,stop,char,markflag)
+ if start ~= stop then
+ local deletemarks = markflag ~= "mark"
+ start.subtype = 1
+ start.char = char
+ local marknum = 1
+ local next = start.next
+ while true do
+ if marks[next.char] then
+ if not deletemarks then
+ set_attribute(next,marknumber,marknum)
+ end
+ else
+ marknum = marknum + 1
+ end
+ if next == stop then
+ break
+ else
+ next = next.next
+ end
+ end
+ next = stop.next
+ while next do
+ if next.id == glyph and next.font == currentfont and marks[next.char] then
+ set_attribute(next,marknumber,marknum)
+ next = next.next
+ else
+ break
+ end
+ end
+ local next = start.next
+ while true do
+ if next == stop or deletemarks or marks[next.char] then
+ local crap = next
+ next.prev.next = next.next
+ if next.next then
+ next.next.prev = next.prev
+ end
+ if next == stop then
+ stop = crap.prev
+ node.free(crap)
+ break
+ else
+ next = next.next
+ node.free(crap)
+ end
+ else
+ next = next.next
+ end
+ end
+ end
+ return start
+ end
+
+ function fonts.otf.features.process.gsub_single(start,kind,lookupname,replacements)
+ if replacements then
+ start.char = replacements
+ if fonts.otf.trace_replacements then
+ report("process otf",format("%s:%s replacing %s by %s",kind,lookupname,start.char,replacements))
+ end
+ return start, true
+ else
+ return start, false
+ end
+ end
+
+ function fonts.otf.features.process.gsub_alternate(start,kind,lookupname,alternatives)
+ if alternatives then
+ start.char = alternatives[1] -- will be preference
+ if fonts.otf.trace_replacements then
+ report("process otf",format("%s:%s alternative %s => %s",kind,lookupname,start.char,table.concat(alternatives,"|")))
+ end
+ return start, true
+ else
+ return start, false
+ end
+ end
+
+ function fonts.otf.features.process.gsub_multiple(start,kind,lookupname,multiples)
+ if multiples then
+ start.char = multiples[1]
+ if #multiples > 1 then
+ for k=2,#multiples do
+ local n = node.copy(start)
+ n.char = multiples[k]
+ n.next = start.next
+ n.prev = start
+ if start.next then
+ start.next.prev = n
+ end
+ start.next = n
+ start = n
+ end
+ end
+ if fonts.otf.trace_replacements then
+ report("process otf",format("%s:%s alternative %s => %s",kind,lookupname,start.char,table.concat(multiples," ")))
+ end
+ return start, true
+ else
+ return start, false
+ end
+ end
+
+ function fonts.otf.features.process.gsub_ligature(start,kind,lookupname,ligatures,alldata,flags)
+ local s, stop = start.next, nil
+ while s and s.id == glyph and s.subtype == 0 and s.font == currentfont do
+ if marks[s.char] then
+ s = s.next
+ else
+ local lg = ligatures[1][s.char]
+ if not lg then
+ break
+ else
+ stop = s
+ ligatures = lg
+ s = s.next
+ end
+ end
+ end
+ if stop and ligatures[2] then
+ start = toligature(start,stop,ligatures[2],flags[1])
+ if fonts.otf.trace_ligatures then
+ report("process otf",format("%s: inserting ligature %s (%s)",kind,start.char,utf.char(start.char)))
+ end
+ return start, true
+ end
+ return start, false
+ end
+
+ -- again, using copies is more efficient than sharing code
+
+ function fonts.otf.features.process.gpos_mark2base(start,kind,lookupname,baseanchors,anchors) -- maybe use copies
+ local bases = baseanchors['basechar']
+ if bases then
+ local component = start.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ local trace = fonts.otf.trace_anchors
+ local last, done = start, false
+ while true do
+ local markanchors = anchors[component.char]
+ if markanchors then
+ local marks = markanchors['mark']
+ if marks then
+ for anchor,data in pairs(marks) do
+ local ba = bases[anchor]
+ if ba then
+ local dx = tex.scale(ba.x-data.x, tfmdata.factor)
+ local dy = tex.scale(ba.y-data.y, tfmdata.factor)
+ component.xoffset = start.xoffset - dx
+ component.yoffset = start.yoffset + dy
+ if trace then
+ report("process otf",format("%s: anchoring mark %s to basechar %s => (%s,%s) => (%s,%s)",kind,component.char,start.char,dx,dy,component.xoffset,component.yoffset))
+ end
+ done = true
+ break
+ end
+ end
+ end
+ last = component
+ end
+ component = component.next
+--~ if component and component.id == kern then
+--~ component = component.next
+--~ end
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ -- ok
+ else
+ break
+ end
+ end
+ return last, done
+ end
+ end
+ return start, false
+ end
+
+ function fonts.otf.features.process.gpos_mark2ligature(start,kind,lookupname,baseanchors,anchors)
+ local bases = baseanchors['baselig']
+ if bases then
+ local component = start.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ local trace = fonts.otf.trace_anchors
+ local last, done = start, false
+ while true do
+ local markanchors = anchors[component.char]
+ if markanchors then
+ local marks = markanchors['mark']
+ if marks then
+ for anchor,data in pairs(marks) do
+ local ba = bases[anchor]
+ if ba then
+ local n = has_attribute(component,marknumber)
+ local ban = ba[n]
+ if ban then
+ local dx = tex.scale(ban.x-data.x, tfmdata.factor)
+ local dy = tex.scale(ban.y-data.y, tfmdata.factor)
+ component.xoffset = start.xoffset - dx
+ component.yoffset = start.yoffset + dy
+ if trace then
+ report("process otf",format("%s:%s:%s anchoring mark %s to baselig %s => (%s,%s) => (%s,%s)",kind,anchor,n,component.char,start.char,dx,dy,component.xoffset,component.yoffset))
+ end
+ done = true
+ break
+ end
+ end
+ end
+ end
+ end
+ last = component
+ component = component.next
+--~ if component and component.id == kern then
+--~ component = component.next
+--~ end
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ -- ok
+ else
+ break
+ end
+ end
+ return last, done
+ end
+ end
+ return start, false
+ end
+
+ function fonts.otf.features.process.gpos_mark2mark(start,kind,lookupname,baseanchors,anchors)
+ -- we can stay in the loop for all anchors
+ local bases = baseanchors['basemark']
+ if bases then
+ local component = start.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ local baseattr = has_attribute(start,marknumber) or 1
+ local trace = fonts.otf.trace_anchors
+ local last, done = start, false
+ while true do
+ local markattr = has_attribute(component,marknumber) or 1
+ if baseattr == markattr then
+ local markanchors = anchors[component.char]
+ if markanchors then
+ local marks = markanchors['mark']
+ if marks then
+ for anchor,data in pairs(marks) do
+ local ba = bases[anchor]
+ if ba then
+ local dx = tex.scale(ba.x-data.x, tfmdata.factor)
+ local dy = tex.scale(ba.y-data.y, tfmdata.factor)
+ component.xoffset = start.xoffset - dx
+ component.yoffset = start.yoffset + dy
+ if trace then
+ report("process otf",format("%s:%s:%s anchoring mark %s to basemark %s => (%s,%s) => (%s,%s)",kind,anchor,n,start.char,component.char,dx,dy,component.xoffset,component.yoffset))
+ end
+ done = true
+ break
+ end
+ end
+ end
+ end
+ last = component
+ component = component.next
+--~ if component and component.id == kern then
+--~ component = component.next
+--~ end
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ -- ok
+ else
+ break
+ end
+ else
+ break
+ end
+ end
+ return last, done
+ end
+ end
+ return start, false
+ end
+
+ function fonts.otf.features.process.gpos_cursive(start,kind,lookupname,exitanchors,anchors)
+ local trace = fonts.otf.trace_anchors
+ local next, done, x, y, total, t, first = start.next, false, 0, 0, 0, { }, nil
+ local function finish()
+ local i = 0
+ while first do
+ if characters[first.char].class == 'mark' then
+ first = first.next
+ else
+ first.yoffset = tex.scale(total, tfmdata.factor)
+ if first == next then
+ break
+ else
+ i = i + 1
+ total = total - (t[i] or 0)
+ first = first.next
+ end
+ end
+ end
+ x, y, total, t, first = 0, 0, 0, { }, nil
+ end
+ while next do
+ if next.id == glyph and next.font == currentfont then
+ local nextchar = next.char
+ if marks[nextchar] then
+ next = next.next
+ else
+ local entryanchors, exitanchors = anchors[nextchar], anchors[start.char]
+ if entryanchors and exitanchors then
+ local centry, cexit = entryanchors['centry'], exitanchors['cexit']
+ if centry and cexit then
+ for anchor, entry in pairs(centry) do
+ local exit = cexit[anchor]
+ if exit then
+ if not first then first = start end
+ t[#t+1] = exit.y + entry.y
+ total = total + t[#t]
+ done = true
+ break
+ end
+ end
+ else
+ finish()
+ end
+ else
+ finish()
+ end
+ start = next
+ next = start.next
+ end
+ else
+ finish()
+ break
+ end
+ end
+ return start, done
+ end
+
+ function fonts.otf.features.process.gpos_single(start,kind,lookupname,basekerns,kerns)
+ report("otf process","gpos_single not yet supported")
+ return start, false
+ end
+
+ function fonts.otf.features.process.gpos_pair(start,kind,lookupname,basekerns,kerns)
+ local next, prev, done = start.next, start, false
+ while next and next.id == glyph and next.font == currentfont do
+ if characters[next.char].class == 'mark' then
+ prev = next
+ next = next.next
+ else
+ local krn = basekerns[next.char]
+ if krn then
+ local a, b = krn[1], krn[2]
+ if a and a.x then
+ local k = node.copy(kern_node)
+ k.kern = tex.scale(a.x,fontdata[currentfont].factor) -- tfmdata.factor
+ if b and b.x then
+ report("otf process","we need to do something with the second kern xoff " .. b.x)
+ end
+ k.next = next
+ k.prev = prev
+ prev.next = k
+ next.prev = k
+ if fonts.otf.trace_kerns then
+ -- todo
+ end
+ end
+ -- skip over marks
+ end
+ break
+ end
+ end
+ return start, done
+ end
+
+ chainprocs = { } -- we can probably optimize this because they're all internal lookups
+
+ -- For the moment we save each looked up glyph in the sequence, which is ok because
+ -- each lookup in the chain has its own sequence. This saves memory. Only ligatures
+ -- are stored in the featurecache, because we don't want to loop over all characters
+ -- in order to locate them.
+
+ -- We had a version that shared code, but it was too much a slow down
+ -- todo n x n.
+
+ function chainprocs.gsub_single(start,stop,kind,lookupname,sequence,lookups)
+ local char = start.char
+ local cacheslot = sequence[1]
+ local replacement = cacheslot[char]
+ if replacement == true then
+ if lookups then
+ local looks = glyphs[tfmdata.characters[char].index].lookups
+ if looks then
+ local lookups = otfdata.luatex.internals[lookups[1]].lookups
+ local unicodes = otfdata.luatex.unicodes
+ for l=1,#lookups do
+ local lv = looks[lookups[l]]
+ if lv then
+ replacement = unicodes[lv[1].specification.variant] or char
+ cacheslot[char] = replacement
+ break
+ end
+ end
+ else
+ replacement, cacheslot[char] = char, char
+ end
+ else
+ replacement, cacheslot[char] = char, char
+ end
+ end
+ if fonts.otf.trace_replacements then
+ report("otf chain",format("%s: replacing character %s by single %s",kind,char,replacement))
+ end
+ start.char = replacement
+ return start
+ end
+
+ function chainprocs.gsub_multiple(start,stop,kind,lookupname,sequence,lookups)
+ local char = start.char
+ local cacheslot = sequence[1]
+ local replacement = cacheslot[char]
+ if replacement == true then
+ if lookups then
+ local looks = glyphs[tfmdata.characters[char].index].lookups
+ if looks then
+ local lookups = otfdata.luatex.internals[lookups[1]].lookups
+ local unicodes = otfdata.luatex.unicodes
+ for l=1,#lookups do
+ local lv = looks[lookups[l]]
+ if lv then
+ replacement = { }
+ for c in lv[1].specification.components:gmatch("(%S+)") do
+ replacement[#replacement+1] = unicodes[c]
+ end
+ cacheslot[char] = replacement
+ break
+ end
+ end
+ else
+ replacement = { char }
+ cacheslot[char] = replacement
+ end
+ else
+ replacement = { char }
+ cacheslot[char] = replacement
+ end
+ end
+ if fonts.otf.trace_replacements then
+ report("otf chain",format("%s: replacing character %s by multiple",kind,char))
+ end
+ start.char = replacement[1]
+ if #replacement > 1 then
+ for k=2,#replacement do
+ local n = node.copy(start)
+ n.char = replacement[k]
+ n.next = start.next
+ n.prev = start
+ if start.next then
+ start.next.prev = n
+ end
+ start.next = n
+ start = n
+ end
+ end
+ return start
+ end
+
+ function chainprocs.gsub_alternate(start,stop,kind,lookupname,sequence,lookups)
+ local char = start.char
+ local cacheslot = sequence[1]
+ local replacement = cacheslot[char]
+ if replacement == true then
+ if lookups then
+ local looks = glyphs[tfmdata.characters[char].index].lookups
+ if looks then
+ local lookups = otfdata.luatex.internals[lookups[1]].lookups
+ local unicodes = otfdata.luatex.unicodes
+ for l=1,#lookups do
+ local lv = looks[lookups[l]]
+ if lv then
+ replacement = { }
+ for c in lv[1].specification.components:gmatch("(%S+)") do
+ replacement[#replacement+1] = unicodes[c]
+ end
+ cacheslot[char] = replacement
+ break
+ end
+ end
+ else
+ replacement = { char }
+ cacheslot[char] = replacement
+ end
+ else
+ replacement = { char }
+ cacheslot[char] = replacement
+ end
+ end
+ if fonts.otf.trace_replacements then
+ report("otf chain",format("%s: replacing character %s by alternate",kind,char))
+ end
+ start.char = replacement[1]
+ return start
+ end
+
+ function chainprocs.gsub_ligature(start,stop,kind,lookupname,sequence,lookups,flags)
+ if lookups then
+ local featurecache = fontdata[currentfont].shared.featurecache
+ if not featurecache[kind] then
+ featurecache[kind] = fonts.otf.features.collect_ligatures(tfmdata,kind)
+ -- to be tested: only collect internal
+ -- featurecache[kind] = fonts.otf.features.collect_ligatures(tfmdata,kind,true) --
+ end
+ local lookups = otfdata.luatex.internals[lookups[1]].lookups
+ local ligaturecache = featurecache[kind]
+ for i=1,#lookups do
+ local ligatures = ligaturecache[lookups[i]]
+ if ligatures and ligatures[start.char] then
+ ligatures = ligatures[start.char]
+ local s = start.next
+ while s do
+ if characters[s.char].class == 'mark' then
+ s = s.next
+ else
+ local lg = ligatures[1][s.char]
+ if not lg then
+ break
+ else
+ ligatures = lg
+ if s == stop then
+ break
+ else
+ s = s.next
+ end
+ end
+ end
+ end
+ if ligatures[2] then
+ if fonts.otf.trace_ligatures then
+ report("otf chain",format("%s: replacing character %s by ligature",kind,start.char))
+ end
+ return toligature(start,stop,ligatures[2],flags[1])
+ end
+ break
+ end
+ end
+ end
+ return stop
+ end
+
+ function chainprocs.gpos_mark2base(start,stop,kind,lookupname,sequence,lookups)
+ local component = start.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ local char = start.char
+ local anchortag = sequence[1][char]
+ if anchortag == true then
+ local classes = otfdata.anchor_classes
+ for k=1,#classes do
+ local v = classes[k]
+ if v.lookup == lookupname and v.type == kind then
+ anchortag = v.name
+ sequence[1][char] = anchortag
+ break
+ end
+ end
+ end
+ if anchortag ~= true then
+ local glyph = glyphs[characters[char].index]
+ if glyph.anchors and glyph.anchors[anchortag] then
+ local trace = fonts.otf.trace_anchors
+ local last, done = start, false
+ local baseanchors = glyph.anchors['basechar'][anchortag]
+ while true do
+ local nextchar = component.char
+ local charnext = characters[nextchar]
+ local markanchors = glyphs[charnext.index].anchors['mark'][anchortag]
+ if markanchors then
+ for anchor,data in pairs(markanchors) do
+ local ba = baseanchors[anchor]
+ if ba then
+ local dx = tex.scale(ba.x-data.x, tfmdata.factor)
+ local dy = tex.scale(ba.y-data.y, tfmdata.factor)
+ component.xoffset = start.xoffset - dx
+ component.yoffset = start.yoffset + dy
+ if trace then
+ report("otf chain",format("%s: anchoring mark %s to basechar %s => (%s,%s) => (%s,%s)",kind,component.char,start.char,dx,dy,component.xoffset,component.yoffset))
+ end
+ done = true
+ break
+ end
+ end
+ end
+ last = component
+ component = component.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ -- ok
+ else
+ break
+ end
+ end
+ return last, done
+ end
+ end
+ end
+ return start, false
+ end
+
+ function chainprocs.gpos_mark2ligature(start,stop,kind,lookupname,sequence,lookups)
+ local component = start.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ local char = start.char
+ local anchortag = sequence[1][char]
+ if anchortag == true then
+ local classes = otfdata.anchor_classes
+ for k=1,#classes do
+ local v = classes[k]
+ if v.lookup == lookupname and v.type == kind then
+ anchortag = v.name
+ sequence[1][char] = anchortag
+ break
+ end
+ end
+ end
+ if anchortag ~= true then
+ local glyph = glyphs[characters[char].index]
+ if glyph.anchors and glyph.anchors[anchortag] then
+ local trace = fonts.otf.trace_anchors
+ local done = false
+ local last = start
+ local baseanchors = glyph.anchors['baselig'][anchortag]
+ while true do
+ local nextchar = component.char
+ local charnext = characters[nextchar]
+ local markanchors = glyphs[charnext.index].anchors['mark'][anchortag]
+ if markanchors then
+ for anchor,data in pairs(markanchors) do
+ local ba = baseanchors[anchor]
+ if ba then
+ local n = has_attribute(component,marknumber)
+ local ban = ba[n]
+ if ban then
+ local dx = tex.scale(ban.x-data.x, tfmdata.factor)
+ local dy = tex.scale(ban.y-data.y, tfmdata.factor)
+ component.xoffset = start.xoffset - dx
+ component.yoffset = start.yoffset + dy
+ if trace then
+ report("otf chain",format("%s: anchoring mark %s to baselig %s => (%s,%s) => (%s,%s)",kind,component.char,start.char,dx,dy,component.xoffset,component.yoffset))
+ end
+ done = true
+ break
+ end
+ end
+ end
+ end
+ last = component
+ component = component.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ -- ok
+ else
+ break
+ end
+ end
+ return last, done
+ end
+ end
+ end
+ return start, false
+ end
+
+ function chainprocs.gpos_mark2mark(start,stop,kind,lookupname,sequence,lookups)
+ local component = start.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ local char = start.char
+ local anchortag = sequence[1][char]
+ if anchortag == true then
+ local classes = otfdata.anchor_classes
+ for k=1,#classes do
+ local v = classes[k]
+ if v.lookup == lookupname and v.type == kind then
+ anchortag = v.name
+ sequence[1][char] = anchortag
+ break
+ end
+ end
+ end
+ local baseattr = has_attribute(start,marknumber)
+ local markattr = has_attribute(component,marknumber)
+ if baseattr == markattr and anchortag ~= true then
+ local glyph = glyphs[characters[char].index]
+ if glyph.anchors and glyph.anchors[anchortag] then
+ local trace = fonts.otf.trace_anchors
+ local last, done = false
+ local baseanchors = glyph.anchors['basemark'][anchortag]
+ while true do
+ local nextchar = component.char
+ local charnext = characters[nextchar]
+ local markanchors = glyphs[charnext.index].anchors['mark'][anchortag]
+ if markanchors then
+ for anchor,data in pairs(markanchors) do
+ local ba = baseanchors[anchor]
+ if ba then
+ local dx = tex.scale(ba.x-data.x, tfmdata.factor)
+ local dy = tex.scale(ba.y-data.y, tfmdata.factor)
+ component.xoffset = start.xoffset - dx
+ component.yoffset = start.yoffset + dy
+ if trace then
+ report("otf chain",format("%s: anchoring mark %s to basemark %s => (%s,%s) => (%s,%s)",kind,component.char,start.char,dx,dy,component.xoffset,component.yoffset))
+ end
+ done = true
+ break
+ end
+ end
+ end
+ last = component
+ component = component.next
+ if component and component.id == glyph and component.font == currentfont and marks[component.char] then
+ markattr = has_attribute(component,marknumber)
+ if baseattr ~= markattr then
+ break
+ end
+ else
+ break
+ end
+ end
+ return last, done
+ end
+ end
+ end
+ return start, false
+ end
+
+ function chainprocs.gpos_cursive(start,stop,kind,lookupname,sequence,lookups)
+ report("otf chain","chainproc gpos_cursive not yet supported")
+ return start
+ end
+ function chainprocs.gpos_single(start,stop,kind,lookupname,sequence,lookups)
+ report("otf process","chainproc gpos_single not yet supported")
+ return start
+ end
+ function chainprocs.gpos_pair(start,stop,kind,lookupname,sequence,lookups)
+ report("otf process","chainproc gpos_pair not yet supported")
+ return start
+ end
+
+ function chainprocs.self(start,stop,kind,lookupname,sequence,lookups)
+ report("otf process","self refering lookup cannot happen")
+ return stop
+ end
+
+ function fonts.otf.features.process.contextchain(start,kind,lookupname,contextdata)
+ local done = false
+ local contexts = contextdata.lookups
+ local flags = contextdata.flags
+ local skipmark, skipligature, skipbase = unpack(flags)
+ for k=1,#contexts do
+ local match, stop = true, start
+ local rule, lookuptype, sequence, before, after, lookups = unpack(contexts[k])
+ if #sequence > 0 then
+ if #sequence == 1 then
+ match = sequence[1][start.char]
+ else -- n = #sequence -> faster
+ for n=1,#sequence do
+ if stop and stop.id == glyph and stop.font == currentfont then
+ local char = stop.char
+ local class = characters[char].class
+ if class == skipmark or class == skipligature or class == skipbase then
+ -- skip 'm
+ elseif sequence[n][char] then
+ if n < #sequence then
+ stop = stop.next
+ end
+ else
+ match = false break
+ end
+ else
+ match = false break
+ end
+ end
+ end
+ end
+ if match and #before > 0 then
+ local prev = start.prev
+ if prev then
+ if #before == 1 then
+ match = prev.id == glyph and prev.font == currentfont and before[1][prev.char]
+ else
+ for n=#before,1 do
+ if prev then
+ if prev.id == glyph and prev.font == currentfont then -- normal char
+ local char = prev.char
+ local class = characters[char].class
+ if class == skipmark or class == skipligature or class == skipbase then
+ -- skip 'm
+ elseif not before[n][char] then
+ match = false break
+ end
+ elseif not before[n][32] then
+ match = false break
+ end
+ prev = prev.prev
+ elseif not before[n][32] then
+ match = false break
+ end
+ end
+ end
+ elseif #before == 1 then
+ match = before[1][32]
+ else
+ for n=#before,1 do
+ if not before[n][32] then
+ match = false break
+ end
+ end
+ end
+ end
+ if match and #after > 0 then
+ local next = stop.next
+ if next then
+ if #after == 1 then
+ match = next.id == glyph and next.font == currentfont and after[1][next.char]
+ else
+ for n=1,#after do
+ if next then
+ if next.id == glyph and next.font == currentfont then -- normal char
+ local char = next.char
+ local class = characters[char].class
+ if class == skipmark or class == skipligature or class == skipbase then
+ -- skip 'm
+ elseif not after[n][char] then
+ match = false break
+ end
+ elseif not after[n][32] then -- brrr
+ match = false break
+ end
+ next = next.next
+ elseif not after[n][32] then
+ match = false break
+ end
+ end
+ end
+ elseif #after == 1 then
+ match = after[1][32]
+ else
+ for n=1,#after do
+ if not after[n][32] then
+ match = false break
+ end
+ end
+ end
+ end
+ if match then
+ local trace = fonts.otf.trace_contexts
+ if trace then
+ report("otf chain",format("%s: rule %s of %s matches %s times at char %s (%s) lookuptype %s",kind,rule,lookupname,#sequence,char,utf.char(char),lookuptype))
+ end
+ if lookups then
+ local cp = chainprocs[lookuptype]
+ if cp then
+ start = cp(start,stop,kind,lookupname,sequence,lookups,flags)
+ else
+ report("otf chain",format("%s: lookuptype %s not supported yet for %s",kind,lookuptype,lookupname))
+ end
+ elseif trace then
+ report("otf chain",format("%s: skipping match for %s",kind,lookupname))
+ end
+ done = true
+ break
+ end
+ end
+ return start, done
+ end
+
+ function fonts.otf.features.process.reversecontextchain(start,kind,lookupname,contextdata)
+ -- there is only a single substitution here so it is a simple case of the normal one
+ -- sequence is one character here and we swap the rest
+ local done = false
+ local contexts = contextdata.lookups
+ local flags = contextdata.flags
+ local skipmark, skipligature, skipbase = unpack(flags)
+ for k=1,#contexts do
+ local match, stop = true, start
+ local rule, lookuptype, sequence, before, after, lookups = unpack(contexts[k])
+ match = sequence[1][start.char]
+ if match and #after > 0 then
+ local prev = start.prev
+ if prev then
+ if #after == 1 then
+ match = prev.id == glyph and prev.font == currentfont and after[1][prev.char]
+ else
+ for n=1,#after do
+ if prev then
+ if prev.id == glyph and prev.font == currentfont then -- normal char
+ local char = prev.char
+ local class = characters[char].class
+ if class == skipmark or class == skipligature or class == skipbase then
+ -- skip 'm
+ elseif not after[n][char] then
+ match = false break
+ end
+ elseif not after[n][32] then
+ match = false break
+ end
+ prev = prev.prev
+ elseif not after[n][32] then
+ match = false break
+ end
+ end
+ end
+ elseif #after == 1 then
+ match = after[1][32]
+ else
+ for n=#after,1 do
+ if not after[n][32] then
+ match = false break
+ end
+ end
+ end
+ end
+ if match and #before > 0 then
+ local next = stop.next
+ if next then
+ if #after == 1 then
+ match = next.id == glyph and next.font == currentfont and before[1][next.char]
+ else
+ for n=#before,1 do
+ if next then
+ if next.id == glyph and next.font == currentfont then -- normal char
+ local char = next.char
+ local class = characters[char].class
+ if class == skipmark or class == skipligature or class == skipbase then
+ -- skip 'm
+ elseif not before[n][char] then
+ match = false break
+ end
+ elseif not before[n][32] then -- brrr
+ match = false break
+ end
+ next = next.next
+ elseif not before[n][32] then
+ match = false break
+ end
+ end
+ end
+ elseif #before == 1 then
+ match = before[1][32]
+ else
+ for n=1,#before do
+ if not before[n][32] then
+ match = false break
+ end
+ end
+ end
+ end
+ if match then
+ local trace = fonts.otf.trace_contexts
+ if trace then
+ report("otf reverse chain",format("%s: rule %s of %s matches %s times at char %s (%s) lookuptype %s",kind,rule,lookupname,#sequence,char,utf.char(char),lookuptype))
+ end
+ if lookups then
+ local cp = chainprocs[lookuptype]
+ if cp then
+ start = cp(start,stop,kind,lookupname,sequence,lookups,flags)
+ else
+ report("otf reverse chain",format("%s: lookuptype %s not supported yet for %s",kind,lookuptype,lookupname))
+ end
+ elseif trace then
+ report("otf reverse chain",format("%s: skipping match for %s",kind,lookupname))
+ end
+ done = true
+ break
+ end
+ end
+ return start, done
+ end
+
+ fonts.otf.features.process.gsub_context = fonts.otf.features.process.contextchain
+ fonts.otf.features.process.gsub_contextchain = fonts.otf.features.process.contextchain
+ fonts.otf.features.process.gsub_reversecontextchain = fonts.otf.features.process.reversecontextchain
+
+ fonts.otf.features.process.gpos_contextchain = fonts.otf.features.process.contextchain
+ fonts.otf.features.process.gpos_context = fonts.otf.features.process.contextchain
+
+end
+
+-- aalt abvf abvs blwf blwm blws dist falt fwid half halt hwid jalt lfbd ljmo
+-- mset opbd palt pwid qwid rand rtbd rtla ruby size tjmo twid valt vatu vert
+-- vhal vjmo vkna vkrn vpal vrt2
+
+do
+
+ local process = fonts.otf.features.process.feature
+
+ function fonts.methods.node.otf.afrc(head,font) return process(head,font,'afrc') end
+ function fonts.methods.node.otf.akhn(head,font) return process(head,font,'akhn') end
+ function fonts.methods.node.otf.c2pc(head,font) return process(head,font,'c2pc') end
+ function fonts.methods.node.otf.c2sc(head,font) return process(head,font,'c2sc') end
+ function fonts.methods.node.otf.calt(head,font) return process(head,font,'calt') end
+ function fonts.methods.node.otf.case(head,font) return process(head,font,'case') end
+ function fonts.methods.node.otf.ccmp(head,font) return process(head,font,'ccmp') end
+ function fonts.methods.node.otf.clig(head,font) return process(head,font,'clig') end
+ function fonts.methods.node.otf.cpsp(head,font) return process(head,font,'cpsp') end
+ function fonts.methods.node.otf.cswh(head,font) return process(head,font,'cswh') end
+ function fonts.methods.node.otf.curs(head,font) return process(head,font,'curs') end
+ function fonts.methods.node.otf.dlig(head,font) return process(head,font,'dlig') end
+ function fonts.methods.node.otf.dnom(head,font) return process(head,font,'dnom') end
+ function fonts.methods.node.otf.expt(head,font) return process(head,font,'expt') end
+ function fonts.methods.node.otf.fin2(head,font) return process(head,font,'fin2') end
+ function fonts.methods.node.otf.fin3(head,font) return process(head,font,'fin3') end
+ function fonts.methods.node.otf.fina(head,font) return process(head,font,'fina',3) end
+ function fonts.methods.node.otf.frac(head,font) return process(head,font,'frac') end
+ function fonts.methods.node.otf.haln(head,font) return process(head,font,'haln') end
+ function fonts.methods.node.otf.hist(head,font) return process(head,font,'hist') end
+ function fonts.methods.node.otf.hkna(head,font) return process(head,font,'hkna') end
+ function fonts.methods.node.otf.hlig(head,font) return process(head,font,'hlig') end
+ function fonts.methods.node.otf.hngl(head,font) return process(head,font,'hngl') end
+ function fonts.methods.node.otf.init(head,font) return process(head,font,'init',1) end
+ function fonts.methods.node.otf.isol(head,font) return process(head,font,'isol',4) end
+ function fonts.methods.node.otf.ital(head,font) return process(head,font,'ital') end
+ function fonts.methods.node.otf.jp78(head,font) return process(head,font,'jp78') end
+ function fonts.methods.node.otf.jp83(head,font) return process(head,font,'jp83') end
+ function fonts.methods.node.otf.jp90(head,font) return process(head,font,'jp90') end
+ function fonts.methods.node.otf.kern(head,font) return process(head,font,'kern') end
+ function fonts.methods.node.otf.liga(head,font) return process(head,font,'liga') end
+ function fonts.methods.node.otf.lnum(head,font) return process(head,font,'lnum') end
+ function fonts.methods.node.otf.locl(head,font) return process(head,font,'locl') end
+ function fonts.methods.node.otf.mark(head,font) return process(head,font,'mark') end
+ function fonts.methods.node.otf.med2(head,font) return process(head,font,'med2') end
+ function fonts.methods.node.otf.medi(head,font) return process(head,font,'medi',2) end
+ function fonts.methods.node.otf.mgrk(head,font) return process(head,font,'mgrk') end
+ function fonts.methods.node.otf.mkmk(head,font) return process(head,font,'mkmk') end
+ function fonts.methods.node.otf.nalt(head,font) return process(head,font,'nalt') end
+ function fonts.methods.node.otf.nlck(head,font) return process(head,font,'nlck') end
+ function fonts.methods.node.otf.nukt(head,font) return process(head,font,'nukt') end
+ function fonts.methods.node.otf.numr(head,font) return process(head,font,'numr') end
+ function fonts.methods.node.otf.onum(head,font) return process(head,font,'onum') end
+ function fonts.methods.node.otf.ordn(head,font) return process(head,font,'ordn') end
+ function fonts.methods.node.otf.ornm(head,font) return process(head,font,'ornm') end
+ function fonts.methods.node.otf.pnum(head,font) return process(head,font,'pnum') end
+ function fonts.methods.node.otf.pref(head,font) return process(head,font,'pref') end
+ function fonts.methods.node.otf.pres(head,font) return process(head,font,'pres') end
+ function fonts.methods.node.otf.pstf(head,font) return process(head,font,'pstf') end
+ function fonts.methods.node.otf.rlig(head,font) return process(head,font,'rlig') end
+ function fonts.methods.node.otf.rphf(head,font) return process(head,font,'rphf') end
+ function fonts.methods.node.otf.salt(head,font) return process(head,font,'calt') end
+ function fonts.methods.node.otf.sinf(head,font) return process(head,font,'sinf') end
+ function fonts.methods.node.otf.smcp(head,font) return process(head,font,'smcp') end
+ function fonts.methods.node.otf.smpl(head,font) return process(head,font,'smpl') end
+ function fonts.methods.node.otf.ss01(head,font) return process(head,font,'ss01') end
+ function fonts.methods.node.otf.ss02(head,font) return process(head,font,'ss02') end
+ function fonts.methods.node.otf.ss03(head,font) return process(head,font,'ss03') end
+ function fonts.methods.node.otf.ss04(head,font) return process(head,font,'ss04') end
+ function fonts.methods.node.otf.ss05(head,font) return process(head,font,'ss05') end
+ function fonts.methods.node.otf.ss06(head,font) return process(head,font,'ss06') end
+ function fonts.methods.node.otf.ss07(head,font) return process(head,font,'ss07') end
+ function fonts.methods.node.otf.ss08(head,font) return process(head,font,'ss08') end
+ function fonts.methods.node.otf.ss09(head,font) return process(head,font,'ss09') end
+ function fonts.methods.node.otf.subs(head,font) return process(head,font,'subs') end
+ function fonts.methods.node.otf.sups(head,font) return process(head,font,'sups') end
+ function fonts.methods.node.otf.swsh(head,font) return process(head,font,'swsh') end
+ function fonts.methods.node.otf.titl(head,font) return process(head,font,'titl') end
+ function fonts.methods.node.otf.tnam(head,font) return process(head,font,'tnam') end
+ function fonts.methods.node.otf.tnum(head,font) return process(head,font,'tnum') end
+ function fonts.methods.node.otf.trad(head,font) return process(head,font,'trad') end
+ function fonts.methods.node.otf.unic(head,font) return process(head,font,'unic') end
+ function fonts.methods.node.otf.zero(head,font) return process(head,font,'zero') end
+
+end
+
+--~ function fonts.initializers.node.otf.install(feature,attribute)
+--~ function fonts.initializers.node.otf[feature](tfm,value) return fonts.otf.features.prepare.feature(tfm,feature,value) end
+--~ function fonts.methods.node.otf[feature] (head,font) return fonts.otf.features.process.feature(head,font,feature,attribute) end
+--~ end
+
+-- common stuff
+
+function fonts.otf.features.language(tfm,value)
+ if value then
+ value = value:lower()
+ if fonts.otf.tables.languages[value] then
+ tfm.language = value
+ end
+ end
+end
+
+function fonts.otf.features.script(tfm,value)
+ if value then
+ value = value:lower()
+ if fonts.otf.tables.scripts[value] then
+ tfm.script = value
+ end
+ end
+end
+
+function fonts.otf.features.mode(tfm,value)
+ if value then
+ tfm.mode = value:lower()
+ end
+end
+
+fonts.initializers.base.otf.language = fonts.otf.features.language
+fonts.initializers.base.otf.script = fonts.otf.features.script
+fonts.initializers.base.otf.mode = fonts.otf.features.mode
+fonts.initializers.base.otf.method = fonts.otf.features.mode
+
+fonts.initializers.node.otf.language = fonts.otf.features.language
+fonts.initializers.node.otf.script = fonts.otf.features.script
+fonts.initializers.node.otf.mode = fonts.otf.features.mode
+fonts.initializers.node.otf.method = fonts.otf.features.mode
+
+fonts.initializers.node.otf.trep = fonts.initializers.base.otf.trep
+fonts.initializers.node.otf.tlig = fonts.initializers.base.otf.tlig
+fonts.initializers.node.otf.texquotes = fonts.initializers.base.otf.texquotes
+fonts.initializers.node.otf.texligatures = fonts.initializers.base.otf.texligatures
+
+-- we need this because fonts can be bugged
+
+-- \definefontfeature[calt][language=nld,script=latn,mode=node,calt=yes,clig=yes,rlig=yes]
+-- \definefontfeature[dflt][language=nld,script=latn,mode=node,calt=no, clig=yes,rlig=yes]
+-- \definefontfeature[fixd][language=nld,script=latn,mode=node,calt=no, clig=yes,rlig=yes,ignoredrules={44,45,47}]
+
+-- \starttext
+
+-- {\type{dflt:}\font\test=ZapfinoExtraLTPro*dflt at 24pt \test \char57777\char57812 c/o} \endgraf
+-- {\type{calt:}\font\test=ZapfinoExtraLTPro*calt at 24pt \test \char57777\char57812 c/o} \endgraf
+-- {\type{fixd:}\font\test=ZapfinoExtraLTPro*fixd at 24pt \test \char57777\char57812 c/o} \endgraf
+
+-- \stoptext
+
+--~ table.insert(fonts.triggers,"ignoredrules")
+
+--~ function fonts.initializers.node.otf.ignoredrules(tfmdata,value)
+--~ if value then
+--~ -- these tests must move !
+--~ tfmdata.unique = tfmdata.unique or { }
+--~ tfmdata.unique.ignoredrules = tfmdata.unique.ignoredrules or { }
+--~ local ignored = tfmdata.unique.ignoredrules
+--~ -- value is already ok now
+--~ for s in string.gmatch(value:gsub("[{}]","")..",", "%s*(.-),") do
+--~ ignored[tonumber(s)] = true
+--~ end
+--~ end
+--~ end
+
+fonts.initializers.base.otf.equaldigits = fonts.initializers.common.equaldigits
+fonts.initializers.node.otf.equaldigits = fonts.initializers.common.equaldigits
+
+fonts.initializers.base.otf.lineheight = fonts.initializers.common.lineheight
+fonts.initializers.node.otf.lineheight = fonts.initializers.common.lineheight
+
+-- temp hack, may change
+
+function fonts.initializers.base.otf.kern(tfmdata,value)
+ fonts.otf.features.prepare_base_kerns(tfmdata,'kern',value)
+end
+
+--~ fonts.initializers.node.otf.kern = fonts.initializers.base.otf.kern
+
+-- there is no real need to register features here, only the defaults; supported
+-- features are part of the font data
+
+-- fonts.otf.features.register('tlig',true)
+-- fonts.otf.features.register('liga',true)
+-- fonts.otf.features.register('kern',true)
+
+-- bonus function
+
+function fonts.otf.name_to_slot(name) -- todo: afm en tfm
+ local tfmdata = fonts.tfm.id[font.current()]
+ if tfmdata and tfmdata.shared then
+ local otfdata = tfmdata.shared.otfdata
+ if otfdata and otfdata.luatex then
+ return otfdata.luatex.unicodes[name]
+ end
+ end
+ return nil
+end
+
+function fonts.otf.char(n) -- todo: afm en tfm
+ if type(n) == "string" then
+ n = fonts.otf.name_to_slot(n)
+ end
+ if n then
+ tex.sprint(tex.ctxcatcodes,string.format("\\char%s ",n))
+ end
+end
+
+--~ function fonts.otf.name_to_table(name)
+--~ lcoal temp, result = { }
+--~ local tfmdata = fonts.tfm.id[font.current()]
+--~ if tfmdata and tfmdata.shared then
+--~ local otfdata = tfmdata.shared.otfdata
+--~ if otfdata and otfdata.luatex then
+--~ for k,v in pairs(otfdata.glyphs) do
+--~ if v.name:find(name) then
+--~ temp[v.name] = v.unicodeenc
+--~ end
+--~ end
+--~ end
+--~ end
+--~ for k,v in pairs(table.sortedkeys(temp)) do
+--~ result[#result+1] = { v, temp[v] }
+--~ end
+--~ return result
+--~ end
+
+-- Here we plug in some analyzing code
+
+
+do
+
+ local glyph = node.id('glyph')
+ local fontdata = fonts.tfm.id
+ local set_attribute = node.set_attribute
+ local has_attribute = node.has_attribute
+ local state = attributes.numbers['state'] or 100
+
+ -- in the future we will use language/script attributes instead of the
+ -- font related value, but then we also need dynamic features which is
+ -- somewhat slower; and .. we need a chain of them
+
+ function fonts.initializers.node.otf.analyze(tfm,value)
+ local script, language = tfm.script, tfm.language
+ local action = fonts.analyzers.initializers[script]
+ if action then
+ if type(action) == "function" then
+ return action(tfm,value)
+ elseif action[language] then
+ return action[language](tfm,value)
+ end
+ end
+ return nil
+ end
+
+ function fonts.methods.node.otf.analyze(head,font)
+ local tfmdata = fontdata[font]
+ local script, language = fontdata[font].script, fontdata[font].language
+ local action = fonts.analyzers.methods[script]
+ if action then
+ if type(action) == "function" then
+ return action(head,font)
+ elseif action[language] then
+ return action[language](head,font)
+ end
+ end
+ return head, false
+ end
+
+ fonts.otf.features.register("analyze",true) -- we always analyze
+ table.insert(fonts.triggers,"analyze") -- we need a proper function for doing this
+
+ -- latin
+
+ fonts.analyzers.methods.latn = fonts.analyzers.aux.setstate
+
+ -- arab / todo: 0640 tadwil
+
+ local isol = {
+ [0x0621] = true,
+ }
+
+ local isol_fina = {
+ [0x0622] = true, [0x0623] = true, [0x0624] = true, [0x0625] = true, [0x0627] = true, [0x062F] = true,
+ [0x0630] = true, [0x0631] = true, [0x0632] = true,
+ [0x0648] = true,
+ [0xFEF5] = true, [0xFEF7] = true, [0xFEF9] = true, [0xFEFB] = true,
+ }
+
+ local isol_fina_medi_init = {
+ [0x0626] = true, [0x0628] = true, [0x0629] = true, [0x062A] = true, [0x062B] = true, [0x062C] = true, [0x062D] = true, [0x062E] = true,
+ [0x0633] = true, [0x0634] = true, [0x0635] = true, [0x0636] = true, [0x0637] = true, [0x0638] = true, [0x0639] = true, [0x063A] = true,
+ [0x0641] = true, [0x0642] = true, [0x0643] = true, [0x0644] = true, [0x0645] = true, [0x0646] = true, [0x0647] = true, [0x0649] = true, [0x064A] = true,
+ [0x067E] = true,
+ [0x0686] = true,
+ }
+
+ local arab_warned = { }
+
+ local function warning(current,what)
+ local char = current.char
+ if not arab_warned[char] then
+ log.report("analyze",string.format("arab: character %s (0x%04X) has no %s class", char, char, what))
+ arab_warned[char] = true
+ end
+ end
+
+ local fcs = fonts.color.set
+ local fcr = fonts.color.reset
+
+ function fonts.analyzers.methods.nocolor(head,font)
+ for n in nodes.traverse(glyph) do
+ if not font or n.font == font then
+ fcr(n)
+ end
+ end
+ return head, true
+ end
+
+ function fonts.analyzers.methods.arab(head,font) -- maybe make a special version with no trace
+ local characters = fontdata[font].characters
+ local first, last, current, done = nil, nil, head, false
+ local trace = fonts.color.trace
+ --~ local laststate = 0
+ local function finish()
+ if last then
+ if first == last then
+ if isol_fina_medi_init[first.char] or isol_fina[first.char] then
+ set_attribute(first,state,4) -- isol
+ if trace then fcs(first,"font:isol") end
+ else
+ warning(first,"isol")
+ set_attribute(first,state,0) -- error
+ if trace then fcr(first) end
+ end
+ else
+ if isol_fina_medi_init[last.char] or isol_fina[last.char] then -- why isol here ?
+ -- if laststate == 1 or laststate == 2 or laststate == 4 then
+ set_attribute(last,state,3) -- fina
+ if trace then fcs(last,"font:fina") end
+ else
+ warning(last,"fina")
+ set_attribute(last,state,0) -- error
+ if trace then fcr(last) end
+ end
+ end
+ first, last = nil, nil
+ elseif first then
+ -- first and last are either both set so we never com here
+ if isol_fina_medi_init[first.char] or isol_fina[first.char] then
+ set_attribute(first,state,4) -- isol
+ if trace then fcs(first,"font:isol") end
+ else
+ warning(first,"isol")
+ set_attribute(first,state,0) -- error
+ if trace then fcr(first) end
+ end
+ first = nil
+ end
+ --~ laststate = 0
+ end
+ while current do
+ if current.id == glyph and current.font == font then
+ done = true
+ local char = current.char
+ if characters[char].class == "mark" then -- marks are now in components
+ set_attribute(current,state,5) -- mark
+ if trace then fcs(current,"font:mark") end
+ elseif isol[char] then
+ finish()
+ set_attribute(current,state,4) -- isol
+ if trace then fcs(current,"font:isol") end
+ first, last = nil, nil
+ --~ laststate = 0
+ elseif not first then
+ if isol_fina_medi_init[char] then
+ set_attribute(current,state,1) -- init
+ if trace then fcs(current,"font:init") end
+ first, last = first or current, current
+ --~ laststate = 1
+ elseif isol_fina[char] then
+ set_attribute(current,state,4) -- isol
+ if trace then fcs(current,"font:isol") end
+ first, last = nil, nil
+ --~ laststate = 0
+ else -- no arab
+ finish()
+ end
+ elseif isol_fina_medi_init[char] then
+ first, last = first or current, current
+ set_attribute(current,state,2) -- medi
+ if trace then fcs(current,"font:medi") end
+ --~ laststate = 2
+ elseif isol_fina[char] then
+ -- if not laststate == 1 then
+ if not has_attribute(last,state,1) then
+ -- tricky, we need to check what last may be !
+ set_attribute(last,state,2) -- medi
+ if trace then fcs(last,"font:medi") end
+ end
+ set_attribute(current,state,3) -- fina
+ if trace then fcs(current,"font:fina") end
+ first, last = nil, nil
+ --~ laststate = 0
+ elseif char >= 0x0600 and char <= 0x06FF then
+ if trace then fcs(current,"font:rest") end
+ finish()
+ else --no
+ finish()
+ end
+ else
+ finish()
+ end
+ current = current.next
+ end
+ finish()
+ return head, done
+ end
+
+end
+
+-- experimental and will probably change
+
+function fonts.install_feature(type,...)
+ if fonts[type] and fonts[type].install_feature then
+ fonts[type].install_feature(...)
+ end
+end
+function fonts.otf.install_feature(tag)
+ fonts.methods.node.otf [tag] = function(head,font) return fonts.otf.features.process.feature(head,font,tag) end
+ fonts.initializers.node.otf[tag] = function(tfm,value) return fonts.otf.features.prepare.feature(tfm,tag,value) end
+end
+
+
+--~ exclam + quoteleft => exclamdown
+--~ question + quoteleft => questiondown
+
+--~ less + less => guillemotleft
+--~ greater + greater => guillemotright
+
+--~ I + J => IJ
+--~ f + f => ff
+--~ f + i => fi
+--~ f + l => fl
+--~ f + k => fk
+--~ ff + i => ffi
+--~ ff + l => ffl
+--~ i + j => ij
+
+--~ comma + comma => quotedblbase
+--~ quoteleft + quoteleft => quotedblleft
+--~ quoteright + quoteright => quotedblright
+
+--~ hyphen + hyphen => endash
+--~ endash + hyphen => emdash
+