解析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 生成的路径
不直接的话,你当然需要一个简化的解析器。
好奇心驱使我,尽管我通常不喜欢“帮我做这个工作”的文章
--- 将 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```
此函数非常宽容,允许留下任何不必要的空格,并且不验证任何语义,除了它将丢弃任何在第一个字母之前的数据,该字母不是e或E。
它还将默默地忽略任何不匹配的数据。
如果您只想匹配现有指令,可以将模式([a-df-zA-DF-Z])([^a-df-zA-DF-Z] *)替换为([MmZzLlHhVvCcSsQqTtAa])([^ MmZzLlHhVvCcSsQqTtAa] *)。但是,这将导致不存在的指令的所有值均添加到上一个指令中,因此我认为这不是一个好主意,最好解析超集并在语义上出现错误。
我喜欢 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}}
- 如何将两个不同的lua文件合成一个 东西有点长 大佬请耐心看完 我是小白研究几天了都没搞定
- 如何在roblox studio中1:1导入真实世界的地形?
- 求解,lua_resume的第二次调用继续执行协程问题。
- 【上海普陀区】内向猫网络招募【Skynet游戏框架Lua后端程序员】
- SF爱好求教:如何用lua实现游戏内调用数据库函数实现账号密码注册?
- Lua实现网站后台开发
- LUA错误显式返回,社区常见的规约是怎么样的
- lua5.3下载库失败
- 请问如何实现文本框内容和某个网页搜索框内容连接,并把网页输出来的结果反馈到另外一个文本框上
- lua lanes多线程使用
- 一个kv数据库
- openresty 有没有比较轻量的 docker 镜像
- 想问一下,有大佬用过luacurl吗
- 在Lua执行过程中使用Load函数出现问题
- 为什么 neovim 里没有显示一些特殊字符?
- Lua比较两个表的值(不考虑键的顺序)
- 有个lua简单的项目,外包,有意者加微信 liuheng600456详谈,最好在成都
- 如何在 Visual Studio 2022 中运行 Lua 代码?
- addEventListener 返回 nil Lua
- Lua中获取用户配置主目录的跨平台方法
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}}