chromatic-python 0.4.1__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chromatic_python-0.4.2/.gitattributes +20 -0
- chromatic_python-0.4.2/.gitignore +28 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/PKG-INFO +3 -4
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/_version.py +3 -3
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/color/core.py +68 -29
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/color/core.pyi +19 -33
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/data/__init__.py +3 -1
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/data/__init__.pyi +3 -1
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/data/userfont.py +2 -16
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/data/userfont.pyi +3 -1
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/image/_array.py +153 -131
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/image/_curses.py +6 -8
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic_python.egg-info/PKG-INFO +3 -4
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic_python.egg-info/SOURCES.txt +0 -1
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/pyproject.toml +12 -12
- chromatic_python-0.4.1/.gitattributes +0 -1
- chromatic_python-0.4.1/.gitignore +0 -6
- chromatic_python-0.4.1/LICENSE +0 -21
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/.github/workflows/publish.yml +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/README.md +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/banner.png +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/__init__.py +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/__init__.pyi +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/_typing.py +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/color/__init__.py +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/color/__init__.pyi +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/color/colorconv.py +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/color/iterators.py +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/color/palette.py +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/color/palette.pyi +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/data/_fetchers.py +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/data/butterfly.jpg +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/data/escher.png +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/data/fonts/consolas.ttf +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/data/fonts/vga437.ttf +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/data/goblin_virus.png +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/data/registry.json +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/data/userfont.schema.json +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/demo.py +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/image/__init__.py +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/image/__init__.pyi +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic/image/_glyph.py +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic_python.egg-info/dependency_links.txt +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic_python.egg-info/requires.txt +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic_python.egg-info/top_level.txt +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/requirements.txt +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/setup.cfg +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/tests/__init__.py +0 -0
- {chromatic_python-0.4.1 → chromatic_python-0.4.2}/tests/test_color_str.py +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# python sources
|
|
2
|
+
*.py text diff=python
|
|
3
|
+
*.pyi text diff=python
|
|
4
|
+
|
|
5
|
+
# documents
|
|
6
|
+
*.md text diff=markdown
|
|
7
|
+
*.txt text
|
|
8
|
+
|
|
9
|
+
# graphics
|
|
10
|
+
*.png binary
|
|
11
|
+
*.jpg binary
|
|
12
|
+
*.jpeg binary
|
|
13
|
+
*.ttf binary
|
|
14
|
+
*.ttc binary
|
|
15
|
+
|
|
16
|
+
# serialization
|
|
17
|
+
*.json text
|
|
18
|
+
*.toml text
|
|
19
|
+
*.yaml text
|
|
20
|
+
*.yml text
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# editor temp/backup files
|
|
2
|
+
*.bak
|
|
3
|
+
*.diff
|
|
4
|
+
.idea/
|
|
5
|
+
*.tmp
|
|
6
|
+
.vscode
|
|
7
|
+
|
|
8
|
+
# compiled source
|
|
9
|
+
*.dll
|
|
10
|
+
*.exe
|
|
11
|
+
*.o
|
|
12
|
+
*.o.d
|
|
13
|
+
*.py[ocd]
|
|
14
|
+
*.so
|
|
15
|
+
*.mod
|
|
16
|
+
|
|
17
|
+
# python files
|
|
18
|
+
build
|
|
19
|
+
build-*
|
|
20
|
+
dist
|
|
21
|
+
_version.py
|
|
22
|
+
doc/build
|
|
23
|
+
*.egg-info
|
|
24
|
+
venv/
|
|
25
|
+
|
|
26
|
+
# this project
|
|
27
|
+
userfont.json
|
|
28
|
+
tests/_*
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chromatic-python
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: ANSI art image processing and colored terminal text
|
|
5
5
|
Author: crypt0lith
|
|
6
|
-
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: repository, https://github.com/crypt0lith/chromatic.git
|
|
7
8
|
Keywords: ansi,ascii,art,font,image,terminal,parser
|
|
8
9
|
Classifier: Programming Language :: Python :: 3.12
|
|
9
10
|
Classifier: Operating System :: OS Independent
|
|
@@ -11,7 +12,6 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
11
12
|
Classifier: Typing :: Typed
|
|
12
13
|
Requires-Python: >=3.12
|
|
13
14
|
Description-Content-Type: text/markdown
|
|
14
|
-
License-File: LICENSE
|
|
15
15
|
Requires-Dist: numpy<2.6,>=1.25.2
|
|
16
16
|
Requires-Dist: pillow!=11.2.*,<12.0,>=10.1
|
|
17
17
|
Requires-Dist: networkx<4.0,>=3.0
|
|
@@ -21,7 +21,6 @@ Requires-Dist: scikit-image
|
|
|
21
21
|
Requires-Dist: scikit-learn
|
|
22
22
|
Requires-Dist: fonttools
|
|
23
23
|
Requires-Dist: lazy_loader
|
|
24
|
-
Dynamic: license-file
|
|
25
24
|
|
|
26
25
|

|
|
27
26
|
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.4.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 4,
|
|
31
|
+
__version__ = version = '0.4.2'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 4, 2)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g399b7735a'
|
|
@@ -280,15 +280,22 @@ class ansicolor4Bit(colorbytes):
|
|
|
280
280
|
"""ANSI 4-bit color format.
|
|
281
281
|
|
|
282
282
|
Supports 16 colors:
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
283
|
+
|
|
284
|
+
* 8 standard colors:
|
|
285
|
+
{0: black, 1: red, 2: green, 3: yellow, 4: blue, 5: magenta, 6: cyan, 7: white}
|
|
286
|
+
|
|
287
|
+
* 8 bright colors, each mapping to a standard color (bright = standard + 8).
|
|
286
288
|
|
|
287
289
|
Color codes use escape sequences of the form:
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
|
|
291
|
+
* `CSI 30–37 m` for standard foreground colors.
|
|
292
|
+
|
|
293
|
+
* `CSI 40–47 m` for standard background colors.
|
|
294
|
+
|
|
295
|
+
* `CSI 90–97 m` for bright foreground colors.
|
|
296
|
+
|
|
297
|
+
* `CSI 100–107 m` for bright background colors.
|
|
298
|
+
|
|
292
299
|
Where `CSI` (Control Sequence Introducer) is `ESC[`.
|
|
293
300
|
|
|
294
301
|
Examples
|
|
@@ -310,13 +317,19 @@ class ansicolor8Bit(colorbytes):
|
|
|
310
317
|
"""ANSI 8-Bit color format.
|
|
311
318
|
|
|
312
319
|
Supports 256 colors, mapped to the following value ranges:
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
320
|
+
|
|
321
|
+
* (0, 15): Corresponds to ANSI 4-bit colors.
|
|
322
|
+
|
|
323
|
+
* (16, 231): Represents a 6x6x6 RGB color cube.
|
|
324
|
+
|
|
325
|
+
* (232, 255): Greyscale colors, from black to white.
|
|
316
326
|
|
|
317
327
|
Color codes use escape sequences of the form:
|
|
318
|
-
|
|
319
|
-
|
|
328
|
+
|
|
329
|
+
* `CSI 38;5;(n) m` for foreground colors.
|
|
330
|
+
|
|
331
|
+
* `CSI 48;5;(n) m` for background colors.
|
|
332
|
+
|
|
320
333
|
Where `CSI` (Control Sequence Introducer) is `ESC[` and `n` is an unsigned 8-bit integer.
|
|
321
334
|
|
|
322
335
|
Examples
|
|
@@ -340,8 +353,11 @@ class ansicolor24Bit(colorbytes):
|
|
|
340
353
|
Supports all colors in the RGB color space (16,777,216 total).
|
|
341
354
|
|
|
342
355
|
Color codes use escape sequences of the form:
|
|
343
|
-
|
|
344
|
-
|
|
356
|
+
|
|
357
|
+
* `CSI 38;2;(r);(g);(b) m` for foreground colors.
|
|
358
|
+
|
|
359
|
+
* `CSI 48;2;(r);(g);(b) m` for background colors.
|
|
360
|
+
|
|
345
361
|
Where `CSI` (Control Sequence Introducer) is `ESC[` and `r,g,b` are unsigned 8-bit integers.
|
|
346
362
|
|
|
347
363
|
Examples
|
|
@@ -523,10 +539,11 @@ def rgb2ansi_escape(fmt, mode, rgb):
|
|
|
523
539
|
class Color(int):
|
|
524
540
|
"""
|
|
525
541
|
Color([x]) -> color
|
|
542
|
+
|
|
526
543
|
Color(x, base=10) -> color
|
|
527
544
|
|
|
528
|
-
|
|
529
|
-
|
|
545
|
+
Convert a number or string into a color, or return Color(0) if no arguments are given.
|
|
546
|
+
Accepts the same arguments as int, but the value must be in range 0,0xFFFFFF (incl).
|
|
530
547
|
"""
|
|
531
548
|
|
|
532
549
|
def __new__(cls, *args, **kwargs):
|
|
@@ -553,7 +570,7 @@ class Color(int):
|
|
|
553
570
|
|
|
554
571
|
|
|
555
572
|
def randcolor():
|
|
556
|
-
"""Return a random color as a Color object
|
|
573
|
+
"""Return a random color as a `Color` object"""
|
|
557
574
|
return Color.from_bytes(random.randbytes(3))
|
|
558
575
|
|
|
559
576
|
|
|
@@ -620,7 +637,9 @@ class SgrParamBuffer[_T]:
|
|
|
620
637
|
def _get_sgr_nums(__x: bytes) -> list[int]:
|
|
621
638
|
"""Return a list of integers from a bytestring of ANSI SGR parameters.
|
|
622
639
|
|
|
623
|
-
Roughly, bitwise equivalent to
|
|
640
|
+
Roughly, bitwise equivalent to:
|
|
641
|
+
|
|
642
|
+
list(map(int, bytes().split(b';')))
|
|
624
643
|
"""
|
|
625
644
|
if __x.isdigit():
|
|
626
645
|
return [int(__x)]
|
|
@@ -654,10 +673,9 @@ def _get_sgr_nums(__x: bytes) -> list[int]:
|
|
|
654
673
|
return res
|
|
655
674
|
|
|
656
675
|
|
|
657
|
-
def _iter_normalized_sgr[_T: (
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
)](__iter: bytes | bytearray | Iterable[_T]) -> Iterator[int | AnsiColorFormat]:
|
|
676
|
+
def _iter_normalized_sgr[_T: (Buffer, SupportsInt)](
|
|
677
|
+
__iter: bytes | bytearray | Iterable[_T],
|
|
678
|
+
) -> Iterator[int | AnsiColorFormat]:
|
|
661
679
|
if _issubclass(type(__iter), (bytes, bytearray)):
|
|
662
680
|
__iter = __iter.split(b';')
|
|
663
681
|
elt: object | Any
|
|
@@ -1019,9 +1037,10 @@ def _colorstr[_T](
|
|
|
1019
1037
|
|
|
1020
1038
|
|
|
1021
1039
|
class _IntFloatMixin:
|
|
1022
|
-
"""Mixin for ColorStr -> int
|
|
1040
|
+
"""Mixin for `ColorStr` -> `int`/`float` conversion
|
|
1023
1041
|
|
|
1024
|
-
Notes
|
|
1042
|
+
Notes
|
|
1043
|
+
-----
|
|
1025
1044
|
If supplying 'base' to `int`, CPython ignores `nb_int` due to `PyUnicode_Check`.
|
|
1026
1045
|
Use `ColorStr.base_str` directly in that case.
|
|
1027
1046
|
"""
|
|
@@ -1084,12 +1103,13 @@ class ColorStr(str, _IntFloatMixin):
|
|
|
1084
1103
|
|
|
1085
1104
|
def recolor(self, *args, **kwargs):
|
|
1086
1105
|
"""ColorStr.recolor(self, __value, *, absolute=False) -> ColorStr
|
|
1106
|
+
|
|
1087
1107
|
ColorStr.recolor(self, *, fg=None, bg=None, absolute=False) -> ColorStr
|
|
1088
1108
|
|
|
1089
1109
|
Return a copy of self with a new color spec.
|
|
1090
1110
|
|
|
1091
1111
|
If no arguments are given, returns self unchanged.
|
|
1092
|
-
If __value is given and a ColorStr
|
|
1112
|
+
If __value is given and a `ColorStr`, return self with the colors of __value.
|
|
1093
1113
|
Else, use keyword arguments { 'fg', 'bg' } for colors.
|
|
1094
1114
|
Any other mix of arguments will fail outright,
|
|
1095
1115
|
since __value along with { fg=... | bg=... } is ambiguous which to use for colors.
|
|
@@ -1575,7 +1595,7 @@ def _collect_masks(
|
|
|
1575
1595
|
return masks
|
|
1576
1596
|
|
|
1577
1597
|
|
|
1578
|
-
class color_chain:
|
|
1598
|
+
class color_chain(Sequence[tuple[SgrSequence, str]]):
|
|
1579
1599
|
@staticmethod
|
|
1580
1600
|
def _is_mask_seq(obj):
|
|
1581
1601
|
if isinstance(obj, Sequence):
|
|
@@ -1615,6 +1635,7 @@ class color_chain:
|
|
|
1615
1635
|
raise TypeError
|
|
1616
1636
|
|
|
1617
1637
|
def shrink(self):
|
|
1638
|
+
"""Return a copy where SGR sequences are joined for spans of empty string parts"""
|
|
1618
1639
|
if self:
|
|
1619
1640
|
maxlen = len(self._masks)
|
|
1620
1641
|
it = enumerate(self._masks)
|
|
@@ -1622,9 +1643,10 @@ class color_chain:
|
|
|
1622
1643
|
while True:
|
|
1623
1644
|
try:
|
|
1624
1645
|
idx, (sgr, s) = next(it)
|
|
1646
|
+
sgr = sgr.copy()
|
|
1625
1647
|
while idx + 1 < maxlen and not s:
|
|
1626
1648
|
idx, xs = next(it)
|
|
1627
|
-
sgr += xs[0]; s = xs[1]
|
|
1649
|
+
sgr += xs[0]; s = xs[1] # fmt: skip
|
|
1628
1650
|
else:
|
|
1629
1651
|
out.append((sgr, s))
|
|
1630
1652
|
except StopIteration:
|
|
@@ -1633,6 +1655,18 @@ class color_chain:
|
|
|
1633
1655
|
out = self.masks
|
|
1634
1656
|
return self._from_masks_unchecked(out, ansi_type=self._ansi_type)
|
|
1635
1657
|
|
|
1658
|
+
def merge(self, *other):
|
|
1659
|
+
if not other:
|
|
1660
|
+
return self
|
|
1661
|
+
masks = self.masks
|
|
1662
|
+
for x in other:
|
|
1663
|
+
for sgr, s in x:
|
|
1664
|
+
if not masks[-1][-1]:
|
|
1665
|
+
masks[-1] = masks[-1][0] + sgr, s
|
|
1666
|
+
else:
|
|
1667
|
+
masks.append((sgr, s))
|
|
1668
|
+
return self._from_masks_unchecked(masks, ansi_type=self._ansi_type)
|
|
1669
|
+
|
|
1636
1670
|
def __add__(self, other):
|
|
1637
1671
|
try:
|
|
1638
1672
|
masks = _collect_masks(
|
|
@@ -1665,6 +1699,11 @@ class color_chain:
|
|
|
1665
1699
|
def __len__(self):
|
|
1666
1700
|
return len(self._masks)
|
|
1667
1701
|
|
|
1702
|
+
def __or__(self, other):
|
|
1703
|
+
if _issubclass(other.__class__, self.__class__):
|
|
1704
|
+
return self.merge(other)
|
|
1705
|
+
return NotImplemented
|
|
1706
|
+
|
|
1668
1707
|
def __radd__(self, other):
|
|
1669
1708
|
if isinstance(other, ColorStr):
|
|
1670
1709
|
return self._from_masks_unchecked(
|
|
@@ -1685,8 +1724,8 @@ class color_chain:
|
|
|
1685
1724
|
return NotImplemented
|
|
1686
1725
|
|
|
1687
1726
|
def __repr__(self):
|
|
1688
|
-
return "{.__name__}({})".format(
|
|
1689
|
-
|
|
1727
|
+
return "{.__class__.__name__}({})".format(
|
|
1728
|
+
self,
|
|
1690
1729
|
', '.join(
|
|
1691
1730
|
[
|
|
1692
1731
|
repr([f"{sgr}{s}" for sgr, s in self._masks]),
|
|
@@ -93,10 +93,7 @@ class Color(int):
|
|
|
93
93
|
class colorbytes(bytes):
|
|
94
94
|
@classmethod
|
|
95
95
|
@overload
|
|
96
|
-
def from_rgb[_T, _VT: (
|
|
97
|
-
SupportsInt,
|
|
98
|
-
RGBVectorLike,
|
|
99
|
-
)](
|
|
96
|
+
def from_rgb[_T, _VT: (SupportsInt, RGBVectorLike)](
|
|
100
97
|
cls: type[_T],
|
|
101
98
|
__rgb: (
|
|
102
99
|
tuple[ColorDictKeys, _VT] | Mapping[L['fg'], _VT] | Mapping[L['bg'], _VT]
|
|
@@ -104,16 +101,13 @@ class colorbytes(bytes):
|
|
|
104
101
|
) -> _T: ...
|
|
105
102
|
@classmethod
|
|
106
103
|
@overload
|
|
107
|
-
def from_rgb[_T, _VT: (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
)](cls: type[_T], __rgb: tuple[str, _VT] | Mapping[str, _VT]) -> _T: ...
|
|
104
|
+
def from_rgb[_T, _VT: (SupportsInt, RGBVectorLike)](
|
|
105
|
+
cls: type[_T], __rgb: tuple[str, _VT] | Mapping[str, _VT]
|
|
106
|
+
) -> _T: ...
|
|
111
107
|
@overload
|
|
112
|
-
def __new__[_T: (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
ansicolor24Bit,
|
|
116
|
-
)](cls: AnsiColorType, __ansi: bytes | AnsiColorFormat) -> Self: ...
|
|
108
|
+
def __new__[_T: (ansicolor4Bit, ansicolor8Bit, ansicolor24Bit)](
|
|
109
|
+
cls: AnsiColorType, __ansi: bytes | AnsiColorFormat
|
|
110
|
+
) -> Self: ...
|
|
117
111
|
@overload
|
|
118
112
|
def __new__(cls, __ansi: bytes | _AnsiColor_co) -> AnsiColorFormat: ...
|
|
119
113
|
def to_param_buffer(self) -> SgrParamBuffer[Self]: ...
|
|
@@ -279,7 +273,7 @@ class _ColorChainKwargs(TypedDict, total=False):
|
|
|
279
273
|
fg: Color | int | Int3Tuple
|
|
280
274
|
bg: Color | int | Int3Tuple
|
|
281
275
|
|
|
282
|
-
class color_chain:
|
|
276
|
+
class color_chain(Sequence[tuple[SgrSequence, str]]):
|
|
283
277
|
@staticmethod
|
|
284
278
|
def _is_mask_seq(obj: object) -> TypeGuard[Sequence[tuple[SgrSequence, str]]]: ...
|
|
285
279
|
@classmethod
|
|
@@ -293,12 +287,10 @@ class color_chain:
|
|
|
293
287
|
ansi_type: type[AnsiColorFormat] = None,
|
|
294
288
|
) -> Self: ...
|
|
295
289
|
def shrink(self) -> Self: ...
|
|
296
|
-
def
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
str,
|
|
301
|
-
)](self, other: _T) -> color_chain: ...
|
|
290
|
+
def merge(self, *other: Iterable[tuple[SgrSequence, str]]) -> Self: ...
|
|
291
|
+
def __add__[_T: (color_chain, SgrSequence, ColorStr, str)](
|
|
292
|
+
self, other: _T
|
|
293
|
+
) -> color_chain: ...
|
|
302
294
|
def __bool__(self) -> bool: ...
|
|
303
295
|
def __call__(self, __obj=None) -> str: ...
|
|
304
296
|
@overload
|
|
@@ -309,12 +301,10 @@ class color_chain:
|
|
|
309
301
|
self, __sgr: Iterable[_T] = ..., *, ansi_type: AnsiColorParam = ...
|
|
310
302
|
) -> None: ...
|
|
311
303
|
def __len__(self) -> int: ...
|
|
312
|
-
def
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
SgrSequence,
|
|
317
|
-
)](self, other: _T) -> color_chain: ...
|
|
304
|
+
def __or__(self, other: color_chain) -> Self: ...
|
|
305
|
+
def __radd__[_T: (color_chain, ColorStr, str, SgrSequence)](
|
|
306
|
+
self, other: _T
|
|
307
|
+
) -> color_chain: ...
|
|
318
308
|
|
|
319
309
|
_ansi_type: type[AnsiColorFormat] | None
|
|
320
310
|
_masks: list[tuple[SgrSequence, str]]
|
|
@@ -416,13 +406,11 @@ class SgrParamBuffer[
|
|
|
416
406
|
@property
|
|
417
407
|
def value(self) -> _T: ...
|
|
418
408
|
__slots__ = ('_bytes', '_is_color', '_is_reset', '_value')
|
|
419
|
-
# _value: _T
|
|
420
409
|
|
|
421
410
|
class SgrSequence(MutableSequence[SgrParamBuffer]):
|
|
422
411
|
def _update_colors(self): ...
|
|
423
412
|
def is_color(self) -> bool: ...
|
|
424
413
|
def is_reset(self) -> bool: ...
|
|
425
|
-
def has_bright_colors(self) -> bool: ...
|
|
426
414
|
def values(self) -> Iterator[bytes | AnsiColorFormat]: ...
|
|
427
415
|
def copy(self) -> Self: ...
|
|
428
416
|
def insert(self, __index: SupportsIndex, __value: bytes | SgrParamBuffer): ...
|
|
@@ -455,11 +443,9 @@ class SgrSequence(MutableSequence[SgrParamBuffer]):
|
|
|
455
443
|
def __delitem__(self, __index: slice): ...
|
|
456
444
|
@overload
|
|
457
445
|
def __delitem__(self, __index: SupportsIndex): ...
|
|
458
|
-
def __init__[_T: (
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
SgrParamBuffer,
|
|
462
|
-
)](self, __iter: Iterable[_T] = ...): ...
|
|
446
|
+
def __init__[_T: (int, Buffer, SgrParamBuffer)](
|
|
447
|
+
self, __iter: Iterable[_T] = ...
|
|
448
|
+
): ...
|
|
463
449
|
def __iter__(self) -> Iterator[SgrParamBuffer]: ...
|
|
464
450
|
|
|
465
451
|
__slots__ = '_rgb_dict', '_sgr_params'
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
__all__ = [
|
|
2
|
+
'DEFAULT_FONT',
|
|
2
3
|
'UserFont',
|
|
4
|
+
'VGA437',
|
|
3
5
|
'butterfly',
|
|
4
6
|
'escher',
|
|
5
7
|
'goblin_virus',
|
|
@@ -14,7 +16,7 @@ if "CHROMATIC_DATADIR" not in os.environ:
|
|
|
14
16
|
os.environ["CHROMATIC_DATADIR"] = osp.dirname(__file__)
|
|
15
17
|
|
|
16
18
|
from ._fetchers import _load
|
|
17
|
-
from .userfont import userfont, register_userfont, UserFont
|
|
19
|
+
from .userfont import userfont, register_userfont, UserFont, VGA437, DEFAULT_FONT
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
def __dir__():
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
__all__ = [
|
|
2
|
+
'DEFAULT_FONT',
|
|
2
3
|
'UserFont',
|
|
4
|
+
'VGA437',
|
|
3
5
|
'butterfly',
|
|
4
6
|
'escher',
|
|
5
7
|
'goblin_virus',
|
|
@@ -9,7 +11,7 @@ __all__ = [
|
|
|
9
11
|
|
|
10
12
|
import PIL.Image
|
|
11
13
|
|
|
12
|
-
from .userfont import
|
|
14
|
+
from .userfont import DEFAULT_FONT, UserFont, VGA437, register_userfont, userfont
|
|
13
15
|
|
|
14
16
|
def butterfly() -> PIL.Image.ImageFile.ImageFile: ...
|
|
15
17
|
def escher() -> PIL.Image.ImageFile.ImageFile: ...
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
3
|
import os.path as osp
|
|
4
|
-
from dataclasses import
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
5
|
from types import MappingProxyType
|
|
6
6
|
from typing import AnyStr, TYPE_CHECKING
|
|
7
7
|
|
|
@@ -38,21 +38,6 @@ class UserFont:
|
|
|
38
38
|
osp.join(os.environ["CHROMATIC_DATADIR"], self.font), strict=True
|
|
39
39
|
)
|
|
40
40
|
|
|
41
|
-
def __repr__(self):
|
|
42
|
-
cls = type(self)
|
|
43
|
-
inst_fields = {}
|
|
44
|
-
for f in fields(cls): # noqa
|
|
45
|
-
v = getattr(self, f.name)
|
|
46
|
-
if f.default is MISSING or v != f.default:
|
|
47
|
-
inst_fields[f.name] = v
|
|
48
|
-
try:
|
|
49
|
-
inst_fields["font"] = os.fspath(self)
|
|
50
|
-
except (OSError, FileNotFoundError):
|
|
51
|
-
pass
|
|
52
|
-
return f"{cls.__name__}(%s)" % ', '.join(
|
|
53
|
-
f"{k}={v!r}" for k, v in inst_fields.items()
|
|
54
|
-
)
|
|
55
|
-
|
|
56
41
|
def to_truetype(self):
|
|
57
42
|
from PIL.ImageFont import truetype
|
|
58
43
|
|
|
@@ -151,3 +136,4 @@ def _init_userfonts():
|
|
|
151
136
|
|
|
152
137
|
|
|
153
138
|
_init_userfonts()
|
|
139
|
+
DEFAULT_FONT = VGA437 = userfont['vga437']
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from os import PathLike
|
|
2
|
-
from typing import AnyStr
|
|
2
|
+
from typing import AnyStr, Final
|
|
3
3
|
from types import MappingProxyType
|
|
4
4
|
|
|
5
5
|
from PIL.ImageFont import FreeTypeFont
|
|
@@ -17,6 +17,8 @@ class UserFont:
|
|
|
17
17
|
def to_truetype(self) -> FreeTypeFont: ...
|
|
18
18
|
|
|
19
19
|
userfont: MappingProxyType[str, UserFont]
|
|
20
|
+
VGA437: Final[UserFont] = userfont['vga437']
|
|
21
|
+
DEFAULT_FONT = VGA437
|
|
20
22
|
|
|
21
23
|
def register_userfont(
|
|
22
24
|
fp: AnyStr | PathLike[AnyStr],
|
|
@@ -56,7 +56,7 @@ from ..color.core import (
|
|
|
56
56
|
sgr_pattern,
|
|
57
57
|
)
|
|
58
58
|
from ..color.palette import rgb_dispatch
|
|
59
|
-
from ..data import UserFont, userfont
|
|
59
|
+
from ..data import UserFont, userfont, VGA437
|
|
60
60
|
|
|
61
61
|
if TYPE_CHECKING:
|
|
62
62
|
from _typeshed import SupportsRead
|
|
@@ -509,7 +509,7 @@ def img2ascii(
|
|
|
509
509
|
|
|
510
510
|
def img2ascii(
|
|
511
511
|
__img: RGBImageLike | PathLike[str] | str,
|
|
512
|
-
__font: FontArgType =
|
|
512
|
+
__font: FontArgType = VGA437,
|
|
513
513
|
factor: int = 200,
|
|
514
514
|
char_set: Iterable[str] = None,
|
|
515
515
|
sort_glyphs: bool | type[reversed] = True,
|
|
@@ -566,9 +566,7 @@ def img2ascii(
|
|
|
566
566
|
if char_set is None:
|
|
567
567
|
from ._curses import ascii_printable, cp437_printable
|
|
568
568
|
|
|
569
|
-
chars_getter = dict.get(
|
|
570
|
-
{userfont['vga437']: cp437_printable}, __font, ascii_printable
|
|
571
|
-
)
|
|
569
|
+
chars_getter = dict.get({VGA437: cp437_printable}, __font, ascii_printable)
|
|
572
570
|
char_set = shuffle_char_set(chars_getter())
|
|
573
571
|
elif type(char_set) is not str:
|
|
574
572
|
char_set = ''.join(char_set)
|
|
@@ -587,7 +585,7 @@ def img2ascii(
|
|
|
587
585
|
@rgb_dispatch('bg')
|
|
588
586
|
def img2ansi(
|
|
589
587
|
__img: RGBImageLike | PathLike[str] | str,
|
|
590
|
-
__font: FontArgType =
|
|
588
|
+
__font: FontArgType = VGA437,
|
|
591
589
|
factor: int = 200,
|
|
592
590
|
char_set: Iterable[str] = None,
|
|
593
591
|
ansi_type: AnsiColorParam = DEFAULT_ANSI,
|
|
@@ -685,7 +683,7 @@ def img2ansi(
|
|
|
685
683
|
@rgb_dispatch('fg', 'bg')
|
|
686
684
|
def ascii2img(
|
|
687
685
|
__s: str,
|
|
688
|
-
font: FontArgType =
|
|
686
|
+
font: FontArgType = VGA437,
|
|
689
687
|
font_size=16,
|
|
690
688
|
*,
|
|
691
689
|
fg: Int3Tuple | str = (0, 0, 0),
|
|
@@ -724,7 +722,7 @@ def ascii2img(
|
|
|
724
722
|
n_rows, n_cols = map(len, (lines, lines[0]))
|
|
725
723
|
cw, ch = _get_bbox_shape(font)
|
|
726
724
|
iw, ih = (int(i * j) for i, j in zip((cw, ch), (n_cols, n_rows)))
|
|
727
|
-
|
|
725
|
+
r, g, b = tuple(map(int, bg))
|
|
728
726
|
img = Image.new('RGB', (iw, ih), (r, g, b))
|
|
729
727
|
draw = ImageDraw.Draw(img)
|
|
730
728
|
y_offset = 0
|
|
@@ -737,7 +735,7 @@ def ascii2img(
|
|
|
737
735
|
@rgb_dispatch('fg_default', 'bg_default')
|
|
738
736
|
def ansi2img(
|
|
739
737
|
__ansi_array: list[list[ColorStr]],
|
|
740
|
-
font: FontArgType =
|
|
738
|
+
font: FontArgType = VGA437,
|
|
741
739
|
font_size=16,
|
|
742
740
|
*,
|
|
743
741
|
fg_default: Int3Tuple | TupleOf4[int] | str = (170, 170, 170),
|
|
@@ -802,7 +800,7 @@ def ansi2img(
|
|
|
802
800
|
else:
|
|
803
801
|
conv = lambda x: x
|
|
804
802
|
img = Image.new('RGB', (iw, ih), bg_default)
|
|
805
|
-
|
|
803
|
+
|
|
806
804
|
draw = ImageDraw.Draw(img)
|
|
807
805
|
y_offset = 0
|
|
808
806
|
for row in __ansi_array:
|
|
@@ -836,7 +834,7 @@ def ansi2img(
|
|
|
836
834
|
|
|
837
835
|
def ansify(
|
|
838
836
|
__img: RGBImageLike | PathLike[str] | str,
|
|
839
|
-
font: FontArgType =
|
|
837
|
+
font: FontArgType = VGA437,
|
|
840
838
|
font_size: int = 16,
|
|
841
839
|
*,
|
|
842
840
|
factor: int = 200,
|
|
@@ -903,59 +901,64 @@ def _is_csi_param(__c: str) -> TypeGuard[Literal[';'] | LiteralDigit]:
|
|
|
903
901
|
def cursor_or_sgr_pattern():
|
|
904
902
|
sgr_re = sgr_pattern().pattern.removeprefix(r'\x1b\[')
|
|
905
903
|
return re.compile(
|
|
906
|
-
|
|
904
|
+
r"(?:\x1b\[(?:(?P<cursor>\d*[A-G]|\d*(?:;\d*)?H)"
|
|
905
|
+
r"|(?P<carriage_return>\r)"
|
|
906
|
+
rf"|(?P<sgr>{sgr_re})))?(?P<text>[^\x1b]*)"
|
|
907
907
|
)
|
|
908
908
|
|
|
909
909
|
|
|
910
|
-
_compile = re.compile
|
|
911
|
-
_compile = lru_cache(maxsize=1)(_compile)
|
|
912
|
-
|
|
913
910
|
def _sub_bold_colors(lines: Iterable[str]) -> Iterator[str]:
|
|
914
911
|
"""Yield lines with bold foreground colors normalized to their ESC[9(n)m variants.
|
|
915
912
|
|
|
916
913
|
Previous colors are also forwarded at each SGR position, if they are not overridden.
|
|
917
914
|
"""
|
|
918
915
|
type TupleOf5[_T] = tuple[_T, _T, _T, _T, _T]
|
|
919
|
-
ansi_16c_fg_std = range(30, 38)
|
|
920
|
-
ansi_16c_fg_bold = range(90, 98)
|
|
921
916
|
|
|
922
917
|
def sub(m: re.Match):
|
|
923
918
|
nonlocal bold_bit, prev_colors
|
|
924
919
|
|
|
925
920
|
params: list[Int3Tuple | TupleOf5[int] | int] = []
|
|
926
921
|
nums = map(int, m[0].removeprefix('\x1b[').removesuffix('m').split(';'))
|
|
927
|
-
for
|
|
928
|
-
if
|
|
922
|
+
for n in nums:
|
|
923
|
+
if n in {38, 48}:
|
|
929
924
|
j = next(nums)
|
|
930
925
|
if j == 5:
|
|
931
|
-
extended_param = (
|
|
926
|
+
extended_param = (n, j, next(nums))
|
|
932
927
|
elif j == 2:
|
|
933
|
-
extended_param = (
|
|
928
|
+
extended_param = (n, j, *(next(nums) for _ in range(3)))
|
|
934
929
|
else:
|
|
935
930
|
raise ValueError("invalid ansi color")
|
|
936
931
|
params.append(extended_param)
|
|
937
932
|
continue
|
|
938
|
-
elif
|
|
933
|
+
elif n == 0:
|
|
939
934
|
bold_bit = False
|
|
940
935
|
prev_colors.clear()
|
|
941
|
-
elif
|
|
936
|
+
elif n == 1:
|
|
942
937
|
bold_bit = True
|
|
943
|
-
for k,
|
|
944
|
-
if
|
|
945
|
-
|
|
946
|
-
|
|
938
|
+
for k, values in prev_colors.items():
|
|
939
|
+
if k != 'fg':
|
|
940
|
+
continue
|
|
941
|
+
for idx, v in enumerate(values):
|
|
942
|
+
if 30 <= v <= 38:
|
|
943
|
+
prev_colors[k][idx] += 60
|
|
944
|
+
prev_colors.setdefault('fg', [97])
|
|
945
|
+
elif n == 22:
|
|
947
946
|
bold_bit = False
|
|
948
|
-
for k,
|
|
949
|
-
if
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
947
|
+
for k, values in prev_colors.items():
|
|
948
|
+
if k != 'fg':
|
|
949
|
+
continue
|
|
950
|
+
for idx, v in enumerate(values):
|
|
951
|
+
if 90 <= v <= 98:
|
|
952
|
+
prev_colors[k][idx] -= 60
|
|
953
|
+
prev_colors.setdefault('fg', [37])
|
|
954
|
+
elif bold_bit and 30 <= n <= 38:
|
|
955
|
+
n += 60
|
|
956
|
+
params.append(n)
|
|
954
957
|
sgr = SgrSequence(
|
|
955
|
-
|
|
958
|
+
n
|
|
956
959
|
for xs in [prev_colors.values(), params]
|
|
957
960
|
for x in xs
|
|
958
|
-
for
|
|
961
|
+
for n in ([x] if isinstance(x, int) else x)
|
|
959
962
|
)
|
|
960
963
|
for p in sgr:
|
|
961
964
|
if p.is_color():
|
|
@@ -965,13 +968,32 @@ def _sub_bold_colors(lines: Iterable[str]) -> Iterator[str]:
|
|
|
965
968
|
)
|
|
966
969
|
return f"{sgr}"
|
|
967
970
|
|
|
971
|
+
bold_bit = False
|
|
972
|
+
prev_colors: dict[str, list[int]] = {}
|
|
968
973
|
for line in lines:
|
|
969
|
-
bold_bit = False
|
|
970
|
-
prev_colors = {}
|
|
971
974
|
yield sgr_pattern().sub(sub, line)
|
|
972
975
|
|
|
973
976
|
|
|
974
|
-
def
|
|
977
|
+
def _utf8_bytes(cp: int) -> Iterator[int]:
|
|
978
|
+
"""yield the UTF-8 bytes of the ordinal value of a character"""
|
|
979
|
+
|
|
980
|
+
if cp <= 0x7F:
|
|
981
|
+
yield cp
|
|
982
|
+
elif cp <= 0x7FF:
|
|
983
|
+
yield 0b11000000 | (cp >> 6)
|
|
984
|
+
yield 0b10000000 | (cp & 0b00111111)
|
|
985
|
+
elif cp <= 0xFFFF:
|
|
986
|
+
yield 0b11100000 | (cp >> 12)
|
|
987
|
+
yield 0b10000000 | ((cp >> 6) & 0b00111111)
|
|
988
|
+
yield 0b10000000 | (cp & 0b00111111)
|
|
989
|
+
else:
|
|
990
|
+
yield 0b11110000 | (cp >> 18)
|
|
991
|
+
yield 0b10000000 | ((cp >> 12) & 0b00111111)
|
|
992
|
+
yield 0b10000000 | ((cp >> 6) & 0b00111111)
|
|
993
|
+
yield 0b10000000 | (cp & 0b00111111)
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
def reshape_ansi(__s: str, w: int, h: int) -> str:
|
|
975
997
|
def cursor() -> Generator[tuple[int, int], tuple[int, int], None]:
|
|
976
998
|
idx, total = 0, h * w
|
|
977
999
|
while idx < total:
|
|
@@ -981,87 +1003,101 @@ def reshape_ansi(__str: str, w: int, h: int) -> str:
|
|
|
981
1003
|
else:
|
|
982
1004
|
idx += 1
|
|
983
1005
|
|
|
984
|
-
def write_cell(
|
|
985
|
-
nonlocal
|
|
986
|
-
if
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
cur.send((
|
|
1006
|
+
def write_cell(ch: str):
|
|
1007
|
+
nonlocal y, x
|
|
1008
|
+
if ch == '\n':
|
|
1009
|
+
y = min(y + 1, h - 1)
|
|
1010
|
+
x = 0
|
|
1011
|
+
cur.send((y, x))
|
|
990
1012
|
return
|
|
991
|
-
elif arr[x][y] == '\x00':
|
|
992
|
-
arr[x][y] = content
|
|
993
1013
|
else:
|
|
994
|
-
arr[
|
|
995
|
-
|
|
996
|
-
|
|
1014
|
+
arr[y][x] = seq, ord(ch)
|
|
1015
|
+
y, x = next(cur)
|
|
1016
|
+
|
|
1017
|
+
cursor_or_sgr_finditer = cursor_or_sgr_pattern().finditer
|
|
997
1018
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1019
|
+
arr: list[list[tuple[SgrSequence | None, int]]] = [
|
|
1020
|
+
[(None, 0) for _ in range(w)] for _ in range(h)
|
|
1021
|
+
]
|
|
1001
1022
|
cur = cursor()
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
if '
|
|
1010
|
-
|
|
1023
|
+
seq = SgrSequence()
|
|
1024
|
+
y, x = next(cur)
|
|
1025
|
+
for line in _sub_bold_colors(__s.split('\n')):
|
|
1026
|
+
try:
|
|
1027
|
+
for m in cursor_or_sgr_finditer(line + '\n'):
|
|
1028
|
+
if cg := m['cursor']:
|
|
1029
|
+
param, code = cg[:-1], cg[-1]
|
|
1030
|
+
if code == 'H':
|
|
1031
|
+
if ';' in param:
|
|
1032
|
+
y, x = (int(i or 1) - 1 for i in param.partition(';')[::2])
|
|
1033
|
+
else:
|
|
1034
|
+
y = int(param or 1) - 1
|
|
1035
|
+
x = 0
|
|
1011
1036
|
else:
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
ansi_ws_suffix = _compile(ansi_ws_base + '$')
|
|
1043
|
-
|
|
1044
|
-
out_lines = []
|
|
1037
|
+
n = int(param or 1)
|
|
1038
|
+
match code:
|
|
1039
|
+
case 'A':
|
|
1040
|
+
y = max(0, y - n)
|
|
1041
|
+
case 'B':
|
|
1042
|
+
y = min(h - 1, y + n)
|
|
1043
|
+
case 'C':
|
|
1044
|
+
x = min(w - 1, x + n)
|
|
1045
|
+
case 'D':
|
|
1046
|
+
x = max(0, x - n)
|
|
1047
|
+
case 'E':
|
|
1048
|
+
y += n
|
|
1049
|
+
x = 0
|
|
1050
|
+
case 'F':
|
|
1051
|
+
y -= n
|
|
1052
|
+
x = 0
|
|
1053
|
+
case 'G':
|
|
1054
|
+
x = n - 1
|
|
1055
|
+
cur.send((y, x))
|
|
1056
|
+
elif m['carriage_return']:
|
|
1057
|
+
cur.send((y, 0))
|
|
1058
|
+
elif m['sgr']:
|
|
1059
|
+
seq = SgrSequence(m['sgr'].encode().removesuffix(b'm'))
|
|
1060
|
+
for c in m['text']:
|
|
1061
|
+
write_cell(c)
|
|
1062
|
+
except StopIteration:
|
|
1063
|
+
break
|
|
1064
|
+
|
|
1065
|
+
out = []
|
|
1066
|
+
prev: SgrSequence | None = None
|
|
1045
1067
|
for row in arr:
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1068
|
+
if not row:
|
|
1069
|
+
continue
|
|
1070
|
+
buf = bytearray()
|
|
1071
|
+
maxlen = len(row)
|
|
1072
|
+
indices = enumerate(row)
|
|
1073
|
+
while True:
|
|
1074
|
+
try:
|
|
1075
|
+
i, [sgr, b] = next(indices)
|
|
1076
|
+
j = i + 1
|
|
1077
|
+
if sgr and sgr != prev:
|
|
1078
|
+
buf.extend(bytes(sgr))
|
|
1079
|
+
prev = sgr
|
|
1080
|
+
buf.extend(_utf8_bytes(b))
|
|
1081
|
+
while j < maxlen and sgr == row[j][0]:
|
|
1082
|
+
j += 1
|
|
1083
|
+
for _ in range(i + 1, j):
|
|
1084
|
+
_, [_, b] = next(indices)
|
|
1085
|
+
buf.extend(_utf8_bytes(b))
|
|
1086
|
+
except StopIteration:
|
|
1087
|
+
break
|
|
1088
|
+
out.append(buf.decode())
|
|
1089
|
+
return '\n'.join(out).translate({0: ' '})
|
|
1054
1090
|
|
|
1055
1091
|
|
|
1056
1092
|
@lru_cache
|
|
1057
|
-
def to_sgr_array(
|
|
1093
|
+
def to_sgr_array(__s: str, ansi_type: AnsiColorParam = None):
|
|
1058
1094
|
ansi_typ = DEFAULT_ANSI if ansi_type is None else get_ansi_type(ansi_type)
|
|
1059
1095
|
new_cs = partial(ColorStr, ansi_type=ansi_typ, reset=False)
|
|
1060
|
-
|
|
1096
|
+
cursor_or_sgr_finditer = cursor_or_sgr_pattern().finditer
|
|
1061
1097
|
xs = []
|
|
1062
|
-
for line in _sub_bold_colors(
|
|
1098
|
+
for line in _sub_bold_colors(__s.split('\n')):
|
|
1063
1099
|
x = []
|
|
1064
|
-
for m in
|
|
1100
|
+
for m in cursor_or_sgr_finditer(line):
|
|
1065
1101
|
text = m["text"]
|
|
1066
1102
|
if m["sgr"]:
|
|
1067
1103
|
sgr = SgrSequence(map(int, m["sgr"].removesuffix('m').split(';')))
|
|
@@ -1077,7 +1113,7 @@ def to_sgr_array(__str: str, ansi_type: AnsiColorParam = None):
|
|
|
1077
1113
|
def render_ans(
|
|
1078
1114
|
__s: str,
|
|
1079
1115
|
shape: TupleOf2[int],
|
|
1080
|
-
font: FontArgType =
|
|
1116
|
+
font: FontArgType = VGA437,
|
|
1081
1117
|
font_size: int = 16,
|
|
1082
1118
|
*,
|
|
1083
1119
|
bg_default: Int3Tuple | TupleOf4[int] | str = (0, 0, 0),
|
|
@@ -1103,10 +1139,7 @@ def render_ans(
|
|
|
1103
1139
|
'auto' will determine background color dynamically.
|
|
1104
1140
|
"""
|
|
1105
1141
|
return ansi2img(
|
|
1106
|
-
to_sgr_array(reshape_ansi(__s, *shape)),
|
|
1107
|
-
font,
|
|
1108
|
-
font_size,
|
|
1109
|
-
bg_default=bg_default,
|
|
1142
|
+
to_sgr_array(reshape_ansi(__s, *shape)), font, font_size, bg_default=bg_default
|
|
1110
1143
|
)
|
|
1111
1144
|
|
|
1112
1145
|
|
|
@@ -1120,7 +1153,7 @@ def read_ans(__buf: SupportsRead[str] | TextIOWrapper[str]) -> str:
|
|
|
1120
1153
|
content = __buf.read().translate({0: ' '})
|
|
1121
1154
|
if ~(sauce_idx := content.rfind('\x1aSAUCE00')):
|
|
1122
1155
|
content = content[:sauce_idx]
|
|
1123
|
-
if
|
|
1156
|
+
if getattr(__buf, 'encoding', None) == 'cp437':
|
|
1124
1157
|
from ._curses import translate_cp437
|
|
1125
1158
|
|
|
1126
1159
|
content = translate_cp437(content, ignore=(0x0A, 0x1A, 0x1B))
|
|
@@ -1130,14 +1163,11 @@ def read_ans(__buf: SupportsRead[str] | TextIOWrapper[str]) -> str:
|
|
|
1130
1163
|
class AnsiImage:
|
|
1131
1164
|
|
|
1132
1165
|
@classmethod
|
|
1133
|
-
def open[AnyStr: (
|
|
1134
|
-
str,
|
|
1135
|
-
bytes,
|
|
1136
|
-
)](
|
|
1166
|
+
def open[AnyStr: (str, bytes)](
|
|
1137
1167
|
cls,
|
|
1138
1168
|
fp: int | PathLike[AnyStr] | AnyStr,
|
|
1139
1169
|
shape: TupleOf2[int] = None,
|
|
1140
|
-
encoding: str = 'cp437',
|
|
1170
|
+
encoding: Optional[str] = 'cp437',
|
|
1141
1171
|
ansi_type: AnsiColorParam = DEFAULT_ANSI,
|
|
1142
1172
|
) -> Self:
|
|
1143
1173
|
"""Construct an `AnsiImage` object from a text file.
|
|
@@ -1164,7 +1194,6 @@ class AnsiImage:
|
|
|
1164
1194
|
inst._ansi_format = get_ansi_type(ansi_type)
|
|
1165
1195
|
inst.file = open(fp, mode='r', encoding=encoding or None)
|
|
1166
1196
|
if shape is None:
|
|
1167
|
-
|
|
1168
1197
|
shape = get_terminal_size()
|
|
1169
1198
|
inst._shape = shape
|
|
1170
1199
|
return inst
|
|
@@ -1176,14 +1205,9 @@ class AnsiImage:
|
|
|
1176
1205
|
raise ValueError("ambiguous value attribute")
|
|
1177
1206
|
if attr_name == 'file':
|
|
1178
1207
|
file: TextIOWrapper[str] = self.__dict__.pop(attr_name)
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
to_sgr_array(
|
|
1183
|
-
reshape_ansi(read_ans(file), *self.shape),
|
|
1184
|
-
ansi_type=self.ansi_format,
|
|
1185
|
-
),
|
|
1186
|
-
)
|
|
1208
|
+
s = reshape_ansi(read_ans(file), *self.shape)
|
|
1209
|
+
arr = to_sgr_array(s, ansi_type=self.ansi_format)
|
|
1210
|
+
setattr(self, 'data', arr)
|
|
1187
1211
|
file.close()
|
|
1188
1212
|
return self.data
|
|
1189
1213
|
|
|
@@ -1204,7 +1228,7 @@ class AnsiImage:
|
|
|
1204
1228
|
return self._shape
|
|
1205
1229
|
|
|
1206
1230
|
def render(
|
|
1207
|
-
self, font: FontArgType =
|
|
1231
|
+
self, font: FontArgType = VGA437, font_size: int = 16, **kwargs
|
|
1208
1232
|
) -> ImageType:
|
|
1209
1233
|
return ansi2img(self._getvalue(), font, font_size, **kwargs)
|
|
1210
1234
|
|
|
@@ -1278,12 +1302,10 @@ def scaled_hu_moments(arr: ShapedNDArray[TupleOf2[int], np.uint8]):
|
|
|
1278
1302
|
|
|
1279
1303
|
|
|
1280
1304
|
def approx_gridlike(
|
|
1281
|
-
fp: PathLike[str] | str,
|
|
1282
|
-
font: FontArgType = userfont['vga437'],
|
|
1283
|
-
shape: TupleOf2[int] = None,
|
|
1305
|
+
fp: PathLike[str] | str, font: FontArgType = VGA437, shape: TupleOf2[int] = None
|
|
1284
1306
|
):
|
|
1285
1307
|
from ._curses import cp437_printable
|
|
1286
|
-
|
|
1308
|
+
|
|
1287
1309
|
if shape is None:
|
|
1288
1310
|
shape = get_terminal_size()
|
|
1289
1311
|
|
|
@@ -69,17 +69,15 @@ CP437_TRANS_TABLE = MappingProxyType(
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
@overload
|
|
72
|
-
def translate_cp437[_T: (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)](__x: str, *, ignore: _T | Iterable[_T] = ...) -> str: ...
|
|
72
|
+
def translate_cp437[_T: (int, str)](
|
|
73
|
+
__x: str, *, ignore: _T | Iterable[_T] = ...
|
|
74
|
+
) -> str: ...
|
|
76
75
|
|
|
77
76
|
|
|
78
77
|
@overload
|
|
79
|
-
def translate_cp437[_T: (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)](__iter: Iterable[str], *, ignore: _T | Iterable[_T] = ...) -> Iterator[str]: ...
|
|
78
|
+
def translate_cp437[_T: (int, str)](
|
|
79
|
+
__iter: Iterable[str], *, ignore: _T | Iterable[_T] = ...
|
|
80
|
+
) -> Iterator[str]: ...
|
|
83
81
|
|
|
84
82
|
|
|
85
83
|
def translate_cp437(
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chromatic-python
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: ANSI art image processing and colored terminal text
|
|
5
5
|
Author: crypt0lith
|
|
6
|
-
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: repository, https://github.com/crypt0lith/chromatic.git
|
|
7
8
|
Keywords: ansi,ascii,art,font,image,terminal,parser
|
|
8
9
|
Classifier: Programming Language :: Python :: 3.12
|
|
9
10
|
Classifier: Operating System :: OS Independent
|
|
@@ -11,7 +12,6 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
11
12
|
Classifier: Typing :: Typed
|
|
12
13
|
Requires-Python: >=3.12
|
|
13
14
|
Description-Content-Type: text/markdown
|
|
14
|
-
License-File: LICENSE
|
|
15
15
|
Requires-Dist: numpy<2.6,>=1.25.2
|
|
16
16
|
Requires-Dist: pillow!=11.2.*,<12.0,>=10.1
|
|
17
17
|
Requires-Dist: networkx<4.0,>=3.0
|
|
@@ -21,7 +21,6 @@ Requires-Dist: scikit-image
|
|
|
21
21
|
Requires-Dist: scikit-learn
|
|
22
22
|
Requires-Dist: fonttools
|
|
23
23
|
Requires-Dist: lazy_loader
|
|
24
|
-
Dynamic: license-file
|
|
25
24
|
|
|
26
25
|

|
|
27
26
|
|
|
@@ -5,7 +5,18 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "chromatic-python"
|
|
7
7
|
dynamic = ["version"]
|
|
8
|
+
description = "ANSI art image processing and colored terminal text"
|
|
9
|
+
readme = "README.md"
|
|
8
10
|
requires-python = ">=3.12"
|
|
11
|
+
keywords = ["ansi", "ascii", "art", "font", "image", "terminal", "parser"]
|
|
12
|
+
authors = [{ name = "crypt0lith" }]
|
|
13
|
+
license = "MIT"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
9
20
|
dependencies = [
|
|
10
21
|
"numpy>=1.25.2,<2.6",
|
|
11
22
|
"pillow>=10.1,!=11.2.*,<12.0",
|
|
@@ -17,20 +28,9 @@ dependencies = [
|
|
|
17
28
|
"fonttools",
|
|
18
29
|
"lazy_loader",
|
|
19
30
|
]
|
|
20
|
-
authors = [{ name = "crypt0lith" }]
|
|
21
|
-
description = "ANSI art image processing and colored terminal text"
|
|
22
|
-
readme = "README.md"
|
|
23
|
-
license-files = ["LICENSE"]
|
|
24
|
-
keywords = ["ansi", "ascii", "art", "font", "image", "terminal", "parser"]
|
|
25
|
-
classifiers = [
|
|
26
|
-
"Programming Language :: Python :: 3.12",
|
|
27
|
-
"Operating System :: OS Independent",
|
|
28
|
-
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
29
|
-
"Typing :: Typed",
|
|
30
|
-
]
|
|
31
31
|
|
|
32
32
|
[project.urls]
|
|
33
|
-
|
|
33
|
+
repository = "https://github.com/crypt0lith/chromatic.git"
|
|
34
34
|
|
|
35
35
|
[tool.setuptools_scm]
|
|
36
36
|
write_to = "chromatic/_version.py"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
* text=auto
|
chromatic_python-0.4.1/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 crypt0lith
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{chromatic_python-0.4.1 → chromatic_python-0.4.2}/chromatic_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|