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