diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/font-otf.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/font-otf.lua | 5599 |
1 files changed, 1197 insertions, 4402 deletions
diff --git a/Master/texmf-dist/tex/context/base/font-otf.lua b/Master/texmf-dist/tex/context/base/font-otf.lua index 5c0db392dba..e8867c53d37 100644 --- a/Master/texmf-dist/tex/context/base/font-otf.lua +++ b/Master/texmf-dist/tex/context/base/font-otf.lua @@ -1,4 +1,4 @@ - if not modules then modules = { } end modules ['font-otf'] = { +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", @@ -6,1170 +6,679 @@ license = "see context related readme files" } --- once we have all features working, i will redo this module .. caching lookups and such +local utf = unicode.utf8 --- the flattening code is a prelude to a more compact table format (so, we're now --- at the fourth version); maybe we will go unicode, although that will mean that we --- miss some glyphs (unicode -1) +local concat, getn, utfbyte = table.concat, table.getn, utf.byte +local format, gmatch, gsub, find, match, lower, strip = string.format, string.gmatch, string.gsub, string.find, string.match, string.lower, string.strip +local type, next, tonumber, tostring = type, next, tonumber, tostring --- todo: featuredata is now indexed by kind,lookup but probably lookup is okay too +local trace_private = false trackers.register("otf.private", function(v) trace_private = v end) +local trace_loading = false trackers.register("otf.loading", function(v) trace_loading = v end) +local trace_features = false trackers.register("otf.features", function(v) trace_features = v end) +local trace_dynamics = false trackers.register("otf.dynamics", function(v) trace_dynamics = v end) +local trace_sequences = false trackers.register("otf.sequences", function(v) trace_sequences = v end) +local trace_math = false trackers.register("otf.math", function(v) trace_math = v end) +local trace_unimapping = false trackers.register("otf.unimapping", function(v) trace_unimapping = v end) --- todo: now that we pack ... resolve strings to unicode points --- todo: unpack already in tmc file, i.e. save tables and return ref''d version --- todo: dependents etc resolve too, maybe even reorder glyphs to unicode --- todo: pack ignoreflags +--~ trackers.enable("otf.loading") --- abvf abvs blwf blws dist falt half halt jalt lfbd ljmo --- mset opbd palt pwid qwid rand rtbd rtla ruby size tjmo twid valt vatu vert --- vhal vjmo vkna vkrn vpal vrt2 - --- The specification of OpenType is vague, very vague. Apart from a lack of proper --- specifications (a free one) there's also the problem that Microsoft and Adobe --- may have their own rules. Anyhow, the following is from Adobe's feature file --- specification: --- --- http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html#6.h --- --- The following is a reference summary of the algorithm used by an OpenType layout --- (OTL) engine to perform substitutions and positionings. The important aspect of --- this for a feature file editor is that each lookup corresponds to one "pass" over --- the glyph run (see step 4 below). Thus, each lookup has as input the accumulated --- result of all previous lookups in the LookupList (whether in the same feature or --- in other features). --- 1. All glyphs in the client's glyph run must belong to the same language --- system. Glyph sequence matching may not occur across language --- systems. Do the following first for the GSUB and then for the GPOS: --- 2. Assemble all features (including any required feature) for the glyph --- run's language system. --- 3. Assemble all lookups in these features, in LookupList order, removing --- any duplicates. All features and thus all lookups needn't be applied to --- every glyph in the run. --- 4. For each lookup: --- 5. For each glyph in the glyph run: --- 6. If the lookup is applied to that glyph and the lookupflag doesn't --- indicate that that glyph is to be ignored: --- 7. For each subtable in the lookup: --- 8. If the subtable's target context is matched: --- 9. Do the glyph substitution or positioning, --- --- OR: --- --- If this is a (chain) contextual lookup do the following --- [(10)-(11)] in the subtable's Subst/PosLookupRecord order: --- 10. For each (sequenceIndex, lookupListIndex) pair: --- 11. Apply lookup[lookupListIndex] at input sequence[sequenceIndex] --- [steps (7)-(11)] --- 12. Goto the glyph after the input sequence matched in (8) --- (i.e. skip any remaining subtables in the lookup). --- The "target context" in step 8 above comprises the input sequence and any --- backtrack and lookahead sequences. --- The input sequence must be matched entirely within the lookup's "application --- range" at that glyph (that contiguous subrun of glyphs including and around --- the current glyph on which the lookup is applied). There is no such restriction --- on the backtrack and lookahead sequences. --- "Matching" includes matching any glyphs designated to be skipped in the --- lookup's LookupFlag. +local zwnj = 0x200C +local zwj = 0x200D --[[ldx-- -<p>This module is sparsely 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> - -<p>Incrementing the version number will force a re-cache. We jump the -number by one when there's a fix in the <l n='fontforge'/> library or -<l n='lua'/> code that results in different tables.</p> +<p>The fontforge table has organized lookups in a certain way. A first implementation +of this code was organized featurewise: information related to features was +collected and processing boiled down to a run over the features. The current +implementation honors the order in the main feature table. Since we can reorder this +table as we want, we can eventually support several models of processing. We kept +the static as well as dynamic feature processing, because it had proved to be +rather useful. The formerly three loop variants have beem discarded but will +reapear at some time.</p> + +<itemize> +<item>we loop over all lookups</item> +<item>for each lookup we do a run over the list of glyphs</item> +<item>but we only process them for features that are enabled</item> +<item>if we're dealing with a contextual lookup, we loop over all contexts</item> +<item>in that loop we quit at a match and then process the list of sublookups</item> +<item>we always continue after the match</item> +</itemize> + +<p>In <l n='context'/> we do this for each font that is used in a list, so in +practice we have quite some nested loops.</p> + +<p>We process the whole list and then consult the glyph nodes. An alternative approach +is to collect strings of characters using the same font including spaces (because some +lookups involve spaces). However, we then need to reconstruct the list which is no fun. +Also, we need to carry quite some information, like attributes, so eventually we don't +gain much (if we gain something at all).</p> + +<p>Another consideration has been to operate on sublists (subhead, subtail) but again +this would complicate matters as we then neext to keep track of a changing subhead +and subtail. On the other hand, this might save some runtime. The number of changes +involved is not that large. This only makes sense when we have many fonts in a list +and don't change to frequently.</p> --ldx]]-- ---~ The node based processing functions look quite complex which is mainly due to ---~ the fact that we need to share data and cache resolved issues (saves much memory and ---~ is also faster). A further complication is that we support static as well as dynamic ---~ features. +fonts = fonts or { } +fonts.otf = fonts.otf or { } +fonts.tfm = fonts.tfm or { } -fonts = fonts or { } -fonts.otf = fonts.otf or { } -fonts.otf.version = 2.09 -fonts.otf.pack = true -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_set_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.trace_cursive = false -fonts.otf.notdef = false -fonts.otf.cache = containers.define("fonts", "otf", fonts.otf.version, true) +local otf = fonts.otf +local tfm = fonts.tfm -function fonts.otf.trace_process() - fonts.otf.trace_replacements = true - fonts.otf.trace_contexts = true - fonts.otf.trace_anchors = true - fonts.otf.trace_ligatures = true - fonts.otf.trace_kerns = true - fonts.otf.trace_cursive = true -end +local fontdata = fonts.ids ---[[ldx-- -<p>We start with a lot of tables and related functions.</p> ---ldx]]-- +otf.tables = otf.tables or { } -- defined in font-ott.lua +otf.meanings = otf.meanings or { } -- defined in font-ott.lua +otf.tables.features = otf.tables.features or { } -- defined in font-ott.lua +otf.tables.languages = otf.tables.languages or { } -- defined in font-ott.lua +otf.tables.scripts = otf.tables.scripts or { } -- defined in font-ott.lua -fonts.otf.tables.scripts = { - ['dflt'] = 'Default', +otf.features = otf.features or { } +otf.features.list = otf.features.list or { } +otf.features.default = otf.features.default or { } - ['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' -} +otf.enhancers = otf.enhancers or { } +otf.glists = { "gsub", "gpos" } -fonts.otf.tables.languages = { - ['dflt'] = 'Default', +otf.version = 2.628 -- beware: also sync font-mis.lua +otf.pack = true -- beware: also sync font-mis.lua +otf.syncspace = true +otf.notdef = false +otf.cache = containers.define("fonts", "otf", otf.version, true) +otf.cleanup_aat = false -- only context - ['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' -} +--[[ldx-- +<p>We start with a lot of tables and related functions.</p> +--ldx]]-- -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'] = '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', - ['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' +otf.tables.global_fields = table.tohash { + "lookups", + "glyphs", + "subfonts", + "luatex", + "pfminfo", + "cidinfo", + "tables", + "names", + "unicodes", + "names", + "anchor_classes", + "kern_classes", + "gpos", + "gsub" } -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' +otf.tables.valid_fields = { + "anchor_classes", + "ascent", + "cache_version", + "cidinfo", + "copyright", + "creationtime", + "descent", + "design_range_bottom", + "design_range_top", + "design_size", + "encodingchanged", + "extrema_bound", + "familyname", + "fontname", + "fontstyle_id", + "fontstyle_name", + "fullname", + "glyphs", + "hasvmetrics", + "head_optimized_for_cleartype", + "horiz_base", + "issans", + "isserif", + "italicangle", + "kerns", + "lookups", + -- "luatex", + "macstyle", + "modificationtime", + "onlybitmaps", + "origname", + "os2_version", + "pfminfo", + "private", + "serifcheck", + "sfd_version", + -- "size", + "strokedfont", + "strokewidth", + "subfonts", + "table_version", + -- "tables", + -- "ttf_tab_saved", + "ttf_tables", + "uni_interp", + "uniqueid", + "units_per_em", + "upos", + "use_typo_metrics", + "uwidth", + "validation_state", + "verbose", + "version", + "vert_base", + "weight", + "weight_width_slope_only", + "xuid", } -function fonts.otf.tables.to_tag(id) - return stringformat("%4s",id:lower()) -end +--[[ldx-- +<p>Here we go.</p> +--ldx]]-- -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" +local function load_featurefile(ff,featurefile) + if featurefile then + featurefile = resolvers.find_file(file.addsuffix(featurefile,'fea')) -- "FONTFEATURES" + if featurefile and featurefile ~= "" then + if trace_loading then + logs.report("load otf", "featurefile: %s", featurefile) + end + fontloader.apply_featurefile(ff, featurefile) + end 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 - -fonts.otf.tables.to_scripts = table.reverse_hash(fonts.otf.tables.scripts ) -fonts.otf.tables.to_languages = table.reverse_hash(fonts.otf.tables.languages) -fonts.otf.tables.to_features = table.reverse_hash(fonts.otf.tables.features ) - -do - - local scripts = fonts.otf.tables.scripts - local languages = fonts.otf.tables.languages - local features = fonts.otf.tables.features - - local to_scripts = fonts.otf.tables.to_scripts - local to_languages = fonts.otf.tables.to_languages - local to_features = fonts.otf.tables.to_features - - function fonts.otf.meanings.normalize(features) - local h = { } - for k,v in pairs(features) do - k = k:lower() -- :gsub("[^a-z0-9%-%.]" -- not needed - if k == "language" or k == "lang" then - v = (v:lower()):gsub("[^a-z0-9%-]","") - k = language - if not languages[v] then - h.language = to_languages[v] or "dflt" - else - h.language = v - end - elseif k == "script" then - v = (v:lower()):gsub("[^a-z0-9%-]","") - if not scripts[v] then - h.script = to_scripts[v] or "dflt" - else - h.script = v - end - else - if type(v) == "string" then - local b = v:is_boolean() - if type(b) == "nil" then - v = tonumber(v) or v:lower() -- gsub("[^a-z0-9%-]") -- too dangerous, e.g. featurefiles - else - v = b - end - end - h[to_features[k] or k] = v - end +function otf.enhance(name,data,filename,verbose) + local enhancer = otf.enhancers[name] + if enhancer then + if (verbose ~= nil and verbose) or trace_loading then + logs.report("load otf","enhance: %s",name) end - return h + enhancer(data,filename) end - end ---[[ldx-- -<p>Here we go.</p> ---ldx]]-- - -fonts.otf.enhance = fonts.otf.enhance or { } -fonts.otf.enhance.add_kerns = true - -fonts.otf.featurefiles = { ---~ "texhistoric.fea" +local enhancers = { + -- pack and unpack are handled separately; they might even be moved + -- away from the enhancers namespace + "patch bugs", + "merge cid fonts", "prepare unicode", "cleanup ttf tables", "compact glyphs", "reverse coverage", + "cleanup aat", "enrich with features", "add some missing characters", + "reorganize kerns", -- moved here + "flatten glyph lookups", "flatten anchor tables", "flatten feature tables", + "prepare luatex tables", + "analyse features", "rehash features", + "analyse anchors", "analyse marks", "analyse unicodes", "analyse subtables", + "check italic correction","check math", + "share widths", + "strip not needed data", + "migrate metadata", } -function fonts.otf.load(filename,format,sub,featurefile) +function otf.load(filename,format,sub,featurefile) local name = file.basename(file.removesuffix(filename)) if featurefile then name = name .. "@" .. file.removesuffix(file.basename(featurefile)) end if sub == "" then sub = false end local hash = name - if sub then -- name cleanup will move to cache code + if sub then hash = hash .. "-" .. sub - hash = hash:lower() - hash = hash:gsub("[^%w%d]+","-") end - local data = containers.read(fonts.otf.cache(), hash) + hash = containers.cleanname(hash) + local data = containers.read(otf.cache(), hash) local size = lfs.attributes(filename,"size") or 0 - if data and data.size ~= size then - data = nil - end - if not data then - logs.report("load otf","loading: " .. filename) + if not data or data.verbose ~= fonts.verbose or data.size ~= size then + logs.report("load otf","loading: %s",filename) local ff, messages if sub then - ff, messages = fontforge.open(filename,sub) + ff, messages = fontloader.open(filename,sub) else - ff, messages = fontforge.open(filename) + ff, messages = fontloader.open(filename) end - if messages and #messages > 0 then - for _, m in ipairs(messages) do - logs.report("load otf","warning: " .. m) + if trace_loading and messages and #messages > 0 then + for m=1,#messages do + logs.report("load otf","warning: %s",messages[m]) end end if ff then - local function load_featurefile(featurefile) - 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 - end - -- for _, featurefile in pairs(fonts.otf.featurefiles) do - -- load_featurefile(featurefile) - -- end - load_featurefile(featurefile) - data = fontforge.to_table(ff) - fontforge.close(ff) + load_featurefile(ff,featurefile) + data = fontloader.to_table(ff) + fontloader.close(ff) if data then - logs.report("load otf","enhance: before") - fonts.otf.enhance.before(data,filename) - logs.report("load otf","enhance: enrich") - fonts.otf.enhance.enrich(data,filename) - logs.report("load otf","enhance: flatten") - fonts.otf.enhance.flatten(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","enhance: patch") - fonts.otf.enhance.patch(data,filename) - logs.report("load otf","enhance: strip") - fonts.otf.enhance.strip(data,filename) - if fonts.otf.pack then - logs.report("load otf","enhance: pack") - fonts.otf.enhance.pack(data) + logs.report("load otf","file size: %s", size) + logs.report("load otf","enhancing ...") + for e=1,#enhancers do + otf.enhance(enhancers[e],data,filename) + end + if otf.pack and not fonts.verbose then + otf.enhance("pack",data,filename) end - logs.report("load otf","file size: " .. size) data.size = size - logs.report("load otf","saving: in cache") - data = containers.write(fonts.otf.cache(), hash, data) + data.verbose = fonts.verbose + logs.report("load otf","saving in cache: %s",filename) + data = containers.write(otf.cache(), hash, data) + collectgarbage("collect") + data = containers.read(otf.cache(), hash) -- this frees the old table and load the sparse one + collectgarbage("collect") else - logs.error("load otf","loading failed (table conversion error)") + logs.report("load otf","loading failed (table conversion error)") end else - logs.error("load otf","loading failed (file read error)") + logs.report("load otf","loading failed (file read error)") + end + end + if data then + otf.enhance("unpack",data,filename,false) -- no message here + otf.add_dimensions(data) + if trace_sequences then + otf.show_feature_order(data,filename) end end - fonts.otf.enhance.unpack(data) return data end --- memory saver .. - -function fonts.otf.enhance.pack(data) +function otf.add_dimensions(data) + -- todo: forget about the width if it's the defaultwidth (saves mem) + -- we could also build the marks hash here (instead of storing it) if data then - local h, t = { }, { } - local concat = table.concat - local function tabstr(t) - for i=1,#t do - if type(t[i]) == "boolean" then - local s = tostring(t[1]) - for i=2,#t do - s = s .. ",".. tostring(t[i]) - end - return s - end - end - return concat(t,",") - end - local function pack(v) - local tag = tabstr(v,",") - if not h[tag] then - t[#t+1] = v - h[tag] = #t + local force = otf.notdef + local luatex = data.luatex + local defaultwidth = luatex.defaultwidth or 0 + local defaultheight = luatex.defaultheight or 0 + local defaultdepth = luatex.defaultdepth or 0 + for _, d in next, data.glyphs do + local bb, wd = d.boundingbox, d.width + if not wd then + d.width = defaultwidth + elseif wd ~= 0 and d.class == "mark" then + d.width = -wd end - return h[tag] - end - for k, v in pairs(data.glyphs) do - v.boundingbox = pack(v.boundingbox) - if v.lookups then - for k,v in pairs(v.lookups) do - for kk, vv in ipairs(v) do - v[kk] = pack(vv) - end - end + if force and not d.name then + d.name = ".notdef" end - end - if data.lookups then - for k, v in pairs(data.lookups) do - if v.rules then - for kk, vv in pairs(v.rules) do - local l = vv.lookups - if l then - vv.lookups = pack(l) - end - local c = vv.coverage - if c then - c.before = c.before and pack(c.before ) - c.after = c.after and pack(c.after ) - c.current = c.current and pack(c.current) - end - end + if bb then + local ht, dp = bb[4], -bb[2] + if ht == 0 or ht < 0 then + -- no need to set it and no negative heights, nil == 0 + else + d.height = ht end - end - end - if data.luatex then - local li = data.luatex.ignore_flags - if li then - for k, v in pairs(li) do - li[k] = pack(v) + if dp == 0 or dp < 0 then + -- no negative depths and no negative depths, nil == 0 + else + d.depth = dp end end end - if #t > 0 then - data.tables = t - end end end -function fonts.otf.enhance.unpack(data) - if data then - local t = data.tables - if t then - for k, v in pairs(data.glyphs) do - v.boundingbox = t[v.boundingbox] - local l = v.lookups - if l then - for k,v in pairs(l) do - for i=1,#v do - v[i] = t[v[i]] - end - end - end +function otf.show_feature_order(otfdata,filename) + local sequences = otfdata.luatex.sequences + if sequences and #sequences > 0 then + if trace_loading then + logs.report("otf check","font %s has %s sequences",filename,#sequences) + logs.report("otf check"," ") + end + for nos=1,#sequences do + local sequence = sequences[nos] + local typ = sequence.type or "no-type" + local name = sequence.name or "no-name" + local subtables = sequence.subtables or { "no-subtables" } + local features = sequence.features + if trace_loading then + logs.report("otf check","%3i %-15s %-20s [%s]",nos,name,typ,concat(subtables,",")) end - if data.lookups then - for k, v in pairs(data.lookups) do - local r = v.rules - if r then - for kk, vv in pairs(r) do - local l = vv.lookups - if l then - vv.lookups = t[l] - end - local c = vv.coverage - if c then - local cc = c.before if cc then c.before = t[cc] end - cc = c.after if cc then c.after = t[cc] end - cc = c.current if cc then c.current = t[cc] end - end + if features then + for feature, scripts in next, features do + local tt = { } + for script, languages in next, scripts do + local ttt = { } + for language, _ in next, languages do + ttt[#ttt+1] = language end + tt[#tt+1] = format("[%s: %s]",script,concat(ttt," ")) end - end - end - if data.luatex then - local li = data.luatex.ignore_flags - if li then - for k, v in pairs(li) do - li[k] = t[v] + if trace_loading then + logs.report("otf check"," %s: %s",feature,concat(tt," ")) end end end - data.tables = nil end + if trace_loading then + logs.report("otf check","\n") + end + elseif trace_loading then + logs.report("otf check","font %s has no sequences",filename) end end -- todo: normalize, design_size => designsize -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, t.ctx_always = fonts.otf.analyze_subtables(data) - data.luatex = t +otf.enhancers["prepare luatex tables"] = function(data,filename) + data.luatex = data.luatex or { } + local luatex = data.luatex + luatex.filename = filename + luatex.version = otf.version + luatex.creator = "context mkiv" end -do - -- original string parser: 0.109, lpeg parser: 0.036 seconds for Adobe-CNS1-4.cidmap - -- - -- 18964 18964 (leader) - -- 0 /.notdef - -- 1..95 0020 - -- 99 3000 - - local number = lpeg.C(lpeg.R("09","af","AF")^1) - local space = lpeg.S(" \n\r\t") - local spaces = space^0 - local period = lpeg.P(".") - local periods = period * period - local name = lpeg.P("/") * lpeg.C((1-space)^1) - - local unicodes, names = { }, {} - - local tonumber = tonumber - - local function do_one(a,b) - unicodes[tonumber(a)] = tonumber(b,16) +otf.enhancers["cleanup aat"] = function(data,filename) + if otf.cleanup_aat then end - local function do_range(a,b,c) - c = tonumber(c,16) - for i=tonumber(a),tonumber(b) do - unicodes[i] = c - c = c + 1 +end + +local function analyze_features(g, features) + if g then + local t, done = { }, { } + for k=1,#g do + local f = features or g[k].features + if f then + for k=1,#f do + -- scripts and tag + local tag = f[k].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 - local function do_name(a,b) - names[tonumber(a)] = b - end + return nil +end - local grammar = lpeg.P { "start", - start = number * spaces * number * lpeg.V("series"), - series = (spaces * (lpeg.V("one") + lpeg.V("range") + lpeg.V("named")) )^1, - one = (number * spaces * number) / do_one, - range = (number * periods * number * spaces * number) / do_range, - named = (number * spaces * name) / do_name - } +otf.enhancers["analyse features"] = function(data,filename) + -- local luatex = data.luatex + -- luatex.gposfeatures = analyze_features(data.gpos) + -- luatex.gsubfeatures = analyze_features(data.gsub) +end - function fonts.otf.load_cidmap(filename) -- lpeg - local data = io.loaddata(filename) - if data then - unicodes, names = { }, { } - grammar:match(data) - local supplement, registry, ordering = filename:match("^(.-)%-(.-)%-()%.(.-)$") - return { - supplement = supplement, - registry = registry, - ordering = ordering, - filename = filename, - unicodes = unicodes, - names = names - } - else - return nil +otf.enhancers["rehash features"] = function(data,filename) + local features = { } + data.luatex.features = features + for k, what in next, otf.glists do + local dw = data[what] + if dw then + local f = { } + features[what] = f + for i=1,#dw do + local d= dw[i] + local dfeatures = d.features + if dfeatures then + for i=1,#dfeatures do + local df = dfeatures[i] + local tag = strip(lower(df.tag)) + local ft = f[tag] if not ft then ft = {} f[tag] = ft end + local dscripts = df.scripts + for script, languages in next, dscripts do + script = strip(lower(script)) + local fts = ft[script] if not fts then fts = {} ft[script] = fts end + for i=1,#languages do + fts[strip(lower(languages[i]))] = true + end + end + end + end + end end end +end +otf.enhancers["analyse anchors"] = function(data,filename) + local classes = data.anchor_classes + local luatex = data.luatex + local anchor_to_lookup, lookup_to_anchor = { }, { } + luatex.anchor_to_lookup, luatex.lookup_to_anchor = anchor_to_lookup, lookup_to_anchor + if classes then + for c=1,#classes do + local class = classes[c] + local anchor = class.name + local lookups = class.lookup + if type(lookups) ~= "table" then + lookups = { lookups } + end + local a = anchor_to_lookup[anchor] + if not a then a = { } anchor_to_lookup[anchor] = a end + for l=1,#lookups do + local lookup = lookups[l] + local l = lookup_to_anchor[lookup] + if not l then l = { } lookup_to_anchor[lookup] = l end + l[anchor] = true + a[lookup] = true + end + end + end end -fonts.otf.cidmaps = { } -fonts.otf.cidmax = 10 +otf.enhancers["analyse marks"] = function(data,filename) + local glyphs = data.glyphs + local marks = { } + data.luatex.marks = marks + for unicode, index in next, data.luatex.indices do + local glyph = glyphs[index] + if glyph.class == "mark" then + marks[unicode] = true + end + end +end -function fonts.otf.cidmap(registry,ordering,supplement) - -- cf Arthur R. we can safely scan upwards since cids are downward compatible - local template = "%s-%s-%s.cidmap" - local supplement = tonumber(supplement) - logs.report("load otf",string.format("needed cidmap, registry: %s, ordering: %s, supplement: %s",registry,ordering,supplement)) - local function locate(registry,ordering,supplement) - local filename = string.format(template,registry,ordering,supplement) - local cidmap = fonts.otf.cidmaps[filename] - if not cidmap then - logs.report("load otf",string.format("checking cidmap, registry: %s, ordering: %s, supplement: %s, filename: %s",registry,ordering,supplement,filename)) - local fullname = input.find_file(texmf.instance,filename,'cid') or "" - if fullname ~= "" then - cidmap = fonts.otf.load_cidmap(fullname) - if cidmap then - logs.report("load otf",string.format("using cidmap file %s",filename)) - fonts.otf.cidmaps[filename] = cidmap - return cidmap +local separator = lpeg.S("_.") +local other = lpeg.C((1 - separator)^1) +local ligsplitter = lpeg.Ct(other * (separator * other)^0) + +--~ print(table.serialize(ligsplitter:match("this"))) +--~ print(table.serialize(ligsplitter:match("this.that"))) +--~ print(table.serialize(ligsplitter:match("japan1.123"))) +--~ print(table.serialize(ligsplitter:match("such_so_more"))) +--~ print(table.serialize(ligsplitter:match("such_so_more.that"))) + +otf.enhancers["analyse unicodes"] = function(data,filename) + local tounicode16, tounicode16sequence = fonts.map.tounicode16, fonts.map.tounicode16sequence + local unicodes = data.luatex.unicodes + -- we need to move this code + unicodes['space'] = unicodes['space'] or 32 -- handly later on + unicodes['hyphen'] = unicodes['hyphen'] or 45 -- handly later on + unicodes['zwj'] = unicodes['zwj'] or zwj -- handly later on + unicodes['zwnj'] = unicodes['zwnj'] or zwnj -- handly later on + -- the tounicode mapping is sparse and only needed for alternatives + local tounicode, originals, ns, nl, private, unknown = { }, { }, 0, 0, fonts.private, format("%04X",utfbyte("?")) + data.luatex.tounicode, data.luatex.originals = tounicode, originals + local lumunic, uparser, oparser + if false then -- will become an option + lumunic = fonts.map.load_lum_table(filename) + lumunic = lumunic and lumunic.tounicode + end + local cidinfo, cidnames, cidcodes = data.cidinfo + local usedmap = cidinfo and cidinfo.usedname + usedmap = usedmap and fonts.cid.map[usedmap] + if usedmap then + oparser = usedmap and fonts.map.make_name_parser(cidinfo.ordering) + cidnames = usedmap.names + cidcodes = usedmap.unicodes + end + uparser = fonts.map.make_name_parser() + for index, glyph in next, data.glyphs do + local name, unic = glyph.name, glyph.unicode or -1 -- play safe + if unic == -1 or unic >= private or (unic >= 0xE000 and unic <= 0xF8FF) or unic == 0xFFFE or unic == 0xFFFF then + local unicode = lumunic and lumunic[name] + if unicode then + originals[index], tounicode[index], ns = unicode, tounicode16(unicode), ns + 1 + end + -- cidmap heuristics, beware, there is no guarantee for a match unless + -- the chain resolves + if not unicode and usedmap then + local foundindex = oparser:match(name) + if foundindex then + unicode = cidcodes[foundindex] -- name to number + if not unicode then + local reference = cidnames[foundindex] -- number to name + if reference then + local foundindex = oparser:match(reference) + if foundindex then + unicode = cidcodes[foundindex] + if unicode then + originals[index], tounicode[index], ns = unicode, tounicode16(unicode), ns + 1 + end + end + if not unicode then + local foundcodes, multiple = uparser:match(reference) + if foundcodes then + if multiple then + originals[index], tounicode[index], nl, unicode = foundcodes, tounicode16sequence(foundcodes), nl + 1, true + else + originals[index], tounicode[index], ns, unicode = foundcodes, tounicode16(foundcodes), ns + 1, foundcodes + end + end + end + end + end end end - end - return cidmap - end - local cidmap = locate(registry,ordering,supplement) - if not cidmap then - local cidnum = nil - -- next highest (alternatively we could start high) - if supplement < fonts.otf.cidmax then - for supplement=supplement+1,fonts.otf.cidmax do - local c = locate(registry,ordering,supplement) - if c then - cidmap, cidnum = c, supplement - break + -- a.whatever or a_b_c.whatever or a_b_c (no numbers) + if not unicode then + local split = ligsplitter:match(name) + local nplit = (split and #split) or 0 + if nplit == 0 then + -- skip + elseif nplit == 1 then + unicode = unicodes[split[1]] + if unicode then + if type(unicode) == "table" then + unicode = unicode[1] + end + originals[index], tounicode[index], ns = unicode, tounicode16(unicode), ns + 1 + end + else + local done = true + for l=1,nplit do + local u = unicodes[split[l]] + if not u then + done = false + break + elseif type(u) == "table" then + split[l] = u[1] + else + split[l] = u + end + end + if done then + originals[index], tounicode[index], nl, unicode = split, tounicode16sequence(split), nl + 1, true + end end end - end - -- next lowest (least worse fit) - if not cidmap and supplement > 0 then - for supplement=supplement-1,0,-1 do - local c = locate(registry,ordering,supplement) - if c then - cidmap, cidnum = c, supplement - break + -- last resort + if not unicode then + local foundcodes, multiple = uparser:match(name) + if foundcodes then + if multiple then + originals[index], tounicode[index], nl, unicode = foundcodes, tounicode16sequence(foundcodes), nl + 1, true + else + originals[index], tounicode[index], ns, unicode = foundcodes, tounicode16(foundcodes), ns + 1, foundcodes + end end end + if not unicode then + originals[index], tounicode[index] = 0xFFFD, "FFFD" + end end - -- prevent further lookups - if cidmap and cidnum > 0 then - for s=0,cidnum-1 do - filename = string.format(template,registry,ordering,s) - if not fonts.otf.cidmaps[filename] then - fonts.otf.cidmaps[filename] = cidmap -- copy of ref - end + end + if trace_unimapping then + for index, glyph in table.sortedpairs(data.glyphs) do + local toun, name, unic = tounicode[index], glyph.name, glyph.unicode or -1 -- play safe + if toun then + logs.report("load otf","internal: 0x%05X, name: %s, unicode: 0x%05X, tounicode: %s",index,name,unic,toun) + else + logs.report("load otf","internal: 0x%05X, name: %s, unicode: 0x%05X",index,name,unic) end end end - return cidmap + if trace_loading and (ns > 0 or nl > 0) then + logs.report("load otf","enhance: %s tounicode entries added (%s ligatures)",nl+ns, ns) + end end ---~ ["cidinfo"]={ ---~ ["ordering"]="Japan1", ---~ ["registry"]="Adobe", ---~ ["supplement"]=6, ---~ ["version"]=6, ---~ }, +otf.enhancers["analyse subtables"] = function(data,filename) + data.luatex = data.luatex or { } + local luatex = data.luatex + local sequences = { } + local lookups = { } + luatex.sequences = sequences + luatex.lookups = lookups + for _, g in next, { data.gsub, data.gpos } do + for k=1,#g do + local gk = g[k] + local typ = gk.type + if typ == "gsub_contextchain" or typ == "gpos_contextchain" then + gk.chain = 1 + elseif typ == "gsub_reversecontextchain" or typ == "gpos_reversecontextchain" then + gk.chain = -1 + else + gk.chain = 0 + end + local features = gk.features + if features then + sequences[#sequences+1] = gk + -- scripts, tag, ismac + local t = { } + for f=1,#features do + local feature = features[f] + local hash = { } + -- only script and langs matter + for s, languages in next, feature.scripts do + s = lower(s) + local h = hash[s] + if not h then h = { } hash[s] = h end + for l=1,#languages do + h[strip(lower(languages[l]))] = true + end + end + t[feature.tag] = hash + end + gk.features = t + else + lookups[gk.name] = gk + gk.name = nil + end + local subtables = gk.subtables + if subtables then + local t = { } + for s=1,#subtables do + local subtable = subtables[s] + local name = subtable.name + t[#t+1] = name + end + gk.subtables = t + end + local flags = gk.flags + if flags then + gk.flags = { -- forcing false packs nicer + (flags.ignorecombiningmarks and "mark") or false, + (flags.ignoreligatures and "ligature") or false, + (flags.ignorebaseglyphs and "base") or false, + flags.r2l or false + } + end + end + end +end -function fonts.otf.enhance.before(data,filename) - local private = fonts.private +otf.enhancers["merge cid fonts"] = function(data,filename) + -- we can also move the names to data.luatex.names which might + -- save us some more memory (at the cost of harder tracing) if data.subfonts and table.is_empty(data.glyphs) then local cidinfo = data.cidinfo + local verbose = fonts.verbose if cidinfo.registry then - local cidmap = fonts.otf.cidmap(cidinfo.registry,cidinfo.ordering,cidinfo.supplement) + local cidmap, cidname = fonts.cid.getmap(cidinfo.registry,cidinfo.ordering,cidinfo.supplement) if cidmap then + cidinfo.usedname = cidmap.usedname local glyphs, uni_to_int, int_to_uni, nofnames, nofunicodes = { }, { }, { }, 0, 0 local unicodes, names = cidmap.unicodes, cidmap.names - for n, subfont in pairs(data.subfonts) do - for index, g in pairs(subfont.glyphs) do + for n, subfont in next, data.subfonts do + for index, g in next, subfont.glyphs do if not next(g) then -- dummy entry else @@ -1178,91 +687,143 @@ function fonts.otf.enhance.before(data,filename) g.boundingbox = g.boundingbox -- or zerobox g.name = g.name or name or "unknown" if unicode then - g.unicode = unicode uni_to_int[unicode] = index int_to_uni[index] = unicode nofunicodes = nofunicodes + 1 + g.unicode = unicode elseif name then - g.unicode = -1 nofnames = nofnames + 1 + g.unicode = -1 end glyphs[index] = g end end subfont.glyphs = nil end - logs.report("load otf",string.format("cid font remapped, %s unicode points, %s symbolic names, %s glyphs",nofunicodes, nofnames, nofunicodes+nofnames)) + if trace_loading then + logs.report("load otf","cid font remapped, %s unicode points, %s symbolic names, %s glyphs",nofunicodes, nofnames, nofunicodes+nofnames) + end data.glyphs = glyphs data.map = data.map or { } data.map.map = uni_to_int data.map.backmap = int_to_uni - else - logs.report("load otf",string.format("unable to remap cid font, missing cid file for %s",filename)) + elseif trace_loading then + logs.report("load otf","unable to remap cid font, missing cid file for %s",filename) end - else - logs.report("load otf",string.format("font %s has no glyphs",filename)) + elseif trace_loading then + logs.report("load otf","font %s has no glyphs",filename) end end - if data.map then - local uni_to_int = data.map.map -- [unic] = slot - local int_to_uni = data.map.backmap -- { [0|1] = unic, ... } - for index, glyph in pairs(data.glyphs) do - if glyph.name then - local unic = glyph.unicode or glyph.unicodeenc or -1 - glyph.unicodeenc = nil -- older luatex version - if index > 0 and (unic == -1 or unic >= 0x110000) then - while uni_to_int[private] do - private = private + 1 - end - uni_to_int[private] = index - int_to_uni[index] = private +end + +otf.enhancers["prepare unicode"] = function(data,filename) + local luatex = data.luatex + if not luatex then luatex = { } data.luatex = luatex end + local indices, unicodes, multiples, internals = { }, { }, { }, { } + local glyphs = data.glyphs + local mapmap = data.map + if not mapmap then + logs.report("load otf","no map in %s",filename) + mapmap = { } + data.map = { map = mapmap } + elseif not mapmap.map then + logs.report("load otf","no unicode map in %s",filename) + mapmap = { } + data.map.map = mapmap + else + mapmap = mapmap.map + end + local criterium = fonts.private + local private = fonts.private + for index, glyph in next, glyphs do + if index > 0 then + local name = glyph.name + if name then + local unicode = glyph.unicode + if unicode == -1 or unicode >= criterium then glyph.unicode = private - if fonts.trace then - logs.report("load otf",string.format("enhance: glyph %s at index %s is moved to private unicode slot %s",glyph.name,index,private)) + indices[private] = index + unicodes[name] = private + internals[index] = true + if trace_private then + logs.report("load otf","enhance: glyph %s at index U+%04X is moved to private unicode slot U+%04X",name,index,private) end + private = private + 1 else - glyph.unicode = unic -- safeguard for older version + indices[unicode] = index + unicodes[name] = unicode end end end - local n = 0 - for k,v in pairs(int_to_uni) do - if v == -1 or v >= 0x110000 then - int_to_uni[k], n = nil, n+1 - end - end - if fonts.trace then - logs.report("load otf",string.format("enhance: %s entries removed from map.backmap",n)) - end - local n = 0 - for k,v in pairs(uni_to_int) do - if k == -1 or k >= 0x110000 then - uni_to_int[k], n = nil, n+1 + end + -- beware: the indeces table is used to initialize the tfm table + for unicode, index in next, mapmap do + if not internals[index] then + local name = glyphs[index].name + if name then + local un = unicodes[name] + if not un then + unicodes[name] = unicode -- or 0 + elseif type(un) == "number" then + if un ~= unicode then + multiples[#multiples+1] = name + unicodes[name] = { un, unicode } + indices[unicode] = index + end + else + local ok = false + for u=1,#un do + if un[u] == unicode then + ok = true + break + end + end + if not ok then + multiples[#multiples+1] = name + un[#un+1] = unicode + indices[unicode] = index + end + end end end - if fonts.trace then - logs.report("load otf",string.format("enhance: %s entries removed from map.mapmap",n)) + end + if trace_loading then + if #multiples > 0 then + logs.report("load otf","%s glyph are reused: %s",#multiples, concat(multiples," ")) + else + logs.report("load otf","no glyph are reused") end - else - data.map = { map = {}, backmap = {} } 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 + luatex.indices = indices + luatex.unicodes = unicodes + luatex.private = private +end + +otf.enhancers["cleanup ttf tables"] = function(data,filename) + local ttf_tables = data.ttf_tables + if ttf_tables then + for k=1,#ttf_tables do + if ttf_tables[k].data then ttf_tables[k].data = "deleted" end end end - table.compact(data.glyphs) + data.ttf_tab_saved = nil +end + +otf.enhancers["compact glyphs"] = function(data,filename) + table.compact(data.glyphs) -- needed? if data.subfonts then - for _, subfont in pairs(data.subfonts) do - table.compact(subfont.glyphs) + for _, subfont in next, data.subfonts do + table.compact(subfont.glyphs) -- needed? end end +end + +otf.enhancers["reverse coverage"] = function(data,filename) -- we prefer the before lookups in a normal order if data.lookups then - for _, v in pairs(data.lookups) do + for _, v in next, data.lookups do if v.rules then - for _, vv in pairs(v.rules) do + for _, vv in next, v.rules do local c = vv.coverage if c and c.before then c.before = table.reverse(c.before) @@ -1271,79 +832,241 @@ function fonts.otf.enhance.before(data,filename) end end end ---~ for index, glyph in pairs(data.glyphs) do ---~ for k,v in pairs(glyph) do ---~ if v == 0 then glyph[k] = nil end ---~ end ---~ end end -function fonts.otf.enhance.after(data,filename) -- to be split - if fonts.otf.enhance.add_kerns then - local glyphs, mapmap, unicodes = data.glyphs, data.map.map, data.luatex.unicodes - local mkdone = false - for index, glyph in pairs(data.glyphs) do - if glyph.kerns then - local mykerns = { } -- unicode indexed ! - for k,v in pairs(glyph.kerns) do - local vc, vo, vl = v.char, v.off, v.lookup - if vc and vo and vl then -- brrr, wrong! we miss the non unicode ones - local uvc = unicodes[vc] - if uvc then - local mkl = mykerns[vl] +otf.enhancers["check italic correction"] = function(data,filename) + local glyphs = data.glyphs + local ok = false + for index, glyph in next, glyphs do + local ic = glyph.italic_correction + if ic then + if ic ~= 0 then + glyph.italic = ic + end + glyph.italic_correction = nil + ok = true + end + end + -- we can use this to avoid calculations + otf.tables.valid_fields[#otf.tables.valid_fields+1] = "has_italic" + data.has_italic = true +end + +otf.enhancers["check math"] = function(data,filename) + if data.math then + -- we move the math stuff into a math subtable because we then can + -- test faster in the tfm copy + local glyphs = data.glyphs + local unicodes = data.luatex.unicodes + for index, glyph in next, glyphs do + local mk = glyph.mathkern + local hv = glyph.horiz_variants + local vv = glyph.vert_variants + if mk or hv or vv then + local math = { } + glyph.math = math + if mk then + for k, v in next, mk do + if not next(v) then + mk[k] = nil + end + end + math.kerns = mk + glyph.mathkern = nil + end + if hv then + math.horiz_variants = hv.variants + local p = hv.parts + if p then + if #p>0 then + for i=1,#p do + local pi = p[i] + pi.glyph = unicodes[pi.component] or 0 + end + math.horiz_parts = p + end + end + local ic = hv.italic_correction + if ic and ic ~= 0 then + math.horiz_italic_correction = ic + end + glyph.horiz_variants = nil + end + if vv then + local uc = unicodes[index] + math.vert_variants = vv.variants + local p = vv.parts + if p then + if #p>0 then + for i=1,#p do + local pi = p[i] + pi.glyph = unicodes[pi.component] or 0 + end + math.vert_parts = p + end + end + local ic = vv.italic_correction + if ic and ic ~= 0 then + math.vert_italic_correction = ic + end + glyph.vert_variants = nil + end + local ic = glyph.italic_correction + if ic then + if ic ~= 0 then + math.italic_correction = ic + end + glyph.italic_correction = nil + end + end + end + end +end + +otf.enhancers["share widths"] = function(data,filename) + local glyphs = data.glyphs + local widths = { } + for index, glyph in next, glyphs do + local width = glyph.width + widths[width] = (widths[width] or 0) + 1 + end + -- share width for cjk fonts + local wd, most = 0, 1 + for k,v in next, widths do + if v > most then + wd, most = k, v + end + end + if most > 1000 then + if trace_loading then + logs.report("load otf", "most common width: %s (%s times), sharing (cjk font)",wd,most) + end + for k, v in next, glyphs do + if v.width == wd then + v.width = nil + end + end + data.luatex.defaultwidth = wd + end +end + +-- kern: ttf has a table with kerns + +otf.enhancers["reorganize kerns"] = function(data,filename) + local glyphs, mapmap, unicodes = data.glyphs, data.luatex.indices, data.luatex.unicodes + local mkdone = false + for index, glyph in next, data.glyphs do + if glyph.kerns then + local mykerns = { } + for k,v in next, glyph.kerns do + local vc, vo, vl = v.char, v.off, v.lookup + if vc and vo and vl then -- brrr, wrong! we miss the non unicode ones + local uvc = unicodes[vc] + if not uvc then + if trace_loading then + logs.report("load otf","problems with unicode %s of kern %s at glyph %s",vc,k,index) + end + else + if type(vl) ~= "table" then + vl = { vl } + end + for l=1,#vl do + local vll = vl[l] + local mkl = mykerns[vll] if not mkl then - mkl = { [unicodes[vc]] = vo } - mykerns[v.lookup] = mkl + mkl = { } + mykerns[vll] = mkl + end + if type(uvc) == "table" then + for u=1,#uvc do + mkl[uvc[u]] = vo + end else - mkl[unicodes[vc]] = vo + mkl[uvc] = vo end - else - logs.report("load otf", string.format("problems with unicode %s of kern %s at glyph %s",vc,k,index)) end end end - glyph.mykerns = mykerns - glyph.kerns = nil -- saves space and time - mkdone = true end + glyph.mykerns = mykerns + glyph.kerns = nil -- saves space and time + mkdone = true end - if mkdone then - logs.report("load otf", "replacing 'kerns' tables by 'mykerns' tables") + end + if trace_loading and mkdone then + logs.report("load otf", "replacing 'kerns' tables by 'mykerns' tables") + end + if data.kerns then + if trace_loading then + logs.report("load otf", "removing global 'kern' table") end - if data.gpos then - for _, gpos in ipairs(data.gpos) do - if gpos.subtables then - for _, subtable in ipairs(gpos.subtables) do - local kernclass = subtable.kernclass - if kernclass then - for _, kcl in ipairs(kernclass) do - local firsts, seconds, offsets, lookup = kcl.firsts, kcl.seconds, kcl.offsets, kcl.lookup - local maxfirsts, maxseconds = table.getn(firsts), table.getn(seconds) - logs.report("load otf", string.format("adding kernclass %s with %s times %s pairs)",lookup, maxfirsts, maxseconds)) - for fk, fv in pairs(firsts) do - for first in fv:gmatch("[^ ]+") do - local glyph = glyphs[mapmap[unicodes[first]]] - local mykerns = glyph.mykerns - if not mykerns then - mykerns = { } -- unicode indexed ! - glyph.mykerns = mykerns - end - local lookupkerns = mykerns[lookup] - if not lookupkerns then - lookupkerns = { } - mykerns[lookup] = lookupkerns + data.kerns = nil + end + local dgpos = data.gpos + if dgpos then + for gp=1,#dgpos do + local gpos = dgpos[gp] + local subtables = gpos.subtables + if subtables then + for s=1,#subtables do + local subtable = subtables[s] + local kernclass = subtable.kernclass -- name is inconsistent with anchor_classes + if kernclass then + for k=1,#kernclass do + local kcl = kernclass[k] + local firsts, seconds, offsets, lookups = kcl.firsts, kcl.seconds, kcl.offsets, kcl.lookup -- singular + if type(lookups) ~= "table" then + lookups = { lookups } + end + for l=1,#lookups do + local lookup = lookups[l] + local maxfirsts, maxseconds = getn(firsts), getn(seconds) + if trace_loading then + logs.report("load otf", "adding kernclass %s with %s times %s pairs",lookup, maxfirsts, maxseconds) + end + for fk, fv in next, firsts do + for first in gmatch(fv,"[^ ]+") do + local first_unicode = unicodes[first] + if type(first_unicode) == "number" then + first_unicode = { first_unicode } end - for sk, sv in pairs(seconds) do - for second in sv:gmatch("[^ ]+") do - lookupkerns[unicodes[second]] = offsets[(fk-1) * maxseconds + sk] + for f=1,#first_unicode do + local glyph = glyphs[mapmap[first_unicode[f]]] + if glyph then + local mykerns = glyph.mykerns + if not mykerns then + mykerns = { } -- unicode indexed ! + glyph.mykerns = mykerns + end + local lookupkerns = mykerns[lookup] + if not lookupkerns then + lookupkerns = { } + mykerns[lookup] = lookupkerns + end + for sk, sv in next, seconds do + local offset = offsets[(fk-1) * maxseconds + sk] + --~ local offset = offsets[sk] -- (fk-1) * maxseconds + sk] + for second in gmatch(sv,"[^ ]+") do + local second_unicode = unicodes[second] + if type(second_unicode) == "number" then + lookupkerns[second_unicode] = offset + else + for s=1,#second_unicode do + lookupkerns[second_unicode[s]] = offset + end + end + end + end + elseif trace_loading then + logs.report("load otf", "no glyph data for U+%04X", first_unicode[f]) end end end end end - subtable.comment = "The kernclass table is merged into mykerns in the indexed glyph tables." - subtable.kernclass = { } end + subtable.comment = "The kernclass table is merged into mykerns in the indexed glyph tables." + subtable.kernclass = { } end end end @@ -1351,22 +1074,70 @@ function fonts.otf.enhance.after(data,filename) -- to be split end end -function fonts.otf.enhance.strip(data) - for k, v in pairs(data.glyphs) do +otf.enhancers["strip not needed data"] = function(data,filename) + local verbose = fonts.verbose + local int_to_uni = data.luatex.unicodes + for k, v in next, data.glyphs do local d = v.dependents if d then v.dependents = nil end + local a = v.altuni + if a then v.altuni = nil end + if verbose then + local code = int_to_uni[k] + -- looks like this is done twice ... bug? + if code then + local vu = v.unicode + if not vu then + v.unicode = code + elseif type(vu) == "table" then + if vu[#vu] == code then + -- weird + else + vu[#vu+1] = code + end + elseif vu ~= code then + v.unicode = { vu, code } + end + end + else + v.unicode = nil + v.index = nil + end end - data.map = nil - data.names = nil data.luatex.comment = "Glyph tables have their original index. When present, mykern tables are indexed by unicode." + data.map = nil + data.names = nil -- funny names for editors + data.glyphcnt = nil + data.glyphmax = nil + if true then + data.gpos = nil + data.gsub = nil + data.anchor_classes = nil + end end -function fonts.otf.enhance.flatten(data,filename) -- to be split - logs.report("load otf", "flattening 'specifications' tables") - for k, v in pairs(data.glyphs) do +otf.enhancers["migrate metadata"] = function(data,filename) + local global_fields = otf.tables.global_fields + local metadata = { } + for k,v in next, data do + if not global_fields[k] then + metadata[k] = v + data[k] = nil + end + end + data.metadata = metadata + -- goodies + local pfminfo = data.pfminfo + metadata.isfixedpitch = metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose["proportion"] == "Monospaced") + metadata.charwidth = pfminfo and pfminfo.avgwidth +end + +otf.enhancers["flatten glyph lookups"] = function(data,filename) + for k, v in next, data.glyphs do if v.lookups then - for kk, vv in pairs(v.lookups) do - for kkk, vvv in ipairs(vv) do + for kk, vv in next, v.lookups do + for kkk=1,#vv do + local vvv = vv[kkk] local s = vvv.specification if s then local t = vvv.type @@ -1379,27 +1150,29 @@ function fonts.otf.enhance.flatten(data,filename) -- to be split elseif t == "multiple" then vv[kkk] = { "multiple", s.components } elseif t == "position" then - vv[kkk] = { "position", s.x or 0, s.y or 0, s.h or 0, s.v or 0 } + vv[kkk] = { "position", { s.x or 0, s.y or 0, s.h or 0, s.v or 0 } } elseif t == "pair" then local one, two, paired = s.offsets[1], s.offsets[2], s.paired or "" if one then if two then - vv[kkk] = { "pair", paired, one.x or 0, one.y or 0, one.h or 0, one.v or 0, two.x or 0, two.y or 0, two.h or 0, two.v or 0 } + vv[kkk] = { "pair", paired, { one.x or 0, one.y or 0, one.h or 0, one.v or 0 }, { two.x or 0, two.y or 0, two.h or 0, two.v or 0 } } else - vv[kkk] = { "pair", paired, one.x or 0, one.y or 0, one.h or 0 } + vv[kkk] = { "pair", paired, { one.x or 0, one.y or 0, one.h or 0, one.v or 0 } } end else if two then - vv[kkk] = { "pair", paired, 0, 0, 0, 0, two.x or 0, two.y or 0, two.h or 0, two.v or 0 } + vv[kkk] = { "pair", paired, { }, { two.x or 0, two.y or 0, two.h or 0, two.v or 0} } -- maybe nil instead of { } else vv[kkk] = { "pair", paired } end end else - logs.report("load otf", "flattening needed, warn Hans and/or Taco") - for a, b in pairs(s) do - if vvv[a] then - logs.report("load otf", "flattening conflict, warn Hans and/or Taco") + if trace_loading then + logs.report("load otf", "flattening needed, report to context list") + end + for a, b in next, s do + if trace_loading and vvv[a] then + logs.report("load otf", "flattening conflict, report to context list") end vvv[a] = b end @@ -1410,15 +1183,18 @@ function fonts.otf.enhance.flatten(data,filename) -- to be split end end end - logs.report("load otf", "flattening 'anchor' tables") - for k, v in pairs(data.glyphs) do +end + +otf.enhancers["flatten anchor tables"] = function(data,filename) + for k, v in next, data.glyphs do if v.anchors then - for kk, vv in pairs(v.anchors) do - for kkk, vvv in pairs(vv) do - if vvv.x or vvv.y then -- kkk == "centry" + for kk, vv in next, v.anchors do + for kkk, vvv in next, vv do + if vvv.x or vvv.y then vv[kkk] = { vvv.x or 0, vvv.y or 0 } else - for kkkk, vvvv in ipairs(vvv) do + for kkkk=1,#vvv do + local vvvv = vvv[kkkk] vvv[kkkk] = { vvvv.x or 0, vvvv.y or 0 } end end @@ -1426,14 +1202,24 @@ function fonts.otf.enhance.flatten(data,filename) -- to be split end end end - for _, tag in pairs({"gpos","gsub"}) do +end + +otf.enhancers["flatten feature tables"] = function(data,filename) + -- is this needed? do we still use them at all? + for _, tag in next, otf.glists do if data[tag] then - logs.report("load otf", "flattening '" .. tag.. "' tables") - for k, v in pairs(data[tag]) do - if v.features then - for kk, vv in ipairs(v.features) do + if trace_loading then + logs.report("load otf", "flattening %s table", tag) + end + for k, v in next, data[tag] do + local features = v.features + if features then + for kk=1,#features do + local vv = features[kk] local t = { } - for kkk, vvv in ipairs(vv.scripts) do + local scripts = vv.scripts + for kkk=1,#scripts do + local vvv = scripts[kkk] t[vvv.script] = vvv.langs end vv.scripts = t @@ -1444,12 +1230,12 @@ function fonts.otf.enhance.flatten(data,filename) -- to be split end end -fonts.otf.enhance.patches = { } +otf.enhancers.patches = otf.enhancers.patches or { } -function fonts.otf.enhance.patch(data,filename) - local basename = file.basename(filename) - for pattern, action in pairs(fonts.otf.enhance.patches) do - if basename:find(pattern) then +otf.enhancers["patch bugs"] = function(data,filename) + local basename = file.basename(lower(filename)) + for pattern, action in next, otf.enhancers.patches do + if find(basename,pattern) then action(data,filename) end end @@ -1457,374 +1243,228 @@ end -- tex features -function fonts.otf.enhance.enrich(data,filename) - -- later -end - --- patching - -do -- will move to a typescript - - local function patch(data,filename) - if data.design_size == 0 then - local ds = (file.basename(filename)):match("(%d+)") - if ds then - logs.report("load otf",string.format("patching design size (%s)",ds)) - data.design_size = tonumber(ds) * 10 - end - end - end - - fonts.otf.enhance.patches["^lmroman"] = patch - fonts.otf.enhance.patches["^lmsans"] = patch - fonts.otf.enhance.patches["^lmtypewriter"] = patch - +fonts.otf.enhancers["enrich with features"] = function(data,filename) + -- later, ctx only 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.unicode] = true - end - end - return classes +function otf.features.register(name,default) + otf.features.list[#otf.features.list+1] = name + otf.features.default[name] = default end -function fonts.otf.analyze_subtables(data) - local subtables, name_to_type, internals, always_valid, ignore_flags, ctx_always = { }, { }, { }, { }, { }, { } - 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 { } - ctx_always[ft] = v.always - for script, languages in pairs(feature.scripts) do - script = script:lower() - script = script:strip() - sft = subtables[ft] - local sfts = sft[script] - if not sfts then - sfts = { } - sft[script] = sfts - end - for _, language in ipairs(languages) do - language = language:lower() - language = language:strip() - local sftsl = sfts[language] - if not sftsl then - sftsl = sfts[language] or { } - sfts[language] = sftsl - end - 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 - 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, ctx_always -end - -function fonts.otf.analyze_unicodes(data) - local unicodes = { } - for _, blob in pairs(data.glyphs) do - if blob.name then - unicodes[blob.name] = blob.unicode or 0 - end - end - unicodes['space'] = unicodes['space'] or 32 -- handly later on - return unicodes -end - -function fonts.otf.analyze_features(g, features) - if g then - local t, done = { }, { } - for k=1,#g do - local f = features or g[k].features - if f then - for k=1,#f do - -- scripts and tag - local tag = f[k].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,kind,script,language) - local tk = otfdata.luatex.subtables[kind] - if tk then - local tks = tk[script] or tk.dflt - if tks then - local tksl = tks[language] or tks.dflt - if tksl then - return tksl.lookups - end - end - end - return false -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) -- node and base, simple mapping - local shared = tfmdata.shared - local otfdata = shared.otfdata - shared.features = fonts.define.check(shared.features,fonts.otf.features.default) - local features = shared.features - local trace = fonts.otf.trace_features or fonts.otf.trace_set_features - if not tfmdata.language then tfmdata.language = 'dflt' end - if not tfmdata.script then tfmdata.script = 'dflt' end +function otf.set_features(tfmdata,features) + local processes = { } 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 lists = { + fonts.triggers, + fonts.processors, + fonts.manipulators, + } + local mode = tfmdata.mode or fonts.mode -- or features.mode local initializers = fonts.initializers local fi = initializers[mode] - if fi then -- todo: delay initilization for mode 'node' + if fi then local fiotf = fi.otf if fiotf then local done = { } - local function initialize(list) -- using tex lig and kerning + for l=1,4 do + local list = lists[l] if list then for i=1,#list do local f = list[i] local value = features[f] if value and fiotf[f] then -- brr if not done[f] then -- so, we can move some to triggers - if trace 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.fullname or 'unknown')) + if trace_features then + logs.report("define otf","initializing feature %s to %s for mode %s for font %s",f,tostring(value),mode or 'unknown', tfmdata.fullname or 'unknown') end fiotf[f](tfmdata,value) -- can set mode (no need to pass otf) mode = tfmdata.mode or fonts.mode -- keep this, mode can be set local ! - local fi = initializers[mode] - fiotf = fi.otf + local im = initializers[mode] + if im then + fiotf = initializers[mode].otf + end done[f] = true end end end end end - initialize(fonts.triggers) - initialize(gsublist) - initialize(gposlist) - initialize(fonts.manipulators) end end local fm = fonts.methods[mode] if fm then local fmotf = fm.otf - local sp = shared.processors if fmotf then - local function register(list) -- node manipulations + for l=1,4 do + local list = lists[l] if list then for i=1,#list do local f = list[i] - if features[f] and fmotf[f] then -- brr - if trace then - logs.report("define otf",string.format("installing feature handler %s for mode %s for font %s",f,mode or 'unknown', tfmdata.fullname or 'unknown')) + if fmotf[f] then -- brr + if trace_features then + logs.report("define otf","installing feature handler %s for mode %s for font %s",f,mode or 'unknown', tfmdata.fullname or 'unknown') end - sp[#sp+1] = fmotf[f] + processes[#processes+1] = fmotf[f] end end end end - register(fonts.triggers) - register(gsublist) - register(gposlist) - register(fonts.manipulators) end + else + -- message end end + return processes, features end -function fonts.otf.otf_to_tfm(specification) +function 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) + local tfmdata = containers.read(tfm.cache(),cache_id) +--~ print(cache_id) if not tfmdata then - local otfdata = fonts.otf.load(filename,format,sub,features and features.featurefile) + local otfdata = otf.load(filename,format,sub,features and features.featurefile) if not table.is_empty(otfdata) then - fonts.otf.add_dimensions(otfdata) - if true then - otfdata._shared_ = otfdata._shared_ or { -- aggressive sharing - processes = { }, - lookuptable = { }, - featuredata = { }, - featurecache = { }, - } - end - tfmdata = fonts.otf.copy_to_tfm(otfdata) + otfdata.shared = otfdata.shared or { + featuredata = { }, + anchorhash = { }, + initialized = false, + } + tfmdata = otf.copy_to_tfm(otfdata,cache_id) if not table.is_empty(tfmdata) then tfmdata.unique = tfmdata.unique or { } tfmdata.shared = tfmdata.shared or { } -- combine local shared = tfmdata.shared shared.otfdata = otfdata - shared.features = features - shared.processors = { } + shared.features = features -- default shared.dynamics = { } shared.processes = { } - shared.lookuptable = { } - shared.featuredata = { } - shared.featurecache = { } - if otfdata._shared_ then - shared.processes = otfdata._shared_.processes - shared.lookuptable = otfdata._shared_.lookuptable - shared.featuredata = otfdata._shared_.featuredata - shared.featurecache = otfdata._shared_.featurecache - end - fonts.otf.set_features(tfmdata) - end - end - containers.write(fonts.tfm.cache(),cache_id,tfmdata) + shared.set_dynamics = otf.set_dynamics -- fast access and makes other modules independent + -- this will be done later anyway, but it's convenient to have + -- them already for fast access + tfmdata.luatex = otfdata.luatex + tfmdata.indices = otfdata.luatex.indices + tfmdata.unicodes = otfdata.luatex.unicodes + tfmdata.marks = otfdata.luatex.marks + tfmdata.originals = otfdata.luatex.originals + tfmdata.changed = { } + tfmdata.has_italic = otfdata.metadata.has_italic + if not tfmdata.language then tfmdata.language = 'dflt' end + if not tfmdata.script then tfmdata.script = 'dflt' end + shared.processes, shared.features = otf.set_features(tfmdata,fonts.define.check(features,otf.features.default)) + end + end + containers.write(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,kind,tfmdata.script,tfmdata.language) - for _, chr in pairs(tfmdata.characters) do - local d = charlist[chr.description.index] - if d then - local dk = d.mykerns - if dk then - local t, done = chr.kerns or { }, false - for lookup,kerns in pairs(dk) do - if somevalid[lookup] then - for k, v in pairs(kerns) do - if v ~= 0 then - t[k], done = v, true - end +--~ { +--~ ['boundingbox']={ 95, -458, 733, 1449 }, +--~ ['class']="base", +--~ ['name']="braceleft", +--~ ['unicode']=123, +--~ ['vert_variants']={ +--~ ['italic_correction']=0, +--~ ['parts']={ +--~ { ['component']="uni23A9", ['endConnectorLength']=1000, ['fullAdvance']=2546, ['is_extender']=0, ['startConnectorLength']=0, }, -- bot +--~ { ['component']="uni23AA", ['endConnectorLength']=2500, ['fullAdvance']=2501, ['is_extender']=1, ['startConnectorLength']=2500, }, -- rep +--~ { ['component']="uni23A8", ['endConnectorLength']=1000, ['fullAdvance']=4688, ['is_extender']=0, ['startConnectorLength']=1000, }, -- mid +--~ { ['component']="uni23AA", ['endConnectorLength']=2500, ['fullAdvance']=2501, ['is_extender']=1, ['startConnectorLength']=2500, }, -- rep +--~ { ['component']="uni23A7", ['endConnectorLength']=0, ['fullAdvance']=2546, ['is_extender']=0, ['startConnectorLength']=1000, }, -- top +--~ }, +--~ ['variants']="braceleft braceleft.vsize1 braceleft.vsize2 braceleft.vsize3 braceleft.vsize4 braceleft.vsize5 braceleft.vsize6 braceleft.vsize7", +--~ }, +--~ ['width']=793, +--~ }, + +-- the first version made a top/mid/not extensible table, now we just pass on the variants data +-- and deal with it in the tfm scaler (there is no longer an extensible table anyway) + +-- we cannot share descriptions as virtual fonts might extend them (ok, we could +-- use a cache with a hash + +function otf.copy_to_tfm(data,cache_id) -- we can save a copy when we reorder the tma to unicode (nasty due to one->many) + if data then + local glyphs, pfminfo, metadata = data.glyphs or { }, data.pfminfo or { }, data.metadata or { } + local luatex = data.luatex + local unicodes = luatex.unicodes -- names to unicodes + local indices = luatex.indices + local characters, parameters, math_parameters, descriptions = { }, { }, { }, { } + local tfm = { + characters = characters, + parameters = parameters, + math_parameters = math_parameters, + descriptions = descriptions, + indices = indices, + unicodes = unicodes, + } + -- indices maps from unicodes to indices + for u, i in next, indices do + characters[u] = { } -- we need this because for instance we add protruding info and loop over characters + descriptions[u] = glyphs[i] + end + -- math + if metadata.math then + -- parameters + for name, value in next, metadata.math do + math_parameters[name] = value + end + -- we could use a subset + for u, char in next, characters do + local d = descriptions[u] + local m = d.math + -- we have them shared because that packs nicer + -- we could prepare the variants and keep 'm in descriptions + if m then + local variants = m.horiz_variants + if variants then + local c = char + for n in variants:gmatch("[^ ]+") do + local un = unicodes[n] + if un and u ~= un then + c.next = un + c = characters[un] end end - end - if done then - chr.kerns = t -- no empty assignments - end - else - dk = d.kerns - if dk then - local t, done = chr.kerns or { }, false - for _, v in pairs(dk) do - if somevalid[v.lookup] then - local k = unicodes[v.char] - if k > 0 then - t[k], done = v.off, true + c.horiz_variants = m.horiz_parts + else + local variants = m.vert_variants + if variants then + local c = char + for n in variants:gmatch("[^ ]+") do + local un = unicodes[n] + if un and u ~= un then + c.next = un + c = characters[un] end end + c.vert_variants = m.vert_parts end - if done then - chr.kerns = t -- no empty assignments - end + end + local kerns = m.kerns + if kerns then + char.mathkerns = kerns end end end end - end -end - -function fonts.otf.add_dimensions(data) - if data then - local force = fonts.otf.notdef - for k, d in pairs(data.glyphs) do - local bb, wd = d.boundingbox, d.width or 0 - if force and not d.name then - d.name = ".notdef" - end - if wd ~= 0 and d.class == "mark" then - d.width = -wd - end - if bb then - local ht, dp = bb[4], -bb[2] - if ht ~= 0 then d.height = ht end - if dp ~= 0 then d.depth = dp end - end - d.index = k - end - end -end - -function fonts.otf.copy_to_tfm(data) -- we can save a copy when we reorder the tma to unicode - if data then - local tfm = { characters = { }, parameters = { } } - local unicodes = data.luatex.unicodes - local characters = tfm.characters - local parameters = tfm.parameters - local glyphs = data.glyphs - for k, d in pairs(glyphs) do - if d.name then - characters[d.unicode] = { description = d } - end - end - local designsize = data.designsize or data.design_size or 100 + -- end math + local designsize = metadata.designsize or metadata.design_size or 100 if designsize == 0 then designsize = 100 end local spaceunits = 500 - tfm.units = data.units_per_em or 1000 + tfm.units = metadata.units_per_em or 1000 -- we need a runtime lookup because of running from cdrom or zip, brrr - tfm.filename = input.findbinfile(texmf.instance,data.luatex.filename,"") or data.luatex.filename - tfm.fullname = data.fontname or data.fullname + tfm.filename = resolvers.findbinfile(luatex.filename,"") or luatex.filename + tfm.fullname = metadata.fontname or metadata.fullname + tfm.psname = tfm.fullname tfm.encodingbytes = 2 tfm.cidinfo = data.cidinfo tfm.cidinfo.registry = tfm.cidinfo.registry or "" @@ -1836,63 +1476,62 @@ function fonts.otf.copy_to_tfm(data) -- we can save a copy when we reorder the t tfm.boundarychar = 65536 tfm.designsize = (designsize/10)*65536 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 - local endash, emdash = unicodes['space'], unicodes['emdash'] - if data.isfixedpitch then - if characters[endash] then - spaceunits, tfm.spacer = characters[endash].description.width, "space" + local endash, emdash = 0x20, 0x2014 -- unicodes['space'], unicodes['emdash'] + if metadata.isfixedpitch then + if descriptions[endash] then + spaceunits, tfm.spacer = descriptions[endash].width, "space" end - if not spaceunits and characters[emdash] then - spaceunits, tfm.spacer = characters[emdash].description.width, "emdash" + if not spaceunits and descriptions[emdash] then + spaceunits, tfm.spacer = descriptions[emdash].width, "emdash" end - if not spaceunits and data.charwidth then - spaceunits, tfm.spacer = data.charwidth, "charwidth" + if not spaceunits and metadata.charwidth then + spaceunits, tfm.spacer = metadata.charwidth, "charwidth" end else - if characters[endash] then - spaceunits, tfm.spacer = characters[endash].description.width, "space" + if descriptions[endash] then + spaceunits, tfm.spacer = descriptions[endash].width, "space" end - if not spaceunits and characters[emdash] then - spaceunits, tfm.spacer = characters[emdash].description.width/2, "emdash/2" + if not spaceunits and descriptions[emdash] then + spaceunits, tfm.spacer = descriptions[emdash].width/2, "emdash/2" end - if not spaceunits and data.charwidth then - spaceunits, tfm.spacer = data.charwidth, "charwidth" + if not spaceunits and metadata.charwidth then + spaceunits, tfm.spacer = metadata.charwidth, "charwidth" end end spaceunits = tonumber(spaceunits) or tfm.units/2 -- 500 -- brrr parameters.slant = 0 - parameters.space = spaceunits - parameters.space_stretch = tfm.units/2 -- 500 - parameters.space_shrink = 2*tfm.units/3 -- 333 - parameters.x_height = 4*tfm.units/5 -- 400 + parameters.space = spaceunits -- 3.333 (cmr10) + parameters.space_stretch = tfm.units/2 -- 500 -- 1.666 (cmr10) + parameters.space_shrink = 1*tfm.units/3 -- 333 -- 1.111 (cmr10) + parameters.x_height = 2*tfm.units/5 -- 400 parameters.quad = tfm.units -- 1000 - parameters.extra_space = 0 if spaceunits < 2*tfm.units/5 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 _ - parameters.slant = parameters.slant - math.round(math.tan(data.italicangle*math.pi/180)) + local italicangle = metadata.italicangle + tfm.ascender = math.abs(metadata.ascent or 0) + tfm.descender = math.abs(metadata.descent or 0) + if italicangle then -- maybe also in afm _ + tfm.italicangle = italicangle + parameters.slant = parameters.slant - math.round(math.tan(italicangle*math.pi/180)) end - if data.isfixedpitch then + if metadata.isfixedpitch then parameters.space_stretch = 0 parameters.space_shrink = 0 - elseif fonts.otf.syncspace then -- + elseif otf.syncspace then -- parameters.space_stretch = spaceunits/2 parameters.space_shrink = spaceunits/3 end - if data.pfminfo and data.pfminfo.os2_xheight and data.pfminfo.os2_xheight > 0 then - parameters.x_height = data.pfminfo.os2_xheight + parameters.extra_space = parameters.space_shrink -- 1.111 (cmr10) + if pfminfo.os2_xheight and pfminfo.os2_xheight > 0 then + parameters.x_height = pfminfo.os2_xheight else - local x = characters[unicodes['x']] + local x = 0x78 -- unicodes['x'] if x then - parameters.x_height = x.description.height + local x = descriptions[x] + if x then + parameters.x_height = x.height + end end end -- [6] @@ -1902,23 +1541,55 @@ function fonts.otf.copy_to_tfm(data) -- we can save a copy when we reorder the t end end -function fonts.tfm.read_from_open_type(specification) - local tfmtable = fonts.otf.otf_to_tfm(specification) +otf.features.register('mathsize') + +function tfm.read_from_open_type(specification) + local tfmtable = otf.otf_to_tfm(specification) if tfmtable then + local otfdata = tfmtable.shared.otfdata tfmtable.name = specification.name tfmtable.sub = specification.sub - tfmtable = fonts.tfm.scale(tfmtable, specification.size) + local s = specification.size + local m = otfdata.metadata.math + if m then + local f = specification.features + if f then + local f = f.normal + if f and f.mathsize then + local mathsize = specification.mathsize or 0 + if mathsize == 2 then + local p = m.ScriptPercentScaleDown + if p then + local ps = p * specification.textsize / 100 + if trace_math then + logs.report("define font","asked script size: %s, used: %s (%2.2f %%)",s,ps,(ps/s)*100) + end + s = ps + end + elseif mathsize == 3 then + local p = m.ScriptScriptPercentScaleDown + if p then + local ps = p * specification.textsize / 100 + if trace_math then + logs.report("define font","asked scriptscript size: %s, used: %s (%2.2f %%)",s,ps,(ps/s)*100) + end + s = ps + end + end + end + end + end + tfmtable = tfm.scale(tfmtable,s) -- here we resolve the name; file can be relocated, so this info is not in the cache - local otfdata = tfmtable.shared.otfdata local filename = (otfdata and otfdata.luatex and otfdata.luatex.filename) or specification.filename if not filename then -- try to locate anyway and set otfdata.luatex.filename end if filename then tfmtable.encodingbytes = 2 - tfmtable.filename = input.findbinfile(texmf.instance,filename,"") or filename - tfmtable.fullname = otfdata.fontname or otfdata.fullname - local order = otfdata and otfdata.order2 + tfmtable.filename = resolvers.findbinfile(filename,"") or filename + tfmtable.fullname = tfmtable.fullname or otfdata.metadata.fontname or otfdata.metadata.fullname + local order = otfdata and otfdata.metadata.order2 if order == 0 then tfmtable.format = 'opentype' elseif order == 1 then @@ -1930,2882 +1601,6 @@ function fonts.tfm.read_from_open_type(specification) end fonts.logger.save(tfmtable,file.extname(specification.filename),specification) end +--~ print(tfmtable.fullname) return tfmtable end - -function fonts.otf.analyze_only(otfdata) - local analyze = fonts.otf.analyze_features - return analyze(otfdata.gpos), analyze(otfdata.gsub) -end - -local a_to_script = { } -local a_to_language = { } - -do - - local context_setups = fonts.define.specify.context_setups - local context_numbers = fonts.define.specify.context_numbers - - function fonts.otf.set_dynamics(tfmdata,attribute,features) --currently experimental and slow / hackery - local shared = tfmdata.shared - if shared then - local dynamics = shared.dynamics - if dynamics then - features = features or context_setups[context_numbers[attribute]] - if features then - local script = features.script or 'dflt' - local language = features.language or 'dflt' - local ds = dynamics[script] - if not ds then - ds = { } - dynamics[script] = ds - end - local dsl = ds[language] - if not dsl then - dsl = { } - ds[language] = dsl - end - local dsla = dsl[attribute] - if dsla then - return dsla - else - a_to_script [attribute] = script - a_to_language[attribute] = language - dsla = { } - local otfdata = shared.otfdata - local methods = fonts.methods.node.otf - local initializers = fonts.initializers.node.otf - local gposfeatures, gsubfeatures = fonts.otf.analyze_only(otfdata,features) - local default = fonts.otf.features.default - local function register(list) - if list then - for i=1,#list do - local f = list[i] - local value = features[f] or default[f] - if value then - local i, m = initializers[f], methods[f] - if i then - i(tfmdata,value) - end - if m then - dsla[#dsla+1] = m - end - end - end - end - end - register(fonts.triggers) - register(gsubfeatures) - register(gposfeatures) - dynamics[script][language][attribute] = dsla - return dsla - end - end - end - end - return { } -- todo: false - end - -end - --- scripts - -fonts.otf.default_language = 'latn' -fonts.otf.default_script = 'dflt' - -function fonts.otf.valid_feature(otfdata,kind,script,language) -- return hash is faster - if otfdata.luatex.ctx_always[kind] then - script, language = 'dflt', 'dflt' - else - script = script or fonts.otf.default_script - language = language or fonts.otf.default_language - end - script, language = script:lower(), language:lower() -- will go away, we will lowercase values - local ft = otfdata.luatex.subtables[kind] - local st = ft[script] or ft.dflt - local lt = st and (st[language] or st.dflt) - return false, otfdata.luatex.always_valid, lt.valid -end - -function fonts.otf.some_valid_feature(otfdata,kind,script,language) - if otfdata.luatex.ctx_always[kind] then - script, language = 'dflt', 'dflt' - else - script = script or fonts.otf.default_script - language = language or fonts.otf.default_language - script, language = script:lower(), language:lower() -- will go away, we will lowercase values - end - local t = otfdata.luatex.subtables[kind] - if t then - local ts = t[script] or t.dflt - if ts then - local tsl = ts[language] or ts.dflt - return (tsl and tsl.valid) or { } - end - end - return { } -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" - local trace = fonts.otf.trace_features - while true do - local ok = false - for k,v in pairs(ligatures) do - local lig = v[1] - if not done[lig] then - local ligs = { } - for s in lig:gmatch("[^ ]+") do - ligs[#ligs+1] = s - end - 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 trace 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 - local t = first.ligatures - if not t then - t = { } - first.ligatures = t - end - t[second] = { - char = unicodes[c.description.name], - type = 0 - } - if trace then - logs.report("define otf",string.format("%s: %s (%s) + %s (%s) = %s (%s)",kind,f,uf,s,us,c.description.name,unicodes[c.description.name])) - end - end - end - ok, done[lig] = true, c.description.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,kind,tfmdata.script,tfmdata.language) - if not table.is_empty(somevalid) then - tfmdata.changed = tfmdata.changed or { } - local changed = tfmdata.changed - local glyphs = otfdata.glyphs - for k,c in pairs(chars) do - local o = glyphs[c.description.index] - if o and o.lookups then - for lookup,ps in pairs(o.lookups) do - if somevalid[lookup] then - for i=1,#ps do - local p = ps[i] - local t = p[1] - if t == 'substitution' then - local pv = p[2] -- p.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].description.name,k,chars[upv].description.name,upv)) - end - chars[k] = chars[upv] - changed[k] = true - end - end - elseif t == 'alternate' then - local pc = p[2] -- p.components - if pc then - pc = pa.components:match("([^ ]+)") - 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].description.name,k,chars[upc].description.name,upc)) - end - chars[k] = chars[upc] - changed[k] = true - end - end - end - elseif t == 'ligature' and not changed[k] then - local pc = p[2] - if pc then - if trace then - logs.report("define otf",string.format("%s: %s => %s (%s)",kind,pc,chars[k].description.name,k)) - end - ligatures[#ligatures+1] = { pc, k } - end - end - end - end - end - end - end - fonts.otf.features.aux.resolve_ligatures(tfmdata,ligatures,kind) - end - 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 - -function fonts.initializers.base.otf.hwid(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'hwid',value) end -function fonts.initializers.base.otf.fwid(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'fwid',value) end - --- 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 = { } - - local falsetable = { false, false, false } - - function fonts.otf.features.prepare.feature(tfmdata,kind,value) - if value then - local language, script = tfmdata.language or "dflt", tfmdata.script or "dflt" - local shared = tfmdata.shared - local otfdata = shared.otfdata - local lookuptable = fonts.otf.valid_subtable(otfdata,kind,script,language) - if lookuptable then - local fullkind = kind .. script .. language - if not shared.lookuptable [fullkind] then - --~ print(tfmdata,file.basename(tfmdata.fullname or ""),kind,script,language,lookuptable,fullkind) - local processes = { } - -- featuredata and featurecache are indexed by lookup so we can share them - shared.featuredata [kind] = shared.featuredata [kind] or { } - shared.featurecache[kind] = shared.featurecache[kind] or false -- signal - shared.lookuptable [fullkind] = lookuptable - shared.processes [fullkind] = processes - 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 i=1,#lookuptable do - local lookupname = lookuptable[i] - 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 falsetable --- share false table - -- local chain = (lookuptype == "gsub_contextchain") or (lookuptype == "gpos_contextchain") - local chain = lookuptype:find("context") ~= nil - processes[#processes+1] = { process[lookuptype], lookupname, processdata, processflags, chain } - end - 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 - - local pairs = pairs - - function fonts.otf.features.collect_ligatures(tfmdata,kind) -- 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[1] == 'ligature' then - if trace then - logs.report("define otf",string.format("feature %s lookup %s ligature %s => %s",kind,lookup,p[2],o.name)) - end - local t = ligatures[lookup] - if not t then - t = { } - ligatures[lookup] = t - end - local first = true - for s in p[2]:gmatch("[^ ]+") do - local u = unicodes[s] - if first then - if not t[u] then - t[u] = { { } } - end - t = t[u] - first = false - else - local t1 = t[1] - if not t1[u] then - t1[u] = { { } } - end - t = t1[u] - end - end - t[2] = o.unicode - end - end - end - local forced, always, okay = fonts.otf.valid_feature(otfdata,kind,tfmdata.script,tfmdata.language) - for _,o in pairs(otfdata.glyphs) do - local lookups = o.lookups - if lookups then - if forced then - for lookup, ps in pairs(lookups) do collect(lookup,o,ps) end - elseif okay then - for lookup, ps in pairs(lookups) do if always[lookup] or okay[lookup] then collect(lookup,o,ps) end end - else - for lookup, ps in pairs(lookups) do if always[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_reversecontextchain -> 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[1] == 'substitution' then - local old, new = o.unicode, unicodes[p[2]] - 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[1] == 'multiple' then - local old, new = o.unicode, { } - substitutions[old] = new - for pc in p[2]:gmatch("[^ ]+") 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[1] == 'alternate' then - local old = o.unicode - local t = { } - for pc in p[2]:gmatch("[^ ]+") 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 characters = tfmdata.characters - local cache = otfdata.luatex.covers - local function uncover(covers,result) - -- lpeg hardly faster (.005 sec on mk) - for n=1,#covers do - local c = covers[n] - local cc = cache[c] - if not cc then - local t = { } - for s in c:gmatch("[^ ]+") do - t[unicodes[s]] = true - end - cache[c] = t - result[#result+1] = t - else - result[#result+1] = cc - end - end - end - local lookupdata = otfdata.lookups[lookupname] - if not lookupdata then - logs.error("otf process", string.format("missing lookupdata table %s",lookupname)) - elseif lookupdata.rules then - local rules = lookupdata.rules - local center_match = fonts.otf.center_match - for nofrules=1,#rules do - local rule = rules[nofrules] - local coverage = rule.coverage - if coverage and coverage.current then - local current, before, after, sequence = coverage.current, coverage.before, coverage.after, { } - if before then - uncover(before,sequence) - end - local start = #sequence + 1 - uncover(current,sequence) - local stop = #sequence - if after then - uncover(after,sequence) - end - if sequence[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 - -- this may be wrong; we cannot copy inside the for loop (out of memory with hz); - -- so we may end up with a different usage of sequence in the chainproc handlers - sequence = table.copy(sequence) - -- we trigger on the first character in current - for unic, _ in pairs(sequence[start]) 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, sequence, start, stop, 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_reversecontextchain -> 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 - anchors = { } - featuredata[lookupname] = anchors - local otfdata = tfmdata.shared.otfdata - local unicodes = otfdata.luatex.unicodes - local validanchors = { } - local glyphs = otfdata.glyphs - local trace = fonts.otf.trace_features - if otfdata.anchor_classes then - local classes = otfdata.anchor_classes - for k=1,#classes do - local class = classes[k] - if class.lookup == lookupname then - if trace then - logs.report("define otf",string.format("%s:%s anchor -> %s",kind,lookupname,class.name)) - end - validanchors[class.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.unicode] = t - end - end - end - end ---~ if kind == "mkmk" then print(lookupname,table.serialize(anchors)) 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 - - -- ["kerns"]={ { ["char"]="ytilde", ["lookup"]="pp_l_1_s", ["off"]=-83, ... - -- ["mykerns"] = { ["pp_l_1_s"] = { [67] = -28, ... - - function fonts.otf.features.prepare.gpos_pair(tfmdata,kind,lookupname) - local featuredata = tfmdata.shared.featuredata[kind] - local kerns = featuredata[lookupname] - if not kerns then - local trace = fonts.otf.trace_features - featuredata[lookupname] = { } - kerns = featuredata[lookupname] - local otfdata = tfmdata.shared.otfdata - local unicodes = otfdata.luatex.unicodes - local glyphs = otfdata.glyphs - -- ff has isolated kerns in a separate table - for k,o in pairs(glyphs) do - local list = o.mykerns - if list then - local omk = list[lookupname] - if omk then - local one = o.unicode - for char, off in pairs(omk) do - local two = char - local krn = kerns[one] - if krn then - krn[two] = off - else - kerns[one] = { two = off } - end - if trace then - logs.report("define otf",string.format("feature %s kern pair %s - %s",kind,one,two)) - end - end - end - elseif o.kerns then - local one = o.unicode - local okerns = o.kerns - for ok=1,#okerns do - local k = okerns[ok] - if k.lookup == lookupname then - local char = k.char - if char then - local two = unicodes[char] - local krn = kerns[one] - if krn then - krn[two] = k.off - else - kerns[one] = { two = k.off } - end - if trace then - logs.report("define otf",string.format("feature %s kern pair %s - %s",kind,one,two)) - end - end - end - end - end - list = o.lookups - if list then - local one = o.unicode - for lookup,ps in pairs(list) do - if lookup == lookupname then - for i=1,#ps do - local p = ps[i] - if p[1] == 'pair' then - local two = unicodes[p[2]] - local krn = kerns[one] - if krn then - krn[two] = p - else - kerns[one] = { two = p } - end - if trace then - logs.report("define otf",string.format("feature %s kern pair %s - %s",kind,one,two)) - 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 - - local prepare = fonts.otf.features.prepare.feature - - function fonts.initializers.node.otf.aalt(tfm,value) return prepare(tfm,'aalt',value) end - function fonts.initializers.node.otf.abvm(tfm,value) return prepare(tfm,'abvm',value) end - 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.blwm(tfm,value) return prepare(tfm,'blwm',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.fwid(tfm,value) return prepare(tfm,'fwid',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.hwid(tfm,value) return prepare(tfm,'hwid',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 - - -- todo: use nodes helpers - - local glyph = node.id('glyph') - local glue = node.id('glue') - local kern = node.id('kern') - local disc = node.id('disc') - - 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 - local scale = tex.scale - - fonts.otf.features.process = { } - - -- we share some 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 - - -- we cheat a bit and assume that a font,attr combination are kind of ranged - - local context_setups = fonts.define.specify.context_setups - local context_numbers = fonts.define.specify.context_numbers - - -- 1 loop over glyphs loop over lookups, quit at match - -- 2 loop over glyphs loop over lookups, continue at match - -- 3 loop over lookups loop over glyphs - - fonts.otf.strategy = 2 - - function fonts.otf.features.process.feature(head,font,attr,kind,attribute) - tfmdata = fontdata[font] - local shared = tfmdata.shared - otfdata = shared.otfdata - characters = tfmdata.characters - marks = otfdata.luatex.marks - glyphs = otfdata.glyphs - currentfont = font - local script, language, strategy - if attr and attr > 0 then - local features = context_setups[context_numbers[attr]] - language, script, strategy = features.language or "dflt", features.script or "dflt", features.strategy or fonts.otf.strategy - else - language, script, strategy = tfmdata.language or "dflt", tfmdata.script or "dflt", tfmdata.strategy or fonts.otf.strategy - end - local fullkind = kind .. script .. language - local lookuptable = shared.lookuptable[fullkind] - if lookuptable then - -- local strategy = fonts.otf.strategy - local types = otfdata.luatex.name_to_type - local start, done, ok = head, false, false - local processes = shared.processes[fullkind] - if #processes == 1 then - local p = processes[1] - while start do -- evt splitsen - if start.id == glyph then - if start.subtype<256 and start.font == font and - (not attr or has_attribute(start,0,attr)) and -- dynamic feature - (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 - elseif start.id == glue and p[5] then - local pp = p[3] -- all lookups - local pc = pp[32] -- space - 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 - elseif strategy == 3 then - for i=1,#processes do local p = processes[i] - local pp = p[3] - start = head - while start do - if start.id == glyph then - if start.subtype<256 and start.font == font and - (not attr or has_attribute(start,0,attr)) and -- dynamic feature - (not attribute or has_attribute(start,state,attribute)) then - 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 - end -- else - if start then start = start.next end - else - start = start.next - end - else - start = start.next - end - elseif start.id == glue then - if p[5] then -- chain - local pc = pp[32] - if pc then - start, ok = p[1](start,kind,p[2],pc,p[3],p[4]) - if ok then - done = true - end - if start then start = start.next end - else - start = start.next - end - else - start = start.next - end - else - start = start.next - end - end - end - else - while start do - if start.id == glyph then - if start.subtype<256 and start.font == font and - (not attr or has_attribute(start,0,attr)) and -- dynamic feature - (not attribute or has_attribute(start,state,attribute)) then - local chr = start.char -- used ? - for i=1,#processes do local p = processes[i] - local pp = p[3] ---~ local pc = pp[chr] - 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 - if strategy == 1 then - break - end - end -- else - if not start then - break - end - end - end - if start then start = start.next end - elseif start.id == glue then - for i=1,#processes do local p = processes[i] - if p[5] then -- chain - local pp = p[3] - local pc = pp[32] - if pc then - start, ok = p[1](start,kind,p[2],pc,pp,p[4]) - if ok then - done = true - if strategy == 1 then - break - end - end - if not start then - break - end - end - end - end - if start then start = start.next end - else - start = start.next - end - else - start = start.next - end - end - end - return head, done - else - return head, false - end - end - - -- we can assume that languages that use marks are not hyphenated - -- we can also assume that at most one discretionary is present - - local function toligature(start,stop,char,markflag,discfound) -- brr head - if start ~= stop then - if discfound then - local lignode = node.copy(start) - lignode.font = start.font - lignode.char = char - lignode.subtype = 2 - start = node.do_ligature_n(start, stop, lignode) - if start.id == disc then - local prev = start.prev - start = start.next - end - else - local deletemarks = markflag ~= "mark" - start.components = node.copy_list(start,stop) - node.slide(start.components) - -- todo: components - start.subtype = 2 - 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 next do - if next == stop or deletemarks or marks[next.char] then - local crap = next - local np, nn = next.prev, next.next - np.next = nn - if nn then - nn.prev = np - end - if next == stop then - stop = crap.prev - node.free(crap) - break - else - next = nn - node.free(crap) - end - else - next = nn - end - end - end - end - return start - end - - function fonts.otf.features.process.gsub_single(start,kind,lookupname,replacements) - if replacements then - if fonts.otf.trace_replacements then - report("otf process",format("%s:%s replacing 0x%04X by 0x%04X",kind,lookupname,start.char,replacements)) - end - start.char = replacements - return start, true - else - return start, false - end - end - - function fonts.otf.features.process.gsub_alternate(start,kind,lookupname,alternatives) - if alternatives then - if fonts.otf.trace_replacements then - report("otf process",format("%s:%s alternative 0x%04X => %s",kind,lookupname,start.char,table.hexed(alternatives))) - end - start.char = alternatives[1] -- will be preference - return start, true - else - return start, false - end - end - - function fonts.otf.features.process.gsub_multiple(start,kind,lookupname,multiples) - if multiples then - if fonts.otf.trace_replacements then - report("otf process",format("%s:%s multiple 0x%04X => %s",kind,lookupname,start.char,table.hexed(multiples))) - end - start.char = multiples[1] - if #multiples > 1 then - for k=2,#multiples do - local n = node.copy(start) - local sn = start.next - n.char = multiples[k] - n.next = sn - n.prev = start - if sn then - sn.prev = n - end - start.next = n - start = n - end - 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, discfound = start.next, nil, false - while s do - local id = s.id - if id == glyph and s.subtype<256 then - if s.font == currentfont then - 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 - else - break - end - elseif id == disc then - discfound = true - s = s.next - else - break - end - end - if stop and ligatures[2] then - start = toligature(start,stop,ligatures[2],flags[1],discfound) - if fonts.otf.trace_ligatures then - report("otf process",format("%s: inserting ligature 0x%04X (%s)",kind,start.char,utf.char(start.char))) - end - return start, true - end - return start, false - end - - function fonts.otf.features.process.gpos_mark2base(start,kind,lookupname,m_anchors,b_anchors) - local markchar = start.char - if marks[markchar] then - local markanchors = m_anchors['mark'] - if markanchors then - local component = start.prev - while component and component.id == glyph and component.subtype<256 and component.font == currentfont do - local basechar = component.char - if marks[basechar] then - component = component.prev - else - local baseanchors = b_anchors[basechar] - if baseanchors then - baseanchors = baseanchors['basechar'] - if baseanchors then - for anchor, ma in pairs(markanchors) do - local ba = baseanchors[anchor] - if ba then - local factor = tfmdata.factor - local dx, dy = scale(ba[1]-ma[1],factor), scale(ba[2]-ma[2],factor) - start.xoffset, start.yoffset = component.xoffset - dx, component.yoffset + dy - if fonts.otf.trace_anchors then - report("otf process",format("%s: anchoring mark 0x%04X to basechar 0x%04X => (%s,%s) => (%s,%s)", - kind,markchar,basechar,dx,dy,start.xoffset,start.yoffset)) - end - return start, true - end - end - end - end - break - end - end - end - end - return start, false - end - - function fonts.otf.features.process.gpos_mark2ligature(start,kind,lookupname,m_anchors,b_anchors) -- maybe use copies - local markchar = start.char - if marks[markchar] then - local markanchors = m_anchors['mark'] - if markanchors then - local component = start.prev - while component and component.id == glyph and component.subtype<256 and component.font == currentfont do - local basechar = component.char - if marks[basechar] then - component = component.prev - else - local baseanchors = b_anchors[basechar] - if baseanchors then - baseanchors = baseanchors['baselig'] - if baseanchors then - for anchor, ma in pairs(markanchors) do - local ba = baseanchors[anchor] - if ba then - local n = has_attribute(start,marknumber) - ba = ba[n] - if ba then - local factor = tfmdata.factor - local dx, dy = scale(ba[1]-ma[1],factor), scale(ba[2]-ma[2],factor) - start.xoffset, start.yoffset = component.xoffset - dx, component.yoffset + dy - if fonts.otf.trace_anchors then - report("otf process",format("%s: anchoring mark 0x%04X to baseligature 0x%04X => (%s,%s) => (%s,%s)", - kind,markchar,basechar,dx,dy,component.xoffset,component.yoffset)) - end - return start, true - end - end - end - end - end - break - end - end - return start, done - end - end - return start, false - end - - function fonts.otf.features.process.gpos_mark2mark(start,kind,lookupname,b_anchors,m_anchors) - local basemarkchar = start.char ---~ print(lookupname) - if marks[basemarkchar] then ---~ print('') ---~ print('basemarkchar',basemarkchar) ---~ print('basemarkanchors', table.serialize(b_anchors)) - local baseanchors = b_anchors['basemark'] - if baseanchors then - local component = start.next - while component and component.id == glyph and component.subtype<256 and component.font == currentfont do - local markchar = component.char - if not marks[markchar] then - break - else ---~ print('markchar',markchar) - local basemarkattr = has_attribute(start, marknumber) or 1 - local markattr = has_attribute(component,marknumber) or 1 ---~ print(basemarkattr,markattr) - if basemarkattr == markattr then -- still needed? ---~ print('markanchors *', table.serialize(m_anchors)) - - local markanchors = m_anchors[markchar] - if markanchors then ---~ print('markanchors') - local markanchor = markanchors['mark'] - if markanchor then - for anchor,ma in pairs(markanchor) do - local ba = baseanchors[anchor] - if ba then - local factor = tfmdata.factor - local dx, dy = scale(ba[1]-ma[1],factor), scale(ba[2]-ma[2],factor) - component.xoffset, component.yoffset = start.xoffset - dx, start.yoffset + dy - if fonts.otf.trace_anchors then - report("otf process",format("%s:%s:%s anchoring mark 0x%04X to basemark 0x%04X => (%s,%s) => (%s,%s)", - kind,anchor,markattr,markchar,basemarkchar,dx,dy,component.xoffset,component.yoffset)) - end - return start, true - end - end - end - end - component = component.next - end - end - end - end - end - return start, false - end - - -- the following can be optimized, also, we can share the table (no need to collect) - - function fonts.otf.features.process.gpos_cursive(start,kind,lookupname,exitanchors,anchors) - local trace = fonts.otf.trace_anchors - local next, done, x, y, total_x, total_y, tx, ty, first = start.next, false, 0, 0, 0, 0, { }, { }, nil - local factor = tfmdata.factor - local function finish() - local i = 0 - while first and first.id == glyph do - if marks[first.char] then - first = first.next - else - i = i + 1 - first.yoffset = scale(total_y, factor) - if fonts.otf.trace_cursive then - report("otf process",format("%s:%s move 0x%04X cursive (%s,%s)",kind,lookupname,first.char,"?",total_y)) - end - if first == next then - break - else - total_y = total_y - (ty[i] or 0) - first = first.next - end - end - end - x, y, total_x, total_y, tx, ty, first = 0, 0, 0, 0, { }, { }, nil - end - while next do - if next.id == glyph and next.subtype<256 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 - local dx, dy = exit[1] + entry[1], -exit[2] + entry[2] - tx[#tx+1], ty[#ty+1] = dx, dy - total_x, total_y = total_x + dx, total_y + dy - 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 - -- to be optimized - local trace = fonts.otf.trace_kerns - local factor = tfmdata.factor - while next and next.id == glyph and next.subtype<256 and next.font == currentfont do - local cn = characters[next.char] - if not cn or cn.description.class == 'mark' then - prev = next - next = next.next - else - local krn = basekerns[next.char] - if not krn then - -- skip - elseif type(krn) == "table" then - local a, b = krn[3], krn[7] - if a and a ~= 0 then - local k = nodes.kern(scale(a,factor)) - k.next = next - k.prev = prev - prev.next = k - next.prev = k - if trace then - -- todo - end - end - if b and b ~= 0 then - report("otf process","we need to do something with the second kern xoff " .. b) - end - else - -- todo, just start, next = node.insert_before(head,next,nodes.kern(scale(kern,factor))) - if fonts.otf.trace_kerns then - report("otf process",format("%s: inserting kern %s between 0x%04X and 0x%04X",kind,krn,prev.char,next.char)) - end - local k = nodes.kern(scale(krn,factor)) - k.next = next - k.prev = prev - prev.next = k - next.prev = k - end - break - end - end - return start, done - end - - local 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. - - function chainprocs.gsub_single(start,stop,kind,lookupname,sequence,f,l,lookups) - local trace = fonts.otf.trace_replacements - local c, r = trace and { }, trace and { } - local lookup, index, current = 1, f, start - while current ~= nil do - if current.id == glyph then -- test for more ? - local char = current.char - local cacheslot = sequence[index] - local replacement = cacheslot[char] - if replacement == true then - if lookups then - local looks = glyphs[tfmdata.characters[char].description.index].lookups -- SLOW, USE OTFDATA - if looks then - local glyphlookups = otfdata.luatex.internals[lookups[lookup]].lookups - local unicodes = otfdata.luatex.unicodes - for gl=1,#glyphlookups do - local lv = looks[glyphlookups[gl]] - if lv then - replacement = unicodes[lv[1][2]] or char - cacheslot[char] = replacement - break - end - end - else - replacement, cacheslot[char] = char, char - end - else - replacement, cacheslot[char] = char, char - end - end - if trace then - c[#c+1], r[#r+1] = char, replacement - end - current.char = replacement - if current == stop then - break - else - current, lookup, index = current.next, lookup + 1, index + 1 - end - elseif current == stop then - break - else - current = current.next - end - end - if trace then - report("otf chain",format("%s: single replacement %s by %s",kind,table.hexed(c),table.hexed(r))) - end - return start - end - - function chainprocs.gsub_multiple(start,stop,kind,lookupname,sequence,f,l,lookups) - local char = start.char - local cacheslot = sequence[f] -- [1] - local replacement = cacheslot[char] - if replacement == true then - if lookups then - local looks = glyphs[tfmdata.characters[char].description.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][2]:gmatch("[^ ]+") 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 0x%04X by multiple 0x%04X",kind,char,table.hexed(replacement))) - end - start.char = replacement[1] - if #replacement > 1 then - for k=2,#replacement do - local n = node.copy(start) - local sn = start.next - n.char = replacement[k] - n.next = sn - n.prev = start - if sn then - sn.prev = n - end - start.next = n - start = n - end - end - return start - end - - function chainprocs.gsub_alternate(start,stop,kind,lookupname,sequence,f,l,lookups) - local char = start.char - local cacheslot = sequence[f] -- [1] - local replacement = cacheslot[char] - if replacement == true then - if lookups then - local looks = glyphs[tfmdata.characters[char].description.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][2]:gmatch("[^ ]+") 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 0x%04X by alternate",kind,char)) - end - start.char = replacement[1] - return start - end - - function chainprocs.gsub_ligature(start,stop,kind,lookupname,sequence,f,l,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) -- double cached ? - end - local lookups = otfdata.luatex.internals[lookups[1]].lookups - local ligaturecache = featurecache[kind] - local trace = fonts.otf.trace_ligatures - for i=1,#lookups do - local ligatures = ligaturecache[lookups[i]] - if ligatures and ligatures[start.char] then - ligatures = ligatures[start.char] - local s, discfound = start.next, false - while s do - local id = s.id - if id == disc then - s = s.next - discfound = true - elseif characters[s.char].description.class == 'mark' then -- marks - 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 trace then - if start == stop then - report("otf chain",format("%s: replacing character 0x%04X by ligature 0x%04X",kind,start.char,ligatures[2])) - else - report("otf chain",format("%s: replacing character 0x%04X upto 0x%04X by ligature 0x%04X",kind,start.char,stop.char,ligatures[2])) - end - end - return toligature(start,stop,ligatures[2],flags[1],discfound) - end - break - end - end - end - return stop - end - - -- weird, mkmk can have a mark2base, in idris font - - function chainprocs.gpos_mark2base(start,stop,kind,lookupname,sequence,f,l,lookups,flags) - -- dynamic resolver - local markchar = start.char - if marks[markchar] then - local anchortag = sequence[f][markchar] - if anchortag == true then - local ok = false - local classes = otfdata.anchor_classes - local lookups = otfdata.luatex.internals[lookups[1]].lookups - for k=1,#classes do - local v = classes[k] - if v.lookup == lookups[1] then -- let's gamble for uniqueness: and v.type == kind then - anchortag = v.name - sequence[f][markchar] = anchortag - ok = true - break - end - end - if not ok and fonts.otf.trace_anchors then - report("otf chain",format("%s: no matching mark2base anchor class for 0x%04X, lookup %s",kind,markchar,lookups[1])) - end - end - if anchortag ~= true then - local component = start.prev - while component and component.id == glyph and component.subtype<256 and component.font == currentfont do - local basechar = component.char - if marks[basechar] then - component = component.prev - else - local bglyph = glyphs[characters[basechar].description.index] -- startchar - local baseanchors = bglyph.anchors['basechar'] - if baseanchors then - local ba = baseanchors[anchortag] - if ba then - local mglyph = glyphs[characters[markchar].description.index] - local markanchors = mglyph.anchors['mark'] - if markanchors then - local ma = markanchors[anchortag] - if ma then - local factor = tfmdata.factor - local dx, dy = scale(ba[1]-ma[1],factor), scale(ba[2]-ma[2],factor) - start.xoffset, start.yoffset = component.xoffset - dx, component.yoffset + dy - if fonts.otf.trace_anchors then - report("otf chain",format("%s: anchoring mark 0x%04X to basechar 0x%04X => (%s,%s) => (%s,%s)", - kind,markchar,basechar,dx,dy,start.xoffset,start.yoffset)) - end - return start, true - end - end - end - end - break - end - end - end - end - return start, false - end - - function chainprocs.gpos_mark2ligature(start,stop,kind,lookupname,sequence,f,l,lookups,flags) - -- dynamic resolver - local markchar = start.char - if marks[markchar] then - local anchortag = sequence[f][markchar] - if anchortag == true then - local classes = otfdata.anchor_classes - local lookups = otfdata.luatex.internals[lookups[1]].lookups - local ok = false - for k=1,#classes do - local v = classes[k] - if v.lookup == lookups[1] then -- and v.type == kind then - anchortag = v.name - sequence[f][markchar] = anchortag - ok = true - break - end - end - if not ok and fonts.otf.trace_anchors then - report("otf chain",format("%s: no matching mark2ligature anchor class for 0x%04X, lookup %s",kind,markchar,lookups[1])) - end - end - if anchortag ~= true then - local component = start.prev - while component and component.id == glyph and component.subtype<256 and component.font == currentfont do - local basechar = component.char - if marks[basechar] then - component = component.prev - else - local bglyph = glyphs[characters[basechar].description.index] -- startchar - local baseanchors = bglyph.anchors['baselig'] - if baseanchors then - local ba = baseanchors[anchortag] - if ba then - local n = has_attribute(start,marknumber) - ba = ba[n] -- ok ? - if ba then - local mglyph = glyphs[characters[markchar].description.index] - local markanchors = mglyph.anchors['mark'] - if markanchors then - local ma = markanchors[anchortag] - if ma then - local factor = tfmdata.factor - local dx, dy = scale(ba[1]-ma[1],factor), scale(ba[2]-ma[2],factor) - start.xoffset, start.yoffset = component.xoffset - dx, component.yoffset + dy - if fonts.otf.trace_anchors then - report("otf chain",format("%s: anchoring mark 0x%04X to baseligature 0x%04X => (%s,%s) => (%s,%s)", - kind,basechar,markchar,dx,dy,start.xoffset,start.yoffset)) - end - return start, true - end - end - end - end - end - break - end - end - end - end - return start, false - end - - -- to be checked - - function chainprocs.gpos_mark2mark(start,stop,kind,lookupname,sequence,f,l,lookups) - local component = start.next - if component and component.id == glyph and component.subtype<256 and component.font == currentfont and marks[component.char] then - local markchar = start.char - local anchortag = sequence[f][markchar] -- [1][char] - if anchortag == true then - local classes = otfdata.anchor_classes - local ok = false - for k=1,#classes do - local v = classes[k] - if v.lookup == lookupname then -- and v.type == kind then - anchortag = v.name - sequence[f][markchar] = anchortag - ok = true - break - end - end - if not ok and fonts.otf.trace_anchors then - report("otf chain",format("%s: no matching mark2mark anchor class for 0x%04X, lookup %s",kind,markchar,lookups[1])) - end - end - if anchortag ~= true then - -- the following may have been be spoiled while idrising the other ones - local markattr = has_attribute(start, marknumber) or 1 -- i need to check this ! 1 is new ! - local baseattr = has_attribute(component,marknumber) or 1 -- i need to check this ! 1 is new ! - if baseattr == markattr then - local glyph = glyphs[characters[markchar].description.index] - if glyph.anchors and glyph.anchors[anchortag] then - local trace = fonts.otf.trace_anchors - local done = false - local baseanchors = glyph.anchors['basemark'][anchortag] - while true do - local basechar = component.char - local charnext = characters[basechar] - local markanchors = glyphs[charnext.description.index].anchors['mark'][anchortag] - if markanchors then - for anchor,data in pairs(markanchors) do - local ba = baseanchors[anchor] - if ba then - local factor = tfmdata.factor - local dx, dy = scale(ba[1]-ma[1],factor), scale(ba[2]-ma[2],factor) - start.xoffset, start.yoffset = component.xoffset - dx, component.yoffset + dy - if fonts.otf.trace_anchors then - report("otf chain",format("%s: anchoring mark 0x%04X to basemark 0x%04X => (%s,%s) => (%s,%s)", - kind,markchar,basechar,dx,dy,component.xoffset,component.yoffset)) - end - done = true - break - end - end - end - component = component.next - if component and component.id == glyph and component.subtype<256 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 start, done - end - end - end - end - return start, false - end - - function chainprocs.gpos_cursive(start,stop,kind,lookupname,sequence,f,l,lookups) - report("otf chain","chainproc gpos_cursive not yet supported") - return start - end - function chainprocs.gpos_single(start,stop,kind,lookupname,sequence,f,l,lookups) - report("otf process","chainproc gpos_single not yet supported") - return start - end - function chainprocs.gpos_pair(start,stop,kind,lookupname,sequence,f,l,lookups) - report("otf process","chainproc gpos_pair not yet supported") - return start - end - - function chainprocs.self(start,stop,kind,lookupname,sequence,f,l,lookups) - report("otf process","self refering lookup cannot happen") - return stop - end - - local zwnj = 0x200C - local zwj = 0x200D - - -- what pointer to return, spec says stop - - function fonts.otf.features.process.contextchain(start,kind,lookupname,contextdata) - local contexts, flags, done = contextdata.lookups, contextdata.flags, false - local skipmark, skipligature, skipbase = unpack(flags) -- unpack slower than assignment - for k=1,#contexts do - local match, next, last = true, start, start - local rule, lookuptype, sequence, f, l, lookups = unpack(contexts[k]) -- unpack is slow - local s = #sequence - if s == 1 then - match = next.id == glyph and next.subtype<256 and next.font == currentfont and sequence[1][next.char] - else - -- todo: better space check (maybe check for glue) - local n = f - while n <= l do - if last then - local id = last.id - if id == glyph and last.subtype<256 and last.font == currentfont then - local char = last.char - local class = characters[char].description.class - if class == skipmark or class == skipligature or class == skipbase then - -- skip 'm - last = last.next - elseif sequence[n][char] then - if n < l then - last = last.next - end - n = n + 1 - else - match = false break - end - elseif id == disc then -- what to do with kerns? - last = last.next - else - match = false break - end - else - match = false break - end - end - if match and f > 1 then - local prev = start.prev - if prev then - if f == 2 then - match = prev.id == glyph and prev.subtype<256 and prev.font == currentfont and sequence[1][prev.char] - else - local n = f-1 - while n >= 1 do - if prev then - local id = prev.id - if id == glyph and prev.subtype<256 and prev.font == currentfont then -- normal char - local char = prev.char - local class = characters[char].description.class - if class == skipmark or class == skipligature or class == skipbase then - -- skip 'm - elseif sequence[n][char] then - n = n -1 - else - match = false break - end - elseif id == disc then - -- skip 'm - elseif sequence[n][32] then - n = n -1 - else - match = false break - end - prev = prev.prev - elseif sequence[n][32] then - n = n -1 - else - match = false break - end - end - end - elseif f == 2 then - match = sequence[1][32] - else - for n=f-1,1 do - if not sequence[n][32] then - match = false break - end - end - end - end - if match and s > l then - local next = last.next - if next then - if s-l == 1 then - match = next.id == glyph and next.subtype<256 and next.font == currentfont and sequence[s][next.char] - else - local n = l+ 1 - while n <= s do - if next then - local id = next.id - if id == glyph and next.subtype<256 and next.font == currentfont then -- normal char - local char = next.char - local class = characters[char].description.class - if class == skipmark or class == skipligature or class == skipbase then - -- skip 'm - elseif sequence[n][char] then - n = n + 1 - else - match = false break - end - elseif id == disc then - -- skip 'm - elseif sequence[n][32] then -- brrr - n = n + 1 - else - match = false break - end - next = next.next - elseif sequence[n][32] then - n = n + 1 - else - match = false break - end - end - end - elseif s-l == 1 then - match = sequence[s][32] - else - for n=l+1,s do - if not sequence[n][32] then - match = false break - end - end - end - end - end - if match then - local trace = fonts.otf.trace_contexts - if trace then - local char = start.char - report("otf chain",format("%s: rule %s of %s matches at char 0x%04X (%s) for (%s,%s,%s) chars, lookuptype %s",kind,rule,lookupname,char,utf.char(char),f-1,l-f+1,s-l,lookuptype)) - end - if lookups then - local cp = chainprocs[lookuptype] - if cp then - start = cp(start,last,kind,lookupname,sequence,f,l,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 - ---~ if true then ---~ if n < f then ---~ texio.write_nl(string.format("%s before %s %04x %s %s %s",lookupname,n,char,class,skipmark or "?",tostring(sequence[n][char]))) ---~ elseif n > l then ---~ texio.write_nl(string.format("%s after %s %04x %s %s %s",lookupname,n,char,class,skipmark or "?",tostring(sequence[n][char]))) ---~ else ---~ texio.write_nl(string.format("%s current %s %04x %s %s %s",lookupname,n,char,class,skipmark or "?",tostring(sequence[n][char]))) ---~ end ---~ end - ---~ elseif char == zwnj and sequence[n][32] then -- brrr - - -- this needs to be fixed ! ! ! ! ! ! ! ! - - function fonts.otf.features.process.reversecontextchain(start,kind,lookupname,contextdata) - -- PROBABLY WRONG, WE NEED TO WALK BACK OVER THE LIST - local done = false - local contexts = contextdata.lookups - local flags = contextdata.flags - local skipmark, skipligature, skipbase = unpack(flags) - for k=1,#contexts do - local match, next, first, last = true, start, start, start - local rule, lookuptype, sequence, f, l, lookups = unpack(contexts[k]) -- unpack is slow - if #sequence == 1 then - match = next.id == glyph and next.subtype<256 and next.font == currentfont and sequence[1][next.char] - else - local n, s = #sequence, 1 - while n > 0 do - if next then - local id = next.id - if id == glyph and next.subtype<256 and next.font == currentfont then -- normal char - local char = next.char - local class = characters[char].description.class - if class == skipmark or class == skipligature or class == skipbase then - -- skip - elseif sequence[n][char] then - if n == f then - first = next -- ok ? - end - if n == l then - last = next -- ok ? - end - n = n - 1 - else - match = false break - end - elseif id == disc then - -- skip - elseif not sequence[n][32] then -- brrr - match = false break - end - next = next.next - elseif sequence[n][32] then - n = n - 1 - else - match = false break - end - end - end - if match then - local trace = fonts.otf.trace_contexts - if trace then - local char = first.char - report("otf reverse chain",format("%s: rule %s of %s matches, replacing starts at char 0x%04X (%s) lookuptype %s",kind,rule,lookupname,char,utf.char(char),lookuptype)) - end - if lookups then - local cp = chainprocs[lookuptype] - if cp then - if start == first then - start = cp(first,last,kind,lookupname,sequence,f,l,lookups,flags) - else - first = cp(first,last,kind,lookupname,sequence,f,l,lookups,flags) - end - 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 - -do - - local process = fonts.otf.features.process.feature - - function fonts.methods.node.otf.aalt(head,font,attr) return process(head,font,attr,'aalt') end - function fonts.methods.node.otf.abvm(head,font,attr) return process(head,font,attr,'abvm') end - function fonts.methods.node.otf.afrc(head,font,attr) return process(head,font,attr,'afrc') end - function fonts.methods.node.otf.akhn(head,font,attr) return process(head,font,attr,'akhn') end - function fonts.methods.node.otf.blwm(head,font,attr) return process(head,font,attr,'blwm') end - function fonts.methods.node.otf.c2pc(head,font,attr) return process(head,font,attr,'c2pc') end - function fonts.methods.node.otf.c2sc(head,font,attr) return process(head,font,attr,'c2sc') end - function fonts.methods.node.otf.calt(head,font,attr) return process(head,font,attr,'calt') end - function fonts.methods.node.otf.case(head,font,attr) return process(head,font,attr,'case') end - function fonts.methods.node.otf.ccmp(head,font,attr) return process(head,font,attr,'ccmp') end - function fonts.methods.node.otf.clig(head,font,attr) return process(head,font,attr,'clig') end - function fonts.methods.node.otf.cpsp(head,font,attr) return process(head,font,attr,'cpsp') end - function fonts.methods.node.otf.cswh(head,font,attr) return process(head,font,attr,'cswh') end - function fonts.methods.node.otf.curs(head,font,attr) return process(head,font,attr,'curs') end - function fonts.methods.node.otf.dlig(head,font,attr) return process(head,font,attr,'dlig') end - function fonts.methods.node.otf.dnom(head,font,attr) return process(head,font,attr,'dnom') end - function fonts.methods.node.otf.expt(head,font,attr) return process(head,font,attr,'expt') end - function fonts.methods.node.otf.fin2(head,font,attr) return process(head,font,attr,'fin2') end - function fonts.methods.node.otf.fin3(head,font,attr) return process(head,font,attr,'fin3') end - function fonts.methods.node.otf.fina(head,font,attr) return process(head,font,attr,'fina',3) end - function fonts.methods.node.otf.frac(head,font,attr) return process(head,font,attr,'frac') end - function fonts.methods.node.otf.fwid(head,font,attr) return process(head,font,attr,'fwid') end - function fonts.methods.node.otf.haln(head,font,attr) return process(head,font,attr,'haln') end - function fonts.methods.node.otf.hist(head,font,attr) return process(head,font,attr,'hist') end - function fonts.methods.node.otf.hkna(head,font,attr) return process(head,font,attr,'hkna') end - function fonts.methods.node.otf.hlig(head,font,attr) return process(head,font,attr,'hlig') end - function fonts.methods.node.otf.hngl(head,font,attr) return process(head,font,attr,'hngl') end - function fonts.methods.node.otf.hwid(head,font,attr) return process(head,font,attr,'hwid') end - function fonts.methods.node.otf.init(head,font,attr) return process(head,font,attr,'init',1) end - function fonts.methods.node.otf.isol(head,font,attr) return process(head,font,attr,'isol',4) end - function fonts.methods.node.otf.ital(head,font,attr) return process(head,font,attr,'ital') end - function fonts.methods.node.otf.jp78(head,font,attr) return process(head,font,attr,'jp78') end - function fonts.methods.node.otf.jp83(head,font,attr) return process(head,font,attr,'jp83') end - function fonts.methods.node.otf.jp90(head,font,attr) return process(head,font,attr,'jp90') end - function fonts.methods.node.otf.kern(head,font,attr) return process(head,font,attr,'kern') end - function fonts.methods.node.otf.liga(head,font,attr) return process(head,font,attr,'liga') end - function fonts.methods.node.otf.lnum(head,font,attr) return process(head,font,attr,'lnum') end - function fonts.methods.node.otf.locl(head,font,attr) return process(head,font,attr,'locl') end - function fonts.methods.node.otf.mark(head,font,attr) return process(head,font,attr,'mark') end - function fonts.methods.node.otf.med2(head,font,attr) return process(head,font,attr,'med2') end - function fonts.methods.node.otf.medi(head,font,attr) return process(head,font,attr,'medi',2) end - function fonts.methods.node.otf.mgrk(head,font,attr) return process(head,font,attr,'mgrk') end - function fonts.methods.node.otf.mkmk(head,font,attr) return process(head,font,attr,'mkmk') end - function fonts.methods.node.otf.nalt(head,font,attr) return process(head,font,attr,'nalt') end - function fonts.methods.node.otf.nlck(head,font,attr) return process(head,font,attr,'nlck') end - function fonts.methods.node.otf.nukt(head,font,attr) return process(head,font,attr,'nukt') end - function fonts.methods.node.otf.numr(head,font,attr) return process(head,font,attr,'numr') end - function fonts.methods.node.otf.onum(head,font,attr) return process(head,font,attr,'onum') end - function fonts.methods.node.otf.ordn(head,font,attr) return process(head,font,attr,'ordn') end - function fonts.methods.node.otf.ornm(head,font,attr) return process(head,font,attr,'ornm') end - function fonts.methods.node.otf.pnum(head,font,attr) return process(head,font,attr,'pnum') end - function fonts.methods.node.otf.pref(head,font,attr) return process(head,font,attr,'pref') end - function fonts.methods.node.otf.pres(head,font,attr) return process(head,font,attr,'pres') end - function fonts.methods.node.otf.pstf(head,font,attr) return process(head,font,attr,'pstf') end - function fonts.methods.node.otf.rlig(head,font,attr) return process(head,font,attr,'rlig') end - function fonts.methods.node.otf.rphf(head,font,attr) return process(head,font,attr,'rphf') end - function fonts.methods.node.otf.salt(head,font,attr) return process(head,font,attr,'calt') end - function fonts.methods.node.otf.sinf(head,font,attr) return process(head,font,attr,'sinf') end - function fonts.methods.node.otf.smcp(head,font,attr) return process(head,font,attr,'smcp') end - function fonts.methods.node.otf.smpl(head,font,attr) return process(head,font,attr,'smpl') end - function fonts.methods.node.otf.ss01(head,font,attr) return process(head,font,attr,'ss01') end - function fonts.methods.node.otf.ss02(head,font,attr) return process(head,font,attr,'ss02') end - function fonts.methods.node.otf.ss03(head,font,attr) return process(head,font,attr,'ss03') end - function fonts.methods.node.otf.ss04(head,font,attr) return process(head,font,attr,'ss04') end - function fonts.methods.node.otf.ss05(head,font,attr) return process(head,font,attr,'ss05') end - function fonts.methods.node.otf.ss06(head,font,attr) return process(head,font,attr,'ss06') end - function fonts.methods.node.otf.ss07(head,font,attr) return process(head,font,attr,'ss07') end - function fonts.methods.node.otf.ss08(head,font,attr) return process(head,font,attr,'ss08') end - function fonts.methods.node.otf.ss09(head,font,attr) return process(head,font,attr,'ss09') end - function fonts.methods.node.otf.subs(head,font,attr) return process(head,font,attr,'subs') end - function fonts.methods.node.otf.sups(head,font,attr) return process(head,font,attr,'sups') end - function fonts.methods.node.otf.swsh(head,font,attr) return process(head,font,attr,'swsh') end - function fonts.methods.node.otf.titl(head,font,attr) return process(head,font,attr,'titl') end - function fonts.methods.node.otf.tnam(head,font,attr) return process(head,font,attr,'tnam') end - function fonts.methods.node.otf.tnum(head,font,attr) return process(head,font,attr,'tnum') end - function fonts.methods.node.otf.trad(head,font,attr) return process(head,font,attr,'trad') end - function fonts.methods.node.otf.unic(head,font,attr) return process(head,font,attr,'unic') end - function fonts.methods.node.otf.zero(head,font,attr) return process(head,font,attr,'zero') end - -end - --- common stuff - -function fonts.otf.features.language(tfmdata,value) - if value then - value = value:lower() - if fonts.otf.tables.languages[value] then - tfmdata.language = value - end - end -end - -function fonts.otf.features.script(tfmdata,value) - if value then - value = value:lower() - if fonts.otf.tables.scripts[value] then - tfmdata.script = value - end - end -end - -function fonts.otf.features.mode(tfmdata,value) - if value then - tfmdata.mode = value:lower() - end -end - -function fonts.otf.features.strategy(tfmdata,value) - if value then - tfmdata.strategy = tonumber(value) or fonts.otf.strategy - 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.base.otf.strategy = fonts.otf.features.strategy -- not needed - -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.strategy = fonts.otf.features.strategy - -do - - local tlig_list = { - endash = "hyphen hyphen", - emdash = "hyphen hyphen hyphen", ---~ quotedblleft = "quoteleft quoteleft", ---~ quotedblright = "quoteright quoteright", ---~ quotedblleft = "grave grave", ---~ quotedblright = "quotesingle quotesingle", ---~ quotedblbase = "comma comma", - } - local trep_list = { ---~ [0x0022] = 0x201D, - [0x0027] = 0x2019, ---~ [0x0060] = 0x2018, - } - - local tlig_feature = { - features = { { scripts = { { script = "DFLT", langs = { "dflt" }, } }, tag = "tlig", comment = "added bij mkiv" }, }, - name = "ctx_tlig", - subtables = { { name = "ctx_tlig_1" } }, - type = "gsub_ligature", - flags = { }, - always = true - } - local trep_feature = { - features = { { scripts = { { script = "DFLT", langs = { "dflt" }, } }, tag = "trep", comment = "added bij mkiv" }, }, - name = "ctx_trep", - subtables = { { name = "ctx_trep_1" } }, - type = "gsub_single", - flags = { }, - always = true - } - - function fonts.otf.enhance.enrich(data,filename) - for index, glyph in pairs(data.glyphs) do - local l = tlig_list[glyph.name] - if l then - local o = glyph.lookups or { } - o["ctx_tlig_1"] = { { "ligature", l, glyph.name } } - glyph.lookups = o - end - local r = trep_list[glyph.unicode] - if r then - local replacement = data.map.map[r] - if replacement then - local o = glyph.lookups or { } - o["ctx_trep_1"] = { { "substitution", data.glyphs[replacement].name } } --- - glyph.lookups = o - end - end - end - data.gsub = data.gsub or { } - logs.report("load otf","enhance: registering tlig feature") - table.insert(data.gsub,1,table.fastcopy(tlig_feature)) - logs.report("load otf","enhance: registering trep feature") - table.insert(data.gsub,1,table.fastcopy(trep_feature)) - end - - local prepare = fonts.otf.features.prepare.feature - local process = fonts.otf.features.process.feature - - fonts.otf.tables.features['tlig'] = 'TeX Ligatures' - fonts.otf.tables.features['trep'] = 'TeX Replacements' - - function fonts.initializers.node.otf.tlig(tfm,value) return prepare(tfm,'tlig',value) end - function fonts.initializers.node.otf.trep(tfm,value) return prepare(tfm,'trep',value) end - - function fonts.methods.node.otf.tlig(head,font,attr) return process(head,font,attr,'tlig') end - function fonts.methods.node.otf.trep(head,font,attr) return process(head,font,attr,'trep') end - - function fonts.initializers.base.otf.tlig(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'tlig',value) end - function fonts.initializers.base.otf.trep(tfm,value) fonts.otf.features.prepare_base_substitutions(tfm,'trep',value) end - -end - --- 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 - -fonts.initializers.base.otf.compose = fonts.initializers.common.compose -fonts.initializers.node.otf.compose = fonts.initializers.common.compose - --- temp hack, may change - -function fonts.initializers.base.otf.kern(tfmdata,value) - fonts.otf.features.prepare_base_kerns(tfmdata,'kern',value) -end - --- 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.unicode ---~ 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 - --- will move to font-tfm - -do - - local glyph = node.id('glyph') - local glue = node.id('glue') - local penalty = node.id('penalty') - - local fontdata = fonts.tfm.id - local set_attribute = node.set_attribute - local has_attribute = node.has_attribute - local state = attributes.numbers['state'] or 100 - - local fcs = fonts.color.set - local fcr = fonts.color.reset - - -- 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 - - local type = type - - local initializers, methods = fonts.analyzers.initializers, fonts.analyzers.methods - - function fonts.initializers.node.otf.analyze(tfmdata,value,attr) - if attr and attr > 0 then - script, language = a_to_script[attr], a_to_language[attr] - else - script, language = tfmdata.script, tfmdata.language - end - local action = initializers[script] - if action then - if type(action) == "function" then - return action(tfmdata,value) - else - local action = action[language] - if action then - return action(tfmdata,value) - end - end - end - return nil - end - - function fonts.methods.node.otf.analyze(head,font,attr) - local tfmdata = fontdata[font] - local script, language - if attr and attr > 0 then - script, language = a_to_script[attr], a_to_language[attr] - else - script, language = tfmdata.script, tfmdata.language - end - local action = methods[script] - if action then - if type(action) == "function" then - return action(head,font,attr) - else - action = action[language] - if action then - return action(head,font,attr) - end - 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 - - -- this info eventually will go into char-def - - local zwnj = 0x200C - local zwj = 0x200D - - local isol = { - [0x0621] = true, [zwnj] = 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, - [0x0640] = true, -- tadwil - [0x0641] = true, [0x0642] = true, [0x0643] = true, [0x0644] = true, [0x0645] = true, [0x0646] = true, [0x0647] = true, [0x0649] = true, [0x064A] = true, - [0x067E] = true, - [0x0686] = true, [zwj] = 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 - - function fonts.analyzers.methods.nocolor(head,font,attr) - for n in node.traverse(head,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,attr) -- 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.subtype<256 and current.font == font then - done = true - local char = current.char - local chardata = characters[char] -- some day we will make a characters.marks hash - if not chardata then -- this is also more efficient since it's shared - -- troubles - -- elseif char == zwj then - -- -- can probably be ignored, we could turn it into a kern or penalty - -- elseif char == zwnj then - -- -- acts like a space, we could turn it into a kern or penalty - -- finish() - elseif chardata.description.class == "mark" then - 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 - - -- han (chinese) (unfinished) - - -- this info eventually will go into char-def - - -- 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 - - local type = type - - local opening_parenthesis_hw = table.tohash { -- half width - 0x0028, - 0x005B, - 0x007B, - 0x2018, -- ‘ - 0x201C, -- “ - } - - local opening_parenthesis_fw = table.tohash { -- full width - 0x3008, -- 〈 Left book quote - 0x300A, -- 《 Left double book quote - 0x300C, -- 「 left quote - 0x300E, -- 『 left double quote - 0x3010, -- 【 left double book quote - 0x3014, -- 〔 left book quote - 0x3016, --〖 left double book quote - 0x3018, -- left tortoise bracket - 0x301A, -- left square bracket - 0x301D, -- reverse double prime qm - 0xFF08, -- ( left parenthesis - 0xFF3B, -- [ left square brackets - 0xFF5B, -- { left curve bracket - 0xFF62, -- left corner bracket - } - - local closing_parenthesis_hw = table.tohash { -- half width - 0x0029, - 0x005D, - 0x007D, - 0x2019, -- ’ right quote, right - 0x201D, -- ” right double quote - } - - local closing_parenthesis_fw = table.tohash { -- full width - 0x3009, -- 〉 book quote - 0x300B, -- 》 double book quote - 0x300D, -- 」 right quote, right - 0x300F, -- 』 right double quote - 0x3011, -- 】 right double book quote - 0x3015, -- 〕 right book quote - 0x3017, -- 〗 right double book quote - 0x3019, -- right tortoise bracket - 0x301B, -- right square bracket - 0x301E, -- double prime qm - 0x301F, -- low double prime qm - 0xFF09, -- ) right parenthesis - 0xFF3D, -- ] right square brackets - 0xFF5D, -- } right curve brackets - 0xFF63, -- right corner bracket - } - - local opening_vertical = table.tohash { - 0xFE35, 0xFE37, 0xFE39, 0xFE3B, 0xFE3D, 0xFE3F, 0xFE41, 0xFE43, 0xFE47, - } - - local closing_vertical = table.tohash { - 0xFE36, 0xFE38, 0xFE3A, 0xFE3C, 0xFE3E, 0xFE40, 0xFE42, 0xFE44, 0xFE48, - } - - local opening_punctuation_hw = table.tohash { -- half width - } - - local opening_punctuation_fw = table.tohash { - -- 0x2236, -- ∶ - -- 0xFF0C, -- , - } - - local closing_punctuation_hw = table.tohash { -- half width - 0x0021, -- ! - 0x002C, -- , - 0x002E, -- . - 0x003A, -- : - 0x003B, -- ; - 0x003F, -- ? - 0xFF61, -- hw full stop - } - - local closing_punctuation_fw = table.tohash { -- full width - 0x3001, -- 、 - 0x3002, -- 。 - 0xFF01, -- ! - 0xFF0C, -- , - 0xFF0E, -- . - 0xFF1A, -- : - 0xFF1B, -- ; - 0xFF1F, -- ? - } - - local non_starter = table.tohash { -- japanese - 0x3005, 0x3041, 0x3043, 0x3045, 0x3047, - 0x3049, 0x3063, 0x3083, 0x3085, 0x3087, - 0x308E, 0x3095, 0x3096, 0x309B, 0x309C, - 0x309D, 0x309E, 0x30A0, 0x30A1, 0x30A3, - 0x30A5, 0x30A7, 0x30A9, 0x30C3, 0x30E3, - 0x30E5, 0x30E7, 0x30EE, 0x30F5, 0x30F6, - 0x30FC, 0x30FD, 0x30FE, 0x31F0, 0x31F1, - 0x30F2, 0x30F3, 0x30F4, 0x31F5, 0x31F6, - 0x30F7, 0x30F8, 0x30F9, 0x31FA, 0x31FB, - 0x30FC, 0x30FD, 0x30FE, 0x31FF, - } - - -- the characters below are always appear in a double form, so there - -- will be two Chinese ellipsis characters together that denote - -- ellipsis marks and it is not allowed to break between them - - local hyphenation = table.tohash { - 0x2026, -- … ellipsis - 0x2014, -- — hyphen - } - - local function is_han_character(char) - return - (char>=0x04E00 and char<=0x09FFF) or - (char>=0x03400 and char<=0x04DFF) or - (char>=0x20000 and char<=0x2A6DF) or - (char>=0x0F900 and char<=0x0FAFF) or - (char>=0x2F800 and char<=0x2FA1F) - end - - --~ opening_parenthesis_hw / closing_parenthesis_hw - --~ opening_parenthesis_fw / closing_parenthesis_fw - --~ opening_punctuation_hw / closing_punctuation_hw - --~ opening_punctuation_fw / closing_punctuation_fw - - --~ non_starter - --~ hyphenation - - --~ opening_vertical / closing_vertical - - fonts.analyzers.methods.stretch_hang = true - - fonts.analyzers.methods.hang_data = { - inter_char_stretch_factor = 2.00, -- we started with 0.5, then 1.0 - inter_char_half_factor = 0.50, -- normally there is no reason to change this - inter_char_half_shrink_factor = 0.25, -- normally there is no reason to change this - } - - local hang_data = fonts.analyzers.methods.hang_data - - local insert_after, insert_before, delete = node.insert_after, node.insert_before, nodes.delete - - local function nobreak_before(head,current) - local p = current.prev - if p then - p = p.prev - if p and p.id == penalty then - p.penalty = 10000 - return head, current - end - end - return insert_before(head,current,nodes.penalty(10000)) - end - - function fonts.analyzers.methods.hani(head,font,attr) - -- maybe make a special version with no trace - local characters = fontdata[font].characters - local current, done, stretch, prevclass = head, false, 0, 0 - if fonts.analyzers.methods.stretch_hang then - stretch = fontdata[font].parameters.quad - end - -- penalty before break - local interspecialskip = - stretch * hang_data.inter_char_half_factor - local interspecialshrink = stretch * hang_data.inter_char_half_shrink_factor - local internormalstretch = stretch * hang_data.inter_char_stretch_factor - local trace = fonts.color.trace --- todo: check for first and last - while current do - if current.id == glyph and current.subtype<256 then - if current.font == font then - local char = current.char - if false then - -- don't ask -) - elseif opening_punctuation_fw[char] or opening_parenthesis_fw[char] then - if trace then fcs(current,"font:init") end -if head ~= current then - head, _ = insert_before(head,current,nodes.glue(interspecialskip,0,interspecialshrink)) -end - head, current = insert_after(head,current,nodes.penalty(10000)) - head, current = insert_after(head,current,nodes.glue(0,internormalstretch,0)) - prevclass, done = 1, true - elseif closing_punctuation_fw[char] or closing_parenthesis_fw[char] then - if trace then fcs(current,"font:fina") end - if prevclass > 0 then - head, current = nobreak_before(head,current) - head, current = insert_after(head,current,nodes.penalty(10000)) - head, current = insert_after(head,current,nodes.glue(interspecialskip,0,interspecialshrink)) - head, current = insert_after(head,current,nodes.penalty(0)) - head, current = insert_after(head,current,nodes.glue(0,internormalstretch,0)) - end - prevclass, done = 2, true - elseif opening_punctuation_hw[char] or opening_parenthesis_hw[char] then - if trace then fcs(current,"font:init") end - head, current = insert_after(head,current,nodes.penalty(10000)) - head, current = insert_after(head,current,nodes.glue(0,internormalstretch,0)) - prevclass, done = 3, true - elseif closing_punctuation_hw[char] or closing_parenthesis_hw[char] then - if trace then fcs(current,"font:fina") end - if prevclass > 0 then - head, current = nobreak_before(head,current) - head, current = insert_after(head,current,nodes.penalty(0)) - head, current = insert_after(head,current,nodes.glue(0,internormalstretch,0)) - end - prevclass, done = 4, true - elseif hyphenation[char] then - if trace then fcs(current,"font:medi") end - if prevclass > 0 then - head, current = nobreak_before(head,current) - head, current = insert_after(head,current,nodes.penalty(0)) - head, current = insert_after(head,current,nodes.glue(0,internormalstretch,0)) - end - prevclass, done = 5, true - elseif non_starter[char] then - if trace then fcs(current,"font:isol") end - head, current = insert_after(head,current,nodes.penalty(10000)) - head, current = insert_after(head,current,nodes.glue(0,internormalstretch,0)) - prevclass, done = 6, true - elseif is_han_character(char) then - prevclass, done = 7, true - head, current = insert_after(head,current,nodes.penalty(0)) - head, current = insert_after(head,current,nodes.glue(0,internormalstretch,0)) - end - else - prevclass = 0 - end - elseif prevclass > 0 and current.id == glue and current.spec and current.spec.width > 0 then - head, current = delete(head,current) - end - if current then - current = current.next - end - end - return head, done - end - - fonts.analyzers.methods.hang = fonts.analyzers.methods.hani - -end - --- experimental and will probably change - -do - local process = fonts.otf.features.process.feature - local prepare = fonts.otf.features.prepare.feature - 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,attr) return process(head,font,attr,tag) end - fonts.initializers.node.otf[tag] = function(tfm,value) return prepare(tfm,tag,value) end - end -end |