@poe-platform/safe-bash 0.1.780 → 0.1.781

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.
@@ -3456,7 +3456,7 @@ function cosString(value, span) {
3456
3456
  if (typeof value === "string") {
3457
3457
  let needsUtf16 = false;
3458
3458
  for (let i = 0; i < value.length; i++) {
3459
- if (value.charCodeAt(i) > 255) {
3459
+ if (!PDF_DOC_ENCODING_REVERSE.has(value.charCodeAt(i))) {
3460
3460
  needsUtf16 = true;
3461
3461
  break;
3462
3462
  }
@@ -3474,7 +3474,7 @@ function cosString(value, span) {
3474
3474
  }
3475
3475
  const bytes = new Uint8Array(value.length);
3476
3476
  for (let i = 0; i < value.length; i++) {
3477
- bytes[i] = value.charCodeAt(i) & 255;
3477
+ bytes[i] = PDF_DOC_ENCODING_REVERSE.get(value.charCodeAt(i));
3478
3478
  }
3479
3479
  return { kind: "string", format: "literal", bytes, span };
3480
3480
  }
@@ -11454,6 +11454,7 @@ function evaluateContentStreamToDisplayList(params) {
11454
11454
  };
11455
11455
  const initialState = {
11456
11456
  ctm: [1, 0, 0, 1, 0, 0],
11457
+ initialCtm: [1, 0, 0, 1, 0, 0],
11457
11458
  strokeColor: { r: 0, g: 0, b: 0 },
11458
11459
  fillColor: { r: 0, g: 0, b: 0 },
11459
11460
  strokeAlpha: 1,
@@ -11751,6 +11752,116 @@ function evaluateContentStreamToDisplayList(params) {
11751
11752
  ] }], void 0, void 0, resources, activeFonts, depth);
11752
11753
  stateStack.pop();
11753
11754
  };
11755
+ const paintPattern = (segments, fillRule, resources, activeFonts, depth, mcid, actualText) => {
11756
+ const st = curState(), doc = params.cosDoc;
11757
+ if (!st.fillPatternName || !doc || !resources) return false;
11758
+ if (depth >= 8) throw new PdfError("E_LIMIT", "Pattern nesting exceeds the form depth limit");
11759
+ const patterns = doc.resolveDict(dictGet(resources, "Pattern"));
11760
+ const pattern = patterns && doc.resolve(dictGet(patterns, st.fillPatternName));
11761
+ const dict = pattern?.kind === "stream" ? pattern.dict : pattern?.kind === "dict" ? pattern : void 0;
11762
+ if (!dict) return false;
11763
+ const nums = (key, fallback) => {
11764
+ const array = doc.resolveArray(dictGet(dict, key));
11765
+ return array ? array.items.map((item, i) => {
11766
+ const value = doc.resolve(item);
11767
+ return value?.kind === "number" ? value.value : fallback[i] ?? 0;
11768
+ }) : fallback;
11769
+ };
11770
+ let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
11771
+ const include = (x, y) => {
11772
+ x0 = Math.min(x0, x);
11773
+ y0 = Math.min(y0, y);
11774
+ x1 = Math.max(x1, x);
11775
+ y1 = Math.max(y1, y);
11776
+ };
11777
+ for (const segment of segments) {
11778
+ if (segment.kind === "close") continue;
11779
+ include(segment.x, segment.y);
11780
+ if (segment.kind === "cubic") {
11781
+ include(segment.x1, segment.y1);
11782
+ include(segment.x2, segment.y2);
11783
+ }
11784
+ if (segment.kind === "rect") include(segment.x + segment.width, segment.y + segment.height);
11785
+ }
11786
+ const [originX, originY] = params.origin ?? [0, 0];
11787
+ const clip = st.clipRect ?? [originX, originY, originX + params.width, originY + params.height];
11788
+ const bounds = [Math.max(x0, clip[0]), Math.max(y0, clip[1]), Math.min(x1, clip[2]), Math.min(y1, clip[3])];
11789
+ if (!(bounds[2] > bounds[0] && bounds[3] > bounds[1])) return true;
11790
+ const matrix = multiplyMatrices(nums("Matrix", [1, 0, 0, 1, 0, 0]), st.initialCtm);
11791
+ const type = doc.resolve(dictGet(dict, "PatternType"));
11792
+ stateStack.push({ ...st, clipRect: bounds, clipPaths: [...st.clipPaths ?? [], { segments, fillRule }] });
11793
+ try {
11794
+ if (type?.kind === "number" && type.value === 2) {
11795
+ const shading = doc.resolve(dictGet(dict, "Shading"));
11796
+ const shadingDict = shading?.kind === "stream" ? shading.dict : shading?.kind === "dict" ? shading : void 0;
11797
+ if (!shadingDict) return false;
11798
+ const image = renderShadingDictToImage(
11799
+ doc,
11800
+ shadingDict,
11801
+ matrix,
11802
+ bounds,
11803
+ st.fillAlpha,
11804
+ "PatternShading_" + st.fillPatternName,
11805
+ bounds,
11806
+ shading?.kind === "stream" ? shading : void 0,
11807
+ st.blendMode
11808
+ );
11809
+ if (image) emit({ kind: "image", value: image });
11810
+ return !!image;
11811
+ }
11812
+ if (pattern?.kind !== "stream") return false;
11813
+ const xStepNode = doc.resolve(dictGet(dict, "XStep"));
11814
+ const yStepNode = doc.resolve(dictGet(dict, "YStep"));
11815
+ const xStep = xStepNode?.kind === "number" ? Math.abs(xStepNode.value) : 0;
11816
+ const yStep = yStepNode?.kind === "number" ? Math.abs(yStepNode.value) : 0;
11817
+ const determinant = matrix[0] * matrix[3] - matrix[1] * matrix[2];
11818
+ if (!xStep || !yStep || !determinant) return true;
11819
+ const inverse = [
11820
+ matrix[3] / determinant,
11821
+ -matrix[1] / determinant,
11822
+ -matrix[2] / determinant,
11823
+ matrix[0] / determinant,
11824
+ (matrix[2] * matrix[5] - matrix[3] * matrix[4]) / determinant,
11825
+ (matrix[1] * matrix[4] - matrix[0] * matrix[5]) / determinant
11826
+ ];
11827
+ const corners = [[bounds[0], bounds[1]], [bounds[2], bounds[1]], [bounds[2], bounds[3]], [bounds[0], bounds[3]]].map(([x, y]) => transformPoint(inverse, x, y));
11828
+ const box = nums("BBox", [0, 0, xStep, yStep]);
11829
+ const ix0 = Math.floor((Math.min(...corners.map((p) => p[0])) - box[2]) / xStep) + 1;
11830
+ const ix1 = Math.ceil((Math.max(...corners.map((p) => p[0])) - box[0]) / xStep) - 1;
11831
+ const iy0 = Math.floor((Math.min(...corners.map((p) => p[1])) - box[3]) / yStep) + 1;
11832
+ const iy1 = Math.ceil((Math.max(...corners.map((p) => p[1])) - box[1]) / yStep) - 1;
11833
+ if ((ix1 - ix0 + 1) * (iy1 - iy0 + 1) > 2e4) throw new PdfError("E_LIMIT", "Pattern tile count exceeds 20000");
11834
+ const nodes = parseContentStream(doc.decodeStream(pattern));
11835
+ const patternResources = doc.resolveDict(dictGet(dict, "Resources")) ?? resources;
11836
+ const patternFonts = new Map(activeFonts);
11837
+ for (const [key, font] of resolvePageFonts(doc, patternResources)) patternFonts.set(key, font);
11838
+ for (let iy = iy0; iy <= iy1; iy++) {
11839
+ for (let ix = ix0; ix <= ix1; ix++) {
11840
+ const tileCtm = multiplyMatrices([1, 0, 0, 1, ix * xStep, iy * yStep], matrix);
11841
+ const points = [[box[0], box[1]], [box[2], box[1]], [box[2], box[3]], [box[0], box[3]]].map(([x, y]) => transformPoint(tileCtm, x, y));
11842
+ const tileClip = [
11843
+ ...points.map(([x, y], i) => ({ kind: i === 0 ? "move" : "line", x, y })),
11844
+ { kind: "close" }
11845
+ ];
11846
+ stateStack.push({
11847
+ ...curState(),
11848
+ fillPatternName: void 0,
11849
+ ctm: tileCtm,
11850
+ initialCtm: tileCtm,
11851
+ clipPaths: [...curState().clipPaths ?? [], tileClip]
11852
+ });
11853
+ try {
11854
+ walkNodes(nodes, mcid, actualText, patternResources, patternFonts, depth + 1);
11855
+ } finally {
11856
+ stateStack.pop();
11857
+ }
11858
+ }
11859
+ }
11860
+ return true;
11861
+ } finally {
11862
+ stateStack.pop();
11863
+ }
11864
+ };
11754
11865
  const paintForm = (form, activeResources, activeFonts, depth, mcid, actualText, maskGroup = false) => {
11755
11866
  const st = curState();
11756
11867
  const group = params.cosDoc.resolveDict(dictGet(form.dict, "Group"));
@@ -11809,7 +11920,7 @@ function evaluateContentStreamToDisplayList(params) {
11809
11920
  }
11810
11921
  const parentOperations = capturedOperations;
11811
11922
  const children = [];
11812
- const nextState = { ...st, ctm: nextCtm };
11923
+ const nextState = { ...st, ctm: nextCtm, initialCtm: nextCtm };
11813
11924
  if (compositeGroup) {
11814
11925
  capturedOperations = children;
11815
11926
  nextState.fillAlpha = nextState.strokeAlpha = 1;
@@ -11967,136 +12078,15 @@ function evaluateContentStreamToDisplayList(params) {
11967
12078
  }
11968
12079
  const isFill = ["f", "F", "f*", "B", "B*", "b", "b*"].includes(node.paint);
11969
12080
  const isStroke = ["S", "s", "B", "B*", "b", "b*"].includes(node.paint);
11970
- let evaluatedFillPattern = false;
11971
- if (isFill && st.fillPatternName && params.cosDoc && activeResources && depth < 6) {
11972
- const patMap = params.cosDoc.resolveDict(dictGet(activeResources, "Pattern"));
11973
- const patNode = patMap ? params.cosDoc.resolve(dictGet(patMap, st.fillPatternName)) : void 0;
11974
- const patDict = patNode?.kind === "stream" ? patNode.dict : patNode?.kind === "dict" ? patNode : void 0;
11975
- const patTypeNode = patDict && params.cosDoc ? params.cosDoc.resolve(dictGet(patDict, "PatternType")) : void 0;
11976
- const patType = patTypeNode?.kind === "number" ? patTypeNode.value : patNode?.kind === "stream" ? 1 : 0;
11977
- if (patDict && patType === 2 && params.cosDoc) {
11978
- let pMinX = Infinity, pMinY = Infinity, pMaxX = -Infinity, pMaxY = -Infinity;
11979
- for (const s of transformedSegments) {
11980
- if (s.kind === "move" || s.kind === "line") {
11981
- pMinX = Math.min(pMinX, s.x);
11982
- pMinY = Math.min(pMinY, s.y);
11983
- pMaxX = Math.max(pMaxX, s.x);
11984
- pMaxY = Math.max(pMaxY, s.y);
11985
- } else if (s.kind === "cubic") {
11986
- pMinX = Math.min(pMinX, s.x1, s.x2, s.x);
11987
- pMinY = Math.min(pMinY, s.y1, s.y2, s.y);
11988
- pMaxX = Math.max(pMaxX, s.x1, s.x2, s.x);
11989
- pMaxY = Math.max(pMaxY, s.y1, s.y2, s.y);
11990
- } else if (s.kind === "rect") {
11991
- pMinX = Math.min(pMinX, s.x);
11992
- pMinY = Math.min(pMinY, s.y);
11993
- pMaxX = Math.max(pMaxX, s.x + s.width);
11994
- pMaxY = Math.max(pMaxY, s.y + s.height);
11995
- }
11996
- }
11997
- if (Number.isFinite(pMinX) && Number.isFinite(pMinY) && pMaxX > pMinX && pMaxY > pMinY) {
11998
- const shading = params.cosDoc.resolve(dictGet(patDict, "Shading"));
11999
- const shDict = shading?.kind === "stream" ? shading.dict : shading?.kind === "dict" ? shading : void 0;
12000
- if (shDict) {
12001
- let patMatrix = [1, 0, 0, 1, 0, 0];
12002
- const pmArr = params.cosDoc.resolveArray(dictGet(patDict, "Matrix"));
12003
- if (pmArr && pmArr.items.length >= 6) {
12004
- const mn = (idx, fb = 0) => {
12005
- const r = params.cosDoc.resolve(pmArr.items[idx]);
12006
- return r?.kind === "number" ? r.value : fb;
12007
- };
12008
- patMatrix = [mn(0, 1), mn(1, 0), mn(2, 0), mn(3, 1), mn(4, 0), mn(5, 0)];
12009
- }
12010
- const patClip = st.clipRect ? [
12011
- Math.max(st.clipRect[0], pMinX),
12012
- Math.max(st.clipRect[1], pMinY),
12013
- Math.min(st.clipRect[2], pMaxX),
12014
- Math.min(st.clipRect[3], pMaxY)
12015
- ] : [pMinX, pMinY, pMaxX, pMaxY];
12016
- const shImg = renderShadingDictToImage(
12017
- params.cosDoc,
12018
- shDict,
12019
- patMatrix,
12020
- patClip,
12021
- st.fillAlpha,
12022
- "PatternShading_" + st.fillPatternName,
12023
- patClip,
12024
- shading?.kind === "stream" ? shading : void 0,
12025
- st.blendMode
12026
- );
12027
- if (shImg) {
12028
- emit({ kind: "image", value: shImg });
12029
- evaluatedFillPattern = true;
12030
- }
12031
- }
12032
- }
12033
- } else if (patNode?.kind === "stream") {
12034
- let pMinX = Infinity, pMinY = Infinity, pMaxX = -Infinity, pMaxY = -Infinity;
12035
- for (const s of transformedSegments) {
12036
- if (s.kind === "move" || s.kind === "line") {
12037
- pMinX = Math.min(pMinX, s.x);
12038
- pMinY = Math.min(pMinY, s.y);
12039
- pMaxX = Math.max(pMaxX, s.x);
12040
- pMaxY = Math.max(pMaxY, s.y);
12041
- } else if (s.kind === "cubic") {
12042
- pMinX = Math.min(pMinX, s.x1, s.x2, s.x);
12043
- pMinY = Math.min(pMinY, s.y1, s.y2, s.y);
12044
- pMaxX = Math.max(pMaxX, s.x1, s.x2, s.x);
12045
- pMaxY = Math.max(pMaxY, s.y1, s.y2, s.y);
12046
- } else if (s.kind === "rect") {
12047
- pMinX = Math.min(pMinX, s.x);
12048
- pMinY = Math.min(pMinY, s.y);
12049
- pMaxX = Math.max(pMaxX, s.x + s.width);
12050
- pMaxY = Math.max(pMaxY, s.y + s.height);
12051
- }
12052
- }
12053
- if (Number.isFinite(pMinX) && Number.isFinite(pMinY) && pMaxX > pMinX && pMaxY > pMinY) {
12054
- const xsNode = params.cosDoc.resolve(dictGet(patNode.dict, "XStep"));
12055
- const ysNode = params.cosDoc.resolve(dictGet(patNode.dict, "YStep"));
12056
- const xStep = xsNode?.kind === "number" && Math.abs(xsNode.value) > 1e-3 ? Math.abs(xsNode.value) : 40;
12057
- const yStep = ysNode?.kind === "number" && Math.abs(ysNode.value) > 1e-3 ? Math.abs(ysNode.value) : 40;
12058
- let patMatrix = [1, 0, 0, 1, 0, 0];
12059
- const pmArr = params.cosDoc.resolveArray(dictGet(patNode.dict, "Matrix"));
12060
- if (pmArr && pmArr.items.length >= 6) {
12061
- const mn = (idx, fb = 0) => {
12062
- const r = params.cosDoc.resolve(pmArr.items[idx]);
12063
- return r?.kind === "number" ? r.value : fb;
12064
- };
12065
- patMatrix = [mn(0, 1), mn(1, 0), mn(2, 0), mn(3, 1), mn(4, 0), mn(5, 0)];
12066
- }
12067
- const tileClip = st.clipRect ? [
12068
- Math.max(st.clipRect[0], pMinX),
12069
- Math.max(st.clipRect[1], pMinY),
12070
- Math.min(st.clipRect[2], pMaxX),
12071
- Math.min(st.clipRect[3], pMaxY)
12072
- ] : [pMinX, pMinY, pMaxX, pMaxY];
12073
- const patNodes = parseContentStream(params.cosDoc.decodeStream(patNode));
12074
- const patResDict = params.cosDoc.resolveDict(dictGet(patNode.dict, "Resources")) ?? activeResources;
12075
- const patFonts = new Map(activeFonts);
12076
- for (const [fk, fv] of resolvePageFonts(params.cosDoc, patResDict).entries()) {
12077
- patFonts.set(fk, fv);
12078
- }
12079
- const ixStart = Math.floor(pMinX / xStep);
12080
- const ixEnd = Math.min(ixStart + 8, Math.ceil(pMaxX / xStep) - 1);
12081
- const iyStart = Math.floor(pMinY / yStep);
12082
- const iyEnd = Math.min(iyStart + 8, Math.ceil(pMaxY / yStep) - 1);
12083
- for (let iy = iyStart; iy <= iyEnd; iy++) {
12084
- for (let ix = ixStart; ix <= ixEnd; ix++) {
12085
- const tileCtm = multiplyMatrices([1, 0, 0, 1, ix * xStep, iy * yStep], patMatrix);
12086
- stateStack.push({
12087
- ...st,
12088
- fillPatternName: void 0,
12089
- ctm: tileCtm,
12090
- clipRect: tileClip
12091
- });
12092
- walkNodes(patNodes, mcid, actualText, patResDict, patFonts, depth + 1);
12093
- if (stateStack.length > 1) stateStack.pop();
12094
- }
12095
- }
12096
- evaluatedFillPattern = true;
12097
- }
12098
- }
12099
- }
12081
+ const evaluatedFillPattern = isFill && paintPattern(
12082
+ transformedSegments,
12083
+ node.paint.includes("*") ? "evenodd" : "nonzero",
12084
+ activeResources,
12085
+ activeFonts,
12086
+ depth,
12087
+ mcid,
12088
+ actualText
12089
+ );
12100
12090
  if (evaluatedFillPattern && !isStroke) {
12101
12091
  applyClip();
12102
12092
  break;
@@ -12297,10 +12287,19 @@ function evaluateContentStreamToDisplayList(params) {
12297
12287
  if (st.textRenderMode >= 4 && st.textRenderMode <= 7) pendingTextClip.push(...transformedGlyphSegs);
12298
12288
  const isFillGlyph = st.textRenderMode === 0 || st.textRenderMode === 2 || st.textRenderMode === 4 || st.textRenderMode === 6;
12299
12289
  const isStrokeGlyph = st.textRenderMode === 1 || st.textRenderMode === 2 || st.textRenderMode === 5 || st.textRenderMode === 6;
12290
+ const patterned = isFillGlyph && paintPattern(
12291
+ transformedGlyphSegs,
12292
+ "nonzero",
12293
+ activeResources,
12294
+ activeFonts,
12295
+ depth,
12296
+ mcid,
12297
+ actualText
12298
+ );
12300
12299
  const paint = {
12301
12300
  segments: transformedGlyphSegs,
12302
- fillColor: isFillGlyph ? st.fillColor : void 0,
12303
- fillAlpha: isFillGlyph ? st.fillAlpha : void 0,
12301
+ fillColor: isFillGlyph && !patterned ? st.fillColor : void 0,
12302
+ fillAlpha: isFillGlyph && !patterned ? st.fillAlpha : void 0,
12304
12303
  strokeColor: isStrokeGlyph ? st.strokeColor : void 0,
12305
12304
  strokeAlpha: isStrokeGlyph ? st.strokeAlpha : void 0,
12306
12305
  strokeWidth: isStrokeGlyph ? st.strokeWidth : 0,
@@ -14443,10 +14442,14 @@ function computePdfBlendRgb(cbR, cbG, cbB, csR, csG, csB, mode) {
14443
14442
  blendChannelSeparable(cbB, csB, mode)
14444
14443
  ];
14445
14444
  }
14446
- function blendPixel(rgba, width, height, px, py, color, alpha, blendMode, clipMask) {
14445
+ function blendPixel(rgba, width, height, px, py, color, alpha, blendMode, clipMask, groupAlpha) {
14447
14446
  if (px < 0 || py < 0 || px >= width || py >= height || alpha <= 0) return;
14448
14447
  const a = Math.min(1, Math.max(0, alpha)) * (clipMask ? clipMask[(py * width + px) * 4 + 3] / 255 : 1);
14449
14448
  const idx = (py * width + px) * 4;
14449
+ if (groupAlpha) {
14450
+ const pixel = py * width + px;
14451
+ groupAlpha[pixel] = a + groupAlpha[pixel] * (1 - a);
14452
+ }
14450
14453
  const csR = Math.min(1, Math.max(0, color.r));
14451
14454
  const csG = Math.min(1, Math.max(0, color.g));
14452
14455
  const csB = Math.min(1, Math.max(0, color.b));
@@ -14578,7 +14581,7 @@ function pathsToEdges(paths, closeSubpaths = false) {
14578
14581
  }
14579
14582
  return edges;
14580
14583
  }
14581
- function fillEdgesScanline4x4(rgba, width, height, edges, color, alpha = 1, fillRule = "nonzero", clipScreen, antialias = true, blendMode, clipMask) {
14584
+ function fillEdgesScanline4x4(rgba, width, height, edges, color, alpha = 1, fillRule = "nonzero", clipScreen, antialias = true, blendMode, clipMask, groupAlpha) {
14582
14585
  if (edges.length === 0) return;
14583
14586
  let minY = Infinity;
14584
14587
  let maxY = -Infinity;
@@ -14644,7 +14647,7 @@ function fillEdgesScanline4x4(rgba, width, height, edges, color, alpha = 1, fill
14644
14647
  const count = rowCounts[px];
14645
14648
  if (count > 0) {
14646
14649
  const cov = antialias ? count / 16 : count >= 8 ? 1 : 0;
14647
- blendPixel(rgba, width, height, px, py, color, cov * alpha, blendMode, clipMask);
14650
+ blendPixel(rgba, width, height, px, py, color, cov * alpha, blendMode, clipMask, groupAlpha);
14648
14651
  }
14649
14652
  }
14650
14653
  }
@@ -14723,19 +14726,16 @@ function hasCompositingEffects(operations, includeSoftMasks = false) {
14723
14726
  return operations.some((operation) => includeSoftMasks && !!operation.value.softMask || !!operation.value.blendMode && operation.value.blendMode !== "Normal" && operation.value.blendMode !== "Compatible" || operation.kind === "group" && hasCompositingEffects(operation.value.operations, includeSoftMasks));
14724
14727
  }
14725
14728
  function needsGroupBackdrop(group) {
14726
- return group.isolated === false && !!group.softMask && hasCompositingEffects(group.operations, true);
14729
+ return group.isolated === false && hasCompositingEffects(group.operations, true);
14727
14730
  }
14728
- function renderDisplayListToBitmap(displayList, options = {}) {
14729
- const baseScale = options.scale ?? (options.dpi ? options.dpi / 72 : 1.5);
14730
- const scaleX = options.dpiX !== void 0 ? options.dpiX / 72 : baseScale;
14731
- const scaleY = options.dpiY !== void 0 ? options.dpiY / 72 : baseScale;
14732
- const scale = scaleX;
14731
+ function renderDisplayListLayer(displayList, options, scale, backdrop) {
14733
14732
  const [originX, originY] = displayList.origin ?? [0, 0];
14734
14733
  const pageTop = originY + displayList.height;
14735
14734
  const toScreen = (x, y) => [(x - originX) * scale, (pageTop - y) * scale];
14736
14735
  const width = Math.max(1, Math.round(displayList.width * scale));
14737
14736
  const height = Math.max(1, Math.round(displayList.height * scale));
14738
- const rgba = new Uint8Array(width * height * 4);
14737
+ const rgba = backdrop ? backdrop.slice() : new Uint8Array(width * height * 4);
14738
+ const groupAlpha = backdrop ? new Float32Array(width * height) : void 0;
14739
14739
  const deferBackground = !options.transparent && hasCompositingEffects(paintOperations(displayList));
14740
14740
  const transparent = options.transparent || deferBackground;
14741
14741
  const bg = options.background ?? { r: 1, g: 1, b: 1 };
@@ -14743,7 +14743,7 @@ function renderDisplayListToBitmap(displayList, options = {}) {
14743
14743
  const bgG = transparent ? 0 : Math.round(bg.g * 255);
14744
14744
  const bgB = transparent ? 0 : Math.round(bg.b * 255);
14745
14745
  const bgA = transparent ? 0 : 255;
14746
- for (let i = 0; i < width * height; i++) {
14746
+ for (let i = 0; !backdrop && i < width * height; i++) {
14747
14747
  rgba[i * 4] = bgR;
14748
14748
  rgba[i * 4 + 1] = bgG;
14749
14749
  rgba[i * 4 + 2] = bgB;
@@ -14761,20 +14761,12 @@ function renderDisplayListToBitmap(displayList, options = {}) {
14761
14761
  let operation = original.kind === "glyph" ? { kind: "path", value: glyphPaint(original.value) } : original;
14762
14762
  if (operation.kind === "group") {
14763
14763
  const group = operation.value;
14764
- let operations = group.operations;
14765
- if (needsGroupBackdrop(group)) {
14766
- operations = [{ kind: "image", value: {
14767
- name: "GroupBackdrop",
14768
- width,
14769
- height,
14770
- decodedRgba: rgba.slice(),
14771
- matrix: [width / scale, 0, 0, height / scale, originX, pageTop - height / scale],
14772
- colorSpace: "DeviceRGB",
14773
- bitsPerComponent: 8,
14774
- ...group.bboxClip ? { clipPaths: [group.bboxClip] } : {}
14775
- } }, ...operations];
14776
- }
14777
- const bitmap = renderDisplayListToBitmap({ ...displayList, rotation: 0, operations }, { ...options, scale, dpiX: void 0, dpiY: void 0, cropRect: void 0, useCropBox: false, transparent: true });
14764
+ const bitmap = renderDisplayListLayer(
14765
+ { ...displayList, operations: group.operations },
14766
+ { ...options, transparent: true },
14767
+ scale,
14768
+ needsGroupBackdrop(group) ? rgba : void 0
14769
+ );
14778
14770
  for (let i = 3; i < bitmap.data.length; i += 4) bitmap.data[i] = Math.round(bitmap.data[i] * group.alpha);
14779
14771
  operation = { kind: "image", value: {
14780
14772
  name: "TransparencyGroup",
@@ -14844,7 +14836,7 @@ function renderDisplayListToBitmap(displayList, options = {}) {
14844
14836
  if (path.fillColor) {
14845
14837
  const edges = pathsToEdges(segmentsToScreenPaths(path.segments, displayList.height, scale, toScreen), true);
14846
14838
  const clipScreen = path.clipRect ? [(path.clipRect[0] - originX) * scale, (pageTop - path.clipRect[3]) * scale, (path.clipRect[2] - originX) * scale, (pageTop - path.clipRect[1]) * scale] : void 0;
14847
- fillEdgesScanline4x4(rgba, width, height, edges, path.fillColor, path.fillAlpha ?? 1, path.fillRule ?? "nonzero", clipScreen, original.kind === "glyph" ? aaTxt : aaVec, path.blendMode, clipMask);
14839
+ fillEdgesScanline4x4(rgba, width, height, edges, path.fillColor, path.fillAlpha ?? 1, path.fillRule ?? "nonzero", clipScreen, original.kind === "glyph" ? aaTxt : aaVec, path.blendMode, clipMask, groupAlpha);
14848
14840
  }
14849
14841
  if (path.strokeColor) {
14850
14842
  const rawSw = path.strokeWidth * scale * (path.strokeMatrix ? Math.hypot(path.strokeMatrix[0], path.strokeMatrix[1]) : 1);
@@ -14862,7 +14854,8 @@ function renderDisplayListToBitmap(displayList, options = {}) {
14862
14854
  clipScreen,
14863
14855
  original.kind === "glyph" ? aaTxt : aaVec,
14864
14856
  path.blendMode,
14865
- clipMask
14857
+ clipMask,
14858
+ groupAlpha
14866
14859
  );
14867
14860
  }
14868
14861
  } else if (operation.kind === "image") {
@@ -14926,12 +14919,31 @@ function renderDisplayListToBitmap(displayList, options = {}) {
14926
14919
  },
14927
14920
  sample[3] / 255,
14928
14921
  img.blendMode,
14929
- clipMask
14922
+ clipMask,
14923
+ groupAlpha
14930
14924
  );
14931
14925
  }
14932
14926
  }
14933
14927
  }
14934
14928
  }
14929
+ if (backdrop && groupAlpha) {
14930
+ for (let i = 0; i < rgba.length; i += 4) {
14931
+ const alpha = groupAlpha[i / 4];
14932
+ if (alpha === 0) {
14933
+ rgba.fill(0, i, i + 4);
14934
+ continue;
14935
+ }
14936
+ const factor = backdrop[i + 3] / 255 * (1 / alpha - 1);
14937
+ for (let channel = 0; channel < 3; channel++) {
14938
+ const color = rgba[i + channel];
14939
+ rgba[i + channel] = Math.max(0, Math.min(
14940
+ 255,
14941
+ Math.round(color + (color - backdrop[i + channel]) * factor)
14942
+ ));
14943
+ }
14944
+ rgba[i + 3] = Math.round(alpha * 255);
14945
+ }
14946
+ }
14935
14947
  if (deferBackground) {
14936
14948
  for (let i = 0; i < rgba.length; i += 4) {
14937
14949
  const alpha = rgba[i + 3] / 255;
@@ -14941,7 +14953,14 @@ function renderDisplayListToBitmap(displayList, options = {}) {
14941
14953
  rgba[i + 3] = 255;
14942
14954
  }
14943
14955
  }
14944
- let result = { width, height, data: rgba };
14956
+ return { width, height, data: rgba };
14957
+ }
14958
+ function renderDisplayListToBitmap(displayList, options = {}) {
14959
+ const baseScale = options.scale ?? (options.dpi ? options.dpi / 72 : 1.5);
14960
+ const scaleX = options.dpiX !== void 0 ? options.dpiX / 72 : baseScale;
14961
+ const scaleY = options.dpiY !== void 0 ? options.dpiY / 72 : baseScale;
14962
+ let result = renderDisplayListLayer(displayList, options, scaleX);
14963
+ const { width, height, data: rgba } = result;
14945
14964
  if (Math.abs(scaleX - scaleY) > 1e-6) {
14946
14965
  const targetW = Math.max(1, Math.round(displayList.width * scaleX));
14947
14966
  const targetH = Math.max(1, Math.round(displayList.height * scaleY));
@@ -15472,6 +15491,7 @@ var CCITT_WHITE_CODES;
15472
15491
  var CCITT_BLACK_CODES;
15473
15492
  var textEncoder;
15474
15493
  var PDF_DOC_ENCODING_MAP;
15494
+ var PDF_DOC_ENCODING_REVERSE;
15475
15495
  var CosByteLexer;
15476
15496
  var MD5_S;
15477
15497
  var MD5_K;
@@ -39928,6 +39948,9 @@ var init_dist = __esm({
39928
39948
  160: 8364,
39929
39949
  173: 65533
39930
39950
  };
39951
+ PDF_DOC_ENCODING_REVERSE = new Map(
39952
+ Array.from({ length: 256 }, (_, byte) => [PDF_DOC_ENCODING_MAP[byte] ?? byte, byte])
39953
+ );
39931
39954
  CosByteLexer = class {
39932
39955
  constructor(bytes, start = 0, end = bytes.length, maxTokenBytes = Infinity, knownCommands) {
39933
39956
  this.knownCommands = knownCommands;
@@ -40160,6 +40183,7 @@ var init_dist = __esm({
40160
40183
  const start = this.pos;
40161
40184
  this.pos++;
40162
40185
  let depth = 1;
40186
+ let recovery;
40163
40187
  const out = [];
40164
40188
  while (this.pos < this.end && depth > 0) {
40165
40189
  const b = this.bytes[this.pos++];
@@ -40194,7 +40218,15 @@ var init_dist = __esm({
40194
40218
  out.push(b);
40195
40219
  } else if (b === 41) {
40196
40220
  depth--;
40197
- if (depth > 0) out.push(b);
40221
+ if (depth > 0) {
40222
+ const next = this.bytes[this.pos];
40223
+ const afterLine = next === 13 && this.bytes[this.pos + 1] === 10 ? this.pos + 2 : this.pos + 1;
40224
+ const delimiter = this.bytes[afterLine];
40225
+ if (!recovery && this.pos + 2 < this.end && (next === 10 || next === 13) && (delimiter === 47 || delimiter === 62)) {
40226
+ recovery = { offset: this.pos, length: out.length };
40227
+ }
40228
+ out.push(b);
40229
+ }
40198
40230
  } else if (b === 13) {
40199
40231
  if (this.pos < this.end && this.bytes[this.pos] === 10) this.pos++;
40200
40232
  out.push(10);
@@ -40205,6 +40237,10 @@ var init_dist = __esm({
40205
40237
  throw new PdfError("E_LIMIT", "PDF literal string exceeds maximum byte length");
40206
40238
  }
40207
40239
  }
40240
+ if (depth > 0 && recovery) {
40241
+ this.pos = recovery.offset;
40242
+ out.length = recovery.length;
40243
+ }
40208
40244
  return {
40209
40245
  kind: "string",
40210
40246
  bytes: Uint8Array.from(out),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-bash",
3
- "version": "0.1.780",
3
+ "version": "0.1.781",
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.780",
844
+ "@poe-platform/safe-fs": "0.1.781",
845
845
  "pdf-lib": "1.17.1",
846
846
  "@noble/hashes": "2.4.0",
847
847
  "pako": "3.0.1",
@@ -404,15 +404,16 @@ and intermediate-surface state reset. Bitmap and SVG exports follow
404
404
  `beginDrawing` in keeping the viewer background outside page blend calculations.
405
405
  `src/render/transparency-group.test.ts` adds local opacity/blend/mask cases and
406
406
  ports the unchanged `transparency_group.pdf` and `bug1873345.pdf` equality
407
- fixtures, with independently obtained reference pixels. Full non-isolated
408
- backdrop compositing for groups with outer effects retains PDF.js's documented
409
- limitation.
407
+ fixtures, with independently obtained reference pixels. Non-isolated backdrop
408
+ compositing now uses PDFBox's group-alpha and backdrop-removal algorithm,
409
+ described below.
410
410
 
411
411
  Masked non-isolated Forms with inner blending or soft-mask effects follow
412
412
  PDF.js `beginGroup`'s `needsBackdropCopy && inSMaskMode` path. The raster adapter
413
413
  copies the existing page within the transformed Form BBox before painting the
414
- group; ordinary outer-opacity groups keep their transparent intermediate.
415
- `src/render/masked-group-backdrop.test.ts` covers this distinction and the
414
+ group. The PDFBox adaptation below extends this to ordinary outer-opacity
415
+ groups and removes the copied backdrop before final compositing.
416
+ `src/render/masked-group-backdrop.test.ts` covers these cases and the
416
417
  unchanged `issue13520.pdf` fixture. SVG uses a raster fallback for pages requiring
417
418
  this backdrop copy; other Form groups retain their vector output.
418
419
 
@@ -504,3 +505,70 @@ or zero when absent. Standard-font widths apply only without an explicit
504
505
  Widths table. `src/fonts/explicit-widths.test.ts` covers Type1, TrueType and
505
506
  Type3 widths with and without ToUnicode, plus the unchanged upstream
506
507
  `Type3WordSpacing.pdf` fixture.
508
+
509
+ # Apache PDFBox rendering regressions
510
+
511
+ `src/render/pdfbox-patterns.test.ts` adapts `TestQuality.java` at revision
512
+ `c4d556abc9d5f0cbc486d459c84682321dd38ff4`, retaining its original page,
513
+ 100 dpi resolution, pixel coordinates, and assertions. Test fixture provenance
514
+ and checksums are recorded in `src/fixtures/SOURCES.md`. Licensed under
515
+ Apache-2.0; see `licenses/PDFJS-APACHE-2.0.txt`.
516
+
517
+ Source: https://github.com/apache/pdfbox
518
+
519
+ The pattern transform handling in `src/content/evaluator.ts` follows PDFBox
520
+ `TilingPaint` and `PageDrawer`: concatenate the containing stream initial
521
+ matrix with the pattern matrix, then clip the paint to the glyph or path.
522
+
523
+ Relevant upstream NOTICE:
524
+
525
+ Apache PDFBox
526
+ Copyright 2014 The Apache Software Foundation
527
+
528
+ This product includes software developed at
529
+ The Apache Software Foundation (http://www.apache.org/).
530
+
531
+ Based on source code originally developed in the PDFBox and
532
+ FontBox projects.
533
+
534
+ Copyright (c) 2002-2007, www.pdfbox.org
535
+
536
+ Based on source code originally developed in the PaDaF project.
537
+ Copyright (c) 2010 Atos Worldline SAS
538
+
539
+ `src/pdfbox-strings.test.ts` adapts `TestCOSString` and
540
+ `PDFDocEncodingTest` at the same PDFBox revision, retaining the Unicode inputs
541
+ and expected round trips, including all 256 PDFBOX-3864 characters. Local COS
542
+ nodes preserve byte representation, so decoded strings replace Java COSString
543
+ value equality. `src/ast.ts` follows `COSString` in using PDFDocEncoding only
544
+ for representable characters and UTF-16BE otherwise. The reverse encoding map
545
+ is derived from the existing decoder table. Apache-2.0, ASF notice above.
546
+
547
+ `src/cos/pdfbox-lexer.test.ts` adapts `TestCOSParser` and `TestCOSNumber`,
548
+ keeping the upstream literal, name, and numeric expectations through the local
549
+ token API. Java-specific number range and object identity checks are omitted.
550
+ Literal recovery in `src/cos/lexer.ts` follows `BaseParser.checkForEndOfString`;
551
+ the adapter defers the recovery until EOF to preserve balanced multiline
552
+ strings. Additional local cases check following fields and token budgets.
553
+
554
+ `src/edit/pdfbox-forms.test.ts` adapts `TestRadioButtons.testRadioButtonPDModel`
555
+ and the PDFBOX-4366/PDFBOX-6207 checkbox regressions. In-memory COS dictionaries
556
+ replace PDFBox model builders; field values and widget appearance states retain
557
+ the original expectations. Empty dictionaries stand in for unused appearances.
558
+ `src/render/pdfbox-blend-mode.test.ts` ports all four
559
+ `TestPDFRendererBlendMode` cases, preserving page dimensions, sample coordinates,
560
+ and expected blue/white pixels. Vitest replaces JUnit; bitmap RGBA values replace
561
+ Java packed ARGB integers. Both use the same PDFBox revision and ASF license.
562
+
563
+ Non-isolated group compositing in `src/render/raster.ts` adapts PDFBox
564
+ `PageDrawer.TransparencyGroup` and `GroupGraphics.removeBackdrop` at the same
565
+ revision. Each paint accumulates group-only alpha separately from its color
566
+ surface, which starts with the parent backdrop. Backdrop removal retains inner
567
+ blend effects and restores group alpha before applying outer opacity and masks.
568
+ The local implementation uses a Float32 alpha plane and straight RGBA bytes,
569
+ with a raster SVG fallback for affected pages. `transparency-group.test.ts`
570
+ checks opaque and partial backdrops, nested opacity, masks, overlapping paints,
571
+ strokes, images, untouched pixels, and normal/incremental/copied-page saves.
572
+ Opaque RGB expectations were independently checked using Poppler 26.08.0's
573
+ Splash renderer; transparent expectations also check the PDF compositing
574
+ formula. ASF, Apache-2.0, notices and license above.