p5.asciify
Version:
Apply real-time ASCII conversion to your favorite p5.js sketches instantly.
1,401 lines • 209 kB
JavaScript
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
}
/**
* 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