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

我有以下形式的路径定义(例如):

`` ` <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 生成的。 规格 inkscape 生成的路径

点赞
用户1847592
用户1847592
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
-- 现在t == {{'M', 20, 30}, {'L', 20, 20}, {'L', 20, 40}, {'L', 40, 40}}
2013-05-31 18:44:51
用户1244588
用户1244588

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

好奇心驱使我,尽管我通常不喜欢“帮我做这个工作”的文章

--- 将 svg `path` 属性解析成 2D 数组
function parsePath(input)
    local output, line = {}, {};
    output[#output+1] = line;

    input = input:gsub("([^%s,;])([%a])", "%1 %2"); -- 将 "100D" 转换为 "100 D"
    input = input:gsub("([%a])([^%s,;])", "%1 %2"); -- 将 "D100" 转换为 "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

-- 测试输出
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 定义 后)

SVG 标准声明:可以消除多余的空白和分隔符,如逗号,但是在 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

-- 测试输出 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```

此函数非常宽容,允许留下任何不必要的空格,并且不验证任何语义,除了它将丢弃任何在第一个字母之前的数据,该字母不是eE

它还将默默地忽略任何不匹配的数据。

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

2013-06-02 15:18:43
用户12968803
用户12968803

我喜欢 Egor 的解决方案,但它不适用于小数和字母 V 和 H,所以:

local function parsePath (input)
    input = input:gsub("([^%s,;])([%a])", "%1 %2") -- 将 "100D" 转换为 "100 D"
    input = input:gsub("([%a])([^%s,;])", "%1 %2") -- 将 "D100" 转换为 "D 100"
    local output, line = {}
    for v in input:gmatch("([^%s,;]+)") do
        if tonumber(v) then
            line[#line+1] = math.floor(tonumber(v)+0.5)
        else
            line = {v}
            output[#output+1] = line
        end
    end
    return output
end

执行:

local ds = {
    "M40,360H-40", -- 线段
    "M -40,480 H 40", -- 线段
    "M 840,1000 V 920 M 720,920 V 1000", -- 两条线段
    "M 1280.1,-39.9 1200.1,39.9", -- 带有小数的线段
    "M 1320,40 1400,-40",
    "M 40,480 H 360",
    "M 760,360 400,360",
    "M 1120,240 1320,40",
    "M 400,360 40,360",

    "M 840,920 C 840,680 1040,320 1120,240", -- 三次贝塞尔曲线
    "M 1200,40 C 1160,80 1120,120 1080,160",
    "M 1080,160 C 920,320 520,360 400,360",
    "M 360,480 C 520,480 720,760 720,920",
    "M 760,360 C 640,360 560,520 640,600",
    "M 640,600 C 720,680 840,640 880,560",
    "M 880,560 C 920,480 880,360 760,360",
    "M 1080,160 C 1040,200 920,360 760,360",
    "M 360,480 C 480,480 560,520 640,600",
    "M 640,600 C 720,680 720,840 720,920",
    "M 840,920 C 840,800 840,640 880,560",
    "M 880,560 C 920,480 1120,240 1120,240",

    "M 0,600 H 360 L 600,840 V 960 H 0",
    "M 1440,0 H 1920 V 960 H 960 V 720",
    "M 0,0 H 1080 L 840,240 H 0"
}

for i, d in ipairs (ds) do
    local parsedPath = parsePath (d)
    local str = '{'
    for i, component in ipairs (list) do
        str = str .. '{'.. table.concat(component, ',') ..'},'
    end
    str = str:sub(1, -2) -- 移除最后的逗号
    str = str .. '}'
    print (str)
end

结果:

{{M,40,360},{H,-40}}
{{M,-40,480},{H,40}}
{{M,840,1000},{V,920}}
{{M,720,920},{V,1000}}
{{M,1200,-40},{V,40}}
{{M,1320,40},{V,-40}}
{{M,840,920},{C,840,800,1000,560,1080,480},{M,1080,480},{C,1160,400,1320,240,1320,40}}
{{M,1200,40},{C,1200,120,1160,160,1080,200},{M,1080,200},{C,920,280,720,360,480,360},{M,480,360},{C,3360,360,120,360,40,360}}
{{M,40,480},{C,40,480,240,480,360,480,520,480,720,760,720,920}}
{{M,760,400},{C,640,400,560,520,640,600}}
{{M,640,600},{C,720,680,800,640,840,600}}
{{M,840,600},{C,880,560,880,400,760,400}}
{{M,1080,200},{C,920,280,920,400,760,400}}
{{M,760,400},{C,640,400,600,360,480,360}}
{{M,360,480},{C,480,480,560,520,640,600}}
{{M,640,600},{C,720,680,720,840,720,920}}
{{M,840,920},{C,840,800,760,680,840,600}}
{{M,840,600},{C,880,560,1040,520,1080,480}}
{{M,0,0},{V,240},{H,720},{C,720,240,1080,120,1080,120},{V,0}}
{{M,0,0},{H,1080},{V,120},{L,840,240},{H,0}}
{{M,0,600},{H,360},{L,600,840},{V,960},{H,0}}
{{M,1440,0},{H,1920},{V,960},{H,960},{V,840},{L,1200,800,1400,600,1440,360}}
2021-12-25 21:52:17