diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53179de --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +docs/_build/ +target/ +.python-version +__pypackages__/ +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +.ropeproject +.mypy_cache/ +.dmypy.json +dmypy.json +.idea +*.iml +.DS_Store +.AppleDouble +.LSOverride +._* diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..80f8348 --- /dev/null +++ b/Pipfile @@ -0,0 +1,19 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +black = "*" +mypy = "*" + +[packages] +numpy = "*" +Pillow = "*" +fonttools = "*" + +[requires] +python_version = "3.8" + +[pipenv] +allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..17bd434 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,215 @@ +{ + "_meta": { + "hash": { + "sha256": "11679fbbfbfe6ca9cf64b908f74c67c912a0b96bff52aa6f34ff7528e5b9f449" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "fonttools": { + "hashes": [ + "sha256:0c16d5da7846a92aedcb448277d13d3b729f7de48a917768d97ebd0d17067d7f", + "sha256:bd93818f304b7f0d087e8ca9b5b3ed0819a68fb6c1a4763134a92bb046c2c31b" + ], + "index": "pypi", + "version": "==4.3.0" + }, + "numpy": { + "hashes": [ + "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6", + "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e", + "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc", + "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc", + "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a", + "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa", + "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3", + "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121", + "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971", + "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26", + "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd", + "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480", + "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec", + "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77", + "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57", + "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07", + "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572", + "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73", + "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca", + "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474", + "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5" + ], + "index": "pypi", + "version": "==1.18.1" + }, + "pillow": { + "hashes": [ + "sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be", + "sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946", + "sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837", + "sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f", + "sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00", + "sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d", + "sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533", + "sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a", + "sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358", + "sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda", + "sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435", + "sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2", + "sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313", + "sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff", + "sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317", + "sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2", + "sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614", + "sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0", + "sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386", + "sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9", + "sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636", + "sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865" + ], + "index": "pypi", + "version": "==7.0.0" + } + }, + "develop": { + "appdirs": { + "hashes": [ + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + ], + "version": "==1.4.3" + }, + "attrs": { + "hashes": [ + "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", + "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" + ], + "version": "==19.3.0" + }, + "black": { + "hashes": [ + "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", + "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539" + ], + "index": "pypi", + "version": "==19.10b0" + }, + "click": { + "hashes": [ + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + ], + "version": "==7.0" + }, + "mypy": { + "hashes": [ + "sha256:0a9a45157e532da06fe56adcfef8a74629566b607fa2c1ac0122d1ff995c748a", + "sha256:2c35cae79ceb20d47facfad51f952df16c2ae9f45db6cb38405a3da1cf8fc0a7", + "sha256:4b9365ade157794cef9685791032521233729cb00ce76b0ddc78749abea463d2", + "sha256:53ea810ae3f83f9c9b452582261ea859828a9ed666f2e1ca840300b69322c474", + "sha256:634aef60b4ff0f650d3e59d4374626ca6153fcaff96ec075b215b568e6ee3cb0", + "sha256:7e396ce53cacd5596ff6d191b47ab0ea18f8e0ec04e15d69728d530e86d4c217", + "sha256:7eadc91af8270455e0d73565b8964da1642fe226665dd5c9560067cd64d56749", + "sha256:7f672d02fffcbace4db2b05369142e0506cdcde20cea0e07c7c2171c4fd11dd6", + "sha256:85baab8d74ec601e86134afe2bcccd87820f79d2f8d5798c889507d1088287bf", + "sha256:87c556fb85d709dacd4b4cb6167eecc5bbb4f0a9864b69136a0d4640fdc76a36", + "sha256:a6bd44efee4dc8c3324c13785a9dc3519b3ee3a92cada42d2b57762b7053b49b", + "sha256:c6d27bd20c3ba60d5b02f20bd28e20091d6286a699174dfad515636cb09b5a72", + "sha256:e2bb577d10d09a2d8822a042a23b8d62bc3b269667c9eb8e60a6edfa000211b1", + "sha256:f97a605d7c8bc2c6d1172c2f0d5a65b24142e11a58de689046e62c2d632ca8c1" + ], + "index": "pypi", + "version": "==0.761" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "pathspec": { + "hashes": [ + "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424", + "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96" + ], + "version": "==0.7.0" + }, + "regex": { + "hashes": [ + "sha256:07b39bf943d3d2fe63d46281d8504f8df0ff3fe4c57e13d1656737950e53e525", + "sha256:0932941cdfb3afcbc26cc3bcf7c3f3d73d5a9b9c56955d432dbf8bbc147d4c5b", + "sha256:0e182d2f097ea8549a249040922fa2b92ae28be4be4895933e369a525ba36576", + "sha256:10671601ee06cf4dc1bc0b4805309040bb34c9af423c12c379c83d7895622bb5", + "sha256:23e2c2c0ff50f44877f64780b815b8fd2e003cda9ce817a7fd00dea5600c84a0", + "sha256:26ff99c980f53b3191d8931b199b29d6787c059f2e029b2b0c694343b1708c35", + "sha256:27429b8d74ba683484a06b260b7bb00f312e7c757792628ea251afdbf1434003", + "sha256:3e77409b678b21a056415da3a56abfd7c3ad03da71f3051bbcdb68cf44d3c34d", + "sha256:4e8f02d3d72ca94efc8396f8036c0d3bcc812aefc28ec70f35bb888c74a25161", + "sha256:4eae742636aec40cf7ab98171ab9400393360b97e8f9da67b1867a9ee0889b26", + "sha256:6a6ae17bf8f2d82d1e8858a47757ce389b880083c4ff2498dba17c56e6c103b9", + "sha256:6a6ba91b94427cd49cd27764679024b14a96874e0dc638ae6bdd4b1a3ce97be1", + "sha256:7bcd322935377abcc79bfe5b63c44abd0b29387f267791d566bbb566edfdd146", + "sha256:98b8ed7bb2155e2cbb8b76f627b2fd12cf4b22ab6e14873e8641f266e0fb6d8f", + "sha256:bd25bb7980917e4e70ccccd7e3b5740614f1c408a642c245019cff9d7d1b6149", + "sha256:d0f424328f9822b0323b3b6f2e4b9c90960b24743d220763c7f07071e0778351", + "sha256:d58e4606da2a41659c84baeb3cfa2e4c87a74cec89a1e7c56bee4b956f9d7461", + "sha256:e3cd21cc2840ca67de0bbe4071f79f031c81418deb544ceda93ad75ca1ee9f7b", + "sha256:e6c02171d62ed6972ca8631f6f34fa3281d51db8b326ee397b9c83093a6b7242", + "sha256:e7c7661f7276507bce416eaae22040fd91ca471b5b33c13f8ff21137ed6f248c", + "sha256:ecc6de77df3ef68fee966bb8cb4e067e84d4d1f397d0ef6fce46913663540d77" + ], + "version": "==2020.1.8" + }, + "toml": { + "hashes": [ + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" + ], + "version": "==0.10.0" + }, + "typed-ast": { + "hashes": [ + "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", + "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", + "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", + "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", + "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", + "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", + "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", + "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", + "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", + "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", + "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", + "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", + "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", + "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", + "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", + "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", + "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", + "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", + "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", + "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", + "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" + ], + "version": "==1.4.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2", + "sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d", + "sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575" + ], + "version": "==3.7.4.1" + } + } +} diff --git a/build.py b/build.py new file mode 100644 index 0000000..4103235 --- /dev/null +++ b/build.py @@ -0,0 +1,51 @@ +from pathlib import Path +from cozette_builder.imagegen import save_sample, save_charlist + +SAMPLE_TEXT = """ +$BLUE$┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ $MAGENTA$♡$NC$ $FG$Cozette$NC$ $MAGENTA$♡$BLUE$ ┃ +┡━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ $WHITE$A B C D E F G H I J K L M $BLUE$│ +│ $WHITE$N O P Q R S T U V W X Y Z $BLUE$│ +╞═══════════════════════════╡ +│ 1 2 3 4 5 6 7 8 9 0 │ +╞═══════════════════════════╡ +│ $WHITE$a b c d e f g h i j k l m $BLUE$│ +│ $WHITE$n o p q r s t u v w x y z $BLUE$│ +├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┤ +│ $RED$() [] {} <> ? / \ : " ' ;$BLUE$ │ +│ $RED$! @ # $ % ^ & * _ = + - |$BLUE$ │ +│ , . ` ~$BLUE$ │ +╰───────────────────────────╯ + $YELLOW$   +   + $BLUE$│ $FG$╔═══════════════╗ + $BLUE$└──$FG$║ $GREEN$✓ ✔ $YELLOW$✕ ✖ $MAGENTA$✗ ✘ $FG$ ║$BLUE$▒ + $FG$║ $BLUE$       $FG$║$BLUE$▒ + $FG$╚═══════════════╝$BLUE$▒ + ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + + + $GREEN$Pchnąć w tę łódź jeża lub + ósm skrzyń fig. + + Eĥoŝanĝo ĉiuĵaŭde. + + Příliš žluťoučký kůň + úpěl ďábelské ódy. + + $FG$♠ ♣ $MAGENTA$♥ ♦ +""" +REPO_ROOT = Path(__file__).resolve().parent + +def save_images(): + bdfpath = REPO_ROOT / "Cozette8" / "Cozette-10.bdf" + + save_charlist(bdfpath, REPO_ROOT / "img" / "characters.png") + + save_sample( + "Clozette", SAMPLE_TEXT, REPO_ROOT / "img" / "sample.png", + ) + +if __name__ == "__main__": + save_images() diff --git a/cozette_builder/__init__.py b/cozette_builder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cozette_builder/bdf/__init__.py b/cozette_builder/bdf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cozette_builder/bdf/bdffont.py b/cozette_builder/bdf/bdffont.py new file mode 100644 index 0000000..853bf38 --- /dev/null +++ b/cozette_builder/bdf/bdffont.py @@ -0,0 +1,88 @@ +from __future__ import annotations +from typing import ( + Iterable, + Union, + Literal, + NamedTuple, + Dict, + List, + TextIO, + Tuple, +) +import numpy as np + +Bit = Union[Literal[1], Literal[0]] + + +class BBX(NamedTuple): + w: int + h: int + x: int + y: int + + +class BdfGlyph: + def __init__(self, bits: np.array, meta: Dict[str, str]): + + self.bbx = bbx = BBX(*[int(s) for s in meta["BBX"].split()]) + # this is probably wrong + self.bits: np.array = np.pad(bits, [[0, 0], [bbx.x, 0]])[ + bbx.y : bbx.y + bbx.h, bbx.x : bbx.x + bbx.w + ] + self.meta = meta + + @classmethod + def from_str(cls, s: str, meta: Dict[str, str]) -> BdfGlyph: + return cls.from_iterable((int(l, 16) for l in s.splitlines()), meta) + + @classmethod + def from_iterable( + cls, values: Iterable[int], meta: Dict[str, str] + ) -> BdfGlyph: + return BdfGlyph( + np.unpackbits( + np.array([[v] for v in values], dtype=np.uint8), axis=1 + ), + meta=meta, + ) + + def __str__(self) -> str: + return "\n".join( + "".join("#" if ch else " " for ch in line) for line in self.bits + ) + + +def parse_char(bdfstream: TextIO): + specs = {} + while not (line := bdfstream.readline()).startswith("BITMAP"): + parts = line.split(maxsplit=1) + specs[parts[0]] = parts[1] + bitmap = [] + while not (line := bdfstream.readline()).startswith("ENDCHAR"): + bitmap.append(int(line.strip(), 16)) + return specs, bitmap + + +class BdfFont: + def __init__(self, metadata: List[str], glyphs: Dict[int, BdfGlyph]): + self.metadata: List[str] = metadata + self.glyphs: Dict[int, BdfGlyph] = glyphs + + @classmethod + def from_bdf(cls, bdfstream: TextIO): + metadata = [] + while not (line := bdfstream.readline()).startswith("CHARS "): + metadata.append(line) + glyphs = {} + for i in range(int(line.split()[1])): + meta, char = parse_char(bdfstream) + glyphs[int(meta["ENCODING"])] = BdfGlyph.from_iterable(char, meta) + return cls(metadata, glyphs) + + @property + def codepoints(self) -> Tuple[int, ...]: + return tuple(self.glyphs.keys()) + + @property + def str_codepoints(self) -> Tuple[str, ...]: + return tuple(f"U+{i:X}" for i in self.glyphs) diff --git a/cozette_builder/imagegen.py b/cozette_builder/imagegen.py new file mode 100644 index 0000000..e9e4e99 --- /dev/null +++ b/cozette_builder/imagegen.py @@ -0,0 +1,124 @@ +import subprocess +from typing import Tuple, Dict, Optional +from shlex import quote +from pathlib import Path +from cozette_builder.bdf.bdffont import BdfFont +import textwrap +import tempfile + +Color = Tuple[int, int, int] + + +def hex_to_rgb(color: str) -> Color: + h = int(color, 16) + return h >> 16, (h >> 8) & 0x00FF, h & 0x0000FF + + +Palette = Dict[str, Optional[Color]] + + +default_palette: Palette = dict( + fg=hex_to_rgb("feffff"), + bg=hex_to_rgb("2d2b33"), + black=hex_to_rgb("383640"), + red=hex_to_rgb("a09ebb"), + green=hex_to_rgb("92b6b1"), + yellow=hex_to_rgb("edaf97"), + blue=hex_to_rgb("8789c0"), + magenta=hex_to_rgb("e47b66"), + cyan=hex_to_rgb("6b9080"), + white=hex_to_rgb("969a97"), + nc=None, +) + +# Todo: rewrite this to use files instead of a string +def save_sample( + font: str, + sample: str, + output_path: Path, + fgcolor: str = "#abb2bf", + bgcolor: str = "#282c34", + palette: Palette = None, +): + if palette is None: + palette = default_palette + colored, stripped = colored_and_stripped_text(sample, palette) + + lines = stripped.splitlines() + h, w = len(lines), max(len(line) for line in lines) + with tempfile.NamedTemporaryFile("w", delete=False) as f: + f.write(colored) + fp = f.name + cmd = quote( + f"tput civis &&" + f"cat {fp} && sleep 0 && " + f"import -window $WINDOWID {output_path}" + ) + subprocess.run( + [ + "xterm", + "-fg", + fgcolor, + "-bg", + bgcolor, + "-fa", + font, + "-geometry", + f"{w}x{h}", + "-dc", + "-e", + f"bash -c {cmd}", + ], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + Path(fp).unlink() + + +def color_escape_seq(color: Color = None): + if color is None: + return f"\033[38;0m" + return f"\033[38;2;{color[0]};{color[1]};{color[2]}m" + + +def colored_and_stripped_text(text: str, palette: Palette) -> Tuple[str, str]: + colored_text = text + stripped_text = text + for color in palette.keys(): + # The PyCharm MyPy plugin throws a fit at `palette.get(color)` because + # it doesn't know that palette.get returns an Optional[Color], PyCharm + # itself throws a fit here because of + # https://youtrack.jetbrains.com/issue/PY-40439, and it sometimes + # forgets that `palette` has a `.get` method, so ignore all those + # warnings: + # noinspection Mypy, PyTypedDict, PyUnresolvedReferences + colored_text = colored_text.replace( + f"${color.upper()}$", color_escape_seq(palette[color]) + ) + stripped_text = stripped_text.replace(f"${color.upper()}$", "") + return colored_text, stripped_text + + +# Todo: write a fill() that ignores character codes + + +def save_charlist(bdf_font: Path, output_path: Path): + with bdf_font.open() as f: + bdf = BdfFont.from_bdf(f) + save_sample( + "Clozette", + textwrap.fill( + "".join( + chr(c) + for c in bdf.codepoints + if c >= 32 + and c + not in ( + 127, + # dumb as shit workaround for a weird bug + 0x26A1, + ) + ) + ), + output_path, + ) diff --git a/img/characters.png b/img/characters.png index 5d9454f..1c15c3f 100644 Binary files a/img/characters.png and b/img/characters.png differ diff --git a/img/sample.png b/img/sample.png index ab92bfc..6022af9 100644 Binary files a/img/sample.png and b/img/sample.png differ diff --git a/img/sample.txt b/img/sample.txt index eb656be..10bcb9e 100644 --- a/img/sample.txt +++ b/img/sample.txt @@ -1,36 +1,34 @@ - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ ♡ Cozette ♡ ┃ - ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ - │ A B C D E F G H I J K L M │ - │ N O P Q R S T U V W X Y Z │ - ╞═══════════════════════════╡ - │ 1 2 3 4 5 6 7 8 9 0 │ - ╞═══════════════════════════╡ - │ a b c d e f g h i j k l m │ - │ n o p q r s t u v w x y z │ - ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┤ - │ () [] {} <> ? / \ : " ' ; │ - │ ! @ # $ % ^ & * _ = + - | │ - │ , . ` ~ │ - ╰───────────────────────────╯ -    -   - │ ╔═══════════════╗ - └──║ ✓ ✔ ✕ ✖ ✗ ✘  ║▒ - ║        ║▒ - ╚═══════════════╝▒ - ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ +$BLUE$┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ $MAGENTA$♡$NC$ $FG$Cozette$NC$ $MAGENTA$♡$BLUE$ ┃ +┡━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ $WHITE$A B C D E F G H I J K L M $BLUE$│ +│ $WHITE$N O P Q R S T U V W X Y Z $BLUE$│ +╞═══════════════════════════╡ +│ 1 2 3 4 5 6 7 8 9 0 │ +╞═══════════════════════════╡ +│ $WHITE$a b c d e f g h i j k l m $BLUE$│ +│ $WHITE$n o p q r s t u v w x y z $BLUE$│ +├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┤ +│ $RED$() [] {} <> ? / \ : " ' ;$BLUE$ │ +│ $RED$! @ # $ % ^ & * _ = + - |$BLUE$ │ +│ , . ` ~$BLUE$ │ +╰───────────────────────────╯ + $YELLOW$   +   + $BLUE$│ $FG$╔═══════════════╗ + $BLUE$└──$FG$║ $GREEN$✓ ✔ $YELLOW$✕ ✖ $MAGENTA$✗ ✘ $FG$ ║$BLUE$▒ + $FG$║ $BLUE$       $FG$║$BLUE$▒ + $FG$╚═══════════════╝$BLUE$▒ + ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - Pchnąć w tę łódź jeża lub - ósm skrzyń fig. + $GREEN$Pchnąć w tę łódź jeża lub + ósm skrzyń fig. - Eĥoŝanĝo ĉiuĵaŭde. - - Příliš žluťoučký kůň - úpěl ďábelské ódy. - - ♠ ♣ ♥ ♦ + Eĥoŝanĝo ĉiuĵaŭde. + Příliš žluťoučký kůň + úpěl ďábelské ódy. + $FG$♠ ♣ $MAGENTA$♥ ♦