aboutsummaryrefslogtreecommitdiff
path: root/src/renderer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/renderer.rs')
-rw-r--r--src/renderer.rs158
1 files changed, 81 insertions, 77 deletions
diff --git a/src/renderer.rs b/src/renderer.rs
index 8ac025b..74f4708 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -5,7 +5,7 @@ use crate::configuration::Configuration;
use crate::stage_type::StageType;
use crate::utils::{percent_to_pixel, set_color};
use crate::RenderError;
-use cairo::{Context, Error, FontSlant, FontWeight, Format, ImageSurface, LineCap, LineJoin, Operator, SvgSurface};
+use cairo::{Context, Error, FontSlant, FontWeight, Format, ImageSurface, LineCap, LineJoin, SvgSurface};
/// Renders a map to PNG format
pub fn render_to_png(
@@ -86,10 +86,22 @@ fn render(
stage_type: StageType,
config: &Configuration,
) -> Result<(), RenderError> {
+ use std::collections::HashMap;
+
// Apply padding transform
ctx.save()?;
ctx.translate(config.theme.sizes.padding, config.theme.sizes.padding);
+ // Build component map once and reuse it
+ let component_map: HashMap<String, (f64, f64)> = map.components
+ .iter()
+ .map(|component| {
+ let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width);
+ let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height);
+ (component.label.to_lowercase(), (x, y))
+ })
+ .collect();
+
// Render in order (back to front)
draw_background(ctx, config)?;
@@ -99,12 +111,12 @@ fn render(
draw_axes(ctx, stage_type, map, config)?;
draw_stage_dividers(ctx, map, config)?;
- draw_dependencies(ctx, map, config)?;
- draw_groups(ctx, map, config)?;
+ draw_dependencies(ctx, map, config, &component_map)?;
+ draw_groups(ctx, map, config, &component_map)?;
draw_vertices(ctx, map, config)?;
- draw_vertex_labels(ctx, map, config)?;
- draw_inertias(ctx, map, config)?;
- draw_evolutions(ctx, map, config)?;
+ draw_vertex_labels(ctx, map, config, &component_map)?;
+ draw_inertias(ctx, map, config, &component_map)?;
+ draw_evolutions(ctx, map, config, &component_map)?;
draw_notes(ctx, map, config)?;
ctx.restore()?;
@@ -312,18 +324,10 @@ fn draw_dependencies(
ctx: &Context,
map: &Map,
config: &Configuration,
+ component_map: &std::collections::HashMap<String, (f64, f64)>,
) -> Result<(), RenderError> {
- use std::collections::HashMap;
use std::f64::consts::PI;
- // Build component map for fast lookup
- let mut component_map: HashMap<String, (f64, f64)> = HashMap::new();
- for component in &map.components {
- let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width);
- let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height);
- component_map.insert(component.label.to_lowercase(), (x, y));
- }
-
ctx.save()?;
set_color(ctx, &config.theme.colors.vertex);
ctx.set_line_width(config.theme.sizes.line_width);
@@ -360,7 +364,6 @@ fn draw_dependencies(
// Draw line
ctx.move_to(offset_origin_x, offset_origin_y);
ctx.line_to(offset_dest_x, offset_dest_y);
- ctx.stroke()?;
// Draw arrowhead if directed
if dependency.is_directed {
@@ -377,28 +380,30 @@ fn draw_dependencies(
offset_dest_x - arrowhead_size * lower_angle.cos(),
offset_dest_y - arrowhead_size * lower_angle.sin(),
);
- ctx.stroke()?;
}
}
+ // Batch stroke all paths at once
+ ctx.stroke()?;
+
ctx.restore()?;
Ok(())
}
-fn draw_groups(ctx: &Context, map: &Map, config: &Configuration) -> Result<(), RenderError> {
+fn draw_groups(
+ ctx: &Context,
+ map: &Map,
+ config: &Configuration,
+ component_map: &std::collections::HashMap<String, (f64, f64)>,
+) -> Result<(), RenderError> {
use geo::{ConcaveHull, LineString, Point};
- use std::collections::HashMap;
use std::f64::consts::PI;
- // Build component map (top-left corner positions)
- let mut component_map: HashMap<String, (f64, f64)> = HashMap::new();
- for component in &map.components {
- let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width);
- let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height);
- component_map.insert(component.label.to_lowercase(), (x, y));
+ if map.groups.is_empty() {
+ return Ok(());
}
- // Create offscreen surface for opacity handling
+ // Create ONE offscreen surface for ALL groups for opacity handling
let width = (config.theme.sizes.map_width + config.theme.sizes.vertex_size) as i32;
let height = (config.theme.sizes.map_height + config.theme.sizes.vertex_size) as i32;
let offscreen = ImageSurface::create(Format::ARgb32, width, height)?;
@@ -454,12 +459,7 @@ fn draw_groups(ctx: &Context, map: &Map, config: &Configuration) -> Result<(), R
continue;
}
- // Clear offscreen canvas
- offscreen_ctx.set_operator(Operator::Clear);
- offscreen_ctx.paint()?;
- offscreen_ctx.set_operator(Operator::Over);
-
- // Draw path
+ // Draw path on offscreen context
set_color(&offscreen_ctx, color);
offscreen_ctx.move_to(coords[0].0, coords[0].1);
for &(x, y) in &coords[1..] {
@@ -477,14 +477,14 @@ fn draw_groups(ctx: &Context, map: &Map, config: &Configuration) -> Result<(), R
offscreen_ctx.set_line_cap(LineCap::Round);
offscreen_ctx.set_line_join(LineJoin::Round);
offscreen_ctx.stroke()?;
-
- // Composite onto main context with opacity
- ctx.save()?;
- ctx.set_source_surface(&offscreen, 0.0, 0.0)?;
- ctx.paint_with_alpha(config.theme.opacity.groups)?;
- ctx.restore()?;
}
+ // Composite entire offscreen surface onto main context with opacity (once for all groups)
+ ctx.save()?;
+ ctx.set_source_surface(&offscreen, 0.0, 0.0)?;
+ ctx.paint_with_alpha(config.theme.opacity.groups)?;
+ ctx.restore()?;
+
Ok(())
}
@@ -520,6 +520,7 @@ fn draw_vertex_labels(
ctx: &Context,
map: &Map,
config: &Configuration,
+ component_map: &std::collections::HashMap<String, (f64, f64)>,
) -> Result<(), RenderError> {
ctx.save()?;
set_color(ctx, &config.theme.colors.label);
@@ -530,35 +531,55 @@ fn draw_vertex_labels(
);
ctx.set_font_size(config.theme.fonts.vertex_label);
- for component in &map.components {
- let (label_x, label_y) = if config.options.smart_label_positioning {
- // Use smart label positioning to find optimal position
- use crate::smart_positioning::calculate_optimal_label_position;
+ if config.options.smart_label_positioning {
+ // Pre-compute collision data once for all labels
+ use crate::smart_positioning::{collect_all_lines, collect_note_rects, calculate_optimal_label_position_cached};
+
+ let all_lines = collect_all_lines(
+ map,
+ config.theme.sizes.map_width,
+ config.theme.sizes.map_height,
+ config.theme.sizes.vertex_size,
+ component_map,
+ );
- let position = calculate_optimal_label_position(
+ let note_rects = collect_note_rects(
+ map,
+ config.theme.sizes.map_width,
+ config.theme.sizes.map_height,
+ ctx,
+ config.theme.fonts.vertex_label,
+ )?;
+
+ for component in &map.components {
+ let position = calculate_optimal_label_position_cached(
component,
- map,
config.theme.sizes.map_width,
config.theme.sizes.map_height,
config.theme.sizes.vertex_size,
ctx,
config.theme.fonts.vertex_label,
+ &all_lines,
+ &note_rects,
)?;
- // Adjust Y position for text baseline
- (position.x, position.y + 7.0)
- } else {
- // Simple label positioning (to the right of vertex)
+ let label_x = position.x;
+ let label_y = position.y + 7.0; // Adjust Y position for text baseline
+
+ ctx.move_to(label_x, label_y);
+ ctx.show_text(&component.label)?;
+ }
+ } else {
+ // Simple label positioning (to the right of vertex)
+ for component in &map.components {
let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width);
let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height);
- (
- x + config.theme.sizes.vertex_size + 5.0,
- y + config.theme.sizes.vertex_size / 2.0 + config.theme.fonts.vertex_label / 3.0,
- )
- };
+ let label_x = x + config.theme.sizes.vertex_size + 5.0;
+ let label_y = y + config.theme.sizes.vertex_size / 2.0 + config.theme.fonts.vertex_label / 3.0;
- ctx.move_to(label_x, label_y);
- ctx.show_text(&component.label)?;
+ ctx.move_to(label_x, label_y);
+ ctx.show_text(&component.label)?;
+ }
}
ctx.restore()?;
@@ -569,17 +590,8 @@ fn draw_inertias(
ctx: &Context,
map: &Map,
config: &Configuration,
+ component_map: &std::collections::HashMap<String, (f64, f64)>,
) -> Result<(), RenderError> {
- use std::collections::HashMap;
-
- // Build component map (top-left corner positions)
- let mut component_map: HashMap<String, (f64, f64)> = HashMap::new();
- for component in &map.components {
- let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width);
- let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height);
- component_map.insert(component.label.to_lowercase(), (x, y));
- }
-
ctx.save()?;
set_color(ctx, &config.theme.colors.inertia);
@@ -690,18 +702,10 @@ fn draw_evolutions(
ctx: &Context,
map: &Map,
config: &Configuration,
+ component_map: &std::collections::HashMap<String, (f64, f64)>,
) -> Result<(), RenderError> {
- use std::collections::HashMap;
use std::f64::consts::PI;
- // Build component map (top-left corner positions)
- let mut component_map: HashMap<String, (f64, f64)> = HashMap::new();
- for component in &map.components {
- let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width);
- let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height);
- component_map.insert(component.label.to_lowercase(), (x, y));
- }
-
ctx.save()?;
set_color(ctx, &config.theme.colors.evolution);
ctx.set_line_width(config.theme.sizes.line_width * 2.0);
@@ -732,7 +736,6 @@ fn draw_evolutions(
// Draw dashed line
ctx.move_to(offset_origin_x, offset_origin_y);
ctx.line_to(offset_dest_x, offset_dest_y);
- ctx.stroke()?;
// Draw arrowhead at destination only
let upper_angle = -PI / 4.0;
@@ -744,17 +747,18 @@ fn draw_evolutions(
offset_dest_x - multiplier * arrowhead_size * upper_angle.cos(),
offset_dest_y - multiplier * arrowhead_size * upper_angle.sin(),
);
- ctx.stroke()?;
ctx.move_to(offset_dest_x, offset_dest_y);
ctx.line_to(
offset_dest_x - multiplier * arrowhead_size * lower_angle.cos(),
offset_dest_y - multiplier * arrowhead_size * lower_angle.sin(),
);
- ctx.stroke()?;
}
}
+ // Batch stroke all paths at once
+ ctx.stroke()?;
+
ctx.restore()?;
Ok(())
}