【问题标题】:Parse SVG path definition ("d") in Lua在 Lua 中解析 SVG 路径定义(“d”)
【发布时间】:2013-05-27 15:27:42
【问题描述】:

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

<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

【问题讨论】:

  • 你想覆盖多少“有效路径”?您是否尝试实际进行 SVG 验证并检查错误?
  • 不,我想知道路径包含哪些命令以及这些命令采用哪些参数。这很棘手,因为很多定义都是有效的:“m 10,20 l 30,40”、“m10 20 l30, 40”等。涵盖所有有效的 svg 路径会很酷,但我可以限制为 Inkscape 生成的路径.

标签: svg lua vector-graphics lua-patterns


【解决方案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

这个函数非常宽松,因为它允许忽略任何不必要的空白,并且不验证任何语义,除了它会丢弃第一个字母之前不是eE的任何数据。

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

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

【讨论】:

  • 谢谢 :) 这并不完全是“为我做这项工作”,我只是不确定是否可以使用 Lua 模式来实现。我找到了一个涉及本机代码的现成解决方案,这对我来说有点难以包含在我的项目中。同时,我使用gmatch 编写了我自己的解决方案,它似乎解析了所有有效和maaany 无效定义。稍后我会在这里发布。谢谢你的回答。
  • 在阅读了 SVG 定义后,我更新了我的答案,疯狂的东西,他们为 BNF 中的每条指令定义了语法,而不是使用可变数量的参数。但是,理论上我认为编写一个包含所有可能功能并拒绝所有不符合要求的大量正则表达式模式是可能的。但不是在普通 Lua 中,因为您需要分支(通常是 | 字符)。
  • 我还注意到 Lua 模式中缺少 |。拒绝不合格的字符串并不好玩,我不是 W3C 验证者——我只想阅读有效的字符串。 ;) 是的,标准实际上非常疯狂。
  • 看起来第二个正则表达式无法处理规范中的以下情况(多点):同样,对于字符串“M 0.6.5”,“moveto”的第一个坐标消耗字符“0.6”并在遇到第二个小数点时停止,因为“坐标”的产生只允许一个小数点。结果是第一个坐标为“0.6”,第二个坐标为“.5”。
  • 不知道什么是多点,8年前我写这个答案时不太可能在标准中遇到它,或者我至少会提到它,我只能说我没有在此期间没有使用 SVG 规范,因此我无法添加任何其他信息。
【解决方案2】:
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}}

【讨论】:

    【解决方案3】:

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

    local function parsePath (input)
        input = input:gsub("([^%s,;])([%a])", "%1 %2") -- Convert "100D" to "100 D"
        input = input:gsub("([%a])([^%s,;])", "%1 %2") -- Convert "D100" to "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", -- line
        "M -40,480 H 40", -- line
        "M 840,1000 V 920 M 720,920 V 1000", -- two lines
        "M 1280.1,-39.9 1200.1,39.9", -- line with decimals
        "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", -- cubic bezier
        "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) -- remove last comma
        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}}
    

    【讨论】:

      猜你喜欢
      • 2012-03-13
      • 1970-01-01
      • 2019-03-12
      • 1970-01-01
      • 2018-02-04
      • 2022-01-26
      • 1970-01-01
      • 1970-01-01
      • 2022-01-14
      相关资源
      最近更新 更多