forked from bro-students/multy-stub
799 lines
28 KiB
JavaScript
799 lines
28 KiB
JavaScript
"use strict";
|
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
});
|
|
};
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.parseSVG = exports.fetchSVGContent = exports.warpSvg = exports.getRandomEntityPath = exports.writeSVGToFile = exports.generateUniqueSVGPath = exports.createCharacterDirectory = exports.validateName = exports.numberOfFiles = exports.svgLinePathNames = exports.generateSvg = exports.printSVG = void 0;
|
|
const fs_1 = __importDefault(require("fs"));
|
|
const paths_1 = require("../paths");
|
|
const jsdom_1 = __importDefault(require("jsdom"));
|
|
/**
|
|
* Returns the number of files in a directory.
|
|
* @param dir - The directory path.
|
|
* @returns A promise that resolves to the number of files in the directory.
|
|
*/
|
|
const numberOfFiles = (dir) => __awaiter(void 0, void 0, void 0, function* () {
|
|
try {
|
|
const files = yield fs_1.default.promises.readdir(dir);
|
|
return files.length;
|
|
}
|
|
catch (err) {
|
|
// File is not found
|
|
return 0;
|
|
}
|
|
});
|
|
exports.numberOfFiles = numberOfFiles;
|
|
/**
|
|
* Returns a string with a valid name based on the input string.
|
|
* Replaces invalid characters with their corresponding names or "lower-" / "upper-" prefix.
|
|
* @param name - The input string to be validated.
|
|
* @returns A string with a valid name.
|
|
*/
|
|
const validateName = (name) => {
|
|
const map = {
|
|
"?": "questionMark",
|
|
"*": "asterisk",
|
|
"/": "slash",
|
|
"\\": "backslash",
|
|
":": "colon",
|
|
"|": "pipe",
|
|
"<": "lessThan",
|
|
">": "greaterThan",
|
|
'"': "doubleQuote",
|
|
"'": "singleQuote",
|
|
"@": "at",
|
|
"#": "hash",
|
|
$: "dollar",
|
|
"%": "percent",
|
|
"^": "caret",
|
|
"&": "ampersand",
|
|
"(": "leftParenthesis",
|
|
")": "rightParenthesis",
|
|
"-": "hyphen",
|
|
_: "underscore",
|
|
"=": "equal",
|
|
"+": "plus",
|
|
"{": "leftCurlyBrace",
|
|
"}": "rightCurlyBrace",
|
|
"[": "leftSquareBracket",
|
|
"]": "rightSquareBracket",
|
|
",": "comma",
|
|
".": "period",
|
|
"!": "exclamationMark",
|
|
"~": "tilde",
|
|
"`": "graveAccent",
|
|
};
|
|
const numbers = {
|
|
"0": "zero",
|
|
"1": "one",
|
|
"2": "two",
|
|
"3": "three",
|
|
"4": "four",
|
|
"5": "five",
|
|
"6": "six",
|
|
"7": "seven",
|
|
"8": "eight",
|
|
"9": "nine",
|
|
};
|
|
if (name in map)
|
|
return map[name];
|
|
if (name in numbers)
|
|
return numbers[name];
|
|
// distingush between upper and lower case
|
|
if (isUpperCase(name))
|
|
return `upper-${name}`;
|
|
return `lower-${name}`;
|
|
};
|
|
exports.validateName = validateName;
|
|
const isUpperCase = (char) => {
|
|
return char === char.toUpperCase();
|
|
};
|
|
/**
|
|
* Returns a random entity path for a given user and character name.
|
|
* @param userName - The name of the user.
|
|
* @param charName - The title of the character.
|
|
* @returns A promise that resolves to a string representing the path to the random entity or empty.
|
|
*/
|
|
const getRandomEntityPath = (username, charName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
try {
|
|
const basePath = `${paths_1.PROFILES_PATH}/${username}/${charName}`;
|
|
const characters = yield fs_1.default.promises.readdir(basePath);
|
|
const randomIndex = Math.floor(Math.random() * characters.length);
|
|
return `${basePath}/${characters[randomIndex]}`;
|
|
}
|
|
catch (err) {
|
|
console.error("Could not get random entity path");
|
|
return "";
|
|
}
|
|
});
|
|
exports.getRandomEntityPath = getRandomEntityPath;
|
|
/**
|
|
* Checks if a given character path contains any special characters.
|
|
* Special characters include: questionMark, asterisk, slash, backslash, colon, pipe, lessThan, greaterThan,
|
|
* doubleQuote, singleQuote, at, hash, dollar, percent, caret, ampersand, leftParenthesis, rightParenthesis,
|
|
* hyphen, underscore, equal, plus, leftCurlyBrace, rightCurlyBrace, leftSquareBracket, rightSquareBracket,
|
|
* comma, period, exclamationMark, tilde, graveAccent.
|
|
*
|
|
* @param charPath - The character path to check.
|
|
* @returns True if the character path contains any special characters, false otherwise.
|
|
*/
|
|
const isSpecialChar = (charPath) => {
|
|
const specialLocatedTop = [
|
|
"singleQuote",
|
|
"doubleQuote",
|
|
"graveAccent",
|
|
"asterisk",
|
|
"caret",
|
|
];
|
|
const specialLocatedMiddle = [
|
|
"colon",
|
|
"lessThan",
|
|
"greaterThan",
|
|
"leftParenthesis",
|
|
"rightParenthesis",
|
|
"hyphen",
|
|
"equal",
|
|
"plus",
|
|
"leftCurlyBrace",
|
|
"rightCurlyBrace",
|
|
"leftSquareBracket",
|
|
"rightSquareBracket",
|
|
"exclamationMark",
|
|
"tilde",
|
|
];
|
|
const specialLocatedBottom = [
|
|
"underscore",
|
|
"questionMark",
|
|
"slash",
|
|
"backslash",
|
|
"pipe",
|
|
"exclamationMark",
|
|
"comma",
|
|
"period",
|
|
];
|
|
let isSpecial = false;
|
|
let position = "bottom";
|
|
for (const special of specialLocatedTop) {
|
|
if (charPath.includes(special)) {
|
|
isSpecial = true;
|
|
position = "top";
|
|
break;
|
|
}
|
|
}
|
|
if (!isSpecial) {
|
|
for (const special of specialLocatedMiddle) {
|
|
if (charPath.includes(special)) {
|
|
isSpecial = true;
|
|
position = "middle";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!isSpecial) {
|
|
for (const special of specialLocatedBottom) {
|
|
if (charPath.includes(special)) {
|
|
isSpecial = true;
|
|
position = "bottom";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return { isSpecial, position };
|
|
};
|
|
const specialWidthGlyphs = (glyphPath) => {
|
|
const symbols = {
|
|
colon: {
|
|
width: 5,
|
|
maxWidth: 7,
|
|
height: 15,
|
|
maxHeight: 20,
|
|
},
|
|
lessThan: {
|
|
width: 15,
|
|
height: 20,
|
|
maxWidth: 25,
|
|
maxHeight: 30,
|
|
},
|
|
greaterThan: {
|
|
width: 15,
|
|
height: 20,
|
|
maxWidth: 25,
|
|
maxHeight: 30,
|
|
},
|
|
leftParenthesis: {
|
|
width: 8,
|
|
height: 20,
|
|
maxWidth: 16,
|
|
maxHeight: 40,
|
|
},
|
|
rightParenthesis: {
|
|
width: 8,
|
|
height: 20,
|
|
maxWidth: 16,
|
|
maxHeight: 40,
|
|
},
|
|
hyphen: {
|
|
width: 10,
|
|
height: 2,
|
|
maxWidth: 20,
|
|
maxHeight: 4,
|
|
},
|
|
equal: {
|
|
width: 10,
|
|
height: 5,
|
|
maxWidth: 20,
|
|
maxHeight: 10,
|
|
},
|
|
plus: {
|
|
width: 10,
|
|
height: 10,
|
|
maxWidth: 20,
|
|
maxHeight: 20,
|
|
},
|
|
leftCurlyBrace: {
|
|
width: 12,
|
|
height: 25,
|
|
maxWidth: 24,
|
|
maxHeight: 50,
|
|
},
|
|
rightCurlyBrace: {
|
|
width: 12,
|
|
height: 25,
|
|
maxWidth: 24,
|
|
maxHeight: 50,
|
|
},
|
|
leftSquareBracket: {
|
|
width: 8,
|
|
height: 20,
|
|
maxWidth: 16,
|
|
maxHeight: 40,
|
|
},
|
|
rightSquareBracket: {
|
|
width: 8,
|
|
height: 20,
|
|
maxWidth: 16,
|
|
maxHeight: 40,
|
|
},
|
|
exclamationMark: {
|
|
width: 6,
|
|
height: 30,
|
|
maxWidth: 9,
|
|
maxHeight: 30,
|
|
},
|
|
tilde: {
|
|
width: 20,
|
|
height: 8,
|
|
maxWidth: 30,
|
|
maxHeight: 10,
|
|
},
|
|
comma: {
|
|
width: 5,
|
|
height: 10,
|
|
maxWidth: 10,
|
|
maxHeight: 20,
|
|
},
|
|
dot: {
|
|
width: 5,
|
|
height: 5,
|
|
maxWidth: 10,
|
|
maxHeight: 10,
|
|
},
|
|
questionMark: {
|
|
width: 10,
|
|
height: 30,
|
|
maxWidth: 20,
|
|
maxHeight: 40,
|
|
},
|
|
underscore: {
|
|
width: 10,
|
|
height: 2,
|
|
maxWidth: 20,
|
|
maxHeight: 4,
|
|
},
|
|
pipe: {
|
|
width: 5,
|
|
height: 30,
|
|
maxWidth: 10,
|
|
maxHeight: 40,
|
|
},
|
|
one: {
|
|
width: 5,
|
|
height: 30,
|
|
maxWidth: 10,
|
|
maxHeight: 40,
|
|
},
|
|
"lower-m": {
|
|
width: 20,
|
|
height: 30,
|
|
maxWidth: 30,
|
|
maxHeight: 40,
|
|
},
|
|
"lower-q": {
|
|
width: 20,
|
|
height: 30,
|
|
maxWidth: 30,
|
|
maxHeight: 40,
|
|
},
|
|
"lower-y": {
|
|
width: 20,
|
|
height: 30,
|
|
maxWidth: 30,
|
|
maxHeight: 40,
|
|
},
|
|
"lower-g": {
|
|
width: 20,
|
|
height: 30,
|
|
maxWidth: 30,
|
|
maxHeight: 40,
|
|
},
|
|
"lower-p": {
|
|
width: 20,
|
|
height: 30,
|
|
maxWidth: 30,
|
|
maxHeight: 40,
|
|
},
|
|
"lower-j": {
|
|
width: 20,
|
|
height: 30,
|
|
maxWidth: 30,
|
|
maxHeight: 40,
|
|
},
|
|
"lower-i": {
|
|
width: 5,
|
|
height: 30,
|
|
maxWidth: 10,
|
|
maxHeight: 40,
|
|
},
|
|
"lower-l": {
|
|
width: 10,
|
|
height: 40,
|
|
maxWidth: 10,
|
|
maxHeight: 40,
|
|
},
|
|
};
|
|
let isSpecialGlyph = false;
|
|
let values = {
|
|
width: 20,
|
|
height: 30,
|
|
maxWidth: 30,
|
|
maxHeight: 40,
|
|
};
|
|
for (const symbol in symbols) {
|
|
if (glyphPath.includes(symbol)) {
|
|
isSpecialGlyph = true;
|
|
values = symbols[symbol];
|
|
break;
|
|
}
|
|
}
|
|
return { isSpecialGlyph, values };
|
|
};
|
|
/**
|
|
* Determines if the given character path should extend below the baseline.
|
|
* @param charPath The character path to check.
|
|
* @returns A boolean indicating whether the character path should extend below the baseline.
|
|
*/
|
|
const extendBelowBaseline = (charPath) => {
|
|
const extendBelowBaseline = [
|
|
"lower-q",
|
|
"lower-y",
|
|
"lower-g",
|
|
"lower-p",
|
|
"lower-j",
|
|
];
|
|
let extend = false;
|
|
for (const char of extendBelowBaseline) {
|
|
if (charPath.includes(char)) {
|
|
extend = true;
|
|
break;
|
|
}
|
|
}
|
|
return extend;
|
|
};
|
|
/**
|
|
* Returns an object containing an array of SVG paths for each word in the input text and an array of missing characters.
|
|
* @param userName - The name of the user.
|
|
* @param text - The input text to generate SVG paths for.
|
|
* @returns An object containing an array of SVG paths for each word in the input text and an array of missing characters.
|
|
*/
|
|
const svgLinePathNames = (userName, text) => __awaiter(void 0, void 0, void 0, function* () {
|
|
let paths = [];
|
|
let missing = [];
|
|
const words = text.split(" ");
|
|
for (const word of words) {
|
|
let wordPath = [];
|
|
const chars = word.trim().split("");
|
|
for (const c of chars) {
|
|
const cName = validateName(c);
|
|
const path = yield getRandomEntityPath(userName, cName);
|
|
if (path === "") {
|
|
missing.push(c);
|
|
}
|
|
else {
|
|
wordPath.push(path);
|
|
}
|
|
}
|
|
paths.push(wordPath);
|
|
}
|
|
return {
|
|
missing,
|
|
paths,
|
|
};
|
|
});
|
|
exports.svgLinePathNames = svgLinePathNames;
|
|
/**
|
|
* Parses an SVG string and returns the SVG element and its paths.
|
|
* @param svg - The SVG string to parse.
|
|
* @returns An object containing the SVG element and its paths.
|
|
*/
|
|
const parseSVG = (svg) => {
|
|
const dom = new jsdom_1.default.JSDOM(svg, { contentType: "image/svg+xml" });
|
|
const svgElement = dom.window.document.querySelector("g");
|
|
return {
|
|
parent: svgElement,
|
|
};
|
|
};
|
|
exports.parseSVG = parseSVG;
|
|
/**
|
|
* Returns a random number between the given minimum and maximum values, with an optional percentage range.
|
|
* @param min The minimum value for the random number.
|
|
* @param max The maximum value for the random number.
|
|
* @param percentage The percentage range for the random number. Defaults to 25%.
|
|
* @returns A random number between the given minimum and maximum values.
|
|
*/
|
|
const getRandomNumber = (min, max, percentage = 25, scale = 1) => {
|
|
const howRandom = Math.round((max - min) * (percentage / 100));
|
|
const randomNumber = Math.floor(Math.random() * (howRandom + 1));
|
|
return Math.round(min + randomNumber) * scale;
|
|
};
|
|
// Get standard values for the characters
|
|
const getStandardValues = (isSpecial, position) => {
|
|
// Standard values for the characters
|
|
const standard = {
|
|
char_width: 20,
|
|
char_height: 30,
|
|
space_width: 10,
|
|
special_char_located_top_width: 5,
|
|
special_char_located_middle_width: 15,
|
|
special_char_located_top_max_width: 10,
|
|
special_char_located_middle_max_width: 20,
|
|
special_char_located_bottom_width: 5,
|
|
special_char_located_bottom_max_width: 10,
|
|
special_char_height_top: 10,
|
|
special_char_height_middle: 20,
|
|
special_char_height_bottom: 30,
|
|
max_char_width: 30,
|
|
max_char_height: 40,
|
|
};
|
|
const standerdWidth = isSpecial
|
|
? position === "top"
|
|
? standard.special_char_located_top_width
|
|
: position === "middle"
|
|
? standard.special_char_located_middle_width
|
|
: standard.special_char_located_bottom_width
|
|
: standard.char_width;
|
|
const standerdMaxWidth = isSpecial
|
|
? position === "top"
|
|
? standard.special_char_located_top_max_width
|
|
: position === "middle"
|
|
? standard.special_char_located_middle_max_width
|
|
: standard.special_char_located_bottom_max_width
|
|
: standard.max_char_width;
|
|
const standerdHeight = isSpecial
|
|
? position === "top"
|
|
? standard.special_char_height_top
|
|
: position === "middle"
|
|
? standard.special_char_height_middle
|
|
: standard.special_char_height_bottom
|
|
: standard.char_height;
|
|
const standerdMaxHeight = isSpecial
|
|
? position === "top"
|
|
? standard.special_char_height_top
|
|
: position === "middle"
|
|
? standard.special_char_height_middle
|
|
: standard.special_char_height_bottom
|
|
: standard.max_char_height;
|
|
return { standerdWidth, standerdMaxWidth, standerdHeight, standerdMaxHeight };
|
|
};
|
|
// Get Random Defects
|
|
const getRandomDefects = (defects, scaleFactor, charPath = "") => {
|
|
const { baseline, kerning, letterSize, lineSpacing, indent } = defects;
|
|
const { isSpecialGlyph, values } = specialWidthGlyphs(charPath);
|
|
if (isSpecialGlyph) {
|
|
const { width, height, maxWidth, maxHeight } = values;
|
|
const letterSizeWidthRandom = getRandomNumber(width, maxWidth, letterSize, scaleFactor);
|
|
const letterSizeRandomHeight = getRandomNumber(height, maxHeight, letterSize, scaleFactor);
|
|
return {
|
|
indentRandom: 0,
|
|
lineSpacingRandom: 0,
|
|
kerningDeffects: 0,
|
|
baselineOffset: 0,
|
|
letterSizeWidthRandom,
|
|
letterSizeRandomHeight,
|
|
};
|
|
}
|
|
const { isSpecial, position } = isSpecialChar(charPath);
|
|
const { standerdWidth, standerdMaxWidth, standerdHeight, standerdMaxHeight } = getStandardValues(isSpecial, position);
|
|
const indentRandom = getRandomNumber(0, 80, indent, scaleFactor);
|
|
const lineSpacingRandom = getRandomNumber(0, 30, lineSpacing, scaleFactor);
|
|
const kerningDeffects = getRandomNumber(5, -5, kerning, scaleFactor);
|
|
const baselineOffset = getRandomNumber(0, 10, baseline, scaleFactor);
|
|
const letterSizeWidthRandom = getRandomNumber(standerdWidth, standerdMaxWidth, letterSize, scaleFactor);
|
|
const letterSizeRandomHeight = getRandomNumber(standerdHeight, standerdMaxHeight, letterSize, scaleFactor);
|
|
return {
|
|
indentRandom,
|
|
lineSpacingRandom,
|
|
kerningDeffects,
|
|
baselineOffset,
|
|
letterSizeWidthRandom,
|
|
letterSizeRandomHeight,
|
|
};
|
|
};
|
|
const fetchSVGContent = (path) => {
|
|
return fs_1.default.readFileSync(path, "utf-8");
|
|
};
|
|
exports.fetchSVGContent = fetchSVGContent;
|
|
/**
|
|
* Assembles a word by processing each character and generating SVG elements.
|
|
*
|
|
* @param {AssembleWord} options - The options for assembling the word.
|
|
* @param {string} options.word - The word to assemble.
|
|
* @param {number} options.offsetX - The initial X offset.
|
|
* @param {number} options.offsetY - The initial Y offset.
|
|
* @param {number} options.scaleFactor - The scale factor for the word.
|
|
* @param {number} options.indentRandom - The random indentation for the word.
|
|
* @param {Defects} options.defects - The defects for the word.
|
|
*
|
|
* @returns {Object} - The assembled word elements, the height of the word, and the updated X offset.
|
|
*/
|
|
const assembleWord = ({ word, offsetX, offsetY, scaleFactor, indentRandom, defects, }) => {
|
|
const space_width = 30;
|
|
let wordElements = [];
|
|
let wordHeight = 0;
|
|
if (word.length === 0) {
|
|
offsetX += space_width;
|
|
}
|
|
else {
|
|
offsetX += indentRandom;
|
|
for (let j = 0; j < word.length; j++) {
|
|
const char = word[j];
|
|
const { kerningDeffects } = getRandomDefects(defects, scaleFactor);
|
|
const { isSpecial, position } = isSpecialChar(char);
|
|
const { letterSizeWidthRandom, letterSizeRandomHeight } = getRandomDefects(defects, scaleFactor, char);
|
|
// You need to load the SVG content from the file
|
|
const svgFileContent = fetchSVGContent(char);
|
|
// Get the width and height of the SVG and its paths children
|
|
const { parent } = parseSVG(svgFileContent);
|
|
const width = parent === null || parent === void 0 ? void 0 : parent.getAttribute("width");
|
|
const height = parent === null || parent === void 0 ? void 0 : parent.getAttribute("height");
|
|
// Scale down the width to the standerd width while keeping the aspect ratio
|
|
// const widthScale = letterSizeWidthRandom / Number(width);
|
|
// const heightScale = letterSizeRandomHeight / Number(height);
|
|
// Calculate the scaled width and height
|
|
// const scale = Math.pow(
|
|
// Math.min(widthScale, heightScale),
|
|
// 1 / scaleFactor
|
|
// );
|
|
const scale = scaleFactor;
|
|
const scaledWidth = Number(width) * scale;
|
|
const scaledHeight = Number(height) * scale;
|
|
// Change the width and height of the SVG
|
|
parent === null || parent === void 0 ? void 0 : parent.setAttribute("width", String(scaledWidth));
|
|
parent === null || parent === void 0 ? void 0 : parent.setAttribute("height", String(scaledHeight));
|
|
// Add translation and scale to the SVG content
|
|
parent === null || parent === void 0 ? void 0 : parent.setAttribute("transform", `translate(${offsetX}, ${offsetY}) scale(${scale})`);
|
|
// Add the SVG content to the SVG content variable
|
|
offsetX += scaledWidth + kerningDeffects;
|
|
wordElements.push({
|
|
element: parent,
|
|
isSpecial,
|
|
position,
|
|
extendBelowBaseline: extendBelowBaseline(char),
|
|
});
|
|
wordHeight = Math.max(wordHeight, scaledHeight);
|
|
}
|
|
// Add a space between words
|
|
offsetX += space_width * scaleFactor;
|
|
}
|
|
return { wordElements, wordHeight, offsetX };
|
|
};
|
|
/**
|
|
* Assembles a line of text into SVG elements.
|
|
*
|
|
* @param {AssembleLine} options - The options for assembling the line.
|
|
* @param {number} options.defects - The number of defects in the line.
|
|
* @param {number} options.scaleFactor - The scale factor for the line.
|
|
* @param {string[]} options.line - The words in the line.
|
|
* @param {number} options.offsetY - The vertical offset of the line.
|
|
* @returns {Object} - The assembled line content and updated vertical offset.
|
|
*/
|
|
const assembleLine = ({ defects, scaleFactor, line, offsetY, }) => {
|
|
const { indentRandom, lineSpacingRandom } = getRandomDefects(defects, scaleFactor);
|
|
let lineContent = "";
|
|
// Add a line container for each line of text
|
|
let lineHeight = 0;
|
|
let offsetX = indentRandom;
|
|
let lineElements = [];
|
|
// Detect the empty line
|
|
const lineArray = line.flat();
|
|
if (lineArray.length === 0) {
|
|
offsetY += 50 * scaleFactor + lineSpacingRandom;
|
|
return { lineContent, offsetY, offsetX };
|
|
}
|
|
for (let i = 0; i < line.length; i++) {
|
|
const word = line[i];
|
|
const { wordElements, wordHeight, offsetX: newOffsetX, } = assembleWord({
|
|
word,
|
|
offsetX,
|
|
offsetY,
|
|
scaleFactor,
|
|
indentRandom,
|
|
defects,
|
|
});
|
|
// Update the offset
|
|
offsetX = newOffsetX;
|
|
lineHeight = Math.max(lineHeight, wordHeight);
|
|
lineElements = lineElements.concat(wordElements);
|
|
}
|
|
// Align the line elements to the bottom of the line
|
|
const aligned = verticalAlign(lineElements, lineHeight, offsetY, scaleFactor, defects);
|
|
[lineElements, lineHeight] = [aligned.lineElements, aligned.lineHeight];
|
|
offsetY += lineHeight || 10 * scaleFactor + lineSpacingRandom;
|
|
// Append the line elements to the SVG content
|
|
lineElements.forEach((e) => {
|
|
lineContent += e.element.outerHTML;
|
|
});
|
|
return { lineContent, offsetY, offsetX };
|
|
};
|
|
const verticalAlign = (lineElements, lineHeight, offsetY, scaleFactor, defects) => {
|
|
// Align the line elements to the bottom of the line
|
|
let extended = false;
|
|
const extendValue = 10 * scaleFactor;
|
|
lineElements.forEach((e) => {
|
|
const { element, isSpecial, position, extendBelowBaseline } = e;
|
|
// Match all numbers in style attribute
|
|
const style = element.getAttribute("transform");
|
|
const regex = /[-+]?[0-9]*\.?[0-9]+/g;
|
|
const { baselineOffset } = getRandomDefects(defects, scaleFactor);
|
|
const [x, y, s] = style.match(regex);
|
|
const elementHeight = parseInt(element.getAttribute("height"));
|
|
const lineYOffset = Math.abs(lineHeight - elementHeight);
|
|
const elementOffset = Math.abs(lineHeight - elementHeight);
|
|
if (isSpecial) {
|
|
if (position === "top") {
|
|
element.setAttribute("transform", `translate(${x}, ${offsetY}) scale(${s})`);
|
|
}
|
|
else if (position === "middle") {
|
|
element.setAttribute("transform", `translate(${x}, ${Number(y) + lineYOffset / 2}) scale(${s})`);
|
|
}
|
|
else {
|
|
element.setAttribute("transform", `translate(${x}, ${Number(y) + lineYOffset}) scale(${s})`);
|
|
}
|
|
}
|
|
else {
|
|
if (extendBelowBaseline) {
|
|
element.setAttribute("transform", `translate(${x}, ${Number(y) + lineYOffset + extendValue}) scale(${s})`);
|
|
extended = true;
|
|
}
|
|
else {
|
|
element.setAttribute("transform", `translate(${x}, ${Number(y) + elementOffset + baselineOffset}) scale(${s})`);
|
|
}
|
|
}
|
|
});
|
|
// Fix the line height
|
|
if (extended)
|
|
lineHeight += extendValue;
|
|
// Add line margin
|
|
lineHeight += 3 * scaleFactor;
|
|
return { lineElements, lineHeight };
|
|
};
|
|
const warpSvg = (svg, width, height) => {
|
|
// Create empty SVG document
|
|
const dom = new jsdom_1.default.JSDOM();
|
|
const svgWrapper = dom.window.document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
// Set the SVG width and height
|
|
svgWrapper.setAttribute("width", String(width));
|
|
svgWrapper.setAttribute("height", String(height));
|
|
// Add the SVG content to the SVG document
|
|
svgWrapper.innerHTML = svg;
|
|
return svgWrapper === null || svgWrapper === void 0 ? void 0 : svgWrapper.outerHTML;
|
|
};
|
|
exports.warpSvg = warpSvg;
|
|
/**
|
|
* Writes the SVG content to a file and returns the server file path.
|
|
* @param svgContent - The SVG content to be written to the file.
|
|
* @returns The server file path of the generated SVG file.
|
|
*/
|
|
const writeSVG = (svgContent, totalHeight, totalWidth) => __awaiter(void 0, void 0, void 0, function* () {
|
|
// wrap the SVG content in an SVG document
|
|
const outputFile = `<svg width="${totalWidth}" height="${totalHeight}" xmlns="http://www.w3.org/2000/svg">${svgContent}</svg>`; // Change this to your desired SVG content
|
|
// const outputFile = svgFlatten(svgContent).pathify().value();
|
|
// Write the SVG content to a file
|
|
const svgFilePath = `${paths_1.STATIC_PATH}/generated.svg`; // Change this to your desired file path
|
|
fs_1.default.writeFileSync(svgFilePath, outputFile);
|
|
// Return the SVG file path
|
|
const basePath = `${process.env.BASE_URL || "http://localhost"}`;
|
|
const port = process.env.PORT || "5000";
|
|
// Date.now() is used to prevent caching (cache busting)
|
|
const serverFilePath = `${basePath}:${port}/static/generated.svg?v=${Date.now()}`;
|
|
return serverFilePath;
|
|
});
|
|
const generateSvg = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
const { paths, scaleFactor = 1, defects } = options;
|
|
let svgContent = "";
|
|
let offsetY = 0;
|
|
let totalHeight = 0;
|
|
let totalWidth = 0;
|
|
// Iterate over the lines, words and chars creating the SVG content
|
|
paths.forEach((line) => {
|
|
const { lineContent, offsetY: newOffsetY, offsetX, } = assembleLine({
|
|
defects,
|
|
scaleFactor,
|
|
line,
|
|
offsetY,
|
|
});
|
|
svgContent += lineContent;
|
|
offsetY = newOffsetY;
|
|
totalHeight = Math.max(totalHeight, offsetY);
|
|
totalWidth = Math.max(totalWidth, offsetX);
|
|
});
|
|
// Write the SVG content to a file
|
|
const serverFilePath = yield writeSVG(svgContent, totalHeight, totalWidth);
|
|
return { serverFilePath, totalWidth, totalHeight };
|
|
});
|
|
exports.generateSvg = generateSvg;
|
|
/**
|
|
* Renders an SVG file to a connected plotter.
|
|
* @param inputPath - The path to the input SVG file.
|
|
* @param outputPath - Optional. The path to the output SVG file.
|
|
* @returns An error if one occurs during the rendering process.
|
|
*/
|
|
const printSVG = (inputPath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
// Execute the following command : axicli inputPath usin os.system
|
|
const { execSync } = require("child_process");
|
|
try {
|
|
const command = `axicli "${inputPath}"`;
|
|
const result = execSync(command, { encoding: "utf-8" });
|
|
// Process the result and return an object
|
|
return {
|
|
success: true,
|
|
message: `success: ${result}`,
|
|
};
|
|
}
|
|
catch (error) {
|
|
const errorMessage = error;
|
|
return {
|
|
success: false,
|
|
message: `error: ${errorMessage.message}`,
|
|
};
|
|
}
|
|
});
|
|
exports.printSVG = printSVG;
|
|
// Function to create the character directory
|
|
const createCharacterDirectory = (userPath, charTitle) => __awaiter(void 0, void 0, void 0, function* () {
|
|
const characters = yield fs_1.default.promises.readdir(userPath);
|
|
if (!characters.includes(charTitle)) {
|
|
yield fs_1.default.promises.mkdir(`${userPath}/${charTitle}`);
|
|
}
|
|
});
|
|
exports.createCharacterDirectory = createCharacterDirectory;
|
|
// Function to generate a unique SVG filename
|
|
const generateUniqueSVGPath = (characterDir) => __awaiter(void 0, void 0, void 0, function* () {
|
|
const characterLength = yield numberOfFiles(characterDir);
|
|
return `${characterDir}/${characterLength}.svg`;
|
|
});
|
|
exports.generateUniqueSVGPath = generateUniqueSVGPath;
|
|
// Function to write SVG content to a file
|
|
const writeSVGToFile = (svgPath, svgContent) => __awaiter(void 0, void 0, void 0, function* () {
|
|
yield fs_1.default.promises.writeFile(svgPath, svgContent);
|
|
});
|
|
exports.writeSVGToFile = writeSVGToFile;
|