fn node_render_dimensions(
layout_shape: Option<&str>,
metrics: crate::text::TextMetrics,
padding: f64,
) -> (f64, f64) {
let text_w = metrics.width.max(0.0);
let text_h = metrics.height.max(0.0);
let p = padding.max(0.0);
let shape = layout_shape.unwrap_or("squareRect");
fn circle_points(
center_x: f64,
center_y: f64,
radius: f64,
num_points: usize,
start_deg: f64,
end_deg: f64,
negate: bool,
) -> Vec<(f64, f64)> {
let start = start_deg.to_radians();
let end = end_deg.to_radians();
let angle_range = end - start;
let angle_step = if num_points > 1 {
angle_range / (num_points as f64 - 1.0)
} else {
0.0
};
let mut out: Vec<(f64, f64)> = Vec::with_capacity(num_points);
for i in 0..num_points {
let a = start + (i as f64) * angle_step;
let x = center_x + radius * a.cos();
let y = center_y + radius * a.sin();
if negate {
out.push((-x, -y));
} else {
out.push((x, y));
}
}
out
}
fn bbox_of_points(points: &[(f64, f64)]) -> Option<(f64, f64, f64, f64)> {
let mut min_x = f64::INFINITY;
let mut min_y = f64::INFINITY;
let mut max_x = f64::NEG_INFINITY;
let mut max_y = f64::NEG_INFINITY;
for &(x, y) in points {
min_x = min_x.min(x);
min_y = min_y.min(y);
max_x = max_x.max(x);
max_y = max_y.max(y);
}
if min_x.is_finite() && min_y.is_finite() && max_x.is_finite() && max_y.is_finite() {
Some((min_x, min_y, max_x, max_y))
} else {
None
}
}
fn f32_dims(w: f64, h: f64) -> (f64, f64) {
let w_f32 = w as f32;
let h_f32 = h as f32;
if w_f32.is_finite()
&& h_f32.is_finite()
&& w_f32.is_sign_positive()
&& h_f32.is_sign_positive()
{
(w_f32 as f64, h_f32 as f64)
} else {
(w.max(0.0), h.max(0.0))
}
}
fn generate_full_sine_wave_points(
x1: f64,
y1: f64,
x2: f64,
y2: f64,
amplitude: f64,
num_cycles: f64,
) -> Vec<(f64, f64)> {
let steps: usize = 50;
let delta_x = x2 - x1;
let delta_y = y2 - y1;
let cycle_length = if num_cycles.abs() < 1e-9 {
delta_x
} else {
delta_x / num_cycles
};
let frequency = if cycle_length.abs() < 1e-9 {
0.0
} else {
(2.0 * std::f64::consts::PI) / cycle_length
};
let mid_y = y1 + delta_y / 2.0;
let mut points: Vec<(f64, f64)> = Vec::with_capacity(steps + 1);
for i in 0..=steps {
let t = (i as f64) / (steps as f64);
let x = x1 + t * delta_x;
let y = mid_y + amplitude * (frequency * (x - x1)).sin();
points.push((x, y));
}
points
}
fn arc_points(
x1: f64,
y1: f64,
x2: f64,
y2: f64,
rx: f64,
ry: f64,
clockwise: bool,
) -> Vec<(f64, f64)> {
let num_points: usize = 20;
let mid_x = (x1 + x2) / 2.0;
let mid_y = (y1 + y2) / 2.0;
let angle = (y2 - y1).atan2(x2 - x1);
let dx = (x2 - x1) / 2.0;
let dy = (y2 - y1) / 2.0;
let transformed_x = dx / rx;
let transformed_y = dy / ry;
let distance = (transformed_x * transformed_x + transformed_y * transformed_y).sqrt();
if distance > 1.0 {
return vec![(x1, y1), (x2, y2)];
}
let scaled_center_distance = (1.0 - distance * distance).sqrt();
let sign = if clockwise { -1.0 } else { 1.0 };
let center_x = mid_x + scaled_center_distance * ry * angle.sin() * sign;
let center_y = mid_y - scaled_center_distance * rx * angle.cos() * sign;
let start_angle = ((y1 - center_y) / ry).atan2((x1 - center_x) / rx);
let end_angle = ((y2 - center_y) / ry).atan2((x2 - center_x) / rx);
let mut angle_range = end_angle - start_angle;
if clockwise && angle_range < 0.0 {
angle_range += 2.0 * std::f64::consts::PI;
}
if !clockwise && angle_range > 0.0 {
angle_range -= 2.0 * std::f64::consts::PI;
}
let mut points: Vec<(f64, f64)> = Vec::with_capacity(num_points);
for i in 0..num_points {
let t = i as f64 / (num_points - 1) as f64;
let a = start_angle + t * angle_range;
let x = center_x + rx * a.cos();
let y = center_y + ry * a.sin();
points.push((x, y));
}
points
}
match shape {
"anchor" => (2.001_899_003_982_544, 2.0),
"squareRect" => (text_w + 4.0 * p, text_h + 2.0 * p),
"roundedRect" | "rounded" | "event" => (text_w + 2.0 * p, text_h + 2.0 * p),
"note" => (text_w + 2.0 * p, text_h + 2.0 * p),
"diamond" | "question" | "diam" | "decision" => {
let w = text_w + p;
let h = text_h + p;
let s = w + h;
(s, s)
}
"hexagon" | "hex" | "prepare" => {
let h = text_h + p;
let w0 = text_w + 2.5 * p;
let mut half_width = w0 / 2.0;
let m = half_width / 6.0;
half_width += m;
(half_width * 2.0, h)
}
"stadium" | "terminal" | "pill" => {
let h = text_h + p;
let w = text_w + h / 4.0 + p;
(w, h)
}
"subroutine" | "fr-rect" | "subproc" | "subprocess" | "framed-rectangle" => {
let w = text_w + p;
let h = text_h + p;
(w + 16.0, h)
}
"cylinder" | "cyl" | "db" | "database" => {
let w = text_w + p;
let rx = w / 2.0;
let ry = rx / (2.5 + w / 50.0);
let height = text_h + p + 3.0 * ry;
(w, height)
}
"h-cyl" | "das" | "horizontal-cylinder" => {
let label_padding = p / 2.0;
let h = text_h + label_padding;
let ry = h / 2.0;
let rx = if ry == 0.0 {
0.0
} else {
ry / (2.5 + h / 50.0)
};
let w = text_w + rx + label_padding;
(w + 2.0 * rx, h)
}
"win-pane" | "internal-storage" | "window-pane" => {
let w = (text_w + 2.0 * p).max(0.0);
let h = (text_h + 2.0 * p).max(0.0);
let rect_offset = 5.0;
f32_dims(w + rect_offset, h + rect_offset)
}
"circle" | "circ" => {
let d = text_w + p;
(d, d)
}
"doublecircle" | "dbl-circ" | "double-circle" => {
let d = text_w + p + 10.0;
(d, d)
}
"sm-circ" | "small-circle" | "start" => (14.0, 14.0),
"fr-circ" | "framed-circle" | "stop" => (14.013_293_266_296_387, 14.0),
"fork" | "join" => (70.0, 10.0),
"choice" => (28.0, 28.0),
"bolt" | "com-link" | "lightning-bolt" => (35.0, 70.0),
"f-circ" | "junction" | "filled-circle" => (14.013_293_266_296_387, 14.0),
"cross-circ" | "summary" | "crossed-circle" => (60.056_972_503_662_11, 60.0),
"delay" | "half-rounded-rectangle" => {
let min_width = 80.0;
let min_height = 50.0;
let w = (text_w + 2.0 * p).max(min_width);
let h = (text_h + 2.0 * p).max(min_height);
let radius = h / 2.0;
let mut points: Vec<(f64, f64)> = Vec::new();
points.push((-w / 2.0, -h / 2.0));
points.push((w / 2.0 - radius, -h / 2.0));
points.extend(circle_points(
-w / 2.0 + radius,
0.0,
radius,
50,
90.0,
270.0,
true,
));
points.push((w / 2.0 - radius, h / 2.0));
points.push((-w / 2.0, h / 2.0));
let (min_x, min_y, max_x, max_y) =
bbox_of_points(&points).unwrap_or((-w / 2.0, -h / 2.0, w / 2.0, h / 2.0));
f32_dims((max_x - min_x).max(0.0), (max_y - min_y).max(0.0))
}
"lin-cyl" | "disk" | "lined-cylinder" => {
let w = text_w + p;
let rx = w / 2.0;
let ry = rx / (2.5 + w / 50.0);
let height = text_h + p + 3.0 * ry;
f32_dims(w, height)
}
"curv-trap" | "display" | "curved-trapezoid" => {
let min_width = 80.0;
let min_height = 20.0;
let w = (text_w + 2.0 * p).mul_add(1.25, 0.0).max(min_width);
let h = (text_h + 2.0 * p).max(min_height);
let radius = h / 2.0;
let total_width = w;
let total_height = h;
let rw = total_width - radius;
let tw = total_height / 4.0;
let mut points: Vec<(f64, f64)> = vec![
(rw, 0.0),
(tw, 0.0),
(0.0, total_height / 2.0),
(tw, total_height),
(rw, total_height),
];
points.extend(circle_points(
-rw,
-total_height / 2.0,
radius,
50,
270.0,
90.0,
true,
));
let (min_x, min_y, max_x, max_y) =
bbox_of_points(&points).unwrap_or((0.0, 0.0, total_width, total_height));
f32_dims((max_x - min_x).max(0.0), (max_y - min_y).max(0.0))
}
"div-rect" | "div-proc" | "divided-rectangle" | "divided-process" => {
let w = text_w + p;
let h = text_h + p;
let rect_offset = h * 0.2;
let x = -w / 2.0;
let y = -h / 2.0 - rect_offset / 2.0;
let points: Vec<(f64, f64)> = vec![
(x, y + rect_offset),
(-x, y + rect_offset),
(-x, -y),
(x, -y),
(x, y),
(-x, y),
(-x, y + rect_offset),
];
let (min_x, min_y, max_x, max_y) = bbox_of_points(&points).unwrap_or((x, y, -x, -y));
f32_dims((max_x - min_x).max(0.0), (max_y - min_y).max(0.0))
}
"tri" | "extract" | "triangle" => {
let w = text_w + p;
let h = w + text_h;
f32_dims(h, h)
}
"manual-file" | "flipped-triangle" | "flip-tri" => {
let w = text_w + p;
let h = w + text_h;
f32_dims(h, h)
}
"manual-input" | "sloped-rectangle" | "sl-rect" => {
let w = (text_w + 2.0 * p).max(0.0);
let h = (text_h + 2.0 * p).max(0.0);
f32_dims(w, (1.5 * h).max(0.0))
}
"doc" | "document" => {
let w = (text_w + 2.0 * p).max(0.0);
let h = (text_h + 2.0 * p).max(0.0);
let wave_amplitude = h / 8.0;
let final_h = h + wave_amplitude;
let min_width = 70.0;
let extra_w = if w < min_width {
(min_width - w) / 2.0
} else {
0.0
};
let mut points: Vec<(f64, f64)> = Vec::new();
points.push((-w / 2.0 - extra_w, final_h / 2.0));
points.extend(generate_full_sine_wave_points(
-w / 2.0 - extra_w,
final_h / 2.0,
w / 2.0 + extra_w,
final_h / 2.0,
wave_amplitude,
0.8,
));
points.push((w / 2.0 + extra_w, -final_h / 2.0));
points.push((-w / 2.0 - extra_w, -final_h / 2.0));
let (min_x, min_y, max_x, max_y) = bbox_of_points(&points).unwrap_or((
-w / 2.0,
-final_h / 2.0,
w / 2.0,
final_h / 2.0,
));
f32_dims((max_x - min_x).max(0.0), (max_y - min_y).max(0.0))
}
"docs" | "documents" | "st-doc" | "stacked-document" => {
let w = (text_w + 2.0 * p).max(0.0);
let h = (text_h + 2.0 * p).max(0.0);
let wave_amplitude = h / 4.0;
let final_h = h + wave_amplitude;
let rect_offset = 5.0;
let x = -w / 2.0;
let y = -final_h / 2.0;
let wave_points = generate_full_sine_wave_points(
x - rect_offset,
y + final_h + rect_offset,
x + w - rect_offset,
y + final_h + rect_offset,
wave_amplitude,
0.8,
);
let (_last_x, last_y) = wave_points[wave_points.len() - 1];
let mut outer_points: Vec<(f64, f64)> = Vec::new();
outer_points.push((x - rect_offset, y + rect_offset));
outer_points.push((x - rect_offset, y + final_h + rect_offset));
outer_points.extend(wave_points.iter().copied());
outer_points.push((x + w - rect_offset, last_y - rect_offset));
outer_points.push((x + w, last_y - rect_offset));
outer_points.push((x + w, last_y - 2.0 * rect_offset));
outer_points.push((x + w + rect_offset, last_y - 2.0 * rect_offset));
outer_points.push((x + w + rect_offset, y - rect_offset));
outer_points.push((x + rect_offset, y - rect_offset));
outer_points.push((x + rect_offset, y));
outer_points.push((x, y));
outer_points.push((x, y + rect_offset));
let (min_x, min_y, max_x, max_y) =
bbox_of_points(&outer_points).unwrap_or((x, y, x + w, y + final_h));
f32_dims((max_x - min_x).max(0.0), (max_y - min_y).max(0.0))
}
"st-rect" | "procs" | "processes" | "stacked-rectangle" => {
let w = (text_w + 2.0 * p).max(0.0);
let h = (text_h + 2.0 * p).max(0.0);
let rect_offset = 5.0;
f32_dims(w + 2.0 * rect_offset, h + 2.0 * rect_offset)
}
"paper-tape" | "flag" => {
let min_width = 100.0;
let min_height = 50.0;
let w = (text_w + 2.0 * p).max(min_width);
let h = (text_h + 2.0 * p).max(min_height);
let wave_amplitude = (h * 0.2).min(h / 4.0);
let final_h = h + wave_amplitude * 2.0;
let mut points: Vec<(f64, f64)> = Vec::new();
points.push((-w / 2.0, final_h / 2.0));
points.extend(generate_full_sine_wave_points(
-w / 2.0,
final_h / 2.0,
w / 2.0,
final_h / 2.0,
wave_amplitude,
1.0,
));
points.push((w / 2.0, -final_h / 2.0));
points.extend(generate_full_sine_wave_points(
w / 2.0,
-final_h / 2.0,
-w / 2.0,
-final_h / 2.0,
wave_amplitude,
-1.0,
));
let (min_x, min_y, max_x, max_y) = bbox_of_points(&points).unwrap_or((
-w / 2.0,
-final_h / 2.0,
w / 2.0,
final_h / 2.0,
));
f32_dims((max_x - min_x).max(0.0), (max_y - min_y).max(0.0))
}
"lin-doc" | "lined-document" => {
let w = (text_w + 2.0 * p).max(0.0);
let h = (text_h + 2.0 * p).max(0.0);
let wave_amplitude = h / 4.0;
let final_h = h + wave_amplitude;
let extra = (w / 2.0) * 0.1;
let mut points: Vec<(f64, f64)> = Vec::new();
points.push((-w / 2.0 - extra, -final_h / 2.0));
points.push((-w / 2.0 - extra, final_h / 2.0));
points.extend(generate_full_sine_wave_points(
-w / 2.0 - extra,
final_h / 2.0,
w / 2.0 + extra,
final_h / 2.0,
wave_amplitude,
0.8,
));
points.push((w / 2.0 + extra, -final_h / 2.0));
points.push((-w / 2.0 - extra, -final_h / 2.0));
points.push((-w / 2.0, -final_h / 2.0));
points.push((-w / 2.0, (final_h / 2.0) * 1.1));
points.push((-w / 2.0, -final_h / 2.0));
let (min_x, min_y, max_x, max_y) = bbox_of_points(&points).unwrap_or((
-w / 2.0,
-final_h / 2.0,
w / 2.0,
final_h / 2.0,
));
f32_dims((max_x - min_x).max(0.0), (max_y - min_y).max(0.0))
}
"tag-rect" | "tagged-rectangle" | "tag-proc" | "tagged-process" => {
let w = (text_w + 2.0 * p).max(0.0);
let h = (text_h + 2.0 * p).max(0.0);
let x = -w / 2.0;
let y = -h / 2.0;
let tag_width = 0.2 * h;
let tag_height = 0.2 * h;
let rect_points = vec![
(x - tag_width / 2.0, y),
(x + w + tag_width / 2.0, y),
(x + w + tag_width / 2.0, y + h),
(x - tag_width / 2.0, y + h),
];
let tag_points = vec![
(x + w - tag_width / 2.0, y + h),
(x + w + tag_width / 2.0, y + h),
(x + w + tag_width / 2.0, y + h - tag_height),
];
let mut pts = rect_points;
pts.extend(tag_points);
let (min_x, min_y, max_x, max_y) = bbox_of_points(&pts).unwrap_or((x, y, x + w, y + h));
f32_dims((max_x - min_x).max(0.0), (max_y - min_y).max(0.0))
}
"tag-doc" | "tagged-document" => {
let w = (text_w + 2.0 * p).max(0.0);
let h = (text_h + 2.0 * p).max(0.0);
let wave_amplitude = h / 4.0;
let final_h = h + wave_amplitude;
let extra = (w / 2.0) * 0.1;
let tag_width = 0.2 * w;
let tag_height = 0.2 * h;
let mut points: Vec<(f64, f64)> = Vec::new();
points.push((-w / 2.0 - extra, final_h / 2.0));
points.extend(generate_full_sine_wave_points(
-w / 2.0 - extra,
final_h / 2.0,
w / 2.0 + extra,
final_h / 2.0,
wave_amplitude,
0.8,
));
points.push((w / 2.0 + extra, -final_h / 2.0));
points.push((-w / 2.0 - extra, -final_h / 2.0));
let x = -w / 2.0 + extra;
let y = -final_h / 2.0 - tag_height * 0.4;
let mut tag_points: Vec<(f64, f64)> = Vec::new();
tag_points.push((x + w - tag_width, (y + h) * 1.4));
tag_points.push((x + w, y + h - tag_height));
tag_points.push((x + w, (y + h) * 0.9));
tag_points.extend(generate_full_sine_wave_points(
x + w,
(y + h) * 1.3,
x + w - tag_width,
(y + h) * 1.5,
-h * 0.03,
0.5,
));
points.extend(tag_points);
let (min_x, min_y, max_x, max_y) = bbox_of_points(&points).unwrap_or((
-w / 2.0,
-final_h / 2.0,
w / 2.0,
final_h / 2.0,
));
f32_dims((max_x - min_x).max(0.0), (max_y - min_y).max(0.0))
}
"notch-pent" | "loop-limit" | "notched-pentagon" => {
let min_width = 60.0;
let min_height = 20.0;
let w = (text_w + 2.0 * p).max(min_width);
let h = (text_h + 2.0 * p).max(min_height);
f32_dims(w, h)
}
"bow-rect" | "stored-data" | "bow-tie-rectangle" => {
let w = text_w + p + 20.0;
let h = text_h + p;
let ry = h / 2.0;
let rx = ry / (2.5 + h / 50.0);
let mut points: Vec<(f64, f64)> = Vec::new();
points.push((w / 2.0, -h / 2.0));
points.push((-w / 2.0, -h / 2.0));
points.extend(arc_points(
-w / 2.0,
-h / 2.0,
-w / 2.0,
h / 2.0,
rx,
ry,
false,
));
points.push((w / 2.0, h / 2.0));
points.extend(arc_points(
w / 2.0,
h / 2.0,
w / 2.0,
-h / 2.0,
rx,
ry,
true,
));
let (min_x, min_y, max_x, max_y) =
bbox_of_points(&points).unwrap_or((-w / 2.0, -h / 2.0, w / 2.0, h / 2.0));
f32_dims((max_x - min_x).max(0.0), (max_y - min_y).max(0.0))
}
"hourglass" | "collate" => (30.0, 30.0),
"notch-rect" | "notched-rectangle" | "card" => (text_w + p + 12.0, text_h + p),
"lin-rect" | "lined-rectangle" | "lined-process" | "lin-proc" | "shaded-process" => {
(text_w + 2.0 * p + 16.0, text_h + 2.0 * p)
}
"text" => (text_w + p, text_h + p),
"comment" | "brace" | "brace-l" => {
let w = text_w + p;
let h = text_h + p;
let radius = (h * 0.1).max(5.0);
let group_tx = radius;
let mut points: Vec<(f64, f64)> = Vec::new();
points.extend(circle_points(
w / 2.0,
-h / 2.0,
radius,
30,
-90.0,
0.0,
true,
));
points.push((-w / 2.0 - radius, radius));
points.extend(circle_points(
w / 2.0 + radius * 2.0,
-radius,
radius,
20,
-180.0,
-270.0,
true,
));
points.extend(circle_points(
w / 2.0 + radius * 2.0,
radius,
radius,
20,
-90.0,
-180.0,
true,
));
points.push((-w / 2.0 - radius, -h / 2.0));
points.extend(circle_points(w / 2.0, h / 2.0, radius, 20, 0.0, 90.0, true));
let mut rect_points: Vec<(f64, f64)> = Vec::new();
rect_points.extend([(w / 2.0, -h / 2.0 - radius), (-w / 2.0, -h / 2.0 - radius)]);
rect_points.extend(circle_points(
w / 2.0,
-h / 2.0,
radius,
20,
-90.0,
0.0,
true,
));
rect_points.push((-w / 2.0 - radius, -radius));
rect_points.extend(circle_points(
w / 2.0 + w * 0.1,
-radius,
radius,
20,
-180.0,
-270.0,
true,
));
rect_points.extend(circle_points(
w / 2.0 + w * 0.1,
radius,
radius,
20,
-90.0,
-180.0,
true,
));
rect_points.push((-w / 2.0 - radius, h / 2.0));
rect_points.extend(circle_points(w / 2.0, h / 2.0, radius, 20, 0.0, 90.0, true));
rect_points.extend([(-w / 2.0, h / 2.0 + radius), (w / 2.0, h / 2.0 + radius)]);
for p in points.iter_mut().chain(rect_points.iter_mut()) {
p.0 += group_tx;
}
let mut all_points: Vec<(f64, f64)> =
Vec::with_capacity(points.len() + rect_points.len());
all_points.extend(points);
all_points.extend(rect_points);
let (min_x, min_y, max_x, max_y) =
bbox_of_points(&all_points).unwrap_or((-w / 2.0, -h / 2.0, w / 2.0, h / 2.0));
((max_x - min_x).max(0.0), (max_y - min_y).max(0.0))
}
"brace-r" => {
let w = text_w + p;
let h = text_h + p;
let radius = (h * 0.1).max(5.0);
let group_tx = -radius;
let mut rect_points: Vec<(f64, f64)> = Vec::new();
rect_points.extend([(-w / 2.0, -h / 2.0 - radius), (w / 2.0, -h / 2.0 - radius)]);
rect_points.extend(circle_points(
w / 2.0,
-h / 2.0,
radius,
20,
-90.0,
0.0,
false,
));
rect_points.push((w / 2.0 + radius, -radius));
rect_points.extend(circle_points(
w / 2.0 + radius * 2.0,
-radius,
radius,
20,
-180.0,
-270.0,
false,
));
rect_points.extend(circle_points(
w / 2.0 + radius * 2.0,
radius,
radius,
20,
-90.0,
-180.0,
false,
));
rect_points.push((w / 2.0 + radius, h / 2.0));
rect_points.extend(circle_points(
w / 2.0,
h / 2.0,
radius,
20,
0.0,
90.0,
false,
));
rect_points.extend([(w / 2.0, h / 2.0 + radius), (-w / 2.0, h / 2.0 + radius)]);
for p in &mut rect_points {
p.0 += group_tx;
}
let (min_x, min_y, max_x, max_y) =
bbox_of_points(&rect_points).unwrap_or((-w / 2.0, -h / 2.0, w / 2.0, h / 2.0));
((max_x - min_x).max(0.0), (max_y - min_y).max(0.0))
}
"braces" => {
let w = text_w + p;
let h = text_h + p;
let radius = (h * 0.1).max(5.0);
let group_tx = radius - radius / 4.0;
let mut rect_points: Vec<(f64, f64)> = Vec::new();
rect_points.extend([(w / 2.0, -h / 2.0 - radius), (-w / 2.0, -h / 2.0 - radius)]);
rect_points.extend(circle_points(
w / 2.0,
-h / 2.0,
radius,
20,
-90.0,
0.0,
true,
));
rect_points.push((-w / 2.0 - radius, -radius));
rect_points.extend(circle_points(
w / 2.0 + radius * 2.0,
-radius,
radius,
20,
-180.0,
-270.0,
true,
));
rect_points.extend(circle_points(
w / 2.0 + radius * 2.0,
radius,
radius,
20,
-90.0,
-180.0,
true,
));
rect_points.push((-w / 2.0 - radius, h / 2.0));
rect_points.extend(circle_points(w / 2.0, h / 2.0, radius, 20, 0.0, 90.0, true));
rect_points.extend([
(-w / 2.0, h / 2.0 + radius),
(w / 2.0 - radius - radius / 2.0, h / 2.0 + radius),
]);
rect_points.extend(circle_points(
-w / 2.0 + radius + radius / 2.0,
-h / 2.0,
radius,
20,
-90.0,
-180.0,
true,
));
rect_points.push((w / 2.0 - radius / 2.0, radius));
rect_points.extend(circle_points(
-w / 2.0 - radius / 2.0,
-radius,
radius,
20,
0.0,
90.0,
true,
));
rect_points.extend(circle_points(
-w / 2.0 - radius / 2.0,
radius,
radius,
20,
-90.0,
0.0,
true,
));
rect_points.push((w / 2.0 - radius / 2.0, -radius));
rect_points.extend(circle_points(
-w / 2.0 + radius + radius / 2.0,
h / 2.0,
radius,
30,
-180.0,
-270.0,
true,
));
for p in &mut rect_points {
p.0 += group_tx;
}
let (min_x, min_y, max_x, max_y) =
bbox_of_points(&rect_points).unwrap_or((-w / 2.0, -h / 2.0, w / 2.0, h / 2.0));
((max_x - min_x).max(0.0), (max_y - min_y).max(0.0))
}
"lean_right" | "lean-r" | "lean-right" | "in-out" | "lean_left" | "lean-l"
| "lean-left" | "out-in" | "trapezoid" | "trap-b" | "priority" | "trapezoid-bottom" => {
let w = text_w + p;
let h = text_h + p;
(w + h, h)
}
"inv_trapezoid" | "inv-trapezoid" | "trap-t" | "manual" | "trapezoid-top" => {
let w = text_w + 2.0 * p;
let h = text_h + 2.0 * p;
(w + h, h)
}
"odd" | "rect_left_inv_arrow" => {
let w = text_w + p;
let h = text_h + p;
(w + h / 4.0, h)
}
"ellipse" => (text_w + 2.0 * p, text_h + 2.0 * p),
_ => (text_w + 4.0 * p, text_h + 2.0 * p),
}
}
pub(crate) fn flowchart_node_render_dimensions(
layout_shape: Option<&str>,
metrics: crate::text::TextMetrics,
padding: f64,
) -> (f64, f64) {
node_render_dimensions(layout_shape, metrics, padding)
}
pub(super) struct NodeLayoutDimensionsRequest<'a> {
pub(super) layout_shape: Option<&'a str>,
pub(super) layout_direction: &'a str,
pub(super) metrics: crate::text::TextMetrics,
pub(super) padding: f64,
pub(super) state_padding: f64,
pub(super) wrap_mode: crate::text::WrapMode,
pub(super) node_icon: Option<&'a str>,
pub(super) node_img: Option<&'a str>,
pub(super) node_pos: Option<&'a str>,
pub(super) node_asset_width: Option<f64>,
pub(super) node_asset_height: Option<f64>,
}
pub(super) fn node_layout_dimensions(req: NodeLayoutDimensionsRequest<'_>) -> (f64, f64) {
let NodeLayoutDimensionsRequest {
layout_shape,
layout_direction,
metrics,
padding,
state_padding,
wrap_mode,
node_icon,
node_img,
node_pos,
node_asset_width,
node_asset_height,
} = req;
fn chromium_tilted_cylinder_bbox_width_shrink(height: f64) -> f64 {
if (height - 79.300_003_051_757_81).abs() < 1e-5 || (height - 79.3).abs() < 1e-5 {
return 0.006_711_671_355_788;
}
if (height - 79.5).abs() < 1e-5 {
return 0.006_714_202_955_465;
}
0.0
}
let shape = layout_shape.unwrap_or("squareRect");
if (shape == "imageSquare" || shape == "icon" || shape.starts_with("icon"))
&& (node_icon.is_some() || node_img.is_some())
{
if shape == "imageSquare" {
if node_img.is_some_and(|s| !s.trim().is_empty()) {
let asset_h = node_asset_height.unwrap_or(60.0).max(1.0);
let asset_w = node_asset_width.unwrap_or(asset_h).max(1.0);
let aspect_ratio = if asset_h > 0.0 {
asset_w / asset_h
} else {
1.0
};
let image_width = if node_asset_height.is_some() {
asset_h * aspect_ratio
} else {
asset_w.max(if metrics.width > 0.0 { 200.0 } else { 0.0 })
};
let image_height = if aspect_ratio != 0.0 {
image_width / aspect_ratio
} else {
asset_h
};
let has_label = metrics.width > 0.0 && metrics.height > 0.0;
let label_padding = if has_label { 8.0 } else { 0.0 };
let label_bbox_w = if has_label { metrics.width } else { 0.0 };
let label_bbox_h = if has_label { metrics.height + 4.0 } else { 0.0 };
return (
image_width.max(label_bbox_w),
image_height + label_padding + label_bbox_h,
);
}
} else if node_icon.is_some_and(|s| !s.trim().is_empty()) {
let has_label = metrics.width > 0.0 && metrics.height > 0.0;
let label_padding = if has_label { 8.0 } else { 0.0 };
let label_bbox_w = if has_label { metrics.width + 4.0 } else { 0.0 };
let label_bbox_h = if has_label { metrics.height + 4.0 } else { 0.0 };
let asset_h = node_asset_height.unwrap_or(48.0).max(1.0);
let asset_w = node_asset_width.unwrap_or(48.0).max(1.0);
let icon_size = asset_h.max(asset_w);
let icon_outer_size = if shape == "iconSquare" {
icon_size + padding
} else {
icon_size
};
let outer_w = icon_outer_size.max(label_bbox_w);
let outer_h = icon_outer_size + label_padding + label_bbox_h;
let _ = node_pos;
return (outer_w, outer_h);
}
}
if matches!(shape, "fork" | "join") {
let extra = (state_padding / 2.0).max(0.0);
let (render_w, render_h) = if layout_direction.eq_ignore_ascii_case("LR") {
(10.0, 70.0)
} else {
(70.0, 10.0)
};
return (render_w + extra, render_h + extra);
}
let (render_w, render_h) = node_render_dimensions(Some(shape), metrics, padding);
if matches!(shape, "stadium" | "terminal" | "pill") {
fn include_circle_points(
center_x: f64,
center_y: f64,
radius: f64,
table: &[(f64, f64)],
mut include: impl FnMut(f64, f64),
) {
for &(cos, sin) in table {
let x = center_x + radius * cos;
let y = center_y + radius * sin;
include(-x, -y);
}
}
let w = render_w.max(0.0);
let h = render_h.max(0.0);
if w > 0.0 && h > 0.0 {
let radius = h / 2.0;
let mut min_x = f64::INFINITY;
let mut max_x = f64::NEG_INFINITY;
let mut min_y = f64::INFINITY;
let mut max_y = f64::NEG_INFINITY;
let mut include = |x: f64, y: f64| {
min_x = min_x.min(x);
max_x = max_x.max(x);
min_y = min_y.min(y);
max_y = max_y.max(y);
};
include(-w / 2.0 + radius, -h / 2.0);
include(w / 2.0 - radius, -h / 2.0);
include_circle_points(
-w / 2.0 + radius,
0.0,
radius,
&crate::trig_tables::STADIUM_ARC_90_270_COS_SIN,
&mut include,
);
include(w / 2.0 - radius, h / 2.0);
include_circle_points(
w / 2.0 - radius,
0.0,
radius,
&crate::trig_tables::STADIUM_ARC_270_450_COS_SIN,
&mut include,
);
if min_x.is_finite() && max_x.is_finite() && min_y.is_finite() && max_y.is_finite() {
let bbox_w = (max_x - min_x).max(0.0);
let bbox_h = (max_y - min_y).max(0.0);
let w_f32 = bbox_w as f32;
let h_f32 = bbox_h as f32;
if w_f32.is_finite()
&& h_f32.is_finite()
&& w_f32.is_sign_positive()
&& h_f32.is_sign_positive()
{
return (w_f32 as f64, h_f32 as f64);
}
return (bbox_w, bbox_h);
}
}
}
if matches!(shape, "hexagon" | "hex" | "prepare")
&& matches!(wrap_mode, crate::text::WrapMode::HtmlLike)
{
let w_f32 = render_w as f32;
let h_f32 = render_h as f32;
if w_f32.is_finite()
&& h_f32.is_finite()
&& w_f32.is_sign_positive()
&& h_f32.is_sign_positive()
{
return (w_f32 as f64, h_f32 as f64);
}
}
if matches!(
shape,
"cylinder" | "cyl" | "db" | "database" | "lin-cyl" | "disk" | "lined-cylinder"
) {
let h_f32 = render_h as f32;
if h_f32.is_finite() && h_f32.is_sign_positive() {
let bits = h_f32.to_bits();
if bits < u32::MAX {
let bumped = f32::from_bits(bits + 1) as f64;
return (render_w, bumped);
}
}
}
if matches!(shape, "h-cyl" | "das" | "horizontal-cylinder") {
let h_f32 = render_h as f32;
let bbox_h = if h_f32.is_finite() && h_f32.is_sign_positive() {
h_f32 as f64
} else {
render_h
};
let shrink = chromium_tilted_cylinder_bbox_width_shrink(bbox_h);
return ((render_w - shrink).max(0.0), bbox_h.max(0.0));
}
(render_w, render_h)
}