@scratch/scratch-svg-renderer 11.0.0-UEPR-176
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +12 -0
- package/README.md +89 -0
- package/dist/node/scratch-svg-renderer.js +3 -0
- package/dist/node/scratch-svg-renderer.js.LICENSE.txt +417 -0
- package/dist/node/scratch-svg-renderer.js.map +1 -0
- package/dist/web/scratch-svg-renderer.js +3 -0
- package/dist/web/scratch-svg-renderer.js.LICENSE.txt +1 -0
- package/dist/web/scratch-svg-renderer.js.map +1 -0
- package/package.json +80 -0
- package/src/bitmap-adapter.js +156 -0
- package/src/fixup-svg-string.js +61 -0
- package/src/font-converter.js +38 -0
- package/src/font-inliner.js +50 -0
- package/src/index.js +22 -0
- package/src/load-svg-string.js +334 -0
- package/src/playground/index.html +132 -0
- package/src/sanitize-svg.js +104 -0
- package/src/serialize-svg-to-string.js +19 -0
- package/src/svg-element.js +71 -0
- package/src/svg-renderer.js +169 -0
- package/src/transform-applier.js +628 -0
- package/src/util/log.js +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! @license DOMPurify 3.2.4 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.4/LICENSE */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scratch-svg-renderer.js","mappings":";AAAA","sources":["webpack://ScratchSVGRenderer/webpack/universalModuleDefinition"],"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})(self, () => {\nreturn "],"names":[],"sourceRoot":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scratch/scratch-svg-renderer",
|
|
3
|
+
"version": "11.0.0-UEPR-176",
|
|
4
|
+
"description": "SVG renderer for Scratch",
|
|
5
|
+
"main": "./dist/node/scratch-svg-renderer.js",
|
|
6
|
+
"browser": "./dist/web/scratch-svg-renderer.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
"webpack": "./src/index.js",
|
|
9
|
+
"browser": "./dist/web/scratch-svg-renderer.js",
|
|
10
|
+
"node": "./dist/node/scratch-svg-renderer.js",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "npm run clean && webpack",
|
|
19
|
+
"clean": "rimraf dist playground",
|
|
20
|
+
"start": "webpack-dev-server",
|
|
21
|
+
"test": "npm run test:lint && npm run test:unit",
|
|
22
|
+
"test:lint": "eslint . --ext .js",
|
|
23
|
+
"test:unit": "tap ./test/*.js",
|
|
24
|
+
"watch": "webpack --watch"
|
|
25
|
+
},
|
|
26
|
+
"author": "Massachusetts Institute of Technology",
|
|
27
|
+
"license": "AGPL-3.0-only",
|
|
28
|
+
"homepage": "https://github.com/scratchfoundation/scratch-svg-renderer#readme",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/scratchfoundation/scratch-editor.git"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"scratch-render-fonts": "^1.0.0"
|
|
35
|
+
},
|
|
36
|
+
"tap": {
|
|
37
|
+
"branches": 70,
|
|
38
|
+
"functions": 50,
|
|
39
|
+
"lines": 70,
|
|
40
|
+
"statements": 70
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"base64-js": "1.5.1",
|
|
44
|
+
"base64-loader": "1.0.0",
|
|
45
|
+
"css-tree": "1.1.3",
|
|
46
|
+
"fastestsmallesttextencoderdecoder": "1.0.22",
|
|
47
|
+
"isomorphic-dompurify": "2.4.0",
|
|
48
|
+
"minilog": "3.1.0",
|
|
49
|
+
"transformation-matrix": "1.15.3"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@babel/core": "7.26.10",
|
|
53
|
+
"@babel/eslint-parser": "7.26.10",
|
|
54
|
+
"@babel/preset-env": "7.26.9",
|
|
55
|
+
"babel-loader": "9.2.1",
|
|
56
|
+
"copy-webpack-plugin": "4.6.0",
|
|
57
|
+
"eslint": "8.57.1",
|
|
58
|
+
"eslint-config-scratch": "9.0.9",
|
|
59
|
+
"eslint-plugin-import": "2.31.0",
|
|
60
|
+
"jsdom": "13.2.0",
|
|
61
|
+
"json": "9.0.6",
|
|
62
|
+
"mkdirp": "2.1.6",
|
|
63
|
+
"rimraf": "3.0.2",
|
|
64
|
+
"scratch-render-fonts": "1.0.177",
|
|
65
|
+
"scratch-semantic-release-config": "3.0.0",
|
|
66
|
+
"scratch-webpack-configuration": "3.0.0",
|
|
67
|
+
"semantic-release": "19.0.5",
|
|
68
|
+
"tap": "16.3.10",
|
|
69
|
+
"webpack": "5.98.0",
|
|
70
|
+
"webpack-cli": "5.1.4",
|
|
71
|
+
"webpack-dev-server": "5.2.0",
|
|
72
|
+
"xmldom": "0.1.31"
|
|
73
|
+
},
|
|
74
|
+
"browserslist": [
|
|
75
|
+
"Chrome >= 63",
|
|
76
|
+
"Edge >= 15",
|
|
77
|
+
"Firefox >= 57",
|
|
78
|
+
"Safari >= 11"
|
|
79
|
+
]
|
|
80
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const base64js = require('base64-js');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Adapts Scratch 2.0 bitmaps for use in scratch 3.0
|
|
5
|
+
*/
|
|
6
|
+
class BitmapAdapter {
|
|
7
|
+
/**
|
|
8
|
+
* @param {?function} makeImage HTML image constructor. Tests can provide this.
|
|
9
|
+
* @param {?function} makeCanvas HTML canvas constructor. Tests can provide this.
|
|
10
|
+
*/
|
|
11
|
+
constructor (makeImage, makeCanvas) {
|
|
12
|
+
this._makeImage = makeImage ? makeImage : () => new Image();
|
|
13
|
+
this._makeCanvas = makeCanvas ? makeCanvas : () => document.createElement('canvas');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Return a canvas with the resized version of the given image, done using nearest-neighbor interpolation
|
|
18
|
+
* @param {CanvasImageSource} image The image to resize
|
|
19
|
+
* @param {int} newWidth The desired post-resize width of the image
|
|
20
|
+
* @param {int} newHeight The desired post-resize height of the image
|
|
21
|
+
* @returns {HTMLCanvasElement} A canvas with the resized image drawn on it.
|
|
22
|
+
*/
|
|
23
|
+
resize (image, newWidth, newHeight) {
|
|
24
|
+
// We want to always resize using nearest-neighbor interpolation. However, canvas implementations are free to
|
|
25
|
+
// use linear interpolation (or other "smooth" interpolation methods) when downscaling:
|
|
26
|
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=1360415
|
|
27
|
+
// It seems we can get around this by resizing in two steps: first width, then height. This will always result
|
|
28
|
+
// in nearest-neighbor interpolation, even when downscaling.
|
|
29
|
+
const stretchWidthCanvas = this._makeCanvas();
|
|
30
|
+
stretchWidthCanvas.width = newWidth;
|
|
31
|
+
stretchWidthCanvas.height = image.height;
|
|
32
|
+
let context = stretchWidthCanvas.getContext('2d');
|
|
33
|
+
context.imageSmoothingEnabled = false;
|
|
34
|
+
context.drawImage(image, 0, 0, stretchWidthCanvas.width, stretchWidthCanvas.height);
|
|
35
|
+
const stretchHeightCanvas = this._makeCanvas();
|
|
36
|
+
stretchHeightCanvas.width = newWidth;
|
|
37
|
+
stretchHeightCanvas.height = newHeight;
|
|
38
|
+
context = stretchHeightCanvas.getContext('2d');
|
|
39
|
+
context.imageSmoothingEnabled = false;
|
|
40
|
+
context.drawImage(stretchWidthCanvas, 0, 0, stretchHeightCanvas.width, stretchHeightCanvas.height);
|
|
41
|
+
return stretchHeightCanvas;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Scratch 2.0 had resolution 1 and 2 bitmaps. All bitmaps in Scratch 3.0 are equivalent
|
|
46
|
+
* to resolution 2 bitmaps. Therefore, converting a resolution 1 bitmap means doubling
|
|
47
|
+
* it in width and height.
|
|
48
|
+
* @param {!string} dataURI Base 64 encoded image data of the bitmap
|
|
49
|
+
* @param {!function} callback Node-style callback that returns updated dataURI if conversion succeeded
|
|
50
|
+
*/
|
|
51
|
+
convertResolution1Bitmap (dataURI, callback) {
|
|
52
|
+
const image = this._makeImage();
|
|
53
|
+
image.src = dataURI;
|
|
54
|
+
image.onload = () => {
|
|
55
|
+
callback(null, this.resize(image, image.width * 2, image.height * 2).toDataURL());
|
|
56
|
+
};
|
|
57
|
+
image.onerror = () => {
|
|
58
|
+
callback('Image load failed');
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Given width/height of an uploaded item, return width/height the image will be resized
|
|
64
|
+
* to in Scratch 3.0
|
|
65
|
+
* @param {!number} oldWidth original width
|
|
66
|
+
* @param {!number} oldHeight original height
|
|
67
|
+
* @return {object} Array of new width, new height
|
|
68
|
+
*/
|
|
69
|
+
getResizedWidthHeight (oldWidth, oldHeight) {
|
|
70
|
+
const STAGE_WIDTH = 480;
|
|
71
|
+
const STAGE_HEIGHT = 360;
|
|
72
|
+
const STAGE_RATIO = STAGE_WIDTH / STAGE_HEIGHT;
|
|
73
|
+
|
|
74
|
+
// If both dimensions are smaller than or equal to corresponding stage dimension,
|
|
75
|
+
// double both dimensions
|
|
76
|
+
if ((oldWidth <= STAGE_WIDTH) && (oldHeight <= STAGE_HEIGHT)) {
|
|
77
|
+
return {width: oldWidth * 2, height: oldHeight * 2};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// If neither dimension is larger than 2x corresponding stage dimension,
|
|
81
|
+
// this is an in-between image, return it as is
|
|
82
|
+
if ((oldWidth <= STAGE_WIDTH * 2) && (oldHeight <= STAGE_HEIGHT * 2)) {
|
|
83
|
+
return {width: oldWidth, height: oldHeight};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const imageRatio = oldWidth / oldHeight;
|
|
87
|
+
// Otherwise, figure out how to resize
|
|
88
|
+
if (imageRatio >= STAGE_RATIO) {
|
|
89
|
+
// Wide Image
|
|
90
|
+
return {width: STAGE_WIDTH * 2, height: STAGE_WIDTH * 2 / imageRatio};
|
|
91
|
+
}
|
|
92
|
+
// In this case we have either:
|
|
93
|
+
// - A wide image, but not with as big a ratio between width and height,
|
|
94
|
+
// making it so that fitting the width to double stage size would leave
|
|
95
|
+
// the height too big to fit in double the stage height
|
|
96
|
+
// - A square image that's still larger than the double at least
|
|
97
|
+
// one of the stage dimensions, so pick the smaller of the two dimensions (to fit)
|
|
98
|
+
// - A tall image
|
|
99
|
+
// In any of these cases, resize the image to fit the height to double the stage height
|
|
100
|
+
return {width: STAGE_HEIGHT * 2 * imageRatio, height: STAGE_HEIGHT * 2};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Given bitmap data, resize as necessary.
|
|
105
|
+
* @param {ArrayBuffer | string} fileData Base 64 encoded image data of the bitmap
|
|
106
|
+
* @param {string} fileType The MIME type of this file
|
|
107
|
+
* @returns {Promise} Resolves to resized image data Uint8Array
|
|
108
|
+
*/
|
|
109
|
+
importBitmap (fileData, fileType) {
|
|
110
|
+
let dataURI = fileData;
|
|
111
|
+
if (fileData instanceof ArrayBuffer) {
|
|
112
|
+
dataURI = this.convertBinaryToDataURI(fileData, fileType);
|
|
113
|
+
}
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
const image = this._makeImage();
|
|
116
|
+
image.src = dataURI;
|
|
117
|
+
image.onload = () => {
|
|
118
|
+
const newSize = this.getResizedWidthHeight(image.width, image.height);
|
|
119
|
+
if (newSize.width === image.width && newSize.height === image.height) {
|
|
120
|
+
// No change
|
|
121
|
+
resolve(this.convertDataURIToBinary(dataURI));
|
|
122
|
+
} else {
|
|
123
|
+
const resizedDataURI = this.resize(image, newSize.width, newSize.height).toDataURL();
|
|
124
|
+
resolve(this.convertDataURIToBinary(resizedDataURI));
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
image.onerror = () => {
|
|
128
|
+
// TODO: reject with an Error (breaking API change!)
|
|
129
|
+
// eslint-disable-next-line prefer-promise-reject-errors
|
|
130
|
+
reject('Image load failed');
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// TODO consolidate with scratch-vm/src/util/base64-util.js
|
|
136
|
+
// From https://gist.github.com/borismus/1032746
|
|
137
|
+
convertDataURIToBinary (dataURI) {
|
|
138
|
+
const BASE64_MARKER = ';base64,';
|
|
139
|
+
const base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
|
|
140
|
+
const base64 = dataURI.substring(base64Index);
|
|
141
|
+
const raw = window.atob(base64);
|
|
142
|
+
const rawLength = raw.length;
|
|
143
|
+
const array = new Uint8Array(new ArrayBuffer(rawLength));
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < rawLength; i++) {
|
|
146
|
+
array[i] = raw.charCodeAt(i);
|
|
147
|
+
}
|
|
148
|
+
return array;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
convertBinaryToDataURI (arrayBuffer, contentType) {
|
|
152
|
+
return `data:${contentType};base64,${base64js.fromByteArray(new Uint8Array(arrayBuffer))}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = BitmapAdapter;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixup svg string prior to parsing.
|
|
3
|
+
* @param {!string} svgString String of the svg to fix.
|
|
4
|
+
* @returns {!string} fixed svg that should be parseable.
|
|
5
|
+
*/
|
|
6
|
+
module.exports = function (svgString) {
|
|
7
|
+
// Add root svg namespace if it does not exist.
|
|
8
|
+
const svgAttrs = svgString.match(/<svg [^>]*>/);
|
|
9
|
+
if (svgAttrs && svgAttrs[0].indexOf('xmlns=') === -1) {
|
|
10
|
+
svgString = svgString.replace('<svg ', '<svg xmlns="http://www.w3.org/2000/svg" ');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// There are some SVGs from Illustrator that use undeclared entities.
|
|
14
|
+
// Just replace those entities with fake namespace references to prevent
|
|
15
|
+
// DOMParser from crashing
|
|
16
|
+
if (svgAttrs && svgAttrs[0].indexOf('&ns_') !== -1 && svgString.indexOf('<!DOCTYPE') === -1) {
|
|
17
|
+
svgString = svgString.replace(svgAttrs[0],
|
|
18
|
+
svgAttrs[0].replace(/&ns_[^;]+;/g, 'http://ns.adobe.com/Extensibility/1.0/'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Some SVGs exported from Photoshop have been found to have an invalid mime type
|
|
22
|
+
// Chrome and Safari won't render these SVGs, so we correct it here
|
|
23
|
+
if (svgString.includes('data:img/png')) {
|
|
24
|
+
svgString = svgString.replace(
|
|
25
|
+
// capture entire image tag with xlink:href=and the quote - dont capture data: bit
|
|
26
|
+
/(<image[^>]+?xlink:href=["'])data:img\/png/g,
|
|
27
|
+
// use the captured <image ..... xlink:href=" then append the right data uri mime type
|
|
28
|
+
($0, $1) => `${$1}data:image/png`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Some SVGs from Inkscape attempt to bind a prefix to a reserved namespace name.
|
|
33
|
+
// This will cause SVG parsing to fail, so replace these with a dummy namespace name.
|
|
34
|
+
// This namespace name is only valid for "xml", and if we bind "xmlns:xml" to the dummy namespace,
|
|
35
|
+
// parsing will fail yet again, so exclude "xmlns:xml" declarations.
|
|
36
|
+
const xmlnsRegex = /(<[^>]+?xmlns:(?!xml=)[^ ]+=)"http:\/\/www.w3.org\/XML\/1998\/namespace"/g;
|
|
37
|
+
if (svgString.match(xmlnsRegex) !== null) {
|
|
38
|
+
svgString = svgString.replace(
|
|
39
|
+
// capture the entire attribute
|
|
40
|
+
xmlnsRegex,
|
|
41
|
+
// use the captured attribute name; replace only the URL
|
|
42
|
+
($0, $1) => `${$1}"http://dummy.namespace"`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Strip `svg:` prefix (sometimes added by Inkscape) from all tags. They interfere with DOMPurify (prefixed tag
|
|
47
|
+
// names are not recognized) and the paint editor.
|
|
48
|
+
// This matches opening and closing tags--the capture group captures the slash if it exists, and it is reinserted
|
|
49
|
+
// in the replacement text.
|
|
50
|
+
svgString = svgString.replace(/<(\/?)\s*svg:/g, '<$1');
|
|
51
|
+
|
|
52
|
+
// The <metadata> element is not needed for rendering and sometimes contains
|
|
53
|
+
// unparseable garbage from Illustrator :( Empty out the contents.
|
|
54
|
+
// Note: [\s\S] matches everything including newlines, which .* does not
|
|
55
|
+
svgString = svgString.replace(/<metadata>[\s\S]*<\/metadata>/, '<metadata></metadata>');
|
|
56
|
+
|
|
57
|
+
// Empty script tags and javascript executing
|
|
58
|
+
svgString = svgString.replace(/<script[\s\S]*>[\s\S]*<\/script>/, '<script></script>');
|
|
59
|
+
|
|
60
|
+
return svgString;
|
|
61
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileOverview Convert 2.0 fonts to 3.0 fonts.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Given an SVG, replace Scratch 2.0 fonts with new 3.0 fonts. Add defaults where there are none.
|
|
7
|
+
* @param {SVGElement} svgTag The SVG dom object
|
|
8
|
+
* @return {void}
|
|
9
|
+
*/
|
|
10
|
+
const convertFonts = function (svgTag) {
|
|
11
|
+
// Collect all text elements into a list.
|
|
12
|
+
const textElements = [];
|
|
13
|
+
const collectText = domElement => {
|
|
14
|
+
if (domElement.localName === 'text') {
|
|
15
|
+
textElements.push(domElement);
|
|
16
|
+
}
|
|
17
|
+
for (let i = 0; i < domElement.childNodes.length; i++) {
|
|
18
|
+
collectText(domElement.childNodes[i]);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
collectText(svgTag);
|
|
22
|
+
// If there's an old font-family, switch to the new one.
|
|
23
|
+
for (const textElement of textElements) {
|
|
24
|
+
// If there's no font-family provided, provide one.
|
|
25
|
+
if (!textElement.getAttribute('font-family') ||
|
|
26
|
+
textElement.getAttribute('font-family') === 'Helvetica') {
|
|
27
|
+
textElement.setAttribute('font-family', 'Sans Serif');
|
|
28
|
+
} else if (textElement.getAttribute('font-family') === 'Mystery') {
|
|
29
|
+
textElement.setAttribute('font-family', 'Curly');
|
|
30
|
+
} else if (textElement.getAttribute('font-family') === 'Gloria') {
|
|
31
|
+
textElement.setAttribute('font-family', 'Handwriting');
|
|
32
|
+
} else if (textElement.getAttribute('font-family') === 'Donegal') {
|
|
33
|
+
textElement.setAttribute('font-family', 'Serif');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
module.exports = convertFonts;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileOverview Import bitmap data into Scratch 3.0, resizing image as necessary.
|
|
3
|
+
*/
|
|
4
|
+
const getFonts = require('scratch-render-fonts');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Given SVG data, inline the fonts. This allows them to be rendered correctly when set
|
|
8
|
+
* as the source of an HTMLImageElement. Here is a note from tmickel:
|
|
9
|
+
* // Inject fonts that are needed.
|
|
10
|
+
* // It would be nice if there were another way to get the SVG-in-canvas
|
|
11
|
+
* // to render the correct font family, but I couldn't find any other way.
|
|
12
|
+
* // Other things I tried:
|
|
13
|
+
* // Just injecting the font-family into the document: no effect.
|
|
14
|
+
* // External stylesheet linked to by SVG: no effect.
|
|
15
|
+
* // Using a <link> or <style>@import</style> to link to font-family
|
|
16
|
+
* // injected into the document: no effect.
|
|
17
|
+
* @param {string} svgString The string representation of the svg to modify
|
|
18
|
+
* @return {string} The svg with any needed fonts inlined
|
|
19
|
+
*/
|
|
20
|
+
const inlineSvgFonts = function (svgString) {
|
|
21
|
+
const FONTS = getFonts();
|
|
22
|
+
// Make it clear that this function only operates on strings.
|
|
23
|
+
// If we don't explicitly throw this here, the function silently fails.
|
|
24
|
+
if (typeof svgString !== 'string') {
|
|
25
|
+
throw new Error('SVG to be inlined is not a string');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Collect fonts that need injection.
|
|
29
|
+
const fontsNeeded = new Set();
|
|
30
|
+
const fontRegex = /font-family="([^"]*)"/g;
|
|
31
|
+
let matches = fontRegex.exec(svgString);
|
|
32
|
+
while (matches) {
|
|
33
|
+
fontsNeeded.add(matches[1]);
|
|
34
|
+
matches = fontRegex.exec(svgString);
|
|
35
|
+
}
|
|
36
|
+
if (fontsNeeded.size > 0) {
|
|
37
|
+
let str = '<defs><style>';
|
|
38
|
+
for (const font of fontsNeeded) {
|
|
39
|
+
if (Object.prototype.hasOwnProperty.call(FONTS, font)) {
|
|
40
|
+
str += `${FONTS[font]}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
str += '</style></defs>';
|
|
44
|
+
svgString = svgString.replace(/<svg[^>]*>/, `$&${str}`);
|
|
45
|
+
return svgString;
|
|
46
|
+
}
|
|
47
|
+
return svgString;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
module.exports = inlineSvgFonts;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const SVGRenderer = require('./svg-renderer');
|
|
2
|
+
const BitmapAdapter = require('./bitmap-adapter');
|
|
3
|
+
const inlineSvgFonts = require('./font-inliner');
|
|
4
|
+
const loadSvgString = require('./load-svg-string');
|
|
5
|
+
const sanitizeSvg = require('./sanitize-svg');
|
|
6
|
+
const serializeSvgToString = require('./serialize-svg-to-string');
|
|
7
|
+
const SvgElement = require('./svg-element');
|
|
8
|
+
const convertFonts = require('./font-converter');
|
|
9
|
+
// /**
|
|
10
|
+
// * Export for NPM & Node.js
|
|
11
|
+
// * @type {RenderWebGL}
|
|
12
|
+
// */
|
|
13
|
+
module.exports = {
|
|
14
|
+
BitmapAdapter: BitmapAdapter,
|
|
15
|
+
convertFonts: convertFonts,
|
|
16
|
+
inlineSvgFonts: inlineSvgFonts,
|
|
17
|
+
loadSvgString: loadSvgString,
|
|
18
|
+
sanitizeSvg: sanitizeSvg,
|
|
19
|
+
serializeSvgToString: serializeSvgToString,
|
|
20
|
+
SvgElement: SvgElement,
|
|
21
|
+
SVGRenderer: SVGRenderer
|
|
22
|
+
};
|