1use std::sync::Arc;
9
10use rpdfium_core::{Matrix, Rect};
11
12use crate::color_convert::RgbaColor;
13
14pub trait RenderProgress: Send + Sync {
16 fn on_tile_complete(&self, tile_x: u32, tile_y: u32, total_tiles: u32) -> bool;
19}
20
21#[derive(Debug, Clone, PartialEq)]
29pub struct ColorScheme {
30 pub text_color: RgbaColor,
32 pub background_color: RgbaColor,
34}
35
36impl ColorScheme {
37 pub fn high_contrast() -> Self {
39 Self {
40 text_color: RgbaColor {
41 r: 0,
42 g: 0,
43 b: 0,
44 a: 255,
45 },
46 background_color: RgbaColor {
47 r: 255,
48 g: 255,
49 b: 255,
50 a: 255,
51 },
52 }
53 }
54}
55
56#[derive(Clone)]
58pub struct RenderConfig {
59 pub width: u32,
61 pub height: u32,
63 pub background: RgbaColor,
65 pub media_box: Option<Rect>,
73 pub rotation: u32,
79 pub tile_size: Option<u32>,
82 pub progress: Option<Arc<dyn RenderProgress>>,
84 pub grayscale: bool,
86 pub antialiasing: bool,
88 pub text_antialiasing: bool,
92 pub path_antialiasing: bool,
96 pub image_antialiasing: bool,
100 pub custom_transform: Option<Matrix>,
105 pub clip_rect: Option<Rect>,
115 pub forced_color_scheme: Option<ColorScheme>,
118}
119
120impl std::fmt::Debug for RenderConfig {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 f.debug_struct("RenderConfig")
123 .field("width", &self.width)
124 .field("height", &self.height)
125 .field("background", &self.background)
126 .field("media_box", &self.media_box)
127 .field("rotation", &self.rotation)
128 .field("tile_size", &self.tile_size)
129 .field("progress", &self.progress.as_ref().map(|_| "..."))
130 .field("grayscale", &self.grayscale)
131 .field("antialiasing", &self.antialiasing)
132 .field("text_antialiasing", &self.text_antialiasing)
133 .field("path_antialiasing", &self.path_antialiasing)
134 .field("image_antialiasing", &self.image_antialiasing)
135 .field("custom_transform", &self.custom_transform)
136 .field("clip_rect", &self.clip_rect)
137 .field("forced_color_scheme", &self.forced_color_scheme)
138 .finish()
139 }
140}
141
142impl RenderConfig {
143 pub fn with_size(mut self, width: u32, height: u32) -> Self {
145 self.width = width;
146 self.height = height;
147 self
148 }
149
150 pub fn with_background(mut self, bg: RgbaColor) -> Self {
152 self.background = bg;
153 self
154 }
155
156 pub fn with_media_box(mut self, media_box: Rect) -> Self {
158 self.media_box = Some(media_box);
159 self
160 }
161
162 pub fn with_rotation(mut self, rotation: u32) -> Self {
164 self.rotation = rotation;
165 self
166 }
167
168 pub fn with_tile_size(mut self, size: u32) -> Self {
170 self.tile_size = Some(size);
171 self
172 }
173
174 pub fn with_progress(mut self, progress: Arc<dyn RenderProgress>) -> Self {
176 self.progress = Some(progress);
177 self
178 }
179
180 pub fn with_grayscale(mut self, grayscale: bool) -> Self {
182 self.grayscale = grayscale;
183 self
184 }
185
186 pub fn with_antialiasing(mut self, antialiasing: bool) -> Self {
188 self.antialiasing = antialiasing;
189 self
190 }
191
192 pub fn with_text_antialiasing(mut self, aa: bool) -> Self {
194 self.text_antialiasing = aa;
195 self
196 }
197
198 pub fn with_path_antialiasing(mut self, aa: bool) -> Self {
200 self.path_antialiasing = aa;
201 self
202 }
203
204 pub fn with_image_antialiasing(mut self, aa: bool) -> Self {
206 self.image_antialiasing = aa;
207 self
208 }
209
210 pub fn with_transform(mut self, matrix: Matrix) -> Self {
218 self.custom_transform = Some(matrix);
219 self
220 }
221
222 pub fn with_clip(mut self, rect: Rect) -> Self {
230 self.clip_rect = Some(rect);
231 self
232 }
233
234 pub fn with_forced_colors(mut self, text: RgbaColor, background: RgbaColor) -> Self {
241 self.forced_color_scheme = Some(ColorScheme {
242 text_color: text,
243 background_color: background,
244 });
245 self
246 }
247}
248
249impl Default for RenderConfig {
250 fn default() -> Self {
251 Self {
252 width: 612,
253 height: 792,
254 background: RgbaColor::WHITE,
255 media_box: None,
256 rotation: 0,
257 tile_size: None,
258 progress: None,
259 grayscale: false,
260 antialiasing: true,
261 text_antialiasing: true,
262 path_antialiasing: true,
263 image_antialiasing: true,
264 custom_transform: None,
265 clip_rect: None,
266 forced_color_scheme: None,
267 }
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn test_default_config() {
277 let config = RenderConfig::default();
278 assert_eq!(config.width, 612);
279 assert_eq!(config.height, 792);
280 assert_eq!(config.background, RgbaColor::WHITE);
281 assert!(config.media_box.is_none());
282 assert_eq!(config.rotation, 0);
283 assert!(config.tile_size.is_none());
284 assert!(config.progress.is_none());
285 assert!(!config.grayscale);
286 assert!(config.antialiasing);
287 assert!(config.text_antialiasing);
288 assert!(config.path_antialiasing);
289 assert!(config.image_antialiasing);
290 }
291
292 #[test]
293 fn test_builder_with_size() {
294 let config = RenderConfig::default().with_size(1024, 768);
295 assert_eq!(config.width, 1024);
296 assert_eq!(config.height, 768);
297 }
298
299 #[test]
300 fn test_builder_with_background() {
301 let bg = RgbaColor {
302 r: 255,
303 g: 0,
304 b: 0,
305 a: 255,
306 };
307 let config = RenderConfig::default().with_background(bg);
308 assert_eq!(config.background.r, 255);
309 assert_eq!(config.background.g, 0);
310 }
311
312 #[test]
313 fn test_builder_with_media_box() {
314 let rect = Rect::new(0.0, 0.0, 612.0, 792.0);
315 let config = RenderConfig::default().with_media_box(rect);
316 assert!(config.media_box.is_some());
317 let mb = config.media_box.unwrap();
318 assert_eq!(mb.right, 612.0);
319 assert_eq!(mb.top, 792.0);
320 }
321
322 #[test]
323 fn test_builder_with_rotation() {
324 let config = RenderConfig::default().with_rotation(90);
325 assert_eq!(config.rotation, 90);
326 }
327
328 #[test]
329 fn test_builder_chaining() {
330 let config = RenderConfig::default()
331 .with_size(800, 600)
332 .with_rotation(180)
333 .with_background(RgbaColor {
334 r: 0,
335 g: 0,
336 b: 0,
337 a: 255,
338 });
339 assert_eq!(config.width, 800);
340 assert_eq!(config.height, 600);
341 assert_eq!(config.rotation, 180);
342 assert_eq!(config.background.r, 0);
343 }
344
345 #[test]
346 fn test_config_is_send_sync() {
347 fn assert_send_sync<T: Send + Sync>() {}
348 assert_send_sync::<RenderConfig>();
349 }
350
351 #[test]
352 fn test_builder_with_tile_size() {
353 let config = RenderConfig::default().with_tile_size(256);
354 assert_eq!(config.tile_size, Some(256));
355 }
356
357 #[test]
358 fn test_builder_with_progress() {
359 struct TestProgress;
360 impl RenderProgress for TestProgress {
361 fn on_tile_complete(&self, _tx: u32, _ty: u32, _total: u32) -> bool {
362 true
363 }
364 }
365 let config = RenderConfig::default().with_progress(Arc::new(TestProgress));
366 assert!(config.progress.is_some());
367 }
368
369 #[test]
370 fn test_builder_with_grayscale() {
371 let config = RenderConfig::default().with_grayscale(true);
372 assert!(config.grayscale);
373 }
374
375 #[test]
376 fn test_builder_with_antialiasing() {
377 let config = RenderConfig::default().with_antialiasing(false);
378 assert!(!config.antialiasing);
379 }
380
381 #[test]
382 fn test_grayscale_conversion() {
383 let r: u32 = 255;
385 let g: u32 = 0;
386 let b: u32 = 0;
387 let gray = (r * 299 + g * 587 + b * 114) / 1000;
388 assert_eq!(gray, 76); let gray_white = (255 * 299 + 255 * 587 + 255 * 114) / 1000;
391 assert_eq!(gray_white, 255);
392
393 let gray_black = 0; assert_eq!(gray_black, 0);
395 }
396
397 #[test]
398 fn test_color_scheme_high_contrast() {
399 let scheme = ColorScheme::high_contrast();
400 assert_eq!(
401 scheme.text_color,
402 RgbaColor {
403 r: 0,
404 g: 0,
405 b: 0,
406 a: 255
407 }
408 );
409 assert_eq!(
410 scheme.background_color,
411 RgbaColor {
412 r: 255,
413 g: 255,
414 b: 255,
415 a: 255
416 }
417 );
418 }
419
420 #[test]
421 fn test_render_config_with_forced_colors_builder() {
422 let text = RgbaColor {
423 r: 255,
424 g: 255,
425 b: 0,
426 a: 255,
427 };
428 let bg = RgbaColor {
429 r: 0,
430 g: 0,
431 b: 128,
432 a: 255,
433 };
434 let config = RenderConfig::default().with_forced_colors(text, bg);
435 assert!(config.forced_color_scheme.is_some());
436 let scheme = config.forced_color_scheme.unwrap();
437 assert_eq!(scheme.text_color.r, 255);
438 assert_eq!(scheme.text_color.g, 255);
439 assert_eq!(scheme.background_color.b, 128);
440 }
441
442 #[test]
443 fn test_default_config_has_no_forced_colors() {
444 let config = RenderConfig::default();
445 assert!(config.forced_color_scheme.is_none());
446 }
447
448 #[test]
449 fn test_render_config_per_feature_aa_defaults() {
450 let config = RenderConfig::default();
451 assert!(config.text_antialiasing);
452 assert!(config.path_antialiasing);
453 assert!(config.image_antialiasing);
454 }
455
456 #[test]
457 fn test_render_config_with_path_antialiasing_false_builder() {
458 let config = RenderConfig::default().with_path_antialiasing(false);
459 assert!(!config.path_antialiasing);
460 assert!(config.text_antialiasing);
462 assert!(config.image_antialiasing);
463 }
464}