swf_emitter/
tags.rs

1use std::cmp::max;
2use std::convert::TryInto;
3use std::io;
4
5use swf_fixed::Sfixed8P8;
6use swf_types as ast;
7
8use crate::basic_data_types::{
9  emit_c_string, emit_color_transform, emit_color_transform_with_alpha, emit_leb128_u32, emit_matrix, emit_rect,
10  emit_s_rgb8, emit_straight_s_rgba8,
11};
12use crate::bit_count::{get_i32_bit_count, get_u32_bit_count};
13use crate::button::{
14  emit_button2_cond_action_string, emit_button_record_string, emit_button_sound, get_min_button_version, ButtonVersion,
15};
16use crate::display::{emit_blend_mode, emit_clip_actions_string, emit_filter_list};
17use crate::morph_shape::{emit_morph_shape, MorphShapeVersion};
18use crate::primitives::{emit_le_f32, emit_le_i16, emit_le_u16, emit_le_u32, emit_u8};
19use crate::shape::emit_glyph;
20use crate::shape::{emit_shape, get_min_shape_version, ShapeVersion};
21use crate::sound::{audio_coding_format_to_code, emit_sound_info, sound_rate_to_code};
22use crate::text::{
23  csm_table_hint_to_code, emit_font_alignment_zone, emit_font_layout, emit_language_code, emit_offset_glyphs,
24  emit_text_alignment, emit_text_record_string, grid_fitting_to_code, text_renderer_to_code, DefineFontInfoVersion,
25  DefineFontVersion, DefineTextVersion,
26};
27
28pub fn emit_tag_string<W: io::Write>(writer: &mut W, value: &[ast::Tag], swf_version: u8) -> io::Result<()> {
29  for tag in value {
30    emit_tag(writer, tag, swf_version)?;
31  }
32  emit_end_of_tags(writer)
33}
34
35pub struct TagHeader {
36  pub code: u16,
37  pub length: u32,
38}
39
40fn emit_tag_header<W: io::Write>(writer: &mut W, value: TagHeader) -> io::Result<()> {
41  const SHORT_TAG_MAX_LENGTH: u16 = (1 << 6) - 1;
42
43  // Some tags require a long header
44  let is_long_required = matches!(
45    value.code,
46    6 // DefineBits
47    | 21 // DefineBitsJPEG2
48    | 35 // DefineBitsJPEG3
49    | 20 // DefineBitsLossless
50    | 36 // DefineBitsLossless2
51    | 90 // DefineBitsJPEG4
52    | 19 // SoundStreamBlock
53  );
54  let is_leading_byte_non_zero = value.length > 0 || (value.code & 0b11) != 0;
55
56  if !is_long_required && value.length < u32::from(SHORT_TAG_MAX_LENGTH) && is_leading_byte_non_zero {
57    let code_and_length: u16 = (value.code << 6) | (u16::try_from(value.length).unwrap());
58    debug_assert!(code_and_length.to_le_bytes()[0] != 0);
59    emit_le_u16(writer, code_and_length)
60  } else {
61    let code_and_length: u16 = (value.code << 6) | SHORT_TAG_MAX_LENGTH;
62    emit_le_u16(writer, code_and_length)?;
63    emit_le_u32(writer, value.length)
64  }
65}
66
67pub fn emit_end_of_tags<W: io::Write>(writer: &mut W) -> io::Result<()> {
68  emit_le_u16(writer, 0)
69}
70
71pub fn emit_tag<W: io::Write>(writer: &mut W, value: &ast::Tag, swf_version: u8) -> io::Result<()> {
72  let mut tag_writer = Vec::new();
73
74  let code: u16 = match value {
75    ast::Tag::CsmTextSettings(ref tag) => {
76      emit_csm_text_settings(&mut tag_writer, tag)?;
77      74
78    }
79    ast::Tag::DefineBinaryData(ref _tag) => unimplemented!(),
80    ast::Tag::DefineBitmap(ref tag) => match emit_define_bitmap_any(&mut tag_writer, tag)? {
81      DefineBitmapVersion::DefineBitsJpeg1 => 6,
82      DefineBitmapVersion::DefineBitsLossless1 => 20,
83      DefineBitmapVersion::DefineBitsJpeg2 => 21,
84      DefineBitmapVersion::DefineBitsJpeg3 => 35,
85      DefineBitmapVersion::DefineBitsLossless2 => 36,
86      DefineBitmapVersion::DefineBitsJpeg4 => 90,
87    },
88    ast::Tag::DefineButton(ref tag) => match emit_define_button_any(&mut tag_writer, tag)? {
89      ButtonVersion::Button1 => 7,
90      ButtonVersion::Button2 => 34,
91    },
92    ast::Tag::DefineButtonColorTransform(ref _tag) => unimplemented!(),
93    ast::Tag::DefineButtonSound(ref tag) => {
94      emit_define_button_sound(&mut tag_writer, tag)?;
95      17
96    }
97    ast::Tag::DefineCffFont(ref _tag) => unimplemented!(),
98    ast::Tag::DefineDynamicText(ref tag) => {
99      emit_define_dynamic_text(&mut tag_writer, tag)?;
100      37
101    }
102    ast::Tag::DefineFont(ref tag) => {
103      match emit_define_font_any(&mut tag_writer, tag)? {
104        // `Font1` is handled in `DefineGlyphFont`
105        DefineFontVersion::Font2 => 48,
106        DefineFontVersion::Font3 => 75,
107        DefineFontVersion::Font4 => 91,
108      }
109    }
110    ast::Tag::DefineFontAlignZones(ref tag) => {
111      emit_define_font_align_zones(&mut tag_writer, tag)?;
112      73
113    }
114    ast::Tag::DefineFontInfo(ref tag) => match emit_define_font_info_any(&mut tag_writer, tag)? {
115      DefineFontInfoVersion::FontInfo1 => 13,
116      DefineFontInfoVersion::FontInfo2 => 62,
117    },
118    ast::Tag::DefineFontName(ref tag) => {
119      emit_define_font_name(&mut tag_writer, tag)?;
120      88
121    }
122    ast::Tag::DefineGlyphFont(ref tag) => {
123      emit_define_glyph_font(&mut tag_writer, tag)?;
124      10
125    }
126    ast::Tag::DefineJpegTables(ref tag) => {
127      emit_define_jpeg_tables(&mut tag_writer, tag)?;
128      8
129    }
130    ast::Tag::DefineMorphShape(ref tag) => match emit_define_morph_shape_any(&mut tag_writer, tag)? {
131      MorphShapeVersion::MorphShape1 => 46,
132      MorphShapeVersion::MorphShape2 => 84,
133    },
134    ast::Tag::DefineScalingGrid(ref _tag) => unimplemented!(),
135    ast::Tag::DefineSceneAndFrameLabelData(ref tag) => {
136      emit_define_scene_and_frame_label_data(&mut tag_writer, tag)?;
137      86
138    }
139    ast::Tag::DefineShape(ref tag) => match emit_define_shape_any(&mut tag_writer, tag)? {
140      ShapeVersion::Shape1 => 2,
141      ShapeVersion::Shape2 => 22,
142      ShapeVersion::Shape3 => 32,
143      ShapeVersion::Shape4 => 83,
144    },
145    ast::Tag::DefineSound(ref tag) => {
146      emit_define_sound(&mut tag_writer, tag)?;
147      14
148    }
149    ast::Tag::DefineSprite(ref tag) => {
150      emit_define_sprite(&mut tag_writer, tag, swf_version)?;
151      39
152    }
153    ast::Tag::DefineText(ref tag) => match emit_define_text_any(&mut tag_writer, tag)? {
154      DefineTextVersion::Text1 => 11,
155      DefineTextVersion::Text2 => 33,
156    },
157    ast::Tag::DefineVideoStream(ref _tag) => unimplemented!(),
158    ast::Tag::DoAbc(ref tag) => match emit_do_abc_any(&mut tag_writer, tag)? {
159      DoAbcVersion::Abc1 => 72,
160      DoAbcVersion::Abc2 => 82,
161    },
162    ast::Tag::DoAction(ref tag) => {
163      emit_do_action(&mut tag_writer, tag)?;
164      12
165    }
166    ast::Tag::DoInitAction(ref _tag) => unimplemented!(),
167    ast::Tag::EnableDebugger(ref _tag) => unimplemented!(),
168    ast::Tag::EnablePostscript => unimplemented!(),
169    ast::Tag::ExportAssets(ref tag) => {
170      emit_export_assets(&mut tag_writer, tag)?;
171      56
172    }
173    ast::Tag::FileAttributes(ref tag) => {
174      emit_file_attributes(&mut tag_writer, tag)?;
175      69
176    }
177    ast::Tag::FrameLabel(ref tag) => {
178      emit_frame_label(&mut tag_writer, tag)?;
179      43
180    }
181    ast::Tag::ImportAssets(ref _tag) => unimplemented!(),
182    ast::Tag::Metadata(ref tag) => {
183      emit_metadata(&mut tag_writer, tag)?;
184      77
185    }
186    ast::Tag::PlaceObject(ref tag) => match emit_place_object_any(&mut tag_writer, tag, swf_version)? {
187      PlaceObjectVersion::PlaceObject1 => 4,
188      PlaceObjectVersion::PlaceObject2 => 26,
189      PlaceObjectVersion::PlaceObject3 => 70,
190    },
191    ast::Tag::Protect(ref tag) => {
192      emit_protect(&mut tag_writer, tag)?;
193      24
194    }
195    ast::Tag::Raw(ref tag) => return writer.write_all(&tag.data),
196    ast::Tag::RawBody(ref tag) => {
197      emit_raw_body(&mut tag_writer, tag)?;
198      tag.code
199    }
200    ast::Tag::RemoveObject(ref tag) => match emit_remove_object_any(&mut tag_writer, tag)? {
201      RemoveObjectVersion::RemoveObject1 => 5,
202      RemoveObjectVersion::RemoveObject2 => 28,
203    },
204    ast::Tag::ScriptLimits(ref _tag) => unimplemented!(),
205    ast::Tag::SetBackgroundColor(ref tag) => {
206      emit_set_background_color(&mut tag_writer, tag)?;
207      9
208    }
209    ast::Tag::SetTabIndex(ref _tag) => unimplemented!(),
210    ast::Tag::ShowFrame => 1,
211    ast::Tag::SoundStreamBlock(ref _tag) => unimplemented!(),
212    ast::Tag::SoundStreamHead(ref _tag) => unimplemented!(),
213    ast::Tag::StartSound(ref tag) => {
214      emit_start_sound(&mut tag_writer, tag)?;
215      15
216    }
217    ast::Tag::StartSound2(ref _tag) => unimplemented!(),
218    ast::Tag::SymbolClass(ref tag) => {
219      emit_symbol_class(&mut tag_writer, tag)?;
220      76
221    }
222    ast::Tag::Telemetry(ref _tag) => unimplemented!(),
223    ast::Tag::VideoFrame(ref _tag) => unimplemented!(),
224  };
225
226  emit_tag_header(
227    writer,
228    TagHeader {
229      code,
230      length: tag_writer.len().try_into().unwrap(),
231    },
232  )?;
233  writer.write_all(&tag_writer)
234}
235
236pub fn emit_csm_text_settings<W: io::Write>(writer: &mut W, value: &ast::tags::CsmTextSettings) -> io::Result<()> {
237  emit_le_u16(writer, value.text_id)?;
238
239  #[allow(clippy::identity_op)]
240  let flags: u8 = 0
241    // Skip bits [0, 2]
242    | (grid_fitting_to_code(value.fitting) << 3)
243    | (text_renderer_to_code(value.renderer) << 6);
244  emit_u8(writer, flags)?;
245
246  emit_le_f32(writer, value.thickness)?;
247  emit_le_f32(writer, value.sharpness)?;
248  emit_u8(writer, 0) // Reserved
249}
250
251#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
252pub enum DefineBitmapVersion {
253  DefineBitsJpeg1,
254  DefineBitsJpeg2,
255  DefineBitsJpeg3,
256  DefineBitsJpeg4,
257  DefineBitsLossless1,
258  DefineBitsLossless2,
259}
260
261pub fn emit_define_bitmap_any<W: io::Write>(
262  writer: &mut W,
263  value: &ast::tags::DefineBitmap,
264) -> io::Result<DefineBitmapVersion> {
265  emit_le_u16(writer, value.id)?;
266  writer.write_all(&value.data)?;
267
268  let version = match value.media_type {
269    ast::ImageType::SwfLossless1 => DefineBitmapVersion::DefineBitsLossless1,
270    ast::ImageType::SwfLossless2 => DefineBitmapVersion::DefineBitsLossless2,
271    ast::ImageType::Jpeg | ast::ImageType::Gif | ast::ImageType::Png => DefineBitmapVersion::DefineBitsJpeg2,
272    ast::ImageType::SwfJpeg3 => DefineBitmapVersion::DefineBitsJpeg3,
273    ast::ImageType::SwfJpeg4 => unimplemented!("image/x-swf-jpeg4"),
274    ast::ImageType::SwfPartialJpeg => DefineBitmapVersion::DefineBitsJpeg1,
275  };
276
277  Ok(version)
278}
279
280pub(crate) fn emit_define_button_any<W: io::Write>(
281  writer: &mut W,
282  value: &ast::tags::DefineButton,
283) -> io::Result<ButtonVersion> {
284  emit_le_u16(writer, value.id)?;
285
286  let version: ButtonVersion = get_min_button_version(value);
287
288  let mut record_writer: Vec<u8> = Vec::new();
289  emit_button_record_string(&mut record_writer, &value.records, version)?;
290
291  match version {
292    ButtonVersion::Button1 => {
293      debug_assert!(!value.track_as_menu);
294      writer.write_all(&record_writer)?;
295      debug_assert_eq!(value.actions.len(), 1);
296      let action: &ast::ButtonCondAction = value.actions.get(0).unwrap();
297      debug_assert!(action.conditions.is_none());
298      writer.write_all(&action.actions)?;
299    }
300    ButtonVersion::Button2 => {
301      let flags: u8 = if value.track_as_menu { 1 << 0 } else { 0 };
302      emit_u8(writer, flags)?;
303
304      if value.actions.is_empty() {
305        emit_le_u16(writer, 0)?;
306        writer.write_all(&record_writer)?;
307      } else {
308        // Add the size of the offset field itself
309        let action_offset = std::mem::size_of::<u16>() + record_writer.len();
310        emit_le_u16(writer, action_offset.try_into().unwrap())?;
311        writer.write_all(&record_writer)?;
312        emit_button2_cond_action_string(writer, &value.actions)?;
313      }
314    }
315  }
316
317  Ok(version)
318}
319
320pub(crate) fn emit_define_button_sound<W: io::Write>(
321  writer: &mut W,
322  value: &ast::tags::DefineButtonSound,
323) -> io::Result<()> {
324  emit_le_u16(writer, value.button_id)?;
325  emit_button_sound(writer, &value.over_up_to_idle)?;
326  emit_button_sound(writer, &value.idle_to_over_up)?;
327  emit_button_sound(writer, &value.over_up_to_over_down)?;
328  emit_button_sound(writer, &value.over_down_to_over_up)?;
329  Ok(())
330}
331
332pub(crate) fn emit_define_dynamic_text<W: io::Write>(
333  writer: &mut W,
334  value: &ast::tags::DefineDynamicText,
335) -> io::Result<()> {
336  emit_le_u16(writer, value.id)?;
337  emit_rect(writer, &value.bounds)?;
338
339  let has_font = value.font_id.is_some() && value.font_size.is_some();
340  let has_max_length = value.max_length.is_some();
341  let has_color = value.color.is_some();
342  let has_text = value.text.is_some();
343  let has_layout = value.align != ast::text::TextAlignment::Left
344    || value.margin_left != 0
345    || value.margin_right != 0
346    || value.indent != 0
347    || value.leading != 0;
348  let has_font_class = value.font_class.is_some() && value.font_size.is_some();
349
350  #[allow(clippy::identity_op)]
351  let flags: u16 = 0
352    | (if has_font { 1 << 0 } else { 0 })
353    | (if has_max_length { 1 << 1 } else { 0 })
354    | (if has_color { 1 << 2 } else { 0 })
355    | (if value.readonly { 1 << 3 } else { 0 })
356    | (if value.password { 1 << 4 } else { 0 })
357    | (if value.multiline { 1 << 5 } else { 0 })
358    | (if value.word_wrap { 1 << 6 } else { 0 })
359    | (if has_text { 1 << 7 } else { 0 })
360    | (if value.use_glyph_font { 1 << 8 } else { 0 })
361    | (if value.html { 1 << 9 } else { 0 })
362    | (if value.was_static { 1 << 10 } else { 0 })
363    | (if value.border { 1 << 11 } else { 0 })
364    | (if value.no_select { 1 << 12 } else { 0 })
365    | (if has_layout { 1 << 13 } else { 0 })
366    | (if value.auto_size { 1 << 14 } else { 0 })
367    | (if has_font_class { 1 << 15 } else { 0 });
368  emit_le_u16(writer, flags)?;
369
370  if let Some(font_id) = value.font_id {
371    assert!(has_font);
372    emit_le_u16(writer, font_id)?;
373  }
374  if let Some(ref font_class) = &value.font_class {
375    assert!(has_font_class);
376    emit_c_string(writer, font_class)?;
377  }
378  if let Some(font_size) = value.font_size {
379    assert!(has_font || has_font_class);
380    emit_le_u16(writer, font_size)?;
381  }
382  if let Some(color) = value.color {
383    emit_straight_s_rgba8(writer, color)?;
384  }
385  if let Some(max_length) = value.max_length {
386    emit_le_u16(writer, max_length.try_into().unwrap())?;
387  }
388  if has_layout {
389    emit_text_alignment(writer, value.align)?;
390    emit_le_u16(writer, value.margin_left)?;
391    emit_le_u16(writer, value.margin_right)?;
392    emit_le_u16(writer, value.indent)?;
393    emit_le_i16(writer, value.leading)?;
394  }
395  emit_c_string(
396    writer,
397    match &value.variable_name {
398      Some(ref v) => v,
399      None => "",
400    },
401  )?;
402  if let Some(ref text) = &value.text {
403    emit_c_string(writer, text)?;
404  }
405
406  Ok(())
407}
408
409pub(crate) fn emit_define_font_any<W: io::Write>(
410  writer: &mut W,
411  value: &ast::tags::DefineFont,
412) -> io::Result<DefineFontVersion> {
413  let version = match value.em_square_size {
414    ast::text::EmSquareSize::EmSquareSize1024 => DefineFontVersion::Font2,
415    ast::text::EmSquareSize::EmSquareSize20480 => DefineFontVersion::Font3,
416  };
417
418  emit_le_u16(writer, value.id)?;
419
420  let use_wide_codes = true; // `false` is deprecated since SWF6
421  let mut offset_glyph_writer = Vec::new();
422  let use_wide_offsets = if let Some(ref glyphs) = &value.glyphs {
423    emit_offset_glyphs(&mut offset_glyph_writer, glyphs)?
424  } else {
425    false
426  };
427  let has_layout = value.layout.is_some();
428
429  #[allow(clippy::identity_op)]
430  let flags: u8 = 0
431    | (if value.is_bold { 1 << 0 } else { 0 })
432    | (if value.is_italic { 1 << 1 } else { 0 })
433    | (if use_wide_codes { 1 << 2 } else { 0 })
434    | (if use_wide_offsets { 1 << 3 } else { 0 })
435    | (if value.is_ansi { 1 << 4 } else { 0 })
436    | (if value.is_small { 1 << 5 } else { 0 })
437    | (if value.is_shift_jis { 1 << 6 } else { 0 })
438    | (if has_layout { 1 << 7 } else { 0 });
439  emit_u8(writer, flags)?;
440
441  emit_language_code(writer, value.language)?;
442
443  let font_name_c_string = std::ffi::CString::new(value.font_name.clone()).unwrap();
444  let font_name_bytes = font_name_c_string.as_bytes_with_nul();
445  emit_u8(writer, font_name_bytes.len().try_into().unwrap())?;
446  writer.write_all(font_name_bytes)?;
447
448  if let Some(ref glyphs) = &value.glyphs {
449    emit_le_u16(writer, glyphs.len().try_into().unwrap())?;
450    writer.write_all(&offset_glyph_writer)?;
451    // TODO: Assert codeUnits is defined (should be defined because of .glyphs)
452    for code_unit in value.code_units.as_ref().unwrap() {
453      debug_assert!(use_wide_codes);
454      emit_le_u16(writer, *code_unit)?;
455    }
456
457    if let Some(ref layout) = &value.layout {
458      emit_font_layout(writer, layout)?;
459    }
460  } else {
461    // According to Shumway:
462    // > The SWF format docs doesn't say that, but the DefineFont{2,3} tag ends
463    // > here for device fonts.
464    emit_le_u16(writer, 0)?;
465  }
466
467  Ok(version)
468}
469
470pub fn emit_define_font_align_zones<W: io::Write>(
471  writer: &mut W,
472  value: &ast::tags::DefineFontAlignZones,
473) -> io::Result<()> {
474  emit_le_u16(writer, value.font_id)?;
475  #[allow(clippy::identity_op)]
476  let flags: u8 = 0
477    // Skip bits [0, 5]
478    | (csm_table_hint_to_code(value.csm_table_hint) << 6);
479  emit_u8(writer, flags)?;
480  for zone in &value.zones {
481    emit_font_alignment_zone(writer, zone)?;
482  }
483  Ok(())
484}
485
486pub(crate) fn emit_define_font_info_any<W: io::Write>(
487  writer: &mut W,
488  value: &ast::tags::DefineFontInfo,
489) -> io::Result<DefineFontInfoVersion> {
490  let version = match value.language {
491    ast::LanguageCode::Auto => DefineFontInfoVersion::FontInfo1,
492    _ => DefineFontInfoVersion::FontInfo2,
493  };
494
495  emit_le_u16(writer, value.font_id)?;
496
497  let font_name_c_string = std::ffi::CString::new(value.font_name.clone()).unwrap();
498  let font_name_bytes = font_name_c_string.as_bytes_with_nul();
499  emit_u8(writer, font_name_bytes.len().try_into().unwrap())?;
500  writer.write_all(font_name_bytes)?;
501
502  let mut use_wide_codes = version >= DefineFontInfoVersion::FontInfo2;
503  if !use_wide_codes {
504    for code_unit in value.code_units.iter() {
505      if *code_unit >= 256 {
506        use_wide_codes = true;
507        break;
508      }
509    }
510  }
511
512  // TODO: `is_ansi` and `is_shift_jis` must be `false` in FontInfo2.
513  #[allow(clippy::identity_op)]
514  let flags: u8 = 0
515    | (if use_wide_codes { 1 << 0 } else { 0 })
516    | (if value.is_bold { 1 << 1 } else { 0 })
517    | (if value.is_italic { 1 << 2 } else { 0 })
518    | (if value.is_ansi { 1 << 3 } else { 0 })
519    | (if value.is_shift_jis { 1 << 4 } else { 0 })
520    | (if value.is_small { 1 << 5 } else { 0 });
521  emit_u8(writer, flags)?;
522
523  if version >= DefineFontInfoVersion::FontInfo2 {
524    emit_language_code(writer, value.language)?;
525  }
526
527  for code_unit in value.code_units.iter() {
528    if use_wide_codes {
529      emit_le_u16(writer, *code_unit)?;
530    } else {
531      emit_u8(writer, (*code_unit).try_into().unwrap())?;
532    }
533  }
534
535  Ok(version)
536}
537
538pub fn emit_define_font_name<W: io::Write>(writer: &mut W, value: &ast::tags::DefineFontName) -> io::Result<()> {
539  emit_le_u16(writer, value.font_id)?;
540  emit_c_string(writer, &value.name)?;
541  emit_c_string(writer, &value.copyright)
542}
543
544pub fn emit_define_glyph_font<W: io::Write>(writer: &mut W, value: &ast::tags::DefineGlyphFont) -> io::Result<()> {
545  emit_le_u16(writer, value.id)?;
546  if value.glyphs.is_empty() {
547    return Ok(());
548  }
549
550  let first_offset = value.glyphs.len() * 2;
551  let mut glyph_writer = Vec::new();
552  for glyph in value.glyphs.iter() {
553    emit_le_u16(writer, (first_offset + glyph_writer.len()) as u16)?;
554    emit_glyph(&mut glyph_writer, glyph)?;
555  }
556  writer.write_all(&glyph_writer)
557}
558
559pub fn emit_define_jpeg_tables<W: io::Write>(writer: &mut W, value: &ast::tags::DefineJpegTables) -> io::Result<()> {
560  writer.write_all(&value.data)
561}
562
563pub fn emit_define_morph_shape_any<W: io::Write>(
564  writer: &mut W,
565  value: &ast::tags::DefineMorphShape,
566) -> io::Result<MorphShapeVersion> {
567  emit_le_u16(writer, value.id)?;
568  emit_rect(writer, &value.bounds)?;
569  emit_rect(writer, &value.morph_bounds)?;
570
571  let version = if let Some(ref edge_bounds) = &value.edge_bounds {
572    let morph_edge_bounds = &value.morph_edge_bounds.unwrap();
573    emit_rect(writer, edge_bounds)?;
574    emit_rect(writer, morph_edge_bounds)?;
575    #[allow(clippy::identity_op)]
576    let flags: u8 = 0
577      | (if value.has_scaling_strokes { 1 << 0 } else { 0 })
578      | (if value.has_non_scaling_strokes { 1 << 1 } else { 0 });
579    // Skip bits [2, 7]
580    emit_u8(writer, flags)?;
581    MorphShapeVersion::MorphShape2
582  } else {
583    MorphShapeVersion::MorphShape1
584  };
585  emit_morph_shape(writer, &value.shape, version)?;
586  Ok(version)
587}
588
589pub fn emit_define_scene_and_frame_label_data<W: io::Write>(
590  writer: &mut W,
591  value: &ast::tags::DefineSceneAndFrameLabelData,
592) -> io::Result<()> {
593  emit_leb128_u32(writer, value.scenes.len().try_into().unwrap())?;
594  for scene in &value.scenes {
595    emit_leb128_u32(writer, scene.offset)?;
596    emit_c_string(writer, &scene.name)?;
597  }
598  emit_leb128_u32(writer, value.labels.len().try_into().unwrap())?;
599  for label in &value.labels {
600    emit_leb128_u32(writer, label.frame)?;
601    emit_c_string(writer, &label.name)?;
602  }
603  Ok(())
604}
605
606pub fn emit_define_shape_any<W: io::Write>(writer: &mut W, value: &ast::tags::DefineShape) -> io::Result<ShapeVersion> {
607  emit_le_u16(writer, value.id)?;
608  emit_rect(writer, &value.bounds)?;
609  let version = if let Some(ref edge_bounds) = &value.edge_bounds {
610    emit_rect(writer, edge_bounds)?;
611    #[allow(clippy::identity_op)]
612    let flags: u8 = 0
613      | (if value.has_scaling_strokes { 1 << 0 } else { 0 })
614      | (if value.has_non_scaling_strokes { 1 << 1 } else { 0 })
615      | (if value.has_fill_winding { 1 << 2 } else { 0 });
616    // Skip bits [3, 7]
617    emit_u8(writer, flags)?;
618    ShapeVersion::Shape4
619  } else {
620    get_min_shape_version(&value.shape)
621  };
622  emit_shape(writer, &value.shape, version)?;
623  Ok(version)
624}
625
626pub fn emit_define_sound<W: io::Write>(writer: &mut W, value: &ast::tags::DefineSound) -> io::Result<()> {
627  emit_le_u16(writer, value.id)?;
628
629  #[allow(clippy::identity_op)]
630  let flags: u8 = 0
631    // (this comment prevent rustfmt from changing the layout, todo: find how to disable on this assignment only)
632    | (if value.sound_type == ast::SoundType::Stereo { 1 << 0 } else { 0 })
633    | (if value.sound_size == ast::SoundSize::SoundSize16 { 1 << 1 } else { 0 })
634    | (sound_rate_to_code(value.sound_rate) << 2)
635    | (audio_coding_format_to_code(value.format) << 4);
636  emit_u8(writer, flags)?;
637
638  emit_le_u32(writer, value.sample_count)?;
639  writer.write_all(&value.data)
640}
641
642pub fn emit_define_sprite<W: io::Write>(
643  writer: &mut W,
644  value: &ast::tags::DefineSprite,
645  swf_version: u8,
646) -> io::Result<()> {
647  emit_le_u16(writer, value.id)?;
648  emit_le_u16(writer, value.frame_count.try_into().unwrap())?;
649  emit_tag_string(writer, &value.tags, swf_version)
650}
651
652pub(crate) fn emit_define_text_any<W: io::Write>(
653  writer: &mut W,
654  value: &ast::tags::DefineText,
655) -> io::Result<DefineTextVersion> {
656  emit_le_u16(writer, value.id)?;
657  emit_rect(writer, &value.bounds)?;
658  emit_matrix(writer, &value.matrix)?;
659  let mut index_bits: u32 = 0;
660  let mut advance_bits: u32 = 0;
661  let mut has_alpha = false;
662  for record in &value.records {
663    if let Some(color) = record.color {
664      if color.a != u8::max_value() {
665        has_alpha = true;
666      }
667    }
668    for entry in &record.entries {
669      index_bits = max(index_bits, get_u32_bit_count(entry.index.try_into().unwrap()));
670      advance_bits = max(advance_bits, get_i32_bit_count(entry.advance));
671    }
672  }
673  emit_u8(writer, index_bits.try_into().unwrap())?;
674  emit_u8(writer, advance_bits.try_into().unwrap())?;
675  emit_text_record_string(writer, &value.records, index_bits, advance_bits, has_alpha)?;
676
677  Ok(if has_alpha {
678    DefineTextVersion::Text2
679  } else {
680    DefineTextVersion::Text1
681  })
682}
683
684enum DoAbcVersion {
685  Abc1,
686  Abc2,
687}
688
689fn emit_do_abc_any<W: io::Write>(writer: &mut W, value: &ast::tags::DoAbc) -> io::Result<DoAbcVersion> {
690  let version: DoAbcVersion = if let Some(ref header) = &value.header {
691    emit_le_u32(writer, header.flags)?;
692    emit_c_string(writer, &header.name)?;
693    DoAbcVersion::Abc2
694  } else {
695    DoAbcVersion::Abc1
696  };
697  writer.write_all(&value.data)?;
698  Ok(version)
699}
700
701pub fn emit_do_action<W: io::Write>(writer: &mut W, value: &ast::tags::DoAction) -> io::Result<()> {
702  writer.write_all(&value.actions)
703}
704
705pub fn emit_export_assets<W: io::Write>(writer: &mut W, value: &ast::tags::ExportAssets) -> io::Result<()> {
706  emit_le_u16(writer, value.assets.len().try_into().unwrap())?;
707  for asset in &value.assets {
708    emit_le_u16(writer, asset.id)?;
709    emit_c_string(writer, &asset.name)?;
710  }
711  Ok(())
712}
713
714pub fn emit_file_attributes<W: io::Write>(writer: &mut W, value: &ast::tags::FileAttributes) -> io::Result<()> {
715  #[allow(clippy::identity_op)]
716  let flags: u32 = 0
717    | (if value.use_network { 1 << 0 } else { 0 })
718    | (if value.use_relative_urls { 1 << 1 } else { 0 })
719    | (if value.no_cross_domain_caching { 1 << 2 } else { 0 })
720    | (if value.use_as3 { 1 << 3 } else { 0 })
721    | (if value.has_metadata { 1 << 4 } else { 0 })
722    | (if value.use_gpu { 1 << 5 } else { 0 })
723    | (if value.use_direct_blit { 1 << 6 } else { 0 });
724  // Skip bits [7, 31]
725
726  emit_le_u32(writer, flags)
727}
728
729pub fn emit_frame_label<W: io::Write>(writer: &mut W, value: &ast::tags::FrameLabel) -> io::Result<()> {
730  emit_c_string(writer, &value.name)?;
731  if value.is_anchor {
732    emit_u8(writer, 1)?;
733  }
734  Ok(())
735}
736
737pub fn emit_metadata<W: io::Write>(writer: &mut W, value: &ast::tags::Metadata) -> io::Result<()> {
738  emit_c_string(writer, &value.metadata)
739}
740
741pub enum PlaceObjectVersion {
742  PlaceObject1,
743  PlaceObject2,
744  PlaceObject3,
745}
746
747pub fn emit_place_object_any<W: io::Write>(
748  writer: &mut W,
749  value: &ast::tags::PlaceObject,
750  swf_version: u8,
751) -> io::Result<PlaceObjectVersion> {
752  const FIXED_ONE: Sfixed8P8 = Sfixed8P8::from_epsilons(256);
753
754  let is_update = value.is_update;
755  let has_character_id = value.character_id.is_some();
756  let has_matrix = value.matrix.is_some();
757  let has_color_transform = value.color_transform.is_some();
758  let has_color_transform_with_alpha = value
759    .color_transform
760    .map(|cx| cx.alpha_mult != FIXED_ONE || cx.alpha_add != 0)
761    .unwrap_or(false);
762  let has_ratio = value.ratio.is_some();
763  let has_name = value.name.is_some();
764  let has_clip_depth = value.clip_depth.is_some();
765  let has_clip_actions = value.clip_actions.is_some();
766  let has_filters = value.filters.is_some();
767  let has_blend_mode = value.blend_mode.is_some();
768  let has_cache_hint = value.bitmap_cache.is_some();
769  let has_class_name = value.class_name.is_some();
770  let has_image = false; // TODO: We need more context to handle images
771  let has_visibility = value.visible.is_some();
772  let has_background_color = value.background_color.is_some();
773
774  if has_filters
775    || has_blend_mode
776    || has_cache_hint
777    || has_class_name
778    || has_image
779    || has_visibility
780    || has_background_color
781  {
782    #[allow(clippy::identity_op)]
783    let flags: u16 = 0
784      | (if is_update { 1 << 0 } else { 0 })
785      | (if has_character_id { 1 << 1 } else { 0 })
786      | (if has_matrix { 1 << 2 } else { 0 })
787      | (if has_color_transform { 1 << 3 } else { 0 })
788      | (if has_ratio { 1 << 4 } else { 0 })
789      | (if has_name { 1 << 5 } else { 0 })
790      | (if has_clip_depth { 1 << 6 } else { 0 })
791      | (if has_clip_actions { 1 << 7 } else { 0 })
792      | (if has_filters { 1 << 8 } else { 0 })
793      | (if has_blend_mode { 1 << 9 } else { 0 })
794      | (if has_cache_hint { 1 << 10 } else { 0 })
795      | (if has_class_name { 1 << 11 } else { 0 })
796      | (if has_image { 1 << 12 } else { 0 })
797      | (if has_visibility { 1 << 13 } else { 0 })
798      | (if has_background_color { 1 << 14 } else { 0 });
799    emit_le_u16(writer, flags)?;
800    emit_le_u16(writer, value.depth)?;
801    if let Some(ref class_name) = &value.class_name {
802      emit_c_string(writer, class_name)?;
803    }
804    if let Some(character_id) = value.character_id {
805      emit_le_u16(writer, character_id)?;
806    }
807    if let Some(ref matrix) = &value.matrix {
808      emit_matrix(writer, matrix)?;
809    }
810    if let Some(ref color_transform) = &value.color_transform {
811      emit_color_transform_with_alpha(writer, color_transform)?;
812    }
813    if let Some(ratio) = value.ratio {
814      emit_le_u16(writer, ratio)?;
815    }
816    if let Some(ref name) = &value.name {
817      emit_c_string(writer, name)?;
818    }
819    if let Some(clip_depth) = value.clip_depth {
820      emit_le_u16(writer, clip_depth)?;
821    }
822    if let Some(ref filters) = &value.filters {
823      emit_filter_list(writer, filters)?;
824    }
825    if let Some(blend_mode) = value.blend_mode {
826      emit_blend_mode(writer, blend_mode)?;
827    }
828    if let Some(bitmap_cache) = value.bitmap_cache {
829      emit_u8(writer, if bitmap_cache { 1 } else { 0 })?;
830    }
831    if let Some(visible) = value.visible {
832      emit_u8(writer, if visible { 1 } else { 0 })?;
833    }
834    if let Some(background_color) = value.background_color {
835      emit_straight_s_rgba8(writer, background_color)?;
836    }
837    if let Some(ref clip_actions) = &value.clip_actions {
838      emit_clip_actions_string(writer, clip_actions, swf_version >= 6)?;
839    }
840    Ok(PlaceObjectVersion::PlaceObject3)
841  } else if !has_character_id
842    || !has_matrix
843    || is_update
844    || has_color_transform_with_alpha
845    || has_ratio
846    || has_name
847    || has_clip_depth
848    || has_clip_actions
849  {
850    #[allow(clippy::identity_op)]
851    let flags: u8 = 0
852      | (if is_update { 1 << 0 } else { 0 })
853      | (if has_character_id { 1 << 1 } else { 0 })
854      | (if has_matrix { 1 << 2 } else { 0 })
855      | (if has_color_transform { 1 << 3 } else { 0 })
856      | (if has_ratio { 1 << 4 } else { 0 })
857      | (if has_name { 1 << 5 } else { 0 })
858      | (if has_clip_depth { 1 << 6 } else { 0 })
859      | (if has_clip_actions { 1 << 7 } else { 0 });
860    emit_u8(writer, flags)?;
861    emit_le_u16(writer, value.depth)?;
862    if let Some(character_id) = value.character_id {
863      emit_le_u16(writer, character_id)?;
864    }
865    if let Some(ref matrix) = &value.matrix {
866      emit_matrix(writer, matrix)?;
867    }
868    if let Some(ref color_transform) = &value.color_transform {
869      emit_color_transform_with_alpha(writer, color_transform)?;
870    }
871    if let Some(ratio) = value.ratio {
872      emit_le_u16(writer, ratio)?;
873    }
874    if let Some(ref name) = &value.name {
875      emit_c_string(writer, name)?;
876    }
877    if let Some(clip_depth) = value.clip_depth {
878      emit_le_u16(writer, clip_depth)?;
879    }
880    if let Some(ref clip_actions) = &value.clip_actions {
881      emit_clip_actions_string(writer, clip_actions, swf_version >= 6)?;
882    }
883    Ok(PlaceObjectVersion::PlaceObject2)
884  } else {
885    debug_assert!(has_character_id && has_matrix && !has_color_transform_with_alpha);
886    emit_le_u16(writer, value.character_id.unwrap())?;
887    emit_le_u16(writer, value.depth)?;
888    emit_matrix(writer, &value.matrix.unwrap())?;
889    if let Some(ref color_transform_with_alpha) = &value.color_transform {
890      let color_transform = ast::ColorTransform {
891        red_mult: color_transform_with_alpha.red_mult,
892        green_mult: color_transform_with_alpha.green_mult,
893        blue_mult: color_transform_with_alpha.blue_mult,
894        red_add: color_transform_with_alpha.red_add,
895        green_add: color_transform_with_alpha.green_add,
896        blue_add: color_transform_with_alpha.blue_add,
897      };
898      emit_color_transform(writer, &color_transform)?;
899    }
900    Ok(PlaceObjectVersion::PlaceObject1)
901  }
902}
903
904pub fn emit_protect<W: io::Write>(writer: &mut W, value: &ast::tags::Protect) -> io::Result<()> {
905  if !value.password.is_empty() {
906    emit_c_string(writer, &value.password)?;
907  }
908  Ok(())
909}
910
911pub enum RemoveObjectVersion {
912  RemoveObject1,
913  RemoveObject2,
914}
915
916pub fn emit_remove_object_any<W: io::Write>(
917  writer: &mut W,
918  value: &ast::tags::RemoveObject,
919) -> io::Result<RemoveObjectVersion> {
920  if let Some(character_id) = value.character_id {
921    emit_le_u16(writer, character_id)?;
922    emit_le_u16(writer, value.depth)?;
923    Ok(RemoveObjectVersion::RemoveObject1)
924  } else {
925    emit_le_u16(writer, value.depth)?;
926    Ok(RemoveObjectVersion::RemoveObject2)
927  }
928}
929
930pub fn emit_set_background_color<W: io::Write>(
931  writer: &mut W,
932  value: &ast::tags::SetBackgroundColor,
933) -> io::Result<()> {
934  emit_s_rgb8(writer, value.color)
935}
936
937pub fn emit_start_sound<W: io::Write>(writer: &mut W, value: &ast::tags::StartSound) -> io::Result<()> {
938  emit_le_u16(writer, value.sound_id)?;
939  emit_sound_info(writer, &value.sound_info)?;
940  Ok(())
941}
942
943pub fn emit_symbol_class<W: io::Write>(writer: &mut W, value: &ast::tags::SymbolClass) -> io::Result<()> {
944  let symbol_count: u16 = value.symbols.len().try_into().unwrap();
945  emit_le_u16(writer, symbol_count)?;
946  for symbol in &value.symbols {
947    emit_le_u16(writer, symbol.id)?;
948    emit_c_string(writer, &symbol.name)?;
949  }
950  Ok(())
951}
952
953pub fn emit_raw_body<W: io::Write>(writer: &mut W, value: &ast::tags::RawBody) -> io::Result<()> {
954  writer.write_all(&value.data)
955}