Skip to main content

ass_renderer/pipeline/
mod.rs

1//! Rendering pipeline for processing events into layers
2
3use crate::renderer::RenderContext;
4use crate::utils::{DirtyRegion, RenderError};
5#[cfg(feature = "analysis-integration")]
6use ass_core::analysis::ScriptAnalysis;
7use ass_core::parser::{Event, Script};
8use smallvec::SmallVec;
9
10#[cfg(feature = "nostd")]
11use alloc::{string::String, vec::Vec};
12#[cfg(not(feature = "nostd"))]
13use std::{string::String, vec::Vec};
14
15pub mod animation;
16pub mod compositing;
17pub mod drawing;
18pub mod font_loader;
19pub mod shaping;
20pub mod tag_processor;
21pub mod text_segmenter;
22pub mod transform;
23pub mod validation;
24
25mod build;
26pub use build::SoftwarePipeline;
27
28/// Pipeline trait for processing events
29pub trait Pipeline: Send + Sync {
30    /// Prepare the pipeline with a script
31    fn prepare_script(
32        &mut self,
33        script: &Script,
34        #[cfg(feature = "analysis-integration")] analysis: Option<&ScriptAnalysis>,
35        #[cfg(not(feature = "analysis-integration"))] _analysis: Option<()>,
36    ) -> Result<(), RenderError>;
37
38    /// Get the current script
39    fn script(&self) -> Option<&Script<'_>>;
40
41    /// Process events into intermediate layers
42    fn process_events(
43        &mut self,
44        events: &[&Event],
45        time_cs: u32,
46        context: &RenderContext,
47    ) -> Result<Vec<IntermediateLayer>, RenderError>;
48
49    /// Compute dirty regions for incremental rendering
50    fn compute_dirty_regions(
51        &self,
52        events: &[&Event],
53        time_cs: u32,
54        prev_time_cs: u32,
55    ) -> Result<Vec<DirtyRegion>, RenderError>;
56}
57
58/// Pipeline stage for processing
59#[derive(Debug, Clone, Copy)]
60pub enum PipelineStage {
61    /// Text shaping stage
62    Shaping,
63    /// Drawing command processing
64    Drawing,
65    /// Effect application
66    Effects,
67    /// Layer compositing
68    Compositing,
69}
70
71/// Intermediate layer representation
72pub enum IntermediateLayer {
73    /// Rasterized bitmap layer
74    Raster(RasterData),
75    /// Vector graphics layer
76    Vector(VectorData),
77    /// Text layer
78    Text(TextData),
79}
80
81impl IntermediateLayer {
82    /// Check if layer intersects with a dirty region
83    pub fn intersects_region(&self, region: &DirtyRegion) -> bool {
84        match self {
85            Self::Raster(data) => {
86                let layer_bounds = (data.x, data.y, data.x + data.width, data.y + data.height);
87                region.intersects(layer_bounds)
88            }
89            Self::Vector(data) => {
90                if let Some(bounds) = &data.bounds {
91                    region.intersects(*bounds)
92                } else {
93                    true
94                }
95            }
96            Self::Text(data) => {
97                let approx_bounds = (
98                    data.x as u32,
99                    data.y as u32,
100                    (data.x + 200.0) as u32,
101                    (data.y + data.font_size * 1.5) as u32,
102                );
103                region.intersects(approx_bounds)
104            }
105        }
106    }
107}
108
109/// Raster layer data
110pub struct RasterData {
111    /// Pixel data (RGBA)
112    pub pixels: Vec<u8>,
113    /// Layer X position
114    pub x: u32,
115    /// Layer Y position
116    pub y: u32,
117    /// Layer width
118    pub width: u32,
119    /// Layer height
120    pub height: u32,
121    /// Opacity (0-255)
122    pub opacity: u8,
123}
124
125/// Vector graphics layer data
126pub struct VectorData {
127    /// Path to draw
128    pub path: Option<tiny_skia::Path>,
129    /// Fill color (RGBA)
130    pub color: [u8; 4],
131    /// Stroke information
132    pub stroke: Option<StrokeInfo>,
133    /// Bounding box
134    pub bounds: Option<(u32, u32, u32, u32)>,
135    /// Rectangular `\clip` / `\iclip` in render coordinates as
136    /// `(x1, y1, x2, y2, inverse)`; `None` when the drawing is unclipped.
137    pub clip: Option<(f32, f32, f32, f32, bool)>,
138    /// Gaussian blur radius in screen pixels (the `\blur` value already scaled by
139    /// `blur_scale` = frame/PlayRes). `0.0` leaves the drawing sharp. Blur is
140    /// applied to the filled-and-stroked shape, then clipped — matching libass,
141    /// so soft `\p` glows (dust, sparkles, gradient banners) read correctly.
142    pub blur: f32,
143}
144
145/// Stroke information for vector graphics
146pub struct StrokeInfo {
147    /// Stroke color (RGBA)
148    pub color: [u8; 4],
149    /// Stroke width
150    pub width: f32,
151}
152
153/// Text layer data
154pub struct TextData {
155    /// Text content
156    pub text: String,
157    /// Font family
158    pub font_family: String,
159    /// Font size in pixels
160    pub font_size: f32,
161    /// Text color (RGBA)
162    pub color: [u8; 4],
163    /// X position
164    pub x: f32,
165    /// Y position
166    pub y: f32,
167    /// Text effects
168    pub effects: SmallVec<[TextEffect; 4]>,
169    /// Letter spacing in pixels
170    pub spacing: f32,
171}
172
173/// Text effect enumeration
174#[derive(Clone, Debug)]
175pub enum TextEffect {
176    /// Bold text
177    Bold,
178    /// Italic text
179    Italic,
180    /// Underline
181    Underline,
182    /// Strikethrough
183    Strikethrough,
184    /// Outline with color and per-axis width (`\bord`/`\xbord`/`\ybord`). For the
185    /// common symmetric case `width_x == width_y`.
186    Outline {
187        color: [u8; 4],
188        width_x: f32,
189        width_y: f32,
190    },
191    /// Shadow with color and offset
192    Shadow {
193        color: [u8; 4],
194        x_offset: f32,
195        y_offset: f32,
196    },
197    /// Blur effect
198    Blur { radius: f32 },
199    /// Edge blur effect (only blurs outline/edges)
200    EdgeBlur { radius: f32 },
201    /// Karaoke effect. `secondary` is the not-yet-sung (secondary) colour;
202    /// the sung colour is the layer's primary `color`.
203    Karaoke {
204        progress: f32,
205        style: u8,
206        secondary: [u8; 4],
207    },
208    /// 3D rotation (in degrees). `origin`, when set, is the rotation centre in
209    /// screen-space pixels (`\org`); otherwise the text's own centre is used.
210    Rotation {
211        x: f32,
212        y: f32,
213        z: f32,
214        origin: Option<(f32, f32)>,
215    },
216    /// Shear/skew transformation
217    Shear { x: f32, y: f32 },
218    /// Scale transformation
219    Scale { x: f32, y: f32 },
220    /// Clip region
221    Clip {
222        x1: f32,
223        y1: f32,
224        x2: f32,
225        y2: f32,
226        inverse: bool,
227    },
228    /// Opaque box behind the text (`BorderStyle: 3`), drawn in the outline
229    /// colour with per-axis padding around the glyph bounds.
230    OpaqueBox {
231        color: [u8; 4],
232        padding_x: f32,
233        padding_y: f32,
234    },
235}