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 let is_long_required = matches!(
45 value.code,
46 6 | 21 | 35 | 20 | 36 | 90 | 19 );
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 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 | (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) }
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 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; 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 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 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 | (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 #[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 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 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 | (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 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; 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}