reamkit 1.17.0 → 1.19.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 (34) hide show
  1. package/dist/esm/core/document-model/types.d.ts +59 -1
  2. package/dist/esm/core/drawingml/colors.js +13 -6
  3. package/dist/esm/core/images.d.ts +2 -2
  4. package/dist/esm/core/images.js +38 -0
  5. package/dist/esm/core/lzw.d.ts +17 -0
  6. package/dist/esm/core/lzw.js +83 -0
  7. package/dist/esm/core/metafile/emf.d.ts +16 -0
  8. package/dist/esm/core/metafile/emf.js +635 -0
  9. package/dist/esm/core/metafile/picture.d.ts +106 -0
  10. package/dist/esm/core/metafile/picture.js +55 -0
  11. package/dist/esm/core/metafile/wmf.d.ts +9 -0
  12. package/dist/esm/core/metafile/wmf.js +371 -0
  13. package/dist/esm/core/po-helpers.d.ts +9 -0
  14. package/dist/esm/core/po-helpers.js +9 -1
  15. package/dist/esm/core/tiff.d.ts +20 -0
  16. package/dist/esm/core/tiff.js +339 -0
  17. package/dist/esm/core/vector.d.ts +5 -0
  18. package/dist/esm/excel/print-model.js +3 -1
  19. package/dist/esm/html/html-writer.js +3 -2
  20. package/dist/esm/layout/page-doc.d.ts +46 -3
  21. package/dist/esm/layout/styled-layout.js +649 -108
  22. package/dist/esm/pdf/styled-page-emitter.js +78 -3
  23. package/dist/esm/pdf/vector-graphics.d.ts +1 -1
  24. package/dist/esm/pdf/vector-graphics.js +2 -1
  25. package/dist/esm/pdf-reader/image-decode.js +7 -75
  26. package/dist/esm/svg/svg-writer.js +1 -1
  27. package/dist/esm/word/document-parser.js +3 -0
  28. package/dist/esm/word/drawing-parser.d.ts +5 -0
  29. package/dist/esm/word/drawing-parser.js +100 -14
  30. package/dist/esm/word/paragraph-properties.js +2 -2
  31. package/dist/esm/word/run-properties.js +2 -2
  32. package/dist/esm/word/xml-helpers.d.ts +12 -0
  33. package/dist/esm/word/xml-helpers.js +23 -3
  34. package/package.json +1 -1
@@ -15,6 +15,33 @@ import { addSignaturePlaceholder } from "./signature.js";
15
15
  import { buildXmpPacket } from "./xmp.js";
16
16
  //#region src/pdf/styled-page-emitter.ts
17
17
  var encoder = new TextEncoder();
18
+ function washVeil(wash) {
19
+ if (!wash) return void 0;
20
+ const alpha = 1 - wash.gain;
21
+ if (!(alpha > .004)) return void 0;
22
+ const offset = .5 * (1 - wash.gain) + wash.black;
23
+ return {
24
+ alpha: Math.min(1, alpha),
25
+ grey: Math.max(0, Math.min(1, offset / alpha))
26
+ };
27
+ }
28
+ /**
29
+ * The veil operators for a washed picture, to follow its own `/Im Do`: the
30
+ * placement matrix maps the unit square onto the picture as drawn, so the wash
31
+ * covers exactly it — cropped, rotated and flipped along with it.
32
+ */
33
+ function washItems(wash, alphaStateNames) {
34
+ const veil = washVeil(wash);
35
+ if (!veil) return [];
36
+ const g = formatNumber(veil.grey);
37
+ const state = veil.alpha < 1 ? alphaStateNames?.get(Math.round(veil.alpha * 1e3) / 1e3) : void 0;
38
+ return [
39
+ ...state === void 0 ? [] : [`/${state} gs`],
40
+ `${g} ${g} ${g} rg`,
41
+ "0 0 1 1 re",
42
+ "f"
43
+ ];
44
+ }
18
45
  /**
19
46
  * The emit phase: turn a laid-out document into PDF objects (ISO 32000) and
20
47
  * serialize it. The counterpart of `layoutStyledDocument` (the seam of
@@ -113,8 +140,13 @@ function assembleStyledPdf(laid, options, doc) {
113
140
  for (const item of plan.shapes) {
114
141
  const s = item.shape.shadow;
115
142
  if (s) wantAlpha(shadowBlurLayers(s).alpha);
143
+ wantAlpha(item.shape.fillAlpha);
144
+ }
145
+ for (const img of plan.images) wantAlpha(washVeil(img.wash)?.alpha);
146
+ for (const line of plan.lines) for (const tok of line.line.tokens) {
147
+ if (tok.kind === "image" && tok.shadow) wantAlpha(shadowBlurLayers(tok.shadow).alpha);
148
+ if (tok.kind === "image") wantAlpha(washVeil(tok.wash)?.alpha);
116
149
  }
117
- for (const line of plan.lines) for (const tok of line.line.tokens) if (tok.kind === "image" && tok.shadow) wantAlpha(shadowBlurLayers(tok.shadow).alpha);
118
150
  }
119
151
  const resourcesDict = dict({
120
152
  Font: fontResourceDict,
@@ -391,7 +423,10 @@ function defaultPageCtx() {
391
423
  }
392
424
  function embedImageResources(doc, laid) {
393
425
  const out = /* @__PURE__ */ new Map();
394
- for (const [resourceId, res] of laid.imageResources) out.set(resourceId, addImage(doc, res.prepared).ref);
426
+ for (const [resourceId, res] of laid.imageResources) {
427
+ if (!res.prepared) continue;
428
+ out.set(resourceId, addImage(doc, res.prepared).ref);
429
+ }
395
430
  return out;
396
431
  }
397
432
  function embedFontResources(doc, laid, pdfaProfile) {
@@ -528,6 +563,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
528
563
  }
529
564
  out.push(...placeImage(img.x, H - img.y - img.height, img.width, img.height, img.crop, img.rotationDeg, img.flipH, img.flipV));
530
565
  out.push(`/${img.imageResourceName} Do`);
566
+ out.push(...washItems(img.wash, alphaStateNames));
531
567
  out.push("Q");
532
568
  });
533
569
  const borders = plan.borders;
@@ -615,7 +651,9 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
615
651
  };
616
652
  const shadowLayer = sh.shape.shadow ? shadowBlurLayers(sh.shape.shadow) : void 0;
617
653
  const alphaState = shadowLayer && shadowLayer.alpha < 1 ? alphaStateNames?.get(Math.round(shadowLayer.alpha * 1e3) / 1e3) : void 0;
618
- for (const op of emitVectorShape(shape, gradientNames?.get(sh.shape), alphaState)) out.push(op);
654
+ const fillAlpha = sh.shape.fillAlpha;
655
+ const fillAlphaState = fillAlpha !== void 0 && fillAlpha < 1 ? alphaStateNames?.get(Math.round(fillAlpha * 1e3) / 1e3) : void 0;
656
+ for (const op of emitVectorShape(shape, gradientNames?.get(sh.shape), alphaState, fillAlphaState)) out.push(op);
619
657
  });
620
658
  const lines = plan.lines;
621
659
  if (lines.length > 0) {
@@ -643,6 +681,42 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
643
681
  }
644
682
  };
645
683
  const emitImageToken = (tok, x, baselineY) => {
684
+ const meta = tok.metafile;
685
+ if (meta) {
686
+ if (inBT) {
687
+ out.push("ET");
688
+ inBT = false;
689
+ }
690
+ const b = tok.drawBox;
691
+ const ox = x + (b?.dxPt ?? 0);
692
+ const oy = baselineY + (b?.dyPt ?? 0);
693
+ for (const sh of meta.shapes) for (const op of emitVectorShape({
694
+ ...sh,
695
+ transform: [
696
+ 1,
697
+ 0,
698
+ 0,
699
+ 1,
700
+ ox,
701
+ oy
702
+ ]
703
+ })) out.push(op);
704
+ for (const t of meta.texts) {
705
+ const first = t.line.tokens.find((k) => k.kind === "text");
706
+ if (!first) continue;
707
+ const [r, g, bl] = hexToRgb01(first.resolvedRun.colorHex);
708
+ out.push("BT");
709
+ out.push(`/${first.font.resourceName} ${formatNumber(first.fontSizePt)} Tf`);
710
+ out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(bl)} rg`);
711
+ out.push(`1 0 0 1 ${formatNumber(ox + t.x)} ${formatNumber(oy + t.y)} Tm`);
712
+ out.push(first.font.measure.showText(first.text));
713
+ out.push("ET");
714
+ }
715
+ lastFont = "";
716
+ lastSize = -1;
717
+ lastColor = "";
718
+ return;
719
+ }
646
720
  if (!tok.imageResourceName) return;
647
721
  if (inBT) {
648
722
  out.push("ET");
@@ -672,6 +746,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
672
746
  const b = tok.drawBox;
673
747
  out.push(...b ? placeImage(x + b.dxPt, baselineY + b.dyPt, b.widthPt, b.heightPt, tok.crop, tok.rotationDeg, tok.flipH, tok.flipV) : placeImage(x, baselineY, tok.widthPt, tok.heightPt, tok.crop, tok.rotationDeg, tok.flipH, tok.flipV));
674
748
  out.push(`/${tok.imageResourceName} Do`);
749
+ out.push(...washItems(tok.wash, alphaStateNames));
675
750
  out.push("Q");
676
751
  const frame = tok.outline;
677
752
  if (frame) {
@@ -31,7 +31,7 @@ export declare function shadowBlurLayers(shadow: {
31
31
  count: number;
32
32
  alpha: number;
33
33
  };
34
- export declare function emitVectorShape(shape: VectorShape, patternName?: string, alphaStateName?: string): Array<string>;
34
+ export declare function emitVectorShape(shape: VectorShape, patternName?: string, alphaStateName?: string, fillAlphaStateName?: string): Array<string>;
35
35
  /**
36
36
  * The operators that make `paths` the current CLIP, under `transform` — the
37
37
  * same local→y-up-page matrix {@link emitVectorShape} takes. Emitted inside the
@@ -53,7 +53,7 @@ function shadowBbox(shape) {
53
53
  maxY
54
54
  } : void 0;
55
55
  }
56
- function emitVectorShape(shape, patternName, alphaStateName) {
56
+ function emitVectorShape(shape, patternName, alphaStateName, fillAlphaStateName) {
57
57
  const out = [];
58
58
  const [a, b, c, d, e, f] = shape.transform;
59
59
  const shadow = shape.shadow;
@@ -88,6 +88,7 @@ function emitVectorShape(shape, patternName, alphaStateName) {
88
88
  }
89
89
  }
90
90
  out.push("q");
91
+ if (fillAlphaStateName !== void 0) out.push(`/${fillAlphaStateName} gs`);
91
92
  out.push(`${num(a)} ${num(b)} ${num(c)} ${num(d)} ${num(e)} ${num(f)} cm`);
92
93
  const stroke = shape.stroke;
93
94
  if (stroke) {
@@ -1,4 +1,5 @@
1
1
  import { PDF_NULL, PdfHexString, PdfName, PdfStream } from "../pdf/objects.js";
2
+ import { lzwDecodeMsb } from "../core/lzw.js";
2
3
  import { reversePredictor } from "./predictor.js";
3
4
  import { decodeCcitt } from "./ccitt.js";
4
5
  import { encodePng } from "./png-encode.js";
@@ -342,9 +343,7 @@ function decodeChain(file, stream, filters) {
342
343
  return;
343
344
  }
344
345
  else if (f === "LZWDecode" || f === "LZW") {
345
- const dec = lzwDecode(data, lzwEarlyChange(file, stream.dict));
346
- if (!dec) return void 0;
347
- data = dec;
346
+ data = lzwDecode(data, lzwEarlyChange(file, stream.dict));
348
347
  mayPredict = true;
349
348
  } else if (f === "RunLengthDecode" || f === "RL") data = runLengthDecode(data);
350
349
  else if (f === "ASCII85Decode" || f === "A85") data = ascii85Decode(data);
@@ -359,7 +358,7 @@ function applyChainExceptLast(filters, raw) {
359
358
  if (f === "FlateDecode" || f === "Fl") try {
360
359
  data = unzlibSync(data);
361
360
  } catch {}
362
- else if (f === "LZWDecode" || f === "LZW") data = lzwDecode(data, 1) ?? data;
361
+ else if (f === "LZWDecode" || f === "LZW") data = lzwDecode(data, 1);
363
362
  else if (f === "RunLengthDecode" || f === "RL") data = runLengthDecode(data);
364
363
  else if (f === "ASCII85Decode" || f === "A85") data = ascii85Decode(data);
365
364
  else if (f === "ASCIIHexDecode" || f === "AHx") data = asciiHexDecode(data);
@@ -380,77 +379,10 @@ function applyPredictor(file, d, data) {
380
379
  }
381
380
  var MAX_LZW_OUT = MAX_PIXELS * 4;
382
381
  function lzwDecode(data, earlyChange) {
383
- let out = new Uint8Array(4096);
384
- let outLen = 0;
385
- const emit = (e) => {
386
- if (outLen + e.length > MAX_LZW_OUT) return false;
387
- if (outLen + e.length > out.length) {
388
- let cap = out.length * 2;
389
- while (cap < outLen + e.length) cap *= 2;
390
- const grown = new Uint8Array(cap);
391
- grown.set(out.subarray(0, outLen));
392
- out = grown;
393
- }
394
- out.set(e, outLen);
395
- outLen += e.length;
396
- return true;
397
- };
398
- let bitBuffer = 0;
399
- let bitCount = 0;
400
- let pos = 0;
401
- let codeLength = 9;
402
- const readCode = () => {
403
- while (bitCount < codeLength) {
404
- if (pos >= data.length) return -1;
405
- bitBuffer = (bitBuffer << 8 | data[pos++]) >>> 0;
406
- bitCount += 8;
407
- }
408
- bitCount -= codeLength;
409
- return bitBuffer >>> bitCount & (1 << codeLength) - 1;
410
- };
411
- const dict = new Array(4096);
412
- let nextCode = 258;
413
- let prev = -1;
414
- const reset = () => {
415
- for (let i = 0; i < 256; i++) dict[i] = Uint8Array.of(i);
416
- nextCode = 258;
417
- codeLength = 9;
418
- prev = -1;
419
- };
420
- reset();
421
- for (;;) {
422
- const code = readCode();
423
- if (code < 0 || code === 257) break;
424
- if (code === 256) {
425
- reset();
426
- continue;
427
- }
428
- if (prev < 0) {
429
- const first = dict[code];
430
- if (!first || !emit(first)) break;
431
- prev = code;
432
- continue;
433
- }
434
- const prevEntry = dict[prev];
435
- let entry = code < nextCode ? dict[code] : void 0;
436
- if (!entry) {
437
- entry = new Uint8Array(prevEntry.length + 1);
438
- entry.set(prevEntry);
439
- entry[prevEntry.length] = prevEntry[0];
440
- }
441
- if (!emit(entry)) break;
442
- if (nextCode < 4096) {
443
- const added = new Uint8Array(prevEntry.length + 1);
444
- added.set(prevEntry);
445
- added[prevEntry.length] = entry[0];
446
- dict[nextCode++] = added;
447
- if (nextCode + earlyChange === 512) codeLength = 10;
448
- else if (nextCode + earlyChange === 1024) codeLength = 11;
449
- else if (nextCode + earlyChange === 2048) codeLength = 12;
450
- }
451
- prev = code;
452
- }
453
- return out.subarray(0, outLen);
382
+ return lzwDecodeMsb(data, {
383
+ earlyChange,
384
+ limit: MAX_LZW_OUT
385
+ });
454
386
  }
455
387
  function lzwEarlyChange(file, d) {
456
388
  const ec = decodeParmsOf(file, d)?.get("EarlyChange");
@@ -156,7 +156,7 @@ function imageHref(resourceName, laid) {
156
156
  for (const [resourceId, res] of laid.imageResources) {
157
157
  if (res.resourceName !== resourceName) continue;
158
158
  const bytes = laid.resources.get(resourceId);
159
- if (!bytes) return void 0;
159
+ if (!bytes || !res.prepared) return void 0;
160
160
  return `data:${res.prepared.mimeType};base64,${toBase64(bytes)}`;
161
161
  }
162
162
  }
@@ -457,6 +457,7 @@ function blocksForDrawing(content, paragraphProperties, ctx) {
457
457
  ...resource ? { resource } : {},
458
458
  ...content.outline ? { outline: content.outline } : {},
459
459
  ...content.shadow ? { shadow: content.shadow } : {},
460
+ ...content.wash ? { wash: content.wash } : {},
460
461
  width: content.width,
461
462
  height: content.height,
462
463
  ...content.crop ? { crop: content.crop } : {},
@@ -935,6 +936,7 @@ function parseRun(r, ctx, anchored) {
935
936
  ...resource ? { resource } : {},
936
937
  ...content.outline ? { outline: content.outline } : {},
937
938
  ...content.shadow ? { shadow: content.shadow } : {},
939
+ ...content.wash ? { wash: content.wash } : {},
938
940
  width: content.width,
939
941
  height: content.height,
940
942
  ...content.crop ? { crop: content.crop } : {},
@@ -953,6 +955,7 @@ function parseRun(r, ctx, anchored) {
953
955
  resource,
954
956
  ...content.outline ? { outline: content.outline } : {},
955
957
  ...content.shadow ? { shadow: content.shadow } : {},
958
+ ...content.wash ? { wash: content.wash } : {},
956
959
  width: content.width,
957
960
  height: content.height
958
961
  };
@@ -57,6 +57,11 @@ export type DrawingContent = {
57
57
  readonly outline?: PictureOutline;
58
58
  /** §20.1.8.40 `a:outerShdw` on the same `pic:spPr` — the picture's shadow. */
59
59
  readonly shadow?: ShapeShadow;
60
+ /** §14.1.2.10 `@gain`/`@blacklevel` — the wash the picture is drawn through. */
61
+ readonly wash?: {
62
+ readonly gain: number;
63
+ readonly black: number;
64
+ };
60
65
  /** §20.1.8.55 `a:srcRect` — the part of the source the frame shows. */
61
66
  readonly crop?: ImageCrop;
62
67
  /** §20.1.7.6 `a:xfrm @rot` — the picture's rotation (1/60000°, clockwise). */
@@ -25,10 +25,16 @@ function parseFloatAnchor(anchor) {
25
25
  const behindRaw = poAttr(anchor, "behindDoc");
26
26
  const behind = behindRaw === "1" || behindRaw === "true";
27
27
  let wrap = "none";
28
- for (const child of poChildren(anchor)) if (poIs(child, "wp:wrapSquare")) wrap = "square";
29
- else if (poIs(child, "wp:wrapTight")) wrap = "tight";
30
- else if (poIs(child, "wp:wrapThrough")) wrap = "through";
31
- else if (poIs(child, "wp:wrapTopAndBottom")) wrap = "topAndBottom";
28
+ let wrapSide;
29
+ for (const child of poChildren(anchor)) {
30
+ if (poIs(child, "wp:wrapSquare")) wrap = "square";
31
+ else if (poIs(child, "wp:wrapTight")) wrap = "tight";
32
+ else if (poIs(child, "wp:wrapThrough")) wrap = "through";
33
+ else if (poIs(child, "wp:wrapTopAndBottom")) wrap = "topAndBottom";
34
+ else continue;
35
+ const side = poAttr(child, "wrapText");
36
+ wrapSide = side === "left" || side === "right" || side === "largest" ? side : "bothSides";
37
+ }
32
38
  const zOrder = poIntAttr(anchor, "relativeHeight");
33
39
  const posH = parseAnchorPos(anchor, "wp:positionH", [
34
40
  "margin",
@@ -53,8 +59,12 @@ function parseFloatAnchor(anchor) {
53
59
  rightPt: dist("distR")
54
60
  };
55
61
  const anyDist = Object.values(wrapDist).some((v) => v > 0);
62
+ const inCellRaw = poAttr(anchor, "layoutInCell");
63
+ const inCell = !(inCellRaw === "0" || inCellRaw === "false");
56
64
  return {
57
65
  wrap,
66
+ ...wrapSide ? { wrapSide } : {},
67
+ ...inCell ? {} : { inCell: false },
58
68
  ...behind ? { behind: true } : {},
59
69
  ...zOrder !== void 0 ? { zOrder } : {},
60
70
  ...anyDist ? { wrapDist } : {},
@@ -355,12 +365,14 @@ function parseVmlPicture(node, parseBody) {
355
365
  const shapeType = typeRef !== void 0 ? vmlShapeTypes(node).get(typeRef) : void 0;
356
366
  const stroked = shape ? poAttr(shape, "stroked") ?? (shapeType ? poAttr(shapeType, "stroked") : void 0) : void 0;
357
367
  const outline = shape && (stroked === "t" || stroked === "true") ? pictureOutline(vmlLine(shape, shapeType)) : void 0;
368
+ const wash = vmlWash(imagedata);
358
369
  return {
359
370
  kind: "image",
360
371
  imageId,
361
372
  width,
362
373
  height,
363
374
  ...outline ? { outline } : {},
375
+ ...wash ? { wash } : {},
364
376
  ...float ? { float } : {},
365
377
  ...altText ? { altText } : {}
366
378
  };
@@ -404,13 +416,29 @@ function parseVmlShape(node, parseBody) {
404
416
  const data = vmlShapeData(shape, tag, pt(Math.max(1, boxW)), pt(Math.max(1, boxH)), parseBody, vmlShapeTypes(node));
405
417
  if (!data) return null;
406
418
  const float = vmlFloat(style, poIntAttr(shape, "z-index"), shape);
419
+ const placed = float && from && to ? vmlLinePlaced(float, from, to) : float;
407
420
  return {
408
421
  kind: "shape",
409
422
  data,
410
- ...float ? { float } : {},
423
+ ...placed ? { float: placed } : {},
411
424
  ...poAttr(shape, "alt")?.trim() ? { altText: poAttr(shape, "alt").trim() } : {}
412
425
  };
413
426
  }
427
+ function vmlLinePlaced(float, from, to) {
428
+ const x = Math.min(from.x, to.x);
429
+ const y = Math.min(from.y, to.y);
430
+ return {
431
+ ...float,
432
+ ...float.posH?.align === void 0 ? { posH: {
433
+ ...float.posH ?? { relativeFrom: "column" },
434
+ offsetPt: pt(x)
435
+ } } : {},
436
+ ...float.posV?.align === void 0 ? { posV: {
437
+ ...float.posV ?? { relativeFrom: "paragraph" },
438
+ offsetPt: pt(y)
439
+ } } : {}
440
+ };
441
+ }
414
442
  /**
415
443
  * One VML primitive as {@link ShapeData}: its preset geometry, fill, outline
416
444
  * and the words in its text box.
@@ -810,20 +838,24 @@ function vmlFill(shape, shapeType, resolveImage) {
810
838
  const fillEl = poChildren(shape).find((c) => poIs(c, "v:fill"));
811
839
  if (fillEl && (poAttr(fillEl, "type") === "none" || poAttr(fillEl, "on") === "f")) return { kind: "none" };
812
840
  const base = vmlColor(poAttr(shape, "fillcolor")) ?? (fillEl ? vmlColor(poAttr(fillEl, "color")) : void 0) ?? vmlColor(shapeType ? poAttr(shapeType, "fillcolor") : void 0) ?? "FFFFFF";
813
- const fillRelId = fillEl && poAttr(fillEl, "type") === "frame" ? poAttr(fillEl, "r:id") : void 0;
841
+ const fillType = fillEl ? poAttr(fillEl, "type") : void 0;
842
+ const fillRelId = fillType === "frame" || fillType === "tile" ? poAttr(fillEl, "r:id") : void 0;
814
843
  const picture = fillRelId !== void 0 ? resolveImage?.(fillRelId) : void 0;
815
844
  if (picture !== void 0) return {
816
845
  kind: "picture",
817
- imageResource: picture
846
+ imageResource: picture,
847
+ ...fillType === "tile" ? { tiled: true } : {}
818
848
  };
819
849
  const gradient = fillEl ? vmlGradient(fillEl, base) : void 0;
820
850
  if (gradient) return {
821
851
  kind: "gradient",
822
852
  gradient
823
853
  };
854
+ const opacity = vmlOpacity(poAttr(shape, "opacity") ?? (fillEl ? poAttr(fillEl, "opacity") : void 0));
824
855
  return {
825
856
  kind: "solid",
826
- colorHex: base
857
+ colorHex: base,
858
+ ...opacity !== void 0 ? { alpha: opacity } : {}
827
859
  };
828
860
  }
829
861
  function vmlGradient(fillEl, base) {
@@ -878,6 +910,33 @@ function vmlFocusStops(raw, base, color2) {
878
910
  }
879
911
  ];
880
912
  }
913
+ function vmlWash(imagedata) {
914
+ const gain = vmlFraction(poAttr(imagedata, "gain"));
915
+ const black = vmlFraction(poAttr(imagedata, "blacklevel"));
916
+ if (gain === void 0 && black === void 0) return void 0;
917
+ const g = Math.max(0, gain ?? 1);
918
+ const b = black ?? 0;
919
+ return Math.abs(g - 1) < .01 && Math.abs(b) < .01 ? void 0 : {
920
+ gain: g,
921
+ black: b
922
+ };
923
+ }
924
+ function vmlFraction(raw) {
925
+ if (raw === void 0 || raw.trim() === "") return void 0;
926
+ const t = raw.trim();
927
+ const pct = t.endsWith("%");
928
+ const n = Number.parseFloat(pct ? t.slice(0, -1) : t.replace(/f$/iu, ""));
929
+ if (!Number.isFinite(n)) return void 0;
930
+ return pct ? n / 100 : Math.abs(n) > 1 ? n / 65536 : n;
931
+ }
932
+ function vmlOpacity(raw) {
933
+ if (raw === void 0 || raw.trim() === "") return void 0;
934
+ const pct = raw.trim().endsWith("%");
935
+ const n = Number.parseFloat(pct ? raw.trim().slice(0, -1) : raw.replace(/f$/iu, ""));
936
+ if (!Number.isFinite(n)) return void 0;
937
+ const v = pct ? n / 100 : n > 1 ? n / 65536 : n;
938
+ return v >= 1 ? void 0 : Math.max(0, v);
939
+ }
881
940
  function vmlStops(raw) {
882
941
  if (raw === void 0 || raw.trim() === "") return void 0;
883
942
  const stops = [];
@@ -1020,6 +1079,7 @@ function vmlFloat(style, zIndexAttr, shape) {
1020
1079
  const zIndex = prop("z-index") ?? zIndexAttr;
1021
1080
  return {
1022
1081
  wrap: vmlWrap(shape),
1082
+ ...poAttrLocal(shape, "allowincell") === "f" ? { inCell: false } : {},
1023
1083
  ...zIndex !== void 0 && zIndex < 0 ? { behind: true } : {},
1024
1084
  ...zIndex !== void 0 ? { zOrder: Math.abs(zIndex) } : {},
1025
1085
  posH: {
@@ -1530,11 +1590,17 @@ function withStyleFontColor(text, style, resolveColor) {
1530
1590
  }
1531
1591
  function parseTextBox(wsp, parseBody) {
1532
1592
  const txbx = poChildren(wsp).find((c) => poIs(c, "wps:txbx"));
1533
- if (!txbx) return void 0;
1534
- const txContent = poChildren(txbx).find((c) => poIs(c, "w:txbxContent"));
1535
- if (!txContent) return void 0;
1536
- const content = parseBody(poChildren(txContent));
1537
- if (content.length === 0) return void 0;
1593
+ const linked = poChildren(wsp).find((c) => poIs(c, "wps:linkedTxbx"));
1594
+ const chainId = poAttr(txbx, "id") ?? poAttr(linked, "id");
1595
+ const chain = chainId === void 0 ? void 0 : {
1596
+ id: chainId,
1597
+ seq: linked ? poIntAttr(linked, "seq") ?? 1 : 0
1598
+ };
1599
+ if (!txbx && !linked) return void 0;
1600
+ const txContent = txbx ? poChildren(txbx).find((c) => poIs(c, "w:txbxContent")) : void 0;
1601
+ if (txbx && !txContent) return void 0;
1602
+ const content = txContent ? parseBody(poChildren(txContent)) : [];
1603
+ if (content.length === 0 && !linked) return void 0;
1538
1604
  const bodyPr = poChildren(wsp).find((c) => poIs(c, "wps:bodyPr"));
1539
1605
  const lIns = bodyPr ? poIntAttr(bodyPr, "lIns") : void 0;
1540
1606
  const tIns = bodyPr ? poIntAttr(bodyPr, "tIns") : void 0;
@@ -1547,6 +1613,7 @@ function parseTextBox(wsp, parseBody) {
1547
1613
  const autoFit = bodyPr !== void 0 && poChildren(bodyPr).some((c) => poIs(c, "a:spAutoFit"));
1548
1614
  return {
1549
1615
  content,
1616
+ ...chain ? { chain } : {},
1550
1617
  ...vertical ? { vertical } : {},
1551
1618
  ...autoFit ? { autoFit: true } : {},
1552
1619
  ...lIns !== void 0 ? { insetLeft: emuToPt(lIns) } : {},
@@ -1737,14 +1804,17 @@ function fillFromNode(child, resolveColor, resolveImage) {
1737
1804
  return {
1738
1805
  kind: "picture",
1739
1806
  imageResource: resource,
1807
+ ...poChildren(child).some((c) => poIs(c, "a:tile")) ? { tiled: true } : {},
1740
1808
  ...crop ? { imageCrop: crop } : {}
1741
1809
  };
1742
1810
  }
1743
1811
  if (poIs(child, "a:solidFill")) {
1744
1812
  const hex = colorFromContainer(child, resolveColor);
1813
+ const alpha = containerAlpha(child);
1745
1814
  return hex ? {
1746
1815
  kind: "solid",
1747
- colorHex: hex
1816
+ colorHex: alpha === void 0 ? hex : unblendWhite(hex, alpha),
1817
+ ...alpha !== void 0 ? { alpha } : {}
1748
1818
  } : { kind: "none" };
1749
1819
  }
1750
1820
  if (poIs(child, "a:pattFill")) {
@@ -1997,6 +2067,22 @@ function normalizeDash(v) {
1997
2067
  if (v === "lgDashDotDot") return "lgDashDot";
1998
2068
  if (v === "sysDashDot" || v === "sysDashDotDot") return "sysDash";
1999
2069
  }
2070
+ function unblendWhite(hex, alpha) {
2071
+ if (alpha <= .004) return hex;
2072
+ const n = parseInt(hex, 16);
2073
+ if (Number.isNaN(n)) return hex;
2074
+ const ch = (shift) => {
2075
+ const raw = ((n >> shift & 255) / 255 - (1 - alpha)) / alpha;
2076
+ return Math.round(Math.max(0, Math.min(1, raw)) * 255).toString(16).padStart(2, "0");
2077
+ };
2078
+ return (ch(16) + ch(8) + ch(0)).toUpperCase();
2079
+ }
2080
+ function containerAlpha(parent) {
2081
+ for (const c of poChildren(parent)) {
2082
+ const mod = readColorMods(c).find((m) => m.kind === "alpha");
2083
+ if (mod) return Math.max(0, Math.min(1, mod.val));
2084
+ }
2085
+ }
2000
2086
  function colorFromContainer(parent, resolveColor) {
2001
2087
  for (const c of poChildren(parent)) {
2002
2088
  const hex = resolveColorNode(c, resolveColor);
@@ -131,8 +131,8 @@ function parseParagraphProperties(pPr, autoSpacingPt = 0) {
131
131
  }
132
132
  if ("w:ind" in el) {
133
133
  const node = el["w:ind"];
134
- const left = parseIntAttr(node, "left");
135
- const right = parseIntAttr(node, "right");
134
+ const left = parseIntAttr(node, "left") ?? parseIntAttr(node, "start");
135
+ const right = parseIntAttr(node, "right") ?? parseIntAttr(node, "end");
136
136
  const firstLine = parseIntAttr(node, "firstLine");
137
137
  const hanging = parseIntAttr(node, "hanging");
138
138
  if (left !== void 0) out.indentLeft = twipsToPt(left);
@@ -1,5 +1,5 @@
1
1
  import { halfPtToPt, twipsToPt } from "../core/ir/units.js";
2
- import { asElement, getAttr, getVal, parseIntAttr, parseToggle } from "./xml-helpers.js";
2
+ import { asElement, getAttr, getVal, parseHalfPointAttr, parseToggle } from "./xml-helpers.js";
3
3
  import { shadingFillHex } from "./shading.js";
4
4
  //#region src/word/run-properties.ts
5
5
  var UNDERLINE_STYLES = new Set([
@@ -81,7 +81,7 @@ function parseRunProperties(rPr) {
81
81
  if (c && /^[0-9A-Fa-f]{6}$/u.test(c)) out.underlineColorHex = c.toUpperCase();
82
82
  }
83
83
  if ("w:sz" in el) {
84
- const v = parseIntAttr(el["w:sz"], "val");
84
+ const v = parseHalfPointAttr(el["w:sz"], "val");
85
85
  if (v !== void 0) out.fontSizePt = halfPtToPt(v);
86
86
  }
87
87
  if ("w:color" in el) {
@@ -36,6 +36,18 @@ export declare function getVal(node: unknown): string | undefined;
36
36
  * @param name The local attribute name.
37
37
  * @returns The parsed number, or `undefined` when absent or not finite.
38
38
  */
39
+ /**
40
+ * §17.3.2.38 ST_HpsMeasure — a size in HALF-POINTS, or the same universal
41
+ * measure with a unit on it ("20pt"). The two units differ by a factor of ten
42
+ * from the twips a length is written in, so a size is read here rather than
43
+ * through {@link parseIntAttr}: tdf108408.docx writes `w:sz w:val="20pt"` and
44
+ * read as twips it set its sample text 200 points tall.
45
+ *
46
+ * @param node The element carrying the attribute.
47
+ * @param name The attribute name.
48
+ * @returns The size in half-points, or undefined.
49
+ */
50
+ export declare function parseHalfPointAttr(node: unknown, name: string): number | undefined;
39
51
  export declare function parseIntAttr(node: unknown, name: string): number | undefined;
40
52
  /**
41
53
  * Parse a toggle (boolean) property per ECMA-376 Part 1 §17.17.4:
@@ -1,3 +1,4 @@
1
+ import { universalMeasureTwips } from "../core/po-helpers.js";
1
2
  //#region src/word/xml-helpers.ts
2
3
  /**
3
4
  * Narrow an arbitrary parsed value to an {@link XmlElement}.
@@ -47,12 +48,31 @@ function getVal(node) {
47
48
  * @param name The local attribute name.
48
49
  * @returns The parsed number, or `undefined` when absent or not finite.
49
50
  */
51
+ /**
52
+ * §17.3.2.38 ST_HpsMeasure — a size in HALF-POINTS, or the same universal
53
+ * measure with a unit on it ("20pt"). The two units differ by a factor of ten
54
+ * from the twips a length is written in, so a size is read here rather than
55
+ * through {@link parseIntAttr}: tdf108408.docx writes `w:sz w:val="20pt"` and
56
+ * read as twips it set its sample text 200 points tall.
57
+ *
58
+ * @param node The element carrying the attribute.
59
+ * @param name The attribute name.
60
+ * @returns The size in half-points, or undefined.
61
+ */
62
+ function parseHalfPointAttr(node, name) {
63
+ const v = getAttr(node, name);
64
+ if (v === void 0) return void 0;
65
+ const n = Number(v);
66
+ if (Number.isFinite(n)) return n;
67
+ const twips = universalMeasureTwips(v);
68
+ return twips === void 0 ? void 0 : twips / 10;
69
+ }
50
70
  function parseIntAttr(node, name) {
51
71
  const v = getAttr(node, name);
52
72
  if (v === void 0) return void 0;
53
73
  const n = Number(v);
54
- if (!Number.isFinite(n)) return void 0;
55
- return n;
74
+ if (Number.isFinite(n)) return n;
75
+ return universalMeasureTwips(v);
56
76
  }
57
77
  /**
58
78
  * Parse a toggle (boolean) property per ECMA-376 Part 1 §17.17.4:
@@ -71,4 +91,4 @@ function parseToggle(node) {
71
91
  return !(v === "false" || v === "0" || v === "off");
72
92
  }
73
93
  //#endregion
74
- export { asArray, asElement, getAttr, getVal, parseIntAttr, parseToggle };
94
+ export { asArray, asElement, getAttr, getVal, parseHalfPointAttr, parseIntAttr, parseToggle };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reamkit",
3
- "version": "1.17.0",
3
+ "version": "1.19.0",
4
4
  "description": "Ream — convert DOCX, XLSX, PPTX and PDF to PDF, SVG, HTML, DOCX and XLSX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
5
5
  "license": "MIT",
6
6
  "author": "Alex Krassavin <info@reamkit.dev>",