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 glyph_result = if chars.len() > 1 {
892 self.font_manager
893 .find_grapheme_glyph(&grapheme, bold, italic)
894 } else {
895 self.font_manager.find_glyph(*ch, bold, italic)
896 };
897
898 if let Some((font_idx, glyph_id)) = glyph_result {
899 let cache_key = ((font_idx as u64) << 32) | (glyph_id as u64);
900 let force_monochrome =
903 chars.len() == 1 && super::atlas::should_render_as_symbol(*ch);
904 let info = if self.glyph_cache.contains_key(&cache_key) {
905 self.lru_remove(cache_key);
907 self.lru_push_front(cache_key);
908 self.glyph_cache.get(&cache_key).unwrap().clone()
909 } else if let Some(raster) =
910 self.rasterize_glyph(font_idx, glyph_id, force_monochrome)
911 {
912 let info = self.upload_glyph(cache_key, &raster);
913 self.glyph_cache.insert(cache_key, info.clone());
914 self.lru_push_front(cache_key);
915 info
916 } else {
917 x_offset += self.cell_width;
918 continue;
919 };
920
921 let char_w = if is_wide {
922 self.cell_width * 2.0
923 } else {
924 self.cell_width
925 };
926 let x0 =
927 (self.window_padding + self.content_offset_x + x_offset).round();
928 let x1 =
929 (self.window_padding + self.content_offset_x + x_offset + char_w)
930 .round();
931 let y0 = (self.window_padding
932 + self.content_offset_y
933 + row as f32 * self.cell_height)
934 .round();
935 let y1 = (self.window_padding
936 + self.content_offset_y
937 + (row + 1) as f32 * self.cell_height)
938 .round();
939
940 let cell_w = x1 - x0;
941 let cell_h = y1 - y0;
942
943 let scale_x = cell_w / char_w;
944 let scale_y = cell_h / self.cell_height;
945
946 let baseline_offset = baseline_y_unrounded
953 - (self.window_padding
954 + self.content_offset_y
955 + row as f32 * self.cell_height);
956 let glyph_left = x0 + (info.bearing_x * scale_x).round();
957 let baseline_in_cell = (baseline_offset * scale_y).round();
958 let glyph_top = y0 + baseline_in_cell - info.bearing_y;
959
960 let render_w = info.width as f32 * scale_x;
961 let render_h = info.height as f32 * scale_y;
962
963 let (final_left, final_top, final_w, final_h) = if chars.len() == 1
967 && block_chars::should_snap_to_boundaries(char_type)
968 {
969 block_chars::snap_glyph_to_cell(
971 glyph_left, glyph_top, render_w, render_h, x0, y0, x1, y1, 3.0,
972 0.5,
973 )
974 } else {
975 (glyph_left, glyph_top, render_w, render_h)
976 };
977
978 row_text.push(TextInstance {
979 position: [
980 final_left / self.config.width as f32 * 2.0 - 1.0,
981 1.0 - (final_top / self.config.height as f32 * 2.0),
982 ],
983 size: [
984 final_w / self.config.width as f32 * 2.0,
985 final_h / self.config.height as f32 * 2.0,
986 ],
987 tex_offset: [info.x as f32 / 2048.0, info.y as f32 / 2048.0],
988 tex_size: [info.width as f32 / 2048.0, info.height as f32 / 2048.0],
989 color: render_fg_color,
990 is_colored: if info.is_colored { 1 } else { 0 },
991 });
992 }
993 }
994 x_offset += self.cell_width;
995 current_col += 1;
996 }
997
998 {
1000 let underline_thickness = (self.cell_height * 0.07).max(1.0).round();
1001 let tex_offset = [
1002 self.solid_pixel_offset.0 as f32 / 2048.0,
1003 self.solid_pixel_offset.1 as f32 / 2048.0,
1004 ];
1005 let tex_size = [1.0 / 2048.0, 1.0 / 2048.0];
1006 let y0 = self.window_padding
1007 + self.content_offset_y
1008 + (row + 1) as f32 * self.cell_height
1009 - underline_thickness;
1010 let ndc_y = 1.0 - (y0 / self.config.height as f32 * 2.0);
1011 let ndc_h = underline_thickness / self.config.height as f32 * 2.0;
1012 let is_stipple =
1013 self.link_underline_style == par_term_config::LinkUnderlineStyle::Stipple;
1014 let stipple_on = 2.0_f32;
1016 let stipple_off = 2.0_f32;
1017 let stipple_period = stipple_on + stipple_off;
1018
1019 for col_idx in 0..self.cols {
1020 let cell = &self.cells[start + col_idx];
1021 if !cell.underline || row_text.len() >= self.cols * 2 {
1022 continue;
1023 }
1024 let text_alpha = if self.keep_text_opaque {
1025 1.0
1026 } else {
1027 self.window_opacity
1028 };
1029 let fg = [
1030 cell.fg_color[0] as f32 / 255.0,
1031 cell.fg_color[1] as f32 / 255.0,
1032 cell.fg_color[2] as f32 / 255.0,
1033 text_alpha,
1034 ];
1035 let cell_x0 = self.window_padding
1036 + self.content_offset_x
1037 + col_idx as f32 * self.cell_width;
1038
1039 if is_stipple {
1040 let mut px = 0.0;
1042 while px < self.cell_width && row_text.len() < self.cols * 2 {
1043 let seg_w = stipple_on.min(self.cell_width - px);
1044 let x = cell_x0 + px;
1045 row_text.push(TextInstance {
1046 position: [x / self.config.width as f32 * 2.0 - 1.0, ndc_y],
1047 size: [seg_w / self.config.width as f32 * 2.0, ndc_h],
1048 tex_offset,
1049 tex_size,
1050 color: fg,
1051 is_colored: 0,
1052 });
1053 px += stipple_period;
1054 }
1055 } else {
1056 row_text.push(TextInstance {
1057 position: [cell_x0 / self.config.width as f32 * 2.0 - 1.0, ndc_y],
1058 size: [self.cell_width / self.config.width as f32 * 2.0, ndc_h],
1059 tex_offset,
1060 tex_size,
1061 color: fg,
1062 is_colored: 0,
1063 });
1064 }
1065 }
1066 }
1067
1068 let bg_start = row * self.cols;
1070 self.bg_instances[bg_start..bg_start + self.cols].copy_from_slice(&row_bg);
1071
1072 let text_start = row * self.cols * 2;
1073 for i in 0..(self.cols * 2) {
1075 self.text_instances[text_start + i].size = [0.0, 0.0];
1076 }
1077 let text_count = row_text.len().min(self.cols * 2);
1079 self.text_instances[text_start..text_start + text_count]
1080 .copy_from_slice(&row_text[..text_count]);
1081
1082 self.queue.write_buffer(
1084 &self.bg_instance_buffer,
1085 (bg_start * std::mem::size_of::<BackgroundInstance>()) as u64,
1086 bytemuck::cast_slice(&row_bg),
1087 );
1088 self.queue.write_buffer(
1089 &self.text_instance_buffer,
1090 (text_start * std::mem::size_of::<TextInstance>()) as u64,
1091 bytemuck::cast_slice(
1092 &self.text_instances[text_start..text_start + self.cols * 2],
1093 ),
1094 );
1095
1096 self.row_cache[row] = Some(RowCacheEntry {});
1097 self.dirty_rows[row] = false;
1098 }
1099 }
1100
1101 let base_overlay_index = self.cols * self.rows;
1104 let mut overlay_instances = vec![
1105 BackgroundInstance {
1106 position: [0.0, 0.0],
1107 size: [0.0, 0.0],
1108 color: [0.0, 0.0, 0.0, 0.0],
1109 };
1110 10
1111 ];
1112
1113 let cursor_visible = self.cursor_opacity > 0.0
1115 && !self.cursor_hidden_for_shader
1116 && (self.is_focused
1117 || self.unfocused_cursor_style != par_term_config::UnfocusedCursorStyle::Hidden);
1118
1119 let cursor_col = self.cursor_pos.0;
1121 let cursor_row = self.cursor_pos.1;
1122 let cursor_x0 =
1123 self.window_padding + self.content_offset_x + cursor_col as f32 * self.cell_width;
1124 let cursor_x1 = cursor_x0 + self.cell_width;
1125 let cursor_y0 =
1126 self.window_padding + self.content_offset_y + cursor_row as f32 * self.cell_height;
1127 let cursor_y1 = cursor_y0 + self.cell_height;
1128
1129 overlay_instances[0] = self.cursor_overlay.unwrap_or(BackgroundInstance {
1131 position: [0.0, 0.0],
1132 size: [0.0, 0.0],
1133 color: [0.0, 0.0, 0.0, 0.0],
1134 });
1135
1136 if cursor_visible && self.cursor_guide_enabled {
1138 let guide_x0 = self.window_padding + self.content_offset_x;
1139 let guide_x1 =
1140 self.config.width as f32 - self.window_padding - self.content_inset_right;
1141 overlay_instances[1] = BackgroundInstance {
1142 position: [
1143 guide_x0 / self.config.width as f32 * 2.0 - 1.0,
1144 1.0 - (cursor_y0 / self.config.height as f32 * 2.0),
1145 ],
1146 size: [
1147 (guide_x1 - guide_x0) / self.config.width as f32 * 2.0,
1148 (cursor_y1 - cursor_y0) / self.config.height as f32 * 2.0,
1149 ],
1150 color: self.cursor_guide_color,
1151 };
1152 }
1153
1154 if cursor_visible && self.cursor_shadow_enabled {
1156 let shadow_x0 = cursor_x0 + self.cursor_shadow_offset[0];
1157 let shadow_y0 = cursor_y0 + self.cursor_shadow_offset[1];
1158 overlay_instances[2] = BackgroundInstance {
1159 position: [
1160 shadow_x0 / self.config.width as f32 * 2.0 - 1.0,
1161 1.0 - (shadow_y0 / self.config.height as f32 * 2.0),
1162 ],
1163 size: [
1164 self.cell_width / self.config.width as f32 * 2.0,
1165 self.cell_height / self.config.height as f32 * 2.0,
1166 ],
1167 color: self.cursor_shadow_color,
1168 };
1169 }
1170
1171 if cursor_visible && self.cursor_boost > 0.0 {
1173 let glow_expand = 4.0 * self.scale_factor * self.cursor_boost; let glow_x0 = cursor_x0 - glow_expand;
1175 let glow_y0 = cursor_y0 - glow_expand;
1176 let glow_w = self.cell_width + glow_expand * 2.0;
1177 let glow_h = self.cell_height + glow_expand * 2.0;
1178 overlay_instances[3] = BackgroundInstance {
1179 position: [
1180 glow_x0 / self.config.width as f32 * 2.0 - 1.0,
1181 1.0 - (glow_y0 / self.config.height as f32 * 2.0),
1182 ],
1183 size: [
1184 glow_w / self.config.width as f32 * 2.0,
1185 glow_h / self.config.height as f32 * 2.0,
1186 ],
1187 color: [
1188 self.cursor_boost_color[0],
1189 self.cursor_boost_color[1],
1190 self.cursor_boost_color[2],
1191 self.cursor_boost * 0.3 * self.cursor_opacity, ],
1193 };
1194 }
1195
1196 let render_hollow = cursor_visible
1199 && !self.is_focused
1200 && self.unfocused_cursor_style == par_term_config::UnfocusedCursorStyle::Hollow;
1201
1202 if render_hollow {
1203 use par_term_emu_core_rust::cursor::CursorStyle;
1204 let is_block = matches!(
1205 self.cursor_style,
1206 CursorStyle::SteadyBlock | CursorStyle::BlinkingBlock
1207 );
1208
1209 if is_block {
1210 let border_width = 2.0; let color = [
1212 self.cursor_color[0],
1213 self.cursor_color[1],
1214 self.cursor_color[2],
1215 self.cursor_opacity,
1216 ];
1217
1218 overlay_instances[4] = BackgroundInstance {
1220 position: [
1221 cursor_x0 / self.config.width as f32 * 2.0 - 1.0,
1222 1.0 - (cursor_y0 / self.config.height as f32 * 2.0),
1223 ],
1224 size: [
1225 self.cell_width / self.config.width as f32 * 2.0,
1226 border_width / self.config.height as f32 * 2.0,
1227 ],
1228 color,
1229 };
1230
1231 overlay_instances[5] = BackgroundInstance {
1233 position: [
1234 cursor_x0 / self.config.width as f32 * 2.0 - 1.0,
1235 1.0 - ((cursor_y1 - border_width) / self.config.height as f32 * 2.0),
1236 ],
1237 size: [
1238 self.cell_width / self.config.width as f32 * 2.0,
1239 border_width / self.config.height as f32 * 2.0,
1240 ],
1241 color,
1242 };
1243
1244 overlay_instances[6] = BackgroundInstance {
1246 position: [
1247 cursor_x0 / self.config.width as f32 * 2.0 - 1.0,
1248 1.0 - ((cursor_y0 + border_width) / self.config.height as f32 * 2.0),
1249 ],
1250 size: [
1251 border_width / self.config.width as f32 * 2.0,
1252 (self.cell_height - border_width * 2.0) / self.config.height as f32 * 2.0,
1253 ],
1254 color,
1255 };
1256
1257 overlay_instances[7] = BackgroundInstance {
1259 position: [
1260 (cursor_x1 - border_width) / self.config.width as f32 * 2.0 - 1.0,
1261 1.0 - ((cursor_y0 + border_width) / self.config.height as f32 * 2.0),
1262 ],
1263 size: [
1264 border_width / self.config.width as f32 * 2.0,
1265 (self.cell_height - border_width * 2.0) / self.config.height as f32 * 2.0,
1266 ],
1267 color,
1268 };
1269 }
1270 }
1271
1272 for (i, instance) in overlay_instances.iter().enumerate() {
1274 self.bg_instances[base_overlay_index + i] = *instance;
1275 }
1276 self.queue.write_buffer(
1277 &self.bg_instance_buffer,
1278 (base_overlay_index * std::mem::size_of::<BackgroundInstance>()) as u64,
1279 bytemuck::cast_slice(&overlay_instances),
1280 );
1281
1282 let separator_base = self.cols * self.rows + 10;
1284 let mut separator_instances = vec![
1285 BackgroundInstance {
1286 position: [0.0, 0.0],
1287 size: [0.0, 0.0],
1288 color: [0.0, 0.0, 0.0, 0.0],
1289 };
1290 self.rows
1291 ];
1292
1293 if self.command_separator_enabled {
1294 let width_f = self.config.width as f32;
1295 let height_f = self.config.height as f32;
1296 for &(screen_row, exit_code, custom_color) in &self.visible_separator_marks {
1297 if screen_row < self.rows {
1298 let x0 = self.window_padding + self.content_offset_x;
1299 let x1 = width_f - self.window_padding - self.content_inset_right;
1300 let y0 = self.window_padding
1301 + self.content_offset_y
1302 + screen_row as f32 * self.cell_height;
1303 let color = self.separator_color(exit_code, custom_color, 1.0);
1304 separator_instances[screen_row] = BackgroundInstance {
1305 position: [x0 / width_f * 2.0 - 1.0, 1.0 - (y0 / height_f * 2.0)],
1306 size: [
1307 (x1 - x0) / width_f * 2.0,
1308 self.command_separator_thickness / height_f * 2.0,
1309 ],
1310 color,
1311 };
1312 }
1313 }
1314 }
1315
1316 for (i, instance) in separator_instances.iter().enumerate() {
1317 if separator_base + i < self.max_bg_instances {
1318 self.bg_instances[separator_base + i] = *instance;
1319 }
1320 }
1321 let separator_byte_offset = separator_base * std::mem::size_of::<BackgroundInstance>();
1322 let separator_byte_count =
1323 separator_instances.len() * std::mem::size_of::<BackgroundInstance>();
1324 if separator_byte_offset + separator_byte_count
1325 <= self.max_bg_instances * std::mem::size_of::<BackgroundInstance>()
1326 {
1327 self.queue.write_buffer(
1328 &self.bg_instance_buffer,
1329 separator_byte_offset as u64,
1330 bytemuck::cast_slice(&separator_instances),
1331 );
1332 }
1333
1334 Ok(())
1335 }
1336
1337 #[allow(dead_code, clippy::too_many_arguments)]
1355 pub fn render_pane_to_view(
1356 &mut self,
1357 surface_view: &wgpu::TextureView,
1358 viewport: &PaneViewport,
1359 cells: &[Cell],
1360 cols: usize,
1361 rows: usize,
1362 cursor_pos: Option<(usize, usize)>,
1363 cursor_opacity: f32,
1364 show_scrollbar: bool,
1365 clear_first: bool,
1366 skip_background_image: bool,
1367 separator_marks: &[SeparatorMark],
1368 pane_background: Option<&par_term_config::PaneBackground>,
1369 ) -> Result<()> {
1370 self.build_pane_instance_buffers(
1373 viewport,
1374 cells,
1375 cols,
1376 rows,
1377 cursor_pos,
1378 cursor_opacity,
1379 skip_background_image,
1380 separator_marks,
1381 )?;
1382
1383 let pane_bg_resources = if let Some(pane_bg) = pane_background
1387 && let Some(ref path) = pane_bg.image_path
1388 {
1389 self.pane_bg_cache.get(path.as_str()).map(|entry| {
1390 self.create_pane_bg_bind_group(
1391 entry,
1392 viewport.x,
1393 viewport.y,
1394 viewport.width,
1395 viewport.height,
1396 pane_bg.mode,
1397 pane_bg.opacity,
1398 pane_bg.darken,
1399 )
1400 })
1401 } else {
1402 None
1403 };
1404
1405 let mut encoder = self
1406 .device
1407 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1408 label: Some("pane render encoder"),
1409 });
1410
1411 let load_op = if clear_first {
1413 let clear_color = if self.bg_is_solid_color {
1414 wgpu::Color {
1415 r: self.solid_bg_color[0] as f64
1416 * self.window_opacity as f64
1417 * viewport.opacity as f64,
1418 g: self.solid_bg_color[1] as f64
1419 * self.window_opacity as f64
1420 * viewport.opacity as f64,
1421 b: self.solid_bg_color[2] as f64
1422 * self.window_opacity as f64
1423 * viewport.opacity as f64,
1424 a: self.window_opacity as f64 * viewport.opacity as f64,
1425 }
1426 } else {
1427 wgpu::Color {
1428 r: self.background_color[0] as f64
1429 * self.window_opacity as f64
1430 * viewport.opacity as f64,
1431 g: self.background_color[1] as f64
1432 * self.window_opacity as f64
1433 * viewport.opacity as f64,
1434 b: self.background_color[2] as f64
1435 * self.window_opacity as f64
1436 * viewport.opacity as f64,
1437 a: self.window_opacity as f64 * viewport.opacity as f64,
1438 }
1439 };
1440 wgpu::LoadOp::Clear(clear_color)
1441 } else {
1442 wgpu::LoadOp::Load
1443 };
1444
1445 {
1446 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1447 label: Some("pane render pass"),
1448 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1449 view: surface_view,
1450 resolve_target: None,
1451 ops: wgpu::Operations {
1452 load: load_op,
1453 store: wgpu::StoreOp::Store,
1454 },
1455 depth_slice: None,
1456 })],
1457 depth_stencil_attachment: None,
1458 timestamp_writes: None,
1459 occlusion_query_set: None,
1460 });
1461
1462 let (sx, sy, sw, sh) = viewport.to_scissor_rect();
1464 render_pass.set_scissor_rect(sx, sy, sw, sh);
1465
1466 if let Some((ref bind_group, ref _buf)) = pane_bg_resources {
1470 render_pass.set_pipeline(&self.bg_image_pipeline);
1471 render_pass.set_bind_group(0, bind_group, &[]);
1472 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1473 render_pass.draw(0..4, 0..1);
1474 }
1475
1476 render_pass.set_pipeline(&self.bg_pipeline);
1478 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1479 render_pass.set_vertex_buffer(1, self.bg_instance_buffer.slice(..));
1480 render_pass.draw(0..4, 0..self.max_bg_instances as u32);
1481
1482 render_pass.set_pipeline(&self.text_pipeline);
1484 render_pass.set_bind_group(0, &self.text_bind_group, &[]);
1485 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
1486 render_pass.set_vertex_buffer(1, self.text_instance_buffer.slice(..));
1487 render_pass.draw(0..4, 0..self.max_text_instances as u32);
1488
1489 if show_scrollbar {
1491 render_pass.set_scissor_rect(0, 0, self.config.width, self.config.height);
1493 self.scrollbar.render(&mut render_pass);
1494 }
1495 }
1496
1497 self.queue.submit(std::iter::once(encoder.finish()));
1498 Ok(())
1499 }
1500
1501 #[allow(clippy::too_many_arguments)]
1510 fn build_pane_instance_buffers(
1511 &mut self,
1512 viewport: &PaneViewport,
1513 cells: &[Cell],
1514 cols: usize,
1515 rows: usize,
1516 cursor_pos: Option<(usize, usize)>,
1517 cursor_opacity: f32,
1518 skip_solid_background: bool,
1519 separator_marks: &[SeparatorMark],
1520 ) -> Result<()> {
1521 let _shaping_options = ShapingOptions {
1522 enable_ligatures: self.enable_ligatures,
1523 enable_kerning: self.enable_kerning,
1524 ..Default::default()
1525 };
1526
1527 for instance in &mut self.bg_instances {
1529 instance.size = [0.0, 0.0];
1530 instance.color = [0.0, 0.0, 0.0, 0.0];
1531 }
1532
1533 let bg_start_index = if !skip_solid_background && !self.bg_instances.is_empty() {
1537 let bg_color = self.background_color;
1538 let opacity = self.window_opacity * viewport.opacity;
1539 self.bg_instances[0] = super::types::BackgroundInstance {
1540 position: [viewport.x, viewport.y],
1541 size: [viewport.width, viewport.height],
1542 color: [
1543 bg_color[0] * opacity,
1544 bg_color[1] * opacity,
1545 bg_color[2] * opacity,
1546 opacity,
1547 ],
1548 };
1549 1 } else {
1551 0 };
1553
1554 for instance in &mut self.text_instances {
1555 instance.size = [0.0, 0.0];
1556 }
1557
1558 let mut bg_index = bg_start_index;
1560 let mut text_index = 0;
1561
1562 let (content_x, content_y) = viewport.content_origin();
1564 let opacity_multiplier = viewport.opacity;
1565
1566 for row in 0..rows {
1567 let row_start = row * cols;
1568 let row_end = (row + 1) * cols;
1569 if row_start >= cells.len() {
1570 break;
1571 }
1572 let row_cells = &cells[row_start..row_end.min(cells.len())];
1573
1574 let mut col = 0;
1576 while col < row_cells.len() {
1577 let cell = &row_cells[col];
1578 let is_default_bg = (cell.bg_color[0] as f32 / 255.0 - self.background_color[0])
1579 .abs()
1580 < 0.001
1581 && (cell.bg_color[1] as f32 / 255.0 - self.background_color[1]).abs() < 0.001
1582 && (cell.bg_color[2] as f32 / 255.0 - self.background_color[2]).abs() < 0.001;
1583
1584 let has_cursor = cursor_pos.is_some_and(|(cx, cy)| cx == col && cy == row)
1586 && cursor_opacity > 0.0
1587 && !self.cursor_hidden_for_shader;
1588
1589 if is_default_bg && !has_cursor {
1590 col += 1;
1591 continue;
1592 }
1593
1594 let bg_alpha =
1596 if self.transparency_affects_only_default_background && !is_default_bg {
1597 1.0
1598 } else {
1599 self.window_opacity
1600 };
1601 let pane_alpha = bg_alpha * opacity_multiplier;
1602 let mut bg_color = [
1603 cell.bg_color[0] as f32 / 255.0,
1604 cell.bg_color[1] as f32 / 255.0,
1605 cell.bg_color[2] as f32 / 255.0,
1606 pane_alpha,
1607 ];
1608
1609 if has_cursor {
1611 use par_term_emu_core_rust::cursor::CursorStyle;
1612 match self.cursor_style {
1613 CursorStyle::SteadyBlock | CursorStyle::BlinkingBlock => {
1614 for (bg, &cursor) in bg_color.iter_mut().take(3).zip(&self.cursor_color)
1615 {
1616 *bg = *bg * (1.0 - cursor_opacity) + cursor * cursor_opacity;
1617 }
1618 bg_color[3] = bg_color[3].max(cursor_opacity * opacity_multiplier);
1619 }
1620 _ => {}
1621 }
1622
1623 let x0 = content_x + col as f32 * self.cell_width;
1625 let y0 = content_y + row as f32 * self.cell_height;
1626 let x1 = x0 + self.cell_width;
1627 let y1 = y0 + self.cell_height;
1628
1629 if bg_index < self.max_bg_instances {
1630 self.bg_instances[bg_index] = BackgroundInstance {
1631 position: [
1632 x0 / self.config.width as f32 * 2.0 - 1.0,
1633 1.0 - (y0 / self.config.height as f32 * 2.0),
1634 ],
1635 size: [
1636 (x1 - x0) / self.config.width as f32 * 2.0,
1637 (y1 - y0) / self.config.height as f32 * 2.0,
1638 ],
1639 color: bg_color,
1640 };
1641 bg_index += 1;
1642 }
1643 col += 1;
1644 continue;
1645 }
1646
1647 let start_col = col;
1649 let run_color = cell.bg_color;
1650 col += 1;
1651 while col < row_cells.len() {
1652 let next_cell = &row_cells[col];
1653 let next_has_cursor = cursor_pos.is_some_and(|(cx, cy)| cx == col && cy == row)
1654 && cursor_opacity > 0.0;
1655 if next_cell.bg_color != run_color || next_has_cursor {
1656 break;
1657 }
1658 col += 1;
1659 }
1660 let run_length = col - start_col;
1661
1662 let x0 = content_x + start_col as f32 * self.cell_width;
1664 let x1 = content_x + (start_col + run_length) as f32 * self.cell_width;
1665 let y0 = content_y + row as f32 * self.cell_height;
1666 let y1 = y0 + self.cell_height;
1667
1668 if bg_index < self.max_bg_instances {
1669 self.bg_instances[bg_index] = BackgroundInstance {
1670 position: [
1671 x0 / self.config.width as f32 * 2.0 - 1.0,
1672 1.0 - (y0 / self.config.height as f32 * 2.0),
1673 ],
1674 size: [
1675 (x1 - x0) / self.config.width as f32 * 2.0,
1676 (y1 - y0) / self.config.height as f32 * 2.0,
1677 ],
1678 color: bg_color,
1679 };
1680 bg_index += 1;
1681 }
1682 }
1683
1684 let natural_line_height = self.font_ascent + self.font_descent + self.font_leading;
1686 let vertical_padding = (self.cell_height - natural_line_height).max(0.0) / 2.0;
1687 let baseline_y =
1688 content_y + (row as f32 * self.cell_height) + vertical_padding + self.font_ascent;
1689
1690 let text_alpha = if self.keep_text_opaque {
1692 opacity_multiplier } else {
1694 self.window_opacity * opacity_multiplier
1695 };
1696
1697 for (col_idx, cell) in row_cells.iter().enumerate() {
1698 if cell.wide_char_spacer || cell.grapheme == " " {
1699 continue;
1700 }
1701
1702 let chars: Vec<char> = cell.grapheme.chars().collect();
1703 if chars.is_empty() {
1704 continue;
1705 }
1706
1707 let ch = chars[0];
1708
1709 let char_type = block_chars::classify_char(ch);
1711 if chars.len() == 1 && block_chars::should_render_geometrically(char_type) {
1712 let char_w = if cell.wide_char {
1713 self.cell_width * 2.0
1714 } else {
1715 self.cell_width
1716 };
1717 let x0 = content_x + col_idx as f32 * self.cell_width;
1718 let y0 = content_y + row as f32 * self.cell_height;
1719
1720 let fg_color = [
1721 cell.fg_color[0] as f32 / 255.0,
1722 cell.fg_color[1] as f32 / 255.0,
1723 cell.fg_color[2] as f32 / 255.0,
1724 text_alpha,
1725 ];
1726
1727 let aspect_ratio = self.cell_height / char_w;
1729 if let Some(box_geo) = block_chars::get_box_drawing_geometry(ch, aspect_ratio) {
1730 for segment in &box_geo.segments {
1731 let rect = segment.to_pixel_rect(x0, y0, char_w, self.cell_height);
1732
1733 let extension = 1.0;
1735 let ext_x = if segment.x <= 0.01 { extension } else { 0.0 };
1736 let ext_y = if segment.y <= 0.01 { extension } else { 0.0 };
1737 let ext_w = if segment.x + segment.width >= 0.99 {
1738 extension
1739 } else {
1740 0.0
1741 };
1742 let ext_h = if segment.y + segment.height >= 0.99 {
1743 extension
1744 } else {
1745 0.0
1746 };
1747
1748 let final_x = rect.x - ext_x;
1749 let final_y = rect.y - ext_y;
1750 let final_w = rect.width + ext_x + ext_w;
1751 let final_h = rect.height + ext_y + ext_h;
1752
1753 if text_index < self.max_text_instances {
1754 self.text_instances[text_index] = TextInstance {
1755 position: [
1756 final_x / self.config.width as f32 * 2.0 - 1.0,
1757 1.0 - (final_y / self.config.height as f32 * 2.0),
1758 ],
1759 size: [
1760 final_w / self.config.width as f32 * 2.0,
1761 final_h / self.config.height as f32 * 2.0,
1762 ],
1763 tex_offset: [
1764 self.solid_pixel_offset.0 as f32 / 2048.0,
1765 self.solid_pixel_offset.1 as f32 / 2048.0,
1766 ],
1767 tex_size: [1.0 / 2048.0, 1.0 / 2048.0],
1768 color: fg_color,
1769 is_colored: 0,
1770 };
1771 text_index += 1;
1772 }
1773 }
1774 continue;
1775 }
1776
1777 if let Some(geo_block) = block_chars::get_geometric_block(ch) {
1779 let rect = geo_block.to_pixel_rect(x0, y0, char_w, self.cell_height);
1780
1781 let extension = 1.0;
1783 let ext_x = if geo_block.x == 0.0 { extension } else { 0.0 };
1784 let ext_y = if geo_block.y == 0.0 { extension } else { 0.0 };
1785 let ext_w = if geo_block.x + geo_block.width >= 1.0 {
1786 extension
1787 } else {
1788 0.0
1789 };
1790 let ext_h = if geo_block.y + geo_block.height >= 1.0 {
1791 extension
1792 } else {
1793 0.0
1794 };
1795
1796 let final_x = rect.x - ext_x;
1797 let final_y = rect.y - ext_y;
1798 let final_w = rect.width + ext_x + ext_w;
1799 let final_h = rect.height + ext_y + ext_h;
1800
1801 if text_index < self.max_text_instances {
1802 self.text_instances[text_index] = TextInstance {
1803 position: [
1804 final_x / self.config.width as f32 * 2.0 - 1.0,
1805 1.0 - (final_y / self.config.height as f32 * 2.0),
1806 ],
1807 size: [
1808 final_w / self.config.width as f32 * 2.0,
1809 final_h / self.config.height as f32 * 2.0,
1810 ],
1811 tex_offset: [
1812 self.solid_pixel_offset.0 as f32 / 2048.0,
1813 self.solid_pixel_offset.1 as f32 / 2048.0,
1814 ],
1815 tex_size: [1.0 / 2048.0, 1.0 / 2048.0],
1816 color: fg_color,
1817 is_colored: 0,
1818 };
1819 text_index += 1;
1820 }
1821 continue;
1822 }
1823 }
1824
1825 let glyph_result = if chars.len() > 1 {
1827 self.font_manager
1828 .find_grapheme_glyph(&cell.grapheme, cell.bold, cell.italic)
1829 } else {
1830 self.font_manager.find_glyph(ch, cell.bold, cell.italic)
1831 };
1832
1833 if let Some((font_idx, glyph_id)) = glyph_result {
1834 let cache_key = ((font_idx as u64) << 32) | (glyph_id as u64);
1835 let force_monochrome =
1838 chars.len() == 1 && super::atlas::should_render_as_symbol(ch);
1839 let info = if self.glyph_cache.contains_key(&cache_key) {
1840 self.lru_remove(cache_key);
1841 self.lru_push_front(cache_key);
1842 self.glyph_cache.get(&cache_key).unwrap().clone()
1843 } else if let Some(raster) =
1844 self.rasterize_glyph(font_idx, glyph_id, force_monochrome)
1845 {
1846 let info = self.upload_glyph(cache_key, &raster);
1847 self.glyph_cache.insert(cache_key, info.clone());
1848 self.lru_push_front(cache_key);
1849 info
1850 } else {
1851 continue;
1852 };
1853
1854 let char_w = if cell.wide_char {
1855 self.cell_width * 2.0
1856 } else {
1857 self.cell_width
1858 };
1859 let x0 = content_x + col_idx as f32 * self.cell_width;
1860 let y0 = content_y + row as f32 * self.cell_height;
1861 let x1 = x0 + char_w;
1862 let y1 = y0 + self.cell_height;
1863
1864 let cell_w = x1 - x0;
1865 let cell_h = y1 - y0;
1866 let scale_x = cell_w / char_w;
1867 let scale_y = cell_h / self.cell_height;
1868
1869 let baseline_offset = baseline_y - (content_y + row as f32 * self.cell_height);
1870 let glyph_left = x0 + (info.bearing_x * scale_x).round();
1871 let baseline_in_cell = (baseline_offset * scale_y).round();
1872 let glyph_top = y0 + baseline_in_cell - info.bearing_y;
1873
1874 let render_w = info.width as f32 * scale_x;
1875 let render_h = info.height as f32 * scale_y;
1876
1877 let (final_left, final_top, final_w, final_h) =
1878 if chars.len() == 1 && block_chars::should_snap_to_boundaries(char_type) {
1879 block_chars::snap_glyph_to_cell(
1880 glyph_left, glyph_top, render_w, render_h, x0, y0, x1, y1, 3.0, 0.5,
1881 )
1882 } else {
1883 (glyph_left, glyph_top, render_w, render_h)
1884 };
1885
1886 let fg_color = [
1887 cell.fg_color[0] as f32 / 255.0,
1888 cell.fg_color[1] as f32 / 255.0,
1889 cell.fg_color[2] as f32 / 255.0,
1890 text_alpha,
1891 ];
1892
1893 if text_index < self.max_text_instances {
1894 self.text_instances[text_index] = TextInstance {
1895 position: [
1896 final_left / self.config.width as f32 * 2.0 - 1.0,
1897 1.0 - (final_top / self.config.height as f32 * 2.0),
1898 ],
1899 size: [
1900 final_w / self.config.width as f32 * 2.0,
1901 final_h / self.config.height as f32 * 2.0,
1902 ],
1903 tex_offset: [info.x as f32 / 2048.0, info.y as f32 / 2048.0],
1904 tex_size: [info.width as f32 / 2048.0, info.height as f32 / 2048.0],
1905 color: fg_color,
1906 is_colored: if info.is_colored { 1 } else { 0 },
1907 };
1908 text_index += 1;
1909 }
1910 }
1911 }
1912 }
1913
1914 if self.command_separator_enabled && !separator_marks.is_empty() {
1916 let width_f = self.config.width as f32;
1917 let height_f = self.config.height as f32;
1918 let opacity_multiplier = viewport.opacity;
1919 for &(screen_row, exit_code, custom_color) in separator_marks {
1920 if screen_row < rows && bg_index < self.max_bg_instances {
1921 let x0 = content_x;
1922 let x1 = content_x + cols as f32 * self.cell_width;
1923 let y0 = content_y + screen_row as f32 * self.cell_height;
1924 let color = self.separator_color(exit_code, custom_color, opacity_multiplier);
1925 self.bg_instances[bg_index] = BackgroundInstance {
1926 position: [x0 / width_f * 2.0 - 1.0, 1.0 - (y0 / height_f * 2.0)],
1927 size: [
1928 (x1 - x0) / width_f * 2.0,
1929 self.command_separator_thickness / height_f * 2.0,
1930 ],
1931 color,
1932 };
1933 bg_index += 1;
1934 }
1935 }
1936 }
1937 let _ = bg_index; self.queue.write_buffer(
1941 &self.bg_instance_buffer,
1942 0,
1943 bytemuck::cast_slice(&self.bg_instances),
1944 );
1945 self.queue.write_buffer(
1946 &self.text_instance_buffer,
1947 0,
1948 bytemuck::cast_slice(&self.text_instances),
1949 );
1950
1951 Ok(())
1952 }
1953}