mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-27 11:27:21 -06:00
CI's `cargo fmt --all -- --check` has failed on every run because the repo had no rustfmt config and was never formatted. Use the same settings as EffectCraft/FilmCraft (max_width 160, use_small_heuristics Max), which also keeps the churn small. Formatting only; no code changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
4.5 KiB
Rust
95 lines
4.5 KiB
Rust
//! Character and paragraph styles through PSD: named sheets in the type layers' engine data,
|
|
//! run references, and untouched files staying byte-exact.
|
|
|
|
use photocraft_color::{ColorMode, SampleType};
|
|
use photocraft_doc::text::{CharStyle, ParagraphRun, ParagraphStyle, TextAlign, TextRun};
|
|
use photocraft_doc::text_styles::{CharacterStyleDef, ParagraphStyleDef, diff_attrs};
|
|
use photocraft_doc::{Document, Layer, LayerContent, Size, TextLayer, TextStyles};
|
|
|
|
fn styles() -> TextStyles {
|
|
let mut s = TextStyles::default();
|
|
s.character.push(CharacterStyleDef {
|
|
id: 3,
|
|
name: "Loud".into(),
|
|
attrs: diff_attrs(&CharStyle { size_pt: 30.0, underline: true, ..Default::default() }, &CharStyle::default()),
|
|
});
|
|
s.paragraph.push(ParagraphStyleDef {
|
|
id: 1,
|
|
name: "Middle".into(),
|
|
para_attrs: diff_attrs(&ParagraphStyle { align: TextAlign::Center, ..Default::default() }, &ParagraphStyle::default()),
|
|
..Default::default()
|
|
});
|
|
s
|
|
}
|
|
|
|
fn doc_with(styles: TextStyles, link: bool) -> Document {
|
|
let mut doc = Document::new("t", Size::new(200, 80), ColorMode::Rgb, SampleType::U8);
|
|
let base = CharStyle { font_family: "Inter".into(), ..Default::default() };
|
|
let loud = if link { styles.resolve_char(None, Some(3), "Inter") } else { base.clone() };
|
|
let para = if link { styles.resolve_para(Some(1)) } else { ParagraphStyle::default() };
|
|
let mut t = TextLayer {
|
|
text: "calm LOUD".into(),
|
|
runs: vec![TextRun { len: 5, style: base }, TextRun { len: 4, style: loud }],
|
|
paragraphs: vec![ParagraphRun { len: 9, style: para }],
|
|
transform: photocraft_geom::Affine::translate(4.0, 40.0),
|
|
..Default::default()
|
|
};
|
|
t.sync_summary();
|
|
photocraft_text::TextEngine::new().render_layer(&mut t, doc.resolution_dpi, doc.pixel_format());
|
|
doc.layers.push(Layer::new("t", LayerContent::Text(t)));
|
|
doc.text_styles = styles;
|
|
doc
|
|
}
|
|
|
|
fn text(doc: &Document) -> &TextLayer {
|
|
match &doc.layers[0].content {
|
|
LayerContent::Text(t) => t,
|
|
_ => panic!("not text"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn named_styles_round_trip_through_psd() {
|
|
let doc = doc_with(styles(), true);
|
|
let out = photocraft_io::export(&doc, "t.psd", &Default::default()).unwrap();
|
|
let back = photocraft_io::import("t.psd", &out.bytes).unwrap().document;
|
|
let st = &back.text_styles;
|
|
assert_eq!(st.character.len(), 1);
|
|
assert_eq!(st.character[0].name, "Loud");
|
|
assert_eq!(st.character[0].attrs.get("size_pt").and_then(|v| v.as_f64()), Some(30.0));
|
|
assert_eq!(st.character[0].attrs.get("underline").and_then(|v| v.as_bool()), Some(true));
|
|
assert_eq!(st.paragraph[0].name, "Middle");
|
|
assert_eq!(st.paragraph[0].para_attrs.get("align").and_then(|v| v.as_str()), Some("Center"));
|
|
let t = text(&back);
|
|
let runs = t.char_runs();
|
|
assert_eq!(runs[0].style.style_sheet, None);
|
|
assert_eq!(runs[1].style.style_sheet, Some(st.character[0].id));
|
|
assert_eq!(t.paragraph_runs()[0].style.style_sheet, Some(st.paragraph[0].id));
|
|
// Export again: stable.
|
|
let out2 = photocraft_io::export(&back, "t.psd", &Default::default()).unwrap();
|
|
let back2 = photocraft_io::import("t.psd", &out2.bytes).unwrap().document;
|
|
assert_eq!(back2.text_styles.character.len(), 1);
|
|
assert_eq!(back2.text_styles.paragraph.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn documents_without_styles_keep_their_type_data() {
|
|
let doc = doc_with(TextStyles::default(), false);
|
|
let out = photocraft_io::export(&doc, "t.psd", &Default::default()).unwrap();
|
|
let back = photocraft_io::import("t.psd", &out.bytes).unwrap().document;
|
|
assert!(back.text_styles.is_empty());
|
|
// Re-export of an imported file without styles keeps TySh byte-exact.
|
|
let raw = text(&back).psd_raw.clone().unwrap();
|
|
let out2 = photocraft_io::export(&back, "t.psd", &Default::default()).unwrap();
|
|
let back2 = photocraft_io::import("t.psd", &out2.bytes).unwrap().document;
|
|
assert_eq!(text(&back2).psd_raw.as_deref(), Some(&*raw));
|
|
// Deleting every style removes the named sheets on the next save.
|
|
let styled = doc_with(styles(), true);
|
|
let out = photocraft_io::export(&styled, "t.psd", &Default::default()).unwrap();
|
|
let mut back = photocraft_io::import("t.psd", &out.bytes).unwrap().document;
|
|
back.text_styles = TextStyles::default();
|
|
let out = photocraft_io::export(&back, "t.psd", &Default::default()).unwrap();
|
|
let back = photocraft_io::import("t.psd", &out.bytes).unwrap().document;
|
|
assert!(back.text_styles.character.is_empty() && back.text_styles.paragraph.is_empty());
|
|
}
|