Calculate the exact size of a font glyph
Asked Answered
T

2

9

I need a way to find the exact size and position of a glyph relative to its bounding box.

We're using D3.js to create an SVG with a heading, a smaller byline and a short body text. Pretty much this:

Lorem ipsum

Lorem ipsum dolor

Lorem ipsum dolor sit amet,
consectetur adipisicing elit,
sed do eiusmod tempor
incididunt ut labore et dolore
magna aliqua

As in this example, I need the text to be left-aligned regardless of font-size.

The problem is that it is each lines bounding box that's aligned and not the glyphs. Which makes the headings look indented. How can I calculate that space between the bounding box and the glyph so I can correctly align the text?

A colleague sat down and manually measured this for the English font, which works pretty well. We could probably do this in Adobe Illustrator, but we need the information for English, Chinese and Arabic fonts. To do this by hand is more or less impossible.

The only solution I can come up with is to type each character in a canvas element and map each pixel to see where the glyph starts and ends.

What I think would be ideal is a way to use the SVG font path information to find the extremes, that way we would get the exact numbers instead of estimates. Our estimates have a big margin of error. Is there a way to do that with node.js?

An example of what the SVG looks like:

<svg viewBox="0,0,1008,1424" height="1052px" width="744px" version="1.1" 
    xmlns:xlink="http://www.w3.org/1999/xlink" 
    xmlns="http://www.w3.org/2000/svg">
    <g transform="translate(50,150)" class="container">
        <g transform="translate(0,205)" class="textContainer">
            <g fill="white" font-family="serif" font-size="" class="header">
                <text>
                    <tspan y="147.7104222717285" style="" font-size="208" 
                        x="-21.8359375">Lorem ipsum</tspan>
                </text>
                <text>
                    <tspan y="201.46275431823733" style="" font-size="45" 
                        x="-4.72412109375">Lorem ipsum dolor</tspan>
                </text>
            </g>
            <g lengthAdjust="spacingAndGlyphs" textLength="297" fill="white" 
                transform="translate(0,214)" font-family="sans-serif" 
                class="paragraph" font-size="14">
                <text y="16.8" x="0">Lorem ipsum dolor sit amet,</text>
                <text y="33.6" x="0">consectetur adipisicing elit,</text>
                <text y="50.4" x="0">sed do eiusmod tempor</text>
                <text y="67.2" x="0">incididunt ut labore et dolore</text>
                <text y="84" x="0">magna aliqua</text>
            </g>
        </g>
    </g>
</svg>

It is the x and y values on the text and tspan elements we need to calculate, the negative x values in particular. Note: we don't actually use the serif or sans-serif font families, we use a custom font. I don't think the English font is created with web use in mind. We used fontsquirrel.com to create the SVG font file.

Update

SVG fonts seemed like the most logical option, since you have all the path information in the file readable in node. You'd just have to interpret it. But we found, and decided to go with, opentype.js which works with otf and ttf fonts.

I'd still like to know if this is at all possible to do with SVG fonts.

Tintoretto answered 20/6, 2014 at 10:59 Comment(4)
There's no way to get glyph metrics in SVG currently. Maybe you should just use a sans-serif font.Wench
I've updated my question. We don't actually use those fonts, we use custom fonts that can't be replaced.Tintoretto
Jason David sometime ago wrote code for d3-cloud plugin to detect collision between characters which worked by rendering the characters and doing recursive bounded box calculations. He did not end up using it in the end, but maybe you can reuse some of that: jasondavies.com/wordcloud/aboutItalic
@Italic That looks interesting, and like somtething I can use. It doesn't really matter if it's inefficientTintoretto
E
1

I think this may do what you want. Note that it requires jQuery and is a bit hacky. You may want to set line-height in the css object.

function glyphSize(glyph,css){
  var span = $('<div>');
  span.text(glyph);
  span.css({
    display:'inline-block',
    position:'fixed',
    top:'100%'
  }).css(css);

  $('body').append(span);
  var out = {
    width:span.width(),
    height:span.height()
  } 
  span.remove();
  return out;
}

var size = glyphSize('a',{
  'font-family':'fantasy, sans-serif' // css to apply to the test element
});

/* size = {
 width:7,
 height:20
} */

edit: quick demo here

Etui answered 30/6, 2014 at 2:18 Comment(1)
It would only give the width and height of the bounding box and not the glyph.Tintoretto
E
0

Saw your question and had the problem myself. I solved it very simple in my opinion. I just created a new text element with opacity 0.0. I add it to the svg, get the bbox and remove it. The bbox contains the width and height:

getBBox : function(svgId, text, styleClass) {
    // Add tmp text element to the SVG
    var tempSvgTextElement = d3.select("#" + svgId)
    .append("text")
    .style("opacity", 0.0)
    .attr("class", styleClass);

    var tSpanTextNode = document.createTextNode(text);
    var svgTSpan = tempSvgTextElement.append("tspan").attr("class", styleClass).node();
    svgTSpan.appendChild(tSpanTextNode);
    var bbox = tempSvgTextElement.node().getBBox();
    tempSvgTextElement.remove();
    return bbox;
}
Erickson answered 23/12, 2014 at 19:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.