reamkit 1.17.0 → 1.18.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.
@@ -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.
@@ -821,9 +849,11 @@ function vmlFill(shape, shapeType, resolveImage) {
821
849
  kind: "gradient",
822
850
  gradient
823
851
  };
852
+ const opacity = vmlOpacity(poAttr(shape, "opacity") ?? (fillEl ? poAttr(fillEl, "opacity") : void 0));
824
853
  return {
825
854
  kind: "solid",
826
- colorHex: base
855
+ colorHex: base,
856
+ ...opacity !== void 0 ? { alpha: opacity } : {}
827
857
  };
828
858
  }
829
859
  function vmlGradient(fillEl, base) {
@@ -878,6 +908,33 @@ function vmlFocusStops(raw, base, color2) {
878
908
  }
879
909
  ];
880
910
  }
911
+ function vmlWash(imagedata) {
912
+ const gain = vmlFraction(poAttr(imagedata, "gain"));
913
+ const black = vmlFraction(poAttr(imagedata, "blacklevel"));
914
+ if (gain === void 0 && black === void 0) return void 0;
915
+ const g = Math.max(0, gain ?? 1);
916
+ const b = black ?? 0;
917
+ return Math.abs(g - 1) < .01 && Math.abs(b) < .01 ? void 0 : {
918
+ gain: g,
919
+ black: b
920
+ };
921
+ }
922
+ function vmlFraction(raw) {
923
+ if (raw === void 0 || raw.trim() === "") return void 0;
924
+ const t = raw.trim();
925
+ const pct = t.endsWith("%");
926
+ const n = Number.parseFloat(pct ? t.slice(0, -1) : t.replace(/f$/iu, ""));
927
+ if (!Number.isFinite(n)) return void 0;
928
+ return pct ? n / 100 : Math.abs(n) > 1 ? n / 65536 : n;
929
+ }
930
+ function vmlOpacity(raw) {
931
+ if (raw === void 0 || raw.trim() === "") return void 0;
932
+ const pct = raw.trim().endsWith("%");
933
+ const n = Number.parseFloat(pct ? raw.trim().slice(0, -1) : raw.replace(/f$/iu, ""));
934
+ if (!Number.isFinite(n)) return void 0;
935
+ const v = pct ? n / 100 : n > 1 ? n / 65536 : n;
936
+ return v >= 1 ? void 0 : Math.max(0, v);
937
+ }
881
938
  function vmlStops(raw) {
882
939
  if (raw === void 0 || raw.trim() === "") return void 0;
883
940
  const stops = [];
@@ -1020,6 +1077,7 @@ function vmlFloat(style, zIndexAttr, shape) {
1020
1077
  const zIndex = prop("z-index") ?? zIndexAttr;
1021
1078
  return {
1022
1079
  wrap: vmlWrap(shape),
1080
+ ...poAttrLocal(shape, "allowincell") === "f" ? { inCell: false } : {},
1023
1081
  ...zIndex !== void 0 && zIndex < 0 ? { behind: true } : {},
1024
1082
  ...zIndex !== void 0 ? { zOrder: Math.abs(zIndex) } : {},
1025
1083
  posH: {
@@ -1530,11 +1588,17 @@ function withStyleFontColor(text, style, resolveColor) {
1530
1588
  }
1531
1589
  function parseTextBox(wsp, parseBody) {
1532
1590
  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;
1591
+ const linked = poChildren(wsp).find((c) => poIs(c, "wps:linkedTxbx"));
1592
+ const chainId = poAttr(txbx, "id") ?? poAttr(linked, "id");
1593
+ const chain = chainId === void 0 ? void 0 : {
1594
+ id: chainId,
1595
+ seq: linked ? poIntAttr(linked, "seq") ?? 1 : 0
1596
+ };
1597
+ if (!txbx && !linked) return void 0;
1598
+ const txContent = txbx ? poChildren(txbx).find((c) => poIs(c, "w:txbxContent")) : void 0;
1599
+ if (txbx && !txContent) return void 0;
1600
+ const content = txContent ? parseBody(poChildren(txContent)) : [];
1601
+ if (content.length === 0 && !linked) return void 0;
1538
1602
  const bodyPr = poChildren(wsp).find((c) => poIs(c, "wps:bodyPr"));
1539
1603
  const lIns = bodyPr ? poIntAttr(bodyPr, "lIns") : void 0;
1540
1604
  const tIns = bodyPr ? poIntAttr(bodyPr, "tIns") : void 0;
@@ -1547,6 +1611,7 @@ function parseTextBox(wsp, parseBody) {
1547
1611
  const autoFit = bodyPr !== void 0 && poChildren(bodyPr).some((c) => poIs(c, "a:spAutoFit"));
1548
1612
  return {
1549
1613
  content,
1614
+ ...chain ? { chain } : {},
1550
1615
  ...vertical ? { vertical } : {},
1551
1616
  ...autoFit ? { autoFit: true } : {},
1552
1617
  ...lIns !== void 0 ? { insetLeft: emuToPt(lIns) } : {},
@@ -1742,9 +1807,11 @@ function fillFromNode(child, resolveColor, resolveImage) {
1742
1807
  }
1743
1808
  if (poIs(child, "a:solidFill")) {
1744
1809
  const hex = colorFromContainer(child, resolveColor);
1810
+ const alpha = containerAlpha(child);
1745
1811
  return hex ? {
1746
1812
  kind: "solid",
1747
- colorHex: hex
1813
+ colorHex: alpha === void 0 ? hex : unblendWhite(hex, alpha),
1814
+ ...alpha !== void 0 ? { alpha } : {}
1748
1815
  } : { kind: "none" };
1749
1816
  }
1750
1817
  if (poIs(child, "a:pattFill")) {
@@ -1997,6 +2064,22 @@ function normalizeDash(v) {
1997
2064
  if (v === "lgDashDotDot") return "lgDashDot";
1998
2065
  if (v === "sysDashDot" || v === "sysDashDotDot") return "sysDash";
1999
2066
  }
2067
+ function unblendWhite(hex, alpha) {
2068
+ if (alpha <= .004) return hex;
2069
+ const n = parseInt(hex, 16);
2070
+ if (Number.isNaN(n)) return hex;
2071
+ const ch = (shift) => {
2072
+ const raw = ((n >> shift & 255) / 255 - (1 - alpha)) / alpha;
2073
+ return Math.round(Math.max(0, Math.min(1, raw)) * 255).toString(16).padStart(2, "0");
2074
+ };
2075
+ return (ch(16) + ch(8) + ch(0)).toUpperCase();
2076
+ }
2077
+ function containerAlpha(parent) {
2078
+ for (const c of poChildren(parent)) {
2079
+ const mod = readColorMods(c).find((m) => m.kind === "alpha");
2080
+ if (mod) return Math.max(0, Math.min(1, mod.val));
2081
+ }
2082
+ }
2000
2083
  function colorFromContainer(parent, resolveColor) {
2001
2084
  for (const c of poChildren(parent)) {
2002
2085
  const hex = resolveColorNode(c, resolveColor);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reamkit",
3
- "version": "1.17.0",
3
+ "version": "1.18.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>",