@poe-platform/safe-bash 0.1.761 → 0.1.763

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 (32) hide show
  1. package/dist/image-ast/index.browser.js +255 -62
  2. package/dist/image-ast/index.js +255 -62
  3. package/dist/pdf-ast/ast.d.ts +23 -0
  4. package/dist/pdf-ast/index.js +344 -98
  5. package/dist/safe-bash/command-chunks/{chunk-6VX5ZQL5.js → chunk-4XHONGIU.js} +2 -2
  6. package/dist/safe-bash/command-chunks/{chunk-OSMLVM7P.js → chunk-S3G2B4OX.js} +258 -62
  7. package/dist/safe-bash/command-chunks/{chunk-GR725WRX.js → chunk-S5EX6UP5.js} +1 -1
  8. package/dist/safe-bash/commands/csvcut/index.d.ts +1 -1
  9. package/dist/safe-bash/commands/csvcut/index.js +3 -1
  10. package/dist/safe-bash/commands/csvgrep/index.d.ts +1 -1
  11. package/dist/safe-bash/commands/csvgrep/index.js +3 -1
  12. package/dist/safe-bash/core.browser.js +8 -6
  13. package/dist/safe-bash/shell/runtime.js +5 -5
  14. package/dist/safe-bash-checksum-engine/index.d.ts +1 -1
  15. package/dist/safe-bash-checksum-engine/index.js +5 -1
  16. package/dist/safe-bash-command-ffmpeg/index.js +7942 -7853
  17. package/dist/safe-bash-command-gh/index.js +174 -105
  18. package/dist/safe-bash-command-imagemagick/index.js +2 -2
  19. package/dist/safe-bash-command-pandoc/command.js +3 -3
  20. package/dist/safe-bash-command-pandoc/index.js +3 -3
  21. package/dist/safe-bash-command-pdfimages/index.js +255 -62
  22. package/dist/safe-bash-command-pdfinfo/index.js +342 -98
  23. package/dist/safe-bash-command-pdftk/index.js +255 -62
  24. package/dist/safe-bash-command-pdftoppm/index.js +342 -98
  25. package/dist/safe-bash-command-pdftotext/index.js +255 -62
  26. package/dist/safe-bash-command-qpdf/index.js +255 -62
  27. package/dist/safe-bash-command-sips/index.js +2 -2
  28. package/dist/safe-bash-command-soffice/index.js +1 -1
  29. package/dist/safe-bash-command-ssconvert/index.js +318 -65
  30. package/dist/safe-bash-command-wkhtmltopdf/index.js +255 -62
  31. package/package.json +2 -2
  32. package/third-party/pdf-ast/THIRD_PARTY_NOTICES.md +23 -0
@@ -11606,14 +11606,18 @@ function evaluateContentStreamToDisplayList(params) {
11606
11606
  const paths = [];
11607
11607
  const images = [];
11608
11608
  const operations = [];
11609
+ let capturedOperations;
11610
+ let insideSoftMask = false;
11609
11611
  const emit = (operation) => {
11610
- const { clipPaths, clipImages } = curState();
11611
- if (clipPaths || clipImages) operation = { ...operation, value: {
11612
+ const { clipPaths, clipImages, softMask } = curState();
11613
+ if (clipPaths || clipImages || softMask) operation = { ...operation, value: {
11612
11614
  ...operation.value,
11613
11615
  ...clipPaths ? { clipPaths } : {},
11614
- ...clipImages ? { clipImages } : {}
11616
+ ...clipImages ? { clipImages } : {},
11617
+ ...softMask ? { softMask } : {}
11615
11618
  } };
11616
- operations.push(operation);
11619
+ (capturedOperations ?? operations).push(operation);
11620
+ if (insideSoftMask) return;
11617
11621
  switch (operation.kind) {
11618
11622
  case "path":
11619
11623
  paths.push(operation.value);
@@ -11719,7 +11723,7 @@ function evaluateContentStreamToDisplayList(params) {
11719
11723
  );
11720
11724
  return { r: kClamp(r), g: kClamp(g), b: kClamp(b) };
11721
11725
  };
11722
- const applyStateOperator = (st, operator, ops, activeResources) => {
11726
+ const applyStateOperator = (st, operator, ops, activeResources, activeFonts, depth) => {
11723
11727
  const num = (i, fb = 0) => ops[i]?.kind === "number" ? ops[i].value : fb;
11724
11728
  if (operator === "cm") {
11725
11729
  const m = [num(0, 1), num(1, 0), num(2, 0), num(3, 1), num(4, 0), num(5, 0)];
@@ -11802,6 +11806,46 @@ function evaluateContentStreamToDisplayList(params) {
11802
11806
  const extDict = params.cosDoc.resolveDict(dictGet(activeResources, "ExtGState"));
11803
11807
  const gsDict = extDict ? params.cosDoc.resolveDict(dictGet(extDict, ops[0].decoded)) : void 0;
11804
11808
  if (gsDict) {
11809
+ const mask = params.cosDoc.resolve(dictGet(gsDict, "SMask"));
11810
+ if (mask?.kind === "name" && mask.decoded === "None") {
11811
+ st.softMask = void 0;
11812
+ } else if (mask?.kind === "dict") {
11813
+ const subtype = params.cosDoc.resolve(dictGet(mask, "S"));
11814
+ const form = params.cosDoc.resolve(dictGet(mask, "G"));
11815
+ if (form?.kind === "stream" && subtype?.kind === "name" && (subtype.decoded === "Alpha" || subtype.decoded === "Luminosity")) {
11816
+ if (depth >= 8) throw new PdfError("E_LIMIT", "Soft-mask nesting exceeds the form depth limit");
11817
+ const parentOperations = capturedOperations;
11818
+ const parentInsideSoftMask = insideSoftMask;
11819
+ const savedTextState = { pendingTextClip, hasTextClip, activeTm, activeTlm };
11820
+ const captured = [];
11821
+ capturedOperations = captured;
11822
+ insideSoftMask = true;
11823
+ const maskState = { ...st, softMask: void 0, fillAlpha: 1, strokeAlpha: 1, blendMode: "Normal" };
11824
+ delete maskState.clipPaths;
11825
+ delete maskState.clipImages;
11826
+ delete maskState.clipRect;
11827
+ stateStack.push(maskState);
11828
+ try {
11829
+ paintForm(form, activeResources, activeFonts, depth, void 0, void 0, true);
11830
+ } finally {
11831
+ stateStack.pop();
11832
+ capturedOperations = parentOperations;
11833
+ insideSoftMask = parentInsideSoftMask;
11834
+ ({ pendingTextClip, hasTextClip, activeTm, activeTlm } = savedTextState);
11835
+ }
11836
+ const group = params.cosDoc.resolveDict(dictGet(form.dict, "Group"));
11837
+ const colorSpace = group ? dictGet(group, "CS") : void 0;
11838
+ const bc = params.cosDoc.resolveArray(dictGet(mask, "BC"));
11839
+ const components = bc?.items.map((item) => {
11840
+ const value = params.cosDoc.resolve(item);
11841
+ return value?.kind === "number" ? value.value : 0;
11842
+ });
11843
+ const [r, g, b] = components ? convertColorSpaceComponentsToRgb(params.cosDoc, colorSpace, "DeviceRGB", components, activeResources) : [0, 0, 0];
11844
+ const transfer = params.cosDoc.resolve(dictGet(mask, "TR"));
11845
+ const transferMap = transfer?.kind === "dict" || transfer?.kind === "stream" ? Uint8Array.from({ length: 256 }, (_, i) => Math.floor(kClamp(Math.fround(evalShadingFunctionToComponents(params.cosDoc, transfer, Math.fround(i / 255))[0] ?? 0)) * 255)) : void 0;
11846
+ st.softMask = { subtype: subtype.decoded, operations: captured, backdrop: { r, g, b }, transferMap };
11847
+ }
11848
+ }
11805
11849
  const bmNode = params.cosDoc.resolve(dictGet(gsDict, "BM"));
11806
11850
  if (bmNode?.kind === "name") {
11807
11851
  st.blendMode = bmNode.decoded === "Compatible" ? "Normal" : bmNode.decoded;
@@ -11888,6 +11932,84 @@ function evaluateContentStreamToDisplayList(params) {
11888
11932
  ] }], void 0, void 0, resources, activeFonts, depth);
11889
11933
  stateStack.pop();
11890
11934
  };
11935
+ const paintForm = (form, activeResources, activeFonts, depth, mcid, actualText, maskGroup = false) => {
11936
+ const st = curState();
11937
+ const group = params.cosDoc.resolveDict(dictGet(form.dict, "Group"));
11938
+ const groupType = group ? params.cosDoc.resolve(dictGet(group, "S")) : void 0;
11939
+ const isolated = group ? params.cosDoc.resolve(dictGet(group, "I")) : void 0;
11940
+ const isolate = !maskGroup && groupType?.kind === "name" && groupType.decoded === "Transparency" && isolated?.kind === "boolean" && isolated.value;
11941
+ const formStreamBytes = params.cosDoc.decodeStream(form);
11942
+ const formNodes = parseContentStream(formStreamBytes);
11943
+ const formResDict = params.cosDoc.resolveDict(dictGet(form.dict, "Resources")) ?? activeResources;
11944
+ const formFonts = new Map(activeFonts);
11945
+ for (const [k, v] of resolvePageFonts(params.cosDoc, formResDict).entries()) {
11946
+ formFonts.set(k, v);
11947
+ }
11948
+ let nextCtm = [...st.ctm];
11949
+ const matArr = params.cosDoc.resolveArray(dictGet(form.dict, "Matrix"));
11950
+ if (matArr && matArr.items.length >= 6) {
11951
+ const mn = (idx, fb = 0) => {
11952
+ const resolved = params.cosDoc.resolve(matArr.items[idx]);
11953
+ return resolved?.kind === "number" ? resolved.value : fb;
11954
+ };
11955
+ const formMat = [mn(0, 1), mn(1, 0), mn(2, 0), mn(3, 1), mn(4, 0), mn(5, 0)];
11956
+ nextCtm = multiplyMatrices(formMat, nextCtm);
11957
+ }
11958
+ let nextClip = !isolate && st.clipRect ? [...st.clipRect] : void 0;
11959
+ let nextClipPaths = isolate ? void 0 : st.clipPaths;
11960
+ const bboxArr = params.cosDoc.resolveArray(dictGet(form.dict, "BBox"));
11961
+ if (bboxArr && bboxArr.items.length >= 4) {
11962
+ const bn = (idx, fb = 0) => {
11963
+ const resolved = params.cosDoc.resolve(bboxArr.items[idx]);
11964
+ return resolved?.kind === "number" ? resolved.value : fb;
11965
+ };
11966
+ const bx0 = bn(0, 0), by0 = bn(1, 0), bx1 = bn(2, 0), by1 = bn(3, 0);
11967
+ const pts = [
11968
+ transformPoint(nextCtm, bx0, by0),
11969
+ transformPoint(nextCtm, bx1, by0),
11970
+ transformPoint(nextCtm, bx1, by1),
11971
+ transformPoint(nextCtm, bx0, by1)
11972
+ ];
11973
+ nextClipPaths = [...nextClipPaths ?? [], [
11974
+ ...pts.map(([x, y], index) => ({ kind: index === 0 ? "move" : "line", x, y })),
11975
+ { kind: "close" }
11976
+ ]];
11977
+ const fMinX = Math.min(...pts.map((p) => p[0]));
11978
+ const fMinY = Math.min(...pts.map((p) => p[1]));
11979
+ const fMaxX = Math.max(...pts.map((p) => p[0]));
11980
+ const fMaxY = Math.max(...pts.map((p) => p[1]));
11981
+ if (fMaxX > fMinX && fMaxY > fMinY) {
11982
+ nextClip = nextClip ? [
11983
+ Math.max(nextClip[0], fMinX),
11984
+ Math.max(nextClip[1], fMinY),
11985
+ Math.min(nextClip[2], fMaxX),
11986
+ Math.min(nextClip[3], fMaxY)
11987
+ ] : [fMinX, fMinY, fMaxX, fMaxY];
11988
+ }
11989
+ }
11990
+ const parentOperations = capturedOperations;
11991
+ const children = [];
11992
+ const nextState = { ...st, ctm: nextCtm };
11993
+ if (isolate) {
11994
+ capturedOperations = children;
11995
+ nextState.fillAlpha = nextState.strokeAlpha = 1;
11996
+ nextState.blendMode = "Normal";
11997
+ nextState.softMask = void 0;
11998
+ delete nextState.clipImages;
11999
+ delete nextState.clipPaths;
12000
+ delete nextState.clipRect;
12001
+ }
12002
+ if (nextClip) nextState.clipRect = nextClip;
12003
+ if (nextClipPaths) nextState.clipPaths = nextClipPaths;
12004
+ stateStack.push(nextState);
12005
+ try {
12006
+ walkNodes(formNodes, mcid, actualText, formResDict, formFonts, depth + 1);
12007
+ } finally {
12008
+ stateStack.pop();
12009
+ capturedOperations = parentOperations;
12010
+ }
12011
+ if (isolate) emit({ kind: "group", value: { operations: children, alpha: st.fillAlpha, blendMode: st.blendMode, clipRect: st.clipRect } });
12012
+ };
11891
12013
  const walkNodes = (nodes, mcid, actualText, activeResources = params.resourcesDict, activeFonts = fonts, depth = 0) => {
11892
12014
  for (const node of nodes) {
11893
12015
  switch (node.kind) {
@@ -11932,7 +12054,7 @@ function evaluateContentStreamToDisplayList(params) {
11932
12054
  break;
11933
12055
  }
11934
12056
  case "state-op": {
11935
- applyStateOperator(curState(), node.operator, node.operands, activeResources);
12057
+ applyStateOperator(curState(), node.operator, node.operands, activeResources, activeFonts, depth);
11936
12058
  break;
11937
12059
  }
11938
12060
  case "path-op": {
@@ -12218,53 +12340,7 @@ function evaluateContentStreamToDisplayList(params) {
12218
12340
  ...st.clipRect ? { clipRect: [...st.clipRect] } : {}
12219
12341
  }, patternMask, activeResources, activeFonts, depth);
12220
12342
  } else if (sub === "Form" && depth < 8) {
12221
- const formStreamBytes = params.cosDoc.decodeStream(xobjNode);
12222
- const formNodes = parseContentStream(formStreamBytes);
12223
- const formResDict = params.cosDoc.resolveDict(dictGet(xobjNode.dict, "Resources")) ?? activeResources;
12224
- const formFonts = new Map(activeFonts);
12225
- for (const [k, v] of resolvePageFonts(params.cosDoc, formResDict).entries()) {
12226
- formFonts.set(k, v);
12227
- }
12228
- let nextCtm = [...st.ctm];
12229
- const matArr = params.cosDoc.resolveArray(dictGet(xobjNode.dict, "Matrix"));
12230
- if (matArr && matArr.items.length >= 6) {
12231
- const mn = (idx, fb = 0) => {
12232
- const resolved = params.cosDoc.resolve(matArr.items[idx]);
12233
- return resolved?.kind === "number" ? resolved.value : fb;
12234
- };
12235
- const formMat = [mn(0, 1), mn(1, 0), mn(2, 0), mn(3, 1), mn(4, 0), mn(5, 0)];
12236
- nextCtm = multiplyMatrices(formMat, nextCtm);
12237
- }
12238
- let nextClip = st.clipRect ? [...st.clipRect] : void 0;
12239
- const bboxArr = params.cosDoc.resolveArray(dictGet(xobjNode.dict, "BBox"));
12240
- if (bboxArr && bboxArr.items.length >= 4) {
12241
- const bn = (idx, fb = 0) => {
12242
- const resolved = params.cosDoc.resolve(bboxArr.items[idx]);
12243
- return resolved?.kind === "number" ? resolved.value : fb;
12244
- };
12245
- const bx0 = bn(0, 0), by0 = bn(1, 0), bx1 = bn(2, 0), by1 = bn(3, 0);
12246
- const pts = [
12247
- transformPoint(nextCtm, bx0, by0),
12248
- transformPoint(nextCtm, bx1, by0),
12249
- transformPoint(nextCtm, bx0, by1),
12250
- transformPoint(nextCtm, bx1, by1)
12251
- ];
12252
- const fMinX = Math.min(...pts.map((p) => p[0]));
12253
- const fMinY = Math.min(...pts.map((p) => p[1]));
12254
- const fMaxX = Math.max(...pts.map((p) => p[0]));
12255
- const fMaxY = Math.max(...pts.map((p) => p[1]));
12256
- if (fMaxX > fMinX && fMaxY > fMinY) {
12257
- nextClip = nextClip ? [
12258
- Math.max(nextClip[0], fMinX),
12259
- Math.max(nextClip[1], fMinY),
12260
- Math.min(nextClip[2], fMaxX),
12261
- Math.min(nextClip[3], fMaxY)
12262
- ] : [fMinX, fMinY, fMaxX, fMaxY];
12263
- }
12264
- }
12265
- stateStack.push({ ...st, ctm: nextCtm, ...nextClip ? { clipRect: nextClip } : {} });
12266
- walkNodes(formNodes, mcid, actualText, formResDict, formFonts, depth + 1);
12267
- if (stateStack.length > 1) stateStack.pop();
12343
+ paintForm(xobjNode, activeResources, activeFonts, depth, mcid, actualText);
12268
12344
  }
12269
12345
  }
12270
12346
  }
@@ -12483,7 +12559,7 @@ function evaluateContentStreamToDisplayList(params) {
12483
12559
  st.textRenderMode = cmd.mode;
12484
12560
  break;
12485
12561
  case "state-op":
12486
- applyStateOperator(st, cmd.operator, cmd.operands, activeResources);
12562
+ applyStateOperator(st, cmd.operator, cmd.operands, activeResources, activeFonts, depth);
12487
12563
  break;
12488
12564
  case "show-text":
12489
12565
  emitTokenBytes(cmd.token.bytes);
@@ -13961,6 +14037,47 @@ function flattenCubic(x1, y1, x2, y2, x3, y3, x4, y4) {
13961
14037
  points.push([x4, y4]);
13962
14038
  return points;
13963
14039
  }
14040
+ function sampleImageLinear(image, x, y, out) {
14041
+ x = Math.max(0, Math.min(image.width - 1, x));
14042
+ y = Math.max(0, Math.min(image.height - 1, y));
14043
+ const x0 = Math.floor(x), y0 = Math.floor(y);
14044
+ const fx = x - x0, fy = y - y0;
14045
+ out.fill(0);
14046
+ for (let j = 0; j < 2; j++) {
14047
+ for (let i = 0; i < 2; i++) {
14048
+ const offset = (Math.min(image.height - 1, y0 + j) * image.width + Math.min(image.width - 1, x0 + i)) * 4;
14049
+ const alpha = image.data[offset + 3] * (i ? fx : 1 - fx) * (j ? fy : 1 - fy);
14050
+ out[0] = out[0] + image.data[offset] * alpha;
14051
+ out[1] = out[1] + image.data[offset + 1] * alpha;
14052
+ out[2] = out[2] + image.data[offset + 2] * alpha;
14053
+ out[3] = out[3] + alpha;
14054
+ }
14055
+ }
14056
+ if (out[3] > 0) {
14057
+ out[0] = out[0] / out[3];
14058
+ out[1] = out[1] / out[3];
14059
+ out[2] = out[2] / out[3];
14060
+ }
14061
+ }
14062
+ function downscaleImage(image, widthScale, heightScale) {
14063
+ const sample = new Float64Array(4);
14064
+ while (widthScale > 2 && image.width > 1 || heightScale > 2 && image.height > 1) {
14065
+ const width = widthScale > 2 ? Math.ceil(image.width / 2) : image.width;
14066
+ const height = heightScale > 2 ? Math.ceil(image.height / 2) : image.height;
14067
+ const data = new Uint8Array(width * height * 4);
14068
+ for (let y = 0; y < height; y++) {
14069
+ for (let x = 0; x < width; x++) {
14070
+ sampleImageLinear(image, (x + 0.5) * image.width / width - 0.5, (y + 0.5) * image.height / height - 0.5, sample);
14071
+ const offset = (y * width + x) * 4;
14072
+ for (let c = 0; c < 4; c++) data[offset + c] = Math.round(sample[c]);
14073
+ }
14074
+ }
14075
+ widthScale /= image.width / width;
14076
+ heightScale /= image.height / height;
14077
+ image = { width, height, data };
14078
+ }
14079
+ return image;
14080
+ }
13964
14081
  function decodePng(pngBytes) {
13965
14082
  if (pngBytes.length < 24 || pngBytes[0] !== 137 || pngBytes[1] !== 80 || pngBytes[2] !== 78 || pngBytes[3] !== 71) {
13966
14083
  throw new Error("Invalid PNG signature");
@@ -14558,6 +14675,30 @@ function paintOperations(displayList) {
14558
14675
  ...displayList.glyphs.map((value) => ({ kind: "glyph", value }))
14559
14676
  ];
14560
14677
  }
14678
+ function renderSoftMask(mask, displayList, scale) {
14679
+ const bitmap = renderDisplayListToBitmap({
14680
+ ...displayList,
14681
+ rotation: 0,
14682
+ glyphs: [],
14683
+ paths: [],
14684
+ images: [],
14685
+ operations: mask.operations
14686
+ }, { scale, transparent: true });
14687
+ const { data } = bitmap;
14688
+ for (let i = 0; i < data.length; i += 4) {
14689
+ let value = data[i + 3];
14690
+ if (mask.subtype === "Luminosity") {
14691
+ const alpha = value / 255;
14692
+ const r = Math.round(data[i] * alpha + mask.backdrop.r * 255 * (1 - alpha));
14693
+ const g = Math.round(data[i + 1] * alpha + mask.backdrop.g * 255 * (1 - alpha));
14694
+ const b = Math.round(data[i + 2] * alpha + mask.backdrop.b * 255 * (1 - alpha));
14695
+ value = Math.round(pdfLum(r, g, b));
14696
+ }
14697
+ data[i] = data[i + 1] = data[i + 2] = 0;
14698
+ data[i + 3] = mask.transferMap?.[value] ?? value;
14699
+ }
14700
+ return bitmap;
14701
+ }
14561
14702
  function renderDisplayListToBitmap(displayList, options = {}) {
14562
14703
  const baseScale = options.scale ?? (options.dpi ? options.dpi / 72 : 1.5);
14563
14704
  const scaleX = options.dpiX !== void 0 ? options.dpiX / 72 : baseScale;
@@ -14581,10 +14722,28 @@ function renderDisplayListToBitmap(displayList, options = {}) {
14581
14722
  const aaTxt = options.antialiasText !== false;
14582
14723
  let cachedClips;
14583
14724
  let cachedClipMask;
14725
+ let cachedSoftMask;
14726
+ let cachedSoftMaskPixels;
14584
14727
  const imageClipMasks = /* @__PURE__ */ new Map();
14585
14728
  for (const original of paintOperations(displayList)) {
14586
14729
  if (original.kind === "glyph" && (original.value.renderMode === 3 || !original.value.outline && !original.value.unicode.trim())) continue;
14587
- const operation = original.kind === "glyph" ? { kind: "path", value: glyphPaint(original.value) } : original;
14730
+ let operation = original.kind === "glyph" ? { kind: "path", value: glyphPaint(original.value) } : original;
14731
+ if (operation.kind === "group") {
14732
+ const group = operation.value;
14733
+ const bitmap = renderDisplayListToBitmap({ ...displayList, rotation: 0, operations: group.operations }, { ...options, scale, dpiX: void 0, dpiY: void 0, cropRect: void 0, transparent: true });
14734
+ for (let i = 3; i < bitmap.data.length; i += 4) bitmap.data[i] = Math.round(bitmap.data[i] * group.alpha);
14735
+ operation = { kind: "image", value: {
14736
+ name: "TransparencyGroup",
14737
+ width: bitmap.width,
14738
+ height: bitmap.height,
14739
+ decodedRgba: bitmap.data,
14740
+ matrix: [bitmap.width / scale, 0, 0, bitmap.height / scale, 0, displayList.height - bitmap.height / scale],
14741
+ colorSpace: "DeviceRGB",
14742
+ bitsPerComponent: 8,
14743
+ blendMode: group.blendMode,
14744
+ clipRect: group.clipRect
14745
+ } };
14746
+ }
14588
14747
  const clips = original.value.clipPaths;
14589
14748
  let clipMask = clips && clips === cachedClips ? cachedClipMask : void 0;
14590
14749
  if (clips && !clipMask) {
@@ -14598,6 +14757,19 @@ function renderDisplayListToBitmap(displayList, options = {}) {
14598
14757
  cachedClips = clips;
14599
14758
  cachedClipMask = clipMask;
14600
14759
  }
14760
+ const softMask = original.value.softMask;
14761
+ if (softMask) {
14762
+ if (softMask !== cachedSoftMask) {
14763
+ cachedSoftMaskPixels = renderSoftMask(softMask, displayList, scale).data;
14764
+ cachedSoftMask = softMask;
14765
+ }
14766
+ if (clipMask) {
14767
+ clipMask = clipMask.slice();
14768
+ for (let i = 3; i < clipMask.length; i += 4) clipMask[i] = Math.round(clipMask[i] * cachedSoftMaskPixels[i] / 255);
14769
+ } else {
14770
+ clipMask = cachedSoftMaskPixels;
14771
+ }
14772
+ }
14601
14773
  const imageClips = original.value.clipImages;
14602
14774
  if (imageClips) {
14603
14775
  let imageMask = imageClipMasks.get(imageClips);
@@ -14701,6 +14873,17 @@ function renderDisplayListToBitmap(displayList, options = {}) {
14701
14873
  const maxPx = Math.min(width - 1, clipMaxPx, Math.ceil(Math.max(...cornersPx)) - 1);
14702
14874
  const minPy = Math.max(0, clipMinPy, Math.floor(Math.min(...cornersPy)));
14703
14875
  const maxPy = Math.min(height - 1, clipMaxPy, Math.ceil(Math.max(...cornersPy)) - 1);
14876
+ if (minPx > maxPx || minPy > maxPy) continue;
14877
+ const source = downscaleImage(
14878
+ { width: img.width, height: img.height, data: img.decodedRgba },
14879
+ Math.hypot(d, c) * img.width / Math.abs(det * scale),
14880
+ Math.hypot(b, a) * img.height / Math.abs(det * scale)
14881
+ );
14882
+ const ax = a * scale / img.width, ay = b * scale / img.width;
14883
+ const bx = c * scale / img.height, by = d * scale / img.height;
14884
+ const s1 = ax * ax + ay * ay, s2 = bx * bx + by * by, cross = ax * bx + ay * by;
14885
+ const smooth = Math.fround((s1 + s2 + Math.hypot(s1 - s2, 2 * cross)) / 2) <= 1;
14886
+ const sample = new Float64Array(4);
14704
14887
  for (let py = minPy; py <= maxPy; py++) {
14705
14888
  const yPdf = displayList.height - (py + 0.5) / scale;
14706
14889
  if (img.clipRect && (yPdf < img.clipRect[1] || yPdf > img.clipRect[3])) continue;
@@ -14712,9 +14895,14 @@ function renderDisplayListToBitmap(displayList, options = {}) {
14712
14895
  const u = (d * dxPdf - c * dyPdf) / det;
14713
14896
  const v = (-b * dxPdf + a * dyPdf) / det;
14714
14897
  if (u < 0 || u > 1 || v < 0 || v > 1) continue;
14715
- const sx = Math.min(img.width - 1, Math.max(0, Math.floor(u * img.width)));
14716
- const sy = Math.min(img.height - 1, Math.max(0, Math.floor((1 - v) * img.height)));
14717
- const sIdx = (sy * img.width + sx) * 4;
14898
+ if (smooth) {
14899
+ sampleImageLinear(source, u * source.width - 0.5, (1 - v) * source.height - 0.5, sample);
14900
+ } else {
14901
+ const sx = Math.min(source.width - 1, Math.max(0, Math.floor(u * source.width)));
14902
+ const sy = Math.min(source.height - 1, Math.max(0, Math.floor((1 - v) * source.height)));
14903
+ const sIdx = (sy * source.width + sx) * 4;
14904
+ for (let c2 = 0; c2 < 4; c2++) sample[c2] = source.data[sIdx + c2];
14905
+ }
14718
14906
  blendPixel(
14719
14907
  rgba,
14720
14908
  width,
@@ -14722,11 +14910,11 @@ function renderDisplayListToBitmap(displayList, options = {}) {
14722
14910
  px,
14723
14911
  py,
14724
14912
  {
14725
- r: img.decodedRgba[sIdx] / 255,
14726
- g: img.decodedRgba[sIdx + 1] / 255,
14727
- b: img.decodedRgba[sIdx + 2] / 255
14913
+ r: sample[0] / 255,
14914
+ g: sample[1] / 255,
14915
+ b: sample[2] / 255
14728
14916
  },
14729
- img.decodedRgba[sIdx + 3] / 255,
14917
+ sample[3] / 255,
14730
14918
  img.blendMode,
14731
14919
  clipMask
14732
14920
  );
@@ -43344,3 +43532,8 @@ export {
43344
43532
  * Adapted from PDFium third_party/agg23/agg_curves.cpp, curve4_div.
43345
43533
  * See THIRD_PARTY_NOTICES.md for the pinned revision and adaptations.
43346
43534
  */
43535
+ /*!
43536
+ * Staged image reduction adapted from Mozilla PDF.js CanvasGraphics._scaleImage.
43537
+ * Copyright 2012 Mozilla Foundation. Licensed under Apache-2.0.
43538
+ * See THIRD_PARTY_NOTICES.md for the source revision and adaptations.
43539
+ */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-bash",
3
- "version": "0.1.761",
3
+ "version": "0.1.763",
4
4
  "description": "Extensible virtual shell with pluggable filesystems and streaming commands",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -841,7 +841,7 @@
841
841
  "access": "public"
842
842
  },
843
843
  "dependencies": {
844
- "@poe-platform/safe-fs": "0.1.761",
844
+ "@poe-platform/safe-fs": "0.1.763",
845
845
  "pdf-lib": "1.17.1",
846
846
  "@noble/hashes": "2.4.0",
847
847
  "pako": "3.0.1",
@@ -349,6 +349,29 @@ The source fixture and hashes are recorded in `src/fixtures/SOURCES.md`.
349
349
  The one-pixel gradient expectation is independently verified by Poppler and
350
350
  MuPDF; PDF.js's `paintSolidColorImageMask` optimization loses that pattern.
351
351
 
352
+ `src/render/image-sampling.ts` adapts `CanvasGraphics._scaleImage` from the same
353
+ pinned PDF.js revision (Copyright 2012 Mozilla Foundation, Apache-2.0). The
354
+ successive half-size canvas draws use local bilinear sampling with premultiplied
355
+ alpha; only the current reduction level is retained. Source-axis footprints
356
+ come from the inverse image transform to preserve rotated anisotropic reductions.
357
+ Final downscaling uses smoothing, as in `getImageSmoothingEnabled`, with one
358
+ device pixel as the enlargement threshold. `image-downsampling.test.ts` adds
359
+ memory-only stripe, alpha, transform, stencil, and save/reopen regressions;
360
+ the unchanged upstream issue13372 fixture also covers reduced patterned masks.
361
+
362
+ ExtGState soft-mask evaluation and rendering follow PDF.js `handleSMask`,
363
+ `beginGroup`, `_prepareSMaskCanvas`, and `_bakeSMaskCanvas` at the same revision
364
+ (Mozilla Foundation, Apache-2.0). The adapter captures page-coordinate mask
365
+ paints, resets group alpha/blend/soft-mask state, converts the luminosity
366
+ backdrop before filtering, and builds a 256-entry transfer table. Isolated
367
+ Forms retain their own paint groups so outer opacity is applied once.
368
+ Bitmap masks retain only the current prepared surface; SVG masks use the
369
+ export resolution while isolated Form contents remain vector groups.
370
+ `soft-mask.test.ts` ports four unchanged upstream equality fixtures, recorded
371
+ in `src/fixtures/SOURCES.md`, and adds local alpha/transform/group tests plus
372
+ the original issue17069 signature regression. Soft-mask evaluation is bounded
373
+ by the existing eight-level Form limit; mask-only text is excluded from extraction.
374
+
352
375
  Malformed dictionary-key recovery follows PDF.js `Parser.getObj`, skipping
353
376
  stray non-Name tokens only in local repair mode. Damaged optional ToUnicode
354
377
  streams follow `PartialEvaluator.readToUnicode` error recovery while retaining