Initial implementation of Arabic character reshaping & handling.

libgui/GuiCanvasWxBackend.py:{arabicShapes{},_reshapeArabic()}: initial implementation.
libgui/GuiCanvasWxBackend.py:draw{CursorMaskWithJournal,Patch}(): update type signature.
libgui/GuiCanvasWxBackend.py:drawPatch(): call _reshapeArabic() on Arabic character patches.
libroar/Roar{Assets,Canvas}Window.py: pass updated set of arguments to backend.draw{CursorWithMask,Patch}().
libtools/ToolText.py:{arabicRegEx{},_checkRtl()}: initial implementation.
libtools/ToolText.py:_processKeyChar(): call _checkRtl().
libtools/ToolText.py:onKeyboardEvent(): initial implementation of RTL backspace support.
assets/text/TODO: updated.
This commit is contained in:
Lucio Andrés Illanes Albornoz 2019-09-16 14:13:44 +02:00
parent 366a958fdb
commit deba33deba
5 changed files with 154 additions and 30 deletions

View File

@ -23,8 +23,9 @@ High-priority list:
Queue:
1) scrolling bug: start @ top, down key til cursor below visible canvas, scroll down, cursor gone GRRRR
2) scrolling bug: scroll down, apply operator to entire canvas, scroll up
3) object tool: object size wrong occasionally w/ external objects
4) scrolling bug: scroll down, un/redo, scroll up
5) clone selection lag
3) scrolling bug: scroll down, text tool, move cursor w/ arrow keys
4) object tool: object size wrong occasionally w/ external objects
5) scrolling bug: scroll down, un/redo, scroll up
6) clone selection lag
vim:ff=dos tw=0

View File

@ -27,6 +27,47 @@ class GuiBufferedDC(wx.MemoryDC):
# }}}
class GuiCanvasWxBackend():
# {{{ arabicShapes{}
arabicShapes = {
u'\u0621': (u'\uFE80'),
u'\u0622': (u'\uFE81', None, None, u'\uFE82'),
u'\u0623': (u'\uFE83', None, None, u'\uFE84'),
u'\u0624': (u'\uFE85', None, None, u'\uFE86'),
u'\u0625': (u'\uFE87', None, None, u'\uFE88'),
u'\u0626': (u'\uFE89', u'\uFE8B', u'\uFE8C', u'\uFE8A'),
u'\u0627': (u'\uFE8D', None, None, u'\uFE8E'),
u'\u0628': (u'\uFE8F', u'\uFE91', u'\uFE92', u'\uFE90'),
u'\u0629': (u'\uFE93', None, None, u'\uFE94'),
u'\u062A': (u'\uFE95', u'\uFE97', u'\uFE98', u'\uFE96'),
u'\u062B': (u'\uFE99', u'\uFE9B', u'\uFE9C', u'\uFE9A'),
u'\u062C': (u'\uFE9D', u'\uFE9F', u'\uFEA0', u'\uFE9E'),
u'\u062D': (u'\uFEA1', u'\uFEA3', u'\uFEA4', u'\uFEA2'),
u'\u062E': (u'\uFEA5', u'\uFEA7', u'\uFEA8', u'\uFEA6'),
u'\u062F': (u'\uFEA9', None, None, u'\uFEAA'),
u'\u0630': (u'\uFEAB', None, None, u'\uFEAC'),
u'\u0631': (u'\uFEAD', None, None, u'\uFEAE'),
u'\u0632': (u'\uFEAF', None, None, u'\uFEB0'),
u'\u0633': (u'\uFEB1', u'\uFEB3', u'\uFEB4', u'\uFEB2'),
u'\u0634': (u'\uFEB5', u'\uFEB7', u'\uFEB8', u'\uFEB6'),
u'\u0635': (u'\uFEB9', u'\uFEBB', u'\uFEBC', u'\uFEBA'),
u'\u0636': (u'\uFEBD', u'\uFEBF', u'\uFEC0', u'\uFEBE'),
u'\u0637': (u'\uFEC1', u'\uFEC3', u'\uFEC4', u'\uFEC2'),
u'\u0638': (u'\uFEC5', u'\uFEC7', u'\uFEC8', u'\uFEC6'),
u'\u0639': (u'\uFEC9', u'\uFECB', u'\uFECC', u'\uFECA'),
u'\u063A': (u'\uFECD', u'\uFECF', u'\uFED0', u'\uFECE'),
u'\u0640': (u'\u0640', None, None, None),
u'\u0641': (u'\uFED1', u'\uFED3', u'\uFED4', u'\uFED2'),
u'\u0642': (u'\uFED5', u'\uFED7', u'\uFED8', u'\uFED6'),
u'\u0643': (u'\uFED9', u'\uFEDB', u'\uFEDC', u'\uFEDA'),
u'\u0644': (u'\uFEDD', u'\uFEDF', u'\uFEE0', u'\uFEDE'),
u'\u0645': (u'\uFEE1', u'\uFEE3', u'\uFEE4', u'\uFEE2'),
u'\u0646': (u'\uFEE5', u'\uFEE7', u'\uFEE8', u'\uFEE6'),
u'\u0647': (u'\uFEE9', u'\uFEEB', u'\uFEEC', u'\uFEEA'),
u'\u0648': (u'\uFEED', None, None, u'\uFEEE'),
u'\u0649': (u'\uFEEF', None, None, u'\uFEF0'),
u'\u064A': (u'\uFEF1', u'\uFEF3', u'\uFEF4', u'\uFEF2'),
}
# }}}
# {{{ _CellState(): Cell state
class _CellState():
CS_NONE = 0x00
@ -99,6 +140,40 @@ class GuiCanvasWxBackend():
self._penAlpha = wx.Pen(wx.Colour(Colours[14][:4]), 1)
self._lastBrushBg, self._lastBrushFg, self._lastPen = None, None, None
# }}}
# {{{ _reshapeArabic(self, canvas, eventDc, patch, point)
def _reshapeArabic(self, canvas, eventDc, patch, point):
lastCell = point[0]
while True:
if ((lastCell + 1) >= (canvas.size[0] - 1)) \
or (not canvas.map[point[1]][lastCell + 1][3] in self.arabicShapes):
break
else:
lastCell += 1
connect = False
for runX in range(lastCell, point[0], -1):
runCell = list(canvas.map[point[1]][runX])
if runX == lastCell:
if self.arabicShapes[runCell[3]][1] != None:
runCell[3] = self.arabicShapes[runCell[3]][1]; connect = True;
else:
runCell[3] = self.arabicShapes[runCell[3]][0]; connect = False;
else:
if connect and (self.arabicShapes[runCell[3]][2] != None):
runCell[3] = self.arabicShapes[runCell[3]][2]; connect = True;
elif connect and (self.arabicShapes[runCell[3]][3] != None):
runCell[3] = self.arabicShapes[runCell[3]][3]; connect = False;
elif not connect and (self.arabicShapes[runCell[3]][1] != None):
runCell[3] = self.arabicShapes[runCell[3]][1]; connect = True;
else:
runCell[3] = self.arabicShapes[runCell[3]][0]; connect = False;
self._drawCharPatch(eventDc, runCell, [runX, point[1]])
runCell = list(patch[2:])
if connect and (self.arabicShapes[patch[5]][3] != None):
runCell[3] = self.arabicShapes[patch[5]][3]
else:
runCell[3] = self.arabicShapes[patch[5]][0]
self._drawCharPatch(eventDc, runCell, [point[0], point[1]])
# }}}
# {{{ _setBrushDc(self, brushBg, brushFg, dc, pen)
def _setBrushDc(self, brushBg, brushFg, dc, pen):
if self._lastBrushBg != brushBg:
@ -113,12 +188,12 @@ class GuiCanvasWxBackend():
return [a * b for a, b in zip(point, self.cellSize)]
# }}}
# {{{ drawCursorMaskWithJournal(self, canvasJournal, eventDc, viewRect)
def drawCursorMaskWithJournal(self, canvasJournal, eventDc, viewRect):
[self.drawPatch(eventDc, patch, viewRect) for patch in canvasJournal.popCursor()]
# {{{ drawCursorMaskWithJournal(self, canvas, canvasJournal, eventDc, viewRect)
def drawCursorMaskWithJournal(self, canvas, canvasJournal, eventDc, viewRect):
[self.drawPatch(canvas, eventDc, patch, viewRect) for patch in canvasJournal.popCursor()]
# }}}
# {{{ drawPatch(self, eventDc, patch, viewRect)
def drawPatch(self, eventDc, patch, viewRect):
# {{{ drawPatch(self, canvas, eventDc, patch, viewRect)
def drawPatch(self, canvas, eventDc, patch, viewRect):
point = [m - n for m, n in zip(patch[:2], viewRect)]
if [(c >= 0) and (c < s) for c, s in zip(point, self.canvasSize)] == [True, True]:
if patch[5] == " ":
@ -128,6 +203,8 @@ class GuiCanvasWxBackend():
self._drawCharPatch(eventDc, patch[2:], point)
else:
self._drawBrushPatch(eventDc, patch[2:], point)
elif patch[5] in self.arabicShapes:
self._reshapeArabic(canvas, eventDc, patch, point)
else:
self._drawCharPatch(eventDc, patch[2:], point)
return True

View File

@ -10,10 +10,10 @@ from GuiWindow import GuiWindow
import json, os, sys, wx
class RoarAssetsWindow(GuiMiniFrame):
# {{{ _drawPatch(self, eventDc, isCursor, patch, viewRect)
def _drawPatch(self, eventDc, isCursor, patch, viewRect):
# {{{ _drawPatch(self, canvas, eventDc, isCursor, patch, viewRect)
def _drawPatch(self, canvas, eventDc, isCursor, patch, viewRect):
if not isCursor:
self.backend.drawPatch(eventDc, patch, viewRect)
self.backend.drawPatch(canvas, eventDc, patch, viewRect)
# }}}
# {{{ _import(self, f, pathName)
def _import(self, f, pathName):
@ -113,7 +113,7 @@ class RoarAssetsWindow(GuiMiniFrame):
eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas, viewRect)
for numRow in range(canvas.size[1]):
for numCol in range(canvas.size[0]):
self.backend.drawPatch(eventDc, [numCol, numRow, *canvas.map[numRow][numCol]], viewRect)
self.backend.drawPatch(canvas, eventDc, [numCol, numRow, *canvas.map[numRow][numCol]], viewRect)
# }}}
# {{{ onPaint(self, event)
def onPaint(self, event):
@ -153,11 +153,11 @@ class RoarAssetsWindow(GuiMiniFrame):
if deltaSize[0] > 0:
for numRow in range(oldSize[1]):
for numNewCol in range(oldSize[0], newSize[0]):
self._drawPatch(eventDc, False, [numNewCol, numRow, 1, 1, 0, " "], viewRect)
self._drawPatch(canvas, eventDc, False, [numNewCol, numRow, 1, 1, 0, " "], viewRect)
if deltaSize[1] > 1:
for numNewRow in range(oldSize[1], newSize[1]):
for numNewCol in range(newSize[0]):
self._drawPatch(eventDc, False, [numNewCol, numNewRow, 1, 1, 0, " "], viewRect)
self._drawPatch(canvas, eventDc, False, [numNewCol, numNewRow, 1, 1, 0, " "], viewRect)
# }}}
# {{{ update(self, canvas, newSize, newCanvas=None)
def update(self, canvas, newSize, newCanvas=None):
@ -167,7 +167,7 @@ class RoarAssetsWindow(GuiMiniFrame):
eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas, viewRect)
for numRow in range(canvas.size[1]):
for numCol in range(canvas.size[0]):
self.backend.drawPatch(eventDc, [numCol, numRow, *canvas.map[numRow][numCol]], viewRect)
self.backend.drawPatch(canvas, eventDc, [numCol, numRow, *canvas.map[numRow][numCol]], viewRect)
# }}}
# {{{ onImportAnsi(self, event)

View File

@ -47,9 +47,9 @@ class RoarCanvasWindow(GuiWindow):
# {{{ _drawPatch(self, eventDc, isCursor, patch, viewRect)
def _drawPatch(self, eventDc, isCursor, patch, viewRect):
if not self.canvas.dirtyCursor:
self.backend.drawCursorMaskWithJournal(self.canvas.journal, eventDc, viewRect)
self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc, viewRect)
self.canvas.dirtyCursor = True
if self.backend.drawPatch(eventDc, patch, viewRect) and isCursor:
if self.backend.drawPatch(self.canvas, eventDc, patch, viewRect) and isCursor:
patchDeltaCell = self.canvas.map[patch[1]][patch[0]]; patchDelta = [*patch[0:2], *patchDeltaCell];
self.canvas.journal.pushCursor(patchDelta)
# }}}
@ -103,7 +103,7 @@ class RoarCanvasWindow(GuiWindow):
viewRect = self.GetViewStart()
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect)
if self.canvas.dirtyCursor:
self.backend.drawCursorMaskWithJournal(self.canvas.journal, eventDc, viewRect)
self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc, viewRect)
self.canvas.dirtyCursor = False
for patch in deltaPatches:
if patch == None:
@ -111,7 +111,7 @@ class RoarCanvasWindow(GuiWindow):
elif patch[0] == "resize":
del eventDc; self.resize(patch[1:], False); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart());
else:
self.canvas._commitPatch(patch); self.backend.drawPatch(eventDc, patch, self.GetViewStart());
self.canvas._commitPatch(patch); self.backend.drawPatch(self.canvas, eventDc, patch, self.GetViewStart());
# }}}
# {{{ dispatchPatch(self, eventDc, isCursor, patch, viewRect)
def dispatchPatch(self, eventDc, isCursor, patch, viewRect):
@ -148,7 +148,7 @@ class RoarCanvasWindow(GuiWindow):
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart())
for numRow in range(newSize[1]):
for numCol in range(newSize[0]):
self.backend.drawPatch(eventDc, [numCol, numRow, *self.canvas.map[numRow][numCol]], self.GetViewStart())
self.backend.drawPatch(self.canvas, eventDc, [numCol, numRow, *self.canvas.map[numRow][numCol]], self.GetViewStart())
# }}}
# {{{ onKeyboardInput(self, event)
@ -193,7 +193,7 @@ class RoarCanvasWindow(GuiWindow):
def onLeaveWindow(self, event):
if False:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart())
self.backend.drawCursorMaskWithJournal(self.canvas.journal, eventDc, self.GetViewStart())
self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc, self.GetViewStart())
self.lastCellState = None
# }}}
# {{{ onMouseInput(self, event)
@ -228,7 +228,7 @@ class RoarCanvasWindow(GuiWindow):
def onPaint(self, event):
viewRect = self.GetViewStart()
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect)
self.backend.drawCursorMaskWithJournal(self.canvas.journal, eventDc, viewRect)
self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc, viewRect)
self.backend.onPaint(self.GetClientSize(), self, self.GetViewStart())
# }}}

View File

@ -5,12 +5,42 @@
#
from Tool import Tool
import re, string, wx
import re, string, time, wx
class ToolText(Tool):
name = "Text"
arabicRegEx = r'^[\u0621-\u063A\u0640-\u064A]+$'
rtlRegEx = r'^[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]+$'
# {{{ _checkRtl(self, canvas, brushPos, keyChar)
def _checkRtl(self, canvas, brushPos, keyChar):
rtlFlag = False
if (keyChar != None) and re.match(self.rtlRegEx, keyChar):
rtlFlag = True
else:
lastX, lastY = brushPos[0], brushPos[1]
while True:
if canvas.map[lastY][lastX][3] == " ":
if (lastX + 1) >= canvas.size[0]:
if lastY == 0:
break
else:
lastX, lastY = 0, lastY - 1
else:
lastX += 1
elif re.match(self.arabicRegEx, canvas.map[lastY][lastX][3]):
rtlFlag = True
if (lastX + 1) >= canvas.size[0]:
if lastY == 0:
break
else:
lastX, lastY = 0, lastY - 1
else:
lastX += 1
else:
break
return rtlFlag
# }}}
# {{{ _processKeyChar(self, brushColours, brushPos, canvas, dispatchFn, eventDc, keyChar, keyModifiers, viewRect)
def _processKeyChar(self, brushColours, brushPos, canvas, dispatchFn, eventDc, keyChar, keyModifiers, viewRect):
if (ord(keyChar) != wx.WXK_NONE) \
@ -18,7 +48,7 @@ class ToolText(Tool):
and ((ord(keyChar) >= 32) if ord(keyChar) < 127 else True) \
and (keyModifiers in (wx.MOD_NONE, wx.MOD_SHIFT)):
dispatchFn(eventDc, False, [*brushPos, *brushColours, 0, keyChar], viewRect);
if not re.match(self.rtlRegEx, keyChar):
if not self._checkRtl(canvas, brushPos, keyChar):
if brushPos[0] < (canvas.size[0] - 1):
brushPos[0] += 1
elif brushPos[1] < (canvas.size[1] - 1):
@ -56,13 +86,29 @@ class ToolText(Tool):
else:
rc, error = False, "Clipboard does not contain text data and/or cannot be opened"
elif keyCode == wx.WXK_BACK:
if brushPos[0] > 0:
brushPos[0] -= 1
elif brushPos[1] > 0:
brushPos[0], brushPos[1] = canvas.size[0] - 1, brushPos[1] - 1
if ((brushPos[0] + 1) >= canvas.size[0]):
if brushPos[1] > 0:
lastBrushPos = [0, brushPos[1] - 1]
else:
lastBrushPos = [0, 0]
else:
brushPos[0], brushPos[1] = canvas.size[0] - 1, canvas.size[1] - 1
rc, dirty = True, False; dispatchFn(eventDc, True, [*brushPos, *brushColours, 0, "_"], viewRect);
lastBrushPos = [brushPos[0] + 1, brushPos[1]]
if not self._checkRtl(canvas, lastBrushPos, None):
if brushPos[0] > 0:
brushPos[0] -= 1
elif brushPos[1] > 0:
brushPos[0], brushPos[1] = canvas.size[0] - 1, brushPos[1] - 1
else:
brushPos[0], brushPos[1] = canvas.size[0] - 1, canvas.size[1] - 1
else:
if brushPos[0] < (canvas.size[0] - 1):
brushPos[0] += 1
elif brushPos[1] > 0:
brushPos[0], brushPos[1] = 0, brushPos[1] - 1
else:
brushPos[0], brushPos[1] = canvas.size[0] - 1, 0
rc, dirty = True, False; dispatchFn(eventDc, False, [*brushPos, *brushColours, 0, " "], viewRect);
dispatchFn(eventDc, True, [*brushPos, *brushColours, 0, "_"], viewRect);
elif keyCode == wx.WXK_RETURN:
if brushPos[1] < (canvas.size[1] - 1):
brushPos[0], brushPos[1] = 0, brushPos[1] + 1