font-imp-effects.lmt /size: 10 Kb    last modification: 2024-01-16 09:02
1if not modules then modules = { } end modules ['font-imp-effects'] = {
2    version   = 1.001,
3    comment   = "companion to font-ini.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-- upgrading musical timestamp: archive, utrecht, oct 2013 (hopefully the mix below is
10-- as good as the excellent stage sound mix, and the performance as exceptional)
11
12local next, type, tonumber = next, type, tonumber
13
14local fonts              = fonts
15local handlers           = fonts.handlers
16local registerotffeature = handlers.otf.features.register
17local registerafmfeature = handlers.afm.features.register
18
19local settings_to_hash   = utilities.parsers.settings_to_hash_colon_too
20
21local report_effect      = logs.reporter("fonts","effect")
22
23local trace              = false
24
25trackers.register("fonts.effect", function(v) trace = v end)
26
27-- This will be a backend function (codeinjection).
28
29local effects = {
30    [0] = 0, [1] = 1, [2] = 2, [3] = 3,
31    inner   = 0,
32    normal  = 0,
33    outer   = 1,
34    outline = 1,
35    both    = 2,
36    hidden  = 3,
37}
38
39-- Math is to be redone, we use characters anyway ...
40
41local rules = {
42    "RadicalRuleThickness",
43    "OverbarRuleThickness",
44    "FractionRuleThickness",
45    "UnderbarRuleThickness",
46}
47
48-- local function setmathparameters(tfmdata,characters,mathparameters,dx,dy,squeeze,multiplier)
49--     if dy ~= 0 then
50--         for i=1,#rules do
51--             local name  = rules[i]
52--             local value = mathparameters[name]
53--             if value then
54--                 mathparameters[name] = (squeeze or 1) * (value + dy)
55--             end
56--         end
57--     end
58-- end
59
60local function applyeffect(tfmdata)
61    local effect = tfmdata.properties.effect
62    if effect and not effect.done then
63        local characters     = tfmdata.characters
64        local parameters     = tfmdata.parameters
65        local mathparameters = tfmdata.mathparameters
66        local extend  = effect.extend
67        local squeeze = effect.squeeze
68     -- local slant   = effect.slant
69        local weight  = effect.weight or 0
70        if weight ~= 0 then
71
72            weight = weight / 2
73
74            local fraction = (parameters.designsize or parameters.size) / parameters.size
75            if not texconditionals["c_font_compact"] then
76                local f  = 65536 * fraction * weight
77                local fw = f * 2
78                local fh = f
79                local fd = f
80                local fi = f
81                for unicode, character in next, characters do
82                    local v = character.width
83                    if v and v > 0 then
84                        if not character.advance then
85                            character.advance = v
86                        end
87                        character.width = v + fw
88                    end
89                    v = character.height
90                    if v and v > 0 then
91                        character.height = v + fh
92                    end
93                    v = character.depth
94                    if v and v > 0 then
95                        character.depth = v + fd
96                    end
97                    v = character.italic
98                    if v and v > 0 then
99                        character.italic = v + fi
100                    end
101                    v = character.topanchor
102                    if v and v > 0 then
103                        character.topanchor = v + fh
104                    end
105                    v = character.bottomanchor
106                    if v and v > 0 then
107                        character.bottomanchor = v - fd
108                    end
109--                     v = character.parts
110--                     if v then
111--                         for i=1,#v do
112--                             local vi = v[i]
113--                             vi.advance = vi.advance + fw
114--                             vi["end"]  = vi["end"]  + fh
115--                             vi.start   = vi.start   + fd
116--                         end
117--                     end
118                end
119-- needs re-checking
120                parameters.hshift = weight * fraction
121
122if mathparameters then
123    -- when we have extensibles we need to use those widths .. engine
124    for i=1,#rules do
125        local name  = rules[i]
126        local value = mathparameters[name]
127        if value then
128            mathparameters[name] = value + f + f -- ht and dp
129        end
130    end
131end
132            end
133            effect.weight = weight -- we divided
134        else
135            effect.weight = 0
136        end
137        effect.done = true
138    end
139end
140
141-- begin of direct setters
142
143-- When we use the setters a composite effect is ignored.
144
145local function seteffect(tfmdata,key,value,min,max)
146    if value then
147        value = tonumber(value) or 0
148        if not value then
149            value =  0
150        elseif value > max then
151            value =  max
152        elseif value < min then
153            value = min
154        end
155        local properties = tfmdata.properties
156        local parameters = tfmdata.parameters
157        local effect     = properties.effect
158        if not effect then
159            effect = { }
160            properties.effect = effect
161        end
162        if trace then
163            report_effect("applying %s %0.3f",key,value)
164        end
165        effect[key] = value
166        effect.done = false
167        if key == "slant" then
168            properties.usedslant = value
169            parameters.usedslant = value
170        end
171    end
172end
173
174local function initializeslant(tfmdata,value)
175    seteffect(tfmdata,"slant",value,-1,1)
176end
177
178local specification = {
179    name        = "slant",
180    description = "slant glyphs",
181    initializers = {
182        base = initializeslant,
183        node = initializeslant,
184    },
185    manipulators = {
186        base = applyeffect,
187        node = applyeffect,
188    },
189}
190
191registerotffeature(specification)
192registerafmfeature(specification)
193
194local function initializeextend(tfmdata,value)
195    seteffect(tfmdata,"extend",value,0,10)
196end
197
198local specification = {
199    name        = "extend",
200    description = "scale glyphs horizontally",
201    initializers = {
202        base = initializeextend,
203        node = initializeextend,
204    },
205    manipulators = {
206        base = applyeffect,
207        node = applyeffect,
208    },
209}
210
211registerotffeature(specification)
212registerafmfeature(specification)
213
214local function initializesqueeze(tfmdata,value)
215    seteffect(tfmdata,"squeeze",value,0,10)
216end
217
218local specification = {
219    name        = "squeeze",
220    description = "scale glyphs vertically",
221    initializers = {
222        base = initializesqueeze,
223        node = initializesqueeze,
224    },
225    manipulators = {
226        base = applyeffect,
227        node = applyeffect,
228    },
229}
230
231registerotffeature(specification)
232registerafmfeature(specification)
233
234local function initializeweight(tfmdata,value)
235    seteffect(tfmdata,"weight",value,0,1)
236end
237
238local specification = {
239    name        = "weight",
240    description = "bolden glyphs",
241    initializers = {
242        base = initializeweight,
243        node = initializeweight,
244    },
245    manipulators = {
246        base = applyeffect,
247        node = applyeffect,
248    },
249}
250
251registerotffeature(specification)
252registerafmfeature(specification)
253
254local function initializeoutline(tfmdata,value)
255    value = tonumber(value)
256    if not value then
257        value = 0
258    else
259        value = tonumber(value) or 0
260    end
261    local parameters = tfmdata.parameters
262    local properties = tfmdata.properties
263    if trace then
264        report_effect("applying outline %0.3f",value)
265    end
266--     parameters.mode   = effects.outline
267--     parameters.weight = value * 1000   -- todo
268    properties.effect = {
269        mode   = effects.outline,
270        weight = value,
271    }
272end
273
274local specification = {
275    name        = "outline",
276    description = "outline glyphs",
277    initializers = {
278        base = initializeoutline,
279        node = initializeoutline,
280    },
281    manipulators = {
282        base = applyeffect,
283        node = applyeffect,
284    },
285}
286
287registerotffeature(specification)
288registerafmfeature(specification)
289
290-- end of direct setters
291
292local function initializeoption(tfmdata,key,value)
293    if value then
294        local parameters = tfmdata.parameters
295        local properties = tfmdata.properties
296        if trace then
297            report_effect("applying effect %a",value)
298        end
299        local effect = properties.effect
300        if not effect then
301            effect = { }
302            properties.effect = effect
303        end
304        effect[key] = value
305    end
306end
307
308local function initializeeffect(tfmdata,value)
309    if tfmdata.properties.effect then
310        report_effect("ignored effect %a",effect)
311    elseif type(value) == "string" then
312        local specification = settings_to_hash(value)
313        if specification.width and not specification.weight then
314            specification.weight = specification.width
315            specification.width = nil
316        end
317        initializeextend (tfmdata,specification.extend)
318        initializesqueeze(tfmdata,specification.squeeze)
319        initializeslant  (tfmdata,specification.slant)
320        initializeweight (tfmdata,specification.weight)
321        initializeoption (tfmdata,"mode",effects[specification.effect or specification.mode or "both"])
322        initializeoption (tfmdata,"auto",specification.auto)
323
324    else
325        value = tonumber(value)
326        if value then
327            initializeextend(tfmdata,value)
328        end
329    end
330end
331
332local specification = {
333    name        = "effect",
334    description = "apply effects to glyphs",
335    initializers = {
336        base = initializeeffect,
337        node = initializeeffect,
338    },
339    manipulators = {
340        base = applyeffect,
341        node = applyeffect,
342    },
343}
344
345registerotffeature(specification)
346registerafmfeature(specification)
347
348local function checkautoeffect(tfmdata)
349    local effect = tfmdata.properties.effect
350    if effect and effect.auto then
351        local weight = effect.weight
352        if weight and weight ~= 0 and not texconditionals["c_font_compact"] then
353            local parameters = tfmdata.parameters
354            local extend     = effect.extend
355            local squeeze    = effect.squeeze
356            local amount     = 65.536 * weight
357            if not squeeze then -- or squeeze == 1 then
358                local xheight = parameters.xheight
359                effect.squeeze = xheight / (xheight + amount)
360            end
361            if not extend then -- or extend == 1 then
362                local emwidth = parameters.quad
363                effect.extend = emwidth / (emwidth + 2 *  amount)
364--                 effect.extend = emwidth / (emwidth + amount)
365            end
366        end
367    end
368end
369
370local specification = {
371    name         = "checkautoeffect",
372    description  = "check auto effect",
373    default      = true,
374    initializers = {
375        base = checkautoeffect,
376        node = checkautoeffect,
377    },
378}
379
380registerotffeature(specification)
381registerafmfeature(specification)
382