diff --git a/crates/render/src/encode/psd.rs b/crates/render/src/encode/psd.rs index f03e73783..777276e69 100644 --- a/crates/render/src/encode/psd.rs +++ b/crates/render/src/encode/psd.rs @@ -222,7 +222,16 @@ impl Record { extra.push(ascii.len() as u8); extra.extend(ascii); extra.resize(extra.len().next_multiple_of(4), 0); - let units: Vec = self.name.encode_utf16().take(255).collect(); + // Cut before the character that would pass 255 units, never inside a surrogate pair. + let mut units: Vec = vec![]; + for c in self.name.chars() { + let mut buf = [0; 2]; + let encoded = c.encode_utf16(&mut buf); + if units.len() + encoded.len() > 255 { + break; + } + units.extend_from_slice(encoded); + } let mut luni = (units.len() as u32).to_be_bytes().to_vec(); luni.extend(units.iter().flat_map(|u| u.to_be_bytes())); block(&mut extra, b"luni", &luni)?; diff --git a/crates/render/src/encode/tests_psd.rs b/crates/render/src/encode/tests_psd.rs index 60f5aae05..e56278f1d 100644 --- a/crates/render/src/encode/tests_psd.rs +++ b/crates/render/src/encode/tests_psd.rs @@ -321,3 +321,12 @@ fn the_profile_and_the_size_limit() { let err = Renderer::new().export_region(&doc, doc.artboards[0].rect, RasterFormat::Psd, &o).unwrap_err(); assert!(err.contains(&MAX_SIDE.to_string()), "{err}"); } + +#[test] +fn long_unicode_name_is_cut_on_a_character_boundary() { + let mut b = DocBuilder::new(100.0, 60.0); + b.layer(&("a".repeat(254) + "😀")); + b.rect(Rect::new(0.0, 0.0, 5.0, 5.0), Color::rgb8(0, 0, 0), |_| {}); + let file = export(&b.build(), &opts(PsdOptions::default())); + assert_eq!(parse(&file).records.last().unwrap().name, "a".repeat(254)); +}