@docx4j/core-ts 0.3.2 → 0.5.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 (72) hide show
  1. package/README.md +8 -3
  2. package/dist/html/convert.d.mts +116 -0
  3. package/dist/html/convert.mjs +565 -0
  4. package/dist/html/elements.d.mts +19 -0
  5. package/dist/html/elements.mjs +182 -0
  6. package/dist/html/index.d.mts +2 -0
  7. package/dist/html/index.mjs +4 -0
  8. package/dist/model/content/Body.d.mts +13 -1
  9. package/dist/model/content/Body.mjs +97 -19
  10. package/dist/model/content/Comment.mjs +7 -0
  11. package/dist/model/content/InlinePicture.d.mts +89 -10
  12. package/dist/model/content/InlinePicture.mjs +245 -35
  13. package/dist/model/content/List.mjs +10 -1
  14. package/dist/model/content/Paragraph.d.mts +28 -5
  15. package/dist/model/content/Paragraph.mjs +77 -37
  16. package/dist/model/content/Range.d.mts +18 -2
  17. package/dist/model/content/Range.mjs +194 -64
  18. package/dist/model/content/Table.d.mts +38 -0
  19. package/dist/model/content/Table.mjs +80 -2
  20. package/dist/model/content/TrackedChange.d.mts +129 -3
  21. package/dist/model/content/TrackedChange.mjs +577 -12
  22. package/dist/model/content/binder.d.mts +2 -1
  23. package/dist/model/content/binder.mjs +48 -1
  24. package/dist/model/content/index.d.mts +4 -3
  25. package/dist/model/content/index.mjs +3 -2
  26. package/dist/model/content/relationships.d.mts +30 -0
  27. package/dist/model/content/relationships.mjs +133 -0
  28. package/dist/model/content/stylesFacade.d.mts +4 -2
  29. package/dist/model/content/stylesFacade.mjs +20 -6
  30. package/dist/model/content/tableColumns.d.mts +48 -0
  31. package/dist/model/content/tableColumns.mjs +484 -0
  32. package/dist/model/content/tree.d.mts +52 -2
  33. package/dist/model/content/tree.mjs +48 -3
  34. package/dist/model/customxml/CustomXmlPartCollection.d.mts +6 -1
  35. package/dist/model/customxml/CustomXmlPartCollection.mjs +19 -3
  36. package/dist/model/customxml/bindings.d.mts +5 -2
  37. package/dist/model/customxml/bindings.mjs +21 -6
  38. package/dist/model/customxml/index.d.mts +3 -0
  39. package/dist/model/customxml/index.mjs +3 -0
  40. package/dist/model/customxml/opendope.d.mts +32 -0
  41. package/dist/model/customxml/opendope.mjs +56 -0
  42. package/dist/model/customxml/pictures.d.mts +57 -0
  43. package/dist/model/customxml/pictures.mjs +192 -0
  44. package/dist/model/customxml/xhtml.d.mts +42 -0
  45. package/dist/model/customxml/xhtml.mjs +162 -0
  46. package/dist/model/fonts/families.generated.mjs +2 -2
  47. package/dist/model/fonts/registry.mjs +7 -1
  48. package/dist/model/fonts/substitutions.generated.mjs +2 -2
  49. package/dist/model/fonts/themes.generated.mjs +1 -1
  50. package/dist/model/listnumbering/definitions.mjs +18 -0
  51. package/dist/model/properties/PropertyResolver.d.mts +27 -6
  52. package/dist/model/properties/PropertyResolver.mjs +83 -16
  53. package/dist/model/properties/catalogue.d.mts +15 -1
  54. package/dist/model/properties/catalogue.mjs +80 -27
  55. package/dist/model/properties/table.d.mts +16 -2
  56. package/dist/model/properties/table.mjs +13 -3
  57. package/dist/opc/Load.d.mts +9 -2
  58. package/dist/opc/index.d.mts +2 -2
  59. package/dist/opc/index.mjs +2 -2
  60. package/dist/opc/mce/preprocessor.d.mts +62 -6
  61. package/dist/opc/mce/preprocessor.mjs +142 -35
  62. package/dist/opc/mce/understood.d.mts +10 -0
  63. package/dist/opc/mce/understood.mjs +25 -3
  64. package/dist/packages/OpcPackage.d.mts +7 -0
  65. package/dist/packages/OpcPackage.mjs +15 -3
  66. package/dist/packages/WordprocessingMLPackage.d.mts +35 -2
  67. package/dist/packages/WordprocessingMLPackage.mjs +99 -2
  68. package/dist/parts/XmlPart.d.mts +26 -5
  69. package/dist/parts/XmlPart.mjs +67 -18
  70. package/dist/parts/wml/knownStyles.d.mts +1 -0
  71. package/dist/parts/wml/knownStyles.mjs +2233 -0
  72. package/package.json +7 -2
@@ -1,10 +1,12 @@
1
1
  import { inlinePicture } from '@docx4j/generated-objects-ts/builders/wml';
2
2
  import { Docx4JException } from '../../opc/exceptions.mjs';
3
3
  import { ContentTypes, IMAGE_CONTENT_TYPES_BY_EXTENSION } from '../../opc/ContentTypes.mjs';
4
- import { base64Decode, base64Encode } from '../../xml/dom.mjs';
4
+ import { base64Decode, base64Encode, decodeXmlText } from '../../xml/dom.mjs';
5
5
  import { ImagePart } from '../../parts/BinaryPart.mjs';
6
6
  import { PartName } from '../../opc/PartName.mjs';
7
- import { find, runOf } from './tree.mjs';
7
+ import { find, runOf, linkParents, revisionKindOf, isElement, typeNameOf, isAlternateContent, selectedBranchOf } from './tree.mjs';
8
+ import { copyRPr } from './tracking.mjs';
9
+ import { relationshipIdsIn, queueRelationshipRemovals } from './relationships.mjs';
8
10
  /** English Metric Units per inch, and per twip (docx4j UnitsOfMeasurement). */
9
11
  const EMU_PER_INCH = 914400;
10
12
  const EMU_PER_TWIP = 635;
@@ -15,7 +17,9 @@ const EMU_PER_POINT = 12700;
15
17
  * The size and kind of an image, from its header: PNG (IHDR and pHYs), JPEG (SOFn and the JFIF
16
18
  * APP0 densities), GIF (the logical screen descriptor) and BMP (the DIB header, with its pixels
17
19
  * per metre). docx4j reads these through XML Graphics Commons' ImageInfo; this is the same
18
- * information, and the same 96 dpi default when a file declares none.
20
+ * information, and the same 96 dpi default when a file declares none. An SVG document is told by
21
+ * its root element and has no pixel size (both 0): it can be the part behind an existing picture
22
+ * (CR-005 section 9, a binding that keeps the drawing) but not be placed as a new one.
19
23
  */
20
24
  export function imageInfoOf(bytes) {
21
25
  const png = pngInfo(bytes);
@@ -30,8 +34,27 @@ export function imageInfoOf(bytes) {
30
34
  const bmp = bmpInfo(bytes);
31
35
  if (bmp)
32
36
  return bmp;
37
+ const svg = svgInfo(bytes);
38
+ if (svg)
39
+ return svg;
33
40
  const head = Array.from(bytes.subarray(0, 8)).map((b) => b.toString(16).padStart(2, '0')).join(' ');
34
- throw new Docx4JException(`Unsupported image format (first bytes ${head}); PNG, JPEG, GIF and BMP are read here`);
41
+ throw new Docx4JException(`Unsupported image format (first bytes ${head}); PNG, JPEG, GIF, BMP and SVG are read here`);
42
+ }
43
+ /** An SVG document: an XML declaration, comments and a doctype allowed before the `svg` root. */
44
+ function svgInfo(b) {
45
+ if (b.length < 5)
46
+ return undefined;
47
+ let head;
48
+ try {
49
+ head = decodeXmlText(b.subarray(0, Math.min(b.length, 4096)));
50
+ }
51
+ catch {
52
+ return undefined;
53
+ }
54
+ const body = head.replace(/^\uFEFF/, '').replace(/<\?xml[^>]*\?>/, '').replace(/<!--[\s\S]*?-->/g, '').replace(/<!DOCTYPE[^>]*>/i, '').trimStart();
55
+ if (!/^<(\w+:)?svg[\s>]/.test(body))
56
+ return undefined;
57
+ return { contentType: ContentTypes.IMAGE_SVG, extension: 'svg', widthPx: 0, heightPx: 0, dpiX: DEFAULT_DPI, dpiY: DEFAULT_DPI };
35
58
  }
36
59
  function be16(b, at) { return (b[at] << 8) | b[at + 1]; }
37
60
  function be32(b, at) { return ((b[at] << 24) >>> 0) + (b[at + 1] << 16) + (b[at + 2] << 8) + b[at + 3]; }
@@ -144,11 +167,12 @@ export function naturalSizeEmu(info) {
144
167
  }
145
168
  /**
146
169
  * Adds the image as a part of `source` (the main document part, or the header or footer the body
147
- * belongs to) with a relationship, and builds the run that shows it. docx4j:
148
- * `BinaryPartAbstractImage.createImagePart` then `createImageInline`.
170
+ * belongs to) with a relationship, and nothing else: docx4j `BinaryPartAbstractImage.createImagePart`
171
+ * (`BindingTraverserXSLT.createImagePartReturnRelId`, where a picture binding replaces an `r:embed`
172
+ * and keeps the drawing, CR-005 section 9). Throws, and adds nothing, when the bytes are not an
173
+ * image this package reads.
149
174
  */
150
- export function addImage(source, base64, scope, maxCx, options = {}) {
151
- var _a, _b;
175
+ export function addImagePart(source, base64) {
152
176
  const pkg = source.package;
153
177
  if (!pkg)
154
178
  throw new Docx4JException('This part is not in a package yet; add it before adding an image');
@@ -157,6 +181,21 @@ export function addImage(source, base64, scope, maxCx, options = {}) {
157
181
  const imagePart = new ImagePart(freeImageName(pkg, info.extension), info.contentType);
158
182
  imagePart.setBytes(bytes);
159
183
  const rel = source.addTargetPart(imagePart);
184
+ return { imagePart, relId: rel.id, info };
185
+ }
186
+ /**
187
+ * Adds the image as a part of `source` with a relationship (`addImagePart`), and builds the run
188
+ * that shows it. docx4j: `BinaryPartAbstractImage.createImagePart` then `createImageInline`.
189
+ */
190
+ export function addImage(source, base64, scope, maxCx, options = {}) {
191
+ var _a, _b;
192
+ const bytes = base64Decode(base64);
193
+ const info = imageInfoOf(bytes);
194
+ if (info.widthPx === 0 || info.heightPx === 0) {
195
+ throw new Docx4JException(`A ${info.extension.toUpperCase()} image has no pixel size to place it at; PNG, JPEG, GIF and BMP can be placed`);
196
+ }
197
+ const { imagePart, relId } = addImagePart(source, base64);
198
+ const rel = { id: relId };
160
199
  let { cx, cy } = naturalSizeEmu(info);
161
200
  if (options.width !== undefined) {
162
201
  const wanted = Math.round(options.width * EMU_PER_POINT);
@@ -211,6 +250,57 @@ export function writableWidthEmu(container) {
211
250
  const twips = pgSz.w - left - right;
212
251
  return twips > 0 ? twips * EMU_PER_TWIP : undefined;
213
252
  }
253
+ /** The TYPE_NAME of `asvg:svgBlip`, Office 2016's SVG picture extension of an `a:blip`. */
254
+ const SVG_BLIP = 'org_docx4j_com_microsoft_schemas_office_drawing_x2016_SVG_main.CTSVGBlip';
255
+ /** The `a:ext` URI that `asvg:svgBlip` stands under, and its namespace. */
256
+ const SVG_BLIP_EXT_URI = '{96DAC541-7B7A-43D3-8B79-37D633B846F1}';
257
+ const SVG_NS = 'http://schemas.microsoft.com/office/drawing/2016/SVG/main';
258
+ /** The `asvg:svgBlip` in a blip's `a:extLst`, if any: an SVG picture's SVG (CR-002 section 52). */
259
+ export function svgBlipOf(blip) {
260
+ var _a, _b;
261
+ for (const ext of (_b = (_a = blip === null || blip === void 0 ? void 0 : blip.extLst) === null || _a === void 0 ? void 0 : _a.ext) !== null && _b !== void 0 ? _b : []) {
262
+ const any = ext.any;
263
+ if (isElement(any) && typeNameOf(any) === SVG_BLIP)
264
+ return any.value;
265
+ }
266
+ return undefined;
267
+ }
268
+ /**
269
+ * Points a blip at a new image the way Word's own picture binding does (test/README.md checks 46 and
270
+ * 47; CR-002 section 52). The blip's effects and other extensions stay.
271
+ * - A raster image goes in `r:embed`, and an `asvg:svgBlip` is taken away. Left in place, Word would go
272
+ * on drawing its old SVG, and would replace the new image with a PNG of that SVG when it saves.
273
+ * - An SVG goes under the `asvg:svgBlip`, and `r:embed` is taken away. Word puts a PNG of the SVG it
274
+ * makes itself in `r:embed`, which this package cannot make, and Word 365 draws the SVG without one.
275
+ * `r:link` is left as it was.
276
+ */
277
+ export function setBlipImage(blip, relId, svg) {
278
+ var _a, _b;
279
+ if (!svg) {
280
+ blip.embed = relId;
281
+ const list = blip.extLst;
282
+ if (list === null || list === void 0 ? void 0 : list.ext) {
283
+ list.ext = list.ext.filter((ext) => !(isElement(ext.any) && typeNameOf(ext.any) === SVG_BLIP));
284
+ if (list.ext.length === 0)
285
+ delete blip.extLst;
286
+ }
287
+ return;
288
+ }
289
+ delete blip.embed;
290
+ const existing = svgBlipOf(blip);
291
+ if (existing) {
292
+ existing.embed = relId;
293
+ return;
294
+ }
295
+ const list = ((_a = blip.extLst) !== null && _a !== void 0 ? _a : (blip.extLst = { TYPE_NAME: 'org_docx4j_dml.CTOfficeArtExtensionList', ext: [] }));
296
+ const ext = {
297
+ TYPE_NAME: 'org_docx4j_dml.CTOfficeArtExtension',
298
+ uri: SVG_BLIP_EXT_URI,
299
+ any: { name: { namespaceURI: SVG_NS, localPart: 'svgBlip' }, value: { TYPE_NAME: SVG_BLIP, embed: relId } },
300
+ };
301
+ ((_b = list.ext) !== null && _b !== void 0 ? _b : (list.ext = [])).push(ext);
302
+ linkParents(list, blip);
303
+ }
214
304
  const FORMAT_BY_CONTENT_TYPE = {
215
305
  [ContentTypes.IMAGE_PNG]: 'Png', [ContentTypes.IMAGE_X_PNG]: 'Png', [ContentTypes.IMAGE_JPEG]: 'Jpeg',
216
306
  [ContentTypes.IMAGE_GIF]: 'Gif', [ContentTypes.IMAGE_BMP]: 'Bmp', [ContentTypes.IMAGE_TIFF]: 'Tiff',
@@ -218,18 +308,48 @@ const FORMAT_BY_CONTENT_TYPE = {
218
308
  [ContentTypes.IMAGE_WMF]: 'Wmf',
219
309
  };
220
310
  /**
221
- * A subset of Office JS `Word.InlinePicture` over a `w:drawing` holding a `wp:inline`. A view:
222
- * the drawing, the run it is in and the paragraph. Sizes are points, as Office JS reports them.
311
+ * The inline pictures a run's item holds: the item itself when it is a `w:drawing` whose `wp:inline`
312
+ * holds a `pic:pic`, and those in a kept `mc:AlternateContent`'s selected branch (`selectedBranchOf`,
313
+ * CR-010 phase 3). A drawing of anything else, such as a chart, a text box, a group or SmartArt, is no
314
+ * inline picture: Office JS lists none for an inline text box (CR-010 section 12.6, test/README.md
315
+ * check 42).
316
+ */
317
+ export function picturesIn(item) {
318
+ var _a;
319
+ if (isAlternateContent(item)) {
320
+ return ((_a = selectedBranchOf(item.value)) !== null && _a !== void 0 ? _a : []).flatMap((child) => (isElement(child) ? picturesIn(child) : []));
321
+ }
322
+ return item.name.localPart === 'drawing' && holdsInlinePicture(item.value) ? [item] : [];
323
+ }
324
+ /** True for a drawing whose `wp:inline` holds a `pic:pic`. */
325
+ function holdsInlinePicture(drawing) {
326
+ var _a, _b, _c, _d;
327
+ const inline = (_a = drawing.anchorOrInline) === null || _a === void 0 ? void 0 : _a[0];
328
+ if (!inline || inline.TYPE_NAME !== 'org_docx4j_dml_wordprocessingDrawing.Inline')
329
+ return false;
330
+ const first = (_d = (_c = (_b = inline.graphic) === null || _b === void 0 ? void 0 : _b.graphicData) === null || _c === void 0 ? void 0 : _c.any) === null || _d === void 0 ? void 0 : _d[0];
331
+ return isElement(first) && typeNameOf(first) === 'org_docx4j_dml_picture.Pic';
332
+ }
333
+ /**
334
+ * A subset of Office JS `Word.InlinePicture` over a `w:drawing` holding a `wp:inline` with a `pic:pic`.
335
+ * A view: the drawing, the run it is in and the paragraph. Sizes are points, as Office JS reports them.
336
+ *
337
+ * A picture in a kept `mc:AlternateContent`'s selected branch has the element as its `holder`. Reads and
338
+ * the setters are the selected branch's drawing; `delete` removes the element whole, every branch with
339
+ * it (CR-010 phase 3).
223
340
  */
224
341
  export class InlinePicture {
225
342
  constructor(
226
343
  /** The `w:drawing` element pair. */
227
344
  element,
228
345
  /** The run holding the drawing, and the array holding the run. */
229
- run, paragraph) {
346
+ run, paragraph,
347
+ /** The run's item the drawing stands in, where that is not the drawing: a kept `mc:AlternateContent`. */
348
+ holder) {
230
349
  this.element = element;
231
350
  this.run = run;
232
351
  this.paragraph = paragraph;
352
+ this.holder = holder;
233
353
  }
234
354
  /** The wp:inline; a floating picture (wp:anchor) is not an InlinePicture, and throws. */
235
355
  get inline() {
@@ -272,20 +392,39 @@ export class InlinePicture {
272
392
  set altTextTitle(text) {
273
393
  this.inline.docPr.title = text;
274
394
  }
275
- /** The kind of image, from the content type of the part the picture embeds. */
395
+ /**
396
+ * The kind of image, from the content type of the part `getBase64` reads: `Svg` for an SVG picture
397
+ * (`svgImagePart`), else the `a:blip`'s part's. Office JS gives `null` for an SVG picture, though its
398
+ * typings declare the property non-null and `Word.ImageFormat` has `Svg` (test/README.md check 45);
399
+ * this package gives `Svg`, a departure (CR-002 section 52).
400
+ */
276
401
  get imageFormat() {
277
- var _a;
278
- const part = this.imagePart();
279
- return (_a = (part && FORMAT_BY_CONTENT_TYPE[part.contentType])) !== null && _a !== void 0 ? _a : 'Unsupported';
402
+ var _a, _b;
403
+ const part = (_a = this.svgImagePart()) !== null && _a !== void 0 ? _a : this.imagePart();
404
+ return (_b = (part && FORMAT_BY_CONTENT_TYPE[part.contentType])) !== null && _b !== void 0 ? _b : 'Unsupported';
280
405
  }
281
406
  /** The relationship id of the embedded image (r:embed), if any (extension). */
282
407
  get relId() {
283
408
  var _a, _b;
284
409
  return (_b = (_a = this.pic()) === null || _a === void 0 ? void 0 : _a.blipFill.blip) === null || _b === void 0 ? void 0 : _b.embed;
285
410
  }
286
- /** The image's bytes as base64 (Office JS getBase64ImageSrc). */
411
+ /**
412
+ * The relationship id of the SVG the picture embeds beside its image, if any (extension): the
413
+ * `r:embed` of an `asvg:svgBlip` in the `a:blip`'s `a:extLst`, as Office 2016 and later write an SVG
414
+ * picture, with the `a:blip`'s own image a PNG of it for readers that don't understand SVG.
415
+ */
416
+ get svgRelId() {
417
+ var _a, _b;
418
+ const embed = (_b = svgBlipOf((_a = this.pic()) === null || _a === void 0 ? void 0 : _a.blipFill.blip)) === null || _b === void 0 ? void 0 : _b.embed;
419
+ return embed === undefined || embed === '' ? undefined : embed;
420
+ }
421
+ /**
422
+ * The image's bytes as base64 (Office JS getBase64ImageSrc). For an SVG picture, the SVG
423
+ * (`svgImagePart`), as Office JS gives it (test/README.md check 43); else the `a:blip`'s image.
424
+ */
287
425
  async getBase64() {
288
- const part = this.imagePart();
426
+ var _a;
427
+ const part = (_a = this.svgImagePart()) !== null && _a !== void 0 ? _a : this.imagePart();
289
428
  if (!part)
290
429
  throw new Docx4JException('This picture has no image part (a linked or missing image)');
291
430
  return base64Encode(await part.getBytes());
@@ -294,29 +433,97 @@ export class InlinePicture {
294
433
  getBase64ImageSrc() {
295
434
  return this.getBase64();
296
435
  }
297
- /** The image part this picture embeds (extension). */
436
+ /** The image part this picture embeds: the `a:blip`'s, which is an SVG picture's PNG (extension). */
298
437
  imagePart() {
438
+ return this.partFor(this.relId);
439
+ }
440
+ /** The SVG part an SVG picture embeds beside its image (`svgRelId`), if any (extension). */
441
+ svgImagePart() {
442
+ return this.partFor(this.svgRelId);
443
+ }
444
+ partFor(relId) {
299
445
  var _a;
300
- const relId = this.relId;
301
446
  const source = this.paragraph.parentBody.part;
302
447
  if (!relId || !source)
303
448
  return undefined;
304
449
  const part = (_a = source.relationshipsPart) === null || _a === void 0 ? void 0 : _a.getPart(relId);
305
450
  return part instanceof ImagePart ? part : undefined;
306
451
  }
307
- /** Removes the picture; the run goes too when the picture was all it held. */
452
+ /**
453
+ * Removes the picture; the run goes too when the picture was all it held. A picture in a kept
454
+ * `mc:AlternateContent` takes the element with it, every branch (`holder`).
455
+ *
456
+ * While the package tracks changes it is marked deleted instead: its run goes into a `w:del`, and
457
+ * when the run held more than the picture, the picture is first given a run of its own with the
458
+ * same properties, so the deletion holds it alone. Accepting the deletion then takes it, and its
459
+ * image part with it (CR-002 section 46). A picture its author inserted under tracking is taken
460
+ * back, as `Paragraph.delete` takes back its own text, and one inside a deletion is refused.
461
+ *
462
+ * A picture removed outright (tracking off, or taken back) queues its image relationship on the
463
+ * package. On save the relationship goes if nothing then references it, and the image part with it
464
+ * when nothing else targets it. Waiting for the save lets a picture cut and pasted back keep its
465
+ * image.
466
+ */
308
467
  delete() {
309
- var _a;
468
+ var _a, _b;
469
+ const place = placeOf(this.paragraph.p, this.run);
470
+ if (!place)
471
+ return;
472
+ const revision = place.revision;
473
+ const tracker = this.paragraph.changeTracker;
474
+ if (tracker && ((revision === null || revision === void 0 ? void 0 : revision.kind) === 'del' || (revision === null || revision === void 0 ? void 0 : revision.kind) === 'moveFrom')) {
475
+ throw new Docx4JException(`This picture is inside a w:${revision.kind} (deleted by ${revision.value.author}); reject the deletion first`);
476
+ }
477
+ if (tracker && !tracker.ownInsertion(revision)) {
478
+ this.markDeleted(tracker, place);
479
+ return;
480
+ }
481
+ const item = (_a = this.holder) !== null && _a !== void 0 ? _a : this.element;
310
482
  const content = this.run.value.content;
311
- const at = (_a = content === null || content === void 0 ? void 0 : content.indexOf(this.element)) !== null && _a !== void 0 ? _a : -1;
483
+ const at = (_b = content === null || content === void 0 ? void 0 : content.indexOf(item)) !== null && _b !== void 0 ? _b : -1;
312
484
  if (content && at >= 0)
313
485
  content.splice(at, 1);
314
486
  if (!content || content.length === 0) {
315
- const holder = runHolderOf(this.paragraph.p, this.run);
316
- const i = holder ? holder.indexOf(this.run) : -1;
317
- if (holder && i >= 0)
318
- holder.splice(i, 1);
487
+ const i = place.items.indexOf(this.run);
488
+ if (i >= 0)
489
+ place.items.splice(i, 1);
490
+ if (revision && revision.items.length === 0) { // an insertion left empty goes with it
491
+ const j = revision.owner.indexOf(revision.element);
492
+ if (j >= 0)
493
+ revision.owner.splice(j, 1);
494
+ }
495
+ }
496
+ const ids = new Set();
497
+ relationshipIdsIn(item, ids);
498
+ const pkg = this.paragraph.parentBody.package_;
499
+ queueRelationshipRemovals(pkg === null || pkg === void 0 ? void 0 : pkg.pendingRelationshipRemovals, this.paragraph.parentBody.part, ids);
500
+ }
501
+ /** The picture's run in a `w:del`, the picture split into a run of its own when the run held more. */
502
+ markDeleted(tracker, place) {
503
+ var _a, _b;
504
+ const run = this.run;
505
+ const at = place.items.indexOf(run);
506
+ const content = ((_a = run.value.content) !== null && _a !== void 0 ? _a : []);
507
+ const item = (_b = this.holder) !== null && _b !== void 0 ? _b : this.element;
508
+ const i = content.indexOf(item);
509
+ if (at < 0 || i < 0)
510
+ return;
511
+ const before = content.slice(0, i);
512
+ const after = content.slice(i + 1);
513
+ const pieces = [];
514
+ let alone = run;
515
+ if (before.length > 0 || after.length > 0) {
516
+ alone = runOf([item], copyRPr(run.value.rPr));
517
+ if (before.length > 0) {
518
+ run.value.content = before;
519
+ pieces.push(run);
520
+ }
319
521
  }
522
+ pieces.push(tracker.del([alone]));
523
+ if (after.length > 0)
524
+ pieces.push(runOf(after, copyRPr(run.value.rPr)));
525
+ place.items.splice(at, 1, ...pieces);
526
+ linkParents(pieces, place.parent);
320
527
  }
321
528
  pic() {
322
529
  const any = this.inline.graphic.graphicData.any;
@@ -338,24 +545,27 @@ export class InlinePicture {
338
545
  }
339
546
  }
340
547
  }
341
- /** The array holding a run inside a paragraph (the paragraph's content, or a holder's). */
342
- function runHolderOf(p, run) {
343
- const seek = (items) => {
344
- var _a, _b, _c;
548
+ function placeOf(p, run) {
549
+ const seek = (items, parent, revision) => {
550
+ var _a, _b, _c, _d;
345
551
  if (!items)
346
552
  return undefined;
347
553
  if (items.includes(run))
348
- return items;
554
+ return revision ? { items, parent, revision } : { items, parent };
349
555
  for (const item of items) {
350
556
  const v = item.value;
351
557
  if (typeof v !== 'object' || v === null)
352
558
  continue;
353
- const nested = seek((_b = (_a = v.content) !== null && _a !== void 0 ? _a : v.customXmlOrSmartTagOrSdt) !== null && _b !== void 0 ? _b : (_c = v.sdtContent) === null || _c === void 0 ? void 0 : _c.content);
354
- if (nested)
355
- return nested;
559
+ const holder = (_a = v.sdtContent) !== null && _a !== void 0 ? _a : v;
560
+ const nested = (_c = (_b = v.content) !== null && _b !== void 0 ? _b : v.customXmlOrSmartTagOrSdt) !== null && _c !== void 0 ? _c : (_d = v.sdtContent) === null || _d === void 0 ? void 0 : _d.content;
561
+ const kind = revisionKindOf(item);
562
+ const inner = kind && nested ? { kind, element: item, value: v, items: nested, owner: items } : revision;
563
+ const found = seek(nested, holder, inner);
564
+ if (found)
565
+ return found;
356
566
  }
357
567
  return undefined;
358
568
  };
359
- return seek(p.content);
569
+ return seek(p.content, p, undefined);
360
570
  }
361
571
  export { IMAGE_CONTENT_TYPES_BY_EXTENSION };
@@ -5,7 +5,7 @@ import { Docx4JException } from '../../opc/exceptions.mjs';
5
5
  import { DEFAULT_NUMBERING_XML } from '../../parts/wml/defaultNumbering.mjs';
6
6
  import { NumberingState, storyKindOf } from '../listnumbering/state.mjs';
7
7
  import { LEVELS } from '../listnumbering/definitions.mjs';
8
- import { isElement, linkParents } from './tree.mjs';
8
+ import { isElement, linkParents, selectedBranchOf, ALTERNATE_CONTENT } from './tree.mjs';
9
9
  import { Font } from './Font.mjs';
10
10
  function packageOf(body) {
11
11
  const pkg = body.package_;
@@ -60,6 +60,9 @@ function numRefOf(paragraph) {
60
60
  * counts in. `test/parity.test.mjs`'s `walkNumbering`, which is the harness's own walk: a text
61
61
  * box's paragraphs stay in this story's list (they are in the part, in document order) but
62
62
  * count in a story of their own, which is docx4j's rule (CR-014 probe P7).
63
+ *
64
+ * A kept `mc:AlternateContent` is walked through its selected branch alone (`selectedBranchOf`,
65
+ * docx4j's `McMode.READ`), so a text box held in both branches counts once (CR-010 phase 3).
63
66
  */
64
67
  function walkStory(root, state, emit) {
65
68
  const walk = (value, current) => {
@@ -70,11 +73,17 @@ function walkStory(root, state, emit) {
70
73
  }
71
74
  if (typeof value !== 'object' || value === null)
72
75
  return;
76
+ if (typeof value.nodeType === 'number')
77
+ return; // DOM the model does not type
73
78
  if (isElement(value)) {
74
79
  walk(value.value, current);
75
80
  return;
76
81
  }
77
82
  const node = value;
83
+ if (node.TYPE_NAME === ALTERNATE_CONTENT) {
84
+ walk(selectedBranchOf(node), current);
85
+ return;
86
+ }
78
87
  let inner = current;
79
88
  if (node.TYPE_NAME === 'org_docx4j_wml.P')
80
89
  emit(node, current);
@@ -1,6 +1,6 @@
1
1
  import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
2
  import type { CellContext } from '../properties/table.mjs';
3
- import { type Element, W_NS, type TextSegment, type TextViewOptions } from './tree.mjs';
3
+ import { type Element, W_NS, type SpanEdges, type SegmentOptions, type TextSegment, type TextViewOptions } from './tree.mjs';
4
4
  import { Font, type FontTracking } from './Font.mjs';
5
5
  import { Range } from './Range.mjs';
6
6
  import { type SearchOptions } from './search.mjs';
@@ -160,7 +160,12 @@ export declare class Paragraph {
160
160
  get parentTableCell(): TableCell | undefined;
161
161
  /** The run-level content controls in this paragraph, in order, nested ones included. */
162
162
  get contentControls(): ContentControl[];
163
- /** The inline pictures in this paragraph, in order (Office JS Paragraph.inlinePictures). */
163
+ /**
164
+ * The inline pictures in this paragraph, in order (Office JS Paragraph.inlinePictures): each
165
+ * `w:drawing` whose `wp:inline` holds a `pic:pic`, in a run or in the selected branch of a kept
166
+ * `mc:AlternateContent` (`picturesIn`). An inline chart, text box or group is not one (CR-010
167
+ * section 12.6).
168
+ */
164
169
  get inlinePictures(): InlinePicture[];
165
170
  /** Text inserted at the start, the end, or replacing the whole paragraph's text. */
166
171
  insertText(text: string, location: 'Start' | 'End' | 'Replace'): Range;
@@ -198,6 +203,10 @@ export declare class Paragraph {
198
203
  replaceText(find: string, replace: string, options?: SearchOptions): number;
199
204
  /** The tracked changes in this paragraph, in document order (CR-002 phase F). */
200
205
  getTrackedChanges(): TrackedChange[];
206
+ /**
207
+ * The paragraph's range: 'Whole' and 'Content' hold its whole content, the pictures at its start and
208
+ * end among them, so deleting it deletes them (CR-002 section 48); 'Start' and 'End' are collapsed.
209
+ */
201
210
  getRange(location?: 'Whole' | 'Start' | 'End' | 'Content'): Range;
202
211
  /**
203
212
  * Removes the paragraph from its container. While the package tracks changes, the content
@@ -214,6 +223,12 @@ export declare class Paragraph {
214
223
  * Does nothing when the mark is already deleted.
215
224
  */
216
225
  markDeletedInPlace(tracker: ChangeTracker): void;
226
+ /**
227
+ * Queues the relationships that content removed outright referenced (a picture's image, a link's
228
+ * target), to be settled when the package is saved: each goes then if nothing references it, and
229
+ * its part with it when nothing else targets that (CR-002 sections 47 and 48).
230
+ */
231
+ releaseRelationships(removed: unknown): void;
217
232
  /** The paragraph as XML (extension; Office JS getOoxml wraps it in a package). */
218
233
  getXml(): Promise<string>;
219
234
  /** docx4j: the P's content list. */
@@ -253,15 +268,19 @@ export declare class Paragraph {
253
268
  get changeTracker(): ChangeTracker | undefined;
254
269
  /** What `Font` needs to write `w:rPrChange`: the tracker, and the runs that are our own insertion (`Range` shares it). */
255
270
  fontTracking(): FontTracking | undefined;
256
- /** The text segments (w:t, w:tab, ...) with their offsets. */
257
- segments(options?: TextViewOptions): TextSegment[];
271
+ /** The text segments (w:t, w:tab, ...) with their offsets; `{ objects: true }` adds the pictures. */
272
+ segments(options?: SegmentOptions): TextSegment[];
258
273
  /**
259
274
  * Replaces the text in [start, end) with `text` and returns the range of the inserted text.
260
275
  * Runs are edited in place: a w:t loses or gains characters, a tab or break inside the range
261
276
  * is removed, a run left empty is removed. Text is inserted into the w:t at `start`,
262
277
  * else into a new run with the formatting of the nearest run.
278
+ *
279
+ * The pictures in the span go with its text: those strictly inside it, and those at an edge `edges`
280
+ * says the span holds (`WHOLE` for a paragraph's whole content), so a span of no text can still
281
+ * delete a picture (CR-002 section 48).
263
282
  */
264
- splice(start: number, end: number, text: string, prefer?: 'before' | 'after'): Range;
283
+ splice(start: number, end: number, text: string, prefer?: 'before' | 'after', edges?: SpanEdges): Range;
265
284
  /**
266
285
  * Inserts run-level items (runs, hyperlinks, content controls) at a text offset, splitting the
267
286
  * run there when the offset falls inside one. PARENT is linked on what is inserted.
@@ -317,6 +336,8 @@ export declare class Paragraph {
317
336
  * The text in [start, end) becomes a deletion: the runs holding it are isolated, their `w:t`
318
337
  * turned into `w:delText`, and each run of consecutive ones moved into one `w:del`. Text this
319
338
  * author had inserted is simply removed, as Word does, rather than nested in a `w:del`.
339
+ * The pictures in the span (`splice`'s `edges`) are deleted with the text, each in a run of its own
340
+ * where its run held more, as `InlinePicture.delete` gives it one (CR-002 section 48).
320
341
  * Returns the `w:del` elements it made, in document order.
321
342
  */
322
343
  private deleteText;
@@ -365,6 +386,8 @@ export declare class Paragraph {
365
386
  * Splits runs so that every run holding text in [start, end) holds nothing else, and returns
366
387
  * the segments inside the span. `splitAt` does the `w:t` boundaries; this also moves the items
367
388
  * of a run that straddles a boundary (a tab, a break, a drawing) out into runs of their own.
389
+ * The pictures in the span (`splice`'s `edges`) are among the segments returned, and a run
390
+ * holding one holds nothing outside the span either.
368
391
  */
369
392
  private isolate;
370
393
  /** Moves the items before `seg` into a run of their own, in front of `seg`'s run. */