diff --git a/TermTk/TTkCore/canvas.py b/TermTk/TTkCore/canvas.py index 56521f2b..c33d3b3b 100644 --- a/TermTk/TTkCore/canvas.py +++ b/TermTk/TTkCore/canvas.py @@ -22,6 +22,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import unicodedata + from TermTk.TTkCore.TTkTerm.term import TTkTerm from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog @@ -29,7 +31,6 @@ from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.string import TTkString - class TTkCanvas: ''' Init the Canvas object @@ -178,6 +179,47 @@ class TTkCanvas: x,y = pos self._set(y, x, char, color) + + def drawTTkString(self, pos, text, width=None, color=TTkColor.RST, alignment=TTkK.NONE, forceColor=False): + ''' + NOTE: + drawText is one of the most abused functions, + there is some redundant code here in order to reduce the footprint + ''' + if not self._visible: return + + # Check the size and bounds + x,y = pos + if y<0 or y>=self._height : return + + lentxt = text.termWidth() + if width is None or width<0: + width = lentxt + + if x+width<0 or x>=self._width : return + + text = text.align(width=width, alignment=alignment, color=color) + txt, colors = text.tab2spaces().getData() + if forceColor: + colors=[color]*len(colors) + a,b = max(0,-x), min(len(txt),self._width-x) + for i in range(a,b): + #self._set(y, x+i, txt[i-x], colors[i-x]) + self._data[y][x+i] = txt[i] + if colors[i] == TTkColor.RST != color: + self._colors[y][x+i] = color.mod(x+i,y) + else: + self._colors[y][x+i] = colors[i].mod(x+i,y) + # Check the full wide chars on the edge of the two canvasses + wcColor = TTkColor.fg("#888888")+TTkColor.bg("000088") + if self._data[y][x+a] == '': + self._data[y][x+a] = '<' + self._colors[y][x+a] = wcColor + if ( len(ch:=self._data[y][x+b-1])==1 + and unicodedata.east_asian_width(ch)=='W'): + self._data[y][x+b-1] = '>' + self._colors[y][x+b-1] = wcColor + def drawText(self, pos, text, width=None, color=TTkColor.RST, alignment=TTkK.NONE, forceColor=False): ''' NOTE: @@ -598,9 +640,29 @@ class TTkCanvas: wslice = w if x+w < bx+bw else bx+bw-x hslice = h if y+h < by+bh else by+bh-y + wcColor = TTkColor.fg("#888888")+TTkColor.bg("000088") + for iy in range(yoffset,hslice): - self._data[y+iy][x+xoffset:x+wslice] = canvas._data[iy][xoffset:wslice] - self._colors[y+iy][x+xoffset:x+wslice] = canvas._colors[iy][xoffset:wslice] + a, b = x+xoffset, x+wslice + self._data[y+iy][a:b] = canvas._data[iy][xoffset:wslice] + self._colors[y+iy][a:b] = canvas._colors[iy][xoffset:wslice] + + # Check the full wide chars on the edge of the two canvasses + if self._data[y+iy][a]=='': + self._data[y+iy][a] = '<' + self._colors[y+iy][a] = wcColor + if ( len(ch:=self._data[y+iy][b-1])==1 + and unicodedata.east_asian_width(ch)=='W'): + self._data[y+iy][b-1] = '>' + self._colors[y+iy][b-1] = wcColor + if ( a and len(ch:=self._data[y+iy][a-1])==1 + and unicodedata.east_asian_width(ch)=='W'): + self._data[y+iy][a-1] = '>' + self._colors[y+iy][a-1] = wcColor + if ( b +# +# 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 logging +import time + +sys.path.append(os.path.join(sys.path[0],'..')) +from TermTk import TTkLog +from TermTk.TTkCore import TTkColor +from TermTk.TTkCore import TTkHelper +from TermTk.TTkCore import TTkString +from TermTk.TTkCore import TTkTerm + + +# TTkLog.use_default_file_logging() +TTkLog.use_default_stdout_logging() + +# TTkTerm.init(mouse=False) +TTkLog.info("Starting") + +s1 = TTkString("-😁😂😍😎----") +s2 = TTkString("--😐😁😂😍😎-") + +zc1 = chr(0x07a6) +zc2 = chr(0x20D7) +zc3 = chr(0x065f) +s3 = TTkString(f"Zero Size: - o{zc1} o{zc2} o{zc3} o{zc1}{zc2} o{zc1}{zc2}{zc3} -") + +s4 = TTkString("This is a notmal string") + +print(s1.getData()[0]) +print(s2.getData()[0]) +print(s3.getData()[0]) +print(s4.getData()[0]) + +# time.sleep(5) + +TTkLog.info("Ending") + +# TTkTerm.exit() \ No newline at end of file diff --git a/tests/test.metaclass.001.py b/tests/test.metaclass.001.py index 09137aa8..1e823673 100755 --- a/tests/test.metaclass.001.py +++ b/tests/test.metaclass.001.py @@ -21,7 +21,6 @@ # 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. - class A_Meta(type): def __new__(mcs, name, bases, d): print(f"{mcs=}") @@ -40,7 +39,7 @@ print(f"{A=}") a = A(1,2,3,4) -print(f"{a=}\n") +print(f"A ---> {a=}\n") class B(A_Meta): def __init__(self, *args, **kwargs): @@ -50,7 +49,7 @@ class B(A_Meta): b = B("NB",(),{}) -print(f"{b=}\n") +print(f"B ---> {b=}\n") class C(): def __init__(self) -> None: @@ -68,4 +67,4 @@ class E(C,D): e = E() e.pippo() -print(f"{issubclass(E,D)=} {issubclass(E,C)=}") \ No newline at end of file +print(f"E,D ---> {issubclass(E,D)=} {issubclass(E,C)=}") \ No newline at end of file diff --git a/tests/test.metaclass.002.py b/tests/test.metaclass.002.py new file mode 100755 index 00000000..b90d885e --- /dev/null +++ b/tests/test.metaclass.002.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2022 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. + +class A(): + test = True + def __new__(cls, *args, **kwargs): + print(f"New: {args=} {kwargs=} {cls=}") + if kwargs.get('mod'): + return super().__new__(A_Mod) + return super().__new__(cls) + + def __init__(self, *args, **kwargs): + print(f"Init: {args=} {kwargs=}") + +class A_Mod(A): + test = False + def __init__(self, *args, **kwargs): + print(f"Init: Mod {args=} {kwargs=}") + +a = A(1,2,3,aa=1,bb=2,cc=3) +print(f"A ---> {a=} {a.test=}") +b = A(1,2,3,aa=1,bb=2,cc=3,mod=4) +print(f"B ---> {b=} {b.test=}") + +def test(x): + print(f"Test {x=}") + return x == 5 + +print(f"{any(test(x) for x in range(10))=}") \ No newline at end of file diff --git a/tests/timeit/.gitignore b/tests/timeit/.gitignore new file mode 100644 index 00000000..d1a9d95b --- /dev/null +++ b/tests/timeit/.gitignore @@ -0,0 +1 @@ +wcwidth \ No newline at end of file diff --git a/tests/timeit/00.template.py b/tests/timeit/00.template.py new file mode 100644 index 00000000..b4b6103b --- /dev/null +++ b/tests/timeit/00.template.py @@ -0,0 +1,88 @@ +#!/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 + +sys.path.append(os.path.join(sys.path[0],'../..')) +sys.path.append(os.path.join(sys.path[0],'.')) +import TermTk as ttk + + +def test1(): + return 1 +def test2(): + return 1 +def test3(): + return 1 +def test4(): + return 1 +def test5(): + return 1 +def test6(): + return 1 +def test7(): + return 1 +def test8(): + return 1 +def test9(): + return 1 +def test10(): + return 1 +def test11(): + return 1 +def test12(): + return 1 + +loop = 100 + +result = timeit.timeit('test1()', globals=globals(), number=loop) +print(f"1 {result / loop:.10f} - {result / loop} {test1()}") +result = timeit.timeit('test2()', globals=globals(), number=loop) +print(f"2 {result / loop:.10f} - {result / loop} {test2()}") +result = timeit.timeit('test3()', globals=globals(), number=loop) +print(f"3 {result / loop:.10f} - {result / loop} {test3()}") +result = timeit.timeit('test4()', globals=globals(), number=loop) +print(f"4 {result / loop:.10f} - {result / loop} {test4()}") +result = timeit.timeit('test5()', globals=globals(), number=loop) +print(f"5 {result / loop:.10f} - {result / loop} {test5()}") +result = timeit.timeit('test6()', globals=globals(), number=loop) +print(f"6 {result / loop:.10f} - {result / loop} {test6()}") +result = timeit.timeit('test7()', globals=globals(), number=loop) +print(f"7 {result / loop:.10f} - {result / loop} {test7()}") +result = timeit.timeit('test8()', globals=globals(), number=loop) +print(f"8 {result / loop:.10f} - {result / loop} {test8()}") +result = timeit.timeit('test9()', globals=globals(), number=loop) +print(f"9 {result / loop:.10f} - {result / loop} {test9()}") +result = timeit.timeit('test10()', globals=globals(), number=loop) +print(f"10 {result / loop:.10f} - {result / loop} {test10()}") +result = timeit.timeit('test11()', globals=globals(), number=loop) +print(f"11 {result / loop:.10f} - {result / loop} {test11()}") +result = timeit.timeit('test12()', globals=globals(), number=loop) +print(f"12 {result / loop:.10f} - {result / loop} {test12()}") + + + diff --git a/tests/timeit/05.wcwidth.bisearch.py b/tests/timeit/05.wcwidth.bisearch.py index a3677f3b..e2962c85 100644 --- a/tests/timeit/05.wcwidth.bisearch.py +++ b/tests/timeit/05.wcwidth.bisearch.py @@ -203,6 +203,20 @@ def test11(): sum([unicodedata.east_asian_width(ch) == 'W' for ch in cstr]) - sum([unicodedata.category(ch) in ('Me','Mn') for ch in cstr]) ) +def test12(): + retTxt = [] + retCol = [] + for i,ch in enumerate(cstr): + if unicodedata.east_asian_width(ch) == 'W': + retTxt += (ch,'') + retCol += (ch,ch) + if unicodedata.category(ch) in ('Me','Mn'): + retTxt[-1]+=ch + else: + retTxt.append(ch) + retCol.append(ch) + return (len(retTxt), len(retCol)) + loop = 100 @@ -222,6 +236,10 @@ result = timeit.timeit('test8()', globals=globals(), number=loop) print(f"8 {result / loop:.10f} - {result / loop} {test8()}") result = timeit.timeit('test9()', globals=globals(), number=loop) print(f"9 {result / loop:.10f} - {result / loop} {test9()}") +result = timeit.timeit('test12()', globals=globals(), number=loop) +print(f"12 {result / loop:.10f} - {result / loop} {test12()}") + + result = timeit.timeit('test3()', globals=globals(), number=loop) print(f"3w {result / loop:.10f} - {result / loop} {test3()}") diff --git a/tests/timeit/06.functionPointer.py b/tests/timeit/06.functionPointer.py new file mode 100644 index 00000000..20e5d894 --- /dev/null +++ b/tests/timeit/06.functionPointer.py @@ -0,0 +1,143 @@ +#!/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 + +sys.path.append(os.path.join(sys.path[0],'../..')) +sys.path.append(os.path.join(sys.path[0],'.')) +import TermTk as ttk + +class A(): + def test(self): + return 1 + +class B(A): + def test(self): + return 2 + +class C(B): + def test(self): + return 3 + +class D(): + __slots__ = ('test') + def __init__(self, sw=True): + if sw: + self.test = self._testA + else: + self.test = self._testB + def _testA(self): + return 11 + def _testB(self): + return 12 + +class E(): + __slots__ = ('_sw') + def __init__(self, sw=True): + self._sw = sw + def test(self): + if self._sw: + return 21 + else: + return 22 + +class F(): + __slots__ = ('_sw') + def __init__(self, sw=True): + self._sw = sw + def test(self): + if self._sw: + return self._testA() + else: + return self._testB() + def _testA(self): + return 31 + def _testB(self): + return 32 + +a = A() +b = B() +c = C() +da = D(sw=True) +db = D(sw=False) +ea = E(sw=True) +eb = E(sw=False) +fa = F(sw=True) +fb = F(sw=False) + +def test1(): + return a.test() +def test2(): + return b.test() +def test3(): + return c.test() +def test4(): + return da.test() +def test5(): + return db.test() +def test6(): + return ea.test() +def test7(): + return eb.test() +def test8(): + return fa.test() +def test9(): + return fb.test() + +def test10(): return None +def test11(): return None +def test12(): return None + +loop = 100000 + +result = timeit.timeit('test1()', globals=globals(), number=loop) +print(f"1a {result / loop:.10f} - {result / loop} {test1()}") +result = timeit.timeit('test2()', globals=globals(), number=loop) +print(f"2b {result / loop:.10f} - {result / loop} {test2()}") +result = timeit.timeit('test3()', globals=globals(), number=loop) +print(f"3c {result / loop:.10f} - {result / loop} {test3()}") +result = timeit.timeit('test4()', globals=globals(), number=loop) +print(f"4da {result / loop:.10f} - {result / loop} {test4()}") +result = timeit.timeit('test5()', globals=globals(), number=loop) +print(f"5db {result / loop:.10f} - {result / loop} {test5()}") +result = timeit.timeit('test6()', globals=globals(), number=loop) +print(f"6ea {result / loop:.10f} - {result / loop} {test6()}") +result = timeit.timeit('test7()', globals=globals(), number=loop) +print(f"7eb {result / loop:.10f} - {result / loop} {test7()}") +result = timeit.timeit('test8()', globals=globals(), number=loop) +print(f"8fa {result / loop:.10f} - {result / loop} {test8()}") +result = timeit.timeit('test9()', globals=globals(), number=loop) +print(f"9fb {result / loop:.10f} - {result / loop} {test9()}") +result = timeit.timeit('test10()', globals=globals(), number=loop) +print(f"10 {result / loop:.10f} - {result / loop} {test10()}") +result = timeit.timeit('test11()', globals=globals(), number=loop) +print(f"11 {result / loop:.10f} - {result / loop} {test11()}") +result = timeit.timeit('test12()', globals=globals(), number=loop) +print(f"12 {result / loop:.10f} - {result / loop} {test12()}") + + + diff --git a/tools/check.import.sh b/tools/check.import.sh index 966188c6..f0b68e4a 100755 --- a/tools/check.import.sh +++ b/tools/check.import.sh @@ -29,8 +29,9 @@ __check(){ -e "ttk.py:import platform" \ -e "clipboard.py:import importlib.util" \ -e "filebuffer.py:import threading" \ - -e "texedit.py:from math import log10, ceil" - + -e "texedit.py:from math import log10, ceil" \ + -e "canvas.py:import unicodedata" \ + -e "string.py:import unicodedata" } ; if __check ; then diff --git a/tools/gen.utf8.sizeMap.py b/tools/gen.utf8.sizeMap.py new file mode 100644 index 00000000..46fd2454 --- /dev/null +++ b/tools/gen.utf8.sizeMap.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2022 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 urllib.request +import re + +URI='http://www.unicode.org/Public/UCD/latest/ucd/DerivedAge.txt' + +response = urllib.request.urlopen(URI) + +# Matching: +# "FE24..FE26 ; 5.1 # [3] COMBINING MACRON LEFT HALF..COMBINING CONJOINING MACRON" +# "10190..1019B ; 5.1 # [12] ROMAN SEXTANS SIGN..ROMAN CENTURIAL SIGN" +rangeMatch = re.compile(r'^([0-9A-F]+)\.\.([0-9A-F]+) *; ([0-9\.]+) (#.*)$') + +# Matching: +# "A95F ; 5.1 # REJANG SECTION MARK" +# "1093F ; 5.1 # LYDIAN TRIANGULAR MARK" +singleMatch = re.compile(r'^([0-9A-F]+) *; ([0-9\.]+) (#.*)$') + + + +for line in response.readlines(): + # print(line.decode('utf-8')) + if m := rangeMatch.match(line.decode('utf-8')): + rfr = m.group(1) + rto = m.group(2) + rver = m.group(3) + rdesc = m.group(4) + print(f"{rver=} {rfr=} {rto=} {rdesc=}") + elif m := singleMatch.match(line.decode('utf-8')): + rm = m.group(1) + rver = m.group(2) + rdesc = m.group(3) + print(f"{rver=} {rm=} {rdesc=}")