use fframes::Svgr;
use crate::ctx::RenderCtx;
use crate::motion::REFERENCE_VMIN;
#[must_use]
pub fn tracked_width(
ctx: &RenderCtx<'_>,
family: &str,
size: f32,
text: &str,
tracking_px: f32,
) -> f32 {
let glyphs = text.chars().count();
let advances = ctx.text_width(family, size, text);
if glyphs > 1 {
advances + (glyphs - 1) as f32 * tracking_px
} else {
advances
}
}
#[must_use]
pub fn tracking_px_from_em(tracking_em: f32, size: f32) -> f32 {
tracking_em * size
}
fn advances_at(ctx: &RenderCtx<'_>, family: &str, weight: u16, size: f32, text: &str) -> f32 {
match ctx.meter_at(family, weight) {
Some(meter) => meter.text_width(text, size),
None => ctx.text_width(family, size, text),
}
}
#[must_use]
pub fn tracked_width_at(
ctx: &RenderCtx<'_>,
family: &str,
weight: u16,
size: f32,
text: &str,
tracking_px: f32,
) -> f32 {
let glyphs = text.chars().count();
let advances = advances_at(ctx, family, weight, size, text);
if glyphs > 1 {
advances + (glyphs - 1) as f32 * tracking_px
} else {
advances
}
}
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn text_node(
ctx: &RenderCtx<'_>,
text: &str,
center_x: f32,
baseline_y: f32,
family: String,
size: f32,
weight: u16,
tracking_px: f32,
fill: String,
opacity: f32,
) -> Svgr<'static> {
let width = tracked_width_at(ctx, family.as_str(), weight, size, text, tracking_px);
let x = center_x - width / 2.0;
let weight = crate::ctx::font_weight_attr(weight);
fframes::svgr!(
<text x={x} y={baseline_y} font-family={family} font-size={size} font-weight={weight}
letter-spacing={tracking_px} fill={fill} opacity={opacity}>{text.to_owned()}</text>
)
}
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn text_node_left(
text: &str,
left_x: f32,
baseline_y: f32,
family: String,
size: f32,
weight: u16,
tracking_px: f32,
fill: String,
opacity: f32,
) -> Svgr<'static> {
let weight = crate::ctx::font_weight_attr(weight);
fframes::svgr!(
<text x={left_x} y={baseline_y} font-family={family} font-size={size} font-weight={weight}
letter-spacing={tracking_px} fill={fill} opacity={opacity}>{text.to_owned()}</text>
)
}
#[must_use]
pub fn baseline_for_centred_box(
ctx: &RenderCtx<'_>,
family: &str,
size: f32,
center_y: f32,
) -> f32 {
let box_height = ctx
.meter(family)
.map_or(size * 1.2, |meter| meter.line_height(size));
center_y - box_height / 2.0 + ctx.baseline_offset(family, size, box_height)
}
#[must_use]
pub fn unit(ctx: &RenderCtx<'_>) -> f32 {
ctx.vmin() / REFERENCE_VMIN
}
#[must_use]
pub fn default_face(ctx: &RenderCtx<'_>) -> String {
const PREFERRED: &str = "Inter";
if ctx.meter(PREFERRED).is_some() {
PREFERRED.to_owned()
} else {
ctx.sans()
}
}
#[must_use]
pub fn resolve_face(
config_family: Option<&String>,
theme_family: Option<&String>,
ctx: &RenderCtx<'_>,
) -> String {
config_family
.or(theme_family)
.cloned()
.unwrap_or_else(|| default_face(ctx))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn em_tracking_scales_with_the_font_size() {
assert!((tracking_px_from_em(-0.03, 108.0) + 3.24).abs() < 1e-6);
assert!((tracking_px_from_em(0.0, 108.0)).abs() < 1e-6);
}
}