@stll/folio-core 0.1.2 → 0.2.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 (101) hide show
  1. package/dist/docx/headerFooterParser.js +3 -0
  2. package/dist/docx/headerFooterVerbatim.d.ts +19 -0
  3. package/dist/docx/headerFooterVerbatim.js +25 -0
  4. package/dist/docx/index.d.ts +2 -0
  5. package/dist/docx/index.js +2 -0
  6. package/dist/docx/metafileRaster.d.ts +30 -0
  7. package/dist/docx/metafileRaster.js +80 -0
  8. package/dist/docx/paragraphParser.js +5 -1
  9. package/dist/docx/parser.d.ts +12 -2
  10. package/dist/docx/parser.js +31 -1
  11. package/dist/docx/serializer/headerFooterSerializer.js +3 -0
  12. package/dist/docx/styleParser.js +2 -2
  13. package/dist/docx/tableParser.js +2 -3
  14. package/dist/docx/unzip.js +7 -1
  15. package/dist/docx/wrapTypes.d.ts +8 -1
  16. package/dist/docx/wrapTypes.js +0 -8
  17. package/dist/docx/xmlParser.d.ts +7 -1
  18. package/dist/docx/xmlParser.js +17 -1
  19. package/dist/i18n/messages/catalogs.gen.d.ts +2776 -0
  20. package/dist/i18n/messages/catalogs.gen.js +2742 -0
  21. package/dist/i18n/messages/messages.gen.d.ts +166 -0
  22. package/dist/i18n/messages/messages.gen.js +0 -0
  23. package/dist/i18n/messages.d.ts +13 -0
  24. package/dist/i18n/messages.js +39 -0
  25. package/dist/layout-bridge/cellSelectionHighlight.d.ts +18 -0
  26. package/dist/layout-bridge/cellSelectionHighlight.js +36 -0
  27. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +3 -2
  28. package/dist/layout-bridge/convert/headerFooterLayout.js +39 -15
  29. package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
  30. package/dist/layout-bridge/headerFooterLayout.d.ts +50 -0
  31. package/dist/layout-bridge/headerFooterLayout.js +216 -0
  32. package/dist/layout-bridge/measuring/measureBlocksPipeline.d.ts +33 -0
  33. package/dist/layout-bridge/measuring/measureBlocksPipeline.js +0 -0
  34. package/dist/layout-bridge/sectionColumns.d.ts +11 -0
  35. package/dist/layout-bridge/sectionColumns.js +21 -0
  36. package/dist/layout-bridge/tableInsertHover.d.ts +42 -0
  37. package/dist/layout-bridge/tableInsertHover.js +102 -0
  38. package/dist/layout-engine/index.d.ts +2 -1
  39. package/dist/layout-engine/index.js +2 -1
  40. package/dist/layout-engine/measure/lineBreaks.d.ts +10 -0
  41. package/dist/layout-engine/measure/lineBreaks.js +30 -0
  42. package/dist/layout-engine/measure/measureContainer.js +1 -1
  43. package/dist/layout-engine/measure/measureParagraph.js +63 -22
  44. package/dist/layout-engine/pmPageIndex.d.ts +24 -0
  45. package/dist/layout-engine/pmPageIndex.js +37 -0
  46. package/dist/layout-engine/types.d.ts +2 -1
  47. package/dist/layout-painter/imageLayout.d.ts +101 -0
  48. package/dist/layout-painter/imageLayout.js +161 -0
  49. package/dist/layout-painter/renderPage.d.ts +16 -1
  50. package/dist/layout-painter/renderPage.js +28 -1
  51. package/dist/layout-painter/renderParagraph.js +2 -1
  52. package/dist/layout-painter/sdtBoundary.d.ts +24 -1
  53. package/dist/layout-painter/sdtBoundary.js +40 -1
  54. package/dist/managers/AutoSaveManager.d.ts +89 -0
  55. package/dist/managers/AutoSaveManager.js +279 -0
  56. package/dist/managers/ClipboardManager.d.ts +31 -0
  57. package/dist/managers/ClipboardManager.js +147 -0
  58. package/dist/paged-layout/paragraphFlash.d.ts +17 -0
  59. package/dist/paged-layout/paragraphFlash.js +56 -0
  60. package/dist/paged-layout/paragraphFlashTypes.d.ts +18 -0
  61. package/dist/paged-layout/paragraphFlashTypes.js +0 -0
  62. package/dist/paged-layout/rangeProjection.d.ts +23 -1
  63. package/dist/paged-layout/rangeProjection.js +34 -1
  64. package/dist/prosemirror/cellDragSelection.d.ts +34 -0
  65. package/dist/prosemirror/cellDragSelection.js +69 -0
  66. package/dist/prosemirror/commands/sectionBreak.d.ts +15 -0
  67. package/dist/prosemirror/commands/sectionBreak.js +54 -0
  68. package/dist/prosemirror/commentIdAllocator.d.ts +32 -0
  69. package/dist/prosemirror/commentIdAllocator.js +35 -0
  70. package/dist/prosemirror/commentOps.d.ts +35 -0
  71. package/dist/prosemirror/commentOps.js +104 -0
  72. package/dist/prosemirror/conversion/toProseDoc.js +29 -6
  73. package/dist/prosemirror/extensions/index.d.ts +3 -0
  74. package/dist/prosemirror/extensions/index.js +3 -0
  75. package/dist/prosemirror/imageCommit.d.ts +42 -0
  76. package/dist/prosemirror/imageCommit.js +118 -0
  77. package/dist/prosemirror/paraText.d.ts +26 -0
  78. package/dist/prosemirror/paraText.js +73 -0
  79. package/dist/prosemirror/plugins/templateDirectives.d.ts +21 -1
  80. package/dist/prosemirror/plugins/templateDirectives.js +40 -1
  81. package/dist/prosemirror/queries.d.ts +46 -0
  82. package/dist/prosemirror/queries.js +74 -0
  83. package/dist/prosemirror/styles/styleResolver.d.ts +43 -1
  84. package/dist/prosemirror/styles/styleResolver.js +34 -0
  85. package/dist/prosemirror/tableResize.d.ts +49 -0
  86. package/dist/prosemirror/tableResize.js +162 -0
  87. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +106 -0
  88. package/dist/prosemirror/utils/extractTrackedChanges.js +379 -0
  89. package/dist/prosemirror/utils/visualLineNavigation.d.ts +27 -0
  90. package/dist/prosemirror/utils/visualLineNavigation.js +217 -0
  91. package/dist/style-engine/index.d.ts +2 -2
  92. package/dist/style-engine/styleEngine.d.ts +14 -1
  93. package/dist/style-engine/styleEngine.js +15 -0
  94. package/dist/utils/colorResolver.d.ts +8 -1
  95. package/dist/utils/colorResolver.js +12 -1
  96. package/dist/utils/findVerticalScrollParent.d.ts +20 -0
  97. package/dist/utils/findVerticalScrollParent.js +30 -0
  98. package/dist/utils/fontResolver.d.ts +22 -3
  99. package/dist/utils/fontResolver.js +216 -31
  100. package/dist/utils/headerFooter.js +2 -0
  101. package/package.json +9 -1
@@ -1,6 +1,7 @@
1
1
  import { collectXmlnsDeclarations, parseXml } from "./xmlParser.js";
2
2
  import { parseWatermark } from "./watermarkParser.js";
3
3
  import { parseFooterReference, parseFooterReferences, parseHeaderReference, parseHeaderReferences } from "./headerFooterRefParser.js";
4
+ import { assignHeaderFooterVerbatimXml } from "./headerFooterVerbatim.js";
4
5
  import { parseBlockContent } from "./blockContentParser.js";
5
6
  //#region src/docx/headerFooterParser.ts
6
7
  /**
@@ -34,6 +35,7 @@ function parseHeader(headerXml, hdrFtrType = "default", styles = null, theme = n
34
35
  inHeaderFooter: true,
35
36
  rootXmlns: collectXmlnsDeclarations(rootElement)
36
37
  });
38
+ assignHeaderFooterVerbatimXml(result, headerXml);
37
39
  return result;
38
40
  }
39
41
  /**
@@ -74,6 +76,7 @@ function parseFooter(footerXml, hdrFtrType = "default", styles = null, theme = n
74
76
  inHeaderFooter: true,
75
77
  rootXmlns: collectXmlnsDeclarations(rootElement)
76
78
  });
79
+ assignHeaderFooterVerbatimXml(result, footerXml);
77
80
  return result;
78
81
  }
79
82
  /**
@@ -0,0 +1,19 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+
3
+ //#region src/docx/headerFooterVerbatim.d.ts
4
+ /**
5
+ * Folio extension on {@link HeaderFooter}: original part XML captured at parse
6
+ * time so unedited headers/footers re-emit byte-identically on save (VML OLE
7
+ * wrappers, smart tags, and other constructs the model cannot fully represent).
8
+ * Cleared on first edit.
9
+ */
10
+ type HeaderFooterWithVerbatim = document_d_exports.HeaderFooter & {
11
+ verbatimXml?: string; /** Fingerprint of modeled fields at parse time; verbatim replay is safe only while it matches. */
12
+ verbatimFingerprint?: string;
13
+ };
14
+ declare const getHeaderFooterVerbatimXml: (hf: document_d_exports.HeaderFooter) => string | undefined;
15
+ declare const canReplayHeaderFooterVerbatim: (hf: document_d_exports.HeaderFooter) => boolean;
16
+ declare const assignHeaderFooterVerbatimXml: (hf: document_d_exports.HeaderFooter, xml: string) => void;
17
+ declare const clearHeaderFooterVerbatimXml: (hf: document_d_exports.HeaderFooter) => void;
18
+ //#endregion
19
+ export { HeaderFooterWithVerbatim, assignHeaderFooterVerbatimXml, canReplayHeaderFooterVerbatim, clearHeaderFooterVerbatimXml, getHeaderFooterVerbatimXml };
@@ -0,0 +1,25 @@
1
+ //#region src/docx/headerFooterVerbatim.ts
2
+ const headerFooterSerializationFingerprint = (hf) => JSON.stringify({
3
+ content: hf.content,
4
+ watermark: hf.watermark,
5
+ watermarkBlockIndex: hf.watermarkBlockIndex,
6
+ rawWatermarkXml: hf.rawWatermarkXml
7
+ });
8
+ const getHeaderFooterVerbatimXml = (hf) => hf.verbatimXml;
9
+ const canReplayHeaderFooterVerbatim = (hf) => {
10
+ const ext = hf;
11
+ if (!ext.verbatimXml || !ext.verbatimFingerprint) return false;
12
+ return ext.verbatimFingerprint === headerFooterSerializationFingerprint(hf);
13
+ };
14
+ const assignHeaderFooterVerbatimXml = (hf, xml) => {
15
+ const ext = hf;
16
+ ext.verbatimXml = xml;
17
+ ext.verbatimFingerprint = headerFooterSerializationFingerprint(hf);
18
+ };
19
+ const clearHeaderFooterVerbatimXml = (hf) => {
20
+ const ext = hf;
21
+ delete ext.verbatimXml;
22
+ delete ext.verbatimFingerprint;
23
+ };
24
+ //#endregion
25
+ export { assignHeaderFooterVerbatimXml, canReplayHeaderFooterVerbatim, clearHeaderFooterVerbatimXml, getHeaderFooterVerbatimXml };
@@ -0,0 +1,2 @@
1
+ import { getCachedNumberingMap } from "./numberingParser.js";
2
+ export { getCachedNumberingMap };
@@ -0,0 +1,2 @@
1
+ import { getCachedNumberingMap } from "./numberingParser.js";
2
+ export { getCachedNumberingMap };
@@ -0,0 +1,30 @@
1
+ //#region src/docx/metafileRaster.d.ts
2
+ /**
3
+ * Extract a browser-renderable raster (PNG/JPEG) embedded inside an
4
+ * EMF/WMF metafile.
5
+ *
6
+ * Word frequently stores header logos / OLE preview pictures as EMF — a
7
+ * Windows GDI metafile browsers cannot decode. In practice such an EMF almost
8
+ * always carries the actual artwork as a single embedded PNG or JPEG (an
9
+ * `EmfPlusObject` bitmap record, or a `StretchDIBits` payload). Rather than
10
+ * implement a GDI renderer, this scans the byte stream for a raster signature,
11
+ * brackets it to its container's end marker, and returns the slice. The caller
12
+ * uses it as the media entry's `dataUrl` so `<img>` just works; the original
13
+ * EMF bytes stay on `MediaFile.data` for round-trip.
14
+ *
15
+ * When no embedded raster is found this returns `null`; callers fall back to a
16
+ * sized placeholder and/or the host-supplied `mediaResolver` hook.
17
+ */
18
+ type ExtractedRaster = {
19
+ bytes: Uint8Array;
20
+ mimeType: "image/png" | "image/jpeg";
21
+ };
22
+ /**
23
+ * Scan a metafile (EMF or WMF) for an embedded browser-renderable raster.
24
+ * Returns the first PNG, else first JPEG found; `null` when none is present.
25
+ */
26
+ declare function extractMetafileRaster(data: ArrayBuffer | Uint8Array): ExtractedRaster | null;
27
+ /** True for EMF/WMF MIME types — the formats browsers cannot render natively. */
28
+ declare function isMetafileMimeType(mimeType: string | undefined): boolean;
29
+ //#endregion
30
+ export { ExtractedRaster, extractMetafileRaster, isMetafileMimeType };
@@ -0,0 +1,80 @@
1
+ //#region src/docx/metafileRaster.ts
2
+ function indexOfBytes(haystack, needle, from = 0) {
3
+ outer: for (let i = from; i + needle.length <= haystack.length; i++) {
4
+ for (let j = 0; j < needle.length; j++) if (haystack[i + j] !== needle[j]) continue outer;
5
+ return i;
6
+ }
7
+ return -1;
8
+ }
9
+ function extractPng(bytes) {
10
+ const start = indexOfBytes(bytes, [
11
+ 137,
12
+ 80,
13
+ 78,
14
+ 71,
15
+ 13,
16
+ 10,
17
+ 26,
18
+ 10
19
+ ]);
20
+ if (start < 0) return null;
21
+ let off = start + 8;
22
+ while (off + 12 <= bytes.length) {
23
+ const b0 = bytes[off];
24
+ const b1 = bytes[off + 1];
25
+ const b2 = bytes[off + 2];
26
+ const b3 = bytes[off + 3];
27
+ const len = b0 << 24 | b1 << 16 | b2 << 8 | b3;
28
+ const type = String.fromCharCode(bytes[off + 4], bytes[off + 5], bytes[off + 6], bytes[off + 7]);
29
+ const next = off + 12 + (len >>> 0);
30
+ if (type === "IEND") return bytes.slice(start, off + 12);
31
+ if (next <= off || next > bytes.length) break;
32
+ off = next;
33
+ }
34
+ return null;
35
+ }
36
+ function extractJpeg(bytes) {
37
+ const start = indexOfBytes(bytes, [
38
+ 255,
39
+ 216,
40
+ 255
41
+ ]);
42
+ if (start < 0) return null;
43
+ let end = -1;
44
+ for (let i = bytes.length - 2; i > start + 1; i--) if (bytes[i] === 255 && bytes[i + 1] === 217) {
45
+ end = i + 2;
46
+ break;
47
+ }
48
+ return end > start ? bytes.slice(start, end) : null;
49
+ }
50
+ /**
51
+ * Scan a metafile (EMF or WMF) for an embedded browser-renderable raster.
52
+ * Returns the first PNG, else first JPEG found; `null` when none is present.
53
+ */
54
+ function extractMetafileRaster(data) {
55
+ const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
56
+ if (bytes.length < 32) return null;
57
+ const png = extractPng(bytes);
58
+ if (png) return {
59
+ bytes: png,
60
+ mimeType: "image/png"
61
+ };
62
+ const jpeg = extractJpeg(bytes);
63
+ if (jpeg) return {
64
+ bytes: jpeg,
65
+ mimeType: "image/jpeg"
66
+ };
67
+ return null;
68
+ }
69
+ const METAFILE_MIME = /* @__PURE__ */ new Set([
70
+ "image/x-emf",
71
+ "image/emf",
72
+ "image/x-wmf",
73
+ "image/wmf"
74
+ ]);
75
+ /** True for EMF/WMF MIME types — the formats browsers cannot render natively. */
76
+ function isMetafileMimeType(mimeType) {
77
+ return !!mimeType && METAFILE_MIME.has(mimeType);
78
+ }
79
+ //#endregion
80
+ export { extractMetafileRaster, isMetafileMimeType };
@@ -815,7 +815,11 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
815
815
  parsedContent: parseParagraphContents(child, styles, theme, null, rels, media, "default", inScopeXmlns)
816
816
  });
817
817
  break;
818
- case "smartTag": break;
818
+ case "smartTag": {
819
+ const inner = parseParagraphContents(child, styles, theme, null, rels, media, trackedContext, mergeXmlnsDeclarations(inScopeXmlns, child));
820
+ contents.push(...inner);
821
+ break;
822
+ }
819
823
  case "moveFromRangeStart": {
820
824
  const id = Number.parseInt(getAttribute(child, "w", "id") ?? "0", 10);
821
825
  const name = getAttribute(child, "w", "name") ?? "";
@@ -7,6 +7,15 @@ import { DocxUnzipLimits } from "./unzip.js";
7
7
  * Progress callback for tracking parsing stages
8
8
  */
9
9
  type ProgressCallback = (stage: string, percent: number) => void;
10
+ /**
11
+ * Host hook for converting media the browser cannot render natively
12
+ * (EMF/WMF/TIFF) into a displayable `data:` or `blob:` URL. Receives the
13
+ * parsed {@link MediaFile} (original bytes on `.data`); return the replacement
14
+ * URL, or `null`/`undefined` to keep the built-in handling. Built-in handling
15
+ * already extracts an embedded PNG/JPEG from EMF/WMF when one exists; this
16
+ * hook is for vector-only metafiles where the host rasterizes server-side.
17
+ */
18
+ type MediaResolver = (file: document_d_exports.MediaFile) => Promise<string | null | undefined>;
10
19
  /**
11
20
  * Parsing options
12
21
  */
@@ -18,7 +27,8 @@ type ParseOptions = {
18
27
  detectVariables?: boolean; /** Security limits for DOCX ZIP extraction */
19
28
  unzipLimits?: Partial<Omit<DocxUnzipLimits, "allowedMediaMimeTypes">> & {
20
29
  allowedMediaMimeTypes?: Iterable<string>;
21
- };
30
+ }; /** Optional async hook to override display URLs for non-browser media. */
31
+ mediaResolver?: MediaResolver;
22
32
  };
23
33
  /**
24
34
  * Parse a DOCX file into a complete Document model
@@ -64,4 +74,4 @@ declare function getDocxSummary(buffer: ArrayBuffer): Promise<{
64
74
  variableCount: number;
65
75
  }>;
66
76
  //#endregion
67
- export { DocxParseError, ParseOptions, ProgressCallback, fullParseDocx, getDocxSummary, getDocxVariables, parseDocx, quickParseDocx };
77
+ export { DocxParseError, MediaResolver, ParseOptions, ProgressCallback, fullParseDocx, getDocxSummary, getDocxVariables, parseDocx, quickParseDocx };
@@ -12,6 +12,7 @@ import { normalizeCommentReferences } from "./commentReferenceNormalization.js";
12
12
  import { extractAllTemplateVariables, parseDocumentBody } from "./documentParser.js";
13
13
  import { parseFooter, parseHeader } from "./headerFooterParser.js";
14
14
  import { normalizeHeaderFooterReferences } from "./headerFooterReferenceNormalization.js";
15
+ import { extractMetafileRaster, isMetafileMimeType } from "./metafileRaster.js";
15
16
  import { normalizeNumberingReferences } from "./numberingReferenceNormalization.js";
16
17
  import { parseSettings } from "./settingsParser.js";
17
18
  import { parseStylesPackage } from "./styleParser.js";
@@ -47,7 +48,7 @@ import { TaggedError } from "better-result";
47
48
  */
48
49
  async function parseDocx(input, options = {}) {
49
50
  const buffer = input instanceof ArrayBuffer ? input : await toArrayBuffer(input);
50
- const { onProgress = () => {}, preloadFonts = true, parseHeadersFooters = true, parseNotes = true, detectVariables = true, unzipLimits } = options;
51
+ const { onProgress = () => {}, preloadFonts = true, parseHeadersFooters = true, parseNotes = true, detectVariables = true, unzipLimits, mediaResolver } = options;
51
52
  const warnings = [];
52
53
  try {
53
54
  const timeStage = (_name, fn) => fn();
@@ -80,6 +81,7 @@ async function parseDocx(input, options = {}) {
80
81
  onProgress("Parsed numbering", 35);
81
82
  onProgress("Processing media files...", 35);
82
83
  const media = await timeStageAsync("media", () => buildMediaMap(raw, rels));
84
+ if (mediaResolver) await timeStageAsync("mediaResolver", () => applyMediaResolver(media, mediaResolver));
83
85
  onProgress("Processed media", 40);
84
86
  onProgress("Parsing document body...", 40);
85
87
  let documentBody = { content: [] };
@@ -196,6 +198,11 @@ var DocxParseError = class extends TaggedError("DocxParseError")() {};
196
198
  /**
197
199
  * Build media file map from raw content and relationships
198
200
  */
201
+ function copyBytesToArrayBuffer(bytes) {
202
+ const buffer = new ArrayBuffer(bytes.byteLength);
203
+ new Uint8Array(buffer).set(bytes);
204
+ return buffer;
205
+ }
199
206
  async function buildMediaMap(raw, _rels) {
200
207
  const media = /* @__PURE__ */ new Map();
201
208
  for (const [path, data] of raw.media.entries()) {
@@ -217,6 +224,20 @@ async function buildMediaMap(raw, _rels) {
217
224
  continue;
218
225
  }
219
226
  }
227
+ const raster = isMetafileMimeType(mimeType) ? extractMetafileRaster(data) : null;
228
+ if (raster) {
229
+ const mediaFile = {
230
+ path,
231
+ filename,
232
+ mimeType,
233
+ data,
234
+ dataUrl: mediaToDataUrl(copyBytesToArrayBuffer(raster.bytes), raster.mimeType)
235
+ };
236
+ media.set(path, mediaFile);
237
+ const normalizedPath = path.replace(/^word\//u, "");
238
+ if (normalizedPath !== path) media.set(normalizedPath, mediaFile);
239
+ continue;
240
+ }
220
241
  const mediaFile = {
221
242
  path,
222
243
  filename,
@@ -230,6 +251,15 @@ async function buildMediaMap(raw, _rels) {
230
251
  }
231
252
  return media;
232
253
  }
254
+ async function applyMediaResolver(media, resolver) {
255
+ const files = [...new Set(media.values())];
256
+ await Promise.all(files.map(async (file) => {
257
+ try {
258
+ const url = await resolver(file);
259
+ if (url) file.dataUrl = url;
260
+ } catch {}
261
+ }));
262
+ }
233
263
  function attachLazyDataUrl(mediaFile) {
234
264
  let cachedDataUrl;
235
265
  Object.defineProperty(mediaFile, "dataUrl", {
@@ -2,6 +2,7 @@ import { escapeXml } from "./xmlUtils.js";
2
2
  import { serializeBlockSdt } from "./blockSdtSerializer.js";
3
3
  import { serializeParagraph } from "./paragraphSerializer.js";
4
4
  import { serializeTable } from "./tableSerializer.js";
5
+ import { canReplayHeaderFooterVerbatim, getHeaderFooterVerbatimXml } from "../headerFooterVerbatim.js";
5
6
  //#region src/docx/serializer/headerFooterSerializer.ts
6
7
  const NAMESPACES = {
7
8
  wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
@@ -45,6 +46,8 @@ function serializeBlock(block) {
45
46
  * @returns Complete XML string for header*.xml or footer*.xml
46
47
  */
47
48
  function serializeHeaderFooter(hf) {
49
+ const verbatim = getHeaderFooterVerbatimXml(hf);
50
+ if (verbatim && canReplayHeaderFooterVerbatim(hf)) return verbatim;
48
51
  const rootTag = hf.type === "header" ? "w:hdr" : "w:ftr";
49
52
  const nsDecl = buildNamespaceDeclarations();
50
53
  const watermarkXml = serializeWatermarkParagraph(hf);
@@ -1,4 +1,4 @@
1
- import { findChild, findChildren, getAttribute, getLocalName, parseBooleanElement, parseNumericAttribute, parseXmlDocument } from "./xmlParser.js";
1
+ import { findChild, findChildren, getAttribute, getLocalName, parseBooleanElement, parseNumericAttribute, parseTableMeasurementValue, parseXmlDocument } from "./xmlParser.js";
2
2
  import { BorderStyleSchema, ConditionalStyleTypeSchema, EmphasisMarkSchema, FontThemeSchema, HighlightColorSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, ShadingPatternSchema, StyleTypeSchema, TabLeaderSchema, TabStopAlignmentSchema, TableCellTextDirectionSchema, TableRowHeightRuleSchema, TableWidthTypeSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
3
3
  import { resolveThemeFontRef } from "./themeParser.js";
4
4
  import { mergeTextFormatting } from "../utils/textFormattingMerge.js";
@@ -360,9 +360,9 @@ function parseParagraphProperties(pPr, theme) {
360
360
  */
361
361
  function parseTableMeasurement(element) {
362
362
  if (!element) return;
363
- const w = parseNumericAttribute(element, "w", "w");
364
363
  const rawType = getAttribute(element, "w", "type");
365
364
  const type = rawType === null ? "dxa" : narrowEnum(rawType, TableWidthTypeSchema);
365
+ const w = type ? parseTableMeasurementValue(element, type) : void 0;
366
366
  if (w !== void 0 && type) return {
367
367
  value: w,
368
368
  type
@@ -1,4 +1,4 @@
1
- import { findChild, findChildByLocalName, findChildren, getAttribute, getChildElements, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
1
+ import { findChild, findChildByLocalName, findChildren, getAttribute, getChildElements, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, parseTableMeasurementValue } from "./xmlParser.js";
2
2
  import { parseBookmarkEnd, parseBookmarkStart } from "./bookmarkParser.js";
3
3
  import { BorderStyleSchema, FloatingTableXSpecSchema, FloatingTableYSpecSchema, ShadingPatternSchema, TableCellTextDirectionSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
4
4
  import { parseParagraph } from "./paragraphParser.js";
@@ -12,12 +12,11 @@ import { appendBookmarkMarkerToLastParagraphInBlocks, appendBookmarkMarkerToLast
12
12
  */
13
13
  function parseTableMeasurement(element) {
14
14
  if (!element) return;
15
- const value = parseNumericAttribute(element, "w", "w") ?? 0;
16
15
  const typeStr = getAttribute(element, "w", "type") ?? "dxa";
17
16
  let type = "dxa";
18
17
  if (typeStr === "auto" || typeStr === "dxa" || typeStr === "nil" || typeStr === "pct") type = typeStr;
19
18
  return {
20
- value,
19
+ value: parseTableMeasurementValue(element, type) ?? 0,
21
20
  type
22
21
  };
23
22
  }
@@ -37,7 +37,9 @@ const DEFAULT_ALLOWED_MEDIA_MIME_TYPES = /* @__PURE__ */ new Set([
37
37
  "image/gif",
38
38
  "image/bmp",
39
39
  "image/tiff",
40
- "image/webp"
40
+ "image/webp",
41
+ "image/x-emf",
42
+ "image/x-wmf"
41
43
  ]);
42
44
  const PRESERVABLE_MEDIA_MIME_TYPES = /* @__PURE__ */ new Set([
43
45
  ...DEFAULT_ALLOWED_MEDIA_MIME_TYPES,
@@ -279,6 +281,10 @@ function isMediaContentAllowed(data, mimeType) {
279
281
  case "image/bmp": return bytes[0] === 66 && bytes[1] === 77;
280
282
  case "image/webp": return bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80;
281
283
  case "image/tiff": return bytes[0] === 73 && bytes[1] === 73 || bytes[0] === 77 && bytes[1] === 77;
284
+ case "image/x-emf":
285
+ case "image/emf": return bytes.length >= 44 && bytes[0] === 1 && bytes[40] === 32 && bytes[41] === 69 && bytes[42] === 77 && bytes[43] === 70;
286
+ case "image/x-wmf":
287
+ case "image/wmf": return bytes.length >= 4 && (bytes[0] === 215 && bytes[1] === 205 && bytes[2] === 198 && bytes[3] === 154 || (bytes[0] === 1 || bytes[0] === 2) && bytes[1] === 0 && bytes[2] === 9 && bytes[3] === 0);
282
288
  default: return false;
283
289
  }
284
290
  }
@@ -7,6 +7,13 @@
7
7
  * which OOXML wrap variants are floating, which paint over body text, and
8
8
  * which wrap around it.
9
9
  */
10
+ /**
11
+ * OOXML text-wrap variants for a drawing anchor (`wp:inline` vs the
12
+ * `wp:wrap*` elements). `inline` keeps the object in the text run; every other
13
+ * variant takes it out of inline flow. Mirrors the eigenpal docx-editor union
14
+ * so adapters share one wrap vocabulary.
15
+ */
16
+ type WrapType = "inline" | "square" | "tight" | "through" | "topAndBottom" | "behind" | "inFront";
10
17
  /**
11
18
  * `true` for any wrap type that takes the object out of inline flow.
12
19
  * Includes `topAndBottom` and the wrapNone variants (`behind`/`inFront`):
@@ -26,4 +33,4 @@ declare function isWrapNone(wrapType: string | undefined): boolean;
26
33
  */
27
34
  declare function wrapsAroundText(wrapType: string | undefined): boolean;
28
35
  //#endregion
29
- export { isFloatingWrapType, isWrapNone, wrapsAroundText };
36
+ export { WrapType, isFloatingWrapType, isWrapNone, wrapsAroundText };
@@ -1,12 +1,4 @@
1
1
  //#region src/docx/wrapTypes.ts
2
- /**
3
- * Wrap-type predicates shared across the layout pipeline.
4
- *
5
- * Mirrors eigenpal docx-editor `wrapTypes` so that the new textBoxFlow
6
- * predicates (eigenpal #474) and the existing image classifiers agree on
7
- * which OOXML wrap variants are floating, which paint over body text, and
8
- * which wrap around it.
9
- */
10
2
  const FLOATING_WRAP_TYPES = /* @__PURE__ */ new Set([
11
3
  "square",
12
4
  "tight",
@@ -237,6 +237,12 @@ declare function parseColorElement(element: XmlElement | null | undefined): {
237
237
  * @returns Parsed number or undefined
238
238
  */
239
239
  declare function parseNumericAttribute(element: XmlElement | null | undefined, namespace: string | null, name: string, scale?: number): number | undefined;
240
+ /**
241
+ * Parse `w:w` on a table width/height element. For `w:type="pct"`, producers
242
+ * sometimes emit human-readable percentages (`100%`) instead of 50ths-of-percent
243
+ * (`5000`); normalize those to the ECMA-376 unit the layout engine expects.
244
+ */
245
+ declare function parseTableMeasurementValue(element: XmlElement | null | undefined, widthType: string): number | undefined;
240
246
  /**
241
247
  * Parse a boolean value from an attribute or element presence
242
248
  *
@@ -294,4 +300,4 @@ declare function mergeXmlnsDeclarations(inherited: Record<string, string>, eleme
294
300
  */
295
301
  declare function cloneWithXmlnsDeclarations(element: XmlElement, xmlnsDecls: Record<string, string>): XmlElement;
296
302
  //#endregion
297
- export { NAMESPACES, XmlElement, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findByFullName, findChild, findChildByLocalName, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumericAttribute, parseXml, parseXmlDocument };
303
+ export { NAMESPACES, XmlElement, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findByFullName, findChild, findChildByLocalName, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumericAttribute, parseTableMeasurementValue, parseXml, parseXmlDocument };
@@ -409,6 +409,22 @@ function parseNumericAttribute(element, namespace, name, scale = 1) {
409
409
  return num * scale;
410
410
  }
411
411
  /**
412
+ * Parse `w:w` on a table width/height element. For `w:type="pct"`, producers
413
+ * sometimes emit human-readable percentages (`100%`) instead of 50ths-of-percent
414
+ * (`5000`); normalize those to the ECMA-376 unit the layout engine expects.
415
+ */
416
+ function parseTableMeasurementValue(element, widthType) {
417
+ const raw = getAttribute(element, "w", "w");
418
+ if (raw === null) return;
419
+ const trimmed = raw.trim();
420
+ if (widthType === "pct" && trimmed.endsWith("%")) {
421
+ const pct = Number.parseFloat(trimmed.slice(0, -1));
422
+ if (!Number.isNaN(pct)) return Math.round(pct * 50);
423
+ }
424
+ const num = Number.parseInt(trimmed, 10);
425
+ return Number.isNaN(num) ? void 0 : num;
426
+ }
427
+ /**
412
428
  * Parse a boolean value from an attribute or element presence
413
429
  *
414
430
  * OOXML boolean conventions:
@@ -524,4 +540,4 @@ function cloneWithXmlnsDeclarations(element, xmlnsDecls) {
524
540
  };
525
541
  }
526
542
  //#endregion
527
- export { NAMESPACES, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findByFullName, findChild, findChildByLocalName, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumericAttribute, parseXml, parseXmlDocument };
543
+ export { NAMESPACES, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findByFullName, findChild, findChildByLocalName, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumericAttribute, parseTableMeasurementValue, parseXml, parseXmlDocument };