GithubHelp home page GithubHelp logo

svgpath's Introduction

svgpath

CI NPM version Coverage Status

Low level toolkit for SVG paths transformations. Sometimes you can't use transform attributes and have to apply changes to svg paths directly. Then this package is for you :) !

Note: this package works with path data strings, not with full svg xml sources.

Install

npm install svgpath

Example

var svgpath = require('svgpath');

var transformed = svgpath(__your_path__)
                    .scale(0.5)
                    .translate(100,200)
                    .rel()
                    .round(1)
                    .toString();

API

All methods are chainable (return self).

new SvgPath(path) -> self

Constructor. Creates new SvgPath class instance with chainable methods. new can be omited.

SvgPath.from(path|SvgPath) -> self

Similar to Array.from(). Creates SvgPath instance from string or another instance (data will be cloned).

.abs() -> self

Converts all path commands to absolute.

.rel() -> self

Converts all path commands to relative. Useful to reduce output size.

.scale(sx [, sy]) -> self

Rescale path (the same as SVG scale transformation). sy = sx by default.

.translate(x [, y]) -> self

Rescale path (the same as SVG translate transformation). y = 0 by default.

.rotate(angle [, rx, ry]) -> self

Rotate path to angle degrees around (rx, ry) point. If rotation center not set, (0, 0) used. The same as SVG rotate transformation.

.skewX(degrees) -> self

Skew path along the X axis by degrees angle.

.skewY(degrees) -> self

Skew path along the Y axis by degrees angle.

.matrix([ m1, m2, m3, m4, m5, m6 ]) -> self

Apply 2x3 affine transform matrix to path. Params - array. The same as SVG matrix transformation.

.transform(string) -> self

Any SVG transform or their combination. For example rotate(90) scale(2,3). The same format, as described in SVG standard for transform attribute.

.unshort() -> self

Converts smooth curves T/t/S/s with "missed" control point to generic curves (Q/q/C/c).

.unarc() -> self

Replaces all arcs with bezier curves.

.toString() -> string

Returns final path string.

.round(precision) -> self

Round all coordinates to given decimal precision. By default round to integer. Useful to reduce resulting output string size.

.iterate(function(segment, index, x, y) [, keepLazyStack]) -> self

Apply iterator to all path segments.

  • Each iterator receives segment, index, x and y params. Where (x, y) - absolute coordinates of segment start point.
  • Iterator can modify current segment directly (return nothing in this case).
  • Iterator can return array of new segments to replace current one ([] means that current segment should be delated).

If second param keepLazyStack set to true, then iterator will not evaluate stacked transforms prior to run. That can be useful to optimize calculations.

Support svgpath

You can support this project via Tidelift subscription.

svgpath's People

Contributors

jamesots avatar kaiido avatar kpym avatar leij1ang avatar lubyshev avatar lvauvillier avatar mathieudutour avatar puzrin avatar rlidwka avatar snb2013 avatar waldyrious avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

svgpath's Issues

support CLI

It would be great if one could just scale is using a command line.

very high cpu usage

I'm using "svgpath" heavily to draw svg in like this.

image

The problem is it uses very high cpu.
Without "svgpath" it usaully costs 10%.
But as soon as I use it, it goes to 40%.
Because I draw 20~30svgs.

Is there any solution? I guess it's very heavy math + a lot of string parsing..

Improve parse speed

String parse speed is narrow place in svg2ttf. Try to replace global regex with more "classic" parser.

Splitting functionality into standalone modules.

Hi there, I'm currently working on a project that needs to use svgpath's scale and translate methods, but nothing else. Would you be interested in splitting them into standalone modules? If so, I'm more than happy to assist. :)

Support transformation units

Is it possible to partially support transformation units such as deg, rad, px? Currently, transform parameters are converted to numbers which fails with units and results in the parameters being zero.

Here's the snippet from transform_parse.js:

    params = item.split(PARAMS_SPLIT_RE).map(function (i) {
      return +i || 0;
    });

I think a potentially easy fix would be to just strip away the units while potentially converting others:

    var UNITS_SPLIT_RE = /\d+/;
    var RAD_TO_DEG = 180 / Math.PI;
    var TWO_PI = Math.PI * 2;

    // ...

    params = item.split(PARAMS_SPLIT_RE).map(function (param) {
      let value = Number(param.match(UNITS_SPLIT_RE)[0]) || 0;
      let unit = param.replace(value, '');

      switch (unit) {
        case 'rad': {
          value = value * RAD_TO_DEG;
          break;
        }
        case 'turn': {
          value = value * TWO_PI * RAD_TO_DEG;
          break;
        }
        case 'in': {
          value = value * 96;
          break;
        }
      }

      return value;
    });

Add pathParse LRU cache

Usage example:

var SvgPath = require('svgpath');
SvgPath.cacheSize = 10000;

Since objects will be modified, cache should return cloned copy of segments (do it fast, via splice)

Support for more modular structure using ES6 modules

Are the any plans to support more modularity for the library, using ES modules? So only the parts of it that are used could be imported using import or require instead of the whole library.
This will help to reduce the size of final Webpack bundle.

As I understand right now all functions like scale, rotate are incapsulated as the parts of the object SvgPath. If each function could return new copy of the object instead of mutating it, that it could make it completely independent so the function could be exported using ES6 export statement and imported lately.

For example:

const {from, scale, round, toString} import from svgpath;

const aPath = from(_some_path); //returns new SVGPath object

const scaled = scale(aPath, 0.5); //returns new SVGPath object
const translated =translate(aPath, 100, 200); //returns new SVGPath object

console.log( toString(scaled) );

Instructions for running locally

Hello, I noticed your README doesn't list how to clone this repository and get the code running locally. Would you be able to share some steps for this?

Should empty arc be dropped ?

I know that you follow the W3C where we can read "If the endpoints (x1, y1) and (x2, y2) are identical, then this is equivalent to omitting the elliptical arc segment entirely.".
But this is dangerous because the browsers don't do this. Check the following paths

  • "M 0 0 C 0 10 10 10 10 0 A 10 10 0 0 0 10 0 S 20 -10 20 0" (with empty arc segment)
  • "M 0 0 C 0 10 10 10 10 0 S 20 -10 20 0" (with empty arc segment removed)
  • "M 0 0 C 0 10 10 10 10 0 L 10 0 S 20 -10 20 0" (with empty line segment)

You can see that the browser treats the empty arc like empty line segment, and don't drop it.
The only situation where dropping arc is dangerous is before short commands (S, s, T,t).

By the way this 'drop empty arcs' rule is inconsistent with the other type of segments. Why to drop empty arcs and not to drop empty lines ?

rel method Supports Typescript

In my typescript project rel method has a problem: Property 'rel' does not exist on type 'SvgPath'.ts(2339), There must be something wrong about rel method.
image
Here is rel method type missing.

How I can create path for mltline text?

How I can create path for multiline text, like below?

<svg width="200" height="200"
    xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 <defs>
  <!-- define lines for text lies on -->
  <path id="path1" d="M10,30 H190 M10,60 H190 M10,90 H190 M10,120 H190"></path>
 </defs>
 <use xlink:href="#path1" x="0" y="35" stroke="blue" stroke-width="1" />
 <text transform="translate(0,35)" fill="red" font-size="20">
  <textPath xlink:href="#path1">This is a long long long text ......</textPath>
 </text>
</svg>

Transforming arc segment is not working properly

The command

svgpath("M0,0 A 10,10 45 0,1 10,10z").transform("scale(1,2)").toString() 

gives the wrong result

"M0 0A10 20 45 0 1 10 20z"

It is obvious that if your ellipse has angle non 0 [mod pi/2] and you scale with two different values, you can't obtain the ellipse with the same angle.
You can check the result by comparing this resulting path with the transform made by the browser :

<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -10 40 40">
  <!-- the original path -->
  <path transform="scale(1,2)" d="M0,0 A 10,10 45 0,1 10,10" style="fill:none;stroke-width:.1;stroke:blue"/>
  <!-- the path transformed by svgpath -->
  <path transform="" d="M 0 0 A 10 20 45 0 1 10 20" style="fill:none;stroke-width:.1;stroke:red"/>
</svg>

arc_transform_test

Example broken: "Object โ€ฆ has no method 'toFixed'"

Maybe I am doing something completly wring, but when trying to run the example code from the README, I got the following error:

test.js:9
.toFixed(1) // Here the real rounding happens
 ^
TypeError: Object M157.5 211L172.6 241.6 206.35 246.5 181.9 270.3 187.65 303.95 157.5 288.1 127.3 304 133.05 270.35 108.65 246.55 142.4 241.65 157.5 211Z has no method 'toFixed'
    at Object.<anonymous> (/Users/ma/CODE/svgpath_test/test.js:9:2)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)
    at node.js:902:3

On quick inspection the chaining seems to be not working as advertised.

complete example (as I said from README, I jus added a random path):

var SvgPath = require('svgpath');

var pathData = "M115,22l30.2,61.2l67.5,9.8l-48.9,47.6l11.5,67.3L115,176.2 L54.6,208l11.5-67.3L17.3,93.1l67.5-9.8L115,22z"

var transformedPath = new SvgPath(pathData)
.scale(0.5)
.translate(100,200)
.abs()
.toFixed(1) // Here the real rounding happens
.rel()
.toFixed(1) // Fix js floating point error/garbage after rel()
.toString()

Support for open/closed path

It would be great to have the ability to detect if the path is open/closed in svgpath.

open-closed

stroke="#9999CC" fill="none" stroke="#9999CC" fill="none"
d="M5 8.5, 13.2c0, 79.9, 91.8, 79.9, 91.8, 159.7 Z" d="M 58.5, 13.2c0, 79.9, 91.8, 79.9, 91.8, 159.7 "

Fore example:

const path = svgpath(__your_path__)
path.isOpen() // true or false

And corresponded methods for opening/closing the path.

const path = svgpath(__your_path__)
path.close()
path.open()

Support for reversing the path

Are there any plans to have support for reversing the path?

const path = svgpath(__your_path__)
const reversed = path.reverse();
Direct Path Inverse Path
d="M50,300 L50,250 C50,150 75,150 100,250 C150,450 200,450 200,250 Q200,100 400,100" d="M400,100 Q200,100 200,250 C200,450 150,450 100,250 C75,150 50,150 50,250 L50,300"

Should comma be treated as optional separator?

As per the SVG spec with regards to separator characters, commas should be treated as optional:

Superfluous white space and separators such as commas can be eliminated (e.g., "M 100 100 L 200 200" contains unnecessary spaces and could be expressed more compactly as "M100 100L200 200").

SVGPath, however, chokes on a path such as:
M50 0 L10.908425876598514 18.82550990706332, 1.2536043909088193 61.12604669781571, 28.305813044122086 95.04844339512096, 71.6941869558779 95.04844339512096, 98.74639560909117 61.12604669781573, 89.0915741234015 18.82550990706333, 50.000000000000014 0, Z
(The problem seems to be the last part, 0, Z)

Is there any reason why the comma imposes restrictions on the syntax? One comment reads: // Stop on next segment (but after comma next param is mandatory)

Differentiate between relative chained and isolated command in parser

Hi. First of all, thank you very much for this utility, it's awesome!

In simple-icons we are using svgpath a lot to lint paths. I was wondering if it might be possible to differentiate between a chained relative segment and an isolated one. For example, the set of chained segments l-.014-.076-.01-.04-.012-.048 are parsed as three segments, and there's no way to tell them apart from l-.014-.076l-.01-.04l-.012-.048. With this functionality we could be able to show more accurate error messages in the linter.

feature request: scale but keep line's width

e.g. i have a font's svg path define:

<glyph glyph-name="uni4E38" unicode="&#x4e38;"
d="M855 1163v-78q0 -222 -31 -409q211 -157 211 -258q0 -64 -53 -64q-30 0 -66 57q-44 70 -116 150q-108 -432 -502 -706l-35 43q345 282 438 759q-107 97 -254 189l27 43q112 -56 241 -139q21 149 21 329v74h-458l-23 51h481v481q203 -3 203 -49q0 -25 -84 -53v-369h436
l76 96q125 -100 125 -127q0 -12.5508 -17 -24l-49 -33v-1054q0 -59 90 -59q97 0 111 61q30 126 43 379l55 -23q-2 -92 -2 -219q0 -108 16 -143q14 -32 54 -29q-3 -93 -64 -117q-50 -20 -207 -20q-138 0 -176 29q-39 23 -39 108v1124h-452z" />

i want to scale the path data, but keep the line 's width (Horizontal and vertical)

Inconsistent transform functions

I noticed using transform functions in other order different to the spec, the script crashes at line 296, something related to toFixed(). Here is a regular use case:

var SvgPath = require(svgpath);
var pathData = 'ANY_VALID_PATH_DATA';

var newData = new SvgPath(pathData).scale(1,1.5).translate(0,150).round(3).toString();

Just to be clear, I have some knowledge on SVG, as you will know in a sec, I'm going to try and briefly explain my best what's wrong and hopefully offer a solution.

According to the spec, in short, the SVG coordinate system expects us to compute path/transform coordinates in a specific order, translate, rotate (rotate/skew), scale, despite some people who would argue otherwise. I mean we all want to have full control over these coordinates despite the severe inconsistencies with CSS and other specific issues, we all want to have consistent coordinate values.

Only when working with the transform attribute it's actually recommended to use translate after rotation for instance when you want to rotate a shape around a certain coordinate (transform origin), take for instance rotate the shape around the center of the parent SVG itself.

<svg viewBox="0 0 100 100">
  <path d="SOME_VALID_PATH" transform="translate(50,50) rotate(45) translate(-50,-50)">
</svg>

Back to our issue, I would suggest to advise developers with your documentation to always use methods in this order: 1 translate, 2a rotate and/or 2b skew, 3 scale, 4 round, 5 toString.

As for you, I suggest to focus on consistency. Allow the transform methods to be used in any order, but build an object to store transform functions and compute them always in the order I mentioned above. I mean what's the point of using toString/round in the middle?

Have a look at my SVG plugin, see how I apply transform functions in animation and see how consistent looks in the demo. As you can imagine I've updated the code in my development (with transform functions order) and I never gotten any crashes.

Let me know if you like what you see, if I find some time I can give a hand.

New project starting from your library

I just came to push a big rewrite of your library to my repo.

First I want to thank you for your work and very good code.

I slightly renamed my project to SVGPath(y).

The final goal is to create a gh-pages with a GUI for this library ... some day ;)

method to reduce a/A commands to c/C

I have a renderer that understands cubic and quadratic beziers, but not elliptical arcs. It would be convenient if svgpath had a method to convert elliptical arc commands to equivalent cubic bezier commands.

Is is possible to split segments

Hi there.

I'm working on a new component for morphing SVGs and was wandering if your script can split segments somehow? Let me explain.

I am currently using some modified code from Raphael.js called path2curve which converts path commands to cubic-bezier, then replicates some of them in order to make both startShape and endShape have same amount of cubic-bezier points, but that's not always best in terms of visuals.

I need something that can normalize path commands by splitting long segments (in terms of length) into 2/more segments so that they're evenly distributed along both paths, and in the end have same number of points in both shapes.

Is this possible with your library?

Feature request for unshort

I would expect the "unshort" function to also replace occurrences of "V" and "H" with L.
If this is not intended, a sperate function for this would be nice.

I would submit a PR for this if you'd accept it.

Bug in iterate function?

It looks to me like the iterate function contains a bug. If one returns a segment to replace the old one from an Iterator function, every member of the new segment array is added individually to the segments array instead of adding it as a whole:

  newSegments = [];

  for (i = 0; i < segments.length; i++) {
    if (typeof replacements[i] !== 'undefined') {
      for (j = 0; j < replacements[i].length; j++) {
        newSegments.push(replacements[i][j]);
      }
    } else {
      newSegments.push(segments[i]);
    }
  }

  this.segments = newSegments;

Should instead be:

  newSegments = [];

  for (i = 0; i < segments.length; i++) {
    if (typeof replacements[i] !== 'undefined') {
      newSegments.push(replacements[i]);
    } else {
      newSegments.push(segments[i]);
    }
  }

  this.segments = newSegments;

in my opinion.

Is it possible to have the pair of transformed coordinates returned as a dataset

Is it possible for svgpath to have the pair of transformed segments returned in an array of objects? For example,

<path d="M650,300L650,150L640,150L640,300" fill="none" stroke="red"></path>

const d = document.querySelector('path').getAttribute('d');     
const path = svgpath(d)
        .matrix([0.707107, 0.707107, -0.707107, 0.707107, 402.513, 371.751])
        .toString();  

console.log(path);

The above would return M650.00045 300.00065000000006L756.0664999999999 193.93460000000005 748.9954299999999 186.86353000000008 642.92938 292.9295800000001.

But I was wondering if there is a method that could return the transformed coordinates in an array of objects like

[{650.00045, 300.00065000000006},{756.0664999999999, 193.93460000000005},{748.9954299999999, 186.86353000000008},{642.92938, 292.9295800000001}]

What are paths?

Hi!

This library seems to do what I want to do, but I can't figure out how to use it, since I don't know what paths are in this case. There is a note on this in the README, and I think a brief explanation on what is actually is would be very helpful.

Thanks!

Shorthand notation ends up in missing parameters

The following path M11.467 3.727c.289.189.37.576.181.865l-4.25 6.5a.625.625 0 01-.943.12l-2.75-2.5a.625.625 0 01.84-.925l2.208 2.007 3.849-5.886a.625.625 0 01.865-.181z gives a parsing error SvgPath: numbers started with '0' such as '09' are ilegal (at pos 59) while being correctly rendered by browsers.

a.625.625 0 01.84-.925 is supposed to be parsed as a 0.625 0.625 0 0 1 0.84 -0.925. Notice how 01. become 0 1 0.. Instead svgpath parses it as a 0.625 0.625 0 1.84 -0.925, and so misses 2 params.

Parsing errors not detected

I found two type of parse errors that the parser do not detect :

  • if their are more parameters than the minimal number, but not enough to complete the next segment.
  • if the number is badly formatted like .e3, it is parsed silently to NaN.

svgpath('M 0 0 0 H 0 .e3 -.e3').toString(); => 'M0 0H0 NaN NaN'

Translate doesn't work for relative paths made with .rel

If one attempts to translate a path and calls .rel nothing will happen.

testPath = "m 70, 70 l 20, 20 l -20, 0 l 0, -20"
svgpath(testPath).translate(100, 100).toString()
// "M170 170l20 20-20 0 0-20" - as expected
svgpath(testPath).translate(100, 100).rel().toString()
// "m70 70l20 20-20 0 0-20" - now.. that one is not translated
svgpath( svgpath(testPath).rel().toString() ).translate(100,100).toString()
// "M170 170l20 20-20 0 0-20" - as expected

Run in Browser. No Node

Since I use this script on a "normal web", not node js. ??
It's a small svg editor, which runs 100% in the client's browser.
Something like..

<script type="module" src="svgpath-master/lib/svgpath.js"></script>

But that obviously doesn't work

Support for calculating bounding boxes

Hi there,
Are there any plans to have support for calculating bounding boxes, similar to SVGGraphicsElement.getBBox(), except without rendering?
Here is an example:

var path = svgpath(__your_path__);
const {x, y, width, hight} = path.getBbox()

I'm using different library to calculate it for now, SnapSVG, but I would like to use svgpath for everething, because it looks cleaner and smaller.

Browser support

This project is currently unable to run in browser due to the unicode variable names in the a2c.js file. Browsers like the latest chrome are complaining about unknown/unidentified tokens in the source code as a result of this.

Can we rewrite this to plain ol english so this project compiles in browsers?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.