自学内容网 自学内容网

【Godot4.2】SVGParser - SVG解析器函数库

概述

这是一个基于GDScript内置XMLParser编写的简易SVG文件解析函数库。

目的就是可以将SVG文件解析为GDSCript可以处理的字典或DOM形式,方便SVG渲染和编辑。

目前还只是一个简易实现版本。还需要一些改进。

函数库源码

# =============================================
# 名称:SVGParser
# 类型:静态函数库
# 描述:解析SVG文件,并转换为字典
# 作者:巽星石
# 创建时间:202472018:22:43
# 最后修改时间:202472101:17:52
# =============================================
class_name SVGParser

# 标签顺序列表
# 返回SVG解析后的所有单标签和双标签的起始和结束标签
static func get_tags_list(path:String) -> Array:
var tags:Array = []
var xml = XMLParser.new()
var err = xml.open(path)
if err == OK:
while xml.read() == OK:
match xml.get_node_type():
XMLParser.NODE_ELEMENT:  # 起始标签
if xml.is_empty(): # 单标签(自闭合)
tags.append("<%s />" % [xml.get_node_name()])
else:
tags.append("<%s>" %  [xml.get_node_name()])
XMLParser.NODE_ELEMENT_END:  # 结束标签
tags.append("</%s>" %  [xml.get_node_name()])
return tags

# 将SVG文档转化为字典
static func to_dict(path:String) -> Dictionary:
var xml = XMLParser.new()
var err = xml.open(path)
if err == OK:
# 1.获取顺序标签列表(包含起始单标签、双标签的起始和结束标签)
var tags:Array = []

while xml.read() == OK:
if xml.get_node_type() in [XMLParser.NODE_ELEMENT,XMLParser.NODE_ELEMENT_END]:
# 构造字典
var tag = {}
if xml.get_node_type() == XMLParser.NODE_ELEMENT:
tag["name"] = xml.get_node_name()
else:
tag["name"] = "/%s" % xml.get_node_name()
tag["is_single"] = xml.is_empty()  # 是否单标签
tag["index"] = tags.size()
tag["children"] = []
# 构造属性字典
tag["attrs"] = {}
for i in range(xml.get_attribute_count()):
tag["attrs"][xml.get_attribute_name(i)] = xml.get_attribute_value(i)
tags.append(tag)
#print(JSON.stringify(tags,"\t"))

# 2.使用栈获取双标签的其实和结束范围序列
var stack:Array = []
var arr:Array = []  # 双标签的起止索引
for i in range(tags.size()):
if tags[i]["is_single"] != true: # 双标签
if !tags[i]["name"].begins_with("/"): #起始标签
stack.push_front(tags[i])   # 推入
else: # 结束标签
var last_tag = stack.pop_front()
if tags[i]["name"] == "/%s" % last_tag["name"]: # 起止标签匹配
arr.append([last_tag["index"],tags[i]["index"]])

#print(JSON.stringify(stack,"\t"))


# 3.设定父子级别关系
# 获取所有双标签的起始标签索引
var dbl_indexs = []
for i in range(arr.size()):
dbl_indexs.append(arr[i][0])
# 删除标签列表中所有双标签结束标签
for i in range(tags.size()):
if tags[i]["name"].begins_with("/"):
tags[i] = null
for tag in tags:
if tag == null:
tags.erase(tag)
for tag in tags:
if tag == null:
tags.erase(tag)
# 遍历双标签索引对,设定父子关系
for i in range(arr.size()):
var start = arr[i][0]
var end = arr[i][1]
# 遍历子标签
for x in range(start+1,end):
var tag = get_tag(tags,x)
#print(tag)
if tag != null and !tag["name"].begins_with("/"):
get_tag(tags,start)["children"].append(tag)
remove_tag(tags,x)
return tags[0]
else:
return {}

static func get_tag(tags,index):
for tag in tags:
if tag != null:
if tag["index"] == index:
return tag

static func remove_tag(tags,index):
for tag in tags:
if tag != null:
if tag["index"] == index:
tags.erase(tag)

# ================================== 简易DOM创建 ==================================
# SVG元素
class item:
var tag_name:String = ""
var attrs:Dictionary
var children:Array[item] = [] # 子节点

func _to_string() -> String:
return "\n%s:%s\n" % [tag_name,str(children)]

# 递归形式创建DOM
static func dom(item_dic:Dictionary) -> item:
var itm = item.new()
itm.tag_name = item_dic["name"]
for dic in item_dic["children"]:
itm.children.append(dom(dic))
return itm

# 返回SVG文件的DOM形式
static func to_DOM(path:String) -> item:
var dict = to_dict(path)
return dom(dict)

获取顺序标签列表

在这里插入图片描述

我们以Godot图标icon.svg为例,其SVG源码如下:

<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg">
    <rect x="2" y="2" width="124" height="124" rx="14" fill="#363d52" stroke="#212532" stroke-width="4" />
    <g transform="scale(.101) translate(122 122)">
        <g fill="#fff">
            <path d="M105 673v33q407 354 814 0v-33z" />
            <path fill="#478cbf"
                d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 813 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H447l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z" />
            <path d="M483 600c3 34 55 34 58 0v-86c-3-34-55-34-58 0z" />
            <circle cx="725" cy="526" r="90" />
            <circle cx="299" cy="526" r="90" />
        </g>
        <g fill="#414042">
            <circle cx="307" cy="532" r="60" />
            <circle cx="717" cy="532" r="60" />
        </g>
    </g>
</svg>

测试代码:

@tool
extends EditorScript

func _run() -> void:
var tags = SVGParser.get_tags_list("icon.svg")
print(JSON.stringify(tags,"\t"))

输出:

[
"<svg>",
"<rect />",
"<g>",
"<g>",
"<path />",
"<path />",
"<path />",
"<circle />",
"<circle />",
"</g>",
"<g>",
"<circle />",
"<circle />",
"</g>",
"</g>",
"</svg>"
]

可以看到其返回SVG解析后的所有单标签和双标签(包括起始和结束标签)的顺序列表。

通过它可以测试函数库是否正确解析了SVG文件的标签结构。

SVG转字典

  • to_dict()方法,可以将SVG文件内容解析和转化为GDScript的字典。

测试代码:

@tool
extends EditorScript

func _run() -> void:
var dict = SVGParser.to_dict("res://icon.svg")
print(JSON.stringify(dict,"\t"))

转化结果:

{
"attrs": {
"height": "128",
"width": "128",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"attrs": {
"fill": "#363d52",
"height": "124",
"rx": "14",
"stroke": "#212532",
"stroke-width": "4",
"width": "124",
"x": "2",
"y": "2"
},
"children": [],
"index": 1,
"is_single": true,
"name": "rect"
},
{
"attrs": {
"transform": "scale(.101) translate(122 122)"
},
"children": [
{
"attrs": {
"fill": "#fff"
},
"children": [
{
"attrs": {
"d": "M105 673v33q407 354 814 0v-33z"
},
"children": [],
"index": 4,
"is_single": true,
"name": "path"
},
{
"attrs": {
"d": "m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 813 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H447l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z",
"fill": "#478cbf"
},
"children": [],
"index": 5,
"is_single": true,
"name": "path"
},
{
"attrs": {
"d": "M483 600c3 34 55 34 58 0v-86c-3-34-55-34-58 0z"
},
"children": [],
"index": 6,
"is_single": true,
"name": "path"
},
{
"attrs": {
"cx": "725",
"cy": "526",
"r": "90"
},
"children": [],
"index": 7,
"is_single": true,
"name": "circle"
},
{
"attrs": {
"cx": "299",
"cy": "526",
"r": "90"
},
"children": [],
"index": 8,
"is_single": true,
"name": "circle"
}
],
"index": 3,
"is_single": false,
"name": "g"
},
{
"attrs": {
"fill": "#414042"
},
"children": [
{
"attrs": {
"cx": "307",
"cy": "532",
"r": "60"
},
"children": [],
"index": 11,
"is_single": true,
"name": "circle"
},
{
"attrs": {
"cx": "717",
"cy": "532",
"r": "60"
},
"children": [],
"index": 12,
"is_single": true,
"name": "circle"
}
],
"index": 10,
"is_single": false,
"name": "g"
}
],
"index": 2,
"is_single": false,
"name": "g"
}
],
"index": 0,
"is_single": false,
"name": "svg"
}

目前除了text标签还需要一点特殊处理外,其他标签已经不存在明显解析问题。

在字典基础上,已经可以实现在Godot中的分层渲染和转为内置绘图函数绘制。也可以进一步转化为DOM形式,方便编辑和二次输出。

生成DOM

@tool
extends EditorScript

func _run() -> void:
var dom = SVGParser.to_DOM("icon.svg")
print(dom)

输出:

svg:[
rect:[]
, 
g:[
g:[
path:[]
, 
path:[]
, 
path:[]
, 
circle:[]
, 
circle:[]
]
, 
g:[
circle:[]
, 
circle:[]
]
]
]

整理后:

svg:[
    rect:[], 
    g:[
        g:[
            path:[], 
            path:[], 
            path:[], 
            circle:[], 
            circle:[]
        ], 
        g:[
            circle:[], 
            circle:[]
        ]
    ]
]

原文地址:https://blog.csdn.net/graypigen1990/article/details/140580540

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!