good-ini.lua /size: 12 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['good-ini'] = {
2    version   = 1.000,
3    comment   = "companion to font-lib.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- depends on ctx
10
11local type, next = type, next
12local gmatch, find, topattern = string.gmatch, string.find, string.topattern
13local sortedhash, insert, contains = table.sortedhash, table.insert, table.contains
14
15local fonts              = fonts
16
17local trace_goodies      = false  trackers.register("fonts.goodies",            function(v) trace_goodies    = v end)
18local trace_extensions   = false  trackers.register("fonts.goodies.extensions", function(v) trace_extensions = v end)
19
20local report_goodies     = logs.reporter("fonts","goodies")
21
22local allocate           = utilities.storage.allocate
23local implement          = interfaces.implement
24local findfile           = resolvers.findfile
25local formatters         = string.formatters
26
27local otf                = fonts.handlers.otf
28local afm                = fonts.handlers.afm
29local tfm                = fonts.handlers.tfm
30
31local registerotffeature = otf.features.register
32local registerafmfeature = afm.features.register
33local registertfmfeature = tfm.features.register
34
35local addotffeature      = otf.enhancers.addfeature
36
37local fontgoodies        = fonts.goodies or { }
38fonts.goodies            = fontgoodies
39
40local data               = fontgoodies.data or { }
41fontgoodies.data         = data -- no allocate as we want to see what is there
42
43local list               = fontgoodies.list or { }
44fontgoodies.list         = list -- no allocate as we want to see what is there
45
46fontgoodies.suffixes     = { "lfg", "lua" } -- lfg is context specific and should not be used elsewhere
47
48local contextsetups      = fonts.specifiers.contextsetups
49
50function fontgoodies.report(what,trace,goodies)
51    if trace_goodies or trace then
52        local whatever = goodies[what]
53        if whatever then
54            report_goodies("goodie %a found in %a",what,goodies.name)
55        end
56    end
57end
58
59local function locate(filename)
60    local suffixes = fontgoodies.suffixes
61    for i=1,#suffixes do
62        local suffix = suffixes[i]
63        local fullname = findfile(file.addsuffix(filename,suffix))
64        if fullname and fullname ~= "" then
65            return fullname
66        end
67    end
68end
69
70local function loadgoodies(filename) -- maybe a merge is better
71    local goodies = data[filename] -- we assume no suffix is given
72    if goodies ~= nil then
73        -- found or tagged unfound
74    elseif type(filename) == "string" then
75        local fullname = locate(filename)
76        if not fullname or fullname == "" then
77            report_goodies("goodie file %a is not found (suffixes: % t)",filename,fontgoodies.suffixes)
78            data[filename] = false -- signal for not found
79        else
80            goodies = dofile(fullname) or false
81            if not goodies then
82                report_goodies("goodie file %a is invalid",fullname)
83                return nil
84            elseif trace_goodies then
85                report_goodies("goodie file %a is loaded",fullname)
86            end
87            goodies.name = goodies.name or "no name"
88            for i=1,#list do
89                local g = list[i]
90                if trace_goodies then
91                    report_goodies("handling goodie %a",g[1])
92                end
93                g[2](goodies)
94            end
95            goodies.initialized = true
96            data[filename] = goodies
97        end
98    end
99    return goodies
100end
101
102function fontgoodies.register(name,fnc,prepend) -- will be a proper sequencer
103    for i=1,#list do
104        local g = list[i]
105        if g[1] == name then
106            g[2] = fnc --overload
107            return
108        end
109    end
110    local g = { name, fnc }
111    if prepend then
112        insert(list,g,prepend == true and 1 or prepend)
113    else
114        insert(list,g)
115    end
116end
117
118fontgoodies.load = loadgoodies
119
120if implement then
121
122    implement {
123        name      = "loadfontgoodies",
124        actions   = loadgoodies,
125        arguments = "string",
126        overload  = true, -- for now, permits new font loader
127    }
128
129end
130
131-- register goodies file
132
133local function setgoodies(tfmdata,value)
134    local goodies = tfmdata.goodies
135    if not goodies then -- actually an error
136        goodies = { }
137        tfmdata.goodies = goodies
138    end
139    for filename in gmatch(value,"[^, ]+") do
140        -- we need to check for duplicates
141        local ok = loadgoodies(filename)
142        if ok then
143            if trace_goodies then
144                report_goodies("assigning goodie %a",filename)
145            end
146            goodies[#goodies+1] = ok
147        end
148    end
149end
150
151-- featuresets
152
153local function flattenedfeatures(t,tt)
154    -- first set value dominates
155    if not tt then
156        tt = { }
157    end
158    for i=1,#t do
159        local ti = t[i]
160        local ty = type(ti)
161        if ty == "table" then
162            flattenedfeatures(ti,tt)
163        elseif ty == "string" then
164            local set = contextsetups[ti]
165            if set then
166                for k, v in next, set do
167                    if k ~= "number" then
168                        tt[k] = v or nil
169                    end
170                end
171            else
172                -- bad
173            end
174        elseif tt[ti] == nil then
175            tt[ti] = true
176        end
177    end
178    for k, v in next, t do
179        if type(k) ~= "number" then -- not tonumber(k)
180            if type(v) == "table" then
181                flattenedfeatures(v,tt)
182            elseif tt[k] == nil then
183                tt[k] = v
184            end
185        end
186    end
187    return tt
188end
189
190-- fonts.features.flattened = flattenedfeatures
191
192local function prepare_features(goodies,name,set)
193    if set then
194        local ff = flattenedfeatures(set)
195        local fullname = goodies.name .. "::" .. name
196        local n, s = fonts.specifiers.presetcontext(fullname,"",ff)
197        goodies.featuresets[name] = s -- set
198        if trace_goodies then
199            report_goodies("feature set %a gets number %a and name %a",name,n,fullname)
200        end
201        return n
202    end
203end
204
205fontgoodies.prepare_features = prepare_features
206
207local function initialize(goodies)
208    local featuresets = goodies.featuresets
209    if featuresets then
210        if trace_goodies then
211            report_goodies("checking featuresets in %a",goodies.name)
212        end
213        for name, set in next, featuresets do
214            prepare_features(goodies,name,set)
215        end
216    end
217end
218
219fontgoodies.register("featureset",initialize)
220
221local function setfeatureset(tfmdata,set,features)
222    local goodies = tfmdata.goodies -- shared ?
223    if goodies then
224        local properties = tfmdata.properties
225        local what
226        for i=1,#goodies do
227            -- last one wins
228            local g = goodies[i]
229            what = g.featuresets and g.featuresets[set] or what
230        end
231        if what then
232            for feature, value in next, what do
233                if features[feature] == nil then
234                    features[feature] = value
235                end
236            end
237            properties.mode = what.mode or properties.mode
238        end
239    end
240end
241
242-- postprocessors (we could hash processor and share code)
243
244function fontgoodies.registerpostprocessor(tfmdata,f,prepend)
245    local postprocessors = tfmdata.postprocessors
246    if not postprocessors then
247        tfmdata.postprocessors = { f }
248    elseif prepend then
249        insert(postprocessors,f,prepend == true and 1 or prepend)
250    else
251        insert(postprocessors,f)
252    end
253end
254
255local function setpostprocessor(tfmdata,processor)
256    local goodies = tfmdata.goodies
257    if goodies and type(processor) == "string" then
258        local found = { }
259        local asked = utilities.parsers.settings_to_array(processor)
260        for i=1,#goodies do
261            local g = goodies[i]
262            local p = g.postprocessors
263            if p then
264                for i=1,#asked do
265                    local a = asked[i]
266                    local f = p[a]
267                    if type(f) == "function" then
268                        found[a] = f
269                    end
270                end
271            end
272        end
273        local postprocessors = tfmdata.postprocessors or { }
274        for i=1,#asked do
275            local a = asked[i]
276            local f = found[a]
277            if f then
278                postprocessors[#postprocessors+1] = f
279            end
280        end
281        if #postprocessors > 0 then
282            tfmdata.postprocessors = postprocessors
283        end
284    end
285end
286
287local function setextrafeatures(tfmdata)
288    local goodies = tfmdata.goodies
289    if goodies then
290        for i=1,#goodies do
291            local g = goodies[i]
292            local f = g.features
293            if f then
294                local rawdata = tfmdata.shared.rawdata
295                local done    = { }
296                -- indexed
297                for i=1,#f do
298                    local specification = f[i]
299                    local feature = specification.name
300                    if feature then
301                        addotffeature(rawdata,feature,specification)
302                        registerotffeature {
303                            name        = feature,
304                            description = formatters["extra: %s"](feature)
305                        }
306                    end
307                    done[i] = true
308                end
309                -- hashed
310                for feature, specification in sortedhash(f) do
311                    if not done[feature] then
312                        feature = specification.name or feature
313                        specification.name = feature
314                        addotffeature(rawdata,feature,specification)
315                        registerotffeature {
316                            name        = feature,
317                            description = formatters["extra: %s"](feature)
318                        }
319                    end
320                end
321            end
322        end
323    end
324end
325
326local function setextensions(tfmdata)
327    local goodies = tfmdata.goodies
328    if goodies then
329        local shared   = tfmdata.shared
330        local metadata = shared and shared.rawdata and shared.rawdata.metadata
331        for i=1,#goodies do
332            local g = goodies[i]
333            local e = g.extensions
334            if e then
335                local goodie   = g.name or "unknown"
336                local fontname = metadata and metadata.fontname
337                if trace_extensions then
338                    report_goodies("checking extensions for font %a",fontname or "unknown")
339                end
340                for i=1,#e do
341                    local entry  = e[i]
342                    local fnames = entry.fonts
343                    local tnames = type(fnames)
344                    local valid  = false
345                    if not fontname then
346                        valid = true
347                    elseif tnames == "table" then
348                        if fnames[fontname] then
349                            valid = true
350                        elseif #fnames > 0 and contains(fnames,fontname) then
351                            valid = true
352                        end
353                    elseif tnames == "string" then
354                        fnames = topattern(fnames)
355                        valid  = find(fontname,fnames) and true
356                    else
357                        valid = true
358                    end
359                    if valid then
360                        local name = "extension-" .. i
361                        if trace_extensions then
362                            report_goodies("adding extension %a from %a for font %a",name,goodie,fontname or "unknown")
363                        end
364                        otf.enhancers.addfeature(tfmdata.shared.rawdata,name,entry)
365                    end
366                end
367            end
368        end
369    end
370end
371
372-- installation
373
374local goodies_specification = {
375    name         = "goodies",
376    description  = "goodies on top of built in features",
377    initializers = {
378        position = 1,
379        base     = setgoodies,
380        node     = setgoodies,
381    }
382}
383
384registerotffeature(goodies_specification)
385registerafmfeature(goodies_specification)
386registertfmfeature(goodies_specification)
387
388-- maybe more of the following could be for type one too
389
390registerotffeature {
391    name        = "extrafeatures",
392    description = "extra features",
393    default     = true,
394    initializers = {
395        position = 2,
396        base     = setextrafeatures,
397        node     = setextrafeatures,
398    }
399}
400
401registerotffeature {
402    name        = "extensions",
403    description = "extensions to features",
404    default     = true,
405    initializers = {
406        position = 2,
407        base     = setextensions,
408        node     = setextensions,
409    }
410}
411
412registerotffeature {
413    name        = "featureset",
414    description = "goodie feature set",
415    initializers = {
416        position = 3,
417        base     = setfeatureset,
418        node     = setfeatureset,
419    }
420}
421
422registerotffeature {
423    name        = "postprocessor",
424    description = "goodie postprocessor",
425    initializers = {
426        base = setpostprocessor,
427        node = setpostprocessor,
428    }
429}
430