Download tinydoc/synth/fonts.py from harryfrz/tinydoc-5m: direct link, hf CLI and curl.
- Browser
- Download file 4.64 kB
-
https://huggingface.co/harryfrz/tinydoc-5m/resolve/main/tinydoc/synth/fonts.py
- Command line
-
hf download hf://harryfrz/tinydoc-5m/tinydoc/synth/fonts.py
-
curl -L -o fonts.py https://huggingface.co/harryfrz/tinydoc-5m/resolve/main/tinydoc/synth/fonts.py
4.64 kB
| """Font bank over the openly licensed fonts fetched by scripts/fetch_fonts.py. | |
| Families are split deterministically into train / val so evaluation measures unseen fonts. | |
| """ | |
| import zlib | |
| from dataclasses import dataclass | |
| from functools import lru_cache | |
| from pathlib import Path | |
| from fontTools.ttLib import TTFont | |
| from PIL import ImageFont | |
| MONO_HINTS = ("mono", "courier", "vt323", "inconsolata", "anonymouspro", "cousine", "sharetech", "dotgothic", "spacemono", "ptmono") | |
| VAL_PERCENT = 15 | |
| # Maths / symbol fonts: never a line's text font, only the fallback for glyphs it lacks (in every split). | |
| FALLBACK_FAMILIES = ("notosansmath", "stixtwotext") | |
| class FontFace: | |
| path: str | |
| family: str | |
| mono: bool | |
| italic: bool | |
| variable: bool | |
| bold_file: bool | |
| def coverage(path: str) -> frozenset: | |
| font = TTFont(path, lazy=True, fontNumber=0) | |
| cmap = font.getBestCmap() or {} | |
| return frozenset(chr(c) for c in cmap) | |
| def load_font(path: str, size: int, bold: bool) -> ImageFont.FreeTypeFont: | |
| font = ImageFont.truetype(path, size) | |
| if "[" in Path(path).name: | |
| try: | |
| axes = font.get_variation_axes() | |
| values = [] | |
| for ax in axes: | |
| name = ax.get("name", b"") | |
| name = name.decode() if isinstance(name, bytes) else str(name) | |
| if name.lower().startswith("weight"): | |
| values.append(max(ax["minimum"], min(ax["maximum"], 700 if bold else 400))) | |
| else: | |
| values.append(ax["default"]) | |
| font.set_variation_by_axes(values) | |
| except (OSError, KeyError): | |
| pass | |
| return font | |
| class FontBank: | |
| def __init__(self, root="fonts", split="train"): | |
| faces, self.fallbacks = [], [] | |
| for fam_dir in sorted(p for p in Path(root).glob("*/*") if p.is_dir()): | |
| fam = fam_dir.name | |
| if fam in FALLBACK_FAMILIES: | |
| self.fallbacks += [FontFace(str(f), fam, False, False, "[" in f.name, False) | |
| for f in sorted(fam_dir.glob("*.[ot]tf")) if not f.name.startswith("._") | |
| and "Italic" not in f.name and ("Bold" not in f.name or "[" in f.name)] | |
| continue | |
| is_val = zlib.crc32(fam.encode()) % 100 < VAL_PERCENT | |
| if split != "all" and is_val != (split == "val"): | |
| continue | |
| for f in sorted(f for f in fam_dir.glob("*.[ot]tf") if not f.name.startswith("._")): # skip macOS metadata files | |
| n = f.name | |
| faces.append(FontFace( | |
| path=str(f), family=fam, | |
| mono=any(h in fam for h in MONO_HINTS), | |
| italic="Italic" in n, | |
| variable="[" in n, | |
| bold_file="Bold" in n and not any(k in n for k in ("Semi", "Extra", "Ultra")), | |
| )) | |
| if not faces: | |
| raise FileNotFoundError(f"no fonts under {root}/ for split={split}; run scripts/fetch_fonts.py") | |
| self.faces = faces | |
| self.families = sorted({f.family for f in faces}) | |
| def pick(self, rng, mono=None, bold=False, italic_ok=False): | |
| """Returns (face, bold_flag). Prefers a real bold file, else a variable font's weight axis.""" | |
| fams = [f for f in self.families if mono is None or any(h in f for h in MONO_HINTS) == mono] or self.families | |
| fam = fams[rng.integers(len(fams))] | |
| cands = [f for f in self.faces if f.family == fam and (italic_ok or not f.italic)] or \ | |
| [f for f in self.faces if f.family == fam] | |
| if bold: | |
| bolds = [f for f in cands if f.bold_file] | |
| if bolds: | |
| return bolds[rng.integers(len(bolds))], False | |
| var = [f for f in cands if f.variable] | |
| if var: | |
| return var[0], True | |
| regular = [f for f in cands if not f.bold_file] or cands | |
| return regular[rng.integers(len(regular))], False | |
| def font(face: FontFace, size: int, bold_axis: bool = False): | |
| return load_font(face.path, int(size), bold_axis) | |
| def fallback(self, ch: str): | |
| """A maths / symbol face that has `ch`, for glyphs the text font lacks (None when none has it).""" | |
| for f in self.fallbacks: | |
| if ch in coverage(f.path): | |
| return f | |
| return None | |
| def supported(face: FontFace, text: str) -> str: | |
| cov = coverage(face.path) | |
| return "".join(c if (c in cov or c == " ") else "" for c in text) | |