explico 0.11.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/CHANGELOG.md +1292 -1
  2. package/README.md +91 -13
  3. package/dist/compiler/astro.d.ts +11 -4
  4. package/dist/compiler/astro.js +48 -15
  5. package/dist/compiler/cli.d.ts +27 -12
  6. package/dist/compiler/cli.js +96 -25
  7. package/dist/compiler/collections.d.ts +33 -2
  8. package/dist/compiler/collections.js +91 -19
  9. package/dist/compiler/content-schema.d.ts +31 -0
  10. package/dist/compiler/content-schema.js +82 -0
  11. package/dist/compiler/content-schemas.d.ts +422 -0
  12. package/dist/compiler/content-schemas.js +229 -0
  13. package/dist/compiler/course-config.d.ts +1 -1
  14. package/dist/compiler/course-config.js +30 -2
  15. package/dist/compiler/exports/combined-html.d.ts +97 -0
  16. package/dist/compiler/exports/combined-html.js +492 -0
  17. package/dist/compiler/exports/download-index.d.ts +9 -0
  18. package/dist/compiler/exports/download-index.js +33 -0
  19. package/dist/compiler/exports/export-chapters.d.ts +98 -0
  20. package/dist/compiler/exports/export-chapters.js +164 -0
  21. package/dist/compiler/exports/export-pages.d.ts +48 -0
  22. package/dist/compiler/exports/export-pages.js +22 -0
  23. package/dist/compiler/exports/html-tree.d.ts +22 -0
  24. package/dist/compiler/exports/html-tree.js +47 -0
  25. package/dist/compiler/exports/latex/capture.d.ts +106 -0
  26. package/dist/compiler/exports/latex/capture.js +435 -0
  27. package/dist/compiler/exports/latex/compile.d.ts +73 -0
  28. package/dist/compiler/exports/latex/compile.js +115 -0
  29. package/dist/compiler/exports/latex/document.d.ts +102 -0
  30. package/dist/compiler/exports/latex/document.js +349 -0
  31. package/dist/compiler/exports/latex/explico.cls +415 -0
  32. package/dist/compiler/exports/latex/numbering.d.ts +18 -0
  33. package/dist/compiler/exports/latex/numbering.js +57 -0
  34. package/dist/compiler/exports/latex/serialize.d.ts +112 -0
  35. package/dist/compiler/exports/latex/serialize.js +606 -0
  36. package/dist/compiler/exports/latex-export.d.ts +44 -0
  37. package/dist/compiler/exports/latex-export.js +169 -0
  38. package/dist/compiler/exports/markdown/serialize.d.ts +51 -0
  39. package/dist/compiler/exports/markdown/serialize.js +527 -0
  40. package/dist/compiler/exports/markdown-export.d.ts +39 -0
  41. package/dist/compiler/exports/markdown-export.js +240 -0
  42. package/dist/compiler/exports/standalone-html.d.ts +15 -5
  43. package/dist/compiler/exports/standalone-html.js +23 -51
  44. package/dist/compiler/exports/static-tree.d.ts +253 -0
  45. package/dist/compiler/exports/static-tree.js +625 -0
  46. package/dist/compiler/exports/zip.d.ts +16 -0
  47. package/dist/compiler/exports/zip.js +72 -0
  48. package/dist/compiler/lesson-derivation.d.ts +8 -2
  49. package/dist/compiler/lesson-derivation.js +6 -7
  50. package/dist/compiler/manifest.d.ts +115 -8
  51. package/dist/compiler/manifest.js +221 -39
  52. package/dist/compiler/render-diagnostics.d.ts +65 -0
  53. package/dist/compiler/render-diagnostics.js +56 -0
  54. package/dist/compiler/resolution-report.d.ts +3 -1
  55. package/dist/{course-config-Dsnri9Tk.d.ts → course-config-rRtME5UV.d.ts} +56 -13
  56. package/dist/curriculum/validation.d.ts +9 -3
  57. package/dist/curriculum/validation.js +35 -17
  58. package/dist/reference/base-abbreviations.d.mts +2 -2
  59. package/dist/reference/base-abbreviations.mjs +2 -2
  60. package/dist/reference/base-operators.d.mts +8 -2
  61. package/dist/reference/base-operators.mjs +8 -10
  62. package/dist/reference/base-roman-labels.d.mts +4 -3
  63. package/dist/reference/base-roman-labels.mjs +4 -3
  64. package/dist/reference/citation-format.d.mts +35 -0
  65. package/dist/reference/citation-format.mjs +74 -0
  66. package/dist/reference/consistency.d.ts +5 -9
  67. package/dist/reference/consistency.js +34 -38
  68. package/dist/reference/curriculum-alignment.js +3 -24
  69. package/dist/reference/derived-entries.d.ts +45 -0
  70. package/dist/reference/derived-entries.js +50 -0
  71. package/dist/reference/derived-notation.d.mts +125 -0
  72. package/dist/reference/derived-notation.mjs +121 -0
  73. package/dist/reference/diagram-fit.d.ts +45 -0
  74. package/dist/reference/diagram-fit.js +54 -0
  75. package/dist/reference/diagram-layout.d.ts +59 -10
  76. package/dist/reference/diagram-layout.js +85 -24
  77. package/dist/reference/diagrams-validate.d.ts +1 -1
  78. package/dist/reference/equations-validate.d.ts +1 -1
  79. package/dist/reference/equations-validate.js +26 -1
  80. package/dist/reference/equations.d.mts +27 -0
  81. package/dist/reference/equations.mjs +120 -22
  82. package/dist/reference/examples-validate.d.ts +1 -1
  83. package/dist/reference/figure-fit.d.ts +70 -0
  84. package/dist/reference/figure-fit.js +60 -0
  85. package/dist/reference/figure-pictures.d.ts +27 -0
  86. package/dist/reference/figure-pictures.js +49 -0
  87. package/dist/reference/figure-symbols.d.ts +32 -0
  88. package/dist/reference/figure-symbols.js +55 -0
  89. package/dist/reference/figures-validate.d.ts +1 -1
  90. package/dist/reference/gate-abbreviations.d.ts +1 -1
  91. package/dist/reference/gate-abbreviations.js +1 -1
  92. package/dist/reference/gate-course-rules.d.ts +4 -3
  93. package/dist/reference/gate-course-rules.js +9 -4
  94. package/dist/reference/gate-heading-length.d.ts +2 -0
  95. package/dist/reference/gate-heading-length.js +7 -5
  96. package/dist/reference/gate-heading-structure.js +26 -0
  97. package/dist/reference/gate-indices.d.ts +35 -0
  98. package/dist/reference/gate-indices.js +299 -0
  99. package/dist/reference/gate-math.d.ts +32 -2
  100. package/dist/reference/gate-math.js +68 -12
  101. package/dist/reference/gate-mdx-expression.d.ts +17 -0
  102. package/dist/reference/gate-mdx-expression.js +29 -0
  103. package/dist/reference/gate-non-chapter-pages.d.ts +39 -0
  104. package/dist/reference/gate-non-chapter-pages.js +156 -0
  105. package/dist/reference/gate-part-pages.d.ts +4 -2
  106. package/dist/reference/gate-part-pages.js +2 -1
  107. package/dist/reference/gate-prop-text.d.ts +28 -0
  108. package/dist/reference/gate-prop-text.js +106 -0
  109. package/dist/reference/gate-prose-notation.d.ts +3 -1
  110. package/dist/reference/gate-prose-notation.js +4 -2
  111. package/dist/reference/gate-retired-keys.d.ts +35 -0
  112. package/dist/reference/gate-retired-keys.js +58 -0
  113. package/dist/reference/gloss.d.ts +2 -0
  114. package/dist/reference/katex-measure.d.ts +17 -0
  115. package/dist/reference/katex-measure.js +223 -0
  116. package/dist/reference/keyed-float-validate.js +12 -0
  117. package/dist/reference/keyed-jsx-floats.d.mts +2 -2
  118. package/dist/reference/keyed-jsx-floats.mjs +11 -14
  119. package/dist/reference/lab-katex-validate.d.ts +43 -0
  120. package/dist/reference/lab-katex-validate.js +96 -0
  121. package/dist/reference/lab-math-scope.d.ts +1 -0
  122. package/dist/reference/lab-math-scope.js +13 -2
  123. package/dist/reference/lint-ignore.js +1 -1
  124. package/dist/reference/math-glyphs.d.mts +94 -2
  125. package/dist/reference/math-glyphs.mjs +851 -46
  126. package/dist/reference/mixed-content.d.ts +8 -3
  127. package/dist/reference/mixed-content.js +4 -2
  128. package/dist/reference/page-notation.d.ts +39 -7
  129. package/dist/reference/page-notation.js +62 -10
  130. package/dist/reference/raw-floats.mjs +37 -13
  131. package/dist/reference/references.d.ts +11 -1
  132. package/dist/reference/references.js +6 -0
  133. package/dist/reference/registry.js +115 -11
  134. package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
  135. package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
  136. package/dist/reference/rehype-heading-anchors.mjs +11 -1
  137. package/dist/reference/remark-citation.mjs +0 -0
  138. package/dist/reference/remark-notation.mjs +275 -23
  139. package/dist/reference/render-lab-math.d.ts +13 -2
  140. package/dist/reference/render-lab-math.js +18 -6
  141. package/dist/reference/sections-validate.d.ts +28 -3
  142. package/dist/reference/sections-validate.js +31 -4
  143. package/dist/reference/sections.d.mts +24 -6
  144. package/dist/reference/sections.mjs +92 -17
  145. package/dist/reference/tables-validate.d.ts +1 -1
  146. package/dist/reference/tables-validate.js +24 -1
  147. package/dist/reference/text-measure.d.ts +10 -0
  148. package/dist/reference/text-measure.js +41 -0
  149. package/dist/reference/text-widths.d.ts +10 -0
  150. package/dist/reference/text-widths.js +1238 -0
  151. package/dist/reference/types.d.ts +64 -8
  152. package/dist/rules/catalog.js +223 -32
  153. package/dist/rules/in-effect.d.ts +1 -1
  154. package/dist/rules/rules.schema.json +100 -27
  155. package/dist/rules/settings.d.ts +1 -1
  156. package/dist/rules/settings.js +7 -0
  157. package/dist/session/PreferenceStore.d.ts +18 -1
  158. package/dist/session/PreferenceStore.js +29 -1
  159. package/docs/authoring/README.md +33 -21
  160. package/docs/authoring/agents.md +38 -7
  161. package/docs/authoring/components.md +115 -10
  162. package/docs/authoring/migrating.md +878 -0
  163. package/docs/authoring/notation.md +285 -30
  164. package/docs/authoring/retired.md +1 -0
  165. package/docs/authoring/structure-rules.md +84 -45
  166. package/docs/authoring/structure.md +67 -2
  167. package/docs/authoring/visualizations.md +167 -25
  168. package/package.json +12 -7
  169. package/src/compiler/astro.ts +72 -21
  170. package/src/compiler/cli.ts +143 -35
  171. package/src/compiler/collections.ts +158 -27
  172. package/src/compiler/content-schema.ts +108 -0
  173. package/src/compiler/content-schemas.ts +391 -0
  174. package/src/compiler/course-config.ts +86 -15
  175. package/src/compiler/exports/combined-html.ts +669 -0
  176. package/src/compiler/exports/download-index.ts +36 -0
  177. package/src/compiler/exports/export-chapters.ts +233 -0
  178. package/src/compiler/exports/export-pages.ts +44 -0
  179. package/src/compiler/exports/html-tree.ts +62 -0
  180. package/src/compiler/exports/latex/capture.ts +651 -0
  181. package/src/compiler/exports/latex/compile.ts +168 -0
  182. package/src/compiler/exports/latex/document.ts +479 -0
  183. package/src/compiler/exports/latex/explico.cls +415 -0
  184. package/src/compiler/exports/latex/numbering.ts +74 -0
  185. package/src/compiler/exports/latex/serialize.ts +787 -0
  186. package/src/compiler/exports/latex/texlive-packages.txt +76 -0
  187. package/src/compiler/exports/latex-export.ts +255 -0
  188. package/src/compiler/exports/markdown/serialize.ts +632 -0
  189. package/src/compiler/exports/markdown-export.ts +329 -0
  190. package/src/compiler/exports/standalone-html.ts +39 -70
  191. package/src/compiler/exports/static-tree.ts +944 -0
  192. package/src/compiler/exports/zip.ts +88 -0
  193. package/src/compiler/lesson-derivation.ts +13 -8
  194. package/src/compiler/manifest.ts +387 -40
  195. package/src/compiler/render-diagnostics.ts +111 -0
  196. package/src/components/CourseContents.astro +6 -0
  197. package/src/components/HoverPanel.astro +204 -6
  198. package/src/components/PartOverview.astro +18 -9
  199. package/src/components/abbreviation/AbbreviationGlossary.astro +6 -2
  200. package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
  201. package/src/components/assessments/AssessmentRunner.tsx +7 -1
  202. package/src/components/assessments/AssessmentSet.astro +14 -1
  203. package/src/components/block-heading.ts +38 -0
  204. package/src/components/card-arrow.ts +18 -4
  205. package/src/components/card-carry.ts +406 -0
  206. package/src/components/card-drag.ts +1 -30
  207. package/src/components/card-fetch.ts +87 -0
  208. package/src/components/card-manager.ts +1222 -38
  209. package/src/components/card-memory.ts +5 -1
  210. package/src/components/card-placement.ts +211 -13
  211. package/src/components/card-side.ts +281 -31
  212. package/src/components/citation/CitationLayer.astro +45 -40
  213. package/src/components/diagram/Diagram.astro +123 -44
  214. package/src/components/disclosure/print-as-shown.ts +42 -6
  215. package/src/components/figure/Figure.astro +17 -1
  216. package/src/components/figure/FloatCaption.astro +4 -17
  217. package/src/components/figure/figure-state.ts +26 -17
  218. package/src/components/float-toolbar.ts +274 -0
  219. package/src/components/memo/Memo.astro +21 -10
  220. package/src/components/memo/ShortNote.astro +4 -0
  221. package/src/components/notation/NotationGlossary.astro +22 -17
  222. package/src/components/notation/NotationLayer.astro +300 -79
  223. package/src/components/notation/NotationMath.astro +8 -1
  224. package/src/components/notation/render-notation-math.ts +51 -5
  225. package/src/components/readable-text.ts +25 -0
  226. package/src/components/reference/FloatRefLayer.astro +162 -89
  227. package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
  228. package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
  229. package/src/components/starlight/LayoutPageSidebar.astro +17 -3
  230. package/src/components/starlight/LayoutSocialIcons.astro +79 -29
  231. package/src/components/starlight/LessonExportActions.astro +384 -310
  232. package/src/components/starlight/LessonFooter.astro +66 -8
  233. package/src/components/starlight/PrintController.astro +28 -0
  234. package/src/components/starlight/TextSizeControl.astro +137 -0
  235. package/src/components/starlight/print-controller.ts +380 -0
  236. package/src/components/starlight/section-summary.ts +13 -5
  237. package/src/components/table/Table.astro +14 -22
  238. package/src/components/table/table-truncation.ts +15 -0
  239. package/src/content-config.ts +20 -312
  240. package/src/curriculum/validation.ts +72 -24
  241. package/src/reference/base-abbreviations.mjs +2 -2
  242. package/src/reference/base-operators.mjs +8 -10
  243. package/src/reference/base-roman-labels.mjs +4 -3
  244. package/src/reference/citation-format.mjs +74 -0
  245. package/src/reference/consistency.ts +47 -61
  246. package/src/reference/curriculum-alignment.ts +4 -41
  247. package/src/reference/derived-entries.ts +68 -0
  248. package/src/reference/derived-notation.mjs +121 -0
  249. package/src/reference/diagram-fit.ts +104 -0
  250. package/src/reference/diagram-layout.ts +182 -51
  251. package/src/reference/diagrams-validate.ts +2 -1
  252. package/src/reference/equations-validate.ts +41 -2
  253. package/src/reference/equations.mjs +120 -22
  254. package/src/reference/examples-validate.ts +1 -0
  255. package/src/reference/figure-fit.ts +139 -0
  256. package/src/reference/figure-pictures.ts +96 -0
  257. package/src/reference/figure-symbols.ts +117 -0
  258. package/src/reference/figures-validate.ts +2 -1
  259. package/src/reference/gate-abbreviations.ts +1 -1
  260. package/src/reference/gate-course-rules.ts +22 -7
  261. package/src/reference/gate-heading-length.ts +25 -7
  262. package/src/reference/gate-heading-structure.ts +40 -1
  263. package/src/reference/gate-indices.ts +470 -0
  264. package/src/reference/gate-math.ts +102 -13
  265. package/src/reference/gate-mdx-expression.ts +45 -0
  266. package/src/reference/gate-non-chapter-pages.ts +241 -0
  267. package/src/reference/gate-part-pages.ts +1 -1
  268. package/src/reference/gate-prop-text.ts +145 -0
  269. package/src/reference/gate-prose-notation.ts +19 -5
  270. package/src/reference/gate-retired-keys.ts +101 -0
  271. package/src/reference/gloss.ts +2 -0
  272. package/src/reference/katex-measure.ts +384 -0
  273. package/src/reference/keyed-float-validate.ts +22 -0
  274. package/src/reference/keyed-jsx-floats.mjs +11 -14
  275. package/src/reference/lab-katex-validate.ts +156 -0
  276. package/src/reference/lab-math-scope.ts +15 -1
  277. package/src/reference/lint-ignore.ts +4 -1
  278. package/src/reference/math-glyphs.mjs +851 -46
  279. package/src/reference/mixed-content.ts +11 -2
  280. package/src/reference/page-notation.ts +114 -14
  281. package/src/reference/raw-floats.mjs +37 -13
  282. package/src/reference/references.ts +14 -0
  283. package/src/reference/registry.ts +170 -9
  284. package/src/reference/rehype-fail-katex-errors.mjs +19 -0
  285. package/src/reference/rehype-heading-anchors.mjs +11 -1
  286. package/src/reference/remark-citation.mjs +0 -0
  287. package/src/reference/remark-notation.mjs +275 -23
  288. package/src/reference/render-lab-math.ts +40 -5
  289. package/src/reference/sections-validate.ts +67 -3
  290. package/src/reference/sections.mjs +92 -17
  291. package/src/reference/tables-validate.ts +40 -2
  292. package/src/reference/text-measure.ts +63 -0
  293. package/src/reference/text-widths.ts +133 -0
  294. package/src/reference/types.ts +75 -8
  295. package/src/rules/catalog.ts +241 -31
  296. package/src/rules/settings.ts +10 -0
  297. package/src/session/PreferenceStore.ts +53 -0
  298. package/src/styles/global.css +322 -83
@@ -0,0 +1,98 @@
1
+ import { ManifestLesson, Manifest } from '../manifest.js';
2
+ import { StaticPage, Block } from './static-tree.js';
3
+ import { CaptureResults } from './latex/capture.js';
4
+ import '../../curriculum/validation.js';
5
+ import '../../reference/consistency.js';
6
+ import '../../course-config-rRtME5UV.js';
7
+ import '../../rules/catalog.js';
8
+ import '../../rules/course-rules.js';
9
+ import '../collections.js';
10
+ import '../../reference/types.js';
11
+ import '../../reference/gate-retired-keys.js';
12
+ import '../../reference/lint-ignore.js';
13
+ import '../../reference/equations-validate.js';
14
+ import '../../reference/tables-validate.js';
15
+ import '../../reference/keyed-float-validate.js';
16
+ import '../../reference/figures-validate.js';
17
+ import '../../reference/diagrams-validate.js';
18
+ import '../../reference/examples-validate.js';
19
+ import '../../reference/folds-validate.js';
20
+ import '../../reference/knowledge-checks-validate.js';
21
+ import '../../reference/raw-floats-validate.js';
22
+ import '../../reference/lab-katex-validate.js';
23
+ import '../../reference/gate-math.js';
24
+ import '../../reference/mixed-content.js';
25
+ import '../../reference/gloss.js';
26
+ import '../../rules/waivers.js';
27
+ import '../knowledge-version.js';
28
+ import '../lesson-derivation.js';
29
+ import './html-tree.js';
30
+ import 'parse5';
31
+
32
+ /**
33
+ * What the print exporters (LaTeX, Markdown) share: every chapter's static
34
+ * reading tree, read once from Astro's output, and one capture pass that
35
+ * draws what only a browser can — a lab, a course component's SVG, and for
36
+ * Markdown an explico `<Diagram>` — as vector PDF and, for Markdown, PNG.
37
+ */
38
+ interface ExportLogger {
39
+ info(message: string): void;
40
+ warn(message: string): void;
41
+ }
42
+ declare const consoleLogger: ExportLogger;
43
+ /** In CI or with `EXPLICO_LATEX_STRICT=1`, a missing engine or browser fails the build. */
44
+ declare function strictMode(): boolean;
45
+ /** The content date as seconds since 1970: every export's timestamps. */
46
+ declare function epochOf(manifest: Pick<Manifest, 'about'>): number;
47
+ type FigureBlock = Extract<Block, {
48
+ kind: 'figure';
49
+ }>;
50
+ type DiagramBlock = Extract<Block, {
51
+ kind: 'diagram';
52
+ }>;
53
+ declare function figuresOf(blocks: readonly Block[]): FigureBlock[];
54
+ declare function diagramsOf(blocks: readonly Block[]): DiagramBlock[];
55
+ /** Every inline `image` block, in reading order. */
56
+ declare function imagesOf(blocks: readonly Block[]): Extract<Block, {
57
+ kind: 'image';
58
+ }>[];
59
+ /** The image file a URL names, by extension. */
60
+ declare function imageExtension(src: string): string;
61
+ /** Whether a figure is drawn by the browser rather than included as a file. */
62
+ declare function needsCapture(figure: FigureBlock): boolean;
63
+ /** The built file an image URL on a page names. */
64
+ declare function imageFile(outputRoot: string, base: string, route: string, src: string): string;
65
+ /** The page with each captured lab's live caption clause filled in. */
66
+ declare function withLiveClauses(page: StaticPage, captured: ReadonlyMap<string, {
67
+ liveHtml: string;
68
+ }>, base: string): StaticPage;
69
+ interface PreparedChapter {
70
+ readonly lesson: ManifestLesson;
71
+ /** The chapter's reading tree, its labs' live caption clauses filled in. */
72
+ readonly page: StaticPage;
73
+ }
74
+ interface PreparedChapters {
75
+ /** The site's base path, `/` or `/course/`. */
76
+ readonly base: string;
77
+ readonly chapters: readonly PreparedChapter[];
78
+ /** What the capture pass drew, by route and anchor. */
79
+ readonly captures: CaptureResults;
80
+ }
81
+ /** Read one built page into its reading tree. */
82
+ declare function readBuiltPage(outputRoot: string, slug: string): Promise<{
83
+ html: string;
84
+ page: StaticPage;
85
+ }>;
86
+ /**
87
+ * Read every chapter and draw its figures once, for whichever print exports
88
+ * the course has on: a figure the browser draws for LaTeX (as vector PDF)
89
+ * and for Markdown (as PNG too), and for Markdown each `<Diagram>`, which
90
+ * LaTeX draws itself as TikZ. Without Playwright's Chromium the figures stay
91
+ * placeholders, with a warning — or, in CI, the build fails.
92
+ */
93
+ declare function prepareChapters(outputRoot: string, manifest: Manifest, options?: {
94
+ readonly logger?: ExportLogger;
95
+ readonly projectRoot?: string;
96
+ }): Promise<PreparedChapters>;
97
+
98
+ export { type ExportLogger, type PreparedChapter, type PreparedChapters, consoleLogger, diagramsOf, epochOf, figuresOf, imageExtension, imageFile, imagesOf, needsCapture, prepareChapters, readBuiltPage, strictMode, withLiveClauses };
@@ -0,0 +1,164 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { extname, join, resolve } from "node:path";
3
+ import { parse } from "parse5";
4
+ import { inferBasePrefix } from "./html-tree.js";
5
+ import { readInlineFragment, readStaticPage } from "./static-tree.js";
6
+ import {
7
+ captureFigures,
8
+ courseChromium,
9
+ figureMeasure,
10
+ LABEL_PT
11
+ } from "./latex/capture.js";
12
+ const consoleLogger = {
13
+ info: (message) => console.log(message),
14
+ warn: (message) => console.warn(message)
15
+ };
16
+ function strictMode() {
17
+ const ci = process.env.CI;
18
+ return !!ci && ci !== "false" && ci !== "0" || process.env.EXPLICO_LATEX_STRICT === "1";
19
+ }
20
+ function epochOf(manifest) {
21
+ const date = manifest.about.knowledge.date;
22
+ const parsed = date ? Date.parse(`${date}T00:00:00Z`) : Number.NaN;
23
+ return Number.isFinite(parsed) ? Math.floor(parsed / 1e3) : 0;
24
+ }
25
+ function blocksOf(blocks, kind) {
26
+ const found = [];
27
+ const visit = (list) => {
28
+ for (const block of list) {
29
+ if (block.kind === kind) found.push(block);
30
+ if (block.kind === "list") block.items.forEach(visit);
31
+ if (block.kind === "examples") block.items.forEach((item) => visit(item.children));
32
+ if ("children" in block && block.kind !== "paragraph" && block.kind !== "heading" && Array.isArray(block.children)) {
33
+ visit(block.children);
34
+ }
35
+ }
36
+ };
37
+ visit(blocks);
38
+ return found;
39
+ }
40
+ function figuresOf(blocks) {
41
+ return blocksOf(blocks, "figure");
42
+ }
43
+ function diagramsOf(blocks) {
44
+ return blocksOf(blocks, "diagram");
45
+ }
46
+ function imagesOf(blocks) {
47
+ return blocksOf(blocks, "image");
48
+ }
49
+ const INCLUDABLE_IMAGES = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".pdf"]);
50
+ function imageExtension(src) {
51
+ return extname(new URL(src, "https://x.invalid/").pathname).toLowerCase();
52
+ }
53
+ function needsCapture(figure) {
54
+ if (figure.picture.kind === "capture") return true;
55
+ if (figure.picture.kind === "image") return !INCLUDABLE_IMAGES.has(imageExtension(figure.picture.src));
56
+ return false;
57
+ }
58
+ function imageFile(outputRoot, base, route, src) {
59
+ const url = new URL(src, `https://x.invalid${base}${route ? `${route}/` : ""}`);
60
+ let path = decodeURIComponent(url.pathname);
61
+ if (path.startsWith(base)) path = path.slice(base.length);
62
+ return resolve(outputRoot, path);
63
+ }
64
+ function withLiveClauses(page, captured, base) {
65
+ const update = (blocks) => blocks.map((block) => {
66
+ if (block.kind === "figure") {
67
+ const live = captured.get(block.anchor)?.liveHtml;
68
+ return live ? { ...block, live: readInlineFragment(live, page.route, base) } : block;
69
+ }
70
+ if (block.kind === "memo" || block.kind === "note" || block.kind === "aside" || block.kind === "fold" || block.kind === "quote") {
71
+ return { ...block, children: update(block.children) };
72
+ }
73
+ if (block.kind === "examples") {
74
+ return { ...block, items: block.items.map((item) => ({ ...item, children: update(item.children) })) };
75
+ }
76
+ if (block.kind === "list") return { ...block, items: block.items.map(update) };
77
+ return block;
78
+ });
79
+ return { ...page, blocks: update(page.blocks) };
80
+ }
81
+ async function readBuiltPage(outputRoot, slug) {
82
+ const route = slug.replace(/(?:^|\/)index$/u, "");
83
+ const html = await readFile(join(outputRoot, route, "index.html"), "utf8");
84
+ return { html, page: readStaticPage(html, slug, route) };
85
+ }
86
+ async function prepareChapters(outputRoot, manifest, options = {}) {
87
+ const logger = options.logger ?? consoleLogger;
88
+ const strict = strictMode();
89
+ const { latex, markdown } = manifest.lessonExports;
90
+ const read = [];
91
+ let base = "/";
92
+ for (const lesson of manifest.lessons) {
93
+ const { html, page } = await readBuiltPage(outputRoot, lesson.slug);
94
+ if (read.length === 0) base = inferBasePrefix(parse(html));
95
+ read.push({ lesson, page });
96
+ }
97
+ const requests = (latex || markdown ? read : []).map(({ page }) => ({
98
+ route: page.route,
99
+ anchors: [
100
+ ...figuresOf(page.blocks).filter(needsCapture).map((figure) => figure.anchor),
101
+ ...markdown ? diagramsOf(page.blocks).map((diagram) => diagram.anchor) : []
102
+ ]
103
+ })).filter((request) => request.anchors.length > 0);
104
+ let captures = /* @__PURE__ */ new Map();
105
+ if (requests.length > 0) {
106
+ const chromium = await courseChromium(options.projectRoot ?? process.cwd());
107
+ if (chromium) {
108
+ try {
109
+ captures = await captureFigures(requests, {
110
+ outputRoot,
111
+ base,
112
+ chromium,
113
+ measure: figureMeasure(manifest.lessonExports.latexPaper === "a4" ? "a4" : "letter"),
114
+ epoch: epochOf(manifest),
115
+ png: markdown
116
+ });
117
+ } catch (error) {
118
+ const message = error instanceof Error ? error.message : String(error);
119
+ if (strict || !/Executable doesn't exist|browserType\.launch/u.test(message)) throw error;
120
+ logger.warn(`Exports: figures stay placeholders, the browser did not start. ${message.split("\n")[0]}`);
121
+ }
122
+ } else {
123
+ const message = "Exports: drawing figures needs Playwright with Chromium in the course (`pnpm add -D @playwright/test && pnpm exec playwright install chromium`).";
124
+ if (strict) throw new Error(message);
125
+ logger.warn(`${message} Figures stay placeholders.`);
126
+ }
127
+ }
128
+ for (const [route, figures] of captures) {
129
+ for (const [anchor, figure] of figures) {
130
+ if (figure.machineFonts.length > 0) {
131
+ logger.warn(
132
+ `Exports: ${route}#${anchor} draws some text in this machine's ${figure.machineFonts.join(", ")}, not the site's fonts, so another machine may draw it differently.`
133
+ );
134
+ }
135
+ if (figure.labelPt !== void 0 && figure.labelPt < LABEL_PT - 0.5) {
136
+ logger.info(
137
+ `Exports: the labels of ${route}#${anchor} print at ${figure.labelPt} pt, not ${LABEL_PT} pt; any larger and two of them would touch.`
138
+ );
139
+ }
140
+ }
141
+ }
142
+ return {
143
+ base,
144
+ captures,
145
+ chapters: read.map(({ lesson, page }) => {
146
+ const captured = captures.get(page.route);
147
+ return { lesson, page: captured ? withLiveClauses(page, captured, base) : page };
148
+ })
149
+ };
150
+ }
151
+ export {
152
+ consoleLogger,
153
+ diagramsOf,
154
+ epochOf,
155
+ figuresOf,
156
+ imageExtension,
157
+ imageFile,
158
+ imagesOf,
159
+ needsCapture,
160
+ prepareChapters,
161
+ readBuiltPage,
162
+ strictMode,
163
+ withLiveClauses
164
+ };
@@ -0,0 +1,48 @@
1
+ import { Manifest } from '../manifest.js';
2
+ import '../../curriculum/validation.js';
3
+ import '../../reference/consistency.js';
4
+ import '../../course-config-rRtME5UV.js';
5
+ import '../../rules/catalog.js';
6
+ import '../../rules/course-rules.js';
7
+ import '../collections.js';
8
+ import '../../reference/types.js';
9
+ import '../../reference/gate-retired-keys.js';
10
+ import '../../reference/lint-ignore.js';
11
+ import '../../reference/equations-validate.js';
12
+ import '../../reference/tables-validate.js';
13
+ import '../../reference/keyed-float-validate.js';
14
+ import '../../reference/figures-validate.js';
15
+ import '../../reference/diagrams-validate.js';
16
+ import '../../reference/examples-validate.js';
17
+ import '../../reference/folds-validate.js';
18
+ import '../../reference/knowledge-checks-validate.js';
19
+ import '../../reference/raw-floats-validate.js';
20
+ import '../../reference/lab-katex-validate.js';
21
+ import '../../reference/gate-math.js';
22
+ import '../../reference/mixed-content.js';
23
+ import '../../reference/gloss.js';
24
+ import '../../rules/waivers.js';
25
+ import '../knowledge-version.js';
26
+ import '../lesson-derivation.js';
27
+
28
+ /**
29
+ * Every page a reader can print or download, by docs slug: each chapter
30
+ * (`motion/constant-acceleration`) and each non-chapter page the manifest
31
+ * knows — the landing page (`index`), a part's page (`motion/index`), the
32
+ * glossary. The 404 route is not one.
33
+ */
34
+ declare function exportPageSlugs(manifest: Pick<Manifest, 'lessons' | 'pages'>): string[];
35
+ /** Where Astro serves a docs slug, below the base: `motion/index` → `motion`, `index` → ``. */
36
+ declare function exportPageRoute(slug: string): string;
37
+ /** The name a download is saved under: `projectile-range.html`, `motion.html`, `index.html`. */
38
+ declare function standaloneFilename(slug: string): string;
39
+ /**
40
+ * The pages with an HTML download of their own: every chapter (D8). A page
41
+ * that is not a chapter — the landing page, a part's page, the glossary —
42
+ * is in its part's file and the course's (`combined-html.ts`).
43
+ */
44
+ declare function standalonePageSlugs(manifest: Pick<Manifest, 'lessons'>): string[];
45
+ /** The name the course's files are saved under: `Explico Playground — Motion` → `explico-playground-motion`. */
46
+ declare function courseFileStem(manifest: Pick<Manifest, 'about'>): string;
47
+
48
+ export { courseFileStem, exportPageRoute, exportPageSlugs, standaloneFilename, standalonePageSlugs };
@@ -0,0 +1,22 @@
1
+ function exportPageSlugs(manifest) {
2
+ return [...manifest.lessons, ...manifest.pages].map((page) => page.slug);
3
+ }
4
+ function exportPageRoute(slug) {
5
+ return slug.replace(/(?:^|\/)index$/u, "");
6
+ }
7
+ function standaloneFilename(slug) {
8
+ return `${exportPageRoute(slug).split("/").at(-1) || "index"}.html`;
9
+ }
10
+ function standalonePageSlugs(manifest) {
11
+ return manifest.lessons.map((lesson) => lesson.slug);
12
+ }
13
+ function courseFileStem(manifest) {
14
+ return manifest.about.material.name.normalize("NFKD").replace(/[\u0300-\u036f]/gu, "").toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || "course";
15
+ }
16
+ export {
17
+ courseFileStem,
18
+ exportPageRoute,
19
+ exportPageSlugs,
20
+ standaloneFilename,
21
+ standalonePageSlugs
22
+ };
@@ -0,0 +1,22 @@
1
+ import { DefaultTreeAdapterMap } from 'parse5';
2
+
3
+ /**
4
+ * Read-only queries over a parse5 tree, shared by the exporters that work on
5
+ * Astro's built pages (`standalone-html.ts`, `static-tree.ts`).
6
+ */
7
+ type HtmlNode = DefaultTreeAdapterMap['node'];
8
+ type HtmlParent = DefaultTreeAdapterMap['parentNode'];
9
+ type HtmlChild = DefaultTreeAdapterMap['childNode'];
10
+ type HtmlElement = DefaultTreeAdapterMap['element'];
11
+ type HtmlText = DefaultTreeAdapterMap['textNode'];
12
+ declare function isElement(node: HtmlNode): node is HtmlElement;
13
+ declare function childrenOf(node: HtmlNode): readonly HtmlChild[];
14
+ /** Depth-first, `<template>` content included. */
15
+ declare function walk(node: HtmlNode, visit: (node: HtmlNode) => void): void;
16
+ declare function elements(root: HtmlNode, predicate?: (node: HtmlElement) => boolean): HtmlElement[];
17
+ declare function attribute(node: HtmlElement, name: string): string | undefined;
18
+ declare function hasClass(node: HtmlElement, name: string): boolean;
19
+ /** The site's base path (`/`, `/course/`), read off Astro's asset URLs. */
20
+ declare function inferBasePrefix(document: HtmlNode): string;
21
+
22
+ export { type HtmlChild, type HtmlElement, type HtmlNode, type HtmlParent, type HtmlText, attribute, childrenOf, elements, hasClass, inferBasePrefix, isElement, walk };
@@ -0,0 +1,47 @@
1
+ function isElement(node) {
2
+ return "tagName" in node;
3
+ }
4
+ function childrenOf(node) {
5
+ return "childNodes" in node ? node.childNodes : [];
6
+ }
7
+ function walk(node, visit) {
8
+ visit(node);
9
+ for (const child of childrenOf(node)) walk(child, visit);
10
+ if (isElement(node) && node.tagName === "template") {
11
+ const template = node;
12
+ for (const child of template.content.childNodes) walk(child, visit);
13
+ }
14
+ }
15
+ function elements(root, predicate) {
16
+ const found = [];
17
+ walk(root, (node) => {
18
+ if (isElement(node) && (predicate?.(node) ?? true)) found.push(node);
19
+ });
20
+ return found;
21
+ }
22
+ function attribute(node, name) {
23
+ return node.attrs.find((entry) => entry.name === name)?.value;
24
+ }
25
+ function hasClass(node, name) {
26
+ return (attribute(node, "class") ?? "").split(/\s+/u).includes(name);
27
+ }
28
+ function inferBasePrefix(document) {
29
+ for (const node of elements(document)) {
30
+ for (const name of ["href", "src", "component-url", "renderer-url"]) {
31
+ const value = attribute(node, name);
32
+ if (!value?.startsWith("/")) continue;
33
+ const marker = value.indexOf("/astro-assets/");
34
+ if (marker !== -1) return `${value.slice(0, marker)}/`;
35
+ }
36
+ }
37
+ return "/";
38
+ }
39
+ export {
40
+ attribute,
41
+ childrenOf,
42
+ elements,
43
+ hasClass,
44
+ inferBasePrefix,
45
+ isElement,
46
+ walk
47
+ };
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The capture pass: draw each figure a browser has to draw (a hydrated lab, a
3
+ * course component's SVG) and print it as a vector PDF for the LaTeX source.
4
+ *
5
+ * Recipe, from the spike that tested it:
6
+ * 1. Serve the finished `dist/` on loopback, at the site's base path.
7
+ * 2. Open the page in a fresh light-mode context and fire
8
+ * `explico:prepare-static-export` in `markdown` mode, which opens every
9
+ * disclosure so a figure in a collapsed section gets a layout box.
10
+ * 3. Scroll each figure into view so its `client:visible` lab hydrates, and
11
+ * wait until it has drawn and stopped changing.
12
+ * 4. Clone the hydrated figure into a clean page *on the same origin* that
13
+ * links the site's stylesheets — hiding the rest of the live page around it
14
+ * loses Plot's tick labels and blanks a diagram, and a page on another
15
+ * origin cannot load the site's fonts (fonts are CORS-restricted).
16
+ * 5. Print that page at the figure's size: every path and label stays vector,
17
+ * with its fonts embedded.
18
+ *
19
+ * Playwright is resolved from the course itself, so the browser it drives is
20
+ * the one the course installed for its own tests.
21
+ */
22
+ interface CaptureRequest {
23
+ /** Base-relative route of the page, e.g. `projectiles/optimal-launch-angle`. */
24
+ readonly route: string;
25
+ /** Anchor ids of the figures and diagrams to capture (`fig-…`, `dia-…`). */
26
+ readonly anchors: readonly string[];
27
+ }
28
+ interface CapturedFigure {
29
+ /** The figure's picture as a one-page vector PDF. */
30
+ readonly pdf: Uint8Array;
31
+ /** The same picture as PNG at two device pixels per CSS pixel, when asked for. */
32
+ readonly png?: Uint8Array;
33
+ /** The caption's live clause after the lab wrote it, as HTML. */
34
+ readonly liveHtml: string;
35
+ /** Fonts some of the figure's text came from that the machine supplied, not the site. */
36
+ readonly machineFonts: readonly string[];
37
+ /** The size, in points, the figure's most common SVG label prints at in the book. */
38
+ readonly labelPt?: number;
39
+ }
40
+ /** By route, then by figure anchor. */
41
+ type CaptureResults = Map<string, Map<string, CapturedFigure>>;
42
+ interface ChromiumLike {
43
+ launch(options?: Record<string, unknown>): Promise<BrowserLike>;
44
+ }
45
+ interface BrowserLike {
46
+ newContext(options?: Record<string, unknown>): Promise<ContextLike>;
47
+ close(): Promise<void>;
48
+ }
49
+ interface ContextLike {
50
+ newPage(): Promise<PageLike>;
51
+ /** Chromium only: the DevTools protocol, which names the fonts text was drawn with. */
52
+ newCDPSession?(page: PageLike): Promise<SessionLike>;
53
+ close(): Promise<void>;
54
+ }
55
+ interface SessionLike {
56
+ send(method: string, params?: Record<string, unknown>): Promise<unknown>;
57
+ detach(): Promise<void>;
58
+ }
59
+ interface PageLike {
60
+ goto(url: string, options?: Record<string, unknown>): Promise<unknown>;
61
+ setContent(html: string, options?: Record<string, unknown>): Promise<void>;
62
+ evaluate<R, A>(fn: (arg: A) => R | Promise<R>, arg?: A): Promise<R>;
63
+ emulateMedia(options: Record<string, unknown>): Promise<void>;
64
+ pdf(options: Record<string, unknown>): Promise<Uint8Array>;
65
+ screenshot(options: Record<string, unknown>): Promise<Uint8Array>;
66
+ close(): Promise<void>;
67
+ }
68
+ /** Playwright's Chromium from the course's own dependencies, or `undefined`. */
69
+ declare function courseChromium(projectRoot: string): Promise<ChromiumLike | undefined>;
70
+ /** The size a figure's most common label prints at, as `\footnotesize` in the book's 11 pt. */
71
+ declare const LABEL_PT = 9;
72
+ /** The book's measure, in PostScript points: the most a figure is set at. */
73
+ interface FigureMeasure {
74
+ /** `\linewidth` of the page. */
75
+ readonly width: number;
76
+ /** `0.6\textheight`, the tallest a figure is included. */
77
+ readonly maxHeight: number;
78
+ }
79
+ /** The measure of explico.cls on letter or A4 paper with one-inch margins. */
80
+ declare function figureMeasure(paper: 'letter' | 'a4'): FigureMeasure;
81
+ /**
82
+ * Chromium stamps a PDF with the moment it prints it (`/CreationDate`,
83
+ * `/ModDate`), and LaTeX copies an included PDF's dates into its own, so
84
+ * no two builds would agree. Each date becomes `epoch`'s, written in the
85
+ * same number of bytes so the cross-reference table stays right.
86
+ */
87
+ declare function stampPdfDates(pdf: Uint8Array, epoch: number): Uint8Array;
88
+ interface CaptureOptions {
89
+ /** The finished build output. */
90
+ readonly outputRoot: string;
91
+ /** The site's base path, `/` or `/course/`. */
92
+ readonly base: string;
93
+ readonly chromium: ChromiumLike;
94
+ /** The book's measure, which sizes figure labels (letter paper's by default). */
95
+ readonly measure?: FigureMeasure;
96
+ /** Seconds since 1970 to date every figure PDF with, so a build is reproducible. */
97
+ readonly epoch?: number;
98
+ /** Also take each figure as PNG, from the same page as its PDF (Markdown). */
99
+ readonly png?: boolean;
100
+ /** Per-figure limit for hydrating and settling. */
101
+ readonly timeoutMs?: number;
102
+ }
103
+ /** Capture every requested figure, one browser for the whole build. */
104
+ declare function captureFigures(requests: readonly CaptureRequest[], options: CaptureOptions): Promise<CaptureResults>;
105
+
106
+ export { type CaptureOptions, type CaptureRequest, type CaptureResults, type CapturedFigure, type FigureMeasure, LABEL_PT, captureFigures, courseChromium, figureMeasure, stampPdfDates };