back-lua.lmt /size: 9 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['back-lua'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to lpdf-ini.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    luatex.registerstopactions(1,function()
97        save()
98    end)
99end
100
101local function initialize(driver,details)
102    reset()
103end
104
105local function finalize(driver,details)
106    local n = details.pagenumber
107    local b = details.boundingbox
108    pages[n] = {
109        number      = n,
110        content     = buffer,
111        boundingbox = {
112            b[1] * bpfactor,
113            b[2] * bpfactor,
114            b[3] * bpfactor,
115            b[4] * bpfactor,
116        },
117    }
118    if not metadata then
119        -- this has to happen while we're still running tex because we do
120        -- prerolls
121        local identity = interactions.general.getidentity()
122        local jobname  = environment.jobname or tex.jobname or "unknown"
123        metadata = {
124            unit     = "bp",
125            jobname  = jobname,
126            title    = identity.title,
127            subject  = identity.subject,
128            author   = identity.author,
129            keywords = identity.keywords,
130            time     = os.date("%Y-%m-%d %H:%M"),
131            engine   = environment.luatexengine .. " " .. environment.luatexversion,
132            context  = environment.version,
133        }
134    end
135end
136
137local function wrapup(driver)
138end
139
140local function cleanup(driver)
141    reset()
142end
143
144local function convert(driver,boxnumber,pagenumber)
145    converter(driver,texgetbox(boxnumber),"page",pagenumber)
146end
147
148-- flushers
149
150local function updatefontstate(id)
151    if not mapping[id] then
152        local fn = #fonts + 1
153        mapping[id] = fn
154        local properties = fontproperties[id]
155        local parameters = fontparameters[id]
156        local filename   = file.basename(properties.filename)
157        local fontname   = properties.fullname or properties.fontname
158        if shapestoo then
159            if not names[fontname] then
160                local sn = #shapes+1
161                names[fontname] = sn
162                shapes[sn] = { }
163                glyphs[sn] = fontshapes[id].glyphs
164            end
165        end
166        fonts[fn] = {
167            filename = filename,
168            name     = fontname,
169            size     = parameters.size * bpfactor,
170            shapes   = shapestoo and names[fontname] or nil,
171        }
172    end
173end
174
175local function flushcharacter(current, pos_h, pos_v, pos_r, font, char)
176    local fnt = mapping[font]
177    b = b + 1
178    buffer[b] = {
179        t = "glyph" ~= t and "glyph" or nil,
180        f = font    ~= f and fnt or nil,
181        c = char    ~= c and char or nil,
182        x = pos_h   ~= x and (pos_h * bpfactor) or nil,
183        y = pos_v   ~= y and (pos_v * bpfactor) or nil,
184        d = pos_r   ~= d and (pos_r == 1 and "r2l" or "l2r") or nil,
185    }
186    t = "glyph"
187    f = font
188    c = char
189    x = pos_h
190    y = pos_v
191    d = pos_r
192    if shapestoo then
193        -- can be sped up if needed
194        local sn = fonts[fnt].shapes
195        local shp = shapes[sn]
196        if not shp[char] then
197            shp[char] = glyphs[sn][char]
198        end
199    end
200end
201
202local function flush_rule(current, pos_h, pos_v, pos_r, size_h, size_v, rule_s, rule_o)
203    b = b + 1
204    buffer[b] = {
205        t = "rule" ~= t and "rule" or nil,
206        r = rule_s ~= r and rule_s or nil,
207        o = rule_s == "outline" and rule_o ~= o and (rule_o * bpfactor) or nil,
208        w = size_h ~= w and (size_h * bpfactor) or nil,
209        h = size_v ~= h and (size_v * bpfactor) or nil,
210        x = pos_h  ~= x and (pos_h  * bpfactor) or nil,
211        y = pos_v  ~= y and (pos_v  * bpfactor) or nil,
212        d = pos_r  ~= d and (pos_r == 1 and "r2l" or "l2r") or nil,
213    }
214    t = "rule"
215    w = size_h
216    h = size_v
217    x = pos_h
218    y = pos_v
219    d = pos_r
220end
221
222local function flushrule(current, pos_h, pos_v, pos_r, size_h, size_v, subtype)
223    local rule_s, rule_o
224    if subtype == normalrule_code then
225        rule_s = normal_rule_code
226    elseif subtype == outlinerule_code then
227        rule_s = outline_rule_code
228        rule_o = getdata(current)
229    else
230        return
231    end
232    return flush_rule(pos_h, pos_v, pos_r, size_h, size_v, rule_s, rule_o)
233end
234
235local function flushsimplerule(pos_h, pos_v, pos_r, size_h, size_v)
236    return flush_rule(false,pos_h,pos_v,pos_r,size_h,size_v,normalrule_code,nil)
237end
238
239local function flushspecialrule(pos_h, pos_v, pos_r, w, h, d, l, outline)
240    return flush_rule(false,pos_h,pos_v-d,pos_r,w,h+d,outline and outlinerule_code or normalrule_code)
241end
242
243-- file stuff too
244-- todo: default flushers
245-- also color (via hash)
246
247-- installer
248
249drivers.install {
250    name    = "lua",
251    actions = {
252        prepare         = prepare,
253        initialize      = initialize,
254        finalize        = finalize,
255        wrapup          = wrapup,
256        cleanup         = cleanup,
257        convert         = convert,
258        outputfilename  = outputfilename,
259    },
260    flushers = {
261        updatefontstate = updatefontstate,
262        character       = flushcharacter,
263        rule            = flushrule,
264        simplerule      = flushsimplerule,
265        specialrule     = flushspecialrule,
266    }
267}
268
269-- actions
270
271local function outputfilename(driver)
272    return tex.jobname .. "-output.json"
273end
274
275local function save() -- might become a driver function that already is plugged into stopactions
276    local filename = outputfilename()
277    drivers.report("saving result in %a",filename)
278    starttiming(drivers)
279    local data = result()
280    if data then
281        if not utilities.json then
282            require("util-jsn")
283        end
284        io.savedata(filename,utilities.json.tostring(data,not compact))
285    end
286    stoptiming(drivers)
287end
288
289local function prepare(driver)
290    converter = drivers.converters.lmtx
291    luatex.registerstopactions(1,function()
292        save()
293    end)
294end
295
296-- installer
297
298drivers.install {
299    name    = "json",
300    actions = {
301        prepare         = prepare,
302        wrapup          = wrapup,
303        cleanup         = cleanup,
304        --
305        initialize      = initialize,
306        convert         = convert,
307        finalize        = finalize,
308        --
309        outputfilename  = outputfilename,
310    },
311    flushers = {
312        updatefontstate = updatefontstate,
313        character       = flushcharacter,
314        rule            = flushrule,
315        simplerule      = flushsimplerule,
316        specialrule     = flushspecialrule,
317        setstate        = function() end,
318    }
319}
320
321-- actions
322
323local function outputfilename(driver)
324    return tex.jobname .. "-output.js"
325end
326
327local function save() -- might become a driver function that already is plugged into stopactions
328    local filename = outputfilename()
329    drivers.report("saving result in %a",filename)
330    starttiming(drivers)
331    local data = result()
332    if data then
333        if not utilities.json then
334            require("util-jsn")
335        end
336        io.savedata(filename,
337            "const JSON_CONTEXT = JSON.parse ( `" ..
338            utilities.json.tostring(data,not compact) ..
339            "` ) ;\n"
340        )
341    end
342    stoptiming(drivers)
343end
344
345local function prepare(driver)
346    converter = drivers.converters.lmtx
347    luatex.registerstopactions(1,function()
348        save()
349    end)
350end
351
352-- installer
353
354drivers.install {
355    name    = "js",
356    actions = {
357        prepare         = prepare,
358        initialize      = initialize,
359        finalize        = finalize,
360        wrapup          = wrapup,
361        cleanup         = cleanup,
362        convert         = convert,
363        outputfilename  = outputfilename,
364    },
365    flushers = {
366        updatefontstate = updatefontstate,
367        character       = flushcharacter,
368        rule            = flushrule,
369        simplerule      = flushsimplerule,
370        specialrule     = flushspecialrule,
371    }
372}
373