Skip to main content

rapiddoc_core/
geometry.rs

1//! 明确坐标系的仿射变换;所有运算拒绝非有限值,空交集保留为空矩形。
2
3use crate::{Error, ErrorCode, Rect};
4
5/// PDF 约定的六参数仿射变换,x'=a*x+c*y+e,y'=b*x+d*y+f。
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct Affine([f64; 6]);
8
9impl Affine {
10    pub const IDENTITY: Self = Self([1., 0., 0., 1., 0., 0.]);
11
12    /// 返回值拷贝,供事实层记录完整变换,不暴露可变内部状态。
13    pub fn values(self) -> [f64; 6] {
14        self.0
15    }
16
17    /// 允许奇异矩阵以保留退化对象,但不允许 NaN 或无限值。
18    pub fn new(values: [f64; 6]) -> Result<Self, Error> {
19        if values.iter().any(|value| !value.is_finite()) {
20            return Err(invalid("变换矩阵包含非有限值"));
21        }
22        Ok(Self(values))
23    }
24
25    /// 应用变换并检查中间结果溢出。
26    pub fn point(self, point: [f64; 2]) -> Result<[f64; 2], Error> {
27        let [a, b, c, d, e, f] = self.0;
28        let [x, y] = point;
29        let result = [a * x + c * y + e, b * x + d * y + f];
30        if result.iter().any(|value| !value.is_finite()) {
31            return Err(invalid("点变换产生非有限坐标"));
32        }
33        Ok(result)
34    }
35
36    /// 先执行 self 再执行 next,适用于局部对象矩阵后接父级矩阵。
37    pub fn then(self, next: Self) -> Result<Self, Error> {
38        let [a, b, c, d, e, f] = self.0;
39        let [g, h, i, j, k, l] = next.0;
40        Self::new([
41            g * a + i * b,
42            h * a + j * b,
43            g * c + i * d,
44            h * c + j * d,
45            g * e + i * f + k,
46            h * e + j * f + l,
47        ])
48    }
49
50    /// 逆变换仅用于可逆坐标系;零行列式明确报错。
51    pub fn inverse(self) -> Result<Self, Error> {
52        let [a, b, c, d, e, f] = self.0;
53        let determinant = a * d - b * c;
54        if determinant == 0. || !determinant.is_finite() {
55            return Err(invalid("变换矩阵不可逆"));
56        }
57        Self::new([
58            d / determinant,
59            -b / determinant,
60            -c / determinant,
61            a / determinant,
62            (c * f - d * e) / determinant,
63            (b * e - a * f) / determinant,
64        ])
65    }
66
67    /// 变换矩形四角后求外框,不能仅变换对角线而丢失旋转或斜切范围。
68    pub fn rect(self, rect: Rect) -> Result<Rect, Error> {
69        let [left, bottom, right, top] = rect.into();
70        bounds(&[
71            self.point([left, bottom])?,
72            self.point([left, top])?,
73            self.point([right, bottom])?,
74            self.point([right, top])?,
75        ])
76    }
77}
78
79/// PDF 原生坐标到旋转后左上原点 point 的映射;bbox 为裁剪后的未旋转页面框。
80pub fn page_transform(bbox: Rect, rotation: u16) -> Result<Affine, Error> {
81    let [left, bottom, right, top] = bbox.into();
82    if right <= left || top <= bottom {
83        return Err(invalid("页面框必须有正面积"));
84    }
85    Affine::new(match rotation {
86        0 => [1., 0., 0., -1., -left, top],
87        90 => [0., 1., 1., 0., -bottom, -left],
88        180 => [-1., 0., 0., 1., right, -bottom],
89        270 => [0., -1., -1., 0., top, right],
90        _ => return Err(invalid("页面旋转必须为 0/90/180/270")),
91    })
92}
93
94/// 对有限点集求框;空点集不是有效的几何证据。
95pub fn bounds(points: &[[f64; 2]]) -> Result<Rect, Error> {
96    let first = points.first().ok_or_else(|| invalid("几何点集为空"))?;
97    let mut values = [first[0], first[1], first[0], first[1]];
98    for [x, y] in points {
99        if !x.is_finite() || !y.is_finite() {
100            return Err(invalid("点集包含非有限值"));
101        }
102        values[0] = values[0].min(*x);
103        values[1] = values[1].min(*y);
104        values[2] = values[2].max(*x);
105        values[3] = values[3].max(*y);
106    }
107    Rect::try_from(values)
108}
109
110/// 空交集保留零面积,不允许重新解释为没有裁剪。
111pub fn intersect(first: Rect, second: Rect) -> Result<Rect, Error> {
112    let [a, b, c, d] = first.into();
113    let [e, f, g, h] = second.into();
114    let left = a.max(e);
115    let bottom = b.max(f);
116    Rect::try_from([left, bottom, c.min(g).max(left), d.min(h).max(bottom)])
117}
118
119/// 构造坐标输入错误。
120fn invalid(message: &str) -> Error {
121    Error {
122        code: ErrorCode::InvalidInput,
123        message: message.into(),
124    }
125}