diff --git a/src/app/rendering/terminal_text/mod.rs b/src/app/rendering/terminal_text/mod.rs index 0f6f8d8..04b8027 100644 --- a/src/app/rendering/terminal_text/mod.rs +++ b/src/app/rendering/terminal_text/mod.rs @@ -16,6 +16,8 @@ pub struct TerminalText { cursor: Cursor, } +mod safe_casts; + use log::{debug, trace}; impl TerminalText { @@ -55,10 +57,11 @@ impl TerminalText { pub fn resize(&mut self, width: u32, height: u32) { trace!("Resizing window - Width: {width} Height: {height}"); + self.buffer.set_size( &mut self.font_system, - Some(width as f32), - Some(height as f32), + Some(safe_casts::u32_to_f32_or_max(width)), + Some(safe_casts::u32_to_f32_or_max(height)), ); // Update the buffer's wrapping based on the new width self.buffer.set_wrap(&mut self.font_system, Wrap::Glyph); @@ -68,6 +71,14 @@ impl TerminalText { } fn merge_dirty_regions(&self) -> TextBounds { + if self.dirty_regions.is_empty() { + return TextBounds { + left: 0, + top: 0, + right: 0, + bottom: 0, + }; + } TextBounds { left: self .dirty_regions @@ -101,29 +112,27 @@ impl TerminalText { }, ); - if !self.dirty_regions.is_empty() { - let region = self.merge_dirty_regions(); - trace!("Preparing region {:?}", region); - self.renderer - .prepare( - device, - queue, - &mut self.font_system, - &mut self.atlas, - &self.viewport, - [TextArea { - buffer: &self.buffer, - left: 0.0, - top: 0.0, - scale: 1.0, - bounds: region, - default_color: Color::rgb(255, 255, 255), - custom_glyphs: &[], - }], - &mut self.cache, - ) - .unwrap(); - } + let region = self.merge_dirty_regions(); + trace!("Preparing region {:?}", region); + self.renderer + .prepare( + device, + queue, + &mut self.font_system, + &mut self.atlas, + &self.viewport, + [TextArea { + buffer: &self.buffer, + left: 0.0, + top: 0.0, + scale: 1.0, + bounds: region, + default_color: Color::rgb(255, 255, 255), + custom_glyphs: &[], + }], + &mut self.cache, + ) + .unwrap(); self.dirty_regions.clear(); } @@ -235,28 +244,15 @@ impl TerminalText { } // Calculate bounds based on visual line positions - let top_f32 = (top_line as f32 * line_height).floor(); - let bottom_f32 = (bottom_line as f32 * line_height).ceil(); + let top_f32 = (safe_casts::usize_to_f32_or_max(top_line) * line_height).floor(); + let bottom_f32 = (safe_casts::usize_to_f32_or_max(bottom_line) * line_height).ceil(); // Safe conversions with overflow checks - let top = if top_f32 > i32::MAX as f32 { - i32::MAX - } else if top_f32 < i32::MIN as f32 { - i32::MIN - } else { - top_f32 as i32 - }; - - let bottom = if bottom_f32 > i32::MAX as f32 { - i32::MAX - } else if bottom_f32 < i32::MIN as f32 { - i32::MIN - } else { - bottom_f32 as i32 - }; + let top = safe_casts::f32_to_i32_or_bound(top_f32); + let bottom = safe_casts::f32_to_i32_or_bound(bottom_f32); // Ensure viewport width doesn't exceed i32::MAX - let right = viewport_width.min(i32::MAX as u32) as i32; + let right = safe_casts::u32_to_i32_or_max(viewport_width.min(i32::MAX as u32)); TextBounds { left: 0, @@ -267,7 +263,7 @@ impl TerminalText { } fn get_visible_line_range(&self) -> (usize, usize) { - let viewport_height = self.viewport.resolution().height as f32; + let viewport_height = safe_casts::u32_to_f32_or_max(self.viewport.resolution().height); let line_height = self.buffer.metrics().line_height; // Start from line 0 (no scrolling yet) @@ -282,7 +278,9 @@ impl TerminalText { last_logical_line = run.line_i; // If we've exceeded the viewport height, we can stop counting - if ((start_line + visual_line_count) as f32 * line_height) > viewport_height { + if (safe_casts::usize_to_f32_or_max(start_line + visual_line_count) * line_height) + > viewport_height + { break; } } diff --git a/src/app/rendering/terminal_text/safe_casts.rs b/src/app/rendering/terminal_text/safe_casts.rs new file mode 100644 index 0000000..424d396 --- /dev/null +++ b/src/app/rendering/terminal_text/safe_casts.rs @@ -0,0 +1,57 @@ +use log::trace; + +// Safe conversions with overflow checks + +pub fn u32_to_i32_or_max(n: u32) -> i32 { + if n > i32::MAX as u32 { + trace!( + "Overflow casting {n}::u32 as i32, defaulting to {}", + i32::MAX + ); + i32::MAX + } else { + n as i32 + } +} + +pub fn u32_to_f32_or_max(n: u32) -> f32 { + if n > f32::MAX as u32 { + trace!( + "Overflow casting {n}::u32 as f32, defaulting to {}", + f32::MAX + ); + f32::MAX + } else { + n as f32 + } +} + +pub fn usize_to_f32_or_max(n: usize) -> f32 { + if n > f32::MAX as usize { + trace!( + "Overflow casting {n}::usize as f32, defaulting to {}", + f32::MAX + ); + f32::MAX + } else { + n as f32 + } +} + +pub fn f32_to_i32_or_bound(n: f32) -> i32 { + if n > i32::MAX as f32 { + trace!( + "Overflow casting {n}::f32 as i32, defaulting to {}", + i32::MAX + ); + i32::MAX + } else if n < i32::MIN as f32 { + trace!( + "Underflow casting {n}::f32 as i32, defaulting to {}", + i32::MIN + ); + i32::MIN + } else { + n as i32 + } +}