summaryrefslogtreecommitdiff
path: root/tex
diff options
context:
space:
mode:
authorContext Git Mirror Bot <phg42.2a@gmail.com>2015-10-04 20:15:06 +0200
committerContext Git Mirror Bot <phg42.2a@gmail.com>2015-10-04 20:15:06 +0200
commitd81d584da5152af05c07f7842054a926aae20e10 (patch)
tree9e15be94f1f8d05cccdbd575d4d5f0bac8cad221 /tex
parentc9ac836b7b44d49c623ad8052639ca7beeaf1311 (diff)
downloadcontext-d81d584da5152af05c07f7842054a926aae20e10.tar.gz
2015-10-04 19:27:00
Diffstat (limited to 'tex')
-rw-r--r--tex/context/base/back-exp.lua4
-rw-r--r--tex/context/base/buff-ver.mkiv56
-rw-r--r--tex/context/base/cont-new.mkiv4
-rw-r--r--tex/context/base/context-version.pdfbin4179 -> 4187 bytes
-rw-r--r--tex/context/base/context.mkiv2
-rw-r--r--tex/context/base/core-dat.lua2
-rw-r--r--tex/context/base/core-uti.lua10
-rw-r--r--tex/context/base/font-ctx.lua5
-rw-r--r--tex/context/base/font-ini.mkvi5
-rw-r--r--tex/context/base/font-otn.lua6
-rw-r--r--tex/context/base/font-ots.lua4
-rw-r--r--tex/context/base/grph-fil.lua10
-rw-r--r--tex/context/base/grph-inc.lua12
-rw-r--r--tex/context/base/hand-ini.mkiv12
-rw-r--r--tex/context/base/lang-dis.lua106
-rw-r--r--tex/context/base/lang-hyp.lua121
-rw-r--r--tex/context/base/lpdf-ano.lua25
-rw-r--r--tex/context/base/lpdf-epa.lua4
-rw-r--r--tex/context/base/lpdf-grp.lua6
-rw-r--r--tex/context/base/lpdf-ini.lua7
-rw-r--r--tex/context/base/lpdf-mis.lua8
-rw-r--r--tex/context/base/lpdf-ren.lua29
-rw-r--r--tex/context/base/lxml-ctx.lua23
-rw-r--r--tex/context/base/lxml-ctx.mkiv2
-rw-r--r--tex/context/base/lxml-ini.lua16
-rw-r--r--tex/context/base/lxml-ini.mkiv8
-rw-r--r--tex/context/base/lxml-tex.lua82
-rw-r--r--tex/context/base/m-escrito.lua7088
-rw-r--r--tex/context/base/m-escrito.mkiv184
-rw-r--r--tex/context/base/m-newotf.mkiv2
-rw-r--r--tex/context/base/m-punk.mkiv1
-rw-r--r--tex/context/base/math-def.mkiv2
-rw-r--r--tex/context/base/math-fen.mkiv16
-rw-r--r--tex/context/base/math-stc.mkvi22
-rw-r--r--tex/context/base/meta-ini.mkiv2
-rw-r--r--tex/context/base/mlib-ctx.lua2
-rw-r--r--tex/context/base/mult-prm.lua55
-rw-r--r--tex/context/base/node-aux.lua16
-rw-r--r--tex/context/base/node-fnt.lua2
-rw-r--r--tex/context/base/node-ltp.lua75
-rw-r--r--tex/context/base/node-met.lua84
-rw-r--r--tex/context/base/node-nut.lua41
-rw-r--r--tex/context/base/node-shp.lua5
-rw-r--r--tex/context/base/pack-box.mkiv1
-rw-r--r--tex/context/base/pack-rul.mkiv2
-rw-r--r--tex/context/base/publ-aut.lua2
-rw-r--r--tex/context/base/publ-dat.lua22
-rw-r--r--tex/context/base/publ-ini.lua66
-rw-r--r--tex/context/base/publ-ini.mkiv41
-rw-r--r--tex/context/base/publ-sor.lua50
-rw-r--r--tex/context/base/spac-ver.lua3
-rw-r--r--tex/context/base/spac-ver.mkiv4
-rw-r--r--tex/context/base/status-files.pdfbin24475 -> 24418 bytes
-rw-r--r--tex/context/base/status-lua.pdfbin255762 -> 256324 bytes
-rw-r--r--tex/context/base/strc-lst.mkvi6
-rw-r--r--tex/context/base/strc-reg.mkiv1
-rw-r--r--tex/context/base/syst-ini.mkiv52
-rw-r--r--tex/context/base/trac-vis.lua32
-rw-r--r--tex/context/base/type-set.mkiv2
-rw-r--r--tex/context/base/typo-lin.lua4
-rw-r--r--tex/context/base/typo-wrp.mkiv4
-rw-r--r--tex/context/base/util-lib-imp-gm.lua69
-rw-r--r--tex/context/base/util-lib-imp-gs.lua212
-rw-r--r--tex/context/base/util-pck.lua56
-rw-r--r--tex/context/sample/carey.tex12
-rw-r--r--tex/context/sample/samples.tex2
-rw-r--r--tex/generic/context/luatex/luatex-fonts-merged.lua6
-rw-r--r--tex/generic/context/luatex/luatex-fonts-otn.lua6
-rw-r--r--tex/generic/context/luatex/luatex-mplib.lua4
-rw-r--r--tex/generic/context/luatex/luatex-plain.tex4
-rw-r--r--tex/generic/context/luatex/luatex-test.tex4
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
index 64cefe13c..d898fe588 100644
--- a/tex/context/base/context-version.pdf
+++ b/tex/context/base/context-version.pdf
Binary files differ
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
index 3c6a08724..79d581f27 100644
--- a/tex/context/base/status-files.pdf
+++ b/tex/context/base/status-files.pdf
Binary files differ
diff --git a/tex/context/base/status-lua.pdf b/tex/context/base/status-lua.pdf
index 986fa8141..efb631bc8 100644
--- a/tex/context/base/status-lua.pdf
+++ b/tex/context/base/status-lua.pdf
Binary files differ
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