text-svg
Version:
Convert text to SVG for node.js
189 lines (188 loc) • 6.58 kB
JavaScript
// index.ts
import { registerFont, createCanvas } from "canvas";
import { Readable } from "stream";
var text2svg = (text, inputOptions = {}) => {
const options = parseOptions(inputOptions);
if (options.localFontPath && options.localFontName) {
registerFont(options.localFontPath, {
family: options.localFontName
});
}
const canvas = createCanvas(0, 0, "svg");
const ctx = canvas.getContext("2d");
const max = {
left: 0,
right: 0,
ascent: 0,
descent: 0
};
let lastDescent = 0;
const lineProps = [];
text.split("\n").forEach((line) => {
ctx.font = options.font;
const words = line.split(" ");
let newLine = "";
let left, right, ascent, descent;
for (let n = 0; n < words.length; n++) {
let testLine;
if (n === 0) {
testLine = words[n];
} else {
testLine = newLine + " " + words[n];
}
const metrics2 = ctx.measureText(testLine);
if (options.wrap && options.maxWidth && metrics2.width > options.maxWidth && n > 0) {
const metrics3 = ctx.measureText(newLine);
left = -1 * metrics3.actualBoundingBoxLeft;
right = metrics3.actualBoundingBoxRight;
ascent = metrics3.actualBoundingBoxAscent;
descent = metrics3.actualBoundingBoxDescent;
max.left = Math.max(max.left, left);
max.right = Math.max(max.right, right);
max.ascent = Math.max(max.ascent, ascent);
max.descent = Math.max(max.descent, descent);
lastDescent = descent;
lineProps.push({
line: newLine,
left,
right,
ascent,
descent
});
newLine = words[n];
} else {
newLine = testLine;
}
}
const metrics = ctx.measureText(newLine);
left = -1 * metrics.actualBoundingBoxLeft;
right = metrics.actualBoundingBoxRight;
ascent = metrics.actualBoundingBoxAscent;
descent = metrics.actualBoundingBoxDescent;
max.left = Math.max(max.left, left);
max.right = Math.max(max.right, right);
max.ascent = Math.max(max.ascent, ascent);
max.descent = Math.max(max.descent, descent);
lastDescent = descent;
lineProps.push({
line: newLine,
left,
right,
ascent,
descent
});
});
const lineHeight = max.ascent + max.descent + options.lineSpacing;
const contentWidth = options.width ? options.width : max.left + max.right;
const contentHeight = lineHeight * lineProps.length - options.lineSpacing - (max.descent - lastDescent);
if (options.width) {
canvas.width = options.width;
} else {
canvas.width = Math.ceil(contentWidth) + options.borderLeftWidth + options.borderRightWidth + options.paddingLeft + options.paddingRight;
}
if (options.height) {
canvas.height = options.height;
} else {
canvas.height = Math.ceil(contentHeight) + options.borderTopWidth + options.borderBottomWidth + options.paddingTop + options.paddingBottom;
}
const hasBorder = options.borderLeftWidth || options.borderTopWidth || options.borderRightWidth || options.borderBottomWidth;
if (hasBorder) {
ctx.fillStyle = options.borderColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
if (options.backgroundColor) {
ctx.fillStyle = options.backgroundColor;
ctx.fillRect(
options.borderLeftWidth,
options.borderTopWidth,
canvas.width - (options.borderLeftWidth + options.borderRightWidth),
canvas.height - (options.borderTopWidth + options.borderBottomWidth)
);
} else if (hasBorder) {
ctx.clearRect(
options.borderLeftWidth,
options.borderTopWidth,
canvas.width - (options.borderLeftWidth + options.borderRightWidth),
canvas.height - (options.borderTopWidth + options.borderBottomWidth)
);
}
ctx.font = options.font;
ctx.fillStyle = options.textColor;
ctx.antialias = "gray";
ctx.textAlign = options.textAlign;
ctx.lineWidth = options.strokeWidth;
ctx.strokeStyle = options.strokeColor;
let offsetY = options.borderTopWidth + options.paddingTop;
lineProps.forEach((lineProp) => {
let x = 0;
const y = max.ascent + offsetY;
switch (options.textAlign) {
case "start":
case "left":
x = lineProp.left + options.borderLeftWidth + options.paddingLeft;
break;
case "end":
case "right":
x = canvas.width - lineProp.left - options.borderRightWidth - options.paddingRight;
break;
case "center":
x = contentWidth / 2 + options.borderLeftWidth + options.paddingLeft;
break;
}
ctx.fillText(lineProp.line, x, y);
if (options.strokeWidth > 0) {
ctx.strokeText(lineProp.line, x, y);
}
offsetY += lineHeight;
});
switch (options.output) {
case "buffer":
return canvas.toBuffer();
case "stream": {
const readable = new Readable();
readable._read = () => {
readable.push(canvas.toBuffer());
readable.push(null);
};
return readable;
}
case "dataURL":
return canvas.toDataURL();
case "canvas":
return canvas;
default:
throw new Error(`output type:${options.output} is not supported.`);
}
};
function parseOptions(options) {
return {
font: options.font || "30px sans-serif",
textAlign: options.textAlign || "left",
textColor: options.textColor || options.color || "black",
backgroundColor: options.bgColor || options.backgroundColor || null,
lineSpacing: options.lineSpacing || 0,
strokeWidth: options.strokeWidth || 0,
strokeColor: options.strokeColor || "white",
paddingLeft: options.paddingLeft || options.padding || 0,
paddingTop: options.paddingTop || options.padding || 0,
paddingRight: options.paddingRight || options.padding || 0,
paddingBottom: options.paddingBottom || options.padding || 0,
borderLeftWidth: options.borderLeftWidth || options.borderWidth || 0,
borderTopWidth: options.borderTopWidth || options.borderWidth || 0,
borderBottomWidth: options.borderBottomWidth || options.borderWidth || 0,
borderRightWidth: options.borderRightWidth || options.borderWidth || 0,
borderColor: options.borderColor || "black",
localFontName: options.localFontName || null,
localFontPath: options.localFontPath || null,
wrap: options.wrap || false,
maxWidth: options.maxWidth || void 0,
output: options.output || "buffer",
width: options.width || void 0,
height: options.height || void 0
};
}
var index_default = text2svg;
export {
index_default as default,
text2svg
};