Skip to main content

hyper_render/
config.rs

1//! Configuration types for rendering.
2
3use crate::error::{Error, Result};
4
5/// Output format for rendered content.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum OutputFormat {
8    /// PNG image format (raster).
9    #[default]
10    Png,
11    /// PDF document format (vector).
12    Pdf,
13}
14
15impl std::fmt::Display for OutputFormat {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        match self {
18            OutputFormat::Png => write!(f, "png"),
19            OutputFormat::Pdf => write!(f, "pdf"),
20        }
21    }
22}
23
24/// Color scheme preference for rendering.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26pub enum ColorScheme {
27    /// Light color scheme.
28    #[default]
29    Light,
30    /// Dark color scheme.
31    Dark,
32}
33
34impl From<ColorScheme> for blitz_traits::shell::ColorScheme {
35    fn from(scheme: ColorScheme) -> Self {
36        match scheme {
37            ColorScheme::Light => blitz_traits::shell::ColorScheme::Light,
38            ColorScheme::Dark => blitz_traits::shell::ColorScheme::Dark,
39        }
40    }
41}
42
43/// Configuration for HTML rendering.
44///
45/// Use the builder pattern to construct a configuration:
46///
47/// ```rust
48/// use hyper_render::{Config, OutputFormat, ColorScheme};
49///
50/// let config = Config::new()
51///     .width(1200)
52///     .height(800)
53///     .scale(2.0)
54///     .format(OutputFormat::Pdf)
55///     .color_scheme(ColorScheme::Dark);
56/// ```
57#[derive(Debug, Clone)]
58pub struct Config {
59    /// Width of the viewport in pixels.
60    pub width: u32,
61
62    /// Height of the viewport in pixels.
63    ///
64    /// For PDF output, this may be adjusted based on content length
65    /// if `auto_height` is enabled.
66    pub height: u32,
67
68    /// Scale factor for rendering (e.g., 2.0 for retina displays).
69    pub scale: f32,
70
71    /// Output format (PNG or PDF).
72    pub format: OutputFormat,
73
74    /// Color scheme preference (light or dark mode).
75    pub color_scheme: ColorScheme,
76
77    /// Whether to automatically adjust height based on content.
78    ///
79    /// When enabled, the renderer will compute the actual content height
80    /// and use that instead of the configured height.
81    pub auto_height: bool,
82
83    /// Background color as RGBA (default: white).
84    pub background: [u8; 4],
85}
86
87impl Default for Config {
88    fn default() -> Self {
89        Self {
90            width: 800,
91            height: 600,
92            scale: 1.0,
93            format: OutputFormat::Png,
94            color_scheme: ColorScheme::Light,
95            auto_height: false,
96            background: [255, 255, 255, 255], // White
97        }
98    }
99}
100
101impl Config {
102    /// Create a new configuration with default values.
103    ///
104    /// Defaults:
105    /// - Width: 800px
106    /// - Height: 600px
107    /// - Scale: 1.0
108    /// - Format: PNG
109    /// - Color scheme: Light
110    pub fn new() -> Self {
111        Self::default()
112    }
113
114    /// Set the viewport width in pixels.
115    ///
116    /// # Example
117    ///
118    /// ```rust
119    /// use hyper_render::Config;
120    ///
121    /// let config = Config::new().width(1920);
122    /// assert_eq!(config.width, 1920);
123    /// ```
124    pub fn width(mut self, width: u32) -> Self {
125        self.width = width;
126        self
127    }
128
129    /// Set the viewport height in pixels.
130    ///
131    /// # Example
132    ///
133    /// ```rust
134    /// use hyper_render::Config;
135    ///
136    /// let config = Config::new().height(1080);
137    /// assert_eq!(config.height, 1080);
138    /// ```
139    pub fn height(mut self, height: u32) -> Self {
140        self.height = height;
141        self
142    }
143
144    /// Set both width and height at once.
145    ///
146    /// # Example
147    ///
148    /// ```rust
149    /// use hyper_render::Config;
150    ///
151    /// let config = Config::new().size(1920, 1080);
152    /// assert_eq!(config.width, 1920);
153    /// assert_eq!(config.height, 1080);
154    /// ```
155    pub fn size(mut self, width: u32, height: u32) -> Self {
156        self.width = width;
157        self.height = height;
158        self
159    }
160
161    /// Set the scale factor for rendering.
162    ///
163    /// Use 2.0 for retina/HiDPI displays to get crisp output.
164    ///
165    /// # Example
166    ///
167    /// ```rust
168    /// use hyper_render::Config;
169    ///
170    /// let config = Config::new().scale(2.0);
171    /// assert_eq!(config.scale, 2.0);
172    /// ```
173    pub fn scale(mut self, scale: f32) -> Self {
174        self.scale = scale;
175        self
176    }
177
178    /// Set the output format.
179    ///
180    /// # Example
181    ///
182    /// ```rust
183    /// use hyper_render::{Config, OutputFormat};
184    ///
185    /// let config = Config::new().format(OutputFormat::Pdf);
186    /// ```
187    pub fn format(mut self, format: OutputFormat) -> Self {
188        self.format = format;
189        self
190    }
191
192    /// Set the color scheme preference.
193    ///
194    /// This affects CSS media queries like `prefers-color-scheme`.
195    ///
196    /// # Example
197    ///
198    /// ```rust
199    /// use hyper_render::{Config, ColorScheme};
200    ///
201    /// let config = Config::new().color_scheme(ColorScheme::Dark);
202    /// ```
203    pub fn color_scheme(mut self, scheme: ColorScheme) -> Self {
204        self.color_scheme = scheme;
205        self
206    }
207
208    /// Enable automatic height detection.
209    ///
210    /// When enabled, the renderer will compute the actual content height
211    /// and use that instead of the configured height. This is useful for
212    /// rendering full-page content.
213    ///
214    /// # Example
215    ///
216    /// ```rust
217    /// use hyper_render::Config;
218    ///
219    /// let config = Config::new().auto_height(true);
220    /// ```
221    pub fn auto_height(mut self, auto: bool) -> Self {
222        self.auto_height = auto;
223        self
224    }
225
226    /// Set the background color as RGBA values.
227    ///
228    /// # Example
229    ///
230    /// ```rust
231    /// use hyper_render::Config;
232    ///
233    /// // Transparent background
234    /// let config = Config::new().background([0, 0, 0, 0]);
235    ///
236    /// // Light gray background
237    /// let config = Config::new().background([240, 240, 240, 255]);
238    /// ```
239    pub fn background(mut self, rgba: [u8; 4]) -> Self {
240        self.background = rgba;
241        self
242    }
243
244    /// Set a transparent background.
245    ///
246    /// Shorthand for `.background([0, 0, 0, 0])`.
247    pub fn transparent(self) -> Self {
248        self.background([0, 0, 0, 0])
249    }
250
251    /// Minimum supported width/height in pixels.
252    ///
253    /// Very small dimensions can cause overflow issues in the underlying
254    /// rendering engine.
255    pub const MIN_DIMENSION: u32 = 16;
256
257    /// Validate the configuration.
258    ///
259    /// Returns an error if any configuration values are invalid:
260    /// - Width must be at least 16
261    /// - Height must be at least 16
262    /// - Scale must be greater than 0
263    ///
264    /// This is called automatically by the render functions.
265    ///
266    /// # Example
267    ///
268    /// ```rust
269    /// use hyper_render::Config;
270    ///
271    /// let config = Config::new().width(0);
272    /// assert!(config.validate().is_err());
273    /// ```
274    pub fn validate(&self) -> Result<()> {
275        if self.width < Self::MIN_DIMENSION {
276            return Err(Error::InvalidConfig(format!(
277                "width must be at least {} pixels",
278                Self::MIN_DIMENSION
279            )));
280        }
281        if self.height < Self::MIN_DIMENSION {
282            return Err(Error::InvalidConfig(format!(
283                "height must be at least {} pixels",
284                Self::MIN_DIMENSION
285            )));
286        }
287        if self.scale <= 0.0 {
288            return Err(Error::InvalidConfig(
289                "scale must be greater than 0".to_string(),
290            ));
291        }
292        if !self.scale.is_finite() {
293            return Err(Error::InvalidConfig(
294                "scale must be a finite number".to_string(),
295            ));
296        }
297        Ok(())
298    }
299}