summaryrefslogtreecommitdiff
path: root/tex/context/base/mkiv/mlib-svg.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/mkiv/mlib-svg.lua')
-rw-r--r--tex/context/base/mkiv/mlib-svg.lua3617
1 files changed, 2382 insertions, 1235 deletions
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<decimal>
--- currentColor : when i run into an example
--- some more fonts
---
--- Todo: some day
---
--- optimize
--- instances
--- withpen -> pickup
+-- 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: when i am motivated
+-- 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).
--
--- 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 <g> 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<decimal>
+-- 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
+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
-metapost = metapost or { }
-local metapost = metapost
-local context = context
+local bpfactor = number.dimenfactors.bp
-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 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 a2c do
-local function a2c(x1, y1, rx, ry, angle, large, sweep, x2, y2, f1, f2, cx, cy)
+ local d120 = (pi * 120) / 180
+ local pi2 = 2 * pi
- local recursive = f1
+ a2c = function(x1, y1, rx, ry, angle, large, sweep, x2, y2, f1, f2, cx, cy)
- if rx == 0 or ry == 0 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
- if x1 == x2 and y1 == y2 then
- return { x1, y1, x2, y2, x2, y2 }
- end
+ local recursive = f1
+ local rad = pi / 180 * angle
+ local res = nil
+ local cosrad = cos(-rad) -- local cosrad = cosd(angle)
+ local sinrad = sin(-rad) -- local sinrad = sind(angle)
- local rad = pi / 180 * angle
- local res = nil
+ if not recursive then
- local cosrad = cos(-rad) -- local cosrad = cosd(angle)
- local sinrad = sin(-rad) -- local sinrad = sind(angle)
+ x1, y1 = x1 * cosrad - y1 * sinrad, x1 * sinrad + y1 * cosrad
+ x2, y2 = x2 * cosrad - y2 * sinrad, x2 * sinrad + y2 * cosrad
- if not recursive then
+ 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)
- x1, y1 = x1 * cosrad - y1 * sinrad, x1 * sinrad + y1 * cosrad
- x2, y2 = x2 * cosrad - y2 * sinrad, x2 * sinrad + y2 * cosrad
+ if h > 1 then
+ h = sqrt(h)
+ rx = h * rx
+ ry = h * ry
+ end
- 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 rx2 = rx * rx
+ local ry2 = ry * ry
+ local ry2xx = ry2 * xx
+ local rx2yy = rx2 * yy
+ local total = rx2yy + ry2xx -- otherwise overflow
- if h > 1 then
- h = sqrt(h)
- rx = h * rx
- ry = h * ry
- end
+ local k = total == 0 and 0 or sqrt(abs((rx2 * ry2 - rx2yy - ry2xx) / total))
- local rx2 = rx * rx
- local ry2 = ry * ry
- local ry2xx = ry2 * xx
- local rx2yy = rx2 * yy
- local total = rx2yy + ry2xx -- otherwise overflow
-
- local k = total == 0 and 0 or sqrt(abs((rx2 * ry2 - rx2yy - ry2xx) / total))
+ if large == sweep then
+ k = -k
+ end
- 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 r = { x1 - hx * s1, y1 + hy * c1, x2 + hx * s2, y2 - hy * c2, x2, y2, unpack(res or { }) }
- 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
+ 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]
-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
+ end
+
+ 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,
+ }
- -- todo: check use ... and make definitions[id] self resolving
+ local function handlechains(c)
+ if tags[c.tg] then
+ local at = c.at
+ local dt = c.dt
+ if at and dt then
+ -- at["inkscape:connector-curvature"] = nil -- cleare entry and might prevent table growth
+ local estyle = rawget(at,"style")
+ if estyle and estyle ~= "" then
+ for k, v in gmatch(estyle,"%s*([^:]+):%s*([^;]+);?") do
+ at[k] = v
+ end
+ end
+ local eclass = rawget(at,"class")
+ if eclass and eclass ~= "" then
+ for c in gmatch(eclass,"[^ ]+") do
+ local s = classstyles[c]
+ if s then
+ for k, v in next, s do
+ at[k] = v
+ end
+ end
+ end
+ end
+ local tstyle = tagstyles[tag]
+ if tstyle then
+ for k, v in next, tstyle do
+ at[k] = v
+ end
+ end
+ if trace_path and pathtracer then
+ for k, v in next, pathtracer do
+ at[k] = v
+ end
+ end
+ for i=1,#dt do
+ local dti = dt[i]
+ if type(dti) == "table" then
+ handlechains(dti)
+ end
+ end
+ end
+ end
+ end
+
+ local handlestyle do
+
+ -- It can also be CDATA but that is probably dealt with because we only
+ -- check for style entries and ignore the rest. But maybe we also need
+ -- to check a style at the outer level?
+
+ local p_key = C((R("az","AZ","09","__","--")^1))
+ local p_spec = P("{") * C((1-P("}"))^1) * P("}")
+ local p_valid = Carg(1) * P(".") * p_key + Carg(2) * p_key
+ local p_grab = ((p_valid * p_space^0 * p_spec / rawset) + p_space^1 + P(1))^1
+
+ local fontspecification = css.fontspecification
+
+ handlestyle = function(c)
+ local s = xmltext(c)
+ lpegmatch(p_grab,s,1,classstyles,tagstyles)
+ for k, v in next, classstyles do
+ local t = { }
+ for k, v in gmatch(v,"%s*([^:]+):%s*([^;]+);?") do
+ if k == "font" then
+ local s = fontspecification(v)
+ for k, v in next, s do
+ t["font-"..k] = v
+ end
+ else
+ t[k] = v
+ end
+ end
+ classstyles[k] = t
+ end
+ for k, v in next, tagstyles do
+ local t = { }
+ for k, v in gmatch(v,"%s*([^:]+):%s*([^;]+);?") do
+ if k == "font" then
+ local s = fontspecification(v)
+ for k, v in next, s do
+ t["font-"..k] = v
+ end
+ else
+ t[k] = v
+ end
+ end
+ tagstyles[k] = t
+ end
+ end
+
+ function handlers.style()
+ -- ignore
+ end
+
+ end
+
+ -- We can have root in definitions and then do a metatable lookup but use
+ -- is not used that often I guess.
local function locate(id)
+ local res = definitions[id]
+ if res then
+ return res
+ end
local ref = gsub(id,"^url%(#(.-)%)$","%1")
local ref = gsub(ref,"^#","")
+ -- we can make a fast id lookup
local res = xmlfirst(root,"**[@id='"..ref.."']")
if res then
definitions[id] = res
- 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 <g> if needed)
+
+ function handlers.defs(c)
for c in xmlcollected(c,"/*") do
- local a = c.at
- local id = a.id
- -- setmetatableindex(a,top)
- if id then
- definitions["#" .. id ] = c
- definitions["url(#" .. id .. ")"] = c
+ 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.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
+ function handlers.symbol(c)
+ if uselevel == 0 then
+ local id = rawget(c.at,"id")
+ if id then
+ definitions["#" .. id ] = c
+ definitions["url(#" .. id .. ")"] = c
end
+ else
+ handlers.g(c)
end
end
- local linecaps = { butt = "butt", square = "squared", round = "rounded" }
- local linejoins = { miter = "mitered", bevel = "beveled", round = "rounded" }
+ local uselevel = 0
- local function stoplineproperties()
- r = r + 1 result[r] = s_endgroup
- end
+ 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 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)
+ if boffset then
+ r = r + 1 result[r] = boffset
end
- if join then
- r = r + 1 result[r] = f_linejoin(join)
+
+ -- local clippath = at.clippath
+
+ if btransform then
+ r = r + 1 result[r] = btransform
+ end
+
+ local _transform = transform
+ local _clippath = clippath
+ at["transform"] = false
+ -- at["clip-path"] = false
+
+ process(res,"/*")
+
+ at["transform"] = _transform
+ -- at["clip-path"] = _clippath
+
+ if etransform then
+ r = r + 1 ; result[r] = etransform
end
- if 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
- 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_ellipse(2*rx,2*ry,cx,cy),true,at)
+ end
+
+ function handlers.circle(c)
+ local at = c.at
+ local cx = rawget(at,"cx")
+ local cy = rawget(at,"cy")
+ local r = rawget(at,"r")
+
+ cx = cx and asnumber_vx(cx) or 0
+ cy = cy and asnumber_vy(cy) or 0
+ r = r and asnumber_r (r) or 0
+
+ flush(f_circle(2*r,cx,cy),true,at)
+ end
+
+ local f_lineto_z = formatters['(%N,%N)']
+ local f_lineto_n = formatters['--(%N,%N)']
+
+ local p_pair = p_optseparator * p_number_vx * p_optseparator * p_number_vy
local p_open = Cc("(")
local p_close = Carg(1) * P(true) / function(s) return s end
- local p_polyline = Cs(p_open * p_pair_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)
+
+ if cpath then
+ r = r + 1 ; result[r] = s_clip_start
+ end
+
+ if btransform then
+ r= r + 1 result[r] = btransform
+ end
+
+ local _transform = transform
+ local _clippath = clippath
+ at["transform"] = false
+ at["clip-path"] = false
+
+ process(c,"/*")
- local p_class = P(".") * C((R("az","AZ","09","__","--")^1))
- local p_spec = P("{") * C((1-P("}"))^1) * P("}")
+ at["transform"] = _transform
+ at["clip-path"] = _clippath
- 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
+ if etransform then
+ r = r + 1 ; result[r] = etransform
+ end
- 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)
+ 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
- fill = style.fill or fill
+ --
+ t[#t+1] = "}"
+ --
+ if eplaced then
+ t[#t+1] = "}"
+ end
+ if elayered then
+ t[#t+1] = "}"
+ end
+ --
+ 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