gutterpress 0.10.0-alpha.4 → 0.10.0
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/README.md +2 -0
- package/dist/api/index.js +2 -2
- package/dist/{audit-k1vnfwvc.js → audit-cvarpa72.js} +4 -4
- package/dist/{build-hqmwgvdw.js → build-6r502chw.js} +6 -4
- package/dist/{cli-0r0tq16s.js → cli-149edp6b.js} +1 -1
- package/dist/{cli-ra0ed2xt.js → cli-cqtggsng.js} +1 -1
- package/dist/{cli-n25qycwz.js → cli-eq5naw4m.js} +222 -141
- package/dist/{cli-revgt4pr.js → cli-yzf38679.js} +1 -0
- package/dist/{cli-hp9r2pzt.js → cli-zfcryxg8.js} +327 -24
- package/dist/cli.js +16 -16
- package/dist/{doctor-dxms7ehm.js → doctor-akvxbtjb.js} +2 -2
- package/dist/engine/compiler/build.d.ts +1 -1
- package/dist/engine/shared/flush.d.ts +59 -0
- package/dist/engine/shared/margin-box-support.d.ts +24 -0
- package/dist/{engine-z4p9sr4h.js → engine-b159tbns.js} +2 -2
- package/dist/{engine-wa7y9av9.js → engine-ft4cr3ep.js} +1 -1
- package/dist/{gutterpress-agent-1ctgfz92.js → gutterpress-agent-cazqstr1.js} +49 -1
- package/dist/{gutterpress-viewer-cem7dmr5.js → gutterpress-viewer-te8g5grx.js} +191 -71
- package/dist/{index-05y3dnxq.js → index-ge7q9xj3.js} +330 -249
- package/dist/{index-xxg4zfrg.js → index-wq3r5pj7.js} +287 -23
- package/dist/{index-9tyq9kks.js → index-ycpvr0am.js} +47 -4
- package/dist/index.js +3 -3
- package/dist/lib/build-runner.d.ts +7 -0
- package/dist/lib/build-staging.d.ts +51 -0
- package/dist/lib/engine.d.ts +9 -0
- package/dist/lib/markdown/gutterpress-css.d.ts +9 -1
- package/dist/lib/markdown/markers.d.ts +51 -1
- package/dist/{lint-xjwm5ep8.js → lint-p2sw53d9.js} +4 -4
- package/dist/{new-kwdwpf0j.js → new-hvq0x91q.js} +4 -4
- package/dist/{plugin-rg4tnn96.js → plugin-pssmk0dx.js} +4 -4
- package/dist/{preflight-3127y25z.js → preflight-4j00yd0g.js} +4 -4
- package/dist/{preview-ncgfhqmw.js → preview-d4s6gk6p.js} +6 -5
- package/dist/{preview-interface-435cczt5.js → preview-interface-0ssk8bmm.js} +153 -6
- package/dist/{publish-pr0rwh6p.js → publish-tkc26en7.js} +4 -4
- package/dist/render.js +83 -7
- package/dist/{repair-8270smfw.js → repair-2va4w12t.js} +4 -4
- package/dist/{validate-54e17rae.js → validate-w9vfefrw.js} +4 -4
- package/package.json +1 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
build
|
|
3
|
-
} from "./index-
|
|
3
|
+
} from "./index-wq3r5pj7.js";
|
|
4
4
|
import {
|
|
5
5
|
BuildError,
|
|
6
6
|
RENDER_TIMEOUT_MS,
|
|
7
7
|
assertMilestone,
|
|
8
8
|
connectChromium,
|
|
9
9
|
getBrowser
|
|
10
|
-
} from "./index-
|
|
10
|
+
} from "./index-ycpvr0am.js";
|
|
11
11
|
import"./index-37x76zdn.js";
|
|
12
12
|
|
|
13
13
|
// src/lib/engine.ts
|
|
@@ -44,8 +44,10 @@
|
|
|
44
44
|
targetTexts: () => targetTexts,
|
|
45
45
|
stringSources: () => stringSources,
|
|
46
46
|
setGenerated: () => setGenerated,
|
|
47
|
+
setFlushFurniture: () => setFlushFurniture,
|
|
47
48
|
instrument: () => instrument,
|
|
48
49
|
forcedBreakSites: () => forcedBreakSites,
|
|
50
|
+
flushRoots: () => flushRoots,
|
|
49
51
|
fillLeaders: () => fillLeaders,
|
|
50
52
|
counterResetSites: () => counterResetSites,
|
|
51
53
|
collectCss: () => collectCss,
|
|
@@ -557,6 +559,50 @@
|
|
|
557
559
|
addCss("gp-generated-content", css);
|
|
558
560
|
return entries.length;
|
|
559
561
|
}
|
|
562
|
+
function flushRoots() {
|
|
563
|
+
const out = [];
|
|
564
|
+
for (const root of Array.from(document.querySelectorAll(".page, .spread"))) {
|
|
565
|
+
const edges = ["top", "right", "bottom", "left"].filter((edge) => root.querySelector(`.gp-pin.gp-flush.gp-${edge}`));
|
|
566
|
+
if (!edges.length)
|
|
567
|
+
continue;
|
|
568
|
+
const page = getComputedStyle(root).page;
|
|
569
|
+
const key = `${page === "auto" ? "" : page}|${edges.map((e) => e[0]).join("")}`;
|
|
570
|
+
root.dataset.gpFlush = key;
|
|
571
|
+
out.push({ id: ensureAnchor(root), page, edges, key });
|
|
572
|
+
}
|
|
573
|
+
return out;
|
|
574
|
+
}
|
|
575
|
+
function setFlushFurniture(items) {
|
|
576
|
+
let painted = 0;
|
|
577
|
+
for (const item of items) {
|
|
578
|
+
const host = anchorHost(item.id);
|
|
579
|
+
if (!host)
|
|
580
|
+
continue;
|
|
581
|
+
let layer = host.querySelector(":scope > .gp-flush-furniture");
|
|
582
|
+
if (!layer) {
|
|
583
|
+
layer = document.createElement("div");
|
|
584
|
+
layer.className = "gp-flush-furniture";
|
|
585
|
+
layer.setAttribute("aria-hidden", "true");
|
|
586
|
+
layer.setAttribute("style", "position:absolute;inset:0;pointer-events:none;z-index:10;");
|
|
587
|
+
host.appendChild(layer);
|
|
588
|
+
}
|
|
589
|
+
layer.textContent = "";
|
|
590
|
+
for (const b of item.boxes) {
|
|
591
|
+
const slot = document.createElement("div");
|
|
592
|
+
slot.dataset.box = b.box;
|
|
593
|
+
const justify = b.align === "center" ? "center" : b.align === "end" ? "flex-end" : "flex-start";
|
|
594
|
+
slot.setAttribute("style", `position:absolute;left:${b.x}px;top:${b.y}px;width:${b.w}px;height:${b.h}px;` + `display:flex;align-items:center;justify-content:${justify};overflow:hidden;white-space:pre;`);
|
|
595
|
+
const content = document.createElement("span");
|
|
596
|
+
content.textContent = b.text;
|
|
597
|
+
for (const [prop, value] of Object.entries(b.decls))
|
|
598
|
+
content.style.setProperty(prop, value);
|
|
599
|
+
slot.appendChild(content);
|
|
600
|
+
layer.appendChild(slot);
|
|
601
|
+
painted++;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return painted;
|
|
605
|
+
}
|
|
560
606
|
var api = {
|
|
561
607
|
auditContent,
|
|
562
608
|
collectCss,
|
|
@@ -569,7 +615,9 @@
|
|
|
569
615
|
fillLeaders,
|
|
570
616
|
instrument,
|
|
571
617
|
addCss,
|
|
572
|
-
setGenerated
|
|
618
|
+
setGenerated,
|
|
619
|
+
flushRoots,
|
|
620
|
+
setFlushFurniture
|
|
573
621
|
};
|
|
574
622
|
if (typeof window !== "undefined")
|
|
575
623
|
window.__gp = api;
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
strideOf: () => strideOf,
|
|
25
25
|
stabilizeFullHeightPageRoots: () => stabilizeFullHeightPageRoots,
|
|
26
26
|
spreadModeSupported: () => spreadModeSupported,
|
|
27
|
+
runPageBox: () => runPageBox,
|
|
27
28
|
rowStrideOf: () => rowStrideOf,
|
|
28
29
|
pageRangeOf: () => pageRangeOf,
|
|
29
30
|
pageOf: () => pageOf,
|
|
@@ -33,9 +34,11 @@
|
|
|
33
34
|
injectBreakMapping: () => injectBreakMapping,
|
|
34
35
|
fragmentDocument: () => fragmentDocument,
|
|
35
36
|
forcedColumnBreaksSupported: () => forcedColumnBreaksSupported,
|
|
37
|
+
contentEdgeRect: () => contentEdgeRect,
|
|
36
38
|
compensateTrailingMarginsBeforeAvoids: () => compensateTrailingMarginsBeforeAvoids,
|
|
37
39
|
compensateRepeatedHeaders: () => compensateRepeatedHeaders,
|
|
38
40
|
compensateRectoBreaks: () => compensateRectoBreaks,
|
|
41
|
+
columnReserve: () => columnReserve,
|
|
39
42
|
collectCssText: () => collectCssText,
|
|
40
43
|
buildStrips: () => buildStrips,
|
|
41
44
|
blankPageIndices: () => blankPageIndices,
|
|
@@ -94,11 +97,23 @@
|
|
|
94
97
|
transform: translate(var(--gp-margin-left), var(--gp-margin-top));
|
|
95
98
|
}
|
|
96
99
|
|
|
97
|
-
/*
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
/* NOTE: there is deliberately no \`.gp-strip > * { break-inside: auto }\` here.
|
|
101
|
+
The original spike shipped one, under a comment about sheet transparency that
|
|
102
|
+
described no rule of its own, and it was never justified anywhere. Because
|
|
103
|
+
viewer chrome is injected after the author's stylesheet, it beat every
|
|
104
|
+
single-class author rule at equal specificity: a top-level block the author
|
|
105
|
+
declared atomic (\`.card\`, \`.section\`, a stat block) split on screen while the
|
|
106
|
+
PDF moved it whole and left a short page. Measured on
|
|
107
|
+
\`docs/fixtures/atomic-blocks\`: print 6pp, viewer 5pp with the rule, 6pp and
|
|
108
|
+
6pp without it; a longer probe of the same shape ran 25pp against 21pp.
|
|
109
|
+
Chromium honours \`break-inside: avoid\` in multicol as it does in paged media,
|
|
110
|
+
including relaxing it for a block too tall to fit an empty fragmentainer, so
|
|
111
|
+
nothing here needs to override the author.
|
|
112
|
+
|
|
113
|
+
Scope: \`>\` means this only ever reached the flow root's OWN children, so a
|
|
114
|
+
book whose atomic blocks sit inside \`@page\`/\`@section\` wrappers was never
|
|
115
|
+
affected — which is why removing it does not move the dc-op-manual field
|
|
116
|
+
guide's remaining 4-page divergence. That one is still open. */
|
|
102
117
|
|
|
103
118
|
/* View mode (\`applySpreadMode\` in fragment.ts): wraps this run's multicol
|
|
104
119
|
columns into \`--gp-wrap-cols\` ROWS instead of one long row — 2 for
|
|
@@ -148,6 +163,23 @@
|
|
|
148
163
|
.gp-run {
|
|
149
164
|
position: relative;
|
|
150
165
|
margin: 0 0 var(--gp-sheet-gap);
|
|
166
|
+
/* Hit-transparent, like \`.gp-layer\` below: in wrap mode a recto-starting
|
|
167
|
+
run is pulled up over the previous run's last row (decorate.ts's
|
|
168
|
+
cross-run negative margin-top — the spread-composition overlap), and
|
|
169
|
+
its leading \`.gp-wrap-spacer\` slot is visually empty. The covered page
|
|
170
|
+
paints through, but the run's full-width box used to win
|
|
171
|
+
elementFromPoint there, so clicks, right-clicks, link hits, and text
|
|
172
|
+
selection were all dead on every covered page (measured: 60/60 dead
|
|
173
|
+
probe points in two-column view). Runs are viewer chrome — only author
|
|
174
|
+
content may capture hits. */
|
|
175
|
+
pointer-events: none;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* …and the author's content re-enables hit testing. Equal specificity with
|
|
179
|
+
\`.gp-run { pointer-events: none }\` above, so ORDER is load-bearing:
|
|
180
|
+
\`auto\` must come after \`none\`. */
|
|
181
|
+
.gp-strip > * {
|
|
182
|
+
pointer-events: auto;
|
|
151
183
|
}
|
|
152
184
|
|
|
153
185
|
.gp-layer {
|
|
@@ -957,6 +989,20 @@
|
|
|
957
989
|
`);
|
|
958
990
|
}
|
|
959
991
|
|
|
992
|
+
// src/engine/shared/flush.ts
|
|
993
|
+
var FLUSH_EDGES = ["top", "right", "bottom", "left"];
|
|
994
|
+
function flushEdgesIn(root) {
|
|
995
|
+
return FLUSH_EDGES.filter((edge) => root.querySelector(`.gp-pin.gp-flush.gp-${edge}`));
|
|
996
|
+
}
|
|
997
|
+
function flushMargins(margin, edges) {
|
|
998
|
+
return {
|
|
999
|
+
top: edges.includes("top") ? 0 : margin.top,
|
|
1000
|
+
right: edges.includes("right") ? 0 : margin.right,
|
|
1001
|
+
bottom: edges.includes("bottom") ? 0 : margin.bottom,
|
|
1002
|
+
left: edges.includes("left") ? 0 : margin.left
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
|
|
960
1006
|
// src/engine/viewer/fragment.ts
|
|
961
1007
|
var PX_PER_PT = 96 / 72;
|
|
962
1008
|
function injectViewerCss(doc = document) {
|
|
@@ -1005,6 +1051,21 @@
|
|
|
1005
1051
|
function forcedColumnBreaksSupported() {
|
|
1006
1052
|
return typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("break-before", "column") && CSS.supports("break-after", "column");
|
|
1007
1053
|
}
|
|
1054
|
+
var FRAGMENT_EPSILON_PX = 1;
|
|
1055
|
+
function contentEdgeRect(site, atEnd) {
|
|
1056
|
+
const rects = Array.from(site.getClientRects());
|
|
1057
|
+
const solid = rects.filter((r) => r.height >= FRAGMENT_EPSILON_PX);
|
|
1058
|
+
const pool = solid.length ? solid : rects;
|
|
1059
|
+
return atEnd ? pool.at(-1) : pool[0];
|
|
1060
|
+
}
|
|
1061
|
+
function columnReserve(offset, columnHeight) {
|
|
1062
|
+
const remaining = columnHeight - offset;
|
|
1063
|
+
if (remaining < FRAGMENT_EPSILON_PX)
|
|
1064
|
+
return null;
|
|
1065
|
+
if (remaining > columnHeight - FRAGMENT_EPSILON_PX)
|
|
1066
|
+
return null;
|
|
1067
|
+
return Math.ceil(remaining);
|
|
1068
|
+
}
|
|
1008
1069
|
function synthesizeColumnBreaks(model) {
|
|
1009
1070
|
const sites = [];
|
|
1010
1071
|
const seen = new WeakMap;
|
|
@@ -1041,23 +1102,22 @@
|
|
|
1041
1102
|
const strip = el.closest(".gp-strip");
|
|
1042
1103
|
if (!strip)
|
|
1043
1104
|
continue;
|
|
1044
|
-
|
|
1045
|
-
|
|
1105
|
+
let site = el;
|
|
1106
|
+
if (prop === "break-before") {
|
|
1107
|
+
const target = propagatedBreakTarget(el, strip);
|
|
1108
|
+
if (!target)
|
|
1109
|
+
continue;
|
|
1110
|
+
site = target;
|
|
1111
|
+
}
|
|
1046
1112
|
if (prop === "break-after" && !el.nextElementSibling)
|
|
1047
1113
|
continue;
|
|
1048
|
-
const
|
|
1049
|
-
const rect = prop === "break-after" ? rects.at(-1) : rects[0];
|
|
1114
|
+
const rect = contentEdgeRect(site, prop === "break-after");
|
|
1050
1115
|
if (!rect)
|
|
1051
1116
|
continue;
|
|
1052
1117
|
const stripTop = strip.getBoundingClientRect().top;
|
|
1053
1118
|
const edge = prop === "break-after" ? rect.bottom : rect.top;
|
|
1054
|
-
const
|
|
1055
|
-
if (
|
|
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)
|
|
1119
|
+
const reserve = columnReserve((edge - stripTop) / cssZoomOf(strip), strip.clientHeight);
|
|
1120
|
+
if (reserve === null)
|
|
1061
1121
|
continue;
|
|
1062
1122
|
const spacer = document.createElement("div");
|
|
1063
1123
|
spacer.className = "gp-column-break-spacer";
|
|
@@ -1066,8 +1126,28 @@
|
|
|
1066
1126
|
if (prop === "break-after")
|
|
1067
1127
|
el.after(spacer);
|
|
1068
1128
|
else
|
|
1069
|
-
|
|
1129
|
+
site.before(spacer);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
function propagatedBreakTarget(el, strip) {
|
|
1133
|
+
let site = el;
|
|
1134
|
+
while (!site.previousElementSibling) {
|
|
1135
|
+
const parent = site.parentElement;
|
|
1136
|
+
if (!parent || parent === strip)
|
|
1137
|
+
return null;
|
|
1138
|
+
const cs = getComputedStyle(site);
|
|
1139
|
+
if (cs.position === "absolute" || cs.position === "fixed" || cs.float !== "none")
|
|
1140
|
+
return null;
|
|
1141
|
+
for (let node = site.previousSibling;node; node = node.previousSibling) {
|
|
1142
|
+
if (node.nodeType === 3 && (node.textContent ?? "").trim() !== "")
|
|
1143
|
+
return site;
|
|
1144
|
+
}
|
|
1145
|
+
const pcs = getComputedStyle(parent);
|
|
1146
|
+
if (!/^(block|flow-root)$/.test(pcs.display) || pcs.columnCount !== "auto")
|
|
1147
|
+
return null;
|
|
1148
|
+
site = parent;
|
|
1070
1149
|
}
|
|
1150
|
+
return site;
|
|
1071
1151
|
}
|
|
1072
1152
|
function directPageName(el, model) {
|
|
1073
1153
|
for (const a of model.pageAssignments) {
|
|
@@ -1087,12 +1167,13 @@
|
|
|
1087
1167
|
}
|
|
1088
1168
|
return false;
|
|
1089
1169
|
}
|
|
1090
|
-
function pushRun(runs, page, nodes) {
|
|
1170
|
+
function pushRun(runs, page, nodes, flushEdges) {
|
|
1171
|
+
const key = (r) => `${r.page ?? ""}\x00${(r.flushEdges ?? []).join(",")}`;
|
|
1091
1172
|
const last = runs[runs.length - 1];
|
|
1092
|
-
if (last && last
|
|
1173
|
+
if (last && key(last) === key({ page, flushEdges }))
|
|
1093
1174
|
last.nodes.push(...nodes);
|
|
1094
1175
|
else
|
|
1095
|
-
runs.push({ page, nodes });
|
|
1176
|
+
runs.push({ page, nodes, flushEdges: flushEdges?.length ? flushEdges : undefined });
|
|
1096
1177
|
}
|
|
1097
1178
|
function explodeChildren(container, model) {
|
|
1098
1179
|
const runs = [];
|
|
@@ -1104,7 +1185,7 @@
|
|
|
1104
1185
|
pending = [];
|
|
1105
1186
|
const last = runs[runs.length - 1];
|
|
1106
1187
|
if (last) {
|
|
1107
|
-
pushRun(runs, last.page, held);
|
|
1188
|
+
pushRun(runs, last.page, held, last.flushEdges);
|
|
1108
1189
|
return [];
|
|
1109
1190
|
}
|
|
1110
1191
|
if (held.some((n) => (n.textContent ?? "").trim() !== "")) {
|
|
@@ -1121,13 +1202,14 @@
|
|
|
1121
1202
|
const kid = node;
|
|
1122
1203
|
if (kid.classList.contains("gp-layer"))
|
|
1123
1204
|
continue;
|
|
1205
|
+
const flush = flushEdgesIn(kid);
|
|
1124
1206
|
const own = directPageName(kid, model);
|
|
1125
1207
|
if (own !== undefined) {
|
|
1126
|
-
pushRun(runs, own, [...carry(), kid]);
|
|
1208
|
+
pushRun(runs, own, [...carry(), kid], flush);
|
|
1127
1209
|
continue;
|
|
1128
1210
|
}
|
|
1129
1211
|
if (!hasDescendantPageAssignment(kid, model)) {
|
|
1130
|
-
pushRun(runs, undefined, [...carry(), kid]);
|
|
1212
|
+
pushRun(runs, undefined, [...carry(), kid], flush);
|
|
1131
1213
|
continue;
|
|
1132
1214
|
}
|
|
1133
1215
|
const inner = explodeChildren(kid, model);
|
|
@@ -1141,7 +1223,7 @@
|
|
|
1141
1223
|
for (const n of r.nodes)
|
|
1142
1224
|
shell.appendChild(n);
|
|
1143
1225
|
kid.before(shell);
|
|
1144
|
-
pushRun(runs, r.page, [...lead, shell]);
|
|
1226
|
+
pushRun(runs, r.page, [...lead, shell], flushEdgesIn(shell));
|
|
1145
1227
|
lead = [];
|
|
1146
1228
|
}
|
|
1147
1229
|
kid.remove();
|
|
@@ -1232,19 +1314,21 @@
|
|
|
1232
1314
|
const prevRect = prevRects.at(-1);
|
|
1233
1315
|
const stripRect = stripEl.getBoundingClientRect();
|
|
1234
1316
|
const { stride } = stripMetrics(stripEl);
|
|
1235
|
-
const
|
|
1317
|
+
const zoom = cssZoomOf(stripEl);
|
|
1318
|
+
const colOf = (r) => Math.floor(((r.left - stripRect.left) / zoom + 1) / stride);
|
|
1236
1319
|
const currentCol = colOf(rect);
|
|
1237
1320
|
if (currentCol !== colOf(prevRect) + 1)
|
|
1238
1321
|
continue;
|
|
1239
|
-
if (Math.abs(rect.top - stripRect.top) > 0.5)
|
|
1322
|
+
if (Math.abs(rect.top - stripRect.top) / zoom > 0.5)
|
|
1240
1323
|
continue;
|
|
1241
1324
|
const marginEnd = parseFloat(getComputedStyle(prev).marginBlockEnd) || 0;
|
|
1242
1325
|
if (marginEnd <= 0.5)
|
|
1243
1326
|
continue;
|
|
1244
|
-
const remaining = stripEl.clientHeight - (prevRect.bottom - stripRect.top);
|
|
1245
|
-
|
|
1327
|
+
const remaining = stripEl.clientHeight - (prevRect.bottom - stripRect.top) / zoom;
|
|
1328
|
+
const height = rect.height / zoom;
|
|
1329
|
+
if (height > remaining + 0.5)
|
|
1246
1330
|
continue;
|
|
1247
|
-
if (
|
|
1331
|
+
if (height + marginEnd <= remaining + 0.5)
|
|
1248
1332
|
continue;
|
|
1249
1333
|
prev.dataset.gpTrailingMargin = "compensated";
|
|
1250
1334
|
prev.dataset.gpTrailingMarginValue = prev.style.getPropertyValue("margin-block-end");
|
|
@@ -1277,6 +1361,20 @@
|
|
|
1277
1361
|
}
|
|
1278
1362
|
}
|
|
1279
1363
|
}
|
|
1364
|
+
function runPageBox(model, name, warnings = []) {
|
|
1365
|
+
const right = resolvePage(model, { name, pseudos: ["right"] }).geometry;
|
|
1366
|
+
const left = resolvePage(model, { name, pseudos: ["left"] }).geometry;
|
|
1367
|
+
const box = (g) => ({
|
|
1368
|
+
w: g.width - g.margin.left - g.margin.right,
|
|
1369
|
+
h: g.height - g.margin.top - g.margin.bottom
|
|
1370
|
+
});
|
|
1371
|
+
const r = box(right);
|
|
1372
|
+
const l = box(left);
|
|
1373
|
+
if (Math.abs(r.w - l.w) < 0.01 && Math.abs(r.h - l.h) < 0.01)
|
|
1374
|
+
return right;
|
|
1375
|
+
warnings.push(`@page :left and @page :right give ${name ? `the "${name}" page` : "this book"} ` + `different content areas (${l.w.toFixed(1)}×${l.h.toFixed(1)}pt vs ` + `${r.w.toFixed(1)}×${r.h.toFixed(1)}pt). Print alternates between them; the preview can ` + `only show one, so page breaks here may not match the PDF. Mirror the margins instead: ` + `swap which edge is wide and keep the total the same.`);
|
|
1376
|
+
return resolvePage(model, { name }).geometry;
|
|
1377
|
+
}
|
|
1280
1378
|
function buildStrips(model, opts = {}, warnings = []) {
|
|
1281
1379
|
const doc = document;
|
|
1282
1380
|
const root = opts.root ?? doc.querySelector("main") ?? doc.body;
|
|
@@ -1284,26 +1382,34 @@
|
|
|
1284
1382
|
const runs = explodeChildren(root, model);
|
|
1285
1383
|
const strips = [];
|
|
1286
1384
|
for (const run of runs) {
|
|
1287
|
-
const
|
|
1385
|
+
const geometry = runPageBox(model, run.page, warnings);
|
|
1288
1386
|
const strip = doc.createElement("div");
|
|
1289
1387
|
strip.className = "gp-strip";
|
|
1290
1388
|
if (run.page)
|
|
1291
1389
|
strip.dataset.page = run.page;
|
|
1292
|
-
const
|
|
1293
|
-
const
|
|
1390
|
+
const margin = run.flushEdges?.length ? flushMargins(geometry.margin, run.flushEdges) : geometry.margin;
|
|
1391
|
+
const w = pt(geometry.width - margin.left - margin.right);
|
|
1392
|
+
const h = pt(geometry.height - margin.top - margin.bottom);
|
|
1294
1393
|
strip.style.setProperty("--gp-content-w", `${w}px`);
|
|
1295
1394
|
strip.style.setProperty("--gp-content-h", `${h}px`);
|
|
1296
1395
|
strip.style.setProperty("--gp-sheet-gap", `${gap}px`);
|
|
1297
1396
|
strip.style.setProperty("--gp-page-w", `${pt(geometry.width)}px`);
|
|
1298
1397
|
strip.style.setProperty("--gp-page-h", `${pt(geometry.height)}px`);
|
|
1299
|
-
strip.style.setProperty("--gp-margin-top", `${pt(
|
|
1300
|
-
strip.style.setProperty("--gp-margin-right", `${pt(
|
|
1301
|
-
strip.style.setProperty("--gp-margin-bottom", `${pt(
|
|
1302
|
-
strip.style.setProperty("--gp-margin-left", `${pt(
|
|
1398
|
+
strip.style.setProperty("--gp-margin-top", `${pt(margin.top)}px`);
|
|
1399
|
+
strip.style.setProperty("--gp-margin-right", `${pt(margin.right)}px`);
|
|
1400
|
+
strip.style.setProperty("--gp-margin-bottom", `${pt(margin.bottom)}px`);
|
|
1401
|
+
strip.style.setProperty("--gp-margin-left", `${pt(margin.left)}px`);
|
|
1303
1402
|
run.nodes[0].before(strip);
|
|
1304
1403
|
for (const n of run.nodes)
|
|
1305
1404
|
strip.appendChild(n);
|
|
1306
|
-
strips.push({
|
|
1405
|
+
strips.push({
|
|
1406
|
+
el: strip,
|
|
1407
|
+
page: run.page,
|
|
1408
|
+
geometry,
|
|
1409
|
+
flushEdges: run.flushEdges,
|
|
1410
|
+
pages: 0,
|
|
1411
|
+
offset: 0
|
|
1412
|
+
});
|
|
1307
1413
|
}
|
|
1308
1414
|
for (const s of strips)
|
|
1309
1415
|
clearLeadingForcedBreaks(s.el);
|
|
@@ -1329,14 +1435,15 @@
|
|
|
1329
1435
|
passes = Math.max(passes, pass + 1);
|
|
1330
1436
|
const stride = strideOf(strip.el);
|
|
1331
1437
|
const stripLeft = strip.el.getBoundingClientRect().left - strip.el.scrollLeft;
|
|
1332
|
-
const
|
|
1438
|
+
const zoom = cssZoomOf(strip.el);
|
|
1439
|
+
const colOf = (r) => Math.floor(((r.left - stripLeft) / zoom + 1) / stride);
|
|
1333
1440
|
const stripTop = strip.el.getBoundingClientRect().top;
|
|
1334
1441
|
const colBottom = strip.el.clientHeight;
|
|
1335
1442
|
const plans = [];
|
|
1336
1443
|
for (const table of tables) {
|
|
1337
1444
|
const head = table.tHead;
|
|
1338
1445
|
const headRect = head?.getClientRects()[0];
|
|
1339
|
-
const footHeight = table.tFoot?.getBoundingClientRect().height ?? 0;
|
|
1446
|
+
const footHeight = (table.tFoot?.getBoundingClientRect().height ?? 0) / zoom;
|
|
1340
1447
|
const rows = Array.from(table.querySelectorAll("tbody > tr")).filter((r) => !r.classList.contains("gp-thead-shim") && !r.classList.contains("gp-tfoot-shim"));
|
|
1341
1448
|
if (!rows.length)
|
|
1342
1449
|
continue;
|
|
@@ -1351,10 +1458,10 @@
|
|
|
1351
1458
|
const row = rows[i];
|
|
1352
1459
|
if (cols[i] === lastCol || claims.has(row))
|
|
1353
1460
|
continue;
|
|
1354
|
-
const bottom = rects[i].bottom - stripTop;
|
|
1461
|
+
const bottom = (rects[i].bottom - stripTop) / zoom;
|
|
1355
1462
|
if (bottom > colBottom - footHeight + 0.5) {
|
|
1356
1463
|
newClaim = row;
|
|
1357
|
-
claims.set(row, colBottom - (rects[i].top - stripTop));
|
|
1464
|
+
claims.set(row, colBottom - (rects[i].top - stripTop) / zoom);
|
|
1358
1465
|
break;
|
|
1359
1466
|
}
|
|
1360
1467
|
}
|
|
@@ -1362,7 +1469,7 @@
|
|
|
1362
1469
|
plans.push({
|
|
1363
1470
|
table,
|
|
1364
1471
|
push: headRect ? colOf(headRect) < cols[0] : false,
|
|
1365
|
-
headHeight: headRect?.height ?? 0,
|
|
1472
|
+
headHeight: (headRect?.height ?? 0) / zoom,
|
|
1366
1473
|
footHeight,
|
|
1367
1474
|
breakRows: headRect ? rows.filter((_, i) => i > 0 && cols[i] > cols[i - 1]) : [],
|
|
1368
1475
|
footRows: [...claims.entries()],
|
|
@@ -1486,6 +1593,9 @@
|
|
|
1486
1593
|
function strideOf(strip) {
|
|
1487
1594
|
return stripMetrics(strip).stride;
|
|
1488
1595
|
}
|
|
1596
|
+
function cssZoomOf(el) {
|
|
1597
|
+
return el.currentCSSZoom ?? 1;
|
|
1598
|
+
}
|
|
1489
1599
|
function stripMetrics(strip) {
|
|
1490
1600
|
const cs = getComputedStyle(strip);
|
|
1491
1601
|
const w = parseFloat(cs.getPropertyValue("--gp-content-w"));
|
|
@@ -1508,12 +1618,13 @@
|
|
|
1508
1618
|
function indexInStrip(left, top, strip) {
|
|
1509
1619
|
const { stride, rowStride } = stripMetrics(strip.el);
|
|
1510
1620
|
const stripBox = strip.el.getBoundingClientRect();
|
|
1511
|
-
const
|
|
1512
|
-
const
|
|
1621
|
+
const zoom = cssZoomOf(strip.el);
|
|
1622
|
+
const x = (left - stripBox.left) / zoom + strip.el.scrollLeft;
|
|
1623
|
+
const y = (top - stripBox.top) / zoom;
|
|
1513
1624
|
const { perRow, shift } = wrapGeometry(strip);
|
|
1514
|
-
const colVisual = Math.floor((
|
|
1625
|
+
const colVisual = Math.floor((x + 1) / stride);
|
|
1515
1626
|
const colClamped = Math.max(0, Math.min(perRow - 1, colVisual));
|
|
1516
|
-
const row = Math.max(0, Math.floor((
|
|
1627
|
+
const row = Math.max(0, Math.floor((y + 1) / rowStride));
|
|
1517
1628
|
const idx = row * perRow + colClamped - shift;
|
|
1518
1629
|
return Math.max(0, Math.min(strip.pages - 1, idx));
|
|
1519
1630
|
}
|
|
@@ -1864,6 +1975,37 @@
|
|
|
1864
1975
|
function isIgnoredMarginBoxProperty(property) {
|
|
1865
1976
|
return MARGIN_BOX_IGNORED_PROPERTIES.has(property.toLowerCase());
|
|
1866
1977
|
}
|
|
1978
|
+
function marginBoxRectPt(name, g) {
|
|
1979
|
+
const { top, right, bottom, left } = g.margin;
|
|
1980
|
+
const cw = g.width - left - right;
|
|
1981
|
+
const ch = g.height - top - bottom;
|
|
1982
|
+
const third = (n) => n / 3;
|
|
1983
|
+
const T = {
|
|
1984
|
+
"top-left-corner": [0, 0, left, top],
|
|
1985
|
+
"top-left": [left, 0, third(cw), top],
|
|
1986
|
+
"top-center": [left + third(cw), 0, third(cw), top],
|
|
1987
|
+
"top-right": [left + 2 * third(cw), 0, third(cw), top],
|
|
1988
|
+
"top-right-corner": [g.width - right, 0, right, top],
|
|
1989
|
+
"bottom-left-corner": [0, g.height - bottom, left, bottom],
|
|
1990
|
+
"bottom-left": [left, g.height - bottom, third(cw), bottom],
|
|
1991
|
+
"bottom-center": [left + third(cw), g.height - bottom, third(cw), bottom],
|
|
1992
|
+
"bottom-right": [left + 2 * third(cw), g.height - bottom, third(cw), bottom],
|
|
1993
|
+
"bottom-right-corner": [g.width - right, g.height - bottom, right, bottom],
|
|
1994
|
+
"left-top": [0, top, left, third(ch)],
|
|
1995
|
+
"left-middle": [0, top + third(ch), left, third(ch)],
|
|
1996
|
+
"left-bottom": [0, top + 2 * third(ch), left, third(ch)],
|
|
1997
|
+
"right-top": [g.width - right, top, right, third(ch)],
|
|
1998
|
+
"right-middle": [g.width - right, top + third(ch), right, third(ch)],
|
|
1999
|
+
"right-bottom": [g.width - right, top + 2 * third(ch), right, third(ch)]
|
|
2000
|
+
};
|
|
2001
|
+
const [x, y, w, h] = T[name] ?? [0, 0, 0, 0];
|
|
2002
|
+
return { x, y, w, h };
|
|
2003
|
+
}
|
|
2004
|
+
function marginBoxAlign(name) {
|
|
2005
|
+
if (name.includes("center") || name.includes("middle"))
|
|
2006
|
+
return "center";
|
|
2007
|
+
return /right/.test(name) ? "end" : "start";
|
|
2008
|
+
}
|
|
1867
2009
|
|
|
1868
2010
|
// src/engine/viewer/decorate.ts
|
|
1869
2011
|
var px = (v) => `${v * PX_PER_PT}px`;
|
|
@@ -2120,7 +2262,7 @@
|
|
|
2120
2262
|
box.className = "gp-marginbox";
|
|
2121
2263
|
box.dataset.box = name;
|
|
2122
2264
|
Object.assign(box.style, rectFor(name, g));
|
|
2123
|
-
box.dataset.align =
|
|
2265
|
+
box.dataset.align = marginBoxAlign(name);
|
|
2124
2266
|
const content = document.createElement("span");
|
|
2125
2267
|
content.className = "gp-marginbox-content";
|
|
2126
2268
|
content.textContent = text;
|
|
@@ -2232,30 +2374,8 @@
|
|
|
2232
2374
|
return run;
|
|
2233
2375
|
}
|
|
2234
2376
|
function rectFor(name, g) {
|
|
2235
|
-
const
|
|
2236
|
-
|
|
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) };
|
|
2377
|
+
const r = marginBoxRectPt(name, g);
|
|
2378
|
+
return { left: px(r.x), top: px(r.y), width: px(r.w), height: px(r.h) };
|
|
2259
2379
|
}
|
|
2260
2380
|
|
|
2261
2381
|
// src/engine/viewer/index.ts
|