if not modules then modules = { } end modules ["features"] = { version = "2.7", comment = "companion to luaotfload-main.lua", author = "Hans Hagen, Khaled Hosny, Elie Roux, Philipp Gesang", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local type = type local next = next local tonumber = tonumber local tostring = tostring local lpeg = require "lpeg" local lpegmatch = lpeg.match local P = lpeg.P local R = lpeg.R local C = lpeg.C local table = table local tabletohash = table.tohash local tablesort = table.sort local setmetatableindex = table.setmetatableindex local insert = table.insert ---[[ begin included font-ltx.lua ]] --- this appears to be based in part on luatex-fonts-def.lua local fonts = fonts local definers = fonts.definers local handlers = fonts.handlers local fontidentifiers = fonts.hashes and fonts.hashes.identifiers local config = config or { luaotfload = { run = { } } } local as_script = true local normalize = function () end if config.luaotfload.run.live ~= false then normalize = handlers.otf.features.normalize as_script = false end --HH A bit of tuning for definitions. if fonts.constructors then fonts.constructors.namemode = "specification" -- somehow latex needs this (changed name!) => will change into an overload end --[[HH-- tricky: we sort of bypass the parser and directly feed all into the sub parser --HH]]-- function fonts.definers.getspecification(str) return "", str, "", ":", str end local log = luaotfload.log local report = log.report local stringfind = string.find local stringlower = string.lower local stringgsub = string.gsub local stringsub = string.sub local stringformat = string.format local stringis_empty = string.is_empty local mathceil = math.ceil local cmp_by_idx = function (a, b) return a.idx < b.idx end local defined_combos = 0 local handle_combination = function (combo, spec) defined_combos = defined_combos + 1 if not combo [1] then report ("both", 0, "features", "combo %d: Empty font combination requested.", defined_combos) return false end if not fontidentifiers then fontidentifiers = fonts.hashes and fonts.hashes.identifiers end local chain = { } local fontids = { } local n = #combo tablesort (combo, cmp_by_idx) --- pass 1: skim combo and resolve fonts report ("both", 2, "features", "combo %d: combining %d fonts.", defined_combos, n) for i = 1, n do local cur = combo [i] local id = cur.id local idx = cur.idx local fnt = fontidentifiers [id] if fnt then local chars = cur.chars if chars == true then report ("both", 2, "features", " *> %.2d: fallback font %d at rank %d.", i, id, idx) else report ("both", 2, "features", " *> %.2d: include font %d at rank %d (%d items).", i, id, idx, (chars and #chars or 0)) end chain [#chain + 1] = { fnt, chars, idx = idx } fontids [#fontids + 1] = { id = id } else report ("both", 0, "features", " *> %.2d: font %d at rank %d unknown, skipping.", n, id, idx) --- TODO might instead attempt to define the font at this point --- but that’d require some modifications to the syntax end end local nc = #chain if nc == 0 then report ("both", 0, "features", " *> no valid font (of %d) in combination.", n) return false end local basefnt = chain [1] [1] if nc == 1 then report ("both", 0, "features", " *> combination boils down to a single font (%s) \z of %d initially specified; not pursuing this any \z further.", basefnt.fullname, n) return basefnt end local basechar = basefnt.characters local baseprop = basefnt.properties baseprop.name = spec.name baseprop.virtualized = true basefnt.fonts = fontids for i = 2, nc do local cur = chain [i] local fnt = cur [1] local def = cur [2] local src = fnt.characters local cnt = 0 local pickchr = function (uc, unavailable) local chr = src [uc] if unavailable == true and basechar [uc] then --- fallback mode: already known return end if chr then chr.commands = { { "slot", i, uc } } basechar [uc] = chr cnt = cnt + 1 end end if def == true then --> fallback; grab all currently unavailable for uc, _chr in next, src do pickchr (uc, true) end else --> grab only defined range for j = 1, #def do local this = def [j] if type (this) == "number" then report ("both", 2, "features", " *> [%d][%d]: import codepoint U+%.4X", i, j, this) pickchr (this) elseif type (this) == "table" then local lo, hi = unpack (this) report ("both", 2, "features", " *> [%d][%d]: import codepoint range U+%.4X--U+%.4X", i, j, lo, hi) for uc = lo, hi do pickchr (uc) end else report ("both", 0, "features", " *> item no. %d of combination definition \z %d not processable.", j, i) end end end report ("both", 2, "features", " *> font %d / %d: imported %d glyphs into combo.", i, nc, cnt) end spec.lookup = "combo" spec.file = basefnt.filename spec.name = stringformat ("luaotfload<%d>", defined_combos) spec.features = { normal = { spec.specification } } spec.forced = "evl" spec.eval = function () return basefnt end return spec end ---[[ begin excerpt from font-ott.lua ]] local scripts = { ["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", } -- [[ [scripts] ]] local languages = { ["aba" ] = "abaza", ["abk" ] = "abkhazian", ["ach" ] = "acholi", ["acr" ] = "achi", ["ady" ] = "adyghe", ["afk" ] = "afrikaans", ["afr" ] = "afar", ["agw" ] = "agaw", ["aio" ] = "aiton", ["aka" ] = "akan", ["als" ] = "alsatian", ["alt" ] = "altai", ["amh" ] = "amharic", ["ang" ] = "anglo-saxon", ["apph"] = "phonetic transcription—americanist conventions", ["ara" ] = "arabic", ["arg" ] = "aragonese", ["ari" ] = "aari", ["ark" ] = "rakhine", ["asm" ] = "assamese", ["ast" ] = "asturian", ["ath" ] = "athapaskan", ["avr" ] = "avar", ["awa" ] = "awadhi", ["aym" ] = "aymara", ["azb" ] = "torki", ["aze" ] = "azerbaijani", ["bad" ] = "badaga", ["bad0"] = "banda", ["bag" ] = "baghelkhandi", ["bal" ] = "balkar", ["ban" ] = "balinese", ["bar" ] = "bavarian", ["bau" ] = "baulé", ["bbc" ] = "batak toba", ["bbr" ] = "berber", ["bch" ] = "bench", ["bcr" ] = "bible cree", ["bdy" ] = "bandjalang", ["bel" ] = "belarussian", ["bem" ] = "bemba", ["ben" ] = "bengali", ["bgc" ] = "haryanvi", ["bgq" ] = "bagri", ["bgr" ] = "bulgarian", ["bhi" ] = "bhili", ["bho" ] = "bhojpuri", ["bik" ] = "bikol", ["bil" ] = "bilen", ["bis" ] = "bislama", ["bjj" ] = "kanauji", ["bkf" ] = "blackfoot", ["bli" ] = "baluchi", ["blk" ] = "pa'o karen", ["bln" ] = "balante", ["blt" ] = "balti", ["bmb" ] = "bambara (bamanankan)", ["bml" ] = "bamileke", ["bos" ] = "bosnian", ["bpy" ] = "bishnupriya manipuri", ["bre" ] = "breton", ["brh" ] = "brahui", ["bri" ] = "braj bhasha", ["brm" ] = "burmese", ["brx" ] = "bodo", ["bsh" ] = "bashkir", ["bti" ] = "beti", ["bts" ] = "batak simalungun", ["bug" ] = "bugis", ["cak" ] = "kaqchikel", ["cat" ] = "catalan", ["cbk" ] = "zamboanga chavacano", ["ceb" ] = "cebuano", ["cgg" ] = "chiga", ["cha" ] = "chamorro", ["che" ] = "chechen", ["chg" ] = "chaha gurage", ["chh" ] = "chattisgarhi", ["chi" ] = "chichewa (chewa, nyanja)", ["chk" ] = "chukchi", ["chk0"] = "chuukese", ["cho" ] = "choctaw", ["chp" ] = "chipewyan", ["chr" ] = "cherokee", ["chu" ] = "chuvash", ["chy" ] = "cheyenne", ["cmr" ] = "comorian", ["cop" ] = "coptic", ["cor" ] = "cornish", ["cos" ] = "corsican", ["cpp" ] = "creoles", ["cre" ] = "cree", ["crr" ] = "carrier", ["crt" ] = "crimean tatar", ["csb" ] = "kashubian", ["csl" ] = "church slavonic", ["csy" ] = "czech", ["ctg" ] = "chittagonian", ["cuk" ] = "san blas kuna", ["dan" ] = "danish", ["dar" ] = "dargwa", ["dax" ] = "dayi", ["dcr" ] = "woods cree", ["deu" ] = "german", ["dgo" ] = "dogri", ["dgr" ] = "dogri", ["dhg" ] = "dhangu", ["dhv" ] = "divehi (dhivehi, maldivian)", ["diq" ] = "dimli", ["div" ] = "divehi (dhivehi, maldivian)", ["djr" ] = "zarma", ["djr0"] = "djambarrpuyngu", ["dng" ] = "dangme", ["dnj" ] = "dan", ["dnk" ] = "dinka", ["dri" ] = "dari", ["duj" ] = "dhuwal", ["dun" ] = "dungan", ["dzn" ] = "dzongkha", ["ebi" ] = "ebira", ["ecr" ] = "eastern cree", ["edo" ] = "edo", ["efi" ] = "efik", ["ell" ] = "greek", ["emk" ] = "eastern maninkakan", ["eng" ] = "english", ["erz" ] = "erzya", ["esp" ] = "spanish", ["esu" ] = "central yupik", ["eti" ] = "estonian", ["euq" ] = "basque", ["evk" ] = "evenki", ["evn" ] = "even", ["ewe" ] = "ewe", ["fan" ] = "french antillean", ["fan0"] = " fang", ["far" ] = "persian", ["fat" ] = "fanti", ["fin" ] = "finnish", ["fji" ] = "fijian", ["fle" ] = "dutch (flemish)", ["fne" ] = "forest nenets", ["fon" ] = "fon", ["fos" ] = "faroese", ["fra" ] = "french", ["frc" ] = "cajun french", ["fri" ] = "frisian", ["frl" ] = "friulian", ["frp" ] = "arpitan", ["fta" ] = "futa", ["ful" ] = "fulah", ["fuv" ] = "nigerian fulfulde", ["gad" ] = "ga", ["gae" ] = "scottish gaelic (gaelic)", ["gag" ] = "gagauz", ["gal" ] = "galician", ["gar" ] = "garshuni", ["gaw" ] = "garhwali", ["gez" ] = "ge'ez", ["gih" ] = "githabul", ["gil" ] = "gilyak", ["gil0"] = " kiribati (gilbertese)", ["gkp" ] = "kpelle (guinea)", ["glk" ] = "gilaki", ["gmz" ] = "gumuz", ["gnn" ] = "gumatj", ["gog" ] = "gogo", ["gon" ] = "gondi", ["grn" ] = "greenlandic", ["gro" ] = "garo", ["gua" ] = "guarani", ["guc" ] = "wayuu", ["guf" ] = "gupapuyngu", ["guj" ] = "gujarati", ["guz" ] = "gusii", ["hai" ] = "haitian (haitian creole)", ["hal" ] = "halam", ["har" ] = "harauti", ["hau" ] = "hausa", ["haw" ] = "hawaiian", ["hay" ] = "haya", ["haz" ] = "hazaragi", ["hbn" ] = "hammer-banna", ["her" ] = "herero", ["hil" ] = "hiligaynon", ["hin" ] = "hindi", ["hma" ] = "high mari", ["hmn" ] = "hmong", ["hmo" ] = "hiri motu", ["hnd" ] = "hindko", ["ho" ] = "ho", ["hri" ] = "harari", ["hrv" ] = "croatian", ["hun" ] = "hungarian", ["hye" ] = "armenian", ["hye0"] = "armenian east", ["iba" ] = "iban", ["ibb" ] = "ibibio", ["ibo" ] = "igbo", ["ido" ] = "ido", ["ijo" ] = "ijo languages", ["ile" ] = "interlingue", ["ilo" ] = "ilokano", ["ina" ] = "interlingua", ["ind" ] = "indonesian", ["ing" ] = "ingush", ["inu" ] = "inuktitut", ["ipk" ] = "inupiat", ["ipph"] = "phonetic transcription—ipa conventions", ["iri" ] = "irish", ["irt" ] = "irish traditional", ["isl" ] = "icelandic", ["ism" ] = "inari sami", ["ita" ] = "italian", ["iwr" ] = "hebrew", ["jam" ] = "jamaican creole", ["jan" ] = "japanese", ["jav" ] = "javanese", ["jbo" ] = "lojban", ["jii" ] = "yiddish", ["jud" ] = "ladino", ["jul" ] = "jula", ["kab" ] = "kabardian", ["kab0"] = "kabyle", ["kac" ] = "kachchi", ["kal" ] = "kalenjin", ["kan" ] = "kannada", ["kar" ] = "karachay", ["kat" ] = "georgian", ["kaz" ] = "kazakh", ["kde" ] = "makonde", ["kea" ] = "kabuverdianu (crioulo)", ["keb" ] = "kebena", ["kek" ] = "kekchi", ["kge" ] = "khutsuri georgian", ["kha" ] = "khakass", ["khk" ] = "khanty-kazim", ["khm" ] = "khmer", ["khs" ] = "khanty-shurishkar", ["kht" ] = "khamti shan", ["khv" ] = "khanty-vakhi", ["khw" ] = "khowar", ["kik" ] = "kikuyu (gikuyu)", ["kir" ] = "kirghiz (kyrgyz)", ["kis" ] = "kisii", ["kiu" ] = "kirmanjki", ["kjd" ] = "southern kiwai", ["kjp" ] = "eastern pwo karen", ["kkn" ] = "kokni", ["klm" ] = "kalmyk", ["kmb" ] = "kamba", ["kmn" ] = "kumaoni", ["kmo" ] = "komo", ["kms" ] = "komso", ["knr" ] = "kanuri", ["kod" ] = "kodagu", ["koh" ] = "korean old hangul", ["kok" ] = "konkani", ["kom" ] = "komi", ["kon" ] = "kikongo", ["kon0"] = "kongo", ["kop" ] = "komi-permyak", ["kor" ] = "korean", ["kos" ] = "kosraean", ["koz" ] = "komi-zyrian", ["kpl" ] = "kpelle", ["kri" ] = "krio", ["krk" ] = "karakalpak", ["krl" ] = "karelian", ["krm" ] = "karaim", ["krn" ] = "karen", ["krt" ] = "koorete", ["ksh" ] = "kashmiri", ["ksh0"] = "ripuarian", ["ksi" ] = "khasi", ["ksm" ] = "kildin sami", ["ksw" ] = "s’gaw karen", ["kua" ] = "kuanyama", ["kui" ] = "kui", ["kul" ] = "kulvi", ["kum" ] = "kumyk", ["kur" ] = "kurdish", ["kuu" ] = "kurukh", ["kuy" ] = "kuy", ["kyk" ] = "koryak", ["kyu" ] = "western kayah", ["lad" ] = "ladin", ["lah" ] = "lahuli", ["lak" ] = "lak", ["lam" ] = "lambani", ["lao" ] = "lao", ["lat" ] = "latin", ["laz" ] = "laz", ["lcr" ] = "l-cree", ["ldk" ] = "ladakhi", ["lez" ] = "lezgi", ["lij" ] = "ligurian", ["lim" ] = "limburgish", ["lin" ] = "lingala", ["lis" ] = "lisu", ["ljp" ] = "lampung", ["lki" ] = "laki", ["lma" ] = "low mari", ["lmb" ] = "limbu", ["lmo" ] = "lombard", ["lmw" ] = "lomwe", ["lom" ] = "loma", ["lrc" ] = "luri", ["lsb" ] = "lower sorbian", ["lsm" ] = "lule sami", ["lth" ] = "lithuanian", ["ltz" ] = "luxembourgish", ["lua" ] = "luba-lulua", ["lub" ] = "luba-katanga", ["lug" ] = "ganda", ["luh" ] = "luyia", ["luo" ] = "luo", ["lvi" ] = "latvian", ["mad" ] = "madura", ["mag" ] = "magahi", ["mah" ] = "marshallese", ["maj" ] = "majang", ["mak" ] = "makhuwa", ["mal" ] = "malayalam reformed", ["mam" ] = "mam", ["man" ] = "mansi", ["map" ] = "mapudungun", ["mar" ] = "marathi", ["maw" ] = "marwari", ["mbn" ] = "mbundu", ["mch" ] = "manchu", ["mcr" ] = "moose cree", ["mde" ] = "mende", ["mdr" ] = "mandar", ["men" ] = "me'en", ["mer" ] = "meru", ["mfe" ] = "morisyen", ["min" ] = "minangkabau", ["miz" ] = "mizo", ["mkd" ] = "macedonian", ["mkr" ] = "makasar", ["mkw" ] = "kituba", ["mle" ] = "male", ["mlg" ] = "malagasy", ["mln" ] = "malinke", ["mly" ] = "malay", ["mnd" ] = "mandinka", ["mng" ] = "mongolian", ["mni" ] = "manipuri", ["mnk" ] = "maninka", ["mnx" ] = "manx", ["moh" ] = "mohawk", ["mok" ] = "moksha", ["mol" ] = "moldavian", ["mon" ] = "mon", ["mor" ] = "moroccan", ["mos" ] = "mossi", ["mri" ] = "maori", ["mth" ] = "maithili", ["mts" ] = "maltese", ["mun" ] = "mundari", ["mus" ] = "muscogee", ["mwl" ] = "mirandese", ["mww" ] = "hmong daw", ["myn" ] = "mayan", ["mzn" ] = "mazanderani", ["nag" ] = "naga-assamese", ["nah" ] = "nahuatl", ["nan" ] = "nanai", ["nap" ] = "neapolitan", ["nas" ] = "naskapi", ["nau" ] = "nauruan", ["nav" ] = "navajo", ["ncr" ] = "n-cree", ["ndb" ] = "ndebele", ["ndc" ] = "ndau", ["ndg" ] = "ndonga", ["nds" ] = "low saxon", ["nep" ] = "nepali", ["new" ] = "newari", ["nga" ] = "ngbaka", ["ngr" ] = "nagari", ["nhc" ] = "norway house cree", ["nis" ] = "nisi", ["niu" ] = "niuean", ["nkl" ] = "nyankole", ["nko" ] = "n'ko", ["nld" ] = "dutch", ["noe" ] = "nimadi", ["nog" ] = "nogai", ["nor" ] = "norwegian", ["nov" ] = "novial", ["nsm" ] = "northern sami", ["nso" ] = "sotho, northern", ["nta" ] = "northern tai", ["nto" ] = "esperanto", ["nym" ] = "nyamwezi", ["nyn" ] = "norwegian nynorsk", ["oci" ] = "occitan", ["ocr" ] = "oji-cree", ["ojb" ] = "ojibway", ["ori" ] = "odia", ["oro" ] = "oromo", ["oss" ] = "ossetian", ["paa" ] = "palestinian aramaic", ["pag" ] = "pangasinan", ["pal" ] = "pali", ["pam" ] = "pampangan", ["pan" ] = "punjabi", ["pap" ] = "palpa", ["pap0"] = "papiamentu", ["pas" ] = "pashto", ["pau" ] = "palauan", ["pcc" ] = "bouyei", ["pcd" ] = "picard", ["pdc" ] = "pennsylvania german", ["pgr" ] = "polytonic greek", ["phk" ] = "phake", ["pih" ] = "norfolk", ["pil" ] = "filipino", ["plg" ] = "palaung", ["plk" ] = "polish", ["pms" ] = "piemontese", ["pnb" ] = "western panjabi", ["poh" ] = "pocomchi", ["pon" ] = "pohnpeian", ["pro" ] = "provencal", ["ptg" ] = "portuguese", ["pwo" ] = "western pwo karen", ["qin" ] = "chin", ["quc" ] = "k’iche’", ["quh" ] = "quechua (bolivia)", ["quz" ] = "quechua", ["qvi" ] = "quechua (ecuador)", ["qwh" ] = "quechua (peru)", ["raj" ] = "rajasthani", ["rar" ] = "rarotongan", ["rbu" ] = "russian buriat", ["rcr" ] = "r-cree", ["rej" ] = "rejang", ["ria" ] = "riang", ["rif" ] = "tarifit", ["rit" ] = "ritarungo", ["rkw" ] = "arakwal", ["rms" ] = "romansh", ["rmy" ] = "vlax romani", ["rom" ] = "romanian", ["roy" ] = "romany", ["rsy" ] = "rusyn", ["rtm" ] = "rotuman", ["rua" ] = "kinyarwanda", ["run" ] = "rundi", ["rup" ] = "aromanian", ["rus" ] = "russian", ["sad" ] = "sadri", ["san" ] = "sanskrit", ["sas" ] = "sasak", ["sat" ] = "santali", ["say" ] = "sayisi", ["scn" ] = "sicilian", ["sco" ] = "scots", ["sek" ] = "sekota", ["sel" ] = "selkup", ["sga" ] = "old irish", ["sgo" ] = "sango", ["sgs" ] = "samogitian", ["shi" ] = "tachelhit", ["shn" ] = "shan", ["sib" ] = "sibe", ["sid" ] = "sidamo", ["sig" ] = "silte gurage", ["sks" ] = "skolt sami", ["sky" ] = "slovak", ["sla" ] = "slavey", ["slv" ] = "slovenian", ["sml" ] = "somali", ["smo" ] = "samoan", ["sna" ] = "sena", ["sna0"] = "shona", ["snd" ] = "sindhi", ["snh" ] = "sinhala (sinhalese)", ["snk" ] = "soninke", ["sog" ] = "sodo gurage", ["sop" ] = "songe", ["sot" ] = "sotho, southern", ["sqi" ] = "albanian", ["srb" ] = "serbian", ["srd" ] = "sardinian", ["srk" ] = "saraiki", ["srr" ] = "serer", ["ssl" ] = "south slavey", ["ssm" ] = "southern sami", ["stq" ] = "saterland frisian", ["suk" ] = "sukuma", ["sun" ] = "sundanese", ["sur" ] = "suri", ["sva" ] = "svan", ["sve" ] = "swedish", ["swa" ] = "swadaya aramaic", ["swk" ] = "swahili", ["swz" ] = "swati", ["sxt" ] = "sutu", ["sxu" ] = "upper saxon", ["syl" ] = "sylheti", ["syr" ] = "syriac", ["szl" ] = "silesian", ["tab" ] = "tabasaran", ["taj" ] = "tajiki", ["tam" ] = "tamil", ["tat" ] = "tatar", ["tcr" ] = "th-cree", ["tdd" ] = "dehong dai", ["tel" ] = "telugu", ["tet" ] = "tetum", ["tgl" ] = "tagalog", ["tgn" ] = "tongan", ["tgr" ] = "tigre", ["tgy" ] = "tigrinya", ["tha" ] = "thai", ["tht" ] = "tahitian", ["tib" ] = "tibetan", ["tiv" ] = "tiv", ["tkm" ] = "turkmen", ["tmh" ] = "tamashek", ["tmn" ] = "temne", ["tna" ] = "tswana", ["tne" ] = "tundra nenets", ["tng" ] = "tonga", ["tod" ] = "todo", ["tod0"] = "toma", ["tpi" ] = "tok pisin", ["trk" ] = "turkish", ["tsg" ] = "tsonga", ["tua" ] = "turoyo aramaic", ["tul" ] = "tulu", ["tuv" ] = "tuvin", ["tvl" ] = "tuvalu", ["twi" ] = "twi", ["tyz" ] = "tày", ["tzm" ] = "tamazight", ["tzo" ] = "tzotzil", ["udm" ] = "udmurt", ["ukr" ] = "ukrainian", ["umb" ] = "umbundu", ["urd" ] = "urdu", ["usb" ] = "upper sorbian", ["uyg" ] = "uyghur", ["uzb" ] = "uzbek", ["vec" ] = "venetian", ["ven" ] = "venda", ["vit" ] = "vietnamese", ["vol" ] = "volapük", ["vro" ] = "võro", ["wa" ] = "wa", ["wag" ] = "wagdi", ["war" ] = "waray-waray", ["wcr" ] = "west-cree", ["wel" ] = "welsh", ["wlf" ] = "wolof", ["wln" ] = "walloon", ["xbd" ] = "lü", ["xhs" ] = "xhosa", ["xjb" ] = "minjangbal", ["xog" ] = "soga", ["xpe" ] = "kpelle (liberia)", ["yak" ] = "sakha", ["yao" ] = "yao", ["yap" ] = "yapese", ["yba" ] = "yoruba", ["ycr" ] = "y-cree", ["yic" ] = "yi classic", ["yim" ] = "yi modern", ["zea" ] = "zealandic", ["zgh" ] = "standard morrocan tamazigh", ["zha" ] = "zhuang", ["zhh" ] = "chinese, hong kong sar", ["zhp" ] = "chinese phonetic", ["zhs" ] = "chinese simplified", ["zht" ] = "chinese traditional", ["znd" ] = "zande", ["zul" ] = "zulu", ["zza" ] = "zazaki", } --[[ [languages] ]] local 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", ["cfar"] = "conjunct form after ro", ["cjct"] = "conjunct forms", ["clig"] = "contextual ligatures", ["cpct"] = "centered cjk punctuation", ["cpsp"] = "capital spacing", ["cswh"] = "contextual swash", ["curs"] = "cursive positioning", ["dflt"] = "default processing", ["dist"] = "distances", ["dlig"] = "discretionary ligatures", ["dnom"] = "denominators", ["dtls"] = "dotless forms", -- math ["expt"] = "expert forms", ["falt"] = "final glyph alternates", ["fin2"] = "terminal forms #2", ["fin3"] = "terminal forms #3", ["fina"] = "terminal forms", ["flac"] = "flattened accents over capitals", -- math ["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", ["ltra"] = "left-to-right alternates", ["ltrm"] = "left-to-right mirrored 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", ["pkna"] = "proportional kana", ["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", ["rclt"] = "required contextual alternates", ["rkrf"] = "rakar forms", ["rlig"] = "required ligatures", ["rphf"] = "reph form", ["rtbd"] = "right bounds", ["rtla"] = "right-to-left alternates", ["rtlm"] = "right to left math", -- math ["ruby"] = "ruby notation forms", ["salt"] = "stylistic alternates", ["sinf"] = "scientific inferiors", ["size"] = "optical size", ["smcp"] = "small capitals", ["smpl"] = "simplified forms", -- ["ss01"] = "stylistic set 1", -- ["ss02"] = "stylistic set 2", -- ["ss03"] = "stylistic set 3", -- ["ss04"] = "stylistic set 4", -- ["ss05"] = "stylistic set 5", -- ["ss06"] = "stylistic set 6", -- ["ss07"] = "stylistic set 7", -- ["ss08"] = "stylistic set 8", -- ["ss09"] = "stylistic set 9", -- ["ss10"] = "stylistic set 10", -- ["ss11"] = "stylistic set 11", -- ["ss12"] = "stylistic set 12", -- ["ss13"] = "stylistic set 13", -- ["ss14"] = "stylistic set 14", -- ["ss15"] = "stylistic set 15", -- ["ss16"] = "stylistic set 16", -- ["ss17"] = "stylistic set 17", -- ["ss18"] = "stylistic set 18", -- ["ss19"] = "stylistic set 19", -- ["ss20"] = "stylistic set 20", ["ssty"] = "script style", -- math ["stch"] = "stretching glyph decomposition", ["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", ["trep"] = "traditional tex replacements", ["tlig"] = "traditional tex ligatures", ["ss.."] = "stylistic set ..", ["cv.."] = "character variant ..", ["js.."] = "justification ..", ["dv.."] = "devanagari ..", ["ml.."] = "malayalam ..", } --[[ [features] ]] local 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"] = "mathematical centered baseline", ["romn"] = "roman baseline" } --[[ [baselines] ]] local swapped = function (h) local r = { } for k, v in next, h do r[stringgsub(v,"[^a-z0-9]","")] = k -- is already lower end return r end local verbosescripts = swapped(scripts ) local verboselanguages = swapped(languages) local verbosefeatures = swapped(features ) local verbosebaselines = swapped(baselines) ---[[ end excerpt from font-ott.lua ]] --[[doc-- As discussed, we will issue a warning because of incomplete support when one of the scripts below is requested. Reference: https://github.com/lualatex/luaotfload/issues/31 --doc]]-- local support_incomplete = tabletohash({ "deva", "beng", "guru", "gujr", "orya", "taml", "telu", "knda", "mlym", "sinh", }, true) --[[doc-- Which features are active by default depends on the script requested. --doc]]-- --- (string, string) dict -> (string, string) dict local apply_default_features = function (speclist) local default_features = luaotfload.features speclist = speclist or { } speclist[""] = nil --- invalid options stub --- handle language tag local language = speclist.language if language then --- already lowercase at this point language = stringgsub(language, "[^a-z0-9]", "") language = rawget(verboselanguages, language) -- srsly, rawget? or (languages[language] and language) or "dflt" else language = "dflt" end speclist.language = language --- handle script tag local script = speclist.script if script then script = stringgsub(script, "[^a-z0-9]","") script = rawget(verbosescripts, script) or (scripts[script] and script) or "dflt" if support_incomplete[script] then report("log", 0, "features", "Support for the requested script: " .. "%q may be incomplete.", script) end else script = "dflt" end speclist.script = script report("log", 2, "features", "Auto-selecting default features for script: %s.", script) local requested = default_features.defaults[script] if not requested then report("log", 2, "features", "No default features for script %q, falling back to \"dflt\".", script) requested = default_features.defaults.dflt end for feat, state in next, requested do if speclist[feat] == nil then speclist[feat] = state end end for feat, state in next, default_features.global do --- This is primarily intended for setting node --- mode unless “base” is requested, as stated --- in the manual. if speclist[feat] == nil then speclist[feat] = state end end return speclist end local import_values = { --- That’s what the 1.x parser did, not quite as graciously, --- with an array of branch expressions. -- "style", "optsize",--> from slashed notation; handled otherwise { "lookup", false }, { "sub", false }, } local lookup_types = { "anon" , "file", "kpse" , "my" , "name", "path" , "combo" } local select_lookup = function (request) for i=1, #lookup_types do local lookup = lookup_types[i] local value = request[lookup] if value then return lookup, value end end end local supported = { b = "b", i = "i", bi = "bi", aat = false, icu = false, gr = false, } --- (string | (string * string) | bool) list -> (string * number) local handle_slashed = function (modifiers) local style, optsize for i=1, #modifiers do local mod = modifiers[i] if type(mod) == "table" and mod[1] == "optsize" then --> optical size optsize = tonumber(mod[2]) elseif mod == false then --- ignore report("log", 0, "features", "unsupported font option: %s", v) elseif supported[mod] then style = supported[mod] elseif not stringis_empty(mod) then style = stringgsub(mod, "[^%a%d]", "") end end return style, optsize end local extract_subfont do local eof = P(-1) local digit = R"09" --- Theoretically a valid subfont address can be up to ten --- digits long. local sub_expr = P"(" * C(digit^1) * P")" * eof local full_path = C(P(1 - sub_expr)^1) extract_subfont = full_path * sub_expr end --- spec -> spec local handle_request = function (specification) local request = lpegmatch(luaotfload.parsers.font_request, specification.specification) ----inspect(request) if not request then --- happens when called with an absolute path --- in an anonymous lookup; --- we try to behave as friendly as possible --- just go with it ... report("log", 1, "features", "invalid request %q of type anon", specification.specification) report("log", 1, "features", "use square bracket syntax or consult the documentation.") --- The result of \fontname must be re-feedable into \font --- which is expected by the Latex font mechanism. Now this --- is complicated with TTC fonts that need to pass the --- number of the requested subfont along with the file name. --- Thus we test whether the request is a bare path only or --- ends in a subfont expression (decimal digits inside --- parentheses). --- https://github.com/lualatex/luaotfload/issues/57 local fullpath, sub = lpegmatch(extract_subfont, specification.specification) if fullpath and sub then specification.sub = tonumber(sub) specification.name = fullpath else specification.name = specification.specification end specification.lookup = "path" return specification end local lookup, name = select_lookup (request) if lookup == "combo" then return handle_combination (request.combo, specification) end request.features = apply_default_features(request.features) if name then if lookup == "file" then local suffix = file.suffix (name) specification.forcedname = name specification.forced = suffix name = file.removesuffix (name) end specification.name = name specification.lookup = lookup or specification.lookup end if request.modifiers then local style, optsize = handle_slashed(request.modifiers) specification.style, specification.optsize = style, optsize end for n=1, #import_values do local feat = import_values[n][1] local keep = import_values[n][2] local newvalue = request.features[feat] if newvalue then specification[feat] = request.features[feat] if not keep then request.features[feat] = nil end end end --- The next line sets the “rand” feature to “random”; I haven’t --- investigated it any further (luatex-fonts-ext), so it will --- just stay here. local features = specification.features if not features then features = { } specification.features = features end features.normal = normalize (request.features) local subfont = tonumber (specification.sub) if subfont and subfont >= 0 then specification.sub = subfont + 1 end return specification end fonts.names.handle_request = handle_request if as_script == true then --- skip the remainder of the file report ("log", 5, "features", "Exiting early from luaotfload-features.lua.") return else local registersplit = definers.registersplit registersplit (":", handle_request, "common") registersplit ("", handle_request, "xetex path style") -- catches \font\text=[names] end ---[[ end included font-ltx.lua ]] -- We assume that the other otf stuff is loaded already; though there’s -- another check below during the initialization phase. ---[[ begin snippet from font-otc.lua ]] local trace_loading = false trackers.register("otf.loading", function(v) trace_loading = v end) local report_otf = logs.reporter("fonts","otf loading") --[[HH-- In the userdata interface we can not longer tweak the loaded font as conveniently as before. For instance, instead of pushing extra data in in the table using the original structure, we now have to operate on the mkiv representation. And as the fontloader interface is modelled after fontforge we cannot change that one too much either. --HH]]-- --- start locals for addfeature() local utf8 = unicode.utf8 local utfbyte = utf8.byte local utflen = utf8.len local otf = handlers and handlers.otf --- filled in later during initialization local normalized = { substitution = "substitution", single = "substitution", ligature = "ligature", alternate = "alternate", multiple = "multiple", kern = "kern", pair = "pair", chainsubstitution = "chainsubstitution", chainposition = "chainposition", } local types = { substitution = "gsub_single", ligature = "gsub_ligature", alternate = "gsub_alternate", multiple = "gsub_multiple", kern = "gpos_pair", pair = "gpos_pair", chainsubstitution = "gsub_contextchain", chainposition = "gpos_contextchain", } setmetatableindex(types, function(t,k) t[k] = k return k end) -- "key" --- stop locals for addfeature() local everywhere = { ["*"] = { ["*"] = true } } -- or: { ["*"] = { "*" } } local noflags = { false, false, false, false } local tohash = table.tohash local function current_addfeature(data,feature,specifications) local descriptions = data.descriptions local resources = data.resources local features = resources.features local sequences = resources.sequences if not features or not sequences then return end -- feature has to be unique but the name entry wins eventually local fontfeatures = resources.features or everywhere local unicodes = resources.unicodes local splitter = lpeg.splitter(" ",unicodes) local done = 0 local skip = 0 local aglunicodes = false if not specifications[1] then -- so we accept a one entry specification specifications = { specifications } end local function tounicode(code) if not code then return end if type(code) == "number" then return code end local u = unicodes[code] if u then return u end if utflen(code) == 1 then u = utfbyte(code) if u then return u end end if not aglunicodes then aglunicodes = fonts.encodings.agl.unicodes -- delayed end return aglunicodes[code] end local coverup = otf.coverup local coveractions = coverup.actions local stepkey = coverup.stepkey local register = coverup.register local function prepare_substitution(list,featuretype) local coverage = { } local cover = coveractions[featuretype] for code, replacement in next, list do local unicode = tounicode(code) local description = descriptions[unicode] if description then if type(replacement) == "table" then replacement = replacement[1] end replacement = tounicode(replacement) if replacement and descriptions[replacement] then cover(coverage,unicode,replacement) done = done + 1 else skip = skip + 1 end else skip = skip + 1 end end return coverage end local function prepare_alternate(list,featuretype) local coverage = { } local cover = coveractions[featuretype] for code, replacement in next, list do local unicode = tounicode(code) local description = descriptions[unicode] if not description then skip = skip + 1 elseif type(replacement) == "table" then local r = { } for i=1,#replacement do local u = tounicode(replacement[i]) r[i] = descriptions[u] and u or unicode end cover(coverage,unicode,r) done = done + 1 else local u = tounicode(replacement) if u then cover(coverage,unicode,{ u }) done = done + 1 else skip = skip + 1 end end end return coverage end local function prepare_multiple(list,featuretype) local coverage = { } local cover = coveractions[featuretype] for code, replacement in next, list do local unicode = tounicode(code) local description = descriptions[unicode] if not description then skip = skip + 1 elseif type(replacement) == "table" then local r, n = { }, 0 for i=1,#replacement do local u = tounicode(replacement[i]) if descriptions[u] then n = n + 1 r[n] = u end end if n > 0 then cover(coverage,unicode,r) done = done + 1 else skip = skip + 1 end else local u = tounicode(replacement) if u then cover(coverage,unicode,{ u }) done = done + 1 else skip = skip + 1 end end end return coverage end local function prepare_ligature(list,featuretype) local coverage = { } local cover = coveractions[featuretype] for code, ligature in next, list do local unicode = tounicode(code) local description = descriptions[unicode] if description then if type(ligature) == "string" then ligature = { lpegmatch(splitter,ligature) } end local present = true for i=1,#ligature do local l = ligature[i] local u = tounicode(l) if descriptions[u] then ligature[i] = u else present = false break end end if present then cover(coverage,unicode,ligature) done = done + 1 else skip = skip + 1 end else skip = skip + 1 end end return coverage end local function prepare_kern(list,featuretype) local coverage = { } local cover = coveractions[featuretype] for code, replacement in next, list do local unicode = tounicode(code) local description = descriptions[unicode] if description and type(replacement) == "table" then local r = { } for k, v in next, replacement do local u = tounicode(k) if u then r[u] = v end end if next(r) then cover(coverage,unicode,r) done = done + 1 else skip = skip + 1 end else skip = skip + 1 end end return coverage end local function prepare_pair(list,featuretype) local coverage = { } local cover = coveractions[featuretype] if cover then for code, replacement in next, list do local unicode = tounicode(code) local description = descriptions[unicode] if description and type(replacement) == "table" then local r = { } for k, v in next, replacement do local u = tounicode(k) if u then r[u] = v end end if next(r) then cover(coverage,unicode,r) done = done + 1 else skip = skip + 1 end else skip = skip + 1 end end else report_otf("unknown cover type %a",featuretype) end return coverage end local function prepare_chain(list,featuretype,sublookups) -- todo: coveractions local rules = list.rules local coverage = { } if rules then local rulehash = { } local rulesize = 0 local sequence = { } local nofsequences = 0 local lookuptype = types[featuretype] for nofrules=1,#rules do local rule = rules[nofrules] local current = rule.current local before = rule.before local after = rule.after local replacements = rule.replacements or false local sequence = { } local nofsequences = 0 if before then for n=1,#before do nofsequences = nofsequences + 1 sequence[nofsequences] = before[n] end end local start = nofsequences + 1 for n=1,#current do nofsequences = nofsequences + 1 sequence[nofsequences] = current[n] end local stop = nofsequences if after then for n=1,#after do nofsequences = nofsequences + 1 sequence[nofsequences] = after[n] end end local lookups = rule.lookups or false local subtype = nil if lookups and sublookups then for k, v in next, lookups do local lookup = sublookups[v] if lookup then lookups[k] = lookup if not subtype then subtype = lookup.type end else -- already expanded end end end if nofsequences > 0 then -- we merge coverage into one -- we copy as we can have different fonts local hashed = { } for i=1,nofsequences do local t = { } local s = sequence[i] for i=1,#s do local u = tounicode(s[i]) if u then t[u] = true end end hashed[i] = t end sequence = hashed -- now we create the rule rulesize = rulesize + 1 rulehash[rulesize] = { nofrules, -- 1 lookuptype, -- 2 sequence, -- 3 start, -- 4 stop, -- 5 lookups, -- 6 (6/7 also signal of what to do) replacements, -- 7 subtype, -- 8 } for unic in next, sequence[start] do local cu = coverage[unic] if not cu then coverage[unic] = rulehash -- can now be done cleaner i think end end end end end return coverage end for s=1,#specifications do local specification = specifications[s] local valid = specification.valid local feature = specification.name or feature if not valid or valid(data,specification,feature) then local initialize = specification.initialize if initialize then -- when false is returned we initialize only once specification.initialize = initialize(specification,data) and initialize or nil end local askedfeatures = specification.features or everywhere local askedsteps = specification.steps or specification.subtables or { specification.data } or { } local featuretype = normalized[specification.type or "substitution"] or "substitution" local featureflags = specification.flags or noflags local featureorder = specification.order or { feature } local featurechain = (featuretype == "chainsubstitution" or featuretype == "chainposition") and 1 or 0 local nofsteps = 0 local steps = { } local sublookups = specification.lookups local category = nil if sublookups then local s = { } for i=1,#sublookups do local specification = sublookups[i] local askedsteps = specification.steps or specification.subtables or { specification.data } or { } local featuretype = normalized[specification.type or "substitution"] or "substitution" local featureflags = specification.flags or noflags local nofsteps = 0 local steps = { } for i=1,#askedsteps do local list = askedsteps[i] local coverage = nil local format = nil if featuretype == "substitution" then coverage = prepare_substitution(list,featuretype) elseif featuretype == "ligature" then coverage = prepare_ligature(list,featuretype) elseif featuretype == "alternate" then coverage = prepare_alternate(list,featuretype) elseif featuretype == "multiple" then coverage = prepare_multiple(list,featuretype) elseif featuretype == "kern" then format = "kern" coverage = prepare_kern(list,featuretype) elseif featuretype == "pair" then format = "pair" coverage = prepare_pair(list,featuretype) end if coverage and next(coverage) then nofsteps = nofsteps + 1 steps[nofsteps] = register(coverage,featuretype,format,feature,nofsteps,descriptions,resources) end end s[i] = { [stepkey] = steps, nofsteps = nofsteps, type = types[featuretype], } end sublookups = s end for i=1,#askedsteps do local list = askedsteps[i] local coverage = nil local format = nil if featuretype == "substitution" then category = "gsub" coverage = prepare_substitution(list,featuretype) elseif featuretype == "ligature" then category = "gsub" coverage = prepare_ligature(list,featuretype) elseif featuretype == "alternate" then category = "gsub" coverage = prepare_alternate(list,featuretype) elseif featuretype == "multiple" then category = "gsub" coverage = prepare_multiple(list,featuretype) elseif featuretype == "kern" then category = "gpos" format = "kern" coverage = prepare_kern(list,featuretype) elseif featuretype == "pair" then category = "gpos" format = "pair" coverage = prepare_pair(list,featuretype) elseif featuretype == "chainsubstitution" then category = "gsub" coverage = prepare_chain(list,featuretype,sublookups) elseif featuretype == "chainposition" then category = "gpos" coverage = prepare_chain(list,featuretype,sublookups) else report_otf("not registering feature %a, unknown category",feature) return end if coverage and next(coverage) then nofsteps = nofsteps + 1 steps[nofsteps] = register(coverage,featuretype,format,feature,nofsteps,descriptions,resources) end end if nofsteps > 0 then -- script = { lang1, lang2, lang3 } or script = { lang1 = true, ... } for k, v in next, askedfeatures do if v[1] then askedfeatures[k] = tohash(v) end end if featureflags[1] then featureflags[1] = "mark" end if featureflags[2] then featureflags[2] = "ligature" end if featureflags[3] then featureflags[3] = "base" end local sequence = { chain = featurechain, features = { [feature] = askedfeatures }, flags = featureflags, name = feature, -- redundant order = featureorder, [stepkey] = steps, nofsteps = nofsteps, type = types[featuretype], } -- todo : before|after|index if specification.prepend then insert(sequences,1,sequence) else insert(sequences,sequence) end -- register in metadata (merge as there can be a few) local features = fontfeatures[category] if not features then features = { } fontfeatures[category] = features end local k = features[feature] if not k then k = { } features[feature] = k end -- for script, languages in next, askedfeatures do local kk = k[script] if not kk then kk = { } k[script] = kk end for language, value in next, languages do kk[language] = value end end end end end if trace_loading then report_otf("registering feature %a, affected glyphs %a, skipped glyphs %a",feature,done,skip) end end ---[[ end snippet from font-otc.lua ]] local tlig_specification = { { type = "substitution", features = everywhere, data = { [0x0022] = 0x201D, -- quotedblright [0x0027] = 0x2019, -- quoteleft [0x0060] = 0x2018, -- quoteright }, flags = noflags, order = { "tlig" }, prepend = true, }, { type = "ligature", features = everywhere, data = { [0x2013] = {0x002D, 0x002D}, -- endash [0x2014] = {0x002D, 0x002D, 0x002D}, -- emdash [0x201C] = {0x2018, 0x2018}, -- quotedblleft [0x201D] = {0x2019, 0x2019}, -- quotedblright [0x00A1] = {0x0021, 0x2018}, -- exclamdown [0x00BF] = {0x003F, 0x2018}, -- questiondown --- next three originate in T1 encoding; Xetex applies --- them too [0x201E] = {0x002C, 0x002C}, -- quotedblbase [0x00AB] = {0x003C, 0x003C}, -- LEFT-POINTING DOUBLE ANGLE QUOTATION MARK [0x00BB] = {0x003E, 0x003E}, -- RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK }, flags = noflags, order = { "tlig" }, prepend = true, }, { type = "ligature", features = everywhere, data = { [0x201C] = {0x0060, 0x0060}, -- quotedblleft [0x201D] = {0x0027, 0x0027}, -- quotedblright [0x00A1] = {0x0021, 0x0060}, -- exclamdown [0x00BF] = {0x003F, 0x0060}, -- questiondown }, flags = noflags, order = { "tlig" }, prepend = true, }, } local anum_arabic = { --- these are the same as in font-otc [0x0030] = 0x0660, [0x0031] = 0x0661, [0x0032] = 0x0662, [0x0033] = 0x0663, [0x0034] = 0x0664, [0x0035] = 0x0665, [0x0036] = 0x0666, [0x0037] = 0x0667, [0x0038] = 0x0668, [0x0039] = 0x0669, } local anum_persian = {--- these are the same as in font-otc [0x0030] = 0x06F0, [0x0031] = 0x06F1, [0x0032] = 0x06F2, [0x0033] = 0x06F3, [0x0034] = 0x06F4, [0x0035] = 0x06F5, [0x0036] = 0x06F6, [0x0037] = 0x06F7, [0x0038] = 0x06F8, [0x0039] = 0x06F9, } local function valid(data) local features = data.resources.features if features then for k, v in next, features do for k, v in next, v do if v.arab then return true end end end end end local anum_specification = { { type = "substitution", features = { arab = { far = true, urd = true, snd = true } }, data = anum_persian, flags = noflags, order = { "anum" }, valid = valid, }, { type = "substitution", features = { arab = { ["*"] = true } }, data = anum_arabic, flags = noflags, order = { "anum" }, valid = valid, }, } local rot13_specification = { type = "substitution", features = everywhere, data = { [65] = 78, [ 97] = 110, [78] = 65, [110] = 97, [66] = 79, [ 98] = 111, [79] = 66, [111] = 98, [67] = 80, [ 99] = 112, [80] = 67, [112] = 99, [68] = 81, [100] = 113, [81] = 68, [113] = 100, [69] = 82, [101] = 114, [82] = 69, [114] = 101, [70] = 83, [102] = 115, [83] = 70, [115] = 102, [71] = 84, [103] = 116, [84] = 71, [116] = 103, [72] = 85, [104] = 117, [85] = 72, [117] = 104, [73] = 86, [105] = 118, [86] = 73, [118] = 105, [74] = 87, [106] = 119, [87] = 74, [119] = 106, [75] = 88, [107] = 120, [88] = 75, [120] = 107, [76] = 89, [108] = 121, [89] = 76, [121] = 108, [77] = 90, [109] = 122, [90] = 77, [122] = 109, }, flags = noflags, order = { "rot13" }, prepend = true, } local extrafeatures = { tlig = { tlig_specification, "tex ligatures and substitutions" }, anum = { anum_specification, "arabic numerals" }, rot13 = { rot13_specification, "rot13" }, } function add_otf_feature (name, specification) if type (name) == "table" then specification = name name = specification.name end if type (name) == "string" then extrafeatures[name] = specification end end otf.addfeature = add_otf_feature local install_extra_features = function (data, filename, raw) local metadata = data and data.metadata if not metadata then logreport ("both", 4, "features", "no metadata received from font “%s”; not \z installing extra features.", filename) return end local format = data.format if not format then logreport ("both", 4, "features", "no format info for font “%s”/“%s”; not \z installing extra features.", fontname, filename) return end for feature, specification in next, extrafeatures do if not fontname then fontname = "" end if not subfont then subfont = -1 end local fontname = tostring (data.metadata.fontname) or "" local subfont = tonumber (metadata.subfontindex) or -1 logreport ("both", 3, "features", "register synthetic feature “%s” for %s font “%s”(%d)", feature, format, fontname, subfont) otf.features.register { name = feature, description = specification[2] } otf.enhancers.addfeature (data, feature, specification[1]) end end return { init = function () logreport = luaotfload.log.report if not fonts and fonts.handlers then logreport ("log", 0, "features", "OTF mechanisms missing -- did you forget to \z load a font loader?") return false end otf = fonts.handlers.otf otf.enhancers.addfeature = current_addfeature otf.enhancers.register ("check extra features", install_extra_features) return true end } -- vim:tw=79:sw=4:ts=4:expandtab