File size: 5,121 Bytes
f46e28b 0a2f23b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# Copied from https://github.com/CyberAgentAILab/cr-renderer/blob/main/src/cr_renderer/fonts.py
import logging
import pickle
from typing import Any, Dict, List, Optional
import fsspec # type: ignore
import huggingface_hub # type: ignore
logger = logging.getLogger(__name__)
FontDict = Dict[str, List[Dict[str, Any]]]
FONT_WEIGHTS = {
"thin",
"light",
"extralight",
"regular",
"medium",
"semibold",
"bold",
"extrabold",
"black",
}
FONT_STYLES = {"regular", "bold", "italic", "bolditalic"}
_OPENCOLE_REPOSITORY = "cyberagent/opencole"
_OPENCOLE_FONTS_PATH = "resources/fonts.pickle"
class FontManager(object):
"""Font face manager.
Example::
# Use the font manager to lookup a font face.
fm = FontManager("/path/to/fonts.pickle")
typeface = skia.Typeface.MakeFromData(fm.lookup("Montserrat"))
"""
def __init__(self, input_path: Optional[str] = None) -> None:
self._fonts: Optional[FontDict] = None
if input_path is None:
input_path = huggingface_hub.hf_hub_download(
repo_id=_OPENCOLE_REPOSITORY,
filename=_OPENCOLE_FONTS_PATH,
repo_type="dataset",
)
self.load(input_path)
def save(self, output_path: str) -> None:
"""Save fonts to a pickle file."""
assert self._fonts is not None, "Fonts not loaded yet."
logger.info("Saving fonts to %s", output_path)
with fsspec.open(output_path, "wb") as f:
pickle.dump(self._fonts, f)
def load(self, input_path: str) -> None:
"""Load fonts from a pickle file."""
logger.info("Loading fonts from %s", input_path)
with fsspec.open(input_path, "rb") as f:
self._fonts = pickle.load(f)
assert self._fonts is not None, "No font loaded."
logger.info("Loaded %d font families", len(self._fonts))
def lookup(
self,
font_family: str,
font_weight: str = "regular",
font_style: str = "regular",
) -> bytes:
"""Lookup the specified font face."""
assert self._fonts is not None, "Fonts not loaded yet."
assert font_weight in FONT_WEIGHTS, f"Invalid font weight: {font_weight}"
assert font_style in FONT_STYLES, f"Invalid font style: {font_style}"
family = []
for i, family_name in enumerate([font_family, "Montserrat"]):
try:
family = self._fonts[normalize_family(family_name)]
if i > 0:
logger.warning(
f"Font family fallback to {family[0]['fontFamily']}."
)
break
except KeyError:
logger.warning(f"Font family not found: {font_family}")
if not family:
family = next(iter(self._fonts.values()))
logger.warning(f"Font family fallback to {family[0]['fontFamily']}.")
font_weight = get_font_weight(font_family, font_weight)
font_style = get_font_style(font_family, font_style)
try:
font = next(
font
for font in family
if font.get("fontWeight", "regular") == font_weight
and font.get("fontStyle", "regular") == font_style
)
except StopIteration:
font = family[0]
logger.warning(
f"Font style for {font['fontFamily']} not found: {font_weight} "
f"{font_style}, fallback to {font.get('fontWeight', 'regular')} "
f"{font.get('fontStyle', 'regular')}"
)
return font["bytes"]
def normalize_family(name: str) -> str:
"""Normalize font name."""
name = name.replace("_", " ").title()
name = name.replace(" Bold", "")
name = name.replace(" Regular", "")
name = name.replace(" Light", "")
name = name.replace(" Italic", "")
name = name.replace(" Medium", "")
FONT_MAP = {
"Arkana Script": "Arkana",
"Blogger": "Blogger Sans",
"Delius Swash": "Delius Swash Caps",
"Elsie Swash": "Elsie Swash Caps",
"Gluk Glametrix": "Gluk Foglihtenno06",
"Gluk Znikomitno25": "Gluk Foglihtenno06",
"Im Fell": "Im Fell Dw Pica Sc",
"Medieval Sharp": "Medievalsharp",
"Playlist Caps": "Playlist",
"Rissa Typeface": "Rissatypeface",
"Selima": "Selima Script",
"Six": "Six Caps",
"V T323": "Vt323",
# The rest is unknown.
"Different Summer": "Montserrat",
"Dukomdesign Constantine": "Montserrat",
"Sunday": "Montserrat",
}
return FONT_MAP.get(name, name)
def get_font_weight(name: str, default: str) -> str:
"""Get font weight from the font name."""
key = name.replace("_", " ").lower().split(" ")[-1]
return key if key in FONT_WEIGHTS else default
def get_font_style(name: str, default: str) -> str:
"""Get font style from the font name."""
key = name.replace("_", " ").lower().split(" ")[-1]
return key if key in FONT_STYLES else default
|