@harbour-enterprises/superdoc 1.5.0-next.5 → 1.5.0-next.7

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.
@@ -3643,6 +3643,11 @@ const DEFAULT_DOCX_DEFS = {
3643
3643
  "xmlns:v": "urn:schemas-microsoft-com:vml",
3644
3644
  "xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
3645
3645
  "xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
3646
+ "xmlns:a": "http://schemas.openxmlformats.org/drawingml/2006/main",
3647
+ "xmlns:pic": "http://schemas.openxmlformats.org/drawingml/2006/picture",
3648
+ "xmlns:c": "http://schemas.openxmlformats.org/drawingml/2006/chart",
3649
+ "xmlns:dgm": "http://schemas.openxmlformats.org/drawingml/2006/diagram",
3650
+ "xmlns:lc": "http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas",
3646
3651
  "xmlns:w10": "urn:schemas-microsoft-com:office:word",
3647
3652
  "xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
3648
3653
  "xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml",
@@ -10087,6 +10092,11 @@ const SD_NODE_NAME$g = "tableRow";
10087
10092
  const validXmlAttributes$9 = ["w:rsidDel", "w:rsidR", "w:rsidRPr", "w:rsidTr", "w14:paraId", "w14:textId"].map(
10088
10093
  (xmlName) => createAttributeHandler(xmlName)
10089
10094
  );
10095
+ const getColspan$1 = (cell) => {
10096
+ const rawColspan = cell?.attrs?.colspan;
10097
+ const numericColspan = typeof rawColspan === "string" ? parseInt(rawColspan, 10) : rawColspan;
10098
+ return Number.isFinite(numericColspan) && numericColspan > 0 ? numericColspan : 1;
10099
+ };
10090
10100
  const encode$v = (params, encodedAttrs) => {
10091
10101
  const { row } = params.extraParams;
10092
10102
  let tableRowProperties = {};
@@ -10219,7 +10229,27 @@ const decode$x = (params, decodedAttrs) => {
10219
10229
  }
10220
10230
  return cell;
10221
10231
  });
10222
- const trimmedContent = sanitizedCells.filter((_2, index2) => !isPlaceholderCell(trimmedSlice[index2]));
10232
+ let trimmedContent = sanitizedCells.filter((_2, index2) => !isPlaceholderCell(trimmedSlice[index2]));
10233
+ const preferTableGrid = params.extraParams?.preferTableGrid === true;
10234
+ const totalColumns = params.extraParams?.totalColumns;
10235
+ if (preferTableGrid && typeof totalColumns === "number" && Number.isFinite(totalColumns) && totalColumns > 0) {
10236
+ const rawGridBefore = node.attrs?.tableRowProperties?.gridBefore;
10237
+ const numericGridBefore = typeof rawGridBefore === "string" ? parseInt(rawGridBefore, 10) : rawGridBefore;
10238
+ const safeGridBefore = Number.isFinite(numericGridBefore) && numericGridBefore > 0 ? numericGridBefore : 0;
10239
+ const effectiveGridBefore = leadingPlaceholders > 0 ? leadingPlaceholders : safeGridBefore;
10240
+ const availableColumns = Math.max(totalColumns - effectiveGridBefore, 0);
10241
+ let usedColumns = 0;
10242
+ const constrainedCells = [];
10243
+ for (const cell of trimmedContent) {
10244
+ const colspan = getColspan$1(cell);
10245
+ if (usedColumns + colspan > availableColumns) {
10246
+ break;
10247
+ }
10248
+ constrainedCells.push(cell);
10249
+ usedColumns += colspan;
10250
+ }
10251
+ trimmedContent = constrainedCells;
10252
+ }
10223
10253
  const translateParams = {
10224
10254
  ...params,
10225
10255
  node: { ...node, content: trimmedContent }
@@ -19241,9 +19271,9 @@ function handleImageNode(node, params, isAnchor) {
19241
19271
  if (!blip) {
19242
19272
  return null;
19243
19273
  }
19244
- const stretch = blipFill?.elements.find((el) => el.name === "a:stretch");
19245
- const fillRect = stretch?.elements.find((el) => el.name === "a:fillRect");
19246
- const srcRect = blipFill?.elements.find((el) => el.name === "a:srcRect");
19274
+ const stretch = blipFill?.elements?.find((el) => el.name === "a:stretch");
19275
+ const fillRect = stretch?.elements?.find((el) => el.name === "a:fillRect");
19276
+ const srcRect = blipFill?.elements?.find((el) => el.name === "a:srcRect");
19247
19277
  const srcRectAttrs = srcRect?.attributes || {};
19248
19278
  const srcRectHasNegativeValues = ["l", "t", "r", "b"].some((attr) => {
19249
19279
  const val = srcRectAttrs[attr];
@@ -25964,6 +25994,53 @@ const config$d = {
25964
25994
  attributes: validXmlAttributes$5
25965
25995
  };
25966
25996
  const translator$s = NodeTranslator.from(config$d);
25997
+ const getColspan = (cell) => {
25998
+ const rawColspan = cell?.attrs?.colspan;
25999
+ const numericColspan = typeof rawColspan === "string" ? parseInt(rawColspan, 10) : rawColspan;
26000
+ return Number.isFinite(numericColspan) && numericColspan > 0 ? numericColspan : 1;
26001
+ };
26002
+ const resolveGridBefore = (row) => {
26003
+ const rawGridBefore = row?.attrs?.tableRowProperties?.gridBefore ?? row?.attrs?.gridBefore;
26004
+ const numericGridBefore = typeof rawGridBefore === "string" ? parseInt(rawGridBefore, 10) : rawGridBefore;
26005
+ if (!Number.isFinite(numericGridBefore) || numericGridBefore <= 0) return 0;
26006
+ const cells = Array.isArray(row.content) ? row.content : [];
26007
+ let leadingGridBefore = 0;
26008
+ while (leadingGridBefore < cells.length && cells[leadingGridBefore]?.attrs?.__placeholder === "gridBefore") {
26009
+ leadingGridBefore += 1;
26010
+ }
26011
+ return leadingGridBefore > 0 ? 0 : numericGridBefore;
26012
+ };
26013
+ const advanceColumnsForCell = (columnIndex, cell) => columnIndex + getColspan(cell);
26014
+ const getCellStartColumn = (row, targetCell) => {
26015
+ const cells = Array.isArray(row.content) ? row.content : [];
26016
+ let columnIndex = resolveGridBefore(row);
26017
+ for (const cell of cells) {
26018
+ if (cell === targetCell) return columnIndex;
26019
+ columnIndex = advanceColumnsForCell(columnIndex, cell);
26020
+ }
26021
+ return columnIndex;
26022
+ };
26023
+ const findCellCoveringColumn = (row, targetColumn) => {
26024
+ const cells = Array.isArray(row.content) ? row.content : [];
26025
+ let columnIndex = resolveGridBefore(row);
26026
+ for (const cell of cells) {
26027
+ const colspan = getColspan(cell);
26028
+ if (targetColumn >= columnIndex && targetColumn < columnIndex + colspan) {
26029
+ return cell;
26030
+ }
26031
+ columnIndex = advanceColumnsForCell(columnIndex, cell);
26032
+ }
26033
+ return null;
26034
+ };
26035
+ const findInsertionIndexForColumn = (row, targetColumn) => {
26036
+ const cells = Array.isArray(row.content) ? row.content : [];
26037
+ let columnIndex = resolveGridBefore(row);
26038
+ for (let index2 = 0; index2 < cells.length; index2++) {
26039
+ if (columnIndex >= targetColumn) return index2;
26040
+ columnIndex = advanceColumnsForCell(columnIndex, cells[index2]);
26041
+ }
26042
+ return cells.length;
26043
+ };
25967
26044
  function preProcessVerticalMergeCells(table, { editorSchema }) {
25968
26045
  if (!table || !Array.isArray(table.content)) {
25969
26046
  return table;
@@ -25979,15 +26056,17 @@ function preProcessVerticalMergeCells(table, { editorSchema }) {
25979
26056
  const cell = row.content[cellIndex];
25980
26057
  if (!cell) continue;
25981
26058
  const attrs = cell.attrs || {};
25982
- if (!attrs.rowspan || attrs.rowspan <= 1) continue;
25983
- const maxRowspan = Math.min(attrs.rowspan, rows.length - rowIndex);
26059
+ const rawRowspan = typeof attrs.rowspan === "string" ? parseInt(attrs.rowspan, 10) : attrs.rowspan;
26060
+ if (!Number.isFinite(rawRowspan) || rawRowspan <= 1) continue;
26061
+ const maxRowspan = Math.min(rawRowspan, rows.length - rowIndex);
26062
+ const startColumn = getCellStartColumn(row, cell);
25984
26063
  for (let offset = 1; offset < maxRowspan; offset++) {
25985
26064
  const rowToChange = rows[rowIndex + offset];
25986
26065
  if (!rowToChange) continue;
25987
26066
  if (!Array.isArray(rowToChange.content)) {
25988
26067
  rowToChange.content = [];
25989
26068
  }
25990
- const existingCell = rowToChange.content[cellIndex];
26069
+ const existingCell = findCellCoveringColumn(rowToChange, startColumn);
25991
26070
  if (existingCell?.attrs?.continueMerge) continue;
25992
26071
  const mergedCell = {
25993
26072
  type: cell.type,
@@ -25998,7 +26077,8 @@ function preProcessVerticalMergeCells(table, { editorSchema }) {
25998
26077
  continueMerge: true
25999
26078
  }
26000
26079
  };
26001
- rowToChange.content.splice(cellIndex, 0, mergedCell);
26080
+ const insertionIndex = findInsertionIndexForColumn(rowToChange, startColumn);
26081
+ rowToChange.content.splice(insertionIndex, 0, mergedCell);
26002
26082
  }
26003
26083
  }
26004
26084
  }
@@ -26150,15 +26230,24 @@ const encode$q = (params) => {
26150
26230
  const decode$s = (params) => {
26151
26231
  const { grid: rawGrid } = params.node.attrs || {};
26152
26232
  const grid = Array.isArray(rawGrid) ? rawGrid : [];
26153
- const { firstRow = {} } = params.extraParams || {};
26233
+ const { firstRow = {}, preferTableGrid = false, totalColumns: requestedColumns } = params.extraParams || {};
26154
26234
  const cellNodes = firstRow.content?.filter((n) => n.type === "tableCell") ?? [];
26155
- const colWidthsFromCellNodes = cellNodes.flatMap((cell) => {
26235
+ let colWidthsFromCellNodes = cellNodes.flatMap((cell) => {
26156
26236
  const spanCount = Math.max(1, cell?.attrs?.colspan ?? 1);
26157
26237
  const colwidth = cell.attrs?.colwidth;
26158
26238
  return Array.from({ length: spanCount }).map((_2, span) => Array.isArray(colwidth) ? colwidth[span] : void 0);
26159
26239
  });
26160
26240
  const columnCountFromCells = colWidthsFromCellNodes.length;
26161
- const totalColumns = Math.max(columnCountFromCells, grid.length);
26241
+ const gridColumnCount = grid.length;
26242
+ let totalColumns = Math.max(columnCountFromCells, gridColumnCount);
26243
+ if (typeof requestedColumns === "number" && Number.isFinite(requestedColumns) && requestedColumns > 0) {
26244
+ totalColumns = requestedColumns;
26245
+ } else if (preferTableGrid && gridColumnCount > 0) {
26246
+ totalColumns = gridColumnCount;
26247
+ }
26248
+ if (colWidthsFromCellNodes.length > totalColumns) {
26249
+ colWidthsFromCellNodes = colWidthsFromCellNodes.slice(0, totalColumns);
26250
+ }
26162
26251
  const fallbackColumnWidthTwips = resolveFallbackColumnWidthTwips(params, totalColumns, cellMinWidth);
26163
26252
  const elements = [];
26164
26253
  const pushColumn = (widthTwips, { enforceMinimum = false } = {}) => {
@@ -26418,22 +26507,31 @@ const encode$p = (params, encodedAttrs) => {
26418
26507
  const decode$r = (params, decodedAttrs) => {
26419
26508
  params.node = preProcessVerticalMergeCells(params.node, params);
26420
26509
  const { node } = params;
26421
- const elements = translateChildNodes(params);
26510
+ const rawGrid = node.attrs?.grid;
26511
+ const grid = Array.isArray(rawGrid) ? rawGrid : [];
26512
+ const preferTableGrid = node.attrs?.userEdited !== true && grid.length > 0;
26513
+ const totalColumns = preferTableGrid ? grid.length : void 0;
26514
+ const extraParams = {
26515
+ ...params.extraParams || {},
26516
+ preferTableGrid,
26517
+ totalColumns
26518
+ };
26519
+ const elements = translateChildNodes({ ...params, extraParams });
26422
26520
  const firstRow = node.content?.find((n) => n.type === "tableRow");
26423
- const properties = node.attrs.grid;
26424
26521
  const element = translator$c.decode({
26425
26522
  ...params,
26426
- node: { ...node, attrs: { ...node.attrs, grid: properties } },
26523
+ node: { ...node, attrs: { ...node.attrs, grid } },
26427
26524
  extraParams: {
26525
+ ...extraParams,
26428
26526
  firstRow
26429
26527
  }
26430
26528
  });
26431
26529
  if (element) elements.unshift(element);
26432
26530
  if (node.attrs?.tableProperties) {
26433
- const properties2 = { ...node.attrs.tableProperties };
26531
+ const properties = { ...node.attrs.tableProperties };
26434
26532
  const element2 = translator$e.decode({
26435
26533
  ...params,
26436
- node: { ...node, attrs: { ...node.attrs, tableProperties: properties2 } }
26534
+ node: { ...node, attrs: { ...node.attrs, tableProperties: properties } }
26437
26535
  });
26438
26536
  if (element2) elements.unshift(element2);
26439
26537
  }
@@ -30034,7 +30132,7 @@ function buildStyles(styleObject) {
30034
30132
  }
30035
30133
  return style;
30036
30134
  }
30037
- function handleShapeImageImport({ params, pict }) {
30135
+ function handleShapeImageWatermarkImport({ params, pict }) {
30038
30136
  const shape = pict.elements?.find((el) => el.name === "v:shape");
30039
30137
  if (!shape) return null;
30040
30138
  const imagedata = shape.elements?.find((el) => el.name === "v:imagedata");
@@ -30060,7 +30158,7 @@ function handleShapeImageImport({ params, pict }) {
30060
30158
  const targetPath = rel.attributes["Target"];
30061
30159
  const normalizedPath = normalizeTargetPath(targetPath);
30062
30160
  const style = shapeAttrs.style || "";
30063
- const styleObj = parseVmlStyle(style);
30161
+ const styleObj = parseVmlStyle$1(style);
30064
30162
  const width = styleObj.width || "100px";
30065
30163
  const height = styleObj.height || "100px";
30066
30164
  const position = {
@@ -30106,12 +30204,12 @@ function handleShapeImageImport({ params, pict }) {
30106
30204
  },
30107
30205
  // Size
30108
30206
  size: {
30109
- width: convertToPixels(width),
30110
- height: convertToPixels(height)
30207
+ width: convertToPixels$1(width),
30208
+ height: convertToPixels$1(height)
30111
30209
  },
30112
30210
  marginOffset: {
30113
- horizontal: convertToPixels(position.marginLeft),
30114
- top: convertToPixels(position.marginTop)
30211
+ horizontal: convertToPixels$1(position.marginLeft),
30212
+ top: convertToPixels$1(position.marginTop)
30115
30213
  },
30116
30214
  // Image adjustments
30117
30215
  ...gain && { gain },
@@ -30127,7 +30225,7 @@ function normalizeTargetPath(targetPath = "") {
30127
30225
  if (trimmed.startsWith("media/")) return `word/${trimmed}`;
30128
30226
  return `word/${trimmed}`;
30129
30227
  }
30130
- function parseVmlStyle(style) {
30228
+ function parseVmlStyle$1(style) {
30131
30229
  const result = {};
30132
30230
  if (!style) return result;
30133
30231
  const declarations = style.split(";").filter((s) => s.trim());
@@ -30139,6 +30237,267 @@ function parseVmlStyle(style) {
30139
30237
  }
30140
30238
  return result;
30141
30239
  }
30240
+ function convertToPixels$1(value) {
30241
+ if (typeof value === "number") return value;
30242
+ if (!value || typeof value !== "string") return 0;
30243
+ const match = value.match(/^([\d.]+)([a-z%]+)?$/i);
30244
+ if (!match) return 0;
30245
+ const num = parseFloat(match[1]);
30246
+ const unit = match[2] || "px";
30247
+ switch (unit.toLowerCase()) {
30248
+ case "px":
30249
+ return num;
30250
+ case "pt":
30251
+ return num * (96 / 72);
30252
+ // 1pt = 1/72 inch, 96 DPI
30253
+ case "in":
30254
+ return num * 96;
30255
+ case "cm":
30256
+ return num * (96 / 2.54);
30257
+ case "mm":
30258
+ return num * (96 / 25.4);
30259
+ case "pc":
30260
+ return num * 16;
30261
+ // 1pc = 12pt
30262
+ default:
30263
+ return num;
30264
+ }
30265
+ }
30266
+ function handleShapeTextWatermarkImport({ pict }) {
30267
+ const shape = pict.elements?.find((el) => el.name === "v:shape");
30268
+ if (!shape) return null;
30269
+ const textpath = shape.elements?.find((el) => el.name === "v:textpath");
30270
+ if (!textpath) return null;
30271
+ const shapeAttrs = shape.attributes || {};
30272
+ const textpathAttrs = textpath.attributes || {};
30273
+ const watermarkText = textpathAttrs["string"] || "";
30274
+ if (!watermarkText) {
30275
+ console.warn("v:textpath missing string attribute");
30276
+ return null;
30277
+ }
30278
+ const style = shapeAttrs.style || "";
30279
+ const styleObj = parseVmlStyle(style);
30280
+ const width = styleObj.width || "481.8pt";
30281
+ const height = styleObj.height || "82.8pt";
30282
+ const position = {
30283
+ type: styleObj.position || "absolute",
30284
+ marginLeft: styleObj["margin-left"] || "0",
30285
+ marginTop: styleObj["margin-top"] || "0"
30286
+ };
30287
+ const rotation = parseFloat(styleObj.rotation) || 0;
30288
+ const hPosition = styleObj["mso-position-horizontal"] || "center";
30289
+ const vPosition = styleObj["mso-position-vertical"] || "center";
30290
+ const hRelativeTo = styleObj["mso-position-horizontal-relative"] || "margin";
30291
+ const vRelativeTo = styleObj["mso-position-vertical-relative"] || "margin";
30292
+ const textAnchor = styleObj["v-text-anchor"] || "middle";
30293
+ const fill = shape.elements?.find((el) => el.name === "v:fill");
30294
+ const fillAttrs = fill?.attributes || {};
30295
+ const rawFillColor = shapeAttrs.fillcolor || fillAttrs.color || "silver";
30296
+ const rawFillColor2 = fillAttrs.color2 || "#3f3f3f";
30297
+ const fillColor = sanitizeColor(rawFillColor, "silver");
30298
+ const fillColor2 = sanitizeColor(rawFillColor2, "#3f3f3f");
30299
+ const opacity = fillAttrs.opacity || "0.5";
30300
+ const fillType = fillAttrs.type || "solid";
30301
+ const stroke = shape.elements?.find((el) => el.name === "v:stroke");
30302
+ const strokeAttrs = stroke?.attributes || {};
30303
+ const stroked = shapeAttrs.stroked || "f";
30304
+ const strokeColor = strokeAttrs.color || "#3465a4";
30305
+ const strokeJoinstyle = strokeAttrs.joinstyle || "round";
30306
+ const strokeEndcap = strokeAttrs.endcap || "flat";
30307
+ const textpathStyle = textpathAttrs.style || "";
30308
+ const textStyleObj = parseVmlStyle(textpathStyle);
30309
+ const rawFontFamily = textStyleObj["font-family"]?.replace(/['"]/g, "");
30310
+ const fontFamily = sanitizeFontFamily(rawFontFamily);
30311
+ const fontSize = textStyleObj["font-size"] || "1pt";
30312
+ const fitshape = textpathAttrs.fitshape || "t";
30313
+ const trim = textpathAttrs.trim || "t";
30314
+ const textpathOn = textpathAttrs.on || "t";
30315
+ const path = shape.elements?.find((el) => el.name === "v:path");
30316
+ const pathAttrs = path?.attributes || {};
30317
+ const textpathok = pathAttrs.textpathok || "t";
30318
+ const wrap2 = shape.elements?.find((el) => el.name === "w10:wrap");
30319
+ const wrapAttrs = wrap2?.attributes || {};
30320
+ const wrapType = wrapAttrs.type || "none";
30321
+ const widthPx = convertToPixels(width);
30322
+ const heightPx = convertToPixels(height);
30323
+ const sanitizedOpacity = sanitizeNumeric(parseFloat(opacity), 0.5, 0, 1);
30324
+ const sanitizedRotation = sanitizeNumeric(rotation, 0, -360, 360);
30325
+ const svgResult = generateTextWatermarkSVG({
30326
+ text: watermarkText,
30327
+ width: widthPx,
30328
+ height: heightPx,
30329
+ rotation: sanitizedRotation,
30330
+ fill: {
30331
+ color: fillColor,
30332
+ opacity: sanitizedOpacity
30333
+ },
30334
+ textStyle: {
30335
+ fontFamily,
30336
+ fontSize
30337
+ }
30338
+ });
30339
+ const svgDataUri = svgResult.dataUri;
30340
+ const imageWatermarkNode = {
30341
+ type: "image",
30342
+ attrs: {
30343
+ src: svgDataUri,
30344
+ alt: watermarkText,
30345
+ title: watermarkText,
30346
+ extension: "svg",
30347
+ // Mark this as a text watermark for export
30348
+ vmlWatermark: true,
30349
+ vmlTextWatermark: true,
30350
+ // Store VML-specific attributes for round-trip
30351
+ vmlStyle: style,
30352
+ vmlAttributes: shapeAttrs,
30353
+ vmlTextpathAttributes: textpathAttrs,
30354
+ vmlPathAttributes: pathAttrs,
30355
+ vmlFillAttributes: fillAttrs,
30356
+ vmlStrokeAttributes: strokeAttrs,
30357
+ vmlWrapAttributes: wrapAttrs,
30358
+ // Positioning (same as image watermarks)
30359
+ isAnchor: true,
30360
+ inline: false,
30361
+ wrap: {
30362
+ type: wrapType === "none" ? "None" : wrapType,
30363
+ attrs: {
30364
+ behindDoc: true
30365
+ }
30366
+ },
30367
+ anchorData: {
30368
+ hRelativeFrom: hRelativeTo,
30369
+ vRelativeFrom: vRelativeTo,
30370
+ alignH: hPosition,
30371
+ alignV: vPosition
30372
+ },
30373
+ // Size - use rotated bounding box dimensions to prevent clipping
30374
+ size: {
30375
+ width: svgResult.svgWidth,
30376
+ height: svgResult.svgHeight
30377
+ },
30378
+ marginOffset: {
30379
+ horizontal: convertToPixels(position.marginLeft),
30380
+ // For center-aligned watermarks relative to margin, Word's margin-top value
30381
+ // is not suitable for browser rendering. Set to 0 to let center alignment work.
30382
+ top: vPosition === "center" && vRelativeTo === "margin" ? 0 : convertToPixels(position.marginTop)
30383
+ },
30384
+ // Store text watermark specific data for export
30385
+ textWatermarkData: {
30386
+ text: watermarkText,
30387
+ rotation: sanitizedRotation,
30388
+ textStyle: {
30389
+ fontFamily,
30390
+ fontSize,
30391
+ textAnchor
30392
+ },
30393
+ fill: {
30394
+ color: fillColor,
30395
+ color2: fillColor2,
30396
+ opacity: sanitizedOpacity,
30397
+ type: fillType
30398
+ },
30399
+ stroke: {
30400
+ enabled: stroked !== "f",
30401
+ color: strokeColor,
30402
+ joinstyle: strokeJoinstyle,
30403
+ endcap: strokeEndcap
30404
+ },
30405
+ textpath: {
30406
+ on: textpathOn === "t",
30407
+ fitshape: fitshape === "t",
30408
+ trim: trim === "t",
30409
+ textpathok: textpathok === "t"
30410
+ }
30411
+ }
30412
+ }
30413
+ };
30414
+ return imageWatermarkNode;
30415
+ }
30416
+ function sanitizeFontFamily(fontFamily) {
30417
+ if (!fontFamily || typeof fontFamily !== "string") {
30418
+ return "Arial";
30419
+ }
30420
+ const sanitized = fontFamily.replace(/[^a-zA-Z0-9\s,\-]/g, "").trim();
30421
+ return sanitized || "Arial";
30422
+ }
30423
+ function sanitizeColor(color, defaultColor = "silver") {
30424
+ if (!color || typeof color !== "string") {
30425
+ return defaultColor;
30426
+ }
30427
+ const sanitized = color.replace(/[^a-zA-Z0-9#%(),.]/g, "").trim();
30428
+ return sanitized || defaultColor;
30429
+ }
30430
+ function sanitizeNumeric(value, defaultValue, min = -Infinity, max = Infinity) {
30431
+ const num = typeof value === "number" ? value : parseFloat(value);
30432
+ if (isNaN(num) || !isFinite(num)) {
30433
+ return defaultValue;
30434
+ }
30435
+ return Math.max(min, Math.min(max, num));
30436
+ }
30437
+ function generateTextWatermarkSVG({ text, width, height, rotation, fill, textStyle }) {
30438
+ let fontSize = height * 0.9;
30439
+ if (textStyle?.fontSize && textStyle.fontSize.trim() !== "1pt") {
30440
+ const match = textStyle.fontSize.match(/^([\d.]+)(pt|px)?$/);
30441
+ if (match) {
30442
+ const value = parseFloat(match[1]);
30443
+ const unit = match[2] || "pt";
30444
+ fontSize = (unit === "pt" ? value * (96 / 72) : value) * 50;
30445
+ }
30446
+ }
30447
+ fontSize = Math.max(fontSize, 48);
30448
+ const color = sanitizeColor(fill?.color, "silver");
30449
+ const opacity = sanitizeNumeric(fill?.opacity, 0.5, 0, 1);
30450
+ const fontFamily = sanitizeFontFamily(textStyle?.fontFamily);
30451
+ const sanitizedRotation = sanitizeNumeric(rotation, 0, -360, 360);
30452
+ const sanitizedWidth = sanitizeNumeric(width, 100, 1, 1e4);
30453
+ const sanitizedHeight = sanitizeNumeric(height, 100, 1, 1e4);
30454
+ const sanitizedFontSize = sanitizeNumeric(fontSize, 48, 1, 1e3);
30455
+ const radians = sanitizedRotation * Math.PI / 180;
30456
+ const cos = Math.abs(Math.cos(radians));
30457
+ const sin = Math.abs(Math.sin(radians));
30458
+ const rotatedWidth = sanitizedWidth * cos + sanitizedHeight * sin;
30459
+ const rotatedHeight = sanitizedWidth * sin + sanitizedHeight * cos;
30460
+ const svgWidth = Math.max(sanitizedWidth, rotatedWidth);
30461
+ const svgHeight = Math.max(sanitizedHeight, rotatedHeight);
30462
+ const centerX = svgWidth / 2;
30463
+ const centerY = svgHeight / 2;
30464
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${svgWidth}" height="${svgHeight}" viewBox="0 0 ${svgWidth} ${svgHeight}">
30465
+ <text
30466
+ x="${centerX}"
30467
+ y="${centerY}"
30468
+ text-anchor="middle"
30469
+ dominant-baseline="middle"
30470
+ font-family="${fontFamily}"
30471
+ font-size="${sanitizedFontSize}px"
30472
+ font-weight="bold"
30473
+ fill="${color}"
30474
+ opacity="${opacity}"
30475
+ transform="rotate(${sanitizedRotation} ${centerX} ${centerY})">${escapeXml(text)}</text>
30476
+ </svg>`;
30477
+ return {
30478
+ dataUri: `data:image/svg+xml,${encodeURIComponent(svg)}`,
30479
+ svgWidth,
30480
+ svgHeight
30481
+ };
30482
+ }
30483
+ function escapeXml(text) {
30484
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
30485
+ }
30486
+ function parseVmlStyle(style) {
30487
+ const result = {};
30488
+ if (!style) return result;
30489
+ const declarations = style.split(";").filter((s) => s.trim());
30490
+ for (const decl of declarations) {
30491
+ const colonIndex = decl.indexOf(":");
30492
+ if (colonIndex === -1) continue;
30493
+ const prop = decl.substring(0, colonIndex).trim();
30494
+ const value = decl.substring(colonIndex + 1).trim();
30495
+ if (prop && value) {
30496
+ result[prop] = value;
30497
+ }
30498
+ }
30499
+ return result;
30500
+ }
30142
30501
  function convertToPixels(value) {
30143
30502
  if (typeof value === "number") return value;
30144
30503
  if (!value || typeof value !== "string") return 0;
@@ -30183,9 +30542,13 @@ function pictNodeTypeStrategy(node) {
30183
30542
  if (textbox) {
30184
30543
  return { type: "shapeContainer", handler: handleShapeTextboxImport };
30185
30544
  }
30545
+ const textpath = shape.elements?.find((el) => el.name === "v:textpath");
30546
+ if (textpath) {
30547
+ return { type: "image", handler: handleShapeTextWatermarkImport };
30548
+ }
30186
30549
  const imagedata = shape.elements?.find((el) => el.name === "v:imagedata");
30187
30550
  if (imagedata) {
30188
- return { type: "image", handler: handleShapeImageImport };
30551
+ return { type: "image", handler: handleShapeImageWatermarkImport };
30189
30552
  }
30190
30553
  }
30191
30554
  return { type: "unknown", handler: null };
@@ -30285,7 +30648,7 @@ function translateVRectContentBlock(params) {
30285
30648
  };
30286
30649
  return wrapTextInRun(pict);
30287
30650
  }
30288
- function translateVmlWatermark(params) {
30651
+ function translateImageWatermark(params) {
30289
30652
  const { node } = params;
30290
30653
  const { attrs } = node;
30291
30654
  if (attrs.vmlAttributes && attrs.vmlImagedata) {
@@ -30315,7 +30678,7 @@ function translateVmlWatermark(params) {
30315
30678
  };
30316
30679
  return par2;
30317
30680
  }
30318
- const style = buildVmlStyle(attrs);
30681
+ const style = buildVmlStyle$1(attrs);
30319
30682
  const shape = {
30320
30683
  name: "v:shape",
30321
30684
  attributes: {
@@ -30350,23 +30713,23 @@ function translateVmlWatermark(params) {
30350
30713
  };
30351
30714
  return par;
30352
30715
  }
30353
- function buildVmlStyle(attrs) {
30716
+ function buildVmlStyle$1(attrs) {
30354
30717
  const styles = [];
30355
30718
  styles.push("position:absolute");
30356
30719
  if (attrs.size) {
30357
30720
  if (attrs.size.width) {
30358
- styles.push(`width:${convertToPt(attrs.size.width)}pt`);
30721
+ styles.push(`width:${convertToPt$1(attrs.size.width)}pt`);
30359
30722
  }
30360
30723
  if (attrs.size.height) {
30361
- styles.push(`height:${convertToPt(attrs.size.height)}pt`);
30724
+ styles.push(`height:${convertToPt$1(attrs.size.height)}pt`);
30362
30725
  }
30363
30726
  }
30364
30727
  if (attrs.marginOffset) {
30365
30728
  if (attrs.marginOffset.horizontal !== void 0) {
30366
- styles.push(`margin-left:${convertToPt(attrs.marginOffset.horizontal)}pt`);
30729
+ styles.push(`margin-left:${convertToPt$1(attrs.marginOffset.horizontal)}pt`);
30367
30730
  }
30368
30731
  if (attrs.marginOffset.top !== void 0) {
30369
- styles.push(`margin-top:${convertToPt(attrs.marginOffset.top)}pt`);
30732
+ styles.push(`margin-top:${convertToPt$1(attrs.marginOffset.top)}pt`);
30370
30733
  }
30371
30734
  }
30372
30735
  if (attrs.wrap?.attrs?.behindDoc) {
@@ -30390,9 +30753,215 @@ function buildVmlStyle(attrs) {
30390
30753
  styles.push("mso-height-percent:0");
30391
30754
  return styles.join(";");
30392
30755
  }
30393
- function convertToPt(pixels) {
30756
+ function convertToPt$1(pixels) {
30394
30757
  return pixels * 72 / 96;
30395
30758
  }
30759
+ function translateTextWatermark(params) {
30760
+ const { node } = params;
30761
+ const { attrs } = node;
30762
+ const text = attrs.textWatermarkData?.text || attrs.vmlTextpathAttributes?.string || "";
30763
+ if (attrs.vmlAttributes && attrs.vmlTextpathAttributes) {
30764
+ const shapeElements2 = [];
30765
+ if (attrs.vmlPathAttributes) {
30766
+ shapeElements2.push({
30767
+ name: "v:path",
30768
+ attributes: attrs.vmlPathAttributes
30769
+ });
30770
+ }
30771
+ shapeElements2.push({
30772
+ name: "v:textpath",
30773
+ attributes: {
30774
+ ...attrs.vmlTextpathAttributes,
30775
+ string: text
30776
+ }
30777
+ });
30778
+ if (attrs.vmlFillAttributes && Object.keys(attrs.vmlFillAttributes).length > 0) {
30779
+ shapeElements2.push({
30780
+ name: "v:fill",
30781
+ attributes: attrs.vmlFillAttributes
30782
+ });
30783
+ }
30784
+ if (attrs.vmlStrokeAttributes && Object.keys(attrs.vmlStrokeAttributes).length > 0) {
30785
+ shapeElements2.push({
30786
+ name: "v:stroke",
30787
+ attributes: attrs.vmlStrokeAttributes
30788
+ });
30789
+ }
30790
+ if (attrs.vmlWrapAttributes) {
30791
+ shapeElements2.push({
30792
+ name: "w10:wrap",
30793
+ attributes: attrs.vmlWrapAttributes
30794
+ });
30795
+ }
30796
+ const shape2 = {
30797
+ name: "v:shape",
30798
+ attributes: attrs.vmlAttributes,
30799
+ elements: shapeElements2
30800
+ };
30801
+ const pict2 = {
30802
+ name: "w:pict",
30803
+ elements: [shape2]
30804
+ };
30805
+ const par2 = {
30806
+ name: "w:p",
30807
+ elements: [wrapTextInRun(pict2)]
30808
+ };
30809
+ return par2;
30810
+ }
30811
+ const wmData = attrs.textWatermarkData || {};
30812
+ const style = buildVmlStyle(attrs, wmData);
30813
+ const textpathStyle = buildTextpathStyle(wmData);
30814
+ const shapeElements = [];
30815
+ shapeElements.push({
30816
+ name: "v:path",
30817
+ attributes: {
30818
+ textpathok: "t"
30819
+ }
30820
+ });
30821
+ shapeElements.push({
30822
+ name: "v:textpath",
30823
+ attributes: {
30824
+ on: "t",
30825
+ fitshape: "t",
30826
+ string: text,
30827
+ style: textpathStyle,
30828
+ ...wmData.textpath?.trim !== void 0 && { trim: wmData.textpath.trim ? "t" : "f" }
30829
+ }
30830
+ });
30831
+ const fillAttrs = {};
30832
+ const fill = wmData.fill || attrs.fill;
30833
+ if (fill) {
30834
+ if (fill.type) fillAttrs.type = fill.type;
30835
+ if (fill.color2) fillAttrs.color2 = fill.color2;
30836
+ if (fill.opacity !== void 0) fillAttrs.opacity = fill.opacity.toString();
30837
+ if (fill.detectmouseclick !== void 0) {
30838
+ fillAttrs["o:detectmouseclick"] = fill.detectmouseclick ? "t" : "f";
30839
+ }
30840
+ }
30841
+ if (Object.keys(fillAttrs).length > 0) {
30842
+ shapeElements.push({
30843
+ name: "v:fill",
30844
+ attributes: fillAttrs
30845
+ });
30846
+ }
30847
+ const stroke = wmData.stroke || attrs.stroke;
30848
+ if (stroke && stroke.enabled !== false) {
30849
+ const strokeAttrs = {};
30850
+ if (stroke.color) strokeAttrs.color = stroke.color;
30851
+ if (stroke.joinstyle) strokeAttrs.joinstyle = stroke.joinstyle;
30852
+ if (stroke.endcap) strokeAttrs.endcap = stroke.endcap;
30853
+ if (Object.keys(strokeAttrs).length > 0) {
30854
+ shapeElements.push({
30855
+ name: "v:stroke",
30856
+ attributes: strokeAttrs
30857
+ });
30858
+ }
30859
+ }
30860
+ shapeElements.push({
30861
+ name: "w10:wrap",
30862
+ attributes: {
30863
+ type: attrs.wrap?.type?.toLowerCase() || "none"
30864
+ }
30865
+ });
30866
+ const shape = {
30867
+ name: "v:shape",
30868
+ attributes: {
30869
+ id: `PowerPlusWaterMarkObject${generateRandomSigned32BitIntStrId().replace("-", "")}`,
30870
+ "o:spid": `shape_${Math.floor(Math.random() * 1e4)}`,
30871
+ type: "#_x0000_t136",
30872
+ style,
30873
+ fillcolor: fill?.color || "silver",
30874
+ stroked: stroke?.enabled !== false ? "t" : "f",
30875
+ "o:allowincell": "f",
30876
+ ...attrs.vmlAttributes?.adj && { adj: attrs.vmlAttributes.adj }
30877
+ },
30878
+ elements: shapeElements
30879
+ };
30880
+ const pict = {
30881
+ name: "w:pict",
30882
+ elements: [shape]
30883
+ };
30884
+ const par = {
30885
+ name: "w:p",
30886
+ elements: [wrapTextInRun(pict)]
30887
+ };
30888
+ return par;
30889
+ }
30890
+ function buildVmlStyle(attrs, wmData) {
30891
+ const styles = [];
30892
+ styles.push("position:absolute");
30893
+ if (attrs.marginOffset) {
30894
+ if (attrs.marginOffset.horizontal !== void 0) {
30895
+ styles.push(`margin-left:${convertToPt(attrs.marginOffset.horizontal)}pt`);
30896
+ }
30897
+ if (attrs.marginOffset.top !== void 0) {
30898
+ styles.push(`margin-top:${convertToPt(attrs.marginOffset.top)}pt`);
30899
+ }
30900
+ } else {
30901
+ styles.push("margin-left:0.05pt");
30902
+ styles.push("margin-top:315.7pt");
30903
+ }
30904
+ if (attrs.size) {
30905
+ if (attrs.size.width) {
30906
+ styles.push(`width:${convertToPt(attrs.size.width)}pt`);
30907
+ }
30908
+ if (attrs.size.height) {
30909
+ styles.push(`height:${convertToPt(attrs.size.height)}pt`);
30910
+ }
30911
+ }
30912
+ const wrapType = attrs.wrap?.type;
30913
+ let msoWrapStyle = "none";
30914
+ if (wrapType) {
30915
+ const wrapTypeLower = wrapType.toLowerCase();
30916
+ if (wrapTypeLower === "topandbottom") {
30917
+ msoWrapStyle = "top-and-bottom";
30918
+ } else if (["square", "tight", "through"].includes(wrapTypeLower)) {
30919
+ msoWrapStyle = wrapTypeLower;
30920
+ }
30921
+ }
30922
+ styles.push(`mso-wrap-style:${msoWrapStyle}`);
30923
+ const textAnchor = wmData.textStyle?.textAnchor || attrs.textStyle?.textAnchor;
30924
+ if (textAnchor) {
30925
+ styles.push(`v-text-anchor:${textAnchor}`);
30926
+ }
30927
+ const rotation = wmData.rotation || attrs.rotation;
30928
+ if (rotation !== void 0 && rotation !== 0) {
30929
+ styles.push(`rotation:${rotation}`);
30930
+ }
30931
+ if (attrs.anchorData) {
30932
+ if (attrs.anchorData.alignH) {
30933
+ styles.push(`mso-position-horizontal:${attrs.anchorData.alignH}`);
30934
+ }
30935
+ if (attrs.anchorData.alignV) {
30936
+ styles.push(`mso-position-vertical:${attrs.anchorData.alignV}`);
30937
+ }
30938
+ if (attrs.anchorData.hRelativeFrom) {
30939
+ styles.push(`mso-position-horizontal-relative:${attrs.anchorData.hRelativeFrom}`);
30940
+ }
30941
+ if (attrs.anchorData.vRelativeFrom) {
30942
+ styles.push(`mso-position-vertical-relative:${attrs.anchorData.vRelativeFrom}`);
30943
+ }
30944
+ }
30945
+ return styles.join(";");
30946
+ }
30947
+ function buildTextpathStyle(wmData) {
30948
+ const styles = [];
30949
+ if (wmData.textStyle) {
30950
+ if (wmData.textStyle.fontFamily) {
30951
+ styles.push(`font-family:"${wmData.textStyle.fontFamily}"`);
30952
+ }
30953
+ if (wmData.textStyle.fontSize) {
30954
+ styles.push(`font-size:${wmData.textStyle.fontSize}`);
30955
+ }
30956
+ }
30957
+ return styles.join(";");
30958
+ }
30959
+ function convertToPt(pixels) {
30960
+ if (typeof pixels === "number") {
30961
+ return pixels * 72 / 96;
30962
+ }
30963
+ return parseFloat(pixels) || 0;
30964
+ }
30396
30965
  const XML_NODE_NAME = "w:pict";
30397
30966
  const SD_NODE_NAME = ["shapeContainer", "contentBlock", "image"];
30398
30967
  const validXmlAttributes = [];
@@ -30420,7 +30989,10 @@ function decode(params) {
30420
30989
  contentBlock: () => translateContentBlock(params),
30421
30990
  image: () => {
30422
30991
  if (node.attrs?.vmlWatermark) {
30423
- return translateVmlWatermark(params);
30992
+ if (node.attrs?.vmlTextWatermark) {
30993
+ return translateTextWatermark(params);
30994
+ }
30995
+ return translateImageWatermark(params);
30424
30996
  }
30425
30997
  return null;
30426
30998
  },
@@ -31565,7 +32137,7 @@ class SuperConverter {
31565
32137
  static getStoredSuperdocVersion(docx) {
31566
32138
  return SuperConverter.getStoredCustomProperty(docx, "SuperdocVersion");
31567
32139
  }
31568
- static setStoredSuperdocVersion(docx = this.convertedXml, version = "1.5.0-next.5") {
32140
+ static setStoredSuperdocVersion(docx = this.convertedXml, version = "1.5.0-next.7") {
31569
32141
  return SuperConverter.setStoredCustomProperty(docx, "SuperdocVersion", version, false);
31570
32142
  }
31571
32143
  /**
@@ -32274,8 +32846,8 @@ exports.registeredHandlers = registeredHandlers;
32274
32846
  exports.replaceStep = replaceStep;
32275
32847
  exports.resolveDocxFontFamily = resolveDocxFontFamily;
32276
32848
  exports.resolveRunProperties = resolveRunProperties;
32277
- exports.translator = translator$14;
32278
- exports.translator$1 = translator$1O;
32849
+ exports.translator = translator$1O;
32850
+ exports.translator$1 = translator$14;
32279
32851
  exports.unflattenListsInHtml = unflattenListsInHtml;
32280
32852
  exports.updateNumberingProperties = updateNumberingProperties;
32281
32853
  exports.wrapTextsInRuns = wrapTextsInRuns;