font-otd.lua /size: 11 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['font-otd'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to font-ini.mkiv",
5    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6    copyright = "PRAGMA ADE / ConTeXt Development Team",
7    license   = "see context related readme files"
8}
9
10local type = type
11local match = string.match
12local sequenced = table.sequenced
13
14local trace_dynamics     = false  trackers.register("otf.dynamics", function(v) trace_dynamics = v end)
15local trace_applied      = false  trackers.register("otf.applied",  function(v) trace_applied      = v end)
16
17local report_otf         = logs.reporter("fonts","otf loading")
18local report_process     = logs.reporter("fonts","otf process")
19
20local allocate           = utilities.storage.allocate
21
22local fonts              = fonts
23local otf                = fonts.handlers.otf
24local hashes             = fonts.hashes
25local definers           = fonts.definers
26local constructors       = fonts.constructors
27local specifiers         = fonts.specifiers
28
29local fontidentifiers    = hashes.identifiers
30local fontresources      = hashes.resources
31local fontproperties     = hashes.properties
32local fontdynamics       = hashes.dynamics
33
34local contextsetups      = specifiers.contextsetups
35local contextnumbers     = specifiers.contextnumbers
36local contextmerged      = specifiers.contextmerged
37
38local setmetatableindex  = table.setmetatableindex
39
40local a_to_script        = { }
41local a_to_language      = { }
42
43-- we can have a scripts hash in fonts.hashes
44
45function otf.setdynamics(font,attribute)
46 -- local features = contextsetups[contextnumbers[attribute]] -- can be moved to caller
47    local features = contextsetups[attribute]
48    if features then
49        local dynamics = fontdynamics[font]
50        dynamic = contextmerged[attribute] or 0
51        local script, language
52        if dynamic == 2 then -- merge
53            language  = features.language or fontproperties[font].language or "dflt"
54            script    = features.script   or fontproperties[font].script   or "dflt"
55        else -- if dynamic == 1 then -- replace
56            language  = features.language or "dflt"
57            script    = features.script   or "dflt"
58        end
59        if script == "auto" then
60            -- checkedscript and resources are defined later so we cannot shortcut them -- todo: make installer
61            script = definers.checkedscript(fontidentifiers[font],fontresources[font],features)
62        end
63        local ds = dynamics[script] -- can be metatable magic (less testing)
64-- or dynamics.dflt
65        if not ds then
66            ds = { }
67            dynamics[script] = ds
68        end
69        local dsl = ds[language]
70-- or ds.dflt
71        if not dsl then
72            dsl = { }
73            ds[language] = dsl
74        end
75        local dsla = dsl[attribute]
76        if not dsla then
77            local tfmdata = fontidentifiers[font]
78            a_to_script  [attribute] = script
79            a_to_language[attribute] = language
80            -- we need to save some values .. quite messy
81            local properties = tfmdata.properties
82            local shared     = tfmdata.shared
83            local s_script   = properties.script
84            local s_language = properties.language
85            local s_mode     = properties.mode
86            local s_features = shared.features
87            properties.mode     = "node"
88            properties.language = language
89            properties.script   = script
90            properties.dynamics = true -- handy for tracing
91            shared.features     = { }
92            -- end of save
93            local set = constructors.checkedfeatures("otf",features)
94            set.mode = "node" -- really needed
95            dsla = otf.setfeatures(tfmdata,set)
96            if trace_dynamics then
97                report_otf("setting dynamics %s: attribute %a, script %a, language %a, set %a",contextnumbers[attribute],attribute,script,language,set)
98            end
99            -- we need to restore some values
100            properties.script   = s_script
101            properties.language = s_language
102            properties.mode     = s_mode
103            shared.features     = s_features
104            -- end of restore
105            dynamics[script][language][attribute] = dsla -- cache
106        elseif trace_dynamics then
107         -- report_otf("using dynamics %s: attribute %a, script %a, language %a",contextnumbers[attribute],attribute,script,language)
108        end
109        return dsla
110    end
111end
112
113function otf.scriptandlanguage(tfmdata,attr)
114    local properties = tfmdata.properties
115    if attr and attr > 0 then
116        return a_to_script[attr] or properties.script or "dflt", a_to_language[attr] or properties.language or "dflt"
117    else
118        return properties.script or "dflt", properties.language or "dflt"
119    end
120end
121
122-- we reimplement the dataset resolver
123
124local autofeatures    = fonts.analyzers.features
125local featuretypes    = otf.tables.featuretypes
126local defaultscript   = otf.features.checkeddefaultscript
127local defaultlanguage = otf.features.checkeddefaultlanguage
128
129local resolved = { } -- we only resolve a font,script,language,attribute pair once
130local wildcard = "*"
131
132-- what about analyze in local and not in font
133
134-- needs checking: some added features can pass twice
135
136local P, C, Cc, lpegmatch = lpeg.P, lpeg.C, lpeg.Cc, lpeg.match
137
138local pattern = P("always") * (P(-1) * Cc(true) + P(":") * C((1-P(-1))^1))
139
140local function initialize(sequence,script,language,s_enabled,a_enabled,font,attr,dynamic,ra,autoscript,autolanguage)
141    local features = sequence.features
142    if features then
143        local order = sequence.order
144        if order then
145            local featuretype = featuretypes[sequence.type or "unknown"]
146            local lookupdone  = false
147            for i=1,#order do --
148                local kind = order[i] --
149                local e_e
150                local a_e = a_enabled and a_enabled[kind] -- the value (location)
151                if a_e ~= nil then
152                    e_e = a_e
153                else
154                    e_e = s_enabled and s_enabled[kind] -- the value (font)
155                end
156                if e_e then
157                    local usedattribute, usedscript, usedlanguage, usedlookup
158                    local valid = type(e_e) == "string" and lpegmatch(pattern,e_e)
159                    if valid then
160                        -- we have hit always
161                        usedattribute = autofeatures[kind] or false
162                        usedlanguage  = "*"
163                        usedscript    = "*"
164                        usedlookup    = { valid, usedattribute, sequence, kind }
165                    else
166                        -- we already checked for e_e
167                        local scripts   = features[kind] --
168                        local languages = scripts[script] or scripts[wildcard]
169                        if not languages and autoscript then
170                            langages = defaultscript(featuretype,autoscript,scripts)
171                        end
172                        if languages then
173                            -- we need detailed control over default because we want to trace
174                            -- only first attribute match check, so we assume simple fina's
175                         -- local valid = false
176                            if languages[language] then
177                                valid = e_e
178                            elseif languages[wildcard] then
179                                valid = e_e
180                            elseif autolanguage and defaultlanguage(featuretype,autolanguage,languages) then
181                                valid = e_e
182                            end
183                        end
184                        if valid then
185                            usedattribute = autofeatures[kind] or false
186                            usedlanguage  = script
187                            usedscript    = language
188                            usedlookup    = { valid, usedattribute, sequence, kind }
189                        end
190                    end
191                    if not usedlookup then
192                        -- go on
193                    elseif lookupdone then
194                        if trace_applied then
195                            report_process(
196                                "font %s, dynamic %a (%a), feature %a, script %a, language %a, lookup %a, value %a, nofsteps %a, lookup already set by %a",
197                                    font,attr or 0,dynamic,kind,usedscript,usedlanguage,sequence.name,valid,sequence.nofsteps,ra[#ra][4])
198                        end
199                    else
200                        ra[#ra+1] = usedlookup
201                        if trace_applied then
202                            report_process(
203                                "font %s, dynamic %a (%a), feature %a, script %a, language %a, lookup %a, value %a, nofsteps %a",
204                                    font,attr or 0,dynamic,kind,usedscript,usedlanguage,sequence.name,valid,sequence.nofsteps)
205                        else
206                            return -- no need to look further
207                        end
208                        lookupdone = true
209                    end
210                end
211            end
212        end
213    end
214end
215
216-- there is some fuzzy language/script state stuff in properties (temporary)
217
218function otf.dataset(tfmdata,font,attr) -- attr only when explicit (as in special parbuilder)
219
220    local script, language, s_enabled, a_enabled, dynamic
221
222    if attr and attr ~= 0 then
223        dynamic = contextmerged[attr] or 0
224     -- local features = contextsetups[contextnumbers[attr]] -- could be a direct list
225        local features = contextsetups[attr]
226        a_enabled = features -- location based
227        if dynamic == 1 then -- or dynamic == -1 then
228            -- replace
229            language  = features.language or "dflt"
230            script    = features.script   or "dflt"
231        elseif dynamic == 2 then -- or dynamic == -2 then
232            -- merge
233            local properties = tfmdata.properties
234            s_enabled = tfmdata.shared.features -- font based
235            language  = features.language or properties.language or  "dflt"
236            script    = features.script   or properties.script   or  "dflt"
237        else
238            -- error
239            local properties = tfmdata.properties
240            language  = properties.language or "dflt"
241            script    = properties.script   or "dflt"
242        end
243    else
244        local properties = tfmdata.properties
245        language  = properties.language or "dflt"
246        script    = properties.script   or "dflt"
247        s_enabled = tfmdata.shared.features -- can be made local to the resolver
248        dynamic   = 0
249    end
250
251    local res = resolved[font]
252    if not res then
253        res = { }
254        resolved[font] = res
255    end
256    local rs = res[script]
257    if not rs then
258        rs = { }
259        res[script] = rs
260    end
261    local rl = rs[language]
262    if not rl then
263        rl = { }
264        rs[language] = rl
265    end
266    local ra = rl[attr]
267    if ra == nil then -- attr can be false
268        ra = {
269            -- indexed but we can also add specific data by key in:
270        }
271        rl[attr] = ra
272        local sequences = tfmdata.shared.reorderedsequences or tfmdata.resources.sequences
273        if sequences then
274            local autoscript   = (s_enabled and s_enabled.autoscript  ) or (a_enabled and a_enabled.autoscript  )
275            local autolanguage = (s_enabled and s_enabled.autolanguage) or (a_enabled and a_enabled.autolanguage)
276            for s=1,#sequences do
277                -- just return nil or ra step
278                initialize(sequences[s],script,language,s_enabled,a_enabled,font,attr,dynamic,ra,autoscript,autolanguage)
279            end
280        end
281    end
282    return ra
283
284end
285