UNPKG

p5.asciify

Version:

Apply real-time ASCII conversion to your favorite p5.js sketches instantly.

1,436 lines 202 kB
var vA = Object.defineProperty; var yA = (a, A, e) => A in a ? vA(a, A, { enumerable: !0, configurable: !0, writable: !0, value: e }) : a[A] = e; var o = (a, A, e) => yA(a, typeof A != "symbol" ? A + "" : A, e); import p from "p5"; class FA { /** * Create a new grid instance. * @param _texture The framebuffer for the asciifier, used to determine the grid dimensions. * @param _cellWidth The width of each cell in the grid. * @param _cellHeight The height of each cell in the grid. * @ignore */ constructor(A, e, r) { /** The number of columns in the grid. */ o(this, "_cols"); /** The number of rows in the grid. */ o(this, "_rows"); /** The total width of the grid in pixels. */ o(this, "_width"); /** The total height of the grid in pixels. */ o(this, "_height"); /** The offset to the outer canvas on the x-axis when centering the grid. */ o(this, "_offsetX"); /** The offset to the outer canvas on the y-axis when centering the grid. */ o(this, "_offsetY"); /** Whether the grid dimensions are fixed, or responsive based on the canvas dimensions. */ o(this, "_fixedDimensions", !1); this._texture = A, this._cellWidth = e, this._cellHeight = r, this.reset(); } /** * Reset the grid to the default number of columns and rows based on the current canvas dimensions, and the grid cell dimensions. * @ignore */ reset() { this._fixedDimensions || ([this._cols, this._rows] = [Math.floor(this._texture.width / this._cellWidth), Math.floor(this._texture.height / this._cellHeight)]), this._resizeGrid(); } /** * Reset the total grid width & height, and the offset to the outer canvas. */ _resizeGrid() { this._width = this._cols * this._cellWidth, this._height = this._rows * this._cellHeight, this._offsetX = Math.floor((this._texture.width - this._width) / 2), this._offsetY = Math.floor((this._texture.height - this._height) / 2); } /** * Re-assign the grid cell dimensions and `reset()` the grid. * @param newCellWidth The new cell width. * @param newCellHeight The new cell height. * @ignore */ resizeCellPixelDimensions(A, e) { [this._cellWidth, this._cellHeight] = [A, e], this.reset(); } /** * Re-assign the grid dimensions and resize the grid. * * Calling this method makes the grid dimensions fixed, meaning they will not automatically resize based on the canvas dimensions. * @param newCols The new number of columns. * @param newRows The new number of rows. * @ignore */ resizeGridDimensions(A, e) { this._fixedDimensions = !0, [this._cols, this._rows] = [A, e], this._resizeGrid(); } /** * Make the grid dimensions flexible again, and `reset()` the grid. * @ignore */ resetGridDimensions() { this._fixedDimensions = !1, this.reset(); } /** * Update the texture used by the grid, and reset the grid dimensions. * @param texture The new framebuffer texture to use for the grid. * @ignore */ updateTexture(A) { this._texture = A, this._fixedDimensions ? this._resizeGrid() : this.reset(); } /** * Returns the width of each cell in the grid. */ get cellWidth() { return this._cellWidth; } /** * Returns the height of each cell in the grid. */ get cellHeight() { return this._cellHeight; } /** * Returns the number of columns in the grid. */ get cols() { return this._cols; } /** * Returns the number of rows in the grid. */ get rows() { return this._rows; } /** * Returns the total width of the grid. */ get width() { return this._width; } /** * Returns the total height of the grid. */ get height() { return this._height; } /** * Returns the offset to the outer canvas borders on the x-axis when centering the grid. */ get offsetX() { return this._offsetX; } /** * Returns the offset to the outer canvas borders on the y-axis when centering the grid. */ get offsetY() { return this._offsetY; } /** * Returns `true` if the grid dimensions *(columns and rows)* are fixed, or `false` if they are responsive based on the canvas dimensions. */ get fixedDimensions() { return this._fixedDimensions; } /** * Sets whether the grid dimensions *(columns and rows)* are fixed or responsive based on the canvas dimensions. * @param value `true` to make the grid dimensions fixed, or `false` to make them responsive. * @ignore */ set fixedDimensions(A) { this._fixedDimensions = A; } } const J = (a) => { const A = [ // Instance version (most common in standard setups) () => a == null ? void 0 : a.VERSION, // Global p5 version (works in many environments) () => typeof p < "u" && p.VERSION ? p.VERSION : void 0, // Window global version (P5LIVE style environments) () => { var e; return typeof window < "u" && ((e = window.p5) != null && e.VERSION) ? window.p5.VERSION : void 0; }, // Constructor version (some bundled environments) () => { var e; return (e = a == null ? void 0 : a.constructor) == null ? void 0 : e.VERSION; }, // Prototype chain version (edge cases) () => { var e, r; return (r = (e = Object.getPrototypeOf(a)) == null ? void 0 : e.constructor) == null ? void 0 : r.VERSION; } ]; for (const e of A) try { const r = e(); if (r && typeof r == "string" && /^\d+\.\d+/.test(r)) return r; } catch { continue; } return "1.0.0"; }, R = (a) => $(a, "2.0.0") >= 0, O = (a, A) => { var e; return A ? !!((e = a == null ? void 0 : a.constructor) != null && e.Color && A instanceof a.constructor.Color || typeof p < "u" && p.Color && A instanceof p.Color) : !1; }, DA = (a, A) => { var e; return A ? !!((e = a == null ? void 0 : a.constructor) != null && e.Font && A instanceof a.constructor.Font || typeof p < "u" && p.Font && A instanceof p.Font) : !1; }, $ = (a, A) => { const [e, r] = [a, A].map((t) => t.split(".").map(Number)); for (let t = 0; t < Math.max(e.length, r.length); t++) { const i = e[t] ?? 0, s = r[t] ?? 0; if (i !== s) return i > s ? 1 : -1; } return 0; }; class sA { /** * Creates a new SVG exporter. * @param p The p5.js instance */ constructor(A) { /** * The p5.js instance. */ o(this, "_p"); this._p = A; } /** * Generates the current ASCII output as an SVG string without downloading. * @param rendererManager The renderer manager containing framebuffers with ASCII data * @param grid The grid information for dimensions and cell sizes * @param fontManager The font manager with character data * @param backgroundColor The background color for the SVG * @param options Options for SVG generation (excludes filename) * @returns SVG string representation of the ASCII output */ generateSVG(A, e, r, t, i = {}) { const s = { includeBackgroundRectangles: !0, drawMode: "fill", strokeWidth: 1, ...i }, n = A.characterFramebuffer, E = A.primaryColorFramebuffer, Q = A.secondaryColorFramebuffer, h = A.transformFramebuffer, c = A.rotationFramebuffer; n.loadPixels(), E.loadPixels(), Q.loadPixels(), h.loadPixels(), c.loadPixels(); const _ = n.pixels, d = E.pixels, l = Q.pixels, m = h.pixels, B = c.pixels, u = e.cols, f = e.rows, b = e.cellWidth, C = e.cellHeight, I = e.width, k = e.height, y = r.characters; let F = this.generateSVGHeader(I, k); if (s.includeBackgroundRectangles) { const M = t, w = this._p.color(M), P = `rgba(${w._array[0] * 255},${w._array[1] * 255},${w._array[2] * 255},${w._array[3]})`; F += ` <rect width="${I}" height="${k}" fill="${P}" />`; } F += ` <g id="ascii-cells">`; let D = 0; for (let M = 0; M < f; M++) for (let w = 0; w < u; w++) { const P = D * 4, z = _[P], V = _[P + 1]; let x = z + (V << 8); x >= y.length && (x = y.length - 1); let H = { r: d[P], g: d[P + 1], b: d[P + 2], a: d[P + 3] }, U = { r: l[P], g: l[P + 1], b: l[P + 2], a: l[P + 3] }; const Z = m[P], L = m[P + 1], q = m[P + 2], AA = Z === 255, dA = L === 255, eA = q === 255; if (AA) { const xA = H; H = U, U = xA; } const rA = B[P] * (360 / 256), tA = w * b, wA = M * C; F += this.generateSVGCellContent( x, H, U, tA, wA, b, C, rA, dA, eA, r, y[x], s ), D++; } return F += ` </g> </svg>`, F; } /** * Exports the current ASCII output as an SVG file. * @param rendererManager The renderer manager containing framebuffers with ASCII data * @param grid The grid information for dimensions and cell sizes * @param fontManager The font manager with character data * @param options Options for SVG export or just the filename as a string for backward compatibility */ saveSVG(A, e, r, t, i) { if (!i.filename) { const n = /* @__PURE__ */ new Date(), E = n.toISOString().split("T")[0], Q = n.toTimeString().split(" ")[0].replace(/:/g, "-"); i.filename = `asciify_output_${E}_${Q}`; } const s = this.generateSVG(A, e, r, t, i); this.downloadSVG(s, i.filename); } /** * Generates the SVG header content * @param width The width of the SVG * @param height The height of the SVG * @returns The SVG header content */ generateSVGHeader(A, e) { return `<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg width="${A}" height="${e}" viewBox="0 0 ${A} ${e}" xmlns="http://www.w3.org/2000/svg" version="1.1"> <title>ascii art generated via p5.asciify</title> <desc>ascii art visualization of a p5.js sketch</desc>`; } /** * Generates the SVG content for a single cell * @param charIndex The index of the character in the font atlas * @param primaryColor The foreground color for the cell * @param secondaryColor The background color for the cell * @param cellX The x position of the cell * @param cellY The y position of the cell * @param cellWidth The width of the cell * @param cellHeight The height of the cell * @param rotationAngle The rotation angle for the character * @param fontManager The font manager * @param char The characters object array * @param options The SVG export options * @returns The SVG content for the cell */ generateSVGCellContent(A, e, r, t, i, s, n, E, Q, h, c, _, d) { let l = ""; if (d.includeBackgroundRectangles && r.a > 0) { const C = `rgba(${r.r},${r.g},${r.b},${r.a / 255})`; d.drawMode === "stroke" ? l += ` <rect x="${t}" y="${i}" width="${s}" height="${n}" stroke="${C}" fill="none" stroke-width="${d.strokeWidth || 1}" />` : l += ` <rect x="${t}" y="${i}" width="${s}" height="${n}" fill="${C}" />`; } const m = t + s / 2, B = i + n / 2, u = `rgba(${e.r},${e.g},${e.b},${e.a / 255})`, f = []; if (Q || h) { const C = Q ? -1 : 1, I = h ? -1 : 1; f.push(`translate(${m} ${B})`), f.push(`scale(${C} ${I})`), f.push(`translate(${-m} ${-B})`); } E && f.push(`rotate(${E} ${m} ${B})`); const b = f.length ? ` transform="${f.join(" ")}"` : ""; if (d.drawMode === "text") { const C = Math.min(s, n) * 0.8; l += ` <text x="${m}" y="${B}" font-family="monospace" font-size="${C}px" fill="${u}" text-anchor="middle" dominant-baseline="middle"${b}>${this.escapeXml(_.character)}</text>`; } else { let C = 1; R(J(this._p)) ? C = c.fontSize / c.font.data.head.unitsPerEm : C = c.fontSize / c.font.font.unitsPerEm; const I = t + (s - _.advanceWidth * C) / 2, k = i + (n + c.fontSize * 0.7) / 2, D = _.getPath(I, k, c.fontSize).toSVG().match(/d="([^"]+)"/); if (D && D[1]) { if (b && (l += ` <g${b}>`), d.drawMode === "stroke") { const M = d.strokeWidth || 1, w = `path-${A}-${t}-${i}`.replace(/\./g, "-"); l += ` <path id="${w}" d="${D[1]}" stroke="${u}" stroke-width="${M}" fill="none" />`; } else l += ` <path d="${D[1]}" fill="${u}" />`; b && (l += ` </g>`); } } return l; } /** * Escapes special XML characters in a string * @param str The string to escape * @returns The escaped string */ escapeXml(A) { return A.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;"); } /** * Creates a downloadable SVG file and initiates the download * @param svgContent The SVG content to download * @param filename The filename for the SVG file */ downloadSVG(A, e) { const r = new Blob([A], { type: "image/svg+xml" }), t = URL.createObjectURL(r), i = document.createElement("a"); i.href = t, i.download = `${e}.svg`, document.body.appendChild(i), i.click(), document.body.removeChild(i), URL.revokeObjectURL(t); } } class oA { /** * Creates a new JSON exporter. * @param p The p5.js instance */ constructor(A) { /** * The p5.js instance. */ o(this, "p"); this.p = A; } /** * Generates the current ASCII output as a JSON string without downloading. * @param rendererManager The renderer manager containing framebuffers with ASCII data * @param grid The grid information for dimensions and cell sizes * @param fontManager The font manager with character data * @param options Options for JSON generation (excludes filename) * @returns JSON string representation of the ASCII output */ generateJSON(A, e, r, t = {}) { const i = { includeEmptyCells: !0, prettyPrint: !0, ...t }, s = A.characterFramebuffer, n = A.primaryColorFramebuffer, E = A.secondaryColorFramebuffer, Q = A.transformFramebuffer, h = A.rotationFramebuffer; s.loadPixels(), n.loadPixels(), E.loadPixels(), Q.loadPixels(), h.loadPixels(); const c = s.pixels, _ = n.pixels, d = E.pixels, l = Q.pixels, m = h.pixels, B = e.cols, u = e.rows, f = r.characters, b = { version: "1.0", created: (/* @__PURE__ */ new Date()).toISOString(), gridSize: { cols: B, rows: u, cellWidth: e.cellWidth, cellHeight: e.cellHeight, width: e.width, height: e.height } }, C = []; let I = 0; for (let y = 0; y < u; y++) for (let F = 0; F < B; F++) { const D = I * 4, M = c[D], w = c[D + 1]; let P = M + (w << 8); P >= f.length && (P = f.length - 1); const z = f[P]; if (!i.includeEmptyCells && (z.character === " " || z.character === "")) { I++; continue; } let V = { r: _[D], g: _[D + 1], b: _[D + 2], a: _[D + 3] }, x = { r: d[D], g: d[D + 1], b: d[D + 2], a: d[D + 3] }; const H = l[D], U = l[D + 1], Z = l[D + 2], L = H === 255, q = U === 255, AA = Z === 255; if (L) { const tA = V; V = x, x = tA; } const eA = m[D] * (360 / 256), fA = this.rgbaToHex( V.r, V.g, V.b, V.a ), rA = this.rgbaToHex( x.r, x.g, x.b, x.a ); C.push({ x: F, y, character: z.character, unicode: z.unicode, color: fA, backgroundColor: rA, rotation: eA, inverted: L, flipHorizontal: q, flipVertical: AA }), I++; } return JSON.stringify( { metadata: b, cells: C }, null, i.prettyPrint ? 2 : 0 ); } /** * Exports the current ASCII output as a JSON file. * @param rendererManager The renderer manager containing framebuffers with ASCII data * @param grid The grid information for dimensions and cell sizes * @param fontManager The font manager with character data * @param options Options for JSON export */ saveJSON(A, e, r, t = {}) { if (!t.filename) { const s = /* @__PURE__ */ new Date(), n = s.toISOString().split("T")[0], E = s.toTimeString().split(" ")[0].replace(/:/g, "-"); t.filename = `asciify_output_${n}_${E}`; } const i = this.generateJSON(A, e, r, t); this.downloadJSON(i, t.filename); } /** * Converts RGBA values to a hex color string * @param r Red channel (0-255) * @param g Green channel (0-255) * @param b Blue channel (0-255) * @param a Alpha channel (0-255) * @returns Hex color string (e.g., "#RRGGBBAA") */ rgbaToHex(A, e, r, t) { const i = (s) => { const n = Math.round(s).toString(16); return n.length === 1 ? "0" + n : n; }; return `#${i(A)}${i(e)}${i(r)}${i(t)}`; } /** * Creates a downloadable JSON file and initiates the download * @param jsonContent The JSON content to download * @param filename The filename for the JSON file */ downloadJSON(A, e) { const r = new Blob([A], { type: "application/json" }), t = URL.createObjectURL(r), i = document.createElement("a"); i.href = t, i.download = `${e}.json`, document.body.appendChild(i), i.click(), document.body.removeChild(i), URL.revokeObjectURL(t); } } function _A(a, A) { const e = a.data.cmap; if (!e || !e.tables) return 0; for (const r of e.tables) if (r.format === 4) { for (let t = 0; t < r.startCount.length; t++) if (A >= r.startCount[t] && A <= r.endCount[t]) { if (r.idRangeOffset[t] === 0) return A + r.idDelta[t] & 65535; { const i = r.idRangeOffset[t] / 2 + (A - r.startCount[t]) - (r.startCount.length - t); if (i >= 0 && i < r.glyphIdArray.length) { const s = r.glyphIdArray[i]; if (s !== 0) return s + r.idDelta[t] & 65535; } } } } return 0; } function K() { return { getBoundingBox: () => ({ x1: 0, y1: 0, x2: 0, y2: 0 }), toSVG: () => "" }; } function mA(a, A, e, r, t) { if (!A || !A.xs || A.xs.length === 0) return K(); const i = t / a.data.head.unitsPerEm; return { getBoundingBox: () => ({ x1: e + A.xMin * i, y1: r + -A.yMax * i, // Flip Y coordinates (TTF uses Y-up) x2: e + A.xMax * i, y2: r + -A.yMin * i // Flip Y coordinates }), toSVG: () => PA(A, e, r, i) }; } function PA(a, A, e, r) { if (!a || !a.xs) return ""; const { xs: t, ys: i, endPts: s, flags: n } = a; if (!t || !i || !s || !n) return ""; let E = "", Q = 0; for (let h = 0; h < s.length; h++) { const c = s[h]; if (!(c < Q)) { if (c >= Q) { const _ = A + t[Q] * r, d = e - i[Q] * r; E += `M${_.toFixed(2)},${d.toFixed(2)}`; let l = Q + 1; for (; l <= c; ) if ((n[l] & 1) !== 0) { const B = A + t[l] * r, u = e - i[l] * r; E += `L${B.toFixed(2)},${u.toFixed(2)}`, l++; } else { const B = A + t[l] * r, u = e - i[l] * r; let f = l + 1 > c ? Q : l + 1; if ((n[f] & 1) !== 0) { const C = A + t[f] * r, I = e - i[f] * r; E += `Q${B.toFixed(2)},${u.toFixed(2)} ${C.toFixed(2)},${I.toFixed(2)}`, l = f + 1; } else { const C = A + t[f] * r, I = e - i[f] * r, k = (B + C) / 2, y = (u + I) / 2; E += `Q${B.toFixed(2)},${u.toFixed(2)} ${k.toFixed(2)},${y.toFixed(2)}`, l = f; } } E += "Z"; } Q = c + 1; } } return `<path d="${E}" />`; } const jA = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, P5AsciifyJSONExporter: oA, P5AsciifySVGExporter: sA, compareVersions: $, createEmptyPath: K, createGlyphPath: mA, detectP5Version: J, getGlyphIndex: _A, glyphToSVGPath: PA, isP5AsyncCapable: R, isValidP5Color: O, isValidP5Font: DA }, Symbol.toStringTag, { value: "Module" })); class Y extends Error { constructor(e, r) { super(e); o(this, "originalError"); this.name = "P5AsciifyError", this.originalError = r; } } var EA = /* @__PURE__ */ ((a) => (a[a.SILENT = 0] = "SILENT", a[a.WARNING = 1] = "WARNING", a[a.ERROR = 2] = "ERROR", a[a.THROW = 3] = "THROW", a))(EA || {}); const T = class T { constructor() { o(this, "_options", { globalLevel: 3, consolePrefix: "[p5.asciify]" }); } static getInstance() { return T._instance || (T._instance = new T()), T._instance; } /** * Handle an error based on the configured settings * @returns true if execution should continue, false if error was handled */ _handle(A, e, r) { switch (this._options.globalLevel, this._options.globalLevel) { case 0: return !1; // Validation failed, handled silently case 1: return console.warn(`${this._options.consolePrefix} ${A}`, e), !1; // Validation failed, warning logged case 2: return console.error(`${this._options.consolePrefix} ${A}`, e), !1; // Validation failed, error logged case 3: default: throw new Y(A, r); } } /** * Validate a condition and handle errors if validation fails * @param condition The condition to validate * @param message Error message if validation fails * @param context Additional context for debugging * @returns true if validation passed, false if validation failed and was handled */ validate(A, e, r) { return A ? !0 : (this._handle(e, r), !1); } /** * Set global error level */ setGlobalLevel(A) { this._options.globalLevel = A; } }; o(T, "_instance", null); let X = T; const g = X.getInstance(), LA = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, P5AsciifyError: Y, P5AsciifyErrorHandler: X, P5AsciifyErrorLevel: EA, errorHandler: g }, Symbol.toStringTag, { value: "Module" })); class SA { /** * Creates a new `P5AsciifyFontManager` instance. * @param _p The p5 instance. * @param _font The font to use for ASCII rendering. * @ignore */ constructor(A, e) { /** An array of supported characters in the font. */ o(this, "_characters", []); /** Maximum width and height of the glyphs in the font. */ o(this, "_maxGlyphDimensions"); /** Texture containing all characters in the font. As square as possible. */ o(this, "_texture"); /** Number of columns in the texture. */ o(this, "_textureColumns"); /** Number of rows in the texture. */ o(this, "_textureRows"); /** Font size to use for the texture that contains all characters of the font. */ o(this, "_fontSize", 16); this._p = A, this._font = e, this._initializeGlyphsAndCharacters(); } /** * Sets up the font manager with the specified font size * and initializes the texture containing all characters in the font. * @param fontSize The font size to use for the texture. * @ignore */ async setup(A) { return this._fontSize = A, this.reset(); } /** * Initializes the character glyphs and characters array. */ _initializeGlyphsAndCharacters() { if (R(J(this._p))) { const A = [], e = /* @__PURE__ */ new Map(); this._font.data.cmap.tables.forEach((t) => { if (t.format === 4) for (let i = 0; i < t.startCount.length; i++) { const s = t.startCount[i], n = t.endCount[i]; if (!(s === 65535 && n === 65535)) for (let E = s; E <= n; E++) { const Q = String.fromCodePoint(E), h = _A(this._font, E); h && h > 0 && (A.push(Q), e.set(Q, h)); } } }); const r = [...new Set(A)]; this._characters = r.map((t, i) => { const s = t.codePointAt(0), n = e.get(t); let E = 0; n !== void 0 && this._font.data.hmtx && this._font.data.hmtx.aWidth && (E = this._font.data.hmtx.aWidth[n]); const Q = i % 256, h = Math.floor(i / 256) % 256, c = Math.floor(i / 65536); return { character: t, unicode: s, // Create a path generator for this glyph getPath: (_, d, l) => { if (n === void 0) return K(); const m = this._font.data.glyf[n]; return m ? mA(this._font, m, _, d, l) : K(); }, advanceWidth: E, r: Q, g: h, b: c }; }); } else { const A = Object.values(this._font.font.glyphs.glyphs); this._characters = [], A.forEach((e, r) => { if (!e.unicode && (!e.unicodes || !e.unicodes.length)) return; const t = this._characters.length, i = t % 256, s = Math.floor(t / 256) % 256, n = Math.floor(t / 65536), E = e.unicode ?? e.unicodes[0]; this._characters.push({ character: String.fromCodePoint(E), unicode: E, getPath: (Q, h, c) => e.getPath(Q, h, c), advanceWidth: e.advanceWidth, r: i, g: s, b: n }); }); } } /** * Loads a font for ASCII rendering. * * Not intended to be called directly. Use {@link P5Asciifier}'s `font()` instead. * Otherwise, other parts of the library are not updated with the new font information. * * @param font The p5.Font object to use for ASCII rendering. * @ignore */ loadFont(A) { g.validate( DA(this._p, A), "Invalid font parameter. Expected a p5.Font object.", { providedValue: A, method: "loadFont" } ) && (this._font = A, this._initializeGlyphsAndCharacters()); } /** * Gets the color of a character in the font. * @param char The character to get the color for. * @returns An array containing the RGB color values for the character, * which can be used to set the fill color when drawing to a custom renderers `characterFramebuffer` * to convert those pixels into the selected character. * @throws If the character is not found in the font. * * @example * ```javascript * function setupAsciify() { * // Get the RGB color of the character 'A' * const color = p5asciify.asciifier().fontManager.glyphColor('A'); * console.log(color); * } * ``` */ glyphColor(A) { if (!g.validate( typeof A == "string" && A.length > 0, "Character must be a non-empty string.", { providedValue: A, method: "glyphColor" } )) return [0, 0, 0]; const r = this._characters.find( (i) => i.character === A ); return g.validate( r !== void 0, (() => { const i = A.codePointAt(0), s = i ? i.toString(16).padStart(4, "0") : "unknown"; return `Could not find character in character set: ${A} (U+${s})`; })(), { providedValue: A, method: "glyphColor" } ) ? [r.r, r.g, r.b] : [0, 0, 0]; } /** * Gets an array of RGB colors for a given string of characters. * @param characters - A string of characters. * @returns Array of RGB color values. * @throws If a character is not found in the fonts available characters. * * @example * ```javascript * function setupAsciify() { * // Get the RGB colors for the characters 'ABC' * const colors = p5asciify.asciifier().fontManager.glyphColors('ABC'); * console.log(colors); * } * ``` */ glyphColors(A = "") { if (!g.validate( typeof A == "string" || Array.isArray(A), "Characters must be a string or array of strings.", { providedValue: A, method: "glyphColors" } )) return [[0, 0, 0]]; const r = []; for (const t of Array.from(A)) { const i = this.glyphColor(t); r.push(i); } return r; } /** * Calculates the maximum width and height of all the glyphs in the font. * @param fontSize - The font size to use for calculations. * @returns An object containing the maximum width and height of the glyphs. */ _getMaxGlyphDimensions(A) { this._p.textFont(this._font), this._p.textSize(A); let e = 0, r = 0; for (const t of this._characters) { const i = this._font.textBounds(t.character, 0, 0, A), s = i.h, n = i.w; e = Math.max(e, n), r = Math.max(r, s); } return { width: Math.ceil(e), height: Math.ceil(r) }; } /** * Resets the texture atlas by recalculating the maximum glyph dimensions and recreating the texture. * @ignore */ async reset() { return this._maxGlyphDimensions = this._getMaxGlyphDimensions(this._fontSize), this._createTexture(this._fontSize); } /** * Sets the font size, recalculates the maximum glyph dimensions, and recreates the texture. * @param fontSize - The new font size. * @ignore */ async setFontSize(A) { return this._fontSize = A, this._maxGlyphDimensions = this._getMaxGlyphDimensions(this._fontSize), this._createTexture(this._fontSize); } /** * Creates a texture containing all characters in the font, arranged in a 2d grid that is as square as possible. * @param fontSize - The font size to use for creating the texture. */ async _createTexture(A) { this._textureColumns = Math.ceil(Math.sqrt(this.characters.length)), this._textureRows = Math.ceil(this.characters.length / this._textureColumns), this._texture ? this._texture.resize(this._maxGlyphDimensions.width * this._textureColumns, this._maxGlyphDimensions.height * this._textureRows) : this._texture = this._p.createFramebuffer({ width: this._maxGlyphDimensions.width * this._textureColumns, height: this._maxGlyphDimensions.height * this._textureRows, depthFormat: this._p.UNSIGNED_INT, textureFiltering: this._p.NEAREST }), this._texture.begin(), this._p.clear(), this._p.textFont(this._font), this._p.fill(255), this._p.textSize(A), this._p.textAlign(this._p.LEFT, this._p.TOP), this._p.noStroke(); for (let e = 0; e < this._characters.length; e++) { const r = e % this._textureColumns, t = Math.floor(e / this._textureColumns), i = this._maxGlyphDimensions.width * r - this._maxGlyphDimensions.width * this._textureColumns / 2, s = this._maxGlyphDimensions.height * t - this._maxGlyphDimensions.height * this._textureRows / 2; this._p.text(this._characters[e].character, i, s); } this._texture.end(); } /** * Returns the maximum width and height found in all the glyphs in the font. */ get maxGlyphDimensions() { return this._maxGlyphDimensions; } /** * Returns the texture containing all characters in the font. */ get texture() { return this._texture; } /** * Returns the number of columns in the texture containing all characters in the font. */ get textureColumns() { return this._textureColumns; } /** * Returns the number of rows in the texture containing all characters in the font. */ get textureRows() { return this._textureRows; } /** * Returns the font size used for the texture containing all characters in the font. */ get fontSize() { return this._fontSize; } /** * The `p5.Font` object used for ASCII rendering. * * @example * ```javascript * function drawAsciify() { * // Draw an FPS counter, using the font set in p5.asciify, on top of the ASCII rendering. * textFont(p5asciify.asciifier().fontManager.font); * textSize(16); * fill(255); * text(frameRate() + " FPS", 10, 10); * } * ``` */ get font() { return this._font; } /** * An array of supported characters in the set font with additional information like unicode, and RGB color values. * * @example * ```javascript * function setupAsciify() { * // Print the supported characters in the font to the console * console.log(p5asciify.asciifier().fontManager.characters); * } * ``` */ get characters() { return this._characters; } /** * Returns all supported characters in the font as a single string. * Useful for quick access to the complete character set or for iteration purposes. * * @example * ```javascript * function setupAsciify() { * // Get all supported characters as a string * const allChars = p5asciify.asciifier().fontManager.charactersString; * console.log("Font supports these characters:", allChars); * console.log("Total character count:", allChars.length); * } * ``` */ get charactersString() { return this._characters.map((A) => A.character).join(""); } } class lA { /** * Constructs a new ASCII renderer instance. Called by derived classes. * @param _p The p5 instance. * @param _grid Grid object containing the relevant grid information. * @param initialFramebufferDimensions The initial framebuffer dimensions. * @param _fontManager The font manager instance containing the ASCII characters texture. * @param _options The options for the ASCII renderer. * @ignore */ constructor(A, e, r, t, i, s) { /** The primary color framebuffer, whose pixels define the character colors of the grid cells. */ o(this, "_primaryColorFramebuffer"); /** The secondary color framebuffer, whose pixels define the background colors of the grid cells. */ o(this, "_secondaryColorFramebuffer"); /** The character framebuffer, whose pixels define the ASCII characters to use in the grid cells. */ o(this, "_characterFramebuffer"); /** The transform framebuffer, where each pixels color channel defines a different transformation: * - Red channel: Swap the character and background colors of the grid cells. * - Green channel: Flip the ASCII characters horizontally. * - Blue channel: Flip the ASCII characters vertically. */ o(this, "_transformFramebuffer"); /** The rotation framebuffer, whose pixels define the rotation angle of the characters in the grid. */ o(this, "_rotationFramebuffer"); this._p = A, this._captureFramebuffer = e, this._grid = r, this.initialFramebufferDimensions = t, this._fontManager = i, this._options = s; const n = { density: 1, antialias: !1, width: t.width, height: t.height, depthFormat: this._p.UNSIGNED_INT, textureFiltering: this._p.NEAREST }; this._primaryColorFramebuffer = this._p.createFramebuffer(n), this._secondaryColorFramebuffer = this._p.createFramebuffer(n), this._transformFramebuffer = this._p.createFramebuffer(n), this._characterFramebuffer = this._p.createFramebuffer(n), this._rotationFramebuffer = this._p.createFramebuffer(n); } /** * Updates renderer options. * @param newOptions - The new options to update. * * @example * ```javascript * function setupAsciify() { * // Update the brightness renderer options * p5asciify.asciifier().renderers().get("brightness").update({ * enabled: true, * characterColor: color(255, 0, 0), * backgroundColor: color(0, 0, 255), * characters: '.:-=+*#%@', * invertMode: true, * rotationAngle: 90, * // ... * }); * } * ``` */ update(A) { (A == null ? void 0 : A.enabled) !== void 0 && this.enabled(A.enabled); } /** * Update the capture framebuffer used by the renderer. * @param newCaptureFramebuffer - The new capture framebuffer or graphics to use. * @ignore */ setCaptureTexture(A) { this._captureFramebuffer = A, this.resizeFramebuffers(), this.resetShaders(); } /** * Enable or disable the renderer. * @param enabled - Whether to enable or disable the renderer. * @returns The current/new state of the renderer. * @throws If the provided enabled value is not a boolean. * * @example * ```javascript * function keyPressed() { * if (key === 'd') { * // Disable the brightness renderer * p5asciify.asciifier().renderers().get("brightness").enabled(false); * } else if (key === 'e') { * // Enable the brightness renderer * p5asciify.asciifier().renderers().get("brightness").enabled(true); * } * } * ``` */ enabled(A) { if (A === void 0) return this._options.enabled; if (!g.validate( typeof A == "boolean", "Enabled must be a boolean.", { providedValue: A, method: "enabled" } )) return this._options.enabled; if (this._options.enabled = A, !A) { const r = [ this._primaryColorFramebuffer, this._secondaryColorFramebuffer, this._transformFramebuffer, this._rotationFramebuffer, this._characterFramebuffer ]; for (const t of r) t.draw(() => { this._p.clear(); }); } return this._options.enabled; } /** * Enable the renderer. * @returns The new state of the renderer. * * @example * ```javascript * function keyPressed() { * if (key === 'd') { * // Disable the brightness renderer * p5asciify.asciifier().renderers().get("brightness").disable(); * } else if (key === 'e') { * // Enable the brightness renderer * p5asciify.asciifier().renderers().get("brightness").enable(); * } * } * ``` */ enable() { return this.enabled(!0); } /** * Disable the renderer. * * Disabling the renderer will clear all framebuffers, * and prevent the renderer from being executed in the rendering pipeline. * * @returns The new state of the renderer. * * @example * ```javascript * function keyPressed() { * if (key === 'd') { * // Disable the brightness renderer * p5asciify.asciifier().renderers().get("brightness").disable(); * } else if (key === 'e') { * // Enable the brightness renderer * p5asciify.asciifier().renderers().get("brightness").enable(); * } * } * ``` */ disable() { return this.enabled(!1); } /** * Get the set options for the ASCII renderer. * * @example * ```javascript * function setupAsciify() { * // Get the brightness renderer options * const brightnessOptions = p5asciify.asciifier().renderers().get("brightness").options(); * console.log(brightnessOptions); * } * ``` */ get options() { return this._options; } /** * Get the primary color framebuffer, whose pixels define the character colors of the grid cells. * * Pre-built ASCII renderers like `'brightness'` write to this buffer automatically based on your settings. * In `'custom2D'` renderers, you must write to it manually in your `draw()` function. * * @example * ```javascript * let characterFramebuffer; * let primaryColorFramebuffer; * let secondaryColorFramebuffer; * * let asciifier; * * function setup() { * createCanvas(400, 400, WEBGL); * } * * function setupAsciify() { * // Get the asciifier instance * asciifier = p5asciify.asciifier(); * * // Enable the default custom renderer * asciifier.renderers().get("custom2D").enable(); * * // Assign the ascii renderer's framebuffers to a global variable * characterFramebuffer = asciifier.renderers().get("custom2D").characterFramebuffer; * primaryColorFramebuffer = asciifier.renderers().get("custom2D").primaryColorFramebuffer; * secondaryColorFramebuffer = asciifier.renderers().get("custom2D").secondaryColorFramebuffer; * } * * function draw() { * // Draw a rectangle with the character 'A' to the character framebuffer * characterFramebuffer.begin(); * clear(); * asciifier.fill("A"); * rect(0, 0, 100, 100); * characterFramebuffer.end(); * * // Makes all ascii characters on the grid white. * primaryColorFramebuffer.begin(); * background(255); * primaryColorFramebuffer.end(); * * // Makes all cell background colors black. * secondaryColorFramebuffer.begin(); * background(0); * secondaryColorFramebuffer.end(); * } * ``` */ get primaryColorFramebuffer() { return this._primaryColorFramebuffer; } /** * Get the secondary color framebuffer, whose pixels define the background colors of the grid cells. * * Pre-built ASCII renderers like `'brightness'` write to this buffer automatically based on your settings. * In `'custom2D'` renderers, you must write to it manually in your `draw()` function. * * @example * ```javascript * let characterFramebuffer; * let primaryColorFramebuffer; * let secondaryColorFramebuffer; * * let asciifier; * * function setup() { * createCanvas(400, 400, WEBGL); * } * * function setupAsciify() { * // Get the asciifier instance * asciifier = p5asciify.asciifier(); * * // Enable the default custom renderer * asciifier.renderers().get("custom2D").enable(); * * // Assign the ascii renderer's framebuffers to a global variable * characterFramebuffer = asciifier.renderers().get("custom2D").characterFramebuffer; * primaryColorFramebuffer = asciifier.renderers().get("custom2D").primaryColorFramebuffer; * secondaryColorFramebuffer = asciifier.renderers().get("custom2D").secondaryColorFramebuffer; * } * * function draw() { * // Draw a rectangle with the character 'A' to the character framebuffer * characterFramebuffer.begin(); * clear(); * asciifier.fill("A"); * rect(0, 0, 100, 100); * characterFramebuffer.end(); * * // Makes all ascii characters on the grid white. * primaryColorFramebuffer.begin(); * background(255); * primaryColorFramebuffer.end(); * * // Makes all cell background colors black. * secondaryColorFramebuffer.begin(); * background(0); * secondaryColorFramebuffer.end(); * } * ``` */ get secondaryColorFramebuffer() { return this._secondaryColorFramebuffer; } /** * Get the transform framebuffer, where each pixels color channel defines a different transformation: * - Red channel: Swap the character and background colors of the grid cells. * - Green channel: Flip the ASCII characters horizontally. * - Blue channel: Flip the ASCII characters vertically. * * Pre-built ASCII renderers like `'brightness'` write to this buffer automatically based on your settings. * In `'custom2D'` renderers, you must write to it manually in your `draw()` function. * * @example * ```javascript * let characterFramebuffer; * let primaryColorFramebuffer; * let secondaryColorFramebuffer; * let transformFramebuffer; * * let asciifier; * * function setup() { * createCanvas(400, 400, WEBGL); * } * * function setupAsciify() { * * // Get the asciifier instance * asciifier = p5asciify.asciifier(); * * // Enable the default custom renderer * asciifier.renderers().get("custom2D").enable(); * * // Assign the ascii renderer's framebuffers to a global variable * characterFramebuffer = asciifier.renderers().get("custom2D").characterFramebuffer; * primaryColorFramebuffer = asciifier.renderers().get("custom2D").primaryColorFramebuffer; * secondaryColorFramebuffer = asciifier.renderers().get("custom2D").secondaryColorFramebuffer; * transformFramebuffer = asciifier.renderers().get("custom2D").transformFramebuffer; * } * * function draw() { * // Draw a rectangle with the character 'A' to the character framebuffer * characterFramebuffer.begin(); * clear(); * asciifier.fill("A"); * rect(0, 0, 100, 100); * characterFramebuffer.end(); * * // Makes all ascii characters on the grid white. * primaryColorFramebuffer.begin(); * background(255); * primaryColorFramebuffer.end(); * * // Makes all cell background colors black. * secondaryColorFramebuffer.begin(); * background(0); * secondaryColorFramebuffer.end(); * * // Swap the character and background colors of all grid cells, * // and flip the ASCII characters horizontally. * transformFramebuffer.begin(); * background(255, 255, 0); * transformFramebuffer.end(); * } * ``` */ get transformFramebuffer() { return this._transformFramebuffer; } /** * Get the rotation framebuffer, whose pixels define the rotation angle of each character in the grid. * * Pre-built ASCII renderers like `'brightness'` write to this buffer automatically based on your settings. * In `'custom2D'` renderers, you must write to it manually in your `draw()` function. * * @example * ```javascript * let characterFramebuffer; * let primaryColorFramebuffer; * let secondaryColorFramebuffer; * let rotationFramebuffer; * * let asciifier; * * function setup() { * createCanvas(400, 400, WEBGL); * } * * function setupAsciify() { * // Get the asciifier instance * asciifier = p5asciify.asciifier(); * * // Enable the default custom renderer * asciifier.renderers().get("custom2D").enable(); * * // Assign the ascii renderer's framebuffers to a global variable * characterFramebuffer = asciifier.renderers().get("custom2D").characterFramebuffer; * primaryColorFramebuffer = asciifier.renderers().get("custom2D").primaryColorFramebuffer; * secondaryColorFramebuffer = asciifier.renderers().get("custom2D").secondaryColorFramebuffer; * rotationFramebuffer = asciifier.renderers().get("custom2D").rotationFramebuffer; * } * * function draw() { * // Draw a rectangle with the character 'A' to the character framebuffer * characterFramebuffer.begin(); * clear(); * asciifier.fill("A"); * rect(0, 0, 100, 100); * characterFramebuffer.end(); * * // Makes all ascii characters on the grid white. * primaryColorFramebuffer.begin(); * background(255); * primaryColorFramebuffer.end(); * * // Makes all cell background colors black. * secondaryColorFramebuffer.begin(); * background(0); * secondaryColorFramebuffer.end(); * * // Rotates all characters in the grid by X degrees. * // Utilize the red color channel for the rotation angle. * rotationFramebuffer.begin(); * background('rgb(25%, 0%, 0%)'); // 25% of 360 degrees = 90 degrees * rotationFramebuffer.end(); * } * ``` */ get rotationFramebuffer() { return this._rotationFramebuffer; } /** * Get the character framebuffer, whose pixels define the ASCII characters to use in the grid cells. * * Pre-built ASCII renderers like `'brightness'` write to this buffer automatically based on your settings. * In `'custom2D'` renderers, you must write to it manually in your `draw()` function. * * @example * ```javascript * let characterFramebuffer; * let primaryColorFramebuffer; * let secondaryColorFramebuffer; * * let asciifier; * * function setup() { * createCanvas(400, 400, WEBGL); * } * * function setupAsciify() { * // Get the asciifier instance * asciifier = p5asciify.asciifier(); * * // Enable the default custom renderer * asciifier.renderers().get("custom2D").enable(); * * // Assign the ascii renderer's framebuffers to a global variable * characterFramebuffer = asciifier.renderers().get("custom2D").characterFramebuffer; * primaryColorFramebuffer = asciifier.renderers().get("custom2D").primaryColorFramebuffer; * secondaryColorFramebuffer = asciifier.renderers().get("custom2D").secondaryColorFramebuffer; * } * * function draw() { * // Draw a rectangle with the character 'A' to the character framebuffer * characterFramebuffer.begin(); * clear(); * asciifier.fill("A"); * rect(0, 0, 100, 100); * characterFramebuffer.end(); * * // Makes all ascii characters on the grid white. * primaryColorFramebuffer.begin(); * background(255); * primaryColorFramebuffer.end(); * * // Makes all cell background colors black. * secondaryColorFramebuffer.begin(); * background(0); * secondaryColorFramebuffer.end(); * } * ``` */ get characterFramebuffer() { return this._characterFramebuffer; } } const nA = { /** Enable/disable the renderer */ enabled: !1 }; class j extends lA { /** * Creates a new `"custom2D"` ASCII renderer instance. * @param _p The p5 instance. * @param _grid Grid object containing the relevant grid information. * @param _fontManager The font texture atlas containing the ASCII characters texture. * @param _options The options for the ASCII renderer. * @ignore */ constructor(A, e, r, t, i = nA) { super(A, e, r, { width: r.cols, height: r.rows }, t, { ...nA, ...i }); } resetShaders() { } /** * Resize the framebuffers to match the 2D grid size based on the number of rows and columns.