Add font thumbnail preview support (#307)

* Add font thumbnail preview support

* Add multiple font sizes to thumbnail

* Ruff reformat

* Ruff reformat

* Added Metadata to info

* Change the way thumbnails are structured

* Small performance improvement

* changed Metadata display structure

* added copyright notice to added file

* fix(ui): dynamically scale font previews; add .woff2, .ttc

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
This commit is contained in:
Theasacraft
2024-07-19 16:22:15 +02:00
committed by GitHub
parent aa0aad4300
commit e463635cc0
4 changed files with 109 additions and 3 deletions

View File

@@ -7,6 +7,11 @@ BACKUP_FOLDER_NAME: str = "backups"
COLLAGE_FOLDER_NAME: str = "collages"
LIBRARY_FILENAME: str = "ts_library.json"
FONT_SAMPLE_TEXT: str = (
"""ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?@$%(){}[]"""
)
FONT_SAMPLE_SIZES: list[int] = [10, 15, 20]
# TODO: Turn this whitelist into a user-configurable blacklist.
IMAGE_TYPES: list[str] = [
".png",
@@ -142,6 +147,7 @@ BLENDER_TYPES: list[str] = [
]
PROGRAM_TYPES: list[str] = [".exe", ".app"]
SHORTCUT_TYPES: list[str] = [".lnk", ".desktop", ".url"]
FONT_TYPES: list[str] = [".ttf", ".otf", ".woff", ".woff2", ".ttc"]
ALL_FILE_TYPES: list[str] = (
IMAGE_TYPES
@@ -153,6 +159,7 @@ ALL_FILE_TYPES: list[str] = (
+ ARCHIVE_TYPES
+ PROGRAM_TYPES
+ SHORTCUT_TYPES
+ FONT_TYPES
)
BOX_FIELDS = ["tag_box", "text_box"]

View File

@@ -0,0 +1,51 @@
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from PIL import Image, ImageDraw, ImageFont
def wrap_line( # type: ignore
text: str,
font: ImageFont.ImageFont,
width: int = 256,
draw: ImageDraw.ImageDraw = None,
) -> int:
"""
Takes in a single line and returns the index it should be broken up at but
it only splits one Time
"""
if draw is None:
bg = Image.new("RGB", (width, width), color="#1e1e1e")
draw = ImageDraw.Draw(bg)
if draw.textlength(text, font=font) > width:
for i in range(
int(len(text) / int(draw.textlength(text, font=font)) * width) - 2,
0,
-1,
):
if draw.textlength(text[:i], font=font) < width:
return i
else:
return -1
def wrap_full_text(
text: str,
font: ImageFont.ImageFont,
width: int = 256,
draw: ImageDraw.ImageDraw = None,
) -> str:
"""
Takes in a string and breaks it up to fit in the canvas given accounts for kerning and font size etc.
"""
lines = []
i = 0
last_i = 0
while wrap_line(text[i:], font=font, width=width, draw=draw) > 0:
i = wrap_line(text[i:], font=font, width=width, draw=draw) + last_i
lines.append(text[last_i:i])
last_i = i
lines.append(text[last_i:])
text_wrapped = "\n".join(lines)
return text_wrapped

View File

@@ -10,7 +10,7 @@ from datetime import datetime as dt
import cv2
import rawpy
from PIL import Image, UnidentifiedImageError
from PIL import Image, UnidentifiedImageError, ImageFont
from PIL.Image import DecompressionBombError
from PySide6.QtCore import QModelIndex, Signal, Qt, QSize
from PySide6.QtGui import QResizeEvent, QAction
@@ -30,7 +30,13 @@ from humanfriendly import format_size
from src.core.enums import SettingItems, Theme
from src.core.library import Entry, ItemType, Library
from src.core.constants import VIDEO_TYPES, IMAGE_TYPES, RAW_IMAGE_TYPES, TS_FOLDER_NAME
from src.core.constants import (
VIDEO_TYPES,
IMAGE_TYPES,
RAW_IMAGE_TYPES,
TS_FOLDER_NAME,
FONT_TYPES,
)
from src.qt.helpers.file_opener import FileOpenerLabel, FileOpenerHelper, open_file
from src.qt.modals.add_field import AddFieldModal
from src.qt.widgets.thumb_renderer import ThumbRenderer
@@ -559,6 +565,11 @@ class PreviewPanel(QWidget):
self.dimensions_label.setText(
f"{filepath.suffix.upper()[1:]}{format_size(filepath.stat().st_size)}\n{image.width} x {image.height} px"
)
elif filepath.suffix.lower() in FONT_TYPES:
font = ImageFont.truetype(filepath)
self.dimensions_label.setText(
f"{filepath.suffix.upper()[1:]}{format_size(filepath.stat().st_size)}\n{font.getname()[0]} ({font.getname()[1]}) "
)
else:
self.dimensions_label.setText(
f"{filepath.suffix.upper()[1:]}{format_size(filepath.stat().st_size)}"

View File

@@ -23,11 +23,15 @@ from PIL.Image import DecompressionBombError
from PySide6.QtCore import QObject, Signal, QSize
from PySide6.QtGui import QPixmap
from src.qt.helpers.gradient import four_corner_gradient_background
from src.qt.helpers.text_wrapper import wrap_full_text
from src.core.constants import (
PLAINTEXT_TYPES,
FONT_TYPES,
VIDEO_TYPES,
IMAGE_TYPES,
RAW_IMAGE_TYPES,
FONT_SAMPLE_TEXT,
FONT_SAMPLE_SIZES,
BLENDER_TYPES,
)
from src.core.utils.encoding import detect_char_encoding
@@ -185,7 +189,40 @@ class ThumbRenderer(QObject):
text = text_file.read(256)
bg = Image.new("RGB", (256, 256), color="#1e1e1e")
draw = ImageDraw.Draw(bg)
draw.text((16, 16), text, file=(255, 255, 255))
draw.text((16, 16), text, fill=(255, 255, 255))
image = bg
# Fonts ========================================================
elif _filepath.suffix.lower() in FONT_TYPES:
# Scale the sample font sizes to the preview image
# resolution,assuming the sizes are tuned for 256px.
scaled_sizes: list[int] = [
math.floor(x * (adj_size / 256)) for x in FONT_SAMPLE_SIZES
]
if gradient:
# handles small thumbnails
bg = Image.new("RGB", (adj_size, adj_size), color="#1e1e1e")
draw = ImageDraw.Draw(bg)
font = ImageFont.truetype(
_filepath, size=math.ceil(adj_size * 0.65)
)
draw.text((10, 0), "Aa", font=font)
else:
# handles big thumbnails and renders a sample text in multiple font sizes
bg = Image.new("RGB", (adj_size, adj_size), color="#1e1e1e")
draw = ImageDraw.Draw(bg)
lines_of_padding = 2
y_offset = 0
for font_size in scaled_sizes:
font = ImageFont.truetype(_filepath, size=font_size)
text_wrapped: str = wrap_full_text(
FONT_SAMPLE_TEXT, font=font, width=adj_size, draw=draw
)
draw.multiline_text((0, y_offset), text_wrapped, font=font)
y_offset += (
len(text_wrapped.split("\n")) + lines_of_padding
) * draw.textbbox((0, 0), "A", font=font)[-1]
image = bg
# 3D ===========================================================
# elif extension == 'stl':