@segnavia/html 0.36.0 → 0.37.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,7 +1,5 @@
1
1
  import { BookDocument, BookDocumentOptions } from "./types.js";
2
2
  import { Book } from "@segnavia/format";
3
3
  //#region src/book/book-document.d.ts
4
- export declare const BOOK_COLUMN_MIN = 2;
5
- export declare const BOOK_COLUMN_MAX = 4;
6
4
  export declare function bookDocument(book: Book, options?: BookDocumentOptions): BookDocument;
7
5
  //#endregion
@@ -1,4 +1,5 @@
1
1
  import { elements, parse, serialize } from "../sanitize.js";
2
+ import { bookDocumentCss, bookFlowPageGeometry, markBlockPositions, resolveBookBuild } from "./book-flow.js";
2
3
  import { element, linkChildren, text } from "./dom.js";
3
4
  import { collectHeadings, designedSection, flowAttribs, markPlacementIssues, sectionNode, templateHolders, templateRegionNameIssues, templatedSection, undeclaredRegionIssues } from "./book-held.js";
4
5
  import { endnotesInOrder, noteState, transformNotes } from "./notes.js";
@@ -6,16 +7,13 @@ import { bookPrintPart } from "./types.js";
6
7
  import { isTag } from "domhandler";
7
8
  import { isBookContents, isDesignedSection, isTemplatedSection, noteMarkerIssues, resolveDirection } from "@segnavia/format";
8
9
  //#region src/book/book-document.ts
9
- const CONTENTS_CSS = ":where(.segnavia-contents-list){list-style:none;padding:0;margin:0}:where(.segnavia-contents-entry){display:flex}:where(.segnavia-contents-page){margin-inline-start:auto}:where(.segnavia-contents-entry[data-level=\"2\"]){padding-inline-start:1em}:where(.segnavia-contents-entry[data-level=\"3\"]){padding-inline-start:2em}:where(.segnavia-contents-entry[data-level=\"4\"]){padding-inline-start:3em}:where(.segnavia-contents-entry[data-level=\"5\"]){padding-inline-start:4em}:where(.segnavia-contents-entry[data-level=\"6\"]){padding-inline-start:5em}";
10
- const PRINT_CSS = `${CONTENTS_CSS}@media print{:where(.segnavia-front,.segnavia-contents)~:where(.segnavia-front,.segnavia-contents),:where(.segnavia-chapter,.segnavia-endnotes,.segnavia-blank){break-before:page}:where(.segnavia-blank){break-after:page}:where(.segnavia-targets){position:absolute;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%)}}`;
11
- const GALLEY_CSS = `${CONTENTS_CSS}:where([data-segnavia-galleys]){position:relative}:where([data-segnavia-flow]){orphans:inherit;widows:inherit}:where([data-segnavia-kind="fixed"],[data-segnavia-kind="templated"],[data-segnavia-template]){display:none}:where([data-segnavia-notes-galley]){border-block-start:1px solid;padding-block-start:8px}:where([data-segnavia-note]){break-inside:avoid}:where([data-segnavia-galleys] img,[data-segnavia-window] img,[data-segnavia-page-note] img){display:block;max-block-size:var(--segnavia-text-height);max-inline-size:100%;block-size:auto;inline-size:auto}`;
12
10
  function direction(book, value) {
13
11
  return resolveDirection(book.dir, book.lang, value);
14
12
  }
15
- function authoredSection(section, id, galley = false) {
13
+ function authoredSection(section, id, build) {
16
14
  const document = parse(section.html);
17
15
  const runningHead = section.title;
18
- const node = sectionNode("front", id, document.children, galley, runningHead, flowAttribs(section.opens, section.parents));
16
+ const node = sectionNode("front", id, document.children, build, runningHead, flowAttribs(section.opens, section.parents));
19
17
  return {
20
18
  info: {
21
19
  id,
@@ -26,7 +24,7 @@ function authoredSection(section, id, galley = false) {
26
24
  node
27
25
  };
28
26
  }
29
- function contentsSection(entry, galley = false) {
27
+ function contentsSection(entry, build) {
30
28
  const id = "segnavia-contents";
31
29
  const title = entry.title;
32
30
  const children = title !== void 0 ? [element("h1", {
@@ -40,10 +38,10 @@ function contentsSection(entry, galley = false) {
40
38
  title,
41
39
  runningHead: title
42
40
  },
43
- node: sectionNode("contents", id, children, galley, title, flowAttribs(entry.opens, entry.parents))
41
+ node: sectionNode("contents", id, children, build, title, flowAttribs(entry.opens, entry.parents))
44
42
  };
45
43
  }
46
- function chapterSection(chapter, index, galley = false) {
44
+ function chapterSection(chapter, index, build) {
47
45
  const id = `segnavia-chapter-${index}`;
48
46
  const document = parse(chapter.html);
49
47
  const runningHead = chapter.runningHead ?? chapter.title;
@@ -54,22 +52,22 @@ function chapterSection(chapter, index, galley = false) {
54
52
  title: chapter.title,
55
53
  runningHead
56
54
  },
57
- node: sectionNode("chapter", id, document.children, galley, runningHead, flowAttribs(chapter.opens, chapter.parents))
55
+ node: sectionNode("chapter", id, document.children, build, runningHead, flowAttribs(chapter.opens, chapter.parents))
58
56
  };
59
57
  }
60
- function appendHeld(built, heldHeadings, extraIssues, book, notes, galley, sections, headings, issues, notesOwner = 0) {
58
+ function appendHeld(built, heldHeadings, extraIssues, book, notes, build, sections, headings, issues, notesOwner = 0) {
61
59
  sections.push(built);
62
60
  issues.push(...reservedIdIssues(built), ...columnIssues(built), ...extraIssues);
63
61
  headings.push(...heldHeadings);
64
- transformNotes(built.node, book, 0, notes, galley, notesOwner);
62
+ transformNotes(built.node, book, 0, notes, build === "galley", notesOwner);
65
63
  }
66
- function appendDesigned(entry, id, kind, book, galley, notes, sections, headings, issues, notesOwner = 0) {
67
- const held = designedSection(entry, id, kind, galley, book.contents.depth);
68
- appendHeld(held.built, held.headings, [], book, notes, galley, sections, headings, issues, notesOwner);
64
+ function appendDesigned(entry, id, kind, book, build, notes, sections, headings, issues, notesOwner = 0) {
65
+ const held = designedSection(entry, id, kind, build, book.contents.depth, build === "flow" ? bookFlowPageGeometry(book) : void 0);
66
+ appendHeld(held.built, held.headings, [], book, notes, build, sections, headings, issues, notesOwner);
69
67
  }
70
- function appendTemplated(entry, id, kind, book, galley, notes, templates, sections, headings, issues, notesOwner = 0) {
71
- const held = templatedSection(entry, id, kind, galley, book.contents.depth);
72
- appendHeld(held.built, held.headings, undeclaredRegionIssues(entry, id, templates), book, notes, galley, sections, headings, issues, notesOwner);
68
+ function appendTemplated(entry, id, kind, book, build, notes, templates, sections, headings, issues, notesOwner = 0) {
69
+ const held = templatedSection(entry, id, kind, build, book.contents.depth, templates, build === "flow" ? bookFlowPageGeometry(book) : void 0);
70
+ appendHeld(held.built, held.headings, undeclaredRegionIssues(entry, id, templates), book, notes, build, sections, headings, issues, notesOwner);
73
71
  }
74
72
  function chapterHeadings(section, chapter, depth) {
75
73
  return collectHeadings(section.node.children, section.info.id, depth, 2, (n) => `segnavia-h-${chapter}-${n}`, { n: 0 });
@@ -125,8 +123,6 @@ function reservedIdIssues(section) {
125
123
  return [`authored id ${id} in ${section.info.title ?? section.info.id} (${section.info.id}) uses the reserved segnavia- prefix`];
126
124
  });
127
125
  }
128
- const BOOK_COLUMN_MIN = 2;
129
- const BOOK_COLUMN_MAX = 4;
130
126
  function columnsValueOk(value) {
131
127
  if (!/^[0-9]+$/.test(value)) return false;
132
128
  const count = Number(value);
@@ -147,7 +143,7 @@ function endnotesHeading(book) {
147
143
  dir: direction(book, titleValue)
148
144
  }, [text(titleValue)]);
149
145
  }
150
- function endnotesSection(book, paragraphs, galley, id, runningHead, opens) {
146
+ function endnotesSection(book, paragraphs, build, id, runningHead, opens) {
151
147
  return {
152
148
  info: {
153
149
  id,
@@ -155,7 +151,7 @@ function endnotesSection(book, paragraphs, galley, id, runningHead, opens) {
155
151
  title: book.endnotes.title,
156
152
  runningHead
157
153
  },
158
- node: sectionNode("endnotes", id, [endnotesHeading(book), ...paragraphs], galley, runningHead, opens ? flowAttribs(opens) : {})
154
+ node: sectionNode("endnotes", id, [endnotesHeading(book), ...paragraphs], build, runningHead, opens ? flowAttribs(opens) : {})
159
155
  };
160
156
  }
161
157
  function notesGalley(paragraphs) {
@@ -182,29 +178,29 @@ function pushChapterEndnotes(book, build, id, owner) {
182
178
  const paragraphs = build.notes.chapterEndnotes.get(owner) ?? [];
183
179
  if (paragraphs.length === 0) return;
184
180
  const runningHead = build.sections.at(-1)?.info.runningHead;
185
- build.sections.push(endnotesSection(book, paragraphs, build.galley, id, runningHead, "any"));
181
+ build.sections.push(endnotesSection(book, paragraphs, build.build, id, runningHead, "any"));
186
182
  }
187
183
  function addFrontMatter(book, build) {
188
184
  let contents;
189
185
  let frontNumber = 0;
190
186
  for (const entry of book.frontMatter) {
191
187
  if (isBookContents(entry)) {
192
- contents = contentsSection(entry, build.galley);
188
+ contents = contentsSection(entry, build.build);
193
189
  build.sections.push(contents);
194
190
  continue;
195
191
  }
196
192
  if (isDesignedSection(entry)) {
197
193
  build.designedNumber += 1;
198
- appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "front", book, build.galley, build.notes, build.sections, build.headings, build.issues);
194
+ appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "front", book, build.build, build.notes, build.sections, build.headings, build.issues);
199
195
  continue;
200
196
  }
201
197
  if (isTemplatedSection(entry)) {
202
198
  build.templatedNumber += 1;
203
- appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "front", book, build.galley, build.notes, build.templates, build.sections, build.headings, build.issues);
199
+ appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "front", book, build.build, build.notes, build.templates, build.sections, build.headings, build.issues);
204
200
  continue;
205
201
  }
206
202
  frontNumber += 1;
207
- const section = authoredSection(entry, `segnavia-front-${frontNumber}`, build.galley);
203
+ const section = authoredSection(entry, `segnavia-front-${frontNumber}`, build.build);
208
204
  build.sections.push(section);
209
205
  build.issues.push(...reservedIdIssues(section), ...columnIssues(section));
210
206
  if (entry.contents && entry.title !== void 0) build.headings.push({
@@ -213,7 +209,7 @@ function addFrontMatter(book, build) {
213
209
  text: entry.title,
214
210
  sectionId: section.info.id
215
211
  });
216
- transformNotes(section.node, book, 0, build.notes, build.galley);
212
+ transformNotes(section.node, book, 0, build.notes, build.build === "galley");
217
213
  }
218
214
  return contents;
219
215
  }
@@ -223,19 +219,19 @@ function addChapters(book, build) {
223
219
  if (isDesignedSection(entry)) {
224
220
  build.designedNumber += 1;
225
221
  const owner = takeHeldNotesOwner(build);
226
- appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "designed", book, build.galley, build.notes, build.sections, build.headings, build.issues, owner);
222
+ appendDesigned(entry, `segnavia-designed-${build.designedNumber}`, "designed", book, build.build, build.notes, build.sections, build.headings, build.issues, owner);
227
223
  pushChapterEndnotes(book, build, `segnavia-endnotes-designed-${build.designedNumber}`, owner);
228
224
  continue;
229
225
  }
230
226
  if (isTemplatedSection(entry)) {
231
227
  build.templatedNumber += 1;
232
228
  const owner = takeHeldNotesOwner(build);
233
- appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "templated", book, build.galley, build.notes, build.templates, build.sections, build.headings, build.issues, owner);
229
+ appendTemplated(entry, `segnavia-templated-${build.templatedNumber}`, "templated", book, build.build, build.notes, build.templates, build.sections, build.headings, build.issues, owner);
234
230
  pushChapterEndnotes(book, build, `segnavia-endnotes-templated-${build.templatedNumber}`, owner);
235
231
  continue;
236
232
  }
237
233
  chapterNumber += 1;
238
- const section = chapterSection(entry, chapterNumber, build.galley);
234
+ const section = chapterSection(entry, chapterNumber, build.build);
239
235
  build.sections.push(section);
240
236
  build.issues.push(...reservedIdIssues(section), ...columnIssues(section));
241
237
  build.headings.push({
@@ -244,13 +240,14 @@ function addChapters(book, build) {
244
240
  text: entry.title,
245
241
  sectionId: section.info.id
246
242
  }, ...chapterHeadings(section, chapterNumber, book.contents.depth));
247
- transformNotes(section.node, book, chapterNumber, build.notes, build.galley);
243
+ transformNotes(section.node, book, chapterNumber, build.notes, build.build === "galley");
248
244
  pushChapterEndnotes(book, build, `segnavia-endnotes-${chapterNumber}`, chapterNumber);
249
245
  }
250
246
  }
251
247
  function bookDocument(book, options = {}) {
248
+ const { build: requestedBuild, issue: buildIssue } = resolveBookBuild(book, options.build);
252
249
  const build = {
253
- galley: book.mode === "paginated",
250
+ build: requestedBuild,
254
251
  notes: noteState(),
255
252
  sections: [],
256
253
  headings: [],
@@ -262,20 +259,21 @@ function bookDocument(book, options = {}) {
262
259
  };
263
260
  const contents = addFrontMatter(book, build);
264
261
  addChapters(book, build);
265
- const { galley, notes, sections, headings, issues } = build;
262
+ const { notes, sections, headings, issues } = build;
266
263
  if (book.endnotes.placement === "chapter") {
267
264
  const leftover = notes.chapterEndnotes.get(0) ?? [];
268
- if (leftover.length > 0) sections.push(endnotesSection(book, leftover, galley, "segnavia-endnotes", book.endnotes.title));
269
- } else if (notes.endnotes) sections.push(endnotesSection(book, endnotesInOrder(notes), galley, "segnavia-endnotes", book.endnotes.title));
265
+ if (leftover.length > 0) sections.push(endnotesSection(book, leftover, requestedBuild, "segnavia-endnotes", book.endnotes.title));
266
+ } else if (notes.endnotes) sections.push(endnotesSection(book, endnotesInOrder(notes), requestedBuild, "segnavia-endnotes", book.endnotes.title));
270
267
  if (contents) addContents(book, contents, headings, options);
268
+ if (requestedBuild !== "print") markBlockPositions(sections);
271
269
  const visible = sections.filter((section) => options.part === void 0 || bookPrintPart(section.info.kind) === options.part);
272
270
  const document = parse("");
273
- document.children = galley ? [element("style", {}, [text(GALLEY_CSS)]), element("div", { "data-segnavia-galleys": "" }, [
271
+ document.children = requestedBuild === "galley" ? [element("style", {}, [text(bookDocumentCss(book, requestedBuild))]), element("div", { "data-segnavia-galleys": "" }, [
274
272
  ...templateHolders(book.templates),
275
273
  ...visible.map((section) => section.node),
276
274
  ...notes.footnoteParagraphs.length > 0 ? [notesGalley(notes.footnoteParagraphs)] : []
277
275
  ])] : [
278
- element("style", {}, [text(PRINT_CSS)]),
276
+ element("style", {}, [text(bookDocumentCss(book, requestedBuild))]),
279
277
  ...options.targets ? [targetLinks(visible)] : [],
280
278
  ...withBlankPages(visible, options.blankBefore)
281
279
  ];
@@ -285,6 +283,7 @@ function bookDocument(book, options = {}) {
285
283
  sections: sections.map((section) => section.info),
286
284
  headings,
287
285
  issues: [
286
+ ...buildIssue ? [buildIssue] : [],
288
287
  ...noteMarkerIssues(book, {
289
288
  footnotes: notes.footnotes,
290
289
  endnotes: notes.endnotes
@@ -297,4 +296,4 @@ function bookDocument(book, options = {}) {
297
296
  };
298
297
  }
299
298
  //#endregion
300
- export { BOOK_COLUMN_MAX, BOOK_COLUMN_MIN, bookDocument };
299
+ export { bookDocument };
@@ -0,0 +1,13 @@
1
+ import { Element } from "domhandler";
2
+ import { Book } from "@segnavia/format";
3
+ //#region src/book/book-flow.d.ts
4
+ export declare const BOOK_FLOW_COLUMN_MIN_PX = 240;
5
+ export declare const BOOK_COLUMN_MIN = 2;
6
+ export declare const BOOK_COLUMN_MAX = 4;
7
+ export interface BookPositionKey {
8
+ flow: string;
9
+ index: number;
10
+ suffix?: string;
11
+ }
12
+ export declare function parseBookPositionKey(value: string): BookPositionKey | undefined;
13
+ //#endregion
@@ -0,0 +1,80 @@
1
+ import { isTag } from "domhandler";
2
+ import { PX_PER_MM, sheetMm } from "@segnavia/format";
3
+ //#region src/book/book-flow.ts
4
+ const BOOK_FLOW_COLUMN_MIN_PX = 240;
5
+ const BOOK_COLUMN_MIN = 2;
6
+ const BOOK_COLUMN_MAX = 4;
7
+ const CONTENTS_CSS = ":where(.segnavia-contents-list){list-style:none;padding:0;margin:0}:where(.segnavia-contents-entry){display:flex}:where(.segnavia-contents-page){margin-inline-start:auto}:where(.segnavia-contents-entry[data-level=\"2\"]){padding-inline-start:1em}:where(.segnavia-contents-entry[data-level=\"3\"]){padding-inline-start:2em}:where(.segnavia-contents-entry[data-level=\"4\"]){padding-inline-start:3em}:where(.segnavia-contents-entry[data-level=\"5\"]){padding-inline-start:4em}:where(.segnavia-contents-entry[data-level=\"6\"]){padding-inline-start:5em}";
8
+ const PRINT_CSS = `${CONTENTS_CSS}@media print{:where(.segnavia-front,.segnavia-contents)~:where(.segnavia-front,.segnavia-contents),:where(.segnavia-chapter,.segnavia-endnotes,.segnavia-blank){break-before:page}:where(.segnavia-blank){break-after:page}:where(.segnavia-targets){position:absolute;inline-size:1px;block-size:1px;overflow:hidden;clip-path:inset(50%)}}`;
9
+ const GALLEY_CSS = `${CONTENTS_CSS}:where([data-segnavia-galleys]){position:relative}:where([data-segnavia-flow]){orphans:inherit;widows:inherit}:where([data-segnavia-kind="fixed"],[data-segnavia-kind="templated"],[data-segnavia-template]){display:none}:where([data-segnavia-notes-galley]){border-block-start:1px solid;padding-block-start:8px}:where([data-segnavia-note]){break-inside:avoid}:where([data-segnavia-galleys] img,[data-segnavia-window] img,[data-segnavia-page-note] img){display:block;max-block-size:var(--segnavia-text-height);max-inline-size:100%;block-size:auto;inline-size:auto}`;
10
+ function bookFlowPageGeometry(book) {
11
+ if (!book.page) return void 0;
12
+ const paper = sheetMm(book.page);
13
+ return {
14
+ paperInline: paper.width * PX_PER_MM,
15
+ paperBlock: paper.height * PX_PER_MM,
16
+ textInline: (paper.width - book.page.margin.inner - book.page.margin.outer) * PX_PER_MM,
17
+ textBlock: (paper.height - book.page.margin.top - book.page.margin.bottom) * PX_PER_MM,
18
+ marginBlockStart: book.page.margin.top * PX_PER_MM,
19
+ marginInlineStartRecto: book.page.margin.inner * PX_PER_MM,
20
+ marginInlineStartVerso: book.page.margin.outer * PX_PER_MM
21
+ };
22
+ }
23
+ function columnFallbacks(gap) {
24
+ return Array.from({ length: 3 }, (_, index) => index + 2).map((count) => {
25
+ return `@container (max-width:${count * 240 + (count - 1) * gap}px){:where([data-columns="${count}"]){column-count:1}}`;
26
+ }).join("");
27
+ }
28
+ function flowCss(book) {
29
+ const gap = (book.page?.columnGap ?? 0) * PX_PER_MM;
30
+ const columns = Array.from({ length: 3 }, (_, index) => index + 2).map((count) => `:where([data-columns="${count}"]){column-count:${count}}`).join("");
31
+ return `${CONTENTS_CSS}:where([data-segnavia-flow]){container-type:inline-size}:where([data-columns]){column-gap:${gap}px}${columns}:where([data-segnavia-flow-page]){inline-size:100%;container-type:inline-size;margin-inline:0}:where([data-segnavia-flow-page-size]){position:relative;inline-size:100cqi;block-size:calc(100cqi * var(--segnavia-paper-ratio));overflow:hidden}:where([data-segnavia-flow-paper]){position:absolute;inset-block-start:0;inset-inline-start:0;inline-size:var(--segnavia-paper-inline);block-size:var(--segnavia-paper-block);overflow:hidden;zoom:var(--segnavia-flow-scale,calc(100cqi / var(--segnavia-paper-inline)))}:where([data-segnavia-flow-text]){position:absolute;inset-block-start:var(--segnavia-text-block-start);inset-inline-start:var(--segnavia-text-inline-start);inline-size:var(--segnavia-text-inline);block-size:var(--segnavia-text-block);container-type:inline-size}${columnFallbacks(gap)}`;
32
+ }
33
+ function resolveBookBuild(book, requested) {
34
+ const build = requested ?? (book.mode === "paginated" ? "galley" : "print");
35
+ if (book.mode === "paginated" || build === "print") return { build };
36
+ return {
37
+ build,
38
+ issue: `a continuous book cannot use the ${build} build; request the print build, or make the book paginated with a page to clear this`
39
+ };
40
+ }
41
+ function bookDocumentCss(book, build) {
42
+ if (build === "galley") return GALLEY_CSS;
43
+ return build === "flow" ? flowCss(book) : PRINT_CSS;
44
+ }
45
+ function flowPageStyle(geometry, recto) {
46
+ const marginInlineStart = recto ? geometry.marginInlineStartRecto : geometry.marginInlineStartVerso;
47
+ return `--segnavia-paper-inline:${geometry.paperInline}px;--segnavia-paper-block:${geometry.paperBlock}px;--segnavia-paper-ratio:${geometry.paperBlock / geometry.paperInline};--segnavia-text-inline:${geometry.textInline}px;--segnavia-text-block:${geometry.textBlock}px;--segnavia-text-block-start:${geometry.marginBlockStart}px;--segnavia-text-inline-start:${marginInlineStart}px`;
48
+ }
49
+ function bookBlockPosition(flowId, block, suffix) {
50
+ return `${flowId}:${block}${suffix ? `:${suffix}` : ""}`;
51
+ }
52
+ function parseBookPositionKey(value) {
53
+ const match = /^(.*):(\d+)(?::([^:]+))?$/.exec(value);
54
+ if (!match?.[1] || !match[2]) return void 0;
55
+ return {
56
+ flow: match[1],
57
+ index: Number(match[2]),
58
+ ...match[3] ? { suffix: match[3] } : {}
59
+ };
60
+ }
61
+ function isFootnoteBlock(node) {
62
+ return (node.attribs.class ?? "").split(/\s+/).includes("segnavia-footnotes");
63
+ }
64
+ function markBlockPositions(sections) {
65
+ for (const section of sections) {
66
+ let position = 0;
67
+ for (const child of section.node.children) {
68
+ if (!isTag(child)) continue;
69
+ if (child.name === "style" || child.name === "script") continue;
70
+ if (isFootnoteBlock(child)) {
71
+ child.attribs["data-segnavia-position"] = bookBlockPosition(section.info.id, position, "footnotes");
72
+ continue;
73
+ }
74
+ position += 1;
75
+ child.attribs["data-segnavia-position"] = bookBlockPosition(section.info.id, position);
76
+ }
77
+ }
78
+ }
79
+ //#endregion
80
+ export { BOOK_COLUMN_MAX, BOOK_COLUMN_MIN, BOOK_FLOW_COLUMN_MIN_PX, bookBlockPosition, bookDocumentCss, bookFlowPageGeometry, flowPageStyle, markBlockPositions, parseBookPositionKey, resolveBookBuild };
@@ -1,5 +1,6 @@
1
1
  import { elements, parse } from "../sanitize.js";
2
- import { element, textContent } from "./dom.js";
2
+ import { flowPageStyle } from "./book-flow.js";
3
+ import { element, linkChildren, textContent } from "./dom.js";
3
4
  import { BOOK_FURNITURE, MOTIF_ATTRIBUTE, REGION_NAME, motifFieldAttribute } from "@segnavia/format";
4
5
  //#region src/book/book-held.ts
5
6
  function flowAttribs(opens, parents, extra = {}) {
@@ -18,16 +19,24 @@ function flowAttribs(opens, parents, extra = {}) {
18
19
  }
19
20
  return attribs;
20
21
  }
21
- function sectionNode(kind, id, children = [], galley = false, runningHead, extra = {}) {
22
+ function sectionNode(kind, id, children = [], build, runningHead, extra = {}) {
22
23
  const attribs = {
23
24
  class: `segnavia-section segnavia-${kind}`,
24
25
  id,
25
26
  ...extra
26
27
  };
27
- if (galley) attribs["data-segnavia-flow"] = id;
28
- if (galley && runningHead !== void 0) attribs["data-running-head"] = runningHead;
28
+ if (build !== "print") attribs["data-segnavia-flow"] = id;
29
+ if (build === "galley" && runningHead !== void 0) attribs["data-running-head"] = runningHead;
29
30
  return element("section", attribs, children);
30
31
  }
32
+ function flowPage(page, kind, children, geometry, recto) {
33
+ const paper = element("div", { "data-segnavia-flow-paper": "" }, children);
34
+ return element("figure", {
35
+ [`data-segnavia-${kind}-page`]: String(page),
36
+ "data-segnavia-flow-page": "",
37
+ style: flowPageStyle(geometry, recto)
38
+ }, [element("div", { "data-segnavia-flow-page-size": "" }, [paper])]);
39
+ }
31
40
  function collectHeadings(children, sectionId, depth, firstLevel, idFor, generated) {
32
41
  return elements(children).flatMap((heading) => {
33
42
  const match = /^h([1-6])$/.exec(heading.name);
@@ -48,17 +57,18 @@ function collectHeadings(children, sectionId, depth, firstLevel, idFor, generate
48
57
  function heldHeadings(children, sectionId, depth, generated) {
49
58
  return collectHeadings(children, sectionId, depth, 1, (n) => `segnavia-h-${sectionId.replace(/^segnavia-/, "")}-${n}`, generated);
50
59
  }
51
- function designedSection(entry, id, kind, galley, depth) {
60
+ function designedSection(entry, id, kind, build, depth, geometry) {
52
61
  const extra = flowAttribs(entry.opens, entry.parents, {
53
62
  "data-segnavia-kind": "fixed",
54
63
  ...kind === "front" ? { "data-sequence": "front" } : {}
55
64
  });
56
65
  const headings = [];
57
66
  const generated = { n: 0 };
67
+ const firstRecto = entry.opens !== "verso";
58
68
  const holders = entry.pages.map((page, offset) => {
59
69
  const document = parse(page.html);
60
70
  headings.push(...heldHeadings(document.children, id, depth, generated));
61
- return element("div", {
71
+ return build === "flow" && geometry ? flowPage(offset + 1, "designed", document.children, geometry, offset % 2 === 0 ? firstRecto : !firstRecto) : element("div", {
62
72
  "data-segnavia-designed-page": String(offset + 1),
63
73
  hidden: ""
64
74
  }, document.children);
@@ -78,7 +88,7 @@ function designedSection(entry, id, kind, galley, depth) {
78
88
  title: entry.title,
79
89
  runningHead
80
90
  },
81
- node: sectionNode(kind, id, holders, galley, runningHead, extra)
91
+ node: sectionNode(kind, id, holders, build, runningHead, extra)
82
92
  },
83
93
  headings
84
94
  };
@@ -127,7 +137,7 @@ function undeclaredRegionIssues(entry, id, templates) {
127
137
  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`]);
128
138
  });
129
139
  }
130
- function templatedSection(entry, id, kind, galley, depth) {
140
+ function templatedSection(entry, id, kind, build, depth, templates, geometry) {
131
141
  const extra = flowAttribs(entry.opens, entry.parents, {
132
142
  "data-segnavia-kind": "templated",
133
143
  "data-template": entry.template,
@@ -135,16 +145,34 @@ function templatedSection(entry, id, kind, galley, depth) {
135
145
  });
136
146
  const headings = [];
137
147
  const generated = { n: 0 };
148
+ const firstRecto = entry.opens !== "verso";
138
149
  const holders = entry.pages.map((page, offset) => {
139
150
  const name = page.template ?? entry.template;
151
+ const regionChildren = /* @__PURE__ */ new Map();
140
152
  const regions = Object.entries(page.regions).map(([region, html]) => {
141
153
  const document = parse(html);
142
154
  headings.push(...heldHeadings(document.children, id, depth, generated));
155
+ regionChildren.set(region, document.children);
143
156
  return element("div", {
144
157
  "data-region": region,
145
158
  hidden: ""
146
159
  }, document.children);
147
160
  });
161
+ if (build === "flow" && geometry) {
162
+ const template = templates?.get(name);
163
+ const document = parse(template?.html ?? "");
164
+ for (const [region, children] of regionChildren) {
165
+ const target = elements(document.children).find((node) => node.attribs["data-region"] === region);
166
+ if (!target) continue;
167
+ target.children = children;
168
+ linkChildren(target);
169
+ }
170
+ const text = element("div", {
171
+ "data-segnavia-flow-text": "",
172
+ "data-template": name
173
+ }, document.children);
174
+ return flowPage(offset + 1, "templated", [text], geometry, offset % 2 === 0 ? firstRecto : !firstRecto);
175
+ }
148
176
  return element("div", {
149
177
  "data-segnavia-templated-page": String(offset + 1),
150
178
  "data-template": name,
@@ -166,7 +194,7 @@ function templatedSection(entry, id, kind, galley, depth) {
166
194
  title: entry.title,
167
195
  runningHead
168
196
  },
169
- node: sectionNode(kind, id, holders, galley, runningHead, extra)
197
+ node: sectionNode(kind, id, holders, build, runningHead, extra)
170
198
  },
171
199
  headings
172
200
  };
@@ -19,8 +19,14 @@ function bookViewFromHtml(book, html) {
19
19
  }
20
20
  function bookView(book) {
21
21
  const document = bookDocument(book);
22
- if (document.issues.length > 0) throw new Error(`The book cannot be shown: ${document.issues.join("; ")}.`);
23
- return bookViewFromHtml(book, document.html);
22
+ const flow = book.mode === "paginated" ? bookDocument(book, { build: "flow" }) : void 0;
23
+ const issues = document.issues.length > 0 ? document.issues : flow?.issues ?? [];
24
+ if (issues.length > 0) throw new Error(`The book cannot be shown: ${issues.join("; ")}.`);
25
+ const view = bookViewFromHtml(book, document.html);
26
+ return flow ? {
27
+ ...view,
28
+ flow: flow.html
29
+ } : view;
24
30
  }
25
31
  //#endregion
26
32
  export { bookView, bookViewFromHtml };
@@ -62,7 +62,7 @@ function paginateKeeps(placed, next, textHeight, orphans, widows, allowEmpty) {
62
62
  return kept(countOf(held, block) - need <= 0 ? dropBlock(held, block) : held.slice(0, held.length - need));
63
63
  }
64
64
  function rewindHeading(held, last, following) {
65
- const other = !following || !sameBlock(following.block, last.block);
65
+ const other = following && !sameBlock(following.block, last.block);
66
66
  if (!keepWithNext(last.block) || countOf(held, last.block) !== last.block.lines.length || !other) return void 0;
67
67
  return kept(dropBlock(held, last.block));
68
68
  }
@@ -70,7 +70,8 @@ function paginateKeeps(placed, next, textHeight, orphans, widows, allowEmpty) {
70
70
  for (let pass = 0; pass < 8; pass += 1) {
71
71
  const last = result[result.length - 1];
72
72
  if (!last) return result;
73
- const nextResult = rewindTogether(result, last) ?? rewindOrphans(result, last) ?? rewindWidows(result, last) ?? rewindHeading(result, last, next);
73
+ const following = placed[result.length] ?? next;
74
+ const nextResult = rewindTogether(result, last) ?? rewindOrphans(result, last) ?? rewindWidows(result, last) ?? rewindHeading(result, last, following);
74
75
  if (!nextResult) return result;
75
76
  result = nextResult;
76
77
  }
@@ -5,7 +5,9 @@ export interface BookDocumentOptions {
5
5
  part?: 'front' | 'body';
6
6
  crossLinks?: boolean;
7
7
  targets?: boolean;
8
+ build?: BookBuild;
8
9
  }
10
+ export type BookBuild = 'print' | 'galley' | 'flow';
9
11
  export interface BookSectionInfo {
10
12
  id: string;
11
13
  kind: 'front' | 'contents' | 'chapter' | 'endnotes' | 'designed' | 'templated';
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { base64ToBytes, bytesToBase64 } from "./base64.js";
2
2
  import { REFUSED_ELEMENTS, SanitizeOptions, findActiveContent, sanitize } from "./sanitize.js";
3
3
  import { BINDINGS, FillOptions, Filled, bindingNames, bindingRoot, fill, hasBindings } from "./bindings.js";
4
- import { BookDocument, BookDocumentOptions, BookHeading, BookPrintPart, BookSectionInfo, bookPrintPart } from "./book/types.js";
5
- import { BOOK_COLUMN_MAX, BOOK_COLUMN_MIN, bookDocument } from "./book/book-document.js";
4
+ import { BookBuild, BookDocument, BookDocumentOptions, BookHeading, BookPrintPart, BookSectionInfo, bookPrintPart } from "./book/types.js";
5
+ import { bookDocument } from "./book/book-document.js";
6
+ import { BOOK_COLUMN_MAX, BOOK_COLUMN_MIN, BOOK_FLOW_COLUMN_MIN_PX, BookPositionKey, parseBookPositionKey } from "./book/book-flow.js";
6
7
  import { bookView, bookViewFromHtml } from "./book/book-view.js";
7
8
  import { PaginateBlock, PaginateBookInput, PaginateBookPage, PaginateBookRefusal, PaginateBookResult, PaginateFlow, PaginateFlowParents, PaginateLine, PaginateMark, PaginateNote, PaginateNoteRef, PaginateOpens, PaginatePageMarks, PaginatePageNote, PaginatePageRole, PaginateSequence, PaginateWindow } from "./book/paginate-types.js";
8
9
  import { BookFurnitureValues, embedFunction, fillBookFurniture, furnitureScript } from "./book/furniture.js";
@@ -23,4 +24,4 @@ export type HtmlKind = 'plain' | 'live-data' | 'scripted' | 'refused';
23
24
  export declare function classify(html: string, prefix: string): HtmlKind;
24
25
  export declare function searchText(html: string): string;
25
26
  //#endregion
26
- export { BINDINGS, BOOK_COLUMN_MAX, BOOK_COLUMN_MIN, type BookDocument, type BookDocumentOptions, type BookFurnitureValues, type BookHeading, BookPageFacts, type BookPrintPart, BookPrintedPagePlan, BookPrintedPageRole, type BookSectionInfo, type BookSectionParents, type ExpandMotifsOptions, type ExpandedMotifs, type FillOptions, type Filled, FontCssRefusedError, MAX_SLOT_DECLARATIONS, MOTIF_DEPTH_LIMIT, MotifRefusedError, PAGINATE_FUNCTIONS, type PaginateBlock, type PaginateBookInput, type PaginateBookPage, type PaginateBookRefusal, type PaginateBookResult, type PaginateFlow, type PaginateFlowParents, type PaginateLine, type PaginateMark, type PaginateNote, type PaginateNoteRef, type PaginateOpens, type PaginatePageMarks, type PaginatePageNote, type PaginatePageRole, type PaginateSequence, type PaginateTarget, type PaginateTargetPage, type PaginateWindow, type PlacedMotif, type PreparedSlots, PrintedPagePlan, PrintedPageSide, REFUSED_ELEMENTS, RequiredSlotsRefusedError, type SanitizeOptions, type SlotFillEntry, type SlotFillRefusal, SlotFillRefusedError, SlotFillsConflictError, SlotFillsRefusedError, type SlotPrintOptions, UNICODE_RANGE_CLEARANCE, type UnicodeRangeParse, UnmeasuredSlotsRefusedError, assertSingleSlotCopy, assertSlotPrintOptions, base64ToBytes, bindingNames, bindingRoot, bookDocument, bookPrintPart, bookStylesheet, bookView, bookViewFromHtml, bytesToBase64, collectSlotPrintCopies, documentLanguage, embedFunction, expandMotifs, fill, fillBookFurniture, findActiveContent, fontCss, fontFaceCss, fontFormat, fontReferenceRefusals, furnitureScript, hasBindings, hasMotifPlacement, hasSlotWork, heldPageNumber, isSlotCopyRefusal, libraryMotifPlacementRefusal, mergeSlotFitResults, motifPlacementRefusals, openerPageIndex, pageIndexForTarget, paginateBook, paginateScript, parseUnicodeRange, planBookPages, planPrintedPages, prepareSlotCollection, prepareSlots, printedPageAt, renderMotifPlacements, requiredSlotRefusal, sanitize, sanitizeBookCss, selectBookParent, slotFillCopyId, slotFillRefusal, slotFillsConflict, unmeasuredSlotRefusal, windowPageIndex };
27
+ export { BINDINGS, BOOK_COLUMN_MAX, BOOK_COLUMN_MIN, BOOK_FLOW_COLUMN_MIN_PX, type BookBuild, type BookDocument, type BookDocumentOptions, type BookFurnitureValues, type BookHeading, BookPageFacts, type BookPositionKey, type BookPrintPart, BookPrintedPagePlan, BookPrintedPageRole, type BookSectionInfo, type BookSectionParents, type ExpandMotifsOptions, type ExpandedMotifs, type FillOptions, type Filled, FontCssRefusedError, MAX_SLOT_DECLARATIONS, MOTIF_DEPTH_LIMIT, MotifRefusedError, PAGINATE_FUNCTIONS, type PaginateBlock, type PaginateBookInput, type PaginateBookPage, type PaginateBookRefusal, type PaginateBookResult, type PaginateFlow, type PaginateFlowParents, type PaginateLine, type PaginateMark, type PaginateNote, type PaginateNoteRef, type PaginateOpens, type PaginatePageMarks, type PaginatePageNote, type PaginatePageRole, type PaginateSequence, type PaginateTarget, type PaginateTargetPage, type PaginateWindow, type PlacedMotif, type PreparedSlots, PrintedPagePlan, PrintedPageSide, REFUSED_ELEMENTS, RequiredSlotsRefusedError, type SanitizeOptions, type SlotFillEntry, type SlotFillRefusal, SlotFillRefusedError, SlotFillsConflictError, SlotFillsRefusedError, type SlotPrintOptions, UNICODE_RANGE_CLEARANCE, type UnicodeRangeParse, UnmeasuredSlotsRefusedError, assertSingleSlotCopy, assertSlotPrintOptions, base64ToBytes, bindingNames, bindingRoot, bookDocument, bookPrintPart, bookStylesheet, bookView, bookViewFromHtml, bytesToBase64, collectSlotPrintCopies, documentLanguage, embedFunction, expandMotifs, fill, fillBookFurniture, findActiveContent, fontCss, fontFaceCss, fontFormat, fontReferenceRefusals, furnitureScript, hasBindings, hasMotifPlacement, hasSlotWork, heldPageNumber, isSlotCopyRefusal, libraryMotifPlacementRefusal, mergeSlotFitResults, motifPlacementRefusals, openerPageIndex, pageIndexForTarget, paginateBook, paginateScript, parseBookPositionKey, parseUnicodeRange, planBookPages, planPrintedPages, prepareSlotCollection, prepareSlots, printedPageAt, renderMotifPlacements, requiredSlotRefusal, sanitize, sanitizeBookCss, selectBookParent, slotFillCopyId, slotFillRefusal, slotFillsConflict, unmeasuredSlotRefusal, windowPageIndex };
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { REFUSED_ELEMENTS, elements, findActiveContent, parse, sanitize } from "./sanitize.js";
2
2
  import { BINDINGS, bindingNames, bindingRoot, fill, hasBindings } from "./bindings.js";
3
3
  import { base64ToBytes, bytesToBase64 } from "./base64.js";
4
+ import { BOOK_COLUMN_MAX, BOOK_COLUMN_MIN, BOOK_FLOW_COLUMN_MIN_PX, parseBookPositionKey } from "./book/book-flow.js";
4
5
  import { bookPrintPart } from "./book/types.js";
5
- import { BOOK_COLUMN_MAX, BOOK_COLUMN_MIN, bookDocument } from "./book/book-document.js";
6
+ import { bookDocument } from "./book/book-document.js";
6
7
  import { bookView, bookViewFromHtml } from "./book/book-view.js";
7
8
  import { embedFunction, fillBookFurniture, furnitureScript } from "./book/furniture.js";
8
9
  import { PAGINATE_FUNCTIONS, paginateBook, paginateScript } from "./book/paginate-book.js";
@@ -29,4 +30,4 @@ function searchText(html) {
29
30
  return html.replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, " ").replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
30
31
  }
31
32
  //#endregion
32
- export { BINDINGS, BOOK_COLUMN_MAX, BOOK_COLUMN_MIN, FontCssRefusedError, MAX_SLOT_DECLARATIONS, MOTIF_DEPTH_LIMIT, MotifRefusedError, PAGINATE_FUNCTIONS, REFUSED_ELEMENTS, RequiredSlotsRefusedError, SlotFillRefusedError, SlotFillsConflictError, SlotFillsRefusedError, UNICODE_RANGE_CLEARANCE, UnmeasuredSlotsRefusedError, assertSingleSlotCopy, assertSlotPrintOptions, base64ToBytes, bindingNames, bindingRoot, bookDocument, bookPrintPart, bookStylesheet, bookView, bookViewFromHtml, bytesToBase64, classify, collectSlotPrintCopies, documentLanguage, embedFunction, expandMotifs, fill, fillBookFurniture, findActiveContent, fontCss, fontFaceCss, fontFormat, fontReferenceRefusals, furnitureScript, hasBindings, hasMotifPlacement, hasSlotWork, heldPageNumber, isSlotCopyRefusal, libraryMotifPlacementRefusal, mergeSlotFitResults, motifPlacementRefusals, openerPageIndex, pageIndexForTarget, paginateBook, paginateScript, parseUnicodeRange, planBookPages, planPrintedPages, prepareSlotCollection, prepareSlots, printedPageAt, renderMotifPlacements, requiredSlotRefusal, sanitize, sanitizeBookCss, searchText, selectBookParent, slotFillCopyId, slotFillRefusal, slotFillsConflict, unmeasuredSlotRefusal, windowPageIndex };
33
+ export { BINDINGS, BOOK_COLUMN_MAX, BOOK_COLUMN_MIN, BOOK_FLOW_COLUMN_MIN_PX, FontCssRefusedError, MAX_SLOT_DECLARATIONS, MOTIF_DEPTH_LIMIT, MotifRefusedError, PAGINATE_FUNCTIONS, REFUSED_ELEMENTS, RequiredSlotsRefusedError, SlotFillRefusedError, SlotFillsConflictError, SlotFillsRefusedError, UNICODE_RANGE_CLEARANCE, UnmeasuredSlotsRefusedError, assertSingleSlotCopy, assertSlotPrintOptions, base64ToBytes, bindingNames, bindingRoot, bookDocument, bookPrintPart, bookStylesheet, bookView, bookViewFromHtml, bytesToBase64, classify, collectSlotPrintCopies, documentLanguage, embedFunction, expandMotifs, fill, fillBookFurniture, findActiveContent, fontCss, fontFaceCss, fontFormat, fontReferenceRefusals, furnitureScript, hasBindings, hasMotifPlacement, hasSlotWork, heldPageNumber, isSlotCopyRefusal, libraryMotifPlacementRefusal, mergeSlotFitResults, motifPlacementRefusals, openerPageIndex, pageIndexForTarget, paginateBook, paginateScript, parseBookPositionKey, parseUnicodeRange, planBookPages, planPrintedPages, prepareSlotCollection, prepareSlots, printedPageAt, renderMotifPlacements, requiredSlotRefusal, sanitize, sanitizeBookCss, searchText, selectBookParent, slotFillCopyId, slotFillRefusal, slotFillsConflict, unmeasuredSlotRefusal, windowPageIndex };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@segnavia/html",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -26,7 +26,7 @@
26
26
  }
27
27
  },
28
28
  "dependencies": {
29
- "@segnavia/format": "0.36.0",
29
+ "@segnavia/format": "0.37.0",
30
30
  "dom-serializer": "^2.0.0",
31
31
  "domhandler": "^5.0.3",
32
32
  "htmlparser2": "^10.0.0",