From abdfff92ca18f7258ecb7e5bcc0381d29509b272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Ml=C3=A1dek?= Date: Sat, 24 Jan 2026 22:39:09 +0100 Subject: [PATCH] feat: add logging, fix text color --- src/app.rs | 56 ++++++++++++++++++++++++++++++++++++----------- src/svg.rs | 23 ++++++++++++++++--- src/text_cache.rs | 27 ++++++++++++++++++----- 3 files changed, 84 insertions(+), 22 deletions(-) diff --git a/src/app.rs b/src/app.rs index 7a56ae7..f32ff92 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,5 +1,5 @@ use crate::svg::{Renderable as _, SvgContent}; -use crate::text_cache::{TextCache, RENDER_SCALE}; +use crate::text_cache::{RENDER_SCALE, TextCache}; /// We derive Deserialize/Serialize so we can persist app state on shutdown. #[derive(serde::Deserialize, serde::Serialize)] @@ -58,8 +58,12 @@ impl Default for TemplateApp { impl TemplateApp { /// Called once before the first frame. pub fn new(cc: &eframe::CreationContext<'_>) -> Self { + log::info!("Initializing application..."); + + log::debug!("Installing image loaders..."); egui_extras::install_image_loaders(&cc.egui_ctx); + log::debug!("Loading app state..."); let mut app: Self = cc .storage .and_then(|s| eframe::get_value(s, eframe::APP_KEY)) @@ -67,18 +71,24 @@ impl TemplateApp { // Load SVG content let svg_path = "../line-and-surface/content/intro.svg"; + log::info!("Loading SVG from: {svg_path}"); + let start = std::time::Instant::now(); + match SvgContent::from_file(svg_path) { Ok(content) => { + let elapsed = start.elapsed(); log::info!( - "Loaded SVG: {} video scrolls, {} audio areas, {} anchors, {} texts", + "Loaded SVG in {:.2?}: {} video scrolls, {} audio areas, {} anchors, {} texts", + elapsed, content.video_scrolls.len(), content.audio_areas.len(), content.anchors.len(), content.texts.len() ); - if let Some((min_x, min_y, _, _)) = content.viewbox { - app.pan_x = -min_x; - app.pan_y = -min_y; + if let Some((vb_x, vb_y, vb_w, vb_h)) = content.viewbox { + log::debug!("SVG viewbox: ({vb_x}, {vb_y}, {vb_w}, {vb_h})"); + app.pan_x = -vb_x; + app.pan_y = -vb_y; } app.svg_content = Some(content); } @@ -87,6 +97,7 @@ impl TemplateApp { } } + log::info!("Application initialized"); app } @@ -258,7 +269,8 @@ impl eframe::App for TemplateApp { // Video scrolls for vs in &content.video_scrolls { let (x, y, w, h) = vs.bounds(); - let rect = egui::Rect::from_min_max(svg_to_screen(x, y), svg_to_screen(x + w, y + h)); + let rect = + egui::Rect::from_min_max(svg_to_screen(x, y), svg_to_screen(x + w, y + h)); if rect.intersects(canvas_rect) { painter.rect_filled(rect, 0.0, video_scroll_color); rendered_count += 1; @@ -268,7 +280,8 @@ impl eframe::App for TemplateApp { // Audio areas for aa in &content.audio_areas { let (x, y, w, h) = aa.bounds(); - let rect = egui::Rect::from_min_max(svg_to_screen(x, y), svg_to_screen(x + w, y + h)); + let rect = + egui::Rect::from_min_max(svg_to_screen(x, y), svg_to_screen(x + w, y + h)); if rect.intersects(canvas_rect) { painter.rect_filled(rect, (w.min(h) * zoom) / 2.0, audio_area_color); rendered_count += 1; @@ -278,7 +291,8 @@ impl eframe::App for TemplateApp { // Anchors for anchor in &content.anchors { let (x, y, w, h) = anchor.bounds(); - let rect = egui::Rect::from_min_max(svg_to_screen(x, y), svg_to_screen(x + w, y + h)); + let rect = + egui::Rect::from_min_max(svg_to_screen(x, y), svg_to_screen(x + w, y + h)); if rect.intersects(canvas_rect) { painter.rect_filled(rect, 0.0, anchor_color); rendered_count += 1; @@ -288,7 +302,8 @@ impl eframe::App for TemplateApp { // Text elements for text_elem in &content.texts { let (x, y, w, h) = text_elem.bounds(); - let rect = egui::Rect::from_min_max(svg_to_screen(x, y), svg_to_screen(x + w, y + h)); + let rect = + egui::Rect::from_min_max(svg_to_screen(x, y), svg_to_screen(x + w, y + h)); if !rect.intersects(canvas_rect) { continue; @@ -301,7 +316,8 @@ impl eframe::App for TemplateApp { continue; } - let cached = text_cache.get_or_create(ctx, &line.content, text_elem.font_size); + let cached = + text_cache.get_or_create(ctx, &line.content, text_elem.font_size); let scale_factor = zoom / RENDER_SCALE; let display_width = cached.width as f32 * scale_factor; let display_height = cached.height as f32 * scale_factor; @@ -311,8 +327,14 @@ impl eframe::App for TemplateApp { painter.image( cached.texture.id(), - egui::Rect::from_min_size(pos, egui::vec2(display_width, display_height)), - egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)), + egui::Rect::from_min_size( + pos, + egui::vec2(display_width, display_height), + ), + egui::Rect::from_min_max( + egui::pos2(0.0, 0.0), + egui::pos2(1.0, 1.0), + ), text_color, // Tint the white texture with desired color ); } @@ -357,7 +379,15 @@ impl eframe::App for TemplateApp { + content.texts.len(); ui.label(format!("Total elements: {total}")); ui.label(format!("Rendered: {rendered_count}")); - ui.label(format!("Culled: {}", total.saturating_sub(rendered_count as usize))); + ui.label(format!( + "Culled: {}", + total.saturating_sub(rendered_count as usize) + )); + } + + if let Some(ref cache) = self.text_cache { + ui.separator(); + ui.label(format!("Text cache: {} entries", cache.cache_size())); } }); } diff --git a/src/svg.rs b/src/svg.rs index d15740c..5fbfe79 100644 --- a/src/svg.rs +++ b/src/svg.rs @@ -1,7 +1,7 @@ //! SVG parsing module for extracting special elements from SVG files. -use quick_xml::events::Event; use quick_xml::Reader; +use quick_xml::events::Event; use std::fs; /// Trait for elements that can be rendered with a bounding box. @@ -95,7 +95,11 @@ impl Renderable for TextElement { .iter() .map(|l| l.x + l.content.len() as f32 * self.font_size * 0.6) .fold(f32::NEG_INFINITY, f32::max); - let max_y = self.lines.iter().map(|l| l.y).fold(f32::NEG_INFINITY, f32::max); + let max_y = self + .lines + .iter() + .map(|l| l.y) + .fold(f32::NEG_INFINITY, f32::max); (min_x, min_y, max_x - min_x, max_y - min_y) } @@ -136,13 +140,16 @@ enum PendingElement { impl SvgContent { /// Parse an SVG file and extract special elements. pub fn from_file(path: &str) -> Result> { + log::debug!("Reading SVG file: {path}"); let content = fs::read_to_string(path)?; + log::debug!("SVG file size: {} bytes", content.len()); Self::parse(&content) } /// Parse SVG content from a string. #[expect(clippy::too_many_lines)] pub fn parse(content: &str) -> Result> { + log::debug!("Parsing SVG content ({} bytes)...", content.len()); let mut reader = Reader::from_str(content); reader.config_mut().trim_text(true); @@ -286,7 +293,9 @@ impl SvgContent { // Parse font-size from style attribute if let Some(size_start) = value.find("font-size:") { let size_str = &value[size_start + 10..]; - if let Some(size_end) = size_str.find(|c: char| !c.is_numeric() && c != '.') { + if let Some(size_end) = + size_str.find(|c: char| !c.is_numeric() && c != '.') + { if let Ok(size) = size_str[..size_end].parse::() { text_font_size = size; } @@ -444,6 +453,14 @@ impl SvgContent { buf.clear(); } + log::debug!( + "SVG parsing complete: {} video scrolls, {} audio areas, {} anchors, {} texts", + svg_content.video_scrolls.len(), + svg_content.audio_areas.len(), + svg_content.anchors.len(), + svg_content.texts.len() + ); + Ok(svg_content) } } diff --git a/src/text_cache.rs b/src/text_cache.rs index 8d873f5..b049070 100644 --- a/src/text_cache.rs +++ b/src/text_cache.rs @@ -44,8 +44,14 @@ impl TextCache { /// /// Text is rendered in white - apply color as a tint when drawing. pub fn new() -> Self { + log::debug!("Initializing text cache..."); let font_data: &'static [u8] = include_bytes!("../assets/NotoSans-Regular.ttf"); let font = FontRef::try_from_slice(font_data).expect("embedded font should be valid"); + log::info!( + "Text cache initialized (font: {} bytes, render scale: {}x)", + font_data.len(), + RENDER_SCALE + ); Self { font, @@ -53,6 +59,11 @@ impl TextCache { } } + /// Returns the number of cached text textures. + pub fn cache_size(&self) -> usize { + self.cache.len() + } + /// Get or create a cached texture for the given text. /// /// The texture is rendered at `RENDER_SCALE` times the nominal font size. @@ -115,7 +126,15 @@ impl TextCache { } // Render glyphs to pixel buffer - let pixels = Self::render_glyphs(text, &scaled_font, scale, ascent, padding, img_width, img_height); + let pixels = Self::render_glyphs( + text, + &scaled_font, + scale, + ascent, + padding, + img_width, + img_height, + ); // Create egui texture let image = ColorImage { @@ -223,11 +242,7 @@ impl TextCache { source_size: egui::Vec2::new(1.0, 1.0), }; - let texture = ctx.load_texture( - format!("text_empty_{size_key}"), - image, - TEXTURE_FILTER, - ); + let texture = ctx.load_texture(format!("text_empty_{size_key}"), image, TEXTURE_FILTER); CachedText { texture,