Текстовый виджет Tkinter для визуализации шрифтов Devanagari (Windows 10, Python 3)
Я работаю над редактором Devanagari/Indic с помощью Tkinter/Python3 и у меня есть вопрос по визуализации шрифта виджета Text.
Деванагари (и все сценарии на индийском языке) - это алфавитный алфавит, а группы согласных ("кластеры") и гласные знаки образуют слог. Согласные объединяются с использованием символа Unicode ZWJ, а за визуализацию отвечает виджет Текст (возможно, диакритические знаки гласные могут быть переупорядочены). Гласные символы, встречающиеся после согласного, используют диакритическую форму, а не их полные формы. Смотрите следующее описание от Microsoft.
Разработка шрифтов OpenType для скрипта деванагари
Деванагари слог - Эффективная орфографическая "единица" системы письма Деванагари. Слоги состоят из согласных букв, независимых> гласных и зависимых гласных. В текстовой последовательности эти символы хранятся> в фонетическом порядке (хотя они могут не отображаться в фонетическом порядке> при отображении). Как только слог сформирован, он неделим. Курсор не может быть расположен внутри слога. Преобразования>, обсуждаемые в этом документе, не пересекают границы слогов.
Когда вы не выделяете текст с помощью перетаскивания мышью, я стараюсь в обработчиках событий Python, чтобы курсоры INSERT и CURRENT никогда не находились в позициях символов, попадающих в слог, так что диалектические и согласные полуформы не отображаются.
Тем не менее, я в тупик, когда дело доходит до обработки перетаскивания мыши (для выделения текста). Кажется, я не могу понять, как запретить курсору мыши во время события перетаскивания (движение B1) перемещаться в позиции символов между слогами.
я используюself.editorText.mark_set ("my_index", "@%d,%d" % (event.x, event.y))
чтобы получить индекс перетаскивания, затем используйте мою процедуру для поиска первой позиции символа слева от слога и за его пределами. Но это просто не работает.
Смотрите изображения ниже о проблемах рендеринга шрифтов...
Текст на деванагари отображается правильно
Курсор перетаскивания мыши попадает внутрь слога и портит отображение
Любая помощь приветствуется!
-PP
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import font
reverse = {}
ucode = {}
varna = {}
enc1 = {}
enc2 = {}
first_vowel = ''
allLines = """VIRAMA Special 2381 -
p Consonant 2346 -
r Consonant 2352 -
A Vowel 2310 2366
aa Vowel 2310 2366
u Vowel 2313 2369
D Consonant 2337 -
ai Vowel 2320 2376
g Consonant 2327 -
n Consonant 2344 -
ZWNJ JoinerSpecial 8204 -
ZWJ JoinerSpecial 8205 -
"""
#default EditorText height in pixels
editorTextWinfoHeightPixels = 1
editorTextFontHeightPixels = 1
class klasse(object):
def __init__(self, master):
self.top_line_number = 1
self.bottom_line_number = 20
self.last_line_number = 1
self.current_line_number = 1
self.current_char_position = 0
self.previous_linelength = 0
self.current_linelength = 0
self.ieSelection = ''
self.ieSelectionLength = 0
self.dragLineStart = 0
self.dragLineEnd = 0
self.dragCharStart = 0
self.dragCharEnd = 0
self.numCopiedLines = 0
self.fonts=list(font.families())
self.fonts.sort()
maxFontNameLength = len(max(self.fonts))
self.frame = tk.Frame(root)
self.editorFrame = tk.Frame(self.frame)
self.editorFrame.pack(side="left", fill="both", expand="yes")
self.editorText = tk.Text(self.editorFrame, exportselection="true")
self.editorText.pack(side="top", fill="both", expand="yes", padx=2)
self.editorText.config( wrap="word", # use word wrapping
undo=True,
font=('Arial', 20))
self.editorText.see(tk.INSERT)
self.editorText.focus()
self.editorText.bindtags((self.editorText))
self.editorText.bind('<B1-Motion>', self.handleMouseDrag)
self.editorText.bind('<Button-1>', self.mouseButtonCallback)
self.frame.pack(fill="both", expand="yes")
self.bottom_line_number = int(301/editorTextFontHeightPixels)
self.editorText.insert("insert", chr(2346))
self.editorText.insert("insert", chr(2381))
self.editorText.insert("insert", chr(2352))
self.editorText.insert("insert", chr(2366))
self.editorText.insert("insert", chr(2313))
self.editorText.insert("insert", chr(2337))
self.editorText.insert("insert", chr(2346))
self.editorText.insert("insert", chr(2366))
#self.editorText.insert("insert", chr(2376))
self.editorText.insert("insert", chr(2327))
self.editorText.insert("insert", chr(2344))
def handleMouseDrag (self, event):
[start_line_number, start_char_position] = self.editorText.index('current').split('.')
start_line_number = int(start_line_number)
start_char_position = int(start_char_position)
self.editorText.mark_set ("anirb_index", "@%d,%d" % (event.x, event.y))
[temp_current_line_number, temp_current_char_position] = self.editorText.index('anirb_index').split('.')
temp_current_line_number = int(temp_current_line_number)
temp_current_char_position = int(temp_current_char_position)
if temp_current_line_number > start_line_number or\
(temp_current_line_number == start_line_number and temp_current_char_position > start_char_position):
#forward selection
self.editorText.mark_set ("sel.first", str(start_line_number)+'.'+str(start_char_position))
self.editorText.mark_set ("sel.last", str(temp_current_line_number)+'.'+str(temp_current_char_position))
else:
#backward selection
self.editorText.mark_set ("sel.first", str(temp_current_line_number)+'.'+str(temp_current_char_position))
self.editorText.mark_set ("sel.last", str(start_line_number)+'.'+str(start_char_position))
if self.editorText.tag_ranges ("sel") != []:
self.editorText.tag_remove ("sel", "1.0", "end")
self.current_line_number = temp_current_line_number
self.current_char_position = temp_current_char_position
self.editorText.tag_add ("sel", "sel.first", "sel.last")
self.editorText.tag_config ("sel", background="darkblue", foreground="white")
self.editorText.mark_set ("insert", "sel.last")
def mouseSettle (self, callingWidget, mouseCursorIndex):
global ucode
current_char = ''
[temp_current_line_number, temp_current_char_position] = mouseCursorIndex
while (1):
if temp_current_char_position == 0:
break
curr_char_index = str(temp_current_line_number) + '.' + str(temp_current_char_position)
prev_char_index = str(temp_current_line_number) + '.' + str(temp_current_char_position - 1)
char_to_left = self.editorText.get (prev_char_index, curr_char_index)
if char_to_left != '':
char_to_left = ord(char_to_left)
next_char_index = str(temp_current_line_number) + '.' + str(temp_current_char_position + 1)
current_char = self.editorText.get (curr_char_index, next_char_index)
if current_char != '':
current_char = ord(current_char)
if current_char not in ucode.keys() or current_char == '':
ucode[current_char] = "null"
if char_to_left not in ucode.keys() or char_to_left == '':
ucode[char_to_left] = "null"
if ("consonant" in ucode[current_char] and "special" not in ucode[char_to_left]) or \
(ucode[current_char] == "null" and "special" not in ucode[char_to_left]) or \
ucode[char_to_left] == "vowel" or \
ucode[char_to_left] == "standalone" or \
ucode[char_to_left] == "null":
break
temp_current_char_position -= 1
#endwhile
return temp_current_char_position
#enddef
#left mouse button click in editorText widget
def mouseButtonCallback (self, event=None):
global ucode
[self.current_line_number, temp_current_char_position] = self.editorText.index('current').split('.')
self.current_line_number = int(self.current_line_number)
temp_current_char_position = int(temp_current_char_position)
self.current_linelength = len(self.editorText.get (str(self.current_line_number)+'.0', str(self.current_line_number)+'.end'))
if temp_current_char_position >= self.current_linelength:
self.current_char_position = self.current_linelength
else:
#mouseSettle will return the new character position for INSERT cursor
self.current_char_position = self.mouseSettle (0, [self.current_line_number, temp_current_char_position])
self.editorText.mark_set ("insert", str(self.current_line_number)+'.'+str(self.current_char_position))
self.editorText.mark_set ("current", str(self.current_line_number)+'.'+str(self.current_char_position))
#remove all selection
if self.editorText.tag_ranges ("sel") != []:
self.editorText.tag_remove ("sel", "1.0", "end")
difference = self.bottom_line_number - self.top_line_number
if self.bottom_line_number < self.current_line_number:
self.bottom_line_number = self.current_line_number
self.top_line_number = self.bottom_line_number - difference
if self.top_line_number > self.current_line_number:
top_line_number = self.current_line_number
self.bottom_line_number = self.top_line_number + difference
#in case user clicked on a partially visible line at the top or bottom adjust those
self.editorText.see(tk.INSERT)
self.editorText.focus()
def parse ():
global reverse
global ucode
global enc1
global enc2
global varna
global first_vowel
for line in allLines.split('\n'):
#split on the comment character
lineList = line.split('#')
line = lineList[0] #whatever came before the comment character
if line.lstrip() == '':
continue
lineList = line.split()
#discard comments
if len(lineList) == 4:
(key, v, e1, e2) = lineList
print ("key is ---", key)
if e1 == "-":
e1 = ''
if e2 == "-":
e2 = ''
elif len(lineList) > 0 and len(lineList) < 4:
print('Error: Illegal Map ')
varna[key] = v.lower()
enc1[key] = e1.lower()
enc2[key] = e2.lower()
if varna[key] == "vowel" and enc2[key] == '':
first_vowel = key
#now create the hashes that characterize each 16-bit unicode character
if "+" not in e1 and e1 != '':
ucode[int(e1)] = v.lower()
#this is the reverse map of enc1 to the latin input character (key)
reverse[int(e1)] = key
if "+" not in e2 and e2 != '':
ucode[int(e2)] = v.lower()
#this is the reverse map of enc2 to the latin input character (key)
reverse[int(e2)] = key
parse()
counter = 0
root = tk.Tk()
app = klasse(root)
root.minsize (900, 350)
root.mainloop()