ferroui_controls_inking/
ink_drawing_attributes.rs1use super::ink_point::InkError;
2use ferroui_base::media::Color;
3use ferroui_base::{Matrix, Size};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
6#[repr(i32)]
7pub enum PenTipShape {
8 Circle = 0,
9 Rectangle = 1,
10}
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13#[repr(i32)]
14pub enum InkDrawingAttributesKind {
15 Default = 0,
16 Pencil = 1,
17}
18
19#[derive(Clone, Copy, Debug, PartialEq)]
22pub struct InkDrawingAttributes {
23 pub color: Color,
24 pub size: Size,
25 pub pen_tip: PenTipShape,
26 pub pen_tip_transform: Matrix,
27 pub draw_as_highlighter: bool,
28 pub ignore_pressure: bool,
29 pub fit_to_curve: bool,
30 pub ignore_tilt: bool,
31 pub kind: InkDrawingAttributesKind,
32 pub pencil_opacity: f64,
33}
34
35impl Default for InkDrawingAttributes {
36 fn default() -> Self {
37 Self {
38 color: Color::from_rgb(0, 0, 0),
39 size: Size::new(2.0, 2.0),
40 pen_tip: PenTipShape::Circle,
41 pen_tip_transform: Matrix::IDENTITY,
42 draw_as_highlighter: false,
43 ignore_pressure: false,
44 fit_to_curve: true,
45 ignore_tilt: false,
46 kind: InkDrawingAttributesKind::Default,
47 pencil_opacity: 1.0,
48 }
49 }
50}
51
52impl InkDrawingAttributes {
53 pub fn new() -> Self {
54 Self::default()
55 }
56 pub fn create_for_pencil() -> Self {
57 Self {
58 kind: InkDrawingAttributesKind::Pencil,
59 pencil_opacity: 0.5,
60 ..Self::default()
61 }
62 }
63
64 pub fn validate(&self) -> Result<(), InkError> {
65 if !self.size.width.is_finite()
66 || !self.size.height.is_finite()
67 || self.size.width <= 0.0
68 || self.size.height <= 0.0
69 || self.size.width > 100_000.0
70 || self.size.height > 100_000.0
71 || !self.pencil_opacity.is_finite()
72 || !(0.0..=1.0).contains(&self.pencil_opacity)
73 {
74 return Err(InkError::InvalidDrawingAttributes);
75 }
76 validate_affine(self.pen_tip_transform)?;
77 let m = self.pen_tip_transform;
78 if m.m31 != 0.0 || m.m32 != 0.0 || (m.m11 * m.m22 - m.m12 * m.m21).abs() < 1.0e-12 {
79 return Err(InkError::InvalidDrawingAttributes);
80 }
81 Ok(())
82 }
83
84 pub(crate) fn pressure_scale(&self, pressure: f32) -> f64 {
93 let pressure = f64::from(pressure);
94 if self.ignore_pressure {
95 1.0
96 } else if !(pressure > 0.0) {
97 0.0
98 } else {
99 0.25 + 1.5 * pressure.min(1.0)
100 }
101 }
102}
103
104pub(crate) fn validate_affine(m: Matrix) -> Result<(), InkError> {
105 if [m.m11, m.m12, m.m21, m.m22, m.m31, m.m32]
106 .iter()
107 .any(|v| !v.is_finite() || v.abs() > 1.0e12)
108 || m.m13 != 0.0
109 || m.m23 != 0.0
110 || m.m33 != 1.0
111 {
112 return Err(InkError::InvalidTransform);
113 }
114 Ok(())
115}
116
117ferroui_base::ferro_markup_enum!(PenTipShape { Circle, Rectangle });
118ferroui_base::ferro_markup_enum!(InkDrawingAttributesKind { Default, Pencil });
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 #[test]
124 fn rejects_invalid_size_and_perspective() {
125 let mut a = InkDrawingAttributes::default();
126 a.size.width = 0.0;
127 assert_eq!(a.validate(), Err(InkError::InvalidDrawingAttributes));
128 a = InkDrawingAttributes::default();
129 a.pen_tip_transform.m13 = 1.0;
130 assert_eq!(a.validate(), Err(InkError::InvalidTransform));
131 }
132 #[test]
133 fn pencil_defaults_are_a_value_snapshot() {
134 let a = InkDrawingAttributes::create_for_pencil();
135 let mut b = a;
136 b.pencil_opacity = 0.8;
137 assert_eq!(a.pencil_opacity, 0.5);
138 assert_eq!(a.kind, InkDrawingAttributesKind::Pencil);
139 assert!(a.validate().is_ok());
140 }
141}