nvim_gtk/render/
context.rs

1use std::collections::HashSet;
2
3use pango;
4
5use crate::sys::pango as sys_pango;
6
7use super::itemize::ItemizeIterator;
8use crate::ui_model::StyledLine;
9
10pub struct Context {
11    font_metrics: FontMetrix,
12    font_features: FontFeatures,
13    line_space: i32,
14}
15
16impl Context {
17    pub fn new(pango_context: pango::Context) -> Self {
18        Context {
19            line_space: 0,
20            font_metrics: FontMetrix::new(pango_context, 0),
21            font_features: FontFeatures::new(),
22        }
23    }
24
25    pub fn update(&mut self, pango_context: pango::Context) {
26        self.font_metrics = FontMetrix::new(pango_context, self.line_space);
27    }
28
29    pub fn update_font_features(&mut self, font_features: FontFeatures) {
30        self.font_features = font_features;
31    }
32
33    pub fn update_line_space(&mut self, line_space: i32) {
34        self.line_space = line_space;
35        let pango_context = self.font_metrics.pango_context.clone();
36        self.font_metrics = FontMetrix::new(pango_context, self.line_space);
37    }
38
39    pub fn itemize(&self, line: &StyledLine) -> Vec<pango::Item> {
40        let attr_iter = line.attr_list.get_iterator();
41
42        ItemizeIterator::new(&line.line_str)
43            .flat_map(|(offset, len)| {
44                pango::itemize(
45                    &self.font_metrics.pango_context,
46                    &line.line_str,
47                    offset as i32,
48                    len as i32,
49                    &line.attr_list,
50                    attr_iter.as_ref(),
51                )
52            })
53            .collect()
54    }
55
56    pub fn create_layout(&self) -> pango::Layout {
57        pango::Layout::new(&self.font_metrics.pango_context)
58    }
59
60    pub fn font_description(&self) -> &pango::FontDescription {
61        &self.font_metrics.font_desc
62    }
63
64    pub fn cell_metrics(&self) -> &CellMetrics {
65        &self.font_metrics.cell_metrics
66    }
67
68    pub fn font_features(&self) -> &FontFeatures {
69        &self.font_features
70    }
71
72    pub fn font_families(&self) -> HashSet<glib::GString> {
73        self.font_metrics
74            .pango_context
75            .list_families()
76            .iter()
77            .filter_map(pango::FontFamilyExt::get_name)
78            .collect()
79    }
80}
81
82struct FontMetrix {
83    pango_context: pango::Context,
84    cell_metrics: CellMetrics,
85    font_desc: pango::FontDescription,
86}
87
88impl FontMetrix {
89    pub fn new(pango_context: pango::Context, line_space: i32) -> Self {
90        let font_metrics = pango_context.get_metrics(None, None).unwrap();
91        let font_desc = pango_context.get_font_description().unwrap();
92
93        FontMetrix {
94            pango_context,
95            cell_metrics: CellMetrics::new(&font_metrics, line_space),
96            font_desc,
97        }
98    }
99}
100
101pub struct CellMetrics {
102    pub line_height: f64,
103    pub char_width: f64,
104    pub ascent: f64,
105    pub underline_position: f64,
106    pub underline_thickness: f64,
107    pub strikethrough_position: f64,
108    pub strikethrough_thickness: f64,
109    pub pango_ascent: i32,
110    pub pango_descent: i32,
111    pub pango_char_width: i32,
112}
113
114impl CellMetrics {
115    fn new(font_metrics: &pango::FontMetrics, line_space: i32) -> Self {
116        let ascent = (f64::from(font_metrics.get_ascent()) / f64::from(pango::SCALE)).ceil();
117        let descent = (f64::from(font_metrics.get_descent()) / f64::from(pango::SCALE)).ceil();
118
119        // distance above top of underline, will typically be negative
120        let pango_underline_position = f64::from(font_metrics.get_underline_position());
121        let underline_position = (pango_underline_position / f64::from(pango::SCALE))
122            .abs()
123            .ceil()
124            .copysign(pango_underline_position);
125
126        let underline_thickness =
127            (f64::from(font_metrics.get_underline_thickness()) / f64::from(pango::SCALE)).ceil();
128
129        let strikethrough_position =
130            (f64::from(font_metrics.get_strikethrough_position()) / f64::from(pango::SCALE)).ceil();
131        let strikethrough_thickness = (f64::from(font_metrics.get_strikethrough_thickness())
132            / f64::from(pango::SCALE))
133        .ceil();
134
135        CellMetrics {
136            pango_ascent: font_metrics.get_ascent(),
137            pango_descent: font_metrics.get_descent(),
138            pango_char_width: font_metrics.get_approximate_char_width(),
139            ascent,
140            line_height: ascent + descent + f64::from(line_space),
141            char_width: f64::from(font_metrics.get_approximate_char_width())
142                / f64::from(pango::SCALE),
143            underline_position: ascent - underline_position + underline_thickness / 2.0,
144            underline_thickness,
145            strikethrough_position: ascent - strikethrough_position + strikethrough_thickness / 2.0,
146            strikethrough_thickness,
147        }
148    }
149
150    #[cfg(test)]
151    pub fn new_hw(line_height: f64, char_width: f64) -> Self {
152        CellMetrics {
153            pango_ascent: 0,
154            pango_descent: 0,
155            pango_char_width: 0,
156            ascent: 0.0,
157            line_height,
158            char_width,
159            underline_position: 0.0,
160            underline_thickness: 0.0,
161            strikethrough_position: 0.0,
162            strikethrough_thickness: 0.0,
163        }
164    }
165}
166
167pub struct FontFeatures {
168    attr: Option<pango::Attribute>,
169}
170
171impl FontFeatures {
172    pub fn new() -> Self {
173        FontFeatures { attr: None }
174    }
175
176    pub fn from(font_features: String) -> Self {
177        if font_features.trim().is_empty() {
178            return Self::new();
179        }
180
181        FontFeatures {
182            attr: sys_pango::attribute::new_features(&font_features),
183        }
184    }
185
186    pub fn insert_into(&self, attr_list: &pango::AttrList) {
187        if let Some(ref attr) = self.attr {
188            attr_list.insert(attr.clone());
189        }
190    }
191}