%D \module
%D   [       file=m-punk,
%D        version=2008.04.15,
%D          title=\CONTEXT\ Modules,
%D       subtitle=Punk Support,
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

%D You can actually turn a punk font into music (wintergatan):
%D
%D \starttyping
%D https://www.youtube.com/watch?v=g5c2Htj8Vtw
%D \stoptyping

\ifcase\contextlmtxmode\else
    \writestatus{punk}{use metapost library punk instead}
    \expandafter\endinput
\fi

\startluacode
local concat   = table.concat
local round    = math.round
local chardata = characters.data
local fontdata = fonts.hashes.identifiers

fonts.mp = fonts.mp or { }

fonts.mp.version = fonts.mp.version or 1.15
fonts.mp.inline  = true
fonts.mp.cache   = containers.define("fonts", "mp", fonts.mp.version, true)

metapost.characters = metapost.characters or { }

-- todo: use table share as in otf

local characters, descriptions = { }, { }
local factor, l, n, w, h, d, total, variants = 100, { }, 0, 0, 0, 0, 0, 0, true

-- A next version of mplib will provide the tfm font information which
-- gives better glyph dimensions, plus additional kerning information.

local flusher = {
    startfigure = function(chrnum,llx,lly,urx,ury)
        l, n = { }, chrnum
        w, h, d = urx - llx, ury, -lly
        total = total + 1
        inline = fonts.mp.inline
    end,
    flushfigure = function(t)
        for i=1, #t do
            l[#l+1] = t[i]
        end
    end,
    stopfigure = function()
        local cd = chardata[n]
        if inline then
            descriptions[n] = {
            --  unicode     = n,
                name        = cd and cd.adobename,
                width       = w*100,
                height      = h*100,
                depth       = d*100,
                boundingbox = { 0, -d, w, h },
            }
            characters[n] = {
                commands = { -- todo: type 3 in lmtx
                    { "pdf", concat(l," ") },
                }
            }
        else
            descriptions[n] = {
            --  unicode = n,
                name = cd and cd.adobename,
                width = w*100,
                height = h*100,
                depth = d*100,
                boundingbox = { 0, -d, w, h },
            }
            characters[n] = {
                commands = {
                    { "image", { stream = concat(l," "), bbox = { 0, -d*65536, w*65536, h*65536 } } },
                }
            }
        end
    end
}

metapost.characters.instances = metapost.characters.instances or 10

function metapost.characters.process(mpxformat, name, instances, scalefactor)
    statistics.starttiming(metapost.characters)
    scalefactor = scalefactor or 1
    instances = instances or metapost.characters.instances or 10
    local fontname = file.removesuffix(file.basename(name))
    local hash  = file.robustname(string.format("%s %05i %03i", fontname, round(scalefactor*1000), instances))
    local lists = containers.read(fonts.mp.cache, hash)
    if not lists then
        statistics.starttiming(flusher)
        -- we can use a format per font
        local data = io.loaddata(resolvers.findfile(name))
        metapost.reset(mpxformat)
        metapost.setoutercolor(2) -- no outer color and no reset either
        lists = { }
        for i=1,instances do
            characters   = { }
            descriptions = { }
            metapost.process {
                mpx         = mpxformat,
             -- trialrun    = false,
                flusher     = flusher,
             -- multipass   = false,
             -- isextrapass = false,
                askedfig    = "all",
             -- incontext   = false,
                data        = {
                    "randomseed := " .. i*10 .. ";",
                    "scale_factor := " .. scalefactor .. " ;",
                    data
                },
            }
            lists[i] = {
                characters   = characters,
                descriptions = descriptions,
                parameters   = {
                    designsize    = 655360,
                    slant         =      0,
                    space         =    333   * scalefactor,
                    space_stretch =    166.5 * scalefactor,
                    space_shrink  =    111   * scalefactor,
                    x_height      =    431   * scalefactor,
                    quad          =   1000   * scalefactor,
                    extra_space   =      0,
                },
                properties  = {
                    name          = string.format("%s-%03i",hash,i),
                    virtualized   = true,
                    spacer        = "space",
                }
            }
        end
        metapost.reset(mpxformat) -- saves memory
        lists = containers.write(fonts.mp.cache, hash, lists)
        statistics.stoptiming(flusher)
    end
    variants = variants + #lists
    statistics.stoptiming(metapost.characters)
    return lists
end

function fonts.handlers.vf.combiner.commands.metafont(g,v)
    local size = g.specification.size
    local data = metapost.characters.process(v[2],v[3],v[4],size/655360)
    local list, t = { }, { }
    for d=1,#data do
        t = data[d]
        t = fonts.constructors.scale(t, -1000)
        local id = font.nextid()
        t.fonts = { { id = id } }
        fontdata[id] = t
        fonts.handlers.vf.helpers.composecharacters(t)
        list[d] = font.define(t)
    end
    for k, v in next, t do
        g[k] = v -- kind of replace, when not present, make nil
    end
    g.properties.virtualized = true
    g.variants = list
end

fonts.definers.methods.install( "punk", {
    { "metafont", "mfplain", "punkfont.mp", 10 },
} )
fonts.definers.methods.install( "punkbold", {
    { "metafont", "mfplain", "punkfont-bold.mp", 10 },
} )
fonts.definers.methods.install( "punkslanted", {
    { "metafont", "mfplain", "punkfont-slanted.mp", 10 },
} )
fonts.definers.methods.install( "punkboldslanted", {
    { "metafont", "mfplain", "punkfont-boldslanted.mp", 10 },
} )

-- typesetters.cases.register("RandomPunk", function(current)
--     local used = fontdata[current].variants
--     if used then
--         local f = math.random(1,#used)
--         current.font = used[f]
--         return current, true
--     else
--         return current, false
--     end
-- end)

local getfont  = nodes.nuts.getfont
local setfield = nodes.nuts.setfield
local random   = math.random

typesetters.cases.register("RandomPunk", function(start)
    local used = fontdata[getfont(start)].variants
    if used then
        local f = random(1,#used)
        setfield(start,"font",used[f])
        return start, true
    else
        return start, false
    end
end)

metapost.characters.flusher = flusher

statistics.register("metapost font generation", function()
    local time = statistics.elapsedtime(flusher)
    if total > 0 then
        return string.format("%i glyphs, %s seconds runtime, %0.3f glyphs/second", total, time, total/tonumber(time))
    else
        return string.format("%i glyphs, %s seconds runtime", total, time)
    end
end)

statistics.register("metapost font loading",function()
    local time = statistics.elapsedtime(metapost.characters)
    if variants > 0 then
        return string.format("%s seconds, %i instances, %0.3f instances/second", time, variants, variants/tonumber(time))
    else
        return string.format("%s seconds, %i instances", time, variants)
    end
end)
\stopluacode

\unexpanded\def\EnableRandomPunk {\setcharactercasing[RandomPunk]}
\unexpanded\def\RandomPunk       {\groupedcommand\EnableRandomPunk\donothing}
\unexpanded\def\StartRandomPunk  {\begingroup\EnableRandomPunk}
\unexpanded\def\StopRandomPunk   {\endgroup}

\starttypescript [serif] [punk]
    \definefontsynonym [Serif]            [demo@punk]
    \definefontsynonym [SerifBold]        [demobold@punkbold]
    \definefontsynonym [SerifSlanted]     [demoslanted@punkslanted]
    \definefontsynonym [SerifBoldSlanted] [demoboldslanted@punkboldslanted]
    \definefontsynonym [SerifItalic]      [SerifSlanted]
    \definefontsynonym [SerifBoldItalic]  [SerifBoldSlanted]
\stoptypescript

\starttypescript [punk]
    \definetypeface [punk] [rm] [serif] [punk] [default]
\stoptypescript

\continueifinputfile{m-punk.mkiv}

\usetypescript[punk]

\setupbodyfont[punk,14pt]

\starttext
    \definedfont[demo@punk at 10pt]hello world\par
    \definedfont[demo@punk at 12pt]hello world\par
    \definedfont[demo@punk at 16pt]hello world\par
    \definedfont[demo@punk at 20pt]hello world\par
\stoptext