import os import numpy as np import cli_ui import unicodedata import json import base64 import tkinter from exporter import export ##### CONFIG ##### WINDOW_SIZE = ( (1280,720), (1920,1019) )[0] GRID_COLOR = "#808080" GRID_GUIDE_COLOR = (128, 128, 255) GRID_SIZE = 32 cross_color = (255, 60, 25) pixel_color = (255,) * 3 ################## project_data={} project_chars = {} project_char_res = None current_project="" current_char=33 canvas_width=0 canvas_height=0 def check_char_exist(char_num): global project_chars return chr(char_num) in project_chars def draw_editor_canvas(): global canvas_editor global project_char_res global canvas_width global canvas_height canvas_editor.delete("all") # draw grid for x in range(project_char_res[0] + 1): x = x * GRID_SIZE canvas_editor.create_line((x,0),(x,canvas_height),width=1,fill=GRID_COLOR) for y in range(project_char_res[1] + 1): y = y * GRID_SIZE canvas_editor.create_line((0,y),(canvas_width,y),width=1,fill=GRID_COLOR) if check_char_exist(current_char): pass else: canvas_editor.create_line((0,0),(canvas_width,canvas_height),width=3,fill="red") canvas_editor.create_line((canvas_width,0),(0,canvas_height),width=3,fill="red") def menu_file_open_project_click(): global project_data global project_chars global project_char_res global last_project global canvas_editor global canvas_width global canvas_height file_path = tkinter.filedialog.askopenfilename( title="Open font project", filetypes=[("Font project (json)", "*.fontproj")] ) if file_path == "": return # save last path to cache with open("last_path_cache.txt", "w") as f: f.write(file_path) current_project=file_path with open(file_path, "r") as f: project_data = json.load(f) project_chars = project_data["chars"] project_char_res = (project_data["char_width"], project_data["char_height"]) canvas_width=project_char_res[0]*GRID_SIZE canvas_height=project_char_res[1]*GRID_SIZE canvas_editor.config(width=canvas_width,height=canvas_height) draw_editor_canvas() def menu_file_save_project_click(): pass def menu_file_save_project_as_click(): pass def number_only_validate(val): return str.isdigit(val) or val=="" window=tkinter.Tk() window.title("fonteditor") window.geometry(f"{WINDOW_SIZE[0]}x{WINDOW_SIZE[1]}") 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) canvas_editor=tkinter.Canvas(window,bg="black") canvas_editor.pack(side="left") frame_controls=tkinter.Frame(window) frame_controls.pack() canvas_preview=tkinter.Canvas(frame_controls,width=100,height=200,bg="black") canvas_preview.pack(side="top") label_glyph_name=tkinter.Label(frame_controls,text="Placeholder") label_glyph_name.pack(side="top") frame_nav=tkinter.Frame(frame_controls) frame_nav.pack(side="top",pady=10) button_prev_glyph=tkinter.Button(frame_nav,width=10,text="Previous") button_prev_glyph.pack(side="left") 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() pixel_size = (window_size[1]-1) / char_res[1] canva_width = pixel_size * char_res[0]""" 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 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 ) ) # 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) #d_time = clock.tick(fps_limit) / 1000