explico 0.12.0 → 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 (279) hide show
  1. package/CHANGELOG.md +1122 -1
  2. package/README.md +91 -13
  3. package/dist/compiler/astro.d.ts +4 -3
  4. package/dist/compiler/astro.js +45 -15
  5. package/dist/compiler/cli.d.ts +26 -12
  6. package/dist/compiler/cli.js +96 -25
  7. package/dist/compiler/collections.d.ts +9 -1
  8. package/dist/compiler/collections.js +55 -16
  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 +14 -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 +87 -6
  51. package/dist/compiler/manifest.js +174 -31
  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 +2 -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 +2 -2
  61. package/dist/reference/base-operators.mjs +2 -2
  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/figures-validate.d.ts +1 -1
  88. package/dist/reference/gate-abbreviations.d.ts +1 -1
  89. package/dist/reference/gate-abbreviations.js +1 -1
  90. package/dist/reference/gate-course-rules.d.ts +4 -3
  91. package/dist/reference/gate-course-rules.js +9 -4
  92. package/dist/reference/gate-heading-length.d.ts +2 -0
  93. package/dist/reference/gate-heading-length.js +7 -5
  94. package/dist/reference/gate-heading-structure.js +26 -0
  95. package/dist/reference/gate-indices.d.ts +35 -0
  96. package/dist/reference/gate-indices.js +299 -0
  97. package/dist/reference/gate-math.d.ts +32 -2
  98. package/dist/reference/gate-math.js +68 -12
  99. package/dist/reference/gate-mdx-expression.d.ts +17 -0
  100. package/dist/reference/gate-mdx-expression.js +29 -0
  101. package/dist/reference/gate-part-pages.d.ts +1 -1
  102. package/dist/reference/gate-prop-text.d.ts +28 -0
  103. package/dist/reference/gate-prop-text.js +106 -0
  104. package/dist/reference/gate-prose-notation.d.ts +3 -1
  105. package/dist/reference/gate-prose-notation.js +4 -2
  106. package/dist/reference/gate-retired-keys.d.ts +35 -0
  107. package/dist/reference/gate-retired-keys.js +58 -0
  108. package/dist/reference/gloss.d.ts +2 -0
  109. package/dist/reference/katex-measure.d.ts +17 -0
  110. package/dist/reference/katex-measure.js +223 -0
  111. package/dist/reference/keyed-float-validate.js +12 -0
  112. package/dist/reference/keyed-jsx-floats.d.mts +2 -2
  113. package/dist/reference/keyed-jsx-floats.mjs +11 -14
  114. package/dist/reference/lab-math-scope.d.ts +1 -0
  115. package/dist/reference/lab-math-scope.js +13 -2
  116. package/dist/reference/math-glyphs.d.mts +94 -2
  117. package/dist/reference/math-glyphs.mjs +851 -46
  118. package/dist/reference/mixed-content.d.ts +8 -3
  119. package/dist/reference/mixed-content.js +4 -2
  120. package/dist/reference/page-notation.d.ts +34 -4
  121. package/dist/reference/page-notation.js +54 -2
  122. package/dist/reference/references.d.ts +11 -1
  123. package/dist/reference/references.js +6 -0
  124. package/dist/reference/registry.js +104 -10
  125. package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
  126. package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
  127. package/dist/reference/rehype-heading-anchors.mjs +11 -1
  128. package/dist/reference/remark-citation.mjs +0 -0
  129. package/dist/reference/remark-notation.mjs +84 -20
  130. package/dist/reference/render-lab-math.d.ts +2 -0
  131. package/dist/reference/render-lab-math.js +17 -5
  132. package/dist/reference/sections-validate.d.ts +28 -3
  133. package/dist/reference/sections-validate.js +31 -4
  134. package/dist/reference/sections.d.mts +24 -6
  135. package/dist/reference/sections.mjs +92 -17
  136. package/dist/reference/tables-validate.d.ts +1 -1
  137. package/dist/reference/tables-validate.js +24 -1
  138. package/dist/reference/text-measure.d.ts +10 -0
  139. package/dist/reference/text-measure.js +41 -0
  140. package/dist/reference/text-widths.d.ts +10 -0
  141. package/dist/reference/text-widths.js +1238 -0
  142. package/dist/reference/types.d.ts +64 -8
  143. package/dist/rules/catalog.js +191 -30
  144. package/dist/rules/in-effect.d.ts +1 -1
  145. package/dist/rules/rules.schema.json +79 -24
  146. package/dist/rules/settings.d.ts +1 -1
  147. package/dist/rules/settings.js +7 -0
  148. package/dist/session/PreferenceStore.d.ts +18 -1
  149. package/dist/session/PreferenceStore.js +29 -1
  150. package/docs/authoring/README.md +32 -21
  151. package/docs/authoring/agents.md +38 -7
  152. package/docs/authoring/components.md +111 -6
  153. package/docs/authoring/migrating.md +663 -0
  154. package/docs/authoring/notation.md +281 -29
  155. package/docs/authoring/retired.md +1 -0
  156. package/docs/authoring/structure-rules.md +10 -41
  157. package/docs/authoring/structure.md +53 -2
  158. package/docs/authoring/visualizations.md +144 -18
  159. package/package.json +12 -7
  160. package/src/compiler/astro.ts +57 -17
  161. package/src/compiler/cli.ts +143 -35
  162. package/src/compiler/collections.ts +92 -19
  163. package/src/compiler/content-schema.ts +108 -0
  164. package/src/compiler/content-schemas.ts +391 -0
  165. package/src/compiler/course-config.ts +86 -15
  166. package/src/compiler/exports/combined-html.ts +669 -0
  167. package/src/compiler/exports/download-index.ts +36 -0
  168. package/src/compiler/exports/export-chapters.ts +233 -0
  169. package/src/compiler/exports/export-pages.ts +44 -0
  170. package/src/compiler/exports/html-tree.ts +62 -0
  171. package/src/compiler/exports/latex/capture.ts +651 -0
  172. package/src/compiler/exports/latex/compile.ts +168 -0
  173. package/src/compiler/exports/latex/document.ts +479 -0
  174. package/src/compiler/exports/latex/explico.cls +415 -0
  175. package/src/compiler/exports/latex/numbering.ts +74 -0
  176. package/src/compiler/exports/latex/serialize.ts +787 -0
  177. package/src/compiler/exports/latex/texlive-packages.txt +76 -0
  178. package/src/compiler/exports/latex-export.ts +255 -0
  179. package/src/compiler/exports/markdown/serialize.ts +632 -0
  180. package/src/compiler/exports/markdown-export.ts +329 -0
  181. package/src/compiler/exports/standalone-html.ts +39 -70
  182. package/src/compiler/exports/static-tree.ts +944 -0
  183. package/src/compiler/exports/zip.ts +88 -0
  184. package/src/compiler/lesson-derivation.ts +13 -8
  185. package/src/compiler/manifest.ts +299 -28
  186. package/src/compiler/render-diagnostics.ts +111 -0
  187. package/src/components/HoverPanel.astro +204 -6
  188. package/src/components/PartOverview.astro +12 -9
  189. package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
  190. package/src/components/assessments/AssessmentRunner.tsx +7 -1
  191. package/src/components/assessments/AssessmentSet.astro +14 -1
  192. package/src/components/block-heading.ts +38 -0
  193. package/src/components/card-arrow.ts +18 -4
  194. package/src/components/card-carry.ts +406 -0
  195. package/src/components/card-drag.ts +1 -30
  196. package/src/components/card-fetch.ts +87 -0
  197. package/src/components/card-manager.ts +1222 -38
  198. package/src/components/card-memory.ts +5 -1
  199. package/src/components/card-placement.ts +211 -13
  200. package/src/components/card-side.ts +281 -31
  201. package/src/components/citation/CitationLayer.astro +45 -40
  202. package/src/components/diagram/Diagram.astro +123 -44
  203. package/src/components/figure/Figure.astro +11 -0
  204. package/src/components/figure/FloatCaption.astro +4 -17
  205. package/src/components/float-toolbar.ts +274 -0
  206. package/src/components/memo/Memo.astro +21 -10
  207. package/src/components/memo/ShortNote.astro +4 -0
  208. package/src/components/notation/NotationGlossary.astro +22 -17
  209. package/src/components/notation/NotationLayer.astro +300 -79
  210. package/src/components/notation/NotationMath.astro +8 -1
  211. package/src/components/notation/render-notation-math.ts +51 -5
  212. package/src/components/readable-text.ts +25 -0
  213. package/src/components/reference/FloatRefLayer.astro +162 -89
  214. package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
  215. package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
  216. package/src/components/starlight/LayoutPageSidebar.astro +17 -3
  217. package/src/components/starlight/LayoutSocialIcons.astro +79 -29
  218. package/src/components/starlight/LessonExportActions.astro +384 -310
  219. package/src/components/starlight/LessonFooter.astro +47 -9
  220. package/src/components/starlight/PrintController.astro +28 -0
  221. package/src/components/starlight/TextSizeControl.astro +137 -0
  222. package/src/components/starlight/print-controller.ts +380 -0
  223. package/src/components/starlight/section-summary.ts +13 -5
  224. package/src/components/table/Table.astro +13 -21
  225. package/src/components/table/table-truncation.ts +15 -0
  226. package/src/content-config.ts +20 -312
  227. package/src/curriculum/validation.ts +72 -24
  228. package/src/reference/base-abbreviations.mjs +2 -2
  229. package/src/reference/base-operators.mjs +2 -2
  230. package/src/reference/base-roman-labels.mjs +4 -3
  231. package/src/reference/citation-format.mjs +74 -0
  232. package/src/reference/consistency.ts +47 -61
  233. package/src/reference/curriculum-alignment.ts +4 -41
  234. package/src/reference/derived-entries.ts +68 -0
  235. package/src/reference/derived-notation.mjs +121 -0
  236. package/src/reference/diagram-fit.ts +104 -0
  237. package/src/reference/diagram-layout.ts +182 -51
  238. package/src/reference/diagrams-validate.ts +2 -1
  239. package/src/reference/equations-validate.ts +41 -2
  240. package/src/reference/equations.mjs +120 -22
  241. package/src/reference/examples-validate.ts +1 -0
  242. package/src/reference/figure-fit.ts +139 -0
  243. package/src/reference/figure-pictures.ts +96 -0
  244. package/src/reference/figures-validate.ts +2 -1
  245. package/src/reference/gate-abbreviations.ts +1 -1
  246. package/src/reference/gate-course-rules.ts +22 -7
  247. package/src/reference/gate-heading-length.ts +25 -7
  248. package/src/reference/gate-heading-structure.ts +40 -1
  249. package/src/reference/gate-indices.ts +470 -0
  250. package/src/reference/gate-math.ts +102 -13
  251. package/src/reference/gate-mdx-expression.ts +45 -0
  252. package/src/reference/gate-prop-text.ts +145 -0
  253. package/src/reference/gate-prose-notation.ts +19 -5
  254. package/src/reference/gate-retired-keys.ts +101 -0
  255. package/src/reference/gloss.ts +2 -0
  256. package/src/reference/katex-measure.ts +384 -0
  257. package/src/reference/keyed-float-validate.ts +22 -0
  258. package/src/reference/keyed-jsx-floats.mjs +11 -14
  259. package/src/reference/lab-math-scope.ts +15 -1
  260. package/src/reference/math-glyphs.mjs +851 -46
  261. package/src/reference/mixed-content.ts +11 -2
  262. package/src/reference/page-notation.ts +100 -3
  263. package/src/reference/references.ts +14 -0
  264. package/src/reference/registry.ts +154 -8
  265. package/src/reference/rehype-fail-katex-errors.mjs +19 -0
  266. package/src/reference/rehype-heading-anchors.mjs +11 -1
  267. package/src/reference/remark-citation.mjs +0 -0
  268. package/src/reference/remark-notation.mjs +84 -20
  269. package/src/reference/render-lab-math.ts +29 -5
  270. package/src/reference/sections-validate.ts +67 -3
  271. package/src/reference/sections.mjs +92 -17
  272. package/src/reference/tables-validate.ts +40 -2
  273. package/src/reference/text-measure.ts +63 -0
  274. package/src/reference/text-widths.ts +133 -0
  275. package/src/reference/types.ts +75 -8
  276. package/src/rules/catalog.ts +205 -29
  277. package/src/rules/settings.ts +10 -0
  278. package/src/session/PreferenceStore.ts +53 -0
  279. package/src/styles/global.css +291 -75
@@ -0,0 +1,102 @@
1
+ import { ManifestLesson, ManifestPart, Manifest } from '../../manifest.js';
2
+ import { StaticPage } from '../static-tree.js';
3
+ import { CheckItem } from './serialize.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
+ * The LaTeX source of one chapter as a folder: what a reader unzips and what
34
+ * explico compiles to make the PDF it offers. Files are returned in memory,
35
+ * keyed by their path inside the folder, so the same tree feeds the zip and
36
+ * the compile.
37
+ */
38
+ interface SourceFile {
39
+ readonly path: string;
40
+ readonly content: string | Uint8Array;
41
+ }
42
+ /** explico's class, shipped next to this module (copied into `dist/` by `copy-assets`). */
43
+ declare function explicoClass(): string;
44
+ /** `parts/02-projectiles/02-optimal-launch-angle.tex` for chapter 2.2. */
45
+ declare function chapterPath(manifest: Pick<Manifest, 'parts'>, lesson: ManifestLesson): string;
46
+ /** The folder name a chapter's download unzips to. */
47
+ declare function chapterFolderName(lesson: ManifestLesson): string;
48
+ /** A chapter's check answers, by assessment id, in authored order. */
49
+ declare function checkItems(manifest: Pick<Manifest, 'assessmentContent'>, lesson: Pick<ManifestLesson, 'id'>): Map<string, CheckItem[]>;
50
+ /**
51
+ * The PDF's file identifier (its trailer `/ID`), which the engines derive
52
+ * from the output file's path unless told: fixed from the course, its
53
+ * content version and the document, so every compile of a source agrees.
54
+ */
55
+ declare function trailerId(manifest: Pick<Manifest, 'about'>, document: string): string;
56
+ interface ChapterSourceInput {
57
+ readonly manifest: Manifest;
58
+ readonly lesson: ManifestLesson;
59
+ readonly page: StaticPage;
60
+ /** Routes of every chapter in the document (just this one for a chapter download). */
61
+ readonly documentRoutes: ReadonlySet<string>;
62
+ /** Figure files already produced for this chapter, by float anchor, with their bytes. */
63
+ readonly pictures: ReadonlyMap<string, {
64
+ readonly path: string;
65
+ readonly content: Uint8Array;
66
+ }>;
67
+ }
68
+ /** Every file of one chapter's LaTeX folder. */
69
+ declare function chapterSource(input: ChapterSourceInput): SourceFile[];
70
+ /** A chapter of a book, with its figure files. */
71
+ interface BookChapter {
72
+ readonly lesson: ManifestLesson;
73
+ readonly page: StaticPage;
74
+ readonly pictures: ReadonlyMap<string, {
75
+ readonly path: string;
76
+ readonly content: Uint8Array;
77
+ }>;
78
+ }
79
+ /** A part of a book: its own page, when it has one, and its chapters in order. */
80
+ interface BookPart {
81
+ readonly part: ManifestPart;
82
+ readonly overview?: StaticPage;
83
+ readonly chapters: readonly BookChapter[];
84
+ }
85
+ interface BookSourceInput {
86
+ readonly manifest: Manifest;
87
+ readonly parts: readonly BookPart[];
88
+ /** What the book is (`part/projectiles`, `course`): it names the PDF's identifier. */
89
+ readonly document: string;
90
+ readonly title: string;
91
+ /** What the book belongs to, on its title page: `Part 2 of …` for a part. */
92
+ readonly subtitle?: string;
93
+ }
94
+ /**
95
+ * Every file of a part's or the course's LaTeX folder: a book with a title
96
+ * page, contents and lists, each part opening with its own page's text,
97
+ * every chapter in order with references between them as `\ref`s, and the
98
+ * notation and abbreviation appendices.
99
+ */
100
+ declare function bookSource(input: BookSourceInput): SourceFile[];
101
+
102
+ export { type BookChapter, type BookPart, type BookSourceInput, type ChapterSourceInput, type SourceFile, bookSource, chapterFolderName, chapterPath, chapterSource, checkItems, explicoClass, trailerId };
@@ -0,0 +1,349 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import {
4
+ escapeText,
5
+ serializeAbbreviationAppendix,
6
+ serializeChapter,
7
+ serializeNotationAppendix,
8
+ serializePartOpener
9
+ } from "./serialize.js";
10
+ function explicoClass() {
11
+ return readFileSync(new URL("./explico.cls", import.meta.url), "utf8");
12
+ }
13
+ function twoDigits(value) {
14
+ return String(value).padStart(2, "0");
15
+ }
16
+ function chapterPath(manifest, lesson) {
17
+ const part = manifest.parts.find((entry) => entry.ordinal === lesson.part);
18
+ const partDir = part?.dir ?? lesson.slug.split("/")[0] ?? "part";
19
+ const name = lesson.slug.split("/").at(-1) ?? lesson.slug;
20
+ return `parts/${twoDigits(lesson.part)}-${partDir}/${twoDigits(lesson.chapter)}-${name}.tex`;
21
+ }
22
+ function chapterFolderName(lesson) {
23
+ return `${lesson.slug.split("/").at(-1) ?? lesson.slug}-latex`;
24
+ }
25
+ function checkItems(manifest, lesson) {
26
+ const items = /* @__PURE__ */ new Map();
27
+ for (const item of manifest.assessmentContent) {
28
+ if (item.lessonId !== lesson.id) continue;
29
+ const list = items.get(item.assessmentId) ?? [];
30
+ list.push(item);
31
+ items.set(item.assessmentId, list);
32
+ }
33
+ return items;
34
+ }
35
+ const DOCUMENT_METADATA = [
36
+ "% A fixed seed for TeX's random numbers, before tagging loads: tagpdf names",
37
+ "% one of its structure namespaces with random digits, and the same source",
38
+ "% should give the same PDF.",
39
+ "\\ExplSyntaxOn \\sys_gset_rand_seed:n { 1 } \\ExplSyntaxOff",
40
+ "% LuaLaTeX: a tagged, accessible PDF (PDF/UA-2) with MathML for every formula.",
41
+ "% pdfLaTeX tags it less completely, so it claims no standard.",
42
+ "\\ifdefined\\directlua",
43
+ " \\DocumentMetadata{lang=en,pdfversion=2.0,pdfstandard=ua-2,tagging=on}",
44
+ "\\else",
45
+ " \\DocumentMetadata{lang=en,pdfversion=2.0,tagging=on}",
46
+ "\\fi"
47
+ ];
48
+ function trailerId(manifest, document) {
49
+ return createHash("md5").update(`${manifest.about.material.name}
50
+ ${manifest.about.knowledge.version}
51
+ ${document}`).digest("hex").toUpperCase();
52
+ }
53
+ function trailerIdLine(id) {
54
+ return `\\ifdefined\\directlua\\pdfvariable trailerid {[<${id}><${id}>]}\\else\\pdftrailerid{${id}}\\fi`;
55
+ }
56
+ function mainTex(manifest, lesson, chapterFile) {
57
+ const paper = manifest.lessonExports.latexPaper === "a4" ? "a4paper" : "letterpaper";
58
+ const { numbering } = manifest;
59
+ const offsets = lesson.numbering;
60
+ const course = manifest.about.material.name;
61
+ const authors = manifest.about.authors?.map((author) => author.name).filter((name) => !!name);
62
+ return [
63
+ "% Compile with `latexmk` (LuaLaTeX, see latexmkrc) or `latexmk -pdf` (pdfLaTeX).",
64
+ ...DOCUMENT_METADATA,
65
+ `\\documentclass[${paper}]{explico}`,
66
+ "% The same identifier for every compile of this source.",
67
+ trailerIdLine(trailerId(manifest, lesson.slug)),
68
+ ...numbering.floats === "part" ? ["\\ExplicoFloatsByPart"] : [],
69
+ `\\ExplicoSectionDepth{${numbering.sections.show ? numbering.sections.depth : 1}}`,
70
+ `\\hypersetup{pdftitle={${escapeText(lesson.title)}},pdfauthor={${escapeText(
71
+ authors && authors.length > 0 ? authors.join(", ") : course
72
+ )}},pdfsubject={${escapeText(course)}}}`,
73
+ "\\begin{document}",
74
+ `\\ExplicoStartChapterAt{${lesson.part}}{${lesson.chapter}}{${offsets.equations}}{${offsets.tables}}{${offsets.figures}}{${offsets.diagrams}}{${offsets.checks}}{${offsets.examples}}`,
75
+ `\\input{${chapterFile.replace(/\.tex$/u, "")}}`,
76
+ "\\end{document}",
77
+ ""
78
+ ].join("\n");
79
+ }
80
+ const LATEXMKRC = [
81
+ "# LuaLaTeX by default; `latexmk -pdf` uses pdfLaTeX instead.",
82
+ "$pdf_mode = 4;",
83
+ "$postscript_mode = $dvi_mode = 0;",
84
+ "# luaotfload rewrites its font caches as fonts are looked up; they are not",
85
+ "# this document's sources, so a rewrite is no reason to run again.",
86
+ "$hash_calc_ignore_pattern{'luc'} = '^';",
87
+ ""
88
+ ].join("\n");
89
+ function readme(manifest, lesson, url) {
90
+ const course = manifest.about.material.name;
91
+ return [
92
+ `# ${lesson.title}`,
93
+ "",
94
+ `The LaTeX source of a chapter of *${course}* (content version ${manifest.about.knowledge.version}).` + (url ? ` Interactive version: ${url}` : ""),
95
+ "",
96
+ "## Build",
97
+ "",
98
+ "Needs TeX Live 2025 or later.",
99
+ "",
100
+ "```sh",
101
+ "latexmk # LuaLaTeX (preferred)",
102
+ "latexmk -pdf # pdfLaTeX",
103
+ "```",
104
+ "",
105
+ "LuaLaTeX writes a tagged, accessible PDF (PDF/UA-2) with MathML for every",
106
+ "formula. pdfLaTeX tags it less completely and claims no standard.",
107
+ "",
108
+ "## Files",
109
+ "",
110
+ "- `main.tex` \u2014 the document: class options, then the chapter.",
111
+ "- `explico.cls` \u2014 explico's book class: layout, numbering, and the",
112
+ " environments the chapter uses (memos, notes, worked examples, checks).",
113
+ `- \`${chapterPath(manifest, lesson)}\` \u2014 the chapter.`,
114
+ "- `figures/` \u2014 the chapter's figures, as vector PDF where the website draws them.",
115
+ "",
116
+ "Numbers are LaTeX's own and match the website when the source is built",
117
+ "unchanged; edit freely and they follow.",
118
+ ""
119
+ ].join("\n");
120
+ }
121
+ function chapterSource(input) {
122
+ const { manifest, lesson, page } = input;
123
+ const file = chapterPath(manifest, lesson);
124
+ const base = manifest.lessonExports.canonicalBaseUrl;
125
+ const url = base ? `${base}${lesson.slug}/` : void 0;
126
+ const chapter = serializeChapter({
127
+ page,
128
+ documentRoutes: input.documentRoutes,
129
+ canonicalBaseUrl: base,
130
+ pictures: new Map([...input.pictures].map(([anchor, picture]) => [anchor, picture.path])),
131
+ checkItems: checkItems(manifest, lesson),
132
+ source: {
133
+ course: manifest.about.material.name,
134
+ version: manifest.about.knowledge.version,
135
+ ...url ? { url } : {}
136
+ }
137
+ });
138
+ return withScripts([
139
+ { path: "README.md", content: readme(manifest, lesson, url) },
140
+ { path: "latexmkrc", content: LATEXMKRC },
141
+ { path: "main.tex", content: mainTex(manifest, lesson, file) },
142
+ { path: "explico.cls", content: explicoClass() },
143
+ { path: file, content: chapter },
144
+ ...[...input.pictures.values()].map((picture) => ({ path: picture.path, content: picture.content }))
145
+ ]);
146
+ }
147
+ const OTHER_SCRIPTS = /[\u0370-\u03FF\u0400-\u04FF\u1F00-\u1FFF\u2E80-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF\uFF00-\uFFEF\u2600-\u27BF\u{1F000}-\u{1FAFF}]/u;
148
+ const MATH_GREEK = /[αβγδεθλμπρστφωΓΔΘΛΠΣΦΩ]/gu;
149
+ function withScripts(files) {
150
+ const needed = files.some(
151
+ (file) => file.path.endsWith(".tex") && typeof file.content === "string" && (file.content.includes("\\ExplicoGreekWord{") || OTHER_SCRIPTS.test(file.content.replace(MATH_GREEK, "")))
152
+ );
153
+ if (!needed) return files;
154
+ return files.map(
155
+ (file) => file.path === "main.tex" && typeof file.content === "string" ? { ...file, content: file.content.replace(/\\documentclass\[([^\]]*)\]\{explico\}/u, "\\documentclass[$1,scripts]{explico}") } : file
156
+ );
157
+ }
158
+ function countNumbered(parts, kind) {
159
+ let count = 0;
160
+ const visit = (blocks) => {
161
+ for (const block of blocks) {
162
+ if (block.kind === kind && "number" in block && block.number) count += 1;
163
+ if (block.kind === "list") block.items.forEach(visit);
164
+ if (block.kind === "examples") block.items.forEach((item) => visit(item.children));
165
+ if ("children" in block && Array.isArray(block.children) && block.kind !== "paragraph" && block.kind !== "heading") {
166
+ visit(block.children);
167
+ }
168
+ }
169
+ };
170
+ for (const { chapters } of parts) for (const { page } of chapters) visit(page.blocks);
171
+ return count;
172
+ }
173
+ function appendixNotation(manifest, parts) {
174
+ const local = /* @__PURE__ */ new Map();
175
+ for (const lesson of manifest.lessons) {
176
+ for (const definition of lesson.notation.definitions) {
177
+ if (definition.scope === "local") local.set(definition.key, `${lesson.part}.${lesson.chapter}`);
178
+ }
179
+ }
180
+ const seen = /* @__PURE__ */ new Set();
181
+ const entries = [];
182
+ const add = (page) => {
183
+ for (const entry of page.notation) {
184
+ if (seen.has(entry.key)) continue;
185
+ seen.add(entry.key);
186
+ const chapter = local.get(entry.key);
187
+ entries.push({ entry, page, ...chapter ? { chapter } : {} });
188
+ }
189
+ };
190
+ for (const { overview, chapters } of parts) {
191
+ if (overview) add(overview);
192
+ for (const { page } of chapters) add(page);
193
+ }
194
+ return entries;
195
+ }
196
+ function bookMainTex(input, inputs, appendices) {
197
+ const { manifest, parts } = input;
198
+ const paper = manifest.lessonExports.latexPaper === "a4" ? "a4paper" : "letterpaper";
199
+ const { numbering } = manifest;
200
+ const course = manifest.about.material.name;
201
+ const authors = manifest.about.authors?.map((author) => author.name).filter((name) => !!name) ?? [];
202
+ const url = manifest.lessonExports.canonicalBaseUrl;
203
+ const lists = [
204
+ ...countNumbered(parts, "figure") > 0 ? ["\\listoffigures"] : [],
205
+ ...countNumbered(parts, "table") > 0 ? ["\\listoftables"] : [],
206
+ ...countNumbered(parts, "diagram") > 0 ? ["\\listofdiagrams"] : []
207
+ ];
208
+ return [
209
+ "% Compile with `latexmk` (LuaLaTeX, see latexmkrc) or `latexmk -pdf` (pdfLaTeX).",
210
+ ...DOCUMENT_METADATA,
211
+ `\\documentclass[${paper}]{explico}`,
212
+ "% The same identifier for every compile of this source.",
213
+ trailerIdLine(trailerId(manifest, input.document)),
214
+ ...numbering.floats === "part" ? ["\\ExplicoFloatsByPart"] : [],
215
+ `\\ExplicoSectionDepth{${numbering.sections.show ? numbering.sections.depth : 1}}`,
216
+ "% The contents list parts, chapters and sections.",
217
+ "\\setcounter{tocdepth}{1}",
218
+ `\\hypersetup{pdftitle={${escapeText(input.title)}},pdfauthor={${escapeText(
219
+ authors.length > 0 ? authors.join(", ") : course
220
+ )}},pdfsubject={${escapeText(course)}}}`,
221
+ "\\begin{document}",
222
+ "\\frontmatter",
223
+ `\\ExplicoTitlePage{${escapeText(input.title)}}{${escapeText(input.subtitle ?? "")}}{${escapeText(
224
+ manifest.about.knowledge.version
225
+ )}}{${escapeText(authors.join(", "))}}{${url ? url.replace(/[\\{}]/gu, "").replace(/[%#]/gu, (char) => `\\${char}`) : ""}}`,
226
+ "% The contents and lists in black, as a book sets them; links in the text keep their colour.",
227
+ "\\begingroup\\hypersetup{linkcolor=black}",
228
+ "\\tableofcontents",
229
+ ...lists,
230
+ "\\endgroup",
231
+ "\\mainmatter",
232
+ ...inputs.map((file) => `\\input{${file.replace(/\.tex$/u, "")}}`),
233
+ ...appendices.length > 0 ? ["\\appendix", ...appendices.map((file) => `\\input{${file.replace(/\.tex$/u, "")}}`)] : [],
234
+ "\\end{document}",
235
+ ""
236
+ ].join("\n");
237
+ }
238
+ function bookReadme(input) {
239
+ const { manifest } = input;
240
+ const url = manifest.lessonExports.canonicalBaseUrl;
241
+ return [
242
+ `# ${input.title}`,
243
+ "",
244
+ `The LaTeX source of ${input.subtitle ? `${input.subtitle.replace(/^Part/u, "part")}` : `*${manifest.about.material.name}*`} (content version ${manifest.about.knowledge.version}).` + (url ? ` Interactive version: ${url}` : ""),
245
+ "",
246
+ "## Build",
247
+ "",
248
+ "Needs TeX Live 2025 or later.",
249
+ "",
250
+ "```sh",
251
+ "latexmk # LuaLaTeX (preferred)",
252
+ "latexmk -pdf # pdfLaTeX",
253
+ "```",
254
+ "",
255
+ "LuaLaTeX writes a tagged, accessible PDF (PDF/UA-2) with MathML for every",
256
+ "formula. pdfLaTeX tags it less completely and claims no standard.",
257
+ "",
258
+ "## Files",
259
+ "",
260
+ "- `main.tex` \u2014 the book: title page, contents, lists of figures, tables and",
261
+ " diagrams, then each part and its chapters, then the appendices.",
262
+ "- `explico.cls` \u2014 explico's book class: layout, numbering, and the",
263
+ " environments the chapters use (memos, notes, worked examples, checks).",
264
+ "- `parts/NN-<part>.tex` \u2014 a part's opening page and text;",
265
+ " `parts/NN-<part>/MM-<chapter>.tex` \u2014 its chapters.",
266
+ "- `backmatter/` \u2014 the notation and abbreviation appendices.",
267
+ "- `figures/` \u2014 the chapters' figures, as vector PDF where the website draws them.",
268
+ "",
269
+ "Numbers are LaTeX's own and match the website when the source is built",
270
+ "unchanged; edit freely and they follow.",
271
+ ""
272
+ ].join("\n");
273
+ }
274
+ function bookSource(input) {
275
+ const { manifest, parts } = input;
276
+ const canonicalBaseUrl = manifest.lessonExports.canonicalBaseUrl;
277
+ const course = manifest.about.material.name;
278
+ const version = manifest.about.knowledge.version;
279
+ const documentRoutes = new Set(
280
+ parts.flatMap(({ overview, chapters }) => [
281
+ ...overview ? [overview.route] : [],
282
+ ...chapters.map(({ page }) => page.route)
283
+ ])
284
+ );
285
+ const notationTargets = new Set(appendixNotation(manifest, parts).map(({ entry }) => entry.key));
286
+ const common = { documentRoutes, canonicalBaseUrl, inBook: true, notationTargets };
287
+ const files = [];
288
+ const inputs = [];
289
+ for (const { part, overview, chapters } of parts) {
290
+ const partFile = `parts/${twoDigits(part.ordinal)}-${part.dir}.tex`;
291
+ files.push({
292
+ path: partFile,
293
+ content: overview ? serializePartOpener({ ...common, page: overview, pictures: /* @__PURE__ */ new Map(), checkItems: /* @__PURE__ */ new Map() }, part) : `\\ExplicoPart{${part.ordinal}}{${escapeText(part.label)}}
294
+ `
295
+ });
296
+ inputs.push(partFile);
297
+ for (const { lesson, page, pictures } of chapters) {
298
+ const file = chapterPath(manifest, lesson);
299
+ const url = canonicalBaseUrl ? `${canonicalBaseUrl}${lesson.slug}/` : void 0;
300
+ files.push({
301
+ path: file,
302
+ content: serializeChapter({
303
+ ...common,
304
+ page,
305
+ pictures: new Map([...pictures].map(([anchor, picture]) => [anchor, picture.path])),
306
+ checkItems: checkItems(manifest, lesson),
307
+ source: { course, version, ...url ? { url } : {} }
308
+ })
309
+ });
310
+ inputs.push(file);
311
+ files.push(...[...pictures.values()].map((picture) => ({ path: picture.path, content: picture.content })));
312
+ }
313
+ }
314
+ const appendices = [];
315
+ const notation = serializeNotationAppendix(appendixNotation(manifest, parts), {
316
+ ...common,
317
+ pictures: /* @__PURE__ */ new Map(),
318
+ checkItems: /* @__PURE__ */ new Map()
319
+ });
320
+ if (notation) {
321
+ files.push({ path: "backmatter/notation.tex", content: notation });
322
+ appendices.push("backmatter/notation.tex");
323
+ }
324
+ const abbreviations = serializeAbbreviationAppendix(
325
+ manifest.abbreviations.flatMap(
326
+ (entry) => typeof entry.abbr === "string" && typeof entry.expansion === "string" ? [{ abbr: entry.abbr, expansion: entry.expansion }] : []
327
+ )
328
+ );
329
+ if (abbreviations) {
330
+ files.push({ path: "backmatter/abbreviations.tex", content: abbreviations });
331
+ appendices.push("backmatter/abbreviations.tex");
332
+ }
333
+ return withScripts([
334
+ { path: "README.md", content: bookReadme(input) },
335
+ { path: "latexmkrc", content: LATEXMKRC },
336
+ { path: "main.tex", content: bookMainTex(input, inputs, appendices) },
337
+ { path: "explico.cls", content: explicoClass() },
338
+ ...files
339
+ ]);
340
+ }
341
+ export {
342
+ bookSource,
343
+ chapterFolderName,
344
+ chapterPath,
345
+ chapterSource,
346
+ checkItems,
347
+ explicoClass,
348
+ trailerId
349
+ };