From f1129626606384a7a55a21a83531f51f8b5dee25 Mon Sep 17 00:00:00 2001 From: Hans Hagen Date: Tue, 14 Jul 2020 00:25:53 +0200 Subject: 2020-07-13 23:52:00 --- tex/context/base/mkii/cont-new.mkii | 2 +- tex/context/base/mkii/context.mkii | 2 +- tex/context/base/mkiv/cont-new.mkiv | 2 +- tex/context/base/mkiv/context.mkiv | 2 +- tex/context/base/mkiv/context.mkxl | 2 +- tex/context/base/mkiv/luat-cod.lua | 6 +- tex/context/base/mkiv/mlib-cnt.lmt | 2022 ++++++++++++ tex/context/base/mkiv/mlib-cnt.lua | 2022 ------------ tex/context/base/mkiv/mlib-ctx.lua | 27 + tex/context/base/mkiv/mlib-ctx.mkiv | 10 +- tex/context/base/mkiv/mlib-ctx.mkxl | 21 +- tex/context/base/mkiv/mlib-int.lmt | 130 + tex/context/base/mkiv/mlib-int.lua | 354 +-- tex/context/base/mkiv/mlib-lmp.lmt | 70 + tex/context/base/mkiv/mlib-lmp.lua | 138 +- tex/context/base/mkiv/mlib-lmt.lmt | 153 + tex/context/base/mkiv/mlib-lmt.lua | 153 - tex/context/base/mkiv/mlib-lua.lmt | 120 + tex/context/base/mkiv/mlib-lua.lua | 1412 +-------- tex/context/base/mkiv/mlib-mat.lmt | 139 + tex/context/base/mkiv/mlib-mat.lua | 139 - tex/context/base/mkiv/mlib-mpf.lua | 1245 ++++++++ tex/context/base/mkiv/mlib-pps.lua | 4 +- tex/context/base/mkiv/mlib-ran.lmt | 237 ++ tex/context/base/mkiv/mlib-ran.lua | 237 -- tex/context/base/mkiv/mlib-run.lua | 273 +- tex/context/base/mkiv/mlib-scn.lmt | 795 +++++ tex/context/base/mkiv/mlib-scn.lua | 795 ----- tex/context/base/mkiv/mlib-svg.lmt | 3277 ++++++++++++++++++++ tex/context/base/mkiv/mlib-svg.lua | 3277 -------------------- tex/context/base/mkiv/status-files.pdf | Bin 27742 -> 27734 bytes tex/context/base/mkiv/status-lua.pdf | Bin 255197 -> 255207 bytes tex/context/base/mkiv/toks-ini.lua | 2 +- tex/context/interface/mkiv/context-en.xml | 124 +- tex/context/interface/mkiv/i-attachment.xml | 22 - tex/context/interface/mkiv/i-bar.xml | 100 - tex/context/interface/mkiv/i-buffer.xml | 6 - tex/context/interface/mkiv/i-columns.xml | 8 - tex/context/interface/mkiv/i-comment.xml | 23 - tex/context/interface/mkiv/i-common-instance.xml | 4 +- tex/context/interface/mkiv/i-context.pdf | Bin 954235 -> 959252 bytes tex/context/interface/mkiv/i-delimitedtext.xml | 80 - tex/context/interface/mkiv/i-fittingpage.xml | 20 - tex/context/interface/mkiv/i-floats.xml | 196 -- tex/context/interface/mkiv/i-formula.xml | 28 - tex/context/interface/mkiv/i-fraction.xml | 67 - tex/context/interface/mkiv/i-framed.xml | 71 - tex/context/interface/mkiv/i-indent.xml | 2 +- tex/context/interface/mkiv/i-labeltext.xml | 385 --- tex/context/interface/mkiv/i-lines.xml | 12 - tex/context/interface/mkiv/i-list.xml | 28 - tex/context/interface/mkiv/i-makeup.xml | 36 - tex/context/interface/mkiv/i-margindata.xml | 265 -- tex/context/interface/mkiv/i-mathalignment.xml | 20 - tex/context/interface/mkiv/i-mathcases.xml | 20 - tex/context/interface/mkiv/i-mathmatrix.xml | 20 - tex/context/interface/mkiv/i-mathstackers.xml | 838 ----- tex/context/interface/mkiv/i-mixedcolumns.xml | 12 - tex/context/interface/mkiv/i-note.xml | 81 - tex/context/interface/mkiv/i-pairedbox.xml | 30 - tex/context/interface/mkiv/i-readme.pdf | Bin 24964 -> 24972 bytes tex/context/interface/mkiv/i-script.xml | 18 - tex/context/interface/mkiv/i-sectionblock.xml | 48 - tex/context/interface/mkiv/i-shift.xml | 16 - tex/context/interface/mkiv/i-sort.xml | 27 - tex/context/interface/mkiv/i-synonym.xml | 28 - tex/context/interface/mkiv/i-tabulation.xml | 18 - tex/context/interface/mkiv/i-tooltip.xml | 26 - tex/context/interface/mkiv/i-unit.xml | 10 - tex/context/interface/mkiv/i-verbatim.xml | 103 - tex/generic/context/luatex/luatex-fonts-merged.lua | 2 +- 71 files changed, 8695 insertions(+), 11167 deletions(-) create mode 100644 tex/context/base/mkiv/mlib-cnt.lmt delete mode 100644 tex/context/base/mkiv/mlib-cnt.lua create mode 100644 tex/context/base/mkiv/mlib-int.lmt create mode 100644 tex/context/base/mkiv/mlib-lmp.lmt create mode 100644 tex/context/base/mkiv/mlib-lmt.lmt delete mode 100644 tex/context/base/mkiv/mlib-lmt.lua create mode 100644 tex/context/base/mkiv/mlib-lua.lmt create mode 100644 tex/context/base/mkiv/mlib-mat.lmt delete mode 100644 tex/context/base/mkiv/mlib-mat.lua create mode 100644 tex/context/base/mkiv/mlib-mpf.lua create mode 100644 tex/context/base/mkiv/mlib-ran.lmt delete mode 100644 tex/context/base/mkiv/mlib-ran.lua create mode 100644 tex/context/base/mkiv/mlib-scn.lmt delete mode 100644 tex/context/base/mkiv/mlib-scn.lua create mode 100644 tex/context/base/mkiv/mlib-svg.lmt delete mode 100644 tex/context/base/mkiv/mlib-svg.lua (limited to 'tex') diff --git a/tex/context/base/mkii/cont-new.mkii b/tex/context/base/mkii/cont-new.mkii index 6a5882366..bc18e3c22 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{2020.07.13 16:23} +\newcontextversion{2020.07.13 23:44} %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 b087acf4f..f45afe0a8 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{2020.07.13 16:23} +\edef\contextversion{2020.07.13 23:44} %D For those who want to use this: diff --git a/tex/context/base/mkiv/cont-new.mkiv b/tex/context/base/mkiv/cont-new.mkiv index f41893ad6..dad7578d4 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{2020.07.13 16:23} +\newcontextversion{2020.07.13 23:44} %D This file is loaded at runtime, thereby providing an excellent place for hacks, %D patches, extensions and new features. There can be local overloads in cont-loc diff --git a/tex/context/base/mkiv/context.mkiv b/tex/context/base/mkiv/context.mkiv index 4ce7764ff..3ef78b28c 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{2020.07.13 16:23} +\edef\contextversion{2020.07.13 23:44} %D Kind of special: diff --git a/tex/context/base/mkiv/context.mkxl b/tex/context/base/mkiv/context.mkxl index 9f3235584..614c9a659 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{2020.07.13 16:23} +\edef\contextversion{2020.07.13 23:44} %D Kind of special: diff --git a/tex/context/base/mkiv/luat-cod.lua b/tex/context/base/mkiv/luat-cod.lua index 8d5d65a45..18126b186 100644 --- a/tex/context/base/mkiv/luat-cod.lua +++ b/tex/context/base/mkiv/luat-cod.lua @@ -59,9 +59,6 @@ local strip = false if arg then for i=-1,#arg do if arg[i] == "--c:strip" then s function lua.registercode(filename,options) local barename = gsub(filename,"%.[%a%d]+$","") - if barename == filename then - filename = filename .. ".lua" - end local basename = match(barename,"^.+[/\\](.-)$") or barename if not bytedone[basename] then local opts = { } @@ -70,6 +67,9 @@ function lua.registercode(filename,options) opts[s] = true end end + if barename == filename then + filename = filename .. (opts.autosuffix and CONTEXTLMTXMODE > 0 and ".lmt" or ".lua") + end local code = environment.luafilechunk(filename,false,opts.optimize) if code then bytedone[basename] = true diff --git a/tex/context/base/mkiv/mlib-cnt.lmt b/tex/context/base/mkiv/mlib-cnt.lmt new file mode 100644 index 000000000..667384ed7 --- /dev/null +++ b/tex/context/base/mkiv/mlib-cnt.lmt @@ -0,0 +1,2022 @@ +if not modules then modules = { } end modules ['mlib-cnt'] = { + version = 1.001, + optimize = true, + 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 bor = bit32.bor -- it's really time to ditch support for luajit + +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 mpcolor = mp.color +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) + end + n = dnan + elseif n == inf then + er = er + 1 + if er < 10 then + report("inf at (%s,%s)",x,y) + 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 + if attributes.colors.model == "cmyk" then + for i=1,#colors do + local c = colors[i] + colors[i] = { 1 - c[1], 1 - c[2], 1 - c[3], 0 } + end + 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)/100 end + if ystep == 0 then ystep = (ymax - ymin)/100 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] = bor(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 (depth == 4 and "cmyk") 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 or depth == 4 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) + local k = depth == 4 and round((c[4] or 0) * 255) or nil + 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, + k + } + 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 then + mpcolor(color) + 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 + +-- Because we share some code surface plots also end up here. When working on the +-- contour macros by concidence I ran into a 3D plot in +-- +-- https://staff.science.uva.nl/a.j.p.heck/Courses/mptut.pdf +-- +-- The code is pure MetaPost and works quite well. With a bit of optimization +-- performance is also ok, but in the end a Lua solution is twice as fast and also +-- permits some more tweaking at no cost. So, below is an adaptation of an example +-- in the mentioned link. It's one of these cases where access to pseudo arrays +-- is slowing down MP. + +local sqrt, sin, cos = math.sqrt, math.sin, math.cos + +local f_fill_rgb = formatters["F (%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--C withcolor (%.3N,%.3N,%.3N) ;"] +local f_draw_rgb = formatters["D (%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--C withcolor %.3F ;"] +local f_mesh_rgb = formatters["U (%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--C withcolor (%.3N,%.3N,%.3N) ;"] +local f_fill_cmy = formatters["F (%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--C withcolor (%.3N,%.3N,%.3N,0) ;"] +local f_draw_cmy = formatters["D (%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--C withcolor %.3F ;"] +local f_mesh_cmy = formatters["U (%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--C withcolor (%.3N,%.3N,%.3N,0) ;"] + +local f_function_n = formatters [ [[ + local math = math + local round = math.round + %s + return function(x,y) + return %s + end +]] ] + +local f_function_y = formatters [ [[ + local math = math + local round = math.round + local nan = NaN + local inf = math.huge + local er = 0 + %s + return function(x,y,dnan,dinf,report) + local n = %s + if n == nan then + er = er + 1 + if er < 10 then + report("nan at (%s,%s)",x,y) + end + n = dnan + elseif n == inf then + er = er + 1 + if er < 10 then + report("inf at (%s,%s)",x,y) + end + n = dinf + end + dx[my] = n + sy = sy + 1 + end + return n, er +end +]] ] + +local f_color = formatters [ [[ + local math = math + return function(f) + return %s + end +]] ] + +function mp.lmt_surface_do(specification) + -- + -- The projection and color brightness calculation have been inlined. We also store + -- differently. + -- + -- todo: ignore weird paths + -- + -- The prototype is now converted to use lmt parameter sets. + -- + local p = getparameterset("surface") + -- + local preamble = p.preamble or "" + local code = p.code or "return x + y" + local colorcode = p.color or "return f, f, f" + local linecolor = p.linecolor or 1 + local xmin = p.xmin or -1 + local xmax = p.xmax or 1 + local ymin = p.ymin or -1 + local ymax = p.ymax or 1 + local xstep = p.xstep or .1 + local ystep = p.ystep or .1 + local bf = p.brightness or 100 + local clip = p.clip or false + local lines = p.lines + local ha = p.snap or 0.01 + local hb = 2 * ha + -- + if lines == nil then lines = true end + -- + if xstep == 0 then xstep = (xmax - xmin)/100 end + if ystep == 0 then ystep = (ymax - ymin)/100 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 xvector = p.xvector or { -0.7, -0.7 } + local yvector = p.yvector or { 1, 0 } + local zvector = p.zvector or { 0, 1 } + local light = p.light or { 3, 3, 10 } + -- + local xrx, xry = xvector[1], xvector[2] + local yrx, yry = yvector[1], yvector[2] + local zrx, zry = zvector[1], zvector[2] + local xp, yp, zp = light[1], light[2], light[3] + -- + local data = setmetatableindex("table") + local dx = (xmax - xmin) / nx + local dy = (ymax - ymin) / ny + local xt = xmin + -- + local minf, maxf + -- + -- similar as contours but no data loop here + -- + local fcode = load((p.check and f_function_y or f_function_n)(preamble,code)) + local func = type(fcode) == "function" and fcode() + if type(func) ~= "function" then + return false -- fatal error + end + -- + local ccode = load(f_color(colorcode)) + local color = type(ccode) == "function" and ccode() + if type(color) ~= "function" then + return false -- fatal error + end + -- + for i=0,nx do + local yt = ymin + for j=0,ny do + local zt = func(xt,yt) + -- projection from 3D to 2D coordinates + local x = xt * xrx + yt * yrx + zt * zrx + local y = xt * xry + yt * yry + zt * zry + local z = zt + -- numerical derivatives by central differences + local dfx = (func(xt+ha,yt) - func(xt-ha,yt)) / hb + local dfy = (func(xt,yt+ha) - func(xt,yt-ha)) / hb + -- compute brightness factor at a point + local ztp = zt - zp + local ytp = yt - yp + local xtp = xt - xp + local ztp = zt - zp + local ytp = yt - yp + local xtp = xt - xp + local ca = -ztp + dfy*ytp + dfx*xtp + local cb = sqrt(1+dfx*dfx+dfy*dfy) + local cc = sqrt(ztp*ztp + ytp*ytp + xtp*xtp) + local fac = bf*ca/(cb*cc*cc*cc) + -- addition: check range + if not minf then + minf = fac + maxf = fac + elseif fac < minf then + minf = fac + elseif fac > maxf then + maxf = fac + end + -- + data[i][j] = { x, y, fac } + -- + yt = yt + dy + end + xt = xt + dx + end + local result = { } + local r = 0 + local range = maxf - minf + local cl = linecolor or 1 + local enforce = attributes.colors.model == "cmyk" + for i=0,nx-1 do + for j=0,ny-1 do + -- points + local z1 = data[i] [j] + local z2 = data[i] [j+1] + local z3 = data[i+1][j+1] + local z4 = data[i+1][j] + -- color + local cf = z1[3] + if clip then + -- best clip here if needed + if cf < 0 then + cf = 0 + elseif cf > 1 then + cf = 1 + end + else + -- or remap when we want to + cf = (z1[3] - minf) / range + end + local z11 = z1[1] + local z12 = z1[2] + local z21 = z2[1] + local z22 = z2[2] + local z31 = z3[1] + local z32 = z3[2] + local z41 = z4[1] + local z42 = z4[2] + -- if lines then + -- -- fill first and draw then, previous shapes can be covered + -- else + -- -- fill and draw in one go to prevent artifacts + -- end + local cr, cg, cb = color(cf) + if not cr then cr = 0 end + if not cg then cg = 0 end + if not cb then cb = 0 end + if enforce then + cr, cg, cb = 1 - cr, 1 - cg, 1 - cb + r = r + 1 + if lines then + result[r] = f_fill_cmy(z11,z12,z21,z22,z31,z32,z41,z42,cr,cg,cb) + r = r + 1 + result[r] = f_draw_cmy(z11,z12,z21,z22,z31,z32,z41,z42,cl) + else + result[r] = f_mesh_cmy(z11,z12,z21,z22,z31,z32,z41,z42,cr,cg,cb) + end + else + r = r + 1 + if lines then + result[r] = f_fill_rgb(z11,z12,z21,z22,z31,z32,z41,z42,cr,cg,cb) + r = r + 1 + result[r] = f_draw_rgb(z11,z12,z21,z22,z31,z32,z41,z42,cl) + else + result[r] = f_mesh_rgb(z11,z12,z21,z22,z31,z32,z41,z42,cr,cg,cb) + end + end + end + end + mp.direct(concat(result)) +end diff --git a/tex/context/base/mkiv/mlib-cnt.lua b/tex/context/base/mkiv/mlib-cnt.lua deleted file mode 100644 index 667384ed7..000000000 --- a/tex/context/base/mkiv/mlib-cnt.lua +++ /dev/null @@ -1,2022 +0,0 @@ -if not modules then modules = { } end modules ['mlib-cnt'] = { - version = 1.001, - optimize = true, - 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 bor = bit32.bor -- it's really time to ditch support for luajit - -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 mpcolor = mp.color -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) - end - n = dnan - elseif n == inf then - er = er + 1 - if er < 10 then - report("inf at (%s,%s)",x,y) - 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 - if attributes.colors.model == "cmyk" then - for i=1,#colors do - local c = colors[i] - colors[i] = { 1 - c[1], 1 - c[2], 1 - c[3], 0 } - end - 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)/100 end - if ystep == 0 then ystep = (ymax - ymin)/100 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] = bor(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 (depth == 4 and "cmyk") 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 or depth == 4 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) - local k = depth == 4 and round((c[4] or 0) * 255) or nil - 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, - k - } - 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 then - mpcolor(color) - 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 - --- Because we share some code surface plots also end up here. When working on the --- contour macros by concidence I ran into a 3D plot in --- --- https://staff.science.uva.nl/a.j.p.heck/Courses/mptut.pdf --- --- The code is pure MetaPost and works quite well. With a bit of optimization --- performance is also ok, but in the end a Lua solution is twice as fast and also --- permits some more tweaking at no cost. So, below is an adaptation of an example --- in the mentioned link. It's one of these cases where access to pseudo arrays --- is slowing down MP. - -local sqrt, sin, cos = math.sqrt, math.sin, math.cos - -local f_fill_rgb = formatters["F (%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--C withcolor (%.3N,%.3N,%.3N) ;"] -local f_draw_rgb = formatters["D (%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--C withcolor %.3F ;"] -local f_mesh_rgb = formatters["U (%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--C withcolor (%.3N,%.3N,%.3N) ;"] -local f_fill_cmy = formatters["F (%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--C withcolor (%.3N,%.3N,%.3N,0) ;"] -local f_draw_cmy = formatters["D (%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--C withcolor %.3F ;"] -local f_mesh_cmy = formatters["U (%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--(%.6N,%.6N)--C withcolor (%.3N,%.3N,%.3N,0) ;"] - -local f_function_n = formatters [ [[ - local math = math - local round = math.round - %s - return function(x,y) - return %s - end -]] ] - -local f_function_y = formatters [ [[ - local math = math - local round = math.round - local nan = NaN - local inf = math.huge - local er = 0 - %s - return function(x,y,dnan,dinf,report) - local n = %s - if n == nan then - er = er + 1 - if er < 10 then - report("nan at (%s,%s)",x,y) - end - n = dnan - elseif n == inf then - er = er + 1 - if er < 10 then - report("inf at (%s,%s)",x,y) - end - n = dinf - end - dx[my] = n - sy = sy + 1 - end - return n, er -end -]] ] - -local f_color = formatters [ [[ - local math = math - return function(f) - return %s - end -]] ] - -function mp.lmt_surface_do(specification) - -- - -- The projection and color brightness calculation have been inlined. We also store - -- differently. - -- - -- todo: ignore weird paths - -- - -- The prototype is now converted to use lmt parameter sets. - -- - local p = getparameterset("surface") - -- - local preamble = p.preamble or "" - local code = p.code or "return x + y" - local colorcode = p.color or "return f, f, f" - local linecolor = p.linecolor or 1 - local xmin = p.xmin or -1 - local xmax = p.xmax or 1 - local ymin = p.ymin or -1 - local ymax = p.ymax or 1 - local xstep = p.xstep or .1 - local ystep = p.ystep or .1 - local bf = p.brightness or 100 - local clip = p.clip or false - local lines = p.lines - local ha = p.snap or 0.01 - local hb = 2 * ha - -- - if lines == nil then lines = true end - -- - if xstep == 0 then xstep = (xmax - xmin)/100 end - if ystep == 0 then ystep = (ymax - ymin)/100 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 xvector = p.xvector or { -0.7, -0.7 } - local yvector = p.yvector or { 1, 0 } - local zvector = p.zvector or { 0, 1 } - local light = p.light or { 3, 3, 10 } - -- - local xrx, xry = xvector[1], xvector[2] - local yrx, yry = yvector[1], yvector[2] - local zrx, zry = zvector[1], zvector[2] - local xp, yp, zp = light[1], light[2], light[3] - -- - local data = setmetatableindex("table") - local dx = (xmax - xmin) / nx - local dy = (ymax - ymin) / ny - local xt = xmin - -- - local minf, maxf - -- - -- similar as contours but no data loop here - -- - local fcode = load((p.check and f_function_y or f_function_n)(preamble,code)) - local func = type(fcode) == "function" and fcode() - if type(func) ~= "function" then - return false -- fatal error - end - -- - local ccode = load(f_color(colorcode)) - local color = type(ccode) == "function" and ccode() - if type(color) ~= "function" then - return false -- fatal error - end - -- - for i=0,nx do - local yt = ymin - for j=0,ny do - local zt = func(xt,yt) - -- projection from 3D to 2D coordinates - local x = xt * xrx + yt * yrx + zt * zrx - local y = xt * xry + yt * yry + zt * zry - local z = zt - -- numerical derivatives by central differences - local dfx = (func(xt+ha,yt) - func(xt-ha,yt)) / hb - local dfy = (func(xt,yt+ha) - func(xt,yt-ha)) / hb - -- compute brightness factor at a point - local ztp = zt - zp - local ytp = yt - yp - local xtp = xt - xp - local ztp = zt - zp - local ytp = yt - yp - local xtp = xt - xp - local ca = -ztp + dfy*ytp + dfx*xtp - local cb = sqrt(1+dfx*dfx+dfy*dfy) - local cc = sqrt(ztp*ztp + ytp*ytp + xtp*xtp) - local fac = bf*ca/(cb*cc*cc*cc) - -- addition: check range - if not minf then - minf = fac - maxf = fac - elseif fac < minf then - minf = fac - elseif fac > maxf then - maxf = fac - end - -- - data[i][j] = { x, y, fac } - -- - yt = yt + dy - end - xt = xt + dx - end - local result = { } - local r = 0 - local range = maxf - minf - local cl = linecolor or 1 - local enforce = attributes.colors.model == "cmyk" - for i=0,nx-1 do - for j=0,ny-1 do - -- points - local z1 = data[i] [j] - local z2 = data[i] [j+1] - local z3 = data[i+1][j+1] - local z4 = data[i+1][j] - -- color - local cf = z1[3] - if clip then - -- best clip here if needed - if cf < 0 then - cf = 0 - elseif cf > 1 then - cf = 1 - end - else - -- or remap when we want to - cf = (z1[3] - minf) / range - end - local z11 = z1[1] - local z12 = z1[2] - local z21 = z2[1] - local z22 = z2[2] - local z31 = z3[1] - local z32 = z3[2] - local z41 = z4[1] - local z42 = z4[2] - -- if lines then - -- -- fill first and draw then, previous shapes can be covered - -- else - -- -- fill and draw in one go to prevent artifacts - -- end - local cr, cg, cb = color(cf) - if not cr then cr = 0 end - if not cg then cg = 0 end - if not cb then cb = 0 end - if enforce then - cr, cg, cb = 1 - cr, 1 - cg, 1 - cb - r = r + 1 - if lines then - result[r] = f_fill_cmy(z11,z12,z21,z22,z31,z32,z41,z42,cr,cg,cb) - r = r + 1 - result[r] = f_draw_cmy(z11,z12,z21,z22,z31,z32,z41,z42,cl) - else - result[r] = f_mesh_cmy(z11,z12,z21,z22,z31,z32,z41,z42,cr,cg,cb) - end - else - r = r + 1 - if lines then - result[r] = f_fill_rgb(z11,z12,z21,z22,z31,z32,z41,z42,cr,cg,cb) - r = r + 1 - result[r] = f_draw_rgb(z11,z12,z21,z22,z31,z32,z41,z42,cl) - else - result[r] = f_mesh_rgb(z11,z12,z21,z22,z31,z32,z41,z42,cr,cg,cb) - end - end - end - end - mp.direct(concat(result)) -end diff --git a/tex/context/base/mkiv/mlib-ctx.lua b/tex/context/base/mkiv/mlib-ctx.lua index c913408cd..0240c6a80 100644 --- a/tex/context/base/mkiv/mlib-ctx.lua +++ b/tex/context/base/mkiv/mlib-ctx.lua @@ -400,3 +400,30 @@ implement { name = "mptexreset", actions = mptex.reset } + +-- moved from mlib-lua: + +mp = mp or { -- system namespace + set = { }, + get = { }, + aux = { }, + scan = { }, + inject = { }, +} + +MP = MP or { } -- user namespace + +-- We had this: +-- +-- table.setmetatablecall(mp,function(t,k) mpprint(k) end) +-- +-- but the next one is more interesting because we cannot use calls like: +-- +-- lua.mp.somedefdname("foo") +-- +-- which is due to expansion of somedefdname during suffix creation. So: +-- +-- lua.mp("somedefdname","foo") + +table.setmetatablecall(mp,function(t,k,...) return t[k](...) end) +table.setmetatablecall(MP,function(t,k,...) return t[k](...) end) diff --git a/tex/context/base/mkiv/mlib-ctx.mkiv b/tex/context/base/mkiv/mlib-ctx.mkiv index a1ed7efeb..61b02ad88 100644 --- a/tex/context/base/mkiv/mlib-ctx.mkiv +++ b/tex/context/base/mkiv/mlib-ctx.mkiv @@ -15,12 +15,10 @@ \registerctxluafile{mlib-run}{} \registerctxluafile{mlib-ctx}{} -\registerctxluafile{mlib-lua}{} -\registerctxluafile{mlib-lmp}{} -\registerctxluafile{mlib-int}{} - -\doifelsefileexists{mlib-cnt.lua}{\registerctxluafile{mlib-cnt}{optimize}}{} -\doifelsefileexists{mlib-svg.lua}{\registerctxluafile{mlib-svg}{optimize}}{} +\registerctxluafile{mlib-lua}{autosuffix} +\registerctxluafile{mlib-mpf}{} +\registerctxluafile{mlib-lmp}{autosuffix} +\registerctxluafile{mlib-int}{autosuffix} \unprotect diff --git a/tex/context/base/mkiv/mlib-ctx.mkxl b/tex/context/base/mkiv/mlib-ctx.mkxl index dd2d0ae24..8069f5932 100644 --- a/tex/context/base/mkiv/mlib-ctx.mkxl +++ b/tex/context/base/mkiv/mlib-ctx.mkxl @@ -15,18 +15,17 @@ \registerctxluafile{mlib-run}{} \registerctxluafile{mlib-ctx}{} -\registerctxluafile{mlib-lua}{} -\registerctxluafile{mlib-scn}{} -\registerctxluafile{mlib-mat}{} -\registerctxluafile{mlib-ran}{} -\registerctxluafile{mlib-lmp}{} -\registerctxluafile{mlib-int}{} -\registerctxluafile{mlib-lmt}{} - -\doifelsefileexists{mlib-cnt.lua}{\registerctxluafile{mlib-cnt}{}}{} -\doifelsefileexists{mlib-svg.lua}{\registerctxluafile{mlib-svg}{}}{} +\registerctxluafile{mlib-lua}{autosuffix} +\registerctxluafile{mlib-mpf}{} +\registerctxluafile{mlib-scn}{autosuffix} +\registerctxluafile{mlib-mat}{autosuffix} +\registerctxluafile{mlib-ran}{autosuffix} +\registerctxluafile{mlib-lmp}{autosuffix} +\registerctxluafile{mlib-int}{autosuffix} +\registerctxluafile{mlib-lmt}{autosuffix} +\registerctxluafile{mlib-cnt}{autosuffix,optimize} +\registerctxluafile{mlib-svg}{autosuffix,optimize} \unprotect \protect \endinput - diff --git a/tex/context/base/mkiv/mlib-int.lmt b/tex/context/base/mkiv/mlib-int.lmt new file mode 100644 index 000000000..4cdc3b076 --- /dev/null +++ b/tex/context/base/mkiv/mlib-int.lmt @@ -0,0 +1,130 @@ +if not modules then modules = { } end modules ['mlib-int'] = { + 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", +} + +local factor = number.dimenfactors.bp +local mpstring = mp.string +local getdimen = tex.getdimen +local getcount = tex.getcount +local getmacro = tokens.getters.macro +local get = tex.get +local emwidths = fonts.hashes.emwidths +local exheights = fonts.hashes.exheights + +local registerscript = metapost.registerscript + +local on_right_page = structures.pages.on_right +local is_odd_page = structures.pages.is_odd +local in_body_page = structures.pages.in_body +local page_fraction = structures.pages.fraction + +local function defaultcolormodel() -- can be helper + local colormethod = getcount("MPcolormethod") + return (colormethod == 0 or colormethod == 1) and 1 or 3 +end + +local t = os.date("*t") -- maybe this should be a very early on global + +registerscript("year", function() return t.year end) +registerscript("month", function() return t.month end) +registerscript("day", function() return t.day end) +registerscript("hour", function() return t.hour end) +registerscript("minute", function() return t.min end) +registerscript("second", function() return t.sec end) + +registerscript("PaperHeight", function() return getdimen("paperheight") * factor end) +registerscript("PaperWidth", function() return getdimen("paperwidth") * factor end) +registerscript("PrintPaperHeight", function() return getdimen("printpaperheight") * factor end) +registerscript("PrintPaperWidth", function() return getdimen("printpaperwidth") * factor end) +registerscript("TopSpace", function() return getdimen("topspace") * factor end) +registerscript("BottomSpace", function() return getdimen("bottomspace") * factor end) +registerscript("BackSpace", function() return getdimen("backspace") * factor end) +registerscript("CutSpace", function() return getdimen("cutspace") * factor end) +registerscript("MakeupHeight", function() return getdimen("makeupheight") * factor end) +registerscript("MakeupWidth", function() return getdimen("makeupwidth") * factor end) +registerscript("TopHeight", function() return getdimen("topheight") * factor end) +registerscript("TopDistance", function() return getdimen("topdistance") * factor end) +registerscript("HeaderHeight", function() return getdimen("headerheight") * factor end) +registerscript("HeaderDistance", function() return getdimen("headerdistance") * factor end) +registerscript("TextHeight", function() return getdimen("textheight") * factor end) +registerscript("FooterDistance", function() return getdimen("footerdistance") * factor end) +registerscript("FooterHeight", function() return getdimen("footerheight") * factor end) +registerscript("BottomDistance", function() return getdimen("bottomdistance") * factor end) +registerscript("BottomHeight", function() return getdimen("bottomheight") * factor end) +registerscript("LeftEdgeWidth", function() return getdimen("leftedgewidth") * factor end) +registerscript("LeftEdgeDistance", function() return getdimen("leftedgedistance") * factor end) +registerscript("LeftMarginWidth", function() return getdimen("leftmarginwidth") * factor end) +registerscript("LeftMarginDistance", function() return getdimen("leftmargindistance") * factor end) +registerscript("TextWidth", function() return getdimen("textwidth") * factor end) +registerscript("RightMarginDistance", function() return getdimen("rightmargindistance") * factor end) +registerscript("RightMarginWidth", function() return getdimen("rightmarginwidth") * factor end) +registerscript("RightEdgeDistance", function() return getdimen("rightedgedistance") * factor end) +registerscript("RightEdgeWidth", function() return getdimen("rightedgewidth") * factor end) +registerscript("InnerMarginDistance", function() return getdimen("innermargindistance") * factor end) +registerscript("InnerMarginWidth", function() return getdimen("innermarginwidth") * factor end) +registerscript("OuterMarginDistance", function() return getdimen("outermargindistance") * factor end) +registerscript("OuterMarginWidth", function() return getdimen("outermarginwidth") * factor end) +registerscript("InnerEdgeDistance", function() return getdimen("inneredgedistance") * factor end) +registerscript("InnerEdgeWidth", function() return getdimen("inneredgewidth") * factor end) +registerscript("OuterEdgeDistance", function() return getdimen("outeredgedistance") * factor end) +registerscript("OuterEdgeWidth", function() return getdimen("outeredgewidth") * factor end) +registerscript("PageOffset", function() return getdimen("pagebackgroundoffset") * factor end) +registerscript("PageDepth", function() return getdimen("pagebackgrounddepth") * factor end) +registerscript("LayoutColumns", function() return getcount("layoutcolumns") end) +registerscript("LayoutColumnDistance", function() return getdimen("layoutcolumndistance") * factor end) +registerscript("LayoutColumnWidth", function() return getdimen("layoutcolumnwidth") * factor end) +registerscript("SpineWidth", function() return getdimen("spinewidth") * factor end) +registerscript("PaperBleed", function() return getdimen("paperbleed") * factor end) + +registerscript("RealPageNumber", function() return getcount("realpageno") end) +registerscript("LastPageNumber", function() return getcount("lastpageno") end) + +registerscript("PageNumber", function() return getcount("pageno") end) +registerscript("NOfPages", function() return getcount("lastpageno") end) + +registerscript("SubPageNumber", function() return getcount("subpageno") end) +registerscript("NOfSubPages", function() return getcount("lastsubpageno") end) + +registerscript("CurrentColumn", function() return getcount("mofcolumns") end) +registerscript("NOfColumns", function() return getcount("nofcolumns") end) + +registerscript("BaseLineSkip", function() return get ("baselineskip",true) * factor end) +registerscript("LineHeight", function() return getdimen("lineheight") * factor end) +registerscript("BodyFontSize", function() return getdimen("bodyfontsize") * factor end) + +registerscript("TopSkip", function() return get ("topskip",true) * factor end) +registerscript("StrutHeight", function() return getdimen("strutht") * factor end) +registerscript("StrutDepth", function() return getdimen("strutdp") * factor end) + +registerscript("PageNumber", function() return getcount("pageno") end) +registerscript("RealPageNumber", function() return getcount("realpageno") end) +registerscript("NOfPages", function() return getcount("lastpageno") end) + +registerscript("CurrentWidth", function() return get ("hsize") * factor end) +registerscript("CurrentHeight", function() return get ("vsize") * factor end) + +registerscript("EmWidth", function() return emwidths [false] * factor end) +registerscript("ExHeight", function() return exheights[false] * factor end) + +registerscript("HSize", function() return get ("hsize") * factor end) +registerscript("VSize", function() return get ("vsize") * factor end) +registerscript("LastPageNumber", function() return getcount("lastpageno") end) + +registerscript("OverlayWidth", function() return getdimen("d_overlay_width") * factor end) +registerscript("OverlayHeight", function() return getdimen("d_overlay_height") * factor end) +registerscript("OverlayDepth", function() return getdimen("d_overlay_depth") * factor end) +registerscript("OverlayLineWidth", function() return getdimen("d_overlay_linewidth") * factor end) +registerscript("OverlayOffset", function() return getdimen("d_overlay_offset") * factor end) +registerscript("OverlayRegion", function() mpstring(getmacro("m_overlay_region")) end) +--------------("CurrentLayout", function() mpstring(getmacro("currentlayout")) end) + +registerscript("PageFraction", page_fraction) +registerscript("OnRightPage", on_right_page) +registerscript("OnOddPage", is_odd_page ) +registerscript("InPageBody", in_body_page ) + +registerscript("defaultcolormodel", defaultcolormodel) diff --git a/tex/context/base/mkiv/mlib-int.lua b/tex/context/base/mkiv/mlib-int.lua index b8d74d5b5..2f5f539e9 100644 --- a/tex/context/base/mkiv/mlib-int.lua +++ b/tex/context/base/mkiv/mlib-int.lua @@ -20,8 +20,6 @@ local mpcolor = attributes.colors.mpcolor local emwidths = fonts.hashes.emwidths local exheights = fonts.hashes.exheights -local mpgetdimen = mp.getdimen - local registerscript = metapost.registerscript local on_right_page = structures.pages.on_right @@ -34,235 +32,123 @@ local function defaultcolormodel() -- can be helper return (colormethod == 0 or colormethod == 1) and 1 or 3 end -if CONTEXTLMTXMODE > 0 then - - local t = os.date("*t") -- maybe this should be a very early on global - - local mpnumeric = mp.inject.numeric - - registerscript("year", function() return t.year end) - registerscript("month", function() return t.month end) - registerscript("day", function() return t.day end) - registerscript("hour", function() return t.hour end) - registerscript("minute", function() return t.min end) - registerscript("second", function() return t.sec end) - - registerscript("PaperHeight", function() return getdimen("paperheight") * factor end) - registerscript("PaperWidth", function() return getdimen("paperwidth") * factor end) - registerscript("PrintPaperHeight", function() return getdimen("printpaperheight") * factor end) - registerscript("PrintPaperWidth", function() return getdimen("printpaperwidth") * factor end) - registerscript("TopSpace", function() return getdimen("topspace") * factor end) - registerscript("BottomSpace", function() return getdimen("bottomspace") * factor end) - registerscript("BackSpace", function() return getdimen("backspace") * factor end) - registerscript("CutSpace", function() return getdimen("cutspace") * factor end) - registerscript("MakeupHeight", function() return getdimen("makeupheight") * factor end) - registerscript("MakeupWidth", function() return getdimen("makeupwidth") * factor end) - registerscript("TopHeight", function() return getdimen("topheight") * factor end) - registerscript("TopDistance", function() return getdimen("topdistance") * factor end) - registerscript("HeaderHeight", function() return getdimen("headerheight") * factor end) - registerscript("HeaderDistance", function() return getdimen("headerdistance") * factor end) - registerscript("TextHeight", function() return getdimen("textheight") * factor end) - registerscript("FooterDistance", function() return getdimen("footerdistance") * factor end) - registerscript("FooterHeight", function() return getdimen("footerheight") * factor end) - registerscript("BottomDistance", function() return getdimen("bottomdistance") * factor end) - registerscript("BottomHeight", function() return getdimen("bottomheight") * factor end) - registerscript("LeftEdgeWidth", function() return getdimen("leftedgewidth") * factor end) - registerscript("LeftEdgeDistance", function() return getdimen("leftedgedistance") * factor end) - registerscript("LeftMarginWidth", function() return getdimen("leftmarginwidth") * factor end) - registerscript("LeftMarginDistance", function() return getdimen("leftmargindistance") * factor end) - registerscript("TextWidth", function() return getdimen("textwidth") * factor end) - registerscript("RightMarginDistance", function() return getdimen("rightmargindistance") * factor end) - registerscript("RightMarginWidth", function() return getdimen("rightmarginwidth") * factor end) - registerscript("RightEdgeDistance", function() return getdimen("rightedgedistance") * factor end) - registerscript("RightEdgeWidth", function() return getdimen("rightedgewidth") * factor end) - registerscript("InnerMarginDistance", function() return getdimen("innermargindistance") * factor end) - registerscript("InnerMarginWidth", function() return getdimen("innermarginwidth") * factor end) - registerscript("OuterMarginDistance", function() return getdimen("outermargindistance") * factor end) - registerscript("OuterMarginWidth", function() return getdimen("outermarginwidth") * factor end) - registerscript("InnerEdgeDistance", function() return getdimen("inneredgedistance") * factor end) - registerscript("InnerEdgeWidth", function() return getdimen("inneredgewidth") * factor end) - registerscript("OuterEdgeDistance", function() return getdimen("outeredgedistance") * factor end) - registerscript("OuterEdgeWidth", function() return getdimen("outeredgewidth") * factor end) - registerscript("PageOffset", function() return getdimen("pagebackgroundoffset") * factor end) - registerscript("PageDepth", function() return getdimen("pagebackgrounddepth") * factor end) - registerscript("LayoutColumns", function() return getcount("layoutcolumns") end) - registerscript("LayoutColumnDistance", function() return getdimen("layoutcolumndistance") * factor end) - registerscript("LayoutColumnWidth", function() return getdimen("layoutcolumnwidth") * factor end) - registerscript("SpineWidth", function() return getdimen("spinewidth") * factor end) - registerscript("PaperBleed", function() return getdimen("paperbleed") * factor end) - - registerscript("RealPageNumber", function() return getcount("realpageno") end) - registerscript("LastPageNumber", function() return getcount("lastpageno") end) - - registerscript("PageNumber", function() return getcount("pageno") end) - registerscript("NOfPages", function() return getcount("lastpageno") end) - - registerscript("SubPageNumber", function() return getcount("subpageno") end) - registerscript("NOfSubPages", function() return getcount("lastsubpageno") end) - - registerscript("CurrentColumn", function() return getcount("mofcolumns") end) - registerscript("NOfColumns", function() return getcount("nofcolumns") end) - - registerscript("BaseLineSkip", function() return get ("baselineskip",true) * factor end) - registerscript("LineHeight", function() return getdimen("lineheight") * factor end) - registerscript("BodyFontSize", function() return getdimen("bodyfontsize") * factor end) - - registerscript("TopSkip", function() return get ("topskip",true) * factor end) - registerscript("StrutHeight", function() return getdimen("strutht") * factor end) - registerscript("StrutDepth", function() return getdimen("strutdp") * factor end) - - registerscript("PageNumber", function() return getcount("pageno") end) - registerscript("RealPageNumber", function() return getcount("realpageno") end) - registerscript("NOfPages", function() return getcount("lastpageno") end) - - registerscript("CurrentWidth", function() return get ("hsize") * factor end) - registerscript("CurrentHeight", function() return get ("vsize") * factor end) - - registerscript("EmWidth", function() return emwidths [false] * factor end) - registerscript("ExHeight", function() return exheights[false] * factor end) - - registerscript("HSize", function() return get ("hsize") * factor end) - registerscript("VSize", function() return get ("vsize") * factor end) - registerscript("LastPageNumber", function() return getcount("lastpageno") end) - - registerscript("OverlayWidth", function() return getdimen("d_overlay_width") * factor end) - registerscript("OverlayHeight", function() return getdimen("d_overlay_height") * factor end) - registerscript("OverlayDepth", function() return getdimen("d_overlay_depth") * factor end) - registerscript("OverlayLineWidth", function() return getdimen("d_overlay_linewidth") * factor end) - registerscript("OverlayOffset", function() return getdimen("d_overlay_offset") * factor end) - registerscript("OverlayRegion", function() mpstring(getmacro("m_overlay_region")) end) - -- ("CurrentLayout", function() mpstring(getmacro("currentlayout")) end) - - registerscript("PageFraction", page_fraction) - registerscript("OnRightPage", on_right_page) - registerscript("OnOddPage", is_odd_page ) - registerscript("InPageBody", in_body_page ) - - registerscript("defaultcolormodel", defaultcolormodel) - -else - - function mp.PaperHeight () mpnumeric(getdimen("paperheight") * factor) end - function mp.PaperWidth () mpnumeric(getdimen("paperwidth") * factor) end - function mp.PrintPaperHeight () mpnumeric(getdimen("printpaperheight") * factor) end - function mp.PrintPaperWidth () mpnumeric(getdimen("printpaperwidth") * factor) end - function mp.TopSpace () mpnumeric(getdimen("topspace") * factor) end - function mp.BottomSpace () mpnumeric(getdimen("bottomspace") * factor) end - function mp.BackSpace () mpnumeric(getdimen("backspace") * factor) end - function mp.CutSpace () mpnumeric(getdimen("cutspace") * factor) end - function mp.MakeupHeight () mpnumeric(getdimen("makeupheight") * factor) end - function mp.MakeupWidth () mpnumeric(getdimen("makeupwidth") * factor) end - function mp.TopHeight () mpnumeric(getdimen("topheight") * factor) end - function mp.TopDistance () mpnumeric(getdimen("topdistance") * factor) end - function mp.HeaderHeight () mpnumeric(getdimen("headerheight") * factor) end - function mp.HeaderDistance () mpnumeric(getdimen("headerdistance") * factor) end - function mp.TextHeight () mpnumeric(getdimen("textheight") * factor) end - function mp.FooterDistance () mpnumeric(getdimen("footerdistance") * factor) end - function mp.FooterHeight () mpnumeric(getdimen("footerheight") * factor) end - function mp.BottomDistance () mpnumeric(getdimen("bottomdistance") * factor) end - function mp.BottomHeight () mpnumeric(getdimen("bottomheight") * factor) end - function mp.LeftEdgeWidth () mpnumeric(getdimen("leftedgewidth") * factor) end - function mp.LeftEdgeDistance () mpnumeric(getdimen("leftedgedistance") * factor) end - function mp.LeftMarginWidth () mpnumeric(getdimen("leftmarginwidth") * factor) end - function mp.LeftMarginDistance () mpnumeric(getdimen("leftmargindistance") * factor) end - function mp.TextWidth () mpnumeric(getdimen("textwidth") * factor) end - function mp.RightMarginDistance () mpnumeric(getdimen("rightmargindistance") * factor) end - function mp.RightMarginWidth () mpnumeric(getdimen("rightmarginwidth") * factor) end - function mp.RightEdgeDistance () mpnumeric(getdimen("rightedgedistance") * factor) end - function mp.RightEdgeWidth () mpnumeric(getdimen("rightedgewidth") * factor) end - function mp.InnerMarginDistance () mpnumeric(getdimen("innermargindistance") * factor) end - function mp.InnerMarginWidth () mpnumeric(getdimen("innermarginwidth") * factor) end - function mp.OuterMarginDistance () mpnumeric(getdimen("outermargindistance") * factor) end - function mp.OuterMarginWidth () mpnumeric(getdimen("outermarginwidth") * factor) end - function mp.InnerEdgeDistance () mpnumeric(getdimen("inneredgedistance") * factor) end - function mp.InnerEdgeWidth () mpnumeric(getdimen("inneredgewidth") * factor) end - function mp.OuterEdgeDistance () mpnumeric(getdimen("outeredgedistance") * factor) end - function mp.OuterEdgeWidth () mpnumeric(getdimen("outeredgewidth") * factor) end - function mp.PageOffset () mpnumeric(getdimen("pagebackgroundoffset") * factor) end - function mp.PageDepth () mpnumeric(getdimen("pagebackgrounddepth") * factor) end - function mp.LayoutColumns () mpnumeric(getcount("layoutcolumns")) end - function mp.LayoutColumnDistance() mpnumeric(getdimen("layoutcolumndistance") * factor) end - function mp.LayoutColumnWidth () mpnumeric(getdimen("layoutcolumnwidth") * factor) end - function mp.SpineWidth () mpnumeric(getdimen("spinewidth") * factor) end - function mp.PaperBleed () mpnumeric(getdimen("paperbleed") * factor) end - - function mp.RealPageNumber () mpnumeric(getcount("realpageno") ) end - function mp.LastPageNumber () mpnumeric(getcount("lastpageno") ) end - - function mp.PageNumber () mpnumeric(getcount("pageno") ) end - function mp.NOfPages () mpnumeric(getcount("lastpageno") ) end - - function mp.SubPageNumber () mpnumeric(getcount("subpageno") ) end - function mp.NOfSubPages () mpnumeric(getcount("lastsubpageno") ) end - - function mp.CurrentColumn () mpnumeric(getcount("mofcolumns") ) end - function mp.NOfColumns () mpnumeric(getcount("nofcolumns") ) end - - function mp.BaseLineSkip () mpnumeric(get ("baselineskip",true) * factor) end - function mp.LineHeight () mpnumeric(getdimen("lineheight") * factor) end - function mp.BodyFontSize () mpnumeric(getdimen("bodyfontsize") * factor) end - - function mp.TopSkip () mpnumeric(get ("topskip",true) * factor) end - function mp.StrutHeight () mpnumeric(getdimen("strutht") * factor) end - function mp.StrutDepth () mpnumeric(getdimen("strutdp") * factor) end - - function mp.PageNumber () mpnumeric(getcount("pageno") ) end - function mp.RealPageNumber () mpnumeric(getcount("realpageno") ) end - function mp.NOfPages () mpnumeric(getcount("lastpageno") ) end - - function mp.CurrentWidth () mpnumeric(get ("hsize") * factor) end - function mp.CurrentHeight () mpnumeric(get ("vsize") * factor) end - - function mp.EmWidth () mpnumeric(emwidths [false] * factor) end - function mp.ExHeight () mpnumeric(exheights[false] * factor) end - - function mp.OverlayWidth () mpnumeric(getdimen("d_overlay_width") * factor) end - function mp.OverlayHeight () mpnumeric(getdimen("d_overlay_height") * factor) end - function mp.OverlayDepth () mpnumeric(getdimen("d_overlay_depth") * factor) end - function mp.OverlayLineWidth () mpnumeric(getdimen("d_overlay_linewidth") * factor) end - function mp.OverlayOffset () mpnumeric(getdimen("d_overlay_offset") * factor) end - function mp.OverlayRegion () mpstring (getmacro("m_overlay_region") ) end - - function mp.PageFraction () mpnumeric(page_fraction() ) end - function mp.OnRightPage () mpboolean(on_right_page() ) end - function mp.OnOddPage () mpboolean(is_odd_page () ) end - function mp.InPageBody () mpboolean(in_body_page () ) end - - function mp.OverlayWidth () mpnumeric(getdimen("d_overlay_width") * factor) end - function mp.OverlayHeight () mpnumeric(getdimen("d_overlay_height") * factor) end - function mp.OverlayDepth () mpnumeric(getdimen("d_overlay_depth") * factor) end - function mp.OverlayLineWidth () mpnumeric(getdimen("d_overlay_linewidth") * factor) end - function mp.OverlayOffset () mpnumeric(getdimen("d_overlay_offset") * factor) end - function mp.OverlayRegion () mpstring (getmacro("m_overlay_region") ) end - -- mp.CurrentLayout () mpstring (getmacro("currentlayout")) end - - function mp.defaultcolormodel () mpnumeric(defaultcolormodel()) end - - mp.HSize = mp.CurrentWidth - mp.VSize = mp.CurrentHeight - mp.LastPageNumber = mp.NOfPages - - -- not much difference (10000 calls in a graphic neither as expansion seems to win - -- over defining the macro etc) so let's not waste counters then - - -- function mp.OverlayColor() - -- local c = mpcolor( - -- getcount("c_overlay_colormodel"), - -- getcount("c_overlay_color"), - -- getcount("c_overlay_transparency") - -- ) - -- mpquoted(c) - -- end - -- - -- function mp.OverlayLineColor() - -- local c = mpcolor( - -- getcount("c_overlay_colormodel"), - -- getcount("c_overlay_linecolor"), - -- getcount("c_overlay_linetransparency") - -- ) - -- mpquoted(c) - -- end - -end - - +function mp.PaperHeight () mpnumeric(getdimen("paperheight") * factor) end +function mp.PaperWidth () mpnumeric(getdimen("paperwidth") * factor) end +function mp.PrintPaperHeight () mpnumeric(getdimen("printpaperheight") * factor) end +function mp.PrintPaperWidth () mpnumeric(getdimen("printpaperwidth") * factor) end +function mp.TopSpace () mpnumeric(getdimen("topspace") * factor) end +function mp.BottomSpace () mpnumeric(getdimen("bottomspace") * factor) end +function mp.BackSpace () mpnumeric(getdimen("backspace") * factor) end +function mp.CutSpace () mpnumeric(getdimen("cutspace") * factor) end +function mp.MakeupHeight () mpnumeric(getdimen("makeupheight") * factor) end +function mp.MakeupWidth () mpnumeric(getdimen("makeupwidth") * factor) end +function mp.TopHeight () mpnumeric(getdimen("topheight") * factor) end +function mp.TopDistance () mpnumeric(getdimen("topdistance") * factor) end +function mp.HeaderHeight () mpnumeric(getdimen("headerheight") * factor) end +function mp.HeaderDistance () mpnumeric(getdimen("headerdistance") * factor) end +function mp.TextHeight () mpnumeric(getdimen("textheight") * factor) end +function mp.FooterDistance () mpnumeric(getdimen("footerdistance") * factor) end +function mp.FooterHeight () mpnumeric(getdimen("footerheight") * factor) end +function mp.BottomDistance () mpnumeric(getdimen("bottomdistance") * factor) end +function mp.BottomHeight () mpnumeric(getdimen("bottomheight") * factor) end +function mp.LeftEdgeWidth () mpnumeric(getdimen("leftedgewidth") * factor) end +function mp.LeftEdgeDistance () mpnumeric(getdimen("leftedgedistance") * factor) end +function mp.LeftMarginWidth () mpnumeric(getdimen("leftmarginwidth") * factor) end +function mp.LeftMarginDistance () mpnumeric(getdimen("leftmargindistance") * factor) end +function mp.TextWidth () mpnumeric(getdimen("textwidth") * factor) end +function mp.RightMarginDistance () mpnumeric(getdimen("rightmargindistance") * factor) end +function mp.RightMarginWidth () mpnumeric(getdimen("rightmarginwidth") * factor) end +function mp.RightEdgeDistance () mpnumeric(getdimen("rightedgedistance") * factor) end +function mp.RightEdgeWidth () mpnumeric(getdimen("rightedgewidth") * factor) end +function mp.InnerMarginDistance () mpnumeric(getdimen("innermargindistance") * factor) end +function mp.InnerMarginWidth () mpnumeric(getdimen("innermarginwidth") * factor) end +function mp.OuterMarginDistance () mpnumeric(getdimen("outermargindistance") * factor) end +function mp.OuterMarginWidth () mpnumeric(getdimen("outermarginwidth") * factor) end +function mp.InnerEdgeDistance () mpnumeric(getdimen("inneredgedistance") * factor) end +function mp.InnerEdgeWidth () mpnumeric(getdimen("inneredgewidth") * factor) end +function mp.OuterEdgeDistance () mpnumeric(getdimen("outeredgedistance") * factor) end +function mp.OuterEdgeWidth () mpnumeric(getdimen("outeredgewidth") * factor) end +function mp.PageOffset () mpnumeric(getdimen("pagebackgroundoffset") * factor) end +function mp.PageDepth () mpnumeric(getdimen("pagebackgrounddepth") * factor) end +function mp.LayoutColumns () mpnumeric(getcount("layoutcolumns")) end +function mp.LayoutColumnDistance() mpnumeric(getdimen("layoutcolumndistance") * factor) end +function mp.LayoutColumnWidth () mpnumeric(getdimen("layoutcolumnwidth") * factor) end +function mp.SpineWidth () mpnumeric(getdimen("spinewidth") * factor) end +function mp.PaperBleed () mpnumeric(getdimen("paperbleed") * factor) end + +function mp.RealPageNumber () mpnumeric(getcount("realpageno") ) end +function mp.LastPageNumber () mpnumeric(getcount("lastpageno") ) end + +function mp.PageNumber () mpnumeric(getcount("pageno") ) end +function mp.NOfPages () mpnumeric(getcount("lastpageno") ) end + +function mp.SubPageNumber () mpnumeric(getcount("subpageno") ) end +function mp.NOfSubPages () mpnumeric(getcount("lastsubpageno") ) end + +function mp.CurrentColumn () mpnumeric(getcount("mofcolumns") ) end +function mp.NOfColumns () mpnumeric(getcount("nofcolumns") ) end + +function mp.BaseLineSkip () mpnumeric(get ("baselineskip",true) * factor) end +function mp.LineHeight () mpnumeric(getdimen("lineheight") * factor) end +function mp.BodyFontSize () mpnumeric(getdimen("bodyfontsize") * factor) end + +function mp.TopSkip () mpnumeric(get ("topskip",true) * factor) end +function mp.StrutHeight () mpnumeric(getdimen("strutht") * factor) end +function mp.StrutDepth () mpnumeric(getdimen("strutdp") * factor) end + +function mp.PageNumber () mpnumeric(getcount("pageno") ) end +function mp.RealPageNumber () mpnumeric(getcount("realpageno") ) end +function mp.NOfPages () mpnumeric(getcount("lastpageno") ) end + +function mp.CurrentWidth () mpnumeric(get ("hsize") * factor) end +function mp.CurrentHeight () mpnumeric(get ("vsize") * factor) end + +function mp.EmWidth () mpnumeric(emwidths [false] * factor) end +function mp.ExHeight () mpnumeric(exheights[false] * factor) end + +function mp.OverlayWidth () mpnumeric(getdimen("d_overlay_width") * factor) end +function mp.OverlayHeight () mpnumeric(getdimen("d_overlay_height") * factor) end +function mp.OverlayDepth () mpnumeric(getdimen("d_overlay_depth") * factor) end +function mp.OverlayLineWidth () mpnumeric(getdimen("d_overlay_linewidth") * factor) end +function mp.OverlayOffset () mpnumeric(getdimen("d_overlay_offset") * factor) end +function mp.OverlayRegion () mpstring (getmacro("m_overlay_region") ) end + +function mp.PageFraction () mpnumeric(page_fraction() ) end +function mp.OnRightPage () mpboolean(on_right_page() ) end +function mp.OnOddPage () mpboolean(is_odd_page () ) end +function mp.InPageBody () mpboolean(in_body_page () ) end + +function mp.OverlayWidth () mpnumeric(getdimen("d_overlay_width") * factor) end +function mp.OverlayHeight () mpnumeric(getdimen("d_overlay_height") * factor) end +function mp.OverlayDepth () mpnumeric(getdimen("d_overlay_depth") * factor) end +function mp.OverlayLineWidth () mpnumeric(getdimen("d_overlay_linewidth") * factor) end +function mp.OverlayOffset () mpnumeric(getdimen("d_overlay_offset") * factor) end +function mp.OverlayRegion () mpstring (getmacro("m_overlay_region") ) end +-- mp.CurrentLayout () mpstring (getmacro("currentlayout")) end + +function mp.defaultcolormodel () mpnumeric(defaultcolormodel()) end + +mp.HSize = mp.CurrentWidth +mp.VSize = mp.CurrentHeight +mp.LastPageNumber = mp.NOfPages + +-- not much difference (10000 calls in a graphic neither as expansion seems to win +-- over defining the macro etc) so let's not waste counters then + +-- function mp.OverlayColor() +-- local c = mpcolor( +-- getcount("c_overlay_colormodel"), +-- getcount("c_overlay_color"), +-- getcount("c_overlay_transparency") +-- ) +-- mpquoted(c) +-- end +-- +-- function mp.OverlayLineColor() +-- local c = mpcolor( +-- getcount("c_overlay_colormodel"), +-- getcount("c_overlay_linecolor"), +-- getcount("c_overlay_linetransparency") +-- ) +-- mpquoted(c) +-- end diff --git a/tex/context/base/mkiv/mlib-lmp.lmt b/tex/context/base/mkiv/mlib-lmp.lmt new file mode 100644 index 000000000..ff9682e87 --- /dev/null +++ b/tex/context/base/mkiv/mlib-lmp.lmt @@ -0,0 +1,70 @@ +if not modules then modules = { } end modules ['mlib-lmp'] = { + 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", +} + +-- path relates stuff ... todo: use a stack (or numeric index to list) + +local type = type + +local aux = mp.aux +local mpnumeric = aux.numeric +local mppair = aux.pair + +local p = nil +local n = 0 + +local function mf_path_reset() + p = nil + n = 0 +end + +local scan = mp.scan +local scannumber = scan.number +local scanpath = scan.path + +local function mf_path_length() + p = scanpath() + n = p and #p or 1 + mpnumeric(n) +end + +local function mf_path_point() + local i = scannumber() + if i > 0 and i <= n then + local pi = p[i] + mppair(pi[1],pi[2]) + end +end + +local function mf_path_left() + local i = scannumber() + if i > 0 and i <= n then + local pi = p[i] + mppair(pi[5],pi[6]) + end +end + +local function mf_path_right() + local i = scannumber() + if i > 0 and i <= n then + local pn + if i == 1 then + pn = p[2] or p[1] + else + pn = p[i+1] or p[1] + end + mppair(pn[3],pn[4]) + end +end + +local registerscript = metapost.registerscript + +registerscript("pathreset", mf_path_reset) +registerscript("pathlengthof", mf_path_length) +registerscript("pathpointof", mf_path_point) +registerscript("pathleftof", mf_path_left) +registerscript("pathrightof", mf_path_right) diff --git a/tex/context/base/mkiv/mlib-lmp.lua b/tex/context/base/mkiv/mlib-lmp.lua index 3186a3b25..79370d2ce 100644 --- a/tex/context/base/mkiv/mlib-lmp.lua +++ b/tex/context/base/mkiv/mlib-lmp.lua @@ -8,116 +8,58 @@ if not modules then modules = { } end modules ['mlib-lmp'] = { -- path relates stuff ... todo: use a stack (or numeric index to list) - local type = type local aux = mp.aux local mpnumeric = aux.numeric local mppair = aux.pair -local mpquoted = aux.quoted - -do - - local p = nil - local n = 0 - - local function mf_path_reset() - p = nil - n = 0 - end - - if CONTEXTLMTXMODE > 0 then - - local scan = mp.scan - local scannumber = scan.number - local scanpath = scan.path - - local function mf_path_length() - p = scanpath() - n = p and #p or 1 - mpnumeric(n) - end - - local function mf_path_point() - local i = scannumber() - if i > 0 and i <= n then - local pi = p[i] - mppair(pi[1],pi[2]) - end - end - - local function mf_path_left() - local i = scannumber() - if i > 0 and i <= n then - local pi = p[i] - mppair(pi[5],pi[6]) - end - end - - local function mf_path_right() - local i = scannumber() - if i > 0 and i <= n then - local pn - if i == 1 then - pn = p[2] or p[1] - else - pn = p[i+1] or p[1] - end - mppair(pn[3],pn[4]) - end - end - local registerscript = metapost.registerscript +local p = nil +local n = 0 - registerscript("pathreset", mf_path_reset) - registerscript("pathlengthof", mf_path_length) - registerscript("pathpointof", mf_path_point) - registerscript("pathleftof", mf_path_left) - registerscript("pathrightof", mf_path_right) - - else +local function mf_path_reset() + p = nil + n = 0 +end - local get = mp.get - local mpgetpath = get.path +local get = mp.get +local mpgetpath = get.path - local function mf_path_length(name) - p = mpgetpath(name) - n = p and #p or 0 - mpnumeric(n) - end +local function mf_path_length(name) + p = mpgetpath(name) + n = p and #p or 0 + mpnumeric(n) +end - local function mf_path_point(i) - if i > 0 and i <= n then - local pi = p[i] - mppair(pi[1],pi[2]) - end - end +local function mf_path_point(i) + if i > 0 and i <= n then + local pi = p[i] + mppair(pi[1],pi[2]) + end +end - local function mf_path_left(i) - if i > 0 and i <= n then - local pi = p[i] - mppair(pi[5],pi[6]) - end - end +local function mf_path_left(i) + if i > 0 and i <= n then + local pi = p[i] + mppair(pi[5],pi[6]) + end +end - local function mf_path_right(i) - if i > 0 and i <= n then - local pn - if i == 1 then - pn = p[2] or p[1] - else - pn = p[i+1] or p[1] - end - mppair(pn[3],pn[4]) - end +local function mf_path_right(i) + if i > 0 and i <= n then + local pn + if i == 1 then + pn = p[2] or p[1] + else + pn = p[i+1] or p[1] end - - mp.mf_path_length = mf_path_length mp.pathlength = mf_path_length - mp.mf_path_point = mf_path_point mp.pathpoint = mf_path_point - mp.mf_path_left = mf_path_left mp.pathleft = mf_path_left - mp.mf_path_right = mf_path_right mp.pathright = mf_path_right - mp.mf_path_reset = mf_path_reset mp.pathreset = mf_path_reset - + mppair(pn[3],pn[4]) end - end + +mp.mf_path_length = mf_path_length mp.pathlength = mf_path_length +mp.mf_path_point = mf_path_point mp.pathpoint = mf_path_point +mp.mf_path_left = mf_path_left mp.pathleft = mf_path_left +mp.mf_path_right = mf_path_right mp.pathright = mf_path_right +mp.mf_path_reset = mf_path_reset mp.pathreset = mf_path_reset + diff --git a/tex/context/base/mkiv/mlib-lmt.lmt b/tex/context/base/mkiv/mlib-lmt.lmt new file mode 100644 index 000000000..f0b59248a --- /dev/null +++ b/tex/context/base/mkiv/mlib-lmt.lmt @@ -0,0 +1,153 @@ +if not modules then modules = { } end modules ['mlib-lmt'] = { + 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", +} + +-- todo: check for possible inject usage + +local type = type + +local aux = mp.aux +local mpdirect = aux.direct +local mppath = mp.path + +local scan = mp.scan +local scannumeric = scan.numeric +local scanpath = scan.path + +local getparameter = metapost.getparameter + +function mp.lmt_function_x(xmin,xmax,xstep,code,shape) -- experimental + local code = "return function(x) return " .. code .. " end" + local action = load(code) + local points = { } + local nofpoints = 0 + if action then + action = action() + end + if shape == "steps" then + local halfx = xstep / 2 + local lastx = xmin + local lasty = action(xmin) + for xi = xmin, xmax, xstep do + local yi = action(xi) + local xx = lastx + halfx + nofpoints = nofpoints + 1 ; points[nofpoints] = { xx, lasty } + nofpoints = nofpoints + 1 ; points[nofpoints] = { xx, yi } + lastx = xi + lasty = yi + end + if points[nofpoints][1] ~= xmax then + local yi = action(xmax) + local xx = lastx + halfx + nofpoints = nofpoints + 1 ; points[nofpoints] = { xx, lasty } + nofpoints = nofpoints + 1 ; points[nofpoints] = { xx, yi } + lastx = xi + lasty = yi + end + else + for xi = xmin, xmax, xstep do + nofpoints = nofpoints + 1 ; points[nofpoints] = { xi, action(xi) } + end + if points[nofpoints][1] ~= xmax then + nofpoints = nofpoints + 1 ; points[nofpoints] = { xmax, action(xmax) } + end + end + mppath(points,shape == "curve" and ".." or "--",false) +end + +function mp.lmt_mesh_set() + local mesh = getparameter { "mesh", "paths" } + structures.references.currentset.mesh = mesh +end + +function mp.lmt_mesh_update() + local mesh = getparameter { "paths" } or getparameter { "mesh", "paths" } + mesh[scannumeric()] = scanpath(true) +end + +-- moved here + +function mp.lmt_svg_include() + local labelfile = metapost.getparameter { "labelfile" } + if labelfile and labelfile ~= "" then + local labels = table.load(labelfile) -- todo: same path as svg file + if type(labels) == "table" then + for i=1,#labels do + metapost.remaptext(labels[i]) + end + end + end + local fontname = metapost.getparameter { "fontname" } + if fontname and fontname ~= "" then + local unicode = metapost.getparameter { "unicode" } + if unicode then + mpdirect ( + metapost.svgglyphtomp(fontname,math.round(unicode)) + ) + end + return + end + local colorfile = metapost.getparameter { "colormap" } + local colormap = false + if colorfile and colorfile ~= "" then + colormap = metapost.svgcolorremapper(colorfile) + end + local filename = metapost.getparameter { "filename" } + if filename and filename ~= "" then + mpdirect ( metapost.svgtomp { + data = io.loaddata(filename), + remap = true, + colormap = colormap, + id = filename, + } ) + else + local buffer = metapost.getparameter { "buffer" } + if buffer then + mpdirect ( metapost.svgtomp { + data = buffers.getcontent(buffer), + -- remap = true, + colormap = colormap, + id = buffer or "buffer", + } ) + else + local code = metapost.getparameter { "code" } + if code then + mpdirect ( metapost.svgtomp { + data = code, + colormap = colormap, + id = "code", + } ) + end + end + end +end + + +function mp.lmt_do_remaptext() + local parameters = metapost.scanparameters() + if parameters and parameters.label then + metapost.remaptext(parameters) + end +end + +do + + local dropins = fonts.dropins + local registerglyphs = dropins.registerglyphs + local registerglyph = dropins.registerglyph + + function mp.lmt_register_glyph() + registerglyph(metapost.getparameterset("mpsglyph")) + end + + function mp.lmt_register_glyphs() + registerglyphs(metapost.getparameterset("mpsglyphs")) + end + +end + +todecimal = xdecimal and xdecimal.new or tonumber -- bonus diff --git a/tex/context/base/mkiv/mlib-lmt.lua b/tex/context/base/mkiv/mlib-lmt.lua deleted file mode 100644 index f0b59248a..000000000 --- a/tex/context/base/mkiv/mlib-lmt.lua +++ /dev/null @@ -1,153 +0,0 @@ -if not modules then modules = { } end modules ['mlib-lmt'] = { - 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", -} - --- todo: check for possible inject usage - -local type = type - -local aux = mp.aux -local mpdirect = aux.direct -local mppath = mp.path - -local scan = mp.scan -local scannumeric = scan.numeric -local scanpath = scan.path - -local getparameter = metapost.getparameter - -function mp.lmt_function_x(xmin,xmax,xstep,code,shape) -- experimental - local code = "return function(x) return " .. code .. " end" - local action = load(code) - local points = { } - local nofpoints = 0 - if action then - action = action() - end - if shape == "steps" then - local halfx = xstep / 2 - local lastx = xmin - local lasty = action(xmin) - for xi = xmin, xmax, xstep do - local yi = action(xi) - local xx = lastx + halfx - nofpoints = nofpoints + 1 ; points[nofpoints] = { xx, lasty } - nofpoints = nofpoints + 1 ; points[nofpoints] = { xx, yi } - lastx = xi - lasty = yi - end - if points[nofpoints][1] ~= xmax then - local yi = action(xmax) - local xx = lastx + halfx - nofpoints = nofpoints + 1 ; points[nofpoints] = { xx, lasty } - nofpoints = nofpoints + 1 ; points[nofpoints] = { xx, yi } - lastx = xi - lasty = yi - end - else - for xi = xmin, xmax, xstep do - nofpoints = nofpoints + 1 ; points[nofpoints] = { xi, action(xi) } - end - if points[nofpoints][1] ~= xmax then - nofpoints = nofpoints + 1 ; points[nofpoints] = { xmax, action(xmax) } - end - end - mppath(points,shape == "curve" and ".." or "--",false) -end - -function mp.lmt_mesh_set() - local mesh = getparameter { "mesh", "paths" } - structures.references.currentset.mesh = mesh -end - -function mp.lmt_mesh_update() - local mesh = getparameter { "paths" } or getparameter { "mesh", "paths" } - mesh[scannumeric()] = scanpath(true) -end - --- moved here - -function mp.lmt_svg_include() - local labelfile = metapost.getparameter { "labelfile" } - if labelfile and labelfile ~= "" then - local labels = table.load(labelfile) -- todo: same path as svg file - if type(labels) == "table" then - for i=1,#labels do - metapost.remaptext(labels[i]) - end - end - end - local fontname = metapost.getparameter { "fontname" } - if fontname and fontname ~= "" then - local unicode = metapost.getparameter { "unicode" } - if unicode then - mpdirect ( - metapost.svgglyphtomp(fontname,math.round(unicode)) - ) - end - return - end - local colorfile = metapost.getparameter { "colormap" } - local colormap = false - if colorfile and colorfile ~= "" then - colormap = metapost.svgcolorremapper(colorfile) - end - local filename = metapost.getparameter { "filename" } - if filename and filename ~= "" then - mpdirect ( metapost.svgtomp { - data = io.loaddata(filename), - remap = true, - colormap = colormap, - id = filename, - } ) - else - local buffer = metapost.getparameter { "buffer" } - if buffer then - mpdirect ( metapost.svgtomp { - data = buffers.getcontent(buffer), - -- remap = true, - colormap = colormap, - id = buffer or "buffer", - } ) - else - local code = metapost.getparameter { "code" } - if code then - mpdirect ( metapost.svgtomp { - data = code, - colormap = colormap, - id = "code", - } ) - end - end - end -end - - -function mp.lmt_do_remaptext() - local parameters = metapost.scanparameters() - if parameters and parameters.label then - metapost.remaptext(parameters) - end -end - -do - - local dropins = fonts.dropins - local registerglyphs = dropins.registerglyphs - local registerglyph = dropins.registerglyph - - function mp.lmt_register_glyph() - registerglyph(metapost.getparameterset("mpsglyph")) - end - - function mp.lmt_register_glyphs() - registerglyphs(metapost.getparameterset("mpsglyphs")) - end - -end - -todecimal = xdecimal and xdecimal.new or tonumber -- bonus diff --git a/tex/context/base/mkiv/mlib-lua.lmt b/tex/context/base/mkiv/mlib-lua.lmt new file mode 100644 index 000000000..ed65b9401 --- /dev/null +++ b/tex/context/base/mkiv/mlib-lua.lmt @@ -0,0 +1,120 @@ +if not modules then modules = { } end modules ['mlib-lua'] = { + 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", +} + +local type = type +local insert, remove = table.insert, table.remove + +local scan = mp.scan +local inject = mp.inject + +local currentmpx = nil +local stack = { } + +local scan_next = mplib.scan_next +local scan_expression = mplib.scan_expression +local scan_token = mplib.scan_token +local scan_symbol = mplib.scan_symbol +local scan_numeric = mplib.scan_numeric +local scan_integer = mplib.scan_integer +local scan_boolean = mplib.scan_boolean +local scan_string = mplib.scan_string +local scan_pair = mplib.scan_pair +local scan_color = mplib.scan_color +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 +scan.token = function(k) return scan_token (currentmpx,k) end +scan.symbol = function(k,e) return scan_symbol (currentmpx,k,e) end +scan.numeric = function() return scan_numeric (currentmpx) end +scan.integer = function() return scan_integer (currentmpx) end +scan.boolean = function() return scan_boolean (currentmpx) end +scan.string = function() return scan_string (currentmpx) end +scan.pair = function(t) return scan_pair (currentmpx,t) end +scan.color = function(t) return scan_color (currentmpx,t) end +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 + +local inject_path = mplib.inject_path +local inject_numeric = mplib.inject_numeric +local inject_pair = mplib.inject_pair +local inject_boolean = mplib.inject_boolean +local inject_integer = mplib.inject_integer +local inject_string = mplib.inject_string +local inject_color = mplib.inject_color +local inject_cmykcolor = mplib.inject_cmykcolor +local inject_transform = mplib.inject_transform +local inject_whatever = mplib.inject_whatever + +inject.path = function(t,cycle,curled) return inject_path (currentmpx,t,cycle,curled) end +inject.numeric = function(n) return inject_numeric (currentmpx,n) end +inject.pair = function(x,y) return inject_pair (currentmpx,x,y) end +inject.boolean = function(b) return inject_boolean (currentmpx,b) end +inject.integer = function(i) return inject_integer (currentmpx,i) end +inject.string = function(s) return inject_string (currentmpx,s) end +inject.color = function(r,g,b) return inject_color (currentmpx,r,g,b) end +inject.cmykcolor = function(c,m,y,k) return inject_cmykcolor(currentmpx,c,m,y,k) end +inject.transform = function(x,y,xx,xy,yx,yy) return inject_transform(currentmpx,x,y,xx,xy,yx,yy) end +inject.whatever = function(...) return inject_whatever (currentmpx,...) end + +-- bonus: + +scan .number = scan .numeric +inject.number = inject.numeric + +table.setmetatablecall(inject,function(t,...) + inject_whatever(currentmpx,...) +end) + +-- experiment + +function mp.autoinject(m) + local t = type(m) + if t == "table" then + local n = #t + if n == 2 then + inject_pair(currentmpx,m) + elseif n == 3 then + inject_color(currentmpx,m) + elseif n == 4 then + inject_cmykcolor(currentmpx,m) + elseif n == 6 then + inject_transform(currentmpx,m) + end + elseif t == "number" then + inject_numeric(currentmpx,m) + elseif t == "string" then + inject_string(currentmpx,m) + elseif t == "boolean" then + inject_boolean(currentmpx,m) + end +end + +function metapost.pushscriptrunner(mpx) + insert(stack,mpx) + currentmpx = mpx +end + +function metapost.popscriptrunner() + currentmpx = remove(stack,mpx) +end + +function metapost.currentmpx() + return currentmpx +end + +local status = mplib.status + +function metapost.currentmpxstatus() + return status and status(currentmpx) or 0 +end diff --git a/tex/context/base/mkiv/mlib-lua.lua b/tex/context/base/mkiv/mlib-lua.lua index 9b4ec44dd..b38dbdcf6 100644 --- a/tex/context/base/mkiv/mlib-lua.lua +++ b/tex/context/base/mkiv/mlib-lua.lua @@ -6,1399 +6,45 @@ if not modules then modules = { } end modules ['mlib-lua'] = { license = "see context related readme files", } --- This is very preliminary code! +local type = type +local insert, remove = table.insert, table.remove --- maybe we need mplib.model, but how with instances +local set = mp.set +local get = mp.get -local type, tostring, tonumber, select, loadstring = type, tostring, tonumber, select, loadstring -local find, match, gsub, gmatch = string.find, string.match, string.gsub, string.gmatch -local concat, insert, remove = table.concat, table.insert, table.remove +local currentmpx = nil +local stack = { } -local formatters = string.formatters -local lpegmatch = lpeg.match -local lpegpatterns = lpeg.patterns +local get_numeric = mplib.get_numeric +local get_integer = mplib.get_integer +local get_string = mplib.get_string +local get_boolean = mplib.get_boolean +local get_path = mplib.get_path +local set_path = mplib.set_path -local P, S, Ct, Cs, Cc, C = lpeg.P, lpeg.S, lpeg.Ct, lpeg.Cs, lpeg.Cc, lpeg.C - -local report_luarun = logs.reporter("metapost","lua") -local report_script = logs.reporter("metapost","script") -local report_message = logs.reporter("metapost") - -local trace_luarun = false trackers.register("metapost.lua",function(v) trace_luarun = v end) - -local be_tolerant = true directives.register("metapost.lua.tolerant", function(v) be_tolerant = v end) - -local get, set, aux, scan, inject = { }, { }, { }, { }, { } - -mp = mp or { -- system namespace - set = set, - get = get, - aux = aux, - scan = scan, - inject = inject, -} - -MP = MP or { -- user namespace -} - --- We had this: --- --- table.setmetatablecall(mp,function(t,k) mpprint(k) end) --- --- but the next one is more interesting because we cannot use calls like: --- --- lua.mp.somedefdname("foo") --- --- which is due to expansion of somedefdname during suffix creation. So: --- --- lua.mp("somedefdname","foo") - -table.setmetatablecall(mp,function(t,k,...) return t[k](...) end) -table.setmetatablecall(MP,function(t,k,...) return t[k](...) end) - -do - - local currentmpx = nil - local stack = { } - - if CONTEXTLMTXMODE > 0 then - - local scan_next = mplib.scan_next - local scan_expression = mplib.scan_expression - local scan_token = mplib.scan_token - local scan_symbol = mplib.scan_symbol - local scan_numeric = mplib.scan_numeric - local scan_integer = mplib.scan_integer - local scan_boolean = mplib.scan_boolean - local scan_string = mplib.scan_string - local scan_pair = mplib.scan_pair - local scan_color = mplib.scan_color - 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 - scan.token = function(k) return scan_token (currentmpx,k) end - scan.symbol = function(k,e) return scan_symbol (currentmpx,k,e) end - scan.numeric = function() return scan_numeric (currentmpx) end - scan.integer = function() return scan_integer (currentmpx) end - scan.boolean = function() return scan_boolean (currentmpx) end - scan.string = function() return scan_string (currentmpx) end - scan.pair = function(t) return scan_pair (currentmpx,t) end - scan.color = function(t) return scan_color (currentmpx,t) end - 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 - - local inject_path = mplib.inject_path - local inject_numeric = mplib.inject_numeric - local inject_pair = mplib.inject_pair - local inject_boolean = mplib.inject_boolean - local inject_integer = mplib.inject_integer - local inject_string = mplib.inject_string - local inject_color = mplib.inject_color - local inject_cmykcolor = mplib.inject_cmykcolor - local inject_transform = mplib.inject_transform - local inject_whatever = mplib.inject_whatever - - inject.path = function(t,cycle,curled) return inject_path (currentmpx,t,cycle,curled) end - inject.numeric = function(n) return inject_numeric (currentmpx,n) end - inject.pair = function(x,y) return inject_pair (currentmpx,x,y) end - inject.boolean = function(b) return inject_boolean (currentmpx,b) end - inject.integer = function(i) return inject_integer (currentmpx,i) end - inject.string = function(s) return inject_string (currentmpx,s) end - inject.color = function(r,g,b) return inject_color (currentmpx,r,g,b) end - inject.cmykcolor = function(c,m,y,k) return inject_cmykcolor(currentmpx,c,m,y,k) end - inject.transform = function(x,y,xx,xy,yx,yy) return inject_transform(currentmpx,x,y,xx,xy,yx,yy) end - inject.whatever = function(...) return inject_whatever (currentmpx,...) end - - -- bonus: - - scan .number = scan .numeric - inject.number = inject.numeric - - table.setmetatablecall(inject,function(t,...) - inject_whatever(currentmpx,...) - end) - - -- experiment - - function mp.autoinject(m) - local t = type(m) - if t == "table" then - local n = #t - if n == 2 then - inject_pair(currentmpx,m) - elseif n == 3 then - inject_color(currentmpx,m) - elseif n == 4 then - inject_cmykcolor(currentmpx,m) - elseif n == 6 then - inject_transform(currentmpx,m) - end - elseif t == "number" then - inject_numeric(currentmpx,m) - elseif t == "string" then - inject_string(currentmpx,m) - elseif t == "boolean" then - inject_boolean(currentmpx,m) - end - end - - else - - local get_numeric = mplib.get_numeric - local get_integer = mplib.get_integer - local get_string = mplib.get_string - local get_boolean = mplib.get_boolean - local get_path = mplib.get_path - local set_path = mplib.set_path - - get.numeric = function(s) return get_numeric(currentmpx,s) end - get.number = function(s) return get_numeric(currentmpx,s) end - get.integer = function(s) return get_integer(currentmpx,s) end - get.string = function(s) return get_string (currentmpx,s) end - get.boolean = function(s) return get_boolean(currentmpx,s) end - get.path = function(s) return get_path (currentmpx,s) end - set.path = function(s,t) return set_path (currentmpx,s,t) end -- not working yet - - end - - function metapost.pushscriptrunner(mpx) - insert(stack,mpx) - currentmpx = mpx - end - - function metapost.popscriptrunner() - currentmpx = remove(stack,mpx) - end - - function metapost.currentmpx() - return currentmpx - end - - local status = mplib.status - - function metapost.currentmpxstatus() - return status and status(currentmpx) or 0 - end - -end - --- unless we adapt the old mp library too, in the end we will have two --- variants - -do - - local lmtxmode = CONTEXTLMTXMODE > 0 - - -- serializers - - local f_integer = formatters["%i"] - local f_numeric = formatters["%F"] - - -- no %n as that can produce -e notation and that is not so nice for scaled butmaybe we - -- should then switch between ... i.e. make a push/pop for the formatters here ... not now. - - local f_integer = formatters["%i"] - local f_numeric = formatters["%F"] - local f_pair = formatters["(%F,%F)"] - local f_ctrl = formatters["(%F,%F) .. controls (%F,%F) and (%F,%F)"] - 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)"] - local f_ctrl_pt = formatters["(%p,%p) .. controls (%p,%p) and (%p,%p)"] - local f_triplet_pt = formatters["(%p,%p,%p)"] - local f_quadruple_pt = formatters["(%p,%p,%p,%p)"] - - local r = P('%') / "percent" - + P('"') / "dquote" - + P('\n') / "crlf" - -- + P(' ') / "space" - local a = Cc("&") - local q = Cc('"') - local p = Cs(q * (r * a)^-1 * (a * r * (P(-1) + a) + P(1))^0 * q) - - mp.cleaned = function(s) return lpegmatch(p,s) or s end - - -- management - - -- sometimes we gain (e.g. .5 sec on the sync test) - - local cache = table.makeweak() - - local runscripts = { } - local runnames = { } - local nofscripts = 0 - - function metapost.registerscript(name,f) - nofscripts = nofscripts + 1 - if f then - runscripts[nofscripts] = f - runnames[name] = nofscripts - else - runscripts[nofscripts] = name - end - return nofscripts - end - - function metapost.scriptindex(name) - return runnames[name] or 0 - end - - -- The gbuffer sharing and such is not really needed now but make a dent when - -- we have a high volume of simpel calls (loops) so we keep it around for a - -- while. - - local nesting = 0 - local runs = 0 - local gbuffer = { } - local buffer = gbuffer - local n = 0 - - local function mpdirect1(a) - n = n + 1 buffer[n] = a - end - local function mpdirect2(a,b) - n = n + 1 buffer[n] = a - n = n + 1 buffer[n] = b - end - local function mpdirect3(a,b,c) - n = n + 1 buffer[n] = a - n = n + 1 buffer[n] = b - n = n + 1 buffer[n] = c - end - local function mpdirect4(a,b,c,d) - n = n + 1 buffer[n] = a - n = n + 1 buffer[n] = b - n = n + 1 buffer[n] = c - n = n + 1 buffer[n] = d - end - local function mpdirect5(a,b,c,d,e) - n = n + 1 buffer[n] = a - n = n + 1 buffer[n] = b - n = n + 1 buffer[n] = c - n = n + 1 buffer[n] = d - n = n + 1 buffer[n] = e - end - - local function mpflush(separator) - buffer[1] = concat(buffer,separator or "",1,n) - n = 1 - end - - function metapost.runscript(code) - nesting = nesting + 1 - runs = runs + 1 - - local index = type(code) == "number" - local f - local result - - if index then - f = runscripts[code] - if not f then - report_luarun("%i: bad index: %s",nesting,code) - elseif trace_luarun then - report_luarun("%i: index: %i",nesting,code) - end - else - if trace_luarun then - report_luarun("%i: code: %s",nesting,code) - end - f = cache[code] - if not f then - f = loadstring("return " .. code) - if f then - cache[code] = f - elseif be_tolerant then - f = loadstring(code) - if f then - cache[code] = f - end - end - end - end - - -- returning nil is more efficient and a signal not to scan in mp - - if f then - - local lbuffer, ln - - if nesting == 1 then - buffer = gbuffer - n = 0 - else - lbuffer = buffer - ln = n - buffer = { } - n = 0 - end - - result = f() - if result then - local t = type(result) - if lmtxmode then - -- we can consider to use the injector for tables but then we need to - -- check of concatination is expected so best keep this! - if t == "number" or t == "boolean" then - -- native types - elseif t == "string" or t == "table" then - -- (concatenated) passed to scantokens - else - -- scantokens - result = tostring(result) - end - else - if t == "number" then - result = f_numeric(result) - elseif t == "table" then - result = concat(result) -- no spaces here - else - result = tostring(result) - end - end - if trace_luarun then - report_luarun("%i: %s result: %s",nesting,t,result) - end - elseif n == 0 then --- result = "" -result = nil -- no scantokens done then - if trace_luarun then - report_luarun("%i: no buffered result",nesting) - end - elseif n == 1 then - result = buffer[1] - if trace_luarun then - report_luarun("%i: 1 buffered result: %s",nesting,result) - end - else - -- the space is why we sometimes have collectors - if nesting == 1 then - -- if we had no space we could pass result directly in lmtx - result = concat(buffer," ",1,n) - if n > 500 or #result > 10000 then - gbuffer = { } -- newtable(20,0) - lbuffer = gbuffer - end - else - -- if we had no space we could pass result directly in lmtx - result = concat(buffer," ") - end - if trace_luarun then - report_luarun("%i: %i buffered results: %s",nesting,n,result) - end - end - - if nesting == 1 then - n = 0 - else - buffer = lbuffer - n = ln - end - - else - report_luarun("%i: no result, invalid code: %s",nesting,code) - result = "" - end - - nesting = nesting - 1 - - return result - end - - function metapost.nofscriptruns() - return runs - end - - -- writers - - local function mpp(value) - n = n + 1 - local t = type(value) - if t == "number" then - buffer[n] = f_numeric(value) - elseif t == "string" then - buffer[n] = value - elseif t == "table" then - if #t == 6 then - buffer[n] = "totransform(" .. concat(value,",") .. ")" - else - buffer[n] = "(" .. concat(value,",") .. ")" - end - else -- boolean or whatever - buffer[n] = tostring(value) - end - end - - local function mpprint(first,second,...) - if second == nil then - if first ~= nil then - mpp(first) - end - else - for i=1,select("#",first,second,...) do - local value = (select(i,first,second,...)) - if value ~= nil then - mpp(value) - end - end - end - end - - local function mpp(value) - n = n + 1 - local t = type(value) - if t == "number" then - buffer[n] = f_numeric(value) - elseif t == "string" then - buffer[n] = lpegmatch(p,value) - elseif t == "table" then - if #t > 4 then - buffer[n] = "" - else - buffer[n] = "(" .. concat(value,",") .. ")" - end - else -- boolean or whatever - buffer[n] = tostring(value) - end - end - - local function mpvprint(first,second,...) -- variable print - if second == nil then - if first ~= nil then - mpp(first) - end - else - for i=1,select("#",first,second,...) do - local value = (select(i,first,second,...)) - if value ~= nil then - mpp(value) - end - end - end - end - - local function mpstring(value) - n = n + 1 - buffer[n] = lpegmatch(p,value) - end - - local function mpboolean(b) - n = n + 1 - buffer[n] = b and "true" or "false" - end - - local function mpnumeric(f) - n = n + 1 - if not f or f == 0 then - buffer[n] = "0" - else - buffer[n] = f_numeric(f) - end - end - - local function mpinteger(i) - n = n + 1 - -- buffer[n] = i and f_integer(i) or "0" - buffer[n] = i or "0" - end - - local function mppoints(i) - n = n + 1 - if not i or i == 0 then - buffer[n] = "0pt" - else - buffer[n] = f_points(i) - end - end - - local function mppair(x,y) - n = n + 1 - if type(x) == "table" then - buffer[n] = f_pair(x[1],x[2]) - else - buffer[n] = f_pair(x,y) - end - end - - local function mppairpoints(x,y) - n = n + 1 - if type(x) == "table" then - buffer[n] = f_pair_pt(x[1],x[2]) - else - buffer[n] = f_pair_pt(x,y) - end - end - - local function mptriplet(x,y,z) - n = n + 1 - if type(x) == "table" then - buffer[n] = f_triplet(x[1],x[2],x[3]) - else - buffer[n] = f_triplet(x,y,z) - end - end - - local function mptripletpoints(x,y,z) - n = n + 1 - if type(x) == "table" then - buffer[n] = f_triplet_pt(x[1],x[2],x[3]) - else - buffer[n] = f_triplet_pt(x,y,z) - end - end - - local function mpquadruple(w,x,y,z) - n = n + 1 - if type(w) == "table" then - buffer[n] = f_quadruple(w[1],w[2],w[3],w[4]) - else - buffer[n] = f_quadruple(w,x,y,z) - end - end - - local function mpquadruplepoints(w,x,y,z) - n = n + 1 - if type(w) == "table" then - buffer[n] = f_quadruple_pt(w[1],w[2],w[3],w[4]) - else - buffer[n] = f_quadruple_pt(w,x,y,z) - end - end - - local function mptransform(x,y,xx,xy,yx,yy) - n = n + 1 - if type(x) == "table" then - buffer[n] = f_transform(x[1],x[2],x[3],x[4],x[5],x[6]) - else - buffer[n] = f_transform(x,y,xx,xy,yx,yy) - end - end - - local function mpcolor(c,m,y,k) - n = n + 1 - if type(c) == "table" then - local l = #c - if l == 4 then - buffer[n] = f_quadruple(c[1],c[2],c[3],c[4]) - elseif l == 3 then - buffer[n] = f_triplet(c[1],c[2],c[3]) - else - buffer[n] = f_numeric(c[1]) - end - else - if k then - buffer[n] = f_quadruple(c,m,y,k) - elseif y then - buffer[n] = f_triplet(c,m,y) - else - buffer[n] = f_numeric(c) - end - end - end - - -- we have three kind of connectors: - -- - -- .. ... -- (true) - - local function mp_path(f2,f6,t,connector,cycle) - if type(t) == "table" then - local tn = #t - if tn == 1 then - local t1 = t[1] - 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 = ".." - elseif connector == false then - connector = "--" - end - if cycle == nil then - cycle = t.cycle - if cycle == nil then - cycle = true - end - end - local six = connector == ".." -- otherwise we use whatever gets asked for - local controls = connector -- whatever - 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 = ".." - else - buffer[n] = f2(a[1],a[2]) - controls = connector - end - for i=2,tn-1 do - a = b - b = t[i+1] - n = n + 1 - buffer[n] = connector - 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 = ".." - else - buffer[n] = f2(a[1],a[2]) - controls = connector - end - end - n = n + 1 - buffer[n] = connector - a = b - b = t[1] - n = n + 1 - if cycle then - 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 = ".." - else - buffer[n] = f2(a[1],a[2]) - controls = connector - end - n = n + 1 - buffer[n] = connector - n = n + 1 - buffer[n] = "cycle" - else - buffer[n] = f2(a[1],a[2]) - end - n = n + 1 - buffer[n] = ")" - end - end - end - - local function mppath(...) - mp_path(f_pair,f_ctrl,...) - end - - local function mppathpoints(...) - mp_path(f_pair_pt,f_ctrl_pt,...) - end - - local function mpsize(t) - n = n + 1 - buffer[n] = type(t) == "table" and f_numeric(#t) or "0" - end - - local replacer = lpeg.replacer("@","%%") - - local function mpfprint(fmt,...) - n = n + 1 - if not find(fmt,"%",1,true) then - fmt = lpegmatch(replacer,fmt) - end - buffer[n] = formatters[fmt](...) - end - - local function mpquoted(fmt,s,...) - if s then - n = n + 1 - if not find(fmt,"%",1,true) then - fmt = lpegmatch(replacer,fmt) - end - -- buffer[n] = '"' .. formatters[fmt](s,...) .. '"' - buffer[n] = lpegmatch(p,formatters[fmt](s,...)) - elseif fmt then - n = n + 1 - -- buffer[n] = '"' .. fmt .. '"' - buffer[n] = lpegmatch(p,fmt) - else - -- something is wrong - end - end - - aux.direct = mpdirect1 - aux.direct1 = mpdirect1 - aux.direct2 = mpdirect2 - aux.direct3 = mpdirect3 - aux.direct4 = mpdirect4 - aux.flush = mpflush - - aux.print = mpprint - aux.vprint = mpvprint - aux.boolean = mpboolean - aux.string = mpstring - aux.numeric = mpnumeric - aux.number = mpnumeric - aux.integer = mpinteger - aux.points = mppoints - aux.pair = mppair - aux.pairpoints = mppairpoints - aux.triplet = mptriplet - aux.tripletpoints = mptripletpoints - aux.quadruple = mpquadruple - aux.quadruplepoints = mpquadruplepoints - aux.path = mppath - aux.pathpoints = mppathpoints - aux.size = mpsize - aux.fprint = mpfprint - aux.quoted = mpquoted - aux.transform = mptransform - aux.color = mpcolor - - -- for the moment - - local function mpdraw(lines,list) -- n * 4 - if list then - local c = #lines - for i=1,c do - local ci = lines[i] - local ni = #ci - n = n + 1 buffer[n] = i < c and "d(" or "D(" - for j=1,ni,2 do - local l = j + 1 - n = n + 1 buffer[n] = ci[j] - n = n + 1 buffer[n] = "," - n = n + 1 buffer[n] = ci[l] - n = n + 1 buffer[n] = l < ni and ")--(" or ");" - end - end - else - local l = #lines - local m = l - 4 - for i=1,l,4 do - n = n + 1 buffer[n] = i < m and "d(" or "D(" - n = n + 1 buffer[n] = lines[i] - n = n + 1 buffer[n] = "," - n = n + 1 buffer[n] = lines[i+1] - n = n + 1 buffer[n] = ")--(" - n = n + 1 buffer[n] = lines[i+2] - n = n + 1 buffer[n] = "," - n = n + 1 buffer[n] = lines[i+3] - n = n + 1 buffer[n] = ");" - end - end - end - - local function mpfill(lines,list) - if list then - local c = #lines - for i=1,c do - local ci = lines[i] - local ni = #ci - n = n + 1 buffer[n] = i < c and "f(" or "F(" - for j=1,ni,2 do - local l = j + 1 - n = n + 1 buffer[n] = ci[j] - n = n + 1 buffer[n] = "," - n = n + 1 buffer[n] = ci[l] - n = n + 1 buffer[n] = l < ni and ")--(" or ")--C;" - end - end - else - local l = #lines - local m = l - 4 - for i=1,l,4 do - n = n + 1 buffer[n] = i < m and "f(" or "F(" - n = n + 1 buffer[n] = lines[i] - n = n + 1 buffer[n] = "," - n = n + 1 buffer[n] = lines[i+1] - n = n + 1 buffer[n] = ")--(" - n = n + 1 buffer[n] = lines[i+2] - n = n + 1 buffer[n] = "," - n = n + 1 buffer[n] = lines[i+3] - n = n + 1 buffer[n] = ")--C;" - end - end - end - - aux.draw = mpdraw - aux.fill = mpfill - - for k, v in next, aux do mp[k] = v end - -end - -do - - -- Another experimental feature: - - local mpnumeric = mp.numeric - local scanstring = scan.string - local scriptindex = metapost.scriptindex - - function mp.mf_script_index(name) - local index = scriptindex(name) - -- report_script("method %i, name %a, index %i",1,name,index) - mpnumeric(index) - end - - -- once bootstrapped ... (needs pushed mpx instances) - - metapost.registerscript("scriptindex",function() - local name = scanstring() - local index = scriptindex(name) - -- report_script("method %i, name %a, index %i",2,name,index) - mpnumeric(index) - end) - -end - --- the next will move to mlib-lmp.lua - -do - - local mpnamedcolor = attributes.colors.mpnamedcolor - local mpprint = aux.print - local scanstring = scan.string - - mp.mf_named_color = function(str) - mpprint(mpnamedcolor(str)) - end - - -- todo: we can inject but currently we always get a string back so then - -- we need to deal with it upstream in the color module ... not now - - metapost.registerscript("namedcolor",function() - mpprint(mpnamedcolor(scanstring())) - end) +get.numeric = function(s) return get_numeric(currentmpx,s) end +get.number = function(s) return get_numeric(currentmpx,s) end +get.integer = function(s) return get_integer(currentmpx,s) end +get.string = function(s) return get_string (currentmpx,s) end +get.boolean = function(s) return get_boolean(currentmpx,s) end +get.path = function(s) return get_path (currentmpx,s) end +set.path = function(s,t) return set_path (currentmpx,s,t) end -- not working yet +function metapost.pushscriptrunner(mpx) + insert(stack,mpx) + currentmpx = mpx end -function mp.n(t) -- used ? - return type(t) == "table" and #t or 0 +function metapost.popscriptrunner() + currentmpx = remove(stack,mpx) end -do - - -- experiment: names can change - - local mppath = aux.path - local mpsize = aux.size - - local whitespace = lpegpatterns.whitespace - local newline = lpegpatterns.newline - local setsep = newline^2 - local comment = (S("#%") + P("--")) * (1-newline)^0 * (whitespace - setsep)^0 - local value = (1-whitespace)^1 / tonumber - local entry = Ct( value * whitespace * value) - local set = Ct((entry * (whitespace-setsep)^0 * comment^0)^1) - local series = Ct((set * whitespace^0)^1) - - local pattern = whitespace^0 * series - - local datasets = { } - mp.datasets = datasets - - function mp.dataset(str) - return lpegmatch(pattern,str) - end - - function datasets.load(tag,filename) - if not filename then - tag, filename = file.basename(tag), tag - end - local data = lpegmatch(pattern,io.loaddata(filename) or "") - datasets[tag] = { - data = data, - line = function(n) mppath(data[n or 1]) end, - size = function() mpsize(data) end, - } - end - - table.setmetatablecall(datasets,function(t,k,f,...) - local d = datasets[k] - local t = type(d) - if t == "table" then - d = d[f] - if type(d) == "function" then - d(...) - else - mpvprint(...) - end - elseif t == "function" then - d(f,...) - end - end) - +function metapost.currentmpx() + return currentmpx end --- \startluacode --- local str = [[ --- 10 20 20 20 --- 30 40 40 60 --- 50 10 --- --- 10 10 20 30 --- 30 50 40 50 --- 50 20 -- the last one --- --- 10 20 % comment --- 20 10 --- 30 40 # comment --- 40 20 --- 50 10 --- ]] --- --- MP.myset = mp.dataset(str) --- --- inspect(MP.myset) --- \stopluacode --- --- \startMPpage --- color c[] ; c[1] := red ; c[2] := green ; c[3] := blue ; --- for i=1 upto lua("mp.print(mp.n(MP.myset))") : --- draw lua("mp.path(MP.myset[" & decimal i & "])") withcolor c[i] ; --- endfor ; --- \stopMPpage - --- texts: - -do - - local mptriplet = mp.triplet - - local bpfactor = number.dimenfactors.bp - local textexts = nil - local mptriplet = mp.triplet - local nbdimensions = nodes.boxes.dimensions - - function mp.mf_tt_initialize(tt) - textexts = tt - end - - function mp.mf_tt_dimensions(n) - local box = textexts and textexts[n] - if box then - -- could be made faster with nuts but not critical - mptriplet(box.width*bpfactor,box.height*bpfactor,box.depth*bpfactor) - else - mptriplet(0,0,0) - end - end - - function mp.mf_tb_dimensions(category,name) - local w, h, d = nbdimensions(category,name) - mptriplet(w*bpfactor,h*bpfactor,d*bpfactor) - end - - function mp.report(a,b,c,...) - if c then - report_message("%s : %s",a,formatters[(gsub(b,"@","%%"))](c,...)) - elseif b then - report_message("%s : %s",a,b) - elseif a then - report_message("%s : %s","message",a) - end - end - -end - -do - - local mpprint = aux.print - local modes = tex.modes - local systemmodes = tex.systemmodes - - function mp.mode(s) - mpprint(modes[s] and true or false) - end - - function mp.systemmode(s) - mpprint(systemmodes[s] and true or false) - end - - mp.processingmode = mp.mode +local status = mplib.status +function metapost.currentmpxstatus() + return status and status(currentmpx) or 0 end - --- for alan's nodes: - -do - - local mpprint = aux.print - local mpquoted = aux.quoted - - function mp.isarray(str) - mpprint(find(str,"%d") and true or false) - end - - function mp.prefix(str) - mpquoted(match(str,"^(.-)[%d%[]") or str) - end - - -- function mp.dimension(str) - -- local n = 0 - -- for s in gmatch(str,"%[?%-?%d+%]?") do --todo: lpeg - -- n = n + 1 - -- end - -- mpprint(n) - -- end - - mp.dimension = lpeg.counter(P("[") * lpegpatterns.integer * P("]") + lpegpatterns.integer,mpprint) - - -- faster and okay as we don't have many variables but probably only - -- basename makes sense and even then it's not called that often - - -- local hash = table.setmetatableindex(function(t,k) - -- local v = find(k,"%d") and true or false - -- t[k] = v - -- return v - -- end) - -- - -- function mp.isarray(str) - -- mpprint(hash[str]) - -- end - -- - -- local hash = table.setmetatableindex(function(t,k) - -- local v = '"' .. (match(k,"^(.-)%d") or k) .. '"' - -- t[k] = v - -- return v - -- end) - -- - -- function mp.prefix(str) - -- mpprint(hash[str]) - -- end - -end - -do - - local getmacro = tex.getmacro - local getdimen = tex.getdimen - local getcount = tex.getcount - local gettoks = tex.gettoks - local setmacro = tex.setmacro - local setdimen = tex.setdimen - local setcount = tex.setcount - local settoks = tex.settoks - - local mpprint = mp.print - local mpquoted = mp.quoted - - local bpfactor = number.dimenfactors.bp - - -- more helpers - - local function getmacro(k) mpprint (getmacro(k)) end - local function getdimen(k) mpprint (getdimen(k)*bpfactor) end - local function getcount(k) mpprint (getcount(k)) end - local function gettoks (k) mpquoted(gettoks (k)) end - - local function setmacro(k,v) setmacro(k,v) end - local function setdimen(k,v) setdimen(k,v/bpfactor) end - local function setcount(k,v) setcount(k,v) end - local function settoks (k,v) settoks (k,v) end - - -- def foo = lua.mp.foo ... enddef ; % loops due to foo in suffix - - mp._get_macro_ = getmacro mp.getmacro = getmacro - mp._get_dimen_ = getdimen mp.getdimen = getdimen - mp._get_count_ = getcount mp.getcount = getcount - mp._get_toks_ = gettoks mp.gettoks = gettoks - - mp._set_macro_ = setmacro mp.setmacro = setmacro - mp._set_dimen_ = setdimen mp.setdimen = setdimen - mp._set_count_ = setcount mp.setcount = setcount - mp._set_toks_ = settoks mp.settoks = settoks - -end - --- position fun - -do - - local mpprint = mp.print - local mpfprint = mp.fprint - local mpquoted = mp.quoted - local jobpositions = job.positions - local getwhd = jobpositions.whd - local getxy = jobpositions.xy - local getposition = jobpositions.position - local getpage = jobpositions.page - local getregion = jobpositions.region - local getmacro = tokens.getters.macro - - function mp.positionpath(name) - local w, h, d = getwhd(name) - if w then - mpfprint("((%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle)",0,-d,w,-d,w,h,0,h) - else - mpprint("(origin--cycle)") - end - end - - function mp.positioncurve(name) - local w, h, d = getwhd(name) - if w then - mpfprint("((%p,%p)..(%p,%p)..(%p,%p)..(%p,%p)..cycle)",0,-d,w,-d,w,h,0,h) - else - mpprint("(origin--cycle)") - end - end - - function mp.positionbox(name) - local p, x, y, w, h, d = getposition(name) - if p then - mpfprint("((%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle)",x,y-d,x+w,y-d,x+w,y+h,x,y+h) - else - mpprint("(%p,%p)",x,y) - end - end - - function mp.positionxy(name) - local x, y = getxy(name) - if x then - mpfprint("(%p,%p)",x,y) - else - mpprint("origin") - end - end - - function mp.positionpage(name) - mpfprint("%i",getpage(name) or 0) - end - - function mp.positionregion(name) - local r = getregion(name) - if r then - mpquoted(r) - else - mpquoted("unknown") - end - end - - function mp.positionwhd(name) - local w, h, d = getwhd(name) - if w then - mpfprint("(%p,%p,%p)",w,h,d) - else - mpprint("(0,0,0)") - end - end - - function mp.positionpxy(name) - local p, x, y = getposition(name) - if p then - mpfprint("(%p,%p,%p)",p,x,y) - else - mpprint("(0,0,0)") - end - end - - function mp.positionanchor() - mpquoted(getmacro("MPanchorid")) - end - -end - -do - - local mppair = mp.pair - - function mp.textextanchor(s) - local x, y = match(s,"tx_anchor=(%S+) (%S+)") -- todo: make an lpeg - if x and y then - x = tonumber(x) - y = tonumber(y) - end - mppair(x or 0,y or 0) - end - -end - -do - - local mpprint = mp.print - local mpquoted = mp.quoted - local getmacro = tokens.getters.macro - - function mp.texvar(name) - mpprint(getmacro(metapost.namespace .. name)) - end - - function mp.texstr(name) - mpquoted(getmacro(metapost.namespace .. name)) - end - -end - -do - - local mpprint = aux.print - local mpvprint = aux.vprint - - local hashes = { } - - function mp.newhash(name) - if name then - hashes[name] = { } - else - for i=1,#hashes+1 do - if not hashes[i] then - hashes[i] = { } - mpvprint(i) - return - end - end - end - end - - function mp.disposehash(n) - if tonumber(n) then - hashes[n] = false - else - hashes[n] = nil - end - end - - function mp.inhash(n,key) - local h = hashes[n] - mpvprint(h and h[key] and true or false) - end - - function mp.tohash(n,key,value) - local h = hashes[n] - if h then - if value == nil then - h[key] = true - else - h[key] = value - end - end - end - - function mp.fromhash(n,key) - local h = hashes[n] - mpvprint(h and h[key] or false) - end - - interfaces.implement { - name = "MPfromhash", - arguments = "2 strings", - actions = function(name,key) - local h = hashes[name] or hashes[tonumber(name)] - if h then - local v = h[key] or h[tonumber(key)] - if v then - context(v) - end - end - end - } - -end - -do - - -- a bit overkill: just a find(str,"mf_object=") can be enough - -- - -- todo : share with mlib-pps.lua metapost,isobject - - local mpboolean = aux.boolean - - local p1 = P("mf_object=") - local p2 = lpegpatterns.eol * p1 - local pattern = (1-p2)^0 * p2 + p1 - - function mp.isobject(str) - mpboolean(pattern and str ~= "" and lpegmatch(pattern,str)) - end - -end - -function mp.flatten(t) - local tn = #t - - local t1 = t[1] - local t2 = t[2] - local t3 = t[3] - local t4 = t[4] - - for i=1,tn-5,2 do - local t5 = t[i+4] - local t6 = t[i+5] - if t1 == t3 and t3 == t5 and ((t2 <= t4 and t4 <= t6) or (t6 <= t4 and t4 <= t2)) then - t[i+3] = t2 - t4 = t2 - t[i] = false - t[i+1] = false - elseif t2 == t4 and t4 == t6 and ((t1 <= t3 and t3 <= t5) or (t5 <= t3 and t3 <= t1)) then - t[i+2] = t1 - t3 = t1 - t[i] = false - t[i+1] = false - end - t1 = t3 - t2 = t4 - t3 = t5 - t4 = t6 - end - - -- remove duplicates - - local t1 = t[1] - local t2 = t[2] - for i=1,tn-2,2 do - local t3 = t[i+2] - local t4 = t[i+3] - if t1 == t3 and t2 == t4 then - t[i] = false - t[i+1] = false - end - t1 = t3 - t2 = t4 - end - - -- move coordinates - - local m = 0 - for i=1,tn,2 do - if t[i] then - m = m + 1 t[m] = t[i] - m = m + 1 t[m] = t[i+1] - end - end - - -- prune the table (not gc'd) - - for i=tn,m+1,-1 do - t[i] = nil - end - - -- safeguard so that we have at least one segment - - if m == 2 then - t[3] = t[1] - t[4] = t[2] - end - -end - -do - - -- if needed we can optimize the sub (cache last split) - - local utflen = utf.len - local utfsub = utf.sub - - function mp.utflen(s) - mpnumeric(utflen(s)) - end - - function mp.utfsub(s,f,t) - mpquoted(utfsub(s,f,t or f)) - end - -end - diff --git a/tex/context/base/mkiv/mlib-mat.lmt b/tex/context/base/mkiv/mlib-mat.lmt new file mode 100644 index 000000000..e5d40319f --- /dev/null +++ b/tex/context/base/mkiv/mlib-mat.lmt @@ -0,0 +1,139 @@ +if not modules then modules = { } end modules ['mlib-mat'] = { + 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", +} + +local scanners = mp.scan +local injectors = mp.inject + +local scannumeric = scanners.numeric +local scanpair = scanners.pair +local scancolor = scanners.color + +local mppair = injectors.pair or mp.pair + +local registerscript = metapost.registerscript + +local m = xmath +local c = xcomplex + +local m_acos = m.acos registerscript("m_acos", function() return m_acos (scannumeric()) end) +local m_acosh = m.acosh registerscript("m_acosh", function() return m_acosh (scannumeric()) end) +local m_asin = m.asin registerscript("m_asin", function() return m_asin (scannumeric()) end) +local m_asinh = m.asinh registerscript("m_asinh", function() return m_asinh (scannumeric()) end) +local m_atan = m.atan registerscript("m_atan", function() return m_atan (scannumeric()) end) +local m_atan2 = m.atan2 registerscript("m_atan2", function() return m_atan2 (scanpair ()) end) +local m_atanh = m.atanh registerscript("m_atanh", function() return m_atanh (scannumeric()) end) +local m_cbrt = m.cbrt registerscript("m_cbrt", function() return m_cbrt (scannumeric()) end) +local m_ceil = m.ceil registerscript("m_ceil", function() return m_ceil (scannumeric()) end) +local m_copysign = m.copysign registerscript("m_copysign", function() return m_copysign (scanpair ()) end) +local m_cos = m.cos registerscript("m_cos", function() return m_cos (scannumeric()) end) +local m_cosh = m.cosh registerscript("m_cosh", function() return m_cosh (scannumeric()) end) +local m_deg = m.deg registerscript("m_deg", function() return m_deg (scannumeric()) end) +local m_erf = m.erf registerscript("m_erf", function() return m_erf (scannumeric()) end) +local m_erfc = m.erfc registerscript("m_erfc", function() return m_erfc (scannumeric()) end) +local m_exp = m.exp registerscript("m_exp", function() return m_exp (scannumeric()) end) +local m_exp2 = m.exp2 registerscript("m_exp2", function() return m_exp2 (scannumeric()) end) +local m_expm1 = m.expm1 registerscript("m_expm1", function() return m_expm1 (scannumeric()) end) +local m_fabs = m.fabs registerscript("m_fabs", function() return m_fabs (scannumeric()) end) +local m_fdim = m.fdim registerscript("m_fdim", function() return m_fdim (scanpair ()) end) +local m_floor = m.floor registerscript("m_floor", function() return m_floor (scannumeric()) end) +local m_fma = m.fma registerscript("m_fma", function() return m_fma (scancolor ()) end) +local m_fmax = m.fmax registerscript("m_fmax", function() return m_fmax (scannumeric()) end) +local m_fmin = m.fmin registerscript("m_fmin", function() return m_fmin (scannumeric()) end) +local m_fmod = m.fmod registerscript("m_fmod", function() return m_fmod (scanpair ()) end) +local m_frexp = m.frexp registerscript("m_frexp", function() return m_frexp (scannumeric()) end) +local m_gamma = m.gamma registerscript("m_gamma", function() return m_gamma (scannumeric()) end) +local m_hypot = m.hypot registerscript("m_hypot", function() return m_hypot (scanpair ()) end) +local m_isfinite = m.isfinite registerscript("m_isfinite", function() return m_isfinite (scannumeric()) end) +local m_isinf = m.isinf registerscript("m_isinf", function() return m_isinf (scannumeric()) end) +local m_isnan = m.isnan registerscript("m_isnan", function() return m_isnan (scannumeric()) end) +local m_isnormal = m.isnormal registerscript("m_isnormal", function() return m_isnormal (scannumeric()) end) +local m_j0 = m.j0 registerscript("m_j0", function() return m_j0 (scannumeric()) end) +local m_j1 = m.j1 registerscript("m_j1", function() return m_j1 (scannumeric()) end) +local m_jn = m.jn registerscript("m_jn", function() return m_jn (scanpair ()) end) +local m_ldexp = m.ldexp registerscript("m_ldexp", function() return m_ldexp (scanpair ()) end) +local m_lgamma = m.lgamma registerscript("m_lgamma", function() return m_lgamma (scannumeric()) end) +local m_log = m.log registerscript("m_log", function() return m_log (scannumeric()) end) +local m_log10 = m.log10 registerscript("m_log10", function() return m_log10 (scannumeric()) end) +local m_log1p = m.log1p registerscript("m_log1p", function() return m_log1p (scannumeric()) end) +local m_log2 = m.log2 registerscript("m_log2", function() return m_log2 (scannumeric()) end) +local m_logb = m.logb registerscript("m_logb", function() return m_logb (scannumeric()) end) +local m_modf = m.modf registerscript("m_modf", function() return m_modf (scannumeric()) end) +local m_nearbyint = m.nearbyint registerscript("m_nearbyint", function() return m_nearbyint(scannumeric()) end) +local m_nextafter = m.nextafter registerscript("m_nextafter", function() return m_nextafter(scanpair ()) end) +local m_pow = m.pow registerscript("m_pow", function() return m_pow (scanpair ()) end) +local m_rad = m.rad registerscript("m_rad", function() return m_rad (scannumeric()) end) +local m_remainder = m.remainder registerscript("m_remainder", function() return m_remainder(scanpair ()) end) +local m_remquo = m.remquo registerscript("m_remquo", function() return m_remquo (scannumeric()) end) +local m_round = m.round registerscript("m_round", function() return m_round (scannumeric()) end) +local m_scalbn = m.scalbn registerscript("m_scalbn", function() return m_scalbn (scanpair ()) end) +local m_sin = m.sin registerscript("m_sin", function() return m_sin (scannumeric()) end) +local m_sinh = m.sinh registerscript("m_sinh", function() return m_sinh (scannumeric()) end) +local m_sqrt = m.sqrt registerscript("m_sqrt", function() return m_sqrt (scannumeric()) end) +local m_tan = m.tan registerscript("m_tan", function() return m_tan (scannumeric()) end) +local m_tanh = m.tanh registerscript("m_tanh", function() return m_tanh (scannumeric()) end) +local m_tgamma = m.tgamma registerscript("m_tgamma", function() return m_tgamma (scannumeric()) end) +local m_trunc = m.trunc registerscript("m_trunc", function() return m_trunc (scannumeric()) end) +local m_y0 = m.y0 registerscript("m_y0", function() return m_y0 (scannumeric()) end) +local m_y1 = m.y1 registerscript("m_y1", function() return m_y1 (scannumeric()) end) +local m_yn = m.yn registerscript("m_yn", function() return m_yn (scanpair ()) end) + +if not (c and c.sin) then + return +end + +local c_topair = c.topair +local c_new = c.new + +local c_sin = c.sin registerscript("c_sin", function() return mppair(c_topair(c_sin (c_new(scanpair())))) end) +local c_cos = c.cos registerscript("c_cos", function() return mppair(c_topair(c_cos (c_new(scanpair())))) end) +local c_tan = c.tan registerscript("c_tan", function() return mppair(c_topair(c_tan (c_new(scanpair())))) end) +local c_sinh = c.sinh registerscript("c_sinh", function() return mppair(c_topair(c_sinh (c_new(scanpair())))) end) +local c_cosh = c.cosh registerscript("c_cosh", function() return mppair(c_topair(c_cosh (c_new(scanpair())))) end) +local c_tanh = c.tanh registerscript("c_tanh", function() return mppair(c_topair(c_tanh (c_new(scanpair())))) end) + +local c_asin = c.asin registerscript("c_asin", function() return mppair(c_topair(c_sin (c_new(scanpair())))) end) +local c_acos = c.acos registerscript("c_acos", function() return mppair(c_topair(c_cos (c_new(scanpair())))) end) +local c_atan = c.atan registerscript("c_atan", function() return mppair(c_topair(c_tan (c_new(scanpair())))) end) +local c_asinh = c.asinh registerscript("c_asinh", function() return mppair(c_topair(c_sinh (c_new(scanpair())))) end) +local c_acosh = c.acosh registerscript("c_acosh", function() return mppair(c_topair(c_cosh (c_new(scanpair())))) end) +local c_atanh = c.atanh registerscript("c_atanh", function() return mppair(c_topair(c_tanh (c_new(scanpair())))) end) + +local c_sqrt = c.sqrt registerscript("c_sqrt", function() return mppair(c_topair(c_sqrt (c_new(scanpair())))) end) +local c_abs = c.abs registerscript("c_abs", function() return c_topair(c_abs (c_new(scanpair()))) end) +local c_arg = c.arg registerscript("c_arg", function() return c_topair(c_arg (c_new(scanpair()))) end) +local c_conj = c.conj registerscript("c_conj", function() return mppair(c_topair(c_conj (c_new(scanpair())))) end) +local c_exp = c.exp registerscript("c_exp", function() return mppair(c_topair(c_exp (c_new(scanpair())))) end) +local c_log = c.log registerscript("c_log", function() return mppair(c_topair(c_log (c_new(scanpair())))) end) +local c_proj = c.proj registerscript("c_proj", function() return mppair(c_topair(c_proj (c_new(scanpair())))) end) + +local c_erf = c.erf registerscript("c_erf", function() return mppair(c_topair(c_erf (c_new(scanpair())))) end) +local c_erfc = c.erfc registerscript("c_erfc", function() return mppair(c_topair(c_erfc (c_new(scanpair())))) end) +local c_erfcx = c.erfcx registerscript("c_erfcx", function() return mppair(c_topair(c_erfcx (c_new(scanpair())))) end) +local c_erfi = c.erfi registerscript("c_erfi", function() return mppair(c_topair(c_erfi (c_new(scanpair())))) end) +local c_dawson = c.dawson registerscript("c_dawson", function() return mppair(c_topair(c_dawson(c_new(scanpair())))) end) + +local c_voigt = c.voigt +local c_voigt_hwhm = c.voigt_hwhm + +registerscript("c_voigt", function() + return mppair(c_topair(c_voigt(c_new(scanpair()),c_new(scanpair()),c_new(scanpair())))) +end) + +registerscript("c_voigt_hwhm", function() + return mppair(c_topair(c_voigt_hwhm(c_new(scanpair()),c_new(scanpair())))) +end) + +local c_pow = c.pow registerscript("c_pow", function() return mppair(c_topair(c_pow(c_new(scanpair()),c_new(scanpair())))) end) +local c_add = c.add registerscript("c_add", function() return mppair(c_topair(c_add(c_new(scanpair()),c_new(scanpair())))) end) +local c_sub = c.sub registerscript("c_sub", function() return mppair(c_topair(c_sub(c_new(scanpair()),c_new(scanpair())))) end) +local c_mul = c.mul registerscript("c_mul", function() return mppair(c_topair(c_mul(c_new(scanpair()),c_new(scanpair())))) end) +local c_div = c.div registerscript("c_div", function() return mppair(c_topair(c_div(c_new(scanpair()),c_new(scanpair())))) end) + +local c_imag = c.imag registerscript("c_imag", function() return c_topair(c_imag(c_new(scanpair()))) end) +local c_real = c.real registerscript("c_real", function() return c_topair(c_real(c_new(scanpair()))) end) +local c_neg = c.neg registerscript("c_new", function() return c_topair(c_neg (c_new(scanpair()))) end) diff --git a/tex/context/base/mkiv/mlib-mat.lua b/tex/context/base/mkiv/mlib-mat.lua deleted file mode 100644 index e5d40319f..000000000 --- a/tex/context/base/mkiv/mlib-mat.lua +++ /dev/null @@ -1,139 +0,0 @@ -if not modules then modules = { } end modules ['mlib-mat'] = { - 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", -} - -local scanners = mp.scan -local injectors = mp.inject - -local scannumeric = scanners.numeric -local scanpair = scanners.pair -local scancolor = scanners.color - -local mppair = injectors.pair or mp.pair - -local registerscript = metapost.registerscript - -local m = xmath -local c = xcomplex - -local m_acos = m.acos registerscript("m_acos", function() return m_acos (scannumeric()) end) -local m_acosh = m.acosh registerscript("m_acosh", function() return m_acosh (scannumeric()) end) -local m_asin = m.asin registerscript("m_asin", function() return m_asin (scannumeric()) end) -local m_asinh = m.asinh registerscript("m_asinh", function() return m_asinh (scannumeric()) end) -local m_atan = m.atan registerscript("m_atan", function() return m_atan (scannumeric()) end) -local m_atan2 = m.atan2 registerscript("m_atan2", function() return m_atan2 (scanpair ()) end) -local m_atanh = m.atanh registerscript("m_atanh", function() return m_atanh (scannumeric()) end) -local m_cbrt = m.cbrt registerscript("m_cbrt", function() return m_cbrt (scannumeric()) end) -local m_ceil = m.ceil registerscript("m_ceil", function() return m_ceil (scannumeric()) end) -local m_copysign = m.copysign registerscript("m_copysign", function() return m_copysign (scanpair ()) end) -local m_cos = m.cos registerscript("m_cos", function() return m_cos (scannumeric()) end) -local m_cosh = m.cosh registerscript("m_cosh", function() return m_cosh (scannumeric()) end) -local m_deg = m.deg registerscript("m_deg", function() return m_deg (scannumeric()) end) -local m_erf = m.erf registerscript("m_erf", function() return m_erf (scannumeric()) end) -local m_erfc = m.erfc registerscript("m_erfc", function() return m_erfc (scannumeric()) end) -local m_exp = m.exp registerscript("m_exp", function() return m_exp (scannumeric()) end) -local m_exp2 = m.exp2 registerscript("m_exp2", function() return m_exp2 (scannumeric()) end) -local m_expm1 = m.expm1 registerscript("m_expm1", function() return m_expm1 (scannumeric()) end) -local m_fabs = m.fabs registerscript("m_fabs", function() return m_fabs (scannumeric()) end) -local m_fdim = m.fdim registerscript("m_fdim", function() return m_fdim (scanpair ()) end) -local m_floor = m.floor registerscript("m_floor", function() return m_floor (scannumeric()) end) -local m_fma = m.fma registerscript("m_fma", function() return m_fma (scancolor ()) end) -local m_fmax = m.fmax registerscript("m_fmax", function() return m_fmax (scannumeric()) end) -local m_fmin = m.fmin registerscript("m_fmin", function() return m_fmin (scannumeric()) end) -local m_fmod = m.fmod registerscript("m_fmod", function() return m_fmod (scanpair ()) end) -local m_frexp = m.frexp registerscript("m_frexp", function() return m_frexp (scannumeric()) end) -local m_gamma = m.gamma registerscript("m_gamma", function() return m_gamma (scannumeric()) end) -local m_hypot = m.hypot registerscript("m_hypot", function() return m_hypot (scanpair ()) end) -local m_isfinite = m.isfinite registerscript("m_isfinite", function() return m_isfinite (scannumeric()) end) -local m_isinf = m.isinf registerscript("m_isinf", function() return m_isinf (scannumeric()) end) -local m_isnan = m.isnan registerscript("m_isnan", function() return m_isnan (scannumeric()) end) -local m_isnormal = m.isnormal registerscript("m_isnormal", function() return m_isnormal (scannumeric()) end) -local m_j0 = m.j0 registerscript("m_j0", function() return m_j0 (scannumeric()) end) -local m_j1 = m.j1 registerscript("m_j1", function() return m_j1 (scannumeric()) end) -local m_jn = m.jn registerscript("m_jn", function() return m_jn (scanpair ()) end) -local m_ldexp = m.ldexp registerscript("m_ldexp", function() return m_ldexp (scanpair ()) end) -local m_lgamma = m.lgamma registerscript("m_lgamma", function() return m_lgamma (scannumeric()) end) -local m_log = m.log registerscript("m_log", function() return m_log (scannumeric()) end) -local m_log10 = m.log10 registerscript("m_log10", function() return m_log10 (scannumeric()) end) -local m_log1p = m.log1p registerscript("m_log1p", function() return m_log1p (scannumeric()) end) -local m_log2 = m.log2 registerscript("m_log2", function() return m_log2 (scannumeric()) end) -local m_logb = m.logb registerscript("m_logb", function() return m_logb (scannumeric()) end) -local m_modf = m.modf registerscript("m_modf", function() return m_modf (scannumeric()) end) -local m_nearbyint = m.nearbyint registerscript("m_nearbyint", function() return m_nearbyint(scannumeric()) end) -local m_nextafter = m.nextafter registerscript("m_nextafter", function() return m_nextafter(scanpair ()) end) -local m_pow = m.pow registerscript("m_pow", function() return m_pow (scanpair ()) end) -local m_rad = m.rad registerscript("m_rad", function() return m_rad (scannumeric()) end) -local m_remainder = m.remainder registerscript("m_remainder", function() return m_remainder(scanpair ()) end) -local m_remquo = m.remquo registerscript("m_remquo", function() return m_remquo (scannumeric()) end) -local m_round = m.round registerscript("m_round", function() return m_round (scannumeric()) end) -local m_scalbn = m.scalbn registerscript("m_scalbn", function() return m_scalbn (scanpair ()) end) -local m_sin = m.sin registerscript("m_sin", function() return m_sin (scannumeric()) end) -local m_sinh = m.sinh registerscript("m_sinh", function() return m_sinh (scannumeric()) end) -local m_sqrt = m.sqrt registerscript("m_sqrt", function() return m_sqrt (scannumeric()) end) -local m_tan = m.tan registerscript("m_tan", function() return m_tan (scannumeric()) end) -local m_tanh = m.tanh registerscript("m_tanh", function() return m_tanh (scannumeric()) end) -local m_tgamma = m.tgamma registerscript("m_tgamma", function() return m_tgamma (scannumeric()) end) -local m_trunc = m.trunc registerscript("m_trunc", function() return m_trunc (scannumeric()) end) -local m_y0 = m.y0 registerscript("m_y0", function() return m_y0 (scannumeric()) end) -local m_y1 = m.y1 registerscript("m_y1", function() return m_y1 (scannumeric()) end) -local m_yn = m.yn registerscript("m_yn", function() return m_yn (scanpair ()) end) - -if not (c and c.sin) then - return -end - -local c_topair = c.topair -local c_new = c.new - -local c_sin = c.sin registerscript("c_sin", function() return mppair(c_topair(c_sin (c_new(scanpair())))) end) -local c_cos = c.cos registerscript("c_cos", function() return mppair(c_topair(c_cos (c_new(scanpair())))) end) -local c_tan = c.tan registerscript("c_tan", function() return mppair(c_topair(c_tan (c_new(scanpair())))) end) -local c_sinh = c.sinh registerscript("c_sinh", function() return mppair(c_topair(c_sinh (c_new(scanpair())))) end) -local c_cosh = c.cosh registerscript("c_cosh", function() return mppair(c_topair(c_cosh (c_new(scanpair())))) end) -local c_tanh = c.tanh registerscript("c_tanh", function() return mppair(c_topair(c_tanh (c_new(scanpair())))) end) - -local c_asin = c.asin registerscript("c_asin", function() return mppair(c_topair(c_sin (c_new(scanpair())))) end) -local c_acos = c.acos registerscript("c_acos", function() return mppair(c_topair(c_cos (c_new(scanpair())))) end) -local c_atan = c.atan registerscript("c_atan", function() return mppair(c_topair(c_tan (c_new(scanpair())))) end) -local c_asinh = c.asinh registerscript("c_asinh", function() return mppair(c_topair(c_sinh (c_new(scanpair())))) end) -local c_acosh = c.acosh registerscript("c_acosh", function() return mppair(c_topair(c_cosh (c_new(scanpair())))) end) -local c_atanh = c.atanh registerscript("c_atanh", function() return mppair(c_topair(c_tanh (c_new(scanpair())))) end) - -local c_sqrt = c.sqrt registerscript("c_sqrt", function() return mppair(c_topair(c_sqrt (c_new(scanpair())))) end) -local c_abs = c.abs registerscript("c_abs", function() return c_topair(c_abs (c_new(scanpair()))) end) -local c_arg = c.arg registerscript("c_arg", function() return c_topair(c_arg (c_new(scanpair()))) end) -local c_conj = c.conj registerscript("c_conj", function() return mppair(c_topair(c_conj (c_new(scanpair())))) end) -local c_exp = c.exp registerscript("c_exp", function() return mppair(c_topair(c_exp (c_new(scanpair())))) end) -local c_log = c.log registerscript("c_log", function() return mppair(c_topair(c_log (c_new(scanpair())))) end) -local c_proj = c.proj registerscript("c_proj", function() return mppair(c_topair(c_proj (c_new(scanpair())))) end) - -local c_erf = c.erf registerscript("c_erf", function() return mppair(c_topair(c_erf (c_new(scanpair())))) end) -local c_erfc = c.erfc registerscript("c_erfc", function() return mppair(c_topair(c_erfc (c_new(scanpair())))) end) -local c_erfcx = c.erfcx registerscript("c_erfcx", function() return mppair(c_topair(c_erfcx (c_new(scanpair())))) end) -local c_erfi = c.erfi registerscript("c_erfi", function() return mppair(c_topair(c_erfi (c_new(scanpair())))) end) -local c_dawson = c.dawson registerscript("c_dawson", function() return mppair(c_topair(c_dawson(c_new(scanpair())))) end) - -local c_voigt = c.voigt -local c_voigt_hwhm = c.voigt_hwhm - -registerscript("c_voigt", function() - return mppair(c_topair(c_voigt(c_new(scanpair()),c_new(scanpair()),c_new(scanpair())))) -end) - -registerscript("c_voigt_hwhm", function() - return mppair(c_topair(c_voigt_hwhm(c_new(scanpair()),c_new(scanpair())))) -end) - -local c_pow = c.pow registerscript("c_pow", function() return mppair(c_topair(c_pow(c_new(scanpair()),c_new(scanpair())))) end) -local c_add = c.add registerscript("c_add", function() return mppair(c_topair(c_add(c_new(scanpair()),c_new(scanpair())))) end) -local c_sub = c.sub registerscript("c_sub", function() return mppair(c_topair(c_sub(c_new(scanpair()),c_new(scanpair())))) end) -local c_mul = c.mul registerscript("c_mul", function() return mppair(c_topair(c_mul(c_new(scanpair()),c_new(scanpair())))) end) -local c_div = c.div registerscript("c_div", function() return mppair(c_topair(c_div(c_new(scanpair()),c_new(scanpair())))) end) - -local c_imag = c.imag registerscript("c_imag", function() return c_topair(c_imag(c_new(scanpair()))) end) -local c_real = c.real registerscript("c_real", function() return c_topair(c_real(c_new(scanpair()))) end) -local c_neg = c.neg registerscript("c_new", function() return c_topair(c_neg (c_new(scanpair()))) end) diff --git a/tex/context/base/mkiv/mlib-mpf.lua b/tex/context/base/mkiv/mlib-mpf.lua new file mode 100644 index 000000000..83a585850 --- /dev/null +++ b/tex/context/base/mkiv/mlib-mpf.lua @@ -0,0 +1,1245 @@ +if not modules then modules = { } end modules ['mlib-mpf'] = { + 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", +} + +-- moved from mlib-lua: + +local type, tostring, tonumber, select, loadstring = type, tostring, tonumber, select, loadstring +local find, match, gsub, gmatch = string.find, string.match, string.gsub, string.gmatch +local concat = table.concat + +local formatters = string.formatters +local lpegmatch = lpeg.match +local lpegpatterns = lpeg.patterns + +local P, S, Ct, Cs, Cc, C = lpeg.P, lpeg.S, lpeg.Ct, lpeg.Cs, lpeg.Cc, lpeg.C + +local report_luarun = logs.reporter("metapost","lua") +local report_script = logs.reporter("metapost","script") +local report_message = logs.reporter("metapost") + +local trace_luarun = false trackers.register("metapost.lua",function(v) trace_luarun = v end) + +local be_tolerant = true directives.register("metapost.lua.tolerant", function(v) be_tolerant = v end) + +local set = mp.set +local get = mp.get +local aux = mp.aux +local scan = mp.scan +local inject = mp.inject + +do + + local lmtxmode = CONTEXTLMTXMODE > 0 -- just a simple one, no need for separation + + -- serializers + + local f_integer = formatters["%i"] + local f_numeric = formatters["%F"] + + -- no %n as that can produce -e notation and that is not so nice for scaled butmaybe we + -- should then switch between ... i.e. make a push/pop for the formatters here ... not now. + + local f_integer = formatters["%i"] + local f_numeric = formatters["%F"] + local f_pair = formatters["(%F,%F)"] + local f_ctrl = formatters["(%F,%F) .. controls (%F,%F) and (%F,%F)"] + 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)"] + local f_ctrl_pt = formatters["(%p,%p) .. controls (%p,%p) and (%p,%p)"] + local f_triplet_pt = formatters["(%p,%p,%p)"] + local f_quadruple_pt = formatters["(%p,%p,%p,%p)"] + + local r = P('%') / "percent" + + P('"') / "dquote" + + P('\n') / "crlf" + -- + P(' ') / "space" + local a = Cc("&") + local q = Cc('"') + local p = Cs(q * (r * a)^-1 * (a * r * (P(-1) + a) + P(1))^0 * q) + + mp.cleaned = function(s) return lpegmatch(p,s) or s end + + -- management + + -- sometimes we gain (e.g. .5 sec on the sync test) + + local cache = table.makeweak() + + local runscripts = { } + local runnames = { } + local nofscripts = 0 + + function metapost.registerscript(name,f) + nofscripts = nofscripts + 1 + if f then + runscripts[nofscripts] = f + runnames[name] = nofscripts + else + runscripts[nofscripts] = name + end + return nofscripts + end + + function metapost.scriptindex(name) + return runnames[name] or 0 + end + + -- The gbuffer sharing and such is not really needed now but make a dent when + -- we have a high volume of simpel calls (loops) so we keep it around for a + -- while. + + local nesting = 0 + local runs = 0 + local gbuffer = { } + local buffer = gbuffer + local n = 0 + + local function mpdirect1(a) + n = n + 1 buffer[n] = a + end + local function mpdirect2(a,b) + n = n + 1 buffer[n] = a + n = n + 1 buffer[n] = b + end + local function mpdirect3(a,b,c) + n = n + 1 buffer[n] = a + n = n + 1 buffer[n] = b + n = n + 1 buffer[n] = c + end + local function mpdirect4(a,b,c,d) + n = n + 1 buffer[n] = a + n = n + 1 buffer[n] = b + n = n + 1 buffer[n] = c + n = n + 1 buffer[n] = d + end + local function mpdirect5(a,b,c,d,e) + n = n + 1 buffer[n] = a + n = n + 1 buffer[n] = b + n = n + 1 buffer[n] = c + n = n + 1 buffer[n] = d + n = n + 1 buffer[n] = e + end + + local function mpflush(separator) + buffer[1] = concat(buffer,separator or "",1,n) + n = 1 + end + + function metapost.runscript(code) + nesting = nesting + 1 + runs = runs + 1 + + local index = type(code) == "number" + local f + local result + + if index then + f = runscripts[code] + if not f then + report_luarun("%i: bad index: %s",nesting,code) + elseif trace_luarun then + report_luarun("%i: index: %i",nesting,code) + end + else + if trace_luarun then + report_luarun("%i: code: %s",nesting,code) + end + f = cache[code] + if not f then + f = loadstring("return " .. code) + if f then + cache[code] = f + elseif be_tolerant then + f = loadstring(code) + if f then + cache[code] = f + end + end + end + end + + -- returning nil is more efficient and a signal not to scan in mp + + if f then + + local lbuffer, ln + + if nesting == 1 then + buffer = gbuffer + n = 0 + else + lbuffer = buffer + ln = n + buffer = { } + n = 0 + end + + result = f() + if result then + local t = type(result) + if lmtxmode then + -- we can consider to use the injector for tables but then we need to + -- check of concatination is expected so best keep this! + if t == "number" or t == "boolean" then + -- native types + elseif t == "string" or t == "table" then + -- (concatenated) passed to scantokens + else + -- scantokens + result = tostring(result) + end + else + if t == "number" then + result = f_numeric(result) + elseif t == "table" then + result = concat(result) -- no spaces here + else + result = tostring(result) + end + end + if trace_luarun then + report_luarun("%i: %s result: %s",nesting,t,result) + end + elseif n == 0 then + -- result = "" + result = nil -- no scantokens done then + if trace_luarun then + report_luarun("%i: no buffered result",nesting) + end + elseif n == 1 then + result = buffer[1] + if trace_luarun then + report_luarun("%i: 1 buffered result: %s",nesting,result) + end + else + -- the space is why we sometimes have collectors + if nesting == 1 then + -- if we had no space we could pass result directly in lmtx + result = concat(buffer," ",1,n) + if n > 500 or #result > 10000 then + gbuffer = { } -- newtable(20,0) + lbuffer = gbuffer + end + else + -- if we had no space we could pass result directly in lmtx + result = concat(buffer," ") + end + if trace_luarun then + report_luarun("%i: %i buffered results: %s",nesting,n,result) + end + end + + if nesting == 1 then + n = 0 + else + buffer = lbuffer + n = ln + end + + else + report_luarun("%i: no result, invalid code: %s",nesting,code) + result = "" + end + + nesting = nesting - 1 + + return result + end + + function metapost.nofscriptruns() + return runs + end + + -- writers + + local function mpp(value) + n = n + 1 + local t = type(value) + if t == "number" then + buffer[n] = f_numeric(value) + elseif t == "string" then + buffer[n] = value + elseif t == "table" then + if #t == 6 then + buffer[n] = "totransform(" .. concat(value,",") .. ")" + else + buffer[n] = "(" .. concat(value,",") .. ")" + end + else -- boolean or whatever + buffer[n] = tostring(value) + end + end + + local function mpprint(first,second,...) + if second == nil then + if first ~= nil then + mpp(first) + end + else + for i=1,select("#",first,second,...) do + local value = (select(i,first,second,...)) + if value ~= nil then + mpp(value) + end + end + end + end + + local function mpp(value) + n = n + 1 + local t = type(value) + if t == "number" then + buffer[n] = f_numeric(value) + elseif t == "string" then + buffer[n] = lpegmatch(p,value) + elseif t == "table" then + if #t > 4 then + buffer[n] = "" + else + buffer[n] = "(" .. concat(value,",") .. ")" + end + else -- boolean or whatever + buffer[n] = tostring(value) + end + end + + local function mpvprint(first,second,...) -- variable print + if second == nil then + if first ~= nil then + mpp(first) + end + else + for i=1,select("#",first,second,...) do + local value = (select(i,first,second,...)) + if value ~= nil then + mpp(value) + end + end + end + end + + local function mpstring(value) + n = n + 1 + buffer[n] = lpegmatch(p,value) + end + + local function mpboolean(b) + n = n + 1 + buffer[n] = b and "true" or "false" + end + + local function mpnumeric(f) + n = n + 1 + if not f or f == 0 then + buffer[n] = "0" + else + buffer[n] = f_numeric(f) + end + end + + local function mpinteger(i) + n = n + 1 + -- buffer[n] = i and f_integer(i) or "0" + buffer[n] = i or "0" + end + + local function mppoints(i) + n = n + 1 + if not i or i == 0 then + buffer[n] = "0pt" + else + buffer[n] = f_points(i) + end + end + + local function mppair(x,y) + n = n + 1 + if type(x) == "table" then + buffer[n] = f_pair(x[1],x[2]) + else + buffer[n] = f_pair(x,y) + end + end + + local function mppairpoints(x,y) + n = n + 1 + if type(x) == "table" then + buffer[n] = f_pair_pt(x[1],x[2]) + else + buffer[n] = f_pair_pt(x,y) + end + end + + local function mptriplet(x,y,z) + n = n + 1 + if type(x) == "table" then + buffer[n] = f_triplet(x[1],x[2],x[3]) + else + buffer[n] = f_triplet(x,y,z) + end + end + + local function mptripletpoints(x,y,z) + n = n + 1 + if type(x) == "table" then + buffer[n] = f_triplet_pt(x[1],x[2],x[3]) + else + buffer[n] = f_triplet_pt(x,y,z) + end + end + + local function mpquadruple(w,x,y,z) + n = n + 1 + if type(w) == "table" then + buffer[n] = f_quadruple(w[1],w[2],w[3],w[4]) + else + buffer[n] = f_quadruple(w,x,y,z) + end + end + + local function mpquadruplepoints(w,x,y,z) + n = n + 1 + if type(w) == "table" then + buffer[n] = f_quadruple_pt(w[1],w[2],w[3],w[4]) + else + buffer[n] = f_quadruple_pt(w,x,y,z) + end + end + + local function mptransform(x,y,xx,xy,yx,yy) + n = n + 1 + if type(x) == "table" then + buffer[n] = f_transform(x[1],x[2],x[3],x[4],x[5],x[6]) + else + buffer[n] = f_transform(x,y,xx,xy,yx,yy) + end + end + + local function mpcolor(c,m,y,k) + n = n + 1 + if type(c) == "table" then + local l = #c + if l == 4 then + buffer[n] = f_quadruple(c[1],c[2],c[3],c[4]) + elseif l == 3 then + buffer[n] = f_triplet(c[1],c[2],c[3]) + else + buffer[n] = f_numeric(c[1]) + end + else + if k then + buffer[n] = f_quadruple(c,m,y,k) + elseif y then + buffer[n] = f_triplet(c,m,y) + else + buffer[n] = f_numeric(c) + end + end + end + + -- we have three kind of connectors: + -- + -- .. ... -- (true) + + local function mp_path(f2,f6,t,connector,cycle) + if type(t) == "table" then + local tn = #t + if tn == 1 then + local t1 = t[1] + 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 = ".." + elseif connector == false then + connector = "--" + end + if cycle == nil then + cycle = t.cycle + if cycle == nil then + cycle = true + end + end + local six = connector == ".." -- otherwise we use whatever gets asked for + local controls = connector -- whatever + 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 = ".." + else + buffer[n] = f2(a[1],a[2]) + controls = connector + end + for i=2,tn-1 do + a = b + b = t[i+1] + n = n + 1 + buffer[n] = connector + 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 = ".." + else + buffer[n] = f2(a[1],a[2]) + controls = connector + end + end + n = n + 1 + buffer[n] = connector + a = b + b = t[1] + n = n + 1 + if cycle then + 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 = ".." + else + buffer[n] = f2(a[1],a[2]) + controls = connector + end + n = n + 1 + buffer[n] = connector + n = n + 1 + buffer[n] = "cycle" + else + buffer[n] = f2(a[1],a[2]) + end + n = n + 1 + buffer[n] = ")" + end + end + end + + local function mppath(...) + mp_path(f_pair,f_ctrl,...) + end + + local function mppathpoints(...) + mp_path(f_pair_pt,f_ctrl_pt,...) + end + + local function mpsize(t) + n = n + 1 + buffer[n] = type(t) == "table" and f_numeric(#t) or "0" + end + + local replacer = lpeg.replacer("@","%%") + + local function mpfprint(fmt,...) + n = n + 1 + if not find(fmt,"%",1,true) then + fmt = lpegmatch(replacer,fmt) + end + buffer[n] = formatters[fmt](...) + end + + local function mpquoted(fmt,s,...) + if s then + n = n + 1 + if not find(fmt,"%",1,true) then + fmt = lpegmatch(replacer,fmt) + end + -- buffer[n] = '"' .. formatters[fmt](s,...) .. '"' + buffer[n] = lpegmatch(p,formatters[fmt](s,...)) + elseif fmt then + n = n + 1 + -- buffer[n] = '"' .. fmt .. '"' + buffer[n] = lpegmatch(p,fmt) + else + -- something is wrong + end + end + + aux.direct = mpdirect1 + aux.direct1 = mpdirect1 + aux.direct2 = mpdirect2 + aux.direct3 = mpdirect3 + aux.direct4 = mpdirect4 + aux.flush = mpflush + + aux.print = mpprint + aux.vprint = mpvprint + aux.boolean = mpboolean + aux.string = mpstring + aux.numeric = mpnumeric + aux.number = mpnumeric + aux.integer = mpinteger + aux.points = mppoints + aux.pair = mppair + aux.pairpoints = mppairpoints + aux.triplet = mptriplet + aux.tripletpoints = mptripletpoints + aux.quadruple = mpquadruple + aux.quadruplepoints = mpquadruplepoints + aux.path = mppath + aux.pathpoints = mppathpoints + aux.size = mpsize + aux.fprint = mpfprint + aux.quoted = mpquoted + aux.transform = mptransform + aux.color = mpcolor + + -- for the moment + + local function mpdraw(lines,list) -- n * 4 + if list then + local c = #lines + for i=1,c do + local ci = lines[i] + local ni = #ci + n = n + 1 buffer[n] = i < c and "d(" or "D(" + for j=1,ni,2 do + local l = j + 1 + n = n + 1 buffer[n] = ci[j] + n = n + 1 buffer[n] = "," + n = n + 1 buffer[n] = ci[l] + n = n + 1 buffer[n] = l < ni and ")--(" or ");" + end + end + else + local l = #lines + local m = l - 4 + for i=1,l,4 do + n = n + 1 buffer[n] = i < m and "d(" or "D(" + n = n + 1 buffer[n] = lines[i] + n = n + 1 buffer[n] = "," + n = n + 1 buffer[n] = lines[i+1] + n = n + 1 buffer[n] = ")--(" + n = n + 1 buffer[n] = lines[i+2] + n = n + 1 buffer[n] = "," + n = n + 1 buffer[n] = lines[i+3] + n = n + 1 buffer[n] = ");" + end + end + end + + local function mpfill(lines,list) + if list then + local c = #lines + for i=1,c do + local ci = lines[i] + local ni = #ci + n = n + 1 buffer[n] = i < c and "f(" or "F(" + for j=1,ni,2 do + local l = j + 1 + n = n + 1 buffer[n] = ci[j] + n = n + 1 buffer[n] = "," + n = n + 1 buffer[n] = ci[l] + n = n + 1 buffer[n] = l < ni and ")--(" or ")--C;" + end + end + else + local l = #lines + local m = l - 4 + for i=1,l,4 do + n = n + 1 buffer[n] = i < m and "f(" or "F(" + n = n + 1 buffer[n] = lines[i] + n = n + 1 buffer[n] = "," + n = n + 1 buffer[n] = lines[i+1] + n = n + 1 buffer[n] = ")--(" + n = n + 1 buffer[n] = lines[i+2] + n = n + 1 buffer[n] = "," + n = n + 1 buffer[n] = lines[i+3] + n = n + 1 buffer[n] = ")--C;" + end + end + end + + aux.draw = mpdraw + aux.fill = mpfill + + for k, v in next, aux do mp[k] = v end + +end + +do + + -- Another experimental feature: + + local mpnumeric = mp.numeric + local scanstring = scan.string + local scriptindex = metapost.scriptindex + + function mp.mf_script_index(name) + local index = scriptindex(name) + -- report_script("method %i, name %a, index %i",1,name,index) + mpnumeric(index) + end + + -- once bootstrapped ... (needs pushed mpx instances) + + metapost.registerscript("scriptindex",function() + local name = scanstring() + local index = scriptindex(name) + -- report_script("method %i, name %a, index %i",2,name,index) + mpnumeric(index) + end) + +end + +-- the next will move to mlib-lmp.lua + +do + + local mpnamedcolor = attributes.colors.mpnamedcolor + local mpprint = aux.print + local scanstring = scan.string + + mp.mf_named_color = function(str) + mpprint(mpnamedcolor(str)) + end + + -- todo: we can inject but currently we always get a string back so then + -- we need to deal with it upstream in the color module ... not now + + metapost.registerscript("namedcolor",function() + mpprint(mpnamedcolor(scanstring())) + end) + +end + +function mp.n(t) -- used ? + return type(t) == "table" and #t or 0 +end + +do + + -- experiment: names can change + + local mppath = aux.path + local mpsize = aux.size + + local whitespace = lpegpatterns.whitespace + local newline = lpegpatterns.newline + local setsep = newline^2 + local comment = (S("#%") + P("--")) * (1-newline)^0 * (whitespace - setsep)^0 + local value = (1-whitespace)^1 / tonumber + local entry = Ct( value * whitespace * value) + local set = Ct((entry * (whitespace-setsep)^0 * comment^0)^1) + local series = Ct((set * whitespace^0)^1) + + local pattern = whitespace^0 * series + + local datasets = { } + mp.datasets = datasets + + function mp.dataset(str) + return lpegmatch(pattern,str) + end + + function datasets.load(tag,filename) + if not filename then + tag, filename = file.basename(tag), tag + end + local data = lpegmatch(pattern,io.loaddata(filename) or "") + datasets[tag] = { + data = data, + line = function(n) mppath(data[n or 1]) end, + size = function() mpsize(data) end, + } + end + + table.setmetatablecall(datasets,function(t,k,f,...) + local d = datasets[k] + local t = type(d) + if t == "table" then + d = d[f] + if type(d) == "function" then + d(...) + else + mpvprint(...) + end + elseif t == "function" then + d(f,...) + end + end) + +end + +-- \startluacode +-- local str = [[ +-- 10 20 20 20 +-- 30 40 40 60 +-- 50 10 +-- +-- 10 10 20 30 +-- 30 50 40 50 +-- 50 20 -- the last one +-- +-- 10 20 % comment +-- 20 10 +-- 30 40 # comment +-- 40 20 +-- 50 10 +-- ]] +-- +-- MP.myset = mp.dataset(str) +-- +-- inspect(MP.myset) +-- \stopluacode +-- +-- \startMPpage +-- color c[] ; c[1] := red ; c[2] := green ; c[3] := blue ; +-- for i=1 upto lua("mp.print(mp.n(MP.myset))") : +-- draw lua("mp.path(MP.myset[" & decimal i & "])") withcolor c[i] ; +-- endfor ; +-- \stopMPpage + +-- texts: + +do + + local mptriplet = mp.triplet + + local bpfactor = number.dimenfactors.bp + local textexts = nil + local mptriplet = mp.triplet + local nbdimensions = nodes.boxes.dimensions + + function mp.mf_tt_initialize(tt) + textexts = tt + end + + function mp.mf_tt_dimensions(n) + local box = textexts and textexts[n] + if box then + -- could be made faster with nuts but not critical + mptriplet(box.width*bpfactor,box.height*bpfactor,box.depth*bpfactor) + else + mptriplet(0,0,0) + end + end + + function mp.mf_tb_dimensions(category,name) + local w, h, d = nbdimensions(category,name) + mptriplet(w*bpfactor,h*bpfactor,d*bpfactor) + end + + function mp.report(a,b,c,...) + if c then + report_message("%s : %s",a,formatters[(gsub(b,"@","%%"))](c,...)) + elseif b then + report_message("%s : %s",a,b) + elseif a then + report_message("%s : %s","message",a) + end + end + +end + +do + + local mpprint = aux.print + local modes = tex.modes + local systemmodes = tex.systemmodes + + function mp.mode(s) + mpprint(modes[s] and true or false) + end + + function mp.systemmode(s) + mpprint(systemmodes[s] and true or false) + end + + mp.processingmode = mp.mode + +end + +-- for alan's nodes: + +do + + local mpprint = aux.print + local mpquoted = aux.quoted + + function mp.isarray(str) + mpprint(find(str,"%d") and true or false) + end + + function mp.prefix(str) + mpquoted(match(str,"^(.-)[%d%[]") or str) + end + + -- function mp.dimension(str) + -- local n = 0 + -- for s in gmatch(str,"%[?%-?%d+%]?") do --todo: lpeg + -- n = n + 1 + -- end + -- mpprint(n) + -- end + + mp.dimension = lpeg.counter(P("[") * lpegpatterns.integer * P("]") + lpegpatterns.integer,mpprint) + + -- faster and okay as we don't have many variables but probably only + -- basename makes sense and even then it's not called that often + + -- local hash = table.setmetatableindex(function(t,k) + -- local v = find(k,"%d") and true or false + -- t[k] = v + -- return v + -- end) + -- + -- function mp.isarray(str) + -- mpprint(hash[str]) + -- end + -- + -- local hash = table.setmetatableindex(function(t,k) + -- local v = '"' .. (match(k,"^(.-)%d") or k) .. '"' + -- t[k] = v + -- return v + -- end) + -- + -- function mp.prefix(str) + -- mpprint(hash[str]) + -- end + +end + +do + + local getmacro = tex.getmacro + local getdimen = tex.getdimen + local getcount = tex.getcount + local gettoks = tex.gettoks + local setmacro = tex.setmacro + local setdimen = tex.setdimen + local setcount = tex.setcount + local settoks = tex.settoks + + local mpprint = mp.print + local mpquoted = mp.quoted + + local bpfactor = number.dimenfactors.bp + + -- more helpers + + local function getmacro(k) mpprint (getmacro(k)) end + local function getdimen(k) mpprint (getdimen(k)*bpfactor) end + local function getcount(k) mpprint (getcount(k)) end + local function gettoks (k) mpquoted(gettoks (k)) end + + local function setmacro(k,v) setmacro(k,v) end + local function setdimen(k,v) setdimen(k,v/bpfactor) end + local function setcount(k,v) setcount(k,v) end + local function settoks (k,v) settoks (k,v) end + + -- def foo = lua.mp.foo ... enddef ; % loops due to foo in suffix + + mp._get_macro_ = getmacro mp.getmacro = getmacro + mp._get_dimen_ = getdimen mp.getdimen = getdimen + mp._get_count_ = getcount mp.getcount = getcount + mp._get_toks_ = gettoks mp.gettoks = gettoks + + mp._set_macro_ = setmacro mp.setmacro = setmacro + mp._set_dimen_ = setdimen mp.setdimen = setdimen + mp._set_count_ = setcount mp.setcount = setcount + mp._set_toks_ = settoks mp.settoks = settoks + +end + +-- position fun + +do + + local mpprint = mp.print + local mpfprint = mp.fprint + local mpquoted = mp.quoted + local jobpositions = job.positions + local getwhd = jobpositions.whd + local getxy = jobpositions.xy + local getposition = jobpositions.position + local getpage = jobpositions.page + local getregion = jobpositions.region + local getmacro = tokens.getters.macro + + function mp.positionpath(name) + local w, h, d = getwhd(name) + if w then + mpfprint("((%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle)",0,-d,w,-d,w,h,0,h) + else + mpprint("(origin--cycle)") + end + end + + function mp.positioncurve(name) + local w, h, d = getwhd(name) + if w then + mpfprint("((%p,%p)..(%p,%p)..(%p,%p)..(%p,%p)..cycle)",0,-d,w,-d,w,h,0,h) + else + mpprint("(origin--cycle)") + end + end + + function mp.positionbox(name) + local p, x, y, w, h, d = getposition(name) + if p then + mpfprint("((%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle)",x,y-d,x+w,y-d,x+w,y+h,x,y+h) + else + mpprint("(%p,%p)",x,y) + end + end + + function mp.positionxy(name) + local x, y = getxy(name) + if x then + mpfprint("(%p,%p)",x,y) + else + mpprint("origin") + end + end + + function mp.positionpage(name) + mpfprint("%i",getpage(name) or 0) + end + + function mp.positionregion(name) + local r = getregion(name) + if r then + mpquoted(r) + else + mpquoted("unknown") + end + end + + function mp.positionwhd(name) + local w, h, d = getwhd(name) + if w then + mpfprint("(%p,%p,%p)",w,h,d) + else + mpprint("(0,0,0)") + end + end + + function mp.positionpxy(name) + local p, x, y = getposition(name) + if p then + mpfprint("(%p,%p,%p)",p,x,y) + else + mpprint("(0,0,0)") + end + end + + function mp.positionanchor() + mpquoted(getmacro("MPanchorid")) + end + +end + +do + + local mppair = mp.pair + + function mp.textextanchor(s) + local x, y = match(s,"tx_anchor=(%S+) (%S+)") -- todo: make an lpeg + if x and y then + x = tonumber(x) + y = tonumber(y) + end + mppair(x or 0,y or 0) + end + +end + +do + + local mpprint = mp.print + local mpquoted = mp.quoted + local getmacro = tokens.getters.macro + + function mp.texvar(name) + mpprint(getmacro(metapost.namespace .. name)) + end + + function mp.texstr(name) + mpquoted(getmacro(metapost.namespace .. name)) + end + +end + +do + + local mpprint = aux.print + local mpvprint = aux.vprint + + local hashes = { } + + function mp.newhash(name) + if name then + hashes[name] = { } + else + for i=1,#hashes+1 do + if not hashes[i] then + hashes[i] = { } + mpvprint(i) + return + end + end + end + end + + function mp.disposehash(n) + if tonumber(n) then + hashes[n] = false + else + hashes[n] = nil + end + end + + function mp.inhash(n,key) + local h = hashes[n] + mpvprint(h and h[key] and true or false) + end + + function mp.tohash(n,key,value) + local h = hashes[n] + if h then + if value == nil then + h[key] = true + else + h[key] = value + end + end + end + + function mp.fromhash(n,key) + local h = hashes[n] + mpvprint(h and h[key] or false) + end + + interfaces.implement { + name = "MPfromhash", + arguments = "2 strings", + actions = function(name,key) + local h = hashes[name] or hashes[tonumber(name)] + if h then + local v = h[key] or h[tonumber(key)] + if v then + context(v) + end + end + end + } + +end + +do + + -- a bit overkill: just a find(str,"mf_object=") can be enough + -- + -- todo : share with mlib-pps.lua metapost,isobject + + local mpboolean = aux.boolean + + local p1 = P("mf_object=") + local p2 = lpegpatterns.eol * p1 + local pattern = (1-p2)^0 * p2 + p1 + + function mp.isobject(str) + mpboolean(pattern and str ~= "" and lpegmatch(pattern,str)) + end + +end + +function mp.flatten(t) + local tn = #t + + local t1 = t[1] + local t2 = t[2] + local t3 = t[3] + local t4 = t[4] + + for i=1,tn-5,2 do + local t5 = t[i+4] + local t6 = t[i+5] + if t1 == t3 and t3 == t5 and ((t2 <= t4 and t4 <= t6) or (t6 <= t4 and t4 <= t2)) then + t[i+3] = t2 + t4 = t2 + t[i] = false + t[i+1] = false + elseif t2 == t4 and t4 == t6 and ((t1 <= t3 and t3 <= t5) or (t5 <= t3 and t3 <= t1)) then + t[i+2] = t1 + t3 = t1 + t[i] = false + t[i+1] = false + end + t1 = t3 + t2 = t4 + t3 = t5 + t4 = t6 + end + + -- remove duplicates + + local t1 = t[1] + local t2 = t[2] + for i=1,tn-2,2 do + local t3 = t[i+2] + local t4 = t[i+3] + if t1 == t3 and t2 == t4 then + t[i] = false + t[i+1] = false + end + t1 = t3 + t2 = t4 + end + + -- move coordinates + + local m = 0 + for i=1,tn,2 do + if t[i] then + m = m + 1 t[m] = t[i] + m = m + 1 t[m] = t[i+1] + end + end + + -- prune the table (not gc'd) + + for i=tn,m+1,-1 do + t[i] = nil + end + + -- safeguard so that we have at least one segment + + if m == 2 then + t[3] = t[1] + t[4] = t[2] + end + +end + +do + + -- if needed we can optimize the sub (cache last split) + + local utflen = utf.len + local utfsub = utf.sub + + function mp.utflen(s) + mpnumeric(utflen(s)) + end + + function mp.utfsub(s,f,t) + mpquoted(utfsub(s,f,t or f)) + end + +end + diff --git a/tex/context/base/mkiv/mlib-pps.lua b/tex/context/base/mkiv/mlib-pps.lua index 51491d513..e34bfe5f9 100644 --- a/tex/context/base/mkiv/mlib-pps.lua +++ b/tex/context/base/mkiv/mlib-pps.lua @@ -964,8 +964,8 @@ local tx_reset, tx_process do -- instance in a macro definition (rawtextext (pass back string)) ... of course one -- should use textext so this is just a catch. When not in lmtx it's never immediate. - local reported = false - local awayswrap = CONTEXTLMTXMODE <= 1 + local reported = false + local alwayswrap = false -- CONTEXTLMTXMODE <= 1 -- was always nil due to typo function metapost.maketext(s,mode) if not reported then diff --git a/tex/context/base/mkiv/mlib-ran.lmt b/tex/context/base/mkiv/mlib-ran.lmt new file mode 100644 index 000000000..cb8645e8d --- /dev/null +++ b/tex/context/base/mkiv/mlib-ran.lmt @@ -0,0 +1,237 @@ +if not modules then modules = { } end modules ['mlib-ran'] = { + 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", +} + +local next = next +local ceil, floor, random, sqrt, cos, sin, pi, max, min = math.ceil, math.floor, math.random, math.sqrt, math.cos, math.sin, math.pi, math.min, math.max +local remove = table.remove + +-- Below is a bit of rainy saturday afternoon hobyism, while listening to Judith +-- Owens redisCOVERed (came there via Leland Sklar who I have on a few live blurays; +-- and who is also on YT). (Also nice: https://www.youtube.com/watch?v=GXqasIRaxlA) + +-- When Aditya pointed me to an article on mazes I ended up at poison distributions +-- which to me looks nicer than what I normally do, fill a grid and then randomize +-- the resulting positions. With some hooks this can be used for interesting patterns +-- too. A few links: +-- +-- https://bost.ocks.org/mike/algorithms/#maze-generation +-- https://extremelearning.com.au/ +-- https://www.jasondavies.com/maps/random-points/ +-- http://devmag.org.za/2009/05/03/poisson-disk-sampling + +-- The next function is quite close to what us discribed in the poisson-disk-sampling +-- link mentioned before. One can either use a one dimensional grid array or a two +-- dimensional one. The example code uses some classes dealing with points. In the +-- process I added some more control. + +-- we could do without the samplepoints list + +local function poisson(width, height, mindist, newpointscount, initialx, initialy) + local starttime = os.clock() + local cellsize = mindist / sqrt(2) + local nofwidth = ceil(width // cellsize) + local nofheight = ceil(height // cellsize) + local grid = lua.newtable(nofwidth,0) -- table.setmetatableindex("table") + local firstx = initialx or random() * width + local firsty = initialy or random() * height + local firstpoint = { firstx, firsty, 1 } + -- local samplepoints = { firstpoint } + local processlist = { firstpoint } + local nofprocesslist = 1 + local nofsamplepoints = 1 + local twopi = 2 * pi + + for i=1,nofwidth do + local g = lua.newindex(nofheight,false) + grid[i] = g + end + + local x = floor(firstx // cellsize) + 1 -- lua indices + local y = floor(firsty // cellsize) + 1 -- lua indices + + x = max(1, min(x, width - 1)) + y = max(1, min(y, height - 1)) + + grid[x][y] = firstpoint + + -- The website shows graphic for this 5*5 grid snippet, if we use a one dimentional + -- array then we could have one loop; a first version used a metatable trick so we + -- had grid[i+gx][j+gy] but we no we also return the grid, so ... we now just check. + + -- There is no need for the samplepoints list as we can get that from the grid but + -- instead we can store the index with the grid. + + while nofprocesslist > 0 do + local point = remove(processlist,random(1,nofprocesslist)) + nofprocesslist = nofprocesslist - 1 + for i=1,newpointscount do -- we start at 1 + local radius = mindist * (random() + 1) + local angle = twopi * random() + local nx = point[1] + radius * cos(angle) + local ny = point[2] + radius * sin(angle) + if nx > 1 and ny > 1 and nx <= width and ny <= height then -- lua indices + local gx = floor(nx // cellsize) + local gy = floor(ny // cellsize) + -- the 5x5 cells around the point + for i=-2,2 do + for j=-2,2 do + local cell = grid[i + gx] + if cell then + cell = cell[j + gy] + if cell and sqrt((cell[1] - nx)^2 + (cell[2] - ny)^2) < mindist then + goto next + end + end + end + end + -- local newpoint = { nx, ny } + nofprocesslist = nofprocesslist + 1 + nofsamplepoints = nofsamplepoints + 1 + local newpoint = { nx, ny, nofsamplepoints } + processlist [nofprocesslist] = newpoint + -- samplepoints[nofsamplepoints] = newpoint + grid[gx][gy] = newpoint + end + ::next:: + end + end + + return { + count = nofsamplepoints, + -- points = samplepoints, + grid = grid, + time = os.clock() - starttime, + } +end + +-- For now: + +local randomizers = utilities.randomizers or { } +utilities.randomizers = randomizers +randomizers.poisson = poisson + +-- The MetaFun interface: + +local formatters = string.formatters +local concat = table.concat + +local f_macro = formatters["%s(%N,%N);"] + +local f_macros = { + [2] = formatters["%s(%N,%N);"], + [3] = formatters["%s(%N,%N,%i);"], + [4] = formatters["%s(%N,%N,%i,%i);"], +} + +function grid_to_mp(t,f,n) + local grid = t.grid + local count = t.count + local result = { } + local r = 0 + local macro = f or "draw" + local runner = f_macros[n or 2] or f_macros[2] + for i=1,#grid do + local g = grid[i] + if g then + for j=1,#g do + local v = g[j] + if v then + r = r + 1 + result[r] = runner(macro,v[1],v[2],v[3],count) + end + end + end + end + return concat(result, " ") +end + +local getparameter = metapost.getparameter + +local function lmt_poisson() + local initialx = getparameter { "initialx" } + local initialy = getparameter { "initialy" } + local width = getparameter { "width" } + local height = getparameter { "height" } + local distance = getparameter { "distance" } + local count = getparameter { "count" } + + local result = poisson ( + width, height, distance, count, + initialx > 0 and initialx or false, + initialy > 0 and initialy or false + ) + + if result then + logs.report("poisson","w=%N, h=%N, d=%N, c=%N, n=%i, runtime %.3f", + width, height, distance, count, result.count, result.time + ) + end + + return result +end + +function mp.lmt_poisson_generate() + local result = lmt_poisson() + if result then + return grid_to_mp ( + result, + getparameter { "macro" }, + getparameter { "arguments" } + ) + end +end + +-- -- some playing around showed no benefit +-- +-- function points_to_mp(t,f) +-- local points = t.points +-- local count = t.count +-- local result = { } +-- local r = 0 +-- local macro = f or "draw" +-- local runner = f_macros[n or 2] or f_macros[2] +-- for i=1,count do +-- local v = points[i] +-- r = r + 1 +-- result[r] = runner(macro,v[1],v[2],v[3],count) +-- end +-- return concat(result, " ") +-- end +-- +-- local result = false +-- local i, j, n = 0, 0, 0 +-- +-- function mp.lmt_poison_start() +-- result = lmt_poisson() +-- end +-- +-- function mp.lmt_poisson_stop() +-- result = false +-- end +-- +-- function mp.lmt_poisson_count() +-- return result and result.count or 0 +-- end +-- +-- function mp.lmt_poisson_get(i) +-- if result then +-- return mp.pair(result.points[i]) +-- end +-- end +-- +-- function mp.lmt_poisson_generate() +-- mp.lmt_poisson_start() +-- if result then +-- return grid_to_mp ( +-- result, +-- getparameter { "macro" }, +-- getparameter { "arguments" } +-- ) +-- end +-- mp.lmt_poisson_stop() +-- end diff --git a/tex/context/base/mkiv/mlib-ran.lua b/tex/context/base/mkiv/mlib-ran.lua deleted file mode 100644 index cb8645e8d..000000000 --- a/tex/context/base/mkiv/mlib-ran.lua +++ /dev/null @@ -1,237 +0,0 @@ -if not modules then modules = { } end modules ['mlib-ran'] = { - 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", -} - -local next = next -local ceil, floor, random, sqrt, cos, sin, pi, max, min = math.ceil, math.floor, math.random, math.sqrt, math.cos, math.sin, math.pi, math.min, math.max -local remove = table.remove - --- Below is a bit of rainy saturday afternoon hobyism, while listening to Judith --- Owens redisCOVERed (came there via Leland Sklar who I have on a few live blurays; --- and who is also on YT). (Also nice: https://www.youtube.com/watch?v=GXqasIRaxlA) - --- When Aditya pointed me to an article on mazes I ended up at poison distributions --- which to me looks nicer than what I normally do, fill a grid and then randomize --- the resulting positions. With some hooks this can be used for interesting patterns --- too. A few links: --- --- https://bost.ocks.org/mike/algorithms/#maze-generation --- https://extremelearning.com.au/ --- https://www.jasondavies.com/maps/random-points/ --- http://devmag.org.za/2009/05/03/poisson-disk-sampling - --- The next function is quite close to what us discribed in the poisson-disk-sampling --- link mentioned before. One can either use a one dimensional grid array or a two --- dimensional one. The example code uses some classes dealing with points. In the --- process I added some more control. - --- we could do without the samplepoints list - -local function poisson(width, height, mindist, newpointscount, initialx, initialy) - local starttime = os.clock() - local cellsize = mindist / sqrt(2) - local nofwidth = ceil(width // cellsize) - local nofheight = ceil(height // cellsize) - local grid = lua.newtable(nofwidth,0) -- table.setmetatableindex("table") - local firstx = initialx or random() * width - local firsty = initialy or random() * height - local firstpoint = { firstx, firsty, 1 } - -- local samplepoints = { firstpoint } - local processlist = { firstpoint } - local nofprocesslist = 1 - local nofsamplepoints = 1 - local twopi = 2 * pi - - for i=1,nofwidth do - local g = lua.newindex(nofheight,false) - grid[i] = g - end - - local x = floor(firstx // cellsize) + 1 -- lua indices - local y = floor(firsty // cellsize) + 1 -- lua indices - - x = max(1, min(x, width - 1)) - y = max(1, min(y, height - 1)) - - grid[x][y] = firstpoint - - -- The website shows graphic for this 5*5 grid snippet, if we use a one dimentional - -- array then we could have one loop; a first version used a metatable trick so we - -- had grid[i+gx][j+gy] but we no we also return the grid, so ... we now just check. - - -- There is no need for the samplepoints list as we can get that from the grid but - -- instead we can store the index with the grid. - - while nofprocesslist > 0 do - local point = remove(processlist,random(1,nofprocesslist)) - nofprocesslist = nofprocesslist - 1 - for i=1,newpointscount do -- we start at 1 - local radius = mindist * (random() + 1) - local angle = twopi * random() - local nx = point[1] + radius * cos(angle) - local ny = point[2] + radius * sin(angle) - if nx > 1 and ny > 1 and nx <= width and ny <= height then -- lua indices - local gx = floor(nx // cellsize) - local gy = floor(ny // cellsize) - -- the 5x5 cells around the point - for i=-2,2 do - for j=-2,2 do - local cell = grid[i + gx] - if cell then - cell = cell[j + gy] - if cell and sqrt((cell[1] - nx)^2 + (cell[2] - ny)^2) < mindist then - goto next - end - end - end - end - -- local newpoint = { nx, ny } - nofprocesslist = nofprocesslist + 1 - nofsamplepoints = nofsamplepoints + 1 - local newpoint = { nx, ny, nofsamplepoints } - processlist [nofprocesslist] = newpoint - -- samplepoints[nofsamplepoints] = newpoint - grid[gx][gy] = newpoint - end - ::next:: - end - end - - return { - count = nofsamplepoints, - -- points = samplepoints, - grid = grid, - time = os.clock() - starttime, - } -end - --- For now: - -local randomizers = utilities.randomizers or { } -utilities.randomizers = randomizers -randomizers.poisson = poisson - --- The MetaFun interface: - -local formatters = string.formatters -local concat = table.concat - -local f_macro = formatters["%s(%N,%N);"] - -local f_macros = { - [2] = formatters["%s(%N,%N);"], - [3] = formatters["%s(%N,%N,%i);"], - [4] = formatters["%s(%N,%N,%i,%i);"], -} - -function grid_to_mp(t,f,n) - local grid = t.grid - local count = t.count - local result = { } - local r = 0 - local macro = f or "draw" - local runner = f_macros[n or 2] or f_macros[2] - for i=1,#grid do - local g = grid[i] - if g then - for j=1,#g do - local v = g[j] - if v then - r = r + 1 - result[r] = runner(macro,v[1],v[2],v[3],count) - end - end - end - end - return concat(result, " ") -end - -local getparameter = metapost.getparameter - -local function lmt_poisson() - local initialx = getparameter { "initialx" } - local initialy = getparameter { "initialy" } - local width = getparameter { "width" } - local height = getparameter { "height" } - local distance = getparameter { "distance" } - local count = getparameter { "count" } - - local result = poisson ( - width, height, distance, count, - initialx > 0 and initialx or false, - initialy > 0 and initialy or false - ) - - if result then - logs.report("poisson","w=%N, h=%N, d=%N, c=%N, n=%i, runtime %.3f", - width, height, distance, count, result.count, result.time - ) - end - - return result -end - -function mp.lmt_poisson_generate() - local result = lmt_poisson() - if result then - return grid_to_mp ( - result, - getparameter { "macro" }, - getparameter { "arguments" } - ) - end -end - --- -- some playing around showed no benefit --- --- function points_to_mp(t,f) --- local points = t.points --- local count = t.count --- local result = { } --- local r = 0 --- local macro = f or "draw" --- local runner = f_macros[n or 2] or f_macros[2] --- for i=1,count do --- local v = points[i] --- r = r + 1 --- result[r] = runner(macro,v[1],v[2],v[3],count) --- end --- return concat(result, " ") --- end --- --- local result = false --- local i, j, n = 0, 0, 0 --- --- function mp.lmt_poison_start() --- result = lmt_poisson() --- end --- --- function mp.lmt_poisson_stop() --- result = false --- end --- --- function mp.lmt_poisson_count() --- return result and result.count or 0 --- end --- --- function mp.lmt_poisson_get(i) --- if result then --- return mp.pair(result.points[i]) --- end --- end --- --- function mp.lmt_poisson_generate() --- mp.lmt_poisson_start() --- if result then --- return grid_to_mp ( --- result, --- getparameter { "macro" }, --- getparameter { "arguments" } --- ) --- end --- mp.lmt_poisson_stop() --- end diff --git a/tex/context/base/mkiv/mlib-run.lua b/tex/context/base/mkiv/mlib-run.lua index 11bd1a9be..6962340c5 100644 --- a/tex/context/base/mkiv/mlib-run.lua +++ b/tex/context/base/mkiv/mlib-run.lua @@ -123,41 +123,7 @@ local mpbasepath = lpeg.instringchecker(P("/metapost/") * (P("context") + P("bas local realtimelogging do - local finders = { } - mplib.finders = finders -- also used in meta-lua.lua - - local new_instance = mplib.new - - local function validftype(ftype) - if ftype == "mp" then - return "mp" - else - return nil - end - end - - finders.file = function(specification,name,mode,ftype) - return resolvers.findfile(name,validftype(ftype)) - end - - -- this will be redone in lmtx - - local function i_finder(name,mode,ftype) -- fake message for mpost.map and metafun.mpvi - local specification = url.hashed(name) - local finder = finders[specification.scheme] or finders.file - local found = finder(specification,name,mode,validftype(ftype)) - return found - end - - local function o_finder(name,mode,ftype) - return name - end - - o_finder = sandbox.register(o_finder,sandbox.filehandlerone,"mplib output finder") - - local function finder(name,mode,ftype) - return (mode == "w" and o_finder or i_finder)(name,mode,validftype(ftype)) - end + -- begin of logger code local report_logger = logs.reporter("metapost log") local report_error = logs.reporter("metapost error") @@ -189,121 +155,144 @@ local realtimelogging do end end - -- experiment, todo: per instance, just a push / pop ? + -- end of logger code - local findtexfile = resolvers.findtexfile - local opentexfile = resolvers.opentexfile - local splitlines = string.splitlines + -- begin of logger code - local function writetoterminal(terminaldata,maxterm,d) - local t = type(d) - local n = 0 - if t == "string" then - d = splitlines(d) - n = #d - for i=1,#d do - maxterm = maxterm + 1 - terminaldata[maxterm] = d[i] - end - elseif t == "table" then - for i=1,#d do - local l = d[i] - if find(l,"[\n\r]") then - local s = splitlines(l) - local m = #s - for i=1,m do - maxterm = maxterm + 1 - terminaldata[maxterm] = s[i] - end - n = n + m - else - maxterm = maxterm + 1 - terminaldata[maxterm] = d[i] - n = 1 - end - end - end - if trace_terminal then - report_metapost("writing %i lines, in cache %s",n,maxterm) - end - return maxterm - end + local finders = { } + mplib.finders = finders -- also used in meta-lua.lua - local function readfromterminal(terminaldata,maxterm,nowterm) - if nowterm >= maxterm then - terminaldata[nowterm] = false - maxterm = 0 - nowterm = 0 - if trace_terminal then - report_metapost("resetting, maxcache %i",#terminaldata) - end - return maxterm, nowterm, nil + local new_instance = mplib.new + + local function validftype(ftype) + if ftype == "mp" then + return "mp" else - if nowterm > 0 then - terminaldata[nowterm] = false - end - nowterm = nowterm + 1 - local s = terminaldata[nowterm] - if trace_terminal then - report_metapost("reading line %i: %s",nowterm,s) - end - return maxterm, nowterm, s + return nil end end - local function fileopener() + finders.file = function(specification,name,mode,ftype) + return resolvers.findfile(name,validftype(ftype)) + end - -- these can go into the table itself + -- end of finder code - local terminaldata = { } - local maxterm = 0 - local nowterm = 0 + if CONTEXTLMTXMODE > 0 then - local terminal = { - name = "terminal", - close = function() - -- terminal = { } - -- maxterm = 0 - -- nowterm = 0 - end, - reader = function() - local line - maxterm, nowterm, line = readfromterminal(terminaldata,maxterm,nowterm) - return line - end, - writer = function(d) - maxterm = writetoterminal(terminaldata,maxterm,d) - end, - } + local findtexfile = resolvers.findtexfile + local opentexfile = resolvers.opentexfile + local splitlines = string.splitlines + + local function writetoterminal(terminaldata,maxterm,d) + local t = type(d) + local n = 0 + if t == "string" then + d = splitlines(d) + n = #d + for i=1,#d do + maxterm = maxterm + 1 + terminaldata[maxterm] = d[i] + end + elseif t == "table" then + for i=1,#d do + local l = d[i] + if find(l,"[\n\r]") then + local s = splitlines(l) + local m = #s + for i=1,m do + maxterm = maxterm + 1 + terminaldata[maxterm] = s[i] + end + n = n + m + else + maxterm = maxterm + 1 + terminaldata[maxterm] = d[i] + n = 1 + end + end + end + if trace_terminal then + report_metapost("writing %i lines, in cache %s",n,maxterm) + end + return maxterm + end - return function(name,mode,kind) - if name == "terminal" then - -- report_metapost("opening terminal") - return terminal - elseif mode == "w" then - local f = io.open(name,"wb") - if f then - -- report_metapost("opening file %a for writing",full) - return { - name = full, - writer = function(s) return f:write(s) end, -- io.write(f,s) - close = function() f:close() end, - } + local function readfromterminal(terminaldata,maxterm,nowterm) + if nowterm >= maxterm then + terminaldata[nowterm] = false + maxterm = 0 + nowterm = 0 + if trace_terminal then + report_metapost("resetting, maxcache %i",#terminaldata) end + return maxterm, nowterm, nil else - local full = findtexfile(name,validftype(ftype)) - if full then - -- report_metapost("opening file %a for reading",full) - return opentexfile(full) + if nowterm > 0 then + terminaldata[nowterm] = false + end + nowterm = nowterm + 1 + local s = terminaldata[nowterm] + if trace_terminal then + report_metapost("reading line %i: %s",nowterm,s) end + return maxterm, nowterm, s end end - end + local function fileopener() + + -- these can go into the table itself + + local terminaldata = { } + local maxterm = 0 + local nowterm = 0 + + local terminal = { + name = "terminal", + close = function() + -- terminal = { } + -- maxterm = 0 + -- nowterm = 0 + end, + reader = function() + local line + maxterm, nowterm, line = readfromterminal(terminaldata,maxterm,nowterm) + return line + end, + writer = function(d) + maxterm = writetoterminal(terminaldata,maxterm,d) + end, + } + + return function(name,mode,kind) + if name == "terminal" then + -- report_metapost("opening terminal") + return terminal + elseif mode == "w" then + local f = io.open(name,"wb") + if f then + -- report_metapost("opening file %a for writing",full) + return { + name = full, + writer = function(s) return f:write(s) end, -- io.write(f,s) + close = function() f:close() end, + } + end + else + local full = findtexfile(name,validftype(ftype)) + if full then + -- report_metapost("opening file %a for reading",full) + return opentexfile(full) + end + end + end - -- end of experiment + end - if CONTEXTLMTXMODE > 0 then + local function finder(name,mode,kind) + return findtexfile(name,kind) + end function mplib.new(specification) local openfile = fileopener() @@ -317,17 +306,37 @@ local realtimelogging do return instance end + mplib.finder = finder + else + local function i_finder(name,mode,ftype) -- fake message for mpost.map and metafun.mpvi + local specification = url.hashed(name) + local finder = finders[specification.scheme] or finders.file + local found = finder(specification,name,mode,validftype(ftype)) + return found + end + + local function o_finder(name,mode,ftype) + return name + end + + o_finder = sandbox.register(o_finder,sandbox.filehandlerone,"mplib output finder") + + local function finder(name,mode,ftype) + return (mode == "w" and o_finder or i_finder)(name,mode,validftype(ftype)) + end + function mplib.new(specification) specification.find_file = finder specification.run_logger = logger return new_instance(specification) end + mplib.finder = finder + end - mplib.finder = finder end diff --git a/tex/context/base/mkiv/mlib-scn.lmt b/tex/context/base/mkiv/mlib-scn.lmt new file mode 100644 index 000000000..5655c507d --- /dev/null +++ b/tex/context/base/mkiv/mlib-scn.lmt @@ -0,0 +1,795 @@ +if not modules then modules = { } end modules ['mlib-scn'] = { + 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", +} + +-- Very experimental, for Alan and me. + +-- for i = 1 upto 32000 : % 0.062 +-- ts := 5mm / 20; +-- endfor ; +-- +-- for i = 1 upto 32000 : % 0.219 +-- ts := (getparameter "axis" "sy") / 20; +-- endfor ; +-- +-- for i = 1 upto 32000 : % 0.266 +-- ts := (getparameterx "axis" "sy") / 20; +-- endfor ; +-- +-- pushparameters "axis"; +-- for i = 1 upto 32000 : % 0.250 +-- ts := (getparameterx "sy") / 20; +-- endfor ; +-- popparameters; + +local type, next, rawget, getmetatable = type, next, rawget, getmetatable +local byte, gmatch = string.byte, string.gmatch +local insert, remove = table.insert, table.remove + +local mplib = mplib +local metapost = metapost + +local codes = mplib.codes() +local types = mplib.types() + +table.hashed(codes) +table.hashed(types) + +metapost.codes = codes +metapost.types = types + +local setmetatableindex = table.setmetatableindex + +local scanners = mp.scan +local injectors = mp.inject + +local scannext = scanners.next +local scanexpression = scanners.expression +local scantoken = scanners.token +local scansymbol = scanners.symbol +local scannumeric = scanners.numeric +local scannumber = scanners.number +local scaninteger = scanners.integer +local scanboolean = scanners.boolean +local scanstring = scanners.string +local scanpair = scanners.pair +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 injectnumeric = injectors.numeric +local injectstring = injectors.string +local injectboolean = injectors.boolean +local injectpair = injectors.pair +local injecttriplet = injectors.color +local injectquadruple = injectors.cmykcolor +local injecttransform = injectors.transform + +local report = logs.reporter("metapost") + +local semicolon_code = codes.semicolon +local equals_code = codes.equals +local comma_code = codes.comma +local colon_code = codes.colon +local leftbrace_code = codes.leftbrace +local rightbrace_code = codes.rightbrace +local leftbracket_code = codes.leftbracket +local rightbracket_code = codes.rightbracket +local leftdelimiter_code = codes.leftdelimiter +local rightdelimiter_code = codes.rightdelimiter +local numeric_code = codes.numeric +local string_code = codes.string +local capsule_code = codes.capsule +local nullary_code = codes.nullary +local tag_code = codes.tag +local definedmacro_code = codes.definedmacro + +local typescanners = nil +local tokenscanners = nil +local scanset = nil +local scanparameters = nil + +local injectpath = mp.path + +do -- todo: this will become an overload + + local flush = mp.path + local inject = injectors.path -- work in progress + + injectpath = function(p,connector,close) + if #p > 1 then + if connector == true or connector == ".." then + return inject(p,false,close or p.close) + elseif connector == "--" then + return inject(p,true,close or p.close) + end + end + return flush(p,connector,close) + end + +end + +scanset = function() -- can be optimized, we now read twice + scantoken() + if scantoken(true) == rightbrace_code then + scantoken() + return { } + else + local l = { } + local i = 0 + while true do + i = i + 1 + local s = scansymbol(true) + if s == "{" then + l[i] = scanset() + elseif s == "[" then + local d = { } + scansymbol() + while true do + local s = scansymbol() + if s == "]" then + break; + elseif s == "," then + -- continue + else + local t = scantoken(true) + if t == equals_code or t == colon_code then + scantoken() + end + d[s] = tokenscanners[scantoken(true)]() + end + end + l[i] = d + else + local e = scanexpression(true) + l[i] = (typescanners[e] or scanexpression)() + end + if scantoken() == rightbrace_code then + break + else + -- whatever + end + end + return l + end +end + +local function scan_pair () return scanpair (true) end +local function scan_color () return scancolor (true) end +local function scan_cmykcolor() return scancmykcolor(true) end +local function scan_transform() return scantransform(true) end + +tokenscanners = { + [leftbrace_code] = scanset, + [numeric_code] = scannumeric, + [string_code] = scanstring, + [nullary_code] = scanboolean, -- todo +} + +typescanners = { + [types.known] = scannumeric, + [types.numeric] = scannumeric, + [types.string] = scanstring, + [types.boolean] = scanboolean, + [types.pair] = scan_pair, + [types.color] = scan_color, + [types.cmykcolor] = scan_cmykcolor, + [types.transform] = scan_transform, + [types.path] = scanpath, + [types.pen] = scanpen, +} + +table.setmetatableindex(tokenscanners,function() + local e = scanexpression(true) + return typescanners[e] or scanexpression +end) + +-- a key like 'color' has code 'declare' + +local function scanparameters(fenced) + local data = { } + local close = "]" + if not fenced then + close = ";" + elseif scansymbol(true) == "[" then + scansymbol() + else + return data + end + while true do + -- local s = scansymbol() + local s = scansymbol(false,false) + if s == close then + break; + elseif s == "," then + -- continue + else + local t = scantoken(true) + if t == equals_code or t == colon_code then + -- optional equal or : + scantoken() + end + local kind = scantoken(true) + if kind == leftdelimiter_code or kind == tag_code or kind == capsule_code then + kind = scanexpression(true) + data[s] = (typescanners[kind] or scanexpression)() + elseif kind == leftbracket_code then + data[s] = get_parameters(true) + else + data[s] = tokenscanners[kind]() + end + end + end + return data +end + +local namespaces = { } +local presets = { } +local passed = { } + +local function get_parameters(nested) + local data = { } + if nested or scansymbol(true) == "[" then + scansymbol() + else + return data + end + while true do + local s = scansymbol(false,false) + if s == "]" then + break; + elseif s == "," then + -- continue + else + local t = scantoken(true) + if t == equals_code or t == colon_code then + -- optional equal or : + scantoken() + end + local kind = scantoken(true) + if kind == leftdelimiter_code or kind == tag_code or kind == capsule_code then + kind = scanexpression(true) + data[s] = (typescanners[kind] or scanexpression)() + elseif kind == leftbracket_code then + data[s] = get_parameters(true) + else + data[s] = tokenscanners[kind]() + end + end + end + return data +end + +local function getparameters() + local namespace = scanstring() + -- same as below + local parameters = get_parameters() + local presets = presets[namespace] + local passed = passed[namespace] + if passed then + if presets then + setmetatableindex(passed,presets) + end + setmetatableindex(parameters,passed) + elseif presets then + setmetatableindex(parameters,presets) + end + namespaces[namespace] = parameters + -- +end + +local function applyparameters() + local saved = namespaces + local namespace = scanstring() + local action = scanstring() -- before we scan the parameters + -- same as above + local parameters = get_parameters() + local presets = presets[namespace] + local passed = passed[namespace] + if passed then + if presets then + setmetatableindex(passed,presets) + end + setmetatableindex(parameters,passed) + elseif presets then + setmetatableindex(parameters,presets) + end + namespaces[namespace] = parameters + -- till here + mpprint(action) + namespaces = saved +end + +local function presetparameters() + local namespace = scanstring() + local parent = nil + local t = scantoken(true) + if t == string_code then + parent = presets[scanstring()] + end + local p = get_parameters() + if parent then + setmetatableindex(p,parent) + end + presets[namespace] = p +end + +local function collectnames() + local l = { } -- can be reused but then we can't nest + local n = 0 + while true do + local t = scantoken(true) + -- (1) not really needed + if t == numeric_code then + n = n + 1 l[n] = scaninteger(1) + elseif t == string_code then + n = n + 1 l[n] = scanstring(1) + elseif t == nullary_code then + n = n + 1 l[n] = scanboolean(1) + elseif t == leftdelimiter_code or t == tag_code or t == capsule_code then + t = scanexpression(true) + n = n + 1 l[n] = (typescanners[t] or scanexpression)() + else + break + end + end + return l, n +end + +local function get(v) + local t = type(v) + if t == "number" then + return injectnumeric(v) + elseif t == "boolean" then + return injectboolean(v) + elseif t == "string" then + return injectstring(v) + elseif t == "table" then + local n = #v + if type(v[1]) == "table" then + return injectpath(v) -- cycle ? + elseif n == 2 then + return injectpair(v) + elseif n == 3 then + return injecttriplet(v) + elseif n == 4 then + return injectquadruple(v) + elseif n == 6 then + return injecttransform(v) + end + end + return injectnumeric(0) +end + +local stack = { } + +local function pushparameters() + local l, n = collectnames() + insert(stack,namespaces) + for i=1,n do + local n = namespaces[l[i]] + if type(n) == "table" then + namespaces = n + else + break + end + end +end + +local function popparameters() + local n = remove(stack) + if n then + namespaces = n + else + report("stack error") + end +end + +-- todo: + +local function getparameter() + local list, n = collectnames() + local v = namespaces + for i=1,n do + local l = list[i] + local vl = v[l] + if vl == nil then + if type(l) == "number" then + vl = v[1] + if vl == nil then + return injectnumeric(0) + end + else + return injectnumeric(0) + end + end + v = vl + end + if v == nil then + return injectnumeric(0) + else + return get(v) + end +end + +local function hasparameter() + local list, n = collectnames() + local v = namespaces + for i=1,n do + local l = list[i] + local vl = rawget(v,l) + if vl == nil then + if type(l) == "number" then + vl = rawget(v,1) + if vl == nil then + return injectboolean(false) + end + else + return injectboolean(false) + end + end + v = vl + end + if v == nil then + return injectboolean(false) + else + return injectboolean(true) + end +end + +local function hasoption() + local list, n = collectnames() + if n > 1 then + local v = namespaces + if n > 2 then + for i=1,n-1 do + local l = list[i] + local vl = v[l] + if vl == nil then + return injectboolean(false) + end + v = vl + end + else + v = v[list[1]] + end + if type(v) == "string" then + -- no caching .. slow anyway + local o = list[n] + if v == o then + return injectboolean(true) + end + for vv in gmatch(v,"[^%s,]+") do + for oo in gmatch(o,"[^%s,]+") do + if vv == oo then + return injectboolean(true) + end + end + end + end + end + return injectboolean(false) +end + +local function getparameterdefault() + local list, n = collectnames() + local v = namespaces + if n == 1 then + local l = list[1] + local vl = v[l] + if vl == nil then + -- maybe backtrack + local top = stack[#stack] + if top then + vl = top[l] + end + end + if vl == nil then + return injectnumeric(0) + else + return get(vl) + end + else + for i=1,n-1 do + local l = list[i] + local vl = v[l] + if vl == nil then + if type(l) == "number" then + vl = v[1] + if vl == nil then + return get(list[n]) + end + else + local last = list[n] + if last == "*" then + -- so, only when not pushed + local m = getmetatable(namespaces[list[1]]) + if n then + m = m.__index -- can also be a _m_ + end + if m then + local v = m + for i=2,n-1 do + local l = list[i] + local vl = v[l] + if vl == nil then + return injectnumeric(0) + end + v = vl + end + if v == nil then + return injectnumeric(0) + else + return get(v) + end + end + return injectnumeric(0) + else + return get(last) + end + end + end + v = vl + end + if v == nil then + return get(list[n]) + else + return get(v) + end + end +end + +local function getparametercount() + local list, n = collectnames() + local v = namespaces + for i=1,n do + v = v[list[i]] + if not v then + break + end + end + return injectnumeric(type(v) == "table" and #v or 0) +end + +local function getmaxparametercount() + local list, n = collectnames() + local v = namespaces + for i=1,n do + v = v[list[i]] + if not v then + break + end + end + local n = 0 + if type(v) == "table" then + local v1 = v[1] + if type(v1) == "table" then + n = #v1 + for i=2,#v do + local vi = v[i] + if type(vi) == "table" then + local vn = #vi + if vn > n then + n = vn + end + else + break; + end + end + end + + end + return injectnumeric(n) +end + +local validconnectors = { + [".."] = true, + ["..."] = true, + ["--"] = true, +} + +local function getparameterpath() + local list, n = collectnames() + local close = list[n] + if type(close) == "boolean" then + n = n - 1 + else + close = false + end + local connector = list[n] + if type(connector) == "string" and validconnectors[connector] then + n = n - 1 + else + connector = "--" + end + 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 injectpath(v,connector,close) + else + return injectpair(0,0) + 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 injectpath(v,"..",true) + else + return injectpair(0,0) + end +end + +local function getparametertext() + local list, n = collectnames() + local strut = list[n] + if type(strut) == "boolean" then + n = n - 1 + else + strut = false + end + local v = namespaces + for i=1,n do + v = v[list[i]] + if not v then + break + end + end + if type(v) == "string" then + return injectstring("\\strut " .. v) + else + return injectstring("") + end +end + +-- local function getparameteroption() +-- local list, n = collectnames() +-- local last = list[n] +-- if type(last) == "string" then +-- n = n - 1 +-- else +-- return false +-- end +-- local v = namespaces +-- for i=1,n do +-- v = v[list[i]] +-- if not v then +-- break +-- end +-- end +-- if type(v) == "string" and v ~= "" then +-- for s in gmatch(v,"[^ ,]+") do +-- if s == last then +-- return true +-- end +-- end +-- end +-- return false +-- end + +function metapost.scanparameters() +-- scantoken() -- we scan the semicolon + return get_parameters() +end + +metapost.registerscript("getparameters", getparameters) +metapost.registerscript("applyparameters", applyparameters) +metapost.registerscript("presetparameters", presetparameters) +metapost.registerscript("hasparameter", hasparameter) +metapost.registerscript("hasoption", hasoption) +metapost.registerscript("getparameter", getparameter) +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) +metapost.registerscript("popparameters", popparameters) + +function metapost.getparameter(list) + local n = #list + local v = namespaces + for i=1,n do + local l = list[i] + local vl = v[l] + if vl == nil then + return + end + v = vl + end + return v +end + +function metapost.getparameterset(namespace) + return namespace and namespaces[namespace] or namespaces +end + +function metapost.setparameterset(namespace,t) + namespaces[namespace] = t +end + +-- goodies + +metapost.registerscript("definecolor", function() + scantoken() -- we scan the semicolon + local s = get_parameters() + attributes.colors.defineprocesscolordirect(s) +end) + +-- tex scanners + +local scanners = tokens.scanners +local scanhash = scanners.hash +local scanstring = scanners.string +local scanvalue = scanners.value +local scaninteger = scanners.integer +local scanboolean = scanners.boolean +local scanfloat = scanners.float +local scandimension = scanners.dimension + +local definitions = { } + +local bpfactor = number.dimenfactors.bp +local comma = byte(",") +local close = byte("]") + +local scanrest = function() return scanvalue(comma,close) or "" end +local scandimension = function() return scandimension() * bpfactor end + +local scanners = { + ["integer"] = scaninteger, + ["number"] = scanfloat, + ["numeric"] = scanfloat, + ["boolean"] = scanboolean, + ["string"] = scanrest, + ["dimension"] = scandimension, +} + +interfaces.implement { + name = "lmt_parameters_define", + arguments = "string", + actions = function(namespace) + local d = scanhash() + for k, v in next, d do + d[k] = scanners[v] or scanrest + end + definitions[namespace] = d + end, +} + +interfaces.implement { + name = "lmt_parameters_preset", + arguments = "string", + actions = function(namespace) + passed[namespace] = scanhash(definitions[namespace]) + end, +} + +interfaces.implement { + name = "lmt_parameters_reset", + arguments = "string", + actions = function(namespace) + passed[namespace] = nil + end, +} diff --git a/tex/context/base/mkiv/mlib-scn.lua b/tex/context/base/mkiv/mlib-scn.lua deleted file mode 100644 index 5655c507d..000000000 --- a/tex/context/base/mkiv/mlib-scn.lua +++ /dev/null @@ -1,795 +0,0 @@ -if not modules then modules = { } end modules ['mlib-scn'] = { - 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", -} - --- Very experimental, for Alan and me. - --- for i = 1 upto 32000 : % 0.062 --- ts := 5mm / 20; --- endfor ; --- --- for i = 1 upto 32000 : % 0.219 --- ts := (getparameter "axis" "sy") / 20; --- endfor ; --- --- for i = 1 upto 32000 : % 0.266 --- ts := (getparameterx "axis" "sy") / 20; --- endfor ; --- --- pushparameters "axis"; --- for i = 1 upto 32000 : % 0.250 --- ts := (getparameterx "sy") / 20; --- endfor ; --- popparameters; - -local type, next, rawget, getmetatable = type, next, rawget, getmetatable -local byte, gmatch = string.byte, string.gmatch -local insert, remove = table.insert, table.remove - -local mplib = mplib -local metapost = metapost - -local codes = mplib.codes() -local types = mplib.types() - -table.hashed(codes) -table.hashed(types) - -metapost.codes = codes -metapost.types = types - -local setmetatableindex = table.setmetatableindex - -local scanners = mp.scan -local injectors = mp.inject - -local scannext = scanners.next -local scanexpression = scanners.expression -local scantoken = scanners.token -local scansymbol = scanners.symbol -local scannumeric = scanners.numeric -local scannumber = scanners.number -local scaninteger = scanners.integer -local scanboolean = scanners.boolean -local scanstring = scanners.string -local scanpair = scanners.pair -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 injectnumeric = injectors.numeric -local injectstring = injectors.string -local injectboolean = injectors.boolean -local injectpair = injectors.pair -local injecttriplet = injectors.color -local injectquadruple = injectors.cmykcolor -local injecttransform = injectors.transform - -local report = logs.reporter("metapost") - -local semicolon_code = codes.semicolon -local equals_code = codes.equals -local comma_code = codes.comma -local colon_code = codes.colon -local leftbrace_code = codes.leftbrace -local rightbrace_code = codes.rightbrace -local leftbracket_code = codes.leftbracket -local rightbracket_code = codes.rightbracket -local leftdelimiter_code = codes.leftdelimiter -local rightdelimiter_code = codes.rightdelimiter -local numeric_code = codes.numeric -local string_code = codes.string -local capsule_code = codes.capsule -local nullary_code = codes.nullary -local tag_code = codes.tag -local definedmacro_code = codes.definedmacro - -local typescanners = nil -local tokenscanners = nil -local scanset = nil -local scanparameters = nil - -local injectpath = mp.path - -do -- todo: this will become an overload - - local flush = mp.path - local inject = injectors.path -- work in progress - - injectpath = function(p,connector,close) - if #p > 1 then - if connector == true or connector == ".." then - return inject(p,false,close or p.close) - elseif connector == "--" then - return inject(p,true,close or p.close) - end - end - return flush(p,connector,close) - end - -end - -scanset = function() -- can be optimized, we now read twice - scantoken() - if scantoken(true) == rightbrace_code then - scantoken() - return { } - else - local l = { } - local i = 0 - while true do - i = i + 1 - local s = scansymbol(true) - if s == "{" then - l[i] = scanset() - elseif s == "[" then - local d = { } - scansymbol() - while true do - local s = scansymbol() - if s == "]" then - break; - elseif s == "," then - -- continue - else - local t = scantoken(true) - if t == equals_code or t == colon_code then - scantoken() - end - d[s] = tokenscanners[scantoken(true)]() - end - end - l[i] = d - else - local e = scanexpression(true) - l[i] = (typescanners[e] or scanexpression)() - end - if scantoken() == rightbrace_code then - break - else - -- whatever - end - end - return l - end -end - -local function scan_pair () return scanpair (true) end -local function scan_color () return scancolor (true) end -local function scan_cmykcolor() return scancmykcolor(true) end -local function scan_transform() return scantransform(true) end - -tokenscanners = { - [leftbrace_code] = scanset, - [numeric_code] = scannumeric, - [string_code] = scanstring, - [nullary_code] = scanboolean, -- todo -} - -typescanners = { - [types.known] = scannumeric, - [types.numeric] = scannumeric, - [types.string] = scanstring, - [types.boolean] = scanboolean, - [types.pair] = scan_pair, - [types.color] = scan_color, - [types.cmykcolor] = scan_cmykcolor, - [types.transform] = scan_transform, - [types.path] = scanpath, - [types.pen] = scanpen, -} - -table.setmetatableindex(tokenscanners,function() - local e = scanexpression(true) - return typescanners[e] or scanexpression -end) - --- a key like 'color' has code 'declare' - -local function scanparameters(fenced) - local data = { } - local close = "]" - if not fenced then - close = ";" - elseif scansymbol(true) == "[" then - scansymbol() - else - return data - end - while true do - -- local s = scansymbol() - local s = scansymbol(false,false) - if s == close then - break; - elseif s == "," then - -- continue - else - local t = scantoken(true) - if t == equals_code or t == colon_code then - -- optional equal or : - scantoken() - end - local kind = scantoken(true) - if kind == leftdelimiter_code or kind == tag_code or kind == capsule_code then - kind = scanexpression(true) - data[s] = (typescanners[kind] or scanexpression)() - elseif kind == leftbracket_code then - data[s] = get_parameters(true) - else - data[s] = tokenscanners[kind]() - end - end - end - return data -end - -local namespaces = { } -local presets = { } -local passed = { } - -local function get_parameters(nested) - local data = { } - if nested or scansymbol(true) == "[" then - scansymbol() - else - return data - end - while true do - local s = scansymbol(false,false) - if s == "]" then - break; - elseif s == "," then - -- continue - else - local t = scantoken(true) - if t == equals_code or t == colon_code then - -- optional equal or : - scantoken() - end - local kind = scantoken(true) - if kind == leftdelimiter_code or kind == tag_code or kind == capsule_code then - kind = scanexpression(true) - data[s] = (typescanners[kind] or scanexpression)() - elseif kind == leftbracket_code then - data[s] = get_parameters(true) - else - data[s] = tokenscanners[kind]() - end - end - end - return data -end - -local function getparameters() - local namespace = scanstring() - -- same as below - local parameters = get_parameters() - local presets = presets[namespace] - local passed = passed[namespace] - if passed then - if presets then - setmetatableindex(passed,presets) - end - setmetatableindex(parameters,passed) - elseif presets then - setmetatableindex(parameters,presets) - end - namespaces[namespace] = parameters - -- -end - -local function applyparameters() - local saved = namespaces - local namespace = scanstring() - local action = scanstring() -- before we scan the parameters - -- same as above - local parameters = get_parameters() - local presets = presets[namespace] - local passed = passed[namespace] - if passed then - if presets then - setmetatableindex(passed,presets) - end - setmetatableindex(parameters,passed) - elseif presets then - setmetatableindex(parameters,presets) - end - namespaces[namespace] = parameters - -- till here - mpprint(action) - namespaces = saved -end - -local function presetparameters() - local namespace = scanstring() - local parent = nil - local t = scantoken(true) - if t == string_code then - parent = presets[scanstring()] - end - local p = get_parameters() - if parent then - setmetatableindex(p,parent) - end - presets[namespace] = p -end - -local function collectnames() - local l = { } -- can be reused but then we can't nest - local n = 0 - while true do - local t = scantoken(true) - -- (1) not really needed - if t == numeric_code then - n = n + 1 l[n] = scaninteger(1) - elseif t == string_code then - n = n + 1 l[n] = scanstring(1) - elseif t == nullary_code then - n = n + 1 l[n] = scanboolean(1) - elseif t == leftdelimiter_code or t == tag_code or t == capsule_code then - t = scanexpression(true) - n = n + 1 l[n] = (typescanners[t] or scanexpression)() - else - break - end - end - return l, n -end - -local function get(v) - local t = type(v) - if t == "number" then - return injectnumeric(v) - elseif t == "boolean" then - return injectboolean(v) - elseif t == "string" then - return injectstring(v) - elseif t == "table" then - local n = #v - if type(v[1]) == "table" then - return injectpath(v) -- cycle ? - elseif n == 2 then - return injectpair(v) - elseif n == 3 then - return injecttriplet(v) - elseif n == 4 then - return injectquadruple(v) - elseif n == 6 then - return injecttransform(v) - end - end - return injectnumeric(0) -end - -local stack = { } - -local function pushparameters() - local l, n = collectnames() - insert(stack,namespaces) - for i=1,n do - local n = namespaces[l[i]] - if type(n) == "table" then - namespaces = n - else - break - end - end -end - -local function popparameters() - local n = remove(stack) - if n then - namespaces = n - else - report("stack error") - end -end - --- todo: - -local function getparameter() - local list, n = collectnames() - local v = namespaces - for i=1,n do - local l = list[i] - local vl = v[l] - if vl == nil then - if type(l) == "number" then - vl = v[1] - if vl == nil then - return injectnumeric(0) - end - else - return injectnumeric(0) - end - end - v = vl - end - if v == nil then - return injectnumeric(0) - else - return get(v) - end -end - -local function hasparameter() - local list, n = collectnames() - local v = namespaces - for i=1,n do - local l = list[i] - local vl = rawget(v,l) - if vl == nil then - if type(l) == "number" then - vl = rawget(v,1) - if vl == nil then - return injectboolean(false) - end - else - return injectboolean(false) - end - end - v = vl - end - if v == nil then - return injectboolean(false) - else - return injectboolean(true) - end -end - -local function hasoption() - local list, n = collectnames() - if n > 1 then - local v = namespaces - if n > 2 then - for i=1,n-1 do - local l = list[i] - local vl = v[l] - if vl == nil then - return injectboolean(false) - end - v = vl - end - else - v = v[list[1]] - end - if type(v) == "string" then - -- no caching .. slow anyway - local o = list[n] - if v == o then - return injectboolean(true) - end - for vv in gmatch(v,"[^%s,]+") do - for oo in gmatch(o,"[^%s,]+") do - if vv == oo then - return injectboolean(true) - end - end - end - end - end - return injectboolean(false) -end - -local function getparameterdefault() - local list, n = collectnames() - local v = namespaces - if n == 1 then - local l = list[1] - local vl = v[l] - if vl == nil then - -- maybe backtrack - local top = stack[#stack] - if top then - vl = top[l] - end - end - if vl == nil then - return injectnumeric(0) - else - return get(vl) - end - else - for i=1,n-1 do - local l = list[i] - local vl = v[l] - if vl == nil then - if type(l) == "number" then - vl = v[1] - if vl == nil then - return get(list[n]) - end - else - local last = list[n] - if last == "*" then - -- so, only when not pushed - local m = getmetatable(namespaces[list[1]]) - if n then - m = m.__index -- can also be a _m_ - end - if m then - local v = m - for i=2,n-1 do - local l = list[i] - local vl = v[l] - if vl == nil then - return injectnumeric(0) - end - v = vl - end - if v == nil then - return injectnumeric(0) - else - return get(v) - end - end - return injectnumeric(0) - else - return get(last) - end - end - end - v = vl - end - if v == nil then - return get(list[n]) - else - return get(v) - end - end -end - -local function getparametercount() - local list, n = collectnames() - local v = namespaces - for i=1,n do - v = v[list[i]] - if not v then - break - end - end - return injectnumeric(type(v) == "table" and #v or 0) -end - -local function getmaxparametercount() - local list, n = collectnames() - local v = namespaces - for i=1,n do - v = v[list[i]] - if not v then - break - end - end - local n = 0 - if type(v) == "table" then - local v1 = v[1] - if type(v1) == "table" then - n = #v1 - for i=2,#v do - local vi = v[i] - if type(vi) == "table" then - local vn = #vi - if vn > n then - n = vn - end - else - break; - end - end - end - - end - return injectnumeric(n) -end - -local validconnectors = { - [".."] = true, - ["..."] = true, - ["--"] = true, -} - -local function getparameterpath() - local list, n = collectnames() - local close = list[n] - if type(close) == "boolean" then - n = n - 1 - else - close = false - end - local connector = list[n] - if type(connector) == "string" and validconnectors[connector] then - n = n - 1 - else - connector = "--" - end - 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 injectpath(v,connector,close) - else - return injectpair(0,0) - 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 injectpath(v,"..",true) - else - return injectpair(0,0) - end -end - -local function getparametertext() - local list, n = collectnames() - local strut = list[n] - if type(strut) == "boolean" then - n = n - 1 - else - strut = false - end - local v = namespaces - for i=1,n do - v = v[list[i]] - if not v then - break - end - end - if type(v) == "string" then - return injectstring("\\strut " .. v) - else - return injectstring("") - end -end - --- local function getparameteroption() --- local list, n = collectnames() --- local last = list[n] --- if type(last) == "string" then --- n = n - 1 --- else --- return false --- end --- local v = namespaces --- for i=1,n do --- v = v[list[i]] --- if not v then --- break --- end --- end --- if type(v) == "string" and v ~= "" then --- for s in gmatch(v,"[^ ,]+") do --- if s == last then --- return true --- end --- end --- end --- return false --- end - -function metapost.scanparameters() --- scantoken() -- we scan the semicolon - return get_parameters() -end - -metapost.registerscript("getparameters", getparameters) -metapost.registerscript("applyparameters", applyparameters) -metapost.registerscript("presetparameters", presetparameters) -metapost.registerscript("hasparameter", hasparameter) -metapost.registerscript("hasoption", hasoption) -metapost.registerscript("getparameter", getparameter) -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) -metapost.registerscript("popparameters", popparameters) - -function metapost.getparameter(list) - local n = #list - local v = namespaces - for i=1,n do - local l = list[i] - local vl = v[l] - if vl == nil then - return - end - v = vl - end - return v -end - -function metapost.getparameterset(namespace) - return namespace and namespaces[namespace] or namespaces -end - -function metapost.setparameterset(namespace,t) - namespaces[namespace] = t -end - --- goodies - -metapost.registerscript("definecolor", function() - scantoken() -- we scan the semicolon - local s = get_parameters() - attributes.colors.defineprocesscolordirect(s) -end) - --- tex scanners - -local scanners = tokens.scanners -local scanhash = scanners.hash -local scanstring = scanners.string -local scanvalue = scanners.value -local scaninteger = scanners.integer -local scanboolean = scanners.boolean -local scanfloat = scanners.float -local scandimension = scanners.dimension - -local definitions = { } - -local bpfactor = number.dimenfactors.bp -local comma = byte(",") -local close = byte("]") - -local scanrest = function() return scanvalue(comma,close) or "" end -local scandimension = function() return scandimension() * bpfactor end - -local scanners = { - ["integer"] = scaninteger, - ["number"] = scanfloat, - ["numeric"] = scanfloat, - ["boolean"] = scanboolean, - ["string"] = scanrest, - ["dimension"] = scandimension, -} - -interfaces.implement { - name = "lmt_parameters_define", - arguments = "string", - actions = function(namespace) - local d = scanhash() - for k, v in next, d do - d[k] = scanners[v] or scanrest - end - definitions[namespace] = d - end, -} - -interfaces.implement { - name = "lmt_parameters_preset", - arguments = "string", - actions = function(namespace) - passed[namespace] = scanhash(definitions[namespace]) - end, -} - -interfaces.implement { - name = "lmt_parameters_reset", - arguments = "string", - actions = function(namespace) - passed[namespace] = nil - end, -} diff --git a/tex/context/base/mkiv/mlib-svg.lmt b/tex/context/base/mkiv/mlib-svg.lmt new file mode 100644 index 000000000..4c4122476 --- /dev/null +++ b/tex/context/base/mkiv/mlib-svg.lmt @@ -0,0 +1,3277 @@ +if not modules then modules = { } end modules ['mlib-svg'] = { + version = 1.001, + optimize = true, + 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", +} + +-- Just a few notes: +-- +-- 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. I need to optimize this a bit but will do that once I'm satisfied +-- with the outcome and don't need more hooks and plugs. At some point I will +-- optimize the MetaPost part because now we probably have more image wrapping +-- than needed. +-- +-- 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). +-- +-- 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. So in the end we now have (1) attributes for properties (which is +-- nice and clean and what attributes are for, (2) a style attribute that needs to +-- be parsed, (3) classes that map to styles and (4) element related styles, plus a +-- kind of inheritance (given the limited number of elements sticking to only as +-- wrapper would have made much sense. Anyway, we need to deal with it. With all +-- these style things going on, one can wonder where it will end. Basically svg +-- became just a html element that way and less clean too. The same is true for +-- tspan, which means that text itself is nested xml. +-- +-- We can do a direct conversion to PDF but then we also loose the abstraction which +-- in the future will be used, and for fonts we need to spawn out to TeX anyway, so +-- the little overhead of calling MetaPost is okay I guess. Also, we want to +-- overload labels, share fonts with the main document, etc. and are not aiming at a +-- general purpose SVG converter. For going to PDF one can just use InkScape. +-- +-- Written with Anne Clark on speakers as distraction. +-- +-- Todo when I run into an example (but ony when needed and reasonable): +-- +-- var(color,color) +-- --color +-- currentColor : when i run into an example +-- a bit more shading +-- clip = [ auto | rect(llx,lly,urx,ury) ] (in svg) +-- xlink url ... whatever +-- masks +-- opacity per group (i need to add that to metafun first, inefficient pdf but +-- maybe filldraw can help here) +-- +-- Maybe in metafun: +-- +-- penciled n -> withpen pencircle scaled n +-- applied (...) -> transformed bymatrix (...) +-- withopacity n -> withtransparency (1,n) + +-- When testing mbo files: +-- +-- empty paths +-- missing control points +-- funny fontnames like abcdefverdana etc +-- paths representing glyphs but also with style specs +-- all kind of attributes +-- very weird and inefficient shading + +-- One can run into pretty crazy images, like lines that are fills being clipped +-- to some width. That's the danger of hiding yourself behind an interface I guess. + +local rawget, rawset, type, tonumber, tostring, next, setmetatable = rawget, rawset, type, tonumber, tostring, next, setmetatable + +local P, S, R, C, Ct, Cs, Cc, Cp, Cg, Cf, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Cc, lpeg.Cp, lpeg.Cg, lpeg.Cf, lpeg.Carg + +local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns +local sqrt, abs = math.sqrt, math.abs +local concat, setmetatableindex, sortedhash = table.concat, table.setmetatableindex, table.sortedhash +local gmatch, gsub, find, match, rep = string.gmatch, string.gsub, string.find, string.match, string.rep +local formatters, fullstrip = string.formatters, string.fullstrip +local utfsplit, utfbyte = utf.split, utf.byte + +local xmlconvert, xmlcollected, xmlcount, xmlfirst, xmlroot = xml.convert, xml.collected, xml.count, xml.first, xml.root +local xmltext, xmltextonly = xml.text, xml.textonly +local css = xml.css or { } -- testing + +local function xmlinheritattributes(c,pa) + local at = c.at + local dt = c.dt + if at and dt then + if pa then + setmetatableindex(at,pa) + end + for i=1,#dt do + local dti = dt[i] + if type(dti) == "table" then + xmlinheritattributes(dti,at) + end + end + else + -- comment of so + end +end + +xml.inheritattributes = xmlinheritattributes + +-- Maybe some day helpers will move to the metapost.svg namespace! + +metapost = metapost or { } +local metapost = metapost +local context = context + +local report = logs.reporter("metapost","svg") + +local trace = false trackers.register("metapost.svg", function(v) trace = v end) +local trace_text = false trackers.register("metapost.svg.text", function(v) trace_text = v end) +local trace_path = false trackers.register("metapost.svg.path", function(v) trace_path = v end) +local trace_result = false trackers.register("metapost.svg.result", function(v) trace_result = v end) +local trace_colors = false trackers.register("metapost.svg.colors", function(v) trace_colors = v end) + +local pathtracer = { + ["stroke"] = "darkred", + ["stroke-opacity"] = ".5", + ["stroke-width"] = ".5", + ["fill"] = "darkgray", + ["fill-opacity"] = ".75", +} + +-- This is just an experiment. Todo: reset hash etc. Also implement +-- an option handler. + +local svghash = false do + + local svglast = 0 + local svglist = false + + local function checkhash(t,k) + local n = svglast + 1 + svglast = n + svglist[n] = k + t[k] = n + return n + end + + function metapost.startsvghashing() + svglast = 0 + svglist = { } + svghash = setmetatableindex(checkhash) + end + + function metapost.stopsvghashing() + svglast = 0 + svglist = false + svghash = false + end + + interfaces.implement { + name = "svghashed", + arguments = "integer", + actions = function(n) + local t = svglist and svglist[n] + if t then + context(t) + end + end + } + +end + +-- We have quite some closures because otherwise we run into the local variable +-- limitations. It doesn't always look pretty now, sorry. I'll clean up this mess +-- some day (the usual nth iteration of code). +-- +-- 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 and possible +-- division by zero). 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 a2c do + + local pi, sin, cos, tan, asin, abs = math.pi, math.sin, math.cos, math.tan, math.asin, math.abs + + local d120 = (pi * 120) / 180 + local pi2 = 2 * pi + + a2c = function(x1, y1, rx, ry, angle, large, sweep, x2, y2, f1, f2, cx, cy) + + if (rx == 0 or ry == 0 ) or (x1 == x2 and y1 == y2) then + return { x1, y1, x2, y2, x2, y2 } + end + + local recursive = f1 + 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 + +end + +-- We share some patterns. + +local p_digit = lpegpatterns.digit +local p_hexdigit = lpegpatterns.hexdigit +local p_space = lpegpatterns.whitespace + +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 + +-- incredible: we can find .123.456 => 0.123 0.456 ... + +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_optseparator = S("\t\n\r ,")^0 +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)) + * (P("e") * S("+-")^0 * p_digit^1)^-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 function asnumber (s) return s and lpegmatch(p_number, s) or 0 end +local function asnumber_r (s) return s and lpegmatch(p_number_r, s) or 0 end +local function asnumber_x (s) return s and lpegmatch(p_number_x, s) or 0 end +local function asnumber_y (s) return s and lpegmatch(p_number_y, s) or 0 end +local function asnumber_vx(s) return s and lpegmatch(p_number_vx,s) or 0 end +local function asnumber_vy(s) return s and lpegmatch(p_number_vy,s) or 0 end + +local p_number_vx_t = Ct { (p_number_vx + p_separator)^1 } +local p_number_vy_t = Ct { (p_number_vy + p_separator)^1 } + +local zerotable = { 0 } + +local function asnumber_vx_t(s) return s and lpegmatch(p_number_vx_t,s) or zerotable end +local function asnumber_vy_t(s) return s and lpegmatch(p_number_vy_t,s) or zerotable end + +local p_numbersep = p_number_n + p_separator +local p_numbers = p_optseparator * P("(") * p_numbersep^0 * p_optseparator * P(")") +local p_fournumbers = p_numbersep^4 +local p_path = Ct ( ( + p_command_xy * (p_optseparator * p_number_vx * + p_optseparator * p_number_vy )^1 + + p_command_x * (p_optseparator * p_number_vx )^1 + + p_command_y * (p_optseparator * p_number_vy )^1 + + p_command_a * (p_optseparator * p_number_vx * + p_optseparator * p_number_vy * + p_optseparator * p_number_r * + p_optseparator * p_number_n * -- flags + p_optseparator * p_number_n * -- flags + p_optseparator * p_number_vx * + p_optseparator * p_number_vy )^1 + + p_command + + p_separator +)^1 ) + +-- We can actually use the svg color definitions from the tex end but maybe a user +-- doesn't want those replace the normal definitions. +-- +-- 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] + +local colormap = false + +local function prepared(t) + if type(t) == "table" then + local mapping = t.mapping or { } + local mapper = t.mapper + local colormap = setmetatableindex(mapping) + if mapper then + setmetatableindex(colormap,function(t,k) + local v = mapper(k) + t[k] = v or k + return v + end) + end + return colormap + else + return false + end +end + +local colormaps = setmetatableindex(function(t,k) + local v = false + if type(k) == "string" then + v = prepared(table.load(k)) -- todo: same path as svg file + elseif type(k) == "table" then + v = prepared(k) + k = k.name or k + end + t[k] = v + return v +end) + +function metapost.svgcolorremapper(colormap) + return colormaps[colormap] +end + +-- todo: cache colors per image / remapper + +local colorcomponents, withcolor, thecolor, usedcolors do + + local svgcolors = { + aliceblue = 0xF0F8FF, antiquewhite = 0xFAEBD7, aqua = 0x00FFFF, aquamarine = 0x7FFFD4, + azure = 0xF0FFFF, beige = 0xF5F5DC, bisque = 0xFFE4C4, black = 0x000000, + blanchedalmond = 0xFFEBCD, blue = 0x0000FF, blueviolet = 0x8A2BE2, brown = 0xA52A2A, + burlywood = 0xDEB887, cadetblue = 0x5F9EA0, hartreuse = 0x7FFF00, chocolate = 0xD2691E, + coral = 0xFF7F50, cornflowerblue = 0x6495ED, cornsilk = 0xFFF8DC, crimson = 0xDC143C, + cyan = 0x00FFFF, darkblue = 0x00008B, darkcyan = 0x008B8B, darkgoldenrod = 0xB8860B, + darkgray = 0xA9A9A9, darkgreen = 0x006400, darkgrey = 0xA9A9A9, darkkhaki = 0xBDB76B, + darkmagenta = 0x8B008B, darkolivegreen = 0x556B2F, darkorange = 0xFF8C00, darkorchid = 0x9932CC, + darkred = 0x8B0000, darksalmon = 0xE9967A, darkseagreen = 0x8FBC8F, darkslateblue = 0x483D8B, + darkslategray = 0x2F4F4F, darkslategrey = 0x2F4F4F, darkturquoise = 0x00CED1, darkviolet = 0x9400D3, + deeppink = 0xFF1493, deepskyblue = 0x00BFFF, dimgray = 0x696969, dimgrey = 0x696969, + dodgerblue = 0x1E90FF, firebrick = 0xB22222, floralwhite = 0xFFFAF0, forestgreen = 0x228B22, + fuchsia = 0xFF00FF, gainsboro = 0xDCDCDC, ghostwhite = 0xF8F8FF, gold = 0xFFD700, + goldenrod = 0xDAA520, gray = 0x808080, green = 0x008000, greenyellow = 0xADFF2F, + grey = 0x808080, honeydew = 0xF0FFF0, hotpink = 0xFF69B4, indianred = 0xCD5C5C, + indigo = 0x4B0082, ivory = 0xFFFFF0, khaki = 0xF0E68C, lavender = 0xE6E6FA, + lavenderblush = 0xFFF0F5, lawngreen = 0x7CFC00, lemonchiffon = 0xFFFACD, lightblue = 0xADD8E6, + lightcoral = 0xF08080, lightcyan = 0xE0FFFF, lightgoldenrodyellow = 0xFAFAD2, lightgray = 0xD3D3D3, + lightgreen = 0x90EE90, lightgrey = 0xD3D3D3, lightpink = 0xFFB6C1, lightsalmon = 0xFFA07A, + lightseagreen = 0x20B2AA, lightskyblue = 0x87CEFA, lightslategray = 0x778899, lightslategrey = 0x778899, + lightsteelblue = 0xB0C4DE, lightyellow = 0xFFFFE0, lime = 0x00FF00, limegreen = 0x32CD32, + linen = 0xFAF0E6, magenta = 0xFF00FF, maroon = 0x800000, mediumaquamarine = 0x66CDAA, + mediumblue = 0x0000CD, mediumorchid = 0xBA55D3, mediumpurple = 0x9370DB, mediumseagreen = 0x3CB371, + mediumslateblue = 0x7B68EE, mediumspringgreen = 0x00FA9A, mediumturquoise = 0x48D1CC, mediumvioletred = 0xC71585, + midnightblue = 0x191970, mintcream = 0xF5FFFA, mistyrose = 0xFFE4E1, moccasin = 0xFFE4B5, + navajowhite = 0xFFDEAD, navy = 0x000080, oldlace = 0xFDF5E6, olive = 0x808000, + olivedrab = 0x6B8E23, orange = 0xFFA500, orangered = 0xFF4500, orchid = 0xDA70D6, + palegoldenrod = 0xEEE8AA, palegreen = 0x98FB98, paleturquoise = 0xAFEEEE, palevioletred = 0xDB7093, + papayawhip = 0xFFEFD5, peachpuff = 0xFFDAB9, peru = 0xCD853F, pink = 0xFFC0CB, + plum = 0xDDA0DD, powderblue = 0xB0E0E6, purple = 0x800080, red = 0xFF0000, + rosybrown = 0xBC8F8F, royalblue = 0x4169E1, saddlebrown = 0x8B4513, salmon = 0xFA8072, + sandybrown = 0xF4A460, seagreen = 0x2E8B57, seashell = 0xFFF5EE, sienna = 0xA0522D, + silver = 0xC0C0C0, skyblue = 0x87CEEB, slateblue = 0x6A5ACD, slategray = 0x708090, + slategrey = 0x708090, snow = 0xFFFAFA, springgreen = 0x00FF7F, steelblue = 0x4682B4, + tan = 0xD2B48C, teal = 0x008080, thistle = 0xD8BFD8, tomato = 0xFF6347, + turquoise = 0x40E0D0, violet = 0xEE82EE, wheat = 0xF5DEB3, white = 0xFFFFFF, + whitesmoke = 0xF5F5F5, yellow = 0xFFFF00, yellowgreen = 0x9ACD32, + } + + local f_rgb = formatters['withcolor svgcolor(%.3N,%.3N,%.3N)'] + local f_cmyk = formatters['withcolor svgcmyk(%.3N,%.3N,%.3N,%.3N)'] + local f_gray = formatters['withcolor svggray(%.3N)'] + local f_rgba = formatters['withcolor svgcolor(%.3N,%.3N,%.3N) withtransparency (1,%.3N)'] + local f_graya = formatters['withcolor svggray(%.3N) withtransparency (1,%.3N)'] + local f_name = formatters['withcolor "%s"'] + local f_svgrgb = formatters['svgcolor(%.3N,%.3N,%.3N)'] + local f_svgcmyk = formatters['svgcmyk(%.3N,%.3N,%.3N,%.3N)'] + local f_svggray = formatters['svggray(%.3N)'] + local f_svgname = formatters['"%s"'] + + local extract = bit32.extract + + local triplets = setmetatableindex(function(t,k) + -- we delay building all these strings + local v = svgcolors[k] + if v then + v = { extract(v,16,8)/255, extract(v,8,8)/255, extract(v,0,8)/255 } + else + v = false + end + t[k] = v + return v + end) + + local p_fraction = C(p_number) * C("%")^-1 / function(a,b) return tonumber(a) / (b and 100 or 255) end + local p_angle = C(p_number) * P("deg")^0 / function(a) return tonumber(a) end + local p_percent = C(p_number) * P("%") / function(a) return tonumber(a) / 100 end + local p_absolute = C(p_number) / tonumber + + local p_left = P("(") + local p_right = P(")") + local p_a = P("a")^-1 + local p_h_a_color = p_left + * p_angle + * p_separator * p_percent + * p_separator * p_percent + * p_separator^0 * p_absolute^0 + * p_right + + local colors = attributes.colors + local colorvalues = colors.values + local colorindex = attributes.list[attributes.private('color')] + local hsvtorgb = colors.hsvtorgb + local hwbtorgb = colors.hwbtorgb + local forcedmodel = colors.forcedmodel + + local p_splitcolor = + P("#") * C(p_hexdigit*p_hexdigit)^1 / function(r,g,b) + if not r then + return "gray", 0 + elseif not (g and b) then + return "gray", tonumber(r or "0", 16) / 255 or 0 + else + return "rgb", + tonumber(r or "0", 16) / 255 or 0, + tonumber(g or "0", 16) / 255 or 0, + tonumber(b or "0", 16) / 255 or 0 + end + end + + P("rgb") * p_a + * p_left * (p_fraction + p_separator)^-3 * (p_absolute + p_separator)^0 * p_right / function(r,g,b,a) + return "rgb", r or 0, g or 0, b or 0, a or false + end + + P("cmyk") + * p_left * (p_absolute + p_separator)^0 * p_right / function(c,m,y,k) + return "cmyk", c or 0, m or 0, y or 0, k or 0 + end + + P("hsl") * p_a + * p_h_a_color / function(h,s,l,a) + local r, g, b = hsvtorgb(h,s,l,a) + return "rgb", r or 0, g or 0, b or 0, a or false + end + + P("hwb") * p_a + * p_h_a_color / function(h,w,b,a) + local r, g, b = hwbtorgb(h,w,b) + return "rgb", r or 0, g or 0, b or 0, a or false + end + + function metapost.svgsplitcolor(color) + if type(color) == "string" then + local what, s1, s2, s3, s4 = lpegmatch(p_splitcolor,color) + if not what then + local t = triplets[color] + if t then + what, s1, s2, s3 = "rgb", t[1], t[2], t[3] + end + end + return what, s1, s2, s3, s4 + else + return "gray", 0, false + end + end + + local function registeredcolor(name) + local color = colorindex[name] + if color then + local v = colorvalues[color] + local t = forcedmodel(v[1]) + if t == 2 then + return "gray", v[2] + elseif t == 3 then + return "rgb", v[3], v[4], v[5] + elseif t == 4 then + return "cmyk", v[6], v[7], v[8], v[9] + else + -- + end + end + end + + -- we can have a fast check for #000000 + + local function validcolor(color) + if usedcolors then + usedcolors[color] = usedcolors[color] + 1 + end + if colormap then + local c = colormap[color] + local t = type(c) + if t == "table" then + local what = t[1] + if what == "rgb" then + return + what, + tonumber(t[2]) or 0, + tonumber(t[3]) or 0, + tonumber(t[4]) or 0, + tonumber(t[4]) or false + elseif what == "cmyk" then + return + what, + tonumber(t[2]) or 0, + tonumber(t[3]) or 0, + tonumber(t[4]) or 0, + tonumber(t[5]) or 0 + elseif what == "gray" then + return + what, + tonumber(t[2]) or 0, + tonumber(t[3]) or false + end + elseif t == "string" then + color = c + end + end + local what, s1, s2, s3, s4 = registeredcolor(color) + if what then + return what, s1, s2, s3, s4 + end + what, s1, s2, s3, s4 = lpegmatch(p_splitcolor,color) + if not what then + local t = triplets[color] + if t then + s1, s3, s3 = t[1], t[2], t[3] + what = "rgb" + end + end + return what, s1, s2, s3, s4 + end + + colorcomponents = function(color) + local what, s1, s2, s3, s4 = validcolor(color) + return s1, s2, s3, s4 -- so 4 means cmyk + end + + withcolor = function(color) + local what, s1, s2, s3, s4 = validcolor(color) + -- print(color,what, s1, s2, s3, s4) + if what == "rgb" then + if s4 then + if s1 == s2 and s1 == s3 then + return f_graya(s1,s4) + else + return f_rgba(s1,s2,s3,s4) + end + else + if s1 == s2 and s1 == s3 then + return f_gray(s1) + else + return f_rgb(s1,s2,s3) + end + end + elseif what == "cmyk" then + return f_cmyk(s1,s2,s3,s4) + elseif what == "gray" then + if s2 then + return f_graya(s1,s2) + else + return f_gray(s1) + end + end + return f_name(color) + end + + thecolor = function(color) + local what, s1, s2, s3, s4 = validcolor(color) + if what == "rgb" then + if s4 then + if s1 == s2 and s1 == s3 then + return f_svggraya(s1,s4) + else + return f_svgrgba(s1,s2,s3,s4) + end + else + if s1 == s2 and s1 == s3 then + return f_svggray(s1) + else + return f_svgrgb(s1,s2,s3) + end + end + elseif what == "cmyk" then + return f_cmyk(s1,s2,s3,s4) + elseif what == "gray" then + if s2 then + return f_svggraya(s1,s2) + else + return f_svggray(s1) + end + end + return f_svgname(color) + end + +end + +-- actually we can loop faster because we can go to the last one + +local grabpath, grablist do + + local f_moveto = formatters['(%N,%N)'] + local f_curveto_z = formatters['controls(%N,%N)and(%N,%N)..(%N,%N)'] + local f_curveto_n = formatters['..controls(%N,%N)and(%N,%N)..(%N,%N)'] + local f_lineto_z = formatters['(%N,%N)'] + local f_lineto_n = formatters['--(%N,%N)'] + + local m = { __index = function() return 0 end } + + grabpath = function(str) + local p = lpegmatch(p_path,str) or { } + local np = #p + local all = { entries = np, closed = false, curve = false } + if np == 0 then + return all + end + setmetatable(p,m) + local t = { } -- no real saving here if we share + local n = 0 + local a = 0 + local i = 0 + local last = "M" + local prev = last + local kind = "L" + local x, y = 0, 0 + local x1, y1 = 0, 0 + local x2, y2 = 0, 0 + local rx, ry = 0, 0 + local ar, al = 0, 0 + local as, ac = 0, nil + local mx, my = 0, 0 + 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 + 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" + mx = x + my = y + 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" + n = n + 1 ; t[n] = "--cycle" + if n > 0 then + a = a + 1 ; all[a] = concat(t,"",1,n) ; n = 0 + end + if i == np then + break + else + i = i - 1 + end + kind = prev + prev = "Z" + -- this is kind of undocumented: a close also moves back + x = mx + y = my + ::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, p + end + + -- this is a bit tricky as what are points for a mark ... the next can be simplified + -- a lot + + grablist = function(p) + local np = #p + if np == 0 then + return nil + end + local t = { } + local n = 0 + local a = 0 + local i = 0 + local last = "M" + local prev = last + local kind = "L" + local x, y = 0, 0 + local x1, y1 = 0, 0 + local x2, y2 = 0, 0 + local rx, ry = 0, 0 + local ar, al = 0, 0 + local as, ac = 0, nil + local mx, my = 0, 0 + 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 + 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 + 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 + 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] = x + n = n + 1 ; t[n] = y + last = last == "M" and "L" or "l" + prev = "M" + mx = x + my = y + goto continue + ::lineto:: + n = n + 1 ; t[n] = x + n = n + 1 ; t[n] = y + prev = "L" + goto continue + ::curveto:: + n = n + 1 ; t[n] = x + n = n + 1 ; t[n] = 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] = ac[i+4] + n = n + 1 ; t[n] = ac[i+5] + end + prev = "A" + goto continue + ::quadratic:: + n = n + 1 ; t[n] = x2 + n = n + 1 ; t[n] = y2 + x = x2 + y = y2 + prev = "C" + goto continue + ::close:: + n = n + 1 ; t[n] = mx + n = n + 1 ; t[n] = my + if i == np then + break + end + kind = prev + prev = "Z" + x = mx + y = my + ::continue:: + end + return t + end + +end + +-- todo: viewbox helper + +local s_wrapped_start = "draw image (" +local f_wrapped_stop = formatters[") shifted (0,%N) scaled %N ;"] + +local handletransform, handleviewbox do + + local sind = math.sind + + --todo: better lpeg + + local f_rotatedaround = formatters[" rotatedaround((%N,%N),%N)"] + local f_rotated = formatters[" rotated(%N)"] + local f_shifted = formatters[" shifted(%N,%N)"] + local f_slanted_x = formatters[" xslanted(%N)"] + local f_slanted_y = formatters[" yslanted(%N)"] + local f_scaled = formatters[" scaled(%N)"] + local f_xyscaled = formatters[" xyscaled(%N,%N)"] + local f_matrix = formatters[" transformed bymatrix(%N,%N,%N,%N,%N,%N)"] + + local s_transform_start = "draw image ( " + local f_transform_stop = formatters[")%s ;"] + + local function rotate(r,x,y) + if x then + return r and f_rotatedaround(x,-(y or x),-r) + elseif r then + return f_rotated(-r) + else + return "" + end + end + + local function translate(x,y) + if y then + return f_shifted(x,-y) + elseif x then + return f_shifted(x,0) + else + return "" + end + end + + local function scale(x,y) + if y then + return f_xyscaled(x,y) + elseif x then + return f_scaled(x) + else + return "" + end + end + + local function skewx(x) + if x then + return f_slanted_x(sind(-x)) + else + return "" + end + end + + local function skewy(y) + if y then + return f_slanted_y(sind(-y)) + else + return "" + end + end + + local function matrix(rx,sx,sy,ry,tx,ty) + 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? Anyway, order seems to matter. + + local p_transform = Cf ( Ct("") * ( + lpegpatterns.whitespace^0 * Cg( + C("translate") * (p_numbers / translate) -- maybe xy + + C("scale") * (p_numbers / scale) + + C("rotate") * (p_numbers / rotate) + + C("matrix") * (p_numbers / matrix) + + C("skewX") * (p_numbers / skewx) + + C("skewY") * (p_numbers / skewy) + ) + )^1, rawset) + + handletransform = function(at) + local t = at.transform + if t then + local e = lpegmatch(p_transform,t) + if e then + e = concat({ + e.rotate or "", + e.skewX or "", + e.skewY or "", + e.scale or "", + e.translate or "", + e.matrix or "", + }, " ") + return s_transform_start, f_transform_stop(e), t + end + end + end + + handleviewbox = function(v) + if v then + local x, y, w, h = lpegmatch(p_fournumbers,v) + if h then + return x, y, w, h + end + end + end + +end + +local dashed do + + -- actually commas are mandate but we're tolerant + + local f_dashed_n = formatters[" dashed dashpattern (%s ) "] + local f_dashed_y = formatters[" dashed dashpattern (%s ) shifted (%N,0) "] + + local p_number = p_optseparator/"" * 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 handlers = { } + local process = false + local root = false + local result = false + local r = false + local definitions = false + local classstyles = false + local tagstyles = false + + local tags = { + ["a"] = true, + -- ["altgGlyph"] = true, + -- ["altgGlyphDef"] = true, + -- ["altgGlyphItem"] = true, + -- ["animate"] = true, + -- ["animateColor"] = true, + -- ["animateMotion"] = true, + -- ["animateTransform"] = true, + ["circle"] = true, + ["clipPath"] = true, + -- ["color-profile"] = true, + -- ["cursor"] = true, + ["defs"] = true, + -- ["desc"] = true, + ["ellipse"] = true, + -- ["filter"] = true, + -- ["font"] = true, + -- ["font-face"] = true, + -- ["font-face-format"] = true, + -- ["font-face-name"] = true, + -- ["font-face-src"] = true, + -- ["font-face-uri"] = true, + -- ["foreignObject"] = true, + ["g"] = true, + -- ["glyph"] = true, + -- ["glyphRef"] = true, + -- ["hkern"] = true, + ["image"] = true, + ["line"] = true, + ["linearGradient"] = true, + ["marker"] = true, + -- ["mask"] = true, + -- ["metadata"] = true, + -- ["missing-glyph"] = true, + -- ["mpath"] = true, + ["path"] = true, + -- ["pattern"] = true, + ["polygon"] = true, + ["polyline"] = true, + ["radialGradient"] = true, + ["rect"] = true, + -- ["script"] = true, + -- ["set"] = true, + ["stop"] = true, + ["style"] = true, + ["svg"] = true, + -- ["switch"] = true, + ["symbol"] = true, + ["text"] = true, + -- ["textPath"] = true, + -- ["title"] = true, + ["tspan"] = true, + ["use"] = true, + -- ["view"] = true, + -- ["vkern"] = true, + } + + local function handlechains(c) + if tags[c.tg] then + local at = c.at + local dt = c.dt + if at and dt then + -- at["inkscape:connector-curvature"] = nil -- cleare entry and might prevent table growth + local estyle = rawget(at,"style") + if estyle and estyle ~= "" then + for k, v in gmatch(estyle,"%s*([^:]+):%s*([^;]+);?") do + at[k] = v + end + end + local eclass = rawget(at,"class") + if eclass and eclass ~= "" then + for c in gmatch(eclass,"[^ ]+") do + local s = classstyles[c] + if s then + for k, v in next, s do + at[k] = v + end + end + end + end + local tstyle = tagstyles[tag] + if tstyle then + for k, v in next, tstyle do + at[k] = v + end + end + if trace_path and pathtracer then + for k, v in next, pathtracer do + at[k] = v + end + end + for i=1,#dt do + local dti = dt[i] + if type(dti) == "table" then + handlechains(dti) + end + end + end + end + end + + local handlestyle do + + -- It can also be CDATA but that is probably dealt with because we only + -- check for style entries and ignore the rest. But maybe we also need + -- to check a style at the outer level? + + local p_key = C((R("az","AZ","09","__","--")^1)) + local p_spec = P("{") * C((1-P("}"))^1) * P("}") + local p_valid = Carg(1) * P(".") * p_key + Carg(2) * p_key + local p_grab = ((p_valid * p_space^0 * p_spec / rawset) + p_space^1 + P(1))^1 + + local fontspecification = css.fontspecification + + handlestyle = function(c) + local s = xmltext(c) + lpegmatch(p_grab,s,1,classstyles,tagstyles) + for k, v in next, classstyles do + local t = { } + for k, v in gmatch(v,"%s*([^:]+):%s*([^;]+);?") do + if k == "font" then + local s = fontspecification(v) + for k, v in next, s do + t["font-"..k] = v + end + else + t[k] = v + end + end + classstyles[k] = t + end + for k, v in next, tagstyles do + local t = { } + for k, v in gmatch(v,"%s*([^:]+):%s*([^;]+);?") do + if k == "font" then + local s = fontspecification(v) + for k, v in next, s do + t["font-"..k] = v + end + else + t[k] = v + end + end + tagstyles[k] = t + end + end + + function handlers.style() + -- ignore + end + + end + + -- We can have root in definitions and then do a metatable lookup but use + -- is not used that often I guess. + + local function locate(id) + local res = definitions[id] + if res then + return res + end + local ref = gsub(id,"^url%(#(.-)%)$","%1") + local ref = gsub(ref,"^#","") + -- we can make a fast id lookup + local res = xmlfirst(root,"**[@id='"..ref.."']") + if res then + definitions[id] = res + end + return res + end + + -- also locate + + local function handleclippath(at) + local clippath = at["clip-path"] + + if not clippath then + return + end + + local spec = definitions[clippath] or locate(clippath) + + -- do we really need thsi crap + if not spec then + local index = match(clippath,"(%d+)") + if index then + spec = xmlfirst(root,"clipPath["..tostring(tonumber(index) or 0).."]") + end + end + -- so far for the crap + + if not spec then + report("unknown clip %a",clippath) + return + elseif spec.tg ~= "clipPath" then + report("bad clip %a",clippath) + return + end + + ::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" + p.close = true + return p, clippath + else + return + 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" + p.close = true + return p, clippath + else + return + end + else + -- inherit? + end + end + end + + local s_shade_linear = ' withshademethod "linear" ' + local s_shade_circular = ' withshademethod "circular" ' + local f_shade_step = formatters['withshadestep ( withshadefraction %N withshadecolors(%s,%s) )'] + local f_shade_one = formatters['withprescript "sh_center_a=%N %N"'] + local f_shade_two = formatters['withprescript "sh_center_b=%N %N"'] + + local f_color = formatters['withcolor "%s"'] + local f_opacity = formatters['withtransparency (1,%N)'] + local f_pen = formatters['withpen pencircle scaled %N'] + + -- todo: gradient unfinished + -- todo: opacity but first we need groups in mp + + local function gradient(id) + local spec = definitions[id] -- no locate ! + 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 = rawget(a,"x1") + local y1 = rawget(a,"y1") + local x2 = rawget(a,"x2") + local y2 = rawget(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 = rawget(a,"cx") -- x center + local cy = rawget(a,"cy") -- y center + local r = rawget(a,"r" ) -- radius + local fx = rawget(a,"fx") -- focal points + local fy = rawget(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 + -- startcolor ? + for c in xmlcollected(spec,"/stop") do + local a = c.at + local offset = rawget(a,"offset") + local colorb = rawget(a,"stop-color") + local opacity = rawget(a,"stop-opacity") + if colorb then + colorb = thecolor(colorb) + end + if not colora then + colora = colorb + end + -- 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 + + if colora and colorb and color_a ~= "" and color_b ~= "" then + n = n + 1 ; shade[n] = f_shade_step(fraction,colora,colorb) + end + + colora = colorb + end + return concat(shade," ") + end + end + + local function drawproperties(stroke,at,opacity) + local p = at["stroke-width"] + if p then + p = f_pen(asnumber_r(p)) + end + local d = at["stroke-dasharray"] + if d == "none" then + d = nil + elseif d then + local o = at["stroke-dashoffset"] + if o and o ~= "none" then + o = asnumber_r(o) + else + o = false + end + d = dashed(d,o) + end + local c = withcolor(stroke) + local o = at["stroke-opacity"] or (opacity and at["opacity"]) + if o == "none" then + o = nil + elseif o then + o = asnumber_r(o) + if o and o ~= 1 then + o = f_opacity(o) + else + o = nil + end + end + return p, d, c, o + end + + local s_opacity_start = "draw image (" + local f_opacity_stop = formatters["setgroup currentpicture to boundingbox currentpicture withtransparency (1,%N)) ;"] + + local function sharedopacity(at) + local o = at["opacity"] + if o and o ~= "none" then + o = asnumber_r(o) + if o and o ~= 1 then + return s_opacity_start, f_opacity_stop(o) + end + end + end + + local function fillproperties(fill,at,opacity) + local c = c ~= "none" and (gradient(fill) or withcolor(fill)) or nil + local o = at["fill-opacity"] or (opacity and at["opacity"]) + if o and o ~= "none" then + o = asnumber_r(o) + if o == 1 then + return c + elseif o then + return c, f_opacity(o), o == 0 + end + end + return c + end + + -- todo: clip = [ auto | rect(llx,lly,urx,ury) ] + + local s_offset_start = "draw image ( " + local f_offset_stop = formatters[") shifted (%N,%N) ;"] + local s_rotation_start = "draw image ( " + local f_rotation_stop = formatters[") rotatedaround((0,0),-angle((%N,%N))) ;"] + local f_rotation_angle = formatters[") rotatedaround((0,0),-%N) ;"] + + local function offset(at) + local x = asnumber_vx(rawget(at,"x")) + local y = asnumber_vy(rawget(at,"y")) + if x ~= 0 or y ~= 0 then + return s_offset_start, f_offset_stop(x,y) + end + end + + local s_viewport_start = "draw image (" + local s_viewport_stop = ") ;" + local f_viewport_shift = formatters["currentpicture := currentpicture shifted (%03N,%03N);"] + local f_viewport_scale = formatters["currentpicture := currentpicture xysized (%03N,%03N);"] + local f_viewport_clip = formatters["clip currentpicture to (unitsquare xyscaled (%03N,%03N));"] + + local function viewport(x,y,w,h,noclip,scale) + r = r + 1 ; result[r] = s_viewport_start + return function() + local okay = w ~= 0 and h ~= 0 + if okay and scale then + r = r + 1 ; result[r] = f_viewport_scale(w,h) + end + if x ~= 0 or y ~= 0 then + r = r + 1 ; result[r] = f_viewport_shift(-x,y) + end + if okay and not noclip then + r = r + 1 ; result[r] = f_viewport_clip(w,-h) + end + + r = r + 1 ; result[r] = s_viewport_stop + end + end + + -- maybe forget about defs and just always locate (and then backtrack + -- over if needed) + + function handlers.defs(c) + for c in xmlcollected(c,"/*") do + local a = c.at + if a then + local id = rawget(a,"id") + if id then + definitions["#" .. id ] = c + definitions["url(#" .. id .. ")"] = c + end + end + end + end + + function handlers.symbol(c) + if uselevel == 0 then + local id = rawget(c.at,"id") + if id then + definitions["#" .. id ] = c + definitions["url(#" .. id .. ")"] = c + end + else + handlers.g(c) + end + end + + local uselevel = 0 + + function handlers.use(c) + local at = c.at + local id = rawget(at,"href") or rawget(at,"xlink:href") -- better a rawget + local res = locate(id) + if res then + -- width height ? + uselevel = uselevel + 1 + local boffset, eoffset = offset(at) + local btransform, etransform, transform = handletransform(at) + + if boffset then + r = r + 1 result[r] = boffset + end + + -- local clippath = at.clippath + + if btransform then + r = r + 1 result[r] = btransform + end + + local _transform = transform + local _clippath = clippath + at["transform"] = false + -- at["clip-path"] = false + + process(res,"/*") + + at["transform"] = _transform + -- at["clip-path"] = _clippath + + if etransform then + r = r + 1 ; result[r] = etransform + end + + if eoffset then + r = r + 1 result[r] = eoffset + end + + uselevel = uselevel - 1 + else + report("use: unknown definition %a",id) + end + end + + local f_no_draw = formatters['nodraw (%s)'] + local f_do_draw = formatters['draw (%s)'] + local f_no_fill_c = formatters['nofill (%s..cycle)'] + local f_do_fill_c = formatters['fill (%s..cycle)'] + local f_eo_fill_c = formatters['eofill (%s..cycle)'] + local f_no_fill_l = formatters['nofill (%s--cycle)'] + local f_do_fill_l = formatters['fill (%s--cycle)'] + local f_eo_fill_l = formatters['eofill (%s--cycle)'] + local f_do_fill = f_do_fill_c + local f_eo_fill = f_eo_fill_c + local f_no_fill = f_no_fill_c + local s_clip_start = 'draw image (' + local f_clip_stop_c = formatters[') ; clip currentpicture to (%s..cycle) ;'] + local f_clip_stop_l = formatters[') ; clip currentpicture to (%s--cycle) ;'] + local f_clip_stop = f_clip_stop_c + local f_eoclip_stop_c = formatters[') ; eoclip currentpicture to (%s..cycle) ;'] + local f_eoclip_stop_l = formatters[') ; eoclip currentpicture to (%s--cycle) ;'] + local f_eoclip_stop = f_eoclip_stop_c + + -- could be shared and then beginobject | endobject + + local function flushobject(object,at,c,o) + local btransform, etransform = handletransform(at) + local cpath = handleclippath(at) + + if cpath then + r = r + 1 ; result[r] = s_clip_start + end + + if btransform then + r = r + 1 ; result[r] = btransform + end + + r = r + 1 ; result[r] = f_do_draw(object) + + if c then + r = r + 1 ; result[r] = c + end + + if o then + r = r + 1 ; result[r] = o + end + + if etransform then + r = r + 1 ; result[r] = etransform + end + + r = r + 1 ; result[r] = ";" + + if cpath then + local f_done = cpath.evenodd + if cpath.curve then + f_done = f_done and f_eoclip_stop_c or f_clip_stop_c + else + f_done = f_done and f_eoclip_stop_l or f_clip_stop_l + end + r = r + 1 ; result[r] = f_done(cpath[1]) + end + end + + do + + local flush + + 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;" + + local linecaps = { butt = "butt", square = "squared", round = "rounded" } + local linejoins = { miter = "mitered", bevel = "beveled", round = "rounded" } + + local function startlineproperties(at) + local cap = at["stroke-linecap"] + local join = at["stroke-linejoin"] + local limit = at["stroke-miterlimit"] + cap = cap and linecaps [cap] + join = join and linejoins[join] + limit = limit and asnumber_r(limit) + 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 function() + at["stroke-linecap"] = false + at["stroke-linejoin"] = false + at["stroke-miterlimit"] = false + r = r + 1 ; result[r] = s_endgroup + at["stroke-linecap"] = cap + at["stroke-linejoin"] = join + at["stroke-miterlimit"] = limit + end + end + end + + -- markers are a quite rediculous thing .. let's assume simple usage for now + + function handlers.marker() + -- todo: is just a def too + end + + -- kind of local svg ... so make a generic one + -- + -- todo: combine more (offset+scale+rotation) + + local function makemarker(where,c,x1,y1,x2,y2,x3,y3,parentat) + local at = c.at + local refx = rawget(at,"refX") + local refy = rawget(at,"refY") + local width = rawget(at,"markerWidth") + local height = rawget(at,"markerHeight") + local view = rawget(at,"viewBox") + local orient = rawget(at,"orient") + -- local ratio = rawget(at,"preserveAspectRatio") + local units = asnumber(at["markerUnits"] or parentat["stroke-width"]) or 1 + + local angx = 0 + local angy = 0 + local angle = 0 + + if where == "beg" then + if orient == "auto" then -- unchecked + -- no angle + angx = abs(x2 - x3) + angy = abs(y2 - y3) + elseif orient == "auto-start-reverse" then -- checked + -- points to start + angx = -abs(x2 - x3) + angy = -abs(y2 - y3) + elseif orient then -- unchecked + angle = asnumber_r(orient) + end + elseif where == "end" then + -- funny standard .. bug turned feature? + if orient == "auto" or orient == "auto-start-reverse" then + angx = abs(x1 - x2) + angy = abs(y1 - y2) + elseif orient then -- unchecked + angle = asnumber_r(orient) + end + elseif orient then -- unchecked + angle = asnumber_r(orient) + end + -- what wins: viewbox or w/h + + refx = asnumber_x(refx) + refy = asnumber_y(refy) + + width = (width and asnumber_x(width) or 3) * units + height = (height and asnumber_y(height) or 3) * units + + local x = 0 + local y = 0 + local w = width + local h = height + + -- kind of like the main svg + + r = r + 1 ; result[r] = s_offset_start + + local wrapupviewport +-- todo : better viewbox code + local xpct, ypct, rpct + if view then + x, y, w, h = handleviewbox(view) + end + + if width ~= 0 then + w = width + end + if height ~= 0 then + h = height + end + + if h then + 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 + wrapupviewport = viewport(x,y,w,h,true,true) -- no clip + end + + -- we can combine a lot here: + + local hasref = refx ~= 0 or refy ~= 0 + local hasrot = angx ~= 0 or angy ~= 0 or angle ~= 0 + + local btransform, etransform, transform = handletransform(at) + + if btransform then + r = r + 1 ; result[r] = btransform + end + + if hasrot then + r = r + 1 ; result[r] = s_rotation_start + end + + if hasref then + r = r + 1 ; result[r] = s_offset_start + end + + local _transform = transform + at["transform"] = false + + handlers.g(c) + + at["transform"] = _transform + + if hasref then + r = r + 1 ; result[r] = f_offset_stop(-refx,refy) + end + + if hasrot then + if angle ~= 0 then + r = r + 1 ; result[r] = f_rotation_angle(angle) + else + r = r + 1 ; result[r] = f_rotation_stop(angx,angy) + end + end + + if etransform then + r = r + 1 ; result[r] = etransform + end + + if h then + percentage_x = xpct + percentage_y = ypct + percentage_r = rpct + if wrapupviewport then + wrapupviewport() + end + end + r = r + 1 ; result[r] = f_offset_stop(x2,y2) + + end + + -- do we need to metatable the attributes here? + + local function addmarkers(list,begmarker,midmarker,endmarker,at) + local n = #list + if n > 3 then + if begmarker then + local m = locate(begmarker) + if m then + makemarker("beg",m,false,false,list[1],list[2],list[3],list[4],at) + end + end + if midmarker then + local m = locate(midmarker) + if m then + for i=3,n-2,2 do + makemarker("mid",m,list[i-2],list[i-1],list[i],list[i+1],list[i+2],list[i+3],at) + end + end + end + if endmarker then + local m = locate(endmarker) + if m then + makemarker("end",m,list[n-3],list[n-2],list[n-1],list[n],false,false,at) + end + end + else + -- no line + end + end + + local function flush(shape,dofill,at,list,begmarker,midmarker,endmarker) + + local fill = dofill and (at["fill"] or "black") + local stroke = at["stroke"] or "none" + + local btransform, etransform = handletransform(at) + local cpath = handleclippath(at) + + if cpath then + r = r + 1 ; result[r] = s_clip_start + end + + local has_stroke = stroke and stroke ~= "none" + local has_fill = fill and fill ~= "none" + + local bopacity, eopacity + if has_stroke and has_fill then + bopacity, eopacity = sharedopacity(at) + end + + if bopacity then + r = r + 1 ; result[r] = bopacity + end + + if has_fill then + local color, opacity = fillproperties(fill,at,not has_stroke) + local f_xx_fill = at["fill-rule"] == "evenodd" and f_eo_fill or f_do_fill + if btransform then + r = r + 1 ; result[r] = btransform + end + r = r + 1 result[r] = f_xx_fill(shape) + if color then + r = r + 1 ; result[r] = color + end + if opacity then + r = r + 1 ; result[r] = opacity + end + r = r + 1 ; result[r] = etransform or ";" + end + + if has_stroke then + local wrapup = startlineproperties(at) + local pen, dashing, color, opacity = drawproperties(stroke,at,not has_fill) + if btransform then + r = r + 1 ; result[r] = btransform + end + r = r + 1 ; result[r] = f_do_draw(shape) + if pen then + r = r + 1 ; result[r] = pen + end + if dashing then + r = r + 1 ; result[r] = dashing + end + if color then + r = r + 1 ; result[r] = color + end + if opacity then + r = r + 1 ; result[r] = opacity + end + r = r + 1 ; result[r] = etransform or ";" + -- + if list then + addmarkers(list,begmarker,midmarker,endmarker,at) + end + -- + if wrapup then + wrapup() + end + end + + if eopacity then + r = r + 1 ; result[r] = eopacity + end + + if cpath then + r = r + 1 ; result[r] = (cpath.evenodd and f_eoclip_stop or f_clip_stop)(cpath[1]) + end + + end + + local f_rectangle = formatters['unitsquare xyscaled (%N,%N) shifted (%N,%N)'] + local f_rounded = formatters['roundedsquarexy(%N,%N,%N,%N) shifted (%N,%N)'] + local f_line = formatters['((%N,%N)--(%N,%N))'] + local f_ellipse = formatters['(fullcircle xyscaled (%N,%N) shifted (%N,%N))'] + local f_circle = formatters['(fullcircle scaled %N shifted (%N,%N))'] + + function handlers.line(c) + local at = c.at + local x1 = rawget(at,"x1") + local y1 = rawget(at,"y1") + local x2 = rawget(at,"x2") + local y2 = rawget(at,"y2") + + x1 = x1 and asnumber_vx(x1) or 0 + y1 = y1 and asnumber_vy(y1) or 0 + x2 = x2 and asnumber_vx(x2) or 0 + y2 = y2 and asnumber_vy(y2) or 0 + + flush(f_line(x1,y1,x2,y2),false,at) + end + + function handlers.rect(c) + local at = c.at + local width = rawget(at,"width") + local height = rawget(at,"height") + local x = rawget(at,"x") + local y = rawget(at,"y") + local rx = rawget(at,"rx") + local ry = rawget(at,"ry") + + width = width and asnumber_x(width) or 0 + height = height and asnumber_y(height) or 0 + x = x and asnumber_vx(x) or 0 + y = y and asnumber_vy(y) or 0 + + y = y - height + + if rx then rx = asnumber(rx) end + if ry then ry = asnumber(ry) end + + if rx or ry then + if not rx then rx = ry end + if not ry then ry = rx end + flush(f_rounded(width,height,rx,ry,x,y),true,at) + else + flush(f_rectangle(width,height,x,y),true,at) + end + end + + function handlers.ellipse(c) + local at = c.at + local cx = rawget(at,"cx") + local cy = rawget(at,"cy") + local rx = rawget(at,"rx") + local ry = rawget(at,"ry") + + cx = cx and asnumber_vx(cx) or 0 + cy = cy and asnumber_vy(cy) or 0 + rx = rx and asnumber_r (rx) or 0 + ry = ry and asnumber_r (ry) or 0 + + flush(f_ellipse(2*rx,2*ry,cx,cy),true,at) + end + + function handlers.circle(c) + local at = c.at + local cx = rawget(at,"cx") + local cy = rawget(at,"cy") + local r = rawget(at,"r") + + cx = cx and asnumber_vx(cx) or 0 + cy = cy and asnumber_vy(cy) or 0 + r = r and asnumber_r (r) or 0 + + flush(f_circle(2*r,cx,cy),true,at) + end + + local f_lineto_z = formatters['(%N,%N)'] + local f_lineto_n = formatters['--(%N,%N)'] + + local p_pair = p_optseparator * p_number_vx * p_optseparator * p_number_vy + local p_open = Cc("(") + local p_close = Carg(1) * P(true) / function(s) return s end + local p_polyline = Cs(p_open * (p_pair / f_lineto_z) * (p_pair / f_lineto_n)^0 * p_close) + local p_polypair = Ct(p_pair^0) + + local function poly(c,final) + local at = c.at + local points = rawget(at,"points") + if points then + local path = lpegmatch(p_polyline,points,1,final) + local list = nil + local begmarker = rawget(at,"marker-start") + local midmarker = rawget(at,"marker-mid") + local endmarker = rawget(at,"marker-end") + if begmarker or midmarker or endmarker then + list = lpegmatch(p_polypair,points) + end + flush(path,true,at,list,begmarker,midmarker,endmarker) + end + end + + function handlers.polyline(c) poly(c, ")") end + function handlers.polygon (c) poly(c,"--cycle)") end + + local s_image_start = "draw image (" + local s_image_stop = ") ;" + + function handlers.path(c) + local at = c.at + local d = rawget(at,"d") + if d then + local shape, l = grabpath(d) + local fill = at["fill"] or "black" + local stroke = at["stroke"] or "none" + local n = #shape + + local btransform, etransform = handletransform(at) + local cpath = handleclippath(at) + if cpath then + r = r + 1 ; result[r] = s_clip_start + end + + -- todo: image (nicer for transform too) + + if fill and fill ~= "none" then + local color, opacity = fillproperties(fill,at) + local f_xx_fill = at["fill-rule"] == "evenodd" + if shape.closed then + f_xx_fill = f_xx_fill and f_eo_fill or f_do_fill + elseif shape.curve then + f_xx_fill = f_xx_fill and f_eo_fill_c or f_do_fill_c + else + f_xx_fill = f_xx_fill and f_eo_fill_l or f_do_fill_l + end + if n == 1 then + if btransform then + r = r + 1 ; result[r] = btransform + end + r = r + 1 result[r] = f_xx_fill(shape[1]) + if color then + r = r + 1 ; result[r] = color + end + if opacity then + r = r + 1 ; result[r] = opacity + end + r = r + 1 ; result[r] = etransform or ";" + else + r = r + 1 ; result[r] = btransform or s_image_start + for i=1,n do + if i == n then + r = r + 1 ; result[r] = f_xx_fill(shape[i]) + if color then + r = r + 1 ; result[r] = color + end + if opacity then + r = r + 1 ; result[r] = opacity + end + else + r = r + 1 ; result[r] = f_no_fill(shape[i]) + end + r = r + 1 ; result[r] = ";" + end + r = r + 1 ; result[r] = etransform or s_image_stop + end + end + + if stroke and stroke ~= "none" then + local begmarker = rawget(at,"marker-start") + local midmarker = rawget(at,"marker-mid") + local endmarker = rawget(at,"marker-end") + if begmarker or midmarker or endmarker then + list = grablist(l) + end + local wrapup = startlineproperties(at) + local pen, dashing, color, opacity = drawproperties(stroke,at) + if n == 1 and not list then + if btransform then + r = r + 1 ; result[r] = btransform + end + r = r + 1 result[r] = f_do_draw(shape[1]) + if pen then + r = r + 1 ; result[r] = pen + end + if dashing then + r = r + 1 ; result[r] = dashing + end + if color then + r = r + 1 ; result[r] = color + end + if opacity then + r = r + 1 ; result[r] = opacity + end + r = r + 1 result[r] = etransform or ";" + else + r = r + 1 result[r] = btransform or "draw image (" + for i=1,n do + r = r + 1 result[r] = f_do_draw(shape[i]) + if pen then + r = r + 1 ; result[r] = pen + end + if dashing then + r = r + 1 ; result[r] = dashing + end + if color then + r = r + 1 ; result[r] = color + end + if opacity then + r = r + 1 ; result[r] = opacity + end + r = r + 1 ; result[r] = ";" + end + if list then + addmarkers(list,begmarker,midmarker,endmarker,at) + end + r = r + 1 ; result[r] = etransform or ") ;" + end + if wrapup then + wrapup() + end + end + + if cpath then + r = r + 1 ; result[r] = f_clip_stop(cpath[1]) + end + + end + end + + end + + -- kind of special + + do + + -- some day: + -- + -- specification = identifiers.jpg(data."string") + -- specification.data = data + -- inclusion takes from data + -- specification.data = false + + local f_image = formatters[ [[figure("%s") xysized (%N,%N) shifted (%N,%N)]] ] + + local nofimages = 0 + + function handlers.image(c) + local at = c.at + local im = rawget(at,"xlink:href") + if im then + local kind, data = match(im,"^data:image/([a-z]+);base64,(.*)$") + if kind == "png" then + -- ok + elseif kind == "jpeg" then + kind = "jpg" + else + kind = false + end + if kind and data then + local w = rawget(at,"width") + local h = rawget(at,"height") + local x = rawget(at,"x") + local y = rawget(at,"y") + w = w and asnumber_x(w) + h = h and asnumber_y(h) + x = x and asnumber_vx(x) or 0 + y = y and asnumber_vy(y) or 0 + nofimages = nofimages + 1 + local name = "temp-svg-image-" .. nofimages .. "." .. kind + local data = mime.decode("base64")(data) + io.savedata(name,data) + if not w or not h then + local info = graphics.identifiers[kind](data,"string") + if info then + -- todo: keep aspect ratio attribute + local xsize = info.xsize + local ysize = info.ysize + if not w then + if not h then + w = xsize + h = ysize + else + w = (h / ysize) * xsize + end + else + h = (w / xsize) * ysize + end + end + end + -- safeguard: + if not w then w = h or 1 end + if not h then h = w or 1 end + luatex.registertempfile(name) + -- done: + flushobject(f_image(name,w,h,x,y - h),at) + else + -- nothing done + end + end + end + + end + + -- these transform: g a text svg symbol + + do + + function handlers.a(c) + process(c,"/*") + end + + function handlers.g(c) -- much like flushobject so better split and share + local at = c.at + + local btransform, etransform, transform = handletransform(at) + local cpath, clippath = handleclippath(at) + + if cpath then + r = r + 1 ; result[r] = s_clip_start + end + + if btransform then + r= r + 1 result[r] = btransform + end + + local _transform = transform + local _clippath = clippath + at["transform"] = false + at["clip-path"] = false + + process(c,"/*") + + at["transform"] = _transform + at["clip-path"] = _clippath + + if etransform then + r = r + 1 ; result[r] = etransform + end + + if cpath then + local f_done = cpath.evenodd + if cpath.curve then + f_done = f_done and f_eoclip_stop_c or f_clip_stop_c + else + f_done = f_done and f_eoclip_stop_l or f_clip_stop_l + end + r = r + 1 ; result[r] = f_done(cpath[1]) + end + end + + -- this will never really work out + -- + -- todo: register text in lua in mapping with id, then draw mapping unless overloaded + -- using lmt_svglabel with family,style,weight,size,id passed + + -- nested tspans are messy: they can have displacements but in inkscape we also + -- see x and y (inner and outer element) + + -- The size is a bit of an issue. I assume that the specified size relates to the + -- designsize but we want to be able to use other fonts. + + do + + local f_styled = formatters["\\svgstyled{%s}{%s}{%s}{%s}"] + local f_colored = formatters["\\svgcolored{%.3N}{%.3N}{%.3N}{"] + local f_placed = formatters["\\svgplaced{%.3N}{%.3N}{}{"] + local f_poschar = formatters["\\svgposchar{%.3N}{%.3N}{%s}"] + local f_char = formatters["\\svgchar{%s}"] + + local f_scaled = formatters["\\svgscaled{%N}{%s}{%s}{%s}"] + local f_normal = formatters["\\svgnormal{%s}{%s}{%s}"] + local f_hashed = formatters["\\svghashed{%s}"] + + -- We move to the outer (x,y) and when we have an inner offset we + -- (need to) compensate for that outer offset. + + -- local f_text_scaled_svg = formatters['(svgtext("%s") scaled %N shifted (%N,%N))'] + -- local f_text_normal_svg = formatters['(svgtext("%s") shifted (%N,%N))'] + -- local f_text_simple_svg = formatters['svgtext("%s")'] + + local anchors = { + ["start"] = "drt", + ["end"] = "dflt", + ["middle"] = "d", + } + + local f_text_normal_svg = formatters['(textext.%s("%s") shifted (%N,%N))'] + local f_text_simple_svg = formatters['textext.%s("%s")'] + + -- or just maptext + + local f_mapped_normal_svg = formatters['(svgtext("%s") shifted (%N,%N))'] + local f_mapped_simple_svg = formatters['svgtext("%s")'] + + local cssfamily = css.family + local cssstyle = css.style + local cssweight = css.weight + local csssize = css.size + + local usedfonts = setmetatableindex(function(t,k) + local v = setmetatableindex("table") + t[k] = v + return v + end) + + local p_texescape = lpegpatterns.texescape + + -- For now as I need it for my (some 1500) test files. + + local function checkedfamily(name) + if find(name,"^.-verdana.-$") then + name = "verdana" + end + return name + end + + -- todo: only escape some chars and handle space + + local defaultsize = 10 + + local function collect(t,c,x,y,size,scale,family,tx,ty) + local at = c.at + local ax = rawget(at,"x") + local ay = rawget(at,"y") + local dx = rawget(at,"dx") + local dy = rawget(at,"dy") + local v_fill = at["fill"] + local v_family = at["font-family"] + local v_style = at["font-style"] + local v_weight = at["font-weight"] + local v_size = at["font-size"] + -- + ax = ax and asnumber_vx(ax) or x + ay = ay and asnumber_vy(ay) or y + dx = dx and asnumber_vx(dx) or 0 + dy = dy and asnumber_vy(dy) or 0 + -- + if v_family then v_family = cssfamily(v_family) end + if v_style then v_style = cssstyle (v_style) end + if v_weight then v_weight = cssweight(v_weight) end + if v_size then v_size = csssize (v_size,factors) end + -- + ax = ax - x + ay = ay - y + -- + local elayered = ax ~= 0 or ay ~= 0 or false + local eplaced = dx ~= 0 or dy ~= 0 or false + + local usedsize, usedscaled + + if elayered then + -- we're now at the outer level again so we need to scale + -- back to the outer level values + t[#t+1] = formatters["\\svgsetlayer{%0N}{%0N}{"](ax,-ay) + usedsize = v_size or defaultsize + usedscale = usedsize / defaultsize + else + -- we're nested so we can be scaled + usedsize = v_size or size + usedscale = (usedsize / defaultsize) / scale + end + -- + -- print("element ",c.tg) + -- print(" layered ",elayered) + -- print(" font size ",v_size) + -- print(" parent size ",size) + -- print(" parent scale",scale) + -- print(" used size ",usedsize) + -- print(" used scale ",usedscale) + -- + if eplaced then + t[#t+1] = f_placed(dx,dy) + end + -- + if not v_family then v_family = family end + if not v_weight then v_weight = "normal" end + if not v_style then v_style = "normal" end + -- + if v_family then + v_family = fonts.names.cleanname(v_family) + v_family = checkedfamily(v_family) + end + -- + usedfonts[v_family][v_weight][v_style] = true + -- +-- if usedscale == 1 then +-- t[#t+1] = f_normal( v_family,v_weight,v_style) +-- else + t[#t+1] = f_scaled(usedscale,v_family,v_weight,v_style) +-- end + t[#t+1] = "{" + -- + local ecolored = v_fill and v_fill ~= "" or false + if ecolored then + -- todo cmyk + local r, g, b = colorcomponents(v_fill) + if r and g and b then + t[#t+1] = f_colored(r,g,b) + else + ecolored = false + end + end + -- + local dt = c.dt + local nt = #dt + for i=1,nt do + local di = dt[i] + if type(di) == "table" then + -- can be a tspan (should we pass dx too) + collect(t,di,x,y,usedsize,usedscale,v_family) + else + if i == 1 then + di = gsub(di,"^%s+","") + end + if i == nt then + di = gsub(di,"%s+$","") + end + local chars = utfsplit(di) + if svghash then + di = f_hashed(svghash[di]) + elseif tx then + for i=1,#chars do + chars[i] = f_poschar( + (tx[i] or 0) - x, + (ty[i] or 0) - y, + utfbyte(chars[i]) + ) + end + di = "{" .. concat(chars) .. "}" + else + -- this needs to be texescaped ! and even quotes and newlines + -- or we could register it but that's a bit tricky as we nest + -- and don't know what we can expect here + -- di = lpegmatch(p_texescape,di) or di + for i=1,#chars do + chars[i] = f_char(utfbyte(chars[i])) + end + di = concat(chars) + end + t[#t+1] = di + end + end + -- + if ecolored then + t[#t+1] = "}" + end + -- + t[#t+1] = "}" + -- + if eplaced then + t[#t+1] = "}" + end + if elayered then + t[#t+1] = "}" + end + -- + return t + end + + local s_startlayer = "\\svgstartlayer " + local s_stoplayer = "\\svgstoplayer " + + function handlers.text(c) + local only = fullstrip(xmltextonly(c)) + -- if metapost.processing() then + local at = c.at + local x = rawget(at,"x") + local y = rawget(at,"y") + + local tx = asnumber_vx_t(x) + local ty = asnumber_vy_t(y) + + x = tx[1] or 0 -- catch bad x/y spec + y = ty[1] or 0 -- catch bad x/y spec + + local v_fill = at["fill"] + if not v_fill or v_fill == "none" then + v_fill = "black" + end + local color, opacity, invisible = fillproperties(v_fill,at) + local anchor = anchors[at["text-anchor"] or "start"] or "drt" + local r = metapost.remappedtext(only) + if r then + if x == 0 and y == 0 then + only = f_mapped_simple_svg(r.index) + else + only = f_mapped_normal_svg(r.index,x,y) + end + flushobject(only,at,color,opacity) + if trace_text then + report("text: %s",only) + end + elseif not invisible then -- can be an option + local scale = 1 + local textid = 0 + local result = { } + local nx = #tx + local ny = #ty + -- + result[#result+1] = s_startlayer + if nx > 1 or ny > 1 then + concat(collect(result,c,x,y,defaultsize,1,"serif",tx,ty)) + else + concat(collect(result,c,x,y,defaultsize,1,"serif")) + end + result[#result+1] = s_stoplayer + result = concat(result) + if x == 0 and y == 0 then + result = f_text_simple_svg(anchor,result) + else + result = f_text_normal_svg(anchor,result,x,y) + end + flushobject(result,at,color,opacity) + if trace_text then + report("text: %s",result) + end + elseif trace_text then + report("invisible text: %s",only) + end + -- elseif trace_text then + -- report("ignored text: %s",only) + -- end + end + + function metapost.reportsvgfonts() + for family, weights in sortedhash(usedfonts) do + for weight, styles in sortedhash(weights) do + for style in sortedhash(styles) do + report("used font: %s-%s-%s",family,weight,style) + end + end + end + end + + statistics.register("used svg fonts",function() + if next(usedfonts) then + -- also in log file + logs.startfilelogging(report,"used svg fonts") + local t = { } + for family, weights in sortedhash(usedfonts) do + for weight, styles in sortedhash(weights) do + for style in sortedhash(styles) do + report("%s-%s-%s",family,weight,style) + t[#t+1] = formatters["%s-%s-%s"](family,weight,style) + end + end + end + logs.stopfilelogging() + return concat(t," ") + end + end) + + end + + function handlers.svg(c,x,y,w,h,noclip,notransform,normalize) + local at = c.at + + local wrapupviewport + local bhacked + local ehacked + local wd = w + -- local ex, em + local xpct, ypct, rpct + + local btransform, etransform, transform = handletransform(at) + + if trace then + report("view: %s, xpct %N, ypct %N","before",percentage_x,percentage_y) + end + + local viewbox = at.viewBox + + if viewbox then + x, y, w, h = handleviewbox(viewbox) + if trace then + report("viewbox: x %N, y %N, width %N, height %N",x,y,w,h) + end + end + if not w or not h or w == 0 or h == 0 then + noclip = true + 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 %N, ypct %N","inside",percentage_x,percentage_y) + end + wrapupviewport = viewport(x,y,w,h,noclip) + end + -- todo: combine transform and offset here + + -- some fonts need this (bad transforms + viewbox) + if v and normalize and w and wd and w ~= wd and w > 0 and wd > 0 then + bhacked = s_wrapped_start + ehacked = f_wrapped_stop(y or 0,wd/w) + end + if btransform then + r = r + 1 ; result[r] = btransform + end + if bhacked then + r = r + 1 ; result[r] = bhacked + end + local boffset, eoffset = offset(at) + if boffset then + r = r + 1 result[r] = boffset + end + + at["transform"] = false + at["viewBox"] = false + + process(c,"/*") + + at["transform"] = transform + at["viewBox"] = viewbox + + if eoffset then + r = r + 1 result[r] = eoffset + end + if ehacked then + r = r + 1 ; result[r] = ehacked + 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 %N, ypct %N","after",percentage_x,percentage_y) + end + end + + end + + process = function(x,p) + for c in xmlcollected(x,p) do + local tg = c.tg + local h = handlers[c.tg] + if h then + h(c) + end + end + end + + -- For huge inefficient files there can be lots of garbage to collect so + -- maybe we should run the collector when a file is larger than say 50K. + + function metapost.svgtomp(specification,pattern,notransform,normalize) + 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 = svg + result = { } + r = 0 + definitions = { } + tagstyles = { } + classstyles = { } + colormap = specification.colormap + usedcolors = trace_colors and setmetatableindex("number") or false + for s in xmlcollected(c,"style") do -- can also be in a def, so let's play safe + handlestyle(c) + end + handlechains(c) + xmlinheritattributes(c) -- put this in handlechains + handlers.svg ( + c, + specification.x, + specification.y, + specification.width, + specification.height, + specification.noclip, + notransform, + normalize, + specification.remap + ) + if trace_result then + report("result graphic:\n %\n t",result) + end + if usedcolors and next(usedcolors) then + report("graphic %a uses colors: %s",specification.id or "unknown",table.sequenced(usedcolors)) + end + mps = concat(result," ") + root = false + result = false + r = false + definitions = false + tagstyles = false + classstyles = false + colormap = false + else + report("missing svg root element") + end + else + report("bad svg blob") + end + return mps + end + +end + +-- These helpers might move to their own module .. some day ... also they will become +-- a bit more efficient, because we now go to mp and back which is kind of redundant, +-- but for now it will do. + +do + + local bpfactor = number.dimenfactors.bp + + function metapost.includesvgfile(filename,offset) -- offset in sp + if lfs.isfile(filename) then + context.startMPcode("doublefun") + context('draw lmt_svg [ filename = "%s", offset = %N ] ;',filename,(offset or 0)*bpfactor) + context.stopMPcode() + end + end + + function metapost.includesvgbuffer(name,offset) -- offset in sp + context.startMPcode("doublefun") + context('draw lmt_svg [ buffer = "%s", offset = %N ] ;',name or "",(offset or 0)*bpfactor) + context.stopMPcode() + end + + interfaces.implement { + name = "includesvgfile", + actions = metapost.includesvgfile, + arguments = { "string", "dimension" }, + } + + interfaces.implement { + name = "includesvgbuffer", + actions = metapost.includesvgbuffer, + arguments = { "string", "dimension" }, + } + + function metapost.showsvgpage(data) + local dd = data.data + if not dd then + local fn = data.filename + dd = fn and table.load(fn) + end + if type(dd) == "table" then + local comment = data.comment + local offset = data.pageoffset + local index = data.index + local first = math.max(index or 1,1) + local last = math.min(index or #dd,#dd) + for i=first,last do + local d = setmetatableindex( { + data = dd[i], + comment = comment and i or false, + pageoffset = offset or nil, + }, data) + metapost.showsvgpage(d) + end + elseif data.method == "code" then + context.startMPcode(doublefun) + context(metapost.svgtomp(data)) + context.stopMPcode() + 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.typesvgpage(data) + local dd = data.data + if not dd then + local fn = data.filename + dd = fn and table.load(fn) + end + if type(dd) == "table" then + local index = data.index + if index and index > 0 and index <= #dd then + data = dd[index] + else + data = nil + end + end + if type(data) == "string" and data ~= "" then + buffers.assign("svgpage",data) + context.typebuffer ({ "svgpage" }, { option = "XML", strip = "yes" }) + end + end + + function metapost.svgtopdf(data,...) + local mps = metapost.svgtomp(data,...) + if mps then + -- todo: special instance, only basics needed + local pdf = metapost.simple("metafun",mps,true,false,"svg") + if pdf then + return pdf + else + -- message + end + else + -- message + end + 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. + + local decompress = gzip.decompress + local compress = gzip.compress + + 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] = "\\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 + if decompress then + data = decompress(data) or data + end + 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,true,true) 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 + if decompress then + data = decompress(data) or data + end + 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 .. "']" + local mpcode = metapost.svgtomp(specification,pattern,true,true) or "" + if mpcode ~= "" and compress then + mpcode = compress(mpcode) or mpcode + end + mpshapes[index] = mpcode + end + end + end + if report_svg then + statistics.stoptiming(mpshapes) + report_svg("svg conversion time %s",statistics.elapsedseconds(mpshapes)) + end + return mpshapes + end + + function metapost.svgglyphtomp(fontname,unicode) + if fontname and unicode then + local id = fonts.definers.internal { name = fontname } + if id then + local tfmdata = fonts.hashes.identifiers[id] + if tfmdata then + local properties = tfmdata.properties + local svg = properties.svg + local hash = svg and svg.hash + local timestamp = svg and svg.timestamp + if hash then + local svgfile = containers.read(fonts.handlers.otf.svgcache,hash) + local svgshapes = svgfile and svgfile.svgshapes + if svgshapes then + if type(unicode) == "string" then + unicode = utfbyte(unicode) + end + local chardata = tfmdata.characters[unicode] + local index = chardata and chardata.index + if index then + for i=1,#svgshapes do + local entry = svgshapes[i] + if index >= entry.first and index <= entry.last then + local data = entry.data + if data then + local root = xml.convert(gzip.decompress(data) or data) + return metapost.svgtomp ( + { + data = root, + x = 0, + y = 1000, + width = 1000, + height = 1000, + noclip = true, + }, + "/svg[@id='glyph" .. index .. "']", + true, + true + ) + end + end + end + end + end + end + end + end + end + end + +end diff --git a/tex/context/base/mkiv/mlib-svg.lua b/tex/context/base/mkiv/mlib-svg.lua deleted file mode 100644 index 4c4122476..000000000 --- a/tex/context/base/mkiv/mlib-svg.lua +++ /dev/null @@ -1,3277 +0,0 @@ -if not modules then modules = { } end modules ['mlib-svg'] = { - version = 1.001, - optimize = true, - 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", -} - --- Just a few notes: --- --- 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. I need to optimize this a bit but will do that once I'm satisfied --- with the outcome and don't need more hooks and plugs. At some point I will --- optimize the MetaPost part because now we probably have more image wrapping --- than needed. --- --- 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). --- --- 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. So in the end we now have (1) attributes for properties (which is --- nice and clean and what attributes are for, (2) a style attribute that needs to --- be parsed, (3) classes that map to styles and (4) element related styles, plus a --- kind of inheritance (given the limited number of elements sticking to only as --- wrapper would have made much sense. Anyway, we need to deal with it. With all --- these style things going on, one can wonder where it will end. Basically svg --- became just a html element that way and less clean too. The same is true for --- tspan, which means that text itself is nested xml. --- --- We can do a direct conversion to PDF but then we also loose the abstraction which --- in the future will be used, and for fonts we need to spawn out to TeX anyway, so --- the little overhead of calling MetaPost is okay I guess. Also, we want to --- overload labels, share fonts with the main document, etc. and are not aiming at a --- general purpose SVG converter. For going to PDF one can just use InkScape. --- --- Written with Anne Clark on speakers as distraction. --- --- Todo when I run into an example (but ony when needed and reasonable): --- --- var(color,color) --- --color --- currentColor : when i run into an example --- a bit more shading --- clip = [ auto | rect(llx,lly,urx,ury) ] (in svg) --- xlink url ... whatever --- masks --- opacity per group (i need to add that to metafun first, inefficient pdf but --- maybe filldraw can help here) --- --- Maybe in metafun: --- --- penciled n -> withpen pencircle scaled n --- applied (...) -> transformed bymatrix (...) --- withopacity n -> withtransparency (1,n) - --- When testing mbo files: --- --- empty paths --- missing control points --- funny fontnames like abcdefverdana etc --- paths representing glyphs but also with style specs --- all kind of attributes --- very weird and inefficient shading - --- One can run into pretty crazy images, like lines that are fills being clipped --- to some width. That's the danger of hiding yourself behind an interface I guess. - -local rawget, rawset, type, tonumber, tostring, next, setmetatable = rawget, rawset, type, tonumber, tostring, next, setmetatable - -local P, S, R, C, Ct, Cs, Cc, Cp, Cg, Cf, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Cc, lpeg.Cp, lpeg.Cg, lpeg.Cf, lpeg.Carg - -local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns -local sqrt, abs = math.sqrt, math.abs -local concat, setmetatableindex, sortedhash = table.concat, table.setmetatableindex, table.sortedhash -local gmatch, gsub, find, match, rep = string.gmatch, string.gsub, string.find, string.match, string.rep -local formatters, fullstrip = string.formatters, string.fullstrip -local utfsplit, utfbyte = utf.split, utf.byte - -local xmlconvert, xmlcollected, xmlcount, xmlfirst, xmlroot = xml.convert, xml.collected, xml.count, xml.first, xml.root -local xmltext, xmltextonly = xml.text, xml.textonly -local css = xml.css or { } -- testing - -local function xmlinheritattributes(c,pa) - local at = c.at - local dt = c.dt - if at and dt then - if pa then - setmetatableindex(at,pa) - end - for i=1,#dt do - local dti = dt[i] - if type(dti) == "table" then - xmlinheritattributes(dti,at) - end - end - else - -- comment of so - end -end - -xml.inheritattributes = xmlinheritattributes - --- Maybe some day helpers will move to the metapost.svg namespace! - -metapost = metapost or { } -local metapost = metapost -local context = context - -local report = logs.reporter("metapost","svg") - -local trace = false trackers.register("metapost.svg", function(v) trace = v end) -local trace_text = false trackers.register("metapost.svg.text", function(v) trace_text = v end) -local trace_path = false trackers.register("metapost.svg.path", function(v) trace_path = v end) -local trace_result = false trackers.register("metapost.svg.result", function(v) trace_result = v end) -local trace_colors = false trackers.register("metapost.svg.colors", function(v) trace_colors = v end) - -local pathtracer = { - ["stroke"] = "darkred", - ["stroke-opacity"] = ".5", - ["stroke-width"] = ".5", - ["fill"] = "darkgray", - ["fill-opacity"] = ".75", -} - --- This is just an experiment. Todo: reset hash etc. Also implement --- an option handler. - -local svghash = false do - - local svglast = 0 - local svglist = false - - local function checkhash(t,k) - local n = svglast + 1 - svglast = n - svglist[n] = k - t[k] = n - return n - end - - function metapost.startsvghashing() - svglast = 0 - svglist = { } - svghash = setmetatableindex(checkhash) - end - - function metapost.stopsvghashing() - svglast = 0 - svglist = false - svghash = false - end - - interfaces.implement { - name = "svghashed", - arguments = "integer", - actions = function(n) - local t = svglist and svglist[n] - if t then - context(t) - end - end - } - -end - --- We have quite some closures because otherwise we run into the local variable --- limitations. It doesn't always look pretty now, sorry. I'll clean up this mess --- some day (the usual nth iteration of code). --- --- 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 and possible --- division by zero). 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 a2c do - - local pi, sin, cos, tan, asin, abs = math.pi, math.sin, math.cos, math.tan, math.asin, math.abs - - local d120 = (pi * 120) / 180 - local pi2 = 2 * pi - - a2c = function(x1, y1, rx, ry, angle, large, sweep, x2, y2, f1, f2, cx, cy) - - if (rx == 0 or ry == 0 ) or (x1 == x2 and y1 == y2) then - return { x1, y1, x2, y2, x2, y2 } - end - - local recursive = f1 - 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 - -end - --- We share some patterns. - -local p_digit = lpegpatterns.digit -local p_hexdigit = lpegpatterns.hexdigit -local p_space = lpegpatterns.whitespace - -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 - --- incredible: we can find .123.456 => 0.123 0.456 ... - -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_optseparator = S("\t\n\r ,")^0 -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)) - * (P("e") * S("+-")^0 * p_digit^1)^-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 function asnumber (s) return s and lpegmatch(p_number, s) or 0 end -local function asnumber_r (s) return s and lpegmatch(p_number_r, s) or 0 end -local function asnumber_x (s) return s and lpegmatch(p_number_x, s) or 0 end -local function asnumber_y (s) return s and lpegmatch(p_number_y, s) or 0 end -local function asnumber_vx(s) return s and lpegmatch(p_number_vx,s) or 0 end -local function asnumber_vy(s) return s and lpegmatch(p_number_vy,s) or 0 end - -local p_number_vx_t = Ct { (p_number_vx + p_separator)^1 } -local p_number_vy_t = Ct { (p_number_vy + p_separator)^1 } - -local zerotable = { 0 } - -local function asnumber_vx_t(s) return s and lpegmatch(p_number_vx_t,s) or zerotable end -local function asnumber_vy_t(s) return s and lpegmatch(p_number_vy_t,s) or zerotable end - -local p_numbersep = p_number_n + p_separator -local p_numbers = p_optseparator * P("(") * p_numbersep^0 * p_optseparator * P(")") -local p_fournumbers = p_numbersep^4 -local p_path = Ct ( ( - p_command_xy * (p_optseparator * p_number_vx * - p_optseparator * p_number_vy )^1 - + p_command_x * (p_optseparator * p_number_vx )^1 - + p_command_y * (p_optseparator * p_number_vy )^1 - + p_command_a * (p_optseparator * p_number_vx * - p_optseparator * p_number_vy * - p_optseparator * p_number_r * - p_optseparator * p_number_n * -- flags - p_optseparator * p_number_n * -- flags - p_optseparator * p_number_vx * - p_optseparator * p_number_vy )^1 - + p_command - + p_separator -)^1 ) - --- We can actually use the svg color definitions from the tex end but maybe a user --- doesn't want those replace the normal definitions. --- --- 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] - -local colormap = false - -local function prepared(t) - if type(t) == "table" then - local mapping = t.mapping or { } - local mapper = t.mapper - local colormap = setmetatableindex(mapping) - if mapper then - setmetatableindex(colormap,function(t,k) - local v = mapper(k) - t[k] = v or k - return v - end) - end - return colormap - else - return false - end -end - -local colormaps = setmetatableindex(function(t,k) - local v = false - if type(k) == "string" then - v = prepared(table.load(k)) -- todo: same path as svg file - elseif type(k) == "table" then - v = prepared(k) - k = k.name or k - end - t[k] = v - return v -end) - -function metapost.svgcolorremapper(colormap) - return colormaps[colormap] -end - --- todo: cache colors per image / remapper - -local colorcomponents, withcolor, thecolor, usedcolors do - - local svgcolors = { - aliceblue = 0xF0F8FF, antiquewhite = 0xFAEBD7, aqua = 0x00FFFF, aquamarine = 0x7FFFD4, - azure = 0xF0FFFF, beige = 0xF5F5DC, bisque = 0xFFE4C4, black = 0x000000, - blanchedalmond = 0xFFEBCD, blue = 0x0000FF, blueviolet = 0x8A2BE2, brown = 0xA52A2A, - burlywood = 0xDEB887, cadetblue = 0x5F9EA0, hartreuse = 0x7FFF00, chocolate = 0xD2691E, - coral = 0xFF7F50, cornflowerblue = 0x6495ED, cornsilk = 0xFFF8DC, crimson = 0xDC143C, - cyan = 0x00FFFF, darkblue = 0x00008B, darkcyan = 0x008B8B, darkgoldenrod = 0xB8860B, - darkgray = 0xA9A9A9, darkgreen = 0x006400, darkgrey = 0xA9A9A9, darkkhaki = 0xBDB76B, - darkmagenta = 0x8B008B, darkolivegreen = 0x556B2F, darkorange = 0xFF8C00, darkorchid = 0x9932CC, - darkred = 0x8B0000, darksalmon = 0xE9967A, darkseagreen = 0x8FBC8F, darkslateblue = 0x483D8B, - darkslategray = 0x2F4F4F, darkslategrey = 0x2F4F4F, darkturquoise = 0x00CED1, darkviolet = 0x9400D3, - deeppink = 0xFF1493, deepskyblue = 0x00BFFF, dimgray = 0x696969, dimgrey = 0x696969, - dodgerblue = 0x1E90FF, firebrick = 0xB22222, floralwhite = 0xFFFAF0, forestgreen = 0x228B22, - fuchsia = 0xFF00FF, gainsboro = 0xDCDCDC, ghostwhite = 0xF8F8FF, gold = 0xFFD700, - goldenrod = 0xDAA520, gray = 0x808080, green = 0x008000, greenyellow = 0xADFF2F, - grey = 0x808080, honeydew = 0xF0FFF0, hotpink = 0xFF69B4, indianred = 0xCD5C5C, - indigo = 0x4B0082, ivory = 0xFFFFF0, khaki = 0xF0E68C, lavender = 0xE6E6FA, - lavenderblush = 0xFFF0F5, lawngreen = 0x7CFC00, lemonchiffon = 0xFFFACD, lightblue = 0xADD8E6, - lightcoral = 0xF08080, lightcyan = 0xE0FFFF, lightgoldenrodyellow = 0xFAFAD2, lightgray = 0xD3D3D3, - lightgreen = 0x90EE90, lightgrey = 0xD3D3D3, lightpink = 0xFFB6C1, lightsalmon = 0xFFA07A, - lightseagreen = 0x20B2AA, lightskyblue = 0x87CEFA, lightslategray = 0x778899, lightslategrey = 0x778899, - lightsteelblue = 0xB0C4DE, lightyellow = 0xFFFFE0, lime = 0x00FF00, limegreen = 0x32CD32, - linen = 0xFAF0E6, magenta = 0xFF00FF, maroon = 0x800000, mediumaquamarine = 0x66CDAA, - mediumblue = 0x0000CD, mediumorchid = 0xBA55D3, mediumpurple = 0x9370DB, mediumseagreen = 0x3CB371, - mediumslateblue = 0x7B68EE, mediumspringgreen = 0x00FA9A, mediumturquoise = 0x48D1CC, mediumvioletred = 0xC71585, - midnightblue = 0x191970, mintcream = 0xF5FFFA, mistyrose = 0xFFE4E1, moccasin = 0xFFE4B5, - navajowhite = 0xFFDEAD, navy = 0x000080, oldlace = 0xFDF5E6, olive = 0x808000, - olivedrab = 0x6B8E23, orange = 0xFFA500, orangered = 0xFF4500, orchid = 0xDA70D6, - palegoldenrod = 0xEEE8AA, palegreen = 0x98FB98, paleturquoise = 0xAFEEEE, palevioletred = 0xDB7093, - papayawhip = 0xFFEFD5, peachpuff = 0xFFDAB9, peru = 0xCD853F, pink = 0xFFC0CB, - plum = 0xDDA0DD, powderblue = 0xB0E0E6, purple = 0x800080, red = 0xFF0000, - rosybrown = 0xBC8F8F, royalblue = 0x4169E1, saddlebrown = 0x8B4513, salmon = 0xFA8072, - sandybrown = 0xF4A460, seagreen = 0x2E8B57, seashell = 0xFFF5EE, sienna = 0xA0522D, - silver = 0xC0C0C0, skyblue = 0x87CEEB, slateblue = 0x6A5ACD, slategray = 0x708090, - slategrey = 0x708090, snow = 0xFFFAFA, springgreen = 0x00FF7F, steelblue = 0x4682B4, - tan = 0xD2B48C, teal = 0x008080, thistle = 0xD8BFD8, tomato = 0xFF6347, - turquoise = 0x40E0D0, violet = 0xEE82EE, wheat = 0xF5DEB3, white = 0xFFFFFF, - whitesmoke = 0xF5F5F5, yellow = 0xFFFF00, yellowgreen = 0x9ACD32, - } - - local f_rgb = formatters['withcolor svgcolor(%.3N,%.3N,%.3N)'] - local f_cmyk = formatters['withcolor svgcmyk(%.3N,%.3N,%.3N,%.3N)'] - local f_gray = formatters['withcolor svggray(%.3N)'] - local f_rgba = formatters['withcolor svgcolor(%.3N,%.3N,%.3N) withtransparency (1,%.3N)'] - local f_graya = formatters['withcolor svggray(%.3N) withtransparency (1,%.3N)'] - local f_name = formatters['withcolor "%s"'] - local f_svgrgb = formatters['svgcolor(%.3N,%.3N,%.3N)'] - local f_svgcmyk = formatters['svgcmyk(%.3N,%.3N,%.3N,%.3N)'] - local f_svggray = formatters['svggray(%.3N)'] - local f_svgname = formatters['"%s"'] - - local extract = bit32.extract - - local triplets = setmetatableindex(function(t,k) - -- we delay building all these strings - local v = svgcolors[k] - if v then - v = { extract(v,16,8)/255, extract(v,8,8)/255, extract(v,0,8)/255 } - else - v = false - end - t[k] = v - return v - end) - - local p_fraction = C(p_number) * C("%")^-1 / function(a,b) return tonumber(a) / (b and 100 or 255) end - local p_angle = C(p_number) * P("deg")^0 / function(a) return tonumber(a) end - local p_percent = C(p_number) * P("%") / function(a) return tonumber(a) / 100 end - local p_absolute = C(p_number) / tonumber - - local p_left = P("(") - local p_right = P(")") - local p_a = P("a")^-1 - local p_h_a_color = p_left - * p_angle - * p_separator * p_percent - * p_separator * p_percent - * p_separator^0 * p_absolute^0 - * p_right - - local colors = attributes.colors - local colorvalues = colors.values - local colorindex = attributes.list[attributes.private('color')] - local hsvtorgb = colors.hsvtorgb - local hwbtorgb = colors.hwbtorgb - local forcedmodel = colors.forcedmodel - - local p_splitcolor = - P("#") * C(p_hexdigit*p_hexdigit)^1 / function(r,g,b) - if not r then - return "gray", 0 - elseif not (g and b) then - return "gray", tonumber(r or "0", 16) / 255 or 0 - else - return "rgb", - tonumber(r or "0", 16) / 255 or 0, - tonumber(g or "0", 16) / 255 or 0, - tonumber(b or "0", 16) / 255 or 0 - end - end - + P("rgb") * p_a - * p_left * (p_fraction + p_separator)^-3 * (p_absolute + p_separator)^0 * p_right / function(r,g,b,a) - return "rgb", r or 0, g or 0, b or 0, a or false - end - + P("cmyk") - * p_left * (p_absolute + p_separator)^0 * p_right / function(c,m,y,k) - return "cmyk", c or 0, m or 0, y or 0, k or 0 - end - + P("hsl") * p_a - * p_h_a_color / function(h,s,l,a) - local r, g, b = hsvtorgb(h,s,l,a) - return "rgb", r or 0, g or 0, b or 0, a or false - end - + P("hwb") * p_a - * p_h_a_color / function(h,w,b,a) - local r, g, b = hwbtorgb(h,w,b) - return "rgb", r or 0, g or 0, b or 0, a or false - end - - function metapost.svgsplitcolor(color) - if type(color) == "string" then - local what, s1, s2, s3, s4 = lpegmatch(p_splitcolor,color) - if not what then - local t = triplets[color] - if t then - what, s1, s2, s3 = "rgb", t[1], t[2], t[3] - end - end - return what, s1, s2, s3, s4 - else - return "gray", 0, false - end - end - - local function registeredcolor(name) - local color = colorindex[name] - if color then - local v = colorvalues[color] - local t = forcedmodel(v[1]) - if t == 2 then - return "gray", v[2] - elseif t == 3 then - return "rgb", v[3], v[4], v[5] - elseif t == 4 then - return "cmyk", v[6], v[7], v[8], v[9] - else - -- - end - end - end - - -- we can have a fast check for #000000 - - local function validcolor(color) - if usedcolors then - usedcolors[color] = usedcolors[color] + 1 - end - if colormap then - local c = colormap[color] - local t = type(c) - if t == "table" then - local what = t[1] - if what == "rgb" then - return - what, - tonumber(t[2]) or 0, - tonumber(t[3]) or 0, - tonumber(t[4]) or 0, - tonumber(t[4]) or false - elseif what == "cmyk" then - return - what, - tonumber(t[2]) or 0, - tonumber(t[3]) or 0, - tonumber(t[4]) or 0, - tonumber(t[5]) or 0 - elseif what == "gray" then - return - what, - tonumber(t[2]) or 0, - tonumber(t[3]) or false - end - elseif t == "string" then - color = c - end - end - local what, s1, s2, s3, s4 = registeredcolor(color) - if what then - return what, s1, s2, s3, s4 - end - what, s1, s2, s3, s4 = lpegmatch(p_splitcolor,color) - if not what then - local t = triplets[color] - if t then - s1, s3, s3 = t[1], t[2], t[3] - what = "rgb" - end - end - return what, s1, s2, s3, s4 - end - - colorcomponents = function(color) - local what, s1, s2, s3, s4 = validcolor(color) - return s1, s2, s3, s4 -- so 4 means cmyk - end - - withcolor = function(color) - local what, s1, s2, s3, s4 = validcolor(color) - -- print(color,what, s1, s2, s3, s4) - if what == "rgb" then - if s4 then - if s1 == s2 and s1 == s3 then - return f_graya(s1,s4) - else - return f_rgba(s1,s2,s3,s4) - end - else - if s1 == s2 and s1 == s3 then - return f_gray(s1) - else - return f_rgb(s1,s2,s3) - end - end - elseif what == "cmyk" then - return f_cmyk(s1,s2,s3,s4) - elseif what == "gray" then - if s2 then - return f_graya(s1,s2) - else - return f_gray(s1) - end - end - return f_name(color) - end - - thecolor = function(color) - local what, s1, s2, s3, s4 = validcolor(color) - if what == "rgb" then - if s4 then - if s1 == s2 and s1 == s3 then - return f_svggraya(s1,s4) - else - return f_svgrgba(s1,s2,s3,s4) - end - else - if s1 == s2 and s1 == s3 then - return f_svggray(s1) - else - return f_svgrgb(s1,s2,s3) - end - end - elseif what == "cmyk" then - return f_cmyk(s1,s2,s3,s4) - elseif what == "gray" then - if s2 then - return f_svggraya(s1,s2) - else - return f_svggray(s1) - end - end - return f_svgname(color) - end - -end - --- actually we can loop faster because we can go to the last one - -local grabpath, grablist do - - local f_moveto = formatters['(%N,%N)'] - local f_curveto_z = formatters['controls(%N,%N)and(%N,%N)..(%N,%N)'] - local f_curveto_n = formatters['..controls(%N,%N)and(%N,%N)..(%N,%N)'] - local f_lineto_z = formatters['(%N,%N)'] - local f_lineto_n = formatters['--(%N,%N)'] - - local m = { __index = function() return 0 end } - - grabpath = function(str) - local p = lpegmatch(p_path,str) or { } - local np = #p - local all = { entries = np, closed = false, curve = false } - if np == 0 then - return all - end - setmetatable(p,m) - local t = { } -- no real saving here if we share - local n = 0 - local a = 0 - local i = 0 - local last = "M" - local prev = last - local kind = "L" - local x, y = 0, 0 - local x1, y1 = 0, 0 - local x2, y2 = 0, 0 - local rx, ry = 0, 0 - local ar, al = 0, 0 - local as, ac = 0, nil - local mx, my = 0, 0 - 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 - 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" - mx = x - my = y - 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" - n = n + 1 ; t[n] = "--cycle" - if n > 0 then - a = a + 1 ; all[a] = concat(t,"",1,n) ; n = 0 - end - if i == np then - break - else - i = i - 1 - end - kind = prev - prev = "Z" - -- this is kind of undocumented: a close also moves back - x = mx - y = my - ::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, p - end - - -- this is a bit tricky as what are points for a mark ... the next can be simplified - -- a lot - - grablist = function(p) - local np = #p - if np == 0 then - return nil - end - local t = { } - local n = 0 - local a = 0 - local i = 0 - local last = "M" - local prev = last - local kind = "L" - local x, y = 0, 0 - local x1, y1 = 0, 0 - local x2, y2 = 0, 0 - local rx, ry = 0, 0 - local ar, al = 0, 0 - local as, ac = 0, nil - local mx, my = 0, 0 - 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 - 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 - 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 - 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] = x - n = n + 1 ; t[n] = y - last = last == "M" and "L" or "l" - prev = "M" - mx = x - my = y - goto continue - ::lineto:: - n = n + 1 ; t[n] = x - n = n + 1 ; t[n] = y - prev = "L" - goto continue - ::curveto:: - n = n + 1 ; t[n] = x - n = n + 1 ; t[n] = 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] = ac[i+4] - n = n + 1 ; t[n] = ac[i+5] - end - prev = "A" - goto continue - ::quadratic:: - n = n + 1 ; t[n] = x2 - n = n + 1 ; t[n] = y2 - x = x2 - y = y2 - prev = "C" - goto continue - ::close:: - n = n + 1 ; t[n] = mx - n = n + 1 ; t[n] = my - if i == np then - break - end - kind = prev - prev = "Z" - x = mx - y = my - ::continue:: - end - return t - end - -end - --- todo: viewbox helper - -local s_wrapped_start = "draw image (" -local f_wrapped_stop = formatters[") shifted (0,%N) scaled %N ;"] - -local handletransform, handleviewbox do - - local sind = math.sind - - --todo: better lpeg - - local f_rotatedaround = formatters[" rotatedaround((%N,%N),%N)"] - local f_rotated = formatters[" rotated(%N)"] - local f_shifted = formatters[" shifted(%N,%N)"] - local f_slanted_x = formatters[" xslanted(%N)"] - local f_slanted_y = formatters[" yslanted(%N)"] - local f_scaled = formatters[" scaled(%N)"] - local f_xyscaled = formatters[" xyscaled(%N,%N)"] - local f_matrix = formatters[" transformed bymatrix(%N,%N,%N,%N,%N,%N)"] - - local s_transform_start = "draw image ( " - local f_transform_stop = formatters[")%s ;"] - - local function rotate(r,x,y) - if x then - return r and f_rotatedaround(x,-(y or x),-r) - elseif r then - return f_rotated(-r) - else - return "" - end - end - - local function translate(x,y) - if y then - return f_shifted(x,-y) - elseif x then - return f_shifted(x,0) - else - return "" - end - end - - local function scale(x,y) - if y then - return f_xyscaled(x,y) - elseif x then - return f_scaled(x) - else - return "" - end - end - - local function skewx(x) - if x then - return f_slanted_x(sind(-x)) - else - return "" - end - end - - local function skewy(y) - if y then - return f_slanted_y(sind(-y)) - else - return "" - end - end - - local function matrix(rx,sx,sy,ry,tx,ty) - 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? Anyway, order seems to matter. - - local p_transform = Cf ( Ct("") * ( - lpegpatterns.whitespace^0 * Cg( - C("translate") * (p_numbers / translate) -- maybe xy - + C("scale") * (p_numbers / scale) - + C("rotate") * (p_numbers / rotate) - + C("matrix") * (p_numbers / matrix) - + C("skewX") * (p_numbers / skewx) - + C("skewY") * (p_numbers / skewy) - ) - )^1, rawset) - - handletransform = function(at) - local t = at.transform - if t then - local e = lpegmatch(p_transform,t) - if e then - e = concat({ - e.rotate or "", - e.skewX or "", - e.skewY or "", - e.scale or "", - e.translate or "", - e.matrix or "", - }, " ") - return s_transform_start, f_transform_stop(e), t - end - end - end - - handleviewbox = function(v) - if v then - local x, y, w, h = lpegmatch(p_fournumbers,v) - if h then - return x, y, w, h - end - end - end - -end - -local dashed do - - -- actually commas are mandate but we're tolerant - - local f_dashed_n = formatters[" dashed dashpattern (%s ) "] - local f_dashed_y = formatters[" dashed dashpattern (%s ) shifted (%N,0) "] - - local p_number = p_optseparator/"" * 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 handlers = { } - local process = false - local root = false - local result = false - local r = false - local definitions = false - local classstyles = false - local tagstyles = false - - local tags = { - ["a"] = true, - -- ["altgGlyph"] = true, - -- ["altgGlyphDef"] = true, - -- ["altgGlyphItem"] = true, - -- ["animate"] = true, - -- ["animateColor"] = true, - -- ["animateMotion"] = true, - -- ["animateTransform"] = true, - ["circle"] = true, - ["clipPath"] = true, - -- ["color-profile"] = true, - -- ["cursor"] = true, - ["defs"] = true, - -- ["desc"] = true, - ["ellipse"] = true, - -- ["filter"] = true, - -- ["font"] = true, - -- ["font-face"] = true, - -- ["font-face-format"] = true, - -- ["font-face-name"] = true, - -- ["font-face-src"] = true, - -- ["font-face-uri"] = true, - -- ["foreignObject"] = true, - ["g"] = true, - -- ["glyph"] = true, - -- ["glyphRef"] = true, - -- ["hkern"] = true, - ["image"] = true, - ["line"] = true, - ["linearGradient"] = true, - ["marker"] = true, - -- ["mask"] = true, - -- ["metadata"] = true, - -- ["missing-glyph"] = true, - -- ["mpath"] = true, - ["path"] = true, - -- ["pattern"] = true, - ["polygon"] = true, - ["polyline"] = true, - ["radialGradient"] = true, - ["rect"] = true, - -- ["script"] = true, - -- ["set"] = true, - ["stop"] = true, - ["style"] = true, - ["svg"] = true, - -- ["switch"] = true, - ["symbol"] = true, - ["text"] = true, - -- ["textPath"] = true, - -- ["title"] = true, - ["tspan"] = true, - ["use"] = true, - -- ["view"] = true, - -- ["vkern"] = true, - } - - local function handlechains(c) - if tags[c.tg] then - local at = c.at - local dt = c.dt - if at and dt then - -- at["inkscape:connector-curvature"] = nil -- cleare entry and might prevent table growth - local estyle = rawget(at,"style") - if estyle and estyle ~= "" then - for k, v in gmatch(estyle,"%s*([^:]+):%s*([^;]+);?") do - at[k] = v - end - end - local eclass = rawget(at,"class") - if eclass and eclass ~= "" then - for c in gmatch(eclass,"[^ ]+") do - local s = classstyles[c] - if s then - for k, v in next, s do - at[k] = v - end - end - end - end - local tstyle = tagstyles[tag] - if tstyle then - for k, v in next, tstyle do - at[k] = v - end - end - if trace_path and pathtracer then - for k, v in next, pathtracer do - at[k] = v - end - end - for i=1,#dt do - local dti = dt[i] - if type(dti) == "table" then - handlechains(dti) - end - end - end - end - end - - local handlestyle do - - -- It can also be CDATA but that is probably dealt with because we only - -- check for style entries and ignore the rest. But maybe we also need - -- to check a style at the outer level? - - local p_key = C((R("az","AZ","09","__","--")^1)) - local p_spec = P("{") * C((1-P("}"))^1) * P("}") - local p_valid = Carg(1) * P(".") * p_key + Carg(2) * p_key - local p_grab = ((p_valid * p_space^0 * p_spec / rawset) + p_space^1 + P(1))^1 - - local fontspecification = css.fontspecification - - handlestyle = function(c) - local s = xmltext(c) - lpegmatch(p_grab,s,1,classstyles,tagstyles) - for k, v in next, classstyles do - local t = { } - for k, v in gmatch(v,"%s*([^:]+):%s*([^;]+);?") do - if k == "font" then - local s = fontspecification(v) - for k, v in next, s do - t["font-"..k] = v - end - else - t[k] = v - end - end - classstyles[k] = t - end - for k, v in next, tagstyles do - local t = { } - for k, v in gmatch(v,"%s*([^:]+):%s*([^;]+);?") do - if k == "font" then - local s = fontspecification(v) - for k, v in next, s do - t["font-"..k] = v - end - else - t[k] = v - end - end - tagstyles[k] = t - end - end - - function handlers.style() - -- ignore - end - - end - - -- We can have root in definitions and then do a metatable lookup but use - -- is not used that often I guess. - - local function locate(id) - local res = definitions[id] - if res then - return res - end - local ref = gsub(id,"^url%(#(.-)%)$","%1") - local ref = gsub(ref,"^#","") - -- we can make a fast id lookup - local res = xmlfirst(root,"**[@id='"..ref.."']") - if res then - definitions[id] = res - end - return res - end - - -- also locate - - local function handleclippath(at) - local clippath = at["clip-path"] - - if not clippath then - return - end - - local spec = definitions[clippath] or locate(clippath) - - -- do we really need thsi crap - if not spec then - local index = match(clippath,"(%d+)") - if index then - spec = xmlfirst(root,"clipPath["..tostring(tonumber(index) or 0).."]") - end - end - -- so far for the crap - - if not spec then - report("unknown clip %a",clippath) - return - elseif spec.tg ~= "clipPath" then - report("bad clip %a",clippath) - return - end - - ::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" - p.close = true - return p, clippath - else - return - 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" - p.close = true - return p, clippath - else - return - end - else - -- inherit? - end - end - end - - local s_shade_linear = ' withshademethod "linear" ' - local s_shade_circular = ' withshademethod "circular" ' - local f_shade_step = formatters['withshadestep ( withshadefraction %N withshadecolors(%s,%s) )'] - local f_shade_one = formatters['withprescript "sh_center_a=%N %N"'] - local f_shade_two = formatters['withprescript "sh_center_b=%N %N"'] - - local f_color = formatters['withcolor "%s"'] - local f_opacity = formatters['withtransparency (1,%N)'] - local f_pen = formatters['withpen pencircle scaled %N'] - - -- todo: gradient unfinished - -- todo: opacity but first we need groups in mp - - local function gradient(id) - local spec = definitions[id] -- no locate ! - 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 = rawget(a,"x1") - local y1 = rawget(a,"y1") - local x2 = rawget(a,"x2") - local y2 = rawget(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 = rawget(a,"cx") -- x center - local cy = rawget(a,"cy") -- y center - local r = rawget(a,"r" ) -- radius - local fx = rawget(a,"fx") -- focal points - local fy = rawget(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 - -- startcolor ? - for c in xmlcollected(spec,"/stop") do - local a = c.at - local offset = rawget(a,"offset") - local colorb = rawget(a,"stop-color") - local opacity = rawget(a,"stop-opacity") - if colorb then - colorb = thecolor(colorb) - end - if not colora then - colora = colorb - end - -- 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 - - if colora and colorb and color_a ~= "" and color_b ~= "" then - n = n + 1 ; shade[n] = f_shade_step(fraction,colora,colorb) - end - - colora = colorb - end - return concat(shade," ") - end - end - - local function drawproperties(stroke,at,opacity) - local p = at["stroke-width"] - if p then - p = f_pen(asnumber_r(p)) - end - local d = at["stroke-dasharray"] - if d == "none" then - d = nil - elseif d then - local o = at["stroke-dashoffset"] - if o and o ~= "none" then - o = asnumber_r(o) - else - o = false - end - d = dashed(d,o) - end - local c = withcolor(stroke) - local o = at["stroke-opacity"] or (opacity and at["opacity"]) - if o == "none" then - o = nil - elseif o then - o = asnumber_r(o) - if o and o ~= 1 then - o = f_opacity(o) - else - o = nil - end - end - return p, d, c, o - end - - local s_opacity_start = "draw image (" - local f_opacity_stop = formatters["setgroup currentpicture to boundingbox currentpicture withtransparency (1,%N)) ;"] - - local function sharedopacity(at) - local o = at["opacity"] - if o and o ~= "none" then - o = asnumber_r(o) - if o and o ~= 1 then - return s_opacity_start, f_opacity_stop(o) - end - end - end - - local function fillproperties(fill,at,opacity) - local c = c ~= "none" and (gradient(fill) or withcolor(fill)) or nil - local o = at["fill-opacity"] or (opacity and at["opacity"]) - if o and o ~= "none" then - o = asnumber_r(o) - if o == 1 then - return c - elseif o then - return c, f_opacity(o), o == 0 - end - end - return c - end - - -- todo: clip = [ auto | rect(llx,lly,urx,ury) ] - - local s_offset_start = "draw image ( " - local f_offset_stop = formatters[") shifted (%N,%N) ;"] - local s_rotation_start = "draw image ( " - local f_rotation_stop = formatters[") rotatedaround((0,0),-angle((%N,%N))) ;"] - local f_rotation_angle = formatters[") rotatedaround((0,0),-%N) ;"] - - local function offset(at) - local x = asnumber_vx(rawget(at,"x")) - local y = asnumber_vy(rawget(at,"y")) - if x ~= 0 or y ~= 0 then - return s_offset_start, f_offset_stop(x,y) - end - end - - local s_viewport_start = "draw image (" - local s_viewport_stop = ") ;" - local f_viewport_shift = formatters["currentpicture := currentpicture shifted (%03N,%03N);"] - local f_viewport_scale = formatters["currentpicture := currentpicture xysized (%03N,%03N);"] - local f_viewport_clip = formatters["clip currentpicture to (unitsquare xyscaled (%03N,%03N));"] - - local function viewport(x,y,w,h,noclip,scale) - r = r + 1 ; result[r] = s_viewport_start - return function() - local okay = w ~= 0 and h ~= 0 - if okay and scale then - r = r + 1 ; result[r] = f_viewport_scale(w,h) - end - if x ~= 0 or y ~= 0 then - r = r + 1 ; result[r] = f_viewport_shift(-x,y) - end - if okay and not noclip then - r = r + 1 ; result[r] = f_viewport_clip(w,-h) - end - - r = r + 1 ; result[r] = s_viewport_stop - end - end - - -- maybe forget about defs and just always locate (and then backtrack - -- over if needed) - - function handlers.defs(c) - for c in xmlcollected(c,"/*") do - local a = c.at - if a then - local id = rawget(a,"id") - if id then - definitions["#" .. id ] = c - definitions["url(#" .. id .. ")"] = c - end - end - end - end - - function handlers.symbol(c) - if uselevel == 0 then - local id = rawget(c.at,"id") - if id then - definitions["#" .. id ] = c - definitions["url(#" .. id .. ")"] = c - end - else - handlers.g(c) - end - end - - local uselevel = 0 - - function handlers.use(c) - local at = c.at - local id = rawget(at,"href") or rawget(at,"xlink:href") -- better a rawget - local res = locate(id) - if res then - -- width height ? - uselevel = uselevel + 1 - local boffset, eoffset = offset(at) - local btransform, etransform, transform = handletransform(at) - - if boffset then - r = r + 1 result[r] = boffset - end - - -- local clippath = at.clippath - - if btransform then - r = r + 1 result[r] = btransform - end - - local _transform = transform - local _clippath = clippath - at["transform"] = false - -- at["clip-path"] = false - - process(res,"/*") - - at["transform"] = _transform - -- at["clip-path"] = _clippath - - if etransform then - r = r + 1 ; result[r] = etransform - end - - if eoffset then - r = r + 1 result[r] = eoffset - end - - uselevel = uselevel - 1 - else - report("use: unknown definition %a",id) - end - end - - local f_no_draw = formatters['nodraw (%s)'] - local f_do_draw = formatters['draw (%s)'] - local f_no_fill_c = formatters['nofill (%s..cycle)'] - local f_do_fill_c = formatters['fill (%s..cycle)'] - local f_eo_fill_c = formatters['eofill (%s..cycle)'] - local f_no_fill_l = formatters['nofill (%s--cycle)'] - local f_do_fill_l = formatters['fill (%s--cycle)'] - local f_eo_fill_l = formatters['eofill (%s--cycle)'] - local f_do_fill = f_do_fill_c - local f_eo_fill = f_eo_fill_c - local f_no_fill = f_no_fill_c - local s_clip_start = 'draw image (' - local f_clip_stop_c = formatters[') ; clip currentpicture to (%s..cycle) ;'] - local f_clip_stop_l = formatters[') ; clip currentpicture to (%s--cycle) ;'] - local f_clip_stop = f_clip_stop_c - local f_eoclip_stop_c = formatters[') ; eoclip currentpicture to (%s..cycle) ;'] - local f_eoclip_stop_l = formatters[') ; eoclip currentpicture to (%s--cycle) ;'] - local f_eoclip_stop = f_eoclip_stop_c - - -- could be shared and then beginobject | endobject - - local function flushobject(object,at,c,o) - local btransform, etransform = handletransform(at) - local cpath = handleclippath(at) - - if cpath then - r = r + 1 ; result[r] = s_clip_start - end - - if btransform then - r = r + 1 ; result[r] = btransform - end - - r = r + 1 ; result[r] = f_do_draw(object) - - if c then - r = r + 1 ; result[r] = c - end - - if o then - r = r + 1 ; result[r] = o - end - - if etransform then - r = r + 1 ; result[r] = etransform - end - - r = r + 1 ; result[r] = ";" - - if cpath then - local f_done = cpath.evenodd - if cpath.curve then - f_done = f_done and f_eoclip_stop_c or f_clip_stop_c - else - f_done = f_done and f_eoclip_stop_l or f_clip_stop_l - end - r = r + 1 ; result[r] = f_done(cpath[1]) - end - end - - do - - local flush - - 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;" - - local linecaps = { butt = "butt", square = "squared", round = "rounded" } - local linejoins = { miter = "mitered", bevel = "beveled", round = "rounded" } - - local function startlineproperties(at) - local cap = at["stroke-linecap"] - local join = at["stroke-linejoin"] - local limit = at["stroke-miterlimit"] - cap = cap and linecaps [cap] - join = join and linejoins[join] - limit = limit and asnumber_r(limit) - 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 function() - at["stroke-linecap"] = false - at["stroke-linejoin"] = false - at["stroke-miterlimit"] = false - r = r + 1 ; result[r] = s_endgroup - at["stroke-linecap"] = cap - at["stroke-linejoin"] = join - at["stroke-miterlimit"] = limit - end - end - end - - -- markers are a quite rediculous thing .. let's assume simple usage for now - - function handlers.marker() - -- todo: is just a def too - end - - -- kind of local svg ... so make a generic one - -- - -- todo: combine more (offset+scale+rotation) - - local function makemarker(where,c,x1,y1,x2,y2,x3,y3,parentat) - local at = c.at - local refx = rawget(at,"refX") - local refy = rawget(at,"refY") - local width = rawget(at,"markerWidth") - local height = rawget(at,"markerHeight") - local view = rawget(at,"viewBox") - local orient = rawget(at,"orient") - -- local ratio = rawget(at,"preserveAspectRatio") - local units = asnumber(at["markerUnits"] or parentat["stroke-width"]) or 1 - - local angx = 0 - local angy = 0 - local angle = 0 - - if where == "beg" then - if orient == "auto" then -- unchecked - -- no angle - angx = abs(x2 - x3) - angy = abs(y2 - y3) - elseif orient == "auto-start-reverse" then -- checked - -- points to start - angx = -abs(x2 - x3) - angy = -abs(y2 - y3) - elseif orient then -- unchecked - angle = asnumber_r(orient) - end - elseif where == "end" then - -- funny standard .. bug turned feature? - if orient == "auto" or orient == "auto-start-reverse" then - angx = abs(x1 - x2) - angy = abs(y1 - y2) - elseif orient then -- unchecked - angle = asnumber_r(orient) - end - elseif orient then -- unchecked - angle = asnumber_r(orient) - end - -- what wins: viewbox or w/h - - refx = asnumber_x(refx) - refy = asnumber_y(refy) - - width = (width and asnumber_x(width) or 3) * units - height = (height and asnumber_y(height) or 3) * units - - local x = 0 - local y = 0 - local w = width - local h = height - - -- kind of like the main svg - - r = r + 1 ; result[r] = s_offset_start - - local wrapupviewport --- todo : better viewbox code - local xpct, ypct, rpct - if view then - x, y, w, h = handleviewbox(view) - end - - if width ~= 0 then - w = width - end - if height ~= 0 then - h = height - end - - if h then - 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 - wrapupviewport = viewport(x,y,w,h,true,true) -- no clip - end - - -- we can combine a lot here: - - local hasref = refx ~= 0 or refy ~= 0 - local hasrot = angx ~= 0 or angy ~= 0 or angle ~= 0 - - local btransform, etransform, transform = handletransform(at) - - if btransform then - r = r + 1 ; result[r] = btransform - end - - if hasrot then - r = r + 1 ; result[r] = s_rotation_start - end - - if hasref then - r = r + 1 ; result[r] = s_offset_start - end - - local _transform = transform - at["transform"] = false - - handlers.g(c) - - at["transform"] = _transform - - if hasref then - r = r + 1 ; result[r] = f_offset_stop(-refx,refy) - end - - if hasrot then - if angle ~= 0 then - r = r + 1 ; result[r] = f_rotation_angle(angle) - else - r = r + 1 ; result[r] = f_rotation_stop(angx,angy) - end - end - - if etransform then - r = r + 1 ; result[r] = etransform - end - - if h then - percentage_x = xpct - percentage_y = ypct - percentage_r = rpct - if wrapupviewport then - wrapupviewport() - end - end - r = r + 1 ; result[r] = f_offset_stop(x2,y2) - - end - - -- do we need to metatable the attributes here? - - local function addmarkers(list,begmarker,midmarker,endmarker,at) - local n = #list - if n > 3 then - if begmarker then - local m = locate(begmarker) - if m then - makemarker("beg",m,false,false,list[1],list[2],list[3],list[4],at) - end - end - if midmarker then - local m = locate(midmarker) - if m then - for i=3,n-2,2 do - makemarker("mid",m,list[i-2],list[i-1],list[i],list[i+1],list[i+2],list[i+3],at) - end - end - end - if endmarker then - local m = locate(endmarker) - if m then - makemarker("end",m,list[n-3],list[n-2],list[n-1],list[n],false,false,at) - end - end - else - -- no line - end - end - - local function flush(shape,dofill,at,list,begmarker,midmarker,endmarker) - - local fill = dofill and (at["fill"] or "black") - local stroke = at["stroke"] or "none" - - local btransform, etransform = handletransform(at) - local cpath = handleclippath(at) - - if cpath then - r = r + 1 ; result[r] = s_clip_start - end - - local has_stroke = stroke and stroke ~= "none" - local has_fill = fill and fill ~= "none" - - local bopacity, eopacity - if has_stroke and has_fill then - bopacity, eopacity = sharedopacity(at) - end - - if bopacity then - r = r + 1 ; result[r] = bopacity - end - - if has_fill then - local color, opacity = fillproperties(fill,at,not has_stroke) - local f_xx_fill = at["fill-rule"] == "evenodd" and f_eo_fill or f_do_fill - if btransform then - r = r + 1 ; result[r] = btransform - end - r = r + 1 result[r] = f_xx_fill(shape) - if color then - r = r + 1 ; result[r] = color - end - if opacity then - r = r + 1 ; result[r] = opacity - end - r = r + 1 ; result[r] = etransform or ";" - end - - if has_stroke then - local wrapup = startlineproperties(at) - local pen, dashing, color, opacity = drawproperties(stroke,at,not has_fill) - if btransform then - r = r + 1 ; result[r] = btransform - end - r = r + 1 ; result[r] = f_do_draw(shape) - if pen then - r = r + 1 ; result[r] = pen - end - if dashing then - r = r + 1 ; result[r] = dashing - end - if color then - r = r + 1 ; result[r] = color - end - if opacity then - r = r + 1 ; result[r] = opacity - end - r = r + 1 ; result[r] = etransform or ";" - -- - if list then - addmarkers(list,begmarker,midmarker,endmarker,at) - end - -- - if wrapup then - wrapup() - end - end - - if eopacity then - r = r + 1 ; result[r] = eopacity - end - - if cpath then - r = r + 1 ; result[r] = (cpath.evenodd and f_eoclip_stop or f_clip_stop)(cpath[1]) - end - - end - - local f_rectangle = formatters['unitsquare xyscaled (%N,%N) shifted (%N,%N)'] - local f_rounded = formatters['roundedsquarexy(%N,%N,%N,%N) shifted (%N,%N)'] - local f_line = formatters['((%N,%N)--(%N,%N))'] - local f_ellipse = formatters['(fullcircle xyscaled (%N,%N) shifted (%N,%N))'] - local f_circle = formatters['(fullcircle scaled %N shifted (%N,%N))'] - - function handlers.line(c) - local at = c.at - local x1 = rawget(at,"x1") - local y1 = rawget(at,"y1") - local x2 = rawget(at,"x2") - local y2 = rawget(at,"y2") - - x1 = x1 and asnumber_vx(x1) or 0 - y1 = y1 and asnumber_vy(y1) or 0 - x2 = x2 and asnumber_vx(x2) or 0 - y2 = y2 and asnumber_vy(y2) or 0 - - flush(f_line(x1,y1,x2,y2),false,at) - end - - function handlers.rect(c) - local at = c.at - local width = rawget(at,"width") - local height = rawget(at,"height") - local x = rawget(at,"x") - local y = rawget(at,"y") - local rx = rawget(at,"rx") - local ry = rawget(at,"ry") - - width = width and asnumber_x(width) or 0 - height = height and asnumber_y(height) or 0 - x = x and asnumber_vx(x) or 0 - y = y and asnumber_vy(y) or 0 - - y = y - height - - if rx then rx = asnumber(rx) end - if ry then ry = asnumber(ry) end - - if rx or ry then - if not rx then rx = ry end - if not ry then ry = rx end - flush(f_rounded(width,height,rx,ry,x,y),true,at) - else - flush(f_rectangle(width,height,x,y),true,at) - end - end - - function handlers.ellipse(c) - local at = c.at - local cx = rawget(at,"cx") - local cy = rawget(at,"cy") - local rx = rawget(at,"rx") - local ry = rawget(at,"ry") - - cx = cx and asnumber_vx(cx) or 0 - cy = cy and asnumber_vy(cy) or 0 - rx = rx and asnumber_r (rx) or 0 - ry = ry and asnumber_r (ry) or 0 - - flush(f_ellipse(2*rx,2*ry,cx,cy),true,at) - end - - function handlers.circle(c) - local at = c.at - local cx = rawget(at,"cx") - local cy = rawget(at,"cy") - local r = rawget(at,"r") - - cx = cx and asnumber_vx(cx) or 0 - cy = cy and asnumber_vy(cy) or 0 - r = r and asnumber_r (r) or 0 - - flush(f_circle(2*r,cx,cy),true,at) - end - - local f_lineto_z = formatters['(%N,%N)'] - local f_lineto_n = formatters['--(%N,%N)'] - - local p_pair = p_optseparator * p_number_vx * p_optseparator * p_number_vy - local p_open = Cc("(") - local p_close = Carg(1) * P(true) / function(s) return s end - local p_polyline = Cs(p_open * (p_pair / f_lineto_z) * (p_pair / f_lineto_n)^0 * p_close) - local p_polypair = Ct(p_pair^0) - - local function poly(c,final) - local at = c.at - local points = rawget(at,"points") - if points then - local path = lpegmatch(p_polyline,points,1,final) - local list = nil - local begmarker = rawget(at,"marker-start") - local midmarker = rawget(at,"marker-mid") - local endmarker = rawget(at,"marker-end") - if begmarker or midmarker or endmarker then - list = lpegmatch(p_polypair,points) - end - flush(path,true,at,list,begmarker,midmarker,endmarker) - end - end - - function handlers.polyline(c) poly(c, ")") end - function handlers.polygon (c) poly(c,"--cycle)") end - - local s_image_start = "draw image (" - local s_image_stop = ") ;" - - function handlers.path(c) - local at = c.at - local d = rawget(at,"d") - if d then - local shape, l = grabpath(d) - local fill = at["fill"] or "black" - local stroke = at["stroke"] or "none" - local n = #shape - - local btransform, etransform = handletransform(at) - local cpath = handleclippath(at) - if cpath then - r = r + 1 ; result[r] = s_clip_start - end - - -- todo: image (nicer for transform too) - - if fill and fill ~= "none" then - local color, opacity = fillproperties(fill,at) - local f_xx_fill = at["fill-rule"] == "evenodd" - if shape.closed then - f_xx_fill = f_xx_fill and f_eo_fill or f_do_fill - elseif shape.curve then - f_xx_fill = f_xx_fill and f_eo_fill_c or f_do_fill_c - else - f_xx_fill = f_xx_fill and f_eo_fill_l or f_do_fill_l - end - if n == 1 then - if btransform then - r = r + 1 ; result[r] = btransform - end - r = r + 1 result[r] = f_xx_fill(shape[1]) - if color then - r = r + 1 ; result[r] = color - end - if opacity then - r = r + 1 ; result[r] = opacity - end - r = r + 1 ; result[r] = etransform or ";" - else - r = r + 1 ; result[r] = btransform or s_image_start - for i=1,n do - if i == n then - r = r + 1 ; result[r] = f_xx_fill(shape[i]) - if color then - r = r + 1 ; result[r] = color - end - if opacity then - r = r + 1 ; result[r] = opacity - end - else - r = r + 1 ; result[r] = f_no_fill(shape[i]) - end - r = r + 1 ; result[r] = ";" - end - r = r + 1 ; result[r] = etransform or s_image_stop - end - end - - if stroke and stroke ~= "none" then - local begmarker = rawget(at,"marker-start") - local midmarker = rawget(at,"marker-mid") - local endmarker = rawget(at,"marker-end") - if begmarker or midmarker or endmarker then - list = grablist(l) - end - local wrapup = startlineproperties(at) - local pen, dashing, color, opacity = drawproperties(stroke,at) - if n == 1 and not list then - if btransform then - r = r + 1 ; result[r] = btransform - end - r = r + 1 result[r] = f_do_draw(shape[1]) - if pen then - r = r + 1 ; result[r] = pen - end - if dashing then - r = r + 1 ; result[r] = dashing - end - if color then - r = r + 1 ; result[r] = color - end - if opacity then - r = r + 1 ; result[r] = opacity - end - r = r + 1 result[r] = etransform or ";" - else - r = r + 1 result[r] = btransform or "draw image (" - for i=1,n do - r = r + 1 result[r] = f_do_draw(shape[i]) - if pen then - r = r + 1 ; result[r] = pen - end - if dashing then - r = r + 1 ; result[r] = dashing - end - if color then - r = r + 1 ; result[r] = color - end - if opacity then - r = r + 1 ; result[r] = opacity - end - r = r + 1 ; result[r] = ";" - end - if list then - addmarkers(list,begmarker,midmarker,endmarker,at) - end - r = r + 1 ; result[r] = etransform or ") ;" - end - if wrapup then - wrapup() - end - end - - if cpath then - r = r + 1 ; result[r] = f_clip_stop(cpath[1]) - end - - end - end - - end - - -- kind of special - - do - - -- some day: - -- - -- specification = identifiers.jpg(data."string") - -- specification.data = data - -- inclusion takes from data - -- specification.data = false - - local f_image = formatters[ [[figure("%s") xysized (%N,%N) shifted (%N,%N)]] ] - - local nofimages = 0 - - function handlers.image(c) - local at = c.at - local im = rawget(at,"xlink:href") - if im then - local kind, data = match(im,"^data:image/([a-z]+);base64,(.*)$") - if kind == "png" then - -- ok - elseif kind == "jpeg" then - kind = "jpg" - else - kind = false - end - if kind and data then - local w = rawget(at,"width") - local h = rawget(at,"height") - local x = rawget(at,"x") - local y = rawget(at,"y") - w = w and asnumber_x(w) - h = h and asnumber_y(h) - x = x and asnumber_vx(x) or 0 - y = y and asnumber_vy(y) or 0 - nofimages = nofimages + 1 - local name = "temp-svg-image-" .. nofimages .. "." .. kind - local data = mime.decode("base64")(data) - io.savedata(name,data) - if not w or not h then - local info = graphics.identifiers[kind](data,"string") - if info then - -- todo: keep aspect ratio attribute - local xsize = info.xsize - local ysize = info.ysize - if not w then - if not h then - w = xsize - h = ysize - else - w = (h / ysize) * xsize - end - else - h = (w / xsize) * ysize - end - end - end - -- safeguard: - if not w then w = h or 1 end - if not h then h = w or 1 end - luatex.registertempfile(name) - -- done: - flushobject(f_image(name,w,h,x,y - h),at) - else - -- nothing done - end - end - end - - end - - -- these transform: g a text svg symbol - - do - - function handlers.a(c) - process(c,"/*") - end - - function handlers.g(c) -- much like flushobject so better split and share - local at = c.at - - local btransform, etransform, transform = handletransform(at) - local cpath, clippath = handleclippath(at) - - if cpath then - r = r + 1 ; result[r] = s_clip_start - end - - if btransform then - r= r + 1 result[r] = btransform - end - - local _transform = transform - local _clippath = clippath - at["transform"] = false - at["clip-path"] = false - - process(c,"/*") - - at["transform"] = _transform - at["clip-path"] = _clippath - - if etransform then - r = r + 1 ; result[r] = etransform - end - - if cpath then - local f_done = cpath.evenodd - if cpath.curve then - f_done = f_done and f_eoclip_stop_c or f_clip_stop_c - else - f_done = f_done and f_eoclip_stop_l or f_clip_stop_l - end - r = r + 1 ; result[r] = f_done(cpath[1]) - end - end - - -- this will never really work out - -- - -- todo: register text in lua in mapping with id, then draw mapping unless overloaded - -- using lmt_svglabel with family,style,weight,size,id passed - - -- nested tspans are messy: they can have displacements but in inkscape we also - -- see x and y (inner and outer element) - - -- The size is a bit of an issue. I assume that the specified size relates to the - -- designsize but we want to be able to use other fonts. - - do - - local f_styled = formatters["\\svgstyled{%s}{%s}{%s}{%s}"] - local f_colored = formatters["\\svgcolored{%.3N}{%.3N}{%.3N}{"] - local f_placed = formatters["\\svgplaced{%.3N}{%.3N}{}{"] - local f_poschar = formatters["\\svgposchar{%.3N}{%.3N}{%s}"] - local f_char = formatters["\\svgchar{%s}"] - - local f_scaled = formatters["\\svgscaled{%N}{%s}{%s}{%s}"] - local f_normal = formatters["\\svgnormal{%s}{%s}{%s}"] - local f_hashed = formatters["\\svghashed{%s}"] - - -- We move to the outer (x,y) and when we have an inner offset we - -- (need to) compensate for that outer offset. - - -- local f_text_scaled_svg = formatters['(svgtext("%s") scaled %N shifted (%N,%N))'] - -- local f_text_normal_svg = formatters['(svgtext("%s") shifted (%N,%N))'] - -- local f_text_simple_svg = formatters['svgtext("%s")'] - - local anchors = { - ["start"] = "drt", - ["end"] = "dflt", - ["middle"] = "d", - } - - local f_text_normal_svg = formatters['(textext.%s("%s") shifted (%N,%N))'] - local f_text_simple_svg = formatters['textext.%s("%s")'] - - -- or just maptext - - local f_mapped_normal_svg = formatters['(svgtext("%s") shifted (%N,%N))'] - local f_mapped_simple_svg = formatters['svgtext("%s")'] - - local cssfamily = css.family - local cssstyle = css.style - local cssweight = css.weight - local csssize = css.size - - local usedfonts = setmetatableindex(function(t,k) - local v = setmetatableindex("table") - t[k] = v - return v - end) - - local p_texescape = lpegpatterns.texescape - - -- For now as I need it for my (some 1500) test files. - - local function checkedfamily(name) - if find(name,"^.-verdana.-$") then - name = "verdana" - end - return name - end - - -- todo: only escape some chars and handle space - - local defaultsize = 10 - - local function collect(t,c,x,y,size,scale,family,tx,ty) - local at = c.at - local ax = rawget(at,"x") - local ay = rawget(at,"y") - local dx = rawget(at,"dx") - local dy = rawget(at,"dy") - local v_fill = at["fill"] - local v_family = at["font-family"] - local v_style = at["font-style"] - local v_weight = at["font-weight"] - local v_size = at["font-size"] - -- - ax = ax and asnumber_vx(ax) or x - ay = ay and asnumber_vy(ay) or y - dx = dx and asnumber_vx(dx) or 0 - dy = dy and asnumber_vy(dy) or 0 - -- - if v_family then v_family = cssfamily(v_family) end - if v_style then v_style = cssstyle (v_style) end - if v_weight then v_weight = cssweight(v_weight) end - if v_size then v_size = csssize (v_size,factors) end - -- - ax = ax - x - ay = ay - y - -- - local elayered = ax ~= 0 or ay ~= 0 or false - local eplaced = dx ~= 0 or dy ~= 0 or false - - local usedsize, usedscaled - - if elayered then - -- we're now at the outer level again so we need to scale - -- back to the outer level values - t[#t+1] = formatters["\\svgsetlayer{%0N}{%0N}{"](ax,-ay) - usedsize = v_size or defaultsize - usedscale = usedsize / defaultsize - else - -- we're nested so we can be scaled - usedsize = v_size or size - usedscale = (usedsize / defaultsize) / scale - end - -- - -- print("element ",c.tg) - -- print(" layered ",elayered) - -- print(" font size ",v_size) - -- print(" parent size ",size) - -- print(" parent scale",scale) - -- print(" used size ",usedsize) - -- print(" used scale ",usedscale) - -- - if eplaced then - t[#t+1] = f_placed(dx,dy) - end - -- - if not v_family then v_family = family end - if not v_weight then v_weight = "normal" end - if not v_style then v_style = "normal" end - -- - if v_family then - v_family = fonts.names.cleanname(v_family) - v_family = checkedfamily(v_family) - end - -- - usedfonts[v_family][v_weight][v_style] = true - -- --- if usedscale == 1 then --- t[#t+1] = f_normal( v_family,v_weight,v_style) --- else - t[#t+1] = f_scaled(usedscale,v_family,v_weight,v_style) --- end - t[#t+1] = "{" - -- - local ecolored = v_fill and v_fill ~= "" or false - if ecolored then - -- todo cmyk - local r, g, b = colorcomponents(v_fill) - if r and g and b then - t[#t+1] = f_colored(r,g,b) - else - ecolored = false - end - end - -- - local dt = c.dt - local nt = #dt - for i=1,nt do - local di = dt[i] - if type(di) == "table" then - -- can be a tspan (should we pass dx too) - collect(t,di,x,y,usedsize,usedscale,v_family) - else - if i == 1 then - di = gsub(di,"^%s+","") - end - if i == nt then - di = gsub(di,"%s+$","") - end - local chars = utfsplit(di) - if svghash then - di = f_hashed(svghash[di]) - elseif tx then - for i=1,#chars do - chars[i] = f_poschar( - (tx[i] or 0) - x, - (ty[i] or 0) - y, - utfbyte(chars[i]) - ) - end - di = "{" .. concat(chars) .. "}" - else - -- this needs to be texescaped ! and even quotes and newlines - -- or we could register it but that's a bit tricky as we nest - -- and don't know what we can expect here - -- di = lpegmatch(p_texescape,di) or di - for i=1,#chars do - chars[i] = f_char(utfbyte(chars[i])) - end - di = concat(chars) - end - t[#t+1] = di - end - end - -- - if ecolored then - t[#t+1] = "}" - end - -- - t[#t+1] = "}" - -- - if eplaced then - t[#t+1] = "}" - end - if elayered then - t[#t+1] = "}" - end - -- - return t - end - - local s_startlayer = "\\svgstartlayer " - local s_stoplayer = "\\svgstoplayer " - - function handlers.text(c) - local only = fullstrip(xmltextonly(c)) - -- if metapost.processing() then - local at = c.at - local x = rawget(at,"x") - local y = rawget(at,"y") - - local tx = asnumber_vx_t(x) - local ty = asnumber_vy_t(y) - - x = tx[1] or 0 -- catch bad x/y spec - y = ty[1] or 0 -- catch bad x/y spec - - local v_fill = at["fill"] - if not v_fill or v_fill == "none" then - v_fill = "black" - end - local color, opacity, invisible = fillproperties(v_fill,at) - local anchor = anchors[at["text-anchor"] or "start"] or "drt" - local r = metapost.remappedtext(only) - if r then - if x == 0 and y == 0 then - only = f_mapped_simple_svg(r.index) - else - only = f_mapped_normal_svg(r.index,x,y) - end - flushobject(only,at,color,opacity) - if trace_text then - report("text: %s",only) - end - elseif not invisible then -- can be an option - local scale = 1 - local textid = 0 - local result = { } - local nx = #tx - local ny = #ty - -- - result[#result+1] = s_startlayer - if nx > 1 or ny > 1 then - concat(collect(result,c,x,y,defaultsize,1,"serif",tx,ty)) - else - concat(collect(result,c,x,y,defaultsize,1,"serif")) - end - result[#result+1] = s_stoplayer - result = concat(result) - if x == 0 and y == 0 then - result = f_text_simple_svg(anchor,result) - else - result = f_text_normal_svg(anchor,result,x,y) - end - flushobject(result,at,color,opacity) - if trace_text then - report("text: %s",result) - end - elseif trace_text then - report("invisible text: %s",only) - end - -- elseif trace_text then - -- report("ignored text: %s",only) - -- end - end - - function metapost.reportsvgfonts() - for family, weights in sortedhash(usedfonts) do - for weight, styles in sortedhash(weights) do - for style in sortedhash(styles) do - report("used font: %s-%s-%s",family,weight,style) - end - end - end - end - - statistics.register("used svg fonts",function() - if next(usedfonts) then - -- also in log file - logs.startfilelogging(report,"used svg fonts") - local t = { } - for family, weights in sortedhash(usedfonts) do - for weight, styles in sortedhash(weights) do - for style in sortedhash(styles) do - report("%s-%s-%s",family,weight,style) - t[#t+1] = formatters["%s-%s-%s"](family,weight,style) - end - end - end - logs.stopfilelogging() - return concat(t," ") - end - end) - - end - - function handlers.svg(c,x,y,w,h,noclip,notransform,normalize) - local at = c.at - - local wrapupviewport - local bhacked - local ehacked - local wd = w - -- local ex, em - local xpct, ypct, rpct - - local btransform, etransform, transform = handletransform(at) - - if trace then - report("view: %s, xpct %N, ypct %N","before",percentage_x,percentage_y) - end - - local viewbox = at.viewBox - - if viewbox then - x, y, w, h = handleviewbox(viewbox) - if trace then - report("viewbox: x %N, y %N, width %N, height %N",x,y,w,h) - end - end - if not w or not h or w == 0 or h == 0 then - noclip = true - 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 %N, ypct %N","inside",percentage_x,percentage_y) - end - wrapupviewport = viewport(x,y,w,h,noclip) - end - -- todo: combine transform and offset here - - -- some fonts need this (bad transforms + viewbox) - if v and normalize and w and wd and w ~= wd and w > 0 and wd > 0 then - bhacked = s_wrapped_start - ehacked = f_wrapped_stop(y or 0,wd/w) - end - if btransform then - r = r + 1 ; result[r] = btransform - end - if bhacked then - r = r + 1 ; result[r] = bhacked - end - local boffset, eoffset = offset(at) - if boffset then - r = r + 1 result[r] = boffset - end - - at["transform"] = false - at["viewBox"] = false - - process(c,"/*") - - at["transform"] = transform - at["viewBox"] = viewbox - - if eoffset then - r = r + 1 result[r] = eoffset - end - if ehacked then - r = r + 1 ; result[r] = ehacked - 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 %N, ypct %N","after",percentage_x,percentage_y) - end - end - - end - - process = function(x,p) - for c in xmlcollected(x,p) do - local tg = c.tg - local h = handlers[c.tg] - if h then - h(c) - end - end - end - - -- For huge inefficient files there can be lots of garbage to collect so - -- maybe we should run the collector when a file is larger than say 50K. - - function metapost.svgtomp(specification,pattern,notransform,normalize) - 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 = svg - result = { } - r = 0 - definitions = { } - tagstyles = { } - classstyles = { } - colormap = specification.colormap - usedcolors = trace_colors and setmetatableindex("number") or false - for s in xmlcollected(c,"style") do -- can also be in a def, so let's play safe - handlestyle(c) - end - handlechains(c) - xmlinheritattributes(c) -- put this in handlechains - handlers.svg ( - c, - specification.x, - specification.y, - specification.width, - specification.height, - specification.noclip, - notransform, - normalize, - specification.remap - ) - if trace_result then - report("result graphic:\n %\n t",result) - end - if usedcolors and next(usedcolors) then - report("graphic %a uses colors: %s",specification.id or "unknown",table.sequenced(usedcolors)) - end - mps = concat(result," ") - root = false - result = false - r = false - definitions = false - tagstyles = false - classstyles = false - colormap = false - else - report("missing svg root element") - end - else - report("bad svg blob") - end - return mps - end - -end - --- These helpers might move to their own module .. some day ... also they will become --- a bit more efficient, because we now go to mp and back which is kind of redundant, --- but for now it will do. - -do - - local bpfactor = number.dimenfactors.bp - - function metapost.includesvgfile(filename,offset) -- offset in sp - if lfs.isfile(filename) then - context.startMPcode("doublefun") - context('draw lmt_svg [ filename = "%s", offset = %N ] ;',filename,(offset or 0)*bpfactor) - context.stopMPcode() - end - end - - function metapost.includesvgbuffer(name,offset) -- offset in sp - context.startMPcode("doublefun") - context('draw lmt_svg [ buffer = "%s", offset = %N ] ;',name or "",(offset or 0)*bpfactor) - context.stopMPcode() - end - - interfaces.implement { - name = "includesvgfile", - actions = metapost.includesvgfile, - arguments = { "string", "dimension" }, - } - - interfaces.implement { - name = "includesvgbuffer", - actions = metapost.includesvgbuffer, - arguments = { "string", "dimension" }, - } - - function metapost.showsvgpage(data) - local dd = data.data - if not dd then - local fn = data.filename - dd = fn and table.load(fn) - end - if type(dd) == "table" then - local comment = data.comment - local offset = data.pageoffset - local index = data.index - local first = math.max(index or 1,1) - local last = math.min(index or #dd,#dd) - for i=first,last do - local d = setmetatableindex( { - data = dd[i], - comment = comment and i or false, - pageoffset = offset or nil, - }, data) - metapost.showsvgpage(d) - end - elseif data.method == "code" then - context.startMPcode(doublefun) - context(metapost.svgtomp(data)) - context.stopMPcode() - 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.typesvgpage(data) - local dd = data.data - if not dd then - local fn = data.filename - dd = fn and table.load(fn) - end - if type(dd) == "table" then - local index = data.index - if index and index > 0 and index <= #dd then - data = dd[index] - else - data = nil - end - end - if type(data) == "string" and data ~= "" then - buffers.assign("svgpage",data) - context.typebuffer ({ "svgpage" }, { option = "XML", strip = "yes" }) - end - end - - function metapost.svgtopdf(data,...) - local mps = metapost.svgtomp(data,...) - if mps then - -- todo: special instance, only basics needed - local pdf = metapost.simple("metafun",mps,true,false,"svg") - if pdf then - return pdf - else - -- message - end - else - -- message - end - 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. - - local decompress = gzip.decompress - local compress = gzip.compress - - 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] = "\\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 - if decompress then - data = decompress(data) or data - end - 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,true,true) 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 - if decompress then - data = decompress(data) or data - end - 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 .. "']" - local mpcode = metapost.svgtomp(specification,pattern,true,true) or "" - if mpcode ~= "" and compress then - mpcode = compress(mpcode) or mpcode - end - mpshapes[index] = mpcode - end - end - end - if report_svg then - statistics.stoptiming(mpshapes) - report_svg("svg conversion time %s",statistics.elapsedseconds(mpshapes)) - end - return mpshapes - end - - function metapost.svgglyphtomp(fontname,unicode) - if fontname and unicode then - local id = fonts.definers.internal { name = fontname } - if id then - local tfmdata = fonts.hashes.identifiers[id] - if tfmdata then - local properties = tfmdata.properties - local svg = properties.svg - local hash = svg and svg.hash - local timestamp = svg and svg.timestamp - if hash then - local svgfile = containers.read(fonts.handlers.otf.svgcache,hash) - local svgshapes = svgfile and svgfile.svgshapes - if svgshapes then - if type(unicode) == "string" then - unicode = utfbyte(unicode) - end - local chardata = tfmdata.characters[unicode] - local index = chardata and chardata.index - if index then - for i=1,#svgshapes do - local entry = svgshapes[i] - if index >= entry.first and index <= entry.last then - local data = entry.data - if data then - local root = xml.convert(gzip.decompress(data) or data) - return metapost.svgtomp ( - { - data = root, - x = 0, - y = 1000, - width = 1000, - height = 1000, - noclip = true, - }, - "/svg[@id='glyph" .. index .. "']", - true, - true - ) - end - end - end - end - end - end - end - end - end - end - -end diff --git a/tex/context/base/mkiv/status-files.pdf b/tex/context/base/mkiv/status-files.pdf index e1d84cd54..9c05f8372 100644 Binary files a/tex/context/base/mkiv/status-files.pdf and b/tex/context/base/mkiv/status-files.pdf differ diff --git a/tex/context/base/mkiv/status-lua.pdf b/tex/context/base/mkiv/status-lua.pdf index 1a650ecbf..7f84d7523 100644 Binary files a/tex/context/base/mkiv/status-lua.pdf and b/tex/context/base/mkiv/status-lua.pdf differ diff --git a/tex/context/base/mkiv/toks-ini.lua b/tex/context/base/mkiv/toks-ini.lua index 65a7645f4..562e2801a 100644 --- a/tex/context/base/mkiv/toks-ini.lua +++ b/tex/context/base/mkiv/toks-ini.lua @@ -219,7 +219,7 @@ tokens.scanners = { -- these expand tokens.getters = { -- these don't expand meaning = token.get_meaning, macro = token.get_macro, - token = token.scan_next, -- not here, use scanners.next or token + token = token.scan_next or token.get_next, -- not here, use scanners.next or token cstoken = token.get_cstoken, count = tex.getcount, dimen = tex.getdimen, diff --git a/tex/context/interface/mkiv/context-en.xml b/tex/context/interface/mkiv/context-en.xml index 485ca4a9a..6cf0cfa0f 100644 --- a/tex/context/interface/mkiv/context-en.xml +++ b/tex/context/interface/mkiv/context-en.xml @@ -16682,7 +16682,93 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -25209,7 +25295,23 @@ - + + + + + + + + + + + + + + + + + @@ -25238,7 +25340,23 @@ - + + + + + + + + + + + + + + + + + diff --git a/tex/context/interface/mkiv/i-attachment.xml b/tex/context/interface/mkiv/i-attachment.xml index 6fb25884c..b75a5ac66 100644 --- a/tex/context/interface/mkiv/i-attachment.xml +++ b/tex/context/interface/mkiv/i-attachment.xml @@ -138,26 +138,4 @@ - - diff --git a/tex/context/interface/mkiv/i-bar.xml b/tex/context/interface/mkiv/i-bar.xml index a90e6bfd2..4f9bd9c34 100644 --- a/tex/context/interface/mkiv/i-bar.xml +++ b/tex/context/interface/mkiv/i-bar.xml @@ -131,106 +131,6 @@ - - diff --git a/tex/context/interface/mkiv/i-buffer.xml b/tex/context/interface/mkiv/i-buffer.xml index b82b17ac5..451c93c7e 100644 --- a/tex/context/interface/mkiv/i-buffer.xml +++ b/tex/context/interface/mkiv/i-buffer.xml @@ -117,12 +117,6 @@ - - diff --git a/tex/context/interface/mkiv/i-columns.xml b/tex/context/interface/mkiv/i-columns.xml index d112efeb9..72260b820 100644 --- a/tex/context/interface/mkiv/i-columns.xml +++ b/tex/context/interface/mkiv/i-columns.xml @@ -102,14 +102,6 @@ - - - - - - - - --> diff --git a/tex/context/interface/mkiv/i-comment.xml b/tex/context/interface/mkiv/i-comment.xml index 913b8e152..e7349a69e 100644 --- a/tex/context/interface/mkiv/i-comment.xml +++ b/tex/context/interface/mkiv/i-comment.xml @@ -130,27 +130,4 @@ - - diff --git a/tex/context/interface/mkiv/i-common-instance.xml b/tex/context/interface/mkiv/i-common-instance.xml index 74d30263a..025dafc4a 100644 --- a/tex/context/interface/mkiv/i-common-instance.xml +++ b/tex/context/interface/mkiv/i-common-instance.xml @@ -385,7 +385,7 @@ - + @@ -408,7 +408,7 @@ - + diff --git a/tex/context/interface/mkiv/i-context.pdf b/tex/context/interface/mkiv/i-context.pdf index 59f603a12..7950faf9f 100644 Binary files a/tex/context/interface/mkiv/i-context.pdf and b/tex/context/interface/mkiv/i-context.pdf differ diff --git a/tex/context/interface/mkiv/i-delimitedtext.xml b/tex/context/interface/mkiv/i-delimitedtext.xml index ce1767b1c..3d881a972 100644 --- a/tex/context/interface/mkiv/i-delimitedtext.xml +++ b/tex/context/interface/mkiv/i-delimitedtext.xml @@ -157,86 +157,6 @@ - - diff --git a/tex/context/interface/mkiv/i-fittingpage.xml b/tex/context/interface/mkiv/i-fittingpage.xml index ce872a33a..7283ae5ac 100644 --- a/tex/context/interface/mkiv/i-fittingpage.xml +++ b/tex/context/interface/mkiv/i-fittingpage.xml @@ -68,26 +68,6 @@ - - diff --git a/tex/context/interface/mkiv/i-floats.xml b/tex/context/interface/mkiv/i-floats.xml index 26d20adef..a29fc7b9d 100644 --- a/tex/context/interface/mkiv/i-floats.xml +++ b/tex/context/interface/mkiv/i-floats.xml @@ -709,202 +709,6 @@ - - diff --git a/tex/context/interface/mkiv/i-formula.xml b/tex/context/interface/mkiv/i-formula.xml index 72693fb6c..2530275c3 100644 --- a/tex/context/interface/mkiv/i-formula.xml +++ b/tex/context/interface/mkiv/i-formula.xml @@ -207,34 +207,6 @@ - - diff --git a/tex/context/interface/mkiv/i-fraction.xml b/tex/context/interface/mkiv/i-fraction.xml index 80eed65d9..6e6a91030 100644 --- a/tex/context/interface/mkiv/i-fraction.xml +++ b/tex/context/interface/mkiv/i-fraction.xml @@ -101,73 +101,6 @@ - - diff --git a/tex/context/interface/mkiv/i-framed.xml b/tex/context/interface/mkiv/i-framed.xml index 10fbd6c37..d57a66969 100644 --- a/tex/context/interface/mkiv/i-framed.xml +++ b/tex/context/interface/mkiv/i-framed.xml @@ -348,19 +348,6 @@ - - @@ -395,37 +382,6 @@ - - @@ -540,33 +496,6 @@ - - diff --git a/tex/context/interface/mkiv/i-indent.xml b/tex/context/interface/mkiv/i-indent.xml index cacae0673..0117cbb10 100644 --- a/tex/context/interface/mkiv/i-indent.xml +++ b/tex/context/interface/mkiv/i-indent.xml @@ -92,7 +92,7 @@ - + diff --git a/tex/context/interface/mkiv/i-labeltext.xml b/tex/context/interface/mkiv/i-labeltext.xml index 47bdd23af..6609f86a9 100644 --- a/tex/context/interface/mkiv/i-labeltext.xml +++ b/tex/context/interface/mkiv/i-labeltext.xml @@ -135,389 +135,4 @@ - - diff --git a/tex/context/interface/mkiv/i-lines.xml b/tex/context/interface/mkiv/i-lines.xml index e2bc35605..7b6a76565 100644 --- a/tex/context/interface/mkiv/i-lines.xml +++ b/tex/context/interface/mkiv/i-lines.xml @@ -112,18 +112,6 @@ - - diff --git a/tex/context/interface/mkiv/i-list.xml b/tex/context/interface/mkiv/i-list.xml index 08dfa0cde..7b207b323 100644 --- a/tex/context/interface/mkiv/i-list.xml +++ b/tex/context/interface/mkiv/i-list.xml @@ -505,34 +505,6 @@ - - diff --git a/tex/context/interface/mkiv/i-makeup.xml b/tex/context/interface/mkiv/i-makeup.xml index 27b80ca01..15c327f6b 100644 --- a/tex/context/interface/mkiv/i-makeup.xml +++ b/tex/context/interface/mkiv/i-makeup.xml @@ -137,40 +137,4 @@ - - diff --git a/tex/context/interface/mkiv/i-margindata.xml b/tex/context/interface/mkiv/i-margindata.xml index cb97090b6..006101afa 100644 --- a/tex/context/interface/mkiv/i-margindata.xml +++ b/tex/context/interface/mkiv/i-margindata.xml @@ -165,269 +165,4 @@ - - diff --git a/tex/context/interface/mkiv/i-mathalignment.xml b/tex/context/interface/mkiv/i-mathalignment.xml index 1a1c139a3..725797cfa 100644 --- a/tex/context/interface/mkiv/i-mathalignment.xml +++ b/tex/context/interface/mkiv/i-mathalignment.xml @@ -67,24 +67,4 @@ - - diff --git a/tex/context/interface/mkiv/i-mathcases.xml b/tex/context/interface/mkiv/i-mathcases.xml index 7066134cf..1736f09b7 100644 --- a/tex/context/interface/mkiv/i-mathcases.xml +++ b/tex/context/interface/mkiv/i-mathcases.xml @@ -55,24 +55,4 @@ - - diff --git a/tex/context/interface/mkiv/i-mathmatrix.xml b/tex/context/interface/mkiv/i-mathmatrix.xml index f7bf61e93..d22e8141f 100644 --- a/tex/context/interface/mkiv/i-mathmatrix.xml +++ b/tex/context/interface/mkiv/i-mathmatrix.xml @@ -70,26 +70,6 @@ - - diff --git a/tex/context/interface/mkiv/i-mathstackers.xml b/tex/context/interface/mkiv/i-mathstackers.xml index 3875f2c4c..b5adfbc60 100644 --- a/tex/context/interface/mkiv/i-mathstackers.xml +++ b/tex/context/interface/mkiv/i-mathstackers.xml @@ -366,844 +366,6 @@ - - diff --git a/tex/context/interface/mkiv/i-mixedcolumns.xml b/tex/context/interface/mkiv/i-mixedcolumns.xml index 5f727de42..7d888a50f 100644 --- a/tex/context/interface/mkiv/i-mixedcolumns.xml +++ b/tex/context/interface/mkiv/i-mixedcolumns.xml @@ -119,18 +119,6 @@ - - diff --git a/tex/context/interface/mkiv/i-note.xml b/tex/context/interface/mkiv/i-note.xml index 12a6706fa..b76335765 100644 --- a/tex/context/interface/mkiv/i-note.xml +++ b/tex/context/interface/mkiv/i-note.xml @@ -518,35 +518,6 @@ - - @@ -578,35 +549,6 @@ - - @@ -696,29 +638,6 @@ - - diff --git a/tex/context/interface/mkiv/i-pairedbox.xml b/tex/context/interface/mkiv/i-pairedbox.xml index 0c9e05346..b7f9b24d8 100644 --- a/tex/context/interface/mkiv/i-pairedbox.xml +++ b/tex/context/interface/mkiv/i-pairedbox.xml @@ -137,36 +137,6 @@ - - diff --git a/tex/context/interface/mkiv/i-readme.pdf b/tex/context/interface/mkiv/i-readme.pdf index 44f939cca..97b40da43 100644 Binary files a/tex/context/interface/mkiv/i-readme.pdf and b/tex/context/interface/mkiv/i-readme.pdf differ diff --git a/tex/context/interface/mkiv/i-script.xml b/tex/context/interface/mkiv/i-script.xml index 4e5b44a29..89a929024 100644 --- a/tex/context/interface/mkiv/i-script.xml +++ b/tex/context/interface/mkiv/i-script.xml @@ -90,22 +90,4 @@ - - diff --git a/tex/context/interface/mkiv/i-sectionblock.xml b/tex/context/interface/mkiv/i-sectionblock.xml index 3c13ee394..e9622dac8 100644 --- a/tex/context/interface/mkiv/i-sectionblock.xml +++ b/tex/context/interface/mkiv/i-sectionblock.xml @@ -81,52 +81,4 @@ - - diff --git a/tex/context/interface/mkiv/i-shift.xml b/tex/context/interface/mkiv/i-shift.xml index 68199337e..2d26f5c51 100644 --- a/tex/context/interface/mkiv/i-shift.xml +++ b/tex/context/interface/mkiv/i-shift.xml @@ -73,20 +73,4 @@ - - diff --git a/tex/context/interface/mkiv/i-sort.xml b/tex/context/interface/mkiv/i-sort.xml index 1cd8419c0..a5763a4bf 100644 --- a/tex/context/interface/mkiv/i-sort.xml +++ b/tex/context/interface/mkiv/i-sort.xml @@ -177,31 +177,4 @@ - - diff --git a/tex/context/interface/mkiv/i-synonym.xml b/tex/context/interface/mkiv/i-synonym.xml index 31b4ae36a..ca3053fe4 100644 --- a/tex/context/interface/mkiv/i-synonym.xml +++ b/tex/context/interface/mkiv/i-synonym.xml @@ -209,32 +209,4 @@ - - diff --git a/tex/context/interface/mkiv/i-tabulation.xml b/tex/context/interface/mkiv/i-tabulation.xml index 09937504f..2c0808769 100644 --- a/tex/context/interface/mkiv/i-tabulation.xml +++ b/tex/context/interface/mkiv/i-tabulation.xml @@ -311,24 +311,6 @@ - diff --git a/tex/context/interface/mkiv/i-unit.xml b/tex/context/interface/mkiv/i-unit.xml index 247254ec5..c92a3b768 100644 --- a/tex/context/interface/mkiv/i-unit.xml +++ b/tex/context/interface/mkiv/i-unit.xml @@ -87,16 +87,6 @@ - - diff --git a/tex/context/interface/mkiv/i-verbatim.xml b/tex/context/interface/mkiv/i-verbatim.xml index eaeeaac1a..20f1bb873 100644 --- a/tex/context/interface/mkiv/i-verbatim.xml +++ b/tex/context/interface/mkiv/i-verbatim.xml @@ -337,107 +337,4 @@ - - diff --git a/tex/generic/context/luatex/luatex-fonts-merged.lua b/tex/generic/context/luatex/luatex-fonts-merged.lua index fea429485..1891e73c6 100644 --- a/tex/generic/context/luatex/luatex-fonts-merged.lua +++ b/tex/generic/context/luatex/luatex-fonts-merged.lua @@ -1,6 +1,6 @@ -- merged file : c:/data/develop/context/sources/luatex-fonts-merged.lua -- parent file : c:/data/develop/context/sources/luatex-fonts.lua --- merge date : 2020-07-13 16:23 +-- merge date : 2020-07-13 23:44 do -- begin closure to overcome local limits and interference -- cgit v1.2.3