├── .editorconfig ├── .github └── workflows │ └── CI.yml ├── .gitignore ├── .yarn └── releases │ └── yarn-3.5.1.cjs ├── .yarnrc.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── __test__ ├── index.spec.ts ├── index.spec.ts.md └── index.spec.ts.snap ├── crates ├── binding │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ └── lib.rs └── core │ ├── Cargo.toml │ ├── __fixture__ │ └── plugins │ │ ├── cleanupIds.01.svg │ │ ├── cleanupIds.02.svg │ │ ├── cleanupIds.03.svg │ │ ├── cleanupIds.04.svg │ │ ├── cleanupIds.05.svg │ │ ├── cleanupIds.06.svg │ │ ├── cleanupIds.07.svg │ │ ├── cleanupIds.08.svg │ │ ├── cleanupIds.09.svg │ │ ├── cleanupIds.10.svg │ │ ├── cleanupIds.11.svg │ │ ├── cleanupIds.12.svg │ │ ├── cleanupIds.13.svg │ │ ├── cleanupIds.14.svg │ │ ├── cleanupIds.15.svg │ │ ├── cleanupIds.16.svg │ │ ├── cleanupIds.17.svg │ │ ├── cleanupIds.18.svg │ │ ├── cleanupIds.19.svg │ │ ├── cleanupIds.20.svg │ │ ├── cleanupIds.21.svg │ │ ├── cleanupNumericValues.01.svg │ │ ├── cleanupNumericValues.02.svg │ │ ├── collapseGroups.01.svg │ │ ├── collapseGroups.02.svg │ │ ├── collapseGroups.06.svg │ │ ├── collapseGroups.07.svg │ │ ├── collapseGroups.08.svg │ │ ├── collapseGroups.09.svg │ │ ├── collapseGroups.11.svg │ │ ├── collapseGroups.12.svg │ │ ├── collapseGroups.13.svg │ │ ├── collapseGroups.14.svg │ │ ├── collapseGroups.15.svg │ │ ├── collapseGroups.16.svg │ │ ├── convertColors.01.svg │ │ ├── convertColors.02.svg │ │ ├── convertColors.03.svg │ │ ├── convertColors.04.svg │ │ └── convertEllipseToCircle.01.svg │ └── src │ ├── collections.rs │ ├── lib.rs │ ├── parser.rs │ ├── plugins │ ├── cleanup_attrs.rs │ ├── cleanup_enable_background.rs │ ├── cleanup_ids.rs │ ├── cleanup_numeric_values.rs │ ├── collapse_groups.rs │ ├── convert_colors.rs │ ├── convert_ellipse_to_circle.rs │ └── mod.rs │ ├── stringifier.rs │ └── testing.rs ├── index.d.ts ├── index.js ├── npm ├── darwin-arm64 │ ├── README.md │ └── package.json ├── darwin-universal │ ├── README.md │ └── package.json ├── darwin-x64 │ ├── README.md │ └── package.json ├── linux-x64-gnu │ ├── README.md │ └── package.json └── win32-x64-msvc │ ├── README.md │ └── package.json ├── package.json ├── rustfmt.toml └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.{js,ts}] 7 | indent_style = tab 8 | indent_size = 2 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | max_line_length = 80 13 | 14 | [*.{yml,yaml,json}] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [test/cases/parsing/bom/bomfile.{css,js}] 19 | charset = utf-8-bom 20 | 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | env: 3 | DEBUG: napi:* 4 | APP_NAME: svgo-rs 5 | MACOSX_DEPLOYMENT_TARGET: '10.13' 6 | 'on': 7 | push: 8 | branches: 9 | - main 10 | tags-ignore: 11 | - '**' 12 | paths-ignore: 13 | - '**/*.md' 14 | - LICENSE 15 | - '**/*.gitignore' 16 | - .editorconfig 17 | - docs/** 18 | pull_request: null 19 | jobs: 20 | cargo-test: 21 | name: Test cargo 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v3 25 | - name: Install Rust 26 | uses: dtolnay/rust-toolchain@stable 27 | with: 28 | toolchain: nightly-2023-06-27 29 | target: x86_64-unknown-linux-gnu 30 | - name: Run cargo test 31 | run: cargo test 32 | build: 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | settings: 37 | - host: macos-latest 38 | target: x86_64-apple-darwin 39 | build: | 40 | yarn build 41 | strip -x *.node 42 | - host: windows-latest 43 | build: yarn build 44 | target: x86_64-pc-windows-msvc 45 | - host: ubuntu-latest 46 | target: x86_64-unknown-linux-gnu 47 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian 48 | build: |- 49 | set -e && 50 | rustup default nightly-2023-06-27 && 51 | yarn build --target x86_64-unknown-linux-gnu && 52 | strip *.node 53 | - host: macos-latest 54 | target: aarch64-apple-darwin 55 | build: | 56 | yarn build --target aarch64-apple-darwin 57 | strip -x *.node 58 | name: stable - ${{ matrix.settings.target }} - node@18 59 | needs: 60 | - cargo-test 61 | runs-on: ${{ matrix.settings.host }} 62 | steps: 63 | - uses: actions/checkout@v3 64 | - name: Setup node 65 | uses: actions/setup-node@v3 66 | if: ${{ !matrix.settings.docker }} 67 | with: 68 | node-version: 18 69 | check-latest: true 70 | cache: yarn 71 | - name: Install 72 | uses: dtolnay/rust-toolchain@stable 73 | if: ${{ !matrix.settings.docker }} 74 | with: 75 | toolchain: nightly-2023-06-27 76 | targets: ${{ matrix.settings.target }} 77 | - name: Cache cargo 78 | uses: actions/cache@v3 79 | with: 80 | path: | 81 | ~/.cargo/registry/index/ 82 | ~/.cargo/registry/cache/ 83 | ~/.cargo/git/db/ 84 | .cargo-cache 85 | target/ 86 | key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }} 87 | - uses: goto-bus-stop/setup-zig@v2 88 | if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' }} 89 | with: 90 | version: 0.10.1 91 | - name: Setup toolchain 92 | run: ${{ matrix.settings.setup }} 93 | if: ${{ matrix.settings.setup }} 94 | shell: bash 95 | - name: Setup node x86 96 | if: matrix.settings.target == 'i686-pc-windows-msvc' 97 | run: yarn config set supportedArchitectures.cpu "ia32" 98 | shell: bash 99 | - name: Install dependencies 100 | run: yarn install 101 | - name: Setup node x86 102 | uses: actions/setup-node@v3 103 | if: matrix.settings.target == 'i686-pc-windows-msvc' 104 | with: 105 | node-version: 18 106 | check-latest: true 107 | cache: yarn 108 | architecture: x86 109 | - name: Build in docker 110 | uses: addnab/docker-run-action@v3 111 | if: ${{ matrix.settings.docker }} 112 | with: 113 | image: ${{ matrix.settings.docker }} 114 | options: '--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build' 115 | run: ${{ matrix.settings.build }} 116 | - name: Build 117 | run: ${{ matrix.settings.build }} 118 | if: ${{ !matrix.settings.docker }} 119 | shell: bash 120 | - name: Upload artifact 121 | uses: actions/upload-artifact@v3 122 | with: 123 | name: bindings-${{ matrix.settings.target }} 124 | path: ${{ env.APP_NAME }}.*.node 125 | if-no-files-found: error 126 | test-macOS-windows-binding: 127 | name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }} 128 | needs: 129 | - build 130 | strategy: 131 | fail-fast: false 132 | matrix: 133 | settings: 134 | - host: windows-latest 135 | target: x86_64-pc-windows-msvc 136 | node: 137 | - '14' 138 | - '16' 139 | - '18' 140 | runs-on: ${{ matrix.settings.host }} 141 | steps: 142 | - uses: actions/checkout@v3 143 | - name: Setup node 144 | uses: actions/setup-node@v3 145 | with: 146 | node-version: ${{ matrix.node }} 147 | check-latest: true 148 | cache: yarn 149 | - name: Install dependencies 150 | run: yarn install 151 | - name: Download artifacts 152 | uses: actions/download-artifact@v3 153 | with: 154 | name: bindings-${{ matrix.settings.target }} 155 | path: . 156 | - name: List packages 157 | run: ls -R . 158 | shell: bash 159 | - name: Test bindings 160 | run: yarn test 161 | test-linux-x64-gnu-binding: 162 | name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }} 163 | needs: 164 | - build 165 | strategy: 166 | fail-fast: false 167 | matrix: 168 | node: 169 | - '14' 170 | - '16' 171 | - '18' 172 | runs-on: ubuntu-latest 173 | steps: 174 | - uses: actions/checkout@v3 175 | - name: Setup node 176 | uses: actions/setup-node@v3 177 | with: 178 | node-version: ${{ matrix.node }} 179 | check-latest: true 180 | cache: yarn 181 | - name: Install dependencies 182 | run: yarn install 183 | - name: Download artifacts 184 | uses: actions/download-artifact@v3 185 | with: 186 | name: bindings-x86_64-unknown-linux-gnu 187 | path: . 188 | - name: List packages 189 | run: ls -R . 190 | shell: bash 191 | - name: Test bindings 192 | run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-slim yarn test 193 | universal-macOS: 194 | name: Build universal macOS binary 195 | needs: 196 | - build 197 | runs-on: macos-latest 198 | steps: 199 | - uses: actions/checkout@v3 200 | - name: Setup node 201 | uses: actions/setup-node@v3 202 | with: 203 | node-version: 18 204 | check-latest: true 205 | cache: yarn 206 | - name: Install dependencies 207 | run: yarn install 208 | - name: Download macOS x64 artifact 209 | uses: actions/download-artifact@v3 210 | with: 211 | name: bindings-x86_64-apple-darwin 212 | path: artifacts 213 | - name: Download macOS arm64 artifact 214 | uses: actions/download-artifact@v3 215 | with: 216 | name: bindings-aarch64-apple-darwin 217 | path: artifacts 218 | - name: Combine binaries 219 | run: yarn universal 220 | - name: Upload artifact 221 | uses: actions/upload-artifact@v3 222 | with: 223 | name: bindings-universal-apple-darwin 224 | path: ${{ env.APP_NAME }}.*.node 225 | if-no-files-found: error 226 | publish: 227 | name: Publish 228 | runs-on: ubuntu-latest 229 | needs: 230 | - test-macOS-windows-binding 231 | - universal-macOS 232 | steps: 233 | - uses: actions/checkout@v3 234 | - name: Setup node 235 | uses: actions/setup-node@v3 236 | with: 237 | node-version: 18 238 | check-latest: true 239 | cache: yarn 240 | - name: Install dependencies 241 | run: yarn install 242 | - name: Download all artifacts 243 | uses: actions/download-artifact@v3 244 | with: 245 | path: artifacts 246 | - name: Move artifacts 247 | run: yarn artifacts 248 | - name: List packages 249 | run: ls -R ./npm 250 | shell: bash 251 | - name: Publish 252 | run: | 253 | if git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+$"; 254 | then 255 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 256 | npm publish --access public 257 | elif git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+"; 258 | then 259 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 260 | npm publish --tag next --access public 261 | else 262 | echo "Not a release, skipping publish" 263 | fi 264 | env: 265 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 266 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 267 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/node 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # TypeScript v1 declaration files 49 | typings/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variables file 76 | .env 77 | .env.test 78 | 79 | # parcel-bundler cache (https://parceljs.org/) 80 | .cache 81 | 82 | # Next.js build output 83 | .next 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # End of https://www.toptal.com/developers/gitignore/api/node 114 | 115 | # Created by https://www.toptal.com/developers/gitignore/api/macos 116 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos 117 | 118 | ### macOS ### 119 | # General 120 | .DS_Store 121 | .AppleDouble 122 | .LSOverride 123 | 124 | # Icon must end with two 125 | Icon 126 | 127 | 128 | # Thumbnails 129 | ._* 130 | 131 | # Files that might appear in the root of a volume 132 | .DocumentRevisions-V100 133 | .fseventsd 134 | .Spotlight-V100 135 | .TemporaryItems 136 | .Trashes 137 | .VolumeIcon.icns 138 | .com.apple.timemachine.donotpresent 139 | 140 | # Directories potentially created on remote AFP share 141 | .AppleDB 142 | .AppleDesktop 143 | Network Trash Folder 144 | Temporary Items 145 | .apdisk 146 | 147 | ### macOS Patch ### 148 | # iCloud generated files 149 | *.icloud 150 | 151 | # End of https://www.toptal.com/developers/gitignore/api/macos 152 | 153 | # Created by https://www.toptal.com/developers/gitignore/api/windows 154 | # Edit at https://www.toptal.com/developers/gitignore?templates=windows 155 | 156 | ### Windows ### 157 | # Windows thumbnail cache files 158 | Thumbs.db 159 | Thumbs.db:encryptable 160 | ehthumbs.db 161 | ehthumbs_vista.db 162 | 163 | # Dump file 164 | *.stackdump 165 | 166 | # Folder config file 167 | [Dd]esktop.ini 168 | 169 | # Recycle Bin used on file shares 170 | $RECYCLE.BIN/ 171 | 172 | # Windows Installer files 173 | *.cab 174 | *.msi 175 | *.msix 176 | *.msm 177 | *.msp 178 | 179 | # Windows shortcuts 180 | *.lnk 181 | 182 | # End of https://www.toptal.com/developers/gitignore/api/windows 183 | 184 | #Added by cargo 185 | 186 | /target 187 | 188 | .pnp.* 189 | .yarn/* 190 | !.yarn/patches 191 | !.yarn/plugins 192 | !.yarn/releases 193 | !.yarn/sdks 194 | !.yarn/versions 195 | 196 | *.node 197 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-3.5.1.cjs 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/core", 4 | "crates/binding" 5 | ] 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 svg-rust 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

SVGO RS

2 | 3 |

Speedy SVGO rewritten in Rust 🦀

4 | 5 | > ⚠️ RVGO RS is in early development and should not be used in production, expect bugs! 🐛 6 | -------------------------------------------------------------------------------- /__test__/index.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { optimize } from '..' 3 | 4 | test('basic', (t) => { 5 | const svg = ` 6 | 7 | 8 | 9 | ` 10 | const output = optimize(svg) 11 | t.snapshot(output) 12 | }) 13 | -------------------------------------------------------------------------------- /__test__/index.spec.ts.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `__test__/index.spec.ts` 2 | 3 | The actual snapshot is saved in `index.spec.ts.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## basic 8 | 9 | > Snapshot 1 10 | 11 | { 12 | data: `␊ 13 | ␊ 14 | ␊ 15 | `, 16 | } 17 | -------------------------------------------------------------------------------- /__test__/index.spec.ts.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svg-rust/svgo-rs/0062a5fd746e55e1a78f3361edf3b43512fdac44/__test__/index.spec.ts.snap -------------------------------------------------------------------------------- /crates/binding/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["SyMind "] 3 | name = "binding" 4 | version = "0.0.0" 5 | edition = "2021" 6 | publish = false 7 | license = "MIT" 8 | 9 | [lib] 10 | bench = false 11 | crate-type = ["cdylib"] 12 | 13 | [dependencies] 14 | napi = { version = "2.12.0", default-features = false, features = ["napi4"] } 15 | napi-derive = "2.12.2" 16 | svgo-rs = { path = "../core", features = ["node"] } 17 | 18 | [build-dependencies] 19 | napi-build = "2.0.1" 20 | 21 | [profile.release] 22 | lto = true 23 | -------------------------------------------------------------------------------- /crates/binding/build.rs: -------------------------------------------------------------------------------- 1 | extern crate napi_build; 2 | 3 | fn main() { 4 | napi_build::setup(); 5 | } 6 | -------------------------------------------------------------------------------- /crates/binding/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::all)] 2 | 3 | #[macro_use] 4 | extern crate napi_derive; 5 | 6 | use svgo_rs::{optimize as optimize_core, Output}; 7 | 8 | /// The core of SVGO RS 9 | #[napi] 10 | pub fn optimize(input: String) -> Output { 11 | optimize_core(input) 12 | } 13 | -------------------------------------------------------------------------------- /crates/core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["SyMind "] 3 | description = "A tool for optimizing SVG vector graphics files" 4 | name = "svgo-rs" 5 | edition = "2021" 6 | license = "MIT" 7 | repository = "https://github.com/svg-rust/svgo-rs.git" 8 | version = "0.0.0" 9 | 10 | [features] 11 | node = ["dep:napi", "dep:napi-derive"] 12 | 13 | [dependencies] 14 | # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix 15 | napi = { version = "2.12.0", default-features = false, features = ["napi4"], optional = true } 16 | napi-derive = { version = "2.12.2", optional = true } 17 | 18 | regex = "1.8.1" 19 | serde = { version = "1", features = ["derive"] } 20 | serde_json = "1" 21 | swc_core = { version = "0.78.28", features = [ 22 | "ecma_ast", 23 | "ecma_ast_serde", 24 | "common_concurrent", 25 | "bundler", 26 | "ecma_loader", 27 | "ecma_transforms", 28 | "ecma_visit", 29 | "ecma_codegen", 30 | "base_node", 31 | "__parser", 32 | ] } 33 | swc_xml_ast = "0.10.17" 34 | swc_xml_codegen = "0.11.22" 35 | swc_xml_parser = "0.11.20" 36 | swc_xml_visit = "0.10.17" 37 | linked-hash-map = "0.5.6" 38 | 39 | [dev-dependencies] 40 | pretty_assertions = "1.3.0" 41 | testing = "0.33.19" 42 | 43 | [profile.release] 44 | lto = true 45 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.01.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | referenced text 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | @@@ 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | referenced text 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.02.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | @@@ 9 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.03.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | @@@ 9 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.04.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | referenced text 5 | 6 | 7 | referenced text 8 | 9 | 10 | referenced text 11 | 12 | 13 | referenced text 14 | 15 | 16 | referenced text 17 | 18 | 19 | referenced text 20 | 21 | 22 | referenced text 23 | 24 | 25 | referenced text 26 | 27 | 28 | referenced text 29 | 30 | 31 | referenced text 32 | 33 | 34 | referenced text 35 | 36 | 37 | referenced text 38 | 39 | 40 | referenced text 41 | 42 | 43 | referenced text 44 | 45 | 46 | referenced text 47 | 48 | 49 | referenced text 50 | 51 | 52 | referenced text 53 | 54 | 55 | referenced text 56 | 57 | 58 | referenced text 59 | 60 | 61 | referenced text 62 | 63 | 64 | referenced text 65 | 66 | 67 | referenced text 68 | 69 | 70 | referenced text 71 | 72 | 73 | referenced text 74 | 75 | 76 | referenced text 77 | 78 | 79 | referenced text 80 | 81 | 82 | referenced text 83 | 84 | 85 | referenced text 86 | 87 | 88 | referenced text 89 | 90 | 91 | referenced text 92 | 93 | 94 | referenced text 95 | 96 | 97 | referenced text 98 | 99 | 100 | referenced text 101 | 102 | 103 | referenced text 104 | 105 | 106 | referenced text 107 | 108 | 109 | referenced text 110 | 111 | 112 | referenced text 113 | 114 | 115 | referenced text 116 | 117 | 118 | referenced text 119 | 120 | 121 | referenced text 122 | 123 | 124 | referenced text 125 | 126 | 127 | referenced text 128 | 129 | 130 | referenced text 131 | 132 | 133 | referenced text 134 | 135 | 136 | referenced text 137 | 138 | 139 | referenced text 140 | 141 | 142 | referenced text 143 | 144 | 145 | referenced text 146 | 147 | 148 | referenced text 149 | 150 | 151 | referenced text 152 | 153 | 154 | referenced text 155 | 156 | 157 | referenced text 158 | 159 | 160 | referenced text 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | @@@ 221 | 222 | 223 | 224 | 225 | referenced text 226 | 227 | 228 | referenced text 229 | 230 | 231 | referenced text 232 | 233 | 234 | referenced text 235 | 236 | 237 | referenced text 238 | 239 | 240 | referenced text 241 | 242 | 243 | referenced text 244 | 245 | 246 | referenced text 247 | 248 | 249 | referenced text 250 | 251 | 252 | referenced text 253 | 254 | 255 | referenced text 256 | 257 | 258 | referenced text 259 | 260 | 261 | referenced text 262 | 263 | 264 | referenced text 265 | 266 | 267 | referenced text 268 | 269 | 270 | referenced text 271 | 272 | 273 | referenced text 274 | 275 | 276 | referenced text 277 | 278 | 279 | referenced text 280 | 281 | 282 | referenced text 283 | 284 | 285 | referenced text 286 | 287 | 288 | referenced text 289 | 290 | 291 | referenced text 292 | 293 | 294 | referenced text 295 | 296 | 297 | referenced text 298 | 299 | 300 | referenced text 301 | 302 | 303 | referenced text 304 | 305 | 306 | referenced text 307 | 308 | 309 | referenced text 310 | 311 | 312 | referenced text 313 | 314 | 315 | referenced text 316 | 317 | 318 | referenced text 319 | 320 | 321 | referenced text 322 | 323 | 324 | referenced text 325 | 326 | 327 | referenced text 328 | 329 | 330 | referenced text 331 | 332 | 333 | referenced text 334 | 335 | 336 | referenced text 337 | 338 | 339 | referenced text 340 | 341 | 342 | referenced text 343 | 344 | 345 | referenced text 346 | 347 | 348 | referenced text 349 | 350 | 351 | referenced text 352 | 353 | 354 | referenced text 355 | 356 | 357 | referenced text 358 | 359 | 360 | referenced text 361 | 362 | 363 | referenced text 364 | 365 | 366 | referenced text 367 | 368 | 369 | referenced text 370 | 371 | 372 | referenced text 373 | 374 | 375 | referenced text 376 | 377 | 378 | referenced text 379 | 380 | 381 | referenced text 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.05.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | @@@ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.06.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | @@@ 9 | 10 | 11 | 14 | 15 | 16 | 17 | @@@ 18 | 19 | {"force": true} 20 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.07.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | @@@ 9 | 10 | 11 | 14 | 15 | 16 | 17 | @@@ 18 | 19 | {"force": true} 20 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.08.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | @@@ 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | @@@ 18 | 19 | {"preserve": ["circle", "rect"]} 20 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.09.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | @@@ 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | @@@ 22 | 23 | {"force": true, "preserve": ["circle", "rect"]} 24 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.10.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | @@@ 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | @@@ 34 | 35 | {"force": true, "preserve": ["figure"]} 36 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.11.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | @@@ 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.12.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | @@@ 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | @@@ 18 | 19 | {"preservePrefixes": ["xyz"]} 20 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.13.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | @@@ 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | @@@ 22 | 23 | {"force": true, "preservePrefixes": ["pre1_", "pre2_"]} 24 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.14.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | @@@ 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | @@@ 34 | 35 | {"force": true, "preservePrefixes": ["pre1_"]} 36 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.15.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | @@@ 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | @@@ 18 | 19 | {"preserve": ["circle"], "preservePrefixes": ["suffix", "rect"]} 20 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.16.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | @@@ 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | @@@ 22 | 23 | {"preserve": ["a"]} 24 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.17.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | @@@ 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | @@@ 22 | 23 | {"preservePrefixes": ["a"]} 24 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.18.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | @@@ 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | @@@ 22 | 23 | {"preservePrefixes": ["a"]} 24 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.19.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | @@@ 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.20.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | @@@ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | @@@ 40 | 41 | {"remove": false} 42 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupIds.21.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | @@@ 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupNumericValues.01.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @@@ 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/cleanupNumericValues.02.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @@@ 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/collapseGroups.01.svg: -------------------------------------------------------------------------------- 1 | Collapse groups without attributes 2 | 3 | === 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | @@@ 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/collapseGroups.02.svg: -------------------------------------------------------------------------------- 1 | Inherit attributes to single child 2 | 3 | === 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | @@@ 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/collapseGroups.06.svg: -------------------------------------------------------------------------------- 1 | Remove inheritable overriden groups attributes 2 | 3 | === 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | @@@ 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/collapseGroups.07.svg: -------------------------------------------------------------------------------- 1 | Remove equal overriden groups attributes 2 | 3 | === 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | @@@ 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/collapseGroups.08.svg: -------------------------------------------------------------------------------- 1 | Combine own child transform and inherited 2 | 3 | === 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | @@@ 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/collapseGroups.09.svg: -------------------------------------------------------------------------------- 1 | Preserve transform when group has clip-path 2 | 3 | === 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | @@@ 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/collapseGroups.11.svg: -------------------------------------------------------------------------------- 1 | Preserve groups when clip-path and mask are used without any other attributes 2 | 3 | === 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | @@@ 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/collapseGroups.12.svg: -------------------------------------------------------------------------------- 1 | Preserve groups with id attribute or animation elements inside 2 | 3 | === 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | @@@ 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/collapseGroups.13.svg: -------------------------------------------------------------------------------- 1 | Preserve groups with classes 2 | 3 | === 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | @@@ 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/collapseGroups.14.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | @@@ 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/collapseGroups.15.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | @@@ 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/collapseGroups.16.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | @@@ 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/convertColors.01.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | @@@ 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/convertColors.02.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @@@ 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/convertColors.03.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @@@ 7 | 8 | 9 | 10 | 11 | 12 | 13 | @@@ 14 | 15 | { "shorthex": false } 16 | -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/convertColors.04.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | @@@ 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | @@@ 24 | 25 | { "currentColor": true } -------------------------------------------------------------------------------- /crates/core/__fixture__/plugins/convertEllipseToCircle.01.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | @@@ 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /crates/core/src/collections.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | // https://www.w3.org/TR/SVG11/intro.html#Definitions 4 | pub fn get_elems_groups() -> HashMap<&'static str, Vec<&'static str>> { 5 | HashMap::from([ 6 | ("animation", vec![ 7 | "animate", 8 | "animateColor", 9 | "animateMotion", 10 | "animateTransform", 11 | "set", 12 | ]), 13 | ("descriptive", vec!["desc", "metadata", "title"]), 14 | ("shape", vec!["circle", "ellipse", "line", "path", "polygon", "polyline", "rect"]), 15 | ("structural", vec!["defs", "g", "svg", "symbol", "use"]), 16 | ("paintServer", vec![ 17 | "solidColor", 18 | "linearGradient", 19 | "radialGradient", 20 | "meshGradient", 21 | "pattern", 22 | "hatch", 23 | ]), 24 | ("nonRendering", vec![ 25 | "linearGradient", 26 | "radialGradient", 27 | "pattern", 28 | "clipPath", 29 | "mask", 30 | "marker", 31 | "symbol", 32 | "filter", 33 | "solidColor", 34 | ]), 35 | ("container", vec![ 36 | "a", 37 | "defs", 38 | "g", 39 | "marker", 40 | "mask", 41 | "missing-glyph", 42 | "pattern", 43 | "svg", 44 | "switch", 45 | "symbol", 46 | "foreignObject", 47 | ]), 48 | ("textContent", vec![ 49 | "altGlyph", 50 | "altGlyphDef", 51 | "altGlyphItem", 52 | "glyph", 53 | "glyphRef", 54 | "textPath", 55 | "text", 56 | "tref", 57 | "tspan", 58 | ]), 59 | ("textContentChild", vec!["altGlyph", "textPath", "tref", "tspan"]), 60 | ("lightSource", vec![ 61 | "feDiffuseLighting", 62 | "feSpecularLighting", 63 | "feDistantLight", 64 | "fePointLight", 65 | "feSpotLight", 66 | ]), 67 | ("filterPrimitive", vec![ 68 | "feBlend", 69 | "feColorMatrix", 70 | "feComponentTransfer", 71 | "feComposite", 72 | "feConvolveMatrix", 73 | "feDiffuseLighting", 74 | "feDisplacementMap", 75 | "feDropShadow", 76 | "feFlood", 77 | "feFuncA", 78 | "feFuncB", 79 | "feFuncG", 80 | "feFuncR", 81 | "feGaussianBlur", 82 | "feImage", 83 | "feMerge", 84 | "feMergeNode", 85 | "feMorphology", 86 | "feOffset", 87 | "feSpecularLighting", 88 | "feTile", 89 | "feTurbulence", 90 | ]), 91 | ]) 92 | } 93 | 94 | pub fn get_text_elems() -> Vec<&'static str> { 95 | let mut elems_groups = get_elems_groups(); 96 | let text_elems = elems_groups.get_mut("textContent").unwrap(); 97 | text_elems.push("title"); 98 | text_elems.to_vec() 99 | } 100 | 101 | // https://www.w3.org/TR/SVG11/propidx.html 102 | pub fn get_inheritable_attrs() -> Vec<&'static str> { 103 | vec![ 104 | "clip-rule", 105 | "color", 106 | "color-interpolation", 107 | "color-interpolation-filters", 108 | "color-profile", 109 | "color-rendering", 110 | "cursor", 111 | "direction", 112 | "dominant-baseline", 113 | "fill", 114 | "fill-opacity", 115 | "fill-rule", 116 | "font", 117 | "font-family", 118 | "font-size", 119 | "font-size-adjust", 120 | "font-stretch", 121 | "font-style", 122 | "font-variant", 123 | "font-weight", 124 | "glyph-orientation-horizontal", 125 | "glyph-orientation-vertical", 126 | "image-rendering", 127 | "letter-spacing", 128 | "marker", 129 | "marker-end", 130 | "marker-mid", 131 | "marker-start", 132 | "paint-order", 133 | "pointer-events", 134 | "shape-rendering", 135 | "stroke", 136 | "stroke-dasharray", 137 | "stroke-dashoffset", 138 | "stroke-linecap", 139 | "stroke-linejoin", 140 | "stroke-miterlimit", 141 | "stroke-opacity", 142 | "stroke-width", 143 | "text-anchor", 144 | "text-rendering", 145 | "transform", 146 | "visibility", 147 | "word-spacing", 148 | "writing-mode", 149 | ] 150 | } 151 | 152 | // https://www.w3.org/TR/SVG11/linking.html#processingIRI 153 | pub fn get_references_props() -> Vec<&'static str> { 154 | vec![ 155 | "clip-path", 156 | "color-profile", 157 | "fill", 158 | "filter", 159 | "marker-start", 160 | "marker-mid", 161 | "marker-end", 162 | "mask", 163 | "stroke", 164 | "style", 165 | ] 166 | } 167 | 168 | pub fn get_colors_names() -> HashMap<&'static str, &'static str> { 169 | HashMap::from([ 170 | ("aliceblue", "#f0f8ff"), 171 | ("antiquewhite", "#faebd7"), 172 | ("aqua", "#0ff"), 173 | ("aquamarine", "#7fffd4"), 174 | ("azure", "#f0ffff"), 175 | ("beige", "#f5f5dc"), 176 | ("bisque", "#ffe4c4"), 177 | ("black", "#000"), 178 | ("blanchedalmond", "#ffebcd"), 179 | ("blue", "#00f"), 180 | ("blueviolet", "#8a2be2"), 181 | ("brown", "#a52a2a"), 182 | ("burlywood", "#deb887"), 183 | ("cadetblue", "#5f9ea0"), 184 | ("chartreuse", "#7fff00"), 185 | ("chocolate", "#d2691e"), 186 | ("coral", "#ff7f50"), 187 | ("cornflowerblue", "#6495ed"), 188 | ("cornsilk", "#fff8dc"), 189 | ("crimson", "#dc143c"), 190 | ("cyan", "#0ff"), 191 | ("darkblue", "#00008b"), 192 | ("darkcyan", "#008b8b"), 193 | ("darkgoldenrod", "#b8860b"), 194 | ("darkgray", "#a9a9a9"), 195 | ("darkgreen", "#006400"), 196 | ("darkgrey", "#a9a9a9"), 197 | ("darkkhaki", "#bdb76b"), 198 | ("darkmagenta", "#8b008b"), 199 | ("darkolivegreen", "#556b2f"), 200 | ("darkorange", "#ff8c00"), 201 | ("darkorchid", "#9932cc"), 202 | ("darkred", "#8b0000"), 203 | ("darksalmon", "#e9967a"), 204 | ("darkseagreen", "#8fbc8f"), 205 | ("darkslateblue", "#483d8b"), 206 | ("darkslategray", "#2f4f4f"), 207 | ("darkslategrey", "#2f4f4f"), 208 | ("darkturquoise", "#00ced1"), 209 | ("darkviolet", "#9400d3"), 210 | ("deeppink", "#ff1493"), 211 | ("deepskyblue", "#00bfff"), 212 | ("dimgray", "#696969"), 213 | ("dimgrey", "#696969"), 214 | ("dodgerblue", "#1e90ff"), 215 | ("firebrick", "#b22222"), 216 | ("floralwhite", "#fffaf0"), 217 | ("forestgreen", "#228b22"), 218 | ("fuchsia", "#f0f"), 219 | ("gainsboro", "#dcdcdc"), 220 | ("ghostwhite", "#f8f8ff"), 221 | ("gold", "#ffd700"), 222 | ("goldenrod", "#daa520"), 223 | ("gray", "#808080"), 224 | ("green", "#008000"), 225 | ("greenyellow", "#adff2f"), 226 | ("grey", "#808080"), 227 | ("honeydew", "#f0fff0"), 228 | ("hotpink", "#ff69b4"), 229 | ("indianred", "#cd5c5c"), 230 | ("indigo", "#4b0082"), 231 | ("ivory", "#fffff0"), 232 | ("khaki", "#f0e68c"), 233 | ("lavender", "#e6e6fa"), 234 | ("lavenderblush", "#fff0f5"), 235 | ("lawngreen", "#7cfc00"), 236 | ("lemonchiffon", "#fffacd"), 237 | ("lightblue", "#add8e6"), 238 | ("lightcoral", "#f08080"), 239 | ("lightcyan", "#e0ffff"), 240 | ("lightgoldenrodyellow", "#fafad2"), 241 | ("lightgray", "#d3d3d3"), 242 | ("lightgreen", "#90ee90"), 243 | ("lightgrey", "#d3d3d3"), 244 | ("lightpink", "#ffb6c1"), 245 | ("lightsalmon", "#ffa07a"), 246 | ("lightseagreen", "#20b2aa"), 247 | ("lightskyblue", "#87cefa"), 248 | ("lightslategray", "#789"), 249 | ("lightslategrey", "#789"), 250 | ("lightsteelblue", "#b0c4de"), 251 | ("lightyellow", "#ffffe0"), 252 | ("lime", "#0f0"), 253 | ("limegreen", "#32cd32"), 254 | ("linen", "#faf0e6"), 255 | ("magenta", "#f0f"), 256 | ("maroon", "#800000"), 257 | ("mediumaquamarine", "#66cdaa"), 258 | ("mediumblue", "#0000cd"), 259 | ("mediumorchid", "#ba55d3"), 260 | ("mediumpurple", "#9370db"), 261 | ("mediumseagreen", "#3cb371"), 262 | ("mediumslateblue", "#7b68ee"), 263 | ("mediumspringgreen", "#00fa9a"), 264 | ("mediumturquoise", "#48d1cc"), 265 | ("mediumvioletred", "#c71585"), 266 | ("midnightblue", "#191970"), 267 | ("mintcream", "#f5fffa"), 268 | ("mistyrose", "#ffe4e1"), 269 | ("moccasin", "#ffe4b5"), 270 | ("navajowhite", "#ffdead"), 271 | ("navy", "#000080"), 272 | ("oldlace", "#fdf5e6"), 273 | ("olive", "#808000"), 274 | ("olivedrab", "#6b8e23"), 275 | ("orange", "#ffa500"), 276 | ("orangered", "#ff4500"), 277 | ("orchid", "#da70d6"), 278 | ("palegoldenrod", "#eee8aa"), 279 | ("palegreen", "#98fb98"), 280 | ("paleturquoise", "#afeeee"), 281 | ("palevioletred", "#db7093"), 282 | ("papayawhip", "#ffefd5"), 283 | ("peachpuff", "#ffdab9"), 284 | ("peru", "#cd853f"), 285 | ("pink", "#ffc0cb"), 286 | ("plum", "#dda0dd"), 287 | ("powderblue", "#b0e0e6"), 288 | ("purple", "#800080"), 289 | ("rebeccapurple", "#639"), 290 | ("red", "#f00"), 291 | ("rosybrown", "#bc8f8f"), 292 | ("royalblue", "#4169e1"), 293 | ("saddlebrown", "#8b4513"), 294 | ("salmon", "#fa8072"), 295 | ("sandybrown", "#f4a460"), 296 | ("seagreen", "#2e8b57"), 297 | ("seashell", "#fff5ee"), 298 | ("sienna", "#a0522d"), 299 | ("silver", "#c0c0c0"), 300 | ("skyblue", "#87ceeb"), 301 | ("slateblue", "#6a5acd"), 302 | ("slategray", "#708090"), 303 | ("slategrey", "#708090"), 304 | ("snow", "#fffafa"), 305 | ("springgreen", "#00ff7f"), 306 | ("steelblue", "#4682b4"), 307 | ("tan", "#d2b48c"), 308 | ("teal", "#008080"), 309 | ("thistle", "#d8bfd8"), 310 | ("tomato", "#ff6347"), 311 | ("turquoise", "#40e0d0"), 312 | ("violet", "#ee82ee"), 313 | ("wheat", "#f5deb3"), 314 | ("white", "#fff"), 315 | ("whitesmoke", "#f5f5f5"), 316 | ("yellow", "#ff0"), 317 | ("yellowgreen", "#9acd32"), 318 | ]) 319 | } 320 | 321 | pub fn get_colors_short_names() -> HashMap<&'static str, &'static str> { 322 | HashMap::from([ 323 | ("#f0ffff", "azure"), 324 | ("#f5f5dc", "beige"), 325 | ("#ffe4c4", "bisque"), 326 | ("#a52a2a", "brown"), 327 | ("#ff7f50", "coral"), 328 | ("#ffd700", "gold"), 329 | ("#808080", "gray"), 330 | ("#008000", "green"), 331 | ("#4b0082", "indigo"), 332 | ("#fffff0", "ivory"), 333 | ("#f0e68c", "khaki"), 334 | ("#faf0e6", "linen"), 335 | ("#800000", "maroon"), 336 | ("#000080", "navy"), 337 | ("#808000", "olive"), 338 | ("#ffa500", "orange"), 339 | ("#da70d6", "orchid"), 340 | ("#cd853f", "peru"), 341 | ("#ffc0cb", "pink"), 342 | ("#dda0dd", "plum"), 343 | ("#800080", "purple"), 344 | ("#f00", "red"), 345 | ("#ff0000", "red"), 346 | ("#fa8072", "salmon"), 347 | ("#a0522d", "sienna"), 348 | ("#c0c0c0", "silver"), 349 | ("#fffafa", "snow"), 350 | ("#d2b48c", "tan"), 351 | ("#008080", "teal"), 352 | ("#ff6347", "tomato"), 353 | ("#ee82ee", "violet"), 354 | ("#f5deb3", "wheat"), 355 | ]) 356 | } 357 | 358 | // https://www.w3.org/TR/SVG11/single-page.html#types-DataTypeColor 359 | pub fn get_colors_props() -> Vec<&'static str> { 360 | vec![ 361 | "color", 362 | "fill", 363 | "stroke", 364 | "stop-color", 365 | "flood-color", 366 | "lighting-color", 367 | ] 368 | } 369 | -------------------------------------------------------------------------------- /crates/core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::all)] 2 | 3 | #[cfg(feature = "node")] 4 | #[macro_use] 5 | extern crate napi_derive; 6 | 7 | mod collections; 8 | mod parser; 9 | mod plugins; 10 | mod stringifier; 11 | 12 | #[cfg(test)] 13 | mod testing; 14 | 15 | use stringifier::{stringify_svg, StringifyOptions}; 16 | 17 | #[cfg(feature = "node")] 18 | #[napi(object)] 19 | pub struct Output { 20 | pub data: String, 21 | } 22 | 23 | #[cfg(not(feature = "node"))] 24 | pub struct Output { 25 | pub data: String, 26 | } 27 | 28 | /// The core of SVGO 29 | pub fn optimize(input: String) -> Output { 30 | let mut doc = parser::parse_svg(input).unwrap(); 31 | 32 | plugins::cleanup_attrs::apply(&mut doc); 33 | plugins::cleanup_enable_background::apply(&mut doc); 34 | plugins::cleanup_ids::apply(&mut doc, &Default::default()); 35 | plugins::cleanup_numeric_values::apply(&mut doc, &Default::default()); 36 | 37 | let data = stringify_svg(&doc, StringifyOptions { 38 | pretty: true, 39 | ..Default::default() 40 | }); 41 | Output { 42 | data, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/core/src/parser.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use regex::Regex; 4 | use swc_xml_ast::*; 5 | use swc_xml_visit::{VisitMut, VisitMutWith}; 6 | use swc_xml_parser::{parse_file_as_document, parser, error::Error}; 7 | use swc_core::common::{SourceMap, FileName}; 8 | 9 | use crate::collections::get_text_elems; 10 | 11 | struct Visitor { 12 | text_elems: Vec<&'static str>, 13 | } 14 | 15 | impl VisitMut for Visitor { 16 | fn visit_mut_comment(&mut self, n: &mut Comment) { 17 | n.data = n.data.to_string().trim().into(); 18 | } 19 | 20 | fn visit_mut_element(&mut self, n: &mut Element) { 21 | let mut children: Vec = vec![]; 22 | n.children.iter_mut().for_each(|child| { 23 | if let Child::Text(text) = child { 24 | if self.text_elems.contains(&n.tag_name.to_string().as_str()) { 25 | children.push(child.clone()) 26 | } else { 27 | let re = Regex::new(r"\S").unwrap(); 28 | if re.is_match(&text.data.to_string()) { 29 | text.data = text.data.to_string().trim().into(); 30 | children.push(child.clone()) 31 | } 32 | } 33 | } else { 34 | children.push(child.clone()) 35 | } 36 | }); 37 | n.children = children; 38 | 39 | n.visit_mut_children_with(self) 40 | } 41 | } 42 | 43 | impl Visitor { 44 | fn new() -> Self { 45 | Self { 46 | text_elems: get_text_elems(), 47 | } 48 | } 49 | } 50 | 51 | pub fn parse_svg(input: String) -> Result { 52 | let cm = Arc::::default(); 53 | let fm = cm.new_source_file(FileName::Anon, input); 54 | 55 | let mut errors = vec![]; 56 | let mut r = parse_file_as_document( 57 | &fm, 58 | parser::ParserConfig::default(), 59 | &mut errors 60 | ); 61 | 62 | match &mut r { 63 | Ok(doc) => { 64 | let mut v = Visitor::new(); 65 | doc.visit_mut_with(&mut v); 66 | r 67 | }, 68 | Err(_) => r 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /crates/core/src/plugins/cleanup_attrs.rs: -------------------------------------------------------------------------------- 1 | // cleanups attributes from newlines, trailing and repeating spaces 2 | 3 | use swc_xml_ast::*; 4 | use swc_xml_visit::{VisitMut, VisitMutWith}; 5 | use regex::{Regex, Captures}; 6 | 7 | struct Visitor { 8 | newlines: bool, 9 | trim: bool, 10 | spaces: bool, 11 | } 12 | 13 | impl Default for Visitor { 14 | fn default() -> Self { 15 | Self { 16 | newlines: true, 17 | trim: true, 18 | spaces: true, 19 | } 20 | } 21 | } 22 | 23 | impl VisitMut for Visitor { 24 | fn visit_mut_element(&mut self, n: &mut Element) { 25 | n.visit_mut_children_with(self); 26 | 27 | for attr in n.attributes.iter_mut() { 28 | if attr.value.is_none() { 29 | break; 30 | } 31 | 32 | let mut value = attr.value.clone().unwrap().to_string(); 33 | 34 | if self.newlines { 35 | // new line which requires a space instead of themselve 36 | let reg_newlines_need_space = Regex::new(r#"(\S)\r?\n(\S)"#).unwrap(); 37 | value = reg_newlines_need_space.replace_all(&value, |caps: &Captures| format!("{} {}", &caps[1], &caps[2])).to_string(); 38 | 39 | // simple new line 40 | let reg_new_lines = Regex::new(r#"\r?\n"#).unwrap(); 41 | value = reg_new_lines.replace_all(&value, |_: &Captures| "").to_string() 42 | } 43 | 44 | if self.trim { 45 | value = value.trim().to_string(); 46 | } 47 | 48 | if self.spaces { 49 | let reg_spaces = Regex::new(r#"\s{2,}"#).unwrap(); 50 | value = reg_spaces.replace_all(&value, |_: &Captures| " ").to_string() 51 | } 52 | 53 | attr.value = Some(value.into()); 54 | } 55 | } 56 | } 57 | 58 | pub fn apply(doc: &mut Document) { 59 | let mut v: Visitor = Default::default(); 60 | doc.visit_mut_with(&mut v); 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | #[cfg(test)] 66 | use pretty_assertions::assert_eq; 67 | 68 | use crate::parser::parse_svg; 69 | use crate::stringifier::{stringify_svg, StringifyOptions}; 70 | use super::*; 71 | 72 | fn code_test(input: String, expected: String) { 73 | let mut doc = parse_svg(input).unwrap(); 74 | apply(&mut doc); 75 | let result = stringify_svg(&doc, StringifyOptions { 76 | pretty: true, 77 | ..Default::default() 78 | }); 79 | assert_eq!(result.trim_end(), expected); 80 | } 81 | 82 | #[test] 83 | fn test_1() { 84 | code_test( 85 | r#" 88 | test 89 | "#.to_string(), 90 | r#" 91 | test 92 | "#.to_string(), 93 | ); 94 | } 95 | 96 | #[test] 97 | fn test_2() { 98 | code_test( 99 | r#" 101 | test & <& > ' " & 102 | "#.to_string(), 103 | r#" 104 | test & <& > ' " & 105 | "#.to_string(), 106 | ); 107 | } 108 | 109 | #[test] 110 | fn test_3() { 111 | code_test( 112 | r#" 115 | 117 | test 118 | 119 | "#.to_string(), 120 | r#" 121 | 122 | test 123 | 124 | "#.to_string(), 125 | ); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /crates/core/src/plugins/cleanup_enable_background.rs: -------------------------------------------------------------------------------- 1 | // remove or cleanup enable-background attribute when possible 2 | // 3 | // @see https://www.w3.org/TR/SVG11/filters.html#EnableBackgroundProperty 4 | // 5 | // @example 6 | // 7 | // ⬇ 8 | // 9 | 10 | use swc_xml_ast::*; 11 | use swc_xml_visit::{VisitMut, Visit, VisitWith, VisitMutWith}; 12 | use regex::Regex; 13 | 14 | struct Visitor { 15 | has_filter: bool, 16 | } 17 | 18 | impl Visitor { 19 | pub fn new(has_filter: bool) -> Visitor { 20 | Self { 21 | has_filter, 22 | } 23 | } 24 | } 25 | 26 | impl VisitMut for Visitor { 27 | fn visit_mut_element(&mut self, n: &mut Element) { 28 | n.visit_mut_children_with(self); 29 | 30 | let enable_background_index = n.attributes.iter().position(|attr| attr.name.to_string() == "enable-background"); 31 | if enable_background_index.is_none() { 32 | return; 33 | } 34 | 35 | let enable_background_index = enable_background_index.unwrap(); 36 | 37 | if self.has_filter { 38 | let tag_name = n.tag_name.to_string(); 39 | 40 | let height_index = n.attributes.iter().position(|attr| attr.name.to_string() == "height" && attr.value.is_some()); 41 | let width_index = n.attributes.iter().position(|attr| attr.name.to_string() == "width" && attr.value.is_some()); 42 | 43 | if (tag_name == "svg" || tag_name == "mask" || tag_name == "pattern") && height_index.is_some() && width_index.is_some() { 44 | let value = match n.attributes[enable_background_index].value { 45 | Some(ref value) => value.to_string(), 46 | None => "".to_string(), 47 | }; 48 | let height = n.attributes[height_index.unwrap()].value.clone().unwrap().to_string(); 49 | let width = n.attributes[width_index.unwrap()].value.clone().unwrap().to_string(); 50 | 51 | let reg_enable_background = Regex::new(r#"^new\s0\s0\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)$"#).unwrap(); 52 | let captures = reg_enable_background.captures(&value); 53 | 54 | if captures.is_some() { 55 | let captures = captures.unwrap(); 56 | 57 | if captures[1] == width && captures[3] == height { 58 | if tag_name == "svg" { 59 | n.attributes.remove(enable_background_index); 60 | } else { 61 | n.attributes[enable_background_index].value = Some("new".into()); 62 | } 63 | } 64 | } 65 | } 66 | } else { 67 | // we don't need 'enable-background' if we have no filters 68 | n.attributes.remove(enable_background_index); 69 | } 70 | } 71 | } 72 | 73 | pub struct FilterVisitor { 74 | has_filter: bool, 75 | } 76 | 77 | impl Default for FilterVisitor { 78 | fn default() -> Self { 79 | Self { 80 | has_filter: false, 81 | } 82 | } 83 | } 84 | 85 | impl Visit for FilterVisitor { 86 | fn visit_element(&mut self, n: &Element) { 87 | if n.tag_name.to_string() == "filter" { 88 | self.has_filter = true 89 | } else { 90 | n.visit_children_with(self); 91 | } 92 | } 93 | } 94 | 95 | pub fn apply(doc: &mut Document) { 96 | let mut filter_visitor: FilterVisitor = Default::default(); 97 | doc.visit_with(&mut filter_visitor); 98 | 99 | let mut v = Visitor::new(filter_visitor.has_filter); 100 | doc.visit_mut_with(&mut v); 101 | } 102 | 103 | #[cfg(test)] 104 | mod tests { 105 | #[cfg(test)] 106 | use pretty_assertions::assert_eq; 107 | 108 | use crate::parser::parse_svg; 109 | use crate::stringifier::{stringify_svg, StringifyOptions}; 110 | use super::*; 111 | 112 | fn code_test(input: String, expected: String) { 113 | let mut doc = parse_svg(input).unwrap(); 114 | apply(&mut doc); 115 | let result = stringify_svg(&doc, StringifyOptions { 116 | pretty: true, 117 | ..Default::default() 118 | }); 119 | assert_eq!(result.trim_end(), expected); 120 | } 121 | 122 | #[test] 123 | fn test_1() { 124 | code_test( 125 | r#" 126 | 127 | 128 | 129 | 130 | 131 | test 132 | "#.to_string(), 133 | r#" 134 | 135 | 136 | 137 | 138 | 139 | test 140 | "#.to_string(), 141 | ); 142 | } 143 | 144 | #[test] 145 | fn test_2() { 146 | code_test( 147 | r#" 148 | 149 | 150 | 151 | 152 | 153 | test 154 | "#.to_string(), 155 | r#" 156 | 157 | 158 | 159 | 160 | 161 | test 162 | "#.to_string(), 163 | ); 164 | } 165 | 166 | #[test] 167 | fn test_3() { 168 | code_test( 169 | r#" 170 | 171 | 172 | 173 | 174 | 175 | 176 | test 177 | 178 | "#.to_string(), 179 | r#" 180 | 181 | 182 | 183 | 184 | 185 | 186 | test 187 | 188 | "#.to_string(), 189 | ); 190 | } 191 | 192 | #[test] 193 | fn test_4() { 194 | code_test( 195 | r#" 196 | 197 | test 198 | 199 | "#.to_string(), 200 | r#" 201 | 202 | test 203 | 204 | "#.to_string(), 205 | ); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /crates/core/src/plugins/cleanup_ids.rs: -------------------------------------------------------------------------------- 1 | // Remove unused and minify used IDs 2 | // (only if there are no any