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_name = match label_dict.get("S") {
102 Some(Object::Name(s)) => Some(s.as_str()),
103 _ => match label_dict.get("Type") {
104 Some(Object::Name(t)) if t != "PageLabel" => Some(t.as_str()),
105 _ => None,
106 },
107 };
108 let style = match style_name {
112 Some("D") => PageLabelStyle::DecimalArabic,
113 Some("R") => PageLabelStyle::UppercaseRoman,
114 Some("r") => PageLabelStyle::LowercaseRoman,
115 Some("A") => PageLabelStyle::UppercaseLetters,
116 Some("a") => PageLabelStyle::LowercaseLetters,
117 _ => PageLabelStyle::None,
118 };
119
120 let mut label = PageLabel::new(style);
121
122 if let Some(Object::String(prefix)) = label_dict.get("P") {
123 label = label.with_prefix(prefix);
124 }
125
126 if let Some(Object::Integer(start)) = label_dict.get("St") {
127 label = label.starting_at(*start as u32);
128 }
129
130 tree.add_range(page_index, label);
131 }
132
133 Some(tree)
134 }
135}
136
137impl Default for PageLabelTree {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143pub struct PageLabelBuilder {
145 tree: PageLabelTree,
146 current_page: u32,
147}
148
149impl Default for PageLabelBuilder {
150 fn default() -> Self {
151 Self::new()
152 }
153}
154
155impl PageLabelBuilder {
156 pub fn new() -> Self {
158 Self {
159 tree: PageLabelTree::new(),
160 current_page: 0,
161 }
162 }
163
164 pub fn add_range(mut self, num_pages: u32, label: PageLabel) -> Self {
166 self.tree.add_range(self.current_page, label);
167 self.current_page += num_pages;
168 self
169 }
170
171 pub fn decimal_pages(self, num_pages: u32) -> Self {
173 self.add_range(num_pages, PageLabel::decimal())
174 }
175
176 pub fn roman_pages(self, num_pages: u32, uppercase: bool) -> Self {
178 let label = if uppercase {
179 PageLabel::roman_uppercase()
180 } else {
181 PageLabel::roman_lowercase()
182 };
183 self.add_range(num_pages, label)
184 }
185
186 pub fn letter_pages(self, num_pages: u32, uppercase: bool) -> Self {
188 let label = if uppercase {
189 PageLabel::letters_uppercase()
190 } else {
191 PageLabel::letters_lowercase()
192 };
193 self.add_range(num_pages, label)
194 }
195
196 pub fn prefix_pages(self, num_pages: u32, prefix: impl Into<String>) -> Self {
198 self.add_range(num_pages, PageLabel::prefix_only(prefix))
199 }
200
201 pub fn build(self) -> PageLabelTree {
203 self.tree
204 }
205}
206
207use crate::page_labels::PageLabelStyle;
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_page_label_tree() {
216 let mut tree = PageLabelTree::new();
217
218 tree.add_range(0, PageLabel::roman_lowercase());
220
221 tree.add_range(3, PageLabel::decimal());
223
224 assert_eq!(tree.get_label(0), Some("i".to_string()));
226 assert_eq!(tree.get_label(1), Some("ii".to_string()));
227 assert_eq!(tree.get_label(2), Some("iii".to_string()));
228 assert_eq!(tree.get_label(3), Some("1".to_string()));
229 assert_eq!(tree.get_label(4), Some("2".to_string()));
230 assert_eq!(tree.get_label(5), Some("3".to_string()));
231 }
232
233 #[test]
234 fn test_page_label_with_prefix() {
235 let mut tree = PageLabelTree::new();
236
237 tree.add_range(0, PageLabel::prefix_only("Cover"));
239 tree.add_range(1, PageLabel::roman_lowercase().with_prefix("p. "));
240 tree.add_range(4, PageLabel::decimal().with_prefix("Chapter "));
241
242 assert_eq!(tree.get_label(0), Some("Cover".to_string()));
243 assert_eq!(tree.get_label(1), Some("p. i".to_string()));
244 assert_eq!(tree.get_label(2), Some("p. ii".to_string()));
245 assert_eq!(tree.get_label(3), Some("p. iii".to_string()));
246 assert_eq!(tree.get_label(4), Some("Chapter 1".to_string()));
247 assert_eq!(tree.get_label(5), Some("Chapter 2".to_string()));
248 }
249
250 #[test]
251 fn test_page_label_with_start() {
252 let mut tree = PageLabelTree::new();
253
254 tree.add_range(0, PageLabel::decimal().starting_at(10));
256
257 assert_eq!(tree.get_label(0), Some("10".to_string()));
258 assert_eq!(tree.get_label(1), Some("11".to_string()));
259 assert_eq!(tree.get_label(2), Some("12".to_string()));
260 }
261
262 #[test]
263 fn test_get_all_labels() {
264 let mut tree = PageLabelTree::new();
265 tree.add_range(0, PageLabel::roman_lowercase());
266 tree.add_range(2, PageLabel::decimal());
267
268 let labels = tree.get_all_labels(5);
269 assert_eq!(labels, vec!["i", "ii", "1", "2", "3"]);
270 }
271
272 #[test]
273 fn test_page_label_builder() {
274 let tree = PageLabelBuilder::new()
275 .prefix_pages(1, "Cover")
276 .roman_pages(3, false)
277 .decimal_pages(10)
278 .letter_pages(3, true)
279 .build();
280
281 assert_eq!(tree.get_label(0), Some("Cover".to_string()));
282 assert_eq!(tree.get_label(1), Some("i".to_string()));
283 assert_eq!(tree.get_label(2), Some("ii".to_string()));
284 assert_eq!(tree.get_label(3), Some("iii".to_string()));
285 assert_eq!(tree.get_label(4), Some("1".to_string()));
286 assert_eq!(tree.get_label(13), Some("10".to_string()));
287 assert_eq!(tree.get_label(14), Some("A".to_string()));
288 assert_eq!(tree.get_label(15), Some("B".to_string()));
289 assert_eq!(tree.get_label(16), Some("C".to_string()));
290 }
291
292 #[test]
293 fn test_to_dict() {
294 let mut tree = PageLabelTree::new();
295 tree.add_range(0, PageLabel::roman_lowercase());
296 tree.add_range(3, PageLabel::decimal().with_prefix("Page "));
297
298 let dict = tree.to_dict();
299 assert!(dict.get("Nums").is_some());
300 }
301
302 #[test]
303 fn test_page_label_tree_default() {
304 let tree = PageLabelTree::default();
305 assert!(tree.get_label(0).is_none());
307 assert!(tree.get_label(100).is_none());
308 }
309
310 #[test]
311 fn test_page_label_tree_clone() {
312 let mut tree = PageLabelTree::new();
313 tree.add_range(0, PageLabel::decimal());
314 let cloned = tree.clone();
315 assert_eq!(tree.get_label(0), cloned.get_label(0));
316 }
317
318 #[test]
319 fn test_page_label_tree_debug() {
320 let tree = PageLabelTree::new();
321 let debug_str = format!("{:?}", tree);
322 assert!(debug_str.contains("PageLabelTree"));
323 }
324
325 #[test]
326 fn test_page_label_builder_default() {
327 let builder = PageLabelBuilder::default();
328 let tree = builder.build();
329 assert!(tree.get_label(0).is_none());
331 }
332
333 #[test]
334 fn test_page_label_builder_roman_uppercase() {
335 let tree = PageLabelBuilder::new().roman_pages(5, true).build();
336
337 assert_eq!(tree.get_label(0), Some("I".to_string()));
338 assert_eq!(tree.get_label(1), Some("II".to_string()));
339 assert_eq!(tree.get_label(2), Some("III".to_string()));
340 assert_eq!(tree.get_label(3), Some("IV".to_string()));
341 assert_eq!(tree.get_label(4), Some("V".to_string()));
342 }
343
344 #[test]
345 fn test_page_label_builder_letter_lowercase() {
346 let tree = PageLabelBuilder::new().letter_pages(3, false).build();
347
348 assert_eq!(tree.get_label(0), Some("a".to_string()));
349 assert_eq!(tree.get_label(1), Some("b".to_string()));
350 assert_eq!(tree.get_label(2), Some("c".to_string()));
351 }
352
353 #[test]
354 fn test_get_all_labels_empty_tree() {
355 let tree = PageLabelTree::new();
356 let labels = tree.get_all_labels(3);
358 assert_eq!(labels, vec!["1", "2", "3"]);
359 }
360
361 #[test]
362 fn test_from_dict_empty() {
363 let mut dict = Dictionary::new();
364 dict.set("Nums", Object::Array(Array::new().into()));
365
366 let tree = PageLabelTree::from_dict(&dict);
367 assert!(tree.is_some());
368 let tree = tree.unwrap();
369 assert!(tree.get_label(0).is_none());
370 }
371
372 #[test]
373 fn test_from_dict_missing_nums() {
374 let dict = Dictionary::new();
375 let tree = PageLabelTree::from_dict(&dict);
376 assert!(tree.is_none());
377 }
378
379 #[test]
380 fn test_from_dict_invalid_nums_type() {
381 let mut dict = Dictionary::new();
382 dict.set("Nums", Object::Integer(42));
383
384 let tree = PageLabelTree::from_dict(&dict);
385 assert!(tree.is_none());
386 }
387
388 #[test]
389 fn test_from_dict_with_decimal_labels() {
390 let mut label_dict = Dictionary::new();
391 label_dict.set("Type", Object::Name("D".to_string()));
392
393 let mut nums = Array::new();
394 nums.push(Object::Integer(0));
395 nums.push(Object::Dictionary(label_dict));
396
397 let mut dict = Dictionary::new();
398 dict.set("Nums", Object::Array(nums.into()));
399
400 let tree = PageLabelTree::from_dict(&dict);
401 assert!(tree.is_some());
402 }
403
404 #[test]
405 fn test_from_dict_reads_roman_case_per_iso_spec() {
406 fn build_tree(style_name: &str) -> PageLabelTree {
419 let mut label_dict = Dictionary::new();
420 label_dict.set("Type", Object::Name("PageLabel".to_string()));
421 label_dict.set("S", Object::Name(style_name.to_string()));
422
423 let mut nums = Array::new();
424 nums.push(Object::Integer(0));
425 nums.push(Object::Dictionary(label_dict));
426
427 let mut dict = Dictionary::new();
428 dict.set("Nums", Object::Array(nums.into()));
429 PageLabelTree::from_dict(&dict).expect("valid page label tree")
430 }
431
432 let upper = build_tree("R");
433 assert_eq!(
434 upper.get_label(0),
435 Some("I".to_string()),
436 "/S /R must be parsed as UppercaseRoman per ISO 32000-1 §12.4.2 \
437 Table 159 (viewer renders 'I', not 'i')"
438 );
439
440 let lower = build_tree("r");
441 assert_eq!(
442 lower.get_label(0),
443 Some("i".to_string()),
444 "/S /r must be parsed as LowercaseRoman per ISO 32000-1 §12.4.2 \
445 Table 159 (viewer renders 'i', not 'I')"
446 );
447 }
448
449 #[test]
450 fn test_from_dict_with_prefix_and_start() {
451 let mut label_dict = Dictionary::new();
452 label_dict.set("Type", Object::Name("D".to_string()));
453 label_dict.set("P", Object::String("Page ".to_string()));
454 label_dict.set("St", Object::Integer(10));
455
456 let mut nums = Array::new();
457 nums.push(Object::Integer(0));
458 nums.push(Object::Dictionary(label_dict));
459
460 let mut dict = Dictionary::new();
461 dict.set("Nums", Object::Array(nums.into()));
462
463 let tree = PageLabelTree::from_dict(&dict);
464 assert!(tree.is_some());
465 }
466
467 #[test]
468 fn test_from_dict_invalid_page_index() {
469 let label_dict = Dictionary::new();
471
472 let mut nums = Array::new();
473 nums.push(Object::String("not_an_integer".to_string()));
474 nums.push(Object::Dictionary(label_dict));
475
476 let mut dict = Dictionary::new();
477 dict.set("Nums", Object::Array(nums.into()));
478
479 let tree = PageLabelTree::from_dict(&dict);
481 assert!(tree.is_some());
482 }
483
484 #[test]
485 fn test_from_dict_invalid_label_dict() {
486 let mut nums = Array::new();
488 nums.push(Object::Integer(0));
489 nums.push(Object::String("not_a_dict".to_string()));
490
491 let mut dict = Dictionary::new();
492 dict.set("Nums", Object::Array(nums.into()));
493
494 let tree = PageLabelTree::from_dict(&dict);
496 assert!(tree.is_some());
497 }
498
499 #[test]
500 fn test_from_dict_odd_length_array() {
501 let label_dict = Dictionary::new();
503
504 let mut nums = Array::new();
505 nums.push(Object::Integer(0));
506 nums.push(Object::Dictionary(label_dict));
507 nums.push(Object::Integer(5)); let mut dict = Dictionary::new();
510 dict.set("Nums", Object::Array(nums.into()));
511
512 let tree = PageLabelTree::from_dict(&dict);
513 assert!(tree.is_some());
514 }
515
516 #[test]
517 fn test_from_dict_all_style_types() {
518 let mut label_dict1 = Dictionary::new();
525 label_dict1.set("Type", Object::Name("r".to_string()));
526
527 let mut label_dict2 = Dictionary::new();
529 label_dict2.set("Type", Object::Name("R".to_string()));
530
531 let mut label_dict3 = Dictionary::new();
533 label_dict3.set("Type", Object::Name("A".to_string()));
534
535 let mut label_dict4 = Dictionary::new();
537 label_dict4.set("Type", Object::Name("a".to_string()));
538
539 let mut label_dict5 = Dictionary::new();
541 label_dict5.set("Type", Object::Name("unknown".to_string()));
542
543 let mut nums = Array::new();
544 nums.push(Object::Integer(0));
545 nums.push(Object::Dictionary(label_dict1));
546 nums.push(Object::Integer(5));
547 nums.push(Object::Dictionary(label_dict2));
548 nums.push(Object::Integer(10));
549 nums.push(Object::Dictionary(label_dict3));
550 nums.push(Object::Integer(15));
551 nums.push(Object::Dictionary(label_dict4));
552 nums.push(Object::Integer(20));
553 nums.push(Object::Dictionary(label_dict5));
554
555 let mut dict = Dictionary::new();
556 dict.set("Nums", Object::Array(nums.into()));
557
558 let tree = PageLabelTree::from_dict(&dict);
559 assert!(tree.is_some());
560 }
561
562 #[test]
563 fn test_get_label_no_applicable_range() {
564 let mut tree = PageLabelTree::new();
565 tree.add_range(5, PageLabel::decimal());
567
568 assert!(tree.get_label(0).is_none());
570 assert!(tree.get_label(4).is_none());
571
572 assert_eq!(tree.get_label(5), Some("1".to_string()));
574 assert_eq!(tree.get_label(6), Some("2".to_string()));
575 }
576
577 #[test]
578 fn test_page_label_builder_chained() {
579 let tree = PageLabelBuilder::new()
580 .prefix_pages(1, "TOC")
581 .roman_pages(2, false)
582 .roman_pages(2, true)
583 .letter_pages(2, false)
584 .letter_pages(2, true)
585 .decimal_pages(5)
586 .build();
587
588 assert_eq!(tree.get_label(0), Some("TOC".to_string()));
589 assert_eq!(tree.get_label(1), Some("i".to_string()));
590 assert_eq!(tree.get_label(3), Some("I".to_string()));
591 assert_eq!(tree.get_label(5), Some("a".to_string()));
592 assert_eq!(tree.get_label(7), Some("A".to_string()));
593 assert_eq!(tree.get_label(9), Some("1".to_string()));
594 }
595
596 #[test]
597 fn test_to_dict_round_trip() {
598 let mut original = PageLabelTree::new();
599 original.add_range(0, PageLabel::roman_lowercase());
600 original.add_range(5, PageLabel::decimal());
601
602 let dict = original.to_dict();
603 let restored = PageLabelTree::from_dict(&dict);
604
605 assert!(restored.is_some());
606 }
609}