gutterpress 0.9.0-alpha.2 → 0.10.0-alpha.3

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 (138) hide show
  1. package/README.md +18 -4
  2. package/dist/api/index.d.ts +11 -5
  3. package/dist/api/index.js +20 -15
  4. package/dist/assets/preview/scripts/preview-bridge.d.ts +1 -0
  5. package/dist/assets/preview/scripts/preview-interface.d.ts +1 -0
  6. package/dist/{audit-nhn2pjz3.js → audit-xxegv0t0.js} +10 -7
  7. package/dist/{build-san7fv2z.js → build-w0vb6mgq.js} +14 -8
  8. package/dist/checks/source/index.d.ts +1 -0
  9. package/dist/checks/source/layout-markers.d.ts +3 -0
  10. package/dist/checks/source/local-ref-parser.d.ts +28 -0
  11. package/dist/cli-46ycxe6r.js +18 -0
  12. package/dist/{cli-k4bd06sd.js → cli-5czby0dd.js} +4986 -5889
  13. package/dist/cli-c41yr7he.js +241 -0
  14. package/dist/{cli-e5zhb0xs.js → cli-f4rcbt2t.js} +17 -24
  15. package/dist/{cli-najycadg.js → cli-gk3wsdpb.js} +69 -253
  16. package/dist/{index-yzrh708h.js → cli-k1065rkg.js} +2 -5
  17. package/dist/{cli-506tg37g.js → cli-revgt4pr.js} +2 -1
  18. package/dist/{cli-wchtvxvw.js → cli-v5mp7a6q.js} +14 -1
  19. package/dist/cli-yp3p0sf2.js +2343 -0
  20. package/dist/cli.js +20 -18
  21. package/dist/{doctor-hrk0kxxz.js → doctor-ctgn1dnt.js} +4 -2
  22. package/dist/engine/compiler/build.d.ts +139 -0
  23. package/dist/engine/compiler/postprocess.d.ts +23 -0
  24. package/dist/engine/compiler/tier2.d.ts +78 -0
  25. package/dist/engine/shared/cdp.d.ts +104 -0
  26. package/dist/engine/shared/content-value.d.ts +64 -0
  27. package/dist/engine/shared/gcpm-extract.d.ts +119 -0
  28. package/dist/engine/shared/margin-box-support.d.ts +12 -0
  29. package/dist/engine/shared/pdf-inspect.d.ts +24 -0
  30. package/dist/engine/shared/synthesis.d.ts +155 -0
  31. package/dist/engine-eng4xh2q.js +41 -0
  32. package/dist/engine-q5skhgms.js +40 -0
  33. package/dist/gutterpress-agent-1ctgfz92.js +576 -0
  34. package/dist/gutterpress-viewer-cem7dmr5.js +2349 -0
  35. package/dist/index-a4kr77td.js +1907 -0
  36. package/dist/{cli-yzrh708h.js → index-mdefp0y5.js} +1 -1
  37. package/dist/index-mvehxh0e.js +708 -0
  38. package/dist/{index-bynn850m.js → index-s416qpdv.js} +3049 -4015
  39. package/dist/{index-wchtvxvw.js → index-v5mp7a6q.js} +14 -1
  40. package/dist/index.d.ts +3 -1
  41. package/dist/index.js +24 -17
  42. package/dist/lib/asset-inline.d.ts +36 -0
  43. package/dist/lib/browser-pool.d.ts +18 -0
  44. package/dist/lib/build-error.d.ts +1 -1
  45. package/dist/lib/build-preflight.d.ts +35 -2
  46. package/dist/lib/build-runner.d.ts +45 -17
  47. package/dist/lib/build-staging.d.ts +9 -55
  48. package/dist/lib/cli-args.d.ts +8 -0
  49. package/dist/lib/desktop.d.ts +2 -2
  50. package/dist/lib/embedded-assets.d.ts +1 -1
  51. package/dist/lib/engine.d.ts +33 -0
  52. package/dist/lib/ghostscript.d.ts +46 -1
  53. package/dist/lib/markdown/assemble.d.ts +18 -10
  54. package/dist/lib/markdown/gp-pin-scope.d.ts +1 -0
  55. package/dist/lib/markdown/gutterpress-css.d.ts +125 -0
  56. package/dist/lib/markdown/images.d.ts +26 -0
  57. package/dist/lib/markdown/index.d.ts +4 -2
  58. package/dist/lib/markdown/inline-source.d.ts +10 -0
  59. package/dist/lib/markdown/markers.d.ts +32 -0
  60. package/dist/lib/markdown/renderer.d.ts +8 -3
  61. package/dist/lib/markdown/source-range.d.ts +61 -0
  62. package/dist/lib/missing-asset-placeholder.d.ts +52 -0
  63. package/dist/lib/presets.d.ts +1 -1
  64. package/dist/lib/printsafe.d.ts +2 -3
  65. package/dist/lib/remote-auth/converge-merge.d.ts +48 -0
  66. package/dist/lib/remote-auth/image-clash.d.ts +17 -0
  67. package/dist/lib/remote-auth/recovery/classify.d.ts +55 -67
  68. package/dist/lib/remote-auth/recovery/inspect.d.ts +14 -13
  69. package/dist/lib/remote-auth/recovery/locks.d.ts +20 -0
  70. package/dist/lib/remote-auth/recovery/repair.d.ts +29 -0
  71. package/dist/lib/remote-auth/recovery/types.d.ts +8 -204
  72. package/dist/lib/remote-auth/sync-messages.d.ts +2 -3
  73. package/dist/lib/remote-auth/sync-types.d.ts +35 -63
  74. package/dist/lib/remote-auth/sync.d.ts +11 -18
  75. package/dist/lib/remote-auth/transport.d.ts +14 -7
  76. package/dist/lib/theme-import.d.ts +4 -5
  77. package/dist/{lint-96j9hrj4.js → lint-gsh6qwrq.js} +10 -7
  78. package/dist/{manifest.schema-z61rzw44.json → manifest.schema-zxgxnbg7.json} +21 -0
  79. package/dist/{new-8p38wavc.js → new-nkhycqy4.js} +12 -8
  80. package/dist/{plugin-ees6nhkc.js → plugin-129wcs93.js} +10 -7
  81. package/dist/{preflight-1q6c2edh.js → preflight-41hcejg9.js} +10 -7
  82. package/dist/preview/file-watcher.d.ts +13 -17
  83. package/dist/preview/lifecycle.d.ts +1 -1
  84. package/dist/{pagedjs-bridge-vn4hk9fx.js → preview-bridge-fz7vpk8m.js} +8 -0
  85. package/dist/preview-interface-fnqb2y4v.js +1015 -0
  86. package/dist/{preview-y5a2zen1.js → preview-pftsvr9z.js} +16 -9
  87. package/dist/preview-shell-c5mfa3q0.js +346 -0
  88. package/dist/{project-source-p0gn1wd5.js → project-source-ekcyp63q.js} +1 -1
  89. package/dist/{publish-rm9yb3wh.js → publish-dnjvb4jg.js} +10 -7
  90. package/dist/render.d.ts +3 -4
  91. package/dist/render.js +728 -64
  92. package/dist/{repair-zgq7q2g6.js → repair-a3enjttn.js} +45 -79
  93. package/dist/schema/manifest.types.d.ts +38 -0
  94. package/dist/{source-provider-c1rjm2c0.js → source-provider-3tcj6qg2.js} +2 -2
  95. package/dist/source-provider-vanafrt9.js +40 -0
  96. package/dist/{theme-zz2ktzqs.css → theme-h5recz6c.css} +8 -7
  97. package/dist/{theme-570zmh2t.css → theme-j2bagrfx.css} +8 -7
  98. package/dist/{theme-nya4nqh6.css → theme-nn6d53zy.css} +8 -7
  99. package/dist/types.d.ts +7 -0
  100. package/dist/{validate-nr0xa6sa.js → validate-ph1xffqy.js} +10 -7
  101. package/package.json +6 -6
  102. package/dist/cli-yja077f6.js +0 -92
  103. package/dist/git-http-yrb4ag6z.js +0 -17
  104. package/dist/index-yja077f6.js +0 -92
  105. package/dist/lib/markdown/markdown-it-paged.d.ts +0 -30
  106. package/dist/lib/pagedjs-marker.d.ts +0 -42
  107. package/dist/lib/pagedjs.d.ts +0 -26
  108. package/dist/lib/pagination.d.ts +0 -149
  109. package/dist/lib/remote-auth/conflict-resolution.d.ts +0 -29
  110. package/dist/lib/remote-auth/recovery/abort-interrupted-operation.d.ts +0 -103
  111. package/dist/lib/remote-auth/recovery/backup.d.ts +0 -113
  112. package/dist/lib/remote-auth/recovery/context.d.ts +0 -47
  113. package/dist/lib/remote-auth/recovery/dispatch.d.ts +0 -28
  114. package/dist/lib/remote-auth/recovery/failsafe.d.ts +0 -33
  115. package/dist/lib/remote-auth/recovery/manual-guidance.d.ts +0 -28
  116. package/dist/lib/remote-auth/recovery/outcome-mapping.d.ts +0 -59
  117. package/dist/lib/remote-auth/recovery/policy.d.ts +0 -47
  118. package/dist/lib/remote-auth/recovery/recover-auth.d.ts +0 -42
  119. package/dist/lib/remote-auth/recovery/recover-binary-conflict.d.ts +0 -37
  120. package/dist/lib/remote-auth/recovery/recover-corrupt-index.d.ts +0 -40
  121. package/dist/lib/remote-auth/recovery/recover-detached-head.d.ts +0 -70
  122. package/dist/lib/remote-auth/recovery/recover-interrupted-cherry-pick.d.ts +0 -23
  123. package/dist/lib/remote-auth/recovery/recover-interrupted-merge.d.ts +0 -28
  124. package/dist/lib/remote-auth/recovery/recover-interrupted-rebase.d.ts +0 -39
  125. package/dist/lib/remote-auth/recovery/recover-merge-conflict.d.ts +0 -34
  126. package/dist/lib/remote-auth/recovery/recover-missing-git-dir.d.ts +0 -37
  127. package/dist/lib/remote-auth/recovery/recover-missing-objects.d.ts +0 -56
  128. package/dist/lib/remote-auth/recovery/recover-network.d.ts +0 -34
  129. package/dist/lib/remote-auth/recovery/recover-non-fast-forward.d.ts +0 -27
  130. package/dist/lib/remote-auth/recovery/recover-stale-lock.d.ts +0 -68
  131. package/dist/lib/remote-auth/recovery/recover-unrelated-histories.d.ts +0 -44
  132. package/dist/lib/remote-auth/recovery/recover-wrong-remote.d.ts +0 -35
  133. package/dist/lib/remote-auth/resolution-plan.d.ts +0 -64
  134. package/dist/paged.polyfill-n95pbxfn.js +0 -33288
  135. package/dist/pagedjs-interface-qxvzgwd7.js +0 -557
  136. package/dist/preview-shell-6dqexx1m.js +0 -581
  137. /package/dist/assets/{preview/scripts/pagedjs-bridge.d.ts → engine/gutterpress-agent.d.ts} +0 -0
  138. /package/dist/assets/{preview/scripts/pagedjs-interface.d.ts → engine/gutterpress-viewer.d.ts} +0 -0
@@ -0,0 +1,2349 @@
1
+ (() => {
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+
17
+ // src/engine/viewer/fragment.ts
18
+ var exports_fragment = {};
19
+ __export(exports_fragment, {
20
+ wrapGeometry: () => wrapGeometry,
21
+ waitForLayoutReady: () => waitForLayoutReady,
22
+ synthesizeColumnBreaks: () => synthesizeColumnBreaks,
23
+ stripMetrics: () => stripMetrics,
24
+ strideOf: () => strideOf,
25
+ stabilizeFullHeightPageRoots: () => stabilizeFullHeightPageRoots,
26
+ spreadModeSupported: () => spreadModeSupported,
27
+ rowStrideOf: () => rowStrideOf,
28
+ pageRangeOf: () => pageRangeOf,
29
+ pageOf: () => pageOf,
30
+ measure: () => measure,
31
+ loadStyleSources: () => loadStyleSources,
32
+ injectViewerCss: () => injectViewerCss,
33
+ injectBreakMapping: () => injectBreakMapping,
34
+ fragmentDocument: () => fragmentDocument,
35
+ forcedColumnBreaksSupported: () => forcedColumnBreaksSupported,
36
+ compensateTrailingMarginsBeforeAvoids: () => compensateTrailingMarginsBeforeAvoids,
37
+ compensateRepeatedHeaders: () => compensateRepeatedHeaders,
38
+ compensateRectoBreaks: () => compensateRectoBreaks,
39
+ collectCssText: () => collectCssText,
40
+ buildStrips: () => buildStrips,
41
+ blankPageIndices: () => blankPageIndices,
42
+ applySpreadMode: () => applySpreadMode,
43
+ PX_PER_PT: () => PX_PER_PT
44
+ });
45
+
46
+ // src/engine/viewer/viewer.css
47
+ var viewer_default = `/* Gutterpress viewer chrome. The author's content CSS is untouched; everything here
48
+ is scoped to Gutterpress's own wrappers/layers. */
49
+
50
+ /* The stage backdrop is viewer chrome and must stay chrome: this rule (0-1-0)
51
+ deliberately outranks an author's \`body { background: … }\` (0-0-1). An
52
+ author's canvas background is not lost — \`decorate()\` reads it before this
53
+ class is applied and replays it on every sheet, which is where print puts
54
+ it (see \`captureCanvasBackground\`). */
55
+ .gp-stage {
56
+ --gp-sheet-bg: #fff;
57
+ --gp-stage-bg: #4a4a52;
58
+ --gp-guide: #e5484d;
59
+ --gp-safe: #30a46c;
60
+ background: var(--gp-stage-bg);
61
+ margin: 0;
62
+ padding: 32px;
63
+ overflow: auto;
64
+ /* Standalone pages use \`--gutterpress-fit-zoom\`; embedded previews use the
65
+ host-owned \`--gutterpress-zoom\`. fitZoom() guarantees only one is active. */
66
+ zoom: calc(var(--gutterpress-zoom, 1) * var(--gutterpress-fit-zoom, 1));
67
+ }
68
+
69
+ /* One flow strip per named-page run. Chromium fragments its content into
70
+ columns; each column IS a page's content area. \`width\` is deliberately ONE
71
+ column wide — it sizes the strip's own box to the first page's content
72
+ area, matching where the strip sits inside that sheet (see the transform
73
+ below). \`overflow\` must stay \`visible\`: with a fixed height and no
74
+ \`column-count\`, Chromium creates as many columns as the content needs,
75
+ extending past this box in the inline direction (\`scrollWidth\` — read by
76
+ \`measure()\` — reflects the true extent). \`overflow: hidden\` here would
77
+ clip every column past the first from painting while leaving
78
+ getClientRects()/scrollWidth (and so pageOf()) unaffected, so pages 2+ of
79
+ a run would measure correctly but render blank — the \`.gp-run\` wrapper
80
+ below is already sized to the run's full width and owns the actual
81
+ clipping/visibility, so the strip does not need to. */
82
+ .gp-strip {
83
+ position: relative;
84
+ width: var(--gp-content-w);
85
+ height: var(--gp-content-h);
86
+ column-width: var(--gp-content-w);
87
+ column-gap: calc(
88
+ var(--gp-margin-right) + var(--gp-margin-left) + var(--gp-sheet-gap)
89
+ );
90
+ column-fill: auto;
91
+ overflow: visible;
92
+ margin: 0;
93
+ /* strip sits inside the first sheet's content box */
94
+ transform: translate(var(--gp-margin-left), var(--gp-margin-top));
95
+ }
96
+
97
+ /* Sheets/margin boxes are painted behind/around the strip by the decoration
98
+ layer; the strip itself must stay transparent so they show through. */
99
+ .gp-strip > * {
100
+ break-inside: auto;
101
+ }
102
+
103
+ /* View mode (\`applySpreadMode\` in fragment.ts): wraps this run's multicol
104
+ columns into \`--gp-wrap-cols\` ROWS instead of one long row — 2 for
105
+ two-up/spread, 1 for single (a plain vertical stack of pages), using
106
+ CSS Multicol L2's \`column-wrap: wrap\` + \`column-height\` (shipped unflagged
107
+ Chrome/Edge 145+ — CSS.supports-gated in JS, so this selector only ever
108
+ matches on a browser that has it; the base \`.gp-strip\` rules above are
109
+ the single-row fallback everywhere else). \`row-gap\` mirrors \`column-gap\`
110
+ above: content box height + top/bottom margins + the visual sheet gap, so
111
+ consecutive wrapped rows are spaced exactly one page-pitch apart, matching
112
+ how \`column-gap\` already encodes left/right margins for columns within a
113
+ row. Width reserves the full column count so a lone page (a strip with
114
+ only one fragment) still gets its correct left/right slot in two-up, with
115
+ nothing inserted to fill the other. */
116
+ .gp-strip[data-wrap="on"] {
117
+ column-wrap: wrap;
118
+ column-height: var(--gp-content-h);
119
+ column-count: var(--gp-wrap-cols, 2);
120
+ row-gap: calc(
121
+ var(--gp-margin-top) + var(--gp-margin-bottom) + var(--gp-sheet-gap)
122
+ );
123
+ width: calc(
124
+ var(--gp-content-w) * var(--gp-wrap-cols, 2) +
125
+ (
126
+ var(--gp-margin-right) + var(--gp-margin-left) + var(--gp-sheet-gap)
127
+ ) * (var(--gp-wrap-cols, 2) - 1)
128
+ );
129
+ }
130
+
131
+ /* Cross-run correctness (no padding, no inserted PAGE): a run whose first
132
+ physical page is a RECTO would otherwise fall into multicol's first
133
+ (LEFT) grid slot and get grouped with the verso that follows it — wrong
134
+ side AND wrong pairing (a recto pairs with the verso that PRECEDES it).
135
+ \`fragment.ts\`'s \`applySpreadMode\` inserts this as the strip's first
136
+ child to consume exactly ONE column-flow slot with zero content, pushing
137
+ the run's real content one slot later so it lands correctly — see its
138
+ doc comment for why a same-box CSS-only mirror (e.g. \`direction: rtl\`)
139
+ cannot fix this (it changes placement, not grouping). */
140
+ .gp-wrap-spacer {
141
+ break-after: column;
142
+ height: 0;
143
+ margin: 0;
144
+ padding: 0;
145
+ border: 0;
146
+ }
147
+
148
+ .gp-run {
149
+ position: relative;
150
+ margin: 0 0 var(--gp-sheet-gap);
151
+ }
152
+
153
+ .gp-layer {
154
+ position: absolute;
155
+ inset: 0;
156
+ pointer-events: none;
157
+ }
158
+
159
+ .gp-sheet {
160
+ position: absolute;
161
+ top: 0;
162
+ width: var(--gp-page-w);
163
+ height: var(--gp-page-h);
164
+ background: var(--gp-sheet-bg);
165
+ box-shadow: 0 2px 12px rgb(0 0 0 / 0.35);
166
+ }
167
+
168
+ .gp-marginbox {
169
+ position: absolute;
170
+ display: flex;
171
+ align-items: center;
172
+ overflow: hidden;
173
+ white-space: pre;
174
+ font: inherit;
175
+ color: inherit;
176
+ }
177
+
178
+ .gp-marginbox[data-align="start"] { justify-content: flex-start; }
179
+ .gp-marginbox[data-align="center"] { justify-content: center; }
180
+ .gp-marginbox[data-align="end"] { justify-content: flex-end; }
181
+
182
+ /* The outer .gp-marginbox is the fixed geometric slot; this inner generated
183
+ box receives the author's @page margin-box declarations. It defaults to the
184
+ slot's full dimensions (the \`auto\` margin-box case), while explicit author
185
+ width/height such as \`fit-content\` override these declarations and the
186
+ outer flex alignment keeps the resulting sticker on the correct edge. */
187
+ .gp-marginbox-content {
188
+ box-sizing: border-box;
189
+ display: flex;
190
+ align-items: center;
191
+ width: 100%;
192
+ height: 100%;
193
+ min-width: 0;
194
+ white-space: inherit;
195
+ }
196
+
197
+ .gp-marginbox[data-align="start"] > .gp-marginbox-content { justify-content: flex-start; }
198
+ .gp-marginbox[data-align="center"] > .gp-marginbox-content { justify-content: center; }
199
+ .gp-marginbox[data-align="end"] > .gp-marginbox-content { justify-content: flex-end; }
200
+
201
+ /* designer mode ------------------------------------------------------- */
202
+ .gp-guide-trim,
203
+ .gp-guide-safe,
204
+ .gp-crop-mark {
205
+ position: absolute;
206
+ pointer-events: none;
207
+ display: none;
208
+ }
209
+
210
+ .gp-stage[data-designer="on"] .gp-guide-trim,
211
+ .gp-stage[data-designer="on"] .gp-guide-safe,
212
+ .gp-stage[data-designer="on"] .gp-crop-mark {
213
+ display: block;
214
+ }
215
+
216
+ .gp-guide-trim { outline: 1px dashed var(--gp-guide); }
217
+ .gp-guide-safe { outline: 1px dashed var(--gp-safe); }
218
+ .gp-crop-mark { background: var(--gp-guide); }
219
+
220
+ .gp-warning {
221
+ position: absolute;
222
+ right: 4px;
223
+ top: 4px;
224
+ background: var(--gp-guide);
225
+ color: #fff;
226
+ font: 600 10px/1.4 ui-sans-serif, system-ui, sans-serif;
227
+ padding: 2px 6px;
228
+ border-radius: 3px;
229
+ }
230
+
231
+ /* An accidental Ctrl+P on the published page must not print the dark stage
232
+ plus decorations — the PDF, shipped alongside book.html, is the print
233
+ artifact. This is a minimal reset, not a print layout. */
234
+ @media print {
235
+ .gp-stage {
236
+ background: none;
237
+ padding: 0;
238
+ overflow: visible;
239
+ zoom: 1;
240
+ }
241
+ .gp-layer,
242
+ .gp-warning,
243
+ .gp-guide-trim,
244
+ .gp-guide-safe,
245
+ .gp-crop-mark {
246
+ display: none !important;
247
+ }
248
+ .gp-sheet {
249
+ box-shadow: none;
250
+ }
251
+ }
252
+ `;
253
+
254
+ // src/engine/shared/gcpm-extract.ts
255
+ var exports_gcpm_extract = {};
256
+ __export(exports_gcpm_extract, {
257
+ toPt: () => toPt,
258
+ splitTopLevel: () => splitTopLevel,
259
+ resolvePage: () => resolvePage,
260
+ parseSize: () => parseSize,
261
+ parseMargin: () => parseMargin,
262
+ parseDeclarations: () => parseDeclarations,
263
+ mediaPrintBodies: () => mediaPrintBodies,
264
+ extract: () => extract,
265
+ PAGE_SIZES: () => PAGE_SIZES,
266
+ MARGIN_BOX_NAMES: () => MARGIN_BOX_NAMES
267
+ });
268
+ var MARGIN_BOX_NAMES = [
269
+ "top-left-corner",
270
+ "top-left",
271
+ "top-center",
272
+ "top-right",
273
+ "top-right-corner",
274
+ "bottom-left-corner",
275
+ "bottom-left",
276
+ "bottom-center",
277
+ "bottom-right",
278
+ "bottom-right-corner",
279
+ "left-top",
280
+ "left-middle",
281
+ "left-bottom",
282
+ "right-top",
283
+ "right-middle",
284
+ "right-bottom"
285
+ ];
286
+ var NESTED_AT_RULES = /^@(media|supports|layer|scope|container|document)\b/i;
287
+ function scanRules(css) {
288
+ const out = [];
289
+ let i = 0;
290
+ let start = 0;
291
+ let depth = 0;
292
+ let bodyStart = -1;
293
+ while (i < css.length) {
294
+ const c = css[i];
295
+ if (c === "/" && css[i + 1] === "*") {
296
+ const end = css.indexOf("*/", i + 2);
297
+ i = end === -1 ? css.length : end + 2;
298
+ continue;
299
+ }
300
+ if (c === '"' || c === "'") {
301
+ i = skipString(css, i);
302
+ continue;
303
+ }
304
+ if (c === "{") {
305
+ if (depth === 0)
306
+ bodyStart = i;
307
+ depth++;
308
+ i++;
309
+ continue;
310
+ }
311
+ if (c === "}") {
312
+ depth--;
313
+ if (depth === 0) {
314
+ out.push({
315
+ prelude: stripComments(css.slice(start, bodyStart)).trim(),
316
+ body: css.slice(bodyStart + 1, i)
317
+ });
318
+ start = i + 1;
319
+ }
320
+ i++;
321
+ continue;
322
+ }
323
+ if (c === ";" && depth === 0) {
324
+ const stmt = stripComments(css.slice(start, i)).trim();
325
+ if (stmt)
326
+ out.push({ statement: stmt });
327
+ start = i + 1;
328
+ i++;
329
+ continue;
330
+ }
331
+ i++;
332
+ }
333
+ return out;
334
+ }
335
+ function stripComments(s) {
336
+ let out = "";
337
+ let i = 0;
338
+ while (i < s.length) {
339
+ if (s[i] === "/" && s[i + 1] === "*") {
340
+ const end = s.indexOf("*/", i + 2);
341
+ i = end === -1 ? s.length : end + 2;
342
+ continue;
343
+ }
344
+ if (s[i] === '"' || s[i] === "'") {
345
+ const end = skipString(s, i);
346
+ out += s.slice(i, end);
347
+ i = end;
348
+ continue;
349
+ }
350
+ out += s[i++];
351
+ }
352
+ return out;
353
+ }
354
+ function skipString(css, i) {
355
+ const quote = css[i];
356
+ i++;
357
+ while (i < css.length) {
358
+ if (css[i] === "\\")
359
+ i += 2;
360
+ else if (css[i] === quote)
361
+ return i + 1;
362
+ else
363
+ i++;
364
+ }
365
+ return i;
366
+ }
367
+ function parseDeclarations(body) {
368
+ const decls = {};
369
+ let i = 0;
370
+ let start = 0;
371
+ let depth = 0;
372
+ const push = (chunk) => {
373
+ const s = stripComments(chunk).trim();
374
+ if (!s)
375
+ return;
376
+ const colon = indexOfTopLevel(s, ":");
377
+ if (colon <= 0)
378
+ return;
379
+ const rawProp = s.slice(0, colon).trim();
380
+ const prop = rawProp.startsWith("--") ? rawProp : rawProp.toLowerCase();
381
+ const value = s.slice(colon + 1).trim().replace(/\s*!important$/i, "");
382
+ if (prop)
383
+ decls[prop] = value;
384
+ };
385
+ while (i < body.length) {
386
+ const c = body[i];
387
+ if (c === "/" && body[i + 1] === "*") {
388
+ const end = body.indexOf("*/", i + 2);
389
+ i = end === -1 ? body.length : end + 2;
390
+ continue;
391
+ }
392
+ if (c === '"' || c === "'") {
393
+ i = skipString(body, i);
394
+ continue;
395
+ }
396
+ if (c === "(" || c === "{" || c === "[")
397
+ depth++;
398
+ else if (c === ")" || c === "}" || c === "]")
399
+ depth--;
400
+ else if (c === ";" && depth === 0) {
401
+ push(body.slice(start, i));
402
+ start = i + 1;
403
+ }
404
+ i++;
405
+ }
406
+ push(body.slice(start));
407
+ return decls;
408
+ }
409
+ function indexOfTopLevel(s, ch) {
410
+ let depth = 0;
411
+ for (let i = 0;i < s.length; i++) {
412
+ const c = s[i];
413
+ if (c === '"' || c === "'") {
414
+ i = skipString(s, i) - 1;
415
+ continue;
416
+ }
417
+ if (c === "(")
418
+ depth++;
419
+ else if (c === ")")
420
+ depth--;
421
+ else if (c === ch && depth === 0)
422
+ return i;
423
+ }
424
+ return -1;
425
+ }
426
+ function mediaPrintBodies(css) {
427
+ const out = [];
428
+ for (const rule of scanRules(css)) {
429
+ if ("statement" in rule)
430
+ continue;
431
+ if (/^@media/i.test(rule.prelude)) {
432
+ const q = rule.prelude.replace(/^@media/i, "").trim();
433
+ if (/print/i.test(q) && !/not\s+print/i.test(q))
434
+ out.push(rule.body);
435
+ }
436
+ }
437
+ return out;
438
+ }
439
+ function extract(css) {
440
+ const model = {
441
+ pageRules: [],
442
+ stringSets: [],
443
+ pageAssignments: [],
444
+ breaks: [],
445
+ xrefs: [],
446
+ counterResets: [],
447
+ pageNames: [],
448
+ warnings: []
449
+ };
450
+ walk(css, model);
451
+ resolveGeometryVars(model, collectRootCustomProperties(css));
452
+ const names = new Set;
453
+ for (const r of model.pageRules)
454
+ if (r.name)
455
+ names.add(r.name);
456
+ for (const a of model.pageAssignments)
457
+ names.add(a.page);
458
+ model.pageNames = [...names];
459
+ return model;
460
+ }
461
+ var GEOMETRY_PROPS = [
462
+ "size",
463
+ "margin",
464
+ "margin-top",
465
+ "margin-right",
466
+ "margin-bottom",
467
+ "margin-left",
468
+ "bleed",
469
+ "marks"
470
+ ];
471
+ function collectRootCustomProperties(css) {
472
+ const props = new Map;
473
+ const walkForRoot = (body) => {
474
+ for (const rule of scanRules(body)) {
475
+ if ("statement" in rule)
476
+ continue;
477
+ const { prelude, body: ruleBody } = rule;
478
+ if (NESTED_AT_RULES.test(prelude)) {
479
+ walkForRoot(ruleBody);
480
+ continue;
481
+ }
482
+ if (!prelude.startsWith("@")) {
483
+ const selectors = splitTopLevel(prelude, ",");
484
+ if (selectors.some((s) => s.trim() === ":root")) {
485
+ const decls = parseDeclarations(ruleBody);
486
+ for (const [k, v] of Object.entries(decls)) {
487
+ if (k.startsWith("--"))
488
+ props.set(k, v);
489
+ }
490
+ }
491
+ }
492
+ }
493
+ };
494
+ walkForRoot(css);
495
+ return props;
496
+ }
497
+ function matchParen(s, open) {
498
+ let depth = 0;
499
+ let i = open;
500
+ while (i < s.length) {
501
+ const c = s[i];
502
+ if (c === '"' || c === "'") {
503
+ i = skipString(s, i);
504
+ continue;
505
+ }
506
+ if (c === "(")
507
+ depth++;
508
+ else if (c === ")") {
509
+ depth--;
510
+ if (depth === 0)
511
+ return i;
512
+ }
513
+ i++;
514
+ }
515
+ return -1;
516
+ }
517
+ function resolveVarsInValue(value, customProps, stack = new Set) {
518
+ let out = "";
519
+ let i = 0;
520
+ while (i < value.length) {
521
+ const isVarStart = /^var\(/i.test(value.slice(i, i + 4)) && (i === 0 || !/[\w-]/.test(value[i - 1]));
522
+ if (!isVarStart) {
523
+ out += value[i];
524
+ i++;
525
+ continue;
526
+ }
527
+ const open = i + 3;
528
+ const close = matchParen(value, open);
529
+ if (close === -1) {
530
+ out += value.slice(i);
531
+ break;
532
+ }
533
+ const inner = value.slice(open + 1, close);
534
+ const commaIdx = indexOfTopLevel(inner, ",");
535
+ const name = (commaIdx === -1 ? inner : inner.slice(0, commaIdx)).trim();
536
+ const fallback = commaIdx === -1 ? undefined : inner.slice(commaIdx + 1).trim();
537
+ if (!/^--[\w-]+$/.test(name)) {
538
+ return { text: out, unresolved: `var(${inner}) is not a valid custom property reference` };
539
+ }
540
+ if (stack.has(name)) {
541
+ return { text: out, unresolved: `var(${name}) is circular` };
542
+ }
543
+ const rootValue = customProps.get(name);
544
+ if (rootValue !== undefined) {
545
+ const nested = resolveVarsInValue(rootValue, customProps, new Set(stack).add(name));
546
+ if (nested.unresolved)
547
+ return { text: out, unresolved: nested.unresolved };
548
+ out += nested.text;
549
+ } else if (fallback !== undefined) {
550
+ if (/\bvar\(/i.test(fallback)) {
551
+ return {
552
+ text: out,
553
+ unresolved: `var(${name}, ${fallback}) — a fallback containing another var() is not resolved`
554
+ };
555
+ }
556
+ out += fallback;
557
+ } else {
558
+ return {
559
+ text: out,
560
+ unresolved: `${name} is not defined at :root and var() has no fallback`
561
+ };
562
+ }
563
+ i = close + 1;
564
+ }
565
+ return { text: out };
566
+ }
567
+ function resolveGeometryVars(model, customProps) {
568
+ for (const rule of model.pageRules) {
569
+ for (const prop of GEOMETRY_PROPS) {
570
+ const value = rule.decls[prop];
571
+ if (!value || !/\bvar\(/i.test(value))
572
+ continue;
573
+ const { text, unresolved } = resolveVarsInValue(value, customProps);
574
+ if (unresolved) {
575
+ throw new Error(`${rule.raw} { ${prop}: ${value} } — cannot resolve ${unresolved}. ` + `Define the custom property at :root, add a literal var(--x, fallback) fallback, or use a literal value.`);
576
+ }
577
+ const unparsable = unparsableGeometry(prop, text);
578
+ if (unparsable) {
579
+ throw new Error(`${rule.raw} { ${prop}: ${value} } — resolves to \`${text.trim()}\`, ${unparsable}. ` + `Use a value this engine can read (a length like 0.75in/12pt/10mm, or a named page size for \`size\`).`);
580
+ }
581
+ rule.decls[prop] = text;
582
+ }
583
+ }
584
+ }
585
+ function unparsableGeometry(prop, text) {
586
+ const t = text.trim();
587
+ if (prop === "size") {
588
+ if (!t)
589
+ return "which is empty";
590
+ return parseSize(t) ? undefined : "which is not a page size";
591
+ }
592
+ if (prop === "margin" || prop.startsWith("margin-")) {
593
+ if (!t)
594
+ return "which is empty";
595
+ const parts = t.split(/\s+/);
596
+ if (prop !== "margin" && parts.length !== 1)
597
+ return "which is not a single length";
598
+ if (parts.length > 4)
599
+ return "which is not a valid margin";
600
+ const bad = parts.filter((p) => toPt(p) === undefined);
601
+ if (bad.length)
602
+ return `which this engine cannot read as a length (\`${bad[0]}\`)`;
603
+ return;
604
+ }
605
+ if (prop === "bleed") {
606
+ if (!t)
607
+ return "which is empty";
608
+ if (/^(auto|none)$/i.test(t))
609
+ return;
610
+ return toPt(t) === undefined ? "which this engine cannot read as a length" : undefined;
611
+ }
612
+ return;
613
+ }
614
+ function walk(css, model) {
615
+ for (const rule of scanRules(css)) {
616
+ if ("statement" in rule)
617
+ continue;
618
+ const { prelude, body } = rule;
619
+ if (NESTED_AT_RULES.test(prelude)) {
620
+ walk(body, model);
621
+ continue;
622
+ }
623
+ if (/^@page\b/i.test(prelude)) {
624
+ model.pageRules.push(parsePageRule(prelude, body));
625
+ continue;
626
+ }
627
+ if (prelude.startsWith("@"))
628
+ continue;
629
+ parseQualifiedRule(prelude, body, model);
630
+ }
631
+ }
632
+ function parsePageRule(prelude, body) {
633
+ const sel = prelude.replace(/^@page\s*/i, "").trim();
634
+ const nameMatch = /^([A-Za-z_][\w-]*)/.exec(sel);
635
+ const pseudos = [...sel.matchAll(/:([A-Za-z-]+)(\([^)]*\))?/g)].map((m) => m[1] + (m[2] ?? ""));
636
+ const rule = {
637
+ name: nameMatch?.[1],
638
+ pseudos,
639
+ decls: {},
640
+ marginBoxes: {},
641
+ raw: `@page ${sel}`.trim()
642
+ };
643
+ let rest = "";
644
+ let i = 0;
645
+ while (i < body.length) {
646
+ const at = findNextAtRule(body, i);
647
+ if (at === -1) {
648
+ rest += body.slice(i);
649
+ break;
650
+ }
651
+ rest += body.slice(i, at);
652
+ const open = body.indexOf("{", at);
653
+ if (open === -1) {
654
+ rest += body.slice(at);
655
+ break;
656
+ }
657
+ const name = body.slice(at + 1, open).trim().toLowerCase();
658
+ let depth = 1;
659
+ let j = open + 1;
660
+ while (j < body.length && depth > 0) {
661
+ const c = body[j];
662
+ if (c === "/" && body[j + 1] === "*") {
663
+ const end = body.indexOf("*/", j + 2);
664
+ j = end === -1 ? body.length : end + 2;
665
+ continue;
666
+ }
667
+ if (c === '"' || c === "'") {
668
+ j = skipString(body, j);
669
+ continue;
670
+ }
671
+ if (c === "{")
672
+ depth++;
673
+ else if (c === "}")
674
+ depth--;
675
+ j++;
676
+ }
677
+ rule.marginBoxes[`@${name}`] = parseDeclarations(body.slice(open + 1, j - 1));
678
+ i = j;
679
+ }
680
+ rule.decls = parseDeclarations(rest);
681
+ return rule;
682
+ }
683
+ function findNextAtRule(body, start) {
684
+ let i = start;
685
+ while (i < body.length) {
686
+ const c = body[i];
687
+ if (c === "/" && body[i + 1] === "*") {
688
+ const end = body.indexOf("*/", i + 2);
689
+ i = end === -1 ? body.length : end + 2;
690
+ continue;
691
+ }
692
+ if (c === '"' || c === "'") {
693
+ i = skipString(body, i);
694
+ continue;
695
+ }
696
+ if (c === "@")
697
+ return i;
698
+ i++;
699
+ }
700
+ return -1;
701
+ }
702
+ function parseQualifiedRule(selector, body, model) {
703
+ const decls = parseDeclarations(body);
704
+ for (const [prop, value] of Object.entries(decls)) {
705
+ if (prop === "string-set") {
706
+ for (const entry of splitTopLevel(value, ",")) {
707
+ const m = /^\s*([A-Za-z_][\w-]*)\s+(.+)$/.exec(entry);
708
+ if (m && m[1] !== undefined && m[2] !== undefined)
709
+ model.stringSets.push({ selector, name: m[1], value: m[2].trim() });
710
+ }
711
+ } else if (prop === "page") {
712
+ if (value && value !== "auto")
713
+ model.pageAssignments.push({ selector, page: value.trim() });
714
+ } else if (prop === "break-before" || prop === "break-after" || prop === "break-inside") {
715
+ model.breaks.push({ selector, prop, value });
716
+ } else if (prop === "counter-reset") {
717
+ const m = /\bpage\s+(-?\d+)/.exec(value);
718
+ if (m)
719
+ model.counterResets.push({ selector, start: Number(m[1]) });
720
+ } else if (prop === "content") {
721
+ for (const fn of ["target-counter", "target-text", "leader", "string"]) {
722
+ if (new RegExp(`\\b${fn}\\s*\\(`).test(value)) {
723
+ model.xrefs.push({ selector, content: value, fn });
724
+ }
725
+ }
726
+ }
727
+ }
728
+ }
729
+ function splitTopLevel(s, sep) {
730
+ const out = [];
731
+ let depth = 0;
732
+ let start = 0;
733
+ for (let i = 0;i < s.length; i++) {
734
+ const c = s[i];
735
+ if (c === '"' || c === "'") {
736
+ i = skipString(s, i) - 1;
737
+ continue;
738
+ }
739
+ if (c === "(")
740
+ depth++;
741
+ else if (c === ")")
742
+ depth--;
743
+ else if (c === sep && depth === 0) {
744
+ out.push(s.slice(start, i));
745
+ start = i + 1;
746
+ }
747
+ }
748
+ out.push(s.slice(start));
749
+ return out.map((x) => x.trim()).filter(Boolean);
750
+ }
751
+ var UNITS_PER_PT = {
752
+ pt: 1,
753
+ px: 0.75,
754
+ in: 72,
755
+ pc: 12,
756
+ cm: 72 / 2.54,
757
+ mm: 72 / 25.4,
758
+ q: 72 / 101.6
759
+ };
760
+ function toPt(value) {
761
+ const m = /^(-?[\d.]+)([a-z%]*)$/i.exec(value.trim());
762
+ if (!m)
763
+ return;
764
+ const n = Number(m[1]);
765
+ const unit = (m[2] || "px").toLowerCase();
766
+ const factor = UNITS_PER_PT[unit];
767
+ return factor === undefined ? undefined : n * factor;
768
+ }
769
+ var PAGE_SIZES = {
770
+ a5: [419.53, 595.28],
771
+ a4: [595.28, 841.89],
772
+ a3: [841.89, 1190.55],
773
+ b5: [498.9, 708.66],
774
+ b4: [708.66, 1000.63],
775
+ "jis-b5": [515.91, 728.5],
776
+ "jis-b4": [728.5, 1031.81],
777
+ letter: [612, 792],
778
+ legal: [612, 1008],
779
+ ledger: [1224, 792]
780
+ };
781
+ function parseSize(value) {
782
+ const parts = value.trim().split(/\s+/);
783
+ let landscape = false;
784
+ const lens = [];
785
+ let named;
786
+ for (const p of parts) {
787
+ const key = p.toLowerCase();
788
+ if (key === "landscape")
789
+ landscape = true;
790
+ else if (key === "portrait")
791
+ continue;
792
+ else if (PAGE_SIZES[key])
793
+ named = PAGE_SIZES[key];
794
+ else {
795
+ const pt = toPt(p);
796
+ if (pt !== undefined)
797
+ lens.push(pt);
798
+ }
799
+ }
800
+ let w, h;
801
+ if (named)
802
+ [w, h] = named;
803
+ else if (lens.length === 1)
804
+ [w, h] = [lens[0], lens[0]];
805
+ else if (lens.length >= 2)
806
+ [w, h] = [lens[0], lens[1]];
807
+ else
808
+ return;
809
+ if (landscape && h > w)
810
+ [w, h] = [h, w];
811
+ return { width: w, height: h };
812
+ }
813
+ function parseMargin(value) {
814
+ const parts = value.trim().split(/\s+/).map((p) => toPt(p) ?? 0);
815
+ const a = parts[0] ?? 0;
816
+ const b = parts[1] ?? a;
817
+ const c = parts[2] ?? a;
818
+ const d = parts[3] ?? b;
819
+ return { top: a, right: b, bottom: c, left: d };
820
+ }
821
+ function resolvePage(model, ctx = {}) {
822
+ const wanted = new Set(ctx.pseudos ?? []);
823
+ const applicable = model.pageRules.filter((r) => r.name ? r.name === ctx.name : true).filter((r) => r.pseudos.every((p) => wanted.has(p))).sort((a, b) => specificity(a) - specificity(b));
824
+ const decls = {};
825
+ const marginBoxes = {};
826
+ const margin = { top: 72, right: 72, bottom: 72, left: 72 };
827
+ for (const rule of applicable) {
828
+ for (const [prop, value] of Object.entries(rule.decls)) {
829
+ if (prop === "margin")
830
+ Object.assign(margin, parseMargin(value));
831
+ else if (prop === "margin-top" || prop === "margin-right" || prop === "margin-bottom" || prop === "margin-left") {
832
+ const side = prop.slice(7);
833
+ margin[side] = toPt(value) ?? margin[side];
834
+ }
835
+ }
836
+ Object.assign(decls, rule.decls);
837
+ for (const [box, d] of Object.entries(rule.marginBoxes)) {
838
+ marginBoxes[box] = { ...marginBoxes[box] ?? {}, ...d };
839
+ }
840
+ }
841
+ const size = parseSize(decls.size ?? "letter") ?? { width: 612, height: 792 };
842
+ return {
843
+ decls,
844
+ marginBoxes,
845
+ geometry: {
846
+ width: size.width,
847
+ height: size.height,
848
+ margin,
849
+ bleed: decls.bleed ? toPt(decls.bleed) ?? 0 : 0,
850
+ marks: (decls.marks ?? "none").split(/\s+/).filter((m) => m && m !== "none")
851
+ }
852
+ };
853
+ }
854
+ function specificity(r) {
855
+ return (r.name ? 2 : 0) + r.pseudos.length;
856
+ }
857
+
858
+ // src/engine/shared/synthesis.ts
859
+ var RECTO_VERSO_VALUES = /^(right|recto|left|verso)$/;
860
+ function isRectoVersoBreak(decl) {
861
+ return decl.prop === "break-before" && RECTO_VERSO_VALUES.test(decl.value.trim());
862
+ }
863
+ function wantsRecto(value) {
864
+ return /^(right|recto)$/.test(value.trim());
865
+ }
866
+ function planRectoBlanks(sites) {
867
+ let shift = 0;
868
+ return sites.map((site) => {
869
+ if (site.page <= 0)
870
+ return false;
871
+ const effective = site.page + shift;
872
+ const onRecto = effective % 2 === 1;
873
+ const wrong = site.wantsRecto ? !onRecto : onRecto;
874
+ if (wrong)
875
+ shift++;
876
+ return wrong;
877
+ });
878
+ }
879
+ var WHICH_VALUES = new Set(["first", "start", "last", "first-except"]);
880
+ function parseWhich(raw) {
881
+ const w = (raw ?? "").trim();
882
+ return WHICH_VALUES.has(w) ? w : "first";
883
+ }
884
+ function stringValueAt(entries, page, which = "first") {
885
+ let entry = "";
886
+ const onPage = [];
887
+ for (const e of entries) {
888
+ if (e.page < page)
889
+ entry = e.value;
890
+ else if (e.page === page)
891
+ onPage.push(e.value);
892
+ else
893
+ break;
894
+ }
895
+ switch (which) {
896
+ case "start":
897
+ return entry;
898
+ case "last":
899
+ return onPage.length ? onPage[onPage.length - 1] : entry;
900
+ case "first-except":
901
+ return onPage.length ? "" : entry;
902
+ case "first":
903
+ return onPage.length ? onPage[0] : entry;
904
+ }
905
+ }
906
+ function pageCounterValues(resets, pageCount) {
907
+ const byPage = new Map;
908
+ for (const r of resets)
909
+ if (r.page >= 1)
910
+ byPage.set(r.page, r.start);
911
+ const values = [];
912
+ let value = 0;
913
+ for (let p = 1;p <= pageCount; p++) {
914
+ if (byPage.has(p))
915
+ value = byPage.get(p) - 1;
916
+ value++;
917
+ values.push(value);
918
+ }
919
+ return values;
920
+ }
921
+ function toFolioPage(physicalPage, pageValues) {
922
+ if (!pageValues)
923
+ return physicalPage;
924
+ return pageValues[physicalPage - 1] ?? physicalPage;
925
+ }
926
+ var LEADER_START = "";
927
+ var LEADER_END = "";
928
+ function leaderMarker(glue) {
929
+ return `${LEADER_START}${glue}${LEADER_END}`;
930
+ }
931
+ var LEADER_RE = /\uE000([^\uE001]*)\uE001/;
932
+ function leaderFillCount(gapPx, gluePx) {
933
+ if (!(gluePx > 0) || !(gapPx > 0))
934
+ return 0;
935
+ return Math.max(0, Math.floor(gapPx / gluePx) - 1);
936
+ }
937
+ function generatedContentCss(selectors) {
938
+ const rules = new Set;
939
+ for (const raw of selectors) {
940
+ for (const one of raw.split(",")) {
941
+ const selector = one.trim();
942
+ if (!selector)
943
+ continue;
944
+ const m = /^(.*?)(::?)(after|before)\s*$/i.exec(selector);
945
+ if (!m)
946
+ continue;
947
+ const [, base, colons, pseudo] = m;
948
+ if (base === undefined || colons === undefined || pseudo === undefined)
949
+ continue;
950
+ const where = pseudo.toLowerCase();
951
+ rules.add(`${base.trim()}[data-gp-${where}]${colons}${where} { content: attr(data-gp-${where}); }`);
952
+ }
953
+ }
954
+ rules.add(`[data-gp-after]::after { content: attr(data-gp-after); }`);
955
+ rules.add(`[data-gp-before]::before { content: attr(data-gp-before); }`);
956
+ return [...rules].join(`
957
+ `);
958
+ }
959
+
960
+ // src/engine/viewer/fragment.ts
961
+ var PX_PER_PT = 96 / 72;
962
+ function injectViewerCss(doc = document) {
963
+ if (doc.getElementById("gp-viewer-css"))
964
+ return;
965
+ const style = doc.createElement("style");
966
+ style.id = "gp-viewer-css";
967
+ style.textContent = viewer_default;
968
+ doc.head.appendChild(style);
969
+ }
970
+ var pt = (v) => v * PX_PER_PT;
971
+ function collectCssText(doc = document) {
972
+ let out = "";
973
+ for (const sheet of Array.from(doc.styleSheets)) {
974
+ const owner = sheet.ownerNode;
975
+ if (owner && owner.tagName === "STYLE") {
976
+ out += owner.textContent + `
977
+ `;
978
+ continue;
979
+ }
980
+ const raw = owner?.__gpSource;
981
+ if (raw)
982
+ out += raw + `
983
+ `;
984
+ }
985
+ return out;
986
+ }
987
+ async function loadStyleSources(doc = document) {
988
+ const links = Array.from(doc.querySelectorAll('link[rel~="stylesheet"]'));
989
+ await Promise.all(links.map(async (l) => {
990
+ if (l.__gpSource)
991
+ return;
992
+ try {
993
+ l.__gpSource = await (await fetch(l.href)).text();
994
+ } catch {
995
+ l.__gpSource = "";
996
+ }
997
+ }));
998
+ return collectCssText(doc);
999
+ }
1000
+ var FORCED_PAGE_LIKE = /^(page|left|right|recto|verso)$/;
1001
+ function injectBreakMapping(_model, doc = document) {
1002
+ doc.getElementById("gp-break-mapping")?.remove();
1003
+ return "";
1004
+ }
1005
+ function forcedColumnBreaksSupported() {
1006
+ return typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("break-before", "column") && CSS.supports("break-after", "column");
1007
+ }
1008
+ function synthesizeColumnBreaks(model) {
1009
+ const sites = [];
1010
+ const seen = new WeakMap;
1011
+ for (const b of model.breaks) {
1012
+ if (b.prop === "break-inside")
1013
+ continue;
1014
+ if (!FORCED_PAGE_LIKE.test(b.value.trim()))
1015
+ continue;
1016
+ let els;
1017
+ try {
1018
+ els = Array.from(document.querySelectorAll(b.selector));
1019
+ } catch {
1020
+ continue;
1021
+ }
1022
+ for (const el of els) {
1023
+ if (!el.closest(".gp-strip"))
1024
+ continue;
1025
+ const cs = getComputedStyle(el);
1026
+ const effective = b.prop === "break-before" ? cs.breakBefore : cs.breakAfter;
1027
+ if (!FORCED_PAGE_LIKE.test(effective.trim()))
1028
+ continue;
1029
+ const props = seen.get(el) ?? new Set;
1030
+ if (props.has(b.prop))
1031
+ continue;
1032
+ props.add(b.prop);
1033
+ seen.set(el, props);
1034
+ sites.push({ el, prop: b.prop });
1035
+ }
1036
+ }
1037
+ sites.sort((a, b) => a.el.compareDocumentPosition(b.el) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
1038
+ for (const { el, prop } of sites) {
1039
+ if (prop === "break-before" && el.style.breakBefore === "auto")
1040
+ continue;
1041
+ const strip = el.closest(".gp-strip");
1042
+ if (!strip)
1043
+ continue;
1044
+ if (prop === "break-before" && !el.previousElementSibling)
1045
+ continue;
1046
+ if (prop === "break-after" && !el.nextElementSibling)
1047
+ continue;
1048
+ const rects = Array.from(el.getClientRects());
1049
+ const rect = prop === "break-after" ? rects.at(-1) : rects[0];
1050
+ if (!rect)
1051
+ continue;
1052
+ const stripTop = strip.getBoundingClientRect().top;
1053
+ const edge = prop === "break-after" ? rect.bottom : rect.top;
1054
+ const offset = edge - stripTop;
1055
+ if (prop === "break-before" && offset < 0.5)
1056
+ continue;
1057
+ if (prop === "break-after" && strip.clientHeight - offset < 0.5)
1058
+ continue;
1059
+ const reserve = Math.ceil(strip.clientHeight - offset);
1060
+ if (reserve <= 0)
1061
+ continue;
1062
+ const spacer = document.createElement("div");
1063
+ spacer.className = "gp-column-break-spacer";
1064
+ spacer.setAttribute("aria-hidden", "true");
1065
+ spacer.style.cssText = `height:${reserve}px;margin:0;padding:0;border:0;`;
1066
+ if (prop === "break-after")
1067
+ el.after(spacer);
1068
+ else
1069
+ el.before(spacer);
1070
+ }
1071
+ }
1072
+ function directPageName(el, model) {
1073
+ for (const a of model.pageAssignments) {
1074
+ try {
1075
+ if (el.matches(a.selector))
1076
+ return a.page;
1077
+ } catch {}
1078
+ }
1079
+ return;
1080
+ }
1081
+ function hasDescendantPageAssignment(el, model) {
1082
+ for (const a of model.pageAssignments) {
1083
+ try {
1084
+ if (el.querySelector(a.selector))
1085
+ return true;
1086
+ } catch {}
1087
+ }
1088
+ return false;
1089
+ }
1090
+ function pushRun(runs, page, nodes) {
1091
+ const last = runs[runs.length - 1];
1092
+ if (last && last.page === page)
1093
+ last.nodes.push(...nodes);
1094
+ else
1095
+ runs.push({ page, nodes });
1096
+ }
1097
+ function explodeChildren(container, model) {
1098
+ const runs = [];
1099
+ let pending = [];
1100
+ const carry = () => {
1101
+ if (!pending.length)
1102
+ return [];
1103
+ const held = pending;
1104
+ pending = [];
1105
+ const last = runs[runs.length - 1];
1106
+ if (last) {
1107
+ pushRun(runs, last.page, held);
1108
+ return [];
1109
+ }
1110
+ if (held.some((n) => (n.textContent ?? "").trim() !== "")) {
1111
+ pushRun(runs, undefined, held);
1112
+ return [];
1113
+ }
1114
+ return held;
1115
+ };
1116
+ for (const node of Array.from(container.childNodes)) {
1117
+ if (node.nodeType !== 1) {
1118
+ pending.push(node);
1119
+ continue;
1120
+ }
1121
+ const kid = node;
1122
+ if (kid.classList.contains("gp-layer"))
1123
+ continue;
1124
+ const own = directPageName(kid, model);
1125
+ if (own !== undefined) {
1126
+ pushRun(runs, own, [...carry(), kid]);
1127
+ continue;
1128
+ }
1129
+ if (!hasDescendantPageAssignment(kid, model)) {
1130
+ pushRun(runs, undefined, [...carry(), kid]);
1131
+ continue;
1132
+ }
1133
+ const inner = explodeChildren(kid, model);
1134
+ if (inner.length <= 1) {
1135
+ pushRun(runs, inner[0]?.page, [...carry(), kid]);
1136
+ continue;
1137
+ }
1138
+ let lead = carry();
1139
+ for (const r of inner) {
1140
+ const shell = kid.cloneNode(false);
1141
+ for (const n of r.nodes)
1142
+ shell.appendChild(n);
1143
+ kid.before(shell);
1144
+ pushRun(runs, r.page, [...lead, shell]);
1145
+ lead = [];
1146
+ }
1147
+ kid.remove();
1148
+ }
1149
+ const trailing = carry();
1150
+ if (trailing.length)
1151
+ pushRun(runs, undefined, trailing);
1152
+ return runs;
1153
+ }
1154
+ var FORCED_BREAK = /^(column|page|left|right|recto|verso|always)$/;
1155
+ function clearLeadingForcedBreaks(strip) {
1156
+ for (let el = strip.firstElementChild;el; el = el.firstElementChild) {
1157
+ const cs = getComputedStyle(el);
1158
+ if (FORCED_BREAK.test(cs.breakBefore))
1159
+ el.style.breakBefore = "auto";
1160
+ }
1161
+ }
1162
+ function stabilizeFullHeightPageRoots(model, strips) {
1163
+ let stabilized = 0;
1164
+ for (const strip of strips) {
1165
+ if (!strip.page)
1166
+ continue;
1167
+ const stripHeight = parseFloat(getComputedStyle(strip.el).getPropertyValue("--gp-content-h"));
1168
+ if (!(stripHeight > 0))
1169
+ continue;
1170
+ for (let el = strip.el.firstElementChild;el; el = el.firstElementChild) {
1171
+ if (directPageName(el, model) !== strip.page)
1172
+ continue;
1173
+ const cs = getComputedStyle(el);
1174
+ const height = parseFloat(cs.height);
1175
+ const rootRects = el.getClientRects();
1176
+ if (cs.display === "block" && cs.position !== "static" && Math.abs(height - stripHeight) <= 0.5 && (rootRects.length > 1 || el.getBoundingClientRect().top - strip.el.getBoundingClientRect().top > 0.5)) {
1177
+ el.dataset.gpLeadingPageRootDisplay = el.style.getPropertyValue("display");
1178
+ el.dataset.gpLeadingPageRootDisplayPriority = el.style.getPropertyPriority("display");
1179
+ el.style.display = "flow-root";
1180
+ el.dataset.gpLeadingPageRoot = "stabilized";
1181
+ stabilized++;
1182
+ }
1183
+ break;
1184
+ }
1185
+ }
1186
+ return stabilized;
1187
+ }
1188
+ function restoreFullHeightPageRoots(doc = document) {
1189
+ for (const el of Array.from(doc.querySelectorAll('[data-gp-leading-page-root="stabilized"]'))) {
1190
+ const value = el.dataset.gpLeadingPageRootDisplay ?? "";
1191
+ const priority = el.dataset.gpLeadingPageRootDisplayPriority ?? "";
1192
+ if (value)
1193
+ el.style.setProperty("display", value, priority);
1194
+ else
1195
+ el.style.removeProperty("display");
1196
+ delete el.dataset.gpLeadingPageRoot;
1197
+ delete el.dataset.gpLeadingPageRootDisplay;
1198
+ delete el.dataset.gpLeadingPageRootDisplayPriority;
1199
+ }
1200
+ }
1201
+ function compensateTrailingMarginsBeforeAvoids(model, strips) {
1202
+ const candidates = new Set;
1203
+ for (const decl of model.breaks) {
1204
+ if (decl.prop !== "break-inside" || !/^avoid(?:-|$)/.test(decl.value.trim()))
1205
+ continue;
1206
+ let els;
1207
+ try {
1208
+ els = Array.from(document.querySelectorAll(decl.selector));
1209
+ } catch {
1210
+ continue;
1211
+ }
1212
+ for (const el of els) {
1213
+ if (el instanceof HTMLElement && el.closest(".gp-strip"))
1214
+ candidates.add(el);
1215
+ }
1216
+ }
1217
+ let compensated = 0;
1218
+ const orderedCandidates = Array.from(candidates).sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
1219
+ for (const el of orderedCandidates) {
1220
+ if (!/^avoid(?:-|$)/.test(getComputedStyle(el).breakInside))
1221
+ continue;
1222
+ const prev = el.previousElementSibling;
1223
+ const stripEl = el.closest(".gp-strip");
1224
+ const strip = strips.find((item) => item.el === stripEl);
1225
+ if (!prev || !stripEl || !strip)
1226
+ continue;
1227
+ const rects = Array.from(el.getClientRects());
1228
+ const prevRects = Array.from(prev.getClientRects());
1229
+ if (rects.length !== 1 || !prevRects.length)
1230
+ continue;
1231
+ const rect = rects[0];
1232
+ const prevRect = prevRects.at(-1);
1233
+ const stripRect = stripEl.getBoundingClientRect();
1234
+ const { stride } = stripMetrics(stripEl);
1235
+ const colOf = (r) => Math.floor((r.left - stripRect.left + 1) / stride);
1236
+ const currentCol = colOf(rect);
1237
+ if (currentCol !== colOf(prevRect) + 1)
1238
+ continue;
1239
+ if (Math.abs(rect.top - stripRect.top) > 0.5)
1240
+ continue;
1241
+ const marginEnd = parseFloat(getComputedStyle(prev).marginBlockEnd) || 0;
1242
+ if (marginEnd <= 0.5)
1243
+ continue;
1244
+ const remaining = stripEl.clientHeight - (prevRect.bottom - stripRect.top);
1245
+ if (rect.height > remaining + 0.5)
1246
+ continue;
1247
+ if (rect.height + marginEnd <= remaining + 0.5)
1248
+ continue;
1249
+ prev.dataset.gpTrailingMargin = "compensated";
1250
+ prev.dataset.gpTrailingMarginValue = prev.style.getPropertyValue("margin-block-end");
1251
+ prev.dataset.gpTrailingMarginPriority = prev.style.getPropertyPriority("margin-block-end");
1252
+ prev.style.setProperty("margin-block-end", "0px");
1253
+ compensated++;
1254
+ }
1255
+ return compensated;
1256
+ }
1257
+ function restoreTrailingMargins(doc = document) {
1258
+ for (const el of Array.from(doc.querySelectorAll('[data-gp-trailing-margin="compensated"]')))
1259
+ restoreTrailingMargin(el);
1260
+ }
1261
+ function restoreTrailingMargin(el) {
1262
+ const value = el.dataset.gpTrailingMarginValue ?? "";
1263
+ const priority = el.dataset.gpTrailingMarginPriority ?? "";
1264
+ if (value)
1265
+ el.style.setProperty("margin-block-end", value, priority);
1266
+ else
1267
+ el.style.removeProperty("margin-block-end");
1268
+ delete el.dataset.gpTrailingMargin;
1269
+ delete el.dataset.gpTrailingMarginValue;
1270
+ delete el.dataset.gpTrailingMarginPriority;
1271
+ }
1272
+ function restoreIneffectiveTrailingMargins(strips) {
1273
+ for (const prev of Array.from(document.querySelectorAll('[data-gp-trailing-margin="compensated"]'))) {
1274
+ const target = prev.nextElementSibling;
1275
+ if (!target || pageOf(target, strips) !== pageRangeOf(prev, strips)[1]) {
1276
+ restoreTrailingMargin(prev);
1277
+ }
1278
+ }
1279
+ }
1280
+ function buildStrips(model, opts = {}, warnings = []) {
1281
+ const doc = document;
1282
+ const root = opts.root ?? doc.querySelector("main") ?? doc.body;
1283
+ const gap = opts.sheetGap ?? 24;
1284
+ const runs = explodeChildren(root, model);
1285
+ const strips = [];
1286
+ for (const run of runs) {
1287
+ const { geometry } = resolvePage(model, { name: run.page });
1288
+ const strip = doc.createElement("div");
1289
+ strip.className = "gp-strip";
1290
+ if (run.page)
1291
+ strip.dataset.page = run.page;
1292
+ const w = pt(geometry.width - geometry.margin.left - geometry.margin.right);
1293
+ const h = pt(geometry.height - geometry.margin.top - geometry.margin.bottom);
1294
+ strip.style.setProperty("--gp-content-w", `${w}px`);
1295
+ strip.style.setProperty("--gp-content-h", `${h}px`);
1296
+ strip.style.setProperty("--gp-sheet-gap", `${gap}px`);
1297
+ strip.style.setProperty("--gp-page-w", `${pt(geometry.width)}px`);
1298
+ strip.style.setProperty("--gp-page-h", `${pt(geometry.height)}px`);
1299
+ strip.style.setProperty("--gp-margin-top", `${pt(geometry.margin.top)}px`);
1300
+ strip.style.setProperty("--gp-margin-right", `${pt(geometry.margin.right)}px`);
1301
+ strip.style.setProperty("--gp-margin-bottom", `${pt(geometry.margin.bottom)}px`);
1302
+ strip.style.setProperty("--gp-margin-left", `${pt(geometry.margin.left)}px`);
1303
+ run.nodes[0].before(strip);
1304
+ for (const n of run.nodes)
1305
+ strip.appendChild(n);
1306
+ strips.push({ el: strip, page: run.page, geometry, pages: 0, offset: 0 });
1307
+ }
1308
+ for (const s of strips)
1309
+ clearLeadingForcedBreaks(s.el);
1310
+ return strips;
1311
+ }
1312
+ function compensateRepeatedHeaders(strips, maxPasses = 24) {
1313
+ const warnings = [];
1314
+ let passes = 0;
1315
+ let touched = 0;
1316
+ for (const strip of strips) {
1317
+ const tables = Array.from(strip.el.querySelectorAll("table")).filter((t) => t.tHead || t.tFoot);
1318
+ if (!tables.length)
1319
+ continue;
1320
+ for (const table of tables) {
1321
+ for (const shim of Array.from(table.querySelectorAll("tr.gp-thead-shim, tr.gp-tfoot-shim")))
1322
+ shim.remove();
1323
+ table.style.breakBefore = "";
1324
+ }
1325
+ const pushed = new Set;
1326
+ const footClaims = new Map;
1327
+ let previous = "";
1328
+ for (let pass = 0;pass < maxPasses; pass++) {
1329
+ passes = Math.max(passes, pass + 1);
1330
+ const stride = strideOf(strip.el);
1331
+ const stripLeft = strip.el.getBoundingClientRect().left - strip.el.scrollLeft;
1332
+ const colOf = (r) => Math.floor((r.left - stripLeft + 1) / stride);
1333
+ const stripTop = strip.el.getBoundingClientRect().top;
1334
+ const colBottom = strip.el.clientHeight;
1335
+ const plans = [];
1336
+ for (const table of tables) {
1337
+ const head = table.tHead;
1338
+ const headRect = head?.getClientRects()[0];
1339
+ const footHeight = table.tFoot?.getBoundingClientRect().height ?? 0;
1340
+ const rows = Array.from(table.querySelectorAll("tbody > tr")).filter((r) => !r.classList.contains("gp-thead-shim") && !r.classList.contains("gp-tfoot-shim"));
1341
+ if (!rows.length)
1342
+ continue;
1343
+ const rects = rows.map((r) => r.getClientRects()[0] ?? r.getBoundingClientRect());
1344
+ const cols = rects.map(colOf);
1345
+ const claims = footClaims.get(table) ?? new Map;
1346
+ footClaims.set(table, claims);
1347
+ let newClaim;
1348
+ if (footHeight > 0) {
1349
+ const lastCol = cols[cols.length - 1];
1350
+ for (let i = 0;i < rows.length; i++) {
1351
+ const row = rows[i];
1352
+ if (cols[i] === lastCol || claims.has(row))
1353
+ continue;
1354
+ const bottom = rects[i].bottom - stripTop;
1355
+ if (bottom > colBottom - footHeight + 0.5) {
1356
+ newClaim = row;
1357
+ claims.set(row, colBottom - (rects[i].top - stripTop));
1358
+ break;
1359
+ }
1360
+ }
1361
+ }
1362
+ plans.push({
1363
+ table,
1364
+ push: headRect ? colOf(headRect) < cols[0] : false,
1365
+ headHeight: headRect?.height ?? 0,
1366
+ footHeight,
1367
+ breakRows: headRect ? rows.filter((_, i) => i > 0 && cols[i] > cols[i - 1]) : [],
1368
+ footRows: [...claims.entries()],
1369
+ grew: newClaim !== undefined,
1370
+ cells: Math.max(1, ...rows.map((r) => r.cells.length))
1371
+ });
1372
+ }
1373
+ const signature = plans.map((p) => `${p.push || pushed.has(p.table) ? "P" : ""}${p.breakRows.map((r) => r.rowIndex).join(".")}~${p.footRows.map(([r]) => r.rowIndex).join(".")}`).join("|");
1374
+ const anyGrowth = plans.some((p) => p.grew);
1375
+ if (!anyGrowth && signature === previous)
1376
+ break;
1377
+ previous = signature;
1378
+ for (const plan of plans) {
1379
+ for (const shim of Array.from(plan.table.querySelectorAll("tr.gp-thead-shim, tr.gp-tfoot-shim")))
1380
+ shim.remove();
1381
+ if (plan.push && !pushed.has(plan.table)) {
1382
+ pushed.add(plan.table);
1383
+ plan.table.style.breakBefore = "column";
1384
+ touched++;
1385
+ continue;
1386
+ }
1387
+ for (const row of plan.breakRows) {
1388
+ row.before(headerShim(plan.table.tHead, plan.headHeight, plan.cells));
1389
+ touched++;
1390
+ }
1391
+ for (const [row, height] of plan.footRows) {
1392
+ row.before(sectionShim(plan.table.tFoot, height, plan.cells, "gp-tfoot-shim"));
1393
+ touched++;
1394
+ }
1395
+ }
1396
+ }
1397
+ }
1398
+ return { tables: touched, passes, warnings };
1399
+ }
1400
+ function headerShim(head, height, cells) {
1401
+ return sectionShim(head, height, cells, "gp-thead-shim");
1402
+ }
1403
+ function sectionShim(section, height, cells, className) {
1404
+ const shim = document.createElement("tr");
1405
+ shim.className = className;
1406
+ shim.setAttribute("aria-hidden", "true");
1407
+ shim.style.height = `${height}px`;
1408
+ if (className === "gp-tfoot-shim")
1409
+ shim.style.verticalAlign = "bottom";
1410
+ const source = section.rows[0];
1411
+ if (source) {
1412
+ for (const cell of Array.from(source.cells)) {
1413
+ const td = document.createElement("td");
1414
+ td.colSpan = cell.colSpan;
1415
+ td.innerHTML = cell.innerHTML;
1416
+ const cs = getComputedStyle(cell);
1417
+ td.style.cssText = `font:${cs.font};text-align:${cs.textAlign};padding:${cs.padding};border:${cs.border};background:${cs.backgroundColor};`;
1418
+ shim.appendChild(td);
1419
+ }
1420
+ } else {
1421
+ const td = document.createElement("td");
1422
+ td.colSpan = cells;
1423
+ td.style.cssText = `height:${height}px;padding:0;border:0;`;
1424
+ shim.appendChild(td);
1425
+ }
1426
+ return shim;
1427
+ }
1428
+ function compensateRectoBreaks(model, strips) {
1429
+ const decls = model.breaks.filter(isRectoVersoBreak);
1430
+ for (const spacer of Array.from(document.querySelectorAll(".gp-recto-spacer")))
1431
+ spacer.remove();
1432
+ if (!decls.length)
1433
+ return 0;
1434
+ const sites = [];
1435
+ for (const d of decls) {
1436
+ let els = [];
1437
+ try {
1438
+ els = Array.from(document.querySelectorAll(d.selector));
1439
+ } catch {
1440
+ continue;
1441
+ }
1442
+ for (const el of els)
1443
+ sites.push({ el, wantsRecto: wantsRecto(d.value) });
1444
+ }
1445
+ sites.sort((a, b) => a.el.compareDocumentPosition(b.el) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
1446
+ const plan = planRectoBlanks(sites.map((site) => ({ page: pageOf(site.el, strips) + 1, wantsRecto: site.wantsRecto })));
1447
+ let inserted = 0;
1448
+ for (const [i, site] of sites.entries()) {
1449
+ if (!plan[i])
1450
+ continue;
1451
+ const spacer = document.createElement("div");
1452
+ spacer.className = "gp-recto-spacer";
1453
+ spacer.setAttribute("aria-hidden", "true");
1454
+ spacer.style.cssText = "break-before: column; break-after: column; height: 0; margin: 0; padding: 0; border: 0;";
1455
+ site.el.before(spacer);
1456
+ inserted++;
1457
+ }
1458
+ return inserted;
1459
+ }
1460
+ function unwrapStrips(strips) {
1461
+ for (const strip of strips) {
1462
+ const stripEl = strip.el;
1463
+ for (const spacer of Array.from(stripEl.querySelectorAll(".gp-wrap-spacer, .gp-column-break-spacer")))
1464
+ spacer.remove();
1465
+ const runWrapper = stripEl.parentElement;
1466
+ const removalTarget = runWrapper && runWrapper.classList.contains("gp-run") ? runWrapper : stripEl;
1467
+ const parent = removalTarget.parentNode;
1468
+ if (!parent)
1469
+ continue;
1470
+ while (stripEl.firstChild)
1471
+ parent.insertBefore(stripEl.firstChild, removalTarget);
1472
+ parent.removeChild(removalTarget);
1473
+ }
1474
+ }
1475
+ function measure(strips) {
1476
+ let offset = 0;
1477
+ for (const strip of strips) {
1478
+ const stride = strideOf(strip.el);
1479
+ strip.pages = Math.max(1, Math.round(strip.el.scrollWidth / stride));
1480
+ strip.offset = offset;
1481
+ offset += strip.pages;
1482
+ strip.el.style.setProperty("--gp-pages", String(strip.pages));
1483
+ }
1484
+ return { strips, totalPages: offset };
1485
+ }
1486
+ function strideOf(strip) {
1487
+ return stripMetrics(strip).stride;
1488
+ }
1489
+ function stripMetrics(strip) {
1490
+ const cs = getComputedStyle(strip);
1491
+ const w = parseFloat(cs.getPropertyValue("--gp-content-w"));
1492
+ const colGap = parseFloat(cs.columnGap) || 0;
1493
+ const h = parseFloat(cs.getPropertyValue("--gp-content-h"));
1494
+ const rowGap = parseFloat(cs.rowGap) || 0;
1495
+ return { stride: w + colGap, rowStride: h + rowGap };
1496
+ }
1497
+ function rowStrideOf(strip) {
1498
+ return stripMetrics(strip).rowStride;
1499
+ }
1500
+ function wrapGeometry(strip) {
1501
+ if (!strip.wrapCols)
1502
+ return { perRow: strip.pages, shift: 0 };
1503
+ return {
1504
+ perRow: strip.wrapCols,
1505
+ shift: strip.wrapCols === 2 && strip.offset % 2 === 0 ? 1 : 0
1506
+ };
1507
+ }
1508
+ function indexInStrip(left, top, strip) {
1509
+ const { stride, rowStride } = stripMetrics(strip.el);
1510
+ const stripBox = strip.el.getBoundingClientRect();
1511
+ const stripLeft = stripBox.left - strip.el.scrollLeft;
1512
+ const stripTop = stripBox.top;
1513
+ const { perRow, shift } = wrapGeometry(strip);
1514
+ const colVisual = Math.floor((left - stripLeft + 1) / stride);
1515
+ const colClamped = Math.max(0, Math.min(perRow - 1, colVisual));
1516
+ const row = Math.max(0, Math.floor((top - stripTop + 1) / rowStride));
1517
+ const idx = row * perRow + colClamped - shift;
1518
+ return Math.max(0, Math.min(strip.pages - 1, idx));
1519
+ }
1520
+ function pageOf(el, strips) {
1521
+ const strip = strips.find((s) => s.el.contains(el));
1522
+ if (!strip)
1523
+ return -1;
1524
+ const rects = el.getClientRects();
1525
+ const first = rects.length ? rects[0] : el.getBoundingClientRect();
1526
+ return strip.offset + indexInStrip(first.left, first.top, strip);
1527
+ }
1528
+ function spreadModeSupported() {
1529
+ return typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("column-wrap", "wrap") && CSS.supports("column-height", "100px");
1530
+ }
1531
+ function applySpreadMode(strips, spread) {
1532
+ const cols = spread ? 2 : 1;
1533
+ const on = spreadModeSupported();
1534
+ for (const strip of strips) {
1535
+ const el = strip.el;
1536
+ const existingSpacer = el.querySelector(":scope > .gp-wrap-spacer");
1537
+ if (!on) {
1538
+ existingSpacer?.remove();
1539
+ delete el.dataset.wrap;
1540
+ el.style.removeProperty("--gp-wrap-cols");
1541
+ strip.wrapCols = undefined;
1542
+ continue;
1543
+ }
1544
+ strip.wrapCols = cols;
1545
+ const { shift } = wrapGeometry(strip);
1546
+ el.dataset.wrap = "on";
1547
+ el.style.setProperty("--gp-wrap-cols", String(cols));
1548
+ if (shift) {
1549
+ if (!existingSpacer) {
1550
+ const spacer = document.createElement("div");
1551
+ spacer.className = "gp-wrap-spacer";
1552
+ spacer.setAttribute("aria-hidden", "true");
1553
+ el.insertBefore(spacer, el.firstChild);
1554
+ }
1555
+ } else {
1556
+ existingSpacer?.remove();
1557
+ }
1558
+ }
1559
+ }
1560
+ function blankPageIndices(strips) {
1561
+ return Array.from(document.querySelectorAll(".gp-recto-spacer")).map((el) => pageOf(el, strips));
1562
+ }
1563
+ function pageRangeOf(el, strips) {
1564
+ const strip = strips.find((s) => s.el.contains(el));
1565
+ if (!strip)
1566
+ return [-1, -1];
1567
+ const rects = Array.from(el.getClientRects());
1568
+ if (!rects.length)
1569
+ return [pageOf(el, strips), pageOf(el, strips)];
1570
+ const idx = rects.map((r) => indexInStrip(r.left, r.top, strip));
1571
+ return [strip.offset + Math.min(...idx), strip.offset + Math.max(...idx)];
1572
+ }
1573
+ function imageIntrinsicSizeReady(img) {
1574
+ if (img.complete)
1575
+ return Promise.resolve();
1576
+ return new Promise((resolve) => {
1577
+ img.addEventListener("load", () => resolve(), { once: true });
1578
+ img.addEventListener("error", () => resolve(), { once: true });
1579
+ });
1580
+ }
1581
+ function waitForLayoutReady(doc = document) {
1582
+ const fontsReady = doc.fonts?.ready ?? Promise.resolve();
1583
+ const imagesReady = Promise.all(Array.from(doc.images).map(imageIntrinsicSizeReady));
1584
+ return Promise.all([fontsReady, imagesReady]).then(() => {
1585
+ return;
1586
+ });
1587
+ }
1588
+ async function fragmentDocument(opts = {}) {
1589
+ const layoutReady = waitForLayoutReady();
1590
+ const css = await loadStyleSources();
1591
+ injectViewerCss();
1592
+ const printOnly = mediaPrintBodies(css).join(`
1593
+ `);
1594
+ if (printOnly && !document.getElementById("gp-media-print")) {
1595
+ const style = document.createElement("style");
1596
+ style.id = "gp-media-print";
1597
+ style.textContent = printOnly;
1598
+ document.head.appendChild(style);
1599
+ }
1600
+ const model = extract(css);
1601
+ injectBreakMapping(model);
1602
+ const authoring = [];
1603
+ const strips = buildStrips(model, opts, authoring);
1604
+ await layoutReady;
1605
+ stabilizeFullHeightPageRoots(model, strips);
1606
+ compensateTrailingMarginsBeforeAvoids(model, strips);
1607
+ synthesizeColumnBreaks(model);
1608
+ measure(strips);
1609
+ const blanks = compensateRectoBreaks(model, strips);
1610
+ if (blanks)
1611
+ measure(strips);
1612
+ const headers = opts.compensateHeaders === false ? { tables: 0, passes: 0, warnings: [] } : compensateRepeatedHeaders(strips);
1613
+ restoreIneffectiveTrailingMargins(strips);
1614
+ const { totalPages } = measure(strips);
1615
+ const api = {
1616
+ model,
1617
+ strips,
1618
+ totalPages,
1619
+ warnings: [...new Set([...authoring, ...headers.warnings])],
1620
+ blankPages: blanks,
1621
+ blankPageIndices: blankPageIndices(strips),
1622
+ pageOf: (sel) => pageOf(typeof sel === "string" ? document.querySelector(sel) : sel, strips),
1623
+ pageRangeOf: (sel) => pageRangeOf(typeof sel === "string" ? document.querySelector(sel) : sel, strips),
1624
+ relayout: () => {
1625
+ restoreFullHeightPageRoots();
1626
+ restoreTrailingMargins();
1627
+ unwrapStrips(strips);
1628
+ for (const spacer of Array.from(document.querySelectorAll(".gp-recto-spacer")))
1629
+ spacer.remove();
1630
+ const rebuilt = buildStrips(model, opts, authoring);
1631
+ strips.length = 0;
1632
+ strips.push(...rebuilt);
1633
+ stabilizeFullHeightPageRoots(model, strips);
1634
+ compensateTrailingMarginsBeforeAvoids(model, strips);
1635
+ synthesizeColumnBreaks(model);
1636
+ measure(strips);
1637
+ api.blankPages = compensateRectoBreaks(model, strips);
1638
+ if (opts.compensateHeaders !== false)
1639
+ api.warnings = [
1640
+ ...new Set([...authoring, ...compensateRepeatedHeaders(strips).warnings])
1641
+ ];
1642
+ restoreIneffectiveTrailingMargins(strips);
1643
+ const r = measure(strips);
1644
+ api.totalPages = r.totalPages;
1645
+ api.blankPageIndices = blankPageIndices(strips);
1646
+ return r;
1647
+ }
1648
+ };
1649
+ return api;
1650
+ }
1651
+
1652
+ // src/engine/shared/content-value.ts
1653
+ var exports_content_value = {};
1654
+ __export(exports_content_value, {
1655
+ unquote: () => unquote,
1656
+ resolveUrlArg: () => resolveUrlArg,
1657
+ parseContent: () => parseContent,
1658
+ needsMeasurement: () => needsMeasurement,
1659
+ formatCounter: () => formatCounter,
1660
+ evaluateContent: () => evaluateContent,
1661
+ evaluate: () => evaluate
1662
+ });
1663
+ var FUNC = /^([a-z-]+)\(/i;
1664
+ function parseContent(value) {
1665
+ const parts = [];
1666
+ let i = 0;
1667
+ const s = value.trim();
1668
+ while (i < s.length) {
1669
+ const c = s[i];
1670
+ if (/\s/.test(c)) {
1671
+ i++;
1672
+ continue;
1673
+ }
1674
+ if (c === '"' || c === "'") {
1675
+ const end = closeString(s, i);
1676
+ parts.push({ type: "literal", value: unquote(s.slice(i, end)) });
1677
+ i = end;
1678
+ continue;
1679
+ }
1680
+ const rest = s.slice(i);
1681
+ const fn = FUNC.exec(rest);
1682
+ if (fn && fn[1] !== undefined) {
1683
+ const open = i + fn[0].length - 1;
1684
+ const close = matchParen2(s, open);
1685
+ const args = splitTopLevel(s.slice(open + 1, close), ",");
1686
+ parts.push(toPart(fn[1].toLowerCase(), args));
1687
+ i = close + 1;
1688
+ continue;
1689
+ }
1690
+ const word = /^[^\s"']+/.exec(rest)[0];
1691
+ parts.push({ type: "keyword", value: word });
1692
+ i += word.length;
1693
+ }
1694
+ return parts;
1695
+ }
1696
+ function toPart(name, args) {
1697
+ switch (name) {
1698
+ case "counter":
1699
+ return { type: "counter", name: args[0] ?? "page", style: args[1] ?? "decimal" };
1700
+ case "string":
1701
+ return { type: "string", name: args[0] ?? "", which: args[1] ?? "first" };
1702
+ case "target-counter":
1703
+ return {
1704
+ type: "target-counter",
1705
+ url: args[0] ?? "",
1706
+ counter: args[1] ?? "page",
1707
+ style: args[2] ?? "decimal"
1708
+ };
1709
+ case "target-text":
1710
+ return { type: "target-text", url: args[0] ?? "", which: args[1] ?? "content" };
1711
+ case "leader":
1712
+ return { type: "leader", glue: unquote(args[0] ?? '"."') };
1713
+ case "attr": {
1714
+ const [a, as] = (args[0] ?? "").split(/\s+/);
1715
+ return { type: "attr", name: a ?? "", as };
1716
+ }
1717
+ case "content":
1718
+ return { type: "content", which: args[0] ?? "text" };
1719
+ default:
1720
+ return { type: "keyword", value: `${name}(${args.join(",")})` };
1721
+ }
1722
+ }
1723
+ function closeString(s, i) {
1724
+ const q = s[i++];
1725
+ while (i < s.length) {
1726
+ if (s[i] === "\\")
1727
+ i += 2;
1728
+ else if (s[i] === q)
1729
+ return i + 1;
1730
+ else
1731
+ i++;
1732
+ }
1733
+ return i;
1734
+ }
1735
+ function matchParen2(s, open) {
1736
+ let depth = 0;
1737
+ for (let i = open;i < s.length; i++) {
1738
+ const c = s[i];
1739
+ if (c === '"' || c === "'") {
1740
+ i = closeString(s, i) - 1;
1741
+ continue;
1742
+ }
1743
+ if (c === "(")
1744
+ depth++;
1745
+ else if (c === ")" && --depth === 0)
1746
+ return i;
1747
+ }
1748
+ return s.length - 1;
1749
+ }
1750
+ function unquote(s) {
1751
+ const t = s.trim();
1752
+ if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'"))
1753
+ return t.slice(1, -1).replace(/\\(.)/g, "$1");
1754
+ return t;
1755
+ }
1756
+ function resolveUrlArg(arg, ctx) {
1757
+ const m = /^attr\(\s*([\w-]+)(?:\s+url)?\s*\)$/i.exec(arg.trim());
1758
+ if (m && m[1] !== undefined)
1759
+ return ctx.attr?.(m[1]) ?? "";
1760
+ return unquote(arg);
1761
+ }
1762
+ var ROMAN = [
1763
+ [1000, "m"],
1764
+ [900, "cm"],
1765
+ [500, "d"],
1766
+ [400, "cd"],
1767
+ [100, "c"],
1768
+ [90, "xc"],
1769
+ [50, "l"],
1770
+ [40, "xl"],
1771
+ [10, "x"],
1772
+ [9, "ix"],
1773
+ [5, "v"],
1774
+ [4, "iv"],
1775
+ [1, "i"]
1776
+ ];
1777
+ function formatCounter(n, style = "decimal") {
1778
+ switch (style.trim()) {
1779
+ case "decimal-leading-zero":
1780
+ return n < 10 ? `0${n}` : String(n);
1781
+ case "lower-roman":
1782
+ case "upper-roman": {
1783
+ let v = n;
1784
+ let out = "";
1785
+ for (const [num, sym] of ROMAN)
1786
+ while (v >= num)
1787
+ out += sym, v -= num;
1788
+ return style === "upper-roman" ? out.toUpperCase() : out;
1789
+ }
1790
+ case "lower-alpha":
1791
+ case "upper-alpha": {
1792
+ let v = n;
1793
+ let out = "";
1794
+ while (v > 0) {
1795
+ const r = (v - 1) % 26;
1796
+ out = String.fromCharCode(97 + r) + out;
1797
+ v = Math.floor((v - 1) / 26);
1798
+ }
1799
+ return style === "upper-alpha" ? out.toUpperCase() : out;
1800
+ }
1801
+ case "none":
1802
+ return "";
1803
+ default:
1804
+ return String(n);
1805
+ }
1806
+ }
1807
+ function evaluateContent(parts, ctx) {
1808
+ let out = "";
1809
+ for (const p of parts) {
1810
+ switch (p.type) {
1811
+ case "literal":
1812
+ out += p.value;
1813
+ break;
1814
+ case "counter":
1815
+ out += formatCounter(p.name === "pages" ? ctx.pages ?? 0 : ctx.page ?? 0, p.style);
1816
+ break;
1817
+ case "string":
1818
+ out += ctx.strings?.(p.name, p.which) ?? "";
1819
+ break;
1820
+ case "target-counter": {
1821
+ const url = resolveUrlArg(p.url, ctx);
1822
+ const page = ctx.targetPage?.(url);
1823
+ out += page === undefined ? "?" : formatCounter(page, p.style);
1824
+ break;
1825
+ }
1826
+ case "target-text": {
1827
+ const url = resolveUrlArg(p.url, ctx);
1828
+ out += ctx.targetText?.(url, p.which) ?? "";
1829
+ break;
1830
+ }
1831
+ case "attr":
1832
+ out += ctx.attr?.(p.name) ?? "";
1833
+ break;
1834
+ case "content":
1835
+ out += ctx.text ?? "";
1836
+ break;
1837
+ case "leader":
1838
+ out += ctx.leader?.(p.glue) ?? "";
1839
+ break;
1840
+ case "keyword":
1841
+ if (p.value === "normal" || p.value === "none")
1842
+ break;
1843
+ out += p.value;
1844
+ break;
1845
+ }
1846
+ }
1847
+ return out;
1848
+ }
1849
+ function evaluate(value, ctx) {
1850
+ return evaluateContent(parseContent(value), ctx);
1851
+ }
1852
+ function needsMeasurement(value) {
1853
+ return parseContent(value).some((p) => p.type === "target-counter" || p.type === "target-text" || p.type === "leader");
1854
+ }
1855
+
1856
+ // src/engine/shared/margin-box-support.ts
1857
+ var MARGIN_BOX_IGNORED_PROPERTIES = new Set([
1858
+ "transform",
1859
+ "rotate",
1860
+ "translate",
1861
+ "scale",
1862
+ "box-shadow"
1863
+ ]);
1864
+ function isIgnoredMarginBoxProperty(property) {
1865
+ return MARGIN_BOX_IGNORED_PROPERTIES.has(property.toLowerCase());
1866
+ }
1867
+
1868
+ // src/engine/viewer/decorate.ts
1869
+ var px = (v) => `${v * PX_PER_PT}px`;
1870
+ function elementForHref(href) {
1871
+ const raw = href.replace(/^#/, "");
1872
+ if (!raw)
1873
+ return null;
1874
+ let id = raw;
1875
+ try {
1876
+ id = decodeURIComponent(raw);
1877
+ } catch {}
1878
+ return document.getElementById(id);
1879
+ }
1880
+ function applyPageBackground(sheet, decls) {
1881
+ for (const [prop, value] of Object.entries(decls)) {
1882
+ const p = prop.toLowerCase();
1883
+ if (p !== "background" && !p.startsWith("background-"))
1884
+ continue;
1885
+ if (p === "background-attachment")
1886
+ continue;
1887
+ sheet.style.setProperty(p, value);
1888
+ }
1889
+ }
1890
+ function decorate(layout, opts = {}) {
1891
+ const model = layout.model;
1892
+ const sheets = new Map;
1893
+ let blankPages = new Set;
1894
+ const warnings = [];
1895
+ const api = {
1896
+ redraw: () => draw(),
1897
+ sheetFor: (p) => sheets.get(p),
1898
+ stringMap: new Map,
1899
+ targets: new Map,
1900
+ pageNumbers: [],
1901
+ warnings,
1902
+ setDesigner(on) {
1903
+ document.body.dataset.designer = on ? "on" : "off";
1904
+ }
1905
+ };
1906
+ const canvasBg = captureCanvasBackground();
1907
+ document.body.classList.add("gp-stage");
1908
+ if (document.body.dataset.designer === undefined)
1909
+ api.setDesigner(!!opts.designer);
1910
+ function pageContext(strip, indexInStrip2, bookIndex) {
1911
+ if (blankPages.has(bookIndex)) {
1912
+ const pseudos2 = ["blank"];
1913
+ const { geometry: geometry2, marginBoxes: marginBoxes2, decls: decls2 } = resolvePage(model, { pseudos: pseudos2 });
1914
+ return { index: bookIndex, strip, pseudos: pseudos2, geometry: geometry2, marginBoxes: marginBoxes2, decls: decls2 };
1915
+ }
1916
+ const pseudos = [];
1917
+ if (bookIndex === 0)
1918
+ pseudos.push("first");
1919
+ if (indexInStrip2 === 0)
1920
+ pseudos.push("nth-first-of-run");
1921
+ pseudos.push(bookIndex % 2 === 0 ? "right" : "left");
1922
+ const { geometry, marginBoxes, decls } = resolvePage(model, {
1923
+ name: strip.page,
1924
+ pseudos
1925
+ });
1926
+ return { index: bookIndex, strip, pseudos, geometry, marginBoxes, decls };
1927
+ }
1928
+ function buildMaps() {
1929
+ api.stringMap = new Map;
1930
+ api.targets = new Map;
1931
+ for (const decl of model.stringSets) {
1932
+ let els = [];
1933
+ try {
1934
+ els = Array.from(document.querySelectorAll(decl.selector));
1935
+ } catch {
1936
+ continue;
1937
+ }
1938
+ const entries = api.stringMap.get(decl.name) ?? [];
1939
+ for (const el of els) {
1940
+ const [page] = pageRangeOf(el, layout.strips);
1941
+ if (page < 0)
1942
+ continue;
1943
+ entries.push({
1944
+ page,
1945
+ value: evaluate(decl.value, {
1946
+ text: (el.textContent ?? "").trim(),
1947
+ attr: (n) => el.getAttribute(n) ?? undefined
1948
+ })
1949
+ });
1950
+ }
1951
+ entries.sort((a, b) => a.page - b.page);
1952
+ api.stringMap.set(decl.name, entries);
1953
+ }
1954
+ const resets = [];
1955
+ for (const r of model.counterResets) {
1956
+ let els = [];
1957
+ try {
1958
+ els = Array.from(document.querySelectorAll(r.selector));
1959
+ } catch {
1960
+ continue;
1961
+ }
1962
+ for (const el of els) {
1963
+ const [page] = pageRangeOf(el, layout.strips);
1964
+ if (page >= 0)
1965
+ resets.push({ page: page + 1, start: r.start });
1966
+ }
1967
+ }
1968
+ api.pageNumbers = resets.length ? pageCounterValues(resets, layout.totalPages) : [];
1969
+ const pageValues = api.pageNumbers.length ? api.pageNumbers : null;
1970
+ const linked = new Set;
1971
+ for (const a of Array.from(document.querySelectorAll("a[href^='#']")))
1972
+ linked.add(a.getAttribute("href"));
1973
+ for (const href of linked) {
1974
+ const el = elementForHref(href);
1975
+ if (!el)
1976
+ continue;
1977
+ const [page] = pageRangeOf(el, layout.strips);
1978
+ if (page >= 0)
1979
+ api.targets.set(href, toFolioPage(page + 1, pageValues));
1980
+ }
1981
+ }
1982
+ function stringAt(name, which, page) {
1983
+ return stringValueAt(api.stringMap.get(name) ?? [], page, parseWhich(which));
1984
+ }
1985
+ function fillXrefs() {
1986
+ const brokenHrefs = new Set;
1987
+ for (const xref of model.xrefs) {
1988
+ if (!needsMeasurement(xref.content))
1989
+ continue;
1990
+ const base = xref.selector.replace(/::?(after|before)$/, "");
1991
+ const pseudo = /::?after$/.test(xref.selector) ? "after" : "before";
1992
+ let els = [];
1993
+ try {
1994
+ els = Array.from(document.querySelectorAll(base));
1995
+ } catch {
1996
+ continue;
1997
+ }
1998
+ for (const el of els) {
1999
+ const text = evaluate(xref.content, {
2000
+ attr: (n) => el.getAttribute(n) ?? undefined,
2001
+ targetPage: (url) => {
2002
+ const page = api.targets.get(url);
2003
+ if (page === undefined && url.startsWith("#"))
2004
+ brokenHrefs.add(url);
2005
+ return page;
2006
+ },
2007
+ targetText: (url) => (elementForHref(url)?.textContent ?? "").trim() || undefined,
2008
+ leader: leaderMarker
2009
+ });
2010
+ el.setAttribute(`data-gp-${pseudo}`, text);
2011
+ }
2012
+ }
2013
+ for (const href of brokenHrefs)
2014
+ warnings.push(`The link "${href}" doesn't point at anything in this book, so its page number can't be shown. Check the spelling, or add that id to the heading you meant.`);
2015
+ fillLeaders();
2016
+ let style = document.getElementById("gp-xref-style");
2017
+ if (!style) {
2018
+ style = document.createElement("style");
2019
+ style.id = "gp-xref-style";
2020
+ document.head.appendChild(style);
2021
+ }
2022
+ style.textContent = generatedContentCss(model.xrefs.map((x) => x.selector));
2023
+ }
2024
+ function fillLeaders() {
2025
+ const marked = [];
2026
+ for (const attr of ["data-gp-after", "data-gp-before"]) {
2027
+ for (const el of Array.from(document.querySelectorAll(`[${attr}]`))) {
2028
+ const raw = el.getAttribute(attr) ?? "";
2029
+ if (LEADER_RE.test(raw))
2030
+ marked.push({ el, attr, raw });
2031
+ }
2032
+ }
2033
+ if (!marked.length)
2034
+ return;
2035
+ const canvas = document.createElement("canvas");
2036
+ const cx = canvas.getContext("2d");
2037
+ for (const m of marked)
2038
+ m.el.setAttribute(m.attr, m.raw.replace(LEADER_RE, ""));
2039
+ document.body.offsetHeight;
2040
+ for (const m of marked) {
2041
+ const glue = LEADER_RE.exec(m.raw)[1] || ".";
2042
+ const block = m.el.parentElement ?? document.body;
2043
+ const blockRect = block.getBoundingClientRect();
2044
+ const cs = getComputedStyle(block);
2045
+ const contentRight = blockRect.right - parseFloat(cs.paddingRight) - parseFloat(cs.borderRightWidth);
2046
+ const rects = m.el.getClientRects();
2047
+ const last = rects.length ? rects[rects.length - 1] : m.el.getBoundingClientRect();
2048
+ cx.font = getComputedStyle(m.el).font;
2049
+ const n = leaderFillCount(contentRight - last.right, cx.measureText(glue).width);
2050
+ m.el.setAttribute(m.attr, m.raw.replace(LEADER_RE, glue.repeat(n)));
2051
+ }
2052
+ }
2053
+ function draw() {
2054
+ sheets.clear();
2055
+ warnings.length = 0;
2056
+ blankPages = new Set(layout.blankPageIndices);
2057
+ buildMaps();
2058
+ fillXrefs();
2059
+ let prevPageH = 0;
2060
+ let first = true;
2061
+ for (const strip of layout.strips) {
2062
+ const run = ensureRun(strip);
2063
+ const { stride, rowStride } = stripMetrics(strip.el);
2064
+ const sheetGap = parseFloat(getComputedStyle(strip.el).getPropertyValue("--gp-sheet-gap")) || 0;
2065
+ run.style.setProperty("--gp-sheet-gap", `${sheetGap}px`);
2066
+ const { perRow, shift } = wrapGeometry(strip);
2067
+ const layer = run.querySelector(".gp-layer");
2068
+ layer.textContent = "";
2069
+ const g = strip.geometry;
2070
+ for (let i = 0;i < strip.pages; i++) {
2071
+ const bookIndex = strip.offset + i;
2072
+ const ctx = pageContext(strip, i, bookIndex);
2073
+ const slot = i + shift;
2074
+ const row = Math.floor(slot / perRow);
2075
+ const colVisual = slot % perRow;
2076
+ const sheetLeft = colVisual * stride;
2077
+ const sheetTop = row * rowStride;
2078
+ const sheet = document.createElement("div");
2079
+ sheet.className = "gp-sheet";
2080
+ sheet.dataset.page = String(bookIndex + 1);
2081
+ sheet.dataset.side = bookIndex % 2 === 0 ? "recto" : "verso";
2082
+ sheet.style.left = `${sheetLeft}px`;
2083
+ sheet.style.top = `${sheetTop}px`;
2084
+ sheet.style.setProperty("--gp-page-w", px(ctx.geometry.width));
2085
+ sheet.style.setProperty("--gp-page-h", px(ctx.geometry.height));
2086
+ applyPageBackground(sheet, ctx.decls);
2087
+ for (const [prop, value] of canvasBg)
2088
+ sheet.style.setProperty(prop, value);
2089
+ layer.appendChild(sheet);
2090
+ sheets.set(bookIndex, sheet);
2091
+ drawMarginBoxes(sheet, ctx, layout.totalPages);
2092
+ drawGuides(sheet, ctx);
2093
+ drawCropMarks(sheet, ctx);
2094
+ }
2095
+ const rows = Math.max(1, Math.ceil((strip.pages + shift) / perRow));
2096
+ run.style.height = `${rowStride * (rows - 1) + PX_PER_PT * g.height}px`;
2097
+ run.style.width = `${stride * perRow}px`;
2098
+ run.style.marginTop = strip.wrapCols && shift === 1 && !first ? `${-(prevPageH + sheetGap)}px` : "";
2099
+ prevPageH = PX_PER_PT * g.height;
2100
+ first = false;
2101
+ if (opts.designer)
2102
+ checkOverflow(strip, warnings);
2103
+ }
2104
+ }
2105
+ function drawMarginBoxes(sheet, ctx, totalPages) {
2106
+ const g = ctx.geometry;
2107
+ for (const name of MARGIN_BOX_NAMES) {
2108
+ const decls = ctx.marginBoxes[`@${name}`];
2109
+ if (!decls?.content)
2110
+ continue;
2111
+ const text = evaluate(decls.content, {
2112
+ page: api.pageNumbers[ctx.index] ?? ctx.index + 1,
2113
+ pages: totalPages,
2114
+ strings: (n, w) => stringAt(n, w, ctx.index),
2115
+ targetPage: (url) => api.targets.get(url)
2116
+ });
2117
+ if (!text)
2118
+ continue;
2119
+ const box = document.createElement("div");
2120
+ box.className = "gp-marginbox";
2121
+ box.dataset.box = name;
2122
+ Object.assign(box.style, rectFor(name, g));
2123
+ box.dataset.align = name.includes("center") || name.includes("middle") ? "center" : /right/.test(name) ? "end" : "start";
2124
+ const content = document.createElement("span");
2125
+ content.className = "gp-marginbox-content";
2126
+ content.textContent = text;
2127
+ for (const [prop, value] of Object.entries(decls)) {
2128
+ if (prop.toLowerCase() === "content" || isIgnoredMarginBoxProperty(prop))
2129
+ continue;
2130
+ content.style.setProperty(prop, value);
2131
+ }
2132
+ box.appendChild(content);
2133
+ sheet.appendChild(box);
2134
+ }
2135
+ }
2136
+ const CROP_LEN = 14;
2137
+ const CROP_GAP = 3;
2138
+ function drawCropMarks(sheet, ctx) {
2139
+ const g = ctx.geometry;
2140
+ if (g.bleed <= 0)
2141
+ return;
2142
+ const w = g.width * PX_PER_PT;
2143
+ const h = g.height * PX_PER_PT;
2144
+ const mark = (left, top, width, height) => {
2145
+ const el = document.createElement("div");
2146
+ el.className = "gp-crop-mark";
2147
+ Object.assign(el.style, { left: `${left}px`, top: `${top}px`, width: `${width}px`, height: `${height}px` });
2148
+ sheet.appendChild(el);
2149
+ };
2150
+ for (const [cx, cy] of [
2151
+ [0, 0],
2152
+ [w, 0],
2153
+ [0, h],
2154
+ [w, h]
2155
+ ]) {
2156
+ const ox = cx === 0 ? -1 : 1;
2157
+ const oy = cy === 0 ? -1 : 1;
2158
+ mark(cx + (ox < 0 ? -(CROP_GAP + CROP_LEN) : CROP_GAP), cy - 0.5, CROP_LEN, 1);
2159
+ mark(cx - 0.5, cy + (oy < 0 ? -(CROP_GAP + CROP_LEN) : CROP_GAP), 1, CROP_LEN);
2160
+ }
2161
+ }
2162
+ function drawGuides(sheet, ctx) {
2163
+ const g = ctx.geometry;
2164
+ if (g.bleed > 0) {
2165
+ const trim = document.createElement("div");
2166
+ trim.className = "gp-guide-trim";
2167
+ Object.assign(trim.style, {
2168
+ left: "0px",
2169
+ top: "0px",
2170
+ width: px(g.width),
2171
+ height: px(g.height)
2172
+ });
2173
+ sheet.appendChild(trim);
2174
+ }
2175
+ const safe = document.createElement("div");
2176
+ safe.className = "gp-guide-safe";
2177
+ Object.assign(safe.style, {
2178
+ left: px(g.margin.left),
2179
+ top: px(g.margin.top),
2180
+ width: px(g.width - g.margin.left - g.margin.right),
2181
+ height: px(g.height - g.margin.top - g.margin.bottom)
2182
+ });
2183
+ sheet.appendChild(safe);
2184
+ }
2185
+ function checkOverflow(strip, out) {
2186
+ const h = strip.el.clientHeight;
2187
+ for (const el of Array.from(strip.el.querySelectorAll("figure,table,img,pre"))) {
2188
+ const r = el.getBoundingClientRect();
2189
+ if (r.height > h + 1) {
2190
+ out.push(`${el.tagName.toLowerCase()}${el.id ? "#" + el.id : ""} is ${Math.round(r.height)}px tall; page content box is ${Math.round(h)}px — it will clip on screen and overflow in print.`);
2191
+ el.classList.add("gp-overflowing");
2192
+ }
2193
+ }
2194
+ }
2195
+ draw();
2196
+ return api;
2197
+ }
2198
+ var CANVAS_BG_PROPS = [
2199
+ "background-color",
2200
+ "background-image",
2201
+ "background-repeat",
2202
+ "background-position",
2203
+ "background-size",
2204
+ "background-origin",
2205
+ "background-clip",
2206
+ "background-blend-mode"
2207
+ ];
2208
+ function captureCanvasBackground() {
2209
+ for (const el of [document.documentElement, document.body]) {
2210
+ const cs = getComputedStyle(el);
2211
+ const transparent = /^(transparent|rgba\(0, ?0, ?0, ?0\))$/.test(cs.backgroundColor);
2212
+ if (cs.backgroundImage === "none" && transparent)
2213
+ continue;
2214
+ const captured = CANVAS_BG_PROPS.map((p) => [p, cs.getPropertyValue(p)]);
2215
+ if (el === document.documentElement)
2216
+ el.style.background = "none";
2217
+ return captured;
2218
+ }
2219
+ return [];
2220
+ }
2221
+ function ensureRun(strip) {
2222
+ const parent = strip.el.parentElement;
2223
+ if (parent.classList.contains("gp-run"))
2224
+ return parent;
2225
+ const run = document.createElement("div");
2226
+ run.className = "gp-run";
2227
+ strip.el.before(run);
2228
+ run.appendChild(strip.el);
2229
+ const layer = document.createElement("div");
2230
+ layer.className = "gp-layer";
2231
+ run.insertBefore(layer, strip.el);
2232
+ return run;
2233
+ }
2234
+ function rectFor(name, g) {
2235
+ const { top, right, bottom, left } = g.margin;
2236
+ const cw = g.width - left - right;
2237
+ const ch = g.height - top - bottom;
2238
+ const third = (n) => n / 3;
2239
+ const T = {
2240
+ "top-left-corner": [0, 0, left, top],
2241
+ "top-left": [left, 0, third(cw), top],
2242
+ "top-center": [left + third(cw), 0, third(cw), top],
2243
+ "top-right": [left + 2 * third(cw), 0, third(cw), top],
2244
+ "top-right-corner": [g.width - right, 0, right, top],
2245
+ "bottom-left-corner": [0, g.height - bottom, left, bottom],
2246
+ "bottom-left": [left, g.height - bottom, third(cw), bottom],
2247
+ "bottom-center": [left + third(cw), g.height - bottom, third(cw), bottom],
2248
+ "bottom-right": [left + 2 * third(cw), g.height - bottom, third(cw), bottom],
2249
+ "bottom-right-corner": [g.width - right, g.height - bottom, right, bottom],
2250
+ "left-top": [0, top, left, third(ch)],
2251
+ "left-middle": [0, top + third(ch), left, third(ch)],
2252
+ "left-bottom": [0, top + 2 * third(ch), left, third(ch)],
2253
+ "right-top": [g.width - right, top, right, third(ch)],
2254
+ "right-middle": [g.width - right, top + third(ch), right, third(ch)],
2255
+ "right-bottom": [g.width - right, top + 2 * third(ch), right, third(ch)]
2256
+ };
2257
+ const [x, y, w, h] = T[name] ?? [0, 0, 0, 0];
2258
+ return { left: px(x), top: px(y), width: px(w), height: px(h) };
2259
+ }
2260
+
2261
+ // src/engine/viewer/index.ts
2262
+ var resizeListener;
2263
+ async function mount(opts = {}) {
2264
+ const t0 = performance.now();
2265
+ const layout = await fragmentDocument(opts);
2266
+ applySpreadMode(layout.strips, false);
2267
+ const decoration = decorate(layout, { designer: opts.designer });
2268
+ let spreadOn = false;
2269
+ const api = Object.assign(layout, {
2270
+ decoration,
2271
+ goto(page) {
2272
+ const clamped = Math.max(1, Math.min(layout.totalPages, Math.round(page)));
2273
+ current = clamped - 1;
2274
+ const target = decoration.sheetFor(current);
2275
+ target?.scrollIntoView({ block: "start", inline: "center" });
2276
+ emit();
2277
+ },
2278
+ next: () => api.goto(api.currentPage() + 1),
2279
+ prev: () => api.goto(api.currentPage() - 1),
2280
+ currentPage: () => current + 1,
2281
+ refresh() {
2282
+ layout.relayout();
2283
+ applySpreadMode(layout.strips, spreadOn);
2284
+ decoration.redraw();
2285
+ emit();
2286
+ },
2287
+ setSpread(on) {
2288
+ spreadOn = on;
2289
+ applySpreadMode(layout.strips, spreadOn);
2290
+ decoration.redraw();
2291
+ emit();
2292
+ }
2293
+ });
2294
+ let current = 0;
2295
+ const emit = () => {
2296
+ const detail = { page: current, pagecount: layout.totalPages };
2297
+ window.dispatchEvent(new CustomEvent("gp:page", { detail }));
2298
+ if (window.parent !== window)
2299
+ window.parent.postMessage({ gp: detail }, "*");
2300
+ };
2301
+ const ns = window.Gutterpress;
2302
+ if (ns) {
2303
+ for (const k of Object.keys(ns))
2304
+ if (!(k in api))
2305
+ api[k] = ns[k];
2306
+ }
2307
+ window.Gutterpress = api;
2308
+ emit();
2309
+ window.dispatchEvent(new CustomEvent("gp:layout", {
2310
+ detail: { ms: performance.now() - t0, pages: layout.totalPages }
2311
+ }));
2312
+ fitZoom();
2313
+ if (resizeListener)
2314
+ window.removeEventListener("resize", resizeListener);
2315
+ resizeListener = fitZoom;
2316
+ window.addEventListener("resize", resizeListener);
2317
+ return api;
2318
+ }
2319
+ function fitZoom() {
2320
+ if (document.documentElement.style.getPropertyValue("--gutterpress-zoom")) {
2321
+ document.body.style.removeProperty("--gutterpress-fit-zoom");
2322
+ return;
2323
+ }
2324
+ const sheet = document.querySelector(".gp-sheet");
2325
+ if (!sheet)
2326
+ return;
2327
+ const pageW = parseFloat(sheet.style.getPropertyValue("--gp-page-w"));
2328
+ if (!pageW)
2329
+ return;
2330
+ const stagePadding = parseFloat(getComputedStyle(document.body).paddingLeft) + parseFloat(getComputedStyle(document.body).paddingRight);
2331
+ const available = window.innerWidth - stagePadding;
2332
+ if (available > 0 && available < pageW)
2333
+ document.body.style.setProperty("--gutterpress-fit-zoom", String(available / pageW));
2334
+ else
2335
+ document.body.style.removeProperty("--gutterpress-fit-zoom");
2336
+ }
2337
+ if (typeof document !== "undefined" && !window.__GP_MANUAL__) {
2338
+ const params = new URLSearchParams(location.search);
2339
+ const start = () => mount({ designer: params.has("designer") });
2340
+ if (document.readyState === "loading")
2341
+ document.addEventListener("DOMContentLoaded", start, { once: true });
2342
+ else
2343
+ start();
2344
+ }
2345
+
2346
+ // src/engine/viewer/global.ts
2347
+ var api = { mount, decorate, ...exports_fragment, gcpm: exports_gcpm_extract, content: exports_content_value };
2348
+ window.Gutterpress = api;
2349
+ })();