1mod context;
2mod itemize;
3mod model_clip_iterator;
4
5pub use self::context::CellMetrics;
6pub use self::context::{Context, FontFeatures};
7use self::model_clip_iterator::{ModelClipIteratorFactory, RowView};
8
9use crate::color;
10use crate::sys::pangocairo::*;
11use cairo;
12use pango;
13use pangocairo;
14
15use crate::cursor::{cursor_rect, Cursor};
16use crate::highlight::HighlightMap;
17use crate::ui_model;
18
19trait ContextAlpha {
20 fn set_source_rgbo(&self, _: &color::Color, _: Option<f64>);
21}
22
23impl ContextAlpha for cairo::Context {
24 fn set_source_rgbo(&self, color: &color::Color, alpha: Option<f64>) {
25 if let Some(alpha) = alpha {
26 self.set_source_rgba(color.0, color.1, color.2, alpha);
27 } else {
28 self.set_source_rgb(color.0, color.1, color.2);
29 }
30 }
31}
32
33pub fn fill_background(ctx: &cairo::Context, hl: &HighlightMap, alpha: Option<f64>) {
34 ctx.set_source_rgbo(hl.bg(), alpha);
35 ctx.paint();
36}
37
38pub fn render<C: Cursor>(
39 ctx: &cairo::Context,
40 cursor: &C,
41 font_ctx: &context::Context,
42 ui_model: &ui_model::UiModel,
43 hl: &HighlightMap,
44 bg_alpha: Option<f64>,
45) {
46 let cell_metrics = font_ctx.cell_metrics();
47 let &CellMetrics { char_width, .. } = cell_metrics;
48
49 ctx.set_antialias(cairo::Antialias::None);
52 for row_view in ui_model.get_clip_iterator(ctx, cell_metrics) {
53 let mut line_x = 0.0;
54
55 for (col, cell) in row_view.line.line.iter().enumerate() {
56 draw_cell_bg(&row_view, hl, cell, col, line_x, bg_alpha);
57 line_x += char_width;
58 }
59 }
60 ctx.set_antialias(cairo::Antialias::Default);
61
62 for row_view in ui_model.get_clip_iterator(ctx, cell_metrics) {
64 let mut line_x = 0.0;
65
66 for (col, cell) in row_view.line.line.iter().enumerate() {
67 draw_cell(&row_view, hl, cell, col, line_x, 0.0);
68 draw_underline_strikethrough(&row_view, hl, cell, line_x, 0.0);
69
70 line_x += char_width;
71 }
72 }
73
74 draw_cursor(ctx, cursor, font_ctx, ui_model, hl, bg_alpha);
75}
76
77fn draw_cursor<C: Cursor>(
78 ctx: &cairo::Context,
79 cursor: &C,
80 font_ctx: &context::Context,
81 ui_model: &ui_model::UiModel,
82 hl: &HighlightMap,
83 bg_alpha: Option<f64>,
84) {
85 let cell_metrics = font_ctx.cell_metrics();
86 let (cursor_row, cursor_col) = ui_model.get_cursor();
87
88 let (x1, y1, x2, y2) = ctx.clip_extents();
89 let line_x = cursor_col as f64 * cell_metrics.char_width;
90 let line_y = cursor_row as f64 * cell_metrics.line_height;
91
92 if line_x < x1 || line_y < y1 || line_x > x2 || line_y > y2 || !cursor.is_visible() {
93 return;
94 }
95
96 let cell_metrics = font_ctx.cell_metrics();
97 let row_view = ui_model.get_row_view(ctx, cell_metrics, cursor_row);
98 let cell_start_col = row_view.line.cell_to_item(cursor_col);
99
100 if let Some(cursor_line) = ui_model.model().get(cursor_row) {
101 let double_width = cursor_line
102 .line
103 .get(cursor_col + 1)
104 .map_or(false, |c| c.double_width);
105
106 if cell_start_col >= 0 {
107 let cell = &cursor_line[cursor_col];
108
109 let (clip_y, clip_width, clip_height) =
111 cursor_rect(cursor.mode_info(), cell_metrics, line_y, double_width);
112 ctx.rectangle(line_x, clip_y, clip_width, clip_height);
113 ctx.clip();
114
115 ctx.set_antialias(cairo::Antialias::None);
118 ctx.set_operator(cairo::Operator::Source);
119 fill_background(ctx, hl, bg_alpha);
120 draw_cell_bg(&row_view, hl, cell, cursor_col, line_x, bg_alpha);
121 ctx.set_antialias(cairo::Antialias::Default);
122
123 ctx.set_operator(cairo::Operator::Over);
125 ctx.move_to(line_x, line_y);
126 let cursor_alpha = cursor.draw(ctx, font_ctx, line_y, double_width, &hl);
127
128 let cell_start_line_x =
129 line_x - (cursor_col as i32 - cell_start_col) as f64 * cell_metrics.char_width;
130
131 debug_assert!(cell_start_line_x >= 0.0);
132
133 ctx.set_operator(cairo::Operator::Xor);
134 draw_cell(
135 &row_view,
136 hl,
137 cell,
138 cell_start_col as usize,
139 cell_start_line_x,
140 cursor_alpha,
141 );
142 draw_underline_strikethrough(&row_view, hl, cell, line_x, cursor_alpha);
143 } else {
144 ctx.move_to(line_x, line_y);
145 cursor.draw(ctx, font_ctx, line_y, double_width, &hl);
146 }
147 }
148}
149
150fn draw_underline_strikethrough(
151 cell_view: &RowView,
152 hl: &HighlightMap,
153 cell: &ui_model::Cell,
154 line_x: f64,
155 inverse_level: f64,
156) {
157 if cell.hl.underline || cell.hl.undercurl || cell.hl.strikethrough {
158 let &RowView {
159 ctx,
160 line_y,
161 cell_metrics:
162 &CellMetrics {
163 line_height,
164 char_width,
165 underline_position,
166 underline_thickness,
167 strikethrough_position,
168 strikethrough_thickness,
169 ..
170 },
171 ..
172 } = cell_view;
173
174 if cell.hl.strikethrough {
175 let fg = hl.actual_cell_fg(cell).inverse(inverse_level);
176 ctx.set_source_rgb(fg.0, fg.1, fg.2);
177 ctx.set_line_width(strikethrough_thickness);
178 ctx.move_to(line_x, line_y + strikethrough_position);
179 ctx.line_to(line_x + char_width, line_y + strikethrough_position);
180 ctx.stroke();
181 }
182
183 if cell.hl.undercurl {
184 let sp = hl.actual_cell_sp(cell).inverse(inverse_level);
185 ctx.set_source_rgba(sp.0, sp.1, sp.2, 0.7);
186
187 let max_undercurl_height = (line_height - underline_position) * 2.0;
188 let undercurl_height = (underline_thickness * 4.0).min(max_undercurl_height);
189 let undercurl_y = line_y + underline_position - undercurl_height / 2.0;
190
191 pangocairo::functions::show_error_underline(
192 ctx,
193 line_x,
194 undercurl_y,
195 char_width,
196 undercurl_height,
197 );
198 } else if cell.hl.underline {
199 let fg = hl.actual_cell_fg(cell).inverse(inverse_level);
200 ctx.set_source_rgb(fg.0, fg.1, fg.2);
201 ctx.set_line_width(underline_thickness);
202 ctx.move_to(line_x, line_y + underline_position);
203 ctx.line_to(line_x + char_width, line_y + underline_position);
204 ctx.stroke();
205 }
206 }
207}
208
209fn draw_cell_bg(
210 cell_view: &RowView,
211 hl: &HighlightMap,
212 cell: &ui_model::Cell,
213 col: usize,
214 line_x: f64,
215 bg_alpha: Option<f64>,
216) {
217 let &RowView {
218 ctx,
219 line,
220 line_y,
221 cell_metrics:
222 &CellMetrics {
223 char_width,
224 line_height,
225 ..
226 },
227 ..
228 } = cell_view;
229
230 let bg = hl.cell_bg(cell);
231
232 if let Some(bg) = bg {
233 if !line.is_binded_to_item(col) {
234 if bg != hl.bg() {
235 ctx.set_source_rgbo(bg, bg_alpha);
236 ctx.rectangle(line_x, line_y, char_width, line_height);
237 ctx.fill();
238 }
239 } else {
240 ctx.set_source_rgbo(bg, bg_alpha);
241 ctx.rectangle(
242 line_x,
243 line_y,
244 char_width * line.item_len_from_idx(col) as f64,
245 line_height,
246 );
247 ctx.fill();
248 }
249 }
250}
251
252fn draw_cell(
253 row_view: &RowView,
254 hl: &HighlightMap,
255 cell: &ui_model::Cell,
256 col: usize,
257 line_x: f64,
258 inverse_level: f64,
259) {
260 let &RowView {
261 ctx,
262 line,
263 line_y,
264 cell_metrics: &CellMetrics { ascent, .. },
265 ..
266 } = row_view;
267
268 if let Some(item) = line.item_line[col].as_ref() {
269 if let Some(ref glyphs) = item.glyphs {
270 let fg = hl.actual_cell_fg(cell).inverse(inverse_level);
271
272 ctx.move_to(line_x, line_y + ascent);
273 ctx.set_source_rgb(fg.0, fg.1, fg.2);
274
275 show_glyph_string(ctx, item.font(), glyphs);
276 }
277 }
278}
279
280pub fn shape_dirty(ctx: &context::Context, ui_model: &mut ui_model::UiModel, hl: &HighlightMap) {
281 for line in ui_model.model_mut() {
282 if !line.dirty_line {
283 continue;
284 }
285
286 let styled_line = ui_model::StyledLine::from(line, hl, ctx.font_features());
287 let items = ctx.itemize(&styled_line);
288 line.merge(&styled_line, &items);
289
290 for (col, cell) in line.line.iter_mut().enumerate() {
291 if cell.dirty {
292 if let Some(item) = line.item_line[col].as_mut() {
293 let mut glyphs = pango::GlyphString::new();
294 {
295 let analysis = item.analysis();
296 let offset = item.item.offset() as usize;
297 let length = item.item.length() as usize;
298 if let Some(line_str) = styled_line.line_str.get(offset..offset + length) {
299 pango::shape(&line_str, analysis, &mut glyphs);
300 } else {
301 warn!("Wrong itemize split");
302 }
303 }
304
305 item.set_glyphs(ctx, glyphs);
306 }
307 }
308
309 cell.dirty = false;
310 }
311
312 line.dirty_line = false;
313 }
314}