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