UNPKG

@bitbybit-dev/base

Version:

Bit By Bit Developers Base CAD Library to Program Geometry

242 lines (241 loc) 7.84 kB
import * as Inputs from "../inputs"; import { defaultsVectorParams } from "../models/simplex"; /** * Contains various text methods. */ export class TextBitByBit { constructor(point) { this.point = point; } /** * Creates a text * @param inputs a text * @returns text * @group create * @shortname text * @drawable false */ create(inputs) { return inputs.text; } /** * Split the text to multiple pieces by a separator * @param inputs a text * @returns text * @group transform * @shortname split * @drawable false */ split(inputs) { return inputs.text.split(inputs.separator); } /** * Replace all occurrences of a text by another text * @param inputs a text * @returns text * @group transform * @shortname replaceAll * @drawable false */ replaceAll(inputs) { return inputs.text.split(inputs.search).join(inputs.replaceWith); } /** * Join multiple items by a separator into text * @param inputs a list of items * @returns text * @group transform * @shortname join * @drawable false */ join(inputs) { return inputs.list.join(inputs.separator); } /** * Transform any item to text * @param inputs any item * @returns text * @group transform * @shortname to string * @drawable false */ toString(inputs) { return inputs.item.toString(); } /** * Transform each item in list to text * @param inputs list of items * @returns texts * @group transform * @shortname to strings * @drawable false */ toStringEach(inputs) { return inputs.list.map(i => i.toString()); } /** * Format a text with values * @param inputs a text and values * @returns formatted text * @group transform * @shortname format * @drawable false */ format(inputs) { return inputs.text.replace(/{(\d+)}/g, (match, number) => { return typeof inputs.values[number] !== "undefined" ? inputs.values[number] : match; }); } /** * Creates a vector segments for character and includes width and height information * @param inputs a text * @returns width, height and segments as json * @group vector * @shortname vector char * @drawable false */ vectorChar(inputs) { const { xOffset, yOffset, font, input, height, extrudeOffset } = this.vectorParamsChar(inputs); let code = input.charCodeAt(0); if (!code || !font[code]) { code = 63; } const glyph = [].concat(font[code]); const ratio = (height - extrudeOffset) / font.height; const extrudeYOffset = (extrudeOffset / 2); const width = glyph.shift() * ratio; const paths = []; let polyline = []; for (let i = 0, il = glyph.length; i < il; i += 2) { const gx = ratio * glyph[i] + xOffset; const gy = ratio * glyph[i + 1] + yOffset + extrudeYOffset; if (glyph[i] !== undefined) { polyline.push([gx, 0, gy]); continue; } paths.push(polyline); polyline = []; i--; } if (polyline.length) { paths.push(polyline); } return { width, height, paths }; } /** * Creates a vector text lines for a given text and includes width and height information * @param inputs a text as string * @returns segments * @group vector * @shortname vector text * @drawable false */ vectorText(inputs) { const { xOffset, yOffset, height, align, extrudeOffset, lineSpacing, letterSpacing } = Object.assign({}, defaultsVectorParams, inputs); const text = inputs.text; if (typeof text !== "string") throw new Error("text must be a string"); // NOTE: Just like CSS letter-spacing, the spacing could be positive or negative const extraLetterSpacing = (height * letterSpacing); // manage the list of lines let maxWidth = 0; // keep track of max width for final alignment let line = { width: 0, height: 0, chars: [] }; let lines = []; const pushLine = () => { maxWidth = Math.max(maxWidth, line.width); if (line.chars.length) lines.push(line); line = { width: 0, height: 0, chars: [] }; }; // convert the text into a list of vector lines let x = xOffset; let y = yOffset; let vchar; const il = text.length; for (let i = 0; i < il; i++) { const character = text[i]; if (character === "\n") { pushLine(); // reset x and y for a new line x = xOffset; y -= height * lineSpacing; continue; } // convert the character vchar = this.vectorChar({ xOffset: x, yOffset: y, height, extrudeOffset, char: character }); const width = vchar.width + extraLetterSpacing; x += width; // update current line line.width += width; line.height = Math.max(line.height, vchar.height); if (character !== " ") { line.chars = line.chars.concat(vchar); } } if (line.chars.length) pushLine(); // align all lines as requested lines = lines.map((line) => { const diff = maxWidth - line.width; if (align === Inputs.Base.horizontalAlignEnum.right) { return this.translateLine({ x: diff }, line); } else if (align === Inputs.Base.horizontalAlignEnum.center) { return this.translateLine({ x: diff / 2 }, line); } else { return line; } }); if (inputs.centerOnOrigin) { const pointsFlat = []; // flatten the lines into a single array of points lines.forEach((line) => { line.chars.forEach((vchar) => { vchar.paths.forEach((path) => { pointsFlat.push(...path); }); }); }); const bbox = this.point.boundingBoxOfPoints({ points: pointsFlat, }); lines.forEach((line) => { line.chars.forEach((vchar) => { vchar.paths = vchar.paths.map((path) => { const pts = this.point.translatePoints({ points: path, translation: [ -bbox.center[0], -bbox.center[1], -bbox.center[2], ], }); return pts; }); return vchar; }); }); } return lines; } vectorParamsChar(inputs) { const params = Object.assign({}, defaultsVectorParams, inputs); params.input = inputs.char || params.char; return params; } translateLine(options, line) { const { x, y } = Object.assign({ x: 0, y: 0 }, options); line.chars = line.chars.map((vchar) => { vchar.paths = vchar.paths.map((path) => { const pts = this.point.translatePoints({ points: path, translation: [x, 0, y], }); return pts; }); return vchar; }); return line; } }