-- [ʞ] render/groff.lua
-- ~ lexi hale <lexi@hale.su>
-- 🄯 AGPLv3
-- ? renders cortav to groff source code, for creating pdfs,
-- dvis, manapages, and html files that are grievously
-- inferior compared to our own illustrious direct-html
-- renderer.
-- > cortav -m render:format groff
local ct = require 'cortav'
local ss = require 'sirsem'
local tcat = function(a,b)
for i,v in ipairs(b) do
table.insert(a, b)
end
return a
end
local lines = function(...)
local s = ss.strac()
for _, v in pairs{...} do s(v) end
return s
end
function ct.render.groff(doc, opts)
-- rs contains state specific to this render job
-- that modules will need access to
local rs = {};
rs.macsets = {
strike = {
'.de ST';
[[.nr ww \w'\\$1']];
[[\Z@\v'-.25m'\l'\\n[ww]u'@\\$1']];
'..';
};
}
rs.macsNeeded = {
order = {};
count = 0;
}
function rs.macAdd(id)
if rs.macsets[id] then
rs.macsNeeded.count = macsNeeded.count + 1
rs.macsNeeded.order[rs.macsNeeded.count] = id
return true
else return false end
end
local job = doc:job('render_groff',nil,rs)
-- the way this module works is we build up a table for each block
-- of individual strings paired with attributes that say how they
-- should be rendered. we then iterate over the table, applying
-- formats as need be, and inserting blanks after each block
local spanRenderers = {}
function spanRenderers.format(rc, s, b, sec)
local rcc = rc:clone()
if s.style == 'strong' then
rcc.prop.bold = true
elseif s.style == 'emph' then
rcc.prop.emph = true
elseif s.style == 'strike' then
rcc.prop.strike = true
rs.macAdd 'strike'
elseif s.style == 'insert' then
end
rs.renderSpans(rcc, s.spans, b, sec)
end;
function rs.renderSpans(rc, sp, b, sec)
for i, v in ipairs(sp) do
if type(v) == 'string' then
rc:add(v)
elseif spanRenderers[v.kind] then
spanRenderers[v.kind](rc, v, b, sec)
end
end
end
local blockRenderers = {}
function blockRenderers.paragraph(rc, b, sec)
rs.renderSpans(rc, b.spans, b, sec)
end
function rs.renderBlock(b, sec)
local rc = {
clone = function(self)
return {
clone = self.clone;
lines = self.lines;
prop = ss.clone(self.prop);
mk = self.mk;
add = self.add;
}
end;
lines = {};
prop = {};
mk = function(self, ln)
local p = ss.clone(self.prop)
p.txt = ln
return p
end;
add = function(self, ln)
table.insert(self.lines, self:mk(ln))
end;
}
if blockRenderers[b.kind] then
blockRenderers[b.kind](rc, b, sec)
end
return rc.lines
end
function rs.emitLine(ln)
local q = ss.strac()
if ln.dsz then
q('\\ps +' .. tostring(ln.dsz))
elseif ln.sz then
q('\\ps ' .. tostring(ln.dsz))
end
if ln.bold and ln.emph then
q '\\f(BI'
elseif ln.bold then
q '\\fB'
elseif ln.emph then
q '\\fI'
end
q(ln.txt)
if ln.bold or ln.emph then
q'\\f[]'
end
if ln.dsz then
q('.ps -' .. tostring(ln.dsz))
elseif ln.sz then
q '.ps'
end
return q
end
local ir = {}
for i, sec in ipairs(doc.secorder) do
if sec.kind == 'ordinary' then
local blks = {}
for j, b in ipairs(sec.blocks) do
local r = rs.renderBlock(b, sec)
if r then table.insert(blks, r) end
end
table.insert(ir, blks)
end
end
local rd = ss.strac()
for i, s in ipairs(ir) do
for j, b in ipairs(s) do
for z, l in ipairs(b) do
rd(rs.emitLine(l))
end
rd'\n'
end
end
local macs = ss.strac()
for _, m in pairs(rs.macsNeeded.order) do
for _, ln in pairs(m) do macs(ln) end
end
return macs:compile'\n' .. rd:compile''
end