font-mps.lmt /size: 12 Kb    last modification: 2021-10-28 13:51
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 texgetbox        = tex.getbox
254
255local nodecodes        = nodes.nodecodes -- no nuts yet
256local rulecodes        = nodes.rulecodes
257
258local rule_code        = nodecodes.rule
259
260local normalrule_code  = rulecodes.normal
261local outlinerule_code = rulecodes.outline
262local userrule_code    = rulecodes.user
263local emptyrule_code   = rulecodes.empty
264
265local nuts             = nodes.nuts
266local getwhd           = nuts.getwhd
267local getexpansion     = nuts.getexpansion
268local isglyph          = nuts.isglyph
269
270local characters       = fonts.hashes.characters
271local parameters       = fonts.hashes.parameters
272local shapes           = fonts.hashes.shapes
273local topaths          = metapost.paths
274
275local f_text           = formatters["mfun_do_outline_text_flush(%q,%i,%N,%N,%q)(%,t);"]
276local f_rule           = formatters["mfun_do_outline_rule_flush(%q,%N,%N,%N,%N);"]
277local f_bounds         = formatters["checkbounds(%N,%N,%N,%N);"]
278local s_nothing        = "(origin scaled 10)"
279
280local sc               = 10
281local fc               = number.dimenfactors.bp
282
283local function glyph(kind,font,char,advance,shift,ex)
284    local character = characters[font][char]
285    if character then
286        local index = character.index
287        if index then
288            local shapedata = shapes[font]
289            local glyphs    = shapedata.glyphs -- todo: subfonts fonts.shapes.indexed(font,sub)
290            if glyphs then
291                local glyf = glyphs[index]
292                if glyf then
293                    local units    = 1000 -- factor already takes shapedata.units into account
294                    local yfactor  = (sc/units) * parameters[font].factor / 655.36
295                    local xfactor  = yfactor
296                    local shift    = shift or 0
297                    local advance  = advance or 0
298                    local exfactor = ex or 0
299                    local wfactor  = 1
300                    local detail   = kind == "p" and tostring(char) or ""
301                    if exfactor ~= 0 then
302                        wfactor = (1+(ex/units)/1000)
303                        xfactor = xfactor * wfactor
304                    end
305                    local paths = topaths(glyf,xfactor,yfactor)
306                    if paths then
307                        return f_text(kind,#paths,advance,shift,detail,paths) -- , character.width * fc * wfactor
308                    end
309                end
310            end
311        end
312    end
313end
314
315metapost.glyph = glyph
316
317local kind   = ""
318local buffer = { }
319local b      = 0
320
321local function reset()
322    buffer = { }
323    b      = 0
324end
325
326local function flushcharacter(current, pos_h, pos_v, pod_r, font, char)
327    local char, font = isglyph(current)
328    local code = glyph(kind,font,char,pos_h*fc,pos_v*fc,getexpansion(current))
329    if code then
330        b = b + 1
331        buffer[b] = code
332    end
333end
334
335local function flushrule(current, pos_h, pos_v, pos_r, size_h, size_v, subtype)
336    if subtype == normalrule_code then
337        b = b + 1
338        buffer[b] = f_rule(kind,pos_h*fc,pos_v*fc,size_h*fc,size_v*fc)
339    elseif subtype == outlinerule_code then
340        b = b + 1
341        buffer[b] = f_rule("d",pos_h*fc,pos_v*fc,size_h*fc,size_v*fc)
342    elseif subtype == userrule_code then
343     -- print("USER RULE")
344     -- b = b + 1
345     -- buffer[b] = f_rule("d",size_h*fc,size_v*fc,pos_h*fc,pos_v*fc)
346    elseif subtype == emptyrule_code then
347        -- ignore
348    else
349     -- b = b + 1
350     -- buffer[b] = f_rule("f",pos_h*fc,pos_v*fc,size_h*fc,size_v*fc)
351    end
352end
353
354local function flushsimplerule(pos_h, pos_v, pos_r, size_h, size_v)
355    flushrule(false,pos_h,pos_v,pos_r,size_h,size_v,normalrule_code)
356end
357
358local function flushspecialrule(pos_h, pos_v, pos_r, w, h, d, l, outline)
359    flushrule(false,pos_h,pos_v-d,pos_r,w,h+d,outline and outlinerule_code or normalrule_code)
360end
361
362-- installer
363
364drivers.install {
365    name    = "mpo",
366    actions = {
367        initialize = function()
368            reset()
369        end,
370        finalize = function(driver,details)
371            local bb  = details.boundingbox
372            local llx = bb[1] * fc
373            local lly = bb[2] * fc
374            local urx = bb[3] * fc
375            local ury = bb[4] * fc
376            b = b + 1
377            buffer[b] = f_bounds(llx,lly,urx,ury)
378         -- inspect(buffer)
379        end,
380    },
381    flushers = {
382        updatefontstate = updatefontstate,
383        character       = flushcharacter,
384        rule            = flushrule,
385        simplerule      = flushsimplerule,
386        specialrule     = flushspecialrule,
387    }
388}
389
390function metapost.boxtomp(n,k)
391    kind = k
392    nodes.handlers.finalizebox(n)
393    drivers.converters.lmtx(drivers.instances.mpo,texgetbox(n),"box",1)
394    local result = concat(buffer,";")
395    reset()
396    return result
397end
398
399