diff --git a/README.md b/README.md index 66012c5..48f7d42 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ To preview ANSI art files in file managers like Nautilus, Thunar, Nemo, or Caja, - Free-Form Select stamping/finalizing is incorrect when the selection is off-screen to the left or top. - Status bar description can be left blank when selecting a menu item. (I think the `Leave` event can come after closing, once the mouse moves.) - Menu items like Copy/Cut/Paste are not grayed out when inapplicable. Only unimplemented items are grayed out. -- Set As Wallpaper may not work on your system. For me, on Ubuntu, the wallpaper setting is updated but the picture is not, unless I manually pick it. There is however untested support for many platforms, and you may have better luck than me. +- Set As Wallpaper may not work on your system. It works for me on Windows 10, and macOS 10.14, but on Ubuntu 22, the wallpaper setting is updated but the picture is not, unless I manually pick it. There is also untested support for many other platforms. - ANSI files (.ans) are treated as UTF-8 when saving and loading, rather than CP437 or Windows-1252 or any other encodings. Unicode is nice and modern terminals support it, but it's not the standard for ANSI files. There isn't really a standard for ANSI files. - ANSI files are loaded with a white background. This may make sense as a default for text files, but ANSI files either draw a background or assume a black background, being designed for terminals. - Hitting Enter in View Bitmap mode exits the mode but may also trigger a menu item. Menu items ought to be disabled when hidden, and View Bitmap should prevent the key event from taking other actions if possible. diff --git a/cspell.json b/cspell.json index 4098fbb..675d3d0 100644 --- a/cspell.json +++ b/cspell.json @@ -33,6 +33,7 @@ "Caja", "clion", "cmdpxl", + "Consolas", "dasharray", "Deutsch", "DIALOGEX", @@ -57,17 +58,20 @@ "hsrgb", "humbnail", "icns", + "Inconsolata", "Intelli", "linewrap", "𝗟𝙇", "llpaper", "lrgb", + "Lucida", "mirc", "modd", "mspaint", "mvim", "myusername", "Nemo", + "Noto", "Odhner", "pagedown", "pageup", diff --git a/src/textual_paint/paint.py b/src/textual_paint/paint.py index 138647e..881e9ed 100755 --- a/src/textual_paint/paint.py +++ b/src/textual_paint/paint.py @@ -41,6 +41,7 @@ from .windows import Window, DialogWindow, CharacterSelectorDialogWindow, Messag from .file_dialogs import SaveAsDialogWindow, OpenDialogWindow from .edit_colors import EditColorsDialogWindow from .localization.i18n import get as _, load_language, remove_hotkey +from .rasterize_ansi_art import rasterize from .wallpaper import get_config_dir, set_wallpaper from .auto_restart import restart_on_changes, restart_program @@ -3562,10 +3563,13 @@ Columns: {len(palette) // 2} try: dir = os.path.join(get_config_dir("textual-paint"), "wallpaper") os.makedirs(dir, exist_ok=True) - svg = self.image.get_svg() - image_path = os.path.join(dir, "wallpaper.svg") - with open(image_path, "w", encoding="utf-8") as f: - f.write(svg) + # svg = self.image.get_svg() + # image_path = os.path.join(dir, "wallpaper.svg") + # with open(image_path, "w", encoding="utf-8") as f: + # f.write(svg) + image_path = os.path.join(dir, "wallpaper.png") + pil_image = rasterize(self.image) + pil_image.save(image_path) set_wallpaper(image_path) except Exception as e: self.message_box(_("Paint"), _("Failed to set the wallpaper."), "ok", error=e) diff --git a/src/textual_paint/rasterize_ansi_art.py b/src/textual_paint/rasterize_ansi_art.py new file mode 100644 index 0000000..daf7018 --- /dev/null +++ b/src/textual_paint/rasterize_ansi_art.py @@ -0,0 +1,91 @@ + +import os +from pathlib import Path +from typing import TYPE_CHECKING + +from PIL import Image, ImageDraw, ImageFont + +if TYPE_CHECKING: + from textual_paint.paint import AnsiArtDocument + +# load font +# font = ImageFont.truetype('fonts/ansi.ttf', size=16, layout_engine=ImageFont.Layout.BASIC) +# font = ImageFont.load_default() +# Pillow actually looks in most of these system font folders by default. +font_dirs = [ + # Windows + R"C:\Windows\Fonts", + R"C:\WINNT\Fonts", + R"%LocalAppData%\Microsoft\Windows\Fonts", # (fonts installed only for the current user) + # macOS + "/Library/Fonts", + "/System/Library/Fonts", + "~/Library/Fonts", # (fonts installed only for the current user) + # Linux: + # "/usr/share/fonts", # handled more generally below: + *[data_dir + "/fonts" for data_dir in os.environ.get("XDG_DATA_DIRS", "/usr/share").split(":")], + "/usr/local/share/fonts", + "~/.fonts", # (fonts installed only for the current user) + # Android: + "/system/fonts", + "/data/fonts", +] +font_names = [ + "NotoSansMono", + "DejaVuSansMono", + "LiberationMono", + "UbuntuMono", + "Hack", + "FiraMono", + "Inconsolata", + "SourceCodePro", + "DroidSansMono", + "Consolas", + "CourierNew", + "LucidaConsole", + "Monaco", +] +font = None +for font_dir in font_dirs: + path = Path(os.path.expandvars(os.path.expanduser(font_dir))) + files = path.glob("**/*.ttf") + for file in files: + # print(file.stem) + if file.stem in font_names: + font = ImageFont.truetype(str(file), size=16, layout_engine=ImageFont.LAYOUT_BASIC) + break + if font: + break +if not font: + print("Font not found, using default (built-in) font.") + font = ImageFont.load_default() + +ch_width: int +ch_height: int +ch_width, ch_height = font.getsize('A') # type: ignore +assert isinstance(ch_width, int), "ch_width is not an int, but a " + str(type(ch_width)) # type: ignore +assert isinstance(ch_height, int), "ch_height is not an int, but a " + str(type(ch_height)) # type: ignore + +def rasterize(doc: 'AnsiArtDocument') -> Image.Image: + # make PIL image + img = Image.new('RGB', (doc.width * ch_width, doc.height * ch_height), color='black') + draw = ImageDraw.Draw(img) + + # draw cell backgrounds + for y in range(doc.height): + for x in range(doc.width): + bg_color = doc.bg[y][x] + draw.rectangle((x * ch_width, y * ch_height, (x + 1) * ch_width, (y + 1) * ch_height), fill=bg_color) + + # draw text + for y in range(doc.height): + for x in range(doc.width): + char = doc.ch[y][x] + bg_color = doc.bg[y][x] + fg_color = doc.fg[y][x] + try: + draw.text((x * ch_width, y * ch_height), char, font=font, fill=fg_color) + except UnicodeEncodeError: + pass + + return img