Skip to content

Commit 8b8788a

Browse files
authored
Merge pull request #189 from posit-dev/enh-termshow-font-rendering
enh: improve termshow font rendering
2 parents 8e4e16c + 85648b5 commit 8b8788a

10 files changed

Lines changed: 212 additions & 9 deletions

File tree

assets/landing-demo.termshow

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
{"version": 1, "format": "termshow", "term": {"cols": 80, "rows": 24, "type": "xterm-256color"}, "title": "Great Docs — Init, Build, Preview"}
2+
[1.0, "o", "\u001b[32m\u001b[1m$\u001b[0m "]
3+
[0.4, "o", "c"]
4+
[0.08, "o", "d"]
5+
[0.12, "o", " "]
6+
[0.1, "o", "w"]
7+
[0.08, "o", "e"]
8+
[0.06, "o", "a"]
9+
[0.09, "o", "t"]
10+
[0.07, "o", "h"]
11+
[0.08, "o", "e"]
12+
[0.06, "o", "r"]
13+
[0.1, "o", "v"]
14+
[0.07, "o", "a"]
15+
[0.09, "o", "u"]
16+
[0.06, "o", "l"]
17+
[0.08, "o", "t"]
18+
[0.3, "o", "\r\n"]
19+
[0.3, "o", "\u001b[32m\u001b[1m$\u001b[0m "]
20+
[0.6, "o", "g"]
21+
[0.09, "o", "r"]
22+
[0.07, "o", "e"]
23+
[0.11, "o", "a"]
24+
[0.06, "o", "t"]
25+
[0.08, "o", "-"]
26+
[0.1, "o", "d"]
27+
[0.07, "o", "o"]
28+
[0.09, "o", "c"]
29+
[0.06, "o", "s"]
30+
[0.12, "o", " "]
31+
[0.09, "o", "i"]
32+
[0.07, "o", "n"]
33+
[0.06, "o", "i"]
34+
[0.08, "o", "t"]
35+
[0.35, "o", "\r\n"]
36+
[0.6, "o", "Initializing great-docs...\r\n"]
37+
[0.4, "o", "Found package __init__.py at: weathervault/__init__.py\r\n"]
38+
[0.8, "o", "Discovered 24 public names\r\n"]
39+
[0.3, "o", "Detected docstring style: numpy\r\n"]
40+
[0.2, "o", "Found Click CLI: weathervault in weathervault.cli\r\n"]
41+
[0.5, "o", "\r\n\u001b[32m✅ Great Docs initialization complete!\u001b[0m\r\n"]
42+
[0.1, "o", "\r\nNext steps:\r\n"]
43+
[0.05, "o", "1. Review great-docs.yml to customize your API reference structure\r\n"]
44+
[0.05, "o", "2. Run `great-docs build` to generate and build your documentation site\r\n"]
45+
[0.05, "o", "3. Run `great-docs preview` to view the site locally\r\n"]
46+
[0.0, "m", "init done"]
47+
[1.8, "o", "\r\n\u001b[32m\u001b[1m$\u001b[0m "]
48+
[0.5, "o", "g"]
49+
[0.09, "o", "r"]
50+
[0.07, "o", "e"]
51+
[0.11, "o", "a"]
52+
[0.06, "o", "t"]
53+
[0.08, "o", "-"]
54+
[0.1, "o", "d"]
55+
[0.07, "o", "o"]
56+
[0.09, "o", "c"]
57+
[0.06, "o", "s"]
58+
[0.12, "o", " "]
59+
[0.08, "o", "b"]
60+
[0.07, "o", "u"]
61+
[0.09, "o", "i"]
62+
[0.06, "o", "l"]
63+
[0.08, "o", "d"]
64+
[0.35, "o", "\r\n"]
65+
[0.5, "o", "\u001b[38;2;49;139;252m┌────────────────────────────────────────────────────────────────────────────┐\u001b[0m\r\n"]
66+
[0.05, "o", "\u001b[38;2;49;139;252m│\u001b[0m great-docs build · weathervault v2.4.1 \u001b[38;2;49;139;252m│\u001b[0m\r\n"]
67+
[0.05, "o", "\u001b[38;2;49;139;252m│\u001b[0m 18 steps · estimated ~1 min \u001b[38;2;49;139;252m│\u001b[0m\r\n"]
68+
[0.05, "o", "\u001b[38;2;49;139;252m└────────────────────────────────────────────────────────────────────────────┘\u001b[0m\r\n"]
69+
[0.3, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 1/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mPrepare build directory\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
70+
[0.4, "o", " \u001b[32m✔\u001b[0m great-docs/ ready \u001b[2m0.3s\u001b[0m\r\n"]
71+
[0.2, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 2/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mConfigure API reference\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
72+
[0.8, "o", " \u001b[32m✔\u001b[0m 24 exports in 4 sections \u001b[2m0.8s\u001b[0m\r\n"]
73+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 3/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mGenerate llms.txt / llms-full.txt\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
74+
[0.5, "o", " \u001b[32m✔\u001b[0m 2 files (4.2 KB) \u001b[2m0.4s\u001b[0m\r\n"]
75+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 4/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mGenerate SKILL.md\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
76+
[0.4, "o", " \u001b[32m✔\u001b[0m SKILL.md written \u001b[2m0.3s\u001b[0m\r\n"]
77+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 5/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mGenerate source links\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
78+
[0.6, "o", " \u001b[32m✔\u001b[0m 24 source links resolved \u001b[2m0.5s\u001b[0m\r\n"]
79+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 6/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mGenerate changelog\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
80+
[0.5, "o", " \u001b[32m✔\u001b[0m 8 releases from GitHub \u001b[2m0.6s\u001b[0m\r\n"]
81+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 7/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mGenerate CLI reference\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
82+
[0.4, "o", " \u001b[32m✔\u001b[0m 6 commands documented \u001b[2m0.3s\u001b[0m\r\n"]
83+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 8/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mProcess User Guide\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
84+
[0.3, "o", " \u001b[32m✔\u001b[0m 5 pages configured \u001b[2m0.2s\u001b[0m\r\n"]
85+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 9/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mProcess custom sections\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
86+
[0.2, "o", " \u001b[2m⊘\u001b[0m \u001b[2mSkipped (none configured)\u001b[0m\r\n"]
87+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 10/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mProcess custom pages\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
88+
[0.2, "o", " \u001b[2m⊘\u001b[0m \u001b[2mSkipped (none configured)\u001b[0m\r\n"]
89+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 11/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mProcess page tags\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
90+
[0.2, "o", " \u001b[2m⊘\u001b[0m \u001b[2mSkipped (none configured)\u001b[0m\r\n"]
91+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 12/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mProcess page status badges\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
92+
[0.2, "o", " \u001b[2m⊘\u001b[0m \u001b[2mSkipped (none configured)\u001b[0m\r\n"]
93+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 13/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mCopy assets\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
94+
[0.3, "o", " \u001b[32m✔\u001b[0m 42 files copied \u001b[2m0.2s\u001b[0m\r\n"]
95+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 14/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mGenerate API reference\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
96+
[1.2, "o", " \u001b[32m✔\u001b[0m 24 .qmd pages generated \u001b[2m1.1s\u001b[0m\r\n"]
97+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 15/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mBuild site with Quarto\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
98+
[0.6, "o", " \u001b[38;2;49;139;252m►\u001b[0m Rendering pages \u001b[38;2;49;139;252m████████████████████████\u001b[2m\u001b[0m 47/47 100%"]
99+
[2.5, "o", "\r \r"]
100+
[0.1, "o", " \u001b[32m✔\u001b[0m Site rendered \u001b[2m8.2s\u001b[0m\r\n"]
101+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 16/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mPost-render processing\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
102+
[0.1, "o", " \u001b[2m├─\u001b[0m \u001b[32m✔\u001b[0m Dark-mode toggle injected\r\n"]
103+
[0.1, "o", " \u001b[2m├─\u001b[0m \u001b[32m✔\u001b[0m Interlinks resolved\r\n"]
104+
[0.1, "o", " \u001b[2m├─\u001b[0m \u001b[32m✔\u001b[0m Responsive tables applied\r\n"]
105+
[0.1, "o", " \u001b[2m├─\u001b[0m \u001b[32m✔\u001b[0m Copy-code buttons added\r\n"]
106+
[0.1, "o", " \u001b[2m└─\u001b[0m \u001b[32m✔\u001b[0m Navbar widgets rendered\r\n"]
107+
[0.3, "o", " \u001b[32m✔\u001b[0m 5 transforms applied \u001b[2m1.4s\u001b[0m\r\n"]
108+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 17/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mGenerate SEO files\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
109+
[0.3, "o", " \u001b[32m✔\u001b[0m sitemap.xml + robots.txt \u001b[2m0.2s\u001b[0m\r\n"]
110+
[0.15, "o", "\r\n\u001b[1;38;2;49;139;252m━━\u001b[1;37m Step 18/18 \u001b[1;38;2;49;139;252m─ \u001b[1;37mPrepare freeze cache\u001b[0m ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\r\n"]
111+
[0.2, "o", " \u001b[2m⊘\u001b[0m \u001b[2mSkipped (no code cells)\u001b[0m\r\n"]
112+
[0.4, "o", "\r\n\u001b[38;2;49;139;252m┌────────────────────────────────────────────────────────────────────────────┐\u001b[0m\r\n"]
113+
[0.05, "o", "\u001b[38;2;49;139;252m│\u001b[0m ✔ Build complete — 14/18 steps (4 skipped) \u001b[38;2;49;139;252m│\u001b[0m\r\n"]
114+
[0.05, "o", "\u001b[38;2;49;139;252m│\u001b[0m Total time: 14.8s \u001b[38;2;49;139;252m│\u001b[0m\r\n"]
115+
[0.05, "o", "\u001b[38;2;49;139;252m│\u001b[0m \u001b[38;2;49;139;252m│\u001b[0m\r\n"]
116+
[0.05, "o", "\u001b[38;2;49;139;252m│\u001b[0m 🎉 Site ready → great-docs/_site/index.html \u001b[38;2;49;139;252m│\u001b[0m\r\n"]
117+
[0.05, "o", "\u001b[38;2;49;139;252m└────────────────────────────────────────────────────────────────────────────┘\u001b[0m\r\n"]
118+
[0.0, "m", "build done"]
119+
[1.8, "o", "\r\n\u001b[32m\u001b[1m$\u001b[0m "]
120+
[0.5, "o", "g"]
121+
[0.09, "o", "r"]
122+
[0.07, "o", "e"]
123+
[0.11, "o", "a"]
124+
[0.06, "o", "t"]
125+
[0.08, "o", "-"]
126+
[0.1, "o", "d"]
127+
[0.07, "o", "o"]
128+
[0.09, "o", "c"]
129+
[0.06, "o", "s"]
130+
[0.12, "o", " "]
131+
[0.08, "o", "p"]
132+
[0.07, "o", "r"]
133+
[0.09, "o", "e"]
134+
[0.06, "o", "v"]
135+
[0.08, "o", "i"]
136+
[0.07, "o", "e"]
137+
[0.09, "o", "w"]
138+
[0.35, "o", "\r\n"]
139+
[0.8, "o", "\r\n\u001b[32m🌐 Serving site at \u001b[1mhttp://localhost:3000\u001b[0m\r\n"]
140+
[0.3, "o", "\u001b[2m Press Ctrl+C to stop\u001b[0m\r\n"]
141+
[4.0, "o", ""]
142+
[0.0, "m", "preview done"]

assets/landing-demo.termshow.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
source: assets/landing-demo.termshow
2+
3+
settings:
4+
window_chrome: colorful
5+
6+
chapters:
7+
- at: 0
8+
label: "Initialize"
9+
- at: 11.5
10+
label: "Build"
11+
- at: 29.23
12+
label: "Preview"
13+
14+
snippets:
15+
- at: 1.4
16+
duration: 9
17+
text: "great-docs init"
18+
label: "Init"
19+
- at: 12.5
20+
duration: 15.1
21+
text: "great-docs build"
22+
label: "Build"
23+
- at: 29.59
24+
duration: 6.62
25+
text: "great-docs preview"
26+
label: "Preview"

great_docs/_term_player/manifest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ def generate_manifest(
153153
# Determine render settings from script
154154
theme = recording.term.theme
155155
font_family = "JetBrains Mono, Fira Code, SF Mono, Menlo, Consolas, monospace"
156+
line_height: float | None = None
156157
show_cursor = True
157158
window_chrome = "none"
158159

@@ -161,6 +162,8 @@ def generate_manifest(
161162
theme = script.theme
162163
if script.font_family:
163164
font_family = script.font_family
165+
if script.line_height is not None:
166+
line_height = script.line_height
164167
show_cursor = script.show_cursor
165168
window_chrome = script.window_chrome
166169

@@ -248,6 +251,7 @@ def generate_manifest(
248251
font_family=font_family,
249252
show_cursor=show_cursor,
250253
window_chrome=window_chrome,
254+
**({"line_height": line_height} if line_height is not None else {}),
251255
)
252256

253257
if out_path:

great_docs/_term_player/renderer.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def render_frame(
2121
*,
2222
font_family: str = DEFAULT_FONT_FAMILY,
2323
font_size: int = DEFAULT_FONT_SIZE,
24+
line_height: float = DEFAULT_LINE_HEIGHT,
2425
show_cursor: bool = True,
2526
window_chrome: str = "none",
2627
) -> str:
@@ -36,6 +37,9 @@ def render_frame(
3637
CSS font-family for terminal text.
3738
font_size
3839
Font size in pixels.
40+
line_height
41+
Line height multiplier. Lower values (e.g. 1.2) make box-drawing
42+
characters connect vertically.
3943
show_cursor
4044
Whether to render the cursor block.
4145
window_chrome
@@ -50,7 +54,7 @@ def render_frame(
5054
theme = Theme()
5155

5256
cell_w = font_size * 0.6 # Monospace character width approximation
53-
cell_h = font_size * DEFAULT_LINE_HEIGHT
57+
cell_h = font_size * line_height
5458

5559
pad = DEFAULT_PADDING
5660
chrome_h = 0.0 # Chrome is rendered as CSS overlay, not in SVG

great_docs/_term_player/script.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class Script:
8383
theme_name: str | None = None
8484
theme: Theme | None = None
8585
font_family: str | None = None
86+
line_height: float | None = None
8687
show_cursor: bool = True
8788
window_chrome: str = "none"
8889
prompt: str | None = None
@@ -131,6 +132,7 @@ def _parse_script_data(data: dict[str, Any]) -> Script:
131132
script.speed = settings.get("speed", 1.0)
132133
script.theme_name = settings.get("theme")
133134
script.font_family = settings.get("font_family")
135+
script.line_height = settings.get("line_height")
134136
script.show_cursor = settings.get("show_cursor", True)
135137
script.window_chrome = settings.get("window_chrome", "none")
136138
if "prompt" in settings:
24.9 KB
Binary file not shown.
24.2 KB
Binary file not shown.

great_docs/assets/termshow.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
/* Great Docs Termshow Player Styles */
22

3+
/* Embedded JetBrains Mono subset (OFL license) — includes box-drawing chars */
4+
@font-face {
5+
font-family: "JetBrains Mono";
6+
font-style: normal;
7+
font-weight: 400;
8+
font-display: swap;
9+
src: url(JetBrainsMono-Regular.subset.woff2) format("woff2");
10+
unicode-range: U+0020-007E, U+00A0-00FF, U+2010-2027, U+2190-219F,
11+
U+2500-257F, U+2580-259F, U+25A0-25C0, U+2298, U+2605-2606,
12+
U+2705, U+2714, U+2716, U+2718, U+26A0;
13+
}
14+
15+
@font-face {
16+
font-family: "JetBrains Mono";
17+
font-style: normal;
18+
font-weight: 700;
19+
font-display: swap;
20+
src: url(JetBrainsMono-Bold.subset.woff2) format("woff2");
21+
unicode-range: U+0020-007E, U+00A0-00FF, U+2010-2027, U+2190-219F,
22+
U+2500-257F, U+2580-259F, U+25A0-25C0, U+2298, U+2605-2606,
23+
U+2705, U+2714, U+2716, U+2718, U+26A0;
24+
}
25+
326
.gd-termshow {
427
--gd-tp-bg: #1e1e2e;
528
--gd-tp-fg: #cdd6f4;

great_docs/core.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,15 @@ def _prepare_build_directory(self) -> None:
269269
if tp_css_src.exists():
270270
shutil.copy2(tp_css_src, self.project_path / "termshow.css")
271271

272+
# Copy termshow font subsets (JetBrains Mono for box-drawing fidelity)
273+
for font_file in (
274+
"JetBrainsMono-Regular.subset.woff2",
275+
"JetBrainsMono-Bold.subset.woff2",
276+
):
277+
font_src = self.assets_path / font_file
278+
if font_src.exists():
279+
shutil.copy2(font_src, self.project_path / font_file)
280+
272281
# Copy the evolution demo data file
273282
demo_json_src = self.assets_path / "api-evolution-demo.json"
274283
if demo_json_src.exists():

index.qmd

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,7 @@ Give your Python package the documentation site it deserves. Great Docs turns yo
44

55
## Three Commands. One Beautiful Site.
66

7-
```bash
8-
pip install great-docs
9-
10-
cd your-python-package
11-
great-docs init # scans your package, writes the config
12-
great-docs build # generates everything, renders with Quarto
13-
great-docs preview # live preview at localhost:3000
14-
```
7+
{{< termshow file="assets/landing-demo" autoplay="true" loop="true" >}}
158

169
That's it. Your API reference, CLI docs, and landing page are ready to go. Push to GitHub and you're live.
1710

0 commit comments

Comments
 (0)