diff options
Diffstat (limited to 'tex')
71 files changed, 8251 insertions, 582 deletions
diff --git a/tex/context/base/back-exp.lua b/tex/context/base/back-exp.lua index da4c5d7e0..b9755ea44 100644 --- a/tex/context/base/back-exp.lua +++ b/tex/context/base/back-exp.lua @@ -1712,9 +1712,11 @@ do function extras.registerlocation(di,element,n,fulltag) local data = referencehash[fulltag] - if data then + if type(data) == "table" then extras.addinternal(di,data.references) return true + else + -- needs checking, probably bookmarks end end diff --git a/tex/context/base/buff-ver.mkiv b/tex/context/base/buff-ver.mkiv index 67f861ba0..450d5164c 100644 --- a/tex/context/base/buff-ver.mkiv +++ b/tex/context/base/buff-ver.mkiv @@ -514,7 +514,7 @@ \startpacked[\v!blank] \doifelseassignment{#1} {\setupcurrenttyping[#1]} - {\doifinset\v!continue{#1}{\lettypingparameter\c!continue\v!yes}}% + {\doif\v!continue{#1}{\lettypingparameter\c!continue\v!yes}}% \buff_verbatim_setup_line_numbering \buff_verbatim_initialize_typing_one \buff_verbatim_setup_keep_together @@ -606,6 +606,10 @@ \unexpanded\def\typefile {\dodoubleempty\buff_verbatim_type_file} +\appendtoks + \setuevalue{\e!type\currenttyping\v!file}{\typefile[\currenttyping]}% +\to \everydefinetyping + \def\buff_verbatim_type_file[#1][#2]#3% {\begingroup \ifsecondargument @@ -776,8 +780,13 @@ % [name] [settings] | [name] | [settings] +% \unexpanded\def\typebuffer +% {\dodoubleempty\buff_verbatim_type_buffer} + \unexpanded\def\typebuffer - {\dodoubleempty\buff_verbatim_type_buffer} + {\begingroup + \let\currenttyping\v!buffer + \dodoubleempty\buff_verbatim_type_buffer} \unexpanded\def\buff_verbatim_type_defined_buffer {\dotripleempty\buff_verbatim_type_defined_buffer_indeed} @@ -789,22 +798,45 @@ \setuevalue{\e!type\currentbuffer}{\buff_verbatim_type_defined_buffer[\v!buffer][\currentdefinedbuffer]}% \to \everydefinebuffer +\appendtoks % \e!buffer + \setuevalue{\e!type\currenttyping\v!buffer}{\buff_verbatim_type_buffer_class{\currenttyping}}% +\to \everydefinetyping + +% \unexpanded\def\buff_verbatim_type_buffer[#1][#2]% +% {\begingroup +% \ifsecondargument +% \setuptyping[\v!buffer][#2]% +% \processcommalist[#1]{\buff_verbatim_type_buffer_indeed\v!buffer}% [name] [settings] +% \else\iffirstargument +% \doifelseassignment{#1} +% {\setuptyping[\v!buffer][#1]% +% \buff_verbatim_type_buffer_indeed\v!buffer\empty}% [settings] +% {\processcommalist[#1]{\buff_verbatim_type_buffer_indeed\v!buffer}}% [name] +% \else +% \buff_verbatim_type_buffer_indeed\v!buffer\empty% [] +% \fi\fi +% \endgroup} + \unexpanded\def\buff_verbatim_type_buffer[#1][#2]% - {\begingroup - \ifsecondargument - \setuptyping[\v!buffer][#2]% - \processcommalist[#1]{\buff_verbatim_type_buffer_indeed\v!buffer}% [name] [settings] + {\ifsecondargument + \setupcurrenttyping[#2]% + \processcommalist[#1]{\buff_verbatim_type_buffer_indeed\currenttyping}% [name] [settings] \else\iffirstargument \doifelseassignment{#1} - {\setuptyping[\v!buffer][#1]% - \buff_verbatim_type_buffer_indeed\v!buffer\empty}% [settings] - {\processcommalist[#1]{\buff_verbatim_type_buffer_indeed\v!buffer}}% [name] + {\setupcurrenttyping[#1]% + \buff_verbatim_type_buffer_indeed\currenttyping\empty}% [settings] + {\processcommalist[#1]{\buff_verbatim_type_buffer_indeed\currenttyping}}% [name] \else - \buff_verbatim_type_buffer_indeed\v!buffer\empty% [] + \buff_verbatim_type_buffer_indeed\currenttyping\empty% [] \fi\fi \endgroup} -\def\buff_verbatim_type_defined_buffer_indeed[#1][#2][#3]% category name settings +\unexpanded\def\buff_verbatim_type_buffer_class#1% + {\begingroup + \edef\currenttyping{#1}% + \dodoubleempty\buff_verbatim_type_buffer} + +\unexpanded\def\buff_verbatim_type_defined_buffer_indeed[#1][#2][#3]% category name settings {\begingroup \ifthirdargument \setuptyping[#1][#3]% @@ -812,7 +844,7 @@ \buff_verbatim_type_buffer_indeed{#1}{#2}% \endgroup} -\def\buff_verbatim_type_buffer_indeed#1#2% category name +\unexpanded\def\buff_verbatim_type_buffer_indeed#1#2% category name {\edef\currenttyping{#1}% \typingparameter\c!before \startpacked[\v!blank] diff --git a/tex/context/base/cont-new.mkiv b/tex/context/base/cont-new.mkiv index 67fe48da8..3f12041ba 100644 --- a/tex/context/base/cont-new.mkiv +++ b/tex/context/base/cont-new.mkiv @@ -11,11 +11,13 @@ %C therefore copyrighted by \PRAGMA. See mreadme.pdf for %C details. -\newcontextversion{2015.09.13 13:31} +\newcontextversion{2015.10.04 19:25} %D This file is loaded at runtime, thereby providing an excellent place for %D hacks, patches, extensions and new features. +\usemodule[newotf] + \unprotect % \writestatus\m!system{beware: some patches loaded from cont-new.mkiv} diff --git a/tex/context/base/context-version.pdf b/tex/context/base/context-version.pdf Binary files differindex 64cefe13c..d898fe588 100644 --- a/tex/context/base/context-version.pdf +++ b/tex/context/base/context-version.pdf diff --git a/tex/context/base/context.mkiv b/tex/context/base/context.mkiv index 16418ea07..dd7320992 100644 --- a/tex/context/base/context.mkiv +++ b/tex/context/base/context.mkiv @@ -39,7 +39,7 @@ %D up and the dependencies are more consistent. \edef\contextformat {\jobname} -\edef\contextversion{2015.09.13 13:31} +\edef\contextversion{2015.10.04 19:25} \edef\contextkind {beta} %D For those who want to use this: diff --git a/tex/context/base/core-dat.lua b/tex/context/base/core-dat.lua index ca6ec4373..c3b00a7c0 100644 --- a/tex/context/base/core-dat.lua +++ b/tex/context/base/core-dat.lua @@ -79,7 +79,7 @@ local function setdata(settings) data = settings_to_hash(data) end if type(data) ~= "table" then - data = { data = settings.data } + data = { data = data } end if not tag then tag = #list + 1 diff --git a/tex/context/base/core-uti.lua b/tex/context/base/core-uti.lua index b3c8b5a5f..23057872f 100644 --- a/tex/context/base/core-uti.lua +++ b/tex/context/base/core-uti.lua @@ -198,7 +198,15 @@ local packlist = { -- "references", -- we need to rename of them as only one packs (not structures.lists.references) } -local jobpacker = packers.new(packlist,job.packversion) -- jump number when changs in hash +local skiplist = { + "datasets", + "userdata", +} + +-- not ok as we can have arbitrary keys in userdata and dataset so some day we +-- might need a bit more granularity, like skippers + +local jobpacker = packers.new(packlist,job.packversion,skiplist) -- jump number when changs in hash job.pack = true -- job.pack = false diff --git a/tex/context/base/font-ctx.lua b/tex/context/base/font-ctx.lua index 6e9a8312f..330508eae 100644 --- a/tex/context/base/font-ctx.lua +++ b/tex/context/base/font-ctx.lua @@ -1750,10 +1750,13 @@ statistics.register("font engine", function() otf.version,afm.version,tfm.version,nofloaded, nofshared,constructors.nofsharedvectors,constructors.nofsharedhashes, elapsed) - else + elseif nofloaded > 0 and elapsed then return format("otf %0.3f, afm %0.3f, tfm %0.3f, %s instances, load time %s", otf.version,afm.version,tfm.version,nofloaded, elapsed) + else + return format("otf %0.3f, afm %0.3f, tfm %0.3f", + otf.version,afm.version,tfm.version) end end) diff --git a/tex/context/base/font-ini.mkvi b/tex/context/base/font-ini.mkvi index c04952a9e..f023d71ab 100644 --- a/tex/context/base/font-ini.mkvi +++ b/tex/context/base/font-ini.mkvi @@ -1187,10 +1187,6 @@ \installcorenamespace{fontenvironmentknown} -% \let\bodyfontenvironmentlist\empty % used in font-run (might change) - -\newtoks\bodyfontenvironmentlist - \def\font_helpers_register_environment#class#body% {\expandafter\let\csname\??fontenvironmentknown#class#body\endcsname\empty} @@ -1224,7 +1220,6 @@ \else \normalizebodyfontsize\m_font_body_normalized\m_font_body \font_basics_define_body_font_environment_size[#class][\m_font_body_normalized][#settings]% - %\addtocommalist\m_font_body_normalized\bodyfontenvironmentlist \clf_registerbodyfontsize{\m_font_body_normalized}% \fi} diff --git a/tex/context/base/font-otn.lua b/tex/context/base/font-otn.lua index 8066b0f08..1b99c56de 100644 --- a/tex/context/base/font-otn.lua +++ b/tex/context/base/font-otn.lua @@ -553,7 +553,7 @@ local function toligature(kind,lookupname,head,start,stop,char,markflag,discfoun resetinjection(base) setfield(base,"char",char) setfield(base,"subtype",ligature_code) - setfield(base,"components",comp) -- start can have components .. do we need to flush? + setfield(base,"components",comp) -- start can have components ... do we need to flush? if prev then setfield(prev,"next",base) end @@ -3334,7 +3334,7 @@ local function featuresprocessor(head,font,attr) rlmode = -1 elseif dir == "-TLT" or dir == "-TRT" then topstack = topstack - 1 - rlmode = dirstack[topstack] == "+TLT" and 1 or -1 + rlmode = dirstack[topstack] == "+TRT" and -1 or 1 else rlmode = rlparmode end @@ -3606,7 +3606,7 @@ local function featuresprocessor(head,font,attr) rlmode = -1 elseif dir == "-TLT" or dir == "-TRT" then topstack = topstack - 1 - rlmode = dirstack[topstack] == "+TLT" and 1 or -1 + rlmode = dirstack[topstack] == "+TRT" and -1 or 1 else rlmode = rlparmode end diff --git a/tex/context/base/font-ots.lua b/tex/context/base/font-ots.lua index a5d4d3a5d..6826ae849 100644 --- a/tex/context/base/font-ots.lua +++ b/tex/context/base/font-ots.lua @@ -3306,7 +3306,7 @@ if not a or (a == attr) then rlmode = -1 elseif dir == "-TLT" or dir == "-TRT" then topstack = topstack - 1 - rlmode = dirstack[topstack] == "+TLT" and 1 or -1 + rlmode = dirstack[topstack] == "+TRT" and -1 or 1 else rlmode = rlparmode end @@ -3582,7 +3582,7 @@ if not a or (a == attr) then rlmode = -1 elseif dir == "-TLT" or dir == "-TRT" then topstack = topstack - 1 - rlmode = dirstack[topstack] == "+TLT" and 1 or -1 + rlmode = dirstack[topstack] == "+TRT" and -1 or 1 else rlmode = rlparmode end diff --git a/tex/context/base/grph-fil.lua b/tex/context/base/grph-fil.lua index c1532ce25..5b091b265 100644 --- a/tex/context/base/grph-fil.lua +++ b/tex/context/base/grph-fil.lua @@ -57,6 +57,8 @@ end -- +local done = { } + function jobfiles.context(name,options) if type(name) == "table" then local result = { } @@ -65,8 +67,12 @@ function jobfiles.context(name,options) end return result else - jobfiles.run(name,"context ".. (options or "") .. " " .. name) - return file.replacesuffix(name,"pdf") + local result = file.replacesuffix(name,"pdf") + if not done[result] then + jobfiles.run(name,"context ".. (options or "") .. " " .. name) + done[result] = true + end + return result end end diff --git a/tex/context/base/grph-inc.lua b/tex/context/base/grph-inc.lua index db8be818e..c161446d8 100644 --- a/tex/context/base/grph-inc.lua +++ b/tex/context/base/grph-inc.lua @@ -2084,3 +2084,15 @@ implement { figures.defaultheight = height end } + +-- require("util-lib-imp-gm") +-- +-- figures.converters.tif.pdf = function(oldname,newname,resolution) +-- logs.report("graphics","using gm library to convert %a",oldname) +-- utilities.graphicmagick.convert { +-- inputname = oldname, +-- outputname = newname, +-- } +-- end +-- +-- \externalfigure[t:/sources/hakker1b.tiff] diff --git a/tex/context/base/hand-ini.mkiv b/tex/context/base/hand-ini.mkiv index fd18e3221..7ee623193 100644 --- a/tex/context/base/hand-ini.mkiv +++ b/tex/context/base/hand-ini.mkiv @@ -48,13 +48,13 @@ % \setupfontprotrusion[quality-upright][vector=quality] % \setupfontprotrusion[quality-slanted][vector=quality,right=1.5] -\let\pdfadjustspacing\relax \newcount\pdfadjustspacing % a little bit protection -\let\pdfprotrudechars\relax \newcount\pdfprotrudechars % a little bit protection +\let\adjustspacing\relax \newcount\adjustspacing % a little bit protection +\let\protrudechars\relax \newcount\protrudechars % a little bit protection -\def\font_expansion_enable {\normalpdfadjustspacing\plustwo } % these will become normal primitives -\def\font_expansion_disable {\normalpdfadjustspacing\zerocount} % these will become normal primitives -\def\font_protruding_enable {\normalpdfprotrudechars\plustwo } % these will become normal primitives -\def\font_protruding_disable{\normalpdfprotrudechars\zerocount} % these will become normal primitives +\def\font_expansion_enable {\normaladjustspacing\plustwo } % these will become normal primitives +\def\font_expansion_disable {\normaladjustspacing\zerocount} % these will become normal primitives +\def\font_protruding_enable {\normalprotrudechars\plustwo } % these will become normal primitives +\def\font_protruding_disable{\normalprotrudechars\zerocount} % these will become normal primitives \appendtoks \font_expansion_disable \to \everyforgetall % Here or not here? \appendtoks \font_protruding_disable \to \everyforgetall % Here or not here? diff --git a/tex/context/base/lang-dis.lua b/tex/context/base/lang-dis.lua index d503cdffd..ca520654a 100644 --- a/tex/context/base/lang-dis.lua +++ b/tex/context/base/lang-dis.lua @@ -49,103 +49,6 @@ local getlanguagedata = languages.getdata local check_regular = true --- local expanders = { --- [disccodes.discretionary] = function(d,template) --- -- \discretionary --- return template --- end, --- [disccodes.explicit] = function(d,template) --- -- \- --- local pre = getfield(d,"pre") --- if pre and getid(pre) == glyph_code and getchar(pre) <= 0 then --- setfield(d,"pre",nil) --- end --- local post = getfield(d,"post") --- if post and getid(post) == glyph_code and getchar(post) <= 0 then --- setfield(d,"post",nil) --- end --- setfield(d,"subtype",discretionary_code) -- to be checked --- return template --- end, --- [disccodes.automatic] = function(d,template) --- -- following a - : the pre and post chars are already appended and set --- -- so we have pre=preex and post=postex .. however, the previous --- -- hyphen is already injected ... downside: the font handler sees this --- -- so this is another argument for doing a hyphenation pass in context --- if getfield(d,"pre") then --- -- we have a preex characters and want that one to replace the --- -- character in front which is the trigger --- if not template then --- -- can there be font kerns already? --- template = getprev(d) --- if template and getid(template) ~= glyph_code then --- template = getnext(d) --- if template and getid(template) ~= glyph_code then --- template = nil --- end --- end --- end --- if template then --- local pseudohead = getprev(template) --- if pseudohead then --- while template ~= d do --- pseudohead, template, removed = remove_node(pseudohead,template) --- setfield(d,"replace",removed) --- -- break ? --- end --- else --- -- can't happen --- end --- setfield(d,"subtype",discretionary_code) --- else --- -- print("lone regular discretionary ignored") --- end --- else --- setfield(d,"subtype",discretionary_code) --- end --- return template --- end, --- [disccodes.regular] = function(d,template) --- -- simple --- if not template then --- -- can there be font kerns already? --- template = getprev(d) --- if template and getid(template) ~= glyph_code then --- template = getnext(d) --- if template and getid(template) ~= glyph_code then --- template = nil --- end --- end --- end --- if template then --- local language = template and getfield(template,"lang") --- local data = getlanguagedata(language) --- local prechar = data.prehyphenchar --- local postchar = data.posthyphenchar --- if prechar and prechar > 0 then --- local c = copy_node(template) --- setfield(c,"char",prechar) --- setfield(d,"pre",c) --- end --- if postchar and postchar > 0 then --- local c = copy_node(template) --- setfield(c,"char",postchar) --- setfield(d,"post",c) --- end --- setfield(d,"subtype",discretionary_code) --- else --- -- print("lone regular discretionary ignored") --- end --- return template --- end, --- [disccodes.first] = function() --- -- forget about them --- end, --- [disccodes.second] = function() --- -- forget about them --- end, --- } - local expanders = { [disccodes.discretionary] = function(d,template) -- \discretionary @@ -164,7 +67,7 @@ local expanders = { post = nil end if done then - setdisc(d,pre,post,replace,discretionary_code) + setdisc(d,pre,post,replace,discretionary_code,tex.exhyphenpenalty) end return template end, @@ -199,12 +102,12 @@ local expanders = { else -- can't happen end - setdisc(d,pre,post,replace,discretionary_code) + setdisc(d,pre,post,replace,discretionary_code,tex.hyphenpenalty) else -- print("lone regular discretionary ignored") end else - setdisc(d,pre,post,replace,discretionary_code) + setdisc(d,pre,post,replace,discretionary_code,tex.hyphenpenalty) end return template end, @@ -241,13 +144,14 @@ local expanders = { setchar(post,postchar) end if done then - setdisc(d,pre,post,replace,discretionary_code) + setdisc(d,pre,post,replace,discretionary_code,tex.hyphenpenalty) end else -- print("lone regular discretionary ignored") end return template else + -- maybe also set penalty here setfield(d,"subtype",discretionary_code) end end, diff --git a/tex/context/base/lang-hyp.lua b/tex/context/base/lang-hyp.lua index 8e17721ed..6c7589956 100644 --- a/tex/context/base/lang-hyp.lua +++ b/tex/context/base/lang-hyp.lua @@ -949,40 +949,40 @@ if context then function traditional.hyphenate(head) - local first = tonut(head) - local tail = nil - local last = nil - local current = first - local dictionary = nil - local instance = nil - local characters = nil - local unicodes = nil - local exhyphenchar = tex.exhyphenchar - -- local discpenalty = tex.discpenalty -- makes no sense globally - local extrachars = nil - local hyphenchars = nil - local language = nil - local start = nil - local stop = nil - local word = { } -- we reuse this table - local size = 0 - local leftchar = false - local rightchar = false -- utfbyte("-") - local leftexchar = false - local rightexchar = false -- utfbyte("-") - local leftmin = 0 - local rightmin = 0 - local charmin = 1 - local leftcharmin = nil - local rightcharmin = nil - ----- leftwordmin = nil - local rightwordmin = nil - local leftchar = nil - local rightchar = nil - local attr = nil - local lastwordlast = nil - local hyphenated = hyphenate - local strict = nil + local first = tonut(head) + local tail = nil + local last = nil + local current = first + local dictionary = nil + local instance = nil + local characters = nil + local unicodes = nil + local exhyphenchar = tex.exhyphenchar + local extrachars = nil + local hyphenchars = nil + local language = nil + local start = nil + local stop = nil + local word = { } -- we reuse this table + local size = 0 + local leftchar = false + local rightchar = false -- utfbyte("-") + local leftexchar = false + local rightexchar = false -- utfbyte("-") + local leftmin = 0 + local rightmin = 0 + local charmin = 1 + local leftcharmin = nil + local rightcharmin = nil + ----- leftwordmin = nil + local rightwordmin = nil + local leftchar = nil + local rightchar = nil + local attr = nil + local lastwordlast = nil + local hyphenated = hyphenate + local strict = nil + local hyphenpenalty = tex.hyphenpenalty -- We cannot use an 'enabled' boolean (false when no characters or extras) because we -- can have plugins that set a characters metatable and so) ... it doesn't save much @@ -1172,18 +1172,18 @@ if context then local r = result[i] if r == true then local disc = new_disc() + local pre = nil + local post = nil if rightchar then - setfield(disc,"pre",serialize(true,rightchar)) + pre = serialize(true,rightchar) end if leftchar then - setfield(disc,"post",serialize(true,leftchar)) + post = serialize(true,leftchar) end + setdisc(disc,pre,post,nil,discretionary_code,hyphenpenalty) if attributes then setfield(disc,"attr",attributes) end - -- if discpenalty > 0 then - -- setfield(disc,"penalty",discpenalty) - -- end -- could be a replace as well insert_before(first,current,disc) elseif type(r) == "table" then @@ -1193,21 +1193,31 @@ if context then local replace = r[3] local right = r[4] ~= false and rightchar local left = r[5] ~= false and leftchar - if pre and pre ~= "" then - setfield(disc,"pre",serialize(pre,false,right)) + if pre then + if pre ~= "" then + pre = serialize(pre,false,right) + else + pre = nil + end end - if post and post ~= "" then - setfield(disc,"post",serialize(post,left,false)) + if post then + if post ~= "" then + post = serialize(post,left,false) + else + post = nil + end end - if replace and replace ~= "" then - setfield(disc,"replace",serialize(replace)) + if replace then + if replace ~= "" then + replace = serialize(replace) + else + replace = nil + end end + setdisc(disc,pre,post,replace,discretionary_code,hyphenpenalty) if attributes then setfield(disc,"attr",attributes) end - -- if discpenalty > 0 then - -- setfield(disc,"penalty",discpenalty) - -- end insert_before(first,current,disc) else setfield(current,"char",characters[r]) @@ -1235,20 +1245,21 @@ if context then setcolor(glyph,"darkred") -- these get checked setcolor(disc,"darkgreen") -- in the colorizer end - setfield(disc,"replace",glyph) + local pre = mil + local post = nil + local replace = glyph if not leftchar then leftchar = code end if rightchar then - local glyph = copy_node(glyph) - setfield(glyph,"char",rightchar) - setfield(disc,"pre",glyph) + pre = copy_node(glyph) + setfield(pre,"char",rightchar) end if leftchar then - local glyph = copy_node(glyph) - setfield(glyph,"char",leftchar) - setfield(disc,"post",glyph) + post = copy_node(glyph) + setfield(post,"char",leftchar) end + setdisc(disc,pre,post,replace,discretionary_code,hyphenpenalty) if attributes then setfield(disc,"attr",attributes) end diff --git a/tex/context/base/lpdf-ano.lua b/tex/context/base/lpdf-ano.lua index 8384313c2..d62fb9e5f 100644 --- a/tex/context/base/lpdf-ano.lua +++ b/tex/context/base/lpdf-ano.lua @@ -1063,32 +1063,39 @@ local function build(levels,start,parent,method,nested) local level = current.level local title = current.title local reference = current.reference - local block = reference.block local opened = current.opened local reftype = type(reference) + local block = nil local variant = "unknown" if reftype == "table" then -- we're okay - variant = "list" + variant = "list" + block = reference.block elseif reftype == "string" then local resolved = references.identify("",reference) local realpage = resolved and structures.references.setreferencerealpage(resolved) or 0 if realpage > 0 then - variant = "realpage" - realpage = realpage + variant = "realpage" + realpage = realpage + reference = structures.pages.collected[realpage] + block = reference and reference.block end elseif reftype == "number" then if reference > 0 then - variant = "realpage" - realpage = reference + variant = "realpage" + realpage = reference + reference = structures.pages.collected[realpage] + block = reference and reference.block end else -- error end + current.block = block if variant == "unknown" then -- error, ignore i = i + 1 - elseif (level < startlevel) or (i > 1 and block ~= levels[i-1].reference.block) then + -- elseif (level < startlevel) or (i > 1 and block ~= levels[i-1].reference.block) then + elseif (level < startlevel) or (i > 1 and block ~= levels[i-1].block) then if nested then -- could be an option but otherwise we quit too soon if entry then pdfflushobject(child,entry) @@ -1103,7 +1110,7 @@ local function build(levels,start,parent,method,nested) end if level == startlevel then if trace_bookmarks then - report_bookmark("%3i %w%s %s",reference.realpage,(level-1)*2,(opened and "+") or "-",title) + report_bookmark("%3i %w%s %s",realpage,(level-1)*2,(opened and "+") or "-",title) end local prev = child child = pdfreserveobject() @@ -1116,6 +1123,8 @@ local function build(levels,start,parent,method,nested) action = somedestination(reference.internal,reference.internal,reference.realpage) elseif variant == "realpage" then action = pagereferences[realpage] + else + -- hm, what to do end entry = pdfdictionary { Title = pdfunicode(title), diff --git a/tex/context/base/lpdf-epa.lua b/tex/context/base/lpdf-epa.lua index 59f62a310..c72e2a424 100644 --- a/tex/context/base/lpdf-epa.lua +++ b/tex/context/base/lpdf-epa.lua @@ -155,8 +155,8 @@ function codeinjections.mergereferences(specification) local size = specification.size or "crop" -- todo local pagedata = document.pages[pagenumber] local annotations = pagedata and pagedata.Annots - local namespace = makenamespace(fullname) - local reference = namespace .. pagenumber + local namespace = makenamespace(fullname) + local reference = namespace .. pagenumber if annotations and annotations.n > 0 then local mediabox = pagedata.MediaBox local llx = mediabox[1] diff --git a/tex/context/base/lpdf-grp.lua b/tex/context/base/lpdf-grp.lua index 95f093424..0eac52dfb 100644 --- a/tex/context/base/lpdf-grp.lua +++ b/tex/context/base/lpdf-grp.lua @@ -151,9 +151,9 @@ function nodeinjections.injectbitmap(t) CS = colorspace, BPC = 8, F = pdfconstant("AHx"), ---~ CS = nil, ---~ BPC = 1, ---~ IM = true, + -- CS = nil, + -- BPC = 1, + -- IM = true, } -- for some reasons it only works well if we take a 1bp boundingbox local urx, ury = 1/basepoints, 1/basepoints diff --git a/tex/context/base/lpdf-ini.lua b/tex/context/base/lpdf-ini.lua index 834f845c5..99e533094 100644 --- a/tex/context/base/lpdf-ini.lua +++ b/tex/context/base/lpdf-ini.lua @@ -542,6 +542,13 @@ local function pdfconstant(str,default) return c end +local escaped = Cs((S(forbidden)/replacements + P(1))^0) +----- escaped = Cs((1-forbidden)^0 * S(forbidden)/replacements * ((S(forbidden)/replacements + P(1))^0) + +function lpdf.escaped(str) + return lpegmatch(escaped,str) or str +end + local p_null = { } setmetatable(p_null, mt_z) local p_true = { } setmetatable(p_true, mt_t) local p_false = { } setmetatable(p_false,mt_f) diff --git a/tex/context/base/lpdf-mis.lua b/tex/context/base/lpdf-mis.lua index f09a60902..521266ad4 100644 --- a/tex/context/base/lpdf-mis.lua +++ b/tex/context/base/lpdf-mis.lua @@ -259,19 +259,19 @@ local pagespecs = { local pagespec, topoffset, leftoffset, height, width, doublesided = "default", 0, 0, 0, 0, false local cropoffset, bleedoffset, trimoffset, artoffset = 0, 0, 0, 0 -local pdfpaperheight = tex.pdfpageheight -local pdfpaperwidth = tex.pdfpagewidth +local pdfpaperheight = tex.normalpageheight -- we use normal because one never knows if an user +local pdfpaperwidth = tex.normalpagewidth -- defines a dimen is defined that overloads the internal function codeinjections.setupcanvas(specification) local paperheight = specification.paperheight local paperwidth = specification.paperwidth local paperdouble = specification.doublesided if paperheight then - texset('global','pdfpageheight',paperheight) + texset('global','pageheight',paperheight) pdfpaperheight = paperheight end if paperwidth then - texset('global','pdfpagewidth',paperwidth) + texset('global','pagewidth',paperwidth) pdfpaperwidth = paperwidth end pagespec = specification.mode or pagespec diff --git a/tex/context/base/lpdf-ren.lua b/tex/context/base/lpdf-ren.lua index 61676d5a8..81b9e9f20 100644 --- a/tex/context/base/lpdf-ren.lua +++ b/tex/context/base/lpdf-ren.lua @@ -46,6 +46,8 @@ local addtopageattributes = lpdf.addtopageattributes local addtopageresources = lpdf.addtopageresources local addtocatalog = lpdf.addtocatalog +local escaped = lpdf.escaped + local nodepool = nodes.pool local register = nodepool.register local pdfliteral = nodepool.pdfliteral @@ -87,6 +89,12 @@ local textlayers, hidelayers, videlayers = pdfarray(), pdfarray(), pdfarray() local pagelayers, pagelayersreference, cache = nil, nil, { } local alphabetic = { } +local escapednames = table.setmetatableindex(function(t,k) + local v = escaped(k) + t[k] = v + return v +end) + local specifications = { } local initialized = { } @@ -139,7 +147,7 @@ local function useviewerlayer(name) -- move up so that we can use it as local else hidelayers[#hidelayers+1] = nr end - pagelayers[tag] = dr -- check + pagelayers[escapednames[tag]] = dr -- check else -- todo: message end @@ -183,14 +191,13 @@ local function flushtextlayers() ON = videlayers, OFF = hidelayers, BaseState = pdf_on, - -AS = pdfarray { - pdfdictionary { - Category = pdfarray { pdfconstant("Print") }, - Event = pdfconstant("Print"), - OCGs = (viewerlayers.hasorder and sortedlayers) or nil, - } -}, + AS = pdfarray { + pdfdictionary { + Category = pdfarray { pdfconstant("Print") }, + Event = pdfconstant("Print"), + OCGs = (viewerlayers.hasorder and sortedlayers) or nil, + } + }, }, } addtocatalog("OCProperties",d) @@ -235,7 +242,7 @@ function codeinjections.startlayer(name) -- used in mp name = "unknown" end useviewerlayer(name) - return format("/OC /%s BDC",name) + return format("/OC /%s BDC",escapednames[name]) end function codeinjections.stoplayer(name) -- used in mp @@ -248,7 +255,7 @@ function nodeinjections.startlayer(name) local c = cache[name] if not c then useviewerlayer(name) - c = register(pdfliteral(format("/OC /%s BDC",name))) + c = register(pdfliteral(format("/OC /%s BDC",escapednames[name]))) cache[name] = c end return copy_node(c) diff --git a/tex/context/base/lxml-ctx.lua b/tex/context/base/lxml-ctx.lua index 1191d6796..677fd6a01 100644 --- a/tex/context/base/lxml-ctx.lua +++ b/tex/context/base/lxml-ctx.lua @@ -6,9 +6,9 @@ if not modules then modules = { } end modules ['lxml-ctx'] = { license = "see context related readme files" } --- is this still used? +-- will be cleaned up -local format, find = string.format, string.find +local format, find, gsub = string.format, string.find, string.gsub local xml = xml xml.ctx = { } @@ -36,6 +36,11 @@ local nodesettostring = xml.nodesettostring -- maybe use detokenize instead of \type +local function cleaned(str) + str = gsub(str,"|","\\textbar ") + return str +end + function xml.ctx.tshow(specification) local pattern = specification.pattern local xmlroot = specification.xmlroot @@ -48,9 +53,9 @@ function xml.ctx.tshow(specification) local parsed = xml.lpath(xmlpattern) local titlecommand = specification.title or "type" if parsed.state then - context[titlecommand]("pattern: " .. pattern .. " (".. parsed.state .. ")") + context[titlecommand]("pattern: " .. cleaned(pattern) .. " (".. parsed.state .. ")") else - context[titlecommand]("pattern: " .. pattern) + context[titlecommand]("pattern: " .. cleaned(pattern)) end context.starttabulate({ "|Tr|Tl|Tp|" } ) if specification.warning then @@ -75,15 +80,15 @@ function xml.ctx.tshow(specification) context(kind) context.NC() if kind == "axis" then - context(pp.axis) + context(cleaned(pp.axis)) elseif kind == "nodes" then - context(nodesettostring(pp.nodes,pp.nodetest)) + context(cleaned(nodesettostring(pp.nodes,pp.nodetest))) elseif kind == "expression" then ---~ context("%s => %s",pp.expression,pp.converted) - context(pp.expression) +-- -- context("%s => %s",pp.expression,pp.converted) + context(cleaned(pp.expression)) elseif kind == "finalizer" then context("%s(%s)",pp.name,pp.arguments) - elseif kind == "error" and pp.error then + elseif kind == "error" and pp.eqrror then context(pp.error) end context.NC() diff --git a/tex/context/base/lxml-ctx.mkiv b/tex/context/base/lxml-ctx.mkiv index 58807339d..6691e36bb 100644 --- a/tex/context/base/lxml-ctx.mkiv +++ b/tex/context/base/lxml-ctx.mkiv @@ -1,5 +1,5 @@ %D \module -%D [ file=lxml-ini, +%D [ file=lxml-ctx, %D version=2007.08.17, %D title=\CONTEXT\ \XML\ Support, %D subtitle=Initialization, diff --git a/tex/context/base/lxml-ini.lua b/tex/context/base/lxml-ini.lua index 20c635f11..379ea31b7 100644 --- a/tex/context/base/lxml-ini.lua +++ b/tex/context/base/lxml-ini.lua @@ -33,9 +33,9 @@ implement { name = "xmldoifelsetext", actions = lxml.doifelsetext, implement { name = "xmldoifempty", actions = lxml.doifempty, arguments = { "string", "string" } } implement { name = "xmldoifnotempty", actions = lxml.doifnotempty, arguments = { "string", "string" } } implement { name = "xmldoifelseempty", actions = lxml.doifelseempty, arguments = { "string", "string" } } -implement { name = "xmldoifselfempty", actions = lxml.doifempty, arguments = "string" } -implement { name = "xmldoifnotselfempty", actions = lxml.doifnotempty, arguments = "string" } -implement { name = "xmldoifelseselfempty", actions = lxml.doifelseempty, arguments = "string" } +implement { name = "xmldoifselfempty", actions = lxml.doifempty, arguments = "string" } -- second arg is not passed (used) +implement { name = "xmldoifnotselfempty", actions = lxml.doifnotempty, arguments = "string" } -- second arg is not passed (used) +implement { name = "xmldoifelseselfempty", actions = lxml.doifelseempty, arguments = "string" } -- second arg is not passed (used) --------- { name = "xmlcontent", actions = lxml.content, arguments = "string" } --------- { name = "xmlflushstripped", actions = lxml.strip, arguments = { "string", true } } @@ -76,12 +76,12 @@ implement { name = "xmlbadinclusions", actions = lxml.badinclusions, implement { name = "xmlindex", actions = lxml.index, arguments = { "string", "string", "string" } } -- can be integer but now we can alias implement { name = "xmlinfo", actions = lxml.info, arguments = "string" } implement { name = "xmlinlineverbatim", actions = lxml.inlineverbatim, arguments = "string" } -implement { name = "xmllast", actions = lxml.last, arguments = "string" } -implement { name = "xmlload", actions = lxml.load, arguments = { "string", "string", "string", "string" } } -implement { name = "xmlloadbuffer", actions = lxml.loadbuffer, arguments = { "string", "string", "string", "string" } } -implement { name = "xmlloaddata", actions = lxml.loaddata, arguments = { "string", "string", "string", "string" } } +implement { name = "xmllast", actions = lxml.last, arguments = { "string", "string" } } +implement { name = "xmlload", actions = lxml.load, arguments = { "string", "string", "string" } } +implement { name = "xmlloadbuffer", actions = lxml.loadbuffer, arguments = { "string", "string", "string" } } +implement { name = "xmlloaddata", actions = lxml.loaddata, arguments = { "string", "string", "string" } } implement { name = "xmlloaddirectives", actions = lxml.directives.load, arguments = "string" } -implement { name = "xmlloadregistered", actions = lxml.loadregistered, arguments = { "string", "string", "string" } } +implement { name = "xmlloadregistered", actions = lxml.loadregistered, arguments = "string" } implement { name = "xmlmain", actions = lxml.main, arguments = "string" } implement { name = "xmlmatch", actions = lxml.match, arguments = "string" } implement { name = "xmlname", actions = lxml.name, arguments = "string" } diff --git a/tex/context/base/lxml-ini.mkiv b/tex/context/base/lxml-ini.mkiv index fc8a4fdbd..edccff831 100644 --- a/tex/context/base/lxml-ini.mkiv +++ b/tex/context/base/lxml-ini.mkiv @@ -71,11 +71,11 @@ %let\xmlposition \xmlindex %def\xmlinlineverbatim #1{\clf_xmlinlineverbatim {#1}} %def\xmllast #1#2{\clf_xmllast {#1}{#2}} -\def\xmlload #1#2{\clf_xmlload {#1}{#2}{\directxmlparameter\c!entities}{\directxmlparameter\c!compress}} -\def\xmlloadbuffer #1#2{\clf_xmlloadbuffer {#1}{#2}{\directxmlparameter\c!entities}{\directxmlparameter\c!compress}} -\def\xmlloaddata #1#2{\clf_xmlloaddata {#1}{#2}{\directxmlparameter\c!entities}{\directxmlparameter\c!compress}} +\def\xmlload #1#2{\clf_xmlload {#1}{#2}{\directxmlparameter\c!compress}} +\def\xmlloadbuffer #1#2{\clf_xmlloadbuffer {#1}{#2}{\directxmlparameter\c!compress}} +\def\xmlloaddata #1#2{\clf_xmlloaddata {#1}{#2}{\directxmlparameter\c!compress}} %def\xmlloaddirectives #1{\clf_xmlloaddirectives {#1}} -\def\xmlloadregistered #1#2{\clf_xmlloadregistered {#1}{\directxmlparameter\c!entities}{\directxmlparameter\c!compress}} +%def\xmlloadregistered #1{\clf_xmlloadregistered {#1}} %def\xmlmain #1{\clf_xmlmain {#1}} %def\xmlmatch #1{\clf_xmlmatch {#1}} %def\xmlname #1{\clf_xmlname {#1}} diff --git a/tex/context/base/lxml-tex.lua b/tex/context/base/lxml-tex.lua index 48e75e9c1..f0a3cb385 100644 --- a/tex/context/base/lxml-tex.lua +++ b/tex/context/base/lxml-tex.lua @@ -181,37 +181,78 @@ local texfinalizers = finalizers.tex -- serialization with entity handling -local ampersand = P("&") -local semicolon = P(";") -local entity = ampersand * C((1-semicolon)^1) * semicolon / lxml.resolvedentity -- context.bold +local exceptions = false -local _, xmltextcapture = context.newtexthandler { +local ampersand = P("&") +local semicolon = P(";") +local entity = ampersand * C((1-semicolon)^1) * semicolon / lxml.resolvedentity -- context.bold + +local _, xmltextcapture_yes = context.newtexthandler { + catcodes = notcatcodes, exception = entity, +} +local _, xmltextcapture_nop = context.newtexthandler { catcodes = notcatcodes, } -local _, xmlspacecapture = context.newtexthandler { +local _, xmlspacecapture_yes = context.newtexthandler { endofline = context.xmlcdataobeyedline, emptyline = context.xmlcdataobeyedline, simpleline = context.xmlcdataobeyedline, space = context.xmlcdataobeyedspace, + catcodes = notcatcodes, exception = entity, +} +local _, xmlspacecapture_nop = context.newtexthandler { + endofline = context.xmlcdataobeyedline, + emptyline = context.xmlcdataobeyedline, + simpleline = context.xmlcdataobeyedline, + space = context.xmlcdataobeyedspace, catcodes = notcatcodes, } -local _, xmllinecapture = context.newtexthandler { +local _, xmllinecapture_yes = context.newtexthandler { endofline = context.xmlcdataobeyedline, emptyline = context.xmlcdataobeyedline, simpleline = context.xmlcdataobeyedline, + catcodes = notcatcodes, exception = entity, +} +local _, xmllinecapture_nop = context.newtexthandler { + endofline = context.xmlcdataobeyedline, + emptyline = context.xmlcdataobeyedline, + simpleline = context.xmlcdataobeyedline, catcodes = notcatcodes, } -local _, ctxtextcapture = context.newtexthandler { +local _, ctxtextcapture_yes = context.newtexthandler { + catcodes = ctxcatcodes, exception = entity, +} +local _, ctxtextcapture_nop = context.newtexthandler { catcodes = ctxcatcodes, } +local xmltextcapture, xmlspacecapture, xmllinecapture, ctxtextcapture + +function lxml.setescapedentities(v) + if v then + xmltextcapture = xmltextcapture_yes + xmlspacecapture = xmlspacecapture_yes + xmllinecapture = xmllinecapture_yes + ctxtextcapture = ctxtextcapture_yes + else + xmltextcapture = xmltextcapture_nop + xmlspacecapture = xmlspacecapture_nop + xmllinecapture = xmllinecapture_nop + ctxtextcapture = ctxtextcapture_nop + end +end + +lxml.setescapedentities() + +directives.register("lxml.escapedentities",lxml.setescapedentities) + -- cdata local toverbatim = context.newverbosehandler { @@ -427,7 +468,7 @@ local function entityconverter(id,str) return xmlentities[str] or xmlprivatetoken(str) or "" -- roundtrip handler end -function lxml.convert(id,data,entities,compress,currentresource) +local function lxmlconvert(id,data,compress,currentresource) local settings = { -- we're now roundtrip anyway unify_predefined_entities = true, utfize_entities = true, @@ -438,23 +479,20 @@ function lxml.convert(id,data,entities,compress,currentresource) if compress and compress == variables.yes then settings.strip_cm_and_dt = true end - -- if entities and entities == variables.yes then - -- settings.utfize_entities = true - -- -- settings.resolve_entities = function (str) return entityconverter(id,str) end - -- end return xml.convert(data,settings) end -function lxml.load(id,filename,compress,entities) +lxml.convert = lxmlconvert + +function lxml.load(id,filename,compress) filename = ctxrunner.preparedfile(filename) if trace_loading then report_lxml("loading file %a as %a",filename,id) end noffiles, nofconverted = noffiles + 1, nofconverted + 1 - -- local xmltable = xml.load(filename) starttiming(xml) local ok, data = resolvers.loadbinfile(filename) - local xmltable = lxml.convert(id,(ok and data) or "",compress,entities,format("id: %s, file: %s",id,filename)) + local xmltable = lxmlconvert(id,(ok and data) or "",compress,format("id: %s, file: %s",id,filename)) stoptiming(xml) lxml.store(id,xmltable,filename) return xmltable, filename @@ -541,29 +579,29 @@ function lxml.save(id,name) xml.save(getid(id),name) end -function xml.getbuffer(name,compress,entities) -- we need to make sure that commands are processed +function xml.getbuffer(name,compress) -- we need to make sure that commands are processed if not name or name == "" then name = tex.jobname end nofconverted = nofconverted + 1 local data = buffers.getcontent(name) - xmltostring(lxml.convert(name,data,compress,entities,format("buffer: %s",tostring(name or "?")))) -- one buffer + xmltostring(lxmlconvert(name,data,compress,format("buffer: %s",tostring(name or "?")))) -- one buffer end -function lxml.loadbuffer(id,name,compress,entities) +function lxml.loadbuffer(id,name,compress) starttiming(xml) nofconverted = nofconverted + 1 local data = buffers.collectcontent(name or id) -- name can be list - local xmltable = lxml.convert(id,data,compress,entities,format("buffer: %s",tostring(name or id or "?"))) + local xmltable = lxmlconvert(id,data,compress,format("buffer: %s",tostring(name or id or "?"))) lxml.store(id,xmltable) stoptiming(xml) return xmltable, name or id end -function lxml.loaddata(id,str,compress,entities) +function lxml.loaddata(id,str,compress) starttiming(xml) nofconverted = nofconverted + 1 - local xmltable = lxml.convert(id,str or "",compress,entities,format("id: %s",id)) + local xmltable = lxmlconvert(id,str or "",compress,format("id: %s",id)) lxml.store(id,xmltable) stoptiming(xml) return xmltable, id @@ -1742,7 +1780,7 @@ local function checkedempty(id,pattern) local nt = #dt return (nt == 0) or (nt == 1 and dt[1] == "") else - return isempty(getid(id),pattern) + return empty(getid(id),pattern) end end diff --git a/tex/context/base/m-escrito.lua b/tex/context/base/m-escrito.lua new file mode 100644 index 000000000..0d7a04741 --- /dev/null +++ b/tex/context/base/m-escrito.lua @@ -0,0 +1,7088 @@ +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 <taco@luatex.org>. 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 <COPYRIGHT HOLDER> ``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. + +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 +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 = true + +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 + +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 { 'boolean', 'unlimited', 'literal', true } + else + push_opstack(a) + push_opstack { 'boolean', 'unlimited', 'literal', 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 { 'boolean', 'unlimited', 'literal', true } + else + push_opstack(a) + push_opstack { 'boolean', 'unlimited', 'literal', 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 { 'boolean', 'unlimited', 'literal', 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 { 'boolean', 'unlimited', 'literal', 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 { 'boolean', 'unlimited', 'literal', 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 { 'boolean', 'unlimited', 'literal', 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 { 'boolean', 'unlimited', 'literal', a == b } + return true + else + return a + end +end + +function operators.ne() + local ok, a, b = both() + if ok then + push_opstack { 'boolean', 'unlimited', 'literal', a ~= b } + 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 { 'boolean', 'unlimited', 'literal', a >= b } + return true + else + return a + end +end + +function operators.gt() + local ok, a, b = both() + if ok then + push_opstack { 'boolean', 'unlimited', 'literal', a > b } + return true + else + return a + end +end + +function operators.le() + local ok, a, b = both() + if ok then + push_opstack { 'boolean', 'unlimited', 'literal', a <= b } + return true + else + return a + end +end + +function operators.lt() + local ok, a, b = both() + if ok then + push_opstack { 'boolean', 'unlimited', 'literal', a < b } + 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 { 'boolean', 'unlimited', 'literal', a[1] and b[1] } + 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 {'boolean', 'unlimited', 'literal', a[1] or b[1] } + 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 {'boolean', 'unlimited', 'literal', a[1] ~= b[1] } + 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 { 'boolean', 'unlimited', 'literal', not a[4] } + 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 { 'boolean', 'unlimited', 'literal', a[3] == 'executable' } + 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 { 'boolean', 'unlimited', 'literal', aa == 'unlimited' or aa == 'read-only' } + 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 { 'boolean', 'unlimited', 'literal', aa == 'unlimited' } + 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 { 'boolean', 'unlimited', 'literal', true } + else + f:close() + push_opstack { 'boolean', 'unlimited', 'literal', 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 { 'boolean', 'unlimited', 'literal', 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 { 'boolean', 'unlimited', 'literal', 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 { 'boolean', 'unlimited', 'literal', n == b[6] } + 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 + if type(v1) == "table" then + local t1 = { } + saved_VM[k1] = t1 + for k2, v2 in next, t1 do + if type(v2) == "table" then + local t2 = { } + t1[k2] = t2 + for k3, v3 in next, v2 do + 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 + +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() } + 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 + 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] = va + 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], f[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 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 + 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 + if minx then + return minx, miny, maxx, maxy + else + return 0, 0, 0, 0 + end +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 otype ~= "clip" and otype ~= "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 otype == "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 + 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' }, + ['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 = { 'boolean', 'unlimited', 'literal', 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,"<A2B3 C3>")) + -- 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 + return false -- handle_error('syntaxerror') + 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 + 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 DEBUG then + 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 diff --git a/tex/context/base/m-escrito.mkiv b/tex/context/base/m-escrito.mkiv new file mode 100644 index 000000000..763857918 --- /dev/null +++ b/tex/context/base/m-escrito.mkiv @@ -0,0 +1,184 @@ +%D \module +%D [ file=m-escrito, +%D version=2015.09.27, +%D title=\CONTEXT\ Extra Modules, +%D subtitle=ESCRITO, +%D author={Taco Hoekwater \& Hans Hagen}, +%D date=\currentdate, +%D copyright={\CONTEXT\ Development Team}] +%C +%C This module is part of the \CONTEXT\ macro||package and is +%C therefore copyrighted by \PRAGMA. See mreadme.pdf for +%C details. + +\registerctxluafile{m-escrito}{1.001} + +%D This is a fun project and not meant for production (yet). It's a follow up on a +%D project by Taco presented at a Bacho\TeX\ meeting years ago. I probably messed up +%D the code so much that some things don't work but then, fonts are not really +%D supported well anyway. However for simple \POSTSCRIPT\ things work out ok. +%D +%D I (Hans) will occasionally have a look at the code. Who knows what our trips to +%D \TeX\ meetings lead to. + +\unprotect + +\unexpanded\def\startESCRITOgraphic#1#2#3#4% + {\dontleavehmode + \begingroup + \MPllx#1\onebasepoint + \MPlly#2\onebasepoint + \MPurx#3\onebasepoint + \MPury#4\onebasepoint + \setbox\b_meta_graphic\hbox\bgroup} + +\unexpanded\def\stopESCRITOgraphic + {\egroup + \setbox\b_meta_graphic\ruledhbox\bgroup + \kern-\MPllx\raise-\MPlly\box\b_meta_graphic + \egroup + \wd\b_meta_graphic\dimexpr\MPurx-\MPllx\relax + \ht\b_meta_graphic\dimexpr\MPury-\MPlly\relax + \dp\b_meta_graphic\zeropoint + \box\b_meta_graphic + \endgroup} + +\unexpanded\def\flushESCRITOtext#1#2#3% no fratures so pretty weak, better use overlays + {\smash{\rlap{\definedfont[#1 at #2bp]#3}}} + +\unexpanded\def\stopESCRITO + {\edef\p_option{\namedbufferparameter{ESCRITO}\c!option}% + \ctxlua{escrito.convert { + buffer = "\thedefinedbuffer{ESCRITO}", + calculatebox = \ifx\p_option\v!fit true\else false\fi, + }}} + +\unexpanded\def\processESCRITO[#1]% + {\begingroup + \getdummyparameters[\c!file=,\c!option=,#1]% + \edef\p_option{\dummyparameter\c!option}% + \ctxlua{escrito.convert { + filename = "\dummyparameter\c!file", + calculatebox = \ifx\p_option\v!fit true\else false\fi, + }}% + \endgroup} + +\definebuffer + [ESCRITO] + +\setupbuffer + [ESCRITO] + [\c!option=, + \c!after=\processESCRITO] + +\protect + +% This will move to m-escrito.lua once we know how to deal with it ... no time +% now. + +\startluacode + + local literal = nodes.pool.register(node.new("whatsit",nodes.whatsitcodes.pdfliteral)) + literal.mode = 0 + + local function newliteral(result) + local l = nodes.copy(literal) + l.data = result + return l + end + + local p = escrito.devices.pdf + + function p.startpage(llx,lly,urx,ury) + context.startESCRITOgraphic(llx,lly,urx,ury) + end + + function p.stoppage() + context.stopESCRITOgraphic() + end + + function p.flushpage(result) + context(newliteral(result)) + end + + -- todo + + local fontnames = { } + local fontfiles = { } + + fontnames['NimbusSanL-Regu'] = 'Sans' + fontnames['StandardSymL'] = 'rpsyr' + fontnames['dejavuserif-regular'] = 'dejavuserif-regular' + + function p.showfont(object) + local color = object.color + local ctype = object.colortype + local matrix = object.matrix + local text = object.string + local size = object.fontmatrix[1] * 1000 + local result = { "q" } + context(newliteral(formatters['%f %f %f %f %f %f cm'](matrix[1],matrix[2],matrix[3],matrix[4],matrix[5],matrix[6]))) + if ctype == "rgb" then + local r, g, b = color[1], color[2], color[3] + context(newliteral(formatters["%f %f %f rg %f %f %f RG"](r,g,b,r,g,b))) + elseif ctype == "cmyk" then + local c, m, y, k = color[1], color[2], color[3], color[4] + context(newliteral(formatters["%f %f %f k %f %f %f K"](c,m,y,k,c,m,y,k))) + elseif ctype == "gray" then + context(newliteral(formatters["%f g %f G"](color,color))) + end + context.flushESCRITOtext(fontnames[object.fontname],size,text) + context(newliteral("Q")) + end + + local function findfont(fontname) + return fontfiles[fontname] + end + +\stopluacode + +\continueifinputfile{m-escrito.mkiv} + +\starttext + +% \startluacode +% local n = 5 +% for i=1,n do +% context.startTEXpage() +% local runtime = escrito.convert { filename = "tiger.eps", calculatebox = true } +% context.par() +% context("calculated boundingbox, time: %s",runtime) +% context.stopTEXpage() +% end +% for i=1,n do +% context.startTEXpage() +% local runtime = escrito.convert { filename = "tiger.eps", calculatebox = false } +% context.par() +% context("built in boundingbox, time: %s",runtime) +% context.stopTEXpage() +% end +% \stopluacode + +\startTEXpage + \startESCRITO + (tiger.eps) run + \stopESCRITO +\stopTEXpage + +\startTEXpage + \setupbuffer[ESCRITO][option=fit]% + \startESCRITO + (tiger.eps) run + \stopESCRITO +\stopTEXpage + +\startTEXpage + \processESCRITO[file=tiger.eps] +\stopTEXpage + +\startTEXpage + \processESCRITO[file=tiger.eps,option=fit] +\stopTEXpage + +\stoptext + diff --git a/tex/context/base/m-newotf.mkiv b/tex/context/base/m-newotf.mkiv index 6b9a5fb14..84f458ac1 100644 --- a/tex/context/base/m-newotf.mkiv +++ b/tex/context/base/m-newotf.mkiv @@ -11,7 +11,7 @@ %C therefore copyrighted by \PRAGMA. See mreadme.pdf for %C details. -\endinput +% \endinput %D This module will go away as soon as we use the new loader code by default. %D That will happen after extensive testing. Generic support will happen after diff --git a/tex/context/base/m-punk.mkiv b/tex/context/base/m-punk.mkiv index c8021a92f..331e90d2e 100644 --- a/tex/context/base/m-punk.mkiv +++ b/tex/context/base/m-punk.mkiv @@ -162,7 +162,6 @@ function fonts.handlers.vf.combiner.commands.metafont(g,v) end g.properties.virtualized = true g.variants = list - print(g) end fonts.definers.methods.install( "punk", { diff --git a/tex/context/base/math-def.mkiv b/tex/context/base/math-def.mkiv index 8247ac008..7337bae4b 100644 --- a/tex/context/base/math-def.mkiv +++ b/tex/context/base/math-def.mkiv @@ -148,7 +148,7 @@ \definemathcommand [equalscoloncolon] [rel] {=\coloncolon\colonsep} \definemathcommand [coloncolonapprox] [rel] {\coloncolon\colonsep\approx} \definemathcommand [approxcoloncolon] [rel] {\approx\coloncolon\colonsep} -\definemathcommand [colonsim] [rel] {\coloncolon\colonsep\sim} +\definemathcommand [coloncolonsim] [rel] {\coloncolon\colonsep\sim} \definemathcommand [simcoloncolon] [rel] {\sim\coloncolon\colonsep} % \appendtoks diff --git a/tex/context/base/math-fen.mkiv b/tex/context/base/math-fen.mkiv index d754bd672..b0c90290d 100644 --- a/tex/context/base/math-fen.mkiv +++ b/tex/context/base/math-fen.mkiv @@ -108,7 +108,8 @@ #2% \math_fenced_right \stopusemathstyleparameter - \endgroup} + \endgroup + \advance\c_math_fenced_nesting\minusone} \appendtoks \let\fenced\math_fenced_fenced @@ -240,16 +241,19 @@ \unexpanded\def\lfence#1% {\settrue\c_math_fenced_done + \let\nexttoken#1% \edef\m_math_left{\meaning#1}% \csname\??mathleft\ifcsname\??mathleft\m_math_left\endcsname\m_math_left\else\s!unknown\fi\endcsname} \unexpanded\def\rfence#1% {\settrue\c_math_fenced_done + \let\nexttoken#1% \edef\m_math_right{\meaning#1}% \csname\??mathright\ifcsname\??mathright\m_math_right\endcsname\m_math_right\else\s!unknown\fi\endcsname} \unexpanded\def\mfence#1% {\settrue\c_math_fenced_done + \let\nexttoken#1% \edef\m_math_middle{\meaning#1}% \csname\??mathmiddle\ifcsname\??mathmiddle\m_math_middle\endcsname\m_math_middle\else\s!unknown\fi\endcsname} @@ -327,11 +331,11 @@ \installmathfencepair \lfloor \Lfloor \rfloor \Rfloor \installmathfencepair \lceil \Lceil \rceil \Rceil -\installmathfencepair \ulcorner \Luppercorner \ulcorner \Ruppercorner -\installmathfencepair \llcorner \Llowercorner \llcorner \Rlowercorner -\installmathfencepair \lmoustache \Lmoustache \lmoustache \Rmoustache -\installmathfencepair \llbracket \Lopenbracket \llbracket \Ropenbracket -\installmathfencepair \lgroup \Lgroup \lgroup \Rgroup +\installmathfencepair \ulcorner \Luppercorner \urcorner \Ruppercorner +\installmathfencepair \llcorner \Llowercorner \lrcorner \Rlowercorner +\installmathfencepair \lmoustache \Lmoustache \rmoustache \Rmoustache +\installmathfencepair \llbracket \Lopenbracket \lrbracket \Ropenbracket +\installmathfencepair \lgroup \Lgroup \rgroup \Rgroup % \setupmathfences[color=darkgreen] diff --git a/tex/context/base/math-stc.mkvi b/tex/context/base/math-stc.mkvi index a879d157f..80d698c77 100644 --- a/tex/context/base/math-stc.mkvi +++ b/tex/context/base/math-stc.mkvi @@ -642,13 +642,17 @@ \unexpanded\def\mathdouble{\begingroup\dodoubleempty\math_stackers_handle_double} \def\math_stackers_handle_over[#category]% - {\math_stackers_direct_double\plusone\zerocount{\iffirstargument#category\else\v!top \fi}} % will be defined later on + {\math_stackers_direct_double\plusone\zerocount + {\iffirstargument#category\else\v!top \fi}} % will be defined later on \def\math_stackers_handle_under[#category]% - {\math_stackers_direct_double\zerocount\plusone{\iffirstargument#category\else\v!bottom\fi}} % will be defined later on + {\math_stackers_direct_double\zerocount\plusone + {\iffirstargument#category\else\v!bottom\fi}} % will be defined later on -\def\math_stackers_handle_double[#category]% - {\math_stackers_direct_double\plusone\plusone {\iffirstargument#category\else\v!bottom\fi}} % will be defined later on +\def\math_stackers_handle_double[#topcategory][#bottomcategory]% + {\math_stackers_direct_double\plusone\plusone + {\iffirstargument #topcategory\else\v!top \fi}% + {\ifsecondargument#bottomcategory\else\v!bottom\fi}} \def\math_stackers_direct_double#top#bottom#category#codepoint#text% {\math_stackers_make_double#top#bottom{#category}{#codepoint}{0}{#text}% @@ -1236,9 +1240,9 @@ %D Extra: -\unexpanded\edef\singlebond{\mathematics{\mathsurround\zeropoint\char\number"002D}} -\unexpanded\edef\doublebond{\mathematics{\mathsurround\zeropoint\char\number"003D}} -\unexpanded\edef\triplebond{\mathematics{\mathsurround\zeropoint\char\number"2261}} +\unexpanded\edef\singlebond{\mathematics{\mathsurround\zeropoint\char\number"002D\relax}} +\unexpanded\edef\doublebond{\mathematics{\mathsurround\zeropoint\char\number"003D\relax}} +\unexpanded\edef\triplebond{\mathematics{\mathsurround\zeropoint\char\number"2261\relax}} % \mathchardef\singlebond"002D % \mathchardef\doublebond"003D @@ -1261,13 +1265,13 @@ \edef\currentmathstackers{#category}% \edef\p_moffset{\mathstackersparameter\c!moffset}% \ifx\p_moffset\empty \else - \mskip\scratchmuskip + \mskip\p_moffset\relax \fi \ifmmode\math_class_by_parameter\mathstackersparameter\else\dontleavehmode\fi {\usemathstackerscolorparameter\c!color \Umathchar\zerocount\defaultmathfamily#codepoint}% \ifx\p_moffset\empty \else - \mskip\scratchmuskip + \mskip\p_moffset\relax \fi \endgroup} diff --git a/tex/context/base/meta-ini.mkiv b/tex/context/base/meta-ini.mkiv index 299f37cef..ee697d03a 100644 --- a/tex/context/base/meta-ini.mkiv +++ b/tex/context/base/meta-ini.mkiv @@ -990,7 +990,7 @@ {\clf_mptexreset} \unexpanded\def\useMPenvironmentbuffer[#1]% - {\clf_mpsetfrombuffer{#1}} + {\clf_mptexsetfrombuffer{#1}} %D This command takes \type {[reset]} as optional %D argument. diff --git a/tex/context/base/mlib-ctx.lua b/tex/context/base/mlib-ctx.lua index b437e1212..3fe7118b7 100644 --- a/tex/context/base/mlib-ctx.lua +++ b/tex/context/base/mlib-ctx.lua @@ -341,7 +341,7 @@ function mptex.set(str) end function mptex.setfrombuffer(name) - environments[#environments+1] = buffers.content(name) + environments[#environments+1] = buffers.getcontent(name) end function mptex.get() diff --git a/tex/context/base/mult-prm.lua b/tex/context/base/mult-prm.lua index 29bdc870f..bf61683ee 100644 --- a/tex/context/base/mult-prm.lua +++ b/tex/context/base/mult-prm.lua @@ -212,52 +212,84 @@ return { "Usubscript", "Usuperscript", "Uunderdelimiter", + "adjustspacing", "alignmark", "aligntab", "attribute", "attributedef", + "bodydir", + "boxdir", "catcodetable", "clearmarks", "crampeddisplaystyle", "crampedscriptscriptstyle", "crampedscriptstyle", "crampedtextstyle", + "efcode", "fontid", "formatname", "gleaders", + "hyphenationmin", "ifabsdim", "ifabsnum", "ifprimitive", + "ignoreligaturesinfont", "initcatcodetable", + "lastxpos", + "lastypos", "latelua", + "leftghost", + "leftmarginkern", + "letterspacefont", + "localbrokenpenalty", + "localinterlinepenalty", + "localleftbox", + "localrightbox", + "lpcode", "luaescapestring", "luastartup", "luatexbanner", "luatexrevision", "luatexversion", "luafunction", + "mathdir", + "matheqnogapstep", "mathstyle", "nokerns", "noligs", + "normaldeviate", "outputbox", + "pagedir", + "pageheight", + "pagebottomoffset", "pageleftoffset", + "pagerightoffset", "pagetopoffset", + "pagewidth", + "pardir", "postexhyphenchar", "posthyphenchar", "preexhyphenchar", "prehyphenchar", - "hyphenationmin", - "discpenalty", "primitive", + "protrudechars", + "randomseed", + "rightghost", + "rightmarginkern", + "rpcode", "savecatcodetable", + "savepos", "scantextokens", + "setrandomseed", "suppressfontnotfounderror", "suppressifcsnameerror", "suppresslongerror", "suppressoutererror", "suppressmathparerror", - "matheqnogapstep", "synctex", + "tagcode", + "textdir", + "uniformdeviate", }, ["omega"]={ "OmegaVersion", @@ -308,11 +340,8 @@ return { "pdfdest", "pdfdestmargin", "pdfdraftmode", - "pdfeachlinedepth", - "pdfeachlineheight", "pdfendlink", "pdfendthread", - "pdffirstlineheight", "pdffontattr", "pdffontexpand", "pdffontname", @@ -333,7 +362,6 @@ return { "pdfinfo", "pdfinsertht", "pdflastannot", - "pdflastlinedepth", "pdflastlink", "pdflastobj", "pdflastxform", @@ -659,6 +687,7 @@ return { "exhyphenpenalty", "expandafter", "expanded", + "expandglyphsinfont", "fam", "fi", "finalhyphendemerits", @@ -731,6 +760,7 @@ return { "ifvmode", "ifvoid", "ifx", + "ignoreligaturesinfont", "ignorespaces", "immediate", "indent", @@ -751,6 +781,8 @@ return { "lastnodetype", "lastpenalty", "lastskip", + "lastxpos", + "lastypos", "latelua", "lccode", "leaders", @@ -827,6 +859,7 @@ return { "nolocalwhatsits", "nonscript", "nonstopmode", + "normaldeviate", "nulldelimiterspace", "nullfont", "number", @@ -889,11 +922,8 @@ return { "pdfdest", "pdfdestmargin", "pdfdraftmode", - "pdfeachlinedepth", - "pdfeachlineheight", "pdfendlink", "pdfendthread", - "pdffirstlineheight", "pdffontattr", "pdffontexpand", "pdffontname", @@ -914,7 +944,6 @@ return { "pdfinfo", "pdfinsertht", "pdflastannot", - "pdflastlinedepth", "pdflastlink", "pdflastobj", "pdflastxform", @@ -994,6 +1023,7 @@ return { "quitvmode", "radical", "raise", + "randomseed", "read", "readline", "relax", @@ -1005,6 +1035,7 @@ return { "rightskip", "romannumeral", "rpcode", + "savepos", "savecatcodetable", "savinghyphcodes", "savingvdiscards", @@ -1018,6 +1049,7 @@ return { "scrollmode", "setbox", "setlanguage", + "setrandomseed", "sfcode", "shipout", "show", @@ -1085,6 +1117,7 @@ return { "unexpanded", "unhbox", "unhcopy", + "uniformdeviate", "unkern", "unless", "unpenalty", diff --git a/tex/context/base/node-aux.lua b/tex/context/base/node-aux.lua index d6f798083..ec408c71a 100644 --- a/tex/context/base/node-aux.lua +++ b/tex/context/base/node-aux.lua @@ -556,19 +556,3 @@ end -- end -- end -function nuts.effectiveglue(glue,parent) - local spec = getfield(glue,"spec") - local width = getfield(spec,"width") - local sign = getfield(parent,"glue_sign") - if sign == 1 then - if getfield(spec,"stretch_order") == getfield(parent,"glue_order") then - return width + getfield(spec,"stretch") * getfield(parent,"glue_set") - end - elseif sign == 2 then - if getfield(spec,"shrink_order") == getfield(parent,"glue_order") then - return width - getfield(spec,"shrink") * getfield(parent,"glue_set") - end - end - return width -end - diff --git a/tex/context/base/node-fnt.lua b/tex/context/base/node-fnt.lua index ae2f8831c..352529dab 100644 --- a/tex/context/base/node-fnt.lua +++ b/tex/context/base/node-fnt.lua @@ -142,7 +142,7 @@ function fonts.getdiscexpansion() return expanders and true or false end --- fonts.setdiscexpansion(true) +fonts.setdiscexpansion(true) function handlers.characters(head) -- either next or not, but definitely no already processed list diff --git a/tex/context/base/node-ltp.lua b/tex/context/base/node-ltp.lua index 6de0a1547..3c539bcfa 100644 --- a/tex/context/base/node-ltp.lua +++ b/tex/context/base/node-ltp.lua @@ -286,6 +286,7 @@ local infinite_penalty = 10000 local eject_penalty = -10000 local infinite_badness = 10000 local awful_badness = 0x3FFFFFFF +local ignore_depth = -65536000 local fit_very_loose_class = 0 -- fitness for lines stretching more than their stretchability local fit_loose_class = 1 -- fitness for lines stretching 0.5 to 1.0 of their stretchability @@ -799,7 +800,8 @@ end local function append_to_vlist(par, b) local prev_depth = par.prev_depth - if prev_depth > par.ignored_dimen then + -- if prev_depth > par.ignored_dimen then + if prev_depth > ignore_depth then if getid(b) == hlist_code then local d = getfield(par.baseline_skip,"width") - prev_depth - getfield(b,"height") -- deficiency of space between baselines local s = d < par.line_skip_limit and new_lineskip(par.lineskip) or new_baselineskip(d) @@ -866,8 +868,8 @@ local function initialize_line_break(head,display) local right_skip = tonut(tex.rightskip) -- nodes local pretolerance = tex.pretolerance local tolerance = tex.tolerance - local adjust_spacing = tex.pdfadjustspacing - local protrude_chars = tex.pdfprotrudechars + local adjust_spacing = tex.adjustspacing + local protrude_chars = tex.protrudechars local last_line_fit = tex.lastlinefit local newhead = new_temp() @@ -932,11 +934,12 @@ local function initialize_line_break(head,display) first_line = 0, -- tex.nest[tex.nest.ptr].modeline, -- 0, -- cur_list.pg_field - each_line_height = tex.pdfeachlineheight or 0, -- this will go away - each_line_depth = tex.pdfeachlinedepth or 0, -- this will go away - first_line_height = tex.pdffirstlineheight or 0, -- this will go away - last_line_depth = tex.pdflastlinedepth or 0, -- this will go away - ignored_dimen = tex.pdfignoreddimen or 0, -- this will go away + -- each_line_height = tex.pdfeachlineheight or 0, -- this will go away + -- each_line_depth = tex.pdfeachlinedepth or 0, -- this will go away + -- first_line_height = tex.pdffirstlineheight or 0, -- this will go away + -- last_line_depth = tex.pdflastlinedepth or 0, -- this will go away + + -- ignored_dimen = tex.pdfignoreddimen or 0, baseline_skip = tonut(tex.baselineskip), lineskip = tonut(tex.lineskip), @@ -1144,7 +1147,7 @@ local function post_line_break(par) local leftskip = par.used_left_skip -- used or normal ? local rightskip = par.right_skip local parshape = par.par_shape_ptr - local ignored_dimen = par.ignored_dimen + ----- ignored_dimen = par.ignored_dimen local adapt_width = par.adapt_width @@ -1359,20 +1362,22 @@ local function post_line_break(par) local pre_adjust_head = texlists.pre_adjust_head -- setfield(finished_line,"shift",cur_indent) - -- this will probably go away: - if par.each_line_height ~= ignored_dimen then - setfield(finished_line,"height",par.each_line_height) - end - if par.each_line_depth ~= ignored_dimen then - setfield(finished_line,"depth",par.each_line_depth) - end - if par.first_line_height ~= ignored_dimen and (current_line == par.first_line + 1) then - setfield(finished_line,"height",par.first_line_height) - end - if par.last_line_depth ~= ignored_dimen and current_line + 1 == par.best_line then - setfield(finished_line,"depth",par.last_line_depth) - end - -- + -- + -- -- this is gone: + -- + -- if par.each_line_height ~= ignored_dimen then + -- setfield(finished_line,"height",par.each_line_height) + -- end + -- if par.each_line_depth ~= ignored_dimen then + -- setfield(finished_line,"depth",par.each_line_depth) + -- end + -- if par.first_line_height ~= ignored_dimen and (current_line == par.first_line + 1) then + -- setfield(finished_line,"height",par.first_line_height) + -- end + -- if par.last_line_depth ~= ignored_dimen and current_line + 1 == par.best_line then + -- setfield(finished_line,"depth",par.last_line_depth) + -- end + -- if texlists.pre_adjust_head ~= pre_adjust_head then append_list(par, texlists.pre_adjust_head) texlists.pre_adjust_head = pre_adjust_head @@ -1428,10 +1433,10 @@ local function post_line_break(par) elseif id < math_code then -- messy criterium break -elseif id == math_code then - -- keep the math node - setfield(next,"surround",0) - break + elseif id == math_code then + -- keep the math node + setfield(next,"surround",0) + break elseif id == kern_code and (subtype ~= userkern_code and not getattr(next,a_fontkern)) then -- fontkerns and accent kerns as well as otf injections break @@ -2241,19 +2246,9 @@ function constructors.methods.basic(head,d) local line_break_dir = par.line_break_dir if second_pass or subtype <= automatic_disc_code then local actual_pen = subtype == automatic_disc_code and par.ex_hyphen_penalty or par.hyphen_penalty - -- > 0.81 - -- local actual_pen = getfield(current,"penalty") - -- if actual_pen == 0 then - -- -- take that one - -- elseif subtype == automatic_disc_code then - -- actual_pen = par.ex_hyphen_penalty - -- else - -- actual_pen = par.hyphen_penalty - -- end + -- 0.81 : + -- local actual_pen = getfield(current,"penalty") -- - if discpenalty > 0 then - actual_pen = discpenalty - end local pre = getfield(current,"pre") if not pre then -- trivial pre-break disc_width.size = 0 @@ -2297,7 +2292,7 @@ function constructors.methods.basic(head,d) -- do_one_seven_eight(sub_disc_width_from_active_width); -- do_one_seven_eight(reset_disc_width); -- s = vlink_no_break(vlink(current)); - -- add_to_widths(s, line_break_dir, pdf_adjust_spacing,disc_width); + -- add_to_widths(s, line_break_dir, adjust_spacing,disc_width); -- ext_try_break(...,first_p,vlink(current)); -- else diff --git a/tex/context/base/node-met.lua b/tex/context/base/node-met.lua index 335ce2a98..584f3bc93 100644 --- a/tex/context/base/node-met.lua +++ b/tex/context/base/node-met.lua @@ -60,13 +60,11 @@ end -- We start with some helpers and provide all relevant basic functions in the -- node namespace as well. -local gonuts = type(node.direct) == "table" --- local gonuts = false - nodes = nodes or { } local nodes = nodes -nodes.gonuts = gonuts +----- gonuts = type(node.direct) == "table" +-----.gonuts = gonuts local nodecodes = nodes.nodecodes local hlist_code = nodecodes.hlist @@ -115,16 +113,16 @@ nodes.kerning = node.kerning nodes.ligaturing = node.ligaturing nodes.mlist_to_hlist = node.mlist_to_hlist -if not gonuts or not node.getfield then - node.getfield = metatable.__index - node.setfield = metatable.__newindex -end +nodes.effective_glue = node.effective_glue --- if gonuts then - nodes.tonode = function(n) return n end - nodes.tonut = function(n) return n end +-- if not gonuts or not node.getfield then +-- node.getfield = metatable.__index +-- node.setfield = metatable.__newindex -- end +nodes.tonode = function(n) return n end +nodes.tonut = function(n) return n end + local getfield = node.getfield local setfield = node.setfield @@ -354,24 +352,24 @@ function nodes.free_spec(old) end end -if gonuts then - - function nodes.reference(n) - return n and tonut(n) or "<none>" - end - -else - - local left, space = lpeg.P("<"), lpeg.P(" ") - - local reference = left * (1-left)^0 * left * space^0 * lpeg.C((1-space)^0) - - function nodes.reference(n) - return n and lpegmatch(reference,tostring(n)) or "<none>" - end +-- if gonuts then +function nodes.reference(n) + return n and tonut(n) or "<none>" end +-- else +-- +-- local left, space = lpeg.P("<"), lpeg.P(" ") +-- +-- local reference = left * (1-left)^0 * left * space^0 * lpeg.C((1-space)^0) +-- +-- function nodes.reference(n) +-- return n and lpegmatch(reference,tostring(n)) or "<none>" +-- end +-- +-- end + -- Here starts an experiment with metatables. Of course this only works with nodes -- wrapped in userdata with a metatable. -- @@ -619,23 +617,23 @@ end -- see node-nut.lua for more info on going nuts -if not gonuts then - - local nuts = { } - nodes.nuts = nuts - - local function dummy(f) return f end - - nodes.vianuts = dummy - nodes.vianodes = dummy - - for k, v in next, nodes do - if type(v) == "function" then - nuts[k] = v - end - end - -end +-- if not gonuts then +-- +-- local nuts = { } +-- nodes.nuts = nuts +-- +-- local function dummy(f) return f end +-- +-- nodes.vianuts = dummy +-- nodes.vianodes = dummy +-- +-- for k, v in next, nodes do +-- if type(v) == "function" then +-- nuts[k] = v +-- end +-- end +-- +-- end -- also handy diff --git a/tex/context/base/node-nut.lua b/tex/context/base/node-nut.lua index 14ee29a45..ef46d4d13 100644 --- a/tex/context/base/node-nut.lua +++ b/tex/context/base/node-nut.lua @@ -89,20 +89,10 @@ if not modules then modules = { } end modules ['node-met'] = { local type, rawget = type, rawget local nodes = nodes -local gonuts = nodes.gonuts local direct = node.direct local fastcopy = table.fastcopy --- if type(direct) ~= "table" then --- return --- elseif gonuts then --- statistics.register("running in nuts mode", function() return "yes" end) --- else --- statistics.register("running in nuts mode", function() return "no" end) --- return --- end - local texget = tex.get local nodecodes = nodes.nodecodes @@ -208,6 +198,32 @@ nuts.unset_attribute = direct.unset_attribute nuts.protect_glyphs = direct.protect_glyphs nuts.unprotect_glyphs = direct.unprotect_glyphs +nuts.effective_glue = direct.effective_glue + +if not nuts.effective_glue then + + local getfield = nuts.getfield + + function nuts.effective_glue(glue,parent) + local spec = getfield(glue,"spec") + local width = getfield(spec,"width") + if parent then + local sign = getfield(parent,"glue_sign") + if sign == 1 then + if getfield(spec,"stretch_order") == getfield(parent,"glue_order") then + return width + getfield(spec,"stretch") * getfield(parent,"glue_set") + end + elseif sign == 2 then + if getfield(spec,"shrink_order") == getfield(parent,"glue_order") then + return width - getfield(spec,"shrink") * getfield(parent,"glue_set") + end + end + end + return width + end + +end + -- placeholders if not direct.kerning then @@ -276,13 +292,16 @@ if not direct.getdisc then -- this one is more efficient than three assignments and we need to -- do it in order to updat ethe internal tail data (will change) - function direct.setdisc(n,pre,post,replace,subtype) + function direct.setdisc(n,pre,post,replace,subtype,penalty) setfield(n,"pre",pre) setfield(n,"post",post) setfield(n,"replace",replace) if subtype then setfield(n,"subtype",subtype) end + if penalty then + -- setfield(n,"penalty",penalty) + end end -- very small speedup but more convenient diff --git a/tex/context/base/node-shp.lua b/tex/context/base/node-shp.lua index 42cc83b8f..af13973c0 100644 --- a/tex/context/base/node-shp.lua +++ b/tex/context/base/node-shp.lua @@ -54,9 +54,10 @@ local removables = { [whatsitcodes.open] = true, [whatsitcodes.close] = true, [whatsitcodes.write] = true, - [whatsitcodes.pdfdest] = true, - [whatsitcodes.pdfsavepos] = true, + [whatsitcodes.savepos or + whatsitcodes.pdfsavepos] = true, -- for now [whatsitcodes.latelua] = true, + [whatsitcodes.pdfdest] = true, } -- About 10% of the nodes make no sense for the backend. By (at least) diff --git a/tex/context/base/pack-box.mkiv b/tex/context/base/pack-box.mkiv index 690c5a663..c5f3940f5 100644 --- a/tex/context/base/pack-box.mkiv +++ b/tex/context/base/pack-box.mkiv @@ -391,6 +391,7 @@ \unexpanded\def\placelayeredtext[#1]% {\bgroup \edef\currentlayeredtext{#1}% + \checklayeredtextparent % bonus \dodoubleempty\pack_layeredtexts_place} \def\pack_layeredtexts_place[#1][#2]#3% layersettings content(framed)settings content diff --git a/tex/context/base/pack-rul.mkiv b/tex/context/base/pack-rul.mkiv index af6841288..e9da069c2 100644 --- a/tex/context/base/pack-rul.mkiv +++ b/tex/context/base/pack-rul.mkiv @@ -962,7 +962,7 @@ \ifx\p_framed_region\v!yes % maybe later named \pack_framed_add_region \fi - \getparameters[\currentframed][#3]% no \expanded ! + \setupcurrentframed[#3]% \edef\p_framed_rulethickness{\framedparameter\c!rulethickness}% also used in backgrounds \d_framed_frameoffset\framedparameter\c!frameoffset\relax % also used in backgrounds \edef\p_framed_frame{\framedparameter\c!frame}% diff --git a/tex/context/base/publ-aut.lua b/tex/context/base/publ-aut.lua index 5a9d48551..5ed25f288 100644 --- a/tex/context/base/publ-aut.lua +++ b/tex/context/base/publ-aut.lua @@ -734,7 +734,7 @@ authorhashers.short = function(authors) else local s = surnames[1] local c = lpegmatch(p_clean,s) - if s ~= c then + if trace_hashing and s ~= c then report_cite("name %a cleaned to %a for short construction",s,c) end return utfsub(c,1,3) diff --git a/tex/context/base/publ-dat.lua b/tex/context/base/publ-dat.lua index e9cf22604..b34a99bc8 100644 --- a/tex/context/base/publ-dat.lua +++ b/tex/context/base/publ-dat.lua @@ -908,12 +908,16 @@ do return v end) + local done = setmetatableindex("table") + function publications.load(specification) - local current = datasets[specification.dataset or v_default] + local name = specification.dataset or v_default + local current = datasets[name] local files = settings_to_array(specification.filename) local kind = specification.kind local dataspec = specification.specification statistics.starttiming(publications) + local somedone = false for i=1,#files do local filetype, filename = string.splitup(files[i],"::") if not filename then @@ -927,7 +931,13 @@ do if file.suffix(filename) == "" then file.addsuffix(filename,filetype) end - loaders[filetype](current,filename) + if done[current][filename] then + report("file %a is already loaded in dataset %a",filename,name) + else + loaders[filetype](current,filename) + done[current][filename] = true + somedone = true + end if kind then current.loaded[current.fullname or filename] = kind end @@ -936,9 +946,11 @@ do end end end - local runner = enhancer.runner - if runner then - runner(current) + if somedone then + local runner = enhancer.runner + if runner then + runner(current) + end end statistics.stoptiming(publications) return current diff --git a/tex/context/base/publ-ini.lua b/tex/context/base/publ-ini.lua index 9db8e8505..c30f780f1 100644 --- a/tex/context/base/publ-ini.lua +++ b/tex/context/base/publ-ini.lua @@ -1081,7 +1081,7 @@ do end -- we have two suffixes: author (dependent of type) and short local kind = dataset.authorconversion or "name" - local field = "author" -- currently only author + local fields = { "author", "editor" } -- will be entry in data definition local shorts = { } local authors = { } local hasher = publications.authorhashers[kind] @@ -1101,36 +1101,42 @@ do if btxspc then -- we could act on the 3rd arg returned by getcasted but in general any string will do -- so we deal with it in the author hashers ... maybe some day ... - local author = getcasted(dataset,tag,field,specifications[btxspc]) - local kind = type(author) - if kind == "table" or kind == "string" then - if u then - u = listentry.entries.text -- hm - else - u = "0" - end - local year = tonumber(entry.year) or 9999 - local data = { tag, year, u, i } - -- authors - local hash = hasher(author) - local found = authors[hash] - if not found then - authors[hash] = { data } - else - found[#found+1] = data - end - -- shorts - local hash = shorter(author) - local short = f_short(hash,mod(year,100)) - local found = shorts[short] - if not found then - shorts[short] = { data } - else - found[#found+1] = data + local done = false + for i=1,#fields do + local field = fields[i] + local author = getcasted(dataset,tag,field,specifications[btxspc]) + local kind = type(author) + if kind == "table" or kind == "string" then + if u then + u = listentry.entries.text -- hm + else + u = "0" + end + local year = tonumber(entry.year) or 9999 + local data = { tag, year, u, i } + -- authors + local hash = hasher(author) + local found = authors[hash] + if not found then + authors[hash] = { data } + else + found[#found+1] = data + end + -- shorts + local hash = shorter(author) + local short = f_short(hash,mod(year,100)) + local found = shorts[short] + if not found then + shorts[short] = { data } + else + found[#found+1] = data + end + done = true + break end - -- - else - report("author typecast expected for field %a",field) + end + if not done then + report("unable to create short for %a, needs one of [%,t]",tag,fields) end else --- no spec so let's forget about it diff --git a/tex/context/base/publ-ini.mkiv b/tex/context/base/publ-ini.mkiv index 3ec8e7380..211c5c00e 100644 --- a/tex/context/base/publ-ini.mkiv +++ b/tex/context/base/publ-ini.mkiv @@ -165,7 +165,7 @@ \unexpanded\def\btxcheckdefine#1{\doifelsecommandhandler\??btx{#1}\gobbleoneargument{\btx_check_chain{define}{#1}}} % {#2} \unexpanded\def\btxchecksetup #1{\doifelsecommandhandler\??btx{#1}\gobbleoneargument{\btx_check_chain {setup}{#1}}} % {#2} -% fpr the moment experimental: +% for the moment experimental: \unexpanded\def\btxenableautodefine {\prependtoks @@ -475,10 +475,10 @@ \let\currentbtxbacklink \empty \let\currentbtxbacktrace\empty \let\currentbtxlanguage \empty - \let\currentbtxtag \empty \let\currentbtxsuffix \empty - \let\currentbtxnumber \empty - \let\currentbtxdataset \empty} + %\let\currentbtxdataset \empty % will always be set + %\let\currentbtxtag \empty % will always be set + \let\currentbtxnumber \empty} \unexpanded\def\btx_reset_cite % check for less .. not all resets needed when we're grouped (only subcites) {\let \currentbtxfirst \empty @@ -492,8 +492,8 @@ \let \currentbtxbacklink \empty \let \currentbtxbacktrace \empty % not used here \let \currentbtxlanguage \empty - \let \currentbtxdataset \empty - \let \currentbtxtag \empty + %\let \currentbtxdataset \empty % will always be set, beware of local reset ~ + %\let \currentbtxtag \empty % will always be set, beware of local reset ~ \let \currentbtxnumber \empty \setconstant\currentbtxoverflow \zerocount \setconstant\currentbtxconcat \zerocount @@ -1448,42 +1448,47 @@ \unexpanded\def\publ_fast_btx_setup_chain_inbetween {\allowbreak->\allowbreak} +\unexpanded\def\publ_fast_btx_setup_colon_inbetween + {\allowbreak:\allowbreak} + \unexpanded\def\publ_fast_btx_setup_chain_yes#1#2% {\dontleavehmode\begingroup + \let\:\publ_fast_btx_setup_colon_inbetween \infofont \ifcase\btxsetuptype\darkred\or\darkblue\or\darkgreen\or\darkcyan\or\darkmagenta\else\darkred\fi - [% - \currentbtxspecification :#1:#2\ifcsname\??setup:\s!btx:\currentbtxspecification :#1:#2\endcsname\else + [\prewordbreak + \currentbtxspecification \:#1\:#2\ifcsname\??setup:\s!btx:\currentbtxspecification:#1:#2\endcsname\else \publ_fast_btx_setup_chain_inbetween - \currentbtxspecificationfallback:#1:#2\ifcsname\??setup:\s!btx:\currentbtxspecificationfallback :#1:#2\endcsname\else + \currentbtxspecificationfallback\:#1\:#2\ifcsname\??setup:\s!btx:\currentbtxspecificationfallback:#1:#2\endcsname\else \publ_fast_btx_setup_chain_inbetween - #1:#2\ifcsname\??setup:\s!btx :#1:#2\endcsname\else + #1\:#2\ifcsname\??setup:\s!btx:#1:#2\endcsname\else \publ_fast_btx_setup_chain_inbetween - \currentbtxspecification :#1:\s!unknown\ifcsname\??setup:\s!btx:\currentbtxspecification :#1:\s!unknown\endcsname\else + \currentbtxspecification \:#1\:\s!unknown\ifcsname\??setup:\s!btx:\currentbtxspecification:#1:\s!unknown\endcsname\else \publ_fast_btx_setup_chain_inbetween - \currentbtxspecificationfallback:#1:\s!unknown\ifcsname\??setup:\s!btx:\currentbtxspecificationfallback:#1:\s!unknown\endcsname\else + \currentbtxspecificationfallback\:#1\:\s!unknown\ifcsname\??setup:\s!btx:\currentbtxspecificationfallback:#1:\s!unknown\endcsname\else \publ_fast_btx_setup_chain_inbetween unset\fi\fi\fi\fi\fi \space @\space \currentbtx - ]% + \prewordbreak]% \endgroup} \unexpanded\def\publ_fast_btx_setup_chain_nop#1#2% {\dontleavehmode\begingroup + \let\:\publ_fast_btx_setup_colon_inbetween \infofont \darkred - [% - \currentbtxspecification :#1:#2\ifcsname\??setup:\s!btx:\currentbtxspecification :#1:#2\endcsname\else + [\prewordbreak + \currentbtxspecification\:#1\:#2\ifcsname\??setup:\s!btx:\currentbtxspecification:#1:#2\endcsname\else \publ_fast_btx_setup_chain_inbetween - #1:#2\ifcsname\??setup:\s!btx :#1:#2\endcsname\else + #1\:#2\ifcsname\??setup:\s!btx:#1:#2\endcsname\else \publ_fast_btx_setup_chain_inbetween - \currentbtxspecification :#1:\s!unknown\ifcsname\??setup:\s!btx:\currentbtxspecification :#1:\s!unknown\endcsname\else + \currentbtxspecification\:#1\:\s!unknown\ifcsname\??setup:\s!btx:\currentbtxspecification:#1:\s!unknown\endcsname\else \publ_fast_btx_setup_chain_inbetween unset\fi\fi\fi \space @\space \currentbtx - ]% + \prewordbreak]% \endgroup} \unexpanded\def\publ_fast_btx_setup_normal#1% diff --git a/tex/context/base/publ-sor.lua b/tex/context/base/publ-sor.lua index b617af760..218d11093 100644 --- a/tex/context/base/publ-sor.lua +++ b/tex/context/base/publ-sor.lua @@ -9,31 +9,31 @@ if not modules then modules = { } end modules ['publ-sor'] = { -- if needed we can optimize this one: chekc if it's detail or something else -- and use direct access, but in practice it's fast enough -local type = type -local concat = table.concat -local formatters = string.formatters -local compare = sorters.comparers.basic -- (a,b) -local sort = table.sort - -local toarray = utilities.parsers.settings_to_array -local utfchar = utf.char - -local publications = publications -local writers = publications.writers - -local variables = interfaces.variables -local v_short = variables.short -local v_default = variables.default -local v_reference = variables.reference -local v_dataset = variables.dataset -local v_list = variables.list -local v_index = variables.index -local v_cite = variables.cite -local v_used = variables.used - -local report = logs.reporter("publications","sorters") - -local trace_sorters trackers.register("publications.sorters",function(v) trace_sorters = v end) +local type = type +local concat = table.concat +local formatters = string.formatters +local compare = sorters.comparers.basic -- (a,b) +local sort = table.sort + +local toarray = utilities.parsers.settings_to_array +local utfchar = utf.char + +local publications = publications +local writers = publications.writers + +local variables = interfaces.variables +local v_short = variables.short +local v_default = variables.default +local v_reference = variables.reference +local v_dataset = variables.dataset +local v_list = variables.list +local v_index = variables.index +local v_cite = variables.cite +local v_used = variables.used + +local report = logs.reporter("publications","sorters") + +local trace_sorters = false trackers.register("publications.sorters",function(v) trace_sorters = v end) -- authors(s) | year | journal | title | pages diff --git a/tex/context/base/spac-ver.lua b/tex/context/base/spac-ver.lua index 038bfc9f6..76f9e1df5 100644 --- a/tex/context/base/spac-ver.lua +++ b/tex/context/base/spac-ver.lua @@ -15,6 +15,9 @@ if not modules then modules = { } end modules ['spac-ver'] = { -- todo: use lua nodes with lua data (>0.79) -- see ** can go when 0.79 +-- needs to be redone, too many calls and tests now ... still within some +-- luatex limitations + -- this code dates from the beginning and is kind of experimental; it -- will be optimized and improved soon .. it's way too complex now but -- dates from less possibilities diff --git a/tex/context/base/spac-ver.mkiv b/tex/context/base/spac-ver.mkiv index 0e1b0cc89..eb5fb603e 100644 --- a/tex/context/base/spac-ver.mkiv +++ b/tex/context/base/spac-ver.mkiv @@ -2159,10 +2159,10 @@ \dp\nextbox\strutdp \scratchwidth\dimexpr\wd\nextbox+\scratchdistance\relax \ifx\m_spac_hanging_location\v!right - \hangindent-\scratchwidth + \hangindent\ifconditional\displaylefttoright-\fi\scratchwidth \rlap{\hskip\dimexpr\hsize-\wd\nextbox\relax\box\nextbox}% \else - \hangindent\scratchwidth + \hangindent\ifconditional\displaylefttoright\else-\fi\scratchwidth \llap{\box\nextbox\hskip\scratchdistance}% \fi \ignorespaces} diff --git a/tex/context/base/status-files.pdf b/tex/context/base/status-files.pdf Binary files differindex 3c6a08724..79d581f27 100644 --- a/tex/context/base/status-files.pdf +++ b/tex/context/base/status-files.pdf diff --git a/tex/context/base/status-lua.pdf b/tex/context/base/status-lua.pdf Binary files differindex 986fa8141..efb631bc8 100644 --- a/tex/context/base/status-lua.pdf +++ b/tex/context/base/status-lua.pdf diff --git a/tex/context/base/strc-lst.mkvi b/tex/context/base/strc-lst.mkvi index 24aeaae7c..c7fd41daf 100644 --- a/tex/context/base/strc-lst.mkvi +++ b/tex/context/base/strc-lst.mkvi @@ -352,7 +352,7 @@ \unexpanded\def\structurelistpagenumber {\dostarttagged\t!listpage\empty \clf_listprefixedpage - {\currentlist} + {\currentlist}% \currentlistindex { separatorset {\listparameter\c!pageprefixseparatorset} @@ -360,13 +360,13 @@ set {\listparameter\c!pageprefixset} segments {\listparameter\c!pageprefixsegments} connector {\listparameter\c!pageprefixconnector} - } + }% { prefix {\listparameter\c!pageprefix} conversionset {\listparameter\c!pageconversionset} starter {\listparameter\c!pagestarter} stopper {\listparameter\c!pagestopper} - } + }% \relax \dostoptagged} diff --git a/tex/context/base/strc-reg.mkiv b/tex/context/base/strc-reg.mkiv index 138a1486f..e3c92ae37 100644 --- a/tex/context/base/strc-reg.mkiv +++ b/tex/context/base/strc-reg.mkiv @@ -108,7 +108,6 @@ \c!entries=, \c!alternative=] - \definemixedcolumns [\v!register] [\c!n=\registerparameter\c!n, diff --git a/tex/context/base/syst-ini.mkiv b/tex/context/base/syst-ini.mkiv index 83cb00b9f..8633e3272 100644 --- a/tex/context/base/syst-ini.mkiv +++ b/tex/context/base/syst-ini.mkiv @@ -98,13 +98,16 @@ local etex = extraprimitives("etex") local pdftex = extraprimitives("pdftex") local luatex = extraprimitives("luatex") - local omega = { + local omega = { % now luatex + "pagewidth", "pageheight", "textdir", "pagedir", "mathdir", "pardir", "bodydir", - "leftghost", "rightghost", "localleftbox", "localrightbox", + "leftghost", "rightghost", + "localleftbox", "localrightbox", "localinterlinepenalty", "localbrokenpenalty", } - local aleph = { - "boxdir", "pagebottomoffset", "pagerightoffset", + local aleph = { % now luatex + "boxdir", + "pagebottomoffset", "pagerightoffset", } for _, subset in next, { etex, pdftex, luatex, omega, aleph } do enableprimitives("",subset) @@ -114,6 +117,14 @@ end } +% for the moment: + +\ifdefined\pagewidth \else \let\pagewidth \pdfpagewidth \let\normalpagewidth \pdfpagewidth \fi +\ifdefined\pageheight \else \let\pageheight \pdfpageheight \let\normalpageheight \pdfpageheight \fi + +\ifdefined\adjustspacing \else \let\adjustspacing\pdfadjustspacing \let\normaladjustspacing\adjustspacing \fi +\ifdefined\protrudechars \else \let\protrudechars\pdfprotrudechars \let\normalprotrudechars\protrudechars \fi + %D \ETEX\ has a not so handy way of telling you the version number, i.e. the revision %D number has a period in it: @@ -986,34 +997,6 @@ \expandafter\let\csname#1\expandafter\endcsname\csname#2\endcsname \fi \fi} -%D Because \XETEX\ also implements some \PDFTEX\ functionality, we take care of this -%D here instead of a dedicated module. Later modules need to handle the undefined -%D cases. - -%D These messy checks will disappear. - -% new after 1.10, watch the change in prefix - -% \bindprimitive quitvmode ptexquitvmode -% \bindprimitive noligatures ptexnoligatures -% \bindprimitive setrandomseed ptexsetrandomseed -% \bindprimitive uniformdeviate ptexuniformdeviate - -% \bindprimitive quitvmode pdfquitvmode -% \bindprimitive noligatures pdfnoligatures -% \bindprimitive setrandomseed pdfsetrandomseed -% \bindprimitive uniformdeviate pdfuniformdeviate - -% \bindprimitive resettimer pdfresettimer -% \bindprimitive elapsedtime pdfelapsedtime - -% new per 1.40 - -% \bindprimitive ifprimitive ifpdfprimitive -% \bindprimitive primitive pdfprimitive -% \bindprimitive ifabsdim ifpdfabsdim -% \bindprimitive ifabsnum ifpdfabsnum - %D We need to make sure that we start up in \DVI\ mode, so, after testing for running %D \PDFTEX, we default to \DVI. Why? @@ -1034,8 +1017,9 @@ \normalpdfcompression -\let\normalsetrandomseed \setrandomseed -\let\normaluniformdeviate\uniformdeviate +% \let\normalsetrandomseed \setrandomseed +% \let\normaluniformdeviate\uniformdeviate +% \let\normalnormaldeviate \normaldeviate %D Basic status stuff. diff --git a/tex/context/base/trac-vis.lua b/tex/context/base/trac-vis.lua index 47dcdd825..2763b5d6d 100644 --- a/tex/context/base/trac-vis.lua +++ b/tex/context/base/trac-vis.lua @@ -91,6 +91,8 @@ local insert_node_after = nuts.insert_after local traverse_nodes = nuts.traverse local linked_nodes = nuts.linked +local effectiveglue = nuts.effective_glue + local fast_hpack = nuts.fasthpack local fast_hpack_string = nuts.typesetters.fast_hpack @@ -394,6 +396,7 @@ local tags = { special = "SPE", localpar = "PAR", dir = "DIR", + savepos = "POS", pdfliteral = "PDF", pdfrefobj = "PDF", pdfrefxform = "PDF", @@ -405,7 +408,7 @@ local tags = { pdfthread = "PDF", pdfstartthread = "PDF", pdfendthread = "PDF", - pdfsavepos = "PDF", + pdfsavepos = "PDF", -- obsolete pdfthreaddata = "PDF", pdflinkdata = "PDF", pdfcolorstack = "PDF", @@ -694,12 +697,13 @@ local tags = { -- we sometimes pass previous as we can have issues in math (not watertight for all) -local function ruledglue(head,current,vertical) - local spec = getfield(current,"spec") - local width = getfield(spec,"width") +local function ruledglue(head,current,vertical,parent) + ----- spec = getfield(current,"spec") + ----- width = getfield(spec,"width") local subtype = getsubtype(current) - local amount = formatters["%s:%0.3f"](tags[subtype] or (vertical and "VS") or "HS",width*pt_factor) - local info = (vertical and g_cache_v or g_cache_h)[amount] + local width = effectiveglue(current,parent) + local amount = formatters["%s:%0.3f"](tags[subtype] or (vertical and "VS") or "HS",width*pt_factor) + local info = (vertical and g_cache_v or g_cache_h)[amount] if info then -- print("glue hit") else @@ -805,7 +809,7 @@ local function ruledpenalty(head,current,vertical) return head, getnext(current) end -local function visualize(head,vertical,forced) +local function visualize(head,vertical,forced,parent) local trace_hbox = false local trace_vbox = false local trace_vtop = false @@ -871,15 +875,15 @@ local function visualize(head,vertical,forced) elseif id == disc_code then local pre = getfield(current,"pre") if pre then - setfield(current,"pre",visualize(pre,false,a)) + setfield(current,"pre",visualize(pre,false,a,parent)) end local post = getfield(current,"post") if post then - setfield(current,"post",visualize(post,false,a)) + setfield(current,"post",visualize(post,false,a,parent)) end local replace = getfield(current,"replace") if replace then - setfield(current,"replace",visualize(replace,false,a)) + setfield(current,"replace",visualize(replace,false,a,parent)) end elseif id == kern_code then local subtype = getsubtype(current) @@ -898,9 +902,9 @@ local function visualize(head,vertical,forced) elseif id == glue_code then local content = getleader(current) if content then - setfield(current,"leader",visualize(content,false)) + setfield(current,"leader",visualize(content,false,nil,parent)) elseif trace_glue then - head, current = ruledglue(head,current,vertical) + head, current = ruledglue(head,current,vertical,parent) end elseif id == penalty_code then if trace_penalty then @@ -909,7 +913,7 @@ local function visualize(head,vertical,forced) elseif id == hlist_code then local content = getlist(current) if content then - setfield(current,"list",visualize(content,false)) + setfield(current,"list",visualize(content,false,nil,current)) end if trace_hbox then head, current = ruledbox(head,current,false,l_hbox,"H__",trace_simple,previous) @@ -917,7 +921,7 @@ local function visualize(head,vertical,forced) elseif id == vlist_code then local content = getlist(current) if content then - setfield(current,"list",visualize(content,true)) + setfield(current,"list",visualize(content,true,nil,current)) end if trace_vtop then head, current = ruledbox(head,current,true,l_vtop,"_T_",trace_simple,previous) diff --git a/tex/context/base/type-set.mkiv b/tex/context/base/type-set.mkiv index a5a3bf5ef..e2d7071d4 100644 --- a/tex/context/base/type-set.mkiv +++ b/tex/context/base/type-set.mkiv @@ -61,6 +61,8 @@ \definefilesynonym [type-imp-lucidaot.mkiv] [type-imp-lucida-opentype.mkiv] \definefilesynonym [type-imp-lucidadk.mkiv] [type-imp-lucida-opentype.mkiv] +\definefilesynonym [type-imp-dejavu-condensed.mkiv] [type-imp-dejavu.mkiv] + \definefilesynonym [type-imp-palatino.mkiv] [type-imp-texgyre.mkiv] \definefilesynonym [type-imp-courier.mkiv] [type-imp-texgyre.mkiv] \definefilesynonym [type-imp-avantgarde.mkiv] [type-imp-texgyre.mkiv] diff --git a/tex/context/base/typo-lin.lua b/tex/context/base/typo-lin.lua index 1c49620d9..aabc39b17 100644 --- a/tex/context/base/typo-lin.lua +++ b/tex/context/base/typo-lin.lua @@ -96,6 +96,8 @@ local setfield = nuts.setfield local setprop = nuts.setprop local getprop = nuts.rawprop -- getprop +local effectiveglue = nuts.effective_glue + local nodepool = nuts.pool local new_glue = nodepool.glue local new_kern = nodepool.kern @@ -205,7 +207,7 @@ local function normalize(line,islocal) -- assumes prestine lines, nothing pre/ap end if id == glue_code then if getsubtype(current) == parfillskip_code then - pskip = nuts.effectiveglue(current,line) + pskip = effectiveglue(current,line) end end end diff --git a/tex/context/base/typo-wrp.mkiv b/tex/context/base/typo-wrp.mkiv index 0538a9662..4b18785bd 100644 --- a/tex/context/base/typo-wrp.mkiv +++ b/tex/context/base/typo-wrp.mkiv @@ -62,4 +62,8 @@ \let\spac_crlf_placeholder\empty \to \everysetnostrut +\appendtoks + \let\spac_crlf\space +\to \everysimplifycommands + \protect \endinput diff --git a/tex/context/base/util-lib-imp-gm.lua b/tex/context/base/util-lib-imp-gm.lua new file mode 100644 index 000000000..4c5254721 --- /dev/null +++ b/tex/context/base/util-lib-imp-gm.lua @@ -0,0 +1,69 @@ +if not modules then modules = { } end modules ['util-lib-imp-gm'] = { + version = 1.001, + comment = "a mkiv swiglib module", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files", +} + +local graphicmagick = utilities.graphicmagick or { } +utilities.graphicmagick = graphicmagick + +local report_gm = logs.reporter("swiglib gm") + +local gm = swiglib("gmwand.core") + +if gm then + report_gm("library loaded") + -- inspect(table.sortedkeys(gm)) +else + return +end + +local nofruns = 0 + +function graphicmagick.convert(specification) + -- + nofruns = nofruns + 1 + statistics.starttiming(graphicmagick) + -- + local inputname = specification.inputname + if not inputname or inputname == "" then + report_gm("invalid run %s, no inputname specified",nofruns) + statistics.stoptiming(graphicmagick) + return false + end + local outputname = specification.outputname + if not outputname or outputname == "" then + outputname = file.replacesuffix(inputname,"pdf") + end + -- + if not lfs.isfile(inputname) then + report_gm("invalid run %s, input file %a is not found",nofruns,inputname) + statistics.stoptiming(graphicmagick) + return false + end + -- + report_gm("run %s, input file %a, outputfile %a",nofruns,inputname,outputname) + local magick_wand = gm.NewMagickWand() + gm.MagickReadImage(magick_wand,inputname) + gm.MagickWriteImage(magick_wand,outputname) + gm.DestroyMagickWand(magick_wand) + -- + statistics.stoptiming(graphicmagick) +end + +function graphicmagick.statistics(report) + local runtime = statistics.elapsedtime(graphicmagick) + if report then + report_gm("nofruns %s, runtime %s",nofruns,runtime) + else + return { + runtime = runtime, + nofruns = nofruns, + } + end +end + +-- graphicmagick.convert { inputname = "t:/sources/hacker.jpg", outputname = "e:/tmp/hacker.png" } +-- graphicmagick.statistics(true) diff --git a/tex/context/base/util-lib-imp-gs.lua b/tex/context/base/util-lib-imp-gs.lua new file mode 100644 index 000000000..8fd2dd458 --- /dev/null +++ b/tex/context/base/util-lib-imp-gs.lua @@ -0,0 +1,212 @@ +if not modules then modules = { } end modules ['util-lib-imp-gs'] = { + version = 1.001, + comment = "a mkiv swiglib module", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files", +} + +local insert = table.insert +local formatters = string.formatters + +local ghostscript = utilities.ghostscript or { } +utilities.ghostscript = ghostscript + +local report_gs = logs.reporter("swiglib gs") + +local gs = swiglib("gs.core") +local helpers = swiglib("helpers.core") + +if gs then + report_gs("library loaded") + -- inspect(table.sortedkeys(gs)) +else + return +end + +-- these could be generic helpers + +local function luatable_to_c_array(t) + local gsargv = helpers.new_char_p_array(#t) + for i=1,#t do + helpers.char_p_array_setitem(gsargv,i-1,t[i]) + end + return gsargv +end + +-- the more abstract interface + +local interface = { } +ghostscript.interface = interface + +function interface.new() + + local instance = helpers.new_void_p_p() + local result = gs.gsapi_new_instance(instance,nil) + local buffer = nil + local object = { } + + local function reader(instance,str,len) + return 0 + end + + local function writer(instance,str,len) + if buffer then + str = buffer .. str + buffer = nil + end + if not string.find(str,"[\n\r]$") then + str, buffer = string.match(str,"(.-)([^\n\r]+)$") + end + local log = object.log + for s in string.gmatch(str,"[^\n\r]+") do + insert(log,s) + report_gs(s) + end + return len + end + + if result < 0 then + return nil + else + local job = helpers.void_p_p_value(instance) + gs.gsapi_set_stdio_callback(job,reader,writer,writer) -- could be option + object.instance = instance + object.job = job + object.result = 0 + object.log = { } + return object + end +end + +function interface.dispose(run) + if run.job then + gs.gsapi_delete_instance(run.job) + run.job = nil + end + if run.instance then + helpers.delete_void_p_p(run.instance) + run.instance = nil + end +end + +function interface.init(run,options) + if run.job then + if not options then + options = { "ps2pdf" } + else + insert(options,1,"ps2pdf") -- a dummy + end + run.log = { } + local ct = luatable_to_c_array(options) + local result = gs.gsapi_init_with_args(run.job,#options,ct) + helpers.delete_char_p_array(ct) + run.initresult = result + return result >= 0 + end +end + +function interface.exit(run) + if run.job then + local result = gs.gsapi_exit(run.job) + if run.initresult == 0 or run.initresult == gs.e_Quit then + run.result = result + end + run.exitresult = result + return run.result >= 0 + end +end + +function interface.process(run,options) + interface.init(run,options) + return interface.exit(run) +end + +-- end of more abstract interface + +local nofruns = 0 + +function ghostscript.convert(specification) + -- + nofruns = nofruns + 1 + statistics.starttiming(ghostscript) + -- + local inputname = specification.inputname + if not inputname or inputname == "" then + report_gs("invalid run %s, no inputname specified",nofruns) + statistics.stoptiming(ghostscript) + return false + end + local outputname = specification.outputname + if not outputname or outputname == "" then + outputname = file.replacesuffix(inputname,"pdf") + end + -- + if not lfs.isfile(inputname) then + report_gs("invalid run %s, input file %a is not found",nofruns,inputname) + statistics.stoptiming(ghostscript) + return false + end + -- + local device = specification.device + if not device or device == "" then + device = "pdfwrite" + end + -- + local code = specification.code + if not code or code == "" then + code = ".setpdfwrite" + end + -- + local run = interface.new() + if gsinstance then + report_gs("invalid run %s, initialization error",nofruns) + statistics.stoptiming(ghostscript) + return false + end + -- + local options = specification.options or { } + -- + insert(options,"-dNOPAUSE") + insert(options,"-dBATCH") + insert(options,"-dSAFER") + insert(options,formatters["-sDEVICE=%s"](device)) + insert(options,formatters["-sOutputFile=%s"](outputname)) + insert(options,"-c") + insert(options,code) + insert(options,"-f") + insert(options,inputname) + -- + report_gs("run %s, input file %a, outputfile %a",nofruns,inputname,outputname) + report_gs("") + local okay = interface.process(run,options) + report_gs("") + -- + interface.dispose(run) + -- + statistics.stoptiming(ghostscript) + if okay then + return outputname + else + report_gs("run %s quit with errors",nofruns) + return false + end +end + +function ghostscript.statistics(report) + local runtime = statistics.elapsedtime(ghostscript) + if report then + report_gs("nofruns %s, runtime %s",nofruns,runtime) + else + return { + runtime = runtime, + nofruns = nofruns, + } + end +end + +-- for i=1,100 do +-- ghostscript.convert { inputname = "temp.eps" } +-- ghostscript.convert { inputname = "t:/escrito/tiger.eps" } +-- end +-- ghostscript.statistics(true) diff --git a/tex/context/base/util-pck.lua b/tex/context/base/util-pck.lua index 7be5e8f42..83b85cd94 100644 --- a/tex/context/base/util-pck.lua +++ b/tex/context/base/util-pck.lua @@ -10,12 +10,12 @@ if not modules then modules = { } end modules ['util-pck'] = { local next, tostring, type = next, tostring, type local sort, concat = table.sort, table.concat -local sortedhashkeys, sortedkeys = table.sortedhashkeys, table.sortedkeys +local sortedhashkeys, sortedkeys, tohash = table.sortedhashkeys, table.sortedkeys, table.tohash utilities = utilities or { } utilities.packers = utilities.packers or { } local packers = utilities.packers -packers.version = 1.00 +packers.version = 1.01 local function hashed(t) local s, ns = { }, 0 @@ -48,33 +48,33 @@ packers.simplehashed = simplehashed -- but in the latest greatest versions (lua 5.2) we really need to sort the keys in order -- not to get endless runs due to a difference in tuc files. -local function pack(t,keys,hash,index) +local function pack(t,keys,skip,hash,index) if t then - -- for k, v in next, t do - -- local sk = sortedkeys(t) - local sk = sortedhashkeys(t) + local sk = #t > 0 and sortedkeys(t) or sortedhashkeys(t) for i=1,#sk do local k = sk[i] - local v = t[k] - -- - if type(v) == "table" then - pack(v,keys,hash,index) - if keys[k] then - local h = hashed(v) - local i = hash[h] - if not i then - i = #index + 1 - index[i] = v - hash[h] = i + if not skip or not skip[k] then + local v = t[k] + -- + if type(v) == "table" then + pack(v,keys,skip,hash,index) + if keys[k] then + local h = hashed(v) + local i = hash[h] + if not i then + i = #index + 1 + index[i] = v + hash[h] = i + end + t[k] = i end - t[k] = i end end end end end -local function unpack(t,keys,index) +local function unpack(t,keys,skip,index) if t then for k, v in next, t do if keys[k] and type(v) == "number" then @@ -84,17 +84,18 @@ local function unpack(t,keys,index) t[k] = v end end - if type(v) == "table" then - unpack(v,keys,index) + if type(v) == "table" and (not skip or not skip[k]) then + unpack(v,keys,skip,index) end end end end -function packers.new(keys,version) +function packers.new(keys,version,skip) return { version = version or packers.version, - keys = table.tohash(keys), + keys = tohash(keys), + skip = tohash(skip), hash = { }, index = { }, } @@ -102,13 +103,14 @@ end function packers.pack(t,p,shared) if shared then - pack(t,p.keys,p.hash,p.index) + pack(t,p.keys,p.skip,p.hash,p.index) elseif not t.packer then - pack(t,p.keys,p.hash,p.index) + pack(t,p.keys,p.skip,p.hash,p.index) if #p.index > 0 then t.packer = { version = p.version or packers.version, keys = p.keys, + skip = p.skip, index = p.index, } end @@ -120,13 +122,13 @@ end function packers.unpack(t,p,shared) if shared then if p then - unpack(t,p.keys,p.index) + unpack(t,p.keys,p.skip,p.index) end else local tp = t.packer if tp then if tp.version == (p and p.version or packers.version) then - unpack(t,tp.keys,tp.index) + unpack(t,tp.keys,tp.skip,tp.index) else return false end diff --git a/tex/context/sample/carey.tex b/tex/context/sample/carey.tex new file mode 100644 index 000000000..81dda1c21 --- /dev/null +++ b/tex/context/sample/carey.tex @@ -0,0 +1,12 @@ +We humans, more than any other species, edit our ncRNA molecules to a remarkable +degree. Not even other primates carry out this reaction as well as we do. We also +edit particularly extensively in the brain. This makes editing of ncRNA an +attractive candidate process to explain why we are mentally so much more +sophisticated than our primate relatives, even though we share so much of our DNA +template in common. + +In some ways, this is the beauty of ncRNAs. They create a relatively safe method +for organisms to use to alter various aspects if cellular regulation. Evolution +has probably favoured this mechanism because it is simply too risky to try to +improve function by changing proteins. Proteins, you see, are the Mary Poppins of +the cell. They are \quote {practically perfect in every way}. diff --git a/tex/context/sample/samples.tex b/tex/context/sample/samples.tex index 6e217a592..97e777ccc 100644 --- a/tex/context/sample/samples.tex +++ b/tex/context/sample/samples.tex @@ -44,6 +44,8 @@ used in testing bibliographic references and citations. Quercus, London, 2006 \NC \NR %NC jojomayer.tex \NC Jojo Mayer \NC Between Zero & One, www.youtube.com/watch?v=mSj298iBjBY \NC \NR %NC schwarzenegger.tex \NC Arnold Schwarzenegger \NC Several place on the World Wide Web. \NC \NR +\NC carey.tex \NC Nessa Carey \NC The Epigenetics Revolution, \endgraf + Columbia University Press, 2012, p. 195 \NC \NR \stoptabulate % Tufte: This quote will always produce hyphenated text, apart from the content, diff --git a/tex/generic/context/luatex/luatex-fonts-merged.lua b/tex/generic/context/luatex/luatex-fonts-merged.lua index 25aef72a0..2fd824467 100644 --- a/tex/generic/context/luatex/luatex-fonts-merged.lua +++ b/tex/generic/context/luatex/luatex-fonts-merged.lua @@ -1,6 +1,6 @@ -- merged file : luatex-fonts-merged.lua -- parent file : luatex-fonts.lua --- merge date : 09/13/15 13:31:08 +-- merge date : 10/04/15 19:25:05 do -- begin closure to overcome local limits and interference @@ -14232,7 +14232,7 @@ local function featuresprocessor(head,font,attr) rlmode=-1 elseif dir=="-TLT" or dir=="-TRT" then topstack=topstack-1 - rlmode=dirstack[topstack]=="+TLT" and 1 or -1 + rlmode=dirstack[topstack]=="+TRT" and -1 or 1 else rlmode=rlparmode end @@ -14488,7 +14488,7 @@ local function featuresprocessor(head,font,attr) rlmode=-1 elseif dir=="-TLT" or dir=="-TRT" then topstack=topstack-1 - rlmode=dirstack[topstack]=="+TLT" and 1 or -1 + rlmode=dirstack[topstack]=="+TRT" and -1 or 1 else rlmode=rlparmode end diff --git a/tex/generic/context/luatex/luatex-fonts-otn.lua b/tex/generic/context/luatex/luatex-fonts-otn.lua index 8066b0f08..1b99c56de 100644 --- a/tex/generic/context/luatex/luatex-fonts-otn.lua +++ b/tex/generic/context/luatex/luatex-fonts-otn.lua @@ -553,7 +553,7 @@ local function toligature(kind,lookupname,head,start,stop,char,markflag,discfoun resetinjection(base) setfield(base,"char",char) setfield(base,"subtype",ligature_code) - setfield(base,"components",comp) -- start can have components .. do we need to flush? + setfield(base,"components",comp) -- start can have components ... do we need to flush? if prev then setfield(prev,"next",base) end @@ -3334,7 +3334,7 @@ local function featuresprocessor(head,font,attr) rlmode = -1 elseif dir == "-TLT" or dir == "-TRT" then topstack = topstack - 1 - rlmode = dirstack[topstack] == "+TLT" and 1 or -1 + rlmode = dirstack[topstack] == "+TRT" and -1 or 1 else rlmode = rlparmode end @@ -3606,7 +3606,7 @@ local function featuresprocessor(head,font,attr) rlmode = -1 elseif dir == "-TLT" or dir == "-TRT" then topstack = topstack - 1 - rlmode = dirstack[topstack] == "+TLT" and 1 or -1 + rlmode = dirstack[topstack] == "+TRT" and -1 or 1 else rlmode = rlparmode end diff --git a/tex/generic/context/luatex/luatex-mplib.lua b/tex/generic/context/luatex/luatex-mplib.lua index c093b8333..fd6eb975c 100644 --- a/tex/generic/context/luatex/luatex-mplib.lua +++ b/tex/generic/context/luatex/luatex-mplib.lua @@ -157,8 +157,8 @@ else \voffset=\hoffset \topskip=0pt \setbox0=\hbox{%s}\relax - \pdfpageheight=\ht0 - \pdfpagewidth=\wd0 + \pageheight=\ht0 + \pagewidth=\wd0 \box0 \bye ]] diff --git a/tex/generic/context/luatex/luatex-plain.tex b/tex/generic/context/luatex/luatex-plain.tex index 2c04a27c0..9902c49f3 100644 --- a/tex/generic/context/luatex/luatex-plain.tex +++ b/tex/generic/context/luatex/luatex-plain.tex @@ -27,8 +27,8 @@ % has to deal with the lack of a page concept on tex by some guessing. Normally % a macro package will set the dimensions to something reasonable anyway. -\pdfpagewidth 8.5in -\pdfpageheight 11.0in +\pagewidth 8.5in +\pageheight 11.0in % We load some code at runtime: diff --git a/tex/generic/context/luatex/luatex-test.tex b/tex/generic/context/luatex/luatex-test.tex index 9f03027bc..f851aab6f 100644 --- a/tex/generic/context/luatex/luatex-test.tex +++ b/tex/generic/context/luatex/luatex-test.tex @@ -39,8 +39,8 @@ \bgroup - \pdfprotrudechars2 - \pdfadjustspacing2 + \ifdefined\pdfprotrudechars \pdfprotrudechars \else \protrudechars \fi 2 \relax + \ifdefined\pdfadjustspacing \pdfadjustspacing \else \adjustspacing \fi 2 \relax \font\testb=file:lmroman12-regular:+liga;extend=1.5 at 12pt \testb \input tufte \par \font\testb=file:lmroman12-regular:+liga;slant=0.8 at 12pt \testb \input tufte \par |