1use lopdf::content::Content;
11use lopdf::{Document, Object, ObjectId};
12
13#[derive(Debug, Clone, Copy, PartialEq)]
15pub struct Rule {
16 pub x0: f64,
18 pub y0: f64,
20 pub x1: f64,
22 pub y1: f64,
24}
25
26impl Rule {
27 pub fn is_horizontal(&self) -> bool {
29 (self.x1 - self.x0) > (self.y1 - self.y0)
30 }
31}
32
33const MIN_RULE_LENGTH: f64 = 3.0;
35const MAX_RULE_THICKNESS: f64 = 2.5;
38const MAX_RULES: usize = 4000;
40
41type Matrix = [f64; 6];
42const IDENTITY: Matrix = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0];
43
44fn mul(m: &Matrix, n: &Matrix) -> Matrix {
45 [
47 m[0] * n[0] + m[1] * n[2],
48 m[0] * n[1] + m[1] * n[3],
49 m[2] * n[0] + m[3] * n[2],
50 m[2] * n[1] + m[3] * n[3],
51 m[4] * n[0] + m[5] * n[2] + n[4],
52 m[4] * n[1] + m[5] * n[3] + n[5],
53 ]
54}
55
56fn apply(m: &Matrix, x: f64, y: f64) -> (f64, f64) {
57 (m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5])
58}
59
60fn num(o: &Object) -> Option<f64> {
61 match o {
62 Object::Integer(i) => Some(*i as f64),
63 Object::Real(r) => Some(f64::from(*r)),
64 _ => None,
65 }
66}
67
68#[derive(Clone, Copy)]
70struct Seg {
71 a: (f64, f64),
72 b: (f64, f64),
73 from_rect: bool,
75}
76
77pub fn page_rules(doc: &Document, page_id: ObjectId) -> Vec<Rule> {
83 let data = doc.get_page_content(page_id);
84 let Ok(content) = Content::decode(&data) else {
85 return Vec::new();
86 };
87
88 let mut rules: Vec<Rule> = Vec::new();
89 let mut ctm = IDENTITY;
90 let mut stack: Vec<Matrix> = Vec::new();
91 let mut path: Vec<Seg> = Vec::new();
92 let mut cur: Option<(f64, f64)> = None;
93 let mut start: Option<(f64, f64)> = None;
94
95 for op in &content.operations {
96 let args: Vec<f64> = op.operands.iter().filter_map(num).collect();
97 match op.operator.as_str() {
98 "q" => stack.push(ctm),
99 "Q" => {
100 if let Some(m) = stack.pop() {
101 ctm = m;
102 }
103 }
104 "cm" if args.len() == 6 => {
105 let m = [args[0], args[1], args[2], args[3], args[4], args[5]];
106 ctm = mul(&m, &ctm);
107 }
108 "m" if args.len() == 2 => {
109 let p = apply(&ctm, args[0], args[1]);
110 cur = Some(p);
111 start = Some(p);
112 }
113 "l" if args.len() == 2 => {
114 let p = apply(&ctm, args[0], args[1]);
115 if let Some(c) = cur {
116 path.push(Seg {
117 a: c,
118 b: p,
119 from_rect: false,
120 });
121 }
122 cur = Some(p);
123 }
124 "h" => {
125 if let (Some(c), Some(s)) = (cur, start) {
126 path.push(Seg {
127 a: c,
128 b: s,
129 from_rect: false,
130 });
131 cur = Some(s);
132 }
133 }
134 "re" if args.len() == 4 => {
135 let (x, y, w, h) = (args[0], args[1], args[2], args[3]);
136 let p0 = apply(&ctm, x, y);
137 let p1 = apply(&ctm, x + w, y);
138 let p2 = apply(&ctm, x + w, y + h);
139 let p3 = apply(&ctm, x, y + h);
140 for (a, b) in [(p0, p1), (p1, p2), (p2, p3), (p3, p0)] {
141 path.push(Seg {
142 a,
143 b,
144 from_rect: true,
145 });
146 }
147 cur = Some(p0);
148 start = Some(p0);
149 }
150 "S" | "s" | "B" | "B*" | "b" | "b*" => {
151 collect(&path, true, &mut rules);
152 path.clear();
153 cur = None;
154 }
155 "f" | "F" | "f*" => {
156 collect(&path, false, &mut rules);
157 path.clear();
158 cur = None;
159 }
160 "n" => {
161 path.clear();
162 cur = None;
163 }
164 _ => {}
165 }
166 if rules.len() > MAX_RULES {
167 return Vec::new();
168 }
169 }
170 rules
171}
172
173fn collect(path: &[Seg], stroked: bool, out: &mut Vec<Rule>) {
174 if path.is_empty() {
175 return;
176 }
177 if !stroked {
178 if !path.iter().all(|s| s.from_rect) {
181 return;
182 }
183 for rect in path.chunks_exact(4) {
184 let xs = [rect[0].a.0, rect[1].a.0, rect[2].a.0, rect[3].a.0];
185 let ys = [rect[0].a.1, rect[1].a.1, rect[2].a.1, rect[3].a.1];
186 let (x0, x1) = min_max(&xs);
187 let (y0, y1) = min_max(&ys);
188 let (w, h) = (x1 - x0, y1 - y0);
189 if h <= MAX_RULE_THICKNESS && w >= MIN_RULE_LENGTH {
190 let y = (y0 + y1) / 2.0;
191 out.push(Rule {
192 x0,
193 y0: y,
194 x1,
195 y1: y,
196 });
197 } else if w <= MAX_RULE_THICKNESS && h >= MIN_RULE_LENGTH {
198 let x = (x0 + x1) / 2.0;
199 out.push(Rule {
200 x0: x,
201 y0,
202 x1: x,
203 y1,
204 });
205 }
206 }
207 return;
208 }
209 for s in path {
210 let (dx, dy) = ((s.b.0 - s.a.0).abs(), (s.b.1 - s.a.1).abs());
211 if dy < 0.5 && dx >= MIN_RULE_LENGTH {
212 out.push(Rule {
213 x0: s.a.0.min(s.b.0),
214 y0: s.a.1,
215 x1: s.a.0.max(s.b.0),
216 y1: s.a.1,
217 });
218 } else if dx < 0.5 && dy >= MIN_RULE_LENGTH {
219 out.push(Rule {
220 x0: s.a.0,
221 y0: s.a.1.min(s.b.1),
222 x1: s.a.0,
223 y1: s.a.1.max(s.b.1),
224 });
225 }
226 }
227}
228
229fn min_max(v: &[f64]) -> (f64, f64) {
230 v.iter()
231 .fold((f64::MAX, f64::MIN), |(lo, hi), &x| (lo.min(x), hi.max(x)))
232}