Files
MeshTerm/tests/test_consolefont.py
T
Jean-Pierre MartineauandClaude Opus 5.5 efc02ddced The code text uses the glossary words in more places
A final pass over the docstrings and comments: "the own X of Y" is now
"Y's own X" (84 places), a navigation entry is a screen on the stack, our
node is our node, a paint is a paint, the quoted UX labels are in code
font, and a list of punctuation glyphs is in code font.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:45:04 -04:00

253 lines
11 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""Tests for the bundled console font and the code that installs and selects it.
A test cannot run the Win32 part of the code off Windows. A test suite must not run it on
Windows either. The installation of a font changes the machine, and a session cannot undo
the change, because Windows locks the file when it loads the font. Thus these tests check
only what they can check without a change to the system:
- The font that MeshTerm ships is the font that MeshTerm says it ships.
- The font can draw what MeshTerm draws.
- Each entry point does its work in a reduced way, and does not raise an exception, when
there is no console to talk to.
The tests make assertions about the coverage of the font from its ``cmap``. Thus these
tests are the equivalent, on the desktop, of :mod:`meshterm.ui.fontset`. That module is
the inventory of the PicoCalc, which tests on the handheld verified.
"""
from __future__ import annotations
import struct
import sys
from pathlib import Path
import pytest
from meshterm.core import consolefont
from meshterm.ui.termfont import CHART_FONTS, face_draws_charts, installed_chart_font
BRAILLE = range(0x2800, 0x2900)
def _cmap(path: Path) -> set[int]:
"""Each codepoint that a TrueType font maps, read from its ``cmap`` table.
This is a short reader for format 4, and not a dependency. The question here is narrow.
The other choice is to trust a font file that MeshTerm ships, with no look inside it.
"""
data = path.read_bytes()
count = struct.unpack(">H", data[4:6])[0]
tables = {}
for i in range(count):
offset = 12 + i * 16
tag, _, start, _length = struct.unpack(">4sIII", data[offset : offset + 16])
tables[tag.decode("latin-1")] = start
cmap = tables["cmap"]
covered: set[int] = set()
for i in range(struct.unpack(">H", data[cmap + 2 : cmap + 4])[0]):
record = cmap + 4 + i * 8
sub = cmap + struct.unpack(">HHI", data[record : record + 8])[2]
if struct.unpack(">H", data[sub : sub + 2])[0] != 4:
continue
seg_bytes = struct.unpack(">H", data[sub + 6 : sub + 8])[0]
segs = seg_bytes // 2
ends = struct.unpack(f">{segs}H", data[sub + 14 : sub + 14 + seg_bytes])
starts_at = sub + 16 + seg_bytes
starts = struct.unpack(f">{segs}H", data[starts_at : starts_at + seg_bytes])
deltas_at = starts_at + seg_bytes
deltas = struct.unpack(f">{segs}h", data[deltas_at : deltas_at + seg_bytes])
ranges_at = deltas_at + seg_bytes
ranges = struct.unpack(f">{segs}H", data[ranges_at : ranges_at + seg_bytes])
for seg in range(segs):
for cp in range(starts[seg], min(ends[seg], 0xFFFF) + 1):
if ranges[seg] == 0:
glyph = (cp + deltas[seg]) & 0xFFFF
else:
at = ranges_at + seg * 2 + ranges[seg] + (cp - starts[seg]) * 2
if at + 2 > len(data):
continue
glyph = struct.unpack(">H", data[at : at + 2])[0]
if glyph:
glyph = (glyph + deltas[seg]) & 0xFFFF
if glyph:
covered.add(cp)
return covered
def test_the_font_we_ship_is_present_with_its_licence() -> None:
"""The font that MeshTerm ships is there with its licence.
MeshTerm redistributes the font under the SIL OFL. The licence must go with the file.
"""
assert consolefont.BUNDLED_FONT.is_file()
licence = consolefont.FONT_DIR / "CascadiaMono-OFL.txt"
assert licence.is_file()
text = licence.read_text(encoding="utf-8")
assert "SIL Open Font License" in text
assert "Reserved Font Name" in text
def test_the_bundled_font_can_draw_the_charts() -> None:
"""The bundled font can draw the charts.
This is the reason for this font and not another: it has the braille block, and few
fonts have it. A measurement of all the fonts on a development machine showed that no
common coder font has the block. Hack Nerd Font, JetBrains Mono, Fira Code, Source Code
Pro, and also DejaVu Sans Mono have none of the 256 characters. If MeshTerm shipped a
font that cannot draw a chart, the offer would succeed in a technical sense, and it
would have no visible effect.
"""
covered = _cmap(consolefont.BUNDLED_FONT)
missing = [cp for cp in BRAILLE if cp not in covered]
assert not missing, f"{len(missing)} braille cells missing from the bundled font"
def test_the_bundled_font_covers_the_marks_and_the_path_chips() -> None:
"""The bundled font covers the marks and the path chips.
These are the other characters that a classic console must draw from its font only.
The powerline separators are the reason that MeshTerm bundles the ``PL`` build and not
the plain build. The ``PL`` build is 25 KB larger, and the path lines keep their chips.
"""
covered = _cmap(consolefont.BUNDLED_FONT)
for mark in "✓❯◉●○▲■─│╭╮╰╯▌▐░▒▓█←↑→↓↔↕…":
assert ord(mark) in covered, f"{mark!r} is not in the bundled font"
for chip in "": # the powerline separator and its round caps
assert ord(chip) in covered, f"U+{ord(chip):04X} is not in the bundled font"
def test_the_bundled_face_is_what_the_font_calls_itself() -> None:
"""The bundled face is the name that the font gives itself.
``SetCurrentConsoleFontEx`` matches the family name exactly, so a typo gives no
message. The code would install the font and fail to select it. Then it would report a
console that "kept its own font", and no message would name the real cause.
"""
data = consolefont.BUNDLED_FONT.read_bytes()
count = struct.unpack(">H", data[4:6])[0]
name_table = next(
struct.unpack(">4sIII", data[12 + i * 16 : 28 + i * 16])[2]
for i in range(count)
if struct.unpack(">4sIII", data[12 + i * 16 : 28 + i * 16])[0] == b"name"
)
records, strings = struct.unpack(">HH", data[name_table + 2 : name_table + 6])
families = set()
for i in range(records):
at = name_table + 6 + i * 12
platform, encoding, language, name_id, length, offset = struct.unpack(
">HHHHHH", data[at : at + 12]
)
if (platform, encoding, language, name_id) == (3, 1, 0x409, 1):
start = name_table + strings + offset
families.add(data[start : start + length].decode("utf-16-be"))
assert consolefont.BUNDLED_FACE in families, f"font calls itself {families}"
def test_the_bundled_face_is_one_we_would_accept() -> None:
"""The bundled face is a face that MeshTerm accepts.
The font that MeshTerm installs must pass the check that decides whether to make the
offer. If it does not, MeshTerm installs it, selects it, and makes the offer again at
the next start.
"""
assert face_draws_charts(consolefont.BUNDLED_FACE)
def test_chart_fonts_are_matched_by_family_however_spelled() -> None:
"""The code matches chart fonts by family, in any spelling.
Cascadia has plain, PL, and NF builds, and the Nerd Font patches rename it. The code
matches the family name. The config of a terminal, the ``HKCU`` registration, and
``SetCurrentConsoleFontEx`` all use the family name. They never use the file name. The
two differ here: ``CascadiaMonoPL.ttf`` calls itself ``Cascadia Mono PL``, with spaces.
"""
assert face_draws_charts("Cascadia Mono")
assert face_draws_charts("Cascadia Mono PL")
assert face_draws_charts("Cascadia Mono NF")
assert face_draws_charts(" cascadia code ") # the code normalizes the spacing and the case
assert face_draws_charts("CaskaydiaCove Nerd Font Mono")
def test_the_fonts_that_cannot_draw_charts_are_not_claimed() -> None:
"""The code does not claim the fonts that cannot draw charts.
A measurement gave zero braille cells for each of these fonts. A wrong yes here is not
true. Then a user on a classic console would see boxes for charts, with no offer to
correct them. The whole check exists to prevent exactly this failure.
"""
for face in (
"Consolas",
"Lucida Console",
"Courier New",
"Hack Nerd Font Mono",
"JetBrains Mono",
"Fira Code",
"Source Code Pro",
"DejaVu Sans Mono",
):
assert not face_draws_charts(face), f"{face} does not carry the braille block"
assert not face_draws_charts(None)
assert not face_draws_charts("")
def test_every_chart_font_entry_is_normalised() -> None:
"""Each chart font entry is normalized.
The code compares the list with normalized faces. Thus an entry with capitals never
matches.
"""
for entry in CHART_FONTS:
assert entry == entry.lower().strip()
assert " " not in entry
def test_asking_the_machine_what_it_has_never_raises() -> None:
"""A query of the machine for its fonts never raises an exception.
The code does its best on each platform. A font scan that raises an exception would
stop the start of the app.
"""
assert installed_chart_font() is None or isinstance(installed_chart_font(), str)
def test_reading_the_console_font_never_raises() -> None:
"""A read of the console font never raises an exception.
Under pytest there is no console to ask, and the answer must be None.
"""
assert consolefont.current_face() is None or isinstance(consolefont.current_face(), str)
def test_the_user_font_directory_is_under_the_users_own_profile() -> None:
"""The font directory of the user is in the user's own profile.
This is the purpose of the location for each user: no administrator rights are
necessary.
"""
directory = consolefont.user_font_dir()
assert directory.parts[-3:] == ("Microsoft", "Windows", "Fonts")
assert "AppData" in str(directory) or "Local" in str(directory)
@pytest.mark.skipif(sys.platform == "win32", reason="would touch the real machine")
def test_installing_is_a_no_op_where_there_are_no_windows_fonts() -> None:
"""The install does nothing where there are no Windows fonts.
Off Windows, MeshTerm never makes the offer. The install refuses, and it does not
pretend to succeed.
"""
assert consolefont.install_bundled_font() is False
assert consolefont.select("Cascadia Mono PL") is False
assert consolefont.use("Cascadia Mono PL") is False
def test_a_font_resource_is_only_added_from_a_file_that_exists(tmp_path: Path) -> None:
"""The code adds a font resource only from a file that exists.
The retry path can get a path for a file that is not there. It must not raise an
exception for it.
"""
assert consolefont._add_font_resource(tmp_path / "nothing.ttf") is False