From f58a2cb5d22c5931581274db1f0ec85ac903b747 Mon Sep 17 00:00:00 2001 From: Hans Hagen Date: Mon, 28 Oct 2019 20:03:50 +0100 Subject: 2019-10-28 18:22:00 --- tex/context/base/mkiv/mlib-svg.lua | 3617 ++++++++++++++++++++++++------------ 1 file changed, 2382 insertions(+), 1235 deletions(-) (limited to 'tex/context/base/mkiv/mlib-svg.lua') diff --git a/tex/context/base/mkiv/mlib-svg.lua b/tex/context/base/mkiv/mlib-svg.lua index d201ec20e..afbf36ecf 100644 --- a/tex/context/base/mkiv/mlib-svg.lua +++ b/tex/context/base/mkiv/mlib-svg.lua @@ -6,36 +6,21 @@ if not modules then modules = { } end modules ['mlib-svg'] = { license = "see context related readme files", } --- As usual with these standards, things like a path can be very compact while the rest is --- very verbose which defeats the point. This is a first attempt. There will be a converter --- to MP as well as directly to PDF. This module was made for one of the dangerous curves --- talks at the 2019 CTX meeting. I will do the font when I need it (not that hard). +-- 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. - --- Written with Anne Clark on speakers as distraction. - --- Todo when I run into an example: --- --- var(color,color) --- --color --- currentColor : when i run into an example --- some more fonts +-- 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. -- --- Todo: some day +-- 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). -- --- optimize --- instances --- withpen -> pickup --- --- Todo: when i am motivated --- --- shading --- "none" -> false --- clip = [ auto | rect(llx,lly,urx,ury) ] (in svg) --- xlink url ... whatever - -- 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 @@ -43,468 +28,236 @@ if not modules then modules = { } end modules ['mlib-svg'] = { -- (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. - +-- 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. +-- 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 type, tonumber = type, tonumber +local rawget, type, tonumber, tostring, next, setmetatable = rawget, type, tonumber, tostring, next, setmetatable local P, S, R, C, Ct, Cs, Cc, Cp, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Cc, lpeg.Cp, lpeg.Carg local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns local pi, sin, cos, asin, sind, cosd, tan, abs, sqrt = math.pi, math.sin, math.cos, math.asin, math.sind, math.cosd, math.tan, math.abs, math.sqrt -local concat, setmetatableindex = table.concat, table.setmetatableindex +local 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 = string.formatters +local formatters, fullstrip = string.formatters, string.fullstrip local extract = bit32.extract +local utfsplit, utfbyte = utf.split, utf.byte -local xmlconvert, xmlcollected, xmlcount, xmlfirst, xmlroot, xmltext = xml.convert, xml.collected, xml.count, xml.first, xml.root, xml.text - -metapost = metapost or { } -local metapost = metapost -local context = context +local 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 report = logs.reporter("metapost","svg") - -local trace = false --- local trace = true - --- todo: also a high res mode --- todo: optimize (no hurry) - -local f_rectangle = formatters['unitsquare xyscaled (%.3N,%.3N) shifted (%.3N,%.3N)'] -local f_rounded = formatters['roundedsquarexy(%.3N,%.3N,%.3N,%.3N) shifted (%.3N,%.3N)'] -local f_ellipse = formatters['(fullcircle xyscaled (%.3N,%.3N) shifted (%.3N,%.3N))'] -local f_circle = formatters['(fullcircle scaled %.3N shifted (%.3N,%.3N))'] -local f_line = formatters['((%.3N,%.3N)--(%.3N,%.3N))'] -local f_fill = formatters['fill %s(%s--cycle)%s%s%s ;'] -- play safe -local f_fill_cycle_c = formatters['fill %s(%s..cycle)%s%s%s ;'] -local f_fill_cycle_l = formatters['fill %s(%s--cycle)%s%s%s ;'] -local f_eofill = formatters['eofill %s(%s--cycle)%s%s%s ;'] -- play safe -local f_eofill_cycle_c = formatters['eofill %s(%s..cycle)%s%s%s ;'] -local f_eofill_cycle_l = formatters['eofill %s(%s--cycle)%s%s%s ;'] -local f_nofill = formatters['nofill %s(%s--cycle)%s ;'] -- play safe -local f_nofill_cycle_c = formatters['nofill %s(%s..cycle)%s ;'] -local f_nofill_cycle_l = formatters['nofill %s(%s--cycle)%s ;'] -local f_draw = formatters['draw %s(%s)%s%s%s%s%s ;'] -local f_nodraw = formatters['nodraw %s(%s)%s ;'] - --- local f_fill = formatters['F %s(%s--C)%s%s%s ;'] -- play safe --- local f_fill_cycle_c = formatters['F %s(%s..C)%s%s%s ;'] --- local f_fill_cycle_l = formatters['F %s(%s--C)%s%s%s ;'] --- local f_eofill = formatters['E %s(%s--C)%s%s%s ;'] -- play safe --- local f_eofill_cycle_c = formatters['E %s(%s..C)%s%s%s ;'] --- local f_eofill_cycle_l = formatters['E %s(%s--C)%s%s%s ;'] --- local f_nofill = formatters['f %s(%s--C)%s ;'] -- play safe --- local f_nofill_cycle_c = formatters['f %s(%s..C)%s ;'] --- local f_nofill_cycle_l = formatters['f %s(%s--C)%s ;'] --- local f_draw = formatters['D %s(%s)%s%s%s%s%s ;'] --- local f_nodraw = formatters['d %s(%s)%s ;'] - -local f_color = formatters[' withcolor "%s"'] ------ f_color_rgb = formatters[' withcolor "0x%02X%02X%02X"'] ------ f_color_rgba = formatters[' withcolor "0x%02X%02X%02X" withtransparency (1,%3N)'] ------ f_color_triplet = formatters['"0x%02X%02X%02X"'] - -local f_rgb = formatters[' withcolor svgcolor(%.3N,%.3N,%.3N)'] -local f_rgba = formatters[' withcolor svgcolor(%.3N,%.3N,%.3N) withtransparency (1,%3N)'] -local f_triplet = formatters['svgvolor(%.3N,%.3N,%.3N)'] -local f_gray = formatters[' withcolor %.3N'] - -local f_opacity = formatters[' withtransparency (1,%.3N)'] -local f_pen = formatters[' withpen pencircle scaled %.3N'] - -local f_dashed_n = formatters[" dashed dashpattern (%s ) "] -local f_dashed_y = formatters[" dashed dashpattern (%s ) shifted (%.3N,0) "] - -local f_moveto = formatters['(%N,%n)'] -local f_curveto_z = formatters[' controls (%.3N,%.3N) and (%.3N,%.3N) .. (%.3N,%.3N)'] -local f_curveto_n = formatters['.. controls (%.3N,%.3N) and (%.3N,%.3N) .. (%.3N,%.3N)'] -local f_lineto_z = formatters['(%.3N,%.3N)'] -local f_lineto_n = formatters['-- (%.3N,%.3N)'] - -local f_rotatedaround = formatters[" ) rotatedaround((%.3N,%.3N),%.3N)"] -local f_rotated = formatters[" ) rotated(%.3N)"] -local f_shifted = formatters[" ) shifted(%.3N,%.3N)"] -local f_slanted_x = formatters[" ) xslanted(%.3N)"] -local f_slanted_y = formatters[" ) yslanted(%.3N)"] -local f_scaled = formatters[" ) scaled(%.3N)"] -local f_xyscaled = formatters[" ) xyscaled(%.3N,%.3N)"] -local f_matrix = formatters[" ) transformed bymatrix(%.3N,%.3N,%.3N,%.3N,%.3N,%.3N)"] - --- penciled n -> withpen pencircle scaled n --- applied (...) -> transformed bymatrix (...) --- withopacity n -> withtransparency (1,n) - -local s_clip_start = 'draw image (' -local f_clip_stop = formatters[') ; clip currentpicture to (%s) ;'] -local f_eoclip_stop = formatters[') ; eoclip currentpicture to (%s) ;'] - -local f_transform_start = formatters["draw %s image ( "] -local f_transform_stop = formatters[") %s ;"] - -local s_offset_start = "draw image ( " -local f_offset_stop = formatters[") shifted (%.3N,%.3N) ;"] - -local s_scaled_start = "draw image ( " -local f_scaled_stop = formatters[") scaled %.3N ;"] - -local s_hacked_start = "draw image (" -local f_hacked_stop = formatters[") shifted (0,%.3N) scaled %.3N ;"] --- local f_hacked_stop = formatters[") scaled %.3N ;"] - -local f_viewport_start = "draw image (" -local f_viewport_stop = ") ;" -local f_viewport_shift = formatters["currentpicture := currentpicture shifted (%03N,%03N);"] -local f_viewport_clip = formatters["clip currentpicture to (unitsquare xyscaled (%03N,%03N));"] - -local f_linecap = formatters["interim linecap := %s ;"] -local f_linejoin = formatters["interim linejoin := %s ;"] -local f_miterlimit = formatters["interim miterlimit := %s ;"] - -local s_begingroup = "begingroup;" -local s_endgroup = "endgroup;" - --- make dedicated macro - -local s_shade_linear = ' withshademethod "linear" ' -local s_shade_circular = ' withshademethod "circular" ' -local f_shade_step = formatters['withshadestep ( withshadefraction %.3N withshadecolors(%s,%s) )'] -local f_shade_one = formatters['withprescript "sh_center_a=%.3N %.3N"'] -local f_shade_two = formatters['withprescript "sh_center_b=%.3N %.3N"'] - -local f_text_scaled = formatters['(textext.drt("%s") scaled %.3N shifted (%.3N,%.3N))'] -local f_text_normal = formatters['(textext.drt("%s") shifted (%.3N,%.3N))'] - --- 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 svgcolors = { - black = 0x000000, - navy = 0x000080, - darkblue = 0x00008B, - mediumblue = 0x0000CD, - blue = 0x0000FF, - darkgreen = 0x006400, - green = 0x008000, - teal = 0x008080, - darkcyan = 0x008B8B, - deepskyblue = 0x00BFFF, - darkturquoise = 0x00CED1, - mediumspringgreen = 0x00FA9A, - lime = 0x00FF00, - springgreen = 0x00FF7F, - cyan = 0x00FFFF, - aqua = 0x00FFFF, - midnightblue = 0x191970, - dodgerblue = 0x1E90FF, - lightseagreen = 0x20B2AA, - forestgreen = 0x228B22, - seagreen = 0x2E8B57, - darkslategray = 0x2F4F4F, - darkslategrey = 0x2F4F4F, - limegreen = 0x32CD32, - mediumseagreen = 0x3CB371, - turquoise = 0x40E0D0, - royalblue = 0x4169E1, - steelblue = 0x4682B4, - darkslateblue = 0x483D8B, - mediumturquoise = 0x48D1CC, - indigo = 0x4B0082, - darkolivegreen = 0x556B2F, - cadetblue = 0x5F9EA0, - cornflowerblue = 0x6495ED, - mediumaquamarine = 0x66CDAA, - dimgrey = 0x696969, - dimgray = 0x696969, - slateblue = 0x6A5ACD, - olivedrab = 0x6B8E23, - slategrey = 0x708090, - slategray = 0x708090, - lightslategray = 0x778899, - lightslategrey = 0x778899, - mediumslateblue = 0x7B68EE, - lawngreen = 0x7CFC00, - chartreuse = 0x7FFF00, - aquamarine = 0x7FFFD4, - maroon = 0x800000, - purple = 0x800080, - olive = 0x808000, - gray = 0x808080, - grey = 0x808080, - skyblue = 0x87CEEB, - lightskyblue = 0x87CEFA, - blueviolet = 0x8A2BE2, - darkred = 0x8B0000, - darkmagenta = 0x8B008B, - saddlebrown = 0x8B4513, - darkseagreen = 0x8FBC8F, - lightgreen = 0x90EE90, - mediumpurple = 0x9370DB, - darkviolet = 0x9400D3, - palegreen = 0x98FB98, - darkorchid = 0x9932CC, - yellowgreen = 0x9ACD32, - sienna = 0xA0522D, - brown = 0xA52A2A, - darkgray = 0xA9A9A9, - darkgrey = 0xA9A9A9, - lightblue = 0xADD8E6, - greenyellow = 0xADFF2F, - paleturquoise = 0xAFEEEE, - lightsteelblue = 0xB0C4DE, - powderblue = 0xB0E0E6, - firebrick = 0xB22222, - darkgoldenrod = 0xB8860B, - mediumorchid = 0xBA55D3, - rosybrown = 0xBC8F8F, - darkkhaki = 0xBDB76B, - silver = 0xC0C0C0, - mediumvioletred = 0xC71585, - indianred = 0xCD5C5C, - peru = 0xCD853F, - chocolate = 0xD2691E, - tan = 0xD2B48C, - lightgray = 0xD3D3D3, - lightgrey = 0xD3D3D3, - thistle = 0xD8BFD8, - orchid = 0xDA70D6, - goldenrod = 0xDAA520, - palevioletred = 0xDB7093, - crimson = 0xDC143C, - gainsboro = 0xDCDCDC, - plum = 0xDDA0DD, - burlywood = 0xDEB887, - lightcyan = 0xE0FFFF, - lavender = 0xE6E6FA, - darksalmon = 0xE9967A, - violet = 0xEE82EE, - palegoldenrod = 0xEEE8AA, - lightcoral = 0xF08080, - khaki = 0xF0E68C, - aliceblue = 0xF0F8FF, - honeydew = 0xF0FFF0, - azure = 0xF0FFFF, - sandybrown = 0xF4A460, - wheat = 0xF5DEB3, - beige = 0xF5F5DC, - whitesmoke = 0xF5F5F5, - mintcream = 0xF5FFFA, - ghostwhite = 0xF8F8FF, - salmon = 0xFA8072, - antiquewhite = 0xFAEBD7, - linen = 0xFAF0E6, - lightgoldenrodyellow = 0xFAFAD2, - oldlace = 0xFDF5E6, - red = 0xFF0000, - fuchsia = 0xFF00FF, - magenta = 0xFF00FF, - deeppink = 0xFF1493, - orangered = 0xFF4500, - tomato = 0xFF6347, - hotpink = 0xFF69B4, - coral = 0xFF7F50, - darkorange = 0xFF8C00, - lightsalmon = 0xFFA07A, - orange = 0xFFA500, - lightpink = 0xFFB6C1, - pink = 0xFFC0CB, - gold = 0xFFD700, - peachpuff = 0xFFDAB9, - navajowhite = 0xFFDEAD, - moccasin = 0xFFE4B5, - bisque = 0xFFE4C4, - mistyrose = 0xFFE4E1, - blanchedalmond = 0xFFEBCD, - papayawhip = 0xFFEFD5, - lavenderblush = 0xFFF0F5, - seashell = 0xFFF5EE, - cornsilk = 0xFFF8DC, - lemonchiffon = 0xFFFACD, - floralwhite = 0xFFFAF0, - snow = 0xFFFAFA, - yellow = 0xFFFF00, - lightyellow = 0xFFFFE0, - ivory = 0xFFFFF0, - white = 0xFFFFFF, -} +local bpfactor = number.dimenfactors.bp -local svgcolor = setmetatableindex(function(t,k) - -- we delay building all these strings - local v = svgcolors[k] - if v then - v = f_rgb(extract(v,16,8),extract(v,8,8),extract(v,0,8)) +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 - v = false + -- comment of so end - t[k] = v - return v -end) +end -local p_digit = lpegpatterns.digit -local p_hexdigit = lpegpatterns.hexdigit -local p_space = lpegpatterns.whitespace +xml.inheritattributes = xmlinheritattributes -local p_hexcolor = P("#") * C(p_hexdigit*p_hexdigit)^1 / function(r,g,b) - r = tonumber(r,16)/255 - if g then - g = tonumber(g,16)/255 - end - if b then - b = tonumber(b,16)/255 - end - if b then - return f_rgb(r,g,b) - else - return f_gray(r) - end -end +-- Maybe some day helpers will move to the metapost.svg namespace! -local p_hexcolor3 = P("#") * C(p_hexdigit*p_hexdigit)^1 / function(r,g,b) - r = tonumber(r,16)/255 - g = g and tonumber(g,16)/255 or r - b = b and tonumber(b,16)/255 or g - return f_triplet(r,g,b) -end +metapost = metapost or { } +local metapost = metapost +local context = context -local function hexcolor(c) - return lpegmatch(p_hexcolor,c) -end +local report = logs.reporter("metapost","svg") -local function hexcolor3(c) - return lpegmatch(p_hexcolor3,c) -end +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) --- gains a little: +local pathtracer = { + ["stroke"] = "darkred", + ["stroke-opacity"] = ".5", + ["stroke-width"] = ".5", + ["fill"] = "darkgray", + ["fill-opacity"] = ".75", +} --- 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 +-- 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). -- --- local function hexcolor (c) return hexhash [c] end -- directly do hexhash [c] --- local function hexcolor3(c) return hexhash3[c] end -- directly do hexhash3[c] - --- Most of the conversion is rather trivial code till I ran into a file with arcs. A bit --- of searching lead to the a2c javascript function but it has some puzzling thingies --- (like sin and cos definitions that look like leftovers). Anyway, we can if needed --- optimize it a bit more. Here does it come from: +-- 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 d120 = (pi * 120) / 180 -local pi2 = 2 * pi - -local function a2c(x1, y1, rx, ry, angle, large, sweep, x2, y2, f1, f2, cx, cy) +local a2c do - local recursive = f1 + local d120 = (pi * 120) / 180 + local pi2 = 2 * pi - if rx == 0 or ry == 0 then - return { x1, y1, x2, y2, x2, y2 } - end + a2c = function(x1, y1, rx, ry, angle, large, sweep, x2, y2, f1, f2, cx, cy) - if x1 == x2 and y1 == y2 then - return { x1, y1, x2, y2, x2, y2 } - end + if (rx == 0 or ry == 0 ) or (x1 == x2 and y1 == y2) then + return { x1, y1, x2, y2, x2, y2 } + end - local rad = pi / 180 * angle - local res = nil + 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) - local cosrad = cos(-rad) -- local cosrad = cosd(angle) - local sinrad = sin(-rad) -- local sinrad = sind(angle) + if not recursive then - if not recursive then + x1, y1 = x1 * cosrad - y1 * sinrad, x1 * sinrad + y1 * cosrad + x2, y2 = x2 * cosrad - y2 * sinrad, x2 * sinrad + y2 * cosrad - 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) - 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 - 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 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)) - local k = total == 0 and 0 or sqrt(abs((rx2 * ry2 - rx2yy - ry2xx) / total)) + if large == sweep then + k = -k + end - if large == sweep then - k = -k - end + cx = k * rx * y / ry + (x1 + x2) / 2 + cy = k * -ry * x / rx + (y1 + y2) / 2 - 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 = (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) - 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 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 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 - if sweep ~= 0 and f1 > f2 then - f1 = f1 - pi2 end - if sweep == 0 and f2 > f1 then - f2 = f2 - pi2 + + 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 - end + local c1 = cos(f1) + local s1 = sin(f1) + local c2 = cos(f2) + local s2 = sin(f2) - 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 t = tan((f2 - f1) / 4) + local hx = 4 * rx * t / 3 + local hy = 4 * ry * t / 3 - 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 + 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 - return r end --- incredible: we can find .123.456 => 0.123 0.456 ... +-- We share some patterns. + +local p_digit = lpegpatterns.digit +local p_hexdigit = lpegpatterns.hexdigit +local p_space = lpegpatterns.whitespace local factors = { ["pt"] = 1.25, @@ -521,14 +274,18 @@ 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_separator = S("\t\n\r ,")^1 -local p_number = (S("+-")^0 * (p_digit^0 * P(".") * p_digit^1 + p_digit^1 * P(".") + p_digit^1) ) +local 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 @@ -537,11 +294,11 @@ local function convert_y (n,u) n = tonumber(n) if u == true then return percen 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_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 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 @@ -550,300 +307,688 @@ local p_number_y = c_number_u / convert_y local p_number_vy = c_number_u / convert_vy local p_number_r = c_number_u / convert_r --- local p_number = p_number_r -- maybe no percent here - -local function asnumber (s) return lpegmatch(p_number, s) end -local function asnumber_r (s) return lpegmatch(p_number_r, s) end -local function asnumber_x (s) return lpegmatch(p_number_x, s) end -local function asnumber_y (s) return lpegmatch(p_number_y, s) end -local function asnumber_vx(s) return lpegmatch(p_number_vx,s) end -local function asnumber_vy(s) return lpegmatch(p_number_vy,s) end - -local p_numbersep = p_number_n + p_separator -local p_numbers = p_separator^0 * P("(") * p_numbersep^0 * p_separator^0 * P(")") -local p_four = p_numbersep^4 - --- local p_path = Ct((p_command + (p_number_x * p_separator^0 * p_number_y * p_separator^0) + p_separator)^1) - -local p_path = Ct ( ( - p_command_xy * (p_separator^0 * p_number_vx * - p_separator^0 * p_number_vy )^1 - + p_command_x * (p_separator^0 * p_number_vx )^1 - + p_command_y * (p_separator^0 * p_number_vy )^1 - + p_command_a * (p_separator^0 * p_number_vx * - p_separator^0 * p_number_vy * - p_separator^0 * p_number_r * - p_separator^0 * p_number_n * -- flags - p_separator^0 * p_number_n * -- flags - p_separator^0 * p_number_vx * - p_separator^0 * p_number_vy )^1 +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 ) -local p_rgbacolor = P("rgba(") * (C(p_number) + p_separator)^1 * P(")") +-- 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 rgbcomponents, withcolor, thecolor 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 function rgbacolor(s) - local r, g, b, a = lpegmatch(p_rgbacolor,s) - if a then - return f_rgba(r/255,g/255,b/255,a) - end -end + local f_rgb = formatters['withcolor svgcolor(%N,%N,%N)'] + local f_gray = formatters['withcolor svggray(%N)'] + local f_rgba = formatters['withcolor svgcolor(%N,%N,%N) withtransparency (1,%N)'] + local f_graya = formatters['withcolor svggray(%N) withtransparency (1,%N)'] + local f_name = formatters['withcolor "%s"'] + local f_svgcolor = formatters['svgcolor(%N,%N,%N)'] + local f_svggray = formatters['svggray(%N)'] + local f_svgname = formatters['"%s"'] + + 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 function viewbox(v) - local x, y, w, h = lpegmatch(p_four,v) - if h then - return x, y, w, h + local p_fraction = C(p_number) * C("%")^-1 / function(a,b) + a = tonumber(a) return a / (b and 100 or 255) end -end + local p_hexcolor = P("#") * C(p_hexdigit*p_hexdigit)^1 / function(r,g,b) + return r and tonumber(r,16)/255 or nil, g and tonumber(g,16)/255 or nil, b and tonumber(b,16)/255 or nil + end + local p_rgbacolor = P("rgb") * (P("a")^-1) * P("(") * (p_fraction + p_separator)^1 * P(")") --- actually we can loop faster because we can go to the last one + rgbcomponents = function(color) + local h = lpegmatch(p_hexcolor,color) + if h then + return h + end + local r, g, b, a = lpegmatch(p_rgbacolor,color) + if r then + return r, g or r, b or r + end + local t = triplets[color] + return t[1], t[2], t[3] + + end -local function grabpath(str) - local p = lpegmatch(p_path,str) - local np = #p - local t = { } -- no real saving here if we share - local n = 0 - local all = { entries = np, closed = false, curve = false } - local a = 0 - local i = 0 - local last = "M" - local prev = last - local kind = "L" - local x = 0 - local y = 0 - local x1 = 0 - local y1 = 0 - local x2 = 0 - local y2 = 0 - local rx = 0 - local ry = 0 - local ar = 0 - local al = 0 - local as = 0 - local ac = nil - while i < np do - i = i + 1 - local pi = p[i] - if type(pi) ~= "number" then - last = pi - i = i + 1 - pi = p[i] - end - -- most often - ::restart:: - if last == "c" then - x1 = x + pi - i = i + 1 ; y1 = y + p[i] - i = i + 1 ; x2 = x + p[i] - i = i + 1 ; y2 = y + p[i] - i = i + 1 ; x = x + p[i] - i = i + 1 ; y = y + p[i] - goto curveto - elseif last == "l" then - x = x + pi - i = i + 1 ; y = y + p[i] - goto lineto - elseif last == "h" then - x = x + pi - goto lineto - elseif last == "v" then - y = y + pi - goto lineto - elseif last == "a" then - x1 = x - y1 = y - rx = pi - i = i + 1 ; ry = p[i] - i = i + 1 ; ar = p[i] - i = i + 1 ; al = p[i] - i = i + 1 ; as = p[i] - i = i + 1 ; x = x + p[i] - i = i + 1 ; y = y + p[i] - goto arc - elseif last == "s" then - if prev == "C" then - x1 = 2 * x - x2 - y1 = 2 * y - y2 + withcolor = function(color) + local r, g, b = lpegmatch(p_hexcolor,color) + if b and not (r == g and g == b) then + return f_rgb(r,g,b) + elseif r then + return f_gray(r) + end + local r, g, b, a = lpegmatch(p_rgbacolor,color) + if a then + if a == 1 then + if r == g and g == b then + return f_gray(r) + else + return f_rgb(r,g,b) + end 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 + if r == g and g == b then + return f_graya(r,a) + else + return f_rgba(r,g,b,a) + end + end + end + if not r then + local t = triplets[color] + if t then + r, g, b = t[1], t[2], t[3] + end + end + if r then + if r == g and g == b then + return f_gray(r) + elseif g and b then + return f_rgb(r,g,b) 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 + return f_gray(r) + end + end + return f_name(color) + end + + thecolor = function(color) + local h = lpegmatch(p_hexcolor,color) + if h then + return h + end + local r, g, b, a = lpegmatch(p_rgbacolor,color) + if not r then + local t = triplets[color] + if t then + r, g, b = t[1], t[2], t[3] + end + end + if r then + if r == g and g == b then + return f_svggray(r) + elseif g and b then + return f_svgcolor(r,g,b) 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 + return f_svggray(r) + 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 - x1 = x - y1 = y + goto continue end - x2 = pi - i = i + 1 ; y2 = p[i] - goto quadratic - else - goto continue - end - ::moveto:: - n = n + 1 ; t[n] = f_moveto(x,y) - last = last == "M" and "L" or "l" - prev = "M" - goto continue - ::lineto:: - n = n + 1 ; t[n] = (n > 0 and f_lineto_n or f_lineto_z)(x,y) - prev = "L" - goto continue - ::curveto:: - n = n + 1 ; t[n] = (n > 0 and f_curveto_n or f_curveto_z)(x1,y1,x2,y2,x,y) - prev = "C" - goto continue - ::arc:: - ac = a2c(x1,y1,rx,ry,ar,al,as,x,y) - for i=1,#ac,6 do + ::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)( - ac[i],ac[i+1],ac[i+2],ac[i+3],ac[i+4],ac[i+5] + 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 - 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 == n then - break + -- 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 - i = i - 1 + goto continue end - kind = prev - prev = "Z" - ::continue:: - end - if n > 0 then - a = a + 1 ; all[a] = concat(t,"",1,n) ; n = 0 - end - if prev == "Z" then - all.closed = true + ::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 - all.curve = (kind == "C" or kind == "A") - return all + end -local transform do +-- todo: viewbox helper + +local s_wrapped_start = "draw image (" +local f_wrapped_stop = formatters[") shifted (0,%N) scaled %N ;"] + +local handletransform, handleviewbox do --todo: better lpeg - local n = 0 + 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 - n = n + 1 return r and f_rotatedaround(x,-(y or x),-r) elseif r then - n = n + 1 return f_rotated(-r) else return "" @@ -852,10 +997,8 @@ local transform do local function translate(x,y) if y then - n = n + 1 return f_shifted(x,-y) elseif x then - n = n + 1 return f_shifted(x,0) else return "" @@ -864,10 +1007,8 @@ local transform do local function scale(x,y) if y then - n = n + 1 return f_xyscaled(x,y) elseif x then - n = n + 1 return f_scaled(x) else return "" @@ -876,7 +1017,6 @@ local transform do local function skewx(x) if x then - n = n + 1 return f_slanted_x(math.sind(-x)) else return "" @@ -885,7 +1025,6 @@ local transform do local function skewy(y) if y then - n = n + 1 return f_slanted_y(math.sind(-y)) else return "" @@ -893,7 +1032,6 @@ local transform do end local function matrix(rx,sx,sy,ry,tx,ty) - n = n + 1 return f_matrix(rx or 1, sx or 0, sy or 0, ry or 1, tx or 0, - (ty or 0)) end @@ -910,14 +1048,20 @@ local transform do + P(1)/"" )^1) - transform = function(t,image) - n = 0 - local e = lpegmatch(p_transform,t) - local b = rep("( ",n) - if image then - return f_transform_start(b), f_transform_stop(e) - else - return b, e + handletransform = function(at) + local t = at.transform + if t then + local e = lpegmatch(p_transform,t) + return s_transform_start, f_transform_stop(e), t + 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 @@ -927,10 +1071,13 @@ local dashed do -- actually commas are mandate but we're tolerant - local p_number = p_separator^0/"" * p_number_r - local p_on = Cc(" on ") * p_number - local p_off = Cc(" off ") * p_number - local p_dashed = Cs((p_on * p_off^-1)^1) + 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 @@ -944,108 +1091,274 @@ end do - local defaults = { - x = 0, x1 = 0, x2 = 0, cx = 0, rx = 0, - y = 0, y1 = 0, y2 = 0, cy = 0, ry = 0, - r = 0, - - width = 0, - height = 0, - - stroke = "none", - fill = "black", - opacity = "none", - - ["stroke-width"] = 1, - ["stroke-linecap"] = "none", - ["stroke-linejoin"] = "none", - ["stroke-dasharray"] = "none", - ["stroke-dashoffset"] = "none", - ["stroke-miterlimit"] = "none", - ["stroke-opacity"] = "none", - - ["fill-opacity"] = "none", - -- ["fill-rule"] = "nonzero", - -- ["clip-rule"] = "nonzero", - } - local handlers = { } local process = false local root = false local result = false local r = false local definitions = false - local styles = false - local bodyfont = false + 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 - -- todo: check use ... and make definitions[id] self resolving + -- 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 - return res - else - -- warning end + return res end - local function clippath(id,a) - local spec = definitions[id] or locate(id) - if spec then - local kind = spec.tg - if kind == "clipPath" then - ::again:: - for c in xmlcollected(spec,"/(path|use|g)") do - local tg = c.tg - if tg == "use" then - local ca = c.at - local id = ca["xlink:href"] - if id then - spec = locate(id) - if spec then - local sa = spec.at - setmetatableindex(sa,ca) - if spec.tg == "path" then - local d = sa.d - if d then - local p = grabpath(d) - p.evenodd = sa["clip-rule"] == "evenodd" or a["clip-rule"] == "evenodd" - return p - else - goto done - end - else - goto again - end + -- 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 - end - -- break - elseif tg == "path" then - local ca = c.at - local d = ca.d - if d then - local p = grabpath(d) - p.evenodd = ca["clip-rule"] == "evenodd" or a["clip-rule"] == "evenodd" - return p else - goto done + goto again end - else - -- inherit? end end - ::done:: + -- 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 - report("unknown clip %a",id) + -- 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] + local spec = definitions[id] -- no locate ! if spec then local kind = spec.tg local shade = nil @@ -1054,25 +1367,25 @@ do if kind == "linearGradient" then shade = { s_shade_linear } -- - local x1 = a.x1 - local y1 = a.y1 - local x2 = a.x2 - local y2 = a.y2 + 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)) + 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)) + n = n + 1 ; shade[n] = f_shade_one(asnumber_vx(x2),asnumber_vy(y2)) end -- elseif kind == "radialGradient" then shade = { s_shade_circular } -- - local cx = a.cx -- x center - local cy = a.cy -- y center - local r = a.r -- radius - local fx = a.fx -- focal points - local fy = a.fy -- focal points + 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 @@ -1091,566 +1404,1328 @@ do -- 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 = a.offset - local colorb = a["stop-color"] - local opacity = a["stop-opacity"] - - colorb = colorb and (hexcolor3(colorb) or svgcolor[colorb] or colorb) - colora = colora or colorb - + 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 - n = n + 1 shade[n] = f_shade_step(fraction,colora,colorb) + if colora and colorb and color_a ~= "" and color_b ~= "" then + n = n + 1 ; shade[n] = f_shade_step(fraction,colora,colorb) + end - prevcolor = color + colora = colorb end - return concat(shade) + return concat(shade," ") end end - local function drawproperties(stroke,a) - local w = a["stroke-width"] - if w then - w = f_pen(asnumber_r(w)) - else - w = "" - end - local d = a["stroke-dasharray"] - if d ~= "none" then - local o = a["stroke-dashoffset"] - if o ~= "none" then + 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) - else - d = "" - end - local c = hexcolor(stroke) - if not c then - c = rgbacolor(stroke) - if not c then - c = f_color(svgcolor[stroke] or stroke) - end end - local o = a["stroke-opacity"] or a.opacity - if o ~= "none" then + 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 = "" + o = nil end - else - o = "" end - return w, d, c, o + return p, d, c, o end - local function fillproperties(fill,a) - local c = gradient(fill) - if not c then - c = hexcolor(fill) - if not c then - c = f_color(svgcolor[fill] or fill) + 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 - local o = a["fill-opacity"] or a.opacity - if o == "none" then + 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 and o ~= 1 then - o = f_opacity(asnumber_r(o)) - else - o = "" + if o == 1 then + return c + elseif o then + return c, f_opacity(o), o == 0 end - else - o = "" end - return c, o + return c end -- todo: clip = [ auto | rect(llx,lly,urx,ury) ] - local function offset(a) - local x = a.x - local y = a.y - if x then x = asnumber_vx(x) end - if y then y = asnumber_vy(y) end - if not x then x = 0 end - if not y then y = 0 end + 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 - r = r + 1 ; result[r] = s_offset_start - return function() - r = r + 1 ; result[r] = f_offset_stop(x,-y) - end + return s_offset_start, f_offset_stop(x,y) end end - local function viewport(x,y,w,h,noclip) - r = r + 1 ; result[r] = f_viewport_start + 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 not noclip then + if okay and not noclip then r = r + 1 ; result[r] = f_viewport_clip(w,-h) end - r = r + 1 ; result[r] = f_viewport_stop + + r = r + 1 ; result[r] = s_viewport_stop end end - function handlers.defs(c,top) + -- 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 - local id = a.id - -- setmetatableindex(a,top) + 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 - function handlers.use(c,top) - local a = setmetatableindex(c.at,top) - local id = a["xlink:href"] -- better a rawget - if id then - local d = definitions[id] - if d then - local h = handlers[d.tg] - if h then - h(d,a) - end - else - local res = locate(id) - if res then - local wrapup = offset(a) - process(res,"/*",top) - if wrapup then - wrapup() - end - else - report("unknown definition %a",id) - end - end - end - end + local uselevel = 0 - local linecaps = { butt = "butt", square = "squared", round = "rounded" } - local linejoins = { miter = "mitered", bevel = "beveled", round = "rounded" } + 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) - local function stoplineproperties() - r = r + 1 result[r] = s_endgroup - end + if boffset then + r = r + 1 result[r] = boffset + end - local function startlineproperties(a) - local cap = a["stroke-linecap"] - local join = a["stroke-linejoin"] - local limit = a["stroke-miterlimit"] - cap = cap ~= "none" and linecaps [cap] or false - join = join ~= "none" and linejoins[join] or false - limit = limit ~= "none" and asnumber_r(limit) or false - if cap or join or limit then - r = r + 1 result[r] = s_begingroup - if cap then - r = r + 1 result[r] = f_linecap(cap) + -- local clippath = at.clippath + + if btransform then + r = r + 1 result[r] = btransform end - if join then - r = r + 1 result[r] = f_linejoin(join) + + 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 limit then - r = r + 1 result[r] = f_miterlimit(limit) + + if eoffset then + r = r + 1 result[r] = eoffset end - return stoplineproperties + + uselevel = uselevel - 1 + else + report("use: unknown definition %a",id) end end - local function flush(a,shape,nofill) - local fill = not nofill and a.fill - local stroke = a.stroke - local cpath = a["clip-path"] - local trans = a.transform - local b, e = "", "" + 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 - cpath = clippath(cpath,a) + r = r + 1 ; result[r] = s_clip_start end - if cpath then - r = r + 1 result[r] = s_clip_start + + if btransform then + r = r + 1 ; result[r] = btransform end - if trans then - b, e = transform(trans) + + r = r + 1 ; result[r] = f_do_draw(object) + + if c then + r = r + 1 ; result[r] = c end - if fill and fill ~= "none" then - r = r + 1 result[r] = (a["fill-rule"] == "evenodd" and f_eofill or f_fill)(b,shape,e,fillproperties(fill,a)) + + if o then + r = r + 1 ; result[r] = o end - if stroke and stroke ~= "none" and stroke ~= 0 then - local wrapup = startlineproperties(a) - r = r + 1 result[r] = f_draw(b,shape,e,drawproperties(stroke,a)) - if wrapup then - wrapup() - end + + if etransform then + r = r + 1 ; result[r] = etransform end + + r = r + 1 ; result[r] = ";" + if cpath then - r = r + 1 result[r] = (cpath.evenodd and f_eoclip_stop or f_clip_stop)(cpath[1]) + 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 - -- todo: strokes in: + do - local function flushpath(a,shape) - local fill = a.fill - local stroke = a.stroke - local cpath = a["clip-path"] - local trans = a.transform - local b, e = "", "" - if cpath then - cpath = definitions[cpath] - end - if cpath then - r = r + 1 result[r] = s_clip_start - end - -- todo: image (nicer for transform too) - if trans then - b, e = transform(trans) - end - if fill and fill ~= "none" then - local n = #shape - for i=1,n do - r = r + 1 - if i == n then - local f = a["fill-rule"] == "evenodd" - if shape.closed then - f = f and f_eofill or f_fill - elseif shape.curve then - f = f and f_eofill_cycle_c or f_fill_cycle_c - else - f = f and f_eofill_cycle_l or f_fill_cycle_l - end - result[r] = f(b,shape[i],e,fillproperties(fill,a)) - else - result[r] = f_nofill(b,shape[i],e) + 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 - if stroke and stroke ~= "none" and stroke ~= 0 then - local wrapup = startlineproperties(a) - local n = #shape - for i=1,n do - r = r + 1 - if i == n then - result[r] = f_draw(b,shape[i],e,drawproperties(stroke,a)) + + -- 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) + 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 = at["markerUnits"] + local height = at["markerHeight"] + local angx = 0 + local angy = 0 + local angle = 0 + + if where == "beg" then + if orient == "auto" then -- unchecked + -- no angle + angx = x2 - x3 + angy = y2 - y3 + elseif orient == "auto-start-reverse" then -- checked + -- points to start + angx = x3 - x2 + angy = y3 - y2 + elseif orient then -- unchecked + angle = asnumber_r(orient) + end + elseif where == "end" then + if orient == "auto" then -- unchecked + -- no angle ? + angx = x1 - x2 + angy = y1 - y2 + elseif orient == "auto-start-reverse" then -- unchecked + -- points to end + angx = x2 - x1 + angy = y2 - y1 + 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 -- defaults + height = height and asnumber_y(height) or 3 -- defaults + + 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 - result[r] = f_nodraw(b,shape[i],e) + r = r + 1 ; result[r] = f_rotation_stop(angx,angy) end end - if wrapup then - wrapup() + + 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 - if cpath then - r = r + 1 result[r] = f_clip_stop(cpath[1]) - end - end - function handlers.line(c,top) - local a = setmetatableindex(c.at,top) - local x1 = asnumber_vx(a.x1) - local y1 = asnumber_vy(a.y1) - local x2 = asnumber_vx(a.x2) - local y2 = asnumber_vy(a.y2) - if trace then - report("line: x1 %.3N, y1 %.3N, x2 %.3N, x3 %.3N",x1,y1,x2,y2) + local function addmarkers(list,begmarker,midmarker,endmarker) + 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]) + 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]) + 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) + end + end + else + -- no line + end end - flush(a,f_line(x1,y1,x2,y2),true) - end - function handlers.rect(c,top) - local a = setmetatableindex(c.at,top) - local w = asnumber_x(a.width) - local h = asnumber_y(a.height) - local x = asnumber_vx(a.x) - local y = asnumber_vy(a.y) - h - local rx = a.rx - local ry = a.ry - if rx == 0 then rx = false end -- maybe no default 0 - if ry == 0 then ry = false end -- maybe no default 0 - if rx or ry then - rx = asnumber_x(rx or ry) - ry = asnumber_y(ry or rx) - if trace then - report("rect: x %.3N, y %.3N, w %.3N, h %.3N, rx %.3N, ry %.3N",x,y,w,h,rx,ry) + 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 - flush(a,f_rounded(w,h,rx,ry,x,y)) - else - if trace then - report("rect: x %.3N, y %.3N, w %.3N, h %.3N",x,y,w,h) + + 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) + 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 - flush(a,f_rectangle(w,h,x,y)) - end - end - function handlers.ellipse(c,top) - local a = setmetatableindex(c.at,top) - local x = asnumber_vx(a.cx) - local y = asnumber_vy(a.cy) - local rx = asnumber_x (a.rx) - local ry = asnumber_y (a.ry) - if trace then - report("ellipse: x %.3N, y %.3N, rx %.3N, ry %.3N",x,y,rx,ry) end - flush(a,f_ellipse(2*rx,2*ry,x,y)) - end - function handlers.circle(c,top) - local a = setmetatableindex(c.at,top) - local x = asnumber_vx(a.cx) - local y = asnumber_vy(a.cy) - local r = asnumber_r (a.r) - if trace then - report("circle: x %.3N, y %.3N, r %.3N",x,y,r) + 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 - flush(a,f_circle(2*r,x,y)) - end - function handlers.path(c,top) - local a = setmetatableindex(c.at,top) - local d = a.d - if d then - local p = grabpath(d) - if trace then - report("path: %i entries, %sclosed",p.entries,p.closed and "" or "not ") + 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 - flushpath(a,p) end - end - do + 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 - local p_pair = p_separator^0 * p_number_vx * p_separator^0 * p_number_vy - local p_pair_z = p_pair / f_lineto_z - local p_pair_n = p_pair / f_lineto_n + 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_z * p_pair_n^0 * p_close) - - function handlers.polyline(c,top) - local a = setmetatableindex(c.at,top) - local p = a.points - if p then - flush(a,lpegmatch(p_polyline,p,1,")"),true) + 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.polygon(c,top) - local a = setmetatableindex(c.at,top) - local p = a.points - if p then - flush(a,lpegmatch(p_polyline,p,1,"--cycle)")) + 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) + 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 - function handlers.g(c,top) - local a = c.at - setmetatableindex(a,top) - process(c,"/*",a) - 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 - function handlers.symbol(c,top) - report("todo: %s","symbol") end - -- We only need to filter classes .. I will complete this when I really need - -- it. Not hard to do but kind of boring. + -- 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) - local p_class = P(".") * C((R("az","AZ","09","__","--")^1)) - local p_spec = P("{") * C((1-P("}"))^1) * P("}") + if cpath then + r = r + 1 ; result[r] = s_clip_start + end + + if btransform then + r= r + 1 result[r] = btransform + end - local p_grab = ((Carg(1) * p_class * p_space^0 * p_spec / function(t,k,v) t[k] = v end) + p_space^1 + P(1))^1 + local _transform = transform + local _clippath = clippath + at["transform"] = false + at["clip-path"] = false - function handlers.style(c,top) - local s = xmltext(c) - lpegmatch(p_grab,s,1,styles) - for k, v in next, styles do - -- todo: lpeg - local t = { } - for k, v in gmatch(v,"%s*([^:]+):%s*([^;]+);") do - if k == "font" then - v = xml.css.fontspecification(v) + 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 - t[k] = v + r = r + 1 ; result[r] = f_done(cpath[1]) end - styles[k] = t end - bodyfont = tex.getdimen("bodyfontsize") / 65536 - end - -- this will never really work out - - function handlers.text(c,top) - local a = c.at - local x = asnumber_vx(a.x) - local y = asnumber_vy(a.y) - local text = xmltext(c) - local size = false - local fill = a.fill - -- escape text or keep it at the tex end - local class = a.class - if class then - local style = styles[class] - if style and next(style) then - local font = style.font - local s_family = font and font.family - local s_style = font and font.style - local s_weight = font and font.weight - local s_size = font and font.size - if s_size then - local f = factors[s_size[2]] - if f then - size = f * s_size[1] + -- 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{%N}{%N}{%N}{"] + local f_placed = formatters["\\svgplaced{%N}{%N}{}{"] + local f_poschar = formatters["\\svgposchar{%N}{%N}{%s}"] + local f_char = formatters["\\svgchar{%s}"] + + local f_scaled = formatters["\\svgscaled{%N}{%s}{%s}{%s}"] + local f_normal = formatters["\\svgnormal{%s}{%s}{%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 f_text_normal_svg = formatters['(textext.drt("%s") shifted (%N,%N))'] + local f_text_simple_svg = formatters['textext.drt("%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 + local r, g, b = rgbcomponents(v_fill) + if r and g and b then + t[#t+1] = f_colored(r,g,b) + else + ecolored = false end end - -- todo: family: serif | sans-serif | monospace : use mapping - if s_family then - if s_family == "serif" then - text = "\\rm " .. text - elseif s_family == "sans-serif" then - text = "\\ss " .. text + -- + 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 - text = "\\tt " .. text + if i == 1 then + di = gsub(di,"^%s+","") + end + if i == nt then + di = gsub(di,"%s+$","") + end + local chars = utfsplit(di) + if 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 s_style or s_weight then - text = formatters['\\style[%s%s]{%s}'](s_weight or "",s_style or "",text) + -- + if ecolored then + t[#t+1] = "}" + end + -- + t[#t+1] = "}" + -- + if eplaced then + t[#t+1] = "}" + end + if elayered then + t[#t+1] = "}" end - fill = style.fill or fill + -- + return t end - end - if size then - -- bodyfontsize - size = size/bodyfont - text = f_text_scaled(text,size,x,y) - else - text = f_text_normal(text,x,y) - end - if fill and fill ~= "none" then - text = f_draw("",text,"",fillproperties(fill,a)) - else - text = f_draw("",text,"","","","","") - end - if trace then - report("text: x %.3N, y %.3N, text %s",x,y,text) - end - r = r + 1 ; result[r] = text - end - function handlers.svg(c,top,x,y,w,h,noclip,notransform,normalize) - local a = setmetatableindex(c.at,top) - local v = a.viewBox - local t = a.transform - local wrapupoffset - local wrapupviewport - local btransform - local etransform - local bhacked - local ehacked - local wd = w - -- local ex, em - local xpct, ypct, rpct - if notransform then --- t = nil - end - if trace then - report("view: %s, xpct %.3N, ypct %.3N","before",percentage_x,percentage_y) - end - if v then - x, y, w, h = viewbox(v) - if trace then - report("viewbox: x %.3N, y %.3N, width %.3N, height %.3N",x,y,w,h) + 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 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(result) + else + result = f_text_normal_svg(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 - 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 + + function handlers.svg(c,x,y,w,h,noclip,notransform,normalize,usetextindex) + 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 %.3N, ypct %.3N","inside",percentage_x,percentage_y) + report("view: %s, xpct %N, ypct %N","before",percentage_x,percentage_y) end - wrapupviewport = viewport(x,y,w,h,noclip) - end - -- todo: combine transform and offset here - if t then - btransform, etransform = transform(t,true) - end - -- 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_hacked_start - ehacked = f_hacked_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 - wrapupoffset = offset(a) - process(c,"/*",top or defaults) - if wrapupoffset then - wrapupoffset() - 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() + + 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 + textindex = usetextindex and 0 or false + + 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 - if trace then - report("view: %s, xpct %.3N, ypct %.3N","after",percentage_x,percentage_y) - end + end - process = function(x,p,top) + process = function(x,p) for c in xmlcollected(x,p) do - local h = handlers[c.tg] + local tg = c.tg + local h = handlers[c.tg] if h then - h(c,top) + 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 @@ -1660,21 +2735,33 @@ do if svg then local c = xmlfirst(svg,pattern or "/svg") if c then - root, result, r, definitions, styles, bodyfont = svg, { }, 0, { }, { }, 12 - -- print("default",x,y,w,h) + root = svg + result = { } + r = 0 + definitions = { } + tagstyles = { } + classstyles = { } + for s in xmlcollected(c,"/style") do + handlestyle(c) + end + handlechains(c) + xmlinheritattributes(c) -- put this in handlechains handlers.svg ( c, - nil, specification.x, specification.y, specification.width, specification.height, specification.noclip, notransform, - normalize + normalize, + specification.remap ) + if trace_result then + report("result graphic:\n %\n t",result) + end mps = concat(result," ") - root, result, r, definitions, styles, bodyfont = false, false, false, false, false, false + root, result, r, definitions, styles = false, false, false, false, false else report("missing svg root element") end @@ -1686,14 +2773,49 @@ do end --- These helpers might move to their own module .. some day ... +-- 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. + +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 = dd.comment - local offset = dd.pageoffset - for i=1,#dd do + 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, @@ -1701,6 +2823,10 @@ function metapost.showsvgpage(data) }, 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)) @@ -1713,10 +2839,31 @@ function metapost.showsvgpage(data) 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 - local pdf = metapost.simple("metafun",mps,true) -- todo: special instance, only basics needed + -- todo: special instance, only basics needed + local pdf = metapost.simple("metafun",mps,true,false,"svg") if pdf then return pdf else @@ -1867,7 +3014,7 @@ do local svgshapes = svgfile and svgfile.svgshapes if svgshapes then if type(unicode) == "string" then - unicode = utf.byte(unicode) + unicode = utfbyte(unicode) end local chardata = tfmdata.characters[unicode] local index = chardata and chardata.index -- cgit v1.2.3