@scratch/scratch-svg-renderer 12.7.0 → 13.0.0
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.
|
@@ -1878,12 +1878,6 @@ module.exports = require("tslog");
|
|
|
1878
1878
|
/******/ if (cachedModule !== undefined) {
|
|
1879
1879
|
/******/ return cachedModule.exports;
|
|
1880
1880
|
/******/ }
|
|
1881
|
-
/******/ // Check if module exists (development only)
|
|
1882
|
-
/******/ if (__webpack_modules__[moduleId] === undefined) {
|
|
1883
|
-
/******/ var e = new Error("Cannot find module '" + moduleId + "'");
|
|
1884
|
-
/******/ e.code = 'MODULE_NOT_FOUND';
|
|
1885
|
-
/******/ throw e;
|
|
1886
|
-
/******/ }
|
|
1887
1881
|
/******/ // Create a new module (and put it into the cache)
|
|
1888
1882
|
/******/ var module = __webpack_module_cache__[moduleId] = {
|
|
1889
1883
|
/******/ // no module.id needed
|
|
@@ -1892,6 +1886,12 @@ module.exports = require("tslog");
|
|
|
1892
1886
|
/******/ };
|
|
1893
1887
|
/******/
|
|
1894
1888
|
/******/ // Execute the module function
|
|
1889
|
+
/******/ if (!(moduleId in __webpack_modules__)) {
|
|
1890
|
+
/******/ delete __webpack_module_cache__[moduleId];
|
|
1891
|
+
/******/ var e = new Error("Cannot find module '" + moduleId + "'");
|
|
1892
|
+
/******/ e.code = 'MODULE_NOT_FOUND';
|
|
1893
|
+
/******/ throw e;
|
|
1894
|
+
/******/ }
|
|
1895
1895
|
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
|
1896
1896
|
/******/
|
|
1897
1897
|
/******/ // Return the exports of the module
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scratch-svg-renderer.js","mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;ACVA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;;;;;;;;;AC3JA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AAEA;AACA;;;;;;;;;;AC5DA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;ACrCA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;ACjDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;ACrBA;AACA;AACA;AACA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;ACjUA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AAEA;AAEA;AAEA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAEA;;;;;;;;;;ACnJA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;AClBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;ACtEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;ACxKA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AAKA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;;AAEA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;AClnBA;AAAA;AAAA;AAEA;AACA;AACA;;;;;;;;;;;ACJA;;;;;;;;;;;ACAA;;;;;;;;;;;ACAA;;;;;;;;;;;ACAA;;;;;;;;;;;ACAA;;;;;;;;;;;ACAA;;;;;;;;;;;ACAA;;;;;;ACAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AE7BA;AACA;AACA;AACA","sources":["webpack://ScratchSVGRenderer/webpack/universalModuleDefinition","webpack://ScratchSVGRenderer/./src/bitmap-adapter.js","webpack://ScratchSVGRenderer/./src/fixup-svg-string.js","webpack://ScratchSVGRenderer/./src/font-converter.js","webpack://ScratchSVGRenderer/./src/font-inliner.js","webpack://ScratchSVGRenderer/./src/index.js","webpack://ScratchSVGRenderer/./src/load-svg-string.js","webpack://ScratchSVGRenderer/./src/sanitize-svg.js","webpack://ScratchSVGRenderer/./src/serialize-svg-to-string.js","webpack://ScratchSVGRenderer/./src/svg-element.js","webpack://ScratchSVGRenderer/./src/svg-renderer.js","webpack://ScratchSVGRenderer/./src/transform-applier.js","webpack://ScratchSVGRenderer/./src/util/log.js","webpack://ScratchSVGRenderer/external commonjs \"base64-js\"","webpack://ScratchSVGRenderer/external commonjs \"css-tree\"","webpack://ScratchSVGRenderer/external commonjs \"fastestsmallesttextencoderdecoder\"","webpack://ScratchSVGRenderer/external commonjs \"isomorphic-dompurify\"","webpack://ScratchSVGRenderer/external commonjs \"scratch-render-fonts\"","webpack://ScratchSVGRenderer/external commonjs \"transformation-matrix\"","webpack://ScratchSVGRenderer/external commonjs \"tslog\"","webpack://ScratchSVGRenderer/webpack/bootstrap","webpack://ScratchSVGRenderer/webpack/before-startup","webpack://ScratchSVGRenderer/webpack/startup","webpack://ScratchSVGRenderer/webpack/after-startup"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ScratchSVGRenderer\"] = factory();\n\telse\n\t\troot[\"ScratchSVGRenderer\"] = factory();\n})(global, () => {\nreturn ","const base64js = require('base64-js');\n\n/**\n * Adapts Scratch 2.0 bitmaps for use in scratch 3.0\n */\nclass BitmapAdapter {\n /**\n * @param {?Function} makeImage HTML image constructor. Tests can provide this.\n * @param {?Function} makeCanvas HTML canvas constructor. Tests can provide this.\n */\n constructor (makeImage, makeCanvas) {\n this._makeImage = makeImage ? makeImage : () => new Image();\n this._makeCanvas = makeCanvas ? makeCanvas : () => document.createElement('canvas');\n }\n\n /**\n * Return a canvas with the resized version of the given image, done using nearest-neighbor interpolation\n * @param {CanvasImageSource} image The image to resize\n * @param {int} newWidth The desired post-resize width of the image\n * @param {int} newHeight The desired post-resize height of the image\n * @returns {HTMLCanvasElement} A canvas with the resized image drawn on it.\n */\n resize (image, newWidth, newHeight) {\n // We want to always resize using nearest-neighbor interpolation. However, canvas implementations are free to\n // use linear interpolation (or other \"smooth\" interpolation methods) when downscaling:\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1360415\n // It seems we can get around this by resizing in two steps: first width, then height. This will always result\n // in nearest-neighbor interpolation, even when downscaling.\n const stretchWidthCanvas = this._makeCanvas();\n stretchWidthCanvas.width = newWidth;\n stretchWidthCanvas.height = image.height;\n let context = stretchWidthCanvas.getContext('2d');\n context.imageSmoothingEnabled = false;\n context.drawImage(image, 0, 0, stretchWidthCanvas.width, stretchWidthCanvas.height);\n const stretchHeightCanvas = this._makeCanvas();\n stretchHeightCanvas.width = newWidth;\n stretchHeightCanvas.height = newHeight;\n context = stretchHeightCanvas.getContext('2d');\n context.imageSmoothingEnabled = false;\n context.drawImage(stretchWidthCanvas, 0, 0, stretchHeightCanvas.width, stretchHeightCanvas.height);\n return stretchHeightCanvas;\n }\n\n /**\n * Scratch 2.0 had resolution 1 and 2 bitmaps. All bitmaps in Scratch 3.0 are equivalent\n * to resolution 2 bitmaps. Therefore, converting a resolution 1 bitmap means doubling\n * it in width and height.\n * @param {!string} dataURI Base 64 encoded image data of the bitmap\n * @param {!Function} callback Node-style callback that returns updated dataURI if conversion succeeded\n */\n convertResolution1Bitmap (dataURI, callback) {\n const image = this._makeImage();\n image.src = dataURI;\n image.onload = () => {\n callback(null, this.resize(image, image.width * 2, image.height * 2).toDataURL());\n };\n image.onerror = () => {\n callback('Image load failed');\n };\n }\n\n /**\n * Given width/height of an uploaded item, return width/height the image will be resized\n * to in Scratch 3.0\n * @param {!number} oldWidth original width\n * @param {!number} oldHeight original height\n * @returns {object} Array of new width, new height\n */\n getResizedWidthHeight (oldWidth, oldHeight) {\n const STAGE_WIDTH = 480;\n const STAGE_HEIGHT = 360;\n const STAGE_RATIO = STAGE_WIDTH / STAGE_HEIGHT;\n\n // If both dimensions are smaller than or equal to corresponding stage dimension,\n // double both dimensions\n if ((oldWidth <= STAGE_WIDTH) && (oldHeight <= STAGE_HEIGHT)) {\n return {width: oldWidth * 2, height: oldHeight * 2};\n }\n\n // If neither dimension is larger than 2x corresponding stage dimension,\n // this is an in-between image, return it as is\n if ((oldWidth <= STAGE_WIDTH * 2) && (oldHeight <= STAGE_HEIGHT * 2)) {\n return {width: oldWidth, height: oldHeight};\n }\n\n const imageRatio = oldWidth / oldHeight;\n // Otherwise, figure out how to resize\n if (imageRatio >= STAGE_RATIO) {\n // Wide Image\n return {width: STAGE_WIDTH * 2, height: STAGE_WIDTH * 2 / imageRatio};\n }\n // In this case we have either:\n // - A wide image, but not with as big a ratio between width and height,\n // making it so that fitting the width to double stage size would leave\n // the height too big to fit in double the stage height\n // - A square image that's still larger than the double at least\n // one of the stage dimensions, so pick the smaller of the two dimensions (to fit)\n // - A tall image\n // In any of these cases, resize the image to fit the height to double the stage height\n return {width: STAGE_HEIGHT * 2 * imageRatio, height: STAGE_HEIGHT * 2};\n }\n\n /**\n * Given bitmap data, resize as necessary.\n * @param {ArrayBuffer | string} fileData Base 64 encoded image data of the bitmap\n * @param {string} fileType The MIME type of this file\n * @returns {Promise} Resolves to resized image data Uint8Array\n */\n importBitmap (fileData, fileType) {\n let dataURI = fileData;\n if (fileData instanceof ArrayBuffer) {\n dataURI = this.convertBinaryToDataURI(fileData, fileType);\n }\n return new Promise((resolve, reject) => {\n const image = this._makeImage();\n image.src = dataURI;\n image.onload = () => {\n const newSize = this.getResizedWidthHeight(image.width, image.height);\n if (newSize.width === image.width && newSize.height === image.height) {\n // No change\n resolve(this.convertDataURIToBinary(dataURI));\n } else {\n const resizedDataURI = this.resize(image, newSize.width, newSize.height).toDataURL();\n resolve(this.convertDataURIToBinary(resizedDataURI));\n }\n };\n image.onerror = () => {\n // TODO: reject with an Error (breaking API change!)\n // eslint-disable-next-line prefer-promise-reject-errors\n reject('Image load failed');\n };\n });\n }\n\n // TODO consolidate with scratch-vm/src/util/base64-util.js\n // From https://gist.github.com/borismus/1032746\n convertDataURIToBinary (dataURI) {\n const BASE64_MARKER = ';base64,';\n const base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;\n const base64 = dataURI.substring(base64Index);\n const raw = window.atob(base64);\n const rawLength = raw.length;\n const array = new Uint8Array(new ArrayBuffer(rawLength));\n\n for (let i = 0; i < rawLength; i++) {\n array[i] = raw.charCodeAt(i);\n }\n return array;\n }\n\n convertBinaryToDataURI (arrayBuffer, contentType) {\n return `data:${contentType};base64,${base64js.fromByteArray(new Uint8Array(arrayBuffer))}`;\n }\n}\n\nmodule.exports = BitmapAdapter;\n","/**\n * Fixup svg string prior to parsing.\n * @param {!string} svgString String of the svg to fix.\n * @returns {!string} fixed svg that should be parseable.\n */\nmodule.exports = function (svgString) {\n // Add root svg namespace if it does not exist.\n const svgAttrs = svgString.match(/<svg [^>]*>/);\n if (svgAttrs && svgAttrs[0].indexOf('xmlns=') === -1) {\n svgString = svgString.replace('<svg ', '<svg xmlns=\"http://www.w3.org/2000/svg\" ');\n }\n\n // There are some SVGs from Illustrator that use undeclared entities.\n // Just replace those entities with fake namespace references to prevent\n // DOMParser from crashing\n if (svgAttrs && svgAttrs[0].indexOf('&ns_') !== -1 && svgString.indexOf('<!DOCTYPE') === -1) {\n svgString = svgString.replace(svgAttrs[0],\n svgAttrs[0].replace(/&ns_[^;]+;/g, 'http://ns.adobe.com/Extensibility/1.0/'));\n }\n\n // Some SVGs exported from Photoshop have been found to have an invalid mime type\n // Chrome and Safari won't render these SVGs, so we correct it here\n if (svgString.includes('data:img/png')) {\n svgString = svgString.replace(\n // capture entire image tag with xlink:href=and the quote - dont capture data: bit\n /(<image[^>]+?xlink:href=[\"'])data:img\\/png/g,\n // use the captured <image ..... xlink:href=\" then append the right data uri mime type\n ($0, $1) => `${$1}data:image/png`\n );\n }\n\n // Some SVGs from Inkscape attempt to bind a prefix to a reserved namespace name.\n // This will cause SVG parsing to fail, so replace these with a dummy namespace name.\n // This namespace name is only valid for \"xml\", and if we bind \"xmlns:xml\" to the dummy namespace,\n // parsing will fail yet again, so exclude \"xmlns:xml\" declarations.\n const xmlnsRegex = /(<[^>]+?xmlns:(?!xml=)[^ ]+=)\"http:\\/\\/www.w3.org\\/XML\\/1998\\/namespace\"/g;\n if (svgString.match(xmlnsRegex) !== null) {\n svgString = svgString.replace(\n // capture the entire attribute\n xmlnsRegex,\n // use the captured attribute name; replace only the URL\n ($0, $1) => `${$1}\"http://dummy.namespace\"`\n );\n }\n\n // Strip `svg:` prefix (sometimes added by Inkscape) from all tags. They interfere with DOMPurify (prefixed tag\n // names are not recognized) and the paint editor.\n // This matches opening and closing tags--the capture group captures the slash if it exists, and it is reinserted\n // in the replacement text.\n svgString = svgString.replace(/<(\\/?)\\s*svg:/g, '<$1');\n\n // The <metadata> element is not needed for rendering and sometimes contains\n // unparseable garbage from Illustrator :( Empty out the contents.\n // Note: [\\s\\S] matches everything including newlines, which .* does not\n svgString = svgString.replace(/<metadata>[\\s\\S]*<\\/metadata>/, '<metadata></metadata>');\n\n // Empty script tags and javascript executing\n svgString = svgString.replace(/<script[\\s\\S]*>[\\s\\S]*<\\/script>/, '<script></script>');\n\n return svgString;\n};\n","/**\n * @fileOverview Convert 2.0 fonts to 3.0 fonts.\n */\n\n/**\n * Given an SVG, replace Scratch 2.0 fonts with new 3.0 fonts. Add defaults where there are none.\n * @param {SVGElement} svgTag The SVG dom object\n * @returns {void}\n */\nconst convertFonts = function (svgTag) {\n // Collect all text elements into a list.\n const textElements = [];\n const collectText = domElement => {\n if (domElement.localName === 'text') {\n textElements.push(domElement);\n }\n for (let i = 0; i < domElement.childNodes.length; i++) {\n collectText(domElement.childNodes[i]);\n }\n };\n collectText(svgTag);\n // If there's an old font-family, switch to the new one.\n for (const textElement of textElements) {\n // If there's no font-family provided, provide one.\n if (!textElement.getAttribute('font-family') ||\n textElement.getAttribute('font-family') === 'Helvetica') {\n textElement.setAttribute('font-family', 'Sans Serif');\n } else if (textElement.getAttribute('font-family') === 'Mystery') {\n textElement.setAttribute('font-family', 'Curly');\n } else if (textElement.getAttribute('font-family') === 'Gloria') {\n textElement.setAttribute('font-family', 'Handwriting');\n } else if (textElement.getAttribute('font-family') === 'Donegal') {\n textElement.setAttribute('font-family', 'Serif');\n }\n }\n};\n\nmodule.exports = convertFonts;\n","/**\n * @fileOverview Import bitmap data into Scratch 3.0, resizing image as necessary.\n */\nconst getFonts = require('scratch-render-fonts');\n\n/**\n * Given SVG data, inline the fonts. This allows them to be rendered correctly when set\n * as the source of an HTMLImageElement. Here is a note from tmickel:\n * // Inject fonts that are needed.\n * // It would be nice if there were another way to get the SVG-in-canvas\n * // to render the correct font family, but I couldn't find any other way.\n * // Other things I tried:\n * // Just injecting the font-family into the document: no effect.\n * // External stylesheet linked to by SVG: no effect.\n * // Using a <link> or <style>@import</style> to link to font-family\n * // injected into the document: no effect.\n * @param {string} svgString The string representation of the svg to modify\n * @returns {string} The svg with any needed fonts inlined\n */\nconst inlineSvgFonts = function (svgString) {\n const FONTS = getFonts();\n // Make it clear that this function only operates on strings.\n // If we don't explicitly throw this here, the function silently fails.\n if (typeof svgString !== 'string') {\n throw new Error('SVG to be inlined is not a string');\n }\n\n // Collect fonts that need injection.\n const fontsNeeded = new Set();\n const fontRegex = /font-family=\"([^\"]*)\"/g;\n let matches = fontRegex.exec(svgString);\n while (matches) {\n fontsNeeded.add(matches[1]);\n matches = fontRegex.exec(svgString);\n }\n if (fontsNeeded.size > 0) {\n let str = '<defs><style>';\n for (const font of fontsNeeded) {\n if (Object.prototype.hasOwnProperty.call(FONTS, font)) {\n str += `${FONTS[font]}`;\n }\n }\n str += '</style></defs>';\n svgString = svgString.replace(/<svg[^>]*>/, `$&${str}`);\n return svgString;\n }\n return svgString;\n};\n\nmodule.exports = inlineSvgFonts;\n","const SVGRenderer = require('./svg-renderer');\nconst BitmapAdapter = require('./bitmap-adapter');\nconst inlineSvgFonts = require('./font-inliner');\nconst loadSvgString = require('./load-svg-string');\nconst sanitizeSvg = require('./sanitize-svg');\nconst serializeSvgToString = require('./serialize-svg-to-string');\nconst SvgElement = require('./svg-element');\nconst convertFonts = require('./font-converter');\n// /**\n// * Export for NPM & Node.js\n// * @type {RenderWebGL}\n// */\nmodule.exports = {\n BitmapAdapter: BitmapAdapter,\n convertFonts: convertFonts,\n inlineSvgFonts: inlineSvgFonts,\n loadSvgString: loadSvgString,\n sanitizeSvg: sanitizeSvg,\n serializeSvgToString: serializeSvgToString,\n SvgElement: SvgElement,\n SVGRenderer: SVGRenderer\n};\n","const SvgElement = require('./svg-element');\nconst convertFonts = require('./font-converter');\nconst transformStrokeWidths = require('./transform-applier');\nconst {sanitizeSvgText} = require('./sanitize-svg');\n\n/**\n * @param {SVGElement} svgTag the tag to search within\n * @param {string} [tagName] svg tag to search for (or collect all elements if not given)\n * @returns {Array} a list of elements with the given tagname\n */\nconst collectElements = (svgTag, tagName) => {\n const elts = [];\n const collectElementsInner = domElement => {\n if ((domElement.localName === tagName || typeof tagName === 'undefined') && domElement.getAttribute) {\n elts.push(domElement);\n }\n for (let i = 0; i < domElement.childNodes.length; i++) {\n collectElementsInner(domElement.childNodes[i]);\n }\n };\n collectElementsInner(svgTag);\n return elts;\n};\n\n/**\n * Fix SVGs to comply with SVG spec. Scratch 2 defaults to x2 = 0 when x2 is missing, but\n * SVG defaults to x2 = 1 when missing.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst transformGradients = svgTag => {\n const linearGradientElements = collectElements(svgTag, 'linearGradient');\n\n // For each gradient element, supply x2 if necessary.\n for (const gradientElement of linearGradientElements) {\n if (!gradientElement.getAttribute('x2')) {\n gradientElement.setAttribute('x2', '0');\n }\n }\n};\n\n/**\n * Fix SVGs to match appearance in Scratch 2, which used nearest neighbor scaling for bitmaps\n * within SVGs.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst transformImages = svgTag => {\n const imageElements = collectElements(svgTag, 'image');\n\n // For each image element, set image rendering to pixelated\n const pixelatedImages = 'image-rendering: optimizespeed; image-rendering: pixelated;';\n for (const elt of imageElements) {\n if (elt.getAttribute('style')) {\n elt.setAttribute('style',\n `${pixelatedImages} ${elt.getAttribute('style')}`);\n } else {\n elt.setAttribute('style', pixelatedImages);\n }\n }\n};\n\n/**\n * Transforms an SVG's text elements for Scratch 2.0 quirks.\n * These quirks include:\n * 1. `x` and `y` properties are removed/ignored.\n * 2. Alignment is set to `text-before-edge`.\n * 3. Line-breaks are converted to explicit <tspan> elements.\n * 4. Any required fonts are injected.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst transformText = svgTag => {\n // Collect all text elements into a list.\n const textElements = [];\n const collectText = domElement => {\n if (domElement.localName === 'text') {\n textElements.push(domElement);\n }\n for (let i = 0; i < domElement.childNodes.length; i++) {\n collectText(domElement.childNodes[i]);\n }\n };\n collectText(svgTag);\n convertFonts(svgTag);\n // For each text element, apply quirks.\n for (const textElement of textElements) {\n // Remove x and y attributes - they are not used in Scratch.\n textElement.removeAttribute('x');\n textElement.removeAttribute('y');\n // Set text-before-edge alignment:\n // Scratch renders all text like this.\n textElement.setAttribute('alignment-baseline', 'text-before-edge');\n textElement.setAttribute('xml:space', 'preserve');\n // If there's no font size provided, provide one.\n if (!textElement.getAttribute('font-size')) {\n textElement.setAttribute('font-size', '18');\n }\n let text = textElement.textContent;\n\n // Fix line breaks in text, which are not natively supported by SVG.\n // Only fix if text does not have child tspans.\n // @todo this will not work for font sizes with units such as em, percent\n // However, text made in scratch 2 should only ever export size 22 font.\n const fontSize = parseFloat(textElement.getAttribute('font-size'));\n const tx = 2;\n let ty = 0;\n let spacing = 1.2;\n // Try to match the position and spacing of Scratch 2.0's fonts.\n // Different fonts seem to use different line spacing.\n // Scratch 2 always uses alignment-baseline=text-before-edge\n // However, most SVG readers don't support this attribute\n // or don't support it alongside use of tspan, so the translations\n // here are to make up for that.\n if (textElement.getAttribute('font-family') === 'Handwriting') {\n spacing = 2;\n ty = -11 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Scratch') {\n spacing = 0.89;\n ty = -3 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Curly') {\n spacing = 1.38;\n ty = -6 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Marker') {\n spacing = 1.45;\n ty = -6 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Sans Serif') {\n spacing = 1.13;\n ty = -3 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Serif') {\n spacing = 1.25;\n ty = -4 * fontSize / 22;\n }\n\n if (textElement.transform.baseVal.numberOfItems === 0) {\n const transform = svgTag.createSVGTransform();\n textElement.transform.baseVal.appendItem(transform);\n }\n\n // Right multiply matrix by a translation of (tx, ty)\n const mtx = textElement.transform.baseVal.getItem(0).matrix;\n mtx.e += (mtx.a * tx) + (mtx.c * ty);\n mtx.f += (mtx.b * tx) + (mtx.d * ty);\n\n if (text && textElement.childElementCount === 0) {\n textElement.textContent = '';\n const lines = text.split('\\n');\n text = '';\n for (const line of lines) {\n const tspanNode = SvgElement.create('tspan');\n tspanNode.setAttribute('x', '0');\n tspanNode.setAttribute('style', 'white-space: pre');\n tspanNode.setAttribute('dy', `${spacing}em`);\n tspanNode.textContent = line ? line : ' ';\n textElement.appendChild(tspanNode);\n }\n }\n }\n};\n\n/**\n * Find the largest stroke width in the svg. If a shape has no\n * `stroke` property, it has a stroke-width of 0. If it has a `stroke`,\n * it is by default a stroke-width of 1.\n * This is used to enlarge the computed bounding box, which doesn't take\n * stroke width into account.\n * @param {SVGSVGElement} rootNode The root SVG node to traverse.\n * @returns {number} The largest stroke width in the SVG.\n */\nconst findLargestStrokeWidth = rootNode => {\n let largestStrokeWidth = 0;\n const collectStrokeWidths = domElement => {\n if (domElement.getAttribute) {\n if (domElement.getAttribute('stroke')) {\n largestStrokeWidth = Math.max(largestStrokeWidth, 1);\n }\n if (domElement.getAttribute('stroke-width')) {\n largestStrokeWidth = Math.max(\n largestStrokeWidth,\n Number(domElement.getAttribute('stroke-width')) || 0\n );\n }\n }\n for (let i = 0; i < domElement.childNodes.length; i++) {\n collectStrokeWidths(domElement.childNodes[i]);\n }\n };\n collectStrokeWidths(rootNode);\n return largestStrokeWidth;\n};\n\n/**\n * Transform the measurements of the SVG.\n * In Scratch 2.0, SVGs are drawn without respect to the width,\n * height, and viewBox attribute on the tag. The exporter\n * does output these properties - but they appear to be incorrect often.\n * To address the incorrect measurements, we append the DOM to the\n * document, and then use SVG's native `getBBox` to find the real\n * drawn dimensions. This ensures things drawn in negative dimensions,\n * outside the given viewBox, etc., are all eventually drawn to the canvas.\n * I tried to do this several other ways: stripping the width/height/viewBox\n * attributes and then drawing (Firefox won't draw anything),\n * or inflating them and then measuring a canvas. But this seems to be\n * a natural and performant way.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst transformMeasurements = svgTag => {\n // Append the SVG dom to the document.\n // This allows us to use `getBBox` on the page,\n // which returns the full bounding-box of all drawn SVG\n // elements, similar to how Scratch 2.0 did measurement.\n const svgSpot = document.createElement('span');\n let bbox;\n try {\n // Insert sanitized value.\n svgSpot.innerHTML = svgTag.outerHTML;\n document.body.appendChild(svgSpot);\n // Take the bounding box. We have to get elements via svgSpot\n // because we added it via innerHTML.\n bbox = svgSpot.children[0].getBBox();\n } finally {\n // Always destroy the element, even if, for example, getBBox throws.\n document.body.removeChild(svgSpot);\n }\n\n // Enlarge the bbox from the largest found stroke width\n // This may have false-positives, but at least the bbox will always\n // contain the full graphic including strokes.\n // If the width or height is zero however, don't enlarge since\n // they won't have a stroke width that needs to be enlarged.\n let halfStrokeWidth;\n if (bbox.width === 0 || bbox.height === 0) {\n halfStrokeWidth = 0;\n } else {\n halfStrokeWidth = findLargestStrokeWidth(svgTag) / 2;\n }\n const width = bbox.width + (halfStrokeWidth * 2);\n const height = bbox.height + (halfStrokeWidth * 2);\n const x = bbox.x - halfStrokeWidth;\n const y = bbox.y - halfStrokeWidth;\n\n // Set the correct measurements on the SVG tag\n svgTag.setAttribute('width', width);\n svgTag.setAttribute('height', height);\n svgTag.setAttribute('viewBox',\n `${x} ${y} ${width} ${height}`);\n};\n\n/**\n * Find all instances of a URL-referenced `stroke` in the svg. In 2.0, all gradient strokes\n * have a round `stroke-linejoin` and `stroke-linecap`... for some reason.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst setGradientStrokeRoundedness = svgTag => {\n const elements = collectElements(svgTag);\n\n for (const elt of elements) {\n if (!elt.style) continue;\n const stroke = elt.style.stroke || elt.getAttribute('stroke');\n if (stroke && stroke.match(/^url\\(#.*\\)$/)) {\n elt.style['stroke-linejoin'] = 'round';\n elt.style['stroke-linecap'] = 'round';\n }\n }\n};\n\n/**\n * In-place, convert passed SVG to something consistent that will be rendered the way we want them to be.\n * @param {SVGSvgElement} svgTag root SVG node to operate upon\n * @param {boolean} [fromVersion2] True if we should perform conversion from version 2 to version 3 svg.\n */\nconst normalizeSvg = (svgTag, fromVersion2) => {\n if (fromVersion2) {\n // Fix gradients. Scratch 2 exports no x2 when x2 = 0, but\n // SVG default is that x2 is 1. This must be done before\n // transformStrokeWidths since transformStrokeWidths affects\n // gradients.\n transformGradients(svgTag);\n }\n transformStrokeWidths(svgTag, window);\n transformImages(svgTag);\n if (fromVersion2) {\n // Transform all text elements.\n transformText(svgTag);\n // Transform measurements.\n transformMeasurements(svgTag);\n // Fix stroke roundedness.\n setGradientStrokeRoundedness(svgTag);\n } else if (!svgTag.getAttribute('viewBox')) {\n // Renderer expects a view box.\n transformMeasurements(svgTag);\n } else if (!svgTag.getAttribute('width') || !svgTag.getAttribute('height')) {\n svgTag.setAttribute('width', svgTag.viewBox.baseVal.width);\n svgTag.setAttribute('height', svgTag.viewBox.baseVal.height);\n }\n};\n\n/**\n * Load an SVG string and normalize it. All the steps before drawing/measuring.\n * Currently, this will normalize stroke widths (see transform-applier.js) and render all embedded images pixelated.\n * The returned SVG will be guaranteed to always have a `width`, `height` and `viewBox`.\n * In addition, if the `fromVersion2` parameter is `true`, several \"quirks-mode\" transformations will be applied which\n * mimic Scratch 2.0's SVG rendering.\n * @param {!string} svgString String of SVG data to draw in quirks-mode.\n * @param {boolean} [fromVersion2] True if we should perform conversion from version 2 to version 3 svg.\n * @returns {SVGSVGElement} The normalized SVG element.\n */\nconst loadSvgString = (svgString, fromVersion2) => {\n // Parse string into SVG XML.\n const parser = new DOMParser();\n\n // Since we're adding user-provided SVG to document.body as part of normalization,\n // sanitization is required. This should not affect bounding box calculation.\n const sanitizedSvgString = sanitizeSvgText(svgString);\n const svgDom = parser.parseFromString(sanitizedSvgString, 'text/xml');\n if (svgDom.childNodes.length < 1 ||\n svgDom.documentElement.localName !== 'svg') {\n throw new Error('Document does not appear to be SVG.');\n }\n const svgTag = svgDom.documentElement;\n normalizeSvg(svgTag, fromVersion2);\n return svgTag;\n};\n\nmodule.exports = loadSvgString;\n","/**\n * @fileOverview Sanitize the content of an SVG aggressively, to make it as safe\n * as possible\n */\nconst fixupSvgString = require('./fixup-svg-string');\nconst {generate, parse, walk} = require('css-tree');\nconst DOMPurify = require('isomorphic-dompurify');\n\nconst sanitizeSvg = {};\n\nconst isInternalRef = ref => ref.startsWith('#') || ref.startsWith('data:');\n\nDOMPurify.addHook(\n 'beforeSanitizeAttributes',\n currentNode => {\n\n if (currentNode && currentNode.href && currentNode.href.baseVal) {\n const href = currentNode.href.baseVal.replace(/\\s/g, '');\n // \"data:\" and \"#\" are valid hrefs\n if (!isInternalRef(href)) {\n // TODO: Those can be in different namespaces than `xlink:`\n if (currentNode.attributes.getNamedItem('xlink:href')) {\n currentNode.attributes.removeNamedItem('xlink:href');\n delete currentNode['xlink:href'];\n }\n if (currentNode.attributes.getNamedItem('href')) {\n currentNode.attributes.removeNamedItem('href');\n delete currentNode.href;\n }\n }\n }\n\n // Remove url(...) usages with external references\n if (currentNode && currentNode.attributes) {\n for (let i = currentNode.attributes.length - 1; i >= 0; i--) {\n const attr = currentNode.attributes[i];\n const rawValue = attr.value || '';\n const value = rawValue.toLowerCase().replace(/\\s/g, '');\n \n const urlMatch = value.match(/url\\((.+?)\\)/);\n if (urlMatch) {\n const ref = urlMatch[1].replace(/['\"]/g, '');\n if (!isInternalRef(ref)) {\n currentNode.removeAttribute(attr.name);\n }\n }\n }\n }\n \n return currentNode;\n }\n);\n\nDOMPurify.addHook(\n 'uponSanitizeElement',\n (node, data) => {\n if (data.tagName === 'style') {\n const ast = parse(node.textContent);\n let isModified = false;\n\n walk(ast, (astNode, item, list) => {\n // @import rules\n if (astNode.type === 'Atrule' && astNode.name.toLowerCase() === 'import') {\n list.remove(item);\n isModified = true;\n }\n \n // Elements using url(...) for external resources\n if (astNode.type === 'Declaration' && astNode.value) {\n let shouldRemove = false;\n walk(astNode.value, valueNode => {\n if (valueNode.type === 'Url') {\n const urlValue = (valueNode.value.value || '').trim().replace(/['\"]/g, '');\n \n if (!isInternalRef(urlValue)) {\n shouldRemove = true;\n }\n }\n });\n\n if (shouldRemove) {\n list.remove(item);\n isModified = true;\n }\n }\n });\n\n if (isModified) {\n node.textContent = generate(ast);\n }\n }\n }\n);\n\n// Use JS implemented TextDecoder and TextEncoder if it is not provided by the\n// browser.\nlet _TextDecoder;\nlet _TextEncoder;\nif (typeof TextDecoder === 'undefined' || typeof TextEncoder === 'undefined') {\n // Wait to require the text encoding polyfill until we know it's needed.\n \n const encoding = require('fastestsmallesttextencoderdecoder');\n _TextDecoder = encoding.TextDecoder;\n _TextEncoder = encoding.TextEncoder;\n} else {\n _TextDecoder = TextDecoder;\n _TextEncoder = TextEncoder;\n}\n\n/**\n * Load an SVG Uint8Array of bytes and \"sanitize\" it\n * @param {!Uint8Array} rawData unsanitized SVG daata\n * @returns {Uint8Array} sanitized SVG data\n */\nsanitizeSvg.sanitizeByteStream = function (rawData) {\n const decoder = new _TextDecoder();\n const encoder = new _TextEncoder();\n const sanitizedText = sanitizeSvg.sanitizeSvgText(decoder.decode(rawData));\n return encoder.encode(sanitizedText);\n};\n\n/**\n * Load an SVG string and \"sanitize\" it. This is more aggressive than the handling in\n * fixup-svg-string.js, and thus more risky; there are known examples of SVGs that\n * it will clobber. We use DOMPurify's svg profile, which restricts many types of tag.\n * @param {!string} rawSvgText unsanitized SVG string\n * @returns {string} sanitized SVG text\n */\nsanitizeSvg.sanitizeSvgText = function (rawSvgText) {\n let sanitizedText = DOMPurify.sanitize(rawSvgText, {\n USE_PROFILES: {svg: true},\n FORBID_TAGS: ['a', 'audio', 'canvas', 'video'],\n // Allow data URI in image tags (e.g. SVGs converted from bitmap)\n ADD_DATA_URI_TAGS: ['image']\n });\n\n // Remove partial XML comment that is sometimes left in the HTML\n const badTag = sanitizedText.indexOf(']>');\n if (badTag >= 0) {\n sanitizedText = sanitizedText.substring(5, sanitizedText.length);\n }\n\n // also use our custom fixup rules\n sanitizedText = fixupSvgString(sanitizedText);\n return sanitizedText;\n};\n\nmodule.exports = sanitizeSvg;\n","const inlineSvgFonts = require('./font-inliner');\n\n/**\n * Serialize a given SVG DOM to a string.\n * @param {SVGSVGElement} svgTag The SVG element to serialize.\n * @param {?boolean} shouldInjectFonts True if fonts should be included in the SVG as\n * base64 data.\n * @returns {string} String representing current SVG data.\n */\nconst serializeSvgToString = (svgTag, shouldInjectFonts) => {\n const serializer = new XMLSerializer();\n let string = serializer.serializeToString(svgTag);\n if (shouldInjectFonts) {\n string = inlineSvgFonts(string);\n }\n return string;\n};\n\nmodule.exports = serializeSvgToString;\n","/* Adapted from\n * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.\n * http://paperjs.org/\n *\n * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey\n * http://scratchdisk.com/ & http://jonathanpuckey.com/\n *\n * Distributed under the MIT license. See LICENSE file for details.\n *\n * All rights reserved.\n */\n\n/**\n * @name SvgElement\n * @namespace\n * @private\n */\nclass SvgElement {\n // SVG related namespaces\n static get svg () {\n return 'http://www.w3.org/2000/svg';\n }\n static get xmlns () {\n return 'http://www.w3.org/2000/xmlns';\n }\n static get xlink () {\n return 'http://www.w3.org/1999/xlink';\n }\n\n // Mapping of attribute names to required namespaces:\n static attributeNamespace () {\n return {\n 'href': SvgElement.xlink,\n 'xlink': SvgElement.xmlns,\n // Only the xmlns attribute needs the trailing slash. See #984\n 'xmlns': `${SvgElement.xmlns}/`,\n // IE needs the xmlns namespace when setting 'xmlns:xlink'. See #984\n 'xmlns:xlink': `${SvgElement.xmlns}/`\n };\n }\n\n static create (tag, attributes, formatter) {\n return SvgElement.set(document.createElementNS(SvgElement.svg, tag), attributes, formatter);\n }\n\n static get (node, name) {\n const namespace = SvgElement.attributeNamespace[name];\n const value = namespace ?\n node.getAttributeNS(namespace, name) :\n node.getAttribute(name);\n return value === 'null' ? null : value;\n }\n\n static set (node, attributes, formatter) {\n for (const name in attributes) {\n let value = attributes[name];\n const namespace = SvgElement.attributeNamespace[name];\n if (typeof value === 'number' && formatter) {\n value = formatter.number(value);\n }\n if (namespace) {\n node.setAttributeNS(namespace, name, value);\n } else {\n node.setAttribute(name, value);\n }\n }\n return node;\n }\n}\n\nmodule.exports = SvgElement;\n","const loadSvgString = require('./load-svg-string');\nconst serializeSvgToString = require('./serialize-svg-to-string');\n\n/**\n * Main quirks-mode SVG rendering code.\n * @deprecated Call into individual methods exported from this library instead.\n */\nclass SvgRenderer {\n /**\n * Create a quirks-mode SVG renderer for a particular canvas.\n * @param {HTMLCanvasElement} [canvas] An optional canvas element to draw to. If this is not provided, the renderer\n * will create a new canvas.\n * @class\n */\n constructor (canvas) {\n /**\n * The canvas that this SVG renderer will render to.\n * @type {HTMLCanvasElement}\n * @private\n */\n this._canvas = canvas || document.createElement('canvas');\n this._context = this._canvas.getContext('2d');\n\n /**\n * A measured SVG \"viewbox\"\n * @typedef {object} SvgRenderer#SvgMeasurements\n * @property {number} x - The left edge of the SVG viewbox.\n * @property {number} y - The top edge of the SVG viewbox.\n * @property {number} width - The width of the SVG viewbox.\n * @property {number} height - The height of the SVG viewbox.\n */\n\n /**\n * The measurement box of the currently loaded SVG.\n * @type {SvgRenderer#SvgMeasurements}\n * @private\n */\n this._measurements = {x: 0, y: 0, width: 0, height: 0};\n\n /**\n * The `<img>` element with the contents of the currently loaded SVG.\n * @type {?HTMLImageElement}\n * @private\n */\n this._cachedImage = null;\n\n /**\n * True if this renderer's current SVG is loaded and can be rendered to the canvas.\n * @type {boolean}\n */\n this.loaded = false;\n }\n\n /**\n * @returns {!HTMLCanvasElement} this renderer's target canvas.\n */\n get canvas () {\n return this._canvas;\n }\n\n /**\n * @returns {Array<number>} the natural size, in Scratch units, of this SVG.\n */\n get size () {\n return [this._measurements.width, this._measurements.height];\n }\n\n /**\n * @returns {Array<number>} the offset (upper left corner) of the SVG's view box.\n */\n get viewOffset () {\n return [this._measurements.x, this._measurements.y];\n }\n\n /**\n * Load an SVG string and normalize it. All the steps before drawing/measuring.\n * @param {!string} svgString String of SVG data to draw in quirks-mode.\n * @param {?boolean} fromVersion2 True if we should perform conversion from\n * version 2 to version 3 svg.\n */\n loadString (svgString, fromVersion2) {\n // New svg string invalidates the cached image\n this._cachedImage = null;\n const svgTag = loadSvgString(svgString, fromVersion2);\n\n this._svgTag = svgTag;\n this._measurements = {\n width: svgTag.viewBox.baseVal.width,\n height: svgTag.viewBox.baseVal.height,\n x: svgTag.viewBox.baseVal.x,\n y: svgTag.viewBox.baseVal.y\n };\n }\n\n /**\n * Load an SVG string, normalize it, and prepare it for (synchronous) rendering.\n * @param {!string} svgString String of SVG data to draw in quirks-mode.\n * @param {?boolean} fromVersion2 True if we should perform conversion from version 2 to version 3 svg.\n * @param {Function} [onFinish] - An optional callback to call when the SVG is loaded and can be rendered.\n */\n loadSVG (svgString, fromVersion2, onFinish) {\n this.loadString(svgString, fromVersion2);\n this._createSVGImage(onFinish);\n }\n\n /**\n * Creates an <img> element for the currently loaded SVG string, then calls the callback once it's loaded.\n * @param {Function} [onFinish] - An optional callback to call when the <img> has loaded.\n */\n _createSVGImage (onFinish) {\n if (this._cachedImage === null) this._cachedImage = new Image();\n const img = this._cachedImage;\n\n img.onload = () => {\n this.loaded = true;\n if (onFinish) onFinish();\n };\n const svgText = this.toString(true /* shouldInjectFonts */);\n img.src = `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`;\n this.loaded = false;\n }\n\n /**\n * Serialize the active SVG DOM to a string.\n * @param {?boolean} shouldInjectFonts True if fonts should be included in the SVG as\n * base64 data.\n * @returns {string} String representing current SVG data.\n * @deprecated Use the standalone `serializeSvgToString` export instead.\n */\n toString (shouldInjectFonts) {\n return serializeSvgToString(this._svgTag, shouldInjectFonts);\n }\n\n /**\n * Synchronously draw the loaded SVG to this renderer's `canvas`.\n * @param {number} [scale] - Optionally, also scale the image by this factor.\n */\n draw (scale) {\n if (!this.loaded) throw new Error('SVG image has not finished loading');\n this._drawFromImage(scale);\n }\n\n /**\n * Draw to the canvas from a loaded image element.\n * @param {number} [scale] - Optionally, also scale the image by this factor.\n */\n _drawFromImage (scale) {\n if (this._cachedImage === null) return;\n\n const ratio = Number.isFinite(scale) ? scale : 1;\n const bbox = this._measurements;\n this._canvas.width = bbox.width * ratio;\n this._canvas.height = bbox.height * ratio;\n // Even if the canvas at the current scale has a nonzero size, the image's dimensions are floored pre-scaling.\n // e.g. if an image has a width of 0.4 and is being rendered at 3x scale, the canvas will have a width of 1, but\n // the image's width will be rounded down to 0 on some browsers (Firefox) prior to being drawn at that scale.\n if (\n this._canvas.width <= 0 ||\n this._canvas.height <= 0 ||\n this._cachedImage.naturalWidth <= 0 ||\n this._cachedImage.naturalHeight <= 0\n ) return;\n this._context.clearRect(0, 0, this._canvas.width, this._canvas.height);\n this._context.setTransform(ratio, 0, 0, ratio, 0, 0);\n this._context.drawImage(this._cachedImage, 0, 0);\n }\n}\n\nmodule.exports = SvgRenderer;\n","const Matrix = require('transformation-matrix');\nconst SvgElement = require('./svg-element');\nconst log = require('./util/log');\n\n/**\n * @fileOverview Apply transforms to match stroke width appearance in 2.0 and 3.0\n */\n\n// Adapted from paper.js's Path.applyTransform\nconst _parseTransform = function (domElement) {\n let matrix = Matrix.identity();\n const string = domElement.attributes && domElement.attributes.transform && domElement.attributes.transform.value;\n if (!string) return matrix;\n // https://www.w3.org/TR/SVG/types.html#DataTypeTransformList\n // Parse SVG transform string. First we split at /)\\s*/, to separate\n // commands\n const transforms = string.split(/\\)\\s*/g);\n for (const transform of transforms) {\n if (!transform) break;\n // Command come before the '(', values after\n const parts = transform.split(/\\(\\s*/);\n const command = parts[0].trim();\n const v = parts[1].split(/[\\s,]+/g);\n // Convert values to floats\n for (let j = 0; j < v.length; j++) {\n v[j] = parseFloat(v[j]);\n }\n switch (command) {\n case 'matrix':\n matrix = Matrix.compose(matrix, {a: v[0], b: v[1], c: v[2], d: v[3], e: v[4], f: v[5]});\n break;\n case 'rotate':\n matrix = Matrix.compose(matrix, Matrix.rotateDEG(v[0], v[1] || 0, v[2] || 0));\n break;\n case 'translate':\n matrix = Matrix.compose(matrix, Matrix.translate(v[0], v[1] || 0));\n break;\n case 'scale':\n matrix = Matrix.compose(matrix, Matrix.scale(v[0], v[1] || v[0]));\n break;\n case 'skewX':\n matrix = Matrix.compose(matrix, Matrix.skewDEG(v[0], 0));\n break;\n case 'skewY':\n matrix = Matrix.compose(matrix, Matrix.skewDEG(0, v[0]));\n break;\n default:\n log.error(`Couldn't parse: ${command}`);\n }\n }\n return matrix;\n};\n\n// Adapted from paper.js's Matrix.decompose\n// Given a matrix, return the x and y scale factors of the matrix\nconst _getScaleFactor = function (matrix) {\n const a = matrix.a;\n const b = matrix.b;\n const c = matrix.c;\n const d = matrix.d;\n const det = (a * d) - (b * c);\n\n if (a !== 0 || b !== 0) {\n const r = Math.sqrt((a * a) + (b * b));\n return {x: r, y: det / r};\n }\n if (c !== 0 || d !== 0) {\n const s = Math.sqrt((c * c) + (d * d));\n return {x: det / s, y: s};\n }\n // a = b = c = d = 0\n return {x: 0, y: 0};\n};\n\n// Returns null if matrix is not invertible. Otherwise returns given ellipse\n// transformed by transform, an object {radiusX, radiusY, rotation}.\nconst _calculateTransformedEllipse = function (radiusX, radiusY, theta, transform) {\n theta = -theta * Math.PI / 180;\n const a = transform.a;\n const b = -transform.c;\n const c = -transform.b;\n const d = transform.d;\n // Since other parameters determine the translation of the ellipse in SVG, we do not need to worry\n // about what e and f are.\n const det = (a * d) - (b * c);\n // Non-invertible matrix\n if (det === 0) return null;\n\n // rotA, rotB, and rotC represent Ax^2 + Bxy + Cy^2 = 1 coefficients for a rotated ellipse formula\n const sinT = Math.sin(theta);\n const cosT = Math.cos(theta);\n const sin2T = Math.sin(2 * theta);\n const rotA = (cosT * cosT / radiusX / radiusX) + (sinT * sinT / radiusY / radiusY);\n const rotB = (sin2T / radiusX / radiusX) - (sin2T / radiusY / radiusY);\n const rotC = (sinT * sinT / radiusX / radiusX) + (cosT * cosT / radiusY / radiusY);\n\n // Calculate the ellipse formula of the transformed ellipse\n // A, B, and C represent Ax^2 + Bxy + Cy^2 = 1 / det / det coefficients in a transformed ellipse formula\n // scaled by inverse det squared (to preserve accuracy)\n const A = ((rotA * d * d) - (rotB * d * c) + (rotC * c * c));\n const B = ((-2 * rotA * b * d) + (rotB * a * d) + (rotB * b * c) - (2 * rotC * a * c));\n const C = ((rotA * b * b) - (rotB * a * b) + (rotC * a * a));\n\n // Derive new radii and theta from the transformed ellipse formula\n const newRadiusXOverDet = Math.sqrt(2) *\n Math.sqrt(\n (A + C - Math.sqrt((A * A) + (B * B) - (2 * A * C) + (C * C))) /\n ((-B * B) + (4 * A * C))\n );\n const newRadiusYOverDet = 1 / Math.sqrt(A + C - (1 / newRadiusXOverDet / newRadiusXOverDet));\n let temp = (A - (1 / newRadiusXOverDet / newRadiusXOverDet)) /\n ((1 / newRadiusYOverDet / newRadiusYOverDet) - (1 / newRadiusXOverDet / newRadiusXOverDet));\n if (temp < 0 && Math.abs(temp) < 1e-8) temp = 0; // Fix floating point issue\n temp = Math.sqrt(temp);\n if (Math.abs(1 - temp) < 1e-8) temp = 1; // Fix floating point issue\n // Solve for which of the two possible thetas is correct\n let newTheta = Math.asin(temp);\n temp = (B / (\n (1 / newRadiusXOverDet / newRadiusXOverDet) -\n (1 / newRadiusYOverDet / newRadiusYOverDet)));\n const newTheta2 = -newTheta;\n if (Math.abs(Math.sin(2 * newTheta2) - temp) <\n Math.abs(Math.sin(2 * newTheta) - temp)) {\n newTheta = newTheta2;\n }\n\n return {\n radiusX: newRadiusXOverDet * det,\n radiusY: newRadiusYOverDet * det,\n rotation: -newTheta * 180 / Math.PI\n };\n};\n\n// Adapted from paper.js's PathItem.setPathData\nconst _transformPath = function (pathString, transform) {\n if (!transform || Matrix.toString(transform) === Matrix.toString(Matrix.identity())) return pathString;\n // First split the path data into parts of command-coordinates pairs\n // Commands are any of these characters: mzlhvcsqta\n const parts = pathString && pathString.match(/[mlhvcsqtaz][^mlhvcsqtaz]*/ig);\n let coords;\n let relative = false;\n let previous;\n let control;\n let current = {x: 0, y: 0};\n let start = {x: 0, y: 0};\n let result = '';\n\n const getCoord = function (index, coord) {\n let val = +coords[index];\n if (relative) {\n val += current[coord];\n }\n return val;\n };\n\n const getPoint = function (index) {\n return {x: getCoord(index, 'x'), y: getCoord(index + 1, 'y')};\n };\n\n const roundTo4Places = function (num) {\n return Number(num.toFixed(4));\n };\n\n // Returns the transformed point as a string\n const getString = function (point) {\n const transformed = Matrix.applyToPoint(transform, point);\n return `${roundTo4Places(transformed.x)} ${roundTo4Places(transformed.y)} `;\n };\n\n for (let i = 0, l = parts && parts.length; i < l; i++) {\n const part = parts[i];\n const command = part[0];\n const lower = command.toLowerCase();\n // Match all coordinate values\n coords = part.match(/[+-]?(?:\\d*\\.\\d+|\\d+\\.?)(?:[eE][+-]?\\d+)?/g);\n const length = coords && coords.length;\n relative = command === lower;\n // Fix issues with z in the middle of SVG path data, not followed by\n // a m command, see paper.js#413:\n if (previous === 'z' && !/[mz]/.test(lower)) {\n result += `M ${current.x} ${current.y} `;\n }\n switch (lower) {\n case 'm': // Move to\n case 'l': // Line to\n {\n let move = lower === 'm';\n for (let j = 0; j < length; j += 2) {\n result += move ? 'M ' : 'L ';\n current = getPoint(j);\n result += getString(current);\n if (move) {\n start = current;\n move = false;\n }\n }\n control = current;\n break;\n }\n case 'h': // Horizontal line\n case 'v': // Vertical line\n {\n const coord = lower === 'h' ? 'x' : 'y';\n current = {x: current.x, y: current.y}; // Clone as we're going to modify it.\n for (let j = 0; j < length; j++) {\n current[coord] = getCoord(j, coord);\n result += `L ${getString(current)}`;\n }\n control = current;\n break;\n }\n case 'c':\n // Cubic Bezier curve\n for (let j = 0; j < length; j += 6) {\n const handle1 = getPoint(j);\n control = getPoint(j + 2);\n current = getPoint(j + 4);\n result += `C ${getString(handle1)}${getString(control)}${getString(current)}`;\n }\n break;\n case 's':\n // Smooth cubic Bezier curve\n for (let j = 0; j < length; j += 4) {\n const handle1 = /[cs]/.test(previous) ?\n {x: (current.x * 2) - control.x, y: (current.y * 2) - control.y} :\n current;\n control = getPoint(j);\n current = getPoint(j + 2);\n\n result += `C ${getString(handle1)}${getString(control)}${getString(current)}`;\n previous = lower;\n }\n break;\n case 'q':\n // Quadratic Bezier curve\n for (let j = 0; j < length; j += 4) {\n control = getPoint(j);\n current = getPoint(j + 2);\n result += `Q ${getString(control)}${getString(current)}`;\n }\n break;\n case 't':\n // Smooth quadratic Bezier curve\n for (let j = 0; j < length; j += 2) {\n control = /[qt]/.test(previous) ?\n {x: (current.x * 2) - control.x, y: (current.y * 2) - control.y} :\n current;\n current = getPoint(j);\n\n result += `Q ${getString(control)}${getString(current)}`;\n previous = lower;\n }\n break;\n case 'a':\n // Elliptical arc curve\n for (let j = 0; j < length; j += 7) {\n current = getPoint(j + 5);\n const rx = +coords[j];\n const ry = +coords[j + 1];\n const rotation = +coords[j + 2];\n const largeArcFlag = +coords[j + 3];\n let clockwiseFlag = +coords[j + 4];\n const newEllipse = _calculateTransformedEllipse(rx, ry, rotation, transform);\n const matrixScale = _getScaleFactor(transform);\n if (newEllipse) {\n if ((matrixScale.x > 0 && matrixScale.y < 0) ||\n (matrixScale.x < 0 && matrixScale.y > 0)) {\n clockwiseFlag = clockwiseFlag ^ 1;\n }\n result += `A ${roundTo4Places(Math.abs(newEllipse.radiusX))} ` +\n `${roundTo4Places(Math.abs(newEllipse.radiusY))} ` +\n `${roundTo4Places(newEllipse.rotation)} ${largeArcFlag} ` +\n `${clockwiseFlag} ${getString(current)}`;\n } else {\n result += `L ${getString(current)}`;\n }\n }\n break;\n case 'z':\n // Close path\n result += `Z `;\n // Correctly handle relative m commands, see paper.js#1101:\n current = start;\n break;\n }\n previous = lower;\n }\n return result;\n};\n\nconst GRAPHICS_ELEMENTS = ['circle', 'ellipse', 'image', 'line', 'path', 'polygon', 'polyline', 'rect', 'text', 'use'];\nconst CONTAINER_ELEMENTS = ['a', 'defs', 'g', 'marker', 'glyph', 'missing-glyph', 'pattern', 'svg', 'switch', 'symbol'];\nconst _isContainerElement = function (element) {\n return element.tagName && CONTAINER_ELEMENTS.includes(element.tagName.toLowerCase());\n};\nconst _isGraphicsElement = function (element) {\n return element.tagName && GRAPHICS_ELEMENTS.includes(element.tagName.toLowerCase());\n};\nconst _isPathWithTransformAndStroke = function (element, strokeWidth) {\n if (!element.attributes) return false;\n strokeWidth = element.attributes['stroke-width'] ?\n Number(element.attributes['stroke-width'].value) : Number(strokeWidth);\n return strokeWidth &&\n element.tagName && element.tagName.toLowerCase() === 'path' &&\n element.attributes.d && element.attributes.d.value;\n};\nconst _quadraticMean = function (a, b) {\n return Math.sqrt(((a * a) + (b * b)) / 2);\n};\n\nconst _createGradient = function (gradientId, svgTag, bbox, matrix) {\n // Adapted from Paper.js's SvgImport.getValue\n const getValue = function (node, name, isString, allowNull, allowPercent, defaultValue) {\n // Interpret value as number. Never return NaN, but 0 instead.\n // If the value is a sequence of numbers, parseFloat will\n // return the first occurring number, which is enough for now.\n let value = SvgElement.get(node, name);\n let res;\n if (value === null) {\n if (defaultValue) {\n res = defaultValue;\n if (/%\\s*$/.test(res)) {\n value = defaultValue;\n res = parseFloat(value);\n }\n } else if (allowNull) {\n res = null;\n } else if (isString) {\n res = '';\n } else {\n res = 0;\n }\n } else if (isString) {\n res = value;\n } else {\n res = parseFloat(value);\n }\n // Support for dimensions in percentage of the root size. If root-size\n // is not set (e.g. during <defs>), just scale the percentage value to\n // 0..1, as required by gradients with gradientUnits=\"objectBoundingBox\"\n if (/%\\s*$/.test(value)) {\n const size = allowPercent ? 1 : bbox[/x|^width/.test(name) ? 'width' : 'height'];\n return res / 100 * size;\n }\n return res;\n };\n const getPoint = function (node, x, y, allowNull, allowPercent, defaultX, defaultY) {\n x = getValue(node, x || 'x', false, allowNull, allowPercent, defaultX);\n y = getValue(node, y || 'y', false, allowNull, allowPercent, defaultY);\n return allowNull && (x === null || y === null) ? null : {x, y};\n };\n\n let defs = svgTag.getElementsByTagName('defs');\n if (defs.length === 0) {\n defs = SvgElement.create('defs');\n svgTag.appendChild(defs);\n } else {\n defs = defs[0];\n }\n\n // Clone the old gradient. We'll make a new one, since the gradient might be reused elsewhere\n // with different transform matrix\n const oldGradient = svgTag.getElementById(gradientId);\n if (!oldGradient) return;\n\n const radial = oldGradient.tagName.toLowerCase() === 'radialgradient';\n const newGradient = svgTag.getElementById(gradientId).cloneNode(true /* deep */);\n\n // Give the new gradient a new ID\n let matrixString = Matrix.toString(matrix);\n matrixString = matrixString.substring(8, matrixString.length - 1);\n const newGradientId = `${gradientId}-${matrixString}`;\n newGradient.setAttribute('id', newGradientId);\n\n // This gradient already exists and was transformed before. Just reuse the already-transformed one.\n if (svgTag.getElementById(newGradientId)) {\n // This is the same code as in the end of the function, but I don't feel like wrapping the next 80 lines\n // in an `if (!svgTag.getElementById(newGradientId))` block\n return `url(#${newGradientId})`;\n }\n\n const scaleToBounds = getValue(newGradient, 'gradientUnits', true) !==\n 'userSpaceOnUse';\n let origin;\n let destination;\n let radius;\n let focal;\n if (radial) {\n origin = getPoint(newGradient, 'cx', 'cy', false, scaleToBounds, '50%', '50%');\n radius = getValue(newGradient, 'r', false, false, scaleToBounds, '50%');\n focal = getPoint(newGradient, 'fx', 'fy', true, scaleToBounds);\n } else {\n origin = getPoint(newGradient, 'x1', 'y1', false, scaleToBounds);\n destination = getPoint(newGradient, 'x2', 'y2', false, scaleToBounds, '1');\n if (origin.x === destination.x && origin.y === destination.y) {\n // If it's degenerate, use the color of the last stop, as described by\n // https://www.w3.org/TR/SVG/pservers.html#LinearGradientNotes\n const stops = newGradient.getElementsByTagName('stop');\n if (!stops.length || !stops[stops.length - 1].attributes ||\n !stops[stops.length - 1].attributes['stop-color']) {\n return null;\n }\n return stops[stops.length - 1].attributes['stop-color'].value;\n }\n }\n\n // Transform points\n // Emulate SVG's gradientUnits=\"objectBoundingBox\"\n if (scaleToBounds) {\n const boundsMatrix = Matrix.compose(Matrix.translate(bbox.x, bbox.y), Matrix.scale(bbox.width, bbox.height));\n origin = Matrix.applyToPoint(boundsMatrix, origin);\n if (destination) destination = Matrix.applyToPoint(boundsMatrix, destination);\n if (radius) {\n radius = _quadraticMean(bbox.width, bbox.height) * radius;\n }\n if (focal) focal = Matrix.applyToPoint(boundsMatrix, focal);\n }\n\n if (radial) {\n origin = Matrix.applyToPoint(matrix, origin);\n const matrixScale = _getScaleFactor(matrix);\n radius = _quadraticMean(matrixScale.x, matrixScale.y) * radius;\n if (focal) focal = Matrix.applyToPoint(matrix, focal);\n } else {\n const dot = (a, b) => (a.x * b.x) + (a.y * b.y);\n const multiply = (coefficient, v) => ({x: coefficient * v.x, y: coefficient * v.y});\n const add = (a, b) => ({x: a.x + b.x, y: a.y + b.y});\n const subtract = (a, b) => ({x: a.x - b.x, y: a.y - b.y});\n\n // The line through origin and gradientPerpendicular is the line at which the gradient starts\n let gradientPerpendicular = Math.abs(origin.x - destination.x) < 1e-8 ?\n add(origin, {x: 1, y: (origin.x - destination.x) / (destination.y - origin.y)}) :\n add(origin, {x: (destination.y - origin.y) / (origin.x - destination.x), y: 1});\n\n // Transform points\n gradientPerpendicular = Matrix.applyToPoint(matrix, gradientPerpendicular);\n origin = Matrix.applyToPoint(matrix, origin);\n destination = Matrix.applyToPoint(matrix, destination);\n\n // Calculate the direction that the gradient has changed to\n const originToPerpendicular = subtract(gradientPerpendicular, origin);\n const originToDestination = subtract(destination, origin);\n const gradientDirection = Math.abs(originToPerpendicular.x) < 1e-8 ?\n {x: 1, y: -originToPerpendicular.x / originToPerpendicular.y} :\n {x: -originToPerpendicular.y / originToPerpendicular.x, y: 1};\n\n // Set the destination so that the gradient moves in the correct direction, by projecting the destination vector\n // onto the gradient direction vector\n const projectionCoeff = dot(originToDestination, gradientDirection) / dot(gradientDirection, gradientDirection);\n const projection = multiply(projectionCoeff, gradientDirection);\n destination = {x: origin.x + projection.x, y: origin.y + projection.y};\n }\n\n // Put values back into svg\n if (radial) {\n newGradient.setAttribute('cx', Number(origin.x.toFixed(4)));\n newGradient.setAttribute('cy', Number(origin.y.toFixed(4)));\n newGradient.setAttribute('r', Number(radius.toFixed(4)));\n if (focal) {\n newGradient.setAttribute('fx', Number(focal.x.toFixed(4)));\n newGradient.setAttribute('fy', Number(focal.y.toFixed(4)));\n }\n } else {\n newGradient.setAttribute('x1', Number(origin.x.toFixed(4)));\n newGradient.setAttribute('y1', Number(origin.y.toFixed(4)));\n newGradient.setAttribute('x2', Number(destination.x.toFixed(4)));\n newGradient.setAttribute('y2', Number(destination.y.toFixed(4)));\n }\n newGradient.setAttribute('gradientUnits', 'userSpaceOnUse');\n defs.appendChild(newGradient);\n\n return `url(#${newGradientId})`;\n};\n\n// Adapted from paper.js's SvgImport.getDefinition\nconst _parseUrl = (value, windowRef) => {\n // When url() comes from a style property, '#'' seems to be missing on\n // WebKit. We also get variations of quotes or no quotes, single or\n // double, so handle it all with one regular expression:\n const match = value && value.match(/\\((?:[\"'#]*)([^\"')]+)/);\n const name = match && match[1];\n const res = name && windowRef ?\n // This is required by Firefox, which can produce absolute\n // urls for local gradients, see paperjs#1001:\n name.replace(`${windowRef.location.href.split('#')[0]}#`, '') :\n name;\n return res;\n};\n\n/**\n * Scratch 2.0 displays stroke widths in a \"normalized\" way, that is,\n * if a shape with a stroke width has a transform applied, it will be\n * rendered with a stroke that is the same width all the way around,\n * instead of stretched looking.\n *\n * The vector paint editor also prefers to normalize the stroke width,\n * rather than keep track of transforms at the group level, as this\n * simplifies editing (e.g. stroke width 3 always means the same thickness)\n *\n * This function performs that normalization process, pushing transforms\n * on groups down to the leaf level and averaging out the stroke width\n * around the shapes. Note that this doens't just change stroke widths, it\n * changes path data and attributes throughout the SVG.\n * @param {SVGElement} svgTag The SVG dom object\n * @param {Window} windowRef The window to use. Need to pass in for\n * tests to work, as they get angry at even the mention of window.\n * @param {object} bboxForTesting The bounds to use. Need to pass in for\n * tests only, because getBBox doesn't work in Node. This should\n * be the bounds of the svgTag without including stroke width or transforms.\n * @returns {void}\n */\nconst transformStrokeWidths = function (svgTag, windowRef, bboxForTesting) {\n const inherited = Matrix.identity();\n\n const applyTransforms = (element, matrix, strokeWidth, fill, stroke) => {\n if (_isContainerElement(element)) {\n // Push fills and stroke width down to leaves\n if (element.attributes['stroke-width']) {\n strokeWidth = element.attributes['stroke-width'].value;\n }\n if (element.attributes) {\n if (element.attributes.fill) fill = element.attributes.fill.value;\n if (element.attributes.stroke) stroke = element.attributes.stroke.value;\n }\n\n // If any child nodes don't take attributes, leave the attributes\n // at the parent level.\n for (let i = 0; i < element.childNodes.length; i++) {\n applyTransforms(\n element.childNodes[i],\n Matrix.compose(matrix, _parseTransform(element)),\n strokeWidth,\n fill,\n stroke\n );\n }\n element.removeAttribute('transform');\n element.removeAttribute('stroke-width');\n element.removeAttribute('fill');\n element.removeAttribute('stroke');\n } else if (_isPathWithTransformAndStroke(element, strokeWidth)) {\n if (element.attributes['stroke-width']) {\n strokeWidth = element.attributes['stroke-width'].value;\n }\n if (element.attributes.fill) fill = element.attributes.fill.value;\n if (element.attributes.stroke) stroke = element.attributes.stroke.value;\n matrix = Matrix.compose(matrix, _parseTransform(element));\n if (Matrix.toString(matrix) === Matrix.toString(Matrix.identity())) {\n element.removeAttribute('transform');\n element.setAttribute('stroke-width', strokeWidth);\n if (fill) element.setAttribute('fill', fill);\n if (stroke) element.setAttribute('stroke', stroke);\n return;\n }\n\n // Transform gradient\n const fillGradientId = _parseUrl(fill, windowRef);\n const strokeGradientId = _parseUrl(stroke, windowRef);\n\n if (fillGradientId || strokeGradientId) {\n const doc = windowRef.document;\n // Need path bounds to transform gradient\n const svgSpot = doc.createElement('span');\n let bbox;\n if (bboxForTesting) {\n bbox = bboxForTesting;\n } else {\n try {\n doc.body.appendChild(svgSpot);\n const svg = SvgElement.set(doc.createElementNS(SvgElement.svg, 'svg'));\n const path = SvgElement.set(doc.createElementNS(SvgElement.svg, 'path'));\n path.setAttribute('d', element.attributes.d.value);\n svg.appendChild(path);\n svgSpot.appendChild(svg);\n // Take the bounding box.\n bbox = svg.getBBox();\n } finally {\n // Always destroy the element, even if, for example, getBBox throws.\n doc.body.removeChild(svgSpot);\n }\n }\n\n if (fillGradientId) {\n const newFillRef = _createGradient(fillGradientId, svgTag, bbox, matrix);\n if (newFillRef) fill = newFillRef;\n }\n\n if (strokeGradientId) {\n const newStrokeRef = _createGradient(strokeGradientId, svgTag, bbox, matrix);\n if (newStrokeRef) stroke = newStrokeRef;\n }\n }\n\n // Transform path data\n element.setAttribute('d', _transformPath(element.attributes.d.value, matrix));\n element.removeAttribute('transform');\n\n // Transform stroke width\n const matrixScale = _getScaleFactor(matrix);\n element.setAttribute('stroke-width', _quadraticMean(matrixScale.x, matrixScale.y) * strokeWidth);\n if (fill) element.setAttribute('fill', fill);\n if (stroke) element.setAttribute('stroke', stroke);\n } else if (_isGraphicsElement(element)) {\n // Push stroke width, fill, and stroke down to leaves\n if (strokeWidth && !element.attributes['stroke-width']) {\n element.setAttribute('stroke-width', strokeWidth);\n }\n if (fill && !element.attributes.fill) {\n element.setAttribute('fill', fill);\n }\n if (stroke && !element.attributes.stroke) {\n element.setAttribute('stroke', stroke);\n }\n\n // Push transform down to leaves\n matrix = Matrix.compose(matrix, _parseTransform(element));\n if (Matrix.toString(matrix) === Matrix.toString(Matrix.identity())) {\n element.removeAttribute('transform');\n } else {\n element.setAttribute('transform', Matrix.toString(matrix));\n }\n }\n };\n applyTransforms(svgTag, inherited, 1 /* default SVG stroke width */);\n};\n\nmodule.exports = transformStrokeWidths;\n","const {Logger} = require('tslog');\n\nmodule.exports = new Logger({\n name: 'scratch-svg-renderer'\n});\n","module.exports = require(\"base64-js\");","module.exports = require(\"css-tree\");","module.exports = require(\"fastestsmallesttextencoderdecoder\");","module.exports = require(\"isomorphic-dompurify\");","module.exports = require(\"scratch-render-fonts\");","module.exports = require(\"transformation-matrix\");","module.exports = require(\"tslog\");","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Check if module exists (development only)\n\tif (__webpack_modules__[moduleId] === undefined) {\n\t\tvar e = new Error(\"Cannot find module '\" + moduleId + \"'\");\n\t\te.code = 'MODULE_NOT_FOUND';\n\t\tthrow e;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(\"./src/index.js\");\n",""],"names":[],"sourceRoot":""}
|
|
1
|
+
{"version":3,"file":"scratch-svg-renderer.js","mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;ACVA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;;;;;;;;;AC3JA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AAEA;AACA;;;;;;;;;;AC5DA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;ACrCA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;ACjDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;ACrBA;AACA;AACA;AACA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;ACjUA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AAEA;AAEA;AAEA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAGA;AAGA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAEA;;;;;;;;;;ACnJA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;AClBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAGA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;ACtEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAMA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;ACxKA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AAKA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAGA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;;AAEA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;;AAEA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;;;;;;;;;;AClnBA;AAAA;AAAA;AAEA;AACA;AACA;;;;;;;;;;;ACJA;;;;;;;;;;;ACAA;;;;;;;;;;;ACAA;;;;;;;;;;;ACAA;;;;;;;;;;;ACAA;;;;;;;;;;;ACAA;;;;;;;;;;;ACAA;;;;;;ACAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AE7BA;AACA;AACA;AACA","sources":["webpack://ScratchSVGRenderer/webpack/universalModuleDefinition","webpack://ScratchSVGRenderer/./src/bitmap-adapter.js","webpack://ScratchSVGRenderer/./src/fixup-svg-string.js","webpack://ScratchSVGRenderer/./src/font-converter.js","webpack://ScratchSVGRenderer/./src/font-inliner.js","webpack://ScratchSVGRenderer/./src/index.js","webpack://ScratchSVGRenderer/./src/load-svg-string.js","webpack://ScratchSVGRenderer/./src/sanitize-svg.js","webpack://ScratchSVGRenderer/./src/serialize-svg-to-string.js","webpack://ScratchSVGRenderer/./src/svg-element.js","webpack://ScratchSVGRenderer/./src/svg-renderer.js","webpack://ScratchSVGRenderer/./src/transform-applier.js","webpack://ScratchSVGRenderer/./src/util/log.js","webpack://ScratchSVGRenderer/external commonjs \"base64-js\"","webpack://ScratchSVGRenderer/external commonjs \"css-tree\"","webpack://ScratchSVGRenderer/external commonjs \"fastestsmallesttextencoderdecoder\"","webpack://ScratchSVGRenderer/external commonjs \"isomorphic-dompurify\"","webpack://ScratchSVGRenderer/external commonjs \"scratch-render-fonts\"","webpack://ScratchSVGRenderer/external commonjs \"transformation-matrix\"","webpack://ScratchSVGRenderer/external commonjs \"tslog\"","webpack://ScratchSVGRenderer/webpack/bootstrap","webpack://ScratchSVGRenderer/webpack/before-startup","webpack://ScratchSVGRenderer/webpack/startup","webpack://ScratchSVGRenderer/webpack/after-startup"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ScratchSVGRenderer\"] = factory();\n\telse\n\t\troot[\"ScratchSVGRenderer\"] = factory();\n})(global, () => {\nreturn ","const base64js = require('base64-js');\n\n/**\n * Adapts Scratch 2.0 bitmaps for use in scratch 3.0\n */\nclass BitmapAdapter {\n /**\n * @param {?Function} makeImage HTML image constructor. Tests can provide this.\n * @param {?Function} makeCanvas HTML canvas constructor. Tests can provide this.\n */\n constructor (makeImage, makeCanvas) {\n this._makeImage = makeImage ? makeImage : () => new Image();\n this._makeCanvas = makeCanvas ? makeCanvas : () => document.createElement('canvas');\n }\n\n /**\n * Return a canvas with the resized version of the given image, done using nearest-neighbor interpolation\n * @param {CanvasImageSource} image The image to resize\n * @param {int} newWidth The desired post-resize width of the image\n * @param {int} newHeight The desired post-resize height of the image\n * @returns {HTMLCanvasElement} A canvas with the resized image drawn on it.\n */\n resize (image, newWidth, newHeight) {\n // We want to always resize using nearest-neighbor interpolation. However, canvas implementations are free to\n // use linear interpolation (or other \"smooth\" interpolation methods) when downscaling:\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1360415\n // It seems we can get around this by resizing in two steps: first width, then height. This will always result\n // in nearest-neighbor interpolation, even when downscaling.\n const stretchWidthCanvas = this._makeCanvas();\n stretchWidthCanvas.width = newWidth;\n stretchWidthCanvas.height = image.height;\n let context = stretchWidthCanvas.getContext('2d');\n context.imageSmoothingEnabled = false;\n context.drawImage(image, 0, 0, stretchWidthCanvas.width, stretchWidthCanvas.height);\n const stretchHeightCanvas = this._makeCanvas();\n stretchHeightCanvas.width = newWidth;\n stretchHeightCanvas.height = newHeight;\n context = stretchHeightCanvas.getContext('2d');\n context.imageSmoothingEnabled = false;\n context.drawImage(stretchWidthCanvas, 0, 0, stretchHeightCanvas.width, stretchHeightCanvas.height);\n return stretchHeightCanvas;\n }\n\n /**\n * Scratch 2.0 had resolution 1 and 2 bitmaps. All bitmaps in Scratch 3.0 are equivalent\n * to resolution 2 bitmaps. Therefore, converting a resolution 1 bitmap means doubling\n * it in width and height.\n * @param {!string} dataURI Base 64 encoded image data of the bitmap\n * @param {!Function} callback Node-style callback that returns updated dataURI if conversion succeeded\n */\n convertResolution1Bitmap (dataURI, callback) {\n const image = this._makeImage();\n image.src = dataURI;\n image.onload = () => {\n callback(null, this.resize(image, image.width * 2, image.height * 2).toDataURL());\n };\n image.onerror = () => {\n callback('Image load failed');\n };\n }\n\n /**\n * Given width/height of an uploaded item, return width/height the image will be resized\n * to in Scratch 3.0\n * @param {!number} oldWidth original width\n * @param {!number} oldHeight original height\n * @returns {object} Array of new width, new height\n */\n getResizedWidthHeight (oldWidth, oldHeight) {\n const STAGE_WIDTH = 480;\n const STAGE_HEIGHT = 360;\n const STAGE_RATIO = STAGE_WIDTH / STAGE_HEIGHT;\n\n // If both dimensions are smaller than or equal to corresponding stage dimension,\n // double both dimensions\n if ((oldWidth <= STAGE_WIDTH) && (oldHeight <= STAGE_HEIGHT)) {\n return {width: oldWidth * 2, height: oldHeight * 2};\n }\n\n // If neither dimension is larger than 2x corresponding stage dimension,\n // this is an in-between image, return it as is\n if ((oldWidth <= STAGE_WIDTH * 2) && (oldHeight <= STAGE_HEIGHT * 2)) {\n return {width: oldWidth, height: oldHeight};\n }\n\n const imageRatio = oldWidth / oldHeight;\n // Otherwise, figure out how to resize\n if (imageRatio >= STAGE_RATIO) {\n // Wide Image\n return {width: STAGE_WIDTH * 2, height: STAGE_WIDTH * 2 / imageRatio};\n }\n // In this case we have either:\n // - A wide image, but not with as big a ratio between width and height,\n // making it so that fitting the width to double stage size would leave\n // the height too big to fit in double the stage height\n // - A square image that's still larger than the double at least\n // one of the stage dimensions, so pick the smaller of the two dimensions (to fit)\n // - A tall image\n // In any of these cases, resize the image to fit the height to double the stage height\n return {width: STAGE_HEIGHT * 2 * imageRatio, height: STAGE_HEIGHT * 2};\n }\n\n /**\n * Given bitmap data, resize as necessary.\n * @param {ArrayBuffer | string} fileData Base 64 encoded image data of the bitmap\n * @param {string} fileType The MIME type of this file\n * @returns {Promise} Resolves to resized image data Uint8Array\n */\n importBitmap (fileData, fileType) {\n let dataURI = fileData;\n if (fileData instanceof ArrayBuffer) {\n dataURI = this.convertBinaryToDataURI(fileData, fileType);\n }\n return new Promise((resolve, reject) => {\n const image = this._makeImage();\n image.src = dataURI;\n image.onload = () => {\n const newSize = this.getResizedWidthHeight(image.width, image.height);\n if (newSize.width === image.width && newSize.height === image.height) {\n // No change\n resolve(this.convertDataURIToBinary(dataURI));\n } else {\n const resizedDataURI = this.resize(image, newSize.width, newSize.height).toDataURL();\n resolve(this.convertDataURIToBinary(resizedDataURI));\n }\n };\n image.onerror = () => {\n // TODO: reject with an Error (breaking API change!)\n // eslint-disable-next-line prefer-promise-reject-errors\n reject('Image load failed');\n };\n });\n }\n\n // TODO consolidate with scratch-vm/src/util/base64-util.js\n // From https://gist.github.com/borismus/1032746\n convertDataURIToBinary (dataURI) {\n const BASE64_MARKER = ';base64,';\n const base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;\n const base64 = dataURI.substring(base64Index);\n const raw = window.atob(base64);\n const rawLength = raw.length;\n const array = new Uint8Array(new ArrayBuffer(rawLength));\n\n for (let i = 0; i < rawLength; i++) {\n array[i] = raw.charCodeAt(i);\n }\n return array;\n }\n\n convertBinaryToDataURI (arrayBuffer, contentType) {\n return `data:${contentType};base64,${base64js.fromByteArray(new Uint8Array(arrayBuffer))}`;\n }\n}\n\nmodule.exports = BitmapAdapter;\n","/**\n * Fixup svg string prior to parsing.\n * @param {!string} svgString String of the svg to fix.\n * @returns {!string} fixed svg that should be parseable.\n */\nmodule.exports = function (svgString) {\n // Add root svg namespace if it does not exist.\n const svgAttrs = svgString.match(/<svg [^>]*>/);\n if (svgAttrs && svgAttrs[0].indexOf('xmlns=') === -1) {\n svgString = svgString.replace('<svg ', '<svg xmlns=\"http://www.w3.org/2000/svg\" ');\n }\n\n // There are some SVGs from Illustrator that use undeclared entities.\n // Just replace those entities with fake namespace references to prevent\n // DOMParser from crashing\n if (svgAttrs && svgAttrs[0].indexOf('&ns_') !== -1 && svgString.indexOf('<!DOCTYPE') === -1) {\n svgString = svgString.replace(svgAttrs[0],\n svgAttrs[0].replace(/&ns_[^;]+;/g, 'http://ns.adobe.com/Extensibility/1.0/'));\n }\n\n // Some SVGs exported from Photoshop have been found to have an invalid mime type\n // Chrome and Safari won't render these SVGs, so we correct it here\n if (svgString.includes('data:img/png')) {\n svgString = svgString.replace(\n // capture entire image tag with xlink:href=and the quote - dont capture data: bit\n /(<image[^>]+?xlink:href=[\"'])data:img\\/png/g,\n // use the captured <image ..... xlink:href=\" then append the right data uri mime type\n ($0, $1) => `${$1}data:image/png`\n );\n }\n\n // Some SVGs from Inkscape attempt to bind a prefix to a reserved namespace name.\n // This will cause SVG parsing to fail, so replace these with a dummy namespace name.\n // This namespace name is only valid for \"xml\", and if we bind \"xmlns:xml\" to the dummy namespace,\n // parsing will fail yet again, so exclude \"xmlns:xml\" declarations.\n const xmlnsRegex = /(<[^>]+?xmlns:(?!xml=)[^ ]+=)\"http:\\/\\/www.w3.org\\/XML\\/1998\\/namespace\"/g;\n if (svgString.match(xmlnsRegex) !== null) {\n svgString = svgString.replace(\n // capture the entire attribute\n xmlnsRegex,\n // use the captured attribute name; replace only the URL\n ($0, $1) => `${$1}\"http://dummy.namespace\"`\n );\n }\n\n // Strip `svg:` prefix (sometimes added by Inkscape) from all tags. They interfere with DOMPurify (prefixed tag\n // names are not recognized) and the paint editor.\n // This matches opening and closing tags--the capture group captures the slash if it exists, and it is reinserted\n // in the replacement text.\n svgString = svgString.replace(/<(\\/?)\\s*svg:/g, '<$1');\n\n // The <metadata> element is not needed for rendering and sometimes contains\n // unparseable garbage from Illustrator :( Empty out the contents.\n // Note: [\\s\\S] matches everything including newlines, which .* does not\n svgString = svgString.replace(/<metadata>[\\s\\S]*<\\/metadata>/, '<metadata></metadata>');\n\n // Empty script tags and javascript executing\n svgString = svgString.replace(/<script[\\s\\S]*>[\\s\\S]*<\\/script>/, '<script></script>');\n\n return svgString;\n};\n","/**\n * @fileOverview Convert 2.0 fonts to 3.0 fonts.\n */\n\n/**\n * Given an SVG, replace Scratch 2.0 fonts with new 3.0 fonts. Add defaults where there are none.\n * @param {SVGElement} svgTag The SVG dom object\n * @returns {void}\n */\nconst convertFonts = function (svgTag) {\n // Collect all text elements into a list.\n const textElements = [];\n const collectText = domElement => {\n if (domElement.localName === 'text') {\n textElements.push(domElement);\n }\n for (let i = 0; i < domElement.childNodes.length; i++) {\n collectText(domElement.childNodes[i]);\n }\n };\n collectText(svgTag);\n // If there's an old font-family, switch to the new one.\n for (const textElement of textElements) {\n // If there's no font-family provided, provide one.\n if (!textElement.getAttribute('font-family') ||\n textElement.getAttribute('font-family') === 'Helvetica') {\n textElement.setAttribute('font-family', 'Sans Serif');\n } else if (textElement.getAttribute('font-family') === 'Mystery') {\n textElement.setAttribute('font-family', 'Curly');\n } else if (textElement.getAttribute('font-family') === 'Gloria') {\n textElement.setAttribute('font-family', 'Handwriting');\n } else if (textElement.getAttribute('font-family') === 'Donegal') {\n textElement.setAttribute('font-family', 'Serif');\n }\n }\n};\n\nmodule.exports = convertFonts;\n","/**\n * @fileOverview Import bitmap data into Scratch 3.0, resizing image as necessary.\n */\nconst getFonts = require('scratch-render-fonts');\n\n/**\n * Given SVG data, inline the fonts. This allows them to be rendered correctly when set\n * as the source of an HTMLImageElement. Here is a note from tmickel:\n * // Inject fonts that are needed.\n * // It would be nice if there were another way to get the SVG-in-canvas\n * // to render the correct font family, but I couldn't find any other way.\n * // Other things I tried:\n * // Just injecting the font-family into the document: no effect.\n * // External stylesheet linked to by SVG: no effect.\n * // Using a <link> or <style>@import</style> to link to font-family\n * // injected into the document: no effect.\n * @param {string} svgString The string representation of the svg to modify\n * @returns {string} The svg with any needed fonts inlined\n */\nconst inlineSvgFonts = function (svgString) {\n const FONTS = getFonts();\n // Make it clear that this function only operates on strings.\n // If we don't explicitly throw this here, the function silently fails.\n if (typeof svgString !== 'string') {\n throw new Error('SVG to be inlined is not a string');\n }\n\n // Collect fonts that need injection.\n const fontsNeeded = new Set();\n const fontRegex = /font-family=\"([^\"]*)\"/g;\n let matches = fontRegex.exec(svgString);\n while (matches) {\n fontsNeeded.add(matches[1]);\n matches = fontRegex.exec(svgString);\n }\n if (fontsNeeded.size > 0) {\n let str = '<defs><style>';\n for (const font of fontsNeeded) {\n if (Object.prototype.hasOwnProperty.call(FONTS, font)) {\n str += `${FONTS[font]}`;\n }\n }\n str += '</style></defs>';\n svgString = svgString.replace(/<svg[^>]*>/, `$&${str}`);\n return svgString;\n }\n return svgString;\n};\n\nmodule.exports = inlineSvgFonts;\n","const SVGRenderer = require('./svg-renderer');\nconst BitmapAdapter = require('./bitmap-adapter');\nconst inlineSvgFonts = require('./font-inliner');\nconst loadSvgString = require('./load-svg-string');\nconst sanitizeSvg = require('./sanitize-svg');\nconst serializeSvgToString = require('./serialize-svg-to-string');\nconst SvgElement = require('./svg-element');\nconst convertFonts = require('./font-converter');\n// /**\n// * Export for NPM & Node.js\n// * @type {RenderWebGL}\n// */\nmodule.exports = {\n BitmapAdapter: BitmapAdapter,\n convertFonts: convertFonts,\n inlineSvgFonts: inlineSvgFonts,\n loadSvgString: loadSvgString,\n sanitizeSvg: sanitizeSvg,\n serializeSvgToString: serializeSvgToString,\n SvgElement: SvgElement,\n SVGRenderer: SVGRenderer\n};\n","const SvgElement = require('./svg-element');\nconst convertFonts = require('./font-converter');\nconst transformStrokeWidths = require('./transform-applier');\nconst {sanitizeSvgText} = require('./sanitize-svg');\n\n/**\n * @param {SVGElement} svgTag the tag to search within\n * @param {string} [tagName] svg tag to search for (or collect all elements if not given)\n * @returns {Array} a list of elements with the given tagname\n */\nconst collectElements = (svgTag, tagName) => {\n const elts = [];\n const collectElementsInner = domElement => {\n if ((domElement.localName === tagName || typeof tagName === 'undefined') && domElement.getAttribute) {\n elts.push(domElement);\n }\n for (let i = 0; i < domElement.childNodes.length; i++) {\n collectElementsInner(domElement.childNodes[i]);\n }\n };\n collectElementsInner(svgTag);\n return elts;\n};\n\n/**\n * Fix SVGs to comply with SVG spec. Scratch 2 defaults to x2 = 0 when x2 is missing, but\n * SVG defaults to x2 = 1 when missing.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst transformGradients = svgTag => {\n const linearGradientElements = collectElements(svgTag, 'linearGradient');\n\n // For each gradient element, supply x2 if necessary.\n for (const gradientElement of linearGradientElements) {\n if (!gradientElement.getAttribute('x2')) {\n gradientElement.setAttribute('x2', '0');\n }\n }\n};\n\n/**\n * Fix SVGs to match appearance in Scratch 2, which used nearest neighbor scaling for bitmaps\n * within SVGs.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst transformImages = svgTag => {\n const imageElements = collectElements(svgTag, 'image');\n\n // For each image element, set image rendering to pixelated\n const pixelatedImages = 'image-rendering: optimizespeed; image-rendering: pixelated;';\n for (const elt of imageElements) {\n if (elt.getAttribute('style')) {\n elt.setAttribute('style',\n `${pixelatedImages} ${elt.getAttribute('style')}`);\n } else {\n elt.setAttribute('style', pixelatedImages);\n }\n }\n};\n\n/**\n * Transforms an SVG's text elements for Scratch 2.0 quirks.\n * These quirks include:\n * 1. `x` and `y` properties are removed/ignored.\n * 2. Alignment is set to `text-before-edge`.\n * 3. Line-breaks are converted to explicit <tspan> elements.\n * 4. Any required fonts are injected.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst transformText = svgTag => {\n // Collect all text elements into a list.\n const textElements = [];\n const collectText = domElement => {\n if (domElement.localName === 'text') {\n textElements.push(domElement);\n }\n for (let i = 0; i < domElement.childNodes.length; i++) {\n collectText(domElement.childNodes[i]);\n }\n };\n collectText(svgTag);\n convertFonts(svgTag);\n // For each text element, apply quirks.\n for (const textElement of textElements) {\n // Remove x and y attributes - they are not used in Scratch.\n textElement.removeAttribute('x');\n textElement.removeAttribute('y');\n // Set text-before-edge alignment:\n // Scratch renders all text like this.\n textElement.setAttribute('alignment-baseline', 'text-before-edge');\n textElement.setAttribute('xml:space', 'preserve');\n // If there's no font size provided, provide one.\n if (!textElement.getAttribute('font-size')) {\n textElement.setAttribute('font-size', '18');\n }\n let text = textElement.textContent;\n\n // Fix line breaks in text, which are not natively supported by SVG.\n // Only fix if text does not have child tspans.\n // @todo this will not work for font sizes with units such as em, percent\n // However, text made in scratch 2 should only ever export size 22 font.\n const fontSize = parseFloat(textElement.getAttribute('font-size'));\n const tx = 2;\n let ty = 0;\n let spacing = 1.2;\n // Try to match the position and spacing of Scratch 2.0's fonts.\n // Different fonts seem to use different line spacing.\n // Scratch 2 always uses alignment-baseline=text-before-edge\n // However, most SVG readers don't support this attribute\n // or don't support it alongside use of tspan, so the translations\n // here are to make up for that.\n if (textElement.getAttribute('font-family') === 'Handwriting') {\n spacing = 2;\n ty = -11 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Scratch') {\n spacing = 0.89;\n ty = -3 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Curly') {\n spacing = 1.38;\n ty = -6 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Marker') {\n spacing = 1.45;\n ty = -6 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Sans Serif') {\n spacing = 1.13;\n ty = -3 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Serif') {\n spacing = 1.25;\n ty = -4 * fontSize / 22;\n }\n\n if (textElement.transform.baseVal.numberOfItems === 0) {\n const transform = svgTag.createSVGTransform();\n textElement.transform.baseVal.appendItem(transform);\n }\n\n // Right multiply matrix by a translation of (tx, ty)\n const mtx = textElement.transform.baseVal.getItem(0).matrix;\n mtx.e += (mtx.a * tx) + (mtx.c * ty);\n mtx.f += (mtx.b * tx) + (mtx.d * ty);\n\n if (text && textElement.childElementCount === 0) {\n textElement.textContent = '';\n const lines = text.split('\\n');\n text = '';\n for (const line of lines) {\n const tspanNode = SvgElement.create('tspan');\n tspanNode.setAttribute('x', '0');\n tspanNode.setAttribute('style', 'white-space: pre');\n tspanNode.setAttribute('dy', `${spacing}em`);\n tspanNode.textContent = line ? line : ' ';\n textElement.appendChild(tspanNode);\n }\n }\n }\n};\n\n/**\n * Find the largest stroke width in the svg. If a shape has no\n * `stroke` property, it has a stroke-width of 0. If it has a `stroke`,\n * it is by default a stroke-width of 1.\n * This is used to enlarge the computed bounding box, which doesn't take\n * stroke width into account.\n * @param {SVGSVGElement} rootNode The root SVG node to traverse.\n * @returns {number} The largest stroke width in the SVG.\n */\nconst findLargestStrokeWidth = rootNode => {\n let largestStrokeWidth = 0;\n const collectStrokeWidths = domElement => {\n if (domElement.getAttribute) {\n if (domElement.getAttribute('stroke')) {\n largestStrokeWidth = Math.max(largestStrokeWidth, 1);\n }\n if (domElement.getAttribute('stroke-width')) {\n largestStrokeWidth = Math.max(\n largestStrokeWidth,\n Number(domElement.getAttribute('stroke-width')) || 0\n );\n }\n }\n for (let i = 0; i < domElement.childNodes.length; i++) {\n collectStrokeWidths(domElement.childNodes[i]);\n }\n };\n collectStrokeWidths(rootNode);\n return largestStrokeWidth;\n};\n\n/**\n * Transform the measurements of the SVG.\n * In Scratch 2.0, SVGs are drawn without respect to the width,\n * height, and viewBox attribute on the tag. The exporter\n * does output these properties - but they appear to be incorrect often.\n * To address the incorrect measurements, we append the DOM to the\n * document, and then use SVG's native `getBBox` to find the real\n * drawn dimensions. This ensures things drawn in negative dimensions,\n * outside the given viewBox, etc., are all eventually drawn to the canvas.\n * I tried to do this several other ways: stripping the width/height/viewBox\n * attributes and then drawing (Firefox won't draw anything),\n * or inflating them and then measuring a canvas. But this seems to be\n * a natural and performant way.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst transformMeasurements = svgTag => {\n // Append the SVG dom to the document.\n // This allows us to use `getBBox` on the page,\n // which returns the full bounding-box of all drawn SVG\n // elements, similar to how Scratch 2.0 did measurement.\n const svgSpot = document.createElement('span');\n let bbox;\n try {\n // Insert sanitized value.\n svgSpot.innerHTML = svgTag.outerHTML;\n document.body.appendChild(svgSpot);\n // Take the bounding box. We have to get elements via svgSpot\n // because we added it via innerHTML.\n bbox = svgSpot.children[0].getBBox();\n } finally {\n // Always destroy the element, even if, for example, getBBox throws.\n document.body.removeChild(svgSpot);\n }\n\n // Enlarge the bbox from the largest found stroke width\n // This may have false-positives, but at least the bbox will always\n // contain the full graphic including strokes.\n // If the width or height is zero however, don't enlarge since\n // they won't have a stroke width that needs to be enlarged.\n let halfStrokeWidth;\n if (bbox.width === 0 || bbox.height === 0) {\n halfStrokeWidth = 0;\n } else {\n halfStrokeWidth = findLargestStrokeWidth(svgTag) / 2;\n }\n const width = bbox.width + (halfStrokeWidth * 2);\n const height = bbox.height + (halfStrokeWidth * 2);\n const x = bbox.x - halfStrokeWidth;\n const y = bbox.y - halfStrokeWidth;\n\n // Set the correct measurements on the SVG tag\n svgTag.setAttribute('width', width);\n svgTag.setAttribute('height', height);\n svgTag.setAttribute('viewBox',\n `${x} ${y} ${width} ${height}`);\n};\n\n/**\n * Find all instances of a URL-referenced `stroke` in the svg. In 2.0, all gradient strokes\n * have a round `stroke-linejoin` and `stroke-linecap`... for some reason.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst setGradientStrokeRoundedness = svgTag => {\n const elements = collectElements(svgTag);\n\n for (const elt of elements) {\n if (!elt.style) continue;\n const stroke = elt.style.stroke || elt.getAttribute('stroke');\n if (stroke && stroke.match(/^url\\(#.*\\)$/)) {\n elt.style['stroke-linejoin'] = 'round';\n elt.style['stroke-linecap'] = 'round';\n }\n }\n};\n\n/**\n * In-place, convert passed SVG to something consistent that will be rendered the way we want them to be.\n * @param {SVGSvgElement} svgTag root SVG node to operate upon\n * @param {boolean} [fromVersion2] True if we should perform conversion from version 2 to version 3 svg.\n */\nconst normalizeSvg = (svgTag, fromVersion2) => {\n if (fromVersion2) {\n // Fix gradients. Scratch 2 exports no x2 when x2 = 0, but\n // SVG default is that x2 is 1. This must be done before\n // transformStrokeWidths since transformStrokeWidths affects\n // gradients.\n transformGradients(svgTag);\n }\n transformStrokeWidths(svgTag, window);\n transformImages(svgTag);\n if (fromVersion2) {\n // Transform all text elements.\n transformText(svgTag);\n // Transform measurements.\n transformMeasurements(svgTag);\n // Fix stroke roundedness.\n setGradientStrokeRoundedness(svgTag);\n } else if (!svgTag.getAttribute('viewBox')) {\n // Renderer expects a view box.\n transformMeasurements(svgTag);\n } else if (!svgTag.getAttribute('width') || !svgTag.getAttribute('height')) {\n svgTag.setAttribute('width', svgTag.viewBox.baseVal.width);\n svgTag.setAttribute('height', svgTag.viewBox.baseVal.height);\n }\n};\n\n/**\n * Load an SVG string and normalize it. All the steps before drawing/measuring.\n * Currently, this will normalize stroke widths (see transform-applier.js) and render all embedded images pixelated.\n * The returned SVG will be guaranteed to always have a `width`, `height` and `viewBox`.\n * In addition, if the `fromVersion2` parameter is `true`, several \"quirks-mode\" transformations will be applied which\n * mimic Scratch 2.0's SVG rendering.\n * @param {!string} svgString String of SVG data to draw in quirks-mode.\n * @param {boolean} [fromVersion2] True if we should perform conversion from version 2 to version 3 svg.\n * @returns {SVGSVGElement} The normalized SVG element.\n */\nconst loadSvgString = (svgString, fromVersion2) => {\n // Parse string into SVG XML.\n const parser = new DOMParser();\n\n // Since we're adding user-provided SVG to document.body as part of normalization,\n // sanitization is required. This should not affect bounding box calculation.\n const sanitizedSvgString = sanitizeSvgText(svgString);\n const svgDom = parser.parseFromString(sanitizedSvgString, 'text/xml');\n if (svgDom.childNodes.length < 1 ||\n svgDom.documentElement.localName !== 'svg') {\n throw new Error('Document does not appear to be SVG.');\n }\n const svgTag = svgDom.documentElement;\n normalizeSvg(svgTag, fromVersion2);\n return svgTag;\n};\n\nmodule.exports = loadSvgString;\n","/**\n * @fileOverview Sanitize the content of an SVG aggressively, to make it as safe\n * as possible\n */\nconst fixupSvgString = require('./fixup-svg-string');\nconst {generate, parse, walk} = require('css-tree');\nconst DOMPurify = require('isomorphic-dompurify');\n\nconst sanitizeSvg = {};\n\nconst isInternalRef = ref => ref.startsWith('#') || ref.startsWith('data:');\n\nDOMPurify.addHook(\n 'beforeSanitizeAttributes',\n currentNode => {\n\n if (currentNode && currentNode.href && currentNode.href.baseVal) {\n const href = currentNode.href.baseVal.replace(/\\s/g, '');\n // \"data:\" and \"#\" are valid hrefs\n if (!isInternalRef(href)) {\n // TODO: Those can be in different namespaces than `xlink:`\n if (currentNode.attributes.getNamedItem('xlink:href')) {\n currentNode.attributes.removeNamedItem('xlink:href');\n delete currentNode['xlink:href'];\n }\n if (currentNode.attributes.getNamedItem('href')) {\n currentNode.attributes.removeNamedItem('href');\n delete currentNode.href;\n }\n }\n }\n\n // Remove url(...) usages with external references\n if (currentNode && currentNode.attributes) {\n for (let i = currentNode.attributes.length - 1; i >= 0; i--) {\n const attr = currentNode.attributes[i];\n const rawValue = attr.value || '';\n const value = rawValue.toLowerCase().replace(/\\s/g, '');\n \n const urlMatch = value.match(/url\\((.+?)\\)/);\n if (urlMatch) {\n const ref = urlMatch[1].replace(/['\"]/g, '');\n if (!isInternalRef(ref)) {\n currentNode.removeAttribute(attr.name);\n }\n }\n }\n }\n \n return currentNode;\n }\n);\n\nDOMPurify.addHook(\n 'uponSanitizeElement',\n (node, data) => {\n if (data.tagName === 'style') {\n const ast = parse(node.textContent);\n let isModified = false;\n\n walk(ast, (astNode, item, list) => {\n // @import rules\n if (astNode.type === 'Atrule' && astNode.name.toLowerCase() === 'import') {\n list.remove(item);\n isModified = true;\n }\n \n // Elements using url(...) for external resources\n if (astNode.type === 'Declaration' && astNode.value) {\n let shouldRemove = false;\n walk(astNode.value, valueNode => {\n if (valueNode.type === 'Url') {\n const urlValue = (valueNode.value.value || '').trim().replace(/['\"]/g, '');\n \n if (!isInternalRef(urlValue)) {\n shouldRemove = true;\n }\n }\n });\n\n if (shouldRemove) {\n list.remove(item);\n isModified = true;\n }\n }\n });\n\n if (isModified) {\n node.textContent = generate(ast);\n }\n }\n }\n);\n\n// Use JS implemented TextDecoder and TextEncoder if it is not provided by the\n// browser.\nlet _TextDecoder;\nlet _TextEncoder;\nif (typeof TextDecoder === 'undefined' || typeof TextEncoder === 'undefined') {\n // Wait to require the text encoding polyfill until we know it's needed.\n \n const encoding = require('fastestsmallesttextencoderdecoder');\n _TextDecoder = encoding.TextDecoder;\n _TextEncoder = encoding.TextEncoder;\n} else {\n _TextDecoder = TextDecoder;\n _TextEncoder = TextEncoder;\n}\n\n/**\n * Load an SVG Uint8Array of bytes and \"sanitize\" it\n * @param {!Uint8Array} rawData unsanitized SVG daata\n * @returns {Uint8Array} sanitized SVG data\n */\nsanitizeSvg.sanitizeByteStream = function (rawData) {\n const decoder = new _TextDecoder();\n const encoder = new _TextEncoder();\n const sanitizedText = sanitizeSvg.sanitizeSvgText(decoder.decode(rawData));\n return encoder.encode(sanitizedText);\n};\n\n/**\n * Load an SVG string and \"sanitize\" it. This is more aggressive than the handling in\n * fixup-svg-string.js, and thus more risky; there are known examples of SVGs that\n * it will clobber. We use DOMPurify's svg profile, which restricts many types of tag.\n * @param {!string} rawSvgText unsanitized SVG string\n * @returns {string} sanitized SVG text\n */\nsanitizeSvg.sanitizeSvgText = function (rawSvgText) {\n let sanitizedText = DOMPurify.sanitize(rawSvgText, {\n USE_PROFILES: {svg: true},\n FORBID_TAGS: ['a', 'audio', 'canvas', 'video'],\n // Allow data URI in image tags (e.g. SVGs converted from bitmap)\n ADD_DATA_URI_TAGS: ['image']\n });\n\n // Remove partial XML comment that is sometimes left in the HTML\n const badTag = sanitizedText.indexOf(']>');\n if (badTag >= 0) {\n sanitizedText = sanitizedText.substring(5, sanitizedText.length);\n }\n\n // also use our custom fixup rules\n sanitizedText = fixupSvgString(sanitizedText);\n return sanitizedText;\n};\n\nmodule.exports = sanitizeSvg;\n","const inlineSvgFonts = require('./font-inliner');\n\n/**\n * Serialize a given SVG DOM to a string.\n * @param {SVGSVGElement} svgTag The SVG element to serialize.\n * @param {?boolean} shouldInjectFonts True if fonts should be included in the SVG as\n * base64 data.\n * @returns {string} String representing current SVG data.\n */\nconst serializeSvgToString = (svgTag, shouldInjectFonts) => {\n const serializer = new XMLSerializer();\n let string = serializer.serializeToString(svgTag);\n if (shouldInjectFonts) {\n string = inlineSvgFonts(string);\n }\n return string;\n};\n\nmodule.exports = serializeSvgToString;\n","/* Adapted from\n * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.\n * http://paperjs.org/\n *\n * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey\n * http://scratchdisk.com/ & http://jonathanpuckey.com/\n *\n * Distributed under the MIT license. See LICENSE file for details.\n *\n * All rights reserved.\n */\n\n/**\n * @name SvgElement\n * @namespace\n * @private\n */\nclass SvgElement {\n // SVG related namespaces\n static get svg () {\n return 'http://www.w3.org/2000/svg';\n }\n static get xmlns () {\n return 'http://www.w3.org/2000/xmlns';\n }\n static get xlink () {\n return 'http://www.w3.org/1999/xlink';\n }\n\n // Mapping of attribute names to required namespaces:\n static attributeNamespace () {\n return {\n 'href': SvgElement.xlink,\n 'xlink': SvgElement.xmlns,\n // Only the xmlns attribute needs the trailing slash. See #984\n 'xmlns': `${SvgElement.xmlns}/`,\n // IE needs the xmlns namespace when setting 'xmlns:xlink'. See #984\n 'xmlns:xlink': `${SvgElement.xmlns}/`\n };\n }\n\n static create (tag, attributes, formatter) {\n return SvgElement.set(document.createElementNS(SvgElement.svg, tag), attributes, formatter);\n }\n\n static get (node, name) {\n const namespace = SvgElement.attributeNamespace[name];\n const value = namespace ?\n node.getAttributeNS(namespace, name) :\n node.getAttribute(name);\n return value === 'null' ? null : value;\n }\n\n static set (node, attributes, formatter) {\n for (const name in attributes) {\n let value = attributes[name];\n const namespace = SvgElement.attributeNamespace[name];\n if (typeof value === 'number' && formatter) {\n value = formatter.number(value);\n }\n if (namespace) {\n node.setAttributeNS(namespace, name, value);\n } else {\n node.setAttribute(name, value);\n }\n }\n return node;\n }\n}\n\nmodule.exports = SvgElement;\n","const loadSvgString = require('./load-svg-string');\nconst serializeSvgToString = require('./serialize-svg-to-string');\n\n/**\n * Main quirks-mode SVG rendering code.\n * @deprecated Call into individual methods exported from this library instead.\n */\nclass SvgRenderer {\n /**\n * Create a quirks-mode SVG renderer for a particular canvas.\n * @param {HTMLCanvasElement} [canvas] An optional canvas element to draw to. If this is not provided, the renderer\n * will create a new canvas.\n * @class\n */\n constructor (canvas) {\n /**\n * The canvas that this SVG renderer will render to.\n * @type {HTMLCanvasElement}\n * @private\n */\n this._canvas = canvas || document.createElement('canvas');\n this._context = this._canvas.getContext('2d');\n\n /**\n * A measured SVG \"viewbox\"\n * @typedef {object} SvgRenderer#SvgMeasurements\n * @property {number} x - The left edge of the SVG viewbox.\n * @property {number} y - The top edge of the SVG viewbox.\n * @property {number} width - The width of the SVG viewbox.\n * @property {number} height - The height of the SVG viewbox.\n */\n\n /**\n * The measurement box of the currently loaded SVG.\n * @type {SvgRenderer#SvgMeasurements}\n * @private\n */\n this._measurements = {x: 0, y: 0, width: 0, height: 0};\n\n /**\n * The `<img>` element with the contents of the currently loaded SVG.\n * @type {?HTMLImageElement}\n * @private\n */\n this._cachedImage = null;\n\n /**\n * True if this renderer's current SVG is loaded and can be rendered to the canvas.\n * @type {boolean}\n */\n this.loaded = false;\n }\n\n /**\n * @returns {!HTMLCanvasElement} this renderer's target canvas.\n */\n get canvas () {\n return this._canvas;\n }\n\n /**\n * @returns {Array<number>} the natural size, in Scratch units, of this SVG.\n */\n get size () {\n return [this._measurements.width, this._measurements.height];\n }\n\n /**\n * @returns {Array<number>} the offset (upper left corner) of the SVG's view box.\n */\n get viewOffset () {\n return [this._measurements.x, this._measurements.y];\n }\n\n /**\n * Load an SVG string and normalize it. All the steps before drawing/measuring.\n * @param {!string} svgString String of SVG data to draw in quirks-mode.\n * @param {?boolean} fromVersion2 True if we should perform conversion from\n * version 2 to version 3 svg.\n */\n loadString (svgString, fromVersion2) {\n // New svg string invalidates the cached image\n this._cachedImage = null;\n const svgTag = loadSvgString(svgString, fromVersion2);\n\n this._svgTag = svgTag;\n this._measurements = {\n width: svgTag.viewBox.baseVal.width,\n height: svgTag.viewBox.baseVal.height,\n x: svgTag.viewBox.baseVal.x,\n y: svgTag.viewBox.baseVal.y\n };\n }\n\n /**\n * Load an SVG string, normalize it, and prepare it for (synchronous) rendering.\n * @param {!string} svgString String of SVG data to draw in quirks-mode.\n * @param {?boolean} fromVersion2 True if we should perform conversion from version 2 to version 3 svg.\n * @param {Function} [onFinish] - An optional callback to call when the SVG is loaded and can be rendered.\n */\n loadSVG (svgString, fromVersion2, onFinish) {\n this.loadString(svgString, fromVersion2);\n this._createSVGImage(onFinish);\n }\n\n /**\n * Creates an <img> element for the currently loaded SVG string, then calls the callback once it's loaded.\n * @param {Function} [onFinish] - An optional callback to call when the <img> has loaded.\n */\n _createSVGImage (onFinish) {\n if (this._cachedImage === null) this._cachedImage = new Image();\n const img = this._cachedImage;\n\n img.onload = () => {\n this.loaded = true;\n if (onFinish) onFinish();\n };\n const svgText = this.toString(true /* shouldInjectFonts */);\n img.src = `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`;\n this.loaded = false;\n }\n\n /**\n * Serialize the active SVG DOM to a string.\n * @param {?boolean} shouldInjectFonts True if fonts should be included in the SVG as\n * base64 data.\n * @returns {string} String representing current SVG data.\n * @deprecated Use the standalone `serializeSvgToString` export instead.\n */\n toString (shouldInjectFonts) {\n return serializeSvgToString(this._svgTag, shouldInjectFonts);\n }\n\n /**\n * Synchronously draw the loaded SVG to this renderer's `canvas`.\n * @param {number} [scale] - Optionally, also scale the image by this factor.\n */\n draw (scale) {\n if (!this.loaded) throw new Error('SVG image has not finished loading');\n this._drawFromImage(scale);\n }\n\n /**\n * Draw to the canvas from a loaded image element.\n * @param {number} [scale] - Optionally, also scale the image by this factor.\n */\n _drawFromImage (scale) {\n if (this._cachedImage === null) return;\n\n const ratio = Number.isFinite(scale) ? scale : 1;\n const bbox = this._measurements;\n this._canvas.width = bbox.width * ratio;\n this._canvas.height = bbox.height * ratio;\n // Even if the canvas at the current scale has a nonzero size, the image's dimensions are floored pre-scaling.\n // e.g. if an image has a width of 0.4 and is being rendered at 3x scale, the canvas will have a width of 1, but\n // the image's width will be rounded down to 0 on some browsers (Firefox) prior to being drawn at that scale.\n if (\n this._canvas.width <= 0 ||\n this._canvas.height <= 0 ||\n this._cachedImage.naturalWidth <= 0 ||\n this._cachedImage.naturalHeight <= 0\n ) return;\n this._context.clearRect(0, 0, this._canvas.width, this._canvas.height);\n this._context.setTransform(ratio, 0, 0, ratio, 0, 0);\n this._context.drawImage(this._cachedImage, 0, 0);\n }\n}\n\nmodule.exports = SvgRenderer;\n","const Matrix = require('transformation-matrix');\nconst SvgElement = require('./svg-element');\nconst log = require('./util/log');\n\n/**\n * @fileOverview Apply transforms to match stroke width appearance in 2.0 and 3.0\n */\n\n// Adapted from paper.js's Path.applyTransform\nconst _parseTransform = function (domElement) {\n let matrix = Matrix.identity();\n const string = domElement.attributes && domElement.attributes.transform && domElement.attributes.transform.value;\n if (!string) return matrix;\n // https://www.w3.org/TR/SVG/types.html#DataTypeTransformList\n // Parse SVG transform string. First we split at /)\\s*/, to separate\n // commands\n const transforms = string.split(/\\)\\s*/g);\n for (const transform of transforms) {\n if (!transform) break;\n // Command come before the '(', values after\n const parts = transform.split(/\\(\\s*/);\n const command = parts[0].trim();\n const v = parts[1].split(/[\\s,]+/g);\n // Convert values to floats\n for (let j = 0; j < v.length; j++) {\n v[j] = parseFloat(v[j]);\n }\n switch (command) {\n case 'matrix':\n matrix = Matrix.compose(matrix, {a: v[0], b: v[1], c: v[2], d: v[3], e: v[4], f: v[5]});\n break;\n case 'rotate':\n matrix = Matrix.compose(matrix, Matrix.rotateDEG(v[0], v[1] || 0, v[2] || 0));\n break;\n case 'translate':\n matrix = Matrix.compose(matrix, Matrix.translate(v[0], v[1] || 0));\n break;\n case 'scale':\n matrix = Matrix.compose(matrix, Matrix.scale(v[0], v[1] || v[0]));\n break;\n case 'skewX':\n matrix = Matrix.compose(matrix, Matrix.skewDEG(v[0], 0));\n break;\n case 'skewY':\n matrix = Matrix.compose(matrix, Matrix.skewDEG(0, v[0]));\n break;\n default:\n log.error(`Couldn't parse: ${command}`);\n }\n }\n return matrix;\n};\n\n// Adapted from paper.js's Matrix.decompose\n// Given a matrix, return the x and y scale factors of the matrix\nconst _getScaleFactor = function (matrix) {\n const a = matrix.a;\n const b = matrix.b;\n const c = matrix.c;\n const d = matrix.d;\n const det = (a * d) - (b * c);\n\n if (a !== 0 || b !== 0) {\n const r = Math.sqrt((a * a) + (b * b));\n return {x: r, y: det / r};\n }\n if (c !== 0 || d !== 0) {\n const s = Math.sqrt((c * c) + (d * d));\n return {x: det / s, y: s};\n }\n // a = b = c = d = 0\n return {x: 0, y: 0};\n};\n\n// Returns null if matrix is not invertible. Otherwise returns given ellipse\n// transformed by transform, an object {radiusX, radiusY, rotation}.\nconst _calculateTransformedEllipse = function (radiusX, radiusY, theta, transform) {\n theta = -theta * Math.PI / 180;\n const a = transform.a;\n const b = -transform.c;\n const c = -transform.b;\n const d = transform.d;\n // Since other parameters determine the translation of the ellipse in SVG, we do not need to worry\n // about what e and f are.\n const det = (a * d) - (b * c);\n // Non-invertible matrix\n if (det === 0) return null;\n\n // rotA, rotB, and rotC represent Ax^2 + Bxy + Cy^2 = 1 coefficients for a rotated ellipse formula\n const sinT = Math.sin(theta);\n const cosT = Math.cos(theta);\n const sin2T = Math.sin(2 * theta);\n const rotA = (cosT * cosT / radiusX / radiusX) + (sinT * sinT / radiusY / radiusY);\n const rotB = (sin2T / radiusX / radiusX) - (sin2T / radiusY / radiusY);\n const rotC = (sinT * sinT / radiusX / radiusX) + (cosT * cosT / radiusY / radiusY);\n\n // Calculate the ellipse formula of the transformed ellipse\n // A, B, and C represent Ax^2 + Bxy + Cy^2 = 1 / det / det coefficients in a transformed ellipse formula\n // scaled by inverse det squared (to preserve accuracy)\n const A = ((rotA * d * d) - (rotB * d * c) + (rotC * c * c));\n const B = ((-2 * rotA * b * d) + (rotB * a * d) + (rotB * b * c) - (2 * rotC * a * c));\n const C = ((rotA * b * b) - (rotB * a * b) + (rotC * a * a));\n\n // Derive new radii and theta from the transformed ellipse formula\n const newRadiusXOverDet = Math.sqrt(2) *\n Math.sqrt(\n (A + C - Math.sqrt((A * A) + (B * B) - (2 * A * C) + (C * C))) /\n ((-B * B) + (4 * A * C))\n );\n const newRadiusYOverDet = 1 / Math.sqrt(A + C - (1 / newRadiusXOverDet / newRadiusXOverDet));\n let temp = (A - (1 / newRadiusXOverDet / newRadiusXOverDet)) /\n ((1 / newRadiusYOverDet / newRadiusYOverDet) - (1 / newRadiusXOverDet / newRadiusXOverDet));\n if (temp < 0 && Math.abs(temp) < 1e-8) temp = 0; // Fix floating point issue\n temp = Math.sqrt(temp);\n if (Math.abs(1 - temp) < 1e-8) temp = 1; // Fix floating point issue\n // Solve for which of the two possible thetas is correct\n let newTheta = Math.asin(temp);\n temp = (B / (\n (1 / newRadiusXOverDet / newRadiusXOverDet) -\n (1 / newRadiusYOverDet / newRadiusYOverDet)));\n const newTheta2 = -newTheta;\n if (Math.abs(Math.sin(2 * newTheta2) - temp) <\n Math.abs(Math.sin(2 * newTheta) - temp)) {\n newTheta = newTheta2;\n }\n\n return {\n radiusX: newRadiusXOverDet * det,\n radiusY: newRadiusYOverDet * det,\n rotation: -newTheta * 180 / Math.PI\n };\n};\n\n// Adapted from paper.js's PathItem.setPathData\nconst _transformPath = function (pathString, transform) {\n if (!transform || Matrix.toString(transform) === Matrix.toString(Matrix.identity())) return pathString;\n // First split the path data into parts of command-coordinates pairs\n // Commands are any of these characters: mzlhvcsqta\n const parts = pathString && pathString.match(/[mlhvcsqtaz][^mlhvcsqtaz]*/ig);\n let coords;\n let relative = false;\n let previous;\n let control;\n let current = {x: 0, y: 0};\n let start = {x: 0, y: 0};\n let result = '';\n\n const getCoord = function (index, coord) {\n let val = +coords[index];\n if (relative) {\n val += current[coord];\n }\n return val;\n };\n\n const getPoint = function (index) {\n return {x: getCoord(index, 'x'), y: getCoord(index + 1, 'y')};\n };\n\n const roundTo4Places = function (num) {\n return Number(num.toFixed(4));\n };\n\n // Returns the transformed point as a string\n const getString = function (point) {\n const transformed = Matrix.applyToPoint(transform, point);\n return `${roundTo4Places(transformed.x)} ${roundTo4Places(transformed.y)} `;\n };\n\n for (let i = 0, l = parts && parts.length; i < l; i++) {\n const part = parts[i];\n const command = part[0];\n const lower = command.toLowerCase();\n // Match all coordinate values\n coords = part.match(/[+-]?(?:\\d*\\.\\d+|\\d+\\.?)(?:[eE][+-]?\\d+)?/g);\n const length = coords && coords.length;\n relative = command === lower;\n // Fix issues with z in the middle of SVG path data, not followed by\n // a m command, see paper.js#413:\n if (previous === 'z' && !/[mz]/.test(lower)) {\n result += `M ${current.x} ${current.y} `;\n }\n switch (lower) {\n case 'm': // Move to\n case 'l': // Line to\n {\n let move = lower === 'm';\n for (let j = 0; j < length; j += 2) {\n result += move ? 'M ' : 'L ';\n current = getPoint(j);\n result += getString(current);\n if (move) {\n start = current;\n move = false;\n }\n }\n control = current;\n break;\n }\n case 'h': // Horizontal line\n case 'v': // Vertical line\n {\n const coord = lower === 'h' ? 'x' : 'y';\n current = {x: current.x, y: current.y}; // Clone as we're going to modify it.\n for (let j = 0; j < length; j++) {\n current[coord] = getCoord(j, coord);\n result += `L ${getString(current)}`;\n }\n control = current;\n break;\n }\n case 'c':\n // Cubic Bezier curve\n for (let j = 0; j < length; j += 6) {\n const handle1 = getPoint(j);\n control = getPoint(j + 2);\n current = getPoint(j + 4);\n result += `C ${getString(handle1)}${getString(control)}${getString(current)}`;\n }\n break;\n case 's':\n // Smooth cubic Bezier curve\n for (let j = 0; j < length; j += 4) {\n const handle1 = /[cs]/.test(previous) ?\n {x: (current.x * 2) - control.x, y: (current.y * 2) - control.y} :\n current;\n control = getPoint(j);\n current = getPoint(j + 2);\n\n result += `C ${getString(handle1)}${getString(control)}${getString(current)}`;\n previous = lower;\n }\n break;\n case 'q':\n // Quadratic Bezier curve\n for (let j = 0; j < length; j += 4) {\n control = getPoint(j);\n current = getPoint(j + 2);\n result += `Q ${getString(control)}${getString(current)}`;\n }\n break;\n case 't':\n // Smooth quadratic Bezier curve\n for (let j = 0; j < length; j += 2) {\n control = /[qt]/.test(previous) ?\n {x: (current.x * 2) - control.x, y: (current.y * 2) - control.y} :\n current;\n current = getPoint(j);\n\n result += `Q ${getString(control)}${getString(current)}`;\n previous = lower;\n }\n break;\n case 'a':\n // Elliptical arc curve\n for (let j = 0; j < length; j += 7) {\n current = getPoint(j + 5);\n const rx = +coords[j];\n const ry = +coords[j + 1];\n const rotation = +coords[j + 2];\n const largeArcFlag = +coords[j + 3];\n let clockwiseFlag = +coords[j + 4];\n const newEllipse = _calculateTransformedEllipse(rx, ry, rotation, transform);\n const matrixScale = _getScaleFactor(transform);\n if (newEllipse) {\n if ((matrixScale.x > 0 && matrixScale.y < 0) ||\n (matrixScale.x < 0 && matrixScale.y > 0)) {\n clockwiseFlag = clockwiseFlag ^ 1;\n }\n result += `A ${roundTo4Places(Math.abs(newEllipse.radiusX))} ` +\n `${roundTo4Places(Math.abs(newEllipse.radiusY))} ` +\n `${roundTo4Places(newEllipse.rotation)} ${largeArcFlag} ` +\n `${clockwiseFlag} ${getString(current)}`;\n } else {\n result += `L ${getString(current)}`;\n }\n }\n break;\n case 'z':\n // Close path\n result += `Z `;\n // Correctly handle relative m commands, see paper.js#1101:\n current = start;\n break;\n }\n previous = lower;\n }\n return result;\n};\n\nconst GRAPHICS_ELEMENTS = ['circle', 'ellipse', 'image', 'line', 'path', 'polygon', 'polyline', 'rect', 'text', 'use'];\nconst CONTAINER_ELEMENTS = ['a', 'defs', 'g', 'marker', 'glyph', 'missing-glyph', 'pattern', 'svg', 'switch', 'symbol'];\nconst _isContainerElement = function (element) {\n return element.tagName && CONTAINER_ELEMENTS.includes(element.tagName.toLowerCase());\n};\nconst _isGraphicsElement = function (element) {\n return element.tagName && GRAPHICS_ELEMENTS.includes(element.tagName.toLowerCase());\n};\nconst _isPathWithTransformAndStroke = function (element, strokeWidth) {\n if (!element.attributes) return false;\n strokeWidth = element.attributes['stroke-width'] ?\n Number(element.attributes['stroke-width'].value) : Number(strokeWidth);\n return strokeWidth &&\n element.tagName && element.tagName.toLowerCase() === 'path' &&\n element.attributes.d && element.attributes.d.value;\n};\nconst _quadraticMean = function (a, b) {\n return Math.sqrt(((a * a) + (b * b)) / 2);\n};\n\nconst _createGradient = function (gradientId, svgTag, bbox, matrix) {\n // Adapted from Paper.js's SvgImport.getValue\n const getValue = function (node, name, isString, allowNull, allowPercent, defaultValue) {\n // Interpret value as number. Never return NaN, but 0 instead.\n // If the value is a sequence of numbers, parseFloat will\n // return the first occurring number, which is enough for now.\n let value = SvgElement.get(node, name);\n let res;\n if (value === null) {\n if (defaultValue) {\n res = defaultValue;\n if (/%\\s*$/.test(res)) {\n value = defaultValue;\n res = parseFloat(value);\n }\n } else if (allowNull) {\n res = null;\n } else if (isString) {\n res = '';\n } else {\n res = 0;\n }\n } else if (isString) {\n res = value;\n } else {\n res = parseFloat(value);\n }\n // Support for dimensions in percentage of the root size. If root-size\n // is not set (e.g. during <defs>), just scale the percentage value to\n // 0..1, as required by gradients with gradientUnits=\"objectBoundingBox\"\n if (/%\\s*$/.test(value)) {\n const size = allowPercent ? 1 : bbox[/x|^width/.test(name) ? 'width' : 'height'];\n return res / 100 * size;\n }\n return res;\n };\n const getPoint = function (node, x, y, allowNull, allowPercent, defaultX, defaultY) {\n x = getValue(node, x || 'x', false, allowNull, allowPercent, defaultX);\n y = getValue(node, y || 'y', false, allowNull, allowPercent, defaultY);\n return allowNull && (x === null || y === null) ? null : {x, y};\n };\n\n let defs = svgTag.getElementsByTagName('defs');\n if (defs.length === 0) {\n defs = SvgElement.create('defs');\n svgTag.appendChild(defs);\n } else {\n defs = defs[0];\n }\n\n // Clone the old gradient. We'll make a new one, since the gradient might be reused elsewhere\n // with different transform matrix\n const oldGradient = svgTag.getElementById(gradientId);\n if (!oldGradient) return;\n\n const radial = oldGradient.tagName.toLowerCase() === 'radialgradient';\n const newGradient = svgTag.getElementById(gradientId).cloneNode(true /* deep */);\n\n // Give the new gradient a new ID\n let matrixString = Matrix.toString(matrix);\n matrixString = matrixString.substring(8, matrixString.length - 1);\n const newGradientId = `${gradientId}-${matrixString}`;\n newGradient.setAttribute('id', newGradientId);\n\n // This gradient already exists and was transformed before. Just reuse the already-transformed one.\n if (svgTag.getElementById(newGradientId)) {\n // This is the same code as in the end of the function, but I don't feel like wrapping the next 80 lines\n // in an `if (!svgTag.getElementById(newGradientId))` block\n return `url(#${newGradientId})`;\n }\n\n const scaleToBounds = getValue(newGradient, 'gradientUnits', true) !==\n 'userSpaceOnUse';\n let origin;\n let destination;\n let radius;\n let focal;\n if (radial) {\n origin = getPoint(newGradient, 'cx', 'cy', false, scaleToBounds, '50%', '50%');\n radius = getValue(newGradient, 'r', false, false, scaleToBounds, '50%');\n focal = getPoint(newGradient, 'fx', 'fy', true, scaleToBounds);\n } else {\n origin = getPoint(newGradient, 'x1', 'y1', false, scaleToBounds);\n destination = getPoint(newGradient, 'x2', 'y2', false, scaleToBounds, '1');\n if (origin.x === destination.x && origin.y === destination.y) {\n // If it's degenerate, use the color of the last stop, as described by\n // https://www.w3.org/TR/SVG/pservers.html#LinearGradientNotes\n const stops = newGradient.getElementsByTagName('stop');\n if (!stops.length || !stops[stops.length - 1].attributes ||\n !stops[stops.length - 1].attributes['stop-color']) {\n return null;\n }\n return stops[stops.length - 1].attributes['stop-color'].value;\n }\n }\n\n // Transform points\n // Emulate SVG's gradientUnits=\"objectBoundingBox\"\n if (scaleToBounds) {\n const boundsMatrix = Matrix.compose(Matrix.translate(bbox.x, bbox.y), Matrix.scale(bbox.width, bbox.height));\n origin = Matrix.applyToPoint(boundsMatrix, origin);\n if (destination) destination = Matrix.applyToPoint(boundsMatrix, destination);\n if (radius) {\n radius = _quadraticMean(bbox.width, bbox.height) * radius;\n }\n if (focal) focal = Matrix.applyToPoint(boundsMatrix, focal);\n }\n\n if (radial) {\n origin = Matrix.applyToPoint(matrix, origin);\n const matrixScale = _getScaleFactor(matrix);\n radius = _quadraticMean(matrixScale.x, matrixScale.y) * radius;\n if (focal) focal = Matrix.applyToPoint(matrix, focal);\n } else {\n const dot = (a, b) => (a.x * b.x) + (a.y * b.y);\n const multiply = (coefficient, v) => ({x: coefficient * v.x, y: coefficient * v.y});\n const add = (a, b) => ({x: a.x + b.x, y: a.y + b.y});\n const subtract = (a, b) => ({x: a.x - b.x, y: a.y - b.y});\n\n // The line through origin and gradientPerpendicular is the line at which the gradient starts\n let gradientPerpendicular = Math.abs(origin.x - destination.x) < 1e-8 ?\n add(origin, {x: 1, y: (origin.x - destination.x) / (destination.y - origin.y)}) :\n add(origin, {x: (destination.y - origin.y) / (origin.x - destination.x), y: 1});\n\n // Transform points\n gradientPerpendicular = Matrix.applyToPoint(matrix, gradientPerpendicular);\n origin = Matrix.applyToPoint(matrix, origin);\n destination = Matrix.applyToPoint(matrix, destination);\n\n // Calculate the direction that the gradient has changed to\n const originToPerpendicular = subtract(gradientPerpendicular, origin);\n const originToDestination = subtract(destination, origin);\n const gradientDirection = Math.abs(originToPerpendicular.x) < 1e-8 ?\n {x: 1, y: -originToPerpendicular.x / originToPerpendicular.y} :\n {x: -originToPerpendicular.y / originToPerpendicular.x, y: 1};\n\n // Set the destination so that the gradient moves in the correct direction, by projecting the destination vector\n // onto the gradient direction vector\n const projectionCoeff = dot(originToDestination, gradientDirection) / dot(gradientDirection, gradientDirection);\n const projection = multiply(projectionCoeff, gradientDirection);\n destination = {x: origin.x + projection.x, y: origin.y + projection.y};\n }\n\n // Put values back into svg\n if (radial) {\n newGradient.setAttribute('cx', Number(origin.x.toFixed(4)));\n newGradient.setAttribute('cy', Number(origin.y.toFixed(4)));\n newGradient.setAttribute('r', Number(radius.toFixed(4)));\n if (focal) {\n newGradient.setAttribute('fx', Number(focal.x.toFixed(4)));\n newGradient.setAttribute('fy', Number(focal.y.toFixed(4)));\n }\n } else {\n newGradient.setAttribute('x1', Number(origin.x.toFixed(4)));\n newGradient.setAttribute('y1', Number(origin.y.toFixed(4)));\n newGradient.setAttribute('x2', Number(destination.x.toFixed(4)));\n newGradient.setAttribute('y2', Number(destination.y.toFixed(4)));\n }\n newGradient.setAttribute('gradientUnits', 'userSpaceOnUse');\n defs.appendChild(newGradient);\n\n return `url(#${newGradientId})`;\n};\n\n// Adapted from paper.js's SvgImport.getDefinition\nconst _parseUrl = (value, windowRef) => {\n // When url() comes from a style property, '#'' seems to be missing on\n // WebKit. We also get variations of quotes or no quotes, single or\n // double, so handle it all with one regular expression:\n const match = value && value.match(/\\((?:[\"'#]*)([^\"')]+)/);\n const name = match && match[1];\n const res = name && windowRef ?\n // This is required by Firefox, which can produce absolute\n // urls for local gradients, see paperjs#1001:\n name.replace(`${windowRef.location.href.split('#')[0]}#`, '') :\n name;\n return res;\n};\n\n/**\n * Scratch 2.0 displays stroke widths in a \"normalized\" way, that is,\n * if a shape with a stroke width has a transform applied, it will be\n * rendered with a stroke that is the same width all the way around,\n * instead of stretched looking.\n *\n * The vector paint editor also prefers to normalize the stroke width,\n * rather than keep track of transforms at the group level, as this\n * simplifies editing (e.g. stroke width 3 always means the same thickness)\n *\n * This function performs that normalization process, pushing transforms\n * on groups down to the leaf level and averaging out the stroke width\n * around the shapes. Note that this doens't just change stroke widths, it\n * changes path data and attributes throughout the SVG.\n * @param {SVGElement} svgTag The SVG dom object\n * @param {Window} windowRef The window to use. Need to pass in for\n * tests to work, as they get angry at even the mention of window.\n * @param {object} bboxForTesting The bounds to use. Need to pass in for\n * tests only, because getBBox doesn't work in Node. This should\n * be the bounds of the svgTag without including stroke width or transforms.\n * @returns {void}\n */\nconst transformStrokeWidths = function (svgTag, windowRef, bboxForTesting) {\n const inherited = Matrix.identity();\n\n const applyTransforms = (element, matrix, strokeWidth, fill, stroke) => {\n if (_isContainerElement(element)) {\n // Push fills and stroke width down to leaves\n if (element.attributes['stroke-width']) {\n strokeWidth = element.attributes['stroke-width'].value;\n }\n if (element.attributes) {\n if (element.attributes.fill) fill = element.attributes.fill.value;\n if (element.attributes.stroke) stroke = element.attributes.stroke.value;\n }\n\n // If any child nodes don't take attributes, leave the attributes\n // at the parent level.\n for (let i = 0; i < element.childNodes.length; i++) {\n applyTransforms(\n element.childNodes[i],\n Matrix.compose(matrix, _parseTransform(element)),\n strokeWidth,\n fill,\n stroke\n );\n }\n element.removeAttribute('transform');\n element.removeAttribute('stroke-width');\n element.removeAttribute('fill');\n element.removeAttribute('stroke');\n } else if (_isPathWithTransformAndStroke(element, strokeWidth)) {\n if (element.attributes['stroke-width']) {\n strokeWidth = element.attributes['stroke-width'].value;\n }\n if (element.attributes.fill) fill = element.attributes.fill.value;\n if (element.attributes.stroke) stroke = element.attributes.stroke.value;\n matrix = Matrix.compose(matrix, _parseTransform(element));\n if (Matrix.toString(matrix) === Matrix.toString(Matrix.identity())) {\n element.removeAttribute('transform');\n element.setAttribute('stroke-width', strokeWidth);\n if (fill) element.setAttribute('fill', fill);\n if (stroke) element.setAttribute('stroke', stroke);\n return;\n }\n\n // Transform gradient\n const fillGradientId = _parseUrl(fill, windowRef);\n const strokeGradientId = _parseUrl(stroke, windowRef);\n\n if (fillGradientId || strokeGradientId) {\n const doc = windowRef.document;\n // Need path bounds to transform gradient\n const svgSpot = doc.createElement('span');\n let bbox;\n if (bboxForTesting) {\n bbox = bboxForTesting;\n } else {\n try {\n doc.body.appendChild(svgSpot);\n const svg = SvgElement.set(doc.createElementNS(SvgElement.svg, 'svg'));\n const path = SvgElement.set(doc.createElementNS(SvgElement.svg, 'path'));\n path.setAttribute('d', element.attributes.d.value);\n svg.appendChild(path);\n svgSpot.appendChild(svg);\n // Take the bounding box.\n bbox = svg.getBBox();\n } finally {\n // Always destroy the element, even if, for example, getBBox throws.\n doc.body.removeChild(svgSpot);\n }\n }\n\n if (fillGradientId) {\n const newFillRef = _createGradient(fillGradientId, svgTag, bbox, matrix);\n if (newFillRef) fill = newFillRef;\n }\n\n if (strokeGradientId) {\n const newStrokeRef = _createGradient(strokeGradientId, svgTag, bbox, matrix);\n if (newStrokeRef) stroke = newStrokeRef;\n }\n }\n\n // Transform path data\n element.setAttribute('d', _transformPath(element.attributes.d.value, matrix));\n element.removeAttribute('transform');\n\n // Transform stroke width\n const matrixScale = _getScaleFactor(matrix);\n element.setAttribute('stroke-width', _quadraticMean(matrixScale.x, matrixScale.y) * strokeWidth);\n if (fill) element.setAttribute('fill', fill);\n if (stroke) element.setAttribute('stroke', stroke);\n } else if (_isGraphicsElement(element)) {\n // Push stroke width, fill, and stroke down to leaves\n if (strokeWidth && !element.attributes['stroke-width']) {\n element.setAttribute('stroke-width', strokeWidth);\n }\n if (fill && !element.attributes.fill) {\n element.setAttribute('fill', fill);\n }\n if (stroke && !element.attributes.stroke) {\n element.setAttribute('stroke', stroke);\n }\n\n // Push transform down to leaves\n matrix = Matrix.compose(matrix, _parseTransform(element));\n if (Matrix.toString(matrix) === Matrix.toString(Matrix.identity())) {\n element.removeAttribute('transform');\n } else {\n element.setAttribute('transform', Matrix.toString(matrix));\n }\n }\n };\n applyTransforms(svgTag, inherited, 1 /* default SVG stroke width */);\n};\n\nmodule.exports = transformStrokeWidths;\n","const {Logger} = require('tslog');\n\nmodule.exports = new Logger({\n name: 'scratch-svg-renderer'\n});\n","module.exports = require(\"base64-js\");","module.exports = require(\"css-tree\");","module.exports = require(\"fastestsmallesttextencoderdecoder\");","module.exports = require(\"isomorphic-dompurify\");","module.exports = require(\"scratch-render-fonts\");","module.exports = require(\"transformation-matrix\");","module.exports = require(\"tslog\");","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\tif (!(moduleId in __webpack_modules__)) {\n\t\tdelete __webpack_module_cache__[moduleId];\n\t\tvar e = new Error(\"Cannot find module '\" + moduleId + \"'\");\n\t\te.code = 'MODULE_NOT_FOUND';\n\t\tthrow e;\n\t}\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(\"./src/index.js\");\n",""],"names":[],"sourceRoot":""}
|
|
@@ -21212,12 +21212,6 @@ module.exports = /*#__PURE__*/JSON.parse('{"absolute-size":{"syntax":"xx-small |
|
|
|
21212
21212
|
/******/ if (cachedModule !== undefined) {
|
|
21213
21213
|
/******/ return cachedModule.exports;
|
|
21214
21214
|
/******/ }
|
|
21215
|
-
/******/ // Check if module exists (development only)
|
|
21216
|
-
/******/ if (__webpack_modules__[moduleId] === undefined) {
|
|
21217
|
-
/******/ var e = new Error("Cannot find module '" + moduleId + "'");
|
|
21218
|
-
/******/ e.code = 'MODULE_NOT_FOUND';
|
|
21219
|
-
/******/ throw e;
|
|
21220
|
-
/******/ }
|
|
21221
21215
|
/******/ // Create a new module (and put it into the cache)
|
|
21222
21216
|
/******/ var module = __webpack_module_cache__[moduleId] = {
|
|
21223
21217
|
/******/ // no module.id needed
|
|
@@ -21226,6 +21220,12 @@ module.exports = /*#__PURE__*/JSON.parse('{"absolute-size":{"syntax":"xx-small |
|
|
|
21226
21220
|
/******/ };
|
|
21227
21221
|
/******/
|
|
21228
21222
|
/******/ // Execute the module function
|
|
21223
|
+
/******/ if (!(moduleId in __webpack_modules__)) {
|
|
21224
|
+
/******/ delete __webpack_module_cache__[moduleId];
|
|
21225
|
+
/******/ var e = new Error("Cannot find module '" + moduleId + "'");
|
|
21226
|
+
/******/ e.code = 'MODULE_NOT_FOUND';
|
|
21227
|
+
/******/ throw e;
|
|
21228
|
+
/******/ }
|
|
21229
21229
|
/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
|
21230
21230
|
/******/
|
|
21231
21231
|
/******/ // Return the exports of the module
|