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