/ adafruit_bitmap_font / pcf.py
pcf.py
1 # pylint: skip-file 2 # Remove the above when PCF is actually supported. 3 4 from .glyph_cache import GlyphCache 5 import displayio 6 import struct 7 8 _PCF_PROPERTIES = 1 << 0 9 _PCF_ACCELERATORS = 1 << 1 10 _PCF_METRICS = 1 << 2 11 _PCF_BITMAPS = 1 << 3 12 _PCF_INK_METRICS = 1 << 4 13 _PCF_BDF_ENCODINGS = 1 << 5 14 _PCF_SWIDTHS = 1 << 6 15 _PCF_GLYPH_NAMES = 1 << 7 16 _PCF_BDF_ACCELERATORS = 1 << 8 17 18 _PCF_DEFAULT_FORMAT = 0x00000000 19 _PCF_INKBOUNDS = 0x00000200 20 _PCF_ACCEL_W_INKBOUNDS = 0x00000100 21 _PCF_COMPRESSED_METRICS = 0x00000100 22 23 _PCF_GLYPH_PAD_MASK = 3 << 0 # See the bitmap table for explanation */ 24 _PCF_BYTE_MASK = 1 << 2 # If set then Most Sig Byte First */ 25 _PCF_BIT_MASK = 1 << 3 # If set then Most Sig Bit First */ 26 _PCF_SCAN_UNIT_MASK = 3 << 4 27 28 # https://fontforge.github.io/en-US/documentation/reference/pcf-format/ 29 30 31 class PCF(GlyphCache): 32 def __init__(self, f): 33 super().__init__() 34 self.file = f 35 self.name = f 36 f.seek(0) 37 header, table_count = self.read("<4sI") 38 self.tables = {} 39 for _ in range(table_count): 40 type, format, size, offset = self.read("<IIII") 41 self.tables[type] = {"format": format, "size": size, "offset": offset} 42 print(type) 43 44 def read(self, format): 45 s = struct.calcsize(format) 46 return struct.unpack_from(format, self.file.read(s)) 47 48 def get_bounding_box(self): 49 property_table_offset = self.tables[_PCF_PROPERTIES]["offset"] 50 self.file.seek(property_table_offset) 51 (format,) = self.read("<I") 52 53 if format & _PCF_BYTE_MASK == 0: 54 raise RuntimeError("Only big endian supported") 55 (nprops,) = self.read(">I") 56 self.file.seek(property_table_offset + 8 + 9 * nprops) 57 58 pos = self.file.tell() 59 if pos % 4 > 0: 60 self.file.read(4 - pos % 4) 61 (string_size,) = self.read(">I") 62 63 strings = self.file.read(string_size) 64 string_map = {} 65 i = 0 66 for s in strings.split(b"\x00"): 67 string_map[i] = s 68 i += len(s) + 1 69 70 self.file.seek(property_table_offset + 8) 71 for _ in range(nprops): 72 name_offset, isStringProp, value = self.read(">IBI") 73 74 if isStringProp: 75 print(string_map[name_offset], string_map[value]) 76 else: 77 print(string_map[name_offset], value) 78 return None 79 80 def load_glyphs(self, code_points): 81 metadata = True 82 character = False 83 code_point = None 84 rounded_x = 1 85 bytes_per_row = 1 86 desired_character = False 87 current_info = None 88 current_y = 0 89 total_remaining = len(code_points) 90 91 x, _, _, _ = self.get_bounding_box() 92 # create a scratch bytearray to load pixels into 93 scratch_row = memoryview(bytearray((((x - 1) // 32) + 1) * 4)) 94 95 self.file.seek(0) 96 while True: 97 line = self.file.readline() 98 if not line: 99 break 100 if line.startswith(b"CHARS "): 101 metadata = False 102 elif line.startswith(b"SIZE"): 103 _, self.point_size, self.x_resolution, self.y_resolution = line.split() 104 elif line.startswith(b"COMMENT"): 105 pass 106 elif line.startswith(b"STARTCHAR"): 107 # print(lineno, line.strip()) 108 # _, character_name = line.split() 109 character = True 110 elif line.startswith(b"ENDCHAR"): 111 character = False 112 if desired_character: 113 self._glyphs[code_point] = current_info 114 if total_remaining == 0: 115 return 116 desired_character = False 117 elif line.startswith(b"BBX"): 118 if desired_character: 119 _, x, y, dx, dy = line.split() 120 x = int(x) 121 y = int(y) 122 dx = int(dx) 123 dy = int(dy) 124 current_info["bounds"] = (x, y, dx, dy) 125 current_info["bitmap"] = displayio.Bitmap(x, y, 2) 126 elif line.startswith(b"BITMAP"): 127 if desired_character: 128 rounded_x = x // 8 129 if x % 8 > 0: 130 rounded_x += 1 131 bytes_per_row = rounded_x 132 if bytes_per_row % 4 > 0: 133 bytes_per_row += 4 - bytes_per_row % 4 134 current_y = 0 135 elif line.startswith(b"ENCODING"): 136 _, code_point = line.split() 137 code_point = int(code_point) 138 if code_point == code_points or code_point in code_points: 139 total_remaining -= 1 140 if code_point not in self._glyphs: 141 desired_character = True 142 current_info = {"bitmap": None, "bounds": None, "shift": None} 143 elif line.startswith(b"DWIDTH"): 144 if desired_character: 145 _, shift_x, shift_y = line.split() 146 shift_x = int(shift_x) 147 shift_y = int(shift_y) 148 current_info["shift"] = (shift_x, shift_y) 149 elif line.startswith(b"SWIDTH"): 150 pass 151 elif character: 152 if desired_character: 153 bits = int(line.strip(), 16) 154 for i in range(rounded_x): 155 val = (bits >> ((rounded_x - i - 1) * 8)) & 0xFF 156 scratch_row[i] = val 157 current_info["bitmap"]._load_row( 158 current_y, scratch_row[:bytes_per_row] 159 ) 160 current_y += 1 161 elif metadata: 162 # print(lineno, line.strip()) 163 pass