#!/usr/bin/luajit -- LuaJIT FFI binding for Smolhershey. --[[ Example: font = (require "smolhershey").load("imlac-pds-1-ssvchr.22.jhf") font:gc(0, 0, print):show("Hello, Mina") The arguments to :gc() are the initial (x, y) and a callback function to invoke for each line segment. The argument to “show” can be either an ASCII string or a glyph number; the assumption is that the glyph numbers are ASCII minus 32. There’s a dirty trick in here. The arguments to the draw_line callback are structs consisting of two ints (plus a userdata pointer we don’t need.) But LuaJIT 2.1.1753364724 doesn’t support by-value struct arguments to callbacks from the FFI into Lua. On ARM64 (aarch64) these structs seem to be passed in as if they were longs, or anyway I seem to be able to get them out of longs with some bitwise math, so we lie to LuaJIT and tell it the callback arguments are longs. This happens to work on both ARM64 Linux and AMD64 Linux. --]] local ffi = require "ffi" local sh = ffi.load "./libsmolhershey.so" ffi.cdef[[ typedef unsigned char u8; typedef struct { u8 **lines; int n; } sh_font; typedef struct { int x, y; } sh_point; typedef struct { sh_font *font; sh_point cp; // Fake! See note above. void (*draw_line)(long p1, long p2); void *userdata; } sh_gc; int sh_load_font(sh_font *f, const u8 *buf, int n); void sh_show(sh_gc *gc, unsigned glyph_index); ]] local mod = {} -- The module namespace to return. -- Convert the results of unsigned bitwise arithmetic to -- 2’s-complement. local function ll_to_number(n) if n < 0x80000000 then return tonumber(n) end return tonumber(n) - 0x100000000 end -- Wrap a callback in a closure that unpacks the -- `sh_point` structs for it. local function unpacking_cb(cb) return function(p1, p2) local x1 = ll_to_number(bit.band(p1, 0xffffffff)) local y1 = ll_to_number(bit.rshift(p1, 32)) local x2 = ll_to_number(bit.band(p2, 0xffffffff)) local y2 = ll_to_number(bit.rshift(p2, 32)) cb(x1, y1, x2, y2) end end -- Fonts have one method, which creates a graphics context. local font_meta = { __index = { gc = function(font, x, y, draw_line) local gc = ffi.new("sh_gc") gc.font = font.font gc.cp = {x=x, y=y} gc.draw_line = unpacking_cb(draw_line) return gc end, }, } -- Converts an in-memory bytestring into a Hershey font object. function mod.string_to_font(s) -- We’re going to return this table, which -- is a level of indirection to retain s or lines -- and keep them from being GCed. local t = {s=s, font = ffi.new "sh_font"} -- Find out how many items are in the font. t.font.n = sh.sh_load_font(t.font, s, #s) -- Allocate space for them. t.lines = ffi.new("u8*["..t.font.n.."]") t.font.lines = t.lines -- Now actually populate them. sh.sh_load_font(t.font, s, #s) return setmetatable(t, font_meta) end -- Load a Hershey font from a filename such as -- "/usr/share/hershey-fonts/timesr.jhf". function mod.load(filename) local f = io.open(filename) local font = mod.string_to_font(f:read("*a")) f:close() return font end -- Graphics contexts have one method, show. But you can also save and -- restore properties of cp (current point). Saving and restoring cp -- itself doesn’t work well because LuaJIT does it by reference. ffi.metatype("sh_gc", { __index = { show = function(gc, c) if type(c) == "string" then for i = 1, #c do gc:show(c:byte(i) - 32) end return end sh.sh_show(gc, c) end, }, }) return mod