@segnavia/idml 0.31.0 → 0.32.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.
@@ -0,0 +1,15 @@
1
+ //#region src/placement-report.ts
2
+ function reportPage(page, consumed, report) {
3
+ for (const link of page.links ?? []) {
4
+ report.link(link.uri, link.location);
5
+ if (!consumed) report.add(report.unsupportedElements, "link", link.location);
6
+ }
7
+ for (const item of page.unmapped ?? []) report.add(report.unsupportedElements, item.name, item.location);
8
+ if (!consumed) for (const art of page.art ?? []) report.add(report.unsupportedElements, "polygon art group", art.location);
9
+ }
10
+ function reportPlacements(model, consumedPages, report) {
11
+ for (const spread of model.spreads) for (const page of spread.pages) reportPage(page, consumedPages.has(page), report);
12
+ for (const master of model.masterSpreads) for (const page of master.pages) reportPage(page, false, report);
13
+ }
14
+ //#endregion
15
+ export { reportPlacements };
@@ -0,0 +1,300 @@
1
+ import { attribute, elements, localName } from "./xml.js";
2
+ import { framePreference } from "./frame-preferences.js";
3
+ import { GRAPHIC_ELEMENTS, SHAPE_ELEMENTS } from "./unsupported-elements.js";
4
+ import { isTag } from "domhandler";
5
+ //#region src/placements.ts
6
+ const IDENTITY = [
7
+ 1,
8
+ 0,
9
+ 0,
10
+ 1,
11
+ 0,
12
+ 0
13
+ ];
14
+ function transform(element) {
15
+ const values = (attribute(element, "ItemTransform") ?? "").trim().split(/\s+/).map(Number);
16
+ return values.length === 6 && values.every(Number.isFinite) ? values : IDENTITY;
17
+ }
18
+ function compose(parent, child) {
19
+ const [a, b, c, d, x, y] = parent;
20
+ const [e, f, g, h, u, v] = child;
21
+ return [
22
+ a * e + c * f,
23
+ b * e + d * f,
24
+ a * g + c * h,
25
+ b * g + d * h,
26
+ a * u + c * v + x,
27
+ b * u + d * v + y
28
+ ];
29
+ }
30
+ function apply(value, point) {
31
+ return [value[0] * point[0] + value[2] * point[1] + value[4], value[1] * point[0] + value[3] * point[1] + value[5]];
32
+ }
33
+ function pageArea(element) {
34
+ const value = (attribute(element, "GeometricBounds") ?? "").trim().split(/\s+/).map(Number);
35
+ if (value.length !== 4 || !value.every(Number.isFinite)) return void 0;
36
+ const [top, left, bottom, right] = value;
37
+ const matrix = transform(element);
38
+ return [
39
+ apply(matrix, [left, top]),
40
+ apply(matrix, [right, top]),
41
+ apply(matrix, [right, bottom]),
42
+ apply(matrix, [left, bottom])
43
+ ];
44
+ }
45
+ function placementBounds(points) {
46
+ if (points.length === 0) return void 0;
47
+ const xs = points.map(([x]) => x);
48
+ const ys = points.map(([, y]) => y);
49
+ return {
50
+ left: Math.min(...xs),
51
+ top: Math.min(...ys),
52
+ right: Math.max(...xs),
53
+ bottom: Math.max(...ys)
54
+ };
55
+ }
56
+ function frameBounds(element, matrix) {
57
+ return placementBounds(elements(element).map((candidate) => attribute(candidate, "Anchor")).filter((value) => value !== void 0).map((value) => value.trim().split(/\s+/).map(Number)).filter((value) => value.length === 2 && value.every(Number.isFinite)).map((point) => apply(matrix, point)));
58
+ }
59
+ function contains(area, [x, y]) {
60
+ let inside = false;
61
+ for (let index = 0, previous = area.length - 1; index < area.length; previous = index, index += 1) {
62
+ const [xi, yi] = area[index];
63
+ const [xj, yj] = area[previous];
64
+ if (yi === yj && y === yi && x >= Math.min(xi, xj) && x <= Math.max(xi, xj)) return true;
65
+ if (yi > y !== yj > y && x <= (xj - xi) * (y - yi) / (yj - yi) + xi) inside = !inside;
66
+ }
67
+ return inside;
68
+ }
69
+ function centered(box) {
70
+ return [(box.left + box.right) / 2, (box.top + box.bottom) / 2];
71
+ }
72
+ function framePlacement(element, matrix, location) {
73
+ const story = attribute(element, "ParentStory");
74
+ if (!story) return void 0;
75
+ const box = frameBounds(element, matrix);
76
+ const preference = framePreference(element, location);
77
+ return {
78
+ id: attribute(element, "Self") ?? story,
79
+ story,
80
+ ...box ? {
81
+ bounds: box,
82
+ center: centered(box)
83
+ } : {},
84
+ ...preference ? { preference } : {},
85
+ location
86
+ };
87
+ }
88
+ function pointAttribute(element, name, fallback) {
89
+ const values = (attribute(element, name) ?? "").trim().split(/\s+/).map(Number);
90
+ return values.length === 2 && values.every(Number.isFinite) ? values : fallback;
91
+ }
92
+ function polygonPlacement(element, matrix, location, group) {
93
+ const paths = elements(element, "GeometryPathType").flatMap((path) => {
94
+ const points = elements(path, "PathPointType").flatMap((candidate) => {
95
+ const anchor = pointAttribute(candidate, "Anchor");
96
+ return anchor ? [{
97
+ anchor: apply(matrix, anchor),
98
+ left: apply(matrix, pointAttribute(candidate, "LeftDirection", anchor)),
99
+ right: apply(matrix, pointAttribute(candidate, "RightDirection", anchor))
100
+ }] : [];
101
+ });
102
+ return points.length > 0 ? [{
103
+ open: attribute(path, "PathOpen") === "true",
104
+ points
105
+ }] : [];
106
+ });
107
+ const box = placementBounds(paths.flatMap((path) => path.points.flatMap((point) => [
108
+ point.anchor,
109
+ point.left,
110
+ point.right
111
+ ])));
112
+ if (!box) return void 0;
113
+ const weight = Number(attribute(element, "StrokeWeight"));
114
+ const color = attribute(element, "StrokeColor");
115
+ return {
116
+ polygon: {
117
+ paths,
118
+ fill: attribute(element, "FillColor") ?? "Swatch/None",
119
+ ...Number.isFinite(weight) && weight > 0 && color && !color.endsWith("/None") ? { stroke: {
120
+ color,
121
+ weight
122
+ } } : {},
123
+ location
124
+ },
125
+ bounds: box,
126
+ center: centered(box),
127
+ group
128
+ };
129
+ }
130
+ function linkedUri(element) {
131
+ const graphic = elements(element).find((candidate) => GRAPHIC_ELEMENTS.includes(localName(candidate.name)));
132
+ return graphic ? elements(graphic, "Link").map((link) => attribute(link, "LinkResourceURI")).find((uri) => Boolean(uri)) : void 0;
133
+ }
134
+ function collectShape(result, element, name, matrix, location, group) {
135
+ const box = frameBounds(element, matrix);
136
+ const uri = linkedUri(element);
137
+ if (uri) result.links.push({
138
+ ...box ? {
139
+ bounds: box,
140
+ center: centered(box)
141
+ } : {},
142
+ uri,
143
+ location
144
+ });
145
+ else if (name === "Polygon") {
146
+ const polygon = polygonPlacement(element, matrix, location, group ?? attribute(element, "Self") ?? location);
147
+ if (polygon) result.polygons.push(polygon);
148
+ else result.unmapped.push({
149
+ name,
150
+ location,
151
+ ...box ? { center: centered(box) } : {}
152
+ });
153
+ } else result.unmapped.push({
154
+ name,
155
+ location,
156
+ ...box ? { center: centered(box) } : {}
157
+ });
158
+ }
159
+ function collectTerminal(result, element, name, matrix, location, group) {
160
+ if (name === "TextFrame") {
161
+ const frame = framePlacement(element, matrix, location);
162
+ if (frame) result.frames.push(frame);
163
+ return true;
164
+ }
165
+ if (SHAPE_ELEMENTS.includes(name)) {
166
+ collectShape(result, element, name, matrix, location, group);
167
+ return true;
168
+ }
169
+ return false;
170
+ }
171
+ function spreadPlacements(spread, location) {
172
+ const result = {
173
+ frames: [],
174
+ polygons: [],
175
+ links: [],
176
+ unmapped: []
177
+ };
178
+ let sequence = 0;
179
+ const pending = [...spread.children].reverse().map((node) => ({
180
+ node,
181
+ matrix: IDENTITY,
182
+ group: void 0
183
+ }));
184
+ while (pending.length > 0) {
185
+ const current = pending.pop();
186
+ if (!current || !isTag(current.node)) continue;
187
+ const name = localName(current.node.name);
188
+ if (name === "Page" || name === "Spread" || name === "MasterSpread") continue;
189
+ const matrix = [
190
+ "Group",
191
+ "TextFrame",
192
+ ...SHAPE_ELEMENTS
193
+ ].includes(name) ? compose(current.matrix, transform(current.node)) : current.matrix;
194
+ const itemLocation = `${location}#${name}[${++sequence}]`;
195
+ const group = name === "Group" ? current.group ?? attribute(current.node, "Self") ?? itemLocation : current.group;
196
+ if (collectTerminal(result, current.node, name, matrix, itemLocation, group)) continue;
197
+ for (let index = current.node.children.length - 1; index >= 0; index -= 1) pending.push({
198
+ node: current.node.children[index],
199
+ matrix,
200
+ group
201
+ });
202
+ }
203
+ return result;
204
+ }
205
+ const relativeBounds = (value, area) => ({
206
+ left: value.left - area.left,
207
+ top: value.top - area.top,
208
+ right: value.right - area.left,
209
+ bottom: value.bottom - area.top
210
+ });
211
+ function relativePolygon(polygon, area) {
212
+ const relative = ([x, y]) => [x - area.left, y - area.top];
213
+ return {
214
+ ...polygon,
215
+ paths: polygon.paths.map((path) => ({
216
+ ...path,
217
+ points: path.points.map((point) => ({
218
+ anchor: relative(point.anchor),
219
+ left: relative(point.left),
220
+ right: relative(point.right)
221
+ }))
222
+ }))
223
+ };
224
+ }
225
+ function placeFrames(frames, pageIndex, areas, pages) {
226
+ for (const frame of frames) {
227
+ const index = pageIndex(frame.center);
228
+ const area = areas[index];
229
+ const page = pages[index];
230
+ if (!area || !page || !frame.bounds) continue;
231
+ page.bodyFrames = [...page.bodyFrames ?? [], {
232
+ story: frame.story,
233
+ bounds: relativeBounds(frame.bounds, area),
234
+ location: frame.location
235
+ }];
236
+ }
237
+ }
238
+ function placeArt(polygons, pageIndex, areas, pages) {
239
+ const groups = /* @__PURE__ */ new Map();
240
+ for (const item of polygons) {
241
+ const index = pageIndex(item.center);
242
+ const area = areas[index];
243
+ if (!area || !pages[index]) continue;
244
+ const group = groups.get(`${index}:${item.group}`) ?? {
245
+ index,
246
+ polygons: [],
247
+ boxes: [],
248
+ location: item.polygon.location
249
+ };
250
+ group.polygons.push(relativePolygon(item.polygon, area));
251
+ group.boxes.push(relativeBounds(item.bounds, area));
252
+ groups.set(`${index}:${item.group}`, group);
253
+ }
254
+ for (const group of groups.values()) {
255
+ const page = pages[group.index];
256
+ if (!page) continue;
257
+ const box = {
258
+ left: Math.min(...group.boxes.map((item) => item.left)),
259
+ top: Math.min(...group.boxes.map((item) => item.top)),
260
+ right: Math.max(...group.boxes.map((item) => item.right)),
261
+ bottom: Math.max(...group.boxes.map((item) => item.bottom))
262
+ };
263
+ page.art = [...page.art ?? [], {
264
+ bounds: box,
265
+ polygons: group.polygons,
266
+ location: group.location
267
+ }];
268
+ }
269
+ }
270
+ function attachPlacements(placements, pageElements, pages, report) {
271
+ const pageAreas = pageElements.map(pageArea);
272
+ const areas = pageAreas.map((points) => points ? placementBounds(points) : void 0);
273
+ const pageIndex = (center) => center ? pageAreas.findIndex((area) => area !== void 0 && contains(area, center)) : -1;
274
+ placeFrames(placements.frames, pageIndex, areas, pages);
275
+ placeArt(placements.polygons, pageIndex, areas, pages);
276
+ for (const link of placements.links) {
277
+ const index = pageIndex(link.center);
278
+ const area = areas[index];
279
+ const page = pages[index];
280
+ if (area && page && link.bounds) page.links = [...page.links ?? [], {
281
+ ...link,
282
+ bounds: relativeBounds(link.bounds, area)
283
+ }];
284
+ else {
285
+ report.link(link.uri, link.location);
286
+ report.add(report.unsupportedElements, "link", link.location);
287
+ }
288
+ }
289
+ for (const item of placements.unmapped) {
290
+ const page = pages[pageIndex(item.center)];
291
+ if (page) page.unmapped = [...page.unmapped ?? [], {
292
+ name: item.name,
293
+ location: item.location
294
+ }];
295
+ else report.add(report.unsupportedElements, item.name, item.location);
296
+ }
297
+ for (const item of placements.polygons) if (pageIndex(item.center) < 0) report.add(report.unsupportedElements, "polygon art group", item.polygon.location);
298
+ }
299
+ //#endregion
300
+ export { attachPlacements, contains, pageArea, placementBounds, spreadPlacements };
@@ -0,0 +1,14 @@
1
+ import { MM_PER_PT } from "@segnavia/format";
2
+ //#region src/positioned-box.ts
3
+ function formatRoundedNumber(value, digits = 2) {
4
+ const rounded = Number(value.toFixed(digits));
5
+ return String(Object.is(rounded, -0) ? 0 : rounded);
6
+ }
7
+ function positionedBoxStyle(bounds, options) {
8
+ const scale = options.unit === "mm" ? MM_PER_PT : 1;
9
+ const digits = options.unit === "mm" ? 6 : 2;
10
+ const value = (points) => `${formatRoundedNumber(points * scale, digits)}${options.unit}`;
11
+ return `position:absolute;inset-inline-start:${value(options.rtl ? options.pageWidth - options.margin.right - bounds.right : bounds.left - options.margin.left)};inline-size:${value(bounds.right - bounds.left)};inset-block-start:${value(bounds.top - options.blockOrigin)};block-size:${value(bounds.bottom - bounds.top)}`;
12
+ }
13
+ //#endregion
14
+ export { formatRoundedNumber, positionedBoxStyle };
package/dist/report.d.ts CHANGED
@@ -10,6 +10,10 @@ export interface IdmlMixedMasterChapter {
10
10
  masters: string[];
11
11
  chosen: string;
12
12
  }
13
+ export interface IdmlLinkUse {
14
+ uri: string;
15
+ locations: string[];
16
+ }
13
17
  export interface IdmlReportData {
14
18
  unsupportedElements: Record<string, number>;
15
19
  unsupportedStyleProperties: Record<string, number>;
@@ -19,6 +23,9 @@ export interface IdmlReportData {
19
23
  spotColors: Record<string, number>;
20
24
  locations: Record<string, string[]>;
21
25
  fonts: IdmlFontUse[];
26
+ unmappedFonts: IdmlFontUse[];
27
+ links: IdmlLinkUse[];
28
+ strippedAssetContent: Record<string, string[]>;
22
29
  mixedMasterChapters: IdmlMixedMasterChapter[];
23
30
  }
24
31
  //#endregion
package/dist/report.js CHANGED
@@ -7,9 +7,15 @@ const BUCKET_NAMES = {
7
7
  unsupportedLanguages: "language",
8
8
  spotColors: "spot"
9
9
  };
10
+ function fontUseKey(appliedFont, fontStyle) {
11
+ return `${appliedFont}\u0000${fontStyle ?? ""}`;
12
+ }
10
13
  var ReportCollector = class {
11
14
  locations = /* @__PURE__ */ new Map();
12
15
  fonts = /* @__PURE__ */ new Map();
16
+ unmappedFonts = /* @__PURE__ */ new Map();
17
+ links = /* @__PURE__ */ new Map();
18
+ strippedAssetContent = /* @__PURE__ */ new Map();
13
19
  mixedMasterChapters;
14
20
  buckets = Object.fromEntries(Object.keys(BUCKET_NAMES).map((name) => [name, /* @__PURE__ */ new Map()]));
15
21
  get unsupportedElements() {
@@ -34,7 +40,13 @@ var ReportCollector = class {
34
40
  this.mixedMasterChapters = [...seed?.mixedMasterChapters ?? []];
35
41
  for (const name of Object.keys(BUCKET_NAMES)) for (const [key, count] of Object.entries(seed?.[name] ?? {})) this.buckets[name].set(key, count);
36
42
  for (const [key, locations] of Object.entries(seed?.locations ?? {})) this.locations.set(key, [...locations]);
43
+ this.seedKeyedRecords(seed);
44
+ }
45
+ seedKeyedRecords(seed) {
37
46
  for (const font of seed?.fonts ?? []) this.font(font.appliedFont, font.fontStyle, font.locations[0] ?? "Resources/Styles.xml");
47
+ for (const font of seed?.unmappedFonts ?? []) for (const location of font.locations) this.unmappedFont(font.appliedFont, font.fontStyle, location);
48
+ for (const link of seed?.links ?? []) for (const location of link.locations) this.link(link.uri, location);
49
+ for (const [uri, findings] of Object.entries(seed?.strippedAssetContent ?? {})) for (const finding of findings) this.strippedAsset(uri, finding);
38
50
  }
39
51
  add(bucket, name, location, count = 1) {
40
52
  bucket.set(name, (bucket.get(name) ?? 0) + count);
@@ -50,14 +62,36 @@ var ReportCollector = class {
50
62
  }
51
63
  font(appliedFont, fontStyle, location) {
52
64
  if (!appliedFont) return;
53
- const key = `${appliedFont}\u0000${fontStyle ?? ""}`;
54
- const found = this.fonts.get(key) ?? {
65
+ this.record(this.fonts, appliedFont, fontStyle, location);
66
+ }
67
+ unmappedFont(appliedFont, fontStyle, location) {
68
+ this.record(this.unmappedFonts, appliedFont, fontStyle, location);
69
+ }
70
+ link(uri, location) {
71
+ const found = this.links.get(uri) ?? {
72
+ uri,
73
+ locations: []
74
+ };
75
+ this.recordLocation(found.locations, location);
76
+ this.links.set(uri, found);
77
+ }
78
+ strippedAsset(uri, finding) {
79
+ const found = this.strippedAssetContent.get(uri) ?? [];
80
+ this.recordLocation(found, finding);
81
+ this.strippedAssetContent.set(uri, found);
82
+ }
83
+ record(target, appliedFont, fontStyle, location) {
84
+ const key = fontUseKey(appliedFont, fontStyle);
85
+ const found = target.get(key) ?? {
55
86
  appliedFont,
56
87
  ...fontStyle ? { fontStyle } : {},
57
88
  locations: []
58
89
  };
59
- if (found.locations.length < 5 && !found.locations.includes(location)) found.locations.push(location);
60
- this.fonts.set(key, found);
90
+ this.recordLocation(found.locations, location);
91
+ target.set(key, found);
92
+ }
93
+ recordLocation(target, location) {
94
+ if (target.length < 5 && !target.includes(location)) target.push(location);
61
95
  }
62
96
  data() {
63
97
  const record = (values) => Object.fromEntries([...values].sort(([a], [b]) => a.localeCompare(b)));
@@ -65,6 +99,9 @@ var ReportCollector = class {
65
99
  ...Object.fromEntries(Object.keys(BUCKET_NAMES).map((name) => [name, record(this.buckets[name])])),
66
100
  locations: Object.fromEntries([...this.locations].sort(([a], [b]) => a.localeCompare(b))),
67
101
  fonts: [...this.fonts.values()],
102
+ unmappedFonts: [...this.unmappedFonts.values()],
103
+ links: [...this.links.values()],
104
+ strippedAssetContent: Object.fromEntries([...this.strippedAssetContent].sort(([a], [b]) => a.localeCompare(b))),
68
105
  mixedMasterChapters: [...this.mixedMasterChapters]
69
106
  };
70
107
  }
@@ -75,4 +112,4 @@ var ReportCollector = class {
75
112
  }
76
113
  };
77
114
  //#endregion
78
- export { ReportCollector };
115
+ export { ReportCollector, fontUseKey };
package/dist/stories.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { BOOK_FURNITURE, MOTIF_ATTRIBUTE, motifFieldAttribute, resolveDirection } from "@segnavia/format";
2
+ import { Element, Text } from "domhandler";
2
3
  import { BOOK_COLUMN_MAX, BOOK_COLUMN_MIN } from "@segnavia/html";
3
4
  import serialize from "dom-serializer";
4
- import { Element, Text } from "domhandler";
5
5
  //#region src/stories.ts
6
6
  const OMITTED = /* @__PURE__ */ new Set([
7
7
  "TextFrame",
package/dist/styles.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { idmlDirection, localName } from "./xml.js";
2
+ import { resolveSwatchColor } from "./swatch-color.js";
2
3
  import { stylePropertyMapping } from "./style-mappings.js";
3
4
  import { reportableStyleProperties } from "./style-property-report.js";
4
5
  import { isTag } from "domhandler";
@@ -84,19 +85,28 @@ function textDeclarations(values, mapped, output) {
84
85
  if (mapped.has("Capitalization") && values.Capitalization === "AllCaps") output.push("text-transform:uppercase");
85
86
  if (mapped.has("Underline") && (values.Underline === "true" || values.Underline === "Underline")) output.push("text-decoration-line:underline");
86
87
  }
88
+ function resolvedFontProperties(changed, resolved) {
89
+ if (changed.AppliedFont === void 0 && changed.FontStyle === void 0) return {};
90
+ return {
91
+ ...resolved.AppliedFont === void 0 ? {} : { AppliedFont: resolved.AppliedFont },
92
+ ...resolved.FontStyle === void 0 ? {} : { FontStyle: resolved.FontStyle }
93
+ };
94
+ }
87
95
  var StyleMapper = class {
88
96
  swatches;
89
97
  report;
90
98
  bookDirection;
99
+ fonts;
91
100
  byId;
92
101
  classes = /* @__PURE__ */ new Map();
93
102
  used = /* @__PURE__ */ new Set();
94
103
  overrides = /* @__PURE__ */ new Map();
95
104
  reportedStyleProperties = /* @__PURE__ */ new Set();
96
- constructor(styles, swatches, report, bookDirection) {
105
+ constructor(styles, swatches, report, bookDirection, fonts) {
97
106
  this.swatches = swatches;
98
107
  this.report = report;
99
108
  this.bookDirection = bookDirection;
109
+ this.fonts = fonts;
100
110
  this.byId = new Map(styles.map((style) => [style.id, style]));
101
111
  const taken = /* @__PURE__ */ new Set();
102
112
  for (const style of styles) {
@@ -140,6 +150,7 @@ var StyleMapper = class {
140
150
  for (const name of reportableStyleProperties(changed, resolved)) this.report.add(this.report.unsupportedOverrideProperties, name, location);
141
151
  if (changed.AppliedFont !== void 0 || changed.FontStyle !== void 0) this.report.font(resolved.AppliedFont, resolved.FontStyle, location);
142
152
  const cssProperties = Object.fromEntries(Object.entries(changed).filter(([name]) => stylePropertyMapping(name)?.kind === "css"));
153
+ Object.assign(cssProperties, resolvedFontProperties(changed, resolved));
143
154
  if (styleDirection && styleDirection !== directionValue) {
144
155
  for (const name of ["LeftIndent", "RightIndent"]) cssProperties[name] = base[name] ?? "0";
145
156
  for (const name of ["Justification"]) if (base[name] !== void 0 && cssProperties[name] === void 0) cssProperties[name] = base[name];
@@ -152,7 +163,8 @@ var StyleMapper = class {
152
163
  this.overrides.set(key, {
153
164
  name,
154
165
  properties: cssProperties,
155
- direction: directionValue
166
+ direction: directionValue,
167
+ location
156
168
  });
157
169
  return name;
158
170
  }
@@ -186,13 +198,14 @@ var StyleMapper = class {
186
198
  rules.push(`.${name}{${declarations}}`);
187
199
  }
188
200
  for (const value of this.overrides.values()) {
189
- const declarations = this.declarations(value.properties, value.direction, value.name, this.report.unsupportedOverrideProperties);
201
+ const declarations = this.declarations(value.properties, value.direction, value.location, this.report.unsupportedOverrideProperties);
190
202
  if (declarations) rules.push(`.${value.name}{${declarations}}`);
191
203
  }
192
204
  return rules.join("");
193
205
  }
194
206
  declarations(values, dir, location, unsupported = this.report.unsupportedStyleProperties) {
195
207
  const output = [];
208
+ output.push(...this.fonts?.declarations(values.AppliedFont, values.FontStyle, location) ?? []);
196
209
  const mapped = new Set(Object.keys(values).filter((name) => stylePropertyMapping(name)?.kind === "css"));
197
210
  const pt = (name, property) => {
198
211
  if (!mapped.has(name)) return;
@@ -215,7 +228,7 @@ var StyleMapper = class {
215
228
  if (mapped.has("Justification")) this.justification(values.Justification, dir, output, location, unsupported);
216
229
  if (hasCssMapping(mapped, "keeps")) this.keeps(values, output);
217
230
  if (mapped.has("StartParagraph") && values.StartParagraph && values.StartParagraph !== "Anywhere") this.report.add(unsupported, `StartParagraph:${values.StartParagraph}`, location);
218
- const color = mapped.has("FillColor") ? this.color(values.FillColor, location) : void 0;
231
+ const color = mapped.has("FillColor") ? resolveSwatchColor(values.FillColor, this.swatches, this.report, location, "text") : void 0;
219
232
  if (color) output.push(`color:${color}`);
220
233
  return output.join(";");
221
234
  }
@@ -264,22 +277,6 @@ var StyleMapper = class {
264
277
  if (Number.isInteger(last) && last > 0) output.push(`widows:${last}`);
265
278
  }
266
279
  }
267
- color(id, location) {
268
- if (!id || id.endsWith("/None")) return void 0;
269
- if (id.endsWith("/Black")) return "#000";
270
- if (id.endsWith("/Paper")) return "#fff";
271
- const swatch = this.swatches.find((value) => value.id === id || value.name === id);
272
- if (!swatch) return void 0;
273
- if (swatch.model === "spot") {
274
- this.report.add(this.report.spotColors, swatch.name, location);
275
- return;
276
- }
277
- if (swatch.model === "rgb" && swatch.values?.length === 3) return `rgb(${swatch.values.join(" ")})`;
278
- if (swatch.model === "cmyk" && swatch.values?.length === 4) {
279
- const [c, m, y, k] = swatch.values.map((value) => value / 100);
280
- return `rgb(${Math.round(255 * (1 - c) * (1 - k))} ${Math.round(255 * (1 - m) * (1 - k))} ${Math.round(255 * (1 - y) * (1 - k))})`;
281
- }
282
- }
283
280
  };
284
281
  //#endregion
285
282
  export { StyleMapper, parseStyles, parseSwatches };
@@ -0,0 +1,33 @@
1
+ import { formatRoundedNumber } from "./positioned-box.js";
2
+ import { resolveSwatchColor } from "./swatch-color.js";
3
+ //#region src/svg-path.ts
4
+ const escapeAttribute = (value) => value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
5
+ function point(value, origin) {
6
+ return `${formatRoundedNumber(value[0] - origin[0])} ${formatRoundedNumber(value[1] - origin[1])}`;
7
+ }
8
+ function pathData(path, origin) {
9
+ const first = path.points[0];
10
+ if (!first) return "";
11
+ const output = [`M${point(first.anchor, origin)}`];
12
+ for (let index = 1; index < path.points.length; index += 1) {
13
+ const previous = path.points[index - 1];
14
+ const current = path.points[index];
15
+ if (previous && current) output.push(`C${point(previous.right, origin)} ${point(current.left, origin)} ${point(current.anchor, origin)}`);
16
+ }
17
+ const last = path.points.at(-1);
18
+ if (!path.open && last && path.points.length > 1) output.push(`C${point(last.right, origin)} ${point(first.left, origin)} ${point(first.anchor, origin)}Z`);
19
+ return output.join("");
20
+ }
21
+ function artSvg(art, swatches, positionStyle, report) {
22
+ const width = art.bounds.right - art.bounds.left;
23
+ const height = art.bounds.bottom - art.bounds.top;
24
+ const origin = [art.bounds.left, art.bounds.top];
25
+ const paths = art.polygons.flatMap((polygon) => {
26
+ if (polygon.stroke) report.add(report.unsupportedStyleProperties, `StrokeColor:${polygon.stroke.color}`, polygon.location);
27
+ const fill = resolveSwatchColor(polygon.fill, swatches, report, polygon.location, "fill") ?? "currentColor";
28
+ return polygon.paths.map((path) => `<path d="${escapeAttribute(pathData(path, origin))}" fill="${escapeAttribute(fill)}"/>`);
29
+ });
30
+ return `<svg style="${escapeAttribute(positionStyle)}" viewBox="0 0 ${formatRoundedNumber(width)} ${formatRoundedNumber(height)}" width="${formatRoundedNumber(width)}pt" height="${formatRoundedNumber(height)}pt" xmlns="http://www.w3.org/2000/svg">${paths.join("")}</svg>`;
31
+ }
32
+ //#endregion
33
+ export { artSvg };
@@ -0,0 +1,25 @@
1
+ //#region src/swatch-color.ts
2
+ const colorComponent = (value) => Math.round(value + 1e-9);
3
+ function unresolved(id, location, use, report) {
4
+ report.add(report.unsupportedStyleProperties, `FillColor:${id}`, location);
5
+ return use === "fill" ? "currentColor" : void 0;
6
+ }
7
+ function resolveSwatchColor(id, swatches, report, location, use) {
8
+ if (!id || id.endsWith("/None") || id === "None") return use === "fill" ? "none" : void 0;
9
+ if (id.endsWith("/Black") || id === "Black") return "#000";
10
+ if (id.endsWith("/Paper") || id === "Paper") return "#fff";
11
+ const swatch = swatches.find((value) => value.id === id || value.name === id);
12
+ if (!swatch) return unresolved(id, location, use, report);
13
+ if (swatch.model === "spot") {
14
+ report.add(report.spotColors, swatch.name, location);
15
+ return use === "fill" ? "currentColor" : void 0;
16
+ }
17
+ if (swatch.model === "rgb" && swatch.values?.length === 3) return `rgb(${swatch.values.map(colorComponent).join(" ")})`;
18
+ if (swatch.model === "cmyk" && swatch.values?.length === 4) {
19
+ const [c, m, y, k] = swatch.values.map((value) => value / 100);
20
+ return `rgb(${colorComponent(255 * (1 - c) * (1 - k))} ${colorComponent(255 * (1 - m) * (1 - k))} ${colorComponent(255 * (1 - y) * (1 - k))})`;
21
+ }
22
+ return unresolved(id, location, use, report);
23
+ }
24
+ //#endregion
25
+ export { resolveSwatchColor };
package/dist/types.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { FontReferenceInput } from "@segnavia/format";
1
2
  //#region src/types.d.ts
2
3
  export interface IdmlTextNode {
3
4
  name: string;
@@ -14,6 +15,60 @@ export interface IdmlStyle {
14
15
  properties: Record<string, string>;
15
16
  location: string;
16
17
  }
18
+ export interface IdmlFont {
19
+ family: string;
20
+ style?: string;
21
+ postscriptName?: string;
22
+ }
23
+ export type IdmlFontMap = (font: IdmlFont) => FontReferenceInput | undefined;
24
+ export type IdmlAssetMap = (uri: string) => string | undefined;
25
+ export interface IdmlImportOptions {
26
+ font?: IdmlFontMap;
27
+ asset?: IdmlAssetMap;
28
+ }
29
+ export type IdmlPoint = [number, number];
30
+ export interface IdmlBounds {
31
+ left: number;
32
+ top: number;
33
+ right: number;
34
+ bottom: number;
35
+ }
36
+ export interface IdmlPathPoint {
37
+ anchor: IdmlPoint;
38
+ left: IdmlPoint;
39
+ right: IdmlPoint;
40
+ }
41
+ export interface IdmlPolygon {
42
+ paths: {
43
+ open: boolean;
44
+ points: IdmlPathPoint[];
45
+ }[];
46
+ fill: string;
47
+ stroke?: {
48
+ color: string;
49
+ weight: number;
50
+ };
51
+ location: string;
52
+ }
53
+ export interface IdmlArtPlacement {
54
+ bounds: IdmlBounds;
55
+ polygons: IdmlPolygon[];
56
+ location: string;
57
+ }
58
+ export interface IdmlFramePlacement {
59
+ bounds: IdmlBounds;
60
+ story: string;
61
+ location: string;
62
+ }
63
+ export interface IdmlLinkPlacement {
64
+ bounds: IdmlBounds;
65
+ uri: string;
66
+ location: string;
67
+ }
68
+ export interface IdmlUnmappedPlacement {
69
+ name: string;
70
+ location: string;
71
+ }
17
72
  export interface IdmlSwatch {
18
73
  id: string;
19
74
  name: string;
@@ -58,6 +113,10 @@ export interface IdmlPage {
58
113
  appliedMaster?: string;
59
114
  stories: string[];
60
115
  side?: 'left' | 'right' | 'single';
116
+ bodyFrames?: IdmlFramePlacement[];
117
+ art?: IdmlArtPlacement[];
118
+ links?: IdmlLinkPlacement[];
119
+ unmapped?: IdmlUnmappedPlacement[];
61
120
  }
62
121
  export interface IdmlSpread {
63
122
  id: string;