Skip to content

Commit bf89d59

Browse files
committed
fix layout render
1 parent 3a35870 commit bf89d59

2 files changed

Lines changed: 115 additions & 48 deletions

File tree

scripts/build_pdf.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,10 @@ def setup_fonts():
8787
registerFontFamily("Inter", normal="Inter", bold="Inter-B",
8888
italic="Inter-I", boldItalic="Inter-BI")
8989

90-
pd = "/usr/share/fonts/truetype/ibm-plex"
91-
if not os.path.isdir(pd):
92-
pd = sd
93-
pdfmetrics.registerFont(TTFont("Plex", os.path.join(pd, "IBMPlexMono-Regular.ttf")))
94-
pdfmetrics.registerFont(TTFont("Plex-B", os.path.join(pd, "IBMPlexMono-Bold.ttf")))
95-
pdfmetrics.registerFont(TTFont("Plex-I", os.path.join(pd, "IBMPlexMono-Italic.ttf")))
96-
pdfmetrics.registerFont(TTFont("Plex-BI", os.path.join(pd, "IBMPlexMono-BoldItalic.ttf")))
90+
pdfmetrics.registerFont(TTFont("Plex", os.path.join(sd, "IBMPlexMono-Regular.ttf")))
91+
pdfmetrics.registerFont(TTFont("Plex-B", os.path.join(sd, "IBMPlexMono-Bold.ttf")))
92+
pdfmetrics.registerFont(TTFont("Plex-I", os.path.join(sd, "IBMPlexMono-Italic.ttf")))
93+
pdfmetrics.registerFont(TTFont("Plex-BI", os.path.join(sd, "IBMPlexMono-BoldItalic.ttf")))
9794
registerFontFamily("Plex", normal="Plex", bold="Plex-B",
9895
italic="Plex-I", boldItalic="Plex-BI")
9996

@@ -204,6 +201,7 @@ def tokenize(code, lang):
204201

205202
class CodeBlock(Flowable):
206203
FS = 7.8; LH = 12; PAD = 8*mm
204+
MAX_LINES = 45 # max lines per block before splitting
207205

208206
def __init__(self, code, lang="", T=None):
209207
super().__init__()
@@ -242,6 +240,20 @@ def draw(self):
242240
y -= self.LH
243241

244242

243+
def make_code_blocks(code, lang, T):
244+
"""Split a code string into one or more CodeBlock flowables that fit on a page."""
245+
lines = code.rstrip("\n").split("\n")
246+
limit = CodeBlock.MAX_LINES
247+
if len(lines) <= limit:
248+
return [CodeBlock(code, lang, T)]
249+
blocks = []
250+
for i in range(0, len(lines), limit):
251+
chunk = "\n".join(lines[i:i+limit])
252+
# Only show language badge on the first chunk
253+
blocks.append(CodeBlock(chunk, lang if i == 0 else "", T))
254+
return blocks
255+
256+
245257
class TipBox(Flowable):
246258
def __init__(self, text, kind="tip", T=None):
247259
super().__init__()
@@ -438,7 +450,8 @@ def md2fl(md, S, T, chnum=None):
438450
cl.append(lines[i]); i+=1
439451
i+=1
440452
fl.append(Spacer(1,2*mm))
441-
fl.append(CodeBlock("\n".join(cl), lang, T))
453+
for cb in make_code_blocks("\n".join(cl), lang, T):
454+
fl.append(cb)
442455
fl.append(Spacer(1,3*mm)); continue
443456
# Blockquote
444457
if st.startswith(">"):
@@ -618,11 +631,20 @@ def build_pdf(output, dark=False):
618631
if is_ch: cn += 1
619632
with open(full, "r", encoding="utf-8") as f:
620633
md = f.read()
621-
story.extend(md2fl(md, S, T, chnum=cn if is_ch else None))
634+
try:
635+
chapter_fl = md2fl(md, S, T, chnum=cn if is_ch else None)
636+
story.extend(chapter_fl)
637+
print(f" ✓ {fp} ({len(chapter_fl)} flowables)")
638+
except Exception as e:
639+
print(f" ✗ {fp} FAILED: {e}")
622640
story.append(PageBreak())
623641

624-
print(f" Building {output} ({mode}) ...")
625-
doc.build(story)
642+
print(f" Building {output} ({mode}), {len(story)} total flowables ...")
643+
try:
644+
doc.build(story)
645+
except Exception as e:
646+
print(f" ✗ doc.build FAILED: {e}")
647+
raise
626648
print(f" ✅ {output} ({os.path.getsize(output)/1024:.0f} KB)")
627649

628650

scripts/convert_fonts.py

Lines changed: 82 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,35 @@
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

1217
import os
1318
import 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

127160
if __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

Comments
 (0)