11#!/usr/bin/env python3
22"""
3- Convert Inter OTF fonts (CFF outlines) to TTF (TrueType outlines) .
3+ Prepare fonts for the PDF build .
44
5- ReportLab cannot read CFF-based OpenType fonts directly. This script
6- converts the CFF cubic curves to TrueType quadratic curves and fixes
7- the sfntVersion and maxp table so ReportLab accepts them.
5+ 1. Convert Inter OTF (CFF outlines) → TTF (TrueType outlines)
6+ because ReportLab cannot read CFF-based OpenType fonts.
87
9- Run once before build_pdf.py — idempotent (skips if already converted).
8+ 2. Copy IBM Plex Mono TTF files into scripts/fonts/ so the project
9+ is self-contained and works both locally and in CI.
10+
11+ Run once before build_pdf.py — idempotent (skips if already done).
12+
13+ System font packages needed (CI installs these):
14+ sudo apt install fonts-inter fonts-ibm-plex
1015"""
1116
1217import os
1318import sys
19+ import shutil
1420
15- def convert_inter_fonts ():
16- script_dir = os .path .dirname (os .path .abspath (__file__ ))
17- out_dir = os .path .join (script_dir , "fonts" )
18- os .makedirs (out_dir , exist_ok = True )
1921
20- # Check if already converted
22+ def ensure_inter (out_dir ):
23+ """Convert Inter OTF → TTF if not already present."""
2124 target = os .path .join (out_dir , "Inter-Regular.ttf" )
2225 if os .path .exists (target ) and os .path .getsize (target ) > 10000 :
23- # Quick check: is it actually TrueType?
2426 with open (target , "rb" ) as f :
2527 sig = f .read (4 )
2628 if sig == b'\x00 \x01 \x00 \x00 ' :
27- print ("Inter TTF fonts already present, skipping conversion." )
28- return
29+ print (" Inter TTF fonts already present, skipping conversion." )
30+ return True
2931
30- # Find Inter OTF source
32+ # Find Inter OTF source (installed via fonts-inter)
3133 search_dirs = [
3234 "/usr/share/fonts/opentype/inter" ,
3335 "/usr/share/fonts/OTF" ,
@@ -40,24 +42,19 @@ def convert_inter_fonts():
4042 break
4143
4244 if src_dir is None :
43- # Try installing
44- print ("Inter fonts not found. Install with: sudo apt install fonts-inter" )
45- sys .exit (1 )
45+ print (" ✗ Inter OTF fonts not found." )
46+ print (" Install with: sudo apt install fonts-inter" )
47+ print (" Or download from: https://rsms.me/inter/" )
48+ return False
4649
4750 from fontTools .ttLib import TTFont
4851 from fontTools .pens .cu2quPen import Cu2QuPen
4952 from fontTools .pens .ttGlyphPen import TTGlyphPen
5053 from fontTools .ttLib .tables ._g_l_y_f import table__g_l_y_f
5154 from fontTools .ttLib .tables ._l_o_c_a import table__l_o_c_a
5255
53- variants = [
54- "Inter-Regular.otf" ,
55- "Inter-Bold.otf" ,
56- "Inter-Italic.otf" ,
57- "Inter-BoldItalic.otf" ,
58- ]
59-
60- for name in variants :
56+ for name in ["Inter-Regular.otf" , "Inter-Bold.otf" ,
57+ "Inter-Italic.otf" , "Inter-BoldItalic.otf" ]:
6158 src = os .path .join (src_dir , name )
6259 out_name = name .replace (".otf" , ".ttf" )
6360 dst = os .path .join (out_dir , out_name )
@@ -69,37 +66,32 @@ def convert_inter_fonts():
6966 print (f" Converting { name } → { out_name } ..." , end = " " , flush = True )
7067
7168 font = TTFont (src )
72-
7369 if "CFF " not in font :
74- print ("(no CFF table, copying as-is)" )
7570 font .save (dst )
71+ print ("(no CFF, copied)" )
7672 continue
7773
7874 gs = font .getGlyphSet ()
7975 glyph_order = font .getGlyphOrder ()
8076
81- # Convert CFF cubic curves → TrueType quadratic curves
8277 glyphs = {}
8378 for gname in glyph_order :
8479 ttpen = TTGlyphPen (None )
8580 cu2qupen = Cu2QuPen (ttpen , max_err = 1.0 , reverse_direction = True )
8681 gs [gname ].draw (cu2qupen )
8782 glyphs [gname ] = ttpen .glyph ()
8883
89- # Replace CFF with glyf + loca
9084 glyf_table = table__g_l_y_f ()
9185 glyf_table .glyphs = glyphs
9286 glyf_table .glyphOrder = glyph_order
9387 font ["glyf" ] = glyf_table
9488 font ["loca" ] = table__l_o_c_a ()
9589 font ["head" ].glyphDataFormat = 0
9690
97- # Remove CFF-specific tables
9891 for tag in ["CFF " , "VORG" ]:
9992 if tag in font :
10093 del font [tag ]
10194
102- # Fix maxp for TrueType (version 1.0 = 0x00010000)
10395 maxp = font ["maxp" ]
10496 maxp .tableVersion = 0x00010000
10597 for attr , default in [
@@ -114,15 +106,68 @@ def convert_inter_fonts():
114106 setattr (maxp , attr , default )
115107 maxp .recalc (font )
116108
117- # Set TrueType sfntVersion
118109 font .sfntVersion = "\x00 \x01 \x00 \x00 "
119-
120110 font .save (dst )
121- size_kb = os .path .getsize (dst ) / 1024
122- print (f"OK ({ size_kb :.0f} KB)" )
111+ print (f"OK ({ os .path .getsize (dst )/ 1024 :.0f} KB)" )
112+
113+ return True
114+
115+
116+ def ensure_plex_mono (out_dir ):
117+ """Copy IBM Plex Mono TTFs into fonts dir if not already there."""
118+ needed = [
119+ "IBMPlexMono-Regular.ttf" ,
120+ "IBMPlexMono-Bold.ttf" ,
121+ "IBMPlexMono-Italic.ttf" ,
122+ "IBMPlexMono-BoldItalic.ttf" ,
123+ ]
124+
125+ # Check if already present
126+ if all (os .path .exists (os .path .join (out_dir , n )) for n in needed ):
127+ print (" IBM Plex Mono fonts already present, skipping." )
128+ return True
129+
130+ # Find system install (installed via fonts-ibm-plex)
131+ search_dirs = [
132+ "/usr/share/fonts/truetype/ibm-plex" ,
133+ "/usr/share/fonts/TTF" ,
134+ "/usr/share/fonts/truetype" ,
135+ ]
136+ src_dir = None
137+ for d in search_dirs :
138+ if os .path .exists (os .path .join (d , "IBMPlexMono-Regular.ttf" )):
139+ src_dir = d
140+ break
123141
124- print (" Done." )
142+ if src_dir is None :
143+ print (" ✗ IBM Plex Mono fonts not found." )
144+ print (" Install with: sudo apt install fonts-ibm-plex" )
145+ print (" Or download from: https://github.com/IBM/plex/releases" )
146+ return False
147+
148+ for name in needed :
149+ src = os .path .join (src_dir , name )
150+ dst = os .path .join (out_dir , name )
151+ if os .path .exists (src ):
152+ shutil .copy2 (src , dst )
153+ print (f" Copied { name } " )
154+ else :
155+ print (f" ⚠ { name } not found in { src_dir } " )
156+
157+ return True
125158
126159
127160if __name__ == "__main__" :
128- convert_inter_fonts ()
161+ script_dir = os .path .dirname (os .path .abspath (__file__ ))
162+ out_dir = os .path .join (script_dir , "fonts" )
163+ os .makedirs (out_dir , exist_ok = True )
164+
165+ print ("Preparing fonts..." )
166+ ok1 = ensure_inter (out_dir )
167+ ok2 = ensure_plex_mono (out_dir )
168+
169+ if ok1 and ok2 :
170+ print ("All fonts ready ✅" )
171+ else :
172+ print ("Some fonts missing — PDF build may fail." )
173+ sys .exit (1 )
0 commit comments