【Godot4.2】SVGParser - SVG解析器函数库
概述
这是一个基于GDScript内置XMLParser编写的简易SVG文件解析函数库。
目的就是可以将SVG文件解析为GDSCript可以处理的字典或DOM形式,方便SVG渲染和编辑。
目前还只是一个简易实现版本。还需要一些改进。
函数库源码
# =============================================
# 名称:SVGParser
# 类型:静态函数库
# 描述:解析SVG文件,并转换为字典
# 作者:巽星石
# 创建时间:2024年7月20日18:22:43
# 最后修改时间:2024年7月21日01: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)!