Files
typewriter/display/src/glyphs.rs
Julien Calixte 45664fc956 feat: add glyphs
2026-07-12 10:43:41 +02:00

342 lines
8.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Extra glyphs the ISO-8859-15 render font lacks.
//!
//! The buffer is UTF-8, so it can hold codepoints outside Latin-9. The
//! `embedded-graphics` `iso_8859_15` font draws those as a fallback box. This
//! module carries hand-authored 10×20 bitmaps for a curated set of common
//! single-width characters — typographic punctuation that arrives in imported
//! prose (curly quotes, en/em dashes, ellipsis, bullet) and the math symbols the
//! user's Markdown snippets insert (→ ≠ Σ). [`Editor::draw`] overlays these over
//! the fallback box after the font has laid out the line.
//!
//! Every glyph is exactly one 10×20 cell (matching the body `FONT_10X20`, i.e.
//! `editor::CW`×`editor::CH`), so the editor's 1-char = 1-cell layout invariant
//! is untouched. A row is a 10-bit pattern: the leftmost column (x = 0) is the
//! high bit, so a binary literal reads left-to-right as the pixels of that row.
//! Rows are top (y = 0) to bottom (y = 19); 1 = ink.
use crate::Frame;
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::*;
const GLYPH_W: usize = 10;
const GLYPH_H: usize = 20;
/// A 10×20 1-bit glyph: one `u16` per row, low `GLYPH_W` bits used, high bit =
/// leftmost pixel.
pub type Glyph = [u16; GLYPH_H];
// → U+2192 RIGHTWARDS ARROW: a horizontal shaft with a `>` head at the right.
#[rustfmt::skip]
const ARROW_RIGHT: Glyph = [
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000010000,
0b0000001000,
0b0000000100,
0b0000000010,
0b0111111111,
0b0111111111,
0b0000000010,
0b0000000100,
0b0000001000,
0b0000010000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
];
// ≠ U+2260 NOT EQUAL TO: two equals bars with a slash through them.
#[rustfmt::skip]
const NOT_EQUAL: Glyph = [
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000010,
0b0000000100,
0b0000000100,
0b0000001000,
0b0111111110,
0b0000010000,
0b0000010000,
0b0000100000,
0b0111111110,
0b0001000000,
0b0001000000,
0b0010000000,
0b0010000000,
0b0000000000,
0b0000000000,
0b0000000000,
];
// Σ U+03A3 GREEK CAPITAL LETTER SIGMA: top & bottom bars with the diagonals
// meeting at a vertex on the centre-right.
#[rustfmt::skip]
const SIGMA: Glyph = [
0b0000000000,
0b0000000000,
0b0000000000,
0b0111111110,
0b0100000000,
0b0010000000,
0b0001000000,
0b0000100000,
0b0000010000,
0b0000100000,
0b0001000000,
0b0010000000,
0b0100000000,
0b0100000000,
0b0111111110,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
];
// • U+2022 BULLET: a filled dot at mid-height.
#[rustfmt::skip]
const BULLET: Glyph = [
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0001111000,
0b0011111100,
0b0011111100,
0b0011111100,
0b0001111000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
];
// … U+2026 HORIZONTAL ELLIPSIS: three dots on the baseline.
#[rustfmt::skip]
const ELLIPSIS: Glyph = [
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0110110110,
0b0110110110,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
];
// U+2013 EN DASH: a short mid-height bar.
#[rustfmt::skip]
const EN_DASH: Glyph = [
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0011111100,
0b0011111100,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
];
// — U+2014 EM DASH: a full-width mid-height bar.
#[rustfmt::skip]
const EM_DASH: Glyph = [
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b1111111111,
0b1111111111,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
];
// ' U+2018 LEFT SINGLE QUOTATION MARK: raised, tail up (opening).
#[rustfmt::skip]
const LEFT_SINGLE_QUOTE: Glyph = [
0b0000000000,
0b0000000000,
0b0000100000,
0b0001100000,
0b0001100000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
];
// ' U+2019 RIGHT SINGLE QUOTATION MARK / apostrophe: raised, tail down (closing).
#[rustfmt::skip]
const RIGHT_SINGLE_QUOTE: Glyph = [
0b0000000000,
0b0000000000,
0b0001100000,
0b0001100000,
0b0000100000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
];
// " U+201C LEFT DOUBLE QUOTATION MARK: two opening quotes.
#[rustfmt::skip]
const LEFT_DOUBLE_QUOTE: Glyph = [
0b0000000000,
0b0000000000,
0b0010010000,
0b0011011000,
0b0011011000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
];
// " U+201D RIGHT DOUBLE QUOTATION MARK: two closing quotes.
#[rustfmt::skip]
const RIGHT_DOUBLE_QUOTE: Glyph = [
0b0000000000,
0b0000000000,
0b0011011000,
0b0011011000,
0b0010010000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
0b0000000000,
];
/// The 10×20 bitmap for a character the ISO-8859-15 font can't draw, or `None`
/// if the base font already covers it (all ASCII and Latin-9, including `œ €`).
pub fn extra_glyph(c: char) -> Option<&'static Glyph> {
Some(match c {
'\u{2192}' => &ARROW_RIGHT, // →
'\u{2260}' => &NOT_EQUAL, // ≠
'\u{03A3}' => &SIGMA, // Σ
'\u{2022}' => &BULLET, // •
'\u{2026}' => &ELLIPSIS, // …
'\u{2013}' => &EN_DASH, //
'\u{2014}' => &EM_DASH, // —
'\u{2018}' => &LEFT_SINGLE_QUOTE, // '
'\u{2019}' => &RIGHT_SINGLE_QUOTE, // '
'\u{201C}' => &LEFT_DOUBLE_QUOTE, // "
'\u{201D}' => &RIGHT_DOUBLE_QUOTE, // "
_ => return None,
})
}
/// Paint a glyph over one 10×20 cell at `(x, y)`. Fills the whole cell — set
/// bits in `ink`, unset bits in `ink.invert()` — so it fully overwrites whatever
/// the base font drew there (e.g. the fallback box). Pass `ink = On` for a
/// normal black-on-white cell, `ink = Off` for a reverse-video cell (a white
/// glyph over the black selection/caret fill).
pub fn blit_glyph(f: &mut Frame, x: i32, y: i32, g: &Glyph, ink: BinaryColor) {
let bg = ink.invert();
let pixels = g.iter().enumerate().flat_map(move |(row, &bits)| {
(0..GLYPH_W).map(move |c| {
let on = (bits >> (GLYPH_W - 1 - c)) & 1 == 1;
Pixel(
Point::new(x + c as i32, y + row as i32),
if on { ink } else { bg },
)
})
});
// Frame's DrawTarget error is Infallible.
let _ = f.draw_iter(pixels);
}