在Lua中解析SVG路径定义(“d”)

时间:2013-05-31 18:25:20

标签: svg lua vector-graphics lua-patterns

我有这种形式的路径定义(例子):

<path d="M 20 30 L 20 20 20 40 40 40"/>

在Lua中,成为:

"M 20 30 L 20 20 20 40 40 40"

我怎样才能在纯Lua中解析它以获得类似的东西:

{'M', 20, 30, 'L', 20, 20, 20, 40, 40, 40 }

或者,完美:

{{'M', 20, 30}, {'L', 20, 20}, {'L', 20, 40}, {'L', 40, 40}}

Lua模式是否具备此类功能?

编辑: 我想要涵盖所有有效的SVG路径,或者至少包含Inkscape生成的路径。 specification inkscape-generated path

2 个答案:

答案 0 :(得分:1)

local path = 'M 20 30 L 20 20 20 40 40 40'

local s, t = '', {}
for c, x, y in path:gmatch'(%a?)%s*(%d+)%s*(%d+)' do
   s = (s..c):sub(-1)
   t[#t+1] = {s, tonumber(x), tonumber(y)}
end
-- Now t == {{'M', 20, 30}, {'L', 20, 20}, {'L', 20, 40}, {'L', 40, 40}}

答案 1 :(得分:1)

不是直接的,你当然需要一个简化的解析器。

好奇心让我变得更好,虽然我常常不喜欢“为我做这件事”的帖子

--- Parse svg `path` attribute into 2-D array
function parsePath(input)
    local output, line = {}, {};
    output[#output+1] = line;

    input = input:gsub("([^%s,;])([%a])", "%1 %2"); -- Convert "100D" to "100 D"
    input = input:gsub("([%a])([^%s,;])", "%1 %2"); -- Convert "D100" to "D 100"
    for v in input:gmatch("([^%s,;]+)") do
        if not tonumber(v) and #line > 0 then
            line = {};
            output[#output+1] = line;
        end
        line[#line+1] = v;
    end
    return output;
end

-- Test output
local input = 'M20 30L20 20,20 40;40 40 X1 2 3 12.8z';
local r = parsePath(input);
for i=1, #r do
    print("{ "..table.concat(r[i], ", ").." }");
end

由于Inkscape似乎总是在指令和数字之间放置一个空格,如果你只解析Inkscape生成的文件,你可以省去两个gsub行。

该函数还抛弃了大多数随机字符,Inkscape喜欢将其置于路径定义中,但如果确实想要读取符合该路径的所有路径定义,则可能会有一些细节需要解决。标准。

更新(略过SVG BNF definition后)

SVG标准声明Superfluous white space and separators such as commas can be eliminated,但是在查看BNF表示法时,我找不到除空格和逗号之外的任何其他分隔符。

所以你可以将第二个正则表达式改为"([^%a%d%.eE-]+)"。但我认为以下功能更适合:

function parsePath(input)
    local out = {};

    for instr, vals in input:gmatch("([a-df-zA-DF-Z])([^a-df-zA-DF-Z]*)") do
        local line = { instr };
        for v in vals:gmatch("([+-]?[%deE.]+)") do
            line[#line+1] = v;
        end
        out[#out+1] = line;
    end
    return out;
end

-- Test output
local input = 'M20-30L20,20,20X40,40-40H1,2E1.7 1.8e22,3,12.8z';
local r = parsePath(input);
for i=1, #r do
    print("{ "..table.concat(r[i], ", ").." }");
end

这个函数非常宽松,因为它允许遗漏任何不必要的空格,并且不会验证任何语义,除了它会在第一个字母不是e或{{1之前丢弃任何数据}}

它也会默默地忽略任何不匹配的数据。

如果您只想匹配现有说明,可以将模式E替换为([a-df-zA-DF-Z])([^a-df-zA-DF-Z]*)。但是这会导致不存在的指令的所有值都被添加到前一条指令中,因此我认为这不是一个好主意,最好解析一个超集并稍后在语义上抛出错误。