summaryrefslogtreecommitdiff
path: root/tex/context/base
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base')
-rw-r--r--tex/context/base/mkii/cont-new.mkii2
-rw-r--r--tex/context/base/mkii/context.mkii2
-rw-r--r--tex/context/base/mkii/mult-en.mkii1
-rw-r--r--tex/context/base/mkii/mult-it.mkii8
-rw-r--r--tex/context/base/mkii/mult-ro.mkii1
-rw-r--r--tex/context/base/mkiv/back-ini.lua23
-rw-r--r--tex/context/base/mkiv/back-pdf.mkiv8
-rw-r--r--tex/context/base/mkiv/back-pdf.mkxl8
-rw-r--r--tex/context/base/mkiv/cont-new.mkiv2
-rw-r--r--tex/context/base/mkiv/context.mkiv2
-rw-r--r--tex/context/base/mkiv/context.mkxl2
-rw-r--r--tex/context/base/mkiv/driv-shp.lua28
-rw-r--r--tex/context/base/mkiv/font-con.lua7
-rw-r--r--tex/context/base/mkiv/font-lib.mkvi16
-rw-r--r--tex/context/base/mkiv/font-ocl.lua89
-rw-r--r--tex/context/base/mkiv/font-ogr.lua381
-rw-r--r--tex/context/base/mkiv/font-otl.lua1
-rw-r--r--tex/context/base/mkiv/font-tpk.lua28
-rw-r--r--tex/context/base/mkiv/grph-chk.lua2
-rw-r--r--tex/context/base/mkiv/grph-img.lua50
-rw-r--r--tex/context/base/mkiv/lpdf-emb.lua397
-rw-r--r--tex/context/base/mkiv/lpdf-img.lua26
-rw-r--r--tex/context/base/mkiv/lpdf-ini.lua79
-rw-r--r--tex/context/base/mkiv/lpdf-lmt.lua111
-rw-r--r--tex/context/base/mkiv/lxml-css.lua17
-rw-r--r--tex/context/base/mkiv/meta-fig.mkiv7
-rw-r--r--tex/context/base/mkiv/meta-ini.mkiv3
-rw-r--r--tex/context/base/mkiv/meta-ini.mkxl3
-rw-r--r--tex/context/base/mkiv/mlib-cnt.lua1770
-rw-r--r--tex/context/base/mkiv/mlib-ctx.lua2
-rw-r--r--tex/context/base/mkiv/mlib-ctx.mkiv3
-rw-r--r--tex/context/base/mkiv/mlib-ctx.mkxl3
-rw-r--r--tex/context/base/mkiv/mlib-lmp.lua8
-rw-r--r--tex/context/base/mkiv/mlib-lua.lua14
-rw-r--r--tex/context/base/mkiv/mlib-pdf.lua13
-rw-r--r--tex/context/base/mkiv/mlib-pps.lua6
-rw-r--r--tex/context/base/mkiv/mlib-run.lua105
-rw-r--r--tex/context/base/mkiv/mlib-scn.lua19
-rw-r--r--tex/context/base/mkiv/mlib-svg.lua1635
-rw-r--r--tex/context/base/mkiv/mult-def.lua4
-rw-r--r--tex/context/base/mkiv/mult-fun.lua1
-rw-r--r--tex/context/base/mkiv/mult-mps.lua2
-rw-r--r--tex/context/base/mkiv/status-files.pdfbin26674 -> 26690 bytes
-rw-r--r--tex/context/base/mkiv/status-lua.pdfbin268804 -> 267983 bytes
-rw-r--r--tex/context/base/mkiv/strc-itm.mklx26
-rw-r--r--tex/context/base/mkiv/strc-itm.mkvi26
-rw-r--r--tex/context/base/mkiv/strc-ref.lua16
-rw-r--r--tex/context/base/mkiv/syst-aux.mkiv2
-rw-r--r--tex/context/base/mkiv/syst-aux.mkxl2
-rw-r--r--tex/context/base/mkiv/trac-deb.lua5
-rw-r--r--tex/context/base/mkiv/util-lua.lua10
-rw-r--r--tex/context/base/mkiv/util-sac.lua57
52 files changed, 4650 insertions, 383 deletions
diff --git a/tex/context/base/mkii/cont-new.mkii b/tex/context/base/mkii/cont-new.mkii
index eced6e05b..a1f67933b 100644
--- a/tex/context/base/mkii/cont-new.mkii
+++ b/tex/context/base/mkii/cont-new.mkii
@@ -11,7 +11,7 @@
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.
-\newcontextversion{2019.09.10 20:03}
+\newcontextversion{2019.09.27 17:59}
%D This file is loaded at runtime, thereby providing an
%D excellent place for hacks, patches, extensions and new
diff --git a/tex/context/base/mkii/context.mkii b/tex/context/base/mkii/context.mkii
index b3afbbceb..2a4b1588d 100644
--- a/tex/context/base/mkii/context.mkii
+++ b/tex/context/base/mkii/context.mkii
@@ -20,7 +20,7 @@
%D your styles an modules.
\edef\contextformat {\jobname}
-\edef\contextversion{2019.09.10 20:03}
+\edef\contextversion{2019.09.27 17:59}
%D For those who want to use this:
diff --git a/tex/context/base/mkii/mult-en.mkii b/tex/context/base/mkii/mult-en.mkii
index 65721ddaa..77f45f832 100644
--- a/tex/context/base/mkii/mult-en.mkii
+++ b/tex/context/base/mkii/mult-en.mkii
@@ -234,6 +234,7 @@
\setinterfacevariable{hanging}{hanging}
\setinterfacevariable{head}{head}
\setinterfacevariable{header}{header}
+\setinterfacevariable{headintext}{headintext}
\setinterfacevariable{height}{height}
\setinterfacevariable{helptext}{helptext}
\setinterfacevariable{hencefore}{hencefore}
diff --git a/tex/context/base/mkii/mult-it.mkii b/tex/context/base/mkii/mult-it.mkii
index 432917617..a5d956ba4 100644
--- a/tex/context/base/mkii/mult-it.mkii
+++ b/tex/context/base/mkii/mult-it.mkii
@@ -234,6 +234,7 @@
\setinterfacevariable{hanging}{sospeso}
\setinterfacevariable{head}{testa}
\setinterfacevariable{header}{intestazione}
+\setinterfacevariable{headintext}{headintext}
\setinterfacevariable{height}{altezza}
\setinterfacevariable{helptext}{testoaiuto}
\setinterfacevariable{hencefore}{precedente}
@@ -867,6 +868,7 @@
\setinterfaceconstant{hfil}{hfil}
\setinterfaceconstant{hidenumber}{hidenumber}
\setinterfaceconstant{hoffset}{hoffset}
+\setinterfaceconstant{horizontal}{orizzontale}
\setinterfaceconstant{horoffset}{horoffset}
\setinterfaceconstant{hyphen}{hyphen}
\setinterfaceconstant{hyphens}{hyphens}
@@ -967,9 +969,12 @@
\setinterfaceconstant{menu}{menu}
\setinterfaceconstant{method}{metodo}
\setinterfaceconstant{middle}{centro}
+\setinterfaceconstant{middlecolor}{middlecolor}
\setinterfaceconstant{middlecommand}{middlecommand}
\setinterfaceconstant{middlespeech}{middlespeech}
+\setinterfaceconstant{middlestyle}{middlestyle}
\setinterfaceconstant{middletext}{testocentro}
+\setinterfaceconstant{middlewidth}{middlewidth}
\setinterfaceconstant{midsentence}{midsentence}
\setinterfaceconstant{min}{min}
\setinterfaceconstant{mindepth}{mindepth}
@@ -1287,6 +1292,7 @@
\setinterfaceconstant{vcompact}{vcompact}
\setinterfaceconstant{vector}{vector}
\setinterfaceconstant{veroffset}{veroffset}
+\setinterfaceconstant{vertical}{verticale}
\setinterfaceconstant{vfactor}{vfactor}
\setinterfaceconstant{vfil}{vfil}
\setinterfaceconstant{viewerprefix}{viewerprefix}
@@ -1297,6 +1303,7 @@
\setinterfaceconstant{white}{bianco}
\setinterfaceconstant{width}{ampiezza}
\setinterfaceconstant{words}{words}
+\setinterfaceconstant{xanchor}{xanchor}
\setinterfaceconstant{xfactor}{xfactor}
\setinterfaceconstant{xhtml}{xhtml}
\setinterfaceconstant{xmax}{xmax}
@@ -1304,6 +1311,7 @@
\setinterfaceconstant{xoffset}{xoffset}
\setinterfaceconstant{xscale}{xscale}
\setinterfaceconstant{xstep}{xstep}
+\setinterfaceconstant{yanchor}{yanchor}
\setinterfaceconstant{yfactor}{yfactor}
\setinterfaceconstant{ymax}{ymax}
\setinterfaceconstant{yoffset}{yoffset}
diff --git a/tex/context/base/mkii/mult-ro.mkii b/tex/context/base/mkii/mult-ro.mkii
index 248cf8b18..ccdce5dcc 100644
--- a/tex/context/base/mkii/mult-ro.mkii
+++ b/tex/context/base/mkii/mult-ro.mkii
@@ -234,6 +234,7 @@
\setinterfacevariable{hanging}{suspendat}
\setinterfacevariable{head}{antet}
\setinterfacevariable{header}{antet}
+\setinterfacevariable{headintext}{headintext}
\setinterfacevariable{height}{inaltime}
\setinterfacevariable{helptext}{textajutator}
\setinterfacevariable{hencefore}{precedent}
diff --git a/tex/context/base/mkiv/back-ini.lua b/tex/context/base/mkiv/back-ini.lua
index bc4a67bc0..b0af61edc 100644
--- a/tex/context/base/mkiv/back-ini.lua
+++ b/tex/context/base/mkiv/back-ini.lua
@@ -182,6 +182,29 @@ implement {
end
}
+
+local page_x_origin = 0
+local page_y_origin = 0
+
+function codeinjections.setpageorigin(x,y)
+ page_x_origin = x
+ page_y_origin = y
+end
+
+function codeinjections.getpageorigin()
+ local x = page_x_origin
+ local y = page_y_origin
+ page_x_origin = 0
+ page_y_origin = 0
+ return x, y, (x ~= 0 or y ~= 0)
+end
+
+implement {
+ name = "setpageorigin",
+ arguments = { "dimension", "dimension" },
+ actions = codeinjections.setpageorigin,
+}
+
-- could also be codeinjections
function backends.noflatelua()
diff --git a/tex/context/base/mkiv/back-pdf.mkiv b/tex/context/base/mkiv/back-pdf.mkiv
index ff05e647f..ec1c641e6 100644
--- a/tex/context/base/mkiv/back-pdf.mkiv
+++ b/tex/context/base/mkiv/back-pdf.mkiv
@@ -145,8 +145,10 @@
%D Bah, this is also needed for tikz:
-\let\pdfsavepos \savepos
-\let\pdflastxpos\lastxpos
-\let\pdflastypos\lastypos
+\ifdefined\pdfsavepos\else
+ \let\pdfsavepos \savepos
+ \let\pdflastxpos\lastxpos
+ \let\pdflastypos\lastypos
+\fi
\protect \endinput
diff --git a/tex/context/base/mkiv/back-pdf.mkxl b/tex/context/base/mkiv/back-pdf.mkxl
index 2b0c4d0b7..ab65458c5 100644
--- a/tex/context/base/mkiv/back-pdf.mkxl
+++ b/tex/context/base/mkiv/back-pdf.mkxl
@@ -145,4 +145,12 @@
\let\pdfactualtext\pdfbackendactualtext
+%D Bah, this is also needed for tikz:
+
+\ifdefined\pdfsavepos\else
+ \let\pdfsavepos \savepos
+ \let\pdflastxpos\lastxpos
+ \let\pdflastypos\lastypos
+\fi
+
\protect \endinput
diff --git a/tex/context/base/mkiv/cont-new.mkiv b/tex/context/base/mkiv/cont-new.mkiv
index 0a68310a0..6b05f4174 100644
--- a/tex/context/base/mkiv/cont-new.mkiv
+++ b/tex/context/base/mkiv/cont-new.mkiv
@@ -13,7 +13,7 @@
% \normalend % uncomment this to get the real base runtime
-\newcontextversion{2019.09.10 20:03}
+\newcontextversion{2019.09.27 17:59}
%D This file is loaded at runtime, thereby providing an excellent place for
%D hacks, patches, extensions and new features.
diff --git a/tex/context/base/mkiv/context.mkiv b/tex/context/base/mkiv/context.mkiv
index 2c17d4b91..907985beb 100644
--- a/tex/context/base/mkiv/context.mkiv
+++ b/tex/context/base/mkiv/context.mkiv
@@ -45,7 +45,7 @@
%D {YYYY.MM.DD HH:MM} format.
\edef\contextformat {\jobname}
-\edef\contextversion{2019.09.10 20:03}
+\edef\contextversion{2019.09.27 17:59}
\edef\contextkind {beta}
%D Kind of special:
diff --git a/tex/context/base/mkiv/context.mkxl b/tex/context/base/mkiv/context.mkxl
index 39f647cc2..6651995c2 100644
--- a/tex/context/base/mkiv/context.mkxl
+++ b/tex/context/base/mkiv/context.mkxl
@@ -29,7 +29,7 @@
%D {YYYY.MM.DD HH:MM} format.
\edef\contextformat {\jobname}
-\edef\contextversion{2019.09.10 20:03}
+\edef\contextversion{2019.09.27 17:59}
\edef\contextkind {beta}
%D Kind of special:
diff --git a/tex/context/base/mkiv/driv-shp.lua b/tex/context/base/mkiv/driv-shp.lua
index ae99d84ce..8fe2b8483 100644
--- a/tex/context/base/mkiv/driv-shp.lua
+++ b/tex/context/base/mkiv/driv-shp.lua
@@ -150,6 +150,7 @@ local updatefontstate
local pushorientation
local poporientation
local flushcharacter
+local flushfontchar
local flushrule
local flushliteral
local flushsetmatrix
@@ -241,6 +242,32 @@ local function flush_vf_packet(pos_h,pos_v,pos_r,font,char,data,factor,vfcommand
pos_h = pos_h + flushchar(font,char,font,chr,f,e)
end
end
+ elseif command == "use" then
+ local index = packet[2]
+ if index then
+ local fnt
+ if index == 0 then
+ fnt = font
+ else
+ local okay = fonts and fonts[index]
+ if okay then
+ fnt = okay.id
+ end
+ end
+ if fnt then
+ -- not efficient but ok for now as experiment
+ local d = characters[fnt]
+ if d then
+ for i=3,#packet do
+ local chr = packet[i]
+ local dat = d[chr]
+ if dat then
+ flushfontchar(fnt,chr,dat)
+ end
+ end
+ end
+ end
+ end
elseif command == "right" then
local h = packet[2] -- * siz
if factor ~= 0 and h ~= 0 then
@@ -1118,6 +1145,7 @@ function drivers.converters.lmtx(driver,box,smode,objnum,specification)
poporientation = flushers.poporientation
flushcharacter = flushers.character
+ flushfontchar = flushers.fontchar
flushrule = flushers.rule
flushsimplerule = flushers.simplerule
flushspecial = flushers.special
diff --git a/tex/context/base/mkiv/font-con.lua b/tex/context/base/mkiv/font-con.lua
index 9c1e825fa..652794142 100644
--- a/tex/context/base/mkiv/font-con.lua
+++ b/tex/context/base/mkiv/font-con.lua
@@ -444,9 +444,10 @@ function constructors.scale(tfmdata,specification)
-- boundarychar = 65536, -- there is now a string 'right_boundary'
-- false_boundarychar = 65536, -- produces invalid tfm in luatex
--
- targetproperties.language = properties.language or "dflt" -- inherited
- targetproperties.script = properties.script or "dflt" -- inherited
- targetproperties.mode = properties.mode or "base" -- inherited
+ targetproperties.language = properties.language or "dflt" -- inherited
+ targetproperties.script = properties.script or "dflt" -- inherited
+ targetproperties.mode = properties.mode or "base" -- inherited
+ targetproperties.method = properties.method
--
local askedscaledpoints = scaledpoints
local scaledpoints, delta = constructors.calculatescale(tfmdata,scaledpoints,nil,specification) -- no shortcut, dan be redefined
diff --git a/tex/context/base/mkiv/font-lib.mkvi b/tex/context/base/mkiv/font-lib.mkvi
index 081ae0f38..c9050f61b 100644
--- a/tex/context/base/mkiv/font-lib.mkvi
+++ b/tex/context/base/mkiv/font-lib.mkvi
@@ -28,7 +28,6 @@
% helpers
-
\registerctxluafile{font-otr}{optimize} % opentype fontloader
\registerctxluafile{font-web}{} % opentype fontloader
\registerctxluafile{font-cff}{optimize} % quadratic outlines
@@ -51,7 +50,12 @@
\registerctxluafile{font-oth}{}
\registerctxluafile{font-osd}{}
% \doifelsefileexists{font-osm.lua}{\registerctxluafile{font-osm}{}}{}
-\registerctxluafile{font-ocl}{}
+
+\ifcase\contextlmtxmode
+ \registerctxluafile{font-ocl}{}
+\else
+ \registerctxluafile{font-ogr}{}
+\fi
% we use otf code for type one
@@ -61,12 +65,8 @@
% tfm
-\doifelsefileexists {font-tpk.lua} {
- \registerctxluafile{font-tpk}{optimize}
- \registerctxluafile{font-tfm}{}
-} {
- \registerctxluafile{font-tfm}{}
-}
+\registerctxluafile{font-tpk}{optimize}
+\registerctxluafile{font-tfm}{}
% name database
diff --git a/tex/context/base/mkiv/font-ocl.lua b/tex/context/base/mkiv/font-ocl.lua
index 10d2df270..9ce6982a8 100644
--- a/tex/context/base/mkiv/font-ocl.lua
+++ b/tex/context/base/mkiv/font-ocl.lua
@@ -8,6 +8,10 @@ if not modules then modules = { } end modules ['font-ocl'] = {
-- todo : user list of colors
+if CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 then
+ return
+end
+
local tostring, tonumber, next = tostring, tonumber, next
local round, max = math.round, math.round
local gsub, find = string.gsub, string.find
@@ -17,8 +21,6 @@ local setmetatableindex = table.setmetatableindex
local formatters = string.formatters
local tounicode = fonts.mappings.tounicode
-local bpfactor = number.dimenfactors.bp
-
local helpers = fonts.helpers
local charcommand = helpers.commands.char
@@ -367,19 +369,25 @@ local function pdftovirtual(tfmdata,pdfshapes,kind) -- kind = png|svg
for unicode, character in sortedhash(characters) do -- sort is nicer for svg
local index = character.index
if index then
- local pdf = pdfshapes[index]
- local typ = type(pdf)
- local data = nil
- local dx = nil
- local dy = nil
+ local pdf = pdfshapes[index]
+ local typ = type(pdf)
+ local data = nil
+ local dx = nil
+ local dy = nil
+ local scale = 1
if typ == "table" then
- data = pdf.data
- dx = pdf.x or pdf.dx or 0
- dy = pdf.y or pdf.dy or 0
+ data = pdf.data
+ dx = pdf.x or pdf.dx or 0
+ dy = pdf.y or pdf.dy or 0
+ scale = pdf.scale or 1
elseif typ == "string" then
data = pdf
dx = 0
dy = 0
+ elseif typ == "number" then
+ data = pdf
+ dx = 0
+ dy = 0
end
if data then
-- We can delay storage by a lua function in commands: but then we need to
@@ -402,7 +410,7 @@ local function pdftovirtual(tfmdata,pdfshapes,kind) -- kind = png|svg
-- mkiv
downcommand [dp + dy * hfactor],
rightcommand[ dx * hfactor],
- vfimage(wd,ht,dp,data,name),
+ vfimage(scale*wd,ht,dp,data,pdfshapes.filename or ""),
actuale,
}
character[kind] = true
@@ -457,12 +465,17 @@ do
-- poor mans variant for generic:
--
runner = function()
- return io.open("inkscape --export-area-drawing --shell > temp-otf-svg-shape.log","w")
+ return io.popen("inkscape --export-area-drawing --shell > temp-otf-svg-shape.log","w")
end
end
-- There are svg out there with bad viewBox specifications where shapes lay outside that area,
- -- but trying to correct that didin'w work out well enough so I discarded that code.
+ -- but trying to correct that didn't work out well enough so I discarded that code. BTW, we
+ -- decouple the inskape run and the loading run because inkscape is working in the background
+ -- in the files so we need to have unique files.
+ --
+ -- Because a generic setup can be flawed we need to catch bad inkscape runs which add a bit of
+ -- ugly overhead. Bah.
function otfsvg.topdf(svgshapes,tfmdata)
local pdfshapes = { }
@@ -476,6 +489,7 @@ do
local f_convert = formatters["%s --export-pdf=%s\n"]
local filterglyph = otfsvg.filterglyph
local nofdone = 0
+ local processed = { }
report_svg("processing %i svg containers",nofshapes)
statistics.starttiming()
for i=1,nofshapes do
@@ -487,30 +501,55 @@ do
local pdffile = f_pdffile(index)
savedata(svgfile,data)
inkscape:write(f_convert(svgfile,pdffile))
- pdfshapes[index] = true
+ processed[index] = true
nofdone = nofdone + 1
- if nofdone % 100 == 0 then
- report_svg("%i shapes processed",nofdone)
+ if nofdone % 25 == 0 then
+ report_svg("%i shapes submitted",nofdone)
end
end
end
end
+ if nofdone % 25 ~= 0 then
+ report_svg("%i shapes submitted",nofdone)
+ end
+ report_svg("processing can be going on for a while")
inkscape:write("quit\n")
inkscape:close()
report_svg("processing %i pdf results",nofshapes)
- for index in next, pdfshapes do
+ for index in next, processed do
local svgfile = f_svgfile(index)
local pdffile = f_pdffile(index)
-- local fntdata = descriptions[indices[index]]
-- local bounds = fntdata and fntdata.boundingbox
- pdfshapes[index] = {
- data = loaddata(pdffile),
- -- x = bounds and bounds[1] or 0,
- -- y = bounds and bounds[2] or 0,
- }
+ local pdfdata = loaddata(pdffile)
+ if pdfdata and pdfdata ~= "" then
+ pdfshapes[index] = {
+ data = pdfdata,
+ -- x = bounds and bounds[1] or 0,
+ -- y = bounds and bounds[2] or 0,
+ }
+ end
remove(svgfile)
remove(pdffile)
end
+local characters = tfmdata.characters
+for k, v in next, characters do
+ local d = descriptions[k]
+ local i = d.index
+ if i then
+ local p = pdfshapes[i]
+ if p then
+ local w = d.width
+ local l = d.boundingbox[1]
+ local r = d.boundingbox[3]
+ p.scale = (r - l) / w
+ p.x = l
+ end
+ end
+end
+ if not next(pdfshapes) then
+ report_svg("there are no converted shapes, fix your setup")
+ end
statistics.stoptiming()
if statistics.elapsedseconds then
report_svg("svg conversion time %s",statistics.elapsedseconds() or "-")
@@ -531,10 +570,12 @@ local function initializesvg(tfmdata,kind,value) -- hm, always value
end
local pdffile = containers.read(otf.pdfcache,hash)
local pdfshapes = pdffile and pdffile.pdfshapes
- if not pdfshapes or pdffile.timestamp ~= timestamp then
+ if not pdfshapes or pdffile.timestamp ~= timestamp or not next(pdfshapes) then
+ -- the next test tries to catch errors in generic usage but of course can result
+ -- in running again and again
local svgfile = containers.read(otf.svgcache,hash)
local svgshapes = svgfile and svgfile.svgshapes
- pdfshapes = svgshapes and otfsvg.topdf(svgshapes,tfmdata) or { }
+ pdfshapes = svgshapes and otfsvg.topdf(svgshapes,tfmdata,otf.pdfcache.writable,hash) or { }
containers.write(otf.pdfcache, hash, {
pdfshapes = pdfshapes,
timestamp = timestamp,
diff --git a/tex/context/base/mkiv/font-ogr.lua b/tex/context/base/mkiv/font-ogr.lua
new file mode 100644
index 000000000..c441ddbf7
--- /dev/null
+++ b/tex/context/base/mkiv/font-ogr.lua
@@ -0,0 +1,381 @@
+if not modules then modules = { } end modules ['font-ogr'] = {
+ version = 1.001,
+ comment = "companion to font-ini.mkiv",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- Here we deal with graphic variants and for now also color support ends up here
+-- but that might change. It's lmtx only code.
+
+if not context then
+ return
+elseif CONTEXTLMTXMODE == 0 then
+ return
+end
+
+local tostring, tonumber, next = tostring, tonumber, next
+local round, max, mod, div = math.round, math.round, math.mod, math.div
+local concat, setmetatableindex = table.concat, table.setmetatableindex
+local formatters = string.formatters
+
+local otf = fonts.handlers.otf
+local otfregister = otf.features.register
+otf.svgenabled = true
+otf.pngenabled = true
+
+-- Just to remind me ... rewritten around the time this was posted on YT which
+-- was also around the 2019 ctx meeting:
+--
+-- Gavin Harrison - "Threatening War" by The Pineapple Thief
+-- https://www.youtube.com/watch?v=ENF9wth4kwM
+
+-- todo: svg color plugin
+-- todo: get rid of double cm in svg (tricky as also elsewhere)
+-- todo: png offsets (depth)
+-- todo: maybe collapse indices so that we have less files (harder to debug)
+-- todo: manage (read: assign) font id's in lua so we know in advance
+
+do
+
+ -- This is a prelude to something better but I'm still experimenting.
+
+ local dropins = { }
+ fonts.dropins = dropins
+ local droppedin = 0
+ local identifiers = fonts.hashes.identifiers
+
+ function dropins.nextid()
+ droppedin = droppedin - 1
+ return droppedin
+ end
+
+ function dropins.provide(method,clone,t_tfmdata,indexdata,...)
+ droppedin = dropins.nextid()
+ local t_characters = t_tfmdata.characters
+ local t_descriptions = t_tfmdata.descriptions
+ local t_properties = t_tfmdata.properties
+ local d_tfmdata = setmetatableindex({ },t_tfmdata)
+ local d_properties = setmetatableindex({ },t_properties)
+ d_tfmdata.properties = d_properties
+ if clone then
+ local d_characters = setmetatableindex({ },t_characters)
+ local d_descriptions = setmetatableindex({ },t_descriptions)
+ d_tfmdata.characters = d_characters
+ d_tfmdata.descriptions = d_descriptions
+ end
+ d_properties.instance = - droppedin -- will become an extra element in the hash
+ t_properties.virtualized = true
+ identifiers[droppedin] = d_tfmdata
+ local fonts = t_tfmdata.fonts or { }
+ t_tfmdata.fonts = fonts
+ d_properties.format = "type3"
+ d_properties.method = method
+ d_properties.indexdata = { indexdata, ... } -- can take quite some memory
+ local slot = #fonts + 1
+ fonts[slot] = { id = droppedin }
+ if not clone then
+ for k, v in next, t_characters do
+ local index = v.index
+ if index and indexdata[index] then
+ -- todo: use extender
+ v.commands = { { "slot", slot, k } }
+ end
+ end
+ else
+ -- make sure that the drop characters should get a copy with no commands
+ -- (false will do)
+ end
+ return slot, droppedin, d_tfmdata, d_properties
+ end
+
+end
+
+-- This sits here for historcal reasons so for now we keep it here.
+
+local startactualtext = nil
+local stopactualtext = nil
+
+function otf.getactualtext(s)
+ if not startactualtext then
+ startactualtext = backends.codeinjections.startunicodetoactualtextdirect
+ stopactualtext = backends.codeinjections.stopunicodetoactualtextdirect
+ end
+ return startactualtext(s), stopactualtext()
+end
+
+-- This is also somewhat specific.
+
+local sharedpalettes do
+
+ sharedpalettes = { }
+
+ local colors = attributes.list[attributes.private('color')] or { }
+ local transparencies = attributes.list[attributes.private('transparency')] or { }
+
+ function otf.registerpalette(name,values)
+ sharedpalettes[name] = values
+ local color = lpdf.color
+ local transparency = lpdf.transparency
+ local register = colors.register
+ for i=1,#values do
+ local v = values[i]
+ if v == "textcolor" then
+ values[i] = false
+ else
+ local c = nil
+ local t = nil
+ if type(v) == "table" then
+ c = register(name,"rgb",
+ max(round((v.r or 0)*255),255)/255,
+ max(round((v.g or 0)*255),255)/255,
+ max(round((v.b or 0)*255),255)/255
+ )
+ else
+ c = colors[v]
+ t = transparencies[v]
+ end
+ if c and t then
+ values[i] = color(1,c) .. " " .. transparency(t)
+ elseif c then
+ values[i] = color(1,c)
+ elseif t then
+ values[i] = color(1,t)
+ end
+ end
+ end
+ end
+
+end
+
+do
+
+ local f_color = formatters["%.3f %.3f %.3f rg"]
+ local f_gray = formatters["%.3f g"]
+
+ local hash = setmetatableindex(function(t,k)
+ local v = k
+ t[k] = v
+ return v
+ end)
+
+ local function convert(t,k)
+ local v = { }
+ for i=1,#k do
+ local p = k[i]
+ local r, g, b = p[1], p[2], p[3]
+ if r == g and g == b then
+ v[i] = hash[f_gray(r/255)]
+ else
+ v[i] = hash[f_color(r/255,g/255,b/255)]
+ end
+ end
+ t[k] = v
+ return v
+ end
+
+ local function initialize(tfmdata,kind,value) -- we really need the id ... todo
+ if value then
+ local resources = tfmdata.resources
+ local palettes = resources.colorpalettes
+ if palettes then
+ --
+ local converted = resources.converted
+ if not converted then
+ converted = setmetatableindex(convert)
+ resources.converted = converted
+ end
+ local colorvalues = sharedpalettes[value]
+ local default = false -- so the text color (bad for icon overloads)
+ if colorvalues then
+ default = colorvalues[#colorvalues]
+ else
+ colorvalues = converted[palettes[tonumber(value) or 1] or palettes[1]] or { }
+ end
+ local classes = #colorvalues
+ if classes == 0 then
+ return
+ end
+ --
+ -- todo: delay with lua function or plugin in vf handler, saves a lot of
+ -- overhead
+ --
+ local characters = tfmdata.characters
+ local descriptions = tfmdata.descriptions
+ local droppedin, tfmdrop, dropchars, dropdescs, colrshapes
+ -- alternative 1:
+ -- local slots = { }
+ -- alternative 2:
+ local idx = 255
+ local slot = 0
+ --
+ for k, v in next, characters do
+ local index = v.index
+ if index then
+ local description = descriptions[k]
+ if description then
+ local colorlist = description.colors
+ if colorlist then
+ -- alternative 1:
+ -- local fnt = div(index,256)
+ -- local idx = mod(index,256)
+ -- local slt = slots[fnt]
+ -- if not slt then
+ -- colrshapes = { }
+ -- slot, droppedin, tfmdrop = fonts.dropins.provide("color",true,tfmdata,colrshapes,colorvalues)
+ -- slt = { slot, colrshapes, tfmdrop.characters, tfmdrop.descriptions }
+ -- slots[fnt] = slt
+ -- end
+ -- slot = slt[1]
+ -- colrshapes = slt[2]
+ -- dropchars = slt[3]
+ -- dropdescs = slt[4]
+ -- alternative 2:
+ if idx >= 255 then
+ idx = 1
+ colrshapes = { }
+ slot, droppedin, tfmdrop = fonts.dropins.provide("color",true,tfmdata,colrshapes,colorvalues)
+ dropchars = tfmdrop.characters
+ dropdescs = tfmdrop.descriptions
+ else
+ idx = idx + 1
+ end
+ --
+ colrshapes[idx] = description
+ -- todo: use extender
+ local u = { "use", 0 }
+ for i=1,#colorlist do
+ u[i+2] = colorlist[i].slot
+ end
+ v.commands = { u, { "slot", slot, idx } }
+ -- hack to prevent that type 3 also gets 'use' flags .. todo
+ local c = { commands = false, index = idx, dropin = tfmdata }
+ local d = { index = idx, dropin = tfmdata }
+ setmetatableindex(c,v)
+ setmetatableindex(d,description)
+ dropchars[idx] = c
+ dropdescs[idx] = d
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ fonts.handlers.otf.features.register {
+ name = "colr",
+ description = "color glyphs",
+ manipulators = {
+ base = initialize,
+ node = initialize,
+ }
+ }
+
+end
+
+do
+
+ local report_svg = logs.reporter("fonts","svg")
+
+ local cached = true directives.register("fonts.svg.cached", function(v) cached = v end)
+
+ local function initializesvg(tfmdata,kind,value) -- hm, always value
+ if value then
+ local properties = tfmdata.properties
+ local svg = properties.svg
+ local hash = svg and svg.hash
+ local timestamp = svg and svg.timestamp
+ if not hash then
+ return
+ end
+ if cached then
+ -- we need a different hash than for mkiv, so we append:
+ local pdfhash = hash .. "-svg"
+ local pdffile = containers.read(otf.pdfcache,pdfhash)
+ local pdfshapes = pdffile and pdffile.pdfshapes
+ local pdftarget = file.join(otf.pdfcache.writable,file.addsuffix(pdfhash,"pdf"))
+ if not pdfshapes or pdffile.timestamp ~= timestamp or not next(pdfshapes) or not lfs.isfile(pdftarget) then
+ local svgfile = containers.read(otf.svgcache,hash)
+ local svgshapes = svgfile and svgfile.svgshapes
+ pdfshapes = svgshapes and metapost.svgshapestopdf(svgshapes,pdftarget,report_svg) or { }
+ containers.write(otf.pdfcache, pdfhash, {
+ pdfshapes = pdfshapes,
+ timestamp = timestamp,
+ })
+ end
+ -- this can be delayed, no need to keep this in mem
+ fonts.dropins.provide("pdf",false,tfmdata,pdfshapes)
+ else
+ local mpsfile = containers.read(otf.mpscache,hash)
+ local mpsshapes = mpsfile and mpsfile.mpsshapes
+ if not mpsshapes or mpsfile.timestamp ~= timestamp or not next(mpsshapes) then
+ local svgfile = containers.read(otf.svgcache,hash)
+ local svgshapes = svgfile and svgfile.svgshapes
+ -- still suboptimal
+ mpsshapes = svgshapes and metapost.svgshapestomp(svgshapes,report_svg) or { }
+ containers.write(otf.mpscache, hash, {
+ mpsshapes = mpsshapes,
+ timestamp = timestamp,
+ })
+ end
+ fonts.dropins.provide("mps",false,tfmdata,mpsshapes)
+ end
+ end
+ end
+
+ otfregister {
+ name = "svg",
+ description = "svg glyphs",
+ manipulators = {
+ base = initializesvg,
+ node = initializesvg,
+ }
+ }
+
+end
+
+do
+
+ -- If this is really critical we can also use a pdf file as cache but I don't expect
+ -- png fonts to remain used.
+
+ local report_png = logs.reporter("fonts","png conversion")
+
+ local function initializepng(tfmdata,kind,value) -- hm, always value
+ if value then
+ local properties = tfmdata.properties
+ local png = properties.png
+ local hash = png and png.hash
+ local timestamp = png and png.timestamp
+ if not hash then
+ return
+ end
+ local pngfile = containers.read(otf.pngcache,hash)
+ local pngshapes = pngfile and pngfile.pngshapes
+ if pngshapes then
+ fonts.dropins.provide("png",false,tfmdata,pngshapes)
+ end
+ end
+ end
+
+ otfregister {
+ name = "sbix",
+ description = "sbix glyphs",
+ manipulators = {
+ base = initializepng,
+ node = initializepng,
+ }
+ }
+
+ otfregister {
+ name = "cblc",
+ description = "cblc glyphs",
+ manipulators = {
+ base = initializepng,
+ node = initializepng,
+ }
+ }
+
+end
diff --git a/tex/context/base/mkiv/font-otl.lua b/tex/context/base/mkiv/font-otl.lua
index f8cd8217c..8bf44d79e 100644
--- a/tex/context/base/mkiv/font-otl.lua
+++ b/tex/context/base/mkiv/font-otl.lua
@@ -57,6 +57,7 @@ otf.cache = containers.define("fonts", "otl", otf.version, true)
otf.svgcache = containers.define("fonts", "svg", otf.version, true)
otf.pngcache = containers.define("fonts", "png", otf.version, true)
otf.pdfcache = containers.define("fonts", "pdf", otf.version, true)
+otf.mpscache = containers.define("fonts", "mps", otf.version, true)
otf.svgenabled = false
otf.pngenabled = false
diff --git a/tex/context/base/mkiv/font-tpk.lua b/tex/context/base/mkiv/font-tpk.lua
index 2a0810a6b..26e3fc0f0 100644
--- a/tex/context/base/mkiv/font-tpk.lua
+++ b/tex/context/base/mkiv/font-tpk.lua
@@ -269,18 +269,18 @@ do
end
local template = formatters [ [[
- %.3N 0 %i %i %i %i d1
- q
- %i 0 0 %i %i %i cm
- BI
- /W %i
- /H %i
- /IM true
- /BPC 1
- /D [1 0]
- ID %t
- EI
- Q]] ]
+%.3N 0 %i %i %i %i d1
+q
+%i 0 0 %i %i %i cm
+BI
+ /W %i
+ /H %i
+ /IM true
+ /BPC 1
+ /D [1 0]
+ID %t
+EI
+Q]] ]
function readers.pktopdf(glyph,width)
local xsize = glyph.xsize or 0
@@ -319,9 +319,7 @@ do
r = r + 1 ; result[r] = char(extract(b,8,8))
end
end
- return
- template(width,llx,lly,urx,ury,xdpi,ydpi,llx,lly,xsize,ysize,result),
- llx, lly, urx, ury
+ return template(width,llx,lly,urx,ury,xdpi,ydpi,llx,lly,xsize,ysize,result)
end
function readers.loadpk(filename)
diff --git a/tex/context/base/mkiv/grph-chk.lua b/tex/context/base/mkiv/grph-chk.lua
index fedd8d3ca..f286dedd6 100644
--- a/tex/context/base/mkiv/grph-chk.lua
+++ b/tex/context/base/mkiv/grph-chk.lua
@@ -207,7 +207,7 @@ function checkers.png(data) -- same as jpg (for now)
local used = data.used
if request and used and not request.scanimage then
local identify = graphics.identify
- local inject = lpdf.injectors.png
+ local inject = lpdf.injectors.png -- currently pdf specific
local found = false
request.scanimage = function(t)
local result = wrappedidentify(identify,t.filename)
diff --git a/tex/context/base/mkiv/grph-img.lua b/tex/context/base/mkiv/grph-img.lua
index cde9c8442..795ea4827 100644
--- a/tex/context/base/mkiv/grph-img.lua
+++ b/tex/context/base/mkiv/grph-img.lua
@@ -29,6 +29,8 @@ local skipbytes = files.skip
local setposition = files.setposition
local getposition = files.getposition
+local newreader = io.newreader
+
local setmetatableindex = table.setmetatableindex
local setmetatablecall = table.setmetatablecall
@@ -543,8 +545,8 @@ do
if once then
for i=1,#t do
local l = t[i]
- setposition(f,l.offset)
- t[i] = readstring(f,l.length)
+ f:setposition(l.offset)
+ t[i] = f:readstring(l.length)
end
local data = concat(t)
-- t wiped in caller
@@ -553,14 +555,14 @@ do
local data = { }
for i=1,#t do
local l = t[i]
- setposition(f,l.offset)
- data[i] = readstring(f,l.length)
+ f:setposition(l.offset)
+ data[i] = f:readstring(l.length)
end
return concat(data)
end
end
- function identifiers.png(filename)
+ function identifiers.png(filename,method)
local specification = {
filename = filename,
filetype = "png",
@@ -569,7 +571,7 @@ do
specification.error = "invalid filename"
return specification -- error
end
- local f = io.open(filename,"rb")
+ local f = newreader(filename,method)
if not f then
specification.error = "unable to open file"
return specification -- error
@@ -581,44 +583,44 @@ do
specification.pagenum = 1
specification.offset = 0
specification.length = 0
- local filesize = getsize(f) -- seek end
+ local filesize = f:getsize() -- seek end
local tables = { }
- local banner = readstring(f,8)
+ local banner = f:readstring(8)
if banner ~= "\137PNG\013\010\026\010" then
specification.error = "no png file"
return specification -- error
end
while true do
- local position = getposition(f)
+ local position = f:getposition()
if position >= filesize then
break
end
- local length = readcardinal4(f)
+ local length = f:readcardinal4()
if not length then
break
end
- local kind = readstring(f,4)
+ local kind = f:readstring(4)
if kind then
kind = lower(kind)
else
break
end
if kind == "ihdr" then -- metadata
- specification.xsize = readcardinal4(f)
- specification.ysize = readcardinal4(f)
- specification.colordepth = readcardinal(f)
- specification.colorspace = readcardinal(f)
- specification.compression = readcardinal(f)
- specification.filter = readcardinal(f)
- specification.interlace = readcardinal(f)
+ specification.xsize = f:readcardinal4()
+ specification.ysize = f:readcardinal4()
+ specification.colordepth = f:readcardinal()
+ specification.colorspace = f:readcardinal()
+ specification.compression = f:readcardinal()
+ specification.filter = f:readcardinal()
+ specification.interlace = f:readcardinal()
tables[kind] = true
elseif kind == "iend" then
tables[kind] = true
break
elseif kind == "phys" then
- local x = readcardinal4(f)
- local y = readcardinal4(f)
- local u = readcardinal(f)
+ local x = f:readcardinal4()
+ local y = f:readcardinal4()
+ local u = f:readcardinal()
if u == 1 then -- meters
-- x = round(0.0254 * x)
-- y = round(0.0254 * y)
@@ -633,14 +635,16 @@ do
tables[kind] = t
end
t[#t+1] = {
- offset = getposition(f),
+-- offset = getposition(f),
+ offset = f:getposition(),
length = length,
}
else
tables[kind] = true
end
- setposition(f,position+length+12) -- #size #kind #crc
+ f:setposition(position+length+12) -- #size #kind #crc
end
+ f:close()
specification.tables = tables
return specification
end
diff --git a/tex/context/base/mkiv/lpdf-emb.lua b/tex/context/base/mkiv/lpdf-emb.lua
index dbf6fd4d3..1cdcac1ae 100644
--- a/tex/context/base/mkiv/lpdf-emb.lua
+++ b/tex/context/base/mkiv/lpdf-emb.lua
@@ -45,7 +45,9 @@ local report_fonts = logs.reporter("backend","fonts")
local trace_fonts = false
local trace_details = false
-local dimenfactors = number.dimenfactors
+local dimenfactors = number.dimenfactors
+local bpfactor = dimenfactors.bp
+local ptfactor = dimenfactors.pt
trackers.register("backend.pdf.fonts",function(v) trace_fonts = v end)
@@ -1360,7 +1362,7 @@ do
-- This is a hack, we have no basepoints.
correction = details.fontdata.parameters.size / 1000
-- And this needs checking.
- correction = correction * dimenfactors.bp / dimenfactors.pt
+ correction = correction * bpfactor / ptfactor
metadata = { }
end
@@ -1510,114 +1512,296 @@ do
mainwriters["opentype"](details)
end
- -- todo: map pdf glyphs onto companion type 3 font .. can be set of small
- -- ones. maybe only private codes with proper tounicode
+ do
+
+ -- The methods might become plugins.
+
+ local methods = { }
+
+ local pdfimage = lpdf.epdf.image
+ local openpdf = pdfimage.open
+ local closepdf = pdfimage.close
+ local copypage = pdfimage.copy
+
+ local embedimage = images.embed
+
+ local f_glyph = formatters["G%d"]
+ local f_char = formatters["BT /V%d 1 Tf [<%04X>] TJ ET"]
+ local f_width = formatters["%.6N 0 d0"]
+ local f_index = formatters["I%d"]
+ local f_image = formatters["%.6N 0 d0 /%s Do"]
+ local f_stream = formatters["%.6N 0 d0 %s"]
- local methods = { }
+ -- A type 3 font has at most 256 characters and Acrobat also wants a zero slot
+ -- to be filled. We can share a mandate zero slot character.
- function methods.pk(filename)
- local resolution = 600
- local widthfactor = resolution / 72
- local scalefactor = 72 / resolution / 10
- local pkfullname = resolvers.findpk(basedfontname,resolution)
- if not pkfullname or pkfullname == "" then
- return
+ local c_notdef = nil
+ local r_notdef = nil
+ local w_notdef = nil
+ local fontbbox = nil
+
+ -- pk inclusion (not really tested but not really used either)
+
+ function methods.pk(filename)
+ local resolution = 600
+ local widthfactor = resolution / 72
+ local scalefactor = 72 / resolution / 10
+ local pkfullname = resolvers.findpk(basedfontname,resolution)
+ if not pkfullname or pkfullname == "" then
+ return
+ end
+ local readers = fonts.handlers.tfm.readers
+ local result = readers.loadpk(pkfullname)
+ if not result or result.error then
+ return
+ end
+ return result.glyphs, widthfactor / 65536, scalefactor, readers.pktopdf
end
- local readers = fonts.handlers.tfm.readers
- local result = readers.loadpk(pkfullname)
- if not result or result.error then
- return
+
+ -- pdf inclusion
+
+ function methods.pdf(filename,details)
+ local properties = details.properties
+ local pdfshapes = properties.indexdata[1]
+ local pdfdoc = openpdf(pdfshapes.filename)
+ local xforms = pdfdictionary()
+ local nofglyphs = 0
+ if pdfdoc then
+ local function pdftopdf(glyph,width,data)
+ local image = copypage(pdfdoc,glyph)
+ width = 100 * width * bpfactor
+ embedimage(image)
+ nofglyphs = nofglyphs + 1
+ local name = f_glyph(nofglyphs)
+ xforms[name] = pdfreference(image.objnum)
+ local pdf = f_image(width,name)
+ return pdf, width
+ end
+ local function closepdf()
+ -- closepdf(pdfdoc)
+ end
+ local function getresources()
+ return pdfdictionary { XObject = xforms }
+ end
+ return pdfshapes, 1, 0.001, pdftopdf, closepdf, getresources
+ end
end
- return result.glyphs, widthfactor / 65536, scalefactor, readers.pktopdf
- end
- mainwriters["type3"] = function(details)
- local properties = details.properties
- local basefontname = details.basefontname or properties.name
- local askedmethod = "pk"
- local method = methods[askedmethod]
- if not method then
- return
+ -- mps inclusion
+
+ function methods.mps(filename,details)
+ local properties = details.properties
+ local mpshapes = properties.indexdata[1]
+ local function mpstopdf(mp,width,data)
+ local pdf = metapost.simple("metafun",mp,true) -- can be sped up, minifun
+ local width = 100 * width * bpfactor
+ local stream = f_stream(width,pdf)
+ return stream, width
+ end
+ local function getresources()
+ return lpdf.collectedresources {
+ serialize = false,
+ }
+ end
+ return mpshapes, 1, 0.001, mpstopdf, nil, getresources
end
- local glyphs, widthfactor, scalefactor, glyphtopdf = method(basedfontname)
- if not glyphs then
- return
+
+ -- png inclusion
+
+ -- With d1 the image mask is used when given and obeys color. So it is needed for pure bw
+ -- bitmap fonts, so here we really need d0.
+ --
+ -- Acrobat X pro only seems to see the image mask but other viewers are doing it ok. Acrobat
+ -- reader crashes. We really need to add a notdef!
+
+ function methods.png(filename,details)
+ local properties = details.properties
+ local png = properties.png
+ local hash = png.hash
+ local pngshapes = properties.indexdata[1]
+ local xforms = pdfdictionary()
+ local nofglyphs = 0
+ if pngshapes then
+ local function pngtopdf(glyph,width,data)
+ local info = graphics.identifiers.png(glyph.data,"string")
+ local image = lpdf.injectors.png(info,"string")
+ local bbox = image.bbox
+ local llx = bbox[1] * bpfactor
+ local lly = bbox[2] * bpfactor
+ local urx = bbox[3] * bpfactor
+ local ury = bbox[4] * bpfactor
+ width = width * bpfactor / 10
+ embedimage(image)
+ nofglyphs = nofglyphs + 1
+ local name = f_glyph(nofglyphs)
+ xforms[name] = pdfreference(image.objnum)
+ local pdf = f_image(width,name)
+ return pdf, width
+ end
+ local function closepng()
+ pngshapes = nil
+ end
+ local function getresources()
+ return pdfdictionary { XObject = xforms }
+ end
+ return pngshapes, 1, 1, pngtopdf, closepng, getresources
+ end
end
- local parameters = details.parameters
- local object = details.objectnumber
- local factor = parameters.factor -- normally 1
- local f_name = formatters["I%05i"]
- local fontmatrix = pdfarray { scalefactor, 0, 0, scalefactor, 0, 0 }
- local indices,
- minindex,
- maxindex = collectindices(details.fontdata.characters,details.indices)
- local widths = pdfarray()
- local differences = pdfarray()
- local charprocs = pdfdictionary()
- local basefont = pdfconstant(basefontname)
- local llx, lly, urx, ury = 0, 0, 0, 0
- for i=1,maxindex-minindex+1 do
- widths[i] = 0
+
+ function methods.color(filename,details)
+ local colrshapes = details.properties.indexdata[1]
+ local colrvalues = details.properties.indexdata[2]
+ local usedfonts = { }
+ local dd = details.fontdata.descriptions -- temp hack
+ local function colrtopdf(description,wd,data) -- todo: chardata instead of descriptions
+ -- descriptions by index
+ local colorlist = description.colors
+ if colorlist then
+ local dropdata = data.dropin
+ local dropid = dropdata.properties.id
+ local dropunits = dropdata.parameters.units
+ usedfonts[dropid] = dropid
+
+ local w = description.width or 0
+ local s = #colorlist
+ local l = nil
+ local t = { f_width(w) }
+ local n = 1
+ local d = colrvalues[#colrvalues]
+
+ for i=1,s do
+ local entry = colorlist[i]
+ local v = colrvalues[entry.class] or d
+ if v and l ~= v then
+ n = n + 1 ; t[n] = v
+ l = v
+ end
+ local e = dd[entry.slot]
+ if e then
+ n = n + 1 ; t[n] = f_char(dropid,e.index)
+ end
+ end
+ return concat(t," "), w / dropunits
+ end
+ end
+ local function getresources()
+ return lpdf.collectedresources {
+ serialize = false,
+ fonts = usedfonts,
+ fontprefix = "V",
+ }
+ end
+ return colrshapes, 1, 1, colrtopdf, false, getresources
end
- local d = 0
- local lastindex = -0xFFFF
- for index, data in sortedhash(indices) do
- local name = f_name(index)
- local glyph = glyphs[index]
- if glyph then
- local width = widthfactor * data.width
- local stream, lx, ly, ux, uy = glyphtopdf(glyph,width)
- if stream then
- if index - 1 ~= lastindex then
- d = d + 1 ; differences[d] = index
+
+ mainwriters["type3"] = function(details)
+ local properties = details.properties
+ local basefontname = details.basefontname or properties.name
+ local askedmethod = properties.method or "pk"
+ local method = methods[askedmethod] or methods.pk
+ if not method then
+ return
+ end
+ local glyphs, widthfactor, scalefactor,
+ glyphtopdf, reset, getresources = method(basedfontname,details)
+ if not glyphs then
+ return
+ end
+ local parameters = details.parameters
+ local object = details.objectnumber
+ local factor = parameters.factor -- normally 1
+ local fontmatrix = pdfarray { scalefactor, 0, 0, scalefactor, 0, 0 }
+ local indices,
+ minindex,
+ maxindex = collectindices(details.fontdata.characters,details.indices)
+ local widths = pdfarray()
+ local differences = pdfarray()
+ local charprocs = pdfdictionary()
+ local basefont = pdfconstant(basefontname)
+ local d = 0
+ local forcenotdef = minindex > 0
+ local lastindex = -0xFF
+
+ if forcenotdef then
+ widths[0] = 0
+ minindex = 0
+ lastindex = 0
+ d = 2
+ if not c_notdef then
+ w_notdef = 0
+ c_notdef = pdfconstant(".notdef")
+ r_notdef = pdfreference(pdfflushstreamobject("0 0 d0"))
+ end
+ differences[1] = w_notdef
+ differences[2] = c_notdef
+ charprocs[".notdef"] = r_notdef
+ end
+
+ for i=1,maxindex-minindex+1 do
+ widths[i] = 0
+ end
+
+ for index, data in sortedhash(indices) do
+ local name = f_index(index)
+ local glyph = glyphs[index]
+ if glyph then
+ local width = widthfactor * data.width
+ local stream, wd = glyphtopdf(glyph,width,data)
+ if stream then
+ if wd then
+ width = wd
+ end
+ if index - 1 ~= lastindex then
+ d = d + 1 differences[d] = index
+ end
+ lastindex = index
+ d = d + 1 differences[d] = pdfconstant(name)
+ charprocs[name] = pdfreference(pdfflushstreamobject(stream))
+ widths[index-minindex+1] = width
end
- lastindex = index
- d = d + 1 ; differences[d] = pdfconstant(name)
- charprocs[name] = pdfreference(pdfflushstreamobject(stream))
- widths[index-minindex+1] = width
- if lx < llx then llx = lx end
- if ux > urx then urx = ux end
- if ly < lly then lly = ly end
- if uy > ury then ury = uy end
+ else
+ report_fonts("missing glyph %i in type3 font %a",index,basefontname)
end
end
+ if not fontbbox then
+ -- The specification permits zero values and these are actually also more
+ -- robust as then there are no assumptions and no accuracy is needed.
+ fontbbox = pdfarray { 0, 0, 0, 0 }
+ end
+ local encoding = pdfdictionary {
+ Type = pdfconstant("Encoding"),
+ Differences = differences,
+ }
+ local tounicode = tounicodedictionary(details,indices,maxindex,basefontname)
+ local resources = getresources and getresources() or lpdf.procset(true)
+ local descriptor = pdfdictionary {
+ -- most is optional in type3
+ Type = pdfconstant("FontDescriptor"),
+ FontName = basefont,
+ Flags = 4,
+ ItalicAngle = 0,
+ }
+ local parent = pdfdictionary {
+ Type = pdfconstant("Font"),
+ Subtype = pdfconstant("Type3"),
+ Name = basefont,
+ FontBBox = fontbbox,
+ FontMatrix = fontmatrix,
+ CharProcs = pdfreference(pdfflushobject(charprocs)),
+ Encoding = pdfreference(pdfflushobject(encoding)),
+ FirstChar = minindex,
+ LastChar = maxindex,
+ Widths = pdfreference(pdfflushobject(widths)),
+ FontDescriptor = pdfreference(pdfflushobject(descriptor)),
+ Resources = resources,
+ ToUnicode = pdfreference(pdfflushstreamobject(tounicode)),
+ }
+ pdfflushobject(object,parent)
+ if reset then
+ reset()
+ end
end
- local fontbbox = pdfarray { llx, lly, urx, ury }
- local encoding = pdfdictionary {
- Type = pdfconstant("Encoding"),
- Differences = differences,
- }
- local tounicode = tounicodedictionary(details,indices,maxindex,basefontname)
- local descriptor = pdfdictionary {
- Type = pdfconstant("FontDescriptor"),
- FontName = basefont,
- Flags = 4,
- FontBBox = fontbbox,
- -- Ascent = scale(ascender),
- -- Descent = scale(descender),
- -- ItalicAngle = round(italicangle or 0),
- -- CapHeight = scale(capheight),
- -- StemV = scale(stemv),
- -- XHeight = scale(xheight),
- -- Metadata = fontmeta and pdfreference(pdfflushstreamobject(fontmeta)) or nil,
- }
- local parent = pdfdictionary {
- Type = pdfconstant("Font"),
- Subtype = pdfconstant("Type3"),
- Name = basefont,
- FontBBox = fontbbox,
- FontMatrix = fontmatrix,
- CharProcs = pdfreference(pdfflushobject(charprocs)),
- Encoding = pdfreference(pdfflushobject(encoding)),
- FirstChar = minindex,
- LastChar = maxindex,
- Widths = pdfreference(pdfflushobject(widths)),
- FontDescriptor = pdfreference(pdfflushobject(descriptor)),
- Resources = lpdf.procset(true),
- ToUnicode = pdfreference(pdfflushstreamobject(tounicode)),
- }
- pdfflushobject(reserved,descriptor)
- pdfflushobject(object,parent)
+
end
end
@@ -1650,7 +1834,8 @@ local objects = setmetatableindex(function(t,k)
report_fonts("font id %i bound to hash %s and object %i",k,h,v)
end
else
- report_fonts("fatal error, hash %s asked but not used",k,h,v)
+ -- no problem as it can be svg only
+ -- report_fonts("fatal error, hash %s asked but not used",k,h,v)
v = pdfreserveobject()
t[k] = v
end
@@ -1752,7 +1937,8 @@ function lpdf.flushfonts()
end
local properties = details.properties
local bitmap = properties.usedbitmap
- if bitmap then
+ local method = properties.method -- will be pk | pdf | svg | ...
+ if bitmap or method then
local format = "type3"
local writer = mainwriters[format]
if writer then
@@ -1786,7 +1972,7 @@ function lpdf.flushfonts()
local vector = encoding.vector
local indices = details.indices
local remapped = { }
- local factor = dimenfactors.bp * size / 65536
+ local factor = bpfactor * size / 65536
for k, v in next, indices do
local name = vector[k]
local index = reverse[name] or 0
@@ -1826,7 +2012,7 @@ function lpdf.flushfonts()
local vector = encoding.vector
local indices = details.indices
local remapped = { }
- local factor = dimenfactors.bp * size / 65536
+ local factor = bpfactor * size / 65536
for k, v in next, indices do
local name = vector[k]
local index = reverse[name] or 0
@@ -1868,8 +2054,11 @@ function lpdf.flushfonts()
report_fonts("no %a writer for %a",format,filename)
end
end
- else
- report_fonts("no indices for %a",filename)
+ else -- not problem for svg ...
+ -- report_fonts("no indices for %a",filename)
+ end
+ if trace_fonts then
+ report_fonts("embedded indices: % t",table.sortedkeys(details.indices))
end
mainfonts[details.hash] = false -- done
end
diff --git a/tex/context/base/mkiv/lpdf-img.lua b/tex/context/base/mkiv/lpdf-img.lua
index 64ed642fa..6b19f0cfd 100644
--- a/tex/context/base/mkiv/lpdf-img.lua
+++ b/tex/context/base/mkiv/lpdf-img.lua
@@ -33,6 +33,8 @@ local openstring = streams.openstring
local readstring = streams.readstring
local readbytetable = streams.readbytetable
+local newreader = io.newreader
+
local tobytetable = string.bytetable
local lpdf = lpdf or { }
@@ -871,7 +873,7 @@ do
end
end)
- function injectors.png(specification)
+ function injectors.png(specification,method) -- todo: method in specification
-- inspect(specification)
if specification.error then
return
@@ -901,7 +903,7 @@ do
if not idat then
return
end
- local pngfile = io.open(filename,"rb") -- todo: in-mem too
+ local pngfile = newreader(filename,method)
if not pngfile then
return
end
@@ -1094,8 +1096,10 @@ do
if specification.colorref then
xobject.ColorSpace = pdfreference(specification.colorref)
end
+ local width = specification.width or xsize * 65536
+ local height = specification.height or ysize * 65536
return createimage {
- bbox = { 0, 0, specification.width/xsize, specification.height/ysize }, -- mandate
+ bbox = { 0, 0, width/xsize, height/ysize }, -- mandate
transform = specification.transform,
nolength = true,
nobbox = true,
@@ -1204,14 +1208,14 @@ do
local content = pack(specification,index and "indexed" or "data")
local mask = specification.mask
local colorspace = pdfconstant(colorspace)
-if index then
- colorspace = pdfarray {
- pdfconstant("Indexed"),
- colorspace,
- #index + (index[0] and 0 or -1), -- upper index
- pdfverbose(pack(specification,"index"))
- }
-end
+ if index then
+ colorspace = pdfarray {
+ pdfconstant("Indexed"),
+ colorspace,
+ #index + (index[0] and 0 or -1), -- upper index
+ pdfverbose(pack(specification,"index"))
+ }
+ end
local xobject = pdfdictionary {
Type = pdfconstant("XObject"),
Subtype = pdfconstant("Image"),
diff --git a/tex/context/base/mkiv/lpdf-ini.lua b/tex/context/base/mkiv/lpdf-ini.lua
index 19654a3d9..fd1e52aad 100644
--- a/tex/context/base/mkiv/lpdf-ini.lua
+++ b/tex/context/base/mkiv/lpdf-ini.lua
@@ -12,7 +12,7 @@ local setmetatable, getmetatable, type, next, tostring, tonumber, rawset = setme
local char, byte, format, gsub, concat, match, sub, gmatch = string.char, string.byte, string.format, string.gsub, table.concat, string.match, string.sub, string.gmatch
local utfchar, utfbyte, utfvalues = utf.char, utf.byte, utf.values
local sind, cosd, max, min = math.sind, math.cosd, math.max, math.min
-local sort = table.sort
+local sort, sortedhash = table.sort, table.sortedhash
local P, C, R, S, Cc, Cs, V = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.Cc, lpeg.Cs, lpeg.V
local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns
local formatters = string.formatters
@@ -170,16 +170,16 @@ end
local pdfgetpagereference
updaters.register("backend.update.lpdf",function()
- pdfreserveobject = pdf.reserveobj
- pdfimmediateobject = pdf.immediateobj
- pdfdeferredobject = pdf.obj
- pdfreferenceobject = pdf.refobj
+ pdfreserveobject = pdf.reserveobj
+ pdfimmediateobject = pdf.immediateobj
+ pdfdeferredobject = pdf.obj
+ pdfreferenceobject = pdf.refobj
- pdfgetpagereference = pdf.getpageref
+ pdfgetpagereference = pdf.getpageref
- pdfsetpageresources = pdf.setpageresources
- pdfsetpageattributes = pdf.setpageattributes
- pdfsetpagesattributes = pdf.setpagesattributes
+ pdfsetpageresources = pdf.setpageresources
+ pdfsetpageattributes = pdf.setpageattributes
+ pdfsetpagesattributes = pdf.setpagesattributes
end)
local jobpositions = job.positions
@@ -1253,32 +1253,47 @@ do
-- todo: share them
-- todo: force when not yet set
+ local f_font = formatters["%s%d"]
+
function lpdf.collectedresources(options)
local ExtGState = d_extgstates and next(d_extgstates ) and p_extgstates
local ColorSpace = d_colorspaces and next(d_colorspaces) and p_colorspaces
local Pattern = d_patterns and next(d_patterns ) and p_patterns
local Shading = d_shades and next(d_shades ) and p_shades
+ local Font
if options and options.patterns == false then
Pattern = nil
end
- if ExtGState or ColorSpace or Pattern or Shading then
+ local fonts = options and options.fonts
+ if fonts and next(fonts) then
+ local pdfgetfontobjnumber = lpdf.getfontobjnumber
+ if pdfgetfontobjnumber then
+ local prefix = options.fontprefix or "F"
+ Font = pdfdictionary { }
+ for k, v in sortedhash(fonts) do
+ Font[f_font(prefix,v)] = pdfreference(pdfgetfontobjnumber(k))
+ end
+ end
+ end
+ if ExtGState or ColorSpace or Pattern or Shading or Fonts then
local collected = pdfdictionary {
ExtGState = ExtGState,
ColorSpace = ColorSpace,
Pattern = Pattern,
Shading = Shading,
+ Font = Font,
}
if options and options.serialize == false then
return collected
else
return collected()
end
+ elseif options and options.notempty then
+ return nil
+ elseif options and options.serialize == false then
+ return pdfdictionary { }
else
- if options and options.serialize == false then
- return pdfdictionary { }
- else
- return ""
- end
+ return ""
end
end
@@ -1487,41 +1502,23 @@ end
do
- local f_actual_text_one = formatters["BT /Span << /ActualText <feff%04x> >> BDC %s EMC ET"]
- local f_actual_text_two = formatters["BT /Span << /ActualText <feff%04x%04x> >> BDC %s EMC ET"]
- local f_actual_text_one_b = formatters["BT /Span << /ActualText <feff%04x> >> BDC"]
- local f_actual_text_two_b = formatters["BT /Span << /ActualText <feff%04x%04x> >> BDC"]
+ local f_actual_text_p = formatters["BT /Span << /ActualText <feff%s> >> BDC %s EMC ET"]
local f_actual_text_b = formatters["BT /Span << /ActualText <feff%s> >> BDC"]
local s_actual_text_e = "EMC ET"
local f_actual_text_b_not = formatters["/Span << /ActualText <feff%s> >> BDC"]
- local f_actual_text_one_b_not = formatters["/Span << /ActualText <feff%04x> >> BDC"]
- local f_actual_text_two_b_not = formatters["/Span << /ActualText <feff%04x%04x> >> BDC"]
local s_actual_text_e_not = "EMC"
local f_actual_text = formatters["/Span <</ActualText %s >> BDC"]
local context = context
local pdfdirect = nodes.pool.directliteral -- we can use nuts.write deep down
-
- -- todo: use tounicode from the font mapper
-
- -- floor(unicode/1024) => rshift(unicode,10) -- better for 5.3
+ local tounicode = fonts.mappings.tounicode
function codeinjections.unicodetoactualtext(unicode,pdfcode)
- if unicode < 0x10000 then
- return f_actual_text_one(unicode,pdfcode)
- else
- return f_actual_text_two(rshift(unicode,10)+0xD800,unicode%1024+0xDC00,pdfcode)
- end
+ return f_actual_text_p(type(unicode) == "string" and unicode or tounicode(unicode),pdfcode)
end
function codeinjections.startunicodetoactualtext(unicode)
- if type(unicode) == "string" then
- return f_actual_text_b(unicode)
- elseif unicode < 0x10000 then
- return f_actual_text_one_b(unicode)
- else
- return f_actual_text_two_b(rshift(unicode,10)+0xD800,unicode%1024+0xDC00)
- end
+ return f_actual_text_b(type(unicode) == "string" and unicode or tounicode(unicode))
end
function codeinjections.stopunicodetoactualtext()
@@ -1529,13 +1526,7 @@ do
end
function codeinjections.startunicodetoactualtextdirect(unicode)
- if type(unicode) == "string" then
- return f_actual_text_b_not(unicode)
- elseif unicode < 0x10000 then
- return f_actual_text_one_b_not(unicode)
- else
- return f_actual_text_two_b_not(rshift(unicode,10)+0xD800,unicode%1024+0xDC00)
- end
+ return f_actual_text_b_not(type(unicode) == "string" and unicode or tounicode(unicode))
end
function codeinjections.stopunicodetoactualtextdirect()
diff --git a/tex/context/base/mkiv/lpdf-lmt.lua b/tex/context/base/mkiv/lpdf-lmt.lua
index 709a9d48a..8b40ee15c 100644
--- a/tex/context/base/mkiv/lpdf-lmt.lua
+++ b/tex/context/base/mkiv/lpdf-lmt.lua
@@ -37,6 +37,7 @@ local formatters, splitupstring = string.formatters, string.splitup
local band, extract = bit32.band, bit32.extract
local concat, sortedhash = table.concat, table.sortedhash
local setmetatableindex = table.setmetatableindex
+local loaddata = io.loaddata
local bpfactor <const> = number.dimenfactors.bp
@@ -49,7 +50,6 @@ local tonut = nodes.tonut
local getdata = nuts.getdata
local getsubtype = nuts.getsubtype
-local getfield = nuts.getfield
local getwhd = nuts.getwhd
local flushlist = nuts.flush_list
@@ -583,6 +583,15 @@ local flushcharacter do
end
+ flushfontchar = function(font,char,data)
+ local dummy = usedfonts[font]
+ local index = data.index or char
+ if not pdfcharacters[index] then
+ pdfcharacters[index] = true
+ end
+ return dummy
+ end
+
end
-- literals
@@ -1410,18 +1419,26 @@ local function finalize(driver,details)
pageresources.XObject = xforms
pageresources.ProcSet = lpdf.procset()
+ local xorigin, yorigin, relocated = backends.codeinjections.getpageorigin() -- for now here
+
+ local bbox = pdfarray {
+ (boundingbox[1] + xorigin) * bpfactor,
+ (boundingbox[2] + yorigin) * bpfactor,
+ (boundingbox[3] + xorigin) * bpfactor,
+ (boundingbox[4] + yorigin) * bpfactor,
+ }
+
+ if relocated then
+ content = formatters["1 0 0 1 %.6N %.6N cm\n%s"](bbox[1],bbox[2],content)
+ end
+
local contentsobj = pdfflushstreamobject(content,false,false)
pageattributes.Type = pdf_page
pageattributes.Contents = pdfreference(contentsobj)
pageattributes.Resources = pageresources
-- pageattributes.Resources = pdfreference(pdfflushobject(pageresources))
- pageattributes.MediaBox = pdfarray {
- boundingbox[1] * bpfactor,
- boundingbox[2] * bpfactor,
- boundingbox[3] * bpfactor,
- boundingbox[4] * bpfactor,
- }
+ pageattributes.MediaBox = bbox
pageattributes.Parent = nil -- precalculate
pageattributes.Group = nil -- todo
@@ -1431,6 +1448,12 @@ local function finalize(driver,details)
lpdf.finalizepage(true)
+ if relocated then
+ if pageattributes.TrimBox then pageattributes.TrimBox = box end
+ if pageattributes.CropBox then pageattributes.CropBox = box end
+ if pageattributes.BleedBox then pageattributes.BleedBox = box end
+ end
+
else
local xformtype = specification.type or 0
@@ -1850,18 +1873,18 @@ local function obj(a,b,c,d)
nolength = a.nolength
if kind == "stream" then
if filename then
- data = io.loaddata(filename) or ""
+ data = loaddata(filename) or ""
end
elseif kind == "raw"then
if filename then
- data = io.loaddata(filename) or ""
+ data = loaddata(filename) or ""
end
elseif kind == "file"then
kind = "raw"
- data = filename and io.loaddata(filename) or ""
+ data = filename and loaddata(filename) or ""
elseif kind == "streamfile" then
kind = "stream"
- data = filename and io.loaddata(filename) or ""
+ data = filename and loaddata(filename) or ""
end
else
if argtype == "number" then
@@ -1877,10 +1900,10 @@ local function obj(a,b,c,d)
data = b
elseif a == "file" then
-- kind = "raw"
- data = io.loaddata(b)
+ data = loaddata(b)
elseif a == "streamfile" then
kind = "stream"
- data = io.loaddata(b)
+ data = loaddata(b)
else
data = "" -- invalid object
end
@@ -2312,10 +2335,13 @@ end)
updaters.register("backend.update.lpdf",function()
- -- for the moment here, todo: an md5 or sha2 hash can save space
+ -- todo: an md5 or sha2 hash can save space
+ -- todo: make a type 3 font instead
+ -- todo: move to lpdf namespace
local pdfimage = lpdf.epdf.image
local newpdf = pdfimage.new
+ local openpdf = pdfimage.open
local closepdf = pdfimage.close
local copypage = pdfimage.copy
@@ -2325,7 +2351,7 @@ updaters.register("backend.update.lpdf",function()
local topdf = { }
local toidx = { }
- local function storedata(pdf)
+ local function storedata_s(pdf)
local idx = toidx[pdf]
if not idx then
nofstreams = nofstreams + 1
@@ -2336,11 +2362,7 @@ updaters.register("backend.update.lpdf",function()
return idx
end
- -- todo: make a type 3 font instead
-
- -- move to lpdf namespace
-
- local function vfimage(id,wd,ht,dp,pos_h,pos_v)
+ local function vfimage_s(id,wd,ht,dp,pos_h,pos_v)
local index = topdf[id]
if type(index) == "string" then
local pdfdoc = newpdf(index,#index)
@@ -2356,11 +2378,51 @@ updaters.register("backend.update.lpdf",function()
flushimage(index,wd,ht,dp,pos_h,pos_v)
end
+ local function storedata_n(name,page)
+ local idx = toidx[pdf]
+ if not idx then
+ nofstreams = nofstreams + 1
+ idx = nofstreams
+ toidx[pdf] = nofstreams
+ topdf[idx] = pdf
+ end
+ return idx
+ end
+
+ -- We need to have a way to close such a pdf ... esp for fonts.
+
+ local pdfdocs = { }
+
+ local function vfimage_n(name,page,wd,ht,dp,pos_h,pos_v)
+ local d = pdfdocs[name]
+ if not d then
+ d = { doc = openpdf(name), pages = { } }
+ pdfdocs[name] = d
+ end
+ local index = d.pages[page]
+ if not index then
+ local image = copypage(d.doc,page)
+ local bbox = image.bbox
+ image.width = bbox[3] - bbox[1]
+ image.height = bbox[4] - bbox[2]
+ embedimage(image)
+ index = image.index
+ d.pages[page] = index
+ end
+ flushimage(index,wd,ht,dp,pos_h,pos_v)
+ end
+
local function pdfvfimage(wd,ht,dp,data,name)
- return { "lua", function(font,char,pos_h,pos_v)
- local id = storedata(data)
- vfimage(id,wd,ht,dp,pos_h,pos_v)
- end }
+ if type(data) == "number" then
+ return { "lua", function(font,char,pos_h,pos_v)
+ vfimage_n(name,data,wd,ht,dp,pos_h,pos_v)
+ end }
+ else
+ return { "lua", function(font,char,pos_h,pos_v)
+ local id = storedata_s(data)
+ vfimage_s(id,wd,ht,dp,pos_h,pos_v)
+ end }
+ end
end
lpdf.vfimage = pdfvfimage
@@ -2482,6 +2544,7 @@ do
name = "pdf",
flushers = {
character = flushcharacter,
+ fontchar = flushfontchar,
rule = flushrule,
simplerule = flushsimplerule,
pushorientation = pushorientation,
diff --git a/tex/context/base/mkiv/lxml-css.lua b/tex/context/base/mkiv/lxml-css.lua
index b0f5c9b72..8f7f19e84 100644
--- a/tex/context/base/mkiv/lxml-css.lua
+++ b/tex/context/base/mkiv/lxml-css.lua
@@ -146,9 +146,11 @@ if context then
end
+local p_digit = lpeg.patterns.digit
+
local pattern = Cf( Ct("") * (
Cg(
- Cc("style") * (
+ Cc("style") * (
C("italic")
+ C("oblique")
+ C("slanted") / "oblique"
@@ -156,12 +158,17 @@ local pattern = Cf( Ct("") * (
+ Cc("variant") * (
(C("smallcaps") + C("caps")) / "small-caps"
)
- + Cc("weight") *
+ + Cc("weight") * (
C("bold")
- + Cc("family") * (
- (C("mono") + C("type")) / "monospace" -- just ignore the "space(d)"
+ )
+ + Cc("family") * (
+ (C("mono") + C("type")) / "monospace" -- just ignore the "space(d)"
+ (C("sansserif") + C("sans")) / "sans-serif" -- match before serif
- + C("serif")
+ + C("serif")
+ )
+ + Cc("size") * Ct (
+ (S("+-")^0 * (p_digit^0 * P(".") * p_digit^1 + p_digit^1 * P(".") + p_digit^1)) / tonumber
+ * C(P("p") * S("txc") + P("e") * S("xm") + S("mc") * P("m") + P("in") + P("%"))
)
)
--+ P("\\") * (
diff --git a/tex/context/base/mkiv/meta-fig.mkiv b/tex/context/base/mkiv/meta-fig.mkiv
index bf37aa7bf..e89cb1442 100644
--- a/tex/context/base/mkiv/meta-fig.mkiv
+++ b/tex/context/base/mkiv/meta-fig.mkiv
@@ -44,12 +44,17 @@
\definefittingpage
[MPpage]
[\c!align=,
- \c!command=\meta_process_graphic_instance{\fittingpageparameter\c!instance},
+ \c!command=\meta_process_graphic_command,
\c!instance=]
\unexpanded\def\setupMPpage
{\setupfittingpage[MPpage]}
+\unexpanded\def\meta_process_graphic_command
+ {\doif{\fittingpageparameter\c!alternative}\v!offset
+ {\def\meta_relocate_graphic{\clf_setpageorigin\MPllx\MPlly\relax}}%
+ \meta_process_graphic_instance{\fittingpageparameter\c!instance}}
+
%D \macros
%D {MPfigure}
%D
diff --git a/tex/context/base/mkiv/meta-ini.mkiv b/tex/context/base/mkiv/meta-ini.mkiv
index fa5aa92c6..97d2e1a7d 100644
--- a/tex/context/base/mkiv/meta-ini.mkiv
+++ b/tex/context/base/mkiv/meta-ini.mkiv
@@ -278,9 +278,12 @@
{\pushMPboundingbox
\setbox\b_meta_graphic\hpack\bgroup}
+\let\meta_relocate_graphic\relax % experimental hook
+
\def\meta_process_graphic_stop
{\egroup
\meta_place_graphic
+ \meta_relocate_graphic
\popMPboundingbox}
\unexpanded\def\meta_process_graphic_instance#1%
diff --git a/tex/context/base/mkiv/meta-ini.mkxl b/tex/context/base/mkiv/meta-ini.mkxl
index 31117420c..745bd9268 100644
--- a/tex/context/base/mkiv/meta-ini.mkxl
+++ b/tex/context/base/mkiv/meta-ini.mkxl
@@ -277,9 +277,12 @@
{\pushMPboundingbox
\setbox\b_meta_graphic\hpack\bgroup}
+\let\meta_relocate_graphic\relax % experimental hook
+
\def\meta_process_graphic_stop
{\egroup
\meta_place_graphic
+ \meta_relocate_graphic
\popMPboundingbox}
\unexpanded\def\meta_process_graphic_instance#1%
diff --git a/tex/context/base/mkiv/mlib-cnt.lua b/tex/context/base/mkiv/mlib-cnt.lua
new file mode 100644
index 000000000..5bd27dbfd
--- /dev/null
+++ b/tex/context/base/mkiv/mlib-cnt.lua
@@ -0,0 +1,1770 @@
+if not modules then modules = { } end modules ['mlib-cnt'] = {
+ version = 1.001,
+ comment = "companion to mlib-ctx.mkiv",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files",
+}
+
+-- The only useful reference that I could find about this topic is in wikipedia:
+--
+-- https://en.wikipedia.org/wiki/Marching_squares
+--
+-- I derived the edge code from:
+--
+-- https://physiology.arizona.edu/people/secomb/contours
+--
+-- and also here:
+--
+-- https://github.com/secomb/GreensV4
+--
+-- which has the banner:
+--
+-- TWS, November 1989. Converted to C September 2007. Revised February 2009.
+--
+-- and has a liberal licence. I optimized the code so that it runs a bit faster in Lua and
+-- there's probably more room for optimization (I tried several variants). For instance I
+-- don't use that many tables because access is costly. We don't have a compiler that does
+-- some optimizing (even then the c code can probably be made more efficient).
+--
+-- We often have the same node so it's cheaper to reuse the wsp tables and reconstruct
+-- the point in the path then to alias the original point. We can be more clever:
+-- straighten, but it's more work so maybe I will do that later; for now I only added
+-- a test for equality. There are some experiments in this file too and not all might
+-- work. It's a playground for me.
+--
+-- The code is meant for metafun so it is not general purpose. The bitmap variant is
+-- relatively fast and bitmaps compress well. When all is settled I might make a couple
+-- of helpers in C for this purpose but not now.
+--
+-- I removed optimization code (more aggressive flattening and such because it didn't really
+-- pay off now that we use combined paths with just line segments. I also moved some other
+-- code to a experimental copy. So we now only have the bare helpers needed here.
+
+-- Todo: look into zero case (lavel 1) for shapes ... omiting the background calculation
+-- speeds up quite a bit.
+
+local next, type, tostring = next, type, tostring
+local round, abs, min, max, floor = math.round, math.abs, math.min, math.max, math.floor
+local concat, move = table.concat, table.move
+
+local starttiming = statistics.starttiming
+local stoptiming = statistics.stoptiming
+local elapsedtime = statistics.elapsedtime
+
+local formatters = string.formatters
+local setmetatableindex = table.setmetatableindex
+local sortedkeys = table.sortedkeys
+local sequenced = table.sequenced
+
+local metapost = metapost or { }
+local mp = mp or { }
+
+local getparameterset = metapost.getparameterset
+
+local mpflush = mp.flush
+local mptriplet = mp.triplet
+local mpstring = mp.string
+local mpdraw = mp.draw
+local mpfill = mp.fill
+local mpflatten = mp.flatten
+
+local report = logs.reporter("mfun contour")
+
+local version = 0.007
+
+-- we iterate using integers so that we get a better behaviour at zero
+
+local f_function_n = formatters [ [[
+ local math = math
+ local round = math.round
+ %s
+ return function(data,nx,ny,nxmin,nxmax,xstep,nymin,nymax,ystep)
+ local sx = nxmin
+ for mx=1,nx do
+ local dx = data[mx]
+ local x = sx * xstep
+ local sy = nymin
+ for my=1,ny do
+ local y = sy * ystep
+ dx[my] = %s
+ sy = sy + 1
+ end
+ sx = sx + 1
+ end
+ return 0
+ end
+]] ]
+
+local f_function_y = formatters [ [[
+ local math = math
+ local round = math.round
+ local nan = NaN
+ local inf = math.huge
+ %s
+ return function(data,nx,ny,nxmin,nxmax,xstep,nymin,nymax,ystep,dnan,dinf,report)
+ local sx = nxmin
+ local er = 0
+ for mx=nxmin,nxmax do
+ local dx = data[mx]
+ local x = sx * xstep
+ local sy = nymin
+ for my=nymin,nymax do
+ local y = sy * ystep
+ local n = %s
+ if n == nan then
+ er = er + 1
+ if er < 10 then
+ report("nan at (%s,%s) -> ",x,y,mx,my)
+ end
+ n = dnan
+ elseif n == inf then
+ er = er + 1
+ if er < 10 then
+ report("inf at (%s,%s) -> ",x,y,mx,my)
+ end
+ n = dinf
+ end
+ dx[my] = n
+ sy = sy + 1
+ end
+ sx = sx + 1
+ end
+ return er
+ end
+]] ]
+
+local f_color = formatters [ [[
+ local math = math
+ local min = math.min
+ local max = math.max
+ local n = %s
+ local minz = %s
+ local maxz = %s
+
+ local color_value = 0
+ local color_step = mp.lmt_color_functions.step
+ local color_shade = mp.lmt_color_functions.shade
+
+ local function step(...)
+ return color_step(color_value,n,...)
+ end
+ local function shade(...)
+ return color_shade(color_value,n,...)
+ end
+ local function lin(l)
+ return l/n
+ end
+ %s
+ return function(l)
+ color_value = l
+ return %s
+ end
+]] ]
+
+local inbetween = attributes and attributes.colors.helpers.inbetween
+
+mp.lmt_color_functions = {
+ step = function(l,n,r,g,b,s)
+ if not s then
+ s = 1
+ end
+ local f = l / n
+ local r = s * 1.5 - 4 * abs(f-r)
+ local g = s * 1.5 - 4 * abs(f-g)
+ local b = s * 1.5 - 4 * abs(f-b)
+ return min(max(r,0),1), min(max(g,0),1), min(max(b,0),1)
+ end,
+ shade = function(l,n,one,two)
+ local f = l / n
+ local r = inbetween(one,two,1,f)
+ local g = inbetween(one,two,2,f)
+ local b = inbetween(one,two,3,f)
+ return min(max(r,0),1), min(max(g,0),1), min(max(b,0),1)
+ end,
+}
+
+local f_box = formatters [ [[draw rawtexbox("contour",%s) xysized (%s,%s) ;]] ]
+
+local n_box = 0
+
+-- todo: remove old one, so we need to store old hashes
+
+local nofcontours = 0
+
+-- We don't want cosmetics like axis and labels to trigger a calculation,
+-- especially a slow one.
+
+local hashfields = {
+ "xmin", "xmax", "xstep", "ymin", "ymax", "ystep",
+ "levels", "colors", "level", "preamble", "function", "functions", "color", "values",
+ "background", "foreground", "linewidth", "backgroundcolor", "linecolor",
+}
+
+local function prepare(p)
+ local h = { }
+ for i=1,#hashfields do
+ local f = hashfields[i]
+ h[f] = p[f]
+ end
+ local hash = md5.HEX(sequenced(h))
+ local name = formatters["%s-m-c-%03i.lua"](tex.jobname,nofcontours)
+ return name, hash
+end
+
+local function getcache(p)
+ local cache = p.cache
+ if cache then
+ local name, hash = prepare(p)
+ local data = table.load(name)
+ if data and data.hash == hash and data.version == version then
+ p.result = data
+ return true
+ else
+ return false, hash, name
+ end
+ end
+ return false, nil, nil
+end
+
+local function setcache(p)
+ local result = p.result
+ local name = result.name
+ if name then
+ if result.bitmap then
+ result.bitmap = nil
+ else
+ result.data = nil
+ end
+ result.color = nil
+ result.action = nil
+ result.cached = nil
+ io.savedata(name, table.fastserialize(result))
+ else
+ os.remove((prepare(p)))
+ end
+end
+
+function mp.lmt_contours_start()
+
+ starttiming("lmt_contours")
+
+ nofcontours = nofcontours + 1
+
+ local p = getparameterset()
+
+ local xmin = p.xmin
+ local xmax = p.xmax
+ local ymin = p.ymin
+ local ymax = p.ymax
+ local xstep = p.xstep
+ local ystep = p.ystep
+ local levels = p.levels
+ local colors = p.colors
+ local nx = 0
+ local ny = 0
+ local means = setmetatableindex("number")
+ local values = setmetatableindex("number")
+ local data = setmetatableindex("table")
+ local minmean = false
+ local maxmean = false
+
+ local cached, hash, name = getcache(p)
+
+ local function setcolors(preamble,levels,minz,maxz,color,nofvalues)
+ if colors then
+ local function f(k)
+ return #colors[1] == 1 and 0 or { 0, 0, 0 }
+ end
+ setmetatableindex(colors, function(t,k)
+ local v = f(k)
+ t[k] = v
+ return v
+ end)
+ local c = { }
+ local n = 1
+ for i=1,nofvalues do
+ c[i] = colors[n]
+ n = n + 1
+ end
+ return c, f
+ else
+ local tcolor = f_color(levels,minz,maxz,preamble,color)
+ local colors = { }
+ local fcolor = load(tcolor)
+ if type(fcolor) ~= "function" then
+ report("error in color function, case %i: %s",1,color)
+ fcolor = false
+ else
+ fcolor = fcolor()
+ if type(fcolor) ~= "function" then
+ report("error in color function, case %i: %s",2,color)
+ fcolor = false
+ end
+ end
+ if not fcolor then
+ local color_step = mp.lmt_color_functions.step
+ fcolor = function(l)
+ return color_step(l,levels,0.25, 0.50, 0.75)
+ end
+ end
+ for i=1,nofvalues do
+ colors[i] = { fcolor(i) }
+ end
+ return colors, fcolor
+ end
+ end
+
+ if cached then
+ local result = p.result
+ local colors, color = setcolors(
+ p.preamble,
+ p.levels,
+ result.minz,
+ result.maxz,
+ p.color,
+ result.nofvalues
+ )
+ result.color = color
+ result.colors = colors
+ result.cached = true
+ return
+ end
+
+ local functioncode = p["function"]
+ local functionrange = p.range
+ local functionlist = functionrange and p.functions
+ local preamble = p.preamble
+
+ if xstep == 0 then xstep = (xmax - xmin)/200 end
+ if ystep == 0 then ystep = (ymax - ymin)/200 end
+
+ local nxmin = round(xmin/xstep)
+ local nxmax = round(xmax/xstep)
+ local nymin = round(ymin/ystep)
+ local nymax = round(ymax/ystep)
+ local nx = nxmax - nxmin + 1
+ local ny = nymax - nymin + 1
+
+ local function executed(data,code)
+ local fcode = p.check and f_function_y or f_function_n
+ fcode = fcode(preamble,code)
+ fcode = load(fcode)
+ if type(fcode) == "function" then
+ fcode = fcode()
+ end
+ if type(fcode) == "function" then
+ local er = fcode(
+ data, nx, ny,
+ nxmin, nxmax, xstep,
+ nymin, nymax, ystep,
+ p.defaultnan, p.defaultinf, report
+ )
+ if er > 0 then
+ report("%i errors in: %s",er,code)
+ end
+ return true
+ else
+ return false -- fatal error
+ end
+ end
+
+
+ -- for i=1,nx do
+ -- data[i] = lua.newtable(ny,0)
+ -- end
+
+ if functionlist then
+
+ local datalist = { }
+ local minr = functionrange[1]
+ local maxr = functionrange[2] or minr
+ local size = #functionlist
+
+ for l=1,size do
+
+ local func = setmetatableindex("table")
+ if not executed(func,functionlist[l]) then
+ report("error in executing function %i from functionlist",l)
+ return
+ end
+
+ local bit = l -- + 1
+
+ if l == 1 then
+ for i=1,nx do
+ local di = data[i]
+ local fi = func[i]
+ for j=1,ny do
+ local f = fi[j]
+ if f >= minr and f <= maxr then
+ di[j] = bit
+ else
+ di[j] = 0
+ end
+ end
+ end
+ else
+ for i=1,nx do
+ local di = data[i]
+ local fi = func[i]
+ for j=1,ny do
+ local f = fi[j]
+ if f >= minr and f <= maxr then
+ di[j] = di[j] | bit
+ end
+ end
+ end
+ end
+
+ end
+
+ -- we can simplify the value mess below
+
+ elseif functioncode then
+
+ if not executed(data,functioncode) then
+ report("error in executing function")
+ return -- fatal error
+ end
+
+ else
+
+ report("no valid function(s)")
+ return -- fatal error
+
+ end
+
+ minz = data[1][1]
+ maxz = minz
+
+ for i=1,nx do
+ local d = data[i]
+ for j=1,ny do
+ local v = d[j]
+ if v < minz then
+ minz = v
+ elseif v > maxz then
+ maxz = v
+ end
+ end
+ end
+
+ if functionlist then
+
+ for i=minz,maxz do
+ values[i] = i
+ end
+
+ levels = maxz
+
+ minmean = minz
+ maxmean = maxz
+
+ else
+
+ local snap = (maxz - minz + 1) / levels
+
+ for i=1,nx do
+ local d = data[i]
+ for j=1,ny do
+ local dj = d[j]
+ local v = round((dj - minz) / snap)
+ values[v] = values[v] + 1
+ means [v] = means [v] + dj
+ d[j] = v
+ end
+ end
+
+ local list = sortedkeys(values)
+ local count = values
+ local remap = { }
+
+ values = { }
+
+ for i=1,#list do
+ local v = list[i]
+ local m = means[v] / count[v]
+ remap [v] = i
+ values[i] = m
+ if not minmean then
+ minmean = m
+ maxmean = m
+ elseif m < minmean then
+ minmean = m
+ elseif m > maxmean then
+ maxmean = m
+ end
+ end
+
+ for i=1,nx do
+ local d = data[i]
+ for j=1,ny do
+ d[j] = remap[d[j]]
+ end
+ end
+
+ end
+
+ -- due to rounding we have values + 1 so we can have a floor, ceil, round
+ -- option as well as levels -1
+
+ local nofvalues = #values
+
+ local colors = setcolors(
+ p.preamble,levels,minz,maxz,p.color,nofvalues
+ )
+
+ p.result = {
+ version = version,
+ values = values,
+ nofvalues = nofvalues,
+ minz = minz,
+ maxz = maxz,
+ minmean = minmean,
+ maxmean = maxmean,
+ data = data,
+ color = color,
+ nx = nx,
+ ny = ny,
+ colors = colors,
+ name = name,
+ hash = hash,
+ islist = functionlist and true or false,
+ }
+
+ report("index %i, nx %i, ny %i, levels %i", nofcontours, nx, ny, nofvalues)
+end
+
+function mp.lmt_contours_stop()
+ local p = getparameterset()
+ local e = stoptiming("lmt_contours")
+ setcache(p)
+ p.result = nil
+ local f = p["function"]
+ local l = p.functions
+ report("index %i, %0.3f seconds for: %s",
+ nofcontours, e, "[ " .. concat(l or { f } ," ] [ ") .. " ]"
+ )
+end
+
+function mp.lmt_contours_bitmap_set()
+ local p = getparameterset()
+ local result = p.result
+
+ local values = result.values
+ local nofvalues = result.nofvalues
+ local rawdata = result.data
+ local nx = result.nx
+ local ny = result.ny
+ local colors = result.colors
+ local depth = #colors[1] -- == 3 and "rgb" or "gray"
+
+ -- i need to figure out this offset of + 1
+
+ local bitmap = graphics.bitmaps.new(nx,ny,depth == 3 and "rgb" or "gray",1,false,true)
+
+ local palette = bitmap.index or { } -- has to start at 0
+ local data = bitmap.data
+ local p = 0
+
+ if depth == 3 then
+ for i=1,nofvalues do
+ local c = colors[i]
+ local r = round((c[1] or 0) * 255)
+ local g = round((c[2] or 0) * 255)
+ local b = round((c[3] or 0) * 255)
+ palette[p] = {
+ (r > 255 and 255) or (r < 0 and 0) or r,
+ (g > 255 and 255) or (g < 0 and 0) or g,
+ (b > 255 and 255) or (b < 0 and 0) or b,
+ }
+ p = p + 1
+ end
+ else
+ for i=1,nofvalues do
+ local s = colors[i][1]
+ local s = round((s or 0) * 255)
+ palette[p] = (
+ (s > 255 and 255) or (s < 0 and 0) or s
+ )
+ p = p + 1
+ end
+ end
+
+ -- As (1,1) is the left top corner so we need to flip of we start in
+ -- the left bottom (we cannot loop reverse because we want a properly
+ -- indexed table.
+
+ local k = 0
+ for y=ny,1,-1 do
+ k = k + 1
+ local d = data[k]
+ for x=1,nx do
+ d[x] = rawdata[x][y] - 1
+ end
+ end
+
+ result.bitmap = bitmap
+end
+
+function mp.lmt_contours_bitmap_get()
+ local p = getparameterset()
+ local result = p.result
+ local bitmap = result.bitmap
+ local box = nodes.hpack(graphics.bitmaps.flush(bitmap))
+ n_box = n_box + 1
+ nodes.boxes.savenode("contour",tostring(n_box),box)
+ return f_box(n_box,bitmap.xsize,bitmap.ysize)
+end
+
+function mp.lmt_contours_cleanup()
+ nodes.boxes.reset("contour")
+ n_box = 0
+end
+
+function mp.lmt_contours_edge_set()
+ local p = getparameterset()
+ local result = p.result
+
+ if result.cached then return end
+
+ local values = result.values
+ local nofvalues = result.nofvalues
+ local data = result.data
+ local nx = result.nx
+ local ny = result.ny
+
+ local xmin = p.xmin
+ local xmax = p.xmax
+ local ymin = p.ymin
+ local ymax = p.ymax
+ local xstep = p.xstep
+ local ystep = p.ystep
+
+ local wsp = { }
+ local edges = { }
+
+ for value=1,nofvalues do
+
+ local iwsp = 0
+ local di = data[1]
+ local dc
+ local edge = { }
+ local e = 0
+ -- the next loop is fast
+ for i=1,nx do
+ local di1 = data[i+1]
+ local dij = di[1]
+ local d = dij - value
+ local dij1
+ for j=1,ny do
+ if j < ny then
+ dij1 = di[j+1]
+ dc = dij1 - value
+ if (d >= 0 and dc < 0) or (d < 0 and dc >= 0) then
+ iwsp = iwsp + 1
+ local y = (d * (j+1) - dc * j) / (d - dc)
+ if i == 1 then
+ wsp[iwsp] = { i, y, 0, (i + (j-1)*nx) }
+ elseif i == nx then
+ wsp[iwsp] = { i, y, (i - 1 + (j-1)*nx), 0 }
+ else
+ local jx = (i + (j-1)*nx)
+ wsp[iwsp] = { i, y, jx - 1, jx }
+ end
+ end
+ end
+ if i < nx then
+ local dc = di1[j] - value
+ if (d >= 0 and dc < 0) or (d < 0 and dc >= 0) then
+ iwsp = iwsp + 1
+ local x = (d * (i+1) - dc * i) / (d - dc)
+ if j == 1 then
+ wsp[iwsp] = { x, j, 0, (i + (j-1)*nx) }
+ elseif j == ny then
+ wsp[iwsp] = { x, j, (i + (j-2)*nx), 0 }
+ else
+ local jx = i + (j-1)*nx
+ wsp[iwsp] = { x, j, jx - nx, jx }
+ end
+ end
+ end
+ dij = dij1
+ d = dc
+ end
+ di = di1
+ end
+ -- the next loop takes time
+ for i=1,iwsp do
+ local wspi = wsp[i]
+ for isq=3,4 do
+ local nsq = wspi[isq]
+ if nsq ~= 0 then
+ local px = wspi[1]
+ local py = wspi[2]
+ local p = { px, py }
+ local pn = 2
+ wspi[isq] = 0
+ while true do
+ for ii=1,iwsp do
+ local w = wsp[ii]
+ local n1 = w[3]
+ local n2 = w[4]
+ if n1 == nsq then
+ local x = w[1]
+ local y = w[2]
+ if x ~= px or y ~= py then
+ pn = pn + 1
+ p[pn] = x
+ pn = pn + 1
+ p[pn] = y
+ px = x
+ py = y
+ end
+ nsq = n2
+ w[3] = 0
+ w[4] = 0
+ if nsq == 0 then
+ if pn == 1 then
+ pn = pn + 1
+ p[pn] = w
+ end
+ goto flush
+ end
+ elseif n2 == nsq then
+ local x = w[1]
+ local y = w[2]
+ if x ~= px or y ~= py then
+ pn = pn + 1
+ p[pn] = x
+ pn = pn + 1
+ p[pn] = y
+ px = x
+ py = y
+ end
+ nsq = n1
+ w[3] = 0
+ w[4] = 0
+ if nsq == 0 then
+ goto flush
+ end
+ end
+ end
+ end
+ ::flush::
+ e = e + 1
+ edge[e] = p
+ if mpflatten then
+ mpflatten(p)
+ end
+ end
+ end
+ end
+
+
+ edges[value] = edge
+
+ end
+
+ result.edges = edges
+
+end
+
+function mp.lmt_contours_shade_set(edgetoo)
+ local p = getparameterset()
+ local result = p.result
+
+ if result.cached then return end
+
+ local values = result.values
+ local nofvalues = result.nofvalues
+ local data = result.data
+ local nx = result.nx
+ local ny = result.ny
+ local color = result.color
+
+ local edges = setmetatableindex("table")
+ local shades = setmetatableindex("table")
+
+ local sqtype = setmetatableindex("table")
+
+ local xspoly = { 0, 0, 0, 0, 0, 0 }
+ local yspoly = { 0, 0, 0, 0, 0, 0 }
+ local xrpoly = { }
+ local yrpoly = { }
+
+ local xrpoly = { } -- lua.newtable(2000,0)
+ local yrpoly = { } -- lua.newtable(2000,0)
+
+ -- for i=1,2000 do
+ -- xrpoly[i] = 0
+ -- yrpoly[i] = 0
+ -- end
+
+ -- Unlike a c compiler lua will not optimize loops to run in parallel so we expand
+ -- some of the loops and make sure we don't calculate when not needed. Not that nice
+ -- but not that bad either. Maybe I should just write this from scratch.
+
+-- local i = 0
+-- local j = 0
+
+ -- Analyze each rectangle separately. Overwrite lower colors
+
+ -- Unrolling the loops and copying code and using constants is faster and doesn't
+ -- produce much more code in the end, also because we then can leave out the not
+ -- seen branches. One can argue about the foundit2* blobs but by stepwise optimizing
+ -- this is the result.
+
+ shades[1] = { { 0, 0, nx - 1, 0, nx - 1, ny - 1, 0, ny - 1 } }
+ edges [1] = { { } }
+
+ -- this is way too slow ... i must have messed up some loop .. what is this with value 1
+
+ for value=1,nofvalues do
+-- for value=2,nofvalues do
+
+ local edge = { }
+ local nofe = 0
+ local shade = { }
+ local nofs = 0
+
+ for i=1,nx-1 do
+ local s = sqtype[i]
+ for j=1,ny-1 do
+ s[j] = 0
+ end
+ end
+
+ local nrp = 0
+
+ local function addedge(a,b,c,d)
+ nofe = nofe + 1 edge[nofe] = a
+ nofe = nofe + 1 edge[nofe] = b
+ nofe = nofe + 1 edge[nofe] = c
+ nofe = nofe + 1 edge[nofe] = d
+ end
+ while true do
+ -- search for a square of type 0 with >= 1 corner above contour level
+ local i
+ local j
+ local d0 = data[1]
+ local d1 = data[2]
+ for ii=1,nx do
+ local s = sqtype[ii]
+ for jj=1,ny do
+ if s[jj] == 0 then
+ if d0[jj] > value then i = ii j = jj goto foundit end
+ if d1[jj] > value then i = ii j = jj goto foundit end
+ local j1 = jj + 1
+ if d1[j1] > value then i = ii j = jj goto foundit end
+ if d0[j1] > value then i = ii j = jj goto foundit end
+ end
+ end
+ d0 = d1
+ d1 = data[ii+1]
+ end
+ break
+ ::foundit::
+ -- initialize r-polygon (nrp seems to be 1 or 2)
+ nrp = nrp + 1
+
+ local first = true
+ local nrpoly = 0
+ local nspoly = 0
+ local nrpm = -nrp
+ -- this is the main loop
+ while true do
+ -- search for a square of type -nrp
+ if first then
+ first = false
+ if sqtype[i][j] == 0 then -- true anyway
+ goto foundit1
+ end
+ end
+ for ii=1,nx do
+ local s = sqtype[ii]
+ for jj=1,ny do
+ if s[jj] == nrpm then
+ i = ii
+ j = jj
+ goto foundit1
+ end
+ end
+ end
+ break
+ ::foundit1::
+ while true do
+
+ -- search current then neighboring squares for square type 0, with a corner in common with current square above contour level
+
+ -- top/bottom ... a bit cheating here
+
+ local i_l, i_c, i_r -- i left current right
+ local j_b, j_c, j_t -- j bottom current top
+
+ local i_n = i + 1 -- i next (right)
+ local j_n = j + 1 -- j next (top)
+
+ local i_p = i - 1 -- i previous (bottom)
+ local j_p = j - 1 -- j previous (right)
+
+ local d_c = data[i]
+ local d_r = data[i_n]
+
+ local sq
+
+ i_c = i ; j_c = j ; if i_c < nx and j_c < ny then sq = sqtype[i_c] if sq[j_c] == 0 then
+ if d_c[j_c] > value then i_l = i_p ; i_r = i_n ; j_b = j_p ; j_t = j_n ; goto foundit21 end
+ if d_c[j_n] > value then i_l = i_p ; i_r = i_n ; j_b = j_p ; j_t = j_n ; goto foundit22 end
+ if d_r[j_c] > value then i_l = i_p ; i_r = i_n ; j_b = j_p ; j_t = j_n ; goto foundit23 end
+ if d_r[j_n] > value then i_l = i_p ; i_r = i_n ; j_b = j_p ; j_t = j_n ; goto foundit24 end
+ end end
+
+ i_c = i_n ; j_c = j ; if i_c < nx and j_c < ny then sq = sqtype[i_c] if sq[j_c] == 0 then
+ if d_r[j_c] > value then i_l = i ; i_r = i_n + 1 ; j_b = j_p ; j_t = j_n ; d_c = d_r ; d_r = data[i_r] ; goto foundit21 end
+ if d_r[j_n] > value then i_l = i ; i_r = i_n + 1 ; j_b = j_p ; j_t = j_n ; d_c = d_r ; d_r = data[i_r] ; goto foundit22 end
+ end end
+
+ i_c = i ; j_c = j_n ; if i_c < nx and j_c < ny then sq = sqtype[i_c] if sq[j_c] == 0 then
+ if d_c[j_n] > value then i_l = i_p ; i_r = i_n ; j_b = j ; j_t = j_n + 1 ; goto foundit21 end
+ if d_r[j_n] > value then i_l = i_p ; i_r = i_n ; j_b = j ; j_t = j_n + 1 ; goto foundit23 end
+ end end
+
+ i_c = i_p ; j_c = j ; if i_c > 0 and j_c < ny then sq = sqtype[i_c] if sq[j_c] == 0 then
+ if d_c[j_c] > value then i_l = i_p - 1 ; i_r = i ; j_b = j_p ; j_t = j_n ; d_r = d_c ; d_c = data[i_p] ; goto foundit23 end
+ if d_c[j_n] > value then i_l = i_p - 1 ; i_r = i ; j_b = j_p ; j_t = j_n ; d_r = d_c ; d_c = data[i_p] ; goto foundit24 end
+ end end
+
+ i_c = i ; j_c = j_p ; if i < nx and j_c > 0 then sq = sqtype[i_c] if sq[j_c] == 0 then
+ if d_c[j] > value then i_l = i_p ; i_r = i_n ; j_b = j_p - 1 ; j_t = j ; goto foundit22 end
+ if d_r[j] > value then i_l = i_p ; i_r = i_n ; j_b = j_p - 1 ; j_t = j ; goto foundit24 end
+ end end
+
+ -- not found
+
+ sqtype[i][j] = nrp
+
+ break
+
+ -- define s-polygon for found square (i_c,j_c) - may have up to 6 sides
+
+ ::foundit21:: -- 1 2 3 4
+
+ sq[j_c] = nrpm
+
+ xspoly[1] = i_l ; yspoly[1] = j_b
+ xspoly[2] = i_c ; yspoly[2] = j_b
+ if d_r[j_c] > value then -- dd2
+ xspoly[3] = i_c ; yspoly[3] = j_c
+ if d_r[j_t] > value then -- dd3
+ xspoly[4] = i_l ; yspoly[4] = j_c
+ if d_c[j_t] > value then -- dd4
+ nspoly = 4
+ else
+ xspoly[5] = i_l ; yspoly[5] = j_c ; nspoly = 5
+ end
+ elseif d_c[j_t] > value then -- dd4
+ xspoly[4] = i_c ; yspoly[4] = j_c ;
+ xspoly[5] = i_l ; yspoly[5] = j_c ; nspoly = 5
+ else
+ xspoly[4] = i_l ; yspoly[4] = j_c ; nspoly = 4
+ if edgetoo then addedge(i_c, j_c, i_l, j_c) end
+ end
+ elseif d_r[j_t] > value then -- dd3
+ xspoly[3] = i_c ; yspoly[3] = j_b
+ xspoly[4] = i_c ; yspoly[4] = j_c
+ if d_c[j_t] > value then -- dd4
+ xspoly[5] = i_l ; yspoly[5] = j_c ; nspoly = 5
+ else
+ xspoly[5] = i_l ; yspoly[5] = j_c ;
+ xspoly[6] = i_l ; yspoly[6] = j_c ; nspoly = 6
+ end
+ elseif d_c[j_t] > value then -- dd4
+ if edgetoo then addedge(i_c, j_b, i_c, j_c) end
+ xspoly[3] = i_c ; yspoly[3] = j_c ;
+ xspoly[4] = i_l ; yspoly[4] = j_c ; nspoly = 4
+ else
+ if edgetoo then addedge(i_c, j_b, i_l, j_c) end
+ xspoly[3] = i_l ; yspoly[3] = j_c ; nspoly = 3
+ end
+ goto done
+
+ ::foundit22:: -- 4 1 2 3
+
+ sq[j_c] = nrpm
+
+ xspoly[1] = i_l ; yspoly[1] = j_c
+ xspoly[2] = i_l ; yspoly[2] = j_b
+ if d_c[j_c] > value then -- dd2
+ xspoly[3] = i_c ; yspoly[3] = j_b
+ if d_r[j_c] > value then -- dd3
+ xspoly[4] = i_c ; yspoly[4] = j_c
+ if d_r[j_t] > value then -- dd4
+ nspoly = 4
+ else
+ xspoly[5] = i_c ; yspoly[5] = j_c ; nspoly = 5 -- suspicious, the same
+ end
+ elseif d_r[j_t] > value then -- dd4
+ xspoly[4] = i_c ; yspoly[4] = j_b ;
+ xspoly[5] = i_c ; yspoly[5] = j_c ; nspoly = 5
+ else
+ if edgetoo then addedge(i_c, j_b, i_c, j_c) end
+ xspoly[4] = i_c ; yspoly[4] = j_c ; nspoly = 4
+ end
+ elseif d_r[j_c] > value then -- dd3
+ xspoly[3] = i_l ; yspoly[3] = j_b
+ xspoly[4] = i_c ; yspoly[4] = j_b
+ xspoly[5] = i_c ; yspoly[5] = j_c
+ if d_r[j_t] > value then -- dd4
+ nspoly = 5
+ else
+ xspoly[6] = i_c ; yspoly[6] = j_c ; nspoly = 6
+ end
+ elseif d_r[j_t] > value then -- dd4
+ if edgetoo then addedge(i_l, j_b, i_c, j_b) end
+ xspoly[3] = i_c ; yspoly[3] = j_b
+ xspoly[4] = i_c ; yspoly[4] = j_c ; nspoly = 4
+ else
+ if edgetoo then addedge(i_l, j_b, i_c, j_c) end
+ xspoly[3] = i_c ; yspoly[3] = j_c ; nspoly = 3
+ end
+ goto done
+
+ ::foundit23:: -- 2 3 4 1
+
+ sq[j_c] = nrpm
+
+ xspoly[1] = i_c ; yspoly[1] = j_b
+ xspoly[2] = i_c ; yspoly[2] = j_c
+ if d_r[j_t] > value then -- dd2
+ xspoly[3] = i_l ; yspoly[3] = j_c
+ if d_c[j_t] > value then -- dd3
+ xspoly[4] = i_l ; yspoly[4] = j_b
+ if d_c[j_c] > value then -- dd4
+ nspoly = 4
+ else
+ xspoly[5] = i_l ; yspoly[5] = j_b ; nspoly = 5
+ end
+ elseif d_c[j_c] > value then -- dd4
+ xspoly[4] = i_l ; yspoly[4] = j_c
+ xspoly[5] = i_l ; yspoly[5] = j_b ; nspoly = 5
+ else
+ if edgetoo then addedge(i_l, j_c, i_l, j_b) end
+ xspoly[4] = i_l ; yspoly[4] = j_b ; nspoly = 4
+ end
+ elseif d_c[j_t] > value then -- dd3
+ xspoly[3] = i_c ; yspoly[3] = j_c
+ xspoly[4] = i_l ; yspoly[4] = j_c
+ xspoly[5] = i_l ; yspoly[5] = j_b
+ if d_c[j_c] > value then -- dd4
+ nspoly = 5
+ else
+ xspoly[6] = i_l ; yspoly[6] = j_b ; nspoly = 6
+ end
+ elseif d_c[j_c] > value then -- dd4
+ if edgetoo then addedge(i_c, j_c, i_l, j_c) end
+ xspoly[3] = i_l ; yspoly[3] = j_c ;
+ xspoly[4] = i_l ; yspoly[4] = j_b ; nspoly = 4
+ else
+ if edgetoo then addedge(i_c, j_c, i_l, j_b) end
+ xspoly[3] = i_l ; yspoly[3] = j_b ; nspoly = 3
+ end
+ goto done
+
+ ::foundit24:: -- 3 4 1 2
+
+ sq[j_c] = nrpm
+
+ xspoly[1] = i_c ; yspoly[1] = j_c
+ xspoly[2] = i_l ; yspoly[2] = j_c
+ if d_c[j_t] > value then -- dd2
+ if d_c[j_c] > value then -- dd3
+ xspoly[3] = i_l ; yspoly[3] = j_b
+ xspoly[4] = i_c ; yspoly[4] = j_b
+ if d_r[j_c] > value then -- dd4
+ nspoly = 4
+ else
+ xspoly[5] = i_c ; yspoly[5] = j_b ; nspoly = 5
+ end
+ else
+ xspoly[3] = i_l ; yspoly[3] = j_b
+ if d_r[j_c] > value then -- dd4
+
+ local xv34 = (dd3*i_c-dd4*i_l)/(dd3 - dd4) -- probably i_l
+ print("4.4 : xv34",xv34,i_c,i_l)
+
+ -- if edgetoo then addedge(i_l, j_b, xv34, j_b) end
+ xspoly[4] = xv34 ; yspoly[4] = j_b ;
+ xspoly[5] = i_c ; yspoly[5] = j_b ; nspoly = 5
+ else
+ if edgetoo then addedge(i_l, j_b, i_c, j_b) end
+ xspoly[4] = i_c ; yspoly[4] = j_b ; nspoly = 4
+ end
+ end
+ elseif d_c[j_c] > value then -- dd3
+ xspoly[3] = i_l ; yspoly[3] = j_b
+ xspoly[4] = i_l ; yspoly[4] = j_b
+ xspoly[5] = i_c ; yspoly[5] = j_b
+ if d_r[j_c] > value then -- dd4
+ nspoly = 5
+ else
+ xspoly[6] = i_c ; yspoly[6] = j_b ; nspoly = 6
+ end
+ elseif d_r[j_c] > value then -- dd4
+ if edgetoo then addedge(i_l, j_c, i_l, j_b) end
+ xspoly[3] = i_l ; yspoly[3] = j_b
+ xspoly[4] = i_c ; yspoly[4] = j_b ; nspoly = 4
+ else
+ if edgetoo then addedge(i_l, j_c, i_c, j_b) end
+ xspoly[3] = i_c ; yspoly[3] = j_b ; nspoly = 3
+ end
+ -- goto done
+
+ ::done::
+ -- combine s-polygon with existing r-polygon, eliminating redundant segments
+
+ if nrpoly == 0 then
+ -- initiate r-polygon
+ for i=1,nspoly do
+ xrpoly[i] = xspoly[i]
+ yrpoly[i] = yspoly[i]
+ end
+ nrpoly = nspoly
+ else
+ -- search r-polygon and s-polygon for one side that matches
+ --
+ -- this is a bottleneck ... we keep this variant here but next go for a faster
+ -- alternative
+ --
+ -- local ispoly, irpoly
+ -- for r=nrpoly,1,-1 do
+ -- local r1
+ -- for s=1,nspoly do
+ -- local s1 = s % nspoly + 1
+ -- if xrpoly[r] == xspoly[s1] and yrpoly[r] == yspoly[s1] then
+ -- if not r1 then
+ -- r1 = r % nrpoly + 1
+ -- end
+ -- if xrpoly[r1] == xspoly[s] and yrpoly[r1] == yspoly[s] then
+ -- ispoly = s
+ -- irpoly = r
+ -- goto foundit3
+ -- end
+ -- end
+ -- end
+ -- end
+ --
+ -- local ispoly, irpoly
+ -- local xr1 = xrpoly[1]
+ -- local yr1 = yrpoly[1]
+ -- for r0=nrpoly,1,-1 do
+ -- for s0=1,nspoly do
+ -- if xr1 == xspoly[s0] and yr1 == yspoly[s0] then
+ -- if s0 == nspoly then
+ -- if xr0 == xspoly[1] and yr0 == yspoly[1] then
+ -- ispoly = s0
+ -- irpoly = r0
+ -- goto foundit3
+ -- end
+ -- else
+ -- local s1 = s0 + 1
+ -- if xr0 == xspoly[s1] and yr0 == yspoly[s1] then
+ -- ispoly = s0
+ -- irpoly = r0
+ -- goto foundit3
+ -- end
+ -- end
+ -- end
+ -- end
+ -- xr1 = xrpoly[r0]
+ -- yr1 = yrpoly[r0]
+ -- end
+ --
+ -- but ...
+ --
+ local minx = xspoly[1]
+ local miny = yspoly[1]
+ local maxx = xspoly[1]
+ local maxy = yspoly[1]
+ for i=1,nspoly do
+ local y = yspoly[i]
+ if y < miny then
+ miny = y
+ elseif y > maxy then
+ maxy = y
+ end
+ local x = xspoly[i]
+ if x < minx then
+ minx = y
+ elseif x > maxx then
+ maxx = x
+ end
+ end
+ -- we can delay accessing y ...
+ local ispoly, irpoly
+ local xr1 = xrpoly[1]
+ local yr1 = yrpoly[1]
+ for r0=nrpoly,1,-1 do
+ if xr1 >= minx and xr1 <= maxx and yr1 >= miny and yr1 <= maxy then
+ local xr0 = xrpoly[r0]
+ local yr0 = yrpoly[r0]
+ for s0=1,nspoly do
+ if xr1 == xspoly[s0] and yr1 == yspoly[s0] then
+ if s0 == nspoly then
+ if xr0 == xspoly[1] and yr0 == yspoly[1] then
+ ispoly = s0
+ irpoly = r0
+ goto foundit3
+ end
+ else
+ local s1 = s0 + 1
+ if xr0 == xspoly[s1] and yr0 == yspoly[s1] then
+ ispoly = s0
+ irpoly = r0
+ goto foundit3
+ end
+ end
+ end
+ end
+ xr1 = xr0
+ yr1 = yr0
+ else
+ xr1 = xrpoly[r0]
+ yr1 = yrpoly[r0]
+ end
+ end
+ --
+ goto nomatch3
+ ::foundit3::
+ local match1 = 0
+ local rpoly1 = irpoly + nrpoly
+ local spoly1 = ispoly - 1
+ for i=2,nspoly-1 do
+ -- search for further matches nearby
+ local ir = (rpoly1 - i) % nrpoly + 1
+ local is = (spoly1 + i) % nspoly + 1
+ if xrpoly[ir] == xspoly[is] and yrpoly[ir] == yspoly[is] then
+ match1 = match1 + 1
+ else
+ break -- goto nomatch1
+ end
+ end
+ ::nomatch1::
+ local match2 = 0
+ local rpoly2 = irpoly - 1
+ local spoly2 = ispoly + nspoly
+ for i=2,nspoly-1 do
+ -- search other way for further matches nearby
+ local ir = (rpoly2 + i) % nrpoly + 1
+ local is = (spoly2 - i) % nspoly + 1
+ if xrpoly[ir] == xspoly[is] and yrpoly[ir] == yspoly[is] then
+ match2 = match2 + 1
+ else
+ break -- goto nomatch2
+ end
+ end
+ ::nomatch2::
+ -- local dnrpoly = nspoly - 2 - 2*match1 - 2*match2
+ local dnrpoly = nspoly - 2*(match1 + match2 + 1)
+ local ispolystart = (ispoly + match1) % nspoly + 1 -- first node of s-polygon to include
+ local irpolyend = (rpoly1 - match1 - 1) % nrpoly + 1 -- last node of s-polygon to include
+ if dnrpoly ~= 0 then
+ local irpolystart = (irpoly + match2) % nrpoly + 1 -- first node of s-polygon to include
+ if irpolystart > irpolyend then
+ -- local ispolyend = (spoly1 - match2 + nspoly)%nspoly + 1 -- last node of s-polygon to include
+ if dnrpoly > 0 then
+ -- expand the arrays xrpoly and yrpoly
+ for i=nrpoly,irpolystart,-1 do
+ local k = i + dnrpoly
+ xrpoly[k] = xrpoly[i]
+ yrpoly[k] = yrpoly[i]
+ end
+ else -- if dnrpoly < 0 then
+ -- contract the arrays xrpoly and yrpoly
+ for i=irpolystart,nrpoly do
+ local k = i + dnrpoly
+ xrpoly[k] = xrpoly[i]
+ yrpoly[k] = yrpoly[i]
+ end
+ end
+ end
+ nrpoly = nrpoly + dnrpoly
+ end
+ if nrpoly < irpolyend then
+ for i=irpolyend,nrpoly+1,-1 do
+ -- otherwise these values get lost!
+ local k = i - nrpoly
+ xrpoly[k] = xrpoly[i]
+ yrpoly[k] = yrpoly[i]
+ end
+ end
+ local n = nspoly - 2 - match1 - match2
+ if n == 1 then
+ local irpoly1 = irpolyend % nrpoly + 1
+ local ispoly1 = ispolystart % nspoly + 1
+ xrpoly[irpoly1] = xspoly[ispoly1]
+ yrpoly[irpoly1] = yspoly[ispoly1]
+ elseif n > 0 then
+ -- often 2
+ for i=1,n do
+ local ii = i - 1
+ local ir = (irpolyend + ii) % nrpoly + 1
+ local is = (ispolystart + ii) % nspoly + 1
+ xrpoly[ir] = xspoly[is]
+ yrpoly[ir] = yspoly[is]
+ end
+ end
+ ::nomatch3::
+ end
+ end
+ end
+
+ if nrpoly > 0 then
+ local t = { }
+ local n = 0
+ for i=1,nrpoly do
+ n = n + 1 t[n] = xrpoly[i]
+ n = n + 1 t[n] = yrpoly[i]
+ end
+ if mpflatten then
+ mpflatten(t) -- maybe integrate
+ end
+ nofs = nofs + 1
+ shade[nofs] = t
+ -- print(value,nrpoly,#t,#t-nrpoly*2)
+ end
+
+ end
+
+ edges [value+1] = edge
+ shades[value+1] = shade
+-- edges [value] = edge
+-- shades[value] = shade
+ end
+
+ result.shades = shades
+ result.shapes = edges
+
+end
+
+-- accessors
+
+function mp.lmt_contours_nx (i) return getparameterset().result.nx end
+function mp.lmt_contours_ny (i) return getparameterset().result.ny end
+
+function mp.lmt_contours_nofvalues() return getparameterset().result.nofvalues end
+function mp.lmt_contours_value (i) return getparameterset().result.values[i] end
+
+function mp.lmt_contours_minz (i) return getparameterset().result.minz end
+function mp.lmt_contours_maxz (i) return getparameterset().result.maxz end
+
+function mp.lmt_contours_minmean (i) return getparameterset().result.minmean end
+function mp.lmt_contours_maxmean (i) return getparameterset().result.maxmean end
+
+function mp.lmt_contours_xrange () local p = getparameterset() mpstring(formatters["x = [%.3N,%.3N] ;"](p.xmin,p.xmax)) end
+function mp.lmt_contours_yrange () local p = getparameterset() mpstring(formatters["y = [%.3N,%.3N] ;"](p.ymin,p.ymax)) end
+
+function mp.lmt_contours_format()
+ local p = getparameterset()
+ return mpstring(p.result.islist and "@i" or p.zformat or p.format)
+end
+
+function mp.lmt_contours_function()
+ local p = getparameterset()
+ return mpstring(p.result.islist and concat(p["functions"], ", ") or p["function"])
+end
+
+function mp.lmt_contours_range()
+ local p = getparameterset()
+ local r = p.result.islist and p.range
+ if not r or #r == 0 then
+ return mpstring("")
+ elseif #r == 1 then
+ return mpstring(r[1])
+ else
+ return mpstring(formatters["z = [%s,%s]"](r[1],r[2]))
+ end
+end
+
+function mp.lmt_contours_edge_paths(value)
+ mpdraw(getparameterset().result.edges[value],true)
+ mpflush()
+end
+
+function mp.lmt_contours_shape_paths(value)
+ mpdraw(getparameterset().result.shapes[value],false)
+ mpflush()
+end
+
+function mp.lmt_contours_shade_paths(value)
+ mpfill(getparameterset().result.shades[value],true)
+ mpflush()
+end
+
+function mp.lmt_contours_color(value)
+ local p = getparameterset()
+ local color = p.result.colors[value]
+ if #color == 3 then
+ mptriplet(color)
+ else
+ return color[1]
+ end
+end
+
+-- The next code is based on the wikipedia page. It was a bit tedius job to define the
+-- coordinates but hupefully I made no errors. I rendered all shapes independently and
+-- tripple checked bit one never knows ...
+
+-- maybe some day write from scatch, like this (axis are swapped):
+
+local d = 1/2
+
+local paths = {
+ { 0, d, d, 0 },
+ { 1, d, d, 0 },
+ { 0, d, 1, d },
+ { 1, d, d, 1 },
+ { 0, d, d, 1, d, 0, 1, d }, -- saddle
+ { d, 0, d, 1 },
+ { 0, d, d, 1 },
+ { 0, d, d, 1 },
+ { d, 0, d, 1 },
+ { 0, d, d, 0, 1, d, d, 1 }, -- saddle
+ { 1, d, d, 1 },
+ { 0, d, 1, d },
+ { d, 0, 1, d },
+ { d, 0, 0, d },
+}
+
+local function whatever(data,nx,ny,threshold)
+ local edges = { }
+ local e = 0
+ local d0 = data[1]
+ for j=1,ny-1 do
+ local d1 = data[j+1]
+ local k = j + 1
+ for i=1,nx-1 do
+ local v = 0
+ local l = i + 1
+ local c1 = d0[i]
+ if c1 < threshold then
+ v = v + 8
+ end
+ local c2 = d0[l]
+ if c2 < threshold then
+ v = v + 4
+ end
+ local c3 = d1[l]
+ if c3 < threshold then
+ v = v + 2
+ end
+ local c4 = d1[i]
+ if c4 < threshold then
+ v = v + 1
+ end
+ if v > 0 and v < 15 then
+ if v == 5 or v == 10 then
+ local a = (c1 + c2 + c3 + c4) / 4
+ if a < threshold then
+ v = v == 5 and 10 or 5
+ end
+ local p = paths[v]
+ e = e + 1 edges[e] = k - p[2]
+ e = e + 1 edges[e] = i + p[1]
+ e = e + 1 edges[e] = k - p[4]
+ e = e + 1 edges[e] = i + p[3]
+ e = e + 1 edges[e] = k - p[6]
+ e = e + 1 edges[e] = i + p[5]
+ e = e + 1 edges[e] = k - p[8]
+ e = e + 1 edges[e] = i + p[7]
+ else
+ local p = paths[v]
+ e = e + 1 edges[e] = k - p[2]
+ e = e + 1 edges[e] = i + p[1]
+ e = e + 1 edges[e] = k - p[4]
+ e = e + 1 edges[e] = i + p[3]
+ end
+ end
+ end
+ d0 = d1
+ end
+ return edges
+end
+
+-- todo: just fetch when needed, no need to cache
+
+function mp.lmt_contours_edge_set_by_cell()
+ local p = getparameterset()
+ local result = p.result
+
+ if result.cached then return end
+
+ local values = result.values
+ local nofvalues = result.nofvalues
+ local data = result.data
+ local nx = result.nx
+ local ny = result.ny
+ local lines = { }
+ result.lines = lines
+ for value=1,nofvalues do
+ lines[value] = whatever(data,ny,nx,value)
+ end
+end
+
+function mp.lmt_contours_edge_get_cell(value)
+ mpdraw(getparameterset().result.lines[value])
+ mpflush()
+end
+
+local singles = {
+ { d, 0, 0, 0, 0, d }, -- 1 0001
+ { d, 0, 0, d }, -- 2 0002
+ { 1, d, 1, 0, d, 0 }, -- 3 0010
+ { 1, d, 1, 0, 0, 0, 0, d }, -- 4 0011
+ { 1, d, 1, 0, d, 0, 0, d }, -- 5 0012
+ { 1, d, d, 0 }, -- 6 0020
+ { 1, d, d, 0, 0, 0, 0, d }, -- 7 0021
+ { 1, d, 0, d }, -- 8 0022
+ { d, 1, 1, 1, 1, d }, -- 9 0100
+ false, -- 10 0101
+ false, -- 11 0102
+ { d, 1, 1, 1, 1, 0, d, 0 }, -- 12 0110
+ { d, 1, 1, 1, 1, 0, 0, 0, 0, d }, -- 13 0111
+ { d, 1, 1, 1, 1, 0, d, 0, 0, d }, -- 14 0112
+ { d, 1, 1, 1, 1, d, d, 0 }, -- 15 0120
+ { d, 1, 1, 1, 1, d, d, 0, 0, 0, 0, d }, -- 16 0121
+ { d, 1, 1, 1, 1, d, 0, d }, -- 17 0122
+ { d, 1, 1, d }, -- 18 0200
+ false, -- 19 0201
+ false, -- 20 0202
+ { d, 1, 1, d, 1, 0, d, 0 }, -- 21 0210
+ { d, 1, 1, d, 1, 0, 0, 0, 0, d }, -- 22 0211
+ false, -- 23 0212
+ { d, 1, d, 0 }, -- 24 0220
+ { d, 1, d, 0, 0, 0, 0, d }, -- 25 0221
+ { d, 1, 0, d }, -- 26 0222
+ { 0, 1, d, 1, 0, d }, -- 27 1000
+ { 0, 1, d, 1, d, 0, 0, 0 }, -- 28 1001
+ { 0, 1, d, 1, d, 0, 0, d }, -- 29 1002
+ false, -- 30 1010
+ { 0, 1, d, 1, 1, d, 1, 0, 0, 0 }, -- 31 1011
+ { 0, 1, d, 1, 1, d, 1, 0, d, 0, 0, d }, -- 32 1012
+ false, -- 33 1020
+ { 0, 1, d, 1, 1, d, d, 0, 0, 0 }, -- 34 1021
+ { 0, 1, d, 1, 1, d, 0, d }, -- 35 1022
+ { 0, 1, 1, 1, 1, d, 0, d }, -- 36 1100
+ { 0, 1, 1, 1, 1, d, d, 0, 0, 0 }, -- 37 1101
+ { 0, 1, 1, 1, 1, d, d, 0, 0, d }, -- 38 1102
+ { 0, 1, 1, 1, 1, 0, d, 0, 0, d }, -- 39 1110
+ { 0, 1, 1, 1, 1, 0, 0, 0 }, -- 40 1111
+ { 0, 1, 1, 1, 1, 0, d, 0, 0, d }, -- 41 1112
+ { 0, 1, 1, 1, 1, d, d, 0, 0, d }, -- 42 1120
+ { 0, 1, 1, 1, 1, d, d, 0, 0, 0 }, -- 43 1121
+ { 0, 1, 1, 1, 1, d, 0, d }, -- 44 1122
+ { 0, 1, d, 1, 1, d, 0, d }, -- 45 1200
+ { 0, 1, d, 1, 1, d, d, 0, 0, 0 }, -- 46 1201
+ false, -- 47 1202
+ { 0, 1, d, 1, 1, d, 1, 0, d, 0, 0, d }, -- 48 1210
+ { 0, 1, d, 1, 1, d, 1, 0, 0, 0 }, -- 49 1211
+ false, -- 50 1212
+ { 0, 1, d, 1, d, 0, 0, d }, -- 51 1220
+ { 0, 1, d, 1, d, 0, 0, 0 }, -- 52 1221
+ { 0, 1, d, 1, 0, d }, -- 53 1222
+ { d, 1, 0, d }, -- 54 2000
+ { d, 1, d, 0, 0, 0, 0, d }, -- 55 2001
+ { d, 1, d, 0 }, -- 56 2002
+ false, -- 57 2010
+ { d, 1, 1, d, 1, 0, 0, 0, 0, d }, -- 58 2011
+ { d, 1, 1, d, 1, 0, d, 0 }, -- 59 2012
+ false, -- 60 2020
+ false, -- 61 2021
+ { d, 1, 1, d }, -- 62 2022
+ { d, 1, 1, 1, 1, d, 0, d }, -- 63 2100
+ { d, 1, 1, 1, 1, d, d, 0, 0, 0, 0, d }, -- 64 2101
+ { d, 1, 1, 1, 1, d, d, 0 }, -- 65 2102
+ { d, 1, 1, 1, 1, 0, d, 0, 0, d }, -- 66 2110
+ { d, 1, 1, 1, 1, 0, 0, 0, 0, d }, -- 67 2111
+ { d, 1, 1, 1, 1, 0, d, 0 }, -- 68 2112
+ false, -- 69 2120
+ false, -- 70 2121
+ { d, 1, 1, 1, 1, d }, -- 71 2122
+ { 1, d, 0, d }, -- 72 2200
+ { 1, d, d, 0, 0, 0, 0, d }, -- 73 2201
+ { 1, d, d, 0 }, -- 74 2202
+ { 1, d, 1, 0, d, 0, 0, d }, -- 75 2210
+ { 1, d, 1, 0, 0, 0, 0, d }, -- 76 2211
+ { 1, d, 1, 0, d, 0 }, -- 77 2212
+ { d, 0, 0, d }, -- 78 2220
+ { 0, d, 0, 0, d, 0 }, -- 79 2221
+}
+
+local sadles = {
+ false, false, false, false, false, false, false, false, false,
+ { { d, 1, 1, 1, 1, d }, { d, 0, 0, 0, 0, d }, { d, 1, 1, 1, 1, d, d, 0, 0, 0, 0, d }, false, false, false }, -- 10 0101
+ { { d, 1, 1, 1, 1, d }, { d, 0, 0, d }, { d, 1, 1, 1, 1, d, d, 0, 0, d }, false, false, false }, -- 11 0102
+ false, false, false, false, false, false, false,
+ { { d, 1, 1, d }, { d, 0, 0, 0, 0, d }, { d, 1, 1, d, d, 0, 0, 0, 0, d }, false, false, false }, -- 19 0201
+ { { d, 1, 1, d }, { d, 0, 0, d }, { d, 1, 1, d, d, 0, 0, d }, false, { d, 1, 0, d }, { 1, d, d, 0 } }, -- 20 0202
+ false, false,
+ { false, false, { d, 1, 1, d, 1, 0, d, 0, 0, d }, false, { d, 1, 0,d, }, { 1, d, 1, 0,d, 0 } }, -- 23 0212
+ false, false, false, false, false, false,
+ { { 0, 1, d, 1, 0, d }, { 1, d, 1, 0, d, 0 }, { 0, 1, d, 1, 1, d, 1, 0, d, 0, 0, d }, false, false, false }, -- 30 1010
+ false, false,
+ { { 1, 0, d, 0, 0, d, }, { 1, d, d, 0 }, { 0, 1, d, 1, 1, d, d, 0, 0, d }, false, false, false }, -- 33 1020
+ false, false, false, false, false, false, false, false, false, false, false, false, false,
+ { false, false, { 0,1, d, 1, 1, d, d, 0, 0, d }, false, { 0,1, d, 1, 0, d }, {1, d, d, 0 } }, -- 47 1202
+ false, false,
+ { false, false, { 0, 1, d, 1, 1, d, 1, 0, d, 0, 0, d }, false, { 0, 1, d, 1, 0, d }, { 1, d, 1, 0, d, 0 } }, -- 50 1212
+ false, false, false, false, false, false,
+ { { d, 1, 0, d }, { 1, d, 1, 0, 0, d }, { d, 1, 1, d, 1, 0, d, 0, 0, d }, false, false, false }, -- 57 2010
+ false, false,
+ { { d, 1, 0,d }, { 1, d, d, 0 }, { d, 1, 1, d, d, 0, 0, d }, false, { d, 1, 1, d }, { d, 0, 0, d } }, -- 60 2020
+ { false, false, { d, 1, 1, d, d, 0, 0, 0, 0, d }, false, { d, 1, 1, d }, { d, 0, 0, 0, 0, d } }, -- 61 2021
+ false, false, false, false, false, false, false,
+ { false, false, { d, 1, 1, 1, 1, d, d, 0, 0, d }, false, { d, 1, 1, 1, 1, d }, { d, 0,0,d } }, -- 69 2120
+ { false, false, { d, 1, 1, 1, 1, d, d, 0, 0, 0, 0, d }, false, { d, 1, 1, 1, 1, d }, { d, 0, 0, 0, 0, d } }, -- 70 2121
+}
+
+local function whatever(data,nx,ny,threshold,background)
+
+ if background then
+
+ local llx = 1/2
+ local lly = llx
+ local urx = ny + llx
+ local ury = nx + lly
+
+ return { { llx, lly, urx, 0, urx, ury, 0, ury } }
+
+ else
+
+ local bands = { }
+ local b = 0
+
+ local function band(s,n,x,y) -- simple. no closure so fast
+ if n == 6 then
+ return {
+ x - s[ 2], y + s[ 1], x - s[ 4], y + s[ 3], x - s[ 6], y + s[ 5],
+ }
+ elseif n == 8 then
+ return {
+ x - s[ 2], y + s[ 1], x - s[ 4], y + s[ 3], x - s[ 6], y + s[ 5],
+ x - s[ 8], y + s[ 7],
+ }
+ elseif n == 10 then
+ return {
+ x - s[ 2], y + s[ 1], x - s[ 4], y + s[ 3], x - s[ 6], y + s[ 5],
+ x - s[ 8], y + s[ 7], x - s[10], y + s[ 9],
+ }
+ elseif n == 4 then
+ return {
+ x - s[ 2], y + s[ 1], x - s[ 4], y + s[ 3],
+ }
+ else -- 12
+ return {
+ x - s[ 2], y + s[ 1], x - s[ 4], y + s[ 3], x - s[ 6], y + s[ 5],
+ x - s[ 8], y + s[ 7], x - s[10], y + s[ 9], x - s[12], y + s[11],
+ }
+ end
+ end
+
+ local pp = { }
+
+ local d0 = data[1]
+ for j=1,ny-1 do
+ local d1 = data[j+1]
+ local k = j + 1
+ local p = false
+ for i=1,nx-1 do
+ local v = 0
+ local l = i + 1
+ local c1 = d0[i]
+ if c1 == threshold then
+ v = v + 27
+ elseif c1 > threshold then
+ v = v + 54
+ end
+ local c2 = d0[l]
+ if c2 == threshold then
+ v = v + 9
+ elseif c2 > threshold then
+ v = v + 18
+ end
+ local c3 = d1[l]
+ if c3 == threshold then
+ v = v + 3
+ elseif c3 > threshold then
+ v = v + 6
+ end
+ local c4 = d1[i]
+ if c4 == threshold then
+ v = v + 1
+ elseif c4 > threshold then
+ v = v + 2
+ end
+ if v > 0 and v < 80 then
+ if v == 40 then
+ -- a little optimization: full areas appended horizontally
+ if p then
+ p[4] = l -- i + 1
+ p[6] = l -- i + 1
+ else
+ -- x-0 y+1 x-1 y+1 x-1 y+0 x-0 y+0
+ p = { j, i, j, l, k, l, k, i }
+ b = b + 1 ; bands[b] = p
+ end
+ else
+ local s = singles[v]
+ if s then
+ b = b + 1 ; bands[b] = band(s,#s,k,i)
+ else
+ local s = sadles[v]
+ if s then
+ local m = (c1 + c2 + c3 + c4) / 4
+ if m < threshold then
+ local s1 = s[1] if s1 then b = b + 1 ; bands[b] = band(s1,#s1,i,j) end
+ local s2 = s[2] if s2 then b = b + 1 ; bands[b] = band(s2,#s2,i,j) end
+ elseif m == threshold then
+ local s3 = s[3] if s3 then b = b + 1 ; bands[b] = band(s3,#s3,i,j) end
+ local s4 = s[4] if s4 then b = b + 1 ; bands[b] = band(s4,#s4,i,j) end
+ else
+ local s5 = s[5] if s5 then b = b + 1 ; bands[b] = band(s5,#s5,i,j) end
+ local s6 = s[6] if s6 then b = b + 1 ; bands[b] = band(s6,#s6,i,j) end
+ end
+ end
+ end
+ p = false
+ end
+ else
+ p = false
+ end
+ end
+ d0 = d1
+ end
+ return bands
+ end
+end
+
+function mp.lmt_contours_edge_set_by_band(value)
+ local p = getparameterset()
+ local result = p.result
+
+ if result.cached then return end
+
+ local values = result.values
+ local nofvalues = result.nofvalues
+ local data = result.data
+ local nx = result.nx
+ local ny = result.ny
+ local bands = { }
+ result.bands = bands
+ for value=1,nofvalues do
+ bands[value] = whatever(data,ny,nx,value,value == 1)
+ end
+end
+
+function mp.lmt_contours_edge_get_band(value)
+ mpfill(getparameterset().result.bands[value],true)
+ mpflush()
+end
diff --git a/tex/context/base/mkiv/mlib-ctx.lua b/tex/context/base/mkiv/mlib-ctx.lua
index e19f111b4..88ef98f58 100644
--- a/tex/context/base/mkiv/mlib-ctx.lua
+++ b/tex/context/base/mkiv/mlib-ctx.lua
@@ -332,7 +332,7 @@ implement {
}
statistics.register("metapost", function()
- local n = metapost.n
+ local n = metapost.nofruns
if n and n > 0 then
local elapsedtime = statistics.elapsedtime
local elapsed = statistics.elapsed
diff --git a/tex/context/base/mkiv/mlib-ctx.mkiv b/tex/context/base/mkiv/mlib-ctx.mkiv
index 78a26ad1c..c90536937 100644
--- a/tex/context/base/mkiv/mlib-ctx.mkiv
+++ b/tex/context/base/mkiv/mlib-ctx.mkiv
@@ -19,7 +19,8 @@
\registerctxluafile{mlib-lmp}{}
\registerctxluafile{mlib-int}{}
-\doifelsefileexists{mlib-cnt.lua}{\registerctxluafile{mlib-int}{}}{}
+\doifelsefileexists{mlib-cnt.lua}{\registerctxluafile{mlib-cnt}{}}{}
+\doifelsefileexists{mlib-svg.lua}{\registerctxluafile{mlib-svg}{}}{}
\unprotect
diff --git a/tex/context/base/mkiv/mlib-ctx.mkxl b/tex/context/base/mkiv/mlib-ctx.mkxl
index c66e6f968..bb2460627 100644
--- a/tex/context/base/mkiv/mlib-ctx.mkxl
+++ b/tex/context/base/mkiv/mlib-ctx.mkxl
@@ -22,6 +22,9 @@
\registerctxluafile{mlib-int}{}
\registerctxluafile{mlib-lmt}{}
+\doifelsefileexists{mlib-cnt.lua}{\registerctxluafile{mlib-cnt}{}}{}
+\doifelsefileexists{mlib-svg.lua}{\registerctxluafile{mlib-svg}{}}{}
+
\unprotect
\protect \endinput
diff --git a/tex/context/base/mkiv/mlib-lmp.lua b/tex/context/base/mkiv/mlib-lmp.lua
index 93a758681..22607be8d 100644
--- a/tex/context/base/mkiv/mlib-lmp.lua
+++ b/tex/context/base/mkiv/mlib-lmp.lua
@@ -134,3 +134,11 @@ do
end
end
+
+function mp.lmt_svg_include()
+ local name = metapost.getparameter { "svg", "filename" }
+ local mps = metapost.svgtomp {
+ data = name and name ~= "" and io.loaddata(name) or "",
+ }
+ mp.direct(mps)
+end
diff --git a/tex/context/base/mkiv/mlib-lua.lua b/tex/context/base/mkiv/mlib-lua.lua
index 9e3802396..29ef334f1 100644
--- a/tex/context/base/mkiv/mlib-lua.lua
+++ b/tex/context/base/mkiv/mlib-lua.lua
@@ -75,6 +75,7 @@ do
local scan_cmykcolor = mplib.scan_cmykcolor
local scan_transform = mplib.scan_transform
local scan_path = mplib.scan_path
+ local scan_pen = mplib.scan_pen
scan.next = function(k) return scan_next (currentmpx,k) end
scan.expression = function(k) return scan_expression(currentmpx,k) end
@@ -90,6 +91,7 @@ do
scan.cmykcolor = function(t) return scan_cmykcolor (currentmpx,t) end
scan.transform = function(t) return scan_transform (currentmpx,t) end
scan.path = function(t) return scan_path (currentmpx,t) end
+ scan.pen = function(t) return scan_pen (currentmpx,t) end
else
@@ -148,6 +150,7 @@ do
local f_triplet = formatters["(%F,%F,%F)"]
local f_quadruple = formatters["(%F,%F,%F,%F)"]
local f_transform = formatters["totransform(%F,%F,%F,%F,%F,%F)"]
+ local f_pen = formatters["(pencircle transformed totransform(%F,%F,%F,%F,%F,%F))"]
local f_points = formatters["%p"]
local f_pair_pt = formatters["(%p,%p)"]
@@ -516,7 +519,12 @@ do
local tn = #t
if tn == 1 then
local t1 = t[1]
- n = n + 1 ; buffer[n] = f2(t1[1],t1[2])
+ n = n + 1
+ if t.pen then
+ buffer[n] = f_pen(unpack(t1))
+ else
+ buffer[n] = f2(t1[1],t1[2])
+ end
elseif tn > 0 then
if connector == true or connector == nil then
connector = ".."
@@ -534,6 +542,8 @@ do
local a = t[1]
local b = t[2]
n = n + 1
+ buffer[n] = "("
+ n = n + 1
if six and #a == 6 and #b == 6 then
buffer[n] = f6(a[1],a[2],a[5],a[6],b[3],b[4])
controls = ".."
@@ -575,6 +585,8 @@ do
else
buffer[n] = f2(a[1],a[2])
end
+ n = n + 1
+ buffer[n] = ")"
end
end
end
diff --git a/tex/context/base/mkiv/mlib-pdf.lua b/tex/context/base/mkiv/mlib-pdf.lua
index 524e930a9..ece332d84 100644
--- a/tex/context/base/mkiv/mlib-pdf.lua
+++ b/tex/context/base/mkiv/mlib-pdf.lua
@@ -400,7 +400,7 @@ function metapost.flush(specification,result)
local linecap = -1
local linejoin = -1
local dashed = false
-local linewidth = false
+ local linewidth = false
local llx = properties.llx
local lly = properties.lly
local urx = properties.urx
@@ -533,10 +533,10 @@ local linewidth = false
if pen then
if pen.type == "elliptical" then
transformed, penwidth = pen_characteristics(original) -- boolean, value
-if penwidth ~= linewidth then
- result[#result+1] = f_w(penwidth) -- todo: only if changed
- linewidth = penwidth
-end
+ if penwidth ~= linewidth then
+ result[#result+1] = f_w(penwidth)
+ linewidth = penwidth
+ end
if objecttype == "fill" then
objecttype = "both"
end
@@ -624,8 +624,7 @@ end
end
if object.grouped then
-- can be qQ'd so changes can end up in groups
- miterlimit, linecap, linejoin, dashed = -1, -1, -1, "" -- was false
-linewidth = false
+ miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false
end
end
end
diff --git a/tex/context/base/mkiv/mlib-pps.lua b/tex/context/base/mkiv/mlib-pps.lua
index 1457020ef..46d436466 100644
--- a/tex/context/base/mkiv/mlib-pps.lua
+++ b/tex/context/base/mkiv/mlib-pps.lua
@@ -691,7 +691,7 @@ metapost.splitprescript = splitprescript
-- end
function metapost.pluginactions(what,t,flushfigure) -- before/after object, depending on what
- if top.plugmode then -- hm, what about other features
+ if top and top.plugmode then -- hm, what about other features
for i=1,#what do
local wi = what[i]
if type(wi) == "function" then
@@ -708,14 +708,14 @@ function metapost.pluginactions(what,t,flushfigure) -- before/after object, depe
end
function metapost.resetplugins(t) -- intialize plugins, before figure
- if top.plugmode then
+ if top and top.plugmode then
outercolormodel = colors.currentmodel() -- currently overloads the one set at the tex end
resetteractions.runner(t)
end
end
function metapost.processplugins(object) -- each object (second pass)
- if top.plugmode then
+ if top and top.plugmode then
local prescript = object.prescript -- specifications
if prescript and #prescript > 0 then
local before = { }
diff --git a/tex/context/base/mkiv/mlib-run.lua b/tex/context/base/mkiv/mlib-run.lua
index fb1367151..bf7c8a796 100644
--- a/tex/context/base/mkiv/mlib-run.lua
+++ b/tex/context/base/mkiv/mlib-run.lua
@@ -57,6 +57,7 @@ metapost.showlog = false
metapost.lastlog = ""
metapost.texerrors = false
metapost.exectime = metapost.exectime or { } -- hack
+metapost.nofruns = 0
local mpxformats = { }
local mpxterminals = { }
@@ -100,6 +101,7 @@ local function executempx(mpx,data)
elseif type(data) == "table" then
data = prepareddata(data,collapse)
end
+ metapost.nofruns = metapost.nofruns + 1
return mpx:execute(data)
end
@@ -822,70 +824,13 @@ function metapost.directrun(formatname,filename,outputformat,astable,mpdata)
report_metapost("producing postscript and svg is no longer supported")
end
--- goodie
-
-function metapost.quickanddirty(mpxformat,data,incontext)
- if not data then
- mpxformat = "metafun"
- data = mpxformat
- end
- local code, bbox
- local flusher = {
- startfigure = function(n,llx,lly,urx,ury)
- code = { }
- bbox = { llx, lly, urx, ury }
- end,
- flushfigure = function(t)
- for i=1,#t do
- code[#code+1] = t[i]
- end
- end,
- stopfigure = function()
- end
- }
- local data = formatters["; beginfig(1) ;\n %s\n ; endfig ;"](data)
- metapost.process {
- mpx = mpxformat,
- flusher = flusher,
- askedfig = "all",
- useplugins = incontext,
- incontext = incontext,
- data = { data },
- }
- if code then
- return {
- bbox = bbox or { 0, 0, 0, 0 },
- code = code,
- data = data,
- }
- else
- report_metapost("invalid quick and dirty run")
- end
-end
-
-function metapost.getstatistics(memonly)
- if memonly then
- local n, m = 0, 0
- for name, mpx in next, mpxformats do
- n = n + 1
- m = m + mpx:statistics().memory
- end
- return n, m
- else
- local t = { }
- for name, mpx in next, mpxformats do
- t[name] = mpx:statistics()
- end
- return t
- end
-end
-
do
local result = { }
local width = 0
local height = 0
local depth = 0
+ local bbox = { 0, 0, 0, 0 }
local flusher = {
startfigure = function(n,llx,lly,urx,ury)
@@ -893,35 +838,59 @@ do
width = urx - llx
height = ury
depth = -lly
+ bbox = { llx, lly, urx, ury }
end,
flushfigure = function(t)
+ local r = #result
for i=1,#t do
- result[#result+1] = t[i]
+ r = r + 1
+ result[r] = t[i]
end
end,
stopfigure = function()
- end
+ end,
}
- function metapost.simple(format,code) -- even less than metapost.quickcanddirty
- local mpx = metapost.pushformat { } -- takes defaults
- -- metapost.setoutercolor(2)
+ function metapost.simple(format,code,useextensions)
+ local mpx = metapost.pushformat {
+ instance = "simplefun",
+ format = "metafun",
+ method = "double",
+ }
metapost.process {
mpx = mpx,
flusher = flusher,
askedfig = 1,
- useplugins = false,
- incontext = false,
+ useplugins = useextensions,
data = { "beginfig(1);", code, "endfig;" },
+ incontext = false,
}
metapost.popformat()
if result then
local stream = concat(result," ")
- result = nil -- cleanup
- return stream, width, height, depth
+ result = { } -- nil -- cleanup .. weird, we can have a dangling q
+ return stream, width, height, depth, bbox
else
- return "", 0, 0, 0
+ return "", 0, 0, 0, { 0, 0, 0, 0 }
end
end
end
+
+function metapost.getstatistics(memonly)
+ if memonly then
+ local n, m = 0, 0
+ for name, mpx in next, mpxformats do
+ n = n + 1
+ m = m + mpx:statistics().memory
+ end
+ return n, m
+ else
+ local t = { }
+ for name, mpx in next, mpxformats do
+ t[name] = mpx:statistics()
+ end
+ return t
+ end
+end
+
diff --git a/tex/context/base/mkiv/mlib-scn.lua b/tex/context/base/mkiv/mlib-scn.lua
index a85c861bd..8867455b6 100644
--- a/tex/context/base/mkiv/mlib-scn.lua
+++ b/tex/context/base/mkiv/mlib-scn.lua
@@ -60,6 +60,7 @@ local scancolor = scanners.color
local scancmykcolor = scanners.cmykcolor
local scantransform = scanners.transform
local scanpath = scanners.path
+local scanpen = scanners.pen
local mpprint = mp.print
local mpnumeric = mp.numeric
@@ -164,6 +165,7 @@ typescanners = {
[types.cmykcolor] = scan_cmykcolor,
[types.transform] = scan_transform,
[types.path] = scanpath,
+ [types.pen] = scanpen,
}
table.setmetatableindex(tokenscanners,function()
@@ -603,6 +605,22 @@ local function getparameterpath()
end
end
+local function getparameterpen()
+ local list, n = collectnames()
+ local v = namespaces
+ for i=1,n do
+ v = v[list[i]]
+ if not v then
+ break
+ end
+ end
+ if type(v) == "table" then
+ return mppath(v,"..",true)
+ else
+ return mppair(0,0)
+ end
+end
+
local function getparametertext()
local list, n = collectnames()
local strut = list[n]
@@ -660,6 +678,7 @@ metapost.registerscript("getparameterdefault", getparameterdefault)
metapost.registerscript("getparametercount", getparametercount)
metapost.registerscript("getmaxparametercount",getmaxparametercount)
metapost.registerscript("getparameterpath", getparameterpath)
+metapost.registerscript("getparameterpen", getparameterpen)
metapost.registerscript("getparametertext", getparametertext)
--------.registerscript("getparameteroption", getparameteroption)
metapost.registerscript("pushparameters", pushparameters)
diff --git a/tex/context/base/mkiv/mlib-svg.lua b/tex/context/base/mkiv/mlib-svg.lua
new file mode 100644
index 000000000..c3635480d
--- /dev/null
+++ b/tex/context/base/mkiv/mlib-svg.lua
@@ -0,0 +1,1635 @@
+if not modules then modules = { } end modules ['mlib-svg'] = {
+ version = 1.001,
+ comment = "companion to mlib-ctx.mkiv",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files",
+}
+
+-- As usual with these standards, things like a path can be very compact while the rest is
+-- very verbose which defeats the point. This is a first attempt. There will be a converter
+-- to MP as well as directly to PDF. This module was made for one of the dangerous curves
+-- talks at the 2019 CTX meeting. I will do the font when I need it (not that hard).
+--
+-- There is no real need to boost performance here .. we can always make a fast variant
+-- when really needed. I will also do some of the todo's when I run into proper fonts.
+
+-- Written with Anne Clark on speakers as distraction.
+
+-- TODO:
+
+-- optimize
+-- test for gzip header 0x1F 0x8B 0x08
+-- var()
+-- color hash
+-- currentColor
+-- instances
+-- --color<decimal>
+-- glyph<id>
+-- shading
+-- "none" -> false
+-- clip = [ auto | rect(llx,lly,urx,ury) ] (in svg)
+-- xlink url ... whatever
+-- mp svg module + shortcuts
+-- withpen -> pickup
+
+-- The fact that in the more recent versions of SVG the older text related elements
+-- are depricated and not even supposed to be supported, combined with the fact that
+-- the text element assumes css styling, demonstrates that there is not so much as a
+-- standard. It basically means that whatever technology dominates at some point
+-- (probably combined with some libraries that at that point exist) determine what
+-- is standard. Anyway, it probably also means that these formats are not that
+-- suitable for long term archival purposes. So don't take the next implementation
+-- too serious.
+
+-- We can do a direct conversion to PDF but then we also loose the abstraction which
+-- in the future will be used.
+
+local type, tonumber = type, tonumber
+
+local P, S, R, C, Ct, Cs, Cc, Cp, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Cc, lpeg.Cp, lpeg.Carg
+
+local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns
+local pi, sin, cos, asin, sind, cosd, tan, abs, sqrt = math.pi, math.sin, math.cos, math.asin, math.sind, math.cosd, math.tan, math.abs, math.sqrt
+local concat, setmetatableindex = table.concat, table.setmetatableindex
+local gmatch, gsub, find, match, rep = string.gmatch, string.gsub, string.find, string.match, string.rep
+local formatters = string.formatters
+
+local xmlconvert, xmlcollected, xmlcount, xmlfirst, xmlroot, xmltext = xml.convert, xml.collected, xml.count, xml.first, xml.root, xml.text
+
+metapost = metapost or { }
+local metapost = metapost
+local context = context
+
+local report = logs.reporter("metapost","svg")
+
+local trace = false
+-- local trace = true
+
+-- todo: also a high res mode
+-- todo: optimize (no hurry)
+
+local f_rectangle = formatters['unitsquare xyscaled (%.3N,%.3N) shifted (%.3N,%.3N)']
+local f_rounded = formatters['roundedsquarexy(%.3N,%.3N,%.3N,%.3N) shifted (%.3N,%.3N)']
+local f_ellipse = formatters['(fullcircle xyscaled (%.3N,%.3N) shifted (%.3N,%.3N))']
+local f_circle = formatters['(fullcircle scaled %.3N shifted (%.3N,%.3N))']
+local f_line = formatters['((%.3N,%.3N)--(%.3N,%.3N))']
+local f_fill = formatters['fill %s(%s--cycle)%s%s%s ;'] -- play safe
+local f_fill_cycle_c = formatters['fill %s(%s..cycle)%s%s%s ;']
+local f_fill_cycle_l = formatters['fill %s(%s--cycle)%s%s%s ;']
+local f_eofill = formatters['eofill %s(%s--cycle)%s%s%s ;'] -- play safe
+local f_eofill_cycle_c = formatters['eofill %s(%s..cycle)%s%s%s ;']
+local f_eofill_cycle_l = formatters['eofill %s(%s--cycle)%s%s%s ;']
+local f_nofill = formatters['nofill %s(%s--cycle)%s ;'] -- play safe
+local f_nofill_cycle_c = formatters['nofill %s(%s..cycle)%s ;']
+local f_nofill_cycle_l = formatters['nofill %s(%s--cycle)%s ;']
+local f_draw = formatters['draw %s(%s)%s%s%s%s%s ;']
+local f_nodraw = formatters['nodraw %s(%s)%s ;']
+
+-- local f_fill = formatters['F %s(%s--C)%s%s%s ;'] -- play safe
+-- local f_fill_cycle_c = formatters['F %s(%s..C)%s%s%s ;']
+-- local f_fill_cycle_l = formatters['F %s(%s--C)%s%s%s ;']
+-- local f_eofill = formatters['E %s(%s--C)%s%s%s ;'] -- play safe
+-- local f_eofill_cycle_c = formatters['E %s(%s..C)%s%s%s ;']
+-- local f_eofill_cycle_l = formatters['E %s(%s--C)%s%s%s ;']
+-- local f_nofill = formatters['f %s(%s--C)%s ;'] -- play safe
+-- local f_nofill_cycle_c = formatters['f %s(%s..C)%s ;']
+-- local f_nofill_cycle_l = formatters['f %s(%s--C)%s ;']
+-- local f_draw = formatters['D %s(%s)%s%s%s%s%s ;']
+-- local f_nodraw = formatters['d %s(%s)%s ;']
+
+local f_color = formatters[' withcolor "%s"']
+local f_rgb = formatters[' withcolor (%.3N,%.3N,%.3N)']
+local f_rgba = formatters[' withcolor (%.3N,%.3N,%.3N) withtransparency (1,%3N)']
+local f_triplet = formatters['(%.3N,%.3N,%.3N)']
+local f_gray = formatters[' withcolor %.3N']
+local f_opacity = formatters[' withtransparency (1,%.3N)']
+local f_pen = formatters[' withpen pencircle scaled %.3N']
+
+local f_dashed_n = formatters[" dashed dashpattern (%s ) "]
+local f_dashed_y = formatters[" dashed dashpattern (%s ) shifted (%.3N,0) "]
+
+local f_moveto = formatters['(%N,%n)']
+local f_curveto_z = formatters[' controls (%.3N,%.3N) and (%.3N,%.3N) .. (%.3N,%.3N)']
+local f_curveto_n = formatters['.. controls (%.3N,%.3N) and (%.3N,%.3N) .. (%.3N,%.3N)']
+local f_lineto_z = formatters['(%.3N,%.3N)']
+local f_lineto_n = formatters['-- (%.3N,%.3N)']
+
+local f_rotatedaround = formatters[" ) rotatedaround((%.3N,%.3N),%.3N)"]
+local f_rotated = formatters[" ) rotated(%.3N)"]
+local f_shifted = formatters[" ) shifted(%.3N,%.3N)"]
+local f_slanted_x = formatters[" ) xslanted(%.3N)"]
+local f_slanted_y = formatters[" ) yslanted(%.3N)"]
+local f_scaled = formatters[" ) scaled(%.3N)"]
+local f_xyscaled = formatters[" ) xyscaled(%.3N,%.3N)"]
+local f_matrix = formatters[" ) transformed bymatrix(%.3N,%.3N,%.3N,%.3N,%.3N,%.3N)"]
+
+-- penciled n -> withpen pencircle scaled n
+-- applied (...) -> transformed bymatrix (...)
+-- withopacity n -> withtransparency (1,n)
+
+local s_clip_start = 'draw image ('
+local f_clip_stop = formatters[') ; clip currentpicture to (%s) ;']
+local f_eoclip_stop = formatters[') ; eoclip currentpicture to (%s) ;']
+
+local f_transform_start = formatters["draw %s image ( "]
+local f_transform_stop = formatters[") %s ;"]
+
+local s_offset_start = "draw image ( "
+local f_offset_stop = formatters[") shifted (%.3N,%.3N) ;"]
+
+local f_viewport_start = "draw image ("
+local f_viewport_stop = ") ;"
+local f_viewport_shift = formatters["currentpicture := currentpicture shifted (%03N,%03N);"]
+local f_viewport_clip = formatters["clip currentpicture to (unitsquare xyscaled (%03N,%03N));"]
+
+local f_linecap = formatters["interim linecap := %s ;"]
+local f_linejoin = formatters["interim linejoin := %s ;"]
+local f_miterlimit = formatters["interim miterlimit := %s ;"]
+
+local s_begingroup = "begingroup;"
+local s_endgroup = "endgroup;"
+
+-- make dedicated macro
+
+local s_shade_linear = ' withshademethod "linear" '
+local s_shade_circular = ' withshademethod "circular" '
+local f_shade_step = formatters['withshadestep ( withshadefraction %.3N withshadecolors(%s,%s) )']
+local f_shade_one = formatters['withprescript "sh_center_a=%.3N %.3N"']
+local f_shade_two = formatters['withprescript "sh_center_b=%.3N %.3N"']
+
+local f_text_scaled = formatters['(textext.drt("%s") scaled %.3N shifted (%.3N,%.3N))']
+local f_text_normal = formatters['(textext.drt("%s") shifted (%.3N,%.3N))']
+
+local p_digit = lpegpatterns.digit
+local p_hexdigit = lpegpatterns.hexdigit
+local p_space = lpegpatterns.whitespace
+
+local p_hexcolor = P("#") * C(p_hexdigit*p_hexdigit)^1 / function(r,g,b)
+ r = tonumber(r,16)/255
+ if g then
+ g = tonumber(g,16)/255
+ end
+ if b then
+ b = tonumber(b,16)/255
+ end
+ if b then
+ return f_rgb(r,g,b)
+ else
+ return f_gray(r)
+ end
+end
+
+local p_hexcolor3 = P("#") * C(p_hexdigit*p_hexdigit)^1 / function(r,g,b)
+ r = tonumber(r,16)/255
+ g = g and tonumber(g,16)/255 or r
+ b = b and tonumber(b,16)/255 or g
+ return f_triplet(r,g,b)
+end
+
+local function hexcolor(c)
+ return lpegmatch(p_hexcolor,c)
+end
+
+local function hexcolor3(c)
+ return lpegmatch(p_hexcolor3,c)
+end
+
+-- gains a little:
+
+-- local hexhash = setmetatableindex(function(t,k) local v = lpegmatch(p_hexcolor, k) t[k] = v return v end) -- per file
+-- local hexhash3 = setmetatableindex(function(t,k) local v = lpegmatch(p_hexcolor3,k) t[k] = v return v end) -- per file
+--
+-- local function hexcolor (c) return hexhash [c] end -- directly do hexhash [c]
+-- local function hexcolor3(c) return hexhash3[c] end -- directly do hexhash3[c]
+
+-- Most of the conversion is rather trivial code till I ran into a file with arcs. A bit
+-- of searching lead to the a2c javascript function but it has some puzzling thingies
+-- (like sin and cos definitions that look like leftovers). Anyway, we can if needed
+-- optimize it a bit more. Here does it come from:
+
+-- http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+-- https://github.com/adobe-webplatform/Snap.svg/blob/b242f49e6798ac297a3dad0dfb03c0893e394464/src/path.js
+
+local d120 = (pi * 120) / 180
+local pi2 = 2 * pi
+
+local function a2c(x1, y1, rx, ry, angle, large, sweep, x2, y2, f1, f2, cx, cy)
+
+ local recursive = f1
+
+ if rx == 0 or ry == 0 then
+ return { x1, y1, x2, y2, x2, y2 }
+ end
+
+ if x1 == x2 and y1 == y2 then
+ return { x1, y1, x2, y2, x2, y2 }
+ end
+
+ local rad = pi / 180 * angle
+ local res = nil
+
+ local cosrad = cos(-rad) -- local cosrad = cosd(angle)
+ local sinrad = sin(-rad) -- local sinrad = sind(angle)
+
+ if not recursive then
+
+ x1, y1 = x1 * cosrad - y1 * sinrad, x1 * sinrad + y1 * cosrad
+ x2, y2 = x2 * cosrad - y2 * sinrad, x2 * sinrad + y2 * cosrad
+
+ local x = (x1 - x2) / 2
+ local y = (y1 - y2) / 2
+ local xx = x * x
+ local yy = y * y
+ local h = xx / (rx * rx) + yy / (ry * ry)
+
+ if h > 1 then
+ h = sqrt(h)
+ rx = h * rx
+ ry = h * ry
+ end
+
+ local rx2 = rx * rx
+ local ry2 = ry * ry
+ local ry2xx = ry2 * xx
+ local rx2yy = rx2 * yy
+ local total = rx2yy + ry2xx -- otherwise overflow
+
+ local k = total == 0 and 0 or sqrt(abs((rx2 * ry2 - rx2yy - ry2xx) / total))
+
+ if large == sweep then
+ k = -k
+ end
+
+ cx = k * rx * y / ry + (x1 + x2) / 2
+ cy = k * -ry * x / rx + (y1 + y2) / 2
+
+ f1 = (y1 - cy) / ry -- otherwise crash on a tiny eps
+ f2 = (y2 - cy) / ry -- otherwise crash on a tiny eps
+
+ f1 = asin((f1 < -1.0 and -1.0) or (f1 > 1.0 and 1.0) or f1)
+ f2 = asin((f2 < -1.0 and -1.0) or (f2 > 1.0 and 1.0) or f2)
+
+ if x1 < cx then f1 = pi - f1 end
+ if x2 < cx then f2 = pi - f2 end
+
+ if f1 < 0 then f1 = pi2 + f1 end
+ if f2 < 0 then f2 = pi2 + f2 end
+
+ if sweep ~= 0 and f1 > f2 then
+ f1 = f1 - pi2
+ end
+ if sweep == 0 and f2 > f1 then
+ f2 = f2 - pi2
+ end
+
+ end
+
+ if abs(f2 - f1) > d120 then
+ local f2old = f2
+ local x2old = x2
+ local y2old = y2
+ f2 = f1 + d120 * ((sweep ~= 0 and f2 > f1) and 1 or -1)
+ x2 = cx + rx * cos(f2)
+ y2 = cy + ry * sin(f2)
+ res = a2c(x2, y2, rx, ry, angle, 0, sweep, x2old, y2old, f2, f2old, cx, cy)
+ end
+
+ local c1 = cos(f1)
+ local s1 = sin(f1)
+ local c2 = cos(f2)
+ local s2 = sin(f2)
+
+ local t = tan((f2 - f1) / 4)
+ local hx = 4 * rx * t / 3
+ local hy = 4 * ry * t / 3
+
+ local r = { x1 - hx * s1, y1 + hy * c1, x2 + hx * s2, y2 - hy * c2, x2, y2, unpack(res or { }) }
+
+ if not recursive then -- we can also check for sin/cos being 0/1
+ cosrad = cos(rad)
+ sinrad = sin(rad)
+ -- cosrad = cosd(angle)
+ -- sinrad = sind(angle)
+ for i0=1,#r,2 do
+ local i1 = i0 + 1
+ local x = r[i0]
+ local y = r[i1]
+ r[i0] = x * cosrad - y * sinrad
+ r[i1] = x * sinrad + y * cosrad
+ end
+ end
+
+ return r
+end
+
+-- incredible: we can find .123.456 => 0.123 0.456 ...
+
+local factors = {
+ ["pt"] = 1.25,
+ ["mm"] = 3.543307,
+ ["cm"] = 35.43307,
+ ["px"] = 1,
+ ["pc"] = 15,
+ ["in"] = 90,
+ ["em"] = 12 * 1.25,
+ ["ex"] = 8 * 1.25,
+}
+
+local percentage_r = 1/100
+local percentage_x = percentage_r
+local percentage_y = percentage_r
+
+local p_command_x = C(S("Hh"))
+local p_command_y = C(S("Vv"))
+local p_command_xy = C(S("CcLlMmQqSsTt"))
+local p_command_a = C(S("Aa"))
+local p_command = C(S("Zz"))
+
+local p_separator = S("\t\n\r ,")^1
+local p_number = (S("+-")^0 * (p_digit^0 * P(".") * p_digit^1 + p_digit^1 * P(".") + p_digit^1) )
+
+local function convert (n) n = tonumber(n) return n end
+local function convert_r (n,u) n = tonumber(n) if u == true then return percentage_r * n elseif u then return u * n else return n end end
+local function convert_x (n,u) n = tonumber(n) if u == true then return percentage_x * n elseif u then return u * n else return n end end
+local function convert_y (n,u) n = tonumber(n) if u == true then return percentage_y * n elseif u then return u * n else return n end end
+local function convert_vx(n,u) n = tonumber(n) if u == true then return percentage_x * n elseif u then return u * n else return n end end
+local function convert_vy(n,u) n = - tonumber(n) if u == true then return percentage_y * n elseif u then return u * n else return n end end
+
+local p_unit = ( P("p") * S("txc") + P("e") * S("xm") + S("mc") * P("m") + P("in")) / factors
+local p_percent = P("%") * Cc(true)
+
+local c_number_n = C(p_number)
+local c_number_u = C(p_number) * (p_unit + p_percent)^-1
+
+local p_number_n = c_number_n / convert
+local p_number_x = c_number_u / convert_x
+local p_number_vx = c_number_u / convert_vx
+local p_number_y = c_number_u / convert_y
+local p_number_vy = c_number_u / convert_vy
+local p_number_r = c_number_u / convert_r
+
+-- local p_number = p_number_r -- maybe no percent here
+
+local function asnumber (s) return lpegmatch(p_number, s) end
+local function asnumber_r (s) return lpegmatch(p_number_r, s) end
+local function asnumber_x (s) return lpegmatch(p_number_x, s) end
+local function asnumber_y (s) return lpegmatch(p_number_y, s) end
+local function asnumber_vx(s) return lpegmatch(p_number_vx,s) end
+local function asnumber_vy(s) return lpegmatch(p_number_vy,s) end
+
+local p_numbersep = p_number_n + p_separator
+local p_numbers = p_separator^0 * P("(") * p_numbersep^0 * p_separator^0 * P(")")
+local p_four = p_numbersep^4
+
+-- local p_path = Ct((p_command + (p_number_x * p_separator^0 * p_number_y * p_separator^0) + p_separator)^1)
+
+local p_path = Ct ( (
+ p_command_xy * (p_separator^0 * p_number_vx *
+ p_separator^0 * p_number_vy )^1
+ + p_command_x * (p_separator^0 * p_number_vx )^1
+ + p_command_y * (p_separator^0 * p_number_vy )^1
+ + p_command_a * (p_separator^0 * p_number_vx *
+ p_separator^0 * p_number_vy *
+ p_separator^0 * p_number_r *
+ p_separator^0 * p_number_n * -- flags
+ p_separator^0 * p_number_n * -- flags
+ p_separator^0 * p_number_vx *
+ p_separator^0 * p_number_vy )^1
+ + p_command
+ + p_separator
+)^1 )
+
+local p_rgbacolor = P("rgba(") * (C(p_number) + p_separator)^1 * P(")")
+
+local function rgbacolor(s)
+ local r, g, b, a = lpegmatch(p_rgbacolor,s)
+ if a then
+ return f_rgba(r/255,g/255,b/255,a)
+ end
+end
+
+local function viewbox(v)
+ local x, y, w, h = lpegmatch(p_four,v)
+ if h then
+ return x, y, w, h
+ end
+end
+
+-- actually we can loop faster because we can go to the last one
+
+local function grabpath(str)
+ local p = lpegmatch(p_path,str)
+ local np = #p
+ local t = { } -- no real saving here if we share
+ local n = 0
+ local all = { entries = np, closed = false, curve = false }
+ local a = 0
+ local i = 0
+ local last = "M"
+ local prev = last
+ local kind = "L"
+ local x = 0
+ local y = 0
+ local x1 = 0
+ local y1 = 0
+ local x2 = 0
+ local y2 = 0
+ local rx = 0
+ local ry = 0
+ local ar = 0
+ local al = 0
+ local as = 0
+ local ac = nil
+ while i < np do
+ i = i + 1
+ local pi = p[i]
+ if type(pi) ~= "number" then
+ last = pi
+ i = i + 1
+ pi = p[i]
+ end
+ -- most often
+ ::restart::
+ if last == "c" then
+ x1 = x + pi
+ i = i + 1 ; y1 = y + p[i]
+ i = i + 1 ; x2 = x + p[i]
+ i = i + 1 ; y2 = y + p[i]
+ i = i + 1 ; x = x + p[i]
+ i = i + 1 ; y = y + p[i]
+ goto curveto
+ elseif last == "l" then
+ x = x + pi
+ i = i + 1 ; y = y + p[i]
+ goto lineto
+ elseif last == "h" then
+ x = x + pi
+ goto lineto
+ elseif last == "v" then
+ y = y + pi
+ goto lineto
+ elseif last == "a" then
+ x1 = x
+ y1 = y
+ rx = pi
+ i = i + 1 ; ry = p[i]
+ i = i + 1 ; ar = p[i]
+ i = i + 1 ; al = p[i]
+ i = i + 1 ; as = p[i]
+ i = i + 1 ; x = x + p[i]
+ i = i + 1 ; y = y + p[i]
+ goto arc
+ elseif last == "s" then
+ if prev == "C" then
+ x1 = 2 * x - x2
+ y1 = 2 * y - y2
+ else
+ x1 = x
+ y1 = y
+ end
+ x2 = x + pi
+ i = i + 1 ; y2 = y + p[i]
+ i = i + 1 ; x = x + p[i]
+ i = i + 1 ; y = y + p[i]
+ goto curveto
+ elseif last == "m" then
+ if n > 0 then
+ a = a + 1
+ all[a] = concat(t,"",1,n)
+ n = 0
+ end
+ x = x + pi
+ i = i + 1 ; y = y + p[i]
+ goto moveto
+ elseif last == "z" then
+ goto close
+ -- less frequent
+ elseif last == "C" then
+ x1 = pi
+ i = i + 1 ; y1 = p[i]
+ i = i + 1 ; x2 = p[i]
+ i = i + 1 ; y2 = p[i]
+ i = i + 1 ; x = p[i]
+ i = i + 1 ; y = p[i]
+ goto curveto
+ elseif last == "L" then
+ x = pi
+ i = i + 1 ; y = p[i]
+ goto lineto
+ elseif last == "H" then
+ x = pi
+ goto lineto
+ elseif last == "V" then
+ y = pi
+ goto lineto
+ elseif last == "A" then
+ x1 = x
+ y1 = y
+ rx = pi
+ i = i + 1 ; ry = p[i]
+ i = i + 1 ; ar = p[i]
+ i = i + 1 ; al = p[i]
+ i = i + 1 ; as = p[i]
+ i = i + 1 ; x = p[i]
+ i = i + 1 ; y = p[i]
+ goto arc
+ elseif last == "S" then
+ if prev == "C" then
+ x1 = 2 * x - x2
+ y1 = 2 * y - y2
+ else
+ x1 = x
+ y1 = y
+ end
+ x2 = pi
+ i = i + 1 ; y2 = p[i]
+ i = i + 1 ; x = p[i]
+ i = i + 1 ; y = p[i]
+ goto curveto
+ elseif last == "M" then
+ if n > 0 then
+ a = a + 1 ; all[a] = concat(t,"",1,n) ; n = 0
+ end
+ x = pi ;
+ i = i + 1 ; y = p[i]
+ goto moveto
+ elseif last == "Z" then
+ goto close
+ -- very seldom
+ elseif last == "q" then
+ x1 = x + pi
+ i = i + 1 ; y1 = y + p[i]
+ i = i + 1 ; x2 = x + p[i]
+ i = i + 1 ; y2 = y + p[i]
+ goto quadratic
+ elseif last == "t" then
+ if prev == "C" then
+ x1 = 2 * x - x1
+ y1 = 2 * y - y1
+ else
+ x1 = x
+ y1 = y
+ end
+ x2 = x + pi
+ i = i + 1 ; y2 = y + p[i]
+ goto quadratic
+ elseif last == "Q" then
+ x1 = pi
+ i = i + 1 ; y1 = p[i]
+ i = i + 1 ; x2 = p[i]
+ i = i + 1 ; y2 = p[i]
+ goto quadratic
+ elseif last == "T" then
+ if prev == "C" then
+ x1 = 2 * x - x1
+ y1 = 2 * y - y1
+ else
+ x1 = x
+ y1 = y
+ end
+ x2 = pi
+ i = i + 1 ; y2 = p[i]
+ goto quadratic
+ else
+ goto continue
+ end
+ ::moveto::
+ n = n + 1 ; t[n] = f_moveto(x,y)
+ last = last == "M" and "L" or "l"
+ prev = "M"
+ goto continue
+ ::lineto::
+ n = n + 1 ; t[n] = (n > 0 and f_lineto_n or f_lineto_z)(x,y)
+ prev = "L"
+ goto continue
+ ::curveto::
+ n = n + 1 ; t[n] = (n > 0 and f_curveto_n or f_curveto_z)(x1,y1,x2,y2,x,y)
+ prev = "C"
+ goto continue
+ ::arc::
+ ac = a2c(x1,y1,rx,ry,ar,al,as,x,y)
+ for i=1,#ac,6 do
+ n = n + 1 ; t[n] = (n > 0 and f_curveto_n or f_curveto_z)(
+ ac[i],ac[i+1],ac[i+2],ac[i+3],ac[i+4],ac[i+5]
+ )
+ end
+ prev = "A"
+ goto continue
+ ::quadratic::
+ n = n + 1 ; t[n] = (n > 0 and f_curveto_n or f_curveto_z)(
+ x + 2/3 * (x1-x ), y + 2/3 * (y1-y ),
+ x2 + 2/3 * (x1-x2), y2 + 2/3 * (y1-y2),
+ x2, y2
+ )
+ x = x2
+ y = y2
+ prev = "C"
+ goto continue
+ ::close::
+ n = n + 1 ; t[n] = prev == "C" and "..cycle" or "--cycle"
+ if n > 0 then
+ a = a + 1 ; all[a] = concat(t,"",1,n) ; n = 0
+ end
+ if i == n then
+ break
+ else
+ i = i - 1
+ end
+ kind = prev
+ prev = "Z"
+ ::continue::
+ end
+ if n > 0 then
+ a = a + 1 ; all[a] = concat(t,"",1,n) ; n = 0
+ end
+ if prev == "Z" then
+ all.closed = true
+ end
+ all.curve = (kind == "C" or kind == "A")
+ return all
+end
+
+local transform do
+
+ --todo: better lpeg
+
+ local n = 0
+
+ local function rotate(r,x,y)
+ if x then
+ n = n + 1
+ return r and f_rotatedaround(x,-(y or x),-r)
+ elseif r then
+ n = n + 1
+ return f_rotated(-r)
+ else
+ return ""
+ end
+ end
+
+ local function translate(x,y)
+ if y then
+ n = n + 1
+ return f_shifted(x,-y)
+ elseif x then
+ n = n + 1
+ return f_shifted(x,0)
+ else
+ return ""
+ end
+ end
+
+ local function scale(x,y)
+ if y then
+ n = n + 1
+ return f_xyscaled(x,y)
+ elseif x then
+ n = n + 1
+ return f_scaled(x)
+ else
+ return ""
+ end
+ end
+
+ local function skewx(x)
+ if x then
+ n = n + 1
+ return f_slanted_x(math.sind(-x))
+ else
+ return ""
+ end
+ end
+
+ local function skewy(y)
+ if y then
+ n = n + 1
+ return f_slanted_y(math.sind(-y))
+ else
+ return ""
+ end
+ end
+
+ local function matrix(rx,sx,sy,ry,tx,ty)
+ n = n + 1
+ return f_matrix(rx or 1, sx or 0, sy or 0, ry or 1, tx or 0, - (ty or 0))
+ end
+
+ -- how to deal with units here?
+
+ local p_transform = Cs ( (
+ P("translate") * p_numbers / translate -- maybe xy
+ + P("scale") * p_numbers / scale
+ + P("rotate") * p_numbers / rotate
+ + P("matrix") * p_numbers / matrix
+ + P("skewX") * p_numbers / skewx
+ + P("skewY") * p_numbers / skewy
+ -- + p_separator
+ + P(1)/""
+ )^1)
+
+ transform = function(t,image)
+ n = 0
+ local e = lpegmatch(p_transform,t)
+ local b = rep("( ",n)
+ if image then
+ return f_transform_start(b), f_transform_stop(e)
+ else
+ return b, e
+ end
+ end
+
+end
+
+local dashed do
+
+ -- actually commas are mandate but we're tolerant
+
+ local p_number = p_separator^0/"" * p_number_r
+ local p_on = Cc(" on ") * p_number
+ local p_off = Cc(" off ") * p_number
+ local p_dashed = Cs((p_on * p_off^-1)^1)
+
+ dashed = function(s,o)
+ if not find(s,",") then
+ -- a bit of a hack:
+ s = s .. " " .. s
+ end
+ return (o and f_dashed_y or f_dashed_n)(lpegmatch(p_dashed,s),o)
+ end
+
+end
+
+do
+
+ local defaults = {
+ x = 0, x1 = 0, x2 = 0, cx = 0, rx = 0,
+ y = 0, y1 = 0, y2 = 0, cy = 0, ry = 0,
+ r = 0,
+
+ width = 0,
+ height = 0,
+
+ stroke = "none",
+ fill = "black",
+ opacity = "none",
+
+ ["stroke-width"] = 1,
+ ["stroke-linecap"] = "none",
+ ["stroke-linejoin"] = "none",
+ ["stroke-dasharray"] = "none",
+ ["stroke-dashoffset"] = "none",
+ ["stroke-miterlimit"] = "none",
+ ["stroke-opacity"] = "none",
+
+ ["fill-opacity"] = "none",
+ -- ["fill-rule"] = "nonzero",
+ -- ["clip-rule"] = "nonzero",
+ }
+
+ local handlers = { }
+ local process = false
+ local root = false
+ local result = false
+ local r = false
+ local definitions = false
+ local styles = false
+ local bodyfont = false
+
+ -- todo: check use ... and make definitions[id] self resolving
+
+ local function locate(id)
+ local ref = gsub(id,"^url%(#(.-)%)$","%1")
+ local ref = gsub(ref,"^#","")
+ local res = xmlfirst(root,"**[@id='"..ref.."']")
+ if res then
+ definitions[id] = res
+ return res
+ else
+ -- warning
+ end
+ end
+
+ local function clippath(id,a)
+ local spec = definitions[id] or locate(id)
+ if spec then
+ local kind = spec.tg
+ if kind == "clipPath" then
+ ::again::
+ for c in xmlcollected(spec,"/(path|use|g)") do
+ local tg = c.tg
+ if tg == "use" then
+ local ca = c.at
+ local id = ca["xlink:href"]
+ if id then
+ spec = locate(id)
+ if spec then
+ local sa = spec.at
+ setmetatableindex(sa,ca)
+ if spec.tg == "path" then
+ local d = sa.d
+ if d then
+ local p = grabpath(d)
+ p.evenodd = sa["clip-rule"] == "evenodd" or a["clip-rule"] == "evenodd"
+ return p
+ else
+ goto done
+ end
+ else
+ goto again
+ end
+ end
+ end
+ -- break
+ elseif tg == "path" then
+ local ca = c.at
+ local d = ca.d
+ if d then
+ local p = grabpath(d)
+ p.evenodd = ca["clip-rule"] == "evenodd" or a["clip-rule"] == "evenodd"
+ return p
+ else
+ goto done
+ end
+ else
+ -- inherit?
+ end
+ end
+ ::done::
+ else
+ report("unknown clip %a",id)
+ end
+ end
+ end
+
+ local function gradient(id)
+ local spec = definitions[id]
+ if spec then
+ local kind = spec.tg
+ local shade = nil
+ local n = 1
+ local a = spec.at
+ if kind == "linearGradient" then
+ shade = { s_shade_linear }
+ --
+ local x1 = a.x1
+ local y1 = a.y1
+ local x2 = a.x2
+ local y2 = a.y2
+ if x1 and y1 then
+ n = n + 1 shade[n] = f_shade_one(asnumber_vx(x1),asnumber_vy(y1))
+ end
+ if x2 and y2 then
+ n = n + 1 shade[n] = f_shade_one(asnumber_vx(x2),asnumber_vy(y2))
+ end
+ --
+ elseif kind == "radialGradient" then
+ shade = { s_shade_circular }
+ --
+ local cx = a.cx -- x center
+ local cy = a.cy -- y center
+ local r = a.r -- radius
+ local fx = a.fx -- focal points
+ local fy = a.fy -- focal points
+ --
+ if cx and cy then
+ -- todo
+ end
+ if r then
+ -- todo
+ end
+ if fx and fy then
+ -- todo
+ end
+ else
+ report("unknown gradient %a",id)
+ return
+ end
+ -- local gu = a.gradientUnits
+ -- local gt = a.gradientTransform
+ -- local sm = a.spreadMethod
+ local colora, colorb
+ for c in xmlcollected(spec,"/stop") do
+ local a = c.at
+ local offset = a.offset
+ local colorb = a["stop-color"]
+ local opacity = a["stop-opacity"]
+
+ colorb = colorb and hexcolor3(colorb) or colorb
+ colora = colora or colorb
+
+ -- what if no percentage
+
+ local fraction = offset and asnumber_r(offset)
+
+ if not fraction then
+ -- offset = tonumber(offset)
+ -- for now
+ fraction = xmlcount(spec,"/stop")/100
+ end
+
+ n = n + 1 shade[n] = f_shade_step(fraction,colora,colorb)
+
+ prevcolor = color
+ end
+ return concat(shade)
+ end
+ end
+
+ local function drawproperties(stroke,a)
+ local w = a["stroke-width"]
+ if w then
+ w = f_pen(asnumber_r(w))
+ else
+ w = ""
+ end
+ local d = a["stroke-dasharray"]
+ if d ~= "none" then
+ local o = a["stroke-dashoffset"]
+ if o ~= "none" then
+ o = asnumber_r(o)
+ else
+ o = false
+ end
+ d = dashed(d,o)
+ else
+ d = ""
+ end
+ local c = hexcolor(stroke)
+ if not c then
+ c = rgbacolor(stroke)
+ if not c then
+ c = f_color(stroke)
+ end
+ end
+ local o = a["stroke-opacity"] or a.opacity
+ if o ~= "none" then
+ o = asnumber_r(o)
+ if o and o ~= 1 then
+ o = f_opacity(o)
+ else
+ o = ""
+ end
+ else
+ o = ""
+ end
+ return w, d, c, o
+ end
+
+ local function fillproperties(fill,a)
+ local c = gradient(fill)
+ if not c then
+ c = hexcolor(fill)
+ if not c then
+ c = f_color(fill)
+ end
+ end
+ local o = a["fill-opacity"] or a.opacity
+ if o == "none" then
+ o = asnumber_r(o)
+ if o and o ~= 1 then
+ o = f_opacity(asnumber_r(o))
+ else
+ o = ""
+ end
+ else
+ o = ""
+ end
+ return c, o
+ end
+
+ -- todo: clip = [ auto | rect(llx,lly,urx,ury) ]
+
+ local function offset(a)
+ local x = a.x
+ local y = a.y
+ if x then x = asnumber_vx(x) end
+ if y then y = asnumber_vy(y) end
+ if not x then x = 0 end
+ if not y then y = 0 end
+ if x ~= 0 or y ~= 0 then
+ r = r + 1 ; result[r] = s_offset_start
+ return function()
+ r = r + 1 ; result[r] = f_offset_stop(x,-y)
+ end
+ end
+ end
+
+ local function viewport(x,y,w,h,noclip)
+ r = r + 1 ; result[r] = f_viewport_start
+ return function()
+ r = r + 1 ; result[r] = f_viewport_shift(-x,y)
+ if not noclip then
+ r = r + 1 ; result[r] = f_viewport_clip(w,-h)
+ end
+ r = r + 1 ; result[r] = f_viewport_stop
+ end
+ end
+
+ function handlers.defs(c,top)
+ for c in xmlcollected(c,"/*") do
+ local a = c.at
+ local id = a.id
+ -- setmetatableindex(a,top)
+ if id then
+ definitions["#" .. id ] = c
+ definitions["url(#" .. id .. ")"] = c
+ end
+ end
+ end
+
+ function handlers.use(c,top)
+ local a = setmetatableindex(c.at,top)
+ local id = a["xlink:href"] -- better a rawget
+ if id then
+ local d = definitions[id]
+ if d then
+ local h = handlers[d.tg]
+ if h then
+ h(d,a)
+ end
+ else
+ local res = locate(id)
+ if res then
+ local wrapup = offset(a)
+ process(res,"/*",top)
+ if wrapup then
+ wrapup()
+ end
+ else
+ report("unknown definition %a",id)
+ end
+ end
+ end
+ end
+
+ local linecaps = { butt = "butt", square = "squared", round = "rounded" }
+ local linejoins = { miter = "mitered", bevel = "beveled", round = "rounded" }
+
+ local function stoplineproperties()
+ r = r + 1 result[r] = s_endgroup
+ end
+
+ local function startlineproperties(a)
+ local cap = a["stroke-linecap"]
+ local join = a["stroke-linejoin"]
+ local limit = a["stroke-miterlimit"]
+ cap = cap ~= "none" and linecaps [cap] or false
+ join = join ~= "none" and linejoins[join] or false
+ limit = limit ~= "none" and asnumber_r(limit) or false
+ if cap or join or limit then
+ r = r + 1 result[r] = s_begingroup
+ if cap then
+ r = r + 1 result[r] = f_linecap(cap)
+ end
+ if join then
+ r = r + 1 result[r] = f_linejoin(join)
+ end
+ if limit then
+ r = r + 1 result[r] = f_miterlimit(limit)
+ end
+ return stoplineproperties
+ end
+ end
+
+ local function flush(a,shape,nofill)
+ local fill = not nofill and a.fill
+ local stroke = a.stroke
+ local cpath = a["clip-path"]
+ local trans = a.transform
+ local b, e = "", ""
+ if cpath then
+ cpath = clippath(cpath,a)
+ end
+ if cpath then
+ r = r + 1 result[r] = s_clip_start
+ end
+ if trans then
+ b, e = transform(trans)
+ end
+ if fill and fill ~= "none" then
+ r = r + 1 result[r] = (a["fill-rule"] == "evenodd" and f_eofill or f_fill)(b,shape,e,fillproperties(fill,a))
+ end
+ if stroke and stroke ~= "none" and stroke ~= 0 then
+ local wrapup = startlineproperties(a)
+ r = r + 1 result[r] = f_draw(b,shape,e,drawproperties(stroke,a))
+ if wrapup then
+ wrapup()
+ end
+ end
+ if cpath then
+ r = r + 1 result[r] = (cpath.evenodd and f_eoclip_stop or f_clip_stop)(cpath[1])
+ end
+ end
+
+ -- todo: strokes in:
+
+ local function flushpath(a,shape)
+ local fill = a.fill
+ local stroke = a.stroke
+ local cpath = a["clip-path"]
+ local trans = a.transform
+ local b, e = "", ""
+ if cpath then
+ cpath = definitions[cpath]
+ end
+ if cpath then
+ r = r + 1 result[r] = s_clip_start
+ end
+ -- todo: image (nicer for transform too)
+ if trans then
+ b, e = transform(trans)
+ end
+ if fill and fill ~= "none" then
+ local n = #shape
+ for i=1,n do
+ r = r + 1
+ if i == n then
+ local f = a["fill-rule"] == "evenodd"
+ if shape.closed then
+ f = f and f_eofill or f_fill
+ elseif shape.curve then
+ f = f and f_eofill_cycle_c or f_fill_cycle_c
+ else
+ f = f and f_eofill_cycle_l or f_fill_cycle_l
+ end
+ result[r] = f(b,shape[i],e,fillproperties(fill,a))
+ else
+ result[r] = f_nofill(b,shape[i],e)
+ end
+ end
+ end
+ if stroke and stroke ~= "none" and stroke ~= 0 then
+ local wrapup = startlineproperties(a)
+ local n = #shape
+ for i=1,n do
+ r = r + 1
+ if i == n then
+ result[r] = f_draw(b,shape[i],e,drawproperties(stroke,a))
+ else
+ result[r] = f_nodraw(b,shape[i],e)
+ end
+ end
+ if wrapup then
+ wrapup()
+ end
+ end
+ if cpath then
+ r = r + 1 result[r] = f_clip_stop(cpath[1])
+ end
+ end
+
+ function handlers.line(c,top)
+ local a = setmetatableindex(c.at,top)
+ local x1 = asnumber_vx(a.x1)
+ local y1 = asnumber_vy(a.y1)
+ local x2 = asnumber_vx(a.x2)
+ local y2 = asnumber_vy(a.y2)
+ if trace then
+ report("line: x1 %.3N, y1 %.3N, x2 %.3N, x3 %.3N",x1,y1,x2,y2)
+ end
+ flush(a,f_line(x1,y1,x2,y2),true)
+ end
+
+ function handlers.rect(c,top)
+ local a = setmetatableindex(c.at,top)
+ local w = asnumber_x(a.width)
+ local h = asnumber_y(a.height)
+ local x = asnumber_vx(a.x)
+ local y = asnumber_vy(a.y) - h
+ local rx = a.rx
+ local ry = a.ry
+ if rx == 0 then rx = false end -- maybe no default 0
+ if ry == 0 then ry = false end -- maybe no default 0
+ if rx or ry then
+ rx = asnumber_x(rx or ry)
+ ry = asnumber_y(ry or rx)
+ if trace then
+ report("rect: x %.3N, y %.3N, w %.3N, h %.3N, rx %.3N, ry %.3N",x,y,w,h,rx,ry)
+ end
+ flush(a,f_rounded(w,h,rx,ry,x,y))
+ else
+ if trace then
+ report("rect: x %.3N, y %.3N, w %.3N, h %.3N",x,y,w,h)
+ end
+ flush(a,f_rectangle(w,h,x,y))
+ end
+ end
+
+ function handlers.ellipse(c,top)
+ local a = setmetatableindex(c.at,top)
+ local x = asnumber_vx(a.cx)
+ local y = asnumber_vy(a.cy)
+ local rx = asnumber_x (a.rx)
+ local ry = asnumber_y (a.ry)
+ if trace then
+ report("ellipse: x %.3N, y %.3N, rx %.3N, ry %.3N",x,y,rx,ry)
+ end
+ flush(a,f_ellipse(2*rx,2*ry,x,y))
+ end
+
+ function handlers.circle(c,top)
+ local a = setmetatableindex(c.at,top)
+ local x = asnumber_vx(a.cx)
+ local y = asnumber_vy(a.cy)
+ local r = asnumber_r (a.r)
+ if trace then
+ report("circle: x %.3N, y %.3N, r %.3N",x,y,r)
+ end
+ flush(a,f_circle(2*r,x,y))
+ end
+
+ function handlers.path(c,top)
+ local a = setmetatableindex(c.at,top)
+ local d = a.d
+ if d then
+ local p = grabpath(d)
+ if trace then
+ report("path: %i entries, %sclosed",p.entries,p.closed and "" or "not ")
+ end
+ flushpath(a,p)
+ end
+ end
+
+ do
+
+ local p_pair = p_separator^0 * p_number_vx * p_separator^0 * p_number_vy
+ local p_pair_z = p_pair / f_lineto_z
+ local p_pair_n = p_pair / f_lineto_n
+ local p_open = Cc("(")
+ local p_close = Carg(1) * P(true) / function(s) return s end
+ local p_polyline = Cs(p_open * p_pair_z * p_pair_n^0 * p_close)
+
+ function handlers.polyline(c,top)
+ local a = setmetatableindex(c.at,top)
+ local p = a.points
+ if p then
+ flush(a,lpegmatch(p_polyline,p,1,")"),true)
+ end
+ end
+
+ function handlers.polygon(c,top)
+ local a = setmetatableindex(c.at,top)
+ local p = a.points
+ if p then
+ flush(a,lpegmatch(p_polyline,p,1,"--cycle)"))
+ end
+ end
+
+ end
+
+ function handlers.g(c,top)
+ local a = c.at
+ setmetatableindex(a,top)
+ process(c,"/*",a)
+ end
+
+ function handlers.symbol(c,top)
+ report("todo: %s","symbol")
+ end
+
+ -- We only need to filter classes .. I will complete this when I really need
+ -- it. Not hard to do but kind of boring.
+
+ local p_class = P(".") * C((R("az","AZ","09","__","--")^1))
+ local p_spec = P("{") * C((1-P("}"))^1) * P("}")
+
+ local p_grab = ((Carg(1) * p_class * p_space^0 * p_spec / function(t,k,v) t[k] = v end) + p_space^1 + P(1))^1
+
+ function handlers.style(c,top)
+ local s = xmltext(c)
+ lpegmatch(p_grab,s,1,styles)
+ for k, v in next, styles do
+ -- todo: lpeg
+ local t = { }
+ for k, v in gmatch(v,"%s*([^:]+):%s*([^;]+);") do
+ if k == "font" then
+ v = xml.css.fontspecification(v)
+ end
+ t[k] = v
+ end
+ styles[k] = t
+ end
+ bodyfont = tex.getdimen("bodyfontsize") / 65536
+ end
+
+ -- this will never really work out
+
+ function handlers.text(c,top)
+ local a = c.at
+ local x = asnumber_vx(a.x)
+ local y = asnumber_vy(a.y)
+ local text = xmltext(c)
+ local size = false
+ local fill = a.fill
+ -- escape text or keep it at the tex end
+ local class = a.class
+ if class then
+ local style = styles[class]
+ if style and next(style) then
+ local font = style.font
+ local s_family = font and font.family
+ local s_style = font and font.style
+ local s_weight = font and font.weight
+ local s_size = font and font.size
+ if s_size then
+ local f = factors[s_size[2]]
+ if f then
+ size = f * s_size[1]
+ end
+ end
+ -- todo: family: serif | sans-serif | monospace : use mapping
+ if s_family then
+ if s_family == "serif" then
+ text = "\\rm " .. text
+ elseif s_family == "sans-serif" then
+ text = "\\ss " .. text
+ else
+ text = "\\tt " .. text
+ end
+ end
+ if s_style or s_weight then
+ text = formatters['\\style[%s%s]{%s}'](s_weight or "",s_style or "",text)
+ end
+ fill = style.fill or fill
+ end
+ end
+ if size then
+ -- bodyfontsize
+ size = size/bodyfont
+ text = f_text_scaled(text,size,x,y)
+ else
+ text = f_text_normal(text,x,y)
+ end
+ if fill and fill ~= "none" then
+ text = f_draw("",text,"",fillproperties(fill,a))
+ else
+ text = f_draw("",text,"","","","","")
+ end
+ if trace then
+ report("text: x %.3N, y %.3N, text %s",x,y,text)
+ end
+ r = r + 1 ; result[r] = text
+ end
+
+ function handlers.svg(c,top,x,y,w,h,noclip)
+ local a = setmetatableindex(c.at,top)
+ local v = a.viewBox
+ local t = a.transform
+ local wrapupoffset
+ local wrapupviewport
+ -- local ex, em
+ local xpct, ypct, rpct
+ if trace then
+ report("view: %s, xpct %.3N, ypct %.3N","before",percentage_x,percentage_y)
+ end
+ if v then
+ x, y, w, h = viewbox(v)
+ if trace then
+ report("viewbox: x %.3N, y %.3N, width %.3N, height %.3N",x,y,w,h)
+ end
+ end
+ if h then
+ --
+ -- em = factors["em"]
+ -- ex = factors["ex"]
+ -- factors["em"] = em
+ -- factors["ex"] = ex
+ --
+ xpct = percentage_x
+ ypct = percentage_y
+ rpct = percentage_r
+ percentage_x = w / 100
+ percentage_y = h / 100
+ percentage_r = (sqrt(w^2 + h^2) / sqrt(2)) / 100
+ if trace then
+ report("view: %s, xpct %.3N, ypct %.3N","inside",percentage_x,percentage_y)
+ end
+ wrapupviewport = viewport(x,y,w,h,noclip)
+ end
+ -- todo: combine transform and offset here
+ if t then
+ btransform, etransform = transform(t,true)
+ end
+ if btransform then
+ r = r + 1 ; result[r] = btransform
+ end
+ wrapupoffset = offset(a)
+ process(c,"/*",top or defaults)
+ if wrapupoffset then
+ wrapupoffset()
+ end
+ if etransform then
+ r = r + 1 ; result[r] = etransform
+ end
+ if h then
+ --
+ -- factors["em"] = em
+ -- factors["ex"] = ex
+ --
+ percentage_x = xpct
+ percentage_y = ypct
+ percentage_r = rpct
+ if wrapupviewport then
+ wrapupviewport()
+ end
+ end
+ if trace then
+ report("view: %s, xpct %.3N, ypct %.3N","after",percentage_x,percentage_y)
+ end
+ end
+
+ process = function(x,p,top)
+ for c in xmlcollected(x,p) do
+ local h = handlers[c.tg]
+ if h then
+ h(c,top)
+ end
+ end
+ end
+
+ function metapost.svgtomp(specification,pattern)
+ local mps = ""
+ local svg = specification.data
+ if type(svg) == "string" then
+ svg = xmlconvert(svg)
+ end
+ if svg then
+ local c = xmlfirst(svg,pattern or "/svg")
+ if c then
+ root, result, r, definitions, styles, bodyfont = svg, { }, 0, { }, { }, 12
+ -- print("default",x,y,w,h)
+ handlers.svg (
+ c,
+ nil,
+ specification.x,
+ specification.y,
+ specification.w,
+ specification.h,
+ specification.noclip
+ )
+ mps = concat(result," ")
+ root, result, r, definitions, styles, bodyfont = false, false, false, false, false, false
+ else
+ report("missing svg root element")
+ end
+ else
+ report("bad svg blob")
+ end
+ return mps
+ end
+
+end
+
+function metapost.showsvgpage(data)
+ local dd = data.data
+ if type(dd) == "table" then
+ local comment = dd.comment
+ local offset = dd.pageoffset
+ for i=1,#dd do
+ local d = setmetatableindex( {
+ data = dd[i],
+ comment = comment and i or false,
+ pageoffset = offset or nil,
+ }, data)
+ metapost.showsvgpage(d)
+ end
+ else
+ context.startMPpage { instance = "doublefun", offset = data.pageoffset or nil }
+ context(metapost.svgtomp(data))
+ local comment = data.comment
+ if comment then
+ context("draw boundingbox currentpicture withcolor .6red ;")
+ context('draw textext.bot("\\strut\\tttf %s") ysized (10pt) shifted center bottomboundary currentpicture ;',comment)
+ end
+ context.stopMPpage()
+ end
+end
+
+function metapost.svgtopdf(data,...)
+ local mps = metapost.svgtomp(data,...)
+ if mps then
+ local pdf = metapost.simple("metafun",mps,true) -- todo: special instance, only basics needed
+ if pdf then
+ return pdf
+ else
+ -- message
+ end
+ else
+ -- message
+ end
+end
+
+do
+
+ local runner = sandbox.registerrunner {
+ name = "otfsvg2pdf",
+ program = "context",
+ template = "--batchmode --purgeall --runs=2 %filename%",
+ reporter = report_svg,
+ }
+
+ -- By using an independent pdf file instead of pdf streams we can use resources and still
+ -- cache. This is the old method updated. Maybe a future version will just do this runtime
+ -- but for now this is the most efficient method.
+
+ function metapost.svgshapestopdf(svgshapes,pdftarget,report_svg)
+ local texname = "temp-otf-svg-to-pdf.tex"
+ local pdfname = "temp-otf-svg-to-pdf.pdf"
+ local tucname = "temp-otf-svg-to-pdf.tuc"
+ local nofshapes = #svgshapes
+ local pdfpages = { filename = pdftarget }
+ local pdfpage = 0
+ local t = { }
+ local n = 0
+ --
+ os.remove(texname)
+ os.remove(pdfname)
+ os.remove(tucname)
+ --
+ if report_svg then
+ report_svg("processing %i svg containers",nofshapes)
+ statistics.starttiming(pdfpages)
+ end
+ --
+ -- can be option:
+ --
+ n = n + 1 ; t[n] = "\\enabledirectives[pdf.stripzeros,metapost.stripzeros]"
+ --
+ -- can be option:
+ --
+ -- n = n + 1 ; t[n] = "\\nopdfcompression"
+ --
+ n = n + 1 ; t[n] = "\\starttext"
+ n = n + 1 ; t[n] = "\\setupMPpage[alternative=offset,instance=doublefun]"
+ --
+ for i=1,nofshapes do
+ local entry = svgshapes[i]
+ local data = entry.data
+ local specification = {
+ data = xmlconvert(data),
+ x = 0,
+ y = 1000,
+ width = 1000,
+ height = 1000,
+ noclip = true,
+ }
+ for index=entry.first,entry.last do
+ if not pdfpages[index] then
+ pdfpage = pdfpage + 1
+ pdfpages[index] = pdfpage
+ local pattern = "/svg[@id='glyph" .. index .. "']"
+ n = n + 1 ; t[n] = "\\startMPpage"
+ n = n + 1 ; t[n] = metapost.svgtomp(specification,pattern) or ""
+ n = n + 1 ; t[n] = "\\stopMPpage"
+ end
+ end
+ end
+ n = n + 1 ; t[n] = "\\stoptext"
+ io.savedata(texname,concat(t,"\n"))
+ runner { filename = texname }
+ os.remove(pdftarget)
+ file.copy(pdfname,pdftarget)
+ if report_svg then
+ statistics.stoptiming(pdfpages)
+ report_svg("svg conversion time %s",statistics.elapsedseconds(pdfpages))
+ end
+ os.remove(texname)
+ os.remove(pdfname)
+ os.remove(tucname)
+ return pdfpages
+ end
+
+ function metapost.svgshapestomp(svgshapes,report_svg)
+ local nofshapes = #svgshapes
+ local mpshapes = { }
+ if report_svg then
+ report_svg("processing %i svg containers",nofshapes)
+ statistics.starttiming(mpshapes)
+ end
+ for i=1,nofshapes do
+ local entry = svgshapes[i]
+ local data = entry.data
+ local specification = {
+ data = xmlconvert(data),
+ x = 0,
+ y = 1000,
+ width = 1000,
+ height = 1000,
+ noclip = true,
+ }
+ for index=entry.first,entry.last do
+ if not mpshapes[index] then
+ local pattern = "/svg[@id='glyph" .. index .. "']"
+ mpshapes[index] = metapost.svgtomp(specification,pattern) or ""
+ end
+ end
+ end
+ if report_svg then
+ statistics.stoptiming(mpshapes)
+ report_svg("svg conversion time %s",statistics.elapsedseconds(mpshapes))
+ end
+ return mpshapes
+ end
+
+end
diff --git a/tex/context/base/mkiv/mult-def.lua b/tex/context/base/mkiv/mult-def.lua
index cb3b4f4e5..3c6ccceaf 100644
--- a/tex/context/base/mkiv/mult-def.lua
+++ b/tex/context/base/mkiv/mult-def.lua
@@ -14336,6 +14336,10 @@ return {
["pe"]="درون‌متن",
["ro"]="intext",
},
+ ["headintext"]={
+ ["en"]="headintext",
+ ["nl"]="kopintekst",
+ },
["intro"]={
["cs"]="uvod",
["de"]="intro",
diff --git a/tex/context/base/mkiv/mult-fun.lua b/tex/context/base/mkiv/mult-fun.lua
index d18d1bc0e..99700f474 100644
--- a/tex/context/base/mkiv/mult-fun.lua
+++ b/tex/context/base/mkiv/mult-fun.lua
@@ -30,6 +30,7 @@ return {
"getparametercount",
"getmaxparametercount",
"getparameterpath",
+ "getparameterpen",
"getparametertext",
-- "getparameteroption",
"applyparameters",
diff --git a/tex/context/base/mkiv/mult-mps.lua b/tex/context/base/mkiv/mult-mps.lua
index 35035308e..625e8ca65 100644
--- a/tex/context/base/mkiv/mult-mps.lua
+++ b/tex/context/base/mkiv/mult-mps.lua
@@ -117,7 +117,7 @@ return {
--
"mm", "pt", "dd", "bp", "cm", "pc", "cc", "in",
--
- "triplet", "quadruplet", "totransform",
+ "triplet", "quadruplet", "totransform", "bymatrix",
--
},
internals = { -- we need to remove duplicates above
diff --git a/tex/context/base/mkiv/status-files.pdf b/tex/context/base/mkiv/status-files.pdf
index 75ea1077e..410f87641 100644
--- a/tex/context/base/mkiv/status-files.pdf
+++ b/tex/context/base/mkiv/status-files.pdf
Binary files differ
diff --git a/tex/context/base/mkiv/status-lua.pdf b/tex/context/base/mkiv/status-lua.pdf
index 726657bc3..8008a61ee 100644
--- a/tex/context/base/mkiv/status-lua.pdf
+++ b/tex/context/base/mkiv/status-lua.pdf
Binary files differ
diff --git a/tex/context/base/mkiv/strc-itm.mklx b/tex/context/base/mkiv/strc-itm.mklx
index eeeb5ce00..e5458b5dd 100644
--- a/tex/context/base/mkiv/strc-itm.mklx
+++ b/tex/context/base/mkiv/strc-itm.mklx
@@ -198,6 +198,7 @@
\newconditional\c_strc_itemgroups_txt
\newconditional\c_strc_itemgroups_extra
\newconditional\c_strc_itemgroups_repeat
+\newconditional\c_strc_itemgroups_inline_head
% 0 = before/after
% 1 = between unless before
@@ -440,6 +441,7 @@
\setitemgroupparameter\c!width{0em}%
\fi} % signal
\setvalue{\??itemgroupkeyword\v!intext }{\settrue\c_strc_itemgroups_inline}
+\setvalue{\??itemgroupkeyword\v!headintext }{\settrue\c_strc_itemgroups_inline_head}
\setvalue{\??itemgroupkeyword\v!loose }{\setfalse\c_strc_itemgroups_optimize}
\setvalue{\??itemgroupkeyword\v!fit }{\settrue\c_strc_itemgroups_fitting}
\setvalue{\??itemgroupkeyword\v!nofit }{\setfalse\c_strc_itemgroups_fitting}
@@ -1211,19 +1213,25 @@
\unexpanded\def\strc_itemgroups_stop_head_indeed
{\removeunwantedspaces
\dostoptagged
- \ifconditional\c_strc_itemgroups_text
- \space
- \ignorespaces
+ \ifconditional\c_strc_itemgroups_inline_head
+ \space
+ \ignorespaces
\else
- \par
- \fi
- \strc_itemgroups_insert_breakno
- \ifconditional\c_strc_itemgroups_pack\else\strc_itemgroups_after_head_command\fi
- \strc_itemgroups_insert_breakno}
+ \ifconditional\c_strc_itemgroups_text
+ \space
+ \ignorespaces
+ \else
+ \par
+ \fi
+ \strc_itemgroups_insert_breakno
+ \ifconditional\c_strc_itemgroups_pack\else\strc_itemgroups_after_head_command\fi
+ \strc_itemgroups_insert_breakno
+ \fi}
\unexpanded\def\strc_itemgroups_head_body_indeed
{\dostarttagged\t!itembody\empty
- \noindentation}
+ \noindentation
+ \ignorespaces}
% Simple commands.
diff --git a/tex/context/base/mkiv/strc-itm.mkvi b/tex/context/base/mkiv/strc-itm.mkvi
index bc140d65b..f09b24cdf 100644
--- a/tex/context/base/mkiv/strc-itm.mkvi
+++ b/tex/context/base/mkiv/strc-itm.mkvi
@@ -198,6 +198,7 @@
\newconditional\c_strc_itemgroups_txt
\newconditional\c_strc_itemgroups_extra
\newconditional\c_strc_itemgroups_repeat
+\newconditional\c_strc_itemgroups_inline_head
% 0 = before/after
% 1 = between unless before
@@ -442,6 +443,7 @@
\setitemgroupparameter\c!width{0em}%
\fi} % signal
\setvalue{\??itemgroupkeyword\v!intext }{\settrue\c_strc_itemgroups_inline}
+\setvalue{\??itemgroupkeyword\v!headintext }{\settrue\c_strc_itemgroups_inline_head}
\setvalue{\??itemgroupkeyword\v!loose }{\setfalse\c_strc_itemgroups_optimize}
\setvalue{\??itemgroupkeyword\v!fit }{\settrue\c_strc_itemgroups_fitting}
\setvalue{\??itemgroupkeyword\v!nofit }{\setfalse\c_strc_itemgroups_fitting}
@@ -1232,19 +1234,25 @@
\unexpanded\def\strc_itemgroups_stop_head_indeed
{\removeunwantedspaces
\dostoptagged
- \ifconditional\c_strc_itemgroups_text
- \space
- \ignorespaces
+ \ifconditional\c_strc_itemgroups_inline_head
+ \space
+ \ignorespaces
\else
- \par
- \fi
- \strc_itemgroups_insert_breakno
- \ifconditional\c_strc_itemgroups_pack\else\strc_itemgroups_after_head_command\fi
- \strc_itemgroups_insert_breakno}
+ \ifconditional\c_strc_itemgroups_text
+ \space
+ \ignorespaces
+ \else
+ \par
+ \fi
+ \strc_itemgroups_insert_breakno
+ \ifconditional\c_strc_itemgroups_pack\else\strc_itemgroups_after_head_command\fi
+ \strc_itemgroups_insert_breakno
+ \fi}
\unexpanded\def\strc_itemgroups_head_body_indeed
{\dostarttagged\t!itembody\empty
- \noindentation}
+ \noindentation
+ \ignorespaces}
% Simple commands.
diff --git a/tex/context/base/mkiv/strc-ref.lua b/tex/context/base/mkiv/strc-ref.lua
index ec5ed5e03..a4f31ec0c 100644
--- a/tex/context/base/mkiv/strc-ref.lua
+++ b/tex/context/base/mkiv/strc-ref.lua
@@ -2613,14 +2613,14 @@ implement {
-- actions = { referencerealpage, context },
-- -- arguments = "string" -- hm, weird
-- }
---
--- implement {
--- name = "referencerealpage",
--- actions = function()
--- local actions = references.currentset
--- context(not actions and 0 or actions.realpage or setreferencerealpage(actions))
--- end
--- }
+
+implement {
+ name = "referencerealpage",
+ actions = function()
+ local actions = references.currentset
+ context(not actions and 0 or actions.realpage or setreferencerealpage(actions))
+ end
+}
local function referencepos(key)
local actions = references.currentset
diff --git a/tex/context/base/mkiv/syst-aux.mkiv b/tex/context/base/mkiv/syst-aux.mkiv
index 1cade869c..255f0746c 100644
--- a/tex/context/base/mkiv/syst-aux.mkiv
+++ b/tex/context/base/mkiv/syst-aux.mkiv
@@ -5250,7 +5250,7 @@
\unexpanded\def\pickupgroupedcommand#1#2#3%
{\def\m_syst_helpers_handle_group_b{#1}%
\def\m_syst_helpers_handle_group_a{#2}%
- \def\m_syst_helpers_handle_group_p{#2}%
+ \def\m_syst_helpers_handle_group_p{#3}%
\doifelsenextbgroupcs\syst_helpers_handle_group_pickup\syst_helpers_handle_group_nop}
\unexpanded\def\triggergroupedcommand#1%
diff --git a/tex/context/base/mkiv/syst-aux.mkxl b/tex/context/base/mkiv/syst-aux.mkxl
index b2fc08487..c14e03008 100644
--- a/tex/context/base/mkiv/syst-aux.mkxl
+++ b/tex/context/base/mkiv/syst-aux.mkxl
@@ -4007,7 +4007,7 @@
\unexpanded\def\pickupgroupedcommand#1#2#3%
{\def\m_syst_helpers_handle_group_b{#1}%
\def\m_syst_helpers_handle_group_a{#2}%
- \def\m_syst_helpers_handle_group_p{#2}%
+ \def\m_syst_helpers_handle_group_p{#3}%
\futureexpandis\bgroup\syst_helpers_handle_group_pickup\syst_helpers_handle_group_nop}
\unexpanded\def\triggergroupedcommand#1%
diff --git a/tex/context/base/mkiv/trac-deb.lua b/tex/context/base/mkiv/trac-deb.lua
index 5101119d7..72294e587 100644
--- a/tex/context/base/mkiv/trac-deb.lua
+++ b/tex/context/base/mkiv/trac-deb.lua
@@ -242,7 +242,10 @@ function tracers.printerror(specification)
local offset = specification.offset
local report = errorreporter(luaerrorline)
if not filename then
- report("error not related to input file: %s ...",lasttexerror)
+ report("error not related to input file:")
+ report(" tex: %s",lasttexerror or "-")
+ report(" lua: %s",lastluaerror or "-")
+ report(" mps: %s",lastmpserror or "-")
elseif type(filename) == "number" then
report("error on line %s of filehandle %s: %s ...",linenumber,lasttexerror)
else
diff --git a/tex/context/base/mkiv/util-lua.lua b/tex/context/base/mkiv/util-lua.lua
index f7e6e4f31..fb4088e7b 100644
--- a/tex/context/base/mkiv/util-lua.lua
+++ b/tex/context/base/mkiv/util-lua.lua
@@ -28,6 +28,13 @@ luautilities.nofstrippedbytes = 0
local strippedchunks = { } -- allocate()
luautilities.strippedchunks = strippedchunks
+if not LUATEXENGINE then
+ --- probably mtxrun ...
+ LUATEXENGINE = status.luatex_engine and string.lower(status.luatex_engine)
+ JITSUPPORTED = LUATEXENGINE == "luajittex" or jit
+ CONTEXTLMTXMODE = CONTEXTLMTXMODE or (LUATEXENGINE == "luametatex" and 1) or 0
+end
+
luautilities.suffixes = {
tma = "tma",
tmc = (CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 and "tmd") or (jit and "tmb") or "tmc",
@@ -37,7 +44,8 @@ luautilities.suffixes = {
luv = "luv",
luj = "luj",
tua = "tua",
- tuc = "tuc",
+ -- tuc = "tuc",
+ tuc = (CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 and "tud") or (jit and "tub") or "tuc",
}
-- environment.loadpreprocessedfile can be set to a preprocessor
diff --git a/tex/context/base/mkiv/util-sac.lua b/tex/context/base/mkiv/util-sac.lua
index 976989a77..19dc0eb0f 100644
--- a/tex/context/base/mkiv/util-sac.lua
+++ b/tex/context/base/mkiv/util-sac.lua
@@ -43,6 +43,8 @@ function streams.size(f)
return f and f[3] or 0
end
+streams.getsize = streams.size
+
function streams.setposition(f,i)
if f[4] then
-- zerobased
@@ -485,3 +487,58 @@ else
end
end
+
+-- For practical reasons we put this here. It's less efficient but ok when we don't
+-- have much access.
+
+do
+
+ local files = utilities.files
+
+ if files then
+
+ local openfile = files.open
+ local openstream = streams.open
+ local openstring = streams.openstring
+
+ local setmetatable = setmetatable
+
+ function io.newreader(str,method)
+ local f, m
+ if method == "string" then
+ f = openstring(str)
+ m = streams
+ elseif method == "stream" then
+ f = openstream(str)
+ m = streams
+ else
+ f = openfile(str,"rb")
+ m = files
+ end
+ if f then
+ local t = { }
+ setmetatable(t, {
+ __index = function(t,k)
+ local r = m[k]
+ if k == "close" then
+ if f then
+ m.close(f)
+ f = nil
+ end
+ return function() end
+ elseif r then
+ local v = function(_,a,b) return r(f,a,b) end
+ t[k] = v
+ return v
+ else
+ print("unknown key",k)
+ end
+ end
+ } )
+ return t
+ end
+ end
+
+ end
+
+end