use crate::graph::geometry::FPoint;
use crate::graph::measure::{
DEFAULT_LABEL_PADDING_X, DEFAULT_LABEL_PADDING_Y, GraphTextStyleKey, TextMetricsProvider,
edge_label_dimensions_wrapped_for_provider_and_style,
measure_text_with_padding_for_provider_and_style,
};
use crate::render::svg::{SvgWriter, escape_text, fmt_f64};
pub(super) struct TextRenderStyle<'a> {
pub(super) color: &'a str,
pub(super) extra_attrs: &'a str,
pub(super) text_style: Option<&'a GraphTextStyleKey>,
pub(super) background: Option<BackgroundStyle<'a>>,
}
pub(super) struct BackgroundStyle<'a> {
pub(super) fill: &'a str,
pub(super) extra_attrs: &'a str,
pub(super) size: Option<(f64, f64)>,
}
pub(super) const LABEL_BG_PAD_X: f64 = DEFAULT_LABEL_PADDING_X;
pub(super) const LABEL_BG_PAD_Y: f64 = DEFAULT_LABEL_PADDING_Y;
pub(super) fn render_text_centered(
writer: &mut SvgWriter,
center: FPoint,
text: &str,
metrics: &dyn TextMetricsProvider,
scale: f64,
style: TextRenderStyle<'_>,
) {
render_text_centered_with_wrap(writer, center, text, None, metrics, scale, style);
}
pub(super) fn render_text_centered_with_wrap(
writer: &mut SvgWriter,
center: FPoint,
text: &str,
wrapped_lines: Option<&[String]>,
metrics: &dyn TextMetricsProvider,
scale: f64,
style: TextRenderStyle<'_>,
) {
let measured_style = style
.text_style
.cloned()
.unwrap_or_else(|| GraphTextStyleKey::default_provider_style(metrics));
if let Some(bg) = &style.background {
let (w, h) = bg.size.unwrap_or_else(|| match wrapped_lines {
Some(lines) => measure_wrapped_with_padding(
metrics,
&measured_style,
lines,
LABEL_BG_PAD_X,
LABEL_BG_PAD_Y,
),
None => measure_text_with_padding_for_provider_and_style(
metrics,
&measured_style,
text,
LABEL_BG_PAD_X,
LABEL_BG_PAD_Y,
),
});
let rect_w = w * scale;
let rect_h = h * scale;
let rect = format!(
"<rect x=\"{x}\" y=\"{y}\" width=\"{w}\" height=\"{h}\" fill=\"{fill}\"{extra} />",
x = fmt_f64(center.x - rect_w / 2.0),
y = fmt_f64(center.y - rect_h / 2.0),
w = fmt_f64(rect_w),
h = fmt_f64(rect_h),
fill = bg.fill,
extra = bg.extra_attrs,
);
writer.push_line(&rect);
}
let owned_fallback: Vec<&str>;
let lines: &[&str] = match wrapped_lines {
Some(wrapped) => {
owned_fallback = wrapped.iter().map(String::as_str).collect();
&owned_fallback
}
None => {
owned_fallback = text.split('\n').collect();
&owned_fallback
}
};
if lines.len() == 1 {
let line = format!(
"<text x=\"{x}\" y=\"{y}\" text-anchor=\"middle\" dominant-baseline=\"middle\" fill=\"{color}\"{extra_attrs}>{text}</text>",
x = fmt_f64(center.x),
y = fmt_f64(center.y),
color = style.color,
extra_attrs = style.extra_attrs,
text = escape_text(lines[0])
);
writer.push_line(&line);
return;
}
let line_height = metrics.line_height_for_style(&measured_style) * scale;
let total_height = line_height * (lines.len().saturating_sub(1) as f64);
let start_y = center.y - total_height / 2.0;
for (idx, line_text) in lines.iter().enumerate() {
let line_y = start_y + line_height * idx as f64;
let line = format!(
"<text x=\"{x}\" y=\"{y}\" text-anchor=\"middle\" dominant-baseline=\"middle\" fill=\"{color}\"{extra_attrs}>{text}</text>",
x = fmt_f64(center.x),
y = fmt_f64(line_y),
color = style.color,
extra_attrs = style.extra_attrs,
text = escape_text(line_text)
);
writer.push_line(&line);
}
}
fn measure_wrapped_with_padding(
metrics: &dyn TextMetricsProvider,
style: &GraphTextStyleKey,
lines: &[String],
padding_x: f64,
padding_y: f64,
) -> (f64, f64) {
let (w, h) = edge_label_dimensions_wrapped_for_provider_and_style(metrics, style, lines);
let raw_w = w - 2.0 * metrics.label_padding_x();
let raw_h = h - 2.0 * metrics.label_padding_y();
(raw_w + 2.0 * padding_x, raw_h + 2.0 * padding_y)
}
pub(super) fn font_attrs_for_style(
default: &GraphTextStyleKey,
style: &GraphTextStyleKey,
) -> String {
let mut attrs = String::new();
if style.font_family != default.font_family {
attrs.push_str(" font-family=\"");
attrs.push_str(&escape_text(&style.font_family));
attrs.push('"');
}
if style.font_size_mpx != default.font_size_mpx {
attrs.push_str(" font-size=\"");
attrs.push_str(&fmt_f64(style.font_size_px()));
attrs.push('"');
}
if style.font_style != default.font_style {
attrs.push_str(" font-style=\"");
attrs.push_str(&escape_text(&style.font_style));
attrs.push('"');
}
if style.font_weight != default.font_weight {
attrs.push_str(" font-weight=\"");
attrs.push_str(&escape_text(&style.font_weight));
attrs.push('"');
}
attrs
}