1use super::block_chars;
2use super::{BackgroundInstance, Cell, CellRenderer, PaneViewport, RowCacheEntry, TextInstance};
3use anyhow::Result;
4use par_term_config::SeparatorMark;
5use par_term_fonts::text_shaper::ShapingOptions;
6
7impl CellRenderer {
8 pub fn render(
9 &mut self,
10 _show_scrollbar: bool,
11 pane_background: Option<&par_term_config::PaneBackground>,
12 ) -> Result<wgpu::SurfaceTexture> {
13 let output = self.surface.get_current_texture()?;
14 let view = output
15 .texture
16 .create_view(&wgpu::TextureViewDescriptor::default());
17 self.build_instance_buffers()?;
18
19 let pane_bg_resources = if !self.bg_is_solid_color {
22 if let Some(pane_bg) = pane_background {
23 if let Some(ref path) = pane_bg.image_path {
24 self.pane_bg_cache.get(path.as_str()).map(|entry| {
25 self.create_pane_bg_bind_group(
26 entry,
27 0.0, 0.0, self.config.width as f32,
30 self.config.height as f32,
31 pane_bg.mode,
32 pane_bg.opacity,
33 pane_bg.darken,
34 )
35 })
36 } else {
37 None
38 }
39 } else {
40 None
41 }
42 } else {
43 None
44 };
45
46 let mut encoder = self
47 .device
48 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
49 label: Some("render encoder"),
50 });
51
52 let has_pane_bg = pane_bg_resources.is_some();
58 let (clear_color, use_bg_image_pipeline) = if has_pane_bg {
59 (wgpu::Color::TRANSPARENT, false)
61 } else if self.bg_is_solid_color {
62 log::info!(
65 "[BACKGROUND] Solid color mode: RGB({:.3}, {:.3}, {:.3}) * opacity {:.3}",
66 self.solid_bg_color[0],
67 self.solid_bg_color[1],
68 self.solid_bg_color[2],
69 self.window_opacity
70 );
71 (
72 wgpu::Color {
73 r: self.solid_bg_color[0] as f64 * self.window_opacity as f64,
74 g: self.solid_bg_color[1] as f64 * self.window_opacity as f64,
75 b: self.solid_bg_color[2] as f64 * self.window_opacity as f64,
76 a: self.window_opacity as f64,
77 },
78 false,
79 )
80 } else if self.bg_image_bind_group.is_some() {
81 (wgpu::Color::TRANSPARENT, true)
83 } else {
84 (
86 wgpu::Color {
87 r: self.background_color[0] as f64 * self.window_opacity as f64,
88 g: self.background_color[1] as f64 * self.window_opacity as f64,
89 b: self.background_color[2] as f64 * self.window_opacity as f64,
90 a: self.window_opacity as f64,
91 },
92 false,
93 )
94 };
95
96 {
97 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
98 label: Some("render pass"),
99 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
100 view: &view,
101 resolve_target: None,
102 ops: wgpu::Operations {
103 load: wgpu::LoadOp::Clear(clear_color),
104 store: wgpu::StoreOp::Store,
105 },
106 depth_slice: None,
107 })],
108 depth_stencil_attachment: None,
109 timestamp_writes: None,
110 occlusion_query_set: None,
111 });
112
113 if let Some((ref bind_group, _)) = pane_bg_resources {
115 render_pass.set_pipeline(&self.bg_image_pipeline);
116 render_pass.set_bind_group(0, bind_group, &[]);
117 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
118 render_pass.draw(0..4, 0..1);
119 }
120
121 if use_bg_image_pipeline && let Some(ref bg_bind_group) = self.bg_image_bind_group {
123 render_pass.set_pipeline(&self.bg_image_pipeline);
124 render_pass.set_bind_group(0, bg_bind_group, &[]);
125 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
126 render_pass.draw(0..4, 0..1);
127 }
128
129 render_pass.set_pipeline(&self.bg_pipeline);
130 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
131 render_pass.set_vertex_buffer(1, self.bg_instance_buffer.slice(..));
132 render_pass.draw(0..4, 0..self.max_bg_instances as u32);
133
134 render_pass.set_pipeline(&self.text_pipeline);
135 render_pass.set_bind_group(0, &self.text_bind_group, &[]);
136 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
137 render_pass.set_vertex_buffer(1, self.text_instance_buffer.slice(..));
138 render_pass.draw(0..4, 0..self.max_text_instances as u32);
139 }
140
141 self.queue.submit(std::iter::once(encoder.finish()));
142 Ok(output)
143 }
144
145 pub fn render_to_texture(
155 &mut self,
156 target_view: &wgpu::TextureView,
157 skip_background_image: bool,
158 ) -> Result<wgpu::SurfaceTexture> {
159 let output = self.surface.get_current_texture()?;
160 self.build_instance_buffers()?;
161
162 let render_background_image =
165 !skip_background_image && !self.bg_is_solid_color && self.bg_image_bind_group.is_some();
166 let saved_window_opacity = self.window_opacity;
167
168 if render_background_image {
169 self.window_opacity = 1.0;
172 self.update_bg_image_uniforms();
173 }
174
175 let mut encoder = self
176 .device
177 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
178 label: Some("render to texture encoder"),
179 });
180
181 let clear_color = wgpu::Color::TRANSPARENT;
183
184 {
185 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
186 label: Some("render pass"),
187 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
188 view: target_view,
189 resolve_target: None,
190 ops: wgpu::Operations {
191 load: wgpu::LoadOp::Clear(clear_color),
192 store: wgpu::StoreOp::Store,
193 },
194 depth_slice: None,
195 })],
196 depth_stencil_attachment: None,
197 timestamp_writes: None,
198 occlusion_query_set: None,
199 });
200
201 if render_background_image && let Some(ref bg_bind_group) = self.bg_image_bind_group {
203 log::info!(
204 "[BACKGROUND] render_to_texture: bg_image_pipeline (image, window_opacity={:.3} applied by shader)",
205 saved_window_opacity
206 );
207 render_pass.set_pipeline(&self.bg_image_pipeline);
208 render_pass.set_bind_group(0, bg_bind_group, &[]);
209 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
210 render_pass.draw(0..4, 0..1);
211 }
212
213 render_pass.set_pipeline(&self.bg_pipeline);
214 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
215 render_pass.set_vertex_buffer(1, self.bg_instance_buffer.slice(..));
216 render_pass.draw(0..4, 0..self.max_bg_instances as u32);
217
218 render_pass.set_pipeline(&self.text_pipeline);
219 render_pass.set_bind_group(0, &self.text_bind_group, &[]);
220 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
221 render_pass.set_vertex_buffer(1, self.text_instance_buffer.slice(..));
222 render_pass.draw(0..4, 0..self.max_text_instances as u32);
223 }
224
225 self.queue.submit(std::iter::once(encoder.finish()));
226
227 if render_background_image {
229 self.window_opacity = saved_window_opacity;
230 self.update_bg_image_uniforms();
231 }
232
233 Ok(output)
234 }
235
236 pub fn render_background_only(
248 &self,
249 target_view: &wgpu::TextureView,
250 clear_first: bool,
251 ) -> Result<bool> {
252 let mut encoder = self
253 .device
254 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
255 label: Some("background only encoder"),
256 });
257
258 let (clear_color, use_bg_image_pipeline) = if self.bg_is_solid_color {
260 (
261 wgpu::Color {
262 r: self.solid_bg_color[0] as f64 * self.window_opacity as f64,
263 g: self.solid_bg_color[1] as f64 * self.window_opacity as f64,
264 b: self.solid_bg_color[2] as f64 * self.window_opacity as f64,
265 a: self.window_opacity as f64,
266 },
267 false,
268 )
269 } else if self.bg_image_bind_group.is_some() {
270 (wgpu::Color::TRANSPARENT, true)
271 } else {
272 (
273 wgpu::Color {
274 r: self.background_color[0] as f64 * self.window_opacity as f64,
275 g: self.background_color[1] as f64 * self.window_opacity as f64,
276 b: self.background_color[2] as f64 * self.window_opacity as f64,
277 a: self.window_opacity as f64,
278 },
279 false,
280 )
281 };
282
283 let load_op = if clear_first {
284 wgpu::LoadOp::Clear(clear_color)
285 } else {
286 wgpu::LoadOp::Load
287 };
288
289 {
290 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
291 label: Some("background only render pass"),
292 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
293 view: target_view,
294 resolve_target: None,
295 ops: wgpu::Operations {
296 load: load_op,
297 store: wgpu::StoreOp::Store,
298 },
299 depth_slice: None,
300 })],
301 depth_stencil_attachment: None,
302 timestamp_writes: None,
303 occlusion_query_set: None,
304 });
305
306 if use_bg_image_pipeline && let Some(ref bg_bind_group) = self.bg_image_bind_group {
308 render_pass.set_pipeline(&self.bg_image_pipeline);
309 render_pass.set_bind_group(0, bg_bind_group, &[]);
310 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
311 render_pass.draw(0..4, 0..1);
312 }
313 }
314
315 self.queue.submit(std::iter::once(encoder.finish()));
316 Ok(use_bg_image_pipeline)
317 }
318
319 pub fn render_to_view(&self, target_view: &wgpu::TextureView) -> Result<()> {
322 let mut encoder = self
326 .device
327 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
328 label: Some("screenshot render encoder"),
329 });
330
331 let (clear_color, use_bg_image_pipeline) = if self.bg_is_solid_color {
333 (
334 wgpu::Color {
335 r: self.solid_bg_color[0] as f64 * self.window_opacity as f64,
336 g: self.solid_bg_color[1] as f64 * self.window_opacity as f64,
337 b: self.solid_bg_color[2] as f64 * self.window_opacity as f64,
338 a: self.window_opacity as f64,
339 },
340 false,
341 )
342 } else if self.bg_image_bind_group.is_some() {
343 (wgpu::Color::TRANSPARENT, true)
344 } else {
345 (
346 wgpu::Color {
347 r: self.background_color[0] as f64 * self.window_opacity as f64,
348 g: self.background_color[1] as f64 * self.window_opacity as f64,
349 b: self.background_color[2] as f64 * self.window_opacity as f64,
350 a: self.window_opacity as f64,
351 },
352 false,
353 )
354 };
355
356 {
357 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
358 label: Some("screenshot render pass"),
359 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
360 view: target_view,
361 resolve_target: None,
362 ops: wgpu::Operations {
363 load: wgpu::LoadOp::Clear(clear_color),
364 store: wgpu::StoreOp::Store,
365 },
366 depth_slice: None,
367 })],
368 depth_stencil_attachment: None,
369 timestamp_writes: None,
370 occlusion_query_set: None,
371 });
372
373 if use_bg_image_pipeline && let Some(ref bg_bind_group) = self.bg_image_bind_group {
375 render_pass.set_pipeline(&self.bg_image_pipeline);
376 render_pass.set_bind_group(0, bg_bind_group, &[]);
377 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
378 render_pass.draw(0..4, 0..1);
379 }
380
381 render_pass.set_pipeline(&self.bg_pipeline);
382 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
383 render_pass.set_vertex_buffer(1, self.bg_instance_buffer.slice(..));
384 render_pass.draw(0..4, 0..self.max_bg_instances as u32);
385
386 render_pass.set_pipeline(&self.text_pipeline);
387 render_pass.set_bind_group(0, &self.text_bind_group, &[]);
388 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
389 render_pass.set_vertex_buffer(1, self.text_instance_buffer.slice(..));
390 render_pass.draw(0..4, 0..self.max_text_instances as u32);
391
392 self.scrollbar.render(&mut render_pass);
394 }
395
396 self.queue.submit(std::iter::once(encoder.finish()));
397 Ok(())
398 }
399
400 pub fn render_overlays(
401 &mut self,
402 surface_texture: &wgpu::SurfaceTexture,
403 show_scrollbar: bool,
404 ) -> Result<()> {
405 let view = surface_texture
406 .texture
407 .create_view(&wgpu::TextureViewDescriptor::default());
408 let mut encoder = self
409 .device
410 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
411 label: Some("overlay encoder"),
412 });
413
414 {
415 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
416 label: Some("overlay pass"),
417 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
418 view: &view,
419 resolve_target: None,
420 ops: wgpu::Operations {
421 load: wgpu::LoadOp::Load,
422 store: wgpu::StoreOp::Store,
423 },
424 depth_slice: None,
425 })],
426 depth_stencil_attachment: None,
427 timestamp_writes: None,
428 occlusion_query_set: None,
429 });
430
431 if show_scrollbar {
432 self.scrollbar.render(&mut render_pass);
433 }
434
435 if self.visual_bell_intensity > 0.0 {
436 }
438 }
439
440 self.queue.submit(std::iter::once(encoder.finish()));
441 Ok(())
442 }
443
444 pub(crate) fn build_instance_buffers(&mut self) -> Result<()> {
445 let _shaping_options = ShapingOptions {
446 enable_ligatures: self.enable_ligatures,
447 enable_kerning: self.enable_kerning,
448 ..Default::default()
449 };
450
451 for row in 0..self.rows {
452 if self.dirty_rows[row] || self.row_cache[row].is_none() {
453 let start = row * self.cols;
454 let end = (row + 1) * self.cols;
455 let row_cells = &self.cells[start..end];
456
457 let mut row_bg = Vec::with_capacity(self.cols);
458 let mut row_text = Vec::with_capacity(self.cols);
459
460 let mut col = 0;
463 while col < row_cells.len() {
464 let cell = &row_cells[col];
465 let is_default_bg =
466 (cell.bg_color[0] as f32 / 255.0 - self.background_color[0]).abs() < 0.001
467 && (cell.bg_color[1] as f32 / 255.0 - self.background_color[1]).abs()
468 < 0.001
469 && (cell.bg_color[2] as f32 / 255.0 - self.background_color[2]).abs()
470 < 0.001;
471
472 let cursor_visible = self.cursor_opacity > 0.0
474 && !self.cursor_hidden_for_shader
475 && self.cursor_pos.1 == row
476 && self.cursor_pos.0 == col;
477
478 let has_cursor = if cursor_visible && !self.is_focused {
480 match self.unfocused_cursor_style {
481 par_term_config::UnfocusedCursorStyle::Hidden => false,
482 par_term_config::UnfocusedCursorStyle::Hollow
483 | par_term_config::UnfocusedCursorStyle::Same => true,
484 }
485 } else {
486 cursor_visible
487 };
488
489 if is_default_bg && !has_cursor {
490 col += 1;
491 continue;
492 }
493
494 let bg_alpha =
496 if self.transparency_affects_only_default_background && !is_default_bg {
497 1.0
498 } else {
499 self.window_opacity
500 };
501 let mut bg_color = [
502 cell.bg_color[0] as f32 / 255.0,
503 cell.bg_color[1] as f32 / 255.0,
504 cell.bg_color[2] as f32 / 255.0,
505 bg_alpha,
506 ];
507
508 if has_cursor && self.cursor_opacity > 0.0 {
510 use par_term_emu_core_rust::cursor::CursorStyle;
511
512 let render_hollow = !self.is_focused
514 && self.unfocused_cursor_style
515 == par_term_config::UnfocusedCursorStyle::Hollow;
516
517 match self.cursor_style {
518 CursorStyle::SteadyBlock | CursorStyle::BlinkingBlock => {
519 if render_hollow {
520 } else {
523 for (bg, &cursor) in
525 bg_color.iter_mut().take(3).zip(&self.cursor_color)
526 {
527 *bg = *bg * (1.0 - self.cursor_opacity)
528 + cursor * self.cursor_opacity;
529 }
530 bg_color[3] = bg_color[3].max(self.cursor_opacity);
531 }
532 }
533 _ => {}
534 }
535 let x0 = self.window_padding
537 + self.content_offset_x
538 + col as f32 * self.cell_width;
539 let x1 = self.window_padding
540 + self.content_offset_x
541 + (col + 1) as f32 * self.cell_width;
542 let y0 = self.window_padding
543 + self.content_offset_y
544 + row as f32 * self.cell_height;
545 let y1 = y0 + self.cell_height;
546 row_bg.push(BackgroundInstance {
547 position: [
548 x0 / self.config.width as f32 * 2.0 - 1.0,
549 1.0 - (y0 / self.config.height as f32 * 2.0),
550 ],
551 size: [
552 (x1 - x0) / self.config.width as f32 * 2.0,
553 (y1 - y0) / self.config.height as f32 * 2.0,
554 ],
555 color: bg_color,
556 });
557 col += 1;
558 continue;
559 }
560
561 let start_col = col;
563 let run_color = cell.bg_color;
564 col += 1;
565 while col < row_cells.len() {
566 let next_cell = &row_cells[col];
567 let next_has_cursor = self.cursor_opacity > 0.0
568 && !self.cursor_hidden_for_shader
569 && self.cursor_pos.1 == row
570 && self.cursor_pos.0 == col;
571 if next_cell.bg_color != run_color || next_has_cursor {
573 break;
574 }
575 col += 1;
576 }
577 let run_length = col - start_col;
578
579 let x0 = self.window_padding
581 + self.content_offset_x
582 + start_col as f32 * self.cell_width;
583 let x1 = self.window_padding
584 + self.content_offset_x
585 + (start_col + run_length) as f32 * self.cell_width;
586 let y0 =
587 self.window_padding + self.content_offset_y + row as f32 * self.cell_height;
588 let y1 = y0 + self.cell_height;
589
590 row_bg.push(BackgroundInstance {
591 position: [
592 x0 / self.config.width as f32 * 2.0 - 1.0,
593 1.0 - (y0 / self.config.height as f32 * 2.0),
594 ],
595 size: [
596 (x1 - x0) / self.config.width as f32 * 2.0,
597 (y1 - y0) / self.config.height as f32 * 2.0,
598 ],
599 color: bg_color,
600 });
601 }
602
603 while row_bg.len() < self.cols {
606 row_bg.push(BackgroundInstance {
607 position: [0.0, 0.0],
608 size: [0.0, 0.0],
609 color: [0.0, 0.0, 0.0, 0.0],
610 });
611 }
612
613 let mut x_offset = 0.0;
615 #[allow(clippy::type_complexity)]
616 let cell_data: Vec<(
617 String,
618 bool,
619 bool,
620 [u8; 4],
621 [u8; 4],
622 bool,
623 bool,
624 )> = row_cells
625 .iter()
626 .map(|c| {
627 (
628 c.grapheme.clone(),
629 c.bold,
630 c.italic,
631 c.fg_color,
632 c.bg_color,
633 c.wide_char_spacer,
634 c.wide_char,
635 )
636 })
637 .collect();
638
639 let natural_line_height = self.font_ascent + self.font_descent + self.font_leading;
641 let vertical_padding = (self.cell_height - natural_line_height).max(0.0) / 2.0;
642 let baseline_y_unrounded = self.window_padding
643 + self.content_offset_y
644 + (row as f32 * self.cell_height)
645 + vertical_padding
646 + self.font_ascent;
647
648 let cursor_is_block_on_this_row = {
651 use par_term_emu_core_rust::cursor::CursorStyle;
652 self.cursor_pos.1 == row
653 && self.cursor_opacity > 0.0
654 && !self.cursor_hidden_for_shader
655 && matches!(
656 self.cursor_style,
657 CursorStyle::SteadyBlock | CursorStyle::BlinkingBlock
658 )
659 && (self.is_focused
660 || self.unfocused_cursor_style
661 == par_term_config::UnfocusedCursorStyle::Same)
662 };
663
664 let mut current_col = 0usize;
665 for (grapheme, bold, italic, fg_color, bg_color, is_spacer, is_wide) in cell_data {
666 if is_spacer || grapheme == " " {
667 x_offset += self.cell_width;
668 current_col += 1;
669 continue;
670 }
671
672 let text_alpha = if self.keep_text_opaque {
675 1.0
676 } else {
677 self.window_opacity
678 };
679
680 let render_fg_color: [f32; 4] =
683 if cursor_is_block_on_this_row && current_col == self.cursor_pos.0 {
684 if let Some(cursor_text) = self.cursor_text_color {
685 [cursor_text[0], cursor_text[1], cursor_text[2], text_alpha]
686 } else {
687 let cursor_brightness = (self.cursor_color[0]
690 + self.cursor_color[1]
691 + self.cursor_color[2])
692 / 3.0;
693 if cursor_brightness > 0.5 {
694 [0.0, 0.0, 0.0, text_alpha] } else {
696 [1.0, 1.0, 1.0, text_alpha] }
698 }
699 } else {
700 let effective_bg = if bg_color[3] > 0 {
703 [
705 bg_color[0] as f32 / 255.0,
706 bg_color[1] as f32 / 255.0,
707 bg_color[2] as f32 / 255.0,
708 1.0,
709 ]
710 } else {
711 [
713 self.background_color[0],
714 self.background_color[1],
715 self.background_color[2],
716 1.0,
717 ]
718 };
719
720 let base_fg = [
721 fg_color[0] as f32 / 255.0,
722 fg_color[1] as f32 / 255.0,
723 fg_color[2] as f32 / 255.0,
724 text_alpha,
725 ];
726
727 self.ensure_minimum_contrast(base_fg, effective_bg)
729 };
730
731 let chars: Vec<char> = grapheme.chars().collect();
732 #[allow(clippy::collapsible_if)]
733 if let Some(ch) = chars.first() {
734 let char_type = block_chars::classify_char(*ch);
737
738 if chars.len() == 1 && block_chars::should_render_geometrically(char_type) {
741 let char_w = if is_wide {
742 self.cell_width * 2.0
743 } else {
744 self.cell_width
745 };
746 let x0 =
747 (self.window_padding + self.content_offset_x + x_offset).round();
748 let y0 = (self.window_padding
749 + self.content_offset_y
750 + row as f32 * self.cell_height)
751 .round();
752
753 let aspect_ratio = self.cell_height / char_w;
756 if let Some(box_geo) =
757 block_chars::get_box_drawing_geometry(*ch, aspect_ratio)
758 {
759 for segment in &box_geo.segments {
760 let rect =
761 segment.to_pixel_rect(x0, y0, char_w, self.cell_height);
762
763 let extension = 1.0;
765 let ext_x = if segment.x <= 0.01 { extension } else { 0.0 };
766 let ext_y = if segment.y <= 0.01 { extension } else { 0.0 };
767 let ext_w = if segment.x + segment.width >= 0.99 {
768 extension
769 } else {
770 0.0
771 };
772 let ext_h = if segment.y + segment.height >= 0.99 {
773 extension
774 } else {
775 0.0
776 };
777
778 let final_x = rect.x - ext_x;
779 let final_y = rect.y - ext_y;
780 let final_w = rect.width + ext_x + ext_w;
781 let final_h = rect.height + ext_y + ext_h;
782
783 row_text.push(TextInstance {
784 position: [
785 final_x / self.config.width as f32 * 2.0 - 1.0,
786 1.0 - (final_y / self.config.height as f32 * 2.0),
787 ],
788 size: [
789 final_w / self.config.width as f32 * 2.0,
790 final_h / self.config.height as f32 * 2.0,
791 ],
792 tex_offset: [
793 self.solid_pixel_offset.0 as f32 / 2048.0,
794 self.solid_pixel_offset.1 as f32 / 2048.0,
795 ],
796 tex_size: [1.0 / 2048.0, 1.0 / 2048.0],
797 color: render_fg_color,
798 is_colored: 0,
799 });
800 }
801 x_offset += self.cell_width;
802 current_col += 1;
803 continue;
804 }
805
806 if let Some(geo_block) = block_chars::get_geometric_block(*ch) {
808 let rect =
809 geo_block.to_pixel_rect(x0, y0, char_w, self.cell_height);
810
811 let extension = 1.0;
813 let ext_x = if geo_block.x == 0.0 { extension } else { 0.0 };
814 let ext_y = if geo_block.y == 0.0 { extension } else { 0.0 };
815 let ext_w = if geo_block.x + geo_block.width >= 1.0 {
816 extension
817 } else {
818 0.0
819 };
820 let ext_h = if geo_block.y + geo_block.height >= 1.0 {
821 extension
822 } else {
823 0.0
824 };
825
826 let final_x = rect.x - ext_x;
827 let final_y = rect.y - ext_y;
828 let final_w = rect.width + ext_x + ext_w;
829 let final_h = rect.height + ext_y + ext_h;
830
831 row_text.push(TextInstance {
834 position: [
835 final_x / self.config.width as f32 * 2.0 - 1.0,
836 1.0 - (final_y / self.config.height as f32 * 2.0),
837 ],
838 size: [
839 final_w / self.config.width as f32 * 2.0,
840 final_h / self.config.height as f32 * 2.0,
841 ],
842 tex_offset: [
844 self.solid_pixel_offset.0 as f32 / 2048.0,
845 self.solid_pixel_offset.1 as f32 / 2048.0,
846 ],
847 tex_size: [1.0 / 2048.0, 1.0 / 2048.0],
848 color: render_fg_color,
849 is_colored: 0,
850 });
851
852 x_offset += self.cell_width;
853 current_col += 1;
854 continue;
855 }
856 }
857
858 let glyph_result = if chars.len() > 1 {
861 self.font_manager
862 .find_grapheme_glyph(&grapheme, bold, italic)
863 } else {
864 self.font_manager.find_glyph(*ch, bold, italic)
865 };
866
867 if let Some((font_idx, glyph_id)) = glyph_result {
868 let cache_key = ((font_idx as u64) << 32) | (glyph_id as u64);
869 let force_monochrome =
872 chars.len() == 1 && super::atlas::should_render_as_symbol(*ch);
873 let info = if self.glyph_cache.contains_key(&cache_key) {
874 self.lru_remove(cache_key);
876 self.lru_push_front(cache_key);
877 self.glyph_cache.get(&cache_key).unwrap().clone()
878 } else if let Some(raster) =
879 self.rasterize_glyph(font_idx, glyph_id, force_monochrome)
880 {
881 let info = self.upload_glyph(cache_key, &raster);
882 self.glyph_cache.insert(cache_key, info.clone());
883 self.lru_push_front(cache_key);
884 info
885 } else {
886 x_offset += self.cell_width;
887 continue;
888 };
889
890 let char_w = if is_wide {
891 self.cell_width * 2.0
892 } else {
893 self.cell_width
894 };
895 let x0 =
896 (self.window_padding + self.content_offset_x + x_offset).round();
897 let x1 =
898 (self.window_padding + self.content_offset_x + x_offset + char_w)
899 .round();
900 let y0 = (self.window_padding
901 + self.content_offset_y
902 + row as f32 * self.cell_height)
903 .round();
904 let y1 = (self.window_padding
905 + self.content_offset_y
906 + (row + 1) as f32 * self.cell_height)
907 .round();
908
909 let cell_w = x1 - x0;
910 let cell_h = y1 - y0;
911
912 let scale_x = cell_w / char_w;
913 let scale_y = cell_h / self.cell_height;
914
915 let baseline_offset = baseline_y_unrounded
922 - (self.window_padding
923 + self.content_offset_y
924 + row as f32 * self.cell_height);
925 let glyph_left = x0 + (info.bearing_x * scale_x).round();
926 let baseline_in_cell = (baseline_offset * scale_y).round();
927 let glyph_top = y0 + baseline_in_cell - info.bearing_y;
928
929 let render_w = info.width as f32 * scale_x;
930 let render_h = info.height as f32 * scale_y;
931
932 let (final_left, final_top, final_w, final_h) = if chars.len() == 1
936 && block_chars::should_snap_to_boundaries(char_type)
937 {
938 block_chars::snap_glyph_to_cell(
940 glyph_left, glyph_top, render_w, render_h, x0, y0, x1, y1, 3.0,
941 0.5,
942 )
943 } else {
944 (glyph_left, glyph_top, render_w, render_h)
945 };
946
947 row_text.push(TextInstance {
948 position: [
949 final_left / self.config.width as f32 * 2.0 - 1.0,
950 1.0 - (final_top / self.config.height as f32 * 2.0),
951 ],
952 size: [
953 final_w / self.config.width as f32 * 2.0,
954 final_h / self.config.height as f32 * 2.0,
955 ],
956 tex_offset: [info.x as f32 / 2048.0, info.y as f32 / 2048.0],
957 tex_size: [info.width as f32 / 2048.0, info.height as f32 / 2048.0],
958 color: render_fg_color,
959 is_colored: if info.is_colored { 1 } else { 0 },
960 });
961 }
962 }
963 x_offset += self.cell_width;
964 current_col += 1;
965 }
966
967 {
969 let underline_thickness = (self.cell_height * 0.07).max(1.0).round();
970 let tex_offset = [
971 self.solid_pixel_offset.0 as f32 / 2048.0,
972 self.solid_pixel_offset.1 as f32 / 2048.0,
973 ];
974 let tex_size = [1.0 / 2048.0, 1.0 / 2048.0];
975 let y0 = self.window_padding
976 + self.content_offset_y
977 + (row + 1) as f32 * self.cell_height
978 - underline_thickness;
979 let ndc_y = 1.0 - (y0 / self.config.height as f32 * 2.0);
980 let ndc_h = underline_thickness / self.config.height as f32 * 2.0;
981 let is_stipple =
982 self.link_underline_style == par_term_config::LinkUnderlineStyle::Stipple;
983 let stipple_on = 2.0_f32;
985 let stipple_off = 2.0_f32;
986 let stipple_period = stipple_on + stipple_off;
987
988 for col_idx in 0..self.cols {
989 let cell = &self.cells[start + col_idx];
990 if !cell.underline || row_text.len() >= self.cols * 2 {
991 continue;
992 }
993 let text_alpha = if self.keep_text_opaque {
994 1.0
995 } else {
996 self.window_opacity
997 };
998 let fg = [
999 cell.fg_color[0] as f32 / 255.0,
1000 cell.fg_color[1] as f32 / 255.0,
1001 cell.fg_color[2] as f32 / 255.0,
1002 text_alpha,
1003 ];
1004 let cell_x0 = self.window_padding
1005 + self.content_offset_x
1006 + col_idx as f32 * self.cell_width;
1007
1008 if is_stipple {
1009 let mut px = 0.0;
1011 while px < self.cell_width && row_text.len() < self.cols * 2 {
1012 let seg_w = stipple_on.min(self.cell_width - px);
1013 let x = cell_x0 + px;
1014 row_text.push(TextInstance {
1015 position: [x / self.config.width as f32 * 2.0 - 1.0, ndc_y],
1016 size: [seg_w / self.config.width as f32 * 2.0, ndc_h],
1017 tex_offset,
1018 tex_size,
1019 color: fg,
1020 is_colored: 0,
1021 });
1022 px += stipple_period;
1023 }
1024 } else {
1025 row_text.push(TextInstance {
1026 position: [cell_x0 / self.config.width as f32 * 2.0 - 1.0, ndc_y],
1027 size: [self.cell_width / self.config.width as f32 * 2.0, ndc_h],
1028 tex_offset,
1029 tex_size,
1030 color: fg,
1031 is_colored: 0,
1032 });
1033 }
1034 }
1035 }
1036
1037 let bg_start = row * self.cols;
1039 self.bg_instances[bg_start..bg_start + self.cols].copy_from_slice(&row_bg);
1040
1041 let text_start = row * self.cols * 2;
1042 for i in 0..(self.cols * 2) {
1044 self.text_instances[text_start + i].size = [0.0, 0.0];
1045 }
1046 let text_count = row_text.len().min(self.cols * 2);
1048 self.text_instances[text_start..text_start + text_count]
1049 .copy_from_slice(&row_text[..text_count]);
1050
1051 self.queue.write_buffer(
1053 &self.bg_instance_buffer,
1054 (bg_start * std::mem::size_of::<BackgroundInstance>()) as u64,
1055 bytemuck::cast_slice(&row_bg),
1056 );
1057 self.queue.write_buffer(
1058 &self.text_instance_buffer,
1059 (text_start * std::mem::size_of::<TextInstance>()) as u64,
1060 bytemuck::cast_slice(
1061 &self.text_instances[text_start..text_start + self.cols * 2],
1062 ),
1063 );
1064
1065 self.row_cache[row] = Some(RowCacheEntry {});
1066 self.dirty_rows[row] = false;
1067 }
1068 }
1069
1070 let base_overlay_index = self.cols * self.rows;
1073 let mut overlay_instances = vec![
1074 BackgroundInstance {
1075 position: [0.0, 0.0],
1076 size: [0.0, 0.0],
1077 color: [0.0, 0.0, 0.0, 0.0],
1078 };
1079 10
1080 ];
1081
1082 let cursor_visible = self.cursor_opacity > 0.0
1084 && !self.cursor_hidden_for_shader
1085 && (self.is_focused
1086 || self.unfocused_cursor_style != par_term_config::UnfocusedCursorStyle::Hidden);
1087
1088 let cursor_col = self.cursor_pos.0;
1090 let cursor_row = self.cursor_pos.1;
1091 let cursor_x0 =
1092 self.window_padding + self.content_offset_x + cursor_col as f32 * self.cell_width;
1093 let cursor_x1 = cursor_x0 + self.cell_width;
1094 let cursor_y0 =
1095 self.window_padding + self.content_offset_y + cursor_row as f32 * self.cell_height;
1096 let cursor_y1 = cursor_y0 + self.cell_height;
1097
1098 overlay_instances[0] = self.cursor_overlay.unwrap_or(BackgroundInstance {
1100 position: [0.0, 0.0],
1101 size: [0.0, 0.0],
1102 color: [0.0, 0.0, 0.0, 0.0],
1103 });
1104
1105 if cursor_visible && self.cursor_guide_enabled {
1107 let guide_x0 = self.window_padding + self.content_offset_x;
1108 let guide_x1 =
1109 self.config.width as f32 - self.window_padding - self.content_inset_right;
1110 overlay_instances[1] = BackgroundInstance {
1111 position: [
1112 guide_x0 / self.config.width as f32 * 2.0 - 1.0,
1113 1.0 - (cursor_y0 / self.config.height as f32 * 2.0),
1114 ],
1115 size: [
1116 (guide_x1 - guide_x0) / self.config.width as f32 * 2.0,
1117 (cursor_y1 - cursor_y0) / self.config.height as f32 * 2.0,
1118 ],
1119 color: self.cursor_guide_color,
1120 };
1121 }
1122
1123 if cursor_visible && self.cursor_shadow_enabled {
1125 let shadow_x0 = cursor_x0 + self.cursor_shadow_offset[0];
1126 let shadow_y0 = cursor_y0 + self.cursor_shadow_offset[1];
1127 overlay_instances[2] = BackgroundInstance {
1128 position: [
1129 shadow_x0 / self.config.width as f32 * 2.0 - 1.0,
1130 1.0 - (shadow_y0 / self.config.height as f32 * 2.0),
1131 ],
1132 size: [
1133 self.cell_width / self.config.width as f32 * 2.0,
1134 self.cell_height / self.config.height as f32 * 2.0,
1135 ],
1136 color: self.cursor_shadow_color,
1137 };
1138 }
1139
1140 if cursor_visible && self.cursor_boost > 0.0 {
1142 let glow_expand = 4.0 * self.scale_factor * self.cursor_boost; let glow_x0 = cursor_x0 - glow_expand;
1144 let glow_y0 = cursor_y0 - glow_expand;
1145 let glow_w = self.cell_width + glow_expand * 2.0;
1146 let glow_h = self.cell_height + glow_expand * 2.0;
1147 overlay_instances[3] = BackgroundInstance {
1148 position: [
1149 glow_x0 / self.config.width as f32 * 2.0 - 1.0,
1150 1.0 - (glow_y0 / self.config.height as f32 * 2.0),
1151 ],
1152 size: [
1153 glow_w / self.config.width as f32 * 2.0,
1154 glow_h / self.config.height as f32 * 2.0,
1155 ],
1156 color: [
1157 self.cursor_boost_color[0],
1158 self.cursor_boost_color[1],
1159 self.cursor_boost_color[2],
1160 self.cursor_boost * 0.3 * self.cursor_opacity, ],
1162 };
1163 }
1164
1165 let render_hollow = cursor_visible
1168 && !self.is_focused
1169 && self.unfocused_cursor_style == par_term_config::UnfocusedCursorStyle::Hollow;
1170
1171 if render_hollow {
1172 use par_term_emu_core_rust::cursor::CursorStyle;
1173 let is_block = matches!(
1174 self.cursor_style,
1175 CursorStyle::SteadyBlock | CursorStyle::BlinkingBlock
1176 );
1177
1178 if is_block {
1179 let border_width = 2.0; let color = [
1181 self.cursor_color[0],
1182 self.cursor_color[1],
1183 self.cursor_color[2],
1184 self.cursor_opacity,
1185 ];
1186
1187 overlay_instances[4] = BackgroundInstance {
1189 position: [
1190 cursor_x0 / self.config.width as f32 * 2.0 - 1.0,
1191 1.0 - (cursor_y0 / self.config.height as f32 * 2.0),
1192 ],
1193 size: [
1194 self.cell_width / self.config.width as f32 * 2.0,
1195 border_width / self.config.height as f32 * 2.0,
1196 ],
1197 color,
1198 };
1199
1200 overlay_instances[5] = BackgroundInstance {
1202 position: [
1203 cursor_x0 / self.config.width as f32 * 2.0 - 1.0,
1204 1.0 - ((cursor_y1 - border_width) / self.config.height as f32 * 2.0),
1205 ],
1206 size: [
1207 self.cell_width / self.config.width as f32 * 2.0,
1208 border_width / self.config.height as f32 * 2.0,
1209 ],
1210 color,
1211 };
1212
1213 overlay_instances[6] = BackgroundInstance {
1215 position: [
1216 cursor_x0 / self.config.width as f32 * 2.0 - 1.0,
1217 1.0 - ((cursor_y0 + border_width) / self.config.height as f32 * 2.0),
1218 ],
1219 size: [
1220 border_width / self.config.width as f32 * 2.0,
1221 (self.cell_height - border_width * 2.0) / self.config.height as f32 * 2.0,
1222 ],
1223 color,
1224 };
1225
1226 overlay_instances[7] = BackgroundInstance {
1228 position: [
1229 (cursor_x1 - border_width) / self.config.width as f32 * 2.0 - 1.0,
1230 1.0 - ((cursor_y0 + border_width) / self.config.height as f32 * 2.0),
1231 ],
1232 size: [
1233 border_width / self.config.width as f32 * 2.0,
1234 (self.cell_height - border_width * 2.0) / self.config.height as f32 * 2.0,
1235 ],
1236 color,
1237 };
1238 }
1239 }
1240
1241 for (i, instance) in overlay_instances.iter().enumerate() {
1243 self.bg_instances[base_overlay_index + i] = *instance;
1244 }
1245 self.queue.write_buffer(
1246 &self.bg_instance_buffer,
1247 (base_overlay_index * std::mem::size_of::<BackgroundInstance>()) as u64,
1248 bytemuck::cast_slice(&overlay_instances),
1249 );
1250
1251 let separator_base = self.cols * self.rows + 10;
1253 let mut separator_instances = vec![
1254 BackgroundInstance {
1255 position: [0.0, 0.0],
1256 size: [0.0, 0.0],
1257 color: [0.0, 0.0, 0.0, 0.0],
1258 };
1259 self.rows
1260 ];
1261
1262 if self.command_separator_enabled {
1263 let width_f = self.config.width as f32;
1264 let height_f = self.config.height as f32;
1265 for &(screen_row, exit_code, custom_color) in &self.visible_separator_marks {
1266 if screen_row < self.rows {
1267 let x0 = self.window_padding + self.content_offset_x;
1268 let x1 = width_f - self.window_padding - self.content_inset_right;
1269 let y0 = self.window_padding
1270 + self.content_offset_y
1271 + screen_row as f32 * self.cell_height;
1272 let color = self.separator_color(exit_code, custom_color, 1.0);
1273 separator_instances[screen_row] = BackgroundInstance {
1274 position: [x0 / width_f * 2.0 - 1.0, 1.0 - (y0 / height_f * 2.0)],
1275 size: [
1276 (x1 - x0) / width_f * 2.0,
1277 self.command_separator_thickness / height_f * 2.0,
1278 ],
1279 color,
1280 };
1281 }
1282 }
1283 }
1284
1285 for (i, instance) in separator_instances.iter().enumerate() {
1286 if separator_base + i < self.max_bg_instances {
1287 self.bg_instances[separator_base + i] = *instance;
1288 }
1289 }
1290 let separator_byte_offset = separator_base * std::mem::size_of::<BackgroundInstance>();
1291 let separator_byte_count =
1292 separator_instances.len() * std::mem::size_of::<BackgroundInstance>();
1293 if separator_byte_offset + separator_byte_count
1294 <= self.max_bg_instances * std::mem::size_of::<BackgroundInstance>()
1295 {
1296 self.queue.write_buffer(
1297 &self.bg_instance_buffer,
1298 separator_byte_offset as u64,
1299 bytemuck::cast_slice(&separator_instances),
1300 );
1301 }
1302
1303 Ok(())
1304 }
1305
1306 #[allow(dead_code, clippy::too_many_arguments)]
1324 pub fn render_pane_to_view(
1325 &mut self,
1326 surface_view: &wgpu::TextureView,
1327 viewport: &PaneViewport,
1328 cells: &[Cell],
1329 cols: usize,
1330 rows: usize,
1331 cursor_pos: Option<(usize, usize)>,
1332 cursor_opacity: f32,
1333 show_scrollbar: bool,
1334 clear_first: bool,
1335 skip_background_image: bool,
1336 separator_marks: &[SeparatorMark],
1337 pane_background: Option<&par_term_config::PaneBackground>,
1338 ) -> Result<()> {
1339 self.build_pane_instance_buffers(
1342 viewport,
1343 cells,
1344 cols,
1345 rows,
1346 cursor_pos,
1347 cursor_opacity,
1348 skip_background_image,
1349 separator_marks,
1350 )?;
1351
1352 let pane_bg_resources = if let Some(pane_bg) = pane_background
1356 && let Some(ref path) = pane_bg.image_path
1357 {
1358 self.pane_bg_cache.get(path.as_str()).map(|entry| {
1359 self.create_pane_bg_bind_group(
1360 entry,
1361 viewport.x,
1362 viewport.y,
1363 viewport.width,
1364 viewport.height,
1365 pane_bg.mode,
1366 pane_bg.opacity,
1367 pane_bg.darken,
1368 )
1369 })
1370 } else {
1371 None
1372 };
1373
1374 let mut encoder = self
1375 .device
1376 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1377 label: Some("pane render encoder"),
1378 });
1379
1380 let load_op = if clear_first {
1382 let clear_color = if self.bg_is_solid_color {
1383 wgpu::Color {
1384 r: self.solid_bg_color[0] as f64
1385 * self.window_opacity as f64
1386 * viewport.opacity as f64,
1387 g: self.solid_bg_color[1] as f64
1388 * self.window_opacity as f64
1389 * viewport.opacity as f64,
1390 b: self.solid_bg_color[2] as f64
1391 * self.window_opacity as f64
1392 * viewport.opacity as f64,
1393 a: self.window_opacity as f64 * viewport.opacity as f64,
1394 }
1395 } else {
1396 wgpu::Color {
1397 r: self.background_color[0] as f64
1398 * self.window_opacity as f64
1399 * viewport.opacity as f64,
1400 g: self.background_color[1] as f64
1401 * self.window_opacity as f64
1402 * viewport.opacity as f64,
1403 b: self.background_color[2] as f64
1404 * self.window_opacity as f64
1405 * viewport.opacity as f64,
1406 a: self.window_opacity as f64 * viewport.opacity as f64,
1407 }
1408 };
1409 wgpu::LoadOp::Clear(clear_color)
1410 } else {
1411 wgpu::LoadOp::Load
1412 };
1413
1414 {
1415 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1416 label: Some("pane render pass"),
1417 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1418 view: surface_view,
1419 resolve_target: None,
1420 ops: wgpu::Operations {
1421 load: load_op,
1422 store: wgpu::StoreOp::Store,
1423 },
1424 depth_slice: None,
1425 })],
1426 depth_stencil_attachment: None,
1427 timestamp_writes: None,
1428 occlusion_query_set: None,
1429 });
1430
1431 let (sx, sy, sw, sh) = viewport.to_scissor_rect();
1433 render_pass.set_scissor_rect(sx, sy, sw, sh);
1434
1435 if let Some((ref bind_group, ref _buf)) = pane_bg_resources {
1439 render_pass.set_pipeline(&self.bg_image_pipeline);
1440 render_pass.set_bind_group(0, bind_group, &[]);
1441 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1442 render_pass.draw(0..4, 0..1);
1443 }
1444
1445 render_pass.set_pipeline(&self.bg_pipeline);
1447 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1448 render_pass.set_vertex_buffer(1, self.bg_instance_buffer.slice(..));
1449 render_pass.draw(0..4, 0..self.max_bg_instances as u32);
1450
1451 render_pass.set_pipeline(&self.text_pipeline);
1453 render_pass.set_bind_group(0, &self.text_bind_group, &[]);
1454 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1455 render_pass.set_vertex_buffer(1, self.text_instance_buffer.slice(..));
1456 render_pass.draw(0..4, 0..self.max_text_instances as u32);
1457
1458 if show_scrollbar {
1460 render_pass.set_scissor_rect(0, 0, self.config.width, self.config.height);
1462 self.scrollbar.render(&mut render_pass);
1463 }
1464 }
1465
1466 self.queue.submit(std::iter::once(encoder.finish()));
1467 Ok(())
1468 }
1469
1470 #[allow(clippy::too_many_arguments)]
1479 fn build_pane_instance_buffers(
1480 &mut self,
1481 viewport: &PaneViewport,
1482 cells: &[Cell],
1483 cols: usize,
1484 rows: usize,
1485 cursor_pos: Option<(usize, usize)>,
1486 cursor_opacity: f32,
1487 skip_solid_background: bool,
1488 separator_marks: &[SeparatorMark],
1489 ) -> Result<()> {
1490 let _shaping_options = ShapingOptions {
1491 enable_ligatures: self.enable_ligatures,
1492 enable_kerning: self.enable_kerning,
1493 ..Default::default()
1494 };
1495
1496 for instance in &mut self.bg_instances {
1498 instance.size = [0.0, 0.0];
1499 instance.color = [0.0, 0.0, 0.0, 0.0];
1500 }
1501
1502 let bg_start_index = if !skip_solid_background && !self.bg_instances.is_empty() {
1506 let bg_color = self.background_color;
1507 let opacity = self.window_opacity * viewport.opacity;
1508 self.bg_instances[0] = super::types::BackgroundInstance {
1509 position: [viewport.x, viewport.y],
1510 size: [viewport.width, viewport.height],
1511 color: [
1512 bg_color[0] * opacity,
1513 bg_color[1] * opacity,
1514 bg_color[2] * opacity,
1515 opacity,
1516 ],
1517 };
1518 1 } else {
1520 0 };
1522
1523 for instance in &mut self.text_instances {
1524 instance.size = [0.0, 0.0];
1525 }
1526
1527 let mut bg_index = bg_start_index;
1529 let mut text_index = 0;
1530
1531 let (content_x, content_y) = viewport.content_origin();
1533 let opacity_multiplier = viewport.opacity;
1534
1535 for row in 0..rows {
1536 let row_start = row * cols;
1537 let row_end = (row + 1) * cols;
1538 if row_start >= cells.len() {
1539 break;
1540 }
1541 let row_cells = &cells[row_start..row_end.min(cells.len())];
1542
1543 let mut col = 0;
1545 while col < row_cells.len() {
1546 let cell = &row_cells[col];
1547 let is_default_bg = (cell.bg_color[0] as f32 / 255.0 - self.background_color[0])
1548 .abs()
1549 < 0.001
1550 && (cell.bg_color[1] as f32 / 255.0 - self.background_color[1]).abs() < 0.001
1551 && (cell.bg_color[2] as f32 / 255.0 - self.background_color[2]).abs() < 0.001;
1552
1553 let has_cursor = cursor_pos.is_some_and(|(cx, cy)| cx == col && cy == row)
1555 && cursor_opacity > 0.0
1556 && !self.cursor_hidden_for_shader;
1557
1558 if is_default_bg && !has_cursor {
1559 col += 1;
1560 continue;
1561 }
1562
1563 let bg_alpha =
1565 if self.transparency_affects_only_default_background && !is_default_bg {
1566 1.0
1567 } else {
1568 self.window_opacity
1569 };
1570 let pane_alpha = bg_alpha * opacity_multiplier;
1571 let mut bg_color = [
1572 cell.bg_color[0] as f32 / 255.0,
1573 cell.bg_color[1] as f32 / 255.0,
1574 cell.bg_color[2] as f32 / 255.0,
1575 pane_alpha,
1576 ];
1577
1578 if has_cursor {
1580 use par_term_emu_core_rust::cursor::CursorStyle;
1581 match self.cursor_style {
1582 CursorStyle::SteadyBlock | CursorStyle::BlinkingBlock => {
1583 for (bg, &cursor) in bg_color.iter_mut().take(3).zip(&self.cursor_color)
1584 {
1585 *bg = *bg * (1.0 - cursor_opacity) + cursor * cursor_opacity;
1586 }
1587 bg_color[3] = bg_color[3].max(cursor_opacity * opacity_multiplier);
1588 }
1589 _ => {}
1590 }
1591
1592 let x0 = content_x + col as f32 * self.cell_width;
1594 let y0 = content_y + row as f32 * self.cell_height;
1595 let x1 = x0 + self.cell_width;
1596 let y1 = y0 + self.cell_height;
1597
1598 if bg_index < self.max_bg_instances {
1599 self.bg_instances[bg_index] = BackgroundInstance {
1600 position: [
1601 x0 / self.config.width as f32 * 2.0 - 1.0,
1602 1.0 - (y0 / self.config.height as f32 * 2.0),
1603 ],
1604 size: [
1605 (x1 - x0) / self.config.width as f32 * 2.0,
1606 (y1 - y0) / self.config.height as f32 * 2.0,
1607 ],
1608 color: bg_color,
1609 };
1610 bg_index += 1;
1611 }
1612 col += 1;
1613 continue;
1614 }
1615
1616 let start_col = col;
1618 let run_color = cell.bg_color;
1619 col += 1;
1620 while col < row_cells.len() {
1621 let next_cell = &row_cells[col];
1622 let next_has_cursor = cursor_pos.is_some_and(|(cx, cy)| cx == col && cy == row)
1623 && cursor_opacity > 0.0;
1624 if next_cell.bg_color != run_color || next_has_cursor {
1625 break;
1626 }
1627 col += 1;
1628 }
1629 let run_length = col - start_col;
1630
1631 let x0 = content_x + start_col as f32 * self.cell_width;
1633 let x1 = content_x + (start_col + run_length) as f32 * self.cell_width;
1634 let y0 = content_y + row as f32 * self.cell_height;
1635 let y1 = y0 + self.cell_height;
1636
1637 if bg_index < self.max_bg_instances {
1638 self.bg_instances[bg_index] = BackgroundInstance {
1639 position: [
1640 x0 / self.config.width as f32 * 2.0 - 1.0,
1641 1.0 - (y0 / self.config.height as f32 * 2.0),
1642 ],
1643 size: [
1644 (x1 - x0) / self.config.width as f32 * 2.0,
1645 (y1 - y0) / self.config.height as f32 * 2.0,
1646 ],
1647 color: bg_color,
1648 };
1649 bg_index += 1;
1650 }
1651 }
1652
1653 let natural_line_height = self.font_ascent + self.font_descent + self.font_leading;
1655 let vertical_padding = (self.cell_height - natural_line_height).max(0.0) / 2.0;
1656 let baseline_y =
1657 content_y + (row as f32 * self.cell_height) + vertical_padding + self.font_ascent;
1658
1659 let text_alpha = if self.keep_text_opaque {
1661 opacity_multiplier } else {
1663 self.window_opacity * opacity_multiplier
1664 };
1665
1666 for (col_idx, cell) in row_cells.iter().enumerate() {
1667 if cell.wide_char_spacer || cell.grapheme == " " {
1668 continue;
1669 }
1670
1671 let chars: Vec<char> = cell.grapheme.chars().collect();
1672 if chars.is_empty() {
1673 continue;
1674 }
1675
1676 let ch = chars[0];
1677
1678 let char_type = block_chars::classify_char(ch);
1680 if chars.len() == 1 && block_chars::should_render_geometrically(char_type) {
1681 let char_w = if cell.wide_char {
1682 self.cell_width * 2.0
1683 } else {
1684 self.cell_width
1685 };
1686 let x0 = content_x + col_idx as f32 * self.cell_width;
1687 let y0 = content_y + row as f32 * self.cell_height;
1688
1689 let fg_color = [
1690 cell.fg_color[0] as f32 / 255.0,
1691 cell.fg_color[1] as f32 / 255.0,
1692 cell.fg_color[2] as f32 / 255.0,
1693 text_alpha,
1694 ];
1695
1696 let aspect_ratio = self.cell_height / char_w;
1698 if let Some(box_geo) = block_chars::get_box_drawing_geometry(ch, aspect_ratio) {
1699 for segment in &box_geo.segments {
1700 let rect = segment.to_pixel_rect(x0, y0, char_w, self.cell_height);
1701
1702 let extension = 1.0;
1704 let ext_x = if segment.x <= 0.01 { extension } else { 0.0 };
1705 let ext_y = if segment.y <= 0.01 { extension } else { 0.0 };
1706 let ext_w = if segment.x + segment.width >= 0.99 {
1707 extension
1708 } else {
1709 0.0
1710 };
1711 let ext_h = if segment.y + segment.height >= 0.99 {
1712 extension
1713 } else {
1714 0.0
1715 };
1716
1717 let final_x = rect.x - ext_x;
1718 let final_y = rect.y - ext_y;
1719 let final_w = rect.width + ext_x + ext_w;
1720 let final_h = rect.height + ext_y + ext_h;
1721
1722 if text_index < self.max_text_instances {
1723 self.text_instances[text_index] = TextInstance {
1724 position: [
1725 final_x / self.config.width as f32 * 2.0 - 1.0,
1726 1.0 - (final_y / self.config.height as f32 * 2.0),
1727 ],
1728 size: [
1729 final_w / self.config.width as f32 * 2.0,
1730 final_h / self.config.height as f32 * 2.0,
1731 ],
1732 tex_offset: [
1733 self.solid_pixel_offset.0 as f32 / 2048.0,
1734 self.solid_pixel_offset.1 as f32 / 2048.0,
1735 ],
1736 tex_size: [1.0 / 2048.0, 1.0 / 2048.0],
1737 color: fg_color,
1738 is_colored: 0,
1739 };
1740 text_index += 1;
1741 }
1742 }
1743 continue;
1744 }
1745
1746 if let Some(geo_block) = block_chars::get_geometric_block(ch) {
1748 let rect = geo_block.to_pixel_rect(x0, y0, char_w, self.cell_height);
1749
1750 let extension = 1.0;
1752 let ext_x = if geo_block.x == 0.0 { extension } else { 0.0 };
1753 let ext_y = if geo_block.y == 0.0 { extension } else { 0.0 };
1754 let ext_w = if geo_block.x + geo_block.width >= 1.0 {
1755 extension
1756 } else {
1757 0.0
1758 };
1759 let ext_h = if geo_block.y + geo_block.height >= 1.0 {
1760 extension
1761 } else {
1762 0.0
1763 };
1764
1765 let final_x = rect.x - ext_x;
1766 let final_y = rect.y - ext_y;
1767 let final_w = rect.width + ext_x + ext_w;
1768 let final_h = rect.height + ext_y + ext_h;
1769
1770 if text_index < self.max_text_instances {
1771 self.text_instances[text_index] = TextInstance {
1772 position: [
1773 final_x / self.config.width as f32 * 2.0 - 1.0,
1774 1.0 - (final_y / self.config.height as f32 * 2.0),
1775 ],
1776 size: [
1777 final_w / self.config.width as f32 * 2.0,
1778 final_h / self.config.height as f32 * 2.0,
1779 ],
1780 tex_offset: [
1781 self.solid_pixel_offset.0 as f32 / 2048.0,
1782 self.solid_pixel_offset.1 as f32 / 2048.0,
1783 ],
1784 tex_size: [1.0 / 2048.0, 1.0 / 2048.0],
1785 color: fg_color,
1786 is_colored: 0,
1787 };
1788 text_index += 1;
1789 }
1790 continue;
1791 }
1792 }
1793
1794 let glyph_result = if chars.len() > 1 {
1796 self.font_manager
1797 .find_grapheme_glyph(&cell.grapheme, cell.bold, cell.italic)
1798 } else {
1799 self.font_manager.find_glyph(ch, cell.bold, cell.italic)
1800 };
1801
1802 if let Some((font_idx, glyph_id)) = glyph_result {
1803 let cache_key = ((font_idx as u64) << 32) | (glyph_id as u64);
1804 let force_monochrome =
1807 chars.len() == 1 && super::atlas::should_render_as_symbol(ch);
1808 let info = if self.glyph_cache.contains_key(&cache_key) {
1809 self.lru_remove(cache_key);
1810 self.lru_push_front(cache_key);
1811 self.glyph_cache.get(&cache_key).unwrap().clone()
1812 } else if let Some(raster) =
1813 self.rasterize_glyph(font_idx, glyph_id, force_monochrome)
1814 {
1815 let info = self.upload_glyph(cache_key, &raster);
1816 self.glyph_cache.insert(cache_key, info.clone());
1817 self.lru_push_front(cache_key);
1818 info
1819 } else {
1820 continue;
1821 };
1822
1823 let char_w = if cell.wide_char {
1824 self.cell_width * 2.0
1825 } else {
1826 self.cell_width
1827 };
1828 let x0 = content_x + col_idx as f32 * self.cell_width;
1829 let y0 = content_y + row as f32 * self.cell_height;
1830 let x1 = x0 + char_w;
1831 let y1 = y0 + self.cell_height;
1832
1833 let cell_w = x1 - x0;
1834 let cell_h = y1 - y0;
1835 let scale_x = cell_w / char_w;
1836 let scale_y = cell_h / self.cell_height;
1837
1838 let baseline_offset = baseline_y - (content_y + row as f32 * self.cell_height);
1839 let glyph_left = x0 + (info.bearing_x * scale_x).round();
1840 let baseline_in_cell = (baseline_offset * scale_y).round();
1841 let glyph_top = y0 + baseline_in_cell - info.bearing_y;
1842
1843 let render_w = info.width as f32 * scale_x;
1844 let render_h = info.height as f32 * scale_y;
1845
1846 let (final_left, final_top, final_w, final_h) =
1847 if chars.len() == 1 && block_chars::should_snap_to_boundaries(char_type) {
1848 block_chars::snap_glyph_to_cell(
1849 glyph_left, glyph_top, render_w, render_h, x0, y0, x1, y1, 3.0, 0.5,
1850 )
1851 } else {
1852 (glyph_left, glyph_top, render_w, render_h)
1853 };
1854
1855 let fg_color = [
1856 cell.fg_color[0] as f32 / 255.0,
1857 cell.fg_color[1] as f32 / 255.0,
1858 cell.fg_color[2] as f32 / 255.0,
1859 text_alpha,
1860 ];
1861
1862 if text_index < self.max_text_instances {
1863 self.text_instances[text_index] = TextInstance {
1864 position: [
1865 final_left / self.config.width as f32 * 2.0 - 1.0,
1866 1.0 - (final_top / self.config.height as f32 * 2.0),
1867 ],
1868 size: [
1869 final_w / self.config.width as f32 * 2.0,
1870 final_h / self.config.height as f32 * 2.0,
1871 ],
1872 tex_offset: [info.x as f32 / 2048.0, info.y as f32 / 2048.0],
1873 tex_size: [info.width as f32 / 2048.0, info.height as f32 / 2048.0],
1874 color: fg_color,
1875 is_colored: if info.is_colored { 1 } else { 0 },
1876 };
1877 text_index += 1;
1878 }
1879 }
1880 }
1881 }
1882
1883 if self.command_separator_enabled && !separator_marks.is_empty() {
1885 let width_f = self.config.width as f32;
1886 let height_f = self.config.height as f32;
1887 let opacity_multiplier = viewport.opacity;
1888 for &(screen_row, exit_code, custom_color) in separator_marks {
1889 if screen_row < rows && bg_index < self.max_bg_instances {
1890 let x0 = content_x;
1891 let x1 = content_x + cols as f32 * self.cell_width;
1892 let y0 = content_y + screen_row as f32 * self.cell_height;
1893 let color = self.separator_color(exit_code, custom_color, opacity_multiplier);
1894 self.bg_instances[bg_index] = BackgroundInstance {
1895 position: [x0 / width_f * 2.0 - 1.0, 1.0 - (y0 / height_f * 2.0)],
1896 size: [
1897 (x1 - x0) / width_f * 2.0,
1898 self.command_separator_thickness / height_f * 2.0,
1899 ],
1900 color,
1901 };
1902 bg_index += 1;
1903 }
1904 }
1905 }
1906 let _ = bg_index; self.queue.write_buffer(
1910 &self.bg_instance_buffer,
1911 0,
1912 bytemuck::cast_slice(&self.bg_instances),
1913 );
1914 self.queue.write_buffer(
1915 &self.text_instance_buffer,
1916 0,
1917 bytemuck::cast_slice(&self.text_instances),
1918 );
1919
1920 Ok(())
1921 }
1922}