svgo-v2 2.8.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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +294 -0
  3. package/bin/svgo +10 -0
  4. package/dist/svgo.browser.js +1 -0
  5. package/lib/css-tools.js +239 -0
  6. package/lib/parser.js +259 -0
  7. package/lib/path.js +347 -0
  8. package/lib/stringifier.js +326 -0
  9. package/lib/style.js +283 -0
  10. package/lib/svgo/coa.js +517 -0
  11. package/lib/svgo/config.js +138 -0
  12. package/lib/svgo/css-class-list.js +72 -0
  13. package/lib/svgo/css-select-adapter.d.ts +2 -0
  14. package/lib/svgo/css-select-adapter.js +120 -0
  15. package/lib/svgo/css-style-declaration.js +232 -0
  16. package/lib/svgo/jsAPI.d.ts +2 -0
  17. package/lib/svgo/jsAPI.js +443 -0
  18. package/lib/svgo/plugins.js +109 -0
  19. package/lib/svgo/tools.js +137 -0
  20. package/lib/svgo-node.js +106 -0
  21. package/lib/svgo.js +83 -0
  22. package/lib/types.ts +172 -0
  23. package/lib/xast.js +102 -0
  24. package/package.json +130 -0
  25. package/plugins/_applyTransforms.js +335 -0
  26. package/plugins/_collections.js +2168 -0
  27. package/plugins/_path.js +816 -0
  28. package/plugins/_transforms.js +379 -0
  29. package/plugins/addAttributesToSVGElement.js +87 -0
  30. package/plugins/addClassesToSVGElement.js +87 -0
  31. package/plugins/cleanupAttrs.js +55 -0
  32. package/plugins/cleanupEnableBackground.js +75 -0
  33. package/plugins/cleanupIDs.js +297 -0
  34. package/plugins/cleanupListOfValues.js +154 -0
  35. package/plugins/cleanupNumericValues.js +113 -0
  36. package/plugins/collapseGroups.js +135 -0
  37. package/plugins/convertColors.js +152 -0
  38. package/plugins/convertEllipseToCircle.js +39 -0
  39. package/plugins/convertPathData.js +1023 -0
  40. package/plugins/convertShapeToPath.js +175 -0
  41. package/plugins/convertStyleToAttrs.js +132 -0
  42. package/plugins/convertTransform.js +432 -0
  43. package/plugins/inlineStyles.js +379 -0
  44. package/plugins/mergePaths.js +104 -0
  45. package/plugins/mergeStyles.js +93 -0
  46. package/plugins/minifyStyles.js +148 -0
  47. package/plugins/moveElemsAttrsToGroup.js +130 -0
  48. package/plugins/moveGroupAttrsToElems.js +62 -0
  49. package/plugins/plugins.js +56 -0
  50. package/plugins/prefixIds.js +241 -0
  51. package/plugins/preset-default.js +80 -0
  52. package/plugins/removeAttributesBySelector.js +99 -0
  53. package/plugins/removeAttrs.js +159 -0
  54. package/plugins/removeComments.js +31 -0
  55. package/plugins/removeDesc.js +41 -0
  56. package/plugins/removeDimensions.js +43 -0
  57. package/plugins/removeDoctype.js +42 -0
  58. package/plugins/removeEditorsNSData.js +68 -0
  59. package/plugins/removeElementsByAttr.js +78 -0
  60. package/plugins/removeEmptyAttrs.js +33 -0
  61. package/plugins/removeEmptyContainers.js +58 -0
  62. package/plugins/removeEmptyText.js +57 -0
  63. package/plugins/removeHiddenElems.js +318 -0
  64. package/plugins/removeMetadata.js +29 -0
  65. package/plugins/removeNonInheritableGroupAttrs.js +38 -0
  66. package/plugins/removeOffCanvasPaths.js +138 -0
  67. package/plugins/removeRasterImages.js +33 -0
  68. package/plugins/removeScriptElement.js +29 -0
  69. package/plugins/removeStyleElement.js +29 -0
  70. package/plugins/removeTitle.js +29 -0
  71. package/plugins/removeUnknownsAndDefaults.js +218 -0
  72. package/plugins/removeUnusedNS.js +61 -0
  73. package/plugins/removeUselessDefs.js +65 -0
  74. package/plugins/removeUselessStrokeAndFill.js +144 -0
  75. package/plugins/removeViewBox.js +51 -0
  76. package/plugins/removeXMLNS.js +30 -0
  77. package/plugins/removeXMLProcInst.js +30 -0
  78. package/plugins/reusePaths.js +113 -0
  79. package/plugins/sortAttrs.js +113 -0
  80. package/plugins/sortDefsChildren.js +60 -0
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ const os = require('os');
4
+ const fs = require('fs');
5
+ const { pathToFileURL } = require('url');
6
+ const path = require('path');
7
+ const {
8
+ extendDefaultPlugins,
9
+ optimize: optimizeAgnostic,
10
+ createContentItem,
11
+ } = require('./svgo.js');
12
+
13
+ exports.extendDefaultPlugins = extendDefaultPlugins;
14
+ exports.createContentItem = createContentItem;
15
+
16
+ const importConfig = async (configFile) => {
17
+ let config;
18
+ // at the moment dynamic import may randomly fail with segfault
19
+ // to workaround this for some users .cjs extension is loaded
20
+ // exclusively with require
21
+ if (configFile.endsWith('.cjs')) {
22
+ config = require(configFile);
23
+ } else {
24
+ try {
25
+ // dynamic import expects file url instead of path and may fail
26
+ // when windows path is provided
27
+ const { default: imported } = await import(pathToFileURL(configFile));
28
+ config = imported;
29
+ } catch (importError) {
30
+ // TODO remove require in v3
31
+ try {
32
+ config = require(configFile);
33
+ } catch (requireError) {
34
+ // throw original error if es module is detected
35
+ if (requireError.code === 'ERR_REQUIRE_ESM') {
36
+ throw importError;
37
+ } else {
38
+ throw requireError;
39
+ }
40
+ }
41
+ }
42
+ }
43
+ if (config == null || typeof config !== 'object' || Array.isArray(config)) {
44
+ throw Error(`Invalid config file "${configFile}"`);
45
+ }
46
+ return config;
47
+ };
48
+
49
+ const isFile = async (file) => {
50
+ try {
51
+ const stats = await fs.promises.stat(file);
52
+ return stats.isFile();
53
+ } catch {
54
+ return false;
55
+ }
56
+ };
57
+
58
+ const loadConfig = async (configFile, cwd = process.cwd()) => {
59
+ if (configFile != null) {
60
+ if (path.isAbsolute(configFile)) {
61
+ return await importConfig(configFile);
62
+ } else {
63
+ return await importConfig(path.join(cwd, configFile));
64
+ }
65
+ }
66
+ let dir = cwd;
67
+ // eslint-disable-next-line no-constant-condition
68
+ while (true) {
69
+ const js = path.join(dir, 'svgo.config.js');
70
+ if (await isFile(js)) {
71
+ return await importConfig(js);
72
+ }
73
+ const mjs = path.join(dir, 'svgo.config.mjs');
74
+ if (await isFile(mjs)) {
75
+ return await importConfig(mjs);
76
+ }
77
+ const cjs = path.join(dir, 'svgo.config.cjs');
78
+ if (await isFile(cjs)) {
79
+ return await importConfig(cjs);
80
+ }
81
+ const parent = path.dirname(dir);
82
+ if (dir === parent) {
83
+ return null;
84
+ }
85
+ dir = parent;
86
+ }
87
+ };
88
+ exports.loadConfig = loadConfig;
89
+
90
+ const optimize = (input, config) => {
91
+ if (config == null) {
92
+ config = {};
93
+ }
94
+ if (typeof config !== 'object') {
95
+ throw Error('Config should be an object');
96
+ }
97
+ return optimizeAgnostic(input, {
98
+ ...config,
99
+ js2svg: {
100
+ // platform specific default for end of line
101
+ eol: os.EOL === '\r\n' ? 'crlf' : 'lf',
102
+ ...config.js2svg,
103
+ },
104
+ });
105
+ };
106
+ exports.optimize = optimize;
package/lib/svgo.js ADDED
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ defaultPlugins,
5
+ resolvePluginConfig,
6
+ extendDefaultPlugins,
7
+ } = require('./svgo/config.js');
8
+ const { parseSvg } = require('./parser.js');
9
+ const { stringifySvg } = require('./stringifier.js');
10
+ const { invokePlugins } = require('./svgo/plugins.js');
11
+ const JSAPI = require('./svgo/jsAPI.js');
12
+ const { encodeSVGDatauri } = require('./svgo/tools.js');
13
+
14
+ exports.extendDefaultPlugins = extendDefaultPlugins;
15
+
16
+ const optimize = (input, config) => {
17
+ if (config == null) {
18
+ config = {};
19
+ }
20
+ if (typeof config !== 'object') {
21
+ throw Error('Config should be an object');
22
+ }
23
+ const maxPassCount = config.multipass ? 10 : 1;
24
+ let prevResultSize = Number.POSITIVE_INFINITY;
25
+ let svgjs = null;
26
+ const info = {};
27
+ if (config.path != null) {
28
+ info.path = config.path;
29
+ }
30
+ for (let i = 0; i < maxPassCount; i += 1) {
31
+ info.multipassCount = i;
32
+ // TODO throw this error in v3
33
+ try {
34
+ svgjs = parseSvg(input, config.path);
35
+ } catch (error) {
36
+ return { error: error.toString(), modernError: error };
37
+ }
38
+ if (svgjs.error != null) {
39
+ if (config.path != null) {
40
+ svgjs.path = config.path;
41
+ }
42
+ return svgjs;
43
+ }
44
+ const plugins = config.plugins || defaultPlugins;
45
+ if (Array.isArray(plugins) === false) {
46
+ throw Error(
47
+ "Invalid plugins list. Provided 'plugins' in config should be an array."
48
+ );
49
+ }
50
+ const resolvedPlugins = plugins.map(resolvePluginConfig);
51
+ const globalOverrides = {};
52
+ if (config.floatPrecision != null) {
53
+ globalOverrides.floatPrecision = config.floatPrecision;
54
+ }
55
+ svgjs = invokePlugins(svgjs, info, resolvedPlugins, null, globalOverrides);
56
+ svgjs = stringifySvg(svgjs, config.js2svg);
57
+ if (svgjs.data.length < prevResultSize) {
58
+ input = svgjs.data;
59
+ prevResultSize = svgjs.data.length;
60
+ } else {
61
+ if (config.datauri) {
62
+ svgjs.data = encodeSVGDatauri(svgjs.data, config.datauri);
63
+ }
64
+ if (config.path != null) {
65
+ svgjs.path = config.path;
66
+ }
67
+ return svgjs;
68
+ }
69
+ }
70
+ return svgjs;
71
+ };
72
+ exports.optimize = optimize;
73
+
74
+ /**
75
+ * The factory that creates a content item with the helper methods.
76
+ *
77
+ * @param {Object} data which is passed to jsAPI constructor
78
+ * @returns {JSAPI} content item
79
+ */
80
+ const createContentItem = (data) => {
81
+ return new JSAPI(data);
82
+ };
83
+ exports.createContentItem = createContentItem;
package/lib/types.ts ADDED
@@ -0,0 +1,172 @@
1
+ export type XastDoctype = {
2
+ type: 'doctype';
3
+ name: string;
4
+ data: {
5
+ doctype: string;
6
+ };
7
+ };
8
+
9
+ export type XastInstruction = {
10
+ type: 'instruction';
11
+ name: string;
12
+ value: string;
13
+ };
14
+
15
+ export type XastComment = {
16
+ type: 'comment';
17
+ value: string;
18
+ };
19
+
20
+ export type XastCdata = {
21
+ type: 'cdata';
22
+ value: string;
23
+ };
24
+
25
+ export type XastText = {
26
+ type: 'text';
27
+ value: string;
28
+ };
29
+
30
+ export type XastElement = {
31
+ type: 'element';
32
+ name: string;
33
+ attributes: Record<string, string>;
34
+ children: Array<XastChild>;
35
+ };
36
+
37
+ export type XastChild =
38
+ | XastDoctype
39
+ | XastInstruction
40
+ | XastComment
41
+ | XastCdata
42
+ | XastText
43
+ | XastElement;
44
+
45
+ export type XastRoot = {
46
+ type: 'root';
47
+ children: Array<XastChild>;
48
+ };
49
+
50
+ export type XastParent = XastRoot | XastElement;
51
+
52
+ export type XastNode = XastRoot | XastChild;
53
+
54
+ export type StringifyOptions = {
55
+ doctypeStart?: string;
56
+ doctypeEnd?: string;
57
+ procInstStart?: string;
58
+ procInstEnd?: string;
59
+ tagOpenStart?: string;
60
+ tagOpenEnd?: string;
61
+ tagCloseStart?: string;
62
+ tagCloseEnd?: string;
63
+ tagShortStart?: string;
64
+ tagShortEnd?: string;
65
+ attrStart?: string;
66
+ attrEnd?: string;
67
+ commentStart?: string;
68
+ commentEnd?: string;
69
+ cdataStart?: string;
70
+ cdataEnd?: string;
71
+ textStart?: string;
72
+ textEnd?: string;
73
+ indent?: number | string;
74
+ regEntities?: RegExp;
75
+ regValEntities?: RegExp;
76
+ encodeEntity?: (char: string) => string;
77
+ pretty?: boolean;
78
+ useShortTags?: boolean;
79
+ eol?: 'lf' | 'crlf';
80
+ finalNewline?: boolean;
81
+ };
82
+
83
+ type VisitorNode<Node> = {
84
+ enter?: (node: Node, parentNode: XastParent) => void | symbol;
85
+ exit?: (node: Node, parentNode: XastParent) => void;
86
+ };
87
+
88
+ type VisitorRoot = {
89
+ enter?: (node: XastRoot, parentNode: null) => void;
90
+ exit?: (node: XastRoot, parentNode: null) => void;
91
+ };
92
+
93
+ export type Visitor = {
94
+ doctype?: VisitorNode<XastDoctype>;
95
+ instruction?: VisitorNode<XastInstruction>;
96
+ comment?: VisitorNode<XastComment>;
97
+ cdata?: VisitorNode<XastCdata>;
98
+ text?: VisitorNode<XastText>;
99
+ element?: VisitorNode<XastElement>;
100
+ root?: VisitorRoot;
101
+ };
102
+
103
+ export type PluginInfo = {
104
+ path?: string;
105
+ multipassCount: number;
106
+ };
107
+
108
+ export type Plugin<Params> = (
109
+ root: XastRoot,
110
+ params: Params,
111
+ info: PluginInfo
112
+ ) => null | Visitor;
113
+
114
+ export type Specificity = [number, number, number, number];
115
+
116
+ export type StylesheetDeclaration = {
117
+ name: string;
118
+ value: string;
119
+ important: boolean;
120
+ };
121
+
122
+ export type StylesheetRule = {
123
+ dynamic: boolean;
124
+ selectors: string;
125
+ specificity: Specificity;
126
+ declarations: Array<StylesheetDeclaration>;
127
+ };
128
+
129
+ export type Stylesheet = {
130
+ rules: Array<StylesheetRule>;
131
+ parents: Map<XastElement, XastParent>;
132
+ };
133
+
134
+ type StaticStyle = {
135
+ type: 'static';
136
+ inherited: boolean;
137
+ value: string;
138
+ };
139
+
140
+ type DynamicStyle = {
141
+ type: 'dynamic';
142
+ inherited: boolean;
143
+ };
144
+
145
+ export type ComputedStyles = Record<string, StaticStyle | DynamicStyle>;
146
+
147
+ export type PathDataCommand =
148
+ | 'M'
149
+ | 'm'
150
+ | 'Z'
151
+ | 'z'
152
+ | 'L'
153
+ | 'l'
154
+ | 'H'
155
+ | 'h'
156
+ | 'V'
157
+ | 'v'
158
+ | 'C'
159
+ | 'c'
160
+ | 'S'
161
+ | 's'
162
+ | 'Q'
163
+ | 'q'
164
+ | 'T'
165
+ | 't'
166
+ | 'A'
167
+ | 'a';
168
+
169
+ export type PathDataItem = {
170
+ command: PathDataCommand;
171
+ args: Array<number>;
172
+ };
package/lib/xast.js ADDED
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @typedef {import('./types').XastNode} XastNode
5
+ * @typedef {import('./types').XastChild} XastChild
6
+ * @typedef {import('./types').XastParent} XastParent
7
+ * @typedef {import('./types').Visitor} Visitor
8
+ */
9
+
10
+ const { selectAll, selectOne, is } = require('css-select');
11
+ const xastAdaptor = require('./svgo/css-select-adapter.js');
12
+
13
+ const cssSelectOptions = {
14
+ xmlMode: true,
15
+ adapter: xastAdaptor,
16
+ };
17
+
18
+ /**
19
+ * @type {(node: XastNode, selector: string) => Array<XastChild>}
20
+ */
21
+ const querySelectorAll = (node, selector) => {
22
+ return selectAll(selector, node, cssSelectOptions);
23
+ };
24
+ exports.querySelectorAll = querySelectorAll;
25
+
26
+ /**
27
+ * @type {(node: XastNode, selector: string) => null | XastChild}
28
+ */
29
+ const querySelector = (node, selector) => {
30
+ return selectOne(selector, node, cssSelectOptions);
31
+ };
32
+ exports.querySelector = querySelector;
33
+
34
+ /**
35
+ * @type {(node: XastChild, selector: string) => boolean}
36
+ */
37
+ const matches = (node, selector) => {
38
+ return is(node, selector, cssSelectOptions);
39
+ };
40
+ exports.matches = matches;
41
+
42
+ /**
43
+ * @type {(node: XastChild, name: string) => null | XastChild}
44
+ */
45
+ const closestByName = (node, name) => {
46
+ let currentNode = node;
47
+ while (currentNode) {
48
+ if (currentNode.type === 'element' && currentNode.name === name) {
49
+ return currentNode;
50
+ }
51
+ // @ts-ignore parentNode is hidden from public usage
52
+ currentNode = currentNode.parentNode;
53
+ }
54
+ return null;
55
+ };
56
+ exports.closestByName = closestByName;
57
+
58
+ const visitSkip = Symbol();
59
+ exports.visitSkip = visitSkip;
60
+
61
+ /**
62
+ * @type {(node: XastNode, visitor: Visitor, parentNode?: any) => void}
63
+ */
64
+ const visit = (node, visitor, parentNode) => {
65
+ const callbacks = visitor[node.type];
66
+ if (callbacks && callbacks.enter) {
67
+ // @ts-ignore hard to infer
68
+ const symbol = callbacks.enter(node, parentNode);
69
+ if (symbol === visitSkip) {
70
+ return;
71
+ }
72
+ }
73
+ // visit root children
74
+ if (node.type === 'root') {
75
+ // copy children array to not loose cursor when children is spliced
76
+ for (const child of node.children) {
77
+ visit(child, visitor, node);
78
+ }
79
+ }
80
+ // visit element children if still attached to parent
81
+ if (node.type === 'element') {
82
+ if (parentNode.children.includes(node)) {
83
+ for (const child of node.children) {
84
+ visit(child, visitor, node);
85
+ }
86
+ }
87
+ }
88
+ if (callbacks && callbacks.exit) {
89
+ // @ts-ignore hard to infer
90
+ callbacks.exit(node, parentNode);
91
+ }
92
+ };
93
+ exports.visit = visit;
94
+
95
+ /**
96
+ * @type {(node: XastChild, parentNode: XastParent) => void}
97
+ */
98
+ const detachNodeFromParent = (node, parentNode) => {
99
+ // avoid splice to not break for loops
100
+ parentNode.children = parentNode.children.filter((child) => child !== node);
101
+ };
102
+ exports.detachNodeFromParent = detachNodeFromParent;
package/package.json ADDED
@@ -0,0 +1,130 @@
1
+ {
2
+ "packageManager": "yarn@2.4.3",
3
+ "name": "svgo-v2",
4
+ "version": "2.8.0",
5
+ "description": "将css-tree替换为css-tree-v2",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "svgo",
9
+ "svg",
10
+ "optimize",
11
+ "minify"
12
+ ],
13
+ "homepage": "https://github.com/svg/svgo",
14
+ "bugs": {
15
+ "url": "https://github.com/svg/svgo/issues"
16
+ },
17
+ "author": {
18
+ "name": "Kir Belevich",
19
+ "email": "kir@belevi.ch",
20
+ "url": "https://github.com/deepsweet"
21
+ },
22
+ "contributors": [
23
+ {
24
+ "name": "Sergey Belov",
25
+ "email": "peimei@ya.ru",
26
+ "url": "https://github.com/arikon"
27
+ },
28
+ {
29
+ "name": "Lev Solntsev",
30
+ "email": "lev.sun@ya.ru",
31
+ "url": "https://github.com/GreLI"
32
+ },
33
+ {
34
+ "name": "Bogdan Chadkin",
35
+ "email": "trysound@yandex.ru",
36
+ "url": "https://github.com/TrySound"
37
+ }
38
+ ],
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git://github.com/svg/svgo.git"
42
+ },
43
+ "main": "./lib/svgo-node.js",
44
+ "bin": "./bin/svgo",
45
+ "files": [
46
+ "bin",
47
+ "lib",
48
+ "plugins",
49
+ "dist",
50
+ "!**/*.test.js"
51
+ ],
52
+ "engines": {
53
+ "node": ">=10.13.0"
54
+ },
55
+ "scripts": {
56
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest --maxWorkers=4 --coverage",
57
+ "lint": "eslint --ignore-path .gitignore . && prettier --check \"**/*.js\" --ignore-path .gitignore",
58
+ "fix": "eslint --ignore-path .gitignore --fix . && prettier --write \"**/*.js\" --ignore-path .gitignore",
59
+ "typecheck": "tsc",
60
+ "test-browser": "rollup -c && node ./test/browser.js",
61
+ "test-regression": "node ./test/regression-extract.js && NO_DIFF=1 node ./test/regression.js"
62
+ },
63
+ "prettier": {
64
+ "singleQuote": true
65
+ },
66
+ "eslintConfig": {
67
+ "parserOptions": {
68
+ "ecmaVersion": "2021"
69
+ },
70
+ "env": {
71
+ "node": true,
72
+ "es2021": true
73
+ },
74
+ "extends": [
75
+ "eslint:recommended"
76
+ ],
77
+ "overrides": [
78
+ {
79
+ "files": [
80
+ "rollup.config.js"
81
+ ],
82
+ "parserOptions": {
83
+ "sourceType": "module"
84
+ }
85
+ },
86
+ {
87
+ "files": [
88
+ "**/*.test.js"
89
+ ],
90
+ "env": {
91
+ "jest": true
92
+ }
93
+ }
94
+ ]
95
+ },
96
+ "jest": {
97
+ "coveragePathIgnorePatterns": [
98
+ "fixtures"
99
+ ]
100
+ },
101
+ "dependencies": {
102
+ "@trysound/sax": "0.2.0",
103
+ "commander": "^7.2.0",
104
+ "css-select": "^4.1.3",
105
+ "css-tree-v2": "^1.1.3",
106
+ "csso": "^4.2.0",
107
+ "picocolors": "^1.0.0",
108
+ "stable": "^0.1.8"
109
+ },
110
+ "devDependencies": {
111
+ "@rollup/plugin-commonjs": "^20.0.0",
112
+ "@rollup/plugin-json": "^4.1.0",
113
+ "@rollup/plugin-node-resolve": "^13.0.4",
114
+ "@types/css-tree": "^1.0.6",
115
+ "@types/csso": "^4.2.0",
116
+ "@types/jest": "^27.0.1",
117
+ "del": "^6.0.0",
118
+ "eslint": "^7.32.0",
119
+ "jest": "^27.2.5",
120
+ "node-fetch": "^2.6.2",
121
+ "pixelmatch": "^5.2.1",
122
+ "playwright": "^1.14.1",
123
+ "pngjs": "^6.0.0",
124
+ "prettier": "^2.4.0",
125
+ "rollup": "^2.56.3",
126
+ "rollup-plugin-terser": "^7.0.2",
127
+ "tar-stream": "^2.2.0",
128
+ "typescript": "^4.4.3"
129
+ }
130
+ }