@scratch/scratch-svg-renderer 13.6.12 → 13.7.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scratch/scratch-svg-renderer",
3
- "version": "13.6.12",
3
+ "version": "13.7.1",
4
4
  "description": "SVG renderer for Scratch",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/scratchfoundation/scratch-svg-renderer#readme",
@@ -64,7 +64,7 @@
64
64
  "babel-loader": "9.2.1",
65
65
  "copy-webpack-plugin": "6.4.1",
66
66
  "eslint": "9.39.4",
67
- "eslint-config-scratch": "14.1.10",
67
+ "eslint-config-scratch": "14.1.12",
68
68
  "globals": "16.5.0",
69
69
  "jsdom": "13.2.0",
70
70
  "mkdirp": "2.1.6",
@@ -73,12 +73,12 @@
73
73
  "scratch-semantic-release-config": "4.0.1",
74
74
  "scratch-webpack-configuration": "3.1.2",
75
75
  "semantic-release": "25.0.3",
76
- "tap": "21.7.0",
76
+ "tap": "21.7.1",
77
77
  "webpack": "5.106.2",
78
78
  "webpack-cli": "5.1.4",
79
79
  "webpack-dev-server": "5.2.3"
80
80
  },
81
81
  "peerDependencies": {
82
- "scratch-render-fonts": "^1.0.0"
82
+ "scratch-render-fonts": "1.0.252"
83
83
  }
84
84
  }
@@ -487,6 +487,56 @@ const _parseUrl = (value, windowRef) => {
487
487
  return res;
488
488
  };
489
489
 
490
+ // Fixes an issue where clip paths didn’t follow transforms.
491
+ //
492
+ // In SVG, elements can have transforms (scale, rotate, translate, etc.)
493
+ // applied via a transform matrix. In this file, we apply those transforms
494
+ // into the actual geometry (e.g. path data) instead of keeping them as
495
+ // separate transform attributes.
496
+ //
497
+ // However, clip paths are defined separately and are referenced by elements.
498
+ // When we applied transforms to the element’s geometry, the clip path itself
499
+ // remained unchanged, so it no longer lined up with the transformed shape.
500
+ //
501
+ // This function clones the original clip path and applies the same transform
502
+ // matrix to it, ensuring the clipping region stays correctly aligned with
503
+ // the transformed element.
504
+ const _createClipPath = function (clipPathId, svgTag, matrix) {
505
+ const oldClipPath = svgTag.getElementById(clipPathId);
506
+ if (!oldClipPath) return null;
507
+
508
+ // Build unique ID from matrix, same pattern as _createGradient
509
+ let matrixString = Matrix.toString(matrix);
510
+ matrixString = matrixString.substring(8, matrixString.length - 1);
511
+ const newClipPathId = `${clipPathId}-${matrixString}`;
512
+
513
+ // Already transformed, reuse it
514
+ if (svgTag.getElementById(newClipPathId)) {
515
+ return `url(#${newClipPathId})`;
516
+ }
517
+
518
+ let defs = svgTag.getElementsByTagName('defs');
519
+ if (defs.length === 0) {
520
+ defs = SvgElement.create('defs');
521
+ svgTag.appendChild(defs);
522
+ } else {
523
+ defs = defs[0];
524
+ }
525
+
526
+ // Clone and give new ID
527
+ const newClipPath = oldClipPath.cloneNode(true);
528
+ newClipPath.setAttribute('id', newClipPathId);
529
+
530
+ // Compose with any existing transform on the clipPath rather than replacing it
531
+ const existingMatrix = _parseTransform(oldClipPath);
532
+ const composedMatrix = Matrix.compose(matrix, existingMatrix);
533
+ newClipPath.setAttribute('transform', Matrix.toString(composedMatrix));
534
+
535
+ defs.appendChild(newClipPath);
536
+
537
+ return `url(#${newClipPathId})`;
538
+ };
539
+
490
540
  /**
491
541
  * Scratch 2.0 displays stroke widths in a "normalized" way, that is,
492
542
  * if a shape with a stroke width has a transform applied, it will be
@@ -512,6 +562,18 @@ const _parseUrl = (value, windowRef) => {
512
562
  const transformStrokeWidths = function (svgTag, windowRef, bboxForTesting) {
513
563
  const inherited = Matrix.identity();
514
564
 
565
+ const _applyTransformToClipPath = function (element, matrix) {
566
+ const clipPathAttr = element.attributes && element.attributes['clip-path'];
567
+ if (!clipPathAttr) return;
568
+ const clipPathId = _parseUrl(clipPathAttr.value, windowRef);
569
+ if (!clipPathId) return;
570
+
571
+ const newClipPathRef = _createClipPath(clipPathId, svgTag, matrix);
572
+ if (newClipPathRef) {
573
+ element.setAttribute('clip-path', newClipPathRef);
574
+ }
575
+ };
576
+
515
577
  const applyTransforms = (element, matrix, strokeWidth, fill, stroke) => {
516
578
  if (_isContainerElement(element)) {
517
579
  // Push fills and stroke width down to leaves
@@ -523,12 +585,15 @@ const transformStrokeWidths = function (svgTag, windowRef, bboxForTesting) {
523
585
  if (element.attributes.stroke) stroke = element.attributes.stroke.value;
524
586
  }
525
587
 
588
+ const currentMatrix = Matrix.compose(matrix, _parseTransform(element));
589
+ _applyTransformToClipPath(element, currentMatrix);
590
+
526
591
  // If any child nodes don't take attributes, leave the attributes
527
592
  // at the parent level.
528
593
  for (let i = 0; i < element.childNodes.length; i++) {
529
594
  applyTransforms(
530
595
  element.childNodes[i],
531
- Matrix.compose(matrix, _parseTransform(element)),
596
+ currentMatrix,
532
597
  strokeWidth,
533
598
  fill,
534
599
  stroke