/// 文档反序列化:从 Document::to_json 的输出重建文档。
/// save/open(持久化)与 Studio 多文档管理的根基。
///
/// 解析策略:定位式递归下降(引擎其余 JSON 输出同款思路,native 可用无依赖)。
/// to_json 结构:{"revision":N,"root_id":"root","nodes":{嵌套树}}
///|
/// 从 JSON 文本重建 Document(失败返回 Err)
pub fn Document::from_json(text : String) -> Result[Document, String] {
// 找 nodes 树起点
let marker = "\"nodes\":"
let idx = match text.find(marker) {
Some(i) => i + marker.length()
None => return Err("json_missing_nodes")
}
let body = text.substring(start=idx)
let store : JsonNodeStore = { nodes: Map::new(), }
match parse_node_obj(body, 0, store) {
ParseFail(msg) => Err(msg)
ParseOk(root, _end) => {
let w = root_fixed(root.width, 390.0)
let h = root_fixed(root.height, 844.0)
let doc = Document::new({ width: w, height: h, })
// 树中已注册全部节点(含 root 原id),统一改挂到 "root"
let rn = { ..root, id: "root", x: Fixed(0.0), y: Fixed(0.0), }
// 先把 store 里 root 原id 的 children 转移过来
doc.nodes.set("root", rn)
remap_children(doc, store, root.id, "root")
Ok(doc)
}
}
}
///|
fn root_fixed(s : SizeSpec, fallback : Double) -> Double {
match s {
Fixed(v) => v
_ => fallback
}
}
///|
/// parse 期间注册的节点表(id → Node,children id 已带)
struct JsonNodeStore {
nodes : Map[String, Node]
}
///|
/// 把 store 中 old_root 子树的全部节点注册进 doc(根用 new_id)
fn remap_children(
doc : Document,
store : JsonNodeStore,
old_root : String,
new_id : String,
) -> Unit {
match store.nodes.get(old_root) {
None => ()
Some(n) =>
for cid in n.children.iter() {
match store.nodes.get(cid) {
None => ()
Some(child) => {
let _ = doc.insert_node(child, new_id)
remap_children(doc, store, cid, cid)
}
}
}
}
}
///|
enum ParseResult {
ParseOk(Node, Int)
ParseFail(String)
}
///|
/// 解析一个节点对象:{"id":...,"kind":...,...,"children":[...]}
/// 整棵子树注册进 store,返回根节点与消费长度
fn parse_node_obj(
s : String,
start : Int,
store : JsonNodeStore,
) -> ParseResult {
// 必须以 { 开头
if char_at2(s, start) != "{" {
return ParseFail("expected_object")
}
let mut pos = start + 1
let mut id = ""
let mut kind = "frame"
let mut name = ""
let mut component = ""
let mut width : SizeSpec = Fixed(100.0)
let mut height : SizeSpec = Fixed(100.0)
let mut x : PosSpec = Fixed(0.0)
let mut y : PosSpec = Fixed(0.0)
let mut text = ""
let mut fill = "none"
let mut font_size = 14.0
let mut font_weight = "normal"
let mut corner_radius = 0.0
let mut stroke = "none"
let mut stroke_width = 0.0
let mut shadow = "none"
let mut opacity = 1.0
let mut text_color = "#111111"
let mut rotate = 0.0
let mut blur = 0.0
let mut blend = "normal"
let mut line_height = 1.5
let mut tracking = 0.0
let mut text_align = "left"
let mut font_style = "normal"
let mut stroke_dash = "solid"
let mut visible = true
let mut constraint = "lt"
let mut flip = "none"
let kids : Array[Node] = []
let mut seen_children = false
let n = s.length()
while pos < n {
pos = skip_ws2(s, pos)
if char_at2(s, pos) == "}" {
pos = pos + 1
break
}
// 字段名(parse_string 已按 JSON 转义规则解码,无需二次 unescape)
let (key, p1) = parse_string(s, pos)
pos = p1
pos = skip_ws2(s, pos)
if char_at2(s, pos) != ":" {
return ParseFail("expected_colon")
}
pos = pos + 1
pos = skip_ws2(s, pos)
let key_t = key
if key_t == "children" {
seen_children = true
if char_at2(s, pos) != "[" {
return ParseFail("children_not_array")
}
pos = pos + 1
while pos < n {
pos = skip_ws2(s, pos)
if char_at2(s, pos) == "]" {
pos = pos + 1
break
}
match parse_node_obj(s, pos, store) {
ParseFail(e) => return ParseFail(e)
ParseOk(child, used) => {
kids.push(child)
pos = used
}
}
pos = skip_ws2(s, pos)
if char_at2(s, pos) == "," {
pos = pos + 1
}
}
} else if key_t == "style" {
// style 子对象:递归解析全部视觉字段
if char_at2(s, pos) == "{" {
let send = skip_object(s, pos)
let sp = parse_style_obj(s.substring(start=pos, end=send))
if sp.fill != "" {
fill = sp.fill
}
if sp.stroke != "" {
stroke = sp.stroke
}
corner_radius = sp.radius
stroke_width = sp.stroke_width
opacity = sp.opacity
font_size = sp.font_size
if sp.text_color != "" {
text_color = sp.text_color
}
if sp.font_weight != "" {
font_weight = sp.font_weight
}
if sp.shadow != "" {
shadow = sp.shadow
}
rotate = sp.rotate
blur = sp.blur
if sp.blend != "" {
blend = sp.blend
}
if sp.line_height > 0.0 {
line_height = sp.line_height
}
tracking = sp.tracking
if sp.flip != "" {
flip = sp.flip
}
if sp.text_align != "" {
text_align = sp.text_align
}
if sp.font_style != "" {
font_style = sp.font_style
}
if sp.stroke_dash != "" {
stroke_dash = sp.stroke_dash
}
visible = sp.visible
pos = send
} else {
return ParseFail("style_not_object")
}
} else if char_at2(s, pos) == "\"" {
let (v, p2) = parse_string(s, pos)
pos = p2
let vt = v
match key_t {
"id" => id = vt
"kind" => kind = vt
"name" => name = vt
"component" => component = vt
"text" => text = vt
"root_id" => id = "root"
"fill" => fill = vt
"stroke" => stroke = vt
"shadow" => shadow = vt
"font_weight" => font_weight = vt
"blend" => blend = vt
"constraint" => constraint = vt
"flip" => flip = vt
_ => ()
}
} else {
// 数值或对象(width/height/x/y 是 spec 对象;font_size 等是数值)
let (num_or_obj, p3) = parse_value(s, pos)
pos = p3
match key_t {
"font_size" => font_size = num_or_obj.num
"corner_radius" => corner_radius = num_or_obj.num
"opacity" => opacity = num_or_obj.num
"rotate" => rotate = num_or_obj.num
"blur" => blur = num_or_obj.num
"line_height" => line_height = num_or_obj.num
"tracking" => tracking = num_or_obj.num
"text_color" => text_color = num_or_obj.str
"width" => width = num_or_obj.spec
"height" => height = num_or_obj.spec
"x" => x = num_or_obj.pos
"y" => y = num_or_obj.pos
_ => ()
}
}
pos = skip_ws2(s, pos)
if char_at2(s, pos) == "," {
pos = pos + 1
}
}
ignore(seen_children)
let k : NodeKind = match kind {
"text" => Text
"rect" => Rect
"image" => Image
"group" => Group
_ => Frame
}
let mut node = {
..Node::make(if id == "" { "n_auto" } else { id }, k),
name: if name == "" {
id
} else {
name
},
component: if component == "" {
kind_component(k)
} else {
component
},
width,
height,
x,
y,
text,
style: {
..NodeStyle::default(),
fill,
stroke,
stroke_width,
corner_radius,
opacity,
font_size,
text_color,
font_weight,
shadow,
rotate,
blur,
blend,
line_height,
tracking,
flip,
text_align,
font_style,
stroke_dash,
visible,
},
constraint,
}
// 注册到 store(父在子之前解析,children 顺序保持)
let real_id = node.id
let final_kids : Array[String] = []
for c in kids.iter() {
final_kids.push(c.id)
}
node = { ..node, children: final_kids, }
store.nodes.set(real_id, node)
ParseOk(node, pos)
}
///|
/// 解析字符串字面量并按 JSON 转义规则完整解码:
/// \n \t \r \b \f \\ \" \/ 与 \uXXXX(含代理对合成)。
/// 返回解码后的内容与结束位置——调用侧不得再做二次 unescape,
/// 否则解码结果中的字面反斜杠会被再次吃掉。
fn parse_string(s : String, start : Int) -> (String, Int) {
let sb = StringBuilder::new()
let mut pos = start + 1 // 跳过开引号
let n = s.length()
while pos < n {
let c = char_at2(s, pos)
if c == "\\" {
if pos + 1 >= n {
break
}
let e = char_at2(s, pos + 1)
match e {
"n" => {
sb.write_char('\n')
pos = pos + 2
}
"t" => {
sb.write_char('\t')
pos = pos + 2
}
"r" => {
sb.write_char('\r')
pos = pos + 2
}
"b" => {
sb.write_char(Char::from_int(8))
pos = pos + 2
}
"f" => {
sb.write_char(Char::from_int(12))
pos = pos + 2
}
"u" => {
// \uXXXX:4 位十六进制码点;高代理后紧跟低代理时合成真实码点
let hi = hex4_at(s, pos + 2)
if hi < 0 {
// 畸形 \u:原样保留前缀,跳过反斜杠继续
sb.write_string("\\u")
pos = pos + 2
} else {
pos = pos + 6
let mut cp = hi
if hi >= 0xD800 && hi <= 0xDBFF &&
pos + 1 < n &&
char_at2(s, pos) == "\\" &&
char_at2(s, pos + 1) == "u" {
let lo = hex4_at(s, pos + 2)
if lo >= 0xDC00 && lo <= 0xDFFF {
cp = 0x10000 + (hi - 0xD800) * 0x400 + (lo - 0xDC00)
pos = pos + 6
}
}
sb.write_char(Char::from_int(cp))
}
}
other => {
// \" \\ \/ 等转义还原为字面字符
sb.write_string(other)
pos = pos + 2
}
}
} else if c == "\"" {
pos = pos + 1
break
} else {
sb.write_string(c)
pos = pos + 1
}
}
(sb.to_string(), pos)
}
///|
/// 从 pos 起读 4 位十六进制(\uXXXX 的 XXXX),非法返回 -1
fn hex4_at(s : String, pos : Int) -> Int {
let n = s.length()
if pos + 4 > n {
return -1
}
let mut v = 0
for i in 0..<4 {
let code = char_code2(s, pos + i)
let d = if code >= 48 && code <= 57 {
code - 48
} else if code >= 97 && code <= 102 {
code - 87
} else if code >= 65 && code <= 70 {
code - 55
} else {
-1
}
if d < 0 {
return -1
}
v = v * 16 + d
}
v
}
///|
/// 通用值:数值 / spec 对象,返回带类型的中间结构
struct ScalarOrSpec {
num : Double
str : String
spec : SizeSpec
pos : PosSpec
}
///|
fn parse_value(s : String, start : Int) -> (ScalarOrSpec, Int) {
let out : ScalarOrSpec = {
num: 0.0,
str: "",
spec: Fixed(0.0),
pos: Fixed(0.0),
}
if char_at2(s, start) == "{" {
// spec 对象 {"kind":"fixed","value":N}
let (kind_s, p1) = parse_string_after_key(s, start, "kind")
let end = skip_object(s, start)
let kind = unescape_json(kind_s)
let value = load_number(s.substring(start~, end~), "value")
let r : ScalarOrSpec = match kind {
"fixed" => { num: value, str: "", spec: Fixed(value), pos: Fixed(value), }
"fill" => { num: 0.0, str: "", spec: Fill, pos: Fixed(0.0), }
"hug" => { num: 0.0, str: "", spec: Hug, pos: Fixed(0.0), }
"align_start" =>
{ num: value, str: "", spec: Fixed(value), pos: AlignStart(value), }
"align_end" =>
{ num: value, str: "", spec: Fixed(value), pos: AlignEnd(value), }
"center" => { num: 0.0, str: "", spec: Fixed(0.0), pos: CenterInParent, }
_ => out
}
(r, end)
// 字符串形式的样式值(text_color 等)
} else if char_at2(s, start) == "\"" {
let (v, p) = parse_string(s, start)
let r : ScalarOrSpec = {
num: 0.0,
str: v,
spec: Fixed(0.0),
pos: Fixed(0.0),
}
(r, p)
} else {
// 纯数字
let mut num = 0.0
let mut frac = 0.1
let mut dot = false
let mut neg = false
let mut pos = start
let n = s.length()
while pos < n {
let c = char_at2(s, pos)
let code = char_code2(s, pos)
if code >= 48 && code <= 57 {
let d = (code - 48).to_double()
if dot {
num = num + d * frac
frac = frac * 0.1
} else {
num = num * 10.0 + d
}
pos = pos + 1
} else if c == "." {
dot = true
pos = pos + 1
} else if c == "-" && num == 0.0 && !dot {
neg = true
pos = pos + 1
} else {
break
}
}
let r : ScalarOrSpec = {
num: if neg {
-num
} else {
num
},
str: "",
spec: Fixed(0.0),
pos: Fixed(0.0),
}
(r, pos)
}
}
///|
/// 在对象里找 "key":"value" 的 value(parse_value 辅助)
fn parse_string_after_key(
s : String,
start : Int,
key : String,
) -> (String, Int) {
let pattern = "\"" + key + "\":\""
let sub = s.substring(start~)
match sub.find(pattern) {
Some(i) => {
let vs = i + pattern.length()
let rest = sub.substring(start=vs)
match rest.find("\"") {
Some(e) => (rest.substring(start=0, end=e), start + vs + e)
None => ("", start)
}
}
None => ("", start)
}
}
///|
/// 跳过一个完整对象/数组文本,返回结束下标(不含)
fn skip_object(s : String, start : Int) -> Int {
let mut depth = 0
let mut pos = start
let mut in_str = false
let n = s.length()
while pos < n {
let c = char_at2(s, pos)
if in_str {
if c == "\\" {
pos = pos + 2
continue
}
if c == "\"" {
in_str = false
}
} else if c == "\"" {
in_str = true
} else if c == "{" || c == "[" {
depth = depth + 1
} else if c == "}" || c == "]" {
depth = depth - 1
if depth == 0 {
return pos + 1
}
}
pos = pos + 1
}
n
}
///|
/// 从子串里提取 "key": 后面的数字
fn load_number(s : String, key : String) -> Double {
let pattern = "\"" + key + "\":"
match s.find(pattern) {
Some(i) => {
let rest = s.substring(start=i + pattern.length())
let mut num = 0.0
let mut frac = 0.1
let mut dot = false
for c in rest.iter() {
let code = c.to_int()
if code >= 48 && code <= 57 {
let d = (code - 48).to_double()
if dot {
num = num + d * frac
frac = frac * 0.1
} else {
num = num * 10.0 + d
}
} else if code == 46 && !dot {
dot = true
} else {
break
}
}
num
}
None => 0.0
}
}
///|
/// 还原 JSON 转义(\n \t \" \\ 等)
fn unescape_json(s : String) -> String {
let sb = StringBuilder::new()
let n = s.length()
let mut i = 0
while i < n {
let c = char_at2(s, i)
if c == "\\" && i + 1 < n {
let e = char_at2(s, i + 1)
match e {
"n" => sb.write_char('\n')
"t" => sb.write_char('\t')
"r" => sb.write_char('\r')
other => sb.write_string(other)
}
i = i + 2
} else {
sb.write_string(c)
i = i + 1
}
}
sb.to_string()
}
///|
fn skip_ws2(s : String, pos : Int) -> Int {
let n = s.length()
let mut p = pos
while p < n {
let c = char_at2(s, p)
if c == " " || c == "\n" || c == "\r" || c == "\t" {
p = p + 1
} else {
break
}
}
p
}
///|
fn char_at2(s : String, i : Int) -> String {
if i < s.length() {
s.substring(start=i, end=i + 1)
} else {
""
}
}
///|
/// 取位置 i 处字符的码点(越界返回 0)
fn char_code2(s : String, i : Int) -> Int {
let mut idx = 0
for c in s.iter() {
if idx == i {
return c.to_int()
}
idx = idx + 1
}
0
}
///|
/// 解析 style 子对象:返回 (fill, stroke, stroke_width, corner_radius, opacity,
/// font_size, text_color, font_weight, shadow, rotate,
/// blur, blend, line_height, tracking, flip,
/// text_align, font_style, stroke_dash, visible)
struct StyleParse {
fill : String
stroke : String
stroke_width : Double
radius : Double
opacity : Double
font_size : Double
text_color : String
font_weight : String
shadow : String
rotate : Double
blur : Double
blend : String
line_height : Double
tracking : Double
flip : String
text_align : String
font_style : String
stroke_dash : String
visible : Bool
}
///|
/// 位置 pos 处是否是 true/false 字面量(visible 等布尔字段解析用)
fn match_bool_at(s : String, pos : Int) -> Bool? {
let n = s.length()
if pos + 4 <= n && s.substring(start=pos, end=pos + 4) == "true" {
Some(true)
} else if pos + 5 <= n && s.substring(start=pos, end=pos + 5) == "false" {
Some(false)
} else {
None
}
}
///|
fn parse_style_obj(obj : String) -> StyleParse {
let mut fill = ""
let mut stroke = ""
let mut stroke_width = 0.0
let mut radius = 0.0
let mut opacity = 1.0
let mut font_size = 14.0
let mut text_color = ""
let mut font_weight = ""
let mut shadow = ""
let mut rotate = 0.0
let mut blur = 0.0
let mut blend = ""
let mut line_height = 1.5
let mut tracking = 0.0
let mut flip_s = ""
let mut text_align = ""
let mut font_style = ""
let mut stroke_dash = ""
let mut visible = true
let n = obj.length()
let mut pos = 1 // 跳过 {
while pos < n {
pos = skip_ws2(obj, pos)
if char_at2(obj, pos) == "}" {
break
}
// 键与字符串值均已由 parse_string 解码,无需二次 unescape
let (key, p1) = parse_string(obj, pos)
pos = p1
pos = skip_ws2(obj, pos)
if char_at2(obj, pos) != ":" {
break
}
pos = pos + 1
pos = skip_ws2(obj, pos)
let key_t = key
if char_at2(obj, pos) == "\"" {
let (v, p2) = parse_string(obj, pos)
pos = p2
let vt = v
match key_t {
"fill" => fill = vt
"stroke" => stroke = vt
"text_color" => text_color = vt
"font_weight" => font_weight = vt
"shadow" => shadow = vt
"blend" => blend = vt
"flip" => flip_s = vt
"text_align" => text_align = vt
"font_style" => font_style = vt
"stroke_dash" => stroke_dash = vt
_ => ()
}
} else if key_t == "visible" {
// 布尔字面量 true/false
match match_bool_at(obj, pos) {
Some(b) => {
visible = b
if b {
pos = pos + 4
} else {
pos = pos + 5
}
}
None => ()
}
} else {
let end = value_end(obj, pos)
let num = parse_num_str(obj.substring(start=pos, end~))
match key_t {
"stroke_width" => stroke_width = num
"corner_radius" => radius = num
"opacity" => opacity = num
"font_size" => font_size = num
"rotate" => rotate = num
"blur" => blur = num
"line_height" => line_height = num
"tracking" => tracking = num
_ => ()
}
pos = end
}
pos = skip_ws2(obj, pos)
if char_at2(obj, pos) == "," {
pos = pos + 1
}
}
{
fill,
stroke,
stroke_width,
radius,
opacity,
font_size,
text_color,
font_weight,
shadow,
rotate,
blur,
blend,
line_height,
tracking,
flip: flip_s,
text_align,
font_style,
stroke_dash,
visible,
}
}
///|
/// 数值 token 结束位置(数字/小数点/负号之外)
fn value_end(s : String, start : Int) -> Int {
let n = s.length()
let mut i = start
while i < n {
let code = char_code2(s, i)
if (code >= 48 && code <= 57) || code == 46 || code == 45 {
i = i + 1
} else {
break
}
}
i
}
///|
fn parse_num_str(s : String) -> Double {
let mut num = 0.0
let mut frac = 0.1
let mut dot = false
let mut neg = false
for c in s.iter() {
let code = c.to_int()
if code >= 48 && code <= 57 {
let d = (code - 48).to_double()
if dot {
num = num + d * frac
frac = frac * 0.1
} else {
num = num * 10.0 + d
}
} else if code == 46 && !dot {
dot = true
} else if code == 45 && num == 0.0 && !dot {
neg = true
} else {
break
}
}
if neg {
-num
} else {
num
}
}