@poe-platform/safe-bash 0.1.762 → 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 (28) hide show
  1. package/dist/image-ast/index.browser.js +186 -55
  2. package/dist/image-ast/index.js +186 -55
  3. package/dist/pdf-ast/ast.d.ts +23 -0
  4. package/dist/pdf-ast/index.js +273 -91
  5. package/dist/safe-bash/command-chunks/{chunk-D4GXXV65.js → chunk-4XHONGIU.js} +2 -2
  6. package/dist/safe-bash/command-chunks/{chunk-P3Q2QSCA.js → chunk-S3G2B4OX.js} +186 -55
  7. package/dist/safe-bash/command-chunks/{chunk-F7MJRXQ2.js → chunk-S5EX6UP5.js} +1 -1
  8. package/dist/safe-bash/core.browser.js +1 -1
  9. package/dist/safe-bash/shell/runtime.js +1 -1
  10. package/dist/safe-bash-checksum-engine/index.d.ts +1 -1
  11. package/dist/safe-bash-checksum-engine/index.js +5 -1
  12. package/dist/safe-bash-command-ffmpeg/index.js +7486 -7432
  13. package/dist/safe-bash-command-gh/index.js +174 -105
  14. package/dist/safe-bash-command-imagemagick/index.js +2 -2
  15. package/dist/safe-bash-command-pandoc/command.js +3 -3
  16. package/dist/safe-bash-command-pandoc/index.js +3 -3
  17. package/dist/safe-bash-command-pdfimages/index.js +186 -55
  18. package/dist/safe-bash-command-pdfinfo/index.js +273 -91
  19. package/dist/safe-bash-command-pdftk/index.js +186 -55
  20. package/dist/safe-bash-command-pdftoppm/index.js +273 -91
  21. package/dist/safe-bash-command-pdftotext/index.js +186 -55
  22. package/dist/safe-bash-command-qpdf/index.js +186 -55
  23. package/dist/safe-bash-command-sips/index.js +2 -2
  24. package/dist/safe-bash-command-soffice/index.js +1 -1
  25. package/dist/safe-bash-command-ssconvert/index.js +249 -58
  26. package/dist/safe-bash-command-wkhtmltopdf/index.js +186 -55
  27. package/package.json +2 -2
  28. package/third-party/pdf-ast/THIRD_PARTY_NOTICES.md +13 -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);
@@ -14599,6 +14675,30 @@ function paintOperations(displayList) {
14599
14675
  ...displayList.glyphs.map((value) => ({ kind: "glyph", value }))
14600
14676
  ];
14601
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
+ }
14602
14702
  function renderDisplayListToBitmap(displayList, options = {}) {
14603
14703
  const baseScale = options.scale ?? (options.dpi ? options.dpi / 72 : 1.5);
14604
14704
  const scaleX = options.dpiX !== void 0 ? options.dpiX / 72 : baseScale;
@@ -14622,10 +14722,28 @@ function renderDisplayListToBitmap(displayList, options = {}) {
14622
14722
  const aaTxt = options.antialiasText !== false;
14623
14723
  let cachedClips;
14624
14724
  let cachedClipMask;
14725
+ let cachedSoftMask;
14726
+ let cachedSoftMaskPixels;
14625
14727
  const imageClipMasks = /* @__PURE__ */ new Map();
14626
14728
  for (const original of paintOperations(displayList)) {
14627
14729
  if (original.kind === "glyph" && (original.value.renderMode === 3 || !original.value.outline && !original.value.unicode.trim())) continue;
14628
- 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
+ }
14629
14747
  const clips = original.value.clipPaths;
14630
14748
  let clipMask = clips && clips === cachedClips ? cachedClipMask : void 0;
14631
14749
  if (clips && !clipMask) {
@@ -14639,6 +14757,19 @@ function renderDisplayListToBitmap(displayList, options = {}) {
14639
14757
  cachedClips = clips;
14640
14758
  cachedClipMask = clipMask;
14641
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
+ }
14642
14773
  const imageClips = original.value.clipImages;
14643
14774
  if (imageClips) {
14644
14775
  let imageMask = imageClipMasks.get(imageClips);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-bash",
3
- "version": "0.1.762",
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.762",
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",
@@ -359,6 +359,19 @@ device pixel as the enlargement threshold. `image-downsampling.test.ts` adds
359
359
  memory-only stripe, alpha, transform, stencil, and save/reopen regressions;
360
360
  the unchanged upstream issue13372 fixture also covers reduced patterned masks.
361
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
+
362
375
  Malformed dictionary-key recovery follows PDF.js `Parser.getObj`, skipping
363
376
  stray non-Name tokens only in local repair mode. Damaged optional ToUnicode
364
377
  streams follow `PartialEvaluator.readToUnicode` error recovery while retaining