gutterpress 0.10.0-alpha.3 → 0.10.0-beta.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 (37) hide show
  1. package/README.md +2 -0
  2. package/dist/api/index.js +2 -2
  3. package/dist/{audit-xxegv0t0.js → audit-85zn35v8.js} +4 -4
  4. package/dist/{build-w0vb6mgq.js → build-1faqmp0y.js} +6 -4
  5. package/dist/{cli-5czby0dd.js → cli-5tffrg48.js} +90 -23
  6. package/dist/{cli-f4rcbt2t.js → cli-7e79d7se.js} +1 -1
  7. package/dist/{cli-yp3p0sf2.js → cli-afm8pa0h.js} +327 -24
  8. package/dist/{cli-gk3wsdpb.js → cli-v935c2ws.js} +1 -1
  9. package/dist/{cli-revgt4pr.js → cli-yzf38679.js} +1 -0
  10. package/dist/cli.js +16 -16
  11. package/dist/{doctor-ctgn1dnt.js → doctor-69v5c9wy.js} +2 -2
  12. package/dist/engine/compiler/build.d.ts +1 -1
  13. package/dist/engine/shared/flush.d.ts +59 -0
  14. package/dist/engine/shared/margin-box-support.d.ts +24 -0
  15. package/dist/{engine-eng4xh2q.js → engine-hrtn0b28.js} +1 -1
  16. package/dist/{engine-q5skhgms.js → engine-nbfdy0pp.js} +2 -2
  17. package/dist/{gutterpress-agent-1ctgfz92.js → gutterpress-agent-cazqstr1.js} +49 -1
  18. package/dist/{gutterpress-viewer-cem7dmr5.js → gutterpress-viewer-fb6fhwsy.js} +137 -51
  19. package/dist/{index-s416qpdv.js → index-052x5fef.js} +89 -22
  20. package/dist/{index-mvehxh0e.js → index-c2b6gyeg.js} +47 -4
  21. package/dist/{index-a4kr77td.js → index-wsydvp08.js} +287 -23
  22. package/dist/index.js +3 -3
  23. package/dist/lib/build-runner.d.ts +7 -0
  24. package/dist/lib/engine.d.ts +9 -0
  25. package/dist/lib/markdown/gutterpress-css.d.ts +9 -1
  26. package/dist/lib/markdown/markers.d.ts +51 -1
  27. package/dist/{lint-gsh6qwrq.js → lint-17404bsx.js} +4 -4
  28. package/dist/{new-nkhycqy4.js → new-vsgpxzek.js} +4 -4
  29. package/dist/{plugin-129wcs93.js → plugin-s4yfpx6p.js} +4 -4
  30. package/dist/{preflight-41hcejg9.js → preflight-1h3apvqh.js} +4 -4
  31. package/dist/{preview-pftsvr9z.js → preview-cqf383g4.js} +6 -5
  32. package/dist/{preview-interface-fnqb2y4v.js → preview-interface-hg5eqv88.js} +193 -4
  33. package/dist/{publish-dnjvb4jg.js → publish-arvycera.js} +4 -4
  34. package/dist/render.js +83 -7
  35. package/dist/{repair-a3enjttn.js → repair-vmwh2bhe.js} +4 -4
  36. package/dist/{validate-ph1xffqy.js → validate-ztzq7p35.js} +4 -4
  37. package/package.json +1 -1
@@ -2,6 +2,7 @@ import {
2
2
  BuildError,
3
3
  EXIT_CODES,
4
4
  INSTALL_HINTS,
5
+ MARGIN_BOX_IGNORED_PROPERTIES,
5
6
  RENDER_TIMEOUT_MS,
6
7
  REQUIRED_MILESTONE,
7
8
  closeBrowser,
@@ -16,7 +17,7 @@ import {
16
17
  resolveChromiumExecutable,
17
18
  run,
18
19
  spawnCapture
19
- } from "./index-mvehxh0e.js";
20
+ } from "./index-c2b6gyeg.js";
20
21
  import {
21
22
  gitDirFor,
22
23
  gitScopeFor,
@@ -731,6 +732,9 @@ function warn(env, line, type, message, marker) {
731
732
  env.layoutWarnings = [];
732
733
  env.layoutWarnings.push({ line, type, message, marker });
733
734
  }
735
+ function proseEscapeHint(kind) {
736
+ return ` If this line is prose that happens to begin with "@${kind}" — a wrapped sentence, for` + ` instance — escape it as \\@${kind} or write it as \`@${kind}\` in backticks, and it stays text.`;
737
+ }
734
738
  function addClasses(token, baseClass, extraClass) {
735
739
  const cls = [];
736
740
  if (baseClass)
@@ -792,14 +796,14 @@ function plugin(md, pluginOptions = {}) {
792
796
  const unknown = parsed.__unknownTokens;
793
797
  delete parsed.__unknownTokens;
794
798
  for (const t of unknown) {
795
- warn(state.env, startLine + 1, "unrecognized_marker_token", `@${parsed.kind}: "${t}" is not something a marker understands, so it was kept verbatim as a class name. Write a class as .my-class (or {.my-class}), an id as #my-id, and anything else as key=value.`, parsed);
799
+ warn(state.env, startLine + 1, "unrecognized_marker_token", `@${parsed.kind}: "${t}" is not something a marker understands, so it was kept verbatim as a class name. Write a class as .my-class (or {.my-class}), an id as #my-id, and anything else as key=value.` + proseEscapeHint(parsed.kind), parsed);
796
800
  }
797
801
  }
798
802
  if (parsed.__extraBareTokens) {
799
803
  const { tokens, named } = parsed.__extraBareTokens;
800
804
  delete parsed.__extraBareTokens;
801
805
  const list = tokens.map((t) => `"${t}"`).join(", ");
802
- warn(state.env, startLine + 1, "extra_bare_marker_token", named ? `@${parsed.kind}: only the first plain word (${list.split(", ")[0]}) is used as the name; the rest (${tokens.slice(1).map((t) => `"${t}"`).join(", ")}) became class names instead. Quote a name that contains spaces ("${tokens.join(" ")}"), or write classes as .${tokens.slice(1).join(" .")}.` : `@${parsed.kind}: ${list} are several plain words, but a marker has only one name slot — so NONE of them was used as the name and they all became class names instead. Quote a name that contains spaces ("${tokens.join(" ")}"), or write classes as .${tokens.join(" .")}.`, parsed);
806
+ warn(state.env, startLine + 1, "extra_bare_marker_token", named ? `@${parsed.kind}: only the first plain word (${list.split(", ")[0]}) is used as the name; the rest (${tokens.slice(1).map((t) => `"${t}"`).join(", ")}) became class names instead. Quote a name that contains spaces ("${tokens.join(" ")}"), or write classes as .${tokens.slice(1).join(" .")}.` : `@${parsed.kind}: ${list} are several plain words, but a marker has only one name slot — so NONE of them was used as the name and they all became class names instead. Quote a name that contains spaces ("${tokens.join(" ")}"), or write classes as .${tokens.join(" .")}.` + proseEscapeHint(parsed.kind), parsed);
803
807
  }
804
808
  state.line = startLine + 1;
805
809
  return true;
@@ -891,7 +895,12 @@ function plugin(md, pluginOptions = {}) {
891
895
  t.meta = { line: meta.__line };
892
896
  addClasses(t, "spread", meta.attrs && meta.attrs.class ? meta.attrs.class : "");
893
897
  attachDataAttrs(t, "spread", meta.name, meta.attrs || {});
894
- stack.open({ kind: "spread", noPagesYet: true, sawAnyPage: false }, t);
898
+ stack.open({
899
+ kind: "spread",
900
+ classes: meta.attrs && meta.attrs.class || "",
901
+ noPagesYet: true,
902
+ sawAnyPage: false
903
+ }, t);
895
904
  }
896
905
  function openPage(meta) {
897
906
  const chapter = stack.get("chapter");
@@ -906,7 +915,7 @@ function plugin(md, pluginOptions = {}) {
906
915
  const label = chapter ? chapter.label : "";
907
916
  if (label)
908
917
  t.attrSet("data-chapter-label", label);
909
- stack.open({ kind: "page" }, t);
918
+ stack.open({ kind: "page", classes: explicit }, t);
910
919
  if (label && !chapter.openerEmitted) {
911
920
  const opener = new state.Token("html_block", "", 0);
912
921
  opener.content = `<div class="chapter-opener" data-chapter-label="${escapeAttr(label)}">${escapeHtml(label)}</div>
@@ -929,13 +938,51 @@ function plugin(md, pluginOptions = {}) {
929
938
  t.meta = { hasColumnBreak: false, line: meta.__line };
930
939
  stack.open({
931
940
  kind: "section",
941
+ classes: meta.attrs && meta.attrs.class || "",
942
+ sawContent: false,
932
943
  meta: { name: meta.name || null, attrs: { ...meta.attrs || {} } },
933
944
  openToken: t
934
945
  }, t);
935
946
  }
947
+ function breakHost() {
948
+ return stack.get("section") || stack.get("page") || stack.get("spread");
949
+ }
950
+ function gridClassOf(frame) {
951
+ if (!frame || !frame.classes)
952
+ return null;
953
+ return frame.classes.split(/\s+/).find((c) => c.startsWith("gp-grid-")) || null;
954
+ }
955
+ function warnBreakInsideGrid(kind, line, meta) {
956
+ const host = breakHost();
957
+ const gridClass = gridClassOf(host);
958
+ if (!gridClass)
959
+ return;
960
+ 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);
961
+ }
962
+ function warnIfEmptyDecoratedSection(closingKind, closingLine) {
963
+ const sec = stack.get("section");
964
+ if (!sec || sec.sawContent)
965
+ return;
966
+ const attrs = sec.meta && sec.meta.attrs || {};
967
+ const decorations = [];
968
+ for (const c of (attrs.class || "").split(/\s+/).filter(Boolean))
969
+ decorations.push(`.${c}`);
970
+ for (const [k, v] of Object.entries(attrs)) {
971
+ if (k === "class")
972
+ continue;
973
+ decorations.push(k === "id" ? `#${v}` : `${k}=${v}`);
974
+ }
975
+ if (!decorations.length)
976
+ return;
977
+ const openLine = sec.openToken && sec.openToken.meta && Number.isFinite(sec.openToken.meta.line) ? sec.openToken.meta.line : closingLine;
978
+ warn(state.env, openLine, "empty_section", `This @section (${decorations.join(" ")}) was closed by the @${closingKind} on line ${closingLine} with no content between the two markers, so its styling applies to an empty element and nothing prints the layout it asked for. Delete one of the two markers, or move the content that belongs inside the section between them.`, null);
979
+ }
936
980
  for (let i = 0;i < state.tokens.length; i++) {
937
981
  const tok = state.tokens[i];
938
982
  if (tok.type !== "layout_marker") {
983
+ const openSectionFrame = stack.get("section");
984
+ if (openSectionFrame)
985
+ openSectionFrame.sawContent = true;
939
986
  out.push(tok);
940
987
  continue;
941
988
  }
@@ -961,6 +1008,7 @@ function plugin(md, pluginOptions = {}) {
961
1008
  continue;
962
1009
  }
963
1010
  if (kind === "section") {
1011
+ warnIfEmptyDecoratedSection("section", line);
964
1012
  stack.close("section");
965
1013
  const spread = stack.get("spread");
966
1014
  if (spread && spread.noPagesYet) {
@@ -989,6 +1037,7 @@ function plugin(md, pluginOptions = {}) {
989
1037
  continue;
990
1038
  }
991
1039
  if (kind === "page-break") {
1040
+ warnBreakInsideGrid("page-break", line, meta);
992
1041
  const t = new state.Token("layout_page_break", "div", 0);
993
1042
  t.meta = { line };
994
1043
  t.attrSet("class", "gp-page-break");
@@ -997,6 +1046,7 @@ function plugin(md, pluginOptions = {}) {
997
1046
  continue;
998
1047
  }
999
1048
  if (kind === "column-break") {
1049
+ warnBreakInsideGrid("column-break", line, meta);
1000
1050
  const openSectionFrame = stack.get("section");
1001
1051
  if (openSectionFrame && openSectionFrame.openToken && !openSectionFrame.openToken.meta.hasColumnBreak) {
1002
1052
  const sectionCls = openSectionFrame.openToken.attrGet("class") || "";
@@ -1012,6 +1062,7 @@ function plugin(md, pluginOptions = {}) {
1012
1062
  continue;
1013
1063
  }
1014
1064
  if (kind === "end-section") {
1065
+ warnIfEmptyDecoratedSection("end-section", line);
1015
1066
  stack.close("section");
1016
1067
  continue;
1017
1068
  }
@@ -1099,7 +1150,7 @@ body { margin: 0; }
1099
1150
  .gp-page-break { break-before: page; }
1100
1151
  .page { break-before: page; }
1101
1152
  .spread { break-before: page; }
1102
- :where(.page, .spread) { position: relative; }
1153
+ :where(.page, .spread) { position: relative; display: flow-root; box-sizing: border-box; min-height: calc(var(--gp-content-h, 1px) - 1px); }
1103
1154
  .gp-column-break { break-after: column; height: 0; font-size: 0; line-height: 0; visibility: hidden; }
1104
1155
 
1105
1156
  :where(h1,h2,h3,h4,h5,h6) { break-after: avoid; }
@@ -1183,6 +1234,26 @@ var GUTTERPRESS_CSS = `
1183
1234
  .gp-columns-2 { columns: 2; column-gap: var(--gp-column-gap, 1.5em); }
1184
1235
  .gp-columns-3 { columns: 3; column-gap: var(--gp-column-gap, 1.5em); }
1185
1236
 
1237
+ /* grid runs — the SLOTTED counterpart to the column runs above. Grid places
1238
+ each child into the next cell, across then down (deterministic slots: card
1239
+ layouts, stat blocks, image-plus-caption pairs); columns FLOW one text run
1240
+ down then across. Same neutral-primitive rationale as .gp-columns-*, and
1241
+ permanent vocabulary for the same reason: standard CSS Grid verbatim, no
1242
+ spec gap to remove later. MEASURED (Chromium 151, gp-grid evidence pack):
1243
+ grid rows fragment across sheets with EXACT print/viewer parity — 2- and
1244
+ 3-col, unequal item heights, mid-row cuts, multi-sheet overflow,
1245
+ break-inside:avoid, gap geometry — so a grid taller than the page is safe,
1246
+ no fit-one-page constraint. Two things to know, not fix:
1247
+ - on a min-height page root (MARKER_CSS), default align-content
1248
+ stretches rows apart to fill the page — identically in both engines.
1249
+ Authors wanting packed rows set align-content: start.
1250
+ - a @page-break / @column-break marker DIRECTLY inside a grid container
1251
+ becomes a grid item and corrupts placement (the one measured parity
1252
+ break); markers.js diagnoses it (break_inside_grid).
1253
+ --gp-grid-gap is author-settable. */
1254
+ .gp-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gp-grid-gap, 1.5em); }
1255
+ .gp-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gp-grid-gap, 1.5em); }
1256
+
1186
1257
  /* shape wrap — text follows the image's alpha silhouette instead of its
1187
1258
  rectangular box. shape-outside only applies to floats, so this is inert
1188
1259
  without .gp-left/.gp-right (and under .gp-pin, which un-floats). The
@@ -1245,8 +1316,14 @@ img.gp-shape {
1245
1316
  opacity, filter, transform on it traps the negative layer inside).
1246
1317
  Core keeps .page/.spread at 'position: relative; z-index: auto'
1247
1318
  precisely so they are not stacking contexts.
1248
- - a clipping ancestor (overflow other than visible), the same mechanism
1249
- that clips a .gp-bleed plate back to the wrapper's width.
1319
+ - a clipping ancestor (overflow other than visible) — but only where
1320
+ the art actually overhangs that ancestor's clip box on a clipped
1321
+ axis: the overhang is cut off, the same mechanism that clips a
1322
+ .gp-bleed plate back to the wrapper's width. Clipping never reorders
1323
+ layers — within-bounds art under a clipping .page prints whole and
1324
+ still behind (measured; see the build audit's comment in
1325
+ engine/compiler/build.ts), and a static wrapper's overflow never
1326
+ binds an abspos .gp-pin at all.
1250
1327
  The build-time engine.layer.trapped audit reports both against the live
1251
1328
  ancestor chain. printsafe/page-containment is only an early source hint for
1252
1329
  declarations written directly on .page/.spread. */
@@ -3873,7 +3950,7 @@ import git from "isomorphic-git";
3873
3950
  // package.json
3874
3951
  var package_default = {
3875
3952
  name: "gutterpress",
3876
- version: "0.10.0-alpha.3",
3953
+ version: "0.10.0-beta.1",
3877
3954
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
3878
3955
  author: "itlackey",
3879
3956
  license: "MPL-2.0",
@@ -4325,17 +4402,6 @@ import { readFile as readFile8 } from "node:fs/promises";
4325
4402
 
4326
4403
  // src/lib/printsafe.ts
4327
4404
  import postcss2 from "postcss";
4328
-
4329
- // src/engine/shared/margin-box-support.ts
4330
- var MARGIN_BOX_IGNORED_PROPERTIES = new Set([
4331
- "transform",
4332
- "rotate",
4333
- "translate",
4334
- "scale",
4335
- "box-shadow"
4336
- ]);
4337
-
4338
- // src/lib/printsafe.ts
4339
4405
  var ruleRemoteUrls = "printsafe/no-remote-urls";
4340
4406
  var ruleRiskyProps = "printsafe/no-risky-print-effects";
4341
4407
  var ruleSyntax = "printsafe/syntax-error";
@@ -7827,11 +7893,12 @@ class PdfOutput {
7827
7893
  const rawPdf = pdfxMode ? path7.join(stage, "raw.pdf") : path7.resolve(pdfFile);
7828
7894
  await fsp2.mkdir(path7.dirname(path7.resolve(pdfFile)), { recursive: true });
7829
7895
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
7830
- const { buildNativePdf } = await import("./engine-q5skhgms.js");
7896
+ const { buildNativePdf } = await import("./engine-nbfdy0pp.js");
7831
7897
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
7832
7898
  title: config.title,
7833
7899
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
7834
- signature: config.print.signature
7900
+ signature: config.print.signature,
7901
+ allowShrink: opts.allowShrink
7835
7902
  }, opts.engineBrowser);
7836
7903
  for (const d of engineDiagnostics)
7837
7904
  log.warn(d.message);
@@ -596,7 +596,7 @@ var favicon_default = "./favicon-wkbm9cjn.ico";
596
596
  var manifest_schema_default = "./manifest.schema-zxgxnbg7.json";
597
597
 
598
598
  // src/assets/preview/scripts/preview-interface.js
599
- var preview_interface_default = "./preview-interface-fnqb2y4v.js";
599
+ var preview_interface_default = "./preview-interface-hg5eqv88.js";
600
600
 
601
601
  // src/assets/preview/scripts/preview-bridge.js
602
602
  var preview_bridge_default = "./preview-bridge-fz7vpk8m.js";
@@ -608,10 +608,10 @@ var preview_shell_default = "./preview-shell-c5mfa3q0.js";
608
608
  var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
609
609
 
610
610
  // src/assets/engine/gutterpress-viewer.js
611
- var gutterpress_viewer_default = "./gutterpress-viewer-cem7dmr5.js";
611
+ var gutterpress_viewer_default = "./gutterpress-viewer-fb6fhwsy.js";
612
612
 
613
613
  // src/assets/engine/gutterpress-agent.js
614
- var gutterpress_agent_default = "./gutterpress-agent-1ctgfz92.js";
614
+ var gutterpress_agent_default = "./gutterpress-agent-cazqstr1.js";
615
615
 
616
616
  // src/assets/templates/book/manifest.yaml
617
617
  var manifest_default = "./manifest-n1gh3qw5.yaml";
@@ -705,4 +705,47 @@ async function getAssetPath(relPath) {
705
705
  return join3(root, relPath);
706
706
  }
707
707
 
708
- export { EXIT_CODES, BuildError, run, spawnCapture, execCapture, isToolAvailable, findTool, INSTALL_HINTS, fullInstallHint, resolveChromiumExecutable, requireChromiumExecutable, RENDER_TIMEOUT_MS, prewarmBrowser, getBrowser, closeBrowser, getAssetPath, REQUIRED_MILESTONE, assertMilestone, readyProbeExpr, DEFAULT_PRINT_OPTS, launchChromium, connectChromium };
708
+ // src/engine/shared/margin-box-support.ts
709
+ var MARGIN_BOX_IGNORED_PROPERTIES = new Set([
710
+ "transform",
711
+ "rotate",
712
+ "translate",
713
+ "scale",
714
+ "box-shadow"
715
+ ]);
716
+ function isIgnoredMarginBoxProperty(property) {
717
+ return MARGIN_BOX_IGNORED_PROPERTIES.has(property.toLowerCase());
718
+ }
719
+ function marginBoxRectPt(name, g) {
720
+ const { top, right, bottom, left } = g.margin;
721
+ const cw = g.width - left - right;
722
+ const ch = g.height - top - bottom;
723
+ const third = (n) => n / 3;
724
+ const T = {
725
+ "top-left-corner": [0, 0, left, top],
726
+ "top-left": [left, 0, third(cw), top],
727
+ "top-center": [left + third(cw), 0, third(cw), top],
728
+ "top-right": [left + 2 * third(cw), 0, third(cw), top],
729
+ "top-right-corner": [g.width - right, 0, right, top],
730
+ "bottom-left-corner": [0, g.height - bottom, left, bottom],
731
+ "bottom-left": [left, g.height - bottom, third(cw), bottom],
732
+ "bottom-center": [left + third(cw), g.height - bottom, third(cw), bottom],
733
+ "bottom-right": [left + 2 * third(cw), g.height - bottom, third(cw), bottom],
734
+ "bottom-right-corner": [g.width - right, g.height - bottom, right, bottom],
735
+ "left-top": [0, top, left, third(ch)],
736
+ "left-middle": [0, top + third(ch), left, third(ch)],
737
+ "left-bottom": [0, top + 2 * third(ch), left, third(ch)],
738
+ "right-top": [g.width - right, top, right, third(ch)],
739
+ "right-middle": [g.width - right, top + third(ch), right, third(ch)],
740
+ "right-bottom": [g.width - right, top + 2 * third(ch), right, third(ch)]
741
+ };
742
+ const [x, y, w, h] = T[name] ?? [0, 0, 0, 0];
743
+ return { x, y, w, h };
744
+ }
745
+ function marginBoxAlign(name) {
746
+ if (name.includes("center") || name.includes("middle"))
747
+ return "center";
748
+ return /right/.test(name) ? "end" : "start";
749
+ }
750
+
751
+ export { EXIT_CODES, BuildError, run, spawnCapture, execCapture, isToolAvailable, findTool, INSTALL_HINTS, fullInstallHint, resolveChromiumExecutable, requireChromiumExecutable, RENDER_TIMEOUT_MS, prewarmBrowser, getBrowser, closeBrowser, getAssetPath, MARGIN_BOX_IGNORED_PROPERTIES, isIgnoredMarginBoxProperty, marginBoxRectPt, marginBoxAlign, REQUIRED_MILESTONE, assertMilestone, readyProbeExpr, DEFAULT_PRINT_OPTS, launchChromium, connectChromium };