import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageDraw, ImageTk
import math
import os
# -------------------------
# Braille mapping utilities
# -------------------------
# Dot-number definitions for letters a..z (Grade-1 braille)
LETTER_DOTS = {
'a': [1],
'b': [1,2],
'c': [1,4],
'd': [1,4,5],
'e': [1,5],
'f': [1,2,4],
'g': [1,2,4,5],
'h': [1,2,5],
'i': [2,4],
'j': [2,4,5],
'k': [1,3],
'l': [1,2,3],
'm': [1,3,4],
'n': [1,3,4,5],
'o': [1,3,5],
'p': [1,2,3,4],
'q': [1,2,3,4,5],
'r': [1,2,3,5],
's': [2,3,4],
't': [2,3,4,5],
'u': [1,3,6],
'v': [1,2,3,6],
'w': [2,4,5,6],
'x': [1,3,4,6],
'y': [1,3,4,5,6],
'z': [1,3,5,6],
}
# Common punctuation (Grade-1)
PUNCT_DOTS = {
',': [2],
';': [2,3],
':': [2,4],
'.': [2,5,6],
'?': [2,6],
'!': [2,3,5],
'(': [2,3,6,5], # open parenthesis commonly encoded as ⠶ (but implementations vary)
')': [3,5,6,2], # mirrored / alternative — we'll use same as '(' for simplicity
"'": [3],
'-': [3,6],
'/': [3,4],
'"': [5,6,2,3], # approximate
'@': [4,1], # uncommon; approximate
'#': [3,4,5,6], # number sign (we will use official number sign below)
}
# Braille special signs
NUMBER_SIGN = [3,4,5,6] # ⠼
CAPITAL_SIGN = [6] # prefix for capital (single capital) — optional use
SPACE = [] # no dots for space -> unicode U+2800
# Build dot -> Unicode mapping utility
def dots_to_braille_unicode(dots):
"""
dots: list of integers 1..8 (we use 1..6)
returns: single unicode braille character
"""
code = 0x2800
for d in dots:
if 1 <= d <= 8:
code += 1 << (d - 1)
return chr(code)
# Precompute maps
LETTER_TO_BRAILLE = {ch: dots_to_braille_unicode(dots) for ch, dots in LETTER_DOTS.items()}
PUNCT_TO_BRAILLE = {p: dots_to_braille_unicode(dots) for p, dots in PUNCT_DOTS.items()}
NUMBER_SIGN_CHAR = dots_to_braille_unicode(NUMBER_SIGN)
CAPITAL_SIGN_CHAR = dots_to_braille_unicode(CAPITAL_SIGN)
SPACE_CHAR = chr(0x2800)
# Digits mapping in Grade-1: number sign + letters a-j represents 1-0
DIGIT_TO_LETTER = {
'1': 'a', '2': 'b', '3': 'c', '4': 'd', '5': 'e',
'6': 'f', '7': 'g', '8': 'h', '9': 'i', '0': 'j'
}
# -------------------------
# Translation function
# -------------------------
def translate_to_braille(text, use_capital_prefix=True, use_number_prefix=True):
"""
Translate plain text into Grade-1 Braille Unicode string.
Options:
- use_capital_prefix: if True, prefix capitals with the capital sign (⠠)
- use_number_prefix: if True, prefix digit sequences with number sign (⠼)
Returns braille_unicode_string
"""
out = []
i = 0
n = len(text)
while i < n:
ch = text[i]
if ch.isspace():
out.append(SPACE_CHAR)
i += 1
continue
# Digit sequence handling
if ch.isdigit():
if use_number_prefix:
out.append(NUMBER_SIGN_CHAR)
# consume contiguous digits
while i < n and text[i].isdigit():
d = text[i]
letter_equiv = DIGIT_TO_LETTER.get(d, None)
if letter_equiv:
out.append(LETTER_TO_BRAILLE[letter_equiv])
else:
# fallback: space for unknown
out.append(SPACE_CHAR)
i += 1
continue
# Letter
if ch.isalpha():
if ch.isupper():
if use_capital_prefix:
out.append(CAPITAL_SIGN_CHAR)
ch_low = ch.lower()
else:
ch_low = ch
code = LETTER_TO_BRAILLE.get(ch_low)
if code:
out.append(code)
else:
out.append(SPACE_CHAR)
i += 1
continue
# Punctuation
if ch in PUNCT_TO_BRAILLE:
out.append(PUNCT_TO_BRAILLE[ch])
i += 1
continue
# Fallback: try common mapping for punctuation by replacement
if ch == '"':
out.append(PUNCT_TO_BRAILLE.get('"', SPACE_CHAR))
i += 1
continue
# Unknown character: attempt to include as space placeholder
out.append(SPACE_CHAR)
i += 1
return "".join(out)
# -------------------------
# Braille image rendering
# -------------------------
def render_braille_image(braille_text, dot_radius=8, dot_gap=10, cell_gap=16, bg_color=(255,255,255)):
"""
Render braille_text (unicode braille characters) into a PIL Image.
Each braille cell is 2 (columns) x 3 (rows) of dots.
We read the Unicode braille codepoints and draw filled circles for active dots.
Returns PIL.Image (RGB).
"""
# Compute rows & columns: we'll wrap to a max columns per line for reasonable width
max_cols = 40 # characters per row, adjust if needed
# Split into lines by breaking long strings
chars = list(braille_text)
lines = [chars[i:i+max_cols] for i in range(0, len(chars), max_cols)]
# cell size
cell_w = dot_radius*2 + dot_gap
cell_h = dot_radius*3 + dot_gap*2 # 3 rows
img_w = len(lines[0]) * (cell_w + cell_gap) + 2*cell_gap if lines else 200
img_h = len(lines) * (cell_h + cell_gap) + 2*cell_gap if lines else 100
img = Image.new("RGB", (img_w, img_h), color=bg_color)
draw = ImageDraw.Draw(img)
for row_idx, line in enumerate(lines):
for col_idx, ch in enumerate(line):
x0 = cell_gap + col_idx * (cell_w + cell_gap)
y0 = cell_gap + row_idx * (cell_h + cell_gap)
# Determine dot pattern from unicode char
codepoint = ord(ch)
base = 0x2800
mask = codepoint - base
# dot positions for 1..6 are arranged:
# (col0,row0)=dot1 (col1,row0)=dot4
# (col0,row1)=dot2 (col1,row1)=dot5
# (col0,row2)=dot3 (col1,row2)=dot6
dot_positions = [
(0,0,1), # dot1
(0,1,2), # dot2
(0,2,3), # dot3
(1,0,4), # dot4
(1,1,5), # dot5
(1,2,6), # dot6
]
for col, r, dotn in dot_positions:
bit = (mask >> (dotn-1)) & 1
cx = x0 + col * (dot_radius + dot_gap/2) + dot_radius + 4
cy = y0 + r * (dot_radius + dot_gap/2) + dot_radius + 4
bbox = [cx - dot_radius, cy - dot_radius, cx + dot_radius, cy + dot_radius]
if bit:
draw.ellipse(bbox, fill=(0,0,0))
else:
# draw faint circle to indicate empty dot (optional)
draw.ellipse(bbox, outline=(200,200,200))
return img
# -------------------------
# GUI
# -------------------------
class BrailleGUI:
def __init__(self, root):
self.root = root
root.title("Braille Translator Tool — Grade-1 (Uncontracted)")
root.geometry("820x520")
# Input frame
frame_in = tk.LabelFrame(root, text="Input Text", padx=8, pady=8)
frame_in.pack(fill="both", padx=12, pady=8)
self.text_input = tk.Text(frame_in, height=6, wrap="word", font=("Arial", 12))
self.text_input.pack(fill="both", expand=True)
self.text_input.insert("1.0", "Hello, World! 123")
# Controls
ctrl = tk.Frame(root)
ctrl.pack(fill="x", padx=12)
tk.Button(ctrl, text="Translate", command=self.on_translate).pack(side="left", padx=6, pady=6)
tk.Button(ctrl, text="Render Braille Image (Preview)", command=self.on_render_preview).pack(side="left", padx=6, pady=6)
tk.Button(ctrl, text="Save Braille Image...", command=self.on_save_image).pack(side="left", padx=6, pady=6)
tk.Button(ctrl, text="Copy Braille Unicode to Clipboard", command=self.on_copy_clipboard).pack(side="left", padx=6, pady=6)
# Output frame (braille unicode text)
frame_out = tk.LabelFrame(root, text="Braille (Unicode)", padx=8, pady=8)
frame_out.pack(fill="both", padx=12, pady=8, expand=True)
self.braille_text_widget = tk.Text(frame_out, height=6, wrap="word", font=("Segoe UI Symbol", 20))
self.braille_text_widget.pack(fill="both", expand=True)
self.braille_text_widget.config(state="disabled")
# Image preview area
preview_frame = tk.LabelFrame(root, text="Image Preview", padx=8, pady=8)
preview_frame.pack(fill="both", padx=12, pady=8)
self.preview_label = tk.Label(preview_frame)
self.preview_label.pack()
self.last_preview_image = None # keep reference to avoid GC
def on_translate(self):
txt = self.text_input.get("1.0", "end").rstrip("\n")
if not txt.strip():
messagebox.showwarning("Input required", "Please enter some text to translate.")
return
braille = translate_to_braille(txt)
self.braille_text_widget.config(state="normal")
self.braille_text_widget.delete("1.0", "end")
self.braille_text_widget.insert("1.0", braille)
self.braille_text_widget.config(state="disabled")
def on_render_preview(self):
braille = self.braille_text_widget.get("1.0", "end").rstrip("\n")
if not braille:
messagebox.showinfo("No Braille", "Translate text first (click Translate).")
return
img = render_braille_image(braille, dot_radius=8, dot_gap=10, cell_gap=14)
self.show_preview(img)
def on_save_image(self):
braille = self.braille_text_widget.get("1.0", "end").rstrip("\n")
if not braille:
messagebox.showinfo("No Braille", "Translate text first (click Translate).")
return
img = render_braille_image(braille, dot_radius=10, dot_gap=12, cell_gap=16)
path = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("PNG image","*.png")], title="Save Braille image")
if path:
img.save(path)
messagebox.showinfo("Saved", f"Braille image saved to:\n{path}")
def on_copy_clipboard(self):
braille = self.braille_text_widget.get("1.0", "end").rstrip("\n")
if not braille:
messagebox.showinfo("No Braille", "Translate text first (click Translate).")
return
# Use Tk clipboard
self.root.clipboard_clear()
self.root.clipboard_append(braille)
messagebox.showinfo("Copied", "Braille Unicode copied to clipboard.")
def show_preview(self, pil_img):
# Resize preview if too big
max_w, max_h = 760, 240
w, h = pil_img.size
scale = min(max_w / w, max_h / h, 1.0)
if scale < 1.0:
pil_img = pil_img.resize((int(w*scale), int(h*scale)), Image.LANCZOS)
tk_img = ImageTk.PhotoImage(pil_img)
self.preview_label.config(image=tk_img)
self.preview_label.image = tk_img # keep ref
# -------------------------
# Run the app
# -------------------------
def main():
root = tk.Tk()
app = BrailleGUI(root)
root.mainloop()
if __name__ == "__main__":
main()
No comments:
Post a Comment