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,255 @@
1
+ /* Wunschliste J2 Nr. 23 (Sitzung 8) – Kopie speichern und Verpacken: save_document_as, package_document. */
2
+
3
+ function idcTargetFileArg(path, field, extensions) {
4
+ var file, lower, i, ok = false;
5
+ if (typeof path !== 'string' || path.length === 0) {
6
+ IDC.fail('invalid_argument', field + ' must be a non-empty file path.', null);
7
+ }
8
+ lower = path.toLowerCase();
9
+ for (i = 0; i < extensions.length; i++) {
10
+ if (lower.length > extensions[i].length && lower.substring(lower.length - extensions[i].length) === extensions[i]) { ok = true; break; }
11
+ }
12
+ if (!ok) {
13
+ IDC.fail('invalid_argument', field + ' must end in ' + extensions.join(' or ') + '.', { path: path });
14
+ }
15
+ file = IDC.fileFor(path);
16
+ if (!IDC.fileParentExists(file)) {
17
+ IDC.fail('io_error', 'The folder for ' + field + ' does not exist: ' + path, { path: path });
18
+ }
19
+ return file;
20
+ }
21
+
22
+ IDC.register('save_document_as', { needs_document: true, read_only: false, ruler: 'none' },
23
+ function (ctx) {
24
+ var args = ctx.args;
25
+ var doc = ctx.doc;
26
+ var mode = args.mode === undefined || args.mode === null ? 'copy' : args.mode;
27
+ var overwrite = args.overwrite === true;
28
+ var extensions = ['.indd', '.indt'];
29
+ var file, own = idcDocumentPath(doc), nameBefore, existed, result;
30
+
31
+ if (mode !== 'copy' && mode !== 'switch') {
32
+ IDC.fail('invalid_argument', 'mode must be copy (the open document keeps its file) or switch (the open document continues under the new name).', { mode: mode });
33
+ }
34
+ file = idcTargetFileArg(args.path, 'path', extensions);
35
+ if (own !== '' && IDC.fsPath(file) === own) {
36
+ IDC.fail('invalid_argument', 'path is the file of the document itself; use save_document.', { path: args.path });
37
+ }
38
+ existed = file.exists === true;
39
+ if (existed && !overwrite) {
40
+ IDC.fail('io_error', 'A file exists at path already; pass overwrite true to replace it.', { path: args.path });
41
+ }
42
+ try { nameBefore = String(doc.name); } catch (e0) { nameBefore = ''; }
43
+ try {
44
+ if (mode === 'copy') {
45
+ doc.saveACopy(file, args.template === true);
46
+ } else {
47
+ doc.save(file, args.template === true);
48
+ }
49
+ } catch (e1) {
50
+ IDC.fail('io_error', 'InDesign could not save ' + (mode === 'copy' ? 'a copy' : 'the document') + ' to ' + args.path + ': ' + String(e1.message ? e1.message : e1), { path: args.path });
51
+ }
52
+ result = {
53
+ document_id: Number(doc.id),
54
+ mode: mode,
55
+ path: IDC.fsPath(file) || args.path,
56
+ bytes: IDC.fileSize(file),
57
+ overwritten: existed,
58
+ template: args.template === true,
59
+ name: String(doc.name),
60
+ document_path: idcDocumentPath(doc),
61
+ name_before: nameBefore
62
+ };
63
+ if (mode === 'copy' && result.document_path === '') {
64
+ ctx.warn('The open document itself is still unsaved (untitled); only the copy was written.');
65
+ }
66
+ ctx.changes = mode === 'copy' ? { written_files: [result.path] } : { saved_document_ids: [Number(doc.id)], written_files: [result.path] };
67
+ return result;
68
+ });
69
+
70
+ IDC.register('package_document', { needs_document: true, read_only: false, ruler: 'none' },
71
+ function (ctx) {
72
+ var args = ctx.args;
73
+ var doc = ctx.doc;
74
+ var folder, issues, ok = false, pdfPreset = '', result, count = 0, reportFile;
75
+ var copyFonts = args.copy_fonts !== false;
76
+ var copyGraphics = args.copy_graphics !== false;
77
+ var copyProfiles = args.copy_profiles === true;
78
+ var updateGraphics = args.update_graphics !== false;
79
+ var includeHidden = args.include_hidden_layers === true;
80
+ var ignorePreflight = args.ignore_preflight_errors !== false;
81
+ var createReport = args.create_report !== false;
82
+ var includeIdml = args.include_idml === true;
83
+ var includePdf = args.include_pdf === true;
84
+
85
+ if (typeof args.folder_path !== 'string' || args.folder_path.length === 0) {
86
+ IDC.fail('invalid_argument', 'folder_path is required (the package folder InDesign creates or fills).', null);
87
+ }
88
+ if (idcDocumentPath(doc) === '') {
89
+ IDC.fail('invalid_state', 'The document has never been saved; save_document with path first.', null);
90
+ }
91
+ folder = IDC.folderFor(args.folder_path);
92
+ if (!IDC.fileParentExists(folder)) {
93
+ IDC.fail('io_error', 'The parent folder of folder_path does not exist: ' + args.folder_path, { folder_path: args.folder_path });
94
+ }
95
+ if (includePdf) {
96
+ if (args.pdf_preset !== undefined && args.pdf_preset !== null) {
97
+ if (typeof args.pdf_preset !== 'string' || args.pdf_preset === '') { IDC.fail('invalid_argument', 'pdf_preset must be a preset name.', null); }
98
+ if (idcPdfPresetByName(args.pdf_preset) === null) {
99
+ IDC.fail('not_found', 'PDF preset "' + args.pdf_preset + '" not found.', { pdf_preset: args.pdf_preset, available: idcPdfPresetNames() });
100
+ }
101
+ pdfPreset = args.pdf_preset;
102
+ } else {
103
+ try { pdfPreset = String(idcHighQualityPreset().name); } catch (e0) { pdfPreset = ''; }
104
+ }
105
+ }
106
+ issues = idcDocumentIssues(doc);
107
+ if (issues.missing_links && issues.missing_links.length > 0) {
108
+ ctx.warn(issues.missing_links.length + ' missing link(s) cannot be packaged: ' + issues.missing_links.join(', '));
109
+ }
110
+ if (issues.missing_fonts && issues.missing_fonts.length > 0) {
111
+ ctx.warn(issues.missing_fonts.length + ' missing font(s): ' + issues.missing_fonts.join(', '));
112
+ }
113
+ try {
114
+ ok = doc.packageForPrint(folder, copyFonts, copyGraphics, copyProfiles, updateGraphics, includeHidden, ignorePreflight, createReport,
115
+ includeIdml, includePdf, pdfPreset, false, '', true);
116
+ } catch (e1) {
117
+ IDC.fail('io_error', 'InDesign could not package the document: ' + String(e1.message ? e1.message : e1), { folder_path: args.folder_path });
118
+ }
119
+ if (ok !== true) {
120
+ IDC.fail('io_error', 'InDesign reported that packaging did not succeed (preflight errors? missing links?).', { folder_path: args.folder_path, missing_links: issues.missing_links, missing_fonts: issues.missing_fonts });
121
+ }
122
+ result = {
123
+ document_id: Number(doc.id),
124
+ folder_path: IDC.fsPath(folder) || args.folder_path,
125
+ copy_fonts: copyFonts,
126
+ copy_graphics: copyGraphics,
127
+ copy_profiles: copyProfiles,
128
+ update_graphics: updateGraphics,
129
+ include_hidden_layers: includeHidden,
130
+ ignore_preflight_errors: ignorePreflight,
131
+ create_report: createReport,
132
+ include_idml: includeIdml,
133
+ include_pdf: includePdf,
134
+ pdf_preset: includePdf ? pdfPreset : null,
135
+ missing_links: issues.missing_links || [],
136
+ missing_fonts: issues.missing_fonts || [],
137
+ document_path: idcDocumentPath(doc)
138
+ };
139
+ try {
140
+ count = folder.getFiles ? folder.getFiles().length : null;
141
+ } catch (e2) { count = null; }
142
+ result.entries_in_folder = count;
143
+
144
+ /* Demo-Heft-Befund 6 (Sitzung 10): welche Schriften liegen im Paket, welche nicht. */
145
+ var fontReport = idcPackagedFontReport(doc, folder, copyFonts);
146
+ result.fonts = fontReport.fonts;
147
+ result.fonts_total = fontReport.fonts.length;
148
+ result.fonts_packaged_count = fontReport.packaged_count;
149
+ result.fonts_not_packaged = fontReport.not_packaged;
150
+ result.fonts_folder = fontReport.folder;
151
+ if (copyFonts && fontReport.not_packaged.length > 0) {
152
+ ctx.warn(fontReport.not_packaged.length + ' font(s) are not in the package folder: ' + fontReport.not_packaged.join(', ') +
153
+ ' – fonts from Adobe Fonts (Creative Cloud) and other protected fonts are never copied by InDesign; the printer needs them installed.');
154
+ }
155
+ ctx.changes = { written_folders: [result.folder_path] };
156
+ return result;
157
+ });
158
+
159
+ /* ------------------------------------------- Schriften im Paket (Befund 6) */
160
+
161
+ /*
162
+ * FontTypes nach indesignjs.de (Adobes Seite liefert 404; live am 05.09.2026
163
+ * zu bestaetigen): TYPE_1, TRUETYPE, CID, ATC, BITMAP, OCF, OPENTYPE_CFF,
164
+ * OPENTYPE_TT, OPENTYPE_CID, UNKNOWN.
165
+ */
166
+ function idcFontTypeName(value) {
167
+ if (typeof FontTypes === 'undefined' || value === undefined || value === null) { return null; }
168
+ if (IDC.enumIs(value, FontTypes.TYPE_1)) { return 'type1'; }
169
+ if (IDC.enumIs(value, FontTypes.TRUETYPE)) { return 'truetype'; }
170
+ if (IDC.enumIs(value, FontTypes.OPENTYPE_CFF)) { return 'opentype_cff'; }
171
+ if (IDC.enumIs(value, FontTypes.OPENTYPE_TT)) { return 'opentype_tt'; }
172
+ if (IDC.enumIs(value, FontTypes.OPENTYPE_CID)) { return 'opentype_cid'; }
173
+ if (IDC.enumIs(value, FontTypes.CID)) { return 'cid'; }
174
+ if (IDC.enumIs(value, FontTypes.ATC)) { return 'atc'; }
175
+ if (IDC.enumIs(value, FontTypes.BITMAP)) { return 'bitmap'; }
176
+ if (IDC.enumIs(value, FontTypes.OCF)) { return 'ocf'; }
177
+ return 'unknown';
178
+ }
179
+
180
+ function idcBaseName(path) {
181
+ return String(path).replace(/^.*[\/\\]/, '');
182
+ }
183
+
184
+ /* Dateinamen (klein geschrieben) im Ordner "Document fonts" des Pakets. */
185
+ function idcPackagedFontFiles(folder) {
186
+ var out = { exists: false, names: [] };
187
+ var base = IDC.fsPath(folder);
188
+ var sub, files, i, entry, name;
189
+ if (base === '') { return out; }
190
+ try { sub = IDC.folderFor(base + '/Document fonts'); } catch (e0) { return out; }
191
+ try { if (sub.exists !== true) { return out; } } catch (e1) { return out; }
192
+ out.exists = true;
193
+ try {
194
+ files = sub.getFiles();
195
+ for (i = 0; i < files.length; i++) {
196
+ entry = files[i];
197
+ name = null;
198
+ try { if (entry && entry.name !== undefined) { name = String(entry.name); } } catch (e2) { name = null; }
199
+ if (name === null) { name = idcBaseName(entry); }
200
+ out.names.push(name.toLowerCase());
201
+ }
202
+ } catch (e3) { /* Liste bleibt, so weit sie kam */ }
203
+ return out;
204
+ }
205
+
206
+ function idcIsAdobeFontsLocation(location) {
207
+ var text = String(location).toLowerCase();
208
+ return text.indexOf('/coresync/') >= 0 || text.indexOf('livetype') >= 0 || text.indexOf('/adobe fonts') >= 0;
209
+ }
210
+
211
+ /* Schriften des Dokuments mit Paketstatus. */
212
+ function idcPackagedFontReport(doc, folder, copyFonts) {
213
+ var files = copyFonts ? idcPackagedFontFiles(folder) : { exists: false, names: [] };
214
+ var out = { fonts: [], packaged_count: 0, not_packaged: [], folder: files.exists ? IDC.fsPath(folder) + '/Document fonts' : null };
215
+ var i, j, font, entry, location, base, status;
216
+ try {
217
+ for (i = 0; i < doc.fonts.length; i++) {
218
+ font = doc.fonts.item(i);
219
+ entry = { family: null, style: null, name: null, postscript_name: null, type: null, status: 'installed', location: null, packaged: null, reason: null };
220
+ try { entry.family = String(font.fontFamily); } catch (e0) { /* bleibt null */ }
221
+ try { entry.style = String(font.fontStyleName); } catch (e1) { /* bleibt null */ }
222
+ try { entry.name = String(font.name); } catch (e2) { /* bleibt null */ }
223
+ try { entry.postscript_name = String(font.postscriptName); } catch (e3) { /* bleibt null */ }
224
+ try { entry.type = idcFontTypeName(font.fontType); } catch (e4) { /* bleibt null */ }
225
+ try { status = font.status; entry.status = idcFontStatusName(status); } catch (e5) { entry.status = 'installed'; }
226
+ location = '';
227
+ try { location = String(font.location); } catch (e6) { location = ''; }
228
+ entry.location = location === '' ? null : location;
229
+ if (!copyFonts) {
230
+ entry.packaged = false;
231
+ entry.reason = 'copy_fonts_off';
232
+ } else if (entry.status !== 'installed') {
233
+ entry.packaged = false;
234
+ entry.reason = 'not_installed';
235
+ } else if (location === '' || !files.exists) {
236
+ entry.packaged = null;
237
+ entry.reason = files.exists ? 'location_unknown' : 'fonts_folder_missing';
238
+ } else {
239
+ base = idcBaseName(location).toLowerCase();
240
+ entry.packaged = false;
241
+ for (j = 0; j < files.names.length; j++) {
242
+ if (files.names[j] === base) { entry.packaged = true; break; }
243
+ }
244
+ if (!entry.packaged) {
245
+ entry.reason = idcIsAdobeFontsLocation(location) ? 'adobe_fonts' : 'not_copied';
246
+ }
247
+ }
248
+ if (entry.packaged === true) { out.packaged_count += 1; }
249
+ if (entry.packaged === false && entry.reason !== 'copy_fonts_off') { out.not_packaged.push(entry.name === null ? String(entry.family) : entry.name); }
250
+ out.fonts.push(entry);
251
+ }
252
+ } catch (e7) { /* Liste bleibt, so weit sie kam */ }
253
+ return out;
254
+ }
255
+
@@ -0,0 +1,379 @@
1
+ /* Bereich A – Export und Preflight. */
2
+
3
+ /* ------------------------------------------------------------ export_pdf */
4
+
5
+ /*
6
+ * Vorgaben ohne Angabe im Aufruf: Hauptpreset = lokalisiertes "High Quality
7
+ * Print", kein zweiter Durchgang. Hauswerte (Preset-Namen, Groessenschwelle)
8
+ * kommen aus der Serverkonfiguration und stehen dann schon in den args
9
+ * (E-54, A-6.3).
10
+ */
11
+ /* Lokalisierte Namen der mitgelieferten Vorgabe "High Quality Print". */
12
+ var IDC_HQ_PRESET_PATTERN = /high quality print|qualitativ hochwertiger druck|hochwertiger druck/i;
13
+
14
+ function idcPdfPresetByName(name) {
15
+ var preset;
16
+ if (typeof name !== 'string' || name.length === 0) { return null; }
17
+ try {
18
+ preset = app.pdfExportPresets.itemByName(name);
19
+ if (preset && preset.isValid === true) { return preset; }
20
+ } catch (e) { /* nicht vorhanden */ }
21
+ return null;
22
+ }
23
+
24
+ function idcPdfPresetNames() {
25
+ var names = [];
26
+ var i;
27
+ try {
28
+ for (i = 0; i < app.pdfExportPresets.length; i++) {
29
+ names.push(String(app.pdfExportPresets.item(i).name));
30
+ }
31
+ } catch (e) { /* leer */ }
32
+ return names;
33
+ }
34
+
35
+ function idcHighQualityPreset() {
36
+ var i, preset;
37
+ try {
38
+ for (i = 0; i < app.pdfExportPresets.length; i++) {
39
+ preset = app.pdfExportPresets.item(i);
40
+ if (IDC_HQ_PRESET_PATTERN.test(String(preset.name))) { return preset; }
41
+ }
42
+ } catch (e) { /* keine */ }
43
+ return null;
44
+ }
45
+
46
+ /* Seitenindex zu einem Namen aus einem Seitenbereich; "+3" = dritte Seite absolut. */
47
+ function idcPageIndexByName(doc, text) {
48
+ var name = text;
49
+ var i, n;
50
+ if (name.charAt(0) === '+') {
51
+ n = Number(name.substr(1));
52
+ if (isNaN(n) || n < 1 || n > doc.pages.length) { return -1; }
53
+ return n - 1;
54
+ }
55
+ for (i = 0; i < doc.pages.length; i++) {
56
+ if (String(doc.pages.item(i).name) === name) { return i; }
57
+ }
58
+ return -1;
59
+ }
60
+
61
+ function idcTrim(text) {
62
+ return String(text).replace(/^\s+/, '').replace(/\s+$/, '');
63
+ }
64
+
65
+ /*
66
+ * Seitenbereich wie in InDesigns Exportdialog ("6-8", "1, 3-5", "+2-+4")
67
+ * gegen die angezeigten Seitennamen aufloesen. Liefert die Zahl der Seiten.
68
+ */
69
+ function idcCountPagesInRange(doc, rangeText) {
70
+ var parts = String(rangeText).split(',');
71
+ var count = 0;
72
+ var p, part, single, from, to, cut, left, right, found;
73
+
74
+ for (p = 0; p < parts.length; p++) {
75
+ part = idcTrim(parts[p]);
76
+ if (part === '') { continue; }
77
+ single = idcPageIndexByName(doc, part);
78
+ if (single >= 0) { count += 1; continue; }
79
+
80
+ found = false;
81
+ for (cut = 1; cut < part.length; cut++) {
82
+ if (part.charAt(cut) !== '-') { continue; }
83
+ left = idcTrim(part.substr(0, cut));
84
+ right = idcTrim(part.substr(cut + 1));
85
+ from = idcPageIndexByName(doc, left);
86
+ to = idcPageIndexByName(doc, right);
87
+ if (from >= 0 && to >= 0) { found = true; break; }
88
+ }
89
+ if (!found) {
90
+ IDC.fail('invalid_argument',
91
+ 'page_range "' + rangeText + '": "' + part + '" names no page of this document (page names are the printed page numbers; "+3" addresses the third page).',
92
+ { page_range: String(rangeText), part: part });
93
+ }
94
+ if (to < from) {
95
+ IDC.fail('invalid_argument', 'page_range "' + rangeText + '": "' + part + '" runs backwards.',
96
+ { page_range: String(rangeText), part: part });
97
+ }
98
+ count += to - from + 1;
99
+ }
100
+ if (count === 0) {
101
+ IDC.fail('invalid_argument', 'page_range "' + rangeText + '" selects no page.', { page_range: String(rangeText) });
102
+ }
103
+ return count;
104
+ }
105
+
106
+ function idcExportPdfOnce(doc, file, preset, rangeText) {
107
+ var prefs = app.pdfExportPreferences;
108
+ var prevRange = null;
109
+ var prevView = null;
110
+ var rangeSet = false;
111
+ var viewSet = false;
112
+
113
+ try {
114
+ try {
115
+ prevView = prefs.viewPDF;
116
+ prefs.viewPDF = false;
117
+ viewSet = true;
118
+ } catch (e0) { /* nicht jede Fassung kennt viewPDF */ }
119
+ prevRange = prefs.pageRange;
120
+ prefs.pageRange = rangeText === null ? PageRange.ALL_PAGES : rangeText;
121
+ rangeSet = true;
122
+ if (preset !== null) { doc.exportFile(ExportFormat.PDF_TYPE, file, false, preset); }
123
+ else { doc.exportFile(ExportFormat.PDF_TYPE, file, false); }
124
+ } catch (e) {
125
+ IDC.fail('io_error', 'PDF export failed: ' + String(e.message ? e.message : e),
126
+ { path: IDC.fsPath(file), preset: preset === null ? null : String(preset.name) });
127
+ } finally {
128
+ if (rangeSet) { try { prefs.pageRange = prevRange; } catch (e1) { /* egal */ } }
129
+ if (viewSet) { try { prefs.viewPDF = prevView; } catch (e2) { /* egal */ } }
130
+ }
131
+ /*
132
+ * Frisches File-Objekt: ExtendScript haelt length im Objekt fest, nach dem
133
+ * zweiten Export stand sonst noch die Groesse des ersten drin (am lebenden
134
+ * InDesign 21.5 am 04.09.2026 gesehen).
135
+ */
136
+ return IDC.fileSize(IDC.fileFor(IDC.fsPath(file)));
137
+ }
138
+
139
+ IDC.register('export_pdf', { needs_document: true, read_only: false, ruler: 'none' },
140
+ function (ctx) {
141
+ var args = ctx.args;
142
+ var doc = ctx.doc;
143
+ var file, rangeText, exportedCount, preset, presetUsed, bytes, fallbackName, threshold, fallback;
144
+
145
+ if (typeof args.path !== 'string' || args.path.length === 0) {
146
+ IDC.fail('invalid_argument', 'path is required (the target file path, not output_path).', null);
147
+ }
148
+ file = IDC.fileFor(args.path);
149
+ if (!IDC.fileParentExists(file)) {
150
+ IDC.fail('io_error', 'The folder for the target path does not exist: ' + args.path, { path: args.path });
151
+ }
152
+
153
+ rangeText = null;
154
+ if (args.page_range !== undefined && args.page_range !== null && idcTrim(args.page_range) !== ''
155
+ && idcTrim(args.page_range).toLowerCase() !== 'all') {
156
+ rangeText = idcTrim(args.page_range);
157
+ exportedCount = idcCountPagesInRange(doc, rangeText);
158
+ } else {
159
+ exportedCount = doc.pages.length;
160
+ }
161
+
162
+ /* Preset-Kette: Wunschpreset, sonst High Quality Print, sonst aktuelle Vorgaben. */
163
+ var wanted = null;
164
+ if (args.preset !== undefined && args.preset !== null && String(args.preset) !== '') {
165
+ wanted = String(args.preset);
166
+ }
167
+ preset = wanted === null ? null : idcPdfPresetByName(wanted);
168
+ presetUsed = wanted;
169
+ if (preset === null) {
170
+ preset = idcHighQualityPreset();
171
+ if (preset !== null) {
172
+ presetUsed = String(preset.name);
173
+ if (wanted !== null) {
174
+ ctx.warn('PDF preset "' + wanted + '" not found; used "' + presetUsed + '" instead. Available: ' + idcPdfPresetNames().join(', '));
175
+ }
176
+ } else {
177
+ presetUsed = '[current export preferences]';
178
+ if (wanted !== null) {
179
+ ctx.warn('PDF preset "' + wanted + '" not found and no High Quality Print preset either; exported with the current export preferences. Available: ' + idcPdfPresetNames().join(', '));
180
+ } else {
181
+ ctx.warn('No High Quality Print preset found; exported with the current export preferences. Available: ' + idcPdfPresetNames().join(', '));
182
+ }
183
+ }
184
+ }
185
+
186
+ bytes = idcExportPdfOnce(doc, file, preset, rangeText);
187
+
188
+ fallbackName = args.fallback_preset === undefined || args.fallback_preset === null
189
+ ? '' : String(args.fallback_preset);
190
+ threshold = args.fallback_over_bytes === undefined || args.fallback_over_bytes === null
191
+ ? 0 : Number(args.fallback_over_bytes);
192
+
193
+ if (fallbackName !== '' && threshold > 0 && bytes !== null && bytes > threshold) {
194
+ fallback = idcPdfPresetByName(fallbackName);
195
+ if (fallback === null) {
196
+ ctx.warn('PDF is ' + bytes + ' bytes (over ' + threshold + '), but fallback preset "' + fallbackName + '" was not found; first export kept.');
197
+ } else {
198
+ ctx.warn('PDF was ' + bytes + ' bytes (over ' + threshold + '); exported again with "' + fallbackName + '". A spread preset turns single pages into spreads (Doppelseiten) – check exported_page_count.');
199
+ bytes = idcExportPdfOnce(doc, file, fallback, rangeText);
200
+ presetUsed = fallbackName;
201
+ if (bytes !== null && bytes > threshold) {
202
+ ctx.warn('Still ' + bytes + ' bytes after the fallback export.');
203
+ }
204
+ }
205
+ }
206
+
207
+ ctx.changes = { exported_files: [IDC.fsPath(file)] };
208
+
209
+ return {
210
+ path: IDC.fsPath(file),
211
+ page_count: exportedCount,
212
+ exported_page_count: exportedCount,
213
+ document_page_count: doc.pages.length,
214
+ bytes: bytes,
215
+ preset_used: presetUsed,
216
+ page_range_used: rangeText === null ? 'all' : rangeText
217
+ };
218
+ });
219
+
220
+ /* ---------------------------------------------------- preflight_document */
221
+
222
+ function idcPreflightProfileNames() {
223
+ var names = [];
224
+ var i;
225
+ try {
226
+ for (i = 0; i < app.preflightProfiles.length; i++) {
227
+ names.push(String(app.preflightProfiles.item(i).name));
228
+ }
229
+ } catch (e) { /* leer */ }
230
+ return names;
231
+ }
232
+
233
+ function idcText(value) {
234
+ if (value === null || value === undefined) { return null; }
235
+ return String(value);
236
+ }
237
+
238
+ /*
239
+ * aggregatedResults ist [Dokumentname, Profilname, Eintraege[]]. Die Form
240
+ * eines Eintrags ist in Adobes Dokumentation nur als "Array of Strings or
241
+ * Numbers or Arrays" beschrieben; deshalb werden beide beobachtete Formen
242
+ * gelesen: [Kategorie, Beschreibung, Seite, Objekt] und
243
+ * [Kategorie, Beschreibung, [[Seite, Objekt], ...]].
244
+ */
245
+ function idcParsePreflightFindings(raw) {
246
+ var out = [];
247
+ var list, i, entry, category, description, rest, j, sub;
248
+
249
+ if (!IDC.isArray(raw)) { return out; }
250
+ list = (raw.length >= 3 && IDC.isArray(raw[2])) ? raw[2] : raw;
251
+
252
+ for (i = 0; i < list.length; i++) {
253
+ entry = list[i];
254
+ if (!IDC.isArray(entry)) { continue; }
255
+ category = idcText(entry[0]);
256
+ description = idcText(entry[1]);
257
+ rest = entry.length > 2 ? entry[2] : undefined;
258
+
259
+ if (IDC.isArray(rest)) {
260
+ if (rest.length === 0) {
261
+ out.push({ category: category, description: description, page_name: null, item: null });
262
+ }
263
+ for (j = 0; j < rest.length; j++) {
264
+ sub = rest[j];
265
+ if (IDC.isArray(sub)) {
266
+ out.push({
267
+ category: category,
268
+ description: description,
269
+ page_name: sub.length > 0 ? idcText(sub[0]) : null,
270
+ item: sub.length > 1 ? idcText(sub[1]) : null
271
+ });
272
+ } else {
273
+ out.push({
274
+ category: category,
275
+ description: description,
276
+ page_name: idcText(sub),
277
+ item: entry.length > 3 ? idcText(entry[3]) : null
278
+ });
279
+ }
280
+ }
281
+ } else {
282
+ out.push({
283
+ category: category,
284
+ description: description,
285
+ page_name: rest === undefined ? null : idcText(rest),
286
+ item: entry.length > 3 ? idcText(entry[3]) : null
287
+ });
288
+ }
289
+ }
290
+ return out;
291
+ }
292
+
293
+ IDC.register('preflight_document', { needs_document: true, read_only: true, ruler: 'none' },
294
+ function (ctx) {
295
+ var args = ctx.args;
296
+ var doc = ctx.doc;
297
+ var profiles = idcPreflightProfileNames();
298
+ var maxFindings, profile, working, process, waited, summary, raw, findings, capped;
299
+
300
+ if (args.list_profiles_only === true) {
301
+ return { profiles: profiles, profile_count: profiles.length };
302
+ }
303
+
304
+ maxFindings = args.max_findings === undefined || args.max_findings === null ? 100 : Number(args.max_findings);
305
+ if (isNaN(maxFindings) || maxFindings < 1 || maxFindings > 500) {
306
+ IDC.fail('invalid_argument', 'max_findings must be between 1 and 500.', { max_findings: args.max_findings });
307
+ }
308
+
309
+ profile = null;
310
+ if (args.profile_name !== undefined && args.profile_name !== null && String(args.profile_name) !== '') {
311
+ try {
312
+ profile = app.preflightProfiles.itemByName(String(args.profile_name));
313
+ if (!profile || profile.isValid !== true) { profile = null; }
314
+ } catch (e) { profile = null; }
315
+ if (profile === null) {
316
+ IDC.fail('not_found', 'Preflight profile "' + args.profile_name + '" not found.',
317
+ { profile_name: String(args.profile_name), available: profiles });
318
+ }
319
+ } else {
320
+ /*
321
+ * Das Arbeitsprofil des Dokuments kann ein ungueltiges Objekt sein –
322
+ * oder laut Adobes DOM auch nur ein Name (String). Beides abfangen.
323
+ */
324
+ try {
325
+ working = doc.preflightOptions.preflightWorkingProfile;
326
+ if (typeof working === 'string' && working !== '') {
327
+ working = app.preflightProfiles.itemByName(working);
328
+ }
329
+ if (working && working.isValid === true) { profile = working; }
330
+ } catch (e2) { profile = null; }
331
+ if (profile === null) {
332
+ if (app.preflightProfiles.length === 0) {
333
+ IDC.fail('invalid_state', 'InDesign has no preflight profile at all.', null);
334
+ }
335
+ profile = app.preflightProfiles.item(0);
336
+ ctx.warn('The document has no valid working profile; used the first available profile "' + String(profile.name) + '".');
337
+ }
338
+ }
339
+
340
+ try {
341
+ process = app.preflightProcesses.add(doc, profile);
342
+ } catch (e3) {
343
+ IDC.fail('script_error', 'Could not start the preflight process: ' + String(e3.message ? e3.message : e3), null);
344
+ }
345
+
346
+ try {
347
+ /*
348
+ * waitForProcess(sekunden) blockiert, bis der Prozess fertig ist oder
349
+ * die Zeit um ist. Adobes Dokumentation und alle bekannten Praxisskripte
350
+ * rufen es genau einmal auf und werten den Rueckgabewert nicht aus –
351
+ * am lebenden InDesign 21.5 kam er sofort und ohne true zurueck, eine
352
+ * Warteschleife darauf drehte leer (04.09.2026). Deshalb: einmal warten,
353
+ * dann lesen.
354
+ */
355
+ var startedAt = new Date().getTime();
356
+ try { process.waitForProcess(600); } catch (eWait) { /* dann eben ohne Warten lesen */ }
357
+ waited = Math.round((new Date().getTime() - startedAt) / 1000);
358
+ if (waited >= 600) {
359
+ IDC.fail('timeout', 'Preflight did not finish within ' + waited + ' seconds.', null);
360
+ }
361
+ summary = idcTrim(String(process.processResults));
362
+ raw = process.aggregatedResults;
363
+ findings = idcParsePreflightFindings(raw);
364
+ } finally {
365
+ try { process.remove(); } catch (e4) { /* Prozess bleibt dann stehen, stoert nicht */ }
366
+ }
367
+
368
+ capped = findings.length > maxFindings ? findings.slice(0, maxFindings) : findings;
369
+ if (findings.length > maxFindings) {
370
+ ctx.warn(findings.length + ' findings, only the first ' + maxFindings + ' returned (max_findings).');
371
+ }
372
+
373
+ return {
374
+ profile_used: String(profile.name),
375
+ summary: summary,
376
+ error_count: findings.length,
377
+ findings: capped
378
+ };
379
+ });