1use crate::{
2 ansi,
3 damage::{Damage, Rect},
4 BlitCell, BoxCharset, Cell, Frame, GlyphRegistry, Grid, RenderOp, Style, TruncateMode,
5};
6
7const DEFAULT_ELLIPSIS: &str = "…";
8const DAMAGE_MAX_RECTS: usize = 64;
9use crate::text::{
10 clip_to_cells_spans, clip_to_cells_text, ellipsis_to_cells_spans, ellipsis_to_cells_text,
11 normalize_spans, wrap_spans_wordwise, Span, WrapOpts,
12};
13use thiserror::Error;
14use unicode_segmentation::UnicodeSegmentation;
15
16#[derive(Debug, Error)]
17pub enum RenderError {
18 #[error("invalid grid size")]
19 InvalidGridSize,
20}
21
22#[derive(Debug, Clone)]
24pub struct Renderer {
25 grid: Grid,
26 reg: GlyphRegistry,
27}
28
29impl Renderer {
30 pub fn new(width: u16, height: u16, reg: GlyphRegistry) -> Result<Self, RenderError> {
31 if width == 0 || height == 0 {
32 return Err(RenderError::InvalidGridSize);
33 }
34 Ok(Self {
35 grid: Grid::new(width, height),
36 reg,
37 })
38 }
39
40 pub fn grid(&self) -> &Grid {
41 &self.grid
42 }
43
44 pub fn grid_mut(&mut self) -> &mut Grid {
45 &mut self.grid
46 }
47
48 pub fn apply(&mut self, frame: &Frame) {
49 for op in &frame.ops {
50 self.apply_op(op);
51 }
52 }
53
54 pub fn apply_with_damage(&mut self, frame: &Frame) -> Damage {
60 let mut dmg = Damage::empty();
61 for op in &frame.ops {
62 self.apply_op(op);
63 self.note_damage_for_op(op, &mut dmg);
64 if dmg.full_redraw {
65 break;
66 }
67 }
68 dmg
69 }
70
71 fn note_damage_for_op(&self, op: &RenderOp, dmg: &mut Damage) {
72 let w = self.grid.width;
73 let h = self.grid.height;
74
75 fn clip_rect(r: Rect, gw: u16, gh: u16) -> Rect {
76 if r.w == 0 || r.h == 0 {
77 return Rect::new(0, 0, 0, 0);
78 }
79 let x1 = r.x.min(gw);
80 let y1 = r.y.min(gh);
81 let x2 = r.right().min(gw);
82 let y2 = r.bottom().min(gh);
83 Rect::new(x1, y1, x2.saturating_sub(x1), y2.saturating_sub(y1))
84 }
85
86 let mut push = |r: Rect| {
87 let r = clip_rect(r, w, h);
88 dmg.push_rect(r, DAMAGE_MAX_RECTS);
89 };
90
91 match op {
92 RenderOp::Clear => {
93 dmg.full_redraw = true;
94 dmg.rects.clear();
95 }
96 RenderOp::ClearLine { y } => push(Rect::new(0, *y, w, 1)),
97 RenderOp::ClearEol { x, y } => push(Rect::new(*x, *y, w.saturating_sub(*x), 1)),
98 RenderOp::ClearBol { x, y } => push(Rect::new(0, *y, x.saturating_add(1), 1)),
99 RenderOp::ClearEos { x, y } => {
100 push(Rect::new(*x, *y, w.saturating_sub(*x), 1));
102 if y.saturating_add(1) < h {
103 push(Rect::new(
104 0,
105 y.saturating_add(1),
106 w,
107 h.saturating_sub(y.saturating_add(1)),
108 ));
109 }
110 }
111 RenderOp::ClearRect { x, y, w: rw, h: rh } => push(Rect::new(*x, *y, *rw, *rh)),
112 RenderOp::FillRect {
113 x, y, w: rw, h: rh, ..
114 } => push(Rect::new(*x, *y, *rw, *rh)),
115 RenderOp::Put { x, y, text, .. } => {
116 let mw = crate::text::measure_cells_text(&self.reg, text) as u16;
117 push(Rect::new(*x, *y, mw, 1));
118 }
119 RenderOp::PutGlyph { x, y, glyph, .. } => {
120 let mw = crate::text::measure_cells_text(&self.reg, glyph) as u16;
121 push(Rect::new(*x, *y, mw, 1));
122 }
123 RenderOp::Label { x, y, w: lw, .. } => push(Rect::new(*x, *y, *lw, 1)),
124 RenderOp::LabelStyled { x, y, w: lw, .. } => push(Rect::new(*x, *y, *lw, 1)),
125 RenderOp::TextBlock {
126 x, y, w: rw, h: rh, ..
127 } => push(Rect::new(*x, *y, *rw, *rh)),
128 RenderOp::TextBlockStyled {
129 x, y, w: rw, h: rh, ..
130 } => push(Rect::new(*x, *y, *rw, *rh)),
131 RenderOp::Blit {
132 x, y, w: bw, h: bh, ..
133 } => push(Rect::new(*x, *y, *bw, *bh)),
134 RenderOp::Box {
135 x, y, w: bw, h: bh, ..
136 } => push(Rect::new(*x, *y, *bw, *bh)),
137 }
138 }
139
140 #[cfg(feature = "debug-validate")]
141 #[inline]
142 fn debug_validate_after(&self, op: &RenderOp) {
143 if let Err(e) = self.grid.validate_invariants(&self.reg) {
144 panic!(
147 "termgrid-core invariant violation after op_ptr={:p}: {:?}",
148 op, e
149 );
150 }
151 }
152
153 #[cfg(not(feature = "debug-validate"))]
154 #[inline]
155 fn debug_validate_after(&self, _op: &RenderOp) {
156 }
158
159 pub fn apply_op(&mut self, op: &RenderOp) {
160 match op {
161 RenderOp::Clear => self.grid.clear(),
162
163 RenderOp::ClearLine { y } => self.clear_line(*y),
164
165 RenderOp::ClearEol { x, y } => self.clear_eol(*x, *y),
166
167 RenderOp::ClearBol { x, y } => self.clear_bol(*x, *y),
168
169 RenderOp::ClearEos { x, y } => self.clear_eos(*x, *y),
170
171 RenderOp::ClearRect { x, y, w, h } => self.clear_rect(*x, *y, *w, *h),
172
173 RenderOp::Put { x, y, text, style } => {
174 let s: Style = *style;
175 self.grid.put_text(*x, *y, text, s, &self.reg);
176 }
177
178 RenderOp::PutGlyph { x, y, glyph, style } => {
179 let s: Style = *style;
180 self.grid.put_text(*x, *y, glyph, s, &self.reg);
181 }
182
183 RenderOp::Label {
184 x,
185 y,
186 w,
187 text,
188 style,
189 truncate,
190 } => {
191 self.label(*x, *y, *w, text, *style, *truncate);
192 }
193
194 RenderOp::LabelStyled {
195 x,
196 y,
197 w,
198 spans,
199 truncate,
200 } => {
201 self.put_styled(*x, *y, *w, spans, *truncate);
202 }
203
204 RenderOp::TextBlock {
205 x,
206 y,
207 w,
208 h,
209 text,
210 style,
211 wrap,
212 } => {
213 let spans = [Span::new(text.as_str(), *style)];
214 self.put_wrapped_styled(*x, *y, *w, &spans, wrap, Some(*h));
215 }
216
217 RenderOp::TextBlockStyled {
218 x,
219 y,
220 w,
221 h,
222 spans,
223 wrap,
224 } => {
225 self.put_wrapped_styled(*x, *y, *w, spans, wrap, Some(*h));
226 }
227
228 RenderOp::Blit { x, y, w, h, cells } => {
229 self.blit(*x, *y, *w, *h, cells);
230 }
231
232 RenderOp::FillRect {
233 x,
234 y,
235 w,
236 h,
237 glyph,
238 style,
239 } => {
240 self.fill_rect(*x, *y, *w, *h, glyph, *style);
241 }
242
243 RenderOp::Box {
244 x,
245 y,
246 w,
247 h,
248 style,
249 charset,
250 } => {
251 self.draw_box(*x, *y, *w, *h, *style, *charset);
252 }
253 }
254 self.debug_validate_after(op);
255 }
256
257 fn clear_line(&mut self, y: u16) {
258 if y >= self.grid.height {
259 return;
260 }
261 let line = " ".repeat(self.grid.width as usize);
262 self.grid.put_text(0, y, &line, Style::plain(), &self.reg);
263 }
264
265 fn clear_eol(&mut self, x: u16, y: u16) {
266 if y >= self.grid.height || x >= self.grid.width {
267 return;
268 }
269
270 if x > 0 && matches!(self.grid.get(x, y), Some(Cell::Continuation)) {
272 self.grid.put_text(x - 1, y, " ", Style::plain(), &self.reg);
273 }
274
275 let count = (self.grid.width - x) as usize;
276 let line = " ".repeat(count);
277 self.grid.put_text(x, y, &line, Style::plain(), &self.reg);
278 }
279
280 fn clear_bol(&mut self, x: u16, y: u16) {
281 if y >= self.grid.height || x >= self.grid.width {
282 return;
283 }
284 let count = (x + 1) as usize;
285 let line = " ".repeat(count);
286 self.grid.put_text(0, y, &line, Style::plain(), &self.reg);
287 }
288
289 fn clear_eos(&mut self, x: u16, y: u16) {
290 if y >= self.grid.height || x >= self.grid.width {
291 return;
292 }
293 self.clear_eol(x, y);
294 for yy in (y + 1)..self.grid.height {
295 self.clear_line(yy);
296 }
297 }
298
299 fn clear_rect(&mut self, x: u16, y: u16, w: u16, h: u16) {
300 self.fill_rect(x, y, w, h, " ", Style::plain());
301 }
302
303 fn label(&mut self, x: u16, y: u16, w: u16, text: &str, style: Style, truncate: TruncateMode) {
304 if w == 0 {
305 return;
306 }
307 if x >= self.grid.width || y >= self.grid.height {
308 return;
309 }
310 let max_w = w.min(self.grid.width - x);
311 if max_w == 0 {
312 return;
313 }
314
315 let line = text.split(['\n', '\r']).next().unwrap_or("");
317
318 let rendered = match truncate {
319 TruncateMode::Clip => clip_to_cells_text(&self.reg, line, max_w as usize).0,
320 TruncateMode::Ellipsis => {
321 ellipsis_to_cells_text(&self.reg, line, max_w as usize, DEFAULT_ELLIPSIS)
322 }
323 };
324
325 let spans = [Span::new(rendered, style)];
326 self.put_spans_line(x, y, max_w, &spans);
327 }
328
329 fn put_wrapped_styled(
333 &mut self,
334 x: u16,
335 y: u16,
336 w: u16,
337 spans: &[Span],
338 wrap_opts: &WrapOpts,
339 max_lines: Option<u16>,
340 ) {
341 if w == 0 {
342 return;
343 }
344 if x >= self.grid.width || y >= self.grid.height {
345 return;
346 }
347 let max_w = w.min(self.grid.width - x);
348 if max_w == 0 {
349 return;
350 }
351
352 let lines = wrap_spans_wordwise(&self.reg, spans, max_w as usize, wrap_opts);
353 let limit = max_lines.map(|v| v as usize);
354
355 let mut cy = y;
356 for (rendered, line_spans) in lines.into_iter().enumerate() {
357 if cy >= self.grid.height {
358 break;
359 }
360 if let Some(lim) = limit {
361 if rendered >= lim {
362 break;
363 }
364 }
365 self.put_spans_line(x, cy, max_w, &line_spans);
366 cy = cy.saturating_add(1);
367 }
368 }
369
370 fn put_styled(&mut self, x: u16, y: u16, w: u16, spans: &[Span], truncate: TruncateMode) {
371 if w == 0 {
372 return;
373 }
374 if x >= self.grid.width || y >= self.grid.height {
375 return;
376 }
377 let max_w = w.min(self.grid.width - x);
378 if max_w == 0 {
379 return;
380 }
381
382 let spans = normalize_spans(spans);
383
384 let rendered_spans = match truncate {
385 TruncateMode::Clip => {
386 let (s, _clipped) = clip_to_cells_spans(&self.reg, &spans, max_w as usize);
387 s
388 }
389 TruncateMode::Ellipsis => {
390 let ell = Span::new(DEFAULT_ELLIPSIS, Style::plain());
394 ellipsis_to_cells_spans(&self.reg, &spans, max_w as usize, &ell)
395 }
396 };
397
398 self.put_spans_line(x, y, max_w, &rendered_spans);
399 }
400
401 fn put_spans_line(&mut self, x: u16, y: u16, max_w: u16, spans: &[Span]) {
402 if max_w == 0 {
403 return;
404 }
405 let limit_x = x.saturating_add(max_w);
406
407 let mut cx = x;
408 for s in spans {
409 for g in s.text.graphemes(true) {
410 if cx >= limit_x {
412 return;
413 }
414 if cx >= self.grid.width {
415 return;
416 }
417 let gw = self.reg.width(g);
418 if gw == 0 {
419 continue;
420 }
421 if gw == 2 {
423 if cx + 1 >= self.grid.width {
424 return;
425 }
426 if cx + 1 >= limit_x {
427 return;
428 }
429 }
430 self.grid.put_text(cx, y, g, s.style, &self.reg);
431 cx = cx.saturating_add(gw as u16);
432 }
433 }
434 }
435
436 fn blit(&mut self, x: u16, y: u16, w: u16, h: u16, cells: &[Option<BlitCell>]) {
437 if w == 0 || h == 0 {
438 return;
439 }
440 if x >= self.grid.width || y >= self.grid.height {
441 return;
442 }
443
444 let max_w = w.min(self.grid.width.saturating_sub(x));
445 let max_h = h.min(self.grid.height.saturating_sub(y));
446 if max_w == 0 || max_h == 0 {
447 return;
448 }
449
450 let src_w = w as usize;
451
452 for sy in 0..max_h {
453 let ty = y.saturating_add(sy);
454 let mut sx: u16 = 0;
455
456 while sx < max_w {
457 let idx = sy as usize * src_w + sx as usize;
458 if idx >= cells.len() {
459 break;
460 }
461
462 let tx = x.saturating_add(sx);
463 if tx >= self.grid.width {
464 break;
465 }
466
467 if let Some(cell) = &cells[idx] {
468 let style = cell.style;
469 let glyph = cell.glyph.as_str();
470 self.grid.put_text(tx, ty, glyph, style, &self.reg);
471
472 let gw = self.reg.width(glyph) as u16;
473 if gw == 2 {
474 sx = sx.saturating_add(2);
476 continue;
477 }
478 }
479 sx = sx.saturating_add(1);
480 }
481 }
482 }
483
484 fn fill_rect(&mut self, x: u16, y: u16, w: u16, h: u16, glyph: &str, style: Style) {
485 if w == 0 || h == 0 {
486 return;
487 }
488 if x >= self.grid.width || y >= self.grid.height {
489 return;
490 }
491
492 let gw = self.reg.width(glyph) as u16;
493 let gw = gw.max(1);
494 if gw == 2 && x + 1 >= self.grid.width {
496 return;
497 }
498
499 let max_w = self.grid.width.saturating_sub(x);
500 let max_h = self.grid.height.saturating_sub(y);
501 let w = w.min(max_w);
502 let h = h.min(max_h);
503 if w == 0 || h == 0 {
504 return;
505 }
506
507 for dy in 0..h {
508 let yy = y.saturating_add(dy);
509 let mut cx = x;
510 let mut remaining = w;
511
512 while remaining > 0 && cx < self.grid.width {
513 if gw == 2 {
514 if remaining < 2 {
515 break;
516 }
517 if cx + 1 >= self.grid.width {
518 break;
519 }
520 self.grid.put_text(cx, yy, glyph, style, &self.reg);
521 cx = cx.saturating_add(2);
522 remaining = remaining.saturating_sub(2);
523 } else {
524 self.grid.put_text(cx, yy, glyph, style, &self.reg);
525 cx = cx.saturating_add(1);
526 remaining = remaining.saturating_sub(1);
527 }
528 }
529 }
530 }
531
532 fn draw_box(&mut self, x: u16, y: u16, w: u16, h: u16, style: Style, charset: BoxCharset) {
533 if w < 2 || h < 2 {
534 return;
535 }
536 if x >= self.grid.width || y >= self.grid.height {
537 return;
538 }
539
540 let x1 = x.saturating_add(w - 1).min(self.grid.width - 1);
541 let y1 = y.saturating_add(h - 1).min(self.grid.height - 1);
542
543 if x1 <= x || y1 <= y {
544 return;
545 }
546
547 let (tl, tr, bl, br, hz, vt) = match charset {
548 BoxCharset::Ascii => ("+", "+", "+", "+", "-", "|"),
549 BoxCharset::UnicodeSingle => ("┌", "┐", "└", "┘", "─", "│"),
550 BoxCharset::UnicodeDouble => ("╔", "╗", "╚", "╝", "═", "║"),
551 };
552
553 self.grid.put_text(x, y, tl, style, &self.reg);
555 self.grid.put_text(x1, y, tr, style, &self.reg);
556 self.grid.put_text(x, y1, bl, style, &self.reg);
557 self.grid.put_text(x1, y1, br, style, &self.reg);
558
559 if x1 > x + 1 {
561 let count = (x1 - x - 1) as usize;
562 let line = hz.repeat(count);
563 self.grid.put_text(x + 1, y, &line, style, &self.reg);
564 self.grid.put_text(x + 1, y1, &line, style, &self.reg);
565 }
566
567 if y1 > y + 1 {
569 for yy in (y + 1)..y1 {
570 self.grid.put_text(x, yy, vt, style, &self.reg);
571 self.grid.put_text(x1, yy, vt, style, &self.reg);
572 }
573 }
574 }
575
576 pub fn to_ansi(&self) -> String {
578 ansi::grid_to_ansi(&self.grid)
579 }
580}