| |
|
|
| import logging |
| import pickle |
| from typing import Any, Dict, List, Optional |
|
|
| import fsspec |
| import huggingface_hub |
|
|
| 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", |
| |
| "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 |
|
|