import os import platform import tkinter.filedialog import unicodedata from canvas import * from project import * ##### CONFIG ##### WINDOW_SIZE = ( (1280,720), (1920,1019) )[0] cross_color = (255, 60, 25) pixel_color = (255,) * 3 bg_col = "#1e1e1e" ################## project=Project() def button_create_project_click(subwindow,w,h): try: width=int(w) except ValueError as e: tkinter.messagebox.showerror("Creating new project", f"Invalid value {w}: {e}") return try: height=int(h) except ValueError as e: tkinter.messagebox.showerror("Creating new project", f"Invalid value {h}: {e}") return project.chars={} project.char_res=(width,height) project.loaded=True canvas_editor.after_project_load() canvas_editor.draw() update_glyph_preview() subwindow.destroy() def menu_file_new_project_click(): global window if project.modified and not tkinter.messagebox.askyesno("Creating new project","You have unsaved changes, are you sure?"): return subwindow=tkinter.Toplevel(window) subwindow.title("New project") label_width=tkinter.Label(subwindow,text="Character width") label_width.pack(side="top") entry_width=tkinter.Entry(subwindow,validate="all",validatecommand=((subwindow.register(number_only_validate)),"%P")) entry_width.pack(side="top",fill="x",padx=5) label_height=tkinter.Label(subwindow,text="Character height") label_height.pack(side="top") entry_height=tkinter.Entry(subwindow,validate="all",validatecommand=((subwindow.register(number_only_validate)),"%P")) entry_height.pack(side="top",fill="x",padx=5) button_create=tkinter.Button(subwindow,text="Create",command=lambda: button_create_project_click(subwindow,entry_width.get(),entry_height.get())) button_create.pack(side="bottom",fill="x",padx=5) def open_project(file_path): try: project.load(file_path) except AttributeError as e: tkinter.messagebox.showerror("Opening project",f"Project '{file_path}' is invalid: {e}") except IOError as e: tkinter.messagebox.showerror("Opening project",f"Failed to open project '{file_path}': {e}") finally: canvas_editor.after_project_load() canvas_editor.draw() update_glyph_preview() def menu_file_open_project_click(): global project global canvas_editor file_path = tkinter.filedialog.askopenfilename( title="Open font project", filetypes=[("Font project (json)", "*.fontproj")] ) if file_path == "" or file_path==(): return # save last path to cache with open("last_path_cache.txt", "w") as f: f.write(file_path) open_project(file_path) def menu_file_open_last_project_click(): try: cache=open("last_path_cache.txt","r") file_path=cache.read() cache.close() open_project(file_path) except IOError: tkinter.messagebox.showerror("Opening last project","Failed to load last project cache") def save_project(ask): global canvas_editor global project if not project.loaded: return path=None if project.path and not ask: path=project.path else: # show file save dialog path=tkinter.filedialog.asksaveasfilename( title="Save font project", filetypes=[("Font project (json)", "*.fontproj")], defaultextension=".fontproj" ) if path=="" or path==(): return # save last path to cache with open("last_path_cache.txt", "w") as f: f.write(path) canvas_editor.save_char() try: project.save(path) except IOError as e: tkinter.messagebox.showerror("Saving project",f"Failed to save project '{path}': {e}") def export_project(ask): if not project.loaded: return path=None if project.export_path and not ask: path=project.export_path else: path = tkinter.filedialog.askdirectory( title="Choose folder where exported pages will be saved" ) if path=="" or path==(): return canvas_editor.save_char() try: project.export(path) except IOError as e: tkinter.messagebox.showerror("Exporting project",f"Failed to export project: {e}") def button_prev_glyph_click(): global canvas_editor global project if not project.loaded: return canvas_editor.prev_glyph() update_glyph_preview() def button_next_glyph_click(): global canvas_editor global project if not project.loaded: return canvas_editor.next_glyph() update_glyph_preview() def button_glyph_search_click(): global canvas_editor global entry_glyph_id global project if not project.loaded: return code=entry_glyph_id.get() canvas_editor.save_char() try: canvas_editor.current_char=int(code,base=16) except ValueError as e: tkinter.messagebox.showerror("Searching glyph", f"Invalid hex value {code}: {e}") finally: canvas_editor.keep_current_char_in_bounds() canvas_editor.load_char() update_glyph_preview() def number_only_validate(val): return val.isdigit() or val=="" def hex_only_validate(val): for x in val: if not x.isdigit() and (ord(x)ord("f")) and (ord(x)ord("F")): return False return True def update_glyph_preview(): global canvas_editor global canvas_preview global label_glyph_name canvas_preview.delete("all") canvas_preview.create_text((50,100),text=chr(canvas_editor.current_char),fill="white",font="tkDefaultFont 70") name=unicodedata.name(chr(canvas_editor.current_char),"unknown") label_glyph_name.config(text=f"{canvas_editor.current_char}\n{name}\nU+{canvas_editor.current_char:04x}") def canvas_editor_handle_scroll(delta): global canvas_editor global project if not project.loaded: return if delta>0: canvas_editor.prev_glyph() else: canvas_editor.next_glyph() update_glyph_preview() 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="New project",command=menu_file_new_project_click) menu_file.add_command(label="Open project",command=menu_file_open_project_click) menu_file.add_command(label="Open last project",command=menu_file_open_last_project_click) menu_file.add_command(label="Save project",command=lambda: save_project(False)) menu_file.add_command(label="Save project as",command=lambda: save_project(True)) menu_export=tkinter.Menu(menubar,tearoff=False) menu_export.add_command(label="Export",command=lambda: export_project(False)) menu_export.add_command(label="Export as",command=lambda: export_project(True)) menu_view=tkinter.Menu(menubar,tearoff=False) menu_view.add_command(label="Zoom in",command=lambda: canvas_editor.zoom_by(10)) menu_view.add_command(label="Zoom out",command=lambda: canvas_editor.zoom_by(-10)) menubar.add_cascade(label="File",menu=menu_file) menubar.add_cascade(label="Export",menu=menu_export) menubar.add_cascade(label="View",menu=menu_view) canvas_editor=EditorCanvas(project,window,bg="black") canvas_editor.pack(side="left",fill="both",expand=True) if platform.system()=="Windows" or platform.system()=="Darwin": canvas_editor.bind("",lambda event: canvas_editor_handle_scroll(event.delta)) else: # This will probably work only on X11 canvas_editor.bind("",lambda _: canvas_editor_handle_scroll(1)) canvas_editor.bind("",lambda _: canvas_editor_handle_scroll(-1)) #### Guide pos #### frame_label_guide_pos = tkinter.Frame(window, bg=bg_col) frame_label_guide_pos.pack(side="right") label_1 = tkinter.Label(frame_label_guide_pos, text="Guide 1 Y", bg=bg_col, fg="#ffffff").pack(side="top") entry_1 = tkinter.Entry(frame_label_guide_pos, validate="all", validatecommand=((window.register(number_only_validate)),"%P")) entry_1.pack(side="top", pady=2) label_2 = tkinter.Label(frame_label_guide_pos, text="Guide 2 Y", bg=bg_col, fg="#ffffff").pack(side="top") entry_2 = tkinter.Entry(frame_label_guide_pos, validate="all", validatecommand=((window.register(number_only_validate)),"%P")) entry_2.pack(side="top", pady=2) label_3 = tkinter.Label(frame_label_guide_pos, text="Guide 3 Y", bg=bg_col, fg="#ffffff").pack(side="top") entry_3 = tkinter.Entry(frame_label_guide_pos, validate="all", validatecommand=((window.register(number_only_validate)),"%P")) entry_3.pack(side="top", pady=2) ## load last values cwd = os.path.dirname(os.path.realpath(__file__)) guide_vals_path = os.path.join(cwd, "guide_values.txt") def get_guide_entries(): return ( int(entry_1.get()), int(entry_2.get()), int(entry_3.get()) ) def update_guide_pos(pos, dont_save=False): canvas_editor.set_guide_pos(pos) if dont_save: return with open(guide_vals_path, "w") as f: f.write(f"{entry_1.get()}\n{entry_2.get()}\n{entry_3.get()}") if os.path.exists(guide_vals_path): with open(guide_vals_path, "r") as f: lines = f.readlines() entry_1.insert(0, lines[0].strip()) entry_2.insert(0, lines[1].strip()) entry_3.insert(0, lines[2].strip()) update_guide_pos(get_guide_entries(), dont_save=True) else: entry_1.insert(0, str(3)) entry_2.insert(0, str(5)) entry_3.insert(0, str(10)) update_guide_pos(get_guide_entries()) set_button = tkinter.Button( frame_label_guide_pos, text="Set", command=lambda: update_guide_pos(get_guide_entries()) ).pack(side="top", pady=[5, 30]) #### Copy #### frame = tkinter.Frame(frame_label_guide_pos, bg=bg_col) frame.pack(side="right") copy_button = tkinter.Button(frame, text="Copy", command=canvas_editor.copy_char) copy_button.pack(side="top", pady=5) #### Preview #### frame_controls=tkinter.Frame(frame, bg=bg_col) frame_controls.pack(side="right") canvas_preview=tkinter.Canvas(frame_controls,width=100,height=200,bg="black") canvas_preview.pack(side="top") label_glyph_name=tkinter.Label(frame_controls, bg=bg_col, fg="#ffffff") label_glyph_name.pack(side="top") #### Navigation #### frame_nav=tkinter.Frame(frame_controls) frame_nav.pack(side="top",pady=10) button_prev_glyph=tkinter.Button(frame_nav,width=10,text="Previous",command=button_prev_glyph_click) button_prev_glyph.pack(side="left") button_next_glyph=tkinter.Button(frame_nav,width=10,text="Next",command=button_next_glyph_click) button_next_glyph.pack(side="left") #### Glyph search #### 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(hex_only_validate)),"%P")) entry_glyph_id.pack(side="left") button_glyph_search=tkinter.Button(frame_glyph_id,width=10,text="Search",command=button_glyph_search_click) button_glyph_search.pack(side="left") window.config(menu=menubar, bg=bg_col) window.mainloop()