Home > database >  SVG incorrect text BBox calculation when google fonts used
SVG incorrect text BBox calculation when google fonts used

Time:09-02

I am trying to generate a line dynamically based on a text's BBox created previously. However, the line is placed inaccurately. I was hoping for the code to place the line at the beginning of the text and not far from text.

I am not sure what has gone wrong here. BBox returns the smallest possible rectangle around the svg element, but why the line is placed far away when it is based on the same BBox dimension.

const body = d3.select('body');

//global specs
const width = 1536;
const height = 720;
const svgns = 'http://www.w3.org/2000/svg';

//generate svg
const svg = body.append('svg')
    .attr('xmlns', svgns)
    .attr('viewBox', `0 0 ${width} ${height}`);

//background rect 
svg.append('rect')
    .attr('class', 'vBoxRect')
    .attr('width', `${width}`)
    .attr('height', `${height}`)
    .attr('fill', '#EFEFEF');

//text data
const data = [{ "cat": "This is a test of text using Javascript" }];

//create grp
const grp = svg
    .append('g')
    .attr('class', 'test')
    
//create text  
const svgText1 = grp
    .append('g')
    .classed('svgText', true)
    .selectAll('text')
    .data(data)
    .join('text')
    .attr('class', (d, i) => { return `textSvgOne`   `${i}` })
    .each(
        function(d, i) {
            const element = svg.node();
            const vBox = element.viewBox.baseVal;
            const width = vBox.width / 2;
            const height = vBox.height / 2;

            d3.select(this)
                .attr('x', `${width}`)
                .attr('y', `${height}`)


        }
    )
    .text((d, i) => { return d.cat })
    .attr('text-anchor', 'middle')
    .attr('dominant-baseline', 'middle')
    .attr('alignment-baseline', 'middle')
    .style('font-size', 'xx-large')
    .style('font-family', "'Oswald', sans-serif");


//create line 
const border1 = d3.select('g.svgText')
    .selectAll('line')
    .data(data)
    .join('line')
    .each(
        function(d, i) {
            const current = d3.select(this);
            const target = current.node().parentNode.childNodes[0];
            const box = target.getBBox();
            const x = box.x;
            const y = box.y;
            const height = box.height;

            current
                .attr('class', (d, i) => { return `textSvgBorder`   `${i}` })
                .attr('x1', x)
                .attr('x2', x)
                .attr('y1', y)
                .attr('y2', `${y height}`)
                .attr('stroke', 'black')

        }
    )
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>

<body>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=DM Sans&display=swap');
        @import url('https://fonts.googleapis.com/css2?family=Oswald:wght@200&display=swap');
    </style>

    <script type="text/javascript" src="prod.js">
    </script>
</body>

</html>

CodePudding user response:

@enxaneta thanks for the hint. The following adapted from this answer perfectly works in chrome/firefox/brave/edge.

The BBox calculation is wrapped in the following promise document.fonts.ready.then(()=>) and the font has following declartaion now

<link rel="preconnect" href="https://fonts.gstatic.com/" />
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin />
<link href='https://fonts.googleapis.com/css2?family=Oswald:wght@200&display=block' rel='stylesheet' type='text/css'>

const body = d3.select('body');

//global specs
const width = 1536;
const height = 720;
const svgns = 'http://www.w3.org/2000/svg';

//text data
const data = [{
    "cat": "This is a test of text using Javascript"
}];

//generate svg
const svg = body.append('svg')
    .attr('xmlns', svgns)
    .attr('viewBox', `0 0 ${width} ${height}`);

//background rect 
svg.append('rect')
    .attr('class', 'vBoxRect')
    .attr('width', `${width}`)
    .attr('height', `${height}`)
    .attr('fill', '#EFEFEF');

//create grp
const grp = svg
    .append('g')
    .attr('class', 'test')

//create text
const svgText1 = grp
    .append('g')
    .classed('svgText', true)
    .selectAll('text')
    .data(data)
    .join('text')
    .attr('class', (d, i) => {
        return `textSvgOne`   `${i}`
    })
    .each(
        function(d, i) {
            const element = svg.node();
            const vBox = element.viewBox.baseVal;
            const width = vBox.width / 2;
            const height = vBox.height / 2;

            d3.select(this)
                .attr('x', `${width}`)
                .attr('y', `${height}`)


        }
    )
    .text((d, i) => {
        return d.cat
    })
    .attr('text-anchor', 'middle')
    .attr('dominant-baseline', 'middle')
    .attr('alignment-baseline', 'middle')
    .attr('font-size', 'xx-large')
    .style('font-family', "'Oswald', sans-serif");

//create line dynamically based on text BBox upon promise fulfillment  
document.fonts.ready.then(() => {
        d3.select('g.svgText')
            .selectAll('line')
            .data(data)
            .join('line')
            .each(
                function(d, i) {
                    const current = d3.select(this);
                    const target = d3.select(`.textSvgOne`   `${i}`).node();
                    //console.log(target);
                    const box = target.getBBox();
                    const x = box.x;
                    const y = box.y;
                    const height = box.height;
          
                    current
                        .attr('class', (d, i) => {
                            return `textSvgBorder`   `${i}`
                        })
                        .attr('x1', x)
                        .attr('x2', x)
                        .attr('y1', y)
                        .attr('y2', `${y height}`)
                        .attr('stroke', 'black')

                }
            )
    }

)
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="preconnect" href="https://fonts.gstatic.com/" />
    <link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin />
    <link href='https://fonts.googleapis.com/css2?family=Oswald:wght@200&display=block' rel='stylesheet' type='text/css'>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>

<body>
<!--
    <style>
        @import url('https://fonts.googleapis.com/css2?family=DM Sans&display=swap');
        @import url('https://fonts.googleapis.com/css2?family=Oswald:wght@200&display=block');
    </style>
-->
    <script type="text/javascript" src="prod.js">
    </script>
</body>

</html>

  • Related