appcore_filemaker/
source_transform.rs1use serde::{Deserialize, Serialize};
12
13use crate::{ErrorCode, FileMakerError, Length, Result, TransformIr, Unit};
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
17#[serde(default, deny_unknown_fields)]
18pub struct TransformSource {
19 pub translate_x: Length,
21 pub translate_y: Length,
23 pub rotate: i32,
25 pub scale_x: i64,
27 pub scale_y: i64,
29 pub flip_x: bool,
31 pub flip_y: bool,
33 pub mirror: MirrorSource,
35 pub origin_x: Length,
37 pub origin_y: Length,
39}
40
41impl Default for TransformSource {
42 fn default() -> Self {
43 Self {
44 translate_x: Length::Absolute(Unit::ZERO),
45 translate_y: Length::Absolute(Unit::ZERO),
46 rotate: 0,
47 scale_x: 1_000_000,
48 scale_y: 1_000_000,
49 flip_x: false,
50 flip_y: false,
51 mirror: MirrorSource::None,
52 origin_x: Length::Percent(500_000),
53 origin_y: Length::Percent(500_000),
54 }
55 }
56}
57
58#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
60#[serde(rename_all = "snake_case")]
61pub enum MirrorSource {
62 #[default]
64 None,
65 Horizontal,
67 Vertical,
69 Both,
71}
72
73pub(crate) fn validate_transform(source: &TransformSource) -> Result<()> {
74 const MAX_SCALE: i64 = 100_000_000;
75 if source.scale_x == 0
76 || source.scale_y == 0
77 || source.scale_x.unsigned_abs() > MAX_SCALE as u64
78 || source.scale_y.unsigned_abs() > MAX_SCALE as u64
79 || matches!(source.origin_x, Length::Auto)
80 || matches!(source.origin_y, Length::Auto)
81 || matches!(source.translate_x, Length::Auto)
82 || matches!(source.translate_y, Length::Auto)
83 {
84 return Err(FileMakerError::new(
85 ErrorCode::SchemaField,
86 "transform requires non-zero bounded scales and explicit translations/origins",
87 ));
88 }
89 Ok(())
90}
91
92pub(crate) fn convert_transform(source: TransformSource) -> Result<TransformIr> {
93 let mirror_x = matches!(source.mirror, MirrorSource::Horizontal | MirrorSource::Both);
94 let mirror_y = matches!(source.mirror, MirrorSource::Vertical | MirrorSource::Both);
95 let scale_x = signed_scale(source.scale_x, source.flip_x ^ mirror_x, "horizontal")?;
96 let scale_y = signed_scale(source.scale_y, source.flip_y ^ mirror_y, "vertical")?;
97 Ok(TransformIr {
98 translate_x: source.translate_x,
99 translate_y: source.translate_y,
100 rotate: source.rotate,
101 scale_x,
102 scale_y,
103 origin_x: source.origin_x,
104 origin_y: source.origin_y,
105 })
106}
107
108fn signed_scale(scale: i64, flip: bool, axis: &str) -> Result<i64> {
109 if flip {
110 scale.checked_neg().ok_or_else(|| {
111 FileMakerError::new(ErrorCode::GeometryInvalid, format!("{axis} scale overflow"))
112 })
113 } else {
114 Ok(scale)
115 }
116}