Skip to main content

easypdf_core/
page_range.rs

1//! Validated zero-based page range.
2
3use std::ops::Range;
4
5use crate::{PdfError, Result};
6
7/// PDF 页的左闭右开零基范围。
8#[derive(Clone, Debug, PartialEq, Eq, Hash)]
9pub struct PageRange(Range<usize>);
10
11impl PageRange {
12    /// 创建并校验页范围。
13    ///
14    /// # Errors
15    ///
16    /// 当结束位置小于起始位置时返回 [`PdfError::InvalidPage`]。
17    pub fn new(range: Range<usize>) -> Result<Self> {
18        if range.end < range.start {
19            return Err(PdfError::InvalidPage(range.end));
20        }
21        Ok(Self(range))
22    }
23
24    /// 在指定索引创建空范围。
25    #[must_use]
26    pub const fn empty_at(index: usize) -> Self {
27        Self(index..index)
28    }
29
30    /// 返回范围起始索引。
31    #[must_use]
32    pub const fn start(&self) -> usize {
33        self.0.start
34    }
35
36    /// 返回范围结束索引,不包含该位置。
37    #[must_use]
38    pub const fn end(&self) -> usize {
39        self.0.end
40    }
41
42    /// 判断零基页索引是否位于范围内。
43    #[must_use]
44    pub fn contains(&self, index: usize) -> bool {
45        self.0.contains(&index)
46    }
47
48    /// 返回标准库范围的借用。
49    #[must_use]
50    pub const fn as_range(&self) -> &Range<usize> {
51        &self.0
52    }
53}
54
55impl TryFrom<Range<usize>> for PageRange {
56    type Error = PdfError;
57
58    fn try_from(value: Range<usize>) -> Result<Self> {
59        Self::new(value)
60    }
61}
62
63#[cfg(test)]
64#[allow(
65    clippy::uninlined_format_args,
66    clippy::float_cmp,
67    clippy::reversed_empty_ranges
68)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn new_valid_range() {
74        let pr = PageRange::new(0..10).unwrap();
75        assert_eq!(pr.start(), 0);
76        assert_eq!(pr.end(), 10);
77    }
78
79    #[test]
80    fn new_empty_range() {
81        let pr = PageRange::new(5..5).unwrap();
82        assert_eq!(pr.start(), 5);
83        assert_eq!(pr.end(), 5);
84    }
85
86    #[test]
87    fn new_reversed_range_returns_error() {
88        let result = PageRange::new(10..5);
89        assert!(result.is_err());
90        let err = result.unwrap_err();
91        assert!(matches!(err, PdfError::InvalidPage(5)));
92    }
93
94    #[test]
95    fn empty_at_creates_empty_range() {
96        let pr = PageRange::empty_at(7);
97        assert_eq!(pr.start(), 7);
98        assert_eq!(pr.end(), 7);
99        // Empty range: start == end
100        assert_eq!(pr.start(), pr.end());
101    }
102
103    #[test]
104    fn contains_within_range() {
105        let pr = PageRange::new(2..8).unwrap();
106        assert!(!pr.contains(1));
107        assert!(pr.contains(2));
108        assert!(pr.contains(5));
109        assert!(!pr.contains(8));
110    }
111
112    #[test]
113    fn contains_empty_range() {
114        let pr = PageRange::empty_at(3);
115        assert!(!pr.contains(3));
116    }
117
118    #[test]
119    fn as_range_returns_inner() {
120        let pr = PageRange::new(1..5).unwrap();
121        let range = pr.as_range();
122        assert_eq!(*range, 1..5);
123    }
124
125    #[test]
126    fn try_from_valid_range() {
127        let pr: PageRange = (0..3).try_into().unwrap();
128        assert_eq!(pr.start(), 0);
129        assert_eq!(pr.end(), 3);
130    }
131
132    #[test]
133    fn try_from_invalid_range() {
134        let result: Result<PageRange> = (5..2).try_into();
135        assert!(result.is_err());
136    }
137
138    #[test]
139    fn clone_preserves_values() {
140        let pr = PageRange::new(1..10).unwrap();
141        let cloned = pr.clone();
142        assert_eq!(pr, cloned);
143    }
144
145    #[test]
146    fn partial_eq_works() {
147        let a = PageRange::new(0..5).unwrap();
148        let b = PageRange::new(0..5).unwrap();
149        let c = PageRange::new(0..6).unwrap();
150        assert_eq!(a, b);
151        assert_ne!(a, c);
152    }
153
154    #[test]
155    fn debug_format() {
156        let pr = PageRange::new(2..8).unwrap();
157        let dbg = format!("{:?}", pr);
158        assert!(dbg.contains('2'));
159        assert!(dbg.contains('8'));
160    }
161
162    #[test]
163    fn hash_same_values() {
164        use std::collections::HashSet;
165        let mut set = HashSet::new();
166        set.insert(PageRange::new(0..5).unwrap());
167        set.insert(PageRange::new(0..5).unwrap());
168        set.insert(PageRange::new(0..6).unwrap());
169        assert_eq!(set.len(), 2);
170    }
171}