1use crate::psd::{BezierKnot, BezierPath, BooleanOperation, FillRule, LayerVectorMask};
20use crate::reader::{
21 check_signature, read_fixed_point_path32, read_int16, read_pascal_string, read_uint16,
22 read_uint32, read_unicode_string, skip_bytes, PsdReader, ReadError, ReadResult,
23};
24use crate::writer::{
25 create_writer, get_writer_buffer, write_fixed_point_path32, write_int16, write_pascal_string,
26 write_uint16, write_uint32, write_unicode_string, write_zeros, PsdWriter,
27};
28
29#[derive(Debug, Clone, Default)]
31pub struct CshShape {
32 pub name: String,
33 pub id: String,
34 pub width: u32,
35 pub height: u32,
36 pub mask: LayerVectorMask,
37}
38
39#[derive(Debug, Clone, Default)]
41pub struct Csh {
42 pub shapes: Vec<CshShape>,
43}
44
45fn boolean_operation(index: i16) -> Option<BooleanOperation> {
47 match index {
48 0 => Some(BooleanOperation::Exclude),
49 1 => Some(BooleanOperation::Combine),
50 2 => Some(BooleanOperation::Subtract),
51 3 => Some(BooleanOperation::Intersect),
52 _ => None,
53 }
54}
55
56fn read_bezier_knot(reader: &mut PsdReader, width: f64, height: f64) -> ReadResult<Vec<f64>> {
58 let y0 = read_fixed_point_path32(reader)? * height;
59 let x0 = read_fixed_point_path32(reader)? * width;
60 let y1 = read_fixed_point_path32(reader)? * height;
61 let x1 = read_fixed_point_path32(reader)? * width;
62 let y2 = read_fixed_point_path32(reader)? * height;
63 let x2 = read_fixed_point_path32(reader)? * width;
64 Ok(vec![x0, y0, x1, y1, x2, y2])
65}
66
67fn read_vector_mask(
71 reader: &mut PsdReader,
72 vector_mask: &mut LayerVectorMask,
73 width: f64,
74 height: f64,
75 size: usize,
76) -> ReadResult<()> {
77 let end = reader.offset + size;
78 let mut current: Option<usize> = None; while end.saturating_sub(reader.offset) >= 26 {
81 let selector = read_uint16(reader)?;
82
83 match selector {
84 0 | 3 => {
85 let _count = read_uint16(reader)?;
87 let bool_op = read_int16(reader)?;
88 let flags = read_uint16(reader)?; skip_bytes(reader, 18);
90 let mut path = BezierPath {
91 open: selector == 3,
92 operation: None,
93 knots: Vec::new(),
94 fill_rule: if flags == 2 {
95 FillRule::NonZero
96 } else {
97 FillRule::EvenOdd
98 },
99 };
100 if bool_op != -1 {
101 path.operation = boolean_operation(bool_op);
102 }
103 vector_mask.paths.push(path);
104 current = Some(vector_mask.paths.len() - 1);
105 }
106 1 | 2 | 4 | 5 => {
107 let points = read_bezier_knot(reader, width, height)?;
109 let idx = current
110 .ok_or_else(|| ReadError::StrictViolation("Invalid vmsk section".to_string()))?;
111 vector_mask.paths[idx].knots.push(BezierKnot {
112 linked: selector == 1 || selector == 4,
113 points,
114 });
115 }
116 6 => {
117 skip_bytes(reader, 24);
119 }
120 7 => {
121 let top = read_fixed_point_path32(reader)?;
123 let left = read_fixed_point_path32(reader)?;
124 let bottom = read_fixed_point_path32(reader)?;
125 let right = read_fixed_point_path32(reader)?;
126 let resolution = read_fixed_point_path32(reader)?;
127 skip_bytes(reader, 4);
128 vector_mask.clipboard = Some(crate::psd::VectorMaskClipboard {
129 top,
130 left,
131 bottom,
132 right,
133 resolution,
134 });
135 }
136 8 => {
137 vector_mask.fill_starts_with_all_pixels = Some(read_uint16(reader)? != 0);
139 skip_bytes(reader, 22);
140 }
141 _ => return Err(ReadError::StrictViolation("Invalid vmsk section".to_string())),
142 }
143 }
144
145 Ok(())
146}
147
148pub fn read_csh(buffer: &[u8]) -> ReadResult<Csh> {
150 let reader = &mut PsdReader::new(buffer, None, None);
151 let mut csh = Csh { shapes: Vec::new() };
152
153 check_signature(reader, "cush", None)?;
154 if read_uint32(reader)? != 2 {
155 return Err(ReadError::StrictViolation("Invalid version".to_string()));
156 }
157 let count = read_uint32(reader)?;
158
159 for _ in 0..count {
160 let name = read_unicode_string(reader)?;
161 while reader.offset % 4 != 0 {
162 reader.offset += 1; }
164 if read_uint32(reader)? != 1 {
165 return Err(ReadError::StrictViolation("Invalid shape version".to_string()));
166 }
167 let size = read_uint32(reader)? as usize;
168 let end = reader.offset + size;
169 let id = read_pascal_string(reader, 1)?;
170 let y1 = read_uint32(reader)?;
172 let x1 = read_uint32(reader)?;
173 let y2 = read_uint32(reader)?;
174 let x2 = read_uint32(reader)?;
175 let width = x2 - x1;
176 let height = y2 - y1;
177 let mut mask = LayerVectorMask::default();
178 read_vector_mask(
179 reader,
180 &mut mask,
181 width as f64,
182 height as f64,
183 end - reader.offset,
184 )?;
185 csh.shapes.push(CshShape {
186 name,
187 id,
188 width,
189 height,
190 mask,
191 });
192
193 reader.offset = end;
194 }
195
196 Ok(csh)
197}
198
199fn boolean_operation_index(op: BooleanOperation) -> i16 {
204 match op {
205 BooleanOperation::Exclude => 0,
206 BooleanOperation::Combine => 1,
207 BooleanOperation::Subtract => 2,
208 BooleanOperation::Intersect => 3,
209 }
210}
211
212fn write_bezier_knot(writer: &mut PsdWriter, points: &[f64], width: f64, height: f64) {
213 let safe = |v: f64, d: f64| if d != 0.0 { v / d } else { 0.0 };
215 write_fixed_point_path32(writer, safe(points[1], height)); write_fixed_point_path32(writer, safe(points[0], width)); write_fixed_point_path32(writer, safe(points[3], height)); write_fixed_point_path32(writer, safe(points[2], width)); write_fixed_point_path32(writer, safe(points[5], height)); write_fixed_point_path32(writer, safe(points[4], width)); }
222
223fn write_vector_mask(writer: &mut PsdWriter, mask: &LayerVectorMask, width: f64, height: f64) {
224 if let Some(fill) = mask.fill_starts_with_all_pixels {
226 write_uint16(writer, 8);
227 write_uint16(writer, if fill { 1 } else { 0 });
228 write_zeros(writer, 22);
229 }
230
231 if let Some(clip) = &mask.clipboard {
232 write_uint16(writer, 7);
233 write_fixed_point_path32(writer, clip.top);
234 write_fixed_point_path32(writer, clip.left);
235 write_fixed_point_path32(writer, clip.bottom);
236 write_fixed_point_path32(writer, clip.right);
237 write_fixed_point_path32(writer, clip.resolution);
238 write_zeros(writer, 4);
239 }
240
241 for path in &mask.paths {
242 write_uint16(writer, if path.open { 3 } else { 0 });
244 write_uint16(writer, path.knots.len() as u16); write_int16(
246 writer,
247 path.operation.map(boolean_operation_index).unwrap_or(-1),
248 );
249 write_uint16(
250 writer,
251 match path.fill_rule {
252 FillRule::NonZero => 2,
253 FillRule::EvenOdd => 0,
254 },
255 );
256 write_zeros(writer, 18);
257
258 for knot in &path.knots {
259 let selector = match (path.open, knot.linked) {
261 (false, true) => 1,
262 (false, false) => 2,
263 (true, true) => 4,
264 (true, false) => 5,
265 };
266 write_uint16(writer, selector);
267 write_bezier_knot(writer, &knot.points, width, height);
268 }
269 }
270}
271
272pub fn write_csh(csh: &Csh) -> Vec<u8> {
274 let mut writer = create_writer(4096);
275
276 for b in b"cush" {
277 crate::writer::write_uint8(&mut writer, *b);
278 }
279 write_uint32(&mut writer, 2); write_uint32(&mut writer, csh.shapes.len() as u32);
281
282 for shape in &csh.shapes {
283 write_unicode_string(&mut writer, &shape.name);
284 while writer.offset % 4 != 0 {
285 crate::writer::write_uint8(&mut writer, 0);
286 }
287 write_uint32(&mut writer, 1); let size_offset = writer.offset;
291 write_uint32(&mut writer, 0);
292 let body_start = writer.offset;
293
294 write_pascal_string(&mut writer, &shape.id, 1);
295 write_uint32(&mut writer, 0); write_uint32(&mut writer, 0); write_uint32(&mut writer, shape.height); write_uint32(&mut writer, shape.width); write_vector_mask(
302 &mut writer,
303 &shape.mask,
304 shape.width as f64,
305 shape.height as f64,
306 );
307
308 let size = (writer.offset - body_start) as u32;
309 writer.buffer[size_offset..size_offset + 4].copy_from_slice(&size.to_be_bytes());
310 }
311
312 get_writer_buffer(&writer)
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 fn approx_eq(a: f64, b: f64) -> bool {
320 (a - b).abs() < 1e-3
321 }
322
323 fn sample() -> Csh {
324 let mut mask = LayerVectorMask::default();
325 mask.paths.push(BezierPath {
326 open: false,
327 operation: Some(BooleanOperation::Combine),
328 knots: vec![
329 BezierKnot {
330 linked: true,
331 points: vec![10.0, 20.0, 11.0, 21.0, 12.0, 22.0],
332 },
333 BezierKnot {
334 linked: false,
335 points: vec![30.0, 40.0, 31.0, 41.0, 32.0, 42.0],
336 },
337 ],
338 fill_rule: FillRule::NonZero,
339 });
340
341 Csh {
342 shapes: vec![CshShape {
343 name: "Square".to_string(),
344 id: "abc123".to_string(),
345 width: 100,
346 height: 80,
347 mask,
348 }],
349 }
350 }
351
352 #[test]
353 fn csh_round_trip() {
354 let csh = sample();
355 let bytes = write_csh(&csh);
356 let decoded = read_csh(&bytes).expect("read_csh");
357
358 assert_eq!(decoded.shapes.len(), 1);
359 let s = &decoded.shapes[0];
360 assert_eq!(s.name, "Square");
361 assert_eq!(s.id, "abc123");
362 assert_eq!(s.width, 100);
363 assert_eq!(s.height, 80);
364 assert_eq!(s.mask.paths.len(), 1);
365
366 let p = &s.mask.paths[0];
367 assert!(!p.open);
368 assert_eq!(p.operation, Some(BooleanOperation::Combine));
369 assert_eq!(p.fill_rule, FillRule::NonZero);
370 assert_eq!(p.knots.len(), 2);
371 assert!(p.knots[0].linked);
372 assert!(!p.knots[1].linked);
373
374 let orig = &csh.shapes[0].mask.paths[0].knots[0].points;
375 let got = &p.knots[0].points;
376 for i in 0..6 {
377 assert!(approx_eq(orig[i], got[i]), "knot point {} mismatch", i);
378 }
379 }
380
381 #[test]
382 fn csh_decodes_fixture() {
383 let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
384 path.pop();
385 path.pop();
386 path.push("test/ag-psd/test/csh-read/animals/src.csh");
387 if !path.exists() {
388 eprintln!("csh fixture missing, skipping");
389 return;
390 }
391 let data = std::fs::read(&path).unwrap();
392 let csh = read_csh(&data).expect("decode csh fixture");
393 assert!(!csh.shapes.is_empty());
394 let first = &csh.shapes[0];
395 assert_eq!(first.name, "Bone");
396 assert_eq!(first.id, "26a9b56b-d040-11d5-a39c-fd27718ef272");
397 assert_eq!(first.width, 194);
398 assert_eq!(first.height, 90);
399 assert!(!first.mask.paths.is_empty());
400 let p = &first.mask.paths[0];
401 assert!(!p.open);
402 assert_eq!(p.operation, Some(BooleanOperation::Combine));
403 assert!(!p.knots.is_empty());
404 assert!((p.knots[0].points[0] - 57.249).abs() < 0.1);
406 }
407
408 #[test]
409 fn csh_rejects_bad_signature() {
410 let bytes = b"XXXX\x00\x00\x00\x02\x00\x00\x00\x00";
411 assert!(read_csh(bytes).is_err());
412 }
413}