├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ ├── ci.yml
│ └── update-copyright-years-in-license-file.yml
├── .gitignore
├── .husky
└── pre-commit
├── .npmignore
├── .npmrc
├── .prettierignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── dist
├── es2015
│ └── index.mjs
└── index.js
├── eslint.config.mjs
├── examples
├── common.cjs
├── esm.mjs
└── typescript.ts
├── jest-puppeteer.config.mjs
├── jest.config.ts
├── lib
├── cases.ts
└── index.template.ts
├── package-lock.json
├── package.json
├── scripts
├── benchmark.ts
├── build
│ ├── index.ts
│ └── patch-esm-dist.ts
├── get-simple-icons-segments-stats.ts
├── show-simple-icons-segments-stats.ts
└── simple-icons-benchmark.cts
├── src
├── cli.cjs
├── index.ts
├── types.d.mts
└── types.d.ts
├── svg-path-bbox.svg
├── tests
├── bbox.test.ts
├── browser.test.ts
├── browser
│ └── index.html
├── cases
│ └── bbox.ts
├── changelog.test.ts
├── cli.test.ts
├── examples.test.ts
├── optimization.test.ts
└── utils
│ └── syscall.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | # Unix-style newlines with a newline ending every file
4 | [*]
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 2
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 |
12 | # Markdown settings
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | package-lock.json -diff
3 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | tags:
8 | - v*
9 | pull_request:
10 | workflow_dispatch:
11 |
12 | jobs:
13 | test:
14 | name: Test
15 | runs-on: ubuntu-latest
16 | strategy:
17 | matrix:
18 | node-version: [18.x, 20.x, 22.x]
19 | steps:
20 | - uses: actions/checkout@v4
21 | - name: Use NodeJS v${{ matrix.node-version }}
22 | uses: actions/setup-node@v4
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 | - name: Install
26 | run: npm ci --ignore-scripts --no-audit --no-fund
27 | - name: Build
28 | run: npm run build
29 | - name: Test
30 | run: |
31 | npx puppeteer browsers install chrome
32 | npm test
33 | - name: Coverage
34 | uses: coverallsapp/github-action@v2
35 | with:
36 | github-token: ${{ secrets.GITHUB_TOKEN }}
37 | file: ./tests/coverage/lcov.info
38 |
39 | lint:
40 | name: Lint
41 | runs-on: ubuntu-latest
42 | steps:
43 | - uses: actions/checkout@v4
44 | - name: Use NodeJS v22
45 | uses: actions/setup-node@v4
46 | with:
47 | node-version: 22.x
48 | - name: Install
49 | run: npm ci --ignore-scripts --no-audit --no-fund
50 | - name: Lint
51 | run: npm run lint
52 |
53 | npm:
54 | if: startsWith(github.ref, 'refs/tags/')
55 | needs:
56 | - test
57 | - lint
58 | name: NPM Package
59 | runs-on: ubuntu-latest
60 | steps:
61 | - name: Checkout
62 | uses: actions/checkout@v4
63 | - name: Use NodeJS v22
64 | uses: actions/setup-node@v4
65 | with:
66 | node-version: 22.x
67 | - name: Install dependencies
68 | run: |
69 | npm ci --ignore-scripts --no-audit --no-fund
70 | npx puppeteer browsers install chrome
71 | - name: Build
72 | run: npm run build
73 | - name: Test
74 | run: npm test
75 | - name: Deploy to NPM
76 | uses: JS-DevTools/npm-publish@v3
77 | with:
78 | token: ${{ secrets.NPM_TOKEN }}
79 |
80 | release:
81 | name: Release
82 | needs: npm
83 | runs-on: ubuntu-latest
84 | steps:
85 | - uses: actions/checkout@v4
86 | - name: Get tag metadata
87 | id: tag
88 | run: |
89 | TAG_TITLE=${GITHUB_REF#refs/*/}
90 | echo "title=$TAG_TITLE" >> $GITHUB_OUTPUT
91 | git -c protocol.version=2 fetch --prune --progress \
92 | --no-recurse-submodules origin \
93 | +refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*
94 | TAG_BODY="$(git tag -l --format='%(contents)' $TAG_TITLE)"
95 | TAG_BODY="${TAG_BODY//'%'/'%25'}"
96 | TAG_BODY="${TAG_BODY//$'\n'/'%0A'}"
97 | TAG_BODY="${TAG_BODY//$'\r'/'%0D'}"
98 | echo "body=$TAG_BODY" >> $GITHUB_OUTPUT
99 | - name: Create Release
100 | uses: softprops/action-gh-release@v2
101 | with:
102 | tag_name: ${{ steps.tag.outputs.title }}
103 | name: ${{ steps.tag.outputs.title }}
104 | body: ${{ steps.tag.outputs.body }}
105 | draft: false
106 | prerelease: false
107 | env:
108 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
109 |
--------------------------------------------------------------------------------
/.github/workflows/update-copyright-years-in-license-file.yml:
--------------------------------------------------------------------------------
1 | name: Update copyright years in license file
2 |
3 | on:
4 | schedule:
5 | - cron: "0 3 2 1 *"
6 | workflow_dispatch:
7 |
8 | jobs:
9 | action-update-license-year:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | with:
14 | fetch-depth: 0
15 | - uses: FantasticFiasco/action-update-license-year@v2
16 | with:
17 | token: ${{ secrets.GITHUB_TOKEN }}
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | __dev__.js
3 | tests/coverage
4 | deps.txt
5 | emsdk
6 | *.wasm
7 | *.tgz
8 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | set -e
2 |
3 | npm run build
4 | npm t
5 | git add -u
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *
2 | !LICENSE
3 | !dist/
4 | !src/
5 | !package.json
6 | !CHANGELOG.md
7 | !README.md
8 | !tsconfig.json
9 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=true
2 | save-exact=true
3 | save-dev=true
4 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | tests/coverage
2 | dist/
3 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## [2.1.0] - 2024-09-04
4 |
5 | - Accept a `SvgPath` interface from [svgpath] as argument. This makes
6 | `svgPathBbox` x2 faster when using `SvgPath` instances.
7 |
8 | [svgpath]: https://www.npmjs.com/package/svgpath
9 |
10 | ## [2.0.0] - 2024-06-06
11 |
12 | - No more default export. Now you have to import `svgPathBbox` function
13 | directly. Use `import { svgPathBbox } from "svg-path-bbox";` instead of
14 | `import svgPathBbox from "svg-path-bbox";`.
15 |
16 | ## [1.2.6] - 2024-03-14
17 |
18 | - Support types without enabling `esModuleInterop` in TypeScript.
19 |
20 | ## [1.2.5] - 2024-03-13
21 |
22 | - Fixed error computing some quadratic Bézier curves cases.
23 |
24 | ## [1.2.4] - 2023-02-09
25 |
26 | - Optimized proccesing of segments based on [Simple Icons] data.
27 |
28 | [Simple Icons]: https://github.com/simple-icons/simple-icons
29 |
30 | ## [1.2.3] - 2022-12-23
31 |
32 | - Fixed CLI not being executed in some versions of Node.js < v16.
33 |
34 | ## [1.2.2] - 2022-05-26
35 |
36 | - Fixed edge case computing cubic Bézier curves bounding boxes.
37 |
38 | ## [1.2.1] - 2022-05-12
39 |
40 | - Fixed error computing cubic Bézier curves bounding boxes.
41 |
42 | ## [1.2.0] - 2022-05-07
43 |
44 | - Use default export for better interoperability.
45 |
46 | ## [1.1.0] - 2022-05-05
47 |
48 | - Add support for Typescript.
49 |
50 | ## [1.0.2] - 2022-01-11
51 |
52 | - Add basic options `--version` and `--help` to CLI.
53 |
54 | ## [1.0.1] - 2021-06-21
55 |
56 | - Fixed error computing limits for cubic Bèzier curves of length 0.
57 |
58 | ## [1.0.0] - 2021-06-03
59 |
60 | - Testing with 100% coverage.
61 | - Make `svgPathBbox` function the default export.
62 |
63 | ## [0.2.0] - 2020-12-22
64 |
65 | - Removed almost all public API functions (only keep `svgPathBbox` function).
66 | - Removed `polf` dependency.
67 | - Optimized quadratic Bézier curves minimum and maximum values computation.
68 | - Optimized cubic Bézier curves minimum and maximum values computation.
69 | - Optimized lineal segments minimum and maximum values computation.
70 |
71 | ## [0.1.5] - 2020-11-26
72 |
73 | - Documentation improved.
74 | - Switch CI to Github Actions.
75 |
76 | ## [0.1.4] - 2020-11-23
77 |
78 | - Document and export `quadraticBezierCurveBbox` function.
79 | - Remove development file from NPM package.
80 | - Update acknowledgments.
81 |
82 | ## [0.1.1] - 2020-11-19
83 |
84 | - Fix error computing bounding boxes for Q, T and some C commands.
85 |
86 | ## [0.0.47] - 2020-07-23
87 |
88 | - Separate point on line functions in another package.
89 | - Replaced svg-path-parser dependency with svgpath to optimize parsing time.
90 | - Removed dregrees to radians converter function.
91 | - Fix error in point on line function.
92 | - Fix errors in utility functions.
93 | - Add tests for utilities and command line client.
94 |
95 | ## [0.0.28] - 2020-05-22
96 |
97 | - Update LICENSE.
98 | - Fix error converting quaratic to Bézier coordinates.
99 | - Add tests for some bounding boxes functions.
100 |
101 | ## [0.0.26] - 2020-05-21
102 |
103 | - Removed `quadraticBezierCurveBbox` function.
104 | - Optimized quadratic Bézier curve bounding box computation.
105 | - Optimized cubic Bézier curve bounding box algorithm.
106 | - Fixed error on V and H commands computing SVG path bbox.
107 |
108 | ## [0.0.20] - 2020-05-17
109 |
110 | - Add function to obtain an array of numbers from SVG path.
111 | - Multiple paths as arguments for command line script.
112 |
113 | ## [0.0.13] - 2020-05-17
114 |
115 | - Add command line interface.
116 | - Add linting.
117 | - Released to NPM.
118 | - Add documentation.
119 | - Add changelog.
120 | - Basic functionalities finished.
121 |
122 | [2.1.0]: https://github.com/mondeja/svg-path-bbox/compare/v2.0.0...v2.1.0
123 | [2.0.0]: https://github.com/mondeja/svg-path-bbox/compare/v1.2.6...v2.0.0
124 | [1.2.6]: https://github.com/mondeja/svg-path-bbox/compare/v1.2.5...v1.2.6
125 | [1.2.5]: https://github.com/mondeja/svg-path-bbox/compare/v1.2.4...v1.2.5
126 | [1.2.4]: https://github.com/mondeja/svg-path-bbox/compare/v1.2.3...v1.2.4
127 | [1.2.3]: https://github.com/mondeja/svg-path-bbox/compare/v1.2.2...v1.2.3
128 | [1.2.2]: https://github.com/mondeja/svg-path-bbox/compare/v1.2.1...v1.2.2
129 | [1.2.1]: https://github.com/mondeja/svg-path-bbox/compare/v1.2.0...v1.2.1
130 | [1.2.0]: https://github.com/mondeja/svg-path-bbox/compare/v1.1.0...v1.2.0
131 | [1.1.0]: https://github.com/mondeja/svg-path-bbox/compare/v1.0.2...v1.1.0
132 | [1.0.2]: https://github.com/mondeja/svg-path-bbox/compare/v1.0.1...v1.0.2
133 | [1.0.1]: https://github.com/mondeja/svg-path-bbox/compare/v1.0.0...v1.0.1
134 | [1.0.0]: https://github.com/mondeja/svg-path-bbox/compare/v0.2.0...v1.0.0
135 | [0.2.0]: https://github.com/mondeja/svg-path-bbox/compare/v0.1.5...v0.2.0
136 | [0.1.5]: https://github.com/mondeja/svg-path-bbox/compare/v0.1.4...v0.1.5
137 | [0.1.4]: https://github.com/mondeja/svg-path-bbox/compare/v0.1.1...v0.1.4
138 | [0.1.1]: https://github.com/mondeja/svg-path-bbox/compare/v0.0.47...v0.1.1
139 | [0.0.47]: https://github.com/mondeja/svg-path-bbox/compare/v0.0.28...v0.0.47
140 | [0.0.28]: https://github.com/mondeja/svg-path-bbox/compare/v0.0.26...v0.0.28
141 | [0.0.26]: https://github.com/mondeja/svg-path-bbox/compare/v0.0.20...v0.0.26
142 | [0.0.20]: https://github.com/mondeja/svg-path-bbox/compare/v0.0.13...v0.0.20
143 | [0.0.13]: https://github.com/mondeja/svg-path-bbox/releases/tag/v0.0.13
144 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2020-2025, Álvaro Mondéjar Rubio
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 📦 svg-path-bbox
2 |
3 | [![NPM version][npm-version-image]][npm-link]
4 | [![License][license-image]][license-link]
5 | [![NodeJS versions][npm-versions-image]][npm-link]
6 |
7 | SVG paths bounding box calculator.
8 |
9 | ## Status
10 |
11 | [![Tests][tests-image]][tests-link]
12 | [![Coverage status][coveralls-image]][coveralls-link]
13 |
14 | ## Install
15 |
16 | ```sh
17 | npm install svg-path-bbox
18 | ```
19 |
20 | ## Documentation
21 |
22 | ### Usage
23 |
24 | ```javascript
25 | > import { svgPathBbox } from "svg-path-bbox";
26 | > svgPathBbox("M5 10l2 3z")
27 | [ 5, 10, 7, 13 ]
28 | > svgPathBbox("M5 10c3 0 3 3 0 3z")
29 | [ 5, 10, 7.25, 13 ]
30 | ```
31 |
32 | Returned bounding box is an array made up like `viewBox` SVG attributes `[x0, y0, x1, y1]` of unrounded values:
33 |
34 |
35 |
36 |
37 |
38 | ### Command line
39 |
40 | ```bash
41 | $ svg-path-bbox "M5 10c3 0 3 3 0 3z"
42 | 5 10 7.25 13
43 |
44 | $ svg-path-bbox "M5 10c3 0 3 3 0 3z" "M2 8m5 5z"
45 | 5 10 7.25 13
46 | 2 8 7 13
47 | ```
48 |
49 | ### Typescript usage
50 |
51 | ```typescript
52 | import { svgPathBbox } from "svg-path-bbox";
53 | import type { BBox } from "svg-path-bbox";
54 |
55 | const cases: [string, BBox][] = [["M0 0H3V6Z", [0, 0, 3, 6]]];
56 | console.log(svgPathBbox(cases[0]));
57 | ```
58 |
59 | ### Reference
60 |
61 | # **svgPathBbox**(d : _string_ | typeof import('svgpath')) ⇒ [minX: _number_, minY: _number_, maxX: _number_, maxY: _number_]
62 |
63 | Computes the bounding box of SVG path following the [SVG 1.1 specification](https://www.w3.org/TR/SVG/paths.html).
64 |
65 | - **d** (_string_ | typeof import('svgpath')) SVG path. Can be a string or a `SvgPath` interface from [svgpath].
66 |
67 | ## Thanks to
68 |
69 | - [simple-icons/simple-icons](https://github.com/simple-icons/simple-icons) for reference dataset.
70 | - [kpym/SVGPathy](https://github.com/kpym/SVGPathy) for reference implementation.
71 | - [icons8/svg-path-bounding-box](https://github.com/icons8/svg-path-bounding-box) because [their bug](https://github.com/icons8/svg-path-bounding-box/issues/3) has been the source of this library.
72 | - [mathandy/svgpathtools](https://github.com/mathandy/svgpathtools/) for reference implementation to compare with.
73 | - [svgpath](https://www.npmjs.com/package/svgpath) as is used internally by svg-path-bbox to parse and transform paths before compute bounding boxes.
74 |
75 | [npm-link]: https://www.npmjs.com/package/svg-path-bbox
76 | [npm-version-image]: https://img.shields.io/npm/v/svg-path-bbox
77 | [tests-image]: https://img.shields.io/github/actions/workflow/status/mondeja/svg-path-bbox/ci.yml?branch=master&logo=github&label=tests
78 | [tests-link]: https://github.com/mondeja/svg-path-bbox/actions?query=workflow%3ATest
79 | [coveralls-image]: https://coveralls.io/repos/github/mondeja/svg-path-bbox/badge.svg?branch=master
80 | [coveralls-link]: https://coveralls.io/github/mondeja/svg-path-bbox?branch=master
81 | [license-image]: https://img.shields.io/npm/l/svg-path-bbox?color=brightgreen
82 | [license-link]: https://github.com/mondeja/svg-path-bbox/blob/master/LICENSE
83 | [npm-versions-image]: https://img.shields.io/node/v/svg-path-bbox
84 | [svgpath]: https://www.npmjs.com/package/svgpath
85 |
--------------------------------------------------------------------------------
/dist/es2015/index.mjs:
--------------------------------------------------------------------------------
1 | "use strict";
2 | // WARNING: This file is autogenerated, edit lib/index.template.ts
3 | import SvgPath from "svgpath";
4 | // Precision for consider cubic polynom as quadratic one
5 | const CBEZIER_MINMAX_EPSILON = 0.00000001;
6 | // https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L89
7 | function minmaxQ(A) {
8 | const min = Math.min(A[0], A[2]), max = Math.max(A[0], A[2]);
9 | if (A[1] >= A[0] ? A[2] >= A[1] : A[2] <= A[1]) {
10 | // if no extremum in ]0,1[
11 | return [min, max];
12 | }
13 | // check if the extremum E is min or max
14 | const E = (A[0] * A[2] - A[1] * A[1]) / (A[0] - 2 * A[1] + A[2]);
15 | return E < min ? [E, max] : [min, E];
16 | }
17 | // https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L127
18 | function minmaxC(A) {
19 | const K = A[0] - 3 * A[1] + 3 * A[2] - A[3];
20 | // if the polynomial is (almost) quadratic and not cubic
21 | if (Math.abs(K) < CBEZIER_MINMAX_EPSILON) {
22 | if (A[0] === A[3] && A[0] === A[1]) {
23 | // no curve, point targeting same location
24 | return [A[0], A[3]];
25 | }
26 | return minmaxQ([
27 | A[0],
28 | -0.5 * A[0] + 1.5 * A[1],
29 | A[0] - 3 * A[1] + 3 * A[2],
30 | ]);
31 | }
32 | // the reduced discriminant of the derivative
33 | const T = -A[0] * A[2] +
34 | A[0] * A[3] -
35 | A[1] * A[2] -
36 | A[1] * A[3] +
37 | A[1] * A[1] +
38 | A[2] * A[2];
39 | // if the polynomial is monotone in [0,1]
40 | if (T <= 0) {
41 | return [Math.min(A[0], A[3]), Math.max(A[0], A[3])];
42 | }
43 | const S = Math.sqrt(T);
44 | // potential extrema
45 | let min = Math.min(A[0], A[3]), max = Math.max(A[0], A[3]);
46 | const L = A[0] - 2 * A[1] + A[2];
47 | // check local extrema
48 | for (let R = (L + S) / K, i = 1; i <= 2; R = (L - S) / K, i++) {
49 | if (R > 0 && R < 1) {
50 | // if the extrema is for R in [0,1]
51 | const Q = A[0] * (1 - R) * (1 - R) * (1 - R) +
52 | A[1] * 3 * (1 - R) * (1 - R) * R +
53 | A[2] * 3 * (1 - R) * R * R +
54 | A[3] * R * R * R;
55 | if (Q < min) {
56 | min = Q;
57 | }
58 | if (Q > max) {
59 | max = Q;
60 | }
61 | }
62 | }
63 | return [min, max];
64 | }
65 | /**
66 | * Compute bounding boxes of SVG paths.
67 | * @param {String | typeof SvgPath} d SVG path for which their bounding box will be computed.
68 | * @returns {BBox}
69 | */
70 | export function svgPathBbox(d) {
71 | const min = [Infinity, Infinity], max = [-Infinity, -Infinity];
72 | SvgPath.from(d)
73 | .abs()
74 | .unarc()
75 | .unshort()
76 | .iterate((seg, _, x, y) => {
77 | switch (seg[0]) {
78 | // The next cases are ordered based on simple-icons data
79 | case "M": {
80 | if (min[0] > seg[1]) {
81 | min[0] = seg[1];
82 | }
83 | if (min[1] > seg[2]) {
84 | min[1] = seg[2];
85 | }
86 | if (max[0] < seg[1]) {
87 | max[0] = seg[1];
88 | }
89 | if (max[1] < seg[2]) {
90 | max[1] = seg[2];
91 | }
92 | break;
93 | }
94 | case "H": {
95 | if (min[0] > seg[1]) {
96 | min[0] = seg[1];
97 | }
98 | if (max[0] < seg[1]) {
99 | max[0] = seg[1];
100 | }
101 | break;
102 | }
103 | case "V": {
104 | if (min[1] > seg[1]) {
105 | min[1] = seg[1];
106 | }
107 | if (max[1] < seg[1]) {
108 | max[1] = seg[1];
109 | }
110 | break;
111 | }
112 | case "L": {
113 | if (min[0] > seg[1]) {
114 | min[0] = seg[1];
115 | }
116 | if (min[1] > seg[2]) {
117 | min[1] = seg[2];
118 | }
119 | if (max[0] < seg[1]) {
120 | max[0] = seg[1];
121 | }
122 | if (max[1] < seg[2]) {
123 | max[1] = seg[2];
124 | }
125 | break;
126 | }
127 | case "C": {
128 | const cxMinMax = minmaxC([x, seg[1], seg[3], seg[5]]);
129 | if (min[0] > cxMinMax[0]) {
130 | min[0] = cxMinMax[0];
131 | }
132 | if (max[0] < cxMinMax[1]) {
133 | max[0] = cxMinMax[1];
134 | }
135 | const cyMinMax = minmaxC([y, seg[2], seg[4], seg[6]]);
136 | if (min[1] > cyMinMax[0]) {
137 | min[1] = cyMinMax[0];
138 | }
139 | if (max[1] < cyMinMax[1]) {
140 | max[1] = cyMinMax[1];
141 | }
142 | break;
143 | }
144 | case "Q": {
145 | const qxMinMax = minmaxQ([x, seg[1], seg[3]]);
146 | if (min[0] > qxMinMax[0]) {
147 | min[0] = qxMinMax[0];
148 | }
149 | if (max[0] < qxMinMax[1]) {
150 | max[0] = qxMinMax[1];
151 | }
152 | const qyMinMax = minmaxQ([y, seg[2], seg[4]]);
153 | if (min[1] > qyMinMax[0]) {
154 | min[1] = qyMinMax[0];
155 | }
156 | if (max[1] < qyMinMax[1]) {
157 | max[1] = qyMinMax[1];
158 | }
159 | break;
160 | }
161 | }
162 | }, true);
163 | return [min[0], min[1], max[0], max[1]];
164 | }
165 |
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | exports.svgPathBbox = svgPathBbox;
4 | // WARNING: This file is autogenerated, edit lib/index.template.ts
5 | var SvgPath = require("svgpath");
6 | // Precision for consider cubic polynom as quadratic one
7 | var CBEZIER_MINMAX_EPSILON = 0.00000001;
8 | // https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L89
9 | function minmaxQ(A) {
10 | var min = Math.min(A[0], A[2]), max = Math.max(A[0], A[2]);
11 | if (A[1] >= A[0] ? A[2] >= A[1] : A[2] <= A[1]) {
12 | // if no extremum in ]0,1[
13 | return [min, max];
14 | }
15 | // check if the extremum E is min or max
16 | var E = (A[0] * A[2] - A[1] * A[1]) / (A[0] - 2 * A[1] + A[2]);
17 | return E < min ? [E, max] : [min, E];
18 | }
19 | // https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L127
20 | function minmaxC(A) {
21 | var K = A[0] - 3 * A[1] + 3 * A[2] - A[3];
22 | // if the polynomial is (almost) quadratic and not cubic
23 | if (Math.abs(K) < CBEZIER_MINMAX_EPSILON) {
24 | if (A[0] === A[3] && A[0] === A[1]) {
25 | // no curve, point targeting same location
26 | return [A[0], A[3]];
27 | }
28 | return minmaxQ([
29 | A[0],
30 | -0.5 * A[0] + 1.5 * A[1],
31 | A[0] - 3 * A[1] + 3 * A[2],
32 | ]);
33 | }
34 | // the reduced discriminant of the derivative
35 | var T = -A[0] * A[2] +
36 | A[0] * A[3] -
37 | A[1] * A[2] -
38 | A[1] * A[3] +
39 | A[1] * A[1] +
40 | A[2] * A[2];
41 | // if the polynomial is monotone in [0,1]
42 | if (T <= 0) {
43 | return [Math.min(A[0], A[3]), Math.max(A[0], A[3])];
44 | }
45 | var S = Math.sqrt(T);
46 | // potential extrema
47 | var min = Math.min(A[0], A[3]), max = Math.max(A[0], A[3]);
48 | var L = A[0] - 2 * A[1] + A[2];
49 | // check local extrema
50 | for (var R = (L + S) / K, i = 1; i <= 2; R = (L - S) / K, i++) {
51 | if (R > 0 && R < 1) {
52 | // if the extrema is for R in [0,1]
53 | var Q = A[0] * (1 - R) * (1 - R) * (1 - R) +
54 | A[1] * 3 * (1 - R) * (1 - R) * R +
55 | A[2] * 3 * (1 - R) * R * R +
56 | A[3] * R * R * R;
57 | if (Q < min) {
58 | min = Q;
59 | }
60 | if (Q > max) {
61 | max = Q;
62 | }
63 | }
64 | }
65 | return [min, max];
66 | }
67 | /**
68 | * Compute bounding boxes of SVG paths.
69 | * @param {String | typeof SvgPath} d SVG path for which their bounding box will be computed.
70 | * @returns {BBox}
71 | */
72 | function svgPathBbox(d) {
73 | var min = [Infinity, Infinity], max = [-Infinity, -Infinity];
74 | SvgPath.from(d)
75 | .abs()
76 | .unarc()
77 | .unshort()
78 | .iterate(function (seg, _, x, y) {
79 | switch (seg[0]) {
80 | // The next cases are ordered based on simple-icons data
81 | case "M": {
82 | if (min[0] > seg[1]) {
83 | min[0] = seg[1];
84 | }
85 | if (min[1] > seg[2]) {
86 | min[1] = seg[2];
87 | }
88 | if (max[0] < seg[1]) {
89 | max[0] = seg[1];
90 | }
91 | if (max[1] < seg[2]) {
92 | max[1] = seg[2];
93 | }
94 | break;
95 | }
96 | case "H": {
97 | if (min[0] > seg[1]) {
98 | min[0] = seg[1];
99 | }
100 | if (max[0] < seg[1]) {
101 | max[0] = seg[1];
102 | }
103 | break;
104 | }
105 | case "V": {
106 | if (min[1] > seg[1]) {
107 | min[1] = seg[1];
108 | }
109 | if (max[1] < seg[1]) {
110 | max[1] = seg[1];
111 | }
112 | break;
113 | }
114 | case "L": {
115 | if (min[0] > seg[1]) {
116 | min[0] = seg[1];
117 | }
118 | if (min[1] > seg[2]) {
119 | min[1] = seg[2];
120 | }
121 | if (max[0] < seg[1]) {
122 | max[0] = seg[1];
123 | }
124 | if (max[1] < seg[2]) {
125 | max[1] = seg[2];
126 | }
127 | break;
128 | }
129 | case "C": {
130 | var cxMinMax = minmaxC([x, seg[1], seg[3], seg[5]]);
131 | if (min[0] > cxMinMax[0]) {
132 | min[0] = cxMinMax[0];
133 | }
134 | if (max[0] < cxMinMax[1]) {
135 | max[0] = cxMinMax[1];
136 | }
137 | var cyMinMax = minmaxC([y, seg[2], seg[4], seg[6]]);
138 | if (min[1] > cyMinMax[0]) {
139 | min[1] = cyMinMax[0];
140 | }
141 | if (max[1] < cyMinMax[1]) {
142 | max[1] = cyMinMax[1];
143 | }
144 | break;
145 | }
146 | case "Q": {
147 | var qxMinMax = minmaxQ([x, seg[1], seg[3]]);
148 | if (min[0] > qxMinMax[0]) {
149 | min[0] = qxMinMax[0];
150 | }
151 | if (max[0] < qxMinMax[1]) {
152 | max[0] = qxMinMax[1];
153 | }
154 | var qyMinMax = minmaxQ([y, seg[2], seg[4]]);
155 | if (min[1] > qyMinMax[0]) {
156 | min[1] = qyMinMax[0];
157 | }
158 | if (max[1] < qyMinMax[1]) {
159 | max[1] = qyMinMax[1];
160 | }
161 | break;
162 | }
163 | }
164 | }, true);
165 | return [min[0], min[1], max[0], max[1]];
166 | }
167 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { fileURLToPath } from "node:url";
3 | import { FlatCompat } from "@eslint/eslintrc";
4 | import js from "@eslint/js";
5 | import tsParser from "@typescript-eslint/parser";
6 | import typescriptEslint from "@typescript-eslint/eslint-plugin";
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: path.dirname(fileURLToPath(import.meta.url)),
10 | recommendedConfig: js.configs.recommended,
11 | allConfig: js.configs.all,
12 | });
13 |
14 | export default [
15 | ...compat.extends(
16 | "eslint:recommended",
17 | "plugin:@typescript-eslint/recommended"
18 | ),
19 | {
20 | plugins: {
21 | "@typescript-eslint": typescriptEslint,
22 | },
23 |
24 | languageOptions: {
25 | globals: {},
26 | parser: tsParser,
27 | ecmaVersion: 2020,
28 | sourceType: "module",
29 |
30 | parserOptions: {
31 | requireConfigFile: false,
32 | },
33 | },
34 |
35 | rules: {
36 | "no-console": [
37 | "error",
38 | {
39 | allow: ["time", "timeEnd"],
40 | },
41 | ],
42 | },
43 | },
44 | ];
45 |
--------------------------------------------------------------------------------
/examples/common.cjs:
--------------------------------------------------------------------------------
1 | const util = require("util")
2 | const { svgPathBbox } = require("../dist/index.js");
3 |
4 | const bbox = svgPathBbox("M0 0H3V6Z");
5 | process.stdout.write(`${util.inspect(bbox)}\n`)
6 |
--------------------------------------------------------------------------------
/examples/esm.mjs:
--------------------------------------------------------------------------------
1 | import { inspect } from "node:util";
2 | import process from "node:process";
3 | import { svgPathBbox } from "../dist/es2015/index.mjs";
4 |
5 | const bbox = svgPathBbox("M0 0H3V6Z");
6 | process.stdout.write(`${inspect(bbox)}\n`)
7 |
--------------------------------------------------------------------------------
/examples/typescript.ts:
--------------------------------------------------------------------------------
1 | import { inspect } from "node:util";
2 | import * as process from "node:process";
3 | import { svgPathBbox } from "../src";
4 | import type { BBox } from "../src";
5 |
6 | const cases: [string, BBox][] = [["M0 0H3V6Z", [0, 0, 3, 6]]];
7 |
8 | for (const [path, expectedBbox] of cases) {
9 | const bbox = svgPathBbox(path);
10 | if (JSON.stringify(bbox) !== JSON.stringify(expectedBbox)) {
11 | process.stderr.write(`UNEXPECTED BBOX: ${inspect(bbox)}\n`);
12 | process.exit(1);
13 | } else {
14 | process.stdout.write(`${inspect(bbox)}\n`);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/jest-puppeteer.config.mjs:
--------------------------------------------------------------------------------
1 | import process from 'node:process';
2 |
3 | const PORT = 8080;
4 |
5 | export default {
6 | launch: {
7 | headless: process.env.TEST_HEADLESS !== "false",
8 | args: ['--no-sandbox', '--disable-setuid-sandbox'],
9 | },
10 | server: {
11 | command: `anywhere -s -p ${PORT} -d tests/browser`,
12 | port: PORT,
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | preset: "jest-puppeteer",
3 | transform: { "^.+\\.ts?$": "ts-jest" },
4 | collectCoverage: true,
5 | coverageDirectory: "/tests/coverage",
6 | collectCoverageFrom: ["src/*.ts"],
7 | };
8 |
--------------------------------------------------------------------------------
/lib/cases.ts:
--------------------------------------------------------------------------------
1 | const ML = `{
2 | if (min[0] > seg[1]) {
3 | min[0] = seg[1];
4 | }
5 | if (min[1] > seg[2]) {
6 | min[1] = seg[2];
7 | }
8 | if (max[0] < seg[1]) {
9 | max[0] = seg[1];
10 | }
11 | if (max[1] < seg[2]) {
12 | max[1] = seg[2];
13 | }
14 | break;
15 | }`;
16 |
17 | export default {
18 | M: ML,
19 | L: ML,
20 | H: `{
21 | if (min[0] > seg[1]) {
22 | min[0] = seg[1];
23 | }
24 | if (max[0] < seg[1]) {
25 | max[0] = seg[1];
26 | }
27 | break;
28 | }`,
29 | V: `{
30 | if (min[1] > seg[1]) {
31 | min[1] = seg[1];
32 | }
33 | if (max[1] < seg[1]) {
34 | max[1] = seg[1];
35 | }
36 | break;
37 | }`,
38 | C: `{
39 | const cxMinMax = minmaxC([x, seg[1], seg[3], seg[5]]);
40 | if (min[0] > cxMinMax[0]) {
41 | min[0] = cxMinMax[0];
42 | }
43 | if (max[0] < cxMinMax[1]) {
44 | max[0] = cxMinMax[1];
45 | }
46 |
47 | const cyMinMax = minmaxC([y, seg[2], seg[4], seg[6]]);
48 | if (min[1] > cyMinMax[0]) {
49 | min[1] = cyMinMax[0];
50 | }
51 | if (max[1] < cyMinMax[1]) {
52 | max[1] = cyMinMax[1];
53 | }
54 | break;
55 | }`,
56 | Q: `{
57 | const qxMinMax = minmaxQ([x, seg[1], seg[3]]);
58 | if (min[0] > qxMinMax[0]) {
59 | min[0] = qxMinMax[0];
60 | }
61 | if (max[0] < qxMinMax[1]) {
62 | max[0] = qxMinMax[1];
63 | }
64 |
65 | const qyMinMax = minmaxQ([y, seg[2], seg[4]]);
66 | if (min[1] > qyMinMax[0]) {
67 | min[1] = qyMinMax[0];
68 | }
69 | if (max[1] < qyMinMax[1]) {
70 | max[1] = qyMinMax[1];
71 | }
72 | break;
73 | }`,
74 | }
75 |
--------------------------------------------------------------------------------
/lib/index.template.ts:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | import * as SvgPath from "svgpath";
4 |
5 | type minMax = [min: number, max: number];
6 | export type BBox = [minX: number, minY: number, maxX: number, maxY: number];
7 |
8 | // Precision for consider cubic polynom as quadratic one
9 | const CBEZIER_MINMAX_EPSILON = 0.00000001;
10 |
11 | // https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L89
12 | function minmaxQ(A: [number, number, number]): minMax {
13 | const min = Math.min(A[0], A[2]),
14 | max = Math.max(A[0], A[2]);
15 |
16 | if (A[1] >= A[0] ? A[2] >= A[1] : A[2] <= A[1]) {
17 | // if no extremum in ]0,1[
18 | return [min, max];
19 | }
20 |
21 | // check if the extremum E is min or max
22 | const E = (A[0] * A[2] - A[1] * A[1]) / (A[0] - 2 * A[1] + A[2]);
23 | return E < min ? [E, max] : [min, E];
24 | }
25 |
26 | // https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L127
27 | function minmaxC(A: [number, number, number, number]): minMax {
28 | const K = A[0] - 3 * A[1] + 3 * A[2] - A[3];
29 |
30 | // if the polynomial is (almost) quadratic and not cubic
31 | if (Math.abs(K) < CBEZIER_MINMAX_EPSILON) {
32 | if (A[0] === A[3] && A[0] === A[1]) {
33 | // no curve, point targeting same location
34 | return [A[0], A[3]];
35 | }
36 |
37 | return minmaxQ([
38 | A[0],
39 | -0.5 * A[0] + 1.5 * A[1],
40 | A[0] - 3 * A[1] + 3 * A[2],
41 | ]);
42 | }
43 |
44 | // the reduced discriminant of the derivative
45 | const T =
46 | -A[0] * A[2] +
47 | A[0] * A[3] -
48 | A[1] * A[2] -
49 | A[1] * A[3] +
50 | A[1] * A[1] +
51 | A[2] * A[2];
52 |
53 | // if the polynomial is monotone in [0,1]
54 | if (T <= 0) {
55 | return [Math.min(A[0], A[3]), Math.max(A[0], A[3])];
56 | }
57 | const S = Math.sqrt(T);
58 |
59 | // potential extrema
60 | let min = Math.min(A[0], A[3]),
61 | max = Math.max(A[0], A[3]);
62 |
63 | const L = A[0] - 2 * A[1] + A[2];
64 | // check local extrema
65 | for (let R = (L + S) / K, i = 1; i <= 2; R = (L - S) / K, i++) {
66 | if (R > 0 && R < 1) {
67 | // if the extrema is for R in [0,1]
68 | const Q =
69 | A[0] * (1 - R) * (1 - R) * (1 - R) +
70 | A[1] * 3 * (1 - R) * (1 - R) * R +
71 | A[2] * 3 * (1 - R) * R * R +
72 | A[3] * R * R * R;
73 | if (Q < min) {
74 | min = Q;
75 | }
76 | if (Q > max) {
77 | max = Q;
78 | }
79 | }
80 | }
81 |
82 | return [min, max];
83 | }
84 |
85 | /**
86 | * Compute bounding boxes of SVG paths.
87 | * @param {String | typeof SvgPath} d SVG path for which their bounding box will be computed.
88 | * @returns {BBox}
89 | */
90 | export function svgPathBbox(d: string | typeof SvgPath): BBox {
91 | const min = [Infinity, Infinity],
92 | max = [-Infinity, -Infinity];
93 | SvgPath.from(d)
94 | .abs()
95 | .unarc()
96 | .unshort()
97 | .iterate((seg, _, x, y) => {
98 | switch (seg[0]) {
99 | /*cases*/
100 | }
101 | }, true);
102 | return [min[0], min[1], max[0], max[1]];
103 | }
104 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svg-path-bbox",
3 | "version": "2.1.0",
4 | "description": "Compute bounding boxes of SVG paths.",
5 | "keywords": [
6 | "svg",
7 | "path",
8 | "bbox"
9 | ],
10 | "main": "./dist/index.js",
11 | "module": "./dist/es2015/index.mjs",
12 | "types": "./src/types.d.ts",
13 | "exports": {
14 | ".": {
15 | "import": {
16 | "types": "./src/types.d.mts",
17 | "default": "./dist/es2015/index.mjs"
18 | },
19 | "require": {
20 | "types": "./src/types.d.ts",
21 | "default": "./dist/index.js"
22 | }
23 | }
24 | },
25 | "bin": {
26 | "svg-path-bbox": "src/cli.cjs"
27 | },
28 | "scripts": {
29 | "coveralls": "cat ./tests/coverage/lcov.info | coveralls",
30 | "prebuild": "npm run build:index && npm run lint:fix && npm run dist:prepare",
31 | "build": "npm run build:ts && npm run build:ts:esm",
32 | "build:index": "tsx scripts/build/index.ts",
33 | "build:ts": "tsc",
34 | "build:ts:esm": "tsc --module es2015 --target es2015 --outDir dist/es2015 && tsx scripts/build/patch-esm-dist.ts",
35 | "examples": "npm run example:cjs && npm run example:esm && npm run example:ts",
36 | "example:cjs": "node examples/common.cjs",
37 | "example:esm": "node examples/esm.mjs",
38 | "example:ts": "tsx examples/typescript.ts",
39 | "lint": "eslint --max-warnings 0 src/index.ts tests scripts examples/typescript.ts",
40 | "lint:fix": "npm run lint -- --fix",
41 | "dist:prepare": "node -e \"f=require('fs/promises');f.rm('dist',{recursive:true,force:true}).then(()=>f.mkdir('dist'))\"",
42 | "test": "jest",
43 | "prepare": "husky",
44 | "bench:si": "tsx scripts/simple-icons-benchmark.cts"
45 | },
46 | "author": {
47 | "name": "Álvaro Mondéjar Rubio",
48 | "email": "mondejar1994@gmail.com"
49 | },
50 | "contributors": [],
51 | "license": "BSD-3-Clause",
52 | "dependencies": {
53 | "svgpath": "^2.6.0"
54 | },
55 | "devDependencies": {
56 | "@types/jest": "29.5.14",
57 | "@types/jest-environment-puppeteer": "5.0.6",
58 | "@types/node": "22.13.8",
59 | "@types/puppeteer": "5.4.7",
60 | "@types/svg-path-bounding-box": "1.0.2",
61 | "@typescript-eslint/eslint-plugin": "8.25.0",
62 | "@typescript-eslint/parser": "8.25.0",
63 | "anywhere": "1.6.0",
64 | "coveralls": "3.1.1",
65 | "eslint": "9.21.0",
66 | "husky": "9.1.7",
67 | "jest": "29.7.0",
68 | "jest-puppeteer": "11.0.0",
69 | "puppeteer": "24.3.0",
70 | "simple-icons": "14.9.0",
71 | "svg-path-bounding-box": "1.0.4",
72 | "ts-jest": "29.2.6",
73 | "tsx": "4.19.3",
74 | "typescript": "5.6.3"
75 | },
76 | "repository": {
77 | "type": "git",
78 | "url": "git+https://github.com/mondeja/svg-path-bbox.git"
79 | },
80 | "bugs": {
81 | "url": "https://github.com/mondeja/svg-path-bbox/issues",
82 | "email": "mondejar1994@gmail.com"
83 | },
84 | "files": [
85 | "src",
86 | "dist"
87 | ],
88 | "engines": {
89 | "node": ">=6.17.1"
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/scripts/benchmark.ts:
--------------------------------------------------------------------------------
1 | import { inspect } from "node:util";
2 |
3 | import { svgPathBbox } from "../src";
4 | import svgPathBoundingBox from "svg-path-bounding-box";
5 | import type { BBox } from "../src";
6 | import type { BoundingBoxView } from "svg-path-bounding-box";
7 |
8 | type LibraryAdapter = {
9 | func: typeof svgPathBbox | typeof svgPathBoundingBox;
10 | resultParser?: (result: BoundingBoxView) => BBox;
11 | };
12 |
13 | const LIBRARIES: {
14 | [key: string]: LibraryAdapter;
15 | } = {
16 | "svg-path-bbox": {
17 | func: svgPathBbox,
18 | },
19 | "svg-path-bounding-box": {
20 | func: svgPathBoundingBox,
21 | resultParser(result) {
22 | return [result.minX, result.minY, result.maxX, result.maxY];
23 | },
24 | },
25 | };
26 |
27 | const PATHS = [
28 | "M0 0L10 10 20 0Z",
29 | "M100,250 c0,-150 300,-150 300,0",
30 | "M 10 20 C 50 -15 90 45 10 80 L 60 80",
31 | ];
32 |
33 | const EPOCHS = [10000, 100000];
34 |
35 | const runLibrariesBenchmark = (
36 | paths: [string | null, string][],
37 | epochs: number[]
38 | ) => {
39 | for (const [key, path] of paths) {
40 | for (const e of epochs) {
41 | let pathType = path.replace(/[0-9\-.\s,]/g, "");
42 | pathType =
43 | pathType.length > 25 ? `${pathType.substring(0, 25)}...` : pathType;
44 | const pathSummary =
45 | path.length > 25 ? `${path.substring(0, 25)}...` : path;
46 |
47 | if (key) {
48 | process.stdout.write(`${key} - `);
49 | }
50 |
51 | process.stdout.write(`${pathSummary} (type ${pathType}) [${e} epochs]\n`);
52 | for (const library in LIBRARIES) {
53 | console.time(library);
54 | const func = LIBRARIES[library]["func"];
55 | let _errorRaised = false,
56 | result;
57 | for (let r = 0; r < e; r++) {
58 | try {
59 | result = func(path);
60 | } catch (err) {
61 | if (!_errorRaised) {
62 | process.stderr.write(
63 | `Error computing bbox with library ${library}:\n`
64 | );
65 | process.stderr.write(`${err}\n`);
66 | }
67 | _errorRaised = true;
68 | }
69 | }
70 | process.stdout.write(" ");
71 | console.timeEnd(library);
72 |
73 | const resultParser = LIBRARIES[library].resultParser;
74 | if (resultParser !== undefined) {
75 | result = resultParser(result as BoundingBoxView);
76 | }
77 | if (result) {
78 | process.stdout.write(
79 | ` + result: ${inspect(result, { colors: true })}\n`
80 | );
81 | }
82 | }
83 | process.stdout.write("\n");
84 | }
85 | }
86 | };
87 |
88 | export default runLibrariesBenchmark;
89 |
90 | if (require.main === module) {
91 | runLibrariesBenchmark(
92 | PATHS.map((path) => [null, path]),
93 | EPOCHS
94 | );
95 | }
96 |
--------------------------------------------------------------------------------
/scripts/build/index.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "node:fs";
2 | import cases from "../../lib/cases";
3 | import { getSimpleIconsSegmentsStats } from "../get-simple-icons-segments-stats";
4 |
5 | function buildIndexTs(indexTemplateTs: string): string {
6 | let indexTs = "";
7 | const [beforeCases, afterCases] = indexTemplateTs.split("/*cases*/");
8 | indexTs += beforeCases.replace(
9 | '"use strict";',
10 | '"use strict";\n\n// WARNING: This file is autogenerated, edit lib/index.template.ts'
11 | );
12 | indexTs +=
13 | "// The next cases are ordered based on simple-icons data\n ";
14 | const siSegmentsStats = getSimpleIconsSegmentsStats().map(([seg]) => seg);
15 | for (const segment of siSegmentsStats) {
16 | const caseBranch = cases[segment as keyof typeof cases];
17 | indexTs += `case "${segment}": ${caseBranch}\n `;
18 | }
19 | indexTs = indexTs.trimEnd();
20 | indexTs += afterCases;
21 | return indexTs;
22 | }
23 |
24 | if (require.main === module) {
25 | const indexTemplateTs = fs.readFileSync("lib/index.template.ts", "utf8");
26 | const indexTs = buildIndexTs(indexTemplateTs);
27 | fs.writeFileSync("src/index.ts", indexTs);
28 | }
29 |
--------------------------------------------------------------------------------
/scripts/build/patch-esm-dist.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "node:fs";
2 |
3 | // .js -> .mjs
4 | fs.renameSync(
5 | "dist/es2015/index.js",
6 | "dist/es2015/index.mjs"
7 | );
8 |
9 | // import * as SvgPath from "svgpath"; -> import SvgPath from "svgpath";
10 | fs.writeFileSync(
11 | "dist/es2015/index.mjs",
12 | fs.readFileSync("dist/es2015/index.mjs", "utf-8").replace(
13 | 'import * as SvgPath from "svgpath";',
14 | 'import SvgPath from "svgpath";'
15 | )
16 | );
17 |
--------------------------------------------------------------------------------
/scripts/get-simple-icons-segments-stats.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Shows how many segments of each type are found in
3 | * simple-icons icons.
4 | */
5 |
6 | import * as icons from "simple-icons";
7 |
8 | export function getSimpleIconsSegmentsStats(): [string, number][] {
9 | const varNames = Object.keys(icons).slice(1);
10 | const segmentsStats = {
11 | M: 0,
12 | V: 0,
13 | H: 0,
14 | L: 0,
15 | C: 0,
16 | Q: 0,
17 | };
18 | for (const varName of varNames) {
19 | const icon = icons[varName as keyof typeof icons];
20 | for (const segment in segmentsStats) {
21 | segmentsStats[segment as keyof typeof segmentsStats] +=
22 | icon.path.split(segment).length;
23 | }
24 | };
25 | return Object.entries(segmentsStats).sort(([, a], [, b]) => b - a);
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/scripts/show-simple-icons-segments-stats.ts:
--------------------------------------------------------------------------------
1 | import { getSimpleIconsSegmentsStats } from "./get-simple-icons-segments-stats";
2 |
3 | const stats = getSimpleIconsSegmentsStats();
4 | for (const [seg, occ] of stats) {
5 | process.stdout.write(`${seg}: ${occ}\n`);
6 | }
7 |
--------------------------------------------------------------------------------
/scripts/simple-icons-benchmark.cts:
--------------------------------------------------------------------------------
1 | import icons from "simple-icons";
2 | import type { SimpleIcon } from "simple-icons";
3 |
4 | import runLibrariesBenchmark from "./benchmark";
5 |
6 | const EPOCHS = [5000];
7 | const MAX_ICONS = 5;
8 | const FILTER = (icon: SimpleIcon | undefined) => icon !== undefined;
9 |
10 | const titlePaths: [string, string][] = [];
11 | for (const icon of Object.values(icons)) {
12 | if (!FILTER(icon)) {
13 | continue;
14 | }
15 | titlePaths.push([icon.title, icon.path]);
16 | if (titlePaths.length >= MAX_ICONS) {
17 | break;
18 | }
19 | }
20 |
21 | runLibrariesBenchmark(titlePaths, EPOCHS);
22 |
23 |
--------------------------------------------------------------------------------
/src/cli.cjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | "use strict";
3 |
4 | /* eslint no-process-exit: 0 */
5 | /* eslint global-require: 0 */
6 |
7 | function help() {
8 | const packageJson = require("../package.json");
9 |
10 | return `${packageJson.description}
11 | Output is redirected to STDOUT.
12 |
13 | Usage:
14 | ${packageJson.name} [-h] [-v] [path] [path] ...
15 |
16 | Options:
17 | -h, --help Display this help text and exit.
18 | -v, --version Show this program version number (${packageJson.version}).`;
19 | }
20 |
21 | if (require.main === module) {
22 | let sliceN = 1;
23 | if (
24 | process.argv.indexOf(module.filename) > -1 ||
25 | require("path").basename(process.argv[1]) === "svg-path-bbox" ||
26 | process.argv.indexOf(module.filename.slice(0, -4)) > -1 // rstrip '.cjs'
27 | ) {
28 | sliceN = 2;
29 | }
30 | const args = process.argv.slice(sliceN, process.argv.length);
31 |
32 | if (args.length === 0) {
33 | console.error(
34 | "You must pass SVG paths enclosed between quotes as parameters."
35 | );
36 | process.exit(1);
37 | } else if (args.includes("--version") || args.includes("-v")) {
38 | console.log(require("../package.json").version);
39 | process.exit(1);
40 | } else if (args.includes("--help") || args.includes("-h")) {
41 | console.error(help());
42 | process.exit(1);
43 | }
44 |
45 | const { svgPathBbox } = require("../dist/index.js");
46 | for (let a = 0; a < args.length; a++) {
47 | console.log(svgPathBbox(args[a]).join(" "));
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // WARNING: This file is autogenerated, edit lib/index.template.ts
4 |
5 | import * as SvgPath from "svgpath";
6 |
7 | type minMax = [min: number, max: number];
8 | export type BBox = [minX: number, minY: number, maxX: number, maxY: number];
9 |
10 | // Precision for consider cubic polynom as quadratic one
11 | const CBEZIER_MINMAX_EPSILON = 0.00000001;
12 |
13 | // https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L89
14 | function minmaxQ(A: [number, number, number]): minMax {
15 | const min = Math.min(A[0], A[2]),
16 | max = Math.max(A[0], A[2]);
17 |
18 | if (A[1] >= A[0] ? A[2] >= A[1] : A[2] <= A[1]) {
19 | // if no extremum in ]0,1[
20 | return [min, max];
21 | }
22 |
23 | // check if the extremum E is min or max
24 | const E = (A[0] * A[2] - A[1] * A[1]) / (A[0] - 2 * A[1] + A[2]);
25 | return E < min ? [E, max] : [min, E];
26 | }
27 |
28 | // https://github.com/kpym/SVGPathy/blob/acd1a50c626b36d81969f6e98e8602e128ba4302/lib/box.js#L127
29 | function minmaxC(A: [number, number, number, number]): minMax {
30 | const K = A[0] - 3 * A[1] + 3 * A[2] - A[3];
31 |
32 | // if the polynomial is (almost) quadratic and not cubic
33 | if (Math.abs(K) < CBEZIER_MINMAX_EPSILON) {
34 | if (A[0] === A[3] && A[0] === A[1]) {
35 | // no curve, point targeting same location
36 | return [A[0], A[3]];
37 | }
38 |
39 | return minmaxQ([
40 | A[0],
41 | -0.5 * A[0] + 1.5 * A[1],
42 | A[0] - 3 * A[1] + 3 * A[2],
43 | ]);
44 | }
45 |
46 | // the reduced discriminant of the derivative
47 | const T =
48 | -A[0] * A[2] +
49 | A[0] * A[3] -
50 | A[1] * A[2] -
51 | A[1] * A[3] +
52 | A[1] * A[1] +
53 | A[2] * A[2];
54 |
55 | // if the polynomial is monotone in [0,1]
56 | if (T <= 0) {
57 | return [Math.min(A[0], A[3]), Math.max(A[0], A[3])];
58 | }
59 | const S = Math.sqrt(T);
60 |
61 | // potential extrema
62 | let min = Math.min(A[0], A[3]),
63 | max = Math.max(A[0], A[3]);
64 |
65 | const L = A[0] - 2 * A[1] + A[2];
66 | // check local extrema
67 | for (let R = (L + S) / K, i = 1; i <= 2; R = (L - S) / K, i++) {
68 | if (R > 0 && R < 1) {
69 | // if the extrema is for R in [0,1]
70 | const Q =
71 | A[0] * (1 - R) * (1 - R) * (1 - R) +
72 | A[1] * 3 * (1 - R) * (1 - R) * R +
73 | A[2] * 3 * (1 - R) * R * R +
74 | A[3] * R * R * R;
75 | if (Q < min) {
76 | min = Q;
77 | }
78 | if (Q > max) {
79 | max = Q;
80 | }
81 | }
82 | }
83 |
84 | return [min, max];
85 | }
86 |
87 | /**
88 | * Compute bounding boxes of SVG paths.
89 | * @param {String | typeof SvgPath} d SVG path for which their bounding box will be computed.
90 | * @returns {BBox}
91 | */
92 | export function svgPathBbox(d: string | typeof SvgPath): BBox {
93 | const min = [Infinity, Infinity],
94 | max = [-Infinity, -Infinity];
95 | SvgPath.from(d)
96 | .abs()
97 | .unarc()
98 | .unshort()
99 | .iterate((seg, _, x, y) => {
100 | switch (seg[0]) {
101 | // The next cases are ordered based on simple-icons data
102 | case "M": {
103 | if (min[0] > seg[1]) {
104 | min[0] = seg[1];
105 | }
106 | if (min[1] > seg[2]) {
107 | min[1] = seg[2];
108 | }
109 | if (max[0] < seg[1]) {
110 | max[0] = seg[1];
111 | }
112 | if (max[1] < seg[2]) {
113 | max[1] = seg[2];
114 | }
115 | break;
116 | }
117 | case "H": {
118 | if (min[0] > seg[1]) {
119 | min[0] = seg[1];
120 | }
121 | if (max[0] < seg[1]) {
122 | max[0] = seg[1];
123 | }
124 | break;
125 | }
126 | case "V": {
127 | if (min[1] > seg[1]) {
128 | min[1] = seg[1];
129 | }
130 | if (max[1] < seg[1]) {
131 | max[1] = seg[1];
132 | }
133 | break;
134 | }
135 | case "L": {
136 | if (min[0] > seg[1]) {
137 | min[0] = seg[1];
138 | }
139 | if (min[1] > seg[2]) {
140 | min[1] = seg[2];
141 | }
142 | if (max[0] < seg[1]) {
143 | max[0] = seg[1];
144 | }
145 | if (max[1] < seg[2]) {
146 | max[1] = seg[2];
147 | }
148 | break;
149 | }
150 | case "C": {
151 | const cxMinMax = minmaxC([x, seg[1], seg[3], seg[5]]);
152 | if (min[0] > cxMinMax[0]) {
153 | min[0] = cxMinMax[0];
154 | }
155 | if (max[0] < cxMinMax[1]) {
156 | max[0] = cxMinMax[1];
157 | }
158 |
159 | const cyMinMax = minmaxC([y, seg[2], seg[4], seg[6]]);
160 | if (min[1] > cyMinMax[0]) {
161 | min[1] = cyMinMax[0];
162 | }
163 | if (max[1] < cyMinMax[1]) {
164 | max[1] = cyMinMax[1];
165 | }
166 | break;
167 | }
168 | case "Q": {
169 | const qxMinMax = minmaxQ([x, seg[1], seg[3]]);
170 | if (min[0] > qxMinMax[0]) {
171 | min[0] = qxMinMax[0];
172 | }
173 | if (max[0] < qxMinMax[1]) {
174 | max[0] = qxMinMax[1];
175 | }
176 |
177 | const qyMinMax = minmaxQ([y, seg[2], seg[4]]);
178 | if (min[1] > qyMinMax[0]) {
179 | min[1] = qyMinMax[0];
180 | }
181 | if (max[1] < qyMinMax[1]) {
182 | max[1] = qyMinMax[1];
183 | }
184 | break;
185 | }
186 | }
187 | }, true);
188 | return [min[0], min[1], max[0], max[1]];
189 | }
190 |
--------------------------------------------------------------------------------
/src/types.d.mts:
--------------------------------------------------------------------------------
1 | import type SvgPath from "svgpath";
2 | export type BBox = [minX: number, minY: number, maxX: number, maxY: number];
3 |
4 | /**
5 | * Compute bounding boxes of SVG paths.
6 | * @param {String | typeof SvgPath} d SVG path for which their bounding box will be computed.
7 | * @returns {BBox}
8 | */
9 | export function svgPathBbox(d: string | typeof SvgPath): BBox;
10 |
--------------------------------------------------------------------------------
/src/types.d.ts:
--------------------------------------------------------------------------------
1 | import type SvgPath from "svgpath";
2 | export type BBox = [minX: number, minY: number, maxX: number, maxY: number];
3 |
4 | /**
5 | * Compute bounding boxes of SVG paths.
6 | * @param {String | typeof SvgPath} d SVG path for which their bounding box will be computed.
7 | * @returns {BBox}
8 | */
9 | export function svgPathBbox(d: string | typeof SvgPath): BBox;
10 |
--------------------------------------------------------------------------------
/svg-path-bbox.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/bbox.test.ts:
--------------------------------------------------------------------------------
1 | import { svgPathBbox } from "../src";
2 | import * as SvgPath from "svgpath";
3 | import { linealCases, pathologicalCases } from "./cases/bbox";
4 |
5 | describe("svgPathBbox(d) [Lineal cases]", () => {
6 | test.each(linealCases)("svgPathBbox(%p) ⇢ %p", (d, bbox) => {
7 | expect(svgPathBbox(d)).toEqual(bbox);
8 | });
9 | });
10 |
11 | describe("svgPathBbox(SvgPath(d)) [Lineal cases]", () => {
12 | test.each(linealCases)("svgPathBbox(SvgPath(%p)) ⇢ %p", (d, bbox) => {
13 | expect(svgPathBbox(new SvgPath(d))).toEqual(bbox);
14 | });
15 | });
16 |
17 | describe("svgPathBbox(d) [Pathological cases]", () => {
18 | test.each(pathologicalCases)("svgPathBbox(%p) ⇢ %p", (d, bbox) => {
19 | expect(svgPathBbox(d)).toEqual(bbox);
20 | });
21 | });
22 |
23 | describe("svgPathBbox(SvgPath(d)) [Pathological cases]", () => {
24 | test.each(pathologicalCases)("svgPathBbox(SvgPath(%p)) ⇢ %p", (d, bbox) => {
25 | expect(svgPathBbox(new SvgPath(d))).toEqual(bbox);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/tests/browser.test.ts:
--------------------------------------------------------------------------------
1 | import cases from "./cases/bbox";
2 | import type { BBox } from "../src";
3 |
4 | describe("Consistency with browser's element.getBBox()", () => {
5 | beforeAll(async () => {
6 | await page.goto("http://localhost:8080");
7 | });
8 |
9 | test.each(cases)(
10 | "browserSvgPathBbox(%p) ⇢ %p",
11 | async (d: string, libBbox: BBox) => {
12 | const browserBbox = await page.evaluate((d) => {
13 | document.body.innerHTML = ``;
14 | const bbox = (
15 | document.querySelector("path") as SVGPathElement
16 | ).getBBox();
17 | return [bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height];
18 | }, d);
19 |
20 | expect(browserBbox[0]).toBeCloseTo(libBbox[0], 3);
21 | expect(browserBbox[1]).toBeCloseTo(libBbox[1], 3);
22 | expect(browserBbox[2]).toBeCloseTo(libBbox[2], 3);
23 | expect(browserBbox[3]).toBeCloseTo(libBbox[3], 3);
24 | }
25 | );
26 | });
27 |
--------------------------------------------------------------------------------
/tests/browser/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/tests/cases/bbox.ts:
--------------------------------------------------------------------------------
1 | import type { BBox } from "../../src";
2 |
3 | type CasesTuple = [string, BBox][];
4 |
5 | export const linealCases: CasesTuple = [
6 | // Mz
7 | ["M5 3z", [5, 3, 5, 3]],
8 | // Mz negative
9 | ["M-5 -3z", [-5, -3, -5, -3]],
10 |
11 | // Mlz
12 | ["M5 3l5 0z", [5, 3, 10, 3]],
13 | // Mlz negative
14 | ["M-5 -3l10 0z", [-5, -3, 5, -3]],
15 | // MLz
16 | ["M5 1L6 2z", [5, 1, 6, 2]],
17 | // MLz negative
18 | ["M-5 -1L3 2", [-5, -1, 3, 2]],
19 |
20 | // Mmz
21 | ["M5 3m5 0z", [5, 3, 10, 3]],
22 | // Mmz negative
23 | ["M-5 -3m10 0z", [-5, -3, 5, -3]],
24 | // MMz
25 | ["M5 3M10 8z", [5, 3, 10, 8]],
26 | // MMz negative
27 | ["M-5 -3M-10 -8z", [-10, -8, -5, -3]],
28 |
29 | // Mvz
30 | ["M1 5v3z", [1, 5, 1, 8]],
31 | // Mvz negative
32 | ["M-5 -5v3z", [-5, -5, -5, -2]],
33 | // MVz
34 | ["M1 1V5z", [1, 1, 1, 5]],
35 | // MVz negative
36 | ["M-1 -1V-5z", [-1, -5, -1, -1]],
37 |
38 | // Mhz
39 | ["M1 5h3z", [1, 5, 4, 5]],
40 | // Mhz negative
41 | ["M-1 -5h-3z", [-4, -5, -1, -5]],
42 | // MHz
43 | ["M1 5H3z", [1, 5, 3, 5]],
44 | // MHz negative
45 | ["M-1 -5H-2z", [-2, -5, -1, -5]],
46 | ];
47 |
48 | export const pathologicalCases: CasesTuple = [
49 | // Pathological tests (https://github.com/simple-icons/simple-icons/issues/3960)
50 | // These paths proves the bug existent in `svg-path-bounding-box`
51 | // that exists in `svg-path-bbox` before v0.1.1
52 |
53 | [
54 | // arXiv
55 | "M20.7 15.404l-1.894-4.967h1.411l1.39 3.582 1.379-3.582h.96l-1.92 4.967zM16.298 9.6V8.48h1.34V9.6zm0 5.808v-4.971h1.34v4.967zm-6.965-.003l2.146-3.3L9.43 8.707h1.627l1.364 2.254L13.9 8.707h1.12l-2.046 3.156 2.126 3.537h-1.622l-1.45-2.4-1.557 2.4H9.333zm-3.346 0v-4.968h1.338v.937c.344-.7.875-1.051 1.585-1.051a1.401 1.401 0 01.248.026v1.194a1.6 1.6 0 00-.53-.102c-.537 0-.968.267-1.303.8v3.164zm-3.028-.536q-.664.65-1.437.65a1.473 1.473 0 01-1.06-.398 1.376 1.376 0 01-.406-1.03 1.45 1.45 0 01.659-1.271q.657-.447 1.884-.448h.355v-.453q0-.772-.88-.772a3.305 3.305 0 00-1.587.443v-.922a5.016 5.016 0 011.808-.345q1.953 0 1.951 1.55v2.206c0 .39.123.58.376.58a.8.8 0 00.174-.02l.032.751a2.745 2.745 0 01-.751.13c-.552 0-.902-.216-1.06-.65h-.054zm0-.72v-1.01h-.32c-.866 0-1.297.274-1.297.815a.64.64 0 00.64.648c.329.004.647-.15.977-.453z",
56 | [0.05495008158100078, 8.48, 23.946000000000005, 15.520000000000001],
57 | // svg-path-bounding-box: [ 0.05495008158100078, 7.594038058172477, 23.946000000000005, 15.52 ]
58 | ],
59 | [
60 | // Emlakjet
61 | "M15.65 16.105v-.24a3.543 3.543 0 00-1.267-2.471c-.724-.663-1.69-.965-2.655-.904-1.87.12-3.378 1.747-3.378 3.615 0 .784.12 1.567.422 2.471H4.55V6.946l7.42-5.123 7.482 5.122v11.692h-4.223c.18-.663.422-1.688.422-2.532m5.068-10.244L12.452.136c-.301-.181-.663-.181-.905 0L3.222 5.86c-.242.12-.362.361-.362.663V19.48c0 .482.362.844.844.844H9.92a.824.824 0 00.844-.844c0-.06 0-.18-.06-.24l-.06-.182c-.302-.723-.664-1.627-.664-2.53v-.182c-.06-.542.12-1.084.482-1.446a2.095 2.095 0 011.388-.723c.543-.06 1.026.12 1.448.482.422.362.664.844.724 1.386v.18c.06 1.206-.724 2.954-.845 3.135l-1.146 2.17-.18-.362c-.122-.181-.302-.362-.483-.422-.182-.06-.423-.06-.604.06-.18.12-.362.301-.422.482s-.06.422.06.603l.905 1.687c.121.241.423.422.724.422.302 0 .604-.18.724-.422l1.81-3.375h5.732a.824.824 0 00.844-.843V6.524c-.06-.302-.18-.543-.422-.663",
62 | [2.86, 0.00025000000000001367, 21.1412251544222, 24.000000000000004],
63 | // svg-path-bounding-box: [ 2.86, 0.136, 21.141225154422205, 24.000000000000004 ]
64 | ],
65 | [
66 | // Microsoft Excel
67 | "M23 1.5q.41 0 .7.3.3.29.3.7v19q0 .41-.3.7-.29.3-.7.3H7q-.41 0-.7-.3-.3-.29-.3-.7V18H1q-.41 0-.7-.3-.3-.29-.3-.7V7q0-.41.3-.7Q.58 6 1 6h5V2.5q0-.41.3-.7.29-.3.7-.3zM6 13.28l1.42 2.66h2.14l-2.38-3.87 2.34-3.8H7.46l-1.3 2.4-.05.08-.04.09-.64-1.28-.66-1.29H2.59l2.27 3.82-2.48 3.85h2.16zM14.25 21v-3H7.5v3zm0-4.5v-3.75H12v3.75zm0-5.25V7.5H12v3.75zm0-5.25V3H7.5v3zm8.25 15v-3h-6.75v3zm0-4.5v-3.75h-6.75v3.75zm0-5.25V7.5h-6.75v3.75zm0-5.25V3h-6.75v3Z",
68 | [5.551115123125783e-17, 1.5, 24.000000000000096, 22.5],
69 | // svg-path-bounding-box (outdated): [ 5.551115123125783e-17, 1.5, 24, 22.769499273762435 ]
70 | ],
71 | [
72 | // Microsoft Exchange
73 | "M24 7.875q0 .293-.117.58t-.317.486L20.496 12l3.07 3.059q.2.199.317.486.117.287.117.58V21q0 .316-.117.586-.117.27-.322.475-.206.205-.475.322-.27.117-.586.117h-4.875q-.293 0-.58-.117t-.486-.317l-3.059-3.07-3.059 3.07q-.199.2-.486.317-.287.117-.58.117H4.5q-.316 0-.586-.117-.27-.117-.475-.322-.205-.206-.322-.475Q3 21.316 3 21v-3H.996q-.41 0-.703-.293T0 17.004V6.996q0-.41.293-.703T.996 6H3V3q0-.316.117-.586.117-.27.322-.475.206-.205.475-.322.27-.117.586-.117h4.875q.293 0 .58.117t.486.317l3.059 3.07 3.059-3.07q.199-.2.486-.317.287-.117.58-.117H22.5q.316 0 .586.117.27.117.475.322.205.206.322.475Q24 2.684 24 3zM4.5 3v3h6.504q.41 0 .703.293t.293.703V5.625L9.375 3zM3.375 15.938h5.25v-1.583h-3.41v-1.593h3.047V11.18H5.215V9.656H8.46V8.062H3.375zm19.125.187L19.875 13.5h-3.691q-.247 0-.463.094-.217.094-.375.252-.159.158-.252.375-.094.216-.094.463v3.691L17.625 21H22.5zm0-8.25V3h-4.875L13.5 7.125v2.191q0 .774-.404 1.424-.405.65-1.096.99v5.274q0 .41-.293.703t-.703.293H4.5v3h4.875l4.125-4.125v-2.191q0-.563.21-1.05.212-.486.575-.849t.85-.574Q15.62 12 16.184 12h2.191Z",
74 | [0, 1.4999999999999998, 24, 22.5],
75 | // svg-path-bouding-box: [ 0, 1.4999999999999998, 24, 22.67233909145166 ]
76 | ],
77 | [
78 | // Microsoft OneDrive
79 | "M19.453 9.95q.961.058 1.787.468.826.41 1.442 1.066.615.657.966 1.512.352.856.352 1.816 0 1.008-.387 1.893-.386.885-1.049 1.547-.662.662-1.546 1.049-.885.387-1.893.387H6q-1.242 0-2.332-.475-1.09-.475-1.904-1.29-.815-.814-1.29-1.903Q0 14.93 0 13.688q0-.985.31-1.887.311-.903.862-1.658.55-.756 1.324-1.325.774-.568 1.711-.861.434-.129.85-.187.416-.06.861-.082h.012q.515-.786 1.207-1.413.691-.627 1.5-1.066.808-.44 1.705-.668.896-.229 1.845-.229 1.278 0 2.456.417 1.177.416 2.144 1.16.967.744 1.658 1.78.692 1.038 1.008 2.28zm-7.265-4.137q-1.325 0-2.52.544-1.195.545-2.04 1.565.446.117.85.299.405.181.792.416l4.78 2.86 2.731-1.15q.27-.117.545-.204.276-.088.58-.147-.293-.937-.855-1.705-.563-.768-1.319-1.318-.755-.551-1.658-.856-.902-.304-1.886-.304zM2.414 16.395l9.914-4.184-3.832-2.297q-.586-.351-1.23-.539-.645-.188-1.325-.188-.914 0-1.722.364-.809.363-1.412.978-.604.616-.955 1.436-.352.82-.352 1.723 0 .703.234 1.423.235.721.68 1.284zm16.711 1.793q.563 0 1.078-.176.516-.176.961-.516l-7.23-4.324-10.301 4.336q.527.328 1.13.504.604.175 1.237.175zm3.012-1.852q.363-.727.363-1.523 0-.774-.293-1.407t-.791-1.072q-.498-.44-1.166-.68-.668-.24-1.406-.24-.422 0-.838.1t-.815.252q-.398.152-.785.334-.386.181-.761.345Z",
80 | [0, 4.312000000000001, 24, 19.688000000000002],
81 | // svg-path-bounding-box: [ 0, 4.312000000000001, 24, 19.99655236943747 ]
82 | ],
83 | [
84 | // Microsoft OneNote
85 | "M23 1.5Q23.41 1.5 23.7 1.8 24 2.09 24 2.5V21.5Q24 21.91 23.7 22.2 23.41 22.5 23 22.5H7Q6.59 22.5 6.3 22.2 6 21.91 6 21.5V18H1Q0.59 18 0.3 17.7 0 17.41 0 17V7Q0 6.59 0.3 6.3 0.58 6 1 6H6V2.5Q6 2.09 6.3 1.8 6.59 1.5 7 1.5ZM4.56 11 7.39 15.93H9.18V8.07H7.44V13.1L4.71 8.07H2.82V15.93H4.56ZM22.5 21V18H19.5V21ZM22.5 16.5V13.5H19.5V16.5ZM22.5 12V9H19.5V12ZM22.5 7.5V3H7.5V6H11Q11.41 6 11.7 6.3 12 6.59 12 7V17Q12 17.41 11.7 17.7 11.41 18 11 18H7.5V21H18V7.5Z",
86 | [0, 1.5, 24.000000000000096, 22.5],
87 | // svg-path-bounding-box (outdated): [ 0, 1.5, 24, 22.769499273762435 ]
88 | ],
89 | [
90 | // Microsoft Outlook
91 | "M7.88 12.04q0 .45-.11.87-.1.41-.33.74-.22.33-.58.52-.37.2-.87.2t-.85-.2q-.35-.21-.57-.55-.22-.33-.33-.75-.1-.42-.1-.86t.1-.87q.1-.43.34-.76.22-.34.59-.54.36-.2.87-.2t.86.2q.35.21.57.55.22.34.31.77.1.43.1.88zM24 12v9.38q0 .46-.33.8-.33.32-.8.32H7.13q-.46 0-.8-.33-.32-.33-.32-.8V18H1q-.41 0-.7-.3-.3-.29-.3-.7V7q0-.41.3-.7Q.58 6 1 6h6.5V2.55q0-.44.3-.75.3-.3.75-.3h12.9q.44 0 .75.3.3.3.3.75V10.85l1.24.72h.01q.1.07.18.18.07.12.07.25zm-6-8.25v3h3v-3zm0 4.5v3h3v-3zm0 4.5v1.83l3.05-1.83zm-5.25-9v3h3.75v-3zm0 4.5v3h3.75v-3zm0 4.5v2.03l2.41 1.5 1.34-.8v-2.73zM9 3.75V6h2l.13.01.12.04v-2.3zM5.98 15.98q.9 0 1.6-.3.7-.32 1.19-.86.48-.55.73-1.28.25-.74.25-1.61 0-.83-.25-1.55-.24-.71-.71-1.24t-1.15-.83q-.68-.3-1.55-.3-.92 0-1.64.3-.71.3-1.2.85-.5.54-.75 1.3-.25.74-.25 1.63 0 .85.26 1.56.26.72.74 1.23.48.52 1.17.81.69.3 1.56.3zM7.5 21h12.39L12 16.08V17q0 .41-.3.7-.29.3-.7.3H7.5zm15-.13v-7.24l-5.9 3.54Z",
92 | [
93 | 5.551115123125783e-17, 1.4999999999999998, 24.000000000000004,
94 | 22.500000000000004,
95 | ],
96 | // svg-path-bounding-box: [ 5.551115123125783e-17, 1.499999999999999, 24.000000000000004, 22.727227619294418 ]
97 | ],
98 | [
99 | // Microsoft Word
100 | "M23.004 1.5q.41 0 .703.293t.293.703v19.008q0 .41-.293.703t-.703.293H6.996q-.41 0-.703-.293T6 21.504V18H.996q-.41 0-.703-.293T0 17.004V6.996q0-.41.293-.703T.996 6H6V2.496q0-.41.293-.703t.703-.293zM6.035 11.203l1.442 4.735h1.64l1.57-7.876H9.036l-.937 4.653-1.325-4.5H5.38l-1.406 4.523-.938-4.675H1.312l1.57 7.874h1.641zM22.5 21v-3h-15v3zm0-4.5v-3.75H12v3.75zm0-5.25V7.5H12v3.75zm0-5.25V3h-15v3Z",
101 | [0, 1.5, 24, 22.499999999999996],
102 | // svg-path-bounding-box: [ 0, 1.5, 24, 22.772661142370712 ]
103 | ],
104 | [
105 | // pre-commit
106 | "M23.355 10.444L13.556.645a2.2 2.2 0 0 0-3.112 0L.645 10.444a2.201 2.201 0 0 0 0 3.112l9.799 9.799a2.201 2.201 0 0 0 3.112 0l9.799-9.799a2.2 2.2 0 0 0 0-3.112zm-1.657 2.918l-8.337 8.337a1.922 1.922 0 0 1-1.362.563c-.493 0-.986-.188-1.362-.563L2.3 13.362A1.92 1.92 0 0 1 1.738 12c0-.514.2-.998.564-1.362l8.337-8.337c.363-.363.847-.563 1.361-.563s.998.2 1.362.564l8.337 8.337c.75.75.75 1.972-.001 2.723zM14.195 9.76c.094.173.142.399.142.678s-.047.505-.142.678c-.095.173-.22.306-.376.401a1.485 1.485 0 0 1-.542.191 4.033 4.033 0 0 1-.641.049h-1.504V9.119h1.504c.222 0 .435.017.641.049.205.033.386.097.542.191.156.095.281.228.376.401zm7.062 1.319L12.92 2.742c-.245-.245-.572-.381-.92-.381s-.675.135-.921.381l-8.337 8.337c-.245.246-.381.573-.381.921s.135.675.381.921l8.337 8.337a1.304 1.304 0 0 0 1.842 0l8.337-8.337a1.305 1.305 0 0 0-.001-1.842zm-5.213.4a2.437 2.437 0 0 1-.53.906 2.624 2.624 0 0 1-.943.635c-.386.16-.855.24-1.405.24h-2.034v3.155H9.197v-8.8h3.969c.55 0 1.019.08 1.405.24.386.16.7.372.943.635.242.263.419.563.53.9.111.337.166.686.166 1.048 0 .353-.055.7-.166 1.041z",
107 | [
108 | 0.0006839756353888008, 0.0002697515222243041, 23.99973024847777,
109 | 23.99931602436461,
110 | ],
111 | // svg-path-bounding-box: [ 0.0035476024103427706, 0.0002697515222243041, 23.99973024847777, 23.999316024364607 ]
112 | ],
113 | [
114 | // RStudio
115 | "M12.178.002a12.002 12.002 0 0 0-8.662 3.515 12.002 12.002 0 0 0 0 16.97 12.002 12.002 0 0 0 16.97 0 12.002 12.002 0 0 0 0-16.97A12.002 12.002 0 0 0 12.179.002zM7.77 5.995c.562.128 1.05.217 1.663.217.921 0 1.863-.217 2.786-.217 1.79 0 3.45.814 3.45 2.8 0 1.54-.921 2.517-2.35 2.93l2.788 4.107h1.301v1.01h-1.986l-3.293-4.934h-1.757v3.924h1.718v1.01H7.77v-1.01h1.483V7.134L7.77 6.951v-.957zm4.466 1.012c-.596 0-1.213.053-1.864.127v3.798l.941.02c2.298.034 3.183-.85 3.183-2.026 0-1.376-.997-1.919-2.26-1.919z",
116 | [
117 | 0.0023908369018924135, 0.0007318976900466977, 23.999609163098103,
118 | 24.000609163098108,
119 | ],
120 | // svg-path-bounding-box: [ 0.07409714635287423, 0.0007318976900466973, 23.9996091630981, 24.000609163098105 ]
121 | ],
122 | [
123 | // Sellfy
124 | "M23.179.818C15.533-.273 8.406-.273.8.818-.266 8.377-.266 15.424.8 22.946 4.511 23.491 8.22 24 12.005 24c3.748 0 7.459-.51 11.17-1.017 1.1-7.56 1.1-14.607 0-22.165h.004zm-11.54 18.314c-2.055 0-4.226-.689-5.179-1.199l.807-3.126c1.064.705 2.682 1.395 4.446 1.395 1.395 0 2.24-.436 2.24-1.305 0-.615-.435-.975-1.575-1.26l-2.279-.631c-2.416-.66-3.557-1.891-3.557-3.855 0-2.365 1.83-4.256 5.619-4.256 1.99 0 3.973.545 5.07 1.092l-.951 2.976c-1.104-.615-2.79-1.125-4.226-1.125-1.365 0-1.95.436-1.95 1.092 0 .619.404.87 1.291 1.092l2.488.734c2.566.736 3.707 1.966 3.707 3.885-.076 2.701-2.461 4.517-5.957 4.517l.006-.026z",
125 | [0.0005000000000000053, -0.0002500000000000062, 23.999999999999993, 24],
126 | // svg-path-bounding-box: [ 0.0005000000000000004, 0.818, 24, 24 ]
127 | ],
128 | [
129 | // Squarespace
130 | "M22.655 8.719c-1.802-1.801-4.726-1.801-6.564 0l-7.351 7.35c-.45.45-.45 1.2 0 1.65.45.449 1.2.449 1.65 0l7.351-7.351c.899-.899 2.362-.899 3.264 0 .9.9.9 2.364 0 3.264l-7.239 7.239c.9.899 2.362.899 3.263 0l5.589-5.589c1.836-1.838 1.836-4.763.037-6.563zm-2.475 2.437c-.451-.45-1.201-.45-1.65 0l-7.354 7.389c-.9.899-2.361.899-3.262 0-.45-.45-1.2-.45-1.65 0s-.45 1.2 0 1.649c1.801 1.801 4.726 1.801 6.564 0l7.351-7.35c.449-.487.449-1.239.001-1.688zm-2.439-7.35c-1.801-1.801-4.726-1.801-6.564 0l-7.351 7.351c-.45.449-.45 1.199 0 1.649s1.2.45 1.65 0l7.395-7.351c.9-.899 2.371-.899 3.27 0 .451.45 1.201.45 1.65 0 .421-.487.421-1.199-.029-1.649h-.021zm-2.475 2.437c-.45-.45-1.2-.45-1.65 0l-7.351 7.389c-.899.9-2.363.9-3.265 0-.9-.899-.9-2.363 0-3.264l7.239-7.239c-.9-.9-2.362-.9-3.263 0L1.35 8.719c-1.8 1.8-1.8 4.725 0 6.563 1.801 1.801 4.725 1.801 6.564 0l7.35-7.351c.451-.488.451-1.238 0-1.688h.002z",
131 | [4.1119371282413197e-17, 2.4539999999999984, 23.999660308381387, 21.54525],
132 | // svg-path-bounding-box: [ 1.35, 2.453999999999999, 23.999660308381387, 21.54525 ]
133 | ],
134 | [
135 | // New York Times
136 | "M21.272,14.815h-0.098c-0.747,2.049-2.335,3.681-4.363,4.483v-4.483l2.444-2.182l-2.444-2.182V7.397 c2.138,0.006,3.885-1.703,3.927-3.84c0-2.629-2.509-3.556-3.927-3.556c-0.367-0.007-0.734,0.033-1.091,0.12v0.131h0.556 c0.801-0.141,1.565,0.394,1.706,1.195C17.99,1.491,17.996,1.537,18,1.583c-0.033,0.789-0.7,1.401-1.488,1.367 c-0.02-0.001-0.041-0.002-0.061-0.004c-2.444,0-5.323-1.985-8.454-1.985C5.547,0.83,3.448,2.692,3.284,5.139 C3.208,6.671,4.258,8.031,5.76,8.346v-0.12C5.301,7.931,5.041,7.407,5.084,6.862c0.074-1.015,0.957-1.779,1.973-1.705 C7.068,5.159,7.08,5.16,7.091,5.161c2.629,0,6.872,2.182,9.501,2.182h0.098v3.142l-2.444,2.182l2.444,2.182v4.549 c-0.978,0.322-2.003,0.481-3.033,0.469c-1.673,0.084-3.318-0.456-4.614-1.516l4.429-1.985V7.451l-6.196,2.727 c0.592-1.75,1.895-3.168,3.589-3.905V6.175c-4.516,1.004-8.138,4.243-8.138,8.705c0,5.193,4.025,9.12,9.818,9.12 c6.011,0,8.727-4.363,8.727-8.814V14.815z M8.858,18.186c-1.363-1.362-2.091-3.235-2.007-5.16c-0.016-0.88,0.109-1.756,0.371-2.596 l2.051-0.938v8.476L8.858,18.186z",
137 | [2.727, 0.00021808510638331264, 21.272, 24],
138 | // svg-path-bounding-box: [ 2.7270000000000003, 0.001000000000000334, 21.272, 24 ]
139 | ],
140 | [
141 | // Treehouse
142 | "M20.537 4.118c-.806-.453-2.092.278-2.871 1.635L16.25 8.215a3.104 3.104 0 0 0 .21 3.18l.041.062c.653.94 1.535 1.808 1.823 2.118a1.613 1.613 0 0 1-.739 2.654 1.603 1.603 0 0 1-2.025-1.747c.045-.35-.067-.927-.574-1.489-.506-.563-1.54.5-1.874 1.61l-.016.061c-.334 1.094-.546 2.05-.482 2.143.037.06.072.12.105.182a1.81 1.81 0 0 1-3.196 1.701 1.806 1.806 0 0 1 .747-2.446l.121-.061c.065-.03.26-.486.423-1.032l.301-.987c.019-.047.033-.107.045-.168l.897-3.19-.957 1.96c-.112-.363-.3-.38-.709-.091-.243.183-.653.531-.85.669-.365.273-.685.788-.851 1.109a1.313 1.313 0 0 1-.41.5c-.684.564-1.687.456-2.234-.227a1.591 1.591 0 0 1 .912-2.552c.409-.092 1.777-.927 2.596-1.52.152-.106.274-.197.38-.304l2.203-1.67-1.914 1.032s-.196-.016-.426.017c-.698.075-1.428.182-1.564.35a.999.999 0 0 1-.29.272c-.637.456-1.519.32-1.989-.317A1.437 1.437 0 0 1 6.29 8.04c.259-.183.577-.274.865-.274.518.016 1.87.29 2.993.092l.288-.045c1.14-.196 2.476-1.186 3.024-2.187l1.184-2.067c.653-1.139.608-2.384-.105-2.795l-1.323-.76c-.653-.363-1.715-.363-2.354 0L2.004 4.97C1.337 5.319.805 6.23.805 6.975v9.744c0 .744.532 1.656 1.178 2.02l8.85 4.983c.652.365 1.716.365 2.354 0l8.826-4.983c.653-.368 1.184-1.276 1.184-2.02v-9.76c0-.744-.531-1.653-1.169-2.02l-1.46-.823",
143 | [0.805, -0.26825000000000065, 23.197000000000003, 23.99575000000006],
144 | // svg-path-bounding-box: [ 0.805, 0.003999999999999337, 23.197000000000003, 23.99575 ]
145 | ],
146 |
147 | // https://github.com/mondeja/svg-path-bbox/issues/61
148 | [
149 | "M 21.1456 21.1456 C 21.1456 21.1456 21.1456 21.1456 200.0000 0.0000 200.0000 0.0000 200.0000 0.0000 221.1456 221.1456 221.1456 221.1456 221.1456 221.1456 21.1456 221.1456 21.1456 221.1456 21.1456 221.1456 21.1456 21.1456",
150 | [21.1456, 0, 221.1456, 221.1456],
151 | // svg-path-bounding-box: [ 21.1456, 0, 221.1456, 221.1456 ],
152 | ],
153 |
154 | // https://github.com/mondeja/svg-path-bbox/issues/91
155 | [
156 | "M436.79 417.31C435.89 415.01 438.59 410.91 439.69 409.31C439.89 408.91 439.69 408.41 439.39 408.31C438.09 407.41 435.19 404.71 435.09 401.61C434.99 399.71 437.39 396.41 438.49 394.91C439.19 393.71 437.69 393.11 437.09 392.31C435.69 390.81 433.99 388.31 434.09 385.71C434.19 382.71 436.69 379.51 440.79 377.01C440.89 376.91 440.99 376.71 440.89 376.61C439.79 374.91 439.29 374.01 439.19 371.41C438.99 367.91 440.79 364.81 444.19 362.41C446.59 360.81 449.89 359.81 452.79 359.21C452.79 359.01 452.79 349.41 452.79 348.21V348.01V340.21C452.79 340.01 452.69 339.91 452.49 339.91L209.89 340.21C209.69 340.21 209.59 340.31 209.59 340.51L209.69 896.1H452.49C452.69 896.1 452.79 896 452.79 895.8V420.31C449.79 421.41 446.59 422.11 444.19 422.11C440.79 422.01 437.69 419.51 436.79 417.31Z",
157 | [209.59000000000003, 339.90999999999764, 452.7900000000486, 896.1],
158 | // svg-path-bounding-box: [ 209.59, 339.91, 452.79, 896.1 ]
159 | ],
160 |
161 | // Tapas
162 | // https://github.com/simple-icons/simple-icons/pull/7442#issuecomment-1137971253
163 | [
164 | "M7.67 1.56c.282-.134.542-.338.81-.513.253-.163.54-.436.894-.33.103.296-.162.503-.331.662-.538.511-1.154.975-1.72 1.456A240.349 240.349 0 0 1 1.5 7.598a7.406 7.406 0 0 1-.612.445c-.183.118-.456.359-.71.165.071-.337.306-.567.512-.778.213-.216.414-.446.629-.66-.248-.427-.473-.821-.662-1.274-.186-.449-.378-.971-.38-1.554-.002-1.109.635-2.043 1.34-2.68C2.34.61 3.306.066 4.429.006 6.015-.078 6.933.71 7.67 1.56zm5.012 18.075v.198c-.278.01-.532-.01-.795-.016v-.198c.277-.008.535.006.795.016zm-1.59 0v.198c-.282-.012-.52.021-.792.018v-.198a9.53 9.53 0 0 1 .793-.018zm3.177.05c-.007.067.013.158-.017.199-.251-.02-.518-.024-.778-.033v-.198c.275.003.542.009.795.032zm-4.763 0v.199c-.274.002-.512.039-.795.032v-.197c.28.001.516-.036.795-.034zm5.555.034c.255.033.544.029.793.064.013.084-.014.129-.015.2-.255-.033-.544-.03-.794-.067a.703.703 0 0 0 .016-.197zm-7.142.065v.2c-.26.02-.517.046-.778.065-.022-.05-.018-.126-.017-.198.265-.024.521-.053.795-.067zm8.73.067c.269.023.537.048.793.082-.02.058-.004.148-.032.199-.25-.036-.518-.053-.778-.083-.01-.083.017-.128.017-.198zm-10.319.082c-.006.08.03.113.017.199-.259.022-.568.082-.793.082.012-.077-.02-.114-.018-.182.252-.045.529-.066.794-.099zm12.684.199c.012.084-.027.114-.017.196-.256-.044-.54-.063-.794-.114.01-.058.025-.109.017-.182.228.008.545.062.795.1zm-14.288 0c.06.022.033.133.05.196-.259.04-.517.08-.777.117a.68.68 0 0 1-.034-.197c.253-.038.515-.072.761-.116zm15.86.233a.628.628 0 0 1-.034.213c-.247-.055-.52-.083-.777-.132a.702.702 0 0 1 .034-.197c.263.032.503.09.776.116zm-17.414.016c.02.057.036.116.034.196-.263.04-.503.105-.778.133-.004-.073-.034-.12-.033-.197.275-.028.515-.092.777-.132zm18.208.132c.255.052.508.109.778.148-.004.072-.034.119-.034.197-.28-.021-.495-.11-.778-.133-.018-.041.016-.15.034-.212zM22.669 16.726c.156.092.47.098.595.246.099.115.144.486.182.744.203 1.296.287 2.808.332 4.219.008.266.016.583.016.891 0 .298.06.704 0 .91-.041.147-.24.194-.363.264a56.558 56.558 0 0 0-.065-2.843c-.124-.101-.444-.047-.464-.166-.044-.252.267-.09.447-.065-.045-1.272-.177-2.46-.33-3.623-.147-.074-.336-.105-.498-.164-.252.259-.636.939-1.223.81-.22-.047-.363-.342-.464-.545a3.243 3.243 0 0 1-.265-.744c-4.88-.936-11.589-1.016-16.502-.05-.153.655-.43 2.053-1.34 1.52a2.014 2.014 0 0 1-.81-.991 8.31 8.31 0 0 1-.547.133c-.192 1.084-.288 2.268-.346 3.489.166.01.416-.122.595-.1.004.066.028.114.033.18-.166.106-.437.105-.645.166a45.286 45.286 0 0 0-.066 2.976c-.08.022-.273-.122-.347-.213.064-2.301.179-4.553.363-6.732.28-.087.568-.17.844-.264-.04-.383-.117-.827.05-1.09.14-.224.531-.352.81-.432.99-.28 1.979-.05 2.63.413.14.102.247.239.396.299.025-.09-.094-.15-.149-.199-.567-.511-1.498-.958-2.612-.761-.348-1.09-.79-2.142-.794-3.538-.005-1.553.562-2.899 1.205-3.953.66-1.078 1.541-1.954 2.498-2.645a11.504 11.504 0 0 1 8.087-2.051c3.01.369 5.008 1.79 6.45 3.853.69.99 1.248 2.174 1.62 3.524.374 1.352.378 3.098-.05 4.53-1.383-.283-2.637.15-3.125 1.026-.004.015-.016.017-.016.033.498-.678 1.736-1.168 2.976-.86.328.082.746.2.908.43.224.317.122.989-.016 1.373zM16.22 9.382c.055.383.227.783.445.944.376.27.602.001.63-.38.035-.504-.174-1.1-.431-1.324-.105-.09-.299-.145-.412-.115-.256.065-.283.528-.232.875zm-8.649 1.092c-.033.556.16 1.277.529 1.472.43.227.633-.095.661-.495.045-.626-.273-1.714-.86-1.605-.25.047-.313.339-.33.628zm6.83 2.579c-.266.06-.633-.058-.926-.117a22.333 22.333 0 0 0-.91-.164c-.567-.088-1.344-.211-1.9.1-.198.11-.444.351-.465.662-.027.46.342.791.612.993.323.237.663.399 1.092.527.917.278 2.293.353 3.075.017.735-.316 1.706-1.062 1.72-2.05.01-.59-.272-1.119-.859-1.042-.65.085-.882.951-1.44 1.074z",
165 | [0.17800000000000005, -0.00016249501660449533, 23.82066666666666, 24],
166 | // svg-path-bounding-box: [ 0.17800000000000005, -0.00016249501660449477, 23.82066666666666, 24 ]
167 | ],
168 |
169 | // https://github.com/mondeja/svg-path-bbox/issues/112
170 | ["M90 100Q109 100 110 110L100 90", [90, 90, 110, 110]],
171 | ["M90 100Q101 100 110 110L100 90", [90, 90, 110, 110]],
172 | ["M90 100Q90 110 110 110L100 90", [90, 90, 110, 110]],
173 | // svg-path-bounding-box: [ 90, 90, 110, 110 ]
174 | ];
175 |
176 | export default [...linealCases, ...pathologicalCases];
177 |
--------------------------------------------------------------------------------
/tests/changelog.test.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "node:fs";
2 |
3 | test("Current version has a CHANGELOG entry", () => {
4 | const changelog = fs.readFileSync("CHANGELOG.md", "utf8");
5 | const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8"));
6 |
7 | const changelogEntry = changelog
8 | .split("\n")
9 | .find((line: string) => line.startsWith(`## [${packageJson.version}]`));
10 | expect(changelogEntry).toBeDefined();
11 | });
12 |
--------------------------------------------------------------------------------
/tests/cli.test.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "node:fs";
2 | import * as path from "node:path";
3 |
4 | import syscall from "./utils/syscall";
5 | import type { SysCallOutput, SysCallArgs } from "./utils/syscall";
6 |
7 | const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8"));
8 |
9 | const cliPath = path.resolve("src", "cli.cjs");
10 |
11 | type CliCases = [SysCallArgs, 0 | 1, string, string][];
12 |
13 | const cliParameterCases: CliCases = [
14 | [['"M5 10c3 0 3 3 0 3z"'], 0, "5 10 7.25 13\n", ""],
15 | [['"M5 10c3 0 3 3 0 3z"', '"M2 8m5 5z"'], 0, "5 10 7.25 13\n2 8 7 13\n", ""],
16 | [
17 | [],
18 | 1,
19 | "",
20 | "You must pass SVG paths enclosed between quotes as parameters.\n",
21 | ],
22 | [["--version"], 1, `^${packageJson.version}\n$`, ""],
23 | [["-v"], 1, `^${packageJson.version}\n$`, ""],
24 | [["--help"], 1, "", `^${packageJson.description}`],
25 | [["-h"], 1, "", `^${packageJson.description}`],
26 | ];
27 |
28 | describe("svg-path-bbox", () => {
29 | test.each(cliParameterCases)(
30 | "svg-path-bbox %p [CODE: %p | STDOUT: %p | STDERR: %p]",
31 | async (args, code, stdout, stderr) => {
32 | args.unshift(cliPath);
33 | const proc = (await syscall(args)) as SysCallOutput;
34 | expect(proc.code).toBe(code);
35 | expect(new RegExp(stdout).test(proc.stdout)).toEqual(true);
36 | expect(new RegExp(stderr).test(proc.stderr)).toEqual(true);
37 | }
38 | );
39 | });
40 |
--------------------------------------------------------------------------------
/tests/examples.test.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "node:fs";
2 | import * as path from "node:path";
3 |
4 | import syscall from "./utils/syscall";
5 | import type { SysCallOutput, SysCallProgram } from "./utils/syscall";
6 |
7 | const examplesDirname = "examples";
8 | const expectedOutput = "[ 0, 0, 3, 6 ]\n";
9 |
10 | type ExamplesCases = [string, SysCallProgram][];
11 |
12 | const examplesCases: ExamplesCases = fs
13 | .readdirSync(examplesDirname)
14 | .map((fname) => [fname, fname.endsWith(".ts") ? "ts-node" : "node"]);
15 |
16 | describe("svg-path-bbox/examples", () => {
17 | test.each(examplesCases)("svg-path-bbox %p [%p]", async (fname, program) => {
18 | const proc = (await syscall(
19 | [path.resolve(examplesDirname, fname)],
20 | program
21 | )) as SysCallOutput;
22 | expect(proc.code).toBe(0);
23 | expect(proc.stdout).toEqual(expectedOutput);
24 | expect(proc.stderr).toEqual("");
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/tests/optimization.test.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "node:fs";
2 | import { getSimpleIconsSegmentsStats } from "../scripts/get-simple-icons-segments-stats";
3 |
4 | test("Segments parsing order switch is optimized", () => {
5 | // We use simple-icons data to compute segment stats
6 | const indexTs = fs.readFileSync("src/index.ts", "utf8");
7 | const siSegmentsByOccurrence = getSimpleIconsSegmentsStats().map(
8 | ([seg]: [string, number]) => seg
9 | );
10 |
11 | const swithCasesSegmentByLines = indexTs
12 | .split("\n")
13 | .map((line) => {
14 | if (line.startsWith(" case ")) {
15 | return line.split('"')[1];
16 | }
17 | return null;
18 | })
19 | .filter((occ) => occ !== null);
20 |
21 | expect(siSegmentsByOccurrence).toStrictEqual(swithCasesSegmentByLines);
22 | });
23 |
--------------------------------------------------------------------------------
/tests/utils/syscall.ts:
--------------------------------------------------------------------------------
1 | import { exec } from "node:child_process";
2 |
3 | export type SysCallArgs = string[];
4 | export type SysCallOutput = { code: number; stdout: string; stderr: string };
5 | export type SysCallProgram = "node" | "ts-node";
6 |
7 | export default (args: SysCallArgs, program: SysCallProgram = "node") =>
8 | new Promise((resolve, reject) => {
9 | const child = exec(`${program} ${args.join(" ")}`);
10 |
11 | const stdoutChunks: string[] = [];
12 | const stderrChunks: string[] = [];
13 | let stdout: string;
14 | let stderr: string;
15 | child.on("exit", (code: number) => {
16 | resolve({ code, stdout, stderr });
17 | });
18 |
19 | if (child.stdout === null) {
20 | return reject("'stdout' object of ChildProcess is null");
21 | }
22 | child.stdout.on("data", (data: string) => {
23 | stdoutChunks.push(data);
24 | });
25 | child.stdout.on("end", () => {
26 | stdout = stdoutChunks.join("");
27 | });
28 |
29 | if (child.stderr === null) {
30 | return reject("'stderr' object of ChildProcess is null");
31 | }
32 | child.stderr.on("data", (data: string) => {
33 | stderrChunks.push(data);
34 | });
35 | child.stderr.on("end", () => {
36 | stderr = stderrChunks.join("");
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "moduleResolution": "node",
5 | "strict": true,
6 | "outDir": "dist",
7 | "skipLibCheck": true
8 | },
9 | "include": ["./src/index.ts"]
10 | }
11 |
--------------------------------------------------------------------------------