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,197 @@
1
+ /* Wunschliste J Nr. 5 (Sitzung 7) – Seiten als Bild: export_page_image. */
2
+
3
+ function idcImageQualityFor(format, name) {
4
+ var table = format === 'jpeg' ? (typeof JPEGOptionsQuality === 'undefined' ? null : JPEGOptionsQuality)
5
+ : (typeof PNGQualityEnum === 'undefined' ? null : PNGQualityEnum);
6
+ if (table === null) { return null; }
7
+ if (name === 'low') { return table.LOW; }
8
+ if (name === 'medium') { return table.MEDIUM; }
9
+ if (name === 'high') { return table.HIGH; }
10
+ return table.MAXIMUM;
11
+ }
12
+
13
+ function idcImageColorSpaceFor(format, name) {
14
+ var table = format === 'jpeg' ? (typeof JpegColorSpaceEnum === 'undefined' ? null : JpegColorSpaceEnum)
15
+ : (typeof PNGColorSpaceEnum === 'undefined' ? null : PNGColorSpaceEnum);
16
+ if (table === null) { return null; }
17
+ if (name === 'gray') { return table.GRAY; }
18
+ if (name === 'cmyk') { return table.CMYK; }
19
+ return table.RGB;
20
+ }
21
+
22
+ /* Seiten aus page_ids oder page_range: [{ page, name }] in Dokumentreihenfolge. */
23
+ function idcPagesForExport(doc, args) {
24
+ var out = [];
25
+ var i, j, found, parts, part, single, cut, left, right, from, to;
26
+ var wanted = {};
27
+ if (args.page_ids !== undefined && args.page_ids !== null) {
28
+ if (!IDC.isArray(args.page_ids) || args.page_ids.length === 0) {
29
+ IDC.fail('invalid_argument', 'page_ids must be a non-empty list of page ids.', { page_ids: args.page_ids });
30
+ }
31
+ for (i = 0; i < args.page_ids.length; i++) {
32
+ found = IDC.pageById(doc, args.page_ids[i]);
33
+ if (found === null || found.is_parent_page) {
34
+ IDC.fail('not_found', 'No document page with page_id ' + args.page_ids[i] + ' (master pages cannot be exported as images).', { page_id: args.page_ids[i] });
35
+ }
36
+ wanted[Number(found.page.id)] = true;
37
+ }
38
+ } else if (args.page_range !== undefined && args.page_range !== null) {
39
+ parts = String(args.page_range).split(',');
40
+ for (i = 0; i < parts.length; i++) {
41
+ part = idcTrim(parts[i]);
42
+ if (part === '') { continue; }
43
+ single = idcPageIndexByName(doc, part);
44
+ if (single >= 0) { wanted[Number(doc.pages.item(single).id)] = true; continue; }
45
+ from = -1; to = -1;
46
+ for (cut = 1; cut < part.length; cut++) {
47
+ if (part.charAt(cut) !== '-') { continue; }
48
+ left = idcTrim(part.substr(0, cut));
49
+ right = idcTrim(part.substr(cut + 1));
50
+ from = idcPageIndexByName(doc, left);
51
+ to = idcPageIndexByName(doc, right);
52
+ if (from >= 0 && to >= 0) { break; }
53
+ from = -1; to = -1;
54
+ }
55
+ if (from < 0 || to < 0) {
56
+ IDC.fail('invalid_argument', 'page_range "' + args.page_range + '": "' + part + '" names no page of this document (page names are the printed page numbers; "+3" addresses the third page).',
57
+ { page_range: String(args.page_range), part: part });
58
+ }
59
+ if (to < from) {
60
+ IDC.fail('invalid_argument', 'page_range "' + args.page_range + '": "' + part + '" runs backwards.', { page_range: String(args.page_range), part: part });
61
+ }
62
+ for (j = from; j <= to; j++) { wanted[Number(doc.pages.item(j).id)] = true; }
63
+ }
64
+ } else {
65
+ IDC.fail('invalid_argument', 'Pass page_ids or page_range.', null);
66
+ }
67
+ for (i = 0; i < doc.pages.length; i++) {
68
+ if (wanted[Number(doc.pages.item(i).id)]) { out.push({ page: doc.pages.item(i), name: String(doc.pages.item(i).name) }); }
69
+ }
70
+ if (out.length === 0) { IDC.fail('invalid_argument', 'No page selected.', null); }
71
+ if (out.length > 48) { IDC.fail('invalid_argument', 'At most 48 pages per call.', { count: out.length }); }
72
+ return out;
73
+ }
74
+
75
+ /* Dateiname mit Seitenname vor der Endung: Seite.png + "8" -> Seite_8.png. */
76
+ function idcPageFileName(path, pageName, single) {
77
+ var dot = path.lastIndexOf('.');
78
+ var slash = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
79
+ var safe = String(pageName).replace(/[^A-Za-z0-9_.+-]/g, '_');
80
+ if (single) { return path; }
81
+ if (dot > slash) { return path.substr(0, dot) + '_' + safe + path.substr(dot); }
82
+ return path + '_' + safe;
83
+ }
84
+
85
+ IDC.register('export_page_image', { needs_document: true, read_only: false, ruler: 'none' },
86
+ function (ctx) {
87
+ var args = ctx.args;
88
+ var doc = ctx.doc;
89
+ var format = args.format === undefined || args.format === null ? 'png' : args.format;
90
+ var dpi = args.dpi === undefined || args.dpi === null ? 150 : args.dpi;
91
+ var quality = args.quality === undefined || args.quality === null ? 'high' : args.quality;
92
+ var colorSpace = args.color_space === undefined || args.color_space === null ? 'rgb' : args.color_space;
93
+ var transparent = args.transparent_background === true;
94
+ var useBleed = args.use_bleed === true;
95
+ var antiAlias = args.anti_alias !== false;
96
+ var path = args.path;
97
+ var pages, prefs, saved = {}, keys, k, file, files = [], i, target, sizeAfter, exportFormat, ext, prevExportFormatOk;
98
+
99
+ if (typeof path !== 'string' || path === '') {
100
+ IDC.fail('invalid_argument', 'path is required: the target file (for several pages the page name is inserted before the extension).', null);
101
+ }
102
+ if (format !== 'png' && format !== 'jpeg') {
103
+ IDC.fail('invalid_argument', 'format must be png or jpeg.', { format: format });
104
+ }
105
+ if (typeof dpi !== 'number' || !isFinite(dpi) || dpi < 36 || dpi > 600) {
106
+ IDC.fail('invalid_argument', 'dpi must be between 36 and 600.', { dpi: dpi });
107
+ }
108
+ if (quality !== 'low' && quality !== 'medium' && quality !== 'high' && quality !== 'maximum') {
109
+ IDC.fail('invalid_argument', 'quality must be low, medium, high or maximum.', { quality: quality });
110
+ }
111
+ if (colorSpace !== 'rgb' && colorSpace !== 'gray' && !(colorSpace === 'cmyk' && format === 'jpeg')) {
112
+ IDC.fail('invalid_argument', 'color_space must be rgb or gray (jpeg also cmyk).', { color_space: colorSpace, format: format });
113
+ }
114
+ ext = format === 'png' ? '.png' : '.jpg';
115
+ if (!(new RegExp('\\.(' + (format === 'png' ? 'png' : 'jpe?g') + ')$', 'i')).test(path)) {
116
+ ctx.warn('path does not end in ' + ext + '; InDesign writes ' + format.toUpperCase() + ' data regardless.');
117
+ }
118
+ pages = idcPagesForExport(doc, args);
119
+ file = IDC.fileFor(path);
120
+ if (!IDC.fileParentExists(file)) {
121
+ IDC.fail('io_error', 'The folder for ' + path + ' does not exist.', { path: path });
122
+ }
123
+
124
+ prefs = format === 'png' ? app.pngExportPreferences : app.jpegExportPreferences;
125
+ if (prefs === undefined || prefs === null) {
126
+ IDC.fail('invalid_state', 'This InDesign runtime has no ' + format.toUpperCase() + ' export preferences.', null);
127
+ }
128
+ exportFormat = format === 'png' ? ExportFormat.PNG_FORMAT : ExportFormat.JPG;
129
+
130
+ /* Vorgaben merken, setzen, am Ende zurueckstellen. */
131
+ keys = format === 'png'
132
+ ? ['exportResolution', 'pngQuality', 'pngColorSpace', 'transparentBackground', 'antiAlias', 'useDocumentBleeds', 'pngExportRange', 'pageString', 'exportingSpread', 'simulateOverprint']
133
+ : ['exportResolution', 'jpegQuality', 'jpegColorSpace', 'antiAlias', 'useDocumentBleeds', 'jpegExportRange', 'pageString', 'exportingSpread', 'simulateOverprint', 'jpegRenderingStyle'];
134
+ for (k = 0; k < keys.length; k++) {
135
+ try { saved[keys[k]] = prefs[keys[k]]; } catch (e0) { /* Eigenschaft fehlt */ }
136
+ }
137
+ var setPref = function (key, value) {
138
+ if (value === null || value === undefined) { return; }
139
+ try { prefs[key] = value; } catch (e1) { ctx.warn('Could not set export preference ' + key + ': ' + String(e1.message ? e1.message : e1)); }
140
+ };
141
+ try {
142
+ setPref('exportResolution', dpi);
143
+ setPref('antiAlias', antiAlias);
144
+ setPref('useDocumentBleeds', useBleed);
145
+ setPref('simulateOverprint', false);
146
+ setPref('exportingSpread', false);
147
+ if (format === 'png') {
148
+ setPref('pngQuality', idcImageQualityFor('png', quality));
149
+ setPref('pngColorSpace', idcImageColorSpaceFor('png', colorSpace));
150
+ setPref('transparentBackground', transparent);
151
+ if (typeof ExportRangeOrAllPages !== 'undefined') { setPref('pngExportRange', ExportRangeOrAllPages.EXPORT_RANGE); }
152
+ } else {
153
+ setPref('jpegQuality', idcImageQualityFor('jpeg', quality));
154
+ setPref('jpegColorSpace', idcImageColorSpaceFor('jpeg', colorSpace));
155
+ if (typeof JPEGOptionsFormat !== 'undefined') { setPref('jpegRenderingStyle', JPEGOptionsFormat.BASELINE_ENCODING); }
156
+ if (typeof ExportRangeOrAllPages !== 'undefined') { setPref('jpegExportRange', ExportRangeOrAllPages.EXPORT_RANGE); }
157
+ }
158
+
159
+ for (i = 0; i < pages.length; i++) {
160
+ target = idcPageFileName(path, pages[i].name, pages.length === 1);
161
+ setPref('pageString', pages[i].name);
162
+ try {
163
+ doc.exportFile(exportFormat, IDC.fileFor(target), false);
164
+ } catch (e2) {
165
+ IDC.fail('io_error', 'Image export of page ' + pages[i].name + ' failed: ' + String(e2.message ? e2.message : e2) +
166
+ (files.length > 0 ? ' (' + files.length + ' page(s) exported before)' : ''), { path: target, page_name: pages[i].name, exported: files });
167
+ }
168
+ sizeAfter = IDC.fileSize(IDC.fileFor(target));
169
+ if (sizeAfter === null || sizeAfter === 0) {
170
+ /* InDesign haengt bei Bedarf selbst den Seitennamen an – dann dort nachsehen. */
171
+ var alt = idcPageFileName(path, pages[i].name, false);
172
+ var alt2 = alt.replace('_' + String(pages[i].name).replace(/[^A-Za-z0-9_.+-]/g, '_'), String(pages[i].name));
173
+ if (IDC.fileSize(IDC.fileFor(alt)) > 0) { target = alt; sizeAfter = IDC.fileSize(IDC.fileFor(alt)); }
174
+ else if (IDC.fileSize(IDC.fileFor(alt2)) > 0) { target = alt2; sizeAfter = IDC.fileSize(IDC.fileFor(alt2)); }
175
+ else { ctx.warn('Page ' + pages[i].name + ': no file found at ' + target + ' after the export.'); }
176
+ }
177
+ files.push({ page_id: Number(pages[i].page.id), page_name: pages[i].name, path: target, bytes: sizeAfter });
178
+ }
179
+ } finally {
180
+ for (k = 0; k < keys.length; k++) {
181
+ if (saved[keys[k]] !== undefined) {
182
+ try { prefs[keys[k]] = saved[keys[k]]; } catch (e3) { ctx.warn('Could not restore export preference ' + keys[k] + '.'); }
183
+ }
184
+ }
185
+ }
186
+
187
+ return {
188
+ format: format,
189
+ dpi: dpi,
190
+ quality: quality,
191
+ color_space: colorSpace,
192
+ transparent_background: format === 'png' ? transparent : null,
193
+ use_bleed: useBleed,
194
+ count: files.length,
195
+ files: files
196
+ };
197
+ });
@@ -0,0 +1,151 @@
1
+ /* Bereich A – Textvorgaben des Dokuments. */
2
+
3
+ /* Schriftfamilie und Schnitt der Vorgabe lesen; appliedFont kann Objekt oder "Familie\tSchnitt" sein. */
4
+ function idcReadDefaultFont(defaults) {
5
+ var family = null;
6
+ var style = null;
7
+ var applied, text, tab;
8
+
9
+ try {
10
+ applied = defaults.appliedFont;
11
+ if (applied && typeof applied === 'object') {
12
+ try { family = String(applied.fontFamily); } catch (e1) { family = null; }
13
+ try { style = String(applied.fontStyleName); } catch (e2) { style = null; }
14
+ if (family === null) {
15
+ text = String(applied.name);
16
+ tab = text.indexOf('\t');
17
+ family = tab >= 0 ? text.substr(0, tab) : text;
18
+ }
19
+ } else if (applied !== undefined && applied !== null) {
20
+ text = String(applied);
21
+ tab = text.indexOf('\t');
22
+ family = tab >= 0 ? text.substr(0, tab) : text;
23
+ if (tab >= 0) { style = text.substr(tab + 1); }
24
+ }
25
+ } catch (e) { /* bleibt null */ }
26
+
27
+ if (style === null) {
28
+ try { style = String(defaults.fontStyle); } catch (e3) { style = null; }
29
+ }
30
+ return { family: family, style: style };
31
+ }
32
+
33
+ function idcStyleName(styleObject) {
34
+ try {
35
+ if (styleObject && styleObject.isValid !== false) { return String(styleObject.name); }
36
+ } catch (e) { /* null */ }
37
+ return null;
38
+ }
39
+
40
+ function idcReadTextDefaults(defaults) {
41
+ var font = idcReadDefaultFont(defaults);
42
+ var size = null;
43
+ /*
44
+ * pointSize kommt in der aktuellen Skript-Masseinheit zurueck – unter der
45
+ * Millimeter-Klammer des Interpreters also als 4.233 statt 12. Deshalb auch
46
+ * das Lesen unter Punkt (am lebenden InDesign 21.5 am 04.09.2026 gesehen).
47
+ */
48
+ try {
49
+ size = IDC.inPoints(function () { return IDC.round(defaults.pointSize, 3); });
50
+ } catch (e) { size = null; }
51
+ return {
52
+ font: font.family,
53
+ font_style: font.style,
54
+ point_size: size,
55
+ paragraph_style: idcStyleName(defaults.appliedParagraphStyle),
56
+ character_style: idcStyleName(defaults.appliedCharacterStyle)
57
+ };
58
+ }
59
+
60
+ IDC.register('document_text_defaults', { needs_document: true, read_only: false, ruler: 'none' },
61
+ function (ctx) {
62
+ var args = ctx.args;
63
+ var doc = ctx.doc;
64
+ var defaults = doc.textDefaults;
65
+ var changed = [];
66
+ var problems = [];
67
+ var wantsChange = false;
68
+ var style, fontObject, fontText, after, current;
69
+
70
+ function problem(field, message) {
71
+ problems.push({ field: field, message: String(message) });
72
+ }
73
+
74
+ /* Erst das Absatzformat, dann die Schrift: so bleibt eine ausdrueckliche Schrift Vorrang. */
75
+ if (args.paragraph_style !== undefined && args.paragraph_style !== null) {
76
+ wantsChange = true;
77
+ style = IDC.paragraphStyleByName(doc, String(args.paragraph_style));
78
+ if (style === null) {
79
+ problem('paragraph_style', 'Paragraph style "' + args.paragraph_style + '" not found.');
80
+ } else {
81
+ try {
82
+ defaults.appliedParagraphStyle = style;
83
+ changed.push('paragraph_style');
84
+ } catch (e1) {
85
+ problem('paragraph_style', e1.message ? e1.message : e1);
86
+ }
87
+ }
88
+ }
89
+
90
+ if (args.font !== undefined && args.font !== null) {
91
+ wantsChange = true;
92
+ fontText = String(args.font);
93
+ try {
94
+ if (fontText.indexOf('\t') >= 0) {
95
+ fontObject = app.fonts.itemByName(fontText);
96
+ if (!fontObject || fontObject.isValid !== true) {
97
+ problem('font', 'Font "' + fontText.replace(/\t/g, ' / ') + '" is not installed.');
98
+ } else {
99
+ defaults.appliedFont = fontObject;
100
+ changed.push('font');
101
+ }
102
+ } else {
103
+ defaults.appliedFont = fontText;
104
+ current = idcReadDefaultFont(defaults);
105
+ if (current.family !== fontText) {
106
+ problem('font', 'Font family "' + fontText + '" is not installed; InDesign kept "' + current.family + '".');
107
+ } else {
108
+ changed.push('font');
109
+ }
110
+ }
111
+ } catch (e2) {
112
+ problem('font', e2.message ? e2.message : e2);
113
+ }
114
+ }
115
+
116
+ if (args.font_style !== undefined && args.font_style !== null) {
117
+ wantsChange = true;
118
+ try {
119
+ defaults.fontStyle = String(args.font_style);
120
+ current = idcReadDefaultFont(defaults);
121
+ if (current.style !== String(args.font_style)) {
122
+ problem('font_style', 'Font style "' + args.font_style + '" is not available for "' + current.family + '"; InDesign kept "' + current.style + '".');
123
+ } else {
124
+ changed.push('font_style');
125
+ }
126
+ } catch (e3) {
127
+ problem('font_style', e3.message ? e3.message : e3);
128
+ }
129
+ }
130
+
131
+ if (args.point_size !== undefined && args.point_size !== null) {
132
+ wantsChange = true;
133
+ if (typeof args.point_size !== 'number' || !(args.point_size > 0) || args.point_size > 1296) {
134
+ problem('point_size', 'point_size must be a number above 0 and up to 1296.');
135
+ } else {
136
+ try {
137
+ IDC.inPoints(function () { defaults.pointSize = args.point_size; });
138
+ changed.push('point_size');
139
+ } catch (e4) {
140
+ problem('point_size', e4.message ? e4.message : e4);
141
+ }
142
+ }
143
+ }
144
+
145
+ after = idcReadTextDefaults(defaults);
146
+ after.changed_fields = changed;
147
+ after.problems = problems;
148
+ after.read_only = !wantsChange;
149
+ if (changed.length > 0) { ctx.changes = { modified_document_ids: [Number(doc.id)] }; }
150
+ return after;
151
+ });
@@ -0,0 +1,174 @@
1
+ /* Wunschliste J2 Nr. 25 (Sitzung 8) – Auswahl lesen und setzen: get_selection, set_selection. */
2
+
3
+ var IDC_TEXT_CLASSES = ['Text', 'InsertionPoint', 'Character', 'Word', 'Line', 'Paragraph', 'TextStyleRange', 'TextColumn'];
4
+
5
+ function idcIsTextClass(name) {
6
+ var i;
7
+ for (i = 0; i < IDC_TEXT_CLASSES.length; i++) { if (IDC_TEXT_CLASSES[i] === name) { return true; } }
8
+ return false;
9
+ }
10
+
11
+ function idcDescribeTextSelection(doc, textObject) {
12
+ var out = { story_id: null, start_index: null, end_index: null, length: null, frame_id: null, text: '' };
13
+ var story = null, first = null, count = 0;
14
+ try { story = textObject.parentStory; out.story_id = Number(story.id); } catch (e0) { story = null; }
15
+ try { count = Number(textObject.characters.length); } catch (e1) { count = 0; }
16
+ out.length = count;
17
+ try {
18
+ if (count > 0) {
19
+ first = textObject.characters.item(0);
20
+ out.start_index = Number(first.index);
21
+ out.end_index = out.start_index + count;
22
+ } else {
23
+ out.start_index = Number(textObject.index);
24
+ out.end_index = out.start_index;
25
+ }
26
+ } catch (e2) { /* null */ }
27
+ try {
28
+ if (textObject.parentTextFrames && textObject.parentTextFrames.length > 0) { out.frame_id = Number(IDC.concrete(textObject.parentTextFrames[0]).id); }
29
+ } catch (e3) { /* null */ }
30
+ try { out.text = idcSnippet(textObject.contents, 200); } catch (e4) { out.text = ''; }
31
+ return out;
32
+ }
33
+
34
+ function idcDescribeSelectedItem(doc, item) {
35
+ var concrete = IDC.concrete(item);
36
+ var landing = IDC.landing(concrete);
37
+ var out = { frame_id: null, type: IDC.itemType(concrete), class_name: IDC.className(concrete), page_id: landing.page_id, page_name: landing.page_name,
38
+ on_parent_page: IDC.isOnParentSpread(concrete), coordinate_space: idcBoundsSpace(concrete), bounds_mm: null,
39
+ layer_name: idcLayerName(concrete), parent_group_id: null };
40
+ try { out.frame_id = Number(concrete.id); } catch (e0) { /* null */ }
41
+ try { out.bounds_mm = idcItemBounds(doc, concrete); } catch (e1) { out.bounds_mm = null; }
42
+ try { if (IDC.className(concrete.parent) === 'Group') { out.parent_group_id = Number(concrete.parent.id); } } catch (e2) { /* null */ }
43
+ return out;
44
+ }
45
+
46
+ function idcReadSelection(doc) {
47
+ var selection = null, i, entry, name, items = [], text = null, other = [];
48
+ try { selection = doc.selection; } catch (e0) { selection = null; }
49
+ if (selection === null || selection === undefined) { return { items: items, text: text, other: other }; }
50
+ /* Live 05.09.2026: doc.selection ist in ExtendScript ein Array, das IDC.isArray nicht erkennt (eigener Array-Typ) – ueber length/Klassennamen pruefen. */
51
+ if (!IDC.isArray(selection) && IDC.className(selection) !== 'Array') {
52
+ try { if (typeof selection.length !== 'number') { selection = [selection]; } } catch (e0b) { selection = [selection]; }
53
+ }
54
+ for (i = 0; i < selection.length; i++) {
55
+ entry = selection[i];
56
+ if (entry === null || entry === undefined) { continue; }
57
+ name = IDC.className(entry);
58
+ if (idcIsTextClass(name)) {
59
+ if (text === null) { text = idcDescribeTextSelection(doc, entry); text.class_name = name; }
60
+ continue;
61
+ }
62
+ try {
63
+ if (entry.id !== undefined && entry.geometricBounds !== undefined) { items.push(idcDescribeSelectedItem(doc, entry)); continue; }
64
+ } catch (e1) { /* kein Seitenobjekt */ }
65
+ other.push(name);
66
+ }
67
+ return { items: items, text: text, other: other };
68
+ }
69
+
70
+ IDC.register('get_selection', { needs_document: true, read_only: true, ruler: 'page' },
71
+ function (ctx) {
72
+ var doc = ctx.doc;
73
+ var active = false, read = idcReadSelection(doc), ids = [], i;
74
+ try { active = app.activeDocument !== null && Number(app.activeDocument.id) === Number(doc.id); } catch (e0) { active = false; }
75
+ for (i = 0; i < read.items.length; i++) { ids.push(read.items[i].frame_id); }
76
+ if (!active) { ctx.warn('This document is not the active one; the selection belongs to the document window and may be empty here.'); }
77
+ return {
78
+ count: read.items.length + (read.text === null ? 0 : 1) + read.other.length,
79
+ frame_ids: ids,
80
+ items: read.items,
81
+ text: read.text,
82
+ other: read.other,
83
+ is_active_document: active
84
+ };
85
+ });
86
+
87
+ IDC.register('set_selection', { needs_document: true, read_only: false, ruler: 'page' },
88
+ function (ctx) {
89
+ var args = ctx.args;
90
+ var doc = ctx.doc;
91
+ var ids = args.frame_ids, items = [], i, found, read, option = null, target = null;
92
+
93
+ if (args.clear === true) {
94
+ if (ids !== undefined && ids !== null) { IDC.fail('invalid_argument', 'clear true cannot be combined with frame_ids.', null); }
95
+ try {
96
+ if (typeof NothingEnum !== 'undefined') { doc.select(NothingEnum.NOTHING); }
97
+ else { doc.select([]); }
98
+ } catch (e0) {
99
+ IDC.fail('script_error', 'InDesign could not clear the selection: ' + String(e0.message ? e0.message : e0), null);
100
+ }
101
+ return { count: 0, frame_ids: [], items: [], text: null, cleared: true };
102
+ }
103
+ /* Sitzung 10 (Kleinigkeit aus Sitzung 9): Textbereich waehlen. */
104
+ if (args.text_range !== undefined && args.text_range !== null) {
105
+ if (ids !== undefined && ids !== null) { IDC.fail('invalid_argument', 'text_range cannot be combined with frame_ids.', null); }
106
+ return idcSelectTextRange(ctx, doc, args.text_range);
107
+ }
108
+ if (!IDC.isArray(ids) || ids.length === 0) { IDC.fail('invalid_argument', 'frame_ids must list at least one object id, or text_range {frame_id, start_index, end_index}, or clear true.', null); }
109
+ if (ids.length > 500) { IDC.fail('invalid_argument', 'frame_ids may list at most 500 objects.', { count: ids.length }); }
110
+ for (i = 0; i < ids.length; i++) {
111
+ if (typeof ids[i] !== 'number' || Math.floor(ids[i]) !== ids[i]) { IDC.fail('invalid_argument', 'frame_ids must contain whole numbers.', { frame_ids: ids }); }
112
+ found = IDC.itemById(doc, ids[i], 'any');
113
+ if (found === null) { IDC.fail('not_found', 'No object with frame_id ' + ids[i] + ' in this document.', { frame_id: ids[i] }); }
114
+ items.push(found.item);
115
+ }
116
+ try { if (typeof SelectionOptions !== 'undefined') { option = SelectionOptions.REPLACE_WITH; } } catch (e1) { option = null; }
117
+ target = items.length === 1 ? items[0] : items;
118
+ try {
119
+ if (option !== null) { doc.select(target, option); } else { doc.select(target); }
120
+ } catch (e2) {
121
+ IDC.fail('script_error', 'InDesign could not select the objects (hidden or locked layer? window not open?): ' + String(e2.message ? e2.message : e2), { frame_ids: ids });
122
+ }
123
+ read = idcReadSelection(doc);
124
+ if (read.items.length !== items.length) {
125
+ ctx.warn('InDesign reports ' + read.items.length + ' selected object(s) instead of ' + items.length + ' (objects on hidden or locked layers cannot be selected).');
126
+ }
127
+ ids = [];
128
+ for (i = 0; i < read.items.length; i++) { ids.push(read.items[i].frame_id); }
129
+ return { count: read.items.length, frame_ids: ids, items: read.items, text: read.text, cleared: false };
130
+ });
131
+
132
+ /*
133
+ * Textbereich (oder Einfuegemarke bei start_index == end_index) in der Story
134
+ * eines Rahmens waehlen: doc.select(text) bringt InDesign in den
135
+ * Textmodus und zeigt die Stelle. Rueckgabe wie get_selection.
136
+ */
137
+ function idcSelectTextRange(ctx, doc, spec) {
138
+ var found, frame, story, range, target, option = null, read, length = 0;
139
+ if (typeof spec !== 'object' || IDC.isArray(spec)) {
140
+ IDC.fail('invalid_argument', 'text_range must be an object {frame_id, start_index, end_index}.', null);
141
+ }
142
+ found = idcTextFrameArg(doc, spec, 'frame_id');
143
+ frame = found.item;
144
+ story = idcStoryOf(frame);
145
+ try { length = Number(story.characters.length); } catch (e0) { length = 0; }
146
+ if (typeof spec.start_index === 'number' && spec.start_index === spec.end_index) {
147
+ /* Einfuegemarke: itemByRange(start, start - 1) waere ein leerer Bereich, den InDesign ablehnt. */
148
+ if (Math.floor(spec.start_index) !== spec.start_index || spec.start_index < 0 || spec.start_index > length) {
149
+ IDC.fail('invalid_argument', 'start_index must be a whole number from 0 to the story length ' + length + '.', { start_index: spec.start_index, story_length: length });
150
+ }
151
+ range = { start: spec.start_index, end: spec.start_index, text: null };
152
+ } else {
153
+ range = idcRangeArg(story, spec, false);
154
+ }
155
+ if (range.start === range.end) {
156
+ try { target = story.insertionPoints.item(range.start); } catch (e1) {
157
+ IDC.fail('script_error', 'InDesign could not address insertion point ' + range.start + ': ' + String(e1.message ? e1.message : e1), { start_index: range.start });
158
+ }
159
+ } else {
160
+ target = range.text;
161
+ }
162
+ try { if (typeof SelectionOptions !== 'undefined') { option = SelectionOptions.REPLACE_WITH; } } catch (e2) { option = null; }
163
+ try {
164
+ if (option !== null) { doc.select(target, option); } else { doc.select(target); }
165
+ } catch (e3) {
166
+ IDC.fail('script_error', 'InDesign could not select the text (hidden or locked layer? window not open?): ' + String(e3.message ? e3.message : e3),
167
+ { frame_id: spec.frame_id, start_index: range.start, end_index: range.end });
168
+ }
169
+ read = idcReadSelection(doc);
170
+ if (read.text === null) {
171
+ read.text = { story_id: Number(story.id), start_index: range.start, end_index: range.end, length: range.end - range.start, frame_id: Number(frame.id), text: '', class_name: 'InsertionPoint' };
172
+ }
173
+ return { count: 1, frame_ids: [], items: [], text: read.text, cleared: false, story_length: length };
174
+ }