1use std::borrow::Cow;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum PdfColor {
10 Rgb(f64, f64, f64),
12 Gray(f64),
14 Cmyk(f64, f64, f64, f64),
16}
17
18impl Default for PdfColor {
19 fn default() -> Self {
20 Self::Rgb(0.0, 0.0, 0.0) }
22}
23
24impl PdfColor {
25 #[must_use]
27 pub fn rgb_u8(r: u8, g: u8, b: u8) -> Self {
28 Self::Rgb(
29 f64::from(r) / 255.0,
30 f64::from(g) / 255.0,
31 f64::from(b) / 255.0,
32 )
33 }
34
35 #[must_use]
37 pub const fn black() -> Self {
38 Self::Rgb(0.0, 0.0, 0.0)
39 }
40
41 #[must_use]
43 pub const fn white() -> Self {
44 Self::Rgb(1.0, 1.0, 1.0)
45 }
46
47 #[must_use]
49 pub const fn red() -> Self {
50 Self::Rgb(1.0, 0.0, 0.0)
51 }
52
53 #[must_use]
55 pub const fn green() -> Self {
56 Self::Rgb(0.0, 1.0, 0.0)
57 }
58
59 #[must_use]
61 pub const fn blue() -> Self {
62 Self::Rgb(0.0, 0.0, 1.0)
63 }
64
65 #[must_use]
67 pub const fn light_gray() -> Self {
68 Self::Gray(0.8)
69 }
70
71 #[must_use]
73 pub const fn gray() -> Self {
74 Self::Gray(0.5)
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq)]
82pub enum FontFamily {
83 BuiltIn(BuiltInFont),
85 Custom(Cow<'static, str>),
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum BuiltInFont {
92 TimesRoman,
94 TimesBold,
96 TimesItalic,
98 TimesBoldItalic,
100 Helvetica,
102 HelveticaBold,
104 HelveticaOblique,
106 HelveticaBoldOblique,
108 Courier,
110 CourierBold,
112 CourierOblique,
114 CourierBoldOblique,
116 Symbol,
118 ZapfDingbats,
120}
121
122#[derive(Debug, Clone, Copy, Default)]
124pub struct FontStyle {
125 pub bold: bool,
127 pub italic: bool,
129}
130
131#[derive(Debug, Clone)]
133pub struct PdfFont {
134 pub family: FontFamily,
136 pub size: f64,
138 pub style: FontStyle,
140}
141
142impl Default for PdfFont {
143 fn default() -> Self {
144 Self {
145 family: FontFamily::BuiltIn(BuiltInFont::Helvetica),
146 size: 12.0,
147 style: FontStyle::default(),
148 }
149 }
150}
151
152impl PdfFont {
153 #[must_use]
155 pub fn helvetica(size: f64) -> Self {
156 Self {
157 family: FontFamily::BuiltIn(BuiltInFont::Helvetica),
158 size,
159 style: FontStyle {
160 bold: false,
161 italic: false,
162 },
163 }
164 }
165
166 #[must_use]
168 pub fn times_roman(size: f64) -> Self {
169 Self {
170 family: FontFamily::BuiltIn(BuiltInFont::TimesRoman),
171 size,
172 style: FontStyle {
173 bold: false,
174 italic: false,
175 },
176 }
177 }
178
179 #[must_use]
181 pub fn courier(size: f64) -> Self {
182 Self {
183 family: FontFamily::BuiltIn(BuiltInFont::Courier),
184 size,
185 style: FontStyle {
186 bold: false,
187 italic: false,
188 },
189 }
190 }
191
192 #[must_use]
194 pub fn with_size(mut self, size: f64) -> Self {
195 self.size = size;
196 self
197 }
198
199 #[must_use]
201 pub fn bold(mut self) -> Self {
202 self.style.bold = true;
203 self
204 }
205
206 #[must_use]
208 pub fn italic(mut self) -> Self {
209 self.style.italic = true;
210 self
211 }
212}
213
214#[derive(Debug, Clone, Copy)]
218pub struct TableBorder {
219 pub width: f64,
221 pub color: PdfColor,
223}
224
225impl Default for TableBorder {
226 fn default() -> Self {
227 Self {
228 width: 0.5,
229 color: PdfColor::black(),
230 }
231 }
232}
233
234#[derive(Debug, Clone)]
236pub struct TableStyle {
237 pub header_bg: Option<PdfColor>,
239 pub header_font: PdfFont,
241 pub body_font: PdfFont,
243 pub border: TableBorder,
245 pub striped: bool,
247 pub stripe_color: PdfColor,
249}
250
251impl Default for TableStyle {
252 fn default() -> Self {
253 Self {
254 header_bg: Some(PdfColor::light_gray()),
255 header_font: PdfFont::helvetica(11.0).bold(),
256 body_font: PdfFont::helvetica(10.0),
257 border: TableBorder::default(),
258 striped: false,
259 stripe_color: PdfColor::Gray(0.95),
260 }
261 }
262}
263
264impl TableStyle {
265 #[must_use]
267 pub fn simple() -> Self {
268 Self {
269 header_bg: None,
270 striped: false,
271 ..Default::default()
272 }
273 }
274
275 #[must_use]
277 pub fn striped() -> Self {
278 Self {
279 header_bg: Some(PdfColor::Gray(0.7)),
280 striped: true,
281 ..Default::default()
282 }
283 }
284}
285
286#[cfg(test)]
287#[allow(clippy::uninlined_format_args, clippy::float_cmp)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn color_default_is_black() {
293 let c = PdfColor::default();
294 assert_eq!(c, PdfColor::Rgb(0.0, 0.0, 0.0));
295 }
296
297 #[test]
298 fn color_rgb_u8() {
299 let c = PdfColor::rgb_u8(255, 0, 0);
300 assert_eq!(c, PdfColor::Rgb(1.0, 0.0, 0.0));
301 }
302
303 #[test]
304 fn color_named() {
305 assert_eq!(PdfColor::black(), PdfColor::Rgb(0.0, 0.0, 0.0));
306 assert_eq!(PdfColor::white(), PdfColor::Rgb(1.0, 1.0, 1.0));
307 assert_eq!(PdfColor::red(), PdfColor::Rgb(1.0, 0.0, 0.0));
308 assert_eq!(PdfColor::green(), PdfColor::Rgb(0.0, 1.0, 0.0));
309 assert_eq!(PdfColor::blue(), PdfColor::Rgb(0.0, 0.0, 1.0));
310 }
311
312 #[test]
313 fn color_gray() {
314 assert_eq!(PdfColor::light_gray(), PdfColor::Gray(0.8));
315 assert_eq!(PdfColor::gray(), PdfColor::Gray(0.5));
316 }
317
318 #[test]
319 fn color_cmyk() {
320 let c = PdfColor::Cmyk(0.0, 1.0, 1.0, 0.0);
321 let _ = format!("{:?}", c);
322 }
323
324 #[test]
325 fn color_clone_copy() {
326 let c = PdfColor::red();
327 let copied = c;
328 assert_eq!(c, copied);
329 }
330
331 #[test]
332 fn font_default() {
333 let f = PdfFont::default();
334 assert_eq!(f.size, 12.0);
335 }
336
337 #[test]
338 fn font_helvetica() {
339 let f = PdfFont::helvetica(14.0);
340 assert_eq!(f.size, 14.0);
341 }
342
343 #[test]
344 fn font_times_roman() {
345 let f = PdfFont::times_roman(10.0);
346 assert_eq!(f.size, 10.0);
347 }
348
349 #[test]
350 fn font_courier() {
351 let f = PdfFont::courier(8.0);
352 assert_eq!(f.size, 8.0);
353 }
354
355 #[test]
356 fn font_with_size() {
357 let f = PdfFont::default().with_size(20.0);
358 assert_eq!(f.size, 20.0);
359 }
360
361 #[test]
362 fn font_bold() {
363 let f = PdfFont::default().bold();
364 assert!(f.style.bold);
365 }
366
367 #[test]
368 fn font_italic() {
369 let f = PdfFont::default().italic();
370 assert!(f.style.italic);
371 }
372
373 #[test]
374 fn font_debug() {
375 let f = PdfFont::default();
376 let _ = format!("{:?}", f);
377 }
378
379 #[test]
380 fn font_family_eq() {
381 assert_eq!(
382 FontFamily::BuiltIn(BuiltInFont::Helvetica),
383 FontFamily::BuiltIn(BuiltInFont::Helvetica)
384 );
385 assert_ne!(
386 FontFamily::BuiltIn(BuiltInFont::Helvetica),
387 FontFamily::BuiltIn(BuiltInFont::Courier)
388 );
389 }
390
391 #[test]
392 fn built_in_font_variants() {
393 assert_ne!(BuiltInFont::TimesRoman, BuiltInFont::TimesBold);
394 }
395
396 #[test]
397 fn font_style_default() {
398 let fs = FontStyle::default();
399 assert!(!fs.bold);
400 assert!(!fs.italic);
401 }
402
403 #[test]
404 fn table_border_default() {
405 let b = TableBorder::default();
406 assert_eq!(b.width, 0.5);
407 }
408
409 #[test]
410 fn table_style_default() {
411 let s = TableStyle::default();
412 assert!(s.header_bg.is_some());
413 assert!(!s.striped);
414 }
415
416 #[test]
417 fn table_style_simple() {
418 let s = TableStyle::simple();
419 assert!(s.header_bg.is_none());
420 assert!(!s.striped);
421 }
422
423 #[test]
424 fn table_style_striped() {
425 let s = TableStyle::striped();
426 assert!(s.header_bg.is_some());
427 assert!(s.striped);
428 }
429
430 #[test]
431 fn table_style_debug_clone() {
432 let s = TableStyle::default();
433 let cloned = s.clone();
434 assert_eq!(s.striped, cloned.striped);
435 let _ = format!("{:?}", s);
436 }
437}