avm2: Improve drawing paths in Graphics

This patch improves drawing paths in Graphics:
* no error is thrown when there are 0 commands,
* no error is thrown when there are missing data points,
* an error is thrown when the number of coordinates is odd,
* no error is thrown and command processing is stopped
  when an unknown command is encountered,
* the vector with parsed commands is removed, as it's not needed.
This commit is contained in:
Kamil Jarosz 2024-06-18 11:12:10 +02:00 committed by Nathan Adams
parent fae1500cc6
commit 0f534c837f
1 changed files with 75 additions and 76 deletions

View File

@ -1182,25 +1182,22 @@ fn read_point<'gc>(
activation: &mut Activation<'_, 'gc>,
data: &VectorStorage<'gc>,
data_index: &mut usize,
) -> Result<Point<Twips>, Error<'gc>> {
if *data_index + 1 >= data.length() {
return Err(make_error_2004(activation, Error2004Type::ArgumentError));
}
) -> Option<Point<Twips>> {
let x = data
.get(*data_index, activation)
.expect("missing data")
.ok()?
.as_number(activation.context.gc_context)
.expect("data is not a Vec.<Number>");
let y = data
.get(*data_index + 1, activation)
.expect("missing data")
.ok()?
.as_number(activation.context.gc_context)
.expect("data is not a Vec.<Number>");
*data_index += 2;
Ok(Point {
Some(Point {
x: Twips::from_pixels(x),
y: Twips::from_pixels(y),
})
@ -1212,15 +1209,77 @@ fn process_commands<'gc>(
commands: &VectorStorage<'gc>,
data: &VectorStorage<'gc>,
) -> Result<(), Error<'gc>> {
let mut data_index = 0;
let mut parsed_commands = vec![];
// Flash special cases this, and doesn't throw an error, even if there are
// commands that try to use points from 'data'
if data.length() == 0 {
// Flash special cases this, and doesn't throw an error,
// even if data has odd number of coordinates.
if commands.length() == 0 {
return Ok(());
}
// This error is always thrown at this point,
// no matter if data is superfluous.
if data.length() % 2 != 0 {
return Err(make_error_2004(activation, Error2004Type::ArgumentError));
}
fn process_command<'gc>(
activation: &mut Activation<'_, 'gc>,
drawing: &mut Drawing,
data: &VectorStorage<'gc>,
command: i32,
data_index: &mut usize,
) -> Option<()> {
// Flash ignores commands which do not have data associated with them.
match command {
// NO_OP
0 => {}
// MOVE_TO
1 => {
let point = read_point(activation, data, data_index)?;
drawing.draw_command(DrawCommand::MoveTo(point));
}
// LINE_TO
2 => {
let point = read_point(activation, data, data_index)?;
drawing.draw_command(DrawCommand::LineTo(point));
}
// CURVE_TO
3 => {
let control = read_point(activation, data, data_index)?;
let anchor = read_point(activation, data, data_index)?;
drawing.draw_command(DrawCommand::QuadraticCurveTo { control, anchor });
}
// WIDE_MOVE_TO
4 => {
*data_index += 2;
let point = read_point(activation, data, data_index)?;
drawing.draw_command(DrawCommand::MoveTo(point));
}
// WIDE_LINE_TO
5 => {
*data_index += 2;
let point = read_point(activation, data, data_index)?;
drawing.draw_command(DrawCommand::LineTo(point));
}
// CUBIC_CURVE_TO
6 => {
let control_a = read_point(activation, data, data_index)?;
let control_b = read_point(activation, data, data_index)?;
let anchor = read_point(activation, data, data_index)?;
drawing.draw_command(DrawCommand::CubicCurveTo {
control_a,
control_b,
anchor,
});
}
_ => {
// Unknown commands stop processing
return None;
}
}
Some(())
}
let mut data_index = 0;
for i in 0..commands.length() {
let command = commands
.get(i, activation)
@ -1228,71 +1287,11 @@ fn process_commands<'gc>(
.as_integer(activation.context.gc_context)
.expect("commands is not a Vec.<int>");
// FIXME - determine correct behavior when data is missing or invalid commands
// are used. Flash doesn't throw an error, and it's unclear what the correct
// behavior is, exactly. For now, just panic when this happens, so we can determine
// if there are any SWFS in the wild relying on this.
let draw_command = match command {
// NO_OP
0 => None,
// MOVE_TO
1 => Some(DrawCommand::MoveTo(read_point(
activation,
data,
&mut data_index,
)?)),
// LINE_TO
2 => Some(DrawCommand::LineTo(read_point(
activation,
data,
&mut data_index,
)?)),
// CURVE_TO
3 => {
let control = read_point(activation, data, &mut data_index)?;
let anchor = read_point(activation, data, &mut data_index)?;
Some(DrawCommand::QuadraticCurveTo { control, anchor })
}
// WIDE_MOVE_TO
4 => {
let _dummy = read_point(activation, data, &mut data_index)?;
Some(DrawCommand::MoveTo(read_point(
activation,
data,
&mut data_index,
)?))
}
// WIDE_LINE_TO
5 => {
let _dummy = read_point(activation, data, &mut data_index)?;
Some(DrawCommand::LineTo(read_point(
activation,
data,
&mut data_index,
)?))
}
// CUBIC_CURVE_TO
6 => {
let control_a = read_point(activation, data, &mut data_index)?;
let control_b = read_point(activation, data, &mut data_index)?;
let anchor = read_point(activation, data, &mut data_index)?;
Some(DrawCommand::CubicCurveTo {
control_a,
control_b,
anchor,
})
}
_ => panic!("Unexpected command value {command}"),
};
if let Some(draw_command) = draw_command {
parsed_commands.push(draw_command);
if process_command(activation, drawing, data, command, &mut data_index).is_none() {
break;
}
}
// Once validation is done, draw everything
for command in parsed_commands {
drawing.draw_command(command);
}
Ok(())
}