@segnavia/html 0.37.0 → 0.39.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.
@@ -1,8 +1,24 @@
1
1
  import { elements, parse } from "../sanitize.js";
2
- import { flowPageStyle } from "./book-flow.js";
3
2
  import { element, linkChildren, textContent } from "./dom.js";
3
+ import { flowPageStyle } from "./book-flow.js";
4
+ import { namedRegionFitIssues, templateRegionFitIssues } from "./region-fit.js";
4
5
  import { BOOK_FURNITURE, MOTIF_ATTRIBUTE, REGION_NAME, motifFieldAttribute } from "@segnavia/format";
5
6
  //#region src/book/book-held.ts
7
+ function authoredSection(section, id, build) {
8
+ const document = parse(section.html);
9
+ const runningHead = section.title;
10
+ const node = sectionNode("front", id, document.children, build, runningHead, flowAttribs(section.opens, section.parents));
11
+ return {
12
+ info: {
13
+ id,
14
+ kind: "front",
15
+ division: "front",
16
+ title: section.title,
17
+ runningHead
18
+ },
19
+ node
20
+ };
21
+ }
6
22
  function flowAttribs(opens, parents, extra = {}) {
7
23
  const attribs = { ...extra };
8
24
  if (opens) attribs["data-opens"] = opens;
@@ -29,6 +45,25 @@ function sectionNode(kind, id, children = [], build, runningHead, extra = {}) {
29
45
  if (build === "galley" && runningHead !== void 0) attribs["data-running-head"] = runningHead;
30
46
  return element("section", attribs, children);
31
47
  }
48
+ function partSection(entry, id, division, build, parts) {
49
+ const children = entry.html === void 0 ? [] : parse(entry.html).children;
50
+ const runningHead = entry.runningHead ?? entry.title;
51
+ const extra = flowAttribs(entry.opens, entry.parents, {
52
+ "data-segnavia-kind": "part",
53
+ "data-segnavia-parts": JSON.stringify(parts),
54
+ ...division === "front" ? { "data-sequence": "front" } : {}
55
+ });
56
+ return {
57
+ info: {
58
+ id,
59
+ kind: "part",
60
+ division,
61
+ title: entry.title,
62
+ runningHead
63
+ },
64
+ node: sectionNode("part", id, children, build, runningHead, extra)
65
+ };
66
+ }
32
67
  function flowPage(page, kind, children, geometry, recto) {
33
68
  const paper = element("div", { "data-segnavia-flow-paper": "" }, children);
34
69
  return element("figure", {
@@ -57,7 +92,7 @@ function collectHeadings(children, sectionId, depth, firstLevel, idFor, generate
57
92
  function heldHeadings(children, sectionId, depth, generated) {
58
93
  return collectHeadings(children, sectionId, depth, 1, (n) => `segnavia-h-${sectionId.replace(/^segnavia-/, "")}-${n}`, generated);
59
94
  }
60
- function designedSection(entry, id, kind, build, depth, geometry) {
95
+ function designedSection(entry, id, kind, division, build, depth, geometry) {
61
96
  const extra = flowAttribs(entry.opens, entry.parents, {
62
97
  "data-segnavia-kind": "fixed",
63
98
  ...kind === "front" ? { "data-sequence": "front" } : {}
@@ -85,6 +120,7 @@ function designedSection(entry, id, kind, build, depth, geometry) {
85
120
  info: {
86
121
  id,
87
122
  kind,
123
+ division,
88
124
  title: entry.title,
89
125
  runningHead
90
126
  },
@@ -93,12 +129,25 @@ function designedSection(entry, id, kind, build, depth, geometry) {
93
129
  headings
94
130
  };
95
131
  }
132
+ function templateRegionElements(html) {
133
+ return elements(parse(html).children).filter((node) => node.attribs["data-region"]);
134
+ }
96
135
  function declaredRegions(html) {
97
- return new Set(elements(parse(html).children).flatMap((node) => node.attribs["data-region"] ? [node.attribs["data-region"]] : []));
136
+ return new Set(templateRegionElements(html).flatMap((node) => node.attribs["data-region"] ? [node.attribs["data-region"]] : []));
98
137
  }
99
138
  const regionNameClear = "use a letter followed by letters, digits, hyphens or underscores to clear this refusal";
100
- function templateRegionNameIssues(templates) {
101
- return templates.flatMap((template) => [...declaredRegions(template.html)].flatMap((key) => REGION_NAME.test(key) ? [] : [`template "${template.name}" marks region "${key}" which is not an HTML name token; ${regionNameClear}`]));
139
+ function templateRegionNameIssues(template, regions) {
140
+ return [...new Set(regions.flatMap((node) => node.attribs["data-region"] ? [node.attribs["data-region"]] : []))].flatMap((key) => REGION_NAME.test(key) ? [] : [`template "${template}" marks region "${key}" which is not an HTML name token; ${regionNameClear}`]);
141
+ }
142
+ function templateRegionIssues(templates) {
143
+ return templates.flatMap((template) => {
144
+ const regions = templateRegionElements(template.html);
145
+ return [...templateRegionNameIssues(template.name, regions), ...templateRegionFitIssues(template.name, regions)];
146
+ });
147
+ }
148
+ function templatedPageRegionFitIssues(entry, id) {
149
+ const label = entry.title ?? id;
150
+ return entry.pages.flatMap((page, index) => Object.values(page.regions).flatMap((html) => namedRegionFitIssues(`section ${label} page ${index + 1}`, templateRegionElements(html))));
102
151
  }
103
152
  const markFurniture = BOOK_FURNITURE.find((item) => item.key === "segnavia:mark");
104
153
  const markFields = new Set(markFurniture.fields.map((field) => motifFieldAttribute(field.key)));
@@ -137,7 +186,10 @@ function undeclaredRegionIssues(entry, id, templates) {
137
186
  return Object.keys(page.regions).flatMap((region) => declared.has(region) ? [] : [`section ${label} page ${index + 1} fills region "${region}" which template "${name}" does not declare; add that region to the template or remove the fill to clear this refusal`]);
138
187
  });
139
188
  }
140
- function templatedSection(entry, id, kind, build, depth, templates, geometry) {
189
+ function templatedSectionIssues(entry, id, templates) {
190
+ return [...undeclaredRegionIssues(entry, id, templates), ...templatedPageRegionFitIssues(entry, id)];
191
+ }
192
+ function templatedSection(entry, id, kind, division, build, depth, templates, geometry) {
141
193
  const extra = flowAttribs(entry.opens, entry.parents, {
142
194
  "data-segnavia-kind": "templated",
143
195
  "data-template": entry.template,
@@ -191,6 +243,7 @@ function templatedSection(entry, id, kind, build, depth, templates, geometry) {
191
243
  info: {
192
244
  id,
193
245
  kind,
246
+ division,
194
247
  title: entry.title,
195
248
  runningHead
196
249
  },
@@ -207,4 +260,4 @@ function templateHolders(templates) {
207
260
  }, parse(template.html).children));
208
261
  }
209
262
  //#endregion
210
- export { collectHeadings, designedSection, flowAttribs, markPlacementIssues, sectionNode, templateHolders, templateRegionNameIssues, templatedSection, undeclaredRegionIssues };
263
+ export { authoredSection, collectHeadings, designedSection, flowAttribs, markPlacementIssues, partSection, sectionNode, templateHolders, templateRegionElements, templateRegionIssues, templateRegionNameIssues, templatedSection, templatedSectionIssues };
@@ -0,0 +1,42 @@
1
+ import { elements } from "../sanitize.js";
2
+ import { pageReferenceNodes, pageReferenceTarget } from "./book-page-reference.js";
3
+ //#region src/book/book-issues.ts
4
+ function sectionIds(section) {
5
+ return [section.info.id, ...elements(section.node.children).flatMap((node) => node.attribs.id ? [node.attribs.id] : [])];
6
+ }
7
+ function sectionName(section) {
8
+ return `${section.title ?? section.id} (${section.id})`;
9
+ }
10
+ function ownersById(sections) {
11
+ const occurrences = /* @__PURE__ */ new Map();
12
+ for (const section of sections) for (const id of sectionIds(section)) occurrences.set(id, [...occurrences.get(id) ?? [], section.info]);
13
+ return occurrences;
14
+ }
15
+ function duplicateIdIssues(sections, headings) {
16
+ const listed = new Set(headings.map((heading) => heading.id));
17
+ return [...ownersById(sections)].flatMap(([id, owners]) => {
18
+ if (!listed.has(id) || owners.length < 2) return [];
19
+ const named = owners.slice(0, 2).map(sectionName);
20
+ return [`destination id ${id} is used in both ${named[0]} and ${named[1]}; listed heading and generated destination ids must be unique`];
21
+ });
22
+ }
23
+ function pageReferenceIssues(sections, scopes) {
24
+ const targets = ownersById(sections);
25
+ return scopes.flatMap((scope) => pageReferenceNodes(scope.roots).flatMap((node) => {
26
+ const id = pageReferenceTarget(node.attribs);
27
+ if (!id) return [];
28
+ const owners = targets.get(id) ?? [];
29
+ if (owners.length === 0) return [`page reference to ${id} in ${scope.owner} has no target; add the id or remove the reference`];
30
+ if (owners.length === 1) return [];
31
+ return [`page reference target ${id} is used in ${owners.length} sections: ${owners.map(sectionName).join(", ")}; make the target id unique to clear this refusal`];
32
+ }));
33
+ }
34
+ function reservedIdIssues(section) {
35
+ return elements(section.node.children).flatMap((node) => {
36
+ const id = node.attribs.id;
37
+ if (!id?.startsWith("segnavia-")) return [];
38
+ return [`authored id ${id} in ${sectionName(section.info)} uses the reserved segnavia- prefix`];
39
+ });
40
+ }
41
+ //#endregion
42
+ export { duplicateIdIssues, pageReferenceIssues, reservedIdIssues };
@@ -0,0 +1,7 @@
1
+ import { Element } from "domhandler";
2
+ //#region src/book/book-page-reference.d.ts
3
+ export declare const PAGE_REFERENCE_ATTRIBUTE = "data-ref";
4
+ export declare const PAGE_REFERENCE_VALUE = "page";
5
+ export declare const PAGE_REFERENCE_SELECTOR = "a[data-ref=\"page\"]";
6
+ export declare function pageReferenceTarget(attribs: Record<string, string>): string | undefined;
7
+ //#endregion
@@ -0,0 +1,19 @@
1
+ import { elements } from "../sanitize.js";
2
+ //#region src/book/book-page-reference.ts
3
+ const PAGE_REFERENCE_ATTRIBUTE = "data-ref";
4
+ const PAGE_REFERENCE_VALUE = "page";
5
+ const PAGE_REFERENCE_SELECTOR = "a[data-ref=\"page\"]";
6
+ function pageReferenceTarget(attribs) {
7
+ if (attribs["data-ref"] !== "page") return void 0;
8
+ const href = attribs.href;
9
+ if (href?.startsWith("#") && href.length > 1) return href.slice(1);
10
+ return href?.startsWith("segnavia:target/") && href.length > 16 ? href.slice(16) : void 0;
11
+ }
12
+ function pageReferenceNodes(roots) {
13
+ return roots.flatMap((root) => [root, ...elements(root.children)]).filter((node) => node.name === "a" && pageReferenceTarget(node.attribs) !== void 0);
14
+ }
15
+ function pageReferenceTargets(roots) {
16
+ return [...new Set(pageReferenceNodes(roots).flatMap((node) => pageReferenceTarget(node.attribs) ?? []))];
17
+ }
18
+ //#endregion
19
+ export { PAGE_REFERENCE_ATTRIBUTE, PAGE_REFERENCE_SELECTOR, PAGE_REFERENCE_VALUE, pageReferenceNodes, pageReferenceTarget, pageReferenceTargets };
@@ -0,0 +1,50 @@
1
+ import { partSection } from "./book-held.js";
2
+ import { isPart } from "@segnavia/format";
3
+ //#region src/book/book-part-build.ts
4
+ function partNames(ancestry, own) {
5
+ return Object.fromEntries([...ancestry, ...own ? [own] : []].map((part) => [part.name, part.runningHead ?? part.title ?? ""]));
6
+ }
7
+ function addPart(sectionEntry, build) {
8
+ const { entry } = sectionEntry;
9
+ if (!isPart(entry)) return false;
10
+ const id = sectionEntry.id ?? "";
11
+ if (entry.html !== void 0) {
12
+ const parts = partNames(sectionEntry.ancestry, {
13
+ name: entry.name,
14
+ title: entry.title,
15
+ runningHead: entry.runningHead,
16
+ id
17
+ });
18
+ build.sections.push(partSection(entry, id, sectionEntry.division, build.build, parts));
19
+ attachPendingPartHeadings(build, sectionEntry, id);
20
+ }
21
+ if (entry.contents && entry.title !== void 0) {
22
+ const heading = {
23
+ level: 1,
24
+ depth: sectionEntry.depth,
25
+ text: entry.title
26
+ };
27
+ if (entry.html === void 0) build.pendingPartHeadings.push({
28
+ ...heading,
29
+ partId: id
30
+ });
31
+ else build.headings.push({
32
+ ...heading,
33
+ id,
34
+ sectionId: id
35
+ });
36
+ }
37
+ return true;
38
+ }
39
+ function attachPendingPartHeadings(build, sectionEntry, id) {
40
+ const ancestryIds = new Set(sectionEntry.ancestry.map((part) => part.id));
41
+ const attached = build.pendingPartHeadings.filter((heading) => ancestryIds.has(heading.partId));
42
+ build.headings.push(...attached.map(({ partId: _partId, ...heading }) => ({
43
+ ...heading,
44
+ id,
45
+ sectionId: id
46
+ })));
47
+ build.pendingPartHeadings = build.pendingPartHeadings.filter((heading) => !ancestryIds.has(heading.partId));
48
+ }
49
+ //#endregion
50
+ export { addPart, attachPendingPartHeadings, partNames };
@@ -0,0 +1 @@
1
+ import { Element, Text } from "domhandler";
@@ -0,0 +1,2 @@
1
+ import { Element } from "domhandler";
2
+ import { Book } from "@segnavia/format";
@@ -32,18 +32,15 @@ const INSIDE_BLOCKS = /* @__PURE__ */ new Set([
32
32
  ]);
33
33
  function noteState() {
34
34
  return {
35
- footnotes: false,
36
- endnotes: false,
37
- globalFootnote: 0,
38
- chapterFootnotes: /* @__PURE__ */ new Map(),
39
- endnoteCount: 0,
40
- chapterEndnotes: /* @__PURE__ */ new Map(),
41
- footnoteParagraphs: []
35
+ used: /* @__PURE__ */ new Set(),
36
+ counters: /* @__PURE__ */ new Map(),
37
+ collected: /* @__PURE__ */ new Map(),
38
+ pageParagraphs: /* @__PURE__ */ new Map(),
39
+ authored: /* @__PURE__ */ new Map(),
40
+ repeatCount: 0,
41
+ issues: []
42
42
  };
43
43
  }
44
- function endnotesInOrder(state) {
45
- return [...state.chapterEndnotes.values()].flat();
46
- }
47
44
  function nearestBlock(note) {
48
45
  for (let parent = note.parent; parent; parent = parent.parent) if (isTag(parent) && BLOCKS.has(parent.name)) return parent;
49
46
  }
@@ -63,59 +60,128 @@ function noteParagraph(className, noteId, refId, marker, content) {
63
60
  ...content
64
61
  ]);
65
62
  }
66
- function footnoteNumber(book, chapter, state) {
67
- if (book.footnotes.restart === "book") {
68
- state.globalFootnote += 1;
69
- return state.globalFootnote;
70
- }
71
- const number = (state.chapterFootnotes.get(chapter) ?? 0) + 1;
72
- state.chapterFootnotes.set(chapter, number);
73
- return number;
63
+ function scopeKey(series, context) {
64
+ if (series.numbering === "book") return "book";
65
+ if (series.numbering === "part") return context.part?.id ?? "book";
66
+ return String(context.chapter);
67
+ }
68
+ function placementKey(series, context) {
69
+ if (series.placement === "book") return "book";
70
+ if (series.placement === "part") return context.part?.id ?? "book";
71
+ return context.placementKey ?? String(context.chapter);
72
+ }
73
+ function nextNumber(series, context, state) {
74
+ const scope = scopeKey(series, context);
75
+ const key = `${series.name}:${scope}`;
76
+ const number = (state.counters.get(key) ?? 0) + 1;
77
+ state.counters.set(key, number);
78
+ return {
79
+ number,
80
+ scope
81
+ };
82
+ }
83
+ function noteIds(series, scope, chapter, number) {
84
+ const scopePart = series.idScope === "chapter" ? `-${chapter}` : series.numbering === "book" ? "" : `-${scope}`;
85
+ return {
86
+ noteId: `segnavia-${series.idStem}${scopePart}-${number}`,
87
+ refId: `segnavia-${series.idStem}ref${scopePart}-${number}`
88
+ };
74
89
  }
75
- function addFootnoteBlock(block, paragraph, blocks) {
76
- const existing = blocks.get(block);
90
+ function addPageBlock(block, paragraph, blocks, series, seriesOrder) {
91
+ const blockSeries = blocks.get(block) ?? /* @__PURE__ */ new Map();
92
+ const existing = blockSeries.get(series.name);
77
93
  if (existing) {
78
94
  append(existing, paragraph);
79
95
  return;
80
96
  }
81
- const notes = element("div", { class: "segnavia-footnotes" }, [paragraph]);
82
- blocks.set(block, notes);
83
- if (INSIDE_BLOCKS.has(block.name)) append(block, notes);
84
- else insertAfter(block, notes);
97
+ const attributes = {
98
+ class: series.blockClass,
99
+ ...series.markBlockSeries ? { "data-segnavia-note-series": series.name } : {}
100
+ };
101
+ const notes = element("div", attributes, [paragraph]);
102
+ const index = seriesOrder.get(series.name) ?? seriesOrder.size;
103
+ const next = [...blockSeries.entries()].filter(([name]) => (seriesOrder.get(name) ?? seriesOrder.size) > index).sort(([left], [right]) => (seriesOrder.get(left) ?? seriesOrder.size) - (seriesOrder.get(right) ?? seriesOrder.size))[0]?.[1];
104
+ if (next?.prev) insertAfter(next.prev, notes);
105
+ else if (INSIDE_BLOCKS.has(block.name)) append(block, notes);
106
+ else {
107
+ const previous = [...blockSeries.entries()].filter(([name]) => (seriesOrder.get(name) ?? seriesOrder.size) < index).sort(([left], [right]) => (seriesOrder.get(right) ?? seriesOrder.size) - (seriesOrder.get(left) ?? seriesOrder.size))[0]?.[1];
108
+ insertAfter(previous ?? block, notes);
109
+ }
110
+ blockSeries.set(series.name, notes);
111
+ blocks.set(block, blockSeries);
85
112
  }
86
- function transformFootnote(note, book, chapter, state, blocks, galley) {
113
+ function collectedNotes(series, context, state) {
114
+ const key = placementKey(series, context);
115
+ const mapKey = `${series.name}:${key}`;
116
+ const existing = state.collected.get(mapKey);
117
+ if (existing) return existing;
118
+ const scopeTitle = series.placement === "part" ? context.part?.title : series.placement === "chapter" ? context.title : void 0;
119
+ const collected = {
120
+ series,
121
+ placementKey: key,
122
+ targetId: series.placement === "book" || series.placement === "part" && context.part === void 0 || series.placement === "chapter" && context.division === "front" ? "book" : series.placement === "part" ? context.part?.id ?? context.targetId : context.targetId,
123
+ scopeTitle,
124
+ division: context.division,
125
+ paragraphs: []
126
+ };
127
+ state.collected.set(mapKey, collected);
128
+ return collected;
129
+ }
130
+ function transformAuthoredNote(note, series, context, state, blocks, seriesOrder, galley) {
87
131
  const block = nearestBlock(note);
88
132
  if (!block) return;
89
- const number = footnoteNumber(book, chapter, state);
90
- const noteId = `segnavia-fn-${chapter}-${number}`;
91
- const refId = `segnavia-fnref-${chapter}-${number}`;
92
- const marker = noteMarker(number, book.footnotes.marker);
133
+ const { number, scope } = nextNumber(series, context, state);
134
+ const { noteId, refId } = noteIds(series, scope, context.chapter, number);
135
+ const marker = noteMarker(number, series.marker);
136
+ const authoredId = note.attribs.id;
93
137
  const content = [...note.children];
94
138
  replace(note, element("sup", { class: "segnavia-note-ref" }, [markerLink(refId, noteId, marker)]));
95
- const paragraph = noteParagraph("segnavia-footnote", noteId, refId, marker, content);
96
- if (galley) state.footnoteParagraphs.push(paragraph);
97
- else addFootnoteBlock(block, paragraph, blocks);
98
- state.footnotes = true;
139
+ const paragraph = noteParagraph(series.bodyClass, noteId, refId, marker, content);
140
+ if (series.placement === "page") {
141
+ if (galley) {
142
+ const paragraphs = state.pageParagraphs.get(series.name) ?? [];
143
+ paragraphs.push(paragraph);
144
+ state.pageParagraphs.set(series.name, paragraphs);
145
+ } else addPageBlock(block, paragraph, blocks, series, seriesOrder);
146
+ } else collectedNotes(series, context, state).paragraphs.push(paragraph);
147
+ if (authoredId) state.authored.set(authoredId, {
148
+ noteId,
149
+ refId,
150
+ marker
151
+ });
152
+ state.used.add(series.name);
99
153
  }
100
- function transformEndnote(note, book, state, owner) {
101
- state.endnoteCount += 1;
102
- const number = state.endnoteCount;
103
- const noteId = `segnavia-en-${number}`;
104
- const refId = `segnavia-enref-${number}`;
105
- const marker = noteMarker(number, book.endnotes.marker);
106
- const content = [...note.children];
107
- replace(note, element("sup", { class: "segnavia-note-ref" }, [markerLink(refId, noteId, marker)]));
108
- const paragraph = noteParagraph("segnavia-endnote", noteId, refId, marker, content);
109
- const held = state.chapterEndnotes.get(owner) ?? [];
110
- held.push(paragraph);
111
- state.chapterEndnotes.set(owner, held);
112
- state.endnotes = true;
154
+ function transformRepeat(node, state) {
155
+ const authoredId = node.attribs["data-note-ref"] ?? "";
156
+ const note = state.authored.get(authoredId);
157
+ if (!note) {
158
+ state.issues.push(`data-note-ref ${authoredId} names no earlier note; place the note before its repeated call`);
159
+ return;
160
+ }
161
+ state.repeatCount += 1;
162
+ replace(node, element("sup", {
163
+ class: "segnavia-note-ref",
164
+ "data-segnavia-note-repeat": ""
165
+ }, [markerLink(`${note.refId}-repeat-${state.repeatCount}`, note.noteId, note.marker)]));
113
166
  }
114
- function transformNotes(root, book, chapter, state, galley = false, notesOwner = chapter) {
167
+ function transformNotes(root, book, context, state, galley = false) {
115
168
  const blocks = /* @__PURE__ */ new Map();
116
- const notes = elements(root.children).filter((node) => node.name === "span" && (node.attribs["data-note"] === "footnote" || node.attribs["data-note"] === "endnote"));
117
- for (const note of notes) if (note.attribs["data-note"] === "footnote") transformFootnote(note, book, chapter, state, blocks, galley);
118
- else transformEndnote(note, book, state, notesOwner);
169
+ const byName = new Map(book.notes.map((series) => [series.name, series]));
170
+ const seriesOrder = new Map(book.notes.map((series, index) => [series.name, index]));
171
+ const nodes = elements(root.children).filter((node) => node.name === "span" && node.attribs["data-note"] !== void 0 || node.name === "a" && node.attribs["data-note-ref"] !== void 0);
172
+ for (const node of nodes) {
173
+ if (node.name === "a") {
174
+ transformRepeat(node, state);
175
+ continue;
176
+ }
177
+ const name = node.attribs["data-note"] ?? "";
178
+ const series = byName.get(name);
179
+ if (!series) {
180
+ state.issues.push(`data-note names unknown note series ${name}; declare the series in notes or use a declared series name`);
181
+ continue;
182
+ }
183
+ transformAuthoredNote(node, series, context, state, blocks, seriesOrder, galley);
184
+ }
119
185
  }
120
186
  //#endregion
121
- export { endnotesInOrder, noteState, transformNotes };
187
+ export { noteState, transformNotes };
@@ -1,4 +1,5 @@
1
1
  import { PaginateBookInput, PaginateBookResult } from "./paginate-types.js";
2
+ import { printedPageAt, selectBookParent } from "../printed-pages.js";
2
3
  import { paginateColumns } from "./paginate-columns.js";
3
4
  import { paginateKeeps } from "./paginate-keeps.js";
4
5
  import { paginateMarks } from "./paginate-marks.js";
@@ -6,6 +7,6 @@ import { paginateNotes } from "./paginate-notes.js";
6
7
  import { paginateSequence } from "./paginate-sequence.js";
7
8
  //#region src/book/paginate-book.d.ts
8
9
  export declare function paginateBook(input: PaginateBookInput): PaginateBookResult;
9
- export declare const PAGINATE_FUNCTIONS: (typeof paginateColumns | typeof paginateNotes | typeof paginateKeeps | typeof paginateMarks | typeof paginateSequence)[];
10
+ export declare const PAGINATE_FUNCTIONS: (typeof printedPageAt | typeof selectBookParent | typeof paginateColumns | typeof paginateNotes | typeof paginateKeeps | typeof paginateMarks | typeof paginateSequence)[];
10
11
  export declare function paginateScript(): string;
11
12
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { printedPageAt, selectBookParent } from "../printed-pages.js";
1
2
  import { paginateColumns } from "./paginate-columns.js";
2
3
  import { paginateKeeps } from "./paginate-keeps.js";
3
4
  import { paginateMarks } from "./paginate-marks.js";
@@ -5,31 +6,36 @@ import { paginateNotes } from "./paginate-notes.js";
5
6
  import { paginateSequence } from "./paginate-sequence.js";
6
7
  //#region src/book/paginate-book.ts
7
8
  function paginateBook(input) {
8
- const textHeight = input.geometry.textHeight;
9
9
  const orphans = input.settings.orphans ?? 2;
10
10
  const widows = input.settings.widows ?? 2;
11
- function used(placed, fragments) {
11
+ function used(textHeight, placed, fragments) {
12
12
  return paginateColumns(placed).height + paginateNotes({
13
13
  op: "space",
14
14
  notes: input.notes,
15
- geometry: input.geometry,
15
+ geometry: {
16
+ ...input.geometry,
17
+ textHeight
18
+ },
16
19
  fragments
17
20
  });
18
21
  }
19
- function withNotes(extra) {
22
+ function withNotes(textHeight, extra) {
20
23
  return {
21
24
  notes: input.notes,
22
- geometry: input.geometry,
23
- used,
25
+ geometry: {
26
+ ...input.geometry,
27
+ textHeight
28
+ },
29
+ used: (placed, fragments) => used(textHeight, placed, fragments),
24
30
  ...extra
25
31
  };
26
32
  }
27
- function refusal(block) {
33
+ function refusal(block, textArea = "the text area") {
28
34
  const position = `chapter ${block.position.chapter} block ${block.position.block}`;
29
35
  return {
30
36
  tag: block.tag,
31
37
  position: block.position,
32
- message: `The ${block.tag} at ${position} could not establish a fit in the text area; shorten it or increase the text-area height to clear this refusal.`
38
+ message: `The ${block.tag} at ${position} could not establish a fit in ${textArea}; shorten it or increase the text-area height to clear this refusal.`
33
39
  };
34
40
  }
35
41
  function blockForNote(id) {
@@ -52,16 +58,16 @@ function paginateBook(input) {
52
58
  message: `The ${found.tag} at ${where} could not establish a fit for a note line in the text area; ${clears}`
53
59
  };
54
60
  }
55
- function lineTooTall(unit) {
61
+ function lineTooTall(unit, textHeight) {
56
62
  return unit.bottom - unit.top > textHeight;
57
63
  }
58
- function greedyFill(placed, fragments, units, index) {
64
+ function greedyFill(placed, fragments, units, index, textHeight) {
59
65
  let next = index;
60
66
  let notes = fragments;
61
67
  while (next < units.length) {
62
68
  const unit = units[next];
63
- if (!unit || lineTooTall(unit)) break;
64
- const allocated = paginateNotes(withNotes({
69
+ if (!unit || lineTooTall(unit, textHeight)) break;
70
+ const allocated = paginateNotes(withNotes(textHeight, {
65
71
  op: "allocate",
66
72
  placed,
67
73
  fragments: notes,
@@ -78,15 +84,15 @@ function paginateBook(input) {
78
84
  index: next
79
85
  };
80
86
  }
81
- function forcePlace(unit) {
82
- if (lineTooTall(unit)) return {
87
+ function forcePlace(unit, textHeight, textArea) {
88
+ if (lineTooTall(unit, textHeight)) return {
83
89
  placed: [],
84
90
  fragments: [],
85
91
  index: 0,
86
92
  pending: [],
87
- refusal: refusal(unit.block)
93
+ refusal: refusal(unit.block, textArea)
88
94
  };
89
- const allocated = paginateNotes(withNotes({
95
+ const allocated = paginateNotes(withNotes(textHeight, {
90
96
  op: "allocate",
91
97
  placed: [],
92
98
  fragments: [],
@@ -97,30 +103,30 @@ function paginateBook(input) {
97
103
  fragments: [],
98
104
  index: 0,
99
105
  pending: [],
100
- refusal: refusal(unit.block)
106
+ refusal: refusal(unit.block, textArea)
101
107
  };
102
108
  return {
103
109
  placed: [unit],
104
110
  fragments: allocated,
105
111
  index: 0,
106
- pending: paginateNotes(withNotes({
112
+ pending: paginateNotes(withNotes(textHeight, {
107
113
  op: "leftover",
108
114
  fragments: allocated
109
115
  }))
110
116
  };
111
117
  }
112
- function fillPage(units, index, pending) {
113
- const held = paginateNotes(withNotes({
118
+ function fillPage(units, index, pending, textHeight, textArea) {
119
+ const held = paginateNotes(withNotes(textHeight, {
114
120
  op: "continue",
115
121
  pending
116
122
  }));
117
123
  const incoming = units[index];
118
- if (held.leftover.length === 0 && incoming && lineTooTall(incoming)) return {
124
+ if (held.leftover.length === 0 && incoming && lineTooTall(incoming, textHeight)) return {
119
125
  placed: [],
120
126
  fragments: [],
121
127
  index,
122
128
  pending,
123
- refusal: refusal(incoming.block)
129
+ refusal: refusal(incoming.block, textArea)
124
130
  };
125
131
  const stuck = held.fragments.length === 0 ? held.leftover[0] : void 0;
126
132
  if (stuck) return {
@@ -136,10 +142,10 @@ function paginateBook(input) {
136
142
  index,
137
143
  pending: held.leftover
138
144
  };
139
- const filled = greedyFill([], held.fragments, units, index);
145
+ const filled = greedyFill([], held.fragments, units, index, textHeight);
140
146
  const placed = paginateKeeps(filled.placed, units[filled.index], textHeight, orphans, widows, held.fragments.length > 0);
141
147
  const nextIndex = filled.index - (filled.placed.length - placed.length);
142
- const fragments = paginateNotes(withNotes({
148
+ const fragments = paginateNotes(withNotes(textHeight, {
143
149
  op: "recompute",
144
150
  placed,
145
151
  seed: held.fragments
@@ -148,7 +154,7 @@ function paginateBook(input) {
148
154
  placed,
149
155
  fragments,
150
156
  index: nextIndex,
151
- pending: paginateNotes(withNotes({
157
+ pending: paginateNotes(withNotes(textHeight, {
152
158
  op: "leftover",
153
159
  fragments
154
160
  }))
@@ -169,7 +175,7 @@ function paginateBook(input) {
169
175
  pending,
170
176
  done: true
171
177
  };
172
- const forced = forcePlace(unit);
178
+ const forced = forcePlace(unit, textHeight, textArea);
173
179
  if (forced.refusal) return forced;
174
180
  return {
175
181
  placed: forced.placed,
@@ -181,6 +187,8 @@ function paginateBook(input) {
181
187
  return paginateSequence(input, fillPage);
182
188
  }
183
189
  const PAGINATE_FUNCTIONS = [
190
+ printedPageAt,
191
+ selectBookParent,
184
192
  paginateColumns,
185
193
  paginateNotes,
186
194
  paginateKeeps,