1use crate::engine_data::EngineValue;
36use crate::psd::{
37 AntiAlias, Color, Cmyk, Font, Grayscale, Justification, LayerTextData, Orientation,
38 ParagraphStyle, ParagraphStyleRun, Rgb, Rgba, TextGridInfo, TextShapeType, TextStyle,
39 TextStyleRun,
40};
41
42fn dict<'a>(v: &'a EngineValue) -> Option<&'a [(String, EngineValue)]> {
47 match v {
48 EngineValue::Dict(m) => Some(m),
49 _ => None,
50 }
51}
52
53fn get<'a>(v: &'a EngineValue, key: &str) -> Option<&'a EngineValue> {
55 dict(v).and_then(|m| m.iter().find(|(k, _)| k == key).map(|(_, val)| val))
56}
57
58fn as_number(v: &EngineValue) -> Option<f64> {
59 match v {
60 EngineValue::Number(n) => Some(*n),
61 _ => None,
62 }
63}
64
65fn as_bool(v: &EngineValue) -> Option<bool> {
66 match v {
67 EngineValue::Bool(b) => Some(*b),
68 _ => None,
69 }
70}
71
72fn as_str(v: &EngineValue) -> Option<&str> {
73 match v {
74 EngineValue::Str(s) => Some(s.as_str()),
75 _ => None,
76 }
77}
78
79fn as_array(v: &EngineValue) -> Option<&[EngineValue]> {
80 match v {
81 EngineValue::Array(a) => Some(a),
82 _ => None,
83 }
84}
85
86fn truthy_bool(v: Option<&EngineValue>) -> bool {
88 match v {
89 Some(EngineValue::Bool(b)) => *b,
90 Some(EngineValue::Number(n)) => *n != 0.0,
91 Some(EngineValue::Str(s)) => !s.is_empty(),
92 _ => false,
93 }
94}
95
96fn num_array(v: &EngineValue) -> Vec<f64> {
97 as_array(v)
98 .map(|a| a.iter().filter_map(as_number).collect())
99 .unwrap_or_default()
100}
101
102fn n(value: f64) -> EngineValue {
103 EngineValue::Number(value)
104}
105
106fn b(value: bool) -> EngineValue {
107 EngineValue::Bool(value)
108}
109
110fn num_arr(values: &[f64]) -> EngineValue {
111 EngineValue::Array(values.iter().map(|x| EngineValue::Number(*x)).collect())
112}
113
114fn d(pairs: Vec<(&str, EngineValue)>) -> EngineValue {
115 EngineValue::Dict(pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
116}
117
118const ANTIALIAS: [AntiAlias; 5] = [
123 AntiAlias::None, AntiAlias::Crisp, AntiAlias::Strong, AntiAlias::Smooth, AntiAlias::Sharp, ];
129
130fn antialias_index(value: AntiAlias) -> i32 {
131 ANTIALIAS.iter().position(|&a| a == value).map(|i| i as i32).unwrap_or(-1)
133}
134
135fn antialias_at(index: f64) -> AntiAlias {
136 let i = index as i64;
137 if i >= 0 && (i as usize) < ANTIALIAS.len() {
138 ANTIALIAS[i as usize]
139 } else {
140 AntiAlias::Smooth }
142}
143
144const JUSTIFICATION: [Justification; 7] = [
145 Justification::Left, Justification::Right, Justification::Center, Justification::JustifyLeft, Justification::JustifyRight, Justification::JustifyCenter, Justification::JustifyAll, ];
153
154fn justification_index(value: Justification) -> f64 {
155 JUSTIFICATION
156 .iter()
157 .position(|&j| j == value)
158 .map(|i| i as f64)
159 .unwrap_or(-1.0)
160}
161
162fn justification_at(index: f64) -> Option<Justification> {
163 let i = index as i64;
164 if i >= 0 && (i as usize) < JUSTIFICATION.len() {
165 Some(JUSTIFICATION[i as usize])
166 } else {
167 None
168 }
169}
170
171fn default_font() -> Font {
177 Font {
178 name: "MyriadPro-Regular".to_string(),
179 script: Some(0.0),
180 font_type: Some(0.0),
181 synthetic: Some(0.0),
182 }
183}
184
185fn default_paragraph_style() -> ParagraphStyle {
186 ParagraphStyle {
187 justification: Some(Justification::Left),
188 first_line_indent: Some(0.0),
189 start_indent: Some(0.0),
190 end_indent: Some(0.0),
191 space_before: Some(0.0),
192 space_after: Some(0.0),
193 auto_hyphenate: Some(true),
194 hyphenated_word_size: Some(6.0),
195 pre_hyphen: Some(2.0),
196 post_hyphen: Some(2.0),
197 consecutive_hyphens: Some(8.0),
198 zone: Some(36.0),
199 word_spacing: Some(vec![0.8, 1.0, 1.33]),
200 letter_spacing: Some(vec![0.0, 0.0, 0.0]),
201 glyph_spacing: Some(vec![1.0, 1.0, 1.0]),
202 auto_leading: Some(1.2),
203 leading_type: Some(0.0),
204 hanging: Some(false),
205 burasagari: Some(false),
206 kinsoku_order: Some(0.0),
207 every_line_composer: Some(false),
208 }
209}
210
211fn default_style() -> TextStyle {
212 TextStyle {
213 font: Some(default_font()),
214 font_size: Some(12.0),
215 faux_bold: Some(false),
216 faux_italic: Some(false),
217 auto_leading: Some(true),
218 leading: Some(0.0),
219 horizontal_scale: Some(1.0),
220 vertical_scale: Some(1.0),
221 tracking: Some(0.0),
222 auto_kerning: Some(true),
223 kerning: Some(0.0),
224 baseline_shift: Some(0.0),
225 font_caps: Some(0.0),
226 font_baseline: Some(0.0),
227 underline: Some(false),
228 strikethrough: Some(false),
229 ligatures: Some(true),
230 d_ligatures: Some(false),
231 baseline_direction: Some(2.0),
232 tsume: Some(0.0),
233 style_run_alignment: Some(2.0),
234 language: Some(0.0),
235 no_break: Some(false),
236 fill_color: Some(Color::Rgb(Rgb { r: 0.0, g: 0.0, b: 0.0 })),
237 stroke_color: Some(Color::Rgb(Rgb { r: 0.0, g: 0.0, b: 0.0 })),
238 fill_flag: Some(true),
239 stroke_flag: Some(false),
240 fill_first: Some(true),
241 y_underline: Some(1.0),
242 outline_width: Some(1.0),
243 character_direction: Some(0.0),
244 hindi_numbers: Some(false),
245 kashida: Some(1.0),
246 diacritic_pos: Some(2.0),
247 }
248}
249
250fn default_grid_info() -> TextGridInfo {
251 TextGridInfo {
252 is_on: Some(false),
253 show: Some(false),
254 size: Some(18.0),
255 leading: Some(22.0),
256 color: Some(Color::Rgb(Rgb { r: 0.0, g: 0.0, b: 255.0 })),
257 leading_fill_color: Some(Color::Rgb(Rgb { r: 0.0, g: 0.0, b: 255.0 })),
258 align_line_height_to_grid_flags: Some(false),
259 }
260}
261
262fn decode_color(color: &EngineValue) -> Result<Color, String> {
268 let ty = get(color, "Type").and_then(as_number).unwrap_or(0.0);
269 let c: Vec<f64> = get(color, "Values").map(num_array).unwrap_or_default();
270 let at = |i: usize| c.get(i).copied().unwrap_or(0.0);
271 match ty as i64 {
272 0 => Ok(Color::Grayscale(Grayscale { k: at(1) * 255.0 })),
273 1 => {
274 if at(0) == 1.0 {
275 Ok(Color::Rgb(Rgb {
276 r: at(1) * 255.0,
277 g: at(2) * 255.0,
278 b: at(3) * 255.0,
279 }))
280 } else {
281 Ok(Color::Rgba(Rgba {
282 r: at(1) * 255.0,
283 g: at(2) * 255.0,
284 b: at(3) * 255.0,
285 a: at(0) * 255.0,
286 }))
287 }
288 }
289 2 => Ok(Color::Cmyk(Cmyk {
290 c: at(1) * 255.0,
291 m: at(2) * 255.0,
292 y: at(3) * 255.0,
293 k: at(4) * 255.0,
294 })),
295 _ => Err("Unknown color type in text layer".to_string()),
296 }
297}
298
299fn encode_color(color: Option<&Color>) -> EngineValue {
301 match color {
302 None => d(vec![("Type", n(1.0)), ("Values", num_arr(&[0.0, 0.0, 0.0, 0.0]))]),
303 Some(Color::Rgb(c)) => d(vec![
304 ("Type", n(1.0)),
305 ("Values", num_arr(&[1.0, c.r / 255.0, c.g / 255.0, c.b / 255.0])),
306 ]),
307 Some(Color::Rgba(c)) => d(vec![
308 ("Type", n(1.0)),
309 ("Values", num_arr(&[c.a / 255.0, c.r / 255.0, c.g / 255.0, c.b / 255.0])),
310 ]),
311 Some(Color::Cmyk(c)) => d(vec![
312 ("Type", n(2.0)),
313 ("Values", num_arr(&[1.0, c.c / 255.0, c.m / 255.0, c.y / 255.0, c.k / 255.0])),
314 ]),
315 Some(Color::Grayscale(c)) => {
316 d(vec![("Type", n(0.0)), ("Values", num_arr(&[1.0, c.k / 255.0]))])
317 }
318 Some(_) => d(vec![("Type", n(1.0)), ("Values", num_arr(&[0.0, 0.0, 0.0, 0.0]))]),
322 }
323}
324
325fn find_or_add_font(fonts: &mut Vec<Font>, font: &Font) -> f64 {
330 for (i, f) in fonts.iter().enumerate() {
331 if f.name == font.name {
332 return i as f64;
333 }
334 }
335 fonts.push(font.clone());
336 (fonts.len() - 1) as f64
337}
338
339fn decode_paragraph_style(obj: &EngineValue) -> ParagraphStyle {
348 let mut s = ParagraphStyle::default();
349 if let Some(v) = get(obj, "Justification").and_then(as_number) {
350 s.justification = justification_at(v);
351 }
352 s.first_line_indent = get(obj, "FirstLineIndent").and_then(as_number);
353 s.start_indent = get(obj, "StartIndent").and_then(as_number);
354 s.end_indent = get(obj, "EndIndent").and_then(as_number);
355 s.space_before = get(obj, "SpaceBefore").and_then(as_number);
356 s.space_after = get(obj, "SpaceAfter").and_then(as_number);
357 s.auto_hyphenate = get(obj, "AutoHyphenate").and_then(as_bool);
358 s.hyphenated_word_size = get(obj, "HyphenatedWordSize").and_then(as_number);
359 s.pre_hyphen = get(obj, "PreHyphen").and_then(as_number);
360 s.post_hyphen = get(obj, "PostHyphen").and_then(as_number);
361 s.consecutive_hyphens = get(obj, "ConsecutiveHyphens").and_then(as_number);
362 s.zone = get(obj, "Zone").and_then(as_number);
363 s.word_spacing = get(obj, "WordSpacing").map(num_array);
364 s.letter_spacing = get(obj, "LetterSpacing").map(num_array);
365 s.glyph_spacing = get(obj, "GlyphSpacing").map(num_array);
366 s.auto_leading = get(obj, "AutoLeading").and_then(as_number);
367 s.leading_type = get(obj, "LeadingType").and_then(as_number);
368 s.hanging = get(obj, "Hanging").and_then(as_bool);
369 s.burasagari = get(obj, "Burasagari").and_then(as_bool);
370 s.kinsoku_order = get(obj, "KinsokuOrder").and_then(as_number);
371 s.every_line_composer = get(obj, "EveryLineComposer").and_then(as_bool);
372 s
373}
374
375fn decode_style(obj: &EngineValue, fonts: &[Font]) -> TextStyle {
377 let mut s = TextStyle::default();
378 if let Some(v) = get(obj, "Font").and_then(as_number) {
379 let idx = v as i64;
380 if idx >= 0 && (idx as usize) < fonts.len() {
381 s.font = Some(fonts[idx as usize].clone());
382 }
383 }
384 s.font_size = get(obj, "FontSize").and_then(as_number);
385 s.faux_bold = get(obj, "FauxBold").and_then(as_bool);
386 s.faux_italic = get(obj, "FauxItalic").and_then(as_bool);
387 s.auto_leading = get(obj, "AutoLeading").and_then(as_bool);
388 s.leading = get(obj, "Leading").and_then(as_number);
389 s.horizontal_scale = get(obj, "HorizontalScale").and_then(as_number);
390 s.vertical_scale = get(obj, "VerticalScale").and_then(as_number);
391 s.tracking = get(obj, "Tracking").and_then(as_number);
392 s.auto_kerning = get(obj, "AutoKerning").and_then(as_bool);
393 s.kerning = get(obj, "Kerning").and_then(as_number);
394 s.baseline_shift = get(obj, "BaselineShift").and_then(as_number);
395 s.font_caps = get(obj, "FontCaps").and_then(as_number);
396 s.font_baseline = get(obj, "FontBaseline").and_then(as_number);
397 s.underline = get(obj, "Underline").and_then(as_bool);
398 s.strikethrough = get(obj, "Strikethrough").and_then(as_bool);
399 s.ligatures = get(obj, "Ligatures").and_then(as_bool);
400 s.d_ligatures = get(obj, "DLigatures").and_then(as_bool);
401 s.baseline_direction = get(obj, "BaselineDirection").and_then(as_number);
402 s.tsume = get(obj, "Tsume").and_then(as_number);
403 s.style_run_alignment = get(obj, "StyleRunAlignment").and_then(as_number);
404 s.language = get(obj, "Language").and_then(as_number);
405 s.no_break = get(obj, "NoBreak").and_then(as_bool);
406 if let Some(c) = get(obj, "FillColor") {
407 s.fill_color = decode_color(c).ok();
408 }
409 if let Some(c) = get(obj, "StrokeColor") {
410 s.stroke_color = decode_color(c).ok();
411 }
412 s.fill_flag = get(obj, "FillFlag").and_then(as_bool);
413 s.stroke_flag = get(obj, "StrokeFlag").and_then(as_bool);
414 s.fill_first = get(obj, "FillFirst").and_then(as_bool);
415 s.y_underline = get(obj, "YUnderline").and_then(as_number);
416 s.outline_width = get(obj, "OutlineWidth").and_then(as_number);
417 s.character_direction = get(obj, "CharacterDirection").and_then(as_number);
418 s.hindi_numbers = get(obj, "HindiNumbers").and_then(as_bool);
419 s.kashida = get(obj, "Kashida").and_then(as_number);
420 s.diacritic_pos = get(obj, "DiacriticPos").and_then(as_number);
421 s
422}
423
424fn encode_paragraph_style(s: &ParagraphStyle) -> EngineValue {
427 let mut out: Vec<(String, EngineValue)> = Vec::new();
428 macro_rules! put {
429 ($key:expr, $opt:expr, $f:expr) => {
430 if let Some(v) = $opt {
431 out.push(($key.to_string(), $f(v)));
432 }
433 };
434 }
435 if let Some(j) = s.justification {
437 out.push(("Justification".to_string(), n(justification_index(j))));
438 }
439 put!("FirstLineIndent", s.first_line_indent, n);
440 put!("StartIndent", s.start_indent, n);
441 put!("EndIndent", s.end_indent, n);
442 put!("SpaceBefore", s.space_before, n);
443 put!("SpaceAfter", s.space_after, n);
444 put!("AutoHyphenate", s.auto_hyphenate, b);
445 put!("HyphenatedWordSize", s.hyphenated_word_size, n);
446 put!("PreHyphen", s.pre_hyphen, n);
447 put!("PostHyphen", s.post_hyphen, n);
448 put!("ConsecutiveHyphens", s.consecutive_hyphens, n);
449 put!("Zone", s.zone, n);
450 put!("WordSpacing", s.word_spacing.as_ref(), |v: &Vec<f64>| num_arr(v));
451 put!("LetterSpacing", s.letter_spacing.as_ref(), |v: &Vec<f64>| num_arr(v));
452 put!("GlyphSpacing", s.glyph_spacing.as_ref(), |v: &Vec<f64>| num_arr(v));
453 put!("AutoLeading", s.auto_leading, n);
454 put!("LeadingType", s.leading_type, n);
455 put!("Hanging", s.hanging, b);
456 put!("Burasagari", s.burasagari, b);
457 put!("KinsokuOrder", s.kinsoku_order, n);
458 put!("EveryLineComposer", s.every_line_composer, b);
459 EngineValue::Dict(out)
460}
461
462fn encode_style(s: &TextStyle, fonts: &mut Vec<Font>) -> EngineValue {
464 let mut out: Vec<(String, EngineValue)> = Vec::new();
465 macro_rules! put {
466 ($key:expr, $opt:expr, $f:expr) => {
467 if let Some(v) = $opt {
468 out.push(($key.to_string(), $f(v)));
469 }
470 };
471 }
472 if let Some(f) = s.font.as_ref() {
474 out.push(("Font".to_string(), n(find_or_add_font(fonts, f))));
475 }
476 put!("FontSize", s.font_size, n);
477 put!("FauxBold", s.faux_bold, b);
478 put!("FauxItalic", s.faux_italic, b);
479 put!("AutoLeading", s.auto_leading, b);
480 put!("Leading", s.leading, n);
481 put!("HorizontalScale", s.horizontal_scale, n);
482 put!("VerticalScale", s.vertical_scale, n);
483 put!("Tracking", s.tracking, n);
484 put!("AutoKerning", s.auto_kerning, b);
485 put!("Kerning", s.kerning, n);
486 put!("BaselineShift", s.baseline_shift, n);
487 put!("FontCaps", s.font_caps, n);
488 put!("FontBaseline", s.font_baseline, n);
489 put!("Underline", s.underline, b);
490 put!("Strikethrough", s.strikethrough, b);
491 put!("Ligatures", s.ligatures, b);
492 put!("DLigatures", s.d_ligatures, b);
493 put!("BaselineDirection", s.baseline_direction, n);
494 put!("Tsume", s.tsume, n);
495 put!("StyleRunAlignment", s.style_run_alignment, n);
496 put!("Language", s.language, n);
497 put!("NoBreak", s.no_break, b);
498 if let Some(c) = s.fill_color.as_ref() {
500 out.push(("FillColor".to_string(), encode_color(Some(c))));
501 }
502 if let Some(c) = s.stroke_color.as_ref() {
503 out.push(("StrokeColor".to_string(), encode_color(Some(c))));
504 }
505 put!("FillFlag", s.fill_flag, b);
506 put!("StrokeFlag", s.stroke_flag, b);
507 put!("FillFirst", s.fill_first, b);
508 put!("YUnderline", s.y_underline, n);
509 put!("OutlineWidth", s.outline_width, n);
510 put!("CharacterDirection", s.character_direction, n);
511 put!("HindiNumbers", s.hindi_numbers, b);
512 put!("Kashida", s.kashida, n);
513 put!("DiacriticPos", s.diacritic_pos, n);
514 EngineValue::Dict(out)
515}
516
517fn merge_paragraph(base: &ParagraphStyle, over: &ParagraphStyle) -> ParagraphStyle {
522 let mut r = base.clone();
523 macro_rules! m {
524 ($field:ident) => {
525 if over.$field.is_some() {
526 r.$field = over.$field.clone();
527 }
528 };
529 }
530 m!(justification);
531 m!(first_line_indent);
532 m!(start_indent);
533 m!(end_indent);
534 m!(space_before);
535 m!(space_after);
536 m!(auto_hyphenate);
537 m!(hyphenated_word_size);
538 m!(pre_hyphen);
539 m!(post_hyphen);
540 m!(consecutive_hyphens);
541 m!(zone);
542 m!(word_spacing);
543 m!(letter_spacing);
544 m!(glyph_spacing);
545 m!(auto_leading);
546 m!(leading_type);
547 m!(hanging);
548 m!(burasagari);
549 m!(kinsoku_order);
550 m!(every_line_composer);
551 r
552}
553
554fn merge_style(base: &TextStyle, over: &TextStyle) -> TextStyle {
555 let mut r = base.clone();
556 macro_rules! m {
557 ($field:ident) => {
558 if over.$field.is_some() {
559 r.$field = over.$field.clone();
560 }
561 };
562 }
563 m!(font);
564 m!(font_size);
565 m!(faux_bold);
566 m!(faux_italic);
567 m!(auto_leading);
568 m!(leading);
569 m!(horizontal_scale);
570 m!(vertical_scale);
571 m!(tracking);
572 m!(auto_kerning);
573 m!(kerning);
574 m!(baseline_shift);
575 m!(font_caps);
576 m!(font_baseline);
577 m!(underline);
578 m!(strikethrough);
579 m!(ligatures);
580 m!(d_ligatures);
581 m!(baseline_direction);
582 m!(tsume);
583 m!(style_run_alignment);
584 m!(language);
585 m!(no_break);
586 m!(fill_color);
587 m!(stroke_color);
588 m!(fill_flag);
589 m!(stroke_flag);
590 m!(fill_first);
591 m!(y_underline);
592 m!(outline_width);
593 m!(character_direction);
594 m!(hindi_numbers);
595 m!(kashida);
596 m!(diacritic_pos);
597 r
598}
599
600pub fn decode_engine_data(engine_data: &EngineValue) -> LayerTextData {
607 let engine_dict = get(engine_data, "EngineDict").cloned().unwrap_or(EngineValue::Null);
608 let resource_dict = get(engine_data, "ResourceDict").cloned().unwrap_or(EngineValue::Null);
609
610 let fonts: Vec<Font> = get(&resource_dict, "FontSet")
612 .and_then(as_array)
613 .map(|arr| {
614 arr.iter()
615 .map(|f| Font {
616 name: get(f, "Name").and_then(as_str).unwrap_or("").to_string(),
617 script: get(f, "Script").and_then(as_number),
618 font_type: get(f, "FontType").and_then(as_number),
619 synthetic: get(f, "Synthetic").and_then(as_number),
620 })
621 .collect()
622 })
623 .unwrap_or_default();
624
625 let raw_text = get(&engine_dict, "Editor")
627 .and_then(|e| get(e, "Text"))
628 .and_then(as_str)
629 .unwrap_or("")
630 .to_string();
631 let mut units: Vec<u16> = raw_text.encode_utf16().map(|u| if u == 13 { 10 } else { u }).collect();
633 let mut removed_characters: usize = 0;
634 while units.last() == Some(&10) {
635 units.pop();
636 removed_characters += 1;
637 }
638 let text = String::from_utf16_lossy(&units);
639
640 let mut result = LayerTextData {
641 text,
642 anti_alias: Some(antialias_at(
643 get(&engine_dict, "AntiAlias").and_then(as_number).unwrap_or(0.0),
644 )),
645 use_fractional_glyph_widths: Some(truthy_bool(get(&engine_dict, "UseFractionalGlyphWidths"))),
646 superscript_size: get(&resource_dict, "SuperscriptSize").and_then(as_number),
647 superscript_position: get(&resource_dict, "SuperscriptPosition").and_then(as_number),
648 subscript_size: get(&resource_dict, "SubscriptSize").and_then(as_number),
649 subscript_position: get(&resource_dict, "SubscriptPosition").and_then(as_number),
650 small_cap_size: get(&resource_dict, "SmallCapSize").and_then(as_number),
651 ..Default::default()
652 };
653
654 let photoshop = get(&engine_dict, "Rendered")
656 .and_then(|r| get(r, "Shapes"))
657 .and_then(|s| get(s, "Children"))
658 .and_then(as_array)
659 .and_then(|c| c.first())
660 .and_then(|c0| get(c0, "Cookie"))
661 .and_then(|ck| get(ck, "Photoshop"))
662 .cloned();
663
664 if let Some(ps) = photoshop {
665 let shape_type = get(&ps, "ShapeType").and_then(as_number).unwrap_or(0.0);
666 result.shape_type = Some(if shape_type == 1.0 {
667 TextShapeType::Box
668 } else {
669 TextShapeType::Point
670 });
671 if let Some(pb) = get(&ps, "PointBase") {
672 result.point_base = Some(num_array(pb));
673 }
674 if let Some(bb) = get(&ps, "BoxBounds") {
675 result.box_bounds = Some(num_array(bb));
676 }
677 }
678
679 let paragraph_run = get(&engine_dict, "ParagraphRun").cloned().unwrap_or(EngineValue::Null);
681 result.paragraph_style = Some(ParagraphStyle::default());
682 let mut paragraph_style_runs: Vec<ParagraphStyleRun> = Vec::new();
683
684 let p_run_array = get(¶graph_run, "RunArray").and_then(as_array).map(|a| a.to_vec()).unwrap_or_default();
685 let p_len_array: Vec<f64> = get(¶graph_run, "RunLengthArray").map(num_array).unwrap_or_default();
686 for (i, run) in p_run_array.iter().enumerate() {
687 let length = p_len_array.get(i).copied().unwrap_or(0.0);
688 let props = get(run, "ParagraphSheet")
689 .and_then(|sheet| get(sheet, "Properties"))
690 .cloned()
691 .unwrap_or(EngineValue::Dict(Vec::new()));
692 let style = decode_paragraph_style(&props);
693 paragraph_style_runs.push(ParagraphStyleRun { length, style });
694 }
695
696 let mut counter = removed_characters;
698 while !paragraph_style_runs.is_empty() && counter > 0 {
699 let last = paragraph_style_runs.len() - 1;
700 paragraph_style_runs[last].length -= 1.0;
701 if paragraph_style_runs[last].length == 0.0 {
702 paragraph_style_runs.pop();
703 }
704 counter -= 1;
705 }
706
707 deduplicate_paragraph(result.paragraph_style.as_mut().unwrap(), &mut paragraph_style_runs);
708 result.paragraph_style_runs = if paragraph_style_runs.is_empty() {
709 None
710 } else {
711 Some(paragraph_style_runs)
712 };
713
714 let style_run = get(&engine_dict, "StyleRun").cloned().unwrap_or(EngineValue::Null);
716 result.style = Some(TextStyle::default());
717 let mut style_runs: Vec<TextStyleRun> = Vec::new();
718
719 let s_run_array = get(&style_run, "RunArray").and_then(as_array).map(|a| a.to_vec()).unwrap_or_default();
720 let s_len_array: Vec<f64> = get(&style_run, "RunLengthArray").map(num_array).unwrap_or_default();
721 for (i, run) in s_run_array.iter().enumerate() {
722 let length = s_len_array.get(i).copied().unwrap_or(0.0);
723 let data = get(run, "StyleSheet")
724 .and_then(|ss| get(ss, "StyleSheetData"))
725 .cloned()
726 .unwrap_or(EngineValue::Dict(Vec::new()));
727 let mut style = decode_style(&data, &fonts);
728 if style.font.is_none() {
729 style.font = fonts.first().cloned();
730 }
731 style_runs.push(TextStyleRun { length, style });
732 }
733
734 let mut counter = removed_characters;
735 while !style_runs.is_empty() && counter > 0 {
736 let last = style_runs.len() - 1;
737 style_runs[last].length -= 1.0;
738 if style_runs[last].length == 0.0 {
739 style_runs.pop();
740 }
741 counter -= 1;
742 }
743
744 deduplicate_style(result.style.as_mut().unwrap(), &mut style_runs);
745 result.style_runs = if style_runs.is_empty() { None } else { Some(style_runs) };
746
747 result
748}
749
750macro_rules! dedup_field {
759 ($base:expr, $runs:expr, $field:ident) => {{
760 if let Some(first) = $runs.first() {
761 let value = first.style.$field.clone();
762 if value.is_some() {
763 let identical = $runs.iter().all(|r| r.style.$field == value);
764 if identical {
765 $base.$field = value.clone();
766 }
767 }
768 if $base.$field.is_some() {
769 let bv = $base.$field.clone();
770 for r in $runs.iter_mut() {
771 if r.style.$field == bv {
772 r.style.$field = None;
773 }
774 }
775 }
776 }
777 }};
778}
779
780fn paragraph_style_is_empty(s: &ParagraphStyle) -> bool {
781 s.justification.is_none()
782 && s.first_line_indent.is_none()
783 && s.start_indent.is_none()
784 && s.end_indent.is_none()
785 && s.space_before.is_none()
786 && s.space_after.is_none()
787 && s.auto_hyphenate.is_none()
788 && s.hyphenated_word_size.is_none()
789 && s.pre_hyphen.is_none()
790 && s.post_hyphen.is_none()
791 && s.consecutive_hyphens.is_none()
792 && s.zone.is_none()
793 && s.word_spacing.is_none()
794 && s.letter_spacing.is_none()
795 && s.glyph_spacing.is_none()
796 && s.auto_leading.is_none()
797 && s.leading_type.is_none()
798 && s.hanging.is_none()
799 && s.burasagari.is_none()
800 && s.kinsoku_order.is_none()
801 && s.every_line_composer.is_none()
802}
803
804fn deduplicate_paragraph(base: &mut ParagraphStyle, runs: &mut Vec<ParagraphStyleRun>) {
805 if runs.is_empty() {
806 return;
807 }
808 dedup_field!(base, runs, justification);
809 dedup_field!(base, runs, first_line_indent);
810 dedup_field!(base, runs, start_indent);
811 dedup_field!(base, runs, end_indent);
812 dedup_field!(base, runs, space_before);
813 dedup_field!(base, runs, space_after);
814 dedup_field!(base, runs, auto_hyphenate);
815 dedup_field!(base, runs, hyphenated_word_size);
816 dedup_field!(base, runs, pre_hyphen);
817 dedup_field!(base, runs, post_hyphen);
818 dedup_field!(base, runs, consecutive_hyphens);
819 dedup_field!(base, runs, zone);
820 dedup_field!(base, runs, word_spacing);
821 dedup_field!(base, runs, letter_spacing);
822 dedup_field!(base, runs, glyph_spacing);
823 dedup_field!(base, runs, auto_leading);
824 dedup_field!(base, runs, leading_type);
825 dedup_field!(base, runs, hanging);
826 dedup_field!(base, runs, burasagari);
827 dedup_field!(base, runs, kinsoku_order);
828 dedup_field!(base, runs, every_line_composer);
829
830 if runs.iter().all(|r| paragraph_style_is_empty(&r.style)) {
831 runs.clear();
832 }
833}
834
835fn style_is_empty(s: &TextStyle) -> bool {
836 s.font.is_none()
837 && s.font_size.is_none()
838 && s.faux_bold.is_none()
839 && s.faux_italic.is_none()
840 && s.auto_leading.is_none()
841 && s.leading.is_none()
842 && s.horizontal_scale.is_none()
843 && s.vertical_scale.is_none()
844 && s.tracking.is_none()
845 && s.auto_kerning.is_none()
846 && s.kerning.is_none()
847 && s.baseline_shift.is_none()
848 && s.font_caps.is_none()
849 && s.font_baseline.is_none()
850 && s.underline.is_none()
851 && s.strikethrough.is_none()
852 && s.ligatures.is_none()
853 && s.d_ligatures.is_none()
854 && s.baseline_direction.is_none()
855 && s.tsume.is_none()
856 && s.style_run_alignment.is_none()
857 && s.language.is_none()
858 && s.no_break.is_none()
859 && s.fill_color.is_none()
860 && s.stroke_color.is_none()
861 && s.fill_flag.is_none()
862 && s.stroke_flag.is_none()
863 && s.fill_first.is_none()
864 && s.y_underline.is_none()
865 && s.outline_width.is_none()
866 && s.character_direction.is_none()
867 && s.hindi_numbers.is_none()
868 && s.kashida.is_none()
869 && s.diacritic_pos.is_none()
870}
871
872fn deduplicate_style(base: &mut TextStyle, runs: &mut Vec<TextStyleRun>) {
873 if runs.is_empty() {
874 return;
875 }
876 {
878 if let Some(first) = runs.first() {
879 let value = first.style.font.clone();
880 if value.is_some() {
881 let identical = runs.iter().all(|r| font_eq(&r.style.font, &value));
882 if identical {
883 base.font = value.clone();
884 }
885 }
886 if base.font.is_some() {
887 let bv = base.font.clone();
888 for r in runs.iter_mut() {
889 if font_eq(&r.style.font, &bv) {
890 r.style.font = None;
891 }
892 }
893 }
894 }
895 }
896 dedup_field!(base, runs, font_size);
897 dedup_field!(base, runs, faux_bold);
898 dedup_field!(base, runs, faux_italic);
899 dedup_field!(base, runs, auto_leading);
900 dedup_field!(base, runs, leading);
901 dedup_field!(base, runs, horizontal_scale);
902 dedup_field!(base, runs, vertical_scale);
903 dedup_field!(base, runs, tracking);
904 dedup_field!(base, runs, auto_kerning);
905 dedup_field!(base, runs, kerning);
906 dedup_field!(base, runs, baseline_shift);
907 dedup_field!(base, runs, font_caps);
908 dedup_field!(base, runs, font_baseline);
909 dedup_field!(base, runs, underline);
910 dedup_field!(base, runs, strikethrough);
911 dedup_field!(base, runs, ligatures);
912 dedup_field!(base, runs, d_ligatures);
913 dedup_field!(base, runs, baseline_direction);
914 dedup_field!(base, runs, tsume);
915 dedup_field!(base, runs, style_run_alignment);
916 dedup_field!(base, runs, language);
917 dedup_field!(base, runs, no_break);
918 dedup_field!(base, runs, fill_color);
919 dedup_field!(base, runs, stroke_color);
920 dedup_field!(base, runs, fill_flag);
921 dedup_field!(base, runs, stroke_flag);
922 dedup_field!(base, runs, fill_first);
923 dedup_field!(base, runs, y_underline);
924 dedup_field!(base, runs, outline_width);
925 dedup_field!(base, runs, character_direction);
926 dedup_field!(base, runs, hindi_numbers);
927 dedup_field!(base, runs, kashida);
928 dedup_field!(base, runs, diacritic_pos);
929
930 if runs.iter().all(|r| style_is_empty(&r.style)) {
931 runs.clear();
932 }
933}
934
935fn font_eq(a: &Option<Font>, b: &Option<Font>) -> bool {
936 match (a, b) {
937 (Some(x), Some(y)) => {
938 x.name == y.name
939 && x.script == y.script
940 && x.font_type == y.font_type
941 && x.synthetic == y.synthetic
942 }
943 (None, None) => true,
944 _ => false,
945 }
946}
947
948pub fn encode_engine_data(data: &LayerTextData) -> EngineValue {
955 let text = normalize_text(&data.text);
957 let text_len = text.encode_utf16().count() as f64;
958 let text_units: Vec<u16> = text.encode_utf16().collect();
959
960 let mut fonts: Vec<Font> = vec![Font {
962 name: "AdobeInvisFont".to_string(),
963 script: Some(0.0),
964 font_type: Some(0.0),
965 synthetic: Some(0.0),
966 }];
967
968 let def_font = data
970 .style
971 .as_ref()
972 .and_then(|s| s.font.clone())
973 .or_else(|| {
974 data.style_runs
975 .as_ref()
976 .and_then(|runs| runs.iter().find_map(|r| r.style.font.clone()))
977 })
978 .unwrap_or_else(default_font);
979
980 let data_paragraph_style = data.paragraph_style.clone().unwrap_or_default();
981 let default_para = default_paragraph_style();
982
983 let mut paragraph_run_array: Vec<EngineValue> = Vec::new();
985 let mut paragraph_run_length_array: Vec<f64> = Vec::new();
986
987 let para_sheet = |props: EngineValue| {
988 d(vec![
989 ("DefaultStyleSheet", n(0.0)),
990 ("Properties", props),
991 ])
992 };
993 let para_run_entry = |props: EngineValue| {
994 d(vec![
995 ("ParagraphSheet", para_sheet(props)),
996 (
997 "Adjustments",
998 d(vec![("Axis", num_arr(&[1.0, 0.0, 1.0])), ("XY", num_arr(&[0.0, 0.0]))]),
999 ),
1000 ])
1001 };
1002
1003 let has_para_runs = data
1004 .paragraph_style_runs
1005 .as_ref()
1006 .map(|r| !r.is_empty())
1007 .unwrap_or(false);
1008
1009 if has_para_runs {
1010 let runs = data.paragraph_style_runs.as_ref().unwrap();
1011 let mut left_length = text_len;
1012 let last_idx = runs.len() - 1;
1013
1014 for (i, run) in runs.iter().enumerate() {
1015 let mut run_length = run.length.min(left_length);
1016 left_length -= run_length;
1017
1018 if run_length == 0.0 {
1019 continue;
1020 }
1021
1022 if left_length == 1.0 && i == last_idx {
1024 run_length += 1.0;
1025 left_length -= 1.0;
1026 }
1027
1028 paragraph_run_length_array.push(run_length);
1029 let merged = merge_paragraph(&merge_paragraph(&default_para, &data_paragraph_style), &run.style);
1030 paragraph_run_array.push(para_run_entry(encode_paragraph_style(&merged)));
1031 }
1032
1033 if left_length != 0.0 {
1034 paragraph_run_length_array.push(left_length);
1035 let merged = merge_paragraph(&default_para, &data_paragraph_style);
1036 paragraph_run_array.push(para_run_entry(encode_paragraph_style(&merged)));
1037 }
1038 } else {
1039 let mut last = 0usize;
1040 for i in 0..text_units.len() {
1041 if text_units[i] == 13 {
1042 paragraph_run_length_array.push((i - last + 1) as f64);
1044 let merged = merge_paragraph(&default_para, &data_paragraph_style);
1045 paragraph_run_array.push(para_run_entry(encode_paragraph_style(&merged)));
1046 last = i + 1;
1047 }
1048 }
1049 }
1050
1051 let mut style_sheet_base = default_style();
1053 style_sheet_base.font = Some(def_font.clone());
1054 let style_sheet_data = encode_style(&style_sheet_base, &mut fonts);
1055
1056 let data_style = data.style.clone().unwrap_or_default();
1057
1058 let style_runs: Vec<TextStyleRun> = match data.style_runs.as_ref() {
1060 Some(r) => r.clone(),
1061 None => vec![TextStyleRun {
1062 length: text_len,
1063 style: data_style.clone(),
1064 }],
1065 };
1066
1067 let mut style_run_array: Vec<EngineValue> = Vec::new();
1068 let mut style_run_length_array: Vec<f64> = Vec::new();
1069
1070 let run_base = || {
1072 let mut s = TextStyle {
1073 kerning: Some(0.0),
1074 auto_kerning: Some(true),
1075 fill_color: Some(Color::Rgb(Rgb { r: 0.0, g: 0.0, b: 0.0 })),
1076 ..Default::default()
1077 };
1078 s = merge_style(&s, &data_style);
1079 s
1080 };
1081
1082 let mut left_length = text_len;
1083 let last_idx = if style_runs.is_empty() { 0 } else { style_runs.len() - 1 };
1084
1085 for (i, run) in style_runs.iter().enumerate() {
1086 let mut run_length = run.length.min(left_length);
1087 left_length -= run_length;
1088
1089 if run_length == 0.0 {
1090 continue;
1091 }
1092
1093 if left_length == 1.0 && i == last_idx {
1094 run_length += 1.0;
1095 left_length -= 1.0;
1096 }
1097
1098 style_run_length_array.push(run_length);
1099 let merged = merge_style(&run_base(), &run.style);
1100 style_run_array.push(d(vec![(
1101 "StyleSheet",
1102 d(vec![("StyleSheetData", encode_style(&merged, &mut fonts))]),
1103 )]));
1104 }
1105
1106 if left_length != 0.0 && !style_runs.is_empty() {
1108 style_run_length_array.push(left_length);
1109 let merged = run_base();
1110 style_run_array.push(d(vec![(
1111 "StyleSheet",
1112 d(vec![("StyleSheetData", encode_style(&merged, &mut fonts))]),
1113 )]));
1114 }
1115
1116 let grid_info = merge_grid(&default_grid_info(), data.grid_info.as_ref());
1118 let writing_direction = if data.orientation == Some(Orientation::Vertical) { 2.0 } else { 0.0 };
1119 let procession = if data.orientation == Some(Orientation::Vertical) { 1.0 } else { 0.0 };
1120 let shape_type = if data.shape_type == Some(TextShapeType::Box) { 1.0 } else { 0.0 };
1121
1122 let mut photoshop_pairs: Vec<(&str, EngineValue)> = vec![("ShapeType", n(shape_type))];
1124 if shape_type == 0.0 {
1125 let pb = data.point_base.clone().unwrap_or_else(|| vec![0.0, 0.0]);
1126 photoshop_pairs.push(("PointBase", num_arr(&pb)));
1127 } else {
1128 let bb = data.box_bounds.clone().unwrap_or_else(|| vec![0.0, 0.0, 0.0, 0.0]);
1129 photoshop_pairs.push(("BoxBounds", num_arr(&bb)));
1130 }
1131 photoshop_pairs.push((
1132 "Base",
1133 d(vec![
1134 ("ShapeType", n(shape_type)),
1135 ("TransformPoint0", num_arr(&[1.0, 0.0])),
1136 ("TransformPoint1", num_arr(&[0.0, 1.0])),
1137 ("TransformPoint2", num_arr(&[0.0, 0.0])),
1138 ]),
1139 ));
1140 let photoshop = d(photoshop_pairs);
1141
1142 let build_resources = || -> EngineValue {
1144 let kinsoku_set = EngineValue::Array(vec![
1145 d(vec![
1146 ("Name", EngineValue::Str("PhotoshopKinsokuHard".to_string())),
1147 ("NoStart", EngineValue::Str("、。,.・:;?!ー―’”)〕]}〉》」』】ヽヾゝゞ々ぁぃぅぇぉっゃゅょゎァィゥェォッャュョヮヵヶ゛゜?!)]},.:;℃℉¢%‰".to_string())),
1148 ("NoEnd", EngineValue::Str("‘“(〔[{〈《「『【([{¥$£@§〒#".to_string())),
1149 ("Keep", EngineValue::Str("―‥".to_string())),
1150 ("Hanging", EngineValue::Str("、。.,".to_string())),
1151 ]),
1152 d(vec![
1153 ("Name", EngineValue::Str("PhotoshopKinsokuSoft".to_string())),
1154 ("NoStart", EngineValue::Str("、。,.・:;?!’”)〕]}〉》」』】ヽヾゝゞ々".to_string())),
1155 ("NoEnd", EngineValue::Str("‘“(〔[{〈《「『【".to_string())),
1156 ("Keep", EngineValue::Str("―‥".to_string())),
1157 ("Hanging", EngineValue::Str("、。.,".to_string())),
1158 ]),
1159 ]);
1160 let mojikumi_set = EngineValue::Array(vec![
1161 d(vec![("InternalName", EngineValue::Str("Photoshop6MojiKumiSet1".to_string()))]),
1162 d(vec![("InternalName", EngineValue::Str("Photoshop6MojiKumiSet2".to_string()))]),
1163 d(vec![("InternalName", EngineValue::Str("Photoshop6MojiKumiSet3".to_string()))]),
1164 d(vec![("InternalName", EngineValue::Str("Photoshop6MojiKumiSet4".to_string()))]),
1165 ]);
1166
1167 let paragraph_sheet_set = EngineValue::Array(vec![d(vec![
1168 ("Name", EngineValue::Str("Normal RGB".to_string())),
1169 ("DefaultStyleSheet", n(0.0)),
1170 (
1171 "Properties",
1172 encode_paragraph_style(&merge_paragraph(&default_para, &data_paragraph_style)),
1173 ),
1174 ])]);
1175
1176 let style_sheet_set = EngineValue::Array(vec![d(vec![
1177 ("Name", EngineValue::Str("Normal RGB".to_string())),
1178 ("StyleSheetData", style_sheet_data.clone()),
1179 ])]);
1180
1181 let font_set = EngineValue::Array(
1182 fonts
1183 .iter()
1184 .map(|f| {
1185 d(vec![
1186 ("Name", EngineValue::Str(f.name.clone())),
1187 ("Script", n(f.script.unwrap_or(0.0))),
1188 ("FontType", n(f.font_type.unwrap_or(0.0))),
1189 ("Synthetic", n(f.synthetic.unwrap_or(0.0))),
1190 ])
1191 })
1192 .collect(),
1193 );
1194
1195 d(vec![
1196 ("KinsokuSet", kinsoku_set),
1197 ("MojiKumiSet", mojikumi_set),
1198 ("TheNormalStyleSheet", n(0.0)),
1199 ("TheNormalParagraphSheet", n(0.0)),
1200 ("ParagraphSheetSet", paragraph_sheet_set),
1201 ("StyleSheetSet", style_sheet_set),
1202 ("FontSet", font_set),
1203 ("SuperscriptSize", n(data.superscript_size.unwrap_or(0.583))),
1204 ("SuperscriptPosition", n(data.superscript_position.unwrap_or(0.333))),
1205 ("SubscriptSize", n(data.subscript_size.unwrap_or(0.583))),
1206 ("SubscriptPosition", n(data.subscript_position.unwrap_or(0.333))),
1207 ("SmallCapSize", n(data.small_cap_size.unwrap_or(0.7))),
1208 ])
1209 };
1210
1211 let resource_dict = build_resources();
1212 let document_resources = build_resources();
1213
1214 let engine_dict = d(vec![
1216 ("Editor", d(vec![("Text", EngineValue::Str(text.clone()))])),
1217 (
1218 "ParagraphRun",
1219 d(vec![
1220 (
1221 "DefaultRunData",
1222 d(vec![
1223 (
1224 "ParagraphSheet",
1225 d(vec![
1226 ("DefaultStyleSheet", n(0.0)),
1227 ("Properties", EngineValue::Dict(Vec::new())),
1228 ]),
1229 ),
1230 (
1231 "Adjustments",
1232 d(vec![("Axis", num_arr(&[1.0, 0.0, 1.0])), ("XY", num_arr(&[0.0, 0.0]))]),
1233 ),
1234 ]),
1235 ),
1236 ("RunArray", EngineValue::Array(paragraph_run_array)),
1237 ("RunLengthArray", num_arr(¶graph_run_length_array)),
1238 ("IsJoinable", n(1.0)),
1239 ]),
1240 ),
1241 (
1242 "StyleRun",
1243 d(vec![
1244 (
1245 "DefaultRunData",
1246 d(vec![(
1247 "StyleSheet",
1248 d(vec![("StyleSheetData", EngineValue::Dict(Vec::new()))]),
1249 )]),
1250 ),
1251 ("RunArray", EngineValue::Array(style_run_array)),
1252 ("RunLengthArray", num_arr(&style_run_length_array)),
1253 ("IsJoinable", n(2.0)),
1254 ]),
1255 ),
1256 (
1257 "GridInfo",
1258 d(vec![
1259 ("GridIsOn", b(grid_info.is_on.unwrap_or(false))),
1260 ("ShowGrid", b(grid_info.show.unwrap_or(false))),
1261 ("GridSize", n(grid_info.size.unwrap_or(18.0))),
1262 ("GridLeading", n(grid_info.leading.unwrap_or(22.0))),
1263 ("GridColor", encode_color(grid_info.color.as_ref())),
1264 ("GridLeadingFillColor", encode_color(grid_info.color.as_ref())),
1265 ("AlignLineHeightToGridFlags", b(grid_info.align_line_height_to_grid_flags.unwrap_or(false))),
1266 ]),
1267 ),
1268 (
1269 "AntiAlias",
1270 n(antialias_index(data.anti_alias.unwrap_or(AntiAlias::Sharp)) as f64),
1271 ),
1272 (
1273 "UseFractionalGlyphWidths",
1274 b(data.use_fractional_glyph_widths.unwrap_or(true)),
1275 ),
1276 (
1277 "Rendered",
1278 d(vec![
1279 ("Version", n(1.0)),
1280 (
1281 "Shapes",
1282 d(vec![
1283 ("WritingDirection", n(writing_direction)),
1284 (
1285 "Children",
1286 EngineValue::Array(vec![d(vec![
1287 ("ShapeType", n(shape_type)),
1288 ("Procession", n(procession)),
1289 (
1290 "Lines",
1291 d(vec![
1292 ("WritingDirection", n(writing_direction)),
1293 ("Children", EngineValue::Array(Vec::new())),
1294 ]),
1295 ),
1296 ("Cookie", d(vec![("Photoshop", photoshop)])),
1297 ])]),
1298 ),
1299 ]),
1300 ),
1301 ]),
1302 ),
1303 ]);
1304
1305 d(vec![
1306 ("EngineDict", engine_dict),
1307 ("ResourceDict", resource_dict),
1308 ("DocumentResources", document_resources),
1309 ])
1310}
1311
1312fn normalize_text(text: &str) -> String {
1314 let mut out = String::with_capacity(text.len() + 1);
1316 let bytes: Vec<char> = text.chars().collect();
1317 let mut i = 0;
1318 while i < bytes.len() {
1319 let c = bytes[i];
1320 if c == '\r' {
1321 if i + 1 < bytes.len() && bytes[i + 1] == '\n' {
1322 out.push('\r');
1324 i += 2;
1325 } else {
1326 out.push('\r');
1328 i += 1;
1329 }
1330 } else if c == '\n' {
1331 out.push('\r');
1332 i += 1;
1333 } else {
1334 out.push(c);
1335 i += 1;
1336 }
1337 }
1338 out.push('\r');
1339 out
1340}
1341
1342fn merge_grid(base: &TextGridInfo, over: Option<&TextGridInfo>) -> TextGridInfo {
1343 let mut r = base.clone();
1344 if let Some(o) = over {
1345 if o.is_on.is_some() {
1346 r.is_on = o.is_on;
1347 }
1348 if o.show.is_some() {
1349 r.show = o.show;
1350 }
1351 if o.size.is_some() {
1352 r.size = o.size;
1353 }
1354 if o.leading.is_some() {
1355 r.leading = o.leading;
1356 }
1357 if o.color.is_some() {
1358 r.color = o.color;
1359 }
1360 if o.leading_fill_color.is_some() {
1361 r.leading_fill_color = o.leading_fill_color;
1362 }
1363 if o.align_line_height_to_grid_flags.is_some() {
1364 r.align_line_height_to_grid_flags = o.align_line_height_to_grid_flags;
1365 }
1366 }
1367 r
1368}
1369
1370#[cfg(test)]
1375mod tests {
1376 use super::*;
1377 use crate::engine_data::{parse_engine_data, serialize_engine_data};
1378
1379 fn keys_of(v: &EngineValue) -> Vec<String> {
1380 match v {
1381 EngineValue::Dict(m) => m.iter().map(|(k, _)| k.clone()).collect(),
1382 _ => vec![],
1383 }
1384 }
1385
1386 #[test]
1387 fn round_trip_two_style_runs() {
1388 let data = LayerTextData {
1389 text: "Hello\nWorld".to_string(),
1390 anti_alias: Some(AntiAlias::Smooth),
1391 orientation: Some(Orientation::Horizontal),
1392 paragraph_style: Some(ParagraphStyle {
1393 justification: Some(Justification::Center),
1394 ..Default::default()
1395 }),
1396 style_runs: Some(vec![
1397 TextStyleRun {
1398 length: 5.0,
1399 style: TextStyle {
1400 font: Some(Font {
1401 name: "Arial".to_string(),
1402 script: Some(0.0),
1403 font_type: Some(0.0),
1404 synthetic: Some(0.0),
1405 }),
1406 font_size: Some(24.0),
1407 fill_color: Some(Color::Rgb(Rgb { r: 255.0, g: 0.0, b: 0.0 })),
1408 ..Default::default()
1409 },
1410 },
1411 TextStyleRun {
1412 length: 6.0,
1413 style: TextStyle {
1414 font: Some(Font {
1415 name: "Arial".to_string(),
1416 script: Some(0.0),
1417 font_type: Some(0.0),
1418 synthetic: Some(0.0),
1419 }),
1420 font_size: Some(24.0),
1421 fill_color: Some(Color::Rgb(Rgb { r: 0.0, g: 0.0, b: 255.0 })),
1422 ..Default::default()
1423 },
1424 },
1425 ]),
1426 ..Default::default()
1427 };
1428
1429 let encoded = encode_engine_data(&data);
1430 let bytes = serialize_engine_data(&encoded, false);
1432 let parsed = parse_engine_data(&bytes).unwrap();
1433 assert_eq!(parsed, encoded, "serialize/parse round-trip must be stable");
1434
1435 let decoded = decode_engine_data(&parsed);
1436
1437 assert_eq!(decoded.text, "Hello\nWorld");
1439 assert_eq!(decoded.anti_alias, Some(AntiAlias::Smooth));
1440
1441 let runs = decoded.style_runs.expect("style runs present");
1443 assert_eq!(runs.len(), 2);
1444 assert_eq!(runs[0].length, 5.0);
1447 assert_eq!(runs[1].length, 6.0);
1448
1449 assert_eq!(
1451 runs[0].style.fill_color,
1452 Some(Color::Rgb(Rgb { r: 255.0, g: 0.0, b: 0.0 }))
1453 );
1454 assert_eq!(
1455 runs[1].style.fill_color,
1456 Some(Color::Rgb(Rgb { r: 0.0, g: 0.0, b: 255.0 }))
1457 );
1458
1459 assert_eq!(decoded.style.as_ref().unwrap().font.as_ref().unwrap().name, "Arial");
1461 assert_eq!(decoded.style.as_ref().unwrap().font_size, Some(24.0));
1462
1463 let pruns = decoded.paragraph_style_runs.as_ref();
1465 let just = decoded
1466 .paragraph_style
1467 .as_ref()
1468 .and_then(|p| p.justification)
1469 .or_else(|| pruns.and_then(|r| r.first()).and_then(|r| r.style.justification));
1470 assert_eq!(just, Some(Justification::Center));
1471 }
1472
1473 #[test]
1474 fn default_sheets_key_sets() {
1475 let data = LayerTextData {
1477 text: "x".to_string(),
1478 ..Default::default()
1479 };
1480 let encoded = encode_engine_data(&data);
1481
1482 assert_eq!(
1484 keys_of(&encoded),
1485 vec!["EngineDict", "ResourceDict", "DocumentResources"]
1486 );
1487
1488 let rd = get(&encoded, "ResourceDict").unwrap();
1490 assert_eq!(
1491 keys_of(rd),
1492 vec![
1493 "KinsokuSet",
1494 "MojiKumiSet",
1495 "TheNormalStyleSheet",
1496 "TheNormalParagraphSheet",
1497 "ParagraphSheetSet",
1498 "StyleSheetSet",
1499 "FontSet",
1500 "SuperscriptSize",
1501 "SuperscriptPosition",
1502 "SubscriptSize",
1503 "SubscriptPosition",
1504 "SmallCapSize",
1505 ]
1506 );
1507
1508 let ed = get(&encoded, "EngineDict").unwrap();
1510 assert_eq!(
1511 keys_of(ed),
1512 vec![
1513 "Editor",
1514 "ParagraphRun",
1515 "StyleRun",
1516 "GridInfo",
1517 "AntiAlias",
1518 "UseFractionalGlyphWidths",
1519 "Rendered",
1520 ]
1521 );
1522
1523 let props = get(rd, "ParagraphSheetSet")
1525 .and_then(as_array)
1526 .and_then(|a| a.first())
1527 .and_then(|s| get(s, "Properties"))
1528 .unwrap();
1529 assert_eq!(
1530 keys_of(props),
1531 vec![
1532 "Justification",
1533 "FirstLineIndent",
1534 "StartIndent",
1535 "EndIndent",
1536 "SpaceBefore",
1537 "SpaceAfter",
1538 "AutoHyphenate",
1539 "HyphenatedWordSize",
1540 "PreHyphen",
1541 "PostHyphen",
1542 "ConsecutiveHyphens",
1543 "Zone",
1544 "WordSpacing",
1545 "LetterSpacing",
1546 "GlyphSpacing",
1547 "AutoLeading",
1548 "LeadingType",
1549 "Hanging",
1550 "Burasagari",
1551 "KinsokuOrder",
1552 "EveryLineComposer",
1553 ]
1554 );
1555
1556 let ssd = get(rd, "StyleSheetSet")
1558 .and_then(as_array)
1559 .and_then(|a| a.first())
1560 .and_then(|s| get(s, "StyleSheetData"))
1561 .unwrap();
1562 let ssd_keys = keys_of(ssd);
1563 assert_eq!(ssd_keys.first().map(|s| s.as_str()), Some("Font"));
1564 assert!(ssd_keys.contains(&"FillColor".to_string()));
1565 assert!(ssd_keys.contains(&"DiacriticPos".to_string()));
1566
1567 let font_set = get(rd, "FontSet").and_then(as_array).unwrap();
1569 assert_eq!(
1570 get(&font_set[0], "Name").and_then(as_str),
1571 Some("AdobeInvisFont")
1572 );
1573 assert_eq!(
1574 get(&font_set[1], "Name").and_then(as_str),
1575 Some("MyriadPro-Regular")
1576 );
1577 }
1578
1579 #[test]
1580 fn color_round_trip() {
1581 let c = encode_color(Some(&Color::Rgb(Rgb { r: 255.0, g: 128.0, b: 0.0 })));
1583 match decode_color(&c).unwrap() {
1584 Color::Rgb(rgb) => {
1585 assert!((rgb.r - 255.0).abs() < 1e-6);
1586 assert!((rgb.g - 128.0).abs() < 1e-6);
1587 assert!((rgb.b - 0.0).abs() < 1e-6);
1588 }
1589 _ => panic!("expected rgb"),
1590 }
1591 let c = encode_color(Some(&Color::Grayscale(Grayscale { k: 200.0 })));
1593 match decode_color(&c).unwrap() {
1594 Color::Grayscale(g) => assert!((g.k - 200.0).abs() < 1e-6),
1595 _ => panic!("expected grayscale"),
1596 }
1597 let c = encode_color(None);
1599 assert_eq!(get(&c, "Type").and_then(as_number), Some(1.0));
1600 }
1601
1602 #[test]
1603 fn paragraph_runs_from_newlines() {
1604 let data = LayerTextData {
1606 text: "a\nb\nc".to_string(),
1607 ..Default::default()
1608 };
1609 let encoded = encode_engine_data(&data);
1610 let pr = get(&encoded, "EngineDict").and_then(|e| get(e, "ParagraphRun")).unwrap();
1611 let lens = get(pr, "RunLengthArray").map(num_array).unwrap();
1612 assert_eq!(lens, vec![2.0, 2.0, 2.0]);
1614 }
1615}