aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuben Beltran del Rio <jj@r.bdr.sh>2025-12-16 11:09:49 +0100
committerRuben Beltran del Rio <jj@r.bdr.sh>2025-12-16 11:15:32 +0100
commit6deeee3848e291dee61e239f104dc29bb26f4608 (patch)
treee97308fc347e2abef2272942a7e5d71e56ac0469
parentf08648e4b5a9f7855a2ed9068a386e2f3c596322 (diff)
Benchmark with example, and raw rendering
-rw-r--r--benches/example.wmap122
-rw-r--r--benches/rendering.rs21
-rw-r--r--src/lib.rs20
-rw-r--r--src/renderer.rs84
4 files changed, 204 insertions, 43 deletions
diff --git a/benches/example.wmap b/benches/example.wmap
new file mode 100644
index 0000000..a238ab1
--- /dev/null
+++ b/benches/example.wmap
@@ -0,0 +1,122 @@
+Anchor (40, 0)
+Tinker (4,30) [x]
+Tailor (7.03, 49.21) [Square]
+Soldier (28, 15) [Triangle]
+AMnchor (28.22, 45.27)
+Tonker (67.3, 4.4) [X]
+Tailor1 (1,1) [Square]
+Tailor2 (10,1) [Square]
+Tailor3 (1,10) [Square]
+Tailor4 (20,1) [Square]
+Tailor5 (1,20) [Square]
+Tailor7 (20,10) [Square]
+Tailor8 (9.36, 16.97) [Square]
+Tailor777(30,10) [Square]
+Tailor10 (18.77, 26.25) [Square]
+Tailor11 (10,10) [Square]
+Tailor12 (30,20)
+Tailor13 (31.10, 28.89)
+Tailor14 (13.44, 21.48) [Square]
+Tailor19 (45, 2) [triangle]
+B (50, 50)
+Hellele
+
+TailorX (20, 50) [Square]
+
+Hello World (90, 20) [x]
+
+Anchor -> Soldier… this is changing.
+Anchor -> A
+Masdlajksd -- Tailor13
+
+Mabanchorn -- Anchornalsdjalks
+
+A
+--B
+
+X (58.52, 27.07)
+
+Tailor11--Tailor12
+Tailor12--Tailor13
+Tailor777--Tailor8
+Tailor777--Tailor7
+Tailor777--Tailor6
+Tailor7-Tailor6
+
+Tinker -- Tailor
+Soldier -- Tinker
+
+[Evolution] Tinker +20
+[Inertia] Soldier
+
+[i] 15
+[ii] 35
+[iii] 80
+
+this is kind of ok … a little bit better to be honest. (69.76, 89.84)
+
+OK.
+
+This map so far is fst. asdasd asda
+
+Holy shit, this is still indeed fast. But everything flashes as I type! This is now happening so fast
+
+Hello my old friend.
+
+OK so this map should eventually start getting slow too!
+
+let’s see how long after.asd asda sda
+
+asda sdasdasd asdasdasdasdasdasd
+OK… Well we are typing. asd as asd as dasd a asdasda sdasd asd
+
+ asd asd asd asdasd asdasd
+Alright, the whole text is still fa asdlashing. Wow. This is pretty bad.
+aas dasd adsa sd a asd asd asdjlkasjd laksdjasd basldjas!!!
+
+we have deleted this (20, 40) [triangle]
+
+a(12, 12)
+Time to see if we get the same performance loss as withthis the other one. Well I will continue. Hello this is me. and i’m
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+b(75,75)
+c(9, 90)
+
+a -- b
+b -- c
+
+d (45, 88)
+
+c -- d
+d -- a
+d -- b
+
+q (90, 60)
+b -- q
+q -- a
+q -- c
+
+[Evolution] c +20
+[Evolution] d +10
+[Evolution] a -20
+[Evolution] q +5
+
+m(50, 60)
+n (65, 65)
+o (45, 63)
+
+m -- o
+o -- n
+n -- m
+o -- c
+n -- q
+m -- d
+
+testasd asdas
+test2asdasa asdasd asd
+
+[Note] (5, 35) Notes also influence the placement of\n labels.
+
+[Group] Tailor2, Tailor7, Tailor11, Tailor12, Tailor10, Anchor, Tinker
+[Group] Tailor3, Tailor5, Tailor14pr \ No newline at end of file
diff --git a/benches/rendering.rs b/benches/rendering.rs
index 549ad21..51e1b13 100644
--- a/benches/rendering.rs
+++ b/benches/rendering.rs
@@ -1,23 +1,22 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use std::fs;
-use wmap_renderer::{render_to_png, Configuration, StageType};
+use wmap_renderer::{render_to_surface, Configuration, StageType};
fn benchmark_rendering(c: &mut Criterion) {
- // Read the huge.wmap file for realistic benchmarking
- let source = fs::read_to_string("benches/huge.wmap").expect("Failed to read benches/huge.wmap");
- let map = wmap_parser::parse(&source);
let config = Configuration::default();
- c.bench_function("render huge map", |b| {
- b.iter(|| render_to_png(black_box(&map), StageType::default(), &config))
+ let example_source = fs::read_to_string("benches/example.wmap").expect("Failed to read benches/example.wmap");
+ let example_map = wmap_parser::parse(&example_source);
+
+ c.bench_function("render example map", |b| {
+ b.iter(|| render_to_surface(black_box(&example_map), StageType::default(), &config))
});
- // Also benchmark a simple map
- let simple_source = "Tea (90, 50)\nCup (80, 40)\nTea -> Cup";
- let simple_map = wmap_parser::parse(simple_source);
+ let huge_source = fs::read_to_string("benches/huge.wmap").expect("Failed to read benches/huge.wmap");
+ let huge_map = wmap_parser::parse(&huge_source);
- c.bench_function("render simple map", |b| {
- b.iter(|| render_to_png(black_box(&simple_map), StageType::default(), &config))
+ c.bench_function("render huge map", |b| {
+ b.iter(|| render_to_surface(black_box(&huge_map), StageType::default(), &config))
});
}
diff --git a/src/lib.rs b/src/lib.rs
index b56c670..de4146d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -19,6 +19,7 @@ pub use configuration::{
pub use stage_type::StageType;
use thiserror::Error;
+use cairo::ImageSurface;
/// Error types for rendering
#[derive(Debug, Error)]
@@ -86,6 +87,25 @@ pub fn render_to_svg(
renderer::render_to_svg(map, stage_type, config)
}
+/// Renders a Wardley map to a cairo context.
+///
+/// # Arguments
+///
+/// * `map` - The parsed Wardley map
+/// * `stage_type` - The stage type to use for axis labels
+/// * `config` - Configuration options
+///
+/// # Returns
+///
+/// A string containing the SVG data
+pub fn render_to_surface(
+ map: &Map,
+ stage_type: StageType,
+ config: &Configuration,
+) -> Result<ImageSurface, RenderError> {
+ renderer::render_to_surface(map, stage_type, config)
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/src/renderer.rs b/src/renderer.rs
index 9991d56..8ac025b 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -5,6 +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};
/// Renders a map to PNG format
pub fn render_to_png(
@@ -17,8 +18,8 @@ pub fn render_to_png(
+ config.theme.sizes.padding
+ config.theme.sizes.bottom_padding) as i32;
- let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, total_width, total_height)?;
- let ctx = cairo::Context::new(&surface)?;
+ let surface = ImageSurface::create(Format::ARgb32, total_width, total_height)?;
+ let ctx = Context::new(&surface)?;
render(&ctx, map, stage_type, config)?;
@@ -27,6 +28,25 @@ pub fn render_to_png(
Ok(buffer.into_inner())
}
+/// Renders a map to the context
+pub fn render_to_surface(
+ map: &Map,
+ stage_type: StageType,
+ config: &Configuration,
+) -> Result<ImageSurface, RenderError> {
+ let total_width = (config.theme.sizes.map_width + config.theme.sizes.padding * 2.0) as i32;
+ let total_height = (config.theme.sizes.map_height
+ + config.theme.sizes.padding
+ + config.theme.sizes.bottom_padding) as i32;
+
+ let surface = ImageSurface::create(Format::ARgb32, total_width, total_height)?;
+ let ctx = Context::new(&surface)?;
+
+ render(&ctx, map, stage_type, config)?;
+
+ Ok(surface)
+}
+
/// Renders a map to SVG format
pub fn render_to_svg(
map: &Map,
@@ -47,8 +67,8 @@ pub fn render_to_svg(
.to_str()
.ok_or_else(|| RenderError::InvalidConfig("Failed to create temp file path".to_string()))?;
- let surface = cairo::SvgSurface::new(total_width, total_height, Some(path))?;
- let ctx = cairo::Context::new(&surface)?;
+ let surface = SvgSurface::new(total_width, total_height, Some(path))?;
+ let ctx = Context::new(&surface)?;
render(&ctx, map, stage_type, config)?;
@@ -61,7 +81,7 @@ pub fn render_to_svg(
/// Core rendering function
fn render(
- ctx: &cairo::Context,
+ ctx: &Context,
map: &Map,
stage_type: StageType,
config: &Configuration,
@@ -91,7 +111,7 @@ fn render(
Ok(())
}
-fn draw_background(ctx: &cairo::Context, config: &Configuration) -> Result<(), RenderError> {
+fn draw_background(ctx: &Context, config: &Configuration) -> Result<(), RenderError> {
ctx.save()?;
set_color(ctx, &config.theme.colors.background);
ctx.paint()?;
@@ -100,7 +120,7 @@ fn draw_background(ctx: &cairo::Context, config: &Configuration) -> Result<(), R
}
fn draw_stage_patterns(
- ctx: &cairo::Context,
+ ctx: &Context,
_stage_type: StageType,
map: &Map,
config: &Configuration,
@@ -173,7 +193,7 @@ fn draw_stage_patterns(
}
fn draw_axes(
- ctx: &cairo::Context,
+ ctx: &Context,
stage_type: StageType,
map: &Map,
config: &Configuration,
@@ -197,8 +217,8 @@ fn draw_axes(
set_color(ctx, &config.theme.colors.label);
ctx.select_font_face(
"sans-serif",
- cairo::FontSlant::Normal,
- cairo::FontWeight::Normal,
+ FontSlant::Normal,
+ FontWeight::Normal,
);
ctx.set_font_size(fonts.axis_label);
ctx.set_line_width(1.0);
@@ -267,7 +287,7 @@ fn draw_axes(
}
fn draw_stage_dividers(
- ctx: &cairo::Context,
+ ctx: &Context,
map: &Map,
config: &Configuration,
) -> Result<(), RenderError> {
@@ -289,7 +309,7 @@ fn draw_stage_dividers(
}
fn draw_dependencies(
- ctx: &cairo::Context,
+ ctx: &Context,
map: &Map,
config: &Configuration,
) -> Result<(), RenderError> {
@@ -365,7 +385,7 @@ fn draw_dependencies(
Ok(())
}
-fn draw_groups(ctx: &cairo::Context, map: &Map, config: &Configuration) -> Result<(), RenderError> {
+fn draw_groups(ctx: &Context, map: &Map, config: &Configuration) -> Result<(), RenderError> {
use geo::{ConcaveHull, LineString, Point};
use std::collections::HashMap;
use std::f64::consts::PI;
@@ -381,8 +401,8 @@ fn draw_groups(ctx: &cairo::Context, map: &Map, config: &Configuration) -> Resul
// Create offscreen surface 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 = cairo::ImageSurface::create(cairo::Format::ARgb32, width, height)?;
- let offscreen_ctx = cairo::Context::new(&offscreen)?;
+ let offscreen = ImageSurface::create(Format::ARgb32, width, height)?;
+ let offscreen_ctx = Context::new(&offscreen)?;
// Translate to account for vertex width/height
offscreen_ctx.translate(
@@ -435,9 +455,9 @@ fn draw_groups(ctx: &cairo::Context, map: &Map, config: &Configuration) -> Resul
}
// Clear offscreen canvas
- offscreen_ctx.set_operator(cairo::Operator::Clear);
+ offscreen_ctx.set_operator(Operator::Clear);
offscreen_ctx.paint()?;
- offscreen_ctx.set_operator(cairo::Operator::Over);
+ offscreen_ctx.set_operator(Operator::Over);
// Draw path
set_color(&offscreen_ctx, color);
@@ -454,8 +474,8 @@ fn draw_groups(ctx: &cairo::Context, map: &Map, config: &Configuration) -> Resul
// Draw stroke with round caps and joins for smoother appearance
offscreen_ctx.set_line_width(config.theme.sizes.vertex_size * 1.75);
- offscreen_ctx.set_line_cap(cairo::LineCap::Round);
- offscreen_ctx.set_line_join(cairo::LineJoin::Round);
+ offscreen_ctx.set_line_cap(LineCap::Round);
+ offscreen_ctx.set_line_join(LineJoin::Round);
offscreen_ctx.stroke()?;
// Composite onto main context with opacity
@@ -469,7 +489,7 @@ fn draw_groups(ctx: &cairo::Context, map: &Map, config: &Configuration) -> Resul
}
fn draw_vertices(
- ctx: &cairo::Context,
+ ctx: &Context,
map: &Map,
config: &Configuration,
) -> Result<(), RenderError> {
@@ -497,7 +517,7 @@ fn draw_vertices(
}
fn draw_vertex_labels(
- ctx: &cairo::Context,
+ ctx: &Context,
map: &Map,
config: &Configuration,
) -> Result<(), RenderError> {
@@ -505,8 +525,8 @@ fn draw_vertex_labels(
set_color(ctx, &config.theme.colors.label);
ctx.select_font_face(
"sans-serif",
- cairo::FontSlant::Normal,
- cairo::FontWeight::Normal,
+ FontSlant::Normal,
+ FontWeight::Normal,
);
ctx.set_font_size(config.theme.fonts.vertex_label);
@@ -546,7 +566,7 @@ fn draw_vertex_labels(
}
fn draw_inertias(
- ctx: &cairo::Context,
+ ctx: &Context,
map: &Map,
config: &Configuration,
) -> Result<(), RenderError> {
@@ -584,13 +604,13 @@ fn draw_inertias(
}
fn draw_rounded_rect(
- ctx: &cairo::Context,
+ ctx: &Context,
x: f64,
y: f64,
width: f64,
height: f64,
radius: f64,
-) -> Result<(), cairo::Error> {
+) -> Result<(), Error> {
let r = radius;
ctx.new_path();
ctx.arc(
@@ -627,10 +647,10 @@ fn draw_rounded_rect(
/// Wraps text to fit within a given width, breaking at word boundaries
fn wrap_text(
- ctx: &cairo::Context,
+ ctx: &Context,
text: &str,
max_width: f64,
-) -> Result<Vec<String>, cairo::Error> {
+) -> Result<Vec<String>, Error> {
let words: Vec<&str> = text.split_whitespace().collect();
let mut lines = Vec::new();
let mut current_line = String::new();
@@ -667,7 +687,7 @@ fn wrap_text(
}
fn draw_evolutions(
- ctx: &cairo::Context,
+ ctx: &Context,
map: &Map,
config: &Configuration,
) -> Result<(), RenderError> {
@@ -739,15 +759,15 @@ fn draw_evolutions(
Ok(())
}
-fn draw_notes(ctx: &cairo::Context, map: &Map, config: &Configuration) -> Result<(), RenderError> {
+fn draw_notes(ctx: &Context, map: &Map, config: &Configuration) -> Result<(), RenderError> {
let padding = 5.0;
let max_width = 400.0;
ctx.save()?;
ctx.select_font_face(
"sans-serif",
- cairo::FontSlant::Normal,
- cairo::FontWeight::Normal,
+ FontSlant::Normal,
+ FontWeight::Normal,
);
ctx.set_font_size(config.theme.fonts.note);