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