chromatic-python 0.4.1__py3-none-any.whl → 0.4.2__py3-none-any.whl

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/_version.py CHANGED
@@ -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.1'
32
- __version_tuple__ = version_tuple = (0, 4, 1)
31
+ __version__ = version = '0.4.2'
32
+ __version_tuple__ = version_tuple = (0, 4, 2)
33
33
 
34
34
  __commit_id__ = commit_id = None
chromatic/color/core.py CHANGED
@@ -280,15 +280,22 @@ class ansicolor4Bit(colorbytes):
280
280
  """ANSI 4-bit color format.
281
281
 
282
282
  Supports 16 colors:
283
- * 8 standard colors:
284
- {0: black, 1: red, 2: green, 3: yellow, 4: blue, 5: magenta, 6: cyan, 7: white}
285
- * 8 bright colors, each mapping to a standard color (bright = standard + 8).
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
- * `CSI 30–37 m` for standard foreground colors.
289
- * `CSI 4047 m` for standard background colors.
290
- * `CSI 90–97 m` for bright foreground colors.
291
- * `CSI 100107 m` for bright background colors.
290
+
291
+ * `CSI 3037 m` for standard foreground colors.
292
+
293
+ * `CSI 4047 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
- * (0, 15): Corresponds to ANSI 4-bit colors.
314
- * (16, 231): Represents a 6x6x6 RGB color cube.
315
- * (232, 255): Greyscale colors, from black to white.
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
- * `CSI 38;5;(n) m` for foreground colors.
319
- * `CSI 48;5;(n) m` for background colors.
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
- * `CSI 38;2;(r);(g);(b) m` for foreground colors.
344
- * `CSI 48;2;(r);(g);(b) m` for background colors.
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
- Convert a number or string into a color, or return Color(0) if no arguments are given.
529
- Accepts the same arguments as int, but the value must be in range 0,0xFFFFFF (incl).
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 `list(map(int, bytes().split(b';')))`.
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
- Buffer,
659
- SupportsInt,
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/float conversion
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, return self with the colors of __value.
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] # fmt: skip
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
- type(self),
1727
+ return "{.__class__.__name__}({})".format(
1728
+ self,
1690
1729
  ', '.join(
1691
1730
  [
1692
1731
  repr([f"{sgr}{s}" for sgr, s in self._masks]),
chromatic/color/core.pyi CHANGED
@@ -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
- SupportsInt,
109
- RGBVectorLike,
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
- ansicolor4Bit,
114
- ansicolor8Bit,
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 __add__[_T: (
297
- color_chain,
298
- SgrSequence,
299
- ColorStr,
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 __radd__[_T: (
313
- color_chain,
314
- ColorStr,
315
- str,
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
- int,
460
- Buffer,
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 userfont, register_userfont, UserFont
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 field, dataclass, MISSING, fields
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],
chromatic/image/_array.py CHANGED
@@ -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 = userfont['vga437'],
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 = userfont['vga437'],
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 = userfont['vga437'],
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
- (r, g, b) = tuple(map(int, bg))
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 = userfont['vga437'],
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 = userfont['vga437'],
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
- rf"(?:\x1b\[(?:(?P<cursor>\d*[A-G]|\d*(?:;\d*)?H)|(?P<sgr>{sgr_re})))?(?P<text>[^\x1b]*)"
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 i in nums:
928
- if i in {38, 48}:
922
+ for n in nums:
923
+ if n in {38, 48}:
929
924
  j = next(nums)
930
925
  if j == 5:
931
- extended_param = (i, j, next(nums))
926
+ extended_param = (n, j, next(nums))
932
927
  elif j == 2:
933
- extended_param = (i, j, *(next(nums) for _ in range(3)))
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 i == 0:
933
+ elif n == 0:
939
934
  bold_bit = False
940
935
  prev_colors.clear()
941
- elif i == 1:
936
+ elif n == 1:
942
937
  bold_bit = True
943
- for k, v in prev_colors.items():
944
- if len(v) == 1 and v[0] in ansi_16c_fg_std:
945
- prev_colors[k][0] += 60
946
- elif i == 22:
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, v in prev_colors.items():
949
- if len(v) == 1 and v[0] in ansi_16c_fg_bold:
950
- prev_colors[k][0] -= 60
951
- elif bold_bit and i in ansi_16c_fg_std:
952
- i += 60
953
- params.append(i)
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
- i
958
+ n
956
959
  for xs in [prev_colors.values(), params]
957
960
  for x in xs
958
- for i in ([x] if isinstance(x, int) else x)
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 reshape_ansi(__str: str, w: int, h: int) -> str:
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(content: str, *, incr=False):
985
- nonlocal x, y
986
- if content == '\n':
987
- x = min(x + 1, h - 1)
988
- y = 0
989
- cur.send((x, y))
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[x][y] += content
995
- if incr:
996
- x, y = next(cur)
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
- iter_matches = cursor_or_sgr_pattern().finditer
999
-
1000
- arr = [['\x00'] * w for _ in range(h)]
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
- x, y = next(cur)
1003
- for line in _sub_bold_colors(__str.splitlines()):
1004
- for m in iter_matches(line + '\n'):
1005
- if cg := m['cursor']:
1006
- write_cell(' ')
1007
- param, code = cg[:-1], cg[-1]
1008
- if code == 'H':
1009
- if ';' in param:
1010
- x, y = (int(i or 1) - 1 for i in param.partition(';')[::2])
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
- x = int(param or 1) - 1
1013
- y = 0
1014
- else:
1015
- n = int(param or 1)
1016
- match code:
1017
- case 'A':
1018
- x = max(0, x - n)
1019
- case 'B':
1020
- x = min(h - 1, x + n)
1021
- case 'C':
1022
- y = min(w - 1, y + n)
1023
- case 'D':
1024
- y = max(0, y - n)
1025
- case 'E':
1026
- x += n
1027
- y = 0
1028
- case 'F':
1029
- x -= n
1030
- y = 0
1031
- case 'G':
1032
- y = n - 1
1033
- cur.send((x, y))
1034
- elif sgr := m['sgr']:
1035
- write_cell(f"\x1b[{sgr}")
1036
- for ch in m['text']:
1037
- write_cell(ch, incr=True)
1038
-
1039
- any_ansi_seq = _compile(r"\x1b\[\d*(?:;\d*)*[A-HJ-KS-Tmf]")
1040
- ansi_ws_base = rf"(\s+)((?:{any_ansi_seq.pattern})+)(\s*)"
1041
- ansi_ws_prefix = _compile('^' + ansi_ws_base)
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
- out_row = ''.join(cell.translate({0: ' '}) for cell in row)
1047
- lhs, rhs = map(' '.__mul__, divmod(w - len(any_ansi_seq.sub('', out_row)), 2))
1048
- line = f"{lhs}{out_row}{rhs}"
1049
- for pat in (ansi_ws_prefix, ansi_ws_suffix):
1050
- while m := pat.search(line):
1051
- line = ''.join([line[:m.start()], m[2], m[1], m[3], line[m.end():]])
1052
- out_lines.append(line)
1053
- return '\n'.join(out_lines)
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(__str: str, ansi_type: AnsiColorParam = None):
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
- iter_matches = cursor_or_sgr_pattern().finditer
1096
+ cursor_or_sgr_finditer = cursor_or_sgr_pattern().finditer
1061
1097
  xs = []
1062
- for line in _sub_bold_colors(__str.splitlines()):
1098
+ for line in _sub_bold_colors(__s.split('\n')):
1063
1099
  x = []
1064
- for m in iter_matches(line):
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 = userfont['vga437'],
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 hasattr(__buf, 'encoding') and __buf.encoding == 'cp437':
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
- setattr(
1180
- self,
1181
- 'data',
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 = userfont['vga437'], font_size: int = 16, **kwargs
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
- int,
74
- str,
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
- int,
81
- str,
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.1
3
+ Version: 0.4.2
4
4
  Summary: ANSI art image processing and colored terminal text
5
5
  Author: crypt0lith
6
- Project-URL: Homepage, https://github.com/crypt0lith/chromatic
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
  ![image](https://raw.githubusercontent.com/crypt0lith/chromatic/master/banner.png)
27
26
 
@@ -1,35 +1,34 @@
1
1
  chromatic/__init__.py,sha256=OMvWLvDuaYpXaku9n0yKa0TShBw-nmY_YuC5MsSxyK4,200
2
2
  chromatic/__init__.pyi,sha256=zSQbEqcA-pbjym13cxu5uNrDTKL5JXWQNfPfiWDJBnY,1691
3
3
  chromatic/_typing.py,sha256=mt_eVyzfG3pDhHPWqr9GIT9Re3NHtS9C0f5-UZsXz-U,15069
4
- chromatic/_version.py,sha256=k7cu0JKra64gmMNU_UfA5sw2eNc_GRvf3QmesiYAy8g,704
4
+ chromatic/_version.py,sha256=A45grTqzrHuDn1CT9K5GVUbY4_Q3OSTcXAl3zdHzcEI,704
5
5
  chromatic/demo.py,sha256=py0qPkikRDcPELENlXJqulql1N4QN-Eg054Bl0g3py4,12653
6
6
  chromatic/color/__init__.py,sha256=t19DW9WMdd6FEN69vkckVRlsDPBHldulHv_JulwXxFc,99
7
7
  chromatic/color/__init__.pyi,sha256=QPR4ZywovtSNaiUGBWq5bStm5_NMDunviSa-3Cr71HA,1615
8
8
  chromatic/color/colorconv.py,sha256=UsST5ChrebQXmoVW4RXaAoyTREOnDd125w91JHeRwpE,8632
9
- chromatic/color/core.py,sha256=HhRWJ9ye_OkqnbdXVaisRQScooDTYPQIEy5TqkIu7mY,51989
10
- chromatic/color/core.pyi,sha256=HmoQiSXixVzRTgnUa1r5dKj06FNaGRG3Nmk6xJa-y_o,15699
9
+ chromatic/color/core.py,sha256=MgBd8YvCam05wi91bYkwt8f1JoR06P76lbLGwUGdGn4,52672
10
+ chromatic/color/core.pyi,sha256=jM818chQ1fPazCjeq93rBGC9zUe9AbsfeXhJvAbjPFw,15686
11
11
  chromatic/color/iterators.py,sha256=7lbqZFG7M09DvMwDogjsAUrfWNVUzKUPfUsCg5H-5tM,5980
12
12
  chromatic/color/palette.py,sha256=I_bS8yKmSL9aNwSWwMyo0MSfcnsHBISXaw9tvAOgAHU,18917
13
13
  chromatic/color/palette.pyi,sha256=vGfuKvzcoE_RDuW0kZvAFOqxbkBF1N2-Hbneq46jH94,8795
14
- chromatic/data/__init__.py,sha256=moB9gGvDHD-XHJbWmQQCjEmsQM5bCa-LrwIq9NMTCKw,546
15
- chromatic/data/__init__.pyi,sha256=vEuZSlMUaomFCthj1r6Qkdd3WA3wig9pTamK94nIs-Q,364
14
+ chromatic/data/__init__.py,sha256=gr7kEXGXKESgpNXoA40WgMRgMNpslcwP7rvnIelxPDY,602
15
+ chromatic/data/__init__.pyi,sha256=YcidgVmJ34Jr5X4iEE67A8WAPNdoAa38wICdK9r5UOs,420
16
16
  chromatic/data/_fetchers.py,sha256=w4j912mDGy8vjZSA0CJUlwxWFgge3ciZEVvdxQQE6aY,1583
17
17
  chromatic/data/butterfly.jpg,sha256=rnSfMVotSgpTNRPQ9CzYpDp8S8lkCAJ5ZySRrvyDe7I,448399
18
18
  chromatic/data/escher.png,sha256=FvAC-WOkUVIqHBxasfgCJbH1CN1IwLE-JyMiATOEKFc,132667
19
19
  chromatic/data/goblin_virus.png,sha256=ygs19t4ZeZYsbiRFXjRyEz8OtgC9Zyl8owX0O-5n40o,8849
20
20
  chromatic/data/registry.json,sha256=8fUcLrULUk40qeIQXGC9SOzirp_3D2-5u4lsvHExRHs,269
21
- chromatic/data/userfont.py,sha256=MresOYGvBY5rAvuaPvv9axBhex-hFC4RAzQjcasuX-0,4861
22
- chromatic/data/userfont.pyi,sha256=n6dx1aKg0kmt6yqUzKqhH9In8p6ObpPmIAtWjjTaKxM,685
21
+ chromatic/data/userfont.py,sha256=lc-SVLAOwxs8NxlQ5Grdw5xvHo2ZVZZmHtfPs1BFOIs,4399
22
+ chromatic/data/userfont.pyi,sha256=lu0qUFKE-ht_zpEIAgk1pGD0A-_4b7gP0JZycO9pOms,759
23
23
  chromatic/data/userfont.schema.json,sha256=nU1qwab5UwibhBBf-jo2p2Q3T-eG7mV8oiF_OKk0NdI,546
24
24
  chromatic/data/fonts/consolas.ttf,sha256=xubOgRn91H7GpUSaCOLSrX9B6gMUOq4ZMGjtn6WOrrw,459180
25
25
  chromatic/data/fonts/vga437.ttf,sha256=qMdn-pJWJNKNmHnDoDqGIE94vOTezaCiBv0VK92QbJQ,50124
26
26
  chromatic/image/__init__.py,sha256=JvSI-emrPJXbvfIDFiF9oEQTwYNGJRT77PTs4IUMKQY,149
27
27
  chromatic/image/__init__.pyi,sha256=54B1nzJWJ39BPLFt3hJtn9rFUHFeQ_diMnipA3k7Mc8,1314
28
- chromatic/image/_array.py,sha256=FH0zgPqn_GcB3X70rYamLXdw-v34d60RaHgl-NAwbyo,40556
29
- chromatic/image/_curses.py,sha256=O-VxliW8KrGAt1gSru2svGa8h5QaBScEWsnhP7khjFU,3504
28
+ chromatic/image/_array.py,sha256=rVV_-zow859hSzj0C6UOeMqEmBxmpevMEP7r2_9tIG0,41484
29
+ chromatic/image/_curses.py,sha256=VgRfIeb2H1QmltYTErMHzpRGSWm1WKThOqgoeUFzi7U,3494
30
30
  chromatic/image/_glyph.py,sha256=gbjV0OZ8gdJr_LSg5pz8-jqepKxgAD2WSszH76z1A2A,2646
31
- chromatic_python-0.4.1.dist-info/licenses/LICENSE,sha256=fKQvJY6-i5UcR_yGbIv2W7oAehJMTJY0NvRtWoXwHKE,1066
32
- chromatic_python-0.4.1.dist-info/METADATA,sha256=NibcpQlfgWz_X-zbdbclyxUP2ni8VnvAaNRa0rQ0Fvg,4822
33
- chromatic_python-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- chromatic_python-0.4.1.dist-info/top_level.txt,sha256=wjzxcxfjO8I4u22BS8wL67Bq64c59kbZCqQ--Fc_Mqw,10
35
- chromatic_python-0.4.1.dist-info/RECORD,,
31
+ chromatic_python-0.4.2.dist-info/METADATA,sha256=mYGRO6KywKgD34ePrOEVL2E-glNfRxdBNNaGYzH88a0,4808
32
+ chromatic_python-0.4.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
33
+ chromatic_python-0.4.2.dist-info/top_level.txt,sha256=wjzxcxfjO8I4u22BS8wL67Bq64c59kbZCqQ--Fc_Mqw,10
34
+ chromatic_python-0.4.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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.