postext 1.22.1 → 1.22.2

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 (65) hide show
  1. package/dist/__tests__/exports.test.js +4 -0
  2. package/dist/__tests__/exports.test.js.map +1 -1
  3. package/dist/comics/lettering/__tests__/panelCache.test.d.ts +2 -0
  4. package/dist/comics/lettering/__tests__/panelCache.test.d.ts.map +1 -0
  5. package/dist/comics/lettering/__tests__/panelCache.test.js +43 -0
  6. package/dist/comics/lettering/__tests__/panelCache.test.js.map +1 -0
  7. package/dist/comics/lettering/letter.d.ts.map +1 -1
  8. package/dist/comics/lettering/letter.js +73 -0
  9. package/dist/comics/lettering/letter.js.map +1 -1
  10. package/dist/comics/lettering/shape-text.d.ts.map +1 -1
  11. package/dist/comics/lettering/shape-text.js +64 -16
  12. package/dist/comics/lettering/shape-text.js.map +1 -1
  13. package/dist/fonts/usage.d.ts +46 -0
  14. package/dist/fonts/usage.d.ts.map +1 -0
  15. package/dist/fonts/usage.js +205 -0
  16. package/dist/fonts/usage.js.map +1 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/word/analyze.d.ts +33 -0
  22. package/dist/word/analyze.d.ts.map +1 -0
  23. package/dist/word/analyze.js +167 -0
  24. package/dist/word/analyze.js.map +1 -0
  25. package/dist/word/docxRead.d.ts +10 -0
  26. package/dist/word/docxRead.d.ts.map +1 -0
  27. package/dist/word/docxRead.js +584 -0
  28. package/dist/word/docxRead.js.map +1 -0
  29. package/dist/word/index.d.ts +14 -0
  30. package/dist/word/index.d.ts.map +1 -0
  31. package/dist/word/index.js +12 -0
  32. package/dist/word/index.js.map +1 -0
  33. package/dist/word/inline.d.ts +48 -0
  34. package/dist/word/inline.d.ts.map +1 -0
  35. package/dist/word/inline.js +606 -0
  36. package/dist/word/inline.js.map +1 -0
  37. package/dist/word/model.d.ts +141 -0
  38. package/dist/word/model.d.ts.map +1 -0
  39. package/dist/word/model.js +23 -0
  40. package/dist/word/model.js.map +1 -0
  41. package/dist/word/omml.d.ts +4 -0
  42. package/dist/word/omml.d.ts.map +1 -0
  43. package/dist/word/omml.js +139 -0
  44. package/dist/word/omml.js.map +1 -0
  45. package/dist/word/template.d.ts +111 -0
  46. package/dist/word/template.d.ts.map +1 -0
  47. package/dist/word/template.js +255 -0
  48. package/dist/word/template.js.map +1 -0
  49. package/dist/word/toDocx.d.ts +28 -0
  50. package/dist/word/toDocx.d.ts.map +1 -0
  51. package/dist/word/toDocx.js +684 -0
  52. package/dist/word/toDocx.js.map +1 -0
  53. package/dist/word/toMarkdown.d.ts +53 -0
  54. package/dist/word/toMarkdown.d.ts.map +1 -0
  55. package/dist/word/toMarkdown.js +632 -0
  56. package/dist/word/toMarkdown.js.map +1 -0
  57. package/dist/word/word.test.d.ts +2 -0
  58. package/dist/word/word.test.d.ts.map +1 -0
  59. package/dist/word/word.test.js +302 -0
  60. package/dist/word/word.test.js.map +1 -0
  61. package/dist/word/xml.d.ts +30 -0
  62. package/dist/word/xml.d.ts.map +1 -0
  63. package/dist/word/xml.js +127 -0
  64. package/dist/word/xml.js.map +1 -0
  65. package/package.json +5 -1
@@ -0,0 +1,632 @@
1
+ // Word model → Postext chapters. Each paragraph style becomes what the
2
+ // template says (heading, paragraph style, callout, quote, caption…), runs
3
+ // become inline marks, Word lists become Postext lists, notes become
4
+ // footnotes, pictures and tables become resources placed where they were.
5
+ import { guardLineStart, renderInline, WORD_JOINER } from './inline';
6
+ import { guessCharacterTarget, guessParagraphTarget, lookup, } from './template';
7
+ /** The target of a paragraph style: the template's, else a guess. */
8
+ export function paragraphTargetOf(doc, styleId, template, config) {
9
+ const id = styleId || doc.defaultParagraphStyle;
10
+ const style = doc.styles.get(id);
11
+ const name = style?.name ?? (id || 'Normal');
12
+ return lookup(template.paragraphs, name) ?? guessParagraphTarget(name, config, doc, style);
13
+ }
14
+ /** The target of a character style: the template's, else a guess. */
15
+ export function characterTargetOf(doc, name, template, config) {
16
+ const style = [...doc.styles.values()].find((s) => s.type === 'character' && s.name === name);
17
+ return lookup(template.characters, name) ?? guessCharacterTarget(name, config, doc, style);
18
+ }
19
+ const CAPTION_LABEL_RE = /^\s*(?:fig(?:ure|ura|\.)?|figs?\.|table|tabla|tab\.|taula|tableau|tabelle|tabella|plate|lámina|làmina|chart|gráfico|map|mapa|image|imagen|imatge|ilustración|il·lustració|illustration|esquema|diagram|diagrama|图|表|図|شكل|جدول)\s*[0-9IVXLC٠-٩]+(?:[.\-–][0-9٠-٩]+)*\s*[.:\-–—]?\s*/iu;
20
+ /** A caption without its typed label (Postext numbers resources). */
21
+ export function stripCaptionLabel(text) {
22
+ return text.replace(CAPTION_LABEL_RE, '');
23
+ }
24
+ const attrValue = (v) => `"${v.replace(/"/g, '”').replace(/[{}]/g, '').replace(/\s+/g, ' ').trim()}"`;
25
+ function slugify(text, fallback) {
26
+ const s = text.normalize('NFKD').replace(/[̀-ͯ]/g, '').toLowerCase()
27
+ .replace(/\.[a-z0-9]{2,4}$/, '')
28
+ .replace(/[^\p{L}\p{N}]+/gu, '-').replace(/^-+|-+$/g, '')
29
+ .split('-').slice(0, 4).join('-').slice(0, 40).replace(/-+$/, '');
30
+ return s || fallback;
31
+ }
32
+ function joinChunks(chunks) {
33
+ let out = '';
34
+ chunks.forEach((c, i) => {
35
+ if (i > 0) {
36
+ const prev = chunks[i - 1];
37
+ out += (prev.kind === c.kind && c.kind !== 'block') ? '\n' : '\n\n';
38
+ }
39
+ out += c.text;
40
+ });
41
+ return out;
42
+ }
43
+ /** Container fences a markup paragraph opens (`:::callout{…}`); a bare
44
+ * `:::` closes the innermost. Inside one, paragraph styles mapped to
45
+ * callouts and paragraph styles do not open boxes of their own. */
46
+ const FENCE_OPEN_RE = /^:::\s*(callout|paragraphs|part|columns|paper|verse|references|page|strip)\b/;
47
+ const FENCE_CLOSE_RE = /^:::\s*$/;
48
+ class Converter {
49
+ doc;
50
+ s;
51
+ chapters = [];
52
+ chunks = [];
53
+ notes = [];
54
+ title;
55
+ group = null;
56
+ explicitDepth = 0;
57
+ list = [];
58
+ counters = new Map();
59
+ noteSeq = 0;
60
+ pendingCaption;
61
+ lastResource = null;
62
+ usedIds;
63
+ hasChapterMarkers;
64
+ pictures = [];
65
+ tables = [];
66
+ skippedPictures = [];
67
+ droppedHeadingNotes = 0;
68
+ /** Tidy style boundaries (spaces between two bold words, a style change
69
+ * inside a word) in documents from Word; a Sandbox export keeps its runs
70
+ * exactly as they were written. */
71
+ tidy;
72
+ constructor(doc, s) {
73
+ this.doc = doc;
74
+ this.s = s;
75
+ this.tidy = doc.embeddedTemplate === undefined;
76
+ this.usedIds = new Set(s.existingIds);
77
+ this.hasChapterMarkers = doc.blocks.some((b) => b.type === 'paragraph' && this.target(b).kind === 'chapter');
78
+ }
79
+ target(p) {
80
+ return paragraphTargetOf(this.doc, p.styleId, this.s.template, this.s.config);
81
+ }
82
+ uniqueId(base) {
83
+ // `table-1` taken: `table-2`, not `table-1-2`.
84
+ const numbered = /^(.*-)(\d+)$/.exec(base);
85
+ let id = base;
86
+ for (let n = 2; this.usedIds.has(id); n++)
87
+ id = numbered ? `${numbered[1]}${Number(numbered[2]) + n - 1}` : `${base}-${n}`;
88
+ this.usedIds.add(id);
89
+ return id;
90
+ }
91
+ render(runs) {
92
+ return renderInline(runs, { tidy: this.tidy }).trim();
93
+ }
94
+ // -- output ---------------------------------------------------------------
95
+ out(chunk) {
96
+ if (chunk.kind !== 'list')
97
+ this.list = [];
98
+ (this.group ? this.group.chunks : this.chunks).push(chunk);
99
+ }
100
+ closeGroup() {
101
+ const g = this.group;
102
+ if (!g)
103
+ return;
104
+ this.group = null;
105
+ if (g.chunks.length === 0 && !g.title)
106
+ return;
107
+ const attrs = g.kind === 'callout'
108
+ ? `{type=${attrValue(g.key)}${g.title ? ` title=${attrValue(g.title)}` : ''}}`
109
+ : `{style=${attrValue(g.key)}}`;
110
+ const inner = joinChunks(g.chunks);
111
+ this.chunks.push({ kind: 'block', text: `:::${g.kind}${attrs}${inner ? `\n${inner}` : ''}\n:::` });
112
+ this.list = [];
113
+ }
114
+ openGroup(kind, key) {
115
+ if (this.group && this.group.kind === kind && this.group.key === key)
116
+ return;
117
+ this.closeGroup();
118
+ this.group = { kind, key, chunks: [] };
119
+ }
120
+ flushChapter(nextTitle) {
121
+ this.closeGroup();
122
+ this.flushCaption();
123
+ const body = joinChunks(this.chunks);
124
+ const notes = this.notes.join('\n\n');
125
+ const markdown = [body, notes].filter(Boolean).join('\n\n');
126
+ if (markdown.trim() || this.title !== undefined) {
127
+ this.chapters.push({ title: this.title ?? this.s.untitledChapter, markdown: markdown ? markdown + '\n' : '' });
128
+ }
129
+ this.chunks = [];
130
+ this.notes = [];
131
+ this.list = [];
132
+ this.title = nextTitle;
133
+ }
134
+ flushCaption() {
135
+ if (this.pendingCaption === undefined)
136
+ return;
137
+ const text = this.pendingCaption;
138
+ this.pendingCaption = undefined;
139
+ this.out({ kind: 'block', text: guardLineStart(text) });
140
+ }
141
+ // -- runs -----------------------------------------------------------------
142
+ charTarget(name) {
143
+ return characterTargetOf(this.doc, name, this.s.template, this.s.config);
144
+ }
145
+ /** Runs → inline runs, split at line breaks when `split` (verse typed
146
+ * with Shift+Enter) and at page breaks. Notes are numbered here. */
147
+ inlineRuns(runs, opts) {
148
+ const lines = [[]];
149
+ const images = [];
150
+ const displayMath = [];
151
+ let pageBreak = false;
152
+ const keepDirect = this.s.template.options.directFormatting === 'keep';
153
+ const cur = () => lines[lines.length - 1];
154
+ for (const r of runs) {
155
+ switch (r.type) {
156
+ case 'text': {
157
+ if (r.hidden)
158
+ break;
159
+ const t = this.textRun(r, keepDirect, !!opts.heading);
160
+ if (t)
161
+ cur().push(t);
162
+ break;
163
+ }
164
+ case 'tab':
165
+ cur().push({ text: ' ' });
166
+ break;
167
+ case 'break':
168
+ if (r.kind === 'page')
169
+ pageBreak = true;
170
+ if (r.kind === 'line' && opts.heading)
171
+ cur().push({ text: ' \\\\ ', raw: true });
172
+ else if (r.kind === 'line' && opts.splitLines)
173
+ lines.push([]);
174
+ else if (r.kind === 'line' && opts.inCell && !opts.inNote)
175
+ cur().push({ text: '\n', raw: true });
176
+ else
177
+ cur().push({ text: ' ' });
178
+ break;
179
+ case 'note': {
180
+ if (opts.heading || opts.inCell || opts.inNote) {
181
+ this.droppedHeadingNotes++;
182
+ break;
183
+ }
184
+ const blocks = (r.kind === 'footnote' ? this.doc.footnotes : this.doc.endnotes).get(r.id);
185
+ if (!blocks)
186
+ break;
187
+ const id = String(++this.noteSeq);
188
+ const text = blocks
189
+ .filter((b) => b.type === 'paragraph')
190
+ .map((b) => this.renderRuns(b.runs, { inNote: true }))
191
+ .filter(Boolean)
192
+ .join(' ');
193
+ this.notes.push(`[^${id}]: ${text}`);
194
+ cur().push({ text: '', note: id });
195
+ break;
196
+ }
197
+ case 'math':
198
+ if (r.display && !opts.inCell && !opts.inNote)
199
+ displayMath.push(r.tex);
200
+ else
201
+ cur().push({ text: `$${r.tex}$`, raw: true });
202
+ break;
203
+ case 'image':
204
+ images.push(r);
205
+ break;
206
+ case 'index': {
207
+ if (opts.inCell)
208
+ break;
209
+ const term = r.term.map((t) => t.replace(/!/g, '')).join('!');
210
+ let a = `term=${attrValue(term)}`;
211
+ if (r.main)
212
+ a += ' main';
213
+ if (r.see)
214
+ a += ` see=${attrValue(r.see)}`;
215
+ cur().push({ text: `:index{${a}}`, raw: true });
216
+ break;
217
+ }
218
+ }
219
+ }
220
+ return { lines, images, pageBreak, displayMath };
221
+ }
222
+ textRun(r, keepDirect, heading) {
223
+ const t = { text: r.text.replace(/[​]/g, '') };
224
+ if (!t.text)
225
+ return null;
226
+ if (r.href !== undefined && !heading)
227
+ t.href = r.href;
228
+ if (keepDirect) {
229
+ if (r.bold)
230
+ t.bold = true;
231
+ if (r.italic)
232
+ t.italic = true;
233
+ if (r.smallCaps)
234
+ t.smallCaps = true;
235
+ if (r.script)
236
+ t.script = r.script;
237
+ }
238
+ if (r.charStyle) {
239
+ const target = this.charTarget(r.charStyle);
240
+ switch (target.kind) {
241
+ case 'drop':
242
+ return null;
243
+ case 'markup': {
244
+ // Verbatim, inside the emphasis and link around it.
245
+ const raw = { text: r.text, raw: true };
246
+ if (t.bold && r.bold !== false)
247
+ raw.bold = true;
248
+ if (t.italic && r.italic !== false)
249
+ raw.italic = true;
250
+ if (t.smallCaps)
251
+ raw.smallCaps = true;
252
+ if (t.href !== undefined)
253
+ raw.href = t.href;
254
+ return raw;
255
+ }
256
+ case 'bold':
257
+ if (r.bold !== false)
258
+ t.bold = true;
259
+ break;
260
+ case 'italic':
261
+ if (r.italic !== false)
262
+ t.italic = true;
263
+ break;
264
+ case 'boldItalic':
265
+ if (r.bold !== false)
266
+ t.bold = true;
267
+ if (r.italic !== false)
268
+ t.italic = true;
269
+ break;
270
+ case 'smallCaps':
271
+ if (r.smallCaps !== false)
272
+ t.smallCaps = true;
273
+ break;
274
+ case 'sup':
275
+ case 'sub':
276
+ t.script = target.kind;
277
+ break;
278
+ case 'chip':
279
+ if (!heading)
280
+ t.chip = target.style;
281
+ break;
282
+ default:
283
+ break;
284
+ }
285
+ }
286
+ if (r.bold === false)
287
+ delete t.bold;
288
+ if (r.italic === false)
289
+ delete t.italic;
290
+ return t;
291
+ }
292
+ renderRuns(runs, opts = {}) {
293
+ const { lines } = this.inlineRuns(runs, { inNote: !!opts.inNote });
294
+ return lines.map((l) => this.render(l)).filter(Boolean).join(' ');
295
+ }
296
+ // -- blocks ---------------------------------------------------------------
297
+ emitImages(images) {
298
+ if (!this.s.template.options.images)
299
+ return;
300
+ for (const img of images) {
301
+ const media = this.doc.media.get(img.rId);
302
+ if (!media)
303
+ continue;
304
+ if (!/^image\/(png|jpeg|gif|webp|svg\+xml)$/.test(media.contentType)) {
305
+ this.skippedPictures.push(media.path.split('/').pop() ?? media.path);
306
+ continue;
307
+ }
308
+ const base = slugify(img.alt ?? '', '') || slugify(img.name ?? '', '') || 'figure';
309
+ const id = this.uniqueId(/^(picture|imagen|image|figure)\b/i.test(base) || !/[a-z]/.test(base) ? `fig-${this.pictures.length + 1}` : base);
310
+ const pic = { id, media, ...(img.name ? { name: img.name } : {}), ...(img.alt ? { alt: img.alt } : {}) };
311
+ if (this.pendingCaption !== undefined) {
312
+ pic.caption = this.pendingCaption;
313
+ this.pendingCaption = undefined;
314
+ }
315
+ this.pictures.push(pic);
316
+ this.lastResource = pic;
317
+ this.out({ kind: 'block', text: `::resource{id="${id}"}` });
318
+ }
319
+ }
320
+ emitMarkup(p) {
321
+ let text = '';
322
+ for (const r of p.runs) {
323
+ if (r.type === 'text' && !r.hidden)
324
+ text += r.text;
325
+ else if (r.type === 'tab')
326
+ text += '\t';
327
+ else if (r.type === 'break' && r.kind === 'line')
328
+ text += '\n';
329
+ }
330
+ this.closeGroup();
331
+ this.flushCaption();
332
+ for (const line of text.split('\n')) {
333
+ const t = line.replace(/\s+$/, '');
334
+ if (FENCE_OPEN_RE.test(t.trim()))
335
+ this.explicitDepth++;
336
+ else if (FENCE_CLOSE_RE.test(t.trim()) && this.explicitDepth > 0)
337
+ this.explicitDepth--;
338
+ }
339
+ this.out({ kind: 'markup', text: text.replace(/\s+$/, '') });
340
+ }
341
+ listMarker(p) {
342
+ const { numId, ilvl } = p.list;
343
+ const num = this.doc.numbering.nums.get(numId);
344
+ const level = num ? this.doc.numbering.abstracts.get(num.abstractId)?.get(ilvl) : undefined;
345
+ const ordered = !!level && level.format !== 'bullet' && level.format !== 'none';
346
+ let marker = '- ';
347
+ if (ordered) {
348
+ const k = `${numId}:${ilvl}`;
349
+ const start = num?.starts.get(ilvl) ?? level?.start ?? 1;
350
+ const n = this.counters.has(k) ? this.counters.get(k) + 1 : start;
351
+ this.counters.set(k, n);
352
+ for (const key of [...this.counters.keys()]) {
353
+ const [id, l] = key.split(':');
354
+ if (id === numId && Number(l) > ilvl)
355
+ this.counters.delete(key);
356
+ }
357
+ marker = `${n}. `;
358
+ }
359
+ while (this.list.length && this.list[this.list.length - 1].ilvl >= ilvl)
360
+ this.list.pop();
361
+ const parent = this.list[this.list.length - 1];
362
+ const indent = parent ? parent.indent + parent.width : 0;
363
+ this.list.push({ ilvl, indent, width: marker.length });
364
+ return { indent: ' '.repeat(indent), marker };
365
+ }
366
+ isManualHeading(p, text) {
367
+ if (!this.s.template.options.manualHeadings || p.list)
368
+ return false;
369
+ const runs = p.runs.filter((r) => r.type === 'text' && r.text.trim() !== '');
370
+ if (!runs.length)
371
+ return false;
372
+ const t = text.trim();
373
+ if (t.length > 120 || /[.:;,]$/.test(t))
374
+ return false;
375
+ return runs.every((r) => r.bold || (r.charStyle && ['bold', 'boldItalic'].includes(this.charTarget(r.charStyle).kind)));
376
+ }
377
+ paragraph(p) {
378
+ let target = this.target(p);
379
+ const plain = p.runs.map((r) => (r.type === 'text' && !r.hidden ? r.text : r.type === 'tab' || r.type === 'break' ? ' ' : '')).join('').replace(/\s+/g, ' ').trim();
380
+ if (target.kind === 'drop')
381
+ return;
382
+ if (target.kind === 'markup') {
383
+ this.emitMarkup(p);
384
+ return;
385
+ }
386
+ if (target.kind === 'chapter') {
387
+ if (this.s.chapters === 'split')
388
+ this.flushChapter(plain || this.s.untitledChapter);
389
+ return;
390
+ }
391
+ if (p.pageBreakBefore && this.s.template.options.pageBreaks === 'keep' && !(target.kind === 'heading' && target.level === 1)) {
392
+ this.closeGroup();
393
+ this.out({ kind: 'block', text: ':::pagebreak' });
394
+ }
395
+ let manualHeading = false;
396
+ if (target.kind === 'body' && this.isManualHeading(p, plain)) {
397
+ target = { kind: 'heading', level: this.s.template.options.manualHeadings };
398
+ manualHeading = true;
399
+ }
400
+ if (this.explicitDepth > 0 && (target.kind === 'callout' || target.kind === 'paragraphs' || target.kind === 'calloutTitle')) {
401
+ target = target.kind === 'calloutTitle' ? target : { kind: 'body' };
402
+ }
403
+ if (target.kind === 'heading') {
404
+ const level = Math.min(6, Math.max(1, target.level));
405
+ if (level === 1 && this.s.chapters === 'split' && !this.hasChapterMarkers)
406
+ this.flushChapter(plain || this.s.untitledChapter);
407
+ else {
408
+ this.closeGroup();
409
+ this.flushCaption();
410
+ }
411
+ const { lines } = this.inlineRuns(p.runs, { heading: true });
412
+ // A heading typed in bold by hand: the bold was its only sign.
413
+ let text = this.render(manualHeading ? lines.flat().map((x) => ({ ...x, bold: undefined })) : lines.flat());
414
+ if (!text)
415
+ return;
416
+ if (target.style) {
417
+ const attrs = /\s\{([^{}]*)\}$/.exec(text);
418
+ if (attrs && !/\bstyle\s*=/.test(attrs[1]))
419
+ text = text.slice(0, attrs.index) + ` {style=${attrValue(target.style)} ${attrs[1].trim()}}`;
420
+ else if (!attrs)
421
+ text += ` {style=${attrValue(target.style)}}`;
422
+ }
423
+ else if (text.endsWith('}') && !/\s\{[^{}]*\}$/.test(text))
424
+ text += WORD_JOINER;
425
+ this.out({ kind: 'block', text: `${'#'.repeat(level)} ${text}` });
426
+ return;
427
+ }
428
+ const splitLines = this.s.template.options.lineBreaks === 'paragraph' && !p.list;
429
+ const { lines, images, pageBreak, displayMath } = this.inlineRuns(p.runs, { splitLines });
430
+ const rendered = lines.map((l) => this.render(l)).filter(Boolean);
431
+ if (target.kind === 'caption') {
432
+ const text = stripCaptionLabel(rendered.join(' '));
433
+ if (images.length === 0 && text) {
434
+ if (this.lastResource && !this.lastResource.caption && this.lastChunkIsResource()) {
435
+ this.lastResource.caption = text;
436
+ }
437
+ else {
438
+ this.flushCaption();
439
+ this.pendingCaption = text;
440
+ }
441
+ return;
442
+ }
443
+ }
444
+ if (target.kind === 'calloutTitle') {
445
+ if (this.explicitDepth > 0) {
446
+ // Inside a fence the box is already open: the title is a paragraph.
447
+ target = { kind: 'body' };
448
+ }
449
+ else {
450
+ this.closeGroup();
451
+ this.flushCaption();
452
+ // A callout title is plain text.
453
+ this.group = { kind: 'callout', key: target.type, title: plain || undefined, chunks: [] };
454
+ this.emitImages(images);
455
+ return;
456
+ }
457
+ }
458
+ if (target.kind === 'callout') {
459
+ if (!(this.group?.kind === 'callout' && this.group.key === target.type))
460
+ this.openGroup('callout', target.type);
461
+ }
462
+ else if (target.kind === 'paragraphs') {
463
+ this.openGroup('paragraphs', target.style);
464
+ }
465
+ else if (this.group) {
466
+ this.closeGroup();
467
+ }
468
+ this.flushCaption();
469
+ if (rendered.length) {
470
+ if (p.list) {
471
+ const { indent, marker } = this.listMarker(p);
472
+ this.out({ kind: 'list', text: indent + marker + rendered.join(' ') });
473
+ }
474
+ else if (target.kind === 'quote') {
475
+ for (const line of rendered)
476
+ this.out({ kind: 'block', text: `> ${line}` });
477
+ }
478
+ else {
479
+ for (const line of rendered)
480
+ this.out({ kind: 'block', text: guardLineStart(line) });
481
+ }
482
+ }
483
+ for (const tex of displayMath)
484
+ this.out({ kind: 'block', text: `$$${tex}$$` });
485
+ this.emitImages(images);
486
+ if (pageBreak && this.s.template.options.pageBreaks === 'keep') {
487
+ this.closeGroup();
488
+ this.out({ kind: 'block', text: ':::pagebreak' });
489
+ }
490
+ for (const box of p.textBoxes ?? [])
491
+ this.block(box);
492
+ }
493
+ lastChunkIsResource() {
494
+ const list = this.group ? this.group.chunks : this.chunks;
495
+ const last = list[list.length - 1];
496
+ return !!last && last.text.startsWith('::resource{');
497
+ }
498
+ table(t) {
499
+ if (!this.s.template.options.tables) {
500
+ for (const row of t.rows)
501
+ for (const cell of row)
502
+ for (const b of cell.blocks)
503
+ this.block(b);
504
+ return;
505
+ }
506
+ this.closeGroup();
507
+ const model = tableModel(t, (p) => this.cellText(p));
508
+ const id = this.uniqueId(`table-${this.tables.length + 1}`);
509
+ const table = { id, model };
510
+ if (this.pendingCaption !== undefined) {
511
+ table.caption = this.pendingCaption;
512
+ this.pendingCaption = undefined;
513
+ }
514
+ this.tables.push(table);
515
+ this.lastResource = table;
516
+ this.out({ kind: 'block', text: `::resource{id="${id}"}` });
517
+ }
518
+ cellText(blocks) {
519
+ const lines = [];
520
+ let depthBase = null;
521
+ for (const b of blocks) {
522
+ if (b.type !== 'paragraph')
523
+ continue;
524
+ const { lines: ls } = this.inlineRuns(b.runs, { inCell: true });
525
+ const text = ls.map((l) => renderInline(l, { collapseSpaces: false, tidy: this.tidy }).replace(/[ \t]+/g, ' ').trim()).join(' ');
526
+ if (!text)
527
+ continue;
528
+ if (b.list) {
529
+ const num = this.doc.numbering.nums.get(b.list.numId);
530
+ const level = num ? this.doc.numbering.abstracts.get(num.abstractId)?.get(b.list.ilvl) : undefined;
531
+ if (depthBase === null)
532
+ depthBase = b.list.ilvl;
533
+ const ordered = !!level && level.format !== 'bullet';
534
+ const k = `cell:${b.list.numId}:${b.list.ilvl}`;
535
+ const n = (this.counters.get(k) ?? (level?.start ?? 1) - 1) + 1;
536
+ this.counters.set(k, n);
537
+ lines.push(' '.repeat(Math.max(0, b.list.ilvl - depthBase)) + (ordered ? `${n}. ` : '- ') + text);
538
+ }
539
+ else
540
+ lines.push(text);
541
+ }
542
+ return lines.join('\n');
543
+ }
544
+ block(b) {
545
+ if (b.type === 'table')
546
+ this.table(b);
547
+ else
548
+ this.paragraph(b);
549
+ }
550
+ run() {
551
+ for (const b of this.doc.blocks)
552
+ this.block(b);
553
+ this.flushChapter();
554
+ if (this.chapters.length === 0)
555
+ this.chapters.push({ title: this.s.untitledChapter, markdown: '' });
556
+ return {
557
+ chapters: this.chapters,
558
+ pictures: this.pictures,
559
+ tables: this.tables,
560
+ skippedPictures: this.skippedPictures,
561
+ droppedHeadingNotes: this.droppedHeadingNotes,
562
+ };
563
+ }
564
+ }
565
+ /** A Word table as a Postext table model: merged cells as spans (covered
566
+ * cells point at the cell covering them), the header rows, the column
567
+ * widths as weights and the cell fills. */
568
+ export function tableModel(t, text) {
569
+ // Lay the Word cells on the grid (a gridSpan takes several columns).
570
+ const grid = [];
571
+ let columns = 0;
572
+ t.rows.forEach((row, ri) => {
573
+ const out = [];
574
+ let col = 0;
575
+ for (const c of row) {
576
+ const cell = { content: c.vMerge === 'continue' ? '' : text(c.blocks) };
577
+ if (c.colSpan > 1)
578
+ cell.colSpan = c.colSpan;
579
+ if (c.shading)
580
+ cell.background = { hex: c.shading, model: 'hex' };
581
+ if (ri < t.headerRows)
582
+ cell.isHeader = true;
583
+ out.push({ cell, vMerge: c.vMerge });
584
+ for (let k = 1; k < c.colSpan; k++)
585
+ out.push({ cell: { content: '', hiddenBy: { row: ri, col } } });
586
+ col += c.colSpan;
587
+ }
588
+ columns = Math.max(columns, col);
589
+ grid.push(out);
590
+ });
591
+ // Vertical merges: the restart cell spans the continue cells under it.
592
+ for (let ri = 0; ri < grid.length; ri++) {
593
+ grid[ri].forEach((g, ci) => {
594
+ if (g.vMerge !== 'restart')
595
+ return;
596
+ let span = 1;
597
+ for (let rj = ri + 1; rj < grid.length; rj++) {
598
+ const below = grid[rj][ci];
599
+ if (below?.vMerge !== 'continue')
600
+ break;
601
+ span++;
602
+ below.cell = { content: '', hiddenBy: { row: ri, col: ci } };
603
+ for (let k = 1; k < (g.cell.colSpan ?? 1); k++) {
604
+ const side = grid[rj][ci + k];
605
+ if (side)
606
+ side.cell = { content: '', hiddenBy: { row: ri, col: ci } };
607
+ }
608
+ }
609
+ if (span > 1)
610
+ g.cell.rowSpan = span;
611
+ });
612
+ }
613
+ const rows = grid.map((r) => {
614
+ const cells = r.map((g) => g.cell);
615
+ while (cells.length < columns)
616
+ cells.push({ content: '' });
617
+ return cells;
618
+ });
619
+ const model = { rows };
620
+ if (t.headerRows > 0)
621
+ model.headerRowCount = t.headerRows;
622
+ if (t.widths && t.widths.length === columns) {
623
+ const min = Math.min(...t.widths);
624
+ model.columnWidths = t.widths.map((w) => Math.round((w / min) * 100) / 100);
625
+ }
626
+ return model;
627
+ }
628
+ /** Convert a read Word document to Postext chapters and resources. */
629
+ export function wordToPostext(doc, settings) {
630
+ return new Converter(doc, settings).run();
631
+ }
632
+ //# sourceMappingURL=toMarkdown.js.map