@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
@@ -5,6 +5,7 @@ import { CustomXmlPart, type CustomXmlPartOwner } from './CustomXmlPart.mjs';
5
5
  import type { CustomXmlPartLookup } from './XmlMapping.mjs';
6
6
  import type { XPathEngine } from './xpath.mjs';
7
7
  import { type BindingResult } from './bindings.mjs';
8
+ import { type XhtmlHost, type XhtmlBindingOptions } from './xhtml.mjs';
8
9
  /**
9
10
  * What the collection needs of its package. A structural interface, so that this module does not
10
11
  * import `WordprocessingMLPackage` (which imports it): the package hands itself in.
@@ -18,6 +19,8 @@ export interface CustomXmlHost {
18
19
  boundBodies(): Body[];
19
20
  /** Where a new custom XML part's relationship goes: the main document part (Word drops parts related from the package alone). */
20
21
  customXmlRelationshipSource(): Part;
22
+ /** The package, for the XHTML pass (list definitions, styles, relationships); absent, XHTML controls are left and noted. */
23
+ readonly xhtmlHost?: XhtmlHost;
21
24
  }
22
25
  /** A subset of Office JS `Word.CustomXmlPartCollection`. */
23
26
  export declare class CustomXmlPartCollection implements CustomXmlPartOwner, CustomXmlPartLookup {
@@ -62,7 +65,9 @@ export declare class CustomXmlPartCollection implements CustomXmlPartOwner, Cust
62
65
  * which is what Word does when it opens the document. Unmarshals the main document part (and the
63
66
  * headers and footers it has) and parses the custom XML parts.
64
67
  */
65
- applyBindings(): Promise<BindingResult>;
68
+ applyBindings(options?: {
69
+ html?: XhtmlBindingOptions;
70
+ }): Promise<BindingResult>;
66
71
  /**
67
72
  * docx4j `UpdateXmlFromDocumentSurface.updateCustomXmlParts`: writes what the controls now show
68
73
  * back into the custom XML. The reverse of `applyBindings`, and what makes an edit to a bound
@@ -14,6 +14,8 @@ import { parseXml, decodeXmlText } from '../../xml/dom.mjs';
14
14
  import { createDatastoreItem } from '@docx4j/generated-objects-ts/factory/org_docx4j_customXmlProperties';
15
15
  import { CustomXmlPart } from './CustomXmlPart.mjs';
16
16
  import { applyBindingsTo, updateFromControls } from './bindings.mjs';
17
+ import { applyXhtmlBindingsTo, isXhtmlBound } from './xhtml.mjs';
18
+ import { applyPictureHandlersTo, updateFromPictureControls } from './pictures.mjs';
17
19
  /** A subset of Office JS `Word.CustomXmlPartCollection`. */
18
20
  export class CustomXmlPartCollection {
19
21
  constructor(host) {
@@ -133,9 +135,19 @@ export class CustomXmlPartCollection {
133
135
  * which is what Word does when it opens the document. Unmarshals the main document part (and the
134
136
  * headers and footers it has) and parses the custom XML parts.
135
137
  */
136
- async applyBindings() {
138
+ async applyBindings(options = {}) {
139
+ var _a, _b;
137
140
  await this.load();
138
- return applyBindingsTo(this.controlsOf(await this.host.getBoundBodies()));
141
+ const controls = this.controlsOf(await this.host.getBoundBodies());
142
+ const result = applyBindingsTo(controls);
143
+ // od:Handler=picture through the tag's od:xpath entry (CR-005 section 9): after the text bindings, which took the picture controls.
144
+ applyPictureHandlersTo(controls, this, result);
145
+ // Escaped XHTML through the tag's od:xpath entry (CR-005 section 8.2): after the text bindings, asynchronous.
146
+ if (this.host.xhtmlHost)
147
+ await applyXhtmlBindingsTo(controls, this, this.host.xhtmlHost, result, (_a = options.html) !== null && _a !== void 0 ? _a : {});
148
+ else if (controls.some(isXhtmlBound))
149
+ ((_b = result.notes) !== null && _b !== void 0 ? _b : (result.notes = [])).push('XHTML-bound controls left: the collection has no package to make their lists, styles and links');
150
+ return result;
139
151
  }
140
152
  /**
141
153
  * docx4j `UpdateXmlFromDocumentSurface.updateCustomXmlParts`: writes what the controls now show
@@ -144,7 +156,11 @@ export class CustomXmlPartCollection {
144
156
  */
145
157
  async updateFromContentControls() {
146
158
  await this.load();
147
- return updateFromControls(this.controlsOf(await this.host.getBoundBodies()));
159
+ const controls = this.controlsOf(await this.host.getBoundBodies());
160
+ const result = updateFromControls(controls);
161
+ // a mapped picture control's image back to its node, as Word 15 writes it (CR-005 section 9, check 38)
162
+ await updateFromPictureControls(controls, result);
163
+ return result;
148
164
  }
149
165
  controlsOf(bodies) {
150
166
  return bodies.flatMap((body) => body.contentControls);
@@ -12,6 +12,8 @@ export interface BindingResult {
12
12
  updated: number;
13
13
  /** Controls left alone: the part is missing, the XPath selects nothing, or the kind is not handled. */
14
14
  skipped: number;
15
+ /** What the XHTML and picture passes left or dropped, one line each (CR-005 sections 8.2 and 9; REQ-076). */
16
+ notes?: string[];
15
17
  }
16
18
  /**
17
19
  * Whether a node's value is a Flat OPC package: what Word writes into the node of a mapped rich
@@ -32,9 +34,10 @@ export declare function formatDate(pattern: string, date: Date, locale?: string)
32
34
  /**
33
35
  * Pushes the custom XML value into one control (docx4j BindingHandler's per-sdt work). Returns
34
36
  * false when nothing was written: no binding, no part, an XPath that selects nothing, a kind
35
- * this phase does not handle (pictures, explicit rich text), or a node holding a Flat OPC package.
37
+ * this phase does not handle (explicit rich text), a node holding a Flat OPC package, or a
38
+ * picture control whose node holds no image (`notes` says which; CR-005 section 9).
36
39
  */
37
- export declare function applyBindingTo(control: ContentControl): boolean;
40
+ export declare function applyBindingTo(control: ContentControl, notes?: string[]): boolean;
38
41
  /** `applyBindings` over a list of controls. */
39
42
  export declare function applyBindingsTo(controls: ContentControl[]): BindingResult;
40
43
  /**
@@ -1,6 +1,7 @@
1
1
  import { deepCopy } from '@docx4j/generated-objects-ts';
2
2
  import * as el from '@docx4j/generated-objects-ts/el/org_docx4j_wml';
3
3
  import { sdtProperty } from '@docx4j/generated-objects-ts/builders/wml';
4
+ import { bindPicture } from './pictures.mjs';
4
5
  /** The style Word gives placeholder text, and the text docx4j's placeholder.xml carries. */
5
6
  export const PLACEHOLDER_STYLE = 'PlaceholderText';
6
7
  export const PLACEHOLDER_TEXT = 'Click here to enter text.';
@@ -114,9 +115,10 @@ function dateOf(value) {
114
115
  /**
115
116
  * Pushes the custom XML value into one control (docx4j BindingHandler's per-sdt work). Returns
116
117
  * false when nothing was written: no binding, no part, an XPath that selects nothing, a kind
117
- * this phase does not handle (pictures, explicit rich text), or a node holding a Flat OPC package.
118
+ * this phase does not handle (explicit rich text), a node holding a Flat OPC package, or a
119
+ * picture control whose node holds no image (`notes` says which; CR-005 section 9).
118
120
  */
119
- export function applyBindingTo(control) {
121
+ export function applyBindingTo(control, notes) {
120
122
  var _a;
121
123
  const mapping = control.xmlMapping;
122
124
  if (!mapping.isMapped)
@@ -125,9 +127,14 @@ export function applyBindingTo(control) {
125
127
  if (!node)
126
128
  return false;
127
129
  const value = node.text.trim();
128
- // A picture binding carries base64 image data; docx4j replaces the a:blip embed. Deferred (section 12).
129
- if (control.type === 'Picture')
130
- return false;
130
+ // A picture control's node holds base64 image data: the picture's a:blip is pointed at a new
131
+ // image part and the drawing kept, as docx4j's bind.xslt mode picture3 does (CR-005 section 9).
132
+ if (control.type === 'Picture') {
133
+ const outcome = bindPicture(control, value, { kind: 'keep' }, control.title || control.tag || `picture control ${control.id}`);
134
+ if (outcome.note)
135
+ notes === null || notes === void 0 ? void 0 : notes.push(outcome.note);
136
+ return outcome.done;
137
+ }
131
138
  // Explicit rich text is bound from flat OPC or XHTML in docx4j; deferred, as it is there for this route.
132
139
  if (sdtProperty(control.sdt.sdtPr, 'richText') !== undefined)
133
140
  return false;
@@ -169,15 +176,18 @@ export function applyBindingTo(control) {
169
176
  /** `applyBindings` over a list of controls. */
170
177
  export function applyBindingsTo(controls) {
171
178
  const result = { bound: 0, updated: 0, skipped: 0 };
179
+ const notes = [];
172
180
  for (const control of controls) {
173
181
  if (!control.xmlMapping.isMapped)
174
182
  continue;
175
183
  result.bound++;
176
- if (applyBindingTo(control))
184
+ if (applyBindingTo(control, notes))
177
185
  result.updated++;
178
186
  else
179
187
  result.skipped++;
180
188
  }
189
+ if (notes.length > 0)
190
+ result.notes = notes;
181
191
  return result;
182
192
  }
183
193
  /**
@@ -194,6 +204,8 @@ export function updateFromControl(control) {
194
204
  const node = mapping.customXmlNode;
195
205
  if (!node)
196
206
  return false;
207
+ // A picture control is the asynchronous pass's (updateFromPictureControl, CR-005 section 9): its
208
+ // image's bytes go back to the node as base64, as Word 15 writes them (check 38).
197
209
  if (control.type === 'Picture')
198
210
  return false;
199
211
  if (sdtProperty(control.sdt.sdtPr, 'richText') !== undefined)
@@ -236,6 +248,9 @@ export function updateFromControls(controls) {
236
248
  for (const control of controls) {
237
249
  if (!control.xmlMapping.isMapped)
238
250
  continue;
251
+ // counted by the picture pass, which reads the image part (CR-005 section 9)
252
+ if (control.type === 'Picture')
253
+ continue;
239
254
  result.bound++;
240
255
  if (updateFromControl(control))
241
256
  result.updated++;
@@ -5,3 +5,6 @@ export { XmlMapping, type CustomXmlPartLookup } from './XmlMapping.mjs';
5
5
  export { CheckboxContentControl, DatePickerContentControl, ListContentControl, ContentControlListItem, PictureContentControl, RepeatingSectionContentControl, GroupContentControl, checkboxRun, CHECKED_SYMBOL, UNCHECKED_SYMBOL, CHECKBOX_FONT, } from './kinds.mjs';
6
6
  export { applyBindingsTo, applyBindingTo, updateFromControls, updateFromControl, runsForValue, formatDate, holdsFlatOpc, PLACEHOLDER_STYLE, PLACEHOLDER_TEXT, type BindingResult } from './bindings.mjs';
7
7
  export { controlIdScope, sdtKindFor, isRunLevel } from './insert.mjs';
8
+ export { tagParamsOf, xpathsEntriesOf, selectEntryNode, OPENDOPE_XPATHS_NS, XHTML_CONTENT_TYPE, PICTURE_HANDLER, type XPathsEntry } from './opendope.mjs';
9
+ export { applyXhtmlBindingTo, applyXhtmlBindingsTo, isXhtmlBound, type XhtmlHost, type XhtmlBindingOptions } from './xhtml.mjs';
10
+ export { applyPictureHandlerTo, applyPictureHandlersTo, isPictureHandlerBound, bindPicture, pictureWidthOf, updateFromPictureControl, updateFromPictureControls, type PictureWidth, type PictureOutcome } from './pictures.mjs';
@@ -7,3 +7,6 @@ export { XmlMapping } from './XmlMapping.mjs';
7
7
  export { CheckboxContentControl, DatePickerContentControl, ListContentControl, ContentControlListItem, PictureContentControl, RepeatingSectionContentControl, GroupContentControl, checkboxRun, CHECKED_SYMBOL, UNCHECKED_SYMBOL, CHECKBOX_FONT, } from './kinds.mjs';
8
8
  export { applyBindingsTo, applyBindingTo, updateFromControls, updateFromControl, runsForValue, formatDate, holdsFlatOpc, PLACEHOLDER_STYLE, PLACEHOLDER_TEXT } from './bindings.mjs';
9
9
  export { controlIdScope, sdtKindFor, isRunLevel } from './insert.mjs';
10
+ export { tagParamsOf, xpathsEntriesOf, selectEntryNode, OPENDOPE_XPATHS_NS, XHTML_CONTENT_TYPE, PICTURE_HANDLER } from './opendope.mjs';
11
+ export { applyXhtmlBindingTo, applyXhtmlBindingsTo, isXhtmlBound } from './xhtml.mjs';
12
+ export { applyPictureHandlerTo, applyPictureHandlersTo, isPictureHandlerBound, bindPicture, pictureWidthOf, updateFromPictureControl, updateFromPictureControls } from './pictures.mjs';
@@ -0,0 +1,32 @@
1
+ import type { CustomXmlPartLookup } from './XmlMapping.mjs';
2
+ import type { CustomXmlNode } from './CustomXmlPart.mjs';
3
+ export declare const OPENDOPE_XPATHS_NS = "http://opendope.org/xpaths";
4
+ export declare const XHTML_CONTENT_TYPE = "application/xhtml+xml";
5
+ /** The tag's `od:Handler` value for a picture (the specification's section 9.3; CR-005 section 9). */
6
+ export declare const PICTURE_HANDLER = "picture";
7
+ /** The tag's parameters, `key=value` pairs separated by `&`, in order; a value is as written (the content type keeps its `/` and `+`). */
8
+ export declare function tagParamsOf(tag: string): Map<string, string>;
9
+ /**
10
+ * An XPaths part entry: `<xpath id="x1"><dataBinding storeItemID xpath prefixMappings/></xpath>`.
11
+ * The attributes are unqualified (`xpaths.xsd`: `attributeFormDefault="unqualified"`; docx4j's
12
+ * `org.opendope.xpaths.Xpaths` and its invoice.docx write them so); a `w:`-qualified form, which
13
+ * this package's own check 37 and 38 files were made with, is read as well.
14
+ */
15
+ export interface XPathsEntry {
16
+ id: string;
17
+ storeItemID: string;
18
+ xpath: string;
19
+ prefixMappings: string;
20
+ }
21
+ /** The XPaths part's entries by id, from the first custom XML part in the OpenDoPE XPaths namespace; empty without one. */
22
+ export declare function xpathsEntriesOf(parts: CustomXmlPartLookup): Map<string, XPathsEntry>;
23
+ /**
24
+ * The node an `od:xpath` entry selects: the entry's XPath over the part its `storeItemID` names
25
+ * (the id compared without braces or case, as `getItem` does). What a tag-bound control's pass
26
+ * starts from; the note says why there is no node.
27
+ */
28
+ export declare function selectEntryNode(entryId: string, parts: CustomXmlPartLookup): {
29
+ node: CustomXmlNode;
30
+ } | {
31
+ note: string;
32
+ };
@@ -0,0 +1,56 @@
1
+ export const OPENDOPE_XPATHS_NS = 'http://opendope.org/xpaths';
2
+ export const XHTML_CONTENT_TYPE = 'application/xhtml+xml';
3
+ /** The tag's `od:Handler` value for a picture (the specification's section 9.3; CR-005 section 9). */
4
+ export const PICTURE_HANDLER = 'picture';
5
+ /** The tag's parameters, `key=value` pairs separated by `&`, in order; a value is as written (the content type keeps its `/` and `+`). */
6
+ export function tagParamsOf(tag) {
7
+ const out = new Map();
8
+ for (const pair of tag.split('&')) {
9
+ const eq = pair.indexOf('=');
10
+ if (eq <= 0)
11
+ continue;
12
+ const key = pair.slice(0, eq).trim();
13
+ if (!out.has(key))
14
+ out.set(key, pair.slice(eq + 1).trim());
15
+ }
16
+ return out;
17
+ }
18
+ /** The XPaths part's entries by id, from the first custom XML part in the OpenDoPE XPaths namespace; empty without one. */
19
+ export function xpathsEntriesOf(parts) {
20
+ const out = new Map();
21
+ const part = parts.items.find((p) => p.namespaceUri === OPENDOPE_XPATHS_NS);
22
+ if (!part)
23
+ return out;
24
+ const root = part.document.documentElement;
25
+ if (!root)
26
+ return out;
27
+ const entries = root.getElementsByTagNameNS(OPENDOPE_XPATHS_NS, 'xpath');
28
+ for (let i = 0; i < entries.length; i++) {
29
+ const entry = entries[i];
30
+ const id = entry.getAttribute('id');
31
+ const binding = entry.getElementsByTagNameNS(OPENDOPE_XPATHS_NS, 'dataBinding')[0];
32
+ if (!id || !binding)
33
+ continue;
34
+ const read = (name) => binding.getAttribute(name) || binding.getAttributeNS('http://schemas.openxmlformats.org/wordprocessingml/2006/main', name) || binding.getAttribute(`w:${name}`) || '';
35
+ out.set(id, { id, storeItemID: read('storeItemID'), xpath: read('xpath'), prefixMappings: read('prefixMappings') });
36
+ }
37
+ return out;
38
+ }
39
+ /**
40
+ * The node an `od:xpath` entry selects: the entry's XPath over the part its `storeItemID` names
41
+ * (the id compared without braces or case, as `getItem` does). What a tag-bound control's pass
42
+ * starts from; the note says why there is no node.
43
+ */
44
+ export function selectEntryNode(entryId, parts) {
45
+ var _a;
46
+ const entry = xpathsEntriesOf(parts).get(entryId);
47
+ if (!entry)
48
+ return { note: `od:xpath=${entryId}: no such entry in the XPaths part` };
49
+ const part = (_a = parts.getItem(entry.storeItemID)) !== null && _a !== void 0 ? _a : parts.items.find((p) => p.id.replace(/[{}]/g, '').toLowerCase() === entry.storeItemID.replace(/[{}]/g, '').toLowerCase());
50
+ if (!part)
51
+ return { note: `od:xpath=${entryId}: no custom XML part ${entry.storeItemID}` };
52
+ const node = part.selectSingleNode(entry.xpath, entry.prefixMappings);
53
+ if (!node)
54
+ return { note: `od:xpath=${entryId}: ${entry.xpath} selects nothing` };
55
+ return { node };
56
+ }
@@ -0,0 +1,57 @@
1
+ import type { ContentControl } from '../content/ContentControl.mjs';
2
+ import type { CustomXmlPartLookup } from './XmlMapping.mjs';
3
+ import type { BindingResult } from './bindings.mjs';
4
+ /**
5
+ * What the tag's `width` says: absent keeps the control's drawing and replaces its image; `auto`
6
+ * a new inline picture at its natural size, scaled down to the text width; `N` the same, scaled
7
+ * down to N twips when that is narrower than the text width (a value that is neither is `auto`,
8
+ * which is what docx4j does with one it cannot parse).
9
+ */
10
+ export type PictureWidth = {
11
+ kind: 'keep';
12
+ } | {
13
+ kind: 'auto';
14
+ } | {
15
+ kind: 'twips';
16
+ twips: number;
17
+ };
18
+ export interface PictureOutcome {
19
+ done: boolean;
20
+ /** Why the control was left as it was. */
21
+ note?: string;
22
+ }
23
+ /** Whether a control is bound to a picture through its tag (`od:Handler=picture` with an `od:xpath`). */
24
+ export declare function isPictureHandlerBound(control: ContentControl): boolean;
25
+ /** The tag's `width` parameter, read as `PictureWidth` says. */
26
+ export declare function pictureWidthOf(params: Map<string, string>): PictureWidth;
27
+ /**
28
+ * Binds one control to a value: base64 image data (a data URL accepted). With `keep`, the first
29
+ * `a:blip` is pointed at a new image part and the drawing is left as it is, as Word binds a picture
30
+ * control (`setBlipImage`: a raster image in `r:embed`, the `asvg:svgBlip` taken away; an SVG under
31
+ * the `asvg:svgBlip`, `r:embed` taken away; the effects kept; CR-002 section 52); a picture
32
+ * control holding no picture gets a new inline one at its natural size instead (docx4j's pre-v3
33
+ * fallback), a tag-bound control holding none is left and noted. Otherwise the control's content
34
+ * becomes one run holding a new inline picture, sized as `PictureWidth` says, in the first
35
+ * paragraph of a block-level control (its properties kept) or as a run-level control's content.
36
+ * An empty value, a value that is not an image this package reads (PNG, JPEG, GIF, BMP; SVG where
37
+ * the drawing is kept, since it has no pixel size to place a new picture at), and a body with no
38
+ * part each leave the control as it was, with a note. The image part the control showed before
39
+ * stays in the package, as docx4j leaves it.
40
+ */
41
+ export declare function bindPicture(control: ContentControl, value: string, width: PictureWidth, label: string): PictureOutcome;
42
+ /** Binds one tag-bound control (shape b): the entry's node, the tag's width. */
43
+ export declare function applyPictureHandlerTo(control: ContentControl, parts: CustomXmlPartLookup): PictureOutcome;
44
+ /** The picture pass over the tag-bound controls, after the text bindings: counted into the result, what was left said in its notes. */
45
+ export declare function applyPictureHandlersTo(controls: ContentControl[], parts: CustomXmlPartLookup, result: BindingResult): void;
46
+ /**
47
+ * The reverse direction for a mapped picture control (docx4j's `UpdateXmlFromDocumentSurface`
48
+ * skips pictures; Word 15 writes the picture back, check 38): the bytes of the image part the
49
+ * first `a:blip` shows, as base64, into the node: its `asvg:svgBlip`'s SVG where it has one, as
50
+ * `InlinePicture.getBase64` gives (CR-002 section 52), else its `r:embed`'s image. Nothing is written when the node already holds
51
+ * those bytes (compared decoded, so Word's line-wrapped base64 counts as the same), when the
52
+ * control shows no picture, or when the blip's part is not an image part. Asynchronous, since a
53
+ * part's bytes may still be in the container.
54
+ */
55
+ export declare function updateFromPictureControl(control: ContentControl): Promise<boolean>;
56
+ /** `updateFromContentControls`' picture pass: the mapped picture controls, counted into the result. */
57
+ export declare function updateFromPictureControls(controls: ContentControl[], result: BindingResult): Promise<void>;
@@ -0,0 +1,192 @@
1
+ import { find } from '../content/tree.mjs';
2
+ import { addImage, addImagePart, writableWidthEmu, setBlipImage, svgBlipOf } from '../content/InlinePicture.mjs';
3
+ import { ContentTypes } from '../../opc/ContentTypes.mjs';
4
+ import { ImagePart } from '../../parts/BinaryPart.mjs';
5
+ import { base64Decode, base64Encode } from '../../xml/dom.mjs';
6
+ import { enclosingCellOf } from '../properties/table.mjs';
7
+ import { tagParamsOf, selectEntryNode, PICTURE_HANDLER } from './opendope.mjs';
8
+ const EMU_PER_TWIP = 635;
9
+ const BLIP = 'org_docx4j_dml.CTBlip';
10
+ /** Whether a control is bound to a picture through its tag (`od:Handler=picture` with an `od:xpath`). */
11
+ export function isPictureHandlerBound(control) {
12
+ const params = tagParamsOf(control.tag);
13
+ return params.get('od:Handler') === PICTURE_HANDLER && params.has('od:xpath');
14
+ }
15
+ /** The tag's `width` parameter, read as `PictureWidth` says. */
16
+ export function pictureWidthOf(params) {
17
+ const width = params.get('width');
18
+ if (width === undefined)
19
+ return { kind: 'keep' };
20
+ const twips = /^\d+$/.test(width.trim()) ? Number(width) : 0;
21
+ return twips > 0 ? { kind: 'twips', twips } : { kind: 'auto' };
22
+ }
23
+ /** The node's value as base64: a data URL's prefix dropped, whitespace left to the lenient decoder. */
24
+ function base64Of(value) {
25
+ return value.replace(/^\s*data:[^,]*,/, '').trim();
26
+ }
27
+ function messageOf(e) {
28
+ return e instanceof Error ? e.message : String(e);
29
+ }
30
+ /** The first `a:blip` anywhere in the control's content, whose `r:embed` is the image. */
31
+ function firstBlipOf(control) {
32
+ var _a;
33
+ for (const blip of find((_a = control.sdt.sdtContent) !== null && _a !== void 0 ? _a : {}, BLIP))
34
+ return blip;
35
+ return undefined;
36
+ }
37
+ /** A width stated in twips (`dxa`, or no type), else undefined. */
38
+ function twipsOf(width) {
39
+ return width && typeof width.w === 'number' && width.w >= 0 && (width.type === undefined || width.type === 'dxa') ? width.w : undefined;
40
+ }
41
+ /**
42
+ * Word's default cell margins, 108 twips left and right (the `Normal Table` style's
43
+ * `w:tblCellMar`, which every table inherits), taken where neither the cell nor the table states
44
+ * one; a table style stating other margins is not read.
45
+ */
46
+ const DEFAULT_CELL_MARGIN_TWIPS = 108;
47
+ /** The cell's left and right margins together, in twips: `w:tcMar`, else the table's `w:tblCellMar`, else Word's defaults. */
48
+ function cellMarginsTwips(tc, tbl) {
49
+ var _a, _b, _c, _d, _e, _f, _g, _h;
50
+ const own = (_a = tc.tcPr) === null || _a === void 0 ? void 0 : _a.tcMar;
51
+ const table = (_b = tbl.tblPr) === null || _b === void 0 ? void 0 : _b.tblCellMar;
52
+ const left = (_e = (_c = twipsOf(own === null || own === void 0 ? void 0 : own.left)) !== null && _c !== void 0 ? _c : twipsOf((_d = table === null || table === void 0 ? void 0 : table.left) !== null && _d !== void 0 ? _d : table === null || table === void 0 ? void 0 : table.start)) !== null && _e !== void 0 ? _e : DEFAULT_CELL_MARGIN_TWIPS;
53
+ const right = (_h = (_f = twipsOf(own === null || own === void 0 ? void 0 : own.right)) !== null && _f !== void 0 ? _f : twipsOf((_g = table === null || table === void 0 ? void 0 : table.right) !== null && _g !== void 0 ? _g : table === null || table === void 0 ? void 0 : table.end)) !== null && _h !== void 0 ? _h : DEFAULT_CELL_MARGIN_TWIPS;
54
+ return left + right;
55
+ }
56
+ /**
57
+ * The width a new picture must fit, in EMU: the cell's `w:tcW` less its margins when the control
58
+ * is in a table cell and the width is stated in twips (a percentage or `auto` falls to the page),
59
+ * else the page's text width; narrowed to `width=N`. Undefined where nothing states a width (a
60
+ * header's body has no `w:sectPr`): the natural size then.
61
+ */
62
+ function maxWidthEmu(control, width) {
63
+ var _a;
64
+ const cell = enclosingCellOf(control.element.value);
65
+ const tcW = cell ? twipsOf((_a = cell.tc.tcPr) === null || _a === void 0 ? void 0 : _a.tcW) : undefined;
66
+ const inner = cell && tcW !== undefined ? tcW - cellMarginsTwips(cell.tc, cell.tbl) : undefined;
67
+ const cellEmu = inner !== undefined && inner > 0 ? inner * EMU_PER_TWIP : undefined;
68
+ const text = cellEmu !== null && cellEmu !== void 0 ? cellEmu : writableWidthEmu(control.parentBody.container);
69
+ if (width.kind === 'twips') {
70
+ const wanted = width.twips * EMU_PER_TWIP;
71
+ return text === undefined ? wanted : Math.min(wanted, text);
72
+ }
73
+ return text;
74
+ }
75
+ /**
76
+ * Binds one control to a value: base64 image data (a data URL accepted). With `keep`, the first
77
+ * `a:blip` is pointed at a new image part and the drawing is left as it is, as Word binds a picture
78
+ * control (`setBlipImage`: a raster image in `r:embed`, the `asvg:svgBlip` taken away; an SVG under
79
+ * the `asvg:svgBlip`, `r:embed` taken away; the effects kept; CR-002 section 52); a picture
80
+ * control holding no picture gets a new inline one at its natural size instead (docx4j's pre-v3
81
+ * fallback), a tag-bound control holding none is left and noted. Otherwise the control's content
82
+ * becomes one run holding a new inline picture, sized as `PictureWidth` says, in the first
83
+ * paragraph of a block-level control (its properties kept) or as a run-level control's content.
84
+ * An empty value, a value that is not an image this package reads (PNG, JPEG, GIF, BMP; SVG where
85
+ * the drawing is kept, since it has no pixel size to place a new picture at), and a body with no
86
+ * part each leave the control as it was, with a note. The image part the control showed before
87
+ * stays in the package, as docx4j leaves it.
88
+ */
89
+ export function bindPicture(control, value, width, label) {
90
+ const base64 = base64Of(value);
91
+ if (base64 === '')
92
+ return { done: false, note: `${label}: the node is empty; the control is left as it is` };
93
+ const source = control.parentBody.part;
94
+ if (!source)
95
+ return { done: false, note: `${label}: the control's body has no part to hold an image` };
96
+ if (width.kind === 'keep') {
97
+ const blip = firstBlipOf(control);
98
+ if (blip) {
99
+ try {
100
+ const added = addImagePart(source, base64);
101
+ setBlipImage(blip, added.relId, added.info.contentType === ContentTypes.IMAGE_SVG);
102
+ }
103
+ catch (e) {
104
+ return { done: false, note: `${label}: not an image this package reads (${messageOf(e)})` };
105
+ }
106
+ control.isShowingPlaceholder = false;
107
+ return { done: true };
108
+ }
109
+ if (control.type !== 'Picture')
110
+ return { done: false, note: `${label}: the control holds no a:blip to point at the image (od:Handler=picture without width keeps the drawing)` };
111
+ width = { kind: 'auto' };
112
+ }
113
+ let run;
114
+ try {
115
+ run = addImage(source, base64, control.parentBody.container, maxWidthEmu(control, width)).run;
116
+ }
117
+ catch (e) {
118
+ return { done: false, note: `${label}: not an image this package reads (${messageOf(e)})` };
119
+ }
120
+ control.setBoundContent([run]);
121
+ return { done: true };
122
+ }
123
+ /** Binds one tag-bound control (shape b): the entry's node, the tag's width. */
124
+ export function applyPictureHandlerTo(control, parts) {
125
+ var _a;
126
+ const params = tagParamsOf(control.tag);
127
+ const entryId = (_a = params.get('od:xpath')) !== null && _a !== void 0 ? _a : '';
128
+ const selected = selectEntryNode(entryId, parts);
129
+ if ('note' in selected)
130
+ return { done: false, note: selected.note };
131
+ return bindPicture(control, selected.node.text, pictureWidthOf(params), `od:xpath=${entryId}`);
132
+ }
133
+ /** The picture pass over the tag-bound controls, after the text bindings: counted into the result, what was left said in its notes. */
134
+ export function applyPictureHandlersTo(controls, parts, result) {
135
+ var _a;
136
+ for (const control of controls) {
137
+ if (!isPictureHandlerBound(control))
138
+ continue;
139
+ result.bound++;
140
+ const outcome = applyPictureHandlerTo(control, parts);
141
+ if (outcome.done)
142
+ result.updated++;
143
+ else
144
+ result.skipped++;
145
+ if (outcome.note)
146
+ ((_a = result.notes) !== null && _a !== void 0 ? _a : (result.notes = [])).push(outcome.note);
147
+ }
148
+ }
149
+ /**
150
+ * The reverse direction for a mapped picture control (docx4j's `UpdateXmlFromDocumentSurface`
151
+ * skips pictures; Word 15 writes the picture back, check 38): the bytes of the image part the
152
+ * first `a:blip` shows, as base64, into the node: its `asvg:svgBlip`'s SVG where it has one, as
153
+ * `InlinePicture.getBase64` gives (CR-002 section 52), else its `r:embed`'s image. Nothing is written when the node already holds
154
+ * those bytes (compared decoded, so Word's line-wrapped base64 counts as the same), when the
155
+ * control shows no picture, or when the blip's part is not an image part. Asynchronous, since a
156
+ * part's bytes may still be in the container.
157
+ */
158
+ export async function updateFromPictureControl(control) {
159
+ var _a, _b;
160
+ const mapping = control.xmlMapping;
161
+ if (!mapping.isMapped || control.type !== 'Picture')
162
+ return false;
163
+ const node = mapping.customXmlNode;
164
+ if (!node)
165
+ return false;
166
+ const blip = firstBlipOf(control);
167
+ const source = control.parentBody.part;
168
+ const relId = ((_a = svgBlipOf(blip)) === null || _a === void 0 ? void 0 : _a.embed) || (blip === null || blip === void 0 ? void 0 : blip.embed);
169
+ if (!relId || !source)
170
+ return false;
171
+ const part = (_b = source.relationshipsPart) === null || _b === void 0 ? void 0 : _b.getPart(relId);
172
+ if (!(part instanceof ImagePart))
173
+ return false;
174
+ const bytes = await part.getBytes();
175
+ const held = base64Decode(node.text);
176
+ if (held.length === bytes.length && held.every((b, i) => b === bytes[i]))
177
+ return false;
178
+ node.text = base64Encode(bytes);
179
+ return true;
180
+ }
181
+ /** `updateFromContentControls`' picture pass: the mapped picture controls, counted into the result. */
182
+ export async function updateFromPictureControls(controls, result) {
183
+ for (const control of controls) {
184
+ if (control.type !== 'Picture' || !control.xmlMapping.isMapped)
185
+ continue;
186
+ result.bound++;
187
+ if (await updateFromPictureControl(control))
188
+ result.updated++;
189
+ else
190
+ result.skipped++;
191
+ }
192
+ }
@@ -0,0 +1,42 @@
1
+ import type * as wml from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';
2
+ import type { PropertyResolver } from '../properties/index.mjs';
3
+ import type { ContentControl } from '../content/ContentControl.mjs';
4
+ import type { CustomXmlPartLookup } from './XmlMapping.mjs';
5
+ import type { BindingResult } from './bindings.mjs';
6
+ /** What the XHTML pass needs of the package. */
7
+ export interface XhtmlHost {
8
+ numbering: {
9
+ newList(options?: {
10
+ bullet?: boolean;
11
+ }): Promise<string>;
12
+ restart(numId: string): string;
13
+ };
14
+ styles: {
15
+ ensure(ids: readonly string[]): Promise<string[]>;
16
+ };
17
+ requireStyles(ids: Iterable<string>): void;
18
+ getPropertyResolver(): Promise<PropertyResolver>;
19
+ getMainDocumentPart(): {
20
+ styleDefinitionsPart?: {
21
+ readContents(): Promise<wml.Styles>;
22
+ } | undefined;
23
+ };
24
+ }
25
+ export interface XhtmlBindingOptions {
26
+ /** The HTML parser: a browser's `DOMParser` by default; jsdom's or linkedom's in Node. */
27
+ parser?: (html: string) => Document;
28
+ }
29
+ /** Whether a control is bound to XHTML through its tag. */
30
+ export declare function isXhtmlBound(control: ContentControl): boolean;
31
+ /**
32
+ * Binds one XHTML control. Returns what was done, or a note saying why not: no entry, no part, no
33
+ * node, no parser in this runtime. The content the markup gave that the control cannot hold is in
34
+ * `dropped` (a run-level control's blocks after the first, and what the converter dropped).
35
+ */
36
+ export declare function applyXhtmlBindingTo(control: ContentControl, parts: CustomXmlPartLookup, host: XhtmlHost, options?: XhtmlBindingOptions): Promise<{
37
+ done: boolean;
38
+ note?: string;
39
+ dropped?: Record<string, number>;
40
+ }>;
41
+ /** The XHTML pass over the controls, after the text bindings: counted into the result, what was left said in its notes. */
42
+ export declare function applyXhtmlBindingsTo(controls: ContentControl[], parts: CustomXmlPartLookup, host: XhtmlHost, result: BindingResult, options?: XhtmlBindingOptions): Promise<void>;