ass_renderer/layout/positioning/
info.rs1use crate::pipeline::tag_processor::ProcessedTags;
2
3use super::{BoundingBox, PositionConfig};
4
5#[derive(Debug, Clone)]
7pub struct PositionInfo {
8 pub render_x: f32,
10 pub render_y: f32,
11 pub anchor_x: f32,
13 pub anchor_y: f32,
14 pub origin_x: f32,
16 pub origin_y: f32,
17 pub explicit_position: bool,
19}
20
21impl PositionInfo {
22 pub fn calculate(tags: &ProcessedTags, bbox: &BoundingBox, config: &PositionConfig) -> Self {
24 let alignment = tags
26 .formatting
27 .alignment
28 .unwrap_or(config.default_alignment);
29
30 let (anchor_x, anchor_y) = Self::get_anchor_point(
32 alignment,
33 bbox,
34 config.screen_width,
35 config.screen_height,
36 config.margin_left,
37 config.margin_right,
38 config.margin_vertical,
39 );
40
41 let (render_x, render_y, explicit) = if let Some((pos_x, pos_y)) = tags.position {
43 let render_x = pos_x - Self::get_alignment_offset_x(alignment, bbox);
45 let render_y = pos_y - Self::get_alignment_offset_y(alignment, bbox);
46 (render_x, render_y, true)
47 } else if let Some((x1, y1, _x2, _y2, _t1, _t2)) = tags.movement {
48 let render_x = x1 - Self::get_alignment_offset_x(alignment, bbox);
51 let render_y = y1 - Self::get_alignment_offset_y(alignment, bbox);
52 (render_x, render_y, true)
53 } else {
54 (anchor_x, anchor_y, false)
56 };
57
58 let (origin_x, origin_y) = if let Some((org_x, org_y)) = tags.origin {
60 (org_x, org_y)
62 } else {
63 (anchor_x + bbox.width / 2.0, anchor_y + bbox.height / 2.0)
65 };
66
67 Self {
68 render_x,
69 render_y,
70 anchor_x,
71 anchor_y,
72 origin_x,
73 origin_y,
74 explicit_position: explicit,
75 }
76 }
77
78 fn get_anchor_point(
80 alignment: u8,
81 bbox: &BoundingBox,
82 screen_width: f32,
83 screen_height: f32,
84 margin_left: f32,
85 margin_right: f32,
86 margin_vertical: f32,
87 ) -> (f32, f32) {
88 let x = match alignment % 3 {
90 1 => {
91 margin_left
93 }
94 2 | 0 => {
95 (screen_width - bbox.width) / 2.0
97 }
98 _ => {
99 screen_width - margin_right - bbox.width
101 }
102 };
103
104 let y = match alignment {
106 1..=3 => {
107 screen_height - margin_vertical - bbox.height
109 }
110 4..=6 => {
111 (screen_height - bbox.height) / 2.0
113 }
114 7..=9 => {
115 margin_vertical
117 }
118 _ => {
119 screen_height - margin_vertical - bbox.height
121 }
122 };
123
124 (x, y)
125 }
126
127 fn get_alignment_offset_x(alignment: u8, bbox: &BoundingBox) -> f32 {
129 match alignment % 3 {
130 1 => 0.0, 2 | 0 => bbox.width / 2.0, _ => bbox.width, }
134 }
135
136 fn get_alignment_offset_y(alignment: u8, bbox: &BoundingBox) -> f32 {
138 match alignment {
139 1..=3 => bbox.height, 4..=6 => bbox.height / 2.0, 7..=9 => 0.0, _ => bbox.height, }
144 }
145
146 pub fn calculate_with_movement(
148 tags: &ProcessedTags,
149 bbox: &BoundingBox,
150 config: &PositionConfig,
151 current_time_ms: u32,
152 event_start_ms: u32,
153 ) -> Self {
154 let mut pos = Self::calculate(tags, bbox, config);
156
157 if let Some((x1, y1, x2, y2, t1, t2)) = tags.movement {
159 let event_time = current_time_ms.saturating_sub(event_start_ms);
160
161 let factor = if t2 > t1 {
163 let progress = (event_time.saturating_sub(t1)) as f32 / (t2 - t1) as f32;
164 progress.clamp(0.0, 1.0)
165 } else {
166 1.0 };
168
169 let current_x = x1 + (x2 - x1) * factor;
171 let current_y = y1 + (y2 - y1) * factor;
172
173 let alignment = tags
175 .formatting
176 .alignment
177 .unwrap_or(config.default_alignment);
178 pos.render_x = current_x - Self::get_alignment_offset_x(alignment, bbox);
179 pos.render_y = current_y - Self::get_alignment_offset_y(alignment, bbox);
180 pos.explicit_position = true;
181 }
182
183 pos
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_alignment_positions() {
193 let bbox = BoundingBox {
194 x: 0.0,
195 y: 0.0,
196 width: 100.0,
197 height: 50.0,
198 };
199
200 let tags = ProcessedTags::default();
201
202 let config = PositionConfig {
204 screen_width: 1920.0,
205 screen_height: 1080.0,
206 margin_left: 0.0,
207 margin_right: 0.0,
208 margin_vertical: 0.0,
209 default_alignment: 2,
210 };
211 let pos = PositionInfo::calculate(&tags, &bbox, &config);
212
213 assert_eq!(pos.anchor_x, 910.0); assert_eq!(pos.anchor_y, 1030.0); }
216
217 #[test]
218 fn test_explicit_position() {
219 let bbox = BoundingBox {
220 x: 0.0,
221 y: 0.0,
222 width: 100.0,
223 height: 50.0,
224 };
225
226 let tags = ProcessedTags {
227 position: Some((500.0, 300.0)),
228 ..ProcessedTags::default()
229 };
230
231 let config = PositionConfig {
232 screen_width: 1920.0,
233 screen_height: 1080.0,
234 margin_left: 0.0,
235 margin_right: 0.0,
236 margin_vertical: 0.0,
237 default_alignment: 2,
238 };
239 let pos = PositionInfo::calculate(&tags, &bbox, &config);
240
241 assert!(pos.explicit_position);
242 assert_eq!(pos.render_x, 450.0); assert_eq!(pos.render_y, 250.0); }
245
246 #[test]
247 fn test_movement_interpolation() {
248 let bbox = BoundingBox {
249 x: 0.0,
250 y: 0.0,
251 width: 100.0,
252 height: 50.0,
253 };
254
255 let tags = ProcessedTags {
256 movement: Some((100.0, 100.0, 500.0, 300.0, 0, 1000)),
257 ..ProcessedTags::default()
258 };
259
260 let config = PositionConfig {
262 screen_width: 1920.0,
263 screen_height: 1080.0,
264 margin_left: 0.0,
265 margin_right: 0.0,
266 margin_vertical: 0.0,
267 default_alignment: 2,
268 };
269 let pos = PositionInfo::calculate_with_movement(&tags, &bbox, &config, 500, 0);
270
271 assert_eq!(pos.render_x, 250.0); assert_eq!(pos.render_y, 150.0); }
275}