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