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