mirage-engine 0.2.0

Mirage, an immediate-mode 3D engine for simple games on desktop and the browser
Documentation
//! What a session lays text out as, for the game that measures its text.

use std::sync::Arc;

use super::*;
use crate::egui::{FontId, Galley};

/// Measures every text it was given, in the font beside it, once per frame.
struct Measurer {
    asked: Vec<(String, FontId)>,
    laid_out: Vec<Arc<Galley>>,
}

impl Game for Measurer {
    type Meshes = CubeSet;
    type Sounds = NoSounds;
    type InputActions = NoInputActions;
    type Skyboxes = NoSkyboxes;
    type SurfaceStyles = NoSurfaceStyles;
    type PostEffects = NoPostEffects;

    fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}

    fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
        self.laid_out = self
            .asked
            .iter()
            .map(|(text, font)| ctx.text_layout(text, font.clone()))
            .collect();
    }
}

/// What one frame lays each text out as, or `None` where the machine has
/// no usable graphics adapter.
fn measured(asked: &[(&str, FontId)]) -> Option<Vec<Arc<Galley>>> {
    let asked = asked
        .iter()
        .map(|(text, font)| ((*text).to_owned(), font.clone()))
        .collect();
    let mut session = started(
        Config::new("headless text"),
        UVec2::splat(SIDE),
        |_ctx: &mut InitContext<'_, Measurer>| {
            Ok(Measurer {
                asked,
                laid_out: Vec::new(),
            })
        },
    )?;

    session.step();
    Some(session.game().laid_out.clone())
}

#[test]
fn a_longer_text_is_wider_and_of_the_same_height() {
    let font = FontId::proportional(14.0);
    let Some(laid_out) = measured(&[("score", font.clone()), ("score: 1200", font)]) else {
        eprintln!("skipped: this machine has no usable graphics adapter");
        return;
    };
    let [short, long] = &laid_out[..] else {
        panic!("one layout per text");
    };

    assert!(
        long.size().x > short.size().x,
        "{} points over {}",
        long.size().x,
        short.size().x
    );
    assert_eq!(long.size().y, short.size().y, "one line either way");
}

#[test]
fn text_at_a_larger_size_is_wider_and_taller() {
    let Some(laid_out) = measured(&[
        ("score", FontId::proportional(12.0)),
        ("score", FontId::proportional(24.0)),
    ]) else {
        eprintln!("skipped: this machine has no usable graphics adapter");
        return;
    };
    let [small, large] = &laid_out[..] else {
        panic!("one layout per text");
    };

    assert!(large.size().x > small.size().x);
    assert!(large.size().y > small.size().y);
}

#[test]
fn the_one_width_family_lays_every_glyph_out_at_one_width() {
    let one_width = FontId::monospace(14.0);
    let own_widths = FontId::proportional(14.0);
    let Some(laid_out) = measured(&[
        ("0", one_width.clone()),
        ("1", one_width.clone()),
        ("11", one_width.clone()),
        ("i", one_width.clone()),
        ("m", one_width),
        ("i", own_widths.clone()),
        ("m", own_widths),
    ]) else {
        eprintln!("skipped: this machine has no usable graphics adapter");
        return;
    };
    let [
        zero,
        one,
        eleven,
        thin,
        broad,
        thin_of_its_own,
        broad_of_its_own,
    ] = &laid_out[..]
    else {
        panic!("one layout per text");
    };

    assert_eq!(zero.size().x, one.size().x, "whichever digit it is");
    assert!(
        eleven.size().x > one.size().x,
        "and a second digit is wider still"
    );
    assert_eq!(thin.size().x, broad.size().x, "whichever glyph it is");
    assert!(
        thin_of_its_own.size().x < broad_of_its_own.size().x,
        "where the other family gives each glyph its own width"
    );
}

#[test]
fn a_text_has_height_and_the_empty_text_has_no_width() {
    let font = FontId::proportional(14.0);
    let Some(laid_out) = measured(&[("score", font.clone()), ("", font)]) else {
        eprintln!("skipped: this machine has no usable graphics adapter");
        return;
    };
    let [text, empty] = &laid_out[..] else {
        panic!("one layout per text");
    };

    assert!(text.size().y > 0.0, "got {}", text.size().y);
    assert_eq!(empty.size().x, 0.0);
}

#[test]
fn a_row_ends_where_a_newline_starts_the_next_one() {
    let font = FontId::proportional(14.0);
    let Some(laid_out) = measured(&[("a", font.clone()), ("bb", font.clone()), ("a\nbb", font)])
    else {
        eprintln!("skipped: this machine has no usable graphics adapter");
        return;
    };
    let [one, other, both] = &laid_out[..] else {
        panic!("one layout per text");
    };

    assert!(
        both.size().y > one.size().y,
        "two rows are taller than one, got {} over {}",
        both.size().y,
        one.size().y
    );
    assert_eq!(
        both.size().x,
        other.size().x,
        "and as wide as the wider row alone"
    );
}

#[test]
fn a_space_at_the_end_of_a_text_widens_it() {
    let font = FontId::proportional(14.0);
    let Some(laid_out) = measured(&[("score", font.clone()), ("score ", font)]) else {
        eprintln!("skipped: this machine has no usable graphics adapter");
        return;
    };
    let [tight, spaced] = &laid_out[..] else {
        panic!("one layout per text");
    };

    assert!(
        spaced.size().x > tight.size().x,
        "the width a text takes holds the spaces at its end, got {} over {}",
        spaced.size().x,
        tight.size().x
    );
}