use super::*;
use crate::gfx::mosaic::MosaicMode;
const INK: Rgba = Rgba::rgb(255, 255, 255);
const GROUND: Rgba = Rgba::rgb(0, 0, 0);
const UPPER: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
fn closest_pair(scale: GlyphScale, mode: MosaicMode) -> (char, char, u32) {
closest_in(UPPER, scale, mode)
}
fn closest_pair_with(style: &BigTextStyle) -> u32 {
closest_pair_in(style, UPPER)
.expect("uppercase is measurable at every offered scale")
.2
}
fn closest_in(set: &str, scale: GlyphScale, mode: MosaicMode) -> (char, char, u32) {
closest_pair_in(&BigTextStyle::new(scale, mode), set).unwrap_or((' ', ' ', 0))
}
#[test]
fn every_letter_stays_distinct_at_the_declared_floor() {
let (a, b, d) = closest_pair(GlyphScale::FLOOR, MosaicMode::Braille);
assert!(
d >= 8,
"at the declared FLOOR the closest pair {a}/{b} differs by only {d} subpixels — \
the floor no longer buys the margin its doc claims"
);
}
#[test]
fn the_tight_scale_is_legible_but_measurably_tighter_than_the_floor() {
let (_, _, tight) = closest_pair(GlyphScale::TIGHT, MosaicMode::Braille);
let (_, _, floor) = closest_pair(GlyphScale::FLOOR, MosaicMode::Braille);
assert!(
tight > 0,
"TIGHT collapses two UPPERCASE letters into identical output"
);
assert!(
tight < floor,
"TIGHT ({tight}) is not tighter than FLOOR ({floor}), so one of them is not worth having"
);
}
const TEXT_MODES: [MosaicMode; 2] = [MosaicMode::Sextant, MosaicMode::Braille];
#[test]
fn the_floor_is_clear_in_braille_and_only_marginal_for_text_in_sextant() {
let braille = BigTextStyle::new(GlyphScale::FLOOR, MosaicMode::Braille);
for content in [Content::Uppercase, Content::Text, Content::Icons] {
let (a, b, d) = closest_pair_in(&braille, content.alphabet()).expect("measurable");
assert_eq!(
legibility(&braille, content),
Legibility::Clear,
"FLOOR is documented as clear for every class in braille, but {content:?} \
measures {a}/{b} at {d} subpixels"
);
}
let sextant = BigTextStyle::new(GlyphScale::FLOOR, MosaicMode::Sextant);
assert_eq!(
legibility(&sextant, Content::Text),
Legibility::Marginal,
"FLOOR's doc, COMPACT_WIDE's reason to exist and the module header all rest on \
mixed text being MARGINAL at 4x3 in sextant. If that moved, three docs are now \
wrong together"
);
for content in [Content::Uppercase, Content::Icons] {
assert_eq!(
legibility(&sextant, content),
Legibility::Clear,
"the sextant caveat is documented as applying to mixed TEXT only"
);
}
}
#[test]
fn the_wide_compact_scale_clears_text_where_the_floor_is_only_marginal() {
for mode in TEXT_MODES {
let wide = BigTextStyle::new(GlyphScale::COMPACT_WIDE, mode);
assert_eq!(
legibility(&wide, Content::Text),
Legibility::Distorted,
"COMPACT_WIDE is the operator's white-bar scale; legibility must refuse it \
on SHAPE in {mode:?}, however far apart the pair measures"
);
let (_, _, wide_d) = closest_pair_in(&wide, Content::Text.alphabet()).unwrap();
let (_, _, floor_d) = closest_pair_in(
&BigTextStyle::new(GlyphScale::FLOOR, mode),
Content::Text.alphabet(),
)
.unwrap();
assert!(
wide_d > floor_d,
"in {mode:?} COMPACT_WIDE gives lowercase {wide_d} subpixels against FLOOR's \
{floor_d} — the doc claims it is the better spend of the same twelve cells"
);
}
assert_eq!(
GlyphScale::COMPACT_WIDE.cols * GlyphScale::COMPACT_WIDE.rows,
GlyphScale::FLOOR.cols * GlyphScale::FLOOR.rows,
"the whole pitch is SAME cost, fewer rows"
);
const { assert!(GlyphScale::COMPACT_WIDE.rows < GlyphScale::FLOOR.rows) };
}
#[test]
fn smallest_clear_recommends_the_scale_the_floor_doc_points_at() {
let pick = smallest_clear(MosaicMode::Sextant, Content::Text)
.expect("sextant text clears somewhere in the band");
assert_eq!(
(pick.cols, pick.rows),
(4, 4),
"FLOOR's doc names the scale sextant callers should use for mixed text; if the \
search disagrees, one of them is lying to the reader"
);
assert!(
!GlyphScale::COMPACT_WIDE.within_aspect_band(),
"COMPACT_WIDE's doc warns it is out of band; if it is back inside, that warning \
and FLOOR's cross-reference are both wrong"
);
}
#[test]
fn the_compact_scale_beats_the_tight_scale_that_used_to_be_offered_instead() {
for mode in TEXT_MODES {
let compact = BigTextStyle::new(GlyphScale::COMPACT, mode);
let tight = BigTextStyle::new(GlyphScale::TIGHT, mode);
assert_eq!(
legibility(&tight, Content::Text),
Legibility::Collides,
"TIGHT is documented as uppercase-only because mixed text COLLIDES there; \
in {mode:?} it no longer does, so the doc oversells the danger"
);
assert_ne!(
legibility(&compact, Content::Text),
Legibility::Collides,
"COMPACT is the scale that keeps apart what TIGHT collapses; in {mode:?} it \
collapses too, so there is no reason to prefer it"
);
let (_, _, c_upper) = closest_pair_in(&compact, Content::Uppercase.alphabet()).unwrap();
let (_, _, t_upper) = closest_pair_in(&tight, Content::Uppercase.alphabet()).unwrap();
assert!(
c_upper >= t_upper,
"in {mode:?} COMPACT gives uppercase {c_upper} subpixels against TIGHT's \
{t_upper} — it is supposed to be at least as good for a row less"
);
const { assert!(GlyphScale::COMPACT.rows < GlyphScale::TIGHT.rows) };
}
}
#[test]
fn one_scale_is_dead_for_text_and_clear_for_icons_at_the_same_time() {
let tiny = BigTextStyle::new(GlyphScale { cols: 2, rows: 2 }, MosaicMode::Braille);
let text = legibility(&tiny, Content::Text);
let icons = legibility(&tiny, Content::Icons);
assert!(
text < Legibility::Clear,
"2x2 no longer fails for mixed text ({text:?}), so the example's headline \
disagreement is stale"
);
assert_eq!(
icons,
Legibility::Clear,
"2x2 is supposed to be perfectly serviceable for ICONS — that is the half \
of the finding a single global floor could not express"
);
}
#[test]
fn the_same_scale_measures_differently_in_different_symbols() {
let scale = GlyphScale::FLOOR;
let braille = closest_pair_in(
&BigTextStyle::new(scale, MosaicMode::Braille),
Content::Uppercase.alphabet(),
)
.unwrap()
.2;
let quadrant = closest_pair_in(
&BigTextStyle::new(scale, MosaicMode::Quadrant),
Content::Uppercase.alphabet(),
)
.unwrap()
.2;
assert!(
braille > quadrant,
"at the same {}x{}, braille measures {braille} and quadrant {quadrant} — if a \
mode-blind predicate were adequate these would not differ",
scale.cols,
scale.rows
);
}
#[test]
fn the_derived_floor_is_cheaper_for_icons_than_for_prose() {
for mode in TEXT_MODES {
let text = smallest_clear(mode, Content::Text)
.unwrap_or_else(|| panic!("{mode:?} must clear for text at SOME offered scale"));
let icons = smallest_clear(mode, Content::Icons)
.unwrap_or_else(|| panic!("{mode:?} must clear for icons at SOME offered scale"));
assert!(
icons.cols * icons.rows <= text.cols * text.rows,
"in {mode:?} the derived icon floor {}x{} costs more than the text floor \
{}x{} — then a per-class floor is buying nothing",
icons.cols,
icons.rows,
text.cols,
text.rows
);
assert_eq!(
legibility(&BigTextStyle::new(text, mode), Content::Text),
Legibility::Clear
);
assert_eq!(
legibility(&BigTextStyle::new(icons, mode), Content::Icons),
Legibility::Clear
);
}
}
#[test]
fn smallest_clear_is_not_beaten_by_anything_else_that_clears() {
for mode in TEXT_MODES {
for content in [Content::Uppercase, Content::Text, Content::Icons] {
let Some(pick) = smallest_clear(mode, content) else {
continue;
};
for other in search_space() {
if legibility(&BigTextStyle::new(other, mode), content) != Legibility::Clear {
continue;
}
assert!(
other.cols * other.rows >= pick.cols * pick.rows,
"{mode:?}/{content:?}: smallest_clear returned {}x{} ({} cells) but \
{}x{} ({} cells) also clears",
pick.cols,
pick.rows,
pick.cols * pick.rows,
other.cols,
other.rows,
other.cols * other.rows
);
}
}
}
}
#[test]
fn the_search_space_holds_no_scale_that_distorts_past_the_band() {
let space: Vec<GlyphScale> = search_space().collect();
assert!(
!space.is_empty(),
"an empty search space would make every guard below vacuously true"
);
for scale in &space {
assert!(
scale.cols <= MAX_STRETCH * scale.rows,
"{}x{} is stretched {}x wide, past MAX_STRETCH",
scale.cols,
scale.rows,
scale.cols / scale.rows
);
assert!(
scale.rows <= MAX_STRETCH * scale.cols,
"{}x{} is stretched {}x tall, past MAX_STRETCH",
scale.cols,
scale.rows,
scale.rows / scale.cols
);
}
for absent in [
GlyphScale::COMPACT_WIDE, GlyphScale { cols: 6, rows: 1 }, GlyphScale { cols: 2, rows: 5 }, ] {
assert!(
!space.contains(&absent),
"{}x{} is out of band and must not be offered",
absent.cols,
absent.rows
);
}
assert!(
space.contains(&GlyphScale { cols: 12, rows: 6 }),
"12x6 is the widest scale the operator asked for; the band must admit it"
);
assert!(
space.contains(&GlyphScale::square(2)),
"square(2) sits on the band's wide edge and both edges are inclusive"
);
}
#[test]
fn only_halfblock_text_needed_the_wider_search_and_the_doc_claimed_all_three() {
const OLD_CEILING: i32 = 6;
for (content, expected, needed_widening) in [
(Content::Uppercase, (6, 5), false),
(Content::Text, (7, 5), true),
(Content::Icons, (5, 3), false),
] {
let pick = smallest_clear(MosaicMode::HalfBlock, content).unwrap_or_else(|| {
panic!(
"smallest_clear's doc says HalfBlock/{content:?} clears at {expected:?}; \
it answers None, so the doc tells callers a mode works when it does not"
)
});
assert_eq!(
(pick.cols, pick.rows),
expected,
"smallest_clear's doc prints this number for HalfBlock/{content:?}"
);
assert_eq!(
pick.cols > OLD_CEILING,
needed_widening,
"the doc's correction turns on WHICH classes the old six-column ceiling was \
hiding: it says {content:?} {} the widening, and {}x{} says otherwise",
if needed_widening {
"needed"
} else {
"did not need"
},
pick.cols,
pick.rows
);
}
}
#[test]
fn no_mode_and_class_pair_is_left_without_a_clear_scale() {
for mode in [
MosaicMode::Braille,
MosaicMode::Sextant,
MosaicMode::Quadrant,
MosaicMode::HalfBlock,
] {
for content in [Content::Uppercase, Content::Text, Content::Icons] {
let pick = smallest_clear(mode, content)
.unwrap_or_else(|| panic!("{mode:?}/{content:?} has no clear scale in the band"));
assert!(
pick.within_aspect_band(),
"{mode:?}/{content:?} returned {}x{}, which is outside the band it searched",
pick.cols,
pick.rows
);
}
}
}
#[test]
fn the_recommended_picks_the_docs_print_are_what_the_search_returns() {
assert_eq!(
smallest_clear(MosaicMode::Sextant, Content::Text).map(|s| (s.cols, s.rows)),
Some((4, 4)),
"FLOOR's doc sends sextant text here"
);
assert_eq!(
smallest_clear(MosaicMode::Braille, Content::Icons).map(|s| (s.cols, s.rows)),
Some((2, 2)),
"square()'s doc sends braille icons here, instead of the out-of-band 3x1 it used \
to recommend"
);
let old = closest_in(
Content::Icons.alphabet(),
GlyphScale { cols: 3, rows: 1 },
MosaicMode::Braille,
);
let new = closest_in(
Content::Icons.alphabet(),
GlyphScale { cols: 2, rows: 2 },
MosaicMode::Braille,
);
assert!(
new.2 > old.2,
"2x2 gives braille icons {} subpixels against 3x1's {} — square()'s doc claims \
the in-band answer is also the better-measured one",
new.2,
old.2
);
}
#[test]
#[ignore = "report-only: run explicitly with --nocapture"]
fn scale_table() {
for mode in [
MosaicMode::Braille,
MosaicMode::Sextant,
MosaicMode::Quadrant,
MosaicMode::HalfBlock,
] {
for content in [Content::Uppercase, Content::Text, Content::Icons] {
eprintln!("\n{mode:?} / {content:?}");
for rows in 1..=6 {
let mut line = String::new();
for cols in 2..=12 {
let scale = GlyphScale { cols, rows };
let style = BigTextStyle::new(scale, mode);
let mark = match legibility(&style, content) {
Legibility::Clear => 'C',
Legibility::Marginal => 'm',
Legibility::Distorted => 'x',
Legibility::Collides => '.',
};
let d = closest_pair_in(&style, content.alphabet())
.map(|(_, _, d)| d)
.unwrap_or(0);
let loss = least_faithful(&style, content).map_or(1.0, |(_, l)| l);
let cell = format!("{cols}x{rows} {mark}{d} {loss:.2}");
line.push_str(&if scale.within_aspect_band() {
format!("{cell:<15}")
} else {
format!("{:<15}", format!("[{cell}]"))
});
}
eprintln!(" {line}");
}
eprintln!(
" => smallest_clear: {:?}",
smallest_clear(mode, content).map(|s| (s.cols, s.rows))
);
}
}
}
#[test]
fn one_row_icons_are_not_distinguishable() {
let one_row = GlyphScale::square(1);
assert_eq!((one_row.cols, one_row.rows), (2, 1));
assert_eq!(
legibility(
&BigTextStyle::new(one_row, MosaicMode::Quadrant),
Content::Icons
),
Legibility::Collides,
"square(1) icons in quadrant used to render two glyphs identically; if that is \
fixed, square(1)'s doc must stop warning about it"
);
for mode in TEXT_MODES {
assert!(
legibility(&BigTextStyle::new(one_row, mode), Content::Icons) < Legibility::Clear,
"square(1) is documented as unsafe for a ROW of icons in {mode:?} too"
);
let pick = smallest_clear(mode, Content::Icons).expect("icons clear somewhere");
assert_eq!(
pick.rows, 2,
"two rows is the doc's claim for a row of icons in {mode:?}"
);
assert_eq!(
legibility(&BigTextStyle::new(pick, mode), Content::Icons),
Legibility::Clear
);
}
}
#[test]
fn the_badge_scale_is_an_icon_answer_in_braille_and_a_distortion_in_sextant() {
let badge = GlyphScale::square(2);
assert_eq!((badge.cols, badge.rows), (4, 2));
assert_eq!(
legibility(
&BigTextStyle::new(badge, MosaicMode::Braille),
Content::Icons
),
Legibility::Clear,
"braille has the subpixels to hold a badge at 4x2"
);
assert_eq!(
legibility(
&BigTextStyle::new(badge, MosaicMode::Sextant),
Content::Icons
),
Legibility::Distorted,
"sextant 4x2 stretches the icon RUN's box horizontally; the doc must not send \
sextant callers here"
);
assert_eq!(
smallest_clear(MosaicMode::Sextant, Content::Icons).map(|s| (s.cols, s.rows)),
Some((3, 2)),
"and the search must name the narrower answer the prose now points at"
);
let (_, _, wide) = closest_pair_in(
&BigTextStyle::new(badge, MosaicMode::Sextant),
Content::Icons.alphabet(),
)
.unwrap();
let (_, _, narrow) = closest_pair_in(
&BigTextStyle::new(GlyphScale { cols: 3, rows: 2 }, MosaicMode::Sextant),
Content::Icons.alphabet(),
)
.unwrap();
assert!(
wide > narrow,
"4x2 measures {wide} subpixels against 3x2's {narrow} — the shape term is the only \
thing standing between the reader and the wider scale"
);
}
#[test]
fn every_content_class_has_enough_characters_to_measure() {
for content in [Content::Uppercase, Content::Text, Content::Icons] {
assert!(
content.alphabet().chars().count() >= 2,
"{content:?} cannot have a closest PAIR"
);
let style = BigTextStyle::new(GlyphScale::FLOOR, MosaicMode::Braille);
assert!(
super::closest_pair(&style, content).is_some(),
"{content:?} has characters the embedded font cannot draw, so its verdict is \
computed from fewer glyphs than it claims"
);
}
}
#[test]
fn a_square_icon_is_twice_as_many_columns_as_rows() {
for rows in 1..=6 {
let s = GlyphScale::square(rows);
assert_eq!(s.cols, rows * 2, "square({rows}) is not square on screen");
}
assert_ne!(GlyphScale { cols: 6, rows: 6 }, GlyphScale::square(6));
}
#[test]
fn measure_agrees_with_the_grid_the_renderer_returns() {
for text in ["A", "AGORA", "AGORA-TUI"] {
for scale in [GlyphScale::TIGHT, GlyphScale::FLOOR, GlyphScale::square(2)] {
let want = measure(text, scale).expect("measure");
let grid = render(text, scale, MosaicMode::Sextant, INK, GROUND).expect("render");
assert_eq!(
(grid.cols() as i32, grid.rows() as i32),
(want.w, want.h),
"measure lied about {text:?} at {}x{}",
scale.cols,
scale.rows
);
}
}
}
#[test]
fn a_nine_character_title_costs_44_columns_and_3_rows_at_the_floor() {
assert_eq!(
measure("AGORA-TUI", GlyphScale::FLOOR),
Some(Size::new(44, 3))
);
}
#[test]
fn an_accented_character_is_refused_by_name_not_dropped() {
let err = rasterize("Café", GlyphScale::FLOOR, MosaicMode::Sextant, INK, GROUND)
.expect_err("the font has no accented glyphs, so this must not succeed");
assert_eq!(err, BigTextError::UnsupportedChar('é'));
assert!(
err.to_string().contains('é'),
"the message must name the character a caller has to fix: {err}"
);
}
fn slot_extent(text: &str, slot: u32, scale: GlyphScale) -> (u32, u32) {
let bmp = rasterize(text, scale, MosaicMode::Braille, INK, GROUND).expect("rasterize");
let (sub_w, _) = (2u32, 4u32); let cell_w = scale.cols as u32 * sub_w;
let x0 = slot * (cell_w + sub_w);
let inked: Vec<u32> = (0..bmp.height())
.filter(|y| (x0..x0 + cell_w).any(|x| bmp.get(x, *y) == Some(INK)))
.collect();
(
*inked.first().expect("slot has ink"),
*inked.last().unwrap(),
)
}
#[test]
fn lowercase_keeps_its_height_because_the_crop_is_run_wide_not_per_glyph() {
let scale = GlyphScale { cols: 6, rows: 4 };
let (cap_top, cap_bottom) = slot_extent("Oo", 0, scale);
let (low_top, low_bottom) = slot_extent("Oo", 1, scale);
assert!(
low_top > cap_top,
"`o` starts at row {low_top} and `O` at {cap_top} — the x-height was \
stretched to cap height, so the crop is per glyph, not run-wide"
);
assert!(
cap_bottom.abs_diff(low_bottom) <= 1,
"`O` ends at {cap_bottom} and `o` at {low_bottom} — they must share a baseline"
);
}
#[test]
fn a_descender_still_descends_below_the_baseline_it_shares() {
let scale = GlyphScale { cols: 6, rows: 4 };
let (_, cap_bottom) = slot_extent("Pp", 0, scale);
let (_, low_bottom) = slot_extent("Pp", 1, scale);
assert!(
low_bottom > cap_bottom,
"`p` ends at row {low_bottom} and `P` at {cap_bottom} — the descender \
was cropped away or lifted onto the baseline"
);
}
#[test]
fn nothing_to_draw_and_no_cells_are_named_errors() {
assert_eq!(
rasterize("", GlyphScale::FLOOR, MosaicMode::Sextant, INK, GROUND),
Err(BigTextError::NothingToDraw)
);
assert_eq!(
rasterize(" ", GlyphScale::FLOOR, MosaicMode::Sextant, INK, GROUND),
Err(BigTextError::NothingToDraw),
"an all-space run has no baseline to crop to"
);
let flat = GlyphScale { cols: 4, rows: 0 };
assert_eq!(
rasterize("A", flat, MosaicMode::Sextant, INK, GROUND),
Err(BigTextError::EmptyScale(flat))
);
assert_eq!(measure("A", flat), None);
}
#[test]
fn each_mode_gets_the_subpixel_density_it_advertises() {
let scale = GlyphScale::FLOOR;
for (mode, (sw, sh)) in [
(MosaicMode::HalfBlock, (1, 2)),
(MosaicMode::Quadrant, (2, 2)),
(MosaicMode::Sextant, (2, 3)),
(MosaicMode::Braille, (2, 4)),
] {
let bmp = rasterize("A", scale, mode, INK, GROUND).expect("rasterize");
assert_eq!(
(bmp.width(), bmp.height()),
(scale.cols as u32 * sw, scale.rows as u32 * sh),
"{mode:?} rasterized at the wrong density"
);
}
}
#[test]
fn area_averaging_keeps_letters_further_apart_than_point_sampling() {
let base = BigTextStyle::new(GlyphScale::FLOOR, MosaicMode::Braille);
let area = closest_pair_with(&base.sampling(Sampling::AreaAverage));
let near = closest_pair_with(&base.sampling(Sampling::Nearest));
assert!(
area > near,
"area averaging ({area}) no longer beats nearest ({near}) at the floor, \
so the documented reason to default to it is gone"
);
}
#[test]
fn synthetic_bold_raises_pairwise_distinctness_at_3x3_and_is_neutral_from_four_rows() {
let tight = BigTextStyle::new(GlyphScale::TIGHT, MosaicMode::Braille);
let regular = closest_pair_with(&tight.weight(GlyphWeight::Regular));
let bold = closest_pair_with(&tight.weight(GlyphWeight::Bold));
assert!(
bold > regular,
"bold ({bold}) no longer exceeds regular ({regular}) at 3x3 — the figure \
quoted in the GlyphWeight::Bold doc is stale"
);
let roomy = BigTextStyle::new(GlyphScale { cols: 4, rows: 4 }, MosaicMode::Braille);
assert_eq!(
closest_pair_with(&roomy.weight(GlyphWeight::Bold)),
closest_pair_with(&roomy.weight(GlyphWeight::Regular)),
"the docs say bold changes nothing at four rows and above"
);
}
#[test]
fn the_bold_weight_adds_ink_rather_than_moving_it() {
let style = BigTextStyle::new(GlyphScale { cols: 6, rows: 4 }, MosaicMode::Braille);
let ink_of = |f| {
let bmp = rasterize_with("E", &style.weight(f), INK, GROUND).expect("rasterize");
(0..bmp.height())
.flat_map(|y| (0..bmp.width()).map(move |x| (x, y)))
.filter(|(x, y)| bmp.get(*x, *y) == Some(INK))
.count()
};
let (regular, bold) = (ink_of(GlyphWeight::Regular), ink_of(GlyphWeight::Bold));
assert!(
bold > regular,
"bold `E` carries {bold} subpixels against regular's {regular} — \
the dilation moved the stem instead of thickening it"
);
}
#[test]
fn the_simple_call_equals_the_explicit_call_at_its_defaults() {
let scale = GlyphScale::FLOOR;
let simple = rasterize("AG", scale, MosaicMode::Sextant, INK, GROUND).expect("simple");
let explicit = rasterize_with(
"AG",
&BigTextStyle::new(scale, MosaicMode::Sextant),
INK,
GROUND,
)
.expect("explicit");
assert_eq!(simple, explicit);
assert_eq!(GlyphWeight::default(), GlyphWeight::Regular);
assert_eq!(Sampling::default(), Sampling::AreaAverage);
}
#[test]
fn render_paints_glyph_cells_in_every_mode_not_an_empty_grid() {
let opaque = Rgba::rgb(0, 0, 0);
let text = "AGORA";
let scale = GlyphScale::FLOOR;
for mode in [
MosaicMode::HalfBlock,
MosaicMode::Quadrant,
MosaicMode::Sextant,
MosaicMode::Braille,
] {
let style = BigTextStyle::new(scale, mode);
let grid = render_with(text, &style, INK, opaque).expect("render");
for slot in 0..text.chars().count() as i32 {
let x0 = slot * (scale.cols + 1);
let painted = (x0..x0 + scale.cols)
.flat_map(|c| (0..grid.rows() as i32).map(move |r| (c, r)))
.filter(|(c, r)| {
grid.get(*c as u32, *r as u32)
.is_some_and(|cell| cell.ch != ' ')
})
.count();
assert!(
painted > 0,
"{mode:?} left character {slot} of {text:?} entirely blank — the \
grid is the right size and nothing is drawn"
);
}
}
}
#[test]
fn a_transparent_ground_is_refused_only_where_it_would_render_blank() {
for mode in [MosaicMode::Quadrant, MosaicMode::Sextant] {
let style = BigTextStyle::new(GlyphScale::FLOOR, mode);
let err = render_with("AG", &style, INK, Rgba::TRANSPARENT)
.err()
.unwrap_or_else(|| panic!("{mode:?} must refuse a transparent ground"));
assert_eq!(err, BigTextError::TransparentGround(mode));
assert!(
err.to_string().contains("transparent ground"),
"the message must name the problem a caller has to fix: {err}"
);
}
for mode in [MosaicMode::Braille, MosaicMode::HalfBlock] {
let style = BigTextStyle::new(GlyphScale::FLOOR, mode);
let grid = render_with("AG", &style, INK, Rgba::TRANSPARENT).unwrap_or_else(|e| {
panic!("{mode:?} thresholds by luminance and must accept a transparent ground: {e}")
});
assert!(
grid.cells().iter().any(|c| c.ch != ' '),
"{mode:?} accepted a transparent ground but painted nothing"
);
}
}
#[test]
fn a_filled_glyph_is_solid_and_the_glyph_column_alone_reports_a_hollow_ring() {
let style = BigTextStyle::new(GlyphScale { cols: 6, rows: 6 }, MosaicMode::Braille);
let grid = render_with("\u{25cf}", &style, INK, GROUND).expect("the disc renders");
let mid = grid.rows() / 2;
let interior: Vec<_> = (1..grid.cols() - 1)
.map(|c| *grid.get(c, mid).expect("cell in range"))
.collect();
assert!(
!interior.is_empty(),
"no interior cells to inspect — the scale or the crop changed"
);
for cell in &interior {
let solid_by_background = cell.bg == INK;
let solid_by_dots = cell.ch == '\u{28FF}' && cell.fg == INK;
assert!(
solid_by_background || solid_by_dots,
"interior cell of a filled disc is neither ink-backed nor fully dotted: \
ch={:?} fg={:?} bg={:?}",
cell.ch,
cell.fg,
cell.bg
);
}
let blank_glyphs = interior
.iter()
.filter(|c| c.ch == ' ' || c.ch == '\u{2800}')
.count();
assert_eq!(
blank_glyphs,
interior.len(),
"every interior cell should carry a BLANK glyph — that is precisely why reading \
cell.ch without cell.bg reported a ring. If this reds, the encoder changed and \
the warning in this test's doc no longer applies"
);
}
#[test]
fn the_scale_the_operator_photographed_is_refused_on_shape_not_on_distance() {
let bars = BigTextStyle::new(GlyphScale::COMPACT_WIDE, MosaicMode::Braille);
assert_eq!(
legibility(&bars, Content::Icons),
Legibility::Distorted,
"6x2 braille icons is the reported screenshot; the verdict must name the shape"
);
let (_, _, apart) = closest_pair_in(&bars, Content::Icons.alphabet()).unwrap();
assert!(
apart > MARGINAL_MAX * 4,
"the pair measure must still be emphatic ({apart} subpixels) — the whole point is \
that it is right and irrelevant"
);
let (_, loss) = least_faithful(&bars, Content::Icons).unwrap();
assert!(
loss <= FIDELITY_MAX,
"6x2 icons measures {loss:.2}, INSIDE the fidelity bar — it is the aspect band that \
catches this one, which is why both terms are applied"
);
assert!(
!GlyphScale::COMPACT_WIDE.within_aspect_band(),
"...and the band is what refuses it"
);
}
#[test]
fn a_scale_inside_the_band_is_still_refused_when_the_shape_is_gone() {
let tall = BigTextStyle::new(GlyphScale { cols: 2, rows: 3 }, MosaicMode::Braille);
assert!(
tall.scale.within_aspect_band(),
"2x3 is the case the band passes — if it stopped, this guard would be testing the band"
);
let (worst, loss) = least_faithful(&tall, Content::Icons).unwrap();
assert!(
loss > FIDELITY_MAX,
"{worst} at 2x3 braille measures {loss:.2}; the fidelity term must be what refuses it"
);
assert_eq!(legibility(&tall, Content::Icons), Legibility::Distorted);
}
#[test]
fn a_character_alone_is_measured_harder_than_the_same_character_in_its_run() {
let style = BigTextStyle::new(GlyphScale::FLOOR, MosaicMode::Braille);
let alone = fidelity_loss('z', &style).unwrap();
let in_run = run_loss_of('z', &style, Content::Text);
assert!(
alone > in_run + 0.1,
"z alone measures {alone:.2} and in the mixed-text run {in_run:.2}; if these ever \
converge, the distinction this module draws between them is gone"
);
assert!(
alone > FIDELITY_MAX && in_run < FIDELITY_MAX,
"the gap has to straddle the bar to matter: alone {alone:.2}, in run {in_run:.2}"
);
assert_eq!(
legibility(&style, Content::Text),
Legibility::Clear,
"FLOOR renders braille prose fine, and the alone-reading said otherwise"
);
}
fn run_loss_of(target: char, style: &BigTextStyle, content: Content) -> f32 {
let alphabet = content.alphabet();
let glyphs: Vec<[u8; GLYPH_H as usize]> = alphabet
.chars()
.map(|c| dilate(lookup(c).expect("glyph"), style.weight))
.collect();
let (top, bottom) = ink_rows(&glyphs).expect("the run has ink");
let bmp = rasterize_with(alphabet, style, INK, GROUND).expect("the run rasterizes");
let idx = alphabet
.chars()
.position(|c| c == target)
.expect("in class");
let (per_col, _) = subpixels(style.mode);
let stride = style.scale.cols as u32 * per_col + per_col;
let x0 = idx as u32 * stride;
slot_loss(&glyphs[idx], top, bottom, style, |x, y| {
bmp.get(x0 + x, y) == Some(INK)
})
.expect("measurable")
}