cortav  groff.lua at [9215a9c850]

File render/groff.lua artifact a43bfa19e3 part of check-in 9215a9c850


-- [ʞ] 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