@segnavia/idml 0.28.0 → 0.30.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/frame-preferences.js +45 -0
- package/dist/import.d.ts +2 -2
- package/dist/import.js +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/map-book.js +56 -19
- package/dist/margins.js +21 -0
- package/dist/masters.js +200 -0
- package/dist/model.d.ts +9 -32
- package/dist/model.js +150 -118
- package/dist/parse-parts.js +61 -0
- package/dist/report.d.ts +24 -0
- package/dist/report.js +78 -0
- package/dist/stories.js +218 -0
- package/dist/story-model.js +53 -0
- package/dist/style-mappings.js +52 -0
- package/dist/style-property-report.js +236 -0
- package/dist/styles.js +285 -0
- package/dist/types.d.ts +90 -0
- package/dist/unsupported-elements.js +51 -0
- package/dist/variables.js +21 -0
- package/dist/xml.js +18 -0
- package/package.json +3 -3
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { localName, numberAttribute } from "./xml.js";
|
|
2
|
+
import { isTag } from "domhandler";
|
|
3
|
+
//#region src/frame-preferences.ts
|
|
4
|
+
function preferenceElement(frame) {
|
|
5
|
+
const pending = [...frame.children];
|
|
6
|
+
while (pending.length > 0) {
|
|
7
|
+
const node = pending.pop();
|
|
8
|
+
if (!node || !isTag(node)) continue;
|
|
9
|
+
if (localName(node.name) === "TextFramePreference") return node;
|
|
10
|
+
pending.push(...node.children);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function framePreference(frame, location) {
|
|
14
|
+
const element = preferenceElement(frame);
|
|
15
|
+
if (!element) return void 0;
|
|
16
|
+
return {
|
|
17
|
+
columns: numberAttribute(element, "TextColumnCount", 1),
|
|
18
|
+
gutter: numberAttribute(element, "TextColumnGutter", 0),
|
|
19
|
+
location
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
var FramePreferenceIndex = class {
|
|
23
|
+
byStory = /* @__PURE__ */ new Map();
|
|
24
|
+
add(story, preference) {
|
|
25
|
+
if (!preference || preference.columns < 2) return;
|
|
26
|
+
const values = this.byStory.get(story) ?? [];
|
|
27
|
+
values.push(preference);
|
|
28
|
+
this.byStory.set(story, values);
|
|
29
|
+
}
|
|
30
|
+
preferred(story) {
|
|
31
|
+
const values = this.byStory.get(story) ?? [];
|
|
32
|
+
const counts = /* @__PURE__ */ new Map();
|
|
33
|
+
for (const value of values) {
|
|
34
|
+
const key = `${value.columns}\u0000${value.gutter}`;
|
|
35
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
36
|
+
}
|
|
37
|
+
const selected = [...counts].sort((left, right) => right[1] - left[1])[0]?.[0];
|
|
38
|
+
return values.find((value) => `${value.columns}\u0000${value.gutter}` === selected);
|
|
39
|
+
}
|
|
40
|
+
values() {
|
|
41
|
+
return [...this.byStory.values()].flat();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
export { FramePreferenceIndex, framePreference };
|
package/dist/import.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { IdmlReportData } from "./report.js";
|
|
1
2
|
import { Book } from "@segnavia/format";
|
|
2
3
|
//#region src/import.d.ts
|
|
3
4
|
export type IdmlImportOptions = Record<never, never>;
|
|
4
|
-
export interface IdmlImportReport {
|
|
5
|
+
export interface IdmlImportReport extends IdmlReportData {
|
|
5
6
|
partsRead: string[];
|
|
6
|
-
unsupportedElements: Record<string, number>;
|
|
7
7
|
}
|
|
8
8
|
export interface IdmlImportResult {
|
|
9
9
|
book: Book;
|
package/dist/import.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { refusal } from "./refusal.js";
|
|
2
|
+
import { ReportCollector } from "./report.js";
|
|
2
3
|
import { modelToBook } from "./map-book.js";
|
|
3
4
|
import { idmlModel } from "./model.js";
|
|
4
5
|
import { readZip } from "./zip.js";
|
|
@@ -13,11 +14,12 @@ async function importIdml(bytes, _options = {}) {
|
|
|
13
14
|
if (value !== IDML_MIMETYPE) throw refusal(`the IDML mimetype entry is ${JSON.stringify(value)}, not ${IDML_MIMETYPE}`, "use the IDML package mimetype");
|
|
14
15
|
if (!byName.has("designmap.xml")) throw refusal("the IDML designmap.xml entry is missing", "add the required designmap.xml entry");
|
|
15
16
|
const model = idmlModel(entries);
|
|
17
|
+
const collector = new ReportCollector(model.report);
|
|
16
18
|
return {
|
|
17
|
-
book: modelToBook(model),
|
|
19
|
+
book: modelToBook(model, collector),
|
|
18
20
|
report: {
|
|
19
21
|
partsRead: model.partsRead,
|
|
20
|
-
|
|
22
|
+
...collector.data()
|
|
21
23
|
}
|
|
22
24
|
};
|
|
23
25
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { IdmlFontUse, IdmlReportData } from "./report.js";
|
|
1
2
|
import { IdmlImportOptions, IdmlImportReport, IdmlImportResult, importIdml } from "./import.js";
|
|
2
3
|
import { IDML_LIMITS, MAX_IDML_ENTRIES, MAX_IDML_PACKAGE_BYTES, MAX_IDML_PART_BYTES, MAX_IDML_TOTAL_INFLATED_BYTES, MAX_IDML_XML_DEPTH } from "./limits.js";
|
|
3
|
-
import { IdmlDocumentPreferences, IdmlMargin, IdmlMasterSpread,
|
|
4
|
-
|
|
4
|
+
import { IdmlDocumentPreferences, IdmlMargin, IdmlMasterSpread, IdmlPage, IdmlSpread, IdmlStory } from "./types.js";
|
|
5
|
+
import { IdmlModel } from "./model.js";
|
|
6
|
+
export { IDML_LIMITS, type IdmlDocumentPreferences, type IdmlFontUse, type IdmlImportOptions, type IdmlImportReport, type IdmlImportResult, type IdmlMargin, type IdmlMasterSpread, type IdmlModel, type IdmlPage, type IdmlReportData, type IdmlSpread, type IdmlStory, MAX_IDML_ENTRIES, MAX_IDML_PACKAGE_BYTES, MAX_IDML_PART_BYTES, MAX_IDML_TOTAL_INFLATED_BYTES, MAX_IDML_XML_DEPTH, importIdml };
|
package/dist/map-book.js
CHANGED
|
@@ -1,27 +1,65 @@
|
|
|
1
1
|
import { refusal } from "./refusal.js";
|
|
2
|
+
import { logicalMargin } from "./margins.js";
|
|
3
|
+
import { storyHtml } from "./stories.js";
|
|
4
|
+
import { chapterParents, mapMasters } from "./masters.js";
|
|
5
|
+
import { ReportCollector } from "./report.js";
|
|
6
|
+
import { StyleMapper } from "./styles.js";
|
|
7
|
+
import { matchVariables } from "./variables.js";
|
|
2
8
|
import { Book, MM_PER_PT } from "@segnavia/format";
|
|
3
9
|
import { findActiveContent } from "@segnavia/html";
|
|
4
|
-
import serialize from "dom-serializer";
|
|
5
|
-
import { Element } from "domhandler";
|
|
6
10
|
//#region src/map-book.ts
|
|
7
|
-
const emptyParagraph = () => serialize(new Element("p", {}, []), { xmlMode: false });
|
|
8
11
|
function orderedStories(model) {
|
|
9
12
|
const order = /* @__PURE__ */ new Map();
|
|
10
13
|
for (const spread of model.spreads) for (const page of spread.pages) for (const story of page.stories) if (!order.has(story)) order.set(story, order.size);
|
|
11
14
|
const masterStories = new Set(model.masterStoryIds);
|
|
12
15
|
return model.stories.filter((story) => !masterStories.has(story.id)).sort((left, right) => (order.get(left.id) ?? Number.MAX_SAFE_INTEGER) - (order.get(right.id) ?? Number.MAX_SAFE_INTEGER));
|
|
13
16
|
}
|
|
14
|
-
function
|
|
17
|
+
function bodyColumnGap(model, stories, report) {
|
|
18
|
+
const storyFrames = stories.flatMap((story) => story.frame?.columns && story.frame.columns >= 2 ? [story.frame] : []);
|
|
19
|
+
const frames = (model.bodyFramePreferences ?? storyFrames).filter((frame) => frame.columns >= 2);
|
|
20
|
+
const representativeFrames = storyFrames.length > 0 ? storyFrames : frames;
|
|
21
|
+
const counts = /* @__PURE__ */ new Map();
|
|
22
|
+
for (const frame of representativeFrames) counts.set(frame.gutter, (counts.get(frame.gutter) ?? 0) + 1);
|
|
23
|
+
const selected = [...counts].sort((left, right) => right[1] - left[1])[0]?.[0];
|
|
24
|
+
if (selected === void 0) return void 0;
|
|
25
|
+
for (const gutter of new Set(frames.map((frame) => frame.gutter).filter((gutter) => gutter !== selected))) {
|
|
26
|
+
const frame = frames.find((candidate) => candidate.gutter === gutter);
|
|
27
|
+
report.add(report.unsupportedElements, "differing body frame gutters", frame?.location ?? model.id);
|
|
28
|
+
}
|
|
29
|
+
return selected;
|
|
30
|
+
}
|
|
31
|
+
function refuseActiveBookHtml(chapters, parents) {
|
|
32
|
+
const html = [...chapters.map((chapter) => chapter.html), ...parents.flatMap((parent) => [parent.head ?? "", parent.foot ?? ""])].join("");
|
|
33
|
+
const active = findActiveContent(html);
|
|
34
|
+
if (active.length > 0) throw refusal(`the imported Book contains active content (${active.join(", ")})`, "remove that active content");
|
|
35
|
+
}
|
|
36
|
+
function modelToBook(model, report = new ReportCollector(model.report)) {
|
|
15
37
|
const preferences = model.preferences;
|
|
16
38
|
const rightBinding = preferences.binding === "right-to-left";
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
39
|
+
const bookDirection = rightBinding ? "rtl" : "ltr";
|
|
40
|
+
const styles = new StyleMapper(model.styles ?? [], model.swatches ?? [], report, bookDirection);
|
|
41
|
+
const matchedVariables = matchVariables(model.textVariables ?? [], report);
|
|
42
|
+
const bookLanguage = styles.rootLanguage();
|
|
43
|
+
const stories = orderedStories(model);
|
|
44
|
+
const masters = mapMasters(model, matchedVariables, styles, report);
|
|
45
|
+
const chapters = stories.map((story) => ({
|
|
46
|
+
story,
|
|
47
|
+
mapped: storyHtml({
|
|
48
|
+
...story,
|
|
49
|
+
textVariables: matchedVariables
|
|
50
|
+
}, styles, report, bookDirection, bookLanguage)
|
|
51
|
+
})).map(({ story, mapped: value }) => {
|
|
52
|
+
const parents = chapterParents(story.id, model, masters, report);
|
|
53
|
+
return {
|
|
54
|
+
title: story.id,
|
|
55
|
+
html: value.html,
|
|
56
|
+
...parents ? { parents } : {}
|
|
57
|
+
};
|
|
58
|
+
});
|
|
21
59
|
if (chapters.length === 0) throw refusal("the IDML has no stories", "add at least one Story");
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
60
|
+
refuseActiveBookHtml(chapters, masters.parents);
|
|
61
|
+
const columnGap = bodyColumnGap(model, stories, report);
|
|
62
|
+
const css = `${styles.css()}${masters.css}`;
|
|
25
63
|
return Book.parse({
|
|
26
64
|
title: model.id,
|
|
27
65
|
mode: "paginated",
|
|
@@ -31,17 +69,16 @@ function modelToBook(model) {
|
|
|
31
69
|
height: preferences.pageHeight,
|
|
32
70
|
unit: "pt"
|
|
33
71
|
},
|
|
34
|
-
margin:
|
|
35
|
-
|
|
36
|
-
bottom: preferences.margin.bottom * MM_PER_PT,
|
|
37
|
-
inner: (rightBinding ? preferences.margin.right : preferences.margin.left) * MM_PER_PT,
|
|
38
|
-
outer: (rightBinding ? preferences.margin.left : preferences.margin.right) * MM_PER_PT
|
|
39
|
-
}
|
|
72
|
+
margin: masters.bookMargin ?? logicalMargin(preferences.margin, rightBinding ? "left" : "right"),
|
|
73
|
+
...columnGap === void 0 ? {} : { columnGap: columnGap * MM_PER_PT }
|
|
40
74
|
},
|
|
41
|
-
dir:
|
|
75
|
+
dir: bookDirection,
|
|
76
|
+
...bookLanguage ? { lang: bookLanguage } : {},
|
|
77
|
+
...css ? { css } : {},
|
|
42
78
|
rectoOpenings: preferences.facingPages,
|
|
79
|
+
parentPages: masters.parents,
|
|
43
80
|
chapters
|
|
44
81
|
});
|
|
45
82
|
}
|
|
46
83
|
//#endregion
|
|
47
|
-
export { modelToBook };
|
|
84
|
+
export { modelToBook, refuseActiveBookHtml };
|
package/dist/margins.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { numberAttribute } from "./xml.js";
|
|
2
|
+
import { MM_PER_PT } from "@segnavia/format";
|
|
3
|
+
//#region src/margins.ts
|
|
4
|
+
function readMargin(element) {
|
|
5
|
+
return {
|
|
6
|
+
top: numberAttribute(element, "Top", 0),
|
|
7
|
+
bottom: numberAttribute(element, "Bottom", 0),
|
|
8
|
+
left: numberAttribute(element, "Left", 0),
|
|
9
|
+
right: numberAttribute(element, "Right", 0)
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function logicalMargin(margin, side) {
|
|
13
|
+
return {
|
|
14
|
+
top: margin.top * MM_PER_PT,
|
|
15
|
+
bottom: margin.bottom * MM_PER_PT,
|
|
16
|
+
inner: (side === "left" ? margin.right : margin.left) * MM_PER_PT,
|
|
17
|
+
outer: (side === "left" ? margin.left : margin.right) * MM_PER_PT
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { logicalMargin, readMargin };
|
package/dist/masters.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { logicalMargin } from "./margins.js";
|
|
2
|
+
import { storyHtml } from "./stories.js";
|
|
3
|
+
import { BOOK_PARENT_NAMES, MM_PER_PT } from "@segnavia/format";
|
|
4
|
+
import serialize from "dom-serializer";
|
|
5
|
+
import { Element } from "domhandler";
|
|
6
|
+
import { parseDocument } from "htmlparser2";
|
|
7
|
+
const slug = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "master";
|
|
8
|
+
const mm = (points) => `${Number((points * MM_PER_PT).toFixed(6))}mm`;
|
|
9
|
+
function roleFor(page, rtl, ordinal) {
|
|
10
|
+
if (!page.side || page.side === "single") return (ordinal ?? 0) % 2 === 0 ? "recto" : "verso";
|
|
11
|
+
return page.side === (rtl ? "left" : "right") ? "recto" : "verso";
|
|
12
|
+
}
|
|
13
|
+
function masterPage(master, role, rtl) {
|
|
14
|
+
if (master.pages.length === 1) return master.pages[0];
|
|
15
|
+
const side = role === "recto" ? rtl ? "left" : "right" : rtl ? "right" : "left";
|
|
16
|
+
return master.pages.find((page) => page.side === side);
|
|
17
|
+
}
|
|
18
|
+
function parentName(master, role) {
|
|
19
|
+
return `idml-${slug(master.name)}-${master.pages.length === 1 ? "recto" : role}`;
|
|
20
|
+
}
|
|
21
|
+
function frameRegion(page, frame, height) {
|
|
22
|
+
if (frame.bounds.top + frame.bounds.height <= page.margin.top) return "head";
|
|
23
|
+
if (frame.bounds.top >= height - page.margin.bottom) return "foot";
|
|
24
|
+
}
|
|
25
|
+
function regionHtml(master, page, region, context) {
|
|
26
|
+
const boxes = [];
|
|
27
|
+
for (const frame of page.frames) {
|
|
28
|
+
if (frameRegion(page, frame, context.model.preferences.pageHeight) !== region) continue;
|
|
29
|
+
const story = context.stories.get(frame.story);
|
|
30
|
+
if (!story) {
|
|
31
|
+
context.report.add(context.report.unsupportedElements, "master story missing", `${master.name}#${frame.id}`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const className = `idml-master-frame-${context.css.length}`;
|
|
35
|
+
const mapped = storyHtml({
|
|
36
|
+
...story,
|
|
37
|
+
textVariables: context.variables,
|
|
38
|
+
master: true
|
|
39
|
+
}, context.styles, context.report, context.rtl ? "rtl" : "ltr", void 0);
|
|
40
|
+
const nodes = parseDocument(mapped.html, { xmlMode: false }).children;
|
|
41
|
+
boxes.push(new Element("div", { class: className }, nodes));
|
|
42
|
+
const right = frame.bounds.left + frame.bounds.width;
|
|
43
|
+
const inlineStart = context.rtl ? context.model.preferences.pageWidth - page.margin.right - right : frame.bounds.left - page.margin.left;
|
|
44
|
+
const blockStart = region === "head" ? frame.bounds.top : frame.bounds.top - (context.model.preferences.pageHeight - page.margin.bottom);
|
|
45
|
+
context.css.push(`:where(.${className}){position:absolute;inset-inline-start:${mm(inlineStart)};inline-size:${mm(frame.bounds.width)};inset-block-start:${mm(blockStart)};block-size:${mm(frame.bounds.height)}}`);
|
|
46
|
+
context.mappedStories.add(frame.story);
|
|
47
|
+
}
|
|
48
|
+
if (boxes.length === 0) return void 0;
|
|
49
|
+
return serialize(new Element("div", { class: "idml-master-region" }, boxes), {
|
|
50
|
+
xmlMode: false,
|
|
51
|
+
encodeEntities: "utf8"
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function pageSide(page, role, rtl) {
|
|
55
|
+
if (page.side === "left" || page.side === "right") return page.side;
|
|
56
|
+
return role === "recto" ? rtl ? "left" : "right" : rtl ? "right" : "left";
|
|
57
|
+
}
|
|
58
|
+
function buildParent(master, role, byId, context) {
|
|
59
|
+
const page = masterPage(master, role, context.rtl);
|
|
60
|
+
if (!page) return void 0;
|
|
61
|
+
const base = master.basedOn ? byId.get(master.basedOn) : void 0;
|
|
62
|
+
const head = regionHtml(master, page, "head", context);
|
|
63
|
+
const foot = regionHtml(master, page, "foot", context);
|
|
64
|
+
for (const frame of page.frames) if (!frameRegion(page, frame, context.model.preferences.pageHeight)) context.report.add(context.report.unsupportedElements, "master TextFrame", `${master.name}#${frame.id}`);
|
|
65
|
+
return {
|
|
66
|
+
name: parentName(master, role),
|
|
67
|
+
...base ? { basedOn: parentName(base, role) } : {},
|
|
68
|
+
margin: logicalMargin(page.margin, pageSide(page, role, context.rtl)),
|
|
69
|
+
...head ? { head } : {},
|
|
70
|
+
...foot ? { foot } : {}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function reportExtraPages(master, selected, report) {
|
|
74
|
+
for (const page of master.pages) {
|
|
75
|
+
if (selected.has(page.id)) continue;
|
|
76
|
+
report.add(report.unsupportedElements, "master page", `${master.name}#${page.id}`);
|
|
77
|
+
for (const frame of page.frames) report.add(report.unsupportedElements, "master TextFrame", `${master.name}#${frame.id}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function buildParents(masters, byId, context) {
|
|
81
|
+
const result = [];
|
|
82
|
+
for (const master of masters) {
|
|
83
|
+
const roles = master.pages.length === 1 ? ["recto"] : ["recto", "verso"];
|
|
84
|
+
const selected = /* @__PURE__ */ new Set();
|
|
85
|
+
for (const role of roles) {
|
|
86
|
+
const page = masterPage(master, role, context.rtl);
|
|
87
|
+
if (page) selected.add(page.id);
|
|
88
|
+
const parent = buildParent(master, role, byId, context);
|
|
89
|
+
if (parent) result.push(parent);
|
|
90
|
+
}
|
|
91
|
+
reportExtraPages(master, selected, context.report);
|
|
92
|
+
for (const item of master.unmappedItems) context.report.add(context.report.unsupportedElements, `master ${item.type}`, `${master.name}#${item.id}`);
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
function emittedMasters(model, applied) {
|
|
97
|
+
const byId = new Map(model.masterSpreads.map((master) => [master.id, master]));
|
|
98
|
+
const emitted = new Set(applied);
|
|
99
|
+
for (const id of emitted) {
|
|
100
|
+
const basedOn = byId.get(id)?.basedOn;
|
|
101
|
+
if (basedOn) emitted.add(basedOn);
|
|
102
|
+
}
|
|
103
|
+
return model.masterSpreads.filter((master) => emitted.has(master.id));
|
|
104
|
+
}
|
|
105
|
+
function appliedBookMargin(model, applied, report) {
|
|
106
|
+
const masters = model.masterSpreads.filter((master) => applied.has(master.id));
|
|
107
|
+
if (masters.length !== applied.size) return void 0;
|
|
108
|
+
const pages = masters.flatMap((master) => master.pages.map((page) => ({
|
|
109
|
+
master,
|
|
110
|
+
page
|
|
111
|
+
})));
|
|
112
|
+
const first = pages[0]?.page.margin;
|
|
113
|
+
if (!first) return void 0;
|
|
114
|
+
const signature = (margin) => `${margin.top},${margin.bottom},${margin.left},${margin.right}`;
|
|
115
|
+
if (pages.some(({ page }) => signature(page.margin) !== signature(first))) {
|
|
116
|
+
for (const master of masters) {
|
|
117
|
+
const margins = [...new Set(master.pages.map((page) => signature(page.margin)))];
|
|
118
|
+
report.add(report.unsupportedElements, "master margins disagree", `${master.name}:${margins.join("|")}`);
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
return logicalMargin(first, model.preferences.binding === "right-to-left" ? "left" : "right");
|
|
123
|
+
}
|
|
124
|
+
function mapMasters(model, variables, styles, report) {
|
|
125
|
+
const rtl = model.preferences.binding === "right-to-left";
|
|
126
|
+
const applied = new Set(model.spreads.flatMap((spread) => spread.pages.map((page) => page.appliedMaster).filter((id) => Boolean(id && id !== "n"))));
|
|
127
|
+
const masters = emittedMasters(model, applied);
|
|
128
|
+
const byId = new Map(model.masterSpreads.map((master) => [master.id, master]));
|
|
129
|
+
for (const spread of model.spreads) for (const page of spread.pages) {
|
|
130
|
+
const id = page.appliedMaster;
|
|
131
|
+
if (id && id !== "n" && !byId.has(id)) report.addOnce(report.unsupportedElements, `applied master unknown:${id}`, page.id);
|
|
132
|
+
}
|
|
133
|
+
const css = [":where(.idml-master-region){position:relative;block-size:100%}"];
|
|
134
|
+
const mappedStories = /* @__PURE__ */ new Set();
|
|
135
|
+
const parents = buildParents(masters, byId, {
|
|
136
|
+
model,
|
|
137
|
+
variables,
|
|
138
|
+
styles,
|
|
139
|
+
report,
|
|
140
|
+
rtl,
|
|
141
|
+
stories: new Map(model.stories.map((story) => [story.id, story])),
|
|
142
|
+
mappedStories,
|
|
143
|
+
css
|
|
144
|
+
});
|
|
145
|
+
if (model.spreads.some((spread) => spread.pages.some((page) => !page.appliedMaster || page.appliedMaster === "n"))) parents.push({
|
|
146
|
+
name: "idml-none",
|
|
147
|
+
basedOn: BOOK_PARENT_NAMES.blank
|
|
148
|
+
});
|
|
149
|
+
for (const story of model.masterStoryIds) if (!mappedStories.has(story)) report.add(report.unsupportedElements, "master story", story);
|
|
150
|
+
const parentFor = (masterId, role, location) => {
|
|
151
|
+
if (!masterId || masterId === "n") return "idml-none";
|
|
152
|
+
const master = byId.get(masterId);
|
|
153
|
+
if (master && applied.has(masterId)) return parentName(master, role);
|
|
154
|
+
report.addOnce(report.unsupportedElements, `applied master unknown:${masterId}`, location);
|
|
155
|
+
return role === "recto" ? BOOK_PARENT_NAMES.recto : BOOK_PARENT_NAMES.verso;
|
|
156
|
+
};
|
|
157
|
+
return {
|
|
158
|
+
parents,
|
|
159
|
+
css: css.join(""),
|
|
160
|
+
bookMargin: appliedBookMargin(model, applied, report),
|
|
161
|
+
parentFor,
|
|
162
|
+
mappedStories
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function chapterParents(story, model, mapping, report) {
|
|
166
|
+
const rtl = model.preferences.binding === "right-to-left";
|
|
167
|
+
const pages = [];
|
|
168
|
+
let ordinal = 0;
|
|
169
|
+
for (const spread of model.spreads) for (const page of spread.pages) {
|
|
170
|
+
if (page.stories.includes(story)) pages.push({
|
|
171
|
+
page,
|
|
172
|
+
ordinal
|
|
173
|
+
});
|
|
174
|
+
ordinal += 1;
|
|
175
|
+
}
|
|
176
|
+
const first = pages[0];
|
|
177
|
+
if (!first) return void 0;
|
|
178
|
+
const firstRole = roleFor(first.page, rtl, first.ordinal);
|
|
179
|
+
const result = { opener: mapping.parentFor(first.page.appliedMaster, firstRole, first.page.id) };
|
|
180
|
+
for (const role of ["recto", "verso"]) {
|
|
181
|
+
const candidates = pages.slice(1).filter((entry) => roleFor(entry.page, rtl, entry.ordinal) === role);
|
|
182
|
+
if (candidates.length === 0) continue;
|
|
183
|
+
const counts = /* @__PURE__ */ new Map();
|
|
184
|
+
for (const entry of candidates) {
|
|
185
|
+
const id = entry.page.appliedMaster ?? "n";
|
|
186
|
+
counts.set(id, (counts.get(id) ?? 0) + 1);
|
|
187
|
+
}
|
|
188
|
+
const chosen = [...counts].sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
189
|
+
if (chosen) result[role] = mapping.parentFor(chosen, role, candidates[0]?.page.id ?? story);
|
|
190
|
+
if (chosen && counts.size > 1) report.mixedMasterChapters.push({
|
|
191
|
+
chapter: story,
|
|
192
|
+
side: role,
|
|
193
|
+
masters: [...counts.keys()],
|
|
194
|
+
chosen
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
//#endregion
|
|
200
|
+
export { chapterParents, mapMasters };
|
package/dist/model.d.ts
CHANGED
|
@@ -1,34 +1,6 @@
|
|
|
1
|
+
import { IdmlReportData } from "./report.js";
|
|
2
|
+
import { IdmlDocumentPreferences, IdmlFramePreference, IdmlMargin, IdmlMasterPage, IdmlMasterSpread, IdmlPage, IdmlSpread, IdmlStory, IdmlStyle, IdmlSwatch, IdmlTextVariable } from "./types.js";
|
|
1
3
|
//#region src/model.d.ts
|
|
2
|
-
export interface IdmlMargin {
|
|
3
|
-
top: number;
|
|
4
|
-
bottom: number;
|
|
5
|
-
left: number;
|
|
6
|
-
right: number;
|
|
7
|
-
}
|
|
8
|
-
export interface IdmlDocumentPreferences {
|
|
9
|
-
pageWidth: number;
|
|
10
|
-
pageHeight: number;
|
|
11
|
-
facingPages: boolean;
|
|
12
|
-
binding: 'left-to-right' | 'right-to-left';
|
|
13
|
-
margin: IdmlMargin;
|
|
14
|
-
}
|
|
15
|
-
export interface IdmlPage {
|
|
16
|
-
id: string;
|
|
17
|
-
appliedMaster?: string;
|
|
18
|
-
stories: string[];
|
|
19
|
-
}
|
|
20
|
-
export interface IdmlSpread {
|
|
21
|
-
id: string;
|
|
22
|
-
pages: IdmlPage[];
|
|
23
|
-
}
|
|
24
|
-
export interface IdmlMasterSpread {
|
|
25
|
-
id: string;
|
|
26
|
-
pages: IdmlPage[];
|
|
27
|
-
}
|
|
28
|
-
export interface IdmlStory {
|
|
29
|
-
id: string;
|
|
30
|
-
part: string;
|
|
31
|
-
}
|
|
32
4
|
export interface IdmlModel {
|
|
33
5
|
id: string;
|
|
34
6
|
preferences: IdmlDocumentPreferences;
|
|
@@ -37,6 +9,11 @@ export interface IdmlModel {
|
|
|
37
9
|
stories: IdmlStory[];
|
|
38
10
|
masterStoryIds: string[];
|
|
39
11
|
partsRead: string[];
|
|
40
|
-
|
|
12
|
+
styles?: IdmlStyle[];
|
|
13
|
+
swatches?: IdmlSwatch[];
|
|
14
|
+
bodyFramePreferences?: IdmlFramePreference[];
|
|
15
|
+
textVariables?: IdmlTextVariable[];
|
|
16
|
+
report?: IdmlReportData;
|
|
41
17
|
}
|
|
42
|
-
//#endregion
|
|
18
|
+
//#endregion
|
|
19
|
+
export type { IdmlDocumentPreferences, IdmlMargin, IdmlMasterSpread, IdmlPage, IdmlSpread, IdmlStory };
|