back-imp-lua.lmt /size: 9 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['back-imp-lua'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to back-imp-lua.mkiv",
5    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6    copyright = "PRAGMA ADE / ConTeXt Development Team",
7    license   = "see context related readme files"
8}
9
10-- we can remap fonts
11
12local fontproperties    = fonts.hashes.properties
13local fontparameters    = fonts.hashes.parameters
14local fontshapes        = fonts.hashes.shapes
15
16local starttiming       = statistics.starttiming
17local stoptiming        = statistics.stoptiming
18
19local bpfactor          = number.dimenfactors.bp
20local texgetbox         = tex.getbox
21
22local rulecodes         = nodes.rulecodes
23local normalrule_code   = rulecodes.normal
24----- boxrule_code      = rulecodes.box
25----- imagerule_code    = rulecodes.image
26----- emptyrule_code    = rulecodes.empty
27----- userrule_code     = rulecodes.user
28----- overrule_code     = rulecodes.over
29----- underrule_code    = rulecodes.under
30----- fractionrule_code = rulecodes.fraction
31----- radicalrule_code  = rulecodes.radical
32local outlinerule_code  = rulecodes.outline
33
34-- todo : per instance
35
36local pages     = { }
37local fonts     = { }
38local names     = { }
39local mapping   = { }
40local used      = { }
41local shapes    = { }
42local glyphs    = { }
43local buffer    = { }
44local metadata  = nil
45local b         = 0
46local converter = nil
47
48local compact   = false -- true
49local shapestoo = true
50
51local x, y, d, f, c, w, h, t, r, o
52
53local function reset()
54    buffer = { }
55    b      = 0
56    t      = nil
57    x      = nil
58    y      = nil
59    d      = nil
60    f      = nil
61    c      = nil
62    w      = nil
63    h      = nil
64    r      = nil
65    o      = nil
66end
67
68local function result()
69    return {
70        metadata = metadata,
71        fonts    = fonts,
72        pages    = pages,
73        shapes   = shapes,
74    }
75end
76
77-- actions
78
79local function outputfilename(driver)
80    return tex.jobname .. "-output.lua"
81end
82
83local function save() -- might become a driver function that already is plugged into stopactions
84    local filename = outputfilename()
85    drivers.report("saving result in %a",filename)
86    starttiming(drivers)
87    local data = result()
88    if data then
89        io.savedata(filename,table.serialize(data))
90    end
91    stoptiming(drivers)
92end
93
94local function prepare(driver)
95    converter = drivers.converters.lmtx
96 -- if not environment.initex then
97        backends.initialize("lua")
98 -- end
99    luatex.registerstopactions(1,function()
100        save()
101    end)
102end
103
104local function initialize(driver,details)
105    reset()
106end
107
108local function finalize(driver,details)
109    local n = details.pagenumber
110    local b = details.boundingbox
111    pages[n] = {
112        number      = n,
113        content     = buffer,
114        boundingbox = {
115            b[1] * bpfactor,
116            b[2] * bpfactor,
117            b[3] * bpfactor,
118            b[4] * bpfactor,
119        },
120    }
121    if not metadata then
122        -- this has to happen while we're still running tex because we do
123        -- prerolls
124        local identity = interactions.general.getidentity()
125        local jobname  = environment.jobname or tex.jobname or "unknown"
126        metadata = {
127            unit     = "bp",
128            jobname  = jobname,
129            title    = identity.title,
130            subject  = identity.subject,
131            author   = identity.author,
132            keywords = identity.keywords,
133            time     = os.date("%Y-%m-%d %H:%M"),
134            engine   = environment.luatexengine .. " " .. environment.luatexversion,
135            context  = environment.version,
136        }
137    end
138end
139
140local function wrapup(driver)
141end
142
143local function cleanup(driver)
144    reset()
145end
146
147local function convert(driver,boxnumber,pagenumber)
148    converter(driver,texgetbox(boxnumber),"page",pagenumber)
149end
150
151-- flushers
152
153local function updatefontstate(id)
154    if not mapping[id] then
155        local fn = #fonts + 1
156        mapping[id] = fn
157        local properties = fontproperties[id]
158        local parameters = fontparameters[id]
159        local filename   = file.basename(properties.filename)
160        local fontname   = properties.fullname or properties.fontname
161        if shapestoo then
162            if not names[fontname] then
163                local sn = #shapes+1
164                names[fontname] = sn
165                shapes[sn] = { }
166                glyphs[sn] = fontshapes[id].glyphs
167            end
168        end
169        fonts[fn] = {
170            filename = filename,
171            name     = fontname,
172            size     = parameters.size * bpfactor,
173            shapes   = shapestoo and names[fontname] or nil,
174        }
175    end
176end
177
178local function flushcharacter(current, pos_h, pos_v, pos_r, font, char)
179    local fnt = mapping[font]
180    b = b + 1
181    buffer[b] = {
182        t = "glyph" ~= t and "glyph" or nil,
183        f = font    ~= f and fnt or nil,
184        c = char    ~= c and char or nil,
185        x = pos_h   ~= x and (pos_h * bpfactor) or nil,
186        y = pos_v   ~= y and (pos_v * bpfactor) or nil,
187        d = pos_r   ~= d and (pos_r == 1 and "r2l" or "l2r") or nil,
188    }
189    t = "glyph"
190    f = font
191    c = char
192    x = pos_h
193    y = pos_v
194    d = pos_r
195    if shapestoo then
196        -- can be sped up if needed
197        local sn = fonts[fnt].shapes
198        local shp = shapes[sn]
199        if not shp[char] then
200            shp[char] = glyphs[sn][char]
201        end
202    end
203end
204
205local function flush_rule(current, pos_h, pos_v, pos_r, size_h, size_v, rule_s, rule_o)
206    b = b + 1
207    buffer[b] = {
208        t = "rule" ~= t and "rule" or nil,
209        r = rule_s ~= r and rule_s or nil,
210        o = rule_s == "outline" and rule_o ~= o and (rule_o * bpfactor) or nil,
211        w = size_h ~= w and (size_h * bpfactor) or nil,
212        h = size_v ~= h and (size_v * bpfactor) or nil,
213        x = pos_h  ~= x and (pos_h  * bpfactor) or nil,
214        y = pos_v  ~= y and (pos_v  * bpfactor) or nil,
215        d = pos_r  ~= d and (pos_r == 1 and "r2l" or "l2r") or nil,
216    }
217    t = "rule"
218    w = size_h
219    h = size_v
220    x = pos_h
221    y = pos_v
222    d = pos_r
223end
224
225local function flushrule(current, pos_h, pos_v, pos_r, size_h, size_v, subtype)
226    local rule_s, rule_o
227    if subtype == normalrule_code then
228        rule_s = normal_rule_code
229    elseif subtype == outlinerule_code then
230        rule_s = outline_rule_code
231        rule_o = getdata(current)
232    else
233        return
234    end
235    return flush_rule(pos_h, pos_v, pos_r, size_h, size_v, rule_s, rule_o)
236end
237
238local function flushsimplerule(pos_h, pos_v, pos_r, size_h, size_v)
239    return flush_rule(false,pos_h,pos_v,pos_r,size_h,size_v,normalrule_code,nil)
240end
241
242local function flushspecialrule(pos_h, pos_v, pos_r, w, h, d, l, outline)
243    return flush_rule(false,pos_h,pos_v-d,pos_r,w,h+d,outline and outlinerule_code or normalrule_code)
244end
245
246-- file stuff too
247-- todo: default flushers
248-- also color (via hash)
249
250-- installer
251
252drivers.install {
253    name    = "lua",
254    actions = {
255        prepare         = prepare,
256        initialize      = initialize,
257        finalize        = finalize,
258        wrapup          = wrapup,
259        cleanup         = cleanup,
260        convert         = convert,
261        outputfilename  = outputfilename,
262    },
263    flushers = {
264        updatefontstate = updatefontstate,
265        character       = flushcharacter,
266        rule            = flushrule,
267        simplerule      = flushsimplerule,
268        specialrule     = flushspecialrule,
269    }
270}
271
272-- actions
273
274local function outputfilename(driver)
275    return tex.jobname .. "-output.json"
276end
277
278local function save() -- might become a driver function that already is plugged into stopactions
279    local filename = outputfilename()
280    drivers.report("saving result in %a",filename)
281    starttiming(drivers)
282    local data = result()
283    if data then
284        if not utilities.json then
285            require("util-jsn")
286        end
287        io.savedata(filename,utilities.json.tostring(data,not compact))
288    end
289    stoptiming(drivers)
290end
291
292local function prepare(driver)
293    converter = drivers.converters.lmtx
294    luatex.registerstopactions(1,function()
295        save()
296    end)
297end
298
299-- installer
300
301drivers.install {
302    name    = "json",
303    actions = {
304        prepare         = prepare,
305        wrapup          = wrapup,
306        cleanup         = cleanup,
307        --
308        initialize      = initialize,
309        convert         = convert,
310        finalize        = finalize,
311        --
312        outputfilename  = outputfilename,
313    },
314    flushers = {
315        updatefontstate = updatefontstate,
316        character       = flushcharacter,
317        rule            = flushrule,
318        simplerule      = flushsimplerule,
319        specialrule     = flushspecialrule,
320        setstate        = function() end,
321    }
322}
323
324-- actions
325
326local function outputfilename(driver)
327    return tex.jobname .. "-output.js"
328end
329
330local function save() -- might become a driver function that already is plugged into stopactions
331    local filename = outputfilename()
332    drivers.report("saving result in %a",filename)
333    starttiming(drivers)
334    local data = result()
335    if data then
336        if not utilities.json then
337            require("util-jsn")
338        end
339        io.savedata(filename,
340            "const JSON_CONTEXT = JSON.parse ( `" ..
341            utilities.json.tostring(data,not compact) ..
342            "` ) ;\n"
343        )
344    end
345    stoptiming(drivers)
346end
347
348local function prepare(driver)
349    converter = drivers.converters.lmtx
350 -- if not environment.initex then
351        backends.initialize("js")
352 -- end
353    luatex.registerstopactions(1,function()
354        save()
355    end)
356end
357
358-- installer
359
360drivers.install {
361    name    = "js",
362    actions = {
363        prepare         = prepare,
364        initialize      = initialize,
365        finalize        = finalize,
366        wrapup          = wrapup,
367        cleanup         = cleanup,
368        convert         = convert,
369        outputfilename  = outputfilename,
370    },
371    flushers = {
372        updatefontstate = updatefontstate,
373        character       = flushcharacter,
374        rule            = flushrule,
375        simplerule      = flushsimplerule,
376        specialrule     = flushspecialrule,
377    }
378}
379