m-punk.mkiv /size: 8745 b    last modification: 2023-12-21 09:45
1%D \module
2%D   [       file=m-punk,
3%D        version=2008.04.15,
4%D          title=\CONTEXT\ Modules,
5%D       subtitle=Punk Support,
6%D         author=Hans Hagen,
7%D           date=\currentdate,
8%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]
9%C
10%C This module is part of the \CONTEXT\ macro||package and is
11%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
12%C details.
13
14%D You can actually turn a punk font into music (wintergatan):
15%D
16%D \starttyping
17%D https://www.youtube.com/watch?v=g5c2Htj8Vtw
18%D \stoptyping
19
20\ifcase\contextlmtxmode\else
21    \writestatus{punk}{use metapost library punk instead}
22    \expandafter\endinput
23\fi
24
25\startluacode
26local concat   = table.concat
27local round    = math.round
28local chardata = characters.data
29local fontdata = fonts.hashes.identifiers
30
31fonts.mp = fonts.mp or { }
32
33fonts.mp.version = fonts.mp.version or 1.15
34fonts.mp.inline  = true
35fonts.mp.cache   = containers.define("fonts", "mp", fonts.mp.version, true)
36
37metapost.characters = metapost.characters or { }
38
39-- todo: use table share as in otf
40
41local characters, descriptions = { }, { }
42local factor, l, n, w, h, d, total, variants = 100, { }, 0, 0, 0, 0, 0, 0, true
43
44-- A next version of mplib will provide the tfm font information which
45-- gives better glyph dimensions, plus additional kerning information.
46
47local flusher = {
48    startfigure = function(chrnum,llx,lly,urx,ury)
49        l, n = { }, chrnum
50        w, h, d = urx - llx, ury, -lly
51        total = total + 1
52        inline = fonts.mp.inline
53    end,
54    flushfigure = function(t)
55        for i=1, #t do
56            l[#l+1] = t[i]
57        end
58    end,
59    stopfigure = function()
60        local cd = chardata[n]
61        if inline then
62            descriptions[n] = {
63            --  unicode     = n,
64                name        = cd and cd.adobename,
65                width       = w*100,
66                height      = h*100,
67                depth       = d*100,
68                boundingbox = { 0, -d, w, h },
69            }
70            characters[n] = {
71                commands = { -- todo: type 3 in lmtx
72                    { "pdf", concat(l," ") },
73                }
74            }
75        else
76            descriptions[n] = {
77            --  unicode = n,
78                name = cd and cd.adobename,
79                width = w*100,
80                height = h*100,
81                depth = d*100,
82                boundingbox = { 0, -d, w, h },
83            }
84            characters[n] = {
85                commands = {
86                    { "image", { stream = concat(l," "), bbox = { 0, -d*65536, w*65536, h*65536 } } },
87                }
88            }
89        end
90    end
91}
92
93metapost.characters.instances = metapost.characters.instances or 10
94
95function metapost.characters.process(mpxformat, name, instances, scalefactor)
96    statistics.starttiming(metapost.characters)
97    scalefactor = scalefactor or 1
98    instances = instances or metapost.characters.instances or 10
99    local fontname = file.removesuffix(file.basename(name))
100    local hash  = file.robustname(string.format("%s %05i %03i", fontname, round(scalefactor*1000), instances))
101    local lists = containers.read(fonts.mp.cache, hash)
102    if not lists then
103        statistics.starttiming(flusher)
104        -- we can use a format per font
105        local data = io.loaddata(resolvers.findfile(name))
106        metapost.reset(mpxformat)
107        metapost.setoutercolor(2) -- no outer color and no reset either
108        lists = { }
109        for i=1,instances do
110            characters   = { }
111            descriptions = { }
112            metapost.process {
113                mpx         = mpxformat,
114             -- trialrun    = false,
115                flusher     = flusher,
116             -- multipass   = false,
117             -- isextrapass = false,
118                askedfig    = "all",
119             -- incontext   = false,
120                data        = {
121                    "randomseed := " .. i*10 .. ";",
122                    "scale_factor := " .. scalefactor .. " ;",
123                    data
124                },
125            }
126            lists[i] = {
127                characters   = characters,
128                descriptions = descriptions,
129                parameters   = {
130                    designsize    = 655360,
131                    slant         =      0,
132                    space         =    333   * scalefactor,
133                    space_stretch =    166.5 * scalefactor,
134                    space_shrink  =    111   * scalefactor,
135                    x_height      =    431   * scalefactor,
136                    quad          =   1000   * scalefactor,
137                    extra_space   =      0,
138                },
139                properties  = {
140                    name          = string.format("%s-%03i",hash,i),
141                    virtualized   = true,
142                    spacer        = "space",
143                }
144            }
145        end
146        metapost.reset(mpxformat) -- saves memory
147        lists = containers.write(fonts.mp.cache, hash, lists)
148        statistics.stoptiming(flusher)
149    end
150    variants = variants + #lists
151    statistics.stoptiming(metapost.characters)
152    return lists
153end
154
155function fonts.handlers.vf.combiner.commands.metafont(g,v)
156    local size = g.specification.size
157    local data = metapost.characters.process(v[2],v[3],v[4],size/655360)
158    local list, t = { }, { }
159    for d=1,#data do
160        t = data[d]
161        t = fonts.constructors.scale(t, -1000)
162        local id = font.nextid()
163        t.fonts = { { id = id } }
164        fontdata[id] = t
165        fonts.handlers.vf.helpers.composecharacters(t)
166        list[d] = font.define(t)
167    end
168    for k, v in next, t do
169        g[k] = v -- kind of replace, when not present, make nil
170    end
171    g.properties.virtualized = true
172    g.variants = list
173end
174
175fonts.definers.methods.install( "punk", {
176    { "metafont", "mfplain", "punkfont.mp", 10 },
177} )
178fonts.definers.methods.install( "punkbold", {
179    { "metafont", "mfplain", "punkfont-bold.mp", 10 },
180} )
181fonts.definers.methods.install( "punkslanted", {
182    { "metafont", "mfplain", "punkfont-slanted.mp", 10 },
183} )
184fonts.definers.methods.install( "punkboldslanted", {
185    { "metafont", "mfplain", "punkfont-boldslanted.mp", 10 },
186} )
187
188-- typesetters.cases.register("RandomPunk", function(current)
189--     local used = fontdata[current].variants
190--     if used then
191--         local f = math.random(1,#used)
192--         current.font = used[f]
193--         return current, true
194--     else
195--         return current, false
196--     end
197-- end)
198
199local getfont  = nodes.nuts.getfont
200local setfield = nodes.nuts.setfield
201local random   = math.random
202
203typesetters.cases.register("RandomPunk", function(start)
204    local used = fontdata[getfont(start)].variants
205    if used then
206        local f = random(1,#used)
207        setfield(start,"font",used[f])
208        return start, true
209    else
210        return start, false
211    end
212end)
213
214metapost.characters.flusher = flusher
215
216statistics.register("metapost font generation", function()
217    local time = statistics.elapsedtime(flusher)
218    if total > 0 then
219        return string.format("%i glyphs, %s seconds runtime, %0.3f glyphs/second", total, time, total/tonumber(time))
220    else
221        return string.format("%i glyphs, %s seconds runtime", total, time)
222    end
223end)
224
225statistics.register("metapost font loading",function()
226    local time = statistics.elapsedtime(metapost.characters)
227    if variants > 0 then
228        return string.format("%s seconds, %i instances, %0.3f instances/second", time, variants, variants/tonumber(time))
229    else
230        return string.format("%s seconds, %i instances", time, variants)
231    end
232end)
233\stopluacode
234
235\unexpanded\def\EnableRandomPunk {\setcharactercasing[RandomPunk]}
236\unexpanded\def\RandomPunk       {\groupedcommand\EnableRandomPunk\donothing}
237\unexpanded\def\StartRandomPunk  {\begingroup\EnableRandomPunk}
238\unexpanded\def\StopRandomPunk   {\endgroup}
239
240\starttypescript [serif] [punk]
241    \definefontsynonym [Serif]            [demo@punk]
242    \definefontsynonym [SerifBold]        [demobold@punkbold]
243    \definefontsynonym [SerifSlanted]     [demoslanted@punkslanted]
244    \definefontsynonym [SerifBoldSlanted] [demoboldslanted@punkboldslanted]
245    \definefontsynonym [SerifItalic]      [SerifSlanted]
246    \definefontsynonym [SerifBoldItalic]  [SerifBoldSlanted]
247\stoptypescript
248
249\starttypescript [punk]
250    \definetypeface [punk] [rm] [serif] [punk] [default]
251\stoptypescript
252
253\continueifinputfile{m-punk.mkiv}
254
255\usetypescript[punk]
256
257\setupbodyfont[punk,14pt]
258
259\starttext
260    \definedfont[demo@punk at 10pt]hello world\par
261    \definedfont[demo@punk at 12pt]hello world\par
262    \definedfont[demo@punk at 16pt]hello world\par
263    \definedfont[demo@punk at 20pt]hello world\par
264\stoptext
265
266