@segnavia/html 0.26.0 → 0.28.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/dist/base64.d.ts +4 -0
- package/dist/base64.js +51 -0
- package/dist/bindings.d.ts +4 -1
- package/dist/bindings.js +19 -2
- package/dist/book/book-document.js +146 -65
- package/dist/book/book-held.js +182 -0
- package/dist/book/book-view.d.ts +5 -0
- package/dist/book/book-view.js +26 -0
- package/dist/book/furniture.d.ts +14 -0
- package/dist/book/furniture.js +36 -0
- package/dist/book/notes.js +18 -9
- package/dist/book/paginate-book.d.ts +11 -0
- package/dist/book/paginate-book.js +195 -0
- package/dist/book/paginate-columns.d.ts +8 -0
- package/dist/book/paginate-columns.js +119 -0
- package/dist/book/paginate-keeps.d.ts +4 -0
- package/dist/book/paginate-keeps.js +80 -0
- package/dist/book/paginate-marks.d.ts +7 -0
- package/dist/book/paginate-marks.js +34 -0
- package/dist/book/paginate-notes.d.ts +21 -0
- package/dist/book/paginate-notes.js +105 -0
- package/dist/book/paginate-sequence.d.ts +4 -0
- package/dist/book/paginate-sequence.js +267 -0
- package/dist/book/paginate-target.d.ts +13 -0
- package/dist/book/paginate-target.js +36 -0
- package/dist/book/paginate-types.d.ts +170 -0
- package/dist/book/types.d.ts +1 -1
- package/dist/book-css.d.ts +5 -0
- package/dist/book-css.js +14 -0
- package/dist/control-char.js +10 -0
- package/dist/css-tokens.js +189 -0
- package/dist/font-css.d.ts +11 -0
- package/dist/font-css.js +75 -0
- package/dist/font-format.d.ts +4 -0
- package/dist/font-format.js +11 -0
- package/dist/font-references.d.ts +4 -0
- package/dist/font-references.js +192 -0
- package/dist/index.d.ts +15 -2
- package/dist/index.js +14 -2
- package/dist/motifs.d.ts +32 -0
- package/dist/motifs.js +245 -0
- package/dist/printed-pages.d.ts +42 -0
- package/dist/printed-pages.js +97 -0
- package/dist/sanitize-css.d.ts +6 -0
- package/dist/sanitize-css.js +308 -0
- package/dist/sanitize.d.ts +1 -0
- package/dist/sanitize.js +37 -15
- package/dist/unicode-range.d.ts +11 -0
- package/dist/unicode-range.js +86 -0
- package/package.json +2 -2
package/dist/motifs.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { elements, parse, sanitize, serialize } from "./sanitize.js";
|
|
2
|
+
import { bindingNames, bindingRoot, fill, hasBindings } from "./bindings.js";
|
|
3
|
+
import { isTag } from "domhandler";
|
|
4
|
+
import { LIBRARY_MOTIF_PREFIX, MOTIF_ATTRIBUTE, PRINTED_PAGE_CONTEXT, chooseMotifVersion, motifFieldAttribute, resolveDirection } from "@segnavia/format";
|
|
5
|
+
//#region src/motifs.ts
|
|
6
|
+
const MOTIF_DEPTH_LIMIT = 8;
|
|
7
|
+
var MotifRefusedError = class extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "MotifRefusedError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
function placementName(element) {
|
|
14
|
+
return `motif placement ${Object.entries(element.attribs).filter(([name]) => name === MOTIF_ATTRIBUTE || name.startsWith(`${MOTIF_ATTRIBUTE}-`)).sort(([left], [right]) => left.localeCompare(right)).map(([name, value]) => `${name}=${JSON.stringify(value)}`).join(", ") || `${MOTIF_ATTRIBUTE}=""`}`;
|
|
15
|
+
}
|
|
16
|
+
function refusal(element, problem, remedy) {
|
|
17
|
+
return `${placementName(element)} could not establish ${problem}; ${remedy} to clear this refusal.`;
|
|
18
|
+
}
|
|
19
|
+
function motifElements(html) {
|
|
20
|
+
return elements(parse(html).children).filter((element) => Object.hasOwn(element.attribs, MOTIF_ATTRIBUTE));
|
|
21
|
+
}
|
|
22
|
+
function libraryMotifPlacementRefusal(html) {
|
|
23
|
+
const placed = motifElements(html).find((element) => (element.attribs[MOTIF_ATTRIBUTE] ?? "").startsWith(LIBRARY_MOTIF_PREFIX));
|
|
24
|
+
return placed ? refusal(placed, `a host motif key because "${LIBRARY_MOTIF_PREFIX}" is reserved for library motifs`, "rename the motif key") : void 0;
|
|
25
|
+
}
|
|
26
|
+
function hasMotifPlacement(html) {
|
|
27
|
+
return motifElements(html).length > 0;
|
|
28
|
+
}
|
|
29
|
+
function renderMotifPlacements(html, replacement) {
|
|
30
|
+
const document = parse(html);
|
|
31
|
+
const visit = (nodes) => {
|
|
32
|
+
for (const node of [...nodes]) {
|
|
33
|
+
if (!isTag(node)) continue;
|
|
34
|
+
if (Object.hasOwn(node.attribs, MOTIF_ATTRIBUTE)) {
|
|
35
|
+
const rendered = replacement(node.attribs[MOTIF_ATTRIBUTE] ?? "");
|
|
36
|
+
if (rendered === void 0) {
|
|
37
|
+
const index = nodes.indexOf(node);
|
|
38
|
+
if (index >= 0) nodes.splice(index, 1);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
delete node.attribs[MOTIF_ATTRIBUTE];
|
|
42
|
+
replaceChildren(node, rendered);
|
|
43
|
+
}
|
|
44
|
+
visit(node.children);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
visit(document.children);
|
|
48
|
+
return serialize(document);
|
|
49
|
+
}
|
|
50
|
+
function refuse(state, element, problem, remedy) {
|
|
51
|
+
const message = refusal(element, problem, remedy);
|
|
52
|
+
if (!state.validateOnly) throw new MotifRefusedError(message);
|
|
53
|
+
state.refusals.push(message);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
function placementValues(element, motif, inheritedLang, state) {
|
|
57
|
+
const known = new Set(motif.fields.map((field) => motifFieldAttribute(field.key).toLowerCase()));
|
|
58
|
+
known.add(motifFieldAttribute("lang").toLowerCase());
|
|
59
|
+
const unknown = Object.keys(element.attribs).filter((name) => name.startsWith(`${MOTIF_ATTRIBUTE}-`) && !known.has(name));
|
|
60
|
+
if (unknown.length > 0) {
|
|
61
|
+
refuse(state, element, `fields for ${unknown.join(", ")}`, "remove the unknown field attributes");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const values = {};
|
|
65
|
+
for (const field of motif.fields) {
|
|
66
|
+
const value = element.attribs[motifFieldAttribute(field.key).toLowerCase()] ?? field.default;
|
|
67
|
+
if (value === void 0) {
|
|
68
|
+
if (field.required) {
|
|
69
|
+
refuse(state, element, `a value for required field "${field.key}"`, `set ${motifFieldAttribute(field.key)}`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (field.choices && !field.choices.includes(value)) {
|
|
75
|
+
refuse(state, element, `an allowed value for field "${field.key}"`, `set ${motifFieldAttribute(field.key)} to one of ${field.choices.join(", ")}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
values[field.key] = value;
|
|
79
|
+
}
|
|
80
|
+
const lang = element.attribs[motifFieldAttribute("lang")] ?? inheritedLang;
|
|
81
|
+
if (lang !== void 0) values.lang = lang;
|
|
82
|
+
return values;
|
|
83
|
+
}
|
|
84
|
+
function replaceChildren(element, html) {
|
|
85
|
+
const children = parse(html).children;
|
|
86
|
+
for (const child of children) child.parent = element;
|
|
87
|
+
element.children = children;
|
|
88
|
+
}
|
|
89
|
+
function needsHostData(html, options) {
|
|
90
|
+
const deferred = new Set(options.deferPaths ?? []);
|
|
91
|
+
const names = new Set(Object.values(bindingNames(options.prefix)));
|
|
92
|
+
return elements(parse(html).children).some((held) => Object.entries(held.attribs).some(([name, path]) => names.has(name) && !deferred.has(bindingRoot(path))));
|
|
93
|
+
}
|
|
94
|
+
async function fillVersion(html, placement, element, state) {
|
|
95
|
+
const data = await state.options.source.data?.(placement, { signal: state.options.signal });
|
|
96
|
+
state.options.signal.throwIfAborted();
|
|
97
|
+
if (data && new Set(state.options.deferPaths ?? []).has(PRINTED_PAGE_CONTEXT) && Object.hasOwn(data, PRINTED_PAGE_CONTEXT)) {
|
|
98
|
+
refuse(state, element, `the reserved ${PRINTED_PAGE_CONTEXT} context because host data contains that key`, `remove ${PRINTED_PAGE_CONTEXT} from MotifSource.data`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const filled = fill(html, {
|
|
102
|
+
prefix: state.options.prefix,
|
|
103
|
+
data: data ?? {},
|
|
104
|
+
linkOrigins: state.options.linkOrigins,
|
|
105
|
+
dataFonts: state.options.dataFonts,
|
|
106
|
+
registerHrefOrigins: true,
|
|
107
|
+
deferPaths: state.options.deferPaths
|
|
108
|
+
});
|
|
109
|
+
for (const path of filled.unavailable) state.unavailable.add(path);
|
|
110
|
+
return filled.html;
|
|
111
|
+
}
|
|
112
|
+
async function motifFor(element, state) {
|
|
113
|
+
const key = element.attribs[MOTIF_ATTRIBUTE] ?? "";
|
|
114
|
+
if (!key) {
|
|
115
|
+
refuse(state, element, "a motif key", `set ${MOTIF_ATTRIBUTE} to a known motif key`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (key.startsWith(LIBRARY_MOTIF_PREFIX)) {
|
|
119
|
+
refuse(state, element, `a host motif key because "${LIBRARY_MOTIF_PREFIX}" is reserved for library motifs`, "rename the motif key");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const revision = state.options.pins && Object.hasOwn(state.options.pins, key) ? state.options.pins[key] : void 0;
|
|
123
|
+
const cacheKey = JSON.stringify([key, revision]);
|
|
124
|
+
let request = state.motifCache.get(cacheKey);
|
|
125
|
+
if (!request) {
|
|
126
|
+
request = state.options.source.motif(key, {
|
|
127
|
+
...revision === void 0 ? {} : { revision },
|
|
128
|
+
signal: state.options.signal
|
|
129
|
+
});
|
|
130
|
+
state.motifCache.set(cacheKey, request);
|
|
131
|
+
}
|
|
132
|
+
const motif = await request;
|
|
133
|
+
state.options.signal.throwIfAborted();
|
|
134
|
+
if (!motif) {
|
|
135
|
+
refuse(state, element, `motif "${key}"${revision ? ` at revision "${revision}"` : ""}`, "make that motif available from the source");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (motif.key.startsWith(LIBRARY_MOTIF_PREFIX)) {
|
|
139
|
+
refuse(state, element, `a host MotifSource result key because "${LIBRARY_MOTIF_PREFIX}" is reserved for library motifs`, "rename the motif key");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
return motif;
|
|
143
|
+
}
|
|
144
|
+
async function expandPlacement(element, inheritedLang, stack, state) {
|
|
145
|
+
state.options.signal.throwIfAborted();
|
|
146
|
+
state.placements += 1;
|
|
147
|
+
if (state.placements > 256) {
|
|
148
|
+
refuse(state, element, `at most 256 expanded motif placements`, "reduce the number of motif placements");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const motif = await motifFor(element, state);
|
|
152
|
+
if (!motif) return;
|
|
153
|
+
if (stack.includes(motif.key)) {
|
|
154
|
+
refuse(state, element, `an acyclic motif path because "${motif.key}" occurs in ${[...stack, motif.key].join(" -> ")}`, "remove the motif cycle");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (stack.length >= 8) {
|
|
158
|
+
refuse(state, element, `a nesting depth within 8`, "reduce the motif nesting depth");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const values = placementValues(element, motif, inheritedLang, state);
|
|
162
|
+
if (!values) return;
|
|
163
|
+
const choice = chooseMotifVersion(motif, values);
|
|
164
|
+
if (!choice.ok) {
|
|
165
|
+
refuse(state, element, `a unique matching version: ${choice.reason}`, "add or revise a matching motif version or its placement values");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const bound = hasBindings(choice.version.html, state.options.prefix);
|
|
169
|
+
if (bound && state.options.scripts !== "refuse") {
|
|
170
|
+
refuse(state, element, "a data-free motif version for a scripted page", "remove its bindings or refuse page scripts");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (needsHostData(choice.version.html, state.options) && !state.options.source.data) {
|
|
174
|
+
refuse(state, element, "data for the motif version bindings", "provide MotifSource.data or remove the bindings");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const lang = choice.version.lang;
|
|
178
|
+
const fields = Object.fromEntries(Object.entries(values).filter(([key]) => key !== "lang"));
|
|
179
|
+
const placement = {
|
|
180
|
+
key: motif.key,
|
|
181
|
+
revision: motif.revision,
|
|
182
|
+
lang,
|
|
183
|
+
fields
|
|
184
|
+
};
|
|
185
|
+
let versionHtml = sanitize(choice.version.html, {
|
|
186
|
+
linkOrigins: state.options.linkOrigins,
|
|
187
|
+
dataFonts: state.options.dataFonts
|
|
188
|
+
});
|
|
189
|
+
if (bound && !state.validateOnly) {
|
|
190
|
+
const filled = await fillVersion(versionHtml, placement, element, state);
|
|
191
|
+
if (filled === void 0) return;
|
|
192
|
+
versionHtml = filled;
|
|
193
|
+
}
|
|
194
|
+
element.attribs.lang = lang;
|
|
195
|
+
element.attribs.dir = choice.version.dir ?? resolveDirection(void 0, lang, versionHtml);
|
|
196
|
+
replaceChildren(element, versionHtml);
|
|
197
|
+
if (!state.validateOnly) {
|
|
198
|
+
state.placed.push({
|
|
199
|
+
...placement,
|
|
200
|
+
version: choice.index
|
|
201
|
+
});
|
|
202
|
+
for (const font of motif.fonts) state.fonts.set(JSON.stringify(font), font);
|
|
203
|
+
}
|
|
204
|
+
await expandNodes(element.children, lang, [...stack, motif.key], state);
|
|
205
|
+
}
|
|
206
|
+
async function expandNodes(nodes, inheritedLang, stack, state) {
|
|
207
|
+
for (const node of [...nodes]) {
|
|
208
|
+
state.options.signal.throwIfAborted();
|
|
209
|
+
if (!isTag(node)) continue;
|
|
210
|
+
if (Object.hasOwn(node.attribs, MOTIF_ATTRIBUTE)) await expandPlacement(node, inheritedLang, stack, state);
|
|
211
|
+
else await expandNodes(node.children, inheritedLang, stack, state);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async function run(html, options, validateOnly) {
|
|
215
|
+
const document = parse(html);
|
|
216
|
+
const state = {
|
|
217
|
+
options,
|
|
218
|
+
validateOnly,
|
|
219
|
+
refusals: [],
|
|
220
|
+
placed: [],
|
|
221
|
+
fonts: /* @__PURE__ */ new Map(),
|
|
222
|
+
unavailable: /* @__PURE__ */ new Set(),
|
|
223
|
+
motifCache: /* @__PURE__ */ new Map(),
|
|
224
|
+
placements: 0
|
|
225
|
+
};
|
|
226
|
+
await expandNodes(document.children, options.lang, [], state);
|
|
227
|
+
return {
|
|
228
|
+
document,
|
|
229
|
+
state
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
async function expandMotifs(html, options) {
|
|
233
|
+
const { document, state } = await run(html, options, false);
|
|
234
|
+
return {
|
|
235
|
+
html: serialize(document),
|
|
236
|
+
placed: state.placed,
|
|
237
|
+
fonts: [...state.fonts.values()],
|
|
238
|
+
unavailable: [...state.unavailable]
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
async function motifPlacementRefusals(html, options) {
|
|
242
|
+
return (await run(html, options, true)).state.refusals;
|
|
243
|
+
}
|
|
244
|
+
//#endregion
|
|
245
|
+
export { MOTIF_DEPTH_LIMIT, MotifRefusedError, expandMotifs, hasMotifPlacement, libraryMotifPlacementRefusal, motifPlacementRefusals, renderMotifPlacements };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { BookSectionParents as BookSectionParents$1, NumberSystem, PrintedPageRole, PrintedPageRules } from "@segnavia/format";
|
|
2
|
+
//#region src/printed-pages.d.ts
|
|
3
|
+
export type PrintedPageSide = 'left' | 'right';
|
|
4
|
+
export interface PrintedPagePlan {
|
|
5
|
+
index: number;
|
|
6
|
+
role: PrintedPageRole;
|
|
7
|
+
parent: string;
|
|
8
|
+
side: PrintedPageSide;
|
|
9
|
+
number: string;
|
|
10
|
+
total: string;
|
|
11
|
+
}
|
|
12
|
+
export type BookPrintedPageRole = 'recto' | 'verso' | 'opener' | 'blank' | 'fixed';
|
|
13
|
+
export interface BookPageFacts {
|
|
14
|
+
blank: boolean;
|
|
15
|
+
sectionOpening: boolean;
|
|
16
|
+
folio?: string;
|
|
17
|
+
runningHead?: string;
|
|
18
|
+
role?: BookPrintedPageRole | 'body';
|
|
19
|
+
parents?: BookSectionParents$1;
|
|
20
|
+
}
|
|
21
|
+
export interface BookPrintedPagePlan extends Omit<BookPageFacts, 'blank' | 'sectionOpening'> {
|
|
22
|
+
index: number;
|
|
23
|
+
role: BookPrintedPageRole;
|
|
24
|
+
parent: string;
|
|
25
|
+
side: PrintedPageSide;
|
|
26
|
+
}
|
|
27
|
+
export declare function printedPageAt(rules: PrintedPageRules, index: number, count: number, direction: 'ltr' | 'rtl'): Pick<PrintedPagePlan, 'role' | 'parent' | 'side'>;
|
|
28
|
+
export declare function planPrintedPages(rules: PrintedPageRules, count: number, direction: 'ltr' | 'rtl', numbering?: NumberSystem): PrintedPagePlan[];
|
|
29
|
+
export declare function selectBookParent(fact: BookPageFacts, index: number, count: number, direction: 'ltr' | 'rtl', names?: {
|
|
30
|
+
readonly recto: "segnavia:recto";
|
|
31
|
+
readonly verso: "segnavia:verso";
|
|
32
|
+
readonly opener: "segnavia:opener";
|
|
33
|
+
readonly blank: "segnavia:blank";
|
|
34
|
+
}): Pick<BookPrintedPagePlan, 'role' | 'parent' | 'side'>;
|
|
35
|
+
export declare function planBookPages(facts: BookPageFacts[], direction: 'ltr' | 'rtl', names?: {
|
|
36
|
+
readonly recto: "segnavia:recto";
|
|
37
|
+
readonly verso: "segnavia:verso";
|
|
38
|
+
readonly opener: "segnavia:opener";
|
|
39
|
+
readonly blank: "segnavia:blank";
|
|
40
|
+
}): BookPrintedPagePlan[];
|
|
41
|
+
//#endregion
|
|
42
|
+
export type { BookSectionParents$1 as BookSectionParents };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { BOOK_PARENT_NAMES, formatNumber } from "@segnavia/format";
|
|
2
|
+
//#region src/printed-pages.ts
|
|
3
|
+
function printedPageAt(rules, index, count, direction) {
|
|
4
|
+
const recto = index % 2 === 1;
|
|
5
|
+
const side = recto === (direction === "ltr") ? "right" : "left";
|
|
6
|
+
let role = "every";
|
|
7
|
+
let parent = rules.every;
|
|
8
|
+
if (recto && rules.recto) {
|
|
9
|
+
role = "recto";
|
|
10
|
+
parent = rules.recto;
|
|
11
|
+
} else if (!recto && rules.verso) {
|
|
12
|
+
role = "verso";
|
|
13
|
+
parent = rules.verso;
|
|
14
|
+
}
|
|
15
|
+
if (index === count && rules.last) {
|
|
16
|
+
role = "last";
|
|
17
|
+
parent = rules.last;
|
|
18
|
+
}
|
|
19
|
+
if (index === 1 && rules.first) {
|
|
20
|
+
role = "first";
|
|
21
|
+
parent = rules.first;
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
role,
|
|
25
|
+
parent,
|
|
26
|
+
side
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function planPrintedPages(rules, count, direction, numbering = "arabic") {
|
|
30
|
+
if (!Number.isSafeInteger(count) || count < 1) throw new RangeError("A printed-page plan needs a positive safe page count.");
|
|
31
|
+
const total = formatNumber(count, numbering);
|
|
32
|
+
return Array.from({ length: count }, (_, offset) => {
|
|
33
|
+
const index = offset + 1;
|
|
34
|
+
return {
|
|
35
|
+
index,
|
|
36
|
+
...printedPageAt(rules, index, count, direction),
|
|
37
|
+
number: formatNumber(index, numbering),
|
|
38
|
+
total
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function selectBookParent(fact, index, count, direction, names = BOOK_PARENT_NAMES) {
|
|
43
|
+
const physical = printedPageAt({
|
|
44
|
+
every: names.recto,
|
|
45
|
+
recto: names.recto,
|
|
46
|
+
verso: names.verso
|
|
47
|
+
}, index, count, direction);
|
|
48
|
+
const named = fact.parents;
|
|
49
|
+
if (fact.blank) return {
|
|
50
|
+
role: "blank",
|
|
51
|
+
parent: named?.blank ?? names.blank,
|
|
52
|
+
side: physical.side
|
|
53
|
+
};
|
|
54
|
+
if (fact.role === "fixed") return {
|
|
55
|
+
role: "fixed",
|
|
56
|
+
parent: named?.fixed ?? names.blank,
|
|
57
|
+
side: physical.side
|
|
58
|
+
};
|
|
59
|
+
if (fact.sectionOpening && fact.role !== "body") return {
|
|
60
|
+
role: "opener",
|
|
61
|
+
parent: named?.opener ?? names.opener,
|
|
62
|
+
side: physical.side
|
|
63
|
+
};
|
|
64
|
+
if (physical.role === "verso") return {
|
|
65
|
+
role: "verso",
|
|
66
|
+
parent: named?.verso ?? names.verso,
|
|
67
|
+
side: physical.side
|
|
68
|
+
};
|
|
69
|
+
return {
|
|
70
|
+
role: "recto",
|
|
71
|
+
parent: named?.recto ?? names.recto,
|
|
72
|
+
side: physical.side
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function planBookPages(facts, direction, names = BOOK_PARENT_NAMES) {
|
|
76
|
+
return facts.map((fact, offset) => {
|
|
77
|
+
const index = offset + 1;
|
|
78
|
+
const selected = selectBookParent(fact, index, facts.length, direction, names);
|
|
79
|
+
if (selected.role === "blank") return {
|
|
80
|
+
index,
|
|
81
|
+
...selected
|
|
82
|
+
};
|
|
83
|
+
if (selected.role === "opener") return {
|
|
84
|
+
index,
|
|
85
|
+
...selected,
|
|
86
|
+
...fact.folio === void 0 ? {} : { folio: fact.folio }
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
index,
|
|
90
|
+
...selected,
|
|
91
|
+
...fact.folio === void 0 ? {} : { folio: fact.folio },
|
|
92
|
+
...fact.runningHead === void 0 ? {} : { runningHead: fact.runningHead }
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { planBookPages, planPrintedPages, printedPageAt, selectBookParent };
|