layoutbridge-light 0.0.0-stage → 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 (88) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +26 -0
  3. package/README.de.md +338 -0
  4. package/README.md +325 -2
  5. package/THIRD-PARTY-NOTICES.md +2878 -0
  6. package/dist/src/mcp/catalog.js +126 -0
  7. package/dist/src/mcp/config.js +211 -0
  8. package/dist/src/mcp/dispatch.js +164 -0
  9. package/dist/src/mcp/epub-check.js +331 -0
  10. package/dist/src/mcp/epub-orientation.js +132 -0
  11. package/dist/src/mcp/license/free-tools.js +16 -0
  12. package/dist/src/mcp/license/gate.js +210 -0
  13. package/dist/src/mcp/license/index.js +107 -0
  14. package/dist/src/mcp/license/instance-name.js +57 -0
  15. package/dist/src/mcp/license/key-source.js +59 -0
  16. package/dist/src/mcp/license/lemonsqueezy-verifier.js +207 -0
  17. package/dist/src/mcp/license/placeholder-verifier.js +10 -0
  18. package/dist/src/mcp/license/polar-ids.js +100 -0
  19. package/dist/src/mcp/license/polar-verifier.js +285 -0
  20. package/dist/src/mcp/license/product-ids.js +73 -0
  21. package/dist/src/mcp/license/state-store.js +91 -0
  22. package/dist/src/mcp/license/types.js +11 -0
  23. package/dist/src/mcp/logging.js +271 -0
  24. package/dist/src/mcp/product-info.js +54 -0
  25. package/dist/src/mcp/product.js +56 -0
  26. package/dist/src/mcp/server.js +253 -0
  27. package/dist/src/mcp/setup.js +680 -0
  28. package/dist/src/mcp/start.cjs +117 -0
  29. package/dist/src/mcp/tool-definition.js +2 -0
  30. package/dist/src/mcp/tools.js +2425 -0
  31. package/dist/src/mcp/typography-rules.js +70 -0
  32. package/dist/src/transport/com.js +250 -0
  33. package/dist/src/transport/osascript.js +171 -0
  34. package/dist/src/transport/queue.js +19 -0
  35. package/dist/src/transport/script-bundle.js +92 -0
  36. package/dist/src/transport/transport.js +30 -0
  37. package/indesign/commands/10-app.js +6 -0
  38. package/indesign/commands/20-documents.js +517 -0
  39. package/indesign/commands/22-document-setup.js +204 -0
  40. package/indesign/commands/23-save-package.js +258 -0
  41. package/indesign/commands/25-export.js +448 -0
  42. package/indesign/commands/26-export-image.js +202 -0
  43. package/indesign/commands/27-textdefaults.js +151 -0
  44. package/indesign/commands/28-selection.js +174 -0
  45. package/indesign/commands/30-pages.js +1032 -0
  46. package/indesign/commands/31-view.js +210 -0
  47. package/indesign/commands/32-sections.js +203 -0
  48. package/indesign/commands/35-parents.js +428 -0
  49. package/indesign/commands/37-layout.js +837 -0
  50. package/indesign/commands/40-frames.js +851 -0
  51. package/indesign/commands/41-shapes.js +505 -0
  52. package/indesign/commands/42-groups.js +117 -0
  53. package/indesign/commands/43-layers.js +310 -0
  54. package/indesign/commands/44-effects.js +274 -0
  55. package/indesign/commands/45-frame-options.js +247 -0
  56. package/indesign/commands/47-anchored.js +331 -0
  57. package/indesign/commands/50-modify.js +781 -0
  58. package/indesign/commands/52-align.js +139 -0
  59. package/indesign/commands/55-fit.js +106 -0
  60. package/indesign/commands/60-images.js +1128 -0
  61. package/indesign/commands/62-embed.js +120 -0
  62. package/indesign/commands/69-placeholders.js +127 -0
  63. package/indesign/commands/70-text.js +675 -0
  64. package/indesign/commands/71-outlines.js +92 -0
  65. package/indesign/commands/72-place-text.js +476 -0
  66. package/indesign/commands/73-story-lock.js +157 -0
  67. package/indesign/commands/74-stories.js +121 -0
  68. package/indesign/commands/75-tables.js +676 -0
  69. package/indesign/commands/76-text-variables.js +260 -0
  70. package/indesign/commands/77-story-options.js +86 -0
  71. package/indesign/commands/78-frame-lines.js +452 -0
  72. package/indesign/commands/79-check-typography.js +341 -0
  73. package/indesign/commands/80-styles.js +1865 -0
  74. package/indesign/commands/81-composition.js +355 -0
  75. package/indesign/commands/82-object-styles.js +263 -0
  76. package/indesign/commands/83-drop-caps.js +87 -0
  77. package/indesign/commands/83-indents-tabs.js +264 -0
  78. package/indesign/commands/84-import-styles.js +142 -0
  79. package/indesign/commands/85-swatches.js +112 -0
  80. package/indesign/commands/86-gradients.js +185 -0
  81. package/indesign/commands/87-replace-swatch.js +180 -0
  82. package/indesign/commands/88-replace-font.js +212 -0
  83. package/indesign/commands/89-toc-index.js +945 -0
  84. package/indesign/commands/90-print-on-demand.js +495 -0
  85. package/indesign/commands/91-epub.js +546 -0
  86. package/indesign/interpreter.js +779 -0
  87. package/package.json +41 -4
  88. package/rules/typography-rules.json +31 -0
@@ -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
+ });
@@ -0,0 +1,476 @@
1
+ /* Wunschliste J2 Nr. 15 (Sitzung 7) – Manuskript einfliessen lassen: place_text_file. */
2
+
3
+ /* Namen aller Absatzformate (auch in Gruppen) als Nachschlagetabelle. */
4
+ function idcParagraphStyleNames(doc) {
5
+ var names = {};
6
+ var all, i;
7
+ try {
8
+ all = doc.allParagraphStyles;
9
+ for (i = 0; i < all.length; i++) { names[String(all[i].name)] = true; }
10
+ } catch (e) { /* leer */ }
11
+ return names;
12
+ }
13
+
14
+ /* Vorgaben eines Einstellungsobjekts merken, setzen und zurueckstellen. */
15
+ function idcWithPreferences(ctx, prefs, values, fn) {
16
+ var saved = {};
17
+ var key;
18
+ if (prefs === null || prefs === undefined) { return fn(); }
19
+ for (key in values) {
20
+ if (!IDC.hasOwn(values, key) || values[key] === null || values[key] === undefined) { continue; }
21
+ try { saved[key] = prefs[key]; } catch (e0) { continue; }
22
+ try { prefs[key] = values[key]; } catch (e1) { ctx.warn('Could not set import preference ' + key + ': ' + String(e1.message ? e1.message : e1)); }
23
+ }
24
+ try {
25
+ return fn();
26
+ } finally {
27
+ for (key in saved) {
28
+ if (IDC.hasOwn(saved, key)) {
29
+ try { prefs[key] = saved[key]; } catch (e2) { ctx.warn('Could not restore import preference ' + key + '.'); }
30
+ }
31
+ }
32
+ }
33
+ }
34
+
35
+ /* A-67 Punkt 2 (Sitzung 72): bis 1.0.3 hoechstens 200 Seiten je Aufruf; ein Roman mit gut 300 Seiten brach ab. */
36
+ var IDC_AUTOFLOW_MAX_PAGES = 2000;
37
+
38
+ IDC.register('place_text_file', { needs_document: true, read_only: false, ruler: 'none' },
39
+ function (ctx) {
40
+ var args = ctx.args;
41
+ var doc = ctx.doc;
42
+ var found = idcTextFrameArg(doc, args, 'frame_id');
43
+ var frame = found.item;
44
+ var story = idcStoryOf(frame);
45
+ var path = args.file_path;
46
+ var mode = args.mode === undefined || args.mode === null ? 'replace' : args.mode;
47
+ var retain = args.retain_formatting === true;
48
+ var styleMap = args.style_map === undefined || args.style_map === null ? null : args.style_map;
49
+ var styleName = args.paragraph_style === undefined || args.paragraph_style === null ? null : args.paragraph_style;
50
+ var typographersQuotes = args.typographers_quotes !== false;
51
+ var pageBreaks = args.page_breaks === undefined || args.page_breaks === null ? 'none' : args.page_breaks;
52
+ var file, ext, isWord, isText, lengthBefore = 0, lengthAfter = 0, insertAt, namesBefore, namesAfter, added = [];
53
+ var key, target, targetStyle, targets = {}, mapped = {}, placedRange, paragraphs, i, p, name, count = 0, insertion;
54
+
55
+ if (typeof path !== 'string' || path === '') {
56
+ IDC.fail('invalid_argument', 'file_path is required: a .docx, .doc, .rtf or .txt file on the machine running InDesign.', null);
57
+ }
58
+ if (mode !== 'replace' && mode !== 'append') {
59
+ IDC.fail('invalid_argument', 'mode must be replace (default) or append.', { mode: mode });
60
+ }
61
+ if (pageBreaks !== 'none' && pageBreaks !== 'page_break' && pageBreaks !== 'column_break') {
62
+ IDC.fail('invalid_argument', 'page_breaks must be none (default), page_break or column_break.', { page_breaks: pageBreaks });
63
+ }
64
+ var autoflow = args.autoflow === true;
65
+ var maxPages = idcCountArg(args.max_pages, 'max_pages', 1, IDC_AUTOFLOW_MAX_PAGES, 20);
66
+ /* Sitzung 10 (Demo-Heft-Befund 1): der Autofluss springt auf die neue Seite; Ansicht merken. */
67
+ var keepView = idcKeepViewArg(args);
68
+ var view = null, viewRestored = null;
69
+ ext = (path.match(/\.([A-Za-z0-9]+)$/) || ['', ''])[1].toLowerCase();
70
+ isWord = ext === 'docx' || ext === 'doc' || ext === 'rtf';
71
+ isText = ext === 'txt' || ext === 'text' || ext === 'md';
72
+ if (!isWord && !isText) {
73
+ IDC.fail('invalid_argument', 'file_path must end in .docx, .doc, .rtf or .txt (got .' + ext + ').', { file_path: path });
74
+ }
75
+ file = IDC.fileFor(path);
76
+ if (!file.exists) {
77
+ IDC.fail('not_found', 'File not found: ' + path, { file_path: path });
78
+ }
79
+ if (IDC.fileSize(file) === 0) {
80
+ IDC.fail('io_error', 'File is empty (0 bytes) – a cloud placeholder that is not downloaded yet? ' + path, { file_path: path });
81
+ }
82
+ idcRejectStoryLocked(story, Number(frame.id), 'place_text_file');
83
+ if (styleMap !== null) {
84
+ if (typeof styleMap !== 'object' || IDC.isArray(styleMap)) {
85
+ IDC.fail('invalid_argument', 'style_map must be an object { "Word style name": "InDesign style name" }.', null);
86
+ }
87
+ for (key in styleMap) {
88
+ if (!IDC.hasOwn(styleMap, key)) { continue; }
89
+ target = styleMap[key];
90
+ if (typeof target !== 'string' || target === '') {
91
+ IDC.fail('invalid_argument', 'style_map values must be style names.', { key: key, value: target });
92
+ }
93
+ targetStyle = IDC.paragraphStyleByName(doc, target);
94
+ if (targetStyle === null) {
95
+ IDC.fail('not_found', 'Paragraph style "' + target + '" (target of style_map "' + key + '") does not exist.', { style_name: target });
96
+ }
97
+ targets[key] = targetStyle;
98
+ }
99
+ retain = true;
100
+ }
101
+ if (styleName !== null) {
102
+ targetStyle = IDC.paragraphStyleByName(doc, styleName);
103
+ if (targetStyle === null) {
104
+ IDC.fail('not_found', 'Paragraph style "' + styleName + '" not found.', { style_name: styleName });
105
+ }
106
+ }
107
+
108
+ try { lengthBefore = Number(story.characters.length); } catch (e0) { lengthBefore = 0; }
109
+ namesBefore = idcParagraphStyleNames(doc);
110
+
111
+ if (mode === 'replace' && lengthBefore > 0) {
112
+ try { story.contents = ''; } catch (e1) {
113
+ IDC.fail('script_error', 'Could not clear the story before placing: ' + String(e1.message ? e1.message : e1), { frame_id: args.frame_id });
114
+ }
115
+ lengthBefore = 0;
116
+ }
117
+ insertAt = lengthBefore;
118
+
119
+ var wordValues = {
120
+ removeFormatting: !retain,
121
+ preserveLocalOverrides: retain,
122
+ importUnusedStyles: false,
123
+ useTypographersQuotes: typographersQuotes,
124
+ preserveGraphics: false,
125
+ preserveTrackChanges: false,
126
+ importFootnotes: true,
127
+ importEndnotes: true,
128
+ importIndex: false,
129
+ importTOC: false
130
+ };
131
+ if (typeof ResolveStyleClash !== 'undefined') {
132
+ wordValues.resolveParagraphStyleClash = ResolveStyleClash.RESOLVE_CLASH_USE_EXISTING;
133
+ wordValues.resolveCharacterStyleClash = ResolveStyleClash.RESOLVE_CLASH_USE_EXISTING;
134
+ }
135
+ /*
136
+ * A-67 Punkt 5 (Sitzung 72): bis 1.0.3 immer NONE – Seiten- und
137
+ * Spaltenumbrueche aus Word fielen weg, eine Titelei stand auf einer
138
+ * Seite. InDesigns eigene Vorgabe ist PAGE_BREAK (Live-Probe 28.10.2026);
139
+ * page_breaks waehlt, Vorgabe bleibt none (E-79).
140
+ */
141
+ if (typeof ConvertPageBreaks !== 'undefined') {
142
+ wordValues.convertPageBreaks = pageBreaks === 'page_break' ? ConvertPageBreaks.PAGE_BREAK : ConvertPageBreaks.NONE;
143
+ if (pageBreaks === 'column_break') { wordValues.convertPageBreaks = ConvertPageBreaks.COLUMN_BREAK; }
144
+ }
145
+ var textValues = { useTypographersQuotes: typographersQuotes, stripReturnsBetweenLines: false, stripReturnsBetweenParagraphs: false };
146
+ if (typeof TextImportCharacterSet !== 'undefined') { textValues.characterSet = TextImportCharacterSet.UTF8; }
147
+
148
+ var prefs = null;
149
+ try { prefs = isWord ? app.wordRTFImportPreferences : app.textImportPreferences; } catch (e2) { prefs = null; }
150
+
151
+ idcWithPreferences(ctx, prefs, isWord ? wordValues : textValues, function () {
152
+ try {
153
+ insertion = story.insertionPoints.item(story.insertionPoints.length - 1);
154
+ placedRange = insertion.place(file);
155
+ } catch (e3) {
156
+ IDC.fail('io_error', 'InDesign could not place ' + path + ': ' + String(e3.message ? e3.message : e3), { file_path: path, frame_id: args.frame_id });
157
+ }
158
+ });
159
+
160
+ try { lengthAfter = Number(story.characters.length); } catch (e4) { lengthAfter = lengthBefore; }
161
+ namesAfter = idcParagraphStyleNames(doc);
162
+ for (name in namesAfter) {
163
+ if (IDC.hasOwn(namesAfter, name) && !namesBefore[name]) { added.push(name); }
164
+ }
165
+
166
+ /* Absatzformate: erst die Zuordnung Word -> Haus, dann ein Format ueber alles Platzierte. */
167
+ /*
168
+ * Ueber story.paragraphs mit Zeichenzaehlung – characters.itemByRange(...)
169
+ * .paragraphs lieferte am lebenden InDesign 21.5 eine leere Sammlung
170
+ * (Live-Fund 05.09.2026), deshalb wurde nichts zugeordnet.
171
+ */
172
+ try {
173
+ if (lengthAfter > insertAt) {
174
+ paragraphs = story.paragraphs;
175
+ var position = 0, plen;
176
+ for (i = 0; i < paragraphs.length; i++) {
177
+ p = paragraphs.item(i);
178
+ try { plen = Number(p.characters.length); } catch (ep) { plen = 0; }
179
+ if (position + plen <= insertAt) { position += plen; continue; }
180
+ position += plen;
181
+ count += 1;
182
+ if (styleMap !== null) {
183
+ name = idcParagraphStyleName(p);
184
+ if (name !== null && IDC.hasOwn(targets, name)) {
185
+ p.appliedParagraphStyle = targets[name];
186
+ mapped[name] = (mapped[name] || 0) + 1;
187
+ }
188
+ }
189
+ if (styleName !== null) { p.appliedParagraphStyle = targetStyle; }
190
+ }
191
+ }
192
+ } catch (e5) {
193
+ ctx.warn('The text was placed, but the paragraph styles could not be applied: ' + String(e5.message ? e5.message : e5));
194
+ }
195
+
196
+ /*
197
+ * Zugeordnete Word-Formate wieder entfernen: sie kamen erst mit diesem
198
+ * Import und sind nach der Zuordnung an keinem Absatz mehr in Gebrauch.
199
+ * remove(ersatz) haengt eventuelle Reste an das Zielformat.
200
+ */
201
+ var removed = [];
202
+ if (styleMap !== null) {
203
+ var remaining = [];
204
+ for (i = 0; i < added.length; i++) {
205
+ name = added[i];
206
+ if (IDC.hasOwn(targets, name)) {
207
+ try {
208
+ var stray = IDC.paragraphStyleByName(doc, name);
209
+ if (stray !== null) { stray.remove(targets[name]); removed.push(name); continue; }
210
+ } catch (e8) { ctx.warn('Imported style "' + name + '" could not be removed: ' + String(e8.message ? e8.message : e8)); }
211
+ }
212
+ remaining.push(name);
213
+ }
214
+ added = remaining;
215
+ }
216
+
217
+ var overset = null;
218
+ try {
219
+ var containers = story.textContainers;
220
+ overset = containers[containers.length - 1].overflows === true;
221
+ } catch (e6) { try { overset = story.overflows === true; } catch (e7) { overset = null; } }
222
+ var flow = null;
223
+ if (overset === true && autoflow) {
224
+ if (keepView) { view = idcCaptureView(doc); }
225
+ flow = idcAutoflow(ctx, doc, story, maxPages);
226
+ overset = flow.overset;
227
+ if (view !== null) { viewRestored = view.window === null ? null : idcRestoreView(view); }
228
+ }
229
+ if (overset === true) {
230
+ ctx.warn('The story overflows (Uebersatz) after placing – ' + (autoflow ? 'autoflow stopped at max_pages ' + maxPages + '; raise it or shorten the text.' : 'thread more frames, pass autoflow true, or shorten the text.'));
231
+ }
232
+ if (added.length > 0) {
233
+ ctx.warn('The import added ' + added.length + ' paragraph style(s): ' + added.join(', ') + '.');
234
+ }
235
+
236
+ ctx.changes = { modified_frame_ids: [Number(frame.id)], modified_story_ids: [Number(story.id)] };
237
+ if (flow !== null && flow.frames_added.length > 0) {
238
+ ctx.changes.created_frame_ids = [];
239
+ ctx.changes.created_page_ids = [];
240
+ for (i = 0; i < flow.frames_added.length; i++) { ctx.changes.created_frame_ids.push(flow.frames_added[i].frame_id); }
241
+ for (i = 0; i < flow.pages_added.length; i++) { ctx.changes.created_page_ids.push(flow.pages_added[i].page_id); }
242
+ }
243
+ return {
244
+ frame_id: Number(frame.id),
245
+ story_id: Number(story.id),
246
+ file_name: String(path).replace(/^.*[\/\\]/, ''),
247
+ mode: mode,
248
+ retain_formatting: retain,
249
+ placed_start_index: insertAt,
250
+ placed_end_index: lengthAfter,
251
+ inserted_chars: lengthAfter - insertAt,
252
+ story_length: lengthAfter,
253
+ paragraph_count: count,
254
+ styles_added: added,
255
+ styles_mapped: mapped,
256
+ styles_removed: removed,
257
+ paragraph_style_applied: styleName,
258
+ page_breaks: isWord ? pageBreaks : null,
259
+ overset: overset,
260
+ autoflow: flow,
261
+ view_restored: viewRestored
262
+ };
263
+ });
264
+
265
+ /*
266
+ * A-67 Punkt 2 (Sitzung 72): Autofluss fuer eine Story, die schon im
267
+ * Dokument steht – ohne Datei. Bis 1.0.3 ging das nur ueber eine Datei mit
268
+ * einem Leerzeichen per place_text_file mode append. Gleiche Seiten, Rahmen
269
+ * und Rueckgabe autoflow {...} wie place_text_file.
270
+ */
271
+ IDC.register('autoflow_story', { needs_document: true, read_only: false, ruler: 'none' },
272
+ function (ctx) {
273
+ var args = ctx.args;
274
+ var doc = ctx.doc;
275
+ var found = idcTextFrameArg(doc, args, 'frame_id');
276
+ var frame = found.item;
277
+ var story = idcStoryOf(frame);
278
+ var maxPages = idcCountArg(args.max_pages, 'max_pages', 1, IDC_AUTOFLOW_MAX_PAGES, 20);
279
+ var keepView = idcKeepViewArg(args);
280
+ var view = null, viewRestored = null, flow, overset = null, containers, i;
281
+
282
+ idcRejectStoryLocked(story, Number(frame.id), 'autoflow_story');
283
+ try {
284
+ containers = story.textContainers;
285
+ overset = containers[containers.length - 1].overflows === true;
286
+ } catch (e0) { try { overset = story.overflows === true; } catch (e1) { overset = null; } }
287
+ if (overset !== true) {
288
+ flow = { pages_added: [], frames_added: [], overset: overset === null ? null : false, stopped: 'fits' };
289
+ } else {
290
+ if (keepView) { view = idcCaptureView(doc); }
291
+ flow = idcAutoflow(ctx, doc, story, maxPages);
292
+ overset = flow.overset;
293
+ if (view !== null) { viewRestored = view.window === null ? null : idcRestoreView(view); }
294
+ }
295
+ if (overset === true) {
296
+ ctx.warn('The story still overflows (Uebersatz) – autoflow stopped at max_pages ' + maxPages + '; call again or raise max_pages.');
297
+ }
298
+ ctx.changes = { modified_frame_ids: [Number(frame.id)], modified_story_ids: [Number(story.id)] };
299
+ if (flow.frames_added.length > 0) {
300
+ ctx.changes.created_frame_ids = [];
301
+ ctx.changes.created_page_ids = [];
302
+ for (i = 0; i < flow.frames_added.length; i++) { ctx.changes.created_frame_ids.push(flow.frames_added[i].frame_id); }
303
+ for (i = 0; i < flow.pages_added.length; i++) { ctx.changes.created_page_ids.push(flow.pages_added[i].page_id); }
304
+ }
305
+ return {
306
+ frame_id: Number(frame.id),
307
+ story_id: Number(story.id),
308
+ overset: overset === null ? null : overset === true,
309
+ autoflow: flow,
310
+ view_restored: viewRestored
311
+ };
312
+ });
313
+
314
+ /*
315
+ * Autoflow: solange die Story ueberlaeuft, hinter der Seite des letzten
316
+ * Rahmens eine Seite (gleiche Musterseite) anlegen, darauf einen Rahmen mit
317
+ * den Massen und Rahmenoptionen des letzten anlegen und verketten.
318
+ */
319
+ function idcAutoflow(ctx, doc, story, maxPages) {
320
+ var out = { pages_added: [], frames_added: [], overset: true, stopped: '' };
321
+ var round, containers, last = null, page, rect, newPage, newFrame, prefsCopied, copied, placed;
322
+ for (round = 0; round < maxPages; round++) {
323
+ /*
324
+ * Der letzte Rahmen ist ab der zweiten Runde der eben angehaengte –
325
+ * textContainers waechst mit jeder Seite und wird nur einmal gelesen
326
+ * (A-67 Punkt 2, bis zu 2000 Seiten je Aufruf).
327
+ */
328
+ if (last === null) {
329
+ try {
330
+ containers = story.textContainers;
331
+ last = IDC.concrete(containers[containers.length - 1]);
332
+ } catch (e0) { out.stopped = 'last frame unreadable'; return out; }
333
+ }
334
+ try { if (last.overflows !== true) { out.overset = false; out.stopped = 'fits'; return out; } } catch (e1) { /* weiter */ }
335
+ page = null;
336
+ try { page = last.parentPage; } catch (e2) { page = null; }
337
+ if (page === null || page === undefined || page.isValid !== true) {
338
+ IDC.fail('invalid_state', 'Autoflow needs the last frame on a document page (it lies on the pasteboard or a parent page).', null);
339
+ }
340
+ if (IDC.isOnParentSpread(last)) {
341
+ IDC.fail('invalid_state', 'Autoflow does not run on parent pages.', null);
342
+ }
343
+ rect = IDC.boundsOnPage(doc, last, page);
344
+ try {
345
+ newPage = doc.pages.add(LocationOptions.AFTER, page);
346
+ } catch (e3) {
347
+ IDC.fail('script_error', 'Autoflow could not add a page: ' + String(e3.message ? e3.message : e3), null);
348
+ }
349
+ try {
350
+ if (page.appliedMaster && page.appliedMaster.isValid === true) { newPage.appliedMaster = page.appliedMaster; }
351
+ } catch (e4) { /* Vorgabe bleibt */ }
352
+ placed = idcAutoflowRect(doc, rect, page, newPage);
353
+ rect = placed.rect;
354
+ newFrame = idcAddItem(doc, newPage, 'textFrames', idcRectToBounds(rect), 'page');
355
+ /*
356
+ * Rahmenoptionen einzeln kopieren (Demo-Heft-Befund 4, 05.09.2026): die
357
+ * Zuweisung von textFramePreferences.properties liess am lebenden
358
+ * InDesign die Spalten des Ausgangsrahmens weg (neuer Rahmen einspaltig).
359
+ */
360
+ copied = idcCopyTextFramePreferences(last, newFrame);
361
+ prefsCopied = copied.failed.length === 0;
362
+ try { newFrame.itemLayer = last.itemLayer; } catch (e6) { /* aktive Ebene */ }
363
+ try {
364
+ last.nextTextFrame = newFrame;
365
+ } catch (e7) {
366
+ IDC.fail('script_error', 'Autoflow could not thread the new frame: ' + String(e7.message ? e7.message : e7), { page_id: Number(newPage.id) });
367
+ }
368
+ out.pages_added.push({ page_id: Number(newPage.id), page_name: String(newPage.name) });
369
+ out.frames_added.push({
370
+ frame_id: Number(newFrame.id),
371
+ page_id: Number(newPage.id),
372
+ bounds_mm: rect,
373
+ placement: placed.placement,
374
+ frame_options_copied: prefsCopied,
375
+ frame_options_failed: copied.failed,
376
+ frame_options_skipped: copied.skipped,
377
+ columns: idcReadColumnsOf(newFrame)
378
+ });
379
+ last = newFrame;
380
+ }
381
+ try {
382
+ containers = story.textContainers;
383
+ last = IDC.concrete(containers[containers.length - 1]);
384
+ out.overset = last.overflows === true;
385
+ } catch (e8) { out.overset = true; }
386
+ out.stopped = out.overset ? 'max_pages' : 'fits';
387
+ return out;
388
+ }
389
+
390
+ /*
391
+ * Lage des neuen Autoflow-Rahmens (A-43 Nr. 5, KDP-Befund 3): Bei
392
+ * Doppelseiten wechselt die Seite die Seite des Bundes. Fuellt der letzte
393
+ * Rahmen den Satzspiegel seiner Seite, kommt der neue in den Satzspiegel der
394
+ * neuen Seite ('type_area'); sonst wird er am Bund gespiegelt – gleicher
395
+ * Abstand zum Bund wie vorher ('mirrored'). Gleiche Seite des Bundes oder
396
+ * Einzelseiten: Masse unveraendert ('same'). Bis 1.0.1 bekam eine linke Seite
397
+ * die Koordinaten der rechten Startseite (Bundsteg und Aussensteg vertauscht).
398
+ */
399
+ function idcAutoflowRect(doc, rect, fromPage, toPage) {
400
+ var fromSide = IDC.pageSide(fromPage);
401
+ var toSide = IDC.pageSide(toPage);
402
+ var fromArea, toArea, fromWidth, toWidth, inside, x;
403
+ if (!idcFacing(doc) || fromSide === 'SINGLE_SIDED' || toSide === 'SINGLE_SIDED' || fromSide === toSide) {
404
+ return { rect: rect, placement: 'same' };
405
+ }
406
+ try {
407
+ fromArea = idcDescribeGrid(fromPage, false, true).type_area_mm;
408
+ toArea = idcDescribeGrid(toPage, false, true).type_area_mm;
409
+ if (Math.abs(rect.x - fromArea.x) <= 0.05 && Math.abs(rect.y - fromArea.y) <= 0.05
410
+ && Math.abs(rect.width - fromArea.width) <= 0.05 && Math.abs(rect.height - fromArea.height) <= 0.05
411
+ && toArea.width > 0 && toArea.height > 0) {
412
+ return { rect: { x: toArea.x, y: toArea.y, width: toArea.width, height: toArea.height }, placement: 'type_area' };
413
+ }
414
+ } catch (e0) { /* dann spiegeln */ }
415
+ fromWidth = IDC.pageSize(fromPage).width;
416
+ toWidth = IDC.pageSize(toPage).width;
417
+ inside = fromSide === 'RIGHT_HAND' ? rect.x : fromWidth - rect.x - rect.width;
418
+ x = toSide === 'RIGHT_HAND' ? inside : toWidth - inside - rect.width;
419
+ return { rect: { x: IDC.round(x), y: rect.y, width: rect.width, height: rect.height }, placement: 'mirrored' };
420
+ }
421
+
422
+ /*
423
+ * Rahmenoptionen eines Textrahmens auf einen anderen uebertragen – Wert fuer
424
+ * Wert, in der Reihenfolge, die InDesign verlangt (erst die Spaltenart, dann
425
+ * Zahl, Steg und feste Breite). Liefert copied[] und failed[] (Eigenschaft
426
+ * samt Meldung); Eigenschaften, die die Laufzeit nicht kennt, zaehlen nicht.
427
+ */
428
+ /* Adobe-DOM TextFramePreference (geprueft 05.09.2026): alle schreibbar. Automatische Groesse und Spaltenlinien bleiben Vorgabe. */
429
+ var IDC_TEXT_FRAME_PREFS = [
430
+ 'useFixedColumnWidth', 'useFlexibleColumnWidth', 'textColumnCount', 'textColumnGutter', 'textColumnFixedWidth',
431
+ 'textColumnMaxWidth', 'insetSpacing', 'verticalJustification', 'verticalThreshold', 'firstBaselineOffset',
432
+ 'minimumFirstBaselineOffset', 'ignoreWrap', 'verticalBalanceColumns'
433
+ ];
434
+
435
+ function idcCopyTextFramePreferences(source, target) {
436
+ var out = { copied: [], skipped: [], failed: [] };
437
+ var from, to, i, key, value, justified = false, fixedWidth = false, flexibleWidth = false;
438
+ try { from = source.textFramePreferences; to = target.textFramePreferences; } catch (e0) {
439
+ out.failed.push({ property: 'textFramePreferences', message: String(e0.message ? e0.message : e0) });
440
+ return out;
441
+ }
442
+ /*
443
+ * Live-Fund 05.09.2026: verticalThreshold laesst sich nur setzen, wenn der
444
+ * Rahmen vertikal im Blocksatz steht ("Diese Eigenschaft ist im aktuellen
445
+ * Status nicht zutreffend."); die festen/flexiblen Spaltenbreiten gelten nur
446
+ * bei der jeweiligen Spaltenart. Was nicht zutrifft, wird uebersprungen.
447
+ */
448
+ try { justified = typeof VerticalJustification !== 'undefined' && IDC.enumIs(from.verticalJustification, VerticalJustification.JUSTIFY_ALIGN); } catch (ej) { justified = false; }
449
+ try { fixedWidth = from.useFixedColumnWidth === true; } catch (ef) { fixedWidth = false; }
450
+ try { flexibleWidth = from.useFlexibleColumnWidth === true; } catch (ex) { flexibleWidth = false; }
451
+ for (i = 0; i < IDC_TEXT_FRAME_PREFS.length; i++) {
452
+ key = IDC_TEXT_FRAME_PREFS[i];
453
+ if ((key === 'verticalThreshold' && !justified) || (key === 'textColumnFixedWidth' && !fixedWidth) || (key === 'textColumnMaxWidth' && !flexibleWidth)) {
454
+ out.skipped.push(key);
455
+ continue;
456
+ }
457
+ try { value = from[key]; } catch (e1) { continue; }
458
+ if (value === undefined) { continue; }
459
+ if (IDC.isArray(value)) { value = value.slice(0); }
460
+ try {
461
+ to[key] = value;
462
+ out.copied.push(key);
463
+ } catch (e2) {
464
+ out.failed.push({ property: key, message: String(e2.message ? e2.message : e2) });
465
+ }
466
+ }
467
+ return out;
468
+ }
469
+
470
+ /* Spalten eines Textrahmens zum Gegenlesen: {count, gutter_mm}. */
471
+ function idcReadColumnsOf(frame) {
472
+ var out = { count: null, gutter_mm: null };
473
+ try { out.count = Number(frame.textFramePreferences.textColumnCount); } catch (e0) { /* bleibt null */ }
474
+ try { out.gutter_mm = IDC.round(Number(frame.textFramePreferences.textColumnGutter)); } catch (e1) { /* bleibt null */ }
475
+ return out;
476
+ }