use super::{TextMetrics, TextStyle, WrapMode};
pub(crate) const MERMAID_CREATE_TEXT_DEFAULT_WIDTH_PX: f64 = 200.0;
pub trait TextMeasurer {
#[doc(hidden)]
fn cancellation_requested(&self) -> bool {
false
}
#[doc(hidden)]
#[allow(private_interfaces)]
fn builtin_operation_carrier(
&self,
_operation: crate::environment::TextMeasurementOperation,
) -> Option<crate::environment::BuiltinTextMeasurementOperationCarrier> {
None
}
#[doc(hidden)]
#[allow(private_interfaces)]
fn begin_svg_text_computed_length(
&self,
_style: &TextStyle,
) -> Option<crate::environment::BuiltinSvgComputedLength> {
None
}
fn measure(&self, text: &str, style: &TextStyle) -> TextMetrics;
fn measure_svg_text_computed_length_px(&self, text: &str, style: &TextStyle) -> f64 {
self.measure_svg_simple_text_bbox_width_px(text, style)
}
fn measure_svg_text_bbox_x(&self, text: &str, style: &TextStyle) -> (f64, f64) {
let m = self.measure(text, style);
let half = (m.width.max(0.0)) / 2.0;
(half, half)
}
fn measure_svg_text_bbox_x_with_ascii_overhang(
&self,
text: &str,
style: &TextStyle,
) -> (f64, f64) {
self.measure_svg_text_bbox_x(text, style)
}
fn measure_svg_title_bbox_x(&self, text: &str, style: &TextStyle) -> (f64, f64) {
self.measure_svg_text_bbox_x(text, style)
}
fn measure_svg_simple_text_bbox_width_px(&self, text: &str, style: &TextStyle) -> f64 {
let (l, r) = self.measure_svg_title_bbox_x(text, style);
(l + r).max(0.0)
}
fn measure_svg_raw_text_bbox_width_px(&self, text: &str, style: &TextStyle) -> f64 {
self.measure_svg_simple_text_bbox_width_px(text, style)
}
fn measure_svg_raw_text_bbox_height_px(&self, text: &str, style: &TextStyle) -> f64 {
self.measure(text, style).height.max(0.0)
}
fn measure_svg_text_bounding_client_rect_width_px(&self, text: &str, style: &TextStyle) -> f64 {
self.measure_svg_raw_text_bbox_width_px(text, style)
}
fn measure_svg_tspan_text_bbox_width_px(&self, text: &str, style: &TextStyle) -> f64 {
self.measure_svg_raw_text_bbox_width_px(text, style)
}
fn measure_svg_tspan_text_bbox_height_px(&self, text: &str, style: &TextStyle) -> f64 {
self.measure(text, style).height.max(0.0)
}
fn measure_svg_create_text_bbox_y_offset_px(&self, _text: &str, _style: &TextStyle) -> f64 {
0.0
}
fn measure_svg_create_text_middle_bbox_y_offset_px(
&self,
_text: &str,
_style: &TextStyle,
) -> f64 {
0.0
}
fn measure_svg_simple_text_bbox_width_for_wrap_px(&self, text: &str, style: &TextStyle) -> f64 {
self.measure_svg_simple_text_bbox_width_px(text, style)
}
fn measure_mermaid_calculate_text_dimensions(
&self,
text: &str,
style: &TextStyle,
) -> TextMetrics {
TextMetrics {
width: self.measure_svg_simple_text_bbox_width_for_wrap_px(text, style),
height: self.measure_svg_simple_text_bbox_height_px(text, style),
line_count: 1,
}
}
fn measure_canvas_text_width_px(&self, text: &str, style: &TextStyle) -> f64 {
self.measure_svg_text_computed_length_px(text, style)
}
fn measure_svg_simple_text_bbox_height_px(&self, text: &str, style: &TextStyle) -> f64 {
let m = self.measure(text, style);
m.height.max(0.0)
}
fn measure_wrapped(
&self,
text: &str,
style: &TextStyle,
max_width: Option<f64>,
wrap_mode: WrapMode,
) -> TextMetrics {
let _ = max_width;
let _ = wrap_mode;
self.measure(text, style)
}
fn measure_wrapped_with_raw_width(
&self,
text: &str,
style: &TextStyle,
max_width: Option<f64>,
wrap_mode: WrapMode,
) -> (TextMetrics, Option<f64>) {
(
self.measure_wrapped(text, style, max_width, wrap_mode),
None,
)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct MermaidTextDimensions {
pub(crate) width: i64,
pub(crate) height: i64,
pub(crate) line_height: i64,
}
fn try_measure_mermaid_text_dimensions_for_family<E>(
measurer: &dyn TextMeasurer,
text: &str,
style: &TextStyle,
checkpoint: &mut impl FnMut() -> std::result::Result<(), E>,
) -> std::result::Result<MermaidTextDimensions, E> {
if text.is_empty() {
return Ok(MermaidTextDimensions::default());
}
let mut dimensions = MermaidTextDimensions::default();
for line in super::split_html_br_lines(text) {
checkpoint()?;
let measured_line = if line.is_empty() { "\u{200b}" } else { line };
checkpoint()?;
let measured = measurer.measure_mermaid_calculate_text_dimensions(measured_line, style);
checkpoint()?;
let width = measured.width.max(0.0).round() as i64;
let height = measured.height.max(0.0).round() as i64;
dimensions.width = dimensions.width.max(width);
dimensions.height += height;
dimensions.line_height = dimensions.line_height.max(height);
}
Ok(dimensions)
}
fn try_measure_mermaid_text_dimensions<E>(
measurer: &dyn TextMeasurer,
text: &str,
configured_style: &TextStyle,
checkpoint: &mut impl FnMut() -> std::result::Result<(), E>,
) -> std::result::Result<MermaidTextDimensions, E> {
let mut sans_style = configured_style.clone();
sans_style.font_family = Some("sans-serif".to_string());
let sans =
try_measure_mermaid_text_dimensions_for_family(measurer, text, &sans_style, checkpoint)?;
let configured = try_measure_mermaid_text_dimensions_for_family(
measurer,
text,
configured_style,
checkpoint,
)?;
if sans.width > configured.width
&& sans.height > configured.height
&& sans.line_height > configured.line_height
{
Ok(sans)
} else {
Ok(configured)
}
}
pub(crate) fn measure_mermaid_text_dimensions(
measurer: &dyn TextMeasurer,
text: &str,
configured_style: &TextStyle,
) -> MermaidTextDimensions {
let mut checkpoint = || Ok::<(), std::convert::Infallible>(());
match try_measure_mermaid_text_dimensions(measurer, text, configured_style, &mut checkpoint) {
Ok(dimensions) => dimensions,
Err(error) => match error {},
}
}
pub(crate) fn measure_mermaid_text_dimensions_with_checkpoint(
measurer: &dyn TextMeasurer,
text: &str,
configured_style: &TextStyle,
mut checkpoint: impl FnMut() -> crate::Result<()>,
) -> crate::Result<MermaidTextDimensions> {
try_measure_mermaid_text_dimensions(measurer, text, configured_style, &mut checkpoint)
}