layoutbridge-spm 0.0.1 → 1.0.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/LICENSE +26 -0
  3. package/README.de.md +318 -0
  4. package/README.md +304 -4
  5. package/THIRD-PARTY-NOTICES.md +2878 -0
  6. package/dist/src/mcp/config.js +125 -0
  7. package/dist/src/mcp/dispatch.js +123 -0
  8. package/dist/src/mcp/license/free-tools.js +16 -0
  9. package/dist/src/mcp/license/gate.js +209 -0
  10. package/dist/src/mcp/license/index.js +96 -0
  11. package/dist/src/mcp/license/instance-name.js +56 -0
  12. package/dist/src/mcp/license/key-source.js +58 -0
  13. package/dist/src/mcp/license/lemonsqueezy-verifier.js +207 -0
  14. package/dist/src/mcp/license/placeholder-verifier.js +10 -0
  15. package/dist/src/mcp/license/polar-ids.js +63 -0
  16. package/dist/src/mcp/license/polar-verifier.js +265 -0
  17. package/dist/src/mcp/license/product-ids.js +72 -0
  18. package/dist/src/mcp/license/state-store.js +91 -0
  19. package/dist/src/mcp/license/types.js +11 -0
  20. package/dist/src/mcp/logging.js +270 -0
  21. package/dist/src/mcp/product-info.js +48 -0
  22. package/dist/src/mcp/server.js +250 -0
  23. package/dist/src/mcp/setup.js +608 -0
  24. package/dist/src/mcp/start.cjs +95 -0
  25. package/dist/src/mcp/tool-definition.js +2 -0
  26. package/dist/src/mcp/tools.js +2004 -0
  27. package/dist/src/mcp/typography-rules.js +70 -0
  28. package/dist/src/transport/com.js +250 -0
  29. package/dist/src/transport/osascript.js +171 -0
  30. package/dist/src/transport/queue.js +19 -0
  31. package/dist/src/transport/script-bundle.js +92 -0
  32. package/dist/src/transport/transport.js +30 -0
  33. package/indesign/commands/10-app.js +6 -0
  34. package/indesign/commands/20-documents.js +513 -0
  35. package/indesign/commands/22-document-setup.js +118 -0
  36. package/indesign/commands/23-save-package.js +255 -0
  37. package/indesign/commands/25-export.js +379 -0
  38. package/indesign/commands/26-export-image.js +197 -0
  39. package/indesign/commands/27-textdefaults.js +151 -0
  40. package/indesign/commands/28-selection.js +174 -0
  41. package/indesign/commands/30-pages.js +1032 -0
  42. package/indesign/commands/31-view.js +210 -0
  43. package/indesign/commands/32-sections.js +203 -0
  44. package/indesign/commands/35-parents.js +428 -0
  45. package/indesign/commands/37-layout.js +760 -0
  46. package/indesign/commands/40-frames.js +836 -0
  47. package/indesign/commands/41-shapes.js +505 -0
  48. package/indesign/commands/42-groups.js +117 -0
  49. package/indesign/commands/43-layers.js +291 -0
  50. package/indesign/commands/44-effects.js +274 -0
  51. package/indesign/commands/45-frame-options.js +247 -0
  52. package/indesign/commands/47-anchored.js +287 -0
  53. package/indesign/commands/50-modify.js +781 -0
  54. package/indesign/commands/52-align.js +139 -0
  55. package/indesign/commands/55-fit.js +106 -0
  56. package/indesign/commands/60-images.js +771 -0
  57. package/indesign/commands/62-embed.js +120 -0
  58. package/indesign/commands/69-placeholders.js +127 -0
  59. package/indesign/commands/70-text.js +567 -0
  60. package/indesign/commands/71-outlines.js +92 -0
  61. package/indesign/commands/72-place-text.js +366 -0
  62. package/indesign/commands/74-stories.js +116 -0
  63. package/indesign/commands/75-tables.js +676 -0
  64. package/indesign/commands/76-text-variables.js +260 -0
  65. package/indesign/commands/77-story-options.js +86 -0
  66. package/indesign/commands/78-frame-lines.js +304 -0
  67. package/indesign/commands/79-check-typography.js +303 -0
  68. package/indesign/commands/80-styles.js +1620 -0
  69. package/indesign/commands/81-composition.js +355 -0
  70. package/indesign/commands/82-object-styles.js +263 -0
  71. package/indesign/commands/83-indents-tabs.js +237 -0
  72. package/indesign/commands/84-import-styles.js +142 -0
  73. package/indesign/commands/85-swatches.js +112 -0
  74. package/indesign/commands/86-gradients.js +185 -0
  75. package/indesign/commands/87-replace-swatch.js +180 -0
  76. package/indesign/commands/88-replace-font.js +217 -0
  77. package/indesign/interpreter.js +758 -0
  78. package/package.json +55 -6
  79. package/rules/typography-rules.json +31 -0
@@ -0,0 +1,758 @@
1
+ /*
2
+ * layoutbridge-spm – Befehlsinterpreter (bis 04.09.2026: indesign-connector-spm)
3
+ *
4
+ * Laeuft in InDesign. Nimmt einen Befehl {cmd, args} als JavaScript-Objekt,
5
+ * fuehrt ihn aus und liefert ein Ergebnisobjekt zurueck.
6
+ *
7
+ * Sprachumfang: gemeinsamer Nenner von ExtendScript (ES3) und UXP (ES6).
8
+ * Verboten und deshalb hier nicht benutzt: $-Objekt, #include, E4X, ScriptUI,
9
+ * UnitValue, globales document, JSON, Array.forEach/map/indexOf,
10
+ * Object.keys, String.trim, eckige Klammern auf DOM-Sammlungen,
11
+ * verschachtelte ?:-Ausdruecke ohne Klammern (ExtendScript klammert links).
12
+ *
13
+ * Diese Datei definiert nur den Kern. Die Handler stehen in commands/*.js und
14
+ * tragen sich ueber IDC.register() ein. Der Transport haengt Kern und Handler
15
+ * zu einem Skript zusammen; #include gibt es nicht.
16
+ */
17
+
18
+ var IDC = (function () {
19
+ var api = {};
20
+
21
+ api.version = '0.1.0';
22
+ api.commands = {};
23
+ /* Der gerade laufende Befehlseintrag (name, read_only ...), sonst null. */
24
+ api.currentCommand = null;
25
+
26
+ /* ---------------------------------------------------------------- Fehler */
27
+
28
+ var KINDS = {
29
+ app_not_available: 1,
30
+ invalid_argument: 1,
31
+ not_found: 1,
32
+ invalid_state: 1,
33
+ io_error: 1,
34
+ timeout: 1,
35
+ script_error: 1
36
+ };
37
+
38
+ function IdcError(kind, message, details) {
39
+ this.isIdcError = true;
40
+ this.kind = KINDS[kind] ? kind : 'script_error';
41
+ this.message = String(message);
42
+ this.details = details === undefined ? null : details;
43
+ }
44
+
45
+ api.fail = function (kind, message, details) {
46
+ throw new IdcError(kind, message, details);
47
+ };
48
+
49
+ /* ------------------------------------------------------------- Werkzeuge */
50
+
51
+ function hasOwn(obj, key) {
52
+ return Object.prototype.hasOwnProperty.call(obj, key);
53
+ }
54
+
55
+ function isArray(v) {
56
+ return Object.prototype.toString.call(v) === '[object Array]';
57
+ }
58
+
59
+ api.hasOwn = hasOwn;
60
+ api.isArray = isArray;
61
+
62
+ /* Enumeratoren vergleichen: ExtendScript liefert Zahlen, UXP teils Text. */
63
+ function enumIs(value, expected) {
64
+ if (value === expected) { return true; }
65
+ var a = Number(value);
66
+ var b = Number(expected);
67
+ if (!isNaN(a) && !isNaN(b)) { return a === b; }
68
+ return String(value) === String(expected);
69
+ }
70
+ api.enumIs = enumIs;
71
+
72
+ /* Rundet Fliesskommarauschen weg (InDesign rechnet intern in Punkt). */
73
+ function round(value, digits) {
74
+ var d = digits === undefined ? 4 : digits;
75
+ var f = Math.pow(10, d);
76
+ var n = Number(value);
77
+ if (isNaN(n)) { return null; }
78
+ return Math.round(n * f) / f;
79
+ }
80
+ api.round = round;
81
+
82
+ /* --------------------------------------------------------- JSON-Ausgabe */
83
+
84
+ function pad4(hex) {
85
+ while (hex.length < 4) { hex = '0' + hex; }
86
+ return hex;
87
+ }
88
+
89
+ function quote(text) {
90
+ var out = '"';
91
+ var i, ch, code;
92
+ for (i = 0; i < text.length; i++) {
93
+ ch = text.charAt(i);
94
+ code = text.charCodeAt(i);
95
+ if (ch === '"') { out += '\\"'; }
96
+ else if (ch === '\\') { out += '\\\\'; }
97
+ else if (code === 8) { out += '\\b'; }
98
+ else if (code === 9) { out += '\\t'; }
99
+ else if (code === 10) { out += '\\n'; }
100
+ else if (code === 12) { out += '\\f'; }
101
+ else if (code === 13) { out += '\\r'; }
102
+ else if (code < 32 || code > 126) { out += '\\u' + pad4(code.toString(16)); }
103
+ else { out += ch; }
104
+ }
105
+ return out + '"';
106
+ }
107
+
108
+ /*
109
+ * Eigener JSON-Schreiber: ES3 kennt kein JSON-Objekt. Alles ausserhalb von
110
+ * ASCII wird als \uXXXX geschrieben, damit die Ergebnisdatei reines ASCII
111
+ * ist und keine Zeichensatzfrage offen bleibt.
112
+ */
113
+ function stringify(value) {
114
+ var t = typeof value;
115
+ var i, parts, keys, k;
116
+ if (value === null || value === undefined) { return 'null'; }
117
+ if (t === 'boolean') { return value ? 'true' : 'false'; }
118
+ if (t === 'number') { return isFinite(value) ? String(value) : 'null'; }
119
+ if (t === 'string') { return quote(value); }
120
+ if (isArray(value)) {
121
+ parts = [];
122
+ for (i = 0; i < value.length; i++) { parts.push(stringify(value[i])); }
123
+ return '[' + parts.join(',') + ']';
124
+ }
125
+ if (t === 'object') {
126
+ parts = [];
127
+ for (k in value) {
128
+ if (hasOwn(value, k) && typeof value[k] !== 'function') {
129
+ parts.push(quote(String(k)) + ':' + stringify(value[k]));
130
+ }
131
+ }
132
+ return '{' + parts.join(',') + '}';
133
+ }
134
+ return 'null';
135
+ }
136
+ api.stringify = stringify;
137
+
138
+ /* ------------------------------------------------------------ Handhabung */
139
+
140
+ /*
141
+ * options:
142
+ * needs_document true = ein Dokument muss aufloesbar sein
143
+ * read_only true = aendert nichts
144
+ * ruler 'page' (Vorgabe) | 'spread' | 'none'
145
+ */
146
+ api.register = function (name, options, handler) {
147
+ var opt = options || {};
148
+ api.commands[name] = {
149
+ name: name,
150
+ needs_document: opt.needs_document === true,
151
+ read_only: opt.read_only === true,
152
+ ruler: opt.ruler || 'page',
153
+ handler: handler
154
+ };
155
+ };
156
+
157
+ /* ------------------------------------------------- Anwendung und Dokument */
158
+
159
+ function appIsReady() {
160
+ try {
161
+ if (typeof app === 'undefined' || app === null) { return false; }
162
+ var probe = app.name;
163
+ return probe !== undefined;
164
+ } catch (e) {
165
+ return false;
166
+ }
167
+ }
168
+
169
+ api.documentById = function (id) {
170
+ var i, d;
171
+ for (i = 0; i < app.documents.length; i++) {
172
+ d = app.documents.item(i);
173
+ if (Number(d.id) === Number(id)) { return d; }
174
+ }
175
+ return null;
176
+ };
177
+
178
+ api.resolveDocument = function (args) {
179
+ var wanted = args && args.document_id !== undefined && args.document_id !== null
180
+ ? args.document_id : null;
181
+ var doc;
182
+ if (wanted !== null) {
183
+ doc = api.documentById(wanted);
184
+ if (doc === null) {
185
+ api.fail('not_found', 'No open document with document_id ' + wanted + '.',
186
+ { document_id: wanted });
187
+ }
188
+ return doc;
189
+ }
190
+ if (app.documents.length === 0) {
191
+ api.fail('not_found', 'No document is open. Open one first or pass document_id.', null);
192
+ }
193
+ try {
194
+ return app.activeDocument;
195
+ } catch (e) {
196
+ return app.documents.item(0);
197
+ }
198
+ };
199
+
200
+ /* Seite (auch Musterseite) ueber ihre ID finden. */
201
+ api.pageById = function (doc, id) {
202
+ var i, j, spread, page;
203
+ for (i = 0; i < doc.pages.length; i++) {
204
+ page = doc.pages.item(i);
205
+ if (Number(page.id) === Number(id)) {
206
+ return { page: page, is_parent_page: false };
207
+ }
208
+ }
209
+ for (i = 0; i < doc.masterSpreads.length; i++) {
210
+ spread = doc.masterSpreads.item(i);
211
+ for (j = 0; j < spread.pages.length; j++) {
212
+ page = spread.pages.item(j);
213
+ if (Number(page.id) === Number(id)) {
214
+ return { page: page, is_parent_page: true };
215
+ }
216
+ }
217
+ }
218
+ return null;
219
+ };
220
+
221
+ /* --------------------------------------------------- Masseinheiten-Klammer */
222
+
223
+ /*
224
+ * Fuehrt fn aus, waehrend die Skript-Masseinheit auf unit steht, und stellt
225
+ * sie danach zurueck. Fuer Punktwerte (Konturstaerke, Schriftgrad) genau um
226
+ * diese eine Zuweisung legen.
227
+ */
228
+ api.withUnit = function (unit, fn) {
229
+ var prev = app.scriptPreferences.measurementUnit;
230
+ app.scriptPreferences.measurementUnit = unit;
231
+ try {
232
+ return fn();
233
+ } finally {
234
+ app.scriptPreferences.measurementUnit = prev;
235
+ }
236
+ };
237
+
238
+ api.inPoints = function (fn) {
239
+ return api.withUnit(MeasurementUnits.POINTS, fn);
240
+ };
241
+
242
+ /*
243
+ * Konkretes Objekt zu einem Sammlungsverweis.
244
+ *
245
+ * ExtendScript liefert aus pageItems.item(i) nur einen allgemeinen
246
+ * "PageItem"-Verweis; erst getElements()[0] ist der TextFrame, das Rectangle
247
+ * usw. Ohne diesen Schritt kennt niemand die Art des Objekts. Am lebenden
248
+ * Dokument am 04.09.2026 beobachtet. Objekte ohne getElements() kommen
249
+ * unveraendert zurueck.
250
+ */
251
+ api.concrete = function (item) {
252
+ var elements;
253
+ try {
254
+ if (item && item.getElements) {
255
+ elements = item.getElements();
256
+ if (elements && elements.length > 0) { return elements[0]; }
257
+ }
258
+ } catch (e) { /* dann eben der Verweis selbst */ }
259
+ return item;
260
+ };
261
+
262
+ /* ------------------------------------------------ Klassen und Objektarten */
263
+
264
+ /*
265
+ * Klassenname eines DOM-Objekts.
266
+ *
267
+ * ExtendScript liefert ueber constructor.name nichts Brauchbares; dort steht
268
+ * die Art in "[object TextFrame]". UXP kann es umgekehrt halten. Deshalb
269
+ * alle Wege, in dieser Reihenfolge; reflect.name ist der rein
270
+ * ExtendScript-seitige Rueckfall.
271
+ */
272
+ api.className = function (item) {
273
+ var name = '';
274
+ var text, match;
275
+
276
+ try {
277
+ if (item.constructor && item.constructor.name) { name = String(item.constructor.name); }
278
+ } catch (e) { name = ''; }
279
+ if (name !== '' && name !== 'Object') { return name; }
280
+
281
+ try {
282
+ text = Object.prototype.toString.call(item);
283
+ match = text.match(/^\[object (.+)\]$/);
284
+ if (match) { return String(match[1]); }
285
+ } catch (e2) { /* naechster Versuch */ }
286
+
287
+ try {
288
+ text = String(item);
289
+ match = text.match(/^\[object (.+)\]$/);
290
+ if (match) { return String(match[1]); }
291
+ } catch (e3) { /* naechster Versuch */ }
292
+
293
+ try {
294
+ if (item.reflect && item.reflect.name) { return String(item.reflect.name); }
295
+ } catch (e4) { /* aufgeben */ }
296
+
297
+ return name;
298
+ };
299
+
300
+ /*
301
+ * Objektart nach Spezifikation: text|image|rectangle|line|group|other;
302
+ * seit A-8.9 (Sitzung 9) auch ellipse|polygon fuer leere Ovale und
303
+ * Polygone (mit Grafik bleiben sie image). Ein Rechteck mit Eckenoptionen
304
+ * bleibt rectangle.
305
+ */
306
+ api.itemType = function (item) {
307
+ var className = api.className(item);
308
+ var hasGraphic = false;
309
+
310
+ if (className === 'TextFrame') { return 'text'; }
311
+ if (className === 'Group') { return 'group'; }
312
+ if (className === 'GraphicLine') { return 'line'; }
313
+
314
+ if (className === 'Rectangle' || className === 'Oval' || className === 'Polygon') {
315
+ try { hasGraphic = item.allGraphics.length > 0; } catch (e) { hasGraphic = false; }
316
+ if (hasGraphic) { return 'image'; }
317
+ if (className === 'Rectangle') { return 'rectangle'; }
318
+ if (className === 'Oval') { return 'ellipse'; }
319
+ return 'polygon';
320
+ }
321
+ return 'other';
322
+ };
323
+
324
+ api.isTextFrame = function (item) {
325
+ return api.className(item) === 'TextFrame';
326
+ };
327
+
328
+ /*
329
+ * Druckbogen (Spread oder MasterSpread), auf dem ein Objekt liegt. Der
330
+ * parent eines Objekts ist der Bogen, bei Gruppenmitgliedern die Gruppe,
331
+ * bei verankerten Objekten die Textstelle – deshalb nach oben laufen.
332
+ */
333
+ api.spreadOfItem = function (item) {
334
+ var current = item;
335
+ var step, name;
336
+ for (step = 0; step < 12; step++) {
337
+ try {
338
+ name = api.className(current);
339
+ if (name === 'Spread' || name === 'MasterSpread') { return current; }
340
+ current = current.parent;
341
+ if (current === null || current === undefined) { return null; }
342
+ } catch (e) { return null; }
343
+ }
344
+ return null;
345
+ };
346
+
347
+ api.isOnParentSpread = function (item) {
348
+ var spread = api.spreadOfItem(item);
349
+ return spread !== null && api.className(spread) === 'MasterSpread';
350
+ };
351
+
352
+ function findInCollection(collection, wanted) {
353
+ var item;
354
+ try {
355
+ item = collection.itemByID(wanted);
356
+ if (item && item.isValid === true) { return api.concrete(item); }
357
+ } catch (e) { /* nicht hier */ }
358
+ return null;
359
+ }
360
+
361
+ /* allPageItems ist ein Array (kein DOM-Sammlungsobjekt), auch in Gruppen. */
362
+ function findInArray(array, wanted) {
363
+ var i;
364
+ try {
365
+ for (i = 0; i < array.length; i++) {
366
+ if (Number(array[i].id) === wanted) { return api.concrete(array[i]); }
367
+ }
368
+ } catch (e) { /* nicht hier */ }
369
+ return null;
370
+ }
371
+
372
+ /*
373
+ * Objekt (Rahmen, Linie, Gruppe) ueber seine ID finden.
374
+ * where: 'any' (Vorgabe) | 'document' (nur Dokumentseiten) | 'parent'
375
+ * (nur Musterseiten). Liefert { item, on_parent } oder null.
376
+ */
377
+ api.itemById = function (doc, id, where) {
378
+ var wanted = Number(id);
379
+ var scope = where || 'any';
380
+ var found = null;
381
+ var i, spread;
382
+
383
+ if (scope !== 'parent') {
384
+ found = findInCollection(doc.pageItems, wanted);
385
+ if (found === null) {
386
+ try { found = findInArray(doc.allPageItems, wanted); } catch (e) { found = null; }
387
+ }
388
+ if (found !== null) {
389
+ if (scope === 'document' && api.isOnParentSpread(found)) { return null; }
390
+ return { item: found, on_parent: api.isOnParentSpread(found) };
391
+ }
392
+ }
393
+ if (scope !== 'document') {
394
+ for (i = 0; i < doc.masterSpreads.length; i++) {
395
+ spread = doc.masterSpreads.item(i);
396
+ found = findInCollection(spread.pageItems, wanted);
397
+ if (found === null) {
398
+ try { found = findInArray(spread.allPageItems, wanted); } catch (e2) { found = null; }
399
+ }
400
+ if (found !== null) { return { item: found, on_parent: true }; }
401
+ }
402
+ }
403
+ return null;
404
+ };
405
+
406
+ /* Absatzformat ueber den Namen, auch in Formatgruppen. */
407
+ api.paragraphStyleByName = function (doc, name) {
408
+ var style, all, i;
409
+ try {
410
+ style = doc.paragraphStyles.itemByName(name);
411
+ if (style && style.isValid === true) { return style; }
412
+ } catch (e) { /* weiter */ }
413
+ try {
414
+ all = doc.allParagraphStyles;
415
+ for (i = 0; i < all.length; i++) {
416
+ if (String(all[i].name) === String(name)) { return all[i]; }
417
+ }
418
+ } catch (e2) { /* weiter */ }
419
+ return null;
420
+ };
421
+
422
+ /* ------------------------------------------------------ Seiten und Boegen */
423
+
424
+ api.spreadIndex = function (spread) {
425
+ try { return Number(spread.index); } catch (e) { return null; }
426
+ };
427
+
428
+ api.pageSide = function (page) {
429
+ var side;
430
+ try { side = page.side; } catch (e) { return 'SINGLE_SIDED'; }
431
+ if (enumIs(side, PageSideOptions.LEFT_HAND)) { return 'LEFT_HAND'; }
432
+ if (enumIs(side, PageSideOptions.RIGHT_HAND)) { return 'RIGHT_HAND'; }
433
+ return 'SINGLE_SIDED';
434
+ };
435
+
436
+ /* Seitenzahl je Druckbogen des Dokuments. */
437
+ api.spreadPageCounts = function (doc) {
438
+ var counts = [];
439
+ var i;
440
+ for (i = 0; i < doc.spreads.length; i++) {
441
+ counts.push(doc.spreads.item(i).pages.length);
442
+ }
443
+ return counts;
444
+ };
445
+
446
+ /*
447
+ * "Seiten neu anordnen" (allowPageShuffle) nur fuer die Dauer von fn
448
+ * umstellen. value null/undefined = unveraendert lassen.
449
+ */
450
+ api.withShuffle = function (doc, value, fn) {
451
+ var prev = null;
452
+ var set = false;
453
+ if (value !== null && value !== undefined) {
454
+ prev = doc.documentPreferences.allowPageShuffle;
455
+ doc.documentPreferences.allowPageShuffle = value === true;
456
+ set = true;
457
+ }
458
+ try {
459
+ return fn();
460
+ } finally {
461
+ if (set) { doc.documentPreferences.allowPageShuffle = prev; }
462
+ }
463
+ };
464
+
465
+ /*
466
+ * Lineal-Nullpunkt (Seite/Bogen) nur fuer die Dauer von fn umstellen –
467
+ * fuer coordinate_space "spread" innerhalb eines Aufrufs, der sonst auf
468
+ * "page" laeuft.
469
+ */
470
+ api.withRulerOrigin = function (doc, origin, fn) {
471
+ var prev = doc.viewPreferences.rulerOrigin;
472
+ doc.viewPreferences.rulerOrigin = origin;
473
+ try {
474
+ return fn();
475
+ } finally {
476
+ doc.viewPreferences.rulerOrigin = prev;
477
+ }
478
+ };
479
+
480
+ /*
481
+ * Wo ist ein Objekt gelandet? parentPage ist die Seite, auf der InDesign
482
+ * das Objekt fuehrt (null = Montageflaeche). Liefert
483
+ * { page_id, page_name, on_pasteboard }.
484
+ */
485
+ api.landing = function (item) {
486
+ var page = null;
487
+ try { page = item.parentPage; } catch (e) { page = null; }
488
+ var valid = false;
489
+ try { valid = page !== null && page !== undefined && page.isValid === true; } catch (e2) { valid = false; }
490
+ if (!valid) {
491
+ return { page_id: null, page_name: '', on_pasteboard: true };
492
+ }
493
+ return { page_id: Number(page.id), page_name: String(page.name), on_pasteboard: false };
494
+ };
495
+
496
+ /* Seitengroesse in mm ueber die Seitenbegrenzung (unter dem aktuellen Lineal). */
497
+ api.pageSize = function (page) {
498
+ var b = page.bounds;
499
+ return { width: round(b[3] - b[1]), height: round(b[2] - b[0]) };
500
+ };
501
+
502
+ /* Versatz einer Seite vom Anfang ihres Bogens in mm (Breiten der Seiten davor). */
503
+ api.spreadOffsetOfPage = function (page) {
504
+ var spread = null;
505
+ var i, other, offset = 0;
506
+ try { spread = page.parent; } catch (e) { return 0; }
507
+ if (spread === null || spread === undefined) { return 0; }
508
+ for (i = 0; i < spread.pages.length; i++) {
509
+ other = spread.pages.item(i);
510
+ if (Number(other.id) === Number(page.id)) { return offset; }
511
+ offset += api.pageSize(other).width;
512
+ }
513
+ return 0;
514
+ };
515
+
516
+ /*
517
+ * Begrenzung eines Objekts relativ zur linken oberen Ecke von page.
518
+ *
519
+ * InDesign liest geometricBounds unter Seiten-Nullpunkt relativ zur ersten
520
+ * Seite, die das Objekt beruehrt – ein Rahmen mit 3 mm Anschnitt auf der
521
+ * rechten Seite eines Bogens steht dann bei x = 207 der linken Seite (am
522
+ * lebenden InDesign 21.5 am 04.09.2026 gesehen). Deshalb unter Bogen-
523
+ * Nullpunkt lesen und selbst um den Seitenversatz verschieben. page null
524
+ * (Montageflaeche): Bogenkoordinaten.
525
+ */
526
+ api.boundsOnPage = function (doc, item, page) {
527
+ var bounds = api.withRulerOrigin(doc, RulerOrigin.SPREAD_ORIGIN, function () {
528
+ return item.geometricBounds;
529
+ });
530
+ var dx = page ? api.spreadOffsetOfPage(page) : 0;
531
+ return {
532
+ x: round(bounds[1] - dx),
533
+ y: round(bounds[0]),
534
+ width: round(bounds[3] - bounds[1]),
535
+ height: round(bounds[2] - bounds[0])
536
+ };
537
+ };
538
+
539
+ /* Begrenzung relativ zur Seite, auf der InDesign das Objekt fuehrt. */
540
+ api.boundsOnOwnPage = function (doc, item) {
541
+ var page = null;
542
+ try { page = item.parentPage; } catch (e) { page = null; }
543
+ var valid = false;
544
+ try { valid = page !== null && page !== undefined && page.isValid === true; } catch (e2) { valid = false; }
545
+ return api.boundsOnPage(doc, item, valid ? page : null);
546
+ };
547
+
548
+ /* geometricBounds ist [y1, x1, y2, x2]. */
549
+ api.boundsToRect = function (bounds) {
550
+ return {
551
+ x: round(bounds[1]),
552
+ y: round(bounds[0]),
553
+ width: round(bounds[3] - bounds[1]),
554
+ height: round(bounds[2] - bounds[0])
555
+ };
556
+ };
557
+
558
+ /* ------------------------------------------------------------- Ausfuehrung */
559
+
560
+ function restore(state) {
561
+ var problems = [];
562
+ if (state.docRulerSet) {
563
+ try { state.doc.viewPreferences.rulerOrigin = state.prevRulerOrigin; }
564
+ catch (e1) { problems.push('ruler origin: ' + String(e1)); }
565
+ }
566
+ if (state.zeroPointSet) {
567
+ try { state.doc.zeroPoint = state.prevZeroPoint; }
568
+ catch (e1b) { problems.push('zero point: ' + String(e1b)); }
569
+ }
570
+ if (state.unitSet) {
571
+ try { app.scriptPreferences.measurementUnit = state.prevUnit; }
572
+ catch (e2) { problems.push('measurement unit: ' + String(e2)); }
573
+ }
574
+ if (state.interactionSet) {
575
+ try { app.scriptPreferences.userInteractionLevel = state.prevInteraction; }
576
+ catch (e3) { problems.push('user interaction level: ' + String(e3)); }
577
+ }
578
+ return problems;
579
+ }
580
+
581
+ function toError(e) {
582
+ if (e && e.isIdcError) {
583
+ return { kind: e.kind, message: e.message, details: e.details };
584
+ }
585
+ var msg;
586
+ try { msg = String(e && e.message ? e.message : e); } catch (x) { msg = 'unknown error'; }
587
+ var details = null;
588
+ try {
589
+ if (e && e.line !== undefined) {
590
+ details = { line: e.line, file: e.fileName ? String(e.fileName) : null };
591
+ }
592
+ } catch (y) { details = null; }
593
+ return { kind: 'script_error', message: msg, details: details };
594
+ }
595
+
596
+ api.execute = function (command) {
597
+ var state = {
598
+ doc: null,
599
+ unitSet: false,
600
+ interactionSet: false,
601
+ docRulerSet: false,
602
+ zeroPointSet: false,
603
+ prevZeroPoint: null,
604
+ prevUnit: null,
605
+ prevInteraction: null,
606
+ prevRulerOrigin: null
607
+ };
608
+ var warnings = [];
609
+ var result;
610
+
611
+ try {
612
+ if (!command || typeof command.cmd !== 'string') {
613
+ api.fail('invalid_argument', 'Command must be an object with a "cmd" string.', null);
614
+ }
615
+ if (!hasOwn(api.commands, command.cmd)) {
616
+ api.fail('not_found', 'Unknown command: ' + command.cmd, { cmd: command.cmd });
617
+ }
618
+ if (!appIsReady()) {
619
+ api.fail('app_not_available', 'InDesign is not available or not ready.', null);
620
+ }
621
+
622
+ var entry = api.commands[command.cmd];
623
+ var args = command.args || {};
624
+
625
+ state.prevUnit = app.scriptPreferences.measurementUnit;
626
+ app.scriptPreferences.measurementUnit = MeasurementUnits.MILLIMETERS;
627
+ state.unitSet = true;
628
+
629
+ state.prevInteraction = app.scriptPreferences.userInteractionLevel;
630
+ app.scriptPreferences.userInteractionLevel = UserInteractionLevels.NEVER_INTERACT;
631
+ state.interactionSet = true;
632
+
633
+ var doc = null;
634
+ if (entry.needs_document) {
635
+ doc = api.resolveDocument(args);
636
+ state.doc = doc;
637
+ if (entry.ruler !== 'none') {
638
+ state.prevRulerOrigin = doc.viewPreferences.rulerOrigin;
639
+ doc.viewPreferences.rulerOrigin = (entry.ruler === 'spread')
640
+ ? RulerOrigin.SPREAD_ORIGIN
641
+ : RulerOrigin.PAGE_ORIGIN;
642
+ state.docRulerSet = true;
643
+ /*
644
+ * Der Nullpunkt kann vom Anwender verschoben sein; dann waeren alle
645
+ * Koordinaten um diesen Versatz falsch. Fuer die Dauer des Aufrufs
646
+ * auf [0, 0] und danach zurueck.
647
+ */
648
+ try {
649
+ state.prevZeroPoint = doc.zeroPoint;
650
+ if (state.prevZeroPoint && (Number(state.prevZeroPoint[0]) !== 0 || Number(state.prevZeroPoint[1]) !== 0)) {
651
+ doc.zeroPoint = [0, 0];
652
+ state.zeroPointSet = true;
653
+ }
654
+ } catch (ez) { state.zeroPointSet = false; }
655
+ }
656
+ }
657
+
658
+ var ctx = {
659
+ args: args,
660
+ doc: doc,
661
+ warnings: warnings,
662
+ changes: null,
663
+ warn: function (text) { warnings.push(String(text)); },
664
+ idc: api
665
+ };
666
+
667
+ /* Fuer Helfer, die wissen muessen, ob der laufende Befehl schreibt (Ebenensperre). */
668
+ api.currentCommand = entry;
669
+ var data = entry.handler(ctx);
670
+ if (data === null || data === undefined) { data = {}; }
671
+
672
+ result = { ok: true, warnings: warnings };
673
+ var key;
674
+ for (key in data) {
675
+ if (hasOwn(data, key)) { result[key] = data[key]; }
676
+ }
677
+ if (ctx.changes !== null) { result.changes = ctx.changes; }
678
+ if (doc !== null && result.document_id === undefined) {
679
+ try { result.document_id = Number(doc.id); } catch (eid) { /* egal */ }
680
+ }
681
+ } catch (err) {
682
+ result = { ok: false, warnings: warnings, error: toError(err) };
683
+ }
684
+
685
+ api.currentCommand = null;
686
+ var restoreProblems = restore(state);
687
+ var r;
688
+ for (r = 0; r < restoreProblems.length; r++) {
689
+ warnings.push('Could not restore setting – ' + restoreProblems[r]);
690
+ }
691
+ result.warnings = warnings;
692
+ return result;
693
+ };
694
+
695
+ api.run = function (command) {
696
+ var out;
697
+ try {
698
+ out = api.execute(command);
699
+ } catch (e) {
700
+ out = { ok: false, warnings: [], error: toError(e) };
701
+ }
702
+ return stringify(out);
703
+ };
704
+
705
+ return api;
706
+ })();
707
+
708
+ /*
709
+ * Kompatibilitaetsschicht fuer die bekannten Unterschiede zwischen
710
+ * ExtendScript und UXP. Bewusst klein gehalten und an einer Stelle gebuendelt.
711
+ */
712
+
713
+ /* Dateiobjekt zu einem Pfad. ExtendScript kennt File, UXP nicht. */
714
+ IDC.fileFor = function (path) {
715
+ if (typeof File !== 'undefined' && File !== null) {
716
+ return new File(String(path));
717
+ }
718
+ IDC.fail('invalid_state',
719
+ 'This runtime has no ExtendScript File object; the UXP transport must hand over file entries.',
720
+ { path: String(path) });
721
+ return null;
722
+ };
723
+
724
+ /* Ordnerobjekt zu einem Pfad. ExtendScript kennt Folder, UXP nicht. */
725
+ IDC.folderFor = function (path) {
726
+ if (typeof Folder !== 'undefined' && Folder !== null) {
727
+ return new Folder(String(path));
728
+ }
729
+ IDC.fail('invalid_state',
730
+ 'This runtime has no ExtendScript Folder object; the UXP transport must hand over folder entries.',
731
+ { path: String(path) });
732
+ return null;
733
+ };
734
+
735
+ /* Betriebssystempfad eines Dateiobjekts, leer wenn nicht ermittelbar. */
736
+ IDC.fsPath = function (fileObject) {
737
+ try {
738
+ if (fileObject && fileObject.fsName) { return String(fileObject.fsName); }
739
+ } catch (e) { /* faellt auf leer zurueck */ }
740
+ return '';
741
+ };
742
+
743
+ /* Groesse einer Datei in Bytes, null wenn nicht ermittelbar. */
744
+ IDC.fileSize = function (fileObject) {
745
+ try {
746
+ if (fileObject && fileObject.exists && fileObject.length !== undefined) {
747
+ return Number(fileObject.length);
748
+ }
749
+ } catch (e) { /* null */ }
750
+ return null;
751
+ };
752
+
753
+ /* Gibt es den Ordner, in dem die Datei liegen soll? */
754
+ IDC.fileParentExists = function (fileObject) {
755
+ try {
756
+ return fileObject && fileObject.parent && fileObject.parent.exists === true;
757
+ } catch (e) { return false; }
758
+ };