Skip to main content

pdfrum_anyrender/
area.rs

1//! Which glyphs a page keeps, and a count of what happened to them.
2
3use peniko::kurbo::{Point, Rect};
4
5/// Where a page's glyphs may land, in its scene's coordinates.
6#[derive(Debug, Clone, Copy, PartialEq, Default)]
7pub enum GlyphArea {
8    /// Every glyph is drawn.
9    #[default]
10    Anywhere,
11    /// A glyph is drawn only if the centre of its box (its advance across, ascent to descent
12    /// down) lies inside: a page's clip hides what falls outside it, but a PDF's text layer
13    /// does not, so a glyph under the clip would still be found, selected and copied from the
14    /// wrong page. The centre decides so each glyph belongs to exactly one of two pages that
15    /// share an edge.
16    Within(Rect),
17}
18
19/// Whether a glyph is kept.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub(crate) enum Keep {
22    Yes,
23    No,
24}
25
26impl GlyphArea {
27    pub(crate) fn holds(self, point: Point) -> Keep {
28        match self {
29            GlyphArea::Anywhere => Keep::Yes,
30            GlyphArea::Within(rect) => {
31                let inside = point.x >= rect.x0
32                    && point.x < rect.x1
33                    && point.y >= rect.y0
34                    && point.y < rect.y1;
35                if inside { Keep::Yes } else { Keep::No }
36            }
37        }
38    }
39}
40
41/// What the pages did with the glyphs they were given.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
43pub struct GlyphTally {
44    /// Glyphs written.
45    pub drawn: usize,
46    /// Glyphs outside their page's [`GlyphArea`] (or past the 16-bit glyph ids PDF can
47    /// name), not written.
48    pub dropped: usize,
49    /// Written glyphs whose text came from the caller's [`RunTexts`](crate::RunTexts).
50    pub layout_text: usize,
51    /// Written glyphs whose text was read back from the font's cmap.
52    pub cmap_text: usize,
53}
54
55impl std::ops::Add for GlyphTally {
56    type Output = GlyphTally;
57
58    fn add(self, other: GlyphTally) -> GlyphTally {
59        GlyphTally {
60            drawn: self.drawn + other.drawn,
61            dropped: self.dropped + other.dropped,
62            layout_text: self.layout_text + other.layout_text,
63            cmap_text: self.cmap_text + other.cmap_text,
64        }
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::{GlyphArea, Keep};
71    use peniko::kurbo::{Point, Rect};
72
73    #[test]
74    fn the_area_keeps_its_top_edge_and_not_its_bottom() {
75        const CASES: &[((f64, f64), Keep)] = &[
76            ((10.0, 0.0), Keep::Yes),
77            ((10.0, 49.9), Keep::Yes),
78            ((10.0, 50.0), Keep::No),
79            ((-0.1, 10.0), Keep::No),
80            ((100.0, 10.0), Keep::No),
81        ];
82        let area = GlyphArea::Within(Rect::new(0.0, 0.0, 100.0, 50.0));
83        for &((x, y), want) in CASES {
84            assert_eq!(area.holds(Point::new(x, y)), want, "({x}, {y})");
85        }
86        assert_eq!(GlyphArea::Anywhere.holds(Point::new(1e9, -1e9)), Keep::Yes);
87    }
88}