├── examples
├── favicon.ico
├── app_usahigh.lua
├── app_path.lua
├── app_circle.lua
├── app_lines.lua
└── app_arcs02.lua
├── testy
├── images
│ ├── charicature.jpg
│ ├── charicature_small.jpg
│ └── textelem.svg
├── experimental.js
├── pentagram.lua
├── test_table.lua
├── test_radialgradient.lua
├── test_lineargradient.lua
├── test_builder.lua
├── netview
├── translatesvg.lua
├── gensvg
├── test_svgattributes.lua
├── ezsvg_example2.lua
├── test_nameplate.lua
├── test_inherit.lua
├── ezsvg_example1.lua
├── test_elemiterator.lua
├── ezsvg_features.lua
├── test_patterns.lua
└── test_literal.lua
├── remotesvg
├── Bezier.lua
├── ctypes.lua
├── SVGStream.lua
├── stream.lua
├── jskeycodes.lua
├── filestream.lua
├── parsesvg.lua
├── maths.lua
├── matrix2D.lua
├── sharesvg.htm
├── viewsvg_htm.lua
├── SVGXmlParser.lua
├── SVGInteractor.lua
├── colors.lua
├── memorystream.lua
├── SVGElements.lua
├── SVGAttributes.lua
└── SVGParser.lua
├── LICENSE
└── README.md
/examples/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wiladams/remotesvg/HEAD/examples/favicon.ico
--------------------------------------------------------------------------------
/testy/images/charicature.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wiladams/remotesvg/HEAD/testy/images/charicature.jpg
--------------------------------------------------------------------------------
/testy/images/charicature_small.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Wiladams/remotesvg/HEAD/testy/images/charicature_small.jpg
--------------------------------------------------------------------------------
/testy/images/textelem.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/testy/experimental.js:
--------------------------------------------------------------------------------
1 | // For loading an image
2 | // without flicker
3 | var downloadingImage = new Image();
4 | downloadingImage.onload = function(){
5 | document.images['myScreen'].src = this.src;
6 | }
7 |
8 | downloadingImage.src = asrc;
9 |
--------------------------------------------------------------------------------
/remotesvg/Bezier.lua:
--------------------------------------------------------------------------------
1 | -- Bezier.lua
2 | -- Evaluate a single dimension along a bezier curve
3 | -- call multiple times with components of each dimension
4 | local function eval(t, p0, p1, p2, p3)
5 | local it = 1.0-t;
6 | return it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3;
7 | end
8 |
9 | return {
10 | eval = eval;
11 | }
--------------------------------------------------------------------------------
/testy/pentagram.lua:
--------------------------------------------------------------------------------
1 | local doc = svg {
2 | width="304",
3 | height="290",
4 | --[[
5 | style {
6 | fill = "#FB2";
7 | stroke = "#BBB";
8 | stroke_width = 15;
9 | stroke_linejoin = "round"
10 | },
11 | --]]
12 | path {
13 | d="M2,111 h300 l-242.7,176.3 92.7,-285.3 92.7,285.3 z",
14 |
15 | style="fill:#FB2;stroke:#BBB;stroke-width:15;stroke-linejoin:round"
16 | },
17 | }
18 |
19 | return doc
20 |
--------------------------------------------------------------------------------
/remotesvg/ctypes.lua:
--------------------------------------------------------------------------------
1 | local function isspace(c)
2 | return c == string.byte(' ') or
3 | c == string.byte('\t') or
4 | c == string.byte('\n') or
5 | c == string.byte('\v') or
6 | c == string.byte('\f') or
7 | c == string.byte('\r')
8 | end
9 |
10 |
11 | local function isdigit(c)
12 | return c >= string.byte('0') and
13 | c <= string.byte('9')
14 | end
15 |
16 | local function isnum(c)
17 | return isdigit(c) or
18 | c == string.byte('+') or
19 | c == string.byte('-') or
20 | c == string.byte('e') or
21 | c == string.byte('E')
22 | end
23 |
24 | return {
25 | isspace = isspace;
26 | isdigit = isdigit;
27 | isnum = isnum;
28 | }
--------------------------------------------------------------------------------
/testy/test_table.lua:
--------------------------------------------------------------------------------
1 | local tbl = {
2 | name1 = "value1";
3 | name2 = "value2";
4 |
5 | [[
6 | some text here
7 | ]];
8 |
9 | name3 = "value3";
10 |
11 | [[
12 | followed by more text
13 | ]];
14 |
15 | function()
16 | print("this is a test")
17 | end,
18 |
19 |
20 | name4 = "value4";
21 | }
22 |
23 | -- This one will print all the things in the table
24 | -- with the literals having a key type that is numeric
25 | for name, value in pairs(tbl) do
26 | print(name, value);
27 | end
28 |
29 | -- This one will only print the literals, starting
30 | -- from an index of '1'
31 | for idx, value in ipairs(tbl) do
32 | print(idx, value);
33 | end
34 |
35 |
--------------------------------------------------------------------------------
/examples/app_usahigh.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env luajit
2 |
3 | package.path = "../?.lua;"..package.path;
4 |
5 | local SVGInteractor = require("remotesvg.SVGInteractor")
6 | require("remotesvg.SVGElements")()
7 |
8 |
9 | local ImageStream = size()
10 |
11 | --[[
12 | -- BUGBUG
13 | -- need to set focus on element
14 | function keyDown(activity)
15 | print(activity.action, activity.json);
16 | end
17 | --]]
18 |
19 | function mouseDown(activity)
20 | print("activity: ", activity.action, activity.id, activity.which, activity.x, activity.y)
21 | end
22 |
23 | function mouseMove(activity)
24 | print("activity: ", activity.action, activity.x, activity.y)
25 | end
26 |
27 | local doc = require("usaHigh_svg")
28 |
29 | doc:write(ImageStream);
30 |
31 | run()
32 |
--------------------------------------------------------------------------------
/testy/test_radialgradient.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env luajit
2 |
3 | package.path = "../?.lua;"..package.path;
4 |
5 | require("remotesvg.SVGElements")()
6 |
7 | local doc = svg {
8 | ['xmlns:xlink']="http://www.w3.org/1999/xlink",
9 | width="120",
10 | height="120",
11 | viewBox="0 0 120 120",
12 |
13 | defs {
14 | radialGradient {id="MyGradient",
15 | stop {offset="10%", ['stop-color']="gold"},
16 | stop {offset="95%", ['stop-color']="green"},
17 | },
18 | },
19 |
20 | circle {fill="url(#MyGradient)",
21 | cx="60", cy="60", r="50"
22 | },
23 | }
24 |
25 | local FileStream = require("remotesvg.filestream")
26 | local SVGStream = require("remotesvg.SVGStream")
27 | local ImageStream = SVGStream(FileStream.open("test_radialgradient.svg"))
28 |
29 | doc:write(ImageStream);
30 |
--------------------------------------------------------------------------------
/testy/test_lineargradient.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env luajit
2 |
3 | package.path = "../?.lua;"..package.path;
4 |
5 | require("remotesvg.SVGElements")()
6 | local FileStream = require("remotesvg.filestream")
7 | local SVGStream = require("remotesvg.SVGStream")
8 |
9 |
10 | local ImageStream = SVGStream(FileStream.open("test_lineargradient.svg"))
11 |
12 | local doc = svg {
13 | width = "120",
14 | height = "120",
15 | viewBox = "0 0 120 120",
16 | ['xmlns:xlink'] ="http://www.w3.org/1999/xlink",
17 |
18 | defs{
19 | linearGradient {id="MyGradient",
20 | stop {offset="5%", ['stop-color']="green"};
21 | stop {offset="95%", ['stop-color']="gold"};
22 | }
23 | },
24 |
25 | rect {
26 | fill="url(#MyGradient)",
27 | x=10, y=10, width=100, height=100,
28 | },
29 | }
30 |
31 | doc:write(ImageStream);
32 |
--------------------------------------------------------------------------------
/examples/app_path.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env luajit
2 |
3 | package.path = "../?.lua;"..package.path;
4 |
5 | local SVGInteractor = require("remotesvg.SVGInteractor")
6 | require("remotesvg.SVGElements")()
7 |
8 | local ImageStream = size()
9 |
10 |
11 | function mouseMove(activity)
12 | print("mouseMove: ", activity.x, activity.y)
13 | end
14 |
15 | --[[
16 |
17 | --]]
18 | local function draw(ImageStream)
19 | ImageStream:reset();
20 |
21 | local p1 = path {
22 | fill="red",
23 | stroke="blue",
24 | ["stroke-width"]=3
25 | };
26 |
27 | p1:moveTo(100, 100);
28 | p1:lineTo(300, 100);
29 | p1:lineTo(200, 300);
30 | p1:close();
31 |
32 | local doc = svg {
33 | width="4cm",
34 | height="4cm",
35 | viewBox="0 0 400 400",
36 | --onload="alert('svg loaded');";
37 |
38 | rect {
39 | x="1", y="1",
40 | width="398", height="398",
41 | fill="none", stroke="blue"};
42 |
43 | p1;
44 | }
45 |
46 | doc:write(ImageStream);
47 | end
48 |
49 | draw(ImageStream);
50 | run();
51 |
52 |
--------------------------------------------------------------------------------
/testy/test_builder.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env luajit
2 |
3 | package.path = "../?.lua;"..package.path;
4 |
5 | local SVGInteractor = require("remotesvg.SVGInteractor")
6 | require("remotesvg.SVGElements")()
7 |
8 |
9 | local ImageStream = size()
10 |
11 |
12 | local function draw(ImageStream)
13 | ImageStream:reset();
14 |
15 | -- Show outline of canvas using 'rect' element
16 |
17 | -- demonstrate document format API
18 | local doc = svg {
19 | width = "12cm", height= "4cm",
20 | viewBox="0 0 1200 400",
21 |
22 |
23 | circle {
24 | fill="red",
25 | stroke="blue",
26 | ["stroke-width"]=10,
27 | cx =600,
28 | cy = 200,
29 | r = 100
30 | },
31 |
32 | }
33 |
34 | -- Demonstrate programmatic builder API
35 | doc:append('rect')
36 | :attr('x', 1)
37 | :attr('y', 1)
38 | :attr('width', 1198)
39 | :attr('height', 398)
40 | :attr('fill', "none")
41 | :attr('stroke', "blue")
42 | :attr('stroke-width', 2);
43 |
44 | doc:write(ImageStream);
45 | end
46 |
47 | draw(ImageStream);
48 |
49 | run()
50 |
--------------------------------------------------------------------------------
/examples/app_circle.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env luajit
2 |
3 | package.path = "../?.lua;"..package.path;
4 |
5 | local SVGInteractor = require("remotesvg.SVGInteractor")
6 | require("remotesvg.SVGElements")()
7 |
8 |
9 | local keycodes = require("remotesvg.jskeycodes")
10 |
11 |
12 | local ImageStream = size()
13 |
14 |
15 | function mouseMove(activity)
16 | print("mouseMove: ", activity.x, activity.y)
17 | end
18 |
19 | local function drawCircle(filename)
20 | ImageStream:reset();
21 |
22 | local doc = svg {
23 | width = "12cm", height= "4cm",
24 | viewBox="0 0 1200 400",
25 |
26 | -- Show outline of canvas using 'rect' element
27 | rect {x = 1, y = 1, width = 1198, height = 398,
28 | fill = "none",
29 | stroke = "blue",
30 | ["stroke-width"] = 2
31 | },
32 |
33 | circle {
34 | fill="red",
35 | stroke="blue",
36 | ["stroke-width"]=10,
37 | cx =600,
38 | cy = 200,
39 | r = 100
40 | },
41 |
42 | }
43 |
44 | doc:write(ImageStream);
45 | end
46 |
47 |
48 | function frame()
49 | drawCircle();
50 | end
51 |
52 | run()
53 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 William Adams
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/testy/netview:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env luajit
2 |
3 | --[[
4 | This little command will read in a .lua file which contains nothing
5 | more than a svg document table, and will generate the .svg file for it.
6 |
7 | The input is the name of the 'document' file, without the lua extension.
8 | So, for example, if you have a file 'mysvg.lua', the command would be:
9 |
10 | $ ./gensvg mysvg
11 |
12 | Which will generate the appropriate svg to stdout. You can then
13 | redirect this to whatever file you actually want it to be stored
14 | in, or redirect to another tool.
15 |
16 |
17 | --]]
18 | package.path = "../?.lua;"..package.path;
19 | require("remotesvg.SVGElements")()
20 |
21 |
22 | -- read the source file
23 | -- pass in the name of your SVG document.lua file, without
24 | -- the '.lua' extension.
25 | local filename = arg[1]
26 | if not filename then return end
27 |
28 | local doc = require(filename)
29 |
30 | -- generate svg and view it as a web page.
31 | -- access as: http://localhost:8080/screen
32 | -- Ctl-C to end
33 |
34 | local SVGInteractor = require("remotesvg.SVGInteractor")
35 | local ImageStream = size();
36 | doc:write(ImageStream)
37 | run();
--------------------------------------------------------------------------------
/testy/translatesvg.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env luajit
2 |
3 | --[[
4 | This little command will read in a .lua file which contains nothing
5 | more than a svg document table, and will generate the .svg file for it.
6 |
7 | The input is the name of the 'document' file, without the lua extension.
8 | So, for example, if you have a file 'mysvg.lua', the command would be:
9 |
10 | $ ./gensvg mysvg
11 |
12 | Which will generate the appropriate svg to stdout. You can then
13 | redirect this to whatever file you actually want it to be stored
14 | in, or redirect to another tool.
15 |
16 |
17 | --]]
18 | package.path = "../?.lua;"..package.path;
19 | local parser = require("remotesvg.parsesvg")
20 |
21 | -- read the source file
22 | -- pass in the name of your SVG document.lua file, without
23 | -- the '.lua' extension.
24 | local filename = arg[1]
25 | if not filename then return end
26 |
27 | local doc = parser:parseFile(filename);
28 | doc:parseAttributes();
29 |
30 | -- generate the svg and write to stdout
31 | local FileStream = require("remotesvg.filestream")
32 | local SVGStream = require("remotesvg.SVGStream")
33 | local ImageStream = SVGStream(FileStream.new(io.stdout))
34 |
35 | doc:write(ImageStream)
36 |
--------------------------------------------------------------------------------
/testy/gensvg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env luajit
2 |
3 | --[[
4 | This little command will read in a .lua file which contains nothing
5 | more than a svg document table, and will generate the .svg file for it.
6 |
7 | The input is the name of the 'document' file, without the lua extension.
8 | So, for example, if you have a file 'mysvg.lua', the command would be:
9 |
10 | $ ./gensvg mysvg
11 |
12 | Which will generate the appropriate svg to stdout. You can then
13 | redirect this to whatever file you actually want it to be stored
14 | in, or redirect to another tool.
15 |
16 |
17 | --]]
18 | package.path = "../?.lua;"..package.path;
19 | require("remotesvg.SVGElements")()
20 |
21 |
22 | -- read the source file
23 | -- pass in the name of your SVG document.lua file, without
24 | -- the '.lua' extension.
25 | local filename = arg[1]
26 | if not filename then return end
27 |
28 | local doc = require(filename)
29 |
30 |
31 | -- generate the svg and write to stdout
32 | local FileStream = require("remotesvg.filestream")
33 | local SVGStream = require("remotesvg.SVGStream")
34 | local ImageStream = SVGStream(FileStream.new(io.stdout))
35 |
36 | doc:write(ImageStream)
37 |
38 | -- If you want to run it as a web page, use the following
39 | --local SVGInteractor = require("remotesvg.SVGInteractor")
40 | --local ImageStream = size();
41 | --doc:write(ImageStream)
42 | --run();
--------------------------------------------------------------------------------
/remotesvg/SVGStream.lua:
--------------------------------------------------------------------------------
1 | --SVGStream.lua
2 |
3 | local SVGStream = {}
4 | setmetatable(SVGStream, {
5 | __call = function(self, ...)
6 | return self:new(...)
7 | end,
8 | })
9 | local SVGStream_mt = {
10 | __index = SVGStream;
11 | }
12 |
13 | function SVGStream.init(self, baseStream)
14 | local obj = {
15 | BaseStream = baseStream;
16 | TopElement = {};
17 | Elements = {};
18 | }
19 | setmetatable(obj, SVGStream_mt);
20 |
21 | return obj;
22 | end
23 |
24 | function SVGStream.new(self, baseStream)
25 | return self:init(baseStream);
26 | end
27 |
28 |
29 | function SVGStream.reset(self)
30 | --strm:write('\n');
31 |
32 | return self.BaseStream:reset();
33 | end
34 |
35 |
36 | function SVGStream.openElement(self, elName)
37 | self.CurrentOpenTag = elName;
38 | self.BaseStream:writeString("<"..elName)
39 | end
40 |
41 | function SVGStream.closeTag(self)
42 | self.BaseStream:writeString(">\n")
43 | self.CurrentOpenTag = nil;
44 | end
45 |
46 |
47 | function SVGStream.writeAttribute(self, name, value)
48 | self.BaseStream:writeString(" "..name.." = '"..value.."'");
49 | end
50 |
51 | function SVGStream.closeElement(self, elName)
52 | if elName then
53 | self.BaseStream:writeString(""..elName..">\n");
54 | else
55 | self.BaseStream:writeString(" />\n");
56 | end
57 | end
58 |
59 | function SVGStream.write(self, str)
60 | self.BaseStream:writeString(str);
61 | end
62 |
63 | return SVGStream
64 |
--------------------------------------------------------------------------------
/examples/app_lines.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env luajit
2 |
3 | package.path = "../?.lua;"..package.path;
4 |
5 | local SVGInteractor = require("remotesvg.SVGInteractor")
6 | require("remotesvg.SVGElements")()
7 |
8 |
9 | local ImageStream = size()
10 |
11 |
12 | function mouseDown(activity)
13 | print("mouseDown: ", activity.which)
14 | end
15 |
16 | function mouseMove(activity)
17 | print("mouseMove: ", activity.x, activity.y)
18 | end
19 |
20 | local function draw(strm)
21 | strm:reset();
22 |
23 | local doc = svg {
24 | width = "12cm",
25 | height= "4cm",
26 | viewBox="0 0 1200 400",
27 | }
28 |
29 |
30 | doc:append('rect')
31 | :attr("x", 1)
32 | :attr("y", 2)
33 | :attr("width", 1198)
34 | :attr("height", 398)
35 | :attr("fill", "none")
36 | :attr("stroke", "blue")
37 | :attr("stroke-width", 2);
38 |
39 | local l1 = line({x1=100, y1=300, x2=300, y2=100, stroke = "green", ["stroke-width"]=5});
40 | local l2 = line({x1=300, y1=300, x2=500, y2=100, stroke = "green", ["stroke-width"]=20});
41 | local l3 = line({x1=500, y1=300, x2=700, y2=100, stroke = "green", ["stroke-width"]=25});
42 | local l4 = line({x1=700, y1=300, x2=900, y2=100, stroke = "green", ["stroke-width"]=20});
43 | local l5 = line({x1=900, y1=300, x2=1100, y2=100, stroke = "green", ["stroke-width"]=25});
44 |
45 |
46 | --doc:append(r1);
47 | doc:append(l1);
48 | doc:append(l2);
49 | doc:append(l3);
50 | doc:append(l4);
51 | doc:append(l5);
52 |
53 |
54 | doc:write(strm);
55 | end
56 |
57 |
58 | draw(ImageStream);
59 |
60 | run()
61 |
--------------------------------------------------------------------------------
/testy/test_svgattributes.lua:
--------------------------------------------------------------------------------
1 | package.path = "../?.lua;"..package.path
2 |
3 | local sattr = require("remotesvg.SVGAttributes")
4 | local parse = sattr.parseAttribute;
5 | local matrix2D = require("remotesvg.matrix2D")
6 |
7 | local svg = require("remotesvg.SVGElements")
8 |
9 | local function test_rect()
10 | local r1 = svg.rect {
11 | x = "10",
12 | y = 10,
13 | width = "100",
14 | height = "200"
15 | }
16 |
17 | r1:parseAttributes();
18 |
19 | for k,v in pairs(r1) do
20 | print(k, v, type(v))
21 | end
22 |
23 | end
24 |
25 | function test_color()
26 | local function printcolor(value)
27 | local name, num = parse("color", value)
28 | print("color - ", value, num)
29 | end
30 |
31 | printcolor("red")
32 | printcolor("#888888")
33 | printcolor("#CCC")
34 | printcolor("rgb(255, 0, 0)")
35 | printcolor("rgb(50%, 25%, 100%)")
36 | end
37 |
38 | function test_coord()
39 | local function printcoord(str)
40 | local name, v = parse('cx', str)
41 | print(name, str, v.value, v.units, v)
42 | end
43 |
44 | printcoord('100%')
45 | printcoord('75')
46 | printcoord('12cm')
47 | printcoord('-306in')
48 |
49 | end
50 |
51 | function test_style()
52 | local style = 'stop-color:#191b21;stop-opacity:0.34437087;'
53 | local name, value = parse("style", style)
54 | print("STYLE: ", name, value);
55 | end
56 |
57 | function test_viewbox()
58 | local name, vbox = parse("viewBox", "0 50 10 100")
59 | print(vbox)
60 |
61 | local name, vbox = parse("viewBox", "20, 30, 100, 200")
62 | print(vbox)
63 |
64 | end
65 |
66 | function test_matrix()
67 | local m = matrix2D.translation(20,30)
68 | print(m)
69 | end
70 |
71 | --test_color();
72 | --test_coord()
73 | --test_matrix();
74 | --test_rect();
75 | test_style();
76 | --test_viewbox();
77 |
--------------------------------------------------------------------------------
/testy/ezsvg_example2.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env luajit
2 |
3 | package.path = "../?.lua;"..package.path;
4 |
5 | -- A translation of this
6 | -- https://github.com/cappelnord/EzSVG/blob/master/example2.lua
7 |
8 | local abs = math.abs
9 | local cos = math.cos
10 | local sin = math.sin
11 |
12 |
13 | require("remotesvg.SVGElements")()
14 |
15 | local dim = 500
16 |
17 | local doc = svg {
18 | width = dim, height = dim,
19 |
20 | rect {
21 | x = 0, y = 0,
22 | width = dim, height = dim,
23 | stroke = "none",
24 | fill = "rgbcolor(40,40,40)"
25 | },
26 |
27 | stroke_linecap= "round",
28 | fill= "white",
29 | stroke= "white",
30 |
31 | }
32 |
33 |
34 | local group = g {}
35 | local circles = g{}
36 |
37 | doc:append(group)
38 | doc:append(circles)
39 |
40 |
41 | local num = 800
42 | for i=0,num do
43 | local norm = i / num
44 |
45 | local length = dim/2.1 * (0.5 + abs(sin(norm * 80)) * 0.5)
46 | local lw = abs(sin(norm*200)) * 1
47 |
48 | local x = dim/2 + sin(norm * 50) * length
49 | local y = dim/2 + cos(norm * 50) * length
50 |
51 | local line = line {
52 | x1 = dim/2,
53 | y1 = dim/2,
54 | x2 = x, y2=y,
55 |
56 | stroke_width= lw + 0.3
57 | }
58 |
59 | group:append(line)
60 |
61 | local circ = circle {
62 | cx = x, cy = y,
63 | r = lw * 15,
64 |
65 | fill= "#000000",
66 | stroke_width = 1
67 | }
68 |
69 | circles:append(circ)
70 |
71 | end
72 |
73 |
74 | doc:append(circle {
75 | cx=dim/2, cy=dim/2, r=60,
76 | fill= "black",
77 | stroke= "white",
78 | stroke_width = 5
79 | })
80 |
81 | local FileStream = require("remotesvg.filestream")
82 | local SVGStream = require("remotesvg.SVGStream")
83 | local ImageStream = SVGStream(FileStream.open("ezsvg_example2.svg"))
84 |
85 | doc:write(ImageStream)
86 |
--------------------------------------------------------------------------------
/testy/test_nameplate.lua:
--------------------------------------------------------------------------------
1 | package.path = "../?.lua;"..package.path;
2 |
3 | require("remotesvg.SVGElements")()
4 |
5 | local doc = svg {
6 | ['xmlns:xlink']="http://www.w3.org/1999/xlink",
7 | width="7.50in",
8 | height="2.00in",
9 | viewBox="0 0 540 144",
10 | --[[
11 | defs {
12 | radialGradient {id="MyGradient",
13 | stop {offset="10%", ['stop-color']="gold"},
14 | stop {offset="95%", ['stop-color']="green"},
15 | },
16 | },
17 |
18 | circle {fill="url(#MyGradient)",
19 | cx="60", cy="60", r="50"
20 | },
21 | --]]
22 | -- The outline to be cut out
23 | rect {
24 | --fill="url(#MyGradient)",
25 | fill="none",
26 | stroke="black",
27 | x=0, y=0, width="7.5in", height="2in",
28 | },
29 |
30 | image {
31 | stroke = "red",
32 | id = "faceimage",
33 | viewBox = "0 0 240 320",
34 | x = "0.75in",
35 | y = "0",
36 | width = "1.0in",
37 | height = "2.0in",
38 | ["xlink:href"] = "images/charicature_small.jpg",
39 | },
40 |
41 | -- shaded rectangle left side
42 | --[[
43 | rect {
44 | fill="gray",
45 | ["fill-opacity"] = "0.4",
46 | stroke="black",
47 | x=0, y=0, width="1.5in", height="2in",
48 | },
49 | --]]
50 |
51 | ellipse {
52 | fill = "none",
53 | stroke = "black",
54 | cx = "0.5in",
55 | cy = "1.25in",
56 | rx = "0.188in",
57 | ry = "0.188in"
58 | };
59 |
60 | ellipse {
61 | fill = "none",
62 | stroke = "black",
63 | cx = "7in",
64 | cy = "1.25in",
65 | rx = "0.188in",
66 | ry = "0.188in"
67 | };
68 | }
69 |
70 | local FileStream = require("remotesvg.filestream")
71 | local SVGStream = require("remotesvg.SVGStream")
72 | local ImageStream = SVGStream(FileStream.open("test_nameplate.svg"))
73 |
74 | doc:write(ImageStream);
75 |
--------------------------------------------------------------------------------
/testy/test_inherit.lua:
--------------------------------------------------------------------------------
1 |
2 | -- Create a new class that inherits from a base class
3 | --
4 | function inheritsFrom( baseClass )
5 |
6 | -- The following lines are equivalent to the SimpleClass example:
7 |
8 | -- Create the table and metatable representing the class.
9 | local new_class = {}
10 | local class_mt = { __index = new_class }
11 |
12 | -- Note that this function uses class_mt as an upvalue, so every instance
13 | -- of the class will share the same metatable.
14 | --
15 | function new_class:create()
16 | local newinst = {}
17 | setmetatable( newinst, class_mt )
18 | return newinst
19 | end
20 |
21 | -- The following is the key to implementing inheritance:
22 |
23 | -- The __index member of the new class's metatable references the
24 | -- base class. This implies that all methods of the base class will
25 | -- be exposed to the sub-class, and that the sub-class can override
26 | -- any of these methods.
27 | --
28 | if baseClass then
29 | setmetatable( new_class, { __index = baseClass } )
30 | end
31 |
32 | return new_class
33 | end
34 |
35 |
36 | local function test_animal()
37 | local Animal = {}
38 | local Animal_mt = {
39 | __index = Animal;
40 | }
41 |
42 | function Animal.new(kind)
43 | local obj = {
44 | kind = kind;
45 | }
46 |
47 | setmetatable(obj, Animal_mt)
48 | return obj;
49 | end
50 |
51 |
52 | function Animal.whatKind(self)
53 | print(self.kind)
54 | end
55 |
56 |
57 |
58 | local Dog = inheritsFrom(Animal);
59 |
60 |
61 | function Dog.new(self)
62 | local obj = {
63 | kind = "canine";
64 | }
65 | setmetatable(obj, Dog_mt)
66 |
67 | return obj;
68 | end
69 |
70 | function Dog.bark(self)
71 | print("I am a dog: woof!")
72 | end
73 |
74 | -- test
75 | local a = Animal.new("ostrich")
76 | a:whatKind();
77 |
78 | local d = Dog:new()
79 | d:whatKind();
80 | d:bark();
81 | end
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | test_animal();
90 |
--------------------------------------------------------------------------------
/remotesvg/stream.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Abstract interface for a stream object
3 |
4 | -- Attributes of the stream
5 | function Stream:GetLength()
6 | end
7 |
8 | function Stream:GetPosition()
9 | end
10 |
11 | function Stream:Seek(offset, origin)
12 | end
13 |
14 | -- Reading interface
15 | function Stream:ReadByte()
16 | return nil;
17 | end
18 |
19 | function Stream:ReadBytes(buffer, len, offset)
20 | return 0
21 | end
22 |
23 | function Stream:ReadString(count)
24 | return nil
25 | end
26 |
27 |
28 | -- Writing interface
29 | function Stream:WriteByte(value)
30 | return 0
31 | end
32 |
33 | function Stream:WriteBytes(buffer, len, offset)
34 | return 0
35 | end
36 |
37 | function Stream:WriteString(str, count, offset)
38 | offset = offset or 0
39 | count = count or #str
40 |
41 | return self:WriteBytes(str, count, offset)
42 | end
43 | --]]
44 |
45 | local STREAM_SEEK_SET = 0 -- from beginning
46 | local STREAM_SEEK_CUR = 1 -- from current position
47 | local STREAM_SEEK_END = 2 -- from end
48 |
49 | -- Read characters from a stream until the specified
50 | -- ending is found, or until the stream runs out of bytes
51 | local function ReadLine(stream, maxbytes)
52 | if not stream then
53 | return nil
54 | end
55 |
56 | maxbytes = maxbytes or 1024
57 | --print(string.format("-- ReadLine(), maxbytes: 0x%x", maxbytes));
58 |
59 | local chartable = {}
60 | local CR = string.byte("\r")
61 | local LF = string.byte("\n")
62 |
63 |
64 | -- use the stream's byte iterator
65 | local haveCR = false
66 | for abyte in stream:Bytes(maxbytes) do
67 | if abyte == CR then
68 | haveCR = true
69 | elseif abyte == LF then
70 | break
71 | else
72 | table.insert(chartable, string.char(abyte))
73 | end
74 | end
75 |
76 | local str = table.concat(chartable)
77 |
78 | return str
79 | end
80 |
81 | return {
82 | SEEK_SET = STREAM_SEEK_SET,
83 | SEEK_CUR = STREAM_SEEK_CUR,
84 | SEEK_END = STREAM_SEEK_END,
85 |
86 | ReadLine = ReadLine,
87 | }
88 |
--------------------------------------------------------------------------------
/testy/ezsvg_example1.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env luajit
2 |
3 | package.path = "../?.lua;"..package.path;
4 |
5 |
6 |
7 | require("remotesvg.SVGElements")()
8 | local FileStream = require("remotesvg.filestream")
9 | local SVGStream = require("remotesvg.SVGStream")
10 | local ImageStream = SVGStream(FileStream.open("ezsvg_example1.svg"))
11 |
12 |
13 | -- A Translation of this:
14 | -- https://github.com/cappelnord/EzSVG/blob/master/example1.lua
15 |
16 | local width = 350
17 | local height = 370
18 |
19 | local doc = svg {
20 | width = width, height = height,
21 |
22 | rect {
23 | x = 0, y = 0,
24 | width = width, height = height,
25 | stroke = "none",
26 | fill = "rgbcolor(50,50,50)"
27 | }
28 | }
29 |
30 | local lines = g{
31 | stroke = "white",
32 | stroke_linecap = "round",
33 | }
34 |
35 |
36 | for x=0, width,width/80 do
37 | lines:append (line ({
38 | stroke_width = x/150,
39 | x1 = x, y1 = 0,
40 | x2 = x, y2 = math.abs(math.sin(x/30) * 250)
41 | }));
42 | end
43 |
44 |
45 | doc:append(lines);
46 |
47 |
48 | for i=0,9 do
49 | local x = i%3 * 70 + 100
50 | local y = math.floor(i/3) * 70 + 100
51 |
52 | local group = g {
53 | transform = string.format("translate(%d, %d)", x, y)
54 | }
55 |
56 | local t1 = nil
57 | for r=-90,0,0.5 do
58 | local rotation = string.format('rotate (%f)', r)
59 | local filler = string.format("rgb(0, %d, %d", 255-(r*-3), r+90)
60 |
61 | t1 = text {
62 | x = 0,
63 | y = 0,
64 | fill = filler,
65 | font_family = "Arial Rounded MT Bold",
66 | font_size = "40px",
67 | transform = rotation,
68 |
69 | -- The actual text
70 | tostring(i);
71 | }
72 |
73 | group:append(t1)
74 | end
75 |
76 | t1:attr("stroke", "#FFFFFF");
77 | t1:attr("fill", "#AAFF00");
78 | group:append(t1)
79 |
80 | doc:append(group)
81 | end
82 |
83 |
84 |
85 | doc:write(ImageStream)
86 |
--------------------------------------------------------------------------------
/remotesvg/jskeycodes.lua:
--------------------------------------------------------------------------------
1 | return {
2 | [8] = "backspace";
3 | [9] = "tab";
4 |
5 | [13] = "enter";
6 | [16] = "shift";
7 | [17] = "ctrl";
8 | [18] = "alt";
9 | [19] = "break";
10 |
11 | [20] = "capslock";
12 | [27] = "esc";
13 |
14 | [32] = "space";
15 | [33] = "pageup";
16 | [34] = "pagedown";
17 | [35] = "end";
18 | [36] = "home";
19 | [37] = "leftarrow";
20 | [38] = "uparrow";
21 | [39] = "rightarrow";
22 |
23 | [40] = "downarrow";
24 | [45] = "insert";
25 | [46] = "delete";
26 |
27 | [48] = "0";
28 | [49] = "1";
29 | [50] = "2";
30 | [51] = "3";
31 | [52] = "4";
32 | [53] = "5";
33 | [54] = "6";
34 | [55] = "7";
35 | [56] = "8";
36 | [57] = "9";
37 |
38 |
39 | [65] = "a";
40 | [66] = "b";
41 | [67] = "c";
42 | [68] = "d";
43 | [69] = "e";
44 |
45 | [70] = "f";
46 | [71] = "g";
47 | [72] = "h";
48 | [73] = "i";
49 | [74] = "j";
50 | [75] = "k";
51 | [76] = "l";
52 | [77] = "m";
53 | [78] = "n";
54 | [79] = "o";
55 |
56 | [80] = "p";
57 | [81] = "q";
58 | [82] = "r";
59 | [83] = "s";
60 | [84] = "t";
61 | [85] = "u";
62 | [86] = "v";
63 | [87] = "w";
64 | [88] = "x";
65 | [89] = "y";
66 | [90] = "z";
67 |
68 | [91] = "";
69 | [92] = "";
70 | [93] = "";
71 |
72 | [96] = "numpad0";
73 | [97] = "numpad1";
74 | [98] = "numpad2";
75 | [99] = "numpad3";
76 | [100] = "numpad4";
77 | [101] = "numpad5";
78 | [102] = "numpad6";
79 | [103] = "numpad7";
80 | [104] = "numpad8";
81 | [105] = "numpad9";
82 |
83 | [106] = "multiply";
84 | [107] = "add";
85 | [109] = "subtract";
86 | [110] = "decimalpoint";
87 | [111] = "divide";
88 |
89 | [112] = "f1";
90 | [113] = "f2";
91 | [114] = "f3";
92 | [115] = "f4";
93 | [116] = "f5";
94 | [117] = "f6";
95 | [118] = "f7";
96 | [119] = "f8";
97 | [120] = "f9";
98 | [121] = "f10";
99 | [122] = "f11";
100 | [123] = "f12";
101 |
102 |
103 | [144] = "numlock";
104 | [145] = "scrolllock";
105 | [186] = "semicolon";
106 | [187] = "equalsign";
107 | [188] = "comma";
108 | [189] = "dash";
109 | [190] = "period";
110 | [191] = "forwardslash";
111 | [192] = "graveaccent";
112 | [219] = "openbracket";
113 | [220] = "backslash";
114 | [221] = "closebracket";
115 | [222] = "singlequote";
116 |
117 | }
--------------------------------------------------------------------------------
/testy/test_elemiterator.lua:
--------------------------------------------------------------------------------
1 | --test_elemiterator.lua
2 | -- load svg
3 | package.path = "../?.lua;"..package.path;
4 | local parser = require("remotesvg.parsesvg")
5 |
6 | -- read the source file
7 | -- pass in the name of your SVG document.lua file, without
8 | -- the '.lua' extension.
9 | local filename = arg[1]
10 | if not filename then return end
11 |
12 | local doc = parser:parseFile(filename);
13 |
14 |
15 | local function printElement(elem)
16 | if type(elem) == "string" then
17 | --print(elem)
18 | return
19 | end
20 |
21 |
22 | print(string.format("==== %s ====", elem._kind))
23 |
24 | -- print the attributes
25 | for name, value in elem:attributes() do
26 | print(name,value)
27 | end
28 |
29 | -- print the content
30 | end
31 |
32 | local function test_selectAll()
33 | -- iterate through all the nodes in
34 | -- document order, printing something interesting along
35 | -- the way
36 |
37 | for child in doc:selectAll() do
38 | printElement(child)
39 | end
40 | end
41 |
42 | local function test_selectAttribute()
43 | -- select the elements that have an attribute
44 | -- with the name 'sodipodi' in them
45 | local function hasSodipodiAttribute(entry)
46 | if type(entry) ~= "table" then
47 | return false;
48 | end
49 |
50 | for name, value in entry:attributes() do
51 | --print("hasSodipodi: ", entry._kind, name, value, type(name))
52 | if name:find("sodipodi") then
53 | return true;
54 | end
55 | end
56 |
57 | return false
58 | end
59 |
60 | for child in doc:selectElementMatches(hasSodipodiAttribute) do
61 | if type(child) == "table" then
62 | printElement(child)
63 | end
64 | end
65 | end
66 |
67 | local function test_selectElementMatches()
68 | print("<==== selectElementMatches: entry._kind == 'g' ====>")
69 | for child in doc:selectElementMatches(function(entry) if entry._kind == "g" then return true end end) do
70 | print(child._kind)
71 | end
72 | end
73 |
74 |
75 |
76 |
77 | local function test_getelementbyid()
78 | local elem = doc:getElementById("radialGradient10091")
79 |
80 | printElement(elem)
81 | end
82 |
83 | --test_selectAll();
84 | test_selectAttribute()
85 | --test_selectElementMatches()
86 | --test_getelementbyid()
87 |
--------------------------------------------------------------------------------
/testy/ezsvg_features.lua:
--------------------------------------------------------------------------------
1 | -- A translation of the example embedded here
2 | -- https://github.com/cappelnord/EzSVG
3 |
4 | --require("remotesvg.SVGElements")()
5 | -- Assuming this will be read into a context where SVGElements
6 | -- has already been read, and exposed in global namespace.
7 |
8 | -- create a document
9 | local doc = svg {
10 | ['xmlns:xlink'] = "http://www.w3.org/1999/xlink",
11 | width = 1000,
12 | height = 1000,
13 |
14 | stroke_width = 2,
15 | stroke = "black"
16 | }
17 |
18 |
19 | for d=0,1000,100 do
20 | -- create a circle
21 | local circ = circle {
22 | cx = d, cy = d, r = d/10,
23 | fill = string.format("rgb(%d, 0, 0)", d/4);
24 | }
25 |
26 | -- add the circle to the doc
27 | doc:append(circ)
28 | end
29 |
30 |
31 | -- you can also set a single style
32 | -- create a group (very handy, also stays a group in Inkscape/Illustrator)
33 | local group = g {
34 | stroke = "green"
35 | }
36 |
37 |
38 | for r=0,360, 10 do
39 | -- create a line and add a transform (rotation with r degrees, centered on 500/500)
40 | local l = line{x1 = 100, y1=500, x2=900, y2=500,
41 | transform = string.format("rotate(%d, 500, 500)", r),
42 | }
43 |
44 | -- add it to the group
45 | group:append(l)
46 | end
47 |
48 | -- add the group to the document
49 | doc:append(group)
50 |
51 |
52 | -- create a path object and set its styling
53 | local path1 = path {
54 | id = "TextPath",
55 | }
56 |
57 | path1:moveBy(500, 500)
58 | :sqCurveBy(300,300)
59 | :sqCurveBy(-200, 0)
60 | :sqCurveBy(-200, -400)
61 | :sqCurveTo(500, 500)
62 |
63 | doc:append (
64 | defs {
65 | radialGradient {id="PathGradient",
66 | stop {offset="0%", ['stop-color']="black"},
67 | stop {offset="100%", ['stop-color']="yellow"},
68 | },
69 |
70 | path1,
71 | })
72 |
73 |
74 |
75 | -- draw the path
76 | doc:append(use {
77 | ['xlink:href'] = "#TextPath",
78 | stroke = "blue",
79 | ['stroke-width'] = 2,
80 | fill = "url(#PathGradient)",
81 | ['fill-opacity'] = "0.8"
82 | })
83 |
84 | -- add text to doc, add a path and style it
85 | doc:append(text {
86 | font_size = 60,
87 | font_family = "Arial",
88 | fill = "#CCCCCC",
89 | stroke = "black",
90 |
91 | textPath {
92 | ['xlink:href'] ="#TextPath",
93 |
94 | [[
95 | It's so ez to draw some lines, it's so easy to draw some lines ...
96 | ]]
97 | }
98 | })
99 |
100 | return doc
101 |
--------------------------------------------------------------------------------
/remotesvg/filestream.lua:
--------------------------------------------------------------------------------
1 |
2 | local ffi = require "ffi"
3 | local stream = require "remotesvg.stream"
4 |
5 |
6 | local FileStream = {}
7 | local FileStream_mt = {
8 | __index = FileStream,
9 | }
10 |
11 | function FileStream.new(handle)
12 | if not handle then return nil end
13 |
14 | local obj = {
15 | FileHandle = handle,
16 | }
17 |
18 | setmetatable(obj, FileStream_mt)
19 |
20 | return obj;
21 | end
22 |
23 |
24 |
25 | function FileStream.open(filename, mode)
26 | if not filename then return nil end
27 |
28 | mode = mode or "wb+"
29 | local handle = io.open(filename, mode)
30 | if not handle then return nil end
31 |
32 | return FileStream.new(handle)
33 | end
34 |
35 | function FileStream:close()
36 | return self.FileHandle:close();
37 | end
38 |
39 | function FileStream:getLength()
40 | local currpos = self.FileHandle:seek()
41 | local size = self.FileHandle:seek("end")
42 |
43 | self.FileHandle:seek("set",currpos)
44 |
45 | return size;
46 | end
47 |
48 | function FileStream:getPosition()
49 | local currpos = self.FileHandle:seek()
50 | return currpos;
51 | end
52 |
53 | function FileStream:seek(offset, origin)
54 | offset = offset or 0
55 | origin = origin or stream.SEEK_SET
56 |
57 | if origin == stream.SEEK_CUR then
58 | return self.FileHandle:seek("cur", offset)
59 | elseif origin == stream.SEEK_SET then
60 | return self.FileHandle:seek("set", offset)
61 | elseif origin == stream.SEEK_END then
62 | return self.FileHandle:seek("end", offset)
63 | end
64 |
65 | return nil
66 | end
67 |
68 | function FileStream:readByte()
69 | local str = self.FileHandle:read(1)
70 | if not str then return str end
71 |
72 | return string.byte(str);
73 | end
74 |
75 | function FileStream:readBytes(buffer, len, offset)
76 | offset = offset or 0
77 | local str = self.FileHandle:read(len)
78 | local maxbytes = math.min(len, #str)
79 | ffi.copy(buffer+offset, str, maxbytes)
80 |
81 | return maxbytes
82 | end
83 |
84 | function FileStream:readString(count)
85 | local str = self.FileHandle:read(count)
86 |
87 | return str
88 | end
89 |
90 |
91 |
92 | function FileStream:writeByte(value)
93 | self.FileHandle:write(string.char(value))
94 | return 1
95 | end
96 |
97 | function FileStream:writeBytes(buffer, len, offset)
98 | offset = offset or 0
99 |
100 | if type(buffer) == "string" then
101 | self.FileHandle:write(buffer)
102 | return len
103 | end
104 |
105 | -- assume we have a pointer to a buffer
106 | -- convert to string and write it out
107 | local str = ffi.string(buffer, len)
108 | self.FileHandle:write(str)
109 |
110 | return len
111 | end
112 |
113 | function FileStream:writeString(str, count, offset)
114 | offset = offset or 0
115 | count = count or #str
116 | local strptr = ffi.cast("char *", str);
117 |
118 | return self:writeBytes(strptr, count, offset)
119 | end
120 |
121 | return FileStream;
122 |
--------------------------------------------------------------------------------
/remotesvg/parsesvg.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | A simple SVG Parser which fills in a lua based object model
3 | for the image specified.
4 | --]]
5 |
6 | local ffi = require("ffi")
7 | local bit = require("bit")
8 | local lshift, rshift, band, bor = bit.lshift, bit.rshift, bit.band, bit.bor
9 |
10 | local XmlParser = require("remotesvg.SVGXmlParser")
11 | local svg = require("remotesvg.SVGElements")
12 |
13 |
14 | local function Stack()
15 | local obj = {}
16 | setmetatable(obj, {
17 | __index = obj;
18 | })
19 |
20 | function obj.push(self, value)
21 | table.insert(self, value);
22 |
23 | return value;
24 | end
25 |
26 | function obj.pop(self)
27 | return table.remove(self)
28 | end
29 |
30 | function obj.top(self)
31 | if #self < 1 then
32 | return nil;
33 | end
34 |
35 | return self[#self];
36 | end
37 |
38 | return obj;
39 | end
40 |
41 |
42 |
43 | local SVGParser = {}
44 | setmetatable(SVGParser, {
45 | __call = function(self, ...)
46 | return self:new(...);
47 | end
48 | })
49 | local SVGParser_mt = {
50 | __index = SVGParser;
51 | }
52 |
53 | function SVGParser.init(self)
54 | local obj = {
55 | ElemStack = Stack();
56 | }
57 |
58 | setmetatable(obj, SVGParser_mt);
59 |
60 | return obj;
61 | end
62 |
63 | function SVGParser.new(self)
64 | local parser = self:init()
65 |
66 | return parser;
67 | end
68 |
69 | function SVGParser.parse(self, input)
70 | XmlParser.parseXML(input, SVGParser.startElement, SVGParser.endElement, SVGParser.content, self)
71 |
72 |
73 | return self.Top;
74 | end
75 |
76 | function SVGParser.parseFile(self, filename)
77 | local fp = io.open(filename, "rb")
78 | if not fp then
79 | return
80 | end
81 |
82 | local data = fp:read("*a");
83 | fp:close();
84 |
85 | local parser = SVGParser();
86 | local shape = parser:parse(data)
87 |
88 | return shape;
89 | end
90 |
91 |
92 |
93 | function SVGParser.startElement(self, name, attr)
94 | --print("SVGParser.startElement: ", el, attr)
95 | --print("BEGIN: ", name)
96 |
97 | -- create new element
98 | local newElem = svg.BasicElem(name, attr)
99 |
100 | -- if there's already something on the stack,
101 | -- then add the current element as a child
102 | if self.ElemStack:top() then
103 | self.ElemStack:top():append(newElem)
104 | else
105 | self.Top = newElem
106 | end
107 |
108 | -- push the new element onto the stack
109 | self.ElemStack:push(newElem);
110 | end
111 |
112 | -- called whenever there is a closing tag for an element
113 | function SVGParser.endElement(self, name)
114 |
115 | -- pop it off the top of the stack
116 | local oldone = self.ElemStack:pop();
117 | --print("END: ", name, oldone._kind, #self.ElemStack)
118 | end
119 |
120 | -- Content is called for things like
121 | -- text, comments, titles, descriptions and the like
122 | function SVGParser.content(self, s)
123 | --print("CONTENT: ", s)
124 | self.ElemStack:top():appendLiteral(s)
125 | end
126 |
127 | return SVGParser
128 |
--------------------------------------------------------------------------------
/examples/app_arcs02.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env luajit
2 |
3 | package.path = "../?.lua;"..package.path;
4 |
5 | local SVGInteractor = require("remotesvg.SVGInteractor")
6 | require("remotesvg.SVGElements")()
7 |
8 | local group = g;
9 |
10 |
11 | local ImageStream = size()
12 |
13 |
14 | local function draw(ImageStream)
15 | ImageStream:reset();
16 |
17 | local doc = svg{
18 | ['xmlns:xlink'] = "http://www.w3.org/1999/xlink",
19 | width="12cm",
20 | height="5.25cm",
21 | viewBox="0 0 1200 525",
22 |
23 | group {
24 | ['font-family'] = "Verdana",
25 |
26 | defs {
27 | group {id="baseEllipses", ["font-size"]=20;
28 | ellipse {cx=125, cy=125, rx=100, ry=50, fill="none", stroke="#888888", ["stroke-width"] = 2};
29 | ellipse {cx=225, cy=75, rx=100, ry=50, fill="none", stroke="#888888", ["stroke-width"] = 2};
30 | text ({x=35, y=70}, "Arc start");
31 | text ({x=225, y=145}, "Arc end");
32 | };
33 | };
34 |
35 | rect {x=1, y=1, width=1198, height=523, fill="none", stroke="blue", ["stroke-width"] = 1};
36 |
37 | group {['font-size'] = 30,
38 | group {transform="translate(0,0)",
39 | use {["xlink:href"] = "#baseEllipses", ["xmlns:xlink"] ="http://www.w3.org/1999/xlink"};
40 | },
41 |
42 | group {transform="translate(400,0)",
43 | text {x=50, y=210, "large-arc-flag=0"};
44 | text {x=50, y=250, "sweep-flag=0"};
45 | use {["xlink:href"] = "#baseEllipses", ["xmlns:xlink"] = "http://www.w3.org/1999/xlink"},
46 | path {d="M 125,75 a100,50 0 0,0 100,50", fill="none", stroke="red", ["stroke-width"] = 6},
47 | },
48 |
49 | group {transform="translate(800,0)",
50 | text {x=50, y=210, "large-arc-flag=0"};
51 | text {x=50, y=250, "sweep-flag=1"};
52 | use {["xlink:href"]="#baseEllipses", ["xmlns:xlink"] = "http://www.w3.org/1999/xlink"};
53 | path {d="M 125,75 a100,50 0 0,1 100,50", fill="none", stroke="red", ["stroke-width"] = 6};
54 | },
55 | group {transform="translate(400,250)";
56 | text {x=50, y=210, "large-arc-flag=1"};
57 | text {x=50, y=250, "sweep-flag=0"};
58 | use {["xlink:href"] ="#baseEllipses", ["xmlns:xlink"] = "http://www.w3.org/1999/xlink"};
59 | path {d="M 125,75 a100,50 0 1,0 100,50", fill="none", stroke="red", ["stroke-width"] = 6};
60 | };
61 | group {transform="translate(800,250)";
62 | text {x=50, y=210, "large-arc-flag=1"};
63 | text {x=50, y=250, "sweep-flag=1"};
64 | use {["xlink:href"]="#baseEllipses", ["xmlns:xlink"] = "http://www.w3.org/1999/xlink"};
65 | path {d="M 125,75 a100,50 0 1,1 100,50", fill="none", stroke="red", ["stroke-width"] = 6};
66 | };
67 | };
68 | };
69 | };
70 |
71 |
72 | doc:write(ImageStream);
73 | end
74 |
75 | -- This is a one off
76 | -- we won't be refreshing the image, so just draw
77 | -- into the image stream once, and do NOT implement
78 | -- the 'frame()' function.
79 | draw(ImageStream);
80 |
81 | run()
82 |
--------------------------------------------------------------------------------
/testy/test_patterns.lua:
--------------------------------------------------------------------------------
1 | --test_patterns.lua
2 | package.path = "../?.lua;"..package.path
3 |
4 | local sattr = require("remotesvg.SVGAttributes")
5 | local parse = sattr.parseAttribute;
6 |
7 | local function test_numbers()
8 | local str = "10, 20.3, 42 375.67"
9 | local patt1 = "(%S+)"
10 | local patt2 = "(%d+%.?%d+)"
11 |
12 |
13 | local function printItems(patt, str)
14 | print("printItems: ", patt, str)
15 |
16 | for item in str:gmatch(patt) do
17 | print(item)
18 | end
19 | end
20 |
21 | printItems(patt1, str)
22 | printItems(patt2, str)
23 | end
24 |
25 | local function test_style()
26 | local str = 'stop-color:#191b21;stop-opacity:0.34437087;'
27 | local patt = "([^;]+)"
28 | print("parseStyle: ", patt, str)
29 |
30 | local tbl = {}
31 |
32 | for item in str:gmatch(patt) do
33 | local name, value = item:match("(%g+):(%g+)")
34 | print("item:match: ", name, value)
35 | name, value = parse(name, value)
36 | print("parse result: ", name, value)
37 | tbl[name] = value;
38 | end
39 |
40 | for k,v in pairs(tbl) do
41 | print(k,v)
42 | end
43 | end
44 |
45 | local function parsetransform(name, value, strict)
46 |
47 | local patt = "(%a+)(%b())"
48 | local numpatt = "([+-]?%d+%.?%d*)"
49 |
50 | local tbl = {}
51 | for kind, value in value:gmatch(patt) do
52 | --print (kind, value)
53 | local nums = {}
54 | -- get the list of numbers
55 | for num in value:gmatch(numpatt) do
56 | table.insert(nums, num)
57 | end
58 |
59 | if #nums > 0 then
60 | if kind == "translate" then
61 | local x = nums[1];
62 | local y = nums[2] or 0;
63 | local item = {name = 'translate', x = x, y = y}
64 | setmetatable(item, {
65 | __tostring = function(self)
66 | return string.format('translate(%f %f)', self.x, self.y);
67 | end,
68 | })
69 | table.insert(tbl, item)
70 | elseif kind == "rotate" then
71 | local angle = nums[1];
72 | local item = {name = "rotate", angle=angle, x = nums[2], y = nums[3]}
73 | setmetatable(item, {
74 | __tostring = function(self)
75 | if self.x and self.y then
76 | return string.format("%s(%f %f %f)", self.name, self.angle, self.x, self.y)
77 | elseif self.angle then
78 | return string.format("%s(%f)", self.name, self.angle);
79 | end
80 | return ""
81 | end,
82 | })
83 | table.insert(tbl, item)
84 | elseif kind == "scale" then
85 | local x = nums[1]
86 | local y = nums[2] or x
87 | local item = {name="scale", x=x, y=y}
88 | setmetatable(item, {
89 | __tostring = function(self)
90 | if self.x and self.y then
91 | return string.format("%s(%f %f)", self.name, self.x, self.y)
92 | elseif self.x then
93 | return string.format("%s(%f)", self.name, self.x);
94 | else
95 | return ""
96 | end
97 | end
98 | })
99 | table.insert(tbl, item)
100 | elseif kind == "skewX" then
101 | elseif kind == "skewY" then
102 | -- get the numbers out
103 | elseif kind == "matrix" then
104 | end
105 | end
106 | end
107 |
108 | return name, tbl
109 | end
110 |
111 | local function test_transform()
112 | print("==== test_transform ====")
113 | local subject = "translate(20 10) rotate(30 10 10)"
114 |
115 | name, obj = parsetransform("transform", subject)
116 |
117 | print("== RESULTS ==")
118 | for idx, item in ipairs(obj) do
119 | print(item)
120 | end
121 | end
122 |
123 | --test_numbers();
124 | --test_style();
125 | test_transform()
126 |
--------------------------------------------------------------------------------
/remotesvg/maths.lua:
--------------------------------------------------------------------------------
1 | local bit = require("bit")
2 | local band, bor, lshift, rshift = bit.band, bit.bor, bit.lshift, bit.rshift
3 |
4 | local acos = math.acos;
5 | local sqrt = math.sqrt;
6 | local floor = math.floor;
7 | local ceil = math.ceil;
8 |
9 |
10 |
11 | local function curveDivs(r, arc, tol)
12 | local da = acos(r / (r + tol)) * 2.0;
13 | local divs = ceil(arc / da);
14 | if (divs < 2) then
15 | divs = 2;
16 | end
17 |
18 | return divs;
19 | end
20 |
21 | -- fairly efficient division by 255
22 | local function div255(x)
23 | return rshift(((x+1) * 257), 16);
24 | end
25 |
26 | local function GetAlignedByteCount(width, bitsPerPixel, byteAlignment)
27 | local nbytes = width * (bitsPerPixel/8);
28 | return nbytes + (byteAlignment - (nbytes % byteAlignment)) % 4
29 | end
30 |
31 | -- determine where a point (p3) intersects the
32 | -- line determined by points p1 and p2
33 | -- Points on line defined by
34 | -- P = P1 + u*(P2 - P1)
35 | local function lineMag(x1, y1, x2, y2)
36 | local x = x2 - x1;
37 | local y = y2 - y1;
38 |
39 | return sqrt(x*x+y*y);
40 | end
41 |
42 | local function vmag(x, y) return sqrt(x*x + y*y); end
43 |
44 | local function normalize(x, y)
45 | local d = sqrt(x*x + y*y);
46 | if (d > 1e-6) then
47 | local id = 1.0 / d;
48 | x = x * id;
49 | y = y * id;
50 | end
51 |
52 | return d, x, y;
53 | end
54 |
55 | local function vecrat(ux, uy, vx, vy)
56 | return (ux*vx + uy*vy) / (vmag(ux,uy) * vmag(vx,vy));
57 | end
58 |
59 |
60 |
61 | local function vecang(ux, uy, vx, vy)
62 |
63 | local r = vecrat(ux,uy, vx,vy);
64 | if (r < -1.0) then
65 | r = -1.0;
66 | end
67 |
68 | if (r > 1.0) then
69 | r = 1.0;
70 | end
71 |
72 | local acs = acos(r);
73 | if (ux*vy < uy*vx) then
74 | return -1.0 * acs;
75 | end
76 |
77 | return acs;
78 | end
79 |
80 | local function pointLineIntersection(x1, y1, x2, y2, x3, y3)
81 | local mag = lineMag(x1, y1, x2, y2)
82 | local u = ((x3 - x1)*(x2-x1) + (y3-y1)*(y2-y1))/(mag*mag)
83 | local x = x1 + u * (x2 - x1);
84 | local y = y1 + u * (y2 - y1);
85 |
86 | return x, y
87 | end
88 |
89 | local function pointLineDistance(x1, y1, x2, y2, x3, y3)
90 | local x, y = pointLineIntersection(x1, y1, x2, y2, x3, y3)
91 |
92 | return lineMag(x3, y3, x, y)
93 | end
94 |
95 | -- determine if two points are equal, within a specified tolerance
96 | local function pointEquals(x1, y1, x2, y2, tol)
97 | local dx = x2 - x1;
98 | local dy = y2 - y1;
99 |
100 | return dx*dx + dy*dy < tol*tol;
101 | end
102 |
103 | function RANGEMAP(x, a, b, c, d)
104 | return c + ((x-a)/(b-a)*(d-c))
105 | end
106 |
107 | local function round(n)
108 | if n >= 0 then
109 | return floor(n+0.5)
110 | end
111 |
112 | return ceil(n-0.5)
113 | end
114 |
115 | local function clamp(x, a, b)
116 | if x < a then return a end
117 | if x > b then return b end
118 |
119 | return x;
120 | end
121 |
122 | local function sgn(x)
123 | if x < 0 then return -1 end
124 | if x > 0 then return 1 end
125 |
126 | return 0
127 | end
128 |
129 | local function sqr(x) return x*x; end
130 |
131 |
132 |
133 |
134 |
135 | return {
136 | clamp = clamp;
137 | curveDivs = curveDivs;
138 | div255 = div255;
139 | GetAlignedByteCount = GetAlignedByteCount;
140 |
141 | lineMag = lineMag;
142 |
143 | pointEquals = pointEquals;
144 | pointLineIntersection = pointLineIntersection;
145 | pointLineDistance = pointLineDistance;
146 |
147 | RANGEMAP = RANGEMAP;
148 | round = round;
149 | sgn = sgn;
150 | sqr = sqr;
151 |
152 | vecang = vecang;
153 | vecrat = vecrat;
154 | vmag = vmag;
155 | }
--------------------------------------------------------------------------------
/remotesvg/matrix2D.lua:
--------------------------------------------------------------------------------
1 | local ffi = require ("ffi")
2 |
3 | local RAD = math.rad
4 | local mtan = math.tan;
5 | local mcos = math.cos;
6 | local msin = math.sin;
7 |
8 | local tan = function(d) return mtan(RAD(d)) end
9 | local cos = function(d) return mcos(RAD(d)) end
10 | local sin = function(d) return msin(RAD(d)) end
11 |
12 |
13 | local matrix2D = {}
14 | setmetatable(matrix2D, {
15 | __call = function (self, ...)
16 | return self:new(...);
17 | end,
18 | })
19 | local matrix2D_mt = {
20 | __index = matrix2D;
21 |
22 | __tostring = function(self)
23 | return string.format("matrix (%f %f %f %f %f %f)",
24 | self[1], self[2], self[3], self[4], self[5], self[6])
25 | end,
26 | }
27 |
28 | function matrix2D.new(self, a, b, c, d, e, f)
29 | a = a or 0
30 | b = b or 0
31 | c = c or 0
32 | d = d or 0
33 | e = e or 0
34 | f = f or 0
35 |
36 | local obj = {a, b, c, d, e, f}
37 | setmetatable(obj, matrix2D_mt)
38 |
39 | return obj;
40 | end
41 |
42 | --[[
43 | Instance Constructors
44 | --]]
45 |
46 | --[[
47 | Create a new identity matrix
48 | --]]
49 | function matrix2D.identity()
50 | return matrix2D(1, 0, 0, 1, 0, 0)
51 | end
52 |
53 | --[[
54 | Create a new matrix which represents the specified
55 | translation
56 | --]]
57 | function matrix2D.translation(tx, ty)
58 | tx = tx or 0
59 | ty = ty or 0
60 | return matrix2D(1, 0, 0, 1, tx, ty)
61 | end
62 |
63 | --[[
64 | Create a new matrix which represents the specified scaling
65 | --]]
66 | function matrix2D.scale(sx, sy)
67 | sx = sx or 1
68 | sy = sy or sx
69 |
70 | return matrix2D(sx, 0, 0, sy, 0, 0)
71 | end
72 |
73 | function matrix2D.skewX(a)
74 | a = a or 0
75 | return matrix2D(1, 0, tan(a), 1, 0, 0)
76 | end
77 |
78 | function matrix2D.skewY(a)
79 | a = a or 0
80 | return matrix2D(1, tan(a), 0, 1, 0, 0)
81 | end
82 |
83 | --[[
84 | create a matrix which is a rotation
85 | around the given angle
86 | --]]
87 | function matrix2D.rotation(a, x, y)
88 | local cs = cos(a);
89 | local sn = sin(a);
90 |
91 | return matrix2D(cs, sn, -sn, cs, 0, 0)
92 | end
93 |
94 | -- BUGBUG - need to convert to 1 based index
95 | function matrix2D.xformMultiply(t, s)
96 |
97 | local t0 = t[0] * s[0] + t[1] * s[2];
98 | local t2 = t[2] * s[0] + t[3] * s[2];
99 | local t4 = t[4] * s[0] + t[5] * s[2] + s[4];
100 | t[1] = t[0] * s[1] + t[1] * s[3];
101 | t[3] = t[2] * s[1] + t[3] * s[3];
102 | t[5] = t[4] * s[1] + t[5] * s[3] + s[5];
103 | t[0] = t0;
104 | t[2] = t2;
105 | t[4] = t4;
106 | end
107 |
108 | --[[
109 | Create a new matrix which is the inverse of the
110 | current matrix.
111 | --]]
112 | function matrix2D.inverse(t)
113 |
114 | local invdet = 0;
115 |
116 | local det = t[1] * t[4] - t[3] * t[2];
117 | if (det > -1e-6 and det < 1e-6) then
118 | return matrix2D.identity();
119 | end
120 |
121 | invdet = 1.0 / det;
122 | local inv = {}
123 | inv[1] = (t[4] * invdet);
124 | inv[3] = (-t[3] * invdet);
125 | inv[5] = ((t[3] * t[6] - t[4] * t[5]) * invdet);
126 | inv[2] = (-t[2] * invdet);
127 | inv[4] = (t[1] * invdet);
128 | inv[6] = ((t[2] * t[5] - t[1] * t[6]) * invdet);
129 | end
130 |
131 | function matrix2D.Premultiply(t, s)
132 |
133 | local s2 = ffi.new("double[6]");
134 | ffi.copy(s2, s, ffi.sizeof("double")*6);
135 | matrix2D.xformMultiply(s2, t);
136 | ffi.copy(t, s2, ffi.sizeof("double")*6);
137 | end
138 |
139 | --[[
140 | return two coordinates transformed
141 | by the current matrix
142 | --]]
143 | function matrix2D.xformPoint(t, x, y)
144 | local dx = x*t[1] + y*t[3] + t[5];
145 | local dy = x*t[2] + y*t[4] + t[6];
146 |
147 |
148 | return dx, dy;
149 | end
150 |
151 | --[[
152 | Return a transformed vector.
153 | Since vectors don't have position,
154 | leave off the translation portion.
155 | --]]
156 | function matrix2D.xformVec(t, x, y)
157 |
158 | local dx = x*t[1] + y*t[3];
159 | local dy = x*t[2] + y*t[4];
160 |
161 | return dx, dy
162 | end
163 |
164 | return matrix2D
165 |
--------------------------------------------------------------------------------
/remotesvg/sharesvg.htm:
--------------------------------------------------------------------------------
1 | return [[
2 |
3 |
4 |
5 | Screen View
6 |
7 |
8 |
75 |
76 |
77 |
78 |
81 |
82 |
83 |
84 |
85 |
86 |
127 |
128 | ]]
129 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # remotesvg
2 | Remote interaction using SVG
3 |
4 | This project presents a fairly straight forward interactive graphics environment
5 | using SVG graphics. The basic premise is that you will use a web browser to view
6 | graphics on a client machine. The rendering on the client is done using SVG
7 | graphics.
8 |
9 | On the server side, the SVG graphics can be built up using any mechanism desired.
10 | Since this is in Lua, you are free to do whatever you want on the lua side, as
11 | long as by the end, you generate an SVG image that can be rendered on the client.
12 |
13 | The SVGGeometry.lua file contains a lot of convenience classes that make building
14 | up an SVG image relatively simple. A typical example might look like this:
15 |
16 | ```lua
17 |
18 | local doc = svg({
19 | version="1.1",
20 | width = "12cm", height= "4cm",
21 | viewBox="0 0 1200 400",
22 |
23 | -- Show outline of canvas using 'rect' element
24 | rect({x = 1;y = 1;width = 1198; height = 398;
25 | fill = "none";
26 | stroke = "blue";
27 | ["stroke-width"] = 2;
28 | });
29 |
30 | circle({
31 | fill="red",
32 | stroke="blue",
33 | ["stroke-width"]=10,
34 | cx =600;
35 | cy = 200;
36 | r = 100;
37 | });
38 |
39 | })
40 |
41 | ```
42 |
43 | The style here is to essentially build up a document as a table of parameters. The 'svg' object, for instance, has top level attributes, followed by graphic
44 | elements, such as rect, and circle. The graphic elements in term have the
45 | same hierarchy.
46 |
47 | The entirety of the application may look like this:
48 |
49 | ```lua
50 | #!/usr/bin/env luajit
51 |
52 | package.path = "../?.lua;"..package.path;
53 |
54 | local SVGInteractor = require("remotesvg.SVGInteractor")
55 | local SVGStream = require("remotesvg.SVGStream")
56 | local SVGGeometry = require("remotesvg.SVGGeometry")
57 | local svg = SVGGeometry.Document;
58 | local rect = SVGGeometry.Rect;
59 | local circle = SVGGeometry.Circle;
60 |
61 | local keycodes = require("remotesvg.jskeycodes")
62 |
63 | local width = 640;
64 | local height = 480;
65 | local mstream = size(width, height)
66 | local ImageStream = SVGStream(mstream);
67 |
68 |
69 |
70 | function mouseMove(activity)
71 | print("mouseMove: ", activity.x, activity.y)
72 | end
73 |
74 | local function drawCircle(filename)
75 | ImageStream:reset();
76 |
77 | local doc = svg({
78 | version="1.1",
79 | width = "12cm", height= "4cm",
80 | viewBox="0 0 1200 400",
81 |
82 | -- Show outline of canvas using 'rect' element
83 | rect({x = 1;y = 1;width = 1198; height = 398;
84 | fill = "none";
85 | stroke = "blue";
86 | ["stroke-width"] = 2;
87 | });
88 |
89 | circle({
90 | fill="red",
91 | stroke="blue",
92 | ["stroke-width"]=10,
93 | cx =600;
94 | cy = 200;
95 | r = 100;
96 | });
97 |
98 | })
99 |
100 | doc:write(ImageStream);
101 | end
102 |
103 |
104 | function frame()
105 | drawCircle();
106 | end
107 |
108 | run()
109 |
110 | ```
111 |
112 | Mouse and keyboard interaction can be achieved by simply implementing the various
113 | mouse and keyboard event routines:
114 |
115 | mouseDown, mouseUp, mouseMove
116 | keyDown, keyUp, keyPress
117 |
118 | These routines each receive an 'activity' argument, which contains the
119 | details of what the event was.
120 |
121 | mouseDown
122 | Mouse Button Pressed, called whenever a button is pressed.
123 |
124 | {action='mouseDown', x, y, which};
125 | x - horizontal location of mouse
126 | y - vertical location of mouse
127 | which - which mouse button (1: left, 2:right, 3:middle)
128 |
129 | mouseUp
130 | Mouse Button Released, called whenever a pressed button
131 | is released.
132 |
133 | {action='mouseUp', x, y, which};
134 | x - horizontal location of mouse
135 | y - vertical location of mouse
136 | which - which mouse button (1: left, 2:right, 3:middle)
137 |
138 | mouseMove(activity)
139 | Mouse Movement, called any time the mouse moves
140 |
141 | {action='mouseMove', x, y};
142 | x - horizontal location of mouse
143 | y - vertical location of mouse
144 |
145 |
146 | Installation
147 | ------------
148 |
149 | remotesvg relies on turbo lua (https://turbo.readthedocs.org/en/latest/) as a web server. You need to first install that before trying to run
150 | your application:
151 |
152 | luarocks install turbo
153 |
154 | Then, assuming you're sitting in the 'examples' directory of remotesvg,
155 | you can simply execute your file:
156 |
157 | luajit myapp.lua
158 |
159 |
--------------------------------------------------------------------------------
/remotesvg/viewsvg_htm.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | This brief html is the most minimal necessary to display
3 | svg graphics oming from the server. There is no interaction
4 | back to the server, other than whatever the svg itself sets up.
5 | --]]
6 | return [[
7 |
8 |
9 |
10 | SVG View
11 |
12 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | ]]
137 |
--------------------------------------------------------------------------------
/remotesvg/SVGXmlParser.lua:
--------------------------------------------------------------------------------
1 | -- Simple XML parser
2 | local ffi = require("ffi")
3 |
4 | local ctypes = require("remotesvg.ctypes")
5 |
6 | local isspace = ctypes.isspace;
7 |
8 | local NSVG_XML_TAG = 1;
9 | local NSVG_XML_CONTENT = 2;
10 | local NSVG_XML_MAX_ATTRIBS = 256;
11 |
12 | local function parseContent(s, markoffset, endoffset, contentCb, ud)
13 | -- Trim start white spaces
14 | local soffset = markoffset;
15 |
16 | -- skip leading whitespace
17 | while (soffset < endoffset and isspace(s[soffset])) do
18 | soffset = soffset + 1;
19 | end
20 |
21 | if soffset == endoffset then
22 | return nil;
23 | end
24 |
25 | if (contentCb) then
26 | contentCb(ud, ffi.string(s+soffset, endoffset-soffset));
27 | end
28 | end
29 |
30 |
31 | -- void (startelCb)(any ud, string name, table attr)
32 | -- void (endelCb)(any ud, string name)
33 |
34 | -- void * ud
35 | -- Some user supplied data that just gets passed along
36 |
37 | local function parseElement(s, startoffset, endoffset, startelCb, endelCb, ud)
38 | local attr = {};
39 | local start = false;
40 | local ending = false;
41 | local quote = nil;
42 | local soffset = startoffset;
43 |
44 | -- Skip white space after the '<'
45 | while (soffset < endoffset and isspace(s[soffset])) do
46 | soffset = soffset + 1;
47 | end
48 |
49 | -- Check if the tag is end tag
50 | if s[soffset] == string.byte('/') then
51 | soffset = soffset + 1;
52 | ending = true;
53 | else
54 | start = true;
55 | end
56 |
57 | -- Skip comments, data and preprocessor stuff.
58 | if soffset == endoffset or s[soffset] == string.byte('?') or s[soffset] == string.byte('!') then
59 | return;
60 | end
61 |
62 |
63 | -- Get tag name
64 | local nameoffset = soffset;
65 | local nameendoffset = nameoffset;
66 |
67 | while (soffset < endoffset) and (not isspace(s[soffset])) do
68 | soffset = soffset + 1;
69 | end
70 |
71 | nameendoffset = soffset;
72 | if (soffset < endoffset) then
73 | soffset = soffset + 1;
74 | end
75 |
76 | -- turn it into a lua string
77 | local namelen = nameendoffset - nameoffset;
78 | --print("TAG name len: ", nameoffset, nameendoffset, namelen)
79 | local name = ffi.string(s+nameoffset, namelen)
80 |
81 | -- Get attribs
82 | while (not ending and (soffset < endoffset) ) do
83 | -- Skip white space before the attrib name
84 | while (soffset < endoffset and isspace(s[soffset])) do
85 | soffset = soffset + 1;
86 | end
87 |
88 | if (soffset == endoffset) then
89 | break;
90 | end
91 |
92 | if s[soffset] == string.byte('/') then
93 | ending = true;
94 | break;
95 | end
96 |
97 | local attrnamemark = soffset;
98 | local attrnameend = soffset;
99 |
100 | -- Find end of the attrib name.
101 | while soffset < endoffset and not isspace(s[soffset]) and s[soffset] ~= string.byte('=') do
102 | soffset = soffset + 1;
103 | end
104 |
105 | if (soffset < endoffset) then
106 | attrnameend = soffset;
107 | soffset = soffset + 1;
108 | end
109 |
110 | -- Skip until the beginning of the value.
111 | while soffset < endoffset and s[soffset] ~= string.byte('\"') and s[soffset] ~= string.byte('\'') do
112 | soffset = soffset + 1;
113 | end
114 |
115 | if (s[soffset] == 0) then
116 | break;
117 | end
118 |
119 | quote = s[soffset];
120 | soffset = soffset + 1;
121 |
122 | -- Store value and find the end of it.
123 | local attrvaluemark = soffset;
124 | local attrvaluemarkend = soffset;
125 |
126 | while (soffset < endoffset and s[soffset] ~= quote) do
127 | soffset = soffset + 1;
128 | end
129 |
130 | attrvaluemarkend = soffset;
131 | if (soffset < endoffset) then
132 | soffset = soffset + 1;
133 | end
134 |
135 | -- store attribute as a key value pair
136 | attr[ffi.string(s+attrnamemark, attrnameend-attrnamemark)] = ffi.string(s+attrvaluemark, attrvaluemarkend-attrvaluemark);
137 | end
138 |
139 | -- Call callbacks.
140 | if (start and startelCb) then
141 | startelCb(ud, name, attr);
142 | end
143 |
144 | if (ending and endelCb) then
145 | endelCb(ud, name);
146 | end
147 | end
148 |
149 |
150 | local function parseXML(input, startelCb, endelCb, contentCb, ud)
151 | local s = ffi.cast("const char *", input);
152 | local soffset = 0;
153 | local markoffset = soffset;
154 | local markoffsetend = soffset;
155 |
156 | local state = NSVG_XML_CONTENT;
157 |
158 | while s[soffset] ~= 0 do
159 | if (s[soffset] == string.byte('<')) and (state == NSVG_XML_CONTENT) then
160 | -- Start of a tag
161 | markoffsetend = soffset;
162 | soffset = soffset + 1;
163 |
164 | parseContent(s, markoffset, markoffsetend, contentCb, ud);
165 | markoffset = soffset;
166 | markoffsetend = markoffset;
167 |
168 | state = NSVG_XML_TAG;
169 | elseif (s[soffset] == string.byte('>')) and (state == NSVG_XML_TAG) then
170 | -- Start of a content or new tag.
171 | markoffsetend = soffset;
172 |
173 | soffset = soffset + 1;
174 |
175 | parseElement(s, markoffset, markoffsetend, startelCb, endelCb, ud);
176 | markoffset = soffset;
177 | markoffsetend = markoffset;
178 |
179 | state = NSVG_XML_CONTENT;
180 | else
181 | soffset = soffset + 1;
182 | end
183 | end
184 |
185 | return true;
186 | end
187 |
188 |
189 | return {
190 | parseXML = parseXML;
191 | }
--------------------------------------------------------------------------------
/testy/test_literal.lua:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env luajit
2 |
3 | package.path = "../?.lua;"..package.path;
4 |
5 | --[[
6 | a literal chunk of text can be injected into the SVG directly.
7 | in the body elements. This is useful in a couple of cases.
8 |
9 | You might want to include an XML comment block. There
10 | is no comment block element, so you enclose it in
11 | a string literal.
12 |
13 | You may have some css style sheet embedded. There
14 | is no CSS specific element type, so you just include
15 | it with a literal.
16 |
17 | You might be converting a large chunk of svg by hand
18 | and you haven't tackled a section yet. you can just
19 | include that section in a literal block, until you
20 | get around to dealing with it.
21 |
22 | The disadvantage of literal blocks is that the content
23 | is not parsed, so it's just an opaque chunk of text.
24 | All the other elements give you access to the various
25 | attributes as lua values, that can be queried and
26 | modified.
27 | --]]
28 |
29 | require("remotesvg.SVGElements")()
30 | local FileStream = require("remotesvg.filestream")
31 | local SVGStream = require("remotesvg.SVGStream")
32 |
33 |
34 | local ImageStream = SVGStream(FileStream.open("test_lineargradient.svg"))
35 |
36 |
37 | local doc = svg{
38 | version="1.2",
39 | baseProfile="tiny",
40 | ['xml:id']="svg-root",
41 | ['xmlns:xlink']="http://www.w3.org/1999/xlink",
42 | ['xmlns:xe']="http://www.w3.org/2001/xml-events",
43 | width="100%",
44 | height="100%",
45 | viewBox="0 0 480 360",
46 |
47 | [[
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | ]];
56 |
57 | [[
58 |
62 |
63 |
Test different ways of defining a motion path.
64 |
65 | An animation moves a triangle along a path. Reference rectangles, lines and text are provided to help show what
66 | the correct behavior is. The red text shows the way that the motion path is specified.
67 |
68 |
This animation uses the 'values' attribute to define the motion path, with a linear calcMode.
69 |
70 |
71 | ]];
72 |
73 | [[
74 | $RCSfile: animate-elem-05-t.svg,v $
75 | ]];
76 |
77 | defs{
78 | [[
79 |
80 |
81 |
82 |
83 |
84 | ]];
85 | };
86 |
87 |
88 | g {
89 | ['xml:id']="test-body-content",
90 | ['font-family']="SVGFreeSansASCII,sans-serif",
91 | ['font-size']="18",
92 |
93 | g {
94 | ['font-family']="Arial",
95 | ['font-size']="36",
96 |
97 | [[
98 | Test a motion path
99 | 'values' attribute.
100 |
101 |
102 | 0 sec.
103 |
104 | 3+
105 |
106 | 6+
107 | ]];
108 |
109 | path {
110 | d="M-30,0 L0,-60 L30,0 z",
111 | fill="blue",
112 | stroke="red",
113 | ['stroke-width']=6,
114 |
115 | animateMotion {values="90,258;240,180;390,180", begin="0s", dur="6s", calcMode="linear", fill="freeze"}
116 | }
117 | }
118 | };
119 |
120 |
121 | g {
122 | ['font-family']="SVGFreeSansASCII,sans-serif",
123 | ['font-size']="32",
124 | text ({['xml:id']="revision", x="10", y="340", stroke="none", fill="black"}, "$Revision: 1.9 $"),
125 | };
126 |
127 | [[
128 |
129 |
130 |
135 | ]]
136 | }
137 |
138 | doc:write(ImageStream)
139 |
140 |
--------------------------------------------------------------------------------
/remotesvg/SVGInteractor.lua:
--------------------------------------------------------------------------------
1 | _G.TURBO_SSL = true -- SSL must be enabled for WebSocket support!
2 |
3 | local ffi = require("ffi")
4 | local turbo = require("turbo")
5 |
6 | local MemoryStream = require("remotesvg.memorystream")
7 | local SVGStream = require("remotesvg.SVGStream")
8 |
9 | --[[
10 | Application Variables
11 | --]]
12 | local serviceport = tonumber(arg[1]) or 8080
13 | local ioinstance = nil;
14 | local FrameInterval = 1000;
15 | local FrameIntervalRef = nil;
16 |
17 |
18 | local PageInterval = 2000;
19 | local ImageBitCount = 32;
20 | local ScreenWidth = nil;
21 | local ScreenHeight = nil;
22 | local captureWidth = nil;
23 | local captureHeight = nil;
24 |
25 | local mstream = nil;
26 | local ImageStream = nil;
27 |
28 |
29 | --[[
30 | pageInterval
31 |
32 | Set the number of milliseconds between page loads. This
33 | value is embedded in the .htm file that the client downloads
34 | initially, so it should be called before 'run()'.
35 | --]]
36 | function pageInterval(newInterval)
37 | PageInterval = newInterval or 1000;
38 | end
39 |
40 | --[[
41 | frameInterval
42 |
43 | Determines how frequently your 'frame()' function is called,
44 | if you choose to implement one.
45 |
46 | From within your application, call 'frameInterval(frameTime)'
47 | to specify the interval between frames. You can call this
48 | at any time.
49 | --]]
50 | function frameInterval(newInterval)
51 |
52 | -- cancel the last interval
53 | if ioinstance then
54 | ioinstance:clear_interval(FrameIntervalRef);
55 | end
56 |
57 | FrameInterval = newInterval;
58 |
59 | if ioinstance then
60 | FrameIntervalRef = ioinstance:set_interval(FrameInterval, onInterval, ioinstance)
61 | end
62 | end
63 |
64 |
65 | --[[
66 | size
67 |
68 | Called initially by your application to get a handle
69 | on the stream your image is written into.
70 |
71 | params can contain 'width' and 'height' as hints as to
72 | how large the image is going to be. This will also be
73 | embedded in the .htm file the client loads.
74 | --]]
75 | function size(params)
76 | params = params or {}
77 |
78 | ScreenWidth = params.width or 0;
79 | ScreenHeight = params.height or 0;
80 |
81 | captureWidth = ScreenWidth;
82 | captureHeight = ScreenHeight;
83 |
84 | ImageWidth = captureWidth * 1.0;
85 | ImageHeight = captureHeight * 1.0;
86 |
87 |
88 | -- 1Mb should be good enough for most
89 | -- images
90 | mstream = MemoryStream:new(1024*1024);
91 | ImageStream = SVGStream(mstream);
92 |
93 | return ImageStream;
94 | end
95 |
96 |
97 | local DefaultHandler = class("DefaultHandler", turbo.web.RequestHandler)
98 | function DefaultHandler:get(...)
99 | self:write("Example Handler: Hello world!")
100 | end
101 |
102 |
103 | -- Request handler for /grab%.svg(.*)$
104 | local GrabHandler = class("GrabHandler", turbo.web.RequestHandler)
105 |
106 | function GrabHandler:get(...)
107 | --turbo.log.devel("ScreenHandler: "..self.request.host)
108 | local bytesWritten = mstream.BytesWritten;
109 |
110 | self:set_status(200)
111 | self:add_header("Content-Type", "image/svg+xml")
112 | self:add_header("Content-Length", tostring(bytesWritten))
113 | --self:add_header("Content-Encoding", "gzip")
114 | self:add_header("Connection", "Keep-Alive")
115 |
116 | --print("=== SVG - BEGIN ===")
117 | local str = ffi.string(mstream.Buffer, bytesWritten)
118 | -- print(str);
119 |
120 | self:write(str);
121 | self:flush();
122 | end
123 |
124 | -- Default request handler
125 | local StartupHandler = class("StartupHandler", turbo.web.RequestHandler)
126 |
127 | local startupContent = nil;
128 |
129 | local function loadStartupContent(self)
130 |
131 | -- load the file into memory
132 | local content = require("remotesvg.viewsvg_htm")
133 |
134 | -- perform the substitution of values
135 | -- assume content looks like this:
136 | -- :
137 | local subs = {
138 | ["authority"] = self.request.host;
139 | --["hostip"] = net:GetLocalAddress(),
140 | --["httpbase"] = self:get_header("x-rmt-http-url-base"),
141 | --["websocketbase"] = self:get_header("x-rmt-ws-url-base"),
142 | ["websocketbase"] = "ws://"..self.request.host..'/',
143 | ["serviceport"] = serviceport,
144 |
145 | ["pageinterval"] = PageInterval,
146 | ["capturewidth"] = captureWidth,
147 | ["captureheight"] = captureHeight,
148 | ["imagewidth"] = ImageWidth,
149 | ["imageheight"] = ImageHeight,
150 | ["screenwidth"] = ScreenWidth,
151 | ["screenheight"] = ScreenHeight,
152 | }
153 |
154 | startupContent = string.gsub(content, "%<%?(%a+)%?%>", subs)
155 |
156 | return startupContent
157 | end
158 |
159 | function StartupHandler:get(...)
160 |
161 | if not startupContent then
162 | loadStartupContent(self)
163 | end
164 |
165 | -- send the content back to the requester
166 | self:set_status(200)
167 | self:add_header("Content-Type", "text/html")
168 | self:add_header("Connection", "Keep-Alive")
169 | self:write(startupContent);
170 | self:flush();
171 |
172 | return true
173 | end
174 |
175 | local WSExHandler = class("WSExHandler", turbo.websocket.WebSocketHandler)
176 |
177 | function WSExHandler:on_message(msg)
178 | --print("WSExHandler:on_message: ", msg)
179 | -- assume the msg is a lua string, so parse it
180 | -- BUGBUG - This should be changed, as it's an easy injection vector
181 | local f = loadstring("return "..msg)
182 | if not f then return end
183 |
184 | local tbl = f();
185 |
186 | if type(tbl) ~= "table" then
187 | return;
188 | end
189 |
190 | self:handleIOActivity(tbl)
191 | end
192 |
193 |
194 | function WSExHandler:handleIOActivity(activity)
195 | --print("IO: ", activity.action)
196 |
197 | local handler = _G[activity.action];
198 |
199 | if not handler then return end
200 |
201 | handler(activity)
202 | end
203 |
204 | --[[
205 | There are two ways to handle iteration
206 | Either by idle time (every time through event loop)
207 | or by time interval.
208 |
209 | You can change which way is utilized by using the
210 | appropriate mechanism in the run() function.
211 |
212 | You can even setup to use both.
213 | --]]
214 | local function onLoop(ioinstance)
215 | if loop then
216 | loop()
217 | end
218 | ioinstance:add_callback(onLoop, ioinstance)
219 | end
220 |
221 | local function onInterval(ioinstance)
222 | if frame then
223 | frame()
224 |
225 | -- create a fresh new memorystream
226 | -- dump the older one
227 | end
228 | end
229 |
230 |
231 | -- uncomment if you want to suppress success messages
232 | --turbo.log.categories.success = false;
233 |
234 |
235 | local app = nil;
236 |
237 | function run()
238 | app = turbo.web.Application({
239 | {"/(jquery%.js)", turbo.web.StaticFileHandler, "./jquery.js"},
240 | {"/(favicon%.ico)", turbo.web.StaticFileHandler, "./favicon.ico"},
241 | {"/grab%.svg(.*)$", GrabHandler},
242 | {"/screen", StartupHandler},
243 | {"/ws/uiosocket", WSExHandler}
244 | })
245 |
246 | app:listen(serviceport)
247 | ioinstance = turbo.ioloop.instance()
248 |
249 | FrameIntervalRef = ioinstance:set_interval(FrameInterval, onInterval, ioinstance)
250 | --ioinstance:add_callback(onLoop, ioinstance)
251 |
252 | ioinstance:start()
253 | end
254 |
--------------------------------------------------------------------------------
/remotesvg/colors.lua:
--------------------------------------------------------------------------------
1 | local bit = require("bit")
2 | local band, bor, lshift, rshift = bit.band, bit.bor, bit.lshift, bit.rshift
3 | local maths = require("remotesvg.maths");
4 | local clamp = maths.clamp
5 |
6 | -- The actual layout in memory is:
7 | -- Little Endian - BGRA
8 | -- Big Endian - ARGB
9 | -- For things that actually depend on this memory ordering
10 | -- they should take care of this difference
11 | local function RGBA(r, g, b, a)
12 | a = a or 255;
13 | local num = tonumber(bor(lshift(tonumber(a),24), lshift(tonumber(r),16), lshift(tonumber(g),8), tonumber(b)))
14 |
15 | return num;
16 | end
17 |
18 | local function colorComponents(c)
19 | local b = band(c, 0xff);
20 | local g = band(rshift(c,8), 0xff);
21 | local r = band(rshift(c,16), 0xff);
22 | local a = band(rshift(c,24), 0xff);
23 |
24 | return r, g, b, a
25 | end
26 |
27 | --[[
28 | c - unsigned int
29 | a color value, including r,g,b,a components
30 | u - float
31 | should be a value between 0.0 and 1.0
32 | --]]
33 | local function applyOpacity(c, u)
34 |
35 | local iu = clamp(u, 0.0, 1.0) * 256.0;
36 | local r, g, b, a = colorComponents(c);
37 | a = rshift(a*iu, 8);
38 |
39 | return RGBA(r, g, b, a);
40 | end
41 |
42 | --[[
43 | linear interpolation of value from color c0 to
44 | color c1
45 | --]]
46 | local function lerpRGBA(c0, c1, u)
47 |
48 | local iu = clamp(u, 0.0, 1.0) * 256.0;
49 | local c0r, c0g, c0b, c0a = colorComponents(c0);
50 | local c1r, c1g, c1b, c1a = colorComponents(c1);
51 |
52 | local r = rshift(c0r*(256-iu) + (c1r*iu), 8);
53 | local g = rshift(c0g*(256-iu) + (c1g*iu), 8);
54 | local b = rshift(c0b*(256-iu) + (c1b*iu), 8);
55 | local a = rshift(c0a*(256-iu) + (c1a*iu), 8);
56 |
57 | return RGBA(r, g, b, a);
58 | end
59 |
60 | --[[
61 | local function SVG_RGB(r, g, b)
62 | return bor(lshift(b,16), lshift(g,8), r)
63 | end
64 | --]]
65 |
66 | local exports = {
67 | applyOpacity = applyOpacity;
68 | colorComponents = colorComponents;
69 | lerpRGBA = lerpRGBA;
70 | RGBA = RGBA;
71 |
72 | white = RGBA(255, 255, 255);
73 | black = RGBA(0,0,0);
74 | blue = RGBA(0,0,255);
75 | green = RGBA(0,255,0);
76 | red = RGBA(255, 0, 0);
77 |
78 | yellow = RGBA(255, 255, 0);
79 | darkyellow = RGBA(127, 127, 0);
80 |
81 | -- grays
82 | lightgray = RGBA(235, 235, 235);
83 |
84 | -- SVG color values
85 | svg = {
86 | ["red"] = RGBA(255, 0, 0) ;
87 | ["green"] = RGBA( 0, 128, 0) ;
88 | ["blue"] = RGBA( 0, 0, 255) ;
89 | ["yellow"] = RGBA(255, 255, 0) ;
90 | ["cyan"] = RGBA( 0, 255, 255) ;
91 | ["magenta"] = RGBA(255, 0, 255) ;
92 | ["black"] = RGBA( 0, 0, 0) ;
93 | ["grey"] = RGBA(128, 128, 128) ;
94 | ["gray"] = RGBA(128, 128, 128) ;
95 | ["white"] = RGBA(255, 255, 255) ;
96 |
97 | ["aliceblue"] = RGBA(240, 248, 255) ;
98 | ["antiquewhite"] = RGBA(250, 235, 215) ;
99 | ["aqua"] = RGBA( 0, 255, 255) ;
100 | ["aquamarine"] = RGBA(127, 255, 212) ;
101 | ["azure"] = RGBA(240, 255, 255) ;
102 | ["beige"] = RGBA(245, 245, 220) ;
103 | ["bisque"] = RGBA(255, 228, 196) ;
104 | ["blanchedalmond"] = RGBA(255, 235, 205) ;
105 | ["blueviolet"] = RGBA(138, 43, 226) ;
106 | ["brown"] = RGBA(165, 42, 42) ;
107 | ["burlywood"] = RGBA(222, 184, 135) ;
108 | ["cadetblue"] = RGBA( 95, 158, 160) ;
109 | ["chartreuse"] = RGBA(127, 255, 0) ;
110 | ["chocolate"] = RGBA(210, 105, 30) ;
111 | ["coral"] = RGBA(255, 127, 80) ;
112 | ["cornflowerblue"] = RGBA(100, 149, 237) ;
113 | ["cornsilk"] = RGBA(255, 248, 220) ;
114 | ["crimson"] = RGBA(220, 20, 60) ;
115 | ["darkblue"] = RGBA( 0, 0, 139) ;
116 | ["darkcyan"] = RGBA( 0, 139, 139) ;
117 | ["darkgoldenrod"] = RGBA(184, 134, 11) ;
118 | ["darkgray"] = RGBA(169, 169, 169) ;
119 | ["darkgreen"] = RGBA( 0, 100, 0) ;
120 | ["darkgrey"] = RGBA(169, 169, 169) ;
121 | ["darkkhaki"] = RGBA(189, 183, 107) ;
122 | ["darkmagenta"] = RGBA(139, 0, 139) ;
123 | ["darkolivegreen"] = RGBA( 85, 107, 47) ;
124 | ["darkorange"] = RGBA(255, 140, 0) ;
125 | ["darkorchid"] = RGBA(153, 50, 204) ;
126 | ["darkred"] = RGBA(139, 0, 0) ;
127 | ["darksalmon"] = RGBA(233, 150, 122) ;
128 | ["darkseagreen"] = RGBA(143, 188, 143) ;
129 | ["darkslateblue"] = RGBA( 72, 61, 139) ;
130 | ["darkslategray"] = RGBA( 47, 79, 79) ;
131 | ["darkslategrey"] = RGBA( 47, 79, 79) ;
132 | ["darkturquoise"] = RGBA( 0, 206, 209) ;
133 | ["darkviolet"] = RGBA(148, 0, 211) ;
134 | ["deeppink"] = RGBA(255, 20, 147) ;
135 | ["deepskyblue"] = RGBA( 0, 191, 255) ;
136 | ["dimgray"] = RGBA(105, 105, 105) ;
137 | ["dimgrey"] = RGBA(105, 105, 105) ;
138 | ["dodgerblue"] = RGBA( 30, 144, 255) ;
139 | ["firebrick"] = RGBA(178, 34, 34) ;
140 | ["floralwhite"] = RGBA(255, 250, 240) ;
141 | ["forestgreen"] = RGBA( 34, 139, 34) ;
142 | ["fuchsia"] = RGBA(255, 0, 255) ;
143 | ["gainsboro"] = RGBA(220, 220, 220) ;
144 | ["ghostwhite"] = RGBA(248, 248, 255) ;
145 | ["gold"] = RGBA(255, 215, 0) ;
146 | ["goldenrod"] = RGBA(218, 165, 32) ;
147 | ["greenyellow"] = RGBA(173, 255, 47) ;
148 | ["honeydew"] = RGBA(240, 255, 240) ;
149 | ["hotpink"] = RGBA(255, 105, 180) ;
150 | ["indianred"] = RGBA(205, 92, 92) ;
151 | ["indigo"] = RGBA( 75, 0, 130) ;
152 | ["ivory"] = RGBA(255, 255, 240) ;
153 | ["khaki"] = RGBA(240, 230, 140) ;
154 | ["lavender"] = RGBA(230, 230, 250) ;
155 | ["lavenderblush"] = RGBA(255, 240, 245) ;
156 | ["lawngreen"] = RGBA(124, 252, 0) ;
157 | ["lemonchiffon"] = RGBA(255, 250, 205) ;
158 | ["lightblue"] = RGBA(173, 216, 230) ;
159 | ["lightcoral"] = RGBA(240, 128, 128) ;
160 | ["lightcyan"] = RGBA(224, 255, 255) ;
161 | ["lightgoldenrodyellow"] = RGBA(250, 250, 210) ;
162 | ["lightgray"] = RGBA(211, 211, 211) ;
163 | ["lightgreen"] = RGBA(144, 238, 144) ;
164 | ["lightgrey"] = RGBA(211, 211, 211) ;
165 | ["lightpink"] = RGBA(255, 182, 193) ;
166 | ["lightsalmon"] = RGBA(255, 160, 122) ;
167 | ["lightseagreen"] = RGBA( 32, 178, 170) ;
168 | ["lightskyblue"] = RGBA(135, 206, 250) ;
169 | ["lightslategray"] = RGBA(119, 136, 153) ;
170 | ["lightslategrey"] = RGBA(119, 136, 153) ;
171 | ["lightsteelblue"] = RGBA(176, 196, 222) ;
172 | ["lightyellow"] = RGBA(255, 255, 224) ;
173 | ["lime"] = RGBA( 0, 255, 0) ;
174 | ["limegreen"] = RGBA( 50, 205, 50) ;
175 | ["linen"] = RGBA(250, 240, 230) ;
176 | ["maroon"] = RGBA(128, 0, 0) ;
177 | ["mediumaquamarine"] = RGBA(102, 205, 170) ;
178 | ["mediumblue"] = RGBA( 0, 0, 205) ;
179 | ["mediumorchid"] = RGBA(186, 85, 211) ;
180 | ["mediumpurple"] = RGBA(147, 112, 219) ;
181 | ["mediumseagreen"] = RGBA( 60, 179, 113) ;
182 | ["mediumslateblue"] = RGBA(123, 104, 238) ;
183 | ["mediumspringgreen"] = RGBA( 0, 250, 154) ;
184 | ["mediumturquoise"] = RGBA( 72, 209, 204) ;
185 | ["mediumvioletred"] = RGBA(199, 21, 133) ;
186 | ["midnightblue"] = RGBA( 25, 25, 112) ;
187 | ["mintcream"] = RGBA(245, 255, 250) ;
188 | ["mistyrose"] = RGBA(255, 228, 225) ;
189 | ["moccasin"] = RGBA(255, 228, 181) ;
190 | ["navajowhite"] = RGBA(255, 222, 173) ;
191 | ["navy"] = RGBA( 0, 0, 128) ;
192 | ["oldlace"] = RGBA(253, 245, 230) ;
193 | ["olive"] = RGBA(128, 128, 0) ;
194 | ["olivedrab"] = RGBA(107, 142, 35) ;
195 | ["orange"] = RGBA(255, 165, 0) ;
196 | ["orangered"] = RGBA(255, 69, 0) ;
197 | ["orchid"] = RGBA(218, 112, 214) ;
198 | ["palegoldenrod"] = RGBA(238, 232, 170) ;
199 | ["palegreen"] = RGBA(152, 251, 152) ;
200 | ["paleturquoise"] = RGBA(175, 238, 238) ;
201 | ["palevioletred"] = RGBA(219, 112, 147) ;
202 | ["papayawhip"] = RGBA(255, 239, 213) ;
203 | ["peachpuff"] = RGBA(255, 218, 185) ;
204 | ["peru"] = RGBA(205, 133, 63) ;
205 | ["pink"] = RGBA(255, 192, 203) ;
206 | ["plum"] = RGBA(221, 160, 221) ;
207 | ["powderblue"] = RGBA(176, 224, 230) ;
208 | ["purple"] = RGBA(128, 0, 128) ;
209 | ["rosybrown"] = RGBA(188, 143, 143) ;
210 | ["royalblue"] = RGBA( 65, 105, 225) ;
211 | ["saddlebrown"] = RGBA(139, 69, 19) ;
212 | ["salmon"] = RGBA(250, 128, 114) ;
213 | ["sandybrown"] = RGBA(244, 164, 96) ;
214 | ["seagreen"] = RGBA( 46, 139, 87) ;
215 | ["seashell"] = RGBA(255, 245, 238) ;
216 | ["sienna"] = RGBA(160, 82, 45) ;
217 | ["silver"] = RGBA(192, 192, 192) ;
218 | ["skyblue"] = RGBA(135, 206, 235) ;
219 | ["slateblue"] = RGBA(106, 90, 205) ;
220 | ["slategray"] = RGBA(112, 128, 144) ;
221 | ["slategrey"] = RGBA(112, 128, 144) ;
222 | ["snow"] = RGBA(255, 250, 250) ;
223 | ["springgreen"] = RGBA( 0, 255, 127) ;
224 | ["steelblue"] = RGBA( 70, 130, 180) ;
225 | ["tan"] = RGBA(210, 180, 140) ;
226 | ["teal"] = RGBA( 0, 128, 128) ;
227 | ["thistle"] = RGBA(216, 191, 216) ;
228 | ["tomato"] = RGBA(255, 99, 71) ;
229 | ["turquoise"] = RGBA( 64, 224, 208) ;
230 | ["violet"] = RGBA(238, 130, 238) ;
231 | ["wheat"] = RGBA(245, 222, 179) ;
232 | ["whitesmoke"] = RGBA(245, 245, 245) ;
233 | ["yellowgreen"] = RGBA(154, 205, 50) ;
234 | }; -- svg
235 | }
236 |
237 | return exports
238 |
--------------------------------------------------------------------------------
/remotesvg/memorystream.lua:
--------------------------------------------------------------------------------
1 |
2 | local ffi = require "ffi"
3 | local stream = require "remotesvg.stream"
4 |
5 |
6 | local MemoryStream = {}
7 | setmetatable(MemoryStream, {
8 | __call = function(self, ...)
9 | return self:new(...)
10 | end,
11 | })
12 |
13 | local MemoryStream_mt = {
14 | __index = MemoryStream;
15 | }
16 |
17 | function MemoryStream.init(self, buff, bufflen, byteswritten)
18 | if not buff then return nil end
19 | if not bufflen then return nil end
20 |
21 | byteswritten = byteswritten or 0
22 |
23 | local obj = {
24 | Length = bufflen,
25 | Buffer = buff,
26 | Position = 0,
27 | BytesWritten = byteswritten,
28 | }
29 |
30 | setmetatable(obj, MemoryStream_mt)
31 |
32 | return obj
33 | end
34 |
35 | -- MemoryStream constructors
36 | --
37 | -- MemoryStream:new(8192) -- Create a buffer with 8192 bytes
38 | -- MemoryStream:new("string" [, len]) -- create a buffer atop some lua string
39 | -- MemoryStream:new(cdata, len) -- create a buffer atop some cdata structure with length
40 |
41 | function MemoryStream.new(self, ...)
42 | local buff = nil;
43 | local bufflen = nil;
44 | local byteswritten = 0;
45 |
46 | local nargs = select('#', ...);
47 |
48 | if nargs == 1 then
49 | if type(select(1, ...)=="number") then
50 | bufflen = select(1,...);
51 | buff = ffi.new("uint8_t[?]", bufflen)
52 | byteswritten = 0
53 | elseif type(select(1,...)=="string") then
54 | buff = select(1, ...);
55 | bufflen = #buff;
56 | byteswritten = bufflen;
57 | end
58 | elseif nargs == 2 then
59 | if type(select(1, ...))=="string" then
60 | if type(select(2,...)) ~= "number" then
61 | return nil;
62 | end
63 |
64 | buff = select(1,...);
65 | bufflen = #buff;
66 | byteswritten = bufflen;
67 | elseif type(select(1,...))=="ctype" then
68 | buff = select(1,...);
69 | bufflen = ffi.sizeof(buff);
70 | byteswritten = bufflen;
71 | end
72 | end
73 |
74 |
75 | return self:init(buff, bufflen, byteswritten);
76 | end
77 |
78 |
79 | function MemoryStream:reset()
80 | self.Position = 0
81 | self.BytesWritten = 0
82 | end
83 |
84 | function MemoryStream:length()
85 | return self.Length
86 | end
87 |
88 | function MemoryStream:position(pos, origin)
89 | if pos ~= nil then
90 | return self:seek(pos, origin);
91 | end
92 |
93 | return self.Position
94 | end
95 |
96 | function MemoryStream:remaining()
97 | return self.Length - self.Position
98 | end
99 |
100 | function MemoryStream:bytesReadyToBeRead()
101 | return self.BytesWritten - self.Position
102 | end
103 |
104 | function MemoryStream:canRead()
105 | return self:bytesReadyToBeRead() > 0
106 | end
107 |
108 | function MemoryStream:seek(pos, origin)
109 | origin = origin or stream.SEEK_SET
110 |
111 | if origin == stream.SEEK_CUR then
112 | local newpos = self.Position + pos
113 | if newpos >= 0 and newpos < self.Length then
114 | self.Position = newpos
115 | end
116 | elseif origin == stream.SEEK_SET then
117 | if pos >= 0 and pos < self.Length then
118 | self.Position = pos;
119 | end
120 | elseif origin == stream.SEEK_END then
121 | local newpos = self.Length-1 + pos
122 | if newpos >= 0 and newpos < self.Length then
123 | self.Position = newpos
124 | end
125 | end
126 |
127 | return self.Position
128 | end
129 |
130 | --[[
131 | Reading interface
132 | --]]
133 | -- The Bytes() function acts as an iterator on bytes
134 | -- from the stream.
135 | function MemoryStream:bytes(maxbytes)
136 | local buffptr = ffi.cast("const uint8_t *", self.Buffer);
137 | local bytesleft = maxbytes or math.huge
138 | local pos = -1
139 |
140 | local function closure()
141 | --print("-- REMAINING: ", bytesleft)
142 | -- if we've read the maximum nuber of bytes
143 | -- then just return nil to indicate finished
144 | if bytesleft == 0 then return end
145 |
146 | pos = pos + 1
147 |
148 | -- We've reached the end of the stream
149 | if pos >= self.Position then
150 | return nil
151 | end
152 | bytesleft = bytesleft - 1
153 |
154 | return buffptr[pos]
155 | end
156 |
157 | return closure
158 | end
159 |
160 | function MemoryStream:readByte()
161 | local buffptr = ffi.cast("const uint8_t *", self.Buffer);
162 |
163 | local pos = self.Position
164 | if pos < self.BytesWritten then
165 | self.Position = pos + 1
166 | return buffptr[pos];
167 | end
168 |
169 | return nil, "eof"
170 | end
171 |
172 | function MemoryStream:readBytes(buff, count, offset)
173 | offset = offset or 0
174 |
175 | local pos = self.Position
176 | local remaining = self:remaining()
177 | local src = ffi.cast("const uint8_t *", self.Buffer)+pos
178 | local dst = ffi.cast("uint8_t *", buff)+offset
179 |
180 | local maxbytes = math.min(count, remaining)
181 | if maxbytes < 1 then
182 | return nil, "eof"
183 | end
184 |
185 | ffi.copy(dst, src, maxbytes)
186 |
187 | self.Position = pos + maxbytes
188 |
189 | return maxbytes
190 | end
191 |
192 | function MemoryStream:readString(count)
193 | local pos = self.Position
194 | local remaining = self.Length - pos
195 |
196 | local maxbytes = math.min(count, remaining)
197 | if maxbytes < 1 then return nil end
198 |
199 |
200 | local src = ffi.cast("const uint8_t *", self.Buffer)+pos
201 |
202 | -- advance the stream position
203 | self.Position = pos + maxbytes
204 |
205 | return ffi.string(src, maxbytes)
206 | end
207 |
208 | -- Read characters from a stream until the specified
209 | -- ending is found, or until the stream runs out of bytes
210 | local CR = string.byte("\r")
211 | local LF = string.byte("\n")
212 |
213 | function MemoryStream:readLine(maxbytes)
214 | --print("-- MemoryStream:ReadLine()");
215 |
216 | local readytoberead = self:bytesReadyToBeRead()
217 |
218 | maxbytes = maxbytes or readytoberead
219 |
220 | local maxlen = math.min(maxbytes, readytoberead)
221 | local buffptr = ffi.cast("uint8_t *", self.Buffer);
222 |
223 | local nchars = 0;
224 | local bytesconsumed = 0;
225 | local startptr = buffptr + self.Position
226 | local abyte
227 | local err
228 |
229 | --print("-- MemoryStream:ReadLine(), maxlen: ", maxlen);
230 |
231 | for n=1, maxlen do
232 | abyte, err = self:readByte()
233 | if not abyte then
234 | break
235 | end
236 |
237 | bytesconsumed = bytesconsumed + 1
238 |
239 | if abyte == LF then
240 | break
241 | elseif abyte ~= CR then
242 | nchars = nchars+1
243 | end
244 | end
245 |
246 | -- End of File, nothing consumed
247 | if bytesconsumed == 0 then
248 | return nil, "eof"
249 | end
250 |
251 | -- A blank line
252 | if nchars == 0 then
253 | return ''
254 | end
255 |
256 | -- an actual line of data
257 | return ffi.string(startptr, nchars);
258 | end
259 |
260 | --[[
261 | Writing interface
262 | --]]
263 |
264 | function MemoryStream:writeByte(byte)
265 | -- don't write a nil value
266 | -- a nil is not the same as a '0'
267 | if not byte then return end
268 |
269 | local pos = self.Position
270 | if pos < self.Length-1 then
271 | (ffi.cast("uint8_t *", self.Buffer)+pos)[0] = byte
272 |
273 | self.Position = pos + 1
274 | if self.Position > self.BytesWritten then
275 | self.BytesWritten = self.Position
276 | end
277 |
278 | return 1
279 | end
280 |
281 | return false
282 | end
283 |
284 | function MemoryStream:writeBytes(buff, count, offset)
285 | offset = offset or 0
286 | local pos = self.Position
287 | local size = self.Length
288 | local remaining = size - pos
289 | local maxbytes = math.min(remaining, count)
290 |
291 | if maxbytes <= 0
292 | then return 0
293 | end
294 |
295 | local dst = ffi.cast("uint8_t *", self.Buffer)+pos
296 | local src = ffi.cast("const uint8_t *", buff)+offset
297 |
298 | ffi.copy(dst, src, maxbytes);
299 |
300 |
301 | self.Position = pos + maxbytes;
302 | if self.Position > self.BytesWritten then
303 | self.BytesWritten = self.Position
304 | end
305 |
306 | return maxbytes;
307 | end
308 |
309 | function MemoryStream:writeString(str, count, offset)
310 | offset = offset or 0
311 | count = count or #str
312 |
313 | --print("-- MemoryStream:WriteString():", str);
314 |
315 | return self:writeBytes(str, count, offset)
316 | end
317 |
318 | --[[
319 | Write the specified number of bytes from the current
320 | stream into the specified stream.
321 |
322 | Start from the current position in the current stream
323 | --]]
324 |
325 | function MemoryStream:writeStream(stream, size)
326 | local count = 0
327 | local abyte = stream:readByte()
328 | while abyte and count < size do
329 | self:writeByte(abyte)
330 | count = count + 1
331 | abyte = stream:readByte()
332 | end
333 |
334 | return count
335 | end
336 |
337 | function MemoryStream:writeLine(line)
338 | local status, err
339 |
340 | if line then
341 | status, err = self:writeString(line)
342 | if err then
343 | return nil, err
344 | end
345 | end
346 |
347 | -- write the terminator
348 | status, err = self:writeString("\r\n");
349 |
350 | return status, err
351 | end
352 |
353 | --[[
354 | Moving big chunks around
355 | --]]
356 |
357 | function MemoryStream:copyTo(stream)
358 | -- copy from the beginning
359 | -- to the current position
360 | local remaining = self.BytesWritten
361 | local byteswritten = 0
362 |
363 | while (byteswritten < remaining) do
364 | byteswritten = byteswritten + stream:writeBytes(self.Buffer, self.Position, byteswritten)
365 | end
366 | end
367 |
368 |
369 |
370 | --[[
371 | Utility
372 | --]]
373 | function MemoryStream:toString()
374 | local len = self.Position
375 |
376 | if len > 0 then
377 | --print("Buffer: ", self.Buffer, len);
378 | local str = ffi.string(self.Buffer, len)
379 | return str;
380 | end
381 |
382 | return nil
383 | end
384 |
385 | return MemoryStream;
386 |
--------------------------------------------------------------------------------
/remotesvg/SVGElements.lua:
--------------------------------------------------------------------------------
1 |
2 | -- Attribute names that have the '-' or ':' characters in them
3 | -- https://www.w3.org/TR/SVG/attindex.html
4 | -- we want to allow the user to specify lua friendly names as
5 | -- table indices, without having to quote them. So, we have
6 | -- this alias table.
7 |
8 | local attrNameAlias = {
9 | accent_height = "accent-height",
10 | arabic_form = "arabic-form",
11 |
12 | cap_height = "cap-height",
13 |
14 | fill_opacity = "fill-opacity",
15 |
16 | font_face = "font-face",
17 | font_face_format = "font-face-format",
18 | font_face_src = "font-face-src",
19 | font_face_name = "font-face-name",
20 | font_family = "font-family",
21 | font_size = "font-size",
22 | font_stretch = "font-stretch",
23 | font_style = "font-style",
24 | font_variant = "font-variant",
25 | font_weight = "font-weight",
26 |
27 | glyph_name = "glyph-name",
28 |
29 | horiz_adv_x = "horiz-adv-x",
30 | horiz_origin_x = "horiz-origin-x",
31 | horiz_origin_y = "horiz-origin-y",
32 |
33 | overline_position = "overline-position",
34 | overline_thickness = "overline-thickness",
35 |
36 | panose_1 = "panose-1",
37 |
38 | rendering_intent = "rendering-intent",
39 |
40 | stop_color = "stop-color",
41 | strikethrough_position = "strikethrough-position",
42 | strikethrough_thickness = "strikethrough-thickness",
43 | stroke_linecap = "stroke-linecap",
44 | stroke_linejoin = "stroke-linejoin",
45 | stroke_width = "stroke-width",
46 |
47 | underline_position = "underline-position",
48 | underline_thickness = "underline-thickness",
49 | unicode_range = "unicode-range",
50 | units_per_em = "units-per-em",
51 |
52 | v_alphabetic = "v-alphabetic",
53 | v_hanging = "v-hanging",
54 | v_ideographic = "v-ideographic",
55 | v_mathematical = "v-mathematical",
56 | vert_adv_y = "vert-adv-y",
57 | vert_origin_x = "vert-origin-x",
58 | vert_origin_y = "vert-origin-y",
59 |
60 | x_height = "x-height",
61 | xlink_actuate = "xlink:actuate",
62 | xlink_arcrole = "xlink:arcrole",
63 | xlink_href = "xlink:href",
64 | xlink_role = "xlink:role",
65 | xlink_show = "xlink:show",
66 | xlink_title = "xlink:title",
67 | xlink_type = "xlink:type",
68 | xml_base = "xml:base",
69 | xml_lang = "xml:lang",
70 | xml_space = "xml:space",
71 | }
72 |
73 | local SVGAttributes = require("remotesvg.SVGAttributes")
74 |
75 | -- Given an attribute name, return the SVG name
76 | -- by looking up in alias table.
77 | local function realAttrName(name)
78 | return attrNameAlias[name] or name;
79 | end
80 |
81 | --[[
82 | SVGElem
83 |
84 | A base type for all other SVG Elements.
85 | This can do the basic writing
86 | --]]
87 | local BasicElem = {}
88 | setmetatable(BasicElem, {
89 | __call = function(self, ...)
90 | return self:new(...);
91 | end,
92 | })
93 | local BasicElem_mt = {
94 | __index = BasicElem;
95 | }
96 |
97 | function BasicElem.new(self, kind, params)
98 | local obj = params or {}
99 | obj._kind = kind;
100 |
101 | setmetatable(obj, BasicElem_mt);
102 |
103 | return obj;
104 | end
105 |
106 | -- Add an attribute to ourself
107 | function BasicElem.attr(self, name, value)
108 | self[name] = value;
109 | return self;
110 | end
111 |
112 | -- Add a new child element
113 | function BasicElem.appendLiteral(self, literal)
114 | table.insert(self, literal)
115 | return self;
116 | end
117 |
118 | function BasicElem.append(self, name)
119 | -- based on the obj, find the right object
120 | -- to represent it.
121 | local child = nil;
122 |
123 | if type(name) == "table" then
124 | child = name;
125 | elseif type(name) == "string" then
126 | child = BasicElem(name);
127 | else
128 | return nil;
129 | end
130 |
131 | table.insert(self, child);
132 |
133 | return child;
134 | end
135 |
136 | function BasicElem.attributes(self)
137 | local function yieldAttributes(parent)
138 | for name, value in pairs(parent) do
139 | if type(name) ~= "number" and name ~= "_kind" then
140 | coroutine.yield(name, value)
141 | end
142 | end
143 | end
144 |
145 | return coroutine.wrap(function() yieldAttributes(self) end)
146 | end
147 |
148 | -- go through each attribute parsing it
149 | -- to get a native lua form
150 | function BasicElem.parseAttributes(self, strict)
151 | local newVals = {}
152 | --for name, value in pairs(self) do
153 | for name, value in self:attributes() do
154 | --print("BasicElem.parseAttributes: ", name, type(name), value)
155 | local n, val = SVGAttributes.parseAttribute(name, value, strict)
156 | if n ~= name then
157 | self[name] = nil;
158 | end
159 |
160 | if n then
161 | self[n] = val;
162 | end
163 | end
164 |
165 | -- tell our children to parse their
166 | -- attributes
167 | for idx, value in ipairs(self) do
168 | if type(value) == "table" then
169 | value:parseAttributes(strict);
170 | end
171 | end
172 | end
173 |
174 |
175 | --[[
176 | Traverse the elements in document order, returning
177 | the ones that match a given predicate.
178 | If no predicate is supplied, then return all the
179 | elements.
180 | --]]
181 | function BasicElem.selectElementMatches(self, pred)
182 | local function yieldMatches(parent, predicate)
183 | for idx, value in ipairs(parent) do
184 | if predicate then
185 | if predicate(value) then
186 | coroutine.yield(value)
187 | end
188 | else
189 | coroutine.yield(value)
190 | end
191 |
192 | if type(value) == "table" then
193 | yieldMatches(value, predicate)
194 | end
195 | end
196 | end
197 |
198 | return coroutine.wrap(function() yieldMatches(self, pred) end)
199 | end
200 |
201 | -- A convenient shorthand for selecting all the elements
202 | -- in the document. No predicate is specified.
203 | function BasicElem.selectAll(self)
204 | return self:selectElementMatches()
205 | end
206 |
207 | function BasicElem.getElementById(self, id)
208 | local function filterById(entry)
209 | print("filterById: ", entry.id, id)
210 | if entry.id == id then
211 | return true;
212 | end
213 | end
214 |
215 | for child in self:selectMatches(filterById) do
216 | return child;
217 | end
218 | end
219 |
220 | function BasicElem.write(self, strm)
221 | strm:openElement(self._kind);
222 |
223 | local childcount = 0;
224 |
225 | for name, value in pairs(self) do
226 | --print("\nBasicElem.write (pairs): ", name, value)
227 | if type(name) == "number" then
228 | childcount = childcount + 1;
229 | else
230 | if name ~= "_kind" then
231 | name = realAttrName(name);
232 | strm:writeAttribute(name, tostring(value));
233 | end
234 | end
235 | end
236 |
237 | -- if we have some number of child nodes
238 | -- then write them out
239 | if childcount > 0 then
240 | -- first close the starting tag
241 | strm:closeTag();
242 |
243 | -- write out child nodes
244 | for idx, value in ipairs(self) do
245 | if type(value) == "table" then
246 | value:write(strm);
247 | elseif type(value) == "function" then
248 | else
249 | -- write out pure text nodes
250 | strm:write(tostring(value));
251 | end
252 | end
253 |
254 | strm:closeElement(self._kind);
255 | else
256 | strm:closeElement();
257 | end
258 | end
259 |
260 |
261 | --[[
262 | SVG
263 |
264 | We specialize this one to ensure the xmlns and version
265 | attributes are there.
266 |
267 | Without the xmlns attribute, some renderers will fail.
268 | --]]
269 | local function SVG(params)
270 | local elem = BasicElem('svg', params);
271 | elem.xmlns = elem.xmlns or "http://www.w3.org/2000/svg"
272 | elem.version = params.version or "1.1"
273 |
274 | return elem;
275 | end
276 |
277 |
278 | --[[
279 | Path
280 |
281 | A specialization so that path construction can happen 'on the fly'.
282 | --]]
283 | local Path = {}
284 | setmetatable(Path, {
285 | __call = function(self, ...) -- functor
286 | return self:new(...);
287 | end,
288 | })
289 |
290 | function Path.new(self, params)
291 | local obj = params or {}
292 | obj._kind = "path";
293 |
294 | local meta = {
295 | __index = Path;
296 | Commands = {};
297 | }
298 | setmetatable(obj, meta);
299 |
300 | return obj;
301 | end
302 |
303 | function Path.addCommand(self, ins, ...)
304 | local tbl = getmetatable(self);
305 | local commands = tbl.Commands;
306 |
307 | -- construct the new instruction
308 | local res = {}
309 | table.insert(res,tostring(ins))
310 | local coords = {...};
311 | for _, value in ipairs(coords) do
312 | table.insert(res, string.format("%d",tonumber(value)))
313 | end
314 |
315 | table.insert(commands, table.concat(res, ' '));
316 | end
317 |
318 | function Path.pathToString(self)
319 | local tbl = getmetatable(self);
320 | local commands = tbl.Commands;
321 |
322 | return table.concat(commands);
323 | end
324 |
325 | -- Path construction commands
326 | -- add a single arc segment
327 | function Path.arcBy(self, rx, ry, rotation, large, sweep, x, y)
328 | self:addCommand('a', rx, ry, rotation, large, sweep, x, y)
329 | return self
330 | end
331 |
332 | function Path.arcTo(self, rx, ry, rotation, large, sweep, x, y)
333 | self:addCommand('A', rx, ry, rotation, large, sweep, x, y)
334 | return self
335 | end
336 |
337 | function Path.close(self)
338 | self:addCommand('z')
339 | return self;
340 | end
341 |
342 | function Path.cubicBezierTo(self, ...)
343 | self:addCommand('C', ...)
344 | return self
345 | end
346 |
347 | function Path.quadraticBezierTo(self, ...)
348 | self:addCommand('Q', ...)
349 | return self
350 | end
351 |
352 | function Path.hLineBy(self, x, y)
353 | self:addCommand('h', x, y)
354 | return self
355 | end
356 |
357 | function Path.hLineTo(self, x, y)
358 | self:addCommand('H', x, y)
359 | return self
360 | end
361 |
362 | -- Line to relative position
363 | function Path.lineBy(self, x,y)
364 | self:addCommand('l', x, y)
365 | return self
366 | end
367 |
368 | -- Line to absolute position
369 | function Path.lineTo(self, x,y)
370 | self:addCommand('L', x, y)
371 | return self
372 | end
373 |
374 | -- Move to position
375 | function Path.moveBy(self, x, y)
376 | self:addCommand('m', x,y)
377 | return self
378 | end
379 | function Path.moveTo(self, x, y)
380 | self:addCommand('M', x,y)
381 | return self
382 | end
383 |
384 | function Path.sqCurveBy(self, ...)
385 | self:addCommand('t', ...)
386 | return self;
387 | end
388 |
389 | function Path.sqCurveTo(self, ...)
390 | self:addCommand('T', ...)
391 | return self;
392 | end
393 |
394 | -- Vertical Line
395 | function Path.vLineBy(self, x, y)
396 | self:addCommand('v', x, y)
397 | return self
398 | end
399 | function Path.vLineTo(self, x, y)
400 | self:addCommand('V', x, y)
401 | return self
402 | end
403 |
404 | -- Write out the thing to a stream
405 | function Path.write(self, strm)
406 | strm:openElement(self._kind);
407 |
408 | local childcount = 0;
409 |
410 | -- write out attributes first
411 | for name, value in pairs(self) do
412 | if type(value) == "table" then
413 | childcount = childcount + 1;
414 | else
415 | if name ~= "_kind" then
416 | strm:writeAttribute(name, tostring(value));
417 | end
418 | end
419 | end
420 |
421 | -- write out instructions attribute if it hasn't
422 | -- been written as an attribute already
423 | if not self.d then
424 | strm:writeAttribute("d", self:pathToString())
425 | end
426 |
427 | if childcount > 0 then
428 | strm:closeTag();
429 |
430 | -- write out child nodes
431 | for _, value in pairs(self) do
432 | if type(value) == "table" then
433 | value:write(strm);
434 | end
435 | end
436 |
437 | strm:closeElement(self._kind);
438 | else
439 | strm:closeElement();
440 | end
441 | end
442 |
443 | --[[
444 | Polygon
445 |
446 | points - table of points, each point represented by a table
447 | --]]
448 | local Polygon = {}
449 | setmetatable(Polygon, {
450 | __call = function (self, ...)
451 | return self:new(...);
452 | end,
453 | })
454 | local Polygon_mt = {
455 | __index = Polygon;
456 | }
457 |
458 | function Polygon.init(self, params)
459 | local obj = params or {}
460 | setmetatable(obj, Polygon_mt);
461 |
462 | obj.points = obj.points or {};
463 |
464 | return obj;
465 | end
466 |
467 | function Polygon.new(self, params)
468 | return self:init(params);
469 | end
470 |
471 | function Polygon.write(self, strm)
472 | strm:openElement("polygon")
473 | for name, value in pairs(self) do
474 | if type(value) == "string" or
475 | type(value) == "number" then
476 | strm:writeAttribute(name, tostring(value));
477 | end
478 | end
479 |
480 | -- write out the points
481 | if #self.points > 0 then
482 | local tbl = {};
483 | for _, pt in ipairs(self.points) do
484 | table.insert(tbl, string.format(" %d,%d", pt[1], pt[2]))
485 | end
486 | local pointsValue = table.concat(tbl, ' ');
487 | --print("pointsValue: ", pointsValue)
488 | strm:writeAttribute("points", pointsValue);
489 | end
490 |
491 | strm:closeElement();
492 | end
493 |
494 |
495 | --[[
496 | PolyLine
497 |
498 | points - table of points, each point represented by a table
499 | --]]
500 | local PolyLine = {}
501 | setmetatable(PolyLine, {
502 | __call = function (self, ...)
503 | return self:new(...);
504 | end,
505 | })
506 | local PolyLine_mt = {
507 | __index = PolyLine;
508 | }
509 |
510 | function PolyLine.init(self, params)
511 | local obj = params or {}
512 | setmetatable(obj, PolyLine_mt);
513 |
514 | obj.points = obj.points or {};
515 |
516 | return obj;
517 | end
518 |
519 | function PolyLine.new(self, params)
520 | return self:init(params);
521 | end
522 |
523 | function PolyLine.write(self, strm)
524 | strm:openElement("polyline")
525 | for name, value in pairs(self) do
526 | if type(value) == "string" or
527 | type(value) == "number" then
528 | strm:writeAttribute(name, tostring(value));
529 | end
530 | end
531 |
532 | -- write out the points
533 | if #self.points > 0 then
534 | local tbl = {};
535 | for _, pt in ipairs(self.points) do
536 | table.insert(tbl, string.format(" %d,%d", pt[1], pt[2]))
537 | end
538 | local pointsValue = table.concat(tbl, ' ');
539 | --print("pointsValue: ", pointsValue)
540 | strm:writeAttribute("points", pointsValue);
541 | end
542 |
543 | strm:closeElement();
544 | end
545 |
546 |
547 | --[[
548 | Interface exposed to outside world
549 |
550 | Reference of SVG Elements
551 | https://developer.mozilla.org/en-US/docs/Web/SVG/Element
552 | --]]
553 | local exports = {
554 | BasicElem = BasicElem;
555 |
556 | -- Animation Elements
557 | animate = function(params) return BasicElem('animate', params) end;
558 | animateColor = function(params) return BasicElem('animateColor', params) end;
559 | animateMotion = function(params) return BasicElem('animateMotion', params) end;
560 | animateTransform = function(params) return BasicElem('animateTransform', params) end;
561 | mpath = function(params) return BasicElem('mpath', params) end;
562 | set = function(params) return BasicElem('set', params) end;
563 |
564 | -- Container elements
565 | a = function(params) return BasicElem('a', params) end;
566 | defs = function(params) return BasicElem('defs', params) end;
567 | glyph = function(params) return BasicElem('glyph', params) end;
568 | g = function(params) return BasicElem('g', params) end;
569 | marker = function(params) return BasicElem('marker', params) end;
570 | mask = function(params) return BasicElem('mask', params) end;
571 | missing_glyph = function(params) return BasicElem('missing-glyph', params) end;
572 | pattern = function(params) return BasicElem('pattern', params) end;
573 | svg = SVG;
574 | switch = function(params) return BasicElem('switch', params) end;
575 | symbol = function(params) return BasicElem('symbol', params) end;
576 | use = function(params) return BasicElem('use', params) end;
577 |
578 | -- Descriptive elements
579 | desc = function(params) return BasicElem('desc', params) end;
580 | metadata = function(params) return BasicElem('metadata', params) end;
581 | title = function(params) return BasicElem('title', params) end;
582 |
583 | -- Filter primitive elements
584 | feBlend = function(params) return BasicElem('feBlend', params) end;
585 | feColorMatrix = function(params) return BasicElem('feColorMatrix', params) end;
586 | feComponentTransfer = function(params) return BasicElem('feComponentTransfer', params) end;
587 | feComposite = function(params) return BasicElem('feComposite', params) end;
588 | feConvolveMatrix = function(params) return BasicElem('feConvolveMatrix', params) end;
589 | feDiffuseLighting = function(params) return BasicElem('feDiffuseLighting', params) end;
590 | feDisplacementMap = function(params) return BasicElem('feDisplacementMap', params) end;
591 | feFlood = function(params) return BasicElem('feFlood', params) end;
592 | feFuncA = function(params) return BasicElem('feFuncA', params) end;
593 | feFuncB = function(params) return BasicElem('feFuncB', params) end;
594 | feFuncR = function(params) return BasicElem('feFuncR', params) end;
595 | feGaussianBlur = function(params) return BasicElem('feGaussianBlur', params) end;
596 | feImage = function(params) return BasicElem('feImage', params) end;
597 | feMerge = function(params) return BasicElem('feMerge', params) end;
598 | feMergeNode = function(params) return BasicElem('feMergeNode', params) end;
599 | feOffset = function(params) return BasicElem('feOffset', params) end;
600 | feSpecularLighting = function(params) return BasicElem('feSpecularLighting', params) end;
601 | feTile = function(params) return BasicElem('feTile', params) end;
602 | feTurbulance = function(params) return BasicElem('feTurbulance', params) end;
603 |
604 | -- Font elements
605 | font = function(params) return BasicElem('font', params) end;
606 | font_face = function(params) return BasicElem('font-face', params) end;
607 | font_face_format = function(params) return BasicElem('font-face-format', params) end;
608 | font_face_name = function(params) return BasicElem('font-face-name', params) end;
609 | font_face_src = function(params) return BasicElem('font-face-src', params) end;
610 | font_face_uri = function(params) return BasicElem('font-face-uri', params) end;
611 | hkern = function(params) return BasicElem('hkern', params) end;
612 | vkern = function(params) return BasicElem('vkern', params) end;
613 |
614 | -- Graphics elements
615 | circle = function(params) return BasicElem('circle', params) end;
616 | ellipse = function(params) return BasicElem('ellipse', params) end;
617 | image = function(params) return BasicElem('image', params) end;
618 | line = function(params) return BasicElem('line', params) end;
619 | path = Path; -- check
620 | polygon = Polygon; -- check
621 | polyline = PolyLine; -- check
622 | rect = function(params) return BasicElem('rect', params) end;
623 |
624 | -- Gradient Elements
625 | linearGradient = function(params) return BasicElem('linearGradient', params) end;
626 | radialGradient = function(params) return BasicElem('radialGradient', params) end;
627 | stop = function(params) return BasicElem('stop', params) end;
628 |
629 | -- Light Source elements
630 | feDistantLight = function(params) return BasicElem('feDistantLight', params) end;
631 | fePointLight = function(params) return BasicElem('fePointLight', params) end;
632 | feSpotLight = function(params) return BasicElem('feSpotLight', params) end;
633 |
634 | -- Text Content elements
635 | altGlyph = function(params) return BasicElem('altGlyph', params) end;
636 | altGlyphDef = function(params) return BasicElem('altGlyphDef', params) end;
637 | altGlyphItem = function(params) return BasicElem('altGlyphItem', params) end;
638 | glyph = function(params) return BasicElem('glyph', params) end;
639 | glyphRef = function(params) return BasicElem('glyphRef', params) end;
640 | text = function(params) return BasicElem('text', params) end;
641 | textPath = function(params) return BasicElem('textPath', params) end;
642 | tref = function(params) return BasicElem('tref', params) end;
643 | tspan = function(params) return BasicElem('tspan', params) end;
644 |
645 | -- Uncategorized elements
646 | clipPath = function(params) return BasicElem('clipPath', params) end;
647 | color_profile = function(params) return BasicElem('color-profile', params) end;
648 | cursor = function(params) return BasicElem('cursor', params) end;
649 | filter = function(params) return BasicElem('filter', params) end;
650 | foreignObject = function(params) return BasicElem('foreignObject', params) end;
651 | script = function(params) return BasicElem('script', params) end;
652 | style = function(params) return BasicElem('style', params) end;
653 | view = function(params) return BasicElem('view', params) end;
654 |
655 | }
656 | setmetatable(exports, {
657 | __call = function(self, tbl)
658 | tbl = tbl or _G;
659 |
660 | for k,v in pairs(exports) do
661 | tbl[k] = v;
662 | end
663 |
664 | return self;
665 | end,
666 | })
667 |
668 | return exports
669 |
--------------------------------------------------------------------------------
/remotesvg/SVGAttributes.lua:
--------------------------------------------------------------------------------
1 |
2 | -- References
3 | -- https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute
4 | --
5 | local colors = require("remotesvg.colors")
6 | local RGB = colors.RGBA;
7 | local maths = require("remotesvg.maths")
8 | local clamp = maths.clamp;
9 | local bit = require("bit")
10 | local lshift, rshift, band, bor = bit.lshift, bit.rshift, bit.band, bit.bor
11 |
12 | local tonumber = tonumber;
13 | local tostring = tostring;
14 |
15 | -- Individual functions which deal with specific
16 | -- attributes
17 | local attrs = {}
18 |
19 | -- This is the function to be used when we don't have
20 | -- any specific specialization for an attribute
21 | function default(name, value, strict)
22 | --print("{name = '', parser = default}: ", name, value);
23 |
24 | return name, value
25 | end
26 |
27 |
28 | -- These functions convert from SVG Attribute string values
29 | -- to lua specific types
30 | local function color(name, value, strict)
31 | local function parseColorName(name)
32 | return colors.svg[name] or RGB(128, 128, 128);
33 | end
34 |
35 | local function parseColorHex(s)
36 | --print(".parseColorHex: ", s)
37 |
38 | local rgb = s:match("#(%x+)")
39 | --print("rgb: ", rgb)
40 | local r,g,b = 0,0,0;
41 | local c = 0;
42 | --rgb = "0x"..rgb;
43 |
44 | if #rgb == 6 then
45 | c = tonumber("0x"..rgb)
46 | elseif #rgb == 3 then
47 | c = tonumber("0x"..rgb);
48 | c = bor(band(c,0xf), lshift(band(c,0xf0), 4), lshift(band(c,0xf00), 8));
49 | c = bor(c, lshift(c,4));
50 | end
51 | b = band(rshift(c, 16), 0xff);
52 | g = band(rshift(c, 8), 0xff);
53 | r = band(c, 0xff);
54 |
55 | return RGB(r,g,b);
56 | end
57 |
58 | local function parseColorRGB(str)
59 | -- if numberpatt uses %x instead of %d, then do the following
60 | -- to get past the 'b' in 'rgb('
61 | --local loc = str:find("%(")
62 | --str = str:sub(loc+1,#str)
63 | local numberpatt = "(%d+)"
64 | local usePercent = str:find("%%")
65 |
66 | --print("parseColorRGB: ", str, usePercent)
67 |
68 | local tbl = {}
69 | for num in str:gmatch(numberpatt) do
70 | if usePercent then
71 | table.insert(tbl, tonumber(num)*255/100)
72 | else
73 | table.insert(tbl, tonumber(num))
74 | end
75 | end
76 |
77 | return RGB(tbl[1], tbl[2], tbl[3])
78 | end
79 |
80 | local str = value:match("%s*(.*)")
81 | local len = #str;
82 |
83 | --print("parseColor: ", str)
84 | if len >= 1 and str:sub(1,1) == '#' then
85 | value = parseColorHex(str);
86 | elseif (len >= 4 and str:match("rgb%(")) then
87 | value = parseColorRGB(str);
88 | else
89 | value = parseColorName(str);
90 | end
91 |
92 | local r, g, b, a = colors.colorComponents(value)
93 | --print("COLOR: ", r,g,b,a)
94 | local obj = {r=r, g = g, b = b, a = a};
95 | setmetatable(obj, {
96 | __tostring = function(self)
97 | return string.format("rgb(%d, %d, %d)", self.r, self.g, self.b)
98 | end
99 | })
100 |
101 | return name, obj
102 | end
103 | local paint = color;
104 |
105 |
106 | local function coord(name, value, strict)
107 | --print("coord: ", name, value)
108 |
109 | if type(value) ~= "string" then
110 | return name, value;
111 | end
112 |
113 | local num, units = value:match("([+-]?%d*%.?%d*)(%g*)")
114 |
115 | --print("coord: ", name, value, num, units)
116 |
117 | if not num then return nil; end
118 |
119 | local obj = {value = tonumber(num), units = units}
120 | setmetatable(obj,{
121 | __tostring = function(self)
122 | --print("coord.tostring: ", self.value, self.units)
123 | return string.format("%f%s",self.value, self.units or "")
124 | end,
125 | })
126 |
127 | return name, obj;
128 | end
129 | -- The specification for length is the same as for
130 | -- coordinates
131 | local length = coord
132 |
133 | local function number(name, value, strict)
134 | if type(value) ~= "string" then
135 | return name, value;
136 | end
137 |
138 | return name, tonumber(value)
139 | end
140 |
141 | -- strictly, the SVG spec say opacity is simply
142 | -- a number between 0..1
143 | -- but, we'll allow specifying a '%' value as well...
144 |
145 | function numberorpercent(name, value, strict)
146 | if type(value) ~= "string" then
147 | return name, value;
148 | end
149 |
150 | local usePercent = value:find("%%")
151 | local num = 1.0
152 | tonumber(value)
153 | if usePercent then
154 | local str = value:match("(%d+)")
155 | num = tonumber(str) / 100;
156 | else
157 | num = tonumber(value);
158 | end
159 |
160 | local val = clamp(tonumber(value), 0, 1);
161 |
162 | return name, val;
163 | end
164 | local offset = opacity;
165 | local opacity = numberorpercent;
166 |
167 | local function parseStyle(name, value, strict)
168 | local tbl = {}
169 |
170 | for item in value:gmatch("([^;]+)") do
171 | local name, value = item:match("(%g+):(%g+)")
172 | --print("item:match: ", name, value)
173 | name, value = attrs.parseAttribute(name, value, strict)
174 | --print("parse result: ", name, value)
175 | tbl[name] = value;
176 | end
177 |
178 | setmetatable(tbl, {
179 | __tostring = function(self)
180 | local res = {}
181 | for name, value in pairs(self) do
182 | table.insert(res, string.format("%s:%s", name, tostring(value)))
183 | end
184 |
185 | return table.concat(res,';')
186 | end,
187 | })
188 |
189 | return name, tbl
190 | end
191 |
192 | local function parseviewBox(name, value, strict)
193 | if type(value) ~= "string" then
194 | return name, value
195 | end
196 | local numpatt = "(%d*%.?%d*)"
197 | local nums = {}
198 | for num in value:gmatch(numpatt) do
199 | --print("NUM: ", num, type(num))
200 | if num ~= "" then
201 | table.insert(nums, tonumber(num))
202 | end
203 | end
204 |
205 | local obj = {
206 | min_x = nums[1],
207 | min_y = nums[2],
208 | width = nums[3],
209 | height= nums[4]
210 | }
211 |
212 | setmetatable(obj, {
213 | __tostring = function(self)
214 | return string.format("%f %f %f %f",
215 | self.min_x, self.min_y, self.width, self.height);
216 | end,
217 | })
218 |
219 | return name, obj;
220 | end
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 | --[[
230 | Entries for parsers per attribute
231 | --]]
232 |
233 | attrs.accent_height = {name = 'accent-height', parser = number};
234 | attrs.accumulate = {name = 'accumulate', pardser = default}; -- 'none' | 'sum'
235 | attrs.additive = {name = 'additive', parser = default}; -- 'replace' | 'sum'
236 | attrs.alignment_baseline = {name = 'alignment-baseline', parser = default};
237 | attrs.allowReorder = {name = 'allowReorder', parser = default};
238 | attrs.alphabetic = {name = 'alphabetic', parser = number};
239 | attrs.amplitude = {name = 'amplitude', parser = default};
240 | attrs.arabic_form = {name = 'arabic-form', parser = default};
241 | attrs.ascent = {name = 'ascent', parser = number};
242 | attrs.attributeName = {name = 'attributeName', parser = default};
243 | attrs.attributeType = {name = 'attributeType', parser = default};
244 | attrs.autoReverse = {name = 'autoReverse', parser = default};
245 | attrs.azimuth = {name = 'azimuth', parser = default};
246 |
247 | attrs.baseFrequency = {name = 'baseFrequency', parser = default};
248 | attrs.baseline_shift = {name = 'baseline-shift', parser = default};
249 | attrs.baseProfile = {name = 'baseProfile', parser = default};
250 | attrs.bbox = {name = 'bbox', parser = default};
251 | attrs.begin = {name = 'begin', parser = default};
252 | attrs.bias = {name = 'bias', parser = default};
253 | attrs.by = {name = 'by', parser = default};
254 |
255 | attrs.calcMode = {name = 'calcMode', parser = default};
256 | attrs.cap_height = {name = 'cap-height', parser = number};
257 | attrs['cap-height'] = attrs.cap_height;
258 | attrs.class = {name = 'class', parser = default};
259 | attrs.clip = {name = 'clip', parser = default};
260 | attrs.clipPathUnits = {name = 'clipPathUnits', parser = default};
261 | attrs.clip_path = {name = 'clip-path', parser = default};
262 | attrs.clip_rule = {name = 'clip-rule', parser = default};
263 | attrs.color = {name = 'color', parser = color};
264 | attrs.color_interpolation = {name = 'color-interpolation', parser = default};
265 | attrs.color_interpolation_filters = {name = 'color-interpolation-filters', parser = default};
266 | attrs.color_profile = {name = 'profile', parser = default};
267 | attrs.color_rendering = {name = 'color-rendering', parser = default};
268 | attrs.contentScriptType = {name = 'contentScriptType', parser = default};
269 | attrs.contentStyleType = {name = 'contentStyleType', parser = default};
270 | attrs.cursor = {name = 'cursor', parser = default};
271 | attrs.cx = {name='cx', parser = coord};
272 | attrs.cy = {name = 'cy', parser = coord};
273 |
274 | attrs.d = {name = 'd', parser = default};
275 | attrs.decelerate = {name = 'decelerate', parser = default};
276 | attrs.descent = {name = 'descent', parser = number};
277 | attrs.diffuseConstant = {name = 'diffuseConstant', parser = default};
278 | attrs.direction = {name = 'direction', parser = default};
279 | attrs.display = {name = 'display', parser = default};
280 | attrs.divisor = {name = 'divisor', parser = default};
281 | attrs.dominant_baseline = {name = 'dominant-baseline', parser = default};
282 | attrs.dur = {name = 'dur', parser = default};
283 | attrs.dx = {name='dx', parser = number};
284 | attrs.dy = {name = 'dy', parser = number};
285 |
286 | attrs.edgeMode = {name = 'edgeMode', parser = default};
287 | attrs.elevation = {name = 'elevation', parser = default};
288 | attrs.enable_background = {name = 'enable-background', parser = default};
289 | attrs['enable-background'] = attrs.enable_background;
290 | attrs['end'] = {name = 'end', parser = default};
291 | attrs.exponent = {name = 'exponent', parser = default};
292 | attrs.externalResourcesRequired = {name = 'externalResourcesRequired', parser = default};
293 |
294 | attrs.fill = {name='fill', parser = paint};
295 | attrs.fill_opacity = {name = 'fill-opacity', parser = opacity};
296 | attrs['fill-opacity'] = attrs.fill_opacity;
297 | attrs.fill_rule = {name = 'fill-rule', parser = default};
298 | attrs['fill-rule'] = attrs.fill_rule;
299 | attrs.filter = {name = 'filter', parser = default};
300 | attrs.filterRes = {name = 'filterRes', parser = default};
301 | attrs.filterUnits = {name = 'filterUnits', parser = default};
302 | attrs.flood_color = {name='flood-color', parser =color};
303 | attrs['flood-color'] = attrs.flood_color;
304 | attrs.flood_opacity = {name = 'flood-opacity', parser = opacity};
305 | attrs.font_family = {name = 'font-family', parser = default};
306 | attrs.font_size = {name = 'font-size', parser = default};
307 | attrs['font-size'] = attrs.font_size;
308 | attrs.font_size_adjust = {name = 'font-size-adjust', parser = default};
309 | attrs.font_stretch = {name = 'font-stretch', parser = default};
310 | attrs.font_style = {name = 'font-style', parser = default};
311 | attrs.font_variant = {name = 'font-variant', parser = default};
312 | attrs.font_weight = {name = 'font-weight', parser = default};
313 | attrs.format = {name = 'format', parser = default};
314 | attrs.from = {name = 'from', parser = default};
315 | attrs.fx = {name='fx', parser=coord};
316 | attrs.fy = {name='fy', parser=coord};
317 |
318 | attrs.g1 = {name = 'g1', parser = default};
319 | attrs.g2 = {name = 'g2', parser = default};
320 | attrs.glyph_name = {name = 'glyph-name', parser = default};
321 | attrs.glyph_orientation_horizontal = {name = 'glyph-orinentation-horizontal', parser = default};
322 | attrs.glyph_orientation_vertical = {name = 'glyph-orientation-vertical', parser = default};
323 | attrs.glyphRef = {name = 'glyphRef', parser = default};
324 | attrs.gradientTransform = {name = 'gradientTransform', parser = default};
325 | attrs.gradientUnits = {name = 'gradientUnits', parser = default};
326 |
327 | attrs.hanging = {name ='hanging', parser=number};
328 | attrs.height = {name = 'height', parser=length};
329 | attrs.horiz_adv_x = {name = 'horiz-adv-x', parser = default};
330 | attrs.horiz_origin_x = {name = 'horiz-origin-x', parser = default};
331 | attrs['horiz-origin-x'] = attrs.horiz_origin_x;
332 |
333 | attrs.id = {name = 'id', parser = default};
334 | attrs.ideographic = {name = 'ideographic', parser = default};
335 | attrs.image_rendering = {name = 'image-rendering', parser = default};
336 | attrs['in'] = {name = 'in', parser = default};
337 | attrs.in2 = {name = 'in2', parser = default};
338 | attrs.intercept = {name = 'intercept', parser = default};
339 |
340 | attrs.k = {name = 'k', parser = number};
341 | attrs.k1 = {name = 'k1', parser = number};
342 | attrs.k2 = {name = 'k2', parser = number};
343 | attrs.k3 = {name = 'k3', parser = number};
344 | attrs.k4 = {name = 'k4', parser = number};
345 | attrs.kernelMatrix = {name = 'kernelMatrix', parser = default};
346 | attrs.kernelUnitLength = {name = 'kernelUnitLength', parser = default};
347 | attrs.kerning = {name = 'kerning', parser = default};
348 | attrs.keyPoints = {name = 'keyPoints', parser = default};
349 | attrs.keySplines = {name = 'keySplines', parser = default};
350 | attrs.keyTimes = {name = 'keyTimes', parser = default};
351 |
352 | attrs.lang = {name = 'lang', parser = default};
353 | attrs.lengthAdjust = {name = 'lengthAdjust', parser = default};
354 | attrs.letter_spacing = {name = 'letter-spacing', parser = default};
355 | attrs.lighting_color = {name = 'lighting-color', parser = color};
356 | attrs['lighting-color'] = attrs.lighting_color;
357 | attrs.limitingConeAngle = {name = 'limitingConeAngle', parser = default};
358 | attrs['local'] = {name = 'local', parser = default};
359 |
360 | attrs.marker_end = {name = 'marker-end', parser = default};
361 | attrs['marker-end'] = attrs.marker_end;
362 | attrs.marker_mid = {name = 'marker_mid', parser = default};
363 | attrs['marker-mid'] = attrs.marker_mid;
364 | attrs.marker_start = {name = 'marker_start', parser = default};
365 | attrs['marker-start'] = attrs.marker_start;
366 | attrs.markerHeight = {name = 'markerHeight', parser = default};
367 | attrs.markerUnits = {name = 'markerUnits', parser = default};
368 | attrs.markerWidth = {name = 'markerWidth', parser = default};
369 | attrs.mask = {name = 'mask', parser = default};
370 | attrs.maskContentUnits = {name = 'maskContentUnits', parser = default};
371 | attrs.maskUnits = {name = 'maskUnits', parser = default};
372 | attrs.mathematical = {name='mathematical', parser=number};
373 | attrs.max = {name = 'max', parser = default};
374 | attrs.media = {name = 'media', parser = default};
375 | attrs.method = {name = 'method', parser = default};
376 | attrs.min = {name = 'min', parser = default};
377 | attrs.mode = {name = 'mode', parser = default};
378 |
379 | attrs.name = {name = 'name', parser = default};
380 | attrs.numOctaves = {name = 'numOctaves', parser = default};
381 |
382 | attrs.offset = {name = 'offset', parser = numberorpercent};
383 | attrs.onabort = {name = 'onabort', parser = default};
384 | attrs.onactivate = {name = 'onactivate', parser = default};
385 | attrs.onbegin = {name = 'onbegin', parser = default};
386 | attrs.onclick = {name = 'onclick', parser = default};
387 | attrs.onend = {name = 'onend', parser = default};
388 | attrs.onerror = {name = 'onerror', parser = default};
389 | attrs.onfocusin = {name = 'onfocusin', parser = default};
390 | attrs.onfocusout = {name = 'onfocusout', parser = default};
391 | attrs.onload = {name = 'onload', parser = default};
392 | attrs.onmousedown = {name = 'onmousedown', parser = default};
393 | attrs.onmousemove = {name = 'onmousemove', parser = default};
394 | attrs.onmouseout = {name = 'onmouseout', parser = default};
395 | attrs.onmouseover = {name = 'onmouseover', parser = default};
396 | attrs.onmouseup = {name = 'onmouseup', parser = default};
397 | attrs.onrepeat = {name = 'onrepeat', parser = default};
398 | attrs.onresize = {name = 'onresize', parser = default};
399 | attrs.onscroll = {name = 'onscroll', parser = default};
400 | attrs.onunload = {name = 'onunload', parser = default};
401 | attrs.onzoom = {name = 'onzoom', parser = default};
402 | attrs.opacity = {name = 'opacity', parser = opacity};
403 | attrs.operator = {name = 'operator', parser = default};
404 | attrs.order = {name = 'order', parser = default};
405 | attrs.orient = {name = 'orient', parser = default};
406 | attrs.orientation = {name = 'orientation', parser = default};
407 | attrs.origin = {name = 'origin', parser = default};
408 | attrs.overflow = {name = 'overflow', parser = default};
409 | attrs.overline_position = {name = 'overline-position', parser = number};
410 | attrs['overline-position'] = attrs.overline_position;
411 | attrs.overline_thickness = {name = 'overline-thickness', parser = number};
412 | attrs['overline-thickness'] = attrs.overline_thickness;
413 |
414 | attrs.panose_1 = {name = 'panose-1', parser = default};
415 | attrs['panose-1'] = attrs.panose_1;
416 | attrs.paint_order = {name = 'paint-order', parser = default};
417 | attrs['paint-order'] = attrs.paint_order;
418 | attrs.pathLength = {name = 'pathLength', parser = default};
419 | attrs.patternContentUnits = {name = 'patternContentUnits', parser = default};
420 | attrs.patternTransform = {name = 'patternTransform', parser = default};
421 | attrs.patternUnits = {name = 'patternUnits', parser = default};
422 | attrs.pointer_events = {name = 'pointer-events', parser = default};
423 | attrs['pointer-events'] = attrs.pointer_events;
424 | attrs.points = {name = 'points', parser = default};
425 | attrs.pointsAtX = {name = 'pointsAtX', parser = default};
426 | attrs.pointsAtY = {name = 'pointsAtY', parser = default};
427 | attrs.pointsAtZ = {name = 'pointsAtZ', parser = default};
428 | attrs.preserveAlpha = {name = 'preserveAlpha', parser = default};
429 | attrs.preserveAspectRatio = {name = 'preserveAspectRatio', parser = default};
430 | attrs.primitiveUnits = {name = 'primitiveUnits', parser = default};
431 |
432 | attrs.r = {name = 'r', parser = default};
433 | attrs.radius = {name = 'radius', parser = default};
434 | attrs.refX = {name = 'refX', parser = default};
435 | attrs.refY = {name = 'refY', parser = default};
436 | attrs.rendering_intent = {name = 'rendering-intent', parser = default};
437 | attrs['rendering-intent'] = attrs.rendering_intent;
438 | attrs.repeatCount = {name = 'repeatCount', parser = default};
439 | attrs.repeatDur = {name = 'repeatDur', parser = default};
440 | attrs.requiredExtensions = {name = 'requiredExtensions', parser = default};
441 | attrs.requiredFeatures = {name = 'requiredFeatures', parser = default};
442 | attrs.restart = {name = 'restart', parser = default};
443 | attrs.result = {name = 'result', parser = default};
444 | attrs.rotate = {name = 'rotate', parser = default};
445 | attrs.rx = {name='rx', parser=length};
446 | attrs.ry = {name='ry', parser=length};
447 |
448 | attrs.scale = {name = 'scale', parser = number};
449 | attrs.seed = {name = 'seed', parser = number};
450 | attrs.shape_rendering = {name = 'shape-rendering', parser = default};
451 | attrs['shape-rendering'] = attrs.shape_rendering;
452 | attrs.slope = {name='slope', parser=number};
453 | attrs.spacing = {name = 'spacing', parser = default};
454 | attrs.specularConstant = {name = 'specularConstant', parser = default};
455 | attrs.specularExponent = {name = 'specularExponent', parser = default};
456 | attrs.speed = {name = 'speed', parser = default};
457 | attrs.spreadMethod = {name = 'spreadMethod', parser = default};
458 | attrs.startOffset = {name = 'startOffset', parser = default};
459 | attrs.stdDeviation = {name = 'stdDeviation', parser = default};
460 | attrs.stemh = {name='stemh', parser = number};
461 | attrs.stemv = {name = 'stemv', parser=number};
462 | attrs.stitchTiles = {name = 'stitchTiles', parser = default};
463 | attrs.stop_color = {name='stop-color', parser=color};
464 | attrs['stop-color'] = attrs.stop_color;
465 | attrs.stop_opacity = {name = 'stop-opacity', parser = opacity};
466 | attrs['stop-opacity'] = attrs.stop_opacity;
467 | attrs.strikethrough_position = {name = 'strikethrough-position', parser = number};
468 | attrs['strikethrough-position'] = attrs.strikethrough_position;
469 | attrs.strikethrough_thickness = {name = 'strikethrough-thickness', parser = number};
470 | attrs['strikethrough-thickness'] = attrs.strikethrough_thickness;
471 | attrs.string = {name = 'string', parser = default};
472 | attrs.stroke = {name='stroke', parser=paint};
473 | attrs.stroke_dasharray = {name = 'stroke-dasharray', parser = default};
474 | attrs['stroke-dasharray'] = attrs.stroke_dasharray;
475 | attrs.stroke_dashoffset = {name = 'stroke-dashoffset', parser = default};
476 | attrs['stroke-dashoffset'] = attrs.stroke_dashoffset;
477 | attrs.stroke_linecap = {name = 'stroke-linecap', parser = default};
478 | attrs['stroke-linecap'] = attrs.stroke_linecap;
479 | attrs.stroke_miterlimit = {name = 'stroke-miterlimit', parser = default};
480 | attrs['stroke-miterlimit'] = attrs.stroke_miterlimit;
481 | attrs.stroke_opacity = {name = 'stroke-opacity', parser = opacity};
482 | attrs['stroke-opacity'] = attrs.stroke_opacity;
483 | attrs.stroke_width = {name = 'stroke-width', parser = length};
484 | attrs['stroke-width'] = attrs.stroke_width;
485 | attrs.style = {name = 'style', parser = parseStyle};
486 | attrs.surfaceScale = {name = 'surfaceScale', parser = default};
487 | attrs.systemLanguage = {name = 'systemLanguage', parser = default};
488 |
489 | attrs.tableValues = {name = 'tableValues', parser = default};
490 | attrs.target = {name = 'target', parser = default};
491 | attrs.targetX = {name = 'targetX', parser = default};
492 | attrs.targetY = {name = 'targetY', parser = default};
493 | attrs.text_anchor = {name = 'text-anchor', parser = default};
494 | attrs['text-anchor'] = attrs.text_anchor;
495 | attrs.text_decoration = {name = 'text-decoration', parser = default};
496 | attrs['text-decoration'] = attrs.text_decoration;
497 | attrs.text_rendering = {name = 'text-rendering', parser = default};
498 | attrs['text-rendering'] = attrs.text_rendering;
499 | attrs.textLength = {name = 'textLength', parser = default};
500 | attrs.to = {name = 'to', parser = default};
501 | attrs.transform = {name = 'transform', parser = default};
502 | attrs['type'] = {name = 'type', parser = default};
503 |
504 | attrs.u1 = {name = '', parser = default};
505 | attrs.u2 = {name = '', parser = default};
506 | attrs.underline_position = {name = 'underline-position', parser = number};
507 | attrs['underline-position'] = attrs.underline_position;
508 | attrs.underline_thickness = {name = 'underline-thickness', parser = number};
509 | attrs['underline-thickness'] = attrs.underline_thickness;
510 | attrs.unicode = {name = 'unicode', parser = default};
511 | attrs.unicode_bidi = {name = 'unicode-bidi', parser = default};
512 | attrs['unicode-bidi'] = attrs.unicode_bidi;
513 | attrs.unicode_range = {name = 'unicode-range', parser = default};
514 | attrs['unicode-range'] = attrs.unicode_range;
515 | attrs.units_per_em = {name = 'units-per-em', parser = number};
516 | attrs['units-per-em'] = attrs.units_per_em;
517 |
518 | attrs.v_alphabetic = {name = 'v-alphabetic', parser = number};
519 | attrs['v-alphabetic'] = attrs.v_alphabetic;
520 | attrs.v_hanging = {name = 'v-hanging', parser = number};
521 | attrs['v-hanging'] = attrs.v_hanging;
522 | attrs.v_ideographic = {name = 'v-ideographic', parser = number};
523 | attrs['v-ideographic'] = attrs.v_ideographic;
524 | attrs.v_mathematical = {name = 'v-mathematical', parser = default};
525 | attrs['v-mathematical'] = attrs.v_mathematical;
526 | attrs.values = {name = 'values', parser = default};
527 | attrs.version = {name = 'version', parser = default};
528 | attrs.vert_adv_y = {name = 'v-adv-y', parser = default};
529 | attrs['vert-adv-y'] = attrs.vert_adv_y;
530 | attrs.vert_origin_x = {name = 'v-origin-x', parser = default};
531 | attrs['vert-origin-x'] = attrs.v_origin_x;
532 | attrs.vert_origin_y = {name = 'vert-origin-y', parser = default};
533 | attrs['vert-origin-y'] = attrs.vert_origin_y;
534 | attrs.viewBox = {name = 'viewBox', parser = parseviewBox};
535 | attrs.visibility = {name = 'visibility', parser = default};
536 |
537 | attrs.width = {name = 'width', parser = length};
538 | attrs.widths = {name = 'widths', parser = default};
539 | attrs.word_spacing = {name = 'word-spacing', parser = default};
540 | attrs['word-spacing'] = attrs.word_spacing;
541 | attrs.writing_mode = {name = 'writing-mode', parser = default};
542 | attrs['writing-mode'] = attrs.writing_mode;
543 |
544 | attrs.x = {name='x', parser=coord};
545 | attrs.x_height = {name='x-height', parser=number};
546 | attrs['x-height'] = attrs.x_height;
547 | attrs.x1 = {name='x1', parser=coord};
548 | attrs.x2 = {name='x2', parser=coord};
549 | attrs.xChannelSelector = {name = 'xChannelSelector', parser = default};
550 | attrs.xlink_actuate = {name = 'xlink:actuate', parser = default};
551 | attrs['xlink:actuate'] = attrs.xlink_actuate;
552 | attrs.xlink_arcrole = {name = 'xlink:arcrole', parser = default};
553 | attrs['xlink:arcrole'] = attrs.xlink_arcrole;
554 | attrs.xlink_href = {name='xlink:href', parser=default};
555 | attrs['xlink:href'] = attrs.xlink_href;
556 | attrs.xlink_role = {name = 'xlink:role', parser = default};
557 | attrs['xlink:role'] = attrs.xlink_role;
558 | attrs.xlink_show = {name = 'xlink:show', parser = default};
559 | attrs['xlink:show'] = attrs.xlink_show;
560 | attrs.xlink_title = {name = 'xlink:title', parser = default};
561 | attrs['xlink:title'] = attrs.xlink_title;
562 | attrs.xlink_type = {name = 'xlink:type', parser = default};
563 | attrs['xlink:type'] = attrs.xlink_type;
564 | attrs.xml_base = {name = 'xml:base', parser = default};
565 | attrs['xml:base'] = attrs.xml_base;
566 | attrs.xml_lang = {name = 'xml:lang', parser = default};
567 | attrs['xml:lang'] = attrs.xml_lang;
568 | attrs.xml_space = {name = 'xml:space', parser = default};
569 | attrs['xml:space'] = attrs.xml_space;
570 |
571 | attrs.y = {name = 'y', parser = coord};
572 | attrs.y1 = {name = 'y1', parser = coord};
573 | attrs.y2 = {name = 'y2', parser = coord};
574 | attrs.yChannelSelector = {name = 'yChannelSelector', parser = default};
575 |
576 | attrs.z = {name = 'z', parser = coord};
577 | attrs.zoomAndPan = {name = 'zoomAndPan', parser = default};
578 |
579 |
580 | function attrs.parseAttribute(name, value, strict)
581 | print("parseAttribute: ", name, value)
582 | local func = attrs[name];
583 |
584 | if not func then
585 | if not strict then
586 | -- Be permissive, if the name is not found,
587 | -- just return what was passed in
588 | --print("attrs.parseAttribute, NOFUNC: ", name, value)
589 | return name, value;
590 | else
591 | -- If we're being strict, then we don't
592 | -- return anything if it's not a valid attribute name
593 | return nil
594 | end
595 | end
596 |
597 | --print("parseAttribute (func.name, func.parser): ", func.name, func.parser)
598 | return func.parser(func.name, value, strict)
599 | end
600 |
601 | return attrs;
602 |
--------------------------------------------------------------------------------
/remotesvg/SVGParser.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | A simple SVG Parser which fills in a lua based object model
3 | for the image specified.
4 | --]]
5 |
6 | local ffi = require("ffi")
7 | local bit = require("bit")
8 | local lshift, rshift, band, bor = bit.lshift, bit.rshift, bit.band, bit.bor
9 |
10 | local XmlParser = require("remotesvg.SVGXmlParser")
11 | local isspace = ctypes.isspace;
12 | local isdigit = ctypes.isdigit;
13 |
14 | local maths = require("remotesvg.maths")
15 | local clamp = maths.clamp;
16 | local sqr = maths.sqr;
17 | local vecrat = maths.vecrat;
18 | local vecang = maths.vecang;
19 | local vmag = maths.vmag;
20 |
21 |
22 | local sqrt = math.sqrt;
23 | local fabs = math.abs;
24 | local abs = math.abs;
25 | local sin = math.sin;
26 | local cos = math.cos;
27 |
28 | local RGB = colors.RGBA;
29 |
30 | local svg = require("remotesvg.SVGElements")
31 | --[[
32 | local PaintType = SVGTypes.PaintType;
33 | local FillRule = SVGTypes.FillRule;
34 | local Flags = SVGTypes.Flags;
35 | local LineCap = SVGTypes.LineCap;
36 | local LineJoin = SVGTypes.LineJoin;
37 | local SVGGradientStop = SVGTypes.SVGGradientStop;
38 | local SVGGradient = SVGTypes.SVGGradient;
39 | local SVGPaint = SVGTypes.SVGPaint;
40 | --]]
41 |
42 |
43 | local SVG_PI = math.pi; -- (3.14159265358979323846264338327f)
44 | local SVG_KAPPA90 = 0.5522847493 -- Length proportional to radius of a cubic bezier handle for 90-deg arcs.
45 | local SVG_EPSILON = 1e-12;
46 |
47 | local SVG_ALIGN_MIN = 0;
48 | local SVG_ALIGN_MID = 1;
49 | local SVG_ALIGN_MAX = 2;
50 | local SVG_ALIGN_NONE = 0;
51 | local SVG_ALIGN_MEET = 1;
52 | local SVG_ALIGN_SLICE = 2;
53 |
54 |
55 |
56 | local minf = math.min;
57 | local maxf = math.max;
58 |
59 | -- Simple SVG parser.
60 | local SVG_MAX_ATTR = 128;
61 |
62 | local SVGgradientUnits = {
63 | NSVG_USER_SPACE = 0;
64 | NSVG_OBJECT_SPACE = 1;
65 | };
66 |
67 |
68 | local SVGUnits = {
69 | USER = 0,
70 | PX = 1,
71 | PT = 2,
72 | PC = 3,
73 | MM = 4,
74 | CM = 5,
75 | IN = 6,
76 | PERCENT = 7,
77 | EM = 8,
78 | EX = 9,
79 | };
80 |
81 |
82 | ffi.cdef[[
83 | typedef struct pt2D
84 | {
85 | union {
86 | struct {
87 | double x, y;
88 | };
89 | double v[2];
90 | };
91 | } pt2D_t;
92 | ]]
93 |
94 |
95 |
96 | local pt2D = ffi.typeof("struct pt2D");
97 |
98 | ffi.cdef[[
99 | typedef struct SVGCoordinate {
100 | float value;
101 | int units;
102 | } SVGCoordinate_t;
103 | ]]
104 | local SVGCoordinate = ffi.typeof("struct SVGCoordinate");
105 |
106 | ffi.cdef[[
107 | typedef struct SVGLinearData {
108 | struct SVGCoordinate x1, y1, x2, y2;
109 | } SVGLinearData_t;
110 | ]]
111 | local SVGLinearData = ffi.typeof("struct SVGLinearData")
112 |
113 |
114 | ffi.cdef[[
115 | typedef struct SVGRadialData {
116 | struct SVGCoordinate cx, cy, r, fx, fy;
117 | } SVGRadialData_t;
118 | ]]
119 | local SVGRadialData = ffi.typeof("struct SVGRadialData")
120 |
121 |
122 | ffi.cdef[[
123 | typedef struct SVGGradientData
124 | {
125 | char id[64];
126 | char ref[64];
127 | char type;
128 | union {
129 | struct SVGLinearData linear;
130 | struct SVGRadialData radial;
131 | };
132 | char spread;
133 | char units;
134 | double xform[6];
135 | int nstops;
136 | struct SVGGradientStop* stops;
137 | struct SVGGradientData* next;
138 | } SVGGradientData_t;
139 | ]]
140 | local SVGGradientData = ffi.typeof("struct SVGGradientData")
141 |
142 | ffi.cdef[[
143 | static const int SVG_MAX_DASHES = 8;
144 |
145 | typedef struct SVGAttrib
146 | {
147 | char id[64];
148 | double xform[6];
149 | uint32_t fillColor;
150 | uint32_t strokeColor;
151 | float opacity;
152 | float fillOpacity;
153 | float strokeOpacity;
154 | char fillGradient[64];
155 | char strokeGradient[64];
156 | float strokeWidth;
157 | float strokeDashOffset;
158 | float strokeDashArray[SVG_MAX_DASHES];
159 | int strokeDashCount;
160 | char strokeLineJoin;
161 | char strokeLineCap;
162 | char fillRule;
163 | float fontSize;
164 | unsigned int stopColor;
165 | float stopOpacity;
166 | float stopOffset;
167 | char hasFill;
168 | char hasStroke;
169 | char visible;
170 | } SVGattrib_t;
171 | ]]
172 | local SVGAttrib = ffi.typeof("struct SVGAttrib")
173 |
174 |
175 | local function AttributeStack()
176 | local obj = {}
177 | setmetatable(obj, {
178 | __index = obj;
179 | })
180 |
181 | function obj.push(self, value)
182 | if not value then
183 | value = SVGAttrib();
184 | -- clone whatever is on the top of the stack
185 | -- right now
186 | local topper = self:top()
187 | if topper then
188 | ffi.copy(value, topper, ffi.sizeof(SVGAttrib))
189 | end
190 | end
191 |
192 | table.insert(self, value);
193 |
194 | return value;
195 | end
196 |
197 | function obj.pop(self)
198 | return table.remove(self)
199 | end
200 |
201 | function obj.top(self)
202 | if #self < 1 then
203 | return nil;
204 | end
205 |
206 | return self[#self];
207 | end
208 |
209 | return obj;
210 | end
211 |
212 |
213 | local function strncpy(dst, value, limit)
214 | local s = ffi.cast("const char *", value)
215 | for i=0,limit-1 do
216 | if value[i] == 0 then
217 | break;
218 | end
219 |
220 | dst[i] = s[i];
221 | end
222 |
223 | return dst;
224 | end
225 |
226 | local function strncmp(str1, str2, num)
227 | local ptr1 = ffi.cast("const char*", str1)
228 | local ptr2 = ffi.cast("const char*", str2)
229 |
230 | for i=0,num-1 do
231 | if ptr1[i] == 0 or ptr2[i] == 0 then return 0 end
232 |
233 | if ptr1[i] > ptr2[i] then return 1 end
234 | if ptr1[i] < ptr2[i] then return -1 end
235 | end
236 |
237 | return 0
238 | end
239 |
240 | --[[
241 | bounds[0] == left
242 | bounds[1] == top
243 | bounds[2] == right
244 | bounds[3] == bottom
245 | --]]
246 | local function ptInBounds(pt, bounds)
247 | return pt.x >= bounds[0] and
248 | pt.x <= bounds[2] and
249 | pt.y >= bounds[1] and
250 | pt.y <= bounds[3];
251 | end
252 |
253 |
254 | local function curveBoundary(bounds, curve, idx)
255 | print("curveBoundary: ", #curve, idx)
256 | local i, j, count = 0,0,0;
257 | local roots = ffi.new("double[2]");
258 | local a, b, c, b2ac, t, v = 0,0,0,0,0,0;
259 |
260 |
261 | local v0 = curve[idx];
262 | local v1 = curve[idx+1];
263 | local v2 = curve[idx+2];
264 | local v3 = curve[idx+3];
265 |
266 | -- Start the bounding box by end points
267 | bounds[0] = minf(v0.x, v3.x);
268 | bounds[1] = minf(v0.y, v3.y);
269 | bounds[2] = maxf(v0.x, v3.x);
270 | bounds[3] = maxf(v0.y, v3.y);
271 |
272 |
273 | -- Bezier curve fits inside the convex hull of it's control points.
274 | -- If control points are inside the bounds, we're done.
275 | if (ptInBounds(v1, bounds) and ptInBounds(v2, bounds)) then
276 | return;
277 | end
278 |
279 | -- Add bezier curve inflection points in X and Y.
280 |
281 | for i = 0, 1 do
282 | a = -3.0 * v0.v[i] + 9.0 * v1.v[i] - 9.0 * v2.v[i] + 3.0 * v3.v[i];
283 | b = 6.0 * v0.v[i] - 12.0 * v1.v[i] + 6.0 * v2.v[i];
284 | c = 3.0 * v1.v[i] - 3.0 * v0.v[i];
285 |
286 | local count = 0;
287 | if (abs(a) < SVG_EPSILON) then
288 | if (abs(b) > SVG_EPSILON) then
289 | t = -c / b;
290 | if (t > SVG_EPSILON and t < 1.0-SVG_EPSILON) then
291 | roots[count] = t;
292 | count = count + 1;
293 | end
294 | end
295 | else
296 | b2ac = b*b - 4.0*c*a;
297 | if (b2ac > SVG_EPSILON) then
298 | t = (-b + sqrt(b2ac)) / (2.0 * a);
299 | if (t > SVG_EPSILON and t < 1.0-SVG_EPSILON) then
300 | roots[count] = t;
301 | count = count + 1;
302 | end
303 |
304 | t = (-b - sqrt(b2ac)) / (2.0 * a);
305 | if (t > SVG_EPSILON and t < 1.0-SVG_EPSILON) then
306 | roots[count] = t;
307 | count = count + 1;
308 | end
309 | end
310 | end
311 | --[[
312 | for (j = 0; j < count; j++) {
313 | v = Bezier.evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]);
314 | bounds[0+i] = minf(bounds[0+i], v);
315 | bounds[2+i] = maxf(bounds[2+i], v);
316 | }
317 | --]]
318 | end
319 |
320 | end
321 |
322 |
323 |
324 |
325 | local SVGParser = {}
326 | setmetatable(SVGParser, {
327 | __call = function(self, ...)
328 | return self:new(...);
329 | end
330 | })
331 | local SVGParser_mt = {
332 | __index = SVGParser;
333 | }
334 |
335 | function SVGParser.init(self)
336 | local obj = {
337 | attr = AttributeStack();
338 | pts = {};
339 | plist = {};
340 | image = SVGImage();
341 | gradients = {};
342 |
343 | viewMinx = 0;
344 | viewMiny = 0;
345 | viewWidth = 0;
346 | viewHeight = 0;
347 |
348 | -- alignment
349 | alignX = 0;
350 | alignY = 0;
351 | alignType = 0;
352 |
353 | dpi = 96;
354 |
355 | pathFlag = false;
356 | defsFlag = false;
357 | }
358 |
359 | setmetatable(obj, SVGParser_mt);
360 |
361 |
362 |
363 |
364 | -- Initialize style with first attribute
365 | local attrib = SVGAttrib();
366 | transform2D.xformIdentity(attrib.xform);
367 | attrib.fillColor = RGB(0,0,0);
368 | attrib.strokeColor = RGB(0,0,0);
369 | attrib.opacity = 1;
370 | attrib.fillOpacity = 1;
371 | attrib.strokeOpacity = 1;
372 | attrib.stopOpacity = 1;
373 | attrib.strokeWidth = 1;
374 | attrib.strokeLineJoin = LineJoin.MITER;
375 | attrib.strokeLineCap = LineCap.BUTT;
376 | attrib.fillRule = FillRule.NONZERO;
377 | attrib.hasFill = 1;
378 | attrib.visible = 1;
379 | obj.attr:push(attrib);
380 |
381 | return obj;
382 | end
383 |
384 | function SVGParser.new(self)
385 | local parser = self:init()
386 |
387 | return parser;
388 | end
389 |
390 | function SVGParser.parse(self, input, units, dpi)
391 | self.dpi = dpi;
392 |
393 | --self:parseXML(input, SVGParser.startElement, SVGParser.endElement, SVGParser.content, self);
394 | XmlParser.parseXML(input, SVGParser.startElement, SVGParser.endElement, SVGParser.content, self)
395 |
396 | -- Scale to viewBox
397 | self:scaleToViewbox(units);
398 |
399 | local ret = self.image;
400 | self.image = NULL;
401 |
402 | return ret;
403 | end
404 |
405 | function SVGParser.parseFromFile(self, filename, units, dpi)
406 | local fp = io.open(filename, "rb")
407 | if not fp then
408 | return
409 | end
410 |
411 | local data = fp:read("*a");
412 | fp:close();
413 |
414 | local parser = SVGParser();
415 | local image = parser:parse(data, units, dpi)
416 |
417 | return image;
418 | end
419 |
420 |
421 |
422 | function SVGParser.resetPath(self)
423 | self.pts = {};
424 | end
425 |
426 | function SVGParser.addPoint(self, x, y)
427 | --print("addPoint: ", x, type(x), y, type(y))
428 | table.insert(self.pts, pt2D({tonumber(x),tonumber(y)}));
429 | end
430 |
431 | function SVGParser.moveTo(self, x, y)
432 | self:addPoint(x, y);
433 | end
434 |
435 | -- Add a line segment. The must be at least
436 | -- one starting point already
437 | function SVGParser.lineTo(self, x, y)
438 | if #self.pts > 0 then
439 | local lastpt = self.pts[#self.pts];
440 | local px = lastpt.x;
441 | local py = lastpt.y;
442 | dx = x - px;
443 | dy = y - py;
444 | self:addPoint(px + dx/3.0, py + dy/3.0);
445 | self:addPoint(x - dx/3.0, y - dy/3.0);
446 | self:addPoint(x, y);
447 | end
448 | end
449 |
450 | function SVGParser.cubicBezTo(self, cpx1, cpy1, cpx2, cpy2, x, y)
451 | self:addPoint(cpx1, cpy1);
452 | self:addPoint(cpx2, cpy2);
453 | self:addPoint(x, y);
454 | end
455 |
456 | function SVGParser.getAttr(self)
457 | return self.attr:top();
458 | end
459 |
460 | function SVGParser.pushAttr(self)
461 | -- take the attribute currently on top
462 | -- make a copy
463 | -- and push that copy on top of the stack
464 | self.attr:push();
465 | end
466 |
467 | function SVGParser.popAttr(self)
468 | return self.attr:pop();
469 | end
470 |
471 | function SVGParser.actualOrigX(self)
472 | return self.viewMinx;
473 | end
474 |
475 | function SVGParser.actualOrigY(self)
476 | return self.viewMiny;
477 | end
478 |
479 | function SVGParser.actualWidth(self)
480 | return self.viewWidth;
481 | end
482 |
483 | function SVGParser.actualHeight(self)
484 | return self.viewHeight;
485 | end
486 |
487 | function SVGParser.actualLength(self)
488 | local w = self:actualWidth();
489 | local h = self:actualHeight();
490 |
491 | return sqrt(w*w + h*h) / sqrt(2.0);
492 | end
493 |
494 |
495 | function SVGParser.convertToPixels(self, c, orig, length)
496 | --print("convertToPixels: ", self, c, orig, length)
497 | local attr = self:getAttr();
498 |
499 | if c.units == SVGUnits.USER then
500 | return c.value;
501 | elseif c.units == SVGUnits.PX then return c.value;
502 | elseif c.units == SVGUnits.PT then return c.value / 72.0 * self.dpi;
503 | elseif c.units == SVGUnits.PC then return c.value / 6.0 * self.dpi;
504 | elseif c.units == SVGUnits.MM then return c.value / 25.4 * self.dpi;
505 | elseif c.units == SVGUnits.CM then return c.value / 2.54 * self.dpi;
506 | elseif c.units == SVGUnits.IN then return c.value * self.dpi;
507 | elseif c.units == SVGUnits.EM then return c.value * attr.fontSize;
508 | elseif c.units == SVGUnits.EX then return c.value * attr.fontSize * 0.52; -- x-height of Helvetica.
509 | elseif c.units == SVGUnits.PERCENT then
510 | return orig + c.value / 100.0 * length;
511 | end
512 |
513 | return c.value;
514 | end
515 |
516 |
517 | function SVGParser.findGradientData(self, id)
518 | return self.gradients[id];
519 | --[[
520 | NSVGgradientData* grad = self.gradients;
521 | while (grad) {
522 | if (strcmp(grad.id, id) == 0)
523 | return grad;
524 | grad = grad.next;
525 | }
526 | return NULL;
527 | --]]
528 | end
529 |
530 |
531 | function SVGParser.createGradient(self, id, localBounds, paintType)
532 |
533 | local attr = self:getAttr();
534 | --NSVGgradientData* data = NULL;
535 | --NSVGgradientData* ref = NULL;
536 | --NSVGgradientStop* stops = NULL;
537 | --NSVGgradient* grad;
538 | local ox, oy, sw, sh, sl= 0,0,0,0,0;
539 | local nstops = 0;
540 |
541 | local data = self:findGradientData(id);
542 | if data == nil then
543 | return nil;
544 | end
545 |
546 | --[[
547 | -- TODO: use ref to fill in all unset values too.
548 | local ref = data;
549 | while (ref ~= NULL) do
550 | if (stops == NULL and ref.stops != NULL) {
551 | stops = ref.stops;
552 | nstops = ref.nstops;
553 | break;
554 | }
555 | ref = self:findGradientData(p, ref.ref);
556 | end
557 |
558 | if (stops == NULL) then
559 | return NULL;
560 | end
561 |
562 | --grad = (NSVGgradient*)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(nstops-1));
563 | local grad = SVGGradient();
564 |
565 | --if (grad == NULL) return NULL;
566 |
567 | -- The shape width and height.
568 | if (data.units == NSVG_OBJECT_SPACE) {
569 | ox = localBounds[0];
570 | oy = localBounds[1];
571 | sw = localBounds[2] - localBounds[0];
572 | sh = localBounds[3] - localBounds[1];
573 | else
574 | ox = self:actualOrigX(p);
575 | oy = self:actualOrigY(p);
576 | sw = self:actualWidth(p);
577 | sh = self:actualHeight(p);
578 | }
579 | sl = sqrtf(sw*sw + sh*sh) / sqrt(2.0);
580 |
581 | if (data.type == SVGTypes.PaintType.LINEAR_GRADIENT) then
582 | --float x1, y1, x2, y2, dx, dy;
583 | local x1 = self:convertToPixels(data.linear.x1, ox, sw);
584 | local y1 = self:convertToPixels(data.linear.y1, oy, sh);
585 | local x2 = self:convertToPixels(data.linear.x2, ox, sw);
586 | local y2 = self:convertToPixels(data.linear.y2, oy, sh);
587 | -- Calculate transform aligned to the line
588 | local dx = x2 - x1;
589 | local dy = y2 - y1;
590 | grad.xform[0] = dy; grad.xform[1] = -dx;
591 | grad.xform[2] = dx; grad.xform[3] = dy;
592 | grad.xform[4] = x1; grad.xform[5] = y1;
593 | else
594 | --float cx, cy, fx, fy, r;
595 | local cx = self:convertToPixels(p, data.radial.cx, ox, sw);
596 | local cy = self:convertToPixels(p, data.radial.cy, oy, sh);
597 | local fx = self:convertToPixels(p, data.radial.fx, ox, sw);
598 | local fy = self:convertToPixels(p, data.radial.fy, oy, sh);
599 | local r = self:convertToPixels(p, data.radial.r, 0, sl);
600 | -- Calculate transform aligned to the circle
601 | grad.xform[0] = r; grad.xform[1] = 0;
602 | grad.xform[2] = 0; grad.xform[3] = r;
603 | grad.xform[4] = cx; grad.xform[5] = cy;
604 | grad.fx = fx / r;
605 | grad.fy = fy / r;
606 | end
607 |
608 | xform.xformMultiply(grad.xform, data.xform);
609 | xform.xformMultiply(grad.xform, attr.xform);
610 |
611 | grad.spread = data.spread;
612 | ffi.copy(grad.stops, stops, nstops*sizeof(NSVGgradientStop));
613 | grad.nstops = nstops;
614 |
615 |
616 | return grad, data.type;
617 | --]]
618 | end
619 |
620 |
621 | function SVGParser.getAverageScale(self, t)
622 |
623 | local sx = sqrt(t[0]*t[0] + t[2]*t[2]);
624 | local sy = sqrt(t[1]*t[1] + t[3]*t[3]);
625 |
626 | return (sx + sy) * 0.5;
627 | end
628 |
629 | --[[
630 | function NSVGParser.getLocalBounds(float* bounds, NSVGshape *shape, float* xform)
631 |
632 | NSVGpath* path;
633 | float curve[4*2], curveBounds[4];
634 | int i,
635 | local first = true;
636 |
637 | for (path = shape.paths; path != NULL; path = path.next) do
638 | curve[0], curve[1] = xform.xformPoint(path.pts[0], path.pts[1], xform);
639 | for (i = 0; i < path.npts-1; i += 3) {
640 | curve[2], curve[3] = xform.xformPoint(path.pts[(i+1)*2], path.pts[(i+1)*2+1], xform);
641 | curve[4], curve[5] = xform.xformPoint(path.pts[(i+2)*2], path.pts[(i+2)*2+1], xform);
642 | curve[6], curve[7] = xform.xformPoint(path.pts[(i+3)*2], path.pts[(i+3)*2+1], xform);
643 | curveBoundary(curveBounds, curve);
644 | if (first) then
645 | bounds[0] = curveBounds[0];
646 | bounds[1] = curveBounds[1];
647 | bounds[2] = curveBounds[2];
648 | bounds[3] = curveBounds[3];
649 | first = false;
650 | else
651 | bounds[0] = self:minf(bounds[0], curveBounds[0]);
652 | bounds[1] = self:minf(bounds[1], curveBounds[1]);
653 | bounds[2] = self:maxf(bounds[2], curveBounds[2]);
654 | bounds[3] = self:maxf(bounds[3], curveBounds[3]);
655 | end
656 | curve[0] = curve[6];
657 | curve[1] = curve[7];
658 | }
659 | end
660 | end
661 | --]]
662 |
663 |
664 | function SVGParser.addShape(self)
665 |
666 | local attr = self:getAttr();
667 | local scale = 1.0;
668 |
669 |
670 | if (self.plist == NULL) then
671 | return;
672 | end
673 |
674 | local shape = SVGShape();
675 |
676 | shape.id = ffi.string(attr.id);
677 | --print("addShape(), shape.id: ", shape.id)
678 |
679 | scale = self:getAverageScale(attr.xform);
680 | shape.strokeWidth = attr.strokeWidth * scale;
681 | shape.strokeDashOffset = attr.strokeDashOffset * scale;
682 | shape.strokeDashCount = attr.strokeDashCount;
683 |
684 |
685 | for i = 0, attr.strokeDashCount-1 do
686 | shape.strokeDashArray[i] = attr.strokeDashArray[i] * scale;
687 | end
688 |
689 | shape.strokeLineJoin = attr.strokeLineJoin;
690 | shape.strokeLineCap = attr.strokeLineCap;
691 | shape.fillRule = attr.fillRule;
692 | shape.opacity = attr.opacity;
693 |
694 | shape.paths = self.plist;
695 | self.plist = {};
696 |
697 | -- Calculate shape bounds
698 | for idx, path in ipairs(shape.paths) do
699 | shape.bounds[0] = minf(shape.bounds[0], path.bounds[0]);
700 | shape.bounds[1] = minf(shape.bounds[1], path.bounds[1]);
701 | shape.bounds[2] = maxf(shape.bounds[2], path.bounds[2]);
702 | shape.bounds[3] = maxf(shape.bounds[3], path.bounds[3]);
703 | end
704 |
705 |
706 | -- Set fill
707 | if (attr.hasFill == 0) then
708 | shape.fill.type = PaintType.NONE;
709 | elseif (attr.hasFill == 1) then
710 | shape.fill.type = PaintType.COLOR;
711 | shape.fill.color = attr.fillColor;
712 | shape.fill.color = bor(shape.fill.color, lshift((attr.fillOpacity*255), 24));
713 | elseif attr.hasFill == 2 then
714 | local inv = ffi.new("double[6]");
715 | local localBounds ffi.new("double[4]");
716 | transform2D.xformInverse(inv, attr.xform);
717 | self:getLocalBounds(localBounds, shape, inv);
718 | shape.fill.gradient, shape.fill.type = self:createGradient(attr.fillGradient, localBounds);
719 | if (shape.fill.gradient == nil) then
720 | shape.fill.type = PaintType.NONE;
721 | end
722 | end
723 |
724 |
725 |
726 | -- Set stroke
727 | if (attr.hasStroke == 0) then
728 | shape.stroke.type = PaintType.NONE;
729 | elseif (attr.hasStroke == 1) then
730 | shape.stroke.type = PaintType.COLOR;
731 | shape.stroke.color = attr.strokeColor;
732 | shape.stroke.color = bor(shape.stroke.color, lshift((attr.strokeOpacity*255), 24));
733 | elseif (attr.hasStroke == 2) then
734 | local inv = ffi.new("double[6]")
735 | local localBounds = ffi.new("double[4]");
736 |
737 | transform2D.xformInverse(inv, attr.xform);
738 | self:getLocalBounds(localBounds, shape, inv);
739 | shape.stroke.gradient, shape.stroke.type = self:createGradient(attr.strokeGradient, localBounds);
740 | if (shape.stroke.gradient == NULL) then
741 | shape.stroke.type = PaintType.NONE;
742 | end
743 | end
744 |
745 |
746 | -- Set flags
747 | if attr.visible ~= 0 then
748 | shape.flags = Flags.VISIBLE
749 | else
750 | shape.flags = 0;
751 | end
752 |
753 | -- Add to tail
754 | table.insert(self.image.shapes, shape);
755 |
756 | return;
757 | end
758 |
759 |
760 |
761 | function SVGParser.addPath(self, closed)
762 | local attr = self:getAttr();
763 | local bounds = ffi.new("double[4]");
764 | --float* curve;
765 | --int i;
766 |
767 | --print("addPath: ", #self.pts)
768 |
769 | if (#self.pts < 4) then
770 | return;
771 | end
772 |
773 | local path = SVGPath();
774 |
775 | path.closed = closed;
776 |
777 | -- Transform path.
778 | for i, pt in ipairs(self.pts) do
779 | pt.x, pt.y = transform2D.xformPoint(pt.x, pt.y, attr.xform);
780 | table.insert(path.pts, pt);
781 | end
782 |
783 | -- Find bounds
784 | for i = 1, #path.pts-1, 3 do
785 | --curve = &path.pts[i*2];
786 | curveBoundary(bounds, path.pts, i);
787 | if (i == 1) then
788 | path.bounds[0] = bounds[0];
789 | path.bounds[1] = bounds[1];
790 | path.bounds[2] = bounds[2];
791 | path.bounds[3] = bounds[3];
792 | else
793 | path.bounds[0] = minf(path.bounds[0], bounds[0]);
794 | path.bounds[1] = minf(path.bounds[1], bounds[1]);
795 | path.bounds[2] = maxf(path.bounds[2], bounds[2]);
796 | path.bounds[3] = maxf(path.bounds[3], bounds[3]);
797 | end
798 | end
799 |
800 | --path:dump();
801 | table.insert(self.plist, path);
802 |
803 | return;
804 | end
805 |
806 |
807 | -- parse a numeric value out of a string pointer
808 | -- stuff it into the 'it' buffer, which is of size 'size'
809 | -- and return the pointer one character past the last
810 | -- digit parsed.
811 | function SVGParser.parseNumber(self, s, it, size)
812 | --print(".parseNumber: ", ffi.string(s), size)
813 |
814 | local last = size-1;
815 | local i = 0;
816 |
817 | -- sign
818 | if (s[0] == string.byte('-') or s[0] == string.byte('+')) then
819 | if i < last then
820 | it[i] = s[0];
821 | i = i + 1;
822 | end
823 |
824 | s = s + 1;
825 | end
826 |
827 | -- integer part
828 | while s[0]~= 0 and isdigit(s[0]) do
829 | if (i < last) then
830 | it[i] = s[0];
831 | i = i + 1;
832 | end
833 |
834 | s = s + 1;
835 | end
836 |
837 | if s[0] == string.byte('.') then
838 | -- decimal point
839 | if (i < last) then
840 | it[i] = s[0];
841 | i = i + 1;
842 | end
843 |
844 | s = s + 1;
845 |
846 | -- fraction part
847 | while s[0] ~= 0 and isdigit(s[0]) do
848 | if (i < last) then
849 | it[i] = s[0];
850 | i = i + 1;
851 | end
852 | s = s + 1;
853 | end
854 | end
855 |
856 | -- exponent
857 | if (s[0] == string.byte('e') or s[0] == string.byte('E')) then
858 | if (i < last) then
859 | it[i] = s[0];
860 | i = i + 1;
861 | end
862 | s = s + 1;
863 |
864 | if s[0] == string.byte('-') or s[0] == string.byte('+') then
865 | if (i < last) then
866 | it[i] = s[0];
867 | i = i + 1;
868 | end
869 | s = s + 1;
870 | end
871 |
872 | while s[0] and isdigit(s[0]) do
873 | if (i < last) then
874 | it[i] = s[0];
875 | i = i + 1;
876 | end
877 | s = s + 1;
878 | end
879 | end
880 |
881 | it[i] = 0;
882 | --print(".parseNumber - END: ", ffi.string(it))
883 |
884 | return s, tonumber(ffi.string(it));
885 | end
886 |
887 |
888 | --[[
889 | static const char* self:getNextPathItem(self, const char* s, char* it)
890 |
891 | it[0] = '\0';
892 | -- Skip white spaces and commas
893 | while (*s && (self:isspace(*s) || *s == ',')) s++;
894 | if (!*s) return s;
895 | if (*s == '-' || *s == '+' || *s == '.' || self:isdigit(*s)) {
896 | s = self:parseNumber(s, it, 64);
897 | } else {
898 | // Parse command
899 | it[0] = *s++;
900 | it[1] = '\0';
901 | return s;
902 | }
903 |
904 | return s;
905 | end
906 | --]]
907 |
908 |
909 | function SVGParser.parseColorHex(self, s)
910 | --print(".parseColorHex: ", s)
911 |
912 | local rgb = s:match("#(%g+)")
913 | --print("rgb: ", rgb)
914 | local r,g,b = 0,0,0;
915 | local c = 0;
916 | rgb = "0x"..rgb;
917 |
918 | if #rgb == 6 then
919 | c = tonumber(rgb)
920 | elseif #rgb == 3 then
921 | c = tonumber(rgb);
922 | c = bor(band(c,0xf), lshift(band(c,0xf0), 4), lshift(band(c,0xf00), 8));
923 | c = bor(c, lshift(c,4));
924 | end
925 | b = band(rshift(c, 16), 0xff);
926 | g = band(rshift(c, 8), 0xff);
927 | r = band(c, 0xff);
928 |
929 | return RGB(r,g,b);
930 | end
931 |
932 |
933 |
934 | function SVGParser.parseColorRGB(self, str)
935 | local percentvaluepatt = "(%d+%%),(%d+%%),(%d+%%)"
936 | local valuepatt = "(%d+)%s*,%s*(%d+)%s*,%s*(%d+)"
937 |
938 | local r,g,b = str:match(valuepatt)
939 | r,g,b = tonumber(r), tonumber(g), tonumber(b)
940 |
941 | --print("parseColorRGB: ", str, r, g, b)
942 |
943 | --[[
944 | char s1[32]="", s2[32]="";
945 | sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b);
946 | if (strchr(s1, '%')) {
947 | return RGB((r*255)/100,(g*255)/100,(b*255)/100);
948 | else
949 | return RGB(r,g,b);
950 | end
951 | --]]
952 |
953 | return RGB(r,g,b)
954 | end
955 |
956 |
957 | function SVGParser.parseColorName(self, name)
958 | return colors.svg[name] or RGB(128, 128, 128);
959 | end
960 |
961 |
962 | function SVGParser.parseColor(self, s)
963 | local str = s:match("%s*(.*)")
964 | local len = #str;
965 |
966 | -- print("SVGParser.parseColor: ", str, len)
967 |
968 | if len >= 1 and str:sub(1,1) == '#' then
969 | return self:parseColorHex(str);
970 | elseif (len >= 4 and str:match("rgb%(")) then
971 | return self:parseColorRGB(str);
972 | end
973 |
974 | return self:parseColorName(str);
975 | end
976 |
977 |
978 |
979 | function SVGParser.parseOpacity(self, str)
980 | local val = clamp(tonumber(str), 0, 1);
981 |
982 | return val;
983 | end
984 |
985 |
986 | function SVGParser.parseUnits(self, units)
987 | --print(".parseUnits: ", units)
988 |
989 | local unitConverter = {
990 | px = SVGUnits.PX;
991 | pt = SVGUnits.PT;
992 | pc = SVGUnits.PC;
993 | mm = SVGUnits.MM;
994 | cm = SVGUnits.CM;
995 | ["in"] = SVGUnits.IN;
996 | ["%"] = SVGUnits.PERCENT;
997 | em = SVGUnits.EM;
998 | ex = SVGUnits.EX;
999 | }
1000 |
1001 | return unitConverter[units] or SVGUnits.USER;
1002 | end
1003 |
1004 | function SVGParser.parseCoordinateRaw(self, str)
1005 | local coord = SVGCoordinate({0, SVGUnits.USER});
1006 | --print(".parseCoordinateRaw: ", str)
1007 |
1008 | --sscanf(str, "%f%s", &coord.value, units);
1009 | local num, units = str:match("(%d*%.?%d*)(.*)")
1010 |
1011 | coord.value = tonumber(num);
1012 | coord.units = self:parseUnits(units);
1013 |
1014 | return coord;
1015 | end
1016 |
1017 | function SVGParser.coord(self, v, units)
1018 | return SVGCoordinate({v, units});
1019 | end
1020 |
1021 |
1022 | function SVGParser.parseCoordinate(self, str, orig, length)
1023 | --print(".parseCoordinate: ", str, orig, length)
1024 | local coord = self:parseCoordinateRaw(str);
1025 | return self:convertToPixels(coord, orig, length);
1026 | end
1027 |
1028 |
1029 | function SVGParser.parseTransformArgs(self, str, args, maxNa, na)
1030 |
1031 | local it = ffi.new("char [64]");
1032 |
1033 | na = 0;
1034 |
1035 | -- advance past the opening '('
1036 | local ptr = str;
1037 | while (ptr[0] ~= 0 and ptr[0] ~= string.byte('(')) do
1038 | ptr = ptr + 1;
1039 | end
1040 |
1041 |
1042 | if ptr[0] == 0 then
1043 | return 1, na;
1044 | end
1045 |
1046 | -- find the right ')'
1047 | local ending = ptr;
1048 | while ending[0]~=0 and ending[0] ~= string.byte(')') do
1049 | ending = ending + 1;
1050 | end
1051 |
1052 | if ending[0] == 0 then
1053 | return 1, na;
1054 | end
1055 |
1056 | while (ptr < ending) do
1057 | if (ptr[0] == string.byte('-') or
1058 | ptr[0] == string.byte('+') or
1059 | ptr[0] == string.byte('.') or
1060 | isdigit(ptr[0])) then
1061 |
1062 | if na >= maxNa then
1063 | return 0;
1064 | end
1065 |
1066 | ptr, num = self:parseNumber(ptr, it, 64);
1067 | --print('.printTransformArgs: ', na, num)
1068 | args[na] = num;
1069 |
1070 | --print("IT: ", ffi.string(it));
1071 |
1072 | na = na + 1;
1073 | else
1074 | ptr = ptr + 1;
1075 | end
1076 | end
1077 |
1078 | return ending - str, na;
1079 | end
1080 |
1081 |
1082 |
1083 | function SVGParser.parseMatrix(self, xform, str)
1084 | print(".parseMatrix: ", ffi.string(str))
1085 |
1086 | local t = ffi.new("double[6]");
1087 | local na = 0;
1088 | local len, na = self:parseTransformArgs(str, t, 6, na);
1089 | print(".parseMatrix, len, na: ", len, na)
1090 |
1091 | if (na ~= 6) then
1092 | return len;
1093 | end
1094 |
1095 | ffi.copy(xform, t, ffi.sizeof("double")*6);
1096 |
1097 | for i=0,5 do
1098 | print(xform[i])
1099 | end
1100 |
1101 | print(".parseMatrix - END")
1102 |
1103 | return len;
1104 | end
1105 |
1106 | --[[
1107 | static int self:parseTranslate(float* xform, const char* str)
1108 | {
1109 | float args[2];
1110 | float t[6];
1111 | int na = 0;
1112 | int len = self:parseTransformArgs(str, args, 2, &na);
1113 | if (na == 1) args[1] = 0.0;
1114 |
1115 | xform.xformSetTranslation(t, args[0], args[1]);
1116 | memcpy(xform, t, sizeof(float)*6);
1117 | return len;
1118 | }
1119 |
1120 | static int self:parseScale(float* xform, const char* str)
1121 | {
1122 | float args[2];
1123 | int na = 0;
1124 | float t[6];
1125 | int len = self:parseTransformArgs(str, args, 2, &na);
1126 | if (na == 1) args[1] = args[0];
1127 | xform.xformSetScale(t, args[0], args[1]);
1128 | memcpy(xform, t, sizeof(float)*6);
1129 | return len;
1130 | }
1131 |
1132 | static int self:parseSkewX(float* xform, const char* str)
1133 | {
1134 | float args[1];
1135 | int na = 0;
1136 | float t[6];
1137 | int len = self:parseTransformArgs(str, args, 1, &na);
1138 | xform.xformSetSkewX(t, args[0]/180.0f*NSVG_PI);
1139 | memcpy(xform, t, sizeof(float)*6);
1140 | return len;
1141 | }
1142 |
1143 | static int self:parseSkewY(float* xform, const char* str)
1144 | {
1145 | float args[1];
1146 | int na = 0;
1147 | float t[6];
1148 | int len = self:parseTransformArgs(str, args, 1, &na);
1149 | xform.xformSetSkewY(t, args[0]/180.0f*NSVG_PI);
1150 | memcpy(xform, t, sizeof(float)*6);
1151 | return len;
1152 | }
1153 |
1154 | static int self:parseRotate(float* xform, const char* str)
1155 | {
1156 | float args[3];
1157 | int na = 0;
1158 | float m[6];
1159 | float t[6];
1160 | int len = self:parseTransformArgs(str, args, 3, &na);
1161 | if (na == 1)
1162 | args[1] = args[2] = 0.0f;
1163 | xform.xformIdentity(m);
1164 |
1165 | if (na > 1) {
1166 | xform.xformSetTranslation(t, -args[1], -args[2]);
1167 | xform.xformMultiply(m, t);
1168 | }
1169 |
1170 | xform.xformSetRotation(t, args[0]/180.0f*NSVG_PI);
1171 | xform.xformMultiply(m, t);
1172 |
1173 | if (na > 1) {
1174 | xform.xformSetTranslation(t, args[1], args[2]);
1175 | xform.xformMultiply(m, t);
1176 | }
1177 |
1178 | memcpy(xform, m, sizeof(float)*6);
1179 |
1180 | return len;
1181 | }
1182 | --]]
1183 |
1184 | function SVGParser.parseTransform(self, xform, s)
1185 | --print(".parseTransform: ", s);
1186 |
1187 | local str = ffi.cast("const char *", s)
1188 | local t=ffi.new("double[6]");
1189 | transform2D.xformIdentity(xform);
1190 |
1191 | while (str[0] ~= 0) do
1192 | local cont = false;
1193 |
1194 | if strncmp(str, "matrix", 6) == 0 then
1195 | str = str + self:parseMatrix(t, str);
1196 | elseif strncmp(str, "translate", 9) == 0 then
1197 | str = str + self:parseTranslate(t, str);
1198 | elseif strncmp(str, "scale", 5) == 0 then
1199 | str = str + self:parseScale(t, str);
1200 | elseif strncmp(str, "rotate", 6) == 0 then
1201 | str = str + self:parseRotate(t, str);
1202 | elseif strncmp(str, "skewX", 5) == 0 then
1203 | str = str + self:parseSkewX(t, str);
1204 | elseif strncmp(str, "skewY", 5) == 0 then
1205 | str = str + self:parseSkewY(t, str);
1206 | else
1207 | str = str + 1;
1208 | cont = true;
1209 | end
1210 |
1211 | if not cont then
1212 | transform2D.xformPremultiply(xform, t);
1213 | end
1214 | end
1215 | end
1216 |
1217 |
1218 | function SVGParser.parseUrl(self, id, str)
1219 | print("parseUrl - BEGIN: ", id, str)
1220 | --[[
1221 | int i = 0;
1222 | str += 4; -- "url(";
1223 | if (*str == '#')
1224 | str++;
1225 | while (i < 63 && *str != ')') {
1226 | id[i] = *str++;
1227 | i++;
1228 | }
1229 | id[i] = '\0';
1230 | --]]
1231 | return id;
1232 | end
1233 |
1234 | function SVGParser.parseLineCap(self, str)
1235 | print(".parseLineCap: ", str)
1236 | --return LineCap[str:upper()] or LineCap.BUTT;
1237 |
1238 | if str == "butt" then
1239 | return LineCap.BUTT;
1240 | elseif str == "round" then
1241 | return LineCap.ROUND;
1242 | elseif str == "square" then
1243 | return LineCap.SQUARE;
1244 | end
1245 |
1246 | -- TODO: handle inherit.
1247 | return LineCap.BUTT;
1248 | end
1249 |
1250 | function SVGParser.parseLineJoin(self, str)
1251 | if str == "miter" then
1252 | return LineJoin.MITER;
1253 | elseif str == "round" then
1254 | return LineJoin.ROUND;
1255 | elseif str == "bevel" then
1256 | return LineJoin.BEVEL;
1257 | end
1258 |
1259 | -- TODO: handle inherit.
1260 | return LineCap.BUTT;
1261 | end
1262 |
1263 | local FillRuleConversion = {
1264 | nonzero = FillRule.NONZERO;
1265 | evenodd = FillRule.EVENODD;
1266 | }
1267 |
1268 | function SVGParser.parseFillRule(self, str)
1269 | return FillRuleConversion[str] or FillRule.NONZERO;
1270 | end
1271 |
1272 |
1273 | --[[
1274 | static const char* self:getNextDashItem(const char* s, char* it)
1275 | {
1276 | int n = 0;
1277 | it[0] = '\0';
1278 | // Skip white spaces and commas
1279 | while (*s && (self:isspace(*s) || *s == ',')) s++;
1280 | // Advance until whitespace, comma or end.
1281 | while (*s && (!self:isspace(*s) && *s != ',')) {
1282 | if (n < 63)
1283 | it[n++] = *s;
1284 | s++;
1285 | }
1286 | it[n++] = '\0';
1287 | return s;
1288 | }
1289 | --]]
1290 |
1291 | --[[
1292 | static int self:parseStrokeDashArray(self, const char* str, float* strokeDashArray)
1293 | {
1294 | char item[64];
1295 | int count = 0, i;
1296 | float sum = 0.0f;
1297 |
1298 | // Handle "none"
1299 | if (str[0] == 'n')
1300 | return 0;
1301 |
1302 | // Parse dashes
1303 | while (*str) {
1304 | str = self:getNextDashItem(str, item);
1305 | if (!*item) break;
1306 | if (count < NSVG_MAX_DASHES)
1307 | strokeDashArray[count++] = fabsf(self:parseCoordinate(p, item, 0.0f, self:actualLength(p)));
1308 | }
1309 |
1310 | for (i = 0; i < count; i++)
1311 | sum += strokeDashArray[i];
1312 | if (sum <= 1e-6f)
1313 | count = 0;
1314 |
1315 | return count;
1316 | }
1317 | --]]
1318 |
1319 |
1320 | function SVGParser.parseAttr(self, name, value)
1321 | --print("parseAttr: ", name, value);
1322 |
1323 | local xform = ffi.new("double[6]");
1324 | local attr = self:getAttr(p);
1325 |
1326 | assert(attr ~= nil)
1327 |
1328 | if name == "style" then
1329 | self:parseStyle(value);
1330 | elseif name == "display" then
1331 | if value == "none" then
1332 | attr.visible = 0;
1333 | -- Don't reset .visible on display:inline,
1334 | -- one display:none hides the whole subtree
1335 | end
1336 | elseif name == "fill" then
1337 | if value == "none" then
1338 | attr.hasFill = 0;
1339 | elseif value:sub(1,4) == "url(" then
1340 | attr.hasFill = 2;
1341 | self:parseUrl(attr.fillGradient, value);
1342 | else
1343 | attr.hasFill = 1;
1344 | attr.fillColor = self:parseColor(value);
1345 | end
1346 | elseif name == "opacity" then
1347 | attr.opacity = self:parseOpacity(value);
1348 | elseif name == "fill-opacity" then
1349 | attr.fillOpacity = self:parseOpacity(value);
1350 | elseif name == "stroke" then
1351 | if value == "none" then
1352 | attr.hasStroke = 0;
1353 | elseif value:sub(1,4) == "url(" then
1354 | attr.hasStroke = 2;
1355 | self:parseUrl(attr.strokeGradient, value);
1356 | else
1357 | attr.hasStroke = 1;
1358 | attr.strokeColor = self:parseColor(value);
1359 | end
1360 | elseif name == "stroke-width" then
1361 | attr.strokeWidth = self:parseCoordinate(value, 0.0, self:actualLength());
1362 | elseif name == "stroke-dasharray" then
1363 | attr.strokeDashCount = self:parseStrokeDashArray(value, attr.strokeDashArray);
1364 | elseif name == "stroke-dashoffset" then
1365 | attr.strokeDashOffset = self:parseCoordinate(value, 0.0, self:actualLength());
1366 | elseif name == "stroke-opacity" then
1367 | attr.strokeOpacity = self:parseOpacity(value);
1368 | elseif name == "stroke-linecap" then
1369 | attr.strokeLineCap = self:parseLineCap(value);
1370 | elseif name == "stroke-linejoin" then
1371 | attr.strokeLineJoin = self:parseLineJoin(value);
1372 | elseif name == "fill-rule" then
1373 | attr.fillRule = self:parseFillRule(value);
1374 | elseif name == "font-size" then
1375 | attr.fontSize = self:parseCoordinate(value, 0.0, self:actualLength());
1376 | elseif name == "transform" then
1377 | self:parseTransform(xform, value);
1378 | print("transform: ", xform[0], xform[1], xform[2], xform[3], xform[4], xform[5])
1379 | transform2D.xformPremultiply(attr.xform, xform);
1380 | elseif name == "stop-color" then
1381 | attr.stopColor = self:parseColor(value);
1382 | elseif name == "stop-opacity" then
1383 | attr.stopOpacity = self:parseOpacity(value);
1384 | elseif name == "offset" then
1385 | attr.stopOffset = self:parseCoordinate(value, 0.0, 1.0);
1386 | elseif name == "id" then
1387 | strncpy(attr.id, value, 63);
1388 | attr.id[63] = 0;
1389 | else
1390 | return false;
1391 | end
1392 |
1393 | return true;
1394 | end
1395 |
1396 | function SVGParser.parseStyle(self, s)
1397 | --print("parseStyle: ", s)
1398 |
1399 | -- match everything but the delimeter
1400 | for word in string.gmatch(s, "([^;]+)") do
1401 | name, value = word:match("([^:]+):(.*)")
1402 | self:parseAttr(name, value)
1403 | end
1404 | end
1405 |
1406 | function SVGParser.parseAttribs(self, attr)
1407 | for k,v in pairs(attr) do
1408 | if k == "style" then
1409 | self:parseStyle(v);
1410 | else
1411 | self:parseAttr(k, v);
1412 | end
1413 | end
1414 | end
1415 |
1416 | function SVGParser.pathMoveTo(self, cpx, cpy, args, rel)
1417 | --print("pathMoveTo: ", rel, args[1], args[2])
1418 | if rel then
1419 | cpx = cpx + args[1];
1420 | cpy = cpy + args[2];
1421 | else
1422 | cpx = args[1];
1423 | cpy = args[2];
1424 | end
1425 |
1426 | self:moveTo(cpx, cpy);
1427 |
1428 | return cpx, cpy;
1429 | end
1430 |
1431 | function SVGParser.pathLineTo(self, cpx, cpy, args, rel)
1432 | --print(".pathLineTo: ", rel, cpx, cpy, args[1], args[2])
1433 | if rel then
1434 | cpx = cpx + args[1];
1435 | cpy = cpy + args[2];
1436 | else
1437 | cpx = args[1];
1438 | cpy = args[2];
1439 | end
1440 |
1441 | self:lineTo(cpx, cpy);
1442 |
1443 | return cpx, cpy;
1444 | end
1445 |
1446 |
1447 | function SVGParser.pathHLineTo(self, cpx, cpy, args, rel)
1448 | if rel then
1449 | cpx = cpx + args[1];
1450 | else
1451 | cpx = args[1];
1452 | end
1453 |
1454 | self:lineTo(cpx, cpy);
1455 |
1456 | return cpx, cpy;
1457 | end
1458 |
1459 | function SVGParser.pathVLineTo(self, cpx, cpy, args, rel)
1460 | if rel then
1461 | cpy = cpy + args[1];
1462 | else
1463 | cpy = args[1];
1464 | end
1465 |
1466 | self:lineTo(cpx, cpy);
1467 |
1468 | return cpx, cpy;
1469 | end
1470 |
1471 | function SVGParser.pathCubicBezTo(self, cpx, cpy, cpx2, cpy2, args, rel)
1472 |
1473 | local x2, y2, cx1, cy1, cx2, cy2 =0,0,0,0,0,0;
1474 |
1475 | if (rel) then
1476 | cx1 = cpx + args[1];
1477 | cy1 = cpy + args[2];
1478 | cx2 = cpx + args[3];
1479 | cy2 = cpy + args[4];
1480 | x2 = cpx + args[5];
1481 | y2 = cpy + args[6];
1482 | else
1483 | cx1 = args[1];
1484 | cy1 = args[2];
1485 | cx2 = args[3];
1486 | cy2 = args[4];
1487 | x2 = args[5];
1488 | y2 = args[6];
1489 | end
1490 |
1491 | self:cubicBezTo(cx1,cy1, cx2,cy2, x2,y2);
1492 |
1493 | cpx2 = cx2;
1494 | cpy2 = cy2;
1495 | cpx = x2;
1496 | cpy = y2;
1497 |
1498 | return cpx, cpy, cpx2, cpy2;
1499 | end
1500 |
1501 |
1502 | function SVGParser.pathCubicBezShortTo(self, cpx, cpy,cpx2, cpy2, args, rel)
1503 |
1504 | local x1 = cpx;
1505 | local y1 = cpy;
1506 |
1507 | local cx2 = args[1];
1508 | local cy2 = args[2];
1509 | local x2 = args[3];
1510 | local y2 = args[4];
1511 |
1512 | if rel then
1513 | cx2 = cx2 + cpx;
1514 | cy2 = cy2 + cpy;
1515 | x2 = x2 + cpx;
1516 | y2 = y2 + cpy;
1517 | end
1518 |
1519 | local cx1 = 2*x1 - cpx2;
1520 | local cy1 = 2*y1 - cpy2;
1521 |
1522 | self:cubicBezTo(cx1,cy1, cx2,cy2, x2,y2);
1523 |
1524 | cpx2 = cx2;
1525 | cpy2 = cy2;
1526 | cpx = x2;
1527 | cpy = y2;
1528 |
1529 | return cpx, cpy, cpx2, cpy2;
1530 | end
1531 |
1532 | function SVGParser.pathQuadBezTo(self, cpx, cpy, cpx2, cpy2, args, rel)
1533 | local x1 = cpx;
1534 | local y1 = cpy;
1535 |
1536 | local cx = args[1];
1537 | local cy = args[2];
1538 | local x2 = args[3];
1539 | local y2 = args[4];
1540 |
1541 | if rel then
1542 | cx = cx + cpx;
1543 | cy = cy + cpy;
1544 | x2 = x2 + cpx;
1545 | y2 = y2 + cpy;
1546 | end
1547 |
1548 | -- Convert to cubic bezier
1549 | local cx1 = x1 + 2.0/3.0*(cx - x1);
1550 | local cy1 = y1 + 2.0/3.0*(cy - y1);
1551 | local cx2 = x2 + 2.0/3.0*(cx - x2);
1552 | local cy2 = y2 + 2.0/3.0*(cy - y2);
1553 |
1554 | self:cubicBezTo(cx1,cy1, cx2,cy2, x2,y2);
1555 |
1556 | cpx2 = cx;
1557 | cpy2 = cy;
1558 | cpx = x2;
1559 | cpy = y2;
1560 |
1561 | return cpx, cpy, cpx2, cpy2;
1562 | end
1563 |
1564 | function SVGParser.pathQuadBezShortTo(self, cpx, cpy, cpx2, cpy2, args, rel)
1565 |
1566 | local x1 = cpx;
1567 | local y1 = cpy;
1568 | local x2 = args[1];
1569 | local y2 = args[2];
1570 |
1571 | if rel then
1572 | x2 = x2 + cpx;
1573 | y2 = y2 + cpy;
1574 | end
1575 |
1576 | local cx = 2*x1 - cpx2;
1577 | local cy = 2*y1 - cpy2;
1578 |
1579 | -- Convert to cubix bezier
1580 | local cx1 = x1 + 2.0/3.0*(cx - x1);
1581 | local cy1 = y1 + 2.0/3.0*(cy - y1);
1582 | local cx2 = x2 + 2.0/3.0*(cx - x2);
1583 | local cy2 = y2 + 2.0/3.0*(cy - y2);
1584 |
1585 | self:cubicBezTo(cx1,cy1, cx2,cy2, x2,y2);
1586 |
1587 | cpx2 = cx;
1588 | cpy2 = cy;
1589 | cpx = x2;
1590 | cpy = y2;
1591 |
1592 | return cpx, cpy, cpx2, cpy2;
1593 | end
1594 |
1595 |
1596 |
1597 |
1598 |
1599 | function SVGParser.pathArcTo(self, cpx, cpy, args, rel)
1600 | print(".pathArcTo: ", rel, cpx, cpy, unpack(args))
1601 |
1602 | --[[
1603 | -- Ported from canvg (https://code.google.com/p/canvg/)
1604 | float x1p, y1p,
1605 | float x, y
1606 | --]]
1607 |
1608 | local rx = abs(args[1]); -- y radius
1609 | local ry = abs(args[2]); -- x radius
1610 | local rotx = args[3] / 180.0 * SVG_PI; -- x rotation angle
1611 |
1612 | -- Large arc
1613 | local fa = false;
1614 | if abs(args[4]) > 1e-6 then
1615 | fa = true;
1616 | end
1617 |
1618 | -- Sweep direction
1619 | local fs = false;
1620 | if abs(args[5]) > 1e-6 then
1621 | fs = true;
1622 | end
1623 |
1624 | local x1 = cpx; -- start point
1625 | local y1 = cpy;
1626 | local x2 = args[6];
1627 | local y2 = args[7];
1628 |
1629 | if rel then -- end point
1630 | x2 = x2 + cpx;
1631 | y2 = y2 + cpy;
1632 | else
1633 | x2 = args[6];
1634 | y2 = args[7];
1635 | end
1636 |
1637 | local dx = x1 - x2;
1638 | local dy = y1 - y2;
1639 | local d = sqrt(dx*dx + dy*dy);
1640 |
1641 |
1642 | if (d < 1e-6 or rx < 1e-6 or ry < 1e-6) then
1643 | -- The arc degenerates to a line
1644 | self:lineTo(x2, y2);
1645 | cpx = x2;
1646 | cpy = y2;
1647 | return cpx, cpy;
1648 | end
1649 |
1650 | local sinrx = sin(rotx);
1651 | local cosrx = cos(rotx);
1652 |
1653 |
1654 | -- Convert to center point parameterization.
1655 | -- http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
1656 | -- 1) Compute x1', y1'
1657 | local x1p = cosrx * dx / 2.0 + sinrx * dy / 2.0;
1658 | local y1p = -sinrx * dx / 2.0 + cosrx * dy / 2.0;
1659 | d = sqr(x1p)/sqr(rx) + sqr(y1p)/sqr(ry);
1660 | if d > 1 then
1661 | d = sqrt(d);
1662 | rx = rx * d;
1663 | ry = ry * d;
1664 | end
1665 |
1666 | -- 2) Compute cx', cy'
1667 | local s = 0.0;
1668 | local sa = sqr(rx)*sqr(ry) - sqr(rx)*sqr(y1p) - sqr(ry)*sqr(x1p);
1669 | local sb = sqr(rx)*sqr(y1p) + sqr(ry)*sqr(x1p);
1670 | if (sa < 0.0) then
1671 | sa = 0.0;
1672 | end
1673 |
1674 | if (sb > 0.0) then
1675 | s = sqrt(sa / sb);
1676 | end
1677 |
1678 | if (fa == fs) then
1679 | s = -s;
1680 | end
1681 |
1682 | local cxp = s * rx * y1p / ry;
1683 | local cyp = s * -ry * x1p / rx;
1684 |
1685 |
1686 | -- 3) Compute cx,cy from cx',cy'
1687 | local cx = (x1 + x2)/2.0 + cosrx*cxp - sinrx*cyp;
1688 | local cy = (y1 + y2)/2.0 + sinrx*cxp + cosrx*cyp;
1689 |
1690 | -- 4) Calculate theta1, and delta theta.
1691 | local ux = (x1p - cxp) / rx;
1692 | local uy = (y1p - cyp) / ry;
1693 | local vx = (-x1p - cxp) / rx;
1694 | local vy = (-y1p - cyp) / ry;
1695 | local a1 = vecang(1.0,0.0, ux,uy); -- Initial angle
1696 | local da = vecang(ux,uy, vx,vy); -- Delta angle
1697 |
1698 | --[[
1699 | // if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI;
1700 | // if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0;
1701 | --]]
1702 |
1703 | if (fa) then
1704 | -- Choose large arc
1705 | if (da > 0.0) then
1706 | da = da - 2*SVG_PI;
1707 | else
1708 | da = 2*SVG_PI + da;
1709 | end
1710 | end
1711 |
1712 |
1713 | -- Approximate the arc using cubic spline segments.
1714 | local t = ffi.new("double[6]");
1715 | t[0] = cosrx; t[1] = sinrx;
1716 | t[2] = -sinrx; t[3] = cosrx;
1717 | t[4] = cx; t[5] = cy;
1718 |
1719 |
1720 | -- Split arc into max 90 degree segments.
1721 | -- The loop assumes an iteration per end point (including start and end), this +1.
1722 | local ndivs = abs(da) / (SVG_PI*0.5) + 1.0;
1723 | local hda = (da / ndivs) / 2.0;
1724 | local kappa = abs(4.0 / 3.0 * (1.0 - cos(hda)) / sin(hda));
1725 |
1726 | if (da < 0.0) then
1727 | kappa = -kappa;
1728 | end
1729 |
1730 | local px, py, ptanx, ptany = 0,0,0,0;
1731 |
1732 | for i = 0, ndivs do
1733 | local a = a1 + da * (i/ndivs);
1734 | local dx = cos(a);
1735 | local dy = sin(a);
1736 | local x, y = transform2D.xformPoint(dx*rx, dy*ry, t); -- position
1737 | local tanx, tany = transform2D.xformVec(-dy*rx * kappa, dx*ry * kappa, t); -- tangent
1738 | if i > 0 then
1739 | self:cubicBezTo(px+ptanx,py+ptany, x-tanx, y-tany, x, y);
1740 | end
1741 |
1742 | px = x;
1743 | py = y;
1744 | ptanx = tanx;
1745 | ptany = tany;
1746 | end
1747 |
1748 | cpx = x2;
1749 | cpy = y2;
1750 |
1751 |
1752 | return cpx, cpy;
1753 | end
1754 |
1755 | --[[
1756 | given a 'd' attribute of an svg path element
1757 | turn that into a table with each command and
1758 | its arguments represented by their own table.
1759 | Each command entry has the command itself (a single character)
1760 | followed by the arguments for that command in subsequent positions
1761 | --]]
1762 | function parseSVGPath(input)
1763 | local out = {};
1764 |
1765 | for instr, vals in input:gmatch("([a-df-zA-DF-Z])([^a-df-zA-DF-Z]*)") do
1766 | local line = { instr };
1767 | for v in vals:gmatch("([+-]?[%deE.]+)") do
1768 | line[#line+1] = v;
1769 | end
1770 | out[#out+1] = line;
1771 | end
1772 | return out;
1773 | end
1774 |
1775 | function SVGParser.parsePath(self, attr)
1776 | --print("parsePath: ")
1777 |
1778 | local s = nil;
1779 | for name,value in pairs(attr) do
1780 | --print(' ',name, value)
1781 | if name == "d" then
1782 | s = value;
1783 | else
1784 | self:parseAttribs({[name] = value});
1785 | end
1786 | end
1787 |
1788 | if (s) then
1789 | self:resetPath();
1790 | local cpx = 0;
1791 | local cpy = 0;
1792 | local cpx2 = 0;
1793 | local cpy2 = 0;
1794 | local closedFlag = false;
1795 |
1796 | local instructions = parseSVGPath(s)
1797 |
1798 | -- what we have in commands is a table of instructions
1799 | -- each line has
1800 | -- instructions[1] == name of instructions
1801 | -- instructions[2..n] == values for instructions
1802 | for _, args in ipairs(instructions) do
1803 | local cmd = args[1];
1804 | table.remove(args,1);
1805 | --print(" CMD: ", cmd, #args)
1806 |
1807 | -- now, we have the instruction in the 'ins' value
1808 | -- and the arguments in the cmd table
1809 | if cmd == "m" or cmd == "M" then
1810 | --print("MOVETO:", unpack(args))
1811 | if #args == 0 then
1812 | -- Commit path.
1813 | if (#self.pts > 0) then
1814 | self:addPath(closedFlag);
1815 | end
1816 |
1817 | -- Start new subpath.
1818 | self:resetPath();
1819 | closedFlag = false;
1820 | else
1821 | -- The first set of arguments determine the new current
1822 | -- location. After we remove them, the remaining args
1823 | -- are treated as lineTo
1824 | cpx, cpy = self:pathMoveTo(cpx, cpy, args, cmd == 'm');
1825 | table.remove(args)
1826 | table.remove(args)
1827 |
1828 | if #args >=2 then
1829 |
1830 | while #args >= 2 do
1831 | cpx, cpy = self:pathLineTo(cpx, cpy, args, cmd == 'm');
1832 | cpx2 = cpx;
1833 | cpy2 = cpy;
1834 | table.remove(args)
1835 | table.remove(args)
1836 | end
1837 | end
1838 | end
1839 | elseif cmd == "l" or cmd == "L" then
1840 | --print("LINETO: ", unpack(args))
1841 | cpx, cpy = self:pathLineTo(cpx, cpy, args, cmd == 'l');
1842 | cpx2 = cpx;
1843 | cpy2 = cpy;
1844 | elseif cmd == "h" or cmd == "H" then
1845 | --print("HLINETO: ", unpack(args))
1846 | cpx, cpy = self:pathHLineTo(cpx, cpy, args, cmd == 'h');
1847 | cpx2 = cpx;
1848 | cpy2 = cpy;
1849 | elseif cmd == "v" or cmd == "V" then
1850 | --print("VLINETO: ", unpack(args))
1851 | cpx, cpy = self:pathVLineTo(cpx, cpy, args, cmd == 'v');
1852 | cpx2 = cpx;
1853 | cpy2 = cpy;
1854 | elseif cmd == "c" or cmd == "C" then
1855 | --print("CUBICBEZIERTO: ", unpack(args))
1856 | cpx, cpy, cpx2, cpy2 = self:pathCubicBezTo(cpx, cpy, cpx2, cpy2, args, cmd == 'c');
1857 | elseif cmd == "s" or cmd == "S" then
1858 | --print("CUBICBEZIERSHORTTO: ", unpack(args))
1859 | cpx, cpy, cpx2, cpy2 = self:pathCubicBezShortTo(cpx, cpy, cpx2, cpy2, args, cmd == 's');
1860 | elseif cmd == "q" or cmd == "Q" then
1861 | --print("QUADBEZIERTO: ", unpack(args))
1862 | cpx, cpy, cpx2, cpy2 = self:pathQuadBezTo(cpx, cpy, cpx2, cpy2, args, cmd == 'q');
1863 | elseif cmd == "t" or cmd == "T" then
1864 | --print("QUADBEZIERSHORTTO: ", unpack(args))
1865 | cpx, cpy, cpx2, cpy2 = self:pathQuadBezShortTo(cpx, cpy, cpx2, cpy2, args, cmd == 't');
1866 |
1867 | elseif cmd == "a" or cmd == "A" then
1868 | cpx, cpy = self:pathArcTo(cpx, cpy, args, cmd == 'a');
1869 | cpx2 = cpx;
1870 | cpy2 = cpy;
1871 | elseif cmd == "z" or cmd == "Z" then
1872 | closedFlag = true;
1873 | -- Commit path.
1874 | if (#self.pts > 0) then
1875 | -- Move current point to first point
1876 | cpx = self.pts[1].x;
1877 | cpy = self.pts[1].y;
1878 | cpx2 = cpx;
1879 | cpy2 = cpy;
1880 | self:addPath(closedFlag);
1881 | end
1882 |
1883 | -- Start new subpath.
1884 | self:resetPath();
1885 | self:moveTo(cpx, cpy);
1886 | closedFlag = false;
1887 | end
1888 | end
1889 |
1890 | -- Commit path.
1891 | --print("COMMIT: ", #self.pts)
1892 | if (#self.pts > 0) then
1893 | self:addPath(closedFlag);
1894 | end
1895 | end
1896 |
1897 | self:addShape();
1898 | end
1899 |
1900 | function SVGParser.parseRect(self, attr)
1901 | local x = 0.0;
1902 | local y = 0.0;
1903 | local w = 0.0;
1904 | local h = 0.0;
1905 | local rx = -1.0; -- marks not set
1906 | local ry = -1.0;
1907 |
1908 | for k,v in pairs(attr) do
1909 | if not self:parseAttr(k,v) then
1910 | if k == "x" then
1911 | x = self:parseCoordinate(v, self:actualOrigX(), self:actualWidth());
1912 | elseif k == "y" then
1913 | y = self:parseCoordinate(v, self:actualOrigY(), self:actualHeight());
1914 | elseif k == "width" then
1915 | w = self:parseCoordinate(v, 0.0, self:actualWidth());
1916 | elseif k == "height" then
1917 | h = self:parseCoordinate(v, 0.0, self:actualHeight());
1918 | elseif k == "rx" then
1919 | rx = math.fabs(self:parseCoordinate(v, 0.0, self:actualWidth()));
1920 | elseif k == "ry" then
1921 | ry = math.fabs(self:parseCoordinate(v, 0.0, self:actualHeight()));
1922 | end
1923 | end
1924 | end
1925 |
1926 |
1927 | if (rx < 0.0 and ry > 0.0) then rx = ry; end
1928 | if (ry < 0.0 and rx > 0.0) then ry = rx; end
1929 | if (rx < 0.0) then rx = 0.0; end
1930 | if (ry < 0.0) then ry = 0.0; end
1931 | if (rx > w/2.0) then rx = w/2.0; end
1932 | if (ry > h/2.0) then ry = h/2.0; end
1933 |
1934 | --print("RECT: ", x, y, w, h, rx, ry)
1935 |
1936 | if w ~= 0 and h ~= 0 then
1937 | self:resetPath();
1938 |
1939 | if (rx < 0.00001 or ry < 0.0001) then
1940 | self:moveTo(x, y);
1941 | self:lineTo(x+w, y);
1942 | self:lineTo(x+w, y+h);
1943 | self:lineTo(x, y+h);
1944 | else
1945 | --print("ROUNDED RECT")
1946 | --[[
1947 | -- Rounded rectangle
1948 | self:moveTo(x+rx, y);
1949 | self:lineTo(x+w-rx, y);
1950 | self:cubicBezTo(x+w-rx*(1-SVG_KAPPA90), y, x+w, y+ry*(1-SVG_KAPPA90), x+w, y+ry);
1951 | self:lineTo(x+w, y+h-ry);
1952 | self:cubicBezTo(x+w, y+h-ry*(1-SVG_KAPPA90), x+w-rx*(1-SVG_KAPPA90), y+h, x+w-rx, y+h);
1953 | self:lineTo(x+rx, y+h);
1954 | self:cubicBezTo(x+rx*(1-SVG_KAPPA90), y+h, x, y+h-ry*(1-SVG_KAPPA90), x, y+h-ry);
1955 | self:lineTo(x, y+ry);
1956 | self:cubicBezTo(x, y+ry*(1-SVG_KAPPA90), x+rx*(1-SVG_KAPPA90), y, x+rx, y);
1957 | --]]
1958 | end
1959 |
1960 | self:addPath(true);
1961 | self:addShape();
1962 | end
1963 | end
1964 |
1965 |
1966 |
1967 | function SVGParser.parseCircle(self, attr)
1968 |
1969 | local cx = 0.0;
1970 | local cy = 0.0;
1971 | local r = 0.0;
1972 |
1973 | for name, value in pairs(attr) do
1974 | if (not self:parseAttr(name, value)) then
1975 | if name == "cx" then
1976 | cx = self:parseCoordinate(value, self:actualOrigX(), self:actualWidth());
1977 | elseif name == "cy" then
1978 | cy = self:parseCoordinate(value, self:actualOrigY(), self:actualHeight());
1979 | elseif name == "r" then
1980 | r = fabs(self:parseCoordinate(value, 0.0, self:actualLength()));
1981 | end
1982 | end
1983 | end
1984 |
1985 | if (r > 0.0) then
1986 | self:resetPath();
1987 |
1988 | self:moveTo(cx+r, cy);
1989 | self:cubicBezTo(cx+r, cy+r*SVG_KAPPA90, cx+r*SVG_KAPPA90, cy+r, cx, cy+r);
1990 | self:cubicBezTo(cx-r*SVG_KAPPA90, cy+r, cx-r, cy+r*SVG_KAPPA90, cx-r, cy);
1991 | self:cubicBezTo(cx-r, cy-r*SVG_KAPPA90, cx-r*SVG_KAPPA90, cy-r, cx, cy-r);
1992 | self:cubicBezTo(cx+r*SVG_KAPPA90, cy-r, cx+r, cy-r*SVG_KAPPA90, cx+r, cy);
1993 |
1994 | self:addPath(true);
1995 |
1996 | self:addShape();
1997 | end
1998 | end
1999 |
2000 |
2001 |
2002 | function SVGParser.parseEllipse(self, attr)
2003 |
2004 | local cx = 0.0;
2005 | local cy = 0.0;
2006 | local rx = 0.0;
2007 | local ry = 0.0;
2008 |
2009 | for name, value in pairs(attr) do
2010 | if (not self:parseAttr(name, value)) then
2011 | if name == "cx" then cx = self:parseCoordinate(value, self:actualOrigX(), self:actualWidth());
2012 | elseif name == "cy" then cy = self:parseCoordinate(value, self:actualOrigY(), self:actualHeight());
2013 | elseif name == "rx" then rx = fabs(self:parseCoordinate(value, 0.0, self:actualWidth()));
2014 | elseif name == "ry" then ry = fabs(self:parseCoordinate(value, 0.0, self:actualHeight()));
2015 | end
2016 | end
2017 | end
2018 |
2019 | if (rx > 0.0 and ry > 0.0) then
2020 | self:resetPath();
2021 |
2022 | self:moveTo(cx+rx, cy);
2023 | self:cubicBezTo(cx+rx, cy+ry*SVG_KAPPA90, cx+rx*SVG_KAPPA90, cy+ry, cx, cy+ry);
2024 | self:cubicBezTo(cx-rx*SVG_KAPPA90, cy+ry, cx-rx, cy+ry*SVG_KAPPA90, cx-rx, cy);
2025 | self:cubicBezTo(cx-rx, cy-ry*SVG_KAPPA90, cx-rx*SVG_KAPPA90, cy-ry, cx, cy-ry);
2026 | self:cubicBezTo(cx+rx*SVG_KAPPA90, cy-ry, cx+rx, cy-ry*SVG_KAPPA90, cx+rx, cy);
2027 |
2028 | self:addPath(true);
2029 |
2030 | self:addShape();
2031 | end
2032 | end
2033 |
2034 | function SVGParser.parseLine(self, attr)
2035 |
2036 | local x1, y1, x2, y2 = 0, 0, 0, 0;
2037 |
2038 | for name, value in pairs(attr) do
2039 | if (not self:parseAttr(name, value)) then
2040 | if name == "x1" then x1 = self:parseCoordinate(value, self:actualOrigX(), self:actualWidth());
2041 | elseif name == "y1" then y1 = self:parseCoordinate(value, self:actualOrigY(), self:actualHeight());
2042 | elseif name == "x2" then x2 = self:parseCoordinate(value, self:actualOrigX(), self:actualWidth());
2043 | elseif name == "y2" then y2 = self:parseCoordinate(value, self:actualOrigY(), self:actualHeight());
2044 | end
2045 | end
2046 | end
2047 |
2048 | self:resetPath();
2049 |
2050 | self:moveTo(x1, y1);
2051 | self:lineTo(x2, y2);
2052 |
2053 | self:addPath(false);
2054 |
2055 | self:addShape();
2056 | end
2057 |
2058 |
2059 |
2060 | function SVGParser.parsePoly(self, attr, closeFlag)
2061 | --local coordspatt = "([^ ]+)";
2062 | local coordspatt = "(%g+%s*,%s*%g+)";
2063 | --local xypatt = "([^,]+),(.*)";
2064 | local xypatt = "(%g+)%s*,%s*(%g+)";
2065 |
2066 | self:resetPath();
2067 |
2068 | for name, value in pairs(attr) do
2069 | if (not self:parseAttr(name, value)) then
2070 | if name == "points" then
2071 | local firstone = true;
2072 | for coords in string.gmatch(value, coordspatt) do
2073 | --print(".parsePoly(): ",coords)
2074 | x, y = coords:match(xypatt)
2075 | if x and y then
2076 | --print(" x,y: ", x, y)
2077 |
2078 | if firstone then
2079 | self:moveTo(tonumber(x), tonumber(y));
2080 | firstone = not firstone;
2081 | else
2082 | self:lineTo(tonumber(x), tonumber(y));
2083 | end
2084 | end
2085 | end
2086 | end
2087 | end
2088 | end
2089 |
2090 | self:addPath(closeFlag);
2091 |
2092 | self:addShape();
2093 | end
2094 |
2095 | function SVGParser.parseSVG(self, attrs)
2096 | --print("SVGParser.parseSVG - BEGIN")
2097 |
2098 | for name, value in pairs(attrs) do
2099 | --print("SVGParser.parseSVG: ", name, value)
2100 |
2101 | if (not self:parseAttr(name, value)) then
2102 | --print(" name, value: ", name, value)
2103 | if name == "width" then
2104 | self.image.width = self:parseCoordinate(value, 0.0, 1.0);
2105 | elseif name == "height" then
2106 | self.image.height = self:parseCoordinate(value, 0.0, 1.0);
2107 | elseif name == "viewBox" then
2108 | local minx, miny, width, height = value:match("(%d+)%s+(%d+)%s+(%d+)%s+(%d+)")
2109 | self.viewMinx = tonumber(minx);
2110 | self.viewMiny = tonumber(miny);
2111 | self.viewWidth = tonumber(width);
2112 | self.viewHeight = tonumber(height);
2113 | --sscanf(value, "%f%*[%%, \t]%f%*[%%, \t]%f%*[%%, \t]%f", &self.viewMinx, &self.viewMiny, &self.viewWidth, &self.viewHeight);
2114 | elseif name == "preserveAspectRatio" then
2115 | if value:find("none") then
2116 | -- No uniform scaling
2117 | self.alignType = SVG_ALIGN_NONE;
2118 | else
2119 | -- Parse X align
2120 | if value:find("xMin") then
2121 | self.alignX = SVG_ALIGN_MIN;
2122 | elseif value:find("xMid") then
2123 | self.alignX = SVG_ALIGN_MID;
2124 | elseif value:find("xMax") then
2125 | self.alignX = SVG_ALIGN_MAX;
2126 | end
2127 |
2128 | -- Parse Y align
2129 | if value:find("yMin") then
2130 | self.alignY = SVG_ALIGN_MIN;
2131 | elseif value:find("yMid") then
2132 | self.alignY = SVG_ALIGN_MID;
2133 | elseif value:find("yMax") then
2134 | self.alignY = SVG_ALIGN_MAX;
2135 | end
2136 |
2137 | -- Parse meet/slice
2138 | self.alignType = SVG_ALIGN_MEET;
2139 | if value:find("slice") then
2140 | self.alignType = SVG_ALIGN_SLICE;
2141 | end
2142 | end
2143 | end
2144 | end
2145 | end
2146 | end
2147 |
2148 |
2149 | --[[
2150 | function NSVGParser.parseGradient(self, const char** attr, char type)
2151 | {
2152 | int i;
2153 | NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData));
2154 | if (grad == NULL) return;
2155 | memset(grad, 0, sizeof(NSVGgradientData));
2156 | grad.units = NSVG_OBJECT_SPACE;
2157 | grad.type = type;
2158 | if (grad.type == NSVG_PAINT_LINEAR_GRADIENT) {
2159 | grad.linear.x1 = self:coord(0.0f, NSVG_UNITS_PERCENT);
2160 | grad.linear.y1 = self:coord(0.0f, NSVG_UNITS_PERCENT);
2161 | grad.linear.x2 = self:coord(100.0f, NSVG_UNITS_PERCENT);
2162 | grad.linear.y2 = self:coord(0.0f, NSVG_UNITS_PERCENT);
2163 | elseif (grad.type == NSVG_PAINT_RADIAL_GRADIENT) {
2164 | grad.radial.cx = self:coord(50.0f, NSVG_UNITS_PERCENT);
2165 | grad.radial.cy = self:coord(50.0f, NSVG_UNITS_PERCENT);
2166 | grad.radial.r = self:coord(50.0f, NSVG_UNITS_PERCENT);
2167 | }
2168 |
2169 | xform.xformIdentity(grad.xform);
2170 |
2171 | for (i = 0; attr[i]; i += 2) {
2172 | if (strcmp(attr[i], "id") == 0) {
2173 | strncpy(grad.id, attr[i+1], 63);
2174 | grad.id[63] = '\0';
2175 | elseif (!self:parseAttr(p, attr[i], attr[i + 1])) {
2176 | if (strcmp(attr[i], "gradientUnits") == 0) {
2177 | if (strcmp(attr[i+1], "objectBoundingBox") == 0)
2178 | grad.units = NSVG_OBJECT_SPACE;
2179 | else
2180 | grad.units = NSVG_USER_SPACE;
2181 | elseif (strcmp(attr[i], "gradientTransform") == 0) {
2182 | self:parseTransform(grad.xform, attr[i + 1]);
2183 | elseif (strcmp(attr[i], "cx") == 0) {
2184 | grad.radial.cx = self:parseCoordinateRaw(attr[i + 1]);
2185 | elseif (strcmp(attr[i], "cy") == 0) {
2186 | grad.radial.cy = self:parseCoordinateRaw(attr[i + 1]);
2187 | elseif (strcmp(attr[i], "r") == 0) {
2188 | grad.radial.r = self:parseCoordinateRaw(attr[i + 1]);
2189 | elseif (strcmp(attr[i], "fx") == 0) {
2190 | grad.radial.fx = self:parseCoordinateRaw(attr[i + 1]);
2191 | elseif (strcmp(attr[i], "fy") == 0) {
2192 | grad.radial.fy = self:parseCoordinateRaw(attr[i + 1]);
2193 | elseif (strcmp(attr[i], "x1") == 0) {
2194 | grad.linear.x1 = self:parseCoordinateRaw(attr[i + 1]);
2195 | elseif (strcmp(attr[i], "y1") == 0) {
2196 | grad.linear.y1 = self:parseCoordinateRaw(attr[i + 1]);
2197 | elseif (strcmp(attr[i], "x2") == 0) {
2198 | grad.linear.x2 = self:parseCoordinateRaw(attr[i + 1]);
2199 | elseif (strcmp(attr[i], "y2") == 0) {
2200 | grad.linear.y2 = self:parseCoordinateRaw(attr[i + 1]);
2201 | elseif (strcmp(attr[i], "spreadMethod") == 0) {
2202 | if (strcmp(attr[i+1], "pad") == 0)
2203 | grad.spread = NSVG_SPREAD_PAD;
2204 | else if (strcmp(attr[i+1], "reflect") == 0)
2205 | grad.spread = NSVG_SPREAD_REFLECT;
2206 | else if (strcmp(attr[i+1], "repeat") == 0)
2207 | grad.spread = NSVG_SPREAD_REPEAT;
2208 | elseif (strcmp(attr[i], "xlink:href") == 0) {
2209 | const char *href = attr[i+1];
2210 | strncpy(grad.ref, href+1, 62);
2211 | grad.ref[62] = '\0';
2212 | }
2213 | }
2214 | }
2215 |
2216 | grad.next = self.gradients;
2217 | self.gradients = grad;
2218 | }
2219 | --]]
2220 |
2221 | --[[
2222 | function NSVGParser.parseGradientStop(self, const char** attr)
2223 | {
2224 | NSVGattrib* curAttr = self:getAttr(p);
2225 | NSVGgradientData* grad;
2226 | NSVGgradientStop* stop;
2227 | int i, idx;
2228 |
2229 | curAttr.stopOffset = 0;
2230 | curAttr.stopColor = 0;
2231 | curAttr.stopOpacity = 1.0f;
2232 |
2233 | for (i = 0; attr[i]; i += 2) {
2234 | self:parseAttr(p, attr[i], attr[i + 1]);
2235 | }
2236 |
2237 | // Add stop to the last gradient.
2238 | grad = self.gradients;
2239 | if (grad == NULL) return;
2240 |
2241 | grad.nstops++;
2242 | grad.stops = (NSVGgradientStop*)realloc(grad.stops, sizeof(NSVGgradientStop)*grad.nstops);
2243 | if (grad.stops == NULL) return;
2244 |
2245 | // Insert
2246 | idx = grad.nstops-1;
2247 | for (i = 0; i < grad.nstops-1; i++) {
2248 | if (curAttr.stopOffset < grad.stops[i].offset) {
2249 | idx = i;
2250 | break;
2251 | }
2252 | }
2253 | if (idx != grad.nstops-1) {
2254 | for (i = grad.nstops-1; i > idx; i--)
2255 | grad.stops[i] = grad.stops[i-1];
2256 | }
2257 |
2258 | stop = &grad.stops[idx];
2259 | stop.color = curAttr.stopColor;
2260 | stop.color |= (unsigned int)(curAttr.stopOpacity*255) << 24;
2261 | stop.offset = curAttr.stopOffset;
2262 | }
2263 | --]]
2264 |
2265 |
2266 | function SVGParser.startElement(self, el, attr)
2267 | --print("SVGParser.startElement: ", el, attr)
2268 |
2269 | if (self.defsFlag) then
2270 | -- Skip everything but gradients in defs
2271 | if el == "linearGradient" then
2272 | self:parseGradient(attr, SVGTypes.PaintType.LINEAR_GRADIENT);
2273 | elseif el == "radialGradient" then
2274 | self:parseGradient(attr, SVGTypes.PaintType.RADIAL_GRADIENT);
2275 | elseif el == "stop" then
2276 | self:parseGradientStop(attr);
2277 | end
2278 |
2279 | return;
2280 | end
2281 |
2282 | if el == "g" then
2283 | self:pushAttr();
2284 | self:parseAttribs(attr);
2285 | elseif el == "path" then
2286 | if self.pathFlag then -- Do not allow nested paths.
2287 | print("NESTED PATH")
2288 | return;
2289 | end
2290 |
2291 | self:pushAttr();
2292 | self:parsePath(attr);
2293 | self:popAttr();
2294 | elseif el == "rect" then
2295 | self:pushAttr();
2296 | self:parseRect(attr);
2297 | self:popAttr();
2298 | elseif el == "circle" then
2299 | self:pushAttr();
2300 | self:parseCircle(attr);
2301 | self:popAttr();
2302 | elseif el == "ellipse" then
2303 | self:pushAttr();
2304 | self:parseEllipse(attr);
2305 | self:popAttr();
2306 | elseif el == "line" then
2307 | self:pushAttr();
2308 | self:parseLine(attr);
2309 | self:popAttr();
2310 | elseif el == "polyline" then
2311 | self:pushAttr();
2312 | self:parsePoly(attr, false);
2313 | self:popAttr();
2314 | elseif el == "polygon" then
2315 | self:pushAttr();
2316 | self:parsePoly(attr, true);
2317 | self:popAttr();
2318 | elseif el == "linearGradient" then
2319 | self:parseGradient(attr, NSVG_PAINT_LINEAR_GRADIENT);
2320 | elseif el == "radialGradient" then
2321 | self:parseGradient(attr, NSVG_PAINT_RADIAL_GRADIENT);
2322 | elseif el == "stop" then
2323 | self:parseGradientStop(attr);
2324 | elseif el == "defs" then
2325 | self.defsFlag = true;
2326 | elseif el == "svg" then
2327 | self:parseSVG(attr);
2328 | end
2329 | end
2330 |
2331 |
2332 | function SVGParser.endElement(self, el)
2333 | --print("SVGParser.endElement: ", el)
2334 | if el == "g" then
2335 | self:popAttr();
2336 | elseif el == "path" then
2337 | self.pathFlag = false;
2338 | elseif el == "defs" then
2339 | self.defsFlag = false;
2340 | end
2341 | end
2342 |
2343 |
2344 | function SVGParser.content(self, s)
2345 | -- empty
2346 | end
2347 |
2348 |
2349 | function SVGParser.imageBounds(self, bounds)
2350 | --print(".imageBounds: ", #self.image.shapes)
2351 |
2352 | if #self.image.shapes < 1 then
2353 | bounds[0],bounds[1],bounds[2],bounds[3] = 0,0,0,0;
2354 | return;
2355 | end
2356 |
2357 | for _, shape in ipairs(self.image.shapes) do
2358 | bounds[0] = minf(bounds[0], shape.bounds[0]);
2359 | bounds[1] = minf(bounds[1], shape.bounds[1]);
2360 | bounds[2] = maxf(bounds[2], shape.bounds[2]);
2361 | bounds[3] = maxf(bounds[3], shape.bounds[3]);
2362 | end
2363 |
2364 | print(".imageBounds - END: ", bounds[0], bounds[1], bounds[2], bounds[3])
2365 |
2366 | return bounds[0], bounds[1], bounds[2], bounds[3];
2367 | end
2368 |
2369 | local function viewAlign(content, container, kind)
2370 |
2371 | if (kind == SVG_ALIGN_MIN) then
2372 | return 0;
2373 | elseif (kind == SVG_ALIGN_MAX) then
2374 | return container - content;
2375 | end
2376 |
2377 | -- mid
2378 | return (container - content) * 0.5;
2379 | end
2380 |
2381 | local function scaleGradient(grad, tx, ty, sx, sy)
2382 | grad.xform[0] = grad.xform[0] *sx;
2383 | grad.xform[1] = grad.xform[1] *sx;
2384 | grad.xform[2] = grad.xform[2] *sy;
2385 | grad.xform[3] = grad.xform[3] *sy;
2386 | grad.xform[4] = grad.xform[4] + tx*sx;
2387 | grad.xform[5] = grad.xform[2] + ty*sx;
2388 | end
2389 |
2390 |
2391 | function SVGParser.scaleToViewbox(self, units)
2392 | print("SHAPE TO VIEW BOX: ", units)
2393 | local bounds = ffi.new("double[4]");
2394 | local t = ffi.new("double[6]");
2395 |
2396 | -- Guess image size if not set completely.
2397 | self:imageBounds(bounds);
2398 | --print("image bounds: ", bounds[0], bounds[1], bounds[2], bounds[3])
2399 | if (self.viewWidth == 0) then
2400 | if self.image.width > 0 then
2401 | self.viewWidth = self.image.width;
2402 | else
2403 | self.viewMinx = bounds[0];
2404 | self.viewWidth = bounds[2] - bounds[0];
2405 | end
2406 | end
2407 |
2408 | if (self.viewHeight == 0) then
2409 | if (self.image.height > 0) then
2410 | self.viewHeight = self.image.height;
2411 | else
2412 | self.viewMiny = bounds[1];
2413 | self.viewHeight = bounds[3] - bounds[1];
2414 | end
2415 | end
2416 |
2417 | if (self.image.width == 0) then
2418 | self.image.width = self.viewWidth;
2419 | end
2420 |
2421 | if (self.image.height == 0) then
2422 | self.image.height = self.viewHeight;
2423 | end
2424 |
2425 | local tx = -self.viewMinx;
2426 | local ty = -self.viewMiny;
2427 | local sx = 0;
2428 | if self.viewWidth > 0 then
2429 | sx = self.image.width / self.viewWidth
2430 | end
2431 |
2432 | local sy = 0;
2433 | if self.viewHeight > 0 then
2434 | sy = self.image.height / self.viewHeight;
2435 | end
2436 |
2437 | -- Unit scaling
2438 | local us = 1.0 / self:convertToPixels(self:coord(1.0, self:parseUnits(units)), 0.0, 1.0);
2439 |
2440 | -- Fix aspect ratio
2441 | if (self.alignType == SVG_ALIGN_MEET) then
2442 | -- fit whole image into viewbox
2443 | sx = minf(sx, sy);
2444 | sy = sx;
2445 | tx = tx + viewAlign(self.viewWidth*sx, self.image.width, self.alignX) / sx;
2446 | ty = ty + viewAlign(self.viewHeight*sy, self.image.height, self.alignY) / sy;
2447 | elseif (self.alignType == SVG_ALIGN_SLICE) then
2448 | -- fill whole viewbox with image
2449 | sx = maxf(sx, sy);
2450 | sy = sx;
2451 | tx = tx + viewAlign(self.viewWidth*sx, self.image.width, self.alignX) / sx;
2452 | ty = ty + viewAlign(self.viewHeight*sy, self.image.height, self.alignY) / sy;
2453 | end
2454 |
2455 | -- Transform
2456 | sx = sx*us;
2457 | sy = sy*us;
2458 | local avgs = (sx+sy) / 2.0;
2459 |
2460 | -- BUGBUG Something wrong with scaling
2461 | -- setting values to 0
2462 |
2463 | for _, shape in ipairs(self.image.shapes) do
2464 | shape.bounds[0] = (shape.bounds[0] + tx) * sx;
2465 | shape.bounds[1] = (shape.bounds[1] + ty) * sy;
2466 | shape.bounds[2] = (shape.bounds[2] + tx) * sx;
2467 | shape.bounds[3] = (shape.bounds[3] + ty) * sy;
2468 |
2469 | for _, path in ipairs(shape.paths) do
2470 | --print("PATH, sx, sy: ", sx, sy)
2471 | path.bounds[0] = (path.bounds[0] + tx) * sx;
2472 | path.bounds[1] = (path.bounds[1] + ty) * sy;
2473 | path.bounds[2] = (path.bounds[2] + tx) * sx;
2474 | path.bounds[3] = (path.bounds[3] + ty) * sy;
2475 |
2476 | for _, pt in ipairs(path.pts) do
2477 | pt.x = (pt.x + tx) * sx;
2478 | pt.y = (pt.y + ty) * sy;
2479 | --print(pt.x, pt.y)
2480 | end
2481 | end
2482 |
2483 | if (shape.fill.type == PaintType.LINEAR_GRADIENT or shape.fill.type == PaintType.RADIAL_GRADIENT) then
2484 | scaleGradient(shape.fill.gradient, tx,ty, sx,sy);
2485 | ffi.copy(t, shape.fill.gradient.xform, ffi.sizeof("double")*6);
2486 | xform.xformInverse(shape.fill.gradient.xform, t);
2487 | end
2488 |
2489 | if (shape.stroke.type == PaintType.LINEAR_GRADIENT or shape.stroke.type == PaintType.RADIAL_GRADIENT) then
2490 | scaleGradient(shape.stroke.gradient, tx,ty, sx,sy);
2491 | ffi.copy(t, shape.stroke.gradient.xform, ffi.sizeof("double")*6);
2492 | xform.xformInverse(shape.stroke.gradient.xform, t);
2493 | end
2494 |
2495 | shape.strokeWidth = shape.strokeWidth * avgs;
2496 | shape.strokeDashOffset = shape.strokeDashOffset * avgs;
2497 | for i = 0, shape.strokeDashCount-1 do
2498 | shape.strokeDashArray[i] = shape.strokeDashArray[i] * avgs;
2499 | end
2500 | end
2501 |
2502 | print("SHAPE TO VIEW BOX - END")
2503 | end
2504 |
2505 |
2506 | return SVGParser
2507 |
--------------------------------------------------------------------------------