#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct TextDecoration {
pub underline: bool,
pub strikethrough: bool,
}
impl TextDecoration {
pub const NONE: Self = Self { underline: false, strikethrough: false };
pub const fn underline() -> Self {
Self { underline: true, strikethrough: false }
}
pub const fn strikethrough() -> Self {
Self { underline: false, strikethrough: true }
}
pub const fn is_none(&self) -> bool {
!self.underline && !self.strikethrough
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum VerticalAlign {
#[default]
Baseline,
Super,
Sub,
}
pub const SCRIPT_SCALE: f64 = 0.65;
pub const SUPERSCRIPT_SHIFT_EM: f64 = 0.58;
pub const SUBSCRIPT_SHIFT_EM: f64 = 0.21;
impl VerticalAlign {
pub(crate) fn shape_font(self, font: crate::model::FontSpec) -> crate::model::FontSpec {
match self {
VerticalAlign::Baseline => font,
VerticalAlign::Super | VerticalAlign::Sub => {
crate::model::FontSpec { size_px: font.size_px * SCRIPT_SCALE, ..font }
}
}
}
pub(crate) fn baseline_shift(self, nominal_size_px: f64) -> f64 {
match self {
VerticalAlign::Baseline => 0.0,
VerticalAlign::Super => -(SUPERSCRIPT_SHIFT_EM * nominal_size_px),
VerticalAlign::Sub => SUBSCRIPT_SHIFT_EM * nominal_size_px,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn text_decoration_default_is_none() {
let d = TextDecoration::default();
assert!(d.is_none());
assert_eq!(d, TextDecoration::NONE);
}
#[test]
fn text_decoration_constructors_set_exactly_one_flag() {
assert_eq!(TextDecoration::underline(), TextDecoration { underline: true, strikethrough: false });
assert_eq!(TextDecoration::strikethrough(), TextDecoration { underline: false, strikethrough: true });
assert!(!TextDecoration::underline().is_none());
}
#[test]
fn vertical_align_default_is_baseline_with_no_shift_and_nominal_font() {
let font = crate::model::FontSpec::new(uzor::fonts::FontFamily::Roboto, 16.0);
assert_eq!(VerticalAlign::default(), VerticalAlign::Baseline);
assert_eq!(VerticalAlign::Baseline.baseline_shift(16.0), 0.0);
assert_eq!(VerticalAlign::Baseline.shape_font(font), font);
}
#[test]
fn superscript_shifts_up_and_shrinks_the_shape_font() {
let font = crate::model::FontSpec::new(uzor::fonts::FontFamily::Roboto, 20.0);
assert!(VerticalAlign::Super.baseline_shift(20.0) < 0.0, "superscript must raise (negative y shift)");
let shaped = VerticalAlign::Super.shape_font(font);
assert!((shaped.size_px - 20.0 * SCRIPT_SCALE).abs() < 1e-9);
}
#[test]
fn subscript_shifts_down_and_shrinks_the_shape_font() {
let font = crate::model::FontSpec::new(uzor::fonts::FontFamily::Roboto, 20.0);
assert!(VerticalAlign::Sub.baseline_shift(20.0) > 0.0, "subscript must lower (positive y shift)");
let shaped = VerticalAlign::Sub.shape_font(font);
assert!((shaped.size_px - 20.0 * SCRIPT_SCALE).abs() < 1e-9);
}
}