use crate::ctx::RenderCtx;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ReferenceFrame {
pub width: f32,
pub height: f32,
pub scale: f32,
pub left: f32,
pub top: f32,
}
impl ReferenceFrame {
#[must_use]
pub fn fit_in(canvas_width: f32, canvas_height: f32, width: f32, height: f32) -> Self {
if width <= 0.0 || height <= 0.0 {
return Self {
width,
height,
scale: 1.0,
left: 0.0,
top: 0.0,
};
}
let scale = (canvas_width / width).min(canvas_height / height);
Self {
width,
height,
scale,
left: (canvas_width - width * scale) / 2.0,
top: (canvas_height - height * scale) / 2.0,
}
}
#[must_use]
pub fn fit(ctx: &RenderCtx<'_>, width: f32, height: f32) -> Self {
Self::fit_in(ctx.width(), ctx.height(), width, height)
}
#[must_use]
pub fn transform(&self) -> String {
format!(
"translate({} {}) scale({})",
self.left, self.top, self.scale
)
}
#[must_use]
pub fn point(&self, x: f32, y: f32) -> (f32, f32) {
(self.left + x * self.scale, self.top + y * self.scale)
}
#[must_use]
pub fn length(&self, value: f32) -> f32 {
value * self.scale
}
}
#[cfg(test)]
mod tests {
use super::*;
const REF_W: f32 = 718.0;
const REF_H: f32 = 394.0;
#[test]
fn a_wider_canvas_is_limited_by_width() {
let frame = ReferenceFrame::fit_in(1280.0, 720.0, REF_W, REF_H);
assert!((frame.scale - 1.782_729_8).abs() < 1e-5, "{}", frame.scale);
assert!(frame.left.abs() < 1e-4);
assert!((frame.top - 8.76).abs() < 0.05, "{}", frame.top);
}
#[test]
fn at_1080p_the_scale_is_not_the_design_unit() {
let frame = ReferenceFrame::fit_in(1920.0, 1080.0, REF_W, REF_H);
assert!((frame.scale - 2.674_4).abs() < 0.001, "{}", frame.scale);
let unit: f32 = 1080.0 / 100.0 / 7.2;
assert!((unit - 1.5).abs() < 1e-6, "the design unit is 1.5 at 1080p");
assert!(
(frame.scale / unit - 1.782_9).abs() < 0.001,
"using the design unit would render the scene at {:.3}x",
unit / frame.scale
);
}
#[test]
fn a_tall_canvas_is_still_width_limited() {
let frame = ReferenceFrame::fit_in(1080.0, 1920.0, REF_W, REF_H);
assert!((frame.scale - 1.504_178_3).abs() < 1e-5, "{}", frame.scale);
assert!(frame.left.abs() < 1e-4, "left {}", frame.left);
assert!(
(frame.top - (1920.0 - REF_H * frame.scale) / 2.0).abs() < 1e-3,
"top {}",
frame.top
);
assert!((frame.top - 663.68).abs() < 0.05, "top {}", frame.top);
}
#[test]
fn a_box_wider_than_the_canvas_is_height_limited() {
let frame = ReferenceFrame::fit_in(1920.0, 1080.0, 400.0, 400.0);
assert!((frame.scale - 2.7).abs() < 1e-5, "{}", frame.scale);
assert!(frame.top.abs() < 1e-4, "top {}", frame.top);
assert!((frame.left - (1920.0 - 400.0 * 2.7) / 2.0).abs() < 1e-3);
}
#[test]
fn the_transform_places_and_scales_the_interior() {
let frame = ReferenceFrame::fit_in(1920.0, 1080.0, REF_W, REF_H);
assert_eq!(
frame.transform(),
format!(
"translate({} {}) scale({})",
frame.left, frame.top, frame.scale
)
);
let (cx, cy) = frame.point(REF_W / 2.0, REF_H / 2.0);
assert!((cx - 960.0).abs() < 0.05, "{cx}");
assert!((cy - 540.0).abs() < 0.05, "{cy}");
}
#[test]
fn a_degenerate_box_does_not_divide_by_zero() {
let frame = ReferenceFrame::fit_in(1920.0, 1080.0, 0.0, 0.0);
assert!((frame.scale - 1.0).abs() < 1e-6);
assert!(frame.transform().contains("scale(1)"));
}
}