@poe-platform/safe-bash 0.1.764 → 0.1.766

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.
@@ -11742,11 +11742,10 @@ function evaluateContentStreamToDisplayList(params) {
11742
11742
  } else if (operator === "d") {
11743
11743
  const arrNode = ops[0]?.kind === "array" ? ops[0] : void 0;
11744
11744
  if (arrNode) {
11745
- const ctmScale = Math.max(1e-6, Math.hypot(st.ctm[0], st.ctm[1]));
11746
- const nums = arrNode.items.map((it) => it.kind === "number" ? it.value * ctmScale : 0).filter((v) => v >= 0);
11745
+ const nums = arrNode.items.map((it) => it.kind === "number" ? it.value : 0).filter((v) => v >= 0);
11747
11746
  const sum = nums.reduce((a, b) => a + b, 0);
11748
11747
  st.dashArray = sum > 0 ? nums : void 0;
11749
- st.dashPhase = num(1, 0) * ctmScale;
11748
+ st.dashPhase = num(1, 0);
11750
11749
  } else {
11751
11750
  st.dashArray = void 0;
11752
11751
  st.dashPhase = void 0;
@@ -11878,10 +11877,11 @@ function evaluateContentStreamToDisplayList(params) {
11878
11877
  const patArr = params.cosDoc.resolveArray(dArr.items[0]);
11879
11878
  const phaseNode = params.cosDoc.resolve(dArr.items[1]);
11880
11879
  if (patArr) {
11881
- st.dashArray = patArr.items.map((it) => {
11880
+ const dashArray = patArr.items.map((it) => {
11882
11881
  const r = params.cosDoc.resolve(it);
11883
11882
  return r?.kind === "number" ? r.value : 0;
11884
- }).filter((n) => n > 0);
11883
+ }).filter((n) => n >= 0);
11884
+ st.dashArray = dashArray.some((value) => value > 0) ? dashArray : void 0;
11885
11885
  st.dashPhase = phaseNode?.kind === "number" ? phaseNode.value : 0;
11886
11886
  }
11887
11887
  }
@@ -12282,15 +12282,14 @@ function evaluateContentStreamToDisplayList(params) {
12282
12282
  break;
12283
12283
  }
12284
12284
  const fillRule = node.paint.includes("*") ? "evenodd" : "nonzero";
12285
- const ctmScale = Math.max(1e-6, Math.hypot(st.ctm[0], st.ctm[1]));
12286
- const scaledStrokeWidth = st.strokeWidth === 0 ? 0 : st.strokeWidth * ctmScale;
12287
12285
  emit({ kind: "path", value: {
12288
12286
  segments: transformedSegments,
12289
12287
  fillColor: isFill && !evaluatedFillPattern ? st.fillColor : void 0,
12290
12288
  fillAlpha: isFill && !evaluatedFillPattern ? st.fillAlpha : void 0,
12291
12289
  strokeColor: isStroke ? st.strokeColor : void 0,
12292
12290
  strokeAlpha: isStroke ? st.strokeAlpha : void 0,
12293
- strokeWidth: scaledStrokeWidth,
12291
+ strokeWidth: st.strokeWidth,
12292
+ ...isStroke ? { strokeMatrix: [...st.ctm] } : {},
12294
12293
  ...st.lineCap !== 0 ? { lineCap: st.lineCap } : {},
12295
12294
  ...st.lineJoin !== 0 ? { lineJoin: st.lineJoin } : {},
12296
12295
  ...st.miterLimit !== 10 ? { miterLimit: st.miterLimit } : {},
@@ -12478,14 +12477,14 @@ function evaluateContentStreamToDisplayList(params) {
12478
12477
  if (st.textRenderMode >= 4 && st.textRenderMode <= 7) pendingTextClip.push(...transformedGlyphSegs);
12479
12478
  const isFillGlyph = st.textRenderMode === 0 || st.textRenderMode === 2 || st.textRenderMode === 4 || st.textRenderMode === 6;
12480
12479
  const isStrokeGlyph = st.textRenderMode === 1 || st.textRenderMode === 2 || st.textRenderMode === 5 || st.textRenderMode === 6;
12481
- const ctmScale = Math.max(1e-6, Math.hypot(st.ctm[0], st.ctm[1]));
12482
12480
  const paint = {
12483
12481
  segments: transformedGlyphSegs,
12484
12482
  fillColor: isFillGlyph ? st.fillColor : void 0,
12485
12483
  fillAlpha: isFillGlyph ? st.fillAlpha : void 0,
12486
12484
  strokeColor: isStrokeGlyph ? st.strokeColor : void 0,
12487
12485
  strokeAlpha: isStrokeGlyph ? st.strokeAlpha : void 0,
12488
- strokeWidth: isStrokeGlyph ? st.strokeWidth * ctmScale : 0,
12486
+ strokeWidth: isStrokeGlyph ? st.strokeWidth : 0,
12487
+ ...isStrokeGlyph ? { strokeMatrix: [...st.ctm] } : {},
12489
12488
  fillRule: "nonzero",
12490
12489
  ...st.blendMode && st.blendMode !== "Normal" ? { blendMode: st.blendMode } : {},
12491
12490
  ...st.clipRect ? { clipRect: [...st.clipRect] } : {}
@@ -14100,7 +14099,7 @@ function dashSubpath(path, pattern, phase) {
14100
14099
  while (remaining <= 0) {
14101
14100
  if (++steps > MAX_STROKE_VERTICES) throw new PdfError("E_LIMIT", "Stroke dash expansion exceeds the vertex limit");
14102
14101
  if (index % 2 === 0) {
14103
- if (pattern[index] === 0) result.push({ points: [point, point], closed: false });
14102
+ if (pattern[index] === 0) result.push({ points: [point, point], closed: false, zeroLengthDash: true });
14104
14103
  flush();
14105
14104
  }
14106
14105
  index = (index + 1) % pattern.length;
@@ -14115,7 +14114,7 @@ function dashSubpath(path, pattern, phase) {
14115
14114
  throw new PdfError("E_LIMIT", "Stroke dash expansion exceeds the vertex limit");
14116
14115
  }
14117
14116
  let distance = 0;
14118
- while (distance < length2) {
14117
+ while (length2 - distance > 8 * Number.EPSILON * Math.max(1, length2)) {
14119
14118
  const point = [start[0] + dx * distance / length2, start[1] + dy * distance / length2];
14120
14119
  advance(point);
14121
14120
  const step = Math.min(remaining, length2 - distance);
@@ -14230,7 +14229,15 @@ function strokeOutlines(paths, width, cap, join, miterLimit, dashArray = [], das
14230
14229
  let closed = subpath.closed;
14231
14230
  if (closed && points.length > 1 && Math.hypot(points[0][0] - points[points.length - 1][0], points[0][1] - points[points.length - 1][1]) <= 1e-14) points.pop();
14232
14231
  if (points.length < 3) closed = false;
14233
- if (points.length === 1 && subpath.points.length > 1 && cap === 1) {
14232
+ if (points.length === 1 && cap === 2 && subpath.zeroLengthDash) {
14233
+ contour = [];
14234
+ const [x, y] = points[0];
14235
+ add(x - half, y - half);
14236
+ add(x + half, y - half);
14237
+ add(x + half, y + half);
14238
+ add(x - half, y + half);
14239
+ contours.push(contour);
14240
+ } else if (points.length === 1 && subpath.points.length > 1 && cap === 1) {
14234
14241
  contour = [];
14235
14242
  const [x, y] = points[0];
14236
14243
  addCap([x, y], [x + 1, y]);
@@ -14637,11 +14644,10 @@ function blendPixel(rgba, width, height, px, py, color, alpha, blendMode, clipMa
14637
14644
  rgba[idx + 2] = Math.round((effB * a + cbB * dstA * (1 - a)) / outA * 255);
14638
14645
  rgba[idx + 3] = Math.round(outA * 255);
14639
14646
  }
14640
- function segmentsToScreenPaths(segments, pageHeight, scale) {
14647
+ function segmentsToScreenPaths(segments, pageHeight, scale, toScreen = (x, y) => [x * scale, (pageHeight - y) * scale]) {
14641
14648
  const paths = [];
14642
14649
  let points = [];
14643
14650
  let current = [0, 0];
14644
- const toScreen = (x, y) => [x * scale, (pageHeight - y) * scale];
14645
14651
  for (const segment of segments) {
14646
14652
  if (segment.kind === "move") {
14647
14653
  if (points.length) paths.push({ points, closed: false });
@@ -14680,6 +14686,63 @@ function segmentsToScreenPaths(segments, pageHeight, scale) {
14680
14686
  if (points.length) paths.push({ points, closed: false });
14681
14687
  return paths;
14682
14688
  }
14689
+ function inverseStrokeMatrix(matrix) {
14690
+ const [a, b, c, d, e, f] = matrix;
14691
+ const det = a * d - b * c;
14692
+ if (!Number.isFinite(det) || det === 0) return void 0;
14693
+ return [d / det, -b / det, -c / det, a / det, (c * f - d * e) / det, (b * e - a * f) / det];
14694
+ }
14695
+ function prepareStroke(path, scale) {
14696
+ const matrix = path.strokeMatrix ? [...path.strokeMatrix] : [1, 0, 0, 1, 0, 0];
14697
+ const [a, b, c, d] = matrix;
14698
+ const area = Math.abs(a * d - b * c) * scale * scale;
14699
+ if (!(area > 0) || !Number.isFinite(area)) return void 0;
14700
+ const normX = Math.hypot(a, b) * scale, normY = Math.hypot(c, d) * scale;
14701
+ let width = path.strokeWidth || 1;
14702
+ const scaleX = path.strokeWidth === 0 ? normY / area : Math.max(1, normY / (width * area));
14703
+ const scaleY = path.strokeWidth === 0 ? normX / area : Math.max(1, normX / (width * area));
14704
+ let dashArray = path.dashArray, dashPhase = path.dashPhase ?? 0;
14705
+ if (scaleX === scaleY) {
14706
+ width *= scaleX;
14707
+ } else {
14708
+ matrix[0] *= scaleX;
14709
+ matrix[1] *= scaleX;
14710
+ matrix[2] *= scaleY;
14711
+ matrix[3] *= scaleY;
14712
+ if (dashArray?.length) {
14713
+ const dashScale = Math.max(scaleX, scaleY);
14714
+ dashArray = dashArray.map((value) => value / dashScale);
14715
+ dashPhase /= dashScale;
14716
+ }
14717
+ }
14718
+ return { matrix, width, dashArray, dashPhase };
14719
+ }
14720
+ function strokeContours(path, pageHeight, scale) {
14721
+ const stroke = prepareStroke(path, scale);
14722
+ if (!stroke) return [];
14723
+ const inverse = inverseStrokeMatrix(stroke.matrix);
14724
+ if (!inverse) return [];
14725
+ const [a, b, c, d, e, f] = stroke.matrix;
14726
+ const strokeScale = scale * Math.hypot(a, b, c, d);
14727
+ const project = (x, y) => [
14728
+ (inverse[0] * x + inverse[2] * y + inverse[4]) * strokeScale,
14729
+ (inverse[1] * x + inverse[3] * y + inverse[5]) * strokeScale
14730
+ ];
14731
+ const toScreen = ([x, y]) => [
14732
+ (a * x / strokeScale + c * y / strokeScale + e) * scale,
14733
+ (pageHeight - b * x / strokeScale - d * y / strokeScale - f) * scale
14734
+ ];
14735
+ const contours = strokeOutlines(
14736
+ segmentsToScreenPaths(path.segments, pageHeight, scale, project),
14737
+ stroke.width * strokeScale,
14738
+ path.lineCap ?? 0,
14739
+ path.lineJoin ?? 0,
14740
+ path.miterLimit ?? 10,
14741
+ stroke.dashArray?.map((value) => Math.max(0, value * strokeScale)),
14742
+ stroke.dashPhase * strokeScale
14743
+ );
14744
+ return contours.map((points) => points.map(toScreen));
14745
+ }
14683
14746
  function pathsToEdges(paths, closeSubpaths = false) {
14684
14747
  const edges = [];
14685
14748
  for (const { points, closed } of paths) {
@@ -14929,19 +14992,9 @@ function renderDisplayListToBitmap(displayList, options = {}) {
14929
14992
  fillEdgesScanline4x4(rgba, width, height, edges, path.fillColor, path.fillAlpha ?? 1, path.fillRule ?? "nonzero", clipScreen, original.kind === "glyph" ? aaTxt : aaVec, path.blendMode, clipMask);
14930
14993
  }
14931
14994
  if (path.strokeColor) {
14932
- const rawSw = path.strokeWidth * scale;
14933
- const sw = Math.max(1, rawSw);
14995
+ const rawSw = path.strokeWidth * scale * (path.strokeMatrix ? Math.hypot(path.strokeMatrix[0], path.strokeMatrix[1]) : 1);
14934
14996
  const strokeAlpha = options.thinLineMode === "shape" && rawSw < 1 ? (path.strokeAlpha ?? 1) * Math.max(0.25, rawSw) : path.strokeAlpha ?? 1;
14935
- const contours = strokeOutlines(
14936
- segmentsToScreenPaths(path.segments, displayList.height, scale),
14937
- sw,
14938
- path.lineCap ?? 0,
14939
- path.lineJoin ?? 0,
14940
- path.miterLimit ?? 10,
14941
- path.dashArray?.map((value) => Math.max(0, value * scale)),
14942
- (path.dashPhase ?? 0) * scale
14943
- );
14944
- const edges = pathsToEdges(contours.map((points) => ({ points, closed: true })));
14997
+ const edges = pathsToEdges(strokeContours(path, displayList.height, scale).map((points) => ({ points, closed: true })));
14945
14998
  const clipScreen = path.clipRect ? [path.clipRect[0] * scale, (displayList.height - path.clipRect[3]) * scale, path.clipRect[2] * scale, (displayList.height - path.clipRect[1]) * scale] : void 0;
14946
14999
  fillEdgesScanline4x4(
14947
15000
  rgba,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poe-platform/safe-bash",
3
- "version": "0.1.764",
3
+ "version": "0.1.766",
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.764",
844
+ "@poe-platform/safe-fs": "0.1.766",
845
845
  "pdf-lib": "1.17.1",
846
846
  "@noble/hashes": "2.4.0",
847
847
  "pako": "3.0.1",
@@ -222,6 +222,9 @@ permission terms. The adapter uses double-precision arrays, PDFium's
222
222
  `miter_join_revert` bevel fallback, and a one-million-vertex expansion limit.
223
223
  Local dash splitting preserves joins across vertices and closed seams, resets
224
224
  phase per subpath, and handles odd arrays and zero-length round dots.
225
+ Square zero-length dashes follow PDF.js/Canvas user-axis squares. Dash stepping
226
+ ignores a few ULPs of transformed endpoint residue; SVG reuses these contours for
227
+ zero-length patterns so its endpoint behavior and opacity match PDF painting.
225
228
  `src/render/stroke-joins.test.ts` checks native cap/join vectors plus independent
226
229
  PDF.js pixels; Poppler also confirms the final zero-length dash is omitted.
227
230
  The native harness calls unchanged upstream math routines with minimal support
@@ -229,6 +232,16 @@ headers and local contour assembly; it is not the full upstream generator.
229
232
 
230
233
  Source: https://github.com/chromium/pdfium/blob/a84323421e94f484faca52dd9d027934eba42ab8/third_party/agg23/agg_math_stroke.h
231
234
 
235
+ Stroke preparation in `src/render/raster.ts` adapts PDF.js
236
+ `CanvasGraphics.getScaleForStroking` and `rescaleAndStroke` from
237
+ `src/display/canvas.js` at revision `91041fb94d6744bc2a5bccd9aad28d617faa8195`
238
+ (Mozilla Foundation, Apache-2.0). It preserves paint-time user coordinates for
239
+ width/dashes, applies the per-axis minimum device thickness, and uses PDF.js's
240
+ larger-factor dash correction when the minimum rescales the axes differently.
241
+ The TypeScript adaptation uses arrays rather than Canvas/DOMMatrix objects,
242
+ shares preparation between bitmap and SVG, and suppresses singular strokes.
243
+ Tests compare independent PDF.js pixels, including identical embedded CFF text.
244
+
232
245
 
233
246
  # SVG labels
234
247