2024-02-29 22:12:14 +01:00
|
|
|
import os
|
2024-03-04 21:01:14 +01:00
|
|
|
import tkinter.filedialog
|
2024-02-29 22:12:14 +01:00
|
|
|
import unicodedata
|
|
|
|
|
2024-03-02 12:28:14 +01:00
|
|
|
from canvas import *
|
|
|
|
from project import *
|
|
|
|
|
2024-02-29 22:12:14 +01:00
|
|
|
##### CONFIG #####
|
|
|
|
|
2024-03-01 20:37:08 +01:00
|
|
|
WINDOW_SIZE = (
|
2024-03-01 21:40:25 +01:00
|
|
|
(1280,720),
|
|
|
|
(1920,1019)
|
2024-02-29 22:12:14 +01:00
|
|
|
)[0]
|
|
|
|
|
|
|
|
cross_color = (255, 60, 25)
|
|
|
|
pixel_color = (255,) * 3
|
|
|
|
|
|
|
|
##################
|
|
|
|
|
2024-03-02 12:28:14 +01:00
|
|
|
project=Project()
|
2024-03-01 21:40:25 +01:00
|
|
|
|
2024-03-03 20:17:42 +01:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2024-03-05 11:33:38 +01:00
|
|
|
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()
|
|
|
|
|
|
|
|
|
2024-03-01 20:37:08 +01:00
|
|
|
def menu_file_open_project_click():
|
2024-03-02 12:28:14 +01:00
|
|
|
global project
|
2024-03-01 21:40:25 +01:00
|
|
|
global canvas_editor
|
|
|
|
|
|
|
|
file_path = tkinter.filedialog.askopenfilename(
|
|
|
|
title="Open font project",
|
|
|
|
filetypes=[("Font project (json)", "*.fontproj")]
|
|
|
|
)
|
2024-03-03 17:32:27 +01:00
|
|
|
if file_path == "" or file_path==():
|
2024-03-01 21:40:25 +01:00
|
|
|
return
|
|
|
|
|
|
|
|
# save last path to cache
|
|
|
|
with open("last_path_cache.txt", "w") as f:
|
|
|
|
f.write(file_path)
|
|
|
|
|
2024-03-05 11:33:38 +01:00
|
|
|
open_project(file_path)
|
|
|
|
|
|
|
|
|
|
|
|
def menu_file_open_last_project_click():
|
2024-03-02 12:28:14 +01:00
|
|
|
try:
|
2024-03-05 11:33:38 +01:00
|
|
|
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")
|
2024-03-01 21:40:25 +01:00
|
|
|
|
2024-02-29 22:12:14 +01:00
|
|
|
|
2024-03-03 20:21:35 +01:00
|
|
|
def save_project(ask):
|
2024-03-03 20:17:42 +01:00
|
|
|
global canvas_editor
|
2024-03-02 22:35:58 +01:00
|
|
|
global project
|
|
|
|
|
2024-03-03 20:17:42 +01:00
|
|
|
if not project.loaded:
|
|
|
|
return
|
|
|
|
|
2024-03-02 22:35:58 +01:00
|
|
|
path=None
|
2024-03-03 20:21:35 +01:00
|
|
|
if project.path and not ask:
|
2024-03-02 22:35:58 +01:00
|
|
|
path=project.path
|
|
|
|
else:
|
|
|
|
# show file save dialog
|
2024-03-03 20:17:42 +01:00
|
|
|
path=tkinter.filedialog.asksaveasfilename(
|
2024-03-02 22:35:58 +01:00
|
|
|
title="Save font project",
|
|
|
|
filetypes=[("Font project (json)", "*.fontproj")],
|
|
|
|
defaultextension=".fontproj"
|
|
|
|
)
|
2024-03-03 20:17:42 +01:00
|
|
|
if path=="" or path==():
|
2024-03-02 22:35:58 +01:00
|
|
|
return
|
|
|
|
|
2024-03-05 11:33:38 +01:00
|
|
|
# save last path to cache
|
|
|
|
with open("last_path_cache.txt", "w") as f:
|
|
|
|
f.write(path)
|
|
|
|
|
2024-03-03 20:17:42 +01:00
|
|
|
canvas_editor.save_char()
|
2024-03-02 22:35:58 +01:00
|
|
|
try:
|
|
|
|
project.save(path)
|
|
|
|
except IOError as e:
|
|
|
|
tkinter.messagebox.showerror("Saving project",f"Failed to save project '{path}': {e}")
|
|
|
|
|
2024-02-29 22:12:14 +01:00
|
|
|
|
2024-03-04 21:01:14 +01:00
|
|
|
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}")
|
|
|
|
|
|
|
|
|
2024-03-01 21:52:39 +01:00
|
|
|
def button_prev_glyph_click():
|
2024-03-03 17:55:29 +01:00
|
|
|
global canvas_editor
|
2024-03-03 19:17:07 +01:00
|
|
|
global project
|
|
|
|
if not project.loaded:
|
|
|
|
return
|
2024-03-02 12:28:14 +01:00
|
|
|
canvas_editor.prev_glyph()
|
2024-03-03 17:55:29 +01:00
|
|
|
update_glyph_preview()
|
2024-03-01 21:52:39 +01:00
|
|
|
|
|
|
|
def button_next_glyph_click():
|
2024-03-03 17:55:29 +01:00
|
|
|
global canvas_editor
|
2024-03-03 19:17:07 +01:00
|
|
|
global project
|
|
|
|
if not project.loaded:
|
|
|
|
return
|
2024-03-02 12:28:14 +01:00
|
|
|
canvas_editor.next_glyph()
|
2024-03-03 17:55:29 +01:00
|
|
|
update_glyph_preview()
|
2024-03-01 21:52:39 +01:00
|
|
|
|
|
|
|
|
2024-03-03 19:33:19 +01:00
|
|
|
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()
|
|
|
|
|
|
|
|
|
2024-03-01 20:37:08 +01:00
|
|
|
def number_only_validate(val):
|
2024-03-03 20:17:42 +01:00
|
|
|
return val.isdigit()
|
|
|
|
|
|
|
|
|
|
|
|
def hex_only_validate(val):
|
2024-03-03 19:33:19 +01:00
|
|
|
for x in val:
|
|
|
|
if not x.isdigit() and (ord(x)<ord("a") or ord(x)>ord("f")) and (ord(x)<ord("A") or ord(x)>ord("F")):
|
|
|
|
return False
|
|
|
|
return True
|
2024-02-29 22:12:14 +01:00
|
|
|
|
2024-03-01 21:52:39 +01:00
|
|
|
|
2024-03-03 17:55:29 +01:00
|
|
|
def update_glyph_preview():
|
|
|
|
global canvas_editor
|
|
|
|
global canvas_preview
|
2024-03-03 19:16:11 +01:00
|
|
|
global label_glyph_name
|
2024-03-03 17:55:29 +01:00
|
|
|
canvas_preview.delete("all")
|
|
|
|
canvas_preview.create_text((50,100),text=chr(canvas_editor.current_char),fill="white",font="tkDefaultFont 70")
|
2024-03-03 19:16:11 +01:00
|
|
|
name=unicodedata.name(chr(canvas_editor.current_char),"unknown")
|
|
|
|
label_glyph_name.config(text=f"{name} U+{canvas_editor.current_char:04x}")
|
2024-03-03 17:55:29 +01:00
|
|
|
|
|
|
|
|
2024-03-01 20:37:08 +01:00
|
|
|
window=tkinter.Tk()
|
|
|
|
window.title("fonteditor")
|
2024-03-01 21:40:25 +01:00
|
|
|
window.geometry(f"{WINDOW_SIZE[0]}x{WINDOW_SIZE[1]}")
|
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)
|
2024-03-03 20:17:42 +01:00
|
|
|
menu_file.add_command(label="New project",command=menu_file_new_project_click)
|
2024-03-01 20:37:08 +01:00
|
|
|
menu_file.add_command(label="Open project",command=menu_file_open_project_click)
|
2024-03-05 11:33:38 +01:00
|
|
|
menu_file.add_command(label="Open last project",command=menu_file_open_last_project_click)
|
2024-03-03 20:21:35 +01:00
|
|
|
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))
|
2024-03-04 21:01:14 +01:00
|
|
|
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))
|
2024-03-01 20:37:08 +01:00
|
|
|
menubar.add_cascade(label="File",menu=menu_file)
|
2024-03-04 21:01:14 +01:00
|
|
|
menubar.add_cascade(label="Export",menu=menu_export)
|
2024-02-29 22:12:14 +01:00
|
|
|
|
2024-03-02 12:28:14 +01:00
|
|
|
canvas_editor=EditorCanvas(project,window,bg="black")
|
2024-03-03 17:12:32 +01:00
|
|
|
canvas_editor.pack(side="left",fill="both",expand=True)
|
2024-02-29 22:12:14 +01:00
|
|
|
|
2024-03-01 20:37:08 +01:00
|
|
|
frame_controls=tkinter.Frame(window)
|
2024-03-03 17:12:32 +01:00
|
|
|
frame_controls.pack(side="right")
|
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-03 19:16:11 +01:00
|
|
|
label_glyph_name=tkinter.Label(frame_controls)
|
2024-03-01 20:37:08 +01:00
|
|
|
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 21:52:39 +01:00
|
|
|
button_prev_glyph=tkinter.Button(frame_nav,width=10,text="Previous",command=button_prev_glyph_click)
|
2024-03-01 20:37:08 +01:00
|
|
|
button_prev_glyph.pack(side="left")
|
2024-02-29 22:12:14 +01:00
|
|
|
|
2024-03-01 21:52:39 +01:00
|
|
|
button_next_glyph=tkinter.Button(frame_nav,width=10,text="Next",command=button_next_glyph_click)
|
2024-03-01 20:37:08 +01:00
|
|
|
button_next_glyph.pack(side="left")
|
|
|
|
|
|
|
|
frame_glyph_id=tkinter.Frame(frame_controls)
|
|
|
|
frame_glyph_id.pack(side="top",pady=10)
|
|
|
|
|
2024-03-03 20:17:42 +01:00
|
|
|
entry_glyph_id=tkinter.Entry(frame_glyph_id,validate="all",validatecommand=((window.register(hex_only_validate)),"%P"))
|
2024-03-01 20:37:08 +01:00
|
|
|
entry_glyph_id.pack(side="left")
|
|
|
|
|
2024-03-03 19:33:19 +01:00
|
|
|
button_glyph_search=tkinter.Button(frame_glyph_id,width=10,text="Search",command=button_glyph_search_click)
|
2024-03-01 20:37:08 +01:00
|
|
|
button_glyph_search.pack(side="left")
|
|
|
|
|
|
|
|
window.config(menu=menubar)
|
|
|
|
window.mainloop()
|