Download core/fonts.py from FineEnvs/DesignGym: direct link, hf CLI and curl.
- Browser
- Download file 8.32 kB
-
https://huggingface.co/spaces/FineEnvs/DesignGym/resolve/main/core/fonts.py
- Command line
-
hf download hf://spaces/FineEnvs/DesignGym/core/fonts.py
-
curl -L -o fonts.py https://huggingface.co/spaces/FineEnvs/DesignGym/resolve/main/core/fonts.py
8.32 kB
| """The font registry: one set of font files for the renderer and the browser. | |
| Crello ships the fonts its templates use as a single pickle (263 families, | |
| TTF and OTF). `prepare` extracts them into the task pack as plain files, which | |
| buys two things: the server can hand the exact same bytes to the browser editor | |
| through `@font-face`, and skia loads them without unpickling 60 MB per process. | |
| Style fallback is deliberately the *browser's* behaviour with | |
| `font-synthesis: none`: a family without a bold face renders bold text in its | |
| regular face. The editor sets that CSS property, so the two renderers agree | |
| instead of the browser inventing a fake bold that skia would not draw. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import re | |
| import threading | |
| from functools import lru_cache | |
| from pathlib import Path | |
| import skia | |
| STYLES = ("regular", "bold", "italic", "bolditalic") | |
| DEFAULT_FAMILY = "Montserrat" | |
| # Crello's family spellings that do not match the font files' own names. | |
| _ALIASES = { | |
| "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", | |
| } | |
| def style_key(bold: bool, italic: bool) -> str: | |
| if bold and italic: | |
| return "bolditalic" | |
| if bold: | |
| return "bold" | |
| if italic: | |
| return "italic" | |
| return "regular" | |
| def slug(family: str) -> str: | |
| return re.sub(r"[^a-z0-9]+", "-", family.lower()).strip("-") | |
| class FontRegistry: | |
| """Family -> style -> font file, plus cached skia typefaces.""" | |
| def __init__(self, root: Path | str | None): | |
| self.root = Path(root) if root else None | |
| self._files: dict[str, dict[str, str]] = {} | |
| self._lower: dict[str, str] = {} | |
| self._lock = threading.Lock() | |
| self._typefaces: dict[tuple[str, str], skia.Typeface] = {} | |
| manifest = self.root / "manifest.json" if self.root else None | |
| if manifest and manifest.exists(): | |
| self._files = json.loads(manifest.read_text())["families"] | |
| self._lower = {name.lower(): name for name in self._files} | |
| # ------------------------------------------------------------- lookup | |
| def available(self) -> bool: | |
| return bool(self._files) | |
| def families(self) -> list[str]: | |
| return sorted(self._files) | |
| def resolve(self, family: str | None) -> str: | |
| """Canonical family name, or the default when the family is unknown.""" | |
| if not family: | |
| return DEFAULT_FAMILY if DEFAULT_FAMILY in self._files or not self._files else self.families()[0] | |
| name = str(family).strip().strip("'\"") | |
| low = name.replace("_", " ").lower() | |
| for suffix in (" bold", " regular", " light", " italic", " medium"): | |
| if low.endswith(suffix): | |
| low = low[: -len(suffix)] | |
| low = _ALIASES.get(low, low).lower() | |
| if low in self._lower: | |
| return self._lower[low] | |
| if not self._files: | |
| return name | |
| return DEFAULT_FAMILY if DEFAULT_FAMILY in self._files else self.families()[0] | |
| def knows(self, family: str | None) -> bool: | |
| if not family: | |
| return False | |
| low = str(family).strip().replace("_", " ").lower() | |
| low = _ALIASES.get(low, low).lower() | |
| return low in self._lower | |
| def styles(self, family: str) -> list[str]: | |
| return [s for s in STYLES if s in self._files.get(self.resolve(family), {})] | |
| def effective_style(self, family: str, bold: bool, italic: bool) -> str: | |
| """The face actually drawn, following `font-synthesis: none` semantics.""" | |
| have = self._files.get(self.resolve(family), {}) | |
| want = style_key(bold, italic) | |
| if want in have or not have: | |
| return want if want in have else "regular" | |
| # Browsers keep the closest face on the axis that exists. | |
| if want == "bolditalic": | |
| for alt in ("bold", "italic"): | |
| if alt in have: | |
| return alt | |
| return "regular" | |
| def file_path(self, family: str, style: str) -> Path | None: | |
| if not self.root: | |
| return None | |
| rel = self._files.get(self.resolve(family), {}).get(style) | |
| return self.root / rel if rel else None | |
| def typeface(self, family: str, bold: bool = False, italic: bool = False) -> skia.Typeface: | |
| fam = self.resolve(family) | |
| style = self.effective_style(fam, bold, italic) | |
| key = (fam, style) | |
| with self._lock: | |
| cached = self._typefaces.get(key) | |
| if cached is not None: | |
| return cached | |
| path = self.file_path(fam, style) | |
| if path is not None and path.exists(): | |
| tf = skia.Typeface.MakeFromData(skia.Data.MakeWithCopy(path.read_bytes())) | |
| else: | |
| tf = None | |
| if tf is None: | |
| # No pack (unit tests, a bare checkout): skia's default face keeps | |
| # layout deterministic, it just is not the right typeface. | |
| tf = skia.Typeface.MakeDefault() | |
| self._typefaces[key] = tf | |
| return tf | |
| # --------------------------------------------------------- measurement | |
| def font(self, family: str, size: float, bold: bool = False, italic: bool = False) -> skia.Font: | |
| font = skia.Font(self.typeface(family, bold, italic), max(1.0, float(size))) | |
| font.setSubpixel(True) | |
| font.setEdging(skia.Font.Edging.kAntiAlias) | |
| font.setHinting(skia.FontHinting.kNone) | |
| return font | |
| def advance( | |
| self, | |
| text: str, | |
| family: str, | |
| size: float, | |
| bold: bool = False, | |
| italic: bool = False, | |
| letter_spacing: float = 0.0, | |
| ) -> float: | |
| """Advance width of one line, with CSS letter-spacing (after every glyph).""" | |
| if not text: | |
| return 0.0 | |
| font = self.font(family, size, bold, italic) | |
| glyphs = font.textToGlyphs(text) | |
| return float(sum(font.getWidths(glyphs))) + letter_spacing * len(text) | |
| def metrics(self, family: str, size: float, bold: bool = False, italic: bool = False): | |
| """(ascent, descent) as positive numbers, the values CSS `line-height: normal` uses.""" | |
| m = self.font(family, size, bold, italic).getMetrics() | |
| return -m.fAscent, m.fDescent | |
| # --------------------------------------------------------------- extraction | |
| def extract_crello_fonts(pickle_path: Path | str, out_dir: Path | str) -> dict: | |
| """Unpack crello's `fonts.pickle` into files plus a manifest.""" | |
| import pickle | |
| out = Path(out_dir) | |
| out.mkdir(parents=True, exist_ok=True) | |
| with open(pickle_path, "rb") as fh: | |
| fonts = pickle.load(fh) | |
| families: dict[str, dict[str, str]] = {} | |
| for family, faces in sorted(fonts.items()): | |
| entry: dict[str, str] = {} | |
| for face in faces: | |
| weight = face.get("fontWeight", "regular") | |
| fstyle = face.get("fontStyle", "regular") | |
| if fstyle in ("bold", "italic", "bolditalic"): | |
| key = fstyle | |
| else: | |
| key = "bold" if weight == "bold" else "regular" | |
| if key in entry: | |
| continue | |
| data: bytes = face["bytes"] | |
| ext = ".otf" if data[:4] == b"OTTO" else ".ttf" | |
| rel = f"{slug(family)}/{key}{ext}" | |
| (out / rel).parent.mkdir(parents=True, exist_ok=True) | |
| (out / rel).write_bytes(data) | |
| entry[key] = rel | |
| if "regular" not in entry and entry: | |
| # A handful of display faces only ship one cut; let it stand in. | |
| entry["regular"] = next(iter(entry.values())) | |
| families[family] = entry | |
| manifest = {"source": "cyberagent/crello resources/fonts.pickle", "families": families} | |
| (out / "manifest.json").write_text(json.dumps(manifest, indent=1)) | |
| return manifest | |
| def registry_for(root: str | None) -> FontRegistry: | |
| return FontRegistry(root) | |