UNPKG

p5.asciify

Version:

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

1,401 lines 209 kB
var SA = Object.defineProperty; var MA = (n, A, e) => A in n ? SA(n, A, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[A] = e; var o = (n, A, e) => MA(n, typeof A != "symbol" ? A + "" : A, e); import P from "p5"; class VA { /** * 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 = (n) => { const A = [ // Instance version (most common in standard setups) () => n == null ? void 0 : n.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 = n == null ? void 0 : n.constructor) == null ? void 0 : e.VERSION; }, // Prototype chain version (edge cases) () => { var e, r; return (r = (e = Object.getPrototypeOf(n)) == 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 = (n) => O(n, "2.0.0") >= 0, J = (n, A) => { var e; return A ? !!((e = n == null ? void 0 : n.constructor) != null && e.Color && A instanceof n.constructor.Color || typeof P < "u" && P.Color && A instanceof P.Color) : !1; }, pA = (n, A) => { var e; return A ? !!((e = n == null ? void 0 : n.constructor) != null && e.Font && A instanceof n.constructor.Font || typeof P < "u" && P.Font && A instanceof P.Font) : !1; }, O = (n, A) => { const [e, r] = [n, 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 BA { /** * 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 }, a = A.characterFramebuffer, E = A.primaryColorFramebuffer, Q = A.secondaryColorFramebuffer, h = A.transformFramebuffer, c = A.rotationFramebuffer; a.loadPixels(), E.loadPixels(), Q.loadPixels(), h.loadPixels(), c.loadPixels(); const p = a.pixels, d = E.pixels, l = Q.pixels, _ = h.pixels, B = c.pixels, C = e.cols, f = e.rows, b = e.cellWidth, D = e.cellHeight, I = e.width, G = e.height, y = r.characters; let F = this.generateSVGHeader(I, G); if (s.includeBackgroundRectangles) { const M = t, w = this._p.color(M), m = `rgba(${w._array[0] * 255},${w._array[1] * 255},${w._array[2] * 255},${w._array[3]})`; F += ` <rect width="${I}" height="${G}" fill="${m}" />`; } F += ` <g id="ascii-cells">`; let u = 0; for (let M = 0; M < f; M++) for (let w = 0; w < C; w++) { const m = u * 4, H = p[m], V = p[m + 1]; let x = H + (V << 8); x >= y.length && (x = y.length - 1); let N = { r: d[m], g: d[m + 1], b: d[m + 2], a: d[m + 3] }, $ = { r: l[m], g: l[m + 1], b: l[m + 2], a: l[m + 3] }; const q = _[m], W = _[m + 1], AA = _[m + 2], eA = q === 255, rA = W === 255, tA = AA === 255; if (eA) { const FA = N; N = $, $ = FA; } const iA = B[m], sA = B[m + 1], oA = iA + sA / 255, nA = Math.round(oA * 360 / 255 * 100) / 100, aA = w * b, yA = M * D; F += this.generateSVGCellContent( x, N, $, aA, yA, b, D, nA, rA, tA, r, y[x], s ), u++; } 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 a = /* @__PURE__ */ new Date(), E = a.toISOString().split("T")[0], Q = a.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 rotation 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, a, E, Q, h, c, p, d) { let l = ""; if (d.includeBackgroundRectangles && r.a > 0) { const D = `rgba(${r.r},${r.g},${r.b},${r.a / 255})`; d.drawMode === "stroke" ? l += ` <rect x="${t}" y="${i}" width="${s}" height="${a}" stroke="${D}" fill="none" stroke-width="${d.strokeWidth || 1}" />` : l += ` <rect x="${t}" y="${i}" width="${s}" height="${a}" fill="${D}" />`; } const _ = t + s / 2, B = i + a / 2, C = `rgba(${e.r},${e.g},${e.b},${e.a / 255})`, f = []; if (Q || h) { const D = Q ? -1 : 1, I = h ? -1 : 1; f.push(`translate(${_} ${B})`), f.push(`scale(${D} ${I})`), f.push(`translate(${-_} ${-B})`); } E && f.push(`rotate(${E} ${_} ${B})`); const b = f.length ? ` transform="${f.join(" ")}"` : ""; if (d.drawMode === "text") { const D = Math.min(s, a) * 0.8; l += ` <text x="${_}" y="${B}" font-family="monospace" font-size="${D}px" fill="${C}" text-anchor="middle" dominant-baseline="middle"${b}>${this.escapeXml(p.character)}</text>`; } else { let D = 1; R(j(this._p)) ? D = c.fontSize / c.font.data.head.unitsPerEm : D = c.fontSize / c.font.font.unitsPerEm; const I = t + (s - p.advanceWidth * D) / 2, G = i + (a + c.fontSize * 0.7) / 2, u = p.getPath(I, G, c.fontSize).toSVG().match(/d="([^"]+)"/); if (u && u[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="${u[1]}" stroke="${C}" stroke-width="${M}" fill="none" />`; } else l += ` <path d="${u[1]}" fill="${C}" />`; 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 QA { /** * 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, a = A.primaryColorFramebuffer, E = A.secondaryColorFramebuffer, Q = A.transformFramebuffer, h = A.rotationFramebuffer; s.loadPixels(), a.loadPixels(), E.loadPixels(), Q.loadPixels(), h.loadPixels(); const c = s.pixels, p = a.pixels, d = E.pixels, l = Q.pixels, _ = h.pixels, B = e.cols, C = e.rows, f = r.characters, b = { version: "1.0", created: (/* @__PURE__ */ new Date()).toISOString(), gridSize: { cols: B, rows: C, cellWidth: e.cellWidth, cellHeight: e.cellHeight, width: e.width, height: e.height } }, D = []; let I = 0; for (let y = 0; y < C; y++) for (let F = 0; F < B; F++) { const u = I * 4, M = c[u], w = c[u + 1]; let m = M + (w << 8); m >= f.length && (m = f.length - 1); const H = f[m]; if (!i.includeEmptyCells && (H.character === " " || H.character === "")) { I++; continue; } let V = { r: p[u], g: p[u + 1], b: p[u + 2], a: p[u + 3] }, x = { r: d[u], g: d[u + 1], b: d[u + 2], a: d[u + 3] }; const N = l[u], $ = l[u + 1], q = l[u + 2], W = N === 255, AA = $ === 255, eA = q === 255; if (W) { const aA = V; V = x, x = aA; } const rA = _[u], tA = _[u + 1], iA = rA + tA / 255, sA = Math.round(iA * 360 / 255 * 100) / 100, oA = this.rgbaToHex( V.r, V.g, V.b, V.a ), nA = this.rgbaToHex( x.r, x.g, x.b, x.a ); D.push({ x: F, y, character: H.character, unicode: H.unicode, color: oA, backgroundColor: nA, rotation: sA, inverted: W, flipHorizontal: AA, flipVertical: eA }), I++; } return JSON.stringify( { metadata: b, cells: D }, 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(), a = s.toISOString().split("T")[0], E = s.toTimeString().split(" ")[0].replace(/:/g, "-"); t.filename = `asciify_output_${a}_${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 a = Math.round(s).toString(16); return a.length === 1 ? "0" + a : a; }; 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 PA(n, A) { const e = n.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 X() { return { getBoundingBox: () => ({ x1: 0, y1: 0, x2: 0, y2: 0 }), toSVG: () => "" }; } function IA(n, A, e, r, t) { if (!A || !A.xs || A.xs.length === 0) return X(); const i = t / n.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: () => bA(A, e, r, i) }; } function bA(n, A, e, r) { if (!n || !n.xs) return ""; const { xs: t, ys: i, endPts: s, flags: a } = n; if (!t || !i || !s || !a) return ""; let E = "", Q = 0; for (let h = 0; h < s.length; h++) { const c = s[h]; if (!(c < Q)) { if (c >= Q) { const p = A + t[Q] * r, d = e - i[Q] * r; E += `M${p.toFixed(2)},${d.toFixed(2)}`; let l = Q + 1; for (; l <= c; ) if ((a[l] & 1) !== 0) { const B = A + t[l] * r, C = e - i[l] * r; E += `L${B.toFixed(2)},${C.toFixed(2)}`, l++; } else { const B = A + t[l] * r, C = e - i[l] * r; let f = l + 1 > c ? Q : l + 1; if ((a[f] & 1) !== 0) { const D = A + t[f] * r, I = e - i[f] * r; E += `Q${B.toFixed(2)},${C.toFixed(2)} ${D.toFixed(2)},${I.toFixed(2)}`, l = f + 1; } else { const D = A + t[f] * r, I = e - i[f] * r, G = (B + D) / 2, y = (C + I) / 2; E += `Q${B.toFixed(2)},${C.toFixed(2)} ${G.toFixed(2)},${y.toFixed(2)}`, l = f; } } E += "Z"; } Q = c + 1; } } return `<path d="${E}" />`; } const WA = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, P5AsciifyJSONExporter: QA, P5AsciifySVGExporter: BA, compareVersions: O, createEmptyPath: X, createGlyphPath: IA, detectP5Version: j, getGlyphIndex: PA, glyphToSVGPath: bA, isP5AsyncCapable: R, isValidP5Color: J, isValidP5Font: pA }, Symbol.toStringTag, { value: "Module" })); class Y extends Error { constructor(e, r) { super(e); o(this, "originalError"); this.name = "P5AsciifyError", this.originalError = r; } } var fA = /* @__PURE__ */ ((n) => (n[n.SILENT = 0] = "SILENT", n[n.WARNING = 1] = "WARNING", n[n.ERROR = 2] = "ERROR", n[n.THROW = 3] = "THROW", n))(fA || {}); 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 Z = T; const g = Z.getInstance(), KA = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, P5AsciifyError: Y, P5AsciifyErrorHandler: Z, P5AsciifyErrorLevel: fA, errorHandler: g }, Symbol.toStringTag, { value: "Module" })); class kA { /** * 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], a = t.endCount[i]; if (!(s === 65535 && a === 65535)) for (let E = s; E <= a; E++) { const Q = String.fromCodePoint(E), h = PA(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), a = e.get(t); let E = 0; a !== void 0 && this._font.data.hmtx && this._font.data.hmtx.aWidth && (E = this._font.data.hmtx.aWidth[a]); 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: (p, d, l) => { if (a === void 0) return X(); const _ = this._font.data.glyf[a]; return _ ? IA(this._font, _, p, d, l) : X(); }, advanceWidth: E, color: this._p.color(Q, h, 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, a = 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, color: this._p.color(i, s, a) }); }); } } /** * 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( pA(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 this._p.color(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.color : this._p.color(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 [this._p.color(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, a = i.w; e = Math.max(e, a), 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 uA { /** * 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 _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) { /** 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"); /** * Framebuffer settings used to configure all internal framebuffers for the renderer. * * These settings are passed to `p5.createFramebuffer()` when creating or recreating framebuffers. * * **Note:** The `width`, `height`, and `density` properties are managed internally and always match the grid size and pixel density. * * Properties: * - `format` (number): Data format of the texture. Either `UNSIGNED_BYTE`, `FLOAT`, or `HALF_FLOAT`. Default is `UNSIGNED_BYTE`. * - `channels` (number): Whether to store `RGB` or `RGBA` color channels. Default is to match the main canvas, which is `RGBA`. * - `depth` (boolean): Whether to include a depth buffer. Default is `true`. * - `depthFormat` (number): Data format of depth information. Either `UNSIGNED_INT` or `FLOAT`. Default is `UNSIGNED_INT`. * - `stencil` (boolean): Whether to include a stencil buffer for masking. `depth` must be `true` for this feature to work. Defaults to the value of `depth` (which is `true`). * - `antialias` (boolean): Whether to perform anti-aliasing. If set to `true`, 2 samples will be used by default. The number of samples can also be set (e.g., 4). Default is `false`. * - `textureFiltering` (number): How to read values from the framebuffer. Either `LINEAR` (nearby pixels will be interpolated) or `NEAREST` (no interpolation). Default is `NEAREST`. * - `width` (number): Width of the framebuffer. Always matches the grid columns. * - `height` (number): Height of the framebuffer. Always matches the grid rows. * - `density` (number): Pixel density of the framebuffer. Always matches the main canvas pixel density. */ o(this, "_framebufferOptions"); this._p = A, this._captureFramebuffer = e, this._grid = r, this._fontManager = t, this._options = i, this._framebufferOptions = { antialias: !1, textureFiltering: this._p.NEAREST, depthFormat: this._p.UNSIGNED_INT, density: 1, width: this._grid.cols, height: this._grid.rows }, this._recreateFramebuffers(); } /** * Recreate all internal framebuffers used by the renderer. */ _recreateFramebuffers() { const A = { ...this._framebufferOptions, density: 1, width: this._grid.cols, height: this._grid.rows }; this._primaryColorFramebuffer = this._p.createFramebuffer(A), this._secondaryColorFramebuffer = this._p.createFramebuffer(A), this._transformFramebuffer = this._p.createFramebuffer(A), this._characterFramebuffer = this._p.createFramebuffer(A), this._rotationFramebuffer = this._p.createFramebuffer(A); } /** * 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: '.:-=+*#%@', * invert: true, * rotation: 90, * // ... * }); * } * ``` */ update(A) { return (A == null ? void 0 : A.enabled) !== void 0 && this.enabled(A.enabled), this; } /** * Update the capture framebuffer used by the renderer. * @param newCaptureFramebuffer - The new capture framebuffer or graphics to use. * @ignore */ setCaptureTexture(A) { return this._captureFramebuffer = A, this.resizeFramebuffers(), this.resetShaders(), this; } /** * 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; if (!g.validate( typeof A == "boolean", "Enabled must be a boolean.", { providedValue: A, method: "enabled" } )) return this; 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; } /** * 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 framebuffer settings used to configure all internal framebuffers for the renderer. */ get framebufferOptions() { return this._framebufferOptions; } /** * 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 characterF