1use crate::objects::{Array, Dictionary, Object};
4use crate::page_labels::PageLabel;
5use std::collections::BTreeMap;
6
7#[derive(Debug, Clone)]
9pub struct PageLabelTree {
10 ranges: BTreeMap<u32, PageLabel>,
12}
13
14impl PageLabelTree {
15 pub fn new() -> Self {
17 Self {
18 ranges: BTreeMap::new(),
19 }
20 }
21
22 pub fn add_range(&mut self, start_page: u32, label: PageLabel) {
24 self.ranges.insert(start_page, label);
25 }
26
27 pub fn get_label(&self, page_index: u32) -> Option<String> {
29 let mut applicable_range = None;
31 let mut range_start = 0;
32
33 for (&start, label) in &self.ranges {
34 if start <= page_index {
35 applicable_range = Some(label);
36 range_start = start;
37 } else {
38 break;
39 }
40 }
41
42 applicable_range.map(|label| {
44 let offset = page_index - range_start;
45 label.format_label(offset)
46 })
47 }
48
49 pub fn get_all_labels(&self, total_pages: u32) -> Vec<String> {
51 (0..total_pages)
52 .map(|i| self.get_label(i).unwrap_or_else(|| (i + 1).to_string()))
53 .collect()
54 }
55
56 pub fn to_dict(&self) -> Dictionary {
58 let mut dict = Dictionary::new();
59
60 let mut nums = Array::new();
62
63 for (&start_page, label) in &self.ranges {
64 nums.push(Object::Integer(start_page as i64));
65 nums.push(Object::Dictionary(label.to_dict()));
66 }
67
68 dict.set("Nums", Object::Array(nums.into()));
69
70 dict
71 }
72
73 pub fn from_dict(dict: &Dictionary) -> Option<Self> {
75 let nums_array = match dict.get("Nums")? {
76 Object::Array(arr) => arr,
77 _ => return None,
78 };
79 let mut tree = Self::new();
80
81 let elements: Vec<&Object> = nums_array.iter().collect();
83 for i in (0..elements.len()).step_by(2) {
84 if i + 1 >= elements.len() {
85 break;
86 }
87
88 let page_index = match elements[i] {
89 Object::Integer(n) => *n as u32,
90 _ => continue,
91 };
92 let label_dict = match elements[i + 1] {
93 Object::Dictionary(d) => d,
94 _ => continue,
95 };
96
97 let style = if let Some(Object::Name(type_name)) = label_dict.get("Type") {
99 match type_name.as_str() {
100 "D" => PageLabelStyle::DecimalArabic,
101 "r" => PageLabelStyle::UppercaseRoman,
102 "R" => PageLabelStyle::LowercaseRoman,
103 "A" => PageLabelStyle::UppercaseLetters,
104 "a" => PageLabelStyle::LowercaseLetters,
105 _ => PageLabelStyle::None,
106 }
107 } else {
108 PageLabelStyle::None
109 };
110
111 let mut label = PageLabel::new(style);
112
113 if let Some(Object::String(prefix)) = label_dict.get("P") {
114 label = label.with_prefix(prefix);
115 }
116
117 if let Some(Object::Integer(start)) = label_dict.get("St") {
118 label = label.starting_at(*start as u32);
119 }
120
121 tree.add_range(page_index, label);
122 }
123
124 Some(tree)
125 }
126}
127
128impl Default for PageLabelTree {
129 fn default() -> Self {
130 Self::new()
131 }
132}
133
134pub struct PageLabelBuilder {
136 tree: PageLabelTree,
137 current_page: u32,
138}
139
140impl Default for PageLabelBuilder {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146impl PageLabelBuilder {
147 pub fn new() -> Self {
149 Self {
150 tree: PageLabelTree::new(),
151 current_page: 0,
152 }
153 }
154
155 pub fn add_range(mut self, num_pages: u32, label: PageLabel) -> Self {
157 self.tree.add_range(self.current_page, label);
158 self.current_page += num_pages;
159 self
160 }
161
162 pub fn decimal_pages(self, num_pages: u32) -> Self {
164 self.add_range(num_pages, PageLabel::decimal())
165 }
166
167 pub fn roman_pages(self, num_pages: u32, uppercase: bool) -> Self {
169 let label = if uppercase {
170 PageLabel::roman_uppercase()
171 } else {
172 PageLabel::roman_lowercase()
173 };
174 self.add_range(num_pages, label)
175 }
176
177 pub fn letter_pages(self, num_pages: u32, uppercase: bool) -> Self {
179 let label = if uppercase {
180 PageLabel::letters_uppercase()
181 } else {
182 PageLabel::letters_lowercase()
183 };
184 self.add_range(num_pages, label)
185 }
186
187 pub fn prefix_pages(self, num_pages: u32, prefix: impl Into<String>) -> Self {
189 self.add_range(num_pages, PageLabel::prefix_only(prefix))
190 }
191
192 pub fn build(self) -> PageLabelTree {
194 self.tree
195 }
196}
197
198use crate::page_labels::PageLabelStyle;
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn test_page_label_tree() {
207 let mut tree = PageLabelTree::new();
208
209 tree.add_range(0, PageLabel::roman_lowercase());
211
212 tree.add_range(3, PageLabel::decimal());
214
215 assert_eq!(tree.get_label(0), Some("i".to_string()));
217 assert_eq!(tree.get_label(1), Some("ii".to_string()));
218 assert_eq!(tree.get_label(2), Some("iii".to_string()));
219 assert_eq!(tree.get_label(3), Some("1".to_string()));
220 assert_eq!(tree.get_label(4), Some("2".to_string()));
221 assert_eq!(tree.get_label(5), Some("3".to_string()));
222 }
223
224 #[test]
225 fn test_page_label_with_prefix() {
226 let mut tree = PageLabelTree::new();
227
228 tree.add_range(0, PageLabel::prefix_only("Cover"));
230 tree.add_range(1, PageLabel::roman_lowercase().with_prefix("p. "));
231 tree.add_range(4, PageLabel::decimal().with_prefix("Chapter "));
232
233 assert_eq!(tree.get_label(0), Some("Cover".to_string()));
234 assert_eq!(tree.get_label(1), Some("p. i".to_string()));
235 assert_eq!(tree.get_label(2), Some("p. ii".to_string()));
236 assert_eq!(tree.get_label(3), Some("p. iii".to_string()));
237 assert_eq!(tree.get_label(4), Some("Chapter 1".to_string()));
238 assert_eq!(tree.get_label(5), Some("Chapter 2".to_string()));
239 }
240
241 #[test]
242 fn test_page_label_with_start() {
243 let mut tree = PageLabelTree::new();
244
245 tree.add_range(0, PageLabel::decimal().starting_at(10));
247
248 assert_eq!(tree.get_label(0), Some("10".to_string()));
249 assert_eq!(tree.get_label(1), Some("11".to_string()));
250 assert_eq!(tree.get_label(2), Some("12".to_string()));
251 }
252
253 #[test]
254 fn test_get_all_labels() {
255 let mut tree = PageLabelTree::new();
256 tree.add_range(0, PageLabel::roman_lowercase());
257 tree.add_range(2, PageLabel::decimal());
258
259 let labels = tree.get_all_labels(5);
260 assert_eq!(labels, vec!["i", "ii", "1", "2", "3"]);
261 }
262
263 #[test]
264 fn test_page_label_builder() {
265 let tree = PageLabelBuilder::new()
266 .prefix_pages(1, "Cover")
267 .roman_pages(3, false)
268 .decimal_pages(10)
269 .letter_pages(3, true)
270 .build();
271
272 assert_eq!(tree.get_label(0), Some("Cover".to_string()));
273 assert_eq!(tree.get_label(1), Some("i".to_string()));
274 assert_eq!(tree.get_label(2), Some("ii".to_string()));
275 assert_eq!(tree.get_label(3), Some("iii".to_string()));
276 assert_eq!(tree.get_label(4), Some("1".to_string()));
277 assert_eq!(tree.get_label(13), Some("10".to_string()));
278 assert_eq!(tree.get_label(14), Some("A".to_string()));
279 assert_eq!(tree.get_label(15), Some("B".to_string()));
280 assert_eq!(tree.get_label(16), Some("C".to_string()));
281 }
282
283 #[test]
284 fn test_to_dict() {
285 let mut tree = PageLabelTree::new();
286 tree.add_range(0, PageLabel::roman_lowercase());
287 tree.add_range(3, PageLabel::decimal().with_prefix("Page "));
288
289 let dict = tree.to_dict();
290 assert!(dict.get("Nums").is_some());
291 }
292
293 #[test]
294 fn test_page_label_tree_default() {
295 let tree = PageLabelTree::default();
296 assert!(tree.get_label(0).is_none());
298 assert!(tree.get_label(100).is_none());
299 }
300
301 #[test]
302 fn test_page_label_tree_clone() {
303 let mut tree = PageLabelTree::new();
304 tree.add_range(0, PageLabel::decimal());
305 let cloned = tree.clone();
306 assert_eq!(tree.get_label(0), cloned.get_label(0));
307 }
308
309 #[test]
310 fn test_page_label_tree_debug() {
311 let tree = PageLabelTree::new();
312 let debug_str = format!("{:?}", tree);
313 assert!(debug_str.contains("PageLabelTree"));
314 }
315
316 #[test]
317 fn test_page_label_builder_default() {
318 let builder = PageLabelBuilder::default();
319 let tree = builder.build();
320 assert!(tree.get_label(0).is_none());
322 }
323
324 #[test]
325 fn test_page_label_builder_roman_uppercase() {
326 let tree = PageLabelBuilder::new().roman_pages(5, true).build();
327
328 assert_eq!(tree.get_label(0), Some("I".to_string()));
329 assert_eq!(tree.get_label(1), Some("II".to_string()));
330 assert_eq!(tree.get_label(2), Some("III".to_string()));
331 assert_eq!(tree.get_label(3), Some("IV".to_string()));
332 assert_eq!(tree.get_label(4), Some("V".to_string()));
333 }
334
335 #[test]
336 fn test_page_label_builder_letter_lowercase() {
337 let tree = PageLabelBuilder::new().letter_pages(3, false).build();
338
339 assert_eq!(tree.get_label(0), Some("a".to_string()));
340 assert_eq!(tree.get_label(1), Some("b".to_string()));
341 assert_eq!(tree.get_label(2), Some("c".to_string()));
342 }
343
344 #[test]
345 fn test_get_all_labels_empty_tree() {
346 let tree = PageLabelTree::new();
347 let labels = tree.get_all_labels(3);
349 assert_eq!(labels, vec!["1", "2", "3"]);
350 }
351
352 #[test]
353 fn test_from_dict_empty() {
354 let mut dict = Dictionary::new();
355 dict.set("Nums", Object::Array(Array::new().into()));
356
357 let tree = PageLabelTree::from_dict(&dict);
358 assert!(tree.is_some());
359 let tree = tree.unwrap();
360 assert!(tree.get_label(0).is_none());
361 }
362
363 #[test]
364 fn test_from_dict_missing_nums() {
365 let dict = Dictionary::new();
366 let tree = PageLabelTree::from_dict(&dict);
367 assert!(tree.is_none());
368 }
369
370 #[test]
371 fn test_from_dict_invalid_nums_type() {
372 let mut dict = Dictionary::new();
373 dict.set("Nums", Object::Integer(42));
374
375 let tree = PageLabelTree::from_dict(&dict);
376 assert!(tree.is_none());
377 }
378
379 #[test]
380 fn test_from_dict_with_decimal_labels() {
381 let mut label_dict = Dictionary::new();
382 label_dict.set("Type", Object::Name("D".to_string()));
383
384 let mut nums = Array::new();
385 nums.push(Object::Integer(0));
386 nums.push(Object::Dictionary(label_dict));
387
388 let mut dict = Dictionary::new();
389 dict.set("Nums", Object::Array(nums.into()));
390
391 let tree = PageLabelTree::from_dict(&dict);
392 assert!(tree.is_some());
393 }
394
395 #[test]
396 fn test_from_dict_with_prefix_and_start() {
397 let mut label_dict = Dictionary::new();
398 label_dict.set("Type", Object::Name("D".to_string()));
399 label_dict.set("P", Object::String("Page ".to_string()));
400 label_dict.set("St", Object::Integer(10));
401
402 let mut nums = Array::new();
403 nums.push(Object::Integer(0));
404 nums.push(Object::Dictionary(label_dict));
405
406 let mut dict = Dictionary::new();
407 dict.set("Nums", Object::Array(nums.into()));
408
409 let tree = PageLabelTree::from_dict(&dict);
410 assert!(tree.is_some());
411 }
412
413 #[test]
414 fn test_from_dict_invalid_page_index() {
415 let label_dict = Dictionary::new();
417
418 let mut nums = Array::new();
419 nums.push(Object::String("not_an_integer".to_string()));
420 nums.push(Object::Dictionary(label_dict));
421
422 let mut dict = Dictionary::new();
423 dict.set("Nums", Object::Array(nums.into()));
424
425 let tree = PageLabelTree::from_dict(&dict);
427 assert!(tree.is_some());
428 }
429
430 #[test]
431 fn test_from_dict_invalid_label_dict() {
432 let mut nums = Array::new();
434 nums.push(Object::Integer(0));
435 nums.push(Object::String("not_a_dict".to_string()));
436
437 let mut dict = Dictionary::new();
438 dict.set("Nums", Object::Array(nums.into()));
439
440 let tree = PageLabelTree::from_dict(&dict);
442 assert!(tree.is_some());
443 }
444
445 #[test]
446 fn test_from_dict_odd_length_array() {
447 let label_dict = Dictionary::new();
449
450 let mut nums = Array::new();
451 nums.push(Object::Integer(0));
452 nums.push(Object::Dictionary(label_dict));
453 nums.push(Object::Integer(5)); let mut dict = Dictionary::new();
456 dict.set("Nums", Object::Array(nums.into()));
457
458 let tree = PageLabelTree::from_dict(&dict);
459 assert!(tree.is_some());
460 }
461
462 #[test]
463 fn test_from_dict_all_style_types() {
464 let mut label_dict1 = Dictionary::new();
466 label_dict1.set("Type", Object::Name("r".to_string()));
467
468 let mut label_dict2 = Dictionary::new();
470 label_dict2.set("Type", Object::Name("R".to_string()));
471
472 let mut label_dict3 = Dictionary::new();
474 label_dict3.set("Type", Object::Name("A".to_string()));
475
476 let mut label_dict4 = Dictionary::new();
478 label_dict4.set("Type", Object::Name("a".to_string()));
479
480 let mut label_dict5 = Dictionary::new();
482 label_dict5.set("Type", Object::Name("unknown".to_string()));
483
484 let mut nums = Array::new();
485 nums.push(Object::Integer(0));
486 nums.push(Object::Dictionary(label_dict1));
487 nums.push(Object::Integer(5));
488 nums.push(Object::Dictionary(label_dict2));
489 nums.push(Object::Integer(10));
490 nums.push(Object::Dictionary(label_dict3));
491 nums.push(Object::Integer(15));
492 nums.push(Object::Dictionary(label_dict4));
493 nums.push(Object::Integer(20));
494 nums.push(Object::Dictionary(label_dict5));
495
496 let mut dict = Dictionary::new();
497 dict.set("Nums", Object::Array(nums.into()));
498
499 let tree = PageLabelTree::from_dict(&dict);
500 assert!(tree.is_some());
501 }
502
503 #[test]
504 fn test_get_label_no_applicable_range() {
505 let mut tree = PageLabelTree::new();
506 tree.add_range(5, PageLabel::decimal());
508
509 assert!(tree.get_label(0).is_none());
511 assert!(tree.get_label(4).is_none());
512
513 assert_eq!(tree.get_label(5), Some("1".to_string()));
515 assert_eq!(tree.get_label(6), Some("2".to_string()));
516 }
517
518 #[test]
519 fn test_page_label_builder_chained() {
520 let tree = PageLabelBuilder::new()
521 .prefix_pages(1, "TOC")
522 .roman_pages(2, false)
523 .roman_pages(2, true)
524 .letter_pages(2, false)
525 .letter_pages(2, true)
526 .decimal_pages(5)
527 .build();
528
529 assert_eq!(tree.get_label(0), Some("TOC".to_string()));
530 assert_eq!(tree.get_label(1), Some("i".to_string()));
531 assert_eq!(tree.get_label(3), Some("I".to_string()));
532 assert_eq!(tree.get_label(5), Some("a".to_string()));
533 assert_eq!(tree.get_label(7), Some("A".to_string()));
534 assert_eq!(tree.get_label(9), Some("1".to_string()));
535 }
536
537 #[test]
538 fn test_to_dict_round_trip() {
539 let mut original = PageLabelTree::new();
540 original.add_range(0, PageLabel::roman_lowercase());
541 original.add_range(5, PageLabel::decimal());
542
543 let dict = original.to_dict();
544 let restored = PageLabelTree::from_dict(&dict);
545
546 assert!(restored.is_some());
547 }
550}