fonteditor/main.py

323 lines
8.4 KiB
Python
Raw Normal View History

2024-02-29 22:12:14 +01:00
import os
import numpy as np
import cli_ui
import unicodedata
import json
import base64
2024-03-01 20:37:08 +01:00
import tkinter
2024-02-29 22:12:14 +01:00
from exporter import export
##### CONFIG #####
2024-03-01 20:37:08 +01:00
# height of guide lines from the top of font pixel grid
2024-02-29 22:12:14 +01:00
ascender_line = 10
descender_line = 20
2024-03-01 20:37:08 +01:00
WINDOW_SIZE = (
"1280x720",
"1920x1019"
2024-02-29 22:12:14 +01:00
)[0]
grid_color = (128,) * 3
grid_guide_color = (128, 128, 255)
cross_color = (255, 60, 25)
pixel_color = (255,) * 3
##################
2024-03-01 20:37:08 +01:00
def menu_file_open_project_click():
pass
2024-02-29 22:12:14 +01:00
2024-03-01 20:37:08 +01:00
def menu_file_save_project_click():
pass
2024-02-29 22:12:14 +01:00
2024-03-01 20:37:08 +01:00
def menu_file_save_project_as_click():
pass
2024-02-29 22:12:14 +01:00
2024-03-01 20:37:08 +01:00
def number_only_validate(val):
return str.isdigit(val) or val==""
2024-02-29 22:12:14 +01:00
2024-03-01 20:37:08 +01:00
window=tkinter.Tk()
window.title("fonteditor")
window.geometry(WINDOW_SIZE)
2024-02-29 22:12:14 +01:00
2024-03-01 20:37:08 +01:00
menubar=tkinter.Menu(window)
menu_file=tkinter.Menu(menubar,tearoff=False)
menu_file.add_command(label="Open project",command=menu_file_open_project_click)
menu_file.add_command(label="Save project",command=menu_file_save_project_click)
menu_file.add_command(label="Save project as",command=menu_file_save_project_as_click)
menubar.add_cascade(label="File",menu=menu_file)
2024-02-29 22:12:14 +01:00
2024-03-01 20:37:08 +01:00
canvas_editor=tkinter.Canvas(window,bg="black")
canvas_editor.pack(side="left",fill="y")
2024-02-29 22:12:14 +01:00
2024-03-01 20:37:08 +01:00
frame_controls=tkinter.Frame(window)
frame_controls.pack()
2024-02-29 22:12:14 +01:00
2024-03-01 20:37:08 +01:00
canvas_preview=tkinter.Canvas(frame_controls,width=100,height=200,bg="black")
canvas_preview.pack(side="top")
2024-02-29 22:12:14 +01:00
2024-03-01 20:37:08 +01:00
label_glyph_name=tkinter.Label(frame_controls,text="Placeholder")
label_glyph_name.pack(side="top")
2024-02-29 22:12:14 +01:00
2024-03-01 20:37:08 +01:00
frame_nav=tkinter.Frame(frame_controls)
frame_nav.pack(side="top",pady=10)
2024-02-29 22:12:14 +01:00
2024-03-01 20:37:08 +01:00
button_prev_glyph=tkinter.Button(frame_nav,width=10,text="Previous")
button_prev_glyph.pack(side="left")
2024-02-29 22:12:14 +01:00
2024-03-01 20:37:08 +01:00
button_next_glyph=tkinter.Button(frame_nav,width=10,text="Next")
button_next_glyph.pack(side="left")
frame_glyph_id=tkinter.Frame(frame_controls)
frame_glyph_id.pack(side="top",pady=10)
entry_glyph_id=tkinter.Entry(frame_glyph_id,validate="all",validatecommand=((window.register(number_only_validate)),"%P"))
entry_glyph_id.pack(side="left")
button_glyph_search=tkinter.Button(frame_glyph_id,width=10,text="Search")
button_glyph_search.pack(side="left")
window.config(menu=menubar)
window.mainloop()
"""proj_data, proj_path = cli_ui.cli_main()
chars = proj_data["chars"]
char_res = (proj_data["char_width"], proj_data["char_height"])
pixel_size = (window_size[1]-1) / char_res[1]
canva_width = pixel_size * char_res[0]"""
2024-02-29 22:12:14 +01:00
def cursor_pos_to_pixel(pos):
pixel_pos = (int(pos[0] // pixel_size), int(pos[1] // pixel_size))
if pixel_pos[0] < 0 or pixel_pos[1] < 0 or pixel_pos[0] >= char_res[0] or pixel_pos[1] >= char_res[1]:
return None
return pixel_pos
#d_time = 1 / fps_limit
is_grid = True
char_num = 33
def check_char_exist(char_num):
return chr(char_num) in chars
does_char_exist = check_char_exist(char_num)
force_deleted = False
# main loop
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
elif event.type == pygame.MOUSEWHEEL:
char_num += event.y * -1
if char_num < 0:
char_num = 0
elif char_num > 99999:
char_num = 99999
does_char_exist = check_char_exist(char_num)
elif event.type == pygame.KEYDOWN:
# delete char
if event.key == pygame.K_DELETE:
if does_char_exist:
del chars[chr(char_num)]
does_char_exist = False
force_deleted = True
# toggle grid
if event.key == pygame.K_g:
is_grid = not is_grid
# export font
elif event.key == pygame.K_e:
export(char_res, chars)
# print key tips
elif event.key == pygame.K_p:
cli_ui.key_tips()
# initialize current stroke
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
pixel_pos = cursor_pos_to_pixel(pygame.mouse.get_pos())
# if char is drawable at current position
if pixel_pos is not None:
# create char dictionary entry if it does not exist
if not does_char_exist:
chars[chr(char_num)] = np.zeros(char_res, dtype=bool)
does_char_exist = True
paint_color = not chars[chr(char_num)][pixel_pos]
# save at the end of the stroke
elif (event.type == pygame.MOUSEBUTTONUP and event.button == 1) or force_deleted:
force_deleted = False
## packbit all chars
# calculate the number of bits needed to pad char array to a multiple of 8
remainder = char_res[0] * char_res[1] % 8
padding = (8 - remainder) % 8
packed_chars = {}
for key, value in chars.items():
# reshape into 1D array
new_data = value.reshape(-1)
# pad
new_data = np.pad(new_data, (0, padding), mode="constant")
# reshape the padded array into a 2D array with 8 columns
new_data = new_data.reshape(-1, 8)
# packbits
new_data = np.packbits(new_data, axis=1).tobytes()
# convert to base64
new_data = base64.b64encode(new_data).decode("utf-8")
packed_chars[key] = new_data
proj_data["chars"] = packed_chars
with open(proj_path, "w", encoding="utf-8") as f:
json.dump(proj_data, f, indent=4, ensure_ascii=False)
print("Autosaved project.")
# perform drawing
if pygame.mouse.get_pressed()[0] and does_char_exist:
pixel_pos = cursor_pos_to_pixel(pygame.mouse.get_pos())
if pixel_pos is not None:
chars[chr(char_num)][pixel_pos] = paint_color
# if all pixels are off, delete char
if not np.any(chars[chr(char_num)]):
del chars[chr(char_num)]
does_char_exist = False
window.fill((0, 0, 0))
if does_char_exist:
current_char_array = chars[chr(char_num)]
# draw pixels from current_char_array
for y in range(char_res[1]):
for x in range(char_res[0]):
if current_char_array[x, y]:
pygame.draw.rect(
window,
pixel_color,
(
x * pixel_size,
y * pixel_size,
pixel_size,
pixel_size
)
)
if is_grid:
# draw grid
for x in range(char_res[0] + 1):
x = x * pixel_size
pygame.draw.line(
window,
grid_color,
(x, 0),
(x, window_size[1]),
)
for y in range(char_res[1] + 1):
real_y = y * pixel_size
pygame.draw.line(
window,
grid_guide_color if (y == ascender_line or y == descender_line) else grid_color,
(0, real_y),
(canva_width, real_y),
)
# draw char num
font.render_to(
window,
(canva_width + 30, 30),
f"dec: {char_num}",
(255, 255, 255),
size=64,
)
# draw char
font.render_to(
window,
(canva_width + 30, 100),
chr(char_num),
(255, 255, 255),
size=150,
)
# draw alternate font char
alt_font.render_to(
window,
(canva_width + 200, 100),
chr(char_num),
(255, 255, 255),
size=256,
)
# draw char name
font.render_to(
window,
(canva_width + 30, 280),
unicodedata.name(chr(char_num), "unknown"),
(255, 255, 255),
size=16,
)
# draw a red cross if curren char does not exist
if not does_char_exist:
pygame.draw.line(
window,
cross_color,
(0, 0),
(canva_width, window_size[1]),
7
)
pygame.draw.line(
window,
cross_color,
(0, window_size[1]),
(canva_width, 0),
7
)
font.render_to(window, (10, 10), f"FPS: {clock.get_fps():.2f}", (255, 255, 255), size=16)
pygame.display.update()
clock.tick(fps_limit)
2024-03-01 20:37:08 +01:00
#d_time = clock.tick(fps_limit) / 1000