1use crate::{InterpreterWarning, WarningSinkFn};
4use kurbo::{Affine, BezPath, PathEl, Rect};
5use log::warn;
6use pdf_syntax::object::Stream;
7use pdf_syntax::object::stream::DecodeFailure;
8use pdf_syntax::page::{Page, Rotation};
9use siphasher::sip128::{Hasher128, SipHasher13};
10use std::hash::Hash;
11use std::ops::Sub;
12
13pub(crate) fn decode_or_warn(stream: &Stream<'_>, sink: &WarningSinkFn) -> Option<Vec<u8>> {
14 match stream.decoded() {
15 Ok(data) => Some(data),
16 Err(DecodeFailure::StreamTooLarge { observed, limit }) => {
17 sink(InterpreterWarning::StreamTooLarge { observed, limit });
18 None
19 }
20 Err(_) => None,
21 }
22}
23
24pub(crate) trait OptionLog {
25 fn warn_none(self, f: &str) -> Self;
26}
27
28impl<T> OptionLog for Option<T> {
29 #[inline]
30 fn warn_none(self, f: &str) -> Self {
31 self.or_else(|| {
32 warn!("{f}");
33
34 None
35 })
36 }
37}
38
39const SCALAR_NEARLY_ZERO: f32 = 1.0 / (1 << 8) as f32;
40
41pub trait Float32Ext: Sized + Sub<f32, Output = f32> + Copy + PartialOrd<f32> {
43 fn is_nearly_zero(&self) -> bool {
45 self.is_nearly_zero_within_tolerance(SCALAR_NEARLY_ZERO)
46 }
47
48 fn is_nearly_equal(&self, other: f32) -> bool {
50 (*self - other).is_nearly_zero()
51 }
52
53 fn is_nearly_less_or_equal(&self, other: f32) -> bool {
55 (*self - other).is_nearly_zero() || *self < other
56 }
57
58 fn is_nearly_greater_or_equal(&self, other: f32) -> bool {
60 (*self - other).is_nearly_zero() || *self > other
61 }
62
63 fn is_nearly_zero_within_tolerance(&self, tolerance: f32) -> bool;
65}
66
67impl Float32Ext for f32 {
68 fn is_nearly_zero_within_tolerance(&self, tolerance: f32) -> bool {
69 debug_assert!(tolerance >= 0.0, "tolerance must be non-negative");
70
71 self.abs() <= tolerance
72 }
73}
74
75pub trait Float64Ext: Sized + Sub<f64, Output = f64> + Copy + PartialOrd<f64> {
77 fn is_nearly_zero(&self) -> bool {
79 self.is_nearly_zero_within_tolerance(SCALAR_NEARLY_ZERO as f64)
80 }
81
82 fn is_nearly_equal(&self, other: f64) -> bool {
84 (*self - other).is_nearly_zero()
85 }
86
87 fn is_nearly_less_or_equal(&self, other: f64) -> bool {
89 (*self - other).is_nearly_zero() || *self < other
90 }
91
92 fn is_nearly_greater_or_equal(&self, other: f64) -> bool {
94 (*self - other).is_nearly_zero() || *self > other
95 }
96
97 fn is_nearly_zero_within_tolerance(&self, tolerance: f64) -> bool;
99}
100
101impl Float64Ext for f64 {
102 fn is_nearly_zero_within_tolerance(&self, tolerance: f64) -> bool {
103 debug_assert!(tolerance >= 0.0, "tolerance must be non-negative");
104
105 self.abs() <= tolerance
106 }
107}
108
109pub(crate) trait PointExt: Sized {
110 fn x(&self) -> f32;
111 fn y(&self) -> f32;
112
113 fn nearly_same(&self, other: Self) -> bool {
114 self.x().is_nearly_equal(other.x()) && self.y().is_nearly_equal(other.y())
115 }
116}
117
118impl PointExt for kurbo::Point {
119 fn x(&self) -> f32 {
120 self.x as f32
121 }
122
123 fn y(&self) -> f32 {
124 self.y as f32
125 }
126}
127
128pub(crate) fn hash128<T: Hash + ?Sized>(value: &T) -> u128 {
130 let mut state = SipHasher13::new();
131 value.hash(&mut state);
132 state.finish128().as_u128()
133}
134
135pub(crate) trait BezPathExt {
136 fn fast_bounding_box(&self) -> Rect;
137}
138
139impl BezPathExt for BezPath {
140 fn fast_bounding_box(&self) -> Rect {
141 let mut min_x = f64::INFINITY;
142 let mut min_y = f64::INFINITY;
143 let mut max_x = f64::NEG_INFINITY;
144 let mut max_y = f64::NEG_INFINITY;
145
146 let mut include = |x: f64, y: f64| {
147 min_x = min_x.min(x);
148 min_y = min_y.min(y);
149 max_x = max_x.max(x);
150 max_y = max_y.max(y);
151 };
152
153 for el in self.elements() {
154 match *el {
155 PathEl::MoveTo(p) | PathEl::LineTo(p) => include(p.x, p.y),
156 PathEl::QuadTo(p1, p2) => {
157 include(p1.x, p1.y);
158 include(p2.x, p2.y);
159 }
160 PathEl::CurveTo(p1, p2, p3) => {
161 include(p1.x, p1.y);
162 include(p2.x, p2.y);
163 include(p3.x, p3.y);
164 }
165 PathEl::ClosePath => {}
166 }
167 }
168
169 if min_x > max_x {
170 Rect::ZERO
171 } else {
172 Rect::new(min_x, min_y, max_x, max_y)
173 }
174 }
175}
176
177pub trait RectExt {
179 fn to_kurbo(&self) -> Rect;
181}
182
183impl RectExt for pdf_syntax::object::Rect {
184 fn to_kurbo(&self) -> Rect {
185 Rect::new(self.x0, self.y0, self.x1, self.y1)
186 }
187}
188
189pub trait PageExt {
192 fn initial_transform(&self, invert_y: bool) -> Affine;
196}
197
198impl PageExt for Page<'_> {
199 fn initial_transform(&self, invert_y: bool) -> Affine {
200 let crop_box = self.crop_box();
208 let (_, base_height) = self.base_dimensions();
209 let (width, height) = self.render_dimensions();
210
211 let horizontal_t =
212 Affine::rotate(90.0_f64.to_radians()) * Affine::translate((0.0, -width as f64));
213 let flipped_horizontal_t =
214 Affine::translate((0.0, height as f64)) * Affine::rotate(-90.0_f64.to_radians());
215
216 let rotation_transform = match self.rotation() {
217 Rotation::None => Affine::IDENTITY,
218 Rotation::Horizontal => {
219 if invert_y {
220 horizontal_t
221 } else {
222 flipped_horizontal_t
223 }
224 }
225 Rotation::Flipped => {
226 Affine::scale(-1.0) * Affine::translate((-width as f64, -height as f64))
227 }
228 Rotation::FlippedHorizontal => {
229 if invert_y {
230 flipped_horizontal_t
231 } else {
232 horizontal_t
233 }
234 }
235 };
236
237 let inversion_transform = if invert_y {
238 Affine::new([1.0, 0.0, 0.0, -1.0, 0.0, base_height as f64])
239 } else {
240 Affine::IDENTITY
241 };
242
243 rotation_transform * inversion_transform * Affine::translate((-crop_box.x0, -crop_box.y0))
244 }
245}
246
247pub(crate) fn decode_text_string(bytes: &[u8]) -> Option<String> {
255 if bytes.len() >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF {
256 let mut units = Vec::with_capacity(bytes.len() / 2);
257 let mut rest = &bytes[2..];
258 while rest.len() >= 2 {
259 units.push(u16::from_be_bytes([rest[0], rest[1]]));
260 rest = &rest[2..];
261 }
262 if let Some(&last) = rest.first() {
263 units.push(u16::from_be_bytes([last, 0]));
264 }
265 return Some(String::from_utf16_lossy(&units));
266 }
267 if bytes.len() >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF {
268 return Some(String::from_utf8_lossy(&bytes[3..]).into_owned());
269 }
270 let mut out = String::with_capacity(bytes.len());
271 for &byte in bytes {
272 let code = match byte {
273 0x18 => 0x02D8,
274 0x19 => 0x02C7,
275 0x1A => 0x02C6,
276 0x1B => 0x02D9,
277 0x1C => 0x02DD,
278 0x1D => 0x02DB,
279 0x1E => 0x02DA,
280 0x1F => 0x02DC,
281 0x80 => 0x2022,
282 0x81 => 0x2020,
283 0x82 => 0x2021,
284 0x83 => 0x2026,
285 0x84 => 0x2014,
286 0x85 => 0x2013,
287 0x86 => 0x0192,
288 0x87 => 0x2044,
289 0x88 => 0x2039,
290 0x89 => 0x203A,
291 0x8A => 0x2212,
292 0x8B => 0x2030,
293 0x8C => 0x201E,
294 0x8D => 0x201C,
295 0x8E => 0x201D,
296 0x8F => 0x2018,
297 0x90 => 0x2019,
298 0x91 => 0x201A,
299 0x92 => 0x2122,
300 0x93 => 0xFB01,
301 0x94 => 0xFB02,
302 0x95 => 0x0141,
303 0x96 => 0x0152,
304 0x97 => 0x0160,
305 0x98 => 0x0178,
306 0x99 => 0x017D,
307 0x9A => 0x0131,
308 0x9B => 0x0142,
309 0x9C => 0x0153,
310 0x9D => 0x0161,
311 0x9E => 0x017E,
312 0xA0 => 0x20AC,
313 0x7F | 0x9F => 0xFFFD,
314 _ => byte as u32,
315 };
316 out.push(char::from_u32(code).unwrap_or('\u{FFFD}'));
317 }
318 Some(out)
319}