explico 0.12.0 → 0.13.1
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.
- package/CHANGELOG.md +1137 -1
- package/README.md +91 -13
- package/dist/compiler/astro.d.ts +4 -3
- package/dist/compiler/astro.js +45 -15
- package/dist/compiler/cli.d.ts +26 -12
- package/dist/compiler/cli.js +96 -25
- package/dist/compiler/collections.d.ts +9 -1
- package/dist/compiler/collections.js +55 -16
- package/dist/compiler/content-schema.d.ts +31 -0
- package/dist/compiler/content-schema.js +82 -0
- package/dist/compiler/content-schemas.d.ts +422 -0
- package/dist/compiler/content-schemas.js +229 -0
- package/dist/compiler/course-config.d.ts +1 -1
- package/dist/compiler/course-config.js +30 -2
- package/dist/compiler/exports/combined-html.d.ts +97 -0
- package/dist/compiler/exports/combined-html.js +492 -0
- package/dist/compiler/exports/download-index.d.ts +9 -0
- package/dist/compiler/exports/download-index.js +33 -0
- package/dist/compiler/exports/export-chapters.d.ts +98 -0
- package/dist/compiler/exports/export-chapters.js +164 -0
- package/dist/compiler/exports/export-pages.d.ts +48 -0
- package/dist/compiler/exports/export-pages.js +22 -0
- package/dist/compiler/exports/html-tree.d.ts +22 -0
- package/dist/compiler/exports/html-tree.js +47 -0
- package/dist/compiler/exports/latex/capture.d.ts +106 -0
- package/dist/compiler/exports/latex/capture.js +435 -0
- package/dist/compiler/exports/latex/compile.d.ts +73 -0
- package/dist/compiler/exports/latex/compile.js +115 -0
- package/dist/compiler/exports/latex/document.d.ts +102 -0
- package/dist/compiler/exports/latex/document.js +349 -0
- package/dist/compiler/exports/latex/explico.cls +415 -0
- package/dist/compiler/exports/latex/numbering.d.ts +18 -0
- package/dist/compiler/exports/latex/numbering.js +57 -0
- package/dist/compiler/exports/latex/serialize.d.ts +112 -0
- package/dist/compiler/exports/latex/serialize.js +606 -0
- package/dist/compiler/exports/latex-export.d.ts +44 -0
- package/dist/compiler/exports/latex-export.js +169 -0
- package/dist/compiler/exports/markdown/serialize.d.ts +51 -0
- package/dist/compiler/exports/markdown/serialize.js +527 -0
- package/dist/compiler/exports/markdown-export.d.ts +39 -0
- package/dist/compiler/exports/markdown-export.js +240 -0
- package/dist/compiler/exports/standalone-html.d.ts +14 -5
- package/dist/compiler/exports/standalone-html.js +23 -51
- package/dist/compiler/exports/static-tree.d.ts +253 -0
- package/dist/compiler/exports/static-tree.js +625 -0
- package/dist/compiler/exports/zip.d.ts +16 -0
- package/dist/compiler/exports/zip.js +72 -0
- package/dist/compiler/lesson-derivation.d.ts +8 -2
- package/dist/compiler/lesson-derivation.js +6 -7
- package/dist/compiler/manifest.d.ts +87 -6
- package/dist/compiler/manifest.js +174 -31
- package/dist/compiler/render-diagnostics.d.ts +65 -0
- package/dist/compiler/render-diagnostics.js +56 -0
- package/dist/compiler/resolution-report.d.ts +2 -1
- package/dist/{course-config-Dsnri9Tk.d.ts → course-config-rRtME5UV.d.ts} +56 -13
- package/dist/curriculum/validation.d.ts +9 -3
- package/dist/curriculum/validation.js +35 -17
- package/dist/reference/base-abbreviations.d.mts +2 -2
- package/dist/reference/base-abbreviations.mjs +2 -2
- package/dist/reference/base-operators.d.mts +2 -2
- package/dist/reference/base-operators.mjs +2 -2
- package/dist/reference/base-roman-labels.d.mts +4 -3
- package/dist/reference/base-roman-labels.mjs +4 -3
- package/dist/reference/citation-format.d.mts +35 -0
- package/dist/reference/citation-format.mjs +74 -0
- package/dist/reference/consistency.d.ts +5 -9
- package/dist/reference/consistency.js +34 -38
- package/dist/reference/curriculum-alignment.js +3 -24
- package/dist/reference/derived-entries.d.ts +45 -0
- package/dist/reference/derived-entries.js +50 -0
- package/dist/reference/derived-notation.d.mts +125 -0
- package/dist/reference/derived-notation.mjs +121 -0
- package/dist/reference/diagram-fit.d.ts +45 -0
- package/dist/reference/diagram-fit.js +54 -0
- package/dist/reference/diagram-layout.d.ts +59 -10
- package/dist/reference/diagram-layout.js +85 -24
- package/dist/reference/diagrams-validate.d.ts +1 -1
- package/dist/reference/equations-validate.d.ts +1 -1
- package/dist/reference/equations-validate.js +26 -1
- package/dist/reference/equations.d.mts +27 -0
- package/dist/reference/equations.mjs +120 -22
- package/dist/reference/examples-validate.d.ts +1 -1
- package/dist/reference/figure-fit.d.ts +70 -0
- package/dist/reference/figure-fit.js +60 -0
- package/dist/reference/figure-pictures.d.ts +27 -0
- package/dist/reference/figure-pictures.js +49 -0
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/gate-abbreviations.d.ts +1 -1
- package/dist/reference/gate-abbreviations.js +1 -1
- package/dist/reference/gate-course-rules.d.ts +4 -3
- package/dist/reference/gate-course-rules.js +9 -4
- package/dist/reference/gate-heading-length.d.ts +2 -0
- package/dist/reference/gate-heading-length.js +7 -5
- package/dist/reference/gate-heading-structure.js +26 -0
- package/dist/reference/gate-indices.d.ts +35 -0
- package/dist/reference/gate-indices.js +299 -0
- package/dist/reference/gate-math.d.ts +32 -2
- package/dist/reference/gate-math.js +68 -12
- package/dist/reference/gate-mdx-expression.d.ts +17 -0
- package/dist/reference/gate-mdx-expression.js +29 -0
- package/dist/reference/gate-part-pages.d.ts +1 -1
- package/dist/reference/gate-prop-text.d.ts +28 -0
- package/dist/reference/gate-prop-text.js +106 -0
- package/dist/reference/gate-prose-notation.d.ts +3 -1
- package/dist/reference/gate-prose-notation.js +4 -2
- package/dist/reference/gate-retired-keys.d.ts +35 -0
- package/dist/reference/gate-retired-keys.js +58 -0
- package/dist/reference/gloss.d.ts +2 -0
- package/dist/reference/katex-measure.d.ts +17 -0
- package/dist/reference/katex-measure.js +223 -0
- package/dist/reference/keyed-float-validate.js +12 -0
- package/dist/reference/keyed-jsx-floats.d.mts +2 -2
- package/dist/reference/keyed-jsx-floats.mjs +11 -14
- package/dist/reference/lab-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-scope.js +13 -2
- package/dist/reference/math-glyphs.d.mts +94 -2
- package/dist/reference/math-glyphs.mjs +851 -46
- package/dist/reference/mixed-content.d.ts +8 -3
- package/dist/reference/mixed-content.js +4 -2
- package/dist/reference/page-notation.d.ts +34 -4
- package/dist/reference/page-notation.js +54 -2
- package/dist/reference/references.d.ts +11 -1
- package/dist/reference/references.js +6 -0
- package/dist/reference/registry.js +104 -10
- package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
- package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
- package/dist/reference/rehype-heading-anchors.mjs +11 -1
- package/dist/reference/remark-citation.mjs +0 -0
- package/dist/reference/remark-notation.mjs +84 -20
- package/dist/reference/render-lab-math.d.ts +2 -0
- package/dist/reference/render-lab-math.js +17 -5
- package/dist/reference/sections-validate.d.ts +28 -3
- package/dist/reference/sections-validate.js +31 -4
- package/dist/reference/sections.d.mts +24 -6
- package/dist/reference/sections.mjs +92 -17
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/tables-validate.js +24 -1
- package/dist/reference/text-measure.d.ts +10 -0
- package/dist/reference/text-measure.js +41 -0
- package/dist/reference/text-widths.d.ts +10 -0
- package/dist/reference/text-widths.js +1238 -0
- package/dist/reference/types.d.ts +64 -8
- package/dist/rules/catalog.js +191 -30
- package/dist/rules/in-effect.d.ts +1 -1
- package/dist/rules/rules.schema.json +79 -24
- package/dist/rules/settings.d.ts +1 -1
- package/dist/rules/settings.js +7 -0
- package/dist/session/PreferenceStore.d.ts +18 -1
- package/dist/session/PreferenceStore.js +29 -1
- package/docs/authoring/README.md +32 -21
- package/docs/authoring/agents.md +38 -7
- package/docs/authoring/components.md +111 -6
- package/docs/authoring/migrating.md +707 -0
- package/docs/authoring/notation.md +281 -29
- package/docs/authoring/retired.md +1 -0
- package/docs/authoring/structure-rules.md +10 -41
- package/docs/authoring/structure.md +53 -2
- package/docs/authoring/visualizations.md +144 -18
- package/package.json +12 -7
- package/src/compiler/astro.ts +57 -17
- package/src/compiler/cli.ts +143 -35
- package/src/compiler/collections.ts +92 -19
- package/src/compiler/content-schema.ts +108 -0
- package/src/compiler/content-schemas.ts +391 -0
- package/src/compiler/course-config.ts +86 -15
- package/src/compiler/exports/combined-html.ts +669 -0
- package/src/compiler/exports/download-index.ts +36 -0
- package/src/compiler/exports/export-chapters.ts +233 -0
- package/src/compiler/exports/export-pages.ts +44 -0
- package/src/compiler/exports/html-tree.ts +62 -0
- package/src/compiler/exports/latex/capture.ts +651 -0
- package/src/compiler/exports/latex/compile.ts +168 -0
- package/src/compiler/exports/latex/document.ts +479 -0
- package/src/compiler/exports/latex/explico.cls +415 -0
- package/src/compiler/exports/latex/numbering.ts +74 -0
- package/src/compiler/exports/latex/serialize.ts +787 -0
- package/src/compiler/exports/latex/texlive-packages.txt +76 -0
- package/src/compiler/exports/latex-export.ts +255 -0
- package/src/compiler/exports/markdown/serialize.ts +632 -0
- package/src/compiler/exports/markdown-export.ts +329 -0
- package/src/compiler/exports/standalone-html.ts +39 -70
- package/src/compiler/exports/static-tree.ts +944 -0
- package/src/compiler/exports/zip.ts +88 -0
- package/src/compiler/lesson-derivation.ts +13 -8
- package/src/compiler/manifest.ts +299 -28
- package/src/compiler/render-diagnostics.ts +111 -0
- package/src/components/HoverPanel.astro +204 -6
- package/src/components/PartOverview.astro +12 -9
- package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
- package/src/components/assessments/AssessmentRunner.tsx +7 -1
- package/src/components/assessments/AssessmentSet.astro +14 -1
- package/src/components/block-heading.ts +38 -0
- package/src/components/card-arrow.ts +18 -4
- package/src/components/card-carry.ts +406 -0
- package/src/components/card-drag.ts +1 -30
- package/src/components/card-fetch.ts +87 -0
- package/src/components/card-manager.ts +1222 -38
- package/src/components/card-memory.ts +5 -1
- package/src/components/card-placement.ts +211 -13
- package/src/components/card-side.ts +281 -31
- package/src/components/citation/CitationLayer.astro +45 -40
- package/src/components/diagram/Diagram.astro +123 -44
- package/src/components/figure/Figure.astro +11 -0
- package/src/components/figure/FloatCaption.astro +4 -17
- package/src/components/float-toolbar.ts +274 -0
- package/src/components/memo/Memo.astro +21 -10
- package/src/components/memo/ShortNote.astro +4 -0
- package/src/components/notation/NotationGlossary.astro +22 -17
- package/src/components/notation/NotationLayer.astro +321 -79
- package/src/components/notation/NotationMath.astro +8 -1
- package/src/components/notation/render-notation-math.ts +51 -5
- package/src/components/readable-text.ts +25 -0
- package/src/components/reference/FloatRefLayer.astro +162 -89
- package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
- package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
- package/src/components/starlight/LayoutPageSidebar.astro +17 -3
- package/src/components/starlight/LayoutSocialIcons.astro +79 -29
- package/src/components/starlight/LessonExportActions.astro +384 -310
- package/src/components/starlight/LessonFooter.astro +47 -9
- package/src/components/starlight/PrintController.astro +28 -0
- package/src/components/starlight/TextSizeControl.astro +137 -0
- package/src/components/starlight/print-controller.ts +380 -0
- package/src/components/starlight/section-summary.ts +13 -5
- package/src/components/table/Table.astro +13 -21
- package/src/components/table/table-truncation.ts +15 -0
- package/src/content-config.ts +20 -312
- package/src/curriculum/validation.ts +72 -24
- package/src/reference/base-abbreviations.mjs +2 -2
- package/src/reference/base-operators.mjs +2 -2
- package/src/reference/base-roman-labels.mjs +4 -3
- package/src/reference/citation-format.mjs +74 -0
- package/src/reference/consistency.ts +47 -61
- package/src/reference/curriculum-alignment.ts +4 -41
- package/src/reference/derived-entries.ts +68 -0
- package/src/reference/derived-notation.mjs +121 -0
- package/src/reference/diagram-fit.ts +104 -0
- package/src/reference/diagram-layout.ts +182 -51
- package/src/reference/diagrams-validate.ts +2 -1
- package/src/reference/equations-validate.ts +41 -2
- package/src/reference/equations.mjs +120 -22
- package/src/reference/examples-validate.ts +1 -0
- package/src/reference/figure-fit.ts +139 -0
- package/src/reference/figure-pictures.ts +96 -0
- package/src/reference/figures-validate.ts +2 -1
- package/src/reference/gate-abbreviations.ts +1 -1
- package/src/reference/gate-course-rules.ts +22 -7
- package/src/reference/gate-heading-length.ts +25 -7
- package/src/reference/gate-heading-structure.ts +40 -1
- package/src/reference/gate-indices.ts +470 -0
- package/src/reference/gate-math.ts +102 -13
- package/src/reference/gate-mdx-expression.ts +45 -0
- package/src/reference/gate-prop-text.ts +145 -0
- package/src/reference/gate-prose-notation.ts +19 -5
- package/src/reference/gate-retired-keys.ts +101 -0
- package/src/reference/gloss.ts +2 -0
- package/src/reference/katex-measure.ts +384 -0
- package/src/reference/keyed-float-validate.ts +22 -0
- package/src/reference/keyed-jsx-floats.mjs +11 -14
- package/src/reference/lab-math-scope.ts +15 -1
- package/src/reference/math-glyphs.mjs +851 -46
- package/src/reference/mixed-content.ts +11 -2
- package/src/reference/page-notation.ts +100 -3
- package/src/reference/references.ts +14 -0
- package/src/reference/registry.ts +154 -8
- package/src/reference/rehype-fail-katex-errors.mjs +19 -0
- package/src/reference/rehype-heading-anchors.mjs +11 -1
- package/src/reference/remark-citation.mjs +0 -0
- package/src/reference/remark-notation.mjs +84 -20
- package/src/reference/render-lab-math.ts +29 -5
- package/src/reference/sections-validate.ts +67 -3
- package/src/reference/sections.mjs +92 -17
- package/src/reference/tables-validate.ts +40 -2
- package/src/reference/text-measure.ts +63 -0
- package/src/reference/text-widths.ts +133 -0
- package/src/reference/types.ts +75 -8
- package/src/rules/catalog.ts +205 -29
- package/src/rules/settings.ts +10 -0
- package/src/session/PreferenceStore.ts +53 -0
- package/src/styles/global.css +291 -75
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# TeX Live 2026 packages for explico's LaTeX export, for the package_file of
|
|
2
|
+
# zauguin/install-texlive; tlmgr adds each one's dependencies. This is what a
|
|
3
|
+
# chapter reads under LuaLaTeX and pdfLaTeX (latexmk -recorder), less what
|
|
4
|
+
# latex-bin brings. Package names change between TeX Live years: check the
|
|
5
|
+
# list again when moving texlive_version.
|
|
6
|
+
|
|
7
|
+
# Engines, formats, build tool
|
|
8
|
+
latex-bin
|
|
9
|
+
latexmk
|
|
10
|
+
|
|
11
|
+
# Latin Modern text and math
|
|
12
|
+
lm
|
|
13
|
+
lm-math
|
|
14
|
+
cm-unicode # optional: Greek and Cyrillic prose in Latin Modern's design (CMU Serif)
|
|
15
|
+
|
|
16
|
+
# Tagged PDF 2.0 with MathML
|
|
17
|
+
latex-lab
|
|
18
|
+
luamml
|
|
19
|
+
pdfmanagement
|
|
20
|
+
tagpdf
|
|
21
|
+
|
|
22
|
+
# explico.cls
|
|
23
|
+
adjustbox
|
|
24
|
+
alphalph
|
|
25
|
+
amsfonts
|
|
26
|
+
booktabs
|
|
27
|
+
caption
|
|
28
|
+
ec
|
|
29
|
+
enumitem
|
|
30
|
+
float # optional in the class, but without it an in-box float loses its tagged caption
|
|
31
|
+
geometry
|
|
32
|
+
hyperref
|
|
33
|
+
iftex
|
|
34
|
+
lualatex-math
|
|
35
|
+
mathtools
|
|
36
|
+
microtype
|
|
37
|
+
newfloat
|
|
38
|
+
newunicodechar
|
|
39
|
+
pgf
|
|
40
|
+
tcolorbox
|
|
41
|
+
unicode-math
|
|
42
|
+
xcolor
|
|
43
|
+
|
|
44
|
+
# What those load
|
|
45
|
+
amscls
|
|
46
|
+
bigintcalc
|
|
47
|
+
bitset
|
|
48
|
+
bookmark
|
|
49
|
+
collectbox
|
|
50
|
+
environ
|
|
51
|
+
etoolbox
|
|
52
|
+
fontspec
|
|
53
|
+
footmisc
|
|
54
|
+
gettitlestring
|
|
55
|
+
hycolor
|
|
56
|
+
ifoddpage
|
|
57
|
+
infwarerr
|
|
58
|
+
intcalc
|
|
59
|
+
kvdefinekeys
|
|
60
|
+
kvoptions
|
|
61
|
+
kvsetkeys
|
|
62
|
+
l3packages
|
|
63
|
+
listings
|
|
64
|
+
listingsutf8
|
|
65
|
+
ltxcmds
|
|
66
|
+
mptopdf
|
|
67
|
+
pdfcol
|
|
68
|
+
pdfescape
|
|
69
|
+
pdftexcmds
|
|
70
|
+
refcount
|
|
71
|
+
stringenc
|
|
72
|
+
tikzfill
|
|
73
|
+
trimspaces
|
|
74
|
+
url
|
|
75
|
+
varwidth
|
|
76
|
+
xkeyval
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { availableParallelism } from 'node:os';
|
|
3
|
+
import { dirname, extname, join } from 'node:path';
|
|
4
|
+
import { performance } from 'node:perf_hooks';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
import type { Manifest } from '../manifest';
|
|
8
|
+
import type { StaticPage } from './static-tree';
|
|
9
|
+
import { zip } from './zip';
|
|
10
|
+
import {
|
|
11
|
+
consoleLogger,
|
|
12
|
+
epochOf,
|
|
13
|
+
figuresOf,
|
|
14
|
+
imageFile,
|
|
15
|
+
needsCapture,
|
|
16
|
+
prepareChapters,
|
|
17
|
+
readBuiltPage,
|
|
18
|
+
strictMode,
|
|
19
|
+
type ExportLogger,
|
|
20
|
+
type PreparedChapters,
|
|
21
|
+
} from './export-chapters';
|
|
22
|
+
import { compileLatex, missingCharacters, findLatexEngine } from './latex/compile';
|
|
23
|
+
import { bookSource, chapterFolderName, chapterSource, type BookPart, type SourceFile } from './latex/document';
|
|
24
|
+
import { courseFileStem } from './export-pages';
|
|
25
|
+
import { auxLabels, numberingMismatches, websiteNumbers } from './latex/numbering';
|
|
26
|
+
|
|
27
|
+
export type { ExportLogger } from './export-chapters';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The LaTeX export (`lessonExports.latex`): after Astro's static build, each
|
|
31
|
+
* chapter's source folder — `main.tex`, `explico.cls`, the chapter and its
|
|
32
|
+
* figures — is zipped to `downloads/<slug>-latex.zip`, and the same folder is
|
|
33
|
+
* compiled to `downloads/<slug>.pdf`. Each part and the course are books
|
|
34
|
+
* (`bookSource`): `downloads/part/<dir>-latex.zip` and `.pdf`,
|
|
35
|
+
* `downloads/course-latex.zip` and `.pdf`. Every document compiles at once,
|
|
36
|
+
* up to one per core.
|
|
37
|
+
*
|
|
38
|
+
* The build fails when a compiled number differs from the website's, when
|
|
39
|
+
* LaTeX reports an error, and — in CI (`CI` set) or with
|
|
40
|
+
* `EXPLICO_LATEX_STRICT=1` — when the TeX engine or the browser for figures
|
|
41
|
+
* is missing. On a developer's machine without them it warns and still
|
|
42
|
+
* writes the source zips.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
async function pagePictures(
|
|
46
|
+
outputRoot: string,
|
|
47
|
+
base: string,
|
|
48
|
+
page: StaticPage,
|
|
49
|
+
captured: ReadonlyMap<string, { pdf: Uint8Array }> | undefined,
|
|
50
|
+
): Promise<Map<string, { path: string; content: Uint8Array }>> {
|
|
51
|
+
const pictures = new Map<string, { path: string; content: Uint8Array }>();
|
|
52
|
+
for (const figure of figuresOf(page.blocks)) {
|
|
53
|
+
const capturedFigure = captured?.get(figure.anchor);
|
|
54
|
+
if (capturedFigure) {
|
|
55
|
+
pictures.set(figure.anchor, {
|
|
56
|
+
path: `figures/${page.route}/${figure.anchor}.pdf`,
|
|
57
|
+
content: capturedFigure.pdf,
|
|
58
|
+
});
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (figure.picture.kind !== 'image' || needsCapture(figure)) continue;
|
|
62
|
+
const file = imageFile(outputRoot, base, page.route, figure.picture.src);
|
|
63
|
+
pictures.set(figure.anchor, {
|
|
64
|
+
path: `figures/${page.route}/${figure.anchor}${extname(file).toLowerCase()}`,
|
|
65
|
+
content: await readFile(file),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return pictures;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface PendingFile {
|
|
72
|
+
readonly target: string;
|
|
73
|
+
readonly content: Uint8Array;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** `work` over every item, at most `limit` at a time; results in the items' order. */
|
|
77
|
+
async function mapWithLimit<T, R>(items: readonly T[], limit: number, work: (item: T) => Promise<R>): Promise<R[]> {
|
|
78
|
+
const results: R[] = new Array(items.length);
|
|
79
|
+
let next = 0;
|
|
80
|
+
const worker = async () => {
|
|
81
|
+
while (next < items.length) {
|
|
82
|
+
const index = next++;
|
|
83
|
+
results[index] = await work(items[index]!);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
|
|
87
|
+
return results;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Write each chapter's, each part's and the course's LaTeX source zip and PDF
|
|
92
|
+
* under `downloads/`, from the chapters `prepareChapters` read and drew (read
|
|
93
|
+
* here when not given).
|
|
94
|
+
*/
|
|
95
|
+
export async function writeLatexExports(
|
|
96
|
+
directory: URL,
|
|
97
|
+
manifest: Manifest,
|
|
98
|
+
options: {
|
|
99
|
+
readonly logger?: ExportLogger;
|
|
100
|
+
readonly projectRoot?: string;
|
|
101
|
+
readonly prepared?: PreparedChapters;
|
|
102
|
+
} = {},
|
|
103
|
+
): Promise<void> {
|
|
104
|
+
if (!manifest.lessonExports.latex) return;
|
|
105
|
+
const logger = options.logger ?? consoleLogger;
|
|
106
|
+
const strict = strictMode();
|
|
107
|
+
const outputRoot = fileURLToPath(directory);
|
|
108
|
+
const { base, chapters, captures } =
|
|
109
|
+
options.prepared ?? (await prepareChapters(outputRoot, manifest, { logger, projectRoot: options.projectRoot }));
|
|
110
|
+
|
|
111
|
+
const engine = await findLatexEngine();
|
|
112
|
+
if ('missing' in engine) {
|
|
113
|
+
if (strict) throw new Error(`LaTeX export cannot build PDFs: ${engine.missing}`);
|
|
114
|
+
logger.warn(`LaTeX export: writing the source zips only. ${engine.missing}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** One LaTeX document: its folder, where its zip and PDF go, and the numbers it must print. */
|
|
118
|
+
interface Job {
|
|
119
|
+
readonly kind: 'chapter' | 'part' | 'course';
|
|
120
|
+
readonly name: string;
|
|
121
|
+
readonly files: SourceFile[];
|
|
122
|
+
readonly target: string;
|
|
123
|
+
readonly folder: string;
|
|
124
|
+
readonly website: Map<string, string>;
|
|
125
|
+
}
|
|
126
|
+
const jobs: Job[] = [];
|
|
127
|
+
const pictures = new Map<string, Awaited<ReturnType<typeof pagePictures>>>();
|
|
128
|
+
for (const { lesson, page } of chapters) {
|
|
129
|
+
const chapterPictures = await pagePictures(outputRoot, base, page, captures.get(page.route));
|
|
130
|
+
pictures.set(lesson.id, chapterPictures);
|
|
131
|
+
jobs.push({
|
|
132
|
+
kind: 'chapter',
|
|
133
|
+
name: lesson.slug,
|
|
134
|
+
files: chapterSource({ manifest, lesson, page, documentRoutes: new Set([page.route]), pictures: chapterPictures }),
|
|
135
|
+
target: lesson.slug,
|
|
136
|
+
folder: chapterFolderName(lesson),
|
|
137
|
+
website: websiteNumbers(page, `${lesson.part}.${lesson.chapter}`),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Each part, and the course, as a book (plan step 6).
|
|
142
|
+
const byId = new Map(chapters.map((chapter) => [chapter.lesson.id, chapter]));
|
|
143
|
+
const pageSlugs = new Set(manifest.pages.map((page) => page.slug));
|
|
144
|
+
const parts: BookPart[] = [];
|
|
145
|
+
for (const part of manifest.parts) {
|
|
146
|
+
const partChapters = part.lessons.flatMap((id) => {
|
|
147
|
+
const chapter = byId.get(id);
|
|
148
|
+
return chapter ? [{ ...chapter, pictures: pictures.get(id)! }] : [];
|
|
149
|
+
});
|
|
150
|
+
if (partChapters.length === 0) continue;
|
|
151
|
+
const overview = pageSlugs.has(`${part.dir}/index`)
|
|
152
|
+
? (await readBuiltPage(outputRoot, `${part.dir}/index`)).page
|
|
153
|
+
: undefined;
|
|
154
|
+
parts.push({ part, ...(overview ? { overview } : {}), chapters: partChapters });
|
|
155
|
+
}
|
|
156
|
+
const bookNumbers = (bookParts: readonly BookPart[]) =>
|
|
157
|
+
new Map(
|
|
158
|
+
bookParts.flatMap(({ chapters: partChapters }) =>
|
|
159
|
+
partChapters.flatMap(({ lesson, page }) => [...websiteNumbers(page, `${lesson.part}.${lesson.chapter}`)]),
|
|
160
|
+
),
|
|
161
|
+
);
|
|
162
|
+
const course = manifest.about.material.name;
|
|
163
|
+
for (const entry of parts) {
|
|
164
|
+
jobs.push({
|
|
165
|
+
kind: 'part',
|
|
166
|
+
name: `part/${entry.part.dir}`,
|
|
167
|
+
files: bookSource({
|
|
168
|
+
manifest,
|
|
169
|
+
parts: [entry],
|
|
170
|
+
document: `part/${entry.part.dir}`,
|
|
171
|
+
title: entry.part.label,
|
|
172
|
+
subtitle: `Part ${entry.part.ordinal} of ${course}`,
|
|
173
|
+
}),
|
|
174
|
+
target: `part/${entry.part.dir}`,
|
|
175
|
+
folder: `${entry.part.dir}-latex`,
|
|
176
|
+
website: bookNumbers([entry]),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (parts.length > 0) {
|
|
180
|
+
jobs.push({
|
|
181
|
+
kind: 'course',
|
|
182
|
+
name: 'course',
|
|
183
|
+
files: bookSource({ manifest, parts, document: 'course', title: course }),
|
|
184
|
+
target: 'course',
|
|
185
|
+
folder: `${courseFileStem(manifest)}-latex`,
|
|
186
|
+
website: bookNumbers(parts),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const pending: PendingFile[] = jobs.map((job) => ({
|
|
191
|
+
target: join(outputRoot, 'downloads', `${job.target}-latex.zip`),
|
|
192
|
+
content: zip(job.files.map((file) => ({ path: `${job.folder}/${file.path}`, content: file.content }))),
|
|
193
|
+
}));
|
|
194
|
+
|
|
195
|
+
// Compile every document at once, up to one per core; a book numbers
|
|
196
|
+
// every chapter, so each is checked against the website whole.
|
|
197
|
+
const durations: { kind: Job['kind']; seconds: number }[] = [];
|
|
198
|
+
const started = performance.now();
|
|
199
|
+
if (!('missing' in engine)) {
|
|
200
|
+
const compiled = await mapWithLimit(jobs, availableParallelism(), async (job) => {
|
|
201
|
+
const jobStarted = performance.now();
|
|
202
|
+
const result = await compileLatex(job.files, engine, { epoch: epochOf(manifest), name: job.name });
|
|
203
|
+
durations.push({ kind: job.kind, seconds: (performance.now() - jobStarted) / 1000 });
|
|
204
|
+
const problems = numberingMismatches(job.website, auxLabels(result.aux));
|
|
205
|
+
if (problems.length > 0) {
|
|
206
|
+
throw new Error(`LaTeX numbers ${job.name} differently from the website:\n ${problems.join('\n ')}`);
|
|
207
|
+
}
|
|
208
|
+
// A part's and the course's PDFs repeat their chapters' text, so each
|
|
209
|
+
// character is reported once, by the chapter that has it.
|
|
210
|
+
const missing = job.kind === 'chapter' ? missingCharacters(result.log) : [];
|
|
211
|
+
if (missing.length > 0) {
|
|
212
|
+
logger.warn(
|
|
213
|
+
`LaTeX export: the PDF of ${job.name} leaves ${missing.length} character(s) blank, ` +
|
|
214
|
+
`which its fonts cannot draw: ${missing.slice(0, 12).join(', ')}` +
|
|
215
|
+
`${missing.length > 12 ? ', …' : ''}. The web page shows them, the PDF does not. Under ` +
|
|
216
|
+
'LuaLaTeX, install a font that covers them on the build machine (explico.cls looks for CMU ' +
|
|
217
|
+
'Serif, Noto Serif or DejaVu Serif for Greek, Noto CJK or Fandol for Chinese, Japanese and ' +
|
|
218
|
+
'Korean, and Noto Color Emoji for emoji); pdfLaTeX sets none of them. Or write the word in ' +
|
|
219
|
+
'Latin script, or a symbol as math ($\\alpha$).',
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
return result.pdf;
|
|
223
|
+
});
|
|
224
|
+
jobs.forEach((job, index) => {
|
|
225
|
+
pending.push({ target: join(outputRoot, 'downloads', `${job.target}.pdf`), content: compiled[index]! });
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
const wall = (performance.now() - started) / 1000;
|
|
229
|
+
|
|
230
|
+
// Nothing is published until every document has compiled and matched.
|
|
231
|
+
const temporary = pending.map((file) => ({ ...file, temporary: `${file.target}.tmp-${process.pid}` }));
|
|
232
|
+
try {
|
|
233
|
+
for (const file of temporary) {
|
|
234
|
+
await mkdir(dirname(file.target), { recursive: true });
|
|
235
|
+
await writeFile(file.temporary, file.content);
|
|
236
|
+
}
|
|
237
|
+
for (const file of temporary) await rename(file.temporary, file.target);
|
|
238
|
+
} finally {
|
|
239
|
+
await Promise.all(temporary.map((file) => rm(file.temporary, { force: true })));
|
|
240
|
+
}
|
|
241
|
+
const pdfs = pending.filter((file) => file.target.endsWith('.pdf')).length;
|
|
242
|
+
const seconds = (kind: Job['kind']) =>
|
|
243
|
+
durations.filter((entry) => entry.kind === kind).reduce((sum, entry) => sum + entry.seconds, 0);
|
|
244
|
+
logger.info(
|
|
245
|
+
`LaTeX export: ${chapters.length} chapter(s), ${parts.length} part(s)${parts.length > 0 ? ' and the course' : ''}: ` +
|
|
246
|
+
`${jobs.length} source zip(s)` +
|
|
247
|
+
(pdfs > 0
|
|
248
|
+
? `, ${pdfs} PDF(s) with ${'name' in engine ? engine.name : ''} in ${wall.toFixed(1)} s on up to ` +
|
|
249
|
+
`${availableParallelism()} core(s) (compile time: chapters ${seconds('chapter').toFixed(1)} s, ` +
|
|
250
|
+
`parts ${seconds('part').toFixed(1)} s, course ${seconds('course').toFixed(1)} s)`
|
|
251
|
+
: '') +
|
|
252
|
+
(captures.size > 0 ? `, figures from ${captures.size} page(s) drawn as vector PDF` : '') +
|
|
253
|
+
'.',
|
|
254
|
+
);
|
|
255
|
+
}
|