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,567 @@
1
+ /* Bereich G – Text lesen und schreiben. */
2
+
3
+ /*
4
+ * Zeilenumbrueche im Parameter: ein echter Zeilenumbruch (LF, CRLF) wird zur
5
+ * Absatzmarke (CR, wie InDesign sie fuehrt). U+2028 bleibt ein weicher
6
+ * Zeilenumbruch, alles andere unveraendert (E-30).
7
+ */
8
+ function idcNormaliseText(text) {
9
+ var s = String(text);
10
+ var out = '';
11
+ var i, ch;
12
+ for (i = 0; i < s.length; i++) {
13
+ ch = s.charAt(i);
14
+ if (ch === '\r') {
15
+ out += '\r';
16
+ if (i + 1 < s.length && s.charAt(i + 1) === '\n') { i += 1; }
17
+ } else if (ch === '\n') {
18
+ out += '\r';
19
+ } else {
20
+ out += ch;
21
+ }
22
+ }
23
+ return out;
24
+ }
25
+
26
+ /* Enumeratorname des letzten Zeichens (SpecialCharacters) in die Absatzende-Art. */
27
+ function idcBreakTypeFromEnum(name, fallback) {
28
+ if (name === 'COLUMN_BREAK') { return 'column'; }
29
+ if (name === 'FRAME_BREAK') { return 'frame'; }
30
+ if (name === 'PAGE_BREAK') { return 'page'; }
31
+ if (name === 'ODD_PAGE_BREAK') { return 'odd_page'; }
32
+ if (name === 'EVEN_PAGE_BREAK') { return 'even_page'; }
33
+ return fallback;
34
+ }
35
+
36
+ /* Absatzende-Art am letzten Zeichen eines Absatzes. */
37
+ function idcBreakType(lastChar) {
38
+ var code = lastChar.length === 0 ? -1 : lastChar.charCodeAt(0);
39
+ if (code === 13) { return 'paragraph'; }
40
+ if (code === 14) { return 'column'; }
41
+ if (code === 15) { return 'frame'; }
42
+ if (code === 16) { return 'page'; }
43
+ if (code === 17) { return 'odd_page'; }
44
+ if (code === 18) { return 'even_page'; }
45
+ return 'none';
46
+ }
47
+
48
+ function idcStoryOf(frame) {
49
+ var story = null;
50
+ try { story = frame.parentStory; } catch (e) { story = null; }
51
+ if (story === null || story === undefined) {
52
+ IDC.fail('invalid_state', 'Text frame ' + Number(frame.id) + ' has no story.', { frame_id: Number(frame.id) });
53
+ }
54
+ return story;
55
+ }
56
+
57
+ function idcOverflows(frame) {
58
+ try { return frame.overflows === true; } catch (e) { return null; }
59
+ }
60
+
61
+ function idcParagraphStyleName(paragraph) {
62
+ try { return String(paragraph.appliedParagraphStyle.name); } catch (e) { return null; }
63
+ }
64
+
65
+ /* ----------------------------------------------------------- get_frame_text */
66
+
67
+ IDC.register('get_frame_text', { needs_document: true, read_only: true, ruler: 'none' },
68
+ function (ctx) {
69
+ var args = ctx.args;
70
+ var doc = ctx.doc;
71
+ var found = idcTextFrameArg(doc, args, 'frame_id');
72
+ var frame = found.item;
73
+ var story = idcStoryOf(frame);
74
+ var paragraphs = [];
75
+ var storyLength = 0;
76
+ var i, p, text, length, last, breakType, position = 0, endExcl, frameStart = null, frameEnd = null, frameChars = null;
77
+ var onlyPortion = args.only_frame_portion === true;
78
+ var showPlaceholders = args.show_placeholders === true;
79
+ var maxChars = args.max_chars === undefined || args.max_chars === null ? null : args.max_chars;
80
+ var kept, used = 0, omitted = 0, truncated = false, entry, from, to;
81
+
82
+ if (args.include_overset !== undefined) {
83
+ IDC.fail('invalid_argument', 'get_frame_text has no include_overset parameter; use only_frame_portion true for the part of the story in this frame.', null);
84
+ }
85
+ if (args.only_frame_portion !== undefined && args.only_frame_portion !== null && typeof args.only_frame_portion !== 'boolean') {
86
+ IDC.fail('invalid_argument', 'only_frame_portion must be true or false.', { only_frame_portion: args.only_frame_portion });
87
+ }
88
+ if (maxChars !== null && (typeof maxChars !== 'number' || maxChars < 1 || Math.floor(maxChars) !== maxChars)) {
89
+ IDC.fail('invalid_argument', 'max_chars must be a whole number of 1 or more.', { max_chars: maxChars });
90
+ }
91
+ try { storyLength = Number(story.characters.length); } catch (e0) { storyLength = 0; }
92
+
93
+ try {
94
+ for (i = 0; i < story.paragraphs.length; i++) {
95
+ p = story.paragraphs.item(i);
96
+ text = '';
97
+ try { text = String(p.contents); } catch (e1) { text = ''; }
98
+ try { length = Number(p.characters.length); } catch (e2) { length = text.length; }
99
+ last = text.length > 0 ? text.charAt(text.length - 1) : '';
100
+ breakType = idcBreakType(last);
101
+ /*
102
+ * Live 05.09.2026: im Absatztext (String) erscheinen Spalten-, Rahmen-
103
+ * und Seitenumbruch als "\r"; erst das letzte Zeichen einzeln gelesen
104
+ * nennt den Enumerator (COLUMN_BREAK ...). Deshalb nachfassen.
105
+ */
106
+ if (breakType === 'paragraph' && length > 0) {
107
+ try { breakType = idcBreakTypeFromEnum(String(p.characters.item(length - 1).contents), breakType); } catch (eBT) { /* bleibt */ }
108
+ }
109
+ endExcl = breakType === 'none' ? position + length : position + length - 1;
110
+ paragraphs.push({
111
+ index: i,
112
+ start_index: position,
113
+ end_index: endExcl,
114
+ end_index_incl_break: position + length,
115
+ paragraph_style: idcParagraphStyleName(p),
116
+ text: (function () { var t = breakType === 'none' ? text : text.substr(0, text.length - 1); return showPlaceholders ? idcShowPlaceholders(t) : t; })(),
117
+ is_empty: endExcl === position,
118
+ break_type: breakType
119
+ });
120
+ position += length;
121
+ }
122
+ } catch (e) {
123
+ IDC.fail('script_error', 'InDesign could not read the paragraphs of frame ' + args.frame_id + ': ' + String(e.message ? e.message : e),
124
+ { frame_id: args.frame_id, paragraphs_read: paragraphs.length });
125
+ }
126
+
127
+ /* Anteil dieses Rahmens an der Story (verkettete Rahmen). */
128
+ try {
129
+ var portion = frame.texts.item(0);
130
+ frameChars = Number(portion.characters.length);
131
+ if (frameChars > 0) {
132
+ frameStart = Number(portion.characters.item(0).index);
133
+ frameEnd = frameStart + frameChars;
134
+ } else {
135
+ /* Leerer Rahmen am Ende der Kette: Anteil 0, Indizes ohne Aussage. */
136
+ frameStart = null; frameEnd = null;
137
+ }
138
+ } catch (e3) { frameStart = null; frameEnd = null; frameChars = null; }
139
+
140
+ /*
141
+ * only_frame_portion: nur Absaetze, die diesen Rahmen beruehren; Text an
142
+ * den Rahmengrenzen beschnitten (Fund 5, Hefttest). max_chars: Textbudget
143
+ * ueber alle gelieferten Absaetze, danach nur noch Zaehlung.
144
+ */
145
+ if (onlyPortion) {
146
+ kept = [];
147
+ if (frameStart !== null) {
148
+ for (i = 0; i < paragraphs.length; i++) {
149
+ entry = paragraphs[i];
150
+ if (entry.end_index_incl_break <= frameStart || entry.start_index >= frameEnd) { continue; }
151
+ from = Math.max(entry.start_index, frameStart);
152
+ to = Math.min(entry.end_index, frameEnd);
153
+ entry.clipped_start = from > entry.start_index;
154
+ entry.clipped_end = to < entry.end_index;
155
+ if (entry.clipped_start || entry.clipped_end) {
156
+ entry.text = entry.text.substr(from - entry.start_index, Math.max(0, to - from));
157
+ }
158
+ kept.push(entry);
159
+ }
160
+ }
161
+ paragraphs = kept;
162
+ }
163
+ if (maxChars !== null) {
164
+ kept = [];
165
+ for (i = 0; i < paragraphs.length; i++) {
166
+ entry = paragraphs[i];
167
+ if (used >= maxChars) { omitted += 1; truncated = true; continue; }
168
+ if (used + entry.text.length > maxChars) {
169
+ entry.text = entry.text.substr(0, maxChars - used);
170
+ entry.clipped_end = true;
171
+ truncated = true;
172
+ }
173
+ used += entry.text.length;
174
+ kept.push(entry);
175
+ }
176
+ paragraphs = kept;
177
+ }
178
+
179
+ return {
180
+ frame_id: Number(frame.id),
181
+ story_id: (function () { try { return Number(story.id); } catch (e4) { return null; } })(),
182
+ story_length: storyLength,
183
+ paragraphs: paragraphs,
184
+ paragraph_count: paragraphs.length,
185
+ only_frame_portion: onlyPortion,
186
+ show_placeholders: showPlaceholders,
187
+ paragraphs_omitted: omitted,
188
+ truncated: truncated,
189
+ overset: idcOverflows(frame),
190
+ threaded: idcIsThreaded(frame),
191
+ frame_start_index: frameStart,
192
+ frame_end_index: frameEnd,
193
+ frame_character_count: frameChars
194
+ };
195
+ });
196
+
197
+ /* ----------------------------------------------------------------- set_text */
198
+
199
+ IDC.register('set_text', { needs_document: true, read_only: false, ruler: 'none' },
200
+ function (ctx) {
201
+ var args = ctx.args;
202
+ var doc = ctx.doc;
203
+ var found = idcTextFrameArg(doc, args, 'frame_id');
204
+ var frame = found.item;
205
+ var story = idcStoryOf(frame);
206
+ var text, before = 0, after = 0;
207
+
208
+ if (typeof args.text !== 'string') {
209
+ IDC.fail('invalid_argument', 'text is required (a string; "" empties the frame).', null);
210
+ }
211
+ /* A-8.8: erst Absatzmarken normalisieren, dann Platzhalter – {{line_break}} bleibt so ein Zeilenumbruch. */
212
+ var expansion = idcExpandPlaceholders(idcNormaliseText(args.text), args.placeholders !== false);
213
+ var split = idcSplitBreaks(expansion.text);
214
+ text = split.plain;
215
+ idcPlaceholderWarnings(ctx, expansion);
216
+ try { before = Number(story.characters.length); } catch (e0) { before = 0; }
217
+
218
+ /* Wirklich ersetzen: die ganze Story (0 … Laenge), nicht anhaengen. */
219
+ try {
220
+ story.contents = text;
221
+ } catch (e) {
222
+ try {
223
+ if (before > 0) { story.characters.itemByRange(0, before - 1).contents = text; }
224
+ else { story.insertionPoints.item(0).contents = text; }
225
+ } catch (e2) {
226
+ IDC.fail('script_error', 'InDesign could not set the text of frame ' + args.frame_id + ': ' + String(e.message ? e.message : e) +
227
+ ' / ' + String(e2.message ? e2.message : e2), { frame_id: args.frame_id });
228
+ }
229
+ }
230
+ try { idcInsertBreaks(story, 0, split.breaks); } catch (eB) {
231
+ if (eB && eB.isIdcError) { throw eB; }
232
+ ctx.warn('Column/frame/page breaks could not be inserted: ' + String(eB.message ? eB.message : eB));
233
+ }
234
+ text = expansion.text;
235
+ try { after = Number(story.characters.length); } catch (e3) { after = null; }
236
+ if (after !== null && after !== text.length) {
237
+ ctx.warn('The story reads ' + after + ' characters after writing ' + text.length + ' – InDesign may have converted or dropped characters.');
238
+ }
239
+ if (idcIsThreaded(frame)) {
240
+ ctx.warn('The frame is threaded (verkettet); the whole story across all its frames was replaced.');
241
+ }
242
+ ctx.changes = { modified_frame_ids: [Number(frame.id)] };
243
+ return {
244
+ frame_id: Number(frame.id),
245
+ character_count: after,
246
+ previous_character_count: before,
247
+ overset: idcOverflows(frame),
248
+ story_id: (function () { try { return Number(story.id); } catch (e4) { return null; } })(),
249
+ placeholders_replaced: expansion.replaced,
250
+ placeholders_unknown: expansion.unknown
251
+ };
252
+ });
253
+
254
+ /* -------------------------------------------------------- set_text_in_range */
255
+
256
+ /*
257
+ * Sitzung 24 (Befund aus dem Praxiseinsatz): contents = auf einem Bereich loescht
258
+ * ihn und setzt am Einfuegepunkt neu ein. Der Einfuegepunkt traegt das
259
+ * Format des Zeichens davor – beginnt der Bereich an einem Absatzanfang, ist
260
+ * das die Absatzmarke des vorigen Absatzes, und der neue Text erbt dessen
261
+ * Format. keep_format_of:
262
+ * start (Vorgabe) neuen Text hinter das erste ersetzte Zeichen setzen –
263
+ * er erbt dessen Format –, dann die alten Zeichen entfernen;
264
+ * before bisheriger Weg (InDesigns eigene Wahl, das Zeichen davor);
265
+ * none bisheriger Weg, danach Zeichenformat [None] und keine lokalen
266
+ * Zeichenabweichungen im neuen Text (Format des Absatzformats).
267
+ */
268
+ function idcKeepFormatOf(args) {
269
+ var mode = args.keep_format_of;
270
+ if (mode === undefined || mode === null) { return 'start'; }
271
+ if (mode !== 'start' && mode !== 'before' && mode !== 'none') {
272
+ IDC.fail('invalid_argument', 'keep_format_of must be "start", "before" or "none".', { keep_format_of: mode });
273
+ }
274
+ return mode;
275
+ }
276
+
277
+ /*
278
+ * Bereich [start, end) durch text ersetzen, absatzweise (Nacharbeit 2 zu
279
+ * Sitzung 24, Lauf S24). Vorbild je ersetztem Absatz ist sein erstes Zeichen
280
+ * im Bereich: der Bereichsanfang, dann jede Stelle hinter einer Absatzmarke
281
+ * im Bereich. Der neue Text wird an den Absatzmarken geteilt; Teil 0 (bis
282
+ * einschliesslich der ersten neuen Absatzmarke) bekommt das Format des
283
+ * ersten ersetzten Zeichens, Teil i das des i-ten ersetzten Absatzes, jeder
284
+ * weitere Teil das des letzten ersetzten Absatzes.
285
+ *
286
+ * Jeder Teil wird hinter sein Vorbild eingefuegt und erbt so dessen Format.
287
+ * Ist das Vorbild eine Absatzmarke, liegt der Einfuegepunkt dahinter schon
288
+ * im naechsten Absatz (Lauf S24: 9 statt 20 pt) – dann vor ihr einfuegen,
289
+ * also im Absatz, dessen Marke ersetzt wird. Danach die alten Zeichen
290
+ * entfernen (von hinten) und jedem neuen Absatz das Absatzformat seines
291
+ * Vorbilds zuweisen, wenn InDesign beim Verschmelzen ein anderes gewaehlt
292
+ * hat (lokale Abweichungen bleiben).
293
+ */
294
+ function idcReplaceKeepingStart(story, start, end, text) {
295
+ var old = '', anchors = [], segments = [], groups = [], i, k, c, shift, ip, piece, first, last, runEnd, offset, para, style;
296
+ try { old = String(story.characters.itemByRange(start, end - 1).contents); } catch (e0) { old = ''; }
297
+ if (old.length !== end - start) {
298
+ old = '';
299
+ for (i = start; i < end; i++) { old += String(story.characters.item(i).contents); }
300
+ }
301
+ anchors.push(start);
302
+ for (i = 0; i < old.length - 1; i++) {
303
+ if (old.charAt(i) === '\r') { anchors.push(start + i + 1); }
304
+ }
305
+ /* neuer Text in Teile bis einschliesslich jeder Absatzmarke */
306
+ piece = '';
307
+ for (i = 0; i < text.length; i++) {
308
+ piece += text.charAt(i);
309
+ if (text.charAt(i) === '\r') { segments.push(piece); piece = ''; }
310
+ }
311
+ if (piece.length > 0 || segments.length === 0) { segments.push(piece); }
312
+ /* Gruppen je Vorbild: Text, Einfuegepunkt, Absatzformat des Vorbilds, Anfang jedes Teils im neuen Text */
313
+ for (k = 0; k < anchors.length; k++) {
314
+ groups.push({ anchor: anchors[k], text: '', parts: [], style: null, ip: 0 });
315
+ }
316
+ offset = 0;
317
+ for (i = 0; i < segments.length; i++) {
318
+ k = i < anchors.length ? i : anchors.length - 1;
319
+ groups[k].parts.push(offset);
320
+ groups[k].text += segments[i];
321
+ offset += segments[i].length;
322
+ }
323
+ for (k = 0; k < groups.length; k++) {
324
+ c = old.charAt(groups[k].anchor - start);
325
+ groups[k].ip = c === '\r' ? groups[k].anchor : groups[k].anchor + 1;
326
+ try { groups[k].style = story.characters.item(groups[k].anchor).appliedParagraphStyle; } catch (eS) { groups[k].style = null; }
327
+ }
328
+ /* einfuegen von hinten, damit die vorderen Stellen gelten */
329
+ for (k = groups.length - 1; k >= 0; k--) {
330
+ if (groups[k].text.length > 0) { story.insertionPoints.item(groups[k].ip).contents = groups[k].text; }
331
+ }
332
+ /* alte Zeichen: neue Lage = alte + Laenge aller Teile, die davor eingefuegt wurden; zusammenhaengend von hinten entfernen */
333
+ runEnd = -1;
334
+ last = -1;
335
+ for (i = end - 1; i >= start - 1; i--) {
336
+ ip = -1;
337
+ if (i >= start) {
338
+ shift = 0;
339
+ for (k = 0; k < groups.length; k++) {
340
+ if (groups[k].ip <= i) { shift += groups[k].text.length; }
341
+ }
342
+ ip = i + shift;
343
+ }
344
+ if (ip >= 0 && runEnd >= 0 && ip === last - 1) {
345
+ last = ip;
346
+ } else {
347
+ if (runEnd >= 0) { story.characters.itemByRange(last, runEnd).remove(); }
348
+ runEnd = ip;
349
+ last = ip;
350
+ }
351
+ }
352
+ /* Absatzformat je neuem Teil */
353
+ for (k = 0; k < groups.length; k++) {
354
+ if (groups[k].style === null) { continue; }
355
+ for (i = 0; i < groups[k].parts.length; i++) {
356
+ first = start + groups[k].parts[i];
357
+ if (first >= start + text.length) { continue; }
358
+ try {
359
+ para = story.characters.item(first).paragraphs.item(0);
360
+ style = para.appliedParagraphStyle;
361
+ if (String(style.name) !== String(groups[k].style.name)) {
362
+ try { para.applyParagraphStyle(groups[k].style, false); } catch (eA) { para.appliedParagraphStyle = groups[k].style; }
363
+ }
364
+ } catch (eP) { /* Absatzformat nicht lesbar: InDesigns Wahl bleibt */ }
365
+ }
366
+ }
367
+ }
368
+
369
+ /* Zeichenformat [None] und keine lokalen Zeichenabweichungen im Bereich [start, start + n). */
370
+ function idcClearCharacterFormat(doc, story, start, n) {
371
+ var range, none;
372
+ if (n < 1) { return; }
373
+ range = story.characters.itemByRange(start, start + n - 1);
374
+ none = idcNoneCharacterStyle(doc);
375
+ if (none !== null) {
376
+ try { range.applyCharacterStyle(none); } catch (e1) { range.appliedCharacterStyle = none; }
377
+ }
378
+ if (typeof OverrideType !== 'undefined') { range.clearOverrides(OverrideType.CHARACTER_ONLY); }
379
+ else { range.clearOverrides(); }
380
+ }
381
+
382
+ IDC.register('set_text_in_range', { needs_document: true, read_only: false, ruler: 'none' },
383
+ function (ctx) {
384
+ var args = ctx.args;
385
+ var doc = ctx.doc;
386
+ var found = idcTextFrameArg(doc, args, 'frame_id');
387
+ var frame = found.item;
388
+ var story = idcStoryOf(frame);
389
+ var start = args.start_index;
390
+ var end = args.end_index;
391
+ var text, length = 0, replaced, after;
392
+ var keepFormat = idcKeepFormatOf(args);
393
+
394
+ if (typeof start !== 'number' || Math.floor(start) !== start || start < 0) {
395
+ IDC.fail('invalid_argument', 'start_index must be a whole number of 0 or more (inclusive).', { start_index: start });
396
+ }
397
+ if (typeof end !== 'number' || Math.floor(end) !== end || end < start) {
398
+ IDC.fail('invalid_argument', 'end_index must be a whole number not smaller than start_index (exclusive).', { start_index: start, end_index: end });
399
+ }
400
+ if (typeof args.text !== 'string') {
401
+ IDC.fail('invalid_argument', 'text is required (a string; "" deletes the range).', null);
402
+ }
403
+ var expansion = idcExpandPlaceholders(idcNormaliseText(args.text), args.placeholders !== false);
404
+ var split = idcSplitBreaks(expansion.text);
405
+ text = split.plain;
406
+ idcPlaceholderWarnings(ctx, expansion);
407
+ try { length = Number(story.characters.length); } catch (e0) { length = 0; }
408
+ if (end > length) {
409
+ IDC.fail('invalid_argument', 'end_index ' + end + ' exceeds the story length ' + length + '.', { end_index: end, story_length: length });
410
+ }
411
+ replaced = end - start;
412
+
413
+ try {
414
+ if (replaced === 0) {
415
+ if (text.length > 0) { story.insertionPoints.item(start).contents = text; }
416
+ } else if (text.length === 0) {
417
+ story.characters.itemByRange(start, end - 1).remove();
418
+ } else if (keepFormat === 'start') {
419
+ idcReplaceKeepingStart(story, start, end, text);
420
+ } else {
421
+ story.characters.itemByRange(start, end - 1).contents = text;
422
+ }
423
+ } catch (e) {
424
+ IDC.fail('script_error', 'InDesign could not change characters ' + start + '–' + end + ' of frame ' + args.frame_id + ': ' +
425
+ String(e.message ? e.message : e), { frame_id: args.frame_id, start_index: start, end_index: end });
426
+ }
427
+ try { idcInsertBreaks(story, start, split.breaks); } catch (eB) {
428
+ if (eB && eB.isIdcError) { throw eB; }
429
+ ctx.warn('Column/frame/page breaks could not be inserted: ' + String(eB.message ? eB.message : eB));
430
+ }
431
+ text = expansion.text;
432
+ if (keepFormat === 'none' && text.length > 0) {
433
+ try { idcClearCharacterFormat(doc, story, start, text.length); } catch (eN) {
434
+ ctx.warn('The new text was written, but its character formatting could not be cleared: ' + String(eN.message ? eN.message : eN));
435
+ }
436
+ }
437
+ try { after = Number(story.characters.length); } catch (e1) { after = null; }
438
+ if (after !== null && after !== length - replaced + text.length) {
439
+ ctx.warn('The story reads ' + after + ' characters, expected ' + (length - replaced + text.length) + '.');
440
+ }
441
+ ctx.changes = { modified_frame_ids: [Number(frame.id)] };
442
+ return {
443
+ frame_id: Number(frame.id),
444
+ replaced_chars: replaced,
445
+ inserted_chars: text.length,
446
+ story_length: after,
447
+ keep_format_of: keepFormat,
448
+ overset: idcOverflows(frame),
449
+ placeholders_replaced: expansion.replaced,
450
+ placeholders_unknown: expansion.unknown
451
+ };
452
+ });
453
+
454
+ /* ------------------------------------------------------------- find_replace */
455
+
456
+ function idcSetFindOptions(options, args) {
457
+ try { options.includeMasterPages = args.include_master_pages === true; } catch (e1) { /* nicht vorhanden */ }
458
+ try { options.includeHiddenLayers = args.include_hidden_layers === true; } catch (e2) { /* nicht vorhanden */ }
459
+ try { options.includeLockedLayersForFind = args.include_locked_layers === true; } catch (e3) { /* nicht vorhanden */ }
460
+ try { options.includeLockedStoriesForFind = args.include_locked_layers === true; } catch (e4) { /* nicht vorhanden */ }
461
+ try { options.includeFootnotes = true; } catch (e5) { /* nicht vorhanden */ }
462
+ try { options.caseSensitive = args.case_sensitive === true; } catch (e6) { /* GREP kennt das nicht */ }
463
+ try { options.wholeWord = args.whole_word === true; } catch (e7) { /* GREP kennt das nicht */ }
464
+ }
465
+
466
+ /* Fundstelle beschreiben: Rahmen, Seite, Index, Text. */
467
+ function idcDescribeMatch(doc, textObject) {
468
+ var entry = { frame_id: null, page_id: null, page_name: '', index: null, text: null, story_id: null };
469
+ var frames, frame, landing;
470
+ try { entry.index = Number(textObject.index); } catch (e0) { entry.index = null; }
471
+ try { entry.text = String(textObject.contents); } catch (e1) { entry.text = null; }
472
+ try { entry.story_id = Number(textObject.parentStory.id); } catch (e2) { entry.story_id = null; }
473
+ try {
474
+ frames = textObject.parentTextFrames;
475
+ if (frames && frames.length > 0) {
476
+ frame = IDC.concrete(frames[0]);
477
+ entry.frame_id = Number(frame.id);
478
+ landing = IDC.landing(frame);
479
+ entry.page_id = landing.page_id;
480
+ entry.page_name = landing.page_name;
481
+ }
482
+ } catch (e3) { /* Text ohne Rahmen (Uebersatz) */ }
483
+ return entry;
484
+ }
485
+
486
+ IDC.register('find_replace', { needs_document: true, read_only: false, ruler: 'none' },
487
+ function (ctx) {
488
+ var args = ctx.args;
489
+ var doc = ctx.doc;
490
+ var mode = args.mode === undefined || args.mode === null ? 'literal' : args.mode;
491
+ var scope = args.scope === undefined || args.scope === null ? 'document' : args.scope;
492
+ var dryRun = args.dry_run === true;
493
+ var maxMatches = 200;
494
+ var target, frame, found, i, matches = [], count = 0, prefsFind, prefsChange, options;
495
+
496
+ if (typeof args.find !== 'string' || args.find === '') {
497
+ IDC.fail('invalid_argument', 'find is required and must not be empty.', null);
498
+ }
499
+ if (!dryRun && typeof args.replace !== 'string') {
500
+ IDC.fail('invalid_argument', 'replace is required (a string; "" deletes the matches). Use dry_run true to only count.', null);
501
+ }
502
+ if (mode !== 'literal' && mode !== 'grep') {
503
+ IDC.fail('invalid_argument', 'mode must be literal or grep.', { mode: mode });
504
+ }
505
+ if (scope !== 'document' && scope !== 'frame') {
506
+ IDC.fail('invalid_argument', 'scope must be document or frame.', { scope: scope });
507
+ }
508
+ if (scope === 'frame') {
509
+ frame = idcTextFrameArg(doc, args, 'frame_id').item;
510
+ try { target = frame.texts.item(0); } catch (e0) { target = null; }
511
+ if (target === null || target === undefined) { target = idcStoryOf(frame); }
512
+ } else {
513
+ target = doc;
514
+ }
515
+
516
+ if (mode === 'grep') {
517
+ prefsFind = 'findGrepPreferences';
518
+ prefsChange = 'changeGrepPreferences';
519
+ options = app.findChangeGrepOptions;
520
+ } else {
521
+ prefsFind = 'findTextPreferences';
522
+ prefsChange = 'changeTextPreferences';
523
+ options = app.findChangeTextOptions;
524
+ }
525
+
526
+ try {
527
+ app[prefsFind] = NothingEnum.NOTHING;
528
+ app[prefsChange] = NothingEnum.NOTHING;
529
+ idcSetFindOptions(options, args);
530
+ app[prefsFind].findWhat = args.find;
531
+ if (!dryRun) { app[prefsChange].changeTo = args.replace; }
532
+
533
+ if (dryRun) {
534
+ found = mode === 'grep' ? target.findGrep() : target.findText();
535
+ count = found.length;
536
+ for (i = 0; i < found.length && i < maxMatches; i++) { matches.push(idcDescribeMatch(doc, found[i])); }
537
+ if (found.length > maxMatches) { ctx.warn('Only the first ' + maxMatches + ' of ' + found.length + ' matches are listed.'); }
538
+ } else {
539
+ found = mode === 'grep' ? target.changeGrep() : target.changeText();
540
+ count = found.length;
541
+ }
542
+ } catch (e) {
543
+ IDC.fail('script_error', 'InDesign could not run find/change (' + mode + '): ' + String(e.message ? e.message : e),
544
+ { find: args.find, mode: mode, scope: scope });
545
+ } finally {
546
+ try { app[prefsFind] = NothingEnum.NOTHING; } catch (er1) { ctx.warn('Could not reset the find preferences.'); }
547
+ try { app[prefsChange] = NothingEnum.NOTHING; } catch (er2) { ctx.warn('Could not reset the change preferences.'); }
548
+ }
549
+
550
+ if (mode === 'literal' && /\u00A0/.test(args.find)) {
551
+ ctx.warn('The search text contains a non-breaking space (U+00A0); literal search does not find it reliably – use mode grep with ~S.');
552
+ }
553
+ if (!dryRun && count > 0) { ctx.changes = { replacements: count, modified_frame_ids: scope === 'frame' ? [Number(frame.id)] : [] }; }
554
+ var out = {
555
+ replacements: dryRun ? 0 : count,
556
+ matches_found: count,
557
+ dry_run: dryRun,
558
+ scope: scope,
559
+ mode: mode,
560
+ frame_id: scope === 'frame' ? Number(frame.id) : null,
561
+ master_pages_included: args.include_master_pages === true,
562
+ hidden_layers_included: args.include_hidden_layers === true,
563
+ locked_layers_included: args.include_locked_layers === true
564
+ };
565
+ if (dryRun) { out.matches = matches; }
566
+ return out;
567
+ });
@@ -0,0 +1,92 @@
1
+ /*
2
+ * Auftrag A-8.7 (Sitzung 9) – convert_text_to_outlines: Text eines Rahmens
3
+ * in Pfade umwandeln (unumkehrbar). Adobe-DOM (Auftrag, geprueft
4
+ * 05.09.2026): TextFrame.createOutlines(deleteOriginal) -> PageItem
5
+ * (Gruppe oder einzelnes Polygon; was InDesign bei mehreren Zeilen
6
+ * liefert: live pruefen, Journal).
7
+ */
8
+
9
+ /* Schriften eines Rahmens: {family, style} je verwendeter Schrift (Reihenfolge des Auftretens). */
10
+ function idcFontsInFrame(frame) {
11
+ var out = [], seen = {}, ranges, i, run, font, key, family = null, style = null;
12
+ try { ranges = frame.parentStory.textStyleRanges; } catch (e0) { return out; }
13
+ for (i = 0; i < ranges.length; i++) {
14
+ try { run = ranges.item(i); } catch (e1) { continue; }
15
+ try { font = idcReadFontLoose(run); family = font.family; style = font.style; } catch (e2) { family = null; style = null; }
16
+ if (family === null) { continue; }
17
+ key = family + '\t' + (style === null ? '' : style);
18
+ if (IDC.hasOwn(seen, key)) { continue; }
19
+ seen[key] = 1;
20
+ out.push({ family: family, style: style });
21
+ }
22
+ return out;
23
+ }
24
+
25
+ IDC.register('convert_text_to_outlines', { needs_document: true, read_only: false, ruler: 'page' },
26
+ function (ctx) {
27
+ var args = ctx.args;
28
+ var doc = ctx.doc;
29
+ var found = idcTextFrameArg(doc, args, 'frame_id');
30
+ var frame = found.item;
31
+ var story = idcStoryOf(frame);
32
+ var deleteOriginal = args.delete_original !== false;
33
+ var characters = 0, frames = 0, result, concrete, type, memberCount = 0, fonts, landing, tables = 0, sourceId;
34
+
35
+ if (args.confirm !== true) {
36
+ IDC.fail('invalid_argument', 'convert_text_to_outlines is irreversible – pass confirm true to run it.', { frame_id: args.frame_id });
37
+ }
38
+ try { characters = Number(story.characters.length); } catch (e0) { characters = 0; }
39
+ if (characters === 0) {
40
+ IDC.fail('invalid_state', 'Text frame ' + args.frame_id + ' is empty; there is no text to convert.', { frame_id: args.frame_id });
41
+ }
42
+ try { frames = Number(story.textContainers.length); } catch (e1) { frames = 1; }
43
+ if (frames > 1) {
44
+ IDC.fail('invalid_state', 'Text frame ' + args.frame_id + ' is threaded (the story runs over ' + frames + ' frames); convert only unthreaded frames.', { frame_id: args.frame_id, frame_count: frames });
45
+ }
46
+ if (idcOverflows(frame) === true) {
47
+ IDC.fail('invalid_state', 'Text frame ' + args.frame_id + ' overflows; overset text would be lost. Enlarge the frame first.', { frame_id: args.frame_id });
48
+ }
49
+ try { tables = Number(story.tables.length); } catch (e2) { tables = 0; }
50
+ if (tables > 0) {
51
+ ctx.warn('The frame holds ' + tables + ' table(s); InDesign converts only the text, table cells may be dropped or changed – check the result.');
52
+ }
53
+ fonts = idcFontsInFrame(frame);
54
+ sourceId = Number(frame.id);
55
+
56
+ try {
57
+ result = frame.createOutlines(deleteOriginal);
58
+ } catch (e) {
59
+ IDC.fail('script_error', 'InDesign could not convert the text of frame ' + args.frame_id + ' to outlines: ' + String(e.message ? e.message : e), { frame_id: args.frame_id });
60
+ }
61
+ /* createOutlines liefert ein ExtendScript-Array (Live 05.09.2026), das IDC.isArray nicht erkennt – ueber Klassenname/length pruefen wie bei get_selection. */
62
+ if (IDC.isArray(result) || IDC.className(result) === 'Array' || (result && typeof result.length === 'number' && result.id === undefined)) {
63
+ result = result.length > 0 ? result[0] : null;
64
+ }
65
+ if (result === null || result === undefined) {
66
+ IDC.fail('script_error', 'InDesign returned no object after converting the text to outlines.', { frame_id: args.frame_id });
67
+ }
68
+ concrete = IDC.concrete(result);
69
+ type = IDC.itemType(concrete);
70
+ if (type === 'group') {
71
+ try { memberCount = Number(concrete.pageItems.length); } catch (e3) { memberCount = null; }
72
+ } else {
73
+ memberCount = 1;
74
+ }
75
+ landing = IDC.landing(concrete);
76
+ ctx.warn('The text is now outlines: it can no longer be searched or edited as text, and the fonts ' + (fonts.length > 0 ? '(' + (function () { var n = [], i; for (i = 0; i < fonts.length; i++) { n.push(fonts[i].family + (fonts[i].style ? ' ' + fonts[i].style : '')); } return n.join(', '); })() + ') ' : '') + 'are no longer needed for it.');
77
+ var changes = { created_frame_ids: [Number(concrete.id)] };
78
+ if (deleteOriginal) { changes.removed_frame_ids = [sourceId]; }
79
+ ctx.changes = changes;
80
+ return {
81
+ frame_id: Number(concrete.id),
82
+ type: type,
83
+ member_count: memberCount,
84
+ bounds_mm: idcItemBounds(doc, concrete),
85
+ page_id: landing.page_id,
86
+ page_name: landing.page_name,
87
+ source_frame_id: sourceId,
88
+ source_deleted: deleteOriginal,
89
+ characters_converted: characters,
90
+ fonts_used: fonts
91
+ };
92
+ });