if not modules then modules = { } end modules ['m-escrito'] = { version = 1.001, comment = "companion to m-escrito.mkiv", author = "Taco Hoekwater (BitText) and Hans Hagen (PRAGMA-ADE)", license = "see below and context related readme files" } -- This file is derived from Taco's escrito interpreter. Because the project was -- more or less stopped, after some chatting we decided to preserve the result -- and make it useable in ConTeXt. Hans went over all code, fixed a couple of -- things, messed other things, made the code more efficient, wrapped all in -- some helpers. So, a diff between the original and this file is depressingly -- large. This means that you shouldn't bother Taco with the side effects (better -- or worse) that result from this. -- Fonts need some work and I will do that when needed. I might cook up something -- similar to what we do with MetaFun. First I need to run into a use case. After -- all, this whole exercise is just that: getting an idea of what processing PS -- code involves. -- Here is the usual copyright blabla: -- -- Copyright 2010 Taco Hoekwater . All rights reserved. -- -- Redistribution and use in source and binary forms, with or without modification, -- are permitted provided that the following conditions are met: -- -- 1. Redistributions of source code must retain the above copyright notice, this -- list of conditions and the following disclaimer. -- -- 2. Redistributions in binary form must reproduce the above copyright notice, this -- list of conditions and the following disclaimer in the documentation and/or -- other materials provided with the distribution. -- -- THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY EXPRESS OR -- IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -- SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -- OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -- STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -- DAMAGE. -- We use a couple of do..ends later on because this rather large file has too many -- locals otherwise. Possible optimizations are using insert/remove and getting rid -- of the VM calls (in direct mode they are no-ops anyway). We can also share some -- more code here and there. -- Notes: -- -- -- all modules are checked / adapted to lmtx but how about this one ... i noticed -- that a file in the test suite failed -- -- -- the idea was to use this for the m4all eps files but we swichted the format -- there; nevertheless i patched a littl but it's still not ok (cold winter work) -- -- -- for instance some ppor mans fancy shading doesn't show up (not that efficient -- either so ...) -- -- -- let's see what the new fast ps->pdf lib from artifact brings ... makes more -- sense in the perspective of ps 2 and 3 .. but there is some sentiment involved -- -- -- room for implification (like no integer / real distinction needed) -- -- -- so for now this is not part of the mkiv/lmtx code split (then also go Lua 5.4) local type, unpack, tonumber, tostring, next = type, unpack, tonumber, tostring, next local format = string.format local gmatch = string.gmatch local match = string.match local sub = string.sub local char = string.char local byte = string.byte local insert = table.insert local remove = table.remove local concat = table.concat local reverse = table.reverse local abs = math.abs local ceil = math.ceil local floor = math.floor local sin = math.sin local cos = math.cos local rad = math.rad local sqrt = math.sqrt local atan2 = math.atan2 local tan = math.tan local deg = math.deg local pow = math.pow local log = math.log local log10 = math.log10 local random = math.random local setranseed = math.randomseed local bitand = bit32.band -- when lmtx: Lua 5.4 local bitor = bit32.bor local bitxor = bit32.bxor local bitrshift = bit32.rshift local bitlshift = bit32.lshift local lpegmatch = lpeg.match local Ct, Cc, Cs, Cp, C, R, S, P, V = lpeg.Ct, lpeg.Cc, lpeg.Cs, lpeg.Cp, lpeg.C, lpeg.R, lpeg.S, lpeg.P, lpeg.V local formatters = string.formatters local setmetatableindex = table.setmetatableindex -- Namespace -- HH: Here we assume just one session. If needed we can support more (just a matter -- of push/pop) but it makes the code more complex and less efficient too. escrito = { } ----- escrito = escrito local initializers = { } local devices = { } local specials local DEBUG = false -- these will become trackers if needed local INITDEBUG = false -- these will become trackers if needed local MAX_INT = 0x7FFFFFFF -- we could have slightly larger ints because lua internally uses doubles initializers[#initializers+1] = function(reset) if reset then specials = nil else specials = { } end end local devicename local device -- "boundingbox", -- "randomseed", -- Composite objects -- -- Arrays, dicts and strings are stored in VM. To do this, VM is an integer-indexed table. This appears -- a bit silly in lua because we are actually just emulating a C implementation detail (pointers) but it -- is documented behavior. There is also supposed to be a VM stack, but I will worry about that when it -- becomes time to implement save/restore. (TH) local VM -- todo: just a hash initializers[#initializers+1] = function() VM = { } end local directvm = false -- true (but then we ned to patch more VM[..] local add_VM, get_VM if directvm then -- if ok then we remove the functions add_VM = function(a) return a end get_VM = function(i) return i end else add_VM = function(a) local n = #VM + 1 VM[n] = a return n end get_VM = function(i) return VM[i] end end -- Execution stack local execstack local execstackptr local do_exec local next_object local stopped initializers[#initializers+1] = function() execstack = { } execstackptr = 0 stopped = false end local function pop_execstack() if execstackptr > 0 then local value = execstack[execstackptr] execstackptr = execstackptr - 1 return value else return nil -- stackunderflow end end local function push_execstack(v) execstackptr = execstackptr + 1 execstack[execstackptr] = v end -- Operand stack -- -- Most operand and exec stack entries are four-item arrays: -- -- [1] = "[integer|real|boolean|name|mark|null|save|font]" (a postscript interpreter type) -- [2] = "[unlimited|read-only|execute-only|noaccess]" -- [3] = "[executable|literal]" (exec attribute) -- [4] = value (a VM index inthe case of names) -- -- But there are some exceptions. -- -- Dictionaries save the access attribute inside the value -- -- [1] = "dict" -- [2] = irrelevant -- [3] = "[executable|literal]" -- [4] = value (a VM index) -- -- Operators have a fifth item: -- -- [1] = "operator" -- [2] = "[unlimited|read-only|execute-only|noaccess]" -- [3] = "[executable|literal]" -- [4] = value -- [5] = identifier (the operator name) -- -- Strings and files have a fifth and a sixth item, the fifth of which is -- only relevant if the exec attribute is 'executable': -- -- [1] = "[string|file]" -- [2] = "[unlimited|read-only|execute-only|noaccess]" -- [3] = "[executable|literal]" -- [4] = value (a VM index) (for input files, this holds the whole file) -- [5] = exec-index -- [6] = length -- [7] = iomode (for files only) -- [8] = filehandle (for files only) -- -- Arrays also have a seven items, the fifth is only relevant if -- the exec attribute is 'executable', and the seventh is used to differentiate -- between direct and indirect interpreter views of the object. -- -- [1] = "array" -- [2] = "[unlimited|read-only|execute-only|noaccess]" -- [3] = "[executable|literal]" -- [4] = value (a VM index) -- [5] = exec-index -- [6] = length (a VM index) -- [7] = "[d|i]" (direct vs. indirect) -- -- The exec stack also has an object with [1] == ".stopped", which is used -- for "stopped" execution contexts local opstack local opstackptr local b_true = { 'boolean', 'unlimited', 'literal', true } local b_false = { 'boolean', 'unlimited', 'literal', false } initializers[#initializers+1] = function() opstack = { } opstackptr = 0 end local function pop_opstack() if opstackptr > 0 then local value = opstack[opstackptr] opstackptr = opstackptr - 1 return value else return nil -- stackunderflow end end local function push_opstack(v) opstackptr = opstackptr + 1 opstack[opstackptr] = v end local function check_opstack(n) return opstackptr >= n end local function get_opstack() if opstackptr > 0 then return opstack[opstackptr] else return nil -- stackunderflow end end -- In case of error, the interpreter has to restore the opstack local function copy_opstack() local t = { } for n=1,opstackptr do local sn = opstack[n] t[n] = { unpack(sn) } end return t end local function set_opstack(new) opstackptr = #new opstack = new end -- Dict stack local dictstack local dictstackptr initializers[#initializers+1] = function() dictstack = { } dictstackptr = 0 end -- this finds a name in the current dictionary stack local function lookup(name) for n=dictstackptr,1,-1 do local found = get_VM(dictstack[n]) if found then local dict = found.dict if dict then local d = dict[name] if d then return d, n end end end end return nil end -- Graphics state stack -- device backends are easier if gsstate items use bare data instead of -- ps objects, much as possible -- todo: just use one color array local gsstate initializers[#initializers+1] = function(reset) if reset then gsstate = nil else gsstate = { matrix = { 1, 0, 0, 1, 0, 0 }, color = { gray = 0, hsb = { }, rgb = { }, cmyk = { }, type = "gray" }, position = { }, -- actual x and y undefined path = { }, clip = { }, font = nil, linewidth = 1, linecap = 0, linejoin = 0, screen = nil, -- by default, we don't use a screen, which matches "1 0 {pop}" transfer = nil, -- by default, we don't have a transfer function, which matches "{}" flatness = 0, miterlimit = 10, dashpattern = { }, dashoffset = 0, } end end local function copy_gsstate() local old = gsstate local position = old.position local matrix = old.matrix local color = old.color local rgb = color.rgb local cmyk = color.cmyk local hsb = color.hsb return { matrix = { matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], matrix[6] }, color = { type = color.type, gray = color.gray, hsb = { hsb[1], hsb[2], hsb[3] }, rgb = { rgb[1], rgb[2], rgb[3] }, cmyk = { cmyk[1], cmyk[2], cmyk[3], cmyk[4] }, }, position = { position[1], position[2] }, path = { unpack (old.path) }, clip = { unpack (old.clip) }, font = old.font, linewidth = old.linewidth, linecap = old.linecap, linejoin = old.linejoin, screen = old.screen, transfer = nil, flatness = old.flatness, miterlimit = old.miterlimit, dashpattern = { }, dashoffset = 0, } end -- gsstack entries are of the form -- [1] "[save|gsave]" -- [2] {gsstate} local gsstack local gsstackptr initializers[#initializers+1] = function(reset) if reset then gsstack = nil gsstackptr = nil else gsstack = { } gsstackptr = 0 end end local function push_gsstack(v) gsstackptr = gsstackptr + 1 gsstack[gsstackptr] = v end local function pop_gsstack() if gsstackptr > 0 then local v = gsstack[gsstackptr] gsstackptr = gsstackptr - 1 return v end end -- Currentpage local currentpage initializers[#initializers+1] = function(reset) if reset then currentpage = nil else currentpage = { } end end -- Errordict -- The standard errordict entry. The rest of these dictionaries will be filled -- in the new() function. local errordict local dicterror -- find an error handler local function lookup_error(name) local dict = get_VM(errordict).dict return dict and dict[name] end -- error handling and reporting local report = logs.reporter("escrito") local function ps_error(a) -- can have print hook return false, a end -- Most entries in systemdict are operators, and the operators each have their own -- implementation function. These functions are grouped by category cf. the summary -- in the Adobe PostScript reference manual, the creation of the systemdict entries -- is alphabetical. -- -- In the summary at the start of the operator sections, the first character means: -- -- "-" => todo -- "+" => done -- "*" => partial -- "^" => see elsewhere local operators = { } -- Operand stack manipulation operators -- -- +pop +exch +dup +copy +index +roll +clear +count +mark +cleartomark +counttomark function operators.pop() local a = pop_opstack() if not a then return ps_error('stackunderflow') end return true end function operators.exch() if opstackptr < 2 then return ps_error('stackunderflow') end local prv = opstackptr - 1 opstack[opstackptr], opstack[prv] = opstack[prv], opstack[opstackptr] return true end function operators.dup() if opstackptr < 1 then return ps_error('stackunderflow') end local nxt = opstackptr + 1 opstack[nxt] = opstack[opstackptr] opstackptr = nxt return true end function operators.copy() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if ta == 'integer' then local va = a[4] if va < 0 then return ps_error('typecheck') end local thestack = opstackptr if va > thestack then return ps_error('stackunderflow') end -- use for loop local n = thestack - va + 1 while n <= thestack do local b = opstack[n] local tb = b[1] if tb == 'array' or tb == 'string' or tb == 'dict' or tb == 'font' then b = { tb, b[2], b[3], add_VM(get_VM(b[4])), b[5], b[6], b[7] } end push_opstack(b) n = n + 1 end elseif ta == 'dict' then local b = a local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'dict' then return ps_error('typecheck') end local thedict = get_VM(b[4]) local tobecopied = get_VM(a[4]) if thedict.maxsize < tobecopied.size then return ps_error('rangecheck') end if thedict.size ~= 0 then return ps_error('typecheck') end local access = thedict.access if access == 'read-only' or access == 'noaccess' then return ps_error('invalidaccess') end local dict = { } for k, v in next, tobecopied.dict do dict[k] = v -- fixed, was thedict[a], must be thedict.dict end thedict.access = tobecopied.access thedict.size = tobecopied.size thedict.dict = dict b = { b[1], b[2], b[3], add_VM(thedict) } push_opstack(b) elseif ta == 'array' then local b = a local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'array' then return ps_error('typecheck') end if b[6] < a[6] then return ps_error('rangecheck') end local access = b[2] if access == 'read-only' or access == 'noaccess' then return ps_error('invalidaccess') end local array = { } local thearray = get_VM(b[4]) local tobecopied = get_VM(a[4]) for k, v in next, tobecopied do array[k] = v end b = { b[1], b[2], b[3], add_VM(array), a[5], a[6], a[7] } -- fixed, was thearray push_opstack(b) elseif ta == 'string' then local b = a local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'string' then return ps_error('typecheck') end if b[6] < a[6] then return ps_error('rangecheck') end local access = b[2] if access == 'read-only' or access == 'noaccess' then return ps_error('invalidaccess') end local thestring = get_VM(b[4]) local repl = get_VM(a[4]) VM[b[4]] = repl .. sub(thestring,#repl+1,-1) b = { b[1], b[2], b[3], add_VM(repl), a[5], b[6] } push_opstack(b) else return ps_error('typecheck') end return true end function operators.index() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if not ta == 'integer' then return ps_error('typecheck') end local n = a[4] if n < 0 then return ps_error('rangecheck') end if n >= opstackptr then return ps_error('stackunderflow') end push_opstack(opstack[opstackptr-n]) return true end function operators.roll() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if b[1] ~= 'integer' then return ps_error('typecheck') end if a[1] ~= 'integer' then return ps_error('typecheck') end local stackcount = a[4] if stackcount < 0 then return ps_error('rangecheck') end if stackcount > opstackptr then return ps_error('stackunderflow') end local rollcount = b[4] if rollcount == 0 then return true end if rollcount > 0 then -- can be simplified while rollcount > 0 do local oldtop = opstack[opstackptr] local n = 0 while n < stackcount do opstack[opstackptr-n] = opstack[opstackptr-n-1] n = n + 1 end opstack[opstackptr-(stackcount-1)] = oldtop rollcount = rollcount - 1 end else -- can be simplified while rollcount < 0 do local oldbot = opstack[opstackptr-stackcount+1] local n = stackcount - 1 while n > 0 do opstack[opstackptr-n] = opstack[opstackptr-n+1] n = n - 1 end opstack[opstackptr] = oldbot rollcount = rollcount + 1 end end return true end function operators.clear() opstack = { } -- or just keep it opstackptr = 0 return true end function operators.count() push_opstack { 'integer', 'unlimited', 'literal', opstackptr } return true end function operators.mark() push_opstack { 'mark', 'unlimited', 'literal', null } end operators.beginarray = operators.mark function operators.cleartomark() while opstackptr > 0 do local val = pop_opstack() if not val then return ps_error('unmatchedmark') end if val[1] == 'mark' then return true end end return ps_error('unmatchedmark') end function operators.counttomark() local v = 0 for n=opstackptr,1,-1 do if opstack[n][1] == 'mark' then push_opstack { 'integer', 'unlimited', 'literal', v } return true end v = v + 1 end return ps_error('unmatchedmark') end -- Arithmetic and math operators -- -- +add +div +idiv +mod +mul +sub +abs +neg +ceiling +floor +round +truncate +sqrt +atan +cos -- +sin +exp +ln +log +rand +srand +rrand function operators.add() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb = a[1], b[1] if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local c = a[4] + b[4] push_opstack { (ta == 'real' or tb == 'real' or c > MAX_INT) and "real" or "integer", 'unlimited', 'literal', c } return true end function operators.sub() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb = a[1], b[1] if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local c = a[4] - b[4] push_opstack { (ta == 'real' or tb == 'real' or c > MAX_INT) and "real" or "integer", 'unlimited', 'literal', c } return true end function operators.div() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb = a[1], b[1] if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local va, vb = a[4], b[4] if vb == 0 then return ps_error('undefinedresult') end push_opstack { 'real', 'unlimited', 'literal', va / vb } return true end function operators.idiv() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb = a[1], b[1] if tb ~= 'integer' then return ps_error('typecheck') end if ta ~= 'integer' then return ps_error('typecheck') end local va, vb = a[4], b[4] if vb == 0 then return ps_error('undefinedresult') end push_opstack { 'integer', 'unlimited', 'literal', floor(va / vb) } return true end function operators.mod() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb = a[1], b[1] if tb ~= 'integer' then return ps_error('typecheck') end if ta ~= 'integer' then return ps_error('typecheck') end local va, vb = a[4], b[4] if vb == 0 then return ps_error('undefinedresult') end local neg = false local v if va < 0 then v = -va neg = true else v = va end local c = v % abs(vb) if neg then c = -c end push_opstack { 'integer', 'unlimited', 'literal', c } return true end function operators.mul() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb = a[1], b[1] if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local c = a[4] * b[4] push_opstack { (ta == 'real' or tb == 'real' or abs(c) > MAX_INT) and 'real' or 'integer', 'unlimited', 'literal', c } return true end function operators.abs() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local v = a[4] local c = abs(v) push_opstack { (ta == 'real' or v == -(MAX_INT+1)) and 'real' or 'integer', -- hm, v or c 'unlimited', 'literal', c } return true end function operators.neg() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local v = a[4] push_opstack { (ta == 'real' or v == -(MAX_INT+1)) and 'real' or 'integer', 'unlimited', 'literal', -v } return true end function operators.ceiling() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local c = ceil(a[4]) push_opstack { ta, 'unlimited', 'literal', c } return true end function operators.floor() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local c = floor(a[4]) push_opstack { ta, 'unlimited', 'literal', c } return true end function operators.round() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local c = floor(a[4]+0.5) push_opstack { ta, 'unlimited', 'literal', c } return true end function operators.truncate() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local v = a[4] local c =v < 0 and -floor(-v) or floor(v) push_opstack { ta, 'unlimited', 'literal', c } return true end function operators.sqrt() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local v = a[4] if v < 0 then return ps_error('rangecheck') end local c = sqrt(v) push_opstack { 'real', 'unlimited', 'literal', c } return true end function operators.atan() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb = a[1], b[1] if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local va, vb = a[4], b[4] if va == 0 and vb == 0 then return ps_error('undefinedresult') end local c = deg(atan2(rad(va),rad(vb))) if c < 0 then c = c + 360 end push_opstack { 'real', 'unlimited', 'literal', c } return true end function operators.sin() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local c = sin(rad(a[4])) -- this is because double calculation introduces a small error if abs(c) < 1.0e-16 then c = 0 end push_opstack { 'real', 'unlimited', 'literal', c } return true end function operators.cos() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local c = cos(rad(a[4])) -- this is because double calculation introduces a small error if abs(c) < 1.0e-16 then c = 0 end push_opstack { 'real', 'unlimited', 'literal', c } return true end function operators.exp() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb = a[1], b[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end local va, vb = a[4], b[4] if va < 0 and floor(vb) ~= vb then return ps_error('undefinedresult') end local c = pow(va,vb) push_opstack { 'real', 'unlimited', 'literal', c } return true end function operators.ln() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local v = a[4] if v <= 0 then return ps_error('undefinedresult') end local c = log(v) push_opstack { 'real', 'unlimited', 'literal', c } return true end function operators.log() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local v = a[4] if v <= 0 then return ps_error('undefinedresult') end local c = log10(v) push_opstack { 'real', 'unlimited', 'literal', c } return true end escrito.randomseed = os.time() -- this interval is one off, but that'll do function operators.rand() local c = random(MAX_INT) - 1 push_opstack { 'integer', 'unlimited', 'literal', c } return true end function operators.srand() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if ta ~= 'integer' then return ps_error('typecheck') end escrito.randomseed = a[4] setranseed(escrito.randomseed) return true end function operators.rrand() push_opstack { 'integer', 'unlimited', 'literal', escrito.randomseed } return true end -- Array operators -- -- +array ^[ +] +length +get +put +getinterval +putinterval +aload +astore ^copy +forall function operators.array() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local t = a[1] local v = a[4] if t ~= 'integer' then return ps_error('typecheck') end if v < 0 then return ps_error('rangecheck') end local array = { } for i=1,v do array[n] = { 'null', 'unlimited', 'literal', true } -- todo: share this one end push_opstack { 'array', 'unlimited', 'literal', add_VM(array), 0, v, 'd'} end function operators.endarray() local n = opstackptr while n > 0 do if opstack[n][1] == 'mark' then break end n = n - 1 end if n == 0 then return ps_error('unmatchedmark') end local top = opstackptr local i = opstackptr - n local array = { } while i > 0 do array[i] = pop_opstack() i = i - 1 end pop_opstack() -- pop the mark push_opstack { 'array', 'unlimited', 'literal', add_VM(array), #array, #array, 'd' } end function operators.length() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local access = a[2] if access == "noaccess" or access == "executeonly" then return ps_error('invalidaccess') end local ta = a[1] local va = a[4] if ta == "dict" or ta == "font" then va = get_VM(va).size elseif ta == "array" or ta == "string" then va = get_VM(va) va = #va else return ps_error('typecheck') end push_opstack { 'integer', 'unlimited', 'literal', va } return true end function operators.get() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local access = a[2] if access == "noaccess" or access == "execute-only" then return ps_error('invalidaccess') end local ta = a[1] local va = a[4] if ta == "dict" then local dict = get_VM(va) local key = b local tb = b[1] local vb = b[4] if tb == "string" or tb == "name" then key = get_VM(vb) end local ddk = dict.dict[key] if ddk then push_opstack(ddk) else return ps_error('undefined') end elseif ta == "array" then local tb = b[1] local vb = b[4] if tb ~= 'integer' then return ps_error('typecheck') end if vb < 0 or vb >= a[6] then return ps_error('rangecheck') end local array = get_VM(va) local index = vb + 1 push_opstack(array[index]) elseif ta == "string" then local tb = b[1] local vb = b[4] if tb ~= 'integer' then return ps_error('typecheck') end if vb < 0 or vb >= a[6] then return ps_error('rangecheck') end local thestring = get_VM(va) local index = vb + 1 local c = sub(thestring,index,index) push_opstack { 'integer', 'unlimited', 'literal', byte(c) } else return ps_error('typecheck') end return true end function operators.put() local c = pop_opstack() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if ta == "dict" then local dict = get_VM(a[4]) if dict.access ~= 'unlimited' then return ps_error('invalidaccess') end local key = b local bt = b[1] if bt == "string" or bt == "name" then key = get_VM(b[4]) end local dd = dict.dict local ds = dict.size local ddk = dd[key] if not ddk and (ds == dict.maxsize) then return ps_error('dictfull') end if c[1] == 'array' then c[7] = 'i' end if not ddk then dict.size = ds + 1 end dd[key] = c elseif ta == "array" then if a[2] ~= 'unlimited' then return ps_error('invalidaccess') end if b[1] ~= 'integer' then return ps_error('typecheck') end local va, vb = a[4], b[4] if vb < 0 or vb >= a[6] then return ps_error('rangecheck') end local vm = VM[va] local vi = bv + 1 if vm[vi][1] == 'null' then a[5] = a[5] + 1 end vm[vi] = c elseif ta == "string" then if a[2] ~= 'unlimited' then return ps_error('invalidaccess') end if b[1] ~= 'integer' then return ps_error('typecheck') end if c[1] ~= 'integer' then return ps_error('typecheck') end local va, vb, vc = a[4], b[4], c[4] if vb < 0 or vb >= a[6] then return ps_error('rangecheck') end if vc < 0 or vc > 255 then return ps_error('rangecheck') end local thestring = get_VM(va) VM[va] = sub(thestring,1,vb) .. char(vc) .. sub(thestring,vb+2) else return ps_error('typecheck') end return true end function operators.getinterval() local c = pop_opstack() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb, tc = a[1], b[1], c[1] local aa, ab, ac = a[2], b[2], c[2] local va, vb, vc = a[4], b[4], c[4] if ta ~= "array" and ta ~= 'string' then return ps_error('typecheck') end if tb ~= 'integer' or tc ~= 'integer' then return ps_error('typecheck') end if aa == "execute-only" or aa == 'noaccess' then return ps_error('invalidaccess') end if vb < 0 or vc < 0 or vb + vc >= a[6] then return ps_error('rangecheck') end -- vb : start -- vc : number if ta == 'array' then local array = get_VM(va) local subarray = { } local index = 1 while index <= vc do subarray[index] = array[index+vb] index = index + 1 end push_opstack { 'array', aa, a[3], add_VM(subarray), vc, vc, 'd' } else local thestring = get_VM(va) local newstring = sub(thestring,vb+1,vb+vc) push_opstack { 'string', aa, a[3], add_VM(newstring), vc, vc } end return true end function operators.putinterval() local c = pop_opstack() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb, tc = a[1], b[1], c[1] local aa, ab, ac = a[2], b[2], c[2] local va, vb, vc = a[4], b[4], c[4] if ta ~= "array" and ta ~= 'string' then return ps_error('typecheck') end if tc ~= "array" and tc ~= 'string' then return ps_error('typecheck') end if ta ~= tc then return ps_error('typecheck') end if aa ~= "unlimited" then return ps_error('invalidaccess') end if tb ~= 'integer' then return ps_error('typecheck') end if vb < 0 or vb + c[6] >= a[6] then return ps_error('rangecheck') end if ta == 'array' then local newarr = get_VM(vc) local oldarr = get_VM(va) local index = 1 local lastindex = c[6] local step = a[5] while index <= lastindex do if oldarr[vb+index][1] == 'null' then a[5] = a[5] + 1 -- needs checking, a[5] not used -- step = step + 1 end oldarr[vb+index] = newarr[index] index = index + 1 end else local thestring = get_VM(va) VM[va] = sub(thestring,1,vb) .. get_VM(vc) .. sub(thestring,vb+c[6]+1) end return true end function operators.aload() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, aa, va = a[1], a[2], a[4] if ta ~= "array" then return ps_error('typecheck') end if aa == "execute-only" or aa == 'noaccess' then return ps_error('invalidaccess') end local array = get_VM(va) for i=1,#array do push_opstack(array[i]) end push_opstack(a) return true end function operators.astore() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, aa, va = a[1], a[2], a[4] if ta ~= "array" then return ps_error('typecheck') end if aa == "execute-only" or aa == 'noaccess' then return ps_error('invalidaccess') end local array = get_VM(va) local count = a[6] for i=1,count do local v = pop_opstack() if not v then return ps_error('stackunderflow') end array[i] = v end a[5] = a[5] + count push_opstack(a) return true end function operators.forall() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, aa, va = a[1], a[2], a[4] local tb, ab, vb = b[1], b[2], b[4] if not tb == "array" and b[3] == 'executable' then return ps_error('typecheck') end if tb == 'noaccess' then return ps_error('invalidaccess') end if not (ta == "array" or ta == 'dict' or ta == 'string' or ta == "font") then return ps_error('typecheck') end if aa == "execute-only" or aa == 'noaccess' then return ps_error('invalidaccess') end push_execstack { '.exit', 'unlimited', 'literal', false } local curstack = execstackptr if ta == 'array' then if a[6] == 0 then return true end b[7] = 'i' local thearray = get_VM(va) for i=1,#thearray do if stopped then stopped = false return false end push_opstack(thearray[i]) b[5] = 1 push_execstack(b) while curstack <= execstackptr do do_exec() end end local entry = execstack[execstackptr] if entry[1] == '.exit' and antry[4] == true then pop_execstack() return true end elseif ta == 'dict' or ta == 'font' then local thedict = get_VM(va) if thedict.size == 0 then return true end b[7] = 'i' local thedict = get_VM(va) for k, v in next, thedict.dict do if stopped then stopped = false return false end if type(k) == "string" then push_opstack { 'name', 'unlimited', 'literal', add_VM(k) } else push_opstack(k) end push_opstack(v) b[5] = 1 push_execstack(b) while curstack < execstackptr do do_exec() end local entry = execstack[execstackptr] if entry[1] == '.exit' and antry[4] == true then pop_execstack() return true end end else -- string if a[6] == 0 then return true end b[7] = 'i' local thestring = get_VM(va) for v in gmatch(thestring,".") do -- we can use string.bytes if stopped then stopped = false return false end push_opstack { 'integer', 'unlimited', 'literal', byte(v) } b[5] = 1 push_execstack(b) while curstack < execstackptr do do_exec() end local entry = execstack[execstackptr] if entry[1] == '.exit' and antry[4] == true then pop_execstack() return true; end end end return true end -- Dictionary operators -- -- +dict ^length +maxlength +begin +end +def +load +store ^get ^put +known +where ^copy -- ^forall ^errordict ^systemdict ^userdict +currentdict +countdictstack +dictstack function operators.dict() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if not a[1] == 'integer' then return ps_error('typecheck') end local s = a[4] if s < 0 then return ps_error('rangecheck') end if s == 0 then -- level 2 feature s = MAX_INT end push_opstack { 'dict', 'unlimited', 'literal', add_VM { access = 'unlimited', size = 0, maxsize = s, dict = { }, } } end function operators.maxlength() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, aa, va = a[1], a[2], a[4] if ta ~= 'dict' then return ps_error('typecheck') end if aa == 'execute-only' or aa == 'noaccess' then return ps_error('invalidaccess') end local thedict = get_VM(va) push_opstack { 'integer', 'unlimited', 'literal', thedict.maxsize } end function operators.begin() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'dict' then return ps_error('typecheck') end dictstackptr = dictstackptr + 1 dictstack[dictstackptr] = a[4] end operators["end"] = function() if dictstackptr < 3 then return ps_error('dictstackunderflow') end dictstack[dictstackptr] = nil dictstackptr = dictstackptr - 1 end function operators.def() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if not (a[1] == 'name' and a[3] == 'literal') then return ps_error('typecheck') end if b[1] == 'array' then b[7] = 'i' end local thedict = get_VM(dictstack[dictstackptr]) if not thedict.dict[get_VM(a[4])] then if thedict.size == thedict.maxsize then -- return ps_error('dictfull') -- level 1 only end thedict.size = thedict.size + 1 end thedict.dict[get_VM(a[4])] = b return true end -- unclear: the book says this operator can return typecheck function operators.load() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local aa = a[2] if aa == 'noaccess' or aa == 'execute-only' then return ps_error('invalidaccess') end local v = lookup(get_VM(a[4])) if not v then return ps_error('undefined') end push_opstack(v) end function operators.store() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if not (a[1] == 'name' and a[3] == 'literal') then return ps_error('typecheck') end if b[7] == 'array' then b[7] = 'i' end local val, dictloc = lookup(a[4]) if val then local thedict = get_VM(dictstack[dictloc]) if thedict.access == 'execute-only' or thedict.access == 'noaccess' then return ps_error('invalidaccess') end thedict.dict[a[4]] = b else local thedict = get_VM(dictstack[dictstackptr]) local access = thedict.access local size = thedict.size if access == 'execute-only' or access == 'noaccess' then return ps_error('invalidaccess') end if size == thedict.maxsize then return ps_error('dictfull') end thedict.size = size + 1 thedict.dict[a[4]] = b end return true end function operators.known() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, aa, va = a[1], a[2], a[4] local tb, vb = b[1], b[4] if ta ~= 'dict' then return ps_error('typecheck') end if not (tb == 'name' or tb == 'operator') then return ps_error('typecheck') end if aa == 'noaccess' or aa == 'execute-only' then return ps_error('invalidaccess') end local thedict = get_VM(va) push_opstack {'boolean', 'unlimited', 'literal', thedict.dict[vb] and true or false } return true end function operators.where() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if not (a[1] == 'name' and a[3] == 'literal') then return ps_error('typecheck') end local val, dictloc = lookup(get_VM(a[4])) local thedict = dictloc and get_VM(dictstack[dictloc]) -- fixed if val then if thedict.access == 'execute-only' or thedict.access == 'noaccess' then return ps_error('invalidaccess') end push_opstack {'dict', 'unlimited', 'literal', dictstack[dictloc]} push_opstack {'boolean', 'unlimited', 'literal', true} else push_opstack {'boolean', 'unlimited', 'literal', false} end return true end function operators.currentdict() push_opstack { 'dict', 'unlimited', 'literal', dictstack[dictstackptr] } return true end function operators.countdictstack() push_opstack { 'integer', 'unlimited', 'literal', dictstackptr } return true end function operators.dictstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if not a[1] == 'array' then return ps_error('typecheck') end if not a[2] == 'unlimited' then return ps_error('invalidaccess') end if a[6] < dictstackptr then return ps_error('rangecheck') end local thearray = get_VM(a[4]) local subarray = { } for i=1,dictstackptr do thearray[n] = { 'dict', 'unlimited', 'literal', dictstack[i] } subarray[n] = thearray[i] end a[5] = a[5] + dictstackptr push_opstack { 'array', 'unlimited', 'literal', add_VM(subarray), dictstackptr, dictstackptr, '' } return true end -- String operators -- -- +string ^length ^get ^put ^getinterval ^putinterval ^copy ^forall +anchorsearch +search -- +token function operators.string() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, va = a[1], a[4] if ta ~= 'integer' then return ps_error('typecheck') end if va < 0 then return ps_error('rangecheck') end push_opstack { 'string', 'unlimited', 'literal', add_VM(''), 1, va } end function operators.anchorsearch() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, aa, va = a[1], a[2], a[4] local tb, ab, vb = b[1], b[2], b[4] if not ta ~= 'string' then return ps_error('typecheck') end if tb ~= 'string' then return ps_error('typecheck') end if aa == 'noaccess' or aa == 'execute-only' then return ps_error('invalidaccess') end if ab == 'noaccess' or ab == 'execute-only' then return ps_error('invalidaccess') end local thestring = get_VM(va) local thesearch = get_VM(vb) local prefix = sub(thestring,1,#thesearch) if prefix == thesearch then if aa == 'read-only' then return ps_error('invalidaccess') end local post = sub(thestring,#thesearch+1) push_opstack { 'string', 'unlimited', 'literal', add_VM(post), 1, #post } push_opstack { 'string', 'unlimited', 'literal', add_VM(prefix), 1, #prefix } push_opstack (b_true) else push_opstack(a) push_opstack (b_false) end return true end function operators.search() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, aa, va = a[1], a[2], a[4] local tb, ab, vb = b[1], b[2], b[4] if not ta ~= 'string' then return ps_error('typecheck') end if tb ~= 'string' then return ps_error('typecheck') end if aa == 'noaccess' or aa == 'execute-only' then return ps_error('invalidaccess') end if ab == 'noaccess' or ab == 'execute-only' then return ps_error('invalidaccess') end local thestring = get_VM(a[4]) local thesearch = get_VM(b[4]) -- hm, can't this be done easier? local n = 1 local match while n + #thesearch-1 <= #thestring do match = sub(thestring,n,n+#thesearch-1) if match == thesearch then break end n = n + 1 end if match == thesearch then if aa == 'read-only' then return ps_error('invalidaccess') end local prefix = sub(thestring,1,n-1) local post = sub(thestring,#thesearch+n) push_opstack { 'string', 'unlimited', 'literal', add_VM(post), 1, #post } push_opstack { 'string', 'unlimited', 'literal', add_VM(thesearch), 1, #thesearch } push_opstack { 'string', 'unlimited', 'literal', add_VM(prefix), 1, #prefix } push_opstack (b_true) else push_opstack(a) push_opstack(b_false) end return true end function operators.token() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, aa, va = a[1], a[2], a[4] if not (ta == 'string' or ta == 'file') then return ps_error('typecheck') end if aa ~= 'unlimited' then return ps_error('invalidaccess') end -- some fiddling with the tokenization process is needed if ta == 'string' then local top = execstackptr push_execstack { '.token', 'unlimited', 'literal', false } push_execstack { a[1], a[2], 'executable', va, 1, a[6] } local v, err = next_object() if not v then pop_execstack() pop_execstack() push_opstack(b_false) else local q = pop_execstack() if execstack[execstackptr][1] == '.token' then pop_execstack() end local tq, vq = q[1], q[4] if tq == 'string' and vq ~= va then push_execstack(q) end local thestring, substring if vq ~= va then thestring = "" substring = "" else thestring = get_VM(vq) substring = sub(thestring,q[5] or 0) end push_opstack { ta, aa, a[3], add_VM(substring), 1, #substring} push_opstack(v) push_opstack(b_true) end else -- file if a[7] ~= 'r' then return ps_error('invalidaccess') end push_execstack { '.token', 'unlimited', 'literal', false } push_execstack { 'file', 'unlimited', 'executable', va, a[5], a[6], a[7], a[8] } local v, err = next_object() if not v then pop_execstack() pop_execstack() push_opstack(b_false) else local q = pop_execstack() -- the file a[5] = q[5] if execstack[execstackptr][1] == '.token' then pop_execstack() end push_opstack(v) push_opstack(b_true) end end return true end -- Relational, boolean and bitwise operators -- -- +eq +ne +ge +gt +le +lt +and +not +or +xor ^true ^false +bitshift local function both() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, aa = a[1], a[2] local tb, ab = b[1], b[2] if aa == 'noaccess' or aa == 'execute-only' then return ps_error('invalidaccess') end if ab == 'noaccess' or ab == 'execute-only' then return ps_error('invalidaccess') end if (ta == 'dict' and tb == 'dict') or (ta == 'array' and tb =='array') then return true, a[4], b[4] elseif ((ta == 'string' or ta == 'name') and (tb == 'string' or tb == 'name' )) then local astr = get_VM(a[4]) local bstr = get_VM(b[4]) return true, astr, bstr elseif ((ta == 'integer' or ta == 'real') and (tb == 'integer' or tb == 'real')) or (ta == tb) then return true, a[4], b[4] else return ps_error('typecheck') end return true end function operators.eq() local ok, a, b = both() if ok then push_opstack(a == b and b_true or b_false) return true else return a end end function operators.ne() local ok, a, b = both() if ok then push_opstack(a ~= b and b_true or b_false) return true else return a end end local function both() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local aa, ab = a[2], b[2] if aa == 'noaccess' or aa == 'execute-only' then return ps_error('invalidaccess') end if ab == 'noaccess' or ab == 'execute-only' then return ps_error('invalidaccess') end local ta, tb = a[1], b[1] local va, vb = a[4], b[4] if (ta == 'real' or ta == 'integer') and (tb == 'real' or tb == 'integer') then return true, va, vb elseif ta == 'string' and tb == 'string' then local va = get_VM(va) local vb = get_VM(vb) return true, va, vb else return ps_error('typecheck') end end function operators.ge() local ok, a, b = both() if ok then push_opstack(a >= b and b_true or b_false) return true else return a end end function operators.gt() local ok, a, b = both() if ok then push_opstack(a > b and b_true or b_false) return true else return a end end function operators.le() local ok, a, b = both() if ok then push_opstack(a <= b and b_true or b_false) return true else return a end end function operators.lt() local ok, a, b = both() if ok then push_opstack(a < b and b_true or b_false) return true else return a end end local function both() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local aa, ab = a[2], b[2] if aa == 'noaccess' or aa == 'execute-only' then return ps_error('invalidaccess') end if ab == 'noaccess' or ab == 'execute-only' then return ps_error('invalidaccess') end local ta, tb = a[1], b[1] local va, vb = a[4], b[4] if ta == 'boolean' and tb == 'boolean' then return ta, va, vb elseif ta == 'integer' and tb == 'integer' then return ta, va, vb else return ps_error('typecheck') end end operators["and"]= function() local ok, a, b = both() if ok == 'boolean' then push_opstack((a[1] and b[1]) and b_true or b_false) return true elseif ok == 'integer' then push_opstack { 'integer', 'unlimited', 'literal', bitand(a[1],b[1]) } return true else return a end end operators["or"] = function() local ok, a, b = both() if ok == 'boolean' then push_opstack((a[1] or b[1]) and b_true or b_false) return true elseif ok == 'integer' then push_opstack {'integer', 'unlimited', 'literal', bitor(a[1],b[1]) } return true else return a end end function operators.xor() local ok, a, b = both() if ok == 'boolean' then push_opstack ((a[1] ~= b[1]) and b_true or b_false) -- hm, unequal ? return true elseif ok == 'integer' then push_opstack {'integer', 'unlimited', 'literal', bitxor(a[1],b[1]) } return true else return a end end operators["not"] = function() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local aa = a[2] local ta = a[1] if aa == 'noaccess' or aa == 'execute-only' then return ps_error('invalidaccess') end if ta == 'boolean' then push_opstack ((not a[4]) and b_true or b_false) elseif ta == 'integer' then push_opstack { 'integer', 'unlimited', 'literal', -a[4] - 1 } else return ps_error('typecheck') end return true end function operators.bitshift() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local aa, ab = a[2], b[2] local ta, tb = a[1], b[1] local va, vb = a[4], b[4] if aa == 'noaccess' or aa == 'execute-only' then return ps_error('invalidaccess') end if ab == 'noaccess' or ab == 'execute-only' then return ps_error('invalidaccess') end if not (ta == 'integer' and tb == 'integer') then return ps_error('typecheck') end push_opstack { 'integer', 'unlimited', 'literal', bitrshift(va,vb < 0 and -vb or vb) } return true end -- Control operators -- -- +exec +if +ifelse +for +repeat +loop +exit +stop +stopped +countexecstack +execstack -- +quit +start function operators.exec() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] == 'array' then a[7] = 'i' a[5] = 1 end push_execstack(a) return true end operators["if"] = function() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'boolean' then return ps_error('typecheck') end if b[1] ~= 'array' then return ps_error('typecheck') end if a[4] == true then b[7] = 'i' b[5] = 1 push_execstack(b) end return true end function operators.ifelse() local c = pop_opstack() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'boolean' then return ps_error('typecheck') end if b[1] ~= 'array' then return ps_error('typecheck') end if c[1] ~= 'array' then return ps_error('typecheck') end if a[4] == true then b[5] = 1 b[7] = 'i' push_execstack(b) else c[5] = 1 c[7] = 'i' push_execstack(c) end return true end operators["for"] = function() local d = pop_opstack() local c = pop_opstack() local b = pop_opstack() local a = pop_opstack() local ta, tb, tc, td = a[1], b[1], c[1], d[1] if not a then return ps_error('stackunderflow') end if not (ta == 'integer' or ta == 'real') then return ps_error('typecheck') end if not (tb == 'integer' or tb == 'real') then return ps_error('typecheck') end if not (tc == 'integer' or tc == 'real') then return ps_error('typecheck') end if not (td == 'array' and d[3] == 'executable') then return ps_error('typecheck') end local initial = a[4] local increment = b[4] local limit = c[4] if initial == limit then return true end push_execstack { '.exit', 'unlimited', 'literal', false } local curstack = execstackptr local tokentype = (a[1] == 'real' or b[1] == 'real' or c[1] == 'real') and 'real' or 'integer' d[7] = 'i' local first, last if increment >= 0 then first, last = initial, limit else first, last = limit, limit end for control=first,last,increment do if stopped then stopped = false return false end push_opstack { tokentype, 'unlimited', 'literal', control } d[5] = 1 push_execstack(d) while curstack < execstackptr do do_exec() end local entry = execstack[execstackptr] if entry[1] == '.exit' and entry[4] == true then pop_execstack() return true; end end return true end operators["repeat"] = function() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'integer' then return ps_error('typecheck') end if a[4] < 0 then return ps_error('rangecheck') end if not (b[1] == 'array' and b[3] == 'executable') then return ps_error('typecheck') end local limit = a[4] if limit == 0 then return true end push_execstack { '.exit', 'unlimited', 'literal', false } local curstack = execstackptr b[7] = 'i' local control = 0 while control < limit do if stopped then stopped = false return false end b[5] = 1 push_execstack(b) while curstack < execstackptr do do_exec() end local entry = execstack[execstackptr] if entry[1] == '.exit' and entry[4] == true then pop_execstack() return true; end control = control + 1 end return true end function operators.loop() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if not (a[1] == 'array' and a[3] == 'executable') then return ps_error('typecheck') end push_execstack { '.exit', 'unlimited', 'literal', false } local curstack = execstackptr a[7] = 'i' while true do if stopped then stopped = false return false end a[5] = 1 push_execstack(a) while curstack < execstackptr do do_exec() end if execstackptr > 0 then local entry = execstack[execstackptr] if entry[1] == '.exit' and entry[4] == true then pop_execstack() return true end end end return true end function operators.exit() local v = pop_execstack() while v do local tv = val[1] if tv == '.exit' then push_execstack { '.exit', 'unlimited', 'literal', true } return true elseif tv == '.stopped' or tv == '.run' then push_execstack(v) return ps_error('invalidexit') end v = pop_execstack() end report("exit without context, quitting") push_execstack { 'operator', 'unlimited', 'executable', operators.quit, "quit" } return true end function operators.stop() local v = pop_execstack() while v do if val[1] == '.stopped' then stopped = true push_opstack { 'boolean', 'unlimited', 'executable', true } return true end v = pop_execstack() end report("stop without context, quitting") push_execstack { 'operator', 'unlimited', 'executable', operators.quit, "quit" } return true end function operators.stopped() local a = pop_opstack() if not a then return ps_error('stackunderflow') end -- push a special token on the exec stack (handled by next_object): push_execstack { '.stopped', 'unlimited', 'literal', false } a[3] = 'executable' if a[1] == 'array' then a[7] = 'i' a[5] = 1 end push_execstack(a) return true end function operators.countexecstack() push_opstack { 'integer', 'unlimited', 'literal', execstackptr } return true end function operators.execstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if not a[1] == 'array' then return ps_error('typecheck') end if not a[2] == 'unlimited' then return ps_error('invalidaccess') end if a[6] < execstackptr then return ps_error('rangecheck') end local thearray = get_VM(a[4]) local subarray = { } for n=1,execstackptr do -- thearray[n] = execstack[n] -- subarray[n] = thearray[n] local v = execstack[n] thearray[n] = v subarray[n] = v a[5] = a[5] + 1 end push_opstack { 'array', 'unlimited', 'literal', add_VM(subarray), execstackptr, execstackptr, "" } return true end -- clearing the execstack does the trick, -- todo: leave open files to be handled by the lua interpreter, for now function operators.quit() while execstackptr >= 0 do -- todo: for loop / slot 0? execstack[execstackptr] = nil execstackptr = execstackptr - 1 end return true end -- does nothing, for now function operators.start() return true end -- Type, attribute and conversion operators -- -- +type +cvlit +cvx +xcheck +executeonly +noaccess +readonly +rcheck +wcheck +cvi -- +cvn +cvr +cvrs +cvs function operators.type() local a = pop_opstack() if not a then return ps_error('stackunderflow') end push_opstack { "name", "unlimited", "executable", add_VM(a[1] .. "type") } return true end function operators.cvlit() -- no need to push/pop local a = get_opstack() if not a then return ps_error('stackunderflow') end a[3] = 'literal' return true end function operators.cvx() local a = get_opstack() if not a then return ps_error('stackunderflow') end a[3] = 'executable' return true end function operators.xcheck() local a = pop_opstack() if not a then return ps_error('stackunderflow') end push_opstack((a[3] == 'executable') and b_true or b_false) return true end function operators.executeonly() local a = pop_opstack() -- get no push if not a then return ps_error('stackunderflow') end local ta = a[1] if ta == 'string' or ta == 'file' or ta == 'array' then if a[2] == 'noaccess' then return ps_error('invalidaccess') end a[2] = 'execute-only' else return ps_error('typecheck') end push_opstack(a) return true end function operators.noaccess() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if ta == 'string' or ta == 'file' or ta == 'array' then if a[2] == 'noaccess' then return ps_error('invalidaccess') end a[2] = 'noaccess' elseif ta == "dict" then local thedict = get_VM(a[4]) if thedict.access == 'noaccess' then return ps_error('invalidaccess') end thedict.access = 'noaccess' else return ps_error('typecheck') end push_opstack(a) return true end function operators.readonly() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if ta == 'string' or ta == 'file' or ta == 'array' then local aa = a[2] if aa == 'noaccess' or aa == 'execute-only' then return ps_error('invalidaccess') end a[2] = 'read-only' elseif ta == 'dict' then local thedict = get_VM(a[4]) local access = thedict.access if access == 'noaccess' or access == 'execute-only' then return ps_error('invalidaccess') end thedict.access = 'read-only' else return ps_error('typecheck') end push_opstack(a) return true end function operators.rcheck() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] local aa if ta == 'string' or ta == 'file' or ta == 'array' then aa = a[2] elseif ta == 'dict' then aa = get_VM(a[4]).access else return ps_error('typecheck') end push_opstack((aa == 'unlimited' or aa == 'read-only') and p_true or p_false) return true end function operators.wcheck() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] local aa if ta == 'string' or ta == 'file' or ta == 'array' then aa = a[2] elseif ta == 'dict' then local thedict = get_VM(a[4]) aa = thedict.access else return ps_error('typecheck') end push_opstack((aa == 'unlimited') and p_true or p_false) return true end function operators.cvi() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if ta == 'string' then push_opstack(a) local ret, err = operators.token() if not ret then return ret, err end local b = pop_opstack() if b[4] == false then return ps_error('syntaxerror') end a = pop_opstack() pop_opstack() -- get rid of the postmatch string remains ta = a[1] end local aa = a[2] if not (aa == 'unlimited' or aa == 'read-only') then return ps_error('invalidaccess') end if ta == 'integer' then push_opstack(a) elseif ta == 'real' then local va = a[4] local c = va < 0 and -floor(-va) or floor(ava) if abs(c) > MAX_INT then return ps_error('rangecheck') end push_opstack { 'integer', 'unlimited', 'literal', c } else return ps_error('typecheck') end return true end function operators.cvn() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, aa = a[1], a[2] local ta = a[1] if ta ~= 'string' then return ps_error('typecheck') end if aa == 'execute-only' or aa == 'noaccess' then return ps_error('invalidaccess') end push_opstack { 'name', aa, a[3], add_VM(get_VM(a[4])) } return true end function operators.cvr() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if ta == 'string' then push_opstack(a) local ret, err = operators.token() if not ret then return ret, err end local b = pop_opstack() if b[4] == false then return ps_error('syntaxerror') end a = pop_opstack() pop_opstack() -- get rid of the postmatch string remains ta = a[1] end local aa = a[2] if not (aa == 'unlimited' or aa == 'read-only') then return ps_error('invalidaccess') end if ta == 'integer' then push_opstack { 'real', 'unlimited', 'literal', a[4] } elseif ta == 'real' then push_opstack(a) else return ps_error('typecheck') end return true end do local byte0 = byte('0') local byteA = byte('A') - 10 function operators.cvrs() local c = pop_opstack() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb, tc = a[1], b[1], c[1] if not (ta == 'integer' or ta == 'real') then return ps_error('typecheck') end if not tb == 'integer' then return ps_error('typecheck') end if not tc == 'string' then return ps_error('typecheck') end if not c[2] == 'unlimited' then return ps_error('invalidaccess') end local va, vb, vc = a[4], b[4], c[4] if (vb < 2 or vb > 36) then return ps_error('rangecheck') end if ta == 'real' then push_opstack(a) local ret, err = operators.cvi() if ret then return ret, err end a = pop_opstack() end -- todo: use an lpeg local decimal = va local str = { } local n = 0 while decimal > 0 do local digit = decimal % vb n = n + 1 str[n] = digit < 10 and char(digit+byte0) or char(digit+byteA) decimal = floor(decimal/vb) end if n > c[6] then return ps_error('rangecheck') end str = concat(reverse(str)) local thestring = get_VM(vc) VM[va] = str .. sub(thestring,n+1,-1) push_opstack { c[1], c[2], c[3], add_VM(repl), n, n } return true end end function operators.cvs() local b = pop_opstack() local a = pop_opstack() if not 4 then return ps_error('stackunderflow') end local ta, tb = a[1], b[1] local ab = b[2] if not tb == 'string' then return ps_error('typecheck') end if not ab == 'unlimited' then return ps_error('invalidaccess') end local va, vb = a[4], b[4] if ta == 'real' then if floor(va) == va then va = tostring(va) .. '.0' else va = tostring(va) end elseif ta == 'integer' then va = tostring(va) elseif ta == 'string' or ta == 'name' then va = get_VM(va) elseif ta == 'operator' then va = a[5] elseif ta == 'boolean' then va = tostring(va) else va = "--nostringval--" end local n = #va if n > b[6] then return ps_error('rangecheck') end local thestring = get_VM(vb) VM[vb] = va .. sub(thestring,n+1,-1) push_opstack { tb, ab, b[3], add_VM(va), n, n } return true end -- File operators -- -- +file +closefile +read +write +writestring +readhexstring +writehexstring +readline ^token -- +bytesavailable +flush +flushfile +resetfile +status +run +currentfile +print ^= ^stack -- +== ^pstack ^prompt +echo function operators.file() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if b[1] ~= 'string' then return ps_error('typecheck') end if a[1] ~= 'string' then return ps_error('typecheck') end local fmode = get_VM(b[4]) local fname = get_VM(a[4]) -- only accept (r), (w) and (a) if fmode ~= "r" and fmode ~= "w" and fmode ~= "a" then return ps_error('typecheck') end if fname == "%stdin" then -- can only read from stdin if fmode ~= "r" then return ps_error('invalidfileaccess') end push_opstack { 'file', 'unlimited', 'literal', 0, 0, 0, fmode, io.stdin } elseif fname == "%stdout" then -- can't read from stdout i.e. can only append, in fact, but lets ignore that if fmode == "r" then return ps_error('invalidfileaccess') end push_opstack { 'file', 'unlimited', 'literal', 0, 0, 0, fmode, io.stdout } elseif fname == "%stderr" then -- cant read from stderr i.e. can only append, in fact, but lets ignore that if fmode == "r" then return ps_error('invalidfileaccess') end push_opstack { 'file', 'unlimited', 'literal', 0, 0, 0, fmode, io.stderr } elseif fname == "%statementedit" or fname == "%lineedit"then return ps_error('invalidfileaccess') else -- so it is a normal file local myfile, error = io.open(fname,fmode) if not myfile then return ps_error('undefinedfilename') end if fmode == 'r' then l = myfile:read("*a") if not l then return ps_error('invalidfileaccess') end -- myfile:close() -- do not close here, easier later on push_opstack { 'file', 'unlimited', 'literal', add_VM(l), 1, #l, fmode, myfile} else push_opstack { 'file', 'unlimited', 'literal', 0, 0, 0, fmode, myfile} end end return true end function operators.read() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'file' then return ps_error('typecheck') end if a[7] ~= 'r' then return ps_error('invalidaccess') end local b local v = a[4] local f = a[8] if v > 0 then local thestr = get_VM(v) local n = a[5] if n < a[6] then byte = sub(thestr,n,n+1) -- a[5] = n + 1 end else -- %stdin b = f:read(1) end if b then push_opstack { 'integer', 'unlimited', 'literal', byte(b) } push_opstack (p_true) else f:close() push_opstack (p_false) end return true end function operators.write() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if b[1] ~= 'integer' then return ps_error('typecheck') end if a[1] ~= 'file' then return ps_error('typecheck') end if a[7] == 'r' then return ps_error('ioerror') end a[8]:write(char(b[4] % 256)) return true end function operators.writestring() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if b[1] ~= 'string' then return ps_error('typecheck') end if a[1] ~= 'file' then return ps_error('typecheck') end if a[7] == 'r' then return ps_error('ioerror') end a[8]:write(get_VM(b[4])) return true end function operators.writehexstring() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if b[1] ~= 'string' then return ps_error('typecheck') end if a[1] ~= 'file' then return ps_error('typecheck') end if a[7] == 'r' then return ps_error('ioerror') end local f = a[8] local s = get_VM(b[4]) for w in gmatch(s,".") do f:write(format("%x",byte(w))) -- we have a table for that somewhere end return true end do local function get_string_line(a) local str = get_VM(a[4]) local start = a[5] local theend = a[6] if start == theend then return nil end str = match(str,"[\n\r]*([^\n\r]*)",start) a[5] = a[5] + #str + 1 -- ? return str end local function get_hexstring_line (a,b) local thestring = get_VM(a[4]) local start, theend = a[5], a[6] if start == theend then return nil end local prefix, result, n = nil, { }, 0 local nmax = b[6] while start < theend do local b = sub(thestring,start,start) if not b then break end local hexbyte = tonumber(b,16) if not hexbyte then -- skip elseif prefix then n = n + 1 result[n] = char(prefix*16+hexbyte) if n == nmax then break else prefix = nil end else prefix = hexbyte end start = start + 1 end a[5] = start + 1 -- ? return concat(result) end function operators.readline() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'file' then return ps_error('typecheck') end if a[7] ~= 'r' then return ps_error('invalidaccess') end local va = a[4] if va > 0 then va = get_string_line(a) else va = a[8]:read('*l') end if not va then push_opstack { 'string', 'unlimited', 'literal', add_VM(''), 0, 0 } push_opstack (p_false) else local n = #va if n > b[6] then return ps_error('rangecheck') end local thestring = get_VM(b[4]) VM[b[4]] = va .. sub(thestring,#va+1, -1) push_opstack { 'string', 'unlimited', 'literal', add_VM(va), n, n } push_opstack (p_true) end return true end function operators.readhexstring() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if not (ta == 'string' or ta == 'file') then return ps_error('typecheck') end local thefile = a[8] local va = a[4] if va > 0 then va = get_hexstring_line (a,b) else local prefix, result, n = nil, { }, 0 -- todo: read #va bytes and lpeg while true do local b = thefile:read(1) if not b then break end local hexbyte = tonumber(b,16) local nmax = b[6] if not hexbyte then -- skip elseif prefix then n = n + 1 result[n] = char(prefix*16+hexbyte) if n == nmax then break else prefix = nil end else prefix = hexbyte end end va = concat(result) end local thestring = get_VM(b[4]) local n = #va VM[b[4]] = repl .. sub(thestring,n+1,-1) push_opstack { b[1], b[2], b[3], add_VM(va), n, n } push_opstack ((n == b[6]) and p_true or p_false) return true end end function operators.flush() io.flush() return true end function operators.bytesavailable() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'file' then return ps_error('typecheck') end if a[7] ~= 'r' then return ps_error('typecheck') end local waiting = (a[4] > 0) and (a[6] - a[5] + 1) or -1 push_opstack { "integer", "unlimited", "literal", waiting } return true end -- this does not really do anything useful function operators.resetfile() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'file' then return ps_error('typecheck') end return true end function operators.flushfile() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'file' then return ps_error('typecheck') end if a[4] > 0 then a[5] = a[6] else a[8]:flush() end return true end function operators.closefile() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'file' then return ps_error('typecheck') end if a[7] == 'r' then a[5] = a[6] else push_opstack(a) operators.flushfile() end a[8]:close() return true end function operators.status() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'file' then return ps_error('typecheck') end local state = io.type(a[8]) push_opstack { "boolean", 'unlimited', 'literal', not state or state == "closed file" } return true end function operators.run() push_opstack { "string", "unlimited", "literal", add_VM("r"), 1, 1 } local ret, err = operators.file() if not ret then return ret, err end ret, err = operators.cvx() if not ret then return ret, err end local a = pop_opstack() -- an executable file push_execstack { ".run", "unlimited", "literal", false } -- constant local curstack = execstackptr local thefile = a[8] push_execstack(a) while curstack < execstackptr do do_exec() end local state = io.type(thefile) if not state or state == "closed file" then -- okay else thefile:close() end if execstackptr > 0 then local entry = execstack[execstackptr] if entry[1] == '.run' and entry[4] == true then pop_execstack() end end return true end function operators.currentfile() local n = execstackptr while n >= 0 do local entry = execstack[n] if entry[1] == 'file' and entry[7] == 'r' then push_opstack(entry) return true end n = n - 1 end push_opstack { 'file', 'unlimited', 'executable', add_VM(''), 0, 0, 'r', stdin } return true end function operators.print() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'string' then return ps_error('typecheck') end report(get_VM(a[4])) end -- '=' is also defined as a procedure below; -- -- it is actually supposed to do this: "equaldict begin dup type exec end" -- where each of the entries in equaldict handles one type only, but this -- works just as well do local pattern = Cs( Cc("(") * ( P("\n") / "\\n" + P("\r") / "\\r" + P("(") / "\\(" + P(")") / "\\)" + P("\\") / "\\\\" + P("\b") / "\\b" + P("\t") / "\\t" + P("\f") / "\\f" + R("\000\032","\127\255") / tonumber / formatters["\\%03o"] + P(1) )^0 * Cc(")") ) -- print(lpegmatch(pattern,[[h(a\nn)s]])) local function do_operator_equal(a) local ta, va = a[1], a[4] if ta == 'real' then if floor(va) == va then return tostring(va .. '.0') else return tostring(va) end elseif ta == 'integer' then return tostring(va) elseif ta == 'string' then return lpegmatch(pattern,get_VM(va)) elseif ta == 'boolean' then return tostring(va) elseif ta == 'operator' then return '--' .. a[5] .. '--' elseif ta == 'name' then if a[3] == 'literal' then return '/' .. get_VM(va) else return get_VM(va) end elseif ta == 'array' then va = get_VM(va) local isexec = a[3] == 'executable' local result = { isexec and "{" or "[" } local n = 1 for i=1,#va do n = n + 1 result[n] = do_operator_equal(va[i]) end result[n+1] = isexec and "}" or "]" return concat(result," ") elseif ta == 'null' then return 'null' elseif ta == 'dict' then return '-dicttype-' elseif ta == 'save' then return '-savetype-' elseif ta == 'mark' then return '-marktype-' elseif ta == 'file' then return '-filetype-' elseif ta == 'font' then return '-fonttype-' end end function operators.equal() local a = pop_opstack() if not a then return ps_error('stackunderflow') end report(do_operator_equal(a)) return true end end local function commonstack(seperator) for n=1,opstackptr do push_opstack { 'string', 'unlimited', 'literal', add_VM(seperator), 1 ,1 } push_opstack(opstack[n]) push_execstack { 'operator','unlimited','executable', operators.print, 'print' } push_execstack { 'operator','unlimited','executable', operators.equal, '==' } end return true end function operators.pstack() return commonstack("\n") end function operators.stack() return commonstack(" ") end -- this does not really do anything useful function operators.echo() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'boolean' then return ps_error('typecheck') end return true end -- Virtual memory operators -- -- +save +restore +vmstatus -- to be checked: we do a one-level shallow copy now, not sure if that -- is good enough yet local savelevel = 0 initializers[#initializers+1] = function(reset) savelevel = 0 end function operators.save() local saved_VM = { } -- for k1, v1 in next, VM do for k1 = 1, #VM do local v1 = VM[k1] if type(v1) == "table" then local t1 = { } saved_VM[k1] = t1 -- for k2, v2 in next, v1 do for k2=1,#v1 do local v2 = v1[k2] if type(v2) == "table" then local t2 = { } t1[k2] = t2 -- for k3, v3 in next, v2 do for k3=1,#v2 do local v3 = v2[k3] t2[k3] = v3 end else t1[k2] = v2 end end else saved_VM[k1] = v1 end end push_gsstack { 'save', copy_gsstate() } savelevel = savelevel + 1 push_opstack { 'save', 'unlimited', 'executable', add_VM(saved_VM) } end function operators.save() local saved_VM = table.copy(VM) push_gsstack { 'save', copy_gsstate() } savelevel = savelevel + 1 push_opstack { 'save', 'unlimited', 'executable', add_VM(saved_VM) } end do local function validstack(stack,index,saved_VM) -- loop over pstack, execstack, and dictstack to make sure -- there are no entries with VM_id > #saved_VM for i=index,1,-1 do local v = stack[i] if type(v) == "table" then local tv = v[1] if tv == "save" or tv == "string" or tv == "array" or tv == "dict" or tv == "name" or tv == "file" then -- todo: check on %stdin/%stdout, but should be ok if v[4] > #saved_VM then return false end end end i = i - 1 end return true end function operators.restore() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'save' then return ps_error('typecheck') end if a[4] == 0 or savelevel == 0 then return ps_error('invalidrestore') end local saved_VM = get_VM(a[4]) if directvm then else if not validstack(execstack,execstackptr,saved_VM) then return ps_error('invalidrestore') end if not validstack(dictstack,dictstackptr,saved_VM) then return ps_error('invalidrestore') end if not validstack(opstack,opstackptr,saved_VM) then return ps_error('invalidrestore') end end while gsstackptr > 0 do local g = gsstack[gsstackptr] gsstackptr = gsstackptr - 1 if g[1] == "save" then gsstate = g[2] return end end a[4] = 0 -- invalidate save object savelevel = savelevel - 1 VM = saved_VM end end function operators.vmstatus() local n = 0 -- #VM * 100 push_opstack { 'integer', 'unlimited', 'literal', savelevel } push_opstack { 'integer', 'unlimited', 'literal', n } push_opstack { 'integer', 'unlimited', 'literal', n } return true end -- Miscellaneous operators -- -- +bind +null +usertime +version -- the reference manual says bind only ERRORS on typecheck local function bind() local a = pop_opstack() if not a then return true -- ps_error('stackunderflow') end if not a[1] == 'array' then return ps_error('typecheck') end local proc = get_VM(a[4]) for i=1,#proc do local v = proc[i] local t = v[1] if t == 'name' then if v[3] == 'executable' then local op = lookup(get_VM(v[4])) if op and op[1] == 'operator' then proc[i] = op end end elseif t == 'array' then if v[2] == 'unlimited' then push_opstack(v) bind() -- recurse pop_opstack() proc[i][2] = 'read-only' end end end push_opstack(a) end operators.bind = bind function operators.null() push_opstack { 'null', 'unlimited', 'literal' } return true end function operators.usertime() push_opstack { 'integer', 'unlimited', 'literal', floor(os.clock() * 1000) } return true end function operators.version() push_opstack { 'string', 'unlimited', 'literal', add_VM('23.0') } return true end -- Graphics state operators -- -- +gsave +grestore +grestoreall +initgraphics +setlinewidth +currentlinewidth +setlinecap +currentlinecap -- +setlinejoin +currentlinejoin +setmiterlimit +currentmiterlimit +setdash +currentdash +setflat +currentflat -- +setgray +currentgray +sethsbcolor +currenthsbcolor +setrgbcolor +setcmykcolor +currentrgbcolor +setscreen -- +currentscreen +settransfer +currenttransfer function operators.gsave() push_gsstack { 'gsave', copy_gsstate() } currentpage[#currentpage+1] = { type = 'gsave', } return true end function operators.grestore() if gsstackptr > 0 then local g = gsstack[gsstackptr] if g[1] == "gsave" then gsstackptr = gsstackptr - 1 gsstate = g[2] end end currentpage[#currentpage+1] = { type = 'grestore', } return true end function operators.grestoreall() -- needs checking for i=gsstackptr,1,-1 do local g = gsstack[i] if g[1] == "save" then gsstate = g[2] gsstackptr = i return true end end gsstackptr = 0 return true end function operators.initgraphics() local newstate = copy_gsstate() -- hm newstate.matrix = { 1, 0, 0, 1, 0, 0 } newstate.color = { gray = 0, hsb = { }, rgb = { }, cmyk = { }, type = "gray" } newstate.position = { } -- actual x and y undefined newstate.path = { } newstate.linewidth = 1 newstate.linecap = 0 newstate.linejoin = 0 newstate.miterlimit = 10 newstate.dashpattern = { } newstate.dashoffset = 0 gsstate = newstate device.initgraphics() operators.initclip() return true end function operators.setlinewidth() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local t = a[1] if not (t == 'integer' or t == 'real') then return ps_error('typecheck') end gsstate.linewidth = a[4] return true end function operators.currentlinewidth() local w = gsstate.linewidth push_opstack { (abs(w) > MAX_INT or floor(w) ~= w) and 'real' or 'integer', 'unlimited', 'literal', w, } return true end function operators.setlinecap() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'integer' then return ps_error('typecheck') end local c = a[4] if c > 2 or c < 0 then return ps_error('rangecheck') end gsstate.linecap = c return true end function operators.currentlinecap() push_opstack { 'integer', 'unlimited', 'literal', gsstate.linecap } return true end function operators.setlinejoin() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'integer' then return ps_error('typecheck') end local j = a[4] if j > 2 or j < 0 then return ps_error('rangecheck') end gsstate.linejoin = j return true end function operators.currentlinejoin() push_opstack { 'integer', 'unlimited', 'literal', gsstate.linejoin } return true end function operators.setmiterlimit() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local t = a[1] if not (t == 'integer' or t == 'real') then return ps_error('typecheck') end local m = a[4] if m < 1 then return ps_error('rangecheck') end gsstate.miterlimit = m return true end function operators.currentmiterlimit() local w = gsstate.miterlimit push_opstack { (abs(w) > MAX_INT or floor(w) ~= w) and 'real' or 'integer', 'unlimited', 'literal', w } return true end function operators.setdash() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb = a[1], b[1] if ta ~= 'array' then return ps_error('typecheck') end if not (tb == 'integer' or tb == 'real') then return ps_error('typecheck') end local pattern = { } local total = 0 local thearray = get_VM(a[4]) for i=1,#thearray do local a = thearray[i] local ta, va = a[1], a[4] if ta ~= "integer" then return ps_error('typecheck') end if va < 0 then return ps_error('limitcheck') end total = total + va pattern[#pattern+1] = va end if #pattern > 0 and total == 0 then return ps_error('limitcheck') end gsstate.dashpattern = pattern gsstate.dashoffset = b[4] return true end function operators.currentdash() local thearray = gsstate.dashpattern local pattern = { } for i=1,#thearray do pattern[i] = { 'integer', 'unlimited', 'literal', thearray[i] } end push_opstack { 'array', 'unlimited', 'literal', add_VM(pattern), #pattern, #pattern } local w = gsstate.dashoffset push_opstack { (abs(w) > MAX_INT or floor(w) ~= w) and 'real' or 'integer', 'unlimited', 'literal', w } return true end function operators.setflat() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, va = a[1], a[4] if not (ta == 'integer' or ta == 'real') then return ps_error('typecheck') end gsstate.flatness = va return true end function operators.currentflat() local w = gsstate.flatness push_opstack { (abs(w) > MAX_INT or floor(w) ~= w) and 'real' or 'integer', 'unlimited', 'literal', w } return true end -- Color conversion functions -- -- normally, level one colors are based on hsb, but for our backend it is better to -- stick with the original request when possible do local function rgb_to_gray (r, g, b) return 0.30 * r + 0.59 * g + 0.11 * b end local function cmyk_to_gray (c, m, y, k) return 0.30 * (1.0 - min(1.0,c+k)) + 0.59 * (1.0 - min(1.0,m+k)) + 0.11 * (1.0 - min(1.0,y+k)) end local function cmyk_to_rgb (c, m, y, k) return 1.0 - min(1.0,c+k), 1.0 - min(1.0,m+k), 1.0 - min(1.0,y+k) end local function rgb_to_hsv(r, g, b) local offset, maximum, other_1, other_2 if r >= g and r >= b then offset, maximum, other_1, other_2 = 0, r, g, b elseif g >= r and g >= b then offset, maximum, other_1, other_2 = 2, g, b, r else offset, maximum, other_1, other_2 = 4, b, r, g end if maximum == 0 then return 0, 0, 0 end local minimum = other_1 < other_2 and other_1 or other_2 if maximum == minimum then return 0, 0, maximum end local delta = maximum - minimum return (offset + (other_1-other_2)/delta)/6, delta/maximum, maximum end local function gray_to_hsv (col) return 0, 0, col end local function gray_to_rgb (col) return 1-col, 1-col, 1-col end local function gray_to_cmyk (col) return 0, 0, 0, col end local function hsv_to_rgb(h,s,v) local hi = floor(h * 6.0) % 6 local f = (h * 6) - floor(h * 6) local p = v * (1 - s) local q = v * (1 - f * s) local t = v * (1 - (1 - f) * s) if hi == 0 then return v, t, p elseif hi == 1 then return q, v, p elseif hi == 2 then return p, v, t elseif hi == 3 then return p, q, v elseif hi == 4 then return t, p, v elseif hi == 5 then return v, p, q end end local function hsv_to_gray(h,s,v) return rgb_to_gray(hsv_to_rgb(h,s,v)) end -- color operators function operators.setgray() local g = pop_opstack() if not g then return ps_error('stackunderflow') end local gt = g[1] if not (gt == 'integer' or gt == 'real') then return ps_error('typecheck') end local gv = g[4] local color = gsstate.color color.type = "gray" color.gray = (gv < 0 and 0) or (gv > 1 and 1) or gv return true end function operators.currentgray() local color = gsstate.color local t = color.type local s if t == "gray" then s = color.gray elseif t == "rgb" then local col = color.rgb s = rgb_to_gray(col[1],col[2],col[3]) elseif t == "cmyk" then local col = cmyk s = cmyk_to_gray(col[1],col[2],col[3],col[4]) else local col = color.hsb s = hsv_to_gray(col[1],col[2],col[3]) end push_opstack { (s == 0 or s == 1) and 'integer' or 'real', 'unlimited', 'literal', s } return true end function operators.sethsbcolor() local b = pop_opstack() local s = pop_opstack() local h = pop_opstack() if not h then return ps_error('stackunderflow') end local ht, st, bt = h[1], s[1], b[1] if not (ht == 'integer' or ht == 'real') then return ps_error('typecheck') end if not (st == 'integer' or st == 'real') then return ps_error('typecheck') end if not (bt == 'integer' or bt == 'real') then return ps_error('typecheck') end local hv, sv, bv = h[4], s[4], b[4] local color = gsstate.color color.type = "hsb" color.hsb = { (hv < 0 and 0) or (hv > 1 and 1) or hv, (sv < 0 and 0) or (sv > 1 and 1) or sv, (bv < 0 and 0) or (bv > 1 and 1) or bv, } return true end function operators.currenthsbcolor() local color = gsstate.color local t = color.type local h, s, b if t == "gray" then h, s, b = gray_to_hsv(color.gray) elseif t == "rgb" then local col = color.rgb h, s, b = rgb_to_hsv(col[1],col[2],col[3]) elseif t == "cmyk" then local col = color.cmyk h, s, b = cmyk_to_hsv(col[1],col[2],col[3],col[4]) else local col = color.hsb h, s, b = col[1], col[2], col[3] end push_opstack { (h == 0 or h == 1) and 'integer' or 'real', 'unlimited', 'literal', h } push_opstack { (s == 0 or s == 1) and 'integer' or 'real', 'unlimited', 'literal', s } push_opstack { (b == 0 or b == 1) and 'integer' or 'real', 'unlimited', 'literal', b } return true end function operators.setrgbcolor() local b = pop_opstack() local g = pop_opstack() local r = pop_opstack() if not r then return ps_error('stackunderflow') end local rt, gt, bt = r[1], g[1], b[1] if not (rt == 'integer' or rt == 'real') then return ps_error('typecheck') end if not (gt == 'integer' or gt == 'real') then return ps_error('typecheck') end if not (bt == 'integer' or bt == 'real') then return ps_error('typecheck') end local rv, gv, bv = r[4], g[4], b[4] local color = gsstate.color color.type = "rgb" color.rgb = { (rv < 0 and 0) or (rv > 1 and 1) or rv, (gv < 0 and 0) or (gv > 1 and 1) or gv, (bv < 0 and 0) or (bv > 1 and 1) or bv, } return true end function operators.currentrgbcolor() local color = gsstate.color local t = color.type local r, g, b if t == "gray" then r, g, b = gray_to_rgb(color.gray) elseif t == "rgb" then local col = color.rgb r, g, b = col[1], col[2], col[3] elseif t == "cmyk" then r, g, b = cmyk_to_rgb(color.cmyk) else local col = color.hsb r, g, b = hsv_to_rgb(col[1], col[2], col[3]) end push_opstack { (r == 0 or r == 1) and "integer" or "real", 'unlimited', 'literal', r } push_opstack { (g == 0 or g == 1) and "integer" or "real", 'unlimited', 'literal', g } push_opstack { (b == 0 or b == 1) and "integer" or "real", 'unlimited', 'literal', b } return true end function operators.setcmykcolor() local k = pop_opstack() local y = pop_opstack() local m = pop_opstack() local c = pop_opstack() if not c then return ps_error('stackunderflow') end local ct, mt, yt, kt = c[1], m[1], y[1], k[1] if not (ct == 'integer' or ct == 'real') then return ps_error('typecheck') end if not (mt == 'integer' or mt == 'real') then return ps_error('typecheck') end if not (yt == 'integer' or yt == 'real') then return ps_error('typecheck') end if not (kt == 'integer' or kt == 'real') then return ps_error('typecheck') end local cv, mv, yv, kv = c[4], m[4], y[4], k[4] local color = gsstate.color color.type = "cmyk" color.cmyk = { (cv < 0 and 0) or (cv > 1 and 1) or cv, (mv < 0 and 0) or (mv > 1 and 1) or mv, (yv < 0 and 0) or (yv > 1 and 1) or yv, (kv < 0 and 0) or (kv > 1 and 1) or kv, } return true end function operators.currentcmykcolor() local color = gsstate.color local t = color.type local c, m, y, k if t == "gray" then c, m, y, k = gray_to_cmyk(color.gray) elseif t == "rgb" then c, m, y, k = rgb_to_cmyk(color.rgb) elseif t == "cmyk" then local col = color.cmyk c, m, y, k = col[1], col[2], col[3], col[4] else local col = color.hsb c, m, y, k = hsv_to_cmyk(col[1], col[2], col[3]) end push_opstack { (c == 0 or c == 1) and "integer" or "real", 'unlimited', 'literal', c } push_opstack { (m == 0 or m == 1) and "integer" or "real", 'unlimited', 'literal', m } push_opstack { (y == 0 or y == 1) and "integer" or "real", 'unlimited', 'literal', y } push_opstack { (k == 0 or k == 1) and "integer" or "real", 'unlimited', 'literal', k } return true end end function operators.setscreen() local c = pop_opstack() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb, tc, ac = a[1], b[1], c[1], c[3] if not (tc == 'array' and ac == 'executable') then return ps_error('typecheck') end if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end local va, vb, vc = a[4], b[4], c[4] if vb < 0 or vb > 360 then return ps_error('rangecheck') end if va < 0 then return ps_error('rangecheck') end gsstate.screen = { va, vb, vc } return true end function operators.currentscreen() local w if not gsstate.screen then local popper = { 'operator', 'unlimited', 'executable', operators.pop, 'pop' } push_opstack { 'integer', 'unlimited', 'literal', 1 } push_opstack { 'integer', 'unlimited', 'literal', 0 } push_opstack { 'array', 'unlimited', 'executable', add_VM{ popper }, 1, 1, 'd' } else local w1 = gsstate.screen[1] local w2 = gsstate.screen[2] local w3 = gsstate.screen[3] push_opstack { (abs(w) > MAX_INT or floor(w1) ~= w1) and 'real' or 'integer', 'unlimited', 'literal', w1 } push_opstack { (abs(w) > MAX_INT or floor(w2) ~= w2) and 'real' or 'integer', 'unlimited', 'literal', w2 } local thearray = get_VM(w3) push_opstack { 'array', 'unlimited', 'executable', w3, 1, #thearray, 'd' } -- w3 or thearray ? end return true end function operators.settransfer() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if not (a[1] == 'array' and a[3] == 'executable') then return ps_error('typecheck') end local va = a[4] if va < 0 then return ps_error('rangecheck') end gsstate.transfer = va return true end function operators.currenttransfer() local transfer = gsstate.transfer if not transfer then push_opstack { 'array', 'unlimited', 'executable', add_VM{ }, 0, 0, 'd'} else local thearray = get_VM(transfer) push_opstack { 'array', 'unlimited', 'executable', transfer, 1, #thearray, 'd' } end return true end -- Coordinate system and matrix operators -- -- +matrix +initmatrix +identmatrix +defaultmatrix +currentmatrix +setmatrix +translate -- +scale +rotate +concat +concatmatrix +transform +dtransform +itransform +idtransform -- +invertmatrix -- are these changed in place or not? if not then we can share function operators.matrix() local matrix = { {'real', 'unlimited', 'literal', 1}, {'real', 'unlimited', 'literal', 0}, {'real', 'unlimited', 'literal', 0}, {'real', 'unlimited', 'literal', 1}, {'real', 'unlimited', 'literal', 0}, {'real', 'unlimited', 'literal', 0}, } push_opstack { 'array', 'unlimited', 'literal', add_VM(matrix), 6, 6 } return true end function operators.initmatrix() gsstate.matrix = { 1, 0, 0, 1, 0, 0 } return true end function operators.identmatrix() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'array' then return ps_error('typecheck') end if a[6] < 6 then return ps_error('rangecheck') end local m = VM[a[4]] -- or can we replace the numbers m[1] = { 'real', 'unlimited', 'literal', 1 } m[2] = { 'real', 'unlimited', 'literal', 0 } m[3] = { 'real', 'unlimited', 'literal', 0 } m[4] = { 'real', 'unlimited', 'literal', 1 } m[5] = { 'real', 'unlimited', 'literal', 0 } m[6] = { 'real', 'unlimited', 'literal', 0 } a[5] = 6 push_opstack(a) return true end operators.defaultmatrix = operators.identmatrix function operators.currentmatrix() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'array' then return ps_error('typecheck') end if a[6] < 6 then return ps_error('rangecheck') end local thearray = get_VM(a[4]) local matrix = gsstate.matrix for i=1,6 do thearray[i] = {'real', 'unlimited', 'literal', matrix[i]} end push_opstack { 'array', 'unlimited', 'literal', a[4], 6, 6 } return true end function operators.setmatrix() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'array' then return ps_error('typecheck') end if a[6] ~= 6 then return ps_error('rangecheck') end local thearray = get_VM(a[4]) local matrix = gsstate.matrix for i=1,#thearray do local a = thearray[i] local ta, tv = a[1], a[4] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end if i > 6 then return ps_error('rangecheck') end matrix[i] = tv end return true end local function do_transform(matrix,a,b) local x = matrix[1] * a + matrix[3] * b + matrix[5] local y = matrix[2] * a + matrix[4] * b + matrix[6] return x, y end local function do_itransform(matrix,a,b) local m1 = matrix[1] local m4 = matrix[4] if m1 == 0 or m4 == 0 then return nil end local x = (a - matrix[5] - matrix[3] * b) / m1 local y = (b - matrix[6] - matrix[2] * a) / m4 return x, y end local function do_concat (a,b) local a1, a2, a3, a4, a5, a6 = a[1], a[2], a[3], a[4], a[5], a[6] local b1, b2, b3, b4, b5, b6 = b[1], b[2], b[3], b[4], b[5], b[6] local c1 = a1 * b1 + a2 * b3 local c2 = a1 * b2 + a2 * b4 local c3 = a1 * b3 + a3 * b4 local c4 = a3 * b2 + a4 * b4 local c5 = a5 * b1 + a6 * b3 + b5 local c6 = a5 * b2 + a6 * b4 + b6 -- this is because double calculation introduces a small error return { abs(c1) < 1.0e-16 and 0 or c1, abs(c2) < 1.0e-16 and 0 or c2, abs(c3) < 1.0e-16 and 0 or c3, abs(c4) < 1.0e-16 and 0 or c4, abs(c5) < 1.0e-16 and 0 or c5, abs(c6) < 1.0e-16 and 0 or c6, } end local function do_inverse (a) local a1, a2, a3, a4, a5, a6 = a[1], a[2], a[3], a[4], a[5], a[6] local det = a1 * a4 - a3 * a2 if det == 0 then return nil end local c1 = a4 / det local c3 = -a3 / det local c2 = -a2 / det local c4 = a1 / det local c5 = (a3 * a6 - a5 * a4) / det local c6 = (a5 * a2 - a1 * a6) / det return { abs(c1) < 1.0e-16 and 0 or c1, abs(c2) < 1.0e-16 and 0 or c2, abs(c3) < 1.0e-16 and 0 or c3, abs(c4) < 1.0e-16 and 0 or c4, abs(c5) < 1.0e-16 and 0 or c5, abs(c6) < 1.0e-16 and 0 or c6, } end function operators.translate() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] == 'array' then if a[6] ~= 6 then return ps_error('typecheck') end local tf = a local a = pop_opstack() local b = pop_opstack() if not b then return ps_error('stackunderflow') end local ta, tb = a[1], b[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end local m = VM[tf[4]] local old = { m[1][4], m[2][4], m[3][4], m[4][4], m[5][4], m[6][4] } local c = do_concat(old,{1,0,0,1,b[4],a[4]}) for i=1,6 do m[i] = { 'real', 'unlimited', 'literal', c[i] } end tf[5] = 6 push_opstack(tf) else local b = pop_opstack() local ta = a[1] local tb = b[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end gsstate.matrix = do_concat(gsstate.matrix,{1,0,0,1,b[4],a[4]}) end return true end function operators.scale() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if ta == 'array' then local tf = a if a[6] ~= 6 then return ps_error('typecheck') end local a = pop_opstack() local b = pop_opstack() if not b then return ps_error('stackunderflow') end local ta, tb = a[1], b[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end local v = VM[tf[4]] local c = do_concat ( { v[1][4], v[2][4], v[3][4], v[4][4], v[5][4], v[6][4] }, { b[4], 0, 0, a[4], 0, 0 } ) for i=1,6 do v[i] = { 'real', 'unlimited', 'literal', c[i] } end tf[5] = 6 push_opstack(tf) else local b = pop_opstack() if not b then return ps_error('stackunderflow') end local ta, tb = a[1], b[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end gsstate.matrix = do_concat(gsstate.matrix, { b[4], 0, 0, a[4], 0, 0 }) end return true end function operators.concat() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= "array" then return ps_error('typecheck') end if a[6] ~= 6 then return ps_error('typecheck') end local thearray = get_VM(a[4]) local l = { } for i=1,#thearray do local v = thearray[i] local t = v[1] if not (t == 'real' or t == 'integer') then return ps_error('typecheck') end l[i] = v[4] end gsstate.matrix = do_concat(gsstate.matrix,l) return true end function operators.concatmatrix() local tf = pop_opstack() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if tf[1] ~= "array" then return ps_error('typecheck') end if b [1] ~= "array" then return ps_error('typecheck') end if a [1] ~= "array" then return ps_error('typecheck') end if tf[6] ~= 6 then return ps_error('typecheck') end if b [6] ~= 6 then return ps_error('typecheck') end if a [6] ~= 6 then return ps_error('typecheck') end local al = { } local thearray = get_VM(a[4]) for i=1,#thearray do local v = thearray[i] local tv = v[1] if not (tv == 'real' or tv == 'integer') then return ps_error('typecheck') end al[i] = v[4] end local bl = { } local thearray = get_VM(b[4]) for i=1,#thearray do local v = thearray[i] local tv = v[1] if not (tv == 'real' or tv == 'integer') then return ps_error('typecheck') end bl[i] = v[4] end local c = do_concat(al, bl) local m = VM[tf[4]] for i=1,6 do m[i] = { 'real', 'unlimited', 'literal', c[i] } end tf[5] = 6 push_opstack(tf) return true end function operators.rotate() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta = a[1] if ta == 'array' then local tf if a[6] ~= 6 then return ps_error('typecheck') end tf = a a = pop_opstack() if not a then return ps_error('stackunderflow') end if not (a[1] == 'real' or a[1] == 'integer') then return ps_error('typecheck') end local m = VM[tf[4]] local old = { m[1][4], m[2][4], m[3][4], m[4][4], m[5][4], m[6][4] } local av = a[4] local c = do_concat (old, {cos(rad(av)),sin(rad(av)),-sin(rad(av)),cos(rad(av)), 0, 0}) for i=1,6 do m[i] = { 'real', 'unlimited', 'literal', c[i] } end push_opstack(tf) elseif ta == 'real' or ta == 'integer' then local av = a[4] gsstate.matrix = do_concat(gsstate.matrix,{cos(rad(av)),sin(rad(av)),-sin(rad(av)),cos(rad(av)),0,0}) else return ps_error('typecheck') end return true end function operators.transform() local a = pop_opstack() local b = pop_opstack() if not b then ps_error('stackunderflow') end local tf if a[1] == 'array' then if a[6] ~= 6 then return ps_error('typecheck') end local thearray = get_VM(a[4]) tf = { } for i=1,#thearray do local v = thearray[i] local v1 = v[1] if not (v1 == 'real' or v1 == 'integer') then return ps_error('typecheck') end tf[i] = v[4] end a = pop_opstack() if not a then return ps_error('stackunderflow') end else tf = gsstate.matrix end local a1 = a[1] local b1 = b[1] if not (a1 == 'real' or a1 == 'integer') then return ps_error('typecheck') end if not (b1 == 'real' or b1 == 'integer') then return ps_error('typecheck') end local x, y = do_transform(tf,b[4],a[4]); push_opstack { 'real', 'unlimited', 'literal', x } push_opstack { 'real', 'unlimited', 'literal', y } return true end local function commontransform() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local tf if a[1] == 'array' then if a[6] ~= 6 then return ps_error('typecheck') end tf = { } local thearray = get_VM(a[4]) for i=1,#thearray do local v = thearray[i] local tv = v[1] if not (tv == 'real' or tv == 'integer') then return ps_error('typecheck') end tf[i] = v[4] end a = pop_opstack() if not a then return ps_error('stackunderflow') end else tf = gsstate.matrix end local b = pop_opstack() if not b then return ps_error('stackunderflow') end local ta = a[1] local tb = b[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end return true, tf, a, b end function operators.dtransform() local ok, tf, a, b = commontransform() if ok then local x, y = do_transform({tf[1],tf[2],tf[3],tf[4],0,0},b[4],a[4]) if not x then return ps_error('undefinedresult') end push_opstack { 'real', 'unlimited', 'literal', x } push_opstack { 'real', 'unlimited', 'literal', y } return true else return false, tf end end function operators.itransform() local ok, tf, a, b = commontransform() if ok then local x, y = do_itransform(tf,b[4],a[4]) if not x then return ps_error('undefinedresult') end push_opstack { 'real', 'unlimited', 'literal', x } push_opstack { 'real', 'unlimited', 'literal', y } return true else return false, tf end end function operators.idtransform() local ok, tf, a, b = commontransform() if ok then local x,y = do_itransform({tf[1],tf[2],tf[3],tf[4],0,0},b[4],a[4]); if not x then return ps_error('undefinedresult') end push_opstack { 'real', 'unlimited', 'literal', x } push_opstack { 'real', 'unlimited', 'literal', y } return true else return false, tf end end function operators.invertmatrix() local tf = pop_opstack() if not tf then return ps_error('stackunderflow') end if tf[1] ~= "array" then return ps_error('typecheck') end if tf[6] ~= 6 then return ps_error('typecheck') end local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= "array" then return ps_error('typecheck') end if a[6] ~= 6 then return ps_error('typecheck') end local al = { } local thearray = get_VM(a[4]) for i=1,#thearray do local v = thearray[i] local tv = v[1] if not (tv == 'real' or tv == 'integer') then return ps_error('typecheck') end al[i] = v[4] end local c = do_inverse(al) if not c then return ps_error('undefinedresult') end local m = VM[tf[4]] for i=1,6 do m[i] = { 'real', 'unlimited', 'literal', c[i] } end tf[5] = 6 push_opstack(tf) return true end -- Path construction operators -- -- +newpath +currentpoint +moveto +rmoveto +lineto +rlineto +arc +arcn +arcto +curveto +rcurveto -- +closepath +flattenpath -reversepath -strokepath -charpath +clippath -pathbbox -pathforall -- +initclip *clip *eoclip function operators.newpath() gsstate.path = { } gsstate.position = { } return true end function operators.currentpoint() local position = gsstate.position if #position == 0 then return ps_error('nocurrentpoint') end local x, y = do_itransform(gsstate.matrix, position[1], position[2]) if not x then return ps_error('undefinedresult') end push_opstack { 'real', 'unlimited', 'literal', x } push_opstack { 'real', 'unlimited', 'literal', y } end function operators.moveto() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local b1 = b[1] local a1 = a[1] if not (b1 == 'real' or b1 == 'integer') then return ps_error('typecheck') end if not (a1 == 'real' or a1 == 'integer') then return ps_error('typecheck') end local path = gsstate.path local length = #path local x, y = do_transform(gsstate.matrix, a[4], b[4]) if length > 0 and path[length][1] == "moveto" then -- replace last moveto else length = length + 1 end path[length] = { "moveto", x, y } gsstate.position = { x, y } return true end function operators.rmoveto() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local bt = b[1] local at = a[1] if not (bt == 'real' or bt == 'integer') then return ps_error('typecheck') end if not (at == 'real' or at == 'integer') then return ps_error('typecheck') end local position = gsstate.position local path = gsstate.path local length = #path if #position == 0 then return ps_error('nocurrentpoint') end local x, y = do_transform(gsstate.matrix, a[4], b[4]) x = position[1] + x y = position[2] + y position[1] = x position[2] = y if length > 0 and path[length][1] == "moveto" then -- replace last moveto else length = length + 1 end path[length] = { "moveto", x, y } return true end function operators.lineto() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local at = a[1] local bt = b[1] if not (bt == 'real' or bt == 'integer') then return ps_error('typecheck') end if not (at == 'real' or at == 'integer') then return ps_error('typecheck') end local position = gsstate.position local path = gsstate.path local length = #path if #position == 0 then return ps_error('nocurrentpoint') end local x, y = do_transform(gsstate.matrix, a[4], b[4]) gsstate.position = { x, y } path[length+1] = { "lineto", x, y } return true end function operators.rlineto() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local at = a[1] local bt = b[1] if not (bt == 'real' or bt == 'integer') then return ps_error('typecheck') end if not (at == 'real' or at == 'integer') then return ps_error('typecheck') end local position = gsstate.position local path = gsstate.path local length = #path if #position == 0 then return ps_error('nocurrentpoint') end local x, y = do_transform(gsstate.matrix, a[4], b[4]) x = position[1] + x y = position[2] + y position[1] = x position[2] = y path[length+1] = { "lineto", x, y } return true end local function arc_to_curve (x, y, r, aa, theta) local th = rad(theta/2.0) local x0 = cos(th) local y0 = sin(th) local x1 = (4.0-x0)/3.0 local y1 = ((1.0-x0)*(3.0-x0))/(3.0*y0) -- y0 != 0... local x2 = x1 local y2 = -y1 -- local x3 = x0 -- local y3 = -y0 local bezAng = rad(aa) + th local cBezAng = cos(bezAng) local sBezAng = sin(bezAng) local rx0 = (cBezAng * x0) - (sBezAng * y0) local ry0 = (sBezAng * x0) + (cBezAng * y0) local rx1 = (cBezAng * x1) - (sBezAng * y1) local ry1 = (sBezAng * x1) + (cBezAng * y1) local rx2 = (cBezAng * x2) - (sBezAng * y2) local ry2 = (sBezAng * x2) + (cBezAng * y2) -- local rx3 = (cBezAng * x3) - (sBezAng * y3) -- local ry3 = (sBezAng * x3) + (cBezAng * y3) local px0 = x + r*rx0 local py0 = y + r*ry0 local px1 = x + r*rx1 local py1 = y + r*ry1 local px2 = x + r*rx2 local py2 = y + r*ry2 -- local px3 = x + r*rx3 -- local py3 = y + r*ry3 return px2, py2, px1, py1, px0, py0 -- no px3, py3 end local function arc_start(x,y,r,aa) local x3 = 1 local y3 = 0 local bezAng = rad(aa) local cBezAng = cos(bezAng) local sBezAng = sin(bezAng) local rx3 = (cBezAng * x3) - (sBezAng * y3) local ry3 = (sBezAng * x3) + (cBezAng * y3) local px3 = x + r*rx3 local py3 = y + r*ry3 return px3, py3 end local function do_arc(matrix,path,x,y,r,aa,ab) local endx, endy local segments = floor((ab-aa+44.999999999)/45) if segments == 0 then return do_transform(gsstate.matrix, x,y) end local theta = (ab-aa) / segments while segments>0 do local x1, y1, x2, y2, x3, y3 = arc_to_curve(x,y,r,aa,theta) local px2, py2 = do_transform(matrix,x2,y2) local px1, py1 = do_transform(matrix,x1,y1) endx, endy = do_transform(matrix, x3,y3) path[#path+1] = { "curveto", px1, py1, px2, py2, endx, endy } segments = segments - 1 aa = aa + theta end return endx, endy end local function do_arcn(matrix,path,x,y,r,aa,ab) local endx, endy local segments = floor((aa-ab+44.999999999)/45) if segments == 0 then return do_transform(matrix, x,y) end local theta = (aa-ab) / segments while segments > 0 do local x1, y1, x2, y2, x3, y3 = arc_to_curve(x,y,r,aa,-theta) local px1, py1 = do_transform(matrix,x1,y1) local px2, py2 = do_transform(matrix,x2,y2) endx, endy = do_transform(matrix,x3,y3) path[#path+1] = { "curveto", px1 , py1 , px2 , py2 , endx , endy } segments = segments - 1 aa = aa - theta end return endx, endy end local function commonarc(action) local e = pop_opstack() local d = pop_opstack() local c = pop_opstack() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb, tc, td, te = a[1], b[1], c[1], d[1], e[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end if not (tc == 'real' or tc == 'integer') then return ps_error('typecheck') end if not (td == 'real' or td == 'integer') then return ps_error('typecheck') end if not (te == 'real' or te == 'integer') then return ps_error('typecheck') end local position = gsstate.position local path = gsstate.path local matrix = gsstate.matrix local vd = d[4] local ve = e[4] if vd < 0 or ve < 0 or vd > 360 or ve > 360 or (vd-ve) <= 0 then return ps_error('limitcheck') end local r = c[4] if r == 0 then ps_error('limitcheck') end local x = a[4] local y = b[4] local x0, y0 = arc_start(x,y,r,vd) -- find starting points local startx, starty = do_transform(matrix,x0,y0) path[#path+1] = { #position == 2 and "lineto" or "moveto", startx, starty } position[1], position[2] = action(matrix,path,x,y,r,vd,ve) return true end function operators.arc() commonarc(do_arc) end function operators.arcn() commonarc(do_arcn) end local function vlength (a,b) return sqrt(a^2+b^2) end local function vscal_ (a,b,c) return a*b, a*c end -- this is of_the_way local function between (dist, pa, pb) local pa1, pa2 = pa[1], pa[2] local pb1, pb2 = pb[1], pb[2] return { pa1 + dist * (pb1 - pa1), pa2 + dist * (pb2 - pa2), } end local function sign (a) return a < 0 and -1 or 1 end local function do_arcto(x,y,r) -- todo: check with original local h = gsstate.position local tx1, tx2, ty1, ty2 local c1, c2 local x1, x2 = x[1], x[2] local y1, y2 = y[1], y[2] local h1, h2 = h[1], h[2] local ux, uy = x1 - h1, x2 - h2 local vx, vy = y1 - x1, y2 - x2 local lx, ly = vlength(ux,uy), vlength(vx,vy) local sx, sy = ux*vy - uy*vx, ux*vx + uy*vy if sx == 0 and sy == 0 then sx = r sy = 0 else sx = r sy = atan2(sx,sy) end local a_arcto = sx*tan(abs(sy)/2) if sx*sy*lx*ly == 0 then tx1 = x1 tx2 = x2 ty1 = x1 ty2 = x2 c1 = x1 c2 = x2 else local tx = between(a_arcto/lx,x,h) local ty = between(a_arcto/ly,x,y) local cc, dd = vscal_(sign(sy)*sx/lx,-uy,ux) tx1 = tx[1] tx2 = tx[2] ty1 = ty[1] ty2 = ty[2] c1 = tx1 + cc c2 = tx2 + dd end -- now tx is the starting point, ty is the endpoint, -- c is the center of the circle. find the two angles local anga = deg(atan2(tx2-c2,tx1-c1)) -- todo, -90 is wrong local angb = deg(atan2(ty2-c2,ty1-c1)) -- todo, -90 is wrong return c1, c2, r, anga, angb, tx1, tx2, ty1, ty2 end function operators.arcto() local e = pop_opstack() local d = pop_opstack() local c = pop_opstack() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb, tc, td, te = a[1], b[2], c[1], d[1], e[1] if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end if not (tc == 'real' or tc == 'integer') then return ps_error('typecheck') end if not (td == 'real' or td == 'integer') then return ps_error('typecheck') end if not (te == 'real' or te == 'integer') then return ps_error('typecheck') end local x1, y1, x2, y2, r = a[4], b[4], c[4], d[4], e[4] local position = gsstate.position local path = gsstate.path if #position == 0 then return ps_error('nocurrentpoint') end local x, y, r, anga, angb, tx1, tx2, ty1, ty2 = do_arcto({x1,y1},{x2, y2},r) local vx, vy = do_transform(gsstate.matrix,tx1,tx2) path[#path+1] = { "lineto", vx, vy } if anga == angb then -- do nothing elseif anga > angb then position[1], position[2] = do_arcn(x,y,r,anga,angb) else position[1], position[2] = do_arc (x,y,r,anga,angb) end push_opstack { 'real', 'unlimited', 'literal', tx1 } push_opstack { 'real', 'unlimited', 'literal', tx2 } push_opstack { 'real', 'unlimited', 'literal', ty1 } push_opstack { 'real', 'unlimited', 'literal', ty2 } end function operators.curveto() local f = pop_opstack() local e = pop_opstack() local d = pop_opstack() local c = pop_opstack() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local f1 = f[1] if not (f1 == 'real' or f1 == 'integer') then return ps_error('typecheck') end local e1 = e[1] if not (e1 == 'real' or e1 == 'integer') then return ps_error('typecheck') end local d1 = d[1] if not (d1 == 'real' or d1 == 'integer') then return ps_error('typecheck') end local c1 = c[1] if not (c1 == 'real' or c1 == 'integer') then return ps_error('typecheck') end local b1 = b[1] if not (b1 == 'real' or b1 == 'integer') then return ps_error('typecheck') end local a1 = a[1] if not (a1 == 'real' or a1 == 'integer') then return ps_error('typecheck') end -- if #gsstate.position == 0 then return ps_error('nocurrentpoint') end -- local matrix = gsstate.matrix local x, y = do_transform(matrix, e[4], f[4]) local ax, ay = do_transform(matrix, a[4], b[4]) local bx, by = do_transform(matrix, c[4], d[4]) gsstate.position = { x, y } -- local path = gsstate.path path[#path+1] = { "curveto", ax, ay, bx, by, x, y } return true end function operators.rcurveto() local f = pop_opstack() local e = pop_opstack() local d = pop_opstack() local c = pop_opstack() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ft if not (ft == 'real' or ft == 'integer') then return ps_error('typecheck') end local et if not (et == 'real' or et == 'integer') then return ps_error('typecheck') end local dt if not (dt == 'real' or dt == 'integer') then return ps_error('typecheck') end local ct if not (ct == 'real' or ct == 'integer') then return ps_error('typecheck') end local bt if not (bt == 'real' or bt == 'integer') then return ps_error('typecheck') end local at if not (at == 'real' or at == 'integer') then return ps_error('typecheck') end local position = gsstate.position local path = gsstate.path if #position == 0 then return ps_error('nocurrentpoint') end local matrix = gsstate.matrix local x, y = do_transform(matrix, e[4], f[4]) local ax, ay = do_transform(matrix, a[4], b[4]) local bx, by = do_transform(matrix, c[4], d[4]) local px = position[1] + x local py = position[2] + y path[#path+1] = { "curveto", position[1] + ax, position[2] + ay, position[1] + bx, position[2] + by, px, py } position[1] = px position[2] = py return true end function operators.closepath() local path = gsstate.path local length = #path if length > 0 and path[length][1] ~= 'closepath' then local m = path[1] local a = m[2] local b = m[3] local x, y = do_transform(gsstate.matrix, a, b) gsstate.position = { x, y } path[length+1] = { "closepath", x, y } end return true end -- finds a point on a bezier curve -- P(x,y) = (1-t)^3*(x0,y0)+3*(1-t)^2*t*(x1,y1)+3*(1-t)*t^2*(x2,y2)+t^3*(x3,y3) local function bezier_at(t,x0,y0,x1,y1,x2,y2,x3,y3) local v = (1 - t) local x = (v^3)*x0 + 3*(v^2)*t*x1 + 3*v*(t^2)*x2 + (t^3)*x3 local y = (v^3)*y0 + 3*(v^2)*t*y1 + 3*v*(t^2)*y2 + (t^3)*y3 return x, y end local delta = 10 -- 100 local function good_enough (flatness,c,ct1,ct2,l) local c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y = c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8] local l0x, l0y, l1x, l1y = l[1], l[2], l[3], l[4] local t = 0 while t < delta do local td = t/delta local bx, by = bezier_at(ct1+(ct2-ct1)*td,c0x,c0y,c1x,c1y,c2x,c2y,c3x,c3y) local lx, ly = (1-td)*l0x + td*l1x, (1-td)*l0y + td*l1y local dist = vlength(bx-lx,by-ly) if dist > flatness then return false end t = t + 1 end return true end -- argument d is recursion depth, 10 levels should be enough to reach a conclusion -- (and already generates over 1000 lineto's in the worst case) local function splitter (flatness,p,d,c,ct1,ct2,l) local c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y = c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8] d = d + 1 local r = good_enough(flatness,c,ct1,ct1+ct2,l) if r or d > 10 then p[#p + 1] = { 'lineto', l[3], l[4] } else local ct22 = ct2/2 local l2x, l2y = bezier_at(ct1+ct22,c0x,c0y,c1x,c1y,c2x,c2y,c3x,c3y) local l1 = { l[1], l[2], l2x, l2y } local l2 = { l2x, l2y, l[3], l[4] } splitter(flatness,p,d,c,ct1,ct22,l1) splitter(flatness,p,d,c,ct1+ct22,ct22,l2) end end local function flattencurve( homex, homey, curve, flatness) local p = { } local c6 = curve[6] local c7 = curve[7] local thecurve = { homex, homey, curve[2], curve[3], curve[4], curve[5], c6, c7 } local theline = { homex, homey, c6, c7 } splitter(flatness, p, 0, thecurve, 0, 1, theline) return p end local function do_flattenpath (p, flatness) local x, y local px = { } local nx = 0 -- we don't care about differences less than a a permille of a point, ever if flatness < 0.001 then flatness = 0.001 end if p then for i=1,#p do local v = p[i] local t = v[1] if t == "curveto" then local pxl = flattencurve(x,y,v,flatness) for i=1,#pxl do nx = nx + 1 ; px[nx] = pxl[i] end x, y = v[6], v[7] elseif t == "lineto" or t == "moveto" then x, y = v[2], v[3] nx = nx + 1 ; px[nx] = v else nx = nx + 1 ; px[nx] = v end end end return px end function operators.flattenpath() gsstate.path = do_flattenpath(gsstate.path,gsstate.flatness) end function operators.clippath() gsstate.path = gsstate.clip return true end function operators.initclip() device.initclip() return true end function operators.eofill() local color = gsstate.color local thecolor = color[color.type] if type(thecolor) == "table" then thecolor = { unpack(thecolor) } end currentpage[#currentpage+1] = { type = 'eofill', path = gsstate.path, colortype = color.type, color = thecolor, } operators.newpath() return true end -- todo: this only fixes the output, not the actual clipping path -- in the gsstate ! function operators.clip() currentpage[#currentpage+1] = { type = 'clip', path = gsstate.path, } return true end -- todo: this only fixes the output, not the actual clipping path -- in the gsstate ! function operators.eoclip() currentpage[#currentpage+1] = { type = 'eoclip', path = gsstate.path, } return true end -- Painting operators -- -- +erasepage +fill +eofill +stroke -image -imagemask -- general graphics todo: transfer function, flatness function operators.erasepage() currentpage = { } return true end function operators.stroke() local color = gsstate.color local ctype = color.type local thecolor = color[ctype] -- if type(thecolor) == "table" then -- thecolor = { unpack(thecolor) } -- end currentpage[#currentpage+1] = { type = 'stroke', path = gsstate.path, colortype = ctype, color = thecolor, miterlimit = gsstate.miterlimit, linewidth = gsstate.linewidth, linecap = gsstate.linecap, linejoin = gsstate.linejoin, -- dashpattern = { unpack (gsstate.dashpattern) }, -- unpack? we don't manipulate dashpattern = gsstate.dashpattern, dashoffset = gsstate.dashoffset } operators.newpath() return true end function operators.fill() local color = gsstate.color local ctype = color.type local thecolor = color[ctype] -- if type(thecolor) == "table" then -- thecolor = { unpack(thecolor) } -- end currentpage[#currentpage+1] = { type = 'fill', path = gsstate.path, colortype = ctype, color = thecolor, } operators.newpath() return true end -- Device setup and output operators -- -- +showpage +copypage +banddevice +framedevice +nulldevice +renderbands -- will be replaced by the argument of 'new' -- this reports the bounding box of a page -- todo: linewidth for strokes -- todo: clips -- todo: strings (width&height) local calculatebox = false initializers[#initializers+1] = function() calculatebox = true end local function boundingbox(page) local bounding = specials.boundingbox if bounding and not calculatebox then return unpack(bounding) end local minx, miny, maxx, maxy local startx, starty local linewidth local function update_bbox (x,y) if not minx then minx = x miny = y maxx = x maxy = y end if linewidth then local xx = x + linewidth/2 if xx > maxx then maxx = xx elseif xx < minx then minx = xx end local xx = x - linewidth/2 if xx > maxx then maxx = xx elseif xx < minx then minx = xx end local yy = y + linewidth/2 if yy > maxy then maxy = yy elseif yy < miny then miny = yy end local yy = y - linewidth/2 if yy > maxy then maxy = yy elseif yy < miny then miny = yy end else if x > maxx then maxx = x elseif x < minx then minx = x end if y > maxy then maxy = y elseif y < miny then miny = y end end startx, starty = x, y end for i=1,#page do local object = page[i] local p = do_flattenpath(object.path,0.5) linewidth = object.type == "stroke" and object.linewidth for i=1,#p do local segment = p[i] local type = segment[1] if type == "lineto" then if startx then update_bbox(startx,starty) end update_bbox(segment[2],segment[3]) elseif type == "curveto" then if startx then update_bbox(startx,starty) end update_bbox(segment[6],segment[7]) elseif type == "moveto" then startx, starty = segment[2], segment[3] end end end if minx then return minx, miny, maxx, maxy else return 0, 0, 0, 0 end end ------------------------------------------------------------------ local function boundingbox (page) local bounding = specials.boundingbox if bounding and not calculatebox then return unpack(bounding) end local minx, miny, maxx, maxy local startx, starty local linewidth local function update_bbox (x,y) if not minx then minx = x miny = y maxx = x maxy = y end if linewidth then local xx = x + linewidth/2 if xx > maxx then maxx = xx elseif xx < minx then minx = xx end local xx = x - linewidth/2 if xx > maxx then maxx = xx elseif xx < minx then minx = xx end local yy = y + linewidth/2 if yy > maxy then maxy = yy elseif yy < miny then miny = yy end local yy = y - linewidth/2 if yy > maxy then maxy = yy elseif yy < miny then miny = yy end else if x > maxx then maxx = x elseif x < minx then minx = x end if y > maxy then maxy = y elseif y < miny then miny = y end end startx, starty = x, y end local delta = 10 -- 100 local function good_enough (ct1,ct2, c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y, l0x, l0y, l1x, l1y) local t = 0 while t < delta do local td = t/delta local bx, by = bezier_at(ct1+(ct2-ct1)*td,c0x,c0y,c1x,c1y,c2x,c2y,c3x,c3y) local lx, ly = (1-td)*l0x + td*l1x, (1-td)*l0y + td*l1y local dist = sqrt((bx-lx)^2+(by-ly)^2) -- vlength(bx-lx,by-ly) if dist > 0.5 then return false end t = t + 1 end return true end local function splitter (d,ct1,ct2, c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y, l0x, l0y, l1x, l1y) d = d + 1 local r = good_enough(ct1,ct1+ct2, c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y, l0x, l0y, l1x, l1y) if r or d > 10 then if startx then update_bbox(l1x, l1y) end else local ct22 = ct2/2 local l2x, l2y = bezier_at(ct1+ct22,c0x,c0y,c1x,c1y,c2x,c2y,c3x,c3y) splitter(d,ct1, ct22, c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y, l0x, l0y, l2x, l2y) splitter(d,ct1+ct22,ct22, c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y, l2x, l2y, l1x, l1y) end end for i=1,#page do local object = page[i] local p = object.path if p then linewidth = object.type == "stroke" and object.linewidth for i=1,#p do local segment = p[i] local type = segment[1] if type == "lineto" then if startx then update_bbox(startx,starty) end update_bbox(segment[2],segment[3]) elseif type == "curveto" then local c6 = segment[6] local c7 = segment[7] splitter(0, 0, 1, startx, starty, segment[2], segment[3], segment[4], segment[5], c6, c7, startx, starty, c6, c7) elseif type == "moveto" then startx, starty = segment[2], segment[3] end end end end if minx then return minx, miny, maxx, maxy else return 0, 0, 0, 0 end end ------------------------------------------------------------------ function operators.pathbbox() print("todo: pathbbox") push_opstack { "real", 'unlimited', 'literal', 0 } push_opstack { "real", 'unlimited', 'literal', 0 } push_opstack { "real", 'unlimited', 'literal', 1 } push_opstack { "real", 'unlimited', 'literal', 1 } return true end ------------------------------------------------------------------ -- most time is spend in calculating the boundingbox -- NULL output devices.null = { initgraphics = function() gsstate.matrix = { 1, 0, 0, 1, 0, 0 } end, initclip = function() gsstate.clip = { } end, showpage = function() return "" end, } -- PDF output local pdf = { initgraphics = function() gsstate.matrix = { 1, 0, 0, 1, 0, 0 } end, initclip = function() gsstate.clip = { } end, -- startpage = function(llc,lly,urx,ury) end, -- flushpage = function() end, -- stoppage = function() end, } devices.pdf = pdf function pdf.showpage(page) -- local startpage = pdf.startpage local stoppage = pdf.stoppage local flushpage = pdf.flushpage local showfont = pdf.showfont -- if not flushpage then return end -- if startpage then startpage(boundingbox(page)) end -- local t = { "q" } local n = 1 local g_colortype = "notacolor" local g_color = "" local g_miterlimit = -1 local g_linejoin = -1 local g_linecap = -1 local g_linewidth = -1 local g_dashpattern = nil local g_dashoffset = -1 local flush = devices.pdf.flush for i=1,#page do local object = page[i] local path = object.path local otyp = object.type if otyp == "gsave" then n = n + 1 ; t[n] = "q" -- todo push / pop g_colortype = "notacolor" g_color = "" g_miterlimit = -1 g_linejoin = -1 g_linecap = -1 g_linewidth = -1 g_dashpattern = nil g_dashoffset = -1 elseif otyp == "grestore" then g_colortype = "notacolor" g_color = "" g_miterlimit = -1 g_linejoin = -1 g_linecap = -1 g_linewidth = -1 g_dashpattern = nil g_dashoffset = -1 n = n + 1 ; t[n] = "Q" else if otyp ~= "clip" and otyp ~= "eoclip" then local colortype = object.colortype local color = object.color if colortype == "gray" then local v = formatters["%f g %f G"](color,color) if g_color ~= v then g_colortype = "gray" g_color = v n = n + 1 ; t[n] = v end elseif colortype == "rgb" then local r, g, b = color[1], color[2], color[3] local v = formatters["%f %f %f rg %f %f %f RG"](r,g,b,r,g,b) if g_color ~= v then g_colortype = "rgb" g_color = v n = n + 1 ; t[n] = v end elseif colortype == "cmyk" then local c, m, y, k = color[1], color[2], color[3], color[4] local v = formatters["%f %f %f %f k %f %f %f %f K"](c,m,y,k,c,m,y,k) if g_color ~= v then g_colortype = "cmyk" g_color = v n = n + 1 ; t[n] = v end elseif colortype == "hsb" then local r, g, b = hsv_to_rgb(color[1],color[2],color[3]) local v = formatters["%f %f %f rg %f %f %f RG"](r,g,b,r,g,b) if g_color ~= v then g_colortype = "rgb" g_color = v n = n + 1 ; t[n] = v end end end if otyp == "stroke" then local miterlimit = object.miterlimit if g_miterlimit ~= miterlimit then g_miterlimit = miterlimit n = n + 1 ; t[n] = formatters["%f M"](miterlimit) end local linejoin = object.linejoin if g_linejoin ~= linejoin then g_linejoin = linejoin n = n + 1 ; t[n] = formatters["%d j"](linejoin) end local linecap = object.linecap if g_linecap ~= linecap then g_linecap = linecap n = n + 1 ; t[n] = formatters["%d J"](linecap) end local linewidth = object.linewidth if g_linewidth ~= linewidth then g_linewidth = linewidth n = n + 1 ; t[n] = formatters["%f w"](linewidth) end local dashpattern = object.dashpattern local dashoffset = object.dashoffset if g_dashpattern ~= dashpattern or g_dashoffset ~= dashoffset then g_dashpattern = dashpattern g_dashoffset = dashoffset local l = #dashpattern if l == 0 then n = n + 1 ; t[n] = "[] 0 d" else n = n + 1 ; t[n] = formatters["[% t] %d d"](dashpattern,dashoffset) end end end if path then for i=1,#path do local segment = path[i] local styp = segment[1] if styp == "moveto" then n = n + 1 ; t[n] = formatters["%f %f m"](segment[2],segment[3]) elseif styp == "lineto" then n = n + 1 ; t[n] = formatters["%f %f l"](segment[2],segment[3]) elseif styp == "curveto" then n = n + 1 ; t[n] = formatters["%f %f %f %f %f %f c"](segment[2],segment[3],segment[4],segment[5],segment[6],segment[7]) elseif styp == "closepath" then n = n + 1 ; t[n] = "h" else report("unknown path segment type %a",styp) end end end if otyp == "stroke" then n = n + 1 ; t[n] = "S" elseif otyp == "fill" then n = n + 1 ; t[n] = "f" elseif otyp == "eofill" then n = n + 1 ; t[n] = "f*" elseif otyp == "clip" then n = n + 1 ; t[n] = "W n" elseif otyp == "eoclip" then n = n + 1 ; t[n] = "W* n" elseif otyp == "show" then if showfont then if n > 0 then flushpage(concat(t,"\n")) n = 0 ; t = { } end showfont(object) end else -- nothing to do end end end n = n + 1 ; t[n] = "Q" flushpage(concat(t,"\n")) -- if startpage then stoppage() end end function operators.showpage() local copies = lookup("#copies") if copies and copies[1] == 'integer' and copies[4] >= 1 then local amount = floor(copies[4]) local render = device.showpage if render then for i=1,amount do render(currentpage) end end end operators.erasepage() operators.initgraphics() return true end function operators.copypage() local render = device.showpage if render then render(currentpage) end return true end function operators.banddevice() local d = pop_opstack() local c = pop_opstack() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb, tc, td = a[1], b[1], c[1], d[1] if not (ta == 'array' and a[5] == 6) then return ps_error('typecheck') end if not (td == 'array' and d[3] == 'executable') then return ps_error('typecheck') end if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end if not (tc == 'real' or tc == 'integer') then return ps_error('typecheck') end local dev = device.banddevice if dev then dev(a,b,c,d) else return ps_error('undefined') -- fixed end return true end function operators.renderbands() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if not (a[1] == 'array' and a[3] == 'executable') then return ps_error('typecheck') end local dev = device.renderbands if dev then dev(d) else return ps_error('undefined') end return true end function operators.framedevice() local d = pop_opstack() local c = pop_opstack() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end local ta, tb, tc, td = a[1], b[1], c[1], d[1] if not (ta == 'array' and a[5] == 6) then return ps_error('typecheck') end if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end if not (tc == 'real' or tc == 'integer') then return ps_error('typecheck') end if not (td == 'array' and d[3] == 'executable') then return ps_error('typecheck') end local dev = device.framedevice if dev then dev(a,b,c,d) else return ps_error('undefined') end return true end function operators.nulldevice() gsstate.device = "null" operators.initgraphics() return true end -- Character and font operators -- -- +definefont *findfont +scalefont +makefont +setfont +currentfont +show -ashow -widthshow -- -awidthshow +kshow -stringwidth ^FontDirectory ^StandardEncoding -- Fonts are a bit special because it is needed to cooperate with the enclosing PDF document. local FontDirectory initializers[#initializers+1] = function(reset) if reset then FontDirectory = nil else FontDirectory = add_VM { access = 'unlimited', size = 0, maxsize = 5000, dict = { }, } end end -- loading actual fonts is a worryingly slow exercise local fontmap initializers[#initializers+1] = function() if reset then fontmap = nil else fontmap = { ['Courier-Bold'] = 'NimbusMonL-Bold.ps', ['Courier-BoldOblique'] = 'NimbusMonL-BoldObli.ps', ['Courier'] = 'NimbusMonL-Regu.ps', ['Courier-Oblique'] = 'NimbusMonL-ReguObli.ps', ['Times-Bold'] = 'NimbusRomNo9L-Medi.ps', ['Times-BoldItalic'] = 'NimbusRomNo9L-MediItal.ps', ['Times-Roman'] = 'NimbusRomNo9L-Regu.ps', ['Times-Italic'] = 'NimbusRomNo9L-ReguItal.ps', ['Helvetica-Bold'] = 'NimbusSanL-Bold.ps', ['Helvetica-BoldOblique'] = 'NimbusSanL-BoldItal.ps', ['Helvetica'] = 'NimbusSanL-Regu.ps', ['Helvetica-Oblique'] = 'NimbusSanL-ReguItal.ps', ['Symbol'] = 'StandardSymL.ps', } end end -- this can be overwritten by the user local function findfont(fontname) return fontmap[fontname] end -- tests required keys in a font dict local function checkfont(f) -- FontMatrix local matrix = f['FontMatrix'] if not matrix or matrix[1] ~= 'array' or matrix[5] ~= 6 then return false end local thearray = get_VM(matrix[4]) for i=1,#thearray do local v = thearray[i] local tv = v[1] if not (tv == 'real' or tv == 'integer') then return false end end -- FontType local ftype = f['FontType'] if not ftype or ftype[1] ~= 'integer' then return false end -- FontBBox local bbox = f['FontBBox'] -- do not test [5] here, because it can be '1' (executable array) if not bbox or bbox[1] ~= 'array' or bbox[6] ~= 4 then return false end local thearray = get_VM(bbox[4]) for i=1,#thearray do local v = thearray[i] local tv = v[1] if not (tv == 'real' or tv == 'integer') then return false end end -- Encoding local bbox = f['Encoding'] if not bbox or bbox[1] ~= 'array' or bbox[5] ~= 256 then return false end local thearray = get_VM(bbox[4]) for i=1,#thearray do local v = thearray[i] local tv = v[1] if tv[1] ~= 'name' then return false end end return true end -- objects of type font as essentially the same as objects of type dict function operators.definefont() local b = pop_opstack() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if b[1] ~= 'dict' then return ps_error('typecheck') end -- force keys to be names if a[1] ~= 'name' then return ps_error('typecheck') end local fontdict = get_VM(b[4]) if not checkfont(fontdict.dict) then return ps_error('invalidfont') end -- check that a FID will fit if fontdict.size == fontdict.maxsize then return ps_error('invalidfont') end fontdict.dict['FID'] = {'font', 'executable', 'literal', b[4]} fontdict.size = fontdict.size + 1 fontdict.access = 'read-only' local dict = get_VM(FontDirectory) local key = get_VM(a[4]) if not dict.dict[key] and dict.size == dict.maxsize then -- return ps_error('dictfull') -- level 1 only end if not dict.dict[key] then dict.size = dict.size + 1 end dict.dict[key] = fontdict.dict['FID'] push_opstack(b) return true end function operators.findfont() local a = pop_opstack() if not a then return ps_error('stackunderflow') end if a[1] ~= 'name' then return ps_error('typecheck') end local fontdict = get_VM(FontDirectory) local key = get_VM(a[4]) local dict = dict.dict if not dict[key] then fname = findfont(key) if not fname then return ps_error('invalidfont') end local oldfontkeys = { } for k, v in next, dict do oldfontkeys[i] = 1 end report("loading font file %a",fname) local theopstack = opstackptr local run = formatters['/eexec {pop} def (%s) run'](fname) push_execstack { '.stopped', 'unlimited', 'literal', false } local curstack = execstackptr push_execstack { 'string', 'unlimited', 'executable', add_VM(run), 1, #run } while curstack < execstackptr do do_exec() end if execstack[execstackptr][1] == '.stopped' then pop_execstack() end opstackptr = theopstack local fkey, ftab for k, v in next, dict do if not oldfontkeys[k] then -- this is the new dict fkey = k ftab = v break end end if not fkey then return ps_error('invalidfont') end dict[key] = ftab -- set up the user requested name as well end push_opstack(dict[key]) return true end local function pushscaledcopy(fontdict,matrix) local olddict = fontdict.dict if not checkfont(olddict) then return ps_error('invalidfont') end local newdict = { } local oldsize = fontdict.size local newfontdict = { dict = newdict, access = 'read-only', size = oldsize, maxsize = oldsize, } for k, v in next, olddict do if k == "FontMatrix" then local oldmatrix = get_VM(v[4]) local old = { oldmatrix[1][4], oldmatrix[2][4], oldmatrix[3][4], oldmatrix[4][4], oldmatrix[5][4], oldmatrix[6][4], } local c = do_concat(old,matrix) local new = { { 'real', 'unlimited', 'literal', c[1] }, { 'real', 'unlimited', 'literal', c[2] }, { 'real', 'unlimited', 'literal', c[3] }, { 'real', 'unlimited', 'literal', c[4] }, { 'real', 'unlimited', 'literal', c[5] }, { 'real', 'unlimited', 'literal', c[6] } } newdict[k] = { 'array', 'unlimited', 'literal', add_VM(new), 6, 6 } elseif k == "FID" then -- updated later else newfontdict.dict[k] = v end end local f = add_VM(newfontdict) newdict['FID'] = { 'font', 'read-only', 'literal', f } push_opstack { 'font', 'read-only', 'literal', f } -- share ? return true end function operators.scalefont() local s = pop_opstack() local b = pop_opstack() if not b then return ps_error('stackunderflow') end if b[1] ~= 'font' then return ps_error('typecheck') end if not (s[1] == 'integer' or s[1] == 'real') then return ps_error('typecheck') end local scals = s[4] local matrix = { scale, 0, 0, scale, 0, 0 } local fontdict = get_VM(b[4]) return pushscaledcopy(fontdict,matrix) end function operators.makefont() local s = pop_opstack() local b = pop_opstack() if not b then return ps_error('stackunderflow') end if b[1] ~= 'font' then return ps_error('typecheck') end if s[1] ~= 'array' then return ps_error('typecheck') end if s[6] ~= 6 then return ps_error('rangecheck') end local matrix = { } local array = get_VM(s[4]) for i=1,#array do local v = array[i] local tv = v[1] if not (tv == 'real' or tv == 'integer') then return ps_error('typecheck') end matrix[i] = v[4] end local fontdict = get_VM(b[4]) pushscaledcopy(fontdict,matrix) return true end function operators.setfont() local b = pop_opstack() if not b then return ps_error('stackunderflow') end if b[1] ~= 'font' then return ps_error('typecheck') end gsstate.font = b[4] return true end -- todo: the invalidfont error is temporary. 'start' should set up at least one font in -- FontDirectory and assing it as the current font function operators.currentfont() if not gsstate.font then return ps_error('invalidfont') end push_opstack {'font', 'read-only', 'literal', gsstate.font } return true end function do_show(fontdict,s) local stringmatrix = { } local truematrix = { } local stringencoding = { } -- local dict = fontdict.dict local fontname = get_VM(dict['FontName'][4]) local fontmatrix = get_VM(dict['FontMatrix'][4]) local encoding = get_VM(dict['Encoding'][4]) local matrix = gsstate.matrix local position = gsstate.position local color = gsstate.color local colortype = color.type local colordata = color[colortype] -- if fontmatrix then for i=1,#fontmatrix do stringmatrix[i] = fontmatrix[i][4] end end if matrix then for i=1,#matrix do truematrix[i] = matrix[i] end end if encoding then for i=1,#m do stringencoding[i] = get_VM(e[i][4]) end end if type(colordata) == "table" then colordata = { unpack(colordata) } -- copy end currentpage[#currentpage+1] = { type = 'show', string = s, fontname = fontname, adjust = nil, x = position[1], y = position[2], encoding = stringencoding, fontmatrix = stringmatrix, matrix = truematrix, colortype = colortype, color = colordata, } -- todo: update currentpoint, needing 'stringwidth' end function operators.show() local s = pop_opstack() if not s then return ps_error('stackunderflow') end if s[1] ~= 'string' then return ps_error('typecheck') end if #gsstate.position == 0 then return ps_error('nocurrentpoint') end if not gsstate.font then return ps_error('invalidfont') end local fontdict = get_VM(gsstate.font) if fontdict.access == "noaccess" then return ps_error('invalidaccess') end if not checkfont(fontdict.dict) then return ps_error('invalidfont') end do_show(fontdict,get_VM(s[4])) end function operators.kshow() local a = pop_opstack() local b = pop_opstack() if not a then return ps_error('stackunderflow') end if b[1] ~= "array" and b[3] == 'executable' then return ps_error('typecheck') end if b[2] == 'noaccess' then return ps_error('invalidaccess') end if not a[1] == 'string' then return ps_error('typecheck') end if a[2] == "execute-only" or a[2] == 'noaccess' then return ps_error('invalidaccess') end local fontdict = get_VM(gsstate.font) if fontdict.access == "noaccess" then return ps_error('invalidaccess') end if #gsstate.position == 0 then return ps_error('nocurrentpoint') end -- ok, that were the errors push_execstack { '.exit', 'unlimited', 'literal', false } local curstack = execstackptr if a[6] == 0 then return true end b[7] = 'i' local thestring = get_VM(a[4]) local v = sub(thestring,1,1) thestring = sub(thestring,2,-1) do_show(fontdict,v) for w in gmatch(thestring,".") do if stopped then stopped = false return false end push_opstack { 'integer', 'unlimited', 'literal', byte(v) } push_opstack { 'integer', 'unlimited', 'literal', byte(w) } b[5] = 1 push_execstack(b) while curstack < execstackptr do do_exec() end local entry = execstack[execstackptr] if entry[1] == '.exit' and entry[4] == true then pop_execstack() return true end do_show(fontdict,w) v = w end return true end local the_standardencoding = { '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '.notdef', 'endash', 'dagger', 'daggerdbl', 'periodcentered', '.notdef', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', '.notdef', 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', '.notdef', 'ring', 'cedilla', '.notdef', 'hungarumlaut', 'ogonek', 'caron', 'emdash', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', 'AE', '.notdef', 'ordfeminine', '.notdef', '.notdef', '.notdef', '.notdef', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', 'ae', '.notdef', '.notdef', '.notdef', 'dotlessi', '.notdef', '.notdef', 'lslash', 'oslash', 'oe', 'germandbls', '.notdef', '.notdef', '.notdef', '.notdef' } local function standardencoding() local a = { } for i=1,#the_standardencoding do a[i] = { 'name', 'unlimited', 'literal', add_VM(the_standardencoding[i]) } end return a end -- Font cache operators -- -- -cachestatus -setcachedevice -setcharwidth -setcachelimit -- userdict (initially empty) local systemdict local userdict initializers[#initializers+1] = function(reset) if reset then systemdict = nil else dictstackptr = dictstackptr + 1 dictstack[dictstackptr] = add_VM { access = 'unlimited', maxsize = MAX_INTEGER, size = 0, dict = { }, } if directvm then systemdict = dictstack[dictstackptr] else systemdict = dictstackptr end end end initializers[#initializers+1] = function(reset) if reset then userdict = nil else dictstackptr = dictstackptr + 1 dictstack[dictstackptr] = add_VM { access = 'unlimited', maxsize = MAX_INTEGER, size = 0, dict = { }, } if directvm then userdict = dictstack[dictstackptr] else userdict = dictstackptr end end end initializers[#initializers+1] = function(reset) if reset then -- already done else local dict = { ['$error'] = { 'dict', 'unlimited', 'literal', dicterror }, ['['] = { 'operator', 'unlimited', 'executable', operators.beginarray, '[' }, [']'] = { 'operator', 'unlimited', 'executable', operators.endarray, ']' }, -- ['='] = { 'operator', 'unlimited', 'executable', operators.EQ, '=' }, ['=='] = { 'operator', 'unlimited', 'executable', operators.equal, '==' }, ['abs'] = { 'operator', 'unlimited', 'executable', operators.abs, 'abs' }, ['add'] = { 'operator', 'unlimited', 'executable', operators.add, 'add' }, ['aload'] = { 'operator', 'unlimited', 'executable', operators.aload, 'aload' }, ['anchorsearch'] = { 'operator', 'unlimited', 'executable', operators.anchorsearch, 'anchorsearch' }, ['and'] = { 'operator', 'unlimited', 'executable', operators["and"], 'and' }, ['arc'] = { 'operator', 'unlimited', 'executable', operators.arc, 'arc' }, ['arcn'] = { 'operator', 'unlimited', 'executable', operators.arcn, 'arcn' }, ['arcto'] = { 'operator', 'unlimited', 'executable', operators.arcto, 'arcto' }, ['array'] = { 'operator', 'unlimited', 'executable', operators.array, 'array' }, ['astore'] = { 'operator', 'unlimited', 'executable', operators.astore, 'astore' }, ['atan'] = { 'operator', 'unlimited', 'executable', operators.atan, 'atan' }, ['banddevice'] = { 'operator', 'unlimited', 'executable', operators.banddevice, 'banddevice' }, ['bind'] = { 'operator', 'unlimited', 'executable', operators.bind, 'bind' }, ['bitshift'] = { 'operator', 'unlimited', 'executable', operators.bitshift, 'bitshift' }, ['begin'] = { 'operator', 'unlimited', 'executable', operators.begin, 'begin' }, ['bytesavailable'] = { 'operator', 'unlimited', 'executable', operators.bytesavailable, 'bytesavailable' }, ['ceiling'] = { 'operator', 'unlimited', 'executable', operators.ceiling, 'ceiling' }, ['clear'] = { 'operator', 'unlimited', 'executable', operators.clear, 'clear' }, ['cleartomark'] = { 'operator', 'unlimited', 'executable', operators.cleartomark, 'cleartomark' }, ['clip'] = { 'operator', 'unlimited', 'executable', operators.clip, 'clip' }, ['clippath'] = { 'operator', 'unlimited', 'executable', operators.clippath, 'clippath' }, ['pathbbox'] = { 'operator', 'unlimited', 'executable', operators.pathbbox, 'pathbbox' }, ['closefile'] = { 'operator', 'unlimited', 'executable', operators.closefile, 'closefile' }, ['closepath'] = { 'operator', 'unlimited', 'executable', operators.closepath, 'closepath' }, ['concat'] = { 'operator', 'unlimited', 'executable', operators.concat, 'concat' }, ['concatmatrix'] = { 'operator', 'unlimited', 'executable', operators.concatmatrix, 'concatmatrix' }, ['copy'] = { 'operator', 'unlimited', 'executable', operators.copy, 'copy' }, ['copypage'] = { 'operator', 'unlimited', 'executable', operators.copypage, 'copypage' }, ['cos'] = { 'operator', 'unlimited', 'executable', operators.cos, 'cos' }, ['count'] = { 'operator', 'unlimited', 'executable', operators.count, 'count' }, ['countdictstack'] = { 'operator', 'unlimited', 'executable', operators.countdictstack, 'countdictstack' }, ['countexecstack'] = { 'operator', 'unlimited', 'executable', operators.countexecstack, 'countexecstack' }, ['counttomark'] = { 'operator', 'unlimited', 'executable', operators.counttomark, 'counttomark' }, ['currentdash'] = { 'operator', 'unlimited', 'executable', operators.currentdash, 'currentdash' }, ['currentdict'] = { 'operator', 'unlimited', 'executable', operators.currentdict, 'currentdict' }, ['currentfile'] = { 'operator', 'unlimited', 'executable', operators.currentfile, 'currentfile' }, ['currentflat'] = { 'operator', 'unlimited', 'executable', operators.currentflat, 'currentflat' }, ['currentfont'] = { 'operator', 'unlimited', 'executable', operators.currentfont, 'currentfont' }, ['currentgray'] = { 'operator', 'unlimited', 'executable', operators.currentgray, 'currentgray' }, ['currenthsbcolor'] = { 'operator', 'unlimited', 'executable', operators.currenthsbcolor, 'currenthsbcolor' }, ['currentlinecap'] = { 'operator', 'unlimited', 'executable', operators.currentlinecap, 'currentlinecap' }, ['currentlinejoin'] = { 'operator', 'unlimited', 'executable', operators.currentlinejoin, 'currentlinejoin' }, ['currentlinewidth'] = { 'operator', 'unlimited', 'executable', operators.currentlinewidth, 'currentlinewidth' }, ['currentmatrix'] = { 'operator', 'unlimited', 'executable', operators.currentmatrix, 'currentmatrix' }, ['currentmiterlimit'] = { 'operator', 'unlimited', 'executable', operators.currentmiterlimit, 'currentmiterlimit' }, ['currentpoint'] = { 'operator', 'unlimited', 'executable', operators.currentpoint, 'currentpoint' }, ['currentrgbcolor'] = { 'operator', 'unlimited', 'executable', operators.currentrgbcolor, 'currentrgbcolor' }, ['currentcmykcolor'] = { 'operator', 'unlimited', 'executable', operators.currentcmykcolor, 'currentcmykcolor' }, ['currentscreen'] = { 'operator', 'unlimited', 'executable', operators.currentscreen, 'currentscreen' }, ['currenttransfer'] = { 'operator', 'unlimited', 'executable', operators.currenttransfer, 'currenttransfer' }, ['curveto'] = { 'operator', 'unlimited', 'executable', operators.curveto, 'curveto' }, ['cvi'] = { 'operator', 'unlimited', 'executable', operators.cvi, 'cvi' }, ['cvlit'] = { 'operator', 'unlimited', 'executable', operators.cvlit, 'cvlit' }, ['cvn'] = { 'operator', 'unlimited', 'executable', operators.cvn, 'cvn' }, ['cvr'] = { 'operator', 'unlimited', 'executable', operators.cvr, 'cvr' }, ['cvrs'] = { 'operator', 'unlimited', 'executable', operators.cvrs, 'cvrs' }, ['cvs'] = { 'operator', 'unlimited', 'executable', operators.cvs, 'cvs' }, ['cvx'] = { 'operator', 'unlimited', 'executable', operators.cvx, 'cvx' }, ['def'] = { 'operator', 'unlimited', 'executable', operators.def, 'def' }, ['definefont'] = { 'operator', 'unlimited', 'executable', operators.definefont, 'definefont' }, ['dict'] = { 'operator', 'unlimited', 'executable', operators.dict, 'dict' }, ['dictstack'] = { 'operator', 'unlimited', 'executable', operators.dictstack, 'dictstack' }, ['div'] = { 'operator', 'unlimited', 'executable', operators.div, 'div' }, ['dtransform'] = { 'operator', 'unlimited', 'executable', operators.dtransform, 'dtransform' }, ['dup'] = { 'operator', 'unlimited', 'executable', operators.dup, 'dup' }, ['echo'] = { 'operator', 'unlimited', 'executable', operators.echo, 'echo' }, ['end'] = { 'operator', 'unlimited', 'executable', operators["end"], 'end' }, ['eoclip'] = { 'operator', 'unlimited', 'executable', operators.eoclip, 'eoclip' }, ['eofill'] = { 'operator', 'unlimited', 'executable', operators.eofill, 'eofill' }, ['eq'] = { 'operator', 'unlimited', 'executable', operators.eq, 'eq' }, ['errordict'] = { 'dict', 'unlimited', 'literal', errordict }, ['exch'] = { 'operator', 'unlimited', 'executable', operators.exch, 'exch' }, ['exec'] = { 'operator', 'unlimited', 'executable', operators.exec, 'exec' }, ['execstack'] = { 'operator', 'unlimited', 'executable', operators.execstack, 'execstack' }, ['executeonly'] = { 'operator', 'unlimited', 'executable', operators.executeonly, 'executeonly' }, ['exit'] = { 'operator', 'unlimited', 'executable', operators.exit, 'exit' }, ['exp'] = { 'operator', 'unlimited', 'executable', operators.exp, 'exp' }, ['false'] = { 'boolean', 'unlimited', 'literal', false }, ['file'] = { 'operator', 'unlimited', 'executable', operators.file, 'file' }, ['fill'] = { 'operator', 'unlimited', 'executable', operators.fill, 'fill' }, ['findfont'] = { 'operator', 'unlimited', 'executable', operators.findfont, 'findfont' }, ['FontDirectory'] = { 'dict', 'unlimited', 'literal', escrito['FontDirectory'] }, ['flattenpath'] = { 'operator', 'unlimited', 'executable', operators.flattenpath, 'flattenpath' }, ['floor'] = { 'operator', 'unlimited', 'executable', operators.floor, 'floor' }, ['flush'] = { 'operator', 'unlimited', 'executable', operators.flush, 'flush' }, ['flushfile'] = { 'operator', 'unlimited', 'executable', operators.flushfile, 'flushfile' }, ['for'] = { 'operator', 'unlimited', 'executable', operators["for"], 'for' }, ['forall'] = { 'operator', 'unlimited', 'executable', operators.forall, 'forall' }, ['framedevice'] = { 'operator', 'unlimited', 'executable', operators.framedevice, 'framedevice' }, ['ge'] = { 'operator', 'unlimited', 'executable', operators.ge, 'ge' }, ['get'] = { 'operator', 'unlimited', 'executable', operators.get, 'get' }, ['getinterval'] = { 'operator', 'unlimited', 'executable', operators.getinterval, 'getinterval' }, ['grestore'] = { 'operator', 'unlimited', 'executable', operators.grestore, 'grestore' }, ['grestoreall'] = { 'operator', 'unlimited', 'executable', operators.grestoreall, 'grestoreall' }, ['gsave'] = { 'operator', 'unlimited', 'executable', operators.gsave, 'gsave' }, ['gt'] = { 'operator', 'unlimited', 'executable', operators.gt, 'gt' }, ['identmatrix'] = { 'operator', 'unlimited', 'executable', operators.identmatrix, 'identmatrix' }, ['idiv'] = { 'operator', 'unlimited', 'executable', operators.idiv, 'idiv' }, ['if'] = { 'operator', 'unlimited', 'executable', operators["if"], 'if' }, ['ifelse'] = { 'operator', 'unlimited', 'executable', operators.ifelse, 'ifelse' }, ['index'] = { 'operator', 'unlimited', 'executable', operators.index, 'index' }, ['initclip'] = { 'operator', 'unlimited', 'executable', operators.initclip, 'initclip' }, ['initgraphics'] = { 'operator', 'unlimited', 'executable', operators.initgraphics, 'initgraphics' }, ['initmatrix'] = { 'operator', 'unlimited', 'executable', operators.initmatrix, 'initmatrix' }, ['invertmatrix'] = { 'operator', 'unlimited', 'executable', operators.invertmatrix, 'invertmatrix' }, ['idtransform'] = { 'operator', 'unlimited', 'executable', operators.idtransform, 'idtransform' }, ['itransform'] = { 'operator', 'unlimited', 'executable', operators.itransform, 'itransform' }, ['known'] = { 'operator', 'unlimited', 'executable', operators.known, 'known' }, ['kshow'] = { 'operator', 'unlimited', 'executable', operators.kshow, 'kshow' }, ['le'] = { 'operator', 'unlimited', 'executable', operators.le, 'le' }, ['length'] = { 'operator', 'unlimited', 'executable', operators.length, 'length' }, ['lineto'] = { 'operator', 'unlimited', 'executable', operators.lineto, 'lineto' }, ['ln'] = { 'operator', 'unlimited', 'executable', operators.ln, 'ln' }, ['load'] = { 'operator', 'unlimited', 'executable', operators.load, 'load' }, ['log'] = { 'operator', 'unlimited', 'executable', operators.log, 'log' }, ['loop'] = { 'operator', 'unlimited', 'executable', operators.loop, 'loop' }, ['lt'] = { 'operator', 'unlimited', 'executable', operators.lt, 'lt' }, ['makefont'] = { 'operator', 'unlimited', 'executable', operators.makefont, 'makefont' }, ['mark'] = { 'operator', 'unlimited', 'executable', operators.mark, 'mark' }, ['matrix'] = { 'operator', 'unlimited', 'executable', operators.matrix, 'matrix' }, ['maxlength'] = { 'operator', 'unlimited', 'executable', operators.maxlength, 'maxlength' }, ['mod'] = { 'operator', 'unlimited', 'executable', operators.mod, 'mod' }, ['moveto'] = { 'operator', 'unlimited', 'executable', operators.moveto, 'moveto' }, ['mul'] = { 'operator', 'unlimited', 'executable', operators.mul, 'mul' }, ['ne'] = { 'operator', 'unlimited', 'executable', operators.ne, 'ne' }, ['neg'] = { 'operator', 'unlimited', 'executable', operators.neg, 'neg' }, ['newpath'] = { 'operator', 'unlimited', 'executable', operators.newpath, 'newpath' }, ['noaccess'] = { 'operator', 'unlimited', 'executable', operators.noaccess, 'noaccess' }, ['not'] = { 'operator', 'unlimited', 'executable', operators["not"], 'not' }, ['null'] = { 'operator', 'unlimited', 'executable', operators.null, 'null' }, ['or'] = { 'operator', 'unlimited', 'executable', operators["or"], 'or' }, ['pop'] = { 'operator', 'unlimited', 'executable', operators.pop, 'pop' }, ['print'] = { 'operator', 'unlimited', 'executable', operators.print, 'print' }, ['pstack'] = { 'operator', 'unlimited', 'executable', operators.pstack, 'pstack' }, ['put'] = { 'operator', 'unlimited', 'executable', operators.put, 'put' }, ['putinterval'] = { 'operator', 'unlimited', 'executable', operators.putinterval, 'putinterval' }, ['quit'] = { 'operator', 'unlimited', 'executable', operators.quit, 'quit' }, ['rand'] = { 'operator', 'unlimited', 'executable', operators.rand, 'rand' }, ['rcheck'] = { 'operator', 'unlimited', 'executable', operators.rcheck, 'rcheck' }, ['rcurveto'] = { 'operator', 'unlimited', 'executable', operators.rcurveto, 'rcurveto' }, ['read'] = { 'operator', 'unlimited', 'executable', operators.read, 'read' }, ['readhexstring'] = { 'operator', 'unlimited', 'executable', operators.readhexstring, 'readhexstring' }, ['readline'] = { 'operator', 'unlimited', 'executable', operators.readline, 'readline' }, ['readonly'] = { 'operator', 'unlimited', 'executable', operators.readonly, 'readonly' }, ['renderbands'] = { 'operator', 'unlimited', 'executable', operators.renderbands, 'renderbands' }, ['repeat'] = { 'operator', 'unlimited', 'executable', operators["repeat"], 'repeat' }, ['resetfile'] = { 'operator', 'unlimited', 'executable', operators.resetfile, 'resetfile' }, ['restore'] = { 'operator', 'unlimited', 'executable', operators.restore, 'restore' }, ['rlineto'] = { 'operator', 'unlimited', 'executable', operators.rlineto, 'rlineto' }, ['rmoveto'] = { 'operator', 'unlimited', 'executable', operators.rmoveto, 'rmoveto' }, ['roll'] = { 'operator', 'unlimited', 'executable', operators.roll, 'roll' }, ['rotate'] = { 'operator', 'unlimited', 'executable', operators.rotate, 'rotate' }, ['round'] = { 'operator', 'unlimited', 'executable', operators.round, 'round' }, ['rrand'] = { 'operator', 'unlimited', 'executable', operators.rrand, 'rrand' }, ['run'] = { 'operator', 'unlimited', 'executable', operators.run, 'run' }, ['save'] = { 'operator', 'unlimited', 'executable', operators.save, 'save' }, ['scale'] = { 'operator', 'unlimited', 'executable', operators.scale, 'scale' }, ['scalefont'] = { 'operator', 'unlimited', 'executable', operators.scalefont, 'scalefont' }, ['search'] = { 'operator', 'unlimited', 'executable', operators.search, 'search' }, ['setdash'] = { 'operator', 'unlimited', 'executable', operators.setdash, 'setdash' }, ['setflat'] = { 'operator', 'unlimited', 'executable', operators.setflat, 'setflat' }, ['setfont'] = { 'operator', 'unlimited', 'executable', operators.setfont, 'setfont' }, ['setgray'] = { 'operator', 'unlimited', 'executable', operators.setgray, 'setgray' }, ['sethsbcolor'] = { 'operator', 'unlimited', 'executable', operators.sethsbcolor, 'sethsbcolor' }, ['setlinecap'] = { 'operator', 'unlimited', 'executable', operators.setlinecap, 'setlinecap' }, ['setlinejoin'] = { 'operator', 'unlimited', 'executable', operators.setlinejoin, 'setlinejoin' }, ['setlinewidth'] = { 'operator', 'unlimited', 'executable', operators.setlinewidth, 'setlinewidth' }, ['setmatrix'] = { 'operator', 'unlimited', 'executable', operators.setmatrix, 'setmatrix' }, ['setmiterlimit'] = { 'operator', 'unlimited', 'executable', operators.setmiterlimit, 'setmiterlimit' }, ['setrgbcolor'] = { 'operator', 'unlimited', 'executable', operators.setrgbcolor, 'setrgbcolor' }, ['setcmykcolor'] = { 'operator', 'unlimited', 'executable', operators.setcmykcolor, 'setcmykcolor' }, ['setscreen'] = { 'operator', 'unlimited', 'executable', operators.setscreen, 'setscreen' }, ['settransfer'] = { 'operator', 'unlimited', 'executable', operators.settransfer, 'settransfer' }, ['show'] = { 'operator', 'unlimited', 'executable', operators.show, 'show' }, ['showpage'] = { 'operator', 'unlimited', 'executable', operators.showpage, 'showpage' }, ['sin'] = { 'operator', 'unlimited', 'executable', operators.sin, 'sin' }, ['sqrt'] = { 'operator', 'unlimited', 'executable', operators.sqrt, 'sqrt' }, ['srand'] = { 'operator', 'unlimited', 'executable', operators.srand, 'srand' }, ['stack'] = { 'operator', 'unlimited', 'executable', operators.stack, 'stack' }, ['start'] = { 'operator', 'unlimited', 'executable', operators.start, 'start' }, ['StandardEncoding'] = { 'array', 'unlimited', 'literal', add_VM(standardencoding()), 256, 256 }, ['status'] = { 'operator', 'unlimited', 'executable', operators.status, 'status' }, ['stop'] = { 'operator', 'unlimited', 'executable', operators.stop, 'stop' }, ['stopped'] = { 'operator', 'unlimited', 'executable', operators.stopped, 'stopped' }, ['store'] = { 'operator', 'unlimited', 'executable', operators.store, 'store' }, ['string'] = { 'operator', 'unlimited', 'executable', operators.string, 'string' }, ['stroke'] = { 'operator', 'unlimited', 'executable', operators.stroke, 'stroke' }, ['sub'] = { 'operator', 'unlimited', 'executable', operators.sub, 'sub' }, ['systemdict'] = { 'dict', 'unlimited', 'literal', systemdict }, ['token'] = { 'operator', 'unlimited', 'executable', operators.token, 'token' }, ['translate'] = { 'operator', 'unlimited', 'executable', operators.translate, 'translate' }, ['transform'] = { 'operator', 'unlimited', 'executable', operators.transform, 'transform' }, ['true'] = { 'boolean', 'unlimited', 'literal', true }, ['truncate'] = { 'operator', 'unlimited', 'executable', operators.truncate, 'truncate' }, ['type'] = { 'operator', 'unlimited', 'executable', operators.type, 'type' }, ['userdict'] = { 'dict', 'unlimited', 'literal', userdict }, ['usertime'] = { 'operator', 'unlimited', 'executable', operators.usertime, 'usertime' }, ['version'] = { 'operator', 'unlimited', 'executable', operators.version, 'version' }, ['vmstatus'] = { 'operator', 'unlimited', 'executable', operators.vmstatus, 'vmstatus' }, ['wcheck'] = { 'operator', 'unlimited', 'executable', operators.wcheck, 'wcheck' }, ['where'] = { 'operator', 'unlimited', 'executable', operators.where, 'where' }, ['write'] = { 'operator', 'unlimited', 'executable', operators.write, 'write' }, ['writehexstring'] = { 'operator', 'unlimited', 'executable', operators.writehexstring, 'writehexstring' }, ['writestring'] = { 'operator', 'unlimited', 'executable', operators.writestring, 'writestring' }, ['xcheck'] = { 'operator', 'unlimited', 'executable', operators.xcheck, 'xcheck' }, ['xor'] = { 'operator', 'unlimited', 'executable', operators.xor, 'xor' }, } if directvm then systemdict.dict = dict else VM[dictstack[systemdict]].dict = dict end end end initializers[#initializers+1] = function(reset) if reset then dicterror = nil errordict = nil else dicterror = add_VM { access = 'unlimited', size = 1, maxsize = 40, dict = { newerror = p_false }, } -- errordict = add_VM { access = 'unlimited', size = 0, maxsize = 40, dict = { }, } -- local d if directvm then d = systemdict.dict else d = VM[dictstack[systemdict]].dict end -- still needed ? d['errordict'] = { 'dict', 'unlimited', 'literal', errordict } d['systemdict'] = { 'dict', 'unlimited', 'literal', systemdict } d['userdict'] = { 'dict', 'unlimited', 'literal', userdict } d['$error'] = { 'dict', 'unlimited', 'literal', dicterror } end end -- What follows is the main interpreter, with the tokenizer first -- procedure scanning stack for the tokenizer local procstack local procstackptr initializers[#initializers+1] = function(reset) if reset then procstack = nil procstackptr = nil else procstack = { } procstackptr = 0 end end -- lpeg parser for tokenization do local function push(v) if procstackptr > 0 then local top = procstack[procstackptr] if top then top[#top+1] = v else procstack[procstackptr] = { v } end return false else push_execstack(v) return true end end local function start() procstackptr = procstackptr + 1 return true end local function stop() local v = procstack[procstackptr] procstack[procstackptr] = { } procstackptr = procstackptr - 1 if push {'array', 'unlimited', 'executable', add_VM(v), 1, #v, 'd' } then return true end end local function hexify(a) return char(tonumber(a,16)) end local function octify(a) return char(tonumber(a,8)) end local function radixed(base,value) base = tonumber(base) if base > 36 or base < 2 then return nil end value = tonumber(value,base) if not value then return "error", false elseif value > MAX_INT then return "integer", value else return "real", value end end local space = S(' ') local spacing = S(' \t\r\n\f') local sign = S('+-')^-1 local digit = R('09') local period = P('.') local letters = R('!~') - S('[]<>{}()%/') local hexdigit = R('09','af','AF') local radixdigit = R('09','az','AZ') local p_integer = (sign * digit^1 * #(1-letters)) / tonumber local p_real = ((sign * digit^0 * period * digit^0 + period * digit^1) * (S('eE') * sign * digit^1)^-1 * #(1-letters)) / tonumber local p_literal = Cs(P("/")/"" * letters^1 * letters^0) local p_symbol = C(letters^1 * letters^0) ----- p_radixed = C(digit^1) * P("#") * C(radixdigit^1) * #(1-letters) / radixed-- weird #() here local p_radixed = C(digit^1) * P("#") * C(radixdigit^1) / radixed local p_unhexed = P("<") * Cs(((C(hexdigit*hexdigit) * Cc(16))/tonumber/char+spacing/"")^0) * P(">") local p_comment = P('%') * (1 - S('\r\n'))^0 * Cc(true) local p_bounding = P('%%BoundingBox:') * Ct((space^0 * p_integer)^4) * (1 - S('\r\n'))^0 local p_lbrace = C("{") local p_rbrace = C("}") local p_lbracket = C("[") local p_rbracket = C("]") local p_finish = Cc(false) local p_string = P("(") * Cs( P { ( (1 - S("()\\"))^1 + P("\\")/"" * ( (C(digit *digit * digit) * Cc(8)) / tonumber / char + P("n") / "\n" + P("r") / "\r" + P("t") / "\t" + P("b") / "\b" + P("f") / "\f" + P("\\") / "\\" + 1 ) + P("(") * V(1) * P(")") )^0 }) * P(")") -- inspect(lpegmatch(p_radixed,"10#123")) -- inspect(lpegmatch(p_unhexed,"")) -- inspect(lpegmatch(p_string,[[(foo(bar \124\125 \( bar\n bar\\bar))]])) local p_unhexed = Cc('string') * p_unhexed local p_string = Cc('string') * p_string local p_array_start = Cc('name') * p_lbracket local p_array_stop = Cc('name') * p_rbracket local p_exec_start = Cc('start') * p_lbrace local p_exec_stop = Cc('stop') * p_rbrace local p_integer = Cc('integer') * p_integer local p_real = Cc('real') * p_real local p_radixed = p_radixed local p_symbol = Cc('name') * p_symbol local p_literal = Cc('literal') * p_literal local p_comment = Cc('comment') * p_comment local p_bounding = Cc('bounding') * p_bounding local p_finish = Cc("eof") * p_finish local p_whitespace = spacing^0 local tokens = p_whitespace * ( p_bounding + p_comment + p_string + p_unhexed + p_array_start + p_array_stop + p_exec_start + p_exec_stop + p_real + p_radixed + p_integer + p_literal + p_symbol + p_finish )^-1 * Cp() -- we can do push etc in the lpeg but the call is not faster than the check -- and this stays closer to the original local function tokenize() local object = execstack[execstackptr] local sequence = object[4] local position = object[5] local length = object[6] local tokentype = nil local value = nil while position < length do tokentype, value, position = lpegmatch(tokens,get_VM(sequence),position) if not position then return false elseif position >= length then pop_execstack() else object[5] = position end if not value then if tokentype == "eof" then -- pop_execstack() return true else return false -- handle_error('syntaxerror') end elseif tokentype == 'integer' or tokentype == 'real' then if push { tokentype, 'unlimited', 'literal', value } then return true end elseif tokentype == 'name' then if push { 'name', 'unlimited', 'executable', add_VM(value) } then return true end elseif tokentype == 'literal' then if push { 'name', 'unlimited', 'literal', add_VM(value) } then return true end elseif tokentype == 'string' then if push { 'string', 'unlimited', 'literal', add_VM(value), 1, #value } then return true end elseif tokentype == 'start' then if start() then -- stay end elseif tokentype == 'stop' then if stop() then return true end elseif tokentype == 'bounding' then specials.boundingbox = value else -- comment end end return position >= length end -- the exec stack can contain a limited amount of interesting item types -- to be handled by next_object: -- executable arrays (procedures) -- executable strings -- executable files next_object = function() if execstackptr == 0 then return nil end local object = execstack[execstackptr] if not object then return nil end local otyp = object[1] local exec = object[3] == 'executable' if not exec then return pop_execstack() elseif otyp == 'array' then if object[7] == 'd' then return pop_execstack() else local proc = get_VM(object[4]) local o = object[5] local val = proc[o] if o >= #proc then object[5] = 1 pop_execstack() else object[5] = o + 1 end return val end elseif otyp == 'string' then if not tokenize() then report("tokenizer failed on string") return nil else return next_object() -- recurse end elseif otyp == 'file' then if object[4] == 0 then report('sorry, interactive mode is not supported') end if not tokenize() then report("tokenizer failed on file") return nil else return next_object() -- recurse end else return pop_execstack() end end -- The main execution control function local detail = false -- much faster local report_exec = logs.reporter("escrito","exec") do_exec = function() -- already a local local ret local savedopstack = detail and copy_opstack() local object = next_object() if not object then return false end local otyp = object[1] if false then -- debugging if otyp == 'operator' then report_exec("%s %s %s",otyp,object[3],object[5]) elseif otyp == 'dict' then local d = get_VM(object[4]) report_exec("%s %s <%s:%s>",otyp,object[3],d.size or '',d.maxsize or '') elseif otyp == 'array' or otyp == 'file' or otyp == 'save' then report_exec("%s <%s:%s>",object[3],object[5] or '',object[6] or '') elseif otyp == 'string' or otyp == 'name' then report_exec("%s %s %s",otyp,object[3],get_VM(object[4])) else report_exec("%s %s %s",otyp,object[3],tostring(object[4])) end end if otyp == 'real' or otyp == 'integer' or otyp == 'boolean' or otyp == 'mark' or otyp == 'save' or otyp == 'font' then push_opstack(object) elseif otyp == '.stopped' then -- when .stopped is seen here, stop was never called push_opstack { 'boolean', 'unlimited', 'executable', false} elseif otyp == '.exit' then -- when .exit is seen here, exit was never called elseif otyp == 'array' then if object[2] == 'noaccess' then escrito.errorname = 'noaccess' else push_opstack(object) end elseif otyp == 'string' then if object[2] == 'noaccess' then escrito.errorname = 'noaccess' else push_opstack(object) end elseif otyp == 'dict' then local dict = get_VM(object[4]) if dict.access == 'noaccess' then escrito.errorname = 'noaccess' else push_opstack(object) end elseif otyp == 'file' then if object[2] == 'noaccess' then errorname = 'noaccess' else push_opstack(object) end elseif otyp == 'null' then push_opstack(object) elseif otyp == 'operator' then if object[3]=='executable' then ret, escrito.errorname = object[4]() else push_opstack(object) end elseif otyp == 'save' then -- todo elseif otyp == 'name' then if object[3] == 'executable' then local v = lookup(get_VM(object[4])) if not v then if escrito.errorname then -- doesn't work, needs thinking error ("recursive error detected inside '" .. escrito.errorname .. "'") end escrito.errorname = 'undefined' else if DEBUG then local vt = v[1] if vt == 'operator' then print ('exec2: ' .. vt .. ' ' .. v[3] .. ' '.. v[5]) elseif vt == 'dict' or vt == 'array' or vt == 'file' or vt == 'save' then print ('exec2: ' .. vt .. ' ' .. v[3] .. ' <'.. (v[5] or '') .. '>') elseif vt == 'string' or vt == 'name' then print ('exec2: ' .. vt .. ' ' .. v[3] .. ' '.. get_VM(v[4])) else print ('exec2: ' .. vt .. ' ' .. v[3] .. ' '.. tostring(v[4])) end end push_execstack(v) end else push_opstack(object) end elseif otyp == 'null' then -- do nothing elseif otyp == 'array' then push_opstack(object) end if escrito.errorname then if savedopstack then local v = lookup_error(escrito.errorname) if not v then print("unknown error handler for '" .. escrito.errorname .. "', quitting") return false else set_opstack(savedopstack) push_opstack { otyp, object[2], "literal", object[4], object[5], object[6], object[7] } push_opstack { 'string','unlimited','literal',add_VM(escrito.errorname), 1 } push_execstack(v) end escrito.errorname = nil else print("error '" .. escrito.errorname .. "', quitting") -- os.exit() end end return true end end do -- some of the errors will never actually happen local errornames = { "dictfull", "dictstackoverflow", "dictstackunderflow", "execstackoverflow", "interrupt", "invalidaccess", "invalidexit", "invalidfileaccess", "invalidfont", "invalidrestore", "ioerror", "limitcheck", "nocurrentpoint", "rangecheck", "stackoverflow", "stackunderflow", "syntaxerror", "timeout", "typecheck", "undefined", "undefinedfilename", "undefinedresult", "unmatchedmark", "unregistered", "VMerror" } local generic_error_proc = [[{ $error /newerror true put $error exch /errorname exch put $error exch /command exch put count array astore $error /ostack 3 -1 roll put $error /dstack countdictstack array dictstack put countexecstack array execstack aload pop pop count array astore $error /estack 3 -1 roll put stop } bind ]] local generic_handleerror_proc = [[{ $error begin /newerror false def (%%[ Error: ) print errorname print (; OffendingCommand: ) print command == ( ]%%\n) print flush end }]] local enabled local function interpret(data) if enabled then push_opstack { 'file', 'unlimited', 'executable', add_VM(data), 1, #data, 'r', stdin } push_execstack { 'operator', 'unlimited', 'executable', operators.stopped, 'stopped' } while true do if not do_exec() then local v = pop_opstack() if v and v[4] == true then local proc = { { 'name', 'unlimited', 'executable', add_VM('errordict') }, -- hm, errordict { 'name', 'unlimited', 'literal', add_VM('handleerror') }, { 'operator', 'unlimited', 'executable', operators.get, 'get' }, { 'operator', 'unlimited', 'executable', operators.exec, 'exec' }, } push_execstack { 'array', 'unlimited', 'executable', add_VM(proc), 1, #proc, 'i' } else return end end end end end local function close() for i=1,#initializers do initializers[i](true) end enabled = false end local function open(options) enabled = true local starttime = os.clock() local stoptime = nil for i=1,#initializers do initializers[i]() end if type(options) == "table" then devicename = options.device or "pdf" findfont = options.findfont or findfont randomseed = options.randomseed or randomseed -- todo calculatebox = options.calculatebox else devicename = "pdf" end device = devices[devicename] or devices.pdf operators.initgraphics() for i=1,#errornames do interpret(formatters["errordict /%s %s put"](errornames[i],generic_error_proc), INITDEBUG) end -- set up the error handler interpret("systemdict /= { 20 string cvs print } bind put", INITDEBUG) interpret("systemdict /prompt { (PS>) print flush } bind put", INITDEBUG) interpret(format("errordict /handleerror %s bind put", generic_handleerror_proc), INITDEBUG) interpret("systemdict /handleerror {errordict /handleerror get exec } bind put", INITDEBUG) -- user dict initializations interpret(format("/quit { stop } bind def"), INITDEBUG) interpret(format("userdict /#copies 1 put"), INITDEBUG) local job = { runtime = 0, interpret = interpret, boundingbox = boundingbox, close = function() close() local runtime = os.clock() - starttime job.runtime = runtime return runtime end, } return job end escrito.open = open if context then function escrito.convert(options) if type(options) == "table" then local data = options.data if not data or data == "" then local buffer = options.buffer local filename = options.filename -- needs escaping if buffer and buffer ~= "" then data = buffers.getcontent(buffer) elseif filename and filename ~= "" then data = io.loaddata(filename) -- use resolver end end if data and data ~= "" then local e = open(options) -- print(data) e.interpret(data) return e.close() end end return 0 end end escrito.devices = devices end return escrito