0

I`m working on a project. Where ppl can upload there svg and apply some materials and other stuff to svg. Then i want to provide how much it will cost for me to make it from this materials. And for the price formula i need to get total area of all pathes in svg.

I know i can do it with Coral Draw and e-cut plugin. But i need to make it work with javascript or php.

I tryed a lot of libraries but they dont have such functionality.

Also i had an idea to transform pathes to polygon points. And then calculate area of polygon like this:

function area(polygon) {
        let total = 0;
        for (let i = 0; i < polygon.length; i++) {
            const addX = polygon[i][0];
            const addY = polygon[i === polygon.length - 1 ? 0 : i + 1][1];
            const subX = polygon[i === polygon.length - 1 ? 0 : i + 1][0];
            const subY = polygon[i][1];
            total += (addX * addY * 0.5) - (subX * subY * 0.5);
        }
        return Math.abs(total);
    }

I have the result. But its totaly differnt from e-cut plugin. That i think is working 100% right.

Maybe someone have another ideas how can i calculate total area? Thank you

  • https://stackoverflow.com/questions/67873157/scaling-the-filling-portion-of-an-svg-path/68082501#68082501 fills a polygon by percentage. So if you add an enclosing square(polygon) and subtract the two, can you work out the area? – Danny '365CSI' Engelman Jul 04 '23 at 13:31
  • Thank you. I`ll look at this. Just the problem that polygon is the last option to work with. Ofcourse this is the only option for now. But maybe someone have any other ideas. Cos When i trying to create svg from polygon points with snap.js its looks not quite the same =( – Alexandr Manin Jul 04 '23 at 14:09

1 Answers1

1

You might combine different approaches from this post
"How can I calculate the area of a bezier curve?"

Area calculation javaScript helper

I came up with this helper function which works like this

  1. query all geometry elements in a svg
  2. calculate each element's area according to it's type:
    2.1 if it's a primitive shape like a circle we can apply a simple formula like π * r² directly
    2.2 if it's a <path> we slice the shape into curve segments and the remaining polygon

The bezier segments' areas can be calculated with this function

function getBezierArea(coords) {
  // preceding command's final point
  let x0 = coords[0];
  let y0 = coords[1];
  //if is cubic command
  if (coords.length == 8) {
    // 1st bézier control point
    let x1 = coords[2];
    let y1 = coords[3];
    // 2nd bézier control point
    let x2 = coords[4];
    let y2 = coords[5];
    // curve's final point
    let x3 = coords[6];
    let y3 = coords[7];
    let area =
      ((x0 * (-2 * y1 - y2 + 3 * y3) +
        x1 * (2 * y0 - y2 - y3) +
        x2 * (y0 + y1 - 2 * y3) +
        x3 * (-3 * y0 + y1 + 2 * y2)) *
        3) /
      20;
    return area;
  } else {
    return 0;
  }
}

The remaining polygon area can be calculated with the common shoelace formula you've already included in your description.

enter image description here

function getSvgElsArea(svg) {
  //query only geometry elements
  let els = svg.querySelectorAll('path, circle, ellipse, rect, polygon');
  let area = 0;
  els.forEach(el => {
    area += getshapeArea(el);
  })
  return area;
}

function getshapeArea(el, decimals = 0) {
  let totalArea = 0;
  let polyPoints = [];
  let type = el.nodeName.toLowerCase();
  switch (type) {
    // 1. paths
    case "path":
      let pathData = el.getPathData({
        normalize: true
      });
      //check subpaths
      let subPathsData = splitSubpaths(pathData);
      let isCompoundPath = subPathsData.length > 1 ? true : false;
      let counterShapes = [];

      // check intersections for compund paths
      if (isCompoundPath) {
        let bboxArr = getSubPathBBoxes(subPathsData);
        bboxArr.forEach(function(bb, b) {
          //let path1 = path;
          for (let i = 0; i < bboxArr.length; i++) {
            let bb2 = bboxArr[i];
            if (bb != bb2) {
              let intersects = checkBBoxIntersections(bb, bb2);
              if (intersects) {
                counterShapes.push(i);
              }
            }
          }
        });
      }

      subPathsData.forEach(function(pathData, d) {
        //reset polygon points for each segment
        polyPoints = [];
        let bezierArea = 0;
        let pathArea = 0;
        let multiplier = 1;

        pathData.forEach(function(com, i) {
          let [type, values] = [com.type, com.values];
          if (values.length) {
            let prevC = i > 0 ? pathData[i - 1] : pathData[0];
            let prevCVals = prevC.values;
            let prevCValsL = prevCVals.length;
            let [x0, y0] = [
              prevCVals[prevCValsL - 2],
              prevCVals[prevCValsL - 1]
            ];
            // C commands
            if (values.length == 6) {
              let area = getBezierArea([
                x0,
                y0,
                values[0],
                values[1],
                values[2],
                values[3],
                values[4],
                values[5]
              ]);
              //push points to calculate inner/remaining polygon area
              polyPoints.push([x0, y0], [values[4], values[5]]);
              bezierArea += area;
            }
            // L commands
            else {
              polyPoints.push([x0, y0], [values[0], values[1]]);
            }
          }
        });
        //get area of remaining polygon
        let areaPoly = polygonArea(polyPoints, false);

        //subtract area by negative multiplier
        if (counterShapes.indexOf(d) !== -1) {
          multiplier = -1;
        }
        //values have the same sign - subtract polygon area
        if (
          (areaPoly < 0 && bezierArea < 0) ||
          (areaPoly > 0 && bezierArea > 0)
        ) {
          pathArea = (Math.abs(bezierArea) - Math.abs(areaPoly)) * multiplier;
        } else {
          pathArea = (Math.abs(bezierArea) + Math.abs(areaPoly)) * multiplier;
        }
        totalArea += pathArea;
      });
      break;

      // 2. primitives:
      // 2.1 circle an ellipse primitives
    case "circle":
    case "ellipse":
      totalArea = getEllipseArea(el);
      break;

      // 2.2 polygons
    case "polygon":
    case "polyline":
      totalArea = getPolygonArea(el);
      break;

      // 2.3 rectancle primitives
    case "rect":
      totalArea = getRectArea(el);
      break;
  }
  if (decimals > 0) {
    totalArea = +totalArea.toFixed(decimals);
  }
  return totalArea;
}

function getPathArea(pathData) {
  let totalArea = 0;
  let polyPoints = [];
  pathData.forEach(function(com, i) {
    let [type, values] = [com.type, com.values];
    if (values.length) {
      let prevC = i > 0 ? pathData[i - 1] : pathData[0];
      let prevCVals = prevC.values;
      let prevCValsL = prevCVals.length;
      let [x0, y0] = [prevCVals[prevCValsL - 2], prevCVals[prevCValsL - 1]];
      // C commands
      if (values.length == 6) {
        let area = getBezierArea([
          x0,
          y0,
          values[0],
          values[1],
          values[2],
          values[3],
          values[4],
          values[5]
        ]);
        //push points to calculate inner/remaining polygon area
        polyPoints.push([x0, y0], [values[4], values[5]]);
        totalArea += area;
      }
      // L commands
      else {
        polyPoints.push([x0, y0], [values[0], values[1]]);
      }
    }
  });
  let areaPoly = polygonArea(polyPoints);
  totalArea = Math.abs(areaPoly) + Math.abs(totalArea);
  return totalArea;
}

/**
 * James Godfrey-Kittle/@jamesgk : https://github.com/Pomax/BezierInfo-2/issues/238
 */
function getBezierArea(coords) {
  let x0 = coords[0];
  let y0 = coords[1];
  //if is cubic command
  if (coords.length == 8) {
    let x1 = coords[2];
    let y1 = coords[3];
    let x2 = coords[4];
    let y2 = coords[5];
    let x3 = coords[6];
    let y3 = coords[7];
    let area =
      ((x0 * (-2 * y1 - y2 + 3 * y3) +
          x1 * (2 * y0 - y2 - y3) +
          x2 * (y0 + y1 - 2 * y3) +
          x3 * (-3 * y0 + y1 + 2 * y2)) *
        3) /
      20;
    return area;
  } else {
    return 0;
  }
}

function polygonArea(points, absolute = true) {
  let area = 0;
  for (let i = 0; i < points.length; i++) {
    const addX = points[i][0];
    const addY = points[i === points.length - 1 ? 0 : i + 1][1];
    const subX = points[i === points.length - 1 ? 0 : i + 1][0];
    const subY = points[i][1];
    area += addX * addY * 0.5 - subX * subY * 0.5;
  }
  if (absolute) {
    area = Math.abs(area);
  }
  return area;
}

function getPolygonArea(el) {
  // convert point string to arra of numbers
  let points = el
    .getAttribute("points")
    .split(/,| /)
    .filter(Boolean)
    .map((val) => {
      return parseFloat(val);
    });
  let polyPoints = [];
  for (let i = 0; i < points.length; i += 2) {
    polyPoints.push([points[i], points[i + 1]]);
  }
  let area = polygonArea(polyPoints);
  return area;
}

function getRectArea(el) {
  let width = el.width.baseVal.value;
  let height = el.height.baseVal.value;
  let area = width * height;
  return area;
}

function getEllipseArea(el) {
  // if is circle
  let r = el.getAttribute("r") ? el.r.baseVal.value : '';
  let rx = el.getAttribute("rx");
  let ry = el.getAttribute("ry");
  //if circle – take radius
  rx = rx ? el.rx.baseVal.value : r;
  ry = ry ? el.ry.baseVal.value : r;
  let area = Math.PI * rx * ry;
  return area;
}

//path data helpers
function splitSubpaths(pathData) {
  let pathDataL = pathData.length;
  let subPathArr = [];
  let subPathMindex = [];
  pathData.forEach(function(com, i) {
    let [type, values] = [com["type"], com["values"]];
    if (type == "M") {
      subPathMindex.push(i);
    }
  });
  //split subPaths
  subPathMindex.forEach(function(index, i) {
    let end = subPathMindex[i + 1];
    let thisSeg = pathData.slice(index, end);
    subPathArr.push(thisSeg);
  });
  return subPathArr;
}

function getSubPathBBoxes(subPaths) {
  let ns = "http://www.w3.org/2000/svg";
  let svgTmp = document.createElementNS(ns, "svg");
  svgTmp.setAttribute("style", "position:absolute; width:0; height:0;");
  document.body.appendChild(svgTmp);
  let bboxArr = [];
  subPaths.forEach(function(pathData) {
    let pathTmp = document.createElementNS(ns, "path");
    svgTmp.appendChild(pathTmp);
    pathTmp.setPathData(pathData);
    let bb = pathTmp.getBBox();
    bboxArr.push(bb);
  });
  svgTmp.remove();
  return bboxArr;
}

function checkBBoxIntersections(bb, bb1) {
  let [x, y, width, height, right, bottom] = [
    bb.x,
    bb.y,
    bb.width,
    bb.height,
    bb.x + bb.width,
    bb.y + bb.height
  ];
  let [x1, y1, width1, height1, right1, bottom1] = [
    bb1.x,
    bb1.y,
    bb1.width,
    bb1.height,
    bb1.x + bb1.width,
    bb1.y + bb1.height
  ];
  let intersects = false;
  if (width * height != width1 * height1) {
    if (width * height > width1 * height1) {
      if (x < x1 && right > right1 && y < y1 && bottom > bottom1) {
        intersects = true;
      }
    }
  }
  return intersects;
}
body {
  font-family: 'Open Sans', 'Myriad', 'Segoe UI', sans-serif;
  font-size: 1.5em;
  line-height: 1.3em;
  background-image: url("data:image/svg+xml, %3Csvg viewBox='0 0 10 9' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='M0 0 L10 0 L10 9' stroke-width='1' fill='none' stroke='%23eee' /%3E%3C/svg%3E");
  background-repeat: repeat;
  background-size: 10px;
}

svg {
  width: 20em;
}
<h3>2 Circles</h3>
<svg id="svg_circles" viewBox="0 0 100 100">
  <circle id="circleOuter" cx="50%" cy="50%" r="50%" />
  <circle id="circleInner" cx="50%" cy="50%" r="25%" fill="red" />
</svg>
<p id="result0"></p>
<h3>Compound path</h3>
<svg id="svg" viewBox="0 0 100 100">
<path  fill-rule="evenodd" d="M50 0a50 50 0 0 1 50 50a50 50 0 0 1-50 50a50 50 0 0 1-50-50a50 50 0 0 1 50-50z 
M50 25a25 25 0 0 1 25 25a25 25 0 0 1-25 25a25 25 0 0 1-25-25a25 25 0 0 1 25-25z"/>
</svg>
<p id="result1"></p>

<h3>Multiple primitives</h3>
<svg id="svg_primitives" viewBox="0 0 200 100">
  <circle cx="50" cy="50" r="50" />
  <rect x="100" y="25" width="50" height="50" />
</svg>
<p id="result2"></p>

<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.3/path-data-polyfill.min.js"></script>

<script>
  window.addEventListener('DOMContentLoaded', e => {

    let areaOuter = getshapeArea(circleOuter);
    let areaInner = getshapeArea(circleInner);
    result0.textContent = areaOuter + '-' + areaInner + '=' + (areaOuter - areaInner);

    let areaCompound = getSvgElsArea(svg);
    result1.textContent = areaCompound;

    let areaPrim = getSvgElsArea(svg_primitives)
    result2.textContent = areaPrim;
  });
</script>

Getting <path> coordinates

We need a parser to retrieve the Bézier control points in a structured way for calculations.
I'm using Jarek Foksa's path-data polyfill. which also takes care of converting relative to absolute commands via normalize option getPathData({normalize:true})

Accuracy

As you can see, there's an inaccuracy comparing example 1 with 2:
Ideally, the result (outer circle - inner one ) should be

Precise area: 7853.981-1963.495 = 5890.486 (based on π * r²)

Due to an arc to cubic bézier conversion we only get a close approximation of the circle geometry

Cubic area: 5892.136

So a deviation of ~ 0.028%

I have the result. But its totaly differnt from e-cut plugin. That i think is working 100% right.

Actually, you should test against simple examples that could be calculated by simple geometry formulas (like circles/ellipses)

Detecting sub paths

If a path contains multiple M commands (e.g the letter O) we're calculating areas for each sub path and checking bounding box intersections via custom helper getSubPathBBoxes(subPaths).
If a sub path is intersecting with an outer shape - we subtract the second area.

Limitations

  • This script won't respect clip paths or masks
  • transformations – most importantly scale()
  • how to handle strokes? Currently not included, but some elements might draw a shape heavily depending on stroke widths
  • and probably a lot more ...
    For testing: See codepen example

Brute force approach: flatten svg via potrace

We're actually converting the svg to a bitmap and trace/vectorize it again and apply the previous calculation helper.
This way, we get a flattened svg combining overlapping shapes.
The aforementioned problems concerning masks and clip paths can be circumvented because we only analyze filled/opaque elements.

Obviously, this approach is more expensive due to the additional conversions.

//svg area
let areaSVG = getSvgElsArea(svg);

//potrace area
let scale = 2;
(async() => {
  let areaTraced = await getTracedArea(svg, scale);
  result.textContent = `area svg: ${areaSVG} | area traced: ${areaTraced}`;
})();

async function getTracedArea(el, scale = 1) {
  let filter = "brightness(0%)";
  let dataUrl = await svg2PngDataUrl(el, scale, filter);
  let tracingOptions = {
    turnpolicy: "minority",
    turdsize: 2,
    optcurve: true,
    alphamax: 1,
    opttolerance: 0.75
  };

  let tracedSVG = await traceFromURL(dataUrl, tracingOptions);
  let areaTraced = getSvgElsArea(tracedSVG);
  document.body.append(tracedSVG);
  return areaTraced / scale / scale;
}

/**
 * svg to canvas
 */
async function svg2PngDataUrl(el, scale = 1, filter = "") {
  /**
   * clone svg to add width and height
   */
  const svgEl = el.cloneNode(true);

  // get dimensions
  let {
    width,
    height
  } = el.getBBox();
  let w = el.viewBox.baseVal.width ?
    svgEl.viewBox.baseVal.width :
    el.width.baseVal.value ?
    el.width.baseVal.value :
    width;
  let h = el.viewBox.baseVal.height ?
    svgEl.viewBox.baseVal.height :
    el.height.baseVal.value ?
    el.height.baseVal.value :
    height;

  // apply scaling
  [w, h] = [w * scale, h * scale];

  // add width and height for firefox compatibility
  svgEl.setAttribute("width", w);
  svgEl.setAttribute("height", h);

  // create canvas
  let canvas = document.createElement("canvas");
  canvas.width = w;
  canvas.height = h;
  let svgString = new XMLSerializer().serializeToString(svgEl);
  let blob = new Blob([svgString], {
    type: "image/svg+xml"
  });
  let blobURL = URL.createObjectURL(blob);

  let tmpImg = new Image();
  tmpImg.src = blobURL;
  tmpImg.width = w;
  tmpImg.height = h;
  tmpImg.crossOrigin = "anonymous";

  await tmpImg.decode();
  let ctx = canvas.getContext("2d");
  ctx.fillStyle = "white";
  ctx.fillRect(0, 0, w, h);

  // apply filter to enhance contrast
  if (filter) {
    ctx.filter = filter;
  }
  ctx.drawImage(tmpImg, 0, 0, w, h);
  let dataUrl = canvas.toDataURL();
  return dataUrl;
}

/**
 * trace img from data URl
 */
async function traceFromURL(url, customOptions) {
  let defaults = {
    turnpolicy: "minority",
    turdsize: 2,
    optcurve: true,
    alphamax: 1,
    opttolerance: 0.75
  };

  let options = {
    ...defaults,
    ...customOptions
  };

  // set parameters
  Potrace.setParameter(options);

  // load
  await Potrace.loadImageFromUrl(url);

  async function processPotraceAsync() {
    return new Promise((resolve) => {
      Potrace.process(() => {
        let svgTraced = new DOMParser()
          .parseFromString(Potrace.getSVG(1), "image/svg+xml")
          .querySelector("svg");

        // remove attributes
        svgTraced.setAttribute(
          "viewBox",
          `0 0 ${svgTraced.width.baseVal.value} ${svgTraced.height.baseVal.value}`
        );
        svgTraced.removeAttribute("id");
        svgTraced.removeAttribute("version");
        svgTraced.removeAttribute("width");
        svgTraced.removeAttribute("height");

        let path = svgTraced.querySelector("path");
        path.removeAttribute("stroke");
        path.removeAttribute("fill");
        path.removeAttribute("fill-rule");

        resolve(svgTraced);
      });
    });
  }

  let tracedSvgEl = await processPotraceAsync();
  return tracedSvgEl;
}

function getSvgElsArea(svg) {
  let els = svg.querySelectorAll("path, circle, ellipse, rect, polygon");
  let area = 0;
  els.forEach((el) => {
    area += getshapeArea(el);
  });
  return area;
}

function getshapeArea(el, decimals = 0) {
  let totalArea = 0;
  let polyPoints = [];
  let type = el.nodeName.toLowerCase();
  switch (type) {
    // 1. paths
    case "path":
      let pathData = el.getPathData({
        normalize: true
      });
      //check subpaths
      let subPathsData = splitSubpaths(pathData);
      let isCompoundPath = subPathsData.length > 1 ? true : false;
      let counterShapes = [];

      // check intersections for compund paths
      if (isCompoundPath) {
        let bboxArr = getSubPathBBoxes(subPathsData);
        bboxArr.forEach(function(bb, b) {
          //let path1 = path;
          for (let i = 0; i < bboxArr.length; i++) {
            let bb2 = bboxArr[i];
            if (bb != bb2) {
              let intersects = checkBBoxIntersections(bb, bb2);
              if (intersects) {
                counterShapes.push(i);
              }
            }
          }
        });
      }

      subPathsData.forEach(function(pathData, d) {
        //reset polygon points for each segment
        polyPoints = [];
        let bezierArea = 0;
        let pathArea = 0;
        let multiplier = 1;

        pathData.forEach(function(com, i) {
          let [type, values] = [com.type, com.values];
          if (values.length) {
            let prevC = i > 0 ? pathData[i - 1] : pathData[0];
            let prevCVals = prevC.values;
            let prevCValsL = prevCVals.length;
            let [x0, y0] = [
              prevCVals[prevCValsL - 2],
              prevCVals[prevCValsL - 1]
            ];
            // C commands
            if (values.length == 6) {
              let area = getBezierArea([
                x0,
                y0,
                values[0],
                values[1],
                values[2],
                values[3],
                values[4],
                values[5]
              ]);
              //push points to calculate inner/remaining polygon area
              polyPoints.push([x0, y0], [values[4], values[5]]);
              bezierArea += area;
            }
            // L commands
            else {
              polyPoints.push([x0, y0], [values[0], values[1]]);
            }
          }
        });
        //get area of remaining polygon
        let areaPoly = polygonArea(polyPoints, false);

        //subtract area by negative multiplier
        if (counterShapes.indexOf(d) !== -1) {
          multiplier = -1;
        }
        //values have the same sign - subtract polygon area
        if (
          (areaPoly < 0 && bezierArea < 0) ||
          (areaPoly > 0 && bezierArea > 0)
        ) {
          pathArea = (Math.abs(bezierArea) - Math.abs(areaPoly)) * multiplier;
        } else {
          pathArea = (Math.abs(bezierArea) + Math.abs(areaPoly)) * multiplier;
        }
        totalArea += pathArea;
      });
      break;

      // 2.1 circle an ellipse primitives
    case "circle":
    case "ellipse":
      totalArea = getEllipseArea(el);
      break;

      // 2.2 polygons
    case "polygon":
    case "polyline":
      totalArea = getPolygonArea(el);
      break;

      // 2.3 rectancle primitives
    case "rect":
      totalArea = getRectArea(el);
      break;
  }
  if (decimals > 0) {
    totalArea = +totalArea.toFixed(decimals);
  }
  return totalArea;
}

function getPathArea(pathData) {
  let totalArea = 0;
  let polyPoints = [];
  pathData.forEach(function(com, i) {
    let [type, values] = [com.type, com.values];
    if (values.length) {
      let prevC = i > 0 ? pathData[i - 1] : pathData[0];
      let prevCVals = prevC.values;
      let prevCValsL = prevCVals.length;
      let [x0, y0] = [prevCVals[prevCValsL - 2], prevCVals[prevCValsL - 1]];
      // C commands
      if (values.length == 6) {
        let area = getBezierArea([
          x0,
          y0,
          values[0],
          values[1],
          values[2],
          values[3],
          values[4],
          values[5]
        ]);
        //push points to calculate inner/remaining polygon area
        polyPoints.push([x0, y0], [values[4], values[5]]);
        totalArea += area;
      }
      // L commands
      else {
        polyPoints.push([x0, y0], [values[0], values[1]]);
      }
    }
  });
  let areaPoly = polygonArea(polyPoints);
  totalArea = Math.abs(areaPoly) + Math.abs(totalArea);
  return totalArea;
}


function getBezierArea(coords) {
  let x0 = coords[0];
  let y0 = coords[1];
  //if is cubic command
  if (coords.length == 8) {
    let x1 = coords[2];
    let y1 = coords[3];
    let x2 = coords[4];
    let y2 = coords[5];
    let x3 = coords[6];
    let y3 = coords[7];
    let area =
      ((x0 * (-2 * y1 - y2 + 3 * y3) +
          x1 * (2 * y0 - y2 - y3) +
          x2 * (y0 + y1 - 2 * y3) +
          x3 * (-3 * y0 + y1 + 2 * y2)) *
        3) /
      20;
    return area;
  } else {
    return 0;
  }
}

function polygonArea(points, absolute = true) {
  let area = 0;
  for (let i = 0; i < points.length; i++) {
    const addX = points[i][0];
    const addY = points[i === points.length - 1 ? 0 : i + 1][1];
    const subX = points[i === points.length - 1 ? 0 : i + 1][0];
    const subY = points[i][1];
    area += addX * addY * 0.5 - subX * subY * 0.5;
  }
  if (absolute) {
    area = Math.abs(area);
  }
  return area;
}

function getPolygonArea(el) {
  // convert point string to arra of numbers
  let points = el
    .getAttribute("points")
    .split(/,| /)
    .filter(Boolean)
    .map((val) => {
      return parseFloat(val);
    });
  let polyPoints = [];
  for (let i = 0; i < points.length; i += 2) {
    polyPoints.push([points[i], points[i + 1]]);
  }
  let area = polygonArea(polyPoints);
  return area;
}

function getRectArea(el) {
  let width = el.width.baseVal.value;
  let height = el.height.baseVal.value;
  let area = width * height;
  return area;
}

function getEllipseArea(el) {
  // if is circle
  let r = el.getAttribute("r") ? el.r.baseVal.value : "";
  let rx = el.getAttribute("rx");
  let ry = el.getAttribute("ry");
  //if circle – take radius
  rx = rx ? el.rx.baseVal.value : r;
  ry = ry ? el.ry.baseVal.value : r;
  let area = Math.PI * rx * ry;
  return area;
}

//path data helpers
function splitSubpaths(pathData) {
  let pathDataL = pathData.length;
  let subPathArr = [];
  let subPathMindex = [];
  pathData.forEach(function(com, i) {
    let [type, values] = [com["type"], com["values"]];
    if (type == "M") {
      subPathMindex.push(i);
    }
  });
  //split subPaths
  subPathMindex.forEach(function(index, i) {
    let end = subPathMindex[i + 1];
    let thisSeg = pathData.slice(index, end);
    subPathArr.push(thisSeg);
  });
  return subPathArr;
}

function getSubPathBBoxes(subPaths) {
  let ns = "http://www.w3.org/2000/svg";
  let svgTmp = document.createElementNS(ns, "svg");
  svgTmp.setAttribute("style", "position:absolute; width:0; height:0;");
  document.body.appendChild(svgTmp);
  let bboxArr = [];
  subPaths.forEach(function(pathData) {
    let pathTmp = document.createElementNS(ns, "path");
    svgTmp.appendChild(pathTmp);
    pathTmp.setPathData(pathData);
    let bb = pathTmp.getBBox();
    bboxArr.push(bb);
  });
  svgTmp.remove();
  return bboxArr;
}

function checkBBoxIntersections(bb, bb1) {
  let [x, y, width, height, right, bottom] = [
    bb.x,
    bb.y,
    bb.width,
    bb.height,
    bb.x + bb.width,
    bb.y + bb.height
  ];
  let [x1, y1, width1, height1, right1, bottom1] = [
    bb1.x,
    bb1.y,
    bb1.width,
    bb1.height,
    bb1.x + bb1.width,
    bb1.y + bb1.height
  ];
  let intersects = false;
  if (width * height != width1 * height1) {
    if (width * height > width1 * height1) {
      if (x < x1 && right > right1 && y < y1 && bottom > bottom1) {
        intersects = true;
      }
    }
  }
  return intersects;
}
svg {
  width: 20em;
  height: auto;
  border: 1px solid #ccc;
  overflow: visible;
}
<svg id="svg" viewBox="0 0 200 100">
<path  fill="yellow" fill-rule="evenodd" d="M50 0a50 50 0 0 1 50 50a50 50 0 0 1-50 50a50 50 0 0 1-50-50a50 50 0 0 1 50-50zm0 25a25 25 0 0 1 25 25a25 25 0 0 1-25 25a25 25 0 0 1-25-25a25 25 0 0 1 25-25z"/>
<path  fill="pink" fill-rule="evenodd" d="M150 0a50 50 0 0 1 50 50a50 50 0 0 1-50 50a50 50 0 0 1-50-50a50 50 0 0 1 50-50zm0 25a25 25 0 0 1 25 25a25 25 0 0 1-25 25a25 25 0 0 1-25-25a25 25 0 0 1 25-25z"/>
</svg>

<p id="result"></p>
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.3/path-data-polyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/kilobtye/potrace@master/potrace.js"></script>

You might also use paper.js to calculate surface areas as described by lmeurs

herrstrietzel
  • 11,541
  • 2
  • 12
  • 34
  • @Alexandr Manin. I've added another example, that be be more tolerant towards different svg structures (clip paths, masks, overlapping shapes) – herrstrietzel Jul 06 '23 at 16:05