Search code examples
javascriptsvgfontsbounding-box

reproduce Bounding Box of text in Browsers


When using SVG in the Browser the browser has a getBBox function to give you the bounding box of various elements. But when it comes to text elements it really confused me how this bouding box is calculated. I know that fontsize is based on the em-Box which is specified in the font file. However my tests show that none of these produce the same results as in FF or Chrome (which differ only a few px on fontsize 1000):

fontSize != bbox-height
(ascender-descender)/unitsPerEm * fontSize != bbox-height
(unitsPerEm-descender)/unitsPerEm * fontSize != bbox-height
...maybe adding a fixed amount to ascender for accents? Like Ć

So what is the secret behind the bbox height of text in browsers?

I even tried to look into the source code of FF and Chrome but finding the right place where the calculation is based is a challenge on its own

// EDIT: In response to the comment: I want to calculate the bbox of svg text as done in the browser (replicate the behavior). I need to know the metrics of the font which are needed to correctly calculate the bbox and the formular which is used to calculate the (width and height is sufficiant)


Solution

  • After lots of reasearch and tril and error I found a possible solution to at least explain chromes behavior of text bbox dimensions.

    BBox Height

    First of all I used the npm package fontkit to load and parse the fontfile. fontkit give you several metrics for the font in a whole which includes:

    • font.ascent
    • font.descent
    • font.lineGap
    • font.unitsPerEm

    So to calculate the height of the bbox I figured the following:

    bboxHeight = (font.ascent - font.descent + font.lineGap) / unitsPerEm * fontSize
    

    However, that leads to errors when having a font which is bigger then the em box (font.ascent - font.descent > unitsPerEm). In this special case the bboxHeight is font.ascent - font.descent.

    That leads to following code for the height:

    var fontHeight = font.ascent - font.descent
    var lineHeight = fontHeight > font.unitsPerEm ? fontHeight : fontHeight + font.lineGap
    var height = lineHeight/font.unitsPerEm * fontSize
    

    BBox Width

    to calculate the width of the text I made use of the layout feature of fontkit. layout gives you access to the glyphs the text is drawn from and also access to the metrics of the glyph. The metric we need is the advanceWidth which includes the margins to other glyphs next to the current glpyh. By summming up all advanceWidths and scaling them accordingly I ended up with the bboxWidth:

    var width = font.layout(text).glyphs.reduce((last, curr) => last + curr.advanceWidth, 0)
    width = width / font.unitsPerEm * fontSize
    

    BBox y position

    Trouble does not stop here, we still have to calculate the y position of the bbox. Thats a rather simple formula:

    var bboxY = y-font.ascent/font.unitsPerEm * fontSize
    

    Where y is the theoretical position you would pull from the dom (y and dy attribute)


    BBox x position

    Thats just the figure you pull from the dom (x and dx)


    Whole Box:

    var box = {
        x:x,
        y: y-font.ascent/font.unitsPerEm * fontSize,
        width: width
        height: height
    }
    

    Hope it helps someone else!