From e32759f9f401f8c001e0f0f907beb3f9342680b7 Mon Sep 17 00:00:00 2001 From: sanine Date: Sat, 16 Sep 2023 04:12:00 -0500 Subject: first light --- .gitignore | 1 + demo.lua | 24 ++++++ draw.lua | 272 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 297 insertions(+) create mode 100644 .gitignore create mode 100755 demo.lua create mode 100644 draw.lua 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( + ''..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 -- cgit v1.2.1