@segnavia/idml 0.33.0 → 0.35.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/anchored-frames.js +81 -0
- package/dist/chapter-openings.js +16 -0
- package/dist/map-book.js +65 -14
- package/dist/master-regions.js +89 -0
- package/dist/masters.js +30 -67
- package/dist/model.d.ts +2 -1
- package/dist/model.js +13 -6
- package/dist/openers.js +2 -14
- package/dist/paragraph-box.js +123 -0
- package/dist/placements.js +20 -14
- package/dist/positioned-box.js +9 -1
- package/dist/report.d.ts +1 -0
- package/dist/report.js +16 -6
- package/dist/stories.js +39 -13
- package/dist/story-model.js +9 -1
- package/dist/style-mappings.js +20 -0
- package/dist/style-property-report.js +19 -16
- package/dist/styles.js +75 -34
- package/dist/swatch-color.js +5 -5
- package/dist/types.d.ts +21 -1
- package/dist/unsupported-elements.js +2 -1
- package/package.json +3 -3
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { applyTransform, itemTransform, placementBounds, pointValue } from "./placements.js";
|
|
2
|
+
import { formatRoundedNumber, logicalCornerRadii } from "./positioned-box.js";
|
|
3
|
+
import { flattenedStyle } from "./styles.js";
|
|
4
|
+
//#region src/anchored-frames.ts
|
|
5
|
+
function descendants(node) {
|
|
6
|
+
return node.children.flatMap((child) => [child, ...descendants(child)]);
|
|
7
|
+
}
|
|
8
|
+
function width(node) {
|
|
9
|
+
const transform = itemTransform(node.attributes.ItemTransform);
|
|
10
|
+
const anchors = descendants(node).flatMap((child) => {
|
|
11
|
+
const point = pointValue(child.attributes.Anchor);
|
|
12
|
+
return point ? [applyTransform(transform, point)] : [];
|
|
13
|
+
});
|
|
14
|
+
const bounds = placementBounds(anchors);
|
|
15
|
+
if (!bounds) return void 0;
|
|
16
|
+
const value = bounds.right - bounds.left;
|
|
17
|
+
return value > 0 ? value : void 0;
|
|
18
|
+
}
|
|
19
|
+
const finite = (value) => {
|
|
20
|
+
const parsed = Number(value);
|
|
21
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
22
|
+
};
|
|
23
|
+
function corner(values, name) {
|
|
24
|
+
const option = values[`${name}CornerOption`] ?? values.CornerOption;
|
|
25
|
+
const radius = finite(values[`${name}CornerRadius`] ?? values.CornerRadius);
|
|
26
|
+
return option && !option.endsWith("None") && radius && radius > 0 ? radius : void 0;
|
|
27
|
+
}
|
|
28
|
+
function anchoredFrame(node, styles) {
|
|
29
|
+
const story = node.attributes.ParentStory;
|
|
30
|
+
if (!story) return void 0;
|
|
31
|
+
const setting = node.children.find((child) => child.name === "Properties")?.children.find((child) => child.name === "AnchoredObjectSetting");
|
|
32
|
+
const values = {
|
|
33
|
+
...flattenedStyle(styles, node.attributes.AppliedObjectStyle, "object"),
|
|
34
|
+
...node.attributes
|
|
35
|
+
};
|
|
36
|
+
const strokeWeight = finite(values.StrokeWeight);
|
|
37
|
+
const strokeColor = values.StrokeColor;
|
|
38
|
+
const corners = {
|
|
39
|
+
topLeft: corner(values, "TopLeft"),
|
|
40
|
+
topRight: corner(values, "TopRight"),
|
|
41
|
+
bottomLeft: corner(values, "BottomLeft"),
|
|
42
|
+
bottomRight: corner(values, "BottomRight")
|
|
43
|
+
};
|
|
44
|
+
const anchorYoffset = finite(setting?.attributes.AnchorYoffset);
|
|
45
|
+
return {
|
|
46
|
+
story,
|
|
47
|
+
location: node.location,
|
|
48
|
+
width: width(node),
|
|
49
|
+
position: setting?.attributes.AnchoredPosition ?? "InlinePosition",
|
|
50
|
+
...anchorYoffset === void 0 ? {} : { anchorYoffset },
|
|
51
|
+
...values.FillColor && !values.FillColor.endsWith("/None") && values.FillColor !== "None" ? { fill: values.FillColor } : {},
|
|
52
|
+
...strokeWeight && strokeWeight > 0 && strokeColor && !strokeColor.endsWith("/None") && strokeColor !== "None" ? { stroke: {
|
|
53
|
+
color: strokeColor,
|
|
54
|
+
weight: strokeWeight
|
|
55
|
+
} } : {},
|
|
56
|
+
...Object.values(corners).some((value) => value !== void 0) ? { corners } : {}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function parseAnchoredFrames(stories, styles) {
|
|
60
|
+
return stories.flatMap((story) => story.root ? descendants(story.root).flatMap((node) => node.name === "TextFrame" ? [anchoredFrame(node, styles)].filter(Boolean) : []) : []);
|
|
61
|
+
}
|
|
62
|
+
function cornerDeclarations(frame, direction) {
|
|
63
|
+
const corners = frame.corners;
|
|
64
|
+
if (!corners) return [];
|
|
65
|
+
return Object.entries(logicalCornerRadii(corners, direction)).flatMap(([property, value]) => value === void 0 ? [] : [`${property}:${formatRoundedNumber(value)}pt`]);
|
|
66
|
+
}
|
|
67
|
+
function anchoredFrameStyle(frame, resolveColor, direction) {
|
|
68
|
+
const output = [
|
|
69
|
+
`display:inline-block`,
|
|
70
|
+
`inline-size:${formatRoundedNumber(frame.width ?? 0)}pt`,
|
|
71
|
+
`vertical-align:baseline`
|
|
72
|
+
];
|
|
73
|
+
const fill = frame.fill ? resolveColor(frame.fill) : void 0;
|
|
74
|
+
if (fill && fill !== "none") output.push(`background:${fill}`);
|
|
75
|
+
const stroke = frame.stroke ? resolveColor(frame.stroke.color) : void 0;
|
|
76
|
+
if (stroke && stroke !== "none" && frame.stroke) output.push(`border:${formatRoundedNumber(frame.stroke.weight)}pt solid ${stroke}`);
|
|
77
|
+
output.push(...cornerDeclarations(frame, direction));
|
|
78
|
+
return output.join(";");
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { anchoredFrameStyle, parseAnchoredFrames };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/chapter-openings.ts
|
|
2
|
+
function chapterOpenings(openings, facingPages) {
|
|
3
|
+
if (!facingPages) return {
|
|
4
|
+
rectoOpenings: false,
|
|
5
|
+
verso: []
|
|
6
|
+
};
|
|
7
|
+
return {
|
|
8
|
+
rectoOpenings: openings.length > 0 && openings.every((opening) => opening.role === "recto"),
|
|
9
|
+
verso: openings.filter((opening) => opening.role === "verso").map(({ story, page }) => ({
|
|
10
|
+
story,
|
|
11
|
+
page
|
|
12
|
+
}))
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { chapterOpenings };
|
package/dist/map-book.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import { refusal } from "./refusal.js";
|
|
2
2
|
import { ReportCollector } from "./report.js";
|
|
3
3
|
import { FontMapper } from "./fonts.js";
|
|
4
|
+
import { resolveSwatchColor } from "./swatch-color.js";
|
|
5
|
+
import { StyleMapper } from "./styles.js";
|
|
6
|
+
import { anchoredFrameStyle } from "./anchored-frames.js";
|
|
7
|
+
import { chapterOpenings } from "./chapter-openings.js";
|
|
4
8
|
import { logicalMargin } from "./margins.js";
|
|
5
9
|
import { storyHtml } from "./stories.js";
|
|
6
|
-
import { chapterParents, mapMasters } from "./masters.js";
|
|
10
|
+
import { chapterParents, mapMasters, openingPage, storyPageUses } from "./masters.js";
|
|
7
11
|
import { mapOpeners } from "./openers.js";
|
|
8
12
|
import { reportPlacements } from "./placement-report.js";
|
|
9
|
-
import { StyleMapper } from "./styles.js";
|
|
10
13
|
import { matchVariables } from "./variables.js";
|
|
11
14
|
import { Book, MM_PER_PT } from "@segnavia/format";
|
|
12
15
|
import { findActiveContent } from "@segnavia/html";
|
|
13
16
|
//#region src/map-book.ts
|
|
14
|
-
function orderedStories(model) {
|
|
17
|
+
function orderedStories(model, anchoredOnly = /* @__PURE__ */ new Set()) {
|
|
15
18
|
const order = /* @__PURE__ */ new Map();
|
|
16
19
|
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);
|
|
17
20
|
const masterStories = new Set(model.masterStoryIds);
|
|
18
|
-
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));
|
|
21
|
+
return model.stories.filter((story) => !masterStories.has(story.id) && !anchoredOnly.has(story.id)).sort((left, right) => (order.get(left.id) ?? Number.MAX_SAFE_INTEGER) - (order.get(right.id) ?? Number.MAX_SAFE_INTEGER));
|
|
19
22
|
}
|
|
20
23
|
function bodyColumnGap(model, stories, report) {
|
|
21
24
|
const storyFrames = stories.flatMap((story) => story.frame?.columns && story.frame.columns >= 2 ? [story.frame] : []);
|
|
@@ -31,6 +34,13 @@ function bodyColumnGap(model, stories, report) {
|
|
|
31
34
|
}
|
|
32
35
|
return selected;
|
|
33
36
|
}
|
|
37
|
+
function anchoredFrameIssue(frame, storyPages, depth, ancestors) {
|
|
38
|
+
if (frame.position !== "InlinePosition") return frame.position;
|
|
39
|
+
if (ancestors.has(frame.story)) return "cycle";
|
|
40
|
+
if (storyPages.has(frame.story)) return "InlinePosition";
|
|
41
|
+
if (depth >= 3) return "depth";
|
|
42
|
+
if (frame.width === void 0) return "missing geometry";
|
|
43
|
+
}
|
|
34
44
|
function refuseActiveBookHtml(chapters, parents) {
|
|
35
45
|
const html = [...chapters.map((chapter) => chapter.html), ...parents.flatMap((parent) => [parent.head ?? "", parent.foot ?? ""])].join("");
|
|
36
46
|
const active = findActiveContent(html);
|
|
@@ -44,19 +54,60 @@ function modelToBook(model, report = new ReportCollector(model.report), options
|
|
|
44
54
|
const styles = new StyleMapper(model.styles ?? [], model.swatches ?? [], report, bookDirection, fonts);
|
|
45
55
|
const matchedVariables = matchVariables(model.textVariables ?? [], report);
|
|
46
56
|
const bookLanguage = styles.rootLanguage();
|
|
47
|
-
const
|
|
48
|
-
const
|
|
57
|
+
const storyPages = storyPageUses(model);
|
|
58
|
+
const anchoredStoryIds = new Set((model.anchoredFrames ?? []).map((frame) => frame.story));
|
|
59
|
+
const stories = orderedStories(model, new Set([...anchoredStoryIds].filter((story) => !storyPages.has(story))));
|
|
60
|
+
const storyById = new Map(model.stories.map((story) => [story.id, story]));
|
|
61
|
+
const frameByLocation = new Map((model.anchoredFrames ?? []).map((frame) => [frame.location, frame]));
|
|
62
|
+
const renderStory = (story, depth = 0, ancestors = /* @__PURE__ */ new Set([story.id]), master = false) => storyHtml({
|
|
63
|
+
...story,
|
|
64
|
+
textVariables: matchedVariables,
|
|
65
|
+
master
|
|
66
|
+
}, styles, report, bookDirection, bookLanguage, {
|
|
67
|
+
inlineParagraphs: depth > 0,
|
|
68
|
+
anchored: (location, direction) => {
|
|
69
|
+
const frame = frameByLocation.get(location);
|
|
70
|
+
if (!frame) {
|
|
71
|
+
report.add(report.unsupportedElements, "TextFrame", location);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (frame.position === "InlinePosition" && frame.anchorYoffset !== void 0 && frame.anchorYoffset !== 0) report.add(report.unsupportedElements, "TextFrame anchored:AnchorYoffset", frame.location);
|
|
75
|
+
const issue = anchoredFrameIssue(frame, storyPages, depth, ancestors);
|
|
76
|
+
if (issue) {
|
|
77
|
+
report.add(report.unsupportedElements, `TextFrame anchored:${issue}`, frame.location);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const nested = storyById.get(frame.story);
|
|
81
|
+
if (!nested) {
|
|
82
|
+
report.add(report.unsupportedElements, "TextFrame anchored:missing story", frame.location);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const nestedAncestors = new Set(ancestors);
|
|
86
|
+
nestedAncestors.add(frame.story);
|
|
87
|
+
const html = renderStory(nested, depth + 1, nestedAncestors, master).html;
|
|
88
|
+
return `<span class="idml-anchored" style="${anchoredFrameStyle(frame, (color) => resolveSwatchColor(color, model.swatches ?? [], report, frame.location, "fill"), direction)}">${html}</span>`;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
const masters = mapMasters(model, matchedVariables, styles, report, (story) => renderStory(story, 0, /* @__PURE__ */ new Set([story.id]), true));
|
|
49
92
|
const mapped = stories.map((story) => ({
|
|
50
93
|
story,
|
|
51
|
-
mapped:
|
|
52
|
-
...story,
|
|
53
|
-
textVariables: matchedVariables
|
|
54
|
-
}, styles, report, bookDirection, bookLanguage)
|
|
94
|
+
mapped: renderStory(story)
|
|
55
95
|
}));
|
|
56
|
-
const openers = mapOpeners(model, mapped, masters, report, options.asset);
|
|
96
|
+
const openers = mapOpeners(model, mapped, storyPages, masters, report, options.asset);
|
|
57
97
|
reportPlacements(model, openers.consumedPages, report);
|
|
58
|
-
const
|
|
59
|
-
|
|
98
|
+
const emitted = mapped.filter(({ story }) => !openers.claimed.has(story.id));
|
|
99
|
+
const openings = emitted.flatMap(({ story }) => {
|
|
100
|
+
const opening = openingPage(story.id, model, storyPages);
|
|
101
|
+
return opening ? [{
|
|
102
|
+
story: story.id,
|
|
103
|
+
page: opening.page.id,
|
|
104
|
+
role: opening.role
|
|
105
|
+
}] : [];
|
|
106
|
+
});
|
|
107
|
+
const openingPolicy = chapterOpenings(openings, preferences.facingPages);
|
|
108
|
+
for (const opening of openingPolicy.verso) report.versoOpening(opening.story, opening.page);
|
|
109
|
+
const chapters = emitted.map(({ story, mapped: value }) => {
|
|
110
|
+
const parents = chapterParents(story.id, model, storyPages, masters, report);
|
|
60
111
|
return {
|
|
61
112
|
title: story.id,
|
|
62
113
|
html: openers.html.get(story.id) ?? value.html,
|
|
@@ -84,7 +135,7 @@ function modelToBook(model, report = new ReportCollector(model.report), options
|
|
|
84
135
|
...bookLanguage ? { lang: bookLanguage } : {},
|
|
85
136
|
...css ? { css } : {},
|
|
86
137
|
...bookFonts.length > 0 ? { fonts: bookFonts } : {},
|
|
87
|
-
rectoOpenings:
|
|
138
|
+
rectoOpenings: openingPolicy.rectoOpenings,
|
|
88
139
|
parentPages: masters.parents,
|
|
89
140
|
chapters
|
|
90
141
|
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { positionedBoxStyle } from "./positioned-box.js";
|
|
2
|
+
import { storyHtml } from "./stories.js";
|
|
3
|
+
import { Element } from "domhandler";
|
|
4
|
+
import serialize from "dom-serializer";
|
|
5
|
+
import { parseDocument } from "htmlparser2";
|
|
6
|
+
const MAX_BASE_DEPTH = 8;
|
|
7
|
+
function masterPage(master, role, rtl) {
|
|
8
|
+
if (master.pages.length === 1) return master.pages[0];
|
|
9
|
+
const side = pageSideForRole(role, rtl);
|
|
10
|
+
return master.pages.find((page) => page.side === side);
|
|
11
|
+
}
|
|
12
|
+
function pageSideForRole(role, rtl) {
|
|
13
|
+
return role === "recto" ? rtl ? "left" : "right" : rtl ? "right" : "left";
|
|
14
|
+
}
|
|
15
|
+
function frameRegion(page, frame, height) {
|
|
16
|
+
if (frame.bounds.top + frame.bounds.height <= page.margin.top) return "head";
|
|
17
|
+
if (frame.bounds.top >= height - page.margin.bottom) return "foot";
|
|
18
|
+
}
|
|
19
|
+
function itemBaseId(master, page) {
|
|
20
|
+
if (master.showMasterItems === false || page.showMasterItems === false) return void 0;
|
|
21
|
+
if (page.appliedMaster && page.appliedMaster !== "n") return page.appliedMaster;
|
|
22
|
+
return master.basedOn;
|
|
23
|
+
}
|
|
24
|
+
function ownBoxes(master, page, applyingPage, region, context) {
|
|
25
|
+
const boxes = [];
|
|
26
|
+
for (const frame of page.frames) {
|
|
27
|
+
const mappedRegion = frameRegion(applyingPage, frame, context.model.preferences.pageHeight);
|
|
28
|
+
if (!mappedRegion) context.report.addOnce(context.report.unsupportedElements, "master TextFrame", `${master.name}#${frame.id}`);
|
|
29
|
+
if (mappedRegion !== region) continue;
|
|
30
|
+
const story = context.stories.get(frame.story);
|
|
31
|
+
if (!story) {
|
|
32
|
+
context.report.add(context.report.unsupportedElements, "master story missing", `${master.name}#${frame.id}`);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const className = `idml-master-frame-${context.css.length}`;
|
|
36
|
+
const mapped = context.renderStory ? context.renderStory(story) : storyHtml({
|
|
37
|
+
...story,
|
|
38
|
+
textVariables: context.variables,
|
|
39
|
+
master: true
|
|
40
|
+
}, context.styles, context.report, context.rtl ? "rtl" : "ltr", void 0);
|
|
41
|
+
boxes.push(new Element("div", { class: className }, parseDocument(mapped.html, { xmlMode: false }).children));
|
|
42
|
+
const blockStart = region === "head" ? frame.bounds.top : frame.bounds.top - (context.model.preferences.pageHeight - applyingPage.margin.bottom);
|
|
43
|
+
const bounds = {
|
|
44
|
+
left: frame.bounds.left,
|
|
45
|
+
top: blockStart,
|
|
46
|
+
right: frame.bounds.left + frame.bounds.width,
|
|
47
|
+
bottom: blockStart + frame.bounds.height
|
|
48
|
+
};
|
|
49
|
+
context.css.push(`:where(.${className}){${positionedBoxStyle(bounds, {
|
|
50
|
+
pageWidth: context.model.preferences.pageWidth,
|
|
51
|
+
margin: applyingPage.margin,
|
|
52
|
+
rtl: context.rtl,
|
|
53
|
+
blockOrigin: 0,
|
|
54
|
+
unit: "mm"
|
|
55
|
+
})}}`);
|
|
56
|
+
context.mappedStories.add(frame.story);
|
|
57
|
+
}
|
|
58
|
+
return boxes;
|
|
59
|
+
}
|
|
60
|
+
function regionBoxes(master, page, role, region, byId, context, applyingPage, ancestors, depth) {
|
|
61
|
+
const boxes = [];
|
|
62
|
+
const baseId = itemBaseId(master, page);
|
|
63
|
+
if (baseId) {
|
|
64
|
+
const location = `${master.name}#${page.id}`;
|
|
65
|
+
if (ancestors.has(baseId)) context.report.addOnce(context.report.unsupportedElements, "master base cycle", location);
|
|
66
|
+
else if (depth >= MAX_BASE_DEPTH) context.report.addOnce(context.report.unsupportedElements, "master base depth", location);
|
|
67
|
+
else {
|
|
68
|
+
const base = byId.get(baseId);
|
|
69
|
+
const basePage = base ? masterPage(base, role, context.rtl) : void 0;
|
|
70
|
+
if (base && basePage) {
|
|
71
|
+
const nextAncestors = new Set(ancestors);
|
|
72
|
+
nextAncestors.add(base.id);
|
|
73
|
+
boxes.push(...regionBoxes(base, basePage, role, region, byId, context, applyingPage, nextAncestors, depth + 1));
|
|
74
|
+
} else context.report.addOnce(context.report.unsupportedElements, `master base unknown:${baseId}`, location);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
boxes.push(...ownBoxes(master, page, applyingPage, region, context));
|
|
78
|
+
return boxes;
|
|
79
|
+
}
|
|
80
|
+
function regionHtml(master, page, role, region, byId, context, empty = false) {
|
|
81
|
+
const boxes = regionBoxes(master, page, role, region, byId, context, page, /* @__PURE__ */ new Set([master.id]), 0);
|
|
82
|
+
if (boxes.length === 0 && !empty) return void 0;
|
|
83
|
+
return serialize(new Element("div", { class: "idml-master-region" }, boxes), {
|
|
84
|
+
xmlMode: false,
|
|
85
|
+
encodeEntities: "utf8"
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { frameRegion, masterPage, pageSideForRole, regionHtml };
|
package/dist/masters.js
CHANGED
|
@@ -1,77 +1,26 @@
|
|
|
1
1
|
import { logicalMargin } from "./margins.js";
|
|
2
|
-
import {
|
|
3
|
-
import { storyHtml } from "./stories.js";
|
|
2
|
+
import { masterPage, pageSideForRole, regionHtml } from "./master-regions.js";
|
|
4
3
|
import { BOOK_PARENT_NAMES } from "@segnavia/format";
|
|
5
|
-
|
|
6
|
-
import serialize from "dom-serializer";
|
|
7
|
-
import { parseDocument } from "htmlparser2";
|
|
4
|
+
//#region src/masters.ts
|
|
8
5
|
const slug = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "master";
|
|
9
6
|
function roleFor(page, rtl, ordinal) {
|
|
10
7
|
if (!page.side || page.side === "single") return (ordinal ?? 0) % 2 === 0 ? "recto" : "verso";
|
|
11
8
|
return page.side === (rtl ? "left" : "right") ? "recto" : "verso";
|
|
12
9
|
}
|
|
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
10
|
function parentName(master, role) {
|
|
19
11
|
return `idml-${slug(master.name)}-${master.pages.length === 1 ? "recto" : role}`;
|
|
20
12
|
}
|
|
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 blockStart = region === "head" ? frame.bounds.top : frame.bounds.top - (context.model.preferences.pageHeight - page.margin.bottom);
|
|
43
|
-
const bounds = {
|
|
44
|
-
left: frame.bounds.left,
|
|
45
|
-
top: blockStart,
|
|
46
|
-
right: frame.bounds.left + frame.bounds.width,
|
|
47
|
-
bottom: blockStart + frame.bounds.height
|
|
48
|
-
};
|
|
49
|
-
context.css.push(`:where(.${className}){${positionedBoxStyle(bounds, {
|
|
50
|
-
pageWidth: context.model.preferences.pageWidth,
|
|
51
|
-
margin: page.margin,
|
|
52
|
-
rtl: context.rtl,
|
|
53
|
-
blockOrigin: 0,
|
|
54
|
-
unit: "mm"
|
|
55
|
-
})}}`);
|
|
56
|
-
context.mappedStories.add(frame.story);
|
|
57
|
-
}
|
|
58
|
-
if (boxes.length === 0) return void 0;
|
|
59
|
-
return serialize(new Element("div", { class: "idml-master-region" }, boxes), {
|
|
60
|
-
xmlMode: false,
|
|
61
|
-
encodeEntities: "utf8"
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
13
|
function pageSide(page, role, rtl) {
|
|
65
14
|
if (page.side === "left" || page.side === "right") return page.side;
|
|
66
|
-
return role
|
|
15
|
+
return pageSideForRole(role, rtl);
|
|
67
16
|
}
|
|
68
17
|
function buildParent(master, role, byId, context) {
|
|
69
18
|
const page = masterPage(master, role, context.rtl);
|
|
70
19
|
if (!page) return void 0;
|
|
71
20
|
const base = master.basedOn ? byId.get(master.basedOn) : void 0;
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
21
|
+
const shadowsSpreadBase = Boolean(base && (page.appliedMaster && page.appliedMaster !== "n" || master.showMasterItems === false || page.showMasterItems === false));
|
|
22
|
+
const head = regionHtml(master, page, role, "head", byId, context, shadowsSpreadBase);
|
|
23
|
+
const foot = regionHtml(master, page, role, "foot", byId, context, shadowsSpreadBase);
|
|
75
24
|
return {
|
|
76
25
|
name: parentName(master, role),
|
|
77
26
|
...base ? { basedOn: parentName(base, role) } : {},
|
|
@@ -131,13 +80,14 @@ function appliedBookMargin(model, applied, report) {
|
|
|
131
80
|
}
|
|
132
81
|
return logicalMargin(first, model.preferences.binding === "right-to-left" ? "left" : "right");
|
|
133
82
|
}
|
|
134
|
-
function mapMasters(model, variables, styles, report) {
|
|
83
|
+
function mapMasters(model, variables, styles, report, renderStory) {
|
|
135
84
|
const rtl = model.preferences.binding === "right-to-left";
|
|
136
85
|
const applied = new Set(model.spreads.flatMap((spread) => spread.pages.map((page) => page.appliedMaster).filter((id) => Boolean(id && id !== "n"))));
|
|
137
86
|
const masters = emittedMasters(model, applied);
|
|
138
87
|
const byId = new Map(model.masterSpreads.map((master) => [master.id, master]));
|
|
139
88
|
for (const spread of model.spreads) for (const page of spread.pages) {
|
|
140
89
|
const id = page.appliedMaster;
|
|
90
|
+
if (page.showMasterItems === false) report.addOnce(report.unsupportedElements, "Page ShowMasterItems", page.id);
|
|
141
91
|
if (id && id !== "n" && !byId.has(id)) report.addOnce(report.unsupportedElements, `applied master unknown:${id}`, page.id);
|
|
142
92
|
}
|
|
143
93
|
const css = [":where(.idml-master-region){position:relative;block-size:100%}"];
|
|
@@ -150,7 +100,8 @@ function mapMasters(model, variables, styles, report) {
|
|
|
150
100
|
rtl,
|
|
151
101
|
stories: new Map(model.stories.map((story) => [story.id, story])),
|
|
152
102
|
mappedStories,
|
|
153
|
-
css
|
|
103
|
+
css,
|
|
104
|
+
renderStory
|
|
154
105
|
});
|
|
155
106
|
if (model.spreads.some((spread) => spread.pages.some((page) => !page.appliedMaster || page.appliedMaster === "n"))) parents.push({
|
|
156
107
|
name: "idml-none",
|
|
@@ -177,23 +128,35 @@ function mapMasters(model, variables, styles, report) {
|
|
|
177
128
|
marginFor
|
|
178
129
|
};
|
|
179
130
|
}
|
|
180
|
-
function
|
|
181
|
-
const
|
|
182
|
-
const pages = [];
|
|
131
|
+
function storyPageUses(model) {
|
|
132
|
+
const result = /* @__PURE__ */ new Map();
|
|
183
133
|
let ordinal = 0;
|
|
184
134
|
for (const spread of model.spreads) for (const page of spread.pages) {
|
|
185
|
-
|
|
135
|
+
for (const story of page.stories) result.set(story, [...result.get(story) ?? [], {
|
|
186
136
|
page,
|
|
187
137
|
ordinal
|
|
188
|
-
});
|
|
138
|
+
}]);
|
|
189
139
|
ordinal += 1;
|
|
190
140
|
}
|
|
191
|
-
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
function openingPage(story, model, pages) {
|
|
144
|
+
const rtl = model.preferences.binding === "right-to-left";
|
|
145
|
+
const first = pages.get(story)?.[0];
|
|
146
|
+
return first ? {
|
|
147
|
+
page: first.page,
|
|
148
|
+
role: roleFor(first.page, rtl, first.ordinal)
|
|
149
|
+
} : void 0;
|
|
150
|
+
}
|
|
151
|
+
function chapterParents(story, model, pages, mapping, report) {
|
|
152
|
+
const rtl = model.preferences.binding === "right-to-left";
|
|
153
|
+
const uses = pages.get(story);
|
|
154
|
+
const first = uses?.[0];
|
|
192
155
|
if (!first) return void 0;
|
|
193
156
|
const firstRole = roleFor(first.page, rtl, first.ordinal);
|
|
194
157
|
const result = { opener: mapping.parentFor(first.page.appliedMaster, firstRole, first.page.id) };
|
|
195
158
|
for (const role of ["recto", "verso"]) {
|
|
196
|
-
const candidates =
|
|
159
|
+
const candidates = uses.slice(1).filter((entry) => roleFor(entry.page, rtl, entry.ordinal) === role);
|
|
197
160
|
if (candidates.length === 0) continue;
|
|
198
161
|
const counts = /* @__PURE__ */ new Map();
|
|
199
162
|
for (const entry of candidates) {
|
|
@@ -212,4 +175,4 @@ function chapterParents(story, model, mapping, report) {
|
|
|
212
175
|
return result;
|
|
213
176
|
}
|
|
214
177
|
//#endregion
|
|
215
|
-
export { chapterParents, mapMasters };
|
|
178
|
+
export { chapterParents, mapMasters, openingPage, storyPageUses };
|
package/dist/model.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IdmlReportData } from "./report.js";
|
|
2
|
-
import { IdmlDocumentPreferences, IdmlFont, IdmlFramePreference, IdmlMargin, IdmlMasterPage, IdmlMasterSpread, IdmlPage, IdmlSpread, IdmlStory, IdmlStyle, IdmlSwatch, IdmlTextVariable } from "./types.js";
|
|
2
|
+
import { IdmlAnchoredFrame, IdmlDocumentPreferences, IdmlFont, IdmlFramePreference, IdmlMargin, IdmlMasterPage, IdmlMasterSpread, IdmlPage, IdmlSpread, IdmlStory, IdmlStyle, IdmlSwatch, IdmlTextVariable } from "./types.js";
|
|
3
3
|
//#region src/model.d.ts
|
|
4
4
|
export interface IdmlModel {
|
|
5
5
|
id: string;
|
|
@@ -7,6 +7,7 @@ export interface IdmlModel {
|
|
|
7
7
|
spreads: IdmlSpread[];
|
|
8
8
|
masterSpreads: IdmlMasterSpread[];
|
|
9
9
|
stories: IdmlStory[];
|
|
10
|
+
anchoredFrames?: IdmlAnchoredFrame[];
|
|
10
11
|
masterStoryIds: string[];
|
|
11
12
|
partsRead: string[];
|
|
12
13
|
styles?: IdmlStyle[];
|
package/dist/model.js
CHANGED
|
@@ -2,13 +2,14 @@ import { refusal } from "./refusal.js";
|
|
|
2
2
|
import { ReportCollector } from "./report.js";
|
|
3
3
|
import { attribute, elements, localName, numberAttribute } from "./xml.js";
|
|
4
4
|
import { parseFonts } from "./fonts.js";
|
|
5
|
-
import { readMargin } from "./margins.js";
|
|
6
|
-
import { parseStyles, parseSwatches } from "./styles.js";
|
|
7
5
|
import { FramePreferenceIndex } from "./frame-preferences.js";
|
|
8
|
-
import { IDML_LIMITS } from "./limits.js";
|
|
9
|
-
import { parseParts } from "./parse-parts.js";
|
|
10
6
|
import { GRAPHIC_ELEMENTS, SHAPE_ELEMENTS, collectUnsupportedElements } from "./unsupported-elements.js";
|
|
11
7
|
import { attachPlacements, contains, pageArea, placementBounds, spreadPlacements } from "./placements.js";
|
|
8
|
+
import { parseStyles, parseSwatches } from "./styles.js";
|
|
9
|
+
import { parseAnchoredFrames } from "./anchored-frames.js";
|
|
10
|
+
import { readMargin } from "./margins.js";
|
|
11
|
+
import { IDML_LIMITS } from "./limits.js";
|
|
12
|
+
import { parseParts } from "./parse-parts.js";
|
|
12
13
|
import { parseStories } from "./story-model.js";
|
|
13
14
|
import { isTag } from "domhandler";
|
|
14
15
|
//#region src/model.ts
|
|
@@ -22,9 +23,11 @@ function boolAttribute(element, name, fallback) {
|
|
|
22
23
|
function page(element) {
|
|
23
24
|
const id = attribute(element, "Self");
|
|
24
25
|
if (!id) throw refusal("an IDML Page has no Self identifier", "give every Page a Self identifier");
|
|
26
|
+
const showMasterItems = boolAttribute(element, "ShowMasterItems", true);
|
|
25
27
|
return {
|
|
26
28
|
id,
|
|
27
29
|
appliedMaster: attribute(element, "AppliedMaster"),
|
|
30
|
+
...showMasterItems ? {} : { showMasterItems },
|
|
28
31
|
stories: [],
|
|
29
32
|
side: "single"
|
|
30
33
|
};
|
|
@@ -96,6 +99,7 @@ function masterSpread(element, pageElements, pages, placements) {
|
|
|
96
99
|
id: attribute(element, "Self") ?? base,
|
|
97
100
|
name,
|
|
98
101
|
...attribute(element, "BasedOn") ? { basedOn: attribute(element, "BasedOn") } : {},
|
|
102
|
+
...boolAttribute(element, "ShowMasterItems", true) ? {} : { showMasterItems: false },
|
|
99
103
|
pages: pages.map((value, index) => ({
|
|
100
104
|
...value,
|
|
101
105
|
margin: readMargin(elements(pageElements[index], "MarginPreference")[0]),
|
|
@@ -183,6 +187,8 @@ function idmlModel(entries, limits = IDML_LIMITS) {
|
|
|
183
187
|
const spreadResult = spreads(parts, "Spread", spreadSources(designmap, "Spread"), report, framePreferences);
|
|
184
188
|
const masterResult = spreads(parts, "MasterSpread", spreadSources(designmap, "MasterSpread"), report, new FramePreferenceIndex());
|
|
185
189
|
const masterStoryIds = [...masterResult.frameStories].filter((story) => !spreadResult.frameStories.has(story));
|
|
190
|
+
const stories = parseStories(parts, framePreferences, report);
|
|
191
|
+
const styles = parts.flatMap((part) => part.name.endsWith("/Styles.xml") || part.name === "Resources/Styles.xml" ? parseStyles(part.all, part.name) : []);
|
|
186
192
|
return {
|
|
187
193
|
id: attribute(document, "Self") ?? "IDML document",
|
|
188
194
|
preferences: {
|
|
@@ -194,12 +200,13 @@ function idmlModel(entries, limits = IDML_LIMITS) {
|
|
|
194
200
|
},
|
|
195
201
|
spreads: spreadResult.values,
|
|
196
202
|
masterSpreads: masterResult.values,
|
|
197
|
-
stories
|
|
203
|
+
stories,
|
|
204
|
+
anchoredFrames: parseAnchoredFrames(stories, styles),
|
|
198
205
|
bodyFramePreferences: framePreferences.values(),
|
|
199
206
|
textVariables: textVariables(designmap),
|
|
200
207
|
masterStoryIds,
|
|
201
208
|
partsRead: parts.map((part) => part.name),
|
|
202
|
-
styles
|
|
209
|
+
styles,
|
|
203
210
|
fonts: parts.flatMap((part) => part.name.endsWith("/Fonts.xml") || part.name === "Resources/Fonts.xml" ? parseFonts(part.all) : []),
|
|
204
211
|
swatches: parts.flatMap((part) => part.name.endsWith("/Graphic.xml") || part.name === "Resources/Graphic.xml" ? parseSwatches(part.all) : []),
|
|
205
212
|
report: report.data()
|
package/dist/openers.js
CHANGED
|
@@ -2,18 +2,6 @@ import { formatRoundedNumber, positionedBoxStyle } from "./positioned-box.js";
|
|
|
2
2
|
import { artSvg } from "./svg-path.js";
|
|
3
3
|
import { findActiveContent, sanitize } from "@segnavia/html";
|
|
4
4
|
//#region src/openers.ts
|
|
5
|
-
function firstPages(model) {
|
|
6
|
-
const result = /* @__PURE__ */ new Map();
|
|
7
|
-
let ordinal = 0;
|
|
8
|
-
for (const spread of model.spreads) for (const page of spread.pages) {
|
|
9
|
-
for (const story of page.stories) if (!result.has(story)) result.set(story, {
|
|
10
|
-
page,
|
|
11
|
-
ordinal
|
|
12
|
-
});
|
|
13
|
-
ordinal += 1;
|
|
14
|
-
}
|
|
15
|
-
return result;
|
|
16
|
-
}
|
|
17
5
|
function storyFramePages(model) {
|
|
18
6
|
const result = /* @__PURE__ */ new Map();
|
|
19
7
|
for (const spread of model.spreads) for (const page of spread.pages) for (const frame of page.bodyFrames ?? []) {
|
|
@@ -109,8 +97,8 @@ function claimHeaders(page, flow, claimed, framePages, byId) {
|
|
|
109
97
|
}
|
|
110
98
|
return headers;
|
|
111
99
|
}
|
|
112
|
-
function mapOpeners(model, stories, masters, report, asset) {
|
|
113
|
-
const first =
|
|
100
|
+
function mapOpeners(model, stories, pages, masters, report, asset) {
|
|
101
|
+
const first = new Map([...pages].flatMap(([story, uses]) => uses[0] ? [[story, uses[0]]] : []));
|
|
114
102
|
const framePages = storyFramePages(model);
|
|
115
103
|
const byId = new Map(stories.map((entry) => [entry.story.id, entry]));
|
|
116
104
|
const claimed = /* @__PURE__ */ new Set();
|