mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-05 14:08:39 +00:00
154 lines
5.0 KiB
Python
154 lines
5.0 KiB
Python
|
# Helpers to increase the font size from existing font data.
|
||
|
# Thanks to it we can save space by not having to include
|
||
|
# the larger font definitions.
|
||
|
|
||
|
from __future__ import annotations
|
||
|
|
||
|
from typing_extensions import Literal
|
||
|
from typing import Tuple
|
||
|
|
||
|
|
||
|
Bit = Literal[0, 1]
|
||
|
Point = Tuple[int, int]
|
||
|
|
||
|
|
||
|
def magnify_glyph_by_two(width: int, height: int, bytes_data: list[int]) -> list[int]:
|
||
|
"""Magnifying the font size by two.
|
||
|
|
||
|
Input and output are bytes (that is the standard in font files),
|
||
|
but internally works with bits.
|
||
|
"""
|
||
|
bits_data = _bytes_to_bits(bytes_data, height * width)
|
||
|
double_size_data = _double_the_bits(width, bits_data)
|
||
|
return _bits_to_bytes(double_size_data)
|
||
|
|
||
|
|
||
|
def _bytes_to_bits(bytes_data: list[int], bits_to_take: int) -> list[Bit]:
|
||
|
"""Transform bytes into bits."""
|
||
|
bits_data = [f"{i:08b}" for i in bytes_data[:-1]]
|
||
|
|
||
|
# Last element needs to be handled carefully,
|
||
|
# to respect the number of bits to take.
|
||
|
missing_bits = bits_to_take - len(bits_data) * 8
|
||
|
last_byte = bytes_data[-1]
|
||
|
last_binary = f"{last_byte:08b}"
|
||
|
# Taking either the right or left part of the last byte
|
||
|
if last_byte < 2**missing_bits:
|
||
|
bits_data.append(last_binary[-missing_bits:])
|
||
|
else:
|
||
|
bits_data.append(last_binary[:missing_bits])
|
||
|
|
||
|
return [1 if int(x) else 0 for x in "".join(bits_data)]
|
||
|
|
||
|
|
||
|
def _bits_to_bytes(bits_data: list[Bit]) -> list[int]:
|
||
|
"""Transform bits into bytes."""
|
||
|
bits_str = "".join([str(bit) for bit in bits_data])
|
||
|
bytes_str_list = [bits_str[i : i + 8] for i in range(0, len(bits_str), 8)]
|
||
|
# Last element needs to be right-padded to 8 bits
|
||
|
while len(bytes_str_list[-1]) != 8:
|
||
|
bytes_str_list[-1] += "0"
|
||
|
return [int(byte, 2) for byte in bytes_str_list]
|
||
|
|
||
|
|
||
|
def _double_the_bits(width: int, bits_data: list[Bit]) -> list[Bit]:
|
||
|
"""Double the dimension of a given glyph."""
|
||
|
# Allocate space for the new data - 2*2 bigger than the original
|
||
|
# Then fill all the new indexes with appropriate bits
|
||
|
double_size_data: list[Bit] = [0 for _ in range(4 * len(bits_data))]
|
||
|
for original_index, bit in enumerate(bits_data):
|
||
|
for new_index in _corresponding_indexes(original_index, width):
|
||
|
double_size_data[new_index] = bit
|
||
|
return double_size_data
|
||
|
|
||
|
|
||
|
def _corresponding_indexes(index: int, width: int) -> list[int]:
|
||
|
"""Find the indexes of the four pixels that correspond to the given one."""
|
||
|
point = _index_to_point(index, width)
|
||
|
points = _scale_by_two(point)
|
||
|
new_width = 2 * width
|
||
|
return [_point_to_index(new_point, new_width) for new_point in points]
|
||
|
|
||
|
|
||
|
def _scale_by_two(point: Point) -> tuple[Point, Point, Point, Point]:
|
||
|
"""Translate one pixel into four adjacent pixels to visually scale it by two."""
|
||
|
x, y = point
|
||
|
return (
|
||
|
(x * 2, y * 2),
|
||
|
(x * 2 + 1, y * 2),
|
||
|
(x * 2, y * 2 + 1),
|
||
|
(x * 2 + 1, y * 2 + 1),
|
||
|
)
|
||
|
|
||
|
|
||
|
def _point_to_index(point: Point, width: int) -> int:
|
||
|
"""Translate point to index according to the glyph width."""
|
||
|
x, y = point
|
||
|
assert 0 <= x < width
|
||
|
return y * width + x
|
||
|
|
||
|
|
||
|
def _index_to_point(index: int, width: int) -> Point:
|
||
|
"""Translate index to point according to the glyph width."""
|
||
|
x = index % width
|
||
|
y = index // width
|
||
|
return x, y
|
||
|
|
||
|
|
||
|
def print_from_bits(width: int, height: int, bit_data: list[Bit]):
|
||
|
"""Print the glyph into terminal from bit data."""
|
||
|
for line_id in range(height):
|
||
|
line = bit_data[line_id * width : (line_id + 1) * width]
|
||
|
str_line = "".join([str(x) for x in line])
|
||
|
print(str_line.replace("0", " "))
|
||
|
print()
|
||
|
|
||
|
|
||
|
def print_from_bytes(width: int, height: int, bytes_data: list[int]):
|
||
|
"""Print the glyph into terminal from byte data."""
|
||
|
bits_data = _bytes_to_bits(bytes_data, height * width)
|
||
|
print_from_bits(width, height, bits_data)
|
||
|
|
||
|
|
||
|
################################
|
||
|
# TEST SECTION for pytest
|
||
|
################################
|
||
|
|
||
|
# fmt: off
|
||
|
HEIGHT = 7
|
||
|
K_WIDTH = 5
|
||
|
K_GLYPH = [140, 169, 138, 74, 32]
|
||
|
K_BITS = [1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1]
|
||
|
K_MAGNIFIED = [192, 240, 60, 51, 12, 204, 51, 15, 3, 192, 204, 51, 12, 51, 12, 192, 240, 48]
|
||
|
M_GLYPH = [131, 7, 29, 89, 48, 96, 128]
|
||
|
M_WIDTH = 7
|
||
|
# fmt: on
|
||
|
|
||
|
|
||
|
def test_bits_to_bytes_and_back():
|
||
|
vectors = ( # height, width, bytes_data
|
||
|
(HEIGHT, K_WIDTH, K_GLYPH),
|
||
|
(HEIGHT, M_WIDTH, M_GLYPH),
|
||
|
)
|
||
|
|
||
|
for height, width, bytes_data in vectors:
|
||
|
bits_data = _bytes_to_bits(bytes_data, height * width)
|
||
|
assert _bits_to_bytes(bits_data) == bytes_data
|
||
|
|
||
|
|
||
|
def test_bit_to_bytes():
|
||
|
assert _bytes_to_bits(K_GLYPH, HEIGHT * K_WIDTH) == K_BITS
|
||
|
|
||
|
|
||
|
def test_overall_magnify():
|
||
|
assert magnify_glyph_by_two(K_WIDTH, HEIGHT, K_GLYPH) == K_MAGNIFIED
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
# Print letter K, magnify it and print it also
|
||
|
print("K_GLYPH", K_GLYPH)
|
||
|
print_from_bytes(K_WIDTH, HEIGHT, K_GLYPH)
|
||
|
magnified_data = magnify_glyph_by_two(K_WIDTH, HEIGHT, K_GLYPH)
|
||
|
print("magnified_data", magnified_data)
|
||
|
print_from_bytes(2 * K_WIDTH, 2 * HEIGHT, magnified_data)
|