use serde::{Deserialize, Serialize};
use crate::{ErrorCode, FileMakerError, Length, Result, TransformIr, Unit};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct TransformSource {
pub translate_x: Length,
pub translate_y: Length,
pub rotate: i32,
pub scale_x: i64,
pub scale_y: i64,
pub flip_x: bool,
pub flip_y: bool,
pub mirror: MirrorSource,
pub origin_x: Length,
pub origin_y: Length,
}
impl Default for TransformSource {
fn default() -> Self {
Self {
translate_x: Length::Absolute(Unit::ZERO),
translate_y: Length::Absolute(Unit::ZERO),
rotate: 0,
scale_x: 1_000_000,
scale_y: 1_000_000,
flip_x: false,
flip_y: false,
mirror: MirrorSource::None,
origin_x: Length::Percent(500_000),
origin_y: Length::Percent(500_000),
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MirrorSource {
#[default]
None,
Horizontal,
Vertical,
Both,
}
pub(crate) fn validate_transform(source: &TransformSource) -> Result<()> {
const MAX_SCALE: i64 = 100_000_000;
if source.scale_x == 0
|| source.scale_y == 0
|| source.scale_x.unsigned_abs() > MAX_SCALE as u64
|| source.scale_y.unsigned_abs() > MAX_SCALE as u64
|| matches!(source.origin_x, Length::Auto)
|| matches!(source.origin_y, Length::Auto)
|| matches!(source.translate_x, Length::Auto)
|| matches!(source.translate_y, Length::Auto)
{
return Err(FileMakerError::new(
ErrorCode::SchemaField,
"transform requires non-zero bounded scales and explicit translations/origins",
));
}
Ok(())
}
pub(crate) fn convert_transform(source: TransformSource) -> Result<TransformIr> {
let mirror_x = matches!(source.mirror, MirrorSource::Horizontal | MirrorSource::Both);
let mirror_y = matches!(source.mirror, MirrorSource::Vertical | MirrorSource::Both);
let scale_x = signed_scale(source.scale_x, source.flip_x ^ mirror_x, "horizontal")?;
let scale_y = signed_scale(source.scale_y, source.flip_y ^ mirror_y, "vertical")?;
Ok(TransformIr {
translate_x: source.translate_x,
translate_y: source.translate_y,
rotate: source.rotate,
scale_x,
scale_y,
origin_x: source.origin_x,
origin_y: source.origin_y,
})
}
fn signed_scale(scale: i64, flip: bool, axis: &str) -> Result<i64> {
if flip {
scale.checked_neg().ok_or_else(|| {
FileMakerError::new(ErrorCode::GeometryInvalid, format!("{axis} scale overflow"))
})
} else {
Ok(scale)
}
}