Skip to main content

ag_psd/
engine_data2.rs

1/*
2File: crates/ag-psd/src/engine_data2.rs
3
4Purpose:
5вариант Engine Data v2 (альтернативное/структурное представление текстовых данных).
6
7Source compatibility:
8- порт upstream-файла `test/ag-psd/src/engineData2.ts` (разбиение 1:1).
9
10Main responsibilities:
11- зеркалировать соответствующий upstream-модуль при портировании;
12- держать публичный контракт этого участка в одном месте.
13
14Mapping TS -> Rust:
15- `decodeEngineData2(data)` -> `decode_engine_data2(data: &EngineValue) -> EngineValue`
16- внутренняя `decodeObj`     -> `decode_obj(obj, keys) -> EngineValue`
17- словари переименования ключей (`keysRoot`, `keysStyleSheet`, ...) -> статически
18  построенные `KeysDict` (lazy через `OnceLock`).
19
20Модель:
21Upstream `decodeObj` принимает нетипизированный распарсенный EngineData (числовые ключи)
22и переименовывает ключи по словарю `KeysDict`, опционально «поднимая» (uproot) вложенный
23узел на уровень выше и проставляя `_type` из ключа `99`. Здесь вход и выход —
24`crate::engine_data::EngineValue`; результат содержит переименованные `Dict`-ключи,
25а `_type` записывается как обычный ключ `"_type"`.
26
27TS-тип `GlobalEngineData` — это loose-структура (`any`/TODO в upstream'е), поэтому
28типобезопасной обёртки нет: `decode_engine_data2` возвращает `EngineValue`.
29*/
30
31use crate::engine_data::EngineValue;
32use std::collections::HashMap;
33use std::sync::OnceLock;
34
35/// Запись в словаре переименования ключей (зеркало `KeysDict[key]`).
36#[derive(Debug, Clone, Default)]
37pub struct KeyEntry {
38    pub name: Option<&'static str>,
39    pub uproot: bool,
40    pub children: Option<KeysDict>,
41}
42
43/// Словарь переименования: ключ EngineData -> правило (`KeysDict`).
44pub type KeysDict = HashMap<&'static str, KeyEntry>;
45
46fn entry_name(name: &'static str) -> KeyEntry {
47    KeyEntry { name: Some(name), uproot: false, children: None }
48}
49
50fn entry(name: Option<&'static str>, uproot: bool, children: Option<KeysDict>) -> KeyEntry {
51    KeyEntry { name, uproot, children }
52}
53
54fn dict(pairs: Vec<(&'static str, KeyEntry)>) -> KeysDict {
55    pairs.into_iter().collect()
56}
57
58fn keys_color() -> KeysDict {
59    dict(vec![(
60        "0",
61        entry(
62            None,
63            true,
64            Some(dict(vec![
65                ("0", entry_name("Type")),
66                ("1", entry_name("Values")),
67            ])),
68        ),
69    )])
70}
71
72fn keys_style_sheet() -> KeysDict {
73    dict(vec![
74        ("0", entry_name("Font")),
75        ("1", entry_name("FontSize")),
76        ("2", entry_name("FauxBold")),
77        ("3", entry_name("FauxItalic")),
78        ("4", entry_name("AutoLeading")),
79        ("5", entry_name("Leading")),
80        ("6", entry_name("HorizontalScale")),
81        ("7", entry_name("VerticalScale")),
82        ("8", entry_name("Tracking")),
83        ("9", entry_name("BaselineShift")),
84        ("11", entry_name("Kerning?")),
85        ("12", entry_name("FontCaps")),
86        ("13", entry_name("FontBaseline")),
87        ("15", entry_name("Strikethrough?")),
88        ("16", entry_name("Underline?")),
89        ("18", entry_name("Ligatures")),
90        ("19", entry_name("DLigatures")),
91        ("23", entry_name("Fractions")),
92        ("24", entry_name("Ordinals")),
93        ("28", entry_name("StylisticAlternates")),
94        ("30", entry_name("OldStyle?")),
95        ("35", entry_name("BaselineDirection")),
96        ("38", entry_name("Language")),
97        ("52", entry_name("NoBreak")),
98        ("53", entry(Some("FillColor"), false, Some(keys_color()))),
99        ("54", entry(Some("StrokeColor"), false, Some(keys_color()))),
100        (
101            "55",
102            entry(
103                None,
104                false,
105                Some(dict(vec![("99", entry(None, true, None))])),
106            ),
107        ),
108        ("79", entry(None, false, Some(keys_color()))),
109    ])
110}
111
112fn keys_paragraph() -> KeysDict {
113    dict(vec![
114        ("0", entry_name("Justification")),
115        ("1", entry_name("FirstLineIndent")),
116        ("2", entry_name("StartIndent")),
117        ("3", entry_name("EndIndent")),
118        ("4", entry_name("SpaceBefore")),
119        ("5", entry_name("SpaceAfter")),
120        ("7", entry_name("AutoLeading")),
121        ("9", entry_name("AutoHyphenate")),
122        ("10", entry_name("HyphenatedWordSize")),
123        ("11", entry_name("PreHyphen")),
124        ("12", entry_name("PostHyphen")),
125        ("13", entry_name("ConsecutiveHyphens?")),
126        ("14", entry_name("Zone")),
127        ("15", entry_name("HypenateCapitalizedWords")),
128        ("17", entry_name("WordSpacing")),
129        ("18", entry_name("LetterSpacing")),
130        ("19", entry_name("GlyphSpacing")),
131        ("32", entry(Some("StyleSheet"), false, Some(keys_style_sheet()))),
132    ])
133}
134
135fn keys_style_sheet_data() -> KeyEntry {
136    entry(Some("StyleSheetData"), false, Some(keys_style_sheet()))
137}
138
139fn keys_root() -> KeysDict {
140    dict(vec![
141        (
142            "0",
143            entry(
144                Some("ResourceDict"),
145                false,
146                Some(dict(vec![
147                    (
148                        "1",
149                        entry(
150                            Some("FontSet"),
151                            false,
152                            Some(dict(vec![(
153                                "0",
154                                entry(
155                                    None,
156                                    true,
157                                    Some(dict(vec![(
158                                        "0",
159                                        entry(
160                                            None,
161                                            true,
162                                            Some(dict(vec![(
163                                                "0",
164                                                entry(
165                                                    None,
166                                                    true,
167                                                    Some(dict(vec![
168                                                        ("0", entry_name("Name")),
169                                                        ("2", entry_name("FontType")),
170                                                    ])),
171                                                ),
172                                            )])),
173                                        ),
174                                    )])),
175                                ),
176                            )])),
177                        ),
178                    ),
179                    ("2", entry(Some("2"), false, Some(dict(vec![])))),
180                    (
181                        "3",
182                        entry(
183                            Some("MojiKumiSet"),
184                            false,
185                            Some(dict(vec![(
186                                "0",
187                                entry(
188                                    None,
189                                    true,
190                                    Some(dict(vec![(
191                                        "0",
192                                        entry(
193                                            None,
194                                            true,
195                                            Some(dict(vec![("0", entry_name("InternalName"))])),
196                                        ),
197                                    )])),
198                                ),
199                            )])),
200                        ),
201                    ),
202                    (
203                        "4",
204                        entry(
205                            Some("KinsokuSet"),
206                            false,
207                            Some(dict(vec![(
208                                "0",
209                                entry(
210                                    None,
211                                    true,
212                                    Some(dict(vec![(
213                                        "0",
214                                        entry(
215                                            None,
216                                            true,
217                                            Some(dict(vec![
218                                                ("0", entry_name("Name")),
219                                                (
220                                                    "5",
221                                                    entry(
222                                                        None,
223                                                        true,
224                                                        Some(dict(vec![
225                                                            ("0", entry_name("NoStart")),
226                                                            ("1", entry_name("NoEnd")),
227                                                            ("2", entry_name("Keep")),
228                                                            ("3", entry_name("Hanging")),
229                                                            ("4", entry_name("Name")),
230                                                        ])),
231                                                    ),
232                                                ),
233                                            ])),
234                                        ),
235                                    )])),
236                                ),
237                            )])),
238                        ),
239                    ),
240                    (
241                        "5",
242                        entry(
243                            Some("StyleSheetSet"),
244                            false,
245                            Some(dict(vec![(
246                                "0",
247                                entry(
248                                    None,
249                                    true,
250                                    Some(dict(vec![(
251                                        "0",
252                                        entry(
253                                            None,
254                                            true,
255                                            Some(dict(vec![
256                                                ("0", entry_name("Name")),
257                                                ("6", keys_style_sheet_data()),
258                                            ])),
259                                        ),
260                                    )])),
261                                ),
262                            )])),
263                        ),
264                    ),
265                    (
266                        "6",
267                        entry(
268                            Some("ParagraphSheetSet"),
269                            false,
270                            Some(dict(vec![(
271                                "0",
272                                entry(
273                                    None,
274                                    true,
275                                    Some(dict(vec![(
276                                        "0",
277                                        entry(
278                                            None,
279                                            true,
280                                            Some(dict(vec![
281                                                ("0", entry_name("Name")),
282                                                (
283                                                    "5",
284                                                    entry(
285                                                        Some("Properties"),
286                                                        false,
287                                                        Some(keys_paragraph()),
288                                                    ),
289                                                ),
290                                                ("6", entry_name("DefaultStyleSheet")),
291                                            ])),
292                                        ),
293                                    )])),
294                                ),
295                            )])),
296                        ),
297                    ),
298                    (
299                        "8",
300                        entry(
301                            Some("TextFrameSet"),
302                            false,
303                            Some(dict(vec![(
304                                "0",
305                                entry(
306                                    None,
307                                    true,
308                                    Some(dict(vec![(
309                                        "0",
310                                        entry(
311                                            Some("path"),
312                                            false,
313                                            Some(dict(vec![
314                                                ("0", entry_name("name")),
315                                                (
316                                                    "1",
317                                                    entry(
318                                                        Some("bezierCurve"),
319                                                        false,
320                                                        Some(dict(vec![(
321                                                            "0",
322                                                            entry_name("controlPoints"),
323                                                        )])),
324                                                    ),
325                                                ),
326                                                (
327                                                    "2",
328                                                    entry(
329                                                        Some("data"),
330                                                        false,
331                                                        Some(dict(vec![
332                                                            ("0", entry_name("type")),
333                                                            ("1", entry_name("orientation")),
334                                                            ("2", entry_name("frameMatrix")),
335                                                            ("4", entry_name("4")),
336                                                            ("6", entry_name("textRange")),
337                                                            ("7", entry_name("rowGutter")),
338                                                            ("8", entry_name("columnGutter")),
339                                                            ("9", entry_name("9")),
340                                                            (
341                                                                "10",
342                                                                entry(
343                                                                    Some("baselineAlignment"),
344                                                                    false,
345                                                                    Some(dict(vec![
346                                                                        ("0", entry_name("flag")),
347                                                                        ("1", entry_name("min")),
348                                                                    ])),
349                                                                ),
350                                                            ),
351                                                            (
352                                                                "11",
353                                                                entry(
354                                                                    Some("pathData"),
355                                                                    false,
356                                                                    Some(dict(vec![
357                                                                        ("1", entry_name("1")),
358                                                                        ("0", entry_name("reversed")),
359                                                                        ("2", entry_name("2")),
360                                                                        ("3", entry_name("3")),
361                                                                        ("4", entry_name("spacing")),
362                                                                        ("5", entry_name("5")),
363                                                                        ("6", entry_name("6")),
364                                                                        ("7", entry_name("7")),
365                                                                        ("18", entry_name("18")),
366                                                                    ])),
367                                                                ),
368                                                            ),
369                                                            ("12", entry_name("12")),
370                                                            ("13", entry_name("13")),
371                                                        ])),
372                                                    ),
373                                                ),
374                                                ("3", entry_name("3")),
375                                                ("97", entry_name("uuid")),
376                                            ])),
377                                        ),
378                                    )])),
379                                ),
380                            )])),
381                        ),
382                    ),
383                    (
384                        "9",
385                        entry(
386                            Some("Predefined"),
387                            false,
388                            Some(dict(vec![
389                                (
390                                    "0",
391                                    entry(
392                                        None,
393                                        false,
394                                        Some(dict(vec![("0", entry(None, true, None))])),
395                                    ),
396                                ),
397                                (
398                                    "1",
399                                    entry(
400                                        None,
401                                        false,
402                                        Some(dict(vec![("0", entry(None, true, None))])),
403                                    ),
404                                ),
405                            ])),
406                        ),
407                    ),
408                ])),
409            ),
410        ),
411        (
412            "1",
413            entry(
414                Some("EngineDict"),
415                false,
416                Some(dict(vec![
417                    (
418                        "0",
419                        entry(
420                            Some("0"),
421                            false,
422                            Some(dict(vec![
423                                ("3", entry_name("SuperscriptSize")),
424                                ("4", entry_name("SuperscriptPosition")),
425                                ("5", entry_name("SubscriptSize")),
426                                ("6", entry_name("SubscriptPosition")),
427                                ("7", entry_name("SmallCapSize")),
428                                ("8", entry_name("UseFractionalGlyphWidths")),
429                                (
430                                    "15",
431                                    entry(
432                                        None,
433                                        false,
434                                        Some(dict(vec![("0", entry(None, true, None))])),
435                                    ),
436                                ),
437                            ])),
438                        ),
439                    ),
440                    (
441                        "1",
442                        entry(
443                            Some("Editors?"),
444                            false,
445                            Some(dict(vec![
446                                (
447                                    "0",
448                                    entry(
449                                        Some("Editor"),
450                                        false,
451                                        Some(dict(vec![
452                                            ("0", entry_name("Text")),
453                                            (
454                                                "5",
455                                                entry(
456                                                    Some("ParagraphRun"),
457                                                    false,
458                                                    Some(dict(vec![(
459                                                        "0",
460                                                        entry(
461                                                            Some("RunArray"),
462                                                            false,
463                                                            Some(dict(vec![
464                                                                (
465                                                                    "0",
466                                                                    entry(
467                                                                        Some("ParagraphSheet"),
468                                                                        false,
469                                                                        Some(dict(vec![(
470                                                                            "0",
471                                                                            entry(
472                                                                                None,
473                                                                                true,
474                                                                                Some(dict(vec![
475                                                                                    ("0", entry_name("0")),
476                                                                                    (
477                                                                                        "5",
478                                                                                        entry(
479                                                                                            Some("5"),
480                                                                                            false,
481                                                                                            Some(keys_paragraph()),
482                                                                                        ),
483                                                                                    ),
484                                                                                    ("6", entry_name("6")),
485                                                                                ])),
486                                                                            ),
487                                                                        )])),
488                                                                    ),
489                                                                ),
490                                                                ("1", entry_name("RunLength")),
491                                                            ])),
492                                                        ),
493                                                    )])),
494                                                ),
495                                            ),
496                                            (
497                                                "6",
498                                                entry(
499                                                    Some("StyleRun"),
500                                                    false,
501                                                    Some(dict(vec![(
502                                                        "0",
503                                                        entry(
504                                                            Some("RunArray"),
505                                                            false,
506                                                            Some(dict(vec![
507                                                                (
508                                                                    "0",
509                                                                    entry(
510                                                                        Some("StyleSheet"),
511                                                                        false,
512                                                                        Some(dict(vec![(
513                                                                            "0",
514                                                                            entry(
515                                                                                None,
516                                                                                true,
517                                                                                Some(dict(vec![(
518                                                                                    "6",
519                                                                                    keys_style_sheet_data(),
520                                                                                )])),
521                                                                            ),
522                                                                        )])),
523                                                                    ),
524                                                                ),
525                                                                ("1", entry_name("RunLength")),
526                                                            ])),
527                                                        ),
528                                                    )])),
529                                                ),
530                                            ),
531                                        ])),
532                                    ),
533                                ),
534                                ("1", entry_name("FontVectorData ???")),
535                            ])),
536                        ),
537                    ),
538                    ("2", entry(Some("StyleSheet"), false, Some(keys_style_sheet()))),
539                    ("3", entry(Some("ParagraphSheet"), false, Some(keys_paragraph()))),
540                ])),
541            ),
542        ),
543    ])
544}
545
546fn keys_root_static() -> &'static KeysDict {
547    static ROOT: OnceLock<KeysDict> = OnceLock::new();
548    ROOT.get_or_init(keys_root)
549}
550
551static EMPTY_KEYS: OnceLock<KeysDict> = OnceLock::new();
552
553fn empty_keys() -> &'static KeysDict {
554    EMPTY_KEYS.get_or_init(HashMap::new)
555}
556
557// Зеркало `decodeObj`. Переименовывает ключи по `keys`, обрабатывает uproot и `_type`.
558fn decode_obj(obj: &EngineValue, keys: &KeysDict) -> EngineValue {
559    match obj {
560        EngineValue::Null => EngineValue::Null,
561        EngineValue::Array(arr) => {
562            EngineValue::Array(arr.iter().map(|x| decode_obj(x, keys)).collect())
563        }
564        // typeof obj !== 'object' -> вернуть как есть (числа, булевы, строки).
565        EngineValue::Number(_) | EngineValue::Bool(_) | EngineValue::Str(_) => obj.clone(),
566        EngineValue::Dict(map) => {
567            let mut result: Vec<(String, EngineValue)> = Vec::new();
568
569            for (key, value) in map {
570                if let Some(entry) = keys.get(key.as_str()) {
571                    if entry.uproot {
572                        // result = decodeObj(obj[key], children) — только если ключ != '99'.
573                        if key != "99" {
574                            let children = entry.children.as_ref().unwrap_or_else(|| empty_keys());
575                            let decoded = decode_obj(value, children);
576                            // result := decoded (перезаписываем целиком).
577                            result = match decoded {
578                                EngineValue::Dict(m) => m,
579                                // decodeObj uproot-узла всегда даёт объект, но на случай
580                                // не-объекта сохраняем под пустым ключом не требуется —
581                                // в upstream'е это присвоение `result = <не-объект>` с
582                                // последующим `result._type`/`result[...]`; для не-объекта
583                                // ветка недостижима в реальных данных. Используем пусто.
584                                other => vec![("".to_string(), other)],
585                            };
586                        }
587                        // if (obj['99']) result._type = obj['99'];
588                        if let Some(t) = map.iter().find(|(k, _)| k == "99").map(|(_, v)| v) {
589                            if is_truthy(t) {
590                                set_kv(&mut result, "_type", t.clone());
591                            }
592                        }
593                        break;
594                    } else {
595                        let name = entry.name.unwrap_or(key.as_str());
596                        let children = entry.children.as_ref().unwrap_or_else(|| empty_keys());
597                        set_kv(&mut result, name, decode_obj(value, children));
598                    }
599                } else if key == "99" {
600                    set_kv(&mut result, "_type", value.clone());
601                } else {
602                    set_kv(&mut result, key, decode_obj(value, empty_keys()));
603                }
604            }
605
606            EngineValue::Dict(result)
607        }
608    }
609}
610
611// JS-истинность значения для `if (obj['99'])`: null/false/0/"" -> false.
612fn is_truthy(v: &EngineValue) -> bool {
613    match v {
614        EngineValue::Null => false,
615        EngineValue::Bool(b) => *b,
616        EngineValue::Number(n) => *n != 0.0 && !n.is_nan(),
617        EngineValue::Str(s) => !s.is_empty(),
618        EngineValue::Array(_) | EngineValue::Dict(_) => true,
619    }
620}
621
622fn set_kv(map: &mut Vec<(String, EngineValue)>, key: &str, value: EngineValue) {
623    if let Some(slot) = map.iter_mut().find(|(k, _)| k == key) {
624        slot.1 = value;
625    } else {
626        map.push((key.to_string(), value));
627    }
628}
629
630/// Порт `decodeEngineData2`. Возвращает декодированную (переименованную) структуру.
631pub fn decode_engine_data2(data: &EngineValue) -> EngineValue {
632    decode_obj(data, keys_root_static())
633}
634
635#[cfg(test)]
636mod tests {
637    use super::*;
638    use crate::engine_data::EngineValue;
639
640    fn d(pairs: Vec<(&str, EngineValue)>) -> EngineValue {
641        EngineValue::Dict(pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
642    }
643
644    fn get<'a>(v: &'a EngineValue, key: &str) -> Option<&'a EngineValue> {
645        if let EngineValue::Dict(m) = v {
646            m.iter().find(|(k, _)| k == key).map(|(_, v)| v)
647        } else {
648            None
649        }
650    }
651
652    #[test]
653    fn renames_top_level_keys() {
654        // { "0": {...}, "1": {...} } -> { ResourceDict, EngineDict }
655        let input = d(vec![
656            ("0", d(vec![])),
657            ("1", d(vec![])),
658        ]);
659        let out = decode_engine_data2(&input);
660        assert!(get(&out, "ResourceDict").is_some());
661        assert!(get(&out, "EngineDict").is_some());
662    }
663
664    #[test]
665    fn renames_style_sheet_keys() {
666        // EngineDict.StyleSheet (key "2") uses keysStyleSheet: "1" -> FontSize.
667        let input = d(vec![(
668            "1",
669            d(vec![(
670                "2",
671                d(vec![("1", EngineValue::Number(12.0))]),
672            )]),
673        )]);
674        let out = decode_engine_data2(&input);
675        let engine = get(&out, "EngineDict").unwrap();
676        let style = get(engine, "StyleSheet").unwrap();
677        assert_eq!(get(style, "FontSize"), Some(&EngineValue::Number(12.0)));
678    }
679
680    #[test]
681    fn uproot_and_type() {
682        // keysColor: "0" uproot with children {0:Type,1:Values}; "99" -> _type.
683        // Build a FillColor (StyleSheet key "53").
684        let input = d(vec![(
685            "1", // EngineDict
686            d(vec![(
687                "2", // EngineDict.StyleSheet
688                d(vec![(
689                    "53", // FillColor, children = keysColor
690                    d(vec![
691                        ("99", EngineValue::Str("/SimplePaint".to_string())),
692                        (
693                            "0",
694                            d(vec![
695                                ("0", EngineValue::Number(1.0)),
696                                ("1", EngineValue::Array(vec![EngineValue::Number(0.0)])),
697                            ]),
698                        ),
699                    ]),
700                )]),
701            )]),
702        )]);
703        let out = decode_engine_data2(&input);
704        let style = get(get(&out, "EngineDict").unwrap(), "StyleSheet").unwrap();
705        let fill = get(style, "FillColor").unwrap();
706        // uproot of "0" pulls Type/Values up; "_type" from "99".
707        assert_eq!(get(fill, "Type"), Some(&EngineValue::Number(1.0)));
708        assert_eq!(get(fill, "_type"), Some(&EngineValue::Str("/SimplePaint".to_string())));
709        assert!(get(fill, "Values").is_some());
710    }
711
712    #[test]
713    fn unknown_keys_passthrough() {
714        let input = d(vec![("zzz", EngineValue::Number(7.0))]);
715        let out = decode_engine_data2(&input);
716        assert_eq!(get(&out, "zzz"), Some(&EngineValue::Number(7.0)));
717    }
718}