1use crate::error::PdfError;
13use crate::graphics::{Color, GraphicsContext};
14use crate::text::{Font, TextAlign};
15
16#[derive(Debug, Clone, Copy, PartialEq)]
18pub enum OrderedListStyle {
19 Decimal,
21 LowerAlpha,
23 UpperAlpha,
25 LowerRoman,
27 UpperRoman,
29 DecimalLeadingZero,
31 GreekLower,
33 GreekUpper,
35 Hebrew,
37 Hiragana,
39 Katakana,
41 ChineseSimplified,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq)]
47pub enum BulletStyle {
48 Disc,
50 Circle,
52 Square,
54 Dash,
56 Custom(char),
58}
59
60#[derive(Debug, Clone, Copy, PartialEq)]
62pub enum ListStyle {
63 Ordered(OrderedListStyle),
65 Unordered(BulletStyle),
67}
68
69#[derive(Debug, Clone)]
71pub struct ListOptions {
72 pub font: Font,
74 pub font_size: f64,
76 pub text_color: Color,
78 pub indent: f64,
80 pub line_spacing: f64,
82 pub marker_spacing: f64,
84 pub max_width: Option<f64>,
86 pub text_align: TextAlign,
88 pub marker_font: Font,
90 pub marker_color: Option<Color>,
92 pub paragraph_spacing: f64,
94 pub draw_separator: bool,
96 pub separator_color: Color,
98 pub separator_width: f64,
100 pub marker_prefix: String,
102 pub marker_suffix: String,
104}
105
106impl Default for ListOptions {
107 fn default() -> Self {
108 Self {
109 font: Font::Helvetica,
110 font_size: 10.0,
111 text_color: Color::black(),
112 indent: 20.0,
113 line_spacing: 1.2,
114 marker_spacing: 10.0,
115 max_width: None,
116 text_align: TextAlign::Left,
117 marker_font: Font::Helvetica,
118 marker_color: None,
119 paragraph_spacing: 0.0,
120 draw_separator: false,
121 separator_color: Color::gray(0.8),
122 separator_width: 0.5,
123 marker_prefix: String::new(),
124 marker_suffix: ".".to_string(),
125 }
126 }
127}
128
129#[derive(Debug, Clone)]
131pub struct OrderedList {
132 items: Vec<ListItem>,
133 style: OrderedListStyle,
134 start_number: u32,
135 options: ListOptions,
136 position: (f64, f64),
137}
138
139#[derive(Debug, Clone)]
141pub struct UnorderedList {
142 items: Vec<ListItem>,
143 bullet_style: BulletStyle,
144 options: ListOptions,
145 position: (f64, f64),
146}
147
148#[derive(Debug, Clone)]
150pub struct ListItem {
151 text: String,
152 children: Vec<ListElement>,
153}
154
155#[derive(Debug, Clone)]
157pub enum ListElement {
158 Ordered(OrderedList),
159 Unordered(UnorderedList),
160}
161
162impl OrderedList {
163 pub fn new(style: OrderedListStyle) -> Self {
165 Self {
166 items: Vec::new(),
167 style,
168 start_number: 1,
169 options: ListOptions::default(),
170 position: (0.0, 0.0),
171 }
172 }
173
174 pub fn set_start_number(&mut self, start: u32) -> &mut Self {
176 self.start_number = start;
177 self
178 }
179
180 pub fn set_options(&mut self, options: ListOptions) -> &mut Self {
182 self.options = options;
183 self
184 }
185
186 pub fn set_position(&mut self, x: f64, y: f64) -> &mut Self {
188 self.position = (x, y);
189 self
190 }
191
192 pub fn add_item(&mut self, text: String) -> &mut Self {
194 self.items.push(ListItem {
195 text,
196 children: Vec::new(),
197 });
198 self
199 }
200
201 pub fn add_item_with_children(
203 &mut self,
204 text: String,
205 children: Vec<ListElement>,
206 ) -> &mut Self {
207 self.items.push(ListItem { text, children });
208 self
209 }
210
211 fn generate_marker(&self, index: usize) -> String {
213 let number = self.start_number + index as u32;
214 let marker_core = match self.style {
215 OrderedListStyle::Decimal => format!("{number}"),
216 OrderedListStyle::DecimalLeadingZero => format!("{number:02}"),
217 OrderedListStyle::LowerAlpha => {
218 let letter = char::from_u32('a' as u32 + (number - 1) % 26).unwrap_or('?');
219 format!("{letter}")
220 }
221 OrderedListStyle::UpperAlpha => {
222 let letter = char::from_u32('A' as u32 + (number - 1) % 26).unwrap_or('?');
223 format!("{letter}")
224 }
225 OrderedListStyle::LowerRoman => to_roman(number).to_lowercase(),
226 OrderedListStyle::UpperRoman => to_roman(number),
227 OrderedListStyle::GreekLower => get_greek_letter(number, false),
228 OrderedListStyle::GreekUpper => get_greek_letter(number, true),
229 OrderedListStyle::Hebrew => get_hebrew_letter(number),
230 OrderedListStyle::Hiragana => get_hiragana_letter(number),
231 OrderedListStyle::Katakana => get_katakana_letter(number),
232 OrderedListStyle::ChineseSimplified => get_chinese_number(number),
233 };
234
235 format!(
236 "{}{}{}",
237 self.options.marker_prefix, marker_core, self.options.marker_suffix
238 )
239 }
240
241 pub fn get_height(&self) -> f64 {
243 self.calculate_height_recursive(0)
244 }
245
246 fn calculate_height_recursive(&self, _level: usize) -> f64 {
247 let mut height = 0.0;
248 for item in &self.items {
249 height += self.options.font_size * self.options.line_spacing;
250 for child in &item.children {
251 height += match child {
252 ListElement::Ordered(list) => list.calculate_height_recursive(_level + 1),
253 ListElement::Unordered(list) => list.calculate_height_recursive(_level + 1),
254 };
255 }
256 }
257 height
258 }
259
260 pub fn render(&self, graphics: &mut GraphicsContext) -> Result<(), PdfError> {
262 let (x, y) = self.position;
263 self.render_recursive(graphics, x, y, 0)?;
264 Ok(())
265 }
266
267 fn render_recursive(
268 &self,
269 graphics: &mut GraphicsContext,
270 x: f64,
271 mut y: f64,
272 level: usize,
273 ) -> Result<f64, PdfError> {
274 let indent = x + (level as f64 * self.options.indent);
275
276 for (index, item) in self.items.iter().enumerate() {
277 let marker = self.generate_marker(index);
279 graphics.save_state();
280 graphics.set_font(self.options.marker_font.clone(), self.options.font_size);
281 let marker_color = self.options.marker_color.unwrap_or(self.options.text_color);
282 graphics.set_fill_color(marker_color);
283 graphics.begin_text();
284 graphics.set_text_position(indent, y);
285 graphics.show_text(&marker)?;
286 graphics.end_text();
287 graphics.restore_state();
288
289 let text_x =
291 indent + self.calculate_marker_width(&marker) + self.options.marker_spacing;
292
293 let text_lines = if let Some(max_width) = self.options.max_width {
294 let available_width = max_width - text_x;
295 self.wrap_text(&item.text, available_width)
296 } else {
297 vec![item.text.clone()]
298 };
299
300 let mut line_y = y;
302 for (line_index, line) in text_lines.iter().enumerate() {
303 graphics.save_state();
304 graphics.set_font(self.options.font.clone(), self.options.font_size);
305 graphics.set_fill_color(self.options.text_color);
306 graphics.begin_text();
307
308 let line_x = if line_index == 0 {
310 text_x
311 } else {
312 text_x + self.options.font_size };
314
315 graphics.set_text_position(line_x, line_y);
316 graphics.show_text(line)?;
317 graphics.end_text();
318 graphics.restore_state();
319
320 if line_index < text_lines.len() - 1 {
321 line_y += self.options.font_size * self.options.line_spacing;
322 }
323 }
324
325 y = line_y;
326
327 y +=
328 self.options.font_size * self.options.line_spacing + self.options.paragraph_spacing;
329
330 if self.options.draw_separator && index < self.items.len() - 1 {
332 graphics.save_state();
333 graphics.set_stroke_color(self.options.separator_color);
334 graphics.set_line_width(self.options.separator_width);
335 graphics.move_to(indent, y + 2.0);
336 graphics.line_to(
337 indent + (self.options.max_width.unwrap_or(500.0) - indent),
338 y + 2.0,
339 );
340 graphics.stroke();
341 graphics.restore_state();
342 y += 5.0;
343 }
344
345 for child in &item.children {
347 y = match child {
348 ListElement::Ordered(list) => {
349 let mut child_list = list.clone();
350 child_list.options = self.options.clone();
351 child_list.render_recursive(graphics, x, y, level + 1)?
352 }
353 ListElement::Unordered(list) => {
354 let mut child_list = list.clone();
355 child_list.options = self.options.clone();
356 child_list.render_recursive(graphics, x, y, level + 1)?
357 }
358 };
359 }
360 }
361
362 Ok(y)
363 }
364
365 fn calculate_marker_width(&self, marker: &str) -> f64 {
366 marker.len() as f64 * self.options.font_size * 0.5
368 }
369
370 fn wrap_text(&self, text: &str, max_width: f64) -> Vec<String> {
372 let avg_char_width = self.options.font_size * 0.5;
375 let chars_per_line = (max_width / avg_char_width) as usize;
376
377 if chars_per_line == 0 || text.len() <= chars_per_line {
378 return vec![text.to_string()];
379 }
380
381 let mut lines = Vec::new();
382 let words: Vec<&str> = text.split_whitespace().collect();
383 let mut current_line = String::new();
384
385 for word in words {
386 let test_line = if current_line.is_empty() {
387 word.to_string()
388 } else {
389 format!("{current_line} {word}")
390 };
391
392 if test_line.len() <= chars_per_line {
393 current_line = test_line;
394 } else {
395 if !current_line.is_empty() {
396 lines.push(current_line);
397 }
398 current_line = word.to_string();
399 }
400 }
401
402 if !current_line.is_empty() {
403 lines.push(current_line);
404 }
405
406 lines
407 }
408}
409
410impl UnorderedList {
411 pub fn new(bullet_style: BulletStyle) -> Self {
413 Self {
414 items: Vec::new(),
415 bullet_style,
416 options: ListOptions::default(),
417 position: (0.0, 0.0),
418 }
419 }
420
421 pub fn set_options(&mut self, options: ListOptions) -> &mut Self {
423 self.options = options;
424 self
425 }
426
427 pub fn set_position(&mut self, x: f64, y: f64) -> &mut Self {
429 self.position = (x, y);
430 self
431 }
432
433 pub fn add_item(&mut self, text: String) -> &mut Self {
435 self.items.push(ListItem {
436 text,
437 children: Vec::new(),
438 });
439 self
440 }
441
442 pub fn add_item_with_children(
444 &mut self,
445 text: String,
446 children: Vec<ListElement>,
447 ) -> &mut Self {
448 self.items.push(ListItem { text, children });
449 self
450 }
451
452 fn get_bullet_char(&self) -> &str {
454 match self.bullet_style {
455 BulletStyle::Disc => "•",
456 BulletStyle::Circle => "○",
457 BulletStyle::Square => "■",
458 BulletStyle::Dash => "-",
459 BulletStyle::Custom(ch) => {
460 match ch {
462 '→' => "→",
463 '▸' => "▸",
464 '▹' => "▹",
465 '★' => "★",
466 '☆' => "☆",
467 _ => "•", }
469 }
470 }
471 }
472
473 pub fn get_height(&self) -> f64 {
475 self.calculate_height_recursive(0)
476 }
477
478 fn calculate_height_recursive(&self, _level: usize) -> f64 {
479 let mut height = 0.0;
480 for item in &self.items {
481 height += self.options.font_size * self.options.line_spacing;
482 for child in &item.children {
483 height += match child {
484 ListElement::Ordered(list) => list.calculate_height_recursive(_level + 1),
485 ListElement::Unordered(list) => list.calculate_height_recursive(_level + 1),
486 };
487 }
488 }
489 height
490 }
491
492 pub fn render(&self, graphics: &mut GraphicsContext) -> Result<(), PdfError> {
494 let (x, y) = self.position;
495 self.render_recursive(graphics, x, y, 0)?;
496 Ok(())
497 }
498
499 fn render_recursive(
500 &self,
501 graphics: &mut GraphicsContext,
502 x: f64,
503 mut y: f64,
504 level: usize,
505 ) -> Result<f64, PdfError> {
506 let indent = x + (level as f64 * self.options.indent);
507 let bullet = self.get_bullet_char();
508
509 for (index, item) in self.items.iter().enumerate() {
510 graphics.save_state();
512 graphics.set_font(self.options.marker_font.clone(), self.options.font_size);
513 let marker_color = self.options.marker_color.unwrap_or(self.options.text_color);
514 graphics.set_fill_color(marker_color);
515 graphics.begin_text();
516 graphics.set_text_position(indent, y);
517 graphics.show_text(bullet)?;
518 graphics.end_text();
519 graphics.restore_state();
520
521 let text_x = indent + self.options.font_size + self.options.marker_spacing;
523
524 let text_lines = if let Some(max_width) = self.options.max_width {
525 let available_width = max_width - text_x;
526 self.wrap_text(&item.text, available_width)
527 } else {
528 vec![item.text.clone()]
529 };
530
531 let mut line_y = y;
533 for (line_index, line) in text_lines.iter().enumerate() {
534 graphics.save_state();
535 graphics.set_font(self.options.font.clone(), self.options.font_size);
536 graphics.set_fill_color(self.options.text_color);
537 graphics.begin_text();
538
539 let line_x = if line_index == 0 {
541 text_x
542 } else {
543 text_x + self.options.font_size };
545
546 graphics.set_text_position(line_x, line_y);
547 graphics.show_text(line)?;
548 graphics.end_text();
549 graphics.restore_state();
550
551 if line_index < text_lines.len() - 1 {
552 line_y += self.options.font_size * self.options.line_spacing;
553 }
554 }
555
556 y = line_y;
557
558 y +=
559 self.options.font_size * self.options.line_spacing + self.options.paragraph_spacing;
560
561 if self.options.draw_separator && (index < self.items.len() - 1) {
563 graphics.save_state();
564 graphics.set_stroke_color(self.options.separator_color);
565 graphics.set_line_width(self.options.separator_width);
566 graphics.move_to(indent, y + 2.0);
567 graphics.line_to(
568 indent + (self.options.max_width.unwrap_or(500.0) - indent),
569 y + 2.0,
570 );
571 graphics.stroke();
572 graphics.restore_state();
573 y += 5.0;
574 }
575
576 for child in &item.children {
578 y = match child {
579 ListElement::Ordered(list) => {
580 let mut child_list = list.clone();
581 child_list.options = self.options.clone();
582 child_list.render_recursive(graphics, x, y, level + 1)?
583 }
584 ListElement::Unordered(list) => {
585 let mut child_list = list.clone();
586 child_list.options = self.options.clone();
587 child_list.render_recursive(graphics, x, y, level + 1)?
588 }
589 };
590 }
591 }
592
593 Ok(y)
594 }
595
596 fn wrap_text(&self, text: &str, max_width: f64) -> Vec<String> {
598 let avg_char_width = self.options.font_size * 0.5;
601 let chars_per_line = (max_width / avg_char_width) as usize;
602
603 if chars_per_line == 0 || text.len() <= chars_per_line {
604 return vec![text.to_string()];
605 }
606
607 let mut lines = Vec::new();
608 let words: Vec<&str> = text.split_whitespace().collect();
609 let mut current_line = String::new();
610
611 for word in words {
612 let test_line = if current_line.is_empty() {
613 word.to_string()
614 } else {
615 format!("{current_line} {word}")
616 };
617
618 if test_line.len() <= chars_per_line {
619 current_line = test_line;
620 } else {
621 if !current_line.is_empty() {
622 lines.push(current_line);
623 }
624 current_line = word.to_string();
625 }
626 }
627
628 if !current_line.is_empty() {
629 lines.push(current_line);
630 }
631
632 lines
633 }
634}
635
636fn to_roman(num: u32) -> String {
638 let values = [
639 (1000, "M"),
640 (900, "CM"),
641 (500, "D"),
642 (400, "CD"),
643 (100, "C"),
644 (90, "XC"),
645 (50, "L"),
646 (40, "XL"),
647 (10, "X"),
648 (9, "IX"),
649 (5, "V"),
650 (4, "IV"),
651 (1, "I"),
652 ];
653
654 let mut result = String::new();
655 let mut n = num;
656
657 for (value, numeral) in &values {
658 while n >= *value {
659 result.push_str(numeral);
660 n -= value;
661 }
662 }
663
664 result
665}
666
667fn get_greek_letter(num: u32, uppercase: bool) -> String {
669 let lower = [
670 "α", "β", "γ", "δ", "ε", "ζ", "η", "θ", "ι", "κ", "λ", "μ", "ν", "ξ", "ο", "π", "ρ", "σ",
671 "τ", "υ", "φ", "χ", "ψ", "ω",
672 ];
673 let upper = [
674 "Α", "Β", "Γ", "Δ", "Ε", "Ζ", "Η", "Θ", "Ι", "Κ", "Λ", "Μ", "Ν", "Ξ", "Ο", "Π", "Ρ", "Σ",
675 "Τ", "Υ", "Φ", "Χ", "Ψ", "Ω",
676 ];
677
678 let index = ((num - 1) % 24) as usize;
679 if uppercase {
680 upper[index].to_string()
681 } else {
682 lower[index].to_string()
683 }
684}
685
686fn get_hebrew_letter(num: u32) -> String {
688 let letters = [
689 "א", "ב", "ג", "ד", "ה", "ו", "ז", "ח", "ט", "י", "כ", "ל", "מ", "נ", "ס", "ע", "פ", "צ",
690 "ק", "ר", "ש", "ת",
691 ];
692 let index = ((num - 1) % 22) as usize;
693 letters[index].to_string()
694}
695
696fn get_hiragana_letter(num: u32) -> String {
698 let letters = [
699 "あ", "い", "う", "え", "お", "か", "き", "く", "け", "こ", "さ", "し", "す", "せ", "そ",
700 "た", "ち", "つ", "て", "と", "な", "に", "ぬ", "ね", "の", "は", "ひ", "ふ", "へ", "ほ",
701 "ま", "み", "む", "め", "も", "や", "ゆ", "よ", "ら", "り", "る", "れ", "ろ", "わ", "を",
702 "ん",
703 ];
704 let index = ((num - 1) % 46) as usize;
705 letters[index].to_string()
706}
707
708fn get_katakana_letter(num: u32) -> String {
710 let letters = [
711 "ア", "イ", "ウ", "エ", "オ", "カ", "キ", "ク", "ケ", "コ", "サ", "シ", "ス", "セ", "ソ",
712 "タ", "チ", "ツ", "テ", "ト", "ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "ヒ", "フ", "ヘ", "ホ",
713 "マ", "ミ", "ム", "メ", "モ", "ヤ", "ユ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ワ", "ヲ",
714 "ン",
715 ];
716 let index = ((num - 1) % 46) as usize;
717 letters[index].to_string()
718}
719
720fn get_chinese_number(num: u32) -> String {
722 let numbers = [
723 "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二", "十三", "十四",
724 "十五", "十六", "十七", "十八", "十九", "二十",
725 ];
726 if num <= 20 {
727 numbers[(num - 1) as usize].to_string()
728 } else {
729 format!("{num}") }
731}
732
733#[cfg(test)]
734mod tests {
735 use super::*;
736
737 #[test]
738 fn test_ordered_list_creation() {
739 let list = OrderedList::new(OrderedListStyle::Decimal);
740 assert_eq!(list.style, OrderedListStyle::Decimal);
741 assert_eq!(list.start_number, 1);
742 assert!(list.items.is_empty());
743 }
744
745 #[test]
746 fn test_unordered_list_creation() {
747 let list = UnorderedList::new(BulletStyle::Disc);
748 assert_eq!(list.bullet_style, BulletStyle::Disc);
749 assert!(list.items.is_empty());
750 }
751
752 #[test]
753 fn test_add_items() {
754 let mut list = OrderedList::new(OrderedListStyle::Decimal);
755 list.add_item("First item".to_string())
756 .add_item("Second item".to_string());
757 assert_eq!(list.items.len(), 2);
758 assert_eq!(list.items[0].text, "First item");
759 assert_eq!(list.items[1].text, "Second item");
760 }
761
762 #[test]
763 fn test_marker_generation_decimal() {
764 let list = OrderedList::new(OrderedListStyle::Decimal);
765 assert_eq!(list.generate_marker(0), "1.");
766 assert_eq!(list.generate_marker(1), "2.");
767 assert_eq!(list.generate_marker(9), "10.");
768 }
769
770 #[test]
771 fn test_marker_generation_lower_alpha() {
772 let list = OrderedList::new(OrderedListStyle::LowerAlpha);
773 assert_eq!(list.generate_marker(0), "a.");
774 assert_eq!(list.generate_marker(1), "b.");
775 assert_eq!(list.generate_marker(25), "z.");
776 }
777
778 #[test]
779 fn test_marker_generation_upper_alpha() {
780 let list = OrderedList::new(OrderedListStyle::UpperAlpha);
781 assert_eq!(list.generate_marker(0), "A.");
782 assert_eq!(list.generate_marker(1), "B.");
783 assert_eq!(list.generate_marker(25), "Z.");
784 }
785
786 #[test]
787 fn test_marker_generation_roman() {
788 let list = OrderedList::new(OrderedListStyle::LowerRoman);
789 assert_eq!(list.generate_marker(0), "i.");
790 assert_eq!(list.generate_marker(3), "iv.");
791 assert_eq!(list.generate_marker(8), "ix.");
792
793 let list_upper = OrderedList::new(OrderedListStyle::UpperRoman);
794 assert_eq!(list_upper.generate_marker(0), "I.");
795 assert_eq!(list_upper.generate_marker(3), "IV.");
796 assert_eq!(list_upper.generate_marker(8), "IX.");
797 }
798
799 #[test]
800 fn test_start_number() {
801 let mut list = OrderedList::new(OrderedListStyle::Decimal);
802 list.set_start_number(5);
803 assert_eq!(list.generate_marker(0), "5.");
804 assert_eq!(list.generate_marker(1), "6.");
805 }
806
807 #[test]
808 fn test_bullet_styles() {
809 let disc = UnorderedList::new(BulletStyle::Disc);
810 assert_eq!(disc.get_bullet_char(), "•");
811
812 let circle = UnorderedList::new(BulletStyle::Circle);
813 assert_eq!(circle.get_bullet_char(), "○");
814
815 let square = UnorderedList::new(BulletStyle::Square);
816 assert_eq!(square.get_bullet_char(), "■");
817
818 let dash = UnorderedList::new(BulletStyle::Dash);
819 assert_eq!(dash.get_bullet_char(), "-");
820 }
821
822 #[test]
823 fn test_custom_bullet() {
824 let arrow = UnorderedList::new(BulletStyle::Custom('→'));
825 assert_eq!(arrow.get_bullet_char(), "→");
826
827 let star = UnorderedList::new(BulletStyle::Custom('★'));
828 assert_eq!(star.get_bullet_char(), "★");
829 }
830
831 #[test]
832 fn test_list_options_default() {
833 let options = ListOptions::default();
834 assert_eq!(options.font_size, 10.0);
835 assert_eq!(options.indent, 20.0);
836 assert_eq!(options.line_spacing, 1.2);
837 assert_eq!(options.marker_spacing, 10.0);
838 }
839
840 #[test]
841 fn test_roman_numerals() {
842 assert_eq!(to_roman(1), "I");
843 assert_eq!(to_roman(4), "IV");
844 assert_eq!(to_roman(5), "V");
845 assert_eq!(to_roman(9), "IX");
846 assert_eq!(to_roman(10), "X");
847 assert_eq!(to_roman(40), "XL");
848 assert_eq!(to_roman(50), "L");
849 assert_eq!(to_roman(90), "XC");
850 assert_eq!(to_roman(100), "C");
851 assert_eq!(to_roman(400), "CD");
852 assert_eq!(to_roman(500), "D");
853 assert_eq!(to_roman(900), "CM");
854 assert_eq!(to_roman(1000), "M");
855 assert_eq!(to_roman(1994), "MCMXCIV");
856 }
857
858 #[test]
859 fn test_nested_lists() {
860 let mut parent = OrderedList::new(OrderedListStyle::Decimal);
861
862 let mut child = UnorderedList::new(BulletStyle::Dash);
863 child.add_item("Nested item 1".to_string());
864 child.add_item("Nested item 2".to_string());
865
866 parent.add_item_with_children(
867 "Parent item".to_string(),
868 vec![ListElement::Unordered(child)],
869 );
870
871 assert_eq!(parent.items.len(), 1);
872 assert_eq!(parent.items[0].children.len(), 1);
873 }
874
875 #[test]
876 fn test_list_position() {
877 let mut list = OrderedList::new(OrderedListStyle::Decimal);
878 list.set_position(100.0, 200.0);
879 assert_eq!(list.position, (100.0, 200.0));
880 }
881
882 #[test]
883 fn test_list_height_calculation() {
884 let mut list = OrderedList::new(OrderedListStyle::Decimal);
885 list.add_item("Item 1".to_string())
886 .add_item("Item 2".to_string())
887 .add_item("Item 3".to_string());
888
889 let height = list.get_height();
890 let expected = 3.0 * list.options.font_size * list.options.line_spacing;
891 assert_eq!(height, expected);
892 }
893
894 #[test]
895 fn test_list_item_structure() {
896 let item = ListItem {
897 text: "Test item".to_string(),
898 children: vec![],
899 };
900 assert_eq!(item.text, "Test item");
901 assert!(item.children.is_empty());
902 }
903
904 #[test]
905 fn test_list_element_enum() {
906 let ordered = OrderedList::new(OrderedListStyle::Decimal);
907 let unordered = UnorderedList::new(BulletStyle::Disc);
908
909 let elements = vec![
910 ListElement::Ordered(ordered),
911 ListElement::Unordered(unordered),
912 ];
913
914 assert_eq!(elements.len(), 2);
915 match &elements[0] {
916 ListElement::Ordered(_) => (),
917 _ => panic!("Expected ordered list"),
918 }
919 match &elements[1] {
920 ListElement::Unordered(_) => (),
921 _ => panic!("Expected unordered list"),
922 }
923 }
924
925 #[test]
926 fn test_advanced_numbering_styles() {
927 let list = OrderedList::new(OrderedListStyle::DecimalLeadingZero);
929 assert_eq!(list.generate_marker(0), "01.");
930 assert_eq!(list.generate_marker(8), "09.");
931 assert_eq!(list.generate_marker(9), "10.");
932 assert_eq!(list.generate_marker(99), "100.");
933
934 let greek_lower = OrderedList::new(OrderedListStyle::GreekLower);
936 assert_eq!(greek_lower.generate_marker(0), "α.");
937 assert_eq!(greek_lower.generate_marker(1), "β.");
938 assert_eq!(greek_lower.generate_marker(23), "ω.");
939 assert_eq!(greek_lower.generate_marker(24), "α."); let greek_upper = OrderedList::new(OrderedListStyle::GreekUpper);
943 assert_eq!(greek_upper.generate_marker(0), "Α.");
944 assert_eq!(greek_upper.generate_marker(1), "Β.");
945 assert_eq!(greek_upper.generate_marker(23), "Ω.");
946
947 let hebrew = OrderedList::new(OrderedListStyle::Hebrew);
949 assert_eq!(hebrew.generate_marker(0), "א.");
950 assert_eq!(hebrew.generate_marker(1), "ב.");
951 assert_eq!(hebrew.generate_marker(21), "ת.");
952
953 let hiragana = OrderedList::new(OrderedListStyle::Hiragana);
955 assert_eq!(hiragana.generate_marker(0), "あ.");
956 assert_eq!(hiragana.generate_marker(1), "い.");
957 assert_eq!(hiragana.generate_marker(4), "お.");
958
959 let katakana = OrderedList::new(OrderedListStyle::Katakana);
961 assert_eq!(katakana.generate_marker(0), "ア.");
962 assert_eq!(katakana.generate_marker(1), "イ.");
963 assert_eq!(katakana.generate_marker(4), "オ.");
964
965 let chinese = OrderedList::new(OrderedListStyle::ChineseSimplified);
967 assert_eq!(chinese.generate_marker(0), "一.");
968 assert_eq!(chinese.generate_marker(1), "二.");
969 assert_eq!(chinese.generate_marker(9), "十.");
970 assert_eq!(chinese.generate_marker(19), "二十.");
971 assert_eq!(chinese.generate_marker(20), "21."); }
973
974 #[test]
975 fn test_custom_prefix_suffix() {
976 let mut list = OrderedList::new(OrderedListStyle::Decimal);
977 let mut options = ListOptions::default();
978 options.marker_prefix = "Chapter ".to_string();
979 options.marker_suffix = ":".to_string();
980 list.set_options(options);
981
982 assert_eq!(list.generate_marker(0), "Chapter 1:");
983 assert_eq!(list.generate_marker(1), "Chapter 2:");
984
985 let mut roman_list = OrderedList::new(OrderedListStyle::UpperRoman);
987 let mut roman_options = ListOptions::default();
988 roman_options.marker_prefix = "Part ".to_string();
989 roman_options.marker_suffix = " -".to_string();
990 roman_list.set_options(roman_options);
991
992 assert_eq!(roman_list.generate_marker(0), "Part I -");
993 assert_eq!(roman_list.generate_marker(3), "Part IV -");
994 }
995
996 #[test]
997 fn test_text_wrapping() {
998 let list = OrderedList::new(OrderedListStyle::Decimal);
999
1000 let wrapped = list.wrap_text("Short text", 100.0);
1002 assert_eq!(wrapped.len(), 1);
1003 assert_eq!(wrapped[0], "Short text");
1004
1005 let long_text =
1007 "This is a very long line that should be wrapped because it exceeds the maximum width";
1008 let wrapped = list.wrap_text(long_text, 50.0); assert!(wrapped.len() > 1);
1010
1011 let wrapped = list.wrap_text("", 100.0);
1013 assert_eq!(wrapped.len(), 1);
1014 assert_eq!(wrapped[0], "");
1015
1016 let wrapped = list.wrap_text("Test", 0.0);
1018 assert_eq!(wrapped.len(), 1);
1019 assert_eq!(wrapped[0], "Test");
1020 }
1021
1022 #[test]
1023 fn test_list_options_advanced() {
1024 let mut options = ListOptions::default();
1025
1026 options.max_width = Some(300.0);
1028 options.text_align = TextAlign::Center;
1029 options.marker_font = Font::HelveticaBold;
1030 options.marker_color = Some(Color::red());
1031 options.paragraph_spacing = 5.0;
1032 options.draw_separator = true;
1033 options.separator_color = Color::gray(0.5);
1034 options.separator_width = 2.0;
1035 options.marker_prefix = "Item ".to_string();
1036 options.marker_suffix = ")".to_string();
1037
1038 assert_eq!(options.max_width, Some(300.0));
1039 assert_eq!(options.text_align, TextAlign::Center);
1040 assert_eq!(options.marker_font, Font::HelveticaBold);
1041 assert!(options.marker_color.is_some());
1042 assert_eq!(options.paragraph_spacing, 5.0);
1043 assert!(options.draw_separator);
1044 assert_eq!(options.separator_width, 2.0);
1045 assert_eq!(options.marker_prefix, "Item ");
1046 assert_eq!(options.marker_suffix, ")");
1047 }
1048
1049 #[test]
1050 fn test_unordered_list_custom_bullets() {
1051 let disc_list = UnorderedList::new(BulletStyle::Disc);
1053 assert_eq!(disc_list.get_bullet_char(), "•");
1054
1055 let circle_list = UnorderedList::new(BulletStyle::Circle);
1056 assert_eq!(circle_list.get_bullet_char(), "○");
1057
1058 let square_list = UnorderedList::new(BulletStyle::Square);
1059 assert_eq!(square_list.get_bullet_char(), "■");
1060
1061 let dash_list = UnorderedList::new(BulletStyle::Dash);
1062 assert_eq!(dash_list.get_bullet_char(), "-");
1063
1064 let arrow_list = UnorderedList::new(BulletStyle::Custom('→'));
1066 assert_eq!(arrow_list.get_bullet_char(), "→");
1067
1068 let star_list = UnorderedList::new(BulletStyle::Custom('★'));
1069 assert_eq!(star_list.get_bullet_char(), "★");
1070
1071 let unknown_list = UnorderedList::new(BulletStyle::Custom('Z'));
1073 assert_eq!(unknown_list.get_bullet_char(), "•"); }
1075
1076 #[test]
1077 fn test_deeply_nested_lists() {
1078 let mut level1 = OrderedList::new(OrderedListStyle::Decimal);
1079
1080 let mut level2 = UnorderedList::new(BulletStyle::Circle);
1082
1083 let mut level3 = OrderedList::new(OrderedListStyle::LowerAlpha);
1085 level3.add_item("Deep item a".to_string());
1086 level3.add_item("Deep item b".to_string());
1087
1088 level2.add_item_with_children(
1090 "Level 2 item with children".to_string(),
1091 vec![ListElement::Ordered(level3)],
1092 );
1093 level2.add_item("Level 2 item without children".to_string());
1094
1095 level1.add_item_with_children(
1097 "Level 1 item with nested list".to_string(),
1098 vec![ListElement::Unordered(level2)],
1099 );
1100
1101 assert_eq!(level1.items.len(), 1);
1102 assert_eq!(level1.items[0].children.len(), 1);
1103
1104 if let ListElement::Unordered(ref list) = level1.items[0].children[0] {
1106 assert_eq!(list.items.len(), 2);
1107 assert_eq!(list.items[0].children.len(), 1);
1108 } else {
1109 panic!("Expected unordered list at level 2");
1110 }
1111 }
1112
1113 #[test]
1114 fn test_height_calculation_with_nested() {
1115 let mut list = OrderedList::new(OrderedListStyle::Decimal);
1116 list.add_item("Item 1".to_string());
1117 list.add_item("Item 2".to_string());
1118
1119 let height_simple = list.get_height();
1120 let expected_simple = 2.0 * 10.0 * 1.2; assert_eq!(height_simple, expected_simple);
1122
1123 let mut nested = UnorderedList::new(BulletStyle::Dash);
1125 nested.add_item("Nested 1".to_string());
1126 nested.add_item("Nested 2".to_string());
1127
1128 list.add_item_with_children(
1129 "Item 3 with children".to_string(),
1130 vec![ListElement::Unordered(nested)],
1131 );
1132
1133 let height_with_nested = list.get_height();
1134 let expected_with_nested = 5.0 * 10.0 * 1.2; assert_eq!(height_with_nested, expected_with_nested);
1136 }
1137
1138 #[test]
1139 fn test_helper_functions() {
1140 assert_eq!(get_greek_letter(1, false), "α");
1142 assert_eq!(get_greek_letter(2, false), "β");
1143 assert_eq!(get_greek_letter(24, false), "ω");
1144 assert_eq!(get_greek_letter(25, false), "α"); assert_eq!(get_greek_letter(1, true), "Α");
1147 assert_eq!(get_greek_letter(2, true), "Β");
1148 assert_eq!(get_greek_letter(24, true), "Ω");
1149
1150 assert_eq!(get_hebrew_letter(1), "א");
1152 assert_eq!(get_hebrew_letter(22), "ת");
1153 assert_eq!(get_hebrew_letter(23), "א"); assert_eq!(get_hiragana_letter(1), "あ");
1157 assert_eq!(get_hiragana_letter(5), "お");
1158 assert_eq!(get_hiragana_letter(46), "ん");
1159 assert_eq!(get_hiragana_letter(47), "あ"); assert_eq!(get_katakana_letter(1), "ア");
1163 assert_eq!(get_katakana_letter(5), "オ");
1164 assert_eq!(get_katakana_letter(46), "ン");
1165
1166 assert_eq!(get_chinese_number(1), "一");
1168 assert_eq!(get_chinese_number(10), "十");
1169 assert_eq!(get_chinese_number(20), "二十");
1170 assert_eq!(get_chinese_number(21), "21"); }
1172
1173 #[test]
1174 fn test_list_cloning() {
1175 let mut original = OrderedList::new(OrderedListStyle::Decimal);
1176 original.add_item("Item 1".to_string());
1177 original.set_position(100.0, 200.0);
1178 original.set_start_number(5);
1179
1180 let cloned = original.clone();
1181 assert_eq!(cloned.items.len(), 1);
1182 assert_eq!(cloned.position, (100.0, 200.0));
1183 assert_eq!(cloned.start_number, 5);
1184 assert_eq!(cloned.style, OrderedListStyle::Decimal);
1185 }
1186}