Skip to main content

zenthra_text/primitives/
shaped_buffer.rs

1use crate::types::shaped_glyph::ShapedGlyph;
2use crate::types::line::LineInfo;
3
4/// A buffer of shaped and positioned glyphs.
5/// This is the final output of a text shaping operation.
6#[derive(Debug, Clone, Default)]
7pub struct ShapedBuffer {
8    glyphs: Vec<ShapedGlyph>,
9    lines: Vec<LineInfo>,
10    width: f32,
11    height: f32,
12}
13
14impl ShapedBuffer {
15    /// Creates a new empty ShapedBuffer.
16    pub fn new(glyphs: Vec<ShapedGlyph>, lines: Vec<LineInfo>, width: f32, height: f32) -> Self {
17        Self { glyphs, lines, width, height }
18    }
19
20    /// Returns the list of shaped glyphs in this buffer.
21    pub fn glyphs(&self) -> &[ShapedGlyph] {
22        &self.glyphs
23    }
24
25    /// Returns the tracked line information for this buffer.
26    pub fn lines(&self) -> &[LineInfo] {
27        &self.lines
28    }
29
30    /// Returns the number of glyphs in the buffer.
31    pub fn len(&self) -> usize {
32        self.glyphs.len()
33    }
34
35    /// Returns true if the buffer contains no glyphs.
36    pub fn is_empty(&self) -> bool {
37        self.glyphs.is_empty()
38    }
39
40    /// Returns the logical width and height of the text content itself (no padding).
41    pub fn content_size(&self) -> (f32, f32) {
42        (self.width, self.height)
43    }
44
45    /// Returns the visual width and height including the provided padding.
46    pub fn outer_size(&self, padding: &crate::types::options::Padding) -> (f32, f32) {
47        (
48            self.width + padding.left + padding.right,
49            self.height + padding.top + padding.bottom,
50        )
51    }
52
53    /// Returns the logical (width, height) used during the last shaping pass.
54    /// This is an alias for `content_size()`.
55    pub fn size(&self) -> (f32, f32) {
56        self.content_size()
57    }
58
59    /// Finds the character byte index closest to the given (x, y) coordinates.
60    pub fn index_at(&self, x: f32, y: f32) -> usize {
61        if self.lines.is_empty() || self.glyphs.is_empty() {
62            return 0;
63        }
64
65        // 1. Find the line with the closest Y coordinate
66        let mut best_line_idx = 0;
67        let mut min_dist_y = f32::MAX;
68
69        for (i, line) in self.lines.iter().enumerate() {
70            let dist = (y - line.y).abs();
71            if dist < min_dist_y {
72                min_dist_y = dist;
73                best_line_idx = i;
74            }
75        }
76
77        let best_line = &self.lines[best_line_idx];
78
79        // 2. Find the glyph on that line with the closest X coordinate
80        let mut best_cluster = 0;
81        let mut min_dist_x = f32::MAX;
82
83        // Note: For now we assume glyphs on a line share the same Y.
84        // In a complex multi-font line, Y might vary slightly, so we use a threshold.
85        let mut found_glyph = false;
86        for glyph in &self.glyphs {
87            if (glyph.y - best_line.y).abs() < 1.0 {
88                let center_x = glyph.x + glyph.width / 2.0;
89                let dist = (x - center_x).abs();
90                if dist < min_dist_x {
91                    min_dist_x = dist;
92                    best_cluster = glyph.cluster;
93                    found_glyph = true;
94                }
95            }
96        }
97
98        if !found_glyph {
99            // Fallback to first/last cluster if no glyphs on the "best" line were found
100            return self.glyphs.first().map(|g| g.cluster).unwrap_or(0);
101        }
102
103        best_cluster
104    }
105
106    /// Returns the (x, y) coordinates for a given character byte index.
107    pub fn position_at(&self, index: usize) -> Option<(f32, f32)> {
108        self.glyphs.iter()
109            .find(|g| g.cluster == index)
110            .map(|g| (g.x, g.y))
111    }
112}
113
114
115