Skip to main content

easypdf_core/
style.rs

1//! 样式类型——颜色、字体和边框。
2
3use std::borrow::Cow;
4
5// --- 颜色 ---
6
7/// 表示不同颜色空间中的颜色。
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum PdfColor {
10    /// RGB 颜色,分量范围 0.0–1.0。
11    Rgb(f64, f64, f64),
12    /// 灰度颜色,分量范围 0.0–1.0。
13    Gray(f64),
14    /// CMYK 颜色,分量范围 0.0–1.0。
15    Cmyk(f64, f64, f64, f64),
16}
17
18impl Default for PdfColor {
19    fn default() -> Self {
20        Self::Rgb(0.0, 0.0, 0.0) // black
21    }
22}
23
24impl PdfColor {
25    /// 从 0–255 整数分量创建 RGB 颜色。
26    #[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    /// 黑色。
36    #[must_use]
37    pub const fn black() -> Self {
38        Self::Rgb(0.0, 0.0, 0.0)
39    }
40
41    /// 白色。
42    #[must_use]
43    pub const fn white() -> Self {
44        Self::Rgb(1.0, 1.0, 1.0)
45    }
46
47    /// 红色。
48    #[must_use]
49    pub const fn red() -> Self {
50        Self::Rgb(1.0, 0.0, 0.0)
51    }
52
53    /// 绿色。
54    #[must_use]
55    pub const fn green() -> Self {
56        Self::Rgb(0.0, 1.0, 0.0)
57    }
58
59    /// 蓝色。
60    #[must_use]
61    pub const fn blue() -> Self {
62        Self::Rgb(0.0, 0.0, 1.0)
63    }
64
65    /// 浅灰色(0.8)。
66    #[must_use]
67    pub const fn light_gray() -> Self {
68        Self::Gray(0.8)
69    }
70
71    /// 中灰色(0.5)。
72    #[must_use]
73    pub const fn gray() -> Self {
74        Self::Gray(0.5)
75    }
76}
77
78// --- 字体 ---
79
80/// 字体族规格。
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub enum FontFamily {
83    /// 14 种内置 PDF 字体之一。
84    BuiltIn(BuiltInFont),
85    /// 从 TTF/OTF 文件路径加载的自定义字体。
86    Custom(Cow<'static, str>),
87}
88
89/// 保证在每个 PDF 阅读器中可用的 14 种标准 Type 1 字体。
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum BuiltInFont {
92    /// Times-Roman(衬线体)。
93    TimesRoman,
94    /// Times-Bold。
95    TimesBold,
96    /// Times-Italic。
97    TimesItalic,
98    /// Times-BoldItalic。
99    TimesBoldItalic,
100    /// Helvetica(无衬线体)。
101    Helvetica,
102    /// Helvetica-Bold。
103    HelveticaBold,
104    /// Helvetica-Oblique。
105    HelveticaOblique,
106    /// Helvetica-BoldOblique。
107    HelveticaBoldOblique,
108    /// Courier(等宽体)。
109    Courier,
110    /// Courier-Bold。
111    CourierBold,
112    /// Courier-Oblique。
113    CourierOblique,
114    /// Courier-BoldOblique。
115    CourierBoldOblique,
116    /// Symbol。
117    Symbol,
118    /// `ZapfDingbats`。
119    ZapfDingbats,
120}
121
122/// 字体样式修饰符。
123#[derive(Debug, Clone, Copy, Default)]
124pub struct FontStyle {
125    /// 粗体。
126    pub bold: bool,
127    /// 斜体/倾斜。
128    pub italic: bool,
129}
130
131/// 完整的字体规格。
132#[derive(Debug, Clone)]
133pub struct PdfFont {
134    /// 字体族名称或路径。
135    pub family: FontFamily,
136    /// 字体大小(PDF 点)。
137    pub size: f64,
138    /// 粗体和/或斜体。
139    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    /// 使用给定大小的 Helvetica 字体。
154    #[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    /// 使用给定大小的 Times-Roman 字体。
167    #[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    /// 使用给定大小的 Courier 字体。
180    #[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    /// 设置字体大小。
193    #[must_use]
194    pub fn with_size(mut self, size: f64) -> Self {
195        self.size = size;
196        self
197    }
198
199    /// 启用粗体。
200    #[must_use]
201    pub fn bold(mut self) -> Self {
202        self.style.bold = true;
203        self
204    }
205
206    /// 启用斜体。
207    #[must_use]
208    pub fn italic(mut self) -> Self {
209        self.style.italic = true;
210        self
211    }
212}
213
214// --- 边框 ---
215
216/// 表格单元格边框定义。
217#[derive(Debug, Clone, Copy)]
218pub struct TableBorder {
219    /// 边框宽度(PDF 点,0 = 无边框)。
220    pub width: f64,
221    /// 边框颜色。
222    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/// 预定义表格样式。
235#[derive(Debug, Clone)]
236pub struct TableStyle {
237    /// 表头背景颜色。
238    pub header_bg: Option<PdfColor>,
239    /// 表头字体。
240    pub header_font: PdfFont,
241    /// 正文字体。
242    pub body_font: PdfFont,
243    /// 单元格边框。
244    pub border: TableBorder,
245    /// 是否使用交替行颜色。
246    pub striped: bool,
247    /// 交替行背景颜色(`striped` 为 `true` 时使用)。
248    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    /// 创建无背景色、细边框的简洁表格样式。
266    #[must_use]
267    pub fn simple() -> Self {
268        Self {
269            header_bg: None,
270            striped: false,
271            ..Default::default()
272        }
273    }
274
275    /// 创建带交替(条纹)行颜色的表格样式。
276    #[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}