Some nice printable calendars to share.
1{
2 description = "Entorno de desarrollo Typst";
3
4 inputs = {
5 nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
6 flake-parts.url = "github:hercules-ci/flake-parts";
7 };
8
9 outputs = inputs @ {flake-parts, ...}:
10 flake-parts.lib.mkFlake {inherit inputs;} {
11 systems = ["x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin"];
12
13 perSystem = {
14 config,
15 self',
16 inputs',
17 pkgs,
18 system,
19 ...
20 }: let
21 generateFontSpecimen = pkgs.writers.writePython3Bin "font-specimen" {} ''
22 import subprocess
23 import sys
24 from datetime import datetime
25 from collections import defaultdict
26
27
28 OUTPUT_TYP = "font-specimen.typ"
29 OUTPUT_PDF = "font-specimen.pdf"
30 PANGRAM = "The quick brown fox jumps over the lazy dog. 0123456789"
31 ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz"
32 DATE = datetime.now().strftime("%Y-%m-%d")
33
34 HEAVY_HITTERS = {
35 "Noto", "SF", "Apple", "Hiragino", "STIX", "CJK",
36 "DecoType", "FiraMono", "iMWritingMono"
37 }
38
39
40 def is_likely_latin(font_name):
41 # 1. Handle the Noto explosion: Only allow base Latin variants
42 if font_name.startswith("Noto "):
43 allowed = {"Noto Sans", "Noto Serif", "Noto Mono"}
44 base = font_name.replace(" UI", "")
45 if base not in allowed:
46 return False
47
48 if font_name.startswith("STIX"):
49 return False
50
51 # 2. Block known non-Latin scripts, math, emoji, and macOS fallbacks
52 blocked_terms = [
53 "Arabic", "Hebrew", "CJK", "Devanagari", "Bangla", "Gurmukhi",
54 "Gujarati", "Oriya", "Tamil", "Telugu", "Kannada", "Malayalam",
55 "Sinhala", "Thai", "Lao", "Khmer", "Myanmar", "Armenian",
56 "Georgian", "Emoji", "Math", "Symbols", "Braille", "Gothic",
57 "Mincho", "Songti", "Heiti", "Kufi", "Naskh", "Nastaliq",
58 "Thuluth", "Ornaments", "Dingbats", "Webdings", "Wingdings",
59 "Fallback", "LastResort", "Al Bayan", "Al Nile", "Al Tarikh",
60 "Ayuthaya", "Baghdad", "Beirut", "Damascus", "DecoType",
61 "Diwan", "Euphemia", "Farah", "Farisi", "Geeza", "Grantha",
62 "Hiragino", "InaiMathi", "Kailasa", "Kefa", "Kohinoor",
63 "Kokonor", "Krungthep", "Mishafi", "Muna", "Nadeem",
64 "Peninim", "Plantagenet", "Raanana", "Sana", "Sathu", "Shree",
65 "Silom", "Sukhumvit", "Waseem", "Zither"
66 ]
67
68 return not any(term in font_name for term in blocked_terms)
69
70
71 def get_fonts():
72 try:
73 result = subprocess.run(
74 ["typst", "fonts"],
75 capture_output=True,
76 text=True,
77 check=True
78 )
79 lines = result.stdout.splitlines()
80 # Extract, clean, and immediately filter out non-Latin fonts
81 fonts = [f.strip() for f in lines if f.strip()]
82 return [f for f in fonts if is_likely_latin(f)]
83 except FileNotFoundError:
84 print("Error: 'typst' command not found.")
85 sys.exit(1)
86
87
88 def deduplicate_fonts(fonts):
89 grouped = defaultdict(list)
90 for font in fonts:
91 parts = font.split()
92 if not parts:
93 continue
94 if parts[0] in HEAVY_HITTERS:
95 prefix = parts[0]
96 else:
97 if len(parts) > 1:
98 prefix = f"{parts[0]} {parts[1]}"
99 else:
100 prefix = parts[0]
101 grouped[prefix].append(font)
102
103 final_fonts = []
104 for prefix in sorted(grouped.keys()):
105 family = grouped[prefix]
106 if len(family) > 3:
107 mid = len(family) // 2
108 final_fonts.extend([family[0], family[mid], family[-1]])
109 else:
110 final_fonts.extend(family)
111 return sorted(list(set(final_fonts)))
112
113
114 def generate_typst(fonts):
115 fonts_str = "\n".join(f' "{f}",' for f in fonts)
116 typst_content = f"""
117 #set page(paper: "a4", margin: (x: 1cm, y: 1cm))
118 #set text(size: 10pt, font: "Arial")
119
120 #align(center)[
121 #text(size: 26pt, weight: "bold")[Typst Font Specimen] \\
122 #v(0.2em)
123 #text(size: 11pt, fill: luma(100))[Filtered & Grouped • Generated: {DATE}]
124 ]
125
126 #v(1cm)
127
128 #let fonts = (
129 {fonts_str}
130 )
131
132 #grid(
133 columns: (1fr, 1fr),
134 column-gutter: 0.8cm,
135 row-gutter: 1.2cm,
136 ..fonts.map(f => {{
137 block(width: 100%, breakable: false)[
138 #text(weight: "bold", size: 9pt, fill: blue.darken(40%))[#f] \\
139 #v(0.3em)
140 #text(font: f, size: 14pt, fallback: false)[{PANGRAM}] \\
141 #v(0.1em)
142 #text(font: f, size: 10pt, fill: luma(80), fallback: false)[{ALPHABET}]
143 #line(length: 100%, stroke: 0.2pt + luma(220))
144 ]
145 }})
146 )
147 """
148 with open(OUTPUT_TYP, "w", encoding="utf-8") as f:
149 f.write(typst_content.strip())
150
151
152 def main():
153 print("Retrieving and filtering font list...")
154 fonts = get_fonts()
155
156 print("Deduplicating families...")
157 processed_fonts = deduplicate_fonts(fonts)
158
159 count = len(processed_fonts)
160 print(f"Generating Typst source ({count} readable fonts mapped)...")
161 generate_typst(processed_fonts)
162
163 print("Compiling to PDF...")
164 subprocess.run(
165 ["typst", "compile", OUTPUT_TYP, OUTPUT_PDF],
166 check=True
167 )
168 print(f"Success! PDF generated at {OUTPUT_PDF}")
169
170
171 if __name__ == "__main__":
172 main()
173 '';
174 in {
175 devShells.default = pkgs.mkShell {
176 name = "typst-dev-shell";
177
178 TYPST_ROOT = ".";
179 TYPST_FONT_PATHS = "./fonts";
180
181 packages = with pkgs; [
182 typst
183 tinymist
184 generateFontSpecimen
185 ];
186 };
187 };
188 };
189}