font-mps.lua /size: 15 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['font-mps'] = {
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 tostring   = tostring
10local concat     = table.concat
11local formatters = string.formatters
12
13-- QP0 [QP1] QP2 => CP0 [CP1 CP2] CP3
14
15-- CP0 = QP0
16-- CP3 = QP2
17--
18-- CP1 = QP0 + 2/3 *(QP1-QP0)
19-- CP2 = QP2 + 2/3 *(QP1-QP2)
20
21fonts               = fonts or { }
22local metapost      = fonts.metapost or { }
23fonts.metapost      = metapost
24
25local f_moveto      = formatters["(%N,%N)"]
26local f_lineto      = formatters["--(%N,%N)"]
27local f_curveto     = formatters["..controls(%N,%N)and(%N,%N)..(%N,%N)"]
28local s_cycle       = "--cycle"
29
30local f_nofill      = formatters["nofill %s;"]
31local f_dofill      = formatters["fill %s;"]
32
33local f_draw_trace  = formatters["drawpathonly %s;"]
34local f_draw        = formatters["draw %s;"]
35
36local f_boundingbox = formatters["((%N,%N)--(%N,%N)--(%N,%N)--(%N,%N)--cycle)"]
37local f_vertical    = formatters["((%N,%N)--(%N,%N))"]
38
39function metapost.boundingbox(d,factor)
40    local bounds = d.boundingbox
41    local factor = factor or 1
42    local llx    = factor*bounds[1]
43    local lly    = factor*bounds[2]
44    local urx    = factor*bounds[3]
45    local ury    = factor*bounds[4]
46    return f_boundingbox(llx,lly,urx,lly,urx,ury,llx,ury)
47end
48
49function metapost.widthline(d,factor)
50    local bounds = d.boundingbox
51    local factor = factor or 1
52    local lly    = factor*bounds[2]
53    local ury    = factor*bounds[4]
54    local width  = factor*d.width
55    return f_vertical(width,lly,width,ury)
56end
57
58function metapost.zeroline(d,factor)
59    local bounds = d.boundingbox
60    local factor = factor or 1
61    local lly    = factor*bounds[2]
62    local ury    = factor*bounds[4]
63    return f_vertical(0,lly,0,ury)
64end
65
66function metapost.paths(d,xfactor,yfactor)
67    local sequence = d.sequence
68    local segments = d.segments
69    local list     = { }
70    local path     = { } -- recycled
71    local size     = 0
72    local xfactor  = xfactor or 1
73    local yfactor  = yfactor or xfactor
74    if sequence then
75        local i = 1
76        local n = #sequence
77        while i < n do
78            local operator = sequence[i]
79            if operator == "m" then -- "moveto"
80                if size > 0 then
81                    size = size + 1
82                    path[size] = s_cycle
83                    list[#list+1] = concat(path,"",1,size)
84                    size = 1
85                else
86                    size = size + 1
87                end
88                path[size] = f_moveto(xfactor*sequence[i+1],yfactor*sequence[i+2])
89                i = i + 3
90            elseif operator == "l" then -- "lineto"
91                size = size + 1
92                path[size] = f_lineto(xfactor*sequence[i+1],yfactor*sequence[i+2])
93                i = i + 3
94            elseif operator == "c" then -- "curveto"
95                size = size + 1
96                path[size] = f_curveto(xfactor*sequence[i+1],yfactor*sequence[i+2],xfactor*sequence[i+3],yfactor*sequence[i+4],xfactor*sequence[i+5],yfactor*sequence[i+6])
97                i = i + 7
98            elseif operator =="q" then -- "quadraticto"
99                size = size + 1
100                -- first is always a moveto
101                local l_x = xfactor*sequence[i-2]
102                local l_y = yfactor*sequence[i-1]
103                local m_x = xfactor*sequence[i+1]
104                local m_y = yfactor*sequence[i+2]
105                local r_x = xfactor*sequence[i+3]
106                local r_y = yfactor*sequence[i+4]
107                path[size] = f_curveto (
108                    l_x + 2/3 * (m_x-l_x),
109                    l_y + 2/3 * (m_y-l_y),
110                    r_x + 2/3 * (m_x-r_x),
111                    r_y + 2/3 * (m_y-r_y),
112                    r_x, r_y
113                )
114                i = i + 5
115            else
116                -- weird
117                i = i + 1
118            end
119        end
120    elseif segments then
121        for i=1,#segments do
122            local segment  = segments[i]
123            local operator = segment[#segment]
124            if operator == "m" then -- "moveto"
125                if size > 0 then
126                    size = size + 1
127                    path[size] = s_cycle
128                    list[#list+1] = concat(path,"",1,size)
129                    size = 1
130                else
131                    size = size + 1
132                end
133                path[size] = f_moveto(xfactor*segment[1],yfactor*segment[2])
134            elseif operator == "l" then -- "lineto"
135                size = size + 1
136                path[size] = f_lineto(xfactor*segment[1],yfactor*segment[2])
137            elseif operator == "c" then -- "curveto"
138                size = size + 1
139                path[size] = f_curveto(xfactor*segment[1],yfactor*segment[2],xfactor*segment[3],yfactor*segment[4],xfactor*segment[5],yfactor*segment[6])
140            elseif operator =="q" then -- "quadraticto"
141                size = size + 1
142                -- first is always a moveto
143                local prev = segments[i-1]
144                local l_x = xfactor*prev[#prev-2]
145                local l_y = yfactor*prev[#prev-1]
146                local m_x = xfactor*segment[1]
147                local m_y = yfactor*segment[2]
148                local r_x = xfactor*segment[3]
149                local r_y = yfactor*segment[4]
150                path[size] = f_curveto (
151                    l_x + 2/3 * (m_x-l_x),
152                    l_y + 2/3 * (m_y-l_y),
153                    r_x + 2/3 * (m_x-r_x),
154                    r_y + 2/3 * (m_y-r_y),
155                    r_x, r_y
156                )
157            else
158                -- weird
159            end
160        end
161    else
162        return
163    end
164    if size > 0 then
165        size = size + 1
166        path[size] = s_cycle
167        list[#list+1] = concat(path,"",1,size)
168    end
169    return list
170end
171
172function metapost.fill(paths)
173    local r = { }
174    local n = #paths
175    for i=1,n do
176        if i < n then
177            r[i] = f_nofill(paths[i])
178        else
179            r[i] = f_dofill(paths[i])
180        end
181    end
182    return concat(r)
183end
184
185function metapost.draw(paths,trace)
186    local r = { }
187    local n = #paths
188    for i=1,n do
189        if trace then
190            r[i] = f_draw_trace(paths[i])
191        else
192            r[i] = f_draw(paths[i])
193        end
194    end
195    return concat(r)
196end
197
198function metapost.maxbounds(data,index,factor)
199    local maxbounds   = data.maxbounds
200    local factor      = factor or 1
201    local glyphs      = data.glyphs
202    local glyph       = glyphs[index]
203    local boundingbox = glyph.boundingbox
204    local xmin, ymin, xmax, ymax
205    if not maxbounds then
206        xmin = 0
207        ymin = 0
208        xmax = 0
209        ymax = 0
210        for i=1,#glyphs do
211            local d = glyphs[i]
212            if d then
213                local b = d.boundingbox
214                if b then
215                    if b[1] < xmin then xmin = b[1] end
216                    if b[2] < ymin then ymin = b[2] end
217                    if b[3] > xmax then xmax = b[3] end
218                    if b[4] > ymax then ymax = b[4] end
219                end
220            end
221        end
222        maxbounds = { xmin, ymin, xmax, ymax }
223        data.maxbounds = maxbounds
224    else
225        xmin = maxbounds[1]
226        ymin = maxbounds[2]
227        xmax = maxbounds[3]
228        ymax = maxbounds[4]
229    end
230    local llx   = boundingbox[1]
231    local lly   = boundingbox[2]
232    local urx   = boundingbox[3]
233    local ury   = boundingbox[4]
234    local width = glyph.width
235    if llx > 0 then
236        llx = 0
237    end
238    if width > urx then
239        urx = width
240    end
241    return f_boundingbox(
242        factor*llx,factor*ymin,
243        factor*urx,factor*ymin,
244        factor*urx,factor*ymax,
245        factor*llx,factor*ymax
246    )
247end
248
249-- This is a nice example of tex, metapost and lua working in tandem. Each kicks in at the
250-- right time. It's probably why I like watching https://www.youtube.com/watch?v=c5FqpddnJmc
251-- so much: precisely (and perfectly) timed too.
252
253local nodecodes       = nodes.nodecodes -- no nuts yet
254local rulecodes       = nodes.rulecodes
255
256local glyph_code      = nodecodes.glyph
257local disc_code       = nodecodes.disc
258local kern_code       = nodecodes.kern
259local glue_code       = nodecodes.glue
260local hlist_code      = nodecodes.hlist
261local vlist_code      = nodecodes.vlist
262local rule_code       = nodecodes.rule
263
264local normalrule_code = rulecodes.normal
265
266local nuts            = nodes.nuts
267local getnext         = nuts.getnext
268local getid           = nuts.getid
269local getlist         = nuts.getlist
270local getsubtype      = nuts.getsubtype
271local getreplace      = nuts.getreplace
272local getbox          = nuts.getbox
273local getwhd          = nuts.getwhd
274local getkern         = nuts.getkern
275local getshift        = nuts.getshift
276local getwidth        = nuts.getwidth
277local getheight       = nuts.getheight
278local getdepth        = nuts.getdepth
279local getexpansion    = nuts.getexpansion
280local isglyph         = nuts.isglyph
281
282local effectiveglue   = nuts.effectiveglue
283
284local characters      = fonts.hashes.characters
285local parameters      = fonts.hashes.parameters
286local shapes          = fonts.hashes.shapes
287local topaths         = metapost.paths
288
289local f_code          = formatters["mfun_do_outline_text_flush(%q,%i,%N,%N,%q)(%,t);"]
290local f_rule          = formatters["mfun_do_outline_rule_flush(%q,%N,%N,%N,%N);"]
291local f_bounds        = formatters["checkbounds(%N,%N,%N,%N);"]
292local s_nothing       = "(origin scaled 10)"
293
294local sc              = 10
295local fc              = number.dimenfactors.bp * sc / sc
296
297-- todo: make the next more efficient:
298
299function metapost.output(kind,font,char,advance,shift,ex)
300    local character = characters[font][char]
301    if character then
302        local index = character.index
303        if index then
304            local shapedata = shapes[font]
305            local glyphs    = shapedata.glyphs -- todo: subfonts fonts.shapes.indexed(font,sub)
306            if glyphs then
307                local glyf = glyphs[index]
308                if glyf then
309                    local units     = 1000 -- factor already takes shapedata.units into account
310                    local yfactor   = (sc/units) * parameters[font].factor / 655.36
311                    local xfactor   = yfactor
312                    local shift     = shift or 0
313                    local advance   = advance or 0
314                    local exfactor  = ex or 0
315                    local wfactor   = 1
316                    local detail    = kind == "p" and tostring(char) or ""
317                    if exfactor ~= 0 then
318                        wfactor = (1+(ex/units)/1000)
319                        xfactor = xfactor * wfactor
320                    end
321                    local paths = topaths(glyf,xfactor,yfactor)
322                    if paths then
323                        local code = f_code(kind,#paths,advance,shift,detail,paths)
324                        return code, character.width * fc * wfactor
325                    else
326                        return "", 0
327                    end
328                end
329            end
330        end
331    end
332    return s_nothing, 10 * sc/1000
333end
334
335-- not ok yet: leftoffset in framed not handled well
336
337local signal = -0x3FFFFFFF - 1
338
339function fonts.metapost.boxtomp(n,kind)
340
341    local result   = { }
342    local advance  = 0   -- in bp
343    local distance = 0
344
345    local llx, lly, urx, ury = 0, 0, 0, 0
346
347    local horizontal, vertical
348
349    horizontal = function(parent,current,xoffset,yoffset)
350        local dx = 0
351        while current do
352            local char, id = isglyph(current)
353            if char then
354                local code, width = metapost.output(kind,id,char,xoffset+dx,yoffset,getexpansion(current))
355                result[#result+1] = code
356                dx = dx + width
357            elseif id == disc_code then
358                local replace = getreplace(current)
359                if replace then
360                    dx = dx + horizontal(parent,replace,xoffset+dx,yoffset)
361                end
362            elseif id == kern_code then
363                dx = dx + getkern(current) * fc
364            elseif id == glue_code then
365                dx = dx + effectiveglue(current,parent) * fc
366            elseif id == hlist_code then
367                local list = getlist(current)
368                if list then
369                    horizontal(current,list,xoffset+dx,yoffset-getshift(current)*fc)
370                end
371                dx = dx + getwidth(current) * fc
372            elseif id == vlist_code then
373                local list = getlist(current)
374                if list then
375                    vertical(current,list,xoffset+dx,yoffset-getshift(current)*fc)
376                end
377                dx = dx + getwidth(current) * fc
378            elseif id == rule_code then
379                local wd, ht, dp = getwhd(current)
380                if wd ~= 0 then
381                    wd = wd * fc
382                    if ht == signal then
383                        ht = getheight(parent)
384                    end
385                    if dp == signal then
386                        dp = getdepth(parent)
387                    end
388                    local hd = (ht + dp) * fc
389                    if hd ~= 0 and getsubtype(current) == normalrule_code then
390                        result[#result+1] = f_rule(kind,xoffset+dx+wd/2,yoffset+hd/2,wd,hd)
391                    end
392                    dx = dx + wd
393                end
394            end
395            current = getnext(current)
396        end
397        return dx
398    end
399
400    vertical = function(parent,current,xoffset,yoffset)
401        local dy = getheight(parent) * fc
402        while current do
403            local id = getid(current)
404            if id == hlist_code then
405                local _, ht, dp = getwhd(current)
406                dy = dy - ht * fc
407                local list = getlist(current)
408                if list then
409                    horizontal(current,list,xoffset+getshift(current)*fc,yoffset+dy)
410                end
411                dy = dy - dp * fc
412            elseif id == vlist_code then
413                local wd, ht, dp = getwhd(current)
414                dy = dy - ht * fc
415                local list = getlist(current)
416                if list then
417                    vertical(current,list,xoffset+getshift(current)*fc,yoffset+dy)
418                end
419                dy = dy - dp * fc
420            elseif id == kern_code then
421                dy = dy - getkern(current) * fc
422            elseif id == glue_code then
423                dy = dy - effectiveglue(current,parent) * fc
424            elseif id == rule_code then
425                local wd, ht, dp = getwhd(current)
426                local hd = (ht + dp) * fc
427                if hd ~= 0  then
428                    if wd == signal then
429                        wd = getwidth(parent) * fc
430                    else
431                        wd = wd * fc
432                    end
433                    dy = dy - ht * fc
434                    if wd ~= 0 and getsubtype(current) == 0 then
435                        result[#result+1] = f_rule(kind,xoffset+wd/2,yoffset+dy+hd/2,wd,hd)
436                    end
437                    dy = dy - dp * fc
438                end
439            end
440            current = getnext(current)
441        end
442        return dy
443    end
444
445    local box  = getbox(n)
446    local list = box and getlist(box)
447    if list then
448        (getid(box) == hlist_code and horizontal or vertical)(box,list,0,0)
449    end
450
451    local wd, ht, dp = getwhd(box)
452
453    result[#result+1] = f_bounds(0,-dp*fc,wd*fc,ht*fc)
454
455    return concat(result)
456
457end
458