@bitbybit-dev/base
Version:
Bit By Bit Developers Base CAD Library to Program Geometry
242 lines (241 loc) • 7.84 kB
JavaScript
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;
}
}