SVG font units-per-em to mm


In an application I'm writing, I need to take a glyph's path from an SVG font and scale it to a particular size, which is specified in millimetres. I take the 'd' path property from the SVG font, scale it by applying a scale factor to the coordinates of the path, and then use it as a regular path in the SVG I'm generating. (For my application I cannot use 'matrix', 'height', 'width' or any other built-in scaling methods; the path coordinates must reflect the exact final dimension and scale). The mechanics of applying a scale the path is not a problem; I just need to figure out the correct scaling factor to apply.

The SVG font provides 'unit-per-em' that is either 2048 or 1000. I know that 'em' is a relative unit of measurement and that 'mm' is an absolute unit, and I'm fairly certain that the conversion can be made. It'd be helpful if someone could explain the detail of such conversion, or indicate if what I'm trying to do doesn't make sense.

Some context: the SVG file I'm generating with my (Python) software is then converted into a coordinate file (Gerber) that is used to physically manufacture an object. All the elements in both files need to represent exact dimensions without any other scaling or transforms.

8/9/2014 10:54:00 AM

Accepted Answer

In typograph, an "em" unit is the maximum height of a set of characters. For latin text, that means the height from the accent on top of capital letters to the bottom of descending lowercase letters. E.g, it would be the total height of Ég.

The "em" value is what you set when you define the font size -- if you set the font to be 24pt, then 1em=24pt=1/3inch. In CSS, you can also set the font size using metric units, so you could specifically set font-size:20mm and the SVG viewer would scale the font to be that height when printed out (or an approximation on screen).

However, that doesn't solve the problem for your manufacturing file, which needs the actual coordinates of the Bezier curves to be in mm. To do that, you need to know how the coordinates of the curves in the font relate to the final output size.

As you've discovered, the key is in the units-per-em attribute of a <font-face> element or CSS @font-face declaration. The standard values (1000 and 2048) are derived from other font formats; theoretically you could use any values for SVG fonts.

Either way, this defines the number of coordinates between the top and bottom of characters, i.e. the number of coordinate units in 1em unit. From there, you can figure out how much each coordinate needs to be scaled to match the desired font size.

If you have 1000 units-per-em, and you want a final maximum height of your words to be 500mm, then your scaling factor is

scalingFactor = (desired em height)/(units per em) 
              = (500mm/em) / (1000 units/em)
              = 0.5mm/unit

In other words, to convert from the raw units to mm, you would multiply every coordinate by 0.5.

However, there are a couple other details that might cause confusion, depending on the requirements of your conversion and material-cutting software and how you're defining the desired size of the letters:

  • The em unit only defines the height of characters. The width (in most fonts) will vary. In SVG, the horiz-adv-x attribute of each glyph (or if not specified on the individual glyph, of the font itself) defines the width of that character, including default character spacing, as a number of coordinate units. If you want to scale your text to a specified width instead of a specified height in mm, this is the number of units you should use in your calculation:

    scalingFactor = (desired width)/(horizontal advance in coordinate units) 
  • Alternately, if you do care about height, but only about the height of a capital letter (no descenders, so not the full em height), then you'll need to calculate the number of units in that height from the cap-height and alphabetical attributes, which define the position of the top and bottom of the capital letters in the coordinate system:

    scalingFactor = (desired height of capital letter) /
                   [(cap-height coordinate)-(alphabetic baseline coordinate)] 

    (There are various other heights which may be defined that you could use for calculating your scale in a similar way.)

  • Unlike everything else in SVG, the Y coordinates of fonts increase as you go up, not down. (This is again done for consistency with other font formats.) If your conversion software expects the opposite, you'll have to flip every Y coordinate before you scale it:

    newYValue = [(units-per-em) - (oldY)] * scalingFactor
  • Finally, you may need to adjust the absolute value of the coordinates so that the (0,0) origin is in the correct position for what you're doing. You can work from one of the attributes given on the <font-face> element, or the horiz/vert-origin-x/y attributes on the <font> element. These attributes define the position of the glyph origin (for latin text, the intersection of the baseline with the left edge of the letter) in the coordinate system. If these are zero (the default if they aren't specified), then descenders will be given in negative coordinates. If negative coordinates are a problem, you'll need to figure out the minimum coordinate in each direction and translate every coordinate by that amount to make everything positive.

I hope that's enough for you to figure out what the numbers represent and how you can adapt them to your needs.

A final option, especially if this is a one-time conversion and you don't need to create a re-usable script, is to open the SVG in a visual editor like Inkscape. You can write out the text and then use the "Path > Object to Path" command. I can't figure out an easy way to force it to scale the coordinates to mm, but it will get rid of most of the font-specific sizing headaches! If you've already got a conversion program that works with SVG paths, then this would be the easiest approach.

8/9/2014 4:33:00 PM