#[derive(Debug, Clone, Copy, PartialEq)]
pub enum HAnchor {
Left,
LeftInk,
Center,
CenterInk,
RightInk,
Right,
Fraction(f32),
}
impl Default for HAnchor {
fn default() -> Self {
HAnchor::Left
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum VAnchor {
Top,
TopInk,
FirstLine,
Center,
CenterInk,
LastLine,
BottomInk,
Bottom,
Fraction(f32),
}
impl Default for VAnchor {
fn default() -> Self {
VAnchor::Top
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct RichAnchor {
pub h: HAnchor,
pub v: VAnchor,
}
impl RichAnchor {
pub fn top_left() -> Self {
Self {
h: HAnchor::Left,
v: VAnchor::Top,
}
}
pub fn center() -> Self {
Self {
h: HAnchor::Center,
v: VAnchor::Center,
}
}
pub fn center_ink() -> Self {
Self {
h: HAnchor::CenterInk,
v: VAnchor::CenterInk,
}
}
pub fn first_line_baseline() -> Self {
Self {
h: HAnchor::Left,
v: VAnchor::FirstLine,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AnchorOffsets {
pub ref_x: f32,
pub ref_y: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LayoutBounds {
pub width: f32,
pub height: f32,
pub ink_left: f32,
pub ink_right: f32,
pub ink_top: f32,
pub ink_bottom: f32,
pub first_baseline: f32,
pub last_baseline: f32,
}
impl LayoutBounds {
pub fn resolve(&self, anchor: RichAnchor) -> AnchorOffsets {
AnchorOffsets {
ref_x: match anchor.h {
HAnchor::Left => 0.0,
HAnchor::LeftInk => self.ink_left,
HAnchor::Center => self.width * 0.5,
HAnchor::CenterInk => (self.ink_left + self.ink_right) * 0.5,
HAnchor::RightInk => self.ink_right,
HAnchor::Right => self.width,
HAnchor::Fraction(f) => self.width * f,
},
ref_y: match anchor.v {
VAnchor::Top => 0.0,
VAnchor::TopInk => self.ink_top,
VAnchor::FirstLine => self.first_baseline,
VAnchor::Center => self.height * 0.5,
VAnchor::CenterInk => (self.ink_top + self.ink_bottom) * 0.5,
VAnchor::LastLine => self.last_baseline,
VAnchor::BottomInk => self.ink_bottom,
VAnchor::Bottom => self.height,
VAnchor::Fraction(f) => self.height * f,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn bounds() -> LayoutBounds {
LayoutBounds {
width: 200.0,
height: 100.0,
ink_left: 5.0,
ink_right: 190.0,
ink_top: 8.0,
ink_bottom: 92.0,
first_baseline: 20.0,
last_baseline: 80.0,
}
}
#[test]
fn left_top_resolves_to_origin() {
let o = bounds().resolve(RichAnchor::top_left());
assert_eq!(o.ref_x, 0.0);
assert_eq!(o.ref_y, 0.0);
}
#[test]
fn center_resolves_to_midpoint() {
let o = bounds().resolve(RichAnchor::center());
assert_eq!(o.ref_x, 100.0);
assert_eq!(o.ref_y, 50.0);
}
#[test]
fn ink_center_uses_ink_extents() {
let o = bounds().resolve(RichAnchor::center_ink());
assert_eq!(o.ref_x, (5.0 + 190.0) * 0.5);
assert_eq!(o.ref_y, (8.0 + 92.0) * 0.5);
}
#[test]
fn first_line_baseline_maps_to_baseline_field() {
let o = bounds().resolve(RichAnchor::first_line_baseline());
assert_eq!(o.ref_x, 0.0);
assert_eq!(o.ref_y, 20.0);
}
#[test]
fn fraction_scales_bounds() {
let a = RichAnchor {
h: HAnchor::Fraction(0.25),
v: VAnchor::Fraction(0.75),
};
let o = bounds().resolve(a);
assert_eq!(o.ref_x, 50.0);
assert_eq!(o.ref_y, 75.0);
}
#[test]
fn last_line_baseline() {
let a = RichAnchor {
h: HAnchor::Left,
v: VAnchor::LastLine,
};
let o = bounds().resolve(a);
assert_eq!(o.ref_y, 80.0);
}
}