/** * @readonly * * @enum {string} */ export const FrameType = { /** * ╔════════════╗ * ║ Hello, TUI ║ * ╚════════════╝ * * @type {string} Double-lined frame */ Double: "Double", /** * ┌────────────┐ * │ Hello, TUI │ * └────────────┘ * * @type {string} Single-lined frame */ Single: "Single", /** * * Hello, TUI * * * @type {string} Double-lined frame */ Invisible: "Invisible", /** * ( ) * ( Hello, TUI ) * ( ) * * @type {string} Double-lined frame */ Parentheses: "Parentheses", /** * +------------+ * | Hello, TUI | * +------------+ * * @type {string} Double-lined frame */ Basic: "Basic", /** * @protected * Default values for the common frame types. * * [north, south, east, west, northwest, northeast, southwest, southeast] */ values: { Basic: "--||++++", Double: "══║║╔╗╚╝", Invisible: " ", Parentheses: " () ", Single: "──││┌┐└┘", }, }; export class FramingOptions { /** @type {number=0} Vertical Padding; number of vertical whitespace (newlines) between the text and the frame. */ vPadding = 0; /** @type {number=0} Margin ; number of newlines to to insert before and after the framed text */ vMargin = 0; /** @type {number=0} Horizontal Padding; number of whitespace characters to insert between the text and the sides of the frame. */ hPadding = 0; /** @type {number=0} Margin ; number of newlines to to insert before and after the text, but inside the frame */ hMargin = 0; /** @type {FrameType=FrameType.Double} Type of frame to put around the text */ frameType = FrameType.Double; /** @type {number=0} Pad each line to become at least this long */ minLineWidth = 0; // Light block: ░ (U+2591) // Medium block: ▒ (U+2592) // Dark block: ▓ (U+2593) // Solid block: █ (U+2588) /** @type {string} Single character to use as filler inside the frame. */ paddingChar = " "; // character used for padding inside the frame. /** @type {string} Single character to use as filler outside the frame. */ marginChar = " "; /** @type {string} The 8 characters that make up the frame elements */ frameChars = FrameType.values.Double; /** * @param {object} o * @returns {FramingOptions} */ static fromObject(o) { const result = new FramingOptions(); result.vPadding = Math.max(0, Number.parseInt(o.vPadding) || 0); result.hPadding = Math.max(0, Number.parseInt(o.hPadding) || 0); result.vMargin = Math.max(0, Number.parseInt(o.vMargin) || 0); result.hMargin = Math.max(0, Number.parseInt(o.hMargin) || 0); result.minLineWidth = Math.max(0, Number.parseInt(o.hMargin) || 0); result.paddingChar = String(o.paddingChar || " ")[0] || " "; result.marginChar = String(o.marginChar || " ")[0] || " "; // // Do we have custom and valid frame chars? if ( typeof o.frameChars === "string" && o.frameChars.length === FrameType.values.Double.length ) { result.frameChars = o.frameChars; // // do we have document frame type instead ? } else if (o.frameType && FrameType.hasOwnProperty(o.frameType)) { result.frameChars = FrameType.values[o.frameType]; // Fall back to using "Double" frame } else { result.frameChars = FrameType.values.Double; } return result; } } /** * @param {string|string[]} text the text to be framed. If array, each element will be treated as one line, and they are joined so the whole is to be framed. * @param {FramingOptions} options */ export function frameText(text, options) { if (!options) { options = new FramingOptions(); } if (!(options instanceof FramingOptions)) { options = FramingOptions.fromObject(options); } // There is a point to this; each element in the array may contain newlines, // so we have to combine everything into a long text and then split into // individual lines afterwards. if (Array.isArray(text)) { text = text.join("\n"); } if (typeof text !== "string") { console.debug(text); throw new Error( `text argument was neither an array or a string, it was a ${typeof text}`, ); } /** @type {string[]} */ const lines = text.split("\n"); const innerLineLength = Math.max( lines.reduce((accumulator, currentLine) => { if (currentLine.length > accumulator) { return currentLine.length; } return accumulator; }, 0), options.minLineWidth, ); const frameThickness = 1; // always 1 for now. const outerLineLength = 0 + innerLineLength + frameThickness * 2 + options.hPadding * 2 + options.hMargin * 2; // get the frame characters from the frameType. let [ fNorth, // horizontal frame top lines fSouth, // horizontal frame bottom lines fWest, // vertical frame lines on the left side fEast, // vertical frame lines on the right side fNorthWest, // upper left frame corner fNorthEast, // upper right frame corner fSouthWest, // lower left frame corner fSouthEast, // lower right frame corner ] = options.frameChars.split(""); if (fNorth === "§") { fNorth = ""; } if (fSouth === "§") { fSouth = ""; } if (fEast === "§") { fEast = ""; } if (fWest === "§") { fWest = ""; } if (fNorthEast === "§") { fNorthEast = ""; } if (fSouthEast === "§") { fSouthEast = ""; } if (fNorthWest === "§") { fNorthWest = ""; } if (fSouthWest === "§") { fSouthWest = ""; } let output = ""; // // GENERATE THE MARGIN SPACE ABOVE THE FRAMED TEXT // // ( we insert space characters even though ) // ( they wouldn't normally be visible. But ) // ( Some fonts might allow us to see blank ) // ( space, and what if we want to nest many ) // ( frames inside each other? ) // output += (options.marginChar.repeat(outerLineLength) + "\n").repeat( options.vMargin, ); // // GENERATE THE TOP PART OF THE FRAME // ╔════════════╗ // // output += "" + // Make sure JS knows we're adding a string. options.marginChar.repeat(options.hMargin) + // the margin before the frame starts fNorthWest + // northwest frame corner fNorth.repeat(innerLineLength + options.hPadding * 2) + // the long horizontal frame top bar fNorthEast + // northeast frame corner options.marginChar.repeat(options.hMargin) + // the margin after the frame ends "\n"; // // GENERATE UPPER PADDING // // ║ ║ // // (the blank lines within the frame and above the text) output += ( options.marginChar.repeat(options.hMargin) + fWest + options.paddingChar.repeat(innerLineLength + options.hPadding * 2) + fEast + options.marginChar.repeat(options.hMargin) + "\n" ).repeat(options.vPadding); // // GENERATE FRAMED TEXT SEGMENT // // ║ My pretty ║ // ║ text here ║ // // ( this could be done with a reduce() ) // for (const line of lines) { output += "" + // Make sure JS knows we're adding a string. options.marginChar.repeat(options.hMargin) + // margin before frame fWest + // vertical frame char options.paddingChar.repeat(options.hPadding) + // padding before text line.padEnd(innerLineLength, " ") + // The actual text. Pad it with normal space character, NOT custom space. options.paddingChar.repeat(options.hPadding) + // padding after text fEast + // vertical frame bar options.marginChar.repeat(options.hMargin) + // margin after frame "\n"; } // // GENERATE LOWER PADDING // // ║ ║ // // ( the blank lines within the ) // ( frame and below the text ) // // ( this code is a direct ) // ( repeat of the code that ) // ( generates top padding ) output += ( options.marginChar.repeat(options.hMargin) + fWest + options.paddingChar.repeat(innerLineLength + options.hPadding * 2) + fEast + options.marginChar.repeat(options.hMargin) + "\n" ).repeat(options.vPadding); // // GENERATE THE BOTTOM PART OF THE FRAME // // ╚════════════╝ // output += "" + // Make sure JS knows we're adding a string. options.marginChar.repeat(options.hMargin) + // the margin before the frame starts fSouthWest + // northwest frame corner fSouth.repeat(innerLineLength + options.hPadding * 2) + // the long horizontal frame top bar fSouthEast + // northeast frame corner options.marginChar.repeat(options.hMargin) + // the margin after the frame starts "\n"; // // GENERATE THE MARGIN SPACE BELOW THE FRAMED TEXT // // ( we insert space characters even though ) // ( they wouldn't normally be visible. But ) // ( Some fonts might allow us to see blank ) // ( space, and what if we want to nest many ) // ( frames inside each other? ) // output += (options.marginChar.repeat(outerLineLength) + "\n").repeat( options.vMargin, ); return output; } // Allow this script to be run directly from node as well as being included! // https://stackoverflow.com/a/66309132/5622463