textmode.js
Version:
Apply real-time ASCII conversion to any HTML canvas.
836 lines (835 loc) • 26.4 kB
TypeScript
import { TextmodeFont } from './font';
import { TextmodeGrid } from './Grid';
import { TextmodeConversionPipeline } from './ConversionPipeline';
import type { TextmodeConverter } from './converters';
import { type SVGExportOptions } from '../export/svg';
import { type TXTExportOptions } from '../export/txt';
import { type ImageExportOptions, type ImageFormat } from '../export/image';
import type { Shader } from '../rendering';
/**
* Supported capture sources for textmode rendering
*/
export type CaptureSource = HTMLCanvasElement | HTMLVideoElement;
/**
* Options for creating a {@link Textmodifier} instance.
*/
export type TextmodeOptions = {
/** The font size to use for text rendering. Defaults to 16. */
fontSize?: number;
/**
* Automatic rendering mode. Defaults to 'auto'.
* - 'manual': Requires manual `render()` calls
* - 'auto': Automatically renders using requestAnimationFrame
*/
renderMode?: 'manual' | 'auto';
/** Maximum frames per second for auto rendering. Defaults to 60. */
frameRate?: number;
/** The width of the canvas in standalone mode. Defaults to 800. */
width?: number;
/** The height of the canvas in standalone mode. Defaults to 600. */
height?: number;
/**
* URL or path to a custom font file (.otf/.ttf).
* Required when using minified builds that don't include embedded fonts.
* Optional for full builds (will override embedded font if provided).
*/
fontSource?: string;
};
/**
* Manages textmode rendering on a canvas or video element.
*
* Each `Textmodifier` instance can be applied to a specific HTML canvas or video element via {@link textmode.create},
* or created as a standalone instance for independent rendering.
*/
export declare class Textmodifier {
/** The element to capture content from (optional for standalone mode) */
private captureSource;
/** Our WebGL overlay canvas manager */
private textmodeCanvas;
/** Core WebGL renderer */
private _renderer;
private _canvasFramebuffer;
private _font;
private _grid;
private resizeObserver;
private _mode;
private _frameRateLimit;
private animationFrameId;
private lastFrameTime;
private frameInterval;
private _frameRate;
private lastRenderTime;
private _frameCount;
private frameTimeHistory;
private frameTimeHistorySize;
private _pipeline;
private _standalone;
private _drawCallback;
private _resizedCallback;
private constructor();
/**
* Static factory method for creating and initializing a Textmodifier instance.
* @param source The HTML canvas or video element to capture content from. Pass `null` for standalone mode.
* @param opts Optional configuration options for the `Textmodifier` instance.
* @ignore
*/
static create(source?: CaptureSource | null, opts?: TextmodeOptions): Promise<Textmodifier>;
private setupEventListeners;
/**
* Generate the current textmode rendering as a text string.
* @param options Options for text generation *(excluding filename)*
* @returns Textmode grid content as a string.
*
* @example
* ```javascript
* // Fetch a canvas element to apply textmode rendering to
* const canvas = document.querySelector('canvas#myCanvas');
*
* // Create a Textmodifier instance
* const textmodifier = await textmode.create(canvas, {renderMode: 'manual'});
*
* // Render a single frame
* textmodifier.render();
*
* // Get the current rendering as a text string
* const textString = textmodifier.toString({
* preserveTrailingSpaces: false,
* lineEnding: 'lf'
* });
*
* // Print to console or use otherwise
* console.log(textString);
*
* ////////
*
* // Example with video element
* const video = document.querySelector('video#myVideo');
* const videoTextmodifier = await textmode.create(video);
*
* // The textmode overlay will automatically update as the video plays
* video.play();
*
* // Get current frame as ASCII
* const videoFrame = videoTextmodifier.toString();
* ```
*/
toString(options?: Omit<TXTExportOptions, 'filename'>): string;
/**
* Export the current textmode rendering to a TXT file.
* @param options Options for TXT export
*
* @example
* ```javascript
* // Fetch a canvas element to apply textmode rendering to
* const canvas = document.querySelector('canvas#myCanvas');
*
* // Create a Textmodifier instance
* const textmodifier = await textmode.create(canvas, {renderMode: 'manual'});
*
* // Render a single frame
* textmodifier.render();
*
* // Export the current rendering to a TXT file
* textmodifier.saveStrings({
* filename: 'my_textmode_rendering',
* preserveTrailingSpaces: false
* });
* ```
*/
saveStrings(options?: TXTExportOptions): void;
/**
* Generate the current textmode rendering as an SVG string.
* @param options Options for SVG generation *(excluding filename)*
* @returns SVG content as a string.
*
* @example
* ```javascript
* // Fetch a canvas element to apply textmode rendering to
* const canvas = document.querySelector('canvas#myCanvas');
*
* // Create a Textmodifier instance
* const textmodifier = await textmode.create(canvas, {renderMode: 'manual'});
*
* // Render a single frame
* textmodifier.render();
*
* // Get the current rendering as an SVG string
* const svgString = textmodifier.toSVG({
* includeBackgroundRectangles: true,
* drawMode: 'fill'
* });
*
* // Print to console or use otherwise
* console.log(svgString);
* ```
*/
toSVG(options?: Omit<SVGExportOptions, 'filename'>): string;
/**
* Export the current textmode rendering to an SVG file.
* @param options Options for SVG export
*
* @example
* ```javascript
* // Fetch a canvas element to apply textmode rendering to
* const canvas = document.querySelector('canvas#myCanvas');
*
* // Create a Textmodifier instance
* const textmodifier = await textmode.create(canvas, {renderMode: 'manual'});
*
* // Render a single frame
* textmodifier.render();
*
* // Export the current rendering to an SVG file
* textmodifier.saveSVG({
* filename: 'my_textmode_rendering',
* });
* ```
*/
saveSVG(options?: SVGExportOptions): void;
/**
* Export the current textmode rendering to an image file.
* @param filename The filename (without extension) to save the image as
* @param format The image format ('png', 'jpg', or 'webp')
* @param options Additional options for image export
*
* @example
* ```javascript
* // Fetch a canvas element to apply textmode rendering to
* const canvas = document.querySelector('canvas#myCanvas');
*
* // Create a Textmodifier instance
* const textmodifier = await textmode.create(canvas, {renderMode: 'manual'});
*
* // Render a single frame
* textmodifier.render();
*
* // Export the current rendering to a PNG file
* textmodifier.saveCanvas('my_textmode_rendering', 'png');
*
* // Export with custom options
* textmodifier.saveCanvas('my_textmode_rendering', 'jpg', {
* quality: 0.8,
* scale: 2.0,
* backgroundColor: 'white'
* });
* ```
*/
saveCanvas(filename: string, format?: ImageFormat, options?: Omit<ImageExportOptions, 'filename' | 'format'>): Promise<void>;
/**
* Update the font used for rendering.
* @param fontSource The URL of the font to load.
*
* @example
* ```javascript
* // Fetch a canvas element to apply textmode rendering to
* const canvas = document.querySelector('canvas#myCanvas');
*
* // Create a Textmodifier instance
* const textmodifier = await textmode.create(canvas);
*
* // Load a custom font from a URL
* await textmodifier.loadFont('https://example.com/fonts/myfont.ttf');
*
* // Local font example
* // await textmodifier.loadFont('./fonts/myfont.ttf');
* ```
*/
loadFont(fontSource: string): Promise<void>;
/**
* Apply textmode rendering to the canvas.
*
* **Note:** In `'auto'` mode, this is called automatically.
* In `'manual'` mode, you need to call this method when you want to update the textmode rendering.
*
* @example
* ```javascript
* // p5.js example
*
* let textmodifier;
*
* // p5.js setup function
* async function setup() {
*
* // Create a p5.js canvas
* const canvas = createCanvas(800, 600);
*
* // Create a Textmodifier instance
* textmodifier = await textmode.create(canvas.elt);
*
* // Update the rendering mode to 'manual'
* textmodifier.renderMode('manual');
* }
*
* // p5.js draw function
* function draw() {
*
* // Draw something on the p5.js canvas
* background(220);
* fill(255, 0, 0);
* rect(50, 50, 100, 100);
*
* // Apply textmode rendering
* textmodifier.render();
* }
* ```
*/
render(): void;
private resize;
/**
* Start automatic rendering
*/
private startAutoRendering;
/**
* Update FPS measurement - works for both auto and manual modes
* Uses a rolling average for smoother frame rate reporting
*/
private measureFrameRate;
/**
* Stop automatic rendering
*/
private stopAutoRendering;
/**
* Update the rendering mode.
*
* If called without arguments, returns the current mode.
*
* - `'manual'`: Requires manual [render](#render) calls
* - `'auto'`: Automatically renders using [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame)
*
* @param mode The new rendering mode to set.
*
* @example
* ```javascript
* // Fetch a canvas element to apply textmode rendering to
* const canvas = document.querySelector('canvas#myCanvas');
*
* // Create a Textmodifier instance
* const textmodifier = await textmode.create(canvas);
*
* // Update the rendering mode to 'manual'
* textmodifier.renderMode('manual');
*
* // Now you need to call textmodifier.render() manually in your animation loop
* ```
*/
renderMode(mode: 'manual' | 'auto'): void;
/**
* Set the maximum frame rate for auto rendering. If called without arguments, returns the current measured frame rate.
* @param fps The maximum frames per second for auto rendering.
*
* @example
* ```javascript
* // Fetch a canvas element to apply textmode rendering to
* const canvas = document.querySelector('canvas#myCanvas');
*
* // Create a Textmodifier instance
* const textmodifier = await textmode.create(canvas);
*
* // Set the maximum frame rate to 30 FPS
* textmodifier.frameRate(30);
* ```
*/
frameRate(fps?: number): number | void;
/**
* Set the font size used for rendering.
* @param size The font size to set.
*
* @example
* ```javascript
* // Fetch a canvas element to apply textmode rendering to
* const canvas = document.querySelector('canvas#myCanvas');
*
* // Create a Textmodifier instance
* const textmodifier = await textmode.create(canvas);
*
* // Set the font size to 24
* textmodifier.fontSize(24);
* ```
*/
fontSize(size: number): void;
/**
* Set a draw callback function that will be executed before each render.
* This method is primarily useful for standalone textmodifier instances.
* @param callback The function to call before each render
*
* @example
* ```javascript
* // Create a standalone textmodifier instance
* const t = await textmode.create({
* width: 800,
* height: 600,
* });
*
* // Set up draw callback
* t.draw(() => {
* // Set background color
* t.background(128);
*
* // Draw some content
* t.fill(255, 0, 0); // Set fill color to red
* t.rect(50, 50, 100, 100);
* });
* ```
*/
draw(callback: () => void): void;
/**
* Set a callback function that will be called when the window is resized.
* @param callback The function to call when the window is resized.
*
* @example
* ```javascript
* // Create a standalone textmodifier instance
* const t = await textmode.create({
* width: window.innerWidth,
* height: window.innerHeight,
* });
*
* // Draw callback to update content
* t.draw(() => {
* // Set background color
* t.background(128);
*
* // Draw some content
* t.fill(255, 0, 0); // Set fill color to red
* t.rect(50, 50, 100, 100);
* });
*
* // Set up window resize callback
* t.windowResized(() => {
* // Resize the canvas to match window size
* t.resizeCanvas(window.innerWidth, window.innerHeight);
* });
*
*/
windowResized(callback: () => void): void;
/**
* Resize the `textmode.js` canvas.
* @param width The new width of the canvas.
* @param height The new height of the canvas.
*/
resizeCanvas(width: number, height: number): void;
/**
* @inheritDoc TextmodeConversionPipeline.get
*
* @example
* ```javascript
* // Fetch a canvas element to apply textmode rendering to
* const canvas = document.querySelector('canvas#myCanvas');
*
* // Create a Textmodifier instance
* const textmodifier = await textmode.create(canvas);
*
* // Get the pre-defined brightness converter from the pipeline
* const brightnessConverter = textmodifier.converter('brightness');
*
* // Update properties of the brightness converter
* brightnessConverter.invert(true);
* brightnessConverter.characters(" .,;:*");
* ```
*/
converter(name: string): TextmodeConverter | void;
/**
* Sets the fill color for subsequent rendering operations
* @param r Red component (0-255)
* @param g Green component (0-255, optional)
* @param b Blue component (0-255, optional)
* @param a Alpha component (0-255, optional)
*
* @example
* ```javascript
* const t = await textmode.create({
* width: 800,
* height: 600,
* })
*
* t.draw(() => {
* // Set the background color to black
* t.background(0);
*
* const centerX = t.width / 2;
* const centerY = t.height / 2;
* const radius = Math.min(t.width, t .height) / 3;
* const speed = 0.02; // Adjust speed of rotation
*
* const angle = t.frameCount * speed;
* const x = centerX + Math.cos(angle) * radius - 100;
* const y = centerY + Math.sin(angle) * radius - 50;
*
* // Set the fill color to white
* t.fill(255);
*
* // Draw a rectangle with the fill color
* t.rect(x, y, 200, 150);
* });
* ```
*/
fill(r: number, g?: number, b?: number, a?: number): void;
/**
* Sets the stroke color for subsequent rendering operations
* @param r Red component (0-255)
* @param g Green component (0-255, optional)
* @param b Blue component (0-255, optional)
* @param a Alpha component (0-255, optional)
*
* @example
* ```javascript
* const t = await textmode.create({
* width: 800,
* height: 600,
* })
*
* t.draw(() => {
* // Set the background color to black
* t.background(0);
*
* // Set stroke color to red and stroke weight to 4 pixels
* t.stroke(255, 0, 0);
* t.strokeWeight(4);
*
* // Draw a rectangle with red stroke
* t.rect(100, 100, 200, 150);
*
* // Rectangle with both fill and stroke
* t.fill(0, 255, 0);
* t.stroke(0, 0, 255);
* t.strokeWeight(2);
* t.rect(350, 100, 200, 150);
* });
* ```
*/
stroke(r: number, g?: number, b?: number, a?: number): void;
/**
* Sets the stroke weight (thickness) for subsequent stroke operations
* @param weight The stroke thickness in pixels
*
* @example
* ```javascript
* const t = await textmode.create({
* width: 800,
* height: 600,
* })
*
* t.draw(() => {
* t.background(0);
*
* // Thin stroke
* t.stroke(255);
* t.strokeWeight(1);
* t.rect(50, 50, 100, 100);
*
* // Thick stroke
* t.strokeWeight(8);
* t.rect(200, 50, 100, 100);
* });
* ```
*/
strokeWeight(weight: number): void;
/**
* Disables stroke rendering for subsequent operations
*
* @example
* ```javascript
* const t = await textmode.create({
* width: 800,
* height: 600,
* })
*
* t.draw(() => {
* t.background(0);
*
* // Rectangle with stroke
* t.fill(255, 0, 0);
* t.stroke(0, 255, 0);
* t.strokeWeight(4);
* t.rect(100, 100, 150, 100);
*
* // Rectangle without stroke (fill only)
* t.noStroke();
* t.rect(300, 100, 150, 100);
* });
* ```
*/
noStroke(): void;
/**
* Disables fill rendering for subsequent operations
*
* @example
* ```javascript
* const t = await textmode.create({
* width: 800,
* height: 600,
* })
*
* t.draw(() => {
* t.background(0);
*
* // Rectangle with fill
* t.fill(255, 0, 0);
* t.stroke(0, 255, 0);
* t.strokeWeight(4);
* t.rect(100, 100, 150, 100);
*
* // Rectangle without fill (stroke only)
* t.noFill();
* t.rect(300, 100, 150, 100);
* });
* ```
*/
noFill(): void;
/**
* Sets the rotation angle for subsequent rendering operations
* @param degrees The rotation angle in degrees
*
* @example
* ```javascript
* const t = await textmode.create({
* width: 800,
* height: 600,
* })
*
* t.draw(() => {
* t.background(0);
*
* // Normal rectangle
* t.fill(255, 0, 0);
* t.rect(100, 100, 150, 100);
*
* // Rotated rectangle
* t.push(); // Save current state
* t.rotate(45); // Rotate 45 degrees
* t.fill(0, 255, 0);
* t.rect(300, 100, 150, 100);
* t.pop(); // Restore state (no rotation)
*
* // Back to normal (no rotation)
* t.fill(0, 0, 255);
* t.rect(500, 100, 150, 100);
* });
* ```
*/
rotate(degrees: number): void;
/**
* Save the current rendering state (fill, stroke, etc.) to the state stack.
* Use with {@link pop} to isolate style changes within a block.
*
* @example
* ```javascript
* const t = await textmode.create({
* width: 800,
* height: 600,
* })
*
* t.draw(() => {
* t.background(0);
*
* // Set initial styles
* t.fill(255, 0, 0); // Red fill
* t.stroke(0, 255, 0); // Green stroke
* t.strokeWeight(4); // Thick stroke
*
* t.push(); // Save current state
*
* // Change styles temporarily
* t.fill(0, 0, 255); // Blue fill
* t.stroke(255, 255, 0); // Yellow stroke
* t.strokeWeight(2); // Thin stroke
* t.rect(100, 100, 150, 100);
*
* t.pop(); // Restore previous state
*
* // Back to red fill, green stroke, thick stroke
* t.rect(300, 100, 150, 100);
* });
* ```
*/
push(): void;
/**
* Restore the most recently saved rendering state from the state stack.
* Use with {@link push} to isolate style changes within a block.
*
* @example
* ```javascript
* const t = await textmode.create({
* width: 800,
* height: 600,
* })
*
* t.draw(() => {
* t.background(0);
*
* // Default styles
* t.fill(255); // White fill
* t.stroke(0); // Black stroke
* t.strokeWeight(1); // Thin stroke
*
* t.push(); // Save current state
*
* // Temporary style changes
* t.fill(255, 0, 0); // Red fill
* t.strokeWeight(8); // Thick stroke
* t.rect(50, 50, 100, 100);
*
* t.push(); // Save red style state
*
* t.fill(0, 255, 0); // Green fill
* t.noStroke(); // No stroke
* t.rect(200, 50, 100, 100);
*
* t.pop(); // Back to red fill, thick stroke
* t.rect(350, 50, 100, 100);
*
* t.pop(); // Back to white fill, black stroke, thin stroke
* t.rect(500, 50, 100, 100);
* });
* ```
*/
pop(): void;
/**
* Draw a rectangle with the current shader or fill color.
* @param x X-coordinate of the rectangle
* @param y Y-coordinate of the rectangle
* @param width Width of the rectangle
* @param height Height of the rectangle
*
* @example
* ```javascript
* const t = await textmode.create({
* width: 800,
* height: 600,
* })
*
* t.draw(() => {
* // Set the background color to black
* t.background(0);
*
* const centerX = t.width / 2;
* const centerY = t.height / 2;
* const radius = Math.min(t.width, t .height) / 3;
* const speed = 0.02; // Adjust speed of rotation
*
* const angle = t.frameCount * speed;
* const x = centerX + Math.cos(angle) * radius - 100;
* const y = centerY + Math.sin(angle) * radius - 50;
*
* // Set the fill color to white
* t.fill(255);
*
* // Draw a rectangle with the fill color
* t.rect(x, y, 200, 150);
* });
* ```
*/
rect(x: number, y: number, width?: number, height?: number): void;
/**
* Draw a line from point (x1, y1) to point (x2, y2) with the current stroke settings.
* Lines respect stroke color, stroke weight, and rotation, but ignore fill properties.
* @param x1 X-coordinate of the line start point
* @param y1 Y-coordinate of the line start point
* @param x2 X-coordinate of the line end point
* @param y2 Y-coordinate of the line end point
*
* @example
* ```javascript
* const t = await textmode.create({
* width: 800,
* height: 600,
* })
*
* t.draw(() => {
* // Set the background color to black
* t.background(0);
*
* // Draw a simple line
* t.stroke(255, 0, 0); // Red stroke
* t.strokeWeight(2); // 2px thick
* t.line(100, 100, 300, 200);
*
* // Draw an animated rotating line
* t.push();
* t.stroke(0, 255, 0); // Green stroke
* t.strokeWeight(4); // 4px thick
* t.rotate(t.frameCount * 2); // Rotate based on frame count
* t.line(400, 300, 600, 300);
* t.pop();
*
* // Draw a thick yellow line
* t.stroke(255, 255, 0); // Yellow stroke
* t.strokeWeight(8); // 8px thick
* t.line(200, 400, 400, 500);
*
* // Line with no stroke won't be visible
* t.noStroke();
* t.line(500, 100, 700, 200); // This won't render
* });
* ```
*/
line(x1: number, y1: number, x2: number, y2: number): void;
/**
* Set the background color for the canvas.
* @param r Red component (0-255)
* @param g Green component (0-255, optional)
* @param b Blue component (0-255, optional)
* @param a Alpha component (0-255, optional)
*
* @example
* ```javascript
* const t = await textmode.create({
* width: 800,
* height: 600,
* })
*
* t.draw(() => {
* // Set the background color to black
* t.background(0);
*
* const centerX = t.width / 2;
* const centerY = t.height / 2;
* const radius = Math.min(t.width, t .height) / 3;
* const speed = 0.02; // Adjust speed of rotation
*
* const angle = t.frameCount * speed;
* const x = centerX + Math.cos(angle) * radius - 100;
* const y = centerY + Math.sin(angle) * radius - 50;
*
* // Set the fill color to white
* t.fill(255);
*
* // Draw a rectangle with the fill color
* t.rect(x, y, 200, 150);
* });
* ```
*/
background(r: number, g?: number, b?: number, a?: number): void;
/**
* Create a shader program from vertex and fragment source code.
* @param vertexSource The GLSL source code for the vertex shader.
* @param fragmentSource The GLSL source code for the fragment shader.
* @returns The created shader program for use in `textmode.js`.
*/
createShader(vertexSource: string, fragmentSource: string): Shader;
/**
* Set the current shader for rendering.
* @param shader The shader program to use for rendering.
*/
shader(shader: Shader): void;
/**
* Set a uniform variable for the current shader.
* @param name The name of the uniform variable to set.
* @param value The value to set for the uniform variable.
*/
setUniform(name: string, value: any): void;
/** Get the current grid object used for rendering. */
get grid(): TextmodeGrid;
/** Get the current font object used for rendering. */
get font(): TextmodeFont;
/** Get the current rendering mode.*/
get mode(): 'manual' | 'auto';
/** Get the current textmode conversion pipeline. */
get pipeline(): TextmodeConversionPipeline;
/** Get the current frame count. */
get frameCount(): number;
/** Get the width of the canvas. */
get width(): number;
/** Get the height of the canvas. */
get height(): number;
}