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,1620 @@
1
+ /* Bereich H – Formate und Zeichenformatierung. */
2
+
3
+ /* ------------------------------------------------------------- Hilfen */
4
+
5
+ /*
6
+ * Vorhanden? DOM-Objekte (Color, Swatch) ueberladen === in ExtendScript und
7
+ * werfen beim Vergleich mit null ("Color.===() cannot work with instances of
8
+ * this class", Live-Fund 04.09.2026) – wirft der Vergleich, ist es ein Objekt.
9
+ */
10
+ function idcPresent(value) {
11
+ try {
12
+ return !(value === null || value === undefined);
13
+ } catch (e) { return true; }
14
+ }
15
+
16
+ /* NothingEnum.NOTHING: so liest InDesign ein nicht gesetztes Attribut (Zeichenformate). */
17
+ function idcIsNothing(value) {
18
+ if (!idcPresent(value)) { return true; }
19
+ try {
20
+ if (typeof NothingEnum !== 'undefined' && IDC.enumIs(value, NothingEnum.NOTHING)) { return true; }
21
+ } catch (e) { /* kein Enum */ }
22
+ return false;
23
+ }
24
+
25
+ /* Schrift eines Text- oder Formatobjekts; nicht gesetzte Attribute bleiben null. */
26
+ function idcReadFontLoose(target) {
27
+ var raw;
28
+ try { raw = target.appliedFont; } catch (e) { raw = null; }
29
+ if (idcIsNothing(raw)) {
30
+ var style = null;
31
+ try { style = target.fontStyle; } catch (e2) { style = null; }
32
+ return { family: null, style: idcIsNothing(style) ? null : String(style) };
33
+ }
34
+ var read = idcReadDefaultFont(target);
35
+ /* Nicht gesetzte Familie eines Zeichenformats liest sich als "" oder "undefined" (Live-Fund 04.09.2026). */
36
+ if (read.family === '' || read.family === 'undefined') { read.family = null; }
37
+ if (read.style === '' || read.style === 'undefined') { read.style = null; }
38
+ return read;
39
+ }
40
+
41
+ /* Punktwert lesen (Schriftgrad, Abstand): unter Punkt-Masseinheit, null wenn nicht gesetzt. */
42
+ function idcReadPt(target, property) {
43
+ var value;
44
+ try {
45
+ value = IDC.inPoints(function () { return target[property]; });
46
+ } catch (e) { return null; }
47
+ if (idcIsNothing(value)) { return null; }
48
+ return IDC.round(Number(value), 3);
49
+ }
50
+
51
+ /* Zeilenabstand: { pt, auto } – pt null bei automatischem Zeilenabstand. */
52
+ function idcReadLeading(target) {
53
+ var value;
54
+ try {
55
+ value = IDC.inPoints(function () { return target.leading; });
56
+ } catch (e) { return { pt: null, auto: false }; }
57
+ if (idcIsNothing(value)) { return { pt: null, auto: false }; }
58
+ if (typeof Leading !== 'undefined' && IDC.enumIs(value, Leading.AUTO)) { return { pt: null, auto: true }; }
59
+ var n = Number(value);
60
+ if (isNaN(n)) { return { pt: null, auto: false }; }
61
+ return { pt: IDC.round(n, 3), auto: false };
62
+ }
63
+
64
+ function idcReadNumberLoose(target, property, digits) {
65
+ var value;
66
+ try { value = target[property]; } catch (e) { return null; }
67
+ if (idcIsNothing(value)) { return null; }
68
+ var n = Number(value);
69
+ return isNaN(n) ? null : IDC.round(n, digits === undefined ? 3 : digits);
70
+ }
71
+
72
+ function idcReadBoolLoose(target, property) {
73
+ var value;
74
+ try { value = target[property]; } catch (e) { return null; }
75
+ if (idcIsNothing(value)) { return null; }
76
+ return value === true;
77
+ }
78
+
79
+ function idcReadSwatchName(target, property) {
80
+ var value;
81
+ try { value = target[property]; } catch (e) { return null; }
82
+ if (idcIsNothing(value)) { return null; }
83
+ try { return String(value.name); } catch (e2) { return null; }
84
+ }
85
+
86
+ /* Gruppenpfad eines Formats ("Group/Subgroup"), leer auf oberster Ebene. */
87
+ function idcStyleGroupPath(style) {
88
+ var names = [];
89
+ var current, name, step;
90
+ try { current = style.parent; } catch (e) { return ''; }
91
+ for (step = 0; step < 12; step++) {
92
+ if (current === null || current === undefined) { break; }
93
+ name = IDC.className(current);
94
+ if (name === 'ParagraphStyleGroup' || name === 'CharacterStyleGroup') {
95
+ try { names.unshift(String(current.name)); } catch (e2) { break; }
96
+ try { current = current.parent; } catch (e3) { break; }
97
+ } else {
98
+ break;
99
+ }
100
+ }
101
+ return names.join('/');
102
+ }
103
+
104
+ /* Name des Grundlagenformats; die Wurzel ([Kein Absatzformat]) kommt als Verweis ohne lesbaren Namen zurueck (Live-Fund 04.09.2026) – dann null. */
105
+ function idcBasedOnName(style) {
106
+ var base, name;
107
+ try { base = style.basedOn; } catch (e) { return null; }
108
+ if (idcIsNothing(base)) { return null; }
109
+ if (typeof base === 'string') { return base === '' || base === 'undefined' ? null : base; }
110
+ try { name = String(base.name); } catch (e2) { name = 'undefined'; }
111
+ if (name === 'undefined' || name === '') {
112
+ try { name = String(IDC.concrete(base).name); } catch (e3) { name = 'undefined'; }
113
+ }
114
+ return name === 'undefined' || name === '' ? null : name;
115
+ }
116
+
117
+ /* Ausrichtungsname zu einem Justification-Wert. */
118
+ function idcJustificationName(value) {
119
+ if (typeof Justification === 'undefined') { return value === undefined ? null : String(value); }
120
+ if (idcIsNothing(value)) { return null; }
121
+ var table = [
122
+ ['left', Justification.LEFT_ALIGN],
123
+ ['center', Justification.CENTER_ALIGN],
124
+ ['right', Justification.RIGHT_ALIGN],
125
+ ['justify_left', Justification.LEFT_JUSTIFIED],
126
+ ['justify_center', Justification.CENTER_JUSTIFIED],
127
+ ['justify_right', Justification.RIGHT_JUSTIFIED],
128
+ ['justify_full', Justification.FULLY_JUSTIFIED],
129
+ ['away_from_binding', Justification.AWAY_FROM_BINDING_SIDE],
130
+ ['to_binding', Justification.TO_BINDING_SIDE]
131
+ ];
132
+ var i;
133
+ for (i = 0; i < table.length; i++) {
134
+ if (IDC.enumIs(value, table[i][1])) { return table[i][0]; }
135
+ }
136
+ return String(value);
137
+ }
138
+
139
+ /* Justification-Wert zu einem Ausrichtungsnamen; "justify" = Blocksatz, letzte Zeile links. */
140
+ function idcJustificationFor(name, field) {
141
+ var text = String(name);
142
+ if (typeof Justification === 'undefined') {
143
+ IDC.fail('invalid_state', 'This runtime has no Justification enumeration.', null);
144
+ }
145
+ if (text === 'left') { return Justification.LEFT_ALIGN; }
146
+ if (text === 'center') { return Justification.CENTER_ALIGN; }
147
+ if (text === 'right') { return Justification.RIGHT_ALIGN; }
148
+ if (text === 'justify' || text === 'justify_left') { return Justification.LEFT_JUSTIFIED; }
149
+ if (text === 'justify_center') { return Justification.CENTER_JUSTIFIED; }
150
+ if (text === 'justify_right') { return Justification.RIGHT_JUSTIFIED; }
151
+ if (text === 'justify_full') { return Justification.FULLY_JUSTIFIED; }
152
+ IDC.fail('invalid_argument', field + ' must be one of left, center, right, justify, justify_left, justify_center, justify_right, justify_full.', { value: text });
153
+ return null;
154
+ }
155
+
156
+ function idcStyleNameArg(value, field) {
157
+ if (typeof value !== 'string' || value.length === 0) {
158
+ IDC.fail('invalid_argument', field + ' is required (a non-empty string).', null);
159
+ }
160
+ if (value.charAt(0) === '[') {
161
+ IDC.fail('invalid_argument', field + ' must not start with "[" – those names are reserved for InDesign\'s internal styles.', { value: value });
162
+ }
163
+ return value;
164
+ }
165
+
166
+ function idcCollisionArg(value) {
167
+ if (value === undefined || value === null) { return 'error'; }
168
+ if (value !== 'error' && value !== 'update' && value !== 'version') {
169
+ IDC.fail('invalid_argument', 'on_collision must be error, update or version.', { value: value });
170
+ }
171
+ return value;
172
+ }
173
+
174
+ function idcNumberArg(value, field, min, max) {
175
+ if (typeof value !== 'number' || !isFinite(value)) {
176
+ IDC.fail('invalid_argument', field + ' must be a number.', { value: value });
177
+ }
178
+ if (min !== null && value < min) {
179
+ IDC.fail('invalid_argument', field + ' must be ' + min + ' or more.', { value: value });
180
+ }
181
+ if (max !== null && value > max) {
182
+ IDC.fail('invalid_argument', field + ' must be ' + max + ' or less.', { value: value });
183
+ }
184
+ return value;
185
+ }
186
+
187
+ function idcGiven(value) {
188
+ return value !== undefined && value !== null;
189
+ }
190
+
191
+ /* Zeichenformat ueber den Namen, auch in Formatgruppen. */
192
+ function idcCharacterStyleByName(doc, name) {
193
+ var style, all, i;
194
+ try {
195
+ style = doc.characterStyles.itemByName(name);
196
+ if (style && style.isValid === true) { return style; }
197
+ } catch (e) { /* weiter */ }
198
+ try {
199
+ all = doc.allCharacterStyles;
200
+ for (i = 0; i < all.length; i++) {
201
+ if (String(all[i].name) === String(name)) { return all[i]; }
202
+ }
203
+ } catch (e2) { /* weiter */ }
204
+ return null;
205
+ }
206
+
207
+ /* Alle Absatz- bzw. Zeichenformate als Array, auch aus Gruppen. */
208
+ function idcAllStyles(doc, kind) {
209
+ var out = [];
210
+ var all, collection, i;
211
+ try {
212
+ all = kind === 'character' ? doc.allCharacterStyles : doc.allParagraphStyles;
213
+ for (i = 0; i < all.length; i++) { out.push(all[i]); }
214
+ return out;
215
+ } catch (e) { /* Rueckfall: nur die oberste Ebene */ }
216
+ try {
217
+ collection = kind === 'character' ? doc.characterStyles : doc.paragraphStyles;
218
+ for (i = 0; i < collection.length; i++) { out.push(collection.item(i)); }
219
+ } catch (e2) { /* leer */ }
220
+ return out;
221
+ }
222
+
223
+ /* Naechster freier Name "Name 2", "Name 3" ... fuer Absatz-/Zeichenformat oder Farbfeld. */
224
+ function idcFreeName(doc, base, kind) {
225
+ var n = 2;
226
+ var candidate;
227
+ function taken(name) {
228
+ if (kind === 'paragraph') { return IDC.paragraphStyleByName(doc, name) !== null; }
229
+ if (kind === 'character') { return idcCharacterStyleByName(doc, name) !== null; }
230
+ return idcPresent(idcSwatchByName(doc, name));
231
+ }
232
+ for (n = 2; n < 1000; n++) {
233
+ candidate = base + ' ' + n;
234
+ if (!taken(candidate)) { return candidate; }
235
+ }
236
+ IDC.fail('invalid_state', 'No free name found for "' + base + '".', { name: base });
237
+ return null;
238
+ }
239
+
240
+ function idcSwatchByName(doc, name) {
241
+ var swatch = null;
242
+ try {
243
+ swatch = doc.swatches.itemByName(String(name));
244
+ if (swatch && swatch.isValid === true) { return swatch; }
245
+ } catch (e) { /* weiter */ }
246
+ return null;
247
+ }
248
+
249
+ /* Zeichenformat [None]; der Skriptname ist in jeder Sprachfassung "[None]". */
250
+ function idcNoneCharacterStyle(doc) {
251
+ var style = null;
252
+ try {
253
+ style = doc.characterStyles.itemByName('[None]');
254
+ if (style && style.isValid === true) { return style; }
255
+ } catch (e) { /* weiter */ }
256
+ try {
257
+ style = doc.characterStyles.item(0);
258
+ if (style && style.isValid === true) { return style; }
259
+ } catch (e2) { /* weiter */ }
260
+ return null;
261
+ }
262
+
263
+ /* Ist ein Zeichenformat das eingebaute [None]? Sein Name ist lokalisiert ("[Ohne]" in der deutschen Fassung, Live-Fund 04.09.2026) – deshalb ueber die ID. */
264
+ function idcIsNoneCharacterStyle(doc, style) {
265
+ var none = idcNoneCharacterStyle(doc);
266
+ if (!idcPresent(style) || none === null) { return null; }
267
+ try { return Number(style.id) === Number(none.id); } catch (e) { /* weiter */ }
268
+ try { return String(style.name).charAt(0) === '['; } catch (e2) { return null; }
269
+ }
270
+
271
+ /* Beschreibung eines Absatzformats fuer list_paragraph_styles und debug_inspect_run. */
272
+ function idcDescribeParagraphStyle(style) {
273
+ var font = idcReadFontLoose(style);
274
+ var leading = idcReadLeading(style);
275
+ var id = null, name = null, alignment = null;
276
+ try { id = Number(style.id); } catch (e1) { id = null; }
277
+ try { name = String(style.name); } catch (e2) { name = null; }
278
+ try { alignment = idcJustificationName(style.justification); } catch (e3) { alignment = null; }
279
+ return {
280
+ name: name,
281
+ style_id: id,
282
+ group: idcStyleGroupPath(style),
283
+ based_on: idcBasedOnName(style),
284
+ font_family: font.family,
285
+ font_style: font.style,
286
+ point_size: idcReadPt(style, 'pointSize'),
287
+ leading_pt: leading.pt,
288
+ leading_auto: leading.auto,
289
+ fill_swatch: idcReadSwatchName(style, 'fillColor'),
290
+ alignment: alignment,
291
+ space_before_pt: idcReadPt(style, 'spaceBefore'),
292
+ space_after_pt: idcReadPt(style, 'spaceAfter'),
293
+ hyphenation: idcReadBoolLoose(style, 'hyphenation'),
294
+ tracking: idcReadNumberLoose(style, 'tracking', 1),
295
+ /* A-7.1 bis A-7.3 (81-composition.js) */
296
+ keep_options: idcReadKeepOptions(style),
297
+ hyphenation_options: idcReadHyphenationOptions(style),
298
+ justification_options: idcReadJustificationOptions(style),
299
+ /* Sitzung 10, Demo-Heft-Befund 3 (83-indents-tabs.js) */
300
+ indents: idcReadIndentTabLanguage(style)
301
+ };
302
+ }
303
+
304
+ /* Beschreibung eines Zeichenformats; nicht gesetzte Attribute sind null. */
305
+ function idcDescribeCharacterStyle(style) {
306
+ var font = idcReadFontLoose(style);
307
+ var id = null, name = null;
308
+ try { id = Number(style.id); } catch (e1) { id = null; }
309
+ try { name = String(style.name); } catch (e2) { name = null; }
310
+ return {
311
+ name: name,
312
+ style_id: id,
313
+ group: idcStyleGroupPath(style),
314
+ based_on: idcBasedOnName(style),
315
+ font_family: font.family,
316
+ font_style: font.style,
317
+ point_size: idcReadPt(style, 'pointSize'),
318
+ fill_swatch: idcReadSwatchName(style, 'fillColor'),
319
+ tracking: idcReadNumberLoose(style, 'tracking', 1)
320
+ };
321
+ }
322
+
323
+ /* ------------------------------------------------------------ Schriften */
324
+
325
+ function idcFontStatusName(status) {
326
+ if (typeof FontStatus === 'undefined' || idcIsNothing(status)) { return 'installed'; }
327
+ if (IDC.enumIs(status, FontStatus.INSTALLED)) { return 'installed'; }
328
+ if (IDC.enumIs(status, FontStatus.SUBSTITUTED)) { return 'substituted'; }
329
+ if (IDC.enumIs(status, FontStatus.FAUXED)) { return 'substituted'; }
330
+ if (IDC.enumIs(status, FontStatus.NOT_AVAILABLE)) { return 'not_available'; }
331
+ if (IDC.enumIs(status, FontStatus.UNKNOWN)) { return 'not_available'; }
332
+ return 'not_available';
333
+ }
334
+
335
+ /*
336
+ * Alle Schriften, die InDesign kennt: [{ family, style, status }]. Ueber
337
+ * everyItem() in drei DOM-Aufrufen; faellt auf die Schleife zurueck, wo es
338
+ * everyItem() nicht gibt.
339
+ */
340
+ function idcFontCatalogue() {
341
+ var out = [];
342
+ var families, styles, statuses, i, entry;
343
+ try {
344
+ families = app.fonts.everyItem().fontFamily;
345
+ styles = app.fonts.everyItem().fontStyleName;
346
+ statuses = app.fonts.everyItem().status;
347
+ if (IDC.isArray(families) && families.length === styles.length) {
348
+ for (i = 0; i < families.length; i++) {
349
+ out.push({ family: String(families[i]), style: String(styles[i]), status: idcFontStatusName(statuses[i]) });
350
+ }
351
+ return out;
352
+ }
353
+ } catch (e) { /* Schleife */ }
354
+ out = [];
355
+ try {
356
+ for (i = 0; i < app.fonts.length; i++) {
357
+ entry = app.fonts.item(i);
358
+ out.push({ family: String(entry.fontFamily), style: String(entry.fontStyleName), status: idcFontStatusName(entry.status) });
359
+ }
360
+ } catch (e2) { /* was da ist */ }
361
+ return out;
362
+ }
363
+
364
+ function idcSameText(a, b) {
365
+ return String(a).toLowerCase() === String(b).toLowerCase();
366
+ }
367
+
368
+ /*
369
+ * Familienname ohne Formatzusatz: InDesign haengt " (OTF)", " (TT)" oder
370
+ * " (T1)" an, wenn es eine Familie in mehreren Formaten kennt (Demo-Heft-
371
+ * Befund 7, 05.09.2026: "Source Sans 3 (OTF)"). Vergleiche von Familiennamen
372
+ * lassen den Zusatz auf beiden Seiten weg.
373
+ */
374
+ function idcFamilyKey(name) {
375
+ return String(name).toLowerCase().replace(/\s*\((otf|tt|t1|ttf|opentype|truetype|type ?1)\)\s*$/, '');
376
+ }
377
+
378
+ function idcSameFamily(a, b) {
379
+ return idcFamilyKey(a) === idcFamilyKey(b);
380
+ }
381
+
382
+ /* Schnitte einer Familie aus dem Katalog. */
383
+ function idcStylesOfFamily(catalogue, family) {
384
+ var out = [];
385
+ var i;
386
+ for (i = 0; i < catalogue.length; i++) {
387
+ if (idcSameFamily(catalogue[i].family, family)) { out.push(catalogue[i].style); }
388
+ }
389
+ return out;
390
+ }
391
+
392
+ function idcCatalogueStatus(catalogue, family, style) {
393
+ var i;
394
+ for (i = 0; i < catalogue.length; i++) {
395
+ if (idcSameFamily(catalogue[i].family, family) && idcSameText(catalogue[i].style, style)) { return catalogue[i].status; }
396
+ }
397
+ return null;
398
+ }
399
+
400
+ /* Status der im Dokument benutzten Schriften: { "Familie\tSchnitt": status }. */
401
+ function idcDocumentFontStatuses(doc) {
402
+ var out = {};
403
+ var i, entry, name, status;
404
+ try {
405
+ for (i = 0; i < doc.fonts.length; i++) {
406
+ entry = doc.fonts.item(i);
407
+ name = String(entry.name);
408
+ try { status = idcFontStatusName(entry.status); } catch (e1) { status = 'installed'; }
409
+ out[name] = status;
410
+ try {
411
+ out[String(entry.fontFamily) + '\t' + String(entry.fontStyleName)] = status;
412
+ } catch (e2) { /* Name reicht */ }
413
+ }
414
+ } catch (e) { /* leer */ }
415
+ return out;
416
+ }
417
+
418
+ /*
419
+ * Schrift auf ein Format oder eine Textstelle setzen. Beides: ueber das
420
+ * Font-Objekt, wenn installiert, sonst als "Familie\tSchnitt" (InDesign fuehrt
421
+ * sie dann als fehlend). Nur Familie: InDesign behaelt eine unbekannte
422
+ * Familie stillschweigend – deshalb zuruecklesen.
423
+ */
424
+ function idcApplyFont(ctx, target, family, styleName, catalogue, probe) {
425
+ var fontObject, text, after;
426
+ var applied = [];
427
+ var reader = idcPresent(probe) ? probe : target;
428
+ if (idcGiven(family) && idcGiven(styleName)) {
429
+ text = String(family) + '\t' + String(styleName);
430
+ fontObject = null;
431
+ try {
432
+ fontObject = app.fonts.itemByName(text);
433
+ if (!fontObject || fontObject.isValid !== true) { fontObject = null; }
434
+ } catch (e0) { fontObject = null; }
435
+ try {
436
+ if (fontObject !== null) { target.appliedFont = fontObject; }
437
+ else { target.appliedFont = text; }
438
+ } catch (e1) {
439
+ /* InDesign 21.5: "Die gewuenschte Schriftfamilie ist nicht verfuegbar." – eine fehlende Schrift laesst sich per Skript nicht setzen (Live-Fund 04.09.2026). */
440
+ IDC.fail('not_found', 'Font "' + String(family) + ' ' + String(styleName) + '" is not installed; InDesign rejects it: ' + String(e1.message ? e1.message : e1),
441
+ { font_family: family, font_style: styleName, available_styles: idcStylesOfFamily(catalogue, family) });
442
+ }
443
+ after = idcReadFontLoose(reader);
444
+ if (fontObject === null) {
445
+ if (after.family !== null && !idcSameFamily(after.family, family)) {
446
+ IDC.fail('not_found', 'Font "' + String(family) + ' ' + String(styleName) + '" is not installed; InDesign kept "' + after.family + ' ' + after.style + '".',
447
+ { font_family: family, font_style: styleName, available_styles: idcStylesOfFamily(catalogue, family) });
448
+ }
449
+ ctx.warn('Font "' + String(family) + ' ' + String(styleName) + '" is not installed; InDesign lists it as missing (fehlende Schrift).');
450
+ }
451
+ applied.push('font_family');
452
+ applied.push('font_style');
453
+ return applied;
454
+ }
455
+ if (idcGiven(family)) {
456
+ try { target.appliedFont = String(family); } catch (e2) {
457
+ /* InDesign 21.5 wirft auch bei der Familie allein (Live-Fund 04.09.2026, Suite Lauf 2). */
458
+ IDC.fail('not_found', 'Font family "' + String(family) + '" is not installed; InDesign rejects it: ' + String(e2.message ? e2.message : e2), { font_family: family });
459
+ }
460
+ after = idcReadFontLoose(reader);
461
+ if (after.family === null || !idcSameFamily(after.family, family)) {
462
+ IDC.fail('not_found', 'Font family "' + String(family) + '" is not installed; InDesign kept "' + after.family + '". Pass font_style as well to define a missing font on purpose.',
463
+ { font_family: family });
464
+ }
465
+ applied.push('font_family');
466
+ return applied;
467
+ }
468
+ if (idcGiven(styleName)) {
469
+ var before = idcReadFontLoose(reader);
470
+ try { target.fontStyle = String(styleName); } catch (e3) {
471
+ IDC.fail('invalid_argument', 'Font style "' + String(styleName) + '" is not available for "' + before.family + '" – use get_font_info_at_index for the exact style names (e.g. "75 Bold").',
472
+ { font_style: styleName, font_family: before.family, available_styles: before.family === null ? [] : idcStylesOfFamily(catalogue, before.family) });
473
+ }
474
+ after = idcReadFontLoose(reader);
475
+ if (after.style === null || !idcSameText(after.style, styleName)) {
476
+ IDC.fail('invalid_argument', 'Font style "' + String(styleName) + '" is not available for "' + before.family + '"; InDesign kept "' + after.style + '" – use get_font_info_at_index for the exact style names.',
477
+ { font_style: styleName, font_family: before.family, available_styles: before.family === null ? [] : idcStylesOfFamily(catalogue, before.family) });
478
+ }
479
+ applied.push('font_style');
480
+ return applied;
481
+ }
482
+ return applied;
483
+ }
484
+
485
+ /* Gemeinsame Attribute von define_paragraph_style und define_character_style. */
486
+ function idcApplyStyleAttributes(ctx, doc, style, args, created, kind) {
487
+ var applied = [];
488
+ var catalogue = null;
489
+ var fontApplied, leading, swatch, hex;
490
+
491
+ if (idcGiven(args.font_family) || idcGiven(args.font_style)) {
492
+ catalogue = idcFontCatalogue();
493
+ fontApplied = idcApplyFont(ctx, style, args.font_family, args.font_style, catalogue);
494
+ applied = applied.concat(fontApplied);
495
+ }
496
+ if (kind === 'paragraph' && idcGiven(args.size_pt)) {
497
+ idcNumberArg(args.size_pt, 'size_pt', 0.1, 1296);
498
+ IDC.inPoints(function () { style.pointSize = args.size_pt; });
499
+ applied.push('size_pt');
500
+ }
501
+ if (kind === 'character' && idcGiven(args.point_size)) {
502
+ idcNumberArg(args.point_size, 'point_size', 0.1, 1296);
503
+ IDC.inPoints(function () { style.pointSize = args.point_size; });
504
+ applied.push('point_size');
505
+ }
506
+ if (idcGiven(args.leading_pt)) {
507
+ leading = args.leading_pt;
508
+ if (leading === 'auto') {
509
+ if (typeof Leading === 'undefined') { IDC.fail('invalid_state', 'This runtime has no Leading enumeration.', null); }
510
+ style.leading = Leading.AUTO;
511
+ } else {
512
+ idcNumberArg(leading, 'leading_pt', 0, 5000);
513
+ IDC.inPoints(function () { style.leading = leading; });
514
+ }
515
+ applied.push('leading_pt');
516
+ }
517
+ if (idcGiven(args.alignment)) {
518
+ style.justification = idcJustificationFor(args.alignment, 'alignment');
519
+ applied.push('alignment');
520
+ }
521
+ hex = kind === 'paragraph' ? args.color_hex : args.fill_hex;
522
+ /* A-9.3: swatch_name setzt ein vorhandenes Farbfeld (CMYK, Vollton, Ton, Verlauf) statt ueber Hex ein RGB-Feld anzulegen. */
523
+ if (idcGiven(hex) && idcGiven(args.swatch_name)) {
524
+ IDC.fail('invalid_argument', 'Pass either ' + (kind === 'paragraph' ? 'color_hex' : 'fill_hex') + ' or swatch_name, not both.', null);
525
+ }
526
+ if (idcGiven(hex)) {
527
+ swatch = idcSwatchForHex(doc, hex, kind === 'paragraph' ? 'color_hex' : 'fill_hex', created);
528
+ style.fillColor = swatch;
529
+ applied.push(kind === 'paragraph' ? 'color_hex' : 'fill_hex');
530
+ }
531
+ if (idcGiven(args.swatch_name)) {
532
+ swatch = idcSwatchArg(doc, args.swatch_name, 'swatch_name');
533
+ style.fillColor = swatch;
534
+ applied.push('fill_swatch');
535
+ }
536
+ if (idcGiven(args.space_before_pt)) {
537
+ idcNumberArg(args.space_before_pt, 'space_before_pt', 0, 8640);
538
+ IDC.inPoints(function () { style.spaceBefore = args.space_before_pt; });
539
+ applied.push('space_before_pt');
540
+ }
541
+ if (idcGiven(args.space_after_pt)) {
542
+ idcNumberArg(args.space_after_pt, 'space_after_pt', 0, 8640);
543
+ IDC.inPoints(function () { style.spaceAfter = args.space_after_pt; });
544
+ applied.push('space_after_pt');
545
+ }
546
+ if (idcGiven(args.tracking)) {
547
+ idcNumberArg(args.tracking, 'tracking', -1000, 10000);
548
+ style.tracking = args.tracking;
549
+ applied.push('tracking');
550
+ }
551
+ if (idcGiven(args.hyphenation)) {
552
+ if (typeof args.hyphenation !== 'boolean') { IDC.fail('invalid_argument', 'hyphenation must be true or false.', { value: args.hyphenation }); }
553
+ style.hyphenation = args.hyphenation;
554
+ applied.push('hyphenation');
555
+ }
556
+ if (kind === 'paragraph') {
557
+ applied = applied.concat(idcApplyCompositionOptions(style, args));
558
+ applied = applied.concat(idcApplyIndentTabLanguage(doc, style, args));
559
+ }
560
+ return applied;
561
+ }
562
+
563
+ /*
564
+ * Farbfelder wieder entfernen, die dieser Aufruf angelegt hat (Ruecknahme).
565
+ * Nur ueber die ID, damit kein gleichnamiges Farbfeld des Hefts getroffen wird.
566
+ */
567
+ function idcRemoveSwatchesById(doc, ids) {
568
+ var i, j, swatch, id;
569
+ if (!ids || ids.length === 0) { return; }
570
+ for (i = 0; i < ids.length; i++) {
571
+ id = Number(ids[i]);
572
+ for (j = doc.swatches.length - 1; j >= 0; j--) {
573
+ swatch = doc.swatches.item(j);
574
+ try {
575
+ if (Number(swatch.id) === id) { swatch.remove(); break; }
576
+ } catch (e) { /* naechstes */ }
577
+ }
578
+ }
579
+ }
580
+
581
+ /*
582
+ * Attribute setzen, aber nichts Halbfertiges hinterlassen: schlaegt ein
583
+ * Attribut fehl (etwa eine nicht installierte Schrift), wird das in diesem
584
+ * Aufruf neu angelegte Format samt seiner neuen Farbfelder wieder entfernt
585
+ * und der Fehler weitergereicht. Ein mit on_collision "update" veraendertes
586
+ * Format bleibt stehen – es gehoerte dem Dokument schon vorher.
587
+ * Live-Fund 04.09.2026 (E-46).
588
+ */
589
+ function idcApplyStyleAttributesOrRollback(ctx, doc, style, args, created, kind, isNew) {
590
+ try {
591
+ return idcApplyStyleAttributes(ctx, doc, style, args, created, kind);
592
+ } catch (e) {
593
+ if (isNew) {
594
+ try { style.remove(); } catch (e1) { /* bleibt stehen */ }
595
+ }
596
+ try { idcRemoveSwatchesById(doc, created); } catch (e2) { /* bleibt stehen */ }
597
+ throw e;
598
+ }
599
+ }
600
+
601
+ /* ---------------------------------------------------- list_paragraph_styles */
602
+
603
+ IDC.register('list_paragraph_styles', { needs_document: true, read_only: true, ruler: 'none' },
604
+ function (ctx) {
605
+ var doc = ctx.doc;
606
+ var styles = [];
607
+ var characterStyles = [];
608
+ var internal = 0;
609
+ var all, i, entry, name;
610
+
611
+ all = idcAllStyles(doc, 'paragraph');
612
+ for (i = 0; i < all.length; i++) {
613
+ try { name = String(all[i].name); } catch (e1) { continue; }
614
+ if (name.charAt(0) === '[') { internal += 1; continue; }
615
+ entry = idcDescribeParagraphStyle(all[i]);
616
+ styles.push(entry);
617
+ }
618
+ all = idcAllStyles(doc, 'character');
619
+ for (i = 0; i < all.length; i++) {
620
+ try { name = String(all[i].name); } catch (e2) { continue; }
621
+ if (name.charAt(0) === '[') { internal += 1; continue; }
622
+ characterStyles.push(idcDescribeCharacterStyle(all[i]));
623
+ }
624
+ return {
625
+ styles: styles,
626
+ count: styles.length,
627
+ character_styles: characterStyles,
628
+ character_style_count: characterStyles.length,
629
+ internal_styles_excluded: internal
630
+ };
631
+ });
632
+
633
+ /* --------------------------------------------------- define_paragraph_style */
634
+
635
+ IDC.register('define_paragraph_style', { needs_document: true, read_only: false, ruler: 'none' },
636
+ function (ctx) {
637
+ var args = ctx.args;
638
+ var doc = ctx.doc;
639
+ var name = idcStyleNameArg(args.name, 'name');
640
+ var onCollision = idcCollisionArg(args.on_collision);
641
+ var existing = IDC.paragraphStyleByName(doc, name);
642
+ var collision = 'none';
643
+ var chosenName = name;
644
+ var createdSwatches = [];
645
+ var style = null;
646
+ var applied, changes;
647
+
648
+ if (existing !== null) {
649
+ if (onCollision === 'error') {
650
+ IDC.fail('invalid_state', 'Paragraph style "' + name + '" exists already. Pass on_collision "update" to change it or "version" to add "' + name + ' 2".',
651
+ { name: name, style_id: (function () { try { return Number(existing.id); } catch (e) { return null; } })() });
652
+ }
653
+ if (onCollision === 'update') {
654
+ style = existing;
655
+ collision = 'updated';
656
+ } else {
657
+ chosenName = idcFreeName(doc, name, 'paragraph');
658
+ collision = 'versioned';
659
+ }
660
+ }
661
+ if (style === null) {
662
+ try {
663
+ style = doc.paragraphStyles.add({ name: chosenName });
664
+ } catch (e) {
665
+ IDC.fail('script_error', 'InDesign could not create the paragraph style "' + chosenName + '": ' + String(e.message ? e.message : e), { name: chosenName });
666
+ }
667
+ }
668
+ applied = idcApplyStyleAttributesOrRollback(ctx, doc, style, args, createdSwatches, 'paragraph', collision !== 'updated');
669
+
670
+ changes = collision === 'updated'
671
+ ? { modified_style_ids: [Number(style.id)] }
672
+ : { created_style_ids: [Number(style.id)] };
673
+ if (createdSwatches.length > 0) { changes.created_swatch_ids = createdSwatches; }
674
+ ctx.changes = changes;
675
+ return {
676
+ style_id: Number(style.id),
677
+ name: String(style.name),
678
+ collision: collision,
679
+ existed: existing !== null,
680
+ applied: applied,
681
+ definition: idcDescribeParagraphStyle(style)
682
+ };
683
+ });
684
+
685
+ /* --------------------------------------------------- define_character_style */
686
+
687
+ IDC.register('define_character_style', { needs_document: true, read_only: false, ruler: 'none' },
688
+ function (ctx) {
689
+ var args = ctx.args;
690
+ var doc = ctx.doc;
691
+ var name = idcStyleNameArg(args.name, 'name');
692
+ var onCollision = idcCollisionArg(args.on_collision);
693
+ var existing = idcCharacterStyleByName(doc, name);
694
+ var collision = 'none';
695
+ var chosenName = name;
696
+ var createdSwatches = [];
697
+ var style = null;
698
+ var applied, changes;
699
+
700
+ if (!idcGiven(args.font_family) && !idcGiven(args.font_style) && !idcGiven(args.point_size) && !idcGiven(args.fill_hex) && !idcGiven(args.swatch_name) && !idcGiven(args.tracking)) {
701
+ IDC.fail('invalid_argument', 'At least one attribute is required: font_family, font_style, point_size, fill_hex, swatch_name or tracking.', null);
702
+ }
703
+ if (existing !== null) {
704
+ if (onCollision === 'error') {
705
+ IDC.fail('invalid_state', 'Character style "' + name + '" exists already. Pass on_collision "update" to change it or "version" to add "' + name + ' 2".',
706
+ { name: name, style_id: (function () { try { return Number(existing.id); } catch (e) { return null; } })() });
707
+ }
708
+ if (onCollision === 'update') {
709
+ style = existing;
710
+ collision = 'updated';
711
+ } else {
712
+ chosenName = idcFreeName(doc, name, 'character');
713
+ collision = 'versioned';
714
+ }
715
+ }
716
+ if (style === null) {
717
+ try {
718
+ style = doc.characterStyles.add({ name: chosenName });
719
+ } catch (e) {
720
+ IDC.fail('script_error', 'InDesign could not create the character style "' + chosenName + '": ' + String(e.message ? e.message : e), { name: chosenName });
721
+ }
722
+ }
723
+ applied = idcApplyStyleAttributesOrRollback(ctx, doc, style, args, createdSwatches, 'character', collision !== 'updated');
724
+
725
+ changes = collision === 'updated'
726
+ ? { modified_style_ids: [Number(style.id)] }
727
+ : { created_style_ids: [Number(style.id)] };
728
+ if (createdSwatches.length > 0) { changes.created_swatch_ids = createdSwatches; }
729
+ ctx.changes = changes;
730
+ return {
731
+ style_id: Number(style.id),
732
+ name: String(style.name),
733
+ collision: collision,
734
+ existed: existing !== null,
735
+ applied: applied,
736
+ definition: idcDescribeCharacterStyle(style)
737
+ };
738
+ });
739
+
740
+ /* --------------------------------------------------------- Zeichenbereiche */
741
+
742
+ /*
743
+ * Zeichenbereich [start_index, end_index) einer Story wie in Bereich G:
744
+ * start einschliesslich, end ausschliesslich, beide aus get_frame_text.
745
+ * Liefert { start, end, text } mit text = characters.itemByRange(start, end - 1).
746
+ */
747
+ function idcRangeArg(story, args, allowEmpty) {
748
+ var start = args.start_index;
749
+ var end = args.end_index;
750
+ var length = 0;
751
+ var text;
752
+ if (typeof start !== 'number' || Math.floor(start) !== start || start < 0) {
753
+ IDC.fail('invalid_argument', 'start_index must be a whole number of 0 or more (inclusive).', { start_index: start });
754
+ }
755
+ if (typeof end !== 'number' || Math.floor(end) !== end || end < start) {
756
+ IDC.fail('invalid_argument', 'end_index must be a whole number not smaller than start_index (exclusive).', { start_index: start, end_index: end });
757
+ }
758
+ try { length = Number(story.characters.length); } catch (e0) { length = 0; }
759
+ if (end > length) {
760
+ IDC.fail('invalid_argument', 'end_index ' + end + ' exceeds the story length ' + length + '.', { end_index: end, story_length: length });
761
+ }
762
+ if (end === start && allowEmpty !== true) {
763
+ IDC.fail('invalid_argument', 'The range is empty (start_index equals end_index); formatting needs at least one character.', { start_index: start, end_index: end });
764
+ }
765
+ try {
766
+ text = story.characters.itemByRange(start, end - 1);
767
+ } catch (e) {
768
+ IDC.fail('script_error', 'InDesign could not address characters ' + start + '–' + end + ': ' + String(e.message ? e.message : e),
769
+ { start_index: start, end_index: end });
770
+ }
771
+ return { start: start, end: end, text: text };
772
+ }
773
+
774
+ /* Erstes Zeichen eines Bereichs (fuer Gegenproben). */
775
+ function idcFirstCharacter(story, index) {
776
+ try { return story.characters.item(index); } catch (e) { return null; }
777
+ }
778
+
779
+ /*
780
+ * Absatzformat sauber zuweisen: Format mit "Abweichungen loeschen", danach
781
+ * Zeichenformat [None], alle lokalen Abweichungen weg, Format noch einmal.
782
+ * So stimmt nicht nur der Formatname, sondern auch der Druck.
783
+ */
784
+ function idcApplyParagraphStyleClean(doc, textObject, style, clearOverrides) {
785
+ var none;
786
+ try {
787
+ textObject.applyParagraphStyle(style, clearOverrides === true);
788
+ } catch (e) {
789
+ textObject.appliedParagraphStyle = style;
790
+ }
791
+ if (clearOverrides !== true) { return; }
792
+ none = idcNoneCharacterStyle(doc);
793
+ if (none !== null) {
794
+ try { textObject.applyCharacterStyle(none); } catch (e1) {
795
+ try { textObject.appliedCharacterStyle = none; } catch (e1b) { /* dann bleibt es */ }
796
+ }
797
+ }
798
+ try {
799
+ if (typeof OverrideType !== 'undefined') { textObject.clearOverrides(OverrideType.ALL); }
800
+ else { textObject.clearOverrides(); }
801
+ } catch (e2) { /* nicht ueberall vorhanden */ }
802
+ try {
803
+ textObject.applyParagraphStyle(style, true);
804
+ } catch (e3) {
805
+ textObject.appliedParagraphStyle = style;
806
+ }
807
+ }
808
+
809
+ function idcParagraphStyleArg(doc, name, field) {
810
+ var style;
811
+ if (typeof name !== 'string' || name.length === 0) {
812
+ IDC.fail('invalid_argument', field + ' is required.', null);
813
+ }
814
+ style = IDC.paragraphStyleByName(doc, name);
815
+ if (style === null) {
816
+ IDC.fail('not_found', 'Paragraph style "' + name + '" not found – list_paragraph_styles shows the names (groups as "Gruppe/Name" are matched by the style name alone).',
817
+ { style_name: name });
818
+ }
819
+ return style;
820
+ }
821
+
822
+ /* Absaetze der Story (als Array), die den Zeichenbereich [start, end) beruehren. */
823
+ function idcStoryParagraphsInRange(story, start, end) {
824
+ var out = [];
825
+ var i, p, position = 0, length;
826
+ try {
827
+ for (i = 0; i < story.paragraphs.length; i++) {
828
+ p = story.paragraphs.item(i);
829
+ try { length = Number(p.characters.length); } catch (e1) { length = 0; }
830
+ if (position < end && position + length > start) { out.push(p); }
831
+ position += length;
832
+ if (position >= end) { break; }
833
+ }
834
+ } catch (e) { /* was da ist */ }
835
+ return out;
836
+ }
837
+
838
+ function idcParagraphStart(paragraph) {
839
+ try { return Number(paragraph.characters.item(0).index); } catch (e) { return null; }
840
+ }
841
+
842
+ function idcParagraphEnd(paragraph) {
843
+ var start = idcParagraphStart(paragraph);
844
+ if (start === null) { return null; }
845
+ try { return start + Number(paragraph.characters.length); } catch (e) { return null; }
846
+ }
847
+
848
+ /* ---------------------------------------------------- apply_paragraph_style */
849
+
850
+ IDC.register('apply_paragraph_style', { needs_document: true, read_only: false, ruler: 'none' },
851
+ function (ctx) {
852
+ var args = ctx.args;
853
+ var doc = ctx.doc;
854
+ var found = idcTextFrameArg(doc, args, 'frame_id');
855
+ var frame = found.item;
856
+ var style = idcParagraphStyleArg(doc, args.style_name, 'style_name');
857
+ var clear = args.clear_overrides !== false;
858
+ var paragraphs, count = 0, i, threaded;
859
+
860
+ try {
861
+ paragraphs = frame.paragraphs;
862
+ count = Number(paragraphs.length);
863
+ } catch (e0) { count = 0; }
864
+ if (count === 0) {
865
+ ctx.warn('The frame holds no text; nothing was formatted.');
866
+ return { frame_id: Number(frame.id), style_applied: String(style.name), affected_paragraphs: 0, overrides_cleared: clear, threaded: idcIsThreaded(frame) };
867
+ }
868
+ try {
869
+ for (i = 0; i < count; i++) {
870
+ idcApplyParagraphStyleClean(doc, paragraphs.item(i), style, clear);
871
+ }
872
+ } catch (e) {
873
+ IDC.fail('script_error', 'InDesign could not apply the paragraph style "' + args.style_name + '" to frame ' + args.frame_id + ': ' + String(e.message ? e.message : e),
874
+ { frame_id: args.frame_id, style_name: args.style_name, paragraphs_done: i });
875
+ }
876
+ threaded = idcIsThreaded(frame);
877
+ if (threaded) {
878
+ ctx.warn('The frame is threaded (verkettet): only the ' + count + ' paragraph(s) touching this frame were formatted – use apply_paragraph_style_to_range over the whole story for the rest.');
879
+ }
880
+ if (!clear) {
881
+ ctx.warn('clear_overrides is false: local character formatting and character styles stay in place – the style name is right, the print may not be.');
882
+ }
883
+ ctx.changes = { modified_frame_ids: [Number(frame.id)] };
884
+ return {
885
+ frame_id: Number(frame.id),
886
+ style_applied: String(style.name),
887
+ affected_paragraphs: count,
888
+ overrides_cleared: clear,
889
+ threaded: threaded
890
+ };
891
+ });
892
+
893
+ /* ------------------------------------------- apply_paragraph_style_to_range */
894
+
895
+ IDC.register('apply_paragraph_style_to_range', { needs_document: true, read_only: false, ruler: 'none' },
896
+ function (ctx) {
897
+ var args = ctx.args;
898
+ var doc = ctx.doc;
899
+ var found = idcTextFrameArg(doc, args, 'frame_id');
900
+ var frame = found.item;
901
+ var story = idcStoryOf(frame);
902
+ var style = idcParagraphStyleArg(doc, args.style_name, 'style_name');
903
+ var range = idcRangeArg(story, args, false);
904
+ var paragraphs, count = 0, i, first, firstStart = null, lastEnd = null, startChar;
905
+
906
+ startChar = idcFirstCharacter(story, range.start);
907
+ try {
908
+ if (startChar !== null && String(startChar.contents).charCodeAt(0) === 13) {
909
+ ctx.warn('start_index ' + range.start + ' is the paragraph mark of the preceding paragraph – that paragraph is formatted too. Start one character later to leave it alone.');
910
+ }
911
+ } catch (e0) { /* egal */ }
912
+
913
+ try {
914
+ paragraphs = range.text.paragraphs;
915
+ count = Number(paragraphs.length);
916
+ } catch (e1) {
917
+ IDC.fail('script_error', 'InDesign could not read the paragraphs of the range: ' + String(e1.message ? e1.message : e1), { start_index: range.start, end_index: range.end });
918
+ }
919
+ try {
920
+ for (i = 0; i < count; i++) {
921
+ idcApplyParagraphStyleClean(doc, paragraphs.item(i), style, true);
922
+ }
923
+ } catch (e) {
924
+ IDC.fail('script_error', 'InDesign could not apply the paragraph style "' + args.style_name + '": ' + String(e.message ? e.message : e),
925
+ { frame_id: args.frame_id, style_name: args.style_name, paragraphs_done: i });
926
+ }
927
+ if (count > 0) {
928
+ first = paragraphs.item(0);
929
+ firstStart = idcParagraphStart(first);
930
+ lastEnd = idcParagraphEnd(paragraphs.item(count - 1));
931
+ }
932
+ ctx.changes = { modified_frame_ids: [Number(frame.id)] };
933
+ return {
934
+ frame_id: Number(frame.id),
935
+ story_id: (function () { try { return Number(story.id); } catch (e2) { return null; } })(),
936
+ style_applied: String(style.name),
937
+ affected_paragraphs: count,
938
+ start_index: range.start,
939
+ end_index: range.end,
940
+ first_paragraph_start_index: firstStart,
941
+ last_paragraph_end_index: lastEnd,
942
+ overrides_cleared: true
943
+ };
944
+ });
945
+
946
+ /* ------------------------------------------- apply_character_style_to_range */
947
+
948
+ IDC.register('apply_character_style_to_range', { needs_document: true, read_only: false, ruler: 'none' },
949
+ function (ctx) {
950
+ var args = ctx.args;
951
+ var doc = ctx.doc;
952
+ var found = idcTextFrameArg(doc, args, 'frame_id');
953
+ var frame = found.item;
954
+ var story = idcStoryOf(frame);
955
+ var name = args.character_style_name;
956
+ var style, range;
957
+
958
+ if (typeof name !== 'string' || name.length === 0) {
959
+ IDC.fail('invalid_argument', 'character_style_name is required.', null);
960
+ }
961
+ style = idcCharacterStyleByName(doc, name);
962
+ if (style === null) {
963
+ IDC.fail('not_found', 'Character style "' + name + '" not found – define it with define_character_style first; list_paragraph_styles lists the existing ones.',
964
+ { character_style_name: name });
965
+ }
966
+ range = idcRangeArg(story, args, false);
967
+ try {
968
+ range.text.applyCharacterStyle(style);
969
+ } catch (e) {
970
+ try { range.text.appliedCharacterStyle = style; } catch (e2) {
971
+ IDC.fail('script_error', 'InDesign could not apply the character style "' + name + '": ' + String(e.message ? e.message : e), { character_style_name: name });
972
+ }
973
+ }
974
+ /*
975
+ * Demo-Heft-Befund 9 (Sitzung 10): die Laufweite des Zeichenformats ersetzt
976
+ * die des Absatzformats – die wirksame Laufweite am ersten Zeichen zurueckgeben.
977
+ */
978
+ var styleTracking = idcReadNumberLoose(style, 'tracking', 1);
979
+ var effective = null, paragraphTracking = null, firstChar = null, paragraphStyle = null;
980
+ try { firstChar = idcFirstCharacter(story, range.start); } catch (e3) { firstChar = null; }
981
+ if (firstChar !== null) {
982
+ effective = idcReadNumberLoose(firstChar, 'tracking', 1);
983
+ try { paragraphStyle = firstChar.appliedParagraphStyle; } catch (e4) { paragraphStyle = null; }
984
+ if (paragraphStyle !== null && paragraphStyle !== undefined) { paragraphTracking = idcReadNumberLoose(paragraphStyle, 'tracking', 1); }
985
+ }
986
+ ctx.changes = { modified_frame_ids: [Number(frame.id)] };
987
+ return {
988
+ frame_id: Number(frame.id),
989
+ characters_affected: range.end - range.start,
990
+ style_applied: String(style.name),
991
+ start_index: range.start,
992
+ end_index: range.end,
993
+ tracking: { effective: effective, character_style: styleTracking, paragraph_style: paragraphTracking }
994
+ };
995
+ });
996
+
997
+ /* ------------------------------------------ apply_character_format_to_range */
998
+
999
+ IDC.register('apply_character_format_to_range', { needs_document: true, read_only: false, ruler: 'none' },
1000
+ function (ctx) {
1001
+ var args = ctx.args;
1002
+ var doc = ctx.doc;
1003
+ var found = idcTextFrameArg(doc, args, 'frame_id');
1004
+ var frame = found.item;
1005
+ var story = idcStoryOf(frame);
1006
+ var applied = [];
1007
+ var range, catalogue, swatch, first, font, sizeApplied = null, fill = null;
1008
+
1009
+ if (!idcGiven(args.font_family) && !idcGiven(args.font_style) && !idcGiven(args.swatch_name) && !idcGiven(args.superscript) &&
1010
+ !idcGiven(args.tracking) && !idcGiven(args.point_size)) {
1011
+ IDC.fail('invalid_argument', 'At least one attribute is required: font_style, swatch_name, superscript, tracking or point_size.', null);
1012
+ }
1013
+ range = idcRangeArg(story, args, false);
1014
+
1015
+ if (idcGiven(args.font_family) || idcGiven(args.font_style)) {
1016
+ catalogue = idcFontCatalogue();
1017
+ applied = applied.concat(idcApplyFont(ctx, range.text, args.font_family, args.font_style, catalogue, idcFirstCharacter(story, range.start)));
1018
+ }
1019
+ if (idcGiven(args.swatch_name)) {
1020
+ swatch = idcSwatchByName(doc, args.swatch_name);
1021
+ if (!idcPresent(swatch)) {
1022
+ IDC.fail('not_found', 'Swatch "' + args.swatch_name + '" not found – create it with create_swatch (names are exact; the built-in ones are "Black", "Paper", "None").',
1023
+ { swatch_name: args.swatch_name });
1024
+ }
1025
+ try { range.text.fillColor = swatch; } catch (e1) {
1026
+ IDC.fail('script_error', 'InDesign could not set the fill colour: ' + String(e1.message ? e1.message : e1), { swatch_name: args.swatch_name });
1027
+ }
1028
+ applied.push('swatch_name');
1029
+ }
1030
+ if (idcGiven(args.superscript)) {
1031
+ if (typeof args.superscript !== 'boolean') { IDC.fail('invalid_argument', 'superscript must be true or false.', { value: args.superscript }); }
1032
+ if (typeof Position === 'undefined') { IDC.fail('invalid_state', 'This runtime has no Position enumeration.', null); }
1033
+ try { range.text.position = args.superscript ? Position.SUPERSCRIPT : Position.NORMAL; } catch (e2) {
1034
+ IDC.fail('script_error', 'InDesign could not set the superscript position: ' + String(e2.message ? e2.message : e2), null);
1035
+ }
1036
+ applied.push('superscript');
1037
+ }
1038
+ if (idcGiven(args.tracking)) {
1039
+ idcNumberArg(args.tracking, 'tracking', -1000, 10000);
1040
+ /* Toleranz nur aus der Serverkonfiguration (tracking_warn_beyond, E-54); ohne Eintrag keine Warnung. */
1041
+ if (typeof args.tracking_warn_beyond === 'number' && isFinite(args.tracking_warn_beyond)
1042
+ && Math.abs(args.tracking) > args.tracking_warn_beyond) {
1043
+ ctx.warn('tracking ' + args.tracking + ' is beyond the configured tolerance of \u00b1' + args.tracking_warn_beyond + ' (tracking_warn_beyond).');
1044
+ }
1045
+ try { range.text.tracking = args.tracking; } catch (e3) {
1046
+ IDC.fail('script_error', 'InDesign could not set the tracking: ' + String(e3.message ? e3.message : e3), null);
1047
+ }
1048
+ applied.push('tracking');
1049
+ }
1050
+ if (idcGiven(args.point_size)) {
1051
+ idcNumberArg(args.point_size, 'point_size', 0.1, 1296);
1052
+ try { IDC.inPoints(function () { range.text.pointSize = args.point_size; }); } catch (e4) {
1053
+ IDC.fail('script_error', 'InDesign could not set the point size: ' + String(e4.message ? e4.message : e4), null);
1054
+ }
1055
+ applied.push('point_size');
1056
+ }
1057
+
1058
+ first = idcFirstCharacter(story, range.start);
1059
+ if (first !== null) {
1060
+ sizeApplied = idcReadPt(first, 'pointSize');
1061
+ font = idcReadFontLoose(first);
1062
+ fill = idcReadSwatchName(first, 'fillColor');
1063
+ } else {
1064
+ font = { family: null, style: null };
1065
+ }
1066
+ ctx.changes = { modified_frame_ids: [Number(frame.id)] };
1067
+ return {
1068
+ frame_id: Number(frame.id),
1069
+ start_index: range.start,
1070
+ end_index: range.end,
1071
+ characters_affected: range.end - range.start,
1072
+ applied: applied,
1073
+ point_size_applied: sizeApplied,
1074
+ font_family: font.family,
1075
+ font_style: font.style,
1076
+ fill_color: fill
1077
+ };
1078
+ });
1079
+
1080
+ /* ----------------------------------------------------- set_paragraph_options */
1081
+
1082
+ IDC.register('set_paragraph_options', { needs_document: true, read_only: false, ruler: 'none' },
1083
+ function (ctx) {
1084
+ var args = ctx.args;
1085
+ var doc = ctx.doc;
1086
+ var found = idcTextFrameArg(doc, args, 'frame_id');
1087
+ var frame = found.item;
1088
+ var story = idcStoryOf(frame);
1089
+ var changed = [];
1090
+ var justification = null;
1091
+ var paragraphs, count = 0, i, p, range = null, first;
1092
+
1093
+ if (!idcGiven(args.alignment) && !idcGiven(args.hyphenation) && !idcGiven(args.no_break) && !idcCompositionGiven(args) && !idcIndentTabLanguageGiven(args)) {
1094
+ IDC.fail('invalid_argument', 'At least one of alignment, hyphenation, no_break, keep_options, hyphenation_options, justification_options, left_indent_mm, first_line_indent_mm, right_indent_mm, last_line_indent_mm, tab_stops or language is required.', null);
1095
+ }
1096
+ if (idcGiven(args.alignment)) {
1097
+ justification = idcJustificationFor(args.alignment, 'alignment');
1098
+ if (String(args.alignment) === 'justify') {
1099
+ IDC.fail('invalid_argument', 'alignment must name the last line: justify_left, justify_center, justify_right or justify_full.', { value: args.alignment });
1100
+ }
1101
+ }
1102
+ if (idcGiven(args.hyphenation) && typeof args.hyphenation !== 'boolean') {
1103
+ IDC.fail('invalid_argument', 'hyphenation must be true or false.', { value: args.hyphenation });
1104
+ }
1105
+ if (idcGiven(args.no_break) && typeof args.no_break !== 'boolean') {
1106
+ IDC.fail('invalid_argument', 'no_break must be true or false.', { value: args.no_break });
1107
+ }
1108
+
1109
+ if (idcGiven(args.start_index) || idcGiven(args.end_index)) {
1110
+ range = idcRangeArg(story, args, false);
1111
+ /*
1112
+ * Nicht range.text.paragraphs: an deren Absaetzen bleibt hyphenation
1113
+ * wirkungslos (Live-Fund 04.09.2026, justification greift). Die
1114
+ * Absaetze der Story, die den Bereich beruehren, verhalten sich wie
1115
+ * frame.paragraphs.
1116
+ */
1117
+ paragraphs = idcStoryParagraphsInRange(story, range.start, range.end);
1118
+ count = paragraphs.length;
1119
+ } else {
1120
+ /*
1121
+ * Nicht frame.paragraphs waehrend der Schleife: Umbruchoptionen schieben
1122
+ * Absaetze in die naechste Spalte oder den Uebersatz, die Rahmensammlung
1123
+ * schrumpft und item(i) wird "Object is invalid" (Live-Fund 05.09.2026).
1124
+ * Deshalb die Absaetze der Story nehmen, die den Rahmenanteil beruehren –
1125
+ * die bleiben gueltig, wohin der Text auch laeuft.
1126
+ */
1127
+ var frameStart = null, frameLength = 0;
1128
+ try { frameLength = Number(frame.characters.length); } catch (e1) { frameLength = 0; }
1129
+ try { if (frameLength > 0) { frameStart = Number(frame.characters.item(0).index); } } catch (e1b) { frameStart = null; }
1130
+ if (frameStart !== null && frameLength > 0) {
1131
+ paragraphs = idcStoryParagraphsInRange(story, frameStart, frameStart + frameLength);
1132
+ count = paragraphs.length;
1133
+ range = { start: frameStart, end: frameStart + frameLength, whole_frame: true };
1134
+ } else {
1135
+ count = 0;
1136
+ }
1137
+ }
1138
+ if (count === 0) {
1139
+ ctx.warn('No paragraphs in the given scope; nothing was changed.');
1140
+ return { frame_id: Number(frame.id), paragraphs_changed: 0, alignment: null, hyphenation: null, no_break: null, changed: [],
1141
+ keep_options: null, hyphenation_options: null, justification_options: null, indents: null, overset: null };
1142
+ }
1143
+ /* Die Optionsgruppen zuerst am ersten Absatz pruefen (Fehler vor jeder Aenderung). */
1144
+ var groups = [];
1145
+ var indentFields = [];
1146
+ try {
1147
+ for (i = 0; i < count; i++) {
1148
+ p = paragraphs[i];
1149
+ if (justification !== null) { p.justification = justification; }
1150
+ if (idcGiven(args.hyphenation)) { p.hyphenation = args.hyphenation; }
1151
+ if (idcGiven(args.no_break)) { p.noBreak = args.no_break; }
1152
+ groups = idcApplyCompositionOptions(p, args);
1153
+ indentFields = idcApplyIndentTabLanguage(doc, p, args);
1154
+ }
1155
+ } catch (e) {
1156
+ if (e && e.isIdcError) { throw e; }
1157
+ IDC.fail('script_error', 'InDesign could not change the paragraph options: ' + String(e.message ? e.message : e), { frame_id: args.frame_id, paragraphs_done: i });
1158
+ }
1159
+ if (justification !== null) { changed.push('alignment'); }
1160
+ if (idcGiven(args.hyphenation)) { changed.push('hyphenation'); }
1161
+ if (idcGiven(args.no_break)) { changed.push('no_break'); }
1162
+ changed = changed.concat(groups).concat(indentFields);
1163
+
1164
+ first = paragraphs[0];
1165
+ var composition = idcReadCompositionOptions(first);
1166
+ var overset = null;
1167
+ try { overset = story.overflows === true; } catch (e3) { overset = null; }
1168
+ if (overset === true && groups.length > 0) {
1169
+ ctx.warn('The story overflows after the change – strict keep or justification options can push text out; check get_frame_lines.');
1170
+ }
1171
+ ctx.changes = { modified_frame_ids: [Number(frame.id)] };
1172
+ return {
1173
+ frame_id: Number(frame.id),
1174
+ paragraphs_changed: count,
1175
+ alignment: (function () { try { return idcJustificationName(first.justification); } catch (e2) { return null; } })(),
1176
+ hyphenation: idcReadBoolLoose(first, 'hyphenation'),
1177
+ no_break: idcReadBoolLoose(first, 'noBreak'),
1178
+ changed: changed,
1179
+ scope: range.whole_frame === true ? 'frame' : 'range',
1180
+ start_index: range.whole_frame === true ? null : range.start,
1181
+ end_index: range.whole_frame === true ? null : range.end,
1182
+ keep_options: composition.keep_options,
1183
+ hyphenation_options: composition.hyphenation_options,
1184
+ justification_options: composition.justification_options,
1185
+ indents: idcReadIndentTabLanguage(first),
1186
+ overset: overset
1187
+ };
1188
+ });
1189
+
1190
+ /* ---------------------------------------------------- get_font_info_at_index */
1191
+
1192
+ IDC.register('get_font_info_at_index', { needs_document: true, read_only: true, ruler: 'none' },
1193
+ function (ctx) {
1194
+ var args = ctx.args;
1195
+ var doc = ctx.doc;
1196
+ var found = idcTextFrameArg(doc, args, 'frame_id');
1197
+ var frame = found.item;
1198
+ var story = idcStoryOf(frame);
1199
+ var index = args.index;
1200
+ var length = 0;
1201
+ var character, font, catalogue, status, text = null;
1202
+
1203
+ if (typeof index !== 'number' || Math.floor(index) !== index || index < 0) {
1204
+ IDC.fail('invalid_argument', 'index must be a whole number of 0 or more (a story index from get_frame_text).', { index: index });
1205
+ }
1206
+ try { length = Number(story.characters.length); } catch (e0) { length = 0; }
1207
+ if (index >= length) {
1208
+ IDC.fail('invalid_argument', 'index ' + index + ' is beyond the story length ' + length + '.', { index: index, story_length: length });
1209
+ }
1210
+ character = idcFirstCharacter(story, index);
1211
+ if (character === null) {
1212
+ IDC.fail('script_error', 'InDesign could not address character ' + index + '.', { index: index });
1213
+ }
1214
+ font = idcReadFontLoose(character);
1215
+ try { text = String(character.contents); } catch (e1) { text = null; }
1216
+ catalogue = idcFontCatalogue();
1217
+ status = font.family === null ? null : idcCatalogueStatus(catalogue, font.family, font.style);
1218
+ if (status === null && font.family !== null) {
1219
+ status = idcDocumentFontStatuses(doc)[font.family + '\t' + font.style] || 'not_available';
1220
+ }
1221
+ return {
1222
+ frame_id: Number(frame.id),
1223
+ index: index,
1224
+ character: text,
1225
+ font_family: font.family,
1226
+ font_style: font.style,
1227
+ font_status: status,
1228
+ available_styles: font.family === null ? [] : idcStylesOfFamily(catalogue, font.family),
1229
+ point_size: idcReadPt(character, 'pointSize'),
1230
+ paragraph_style: idcReadSwatchName(character, 'appliedParagraphStyle'),
1231
+ character_style: idcReadSwatchName(character, 'appliedCharacterStyle'),
1232
+ character_style_is_none: (function () { try { return idcIsNoneCharacterStyle(doc, character.appliedCharacterStyle); } catch (e2) { return null; } })()
1233
+ };
1234
+ });
1235
+
1236
+ /* ----------------------------------------------------------- find_font_usage */
1237
+
1238
+ /* Rahmen eines Textlaufs: IDs und Seite des ersten Rahmens. */
1239
+ function idcRunFrames(textObject) {
1240
+ var out = { frame_ids: [], page_id: null, page_name: '', is_master: false };
1241
+ var frames, i, frame, landing;
1242
+ try {
1243
+ frames = textObject.parentTextFrames;
1244
+ for (i = 0; i < frames.length; i++) {
1245
+ frame = IDC.concrete(frames[i]);
1246
+ out.frame_ids.push(Number(frame.id));
1247
+ if (i === 0) {
1248
+ landing = IDC.landing(frame);
1249
+ out.page_id = landing.page_id;
1250
+ out.page_name = landing.page_name;
1251
+ out.is_master = IDC.isOnParentSpread(frame);
1252
+ }
1253
+ }
1254
+ } catch (e) { /* Uebersatz ohne Rahmen */ }
1255
+ return out;
1256
+ }
1257
+
1258
+ /* Liegt eine Story (ueber ihren ersten Rahmen) auf einer Musterseite? */
1259
+ function idcStoryOnMaster(story) {
1260
+ var containers, first;
1261
+ try {
1262
+ containers = story.textContainers;
1263
+ if (containers && containers.length > 0) {
1264
+ first = IDC.concrete(containers[0]);
1265
+ return IDC.isOnParentSpread(first);
1266
+ }
1267
+ } catch (e) { /* unbekannt */ }
1268
+ return false;
1269
+ }
1270
+
1271
+ function idcSnippet(text, max) {
1272
+ /* Absatz-/Zeilenwechsel, Sonderzeichen und das Tabellenzeichen U+0016 (live 05.09.2026) werden Leerzeichen. */
1273
+ var s = String(text).replace(/[\r\n\u000e\u000f\u0010\u0011\u0012\u0016\u0007\u0008]/g, ' ');
1274
+ if (s.length > max) { s = s.substr(0, max) + '...'; }
1275
+ return s.replace(/\s+$/, '');
1276
+ }
1277
+
1278
+ function idcMergeIds(target, extra) {
1279
+ var i, j, seen;
1280
+ for (i = 0; i < extra.length; i++) {
1281
+ seen = false;
1282
+ for (j = 0; j < target.length; j++) { if (target[j] === extra[i]) { seen = true; break; } }
1283
+ if (!seen) { target.push(extra[i]); }
1284
+ }
1285
+ }
1286
+
1287
+ IDC.register('find_font_usage', { needs_document: true, read_only: true, ruler: 'none' },
1288
+ function (ctx) {
1289
+ var args = ctx.args;
1290
+ var doc = ctx.doc;
1291
+ var wantFamily = idcGiven(args.font_family) ? String(args.font_family) : null;
1292
+ var wantStyle = idcGiven(args.font_style) ? String(args.font_style) : null;
1293
+ var onlyMissing = args.only_missing === true;
1294
+ var includeMasters = args.include_master_pages !== false;
1295
+ var maxOccurrences = 500;
1296
+ var docStatuses = idcDocumentFontStatuses(doc);
1297
+ var catalogue = null;
1298
+ var used = {};
1299
+ var usedOrder = [];
1300
+ var occurrences = [];
1301
+ var totalMatching = 0;
1302
+ var storyCount = 0, s, story, storyId, onMaster, ranges, r, run, runs, range, font, key, start, length, frames, snippet, i, entry;
1303
+
1304
+ function statusOf(family, style) {
1305
+ var name = family + '\t' + style;
1306
+ var status = docStatuses[name];
1307
+ if (status !== undefined) { return status; }
1308
+ if (catalogue === null) { catalogue = idcFontCatalogue(); }
1309
+ status = idcCatalogueStatus(catalogue, family, style);
1310
+ return status === null ? 'not_available' : status;
1311
+ }
1312
+
1313
+ function matches(runEntry, status) {
1314
+ if (wantFamily !== null && !idcSameFamily(runEntry.family, wantFamily)) { return false; }
1315
+ if (wantStyle !== null && !idcSameText(runEntry.style, wantStyle)) { return false; }
1316
+ if (onlyMissing && status === 'installed') { return false; }
1317
+ return true;
1318
+ }
1319
+
1320
+ try { storyCount = Number(doc.stories.length); } catch (e0) {
1321
+ IDC.fail('script_error', 'InDesign could not list the stories of the document.', null);
1322
+ }
1323
+
1324
+ for (s = 0; s < storyCount; s++) {
1325
+ story = doc.stories.item(s);
1326
+ try { storyId = Number(story.id); } catch (e1) { storyId = null; }
1327
+ onMaster = idcStoryOnMaster(story);
1328
+ if (onMaster && !includeMasters) { continue; }
1329
+ runs = [];
1330
+ try {
1331
+ ranges = story.textStyleRanges;
1332
+ for (r = 0; r < ranges.length; r++) {
1333
+ range = ranges.item(r);
1334
+ font = idcReadFontLoose(range);
1335
+ if (font.family === null) { continue; }
1336
+ key = font.family + '\t' + (font.style === null ? '' : font.style);
1337
+ try { start = Number(range.index); } catch (e2) { start = null; }
1338
+ try { length = Number(range.characters.length); } catch (e3) { length = 0; }
1339
+ if (start === null) {
1340
+ try { start = Number(range.characters.item(0).index); } catch (e3b) { start = 0; }
1341
+ }
1342
+ frames = idcRunFrames(range);
1343
+ if (runs.length > 0 && runs[runs.length - 1].key === key && runs[runs.length - 1].end === start) {
1344
+ run = runs[runs.length - 1];
1345
+ run.end = start + length;
1346
+ idcMergeIds(run.frame_ids, frames.frame_ids);
1347
+ if (run.page_id === null) { run.page_id = frames.page_id; run.page_name = frames.page_name; }
1348
+ if (run.raw.length < 60) {
1349
+ try { run.raw += String(range.contents); } catch (e4b) { /* bleibt */ }
1350
+ }
1351
+ } else {
1352
+ try { snippet = String(range.contents); } catch (e4) { snippet = ''; }
1353
+ runs.push({
1354
+ key: key, family: font.family, style: font.style === null ? '' : font.style,
1355
+ start: start, end: start + length, raw: snippet,
1356
+ frame_ids: frames.frame_ids, page_id: frames.page_id, page_name: frames.page_name,
1357
+ is_master: onMaster || frames.is_master
1358
+ });
1359
+ }
1360
+ }
1361
+ } catch (e5) {
1362
+ ctx.warn('Story ' + storyId + ' could not be read completely: ' + String(e5.message ? e5.message : e5));
1363
+ }
1364
+ for (i = 0; i < runs.length; i++) {
1365
+ run = runs[i];
1366
+ entry = used[run.key];
1367
+ if (entry === undefined) {
1368
+ entry = { family: run.family, style: run.style, status: statusOf(run.family, run.style), occurrences: 0, characters: 0 };
1369
+ used[run.key] = entry;
1370
+ usedOrder.push(run.key);
1371
+ }
1372
+ entry.occurrences += 1;
1373
+ entry.characters += run.end - run.start;
1374
+ if (matches(run, entry.status)) {
1375
+ totalMatching += 1;
1376
+ if (occurrences.length < maxOccurrences) {
1377
+ occurrences.push({
1378
+ family: run.family, style: run.style, status: entry.status,
1379
+ story_id: storyId, frame_ids: run.frame_ids, page_id: run.page_id, page_name: run.page_name,
1380
+ is_master: run.is_master, start_index: run.start, end_index: run.end, snippet: idcSnippet(run.raw, 60)
1381
+ });
1382
+ }
1383
+ }
1384
+ }
1385
+ }
1386
+
1387
+ var fontsUsed = [];
1388
+ for (i = 0; i < usedOrder.length; i++) { fontsUsed.push(used[usedOrder[i]]); }
1389
+ fontsUsed.sort(function (a, b) {
1390
+ var ka = (a.family + '\t' + a.style).toLowerCase();
1391
+ var kb = (b.family + '\t' + b.style).toLowerCase();
1392
+ return ka < kb ? -1 : (ka > kb ? 1 : 0);
1393
+ });
1394
+ if (totalMatching > maxOccurrences) {
1395
+ ctx.warn('Only the first ' + maxOccurrences + ' of ' + totalMatching + ' matching occurrences are listed.');
1396
+ }
1397
+ return {
1398
+ fonts_used: fontsUsed,
1399
+ occurrences: occurrences,
1400
+ occurrence_count: totalMatching,
1401
+ stories_scanned: storyCount,
1402
+ filter: { font_family: wantFamily, font_style: wantStyle, only_missing: onlyMissing, include_master_pages: includeMasters }
1403
+ };
1404
+ });
1405
+
1406
+ /* ----------------------------------------------------------- debug_inspect_run */
1407
+
1408
+ function idcFrameScale(frame) {
1409
+ var h = null, v = null;
1410
+ try { h = IDC.round(Number(frame.absoluteHorizontalScale), 3); } catch (e1) { h = null; }
1411
+ try { v = IDC.round(Number(frame.absoluteVerticalScale), 3); } catch (e2) { v = null; }
1412
+ if (h === null) { try { h = IDC.round(Number(frame.horizontalScale), 3); } catch (e3) { h = null; } }
1413
+ if (v === null) { try { v = IDC.round(Number(frame.verticalScale), 3); } catch (e4) { v = null; } }
1414
+ return { horizontal: h, vertical: v };
1415
+ }
1416
+
1417
+ /* Absatz des Zeichens mit Umbruch-, Trennungs- und Blocksatzoptionen (A-7). */
1418
+ function idcInspectParagraph(character) {
1419
+ var paragraph = null;
1420
+ var out;
1421
+ try { paragraph = character.paragraphs.item(0); } catch (e) { paragraph = null; }
1422
+ if (paragraph === null || paragraph === undefined) { return null; }
1423
+ out = idcReadCompositionOptions(paragraph);
1424
+ /* Live 05.09.2026: Paragraph.index ist der Zeichenindex des Absatzanfangs, keine Ordnungszahl – deshalb beides ausdruecklich. */
1425
+ out.start_index = idcParagraphStart(paragraph);
1426
+ out.index = null;
1427
+ try {
1428
+ var story = paragraph.parentStory, ordinal, probe;
1429
+ for (ordinal = 0; ordinal < story.paragraphs.length; ordinal++) {
1430
+ probe = idcParagraphStart(story.paragraphs.item(ordinal));
1431
+ if (probe === out.start_index) { out.index = ordinal; break; }
1432
+ if (probe !== null && probe > out.start_index) { break; }
1433
+ }
1434
+ } catch (e2) { out.index = null; }
1435
+ try { out.alignment = idcJustificationName(paragraph.justification); } catch (e3) { out.alignment = null; }
1436
+ out.hyphenation = idcReadBoolLoose(paragraph, 'hyphenation');
1437
+ return out;
1438
+ }
1439
+
1440
+ IDC.register('debug_inspect_run', { needs_document: true, read_only: true, ruler: 'none' },
1441
+ function (ctx) {
1442
+ var args = ctx.args;
1443
+ var doc = ctx.doc;
1444
+ var found = idcTextFrameArg(doc, args, 'frame_id');
1445
+ var frame = found.item;
1446
+ var story = idcStoryOf(frame);
1447
+ var index = idcGiven(args.index) ? args.index : 0;
1448
+ var length = 0;
1449
+ var character, font, out, styleInfo = null, all, i, name, hits, kind;
1450
+
1451
+ if (typeof index !== 'number' || Math.floor(index) !== index || index < 0) {
1452
+ IDC.fail('invalid_argument', 'index must be a whole number of 0 or more.', { index: index });
1453
+ }
1454
+ try { length = Number(story.characters.length); } catch (e0) { length = 0; }
1455
+ if (length === 0) {
1456
+ IDC.fail('invalid_state', 'The frame holds no text; there is no character to inspect.', { frame_id: args.frame_id });
1457
+ }
1458
+ if (index >= length) {
1459
+ IDC.fail('invalid_argument', 'index ' + index + ' is beyond the story length ' + length + '.', { index: index, story_length: length });
1460
+ }
1461
+ character = idcFirstCharacter(story, index);
1462
+ font = idcReadFontLoose(character);
1463
+ out = {
1464
+ frame_id: Number(frame.id),
1465
+ index: index,
1466
+ character: (function () { try { return String(character.contents); } catch (e1) { return null; } })(),
1467
+ point_size: idcReadPt(character, 'pointSize'),
1468
+ leading_pt: idcReadLeading(character).pt,
1469
+ leading_auto: idcReadLeading(character).auto,
1470
+ applied_paragraph_style: idcReadSwatchName(character, 'appliedParagraphStyle'),
1471
+ applied_character_style: idcReadSwatchName(character, 'appliedCharacterStyle'),
1472
+ character_style_is_none: (function () { try { return idcIsNoneCharacterStyle(doc, character.appliedCharacterStyle); } catch (e1b) { return null; } })(),
1473
+ fill_color: idcReadSwatchName(character, 'fillColor'),
1474
+ applied_font: font.family,
1475
+ font_style: font.style,
1476
+ tracking: idcReadNumberLoose(character, 'tracking', 1),
1477
+ horizontal_scale: idcReadNumberLoose(character, 'horizontalScale', 3),
1478
+ vertical_scale: idcReadNumberLoose(character, 'verticalScale', 3),
1479
+ style_overridden: idcReadBoolLoose(character, 'styleOverridden'),
1480
+ frame_scale: idcFrameScale(frame),
1481
+ paragraph: idcInspectParagraph(character)
1482
+ };
1483
+
1484
+ if (idcGiven(args.style_name)) {
1485
+ name = String(args.style_name);
1486
+ hits = [];
1487
+ kind = null;
1488
+ all = idcAllStyles(doc, 'paragraph');
1489
+ for (i = 0; i < all.length; i++) {
1490
+ try { if (String(all[i].name) === name) { hits.push(all[i]); } } catch (e2) { /* weiter */ }
1491
+ }
1492
+ if (hits.length > 0) { kind = 'paragraph'; }
1493
+ else {
1494
+ all = idcAllStyles(doc, 'character');
1495
+ for (i = 0; i < all.length; i++) {
1496
+ try { if (String(all[i].name) === name) { hits.push(all[i]); } } catch (e3) { /* weiter */ }
1497
+ }
1498
+ if (hits.length > 0) { kind = 'character'; }
1499
+ }
1500
+ if (hits.length === 0) {
1501
+ styleInfo = { name: name, found: false, kind: null, matches: 0, duplicated: false, nested: false, definition: null, groups: [] };
1502
+ } else {
1503
+ var groups = [];
1504
+ for (i = 0; i < hits.length; i++) { groups.push(idcStyleGroupPath(hits[i])); }
1505
+ styleInfo = {
1506
+ name: name,
1507
+ found: true,
1508
+ kind: kind,
1509
+ matches: hits.length,
1510
+ duplicated: hits.length > 1,
1511
+ nested: groups[0] !== '',
1512
+ groups: groups,
1513
+ definition: kind === 'paragraph' ? idcDescribeParagraphStyle(hits[0]) : idcDescribeCharacterStyle(hits[0])
1514
+ };
1515
+ if (hits.length > 1) {
1516
+ ctx.warn('The style name "' + name + '" occurs ' + hits.length + ' times (in groups: ' + groups.join(', ') + ') – apply by name may pick the wrong one.');
1517
+ }
1518
+ }
1519
+ out.style = styleInfo;
1520
+ }
1521
+ return out;
1522
+ });
1523
+
1524
+ /* --------------------------------------------------------------- create_swatch */
1525
+
1526
+ function idcCmykArg(spec) {
1527
+ var keys = ['c', 'm', 'y', 'k'];
1528
+ var out = [];
1529
+ var i, v;
1530
+ if (typeof spec !== 'object' || spec === null || IDC.isArray(spec)) {
1531
+ IDC.fail('invalid_argument', 'cmyk must be an object { c, m, y, k } with values 0–100.', { value: spec });
1532
+ }
1533
+ for (i = 0; i < keys.length; i++) {
1534
+ v = spec[keys[i]];
1535
+ if (typeof v !== 'number' || !isFinite(v) || v < 0 || v > 100) {
1536
+ IDC.fail('invalid_argument', 'cmyk.' + keys[i] + ' must be a number from 0 to 100.', { value: v });
1537
+ }
1538
+ out.push(v);
1539
+ }
1540
+ return out;
1541
+ }
1542
+
1543
+ IDC.register('create_swatch', { needs_document: true, read_only: false, ruler: 'none' },
1544
+ function (ctx) {
1545
+ var args = ctx.args;
1546
+ var doc = ctx.doc;
1547
+ var name = args.name;
1548
+ var onCollision = idcCollisionArg(args.on_collision);
1549
+ var hasHex = idcGiven(args.hex);
1550
+ var hasCmyk = idcGiven(args.cmyk);
1551
+ var values, space, spaceName, existing, collision = 'none', chosenName, swatch = null, className;
1552
+ var haveSwatch = false, existed = false;
1553
+
1554
+ if (typeof name !== 'string' || name.length < 1 || name.length > 60) {
1555
+ IDC.fail('invalid_argument', 'name is required (1–60 characters).', { name: name });
1556
+ }
1557
+ if (name.charAt(0) === '[') {
1558
+ IDC.fail('invalid_argument', 'name must not start with "[" – those names are reserved for InDesign\'s built-in swatches.', { name: name });
1559
+ }
1560
+ if (hasHex === hasCmyk) {
1561
+ IDC.fail('invalid_argument', 'Pass exactly one of hex (RGB, e.g. "#1A2B3C") or cmyk ({ c, m, y, k } in percent).', null);
1562
+ }
1563
+ if (hasHex) {
1564
+ var rgb = idcParseHex(args.hex, 'hex');
1565
+ values = [rgb.r, rgb.g, rgb.b];
1566
+ space = ColorSpace.RGB;
1567
+ spaceName = 'rgb';
1568
+ } else {
1569
+ values = idcCmykArg(args.cmyk);
1570
+ space = ColorSpace.CMYK;
1571
+ spaceName = 'cmyk';
1572
+ }
1573
+ chosenName = name;
1574
+ existing = idcSwatchByName(doc, name);
1575
+ existed = idcPresent(existing);
1576
+ if (existed) {
1577
+ if (onCollision === 'error') {
1578
+ IDC.fail('invalid_state', 'Swatch "' + name + '" exists already. Pass on_collision "update" to change it or "version" to add "' + name + ' 2".',
1579
+ { name: name, swatch_id: (function () { try { return Number(existing.id); } catch (e) { return null; } })() });
1580
+ }
1581
+ if (onCollision === 'update') {
1582
+ className = IDC.className(existing);
1583
+ if (className !== 'Color' && className !== 'Swatch') {
1584
+ IDC.fail('invalid_state', 'Swatch "' + name + '" is a ' + className + ', not a colour; it cannot be updated with values.', { name: name, type: className });
1585
+ }
1586
+ swatch = existing;
1587
+ haveSwatch = true;
1588
+ collision = 'updated';
1589
+ try {
1590
+ swatch.model = ColorModel.PROCESS;
1591
+ swatch.space = space;
1592
+ swatch.colorValue = values;
1593
+ } catch (e1) {
1594
+ IDC.fail('script_error', 'InDesign could not update the swatch "' + name + '": ' + String(e1.message ? e1.message : e1), { name: name });
1595
+ }
1596
+ } else {
1597
+ chosenName = idcFreeName(doc, name, 'swatch');
1598
+ collision = 'versioned';
1599
+ }
1600
+ }
1601
+ if (!haveSwatch) {
1602
+ try {
1603
+ /* Colors.add nimmt nur withProperties (Live-Fund Sitzung 3). */
1604
+ swatch = doc.colors.add({ name: chosenName, model: ColorModel.PROCESS, space: space, colorValue: values });
1605
+ } catch (e2) {
1606
+ IDC.fail('script_error', 'InDesign could not create the swatch "' + chosenName + '": ' + String(e2.message ? e2.message : e2), { name: chosenName });
1607
+ }
1608
+ }
1609
+ ctx.changes = collision === 'updated'
1610
+ ? { modified_swatch_ids: [Number(swatch.id)] }
1611
+ : { created_swatch_ids: [Number(swatch.id)] };
1612
+ return {
1613
+ name: String(swatch.name),
1614
+ swatch_id: Number(swatch.id),
1615
+ collision: collision,
1616
+ existed: existed,
1617
+ space: spaceName,
1618
+ values: values
1619
+ };
1620
+ });