gutterpress 0.11.22 → 0.11.23-alpha.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.
Files changed (57) hide show
  1. package/README.md +5 -5
  2. package/dist/{README-3nj919rm.md → README-8ef336pp.md} +1 -1
  3. package/dist/api/index.js +4 -4
  4. package/dist/{build-53bw14h7.js → build-09cd1n7s.js} +8 -8
  5. package/dist/{chapter-01-rkpyvjna.md → chapter-01-gsbjt584.md} +1 -1
  6. package/dist/chapter-01-s71xrf5q.md +69 -0
  7. package/dist/{chapter-01-7jwqg1ct.md → chapter-01-wgjfz4w6.md} +1 -1
  8. package/dist/checks/source/character-table.d.ts +12 -6
  9. package/dist/{cli-ke86zsz6.js → cli-5hkmp2wy.js} +70 -40
  10. package/dist/{cli-kx4hbnx2.js → cli-b0vyjvmt.js} +1 -1
  11. package/dist/{cli-1hxxwbaq.js → cli-ccp7sxz3.js} +1 -1
  12. package/dist/{cli-zj5xaepv.js → cli-dd115fa8.js} +22 -6
  13. package/dist/{cli-8kg0wjdt.js → cli-dtckwda1.js} +1 -1
  14. package/dist/{cli-1ngqzg8s.js → cli-fe43evmf.js} +21 -5
  15. package/dist/{cli-ntwqmdnw.js → cli-h17e3mtz.js} +462 -4
  16. package/dist/{cli-cjftezxr.js → cli-nr6j6300.js} +2 -1
  17. package/dist/cli.js +12 -12
  18. package/dist/{doctor-xka1frnx.js → doctor-nddspx4n.js} +3 -3
  19. package/dist/{engine-p93f06gj.js → engine-892swzzc.js} +2 -2
  20. package/dist/{engine-0ndvy8vj.js → engine-yjzknt0g.js} +2 -2
  21. package/dist/{ext-dwfqyy1j.js → ext-zjpen4yx.js} +9 -9
  22. package/dist/{extension-restore-7vp3vh77.js → extension-restore-jvs60x9b.js} +3 -3
  23. package/dist/{extension-restore-ct1wk344.js → extension-restore-w8xt4kbq.js} +5 -5
  24. package/dist/{index-53nrm5j1.js → index-0w2azsk1.js} +1 -1
  25. package/dist/{index-4v7e621e.js → index-2jpk25en.js} +464 -5
  26. package/dist/{index-vay3ff1f.js → index-dsvk7q6s.js} +21 -5
  27. package/dist/{index-6cmn2wpd.js → index-m4hhzg3c.js} +21 -5
  28. package/dist/{index-kh7fayjv.js → index-zrhyb967.js} +328 -311
  29. package/dist/index.js +5 -5
  30. package/dist/lib/build-runner.d.ts +1 -1
  31. package/dist/lib/extension-manager.d.ts +1 -1
  32. package/dist/lib/extension-specifier.d.ts +1 -1
  33. package/dist/lib/markdown/assemble.d.ts +7 -1
  34. package/dist/lib/markdown/gutterpress-css.d.ts +1 -1
  35. package/dist/lib/markdown/index.d.ts +4 -0
  36. package/dist/lib/markdown/math.d.ts +43 -0
  37. package/dist/lib/markdown/renderer.d.ts +11 -8
  38. package/dist/lib/markdown/table.d.ts +46 -0
  39. package/dist/lib/markdown/typography.d.ts +12 -0
  40. package/dist/lib/presets.d.ts +1 -1
  41. package/dist/lib/project-scaffold.d.ts +1 -1
  42. package/dist/lib/project-templates.d.ts +9 -1
  43. package/dist/manifest-8f10dr4f.yaml +17 -0
  44. package/dist/{manifest.schema-399bx3vc.json → manifest.schema-9aeaered.json} +8 -1
  45. package/dist/{new-1pawj2qf.js → new-xhq8wczn.js} +8 -8
  46. package/dist/package-zttkgydq.json +10 -0
  47. package/dist/{plugins-fr4fnzhb.js → plugins-9en8m9y0.js} +3 -3
  48. package/dist/{plugins-g4dgw7mn.js → plugins-gf0gkjmv.js} +1 -1
  49. package/dist/{preflight-ept9q57t.js → preflight-negnxg7b.js} +8 -8
  50. package/dist/preview/file-watcher.d.ts +1 -0
  51. package/dist/{preview-fxpnjv7k.js → preview-cep2s9cg.js} +8 -8
  52. package/dist/{publish-zhqqsgmr.js → publish-xery6qc6.js} +8 -8
  53. package/dist/render.js +470 -11
  54. package/dist/schema/manifest.types.d.ts +8 -0
  55. package/dist/theme-9c58td2d.css +76 -0
  56. package/dist/{validate-t68gvckq.js → validate-3d0sgf4g.js} +8 -8
  57. package/package.json +2 -1
package/README.md CHANGED
@@ -159,7 +159,7 @@ gutterpress new <name> [--kind <id>] [options]
159
159
  --author <name> Author name to record in the project
160
160
  --dir <path> Parent directory to create the project in (default: current directory)
161
161
  --folder <name> Folder name to create (default: a slug of the project name)
162
- --template <id> Starter template: book, zine, technical (default: book)
162
+ --template <id> Starter template: book, zine, technical, paper (default: book)
163
163
  --targets <ids> Publish targets recorded in the manifest (comma-separated: dtrpg, itch; or "none") — default: the preset's
164
164
  --page-width <pt> Trim width in points, 72pt = 1in (required with --preset custom; optional override otherwise)
165
165
  --page-height <pt> Trim height in points, 72pt = 1in (required with --preset custom; optional override otherwise)
@@ -396,8 +396,8 @@ Add an extension. What `SOURCE` is decides what happens:
396
396
  as `name@<exact version>`. This does not invoke npm, Bun, Node.js tooling,
397
397
  or package install scripts;
398
398
  - a **bundled feature** (`markdown-it-mark`, `markdown-it-sub`,
399
- `markdown-it-sup`, `markdown-it-abbr`, `gutterpress-gfm-alerts`) is simply
400
- listed — nothing to install, works offline;
399
+ `markdown-it-sup`, `markdown-it-abbr`, `gutterpress-gfm-alerts`,
400
+ `gutterpress-math`) is simply listed — nothing to install, works offline;
401
401
  - a **folder or plugin-file path** is load-tested and listed as a
402
402
  manifest-relative path, referenced in place — never copied;
403
403
  - a **`.zip` or `.css` file**, or an **http(s) URL**, is validated (every
@@ -438,7 +438,7 @@ folders it created and nothing else.
438
438
  gutterpress ext add <source> [dir] [options]
439
439
 
440
440
  --export <name> Named module export to use as the plugin function (npm and path extensions only)
441
- --look Treat SOURCE as a built-in look id (clean-book, zine, technical-doc): copy it into extensions/ and add it
441
+ --look Treat SOURCE as a built-in look id (clean-book, zine, technical-doc, academic): copy it into extensions/ and add it
442
442
  ```
443
443
 
444
444
  ```sh
@@ -599,7 +599,7 @@ The binary is self-contained except for the system tools described in [User Guid
599
599
 
600
600
  - **`Ghostscript executable not found`** — Ghostscript is required only for PDF/X and ink-coverage checks. Standard Windows installs are auto-detected; for a non-standard install, set `GHOSTSCRIPT_PATH` to the full path of `gs`, `gswin64c.exe`, or `gswin32c.exe`. See [User Guide: Chapter 7 — System Setup](https://github.com/dimm-city/gutterpress/blob/main/examples/gutterpress-user-guide/07-system-setup.md).
601
601
  - **`No Chrome or Chromium binary found`** — install a Chromium-based browser or set `CHROMIUM_PATH=/path/to/chrome`. The desktop app includes its own browser and needs no separate browser install.
602
- - **`Tool "X" not found — skipping`** during validate — that's the graceful path; the check requires `X` and isn't available. Install the tool or accept the skip.
602
+ - **`Tool "X" not found; skipping`** during validate — that's the graceful path; the check requires `X` and isn't available. Install the tool or accept the skip.
603
603
  - **All validate checks skipped on Windows** — was a bug pre-0.1.7 (used `which`, which isn't on stock Windows); fixed to use `where.exe`.
604
604
 
605
605
  ## Links
@@ -50,7 +50,7 @@ Everything in here stays together on one page.
50
50
 
51
51
  ## Why six files
52
52
 
53
- Gutterpress ships three built-in themes of about sixty lines each. They style
53
+ Gutterpress's built-in themes are one short stylesheet each. They style
54
54
  bare elements and stop, which is the right size for a plain book and is not an
55
55
  architecture. The first book that needs components outgrows one in a week, and
56
56
  then re-derives this arrangement from scratch — badly, usually twice.
package/dist/api/index.js CHANGED
@@ -101,7 +101,7 @@ import {
101
101
  substituteVariables,
102
102
  syncProject,
103
103
  testRemoteAccess
104
- } from "../index-kh7fayjv.js";
104
+ } from "../index-zrhyb967.js";
105
105
  import {
106
106
  BUILT_IN_STYLE_SET_IDS,
107
107
  BUNDLED_EXTENSIONS,
@@ -138,7 +138,7 @@ import {
138
138
  setExtensionEnabled,
139
139
  updateExtensions,
140
140
  validateProjectExtensions
141
- } from "../index-6cmn2wpd.js";
141
+ } from "../index-m4hhzg3c.js";
142
142
  import {
143
143
  EXTENSION_MANIFEST_FILENAME,
144
144
  assertExtensionContained,
@@ -148,12 +148,12 @@ import {
148
148
  pathEscapesFolder,
149
149
  readExtensionMeta,
150
150
  resolveExtension
151
- } from "../index-4v7e621e.js";
151
+ } from "../index-2jpk25en.js";
152
152
  import"../index-ht1bcqdd.js";
153
153
  import"../index-apc8w9w8.js";
154
154
  import {
155
155
  BuildError
156
- } from "../index-vay3ff1f.js";
156
+ } from "../index-dsvk7q6s.js";
157
157
  import"../index-2ppw6q9v.js";
158
158
  import {
159
159
  DEFAULT_PRINT_OPTS,
@@ -1,20 +1,20 @@
1
1
  import {
2
2
  runBuild,
3
3
  splitOutPath
4
- } from "./cli-ke86zsz6.js";
5
- import"./cli-1hxxwbaq.js";
4
+ } from "./cli-5hkmp2wy.js";
5
+ import"./cli-ccp7sxz3.js";
6
6
  import"./cli-ht1bcqdd.js";
7
- import"./cli-8kg0wjdt.js";
7
+ import"./cli-dtckwda1.js";
8
8
  import"./cli-2ppw6q9v.js";
9
9
  import"./cli-eyyggect.js";
10
10
  import"./cli-asap4t1z.js";
11
11
  import"./cli-habkc5ng.js";
12
12
  import"./cli-v5mp7a6q.js";
13
- import"./cli-zj5xaepv.js";
14
- import"./cli-ntwqmdnw.js";
13
+ import"./cli-dd115fa8.js";
14
+ import"./cli-h17e3mtz.js";
15
15
  import {
16
16
  log
17
- } from "./cli-kx4hbnx2.js";
17
+ } from "./cli-b0vyjvmt.js";
18
18
  import {
19
19
  UsageError,
20
20
  parseFormat,
@@ -22,8 +22,8 @@ import {
22
22
  rejectExtraPositionals,
23
23
  rejectUnknownFlags
24
24
  } from "./cli-d8xjns66.js";
25
- import"./cli-cjftezxr.js";
26
- import"./cli-1ngqzg8s.js";
25
+ import"./cli-nr6j6300.js";
26
+ import"./cli-fe43evmf.js";
27
27
  import {
28
28
  BuildError
29
29
  } from "./cli-46ycxe6r.js";
@@ -1,7 +1,7 @@
1
1
  # Chapter One
2
2
 
3
3
  Welcome to {{TITLE}}. This is your first chapter, and it is ready for you to
4
- make it your own. Everything you see on this page is just an example — replace it
4
+ make it your own. Everything you see on this page is just an example --- replace it
5
5
  with your own writing whenever you are ready.
6
6
 
7
7
  You write in plain text. Start a new paragraph by leaving a blank line, like
@@ -0,0 +1,69 @@
1
+ @section .title
2
+
3
+ # {{TITLE}}
4
+
5
+ **First Author**^1^ and **Second Author**^2^
6
+
7
+ ^1^ Department, Institution\
8
+ ^2^ Department, Institution
9
+
10
+ @end-section
11
+
12
+ @section .abstract
13
+
14
+ **Abstract.** State the question, what you did, and what you found, in one
15
+ paragraph a reader can judge the paper by.
16
+
17
+ **Keywords:** first · second · third
18
+
19
+ @end-section
20
+
21
+ ## 1 Introduction
22
+
23
+ Say what the problem is and why it matters, then what this paper
24
+ contributes. Cite earlier work with a number in brackets, like [[1]], and
25
+ list it under References below.
26
+
27
+ ## 2 Method
28
+
29
+ Write mathematics as you would in LaTeX: $x \in \mathbb{R}^d$ in a line, or
30
+ on lines of its own, numbered with `\tag`:
31
+
32
+ $$
33
+ \hat{\theta} = \operatorname*{arg\,min}_{\theta} \; \frac{1}{n} \sum_{i=1}^{n} \ell\big(f_\theta(x_i), y_i\big)
34
+ \tag{1}
35
+ $$
36
+
37
+ Refer back to it as (1). A footnote goes like this.[^note]
38
+
39
+ [^note]: Notes are collected at the end of the file, before the references.
40
+
41
+ ## 3 Results
42
+
43
+ @section .figure
44
+
45
+ **Table 1.** A caption above the table, kept on the same page as it. {.caption}
46
+
47
+ | Method | Error | Cost |
48
+ |:--|--:|--:|
49
+ | Baseline | $1.4 \times 10^{-7}$ | 1025 |
50
+ | This paper | $3.4 \times 10^{-12}$ | 33 |
51
+
52
+ @end-section
53
+
54
+ A figure works the same way: put the image and its caption inside
55
+ `@section .figure`, with `{.caption}` on the caption paragraph.
56
+
57
+ ## 4 Conclusion
58
+
59
+ What was shown, and what it means.
60
+
61
+ @section .references
62
+
63
+ ## References
64
+
65
+ 1. A. Author, *Title of the paper*, Journal, 1 (2026), pp. 1--10. {#ref-1}
66
+
67
+ @end-section
68
+
69
+ [1]: #ref-1
@@ -1,7 +1,7 @@
1
1
  # {{TITLE}}
2
2
 
3
3
  A zine is short, personal, and made to be photocopied and shared. This page is
4
- your opening spread — keep it punchy.
4
+ your opening spread --- keep it punchy.
5
5
 
6
6
  Write a few lines that set the tone. A zine does not need to explain itself; it
7
7
  just needs a voice.
@@ -9,12 +9,18 @@
9
9
  * mark at the top of a file turns the first heading into a paragraph).
10
10
  *
11
11
  * Every spelling here is plain ASCII that the renderer turns back into the
12
- * same character: markdown-it's typographer (`typographer: true` in
13
- * `lib/markdown/renderer.ts`) makes `---` an em dash and straight quotes
14
- * curly, and HTML entities cover the rest. So an intentional use stays
12
+ * same character: `gp_typography` (`lib/markdown/typography.ts`) makes
13
+ * `---`, `--` and `...` the dash and ellipsis characters, markdown-it's
14
+ * `smartquotes` curls straight quotes in the book's `language`, and HTML
15
+ * entities cover the rest. So an intentional use stays
15
16
  * possible and visible in the source; only the raw pasted character is
16
- * reported. « » „ ‚ ‹ › are deliberately absent: they are correct quotation
17
- * marks in other languages and the typographer cannot produce them.
17
+ * reported. « » „ ‚ ‹ › are deliberately absent: they are the marks the
18
+ * book's `language` prints for other languages, so an author who typed `"`
19
+ * in such a book has nothing to fix, and a pasted one cannot be told apart.
20
+ * The names of the curly quotes say which side they are, not what they do:
21
+ * “ opens an English quotation and closes a German one. One curly character
22
+ * is let through on purpose: a ’ that starts a word (’90s, ’tis), because a
23
+ * typed ' there prints straight — there is nothing else to type.
18
24
  *
19
25
  * PURE data, no imports: the desktop editor mirrors this module
20
26
  * (`packages/desktop/src/lib/editor/special-characters.ts`) to mark the same
@@ -23,7 +29,7 @@
23
29
  */
24
30
  /** Stable id for the kind of character, carried on each finding's `code`. */
25
31
  export type CharacterCode =
26
- /** The typographer makes it from plain typing (`---`, `"`, `...`). */
32
+ /** The renderer makes it from plain typing (`---`, `"`, `...`). */
27
33
  "typographer"
28
34
  /** Looks like a quote mark or hyphen but is not one. */
29
35
  | "lookalike"
@@ -8,7 +8,7 @@ import {
8
8
  stampCreator,
9
9
  stripAnnotations,
10
10
  transparentPages
11
- } from "./cli-1hxxwbaq.js";
11
+ } from "./cli-ccp7sxz3.js";
12
12
  import {
13
13
  blackTextToGray
14
14
  } from "./cli-ht1bcqdd.js";
@@ -17,7 +17,7 @@ import {
17
17
  extract,
18
18
  lastPageMode,
19
19
  resolvePage
20
- } from "./cli-8kg0wjdt.js";
20
+ } from "./cli-dtckwda1.js";
21
21
  import {
22
22
  countLinkAnnotations,
23
23
  getImageResolutions,
@@ -55,6 +55,7 @@ import {
55
55
  PRESET_IDS,
56
56
  TARGETS,
57
57
  TARGET_IDS,
58
+ addBuiltInStyleSet,
58
59
  addExtension,
59
60
  applyDefaultPdfStrictChecks,
60
61
  ensureProjectGitignore,
@@ -70,7 +71,7 @@ import {
70
71
  uniqueExtensionId,
71
72
  withFetchTimeout,
72
73
  writeManifestDoc
73
- } from "./cli-zj5xaepv.js";
74
+ } from "./cli-dd115fa8.js";
74
75
  import {
75
76
  EXTENSION_MANIFEST_FILENAME,
76
77
  GUTTERPRESS_CSS,
@@ -91,7 +92,7 @@ import {
91
92
  readExtensionMeta,
92
93
  resolveDeclaredStyles,
93
94
  slugify
94
- } from "./cli-ntwqmdnw.js";
95
+ } from "./cli-h17e3mtz.js";
95
96
  import {
96
97
  PACKAGE_META,
97
98
  PACKAGE_VERSION,
@@ -101,7 +102,7 @@ import {
101
102
  log,
102
103
  setLogLevel,
103
104
  warn
104
- } from "./cli-kx4hbnx2.js";
105
+ } from "./cli-b0vyjvmt.js";
105
106
  import {
106
107
  DEBOUNCE,
107
108
  UsageError,
@@ -109,7 +110,7 @@ import {
109
110
  } from "./cli-d8xjns66.js";
110
111
  import {
111
112
  getAssetPath
112
- } from "./cli-1ngqzg8s.js";
113
+ } from "./cli-fe43evmf.js";
113
114
  import {
114
115
  BuildError
115
116
  } from "./cli-46ycxe6r.js";
@@ -3024,7 +3025,7 @@ async function collectKnownClasses(ctx, plugins) {
3024
3025
  var check26 = {
3025
3026
  id: "source.markdown.layout-markers",
3026
3027
  name: "Layout Markers",
3027
- description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, plus any unknown gp-* class and any other class no stylesheet mentions",
3028
+ description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, any unknown gp-* class and any other class no stylesheet mentions, a table row with more cells than its table has columns, and a formula that could not be typeset",
3028
3029
  category: "source",
3029
3030
  phase: "pre-build",
3030
3031
  async run(ctx) {
@@ -3188,14 +3189,14 @@ registerCheck(check27);
3188
3189
  import { readFile as readFile13 } from "node:fs/promises";
3189
3190
 
3190
3191
  // src/checks/source/character-table.ts
3191
- var CHARACTER_PATTERN = new RegExp("[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u00A0\\u00AD\\u00B4\\u02BC" + "\\u061C\\u2000-\\u2015\\u2018\\u2019\\u201B-\\u201D\\u201F\\u2022\\u2026" + "\\u2028-\\u202F\\u2032\\u2033\\u205F\\u2060\\u2066-\\u2069\\u2190\\u2192" + "\\u2194\\u21D2\\u2212\\u25AA\\u25E6\\u2605\\u2713\\u2714\\u2717\\u3000" + "\\uE000-\\uF8FF\\uFEFF\\uFF02\\uFF07\\uFFFD]" + "|\\p{Emoji_Presentation}|\\p{Extended_Pictographic}(?=\\uFE0F)", "gu");
3192
+ var CHARACTER_PATTERN = new RegExp("[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u00A0\\u00AD\\u00B4\\u02BC" + "\\u061C\\u2000-\\u2015\\u2018\\u201B-\\u201D\\u201F\\u2022\\u2026" + "\\u2028-\\u202F\\u2032\\u2033\\u205F\\u2060\\u2066-\\u2069\\u2190\\u2192" + "\\u2194\\u21D2\\u2212\\u25AA\\u25E6\\u2605\\u2713\\u2714\\u2717\\u3000" + "\\uE000-\\uF8FF\\uFEFF\\uFF02\\uFF07\\uFFFD]" + "|\\p{Emoji_Presentation}|\\p{Extended_Pictographic}(?=\\uFE0F)" + "|(?<=[\\p{L}\\p{N}])\\u2019|\\u2019(?![\\p{L}\\p{N}])", "gu");
3192
3193
  var NAMED = {
3193
3194
  8212: ["Em dash", "typographer", "---"],
3194
3195
  8211: ["En dash", "typographer", "--"],
3195
- 8220: ["Opening curly quote", "typographer", '"'],
3196
- 8221: ["Closing curly quote", "typographer", '"'],
3197
- 8216: ["Opening curly single quote", "typographer", "'"],
3198
- 8217: ["Curly apostrophe", "typographer", "'"],
3196
+ 8220: ["Left curly quote", "typographer", '"'],
3197
+ 8221: ["Right curly quote", "typographer", '"'],
3198
+ 8216: ["Left curly single quote", "typographer", "'"],
3199
+ 8217: ["Right curly single quote (apostrophe)", "typographer", "'"],
3199
3200
  8230: ["Ellipsis character", "typographer", "..."],
3200
3201
  700: ["Modifier-letter apostrophe", "lookalike", "'"],
3201
3202
  180: ["Acute accent", "lookalike", "'"],
@@ -3955,7 +3956,7 @@ function reportMissingTools(result) {
3955
3956
  const affected = checkIds.filter((id) => result.skippedChecks.includes(id));
3956
3957
  if (affected.length === 0)
3957
3958
  continue;
3958
- log.warn(`Tool "${tool}" not found — skipping: ${affected.join(", ")}`);
3959
+ log.warn(`Tool "${tool}" not found; skipping: ${affected.join(", ")}`);
3959
3960
  }
3960
3961
  }
3961
3962
 
@@ -4020,11 +4021,12 @@ async function assembleBookHtml(opts) {
4020
4021
  const title = opts.title ?? "Document";
4021
4022
  const projectCss = opts.projectCss ?? "";
4022
4023
  const pluginCss = opts.pluginCss ?? "";
4024
+ const language = opts.language ?? "en";
4023
4025
  const files = opts.files;
4024
4026
  if (files.length === 0) {
4025
4027
  throw new Error("No markdown files to render");
4026
4028
  }
4027
- const md = createMarkdownRenderer(opts.plugins);
4029
+ const md = createMarkdownRenderer(opts.plugins, { language });
4028
4030
  let bodyContent = "";
4029
4031
  const imageRefs = new Set;
4030
4032
  for (const file of files) {
@@ -4036,7 +4038,7 @@ async function assembleBookHtml(opts) {
4036
4038
  const errorMsg = err instanceof Error ? err.message : String(err);
4037
4039
  throw new Error(`Failed to read file ${file}: ${errorMsg}`);
4038
4040
  }
4039
- const env = {};
4041
+ const env = { docId: slugify(chapterId) };
4040
4042
  if (opts.annotateSourceChapters)
4041
4043
  env.sourceChapter = chapterId;
4042
4044
  const rendered = md.render(content, env);
@@ -4082,7 +4084,7 @@ ${projectCss.trim()}` : null
4082
4084
  const lastPage = opts.print && lastPageMode(opts.print);
4083
4085
  const printAttrs = (opts.print?.signature && opts.print.signature > 1 ? ` data-gp-signature="${opts.print.signature}"` : "") + (lastPage ? ` data-gp-last-page="${lastPage}"` : "");
4084
4086
  return `<!DOCTYPE html>
4085
- <html lang="en"${printAttrs}>
4087
+ <html lang="${language}"${printAttrs}>
4086
4088
  <head>
4087
4089
  <meta charset="UTF-8">
4088
4090
  <meta name="viewport" content="width=device-width, initial-scale=1.0">${opts.printArea ? `
@@ -4145,6 +4147,7 @@ ${css.trim()}
4145
4147
  projectCss: inlined.css,
4146
4148
  preloadImages: cssAssetCopies.map((c) => c.to),
4147
4149
  title: opts.title,
4150
+ language: opts.language,
4148
4151
  print: opts.print,
4149
4152
  printArea: opts.printArea,
4150
4153
  plugins: opts.plugins,
@@ -4158,6 +4161,7 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
4158
4161
  await mkdir(outDir, { recursive: true });
4159
4162
  const html = await renderChapters(inputDir, {
4160
4163
  title: opts.title,
4164
+ language: opts.language,
4161
4165
  print: opts.print,
4162
4166
  printArea: opts.printArea,
4163
4167
  styles: opts.styles,
@@ -5058,6 +5062,7 @@ async function renderBook(ctx) {
5058
5062
  const cssAssets = [];
5059
5063
  const htmlFile = await renderChaptersToFile(renderDir, workDir, {
5060
5064
  title: config.title,
5065
+ language: config.language,
5061
5066
  print: config.print,
5062
5067
  printArea: { ...config.page, preset: config.preset, spec: config.spec },
5063
5068
  styles: config.styles,
@@ -5212,7 +5217,7 @@ class PdfOutput {
5212
5217
  const rawPdf = pdfxMode ? path7.join(stage, "raw.pdf") : path7.resolve(pdfFile);
5213
5218
  await fsp2.mkdir(path7.dirname(path7.resolve(pdfFile)), { recursive: true });
5214
5219
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
5215
- const { buildNativePdf } = await import("./engine-p93f06gj.js");
5220
+ const { buildNativePdf } = await import("./engine-892swzzc.js");
5216
5221
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
5217
5222
  title: config.title,
5218
5223
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -5514,6 +5519,7 @@ async function renderPreviewBook(inputPath, config, opts) {
5514
5519
  const { plugins, pluginStyles } = await loadPluginsWithCss(config.extensions, inputPath, (ref, err) => warn(`Skipping plugin "${ref}" in preview — ${err.message}`));
5515
5520
  return renderChapters(inputPath, {
5516
5521
  title: config.title ?? "Document",
5522
+ language: config.language,
5517
5523
  print: config.print,
5518
5524
  printArea: config.printArea,
5519
5525
  styles: config.styles,
@@ -6500,6 +6506,43 @@ async function startPreviewServer(options) {
6500
6506
  };
6501
6507
  }
6502
6508
 
6509
+ // src/lib/project-templates.ts
6510
+ import { isMap, isScalar, isSeq, parseDocument } from "yaml";
6511
+ var BUILT_IN_TEMPLATE_IDS = [
6512
+ "book",
6513
+ "zine",
6514
+ "technical",
6515
+ "paper"
6516
+ ];
6517
+ var BUILT_IN_META = {
6518
+ book: {
6519
+ label: "Book",
6520
+ description: "A clean starting point for a novel, memoir, or any long-form book."
6521
+ },
6522
+ zine: {
6523
+ label: "Zine",
6524
+ description: "A short, personal zine made to be printed, folded, and shared."
6525
+ },
6526
+ technical: {
6527
+ label: "Technical document",
6528
+ description: "A manual or guide with steps, reference tables, and code examples."
6529
+ },
6530
+ paper: {
6531
+ label: "Academic paper",
6532
+ description: "A journal-style article with LaTeX math, numbered equations, figures, tables, notes and references.",
6533
+ look: "academic"
6534
+ }
6535
+ };
6536
+ var SKIP_ENTRIES = new Set([
6537
+ ".git",
6538
+ "node_modules",
6539
+ "out",
6540
+ "dist",
6541
+ "build",
6542
+ ".gutterpress",
6543
+ ".DS_Store"
6544
+ ]);
6545
+
6503
6546
  // src/lib/project-scaffold.ts
6504
6547
  import { access, copyFile, cp, mkdir as mkdir4, readdir as readdir5, readFile as readFile22, stat as stat4, writeFile as writeFile5 } from "node:fs/promises";
6505
6548
  import { constants as FS, existsSync as existsSync7 } from "node:fs";
@@ -6507,10 +6550,10 @@ import path12 from "node:path";
6507
6550
 
6508
6551
  // src/lib/page-size.ts
6509
6552
  import postcss5 from "postcss";
6510
- import { isMap, Scalar } from "yaml";
6553
+ import { isMap as isMap2, Scalar } from "yaml";
6511
6554
  function writePageBounds(doc, page) {
6512
6555
  const tolerance = page.tolerance ?? numberAt(doc, ["page", "tolerance"]);
6513
- if (!isMap(doc.get("page", true)))
6556
+ if (!isMap2(doc.get("page", true)))
6514
6557
  doc.set("page", doc.createNode({}));
6515
6558
  doc.setIn(["page", "width"], page.width);
6516
6559
  doc.setIn(["page", "height"], page.height);
@@ -6528,7 +6571,8 @@ function starterOverridesCss() {
6528
6571
  * Your book's own styles.
6529
6572
  *
6530
6573
  * This file loads AFTER every extension, so any rule you add here overrides
6531
- * a look's defaults. A new book has no look until you add one:
6574
+ * a look's defaults. A new book has no look until you add one (the paper
6575
+ * template adds the academic look for you):
6532
6576
  * gutterpress ext add clean-book <book> --look (a built-in look)
6533
6577
  * gutterpress ext add <package> <book> (a look from npm)
6534
6578
  * A book has one look at a time; adding another turns the current one off.
@@ -6650,6 +6694,9 @@ async function scaffoldProject(options) {
6650
6694
  let openFile = path12.join(projectDir, "chapter-01.md");
6651
6695
  try {
6652
6696
  await fillTemplateFile(manifestPath, substitutions);
6697
+ const look = customTemplateDir ? undefined : BUILT_IN_META[template].look;
6698
+ if (look)
6699
+ await addBuiltInStyleSet(projectDir, look);
6653
6700
  if (preset) {
6654
6701
  const { doc, file } = await loadManifestDoc(projectDir);
6655
6702
  doc.set("preset", preset);
@@ -6862,23 +6909,6 @@ function substitute(text, table) {
6862
6909
  return out;
6863
6910
  }
6864
6911
 
6865
- // src/lib/project-templates.ts
6866
- import { isMap as isMap2, isScalar, isSeq, parseDocument } from "yaml";
6867
- var BUILT_IN_TEMPLATE_IDS = [
6868
- "book",
6869
- "zine",
6870
- "technical"
6871
- ];
6872
- var SKIP_ENTRIES = new Set([
6873
- ".git",
6874
- "node_modules",
6875
- "out",
6876
- "dist",
6877
- "build",
6878
- ".gutterpress",
6879
- ".DS_Store"
6880
- ]);
6881
-
6882
6912
  // src/lib/extension-import.ts
6883
6913
  import { existsSync as existsSync8 } from "node:fs";
6884
6914
  import { cp as cp2, mkdir as mkdir6, mkdtemp, readFile as readFile24, rm, writeFile as writeFile7 } from "node:fs/promises";
@@ -9023,7 +9053,7 @@ async function resolvePublishProvider(id, book) {
9023
9053
  const ext = await bookExtensions(book);
9024
9054
  const npmEntry = ext.extensions.some((c) => !c.path && c.name === id);
9025
9055
  if (npmEntry) {
9026
- const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-ct1wk344.js");
9056
+ const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-w8xt4kbq.js");
9027
9057
  await restoreForCommand2(book.projectDir, { manifestPath: book.manifestPath, failFast: true });
9028
9058
  }
9029
9059
  const publishers = await discover(ext);
@@ -9061,7 +9091,7 @@ function infoFor(p) {
9061
9091
  }
9062
9092
  function pluginProvider(p) {
9063
9093
  let loading;
9064
- const load = () => loading ??= import("./plugins-fr4fnzhb.js").then((m) => m.loadPublishFunction(p));
9094
+ const load = () => loading ??= import("./plugins-9en8m9y0.js").then((m) => m.loadPublishFunction(p));
9065
9095
  return {
9066
9096
  info: infoFor(p),
9067
9097
  async authenticate() {
@@ -9548,4 +9578,4 @@ function normalizeProjectKey(projectDir) {
9548
9578
  }
9549
9579
  // src/lib/publish/manifest-publish.ts
9550
9580
  import { isMap as isMap4 } from "yaml";
9551
- export { reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, scaffoldProject, EXTENSION_KINDS, scaffoldExtension, BUILT_IN_TEMPLATE_IDS, importExtensionFromFile, importExtensionFromUrl, searchNpmExtensions, publishCredentialKey, publishConnectionStatus, listPublishAccounts, connectGoogleDrive, listBookPublishProviders, resolvePublishProvider, resolvePublishFormat, runPublish, connectPublishProvider, disconnectPublishCredential };
9581
+ export { reportMissingTools, executeValidation, executeAndReport, splitOutPath, runBuild, openPath, startPreviewServer, BUILT_IN_TEMPLATE_IDS, scaffoldProject, EXTENSION_KINDS, scaffoldExtension, importExtensionFromFile, importExtensionFromUrl, searchNpmExtensions, publishCredentialKey, publishConnectionStatus, listPublishAccounts, connectGoogleDrive, listBookPublishProviders, resolvePublishProvider, resolvePublishFormat, runPublish, connectPublishProvider, disconnectPublishCredential };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "./cli-cjftezxr.js";
3
+ } from "./cli-nr6j6300.js";
4
4
 
5
5
  // src/utils/logger.ts
6
6
  var currentLevel = "INFO";
@@ -11,7 +11,7 @@ import {
11
11
  import {
12
12
  PACKAGE_VERSION,
13
13
  log
14
- } from "./cli-kx4hbnx2.js";
14
+ } from "./cli-b0vyjvmt.js";
15
15
  import {
16
16
  __require
17
17
  } from "./cli-37x76zdn.js";
@@ -7,16 +7,16 @@ import {
7
7
  prettify,
8
8
  readExtensionMeta,
9
9
  slugify
10
- } from "./cli-ntwqmdnw.js";
10
+ } from "./cli-h17e3mtz.js";
11
11
  import {
12
12
  log
13
- } from "./cli-kx4hbnx2.js";
13
+ } from "./cli-b0vyjvmt.js";
14
14
  import {
15
15
  UsageError
16
16
  } from "./cli-d8xjns66.js";
17
17
  import {
18
18
  getAssetsDir
19
- } from "./cli-1ngqzg8s.js";
19
+ } from "./cli-fe43evmf.js";
20
20
  import {
21
21
  BuildError,
22
22
  EXIT_CODES
@@ -98,7 +98,8 @@ var BUNDLED_EXTENSIONS = [
98
98
  "markdown-it-sub",
99
99
  "markdown-it-sup",
100
100
  "markdown-it-abbr",
101
- "gutterpress-gfm-alerts"
101
+ "gutterpress-gfm-alerts",
102
+ "gutterpress-math"
102
103
  ];
103
104
  function isBundledExtension(name) {
104
105
  return BUNDLED_EXTENSIONS.includes(name);
@@ -114,7 +115,7 @@ function parseExtensionSpecifier(use) {
114
115
  if (isPathSpecifier(spec))
115
116
  return { kind: "path", path: spec };
116
117
  if (/\.(m?js|cjs)$/i.test(spec) || !spec.startsWith("@") && spec.includes("/")) {
117
- throw new Error(`"${spec}" looks like a path — write it as "./${spec}". A specifier that does not ` + "start with ./, ../ or / is an npm package name.");
118
+ throw new Error(`"${spec}" looks like a path; write it as "./${spec}". A specifier that does not ` + "start with ./, ../ or / is an npm package name.");
118
119
  }
119
120
  const parsed = parseNpmPluginSpec(spec);
120
121
  if (isBundledExtension(parsed.name)) {
@@ -569,6 +570,15 @@ function mergeShape(cli, manifest, preset) {
569
570
  }
570
571
  return out;
571
572
  }
573
+ var LANGUAGE_TAG = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/;
574
+ function resolveLanguage(value) {
575
+ if (value === undefined)
576
+ return "en";
577
+ if (typeof value !== "string" || !LANGUAGE_TAG.test(value)) {
578
+ throw new UsageError(`Manifest field \`language\` must be a language tag such as "en", "de" or "pt-BR", got ${JSON.stringify(value)}.`);
579
+ }
580
+ return value;
581
+ }
572
582
  function resolveConfig(cliOverrides, manifest) {
573
583
  const presetName = cliOverrides.preset ?? manifest.preset;
574
584
  return resolveWithPreset(cliOverrides, manifest, resolvePreset(presetName));
@@ -656,6 +666,7 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
656
666
  return {
657
667
  title: c.title ?? m.title ?? "Document",
658
668
  authors: c.authors ?? m.authors ?? [],
669
+ language: resolveLanguage(c.language ?? m.language),
659
670
  preset: preset.id,
660
671
  spec: preset.spec,
661
672
  styles: c.styles ?? m.styles,
@@ -1451,9 +1462,14 @@ var RECOMMENDED_EXTENSIONS = [
1451
1462
  use: "gutterpress-gfm-alerts",
1452
1463
  label: "Callouts",
1453
1464
  description: "Turn a quote into a boxed note: start it with `> [!NOTE]` (or `TIP`, `IMPORTANT`, `WARNING`, `CAUTION`)."
1465
+ },
1466
+ {
1467
+ use: "gutterpress-math",
1468
+ label: "Math",
1469
+ description: "Write LaTeX math between dollar signs: `$E = mc^2$` in a line, or `$$` … `$$` on lines of their own."
1454
1470
  }
1455
1471
  ];
1456
- var BUILT_IN_STYLE_SET_IDS = ["clean-book", "zine", "technical-doc"];
1472
+ var BUILT_IN_STYLE_SET_IDS = ["clean-book", "zine", "technical-doc", "academic"];
1457
1473
  var mutationQueues = new Map;
1458
1474
  function projectKey(projectDir) {
1459
1475
  const resolved = path4.resolve(projectDir);
@@ -3,7 +3,7 @@ import {
3
3
  } from "./cli-eyyggect.js";
4
4
  import {
5
5
  getAssetPath
6
- } from "./cli-1ngqzg8s.js";
6
+ } from "./cli-fe43evmf.js";
7
7
 
8
8
  // src/engine/compiler/build.ts
9
9
  import { readFile } from "node:fs/promises";