Codync
A free, open-source 1:1 alternative to Grok Bot and Muse. Message Claude Code, Codex, Cursor and 40+ coding agents as persistent bots from your iPhone, Mac or Linux desktop. Group chats, threads, appr
Deterministic 2D Canvas renderer achieving pixel-perfect, cross-browser consistent graphics output.
Translation pending
Developer toolsJavaScript · HTMLGitHub @davidedcOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗A deterministic 2D Javascript raster engine with Canvas-like API, cross-browser and Node.js.
Demo • Tests • Minimal Example
See index page for link to all browser tests, demos, and documentation.
isPointInPath and isPointInStroke implementation with accurate geometric calculationglobalCompositeOperation support with all 10 standard operations working correctlySWCanvas focuses on deterministic 2D graphics primitives and does not implement several HTML5 Canvas features:
fillText(), measureText(), font, textAlign, textBaseline, textPixelDensity) is supported via the vendored BitmapText.js bitmap-font engine — see § "Text Rendering" below for the API and ARCHITECTURE.md § "Text Rendering System" for the internals. strokeText() throws (BitmapText doesn't stroke glyphs); fillText(text, x, y, maxWidth) throws if maxWidth is passed (pre-rasterised atlas glyphs can't shrink-to-fit — explicit failure beats silent mis-render). No complex text layout, no direction handling beyond storage.drawImage() support (works with ImageLike objects, not arbitrary Canvas elements)getImageData()/putImageData()roundRect() Corners: Per-corner radii are supported, but each corner is circular - a {x, y} (DOMPointInit) radius throws a TypeError instead of drawing an elliptical corner⚠️ Important Performance Note: SWCanvas prioritized cross-platform rendering consistency, educational and debugging purposes, and is not optimized for:
For animations beyond very basic drawings, expect significant performance limitations. The rendering engine uses CPU-based pixel manipulation in Javascript without GPU acceleration, making it unsuitable for intensive animated content.
For a minimal implementation containing only transformations, basic shapes via direct drawing, and clipping (without the full path-based pipeline), see Minimal-2D-Js-Software-Renderer. This proof-of-concept project demonstrates the core direct-rendering approach with a significantly smaller codebase.
npm run build # Build the library
npm run minify # Create minified version
npm run build:prod # Build + minify in one command
This generates:
dist/swcanvas.js - Complete library for developmentdist/swcanvas.min.js - Minified library for production (84% smaller)dist/swcanvas.min.js.map - Source map for debuggingtests/dist/core-functionality-tests.js from the individual test files in /tests/core/tests/dist/visual-rendering-tests.js from the individual test files in /tests/visual/const SWCanvas = require('./dist/swcanvas.js');
// Create surface with immutable dimensions
const surface = SWCanvas.Core.Surface(800, 600);
const ctx = new SWCanvas.Core.Context2D(surface);
// Use Canvas 2D API
ctx.setFillStyle(255, 0, 0, 255); // Red
ctx.fillRect(10, 10, 100, 50);
// Advanced: Use OO classes directly
const transform = new SWCanvas.Core.Transform2D()
.translate(100, 100)
.rotate(Math.PI / 4);
const point = new SWCanvas.Core.Point(50, 75);
const rect = new SWCanvas.Core.Rectangle(0, 0, 200, 150);
const color = new SWCanvas.Core.Color(255, 128, 0, 200);
// Export as PNG (recommended - preserves transparency)
const pngData = SWCanvas.Core.PngEncoder.encode(surface);
// Or export as BMP (legacy - composites with white background)
const bmpData = SWCanvas.Core.BitmapEncoder.encode(surface);
<script src="dist/swcanvas.js"></script>
<script>
// Create surface and context
const surface = SWCanvas.Core.Surface(800, 600);
const ctx = new SWCanvas.Core.Context2D(surface);
// Standard Canvas 2D operations
ctx.setFillStyle(255, 0, 0, 255); // Red
ctx.fillRect(10, 10, 100, 50);
// Path operations including ellipses
ctx.beginPath();
ctx.ellipse(400, 200, 80, 40, Math.PI / 4, 0, 2 * Math.PI);
ctx.fill();
// Use immutable geometry classes
const center = new SWCanvas.Core.Point(400, 300);
const bounds = new SWCanvas.Core.Rectangle(100, 100, 600, 400);
// Check if point is within bounds
if (center.x >= bounds.left && center.x <= bounds.right &&
center.y >= bounds.top && center.y <= bounds.bottom) {
console.log('Center is within bounds');
}
// Transform operations
ctx.save();
const rotTransform = SWCanvas.Core.Transform2D.rotation(Math.PI / 6);
ctx.setTransform(rotTransform.a, rotTransform.b, rotTransform.c, rotTransform.d, rotTransform.e, rotTransform.f);
ctx.fillRect(50, 50, 100, 100);
ctx.restore();
</script>
Open examples/showcase.html in a web browser for a comprehensive demonstration of SWCanvas capabilities:
swcanvas.min.js) with automatic fallbackFeatures Demonstrated:
# View the example
open examples/showcase.html
See examples/README.md for additional examples and usage instructions.
npm test
This runs:
/tests/core/)tests/output/Open tests/browser/index.html in a web browser for:
Open tests/direct-rendering/performance-tests.html for comparative benchmarking between SWCanvas Direct rendering and native HTML5 Canvas (ramp-up methodology measuring maximum shapes per frame).
See tests/README.md for detailed test documentation.
SWCanvas provides dual API architecture for maximum flexibility:
Drop-in replacement for HTML5 Canvas with familiar API:
// Create canvas element (works in Node.js and browsers)
const canvas = SWCanvas.createCanvas(800, 600);
const ctx = canvas.getContext('2d');
// Standard HTML5 Canvas API
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 100, 50);
ctx.strokeStyle = '#0066cc';
ctx.lineWidth = 2;
ctx.strokeRect(20, 20, 80, 30);
// Line dashing
ctx.setLineDash([5, 5]); // Dashed line pattern
ctx.lineDashOffset = 0; // Starting offset
ctx.beginPath();
ctx.moveTo(10, 70);
ctx.lineTo(100, 70);
ctx.stroke();
// Shadows
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'; // Semi-transparent black
ctx.shadowBlur = 5; // 5px blur radius
ctx.shadowOffsetX = 3; // 3px right offset
ctx.shadowOffsetY = 3; // 3px down offset
ctx.fillStyle = 'red';
ctx.fillRect(120, 10, 50, 30); // Rectangle with shadow
// Rounded corners with arcTo
ctx.beginPath();
ctx.moveTo(50, 100);
ctx.lineTo(150, 100);
ctx.arcTo(200, 100, 200, 150, 25); // 25px radius rounded corner
ctx.lineTo(200, 200);
ctx.stroke();
// Rounded rectangles: one radius, or per-corner radii in CSS border-radius order
// [topLeft, topRight, bottomRight, bottomLeft] ([a, b] and [a, b, c] expand as in HTML5)
ctx.beginPath();
ctx.roundRect(10, 10, 160, 24, [8, 8, 0, 0]); // a title bar: rounded top, square bottom
ctx.fill();
// Path hit testing
ctx.beginPath();
ctx.rect(10, 120, 100, 60);
ctx.fillStyle = 'blue';
ctx.fill();
// Test if points are inside the filled rectangle
if (ctx.isPointInPath(60, 150)) {
console.log('Point (60, 150) is inside the rectangle');
}
if (ctx.isPointInPath(60, 150, 'evenodd')) {
console.log('Point is inside using evenodd fill rule');
}
// Test if points are on the stroke outline
ctx.lineWidth = 5;
ctx.stroke();
if (ctx.isPointInStroke(48, 150)) { // On stroke edge
console.log('Point (48, 150) is on the stroke outline');
}
// Composite operations (Porter-Duff blending)
ctx.fillStyle = 'red';
ctx.fillRect(30, 30, 40, 40);
ctx.globalCompositeOperation = 'destination-over'; // Draw behind existing content
ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 40, 40);
// All Porter-Duff operations supported:
// Source-bounded operations: source-over (default), destination-over, destination-out, xor
// Canvas-wide operations: destination-atop, destination-in, source-atop, source-in, source-out, copy
// ImageData API for pixel manipulation
const imageData = ctx.createImageData(100, 100);
// ... modify imageData.data ...
ctx.putImageData(imageData, 50, 50);
// Extract pixel data
const pixelData = ctx.getImageData(60, 60, 10, 10);
// Factory method for ImageData objects
const blankImage = SWCanvas.createImageData(50, 50);
Direct access to core classes with explicit RGBA values:
// Create surface and context directly
const surface = SWCanvas.Core.Surface(width, height);
const ctx = new SWCanvas.Core.Context2D(surface);
// Explicit RGBA values (0-255)
ctx.setFillStyle(255, 0, 0, 255); // Red
ctx.setStrokeStyle(0, 102, 204, 255); // Blue
// Rectangle filling
ctx.fillRect(x, y, width, height);
// Path operations
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x2, y2);
ctx.arc(cx, cy, radius, startAngle, endAngle, counterclockwise);
ctx.ellipse(cx, cy, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
ctx.arcTo(x1, y1, x2, y2, radius); // Rounded corners between lines
ctx.roundRect(x, y, w, h, radii); // radii: r, or [tl, tr, br, bl] (1-4 entries, as HTML5)
ctx.fill();
ctx.stroke();
// Path testing
const isInside = ctx.isPointInPath(x, y, fillRule); // Test if point is inside current path
const isOnStroke = ctx.isPointInStroke(x, y); // Test if point is on stroke outline
// Transforms
ctx.translate(x, y);
ctx.scale(x, y);
ctx.rotate(angle);
// Clipping
ctx.clip();
// State management
ctx.save();
ctx.restore();
// Image rendering
ctx.drawImage(imagelike, dx, dy); // Basic positioning
ctx.drawImage(imagelike, dx, dy, dw, dh); // With scaling
ctx.drawImage(imagelike, sx, sy, sw, sh, dx, dy, dw, dh); // With source rectangle
// Line dashing
ctx.setLineDash([10, 5]); // Set dash pattern: 10px dash, 5px gap
ctx.lineDashOffset = 2; // Starting offset into pattern
const pattern = ctx.getLineDash(); // Get current pattern: [10, 5]
// Shadow properties
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)'; // Shadow color with transparency
ctx.shadowBlur = 8; // Blur radius in pixels
ctx.shadowOffsetX = 4; // Horizontal shadow offset
ctx.shadowOffsetY = 4; // Vertical shadow offset
// Drawing with shadows (works with all drawing operations)
ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 100, 60); // Rectangle with shadow
ctx.strokeStyle = 'green';
ctx.lineWidth = 3;
ctx.strokeRect(50, 120, 100, 60); // Stroked rectangle with shadow
// Shadows work with paths and complex shapes
ctx.beginPath();
ctx.arc(300, 100, 40, 0, Math.PI * 2);
ctx.fillStyle = 'orange';
ctx.fill(); // Circle with shadow
// Turn off shadows
ctx.shadowColor = 'transparent'; // Or set to 'rgba(0,0,0,0)'
ctx.setFillStyle(r, g, b, a); // 0-255 values
ctx.setStrokeStyle(r, g, b, a); // 0-255 values
// Shadow properties (Core API uses explicit RGBA values)
ctx.setShadowColor(0, 0, 0, 128); // Semi-transparent black shadow
ctx.shadowBlur = 5; // 5px blur radius
ctx.shadowOffsetX = 3; // 3px horizontal offset
ctx.shadowOffsetY = 3; // 3px vertical offset
// Or use Color objects directly
const color = new SWCanvas.Core.Color(255, 128, 0, 200);
ctx.setFillStyle(color.r, color.g, color.b, color.a);
const shadowColor = new SWCanvas.Core.Color(0, 0, 0, 100);
ctx.setShadowColor(shadowColor.r, shadowColor.g, shadowColor.b, shadowColor.a);
SWCanvas supports HTML5 Canvas-compatible gradients and patterns for advanced fill and stroke operations. Both are evaluated at each device pixel's centre mapped back through the current transform, so they transform with the CTM under any affine. The radial gradient is HTML5's two-circle cone (transparent outside it). A pattern samples exactly as drawImage does (see Image Rendering) and copies its image's pixels at createPattern:
const canvas = SWCanvas.createCanvas(400, 300);
const ctx = canvas.getContext('2d');
// Linear gradients
const linearGrad = ctx.createLinearGradient(0, 0, 200, 0);
linearGrad.addColorStop(0, 'red');
linearGrad.addColorStop(0.5, 'yellow');
linearGrad.addColorStop(1, 'blue');
ctx.fillStyle = linearGrad;
ctx.fillRect(10, 10, 200, 100);
// Radial gradients
const radialGrad = ctx.createRadialGradient(150, 75, 0, 150, 75, 50);
radialGrad.addColorStop(0, '#ff0000');
radialGrad.addColorStop(1, '#0000ff');
ctx.fillStyle = radialGrad;
ctx.fillRect(100, 50, 100, 100);
// Conic gradients (CSS conic-gradient equivalent)
const conicGrad = ctx.createConicGradient(Math.PI / 4, 200, 150);
conicGrad.addColorStop(0, 'red');
conicGrad.addColorStop(0.25, 'yellow');
conicGrad.addColorStop(0.5, 'lime');
conicGrad.addColorStop(0.75, 'aqua');
conicGrad.addColorStop(1, 'red');
ctx.fillStyle = conicGrad;
ctx.fillRect(150, 100, 100, 100);
// Patterns with ImageLike objects
const patternImage = ctx.createImageData(20, 20);
// ... fill patternImage.data with pattern ...
const pattern = ctx.createPattern(patternImage, 'repeat');
ctx.fillStyle = pattern;
ctx.fillRect(50, 150, 150, 100);
// Gradients work with strokes too, including sub-pixel strokes
ctx.strokeStyle = linearGrad;
ctx.lineWidth = 5;
ctx.strokeRect(250, 50, 100, 100);
// Sub-pixel strokes work with all paint sources
ctx.strokeStyle = radialGrad;
ctx.lineWidth = 0.5; // 50% opacity stroke
ctx.strokeRect(250, 150, 100, 100);
// Shadows work with all paint sources
ctx.shadowColor = 'rgba(0, 0, 0, 0.4)';
ctx.shadowBlur = 6;
ctx.shadowOffsetX = 4;
ctx.shadowOffsetY = 4;
ctx.fillStyle = conicGrad;
ctx.fillRect(300, 10, 80, 80); // Gradient fill with shadow
const surface = SWCanvas.Core.Surface(400, 300);
const ctx = new SWCanvas.Core.Context2D(surface);
// Linear gradients
const linearGrad = ctx.createLinearGradient(0, 0, 200, 0);
linearGrad.addColorStop(0, new SWCanvas.Core.Color(255, 0, 0, 255));
linearGrad.addColorStop(1, new SWCanvas.Core.Color(0, 0, 255, 255));
ctx.setFillStyle(linearGrad);
ctx.fillRect(10, 10, 200, 100);
// Radial gradients
const radialGrad = ctx.createRadialGradient(150, 75, 0, 150, 75, 50);
radialGrad.addColorStop(0, new SWCanvas.Core.Color(255, 255, 0, 255));
radialGrad.addColorStop(1, new SWCanvas.Core.Color(255, 0, 255, 255));
ctx.setStrokeStyle(radialGrad);
ctx.lineWidth = 8;
ctx.beginPath();
ctx.arc(150, 75, 40);
ctx.stroke();
// Conic gradients
const conicGrad = ctx.createConicGradient(0, 200, 150);
conicGrad.addColorStop(0, new SWCanvas.Core.Color(255, 0, 0, 255));
conicGrad.addColorStop(0.33, new SWCanvas.Core.Color(0, 255, 0, 255));
conicGrad.addColorStop(0.66, new SWCanvas.Core.Color(0, 0, 255, 255));
conicGrad.addColorStop(1, new SWCanvas.Core.Color(255, 0, 0, 255));
ctx.setFillStyle(conicGrad);
ctx.fillRect(150, 100, 100, 100);
// Patterns with sub-pixel strokes
const imagelike = { width: 10, height: 10, data: new Uint8ClampedArray(400) };
// ... fill imagelike.data ...
const pattern = ctx.createPattern(imagelike, 'repeat-x');
ctx.setStrokeStyle(pattern);
ctx.lineWidth = 0.25; // 25% opacity stroke
ctx.strokeRect(50, 200, 200, 50);
// Shadows work with all paint sources (Core API)
ctx.setShadowColor(0, 0, 0, 100); // RGBA shadow color
ctx.shadowBlur = 4;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.setFillStyle(conicGrad);
ctx.fillRect(300, 200, 80, 80); // Conic gradient fill with shadow
'repeat' - Tile in both directions (default)'repeat-x' - Tile horizontally only'repeat-y' - Tile vertically only'no-repeat' - Display once at pattern origincreateLinearGradient(x0, y0, x1, y1) - Linear color transitioncreateRadialGradient(x0, y0, r0, x1, y1, r1) - Radial color transitioncreateConicGradient(startAngle, centerX, centerY) - Conic sweep transitionSWCanvas provides rich OO classes for advanced operations through the Core API:
// Immutable geometry classes
const point = new SWCanvas.Core.Point(100, 50);
const rect = new SWCanvas.Core.Rectangle(10, 20, 100, 80);
const center = rect.center; // Returns Point(60, 60)
// Immutable transformation matrix
const transform = new SWCanvas.Core.Transform2D()
.translate(100, 100)
.scale(2, 2)
.rotate(Math.PI / 4);
// Apply to points
const transformed = transform.transformPoint(point);
// Bit manipulation utility (used by mask classes)
const bitBuffer = new SWCanvas.Core.BitBuffer(100, 100, 0); // Default to 0s
bitBuffer.setPixel(50, 50, true);
console.log(bitBuffer.getPixel(50, 50)); // true
// Bounds tracking utility (used by SourceMask and ShadowBuffer)
const boundsTracker = new SWCanvas.Core.BoundsTracker();
boundsTracker.updateBounds(50, 75);
console.log(boundsTracker.getBounds()); // { minX: 50, maxX: 50, minY: 75, maxY: 75, isEmpty: false }
// Mask classes (use BitBuffer and BoundsTracker composition internally)
const clipMask = new SWCanvas.Core.ClipMask(800, 600);
const sourceMask = new SWCanvas.Core.SourceMask(800, 600);
// Image processing utilities
const validImage = SWCanvas.Core.ImageProcessor.validateAndConvert(imageData);
// Composite operations utilities
const supportedOps = SWCanvas.Core.CompositeOperations.getSupportedOperations();
const isSupported = SWCanvas.Core.CompositeOperations.isSupported('xor');
// Path processing utilities
const polygons = SWCanvas.Core.PathFlattener.flattenPath(path2d);
const strokePolys = SWCanvas.Core.StrokeGenerator.generateStrokePolygons(path2d, strokeProps);
// BMP encoding configuration
const encodingOptions = SWCanvas.Core.BitmapEncodingOptions.withGrayBackground(128);
SWCanvas supports drawing ImageLike objects. drawImage smooths whenever the
draw actually RESAMPLES the source — any rotation/skew, or an axis-aligned draw
whose effective sample step ≠ 1 (the CTM scale and the src/dst rect ratio
compose) — sampling bilinear at the destination pixel center, filtered
premultiplied. Draws that do not resample (axis-aligned, step exactly 1 — every
plain blit) take the texel under each destination pixel centre, so a plain blit
keeps its texels exactly. WHY bilinear on
rotation: a nearest-neighbor rotation periodically drops the pixels of 1-2px
source features, disintegrating them into dashes (see
debug/probe-rotated-thinline-gaps.js and
tests/core/057-drawimage-rotated-bilinear-contract.js); scaled-draw smoothing
matches the HTML5 default (imageSmoothingEnabled is implemented: default
true, boolean-coerced, save/restore state, settable on both API layers —
false forces nearest-neighbor for every transform, rotation included; see
tests/core/058-drawimage-scaled-smoothing-contract.js). A draw whose samples
land exactly on texel centers (e.g. an exact 90° setTransform with integer
translation) reproduces pure texels bit-exactly, no blur.
drawImage follows HTML5's rectangle and filtering rules: negative sizes name
the same rect from the opposite corner, a source rect reaching outside the image
is clipped to it (and the destination in proportion), and the bilinear taps
clamp to the image's edge texel. Patterns sample by the same rule, with one
difference at the border: a pattern's taps wrap on a repeating axis and read
transparent past a non-repeating one. Its mapping is the CTM composed with its
setTransform.
// ImageLike interface: { width, height, data: Uint8ClampedArray }
const imagelike = {
width: 10,
height: 10,
data: new Uint8ClampedArray(10 * 10 * 4) // RGBA
};
// RGB images auto-convert to RGBA
const rgbImage = {
width: 5,
height: 5,
data: new Uint8ClampedArray(5 * 5 * 3) // RGB → RGBA with alpha=255
};
// Basic usage
ctx.drawImage(imagelike, 10, 10); // Draw at position
ctx.drawImage(imagelike, 10, 10, 20, 20); // Draw with scaling
ctx.drawImage(imagelike, 0, 0, 5, 5, 10, 10, 10, 10); // Source rectangle
// Works with transforms and clipping
ctx.translate(50, 50);
ctx.rotate(Math.PI / 4);
ctx.drawImage(imagelike, 0, 0);
Text rendering is backed by the vendored BitmapText.js engine — pre-rasterised glyph atlases blitted at integer pixel positions. The HTML5 surface is ctx.font / ctx.textAlign / ctx.textBaseline / ctx.fillText / ctx.measureText, plus one non-standard property ctx.textPixelDensity for HiDPI. See ARCHITECTURE.md § "Text Rendering System" for the render-path internals.
One-time setup. Atlases ship separately from dist/swcanvas.js. Run the download script once to populate font-assets/:
./scripts/download-bitmaptext-assets.sh # ~157 MB of WebP atlases (browser-side)
# Optional, only for file:// loading in browsers:
npm run text:wrap-for-file # generate atlas-*-webp.js wrappers
Without assets, ctx.fillText(...) runs without crashing but emits no pixels — placeholder rectangles in textColor for every glyph.
Loading fonts. SWCanvas.fonts is shaped like the CSS Font Loading API:
// Load a font (returns a Promise)
await SWCanvas.fonts.load('Arial', { size: 16 });
await SWCanvas.fonts.load('Arial', { size: 16, weight: 'bold' });
await SWCanvas.fonts.load('Courier New', { size: 24, style: 'italic' });
// Check / unload
SWCanvas.fonts.has('Arial', { size: 16 }); // → boolean
SWCanvas.fonts.unload('Arial', { size: 16 });
// Or pass a BitmapText idString directly:
await SWCanvas.fonts.load('density-1-0-Arial-style-normal-weight-normal-size-16-0');
Atlases are addressed by { family, size, style?, weight?, pixelDensity? }. Sizes are in CSS pixels; valid sizes are 9–96 in the published font-assets release (sub-9 sizes render as placeholder rectangles).
Drawing.
const canvas = SWCanvas.createCanvas(400, 100);
const ctx = canvas.getContext('2d');
await SWCanvas.fonts.load('Arial', { size: 16 });
ctx.font = '16px Arial';
ctx.fillStyle = '#003388';
ctx.textBaseline = 'middle';
ctx.fillText('Hello, SWCanvas!', 10, 50);
// measureText returns a TextMetrics-shaped object (or null if no font is set):
const m = ctx.measureText('Hello'); // { width, actualBoundingBoxLeft, ... }
Behaviour worth knowing up front:
strokeText() throws — BitmapText doesn't stroke glyphs.fillText(text, x, y, maxWidth) throws if maxWidth is passed — atlas glyphs can't shrink-to-fit without distortion; explicit failure beats silent mis-render.ctx.font getter returns the canonical CSS shorthand (per HTML5 spec), not the user's verbatim input.console.warn — BitmapText needs a single CSS color.textAlign='start' / 'end' map to 'left' / 'right'; SWCanvas doesn't implement RTL.textColor rather than throwing — safe to call fillText before atlases finish loading.HiDPI text. ctx.textPixelDensity (non-standard, defaults to window.devicePixelRatio in browsers, 1 in Node) picks the atlas density used for fillText. For crisp HiDPI text, combine three things:
const dpr = window.devicePixelRatio || 1;
// 1. Backing surface at physical resolution
const canvas = SWCanvas.createCanvas(cssW * dpr, cssH * dpr);
const ctx = canvas.getContext('2d');
// 2. Scale the CTM so the rest of your drawing stays in CSS pixels
ctx.scale(dpr, dpr);
// 3. Pick the matching atlas density and pre-load it
ctx.textPixelDensity = dpr;
await SWCanvas.fonts.load('Arial', { size: 16, pixelDensity: dpr });
Because the CTM scale (step 2) matches the atlas density (step 3), this recipe takes TextRenderer's direct-blit fast path — glyphs go straight to the backing surface with no intermediate buffer (see ARCHITECTURE.md § "Text Rendering System"). If instead you keep the SWCanvas at logical size and only set textPixelDensity = dpr (no ctx.scale), text still renders correctly but takes the slow path: the higher-resolution intermediate is downsampled back to logical pixels inside drawImage — harmless but pointless. Density 1 and density 2 are separate atlas downloads and separate LRU entries.
Custom asset directory. The default is ./font-assets/ (relative to the page in browsers, to process.cwd() in Node). To use a different path:
SWCanvas.fonts._raw.BitmapText.setFontDirectory('./assets/fonts/');
SWCanvas.fonts._raw — escape hatch for advanced use. Exposes BitmapText internals for callers that need them (custom loaders, LRU eviction policies, the demo at examples/text-lru-atlas-demo.html):
| Member | Use |
|---|---|
BitmapText | The static engine class — setFontDirectory, setFontLoader, drawTextFromAtlas, computeInkBoundingBox, etc. |
FontProperties | new FontProperties(density, family, style, weight, size) / FontProperties.fromIDString(id). |
TextProperties | Construct { textAlign, textBaseline, textColor, isKerningEnabled } for direct BitmapText calls. |
AtlasDataStore | Inspect / manipulate the in-memory atlas cache. |
FontManifest | Enumerate registered font idStrings (FontManifest.allFontIDs()). |
AtlasLRU | The reusable LRU cache class used by the demo. |
const pngData = SWCanvas.Core.PngEncoder.encode(surface);
// Returns ArrayBuffer containing PNG file data
// Preserves transparency without background compositing
// Compressed (DEFLATE, compressionLevel 6 by default) and deterministic:
// identical pixels give identical bytes in every engine
// PNG with custom options
const pngOptions = SWCanvas.Core.PngEncodingOptions.withTransparency();
const pngData = SWCanvas.Core.PngEncoder.encode(surface, pngOptions);
// compressionLevel 0 = uncompressed (stored blocks); 1-9 = DEFLATE, searching longer for matches
const storedPng = SWCanvas.Core.PngEncoder.encode(surface, SWCanvas.Core.PngEncodingOptions.withCompressionLevel(0));
const bmpData = SWCanvas.Core.BitmapEncoder.encode(surface);
// Returns ArrayBuffer containing BMP file data
// Transparent pixels composited with white background (default)
// Custom background colors for transparent pixel compositing
const grayOptions = SWCanvas.Core.BitmapEncodingOptions.withGrayBackground(128);
const bmpData = SWCanvas.Core.BitmapEncoder.encode(surface, grayOptions);
// Pre-defined background options
const blackBmp = SWCanvas.Core.BitmapEncoder.encode(surface,
SWCanvas.Core.BitmapEncodingOptions.withBlackBackground());
For detailed architecture, design patterns, and component organization, see ARCHITECTURE.md.
For direct rendering system documentation (RectOps, CircleOps, LineOps, ArcOps, RoundedRectOps), see DIRECT-RENDERING-SUMMARY.MD.
Linting and formatting tools are available for on-demand quality checks:
npm run lint # Check for code issues
npm run lint:fix # Auto-fix fixable issues
npm run format # Format all source files
npm run format:check # Check which files need formatting
These tools are configured to match existing code style (4-space indent, single quotes, no trailing commas) and handle project-specific patterns (preprocessor markers, build concatenation globals).
Enable debug assertions in development to catch contract violations and trace execution:
// Enable before loading SWCanvas
globalThis.__SWCANVAS_DEBUG__ = true;
// Then load SWCanvas
const SWCanvas = require('./dist/swcanvas.js');
// Debug utilities available via Core API
SWCanvas.Core.assertDebug(condition, 'error message');
SWCanvas.Core.debugLog('trace message');
SWCanvas.Core.debugWarn('warning message');
Debug mode provides:
Production builds completely strip debug code via Terser's dead code elimination.
See debug/README.md for debugging scripts, templates, and investigation workflows.
MIT License - see LICENSE file for details.
npm run buildnpm testtests/browser/index.html in browser/tests/core/ or /tests/visual/ (see tests/README.md for test development, tests/build/README.md for build utilities)The comprehensive test suite ensures any changes maintain compatibility/similarity with HTML5 Canvas.