gutterpress 0.11.18-alpha.5 → 0.11.18-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.
package/dist/api/index.js CHANGED
@@ -136,11 +136,11 @@ import {
136
136
  testRemoteAccess,
137
137
  updateExtensions,
138
138
  validateProjectExtensions
139
- } from "../index-r2ry6j3x.js";
139
+ } from "../index-bf051pv9.js";
140
140
  import"../index-ht1bcqdd.js";
141
141
  import {
142
142
  BuildError
143
- } from "../index-pb4fj8m2.js";
143
+ } from "../index-jt552wtf.js";
144
144
  import {
145
145
  DEFAULT_PRINT_OPTS,
146
146
  assertMilestone,
@@ -1,19 +1,19 @@
1
1
  import {
2
2
  runBuild,
3
3
  splitOutPath
4
- } from "./cli-dxs69beq.js";
4
+ } from "./cli-mwg13jsy.js";
5
5
  import {
6
6
  log
7
- } from "./cli-bx24fj78.js";
7
+ } from "./cli-8hr9cs2d.js";
8
8
  import {
9
9
  UsageError,
10
10
  parseFormat,
11
11
  parsePdfxFlavor,
12
12
  rejectExtraPositionals,
13
13
  rejectUnknownFlags
14
- } from "./cli-q90qmcvp.js";
14
+ } from "./cli-tgfkz5p1.js";
15
15
  import"./cli-ht1bcqdd.js";
16
- import"./cli-dm3pa0cw.js";
16
+ import"./cli-ptez7zcc.js";
17
17
  import {
18
18
  BuildError
19
19
  } from "./cli-46ycxe6r.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "./cli-q90qmcvp.js";
3
+ } from "./cli-tgfkz5p1.js";
4
4
  import {
5
5
  execCapture,
6
6
  findTool,
@@ -13,12 +13,12 @@ import {
13
13
  stampCreator,
14
14
  stripAnnotations,
15
15
  warn
16
- } from "./cli-bx24fj78.js";
16
+ } from "./cli-8hr9cs2d.js";
17
17
  import {
18
18
  DEBOUNCE,
19
19
  UsageError,
20
20
  resolvePort
21
- } from "./cli-q90qmcvp.js";
21
+ } from "./cli-tgfkz5p1.js";
22
22
  import {
23
23
  blackTextToGray
24
24
  } from "./cli-ht1bcqdd.js";
@@ -27,7 +27,7 @@ import {
27
27
  getAssetPath,
28
28
  getAssetsDir,
29
29
  lastPageMode
30
- } from "./cli-dm3pa0cw.js";
30
+ } from "./cli-ptez7zcc.js";
31
31
  import {
32
32
  BuildError,
33
33
  EXIT_CODES
@@ -6351,7 +6351,6 @@ var ruleRiskyProps = "printsafe/no-risky-print-effects";
6351
6351
  var ruleSyntax = "printsafe/syntax-error";
6352
6352
  var rulePageContainment = "printsafe/page-containment";
6353
6353
  var rulePreviewCannotFollow = "printsafe/preview-cannot-follow";
6354
- var ruleEngineHyphenation = "printsafe/engine-hyphenation";
6355
6354
  function extractUrls(value) {
6356
6355
  const urls = [];
6357
6356
  const re = /url\(\s*(['"]?)(.*?)\1\s*\)/gi;
@@ -6553,10 +6552,6 @@ function previewBlindSpot(decl) {
6553
6552
  }
6554
6553
  return null;
6555
6554
  }
6556
- function isAutoHyphenation(decl) {
6557
- const prop = decl.prop.toLowerCase();
6558
- return (prop === "hyphens" || prop === "-webkit-hyphens") && decl.value.trim().toLowerCase() === "auto";
6559
- }
6560
6555
  function nodeLoc(node) {
6561
6556
  return {
6562
6557
  line: node.source?.start?.line ?? 1,
@@ -6675,16 +6670,6 @@ function checkCssRaw(css, from) {
6675
6670
  warnings.push({ rule: rulePreviewCannotFollow, severity: "warning", message, ...nodeLoc(decl) });
6676
6671
  }
6677
6672
  });
6678
- root.walkDecls((decl) => {
6679
- if (!isAutoHyphenation(decl))
6680
- return;
6681
- warnings.push({
6682
- rule: ruleEngineHyphenation,
6683
- severity: "warning",
6684
- message: `"${decl.prop}: auto" depends on the browser's hyphenation dictionaries, so the desktop app and the CLI (and different operating systems) can break lines and pages differently. Use "hyphens: manual" with soft hyphens (­) for identical pages everywhere.`,
6685
- ...nodeLoc(decl)
6686
- });
6687
- });
6688
6673
  root.walkRules((rule) => {
6689
6674
  if (rule.parent?.type === "atrule") {
6690
6675
  const at = rule.parent.name.toLowerCase();
@@ -10097,7 +10082,7 @@ class PdfOutput {
10097
10082
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10098
10083
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10099
10084
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10100
- const { buildNativePdf } = await import("./engine-vvtkew4q.js");
10085
+ const { buildNativePdf } = await import("./engine-wtnakejg.js");
10101
10086
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10102
10087
  title: config.title,
10103
10088
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -1116,7 +1116,7 @@ import { fileURLToPath } from "node:url";
1116
1116
  var favicon_default = "./favicon-272ga6y0.ico";
1117
1117
 
1118
1118
  // src/assets/manifest.schema.json
1119
- var manifest_schema_default = "./manifest.schema-nc30dyfs.json";
1119
+ var manifest_schema_default = "./manifest.schema-5pzhscqh.json";
1120
1120
 
1121
1121
  // src/assets/preview/scripts/preview-interface.js
1122
1122
  var preview_interface_default = "./preview-interface-2n1d9pbf.js";
@@ -1209,7 +1209,7 @@ var page_rules_default = "./page-rules-nsj3aw42.css";
1209
1209
  var book_default = "./book-ted3tzrp.css";
1210
1210
 
1211
1211
  // src/assets/themes/clean-book/theme.css
1212
- var theme_default = "./theme-5729pyq1.css";
1212
+ var theme_default = "./theme-j2bagrfx.css";
1213
1213
 
1214
1214
  // src/assets/themes/clean-book/package.json
1215
1215
  var package_default3 = "./package-97gm31sg.json";
@@ -2230,6 +2230,54 @@ ${mapCss}`;
2230
2230
  if (multicol.length)
2231
2231
  log(`audit: ${multicol.length} fragmenting multicol warning(s)`);
2232
2232
  }
2233
+ {
2234
+ const missing = JSON.parse(await page.evaluate(`(() => {
2235
+ const words = new Map();
2236
+ const styled = new Map();
2237
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
2238
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
2239
+ const found = n.data.match(/\\p{L}{7,}/gu);
2240
+ const el = n.parentElement;
2241
+ if (!found || !el) continue;
2242
+ if (!styled.has(el)) styled.set(el, getComputedStyle(el).hyphens === "auto");
2243
+ if (!styled.get(el)) continue;
2244
+ const host = el.closest("[lang]");
2245
+ const lang = host ? host.getAttribute("lang") : null;
2246
+ if (!words.has(lang)) words.set(lang, new Set());
2247
+ const set = words.get(lang);
2248
+ for (const w of found) if (set.size < 200) set.add(w);
2249
+ }
2250
+ if (!words.size) return "[]";
2251
+ const box = document.createElement("div");
2252
+ box.style.cssText = "position:absolute;left:-10000px;top:0;visibility:hidden";
2253
+ const probes = [];
2254
+ for (const [lang, set] of words) {
2255
+ for (const w of [...set].sort((a, b) => b.length - a.length).slice(0, 8)) {
2256
+ const pair = ["auto", "manual"].map((h) => {
2257
+ const d = document.createElement("div");
2258
+ if (lang !== null) d.setAttribute("lang", lang);
2259
+ d.style.cssText = "width:1px;font:16px/16px serif;word-break:normal;overflow-wrap:normal;hyphens:" + h;
2260
+ d.textContent = w;
2261
+ box.appendChild(d);
2262
+ return d;
2263
+ });
2264
+ probes.push({ lang, pair });
2265
+ }
2266
+ }
2267
+ document.body.appendChild(box);
2268
+ const hyphenates = new Set();
2269
+ for (const { lang, pair } of probes)
2270
+ if (pair[0].offsetHeight > pair[1].offsetHeight) hyphenates.add(lang);
2271
+ box.remove();
2272
+ const ua = navigator.userAgent;
2273
+ const browser = (/Electron\\/[\\d.]+/.exec(ua) || /(?:HeadlessChrome|Chrome)\\/[\\d.]+/.exec(ua) || [ua])[0];
2274
+ return JSON.stringify([...words.keys()].filter((l) => !hyphenates.has(l)).map((lang) => ({ lang, browser })));
2275
+ })()`));
2276
+ for (const { lang, browser: browser2 } of missing)
2277
+ diagnose("engine.hyphenation.unsupported", `Your stylesheets set hyphens: auto, but the browser making this PDF (${browser2}) has no hyphenation dictionary for ${lang ? `"${lang}"` : "the document's language"}, so no word is hyphenated automatically and lines (and pages) break differently than in a browser that has one. Build in a browser that hyphenates, such as Chrome (which the gutterpress CLI uses), or add soft hyphens (&shy;) where a word may break.`);
2278
+ if (missing.length)
2279
+ log(`audit: hyphenation unavailable for ${missing.length} language(s)`);
2280
+ }
2233
2281
  if (bytes === undefined) {
2234
2282
  prints++;
2235
2283
  bytes = await printPdf(page);
@@ -4,7 +4,7 @@ import {
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "gutterpress",
7
- version: "0.11.18-alpha.5",
7
+ version: "0.11.18-alpha.6",
8
8
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
9
9
  author: "itlackey",
10
10
  license: "MPL-2.0",
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  UsageError,
7
7
  package_default,
8
8
  rejectUnknownFlags
9
- } from "./cli-q90qmcvp.js";
9
+ } from "./cli-tgfkz5p1.js";
10
10
  import {
11
11
  EXIT_CODES
12
12
  } from "./cli-46ycxe6r.js";
@@ -18,14 +18,14 @@ import {
18
18
  import { defineCommand, parseArgs, runMain } from "citty";
19
19
  import { statSync } from "node:fs";
20
20
  var SUBCOMMANDS = {
21
- new: () => import("./new-zrvm9vpe.js").then((m) => m.default),
22
- preview: () => import("./preview-xd02xc2h.js").then((m) => m.default),
23
- build: () => import("./build-hc743bws.js").then((m) => m.default),
24
- publish: () => import("./publish-1yzay4z4.js").then((m) => m.default),
25
- validate: () => import("./validate-gmma4d2t.js").then((m) => m.default),
26
- preflight: () => import("./preflight-h003cdh9.js").then((m) => m.default),
27
- doctor: () => import("./doctor-vq5xetqk.js").then((m) => m.default),
28
- ext: () => import("./ext-zspsqk4y.js").then((m) => m.default)
21
+ new: () => import("./new-x1p4agff.js").then((m) => m.default),
22
+ preview: () => import("./preview-46pvad9t.js").then((m) => m.default),
23
+ build: () => import("./build-0ggjr8b2.js").then((m) => m.default),
24
+ publish: () => import("./publish-zm4tp6ms.js").then((m) => m.default),
25
+ validate: () => import("./validate-vrqdx7xb.js").then((m) => m.default),
26
+ preflight: () => import("./preflight-q75rr1pz.js").then((m) => m.default),
27
+ doctor: () => import("./doctor-7eeh2933.js").then((m) => m.default),
28
+ ext: () => import("./ext-rnvgxe91.js").then((m) => m.default)
29
29
  };
30
30
  var VERSION = package_default.version;
31
31
  var main = defineCommand({
@@ -50,7 +50,7 @@ async function preflightRequiredInvocations(rawArgs) {
50
50
  const [command, ...commandArgs] = rawArgs;
51
51
  try {
52
52
  if (command === "new") {
53
- const { newArgs } = await import("./new-zrvm9vpe.js");
53
+ const { newArgs } = await import("./new-x1p4agff.js");
54
54
  rejectUnknownFlags(commandArgs, newArgs, "new");
55
55
  const parsed = parseArgs(commandArgs, {
56
56
  ...newArgs,
@@ -62,7 +62,7 @@ async function preflightRequiredInvocations(rawArgs) {
62
62
  return;
63
63
  }
64
64
  if (command === "preflight") {
65
- const { preflightArgs } = await import("./preflight-h003cdh9.js");
65
+ const { preflightArgs } = await import("./preflight-q75rr1pz.js");
66
66
  rejectUnknownFlags(commandArgs, preflightArgs, "preflight");
67
67
  const parsed = parseArgs(commandArgs, {
68
68
  ...preflightArgs,
@@ -82,7 +82,7 @@ async function preflightRequiredInvocations(rawArgs) {
82
82
  }
83
83
  if (subcommand.startsWith("-"))
84
84
  return;
85
- const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-zspsqk4y.js");
85
+ const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-rnvgxe91.js");
86
86
  if (!EXT_SUBCOMMANDS.includes(subcommand)) {
87
87
  throw new UsageError(`gutterpress ext: unknown command "${subcommand}"`);
88
88
  }
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  getSystemDiagnostics,
3
3
  log
4
- } from "./cli-bx24fj78.js";
4
+ } from "./cli-8hr9cs2d.js";
5
5
  import {
6
6
  UsageError,
7
7
  rejectExtraPositionals,
8
8
  rejectUnknownFlags
9
- } from "./cli-q90qmcvp.js";
9
+ } from "./cli-tgfkz5p1.js";
10
10
  import"./cli-46ycxe6r.js";
11
11
  import"./cli-7fkhgtfn.js";
12
12
  import"./cli-37x76zdn.js";
@@ -8,7 +8,7 @@ import { type PostprocessResult } from "./postprocess.ts";
8
8
  * set is closed here so a surface's label table can be asserted complete
9
9
  * against it rather than drifting silently as checks are added.
10
10
  */
11
- export type BuildDiagnosticCode = "engine.width.overflow" | "engine.width.intrinsic" | "engine.xref.broken" | "engine.abspos.leak" | "engine.layer.trapped" | "engine.multicol.dead-column" | "engine.content.overheight" | "engine.image.low-dpi" | "engine.flush.margin-box" | "engine.page-background.unreferenced";
11
+ export type BuildDiagnosticCode = "engine.width.overflow" | "engine.width.intrinsic" | "engine.xref.broken" | "engine.abspos.leak" | "engine.layer.trapped" | "engine.multicol.dead-column" | "engine.content.overheight" | "engine.image.low-dpi" | "engine.flush.margin-box" | "engine.page-background.unreferenced" | "engine.hyphenation.unsupported";
12
12
  export declare const BUILD_DIAGNOSTIC_CODES: readonly BuildDiagnosticCode[];
13
13
  export interface BuildDiagnostic {
14
14
  code: BuildDiagnosticCode;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  build
3
- } from "./cli-dm3pa0cw.js";
3
+ } from "./cli-ptez7zcc.js";
4
4
  import {
5
5
  BuildError
6
6
  } from "./cli-46ycxe6r.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  build
3
- } from "./index-89zg3jtk.js";
3
+ } from "./index-n8vs6pss.js";
4
4
  import {
5
5
  BuildError
6
- } from "./index-pb4fj8m2.js";
6
+ } from "./index-jt552wtf.js";
7
7
  import"./index-rzm1phg0.js";
8
8
  import"./index-37x76zdn.js";
9
9
 
@@ -12,17 +12,17 @@ import {
12
12
  searchNpmExtensions,
13
13
  setExtensionEnabled,
14
14
  updateExtensions
15
- } from "./cli-dxs69beq.js";
16
- import"./cli-bx24fj78.js";
15
+ } from "./cli-mwg13jsy.js";
16
+ import"./cli-8hr9cs2d.js";
17
17
  import {
18
18
  UsageError,
19
19
  exitForUsage,
20
20
  rejectExtraPositionals,
21
21
  rejectUnknownFlags,
22
22
  resolveProjectDir
23
- } from "./cli-q90qmcvp.js";
23
+ } from "./cli-tgfkz5p1.js";
24
24
  import"./cli-ht1bcqdd.js";
25
- import"./cli-dm3pa0cw.js";
25
+ import"./cli-ptez7zcc.js";
26
26
  import {
27
27
  EXIT_CODES
28
28
  } from "./cli-46ycxe6r.js";
@@ -9,7 +9,7 @@ import {
9
9
  getAssetsDir,
10
10
  lastPageMode,
11
11
  parseSize
12
- } from "./index-pb4fj8m2.js";
12
+ } from "./index-jt552wtf.js";
13
13
  import {
14
14
  INSTALL_HINTS,
15
15
  execCapture,
@@ -5916,7 +5916,7 @@ import git from "isomorphic-git";
5916
5916
  // package.json
5917
5917
  var package_default = {
5918
5918
  name: "gutterpress",
5919
- version: "0.11.18-alpha.5",
5919
+ version: "0.11.18-alpha.6",
5920
5920
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5921
5921
  author: "itlackey",
5922
5922
  license: "MPL-2.0",
@@ -7787,7 +7787,6 @@ var ruleRiskyProps = "printsafe/no-risky-print-effects";
7787
7787
  var ruleSyntax = "printsafe/syntax-error";
7788
7788
  var rulePageContainment = "printsafe/page-containment";
7789
7789
  var rulePreviewCannotFollow = "printsafe/preview-cannot-follow";
7790
- var ruleEngineHyphenation = "printsafe/engine-hyphenation";
7791
7790
  function extractUrls(value) {
7792
7791
  const urls = [];
7793
7792
  const re = /url\(\s*(['"]?)(.*?)\1\s*\)/gi;
@@ -7989,10 +7988,6 @@ function previewBlindSpot(decl) {
7989
7988
  }
7990
7989
  return null;
7991
7990
  }
7992
- function isAutoHyphenation(decl) {
7993
- const prop = decl.prop.toLowerCase();
7994
- return (prop === "hyphens" || prop === "-webkit-hyphens") && decl.value.trim().toLowerCase() === "auto";
7995
- }
7996
7991
  function nodeLoc(node) {
7997
7992
  return {
7998
7993
  line: node.source?.start?.line ?? 1,
@@ -8111,16 +8106,6 @@ function checkCssRaw(css, from) {
8111
8106
  warnings.push({ rule: rulePreviewCannotFollow, severity: "warning", message, ...nodeLoc(decl) });
8112
8107
  }
8113
8108
  });
8114
- root.walkDecls((decl) => {
8115
- if (!isAutoHyphenation(decl))
8116
- return;
8117
- warnings.push({
8118
- rule: ruleEngineHyphenation,
8119
- severity: "warning",
8120
- message: `"${decl.prop}: auto" depends on the browser's hyphenation dictionaries, so the desktop app and the CLI (and different operating systems) can break lines and pages differently. Use "hyphens: manual" with soft hyphens (&shy;) for identical pages everywhere.`,
8121
- ...nodeLoc(decl)
8122
- });
8123
- });
8124
8109
  root.walkRules((rule) => {
8125
8110
  if (rule.parent?.type === "atrule") {
8126
8111
  const at = rule.parent.name.toLowerCase();
@@ -10677,7 +10662,7 @@ class PdfOutput {
10677
10662
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10678
10663
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10679
10664
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10680
- const { buildNativePdf } = await import("./engine-wjz5yhmp.js");
10665
+ const { buildNativePdf } = await import("./engine-zy6fzs5a.js");
10681
10666
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10682
10667
  title: config.title,
10683
10668
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -148,7 +148,7 @@ import { fileURLToPath } from "node:url";
148
148
  var favicon_default = "./favicon-272ga6y0.ico";
149
149
 
150
150
  // src/assets/manifest.schema.json
151
- var manifest_schema_default = "./manifest.schema-nc30dyfs.json";
151
+ var manifest_schema_default = "./manifest.schema-5pzhscqh.json";
152
152
 
153
153
  // src/assets/preview/scripts/preview-interface.js
154
154
  var preview_interface_default = "./preview-interface-2n1d9pbf.js";
@@ -241,7 +241,7 @@ var page_rules_default = "./page-rules-nsj3aw42.css";
241
241
  var book_default = "./book-ted3tzrp.css";
242
242
 
243
243
  // src/assets/themes/clean-book/theme.css
244
- var theme_default = "./theme-5729pyq1.css";
244
+ var theme_default = "./theme-j2bagrfx.css";
245
245
 
246
246
  // src/assets/themes/clean-book/package.json
247
247
  var package_default3 = "./package-97gm31sg.json";
@@ -19,7 +19,7 @@ import {
19
19
  stringValueAt,
20
20
  toFolioPage,
21
21
  wantsRecto
22
- } from "./index-pb4fj8m2.js";
22
+ } from "./index-jt552wtf.js";
23
23
  import {
24
24
  launchChromium
25
25
  } from "./index-rzm1phg0.js";
@@ -724,7 +724,8 @@ var BUILD_DIAGNOSTIC_CODES = [
724
724
  "engine.content.overheight",
725
725
  "engine.image.low-dpi",
726
726
  "engine.flush.margin-box",
727
- "engine.page-background.unreferenced"
727
+ "engine.page-background.unreferenced",
728
+ "engine.hyphenation.unsupported"
728
729
  ];
729
730
  async function build(opts) {
730
731
  const log = opts.onProgress ?? (() => {});
@@ -1333,6 +1334,54 @@ ${mapCss}`;
1333
1334
  if (multicol.length)
1334
1335
  log(`audit: ${multicol.length} fragmenting multicol warning(s)`);
1335
1336
  }
1337
+ {
1338
+ const missing = JSON.parse(await page.evaluate(`(() => {
1339
+ const words = new Map();
1340
+ const styled = new Map();
1341
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
1342
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
1343
+ const found = n.data.match(/\\p{L}{7,}/gu);
1344
+ const el = n.parentElement;
1345
+ if (!found || !el) continue;
1346
+ if (!styled.has(el)) styled.set(el, getComputedStyle(el).hyphens === "auto");
1347
+ if (!styled.get(el)) continue;
1348
+ const host = el.closest("[lang]");
1349
+ const lang = host ? host.getAttribute("lang") : null;
1350
+ if (!words.has(lang)) words.set(lang, new Set());
1351
+ const set = words.get(lang);
1352
+ for (const w of found) if (set.size < 200) set.add(w);
1353
+ }
1354
+ if (!words.size) return "[]";
1355
+ const box = document.createElement("div");
1356
+ box.style.cssText = "position:absolute;left:-10000px;top:0;visibility:hidden";
1357
+ const probes = [];
1358
+ for (const [lang, set] of words) {
1359
+ for (const w of [...set].sort((a, b) => b.length - a.length).slice(0, 8)) {
1360
+ const pair = ["auto", "manual"].map((h) => {
1361
+ const d = document.createElement("div");
1362
+ if (lang !== null) d.setAttribute("lang", lang);
1363
+ d.style.cssText = "width:1px;font:16px/16px serif;word-break:normal;overflow-wrap:normal;hyphens:" + h;
1364
+ d.textContent = w;
1365
+ box.appendChild(d);
1366
+ return d;
1367
+ });
1368
+ probes.push({ lang, pair });
1369
+ }
1370
+ }
1371
+ document.body.appendChild(box);
1372
+ const hyphenates = new Set();
1373
+ for (const { lang, pair } of probes)
1374
+ if (pair[0].offsetHeight > pair[1].offsetHeight) hyphenates.add(lang);
1375
+ box.remove();
1376
+ const ua = navigator.userAgent;
1377
+ const browser = (/Electron\\/[\\d.]+/.exec(ua) || /(?:HeadlessChrome|Chrome)\\/[\\d.]+/.exec(ua) || [ua])[0];
1378
+ return JSON.stringify([...words.keys()].filter((l) => !hyphenates.has(l)).map((lang) => ({ lang, browser })));
1379
+ })()`));
1380
+ for (const { lang, browser: browser2 } of missing)
1381
+ diagnose("engine.hyphenation.unsupported", `Your stylesheets set hyphens: auto, but the browser making this PDF (${browser2}) has no hyphenation dictionary for ${lang ? `"${lang}"` : "the document's language"}, so no word is hyphenated automatically and lines (and pages) break differently than in a browser that has one. Build in a browser that hyphenates, such as Chrome (which the gutterpress CLI uses), or add soft hyphens (&shy;) where a word may break.`);
1382
+ if (missing.length)
1383
+ log(`audit: hyphenation unavailable for ${missing.length} language(s)`);
1384
+ }
1336
1385
  if (bytes === undefined) {
1337
1386
  prints++;
1338
1387
  bytes = await printPdf(page);
package/dist/index.js CHANGED
@@ -153,14 +153,14 @@ import {
153
153
  testRemoteAccess,
154
154
  updateExtensions,
155
155
  validateProjectExtensions
156
- } from "./index-r2ry6j3x.js";
156
+ } from "./index-bf051pv9.js";
157
157
  import"./index-ht1bcqdd.js";
158
158
  import {
159
159
  BUILD_DIAGNOSTIC_CODES
160
- } from "./index-89zg3jtk.js";
160
+ } from "./index-n8vs6pss.js";
161
161
  import {
162
162
  BuildError
163
- } from "./index-pb4fj8m2.js";
163
+ } from "./index-jt552wtf.js";
164
164
  import {
165
165
  DEFAULT_PRINT_OPTS,
166
166
  assertMilestone,
@@ -3,7 +3,6 @@ export declare const ruleRiskyProps = "printsafe/no-risky-print-effects";
3
3
  export declare const ruleSyntax = "printsafe/syntax-error";
4
4
  export declare const rulePageContainment = "printsafe/page-containment";
5
5
  export declare const rulePreviewCannotFollow = "printsafe/preview-cannot-follow";
6
- export declare const ruleEngineHyphenation = "printsafe/engine-hyphenation";
7
6
  export interface PrintSafeWarning {
8
7
  rule: string;
9
8
  severity: "error" | "warning";
@@ -243,7 +243,7 @@
243
243
  },
244
244
  "rules": {
245
245
  "type": "object",
246
- "description": "Per-rule level overrides, keyed by rule id: a printsafe rule (printsafe/no-risky-print-effects, printsafe/page-containment, printsafe/preview-cannot-follow, printsafe/engine-hyphenation, printsafe/no-remote-urls) or a check id such as source.accessibility.heading-order. \"off\" silences the rule; \"warn\" / \"error\" set its severity. An id that matches no rule is ignored. To silence one site instead, put a `gutterpress-disable-next-line <rule-id> -- reason` comment (CSS `/* */` or markdown `<!-- -->`) on the line above it.",
246
+ "description": "Per-rule level overrides, keyed by rule id: a printsafe rule (printsafe/no-risky-print-effects, printsafe/page-containment, printsafe/preview-cannot-follow, printsafe/no-remote-urls) or a check id such as source.accessibility.heading-order. \"off\" silences the rule; \"warn\" / \"error\" set its severity. An id that matches no rule is ignored. To silence one site instead, put a `gutterpress-disable-next-line <rule-id> -- reason` comment (CSS `/* */` or markdown `<!-- -->`) on the line above it.",
247
247
  "additionalProperties": { "enum": ["off", "warn", "error"] },
248
248
  "examples": [{ "printsafe/no-risky-print-effects": "off" }]
249
249
  }
@@ -7,17 +7,17 @@ import {
7
7
  TARGET_IDS,
8
8
  scaffoldExtension,
9
9
  scaffoldProject
10
- } from "./cli-dxs69beq.js";
10
+ } from "./cli-mwg13jsy.js";
11
11
  import {
12
12
  resolveGhostscript
13
- } from "./cli-bx24fj78.js";
13
+ } from "./cli-8hr9cs2d.js";
14
14
  import {
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-q90qmcvp.js";
18
+ } from "./cli-tgfkz5p1.js";
19
19
  import"./cli-ht1bcqdd.js";
20
- import"./cli-dm3pa0cw.js";
20
+ import"./cli-ptez7zcc.js";
21
21
  import {
22
22
  EXIT_CODES
23
23
  } from "./cli-46ycxe6r.js";
@@ -2,17 +2,17 @@ import {
2
2
  executeValidation,
3
3
  publishTargetFor,
4
4
  reportMissingTools
5
- } from "./cli-dxs69beq.js";
5
+ } from "./cli-mwg13jsy.js";
6
6
  import {
7
7
  log
8
- } from "./cli-bx24fj78.js";
8
+ } from "./cli-8hr9cs2d.js";
9
9
  import {
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-q90qmcvp.js";
13
+ } from "./cli-tgfkz5p1.js";
14
14
  import"./cli-ht1bcqdd.js";
15
- import"./cli-dm3pa0cw.js";
15
+ import"./cli-ptez7zcc.js";
16
16
  import {
17
17
  BuildError,
18
18
  EXIT_CODES
@@ -7,10 +7,10 @@ import {
7
7
  runBuild,
8
8
  splitOutPath,
9
9
  startPreviewServer
10
- } from "./cli-dxs69beq.js";
10
+ } from "./cli-mwg13jsy.js";
11
11
  import {
12
12
  log
13
- } from "./cli-bx24fj78.js";
13
+ } from "./cli-8hr9cs2d.js";
14
14
  import {
15
15
  UsageError,
16
16
  parseFormat,
@@ -18,9 +18,9 @@ import {
18
18
  rejectExtraPositionals,
19
19
  rejectUnknownFlags,
20
20
  resolvePort
21
- } from "./cli-q90qmcvp.js";
21
+ } from "./cli-tgfkz5p1.js";
22
22
  import"./cli-ht1bcqdd.js";
23
- import"./cli-dm3pa0cw.js";
23
+ import"./cli-ptez7zcc.js";
24
24
  import {
25
25
  BuildError
26
26
  } from "./cli-46ycxe6r.js";
@@ -11,18 +11,18 @@ import {
11
11
  publishProviderFor,
12
12
  resolvePublishFormat,
13
13
  runPublish
14
- } from "./cli-dxs69beq.js";
14
+ } from "./cli-mwg13jsy.js";
15
15
  import {
16
16
  FileTokenStore,
17
17
  log
18
- } from "./cli-bx24fj78.js";
18
+ } from "./cli-8hr9cs2d.js";
19
19
  import {
20
20
  UsageError,
21
21
  rejectExtraPositionals,
22
22
  rejectUnknownFlags
23
- } from "./cli-q90qmcvp.js";
23
+ } from "./cli-tgfkz5p1.js";
24
24
  import"./cli-ht1bcqdd.js";
25
- import"./cli-dm3pa0cw.js";
25
+ import"./cli-ptez7zcc.js";
26
26
  import {
27
27
  EXIT_CODES
28
28
  } from "./cli-46ycxe6r.js";
@@ -44,10 +44,7 @@ h1 { font-size: var(--fs-h1); margin: 0 0 0.6em; }
44
44
  h2 { font-size: var(--fs-h2); margin: 1.4em 0 0.4em; }
45
45
  h3 { font-size: var(--fs-h3); margin: 1.2em 0 0.3em; }
46
46
 
47
- /* hyphens: manual, not auto: auto breaks words only where the running Chromium
48
- ships dictionaries (desktop app and CLI differ), which changes the page count.
49
- Authors add soft hyphens (&shy;) where they want a break. */
50
- p { margin: 0 0 0.8em; text-align: justify; hyphens: manual; }
47
+ p { margin: 0 0 0.8em; text-align: justify; hyphens: auto; }
51
48
 
52
49
  a { color: var(--color-accent); text-decoration: none; }
53
50
 
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  executeAndReport
3
- } from "./cli-dxs69beq.js";
3
+ } from "./cli-mwg13jsy.js";
4
4
  import {
5
5
  log
6
- } from "./cli-bx24fj78.js";
6
+ } from "./cli-8hr9cs2d.js";
7
7
  import {
8
8
  UsageError,
9
9
  rejectExtraPositionals,
10
10
  rejectUnknownFlags
11
- } from "./cli-q90qmcvp.js";
11
+ } from "./cli-tgfkz5p1.js";
12
12
  import"./cli-ht1bcqdd.js";
13
- import"./cli-dm3pa0cw.js";
13
+ import"./cli-ptez7zcc.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.18-alpha.5",
3
+ "version": "0.11.18-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",