Trim Decimal Places from Numbers in a Text Editor - svg

I have a set of SVG definition exported from Inkspace and as anyone who has done this themselves will know the resulting definition often contains far more accuracy that is actually justified. For example for a 32 x32 icon definition shown below, Inkspace has generated 6 (or more!) decimal places of accuracy per pixel. Now I know that means it will scale but in my case I would rather save space in my app which requires input of hundreds of such icon definitions.
<svg width="32" height="32"><path d="M 14.576172 5.453125 L 13.78125 8.8222656 L 12.464844 9.3691406 L 9.5625 7.5898438 L 7.5898438 9.5351562 L 9.3691406 12.576172 L 8.8222656 13.78125 L 5.4785156 14.630859 L 5.4785156 17.369141 L 8.8496094 18.246094 L 9.3691406 19.507812 L 7.5625 22.464844 L 9.5351562 24.410156 L 12.548828 22.658203 L 13.808594 23.177734 L 14.603516 26.494141 L 17.396484 26.521484 L 18.191406 23.177734 L 19.505859 22.658203 L 22.410156 24.410156 L 24.355469 22.410156 L 22.658203 19.451172 L 23.150391 18.191406 L 26.492188 17.369141 L 26.492188 14.630859 L 23.123047 13.726562 L 22.630859 12.521484 L 24.410156 9.5351562 L 22.410156 7.5898438 L 19.451172 9.3417969 L 18.246094 8.8222656 L 17.396484 5.453125 L 14.576172 5.453125 z M 15.984375 12.082031 A 3.9078221 3.9078221 0 0 1 19.892578 15.988281 A 3.9078221 3.9078221 0 0 1 15.984375 19.896484 A 3.9078221 3.9078221 0 0 1 12.078125 15.988281 A 3.9078221 3.9078221 0 0 1 15.984375 12.082031 Z" fill="#f9f9f9" /></svg>
So my question is how can I trim the definitions using a text editor to say 2 decimal places.
I have found that by using TextWrangler with the following grep search and replace string
(\.[0-9][0-9])([0-9][0-9][0-9][0-9][0-9][0-9])
\1
that I can search for all 6 decimal places strings and replace them with 2 decimal places. Then I can change the search and replace to
(\.[0-9][0-9])([0-9][0-9][0-9][0-9][0-9])
\1
and then to
(\.[0-9][0-9])([0-9][0-9][0-9][0-9])
\1
and finally to
(\.[0-9][0-9])([0-9][0-9][0-9])
\1
and this works.
However if someone with slightly (or a lot) more brain pixels than me, could show me how to do all of the above with just one search and replace operation, I would be very happy.

To remove the unwanted precision from the (bash or shell) command line, try:
sed -E 's/([[:digit:]]\.[[:digit:]]{2})[[:digit:]]+/\1/g' file.svg
To change the file in place (Linux),
sed -i.bak -E 's/([[:digit:]]\.[[:digit:]]{2})[[:digit:]]+/\1/g' file.svg
Or OSX:
sed -i .bak -E 's/([[:digit:]]\.[[:digit:]]{2})[[:digit:]]+/\1/g' file.svg
How it works
This looks for any place where there is a digit, followed by a period, followed by two digits, followed by some number of more digits:
([[:digit:]]\.[[:digit:]]{2})[[:digit:]]+
The first digit, period, and two following digits are in parens. This saves them to group 1 which we use as the replacement text.
Example
Your input file:
$ cat >file.svg
<svg width="32" height="32"><path d="M 14.576172 5.453125 L 13.78125 8.8222656 L 12.464844 9.3691406 L 9.5625 7.5898438 L 7.5898438 9.5351562 L 9.3691406 12.576172 L 8.8222656 13.78125 L 5.4785156 14.630859 L 5.4785156 17.369141 L 8.8496094 18.246094 L 9.3691406 19.507812 L 7.5625 22.464844 L 9.5351562 24.410156 L 12.548828 22.658203 L 13.808594 23.177734 L 14.603516 26.494141 L 17.396484 26.521484 L 18.191406 23.177734 L 19.505859 22.658203 L 22.410156 24.410156 L 24.355469 22.410156 L 22.658203 19.451172 L 23.150391 18.191406 L 26.492188 17.369141 L 26.492188 14.630859 L 23.123047 13.726562 L 22.630859 12.521484 L 24.410156 9.5351562 L 22.410156 7.5898438 L 19.451172 9.3417969 L 18.246094 8.8222656 L 17.396484 5.453125 L 14.576172 5.453125 z M 15.984375 12.082031 A 3.9078221 3.9078221 0 0 1 19.892578 15.988281 A 3.9078221 3.9078221 0 0 1 15.984375 19.896484 A 3.9078221 3.9078221 0 0 1 12.078125 15.988281 A 3.9078221 3.9078221 0 0 1 15.984375 12.082031 Z" fill="#f9f9f9" /></svg>
Our command:
$ sed -E 's/([[:digit:]]\.[[:digit:]]{2})[[:digit:]]+/\1/g' file.svg
<svg width="32" height="32"><path d="M 14.57 5.45 L 13.78 8.82 L 12.46 9.36 L 9.56 7.58 L 7.58 9.53 L 9.36 12.57 L 8.82 13.78 L 5.47 14.63 L 5.47 17.36 L 8.84 18.24 L 9.36 19.50 L 7.56 22.46 L 9.53 24.41 L 12.54 22.65 L 13.80 23.17 L 14.60 26.49 L 17.39 26.52 L 18.19 23.17 L 19.50 22.65 L 22.41 24.41 L 24.35 22.41 L 22.65 19.45 L 23.15 18.19 L 26.49 17.36 L 26.49 14.63 L 23.12 13.72 L 22.63 12.52 L 24.41 9.53 L 22.41 7.58 L 19.45 9.34 L 18.24 8.82 L 17.39 5.45 L 14.57 5.45 z M 15.98 12.08 A 3.90 3.90 0 0 1 19.89 15.98 A 3.90 3.90 0 0 1 15.98 19.89 A 3.90 3.90 0 0 1 12.07 15.98 A 3.90 3.90 0 0 1 15.98 12.08 Z" fill="#f9f9f9" /></svg>

I'm very helpful in optimizing svg files, drawn in vector editors,
a utility made by Peter Collingridge - SVG Editor
The interface is intuitive, almost immediately you start working.
The results are good:
Reduces the size of files almost several times
Removes all the service information left after Inkscape
There is a choice of the number of decimal digits

Set precision in Inkscape:
Newer version: Menubar | Edit | Preferences | Input/Output | SVG Output | Numbers
Older version: Menubar | Edit | Inkscape Preferences | SVG Output | Numbers

To trim the definitions using a text editor to x decimal places you can simply use the Regular Expression Search Mode of Notepad++ :
Open Find and Replace With Ctrl+H
Change the search mode to Regular Expression
Put (\d+\.\d{x})\d* in the Find What ( Replace x by the number of digits that you want)
Put \1 in Replace With
Press Replace All
Result With your Example :
Before :
After :
You can download the latest version of Notepad++ here.

Using a RegEx approach that cuts all numbers to two decimal places is pretty naive. Not all decimals are the same. Consider for example the following matrix transform:
transform="matrix(0.999848,0.017452,-0.034905,0.999543,-4.0838,-3.8401)"
Cutting these numbers to two decimal places changes the second number by 41%. Is that significant? Yes, in fact you can use it for a nice embossing effect:
<svg viewBox="0 0 20 20" width="100" height="100">
<text x="7" y="22" style="font-size:20;fill:#000000"
transform="matrix(0.999848,0.017452,-0.034905,0.999543,-4.0838,-3.8401)">A</text>
<text x="7" y="22" style="font-size:20;fill:#ffffff;opacity:0.8"
transform="matrix(0.99,0.01,-0.03,0.99,-4.08,-3.84)">A</text>
</svg>
This is why Inkscape uses the term "precision" in its settings and rounds to a fixed number of significant digits (123.4, 0.01234,...)
The second problem is that you might have a scaling transformation somewhere in your SVG. Did you see in the above snippet that the given size of the rectangle is scaled with a factor 5? It is implicit in the viewBox, ẁidth and height attributes. What if the scaling had been not 5, but 1000? And yes, I have seen these kind of relations in the wild. Especially if the grafic has been produced with Inkscape.
So: handle with care.

The answer depends how you insert the content into your files.
If you manually copy the a .svg file's content and have to manually use the steps you did above to minify the HTML, then there's definitely a better way: store your main .svgs in one folder called svgs, and call a script that minifies those .svgs into another folder called svgsminified. That way everytime you manually copy the markup, you would use the minified version in svgsminified folder.
If your IDE adds the .svg file's markup for you, then hopefully there's a scripting function in the IDE that allows you to call an external script to pre-minify the incoming <svg> markup .
If you're using Powershell (which works on *nix and Windows), a minifying script to minify all .svgs in a folder would look something like:
# All .svgs in $svg_dir will be minified into $svg_minified_dir
$svg_dir = 'D:/svg'
$svg_minified_dir = 'D:/svgs_minified'
Get-ChildItem $svg_dir -Filter *.svg | ForEach-Object {
$svg_content = Get-Content $_.FullName -Raw
$svg_minified = $svg_content -replace '(\.[0-9]{2})([0-9]{1,5})', '${1}'
$svg_minified.Trim() | Out-File "$svg_minified_dir/$($_.Name)" -Encoding utf8 -NoNewline
}

Given an svg with :
<path id="hair" d="m208 14.2c-61.3 0-111 49.7-111 111 0 61.3 49.7 111 111 111 61.3 0 111-49.7 111-111-0.1-61.2-49.7-111-111-111z"/>
To round to closest integer, use :
perl -i -pe 's/(\d*\.\d*)/int($1+0.5)/ge' file
Results:
<path id="hair" d="m208 14c-61 0-111 50-111 111 0 61 50 111 111 111 61 0 111-50 111-111-0-61-50-111-111-111z"/>
Thanks to https://askubuntu.com/a/601190/158008

Related

Fill color is not applied to the SVG path

To draw some shape based on the data, I am using the SVG path, but with a particular height and width, fill color is not applied to the path element properly. Just copied my path direction and created a sample below, if you change the width for SVG, the path is filled properly
<svg height="450" width="450">
<path d="M 0 118.65730685696555 L 0 0 L 46.4375 335.9090909090909 L 92.875 0 L 139.3125 0 L 185.75 0 L 278.625 0 L 325.0625 0 L 371.5 0 L 371.5 12.864603481624718 L 325.0625 13.465478183788035 L 278.625 28.056480133139154 L 185.75 64.31377161001998 L 139.3125 130.11162255466053 L 92.875 335.9090909090909 L 46.4375 335.9090909090909 L 0 118.65730685696555" />
</svg>
what will be the issue, how to fix this?
If you tweak the path just slightly so that the two points at the bottom left aren't coincident, Chrome can properly fill it.
<svg height="450" width="450">
<path d="M 0 118.65730685696555 L 0 0 L 46.4375 335.9090909090909 L 92.875 0 L 139.3125 0 L 185.75 0 L 278.625 0 L 325.0625 0 L 371.5 0 L 371.5 12.864603481624718 L 325.0625 13.465478183788035 L 278.625 28.056480133139154 L 185.75 64.31377161001998 L 139.3125 130.11162255466053 L 92.875 335.87 L 46.4375 335.9090909090909 L 0 118.65730685696555" />
</svg>

SVG - How to get the total no of subpaths in SVGPathElement using Batik libraries?

For example in following case, there are two nested drawings, I want to calculate the area of the larger path and ignore the other.
path style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.0343547;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0"
d="M 207.87305 372.2832 L 207.87305 750.23633 L 585.82617 750.23633 L 585.82617 372.2832 L 207.87305 372.2832 z M 302.36133 466.77148 L 491.33789 466.77148 L 491.33789 655.74805 L 302.36133 655.74805 L 302.36133 466.77148 z " transform="matrix(0.9375,0,0,0.9375,0,5.0439605e-6)" id="path4562" />

how to split one path into two paths in svg

I'm very new to svg syntax and I want to know how I can split a path into two paths. actually I have something like this:
M Xm Ym ... C Xc1 Yc1 Xc2 Yc2 Xc3 Yc3 (*) C Xd1 Yd1 Xd2 Yd2 Xd3 Yd3 C ...
(*) is where I want to split the path
and I want to convert it to two paths like this:
M Am Bm ... C Ac1 Bc1 Ac2 Bc2 Ac3 Bc3
and
M An Bn C Ad1 Bd1 Ad2 Bd2 Ad3 Bd3 ...
How to calculate A and B numbers by X and Y nums?
If you can rely on the path commands being absolute (ie capital letters like 'C' rather than 'c'), then it is easy.
M Xm Ym ... C Xc1 Yc1 Xc2 Yc2 Xc3 Yc3 (*) C Xd1 Yd1 Xd2 Yd2 Xd3 Yd3 C ...
would become
M Xm Ym ... C Xc1 Yc1 Xc2 Yc2 Xc3 Yc3
and
M Xc3 Yc3 C Xd1 Yd1 Xd2 Yd2 Xd3 Yd3 C ...
That is, just use the last coordinate pair from the previous path command.
However be aware that, if the path has a fill, splitting it like this may mess up the fill.
If the path has relative path commands (eg. c) - particularly the command before the split - then you will need to do a lot more work. You will need to work out what that last coordinate is in absolute terms before you can use them in the inserted M command.
Example:
<svg width="200" height="200" viewBox="0 0 20 20">
<path transform="translate(10,10)"
d="M -10,0
C -10,-5.5 -5.5,-10 0,-10
C 5.5,-10 10,-5.5 10,0"/>
</svg>
<svg width="200" height="200" viewBox="0 0 20 20">
<path transform="translate(10,10)" fill="red"
d="M -10,0
C -10,-5.5 -5.5,-10 0,-10"/>
<path transform="translate(10,10)" fill="green"
d="M 0,-10
C 5.5,-10 10,-5.5 10,0"/>
</svg>

Trouble creating a closed line chart in svg

I've got a react component which is creating an svg line chart (I'm not using a library, just creating the svg itself).
Problem is, when I add a final point to the path to return to the starting point, I have a strange 45degree angle showing up at the end of the chart.
Can anybody explain why this is not nicely closed?
Here's an example https://jsfiddle.net/7svavrmu/1/
From what I understand, the final L 0 300 should be returning the path to the origin.
Here's the code
<svg width="300" height="67.40652464075235">
<path fill="blue" stroke="black"
d="M 0 40.32613081539207
L 0.15306122448979592 40.990776224724726
L 0.25510204081632654 41.834373941621585
L 0.30612244897959184 62.31225269212592
L 299.0816326530612 45.84534164491692
L 299.33673469387753 65.256033885832
L 299.48979591836735 45.314084715607414
L 300 45.27080004137377 L 0 300 "></path>
</svg>
In SVG paths, each letter is an instruction and the following numbers are the coordinates for that instruction.
Your path ends at a strange location, L 0 300 is the bottom left location but way off the viewport, you need to "draw" the bottom part of your graph by removing the last instruction and adding L 300 67 (bottom right corner) and L 0 67 (bottom left corner). Putting it all together your path needs to look like this:
d="M 0 40.32613081539207
L 0.15306122448979592 40.990776224724726
L 0.25510204081632654 41.834373941621585
L 0.30612244897959184 62.31225269212592
L 299.0816326530612 45.84534164491692
L 299.33673469387753 65.256033885832
L 299.48979591836735 45.314084715607414
L 300 45.27080004137377 L 300 67 L 0 67"

Open document format: meaning of "G" command in drawooo:enhanced-path attribute

I'm writing converter from .odp to .html with some cutomized formatting.
I represent elements as with path in it.
Everything works until there is "drawooo:enhanced-path" attribute, like:
<draw:enhanced-geometry draw:mirror-horizontal="false" draw:mirror-vertical="false" draw:text-areas="?f5 ?f5 ?f6 ?f7" svg:viewBox="0 0 0 0" draw:type="ooxml-roundRect" draw:modifiers="16667" draw:enhanced-path="M 0 ?f2 L ?f3 0 L ?f11 ?f4 L ?f2 ?f10 Z N" drawooo:enhanced-path="M 0 ?f2 G ?f2 ?f2 ?f12 ?f13 L ?f3 0 G ?f2 ?f2 ?f14 ?f15 L ?f11 ?f4 G ?f2 ?f2 ?f16 ?f17 L ?f2 ?f10 G ?f2 ?f2 ?f18 ?f19 Z N">
What causes problems is "G" command, which is not supported inside svg path's "d" attribute.
When I converted the presentation by libreoffice command to .svg, it produced this element:
<path fill="rgb(0,0,0)" stroke="none" d="M 2468,2780 L 2468,2780 2469,2745 2472,2710 2476,2675 2483,2641 2491,2607 2501,2573 2513,2540 2526,2507 2541,2476 2558,2445 2576,2415 2596,2386 2617,2358 2640,2332 2664,2306 2690,2282 2716,2259 2744,2238 2773,2218 2803,2200 2834,2183 2865,2168 2898,2155 2931,2143 2965,2133 2999,2125 3033,2118 3068,2114 3103,2111 3138,2110 7934,2110 7934,2110 7969,2111 8004,2114 8039,2118 8073,2125 8107,2133 8141,2143 8174,2155 8207,2168 8238,2183 8269,2200 8299,2218 8328,2238 8356,2259 8382,2282 8408,2306 8432,2332 8455,2358 8476,2386 8496,2415 8514,2445 8531,2476 8546,2507 8559,2540 8571,2573 8581,2607 8589,2641 8596,2675 8600,2710 8603,2745 8604,2780 8605,5460 8605,5460 8604,5495 8601,5530 8597,5565 8590,5599 8582,5633 8572,5667 8560,5700 8547,5733 8532,5764 8515,5795 8497,5825 8477,5854 8456,5882 8433,5908 8409,5934 8383,5958 8357,5981 8329,6002 8300,6022 8270,6040 8239,6057 8208,6072 8175,6085 8142,6097 8108,6107 8074,6115 8040,6122 8005,6126 7970,6129 7935,6130 3138,6131 3138,6131 3103,6130 3068,6127 3033,6123 2999,6116 2965,6108 2931,6098 2898,6086 2865,6073 2834,6058 2803,6041 2773,6023 2744,6003 2716,5982 2690,5959 2664,5935 2640,5909 2617,5883 2596,5855 2576,5826 2558,5796 2541,5765 2526,5734 2513,5701 2501,5668 2491,5634 2483,5600 2476,5566 2472,5531 2469,5496 2468,5461 2468,2780 Z M 2468,2110 L 2468,2110 Z M 8605,6131 L 8605,6131 Z"/>
Just to illustrate how the final shape is affected by ignoring the "G", see a comparison.
I would like to do a similar conversion, but I cannot find any specification of "G" command anywhere on the internet.
Can you please point me to some specification of it, or better, to some library already doing some kind of conversion?
From ximpcustomshape.cxx in the LibreOffice source code:
case 'G' :
{
nLatestSegmentCommand = css::drawing::EnhancedCustomShapeSegmentCommand::ARCANGLETO;
nParametersNeeded = 2;
nIndex++;
}
This leads us to EnhancedCustomShape2d.cxx which contains the following:
OSL_TRACE("ARCANGLETO scale: %f x %f angles: %f, %f", fWR, fHR, fStartAngle, fSwingAngle);
Apparently the G command takes two pairs of floating point parameters: (fWR, fHR) and (fStartAngle, fSwingAngle).

Resources