1use crate::error::PdfError;
6use crate::graphics::Color;
7use crate::page::Page;
8use crate::text::{BulletStyle, Font, ListOptions, OrderedList, OrderedListStyle, UnorderedList};
9
10pub trait PageLists {
12 fn add_ordered_list(
14 &mut self,
15 list: &OrderedList,
16 x: f64,
17 y: f64,
18 ) -> Result<&mut Self, PdfError>;
19
20 fn add_unordered_list(
22 &mut self,
23 list: &UnorderedList,
24 x: f64,
25 y: f64,
26 ) -> Result<&mut Self, PdfError>;
27
28 fn add_quick_ordered_list(
30 &mut self,
31 items: Vec<String>,
32 x: f64,
33 y: f64,
34 style: OrderedListStyle,
35 ) -> Result<&mut Self, PdfError>;
36
37 fn add_quick_unordered_list(
39 &mut self,
40 items: Vec<String>,
41 x: f64,
42 y: f64,
43 bullet: BulletStyle,
44 ) -> Result<&mut Self, PdfError>;
45
46 fn add_styled_ordered_list(
48 &mut self,
49 items: Vec<String>,
50 x: f64,
51 y: f64,
52 style: ListStyle,
53 ) -> Result<&mut Self, PdfError>;
54
55 fn add_styled_unordered_list(
57 &mut self,
58 items: Vec<String>,
59 x: f64,
60 y: f64,
61 style: ListStyle,
62 ) -> Result<&mut Self, PdfError>;
63}
64
65#[derive(Debug, Clone)]
67pub struct ListStyle {
68 pub list_type: ListType,
70 pub font: Font,
72 pub font_size: f64,
74 pub text_color: Color,
76 pub marker_color: Option<Color>,
78 pub max_width: Option<f64>,
80 pub line_spacing: f64,
82 pub indent: f64,
84 pub paragraph_spacing: f64,
86 pub draw_separator: bool,
88}
89
90#[derive(Debug, Clone, Copy)]
92pub enum ListType {
93 Ordered(OrderedListStyle),
95 Unordered(BulletStyle),
97}
98
99impl ListStyle {
100 pub fn minimal(list_type: ListType) -> Self {
102 Self {
103 list_type,
104 font: Font::Helvetica,
105 font_size: 10.0,
106 text_color: Color::black(),
107 marker_color: None,
108 max_width: None,
109 line_spacing: 1.2,
110 indent: 20.0,
111 paragraph_spacing: 0.0,
112 draw_separator: false,
113 }
114 }
115
116 pub fn professional(list_type: ListType) -> Self {
118 Self {
119 list_type,
120 font: Font::Helvetica,
121 font_size: 11.0,
122 text_color: Color::gray(0.1),
123 marker_color: Some(Color::rgb(0.2, 0.4, 0.7)),
124 max_width: Some(500.0),
125 line_spacing: 1.3,
126 indent: 25.0,
127 paragraph_spacing: 3.0,
128 draw_separator: false,
129 }
130 }
131
132 pub fn document(list_type: ListType) -> Self {
134 Self {
135 list_type,
136 font: Font::TimesRoman,
137 font_size: 12.0,
138 text_color: Color::black(),
139 marker_color: None,
140 max_width: Some(450.0),
141 line_spacing: 1.5,
142 indent: 30.0,
143 paragraph_spacing: 5.0,
144 draw_separator: false,
145 }
146 }
147
148 pub fn presentation(list_type: ListType) -> Self {
150 Self {
151 list_type,
152 font: Font::HelveticaBold,
153 font_size: 14.0,
154 text_color: Color::gray(0.2),
155 marker_color: Some(Color::rgb(0.8, 0.2, 0.2)),
156 max_width: Some(600.0),
157 line_spacing: 1.6,
158 indent: 35.0,
159 paragraph_spacing: 8.0,
160 draw_separator: false,
161 }
162 }
163
164 pub fn checklist() -> Self {
166 Self {
167 list_type: ListType::Unordered(BulletStyle::Square),
168 font: Font::Helvetica,
169 font_size: 11.0,
170 text_color: Color::gray(0.1),
171 marker_color: Some(Color::gray(0.4)),
172 max_width: Some(500.0),
173 line_spacing: 1.4,
174 indent: 25.0,
175 paragraph_spacing: 5.0,
176 draw_separator: true,
177 }
178 }
179}
180
181impl PageLists for Page {
182 fn add_ordered_list(
183 &mut self,
184 list: &OrderedList,
185 x: f64,
186 y: f64,
187 ) -> Result<&mut Self, PdfError> {
188 let mut list_clone = list.clone();
189 list_clone.set_position(x, y);
190 list_clone.render(self.graphics())?;
191 Ok(self)
192 }
193
194 fn add_unordered_list(
195 &mut self,
196 list: &UnorderedList,
197 x: f64,
198 y: f64,
199 ) -> Result<&mut Self, PdfError> {
200 let mut list_clone = list.clone();
201 list_clone.set_position(x, y);
202 list_clone.render(self.graphics())?;
203 Ok(self)
204 }
205
206 fn add_quick_ordered_list(
207 &mut self,
208 items: Vec<String>,
209 x: f64,
210 y: f64,
211 style: OrderedListStyle,
212 ) -> Result<&mut Self, PdfError> {
213 let mut list = OrderedList::new(style);
214 for item in items {
215 list.add_item(item);
216 }
217 self.add_ordered_list(&list, x, y)
218 }
219
220 fn add_quick_unordered_list(
221 &mut self,
222 items: Vec<String>,
223 x: f64,
224 y: f64,
225 bullet: BulletStyle,
226 ) -> Result<&mut Self, PdfError> {
227 let mut list = UnorderedList::new(bullet);
228 for item in items {
229 list.add_item(item);
230 }
231 self.add_unordered_list(&list, x, y)
232 }
233
234 fn add_styled_ordered_list(
235 &mut self,
236 items: Vec<String>,
237 x: f64,
238 y: f64,
239 style: ListStyle,
240 ) -> Result<&mut Self, PdfError> {
241 if let ListType::Ordered(ordered_style) = style.list_type {
242 let mut list = OrderedList::new(ordered_style);
243
244 let options = ListOptions {
246 font: style.font,
247 font_size: style.font_size,
248 text_color: style.text_color,
249 marker_color: style.marker_color,
250 max_width: style.max_width,
251 line_spacing: style.line_spacing,
252 indent: style.indent,
253 paragraph_spacing: style.paragraph_spacing,
254 draw_separator: style.draw_separator,
255 ..Default::default()
256 };
257
258 list.set_options(options);
259
260 for item in items {
261 list.add_item(item);
262 }
263
264 self.add_ordered_list(&list, x, y)
265 } else {
266 Err(PdfError::InvalidFormat(
267 "Expected ordered list style".to_string(),
268 ))
269 }
270 }
271
272 fn add_styled_unordered_list(
273 &mut self,
274 items: Vec<String>,
275 x: f64,
276 y: f64,
277 style: ListStyle,
278 ) -> Result<&mut Self, PdfError> {
279 if let ListType::Unordered(bullet_style) = style.list_type {
280 let mut list = UnorderedList::new(bullet_style);
281
282 let options = ListOptions {
284 font: style.font,
285 font_size: style.font_size,
286 text_color: style.text_color,
287 marker_color: style.marker_color,
288 max_width: style.max_width,
289 line_spacing: style.line_spacing,
290 indent: style.indent,
291 paragraph_spacing: style.paragraph_spacing,
292 draw_separator: style.draw_separator,
293 ..Default::default()
294 };
295
296 list.set_options(options);
297
298 for item in items {
299 list.add_item(item);
300 }
301
302 self.add_unordered_list(&list, x, y)
303 } else {
304 Err(PdfError::InvalidFormat(
305 "Expected unordered list style".to_string(),
306 ))
307 }
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn test_page_lists_trait() {
317 let mut page = Page::a4();
318
319 let items = vec![
321 "First item".to_string(),
322 "Second item".to_string(),
323 "Third item".to_string(),
324 ];
325
326 let result =
327 page.add_quick_ordered_list(items.clone(), 50.0, 700.0, OrderedListStyle::Decimal);
328 assert!(result.is_ok());
329 }
330
331 #[test]
332 fn test_quick_unordered_list() {
333 let mut page = Page::a4();
334
335 let items = vec![
336 "Apple".to_string(),
337 "Banana".to_string(),
338 "Cherry".to_string(),
339 ];
340
341 let result = page.add_quick_unordered_list(items, 50.0, 700.0, BulletStyle::Disc);
342 assert!(result.is_ok());
343 }
344
345 #[test]
346 fn test_list_styles() {
347 let minimal = ListStyle::minimal(ListType::Ordered(OrderedListStyle::Decimal));
348 assert_eq!(minimal.font_size, 10.0);
349 assert!(minimal.marker_color.is_none());
350
351 let professional = ListStyle::professional(ListType::Unordered(BulletStyle::Circle));
352 assert_eq!(professional.font_size, 11.0);
353 assert!(professional.marker_color.is_some());
354
355 let document = ListStyle::document(ListType::Ordered(OrderedListStyle::UpperRoman));
356 assert_eq!(document.line_spacing, 1.5);
357
358 let presentation = ListStyle::presentation(ListType::Unordered(BulletStyle::Dash));
359 assert_eq!(presentation.font_size, 14.0);
360
361 let checklist = ListStyle::checklist();
362 assert!(checklist.draw_separator);
363 }
364
365 #[test]
366 fn test_styled_lists() {
367 let mut page = Page::a4();
368
369 let items = vec![
370 "Executive Summary".to_string(),
371 "Market Analysis".to_string(),
372 "Financial Projections".to_string(),
373 ];
374
375 let style = ListStyle::professional(ListType::Ordered(OrderedListStyle::UpperAlpha));
376 let result = page.add_styled_ordered_list(items, 50.0, 700.0, style);
377 assert!(result.is_ok());
378 }
379
380 #[test]
381 fn test_empty_list() {
382 let mut page = Page::a4();
383
384 let items: Vec<String> = vec![];
385 let result = page.add_quick_ordered_list(items, 50.0, 700.0, OrderedListStyle::Decimal);
386 assert!(result.is_ok());
387 }
388
389 #[test]
390 fn test_list_with_long_text() {
391 let mut page = Page::a4();
392
393 let items = vec![
394 "This is a very long list item that should wrap to multiple lines when rendered with a maximum width constraint".to_string(),
395 "Short item".to_string(),
396 ];
397
398 let mut style = ListStyle::professional(ListType::Ordered(OrderedListStyle::Decimal));
399 style.max_width = Some(300.0);
400
401 let result = page.add_styled_ordered_list(items, 50.0, 700.0, style);
402 assert!(result.is_ok());
403 }
404}