@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.
@@ -2,7 +2,13 @@
2
2
  function paginateNotes(payload) {
3
3
  const notesById = new Map(payload.notes.map((note) => [note.id, note]));
4
4
  const notesGap = payload.geometry.notesGap;
5
+ const noteSeries = payload.geometry.noteSeries;
5
6
  const textHeight = payload.geometry.textHeight;
7
+ const seriesOrder = new Map(noteSeries?.map((series, index) => [series.name, index]));
8
+ function ordered(fragments) {
9
+ if (!noteSeries) return fragments;
10
+ return fragments.slice().sort((left, right) => (seriesOrder.get(left.series) ?? noteSeries.length) - (seriesOrder.get(right.series) ?? noteSeries.length));
11
+ }
6
12
  function noteEnd(id) {
7
13
  return notesById.get(id)?.lines.length ?? 0;
8
14
  }
@@ -15,8 +21,13 @@ function paginateNotes(payload) {
15
21
  function notesSpace(fragments) {
16
22
  if (fragments.length === 0) return 0;
17
23
  let height = 0;
18
- for (const fragment of fragments) height += fragmentHeight(fragment);
19
- return notesGap + height;
24
+ let previous;
25
+ for (const fragment of noteSeries ? ordered(fragments) : fragments) {
26
+ if (fragment.series !== previous) height += noteSeries?.find(({ name }) => name === fragment.series)?.gap ?? notesGap;
27
+ height += fragmentHeight(fragment);
28
+ previous = fragment.series;
29
+ }
30
+ return height;
20
31
  }
21
32
  function measure(placed, fragments) {
22
33
  if (payload.op === "continue" || payload.op === "allocate" || payload.op === "recompute") return payload.used(placed, fragments);
@@ -25,13 +36,14 @@ function paginateNotes(payload) {
25
36
  function makeFragment(id, start, end) {
26
37
  return {
27
38
  id,
39
+ series: notesById.get(id)?.series ?? "",
28
40
  whole: start === 0 && end === noteEnd(id),
29
41
  start,
30
42
  end
31
43
  };
32
44
  }
33
45
  function leftoverFrom(fragments) {
34
- return fragments.flatMap((fragment) => fragment.end < noteEnd(fragment.id) ? [makeFragment(fragment.id, fragment.end, noteEnd(fragment.id))] : []);
46
+ return ordered(fragments.flatMap((fragment) => fragment.end < noteEnd(fragment.id) ? [makeFragment(fragment.id, fragment.end, noteEnd(fragment.id))] : []));
35
47
  }
36
48
  function takeContinued(continued) {
37
49
  if (continued.length === 0) return {
@@ -41,7 +53,7 @@ function paginateNotes(payload) {
41
53
  const fragments = [];
42
54
  const leftover = [];
43
55
  let pending = true;
44
- for (const item of continued) {
56
+ for (const item of ordered(continued)) {
45
57
  const total = noteEnd(item.id);
46
58
  if (!pending) {
47
59
  leftover.push(item);
@@ -59,8 +71,8 @@ function paginateNotes(payload) {
59
71
  pending = false;
60
72
  }
61
73
  return {
62
- fragments,
63
- leftover
74
+ fragments: ordered(fragments),
75
+ leftover: ordered(leftover)
64
76
  };
65
77
  }
66
78
  function fitNote(trial, fragments, id) {
@@ -84,7 +96,7 @@ function paginateNotes(payload) {
84
96
  if (!fragment) return void 0;
85
97
  fragments.push(fragment);
86
98
  }
87
- return fragments;
99
+ return ordered(fragments);
88
100
  }
89
101
  function notesFor(placed, seed) {
90
102
  let fragments = seed;
@@ -1,4 +1,4 @@
1
1
  import { PaginateBookInput, PaginateBookResult, PaginatePageFill, PaginatePageNote, PaginateUnit } from "./paginate-types.js";
2
2
  //#region src/book/paginate-sequence.d.ts
3
- export declare function paginateSequence(input: PaginateBookInput, fillPage: (units: PaginateUnit[], index: number, pending: PaginatePageNote[]) => PaginatePageFill): PaginateBookResult;
3
+ export declare function paginateSequence(input: PaginateBookInput, fillPage: (units: PaginateUnit[], index: number, pending: PaginatePageNote[], textHeight: number, textArea?: string) => PaginatePageFill): PaginateBookResult;
4
4
  //#endregion
@@ -1,7 +1,23 @@
1
+ import { selectBookParent } from "../printed-pages.js";
1
2
  import { paginateColumns } from "./paginate-columns.js";
2
3
  import { paginateMarks } from "./paginate-marks.js";
3
4
  //#region src/book/paginate-sequence.ts
4
5
  function paginateSequence(input, fillPage) {
6
+ function selected(flow, opening, blank, role, pageIndex, parents = flow.parents) {
7
+ const fact = {
8
+ blank,
9
+ sectionOpening: opening,
10
+ ...role === void 0 ? {} : { role },
11
+ ...parents === void 0 ? {} : { parents }
12
+ };
13
+ return selectBookParent(fact, pageIndex, pageIndex, input.settings.direction, input.parentNames);
14
+ }
15
+ function pageHeight(parent) {
16
+ return input.parents[parent]?.height ?? 0;
17
+ }
18
+ function notesOnlyOpeningPage(page, opener) {
19
+ return opener && page.placed.length === 0 && page.fragments.length > 0 && !page.refusal;
20
+ }
5
21
  function emptyMarks() {
6
22
  return Object.create(null);
7
23
  }
@@ -37,7 +53,7 @@ function paginateSequence(input, fillPage) {
37
53
  }
38
54
  }
39
55
  function isFront(flow) {
40
- if (flow.kind === "fixed") return flow.sequence === "front";
56
+ if (flow.sequence !== void 0) return flow.sequence === "front";
41
57
  return flow.kind === "front" || flow.kind === "contents";
42
58
  }
43
59
  function unitsOf(flow) {
@@ -62,9 +78,6 @@ function paginateSequence(input, fillPage) {
62
78
  }
63
79
  return units;
64
80
  }
65
- function flowParents(flow) {
66
- return flow.parents === void 0 ? {} : { parents: flow.parents };
67
- }
68
81
  function openingOf(flow) {
69
82
  if (flow.opens === "recto" || flow.opens === "verso" || flow.opens === "any") return flow.opens;
70
83
  return input.settings.rectoOpenings ? "recto" : "any";
@@ -75,7 +88,8 @@ function paginateSequence(input, fillPage) {
75
88
  if (opens === "verso") return nextIsRecto;
76
89
  return false;
77
90
  }
78
- function makeBlank(number, sequence, flow) {
91
+ function makeBlank(number, sequence, flow, physicalIndex) {
92
+ const planned = selected(flow, false, true, "body", physicalIndex);
79
93
  return {
80
94
  kind: "blank",
81
95
  number,
@@ -86,10 +100,11 @@ function paginateSequence(input, fillPage) {
86
100
  notes: [],
87
101
  blank: true,
88
102
  sectionOpening: false,
89
- ...flowParents(flow)
103
+ parent: planned.parent,
104
+ side: planned.side
90
105
  };
91
106
  }
92
- function makeContent(part, number, flow, opener, placed, fragments, marks) {
107
+ function makeContent(part, number, flow, opener, placed, fragments, marks, planned) {
93
108
  const layout = paginateColumns(placed);
94
109
  return {
95
110
  kind: part,
@@ -103,12 +118,15 @@ function paginateSequence(input, fillPage) {
103
118
  blank: false,
104
119
  sectionOpening: opener,
105
120
  ...opener || flow.runningHead === void 0 ? {} : { runningHead: flow.runningHead },
121
+ ...flow.parts === void 0 ? {} : { parts: flow.parts },
106
122
  marks,
107
- ...flowParents(flow)
123
+ parent: planned.parent,
124
+ side: planned.side
108
125
  };
109
126
  }
110
- function makeFixed(part, number, flow, opener, marks) {
127
+ function makeFixed(part, number, flow, opener, marks, physicalIndex, parents = flow.parents) {
111
128
  const templated = flow.kind === "fixed" && flow.role === "body";
129
+ const planned = selected(flow, opener, false, templated ? "body" : "fixed", physicalIndex, parents);
112
130
  return {
113
131
  kind: part,
114
132
  number,
@@ -120,38 +138,47 @@ function paginateSequence(input, fillPage) {
120
138
  blank: false,
121
139
  sectionOpening: opener,
122
140
  ...opener && !templated || flow.runningHead === void 0 ? {} : { runningHead: flow.runningHead },
141
+ ...flow.parts === void 0 ? {} : { parts: flow.parts },
123
142
  marks,
124
- ...flowParents(flow)
143
+ parent: planned.parent,
144
+ side: planned.side
125
145
  };
126
146
  }
127
- function emitMarked(part, flow, opener, placed, fragments, pages, carried) {
147
+ function emitMarked(part, flow, opener, placed, fragments, pages, carried, planned) {
128
148
  const marked = paginateMarks(placed, carried);
129
- pages.push(makeContent(part, pages.length + 1, flow, opener, placed, fragments, marked.marks));
149
+ pages.push(makeContent(part, pages.length + 1, flow, opener, placed, fragments, marked.marks, planned));
130
150
  return marked.carried;
131
151
  }
132
- function appendFixed(flow, part, pages, continued, carried) {
152
+ function appendFixed(flow, part, pages, continued, carried, physicalOffset) {
133
153
  const count = flow.kind === "fixed" ? flow.pages : 0;
134
154
  let next = carried;
135
155
  for (let index = 0; index < count; index += 1) {
136
156
  const opener = index === 0;
137
157
  const marked = paginateMarks([], next);
138
158
  next = marked.carried;
139
- pages.push(makeFixed(part, pages.length + 1, flow, opener, marked.marks));
159
+ pages.push(makeFixed(part, pages.length + 1, flow, opener, marked.marks, physicalOffset + pages.length + 1, flow.kind === "fixed" ? flow.pageParents?.[index] : void 0));
140
160
  }
141
161
  return {
142
162
  pending: continued,
143
163
  carried: next
144
164
  };
145
165
  }
146
- function paginateFlow(flow, part, pages, continued, carried) {
147
- if (flow.kind === "fixed") return appendFixed(flow, part, pages, continued, carried);
166
+ function paginateFlowing(flow, part, pages, continued, carried, physicalOffset) {
148
167
  const units = unitsOf(flow);
149
168
  let index = 0;
150
169
  let pending = continued;
151
170
  let opener = true;
152
171
  let nextCarried = carried;
153
172
  while (index < units.length || pending.length > 0) {
154
- const page = fillPage(units, index, pending);
173
+ let planned = selected(flow, opener, false, void 0, physicalOffset + pages.length + 1);
174
+ const height = pageHeight(planned.parent);
175
+ const textArea = opener ? `the "${planned.parent}" opener parent's ${height}px text area` : void 0;
176
+ let page = fillPage(units, index, pending, height, textArea);
177
+ const isOpening = opener && page.placed.length > 0;
178
+ if (notesOnlyOpeningPage(page, opener)) {
179
+ planned = selected(flow, isOpening, false, void 0, physicalOffset + pages.length + 1);
180
+ page = fillPage(units, index, pending, pageHeight(planned.parent));
181
+ }
155
182
  if (page.refusal) return {
156
183
  refusal: page.refusal,
157
184
  pending,
@@ -161,7 +188,7 @@ function paginateSequence(input, fillPage) {
161
188
  pending,
162
189
  carried: nextCarried
163
190
  };
164
- nextCarried = emitMarked(part, flow, opener && page.placed.length > 0, page.placed, page.fragments, pages, nextCarried);
191
+ nextCarried = emitMarked(part, flow, isOpening, page.placed, page.fragments, pages, nextCarried, planned);
165
192
  opener = opener && page.placed.length === 0;
166
193
  index = page.index;
167
194
  pending = page.pending;
@@ -171,27 +198,32 @@ function paginateSequence(input, fillPage) {
171
198
  carried: nextCarried
172
199
  };
173
200
  }
174
- function flushNotes(continued, flow, part, pages, carried) {
201
+ function paginateFlow(flow, part, pages, continued, carried, physicalOffset) {
202
+ if (flow.kind === "fixed") return appendFixed(flow, part, pages, continued, carried, physicalOffset);
203
+ return paginateFlowing(flow, part, pages, continued, carried, physicalOffset);
204
+ }
205
+ function flushNotes(continued, flow, part, pages, carried, physicalOffset) {
175
206
  let pending = continued;
176
207
  let nextCarried = carried;
177
208
  while (pending.length > 0) {
178
- const page = fillPage([], 0, pending);
209
+ const planned = selected(flow, false, false, "body", physicalOffset + pages.length + 1);
210
+ const page = fillPage([], 0, pending, pageHeight(planned.parent));
179
211
  if (page.refusal) return {
180
212
  refusal: page.refusal,
181
213
  carried: nextCarried
182
214
  };
183
215
  if (page.done || page.placed.length === 0 && page.fragments.length === 0) return { carried: nextCarried };
184
- nextCarried = emitMarked(part, flow, false, page.placed, page.fragments, pages, nextCarried);
216
+ nextCarried = emitMarked(part, flow, false, page.placed, page.fragments, pages, nextCarried, planned);
185
217
  pending = page.pending;
186
218
  }
187
219
  return { carried: nextCarried };
188
220
  }
189
- function flushBeforeFixed(flow, previous, continued, carried, part, pages) {
221
+ function flushBeforeFixed(flow, previous, continued, carried, part, pages, physicalOffset) {
190
222
  if (flow.kind !== "fixed" || continued.length === 0 || !previous) return {
191
223
  continued,
192
224
  carried
193
225
  };
194
- const flushed = flushNotes(continued, previous, part, pages, carried);
226
+ const flushed = flushNotes(continued, previous, part, pages, carried, physicalOffset);
195
227
  if ("refusal" in flushed && flushed.refusal) return { refusal: flushed.refusal };
196
228
  return {
197
229
  continued: [],
@@ -199,10 +231,10 @@ function paginateSequence(input, fillPage) {
199
231
  };
200
232
  }
201
233
  function emitFlow(flow, previous, continued, carried, part, pages, physicalOffset) {
202
- const held = flushBeforeFixed(flow, previous, continued, carried, part, pages);
234
+ const held = flushBeforeFixed(flow, previous, continued, carried, part, pages, physicalOffset);
203
235
  if ("refusal" in held && held.refusal) return { refusal: held.refusal };
204
- if (needsOpeningBlank(openingOf(flow), physicalOffset + pages.length)) pages.push(makeBlank(pages.length + 1, part, flow));
205
- const result = paginateFlow(flow, part, pages, held.continued, held.carried);
236
+ if (needsOpeningBlank(openingOf(flow), physicalOffset + pages.length)) pages.push(makeBlank(pages.length + 1, part, flow, physicalOffset + pages.length + 1));
237
+ const result = paginateFlow(flow, part, pages, held.continued, held.carried, physicalOffset);
206
238
  if ("refusal" in result && result.refusal) return { refusal: result.refusal };
207
239
  return {
208
240
  continued: result.pending,
@@ -226,7 +258,7 @@ function paginateSequence(input, fillPage) {
226
258
  }
227
259
  const last = flows[flows.length - 1];
228
260
  if (last) {
229
- const flushed = flushNotes(continued, last, part, pages, carried);
261
+ const flushed = flushNotes(continued, last, part, pages, carried, physicalOffset);
230
262
  if ("refusal" in flushed && flushed.refusal) return {
231
263
  pages,
232
264
  refusals: [flushed.refusal]
@@ -248,7 +280,7 @@ function paginateSequence(input, fillPage) {
248
280
  const firstBody = bodyFlows[0];
249
281
  const lastFront = frontFlows[frontFlows.length - 1];
250
282
  const padFront = input.settings.rectoOpenings ? front.pages.length % 2 === 1 : firstBody !== void 0 && needsOpeningBlank(openingOf(firstBody), front.pages.length);
251
- if (front.pages.length > 0 && lastFront && padFront) front.pages.push(makeBlank(front.pages.length + 1, "front", lastFront));
283
+ if (front.pages.length > 0 && lastFront && padFront) front.pages.push(makeBlank(front.pages.length + 1, "front", lastFront, front.pages.length + 1));
252
284
  const body = paginatePart(bodyFlows, "body", front.pages.length);
253
285
  if (body.refusals.length > 0) return {
254
286
  pages: [...front.pages, ...body.pages],
@@ -1,4 +1,4 @@
1
- import { BookOpens, BookSectionParents } from "@segnavia/format";
1
+ import { BOOK_PARENT_NAMES, BookOpens, BookSectionParents, TextArea } from "@segnavia/format";
2
2
  //#region src/book/paginate-types.d.ts
3
3
  export type PaginateSequence = 'front' | 'body';
4
4
  export type PaginatePageRole = 'opener' | 'body' | 'blank' | 'fixed';
@@ -36,30 +36,39 @@ export type PaginateFlowParents = BookSectionParents;
36
36
  export type PaginateOpens = BookOpens;
37
37
  interface PaginateFlowBase {
38
38
  id: string;
39
+ sequence?: PaginateSequence;
39
40
  runningHead?: string;
40
41
  opens?: PaginateOpens;
41
42
  parents?: PaginateFlowParents;
43
+ parts?: Record<string, string>;
42
44
  }
43
45
  export type PaginateFlow = (PaginateFlowBase & {
44
- kind: 'front' | 'contents' | 'chapter' | 'endnotes';
46
+ kind: 'front' | 'contents' | 'part' | 'chapter' | 'endnotes';
45
47
  blocks: PaginateBlock[];
46
48
  }) | (PaginateFlowBase & {
47
49
  kind: 'fixed';
48
50
  pages: number;
49
- sequence?: PaginateSequence;
50
51
  role?: 'fixed' | 'body';
52
+ pageParents?: PaginateFlowParents[];
51
53
  });
52
54
  export interface PaginateNote {
53
55
  id: string;
56
+ series: string;
54
57
  lines: PaginateLine[];
55
58
  }
59
+ export interface PaginateNoteSeriesGeometry {
60
+ name: string;
61
+ gap: number;
62
+ }
56
63
  export interface PaginateBookInput {
57
64
  flows: PaginateFlow[];
58
65
  notes: PaginateNote[];
59
66
  geometry: {
60
- textHeight: number;
61
67
  notesGap: number;
68
+ noteSeries?: PaginateNoteSeriesGeometry[];
62
69
  };
70
+ parents: Record<string, TextArea>;
71
+ parentNames: typeof BOOK_PARENT_NAMES;
63
72
  settings: {
64
73
  orphans?: number;
65
74
  widows?: number;
@@ -78,6 +87,7 @@ export interface PaginateWindow {
78
87
  }
79
88
  export interface PaginatePageNote {
80
89
  id: string;
90
+ series: string;
81
91
  whole: boolean;
82
92
  start: number;
83
93
  end: number;
@@ -98,8 +108,10 @@ export interface PaginateBookPage {
98
108
  blank: boolean;
99
109
  sectionOpening: boolean;
100
110
  runningHead?: string;
111
+ parts?: Record<string, string>;
101
112
  marks?: PaginatePageMarks;
102
- parents?: PaginateFlowParents;
113
+ parent: string;
114
+ side: 'left' | 'right';
103
115
  }
104
116
  export type PaginateBookRefusal = {
105
117
  tag: string;
@@ -143,6 +155,7 @@ interface PaginateNotesBase {
143
155
  geometry: {
144
156
  textHeight: number;
145
157
  notesGap: number;
158
+ noteSeries?: PaginateNoteSeriesGeometry[];
146
159
  };
147
160
  }
148
161
  export type PaginateNotesOp = (PaginateNotesBase & {
@@ -0,0 +1,16 @@
1
+ //#region src/book/region-fit.d.ts
2
+ export declare const ABSOLUTE_UNITS: readonly ["px", "pt", "pc", "in", "cm", "mm", "q"];
3
+ type AbsoluteUnit = (typeof ABSOLUTE_UNITS)[number];
4
+ export declare const ABSOLUTE_PX: Record<AbsoluteUnit, number>;
5
+ export declare function parseCssLength(value: string): {
6
+ value: number;
7
+ unit: string;
8
+ } | undefined;
9
+ export declare function lengthPx(value: string, table: Readonly<Record<string, number>>): number | undefined;
10
+ export declare function fitRegionSize(max: number, min: number, step: number, overflows: (size: number) => boolean): {
11
+ overflow: boolean;
12
+ size: number;
13
+ } | {
14
+ usable: false;
15
+ };
16
+ //#endregion
@@ -0,0 +1,111 @@
1
+ import { PX_PER_MM, PX_PER_PT } from "@segnavia/format";
2
+ //#region src/book/region-fit.ts
3
+ const ABSOLUTE_UNITS = [
4
+ "px",
5
+ "pt",
6
+ "pc",
7
+ "in",
8
+ "cm",
9
+ "mm",
10
+ "q"
11
+ ];
12
+ const ABSOLUTE_PX = {
13
+ px: 1,
14
+ pt: PX_PER_PT,
15
+ pc: 12 * PX_PER_PT,
16
+ in: 72 * PX_PER_PT,
17
+ cm: 10 * PX_PER_MM,
18
+ mm: PX_PER_MM,
19
+ q: PX_PER_MM / 4
20
+ };
21
+ function listedAbsoluteUnits() {
22
+ const last = ABSOLUTE_UNITS.length - 1;
23
+ return `${ABSOLUTE_UNITS.slice(0, last).join(", ")} or ${ABSOLUTE_UNITS[last]}`;
24
+ }
25
+ function isAbsoluteUnit(unit) {
26
+ return ABSOLUTE_UNITS.includes(unit);
27
+ }
28
+ function parseCssLength(value) {
29
+ const held = value.trim().replace(/\s*!important\s*$/i, "");
30
+ const match = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+))([a-z%]+)$/i.exec(held);
31
+ const amount = match?.[1];
32
+ const unit = match?.[2];
33
+ if (amount === void 0 || unit === void 0) return void 0;
34
+ return {
35
+ value: Number(amount),
36
+ unit: unit.toLowerCase()
37
+ };
38
+ }
39
+ function lengthPx(value, table) {
40
+ const parsed = parseCssLength(value);
41
+ if (parsed === void 0 || !(parsed.value > 0)) return void 0;
42
+ const factor = table[parsed.unit];
43
+ return factor === void 0 ? void 0 : parsed.value * factor;
44
+ }
45
+ function fitExample(attribute) {
46
+ return attribute === "data-fit-min" ? "48pt" : "1pt";
47
+ }
48
+ function fitLengthIssues(where, attribute, value) {
49
+ const example = fitExample(attribute);
50
+ const parsed = parseCssLength(value);
51
+ if (parsed === void 0) return [`${where} ${attribute} "${value}" is not an absolute CSS length; use an absolute CSS length such as ${example} to clear this refusal`];
52
+ if (!isAbsoluteUnit(parsed.unit)) return [`${where} ${attribute} "${value}" uses unit "${parsed.unit}"; use an absolute CSS length (${listedAbsoluteUnits()}) such as ${example} to clear this refusal`];
53
+ if (!(parsed.value > 0)) return [`${where} ${attribute} "${value}" is not a positive absolute CSS length; use a positive absolute CSS length such as ${example} to clear this refusal`];
54
+ return [];
55
+ }
56
+ function regionFitIssues(where, attribs) {
57
+ const minText = attribs["data-fit-min"];
58
+ const stepText = attribs["data-fit-step"];
59
+ if (minText === void 0 && stepText === void 0) return [];
60
+ const issues = [];
61
+ if (minText !== void 0) issues.push(...fitLengthIssues(where, "data-fit-min", minText));
62
+ if (minText !== void 0 || stepText !== void 0) issues.push(...fitLengthIssues(where, "data-fit-step", stepText ?? ""));
63
+ return issues;
64
+ }
65
+ function namedRegionFitIssues(where, regions) {
66
+ return regions.flatMap((node) => {
67
+ const region = node.attribs["data-region"];
68
+ return region ? regionFitIssues(`${where} region "${region}"`, node.attribs) : [];
69
+ });
70
+ }
71
+ function templateRegionFitIssues(template, regions) {
72
+ return namedRegionFitIssues(`template "${template}"`, regions);
73
+ }
74
+ function fitRegionSize(max, min, step, overflows) {
75
+ function sizeAt(index) {
76
+ return max - index * step;
77
+ }
78
+ if (!(max >= min) || !(step > 0) || !Number.isFinite(max) || !Number.isFinite(min) || !Number.isFinite(step)) return { usable: false };
79
+ const last = Math.floor((max - min) / step + 1e-9);
80
+ if (!Number.isSafeInteger(last)) {
81
+ if (!overflows(max)) return {
82
+ size: max,
83
+ overflow: false
84
+ };
85
+ return {
86
+ size: min,
87
+ overflow: overflows(min)
88
+ };
89
+ }
90
+ let low = 0;
91
+ let high = last;
92
+ let fitted = last + 1;
93
+ while (low <= high) {
94
+ const mid = Math.floor((low + high) / 2);
95
+ if (overflows(sizeAt(mid))) low = mid + 1;
96
+ else {
97
+ fitted = mid;
98
+ high = mid - 1;
99
+ }
100
+ }
101
+ if (fitted > last) return {
102
+ size: sizeAt(last),
103
+ overflow: true
104
+ };
105
+ return {
106
+ size: sizeAt(fitted),
107
+ overflow: false
108
+ };
109
+ }
110
+ //#endregion
111
+ export { ABSOLUTE_PX, ABSOLUTE_UNITS, fitRegionSize, lengthPx, namedRegionFitIssues, parseCssLength, templateRegionFitIssues };
@@ -10,22 +10,25 @@ export interface BookDocumentOptions {
10
10
  export type BookBuild = 'print' | 'galley' | 'flow';
11
11
  export interface BookSectionInfo {
12
12
  id: string;
13
- kind: 'front' | 'contents' | 'chapter' | 'endnotes' | 'designed' | 'templated';
13
+ kind: 'front' | 'contents' | 'part' | 'chapter' | 'endnotes' | 'designed' | 'templated';
14
+ division: 'front' | 'body' | 'end';
14
15
  title?: string;
15
16
  runningHead?: string;
16
17
  }
17
18
  export type BookPrintPart = 'front' | 'body';
18
- export declare function bookPrintPart(kind: BookSectionInfo['kind']): BookPrintPart;
19
+ export declare function bookPrintPart(division: BookSectionInfo['division']): BookPrintPart;
19
20
  export interface BookHeading {
20
21
  id: string;
21
22
  level: number;
22
23
  text: string;
23
24
  sectionId: string;
25
+ depth?: number;
24
26
  }
25
27
  export interface BookDocument {
26
28
  html: string;
27
29
  sections: BookSectionInfo[];
28
30
  headings: BookHeading[];
31
+ pageReferenceTargets: string[];
29
32
  issues: string[];
30
33
  }
31
34
  //#endregion
@@ -1,6 +1,6 @@
1
1
  //#region src/book/types.ts
2
- function bookPrintPart(kind) {
3
- return kind === "front" || kind === "contents" ? "front" : "body";
2
+ function bookPrintPart(division) {
3
+ return division === "front" ? "front" : "body";
4
4
  }
5
5
  //#endregion
6
6
  export { bookPrintPart };
package/dist/font-css.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { bytesToBase64 } from "./base64.js";
2
2
  import { hasControlChar } from "./control-char.js";
3
3
  import { UNICODE_RANGE_CLEARANCE, parseUnicodeRange } from "./unicode-range.js";
4
- import { APP_FONT_NAME, FONT_FORMATS } from "@segnavia/format";
4
+ import { APP_FONT_NAME, FONT_FORMATS, FontFaceRefusedError, resolveFontFace } from "@segnavia/format";
5
5
  //#region src/font-css.ts
6
6
  var FontCssRefusedError = class extends Error {
7
7
  constructor(message) {
@@ -59,7 +59,18 @@ function validatedFace(font) {
59
59
  return font;
60
60
  }
61
61
  function fontFaceCss(font) {
62
- const face = validatedFace(font);
62
+ const validated = validatedFace(font);
63
+ let resolved;
64
+ try {
65
+ resolved = resolveFontFace(validated.data, validated.format, validated.postscriptName);
66
+ } catch (error) {
67
+ if (error instanceof FontFaceRefusedError) throw new FontCssRefusedError(error.message);
68
+ throw error;
69
+ }
70
+ const face = {
71
+ ...validated,
72
+ ...resolved
73
+ };
63
74
  return `@font-face{font-family:${quoteFamily(face.family)};src:url(data:font/${mimeType(face.format)};base64,${bytesToBase64(face.data)}) format(${JSON.stringify(face.format)});font-weight:${face.weight ?? "normal"};font-style:${face.style ?? "normal"};${unicodeRangeDeclaration(face)}font-display:block}`;
64
75
  }
65
76
  function customProperty(name, font) {
@@ -1,11 +1,7 @@
1
+ import { classifyFont } from "@segnavia/format";
1
2
  //#region src/font-format.ts
2
3
  function fontFormat(data) {
3
- if (data.length < 4) return;
4
- const tag = String.fromCharCode(data[0] ?? 0, data[1] ?? 0, data[2] ?? 0, data[3] ?? 0);
5
- if (tag === "wOF2") return "woff2";
6
- if (tag === "wOFF") return "woff";
7
- if (tag === "OTTO") return "opentype";
8
- if (tag === "true" || data[0] === 0 && data[1] === 1 && data[2] === 0 && data[3] === 0) return "truetype";
4
+ return classifyFont(data);
9
5
  }
10
6
  //#endregion
11
7
  export { fontFormat };
package/dist/index.d.ts CHANGED
@@ -1,20 +1,23 @@
1
1
  import { base64ToBytes, bytesToBase64 } from "./base64.js";
2
- import { REFUSED_ELEMENTS, SanitizeOptions, findActiveContent, sanitize } from "./sanitize.js";
2
+ import { REFUSED_ELEMENTS, SanitizeOptions, findActiveContent, safeImageSource, sanitize } from "./sanitize.js";
3
3
  import { BINDINGS, FillOptions, Filled, bindingNames, bindingRoot, fill, hasBindings } from "./bindings.js";
4
4
  import { BookBuild, BookDocument, BookDocumentOptions, BookHeading, BookPrintPart, BookSectionInfo, bookPrintPart } from "./book/types.js";
5
- import { bookDocument } from "./book/book-document.js";
6
5
  import { BOOK_COLUMN_MAX, BOOK_COLUMN_MIN, BOOK_FLOW_COLUMN_MIN_PX, BookPositionKey, parseBookPositionKey } from "./book/book-flow.js";
6
+ import { bookDocument } from "./book/book-document.js";
7
+ import { PAGE_REFERENCE_ATTRIBUTE, PAGE_REFERENCE_SELECTOR, PAGE_REFERENCE_VALUE, pageReferenceTarget } from "./book/book-page-reference.js";
7
8
  import { bookView, bookViewFromHtml } from "./book/book-view.js";
8
- import { PaginateBlock, PaginateBookInput, PaginateBookPage, PaginateBookRefusal, PaginateBookResult, PaginateFlow, PaginateFlowParents, PaginateLine, PaginateMark, PaginateNote, PaginateNoteRef, PaginateOpens, PaginatePageMarks, PaginatePageNote, PaginatePageRole, PaginateSequence, PaginateWindow } from "./book/paginate-types.js";
9
+ import { PaginateBlock, PaginateBookInput, PaginateBookPage, PaginateBookRefusal, PaginateBookResult, PaginateFlow, PaginateFlowParents, PaginateLine, PaginateMark, PaginateNote, PaginateNoteRef, PaginateNoteSeriesGeometry, PaginateOpens, PaginatePageMarks, PaginatePageNote, PaginatePageRole, PaginateSequence, PaginateWindow } from "./book/paginate-types.js";
9
10
  import { BookFurnitureValues, embedFunction, fillBookFurniture, furnitureScript } from "./book/furniture.js";
11
+ import { BookPageFacts, BookPrintedPagePlan, BookPrintedPageRole, BookSectionParents, PrintedPagePlan, PrintedPageSide, planBookPages, planPrintedPages, printedPageAt, selectBookParent } from "./printed-pages.js";
10
12
  import { PAGINATE_FUNCTIONS, paginateBook, paginateScript } from "./book/paginate-book.js";
11
13
  import { PaginateTarget, PaginateTargetPage, heldPageNumber, openerPageIndex, pageIndexForTarget, windowPageIndex } from "./book/paginate-target.js";
14
+ import { ABSOLUTE_PX, ABSOLUTE_UNITS, fitRegionSize, lengthPx, parseCssLength } from "./book/region-fit.js";
12
15
  import { bookStylesheet, sanitizeBookCss } from "./book-css.js";
13
16
  import { FontCssRefusedError, fontCss, fontFaceCss } from "./font-css.js";
14
17
  import { fontFormat } from "./font-format.js";
15
18
  import { fontReferenceRefusals } from "./font-references.js";
16
- import { ExpandMotifsOptions, ExpandedMotifs, MOTIF_DEPTH_LIMIT, MotifRefusedError, PlacedMotif, documentLanguage, expandMotifs, hasMotifPlacement, libraryMotifPlacementRefusal, motifPlacementRefusals, renderMotifPlacements } from "./motifs.js";
17
- import { BookPageFacts, BookPrintedPagePlan, BookPrintedPageRole, BookSectionParents, PrintedPagePlan, PrintedPageSide, planBookPages, planPrintedPages, printedPageAt, selectBookParent } from "./printed-pages.js";
19
+ import { ExpandMotifsOptions, ExpandedMotifs, MOTIF_DEPTH_LIMIT, MotifRefusedError, PlacedMotif, documentLanguage, expandMotifs, hasHostMotifPlacement, hasMotifPlacement, libraryMotifPlacementRefusal, motifPlacementRefusals, renderMotifPlacements } from "./motifs.js";
20
+ import { MediaAnswer, PLACEMENT_ATTRIBUTES, PlacementPlan, PlacementPlanEntry, PlacementRefusal, PlacementReport, PlacementsOptions, ResolvePlacement, ResolvedPlacement, classifyMediaAnswer, placementCropEdges, placementPlaceholderSource, placementPlan, resolvePlacements } from "./placements.js";
18
21
  import { SlotFillEntry, SlotFillRefusal, SlotFillsConflictError, SlotFillsRefusedError, SlotPrintOptions, assertSingleSlotCopy, assertSlotPrintOptions, collectSlotPrintCopies, isSlotCopyRefusal, slotFillCopyId, slotFillsConflict } from "./slot-print.js";
19
22
  import { RequiredSlotsRefusedError, UnmeasuredSlotsRefusedError, mergeSlotFitResults, requiredSlotRefusal, unmeasuredSlotRefusal } from "./slot-policy.js";
20
23
  import { MAX_SLOT_DECLARATIONS, PreparedSlots, SlotFillRefusedError, hasSlotWork, prepareSlotCollection, prepareSlots, slotFillRefusal } from "./slots.js";
@@ -24,4 +27,4 @@ export type HtmlKind = 'plain' | 'live-data' | 'scripted' | 'refused';
24
27
  export declare function classify(html: string, prefix: string): HtmlKind;
25
28
  export declare function searchText(html: string): string;
26
29
  //#endregion
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 };
30
+ export { ABSOLUTE_PX, ABSOLUTE_UNITS, 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, type MediaAnswer, MotifRefusedError, PAGE_REFERENCE_ATTRIBUTE, PAGE_REFERENCE_SELECTOR, PAGE_REFERENCE_VALUE, PAGINATE_FUNCTIONS, PLACEMENT_ATTRIBUTES, type PaginateBlock, type PaginateBookInput, type PaginateBookPage, type PaginateBookRefusal, type PaginateBookResult, type PaginateFlow, type PaginateFlowParents, type PaginateLine, type PaginateMark, type PaginateNote, type PaginateNoteRef, type PaginateNoteSeriesGeometry, type PaginateOpens, type PaginatePageMarks, type PaginatePageNote, type PaginatePageRole, type PaginateSequence, type PaginateTarget, type PaginateTargetPage, type PaginateWindow, type PlacedMotif, type PlacementPlan, type PlacementPlanEntry, type PlacementRefusal, type PlacementReport, type PlacementsOptions, type PreparedSlots, PrintedPagePlan, PrintedPageSide, REFUSED_ELEMENTS, RequiredSlotsRefusedError, type ResolvePlacement, type ResolvedPlacement, 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, classifyMediaAnswer, collectSlotPrintCopies, documentLanguage, embedFunction, expandMotifs, fill, fillBookFurniture, findActiveContent, fitRegionSize, fontCss, fontFaceCss, fontFormat, fontReferenceRefusals, furnitureScript, hasBindings, hasHostMotifPlacement, hasMotifPlacement, hasSlotWork, heldPageNumber, isSlotCopyRefusal, lengthPx, libraryMotifPlacementRefusal, mergeSlotFitResults, motifPlacementRefusals, openerPageIndex, pageIndexForTarget, pageReferenceTarget, paginateBook, paginateScript, parseBookPositionKey, parseCssLength, parseUnicodeRange, placementCropEdges, placementPlaceholderSource, placementPlan, planBookPages, planPrintedPages, prepareSlotCollection, prepareSlots, printedPageAt, renderMotifPlacements, requiredSlotRefusal, resolvePlacements, safeImageSource, sanitize, sanitizeBookCss, selectBookParent, slotFillCopyId, slotFillRefusal, slotFillsConflict, unmeasuredSlotRefusal, windowPageIndex };