#!/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]} ') 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]))