Search code examples
javascriptreactjssvgpathopentype

Text is not converting to right svg code - Opentype Javascript


I am trying to convert text to svg in my React web app, but the resulted svg code which i am getting is not right, t's not showing svg.
Code:

import opentype from "opentype.js"

async function textToPath(){
    const text = "Hello"
    const size = {x: 50, y: 25}
    const fontFile = "https://fonts.gstatic.com/s/firasans/v15/va9E4kDNxMZdWfMOD5Vvl4jO.ttf"
    let params = {
        string: text,
        font: fontFile,
        fontSize: size.x,
        decimals: 1,
        singleGylyphs: false
    }
    const font = await opentype.load(params.font);

    let options = params.options;
    let unitsPerEm = font.unitsPerEm;
    let ratio = params.fontSize / unitsPerEm;
    let ascender = font.ascender;
    let descender = Math.abs(font.descender);
    let ratAsc = ascender / unitsPerEm;
    let ratDesc = descender / unitsPerEm;
    let yOffset = params.fontSize * ratAsc;
    let lineHeight = params.fontSize + params.fontSize * ratDesc;
    let singleGylyphs = params.singleGylyphs;

    let teststring = params.string.split("");

    let glyphs = font.stringToGlyphs(params.string);
    let leftSB = glyphs[0].leftSideBearing * ratio;
    let textPath = "";

    //individual paths for each glyph
    if (singleGylyphs) {
        let paths = font.getPaths(
            params.string,
            -leftSB,
            yOffset,
            params.fontSize,
            options
        );
        paths.forEach(function (path, i) {
            let pathEl = path.toSVG(params.decimals);
            textPath += pathEl.replaceAll(
                "d=",
                'class="glyph glyph-' + teststring[i] + '" d='
            );
        });
    }
    //word (all glyphs) merged to one path
    else {
        let path = font.getPath(
            params.string,
            -leftSB,
            yOffset,
            params.fontSize,
            options
        );
        textPath += path
            .toSVG(params.decimals)
            .replaceAll("d=", 'class="glyph" d=');
    }

    // render
    let fontSvgWrp = document.createElement("div");
    fontSvgWrp.classList.add("fontSvgWrp");
    let fontSvg = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "svg"
    );
    fontSvg.classList.add("svgText");
    fontSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
    fontSvg.innerHTML = textPath;
    fontSvgWrp.appendChild(fontSvg);

    // adjust bbox
    let bb = fontSvg.getBBox();
    let stringWidth = Math.ceil(bb.width + bb.x);
    fontSvg.setAttribute("viewBox", "0 0 " + stringWidth + " " + lineHeight);
    fontSvg.setAttribute("width", size.x);
    fontSvg.setAttribute("data-asc", ratAsc);
    console.log(fontSvg); // getting svg code

    let textPathSvg = fontSvg.querySelector(".glyph");
    let textPathLength = textPathSvg.getTotalLength();
    
    return textPathLength
}

It's generating this svg which is not working:

<svg class="svgText" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 0 5013.25" width="50" data-asc="0.935"><path class="glyph" d="M24 46.8L19.3 46.8L19.3 30.6L4.8 30.6L4.8 46.8L0 46.8L0 12.3L4.8 12.3L4.8 26.7L19.3 26.7L19.3 12.3L24 12.3L24 46.8ZM53.7 32.8Q53.7 34.0 53.5 35.1L53.5 35.1L36.8 35.1Q37.0 39.5 39.0 41.5Q40.9 43.6 43.9 43.6L43.9 43.6Q45.8 43.6 47.4 43.0Q49.0 42.5 50.7 41.3L50.7 41.3L52.7 44.0Q48.5 47.4 43.5 47.4L43.5 47.4Q38 47.4 34.9 43.8Q31.9 40.1 31.9 33.9L31.9 33.9Q31.9 29.8 33.2 26.6Q34.5 23.4 37.0 21.6Q39.5 19.8 42.8 19.8L42.8 19.8Q48.0 19.8 50.9 23.3Q53.7 26.7 53.7 32.8L53.7 32.8ZM49.1 31.8L49.1 31.4Q49.1 27.5 47.5 25.5Q46 23.4 42.9 23.4L42.9 23.4Q37.3 23.4 36.8 31.8L36.8 31.8L49.1 31.8ZM66.3 47.4Q63.7 47.4 62.2 45.8Q60.8 44.3 60.8 41.5L60.8 41.5L60.8 9.8L65.3 9.3L65.3 41.5Q65.3 42.5 65.7 43.0Q66.1 43.5 67 43.5L67 43.5Q68.0 43.5 68.7 43.3L68.7 43.3L69.9 46.5Q68.3 47.4 66.3 47.4L66.3 47.4ZM80.9 47.4Q78.4 47.4 76.9 45.8Q75.4 44.3 75.4 41.5L75.4 41.5L75.4 9.8L80 9.3L80 41.5Q80 42.5 80.4 43.0Q80.8 43.5 81.7 43.5L81.7 43.5Q82.6 43.5 83.4 43.3L83.4 43.3L84.6 46.5Q82.9 47.4 80.9 47.4L80.9 47.4ZM100.2 19.8Q105.8 19.8 108.8 23.5Q111.9 27.2 111.9 33.5L111.9 33.5Q111.9 37.6 110.5 40.8Q109.1 43.9 106.5 45.6Q103.8 47.4 100.2 47.4L100.2 47.4Q94.6 47.4 91.5 43.6Q88.4 40.0 88.4 33.6L88.4 33.6Q88.4 29.5 89.8 26.4Q91.2 23.3 93.9 21.5Q96.5 19.8 100.2 19.8L100.2 19.8ZM100.2 23.5Q93.4 23.5 93.4 33.6L93.4 33.6Q93.4 43.6 100.2 43.6L100.2 43.6Q107.0 43.6 107.0 33.5L107.0 33.5Q107.0 23.5 100.2 23.5L100.2 23.5Z"></path></svg>

I tried & searched a lot but i didn't get anything to fix this thing and why the converted svg is not working. Can someone help?


Solution

  • The svg output is actually OK but your viewBox isn't calculated correctly.

    The viewBox width value is 0 – so your path is completely cropped/invisible.

    The main problem in your script: you need to append the svg to your DOM otherwise getBBox() will return an empty object (or width:0, height:0).

    It should rather look like this:

    fontSvgWrp.appendChild(fontSvg);
    document.body.appendChild(fontSvgWrp);
    // adjust bbox
    let bb = fontSvg.getBBox();
    

    Example: appending svg before getBBox()

    let fontSVG = textToPath();
    
    async function textToPath(){
        const text = "Hello"
        const size = {x: 50, y: 25}
        const fontFile = "https://fonts.gstatic.com/s/firasans/v15/va9E4kDNxMZdWfMOD5Vvl4jO.ttf"
        let params = {
            string: text,
            font: fontFile,
            fontSize: size.x,
            decimals: 1,
            singleGylyphs: false
        }
        const font = await opentype.load(params.font);
    
        let options = params.options;
        let unitsPerEm = font.unitsPerEm;
        let ratio = params.fontSize / unitsPerEm;
        let ascender = font.ascender;
        let descender = Math.abs(font.descender);
        let ratAsc = ascender / unitsPerEm;
        let ratDesc = descender / unitsPerEm;
        let yOffset = params.fontSize * ratAsc;
        let lineHeight = params.fontSize + params.fontSize * ratDesc;
        let singleGylyphs = params.singleGylyphs;
    
        let teststring = params.string.split("");
    
        let glyphs = font.stringToGlyphs(params.string);
        let leftSB = glyphs[0].leftSideBearing * ratio;
        let textPath = "";
    
        //individual paths for each glyph
        if (singleGylyphs) {
            let paths = font.getPaths(
                params.string,
                -leftSB,
                yOffset,
                params.fontSize,
                options
            );
            paths.forEach(function (path, i) {
                let pathEl = path.toSVG(params.decimals);
                textPath += pathEl.replaceAll(
                    "d=",
                    'class="glyph glyph-' + teststring[i] + '" d='
                );
            });
        }
        //word (all glyphs) merged to one path
        else {
            let path = font.getPath(
                params.string,
                -leftSB,
                yOffset,
                params.fontSize,
                options
            );
            textPath += path
                .toSVG(params.decimals)
                .replaceAll("d=", 'class="glyph" d=');
        }
    
        // render
        let fontSvgWrp = document.createElement("div");
        fontSvgWrp.classList.add("fontSvgWrp");
        let fontSvg = document.createElementNS(
            "http://www.w3.org/2000/svg",
            "svg"
        );
        fontSvg.classList.add("svgText");
        fontSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
        fontSvg.innerHTML = textPath;
        fontSvgWrp.appendChild(fontSvg);
        document.body.appendChild(fontSvgWrp);
    
        // adjust bbox
        let bb = fontSvg.getBBox();
      //console.log(bb)
        let stringWidth = Math.ceil(bb.width + bb.x);
        //let stringWidth = Math.ceil(bb.width);
        fontSvg.setAttribute("viewBox", "0 0 " + stringWidth + " " + lineHeight);
        //fontSvg.setAttribute("width", size.x);
        fontSvg.setAttribute("data-asc", ratAsc);
        console.log(fontSvg); // getting svg code
      
        let textPathSvg = fontSvg.querySelector(".glyph");
        let textPathLength = textPathSvg.getTotalLength();
        
        return textPathLength
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/opentype.js/1.3.4/opentype.min.js"></script>

    Alternative: calculate total width from glyph data

    If you don't want to append the svg you could also calculate the viewBox width using the getAdvanceWidth() (for seperate glyph paths) or getBoundingBox() method:

    let firstGlyph = glyphs[0];
    let lastGlyph = glyphs[glyphs.length-1];
    let firstSideBearing = firstGlyph.leftSideBearing * ratio;
    let lastSideBearing = (lastGlyph.advanceWidth - lastGlyph.xMax) * ratio;
    let stringWidth = 0;
    
    if (!singleGylyphs) {
           stringWidth = Math.ceil(path.getBoundingBox().x2);
    } else{
           stringWidth = Math.ceil(font.getAdvanceWidth(params.string, params.fontSize, options) - leftSB - lastRightSideBearing);
    }
    

    let fontSVG = textToPath();
    
    async function textToPath() {
      const text = "Hej";
      const size = { x: 50, y: 25 };
      const fontFile =
        "https://fonts.gstatic.com/s/firasans/v15/va9E4kDNxMZdWfMOD5Vvl4jO.ttf";
      let params = {
        string: text,
        font: fontFile,
        fontSize: size.x,
        decimals: 1,
        singleGylyphs: true
      };
      const font = await opentype.load(params.font);
      let options = params.options;
      let unitsPerEm = font.unitsPerEm;
      let ratio = params.fontSize / unitsPerEm;
      let ascender = font.ascender;
      let descender = Math.abs(font.descender);
      let ratAsc = ascender / unitsPerEm;
      let ratDesc = descender / unitsPerEm;
      let yOffset = params.fontSize * ratAsc;
      let lineHeight = params.fontSize + params.fontSize * ratDesc;
      let singleGylyphs = params.singleGylyphs;
      let teststring = params.string.split("");
      let glyphs = font.stringToGlyphs(params.string);
      let firstGlyph = glyphs[0];
      let lastGlyph = glyphs[glyphs.length-1];
      let leftSB = firstGlyph.leftSideBearing * ratio;
      
      let textPath = "";
      let path = "";
      let paths = "";
      //individual paths for each glyph
      if (singleGylyphs) {
          paths = font.getPaths(
          params.string,
          -leftSB,
          yOffset,
          params.fontSize,
          options
        );
        paths.forEach(function (path, i) {
          let pathEl = path.toSVG(params.decimals);
          textPath += pathEl.replaceAll(
            "d=",
            'class="glyph glyph-' + teststring[i] + '" d='
          );
        });
      }
      //word (all glyphs) merged to one path
      else {
          path = font.getPath(
          params.string,
          -leftSB,
          yOffset,
          params.fontSize,
          options
        );
        textPath += path
          .toSVG(params.decimals)
          .replaceAll("d=", 'class="glyph" d=');
      }
      
      // render
      let fontSvgWrp = document.createElement("div");
      fontSvgWrp.classList.add("fontSvgWrp");
      let fontSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
      fontSvg.classList.add("svgText");
      fontSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
      fontSvg.innerHTML = textPath;
      fontSvgWrp.appendChild(fontSvg);
      
      
      //calculate viewBox width by getAdvanceWidth() or getBoundingBox()
      let lastRightSideBearing = (lastGlyph.advanceWidth - lastGlyph.xMax) * ratio;
      let stringWidth = 0;  
      if (!singleGylyphs) {
           stringWidth = Math.ceil(path.getBoundingBox().x2);
      } else{
           stringWidth = Math.ceil(font.getAdvanceWidth(params.string, params.fontSize, options) - leftSB - lastRightSideBearing);
      }
      
      // adjust bbox
      fontSvg.setAttribute("viewBox", "0 0 " + stringWidth + " " + lineHeight);
      fontSvg.setAttribute("data-asc", ratAsc);
    
      let textPathSvg = fontSvg.querySelector(".glyph");
      let textPathLength = textPathSvg.getTotalLength();
      document.body.appendChild(fontSvgWrp);
    
      return textPathLength;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/opentype.js/1.3.4/opentype.min.js"></script>