mirror of
https://github.com/google/pebble.git
synced 2025-05-20 10:24:58 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
314
tools/font/dump_font.py
Executable file
314
tools/font/dump_font.py
Executable file
|
@ -0,0 +1,314 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright 2024 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import struct
|
||||
import array
|
||||
import pprint
|
||||
import math
|
||||
import json
|
||||
import itertools
|
||||
from PIL import Image
|
||||
|
||||
FONT_VERSION_1 = 1
|
||||
FONT_VERSION_2 = 2
|
||||
FONT_VERSION_3 = 3
|
||||
# Features
|
||||
FEATURE_OFFSET_16 = 0x01
|
||||
FEATURE_RLE4 = 0x02
|
||||
GLYPH_MD_STRUCT = 'BBbbb'
|
||||
|
||||
def dec_and_hex(i):
|
||||
return "0x{:x} ({:d})".format(i, i)
|
||||
|
||||
|
||||
def grouper(n, iterable, fillvalue=None):
|
||||
"""grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"""
|
||||
args = [iter(iterable)] * n
|
||||
return itertools.izip_longest(fillvalue=fillvalue, *args)
|
||||
|
||||
|
||||
def get_glyph(features, tbl, offset_bytes):
|
||||
bitmap_offset_bytes = offset_bytes + struct.calcsize(GLYPH_MD_STRUCT)
|
||||
header = tbl[offset_bytes: bitmap_offset_bytes]
|
||||
(width, height, left, top, adv) = struct.unpack(GLYPH_MD_STRUCT, header)
|
||||
|
||||
if (features & FEATURE_RLE4):
|
||||
bitmap_length_bytes = (height + 1) / 2 # RLE4 stores 2 rle_units per byte
|
||||
else:
|
||||
bitmap_length_bytes = ((height * width) + 7) / 8
|
||||
|
||||
bitmap_length_bytes_word_aligned = ((bitmap_length_bytes + 3) / 4) * 4
|
||||
data = tbl[bitmap_offset_bytes: bitmap_offset_bytes + bitmap_length_bytes_word_aligned]
|
||||
return {
|
||||
'offset_bytes' : dec_and_hex(offset_bytes),
|
||||
'top' : top,
|
||||
'left' : left,
|
||||
'height' : height,
|
||||
'width' : width,
|
||||
'advance' : adv,
|
||||
'bitmap' : data
|
||||
}, header
|
||||
|
||||
def hasher(codepoint, table_size):
|
||||
return (codepoint % table_size)
|
||||
|
||||
def print_hash_table(hash_table):
|
||||
print "index\tcount\toffset"
|
||||
for idx, sz, off in hash_table:
|
||||
print "%d\t%d\t%s" % (idx, sz, str(dec_and_hex(off)))
|
||||
|
||||
|
||||
def print_glyph(features, glyph_table, offset, raw, show_image):
|
||||
|
||||
def decompress_glyph_RLE4(rle_stream, num_units, width):
|
||||
bitmap = []
|
||||
|
||||
for b in array.array('B', rle_stream):
|
||||
# There are two RLE4 units per byte. LSNibble first.
|
||||
for i in range(2):
|
||||
# Handle the padding by skipping whatever remains
|
||||
if (num_units == 0):
|
||||
break
|
||||
num_units -= 1
|
||||
|
||||
# Each unit is <xyyy> where x is the symbol and yyy is (length - 1)
|
||||
length = (b & 0x7) + 1
|
||||
symbol = 1 if ((b >> 3) & 1) == 1 else 0
|
||||
bitmap.extend([symbol]*length)
|
||||
b >>= 4
|
||||
|
||||
# Correct the height, now that we know the decompressed size
|
||||
height = len(bitmap)/width
|
||||
|
||||
return bitmap, height
|
||||
|
||||
def glyph_bitmap_to_bitlist(g):
|
||||
height = g['height']
|
||||
|
||||
if not g['bitmap']:
|
||||
return None, height
|
||||
|
||||
if (features & FEATURE_RLE4):
|
||||
b, height = decompress_glyph_RLE4(g['bitmap'], height, g['width'])
|
||||
else:
|
||||
b = []
|
||||
for w in array.array('I', g['bitmap']):
|
||||
b.extend(((w & (1 << bit)) != 0 for bit in xrange(0, 32)))
|
||||
|
||||
return b, height
|
||||
|
||||
def draw_glyph_image(bitlist, width, height):
|
||||
img = Image.new('RGB', (width, height), "black")
|
||||
pixels = img.load()
|
||||
for i in range(img.size[0]):
|
||||
for j in range(img.size[1]):
|
||||
pixels[i, j] = (0, 0, 0) if bitlist[j * width + i] else (255, 255, 255)
|
||||
img.show()
|
||||
|
||||
def draw_glyph_ascii(bitlist, width, height):
|
||||
for y in xrange(height):
|
||||
for x in xrange(width):
|
||||
if bitlist[y * width + x]:
|
||||
sys.stdout.write('X')
|
||||
else:
|
||||
sys.stdout.write(' ')
|
||||
print
|
||||
|
||||
def draw_glyph_raw(header, bitlist, width, height):
|
||||
# Header:
|
||||
for byte in header:
|
||||
print '{:02x}'.format(ord(byte)),
|
||||
print ' - ',
|
||||
# Repack the glyph data. This is required because the glyph may have been compressed
|
||||
for byte in xrange(height * width / 8):
|
||||
w = 0
|
||||
for bit in range(8):
|
||||
if bitlist[byte * 8 + bit]:
|
||||
w |= 1 << bit
|
||||
print '{:02x}'.format(w),
|
||||
print
|
||||
|
||||
g, header = get_glyph(features, glyph_table, offset)
|
||||
|
||||
# FEATURE_RLE4 uses the height field for # RLE Units!
|
||||
bitlist, height = glyph_bitmap_to_bitlist(g)
|
||||
|
||||
if raw:
|
||||
header_offset = offset + struct.calcsize(GLYPH_MD_STRUCT)
|
||||
draw_glyph_raw(header, bitlist, g['width'], height)
|
||||
else:
|
||||
output = []
|
||||
output.append("offset bytes: {}".format(g['offset_bytes']))
|
||||
output.append("top: {}".format(g['top']))
|
||||
output.append("left: {}".format(g['left']))
|
||||
output.append("height: {}".format(height))
|
||||
output.append("width: {}".format(g['width']))
|
||||
output.append("advance: {}".format(g['advance']))
|
||||
output.append("bitmap:")
|
||||
print '\n'.join(output)
|
||||
print
|
||||
|
||||
if bitlist:
|
||||
if show_image:
|
||||
draw_glyph_image(bitlist, g['width'], height)
|
||||
else:
|
||||
draw_glyph_ascii(bitlist, g['width'], height)
|
||||
|
||||
|
||||
# Allow extended codepoint encoding for 'narrow Python builds'
|
||||
def my_unichr(i):
|
||||
try:
|
||||
return unichr(i)
|
||||
except ValueError:
|
||||
return struct.pack('i', i).decode('utf-32')
|
||||
|
||||
|
||||
# Attempt to print unicode characters. Do the best we can when redirecting output.
|
||||
# (See PYTHONIOENCODING)
|
||||
def print_glyph_header(codepoint, offset, raw=False):
|
||||
if raw:
|
||||
print '{:08X}:'.format(codepoint),
|
||||
else:
|
||||
print
|
||||
print u'{}\t({})\t{}'.format(dec_and_hex(codepoint), my_unichr(codepoint),
|
||||
dec_and_hex(offset)).encode('utf-8', 'replace')
|
||||
print
|
||||
|
||||
|
||||
def main(pfo_path, show_hash_table, offset_table, glyph, all_glyphs, show_image, raw):
|
||||
with open(pfo_path, 'rb') as f:
|
||||
font = f.read()
|
||||
|
||||
# Assume version 3 to start
|
||||
version = 3
|
||||
font_md_format = ['', '<BBHH', '<BBHHBB', '<BBHHBBBB']
|
||||
font_md_size = struct.calcsize(font_md_format[version])
|
||||
(version, max_height, num_glyphs, wildcard_cp, table_size, cp_bytes,
|
||||
struct_size, features) = struct.unpack(font_md_format[version], font[:font_md_size])
|
||||
if version == 3:
|
||||
pass
|
||||
elif version == 2:
|
||||
# Set the defaults
|
||||
font_md_size = struct.calcsize(font_md_format[version])
|
||||
features = 0
|
||||
else:
|
||||
raise Exception('Error: Unexpected font file version {}'.format(version))
|
||||
|
||||
# Build up the offset entry struct
|
||||
offset_table_format = '<'
|
||||
offset_table_format += 'L' if cp_bytes == 4 else 'H'
|
||||
offset_table_format += 'H' if features & FEATURE_OFFSET_16 else 'L'
|
||||
offset_entry_size = struct.calcsize(offset_table_format)
|
||||
|
||||
hash_entry_format = '<BBH'
|
||||
hash_entry_size = struct.calcsize(hash_entry_format)
|
||||
|
||||
def hash_iterator(tbl, num):
|
||||
for i in xrange(0, num * hash_entry_size, hash_entry_size):
|
||||
yield struct.unpack(hash_entry_format, tbl[i:i + hash_entry_size])
|
||||
|
||||
def offset_iterator(tbl, num):
|
||||
for i in xrange(0, num * offset_entry_size, offset_entry_size):
|
||||
yield struct.unpack(offset_table_format, tbl[i:i + offset_entry_size])
|
||||
|
||||
hash_table = [(a,b,c) for (a,b,c) in hash_iterator(font[font_md_size:], table_size)]
|
||||
offset_tables_start = font_md_size + hash_entry_size * table_size
|
||||
glyph_table_start = offset_tables_start + offset_entry_size * (num_glyphs)
|
||||
glyph_table = font[glyph_table_start:]
|
||||
|
||||
if not raw:
|
||||
print 'Font info'
|
||||
pprint.pprint(
|
||||
{'version': version,
|
||||
'max_height': max_height,
|
||||
'num_glyphs': num_glyphs,
|
||||
'offset_table_size': num_glyphs * offset_entry_size,
|
||||
'glyph_table_start': dec_and_hex(glyph_table_start),
|
||||
'wildcard_cp': dec_and_hex(wildcard_cp),
|
||||
'codepoint bytes': cp_bytes,
|
||||
'hash_table_size': table_size,
|
||||
'font_header_size': font_md_size,
|
||||
'features': features,
|
||||
'features - offset size': 16 if (features & FEATURE_OFFSET_16) else 32,
|
||||
'features - RLE4': True if (features & FEATURE_RLE4) else False})
|
||||
|
||||
print
|
||||
print 'Hash Table start: {}'.format(font_md_size)
|
||||
print 'Offset Table start: {}'.format(offset_tables_start)
|
||||
print 'Glyph Table start: {}'.format(glyph_table_start)
|
||||
print '--------------------------'
|
||||
|
||||
if all_glyphs:
|
||||
for _,sz,off in hash_table:
|
||||
off_table = dict([x for x in offset_iterator(font[(offset_tables_start + off):], sz)])
|
||||
for k,v in sorted(off_table.items()):
|
||||
print_glyph_header(k, v, raw)
|
||||
print_glyph(features, glyph_table, v, raw, False)
|
||||
if not raw:
|
||||
print
|
||||
print
|
||||
return
|
||||
|
||||
if show_hash_table:
|
||||
print
|
||||
print_hash_table(hash_table)
|
||||
|
||||
if offset_table:
|
||||
offset_table = int(offset_table)
|
||||
_,sz,off = hash_table[offset_table]
|
||||
if not raw:
|
||||
print 'Offset Table {} offset: {}'.format(offset_table, offset_tables_start + off)
|
||||
off_table = dict([x for x in offset_iterator(font[(offset_tables_start + off):], sz)])
|
||||
for k,v in sorted(off_table.items()):
|
||||
print_glyph_header(k, v, raw)
|
||||
print_glyph(features, glyph_table, v, raw, show_image)
|
||||
if not raw:
|
||||
print
|
||||
print
|
||||
|
||||
if glyph:
|
||||
codepoint = int(glyph, 16)
|
||||
glyph_hash = hasher(codepoint, table_size)
|
||||
_,sz,off = hash_table[glyph_hash]
|
||||
off_table = dict([x for x in offset_iterator(font[(offset_tables_start + off):], sz)])
|
||||
glyph_off = 0
|
||||
for (cp, off) in off_table.items():
|
||||
if cp == codepoint:
|
||||
glyph_off = off_table[codepoint]
|
||||
break
|
||||
else:
|
||||
print "{} not in font".format(codepoint)
|
||||
return
|
||||
print_glyph_header(codepoint, glyph_off, raw)
|
||||
print_glyph(features, glyph_table, glyph_off, raw, show_image)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-o', '--offsets', help='print hash table')
|
||||
parser.add_argument('-t', '--hashes', action='store_true', help='print hash table')
|
||||
parser.add_argument('-g', '--glyph', help='draw glyph in external image viewer')
|
||||
parser.add_argument('-G', '--ascii_glyph', help='draw glyph in ASCII')
|
||||
parser.add_argument('-a', '--all_glyphs', action='store_true', help='print all glyphs')
|
||||
parser.add_argument('-r', '--raw', action='store_true')
|
||||
parser.add_argument('pebble_font')
|
||||
args = parser.parse_args()
|
||||
|
||||
codepoint = args.ascii_glyph if args.ascii_glyph else args.glyph
|
||||
|
||||
main(args.pebble_font, args.hashes, args.offsets, codepoint, args.all_glyphs,
|
||||
args.ascii_glyph is None, args.raw)
|
Loading…
Add table
Add a link
Reference in a new issue