mlib-fnt.lmt /size: 19 Kb    last modification: 2024-01-16 10:22
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 type, tonumber, tostring = type, tonumber, tostring
10local concat, insert, remove = table.concat, table.insert, table.remove
11local formatters, match = string.formatters, string.match
12local utfbyte = utf.byte
13
14-- QP0 [QP1] QP2 => CP0 [CP1 CP2] CP3
15
16-- CP0 = QP0
17-- CP3 = QP2
18--
19-- CP1 = QP0 + 2/3 *(QP1-QP0)
20-- CP2 = QP2 + 2/3 *(QP1-QP2)
21
22fonts               = fonts or { }
23local metapost      = fonts.metapost or { }
24fonts.metapost      = metapost
25
26local f_moveto      = formatters["(%N,%N)"]
27local f_lineto      = formatters["--(%N,%N)"]
28local f_curveto     = formatters["..controls(%N,%N)and(%N,%N)..(%N,%N)"]
29local s_cycle       = "--cycle"
30
31local f_nofill      = formatters["nofill %s;"]
32local f_dofill      = formatters["fill %s;"]
33
34local f_draw_trace  = formatters["drawpathonly %s;"]
35local f_draw        = formatters["draw %s;"]
36
37local f_rectangle   = formatters["((%N,%N)--(%N,%N)--(%N,%N)--(%N,%N)--cycle)"]
38local f_line        = formatters["((%N,%N)--(%N,%N))"]
39
40function metapost.boundingbox(d,factor)
41    local bounds = d.boundingbox
42    local factor = factor or 1
43    local llx    = factor*bounds[1]
44    local lly    = factor*bounds[2]
45    local urx    = factor*bounds[3]
46    local ury    = factor*bounds[4]
47    return f_rectangle(llx,lly,urx,lly,urx,ury,llx,ury)
48end
49
50function metapost.baseline(d,factor)
51    local bounds = d.boundingbox
52    local factor = factor or 1
53    local llx    = factor*bounds[1]
54    local urx    = factor*bounds[3]
55    return f_line(llx,0,urx,0)
56end
57
58function metapost.widthline(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    local width  = factor*d.width
64    return f_line(width,lly,width,ury)
65end
66
67function metapost.zeroline(d,factor)
68    local bounds = d.boundingbox
69    local factor = factor or 1
70    local lly    = factor*bounds[2]
71    local ury    = factor*bounds[4]
72    return f_line(0,lly,0,ury)
73end
74
75function metapost.paths(d,xfactor,yfactor)
76    local sequence = d.sequence
77    local segments = d.segments
78    local list     = { }
79    local path     = { } -- recycled
80    local size     = 0
81    local xfactor  = xfactor or 1
82    local yfactor  = yfactor or xfactor
83    if sequence then
84        local i = 1
85        local n = #sequence
86        while i < n do
87            local operator = sequence[i]
88            if operator == "m" then -- "moveto"
89                if size > 0 then
90                    size = size + 1
91                    path[size] = s_cycle
92                    list[#list+1] = concat(path,"",1,size)
93                    size = 1
94                else
95                    size = size + 1
96                end
97                path[size] = f_moveto(xfactor*sequence[i+1],yfactor*sequence[i+2])
98                i = i + 3
99            elseif operator == "l" then -- "lineto"
100                size = size + 1
101                path[size] = f_lineto(xfactor*sequence[i+1],yfactor*sequence[i+2])
102                i = i + 3
103            elseif operator == "c" then -- "curveto"
104                size = size + 1
105                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])
106                i = i + 7
107            elseif operator =="q" then -- "quadraticto"
108                size = size + 1
109                -- first is always a moveto
110                local l_x = xfactor*sequence[i-2]
111                local l_y = yfactor*sequence[i-1]
112                local m_x = xfactor*sequence[i+1]
113                local m_y = yfactor*sequence[i+2]
114                local r_x = xfactor*sequence[i+3]
115                local r_y = yfactor*sequence[i+4]
116                path[size] = f_curveto (
117                    l_x + 2/3 * (m_x-l_x),
118                    l_y + 2/3 * (m_y-l_y),
119                    r_x + 2/3 * (m_x-r_x),
120                    r_y + 2/3 * (m_y-r_y),
121                    r_x, r_y
122                )
123                i = i + 5
124            else
125                -- weird
126                i = i + 1
127            end
128        end
129    elseif segments then
130-- inspect(segments)
131        for i=1,#segments do
132            local segment  = segments[i]
133            local operator = segment[#segment]
134            if operator == "m" then -- "moveto"
135                if size > 0 then
136                    size = size + 1
137                    path[size] = s_cycle
138                    list[#list+1] = concat(path,"",1,size)
139                    size = 1
140                else
141                    size = size + 1
142                end
143                path[size] = f_moveto(xfactor*segment[1],yfactor*segment[2])
144            elseif operator == "l" then -- "lineto"
145                size = size + 1
146                path[size] = f_lineto(xfactor*segment[1],yfactor*segment[2])
147            elseif operator == "c" then -- "curveto"
148                size = size + 1
149                path[size] = f_curveto(xfactor*segment[1],yfactor*segment[2],xfactor*segment[3],yfactor*segment[4],xfactor*segment[5],yfactor*segment[6])
150            elseif operator == "q" then -- "quadraticto"
151                size = size + 1
152                -- first is always a moveto
153                local prev = segments[i-1]
154                local l_x = xfactor*prev[#prev-2]
155                local l_y = yfactor*prev[#prev-1]
156                local m_x = xfactor*segment[1]
157                local m_y = yfactor*segment[2]
158                local r_x = xfactor*segment[3]
159                local r_y = yfactor*segment[4]
160                path[size] = f_curveto (
161                    l_x + 2/3 * (m_x-l_x),
162                    l_y + 2/3 * (m_y-l_y),
163                    r_x + 2/3 * (m_x-r_x),
164                    r_y + 2/3 * (m_y-r_y),
165                    r_x, r_y
166                )
167            else
168                -- weird
169            end
170        end
171    else
172        return
173    end
174    if size > 0 then
175        size = size + 1
176        path[size] = s_cycle
177        list[#list+1] = concat(path,"",1,size)
178    end
179    return list
180end
181
182function metapost.fill(paths)
183    local r = { }
184    local n = #paths
185    for i=1,n do
186        if i < n then
187            r[i] = f_nofill(paths[i])
188        else
189            r[i] = f_dofill(paths[i])
190        end
191    end
192    return concat(r)
193end
194
195function metapost.draw(paths,trace)
196    local r = { }
197    local n = #paths
198    for i=1,n do
199        if trace then
200            r[i] = f_draw_trace(paths[i])
201        else
202            r[i] = f_draw(paths[i])
203        end
204    end
205    return concat(r)
206end
207
208function metapost.maxbounds(data,index,factor)
209    local maxbounds   = data.maxbounds
210    local factor      = factor or 1
211    local glyphs      = data.glyphs
212    local glyph       = glyphs[index]
213    local boundingbox = glyph.boundingbox
214    local xmin, ymin, xmax, ymax
215    if not maxbounds then
216        xmin = 0
217        ymin = 0
218        xmax = 0
219        ymax = 0
220        for i=1,#glyphs do
221            local d = glyphs[i]
222            if d then
223                local b = d.boundingbox
224                if b then
225                    if b[1] < xmin then xmin = b[1] end
226                    if b[2] < ymin then ymin = b[2] end
227                    if b[3] > xmax then xmax = b[3] end
228                    if b[4] > ymax then ymax = b[4] end
229                end
230            end
231        end
232        maxbounds = { xmin, ymin, xmax, ymax }
233        data.maxbounds = maxbounds
234    else
235        xmin = maxbounds[1]
236        ymin = maxbounds[2]
237        xmax = maxbounds[3]
238        ymax = maxbounds[4]
239    end
240    local llx   = boundingbox[1]
241    local lly   = boundingbox[2]
242    local urx   = boundingbox[3]
243    local ury   = boundingbox[4]
244    local width = glyph.width
245    if llx > 0 then
246        llx = 0
247    end
248    if width > urx then
249        urx = width
250    end
251    return f_rectangle(
252        factor*llx,factor*ymin,
253        factor*urx,factor*ymin,
254        factor*urx,factor*ymax,
255        factor*llx,factor*ymax
256    )
257end
258
259-- This is a nice example of tex, metapost and lua working in tandem. Each kicks in at the
260-- right time. It's probably why I like watching https://www.youtube.com/watch?v=c5FqpddnJmc
261-- so much: precisely (and perfectly) timed too.
262
263local texgetbox          = tex.getbox
264
265local nodecodes          = nodes.nodecodes -- no nuts yet
266local rulecodes          = nodes.rulecodes
267
268local rule_code          = nodecodes.rule
269
270local normalrule_code    = rulecodes.normal
271local outlinerule_code   = rulecodes.outline
272local userrule_code      = rulecodes.user
273local emptyrule_code     = rulecodes.empty
274
275local nuts               = nodes.nuts
276----- getwhd             = nuts.getwhd
277local getexpansion       = nuts.getexpansion
278local getscales          = nuts.getscales
279local isglyph            = nuts.isglyph
280local getglyphdimensions = nuts.getglyphdimensions
281
282local fonthashes         = fonts.hashes
283local fontcharacters     = fonthashes.characters
284local fontparameters     = fonthashes.parameters
285local fontshapes         = fonthashes.shapes
286local fontdescriptions   = fonthashes.descriptions
287
288local topaths            = metapost.paths
289
290local f_text             = formatters["mfun_do_outline_text_flush(%q,%i,%N,%N,%q)(%,t);"]
291local f_rule             = formatters["mfun_do_outline_rule_flush(%q,%N,%N,%N,%N);"]
292local f_bounds           = formatters["checkbounds(%N,%N,%N,%N);"]
293local s_nothing          = "(origin scaled 10)"
294
295local sc                 = 10
296local fc                 = number.dimenfactors.bp
297
298-- handle compact mode here:
299
300local function glyph(kind,font,char,advance,shift,ex,s,sx,sy)
301    local character = fontcharacters[font][char]
302    if character then
303        local index = character.index
304        if index then
305            local shapedata = fontshapes[font]
306            local glyphs    = shapedata.glyphs
307            if glyphs then
308                local glyf = glyphs[index]
309                if glyf then
310                    local units    = 1000 -- factor already takes shapedata.units into account
311                    local yfactor  = (sc/units) * fontparameters[font].factor / 655.36
312                    local xfactor  = yfactor
313                    local shift    = shift or 0
314                    local advance  = advance or 0
315                    local exfactor = ex or 0
316                    local wfactor  = 1
317                    local detail   = kind == "p" and tostring(char) or ""
318                    -- what about other effects
319                    local xoffset  = character.xoffset or 0
320                    local yoffset  = character.yoffset or 0 -- todo
321                    --
322                    if exfactor ~= 0 then
323                        wfactor = (1+(ex/units)/1000)
324                        xfactor = xfactor * wfactor
325                    end
326                    if xoffset ~= 0 then
327                        advance = advance + s * sx * xoffset * fc / 1000000
328                    end
329                    if yoffset ~= 0 then
330                        shift = shift + s * sy * yoffset * fc / 1000000
331                    end
332                    if s then
333                        xfactor = (s/1000) * ((sx or 1000)/1000) * xfactor
334                        yfactor = (s/1000) * ((sy or 1000)/1000) * yfactor
335                    end
336                    local paths = topaths(glyf,xfactor,yfactor)
337                    if paths then
338                        return f_text(kind,#paths,advance,shift,detail,paths) -- , character.width * fc * wfactor
339                    end
340                end
341            end
342        end
343    end
344end
345
346metapost.glyph = glyph
347
348local kind   = ""
349local buffer = { }
350local b      = 0
351
352local function reset()
353    buffer = { }
354    b      = 0
355end
356
357local function flushcharacter(current, pos_h, pos_v, pod_r, font, char)
358    if current then
359        local char, font = isglyph(current)
360        local s, sx, sy = getscales(current)
361        local code = glyph(kind,font,char,pos_h*fc,pos_v*fc,getexpansion(current),s,sx,sy)
362        if code then
363            b = b + 1
364            buffer[b] = code
365        end
366    else
367        logs.report("mlib-fnt","check 'flushcharacter', no current")
368    end
369end
370
371-- Messy ... todo:
372
373-- local function flushcharacter(current,pos_h,pos_v,pos_r,font,char,data,csx,csy,factor,ssx,ssy)
374--     local width, height, depth, sx, sy
375--     if current then
376--         char, font = isglyph(current)
377--         width, height, depth, factor, sx, sy = getglyphdimensions(current)
378--     else
379--         if not data then
380--             data = fontcharacters[font][char] or { }
381--         end
382--         width  = data.width or 0
383--         height = data.height or 0
384--         depth  = data.depth or 0
385--         sx = 1
386--         sy = 1
387--     end
388--     if csx then
389--         sx = sx * csx
390--     end
391--     if csy then
392--         sy = sy * csy
393--     end
394--     if ssx then
395--         sx = sx * ssx
396--     end
397--     if ssy then
398--         sy = sy * ssy
399--     end
400--     local code = glyph(kind,font,char,pos_h*fc,pos_v*fc,factor,s,sx,sy)
401--     if code then
402--         b = b + 1
403--         buffer[b] = code
404--     end
405--     return width, height, depth
406-- end
407
408local function flushrule(current,pos_h,pos_v,pos_r,size_h,size_v,subtype)
409    if subtype == normalrule_code then
410        b = b + 1
411        buffer[b] = f_rule(kind,pos_h*fc,pos_v*fc,size_h*fc,size_v*fc)
412    elseif subtype == outlinerule_code then
413        b = b + 1
414        buffer[b] = f_rule("d",pos_h*fc,pos_v*fc,size_h*fc,size_v*fc)
415    elseif subtype == userrule_code then
416     -- print("USER RULE")
417     -- b = b + 1
418     -- buffer[b] = f_rule("d",size_h*fc,size_v*fc,pos_h*fc,pos_v*fc)
419    elseif subtype == emptyrule_code then
420        -- ignore
421    else
422     -- b = b + 1
423     -- buffer[b] = f_rule("f",pos_h*fc,pos_v*fc,size_h*fc,size_v*fc)
424    end
425end
426
427local function flushsimplerule(pos_h, pos_v, pos_r, size_h, size_v)
428    flushrule(false,pos_h,pos_v,pos_r,size_h,size_v,normalrule_code)
429end
430
431local function flushspecialrule(pos_h, pos_v, pos_r, w, h, d, l, outline)
432    flushrule(false,pos_h,pos_v-d,pos_r,w,h+d,outline and outlinerule_code or normalrule_code)
433end
434
435-- installer
436
437drivers.install {
438    name    = "mpo",
439    actions = {
440        initialize = function()
441            reset()
442        end,
443        finalize = function(driver,details)
444            local bb  = details.boundingbox
445            local llx = bb[1] * fc
446            local lly = bb[2] * fc
447            local urx = bb[3] * fc
448            local ury = bb[4] * fc
449            b = b + 1
450            buffer[b] = f_bounds(llx,lly,urx,ury)
451         -- inspect(buffer)
452        end,
453    },
454    flushers = {
455        updatefontstate = updatefontstate,
456        character       = flushcharacter,
457        rule            = flushrule,
458        simplerule      = flushsimplerule,
459        specialrule     = flushspecialrule,
460    }
461}
462
463function metapost.boxtomp(n,k)
464    kind = k
465    nodes.handlers.finalizebox(n)
466    drivers.converters.lmtx(drivers.instances.mpo,texgetbox(n),"box",1)
467    local result = concat(buffer,";")
468    reset()
469    return result
470end
471
472-- This is a new set of commands:
473
474local loaded = table.setmetatableindex(function(t,k)
475    local v = fonts.definers.internal({ name = k } ,"<lmt:glyphshape:font>")
476    t[k] = v
477    return v
478end)
479
480local mpdata  = 0
481local mpstack = { }
482
483function mp.lmt_glyphshape_start(id,character)
484    if type(id) == "string" then
485        id = loaded[id]
486    end
487    local fontid       = (id and id ~= 0 and id) or font.current()
488    local shapedata    = fontshapes      [fontid] -- by index
489    local characters   = fontcharacters  [fontid] -- by unicode
490    local descriptions = fontdescriptions[fontid] -- by unicode
491    local mathgaps     = mathematics.gaps -- not yet loaded
492    local shapeglyphs  = shapedata.glyphs or { }
493    if type(character) == "string" and character ~= "" then
494        local hex = match(character,"^0x(.+)")
495        if hex then
496            character = tonumber(hex,16)
497        else
498            character = utfbyte(character)
499        end
500    else
501        character = tonumber(character)
502    end
503    local unicode  = mathgaps[character] or character
504    local chardata = characters[unicode]
505    local descdata = descriptions[unicode]
506    if chardata then
507        glyph = shapeglyphs[chardata.index]
508        if glyph then
509            mpdata = glyph.mpdata
510            if not mpdata then
511                if glyph.segments or glyph.sequence then
512                    local units  = shapedata.units or 1000
513                    local factor = 100/units
514                    local width  = (descdata.width or 0)  * factor
515                    local height = descdata.boundingbox[4] * factor
516                    local depth  = descdata.boundingbox[2] * factor
517                    local llx    = descdata.boundingbox[1] * factor
518                    local math   = descdata.math
519                    local italic = (math and math.italic or 0) * factor
520                    local accent = (math and math.accent or 0) * factor
521                    mpdata = {
522                        paths       = metapost.paths(glyph,factor),
523                        boundingbox = metapost.boundingbox(glyph,factor),
524                        baseline    = metapost.baseline(glyph,factor),
525                        width       = width,
526                        height      = height,
527                        depth       = depth,
528                        italic      = italic,
529                        accent      = accent,
530                        llx         = llx,
531                        usedbox     = f_rectangle(llx,depth,llx+width,depth,llx+width,height,llx,height),
532                        usedline    = f_line(llx,0,llx+width,0),
533                    }
534                    glyph.mpdata = mpdata
535                else
536                    print("CHECK 1",id,character)
537                end
538            end
539        end
540    else
541        print("CHECK 2",id,character)
542    end
543    insert(mpstack, mpdata)
544end
545
546local mpprint       = mp.print
547local injectpair    = mp.inject.pair
548local injectnumeric = mp.inject.numeric
549
550function mp.lmt_glyphshape_stop()
551    mpdata = remove(mpstack)
552end
553
554function mp.lmt_glyphshape_n()
555    if mpdata then
556        mpprint(#mpdata.paths)
557    else
558        injectnumeric(0)
559    end
560end
561
562function mp.lmt_glyphshape_path(i)
563    if mpdata then
564        mpprint(mpdata.paths[i])
565    else
566        injectpair(0,0)
567    end
568end
569
570function mp.lmt_glyphshape_boundingbox()
571    if mpdata then
572        mpprint(mpdata.boundingbox)
573    else
574        injectpair(0,0)
575    end
576end
577function mp.lmt_glyphshape_usedbox()
578    if mpdata then
579        mpprint(mpdata.usedbox)
580    else
581        injectpair(0,0)
582    end
583end
584
585function mp.lmt_glyphshape_baseline()
586    if mpdata then
587        mpprint(mpdata.baseline)
588    else
589        injectpair(0,0)
590    end
591end
592function mp.lmt_glyphshape_usedline()
593    if mpdata then
594        mpprint(mpdata.usedline)
595    else
596        injectpair(0,0)
597    end
598end
599
600function mp.lmt_glyphshape_width () injectnumeric(mpdata and mpdata.width  or 0) end
601function mp.lmt_glyphshape_depth () injectnumeric(mpdata and mpdata.depth  or 0) end
602function mp.lmt_glyphshape_height() injectnumeric(mpdata and mpdata.height or 0) end
603function mp.lmt_glyphshape_italic() injectnumeric(mpdata and mpdata.italic or 0) end
604function mp.lmt_glyphshape_accent() injectnumeric(mpdata and mpdata.accent or 0) end
605function mp.lmt_glyphshape_llx   () injectnumeric(mpdata and mpdata.llx    or 0) end
606