-- this file is part of barracuda project -- Copyright (C) 2019-2022 Roberto Giacomelli -- see https://github.com/robitex/barracuda -- -- libgeo simple Geometric Library -- All dimension must be in scaled point (sp) a TeX unit equal to 1/65536pt local libgeo = { _VERSION = "libgeo v0.0.6", _NAME = "libgeo", _DESCRIPTION = "simple geometric library", } -- a simple tree structured Archive class libgeo.Archive = {_classname = "Archive"} local Archive = libgeo.Archive Archive.__index = Archive function Archive:new() --> object local o = { _archive = {} } setmetatable(o, self) return o end function Archive:insert(o, ...) --> ok, err if type(o) ~= "table" then return false, "[Err] " end local a = self._archive local keys = {...} for i = 1, (#keys - 1) do -- dive into the tree local k = keys[i] local leaf = a[k] if not leaf then a[k] = {} leaf = a[k] end a = leaf end local k = keys[#keys] if a[k] ~= nil then return false, "[Err] an object " end a[k] = o return true, nil end function Archive:contains_key(...) --> boolean local a = self._archive for _, k in ipairs{...} do local leaf = a[k] if leaf == nil then return false end a = leaf end return true end function Archive:get(...) --> object, err local a = self._archive for _, k in ipairs{...} do local leaf = a[k] if leaf == nil then return nil, "[Err] key '"..k.."' not found" end a = leaf end return a, nil end -- Queue Class local VbarQueue = {_classname = "VbarQueue"} libgeo.Vbar_queue = VbarQueue VbarQueue.__index = VbarQueue function VbarQueue:new() local o = { 0 } setmetatable(o, self) return o end VbarQueue.__add = function (lhs, rhs) if type(lhs) == "number" then -- dist + queue local i = 1 while rhs[i] do rhs[i] = rhs[i] + lhs i = i + 2 end return rhs else -- queue + object if type(rhs) == "number" then lhs[#lhs] = lhs[#lhs] + rhs return lhs elseif type(rhs) == "table" then if rhs._classname == "VbarQueue" then -- queue + queue local q = {} for _, v in ipairs(lhs) do q[#q + 1] = v end local w = lhs[#lhs] for i = 1, #rhs/2 do q[#q + 1] = rhs[i] + w q[#q + 1] = rhs[i + 1] end return q elseif rhs._classname == "Vbar" then -- queue + vbar local w = lhs[#lhs] lhs[#lhs + 1] = rhs lhs[#lhs + 1] = w + rhs._x_lim return lhs else error("[Err] unsupported object type for queue operation") end else error("[Err] unsupported type for queue operation") end end end function VbarQueue:width() return self[#self] - self[1] end -- Vbar class -- a pure geometric entity of several infinite vertical lines libgeo.Vbar = {_classname = "Vbar"} local Vbar = libgeo.Vbar Vbar.__index = Vbar Vbar.__add = function (lhs, rhs) return VbarQueue:new() + lhs + rhs end -- Vbar costructors -- VBar costructor from an array [xcenter1, width1, xcenter2, width2, ...] function Vbar:from_array(yl_arr) --> assert(type(yl_arr) == "table", "'yline_array' is a mandatory arg") -- stream scanning local i = 1 local nbars = 0 local xlim = 0.0 while yl_arr[i] do local x = yl_arr[i]; i = i + 1 local w = yl_arr[i]; i = i + 1 assert(type(x) == "number", "[InternalErr] not a number") assert(type(w) == "number", "[InternalErr] not a number") xlim = x + w/2 nbars = nbars + 1 end assert(i % 2 == 0, "[InternalErr] the index is not even") assert(nbars > 0, "[InternalErr] empty array") local o = { _yline = yl_arr, -- [, , ...] flat array _x_lim = xlim, -- right external bounding box coordinates _nbars = nbars, -- number of bars } setmetatable(o, self) return o end -- costructor useful for Code128 encoder -- from an integer: 21231 -> binary 11 0 11 000 1 -> symbol 110110001 -- is_bar :: boolean :: bar or space as first element, default true function Vbar:from_int(ngen, mod, is_bar) --> assert(type(ngen) == "number", "Invalid argument for n") assert(type(mod) == "number", "Invalid argument for module width") if is_bar == nil then is_bar = true else assert(type(is_bar) == "boolean", "Invalid argument for is_bar") end -- scan ngen for digits local digits = {} while ngen > 0 do local d = ngen % 10 digits[#digits + 1] = d ngen = (ngen - d)/10 end local nbars = 0 local x0 = 0.0 -- axis reference local yl = {} for k = #digits, 1, -1 do local d = digits[k] local w = d*mod -- bar width if is_bar then -- bar yl[#yl + 1] = x0 + w/2 yl[#yl + 1] = w nbars = nbars + 1 end x0 = x0 + w is_bar = not is_bar end assert(nbars > 0, "[InternalErr] no bars") local o = { _yline = yl, -- [, , ...] flat array _x_lim = x0, -- right external coordinate _nbars = nbars, -- number of bars } setmetatable(o, self) return o end -- from an integer to read from right to left -- 13212 ->rev 21231 ->binary 11 0 11 000 1 -> symbol 110110001 -- is_bar :: boolean :: bar or space for first, default true function Vbar:from_int_revstep(ngen, mod, is_bar) --> assert(type(ngen) == "number", "Invalid argument for n") assert(type(mod) == "number", "Invalid argument for module width") if is_bar == nil then is_bar = true else assert(type(is_bar) == "boolean", "Invalid argument for is_bar") end -- local nbars = 0 local x0 = 0.0 -- axis reference local i = 0 local yl = {} while ngen > 0 do local d = ngen % 10 -- first digit local w = d*mod -- bar width if is_bar then -- bar i = i + 1; yl[i] = x0 + w/2 i = i + 1; yl[i] = w nbars = nbars + 1 end x0 = x0 + w is_bar = not is_bar ngen = (ngen - d)/10 end assert(not is_bar, "[InternalErr] the last element in not a bar") assert(nbars > 0, "[InternalErr] no bars") local o = { _yline = yl, -- [, , ...] flat array _x_lim = x0, -- right external coordinate _nbars = nbars, -- number of bars } setmetatable(o, self) return o end -- costructor useful for Code39 encoder -- i.e. 11212 -> rev -> 2 1 2 1 1 -> decodes to -> B w B w b -- build a yline array from the integer definition. Digit decoding rule: -- mod: b or w => 1 -- narrow bar/space -- MOD: B or W => 2 -- wide bar/space -- is_bar: the first element is a bar not a space, default to true function Vbar:from_int_revpair(ngen, mod, MOD, is_bar) --> assert(type(ngen) == "number", "Invalid argument for n") assert(type(mod) == "number", "Invalid argument for narrow module width") assert(type(MOD) == "number", "Invalid argument for wide module width") assert(mod < MOD, "Not ordered narrow/Wide values") if is_bar == nil then is_bar = true else assert(type(is_bar) == "boolean", "Invalid argument for 'is_bar'") end local nbars = 0 local yl = {} local x0 = 0.0 local k = 0 while ngen > 0 do local d = ngen % 10 -- digit ngen = (ngen - d)/10 local w; if d == 1 then w = mod elseif d == 2 then w = MOD end; assert(w, "[InternalErr] Allowed digits are only 1 or 2") if is_bar then -- bars k = k + 1; yl[k] = x0 + w/2 -- xcenter k = k + 1; yl[k] = w -- width nbars = nbars + 1 end is_bar = not is_bar x0 = x0 + w end assert(nbars > 0, "[InternalErr] no bars") local o = { _yline = yl, -- [, , ...] flat array _x_lim = x0, -- external x coordinate _nbars = nbars, -- number of bars } setmetatable(o, self) return o end -- return a Vbar interleaving narrow/Wide sequences -- tbar, tspace = {boolean sequence}, true -> narrow, false -> Wide function Vbar:from_two_tab(tbar, tspace, mod, MOD) --> assert(type(tbar) == "table", "tbar must be a table") assert(type(tspace) == "table", "tspace must be a table") assert(#tbar == #tspace, "tbar and tspace must be longer the same") assert(type(mod) == "number", "Invalid argument for narrow module width") assert(type(MOD) == "number", "Invalid argument for wide module width") assert(mod < MOD, "Not ordered narrow/Wide values") local nbars = 0 local x0 = 0.0 -- x-coordinate local yl = {} for i = 1, #tbar do local is_narrow = tbar[i] assert(type(is_narrow) == "boolean", "[InternalErr] found a not boolean value") if is_narrow then yl[#yl + 1] = x0 + mod/2 -- bar x-coordinate yl[#yl + 1] = mod -- bar width x0 = x0 + mod else yl[#yl + 1] = x0 + MOD/2 -- bar x-coordinate yl[#yl + 1] = MOD -- bar width x0 = x0 + MOD end nbars = nbars + 1 local is_narrow_space = tspace[i] assert(type(is_narrow_space) == "boolean", "[InternalErr] found a not boolean value") if is_narrow_space then x0 = x0 + mod else x0 = x0 + MOD end end assert(nbars > 0, "[InternalErr] no bars") local o = { _yline = yl, -- [, , ...] flat array _x_lim = x0, -- external x coordinate _nbars = nbars, -- number of bars } setmetatable(o, self) return o end function Vbar:get_bars() --> nbars, return self._nbars, self._yline end -- Polyline class local Polyline = {_classname = "Polyline"} Polyline.__index = Polyline libgeo.Polyline = Polyline -- optional argument a first point (x, y) function Polyline:new(x, y) --> local o = { _point = {}, _n = 0, } setmetatable(o, self) if x ~= nil then assert(type(x) == "number", "[Polyline:new()] Invalid type for x-coordinate") assert(type(y) == "number", "[Polyline:new()] Invalid type for y-coordinate") self:add_point(x, y) end return o end -- get a clone of points' coordinates function Polyline:get_points() local res = {} local p = self._point for i, c in ipairs(p) do res[i] = c end return self._n, res end -- append a new point with absolute coordinates function Polyline:add_point(x, y) assert(type(x) == "number", "[Polyline:add_point()] Invalid type for x-coordinate") assert(type(y) == "number", "[Polyline:add_point()] Invalid type for y-coordinate") local point = self._point point[#point + 1] = x point[#point + 1] = y self._n = self._n + 1 end -- append a new point with relative coordinates respect to the last one function Polyline:add_relpoint(x, y) assert(type(x) == "number", "Invalid type for x-coordinate") assert(type(y) == "number", "Invalid type for y-coordinate") local point = self._point local n = self._n assert(n > 0, "Attempt to add a relative point to an empty polyline") local i = 2 * n point[#point + 1] = point[i - 1] + x point[#point + 1] = point[i] + y self._n = n + 1 end -- Text class libgeo.Text = {_classname="Text"} local Text = libgeo.Text Text.__index = Text -- costructors -- internally it keeps text as a sequence of codepoint function Text:from_string(s) --> object assert(type(s) == "string", "[ArgErr] 's' not a valid string") assert(#s > 0, "[Err] 's' empty string not allowed") local cp = {} -- codepoint array for b in string.gmatch(s, ".") do cp[#cp + 1] = string.byte(b) end local o = { codepoint = cp, } setmetatable(o, self) return o end -- arr, array of single digit number -- i start index -- j stop index function Text:from_digit_array(arr, i, j) --> object assert(type(arr) == "table", "[ArgErr] 'arr' not a table") assert(#arr > 0, "[ArgErr] 'arr' is an empty array") local cp = {} -- codepoint array if i ~= nil then assert(type(i) == "number", "[ArgErr] 'i' is not a number") else i = 1 end if j ~= nil then assert(type(j) == "number", "[ArgErr] 'j' is not a number") assert(i <= j, "[ArgErr] not suitable pair of array index") else j = #arr end for k = i, j do local d = arr[k] assert(type(d) == "number", "[ArgErr] array contains a not number element") assert(d == math.floor(d), "[ArgErr] array contains a not integer number") assert(d >= 0 or d < 10, "[ArgErr] array contains a not single digit number") cp[#cp + 1] = d + 48 end local o = { codepoint = cp, } setmetatable(o, self) return o end -- from an array of chars function Text:from_chars(chars) --> object assert(type(chars) == "table", "[ArgErr] 'chars' must be a table") local arr = {} for _, c in ipairs(chars) do arr[#arr + 1] = string.byte(c) end local o = { codepoint = arr, } setmetatable(o, self) return o end -- provide an integer to build a Text object function Text:from_int(n) --> object assert(type(n) == "number", "[ArgErr] 'n' must be a number") assert( n > 0, "[Err] 'n' must be positive") assert( n == math.floor(n), "[Err] 'n' must be an integer") local cp = {} while n > 0 do local d = n % 10 cp[#cp + 1] = d + 48 n = (n - d)/10 end local digits = #cp for i = 1, digits/2 do -- reverse the array local d = cp[digits - i + 1] cp[digits - i + 1] = cp[i] cp[i] = d end local o = { codepoint = cp, } setmetatable(o, self) return o end return libgeo