From 8ab52e7ff166875380ccae0773293d6808202fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucio=20Andr=C3=A9s=20Illanes=20Albornoz?= Date: Mon, 16 Sep 2019 14:13:44 +0200 Subject: [PATCH] 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. --- assets/text/TODO | 7 +-- libgui/GuiCanvasWxBackend.py | 87 +++++++++++++++++++++++++++++++++--- libroar/RoarAssetsWindow.py | 14 +++--- libroar/RoarCanvasWindow.py | 14 +++--- libtools/ToolText.py | 62 +++++++++++++++++++++---- 5 files changed, 154 insertions(+), 30 deletions(-) diff --git a/assets/text/TODO b/assets/text/TODO index 93fbb93..409e7f2 100644 --- a/assets/text/TODO +++ b/assets/text/TODO @@ -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 diff --git a/libgui/GuiCanvasWxBackend.py b/libgui/GuiCanvasWxBackend.py index 70d4cc4..c4d35f3 100644 --- a/libgui/GuiCanvasWxBackend.py +++ b/libgui/GuiCanvasWxBackend.py @@ -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 diff --git a/libroar/RoarAssetsWindow.py b/libroar/RoarAssetsWindow.py index 675d6e7..fdafe8b 100644 --- a/libroar/RoarAssetsWindow.py +++ b/libroar/RoarAssetsWindow.py @@ -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) diff --git a/libroar/RoarCanvasWindow.py b/libroar/RoarCanvasWindow.py index eb0824d..1919b83 100644 --- a/libroar/RoarCanvasWindow.py +++ b/libroar/RoarCanvasWindow.py @@ -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()) # }}} diff --git a/libtools/ToolText.py b/libtools/ToolText.py index bcd51f5..bb7c478 100644 --- a/libtools/ToolText.py +++ b/libtools/ToolText.py @@ -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