mirror of
https://github.com/NaitLee/Cat-Printer.git
synced 2025-05-15 23:00:15 -07:00
Remove `COPYING` for being identified as "unknown", moved to readme Few fixes to readme and contributing More in TODO & dev-diary
192 lines
5.5 KiB
Python
192 lines
5.5 KiB
Python
'''
|
|
Python lib for reading PF2 font files: http://grub.gibibit.com/New_font_format
|
|
|
|
Copyright © 2021-2022 NaitLee Soft. No rights reserved.
|
|
License CC0-1.0-only: https://directory.fsf.org/wiki/License:CC0
|
|
|
|
Don't forget to see how it's used in `text_print.py`
|
|
'''
|
|
|
|
import io
|
|
from typing import Dict, Tuple
|
|
|
|
def uint32be(b: bytes):
|
|
'Translate 4 bytes as unsigned big-endian 32-bit int'
|
|
return (
|
|
(b[0] << 24) +
|
|
(b[1] << 16) +
|
|
(b[2] << 8) +
|
|
b[3]
|
|
)
|
|
|
|
def int32be(b: bytes):
|
|
'Translate 4 bytes as signed big-endian 32-bit int'
|
|
u = uint32be(b)
|
|
return u - ((u >> 31 & 0b1) << 32)
|
|
|
|
def uint16be(b: bytes):
|
|
'Translate 2 bytes as big-endian unsigned 16-bit int'
|
|
return (b[0] << 8) + b[1]
|
|
|
|
def int16be(b: bytes):
|
|
'Translate 2 bytes as big-endian signed 16-bit int'
|
|
u = uint16be(b)
|
|
return u - ((u >> 15 & 0b1) << 16)
|
|
|
|
|
|
class Character():
|
|
'A PF2 character'
|
|
|
|
width: int
|
|
height: int
|
|
x_offset: int
|
|
y_offset: int
|
|
device_width: int
|
|
bitmap_data: bytes
|
|
|
|
def get_bit(self, x, y):
|
|
'Get the bit at (x, y) of this character\'s raster glyph'
|
|
char_byte = (self.width * y + x) // 8
|
|
char_bit = 7 - (self.width * y + x) % 8
|
|
return self.bitmap_data[char_byte] & (0b1 << char_bit)
|
|
|
|
class PF2():
|
|
'The PF2 class, for serializing a PF2 font file'
|
|
|
|
is_pf2: bool
|
|
'Sets to false if the read file is not PF2 font file'
|
|
|
|
missing_character_code: int
|
|
in_memory: bool
|
|
|
|
font_name: str
|
|
family: str
|
|
weight: str
|
|
slant: str
|
|
point_size: int
|
|
max_width: int
|
|
max_height: int
|
|
ascent: int
|
|
descent: int
|
|
character_index: Dict[int, Tuple[int, int]]
|
|
data_offset: int
|
|
data_io: io.BufferedIOBase
|
|
|
|
def __init__(self, path='font.pf2', *, read_to_mem=True, missing_character: str='?'):
|
|
self.missing_character_code = ord(missing_character)
|
|
self.in_memory = read_to_mem
|
|
file = open(path, 'rb')
|
|
if read_to_mem:
|
|
self.data_io = io.BytesIO(file.read())
|
|
file.close()
|
|
file = self.data_io
|
|
self.is_pf2 = (file.read(12) == b'FILE\x00\x00\x00\x04PFF2')
|
|
if not self.is_pf2:
|
|
return
|
|
while True:
|
|
name = file.read(4)
|
|
data_length = int32be(file.read(4))
|
|
if name == b'CHIX':
|
|
self.character_index = {}
|
|
for _ in range(data_length // (4 + 1 + 4)):
|
|
code_point = int32be(file.read(4))
|
|
compression = file.read(1)[0]
|
|
offset = int32be(file.read(4))
|
|
self.character_index[code_point] = (
|
|
compression, offset
|
|
)
|
|
continue
|
|
elif name == b'DATA':
|
|
file.seek(0)
|
|
break
|
|
data = file.read(data_length)
|
|
if name == b'NAME':
|
|
self.font_name = data
|
|
elif name == b'FAMI':
|
|
self.family = data
|
|
elif name == b'WEIG':
|
|
self.weight = data
|
|
elif name == b'SLAN':
|
|
self.slant = data
|
|
elif name == b'PTSZ':
|
|
self.point_size = uint16be(data)
|
|
elif name == b'MAXW':
|
|
self.max_width = uint16be(data)
|
|
elif name == b'MAXH':
|
|
self.max_height = uint16be(data)
|
|
elif name == b'ASCE':
|
|
self.ascent = uint16be(data)
|
|
elif name == b'DESC':
|
|
self.descent = uint16be(data)
|
|
|
|
def get_char(self, char: str):
|
|
'Get a character, returning a `Character` instance'
|
|
char_point = ord(char)
|
|
info = self.character_index.get(char_point)
|
|
if info is None:
|
|
info = self.character_index[self.missing_character_code]
|
|
_compression, offset = info
|
|
data = self.data_io
|
|
data.seek(offset)
|
|
char = Character()
|
|
char.width = uint16be(data.read(2))
|
|
char.height = uint16be(data.read(2))
|
|
char.x_offset = int16be(data.read(2))
|
|
char.y_offset = int16be(data.read(2))
|
|
char.device_width = int16be(data.read(2))
|
|
char.bitmap_data = data.read(
|
|
(char.width * char.height + 7) // 8
|
|
)
|
|
return char
|
|
|
|
__getitem__ = get_char
|
|
|
|
def __del__(self):
|
|
self.data_io.close()
|
|
|
|
|
|
class CharacterS(Character):
|
|
'A "scaled" character'
|
|
|
|
scale: int = 1
|
|
|
|
def get_bit(self, x, y):
|
|
'Get the bit at (x, y) of this character\'s raster glyph'
|
|
scale = self.scale
|
|
width = self.width // scale
|
|
x //= scale
|
|
y //= scale
|
|
char_byte = (width * y + x) // 8
|
|
char_bit = 7 - (width * y + x) % 8
|
|
return (self.bitmap_data[char_byte] &
|
|
(0b1 << char_bit)) >> char_bit
|
|
|
|
class PF2S(PF2):
|
|
'PF2 class with glyph scaling support'
|
|
|
|
scale: int = 1
|
|
|
|
def __init__(self, *args, scale: int=1, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.scale = scale
|
|
self.point_size *= scale
|
|
self.max_width *= scale
|
|
self.max_height *= scale
|
|
self.ascent *= scale
|
|
self.descent *= scale
|
|
|
|
def get_char(self, char):
|
|
scale = self.scale
|
|
char = super().get_char(char)
|
|
chars = CharacterS()
|
|
chars.scale = scale
|
|
chars.width = char.width * scale
|
|
chars.height = char.height * scale
|
|
chars.device_width = char.device_width * scale
|
|
chars.x_offset = char.x_offset * scale
|
|
chars.y_offset = char.y_offset * scale
|
|
chars.bitmap_data = char.bitmap_data
|
|
return chars
|
|
|
|
__getitem__ = get_char
|