local module = {} setmetatable(module, {__index=_G}) setfenv(1, module) local m = require 'marigold-svg.marigold' local function merge2(a, b) local tbl = {} for k, v in pairs(a) do tbl[k] = v end for k, v in pairs(b) do tbl[k] = v end return tbl end local function merge_recursive(array, acc) local tbl = array[1] table.remove(array, 1) local acc = merge2(acc, tbl) if #array == 0 then return acc end return merge_recursive(array, acc) end local function merge(...) return merge_recursive({...}, {}) end function capitalize(str) return string.gsub(str, "^.", string.upper) end --===== boxes =====-- Box = {} function Box.new(_, tbl) local parent = tbl.parent or { x0=0, y0=0 } local width = tbl.width or parent.width local height = tbl.height or parent.width local offset = tbl.offset or { x=0, y=0 } local padding = tbl.padding or { left=0, right=0, top=0, bottom=0 } padding.left = padding.left or 0 padding.right = padding.right or 0 padding.top = padding.top or 0 padding.bottom = padding.bottom or 0 local self = { x0 = parent.x0 + offset.x + padding.left, y0 = parent.y0 + offset.y + padding.top, width = width - padding.left - padding.right, height = height - padding.top - padding.bottom, } setmetatable(self, {__index=Box}) return self end setmetatable(Box, {__call=Box.new}) function Box.point(self, x, y) return x + self.x0, y + self.y0 end function Box.center(self) return self.x0 + (self.width/2), self.y0 + (self.height/2) end local function hsplit(box, acc, count, settings) local index = #acc if index == count then return acc end local z = box.width / count table.insert( acc, Box(merge({ parent=box, width=z, offset= { x=index*z, y=0 } }, settings)) ) return hsplit(box, acc, count, settings) end function Box.hsplit(self, count, settings) local count = count or 2 local settings = settings or {} return hsplit(self, {}, count, settings) end local function vsplit(box, acc, count, settings) local index = #acc if index == count then return acc end local z = box.height / count table.insert( acc, Box(merge({ parent=box, height=z, offset= { y=index*z, x=0 } }, settings)) ) return vsplit(box, acc, count, settings) end function Box.vsplit(self, count, settings) local count = count or 2 local settings = settings or {} return vsplit(self, {}, count, settings) end function Box.split_down(self, height) local a = Box{ parent=self, height=height, } local b = Box{ parent=self, height=self.height - height, offset={x=0, y=height}, } return a, b end --===== misc =====-- local function stack(...) local array = {...} local l l = function(acc_f, acc_h) if #array == 0 then return acc_f, acc_h end local f, h = array[1][1], array[1][2] table.remove(array, 1) local acc_f = function(y) local tbl = acc_f(y) table.insert(tbl, f(y+acc_h)) return tbl end local acc_h = acc_h + h return l(acc_f, acc_h) end local f, h = l(function(y) return {} end, 0) return function(y) return m.g(f(y)) end, h end local function pad(top, bottom, f, h) return function(y) return f(y+top) end, top + h + bottom end local function _divider() local size = 3 return function(y) return m.rect{ x=0, y=y, width=500, height=size, style="fill:darkred;", } end, size end local divider = function() return pad(0, 10, _divider()) end --===== basic stats =====-- local function title(stats) local size = 40 return function(y) return m.text(stats.title, { x=10, y=y+size, style="font-size:"..size.."px;font-variant-caps:small-caps;stroke:black;fill:darkred;font-family:serif", }) end, size end local function subheading(stats) local size = 14 return function(y) return m.text( string.format("%s %s, %s", capitalize(stats.size), stats.type, stats.alignment), { x=10, y=y+size, style="font-size:"..size.."px;font-style:italic;" } ) end, size end local function header(stats) local f, h = stack( {pad(10, 10, title(stats))}, {pad(0, 10, subheading(stats))} ) return f, h end local function _statline(name, value) local size = 14 return function(y) return m.text( ''..name..' '..value, { x=10, y=y+size, style="font-size:"..size.."px;fill:darkred;" } ) end, 14 end local statline = function(name, value) return pad(0, 2, _statline(name, value)) end local function armor_hp(stats) local f, h = pad(0, 10, stack( {statline('Armor Class', stats.ac)}, {statline('Hit Points', stats.hp)}, {statline('Speed', stats.speed)} )) return f, h end local function bonus(raw) return math.floor( (raw-10)/2 ) end local function rawbox(name, raw, x, y) local raw = tonumber(raw) local value = string.format("%d (%s%d)", raw, (raw<0 and '-') or '+', bonus(raw)) return m.g{ m.text(name, { x=x+10, y=y+15, style="font-weight:bold;font-size:15px;fill:darkred" }), m.text(value, {x=x, y=y+30, style="font-size:15px;fill:darkred;" }), } end local function raw(stats) return function(y) return m.g{ rawbox('STR', stats.str, 10, y), rawbox('DEX', stats.dex, 90, y), rawbox('CON', stats.con, 170, y), rawbox('INT', stats.int, 250, y), rawbox('WIS', stats.wis, 330, y), rawbox('CHA', stats.cha, 410, y), } end, 40 end local function base(stats) local f, h = stack( {header(stats)}, {divider()}, {armor_hp(stats)}, {divider()}, {raw(stats)}, {divider()} ) return f, h end --===== attributes =====-- local function attribs(stats) return function(y) return m.rect{ x=0, y=y, width=500, height=20, style="fill:blue", } end, 20 end --===== actions =====-- local function actions(stats) return function(y) return m.g{} end, 0 end --===== draw =====-- function draw(stats) local f, h = stack({base(stats)}, {attribs(stats)}, {actions(stats)}) return m.render(m.svg{ viewBox = string.format("0 0 500 %d", h), width = 500, height = height, style = "font-family:sans-serif;", f(0), }) end return module