diff --git a/src/util.rs b/src/util.rs index 3e83be7..7ff40ca 100644 --- a/src/util.rs +++ b/src/util.rs @@ -29,19 +29,30 @@ pub fn gerber_doc_as_str(gerber_doc: &GerberDoc) -> String { str::from_utf8(&filevec).unwrap().to_string() } +/// Converts a raw Gerber integer in format `fs` to the nano (6-decimal) +/// precision `CoordinateNumber` uses, rejecting formats with more than 6 +/// decimals and values that overflow instead of panicking. +fn to_nano(value: i64, fs: &CoordinateFormat) -> Result { + let factor = 6u8.checked_sub(fs.decimal).ok_or_else(|| { + GerberError::CoordinateFormatError(format!( + "{} decimal places are more than the supported 6", + fs.decimal + )) + })?; + let nano = value + .checked_mul(10i64.pow(factor as u32)) + .ok_or_else(|| GerberError::RangeError(format!("coordinate {value} is too large")))?; + CoordinateNumber::new(nano).validate(fs) +} + pub fn coordinates_from_gerber( - mut x_as_int: i64, - mut y_as_int: i64, + x_as_int: i64, + y_as_int: i64, fs: CoordinateFormat, ) -> Result, GerberError> { - // we have the raw gerber string as int but now have to convert it to nano precision format - // (i.e. 6 decimal precision) as this is what CoordinateNumber uses internally - let factor = (6u8 - fs.decimal) as u32; - x_as_int *= 10i64.pow(factor); - y_as_int *= 10i64.pow(factor); Ok(Some(Coordinates::new( - CoordinateNumber::new(x_as_int).validate(&fs)?, - CoordinateNumber::new(y_as_int).validate(&fs)?, + to_nano(x_as_int, &fs)?, + to_nano(y_as_int, &fs)?, fs, ))) } @@ -51,15 +62,8 @@ pub fn partial_coordinates_from_gerber( y_as_int: Option, fs: CoordinateFormat, ) -> Result, GerberError> { - // we have the raw gerber string as int but now have to convert it to nano precision format - // (i.e. 6 decimal precision) as this is what CoordinateNumber uses internally - let factor = (6u8 - fs.decimal) as u32; - let x = x_as_int - .map(|value| CoordinateNumber::new(value * 10i64.pow(factor)).validate(&fs)) - .transpose()?; - let y = y_as_int - .map(|value| CoordinateNumber::new(value * 10i64.pow(factor)).validate(&fs)) - .transpose()?; + let x = x_as_int.map(|value| to_nano(value, &fs)).transpose()?; + let y = y_as_int.map(|value| to_nano(value, &fs)).transpose()?; let coordinates = match (x, y) { (None, None) => None, @@ -73,15 +77,8 @@ pub fn partial_coordinates_offset_from_gerber( y_as_int: Option, fs: CoordinateFormat, ) -> Result, GerberError> { - // we have the raw gerber string as int but now have to convert it to nano precision format - // (i.e. 6 decimal precision) as this is what CoordinateNumber uses internally - let factor = (6u8 - fs.decimal) as u32; - let x = x_as_int - .map(|value| CoordinateNumber::new(value * 10i64.pow(factor)).validate(&fs)) - .transpose()?; - let y = y_as_int - .map(|value| CoordinateNumber::new(value * 10i64.pow(factor)).validate(&fs)) - .transpose()?; + let x = x_as_int.map(|value| to_nano(value, &fs)).transpose()?; + let y = y_as_int.map(|value| to_nano(value, &fs)).transpose()?; let coordinate_offset = match (x, y) { (None, None) => None, diff --git a/tests/component_tests.rs b/tests/component_tests.rs index 2d6b2b1..e35cc4a 100644 --- a/tests/component_tests.rs +++ b/tests/component_tests.rs @@ -4076,3 +4076,22 @@ fn generation_software_with_one_value_does_not_panic() { ))) if name == ".GenerationSoftware" ))); } + +/// More than 6 decimal places, or a coordinate too large for nano precision, is an +/// error on that command, not a panic. +#[test] +fn oversized_coordinate_formats_do_not_panic() { + // given + logging_init(); + + for gerber in [ + "%FSLAX27Y27*%\n%MOMM*%\nX1Y1D02*\nM02*\n", + "%FSLAX46Y46*%\n%MOMM*%\nX999999999999999999Y1D02*\nM02*\n", + ] { + // when + let doc = parse(gerber_to_reader(gerber)).unwrap(); + + // then + assert!(doc.commands.iter().any(Result::is_err), "{gerber}"); + } +}