2

I have a large collection of SVG files that are accurate in size (em or inches) but variable margins of space around the outside. There is a large variety of drawing elements inside, with transforms applied. Is there a tool or code algorithm that can parse through the svg elements, add up the highest and lowest vertical/horizontal limits, and adjust the viewbox/width/height elements in the svg element with totals...effectively auto-cropping the svg to the edge of what is drawn without distorting the size of the image.

Note: these pdfs were created with pdf2svg. I tried searching for possible features there, but missed any features that might already do this.

Example input:

<svg height="100" width="100" viewbox="0 0 100 100>
  <circle cx="50" cy="50" r="30" stroke="black" stroke-width="3" fill="red" />
</svg>

Example output:

<svg height="66" width="66" viewbox="17 17 66 66>
  <circle cx="50" cy="50" r="30" stroke="black" stroke-width="3" fill="red" />
</svg>

It's a simplified example, and hopefully my headmath is accurate, but the first line svg element was only changed, to shrink the svg as much as possible while still allowing for the stroke-width=3 to be fully shown in the image.

I theoretically know how to do this, but the nature of the problem seems "super exhaustive and prone to bugs around matrix math and svg xml implementation, and necessary enough to possibly be already implemented, so creating it could be reinventing the wheel"

chrisroode
  • 77
  • 10
  • 1
    There's no straightforward way to do this that accounts for filters, masks etc. This is an answer with a few approaches: https://stackoverflow.com/questions/10623809/get-bounding-box-of-element-accounting-for-its-transform If I were doing this, I would probably dump the SVG to canvas and then do pixel counting to figure out the true % of empty space, and then use that to go back and rewrite the viewBox. – Michael Mullany Aug 10 '21 at 14:34
  • Thanks, now I feel motivated to create something! Also your approach sounds pretty straightforward. I agree, using a web engine would give the most stable result. I'll repeat back what I heard you say as an approach so I can make sure I've got a sound plan moving forward: 1) open SVG file with javascript (to allow batch processing) 2) create a canvas element large enough to content. 3) draw element to canvas 4) then test each row from top, bottom, left, and write to find crop distance. 5) Crop viewbox and size and save file. – chrisroode Aug 10 '21 at 14:58
  • I don't want to exhaust the scope of what I'm doing. But in this particular case, I am creating the PDF's in Finale music notation software. I can use any size page, and pdf, but the nature of music notation, Flute music will average higher y content than Tuba music based on the center of the staff. So the easiest way to create the SVG files is to use an all inclusive area, which will be too large depending on the particular image. I want to avoid anything GUI since it's part of a workflow to avoid manual graphics cropping on the web end. – chrisroode Aug 10 '21 at 17:32

1 Answers1

1

If you can run javascript, you might use getBBox() for that:

const s = document.querySelector('svg');

const r = s.getBBox({stroke: true});
s.setAttribute('viewBox', `${r.x} ${r.y} ${r.width} ${r.height}`);
<svg height="100" width="100" viewbox="0 0 100 100">
  <circle cx="50" cy="50" r="30" stroke="black" stroke-width="3" fill="red" />
  <rect x="20" y="10" width="30" height="30" fill="blue" transform="rotate(45) translate(20)"/>
  <rect id="BB" x="50" y="50" width="2" height="2" 
stroke="red" fill="yellow" opacity="0.4"/>
</svg>
</svg>

Notice that there is some vertical spacing. The reason is that we have given width and height properties to the svg, and the bounding rectangle does not have the same proportions. Also, the stroke: true option seems to be ignored by Chrome, as it is experimental. In that case, the stroke may be cropped.

vqf
  • 2,600
  • 10
  • 16
  • Thank you so much! This took care of my problem. I did notice the arguments for getBBox() didn't seem to change anything, and the stroke of the circle was getting cropped out a little. Not a problem for me, I'll just band-aid in some padding to the values should I need them: s.setAttribute('viewBox', `${r.x-2} ${r.y-2} ${r.width+4} ${r.height+4}`); – chrisroode Aug 14 '21 at 20:07