gutterpress 0.11.17-alpha.4 → 0.11.17-alpha.6

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 (29) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{build-fam0tqg6.js → build-yswp3npj.js} +4 -4
  3. package/dist/{cli-8apk8571.js → cli-0wtnw98y.js} +1 -1
  4. package/dist/{cli-2kc34kme.js → cli-an3pxzzk.js} +77 -110
  5. package/dist/{cli-pqcb2nnb.js → cli-jjbeqx94.js} +1 -1
  6. package/dist/{cli-jyy9pqf3.js → cli-ycbg7mxk.js} +5 -5
  7. package/dist/cli.js +12 -12
  8. package/dist/{components-dhgv2sm7.yaml → components-zzqdpt3m.yaml} +1 -1
  9. package/dist/{doctor-zre5k5fg.js → doctor-xjgxr88y.js} +2 -2
  10. package/dist/{engine-3xqgm82d.js → engine-5d1rpxxq.js} +2 -2
  11. package/dist/{engine-zdb41ay1.js → engine-yf2w9hj7.js} +1 -1
  12. package/dist/{ext-hfqcg6ax.js → ext-n664e0ka.js} +4 -4
  13. package/dist/favicon-272ga6y0.ico +0 -0
  14. package/dist/{gutterpress-viewer-1n793gwd.js → gutterpress-viewer-ez7k4ha7.js} +19 -1
  15. package/dist/{index-hrx0gfkj.js → index-09h3n36d.js} +2 -2
  16. package/dist/{index-gp503ze9.js → index-2vd5b3vy.js} +76 -109
  17. package/dist/{index-q1xqxrh9.js → index-vmcfahe2.js} +4 -4
  18. package/dist/index.js +3 -3
  19. package/dist/lib/markdown/gutterpress-css.d.ts +1 -1
  20. package/dist/lib/markdown/markers.d.ts +14 -6
  21. package/dist/{new-bjdf7bhg.js → new-8xwac8nd.js} +4 -4
  22. package/dist/{page-templates-7md4ev7d.css → page-templates-tx9ryt19.css} +4 -11
  23. package/dist/{preflight-gvw01661.js → preflight-62c2r37w.js} +4 -4
  24. package/dist/{preview-1cschq8s.js → preview-qdby30fx.js} +4 -4
  25. package/dist/{publish-ddqrb8s9.js → publish-v9tdq7kc.js} +4 -4
  26. package/dist/render.js +73 -106
  27. package/dist/{validate-3hjkgxrz.js → validate-9nszbckg.js} +4 -4
  28. package/package.json +1 -1
  29. package/dist/favicon-wkbm9cjn.ico +0 -0
package/dist/render.js CHANGED
@@ -432,6 +432,16 @@ function resolveContainerShape(name, pluginName, decl) {
432
432
  ...check
433
433
  };
434
434
  }
435
+ function presetVariantOf(name, pluginName, rawDecl) {
436
+ const preset = rawDecl.preset !== undefined ? rawDecl.preset : {};
437
+ if (typeof preset !== "object" || preset === null || Array.isArray(preset)) {
438
+ throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset\` that is not a plain object.`);
439
+ }
440
+ if (preset.variant !== undefined && typeof preset.variant !== "string") {
441
+ throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset.variant\` that is not a string.`);
442
+ }
443
+ return preset.variant;
444
+ }
435
445
  function resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf) {
436
446
  const pluginName = originOf.get(name);
437
447
  if (typeof rawDecl !== "object" || rawDecl === null || Array.isArray(rawDecl)) {
@@ -460,22 +470,22 @@ function resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf) {
460
470
  if (targetRaw.alias !== undefined) {
461
471
  throw new Error(`Plugin "${pluginName}"'s marker "@${name}" aliases "@${rawDecl.alias}", which is itself an alias. ` + `Chained aliases are not supported — alias the base marker directly.`);
462
472
  }
463
- const preset = rawDecl.preset !== undefined ? rawDecl.preset : {};
464
- if (typeof preset !== "object" || preset === null || Array.isArray(preset)) {
465
- throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset\` that is not a plain object.`);
466
- }
467
- if (preset.variant !== undefined && typeof preset.variant !== "string") {
468
- throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset.variant\` that is not a string.`);
469
- }
470
473
  const container = resolveContainerShape(rawDecl.alias, originOf.get(rawDecl.alias), targetRaw);
471
- return { name, baseKind: rawDecl.alias, presetVariant: preset.variant, ...container };
474
+ return { name, baseKind: rawDecl.alias, presetVariant: presetVariantOf(name, pluginName, rawDecl), ...container };
472
475
  }
473
- return { name, baseKind: name, ...resolveContainerShape(name, pluginName, rawDecl) };
476
+ const presetVariant = presetVariantOf(name, pluginName, rawDecl);
477
+ return {
478
+ name,
479
+ baseKind: name,
480
+ ...presetVariant !== undefined ? { presetVariant } : {},
481
+ ...resolveContainerShape(name, pluginName, rawDecl)
482
+ };
474
483
  }
475
484
  var CORE_MARKER_OWNER = "Gutterpress";
476
485
  var CORE_DECLARED_MARKERS = {
477
486
  columns: {
478
487
  class: "gp-columns",
488
+ preset: { variant: "2" },
479
489
  variants: { 1: "gp-columns-1", 2: "gp-columns-2", 3: "gp-columns-3", 4: "gp-columns-4", 5: "gp-columns-5" }
480
490
  }
481
491
  };
@@ -651,20 +661,35 @@ function plugin(md, pluginOptions = {}) {
651
661
  const at = declaredFrameIndex(kind);
652
662
  if (at === -1)
653
663
  return false;
654
- while (declaredFrames.length > at) {
655
- const frame = declaredFrames.pop();
656
- out.push(new state.Token("layout_component_close", frame.tag, -1));
657
- }
664
+ drainDeclaredFrames(at);
658
665
  return true;
659
666
  }
660
- function drainDeclaredFrames() {
661
- while (declaredFrames.length) {
667
+ function sectionAbove(length) {
668
+ const section = stack.get("section");
669
+ return !!section && section.inColumns && declaredFrameIndex("columns") === length - 1;
670
+ }
671
+ function sectionHostDepth() {
672
+ const section = stack.get("section");
673
+ if (section && !section.inColumns)
674
+ return 0;
675
+ return declaredFrameIndex("columns") + 1;
676
+ }
677
+ function drainDeclaredFrames(keep = 0) {
678
+ while (declaredFrames.length > keep) {
679
+ if (sectionAbove(declaredFrames.length)) {
680
+ stack.close("section");
681
+ continue;
682
+ }
662
683
  const frame = declaredFrames.pop();
663
684
  out.push(new state.Token("layout_component_close", frame.tag, -1));
664
685
  }
665
686
  }
666
687
  function drainDeclaredFramesAtEof() {
667
688
  while (declaredFrames.length) {
689
+ if (sectionAbove(declaredFrames.length)) {
690
+ stack.close("section");
691
+ continue;
692
+ }
668
693
  const frame = declaredFrames.pop();
669
694
  if (!frame.decl.autoCloseAtEof) {
670
695
  warn(state.env, frame.line || 0, "declared_marker_eof_close", `An open @${frame.kind} reached end-of-document; closing it automatically. Add ` + `@end-${frame.kind} to close it explicitly, or declare autoCloseAt: ["eof"] on this ` + `marker if running to end-of-document is expected.`, null);
@@ -808,7 +833,7 @@ function plugin(md, pluginOptions = {}) {
808
833
  __component: { component: meta.kind, kind: decl.baseKind, variant, attrs }
809
834
  };
810
835
  }
811
- function openSection(meta) {
836
+ function openSection(meta, inColumns = false) {
812
837
  const t = new state.Token("layout_section_open", "div", 1);
813
838
  const component = meta.__component || null;
814
839
  addClasses(t, "section", meta.attrs && meta.attrs.class ? meta.attrs.class : "");
@@ -821,25 +846,11 @@ function plugin(md, pluginOptions = {}) {
821
846
  kind: "section",
822
847
  classes: meta.attrs && meta.attrs.class || "",
823
848
  sawContent: false,
849
+ ...inColumns ? { inColumns: true } : {},
824
850
  meta: { name: meta.name || null, attrs: { ...meta.attrs || {} }, component },
825
851
  openToken: t
826
852
  }, t);
827
853
  }
828
- function breakHost() {
829
- return stack.get("section") || stack.get("page") || stack.get("spread");
830
- }
831
- function gridClassOf(frame) {
832
- if (!frame || !frame.classes)
833
- return null;
834
- return frame.classes.split(/\s+/).find((c) => c.startsWith("gp-grid-")) || null;
835
- }
836
- function warnBreakInsideGrid(kind, line, meta) {
837
- const host = breakHost();
838
- const gridClass = gridClassOf(host);
839
- if (!gridClass)
840
- return;
841
- warn(state.env, line, "break_inside_grid", `@${kind} inside a grid container: the enclosing @${host.kind} carries .${gridClass}, so this break's <div> becomes a grid ITEM — it takes a cell of its own, corrupts the grid's placement, and print and the live preview then disagree about which page the content after it lands on. Move the break outside the grid @${host.kind}, or remove it — grid rows already flow and fragment across pages on their own.`, meta);
842
- }
843
854
  function warnIfEmptyDecoratedSection(closingKind, closingLine) {
844
855
  const sec = stack.get("section");
845
856
  if (!sec || sec.sawContent)
@@ -904,14 +915,15 @@ function plugin(md, pluginOptions = {}) {
904
915
  continue;
905
916
  }
906
917
  if (kind === "section") {
907
- drainDeclaredFrames();
918
+ const keep = sectionHostDepth();
919
+ drainDeclaredFrames(keep);
908
920
  warnIfEmptyDecoratedSection("section", line);
909
921
  stack.close("section");
910
922
  const spread = stack.get("spread");
911
923
  if (spread && spread.noPagesYet) {
912
924
  warn(state.env, line, "spread_without_pages", "@section inside a spread without an explicit @page. Allowed (adhoc spread), but explicit @page markers give stronger control.", meta);
913
925
  }
914
- openSection(meta);
926
+ openSection(meta, keep > 0);
915
927
  continue;
916
928
  }
917
929
  if (kind === "continue") {
@@ -920,7 +932,8 @@ function plugin(md, pluginOptions = {}) {
920
932
  warn(state.env, line, "continue_without_section", "@continue used without an open @section; ignoring marker.", meta);
921
933
  continue;
922
934
  }
923
- drainDeclaredFrames();
935
+ const keep = sectionHostDepth();
936
+ drainDeclaredFrames(keep);
924
937
  const contMeta = {
925
938
  name: section.meta.name,
926
939
  attrs: { ...section.meta.attrs || {} }
@@ -935,11 +948,10 @@ function plugin(md, pluginOptions = {}) {
935
948
  cls.push("gp-continued");
936
949
  contMeta.attrs.class = cls.join(" ");
937
950
  stack.close("section");
938
- openSection(contMeta);
951
+ openSection(contMeta, keep > 0);
939
952
  continue;
940
953
  }
941
954
  if (kind === "page-break") {
942
- warnBreakInsideGrid("page-break", line, meta);
943
955
  const t = new state.Token("layout_page_break", "div", 0);
944
956
  t.meta = { line };
945
957
  t.attrSet("class", "gp-page-break");
@@ -948,7 +960,6 @@ function plugin(md, pluginOptions = {}) {
948
960
  continue;
949
961
  }
950
962
  if (kind === "column-break") {
951
- warnBreakInsideGrid("column-break", line, meta);
952
963
  const t = new state.Token("layout_column_break", "div", 0);
953
964
  t.meta = { line };
954
965
  t.attrSet("class", "gp-column-break");
@@ -957,7 +968,7 @@ function plugin(md, pluginOptions = {}) {
957
968
  continue;
958
969
  }
959
970
  if (kind === "end-section") {
960
- drainDeclaredFrames();
971
+ drainDeclaredFrames(sectionHostDepth());
961
972
  warnIfEmptyDecoratedSection("end-section", line);
962
973
  stack.close("section");
963
974
  continue;
@@ -1055,11 +1066,20 @@ body { margin: 0; }
1055
1066
  .gp-column-break { break-after: column; height: 0; font-size: 0; line-height: 0; visibility: hidden; }
1056
1067
 
1057
1068
  :where(h1,h2,h3,h4,h5,h6) { break-after: avoid; }
1069
+ /* A lead-in paragraph ("**ROLL A DIE!**" above a roll table, "You will need:"
1070
+ above a list) stays with the table or list it introduces, the way a heading
1071
+ stays with what follows it -- otherwise the label can end a column or page
1072
+ with its table starting on the next (#370). Standard break-after; the
1073
+ :has() selector is Chromium's own. :where() keeps specificity at zero so an
1074
+ author rule such as p { break-after: auto } wins. Deliberately table/ul/ol only,
1075
+ also when a @section wraps it (the second rule): the lead-in then keeps with
1076
+ the section that opens with the table or list. */
1077
+ :where(p):has(+ :where(table, ul, ol)) { break-after: avoid; }
1078
+ :where(p):has(+ :where(.section) > :where(table, ul, ol):first-child) { break-after: avoid; }
1058
1079
  :where(img, svg, video) { max-width: 100%; }
1059
1080
  :where(p > img:only-child, figure > img) { width: fit-content; max-width: 100%; height: auto; vertical-align: bottom; }
1060
1081
  :where(p) > :where(img:not([class])) { max-height: calc(var(--gp-content-h) - 4px); object-fit: contain; }
1061
1082
  :where(figure) { break-inside: avoid; }
1062
- :where(.section, figure) > :where(:first-child) { break-before: avoid; }
1063
1083
 
1064
1084
  `;
1065
1085
 
@@ -1119,26 +1139,24 @@ var GUTTERPRESS_CSS = `
1119
1139
  .gp-tight { --gp-gap: 0.5em; }
1120
1140
  .gp-loose { --gp-gap: 2em; }
1121
1141
 
1122
- /* column runs — plain CSS Multi-column, exposed as author vocabulary so
1123
- "put this in two columns" does not require borrowing a styled container
1124
- from the book's own component layer. That borrowing is what this exists
1125
- to prevent: a book whose theme paints .section chrome by default gives
1126
- every author who opens a section just to start a column run a panel they
1127
- did not ask for, and the book then needs a reset rule to take it back.
1128
- With a neutral primitive the author opts into columns and nothing else.
1142
+ /* column runs — plain CSS Multi-column, emitted by the core @columns marker
1143
+ (markers.js): @columns [1-5] ... @end-columns is the ONE way to lay
1144
+ content out in columns, and @column-break moves to the next column.
1145
+ The classes below are the CSS that marker emits, not author vocabulary.
1129
1146
 
1130
1147
  Permanent vocabulary, not a shim: Chromium implements multicol natively
1131
1148
  and these rules are the standard properties verbatim, so there is no
1132
1149
  spec gap here to remove later. Deliberately minimal — column-fill is
1133
- NOT set, because the correct value depends on whether the run fragments
1134
- across pages (auto packs each page's columns; the CSS initial balance is
1135
- right for a run that fits on one page) and only the author knows which.
1136
- --gp-column-gap is author-settable.
1137
- .gp-columns is what the core @columns marker emits (markers.js); on its
1138
- own it is two columns, and @columns 1 to 5 add the matching count, which
1139
- wins because it comes later at the same specificity. */
1140
- .gp-columns, .gp-columns-1, .gp-columns-2, .gp-columns-3, .gp-columns-4, .gp-columns-5 {
1150
+ NOT set (the CSS initial balance applies).
1151
+ Themes style a run through two tokens instead of writing column rules:
1152
+ --gp-column-gap default 1.5em
1153
+ --gp-column-rule default none (any column-rule shorthand value)
1154
+ Both hooks sit at zero specificity (:where), so any author rule wins.
1155
+ .gp-columns is two columns on its own; a count class (bare @columns
1156
+ emits gp-columns-2) wins because it comes later at the same specificity. */
1157
+ :where(.gp-columns, .gp-columns-1, .gp-columns-2, .gp-columns-3, .gp-columns-4, .gp-columns-5) {
1141
1158
  column-gap: var(--gp-column-gap, 1.5em);
1159
+ column-rule: var(--gp-column-rule, none);
1142
1160
  }
1143
1161
  .gp-columns { columns: 2; }
1144
1162
  .gp-columns-1 { columns: 1; }
@@ -1147,54 +1165,8 @@ var GUTTERPRESS_CSS = `
1147
1165
  .gp-columns-4 { columns: 4; }
1148
1166
  .gp-columns-5 { columns: 5; }
1149
1167
 
1150
- /* the per-shape decisions the paragraph above deliberately leaves to the
1151
- author, named instead of left as raw CSS every book was reinventing
1152
- (CLAUDE.md §0: "behavior broadly useful to non-technical authors belongs
1153
- in core").
1154
- Permanent vocabulary, standard properties verbatim — same rationale as
1155
- the column/grid runs above. One name each, no aliases:
1156
- .gp-columns-all column-span: all a heading or block that
1157
- spans every column in the
1158
- run it sits inside.
1159
- .gp-columns-flow column-fill: auto a run that FRAGMENTS across
1160
- pages — every page's
1161
- columns fill instead of
1162
- only the last one
1163
- balancing (the dead-column
1164
- collapse the build's
1165
- engine.multicol.dead-column
1166
- warning names this fix
1167
- for).
1168
- .gp-columns-balanced column-fill: balance a run that fits on ONE
1169
- page (the CSS initial
1170
- value — ragged columns
1171
- would be wrong here). */
1172
- .gp-columns-all { column-span: all; }
1173
- .gp-columns-flow { column-fill: auto; }
1174
- .gp-columns-balanced { column-fill: balance; }
1175
-
1176
- /* grid runs — the SLOTTED counterpart to the column runs above. Grid places
1177
- each child into the next cell, across then down (deterministic slots: card
1178
- layouts, stat blocks, image-plus-caption pairs); columns FLOW one text run
1179
- down then across. Same neutral-primitive rationale as .gp-columns-*, and
1180
- permanent vocabulary for the same reason: standard CSS Grid verbatim, no
1181
- spec gap to remove later. MEASURED (Chromium 151, gp-grid evidence pack):
1182
- grid rows fragment across sheets with EXACT print/viewer parity — 2- and
1183
- 3-col, unequal item heights, mid-row cuts, multi-sheet overflow,
1184
- break-inside:avoid, gap geometry — so a grid taller than the page is safe,
1185
- no fit-one-page constraint. Two things to know, not fix:
1186
- - on a min-height page root (MARKER_CSS), default align-content
1187
- stretches rows apart to fill the page — identically in both engines.
1188
- Authors wanting packed rows set align-content: start.
1189
- - a @page-break / @column-break marker DIRECTLY inside a grid container
1190
- becomes a grid item and corrupts placement (the one measured parity
1191
- break); markers.js diagnoses it (break_inside_grid).
1192
- --gp-grid-gap is author-settable. */
1193
- .gp-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gp-grid-gap, 1.5em); }
1194
- .gp-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gp-grid-gap, 1.5em); }
1195
-
1196
1168
  /* fragmentation controls — generic pagination utilities for any element,
1197
- independent of the column/grid vocabulary above (a book reached for
1169
+ independent of the column vocabulary above (a book reached for
1198
1170
  these just as often outside a multicol run: keeping a card whole,
1199
1171
  forcing a section to start a fresh page). Standard properties only —
1200
1172
  this project is Chromium-only (CLAUDE.md), so there are no legacy
@@ -1352,13 +1324,8 @@ var GP_CLASSES = new Set([
1352
1324
  "gp-columns-3",
1353
1325
  "gp-columns-4",
1354
1326
  "gp-columns-5",
1355
- "gp-columns-all",
1356
- "gp-columns-flow",
1357
- "gp-columns-balanced",
1358
1327
  "gp-no-break",
1359
1328
  "gp-break-before",
1360
- "gp-grid-2",
1361
- "gp-grid-3",
1362
1329
  "gp-shape",
1363
1330
  "gp-pin",
1364
1331
  "gp-top",
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  executeAndReport
3
- } from "./cli-2kc34kme.js";
3
+ } from "./cli-an3pxzzk.js";
4
4
  import {
5
5
  log
6
- } from "./cli-8apk8571.js";
6
+ } from "./cli-0wtnw98y.js";
7
7
  import {
8
8
  UsageError,
9
9
  rejectExtraPositionals,
10
10
  rejectUnknownFlags
11
- } from "./cli-pqcb2nnb.js";
11
+ } from "./cli-jjbeqx94.js";
12
12
  import"./cli-ht1bcqdd.js";
13
- import"./cli-jyy9pqf3.js";
13
+ import"./cli-ycbg7mxk.js";
14
14
  import {
15
15
  BuildError,
16
16
  EXIT_CODES
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gutterpress",
3
- "version": "0.11.17-alpha.4",
3
+ "version": "0.11.17-alpha.6",
4
4
  "description": "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5
5
  "author": "itlackey",
6
6
  "license": "MPL-2.0",
Binary file