diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index c5e47dee..65bcea9c 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -47,7 +47,8 @@ jobs: run: | # Download the input test mkdir -p tmp - wget -O tmp/test.input.bin https://github.com/ceccopierangiolieugenio/binaryRepo/raw/master/pyTermTk/tests/test.input.001.bin + wget -O tmp/test.input.001.bin https://github.com/ceccopierangiolieugenio/binaryRepo/raw/master/pyTermTk/tests/test.input.001.bin + wget -O tmp/test.input.002.bin https://github.com/ceccopierangiolieugenio/binaryRepo/raw/master/pyTermTk/tests/test.input.002.bin pytest tests/pytest/test_003_string.py pytest tests/pytest/test_002_textedit.py pytest tests/pytest/test_001_demo.py diff --git a/Makefile b/Makefile index d5faff06..f43e42d9 100644 --- a/Makefile +++ b/Makefile @@ -91,7 +91,8 @@ test: .venv # Play the test stream # tests/pytest/test_001_demo.py -p test.input.bin mkdir -p tmp - wget -O tmp/test.input.bin https://github.com/ceccopierangiolieugenio/binaryRepo/raw/master/pyTermTk/tests/test.input.001.bin + wget -O tmp/test.input.001.bin https://github.com/ceccopierangiolieugenio/binaryRepo/raw/master/pyTermTk/tests/test.input.001.bin + wget -O tmp/test.input.002.bin https://github.com/ceccopierangiolieugenio/binaryRepo/raw/master/pyTermTk/tests/test.input.002.bin tools/check.import.sh . .venv/bin/activate ; \ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude .venv,build,tmp ; \ diff --git a/TermTk/TTkCore/string.py b/TermTk/TTkCore/string.py index 1a43e601..a50c7f66 100644 --- a/TermTk/TTkCore/string.py +++ b/TermTk/TTkCore/string.py @@ -103,16 +103,20 @@ class TTkString(): if isinstance(other, TTkString): ret._text = self._text + other._text ret._colors = self._colors + other._colors + ret._hasTab = '\t' in ret._text + ret._fastCheckWidth(self._hasSpecialWidth, other._hasSpecialWidth) elif isinstance(other, str): atxt, acol = TTkString._parseAnsi(other, self._baseColor) ret._text = self._text + atxt ret._colors = self._colors + acol + ret._hasTab = '\t' in ret._text + ret._checkWidth() elif isinstance(other, _TTkColor): ret._text = self._text ret._colors = self._colors + ret._hasSpecialWidth = self._hasSpecialWidth + ret._hasTab = self._hasTab ret._baseColor = other - ret._hasTab = '\t' in ret._text - ret._checkWidth() return ret def __radd__(self, other): @@ -121,11 +125,13 @@ class TTkString(): if isinstance(other, TTkString): ret._text = other._text + self._text ret._colors = other._colors + self._colors + ret._hasTab = '\t' in ret._text + ret._fastCheckWidth(self._hasSpecialWidth, other._hasSpecialWidth) elif isinstance(other, str): ret._text = other + self._text ret._colors = [self._baseColor]*len(other) + self._colors - ret._hasTab = '\t' in ret._text - ret._checkWidth() + ret._hasTab = '\t' in ret._text + ret._checkWidth() return ret def __setitem__(self, index, value): @@ -182,7 +188,7 @@ class TTkString(): spaces = tabSpaces - (lentxt+tabSpaces)%tabSpaces ret._text += " "*spaces + s ret._colors += [c]*spaces + self._colors[pos+1:pos+1+len(s)] - ret._checkWidth() + ret._fastCheckWidth(self._hasSpecialWidth) pos+=len(s)+1 return ret @@ -328,7 +334,7 @@ class TTkString(): ret._colors = self._colors[:width] ret._hasTab = '\t' in ret._text - ret._checkWidth() + ret._fastCheckWidth(self._hasSpecialWidth) return ret @@ -467,7 +473,7 @@ class TTkString(): ret._text = self._text[fr:to] ret._colors = self._colors[fr:to] ret._hasTab = '\t' in ret._text - ret._checkWidth() + ret._fastCheckWidth(self._hasSpecialWidth) return ret def split(self, separator ): @@ -578,10 +584,17 @@ class TTkString(): return pos-i-1 return 0 + def _fastCheckWidth(self,a,b=None): + self._hasSpecialWidth = None if ( + a is None and b is None ) else self._termWidthW() + def _checkWidth(self): - self._hasSpecialWidth = self._termWidthW() if ( - any(unicodedata.east_asian_width(ch) == 'W' for ch in self._text) or - any(unicodedata.category(ch) in ('Me','Mn') for ch in self._text) ) else None + # from: tests/timeit/09.widechar.check.py + # the first not halfsize char is 0x300 + # this check is ~3 times faster than the 2 combined unicode checks + # and will quickly filter out the (more common) simple ascii text + tw = self._termWidthW() if any(ord(ch)>=0x300 for ch in self._text) else None + self._hasSpecialWidth = tw if tw != len(self._text) else None def _termWidthW(self): ''' String displayed length diff --git a/TermTk/TTkTestWidgets/testwidget.py b/TermTk/TTkTestWidgets/testwidget.py index 1dd5f95e..4bb632fb 100644 --- a/TermTk/TTkTestWidgets/testwidget.py +++ b/TermTk/TTkTestWidgets/testwidget.py @@ -31,16 +31,24 @@ from TermTk.TTkWidgets.label import * from TermTk.TTkWidgets.frame import * class _TestContent(TTkWidget): + t01 = TTkString(color=TTkColor.fg("#ff0000") ,text=" L😎rem ipsum dolor sit amet, ⌚ ❀ πŸ’™ πŸ™‹'") + t02 = TTkString(color=TTkColor.fg("#ff8800") ,text="consectetur adipiscing elit,") + t03 = TTkString(color=TTkColor.fg("#ffff00") ,text="sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.") + t04 = TTkString(color=TTkColor.fg("#00ff00") ,text="Ut enim ad minim veniam,") + t05 = TTkString(color=TTkColor.fg("#00ffff") ,text="quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.") + t06 = TTkString(color=TTkColor.fg("#0088ff") ,text="Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.") + t07 = TTkString(color=TTkColor.fg("#0000ff") ,text="Excepteur sint occaecat cupidatat non proident,") + t08 = TTkString(color=TTkColor.fg("#ff00ff") ,text="sunt in culpa qui officia deserunt mollit anim id est laborum.") def paintEvent(self): # TTkLog.debug(f"Test Paint - {self._name}") - y=0; self._canvas.drawText(pos=(-5,y),text=TTkString(color=TTkColor.fg("#ff0000") ,text=" L😎rem ipsum dolor sit amet, ⌚ ❀ πŸ’™ πŸ™‹'")) - y+=1; self._canvas.drawText(pos=(0,y), text=TTkString(color=TTkColor.fg("#ff8800") ,text="consectetur adipiscing elit,")) - y+=1; self._canvas.drawText(pos=(0,y), text=TTkString(color=TTkColor.fg("#ffff00") ,text="sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")) - y+=1; self._canvas.drawText(pos=(0,y), text=TTkString(color=TTkColor.fg("#00ff00") ,text="Ut enim ad minim veniam,")) - y+=1; self._canvas.drawText(pos=(0,y), text=TTkString(color=TTkColor.fg("#00ffff") ,text="quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.")) - y+=1; self._canvas.drawText(pos=(0,y), text=TTkString(color=TTkColor.fg("#0088ff") ,text="Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.")) - y+=1; self._canvas.drawText(pos=(0,y), text=TTkString(color=TTkColor.fg("#0000ff") ,text="Excepteur sint occaecat cupidatat non proident,")) - y+=1; self._canvas.drawText(pos=(0,y), text=TTkString(color=TTkColor.fg("#ff00ff") ,text="sunt in culpa qui officia deserunt mollit anim id est laborum.")) + y=0; self._canvas.drawText(pos=(-5,y), text=self.t01) + y+=1; self._canvas.drawText(pos=( 0,y), text=self.t02) + y+=1; self._canvas.drawText(pos=( 0,y), text=self.t03) + y+=1; self._canvas.drawText(pos=( 0,y), text=self.t04) + y+=1; self._canvas.drawText(pos=( 0,y), text=self.t05) + y+=1; self._canvas.drawText(pos=( 0,y), text=self.t06) + y+=1; self._canvas.drawText(pos=( 0,y), text=self.t07) + y+=1; self._canvas.drawText(pos=( 0,y), text=self.t08) y+=1; self._canvas.drawGrid( pos=(0,y),size=(self._width,self._height-y), hlines=(2,5,7), vlines=(4,7,15,30), diff --git a/tests/pytest/test_001_demo.py b/tests/pytest/test_001_demo.py index 37b99768..15b54d9d 100755 --- a/tests/pytest/test_001_demo.py +++ b/tests/pytest/test_001_demo.py @@ -166,7 +166,16 @@ def test_recording1(): # demo.ttk.TTkLog.use_default_file_logging() demo.ttk.TTkLog.installMessageHandler(message_handler) root = TTkRecord(title="pyTermTk Demo Record", record=False) - root.loadQueue(open('tmp/test.input.bin', 'rb')) + root.loadQueue(open('tmp/test.input.001.bin', 'rb')) + winTabbed1 = demo.ttk.TTkWindow(parent=root,pos=(0,0), size=(80,24), title="pyTermTk Showcase", border=True, layout=demo.ttk.TTkGridLayout()) + demo.demoShowcase(winTabbed1, True) + root.mainloop() + +def test_recording2(): + # demo.ttk.TTkLog.use_default_file_logging() + demo.ttk.TTkLog.installMessageHandler(message_handler) + root = TTkRecord(title="pyTermTk Demo Record", record=False) + root.loadQueue(open('tmp/test.input.002.bin', 'rb')) winTabbed1 = demo.ttk.TTkWindow(parent=root,pos=(0,0), size=(80,24), title="pyTermTk Showcase", border=True, layout=demo.ttk.TTkGridLayout()) demo.demoShowcase(winTabbed1, True) root.mainloop() \ No newline at end of file diff --git a/tests/timeit/09.widechar.check.py b/tests/timeit/09.widechar.check.py new file mode 100644 index 00000000..d9346ded --- /dev/null +++ b/tests/timeit/09.widechar.check.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import sys, os + +import timeit +import random +import unicodedata + +sys.path.append(os.path.join(sys.path[0],'../..')) +sys.path.append(os.path.join(sys.path[0],'.')) +import TermTk as ttk + +# first Zero size char 0x00300 +# first Full size char 0x01100 + + +print(f"Create CharSetStringTest...") +cstr = "" +cstrw = "" +for _ in range(0x4000): + cstr += chr(random.randint(0x100,0x2FF)) + cstrw += chr(random.randint(0x100,0x20000)) +print(f"Create CharSetStringTest DONE!!!") + +print(f"len cstr 0x{len(cstr):04x}") +print(f"len cstrw 0x{len(cstrw):04x}") + +# print([f"'{ch}':{unicodedata.east_asian_width(ch)}:{unicodedata.category(ch)}" for ch in cstr]) +# print([f"'{ch}':{unicodedata.east_asian_width(ch)}:{unicodedata.category(ch)}" for ch in cstrw]) + + +def _tw(text): + return ( len(text) + + sum(unicodedata.east_asian_width(ch) == 'W' for ch in text) - + sum(unicodedata.category(ch) in ('Me','Mn') for ch in text) ) + +def test1(text): + return ( len(text) + + sum(unicodedata.east_asian_width(ch) == 'W' for ch in text) - + sum(unicodedata.category(ch) in ('Me','Mn') for ch in text) ) + +def test2(text): + tw = _tw(text) if any(ord(ch)>=0x300 for ch in text) else None + return tw if tw != len(text) else None + +def test3(text): + return ( any(unicodedata.east_asian_width(ch) == 'W' for ch in text) or + any(unicodedata.category(ch) in ('Me','Mn') for ch in text) ) + +def test4(text): + return (any(ord(ch)>=0x300 for ch in text) and + ( any(unicodedata.east_asian_width(ch) == 'W' for ch in text) or + any(unicodedata.category(ch) in ('Me','Mn') for ch in text) ) ) + +def test5(text): + return any(ord(ch)>=0x300 for ch in text) + +def test6(text): + return any(ord(ch)>=0x300 for ch in text) + +loop = 200 + + +a=cstr +result = timeit.timeit('test1(a)', globals=globals(), number=loop) +print(f"1a {result / loop:.10f} - {result / loop} {test1(a)}") +result = timeit.timeit('test2(a)', globals=globals(), number=loop) +print(f"2a {result / loop:.10f} - {result / loop} {test2(a)}") +result = timeit.timeit('test3(a)', globals=globals(), number=loop) +print(f"3a {result / loop:.10f} - {result / loop} {test3(a)}") +result = timeit.timeit('test4(a)', globals=globals(), number=loop) +print(f"4a {result / loop:.10f} - {result / loop} {test4(a)}") +result = timeit.timeit('test5(a)', globals=globals(), number=loop) +print(f"5a {result / loop:.10f} - {result / loop} {test5(a)}") +result = timeit.timeit('test6(a)', globals=globals(), number=loop) +print(f"6a {result / loop:.10f} - {result / loop} {test6(a)}") + +a=cstrw +result = timeit.timeit('test1(a)', globals=globals(), number=loop) +print(f"1b {result / loop:.10f} - {result / loop} {test1(a)}") +result = timeit.timeit('test2(a)', globals=globals(), number=loop) +print(f"2b {result / loop:.10f} - {result / loop} {test2(a)}") +result = timeit.timeit('test3(a)', globals=globals(), number=loop) +print(f"3b {result / loop:.10f} - {result / loop} {test3(a)}") +result = timeit.timeit('test4(a)', globals=globals(), number=loop) +print(f"4b {result / loop:.10f} - {result / loop} {test4(a)}") +result = timeit.timeit('test5(a)', globals=globals(), number=loop) +print(f"5b {result / loop:.10f} - {result / loop} {test5(a)}") +result = timeit.timeit('test6(a)', globals=globals(), number=loop) +print(f"6b {result / loop:.10f} - {result / loop} {test6(a)}") \ No newline at end of file