Files
pebble-misc/tools/scale_bdf.py
T

183 lines
5.9 KiB
Python
Executable File

#!/usr/bin/env python3
import sys
def scale_bitmap_rows(rows, bbx_width, factor):
new_width = bbx_width * factor
new_bytes = (new_width + 7) // 8
new_bits = new_bytes * 8
old_bytes = (bbx_width + 7) // 8
old_bits = old_bytes * 8
result = []
for row_hex in rows:
hex_val = int(row_hex, 16)
# Extract bits MSB-first — BDF pixels are left-aligned within
# ceil(width/8) bytes, MSB of first byte = leftmost pixel.
bits = []
for bit_idx in range(bbx_width):
bit = (hex_val >> (old_bits - 1 - bit_idx)) & 1
bits.append(bit)
# Expand horizontally: each bit repeated `factor` times
expanded = []
for b in bits:
expanded.extend([b] * factor)
# Pack back into MSB-first, left-aligned hex
packed = 0
for idx, b in enumerate(expanded):
if b:
packed |= 1 << (new_bits - 1 - idx)
hex_str = format(packed, f'0{new_bytes * 2}X')
# Expand vertically: duplicate the row `factor` times
for _ in range(factor):
result.append(hex_str)
return result
def scale_bdf(input_path, output_path, factor):
if factor < 1 or not isinstance(factor, int):
print(f"Error: scale factor must be a positive integer, got {factor}")
sys.exit(1)
with open(input_path) as f:
lines = f.readlines()
out = []
in_bitmap = False
bitmap_rows = []
current_bbx_w = 0
current_bbx_h = 0
current_bbx_x = 0
current_bbx_y = 0
for line in lines:
line = line.rstrip('\n')
if in_bitmap:
if line == 'ENDCHAR':
scaled_rows = scale_bitmap_rows(bitmap_rows, current_bbx_w, factor)
out.extend(scaled_rows)
out.append('ENDCHAR')
in_bitmap = False
bitmap_rows = []
else:
bitmap_rows.append(line)
continue
if line == 'STARTCHAR':
in_bitmap = False
out.append(line)
elif line == 'BITMAP':
in_bitmap = True
out.append('BITMAP')
elif line.startswith('FONT '):
out.append(scale_font_line(line, factor))
elif line.startswith('SIZE '):
parts = line.split()
out.append(
f'SIZE {int(parts[1]) * factor} {parts[2]} {parts[3]}'
)
elif line.startswith('FONTBOUNDINGBOX '):
parts = line.split()
out.append(
f'FONTBOUNDINGBOX {int(parts[1]) * factor} '
f'{int(parts[2]) * factor} '
f'{int(parts[3]) * factor} '
f'{int(parts[4]) * factor}'
)
elif line.startswith('BBX '):
parts = line.split()
current_bbx_w = int(parts[1])
current_bbx_h = int(parts[2])
current_bbx_x = int(parts[3])
current_bbx_y = int(parts[4])
out.append(
f'BBX {current_bbx_w * factor} '
f'{current_bbx_h * factor} '
f'{current_bbx_x * factor} '
f'{current_bbx_y * factor}'
)
elif line.startswith('PIXEL_SIZE '):
parts = line.split()
out.append(f'PIXEL_SIZE {int(parts[1]) * factor}')
elif line.startswith('POINT_SIZE '):
parts = line.split()
out.append(f'POINT_SIZE {int(parts[1]) * factor}')
elif line.startswith('FONT_ASCENT '):
parts = line.split()
out.append(f'FONT_ASCENT {int(parts[1]) * factor}')
elif line.startswith('FONT_DESCENT '):
parts = line.split()
out.append(f'FONT_DESCENT {int(parts[1]) * factor}')
elif line.startswith('CAP_HEIGHT '):
parts = line.split()
val = int(parts[1])
if val > 0:
out.append(f'CAP_HEIGHT {val * factor}')
else:
out.append(line)
elif line.startswith('X_HEIGHT '):
parts = line.split()
val = int(parts[1])
if val > 0:
out.append(f'X_HEIGHT {val * factor}')
else:
out.append(line)
elif line.startswith('AVERAGE_WIDTH '):
parts = line.split()
out.append(f'AVERAGE_WIDTH {int(parts[1]) * factor}')
elif line.startswith('DWIDTH '):
parts = line.split()
out.append(f'DWIDTH {int(parts[1]) * factor} {parts[2]}')
elif line.startswith('SWIDTH '):
parts = line.split()
out.append(f'SWIDTH {int(parts[1]) * factor} {parts[2]}')
elif line.startswith('BITED_DWIDTH '):
parts = line.split()
out.append(f'BITED_DWIDTH {int(parts[1]) * factor}')
elif line.startswith('METRICSSET '):
pass # skip Apple metrics, they'll be regenerated
else:
out.append(line)
with open(output_path, 'w') as f:
f.write('\n'.join(out) + '\n')
print(f'Scaled {input_path} by {factor}x → {output_path}')
def scale_font_line(line, factor):
"""Scale the XLFD FONT line."""
# FONT -foundry-family-weight-slant-setwidth-addstyle-pixel-point-resx-resy...
# Fields are dash-separated
parts = line.split('-')
# parts[0] = 'FONT '
# parts[7] = pixel size (should be int)
# parts[8] = point size (decipoints)
try:
pixel_size = int(parts[7])
parts[7] = str(pixel_size * factor)
except (ValueError, IndexError):
pass
try:
point_size = int(parts[8])
parts[8] = str(point_size * factor)
except (ValueError, IndexError):
pass
return '-'.join(parts)
if __name__ == '__main__':
if len(sys.argv) != 4:
print(f'Usage: {sys.argv[0]} <input.bdf> <output.bdf> <scale_factor>')
print(f'Example: {sys.argv[0]} lecoishmono.bdf lecoishmono_18.bdf 2')
sys.exit(1)
scale_bdf(sys.argv[1], sys.argv[2], int(sys.argv[3]))