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 if let Some(rect) = block_chars::get_geometric_shape_rect(
859 *ch,
860 x0,
861 y0,
862 char_w,
863 self.cell_height,
864 ) {
865 row_text.push(TextInstance {
866 position: [
867 rect.x / self.config.width as f32 * 2.0 - 1.0,
868 1.0 - (rect.y / self.config.height as f32 * 2.0),
869 ],
870 size: [
871 rect.width / self.config.width as f32 * 2.0,
872 rect.height / self.config.height as f32 * 2.0,
873 ],
874 tex_offset: [
875 self.solid_pixel_offset.0 as f32 / 2048.0,
876 self.solid_pixel_offset.1 as f32 / 2048.0,
877 ],
878 tex_size: [1.0 / 2048.0, 1.0 / 2048.0],
879 color: render_fg_color,
880 is_colored: 0,
881 });
882
883 x_offset += self.cell_width;
884 current_col += 1;
885 continue;
886 }
887 }
888
889 let (force_monochrome, base_char) = if chars.len() == 1 {
894 (super::atlas::should_render_as_symbol(*ch), *ch)
895 } else if chars.len() == 2
896 && chars[1] == '\u{FE0F}'
897 && super::atlas::should_render_as_symbol(chars[0])
898 {
899 (true, chars[0])
901 } else {
902 (false, *ch)
903 };
904
905 let mut glyph_result = if force_monochrome || chars.len() == 1 {
909 self.font_manager.find_glyph(base_char, bold, italic)
910 } else {
911 self.font_manager
912 .find_grapheme_glyph(&grapheme, bold, italic)
913 };
914
915 let mut excluded_fonts: Vec<usize> = Vec::new();
919 let resolved_info = loop {
920 match glyph_result {
921 Some((font_idx, glyph_id)) => {
922 let cache_key = ((font_idx as u64) << 32) | (glyph_id as u64);
923 if self.glyph_cache.contains_key(&cache_key) {
924 self.lru_remove(cache_key);
925 self.lru_push_front(cache_key);
926 break Some(
927 self.glyph_cache.get(&cache_key).unwrap().clone(),
928 );
929 } else if let Some(raster) =
930 self.rasterize_glyph(font_idx, glyph_id, force_monochrome)
931 {
932 let info = self.upload_glyph(cache_key, &raster);
933 self.glyph_cache.insert(cache_key, info.clone());
934 self.lru_push_front(cache_key);
935 break Some(info);
936 } else {
937 excluded_fonts.push(font_idx);
939 glyph_result = self.font_manager.find_glyph_excluding(
940 base_char,
941 bold,
942 italic,
943 &excluded_fonts,
944 );
945 continue;
946 }
947 }
948 None => break None,
949 }
950 };
951
952 let resolved_info = if resolved_info.is_none() && force_monochrome {
958 let mut glyph_result2 =
959 self.font_manager.find_glyph(base_char, bold, italic);
960 loop {
961 match glyph_result2 {
962 Some((font_idx, glyph_id)) => {
963 let cache_key = ((font_idx as u64) << 32)
964 | (glyph_id as u64)
965 | (1u64 << 63); if let Some(raster) =
967 self.rasterize_glyph(font_idx, glyph_id, false)
968 {
969 let info = self.upload_glyph(cache_key, &raster);
970 self.glyph_cache.insert(cache_key, info.clone());
971 self.lru_push_front(cache_key);
972 break Some(info);
973 } else {
974 glyph_result2 = self.font_manager.find_glyph_excluding(
975 base_char,
976 bold,
977 italic,
978 &[font_idx],
979 );
980 continue;
981 }
982 }
983 None => break None,
984 }
985 }
986 } else {
987 resolved_info
988 };
989
990 let info = match resolved_info {
991 Some(info) => info,
992 None => {
993 x_offset += self.cell_width;
994 continue;
995 }
996 };
997
998 let char_w = if is_wide {
999 self.cell_width * 2.0
1000 } else {
1001 self.cell_width
1002 };
1003 let x0 = (self.window_padding + self.content_offset_x + x_offset).round();
1004 let x1 = (self.window_padding + self.content_offset_x + x_offset + char_w)
1005 .round();
1006 let y0 = (self.window_padding
1007 + self.content_offset_y
1008 + row as f32 * self.cell_height)
1009 .round();
1010 let y1 = (self.window_padding
1011 + self.content_offset_y
1012 + (row + 1) as f32 * self.cell_height)
1013 .round();
1014
1015 let cell_w = x1 - x0;
1016 let cell_h = y1 - y0;
1017
1018 let scale_x = cell_w / char_w;
1019 let scale_y = cell_h / self.cell_height;
1020
1021 let baseline_offset = baseline_y_unrounded
1028 - (self.window_padding
1029 + self.content_offset_y
1030 + row as f32 * self.cell_height);
1031 let glyph_left = x0 + (info.bearing_x * scale_x).round();
1032 let baseline_in_cell = (baseline_offset * scale_y).round();
1033 let glyph_top = y0 + baseline_in_cell - info.bearing_y;
1034
1035 let render_w = info.width as f32 * scale_x;
1036 let render_h = info.height as f32 * scale_y;
1037
1038 let (final_left, final_top, final_w, final_h) = if chars.len() == 1
1042 && block_chars::should_snap_to_boundaries(char_type)
1043 {
1044 block_chars::snap_glyph_to_cell(
1046 glyph_left, glyph_top, render_w, render_h, x0, y0, x1, y1, 3.0, 0.5,
1047 )
1048 } else {
1049 (glyph_left, glyph_top, render_w, render_h)
1050 };
1051
1052 row_text.push(TextInstance {
1053 position: [
1054 final_left / self.config.width as f32 * 2.0 - 1.0,
1055 1.0 - (final_top / self.config.height as f32 * 2.0),
1056 ],
1057 size: [
1058 final_w / self.config.width as f32 * 2.0,
1059 final_h / self.config.height as f32 * 2.0,
1060 ],
1061 tex_offset: [info.x as f32 / 2048.0, info.y as f32 / 2048.0],
1062 tex_size: [info.width as f32 / 2048.0, info.height as f32 / 2048.0],
1063 color: render_fg_color,
1064 is_colored: if info.is_colored { 1 } else { 0 },
1065 });
1066 }
1067 x_offset += self.cell_width;
1068 current_col += 1;
1069 }
1070
1071 {
1073 let underline_thickness = (self.cell_height * 0.07).max(1.0).round();
1074 let tex_offset = [
1075 self.solid_pixel_offset.0 as f32 / 2048.0,
1076 self.solid_pixel_offset.1 as f32 / 2048.0,
1077 ];
1078 let tex_size = [1.0 / 2048.0, 1.0 / 2048.0];
1079 let y0 = self.window_padding
1080 + self.content_offset_y
1081 + (row + 1) as f32 * self.cell_height
1082 - underline_thickness;
1083 let ndc_y = 1.0 - (y0 / self.config.height as f32 * 2.0);
1084 let ndc_h = underline_thickness / self.config.height as f32 * 2.0;
1085 let is_stipple =
1086 self.link_underline_style == par_term_config::LinkUnderlineStyle::Stipple;
1087 let stipple_on = 2.0_f32;
1089 let stipple_off = 2.0_f32;
1090 let stipple_period = stipple_on + stipple_off;
1091
1092 for col_idx in 0..self.cols {
1093 let cell = &self.cells[start + col_idx];
1094 if !cell.underline || row_text.len() >= self.cols * 2 {
1095 continue;
1096 }
1097 let text_alpha = if self.keep_text_opaque {
1098 1.0
1099 } else {
1100 self.window_opacity
1101 };
1102 let fg = [
1103 cell.fg_color[0] as f32 / 255.0,
1104 cell.fg_color[1] as f32 / 255.0,
1105 cell.fg_color[2] as f32 / 255.0,
1106 text_alpha,
1107 ];
1108 let cell_x0 = self.window_padding
1109 + self.content_offset_x
1110 + col_idx as f32 * self.cell_width;
1111
1112 if is_stipple {
1113 let mut px = 0.0;
1115 while px < self.cell_width && row_text.len() < self.cols * 2 {
1116 let seg_w = stipple_on.min(self.cell_width - px);
1117 let x = cell_x0 + px;
1118 row_text.push(TextInstance {
1119 position: [x / self.config.width as f32 * 2.0 - 1.0, ndc_y],
1120 size: [seg_w / self.config.width as f32 * 2.0, ndc_h],
1121 tex_offset,
1122 tex_size,
1123 color: fg,
1124 is_colored: 0,
1125 });
1126 px += stipple_period;
1127 }
1128 } else {
1129 row_text.push(TextInstance {
1130 position: [cell_x0 / self.config.width as f32 * 2.0 - 1.0, ndc_y],
1131 size: [self.cell_width / self.config.width as f32 * 2.0, ndc_h],
1132 tex_offset,
1133 tex_size,
1134 color: fg,
1135 is_colored: 0,
1136 });
1137 }
1138 }
1139 }
1140
1141 let bg_start = row * self.cols;
1143 self.bg_instances[bg_start..bg_start + self.cols].copy_from_slice(&row_bg);
1144
1145 let text_start = row * self.cols * 2;
1146 for i in 0..(self.cols * 2) {
1148 self.text_instances[text_start + i].size = [0.0, 0.0];
1149 }
1150 let text_count = row_text.len().min(self.cols * 2);
1152 self.text_instances[text_start..text_start + text_count]
1153 .copy_from_slice(&row_text[..text_count]);
1154
1155 self.queue.write_buffer(
1157 &self.bg_instance_buffer,
1158 (bg_start * std::mem::size_of::<BackgroundInstance>()) as u64,
1159 bytemuck::cast_slice(&row_bg),
1160 );
1161 self.queue.write_buffer(
1162 &self.text_instance_buffer,
1163 (text_start * std::mem::size_of::<TextInstance>()) as u64,
1164 bytemuck::cast_slice(
1165 &self.text_instances[text_start..text_start + self.cols * 2],
1166 ),
1167 );
1168
1169 self.row_cache[row] = Some(RowCacheEntry {});
1170 self.dirty_rows[row] = false;
1171 }
1172 }
1173
1174 let base_overlay_index = self.cols * self.rows;
1177 let mut overlay_instances = vec![
1178 BackgroundInstance {
1179 position: [0.0, 0.0],
1180 size: [0.0, 0.0],
1181 color: [0.0, 0.0, 0.0, 0.0],
1182 };
1183 10
1184 ];
1185
1186 let cursor_visible = self.cursor_opacity > 0.0
1188 && !self.cursor_hidden_for_shader
1189 && (self.is_focused
1190 || self.unfocused_cursor_style != par_term_config::UnfocusedCursorStyle::Hidden);
1191
1192 let cursor_col = self.cursor_pos.0;
1194 let cursor_row = self.cursor_pos.1;
1195 let cursor_x0 =
1196 self.window_padding + self.content_offset_x + cursor_col as f32 * self.cell_width;
1197 let cursor_x1 = cursor_x0 + self.cell_width;
1198 let cursor_y0 =
1199 self.window_padding + self.content_offset_y + cursor_row as f32 * self.cell_height;
1200 let cursor_y1 = cursor_y0 + self.cell_height;
1201
1202 overlay_instances[0] = self.cursor_overlay.unwrap_or(BackgroundInstance {
1204 position: [0.0, 0.0],
1205 size: [0.0, 0.0],
1206 color: [0.0, 0.0, 0.0, 0.0],
1207 });
1208
1209 if cursor_visible && self.cursor_guide_enabled {
1211 let guide_x0 = self.window_padding + self.content_offset_x;
1212 let guide_x1 =
1213 self.config.width as f32 - self.window_padding - self.content_inset_right;
1214 overlay_instances[1] = BackgroundInstance {
1215 position: [
1216 guide_x0 / self.config.width as f32 * 2.0 - 1.0,
1217 1.0 - (cursor_y0 / self.config.height as f32 * 2.0),
1218 ],
1219 size: [
1220 (guide_x1 - guide_x0) / self.config.width as f32 * 2.0,
1221 (cursor_y1 - cursor_y0) / self.config.height as f32 * 2.0,
1222 ],
1223 color: self.cursor_guide_color,
1224 };
1225 }
1226
1227 if cursor_visible && self.cursor_shadow_enabled {
1229 let shadow_x0 = cursor_x0 + self.cursor_shadow_offset[0];
1230 let shadow_y0 = cursor_y0 + self.cursor_shadow_offset[1];
1231 overlay_instances[2] = BackgroundInstance {
1232 position: [
1233 shadow_x0 / self.config.width as f32 * 2.0 - 1.0,
1234 1.0 - (shadow_y0 / self.config.height as f32 * 2.0),
1235 ],
1236 size: [
1237 self.cell_width / self.config.width as f32 * 2.0,
1238 self.cell_height / self.config.height as f32 * 2.0,
1239 ],
1240 color: self.cursor_shadow_color,
1241 };
1242 }
1243
1244 if cursor_visible && self.cursor_boost > 0.0 {
1246 let glow_expand = 4.0 * self.scale_factor * self.cursor_boost; let glow_x0 = cursor_x0 - glow_expand;
1248 let glow_y0 = cursor_y0 - glow_expand;
1249 let glow_w = self.cell_width + glow_expand * 2.0;
1250 let glow_h = self.cell_height + glow_expand * 2.0;
1251 overlay_instances[3] = BackgroundInstance {
1252 position: [
1253 glow_x0 / self.config.width as f32 * 2.0 - 1.0,
1254 1.0 - (glow_y0 / self.config.height as f32 * 2.0),
1255 ],
1256 size: [
1257 glow_w / self.config.width as f32 * 2.0,
1258 glow_h / self.config.height as f32 * 2.0,
1259 ],
1260 color: [
1261 self.cursor_boost_color[0],
1262 self.cursor_boost_color[1],
1263 self.cursor_boost_color[2],
1264 self.cursor_boost * 0.3 * self.cursor_opacity, ],
1266 };
1267 }
1268
1269 let render_hollow = cursor_visible
1272 && !self.is_focused
1273 && self.unfocused_cursor_style == par_term_config::UnfocusedCursorStyle::Hollow;
1274
1275 if render_hollow {
1276 use par_term_emu_core_rust::cursor::CursorStyle;
1277 let is_block = matches!(
1278 self.cursor_style,
1279 CursorStyle::SteadyBlock | CursorStyle::BlinkingBlock
1280 );
1281
1282 if is_block {
1283 let border_width = 2.0; let color = [
1285 self.cursor_color[0],
1286 self.cursor_color[1],
1287 self.cursor_color[2],
1288 self.cursor_opacity,
1289 ];
1290
1291 overlay_instances[4] = BackgroundInstance {
1293 position: [
1294 cursor_x0 / self.config.width as f32 * 2.0 - 1.0,
1295 1.0 - (cursor_y0 / self.config.height as f32 * 2.0),
1296 ],
1297 size: [
1298 self.cell_width / self.config.width as f32 * 2.0,
1299 border_width / self.config.height as f32 * 2.0,
1300 ],
1301 color,
1302 };
1303
1304 overlay_instances[5] = BackgroundInstance {
1306 position: [
1307 cursor_x0 / self.config.width as f32 * 2.0 - 1.0,
1308 1.0 - ((cursor_y1 - border_width) / self.config.height as f32 * 2.0),
1309 ],
1310 size: [
1311 self.cell_width / self.config.width as f32 * 2.0,
1312 border_width / self.config.height as f32 * 2.0,
1313 ],
1314 color,
1315 };
1316
1317 overlay_instances[6] = BackgroundInstance {
1319 position: [
1320 cursor_x0 / self.config.width as f32 * 2.0 - 1.0,
1321 1.0 - ((cursor_y0 + border_width) / self.config.height as f32 * 2.0),
1322 ],
1323 size: [
1324 border_width / self.config.width as f32 * 2.0,
1325 (self.cell_height - border_width * 2.0) / self.config.height as f32 * 2.0,
1326 ],
1327 color,
1328 };
1329
1330 overlay_instances[7] = BackgroundInstance {
1332 position: [
1333 (cursor_x1 - border_width) / self.config.width as f32 * 2.0 - 1.0,
1334 1.0 - ((cursor_y0 + border_width) / self.config.height as f32 * 2.0),
1335 ],
1336 size: [
1337 border_width / self.config.width as f32 * 2.0,
1338 (self.cell_height - border_width * 2.0) / self.config.height as f32 * 2.0,
1339 ],
1340 color,
1341 };
1342 }
1343 }
1344
1345 for (i, instance) in overlay_instances.iter().enumerate() {
1347 self.bg_instances[base_overlay_index + i] = *instance;
1348 }
1349 self.queue.write_buffer(
1350 &self.bg_instance_buffer,
1351 (base_overlay_index * std::mem::size_of::<BackgroundInstance>()) as u64,
1352 bytemuck::cast_slice(&overlay_instances),
1353 );
1354
1355 let separator_base = self.cols * self.rows + 10;
1357 let mut separator_instances = vec![
1358 BackgroundInstance {
1359 position: [0.0, 0.0],
1360 size: [0.0, 0.0],
1361 color: [0.0, 0.0, 0.0, 0.0],
1362 };
1363 self.rows
1364 ];
1365
1366 if self.command_separator_enabled {
1367 let width_f = self.config.width as f32;
1368 let height_f = self.config.height as f32;
1369 for &(screen_row, exit_code, custom_color) in &self.visible_separator_marks {
1370 if screen_row < self.rows {
1371 let x0 = self.window_padding + self.content_offset_x;
1372 let x1 = width_f - self.window_padding - self.content_inset_right;
1373 let y0 = self.window_padding
1374 + self.content_offset_y
1375 + screen_row as f32 * self.cell_height;
1376 let color = self.separator_color(exit_code, custom_color, 1.0);
1377 separator_instances[screen_row] = BackgroundInstance {
1378 position: [x0 / width_f * 2.0 - 1.0, 1.0 - (y0 / height_f * 2.0)],
1379 size: [
1380 (x1 - x0) / width_f * 2.0,
1381 self.command_separator_thickness / height_f * 2.0,
1382 ],
1383 color,
1384 };
1385 }
1386 }
1387 }
1388
1389 for (i, instance) in separator_instances.iter().enumerate() {
1390 if separator_base + i < self.max_bg_instances {
1391 self.bg_instances[separator_base + i] = *instance;
1392 }
1393 }
1394 let separator_byte_offset = separator_base * std::mem::size_of::<BackgroundInstance>();
1395 let separator_byte_count =
1396 separator_instances.len() * std::mem::size_of::<BackgroundInstance>();
1397 if separator_byte_offset + separator_byte_count
1398 <= self.max_bg_instances * std::mem::size_of::<BackgroundInstance>()
1399 {
1400 self.queue.write_buffer(
1401 &self.bg_instance_buffer,
1402 separator_byte_offset as u64,
1403 bytemuck::cast_slice(&separator_instances),
1404 );
1405 }
1406
1407 let gutter_base = separator_base + self.rows;
1409 let mut gutter_instances = vec![
1410 BackgroundInstance {
1411 position: [0.0, 0.0],
1412 size: [0.0, 0.0],
1413 color: [0.0, 0.0, 0.0, 0.0],
1414 };
1415 self.rows
1416 ];
1417
1418 if !self.gutter_indicators.is_empty() {
1419 let width_f = self.config.width as f32;
1420 let height_f = self.config.height as f32;
1421 for &(screen_row, color) in &self.gutter_indicators {
1422 if screen_row < self.rows {
1423 let x0 = self.window_padding + self.content_offset_x;
1424 let x1 = x0 + 2.0 * self.cell_width; let y0 = self.window_padding
1426 + self.content_offset_y
1427 + screen_row as f32 * self.cell_height;
1428 gutter_instances[screen_row] = BackgroundInstance {
1429 position: [x0 / width_f * 2.0 - 1.0, 1.0 - (y0 / height_f * 2.0)],
1430 size: [(x1 - x0) / width_f * 2.0, self.cell_height / height_f * 2.0],
1431 color,
1432 };
1433 }
1434 }
1435 }
1436
1437 for (i, instance) in gutter_instances.iter().enumerate() {
1438 if gutter_base + i < self.max_bg_instances {
1439 self.bg_instances[gutter_base + i] = *instance;
1440 }
1441 }
1442 let gutter_byte_offset = gutter_base * std::mem::size_of::<BackgroundInstance>();
1443 let gutter_byte_count = gutter_instances.len() * std::mem::size_of::<BackgroundInstance>();
1444 if gutter_byte_offset + gutter_byte_count
1445 <= self.max_bg_instances * std::mem::size_of::<BackgroundInstance>()
1446 {
1447 self.queue.write_buffer(
1448 &self.bg_instance_buffer,
1449 gutter_byte_offset as u64,
1450 bytemuck::cast_slice(&gutter_instances),
1451 );
1452 }
1453
1454 Ok(())
1455 }
1456
1457 #[allow(dead_code, clippy::too_many_arguments)]
1475 pub fn render_pane_to_view(
1476 &mut self,
1477 surface_view: &wgpu::TextureView,
1478 viewport: &PaneViewport,
1479 cells: &[Cell],
1480 cols: usize,
1481 rows: usize,
1482 cursor_pos: Option<(usize, usize)>,
1483 cursor_opacity: f32,
1484 show_scrollbar: bool,
1485 clear_first: bool,
1486 skip_background_image: bool,
1487 separator_marks: &[SeparatorMark],
1488 pane_background: Option<&par_term_config::PaneBackground>,
1489 ) -> Result<()> {
1490 self.build_pane_instance_buffers(
1493 viewport,
1494 cells,
1495 cols,
1496 rows,
1497 cursor_pos,
1498 cursor_opacity,
1499 skip_background_image,
1500 separator_marks,
1501 )?;
1502
1503 let pane_bg_resources = if let Some(pane_bg) = pane_background
1507 && let Some(ref path) = pane_bg.image_path
1508 {
1509 self.pane_bg_cache.get(path.as_str()).map(|entry| {
1510 self.create_pane_bg_bind_group(
1511 entry,
1512 viewport.x,
1513 viewport.y,
1514 viewport.width,
1515 viewport.height,
1516 pane_bg.mode,
1517 pane_bg.opacity,
1518 pane_bg.darken,
1519 )
1520 })
1521 } else {
1522 None
1523 };
1524
1525 let mut encoder = self
1526 .device
1527 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1528 label: Some("pane render encoder"),
1529 });
1530
1531 let load_op = if clear_first {
1533 let clear_color = if self.bg_is_solid_color {
1534 wgpu::Color {
1535 r: self.solid_bg_color[0] as f64
1536 * self.window_opacity as f64
1537 * viewport.opacity as f64,
1538 g: self.solid_bg_color[1] as f64
1539 * self.window_opacity as f64
1540 * viewport.opacity as f64,
1541 b: self.solid_bg_color[2] as f64
1542 * self.window_opacity as f64
1543 * viewport.opacity as f64,
1544 a: self.window_opacity as f64 * viewport.opacity as f64,
1545 }
1546 } else {
1547 wgpu::Color {
1548 r: self.background_color[0] as f64
1549 * self.window_opacity as f64
1550 * viewport.opacity as f64,
1551 g: self.background_color[1] as f64
1552 * self.window_opacity as f64
1553 * viewport.opacity as f64,
1554 b: self.background_color[2] as f64
1555 * self.window_opacity as f64
1556 * viewport.opacity as f64,
1557 a: self.window_opacity as f64 * viewport.opacity as f64,
1558 }
1559 };
1560 wgpu::LoadOp::Clear(clear_color)
1561 } else {
1562 wgpu::LoadOp::Load
1563 };
1564
1565 {
1566 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1567 label: Some("pane render pass"),
1568 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1569 view: surface_view,
1570 resolve_target: None,
1571 ops: wgpu::Operations {
1572 load: load_op,
1573 store: wgpu::StoreOp::Store,
1574 },
1575 depth_slice: None,
1576 })],
1577 depth_stencil_attachment: None,
1578 timestamp_writes: None,
1579 occlusion_query_set: None,
1580 });
1581
1582 let (sx, sy, sw, sh) = viewport.to_scissor_rect();
1584 render_pass.set_scissor_rect(sx, sy, sw, sh);
1585
1586 if let Some((ref bind_group, ref _buf)) = pane_bg_resources {
1590 render_pass.set_pipeline(&self.bg_image_pipeline);
1591 render_pass.set_bind_group(0, bind_group, &[]);
1592 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1593 render_pass.draw(0..4, 0..1);
1594 }
1595
1596 render_pass.set_pipeline(&self.bg_pipeline);
1598 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1599 render_pass.set_vertex_buffer(1, self.bg_instance_buffer.slice(..));
1600 render_pass.draw(0..4, 0..self.max_bg_instances as u32);
1601
1602 render_pass.set_pipeline(&self.text_pipeline);
1604 render_pass.set_bind_group(0, &self.text_bind_group, &[]);
1605 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1606 render_pass.set_vertex_buffer(1, self.text_instance_buffer.slice(..));
1607 render_pass.draw(0..4, 0..self.max_text_instances as u32);
1608
1609 if show_scrollbar {
1611 render_pass.set_scissor_rect(0, 0, self.config.width, self.config.height);
1613 self.scrollbar.render(&mut render_pass);
1614 }
1615 }
1616
1617 self.queue.submit(std::iter::once(encoder.finish()));
1618 Ok(())
1619 }
1620
1621 #[allow(clippy::too_many_arguments)]
1630 fn build_pane_instance_buffers(
1631 &mut self,
1632 viewport: &PaneViewport,
1633 cells: &[Cell],
1634 cols: usize,
1635 rows: usize,
1636 cursor_pos: Option<(usize, usize)>,
1637 cursor_opacity: f32,
1638 skip_solid_background: bool,
1639 separator_marks: &[SeparatorMark],
1640 ) -> Result<()> {
1641 let _shaping_options = ShapingOptions {
1642 enable_ligatures: self.enable_ligatures,
1643 enable_kerning: self.enable_kerning,
1644 ..Default::default()
1645 };
1646
1647 for instance in &mut self.bg_instances {
1649 instance.size = [0.0, 0.0];
1650 instance.color = [0.0, 0.0, 0.0, 0.0];
1651 }
1652
1653 let bg_start_index = if !skip_solid_background && !self.bg_instances.is_empty() {
1657 let bg_color = self.background_color;
1658 let opacity = self.window_opacity * viewport.opacity;
1659 self.bg_instances[0] = super::types::BackgroundInstance {
1660 position: [viewport.x, viewport.y],
1661 size: [viewport.width, viewport.height],
1662 color: [
1663 bg_color[0] * opacity,
1664 bg_color[1] * opacity,
1665 bg_color[2] * opacity,
1666 opacity,
1667 ],
1668 };
1669 1 } else {
1671 0 };
1673
1674 for instance in &mut self.text_instances {
1675 instance.size = [0.0, 0.0];
1676 }
1677
1678 let mut bg_index = bg_start_index;
1680 let mut text_index = 0;
1681
1682 let (content_x, content_y) = viewport.content_origin();
1684 let opacity_multiplier = viewport.opacity;
1685
1686 for row in 0..rows {
1687 let row_start = row * cols;
1688 let row_end = (row + 1) * cols;
1689 if row_start >= cells.len() {
1690 break;
1691 }
1692 let row_cells = &cells[row_start..row_end.min(cells.len())];
1693
1694 let mut col = 0;
1696 while col < row_cells.len() {
1697 let cell = &row_cells[col];
1698 let is_default_bg = (cell.bg_color[0] as f32 / 255.0 - self.background_color[0])
1699 .abs()
1700 < 0.001
1701 && (cell.bg_color[1] as f32 / 255.0 - self.background_color[1]).abs() < 0.001
1702 && (cell.bg_color[2] as f32 / 255.0 - self.background_color[2]).abs() < 0.001;
1703
1704 let has_cursor = cursor_pos.is_some_and(|(cx, cy)| cx == col && cy == row)
1706 && cursor_opacity > 0.0
1707 && !self.cursor_hidden_for_shader;
1708
1709 if is_default_bg && !has_cursor {
1710 col += 1;
1711 continue;
1712 }
1713
1714 let bg_alpha =
1716 if self.transparency_affects_only_default_background && !is_default_bg {
1717 1.0
1718 } else {
1719 self.window_opacity
1720 };
1721 let pane_alpha = bg_alpha * opacity_multiplier;
1722 let mut bg_color = [
1723 cell.bg_color[0] as f32 / 255.0,
1724 cell.bg_color[1] as f32 / 255.0,
1725 cell.bg_color[2] as f32 / 255.0,
1726 pane_alpha,
1727 ];
1728
1729 if has_cursor {
1731 use par_term_emu_core_rust::cursor::CursorStyle;
1732 match self.cursor_style {
1733 CursorStyle::SteadyBlock | CursorStyle::BlinkingBlock => {
1734 for (bg, &cursor) in bg_color.iter_mut().take(3).zip(&self.cursor_color)
1735 {
1736 *bg = *bg * (1.0 - cursor_opacity) + cursor * cursor_opacity;
1737 }
1738 bg_color[3] = bg_color[3].max(cursor_opacity * opacity_multiplier);
1739 }
1740 _ => {}
1741 }
1742
1743 let x0 = content_x + col as f32 * self.cell_width;
1745 let y0 = content_y + row as f32 * self.cell_height;
1746 let x1 = x0 + self.cell_width;
1747 let y1 = y0 + self.cell_height;
1748
1749 if bg_index < self.max_bg_instances {
1750 self.bg_instances[bg_index] = BackgroundInstance {
1751 position: [
1752 x0 / self.config.width as f32 * 2.0 - 1.0,
1753 1.0 - (y0 / self.config.height as f32 * 2.0),
1754 ],
1755 size: [
1756 (x1 - x0) / self.config.width as f32 * 2.0,
1757 (y1 - y0) / self.config.height as f32 * 2.0,
1758 ],
1759 color: bg_color,
1760 };
1761 bg_index += 1;
1762 }
1763 col += 1;
1764 continue;
1765 }
1766
1767 let start_col = col;
1769 let run_color = cell.bg_color;
1770 col += 1;
1771 while col < row_cells.len() {
1772 let next_cell = &row_cells[col];
1773 let next_has_cursor = cursor_pos.is_some_and(|(cx, cy)| cx == col && cy == row)
1774 && cursor_opacity > 0.0;
1775 if next_cell.bg_color != run_color || next_has_cursor {
1776 break;
1777 }
1778 col += 1;
1779 }
1780 let run_length = col - start_col;
1781
1782 let x0 = content_x + start_col as f32 * self.cell_width;
1784 let x1 = content_x + (start_col + run_length) as f32 * self.cell_width;
1785 let y0 = content_y + row as f32 * self.cell_height;
1786 let y1 = y0 + self.cell_height;
1787
1788 if bg_index < self.max_bg_instances {
1789 self.bg_instances[bg_index] = BackgroundInstance {
1790 position: [
1791 x0 / self.config.width as f32 * 2.0 - 1.0,
1792 1.0 - (y0 / self.config.height as f32 * 2.0),
1793 ],
1794 size: [
1795 (x1 - x0) / self.config.width as f32 * 2.0,
1796 (y1 - y0) / self.config.height as f32 * 2.0,
1797 ],
1798 color: bg_color,
1799 };
1800 bg_index += 1;
1801 }
1802 }
1803
1804 let natural_line_height = self.font_ascent + self.font_descent + self.font_leading;
1806 let vertical_padding = (self.cell_height - natural_line_height).max(0.0) / 2.0;
1807 let baseline_y =
1808 content_y + (row as f32 * self.cell_height) + vertical_padding + self.font_ascent;
1809
1810 let text_alpha = if self.keep_text_opaque {
1812 opacity_multiplier } else {
1814 self.window_opacity * opacity_multiplier
1815 };
1816
1817 for (col_idx, cell) in row_cells.iter().enumerate() {
1818 if cell.wide_char_spacer || cell.grapheme == " " {
1819 continue;
1820 }
1821
1822 let chars: Vec<char> = cell.grapheme.chars().collect();
1823 if chars.is_empty() {
1824 continue;
1825 }
1826
1827 let ch = chars[0];
1828
1829 let char_type = block_chars::classify_char(ch);
1831 if chars.len() == 1 && block_chars::should_render_geometrically(char_type) {
1832 let char_w = if cell.wide_char {
1833 self.cell_width * 2.0
1834 } else {
1835 self.cell_width
1836 };
1837 let x0 = content_x + col_idx as f32 * self.cell_width;
1838 let y0 = content_y + row as f32 * self.cell_height;
1839
1840 let fg_color = [
1841 cell.fg_color[0] as f32 / 255.0,
1842 cell.fg_color[1] as f32 / 255.0,
1843 cell.fg_color[2] as f32 / 255.0,
1844 text_alpha,
1845 ];
1846
1847 let aspect_ratio = self.cell_height / char_w;
1849 if let Some(box_geo) = block_chars::get_box_drawing_geometry(ch, aspect_ratio) {
1850 for segment in &box_geo.segments {
1851 let rect = segment.to_pixel_rect(x0, y0, char_w, self.cell_height);
1852
1853 let extension = 1.0;
1855 let ext_x = if segment.x <= 0.01 { extension } else { 0.0 };
1856 let ext_y = if segment.y <= 0.01 { extension } else { 0.0 };
1857 let ext_w = if segment.x + segment.width >= 0.99 {
1858 extension
1859 } else {
1860 0.0
1861 };
1862 let ext_h = if segment.y + segment.height >= 0.99 {
1863 extension
1864 } else {
1865 0.0
1866 };
1867
1868 let final_x = rect.x - ext_x;
1869 let final_y = rect.y - ext_y;
1870 let final_w = rect.width + ext_x + ext_w;
1871 let final_h = rect.height + ext_y + ext_h;
1872
1873 if text_index < self.max_text_instances {
1874 self.text_instances[text_index] = TextInstance {
1875 position: [
1876 final_x / self.config.width as f32 * 2.0 - 1.0,
1877 1.0 - (final_y / self.config.height as f32 * 2.0),
1878 ],
1879 size: [
1880 final_w / self.config.width as f32 * 2.0,
1881 final_h / self.config.height as f32 * 2.0,
1882 ],
1883 tex_offset: [
1884 self.solid_pixel_offset.0 as f32 / 2048.0,
1885 self.solid_pixel_offset.1 as f32 / 2048.0,
1886 ],
1887 tex_size: [1.0 / 2048.0, 1.0 / 2048.0],
1888 color: fg_color,
1889 is_colored: 0,
1890 };
1891 text_index += 1;
1892 }
1893 }
1894 continue;
1895 }
1896
1897 if let Some(geo_block) = block_chars::get_geometric_block(ch) {
1899 let rect = geo_block.to_pixel_rect(x0, y0, char_w, self.cell_height);
1900
1901 let extension = 1.0;
1903 let ext_x = if geo_block.x == 0.0 { extension } else { 0.0 };
1904 let ext_y = if geo_block.y == 0.0 { extension } else { 0.0 };
1905 let ext_w = if geo_block.x + geo_block.width >= 1.0 {
1906 extension
1907 } else {
1908 0.0
1909 };
1910 let ext_h = if geo_block.y + geo_block.height >= 1.0 {
1911 extension
1912 } else {
1913 0.0
1914 };
1915
1916 let final_x = rect.x - ext_x;
1917 let final_y = rect.y - ext_y;
1918 let final_w = rect.width + ext_x + ext_w;
1919 let final_h = rect.height + ext_y + ext_h;
1920
1921 if text_index < self.max_text_instances {
1922 self.text_instances[text_index] = TextInstance {
1923 position: [
1924 final_x / self.config.width as f32 * 2.0 - 1.0,
1925 1.0 - (final_y / self.config.height as f32 * 2.0),
1926 ],
1927 size: [
1928 final_w / self.config.width as f32 * 2.0,
1929 final_h / self.config.height as f32 * 2.0,
1930 ],
1931 tex_offset: [
1932 self.solid_pixel_offset.0 as f32 / 2048.0,
1933 self.solid_pixel_offset.1 as f32 / 2048.0,
1934 ],
1935 tex_size: [1.0 / 2048.0, 1.0 / 2048.0],
1936 color: fg_color,
1937 is_colored: 0,
1938 };
1939 text_index += 1;
1940 }
1941 continue;
1942 }
1943 }
1944
1945 let (force_monochrome, base_char) = if chars.len() == 1 {
1948 (super::atlas::should_render_as_symbol(ch), ch)
1949 } else if chars.len() == 2
1950 && chars[1] == '\u{FE0F}'
1951 && super::atlas::should_render_as_symbol(chars[0])
1952 {
1953 (true, chars[0])
1954 } else {
1955 (false, ch)
1956 };
1957
1958 let mut glyph_result = if force_monochrome || chars.len() == 1 {
1961 self.font_manager
1962 .find_glyph(base_char, cell.bold, cell.italic)
1963 } else {
1964 self.font_manager
1965 .find_grapheme_glyph(&cell.grapheme, cell.bold, cell.italic)
1966 };
1967
1968 let mut excluded_fonts: Vec<usize> = Vec::new();
1970 let resolved_info = loop {
1971 match glyph_result {
1972 Some((font_idx, glyph_id)) => {
1973 let cache_key = ((font_idx as u64) << 32) | (glyph_id as u64);
1974 if self.glyph_cache.contains_key(&cache_key) {
1975 self.lru_remove(cache_key);
1976 self.lru_push_front(cache_key);
1977 break Some(self.glyph_cache.get(&cache_key).unwrap().clone());
1978 } else if let Some(raster) =
1979 self.rasterize_glyph(font_idx, glyph_id, force_monochrome)
1980 {
1981 let info = self.upload_glyph(cache_key, &raster);
1982 self.glyph_cache.insert(cache_key, info.clone());
1983 self.lru_push_front(cache_key);
1984 break Some(info);
1985 } else {
1986 excluded_fonts.push(font_idx);
1988 glyph_result = self.font_manager.find_glyph_excluding(
1989 base_char,
1990 cell.bold,
1991 cell.italic,
1992 &excluded_fonts,
1993 );
1994 continue;
1995 }
1996 }
1997 None => break None,
1998 }
1999 };
2000
2001 let resolved_info = if resolved_info.is_none() && force_monochrome {
2003 let mut glyph_result2 =
2004 self.font_manager
2005 .find_glyph(base_char, cell.bold, cell.italic);
2006 loop {
2007 match glyph_result2 {
2008 Some((font_idx, glyph_id)) => {
2009 let cache_key =
2010 ((font_idx as u64) << 32) | (glyph_id as u64) | (1u64 << 63);
2011 if let Some(raster) =
2012 self.rasterize_glyph(font_idx, glyph_id, false)
2013 {
2014 let info = self.upload_glyph(cache_key, &raster);
2015 self.glyph_cache.insert(cache_key, info.clone());
2016 self.lru_push_front(cache_key);
2017 break Some(info);
2018 } else {
2019 glyph_result2 = self.font_manager.find_glyph_excluding(
2020 base_char,
2021 cell.bold,
2022 cell.italic,
2023 &[font_idx],
2024 );
2025 continue;
2026 }
2027 }
2028 None => break None,
2029 }
2030 }
2031 } else {
2032 resolved_info
2033 };
2034
2035 if let Some(info) = resolved_info {
2036 let char_w = if cell.wide_char {
2037 self.cell_width * 2.0
2038 } else {
2039 self.cell_width
2040 };
2041 let x0 = content_x + col_idx as f32 * self.cell_width;
2042 let y0 = content_y + row as f32 * self.cell_height;
2043 let x1 = x0 + char_w;
2044 let y1 = y0 + self.cell_height;
2045
2046 let cell_w = x1 - x0;
2047 let cell_h = y1 - y0;
2048 let scale_x = cell_w / char_w;
2049 let scale_y = cell_h / self.cell_height;
2050
2051 let baseline_offset = baseline_y - (content_y + row as f32 * self.cell_height);
2052 let glyph_left = x0 + (info.bearing_x * scale_x).round();
2053 let baseline_in_cell = (baseline_offset * scale_y).round();
2054 let glyph_top = y0 + baseline_in_cell - info.bearing_y;
2055
2056 let render_w = info.width as f32 * scale_x;
2057 let render_h = info.height as f32 * scale_y;
2058
2059 let (final_left, final_top, final_w, final_h) =
2060 if chars.len() == 1 && block_chars::should_snap_to_boundaries(char_type) {
2061 block_chars::snap_glyph_to_cell(
2062 glyph_left, glyph_top, render_w, render_h, x0, y0, x1, y1, 3.0, 0.5,
2063 )
2064 } else {
2065 (glyph_left, glyph_top, render_w, render_h)
2066 };
2067
2068 let fg_color = [
2069 cell.fg_color[0] as f32 / 255.0,
2070 cell.fg_color[1] as f32 / 255.0,
2071 cell.fg_color[2] as f32 / 255.0,
2072 text_alpha,
2073 ];
2074
2075 if text_index < self.max_text_instances {
2076 self.text_instances[text_index] = TextInstance {
2077 position: [
2078 final_left / self.config.width as f32 * 2.0 - 1.0,
2079 1.0 - (final_top / self.config.height as f32 * 2.0),
2080 ],
2081 size: [
2082 final_w / self.config.width as f32 * 2.0,
2083 final_h / self.config.height as f32 * 2.0,
2084 ],
2085 tex_offset: [info.x as f32 / 2048.0, info.y as f32 / 2048.0],
2086 tex_size: [info.width as f32 / 2048.0, info.height as f32 / 2048.0],
2087 color: fg_color,
2088 is_colored: if info.is_colored { 1 } else { 0 },
2089 };
2090 text_index += 1;
2091 }
2092 }
2093 }
2094 }
2095
2096 if self.command_separator_enabled && !separator_marks.is_empty() {
2098 let width_f = self.config.width as f32;
2099 let height_f = self.config.height as f32;
2100 let opacity_multiplier = viewport.opacity;
2101 for &(screen_row, exit_code, custom_color) in separator_marks {
2102 if screen_row < rows && bg_index < self.max_bg_instances {
2103 let x0 = content_x;
2104 let x1 = content_x + cols as f32 * self.cell_width;
2105 let y0 = content_y + screen_row as f32 * self.cell_height;
2106 let color = self.separator_color(exit_code, custom_color, opacity_multiplier);
2107 self.bg_instances[bg_index] = BackgroundInstance {
2108 position: [x0 / width_f * 2.0 - 1.0, 1.0 - (y0 / height_f * 2.0)],
2109 size: [
2110 (x1 - x0) / width_f * 2.0,
2111 self.command_separator_thickness / height_f * 2.0,
2112 ],
2113 color,
2114 };
2115 bg_index += 1;
2116 }
2117 }
2118 }
2119 let _ = bg_index; self.queue.write_buffer(
2123 &self.bg_instance_buffer,
2124 0,
2125 bytemuck::cast_slice(&self.bg_instances),
2126 );
2127 self.queue.write_buffer(
2128 &self.text_instance_buffer,
2129 0,
2130 bytemuck::cast_slice(&self.text_instances),
2131 );
2132
2133 Ok(())
2134 }
2135}