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,452 @@
1
+ /*
2
+ * Auftrag A-7.5 (Sitzung 9) – get_frame_lines: den Umbruch direkt in
3
+ * InDesign lesen – Zeilen je Textspalte eines Rahmens mit Grundlinie,
4
+ * Story-Indizes, Absatzstellung und Kennzahlen (Hurenkind-/Schusterjungen-
5
+ * Verdacht, Trennstrichleitern, Leerzeilen). Rein lesend.
6
+ *
7
+ * Adobe-DOM (geprueft 05.09.2026): TextFrame.textColumns, TextFrame.lines,
8
+ * Line {baseline, ascent, descent, horizontalOffset, endHorizontalOffset,
9
+ * characters, paragraphs, words, contents}. TextColumn lieferte 404 – die
10
+ * Klasse ist ein Text-Objekt mit lines; ohne textColumns gilt der Rahmen
11
+ * als eine Spalte.
12
+ *
13
+ * Grundlinien und horizontale Lagen werden unter Bogen-Nullpunkt gelesen
14
+ * (Millimeter ueber die Masseinheiten-Klammer) und um den Seitenversatz
15
+ * verschoben – wie IDC.boundsOnPage.
16
+ */
17
+
18
+ /* Bindestrich, weicher Trennstrich, Trennstrich, geschuetzter Trennstrich. */
19
+ var IDC_LINE_HYPHENS = {};
20
+ IDC_LINE_HYPHENS['-'] = 1;
21
+ IDC_LINE_HYPHENS['\u00AD'] = 1;
22
+ IDC_LINE_HYPHENS['\u2010'] = 1;
23
+ IDC_LINE_HYPHENS['\u2011'] = 1;
24
+
25
+ function idcIsLetter(ch) {
26
+ if (ch === '' || ch === null || ch === undefined) { return false; }
27
+ return /[A-Za-z\u00C0-\u024F\u0370-\u03FF\u0400-\u04FF]/.test(ch);
28
+ }
29
+
30
+ /* Story-Index des ersten Zeichens eines Textobjekts (Zeile, Absatz). */
31
+ function idcTextStartIndex(textObject) {
32
+ try { return Number(textObject.characters.item(0).index); } catch (e) { return null; }
33
+ }
34
+
35
+ function idcTextLength(textObject) {
36
+ try { return Number(textObject.characters.length); } catch (e) { return null; }
37
+ }
38
+
39
+ /* Ergebnis eines everyItem()-Lesens als Array mit genau count Eintraegen, sonst null. */
40
+ function idcBundled(value, count) {
41
+ if (value === null || value === undefined) { return null; }
42
+ if (!IDC.isArray(value)) { value = [value]; }
43
+ return value.length === count ? value : null;
44
+ }
45
+
46
+ /*
47
+ * Absaetze der Story: Startindex -> Ordnungszahl (erster Absatz mit diesem
48
+ * Start), einmal je Story. A-69: frueher je Rahmen und mit Laenge und
49
+ * Zeilenzahl jedes Absatzes der ganzen Kette – beides unbenutzt, die
50
+ * Zeilenzahl liest idcDescribeLine nur fuer den Absatz der Zeile.
51
+ * A-69 Teil 2 (Live-Probe 30.10.2026, 3185 Absaetze): paragraphs.item(i)
52
+ * wird mit i langsamer (16,5 s fuer alle), everyItem() liest alle Starts in
53
+ * 0,6 s mit denselben Werten. starts (aufsteigend) und end (Laenge der
54
+ * Story) tragen idcLineDataBundled; ohne everyItem() der Weg je Absatz.
55
+ */
56
+ function idcParagraphMap(story) {
57
+ var map = { byStart: {}, count: 0, starts: null, end: null };
58
+ var paragraphs, i, start, starts = null;
59
+ try { paragraphs = story.paragraphs; map.count = Number(paragraphs.length); } catch (e0) { return map; }
60
+ if (map.count === 0) { return map; }
61
+ try { starts = idcBundled(paragraphs.everyItem().characters.firstItem().index, map.count); } catch (e1) { starts = null; }
62
+ if (starts !== null) {
63
+ for (i = 0; i < map.count; i++) {
64
+ start = Number(starts[i]);
65
+ if (i > 0 && !(start >= starts[i - 1])) { starts = null; break; }
66
+ starts[i] = start;
67
+ if (!IDC.hasOwn(map.byStart, 'k' + start)) { map.byStart['k' + start] = i; }
68
+ }
69
+ }
70
+ if (starts !== null) {
71
+ map.starts = starts;
72
+ try { map.end = Number(story.characters.length); } catch (e2) { map.starts = null; }
73
+ return map;
74
+ }
75
+ map.byStart = {};
76
+ for (i = 0; i < map.count; i++) {
77
+ start = idcTextStartIndex(paragraphs.item(i));
78
+ if (start !== null && !IDC.hasOwn(map.byStart, 'k' + start)) { map.byStart['k' + start] = i; }
79
+ }
80
+ return map;
81
+ }
82
+
83
+ /* Ordnungszahl des Absatzes, in dem der Story-Index liegt (letzter Start <= index). */
84
+ function idcParagraphAt(map, index) {
85
+ var lo = 0, hi = map.starts.length - 1, mid;
86
+ if (hi < 0 || index < map.starts[0]) { return -1; }
87
+ while (lo < hi) {
88
+ mid = Math.floor((lo + hi + 1) / 2);
89
+ if (map.starts[mid] <= index) { lo = mid; } else { hi = mid - 1; }
90
+ }
91
+ return lo;
92
+ }
93
+
94
+ function idcParagraphOrdinal(map, startIndex) {
95
+ if (startIndex === null || !IDC.hasOwn(map.byStart, 'k' + startIndex)) { return null; }
96
+ return map.byStart['k' + startIndex];
97
+ }
98
+
99
+ /*
100
+ * Was alle Rahmen einer Story teilen (A-69): Absatztabelle, Lage jedes
101
+ * Rahmens in der Kette (frame id -> Index) und Uebersatz. check_typography
102
+ * legt es einmal je Story an; get_frame_lines fuer seinen einen Rahmen.
103
+ */
104
+ function idcStoryLineCache(story) {
105
+ var cache = { map: idcParagraphMap(story), frameIndexById: {}, overset: null, paragraphLines: {} };
106
+ var containers, i, key;
107
+ try {
108
+ containers = story.textContainers;
109
+ for (i = 0; i < containers.length; i++) {
110
+ key = 'k' + Number(containers[i].id);
111
+ if (!IDC.hasOwn(cache.frameIndexById, key)) { cache.frameIndexById[key] = i; }
112
+ }
113
+ } catch (e1) { cache.frameIndexById = {}; }
114
+ try { cache.overset = story.overflows === true; } catch (e2) { cache.overset = null; }
115
+ return cache;
116
+ }
117
+
118
+ /* Spaltenbegrenzungen aus Rahmen, Innenabstand, Spaltenzahl und Steg (Seitenkoordinaten). */
119
+ function idcColumnBounds(doc, frame, page, count) {
120
+ var bounds = IDC.boundsOnPage(doc, frame, page);
121
+ var inset = [0, 0, 0, 0];
122
+ var gutter = 0;
123
+ var out = [], i, width, x;
124
+ try { inset = frame.textFramePreferences.insetSpacing; } catch (e0) { inset = [0, 0, 0, 0]; }
125
+ if (!IDC.isArray(inset) || inset.length < 4) { inset = [0, 0, 0, 0]; }
126
+ try { gutter = Number(frame.textFramePreferences.textColumnGutter); } catch (e1) { gutter = 0; }
127
+ if (isNaN(gutter)) { gutter = 0; }
128
+ width = (bounds.width - Number(inset[1]) - Number(inset[3]) - (count - 1) * gutter) / count;
129
+ for (i = 0; i < count; i++) {
130
+ x = bounds.x + Number(inset[1]) + i * (width + gutter);
131
+ out.push({
132
+ x: IDC.round(x),
133
+ y: IDC.round(bounds.y + Number(inset[0])),
134
+ width: IDC.round(width),
135
+ height: IDC.round(bounds.height - Number(inset[0]) - Number(inset[2]))
136
+ });
137
+ }
138
+ return out;
139
+ }
140
+
141
+ function idcDescribeLine(doc, line, indexInColumn, map, dx, includeText) {
142
+ var start = idcTextStartIndex(line);
143
+ var length = idcTextLength(line);
144
+ var paragraph = null, pStart = null, pLength = null, pLines = null;
145
+ var contents = '', wordCount = null, geometry = {};
146
+
147
+ try { paragraph = line.paragraphs.item(0); } catch (e0) { paragraph = null; }
148
+ if (paragraph !== null) {
149
+ pStart = idcTextStartIndex(paragraph);
150
+ pLength = idcTextLength(paragraph);
151
+ try { pLines = Number(paragraph.lines.length); } catch (e1) { pLines = null; }
152
+ }
153
+ try { contents = String(line.contents); } catch (e2) { contents = ''; }
154
+ try { wordCount = Number(line.words.length); } catch (e3) { wordCount = null; }
155
+ try { geometry.baseline = Number(line.baseline); } catch (e4) { /* null */ }
156
+ try { geometry.ascent = IDC.inPoints(function () { return Number(line.ascent); }); } catch (e5) { /* null */ }
157
+ try { geometry.descent = IDC.inPoints(function () { return Number(line.descent); }); } catch (e6) { /* null */ }
158
+ try { geometry.left = Number(line.horizontalOffset); } catch (e7) { /* null */ }
159
+ try { geometry.right = Number(line.endHorizontalOffset); } catch (e8) { /* null */ }
160
+ return idcLineEntry(indexInColumn, start, length, pStart,
161
+ pStart === null || pLength === null ? null : pStart + pLength,
162
+ pStart === null ? null : idcParagraphOrdinal(map, pStart), pLines, contents, wordCount, geometry, dx, includeText);
163
+ }
164
+
165
+ /*
166
+ * Eine Zeile aus gelesenen Werten – gemeinsam fuer den Weg je Zeile
167
+ * (idcDescribeLine) und das gebuendelte Lesen (idcLineDataBundled), damit
168
+ * beide dieselbe Ausgabe liefern. pEnd = Story-Index hinter dem Absatz;
169
+ * geometry {baseline, left, right} in mm, {ascent, descent} in pt, fehlende
170
+ * Werte bleiben null.
171
+ */
172
+ function idcLineEntry(indexInColumn, start, length, pStart, pEnd, pIndex, pLines, contents, wordCount, geometry, dx, includeText) {
173
+ var lastChar = '', out;
174
+ if (contents.length > 0) { lastChar = contents.charAt(contents.length - 1); }
175
+ if (contents.replace(/[\r\n\s]/g, '') === '') { wordCount = 0; }
176
+
177
+ out = {
178
+ index: indexInColumn,
179
+ start_index: start,
180
+ end_index: start === null || length === null ? null : start + length,
181
+ baseline_mm: null,
182
+ ascent_pt: null,
183
+ descent_pt: null,
184
+ left_mm: null,
185
+ right_mm: null,
186
+ paragraph_index: pIndex,
187
+ is_first_line_of_paragraph: pStart !== null && start === pStart,
188
+ is_last_line_of_paragraph: pEnd !== null && start !== null && length !== null && start + length === pEnd,
189
+ paragraph_line_count: pLines,
190
+ ends_with_hyphen: IDC.hasOwn(IDC_LINE_HYPHENS, lastChar),
191
+ is_empty: length !== null && (length === 0 || (length === 1 && (lastChar === '\r' || lastChar === '\n'))),
192
+ word_count: wordCount,
193
+ last_char: lastChar === '\r' ? '\\r' : lastChar
194
+ };
195
+ if (geometry.baseline !== undefined) { out.baseline_mm = IDC.round(geometry.baseline); }
196
+ if (geometry.ascent !== undefined) { out.ascent_pt = IDC.round(geometry.ascent, 3); }
197
+ if (geometry.descent !== undefined) { out.descent_pt = IDC.round(geometry.descent, 3); }
198
+ if (geometry.left !== undefined) { out.left_mm = IDC.round(geometry.left - dx); }
199
+ if (geometry.right !== undefined) { out.right_mm = IDC.round(geometry.right - dx); }
200
+ if (includeText) { out.text = contents.replace(/[\r\n]+$/, ''); }
201
+ return out;
202
+ }
203
+
204
+ /*
205
+ * Alle Zeilen einer Spalte (oder eines Rahmens) gebuendelt lesen (A-69 Teil 2,
206
+ * Live-Probe 30.10.2026: je Rahmen ~50 ms statt ~290 ms, Werte in 228 Zeilen
207
+ * gleich dem Weg je Zeile). Je Eigenschaft ein everyItem()-Zugriff; den Absatz
208
+ * jeder Zeile liefert die Absatztabelle der Story (map.starts). Zeilenzahl des
209
+ * Absatzes: liegen erste und letzte Zeile in dieser Spalte, wird gezaehlt,
210
+ * sonst einmal je Absatz paragraph.lines (in cache.paragraphLines).
211
+ * lean: word_count nur fuer letzte Absatzzeilen (die Regeln brauchen ihn nur
212
+ * dort), sonst null. Liefert null, wenn etwas nicht gebuendelt lesbar ist
213
+ * (z. B. eine leere Zeile) – dann gilt der Weg je Zeile.
214
+ */
215
+ function idcLineDataBundled(textObject, map, dx, includeText, cache, lean) {
216
+ var lines, n, every, starts, lasts, contents, baselines, lefts, rights, metrics, i;
217
+ var entries = [], keys = [], pIndex, pStart, pEnd, start, length, word, key, firstIn = {}, lastIn = {}, countIn = {};
218
+ if (map.starts === null) { return null; }
219
+ try {
220
+ lines = textObject.lines;
221
+ n = Number(lines.length);
222
+ if (n === 0) { return { entries: entries, contents: [] }; }
223
+ every = lines.everyItem();
224
+ starts = idcBundled(every.index, n);
225
+ lasts = idcBundled(every.characters.lastItem().index, n);
226
+ contents = idcBundled(every.contents, n);
227
+ baselines = idcBundled(every.baseline, n);
228
+ lefts = idcBundled(every.horizontalOffset, n);
229
+ rights = idcBundled(every.endHorizontalOffset, n);
230
+ metrics = IDC.inPoints(function () { return [idcBundled(every.ascent, n), idcBundled(every.descent, n)]; });
231
+ } catch (e0) { return null; }
232
+ if (starts === null || lasts === null || contents === null || baselines === null || lefts === null || rights === null || metrics[0] === null || metrics[1] === null) { return null; }
233
+
234
+ for (i = 0; i < n; i++) {
235
+ start = Number(starts[i]);
236
+ length = Number(lasts[i]) - start + 1;
237
+ pIndex = idcParagraphAt(map, start);
238
+ if (pIndex < 0) { return null; }
239
+ pStart = map.starts[pIndex];
240
+ pEnd = pIndex + 1 < map.starts.length ? map.starts[pIndex + 1] : map.end;
241
+ entries.push(idcLineEntry(i, start, length, pStart, pEnd, idcParagraphOrdinal(map, pStart), null,
242
+ String(contents[i]), null,
243
+ { baseline: Number(baselines[i]), ascent: Number(metrics[0][i]), descent: Number(metrics[1][i]), left: Number(lefts[i]), right: Number(rights[i]) },
244
+ dx, includeText));
245
+ key = 'k' + pStart;
246
+ keys.push(key);
247
+ if (entries[i].is_first_line_of_paragraph) { firstIn[key] = true; }
248
+ if (entries[i].is_last_line_of_paragraph) { lastIn[key] = true; }
249
+ countIn[key] = (countIn[key] || 0) + 1;
250
+ }
251
+
252
+ for (i = 0; i < n; i++) {
253
+ key = keys[i];
254
+ if (firstIn[key] === true && lastIn[key] === true) {
255
+ entries[i].paragraph_line_count = countIn[key];
256
+ } else {
257
+ if (!IDC.hasOwn(cache.paragraphLines, key)) {
258
+ try { cache.paragraphLines[key] = Number(lines.item(i).paragraphs.item(0).lines.length); } catch (e1) { cache.paragraphLines[key] = null; }
259
+ }
260
+ entries[i].paragraph_line_count = cache.paragraphLines[key];
261
+ }
262
+ if (!lean || (entries[i].is_last_line_of_paragraph && !entries[i].is_empty)) {
263
+ word = null;
264
+ try { word = Number(lines.item(i).words.length); } catch (e2) { word = null; }
265
+ if (String(contents[i]).replace(/[\r\n\s]/g, '') === '') { word = 0; }
266
+ entries[i].word_count = word;
267
+ }
268
+ contents[i] = String(contents[i]);
269
+ }
270
+ return { entries: entries, contents: contents };
271
+ }
272
+
273
+ /*
274
+ * Automatische Trennung: der Trennstrich steht nicht im Text. Endet eine
275
+ * Zeile mit einem Buchstaben und beginnt die naechste Zeile desselben
276
+ * Absatzes mit einem Buchstaben, ist das Wort getrennt (oder erzwungen
277
+ * gebrochen) – ends_with_hyphen wird dann gesetzt.
278
+ */
279
+ function idcMarkSoftHyphens(lines, texts) {
280
+ var i, a, b;
281
+ for (i = 0; i + 1 < lines.length; i++) {
282
+ a = lines[i];
283
+ b = lines[i + 1];
284
+ if (a.ends_with_hyphen) { continue; }
285
+ if (a.paragraph_index === null || a.paragraph_index !== b.paragraph_index) { continue; }
286
+ if (a.is_last_line_of_paragraph) { continue; }
287
+ if (idcIsLetter(texts[i].last) && idcIsLetter(texts[i + 1].first)) { a.ends_with_hyphen = true; }
288
+ }
289
+ }
290
+
291
+ function idcColumnFigures(lines) {
292
+ var out = {
293
+ line_count: lines.length,
294
+ first_baseline_mm: null,
295
+ last_baseline_mm: null,
296
+ starts_with_last_line_of_paragraph: false,
297
+ ends_with_first_line_of_paragraph: false,
298
+ starts_with_empty_line: false,
299
+ consecutive_hyphens_max: 0,
300
+ ends_with_single_word: false,
301
+ single_word_last_lines: []
302
+ };
303
+ var i, run = 0, first, last;
304
+ if (lines.length === 0) { return out; }
305
+ first = lines[0];
306
+ last = lines[lines.length - 1];
307
+ out.first_baseline_mm = first.baseline_mm;
308
+ out.last_baseline_mm = last.baseline_mm;
309
+ out.starts_with_last_line_of_paragraph = first.is_last_line_of_paragraph && !first.is_first_line_of_paragraph && !first.is_empty;
310
+ out.ends_with_first_line_of_paragraph = last.is_first_line_of_paragraph && !last.is_last_line_of_paragraph && !last.is_empty;
311
+ out.starts_with_empty_line = first.is_empty === true;
312
+ for (i = 0; i < lines.length; i++) {
313
+ if (lines[i].ends_with_hyphen) { run += 1; if (run > out.consecutive_hyphens_max) { out.consecutive_hyphens_max = run; } }
314
+ else { run = 0; }
315
+ if (lines[i].is_last_line_of_paragraph && !lines[i].is_empty && lines[i].word_count === 1 && lines[i].paragraph_line_count !== null && lines[i].paragraph_line_count > 1) {
316
+ out.single_word_last_lines.push(lines[i].index);
317
+ }
318
+ }
319
+ out.ends_with_single_word = last.is_last_line_of_paragraph && !last.is_empty && last.word_count === 1 && last.paragraph_line_count !== null && last.paragraph_line_count > 1;
320
+ return out;
321
+ }
322
+
323
+ /* Zeilen einer Spalte (TextColumn) oder des ganzen Rahmens als Array. */
324
+ function idcLinesOf(textObject) {
325
+ var out = [], lines, i;
326
+ try { lines = textObject.lines; } catch (e0) { return out; }
327
+ for (i = 0; i < lines.length; i++) { out.push(lines.item(i)); }
328
+ return out;
329
+ }
330
+
331
+ function idcFrameColumns(frame) {
332
+ var out = [], columns, i;
333
+ try { columns = frame.textColumns; } catch (e0) { columns = null; }
334
+ if (columns === null || columns === undefined) { return null; }
335
+ try {
336
+ for (i = 0; i < columns.length; i++) { out.push(columns.item(i)); }
337
+ } catch (e1) { return null; }
338
+ return out;
339
+ }
340
+
341
+ /*
342
+ * Zeilen eines Rahmens je Spalte samt Kennzahlen – gemeinsam fuer
343
+ * get_frame_lines und check_typography. Warnungen gehen an ctx (null =
344
+ * keine). cache aus idcStoryLineCache(story); ohne wird er hier angelegt.
345
+ * lean (check_typography): word_count nur, wo die Regeln ihn lesen.
346
+ */
347
+ function idcFrameLineData(doc, frame, story, includeText, maxLines, ctx, cache, lean) {
348
+ var page = null, dx = 0, columns, columnObjects, i, c, lines, entries, texts, raw, bundled, line, total = 0, truncated = false, overset = null;
349
+ var map, bounds, frameIndex = null, frameKey;
350
+
351
+ try { page = frame.parentPage; } catch (e0) { page = null; }
352
+ try { if (page !== null && page !== undefined && page.isValid !== true) { page = null; } } catch (e0b) { page = null; }
353
+ if (page === null || page === undefined) { page = null; }
354
+ dx = page === null ? 0 : IDC.spreadOffsetOfPage(page);
355
+
356
+ if (cache === undefined || cache === null) { cache = idcStoryLineCache(story); }
357
+ try {
358
+ frameKey = 'k' + Number(frame.id);
359
+ if (IDC.hasOwn(cache.frameIndexById, frameKey)) { frameIndex = cache.frameIndexById[frameKey]; }
360
+ } catch (e1) { frameIndex = null; }
361
+ overset = cache.overset;
362
+ map = cache.map;
363
+ columnObjects = idcFrameColumns(frame);
364
+ if (columnObjects === null || columnObjects.length === 0) { columnObjects = [frame]; }
365
+ bounds = idcColumnBounds(doc, frame, page, columnObjects.length);
366
+
367
+ columns = [];
368
+ for (c = 0; c < columnObjects.length; c++) {
369
+ bundled = idcLineDataBundled(columnObjects[c], map, dx, includeText, cache, lean === true);
370
+ entries = [];
371
+ raw = [];
372
+ if (bundled !== null) {
373
+ for (i = 0; i < bundled.entries.length; i++) {
374
+ if (total >= maxLines) { truncated = true; break; }
375
+ entries.push(bundled.entries[i]);
376
+ raw.push(bundled.contents[i]);
377
+ total += 1;
378
+ }
379
+ } else {
380
+ lines = idcLinesOf(columnObjects[c]);
381
+ for (i = 0; i < lines.length; i++) {
382
+ if (total >= maxLines) { truncated = true; break; }
383
+ line = lines[i];
384
+ entries.push(idcDescribeLine(doc, line, i, map, dx, includeText));
385
+ try { raw.push(String(line.contents)); } catch (e3) { raw.push(''); }
386
+ total += 1;
387
+ }
388
+ }
389
+ texts = [];
390
+ for (i = 0; i < raw.length; i++) {
391
+ /* Endet die Zeile mit Absatzmarke, erzwungenem Umbruch (Live-Fund 05.09.2026: initial_text mit LF) oder Zeilentrenner, ist nichts getrennt. */
392
+ var rawLast = raw[i].charAt(raw[i].length - 1);
393
+ var brokenByBreak = rawLast === '\r' || rawLast === '\n' || rawLast === '\u2028' || rawLast === '\u2029';
394
+ texts.push({ first: raw[i].charAt(0), last: brokenByBreak ? '' : rawLast });
395
+ }
396
+ idcMarkSoftHyphens(entries, texts);
397
+ var figures = idcColumnFigures(entries);
398
+ figures.column_index = c;
399
+ figures.bounds_mm = bounds[c] || null;
400
+ figures.lines = entries;
401
+ columns.push(figures);
402
+ if (truncated) { break; }
403
+ }
404
+ if (ctx !== null && truncated) {
405
+ ctx.warn('More than ' + maxLines + ' lines; output cut at max_lines (truncated true).');
406
+ }
407
+ if (ctx !== null && total === 0) {
408
+ ctx.warn('The frame shows no composed line – it is empty, or the text is overset before it.');
409
+ }
410
+
411
+ return {
412
+ frame_id: Number(frame.id),
413
+ story_id: Number(story.id),
414
+ frame_index: frameIndex,
415
+ page_id: page === null ? null : Number(page.id),
416
+ page_name: page === null ? '' : String(page.name),
417
+ paragraph_count: map.count,
418
+ column_count: columns.length,
419
+ line_count: total,
420
+ columns: columns,
421
+ overset: overset,
422
+ truncated: truncated
423
+ };
424
+ }
425
+
426
+ IDC.register('get_frame_lines', { needs_document: true, read_only: true, ruler: 'spread' },
427
+ function (ctx) {
428
+ var args = ctx.args;
429
+ var doc = ctx.doc;
430
+ var found = idcTextFrameArg(doc, args, 'frame_id');
431
+ var frame = found.item;
432
+ var story = idcStoryOf(frame);
433
+ var includeText = args.include_text === true;
434
+ var maxLines = idcGiven(args.max_lines) ? args.max_lines : 500;
435
+
436
+ if (typeof maxLines !== 'number' || Math.floor(maxLines) !== maxLines || maxLines < 1 || maxLines > 5000) {
437
+ IDC.fail('invalid_argument', 'max_lines must be a whole number between 1 and 5000.', { max_lines: args.max_lines });
438
+ }
439
+ var data = idcFrameLineData(doc, frame, story, includeText, maxLines, ctx);
440
+ return {
441
+ frame_id: data.frame_id,
442
+ story_id: data.story_id,
443
+ frame_index: data.frame_index,
444
+ page_id: data.page_id,
445
+ coordinate_space: data.page_id === null ? 'spread' : 'page',
446
+ column_count: data.column_count,
447
+ line_count: data.line_count,
448
+ columns: data.columns,
449
+ overset: data.overset,
450
+ truncated: data.truncated
451
+ };
452
+ });