summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2023-09-16 04:12:00 -0500
committersanine <sanine.not@pm.me>2023-09-16 04:12:00 -0500
commite32759f9f401f8c001e0f0f907beb3f9342680b7 (patch)
treea671d85b643e45fa03a72f2c30cfeafe743749b3
parent285938592215e239a5388f8018cc12fe978b2c0a (diff)
first light
-rw-r--r--.gitignore1
-rwxr-xr-xdemo.lua24
-rw-r--r--draw.lua272
3 files changed, 297 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8516afb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+azer.svg
diff --git a/demo.lua b/demo.lua
new file mode 100755
index 0000000..bbf02ab
--- /dev/null
+++ b/demo.lua
@@ -0,0 +1,24 @@
+#!/usr/bin/env lua5.1
+
+local d = require 'draw'
+
+local stats = {
+ title = 'Azer',
+ alignment = "lawful neutral",
+ size = "medium",
+ type = "elemental",
+
+ ac = '17 (natural armor, shield)',
+ hp = '39 (6d8 + 12)',
+ speed = '30 ft.',
+
+ str = 17,
+ dex = 12,
+ con = 15,
+ int = 12,
+ wis = 13,
+ cha = 10,
+}
+
+
+print(d.draw(stats))
diff --git a/draw.lua b/draw.lua
new file mode 100644
index 0000000..e75274c
--- /dev/null
+++ b/draw.lua
@@ -0,0 +1,272 @@
+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(
+ '<tspan style="font-weight:bold">'..name..'</tspan> '..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