textual-paint/scripts/repack_font.py
2023-06-27 19:13:47 -04:00

179 lines
7.0 KiB
Python
Executable File

#!/usr/bin/env python3
# Install with `pip install -e .` in the root directory of the repository before running this script.
# This is needed because PYTHON HAS NO GOOD WAY OF IMPORTING FILES RELATIVELY.
# It can't be done outside of a package. Why? WHY?!
import os
from PIL import Image
from textual_paint.figlet_font_writer import FIGletFontWriter
block_char_lookup = {
0x0: ' ',
0x1: '',
0x2: '',
0x3: '',
0x4: '',
0x5: '',
0x6: '',
0x7: '',
0x8: '',
0x9: '',
0xA: '',
0xB: '',
0xC: '',
0xD: '',
0xE: '',
0xF: '',
}
def extract_textures(image_path: str):
"""Removes the border around glyphs in an image, creates a new image without the border, and converts the image into FIGlet format font files."""
# Open the image
image = Image.open(image_path)
# Calculate the texture size and border width
width, height = image.size
texture_width = 4
texture_height = 4
border_width = 1
# Calculate the number of textures in each dimension
num_textures_x = (width - border_width) // (texture_width + border_width)
num_textures_y = (height - border_width) // (texture_height + border_width)
# Create a new image to store the extracted textures
extracted_image = Image.new('RGB', (num_textures_x * texture_width, num_textures_y * texture_height))
half_size_meta_glyphs: dict[int, str] = {}
full_size_meta_glyphs: dict[int, str] = {}
# Extract textures
for row in range(num_textures_y):
for col in range(num_textures_x):
# Calculate the coordinates for the current texture
left = col * (texture_width + border_width) + border_width
upper = row * (texture_height + border_width) + border_width
right = left + texture_width
lower = upper + texture_height
# Crop the texture from the original image
texture = image.crop((left, upper, right, lower))
# Calculate the paste coordinates on the extracted image
paste_x = col * texture_width
paste_y = row * texture_height
# Paste the texture onto the extracted image
extracted_image.paste(texture, (paste_x, paste_y))
# Calculate the ordinal of the character
ordinal = row * num_textures_x + col
ordinal -= 2
# Extract as half-size FIGlet font
extracted_text_half = ""
for y in range(0, texture_height, 2):
for x in range(0, texture_width, 2):
# Get the four pixels that make up a character
fg_palette_index = 1
aa = texture.getpixel((x, y)) == fg_palette_index
ab = texture.getpixel((x, y + 1)) == fg_palette_index
ba = texture.getpixel((x + 1, y)) == fg_palette_index
bb = texture.getpixel((x + 1, y + 1)) == fg_palette_index
# Convert the pixel to a character
# char = block_char_lookup[(aa << 3) | (ab << 2) | (ba << 1) | bb]
char = block_char_lookup[(bb << 3) | (ab << 2) | (ba << 1) | aa]
# Add the character to the extracted text
extracted_text_half += char
# Add a newline after each row
extracted_text_half += '\n'
half_size_meta_glyphs[ordinal] = extracted_text_half
# Extract as full-size FIGlet font
extracted_text_full = ""
for y in range(texture_height):
for x in range(texture_width):
# Get the pixel
fg_palette_index = 1
pixel = texture.getpixel((x, y)) == fg_palette_index
# Convert the pixel to a character
char = '' if pixel else ' '
# Add the character to the extracted text
extracted_text_full += char
# Add a newline after each row
extracted_text_full += '\n'
full_size_meta_glyphs[ordinal] = extracted_text_full
for figChars in [half_size_meta_glyphs, full_size_meta_glyphs]:
# Fill in the space characters with hard blanks
# figChars[32] = figChars[32].replace(' ', '$')
# Or just half of the max width of the FIGcharacters
figChars[32] = '\n'.join(['$' * (len(row) // 2) for row in figChars[32].split('\n')])
# Add hard blanks to the end of non-whitespace of each row of each FIGcharacter
# With FIGletFontWriter.Layout.FULL, this will ensure a space between rendered FIGcharacters.
# The fixup code (_fixFigChars) will handle the ragged right edge,
# although it won't look pretty having the dollar signs scattered in the font file.
for ordinal in figChars:
figChars[ordinal] = '\n'.join([row.rstrip() + '$' for row in figChars[ordinal].split('\n')])
shared_comment_lines = [
"by Isaiah Odhner",
"",
"Copyright (c) 2023, Isaiah Odhner (https://isaiahodhner.io),",
"with Reserved Font Name NanoTiny.",
"",
"This Font Software is licensed under the SIL Open Font License, Version 1.1.",
"This license is available in OFL.txt and is also available with a FAQ at:",
"http://scripts.sil.org/OFL",
]
half_size_font = FIGletFontWriter(
figChars=half_size_meta_glyphs,
baseline=2,
commentLines=[
"NanoTiny 2x2 (version 14)",
*shared_comment_lines,
],
horizontalLayout=FIGletFontWriter.Layout.FULL,
verticalLayout=FIGletFontWriter.Layout.FULL,
)
full_size_font = FIGletFontWriter(
figChars=full_size_meta_glyphs,
baseline=4,
commentLines=[
"NanoTiny 4x4 (version 14)",
*shared_comment_lines,
],
horizontalLayout=FIGletFontWriter.Layout.FULL,
verticalLayout=FIGletFontWriter.Layout.FULL,
)
return extracted_image, half_size_font.createFigFileData(), full_size_font.createFigFileData()
repo_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
font_folder = os.path.join(repo_folder, 'src/textual_paint/fonts/NanoTiny')
image_input_path = os.path.join(font_folder, 'NanoTiny_v14.png')
image_output_path = os.path.join(font_folder, 'NanoTiny_v14_no_border.png')
half_size_flf_output_path = os.path.join(font_folder, 'NanoTiny_v14_2x2.flf')
full_size_flf_output_path = os.path.join(font_folder, 'NanoTiny_v14_4x4.flf')
extracted_image, extracted_text_half, extracted_text_full = extract_textures(image_input_path)
extracted_image.save(image_output_path)
print(f'Wrote extracted textures to {image_output_path}')
with open(full_size_flf_output_path, 'w') as f:
f.write(extracted_text_full)
print(f'Wrote FIGlet font {full_size_flf_output_path}')
with open(half_size_flf_output_path, 'w') as f:
f.write(extracted_text_half)
print(f'Wrote FIGlet font {half_size_flf_output_path}')