diff --git a/src/read.rs b/src/read.rs index 653192355..be42735f1 100644 --- a/src/read.rs +++ b/src/read.rs @@ -500,6 +500,8 @@ impl Reader { Some(TagCode::DefineFontInfo) => tag_reader.read_define_font_info(1)?, Some(TagCode::DefineFontInfo2) => tag_reader.read_define_font_info(2)?, Some(TagCode::DefineFontName) => tag_reader.read_define_font_name()?, + Some(TagCode::DefineMorphShape) => tag_reader.read_define_morph_shape(1)?, + Some(TagCode::DefineMorphShape2) => tag_reader.read_define_morph_shape(2)?, Some(TagCode::DefineShape) => tag_reader.read_define_shape(1)?, Some(TagCode::DefineShape2) => tag_reader.read_define_shape(2)?, Some(TagCode::DefineShape3) => tag_reader.read_define_shape(3)?, @@ -1174,6 +1176,265 @@ impl Reader { }) } + fn read_define_morph_shape(&mut self, shape_version: u8) -> Result { + let id = self.read_character_id()?; + let start_shape_bounds = self.read_rectangle()?; + let end_shape_bounds = self.read_rectangle()?; + let (start_edge_bounds, end_edge_bounds, + has_non_scaling_strokes, has_scaling_strokes) = + if shape_version >= 2 { + let start_edge_bounds = self.read_rectangle()?; + let end_edge_bounds = self.read_rectangle()?; + let flags = self.read_u8()?; + (start_edge_bounds, end_edge_bounds, flags & 0b10 != 0, flags & 0b1 != 0) + } else { + (start_shape_bounds.clone(), end_shape_bounds.clone(), true, false) + }; + + self.read_u32()?; // Offset to EndEdges. + + let num_fill_styles = match self.read_u8()? { + 0xff => self.read_u16()? as usize, + n => n as usize, + }; + let mut start_fill_styles = Vec::with_capacity(num_fill_styles); + let mut end_fill_styles = Vec::with_capacity(num_fill_styles); + for _ in 0..num_fill_styles { + let (start, end) = self.read_morph_fill_style(shape_version)?; + start_fill_styles.push(start); + end_fill_styles.push(end); + } + + let num_line_styles = match self.read_u8()? { + 0xff => self.read_u16()? as usize, + n => n as usize, + }; + let mut start_line_styles = Vec::with_capacity(num_line_styles); + let mut end_line_styles = Vec::with_capacity(num_line_styles); + for _ in 0..num_line_styles { + let (start, end) = self.read_morph_line_style(shape_version)?; + start_line_styles.push(start); + end_line_styles.push(end); + } + + // TODO(Herschel): Add read_shape + self.num_fill_bits = self.read_ubits(4)? as u8; + self.num_line_bits = self.read_ubits(4)? as u8; + let mut start_shape = Vec::new(); + while let Some(record) = self.read_shape_record(1)? { + start_shape.push(record); + } + + self.byte_align(); + let mut end_shape = Vec::new(); + self.read_u8()?; // NumFillBits and NumLineBits are written as 0 for the end shape. + while let Some(record) = self.read_shape_record(1)? { + end_shape.push(record); + } + Ok(Tag::DefineMorphShape(Box::new(DefineMorphShape { + id: id, + version: shape_version, + has_non_scaling_strokes: has_non_scaling_strokes, + has_scaling_strokes: has_scaling_strokes, + start: MorphShape { + shape_bounds: start_shape_bounds, + edge_bounds: start_edge_bounds, + shape: start_shape, + fill_styles: start_fill_styles, + line_styles: start_line_styles, + }, + end: MorphShape { + shape_bounds: end_shape_bounds, + edge_bounds: end_edge_bounds, + shape: end_shape, + fill_styles: end_fill_styles, + line_styles: end_line_styles, + }, + }))) + } + + fn read_morph_line_style(&mut self, shape_version: u8) -> Result<(LineStyle, LineStyle)> { + if shape_version < 2 { + let start_width = self.read_u16()?; + let end_width = self.read_u16()?; + let start_color = self.read_rgba()?; + let end_color = self.read_rgba()?; + + Ok(( + LineStyle::new_v1(start_width, start_color), + LineStyle::new_v1(end_width, end_color) + )) + } else { + // MorphLineStyle2 in DefineMorphShape2. + let start_width = self.read_u16()?; + let end_width = self.read_u16()?; + let start_cap = match self.read_ubits(2)? { + 0 => LineCapStyle::Round, + 1 => LineCapStyle::None, + 2 => LineCapStyle::Square, + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid line cap type.")), + }; + let join_style_id = self.read_ubits(2)?; + let has_fill = self.read_bit()?; + let allow_scale_x = !self.read_bit()?; + let allow_scale_y = !self.read_bit()?; + let is_pixel_hinted = self.read_bit()?; + self.read_ubits(5)?; + let allow_close = !self.read_bit()?; + let end_cap = match self.read_ubits(2)? { + 0 => LineCapStyle::Round, + 1 => LineCapStyle::None, + 2 => LineCapStyle::Square, + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid line cap type.")), + }; + let join_style = match join_style_id { + 0 => LineJoinStyle::Round, + 1 => LineJoinStyle::Bevel, + 2 => LineJoinStyle::Miter(self.read_fixed8()?), + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid line cap type.")), + }; + let (start_color, end_color) = if !has_fill { + (self.read_rgba()?, self.read_rgba()?) + } else { + (Color { r: 0, g: 0, b: 0, a: 0 }, Color { r: 0, g: 0, b: 0, a: 0 }) + }; + let (start_fill_style, end_fill_style) = if has_fill { + let (start, end) = self.read_morph_fill_style(shape_version)?; + (Some(start), Some(end)) + } else { + (None, None) + }; + Ok(( + LineStyle { + width: start_width, + color: start_color, + start_cap: start_cap, + end_cap: end_cap, + join_style: join_style, + allow_scale_x: allow_scale_x, + allow_scale_y: allow_scale_y, + is_pixel_hinted: is_pixel_hinted, + allow_close: allow_close, + fill_style: start_fill_style, + }, + LineStyle { + width: end_width, + color: end_color, + start_cap: start_cap, + end_cap: end_cap, + join_style: join_style, + allow_scale_x: allow_scale_x, + allow_scale_y: allow_scale_y, + is_pixel_hinted: is_pixel_hinted, + allow_close: allow_close, + fill_style: end_fill_style, + } + )) + } + } + + fn read_morph_fill_style(&mut self, shape_version: u8) -> Result<(FillStyle, FillStyle)> { + let fill_style_type = self.read_u8()?; + let fill_style = match fill_style_type { + 0x00 => { + let start_color = self.read_rgba()?; + let end_color = self.read_rgba()?; + (FillStyle::Color(start_color), FillStyle::Color(end_color)) + }, + + 0x10 => { + let (start_gradient, end_gradient) = self.read_morph_gradient()?; + ( + FillStyle::LinearGradient(start_gradient), + FillStyle::LinearGradient(end_gradient) + ) + }, + + 0x12 => { + let (start_gradient, end_gradient) = self.read_morph_gradient()?; + ( + FillStyle::RadialGradient(start_gradient), + FillStyle::RadialGradient(end_gradient) + ) + }, + + 0x13 => { + if self.version < 8 || shape_version < 2 { + return Err(Error::new(ErrorKind::InvalidData, + "Focal gradients are only supported in SWF version 8 \ + or higher.")); + } + // TODO(Herschel): How is focal_point stored? + let (start_gradient, end_gradient) = self.read_morph_gradient()?; + let start_focal_point = self.read_fixed8()?; + let end_focal_point = self.read_fixed8()?; + ( + FillStyle::FocalGradient { + gradient: start_gradient, + focal_point: start_focal_point, + }, + FillStyle::FocalGradient { + gradient: end_gradient, + focal_point: end_focal_point, + } + ) + }, + + 0x40...0x43 => { + let id = self.read_character_id()?; + ( + FillStyle::Bitmap { + id: id, + matrix: self.read_matrix()?, + is_smoothed: (fill_style_type & 0b10) == 0, + is_repeating: (fill_style_type & 0b01) == 0, + }, + FillStyle::Bitmap { + id: id, + matrix: self.read_matrix()?, + is_smoothed: (fill_style_type & 0b10) == 0, + is_repeating: (fill_style_type & 0b01) == 0, + }, + ) + } + + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid fill style.")), + }; + Ok(fill_style) + } + + fn read_morph_gradient(&mut self) -> Result<(Gradient, Gradient)> { + let start_matrix = self.read_matrix()?; + let end_matrix = self.read_matrix()?; + let num_records = self.read_u8()? as usize; + let mut start_records = Vec::with_capacity(num_records); + let mut end_records = Vec::with_capacity(num_records); + for _ in 0..num_records { + start_records.push(GradientRecord { + ratio: self.read_u8()?, + color: self.read_rgba()?, + }); + end_records.push(GradientRecord { + ratio: self.read_u8()?, + color: self.read_rgba()?, + }); + } + Ok(( + Gradient { + matrix: start_matrix, + spread: GradientSpread::Pad, // TODO(Herschel): What are the defaults? + interpolation: GradientInterpolation::RGB, + records: start_records, + }, + Gradient { + matrix: end_matrix, + spread: GradientSpread::Pad, // TODO(Herschel): What are the defaults? + interpolation: GradientInterpolation::RGB, + records: end_records, + } + )) + } + fn read_define_shape(&mut self, version: u8) -> Result { let id = self.read_u16()?; let shape_bounds = self.read_rectangle()?; diff --git a/src/test_data.rs b/src/test_data.rs index d6203fb9e..ec90951fa 100644 --- a/src/test_data.rs +++ b/src/test_data.rs @@ -534,6 +534,238 @@ pub fn tag_tests() -> Vec { vec![ read_tag_bytes_from_file("tests/swfs/DefineFont4-CC.swf", TagCode::DefineFontName) ), + ( + 3, + Tag::DefineMorphShape(Box::new(DefineMorphShape { + version: 1, + id: 1, + has_non_scaling_strokes: true, + has_scaling_strokes: false, + start: MorphShape { + shape_bounds: Rectangle { x_min: 15.0, x_max: 65.0, y_min: 15.0, y_max: 65.0 }, + edge_bounds: Rectangle { x_min: 15.0, x_max: 65.0, y_min: 15.0, y_max: 65.0 }, + fill_styles: vec![ + FillStyle::LinearGradient( + Gradient { + matrix: Matrix { + translate_x: 40.0, + translate_y: 40.0, + scale_x: 0.024429321, + scale_y: 0.024429321, + rotate_skew_0: 0.024429321, + rotate_skew_1: -0.024429321 + }, + spread: GradientSpread::Pad, + interpolation: GradientInterpolation::RGB, + records: vec![ + GradientRecord { ratio: 0, color: Color { r: 255, g: 255, b: 255, a: 255 } }, + GradientRecord { ratio: 255, color: Color { r: 0, g: 0, b: 0, a: 255 } }, + ] + } + ) + ], + line_styles: vec![LineStyle::new_v1(200, Color { r: 0, g: 255, b: 0, a: 255 })], + shape: vec![ + ShapeRecord::StyleChange(StyleChangeData { + move_to: Some((20.0, 20.0)), + fill_style_0: None, + fill_style_1: None, + line_style: Some(1), + new_styles: None + }), + ShapeRecord::StraightEdge { delta_x: 40.0, delta_y: 0.0 }, + ShapeRecord::StraightEdge { delta_x: 0.0, delta_y: 40.0 }, + ShapeRecord::StraightEdge { delta_x: -40.0, delta_y: 0.0 }, + ShapeRecord::StraightEdge { delta_x: 0.0, delta_y: -40.0 }, + ShapeRecord::StyleChange(StyleChangeData { + move_to: None, + fill_style_0: Some(1), + fill_style_1: None, + line_style: None, + new_styles: None + }), + ShapeRecord::StraightEdge { delta_x: 40.0, delta_y: 0.0 }, + ShapeRecord::StraightEdge { delta_x: 0.0, delta_y: 40.0 }, + ShapeRecord::StraightEdge { delta_x: -40.0, delta_y: 0.0 }, + ShapeRecord::StraightEdge { delta_x: 0.0, delta_y: -40.0 }, + ] + }, + end: MorphShape { + shape_bounds: Rectangle { x_min: 19.0, x_max: 75.05, y_min: 8.35, y_max: 61.0 }, + edge_bounds: Rectangle { x_min: 19.0, x_max: 75.05, y_min: 8.35, y_max: 61.0 }, + fill_styles: vec![ + FillStyle::LinearGradient(Gradient { + matrix: Matrix { + translate_x: 48.4, + translate_y: 34.65, + scale_x: 0.0058898926, + scale_y: 0.030914307, + rotate_skew_0: 0.0, + rotate_skew_1: 0.0 + }, + spread: GradientSpread::Pad, + interpolation: GradientInterpolation::RGB, + records: vec![ + GradientRecord { ratio: 56, color: Color { r: 255, g: 0, b: 0, a: 255 } }, + GradientRecord { ratio: 157, color: Color { r: 0, g: 0, b: 255, a: 255 } } + ] + }) + ], + line_styles: vec![LineStyle::new_v1( 40, Color { r: 255, g: 255, b: 0, a: 255 } )], + shape: vec![ + ShapeRecord::StyleChange(StyleChangeData { + move_to: Some((20.0, 60.0)), + fill_style_0: None, + fill_style_1: None, + line_style: None, + new_styles: None + }), + ShapeRecord::StraightEdge { delta_x: 17.4, delta_y: -50.65 }, + ShapeRecord::StraightEdge { delta_x: 22.6, delta_y: 10.65 }, + ShapeRecord::CurvedEdge { control_delta_x: 28.15, control_delta_y: 19.1, anchor_delta_x: -28.15, anchor_delta_y: 20.9 }, + ShapeRecord::CurvedEdge { control_delta_x: -19.05, control_delta_y: -22.0, anchor_delta_x: -20.95, anchor_delta_y: 22.0 }, + ShapeRecord::StraightEdge { delta_x: 17.4, delta_y: -50.65 }, + ShapeRecord::StraightEdge { delta_x: 22.6, delta_y: 10.65 }, + ShapeRecord::CurvedEdge { control_delta_x: 28.15, control_delta_y: 19.1, anchor_delta_x: -28.15, anchor_delta_y: 20.9 }, + ShapeRecord::CurvedEdge { control_delta_x: -19.05, control_delta_y: -22.0, anchor_delta_x: -20.95, anchor_delta_y: 22.0 } + ] + } + })), + read_tag_bytes_from_file("tests/swfs/DefineMorphShape-MX.swf", TagCode::DefineMorphShape) + ), + + ( + 8, + Tag::DefineMorphShape(Box::new(DefineMorphShape { + version: 2, + id: 1, + has_non_scaling_strokes: false, + has_scaling_strokes: true, + start: MorphShape { + shape_bounds: Rectangle { x_min: 15.0, x_max: 225.0, y_min: 15.0, y_max: 225.0 }, + edge_bounds: Rectangle { x_min: 20.0, x_max: 220.0, y_min: 20.0, y_max: 220.0 }, + fill_styles: vec![ + FillStyle::FocalGradient { + gradient: Gradient { + matrix: Matrix { + translate_x: 116.05, + translate_y: 135.05, + scale_x: 0.11468506, + scale_y: 0.18927002, + rotate_skew_0: 0.0, + rotate_skew_1: 0.0 + }, + spread: GradientSpread::Pad, + interpolation: GradientInterpolation::RGB, + records: vec![ + GradientRecord { ratio: 0, color: Color { r: 255, g: 0, b: 0, a: 255 } }, + GradientRecord { ratio: 70, color: Color { r: 255, g: 0, b: 255, a: 255 } }, + GradientRecord { ratio: 255, color: Color { r: 0, g: 0, b: 0, a: 255 } } + ] + }, + focal_point: 0.97265625 + } + ], + line_styles: vec![ + LineStyle { + width: 200, + color: Color { r: 0, g: 255, b: 0, a: 255 }, + start_cap: LineCapStyle::Round, + end_cap: LineCapStyle::Round, + join_style: LineJoinStyle::Round, + fill_style: None, + allow_scale_x: true, + allow_scale_y: true, + is_pixel_hinted: false, + allow_close: true + } + ], + shape: vec![ + ShapeRecord::StyleChange(StyleChangeData { + move_to: Some((20.0, 20.0)), + fill_style_0: None, + fill_style_1: None, + line_style: Some(1), + new_styles: None + }), + ShapeRecord::StraightEdge { delta_x: 200.0, delta_y: 0.0 }, + ShapeRecord::StraightEdge { delta_x: 0.0, delta_y: 200.0 }, + ShapeRecord::StraightEdge { delta_x: -200.0, delta_y: 0.0 }, + ShapeRecord::StraightEdge { delta_x: 0.0, delta_y: -200.0 }, + ShapeRecord::StyleChange(StyleChangeData { + move_to: None, + fill_style_0: Some(1), + fill_style_1: None, + line_style: None, + new_styles: None + }), + ShapeRecord::StraightEdge { delta_x: 200.0, delta_y: 0.0 }, + ShapeRecord::StraightEdge { delta_x: 0.0, delta_y: 200.0 }, + ShapeRecord::StraightEdge { delta_x: -200.0, delta_y: 0.0 }, + ShapeRecord::StraightEdge { delta_x: 0.0, delta_y: -200.0 } + ] + }, + end: MorphShape { + shape_bounds: Rectangle { x_min: 25.0, x_max: 212.05, y_min: 15.35, y_max: 148.35 }, + edge_bounds: Rectangle { x_min: 26.0, x_max: 211.05, y_min: 16.35, y_max: 147.35 }, + fill_styles: vec![ + FillStyle::FocalGradient { + gradient: Gradient { + matrix: Matrix { + translate_x: 164.0, + translate_y: 150.05, + scale_x: 0.036087036, + scale_y: 0.041992188, + rotate_skew_0: 0.1347351, + rotate_skew_1: -0.15675354 + }, + spread: GradientSpread::Pad, + interpolation: GradientInterpolation::RGB, + records: vec![ + GradientRecord { ratio: 0, color: Color { r: 0, g: 255, b: 255, a: 255 } }, + GradientRecord { ratio: 183, color: Color { r: 0, g: 255, b: 0, a: 255 } }, + GradientRecord { ratio: 226, color: Color { r: 255, g: 0, b: 255, a: 255 } } + ] + }, + focal_point: -0.9921875 + } + ], + line_styles: vec![ + LineStyle { + width: 40, + color: Color { r: 255, g: 255, b:0, a: 255 }, + start_cap: LineCapStyle::Round, + end_cap: LineCapStyle::Round, + join_style: LineJoinStyle::Round, + fill_style: None, + allow_scale_x: true, + allow_scale_y: true, + is_pixel_hinted: false, + allow_close: true + } + ], + shape: vec![ + ShapeRecord::StyleChange(StyleChangeData { + move_to: Some((26.0, 147.35)), + fill_style_0: None, + fill_style_1: None, + line_style: None, + new_styles: None + }), + ShapeRecord::StraightEdge { delta_x: 95.0, delta_y: -131.0 }, + ShapeRecord::StraightEdge { delta_x: 59.0, delta_y: 17.0 }, + ShapeRecord::CurvedEdge { control_delta_x: 62.1, control_delta_y: 57.0, anchor_delta_x: -62.1, anchor_delta_y: 57.0 }, + ShapeRecord::CurvedEdge { control_delta_x: -73.2, control_delta_y: -70.6, anchor_delta_x: -80.8, anchor_delta_y: 70.6 }, + ShapeRecord::StraightEdge { delta_x: 95.0, delta_y: -131.0 }, + ShapeRecord::StraightEdge { delta_x: 59.0, delta_y: 17.0 }, + ShapeRecord::CurvedEdge { control_delta_x: 62.1, control_delta_y: 57.0, anchor_delta_x: -62.1, anchor_delta_y: 57.0 }, + ShapeRecord::CurvedEdge { control_delta_x: -73.2, control_delta_y: -70.6, anchor_delta_x: -80.8, anchor_delta_y: 70.6 } + ] + } + })), + read_tag_bytes_from_file("tests/swfs/DefineMorphShape2-CC.swf", TagCode::DefineMorphShape2) + ), + ( 8, Tag::DefineScalingGrid { diff --git a/src/types.rs b/src/types.rs index 5b07cfeb0..ed2108ef8 100644 --- a/src/types.rs +++ b/src/types.rs @@ -318,6 +318,7 @@ pub enum Tag { DefineFontAlignZones { id: CharacterId, thickness: FontThickness, zones: Vec }, DefineFontInfo(Box), DefineFontName { id: CharacterId, name: String, copyright_info: String }, + DefineMorphShape(Box), DefineScalingGrid { id: CharacterId, splitter_rect: Rectangle }, DefineShape(Shape), DefineSound(Box), @@ -624,6 +625,25 @@ pub enum ButtonActionCondition { KeyPress } +#[derive(Clone, Debug, PartialEq)] +pub struct DefineMorphShape { + pub version: u8, + pub id: CharacterId, + pub has_non_scaling_strokes: bool, + pub has_scaling_strokes: bool, + pub start: MorphShape, + pub end: MorphShape, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct MorphShape { + pub shape_bounds: Rectangle, + pub edge_bounds: Rectangle, + pub fill_styles: Vec, + pub line_styles: Vec, + pub shape: Vec, +} + #[derive(Clone, Debug, PartialEq)] pub struct FontV1 { pub id: CharacterId, diff --git a/src/write.rs b/src/write.rs index cbadd4a52..ea61d800e 100644 --- a/src/write.rs +++ b/src/write.rs @@ -628,6 +628,8 @@ impl Writer { self.write_c_string(copyright_info)?; }, + &Tag::DefineMorphShape(ref define_morph_shape) => self.write_define_morph_shape(define_morph_shape)?, + &Tag::DefineScalingGrid { id, ref splitter_rect } => { let mut buf = Vec::new(); { @@ -912,6 +914,228 @@ impl Writer { Ok(()) } + fn write_define_morph_shape(&mut self, data: &DefineMorphShape) -> Result<()> { + if data.start.fill_styles.len() != data.end.fill_styles.len() || + data.start.line_styles.len() != data.end.line_styles.len() { + return Err(Error::new(ErrorKind::InvalidData, + "Start and end state of a morph shape must have the same number of styles.")); + } + + let num_fill_styles = data.start.fill_styles.len(); + let num_line_styles = data.start.line_styles.len(); + let num_fill_bits = count_ubits(num_fill_styles as u32); + let num_line_bits = count_ubits(num_line_styles as u32); + + // Need to write styles first, to calculate offset to EndEdges. + let mut start_buf = Vec::new(); + { + let mut writer = Writer::new(&mut start_buf, self.version); + + // Styles + // TODO(Herschel): Make fn write_style_len. Check version. + if num_fill_styles >= 0xff { + writer.write_u8(0xff)?; + writer.write_u16(num_fill_styles as u16)?; + } else { + writer.write_u8(num_fill_styles as u8)?; + } + for (start, end) in data.start.fill_styles.iter().zip(data.end.fill_styles.iter()) { + writer.write_morph_fill_style(start, end, data.version)?; + } + + if num_line_styles >= 0xff { + writer.write_u8(0xff)?; + writer.write_u16(num_line_styles as u16)?; + } else { + writer.write_u8(num_line_styles as u8)?; + } + for (start, end) in data.start.line_styles.iter().zip(data.end.line_styles.iter()) { + writer.write_morph_line_style(start, end, data.version)?; + } + + // TODO(Herschel): Make fn write_shape. + writer.write_ubits(4, num_fill_bits as u32)?; + writer.write_ubits(4, num_line_bits as u32)?; + writer.num_fill_bits = num_fill_bits; + writer.num_line_bits = num_line_bits; + for shape_record in &data.start.shape { + writer.write_shape_record(shape_record, 1)?; + } + // End shape record. + writer.write_ubits(6, 0)?; + writer.flush_bits()?; + } + + let mut buf = Vec::new(); + { + let mut writer = Writer::new(&mut buf, self.version); + writer.write_character_id(data.id)?; + writer.write_rectangle(&data.start.shape_bounds)?; + writer.write_rectangle(&data.end.shape_bounds)?; + if data.version >= 2 { + writer.write_rectangle(&data.start.edge_bounds)?; + writer.write_rectangle(&data.end.edge_bounds)?; + writer.write_u8( + if data.has_non_scaling_strokes { 0b10 } else { 0 } | + if data.has_scaling_strokes { 0b1 } else { 0 } + )?; + } + + // Offset to EndEdges. + writer.write_u32(start_buf.len() as u32)?; + + writer.output.write_all(&start_buf)?; + + // EndEdges. + writer.write_u8(0)?; // NumFillBits and NumLineBits are written as 0 for the end shape. + writer.num_fill_bits = num_fill_bits; + writer.num_line_bits = num_line_bits; + for shape_record in &data.end.shape { + writer.write_shape_record(shape_record, 1)?; + } + // End shape record. + writer.write_ubits(6, 0)?; + writer.flush_bits()?; + } + + let tag_code = if data.version == 1 { TagCode::DefineMorphShape } else { TagCode::DefineMorphShape2 }; + self.write_tag_header(tag_code, buf.len() as u32)?; + self.output.write_all(&buf)?; + Ok(()) + } + + fn write_morph_fill_style(&mut self, start: &FillStyle, end: &FillStyle, shape_version: u8) -> Result<()> { + match (start, end) { + (&FillStyle::Color(ref start_color), &FillStyle::Color(ref end_color)) => { + self.write_u8(0x00)?; // Solid color. + self.write_rgba(start_color)?; + self.write_rgba(end_color)?; + }, + + (&FillStyle::LinearGradient(ref start_gradient), &FillStyle::LinearGradient(ref end_gradient)) => { + self.write_u8(0x10)?; // Linear gradient. + self.write_morph_gradient(start_gradient, end_gradient)?; + }, + + (&FillStyle::RadialGradient(ref start_gradient), &FillStyle::RadialGradient(ref end_gradient)) => { + self.write_u8(0x12)?; // Linear gradient. + self.write_morph_gradient(start_gradient, end_gradient)?; + }, + + ( + &FillStyle::FocalGradient { gradient: ref start_gradient, focal_point: start_focal_point }, + &FillStyle::FocalGradient { gradient: ref end_gradient, focal_point: end_focal_point } + ) => { + if self.version < 8 || shape_version < 2 { + return Err(Error::new(ErrorKind::InvalidData, + "Focal gradients are only support in SWF version 8 \ + and higher.")); + } + + self.write_u8(0x13)?; // Focal gradient. + self.write_morph_gradient(start_gradient, end_gradient)?; + self.write_fixed8(start_focal_point)?; + self.write_fixed8(end_focal_point)?; + }, + + ( + &FillStyle::Bitmap { id, matrix: ref start_matrix, is_smoothed, is_repeating }, + &FillStyle::Bitmap { id: end_id, matrix: ref end_matrix, is_smoothed: end_is_smoothed, is_repeating: end_is_repeating } + ) if id == end_id && is_smoothed == end_is_smoothed || is_repeating == end_is_repeating => { + let fill_style_type = match (is_smoothed, is_repeating) { + (true, true) => 0x40, + (true, false) => 0x41, + (false, true) => 0x42, + (false, false) => 0x43, + }; + self.write_u8(fill_style_type)?; + self.write_u16(id)?; + self.write_matrix(start_matrix)?; + self.write_matrix(end_matrix)?; + }, + + _ => return Err(Error::new(ErrorKind::InvalidData, + "Morph start and end fill styles must be the same variant.")), + } + Ok(()) + } + + fn write_morph_gradient(&mut self, start: &Gradient, end: &Gradient) -> Result<()> { + self.write_matrix(&start.matrix)?; + self.write_matrix(&end.matrix)?; + if start.records.len() != end.records.len() { + return Err(Error::new(ErrorKind::InvalidData, + "Morph start and end gradient must have the same amount of records.")); + } + self.write_u8(start.records.len() as u8)?; + for (start_record, end_record) in start.records.iter().zip(end.records.iter()) { + self.write_u8(start_record.ratio)?; + self.write_rgba(&start_record.color)?; + self.write_u8(end_record.ratio)?; + self.write_rgba(&end_record.color)?; + } + Ok(()) + } + + fn write_morph_line_style(&mut self, start: &LineStyle, end: &LineStyle, shape_version: u8) -> Result<()> { + if shape_version < 2 { + self.write_u16(start.width)?; + self.write_u16(end.width)?; + self.write_rgba(&start.color)?; + self.write_rgba(&end.color)?; + } else { + if start.start_cap != end.start_cap || start.join_style != end.join_style || + start.allow_scale_x != end.allow_scale_x || start.allow_scale_y != end.allow_scale_y || + start.is_pixel_hinted != end.is_pixel_hinted || start.allow_close != end.allow_close || + start.end_cap != end.end_cap { + return Err(Error::new(ErrorKind::InvalidData, + "Morph start and end line styles must have the same join parameters.")); + } + + self.write_u16(start.width)?; + self.write_u16(end.width)?; + + // MorphLineStyle2 + self.write_ubits(2, match start.start_cap { + LineCapStyle::Round => 0, + LineCapStyle::None => 1, + LineCapStyle::Square => 2, + })?; + self.write_ubits(2, match start.join_style { + LineJoinStyle::Round => 0, + LineJoinStyle::Bevel => 1, + LineJoinStyle::Miter(_) => 2, + })?; + self.write_bit(start.fill_style.is_some())?; + self.write_bit(!start.allow_scale_x)?; + self.write_bit(!start.allow_scale_y)?; + self.write_bit(start.is_pixel_hinted)?; + self.write_ubits(5, 0)?; + self.write_bit(!start.allow_close)?; + self.write_ubits(2, match start.end_cap { + LineCapStyle::Round => 0, + LineCapStyle::None => 1, + LineCapStyle::Square => 2, + })?; + if let LineJoinStyle::Miter(miter_factor) = start.join_style { + self.write_fixed8(miter_factor)?; + } + match (&start.fill_style, &end.fill_style) { + (&None, &None) => { + self.write_rgba(&start.color)?; + self.write_rgba(&end.color)?; + }, + + (&Some(ref start_fill), &Some(ref end_fill)) => + self.write_morph_fill_style(start_fill, end_fill, shape_version)?, + + _ => return Err(Error::new(ErrorKind::InvalidData, + "Morph start and end line styles must both have fill styles.")), + } + } + Ok(()) + } + fn write_define_scene_and_frame_label_data(&mut self, scenes: &Vec, frame_labels: &Vec) diff --git a/tests/swfs/DefineMorphShape-MX.fla b/tests/swfs/DefineMorphShape-MX.fla new file mode 100644 index 000000000..fae79cf97 Binary files /dev/null and b/tests/swfs/DefineMorphShape-MX.fla differ diff --git a/tests/swfs/DefineMorphShape-MX.swf b/tests/swfs/DefineMorphShape-MX.swf new file mode 100644 index 000000000..08f0466bb Binary files /dev/null and b/tests/swfs/DefineMorphShape-MX.swf differ diff --git a/tests/swfs/DefineMorphShape2-CC.fla b/tests/swfs/DefineMorphShape2-CC.fla new file mode 100644 index 000000000..5931ae741 Binary files /dev/null and b/tests/swfs/DefineMorphShape2-CC.fla differ diff --git a/tests/swfs/DefineMorphShape2-CC.swf b/tests/swfs/DefineMorphShape2-CC.swf new file mode 100644 index 000000000..2e9d7b2f8 Binary files /dev/null and b/tests/swfs/DefineMorphShape2-CC.swf differ