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?
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();
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>
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>