ass_renderer/pipeline/build/
run.rs1#[cfg(feature = "nostd")]
4use alloc::{string::ToString, vec::Vec};
5#[cfg(not(feature = "nostd"))]
6use std::{string::ToString, vec::Vec};
7
8#[cfg(feature = "analysis-integration")]
9use ass_core::analysis::ScriptAnalysis;
10use ass_core::parser::{Event, Script};
11
12use super::OwnedStyle;
13use crate::collision::PositionedEvent;
14use crate::pipeline::{text_segmenter::segment_text_with_tags, IntermediateLayer, Pipeline};
15use crate::renderer::RenderContext;
16use crate::utils::{DirtyRegion, RenderError};
17
18impl super::SoftwarePipeline {
19 fn process_event(
20 &mut self,
21 event: &Event,
22 time_cs: u32,
23 context: &RenderContext,
24 ) -> Result<Vec<IntermediateLayer>, RenderError> {
25 let segments = segment_text_with_tags(event.text, None)?;
27
28 if segments.is_empty() {
29 return Ok(Vec::new());
30 }
31
32 if let Some(draw_level) = segments[0].tags.drawing_mode {
34 if draw_level > 0 {
35 let style_cloned = self
37 .styles_map
38 .get(event.style)
39 .or(self.default_style.as_ref())
40 .cloned();
41
42 return self.process_drawing_command(
43 &segments[0],
44 event,
45 style_cloned.as_ref(),
46 time_cs,
47 context,
48 );
49 }
50 }
51
52 let style_cloned = self
54 .styles_map
55 .get(event.style)
56 .or(self.default_style.as_ref())
57 .cloned();
58
59 self.process_text_segments(segments, event, style_cloned.as_ref(), time_cs, context)
61 }
62}
63
64#[allow(dead_code)] fn calculate_karaoke_progress(time_cs: u32, start_time_cs: u32, duration_cs: u32) -> f32 {
66 if time_cs < start_time_cs {
67 return 0.0;
68 }
69 let elapsed = time_cs - start_time_cs;
70 if elapsed >= duration_cs {
71 return 1.0;
72 }
73 elapsed as f32 / duration_cs as f32
74}
75
76impl Pipeline for super::SoftwarePipeline {
77 fn prepare_script(
78 &mut self,
79 script: &Script,
80 #[cfg(feature = "analysis-integration")] analysis: Option<&ScriptAnalysis>,
81 #[cfg(not(feature = "analysis-integration"))] _analysis: Option<()>,
82 ) -> Result<(), RenderError> {
83 super::super::font_loader::load_script_fonts(script, &mut self.font_database);
85
86 self.styles_map.clear();
88 self.default_style = None;
89
90 #[cfg(feature = "analysis-integration")]
93 let _use_resolved_styles = analysis.is_some();
94 #[cfg(not(feature = "analysis-integration"))]
95 let _use_resolved_styles = false;
96
97 for section in script.sections() {
99 match section {
100 ass_core::parser::Section::ScriptInfo(info) => {
101 if let Some((res_x, res_y)) = info.play_resolution() {
103 self.play_res_x = res_x as f32;
104 self.play_res_y = res_y as f32;
105 }
106
107 self.wrap_style = info.wrap_style();
109
110 if let Some((layout_x, layout_y)) = info.layout_resolution() {
112 self.layout_res_x = Some(layout_x as f32);
113 self.layout_res_y = Some(layout_y as f32);
114
115 }
118
119 if let Some(scaled_value) = info.get_field("ScaledBorderAndShadow") {
122 self.scaled_border_and_shadow = scaled_value.to_lowercase() != "no";
123 }
124 }
125 ass_core::parser::Section::Styles(styles) => {
126 let layout_to_play_scale_x = if let Some(layout_x) = self.layout_res_x {
128 if layout_x != self.play_res_x {
129 self.play_res_x / layout_x
130 } else {
131 1.0
132 }
133 } else {
134 1.0
135 };
136
137 let layout_to_play_scale_y = if let Some(layout_y) = self.layout_res_y {
138 if layout_y != self.play_res_y {
139 self.play_res_y / layout_y
140 } else {
141 1.0
142 }
143 } else {
144 1.0
145 };
146
147 let needs_layout_scaling =
148 layout_to_play_scale_x != 1.0 || layout_to_play_scale_y != 1.0;
149
150 for style in styles {
151 let style_name = style.name.to_string();
152 let mut owned_style = OwnedStyle::from_style(style);
153
154 if needs_layout_scaling {
156 if let Ok(font_size) = owned_style.fontsize.parse::<f32>() {
158 owned_style.fontsize =
159 (font_size * layout_to_play_scale_y).to_string();
160 }
161
162 if let Ok(margin_l) = owned_style.margin_l.parse::<f32>() {
164 owned_style.margin_l =
165 (margin_l * layout_to_play_scale_x).to_string();
166 }
167 if let Ok(margin_r) = owned_style.margin_r.parse::<f32>() {
168 owned_style.margin_r =
169 (margin_r * layout_to_play_scale_x).to_string();
170 }
171 if let Ok(margin_v) = owned_style.margin_v.parse::<f32>() {
172 owned_style.margin_v =
173 (margin_v * layout_to_play_scale_y).to_string();
174 }
175
176 if self.scaled_border_and_shadow {
178 if let Ok(outline) = owned_style.outline.parse::<f32>() {
179 owned_style.outline =
180 (outline * layout_to_play_scale_y).to_string();
181 }
182 if let Ok(shadow) = owned_style.shadow.parse::<f32>() {
183 owned_style.shadow =
184 (shadow * layout_to_play_scale_y).to_string();
185 }
186 }
187
188 if let Ok(spacing) = owned_style.spacing.parse::<f32>() {
190 owned_style.spacing =
191 (spacing * layout_to_play_scale_x).to_string();
192 }
193 }
194
195 if style_name == "Default" || style_name == "*Default" {
196 self.default_style = Some(owned_style.clone());
197 }
198
199 self.styles_map.insert(style_name, owned_style);
200 }
201 }
202 _ => {}
203 }
204 }
205
206 if self.default_style.is_none() && !self.styles_map.is_empty() {
208 self.default_style = self.styles_map.values().next().cloned();
209 }
210
211 Ok(())
212 }
213
214 fn script(&self) -> Option<&Script<'_>> {
215 None }
217
218 fn process_events(
219 &mut self,
220 events: &[&Event],
221 time_cs: u32,
222 context: &RenderContext,
223 ) -> Result<Vec<IntermediateLayer>, RenderError> {
224 self.collision_resolver.clear();
226
227 let mut all_layers = Vec::with_capacity(events.len() * 3);
229
230 let mut sorted_events = events.to_vec();
232 sorted_events.sort_by(|a, b| {
233 let layer_a = a.layer.parse::<i32>().unwrap_or(0);
234 let layer_b = b.layer.parse::<i32>().unwrap_or(0);
235 let start_a = a.start_time_cs().unwrap_or(0);
236 let start_b = b.start_time_cs().unwrap_or(0);
237
238 layer_a.cmp(&layer_b).then(start_a.cmp(&start_b))
240 });
241
242 let scale_y = context.height() as f32 / self.play_res_y;
243
244 for event in sorted_events {
249 let mut event_layers = self.process_event(event, time_cs, context)?;
250
251 if !Self::event_is_positioned(event) {
252 if let Some(bbox) = self.event_bounding_box(&event_layers) {
253 let positioned = PositionedEvent {
254 bbox,
255 layer: event.layer.parse::<i32>().unwrap_or(0),
256 margin_v: self.event_margin_v(event, scale_y) as i32,
257 margin_l: 0,
258 margin_r: 0,
259 alignment: self.effective_alignment(event),
260 priority: 0,
261 };
262 let resolved = self.collision_resolver.find_position(positioned);
263 let dy = resolved.y - bbox.y;
264 if dy.abs() > 0.5 {
265 Self::offset_layers_y(&mut event_layers, dy);
266 }
267 }
268 }
269
270 all_layers.extend(event_layers);
271 }
272
273 Ok(all_layers)
274 }
275
276 fn compute_dirty_regions(
277 &self,
278 events: &[&Event],
279 time_cs: u32,
280 prev_time_cs: u32,
281 ) -> Result<Vec<DirtyRegion>, RenderError> {
282 let mut regions = Vec::new();
283
284 for event in events {
285 let was_active = event.start_time_cs().unwrap_or(0) <= prev_time_cs
286 && event.end_time_cs().unwrap_or(u32::MAX) > prev_time_cs;
287 let is_active = event.start_time_cs().unwrap_or(0) <= time_cs
288 && event.end_time_cs().unwrap_or(u32::MAX) > time_cs;
289
290 if was_active != is_active {
291 regions.push(DirtyRegion::full_screen());
293 break;
294 }
295 }
296
297 Ok(regions)
298 }
299}