swf: Clean up LineStyle

* Remove LineStyle::color, instead using fill_style with
   FillStyle::Color to indicate solid color.
 * Store `flags` in the struct instead of separate bools/values.
 * Add getters/setters for ease of use.
 * Add builder-style methods for setting LineStyle properties.
 * Fix misnamed ALLOW_CLOSE flag to NO_CLOSE.
This commit is contained in:
Mike Welsh 2022-04-09 11:17:40 -07:00
parent 99d8a5bff3
commit 146b8adc68
13 changed files with 392 additions and 420 deletions

View File

@ -291,21 +291,20 @@ fn line_style<'gc>(
Some(v) if v == b"bevel" => LineJoinStyle::Bevel,
_ => LineJoinStyle::Round,
};
let line_style = LineStyle::new()
.with_width(width)
.with_color(color)
.with_start_cap(cap_style)
.with_end_cap(cap_style)
.with_join_style(join_style)
.with_allow_scale_x(allow_scale_x)
.with_allow_scale_y(allow_scale_y)
.with_is_pixel_hinted(is_pixel_hinted)
.with_allow_close(false);
movie_clip
.as_drawing(activation.context.gc_context)
.unwrap()
.set_line_style(Some(LineStyle {
width,
color,
start_cap: cap_style,
end_cap: cap_style,
join_style,
fill_style: None,
allow_scale_x,
allow_scale_y,
is_pixel_hinted,
allow_close: false,
}));
.set_line_style(Some(line_style));
} else {
movie_clip
.as_drawing(activation.context.gc_context)

View File

@ -248,18 +248,16 @@ fn line_style<'gc>(
let join_style = joints_to_join_style(activation, joints, miter_limit)?;
let (allow_scale_x, allow_scale_y) = scale_mode_to_allow_scale_bits(&scale_mode)?;
let line_style = LineStyle {
width,
color,
start_cap: caps,
end_cap: caps,
join_style,
fill_style: None,
allow_scale_x,
allow_scale_y,
is_pixel_hinted,
allow_close: true,
};
let line_style = LineStyle::new()
.with_width(width)
.with_color(color)
.with_start_cap(caps)
.with_end_cap(caps)
.with_join_style(join_style)
.with_allow_scale_x(allow_scale_x)
.with_allow_scale_y(allow_scale_y)
.with_is_pixel_hinted(is_pixel_hinted)
.with_allow_close(false);
if let Some(mut draw) = this.as_drawing(activation.context.gc_context) {
draw.set_line_style(Some(line_style));

View File

@ -705,10 +705,11 @@ impl<'gc> EditText<'gc> {
let background_color = write.background_color;
if write.has_border {
write.drawing.set_line_style(Some(swf::LineStyle::new_v1(
Twips::new(1),
swf::Color::from_rgb(border_color, 0xFF),
)));
write.drawing.set_line_style(Some(
swf::LineStyle::new()
.with_width(Twips::new(1))
.with_color(swf::Color::from_rgb(border_color, 0xFF)),
));
} else {
write.drawing.set_line_style(None);
}

View File

@ -204,17 +204,11 @@ impl MorphShapeStatic {
.line_styles
.iter()
.zip(self.end.line_styles.iter())
.map(|(start, end)| LineStyle {
width: lerp_twips(start.width, end.width, a, b),
color: lerp_color(&start.color, &end.color, a, b),
start_cap: start.start_cap,
end_cap: start.end_cap,
join_style: start.join_style,
fill_style: None,
allow_scale_x: start.allow_scale_x,
allow_scale_y: start.allow_scale_y,
is_pixel_hinted: start.is_pixel_hinted,
allow_close: start.allow_close,
.map(|(start, end)| {
start
.clone()
.with_width(lerp_twips(start.width(), end.width(), a, b))
.with_fill_style(lerp_fill(start.fill_style(), end.fill_style(), a, b))
})
.collect();

View File

@ -177,7 +177,7 @@ impl Drawing {
// Add command to current line.
let stroke_width = if let Some(line) = &mut self.current_line {
line.commands.push(command.clone());
line.style.width
line.style.width()
} else {
Twips::ZERO
};
@ -310,7 +310,7 @@ impl Drawing {
DrawingPath::Line(line) => {
if shape_utils::draw_command_stroke_hit_test(
&line.commands,
line.style.width,
line.style.width(),
point,
local_matrix,
) {
@ -330,7 +330,7 @@ impl Drawing {
for line in &self.pending_lines {
if shape_utils::draw_command_stroke_hit_test(
&line.commands,
line.style.width,
line.style.width(),
point,
local_matrix,
) {
@ -341,7 +341,7 @@ impl Drawing {
if let Some(line) = &self.current_line {
if shape_utils::draw_command_stroke_hit_test(
&line.commands,
line.style.width,
line.style.width(),
point,
local_matrix,
) {
@ -362,7 +362,7 @@ impl Drawing {
y: self.fill_start.1,
},
],
line.style.width,
line.style.width(),
point,
local_matrix,
)

View File

@ -145,10 +145,11 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
let mut line_drawing = Drawing::new();
let mut has_underline: bool = false;
line_drawing.set_line_style(Some(swf::LineStyle::new_v1(
Twips::new(1),
swf::Color::from_rgb(0, 255),
)));
line_drawing.set_line_style(Some(
swf::LineStyle::new()
.with_width(Twips::new(1))
.with_color(swf::Color::from_rgb(0, 255)),
));
if let Some(linelist) = self.boxes.get(self.current_line..) {
for linebox in linelist {

View File

@ -761,7 +761,7 @@ pub fn shape_hit_test(
stroke_width = if i > 0 {
// Flash renders strokes with a 1px minimum width.
if let Some(line_style) = line_styles.get(i as usize - 1) {
let width = line_style.width.get() as f64;
let width = line_style.width().get() as f64;
let scaled_width = 0.5 * width.max(min_width);
Some((scaled_width, scaled_width * scaled_width))
} else {

View File

@ -1219,20 +1219,22 @@ fn swf_shape_to_svg(
// strokes end up rendering very faintly if we use the actual width of 1 twip.
// Therefore, we clamp the stroke width to 1 pixel (20 twips). This won't be 100% accurate
// if the shape is scaled, but it looks much closer to the Flash Player.
let stroke_width = std::cmp::max(style.width.get(), 20);
let stroke_width = std::cmp::max(style.width().get(), 20);
let color = if let FillStyle::Color(color) = style.fill_style() {
color.clone()
} else {
Color::from_rgba(0)
};
let mut svg_path = SvgPath::new()
.set("fill", "none")
.set(
"stroke",
format!(
"rgba({},{},{},{})",
style.color.r, style.color.g, style.color.b, style.color.a
),
format!("rgba({},{},{},{})", color.r, color.g, color.b, color.a),
)
.set("stroke-width", stroke_width)
.set(
"stroke-linecap",
match style.start_cap {
match style.start_cap() {
LineCapStyle::Round => "round",
LineCapStyle::Square => "square",
LineCapStyle::None => "butt",
@ -1240,14 +1242,14 @@ fn swf_shape_to_svg(
)
.set(
"stroke-linejoin",
match style.join_style {
match style.join_style() {
LineJoinStyle::Round => "round",
LineJoinStyle::Bevel => "bevel",
LineJoinStyle::Miter(_) => "miter",
},
);
if let LineJoinStyle::Miter(miter_limit) = style.join_style {
if let LineJoinStyle::Miter(miter_limit) = style.join_style() {
svg_path = svg_path.set("stroke-miterlimit", miter_limit.to_f32());
}
@ -1480,23 +1482,25 @@ fn swf_shape_to_canvas_commands(
// strokes end up rendering very faintly if we use the actual width of 1 twip.
// Therefore, we clamp the stroke width to 1 pixel (20 twips). This won't be 100% accurate
// if the shape is scaled, but it looks much closer to the Flash Player.
let line_width = std::cmp::max(style.width.get(), 20);
let line_width = std::cmp::max(style.width().get(), 20);
let color = if let FillStyle::Color(color) = style.fill_style() {
color.clone()
} else {
Color::from_rgba(0)
};
let stroke_style = CanvasColor(
format!(
"rgba({},{},{},{})",
style.color.r, style.color.g, style.color.b, style.color.a
),
style.color.r,
style.color.g,
style.color.b,
style.color.a,
format!("rgba({},{},{},{})", color.r, color.g, color.b, color.a),
color.r,
color.g,
color.b,
color.a,
);
let line_cap = match style.start_cap {
let line_cap = match style.start_cap() {
LineCapStyle::Round => "round",
LineCapStyle::Square => "square",
LineCapStyle::None => "butt",
};
let (line_join, miter_limit) = match style.join_style {
let (line_join, miter_limit) = match style.join_style() {
LineJoinStyle::Round => ("round", 999_999.0),
LineJoinStyle::Bevel => ("bevel", 999_999.0),
LineJoinStyle::Miter(ml) => ("miter", ml.to_f32()),

View File

@ -205,30 +205,31 @@ impl ShapeTessellator {
commands,
is_closed,
} => {
let mut buffers_builder = BuffersBuilder::new(
&mut lyon_mesh,
RuffleVertexCtor {
color: style.color.clone(),
},
);
let color = if let swf::FillStyle::Color(color) = &style.fill_style() {
color.clone()
} else {
swf::Color::from_rgba(0)
};
let mut buffers_builder =
BuffersBuilder::new(&mut lyon_mesh, RuffleVertexCtor { color });
// TODO(Herschel): 0 width indicates "hairline".
let width = (style.width.to_pixels() as f32).max(1.0);
let width = (style.width().to_pixels() as f32).max(1.0);
let mut options = StrokeOptions::default()
.with_line_width(width)
.with_start_cap(match style.start_cap {
.with_start_cap(match style.start_cap() {
swf::LineCapStyle::None => tessellation::LineCap::Butt,
swf::LineCapStyle::Round => tessellation::LineCap::Round,
swf::LineCapStyle::Square => tessellation::LineCap::Square,
})
.with_end_cap(match style.end_cap {
.with_end_cap(match style.end_cap() {
swf::LineCapStyle::None => tessellation::LineCap::Butt,
swf::LineCapStyle::Round => tessellation::LineCap::Round,
swf::LineCapStyle::Square => tessellation::LineCap::Square,
});
let line_join = match style.join_style {
let line_join = match style.join_style() {
swf::LineJoinStyle::Round => tessellation::LineJoin::Round,
swf::LineJoinStyle::Bevel => tessellation::LineJoin::Bevel,
swf::LineJoinStyle::Miter(limit) => {

View File

@ -1354,78 +1354,53 @@ impl<'a> Reader<'a> {
let end_color = self.read_rgba()?;
Ok((
LineStyle::new_v1(start_width, start_color),
LineStyle::new_v1(end_width, end_color),
LineStyle::new()
.with_width(start_width)
.with_color(start_color),
LineStyle::new().with_width(end_width).with_color(end_color),
))
} else {
// MorphLineStyle2 in DefineMorphShape2.
let flags = LineStyleFlag::from_bits_truncate(self.read_u16()?);
let is_pixel_hinted = flags.contains(LineStyleFlag::PIXEL_HINTING);
let allow_scale_y = !flags.contains(LineStyleFlag::NO_V_SCALE);
let allow_scale_x = !flags.contains(LineStyleFlag::NO_H_SCALE);
let has_fill = flags.contains(LineStyleFlag::HAS_FILL);
let join_style = match flags & LineStyleFlag::JOIN_STYLE {
LineStyleFlag::ROUND => LineJoinStyle::Round,
LineStyleFlag::BEVEL => LineJoinStyle::Bevel,
LineStyleFlag::MITER => LineJoinStyle::Miter(self.read_fixed8()?),
_ => return Err(Error::invalid_data("Invalid line cap type.")),
let mut flags = LineStyleFlag::from_bits_truncate(self.read_u16()?);
// Verify valid cap and join styles.
if flags.contains(LineStyleFlag::JOIN_STYLE) {
log::warn!("Invalid line join style");
flags -= LineStyleFlag::JOIN_STYLE;
}
if flags.contains(LineStyleFlag::START_CAP_STYLE) {
log::warn!("Invalid line start cap style");
flags -= LineStyleFlag::START_CAP_STYLE;
}
if flags.contains(LineStyleFlag::END_CAP_STYLE) {
log::warn!("Invalid line end cap style");
flags -= LineStyleFlag::END_CAP_STYLE;
}
let miter_limit = if flags & LineStyleFlag::JOIN_STYLE == LineStyleFlag::MITER {
self.read_fixed8()?
} else {
Fixed8::ZERO
};
let start_cap =
LineCapStyle::from_u8(((flags & LineStyleFlag::START_CAP_STYLE).bits() >> 6) as u8)
.ok_or_else(|| Error::invalid_data("Invalid line cap type."))?;
let end_cap =
LineCapStyle::from_u8(((flags & LineStyleFlag::END_CAP_STYLE).bits() >> 8) as u8)
.ok_or_else(|| Error::invalid_data("Invalid line cap type."))?;
let allow_close = !flags.contains(LineStyleFlag::ALLOW_CLOSE);
let (start_color, end_color) = if !has_fill {
(self.read_rgba()?, self.read_rgba()?)
let (start_fill_style, end_fill_style) = if flags.contains(LineStyleFlag::HAS_FILL) {
let (start, end) = self.read_morph_fill_style()?;
(start, end)
} else {
(
Color {
r: 0,
g: 0,
b: 0,
a: 0,
},
Color {
r: 0,
g: 0,
b: 0,
a: 0,
},
FillStyle::Color(self.read_rgba()?),
FillStyle::Color(self.read_rgba()?),
)
};
let (start_fill_style, end_fill_style) = if has_fill {
let (start, end) = self.read_morph_fill_style()?;
(Some(start), Some(end))
} else {
(None, None)
};
Ok((
LineStyle {
width: start_width,
color: start_color,
start_cap,
end_cap,
join_style,
allow_scale_x,
allow_scale_y,
is_pixel_hinted,
allow_close,
fill_style: start_fill_style,
flags,
miter_limit,
},
LineStyle {
width: end_width,
color: end_color,
start_cap,
end_cap,
join_style,
allow_scale_x,
allow_scale_y,
is_pixel_hinted,
allow_close,
fill_style: end_fill_style,
flags,
miter_limit,
},
))
}
@ -1682,54 +1657,39 @@ impl<'a> Reader<'a> {
} else {
self.read_rgb()?
};
Ok(LineStyle::new_v1(width, color))
Ok(LineStyle::new().with_width(width).with_color(color))
} else {
// LineStyle2 in DefineShape4
let flags = LineStyleFlag::from_bits_truncate(self.read_u16()?);
let is_pixel_hinted = flags.contains(LineStyleFlag::PIXEL_HINTING);
let allow_scale_y = !flags.contains(LineStyleFlag::NO_V_SCALE);
let allow_scale_x = !flags.contains(LineStyleFlag::NO_H_SCALE);
let has_fill = flags.contains(LineStyleFlag::HAS_FILL);
let join_style = match flags & LineStyleFlag::JOIN_STYLE {
LineStyleFlag::ROUND => LineJoinStyle::Round,
LineStyleFlag::BEVEL => LineJoinStyle::Bevel,
LineStyleFlag::MITER => LineJoinStyle::Miter(self.read_fixed8()?),
_ => return Err(Error::invalid_data("Invalid line cap type.")),
};
let start_cap =
LineCapStyle::from_u8(((flags & LineStyleFlag::START_CAP_STYLE).bits() >> 6) as u8)
.ok_or_else(|| Error::invalid_data("Invalid line cap type."))?;
let end_cap =
LineCapStyle::from_u8(((flags & LineStyleFlag::END_CAP_STYLE).bits() >> 8) as u8)
.ok_or_else(|| Error::invalid_data("Invalid line cap type."))?;
let allow_close = !flags.contains(LineStyleFlag::ALLOW_CLOSE);
let color = if !has_fill {
self.read_rgba()?
} else {
Color {
r: 0,
g: 0,
b: 0,
a: 0,
let mut flags = LineStyleFlag::from_bits_truncate(self.read_u16()?);
// Verify valid cap and join styles.
if flags.contains(LineStyleFlag::JOIN_STYLE) {
log::warn!("Invalid line join style");
flags -= LineStyleFlag::JOIN_STYLE;
}
};
let fill_style = if has_fill {
Some(self.read_fill_style(shape_version)?)
if flags.contains(LineStyleFlag::START_CAP_STYLE) {
log::warn!("Invalid line start cap style");
flags -= LineStyleFlag::START_CAP_STYLE;
}
if flags.contains(LineStyleFlag::END_CAP_STYLE) {
log::warn!("Invalid line end cap style");
flags -= LineStyleFlag::END_CAP_STYLE;
}
let miter_limit = if flags & LineStyleFlag::JOIN_STYLE == LineStyleFlag::MITER {
self.read_fixed8()?
} else {
None
Fixed8::ZERO
};
let fill_style = if flags.contains(LineStyleFlag::HAS_FILL) {
self.read_fill_style(shape_version)?
} else {
FillStyle::Color(self.read_rgba()?)
};
Ok(LineStyle {
width,
color,
start_cap,
end_cap,
join_style,
fill_style,
allow_scale_x,
allow_scale_y,
is_pixel_hinted,
allow_close,
flags,
miter_limit,
})
}
}
@ -2995,15 +2955,9 @@ pub mod tests {
#[test]
fn read_line_style() {
// DefineShape1 and 2 read RGB colors.
let line_style = LineStyle::new_v1(
Twips::from_pixels(0.0),
Color {
r: 255,
g: 0,
b: 0,
a: 255,
},
);
let line_style = LineStyle::new()
.with_width(Twips::from_pixels(0.0))
.with_color(Color::from_rgba(0xffff0000));
assert_eq!(
reader(&[0, 0, 255, 0, 0]).read_line_style(2).unwrap(),
line_style

View File

@ -737,15 +737,9 @@ pub fn tag_tests() -> Vec<TagTestData> {
},
],
})],
line_styles: vec![LineStyle::new_v1(
Twips::from_pixels(10.0),
Color {
r: 0,
g: 255,
b: 0,
a: 255,
},
)],
line_styles: vec![LineStyle::new()
.with_width(Twips::from_pixels(10.0))
.with_color(Color::from_rgba(0xff00ff00))],
shape: vec![
ShapeRecord::StyleChange(Box::new(StyleChangeData {
move_to: Some((Twips::from_pixels(20.0), Twips::from_pixels(20.0))),
@ -840,15 +834,9 @@ pub fn tag_tests() -> Vec<TagTestData> {
},
],
})],
line_styles: vec![LineStyle::new_v1(
Twips::from_pixels(2.0),
Color {
r: 255,
g: 255,
b: 0,
a: 255,
},
)],
line_styles: vec![LineStyle::new()
.with_width(Twips::from_pixels(2.0))
.with_color(Color::from_rgba(0xffffff00))],
shape: vec![
ShapeRecord::StyleChange(Box::new(StyleChangeData {
move_to: Some((Twips::from_pixels(20.0), Twips::from_pixels(60.0))),
@ -969,23 +957,9 @@ pub fn tag_tests() -> Vec<TagTestData> {
},
focal_point: Fixed8::from_f64(0.97265625),
}],
line_styles: vec![LineStyle {
width: Twips::from_pixels(10.0),
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,
}],
line_styles: vec![LineStyle::new()
.with_width(Twips::from_pixels(10.0))
.with_color(Color::from_rgba(0xff00ff00))],
shape: vec![
ShapeRecord::StyleChange(Box::new(StyleChangeData {
move_to: Some((Twips::from_pixels(20.0), Twips::from_pixels(20.0))),
@ -1092,23 +1066,9 @@ pub fn tag_tests() -> Vec<TagTestData> {
},
focal_point: Fixed8::from_f64(-0.9921875),
}],
line_styles: vec![LineStyle {
width: Twips::from_pixels(2.0),
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,
}],
line_styles: vec![LineStyle::new()
.with_width(Twips::from_pixels(2.0))
.with_color(Color::from_rgba(0xffffff00))],
shape: vec![
ShapeRecord::StyleChange(Box::new(StyleChangeData {
move_to: Some((Twips::from_pixels(26.0), Twips::from_pixels(147.35))),
@ -1217,23 +1177,9 @@ pub fn tag_tests() -> Vec<TagTestData> {
},
],
})],
line_styles: vec![LineStyle {
width: Twips::from_pixels(0.0),
color: Color {
r: 0,
g: 0,
b: 0,
a: 0,
},
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,
}],
line_styles: vec![LineStyle::new()
.with_width(Twips::ZERO)
.with_color(Color::from_rgba(0x00000000))],
shape: vec![
ShapeRecord::StyleChange(Box::new(StyleChangeData {
move_to: None,
@ -1305,23 +1251,9 @@ pub fn tag_tests() -> Vec<TagTestData> {
},
],
})],
line_styles: vec![LineStyle {
width: Twips::from_pixels(0.0),
color: Color {
r: 0,
g: 0,
b: 0,
a: 0,
},
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,
}],
line_styles: vec![LineStyle::new()
.with_width(Twips::from_pixels(0.0))
.with_color(Color::from_rgba(0x00000000))],
shape: vec![
ShapeRecord::StraightEdge {
delta_x: Twips::from_pixels(200.0),
@ -1641,35 +1573,23 @@ pub fn tag_tests() -> Vec<TagTestData> {
},
],
line_styles: vec![
LineStyle {
width: Twips::from_pixels(20.0),
color: Color {
LineStyle::new()
.with_width(Twips::from_pixels(20.0))
.with_color(Color {
r: 0,
g: 153,
b: 0,
a: 255,
},
start_cap: LineCapStyle::None,
end_cap: LineCapStyle::None,
join_style: LineJoinStyle::Bevel,
fill_style: None,
allow_scale_x: false,
allow_scale_y: false,
is_pixel_hinted: true,
allow_close: true,
},
LineStyle {
width: Twips::from_pixels(20.0),
color: Color {
r: 0,
g: 0,
b: 0,
a: 0,
},
start_cap: LineCapStyle::Round,
end_cap: LineCapStyle::Round,
join_style: LineJoinStyle::Round,
fill_style: Some(FillStyle::LinearGradient(Gradient {
})
.with_allow_scale_x(false)
.with_allow_scale_y(false)
.with_is_pixel_hinted(true)
.with_join_style(LineJoinStyle::Bevel)
.with_start_cap(LineCapStyle::None)
.with_end_cap(LineCapStyle::None),
LineStyle::new()
.with_width(Twips::from_pixels(20.0))
.with_fill_style(FillStyle::LinearGradient(Gradient {
matrix: Matrix {
tx: Twips::from_pixels(50.0),
ty: Twips::from_pixels(50.0),
@ -1700,29 +1620,20 @@ pub fn tag_tests() -> Vec<TagTestData> {
},
},
],
})),
allow_scale_x: true,
allow_scale_y: false,
is_pixel_hinted: true,
allow_close: true,
},
LineStyle {
width: Twips::from_pixels(20.0),
color: Color {
}))
.with_allow_scale_y(false)
.with_is_pixel_hinted(true),
LineStyle::new()
.with_width(Twips::from_pixels(20.0))
.with_color(Color {
r: 0,
g: 153,
b: 0,
a: 255,
},
start_cap: LineCapStyle::Round,
end_cap: LineCapStyle::Round,
join_style: LineJoinStyle::Miter(Fixed8::from_f32(56.0)),
fill_style: None,
allow_scale_x: true,
allow_scale_y: false,
is_pixel_hinted: true,
allow_close: true,
},
})
.with_join_style(LineJoinStyle::Miter(Fixed8::from_f32(56.0)))
.with_allow_scale_y(false)
.with_is_pixel_hinted(true),
],
},
shape: vec![
@ -2572,18 +2483,9 @@ pub fn tag_tests() -> Vec<TagTestData> {
has_scaling_strokes: true,
styles: ShapeStyles {
fill_styles: vec![],
line_styles: vec![LineStyle {
width: Twips::from_pixels(40.0),
color: Color {
r: 0,
g: 0,
b: 0,
a: 0,
},
start_cap: LineCapStyle::Round,
end_cap: LineCapStyle::Round,
join_style: LineJoinStyle::Round,
fill_style: Some(FillStyle::Bitmap {
line_styles: vec![LineStyle::new()
.with_width(Twips::from_pixels(40.0))
.with_fill_style(FillStyle::Bitmap {
id: 1,
matrix: Matrix {
a: Fixed16::from_f32(20.0),
@ -2594,12 +2496,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
},
is_smoothed: false,
is_repeating: true,
}),
allow_scale_x: true,
allow_scale_y: true,
is_pixel_hinted: false,
allow_close: true,
}],
})],
},
shape: vec![
ShapeRecord::StyleChange(Box::new(StyleChangeData {

View File

@ -1116,31 +1116,153 @@ pub struct GradientRecord {
#[derive(Debug, PartialEq, Clone)]
pub struct LineStyle {
pub width: Twips,
pub color: Color,
pub start_cap: LineCapStyle,
pub end_cap: LineCapStyle,
pub join_style: LineJoinStyle,
pub fill_style: Option<FillStyle>,
pub allow_scale_x: bool,
pub allow_scale_y: bool,
pub is_pixel_hinted: bool,
pub allow_close: bool,
pub(crate) width: Twips,
pub(crate) fill_style: FillStyle,
pub(crate) flags: LineStyleFlag,
pub(crate) miter_limit: Fixed8,
}
impl LineStyle {
pub const fn new_v1(width: Twips, color: Color) -> LineStyle {
LineStyle {
width,
color,
start_cap: LineCapStyle::Round,
end_cap: LineCapStyle::Round,
join_style: LineJoinStyle::Round,
fill_style: None,
allow_scale_x: false,
allow_scale_y: false,
is_pixel_hinted: false,
allow_close: true,
#[inline]
pub fn new() -> LineStyle {
Default::default()
}
#[inline]
pub fn allow_close(&self) -> bool {
!self.flags.contains(LineStyleFlag::NO_CLOSE)
}
#[inline]
pub fn with_allow_close(mut self, val: bool) -> Self {
self.flags.set(LineStyleFlag::NO_CLOSE, !val);
self
}
#[inline]
pub fn allow_scale_x(&self) -> bool {
!self.flags.contains(LineStyleFlag::NO_H_SCALE)
}
#[inline]
pub fn with_allow_scale_x(mut self, val: bool) -> Self {
self.flags.set(LineStyleFlag::NO_H_SCALE, !val);
self
}
#[inline]
pub fn allow_scale_y(&self) -> bool {
!self.flags.contains(LineStyleFlag::NO_V_SCALE)
}
#[inline]
pub fn with_allow_scale_y(mut self, val: bool) -> Self {
self.flags.set(LineStyleFlag::NO_V_SCALE, !val);
self
}
#[inline]
pub fn is_pixel_hinted(&self) -> bool {
self.flags.contains(LineStyleFlag::PIXEL_HINTING)
}
#[inline]
pub fn with_is_pixel_hinted(mut self, val: bool) -> Self {
self.flags.set(LineStyleFlag::PIXEL_HINTING, val);
self
}
#[inline]
pub fn start_cap(&self) -> LineCapStyle {
let cap = (self.flags & LineStyleFlag::START_CAP_STYLE).bits() >> 6;
LineCapStyle::from_u8(cap as u8).unwrap()
}
#[inline]
pub fn with_start_cap(mut self, val: LineCapStyle) -> Self {
self.flags -= LineStyleFlag::START_CAP_STYLE;
self.flags |= LineStyleFlag::from_bits_truncate((val as u16) << 6);
self
}
#[inline]
pub fn end_cap(&self) -> LineCapStyle {
let cap = (self.flags & LineStyleFlag::END_CAP_STYLE).bits() >> 8;
LineCapStyle::from_u8(cap as u8).unwrap()
}
#[inline]
pub fn with_end_cap(mut self, val: LineCapStyle) -> Self {
self.flags -= LineStyleFlag::END_CAP_STYLE;
self.flags |= LineStyleFlag::from_bits_truncate((val as u16) << 8);
self
}
#[inline]
pub fn join_style(&self) -> LineJoinStyle {
match self.flags & LineStyleFlag::JOIN_STYLE {
LineStyleFlag::ROUND => LineJoinStyle::Round,
LineStyleFlag::BEVEL => LineJoinStyle::Bevel,
LineStyleFlag::MITER => LineJoinStyle::Miter(self.miter_limit),
_ => unreachable!(),
}
}
#[inline]
pub fn with_join_style(mut self, val: LineJoinStyle) -> Self {
self.flags -= LineStyleFlag::JOIN_STYLE;
self.flags |= match val {
LineJoinStyle::Round => LineStyleFlag::ROUND,
LineJoinStyle::Bevel => LineStyleFlag::BEVEL,
LineJoinStyle::Miter(miter_limit) => {
self.miter_limit = miter_limit;
LineStyleFlag::MITER
}
};
self
}
#[inline]
pub fn fill_style(&self) -> &FillStyle {
&self.fill_style
}
#[inline]
pub fn with_fill_style(mut self, val: FillStyle) -> Self {
self.flags
.set(LineStyleFlag::HAS_FILL, !matches!(val, FillStyle::Color(_)));
self.fill_style = val;
self
}
#[inline]
pub fn with_color(mut self, val: Color) -> Self {
self.flags.remove(LineStyleFlag::HAS_FILL);
self.fill_style = FillStyle::Color(val);
self
}
#[inline]
pub fn width(&self) -> Twips {
self.width
}
#[inline]
pub fn with_width(mut self, val: Twips) -> Self {
self.width = val;
self
}
}
impl Default for LineStyle {
#[inline]
fn default() -> Self {
// Hairline black stroke.
Self {
width: Twips::ZERO,
fill_style: FillStyle::Color(Color::from_rgb(0, 255)),
flags: Default::default(),
miter_limit: Default::default(),
}
}
}
@ -1157,7 +1279,7 @@ bitflags! {
// Second byte.
const END_CAP_STYLE = 0b11 << 8;
const ALLOW_CLOSE = 1 << 10;
const NO_CLOSE = 1 << 10;
// JOIN_STYLE mask values.
const ROUND = 0b00 << 4;
@ -1166,6 +1288,13 @@ bitflags! {
}
}
impl Default for LineStyleFlag {
#[inline]
fn default() -> Self {
LineStyleFlag::empty()
}
}
#[derive(Debug, PartialEq, Clone, Copy, FromPrimitive)]
pub enum LineCapStyle {
Round = 0,
@ -1174,11 +1303,19 @@ pub enum LineCapStyle {
}
impl LineCapStyle {
#[inline]
pub fn from_u8(n: u8) -> Option<Self> {
num_traits::FromPrimitive::from_u8(n)
}
}
impl Default for LineCapStyle {
#[inline]
fn default() -> Self {
Self::Round
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum LineJoinStyle {
Round,
@ -1186,6 +1323,13 @@ pub enum LineJoinStyle {
Miter(Fixed8),
}
impl Default for LineJoinStyle {
#[inline]
fn default() -> Self {
Self::Round
}
}
#[derive(Debug, PartialEq, Clone, Copy, FromPrimitive)]
pub enum AudioCompression {
UncompressedUnknownEndian = 0,

View File

@ -1205,17 +1205,19 @@ impl<W: Write> Writer<W> {
// TODO(Herschel): Handle overflow.
self.write_u16(start.width.get() as u16)?;
self.write_u16(end.width.get() as u16)?;
self.write_rgba(&start.color)?;
self.write_rgba(&end.color)?;
match (&start.fill_style, &end.fill_style) {
(FillStyle::Color(start), FillStyle::Color(end)) => {
self.write_rgba(start)?;
self.write_rgba(end)?;
}
_ => {
return Err(Error::invalid_data(
"Complex line styles can only be used in DefineMorphShape2 tags",
));
}
}
} 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
{
if start.flags != end.flags {
return Err(Error::invalid_data(
"Morph start and end line styles must have the same join parameters.",
));
@ -1226,41 +1228,21 @@ impl<W: Write> Writer<W> {
self.write_u16(end.width.get() as u16)?;
// MorphLineStyle2
let mut bits = self.bits();
bits.write_ubits(2, start.start_cap as u32)?;
bits.write_ubits(
2,
match start.join_style {
LineJoinStyle::Round => 0,
LineJoinStyle::Bevel => 1,
LineJoinStyle::Miter(_) => 2,
},
)?;
bits.write_bit(start.fill_style.is_some())?;
bits.write_bit(!start.allow_scale_x)?;
bits.write_bit(!start.allow_scale_y)?;
bits.write_bit(start.is_pixel_hinted)?;
bits.write_ubits(5, 0)?;
bits.write_bit(!start.allow_close)?;
bits.write_ubits(2, start.end_cap as u32)?;
drop(bits);
if let LineJoinStyle::Miter(miter_factor) = start.join_style {
self.write_u16(start.flags.bits())?;
if let LineJoinStyle::Miter(miter_factor) = start.join_style() {
self.write_fixed8(miter_factor)?;
}
if start.flags.contains(LineStyleFlag::HAS_FILL) {
self.write_morph_fill_style(&start.fill_style, &end.fill_style)?;
} else {
match (&start.fill_style, &end.fill_style) {
(&None, &None) => {
self.write_rgba(&start.color)?;
self.write_rgba(&end.color)?;
(FillStyle::Color(start), FillStyle::Color(end)) => {
self.write_rgba(start)?;
self.write_rgba(end)?;
}
(&Some(ref start_fill), &Some(ref end_fill)) => {
self.write_morph_fill_style(start_fill, end_fill)?
}
_ => {
return Err(Error::invalid_data(
"Morph start and end line styles must both have fill styles.",
))
return Err(Error::invalid_data("Unexpected line fill style fill type"));
}
}
}
}
@ -1584,34 +1566,31 @@ impl<W: Write> Writer<W> {
self.write_u16(line_style.width.get() as u16)?;
if shape_version >= 4 {
// LineStyle2
let mut flags = LineStyleFlag::empty();
flags.set(LineStyleFlag::PIXEL_HINTING, line_style.is_pixel_hinted);
flags.set(LineStyleFlag::NO_V_SCALE, !line_style.allow_scale_y);
flags.set(LineStyleFlag::NO_H_SCALE, !line_style.allow_scale_x);
flags.set(LineStyleFlag::HAS_FILL, line_style.fill_style.is_some());
flags |= match line_style.join_style {
LineJoinStyle::Round => LineStyleFlag::ROUND,
LineJoinStyle::Bevel => LineStyleFlag::BEVEL,
LineJoinStyle::Miter(_) => LineStyleFlag::MITER,
};
let mut flags = flags.bits();
flags |= (line_style.start_cap as u16) << 6;
flags |= (line_style.end_cap as u16) << 8;
self.write_u16(flags)?;
if let LineJoinStyle::Miter(miter_factor) = line_style.join_style {
self.write_u16(line_style.flags.bits())?;
if let LineJoinStyle::Miter(miter_factor) = line_style.join_style() {
self.write_fixed8(miter_factor)?;
}
match line_style.fill_style {
None => self.write_rgba(&line_style.color)?,
Some(ref fill) => self.write_fill_style(fill, shape_version)?,
}
} else if shape_version >= 3 {
// LineStyle1 with RGBA
self.write_rgba(&line_style.color)?;
if line_style.flags.contains(LineStyleFlag::HAS_FILL) {
self.write_fill_style(&line_style.fill_style, shape_version)?;
} else if let FillStyle::Color(color) = &line_style.fill_style {
self.write_rgba(color)?;
} else {
// LineStyle1 with RGB
self.write_rgb(&line_style.color)?;
return Err(Error::invalid_data("Unexpected line style fill type"));
}
} else {
// LineStyle1
let color = if let FillStyle::Color(color) = &line_style.fill_style {
color
} else {
return Err(Error::invalid_data(
"Complex line styles can only be used in DefineShape4 tags",
));
};
if shape_version >= 3 {
self.write_rgba(color)?
} else {
self.write_rgb(color)?
}
}
Ok(())
}