0

Input is below:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800pt" height="600pt" viewBox="0 0 800 600">
    <g enable-background="new">
    <path style="fill:none;stroke-width:0.074;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(50.19989%,50.19989%,50.19989%);stroke-opacity:1;stroke-miterlimit:10;" d="M 110.949219 265.421875 L 109.730469 265.691406 L 108.699219 266.410156 L 108.011719 267.460938 L 107.78125 268.679688 " transform="matrix(1,0,0,-1,0,600)"/>
    <path style="fill:none;stroke-width:0.074;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(50.19989%,50.19989%,50.19989%);stroke-opacity:1;stroke-miterlimit:10;" d="M 111.050781 268.679688 L 111.050781 265.421875 " transform="matrix(1,0,0,-1,0,600)"/>
    <path style="fill:none;stroke-width:0.074;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(50.19989%,50.19989%,50.19989%);stroke-opacity:1;stroke-miterlimit:10;" d="M 110.949219 268.679688 L 110.949219 265.421875 " transform="matrix(1,0,0,-1,0,600)"/>
      </g>
    </svg>

Expected Output is below:

    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800pt" height="600pt" viewBox="0 0 800 600">
        <g enable-background="new">
        <path transform="matrix(1,0,0,-1,0,600)" stroke-width="0.074" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="#929292" d="M 110.95 265.42 L 109.73 265.69 L 108.7 266.41 L 108.01 267.46 L 107.78 268.68 "/>
        <path transform="matrix(1,0,0,-1,0,600)" stroke-width="0.074" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="#929292" d="M 111.05 268.68 L 111.05 265.42 "/>
        <path transform="matrix(1,0,0,-1,0,600)" stroke-width="0.074" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="#929292" d="M 110.95 268.68 L 110.95 265.42 "/>
          </g>
        </svg>

This is about simplifying and beautifying SVG path. I would like the expected output to have the elements of path transform, stroke-width, stroke-linecap, stroke-linejoin, fill stroke and d (rounded to 2 decimals for simplicity).

I have tried let svg_object_alt=document.querySelector('svg').outerHTML;, to convert svg as a string to be read and I am not sure how to extract all the information from it, to be formatted to the expected output? I would really appreciate any help I can obtain :)

I think that the stroke-colour can just hardcode the code number (#929292) into it instead of using the rgb colour :)

  • https://stackoverflow.com/questions/10585029/parse-an-html-string-with-js – Brett Donald Feb 20 '23 at 03:02
  • 1
    Your question is actually about **converting style properties to svg attributes** ("simplifying" paths means to reduce the `d` pathData by reducing excessive commands, so changing the geometry. The term "Beautifying" is rather used for formatting: e.g making a highly minyfied code readable by adding spaces/indentations/line breaks) – herrstrietzel Feb 20 '23 at 05:19

2 Answers2

1

You could actually do all these conversions with SVGOMG.
If you need to do this in a custom script:

Working example: style to attributes, round pathData, rgb to hex

let paths = document.querySelectorAll('path');
let decimals = 2;
paths.forEach((path, c) => {

  /**
   * 1. convert inline css style to 
   * svg presentational attributes
   */
  let style = path.style;
  if (style.length) {
    for (let i = 0; i < style.length; i++) {
      let propertyName = style.item(i);
      let value = style.getPropertyValue(propertyName);

      // 1.2 convert rgb colors to hex
      if ((propertyName === 'stroke' || propertyName === 'fill') && value !== 'none') {
        //hexToRgb
        let [r, g, b] = value.replaceAll('rgb(', '').replaceAll(')', '').split(',');
        let colorHex = rgbToHex(r, g, b);
        value = colorHex;
      }
      //set attribute
      path.setAttribute(propertyName, value)

    }
    // remove style attribute
    path.removeAttribute('style')
  }

  /**
   * 1.3 optional: convert transform matrix 
   * to readable properties
   */
  let transform = window.getComputedStyle(path).transform;
  let matrix = path.transform.baseVal[0].matrix;
  let transforms = qrDecomposeMatrix(matrix);
  path.setAttribute('transform', transforms.svgTransform)


  /**
   * 2. round pathdata
   * requires getPathData() polyfill
   */
  roundPath(path, decimals)
  
  // output
  output.value = new XMLSerializer().serializeToString(svg)


})


/**
 * round pathData
 */
function roundPath(path, decimals = 3) {
  let pathData = path.getPathData();
  pathData.forEach((com, c) => {
    if (decimals >= 0) {
      com.values.forEach((val, v) => {
        pathData[c].values[v] = +val.toFixed(decimals);
      });
    }
  });
  // apply rounded
  path.setPathData(pathData)
}



/**
 *  Decompose matrix to readable transform properties 
 *  translate() rotate() scale() etc.
 *  based on @AndreaBogazzi's answer
 *  https://stackoverflow.com/questions/5107134/find-the-rotation-and-skew-of-a-matrix-transformation#32125700
 *  return object with seperate transform properties 
 *  and ready to use css or svg attribute strings
 */
function qrDecomposeMatrix(matrix, precision = 1) {
  let {
    a,
    b,
    c,
    d,
    e,
    f
  } = matrix;
  // matrix is array
  if (Array.isArray(matrix)) {
    [a, b, c, d, e, f] = matrix;
  }

  let angle = Math.atan2(b, a),
    denom = Math.pow(a, 2) + Math.pow(b, 2),
    scaleX = Math.sqrt(denom),
    scaleY = (a * d - c * b) / scaleX,
    skewX = Math.atan2(a * c + b * d, denom) / (Math.PI / 180),
    translateX = e ? e : 0,
    translateY = f ? f : 0,
    rotate = angle ? angle / (Math.PI / 180) : 0;
  let transObj = {
    translateX: translateX,
    translateY: translateY,
    rotate: rotate,
    scaleX: scaleX,
    scaleY: scaleY,
    skewX: skewX,
    skewY: 0
  };

  let cssTransforms = [];
  let svgTransforms = [];
  for (let prop in transObj) {
    transObj[prop] = +parseFloat(transObj[prop]).toFixed(precision);
    let val = transObj[prop];
    let unit = "";
    if (prop == "rotate" || prop == "skewX") {
      unit = "deg";
    }
    if (prop.indexOf("translate") != -1) {
      unit = "px";
    }

    // combine these properties
    let convert = ["scaleX", "scaleY", "translateX", "translateY"];

    if (val !== 0) {
      cssTransforms.push(`${prop}(${val}${unit})`);
    }

    if (convert.indexOf(prop) == -1 && val !== 0) {
      svgTransforms.push(`${prop}(${val})`);
    } else if (prop == "scaleX") {
      svgTransforms.push(
        `scale(${+scaleX.toFixed(precision)} ${+scaleY.toFixed(precision)})`
      );
    } else if (prop == "translateX") {
      svgTransforms.push(
        `translate(${transObj.translateX} ${transObj.translateY})`
      );
    }

  }
  // append css style string to object
  transObj.cssTransform = cssTransforms.join(" ");
  transObj.svgTransform = svgTransforms.join(" ");
  return transObj;
}



/**
 *  based on @Tim Down's answer
 *  https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb#5624139
 */
function rgbToHex(r, g, b) {
  return "#" + (1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1);
}
body {
  padding: 2em;
}

svg {
  border: 1px solid #ccc;
  overflow: visible;
  width: 50%;
}

textarea{
min-height:20em;
width:100%;
}
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.4/path-data-polyfill.min.js"></script>


<svg id="svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="107.7 331.25 3.5 3.5">
        <g>
            <path
                style="fill:none;stroke-width:0.074;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(50.19989%,50.19989%,50.19989%);stroke-opacity:1;stroke-miterlimit:10;"
                d="M 110.949219 265.421875 L 109.730469 265.691406 L 108.699219 266.410156 L 108.011719 267.460938 L 107.78125 268.679688 "
                transform="matrix(1,0,0,-1,0,600)" />
            <path
                style="fill:none;stroke-width:0.074;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(50.19989%,50.19989%,50.19989%);stroke-opacity:1;stroke-miterlimit:10;"
                d="M 111.050781 268.679688 L 111.050781 265.421875 " transform="matrix(1,0,0,-1,0,600)" />
            <path
                style="fill:none;stroke-width:0.074;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(50.19989%,50.19989%,50.19989%);stroke-opacity:1;stroke-miterlimit:10;"
                d="M 110.949219 268.679688 L 110.949219 265.421875 " transform="matrix(1,0,0,-1,0,600)" />
        </g>
    </svg>
    
    
    <textarea id="output"></textarea>

1. Replace inline css style with svg attribute

Get each path's style:

let style = path.style;

Loop through the style list and replace style with attribute

for (let i = 0; i < style.length; i++) {
    let propertyName = style.item(i);
    let value = style.getPropertyValue(propertyName);
    //set attribute
    path.setAttribute(propertyName, value)
}
// remove style attribute
path.removeAttribute('style')

1.2 Color conversion rgb to hex

See SO answer "RGB to hex and hex to RGB"

1.3 Optional: decompose the transform matrix

You might also prefer separate transformation properties like scale(), translate().
Based on @AndreaBogazzi's answer

2. Round pathData

Use a reliable parser like getPathData() polyfill and parse your pathData like so

/**
 * round pathData
 */
 let pathData = path.getPathData();
 roundPath(path, 2)

function roundPath(path, decimals = 3) {
    let pathData = path.getPathData();
    pathData.forEach((com, c) => {
        if (decimals >= 0) {
            com.values.forEach((val, v) => {
                pathData[c].values[v] = +val.toFixed(decimals);
            });
        }
    });
    // apply rounded
    path.setPathData(pathData)
}

Since getPathData() returns an array of objects like this

{type:'L', values: [10 20]}

We can easily round each commands values.

herrstrietzel
  • 11,541
  • 2
  • 12
  • 34
0

you can use that...

const mySvg_Paths = document.querySelectorAll('#my-svg > g > path');
 

for ( let attr of mySvg_Paths[2].attributes ) 
  {
  console.log(attr.name,'-->\n', attr.value);
  }
  
//  or
// console.log( 'outerHTML -->\n', mySvg_Paths[2].outerHTML );
// console.log( 'path.d -->\n', mySvg_Paths[2].getAttribute('d'));
#my-svg {
  width  : 800pt;
  height : 600pt;
  background : lightgreen;
  }
#my-svg path {
  fill              : none;
  stroke-width      : 0.074;
  stroke-linecap    : round;
  stroke-linejoin   : round;
  stroke            : rgb(50.19989%, 50.19989%, 50.19989%);
  stroke-opacity    : 1;
  stroke-miterlimit : 10;
  }
<svg id="my-svg" viewBox="0 0 800 600">
  <g enable-background="new">
    <path
      d="M 110.949219 265.421875 L 109.730469 265.691406 L 108.699219 266.410156 L 108.011719 267.460938 L 107.78125 268.679688 "
      transform="matrix(1,0,0,-1,0,600)"/>
    <path
      d="M 111.050781 268.679688 L 111.050781 265.421875 "
      transform="matrix(1,0,0,-1,0,600)" />
    <path
      style="fill:none;stroke-width:0.074;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(50.19989%,50.19989%,50.19989%);stroke-opacity:1;stroke-miterlimit:10;" 
      d="M 110.949219 268.679688 L 110.949219 265.421875 " 
      transform="matrix(1,0,0,-1,0,600)"/>
  </g>
</svg>

then, you can have a look to -> Use RegEx to parse a string with complicated delimiting

specialy this function:

function parseData(pathData)
  {
  var pieces = pathData.match(/([a-z]+[-.,\d ]*)/gi), i;
  /* now parse each piece into its own array */
  for (i=0; i<pieces.length; i++)
      pieces[i] = pieces[i].match(/([a-z]+|-?[.\d]*\d)/gi);
  return pieces;
  }
Mister Jojo
  • 20,093
  • 6
  • 21
  • 40