14

I have an SVG with a load of text on it. It's a carpark map with space numbers written on it. I display this in a web browser and thanks to a wonderful little bug in Firefox, the browser renders the text incorrectly. Boo.

So I converted the text to paths. We're talking about up to 4000 separate labels. Maybe 15,000 new shapes now they're vectors. It's 4MB. Normally you might argue this would lend itself to compression but I have to in-line this SVG into the HTML. I'm adding CSS alterations dynamically and this is the only way I have a chance of cross-browser support. So anyway, the raw —even scoured— output of this is too big to be useful.

What strikes me here is all these space numbers share common glyphs. Zero through nine. Why am I including a shape definition for every instance of every number? Can I de-duplicate these?

I'm using Inkscape but I'm open to suggestions.

  • Any chance you could share the SVG file so we can experiment with it? – JohnB Nov 2 '16 at 15:27
  • 1
    What is the bug exactly (screenshot), and how are you exporting and loading it to a browser? There's a good chance you can fix it with just some CSS styling, or a small script to the page to fix/replace the text elements. If this is a dynamic map display, you may want to turn it into an actual map - most browser mapping libraries (and many data-based ones) have great support for labeling. – brichins Nov 2 '16 at 18:49
  • 1
    Sorry, the SVG isn't mine to share (it belongs to a client) but the bug is known about. It essentially means the fonts in the SVG won't scale down past a point... Which is a serious issue if you're zoomed all the way out on a map with thousands of numbered parking spaces. They should be (eg) ~0.08pt and they're actually 13pt. – Oli Nov 2 '16 at 21:52
  • 1
    You've got the wrong bug number, you're actually looking for this one – Robert Longson Nov 2 '16 at 22:47
  • You may want to test this on a subset, but can path|simplify help you at all? A lowercase "s" converts to a 28 point path, simplify srops it to 17, and overlaying the simplified and unsimplified versions even zoomed so one "s" fills the screen there's no difference. – Chris H Nov 3 '16 at 10:40
6

The <use> element allows you to reuse objects defined elsewhere in the document. For example, you could define each glyph as a <symbol> and then reuse them multiple times. Here's a nice article about it: Structuring, Grouping, and Referencing in SVG — The <g>, <use>, <defs> and <symbol> Elements.

I don't know how to do that directly in Inkscape, though -- especially not for a bunch of text you already have as text. You might have to write a script to post-process the SVG and find all the paths that can be reused.

  • The <use> was what I was thinking when I asked this, the scripting is the how. Seems silly that there's nothing out there to de-duplicate near-identical markup. – Oli Nov 2 '16 at 23:24
  • @Oli there was nothing even to dedupe identical markup; I had to write my own (and by identical I mean to the byte; I hashed them and compared hashes) – Chris H Nov 3 '16 at 11:25
  • I had a think over lunch, and it feels to me like a (python) script within inkscape would be the way to go. The script would start with the text as text, and replace it with references to the <symbol>s (or it looks like you could <def> your text-to-path glyphs – Chris H Nov 3 '16 at 13:25
4

There are some compression options available that will offer varying degrees of success. To test them, I created an artwork file that solely had lots of repeated text. Un-expanded, the file size is 13.8 KB. Expanded, the file size is 1.42 MB.

Good option: Use SVGZ - 46.5 KB

Saving the expanded artwork as SVGZ gave me an output file of 46.5 KB which is significantly smaller than the standard SVG. Take note though that support may vary.

Better Option: Compress with Scour - 21.1 KB

Scour is a tool that will scrub and optimize your SVG file for you. Using the "maximum compression" command of scour -i input.svg -o output.svgz --enable-viewboxing --enable-id-stripping --enable-comment-stripping --shorten-ids --indent=none, the expanded artwork was reduced to 21.1 KB. Not far off from the original un-expanded file size!

  • 3
    Scouring doesn't compress, it just removes junk and while it does do a fair amount of good for me (given the huge number of objects), my "4MB" was post-scour. Compressed content would be great but —and I should have mentioned this earlier— I have to inline the SVG for browser targeting issues (and also frontend manipulation requirements). I know, I know, it's painful but trust me when I say it's hurting me more at the moment D: – Oli Nov 2 '16 at 21:55
  • I should have said, the scouring part of scour doesn't compress. It can also throw the file through gzip for you but the ID and whitespace minification stuff is its main gig. – Oli Nov 3 '16 at 11:50
  • @Oli right, hence my careful choice of the word "optimize" :) That's a bummer that your file is already Scoured though...ack! – JohnB Nov 3 '16 at 13:24
  • I messed around in Illustrator a bit; its SVG output does respect Symbols . Here's a quick example. It would take quite a bit of effort, but you might be able to create an Illustrator script that will break apart your text into individual characters and replace them with Symbols. The biggest hurdle I see would be dealing with rotation. If all text is parallel with the X axis it doesn't seem to difficult, but dealing with text on an angle might be a challenge. Of course, all that isn't very helpful if you don't have Illustrator available – JohnB Nov 3 '16 at 13:31
3

This is an in-browser or on server solution

There are many different ways to optimize SVG files. Sounds like you have already done a fair bit of it.

A few resources which I've found very useful are a css-tricks article that focuses on very specific details. And specifically, the tool it uses SVGO.

If you have many, repeated paths, I would consider using javascript to create the shapes Dynamically. There is an example here. One direction would be to have a defined function for each glyph, and simply have each path within the svg element created by a request for that function. Or take a full string, and/or array of arguments, to create your inline svg. This, of course, expects that your paths are longer than the length of the code required to request said function (pretty easy assumption).

  • 1
    While this answer presents multiple possible solutions, SVGO seems most interesting. It could also be run on .svg file to optimize it. However it does not seem to currently do path deduplication. If the asker ends up doing custom scripting anyway, doing it as an enhancement to SVGO might be a good idea. – jpa Nov 3 '16 at 6:15
  • @jpa deduping before (or while) converting to paths would be the way to go, surely. Replace all instances of text char "1" with <use xlink:href="#digit_one"> where digit_one is a path – Chris H Nov 3 '16 at 13:30
  • @ChrisH I can't see why one couldn't dedup afterwards just as well. Just normalize all paths to origin at starting point, take hash, use that to find if the path has occurred already. Sure, not as elegant, but should work and could be easier to implement than having to reimplement / figure out all the text layout logic. – jpa Nov 3 '16 at 14:49
  • @jpa you could, but if normalising the origin introduced rounding errors the hash wouldn't match. The script I'm thinking of would call inkscape's text-to-path to lay out the character paths but then replace them with references to a set of masters. – Chris H Nov 3 '16 at 14:55
  • @jpa and the rounding errors are real. On a toy example the first quadratic curve of a zero differs by 1 in the sixth decimal place -- enough to screw up hashing – Chris H Nov 9 '16 at 9:15
2

Here at a very high level is what your script should do. Feel free to use your preferred language and environment, this is just a starting point for a logic which should give you what you're looking for.

  • Loop through all the text elements in the SVG's xml and for the ones that have numeric text (the parking spots, you do not specify if there is more text in your svg), change the id automatically created by Inkscape to a string with that parking spot's number. Inkscape only needs unique IDs, it generates non-descriptive IDs but will respect and not change IDs created by other software or manually created or changed by the user.
  • In a table store the x & y coordinates of the text element of each parking spot.
  • Whether in Inkscape or by scripting, convert all parking spot text into paths.
  • For digits 0-9 append to the SVG file an appropriate <defs> section defining the path information for each digit.
  • Loop through all the <g>s of the parking spots and replace them with a <use xlink:href="#digit" x=x y=y />
  • Profit

I can foresee some complications that you will have to deal with, and good luck with those.

  • The loop will need to split up the string of parking spots with 2 digits or more and the appropriate spacing between the first digit and subsequent digits might be tricky; this will heavily depend on the fonts you are using.
  • You do not specify what orientation the parking spots are in or if they are even on curved set-ups. Because of the manual x,y offsets for 2 digit numbers and greater, you might need additional logic to figure out the 'orientation' of a parking spot and how to correctly space the different paths of individual digits from the first one.

Hope this helps. Good Luck.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.