font-tex.lmt /size: 9369 b    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['font-mpf'] = {
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
9local formatters     = string.formatters
10local sortedhash     = table.sortedhash
11
12local addcharacters  = fonts.constructors.addcharacters
13local fontdata       = fonts.hashes.identifiers
14local fontchars      = fonts.hashes.characters
15
16local otf            = fonts.handlers.otf
17local otfregister    = otf.features.register
18
19----- getshapes      = fonts.dropins.getshapes
20
21local register       = fonts.collections.register
22local checkenabled   = fonts.collections.checkenabled
23local newprivateslot = fonts.helpers.newprivateslot
24
25local currentfont    = font.current
26
27-- hm, seems to gobble the first \char
28-- the tounicode is a bit messy here
29
30local makesetups = formatters["box:%s:%S"]
31
32local a_exportstatus <const> = attributes.private('exportstatus')
33
34local function makeglyphbox(char,spec)
35    token.expand_macro("makeglyphbox",true,spec.setups or tostring(char))
36    local b = tex.takebox("glyphbox")
37-- b[a_exportstatus] = 0
38    spec.code = {
39        width     = b.width,
40        height    = b.height,
41        depth     = b.depth,
42        objnum    = tex.boxresources.save(b,nil,nil,true),
43    }
44end
45
46local function setboxglyphs(category,fontid,unicode,specification)
47    local box = specification.code
48    if unicode and box then
49        local tfmdata    = fontdata[fontid]
50        local characters = tfmdata.characters
51        local newdata    = {
52            width   = box.width  or 0,
53            height  = box.height or 0,
54            depth   = box.depth  or 0,
55            unicode = specification.tounicode or unicode,
56        }
57        -- pass dimensions to lua
58        characters[unicode] = newdata
59        -- this also adapts newdata (add commands)
60        fonts.dropins.swapone("box",tfmdata,specification,unicode)
61        -- pass dimensions to tex
62        addcharacters(fontid, { characters = { [unicode] = newdata } })
63        return fontid, unicode
64    else
65        logs.report("box glyph", "invalid glyph box for %C",unicode)
66    end
67end
68
69local function setboxglyph(specification)
70    if specification then
71        local name    = specification.name
72        local unicode = specification.unicode
73        local font    = currentfont()
74        if not unicode and name then
75            unicode = newprivateslot(name)
76            specification.unicode = unicode
77        end
78        if unicode then
79            if not specification.setups then
80                specification.setups = name
81            end
82            -- we can actually delay font if needed
83            register(font,unicode,function(font,private)
84                makeglyphbox(unicode,specification)
85                return setboxglyphs(category,font,unicode,specification)
86            end)
87            checkenabled()
88        end
89    end
90end
91
92local function setboxdirectly(font,unicode,box,expose) -- hash based on wd/ht/dp
93    if box then
94        local tfmdata      = fontdata[font]
95        local glyphboxes   = (tfmdata.glyphboxes or 0) + 1
96        tfmdata.glyphboxes = glyphboxes
97        local private      = newprivateslot(formatters["BG:%05X"](glyphboxes))
98-- box[a_exportstatus] = 0
99        local newdata      = {
100            width   = box.width  or 0,
101            height  = box.height or 0,
102            depth   = box.depth  or 0,
103            unicode = unicode,
104            objnum  = tex.boxresources.save(box,nil,nil,true),
105            expose  = expose, -- permit color etc
106        }
107        local specification = {
108            code = newdata
109        }
110        tfmdata.characters[private] = newdata
111        addcharacters(font, { characters = { [private] = newdata } })
112        fonts.dropins.swapone("box",tfmdata,specification,private)
113        checkenabled()
114        return private
115    end
116end
117
118fonts.helpers.setboxdirectly = setboxdirectly
119
120local boxes = table.setmetatableindex("table")
121
122function fonts.helpers.registerglyphbox(specification)
123    local category = specification.category
124    local whatever = specification.unicode or specification.name
125    if category and whatever then
126        boxes[category][whatever] = { action = makeglyphbox } -- always
127    end
128end
129
130local function initializebox(tfmdata,kind,value)
131    local boxes = boxes[value]
132    if value then
133        local font = tfmdata.properties.id
134        -- just preregister the list
135        for char, spec in sortedhash(boxes) do
136            spec.setups = makesetups(value,char)
137            local char = type(char) == "string" and newprivateslot(name) or char
138            -- check if spec.code
139            register(font,char,function(font,char)
140                local action = spec.action
141                if type(action) == "function" then
142                    action(char,spec)
143                end
144                return setboxglyphs(value,font,char,spec)
145            end)
146        end
147        checkenabled()
148    end
149end
150
151fonts.helpers.setboxglyphs = setboxglyphs
152fonts.helpers.setboxglyph  = setboxglyph
153
154interfaces.implement {
155    name      = "registerboxglyph", -- combine with next one
156    public    = true,
157    protected = true,
158    actions   = fonts.helpers.registerglyphbox,
159    arguments = { {
160        { "category" },
161        { "unicode", "integer" },
162        { "tounicode", "integer" },
163        { "name" },
164    } },
165}
166
167interfaces.implement {
168    name      = "setboxglyph",
169    public    = true,
170    protected = true,
171    actions   = setboxglyph,
172    arguments = { {
173        { "category" },
174        { "unicode", "integer" },
175        { "tounicode", "integer" },
176        { "name" },
177        { "*" }
178    } },
179}
180
181fonts.handlers.otf.features.register {
182    name         = "box",
183    description  = "box glyphs",
184    manipulators = {
185        base = initializebox,
186        node = initializebox,
187    }
188}
189
190-- fonts.helpers.registerboxglyph { category = "demo", unicode = 103 }
191-- fonts.helpers.registerboxglyph { category = "demo", unicode = 104 }
192-- fonts.helpers.registerboxglyph { category = "demo", unicode = 105 }
193-- fonts.helpers.registerboxglyph { category = "demo", unicode = 106 }
194
195local fontcallbacks = fonts.callbacks or { }
196fonts.callbacks     = fontcallbacks
197
198function fontcallbacks.devirtualize(chardata,f,c)
199    if chardata.commands then
200        local h = node.hpack(nodes.pool.glyph(f,c))
201        local p = setboxdirectly(f,c,h)
202        chardata.oldcommands = chardata.commands
203        chardata.commands = { { "char", p } }
204        chardata.callback = false
205    end
206end
207
208local function processcallback(f,c)
209    local characters = fontchars[f]
210    local chardata   = characters[c]
211    if chardata then
212        local callback = chardata.callback
213        if callback then
214            local action = type(callback) == "function" and callback or fontcallbacks[callback]
215            if action then
216                action(chardata,f,c,characters)
217            end
218        end
219    end
220end
221
222do
223
224    local unpack = unpack
225
226    local nuts      = nodes.nuts
227    local new_glyph = nuts.pool.glyph
228    local new_kern  = nuts.pool.kern
229    local tonode    = nuts.tonode
230    local hpack     = nuts.hpack
231    local setlink   = nuts.setlink
232
233    function fontcallbacks.compile(chardata,fnt,chr,characters)
234        -- no need for speed here
235        local c = chardata.commands
236        if c then
237            local t = { }
238            for i=1,#c do
239                local ci = c[i]
240                local c1 = ci[1]
241                local c2 = ci[2]
242                if c1 == "char" then
243                    t[i] = new_glyph(fnt,c2)
244                elseif c1 == "slot" then
245                    t[i] = new_glyph(c2 == 0 and fnt or c2,ci[3])
246                elseif c1 == "left" then
247                    t[i] = new_kern(-c2)
248                elseif c1 == "right" then
249                    t[i] = new_kern(c2)
250                elseif c1 == "offset" then
251                    local c4 = ci[4] -- c
252                    if c4 then
253                        t[i] = new_glyph(fnt or 0,c4)
254                        ----- c2 = ci[2] -- ox
255                        local c3 = ci[3] -- oy
256                        local c5 = ci[5] -- sx
257                        local c6 = ci[6] -- sy
258                        local c7 = ci[7] -- slant
259                        if c2 or c3 then
260                            nuts.setoffsets(t[i],c2 or 0,c3 or 0)
261                        end
262                     -- if c5 or c6 then
263                     --     nuts.setscales(t[i],1,(c5 or 1)*1000,(c6 or 1)*1000)
264                     -- end
265                        if c7 then
266                            nuts.setslant(t[i],c7*1000)
267                        end
268                    end
269                else
270                    print("fcc todo: " .. c1)
271                    goto done
272                end
273            end
274            local box = tonode(hpack(setlink(unpack(t))))
275            local prv = setboxdirectly(fnt,chr,box,true) -- expose
276            chardata.commands = { { "char", prv } }
277            characters[prv].unicode = chardata.unicode
278        end
279      ::done::
280        chardata.callback = false -- redundant
281    end
282
283end
284
285callbacks.register("process_character",processcallback,"apply an action to a character in a font")
286
287fontcallbacks.callback = processcallback
288