@@ -22,19 +22,21 @@ dependencies {
2222 implementation(" org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0" )
2323}
2424
25- // Third-party library bundling
26- //
27- // Libraries from node_modules are copied into src/main/resources/render/lib/
28- // at Gradle build time, so they are bundled in the JAR and copied to the
29- // Quarkdown output directory at compilation time.
30- //
31- // A nested third-party-manifest.json is generated at the end so that the
32- // Kotlin runtime can enumerate JAR resources (JAR directories are not listable).
33- //
34- // To add a new library:
35- // 1. Add the npm package to package.json
36- // 2. Add a LibrarySpec entry to `librariesToBundle`
37- // 3. Add a ThirdPartyLibrary subclass in ThirdPartyLibrary.kt
25+ /*
26+ Third-party library bundling
27+
28+ Libraries from node_modules are copied into src/main/resources/render/lib/
29+ at Gradle build time, so they are bundled in the JAR and copied to the
30+ Quarkdown output directory at compilation time.
31+
32+ A nested third-party-manifest.json is generated at the end so that the
33+ Kotlin runtime can enumerate JAR resources (JAR directories are not listable).
34+
35+ To add a new library:
36+ 1. Add the npm package to package.json
37+ 2. Add a LibrarySpec entry to `librariesToBundle` (or to `fontsourcePackages` for fonts)
38+ 3. For non-font libraries, add a ThirdPartyLibrary subclass in ThirdPartyLibrary.kt
39+ */
3840
3941val libDir = projectDir.resolve(" src/main/resources/render/lib" )
4042val nodeModules = projectDir.resolve(" node_modules" )
@@ -70,16 +72,12 @@ val librariesToBundle =
7072 )
7173
7274/* *
73- * @fontsource font sets, keyed by layout theme name.
74- * Each entry lists the @fontsource package names to include.
75- * All latin-subset woff2 variants are auto-discovered from each package's `files/` directory,
76- * and `@font-face` declarations are derived from the `{font}-latin-{weight}-{style}.woff2` naming convention.
75+ * @fontsource packages to bundle. Each is placed in its own `lib/fonts/{name}/` directory
76+ * with auto-discovered latin woff2 variants and a generated `fonts.css`.
77+ * SCSS layout themes select which fonts they need via `@import url(...)`.
7778 */
78- val fontsourceSets =
79- mapOf (
80- " fonts/minimal" to listOf (" lato" , " inter" , " noto-sans-mono" ),
81- " fonts/beamer" to listOf (" source-sans-pro" , " fira-sans" , " noto-sans-mono" ),
82- )
79+ val fontsourcePackages =
80+ listOf (" lato" , " inter" , " noto-sans-mono" , " source-sans-pro" , " fira-sans" )
8381
8482/* *
8583 * Highlight.js theme CSS files to copy as SCSS partials for compile-time inlining into color themes.
@@ -137,8 +135,8 @@ val bundleThirdParty =
137135 }
138136 }
139137
140- fontsourceSets .forEach { (targetSubdir, fontNames) ->
141- bundleFontsource(targetSubdir, fontNames )
138+ fontsourcePackages .forEach { fontName ->
139+ bundleFontsource(" fonts/ $fontName " , listOf (fontName) )
142140 }
143141
144142 scssThirdParty.mkdirs()
@@ -174,7 +172,8 @@ fun bundleFontsource(
174172 val filesDir = nodeModules.resolve(" @fontsource/$fontName /files" )
175173 val family = fontName.split(" -" ).joinToString(" " ) { it.replaceFirstChar(Char ::uppercaseChar) }
176174
177- filesDir.listFiles()
175+ filesDir
176+ .listFiles()
178177 ?.filter { it.name.startsWith(" $fontName -latin-" ) && it.extension == " woff2" }
179178 ?.sortedBy { it.name }
180179 ?.forEach { sourceFile ->
@@ -203,74 +202,61 @@ fun bundleFontsource(
203202}
204203
205204/* *
206- * Generates `third-party-manifest.json` with one top-level key per library.
207- *
208- * Each key maps to a nested JSON tree of that library's internal directory structure,
209- * with files at each level listed under `_files`:
205+ * Generates `third-party-manifest.json` as a nested JSON tree mirroring the `lib/` directory structure.
206+ * Files at each level are listed under `_files`; subdirectories become nested objects.
210207 *
211208 * ```json
212209 * {
213210 * "katex": {
214211 * "_files": ["katex.min.css", "katex.min.js"],
215212 * "fonts": { "_files": ["KaTeX_Main-Regular.woff2"] }
216213 * },
217- * "fonts/latex ": {
218- * "_files": ["fonts.css", "ComputerModern-Serif-Regular.woff"]
214+ * "fonts": {
215+ * "latex": { " _files": ["fonts.css", "ComputerModern-Serif-Regular.woff"] }
219216 * }
220217 * }
221218 * ```
222- *
223- * A directory that contains files is a library (e.g. `katex`). A directory that only contains
224- * subdirectories is not a library itself; its children are explored recursively until a library
225- * is found (e.g. `fonts/` has no files, so `fonts/latex` becomes a library entry).
226- * This allows the Kotlin runtime to look up libraries by name without path traversal.
227219 */
228220fun generateNestedManifest () {
229221 fun dirToJson (dir : File ): Map <String , Any > =
230222 buildMap {
231- dir.listFiles()
232- ?.filter { it.isFile }
223+ dir
224+ .listFiles()
225+ ?.filter { it.isFile && it.name != " third-party-manifest.json" }
233226 ?.map { it.name }
234227 ?.sorted()
235228 ?.takeIf { it.isNotEmpty() }
236229 ?.let { put(" _files" , it) }
237230
238- dir.listFiles()
231+ dir
232+ .listFiles()
239233 ?.filter { it.isDirectory }
240234 ?.sortedBy { it.name }
241235 ?.forEach { put(it.name, dirToJson(it)) }
242236 }
243237
244- val manifest = linkedMapOf<String , Any >()
245-
246- fun collectLibraries (dir : File , prefix : String ) {
247- dir.listFiles()
248- ?.filter { it.isDirectory }
249- ?.sortedBy { it.name }
250- ?.forEach { child ->
251- val key = if (prefix.isEmpty()) child.name else " $prefix /${child.name} "
252- val hasFiles = child.listFiles()?.any { it.isFile } ? : false
253-
254- if (hasFiles) {
255- manifest[key] = dirToJson(child)
256- } else {
257- collectLibraries(child, key)
258- }
259- }
260- }
261-
262- collectLibraries(libDir, " " )
263-
264- libDir.resolve(" third-party-manifest.json" )
265- .writeText(JsonOutput .prettyPrint(JsonOutput .toJson(manifest)))
238+ libDir
239+ .resolve(" third-party-manifest.json" )
240+ .writeText(JsonOutput .prettyPrint(JsonOutput .toJson(dirToJson(libDir))))
266241}
267242
268243// SCSS and TypeScript bundling
269244
245+ val scssDir = projectDir.resolve(" src/main/scss" )
246+ val themeOutputDir = projectDir.resolve(" src/main/resources/render/theme" )
247+
270248tasks.compileSass {
271- sourceDir = projectDir.resolve( " src/main/scss " )
272- outputDir = projectDir.resolve( " src/main/resources/render/theme " )
249+ sourceDir = scssDir
250+ outputDir = themeOutputDir
273251 dependsOn(bundleThirdParty) // hljs SCSS partials must be copied before SASS compilation
252+
253+ // Copy layout theme JSON manifests (font dependencies) alongside the compiled CSS.
254+ doLast {
255+ copy {
256+ from(scssDir.resolve(" layout" )) { include(" *.json" ) }
257+ into(themeOutputDir.resolve(" layout" ))
258+ }
259+ }
274260}
275261
276262val bundleTypeScript =
0 commit comments