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=",
' 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=", ' 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 xmlns="http://www.w3.org/2000/svg" viewBox="0 0 0 5013.25" width="50" data-asc="0.935"><path 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?
CodePudding user response:
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=",
' 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=", ' 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 glyphs advanceWidth data:
//calculate viewBox width by glyphs' advanceWidths
let totalWidth = 0;
let lastGlyph = glyphs[glyphs.length-1];
let lastSideBearing = (lastGlyph.leftSideBearing) * ratio;
glyphs.forEach(function (glyph, i) {
let advanceWidth = glyph.advanceWidth;
totalWidth = advanceWidth * ratio;
});
let stringWidth = Math.ceil(totalWidth - leftSB - lastSideBearing);
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=",
' 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=", ' 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 glyphs' advanceWidths
let totalWidth = 0;
let lastGlyph = glyphs[glyphs.length-1];
let lastSideBearing = (lastGlyph.leftSideBearing) * ratio;
glyphs.forEach(function (glyph, i) {
let advanceWidth = glyph.advanceWidth;
totalWidth = advanceWidth * ratio;
});
let stringWidth = Math.ceil(totalWidth - leftSB - lastSideBearing);
// 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();
document.body.appendChild(fontSvgWrp);
return textPathLength;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/opentype.js/1.3.4/opentype.min.js"></script>