swf_emitter/
shape.rs

1use std::cmp::max;
2use std::convert::{TryFrom, TryInto};
3use std::io;
4
5use swf_types as ast;
6
7use crate::basic_data_types::{emit_matrix, emit_s_rgb8, emit_straight_s_rgba8};
8use crate::bit_count::{get_i32_min_bit_count, get_u32_bit_count};
9use crate::gradient::emit_gradient;
10use crate::io_bits::{BitsWriter, WriteBits};
11use crate::primitives::{emit_le_i16, emit_le_u16, emit_u8};
12
13#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub enum ShapeVersion {
15  Shape1,
16  Shape2,
17  Shape3,
18  Shape4,
19}
20
21pub(crate) fn emit_glyph<W: io::Write>(writer: &mut W, value: &ast::Glyph) -> io::Result<()> {
22  let mut bits_writer = BitsWriter::new(Vec::new());
23  emit_glyph_bits(&mut bits_writer, value)?;
24  writer.write_all(&bits_writer.into_inner()?)
25}
26
27pub(crate) fn emit_glyph_bits<W: WriteBits>(writer: &mut W, value: &ast::Glyph) -> io::Result<()> {
28  // TODO: Check how to determine the bit count (scan records?)
29  let fill_bits: u32 = 1; // 2 styles (empty and filled) -> 1 bit
30  let line_bits: u32 = 0; // no line styles
31  writer.write_u32_bits(4, fill_bits)?;
32  writer.write_u32_bits(4, line_bits)?;
33  // TODO: Check which shape version to use
34  emit_shape_record_string_bits(writer, &value.records, fill_bits, line_bits, ShapeVersion::Shape1)
35}
36
37pub(crate) fn emit_shape<W: io::Write>(writer: &mut W, value: &ast::Shape, version: ShapeVersion) -> io::Result<()> {
38  let mut bits_writer = BitsWriter::new(Vec::new());
39  emit_shape_bits(&mut bits_writer, value, version)?;
40  writer.write_all(&bits_writer.into_inner()?)
41}
42
43pub(crate) fn emit_shape_bits<W: WriteBits>(
44  writer: &mut W,
45  value: &ast::Shape,
46  version: ShapeVersion,
47) -> io::Result<()> {
48  let (fill_bits, line_bits) = emit_shape_styles_bits(writer, &value.initial_styles, version)?;
49  emit_shape_record_string_bits(writer, &value.records, fill_bits, line_bits, version)
50}
51
52pub(crate) fn emit_shape_styles_bits<W: WriteBits>(
53  writer: &mut W,
54  value: &ast::ShapeStyles,
55  version: ShapeVersion,
56) -> io::Result<(u32, u32)> {
57  let bytes_writer = writer.write_bytes()?;
58  emit_fill_style_list(bytes_writer, &value.fill, version)?;
59  emit_line_style_list(bytes_writer, &value.line, version)?;
60  // The max style `id` is `.len()` (and not `.len() - 1`) because `0` always
61  // represents the empty style and custom styles are 1-indexed.
62  let max_fill_id: u32 = u32::try_from(value.fill.len()).unwrap();
63  let max_line_id: u32 = u32::try_from(value.line.len()).unwrap();
64  let fill_bits: u32 = get_u32_bit_count(max_fill_id);
65  let line_bits: u32 = get_u32_bit_count(max_line_id);
66  writer.write_u32_bits(4, fill_bits)?;
67  writer.write_u32_bits(4, line_bits)?;
68  Ok((fill_bits, line_bits))
69}
70
71pub(crate) fn emit_shape_record_string_bits<W: WriteBits>(
72  writer: &mut W,
73  value: &[ast::ShapeRecord],
74  mut fill_bits: u32,
75  mut line_bits: u32,
76  version: ShapeVersion,
77) -> io::Result<()> {
78  for record in value {
79    match record {
80      ast::ShapeRecord::Edge(ref record) => {
81        writer.write_bool_bits(true)?; // is_edge
82        emit_edge_bits(writer, record)?;
83      }
84      ast::ShapeRecord::StyleChange(ref record) => {
85        writer.write_bool_bits(false)?; // is_edge
86        let (next_fill_bits, next_line_bits) = emit_style_change_bits(writer, record, fill_bits, line_bits, version)?;
87        fill_bits = next_fill_bits;
88        line_bits = next_line_bits;
89      }
90    }
91  }
92  writer.write_u32_bits(6, 0)
93}
94
95pub(crate) fn emit_edge_bits<W: WriteBits>(writer: &mut W, value: &ast::shape_records::Edge) -> io::Result<()> {
96  if let Some(control_delta) = value.control_delta {
97    writer.write_bool_bits(false)?; // is_straight
98    let anchor_delta = ast::Vector2D {
99      x: value.delta.x - control_delta.x,
100      y: value.delta.y - control_delta.y,
101    };
102    let bits =
103      get_i32_min_bit_count(vec![control_delta.x, control_delta.y, anchor_delta.x, anchor_delta.y].into_iter());
104    let bits = 2 + bits.saturating_sub(2);
105    writer.write_u32_bits(4, bits - 2)?;
106    writer.write_i32_bits(bits, control_delta.x)?;
107    writer.write_i32_bits(bits, control_delta.y)?;
108    writer.write_i32_bits(bits, anchor_delta.x)?;
109    writer.write_i32_bits(bits, anchor_delta.y)?;
110  } else {
111    writer.write_bool_bits(true)?; // is_straight
112    let bits = get_i32_min_bit_count(vec![value.delta.x, value.delta.y].into_iter());
113    let bits = 2 + bits.saturating_sub(2);
114    writer.write_u32_bits(4, bits - 2)?;
115    let is_diagonal = value.delta.x != 0 && value.delta.y != 0;
116    writer.write_bool_bits(is_diagonal)?;
117    if is_diagonal {
118      writer.write_i32_bits(bits, value.delta.x)?;
119      writer.write_i32_bits(bits, value.delta.y)?;
120    } else {
121      let is_vertical = value.delta.x == 0;
122      writer.write_bool_bits(is_vertical)?;
123      if is_vertical {
124        writer.write_i32_bits(bits, value.delta.y)?;
125      } else {
126        writer.write_i32_bits(bits, value.delta.x)?;
127      }
128    }
129  }
130  Ok(())
131}
132
133pub(crate) fn emit_style_change_bits<W: WriteBits>(
134  writer: &mut W,
135  value: &ast::shape_records::StyleChange,
136  fill_bits: u32,
137  line_bits: u32,
138  version: ShapeVersion,
139) -> io::Result<(u32, u32)> {
140  let has_move_to = value.move_to.is_some();
141  let has_new_left_fill = value.left_fill.is_some();
142  let has_new_right_fill = value.right_fill.is_some();
143  let has_new_line_style = value.line_style.is_some();
144  let has_new_styles = value.new_styles.is_some();
145
146  #[allow(clippy::identity_op)]
147  let flags: u8 = 0
148    | (if has_move_to { 1 << 0 } else { 0 })
149    | (if has_new_left_fill { 1 << 1 } else { 0 })
150    | (if has_new_right_fill { 1 << 2 } else { 0 })
151    | (if has_new_line_style { 1 << 3 } else { 0 })
152    | (if has_new_styles { 1 << 4 } else { 0 });
153
154  assert_ne!(flags, 0);
155
156  writer.write_u32_bits(5, flags.into())?;
157
158  if let Some(move_to) = value.move_to {
159    let bits = get_i32_min_bit_count(vec![move_to.x, move_to.y].into_iter());
160    writer.write_u32_bits(5, bits)?;
161    writer.write_i32_bits(bits, move_to.x)?;
162    writer.write_i32_bits(bits, move_to.y)?;
163  }
164
165  if let Some(left_fill) = value.left_fill {
166    writer.write_u32_bits(fill_bits, left_fill.try_into().unwrap())?;
167  }
168  if let Some(right_fill) = value.right_fill {
169    writer.write_u32_bits(fill_bits, right_fill.try_into().unwrap())?;
170  }
171  if let Some(line_style) = value.line_style {
172    writer.write_u32_bits(line_bits, line_style.try_into().unwrap())?;
173  }
174
175  if let Some(ref new_styles) = &value.new_styles {
176    emit_shape_styles_bits(writer, new_styles, version)
177  } else {
178    Ok((fill_bits, line_bits))
179  }
180}
181
182pub(crate) fn emit_list_length<W: io::Write + ?Sized>(
183  writer: &mut W,
184  value: usize,
185  support_extended: bool,
186) -> io::Result<()> {
187  if !support_extended {
188    assert!(value <= 0xff);
189    emit_u8(writer, value.try_into().unwrap())
190  } else {
191    assert!(value <= 0xffff);
192    if value < 0xff {
193      emit_u8(writer, value.try_into().unwrap())
194    } else {
195      emit_u8(writer, 0xff)?;
196      emit_le_u16(writer, value.try_into().unwrap())
197    }
198  }
199}
200
201pub(crate) fn emit_fill_style_list<W: io::Write + ?Sized>(
202  writer: &mut W,
203  value: &[ast::FillStyle],
204  version: ShapeVersion,
205) -> io::Result<()> {
206  emit_list_length(writer, value.len(), version >= ShapeVersion::Shape2)?;
207  for fill_style in value {
208    emit_fill_style(writer, fill_style, version >= ShapeVersion::Shape3)?;
209  }
210  Ok(())
211}
212
213pub(crate) fn emit_fill_style<W: io::Write + ?Sized>(
214  writer: &mut W,
215  value: &ast::FillStyle,
216  with_alpha: bool,
217) -> io::Result<()> {
218  match value {
219    ast::FillStyle::Bitmap(ref style) => {
220      #[allow(clippy::identity_op)]
221      let code: u8 = 0
222        | (if !style.repeating { 1 << 0 } else { 0 })
223        | (if !style.smoothed { 1 << 1 } else { 0 }) | 0x40;
224      emit_u8(writer, code)?;
225      emit_bitmap_fill(writer, style)
226    }
227    ast::FillStyle::FocalGradient(ref style) => {
228      emit_u8(writer, 0x13)?;
229      emit_focal_gradient_fill(writer, style, with_alpha)
230    }
231    ast::FillStyle::LinearGradient(ref style) => {
232      emit_u8(writer, 0x10)?;
233      emit_linear_gradient_fill(writer, style, with_alpha)
234    }
235    ast::FillStyle::RadialGradient(ref style) => {
236      emit_u8(writer, 0x12)?;
237      emit_radial_gradient_fill(writer, style, with_alpha)
238    }
239    ast::FillStyle::Solid(ref style) => {
240      emit_u8(writer, 0x00)?;
241      emit_solid_fill(writer, style, with_alpha)
242    }
243  }
244}
245
246pub(crate) fn emit_bitmap_fill<W: io::Write + ?Sized>(
247  writer: &mut W,
248  value: &ast::fill_styles::Bitmap,
249) -> io::Result<()> {
250  emit_le_u16(writer, value.bitmap_id)?;
251  emit_matrix(writer, &value.matrix)
252}
253
254pub(crate) fn emit_focal_gradient_fill<W: io::Write + ?Sized>(
255  writer: &mut W,
256  value: &ast::fill_styles::FocalGradient,
257  with_alpha: bool,
258) -> io::Result<()> {
259  emit_matrix(writer, &value.matrix)?;
260  emit_gradient(writer, &value.gradient, with_alpha)?;
261  emit_le_i16(writer, value.focal_point.epsilons)
262}
263
264pub(crate) fn emit_linear_gradient_fill<W: io::Write + ?Sized>(
265  writer: &mut W,
266  value: &ast::fill_styles::LinearGradient,
267  with_alpha: bool,
268) -> io::Result<()> {
269  emit_matrix(writer, &value.matrix)?;
270  emit_gradient(writer, &value.gradient, with_alpha)
271}
272
273pub(crate) fn emit_radial_gradient_fill<W: io::Write + ?Sized>(
274  writer: &mut W,
275  value: &ast::fill_styles::RadialGradient,
276  with_alpha: bool,
277) -> io::Result<()> {
278  emit_matrix(writer, &value.matrix)?;
279  emit_gradient(writer, &value.gradient, with_alpha)
280}
281
282pub(crate) fn emit_solid_fill<W: io::Write + ?Sized>(
283  writer: &mut W,
284  value: &ast::fill_styles::Solid,
285  with_alpha: bool,
286) -> io::Result<()> {
287  if with_alpha {
288    emit_straight_s_rgba8(writer, value.color)
289  } else {
290    assert!(value.color.a == u8::max_value());
291    emit_s_rgb8(
292      writer,
293      ast::SRgb8 {
294        r: value.color.r,
295        g: value.color.g,
296        b: value.color.b,
297      },
298    )
299  }
300}
301
302pub(crate) fn emit_line_style_list<W: io::Write + ?Sized>(
303  writer: &mut W,
304  value: &[ast::LineStyle],
305  version: ShapeVersion,
306) -> io::Result<()> {
307  emit_list_length(writer, value.len(), version >= ShapeVersion::Shape2)?;
308  for line_style in value {
309    if version < ShapeVersion::Shape4 {
310      emit_line_style1(writer, line_style, version >= ShapeVersion::Shape3)?;
311    } else {
312      emit_line_style2(writer, line_style)?;
313    }
314  }
315  Ok(())
316}
317
318pub(crate) fn emit_line_style1<W: io::Write + ?Sized>(
319  writer: &mut W,
320  value: &ast::LineStyle,
321  with_alpha: bool,
322) -> io::Result<()> {
323  match value.fill {
324    ast::FillStyle::Solid(ref style) => {
325      emit_le_u16(writer, value.width)?;
326      emit_solid_fill(writer, style, with_alpha)
327    }
328    _ => panic!("InvalidLineStyle1Fill"),
329  }
330}
331
332pub(crate) fn emit_line_style2<W: io::Write + ?Sized>(writer: &mut W, value: &ast::LineStyle) -> io::Result<()> {
333  emit_le_u16(writer, value.width)?;
334
335  let has_fill = !matches!(&value.fill, ast::FillStyle::Solid(_));
336  let join_style_code = join_style_to_code(value.join);
337  let start_cap_style_code = cap_style_to_code(value.start_cap);
338  let end_cap_style_code = cap_style_to_code(value.end_cap);
339
340  #[allow(clippy::identity_op)]
341  let flags: u16 = 0
342    | (if value.pixel_hinting { 1 << 0 } else { 0 })
343    | (if value.no_v_scale { 1 << 1 } else { 0 })
344    | (if value.no_h_scale { 1 << 2 } else { 0 })
345    | (if has_fill { 1 << 3 } else { 0 })
346    | ((u16::from(join_style_code) & 0b11) << 4)
347    | ((u16::from(start_cap_style_code) & 0b11) << 6)
348    | ((u16::from(end_cap_style_code) & 0b11) << 8)
349    | (if value.no_close { 1 << 10 } else { 0 });
350  // Skip bits [11, 15]
351  emit_le_u16(writer, flags)?;
352
353  if let ast::JoinStyle::Miter(miter) = value.join {
354    emit_le_u16(writer, miter.limit)?;
355  }
356
357  match &value.fill {
358    ast::FillStyle::Solid(ref style) => emit_solid_fill(writer, style, true),
359    style => emit_fill_style(writer, style, true),
360  }
361}
362
363pub(crate) fn join_style_to_code(value: ast::JoinStyle) -> u8 {
364  match value {
365    ast::JoinStyle::Bevel => 1,
366    ast::JoinStyle::Round => 0,
367    ast::JoinStyle::Miter(_) => 2,
368  }
369}
370
371pub(crate) fn cap_style_to_code(value: ast::CapStyle) -> u8 {
372  match value {
373    ast::CapStyle::None => 1,
374    ast::CapStyle::Round => 0,
375    ast::CapStyle::Square => 2,
376  }
377}
378
379pub(crate) fn get_min_shape_version(value: &ast::Shape) -> ShapeVersion {
380  value.records.iter().fold(
381    get_shape_styles_min_shape_version(&value.initial_styles),
382    |acc, record| match record {
383      ast::ShapeRecord::StyleChange(ref record) => match &record.new_styles {
384        Some(ref styles) => max(acc, get_shape_styles_min_shape_version(styles)),
385        _ => acc,
386      },
387      _ => acc,
388    },
389  )
390}
391
392pub(crate) fn get_shape_styles_min_shape_version(value: &ast::ShapeStyles) -> ShapeVersion {
393  max(
394    get_fill_style_list_min_shape_version(&value.fill),
395    get_line_style_list_min_shape_version(&value.line),
396  )
397}
398
399pub(crate) fn get_fill_style_list_min_shape_version(value: &[ast::FillStyle]) -> ShapeVersion {
400  value.iter().map(get_fill_style_min_shape_version).fold(
401    if value.len() < 0xff {
402      ShapeVersion::Shape1
403    } else {
404      ShapeVersion::Shape2
405    },
406    max,
407  )
408}
409
410pub(crate) fn get_fill_style_min_shape_version(value: &ast::FillStyle) -> ShapeVersion {
411  let has_alpha = match value {
412    ast::FillStyle::Solid(ref style) => style.color.a != u8::max_value(),
413    ast::FillStyle::FocalGradient(ref style) => style.gradient.colors.iter().any(|cs| cs.color.a != u8::max_value()),
414    ast::FillStyle::LinearGradient(ref style) => style.gradient.colors.iter().any(|cs| cs.color.a != u8::max_value()),
415    ast::FillStyle::RadialGradient(ref style) => style.gradient.colors.iter().any(|cs| cs.color.a != u8::max_value()),
416    _ => false,
417  };
418
419  if has_alpha {
420    ShapeVersion::Shape3
421  } else {
422    ShapeVersion::Shape1
423  }
424}
425
426pub(crate) fn get_line_style_list_min_shape_version(value: &[ast::LineStyle]) -> ShapeVersion {
427  value.iter().map(get_line_style_min_shape_version).fold(
428    if value.len() < 0xff {
429      ShapeVersion::Shape1
430    } else {
431      ShapeVersion::Shape2
432    },
433    max,
434  )
435}
436
437pub(crate) fn get_line_style_min_shape_version(value: &ast::LineStyle) -> ShapeVersion {
438  let is_solid_fill = !matches!(&value.fill, ast::FillStyle::Solid(_));
439  let is_line_style2 = value.start_cap != ast::CapStyle::Round
440    || value.end_cap != ast::CapStyle::Round
441    || value.join != ast::JoinStyle::Round
442    || value.no_h_scale
443    || value.no_v_scale
444    || value.no_close
445    || value.pixel_hinting
446    || !is_solid_fill;
447
448  if is_line_style2 {
449    ShapeVersion::Shape4
450  } else {
451    match &value.fill {
452      ast::FillStyle::Solid(ref style) if style.color.a != u8::max_value() => ShapeVersion::Shape3,
453      _ => ShapeVersion::Shape1,
454    }
455  }
456}