Skip to main content

Canvas Guide

The Canvas API provides an HTML5-style 2D vector graphics rendering system for EvolveUI. It follows a familiar immediate-mode drawing pattern similar to the browser's Canvas 2D context, making it intuitive for developers with web experience.

Core Concepts

State Machine

Canvas operates as a state machine. You configure drawing properties (colors, transforms, line styles) and then issue drawing commands. The current state affects all subsequent drawing operations until you change it.

canvas.SetFillColor(Color.red);    // Set state
canvas.FillRect(0, 0, 100, 100); // Draw with current state
canvas.SetFillColor(Color.blue); // Change state
canvas.FillRect(50, 50, 100, 100); // Draw with new state

Save and Restore

Canvas supports a state stack. Use Save() to push the current state and Restore() to pop it back. This is essential for isolating transformations and style changes.

canvas.Save();
canvas.Translate(100, 100);
canvas.Rotate(0.5f);
canvas.SetFillColor(Color.green);
canvas.FillRect(-25, -25, 50, 50); // Draws rotated rect at (100, 100)
canvas.Restore(); // Back to original transform and color

Coordinate System

Canvas uses a top-left origin coordinate system where:

  • X increases to the right
  • Y increases downward
  • Angles are in radians (clockwise rotation)

Path-Based Drawing

Canvas uses a path-based drawing model. You build up a path using path commands, then fill or stroke it.

Basic Path Workflow

canvas.BeginPath();           // Start a new path
canvas.MoveTo(10, 10); // Position pen
canvas.LineTo(100, 10); // Draw line
canvas.LineTo(100, 100); // Continue path
canvas.ClosePath(); // Connect back to start
canvas.Fill(); // Fill the shape
// or canvas.Stroke(); // Stroke the outline

Path Commands

CommandDescription
BeginPath()Clear and start a new path
MoveTo(x, y)Move pen without drawing
LineTo(x, y)Draw line to point
QuadraticCurveTo(cx, cy, x, y)Quadratic bezier curve
BezierCurveTo(c1x, c1y, c2x, c2y, x, y)Cubic bezier curve
Arc(cx, cy, r, startAngle, endAngle, dir)Arc segment
ArcTo(x1, y1, x2, y2, radius)Arc connecting two points
ClosePath()Close path to start point

Shape Primitives

Canvas provides convenience methods for common shapes:

canvas.Rect(x, y, width, height);
canvas.RoundedRect(x, y, width, height, radius);
canvas.RoundedRect(x, y, width, height, rTL, rTR, rBR, rBL); // Per-corner
canvas.BevelledRect(x, y, width, height, bevel);
canvas.Circle(cx, cy, radius);
canvas.Ellipse(cx, cy, rx, ry);

Convenience Drawing Methods

For simple cases, use the combined methods:

canvas.FillRect(x, y, w, h);      // BeginPath + Rect + Fill
canvas.StrokeRect(x, y, w, h); // BeginPath + Rect + Stroke
canvas.FillCircle(cx, cy, r); // BeginPath + Circle + Fill
canvas.StrokeCircle(cx, cy, r); // BeginPath + Circle + Stroke

Fill Styles

Solid Colors

Set fill color using multiple formats:

// Unity Color32 (sRGB)
canvas.SetFillColor(new Color32(255, 128, 0, 255));

// Unity Color (sRGB)
canvas.SetFillColor(Color.red);

// Byte RGBA (0-255, sRGB)
canvas.SetFillColor(255, 128, 0, 255);

// Float RGBA (0.0-1.0, sRGB)
canvas.SetFillColor(1.0f, 0.5f, 0.0f, 1.0f);

Linear Gradients

Simple two-color gradient:

canvas.SetLinearGradient(
startX, startY, // Gradient start point
endX, endY, // Gradient end point
Color.red, // Start color
Color.blue // End color
);
canvas.FillRect(0, 0, 200, 100);

Multi-stop gradient using ColorStop array:

var stops = new ColorStop[] {
new ColorStop(0.0f, Color.red),
new ColorStop(0.5f, Color.yellow),
new ColorStop(1.0f, Color.blue)
};
canvas.SetLinearGradient(0, 0, 200, 0, stops);
canvas.FillRect(0, 0, 200, 100);

Radial Gradients

Simple radial gradient:

canvas.SetRadialGradient(
centerX, centerY, // Center point
radius, // Radius
Color.white, // Inner color
Color.black // Outer color
);

Two-circle radial (HTML Canvas style):

canvas.SetRadialGradient(
x0, y0, r0, // Inner circle
x1, y1, r1, // Outer circle
innerColor, outerColor
);

Conic Gradients

Sweep around a center point:

canvas.SetConicGradient(
centerX, centerY,
startAngle, // In radians
startColor, endColor
);

Using CanvasGradient Objects

For reusable gradients with more control:

var gradient = CanvasGradient.CreateLinearGradient(0, 0, 100, 0);
gradient.AddColorStop(0.0f, Color.red);
gradient.AddColorStop(0.5f, Color.green);
gradient.AddColorStop(1.0f, Color.blue);

canvas.SetFillGradient(gradient);
canvas.FillRect(0, 0, 100, 50);

Step Interpolation

Create hard edges between colors:

var gradient = CanvasGradient.CreateLinearGradient(0, 0, 100, 0);
gradient.AddColorStop(0.0f, Color.red);
gradient.AddColorStopStep(0.5f, Color.blue); // Hard edge at 50%
gradient.AddColorStop(1.0f, Color.green);

Or use ColorStopInterpolation.Step in the ColorStop constructor.

Stroke Styles

Stroke Color

canvas.SetStrokeColor(Color.black);
// Same variants as SetFillColor: Color32, byte RGBA, float RGBA

Stroke Gradients

canvas.SetStrokeLinearGradient(x0, y0, x1, y1, startColor, endColor);
canvas.SetStrokeRadialGradient(cx, cy, radius, innerColor, outerColor);
canvas.SetStrokeConicGradient(cx, cy, angle, startColor, endColor);
canvas.SetStrokeGradient(canvasGradient);

Line Properties

canvas.SetStrokeWidth(3.0f);              // Stroke width in pixels
canvas.SetStrokeCapType(StrokeCapType.Round); // Line endings
canvas.SetStrokeJoinType(StrokeJoinType.Round); // Corner style
canvas.SetStrokeMiterLimit(10.0f); // Miter join limit

Line Cap Types:

  • None - No cap
  • Flat - Flat cap at endpoint
  • Square - Square cap extending past endpoint
  • Round - Rounded cap
  • Triangle - Triangular cap

Line Join Types:

  • Miter - Sharp corners (with miter limit fallback to bevel)
  • Bevel - Beveled corners
  • Round - Rounded corners

Dash Patterns

canvas.SetDashPattern(new float[] { 10, 5 });           // Array form
canvas.SetDashPattern(10, 5); // Individual values
canvas.SetDashPattern(new float[] { 10, 5 }, offset: 3); // With offset

Transforms

Basic Transforms

canvas.Translate(x, y);      // Move origin
canvas.Rotate(angle); // Rotate (radians)
canvas.Scale(sx, sy); // Scale
canvas.SkewX(angle); // Skew X axis
canvas.SkewY(angle); // Skew Y axis

Matrix Operations

// Apply transformation matrix [a b c d e f]
// | a c e |
// | b d f |
// | 0 0 1 |
canvas.Transform(a, b, c, d, e, f);

// Replace current transform
canvas.SetTransform(a, b, c, d, e, f);

// Reset to identity
canvas.ResetTransform();

// Get current transform
EVGTransform t = canvas.GetTransform();

Transform Example

canvas.Save();
canvas.Translate(150, 150); // Move to center
canvas.Rotate(MathF.PI / 4); // Rotate 45 degrees
canvas.SetFillColor(Color.red);
canvas.FillRect(-50, -50, 100, 100); // Draw centered rect
canvas.Restore();

Font Loading

Before rendering text, you need to load fonts. The UIAssetManager provides methods to load fonts at runtime.

Loading from File

// Load a font from a file path
VectorFont titleFont = assetManager.LoadVectorFont("/path/to/font.ttf");

// Load with custom name and scaling
VectorFont scaledFont = assetManager.LoadVectorFont(
"/path/to/font.otf",
fontName: "MyFont",
scale: 1.2f,
offset: 0.0f
);

Loading from Memory

// Load from a byte array (useful for embedded resources)
byte[] fontData = File.ReadAllBytes("font.ttf");
VectorFont font = assetManager.LoadVectorFontFromMemory(fontData, "EmbeddedFont");

// With scaling parameters
VectorFont scaled = assetManager.LoadVectorFontFromMemory(
fontData,
"ScaledFont",
scale: 0.9f,
offset: -0.05f
);

Finding Loaded Fonts

// Check if a font is loaded
if (assetManager.HasVectorFont("MyFont")) {
VectorFont font = assetManager.FindVectorFont("MyFont");
canvas.SetFont(font);
}

Font Chains

Font chains allow you to specify multiple fonts as fallbacks. When a character is not found in the first font, subsequent fonts in the chain are tried in order. This is useful for combining fonts with different character coverage.

// Load individual fonts
VectorFont mainFont = assetManager.LoadVectorFont("/path/to/main.ttf", "Main");
VectorFont emojiFont = assetManager.LoadVectorFont("/path/to/emoji.ttf", "Emoji");
VectorFont cjkFont = assetManager.LoadVectorFont("/path/to/cjk.ttf", "CJK");

// Create a chain: try Main first, then Emoji, then CJK
VectorFont chainedFont = assetManager.CreateFontChain("MyChain", mainFont, emojiFont, cjkFont);

// Use the chain like any other font
canvas.SetFont(chainedFont);
canvas.Text(10, 10, 500, "Hello 😀 世界"); // Each character uses the appropriate font

Font Families

Font families group multiple weights and styles of the same typeface. The system automatically selects the best match based on the current font weight and style settings.

// First load FontInfo for each variant
FontInfo regular = assetManager.LoadFontInfo("/fonts/Roboto-Regular.ttf");
FontInfo bold = assetManager.LoadFontInfo("/fonts/Roboto-Bold.ttf");
FontInfo italic = assetManager.LoadFontInfo("/fonts/Roboto-Italic.ttf");
FontInfo boldItalic = assetManager.LoadFontInfo("/fonts/Roboto-BoldItalic.ttf");

// Create a family with multiple weights and styles
VectorFont robotoFamily = assetManager.CreateFontFamily("Roboto",
new FontFamilyMemberInfo(regular, 400, FontStyle.Normal),
new FontFamilyMemberInfo(bold, 700, FontStyle.Normal),
new FontFamilyMemberInfo(italic, 400, FontStyle.Italic),
new FontFamilyMemberInfo(boldItalic, 700, FontStyle.Italic)
);

canvas.SetFont(robotoFamily);
canvas.SetFontWeight(400);
canvas.Text(10, 10, 500, "Regular text");

canvas.SetFontWeight(700);
canvas.Text(10, 40, 500, "Bold text");

canvas.SetFontStyle(FontStyle.Italic);
canvas.Text(10, 70, 500, "Bold italic text");

Global Fallback Font

In addition to font chains, you can set a global fallback font that applies to all text rendering when a character is not found in the primary font (or its chain).

VectorFont emojiFont = assetManager.LoadVectorFont("/path/to/emoji.ttf", "Emoji");
assetManager.SetGlobalFallbackFont(emojiFont);

// Get the current fallback
VectorFont currentFallback = assetManager.GetGlobalFallbackFont();

How Fallback Resolution Works

When rendering a character, the system searches for it in this order:

  1. Primary font - The font set via SetFont()
  2. Chain fonts - If the primary font is a chain, try each font in order
  3. Global fallback - If set via SetGlobalFallbackFont()
  4. Missing glyph - A placeholder glyph (typically a box or question mark)

Font Parameters

ParameterDescription
filePathPath to the font file (.ttf, .otf, .woff, etc.)
fontNameName to identify the font. If null, uses the filename.
scaleScale factor applied to the font (default: 1.0)
offsetVertical offset for baseline adjustment (default: 0.0)

Supported Font Formats

EvolveUI supports standard TrueType (.ttf) and OpenType (.otf) fonts, including:

  • Variable fonts with axis variations (weight, width, etc.)
  • Color fonts using CPAL/COLRv0 tables (multi-colored glyphs, layered emoji)

Note: COLRv1 fonts are not currently supported. If you need color emoji, use a font with COLRv0 or CPAL tables.

Text Rendering

Basic Text

canvas.SetFont(myVectorFont);
canvas.SetFontSize(24);
canvas.SetTextFillColor(Color.black);
canvas.Text(x, y, maxWidth, "Hello World");

Text Properties

// Font selection
canvas.SetFont(font);
canvas.SetFontSize(24);
canvas.SetFontWeight(700); // 100-900, 400=normal, 700=bold
canvas.SetFontStyle(FontStyle.Italic);
canvas.SetFontStretch(1.0f); // Horizontal stretch

// Typography
canvas.SetCharSpacing(0.05f); // Tracking in em
canvas.SetTextLineHeight(1.5f); // Line height in em
canvas.SetTextAlignment(TextAlignment.Center);
canvas.SetTextBaseline(TextBaseline.Alphabetic);

// Transforms
canvas.SetTextScale(1.0f, 1.0f);
canvas.SetTextSkew(0.2f); // Slant

Text Effects

Enable effects using flags:

canvas.SetTextEffect(TextEffect.Fill | TextEffect.Shadow | TextEffect.Stroke);

Available Effects:

  • TextEffect.Fill - Base filled text
  • TextEffect.Shadow - Drop shadow beneath
  • TextEffect.Outline - Expanded outline beneath
  • TextEffect.Stroke - Stroked outline on top

Configure each effect:

// Fill (base text color)
canvas.SetTextFillColor(Color.white);

// Shadow
canvas.SetTextShadowColor(new Color32(0, 0, 0, 128));
canvas.SetTextShadowOffset(2, 2);

// Outline
canvas.SetTextOutlineColor(Color.black);
canvas.SetTextOutlineSize(0.05f); // In em
canvas.SetTextOutlineOffset(0, 0);

// Stroke
canvas.SetTextStrokeColor(Color.black);
canvas.SetTextStrokeWidth(0.02f); // In em
canvas.SetTextStrokeCapType(StrokeCapType.Round);
canvas.SetTextStrokeJoinType(StrokeJoinType.Round);

Text Gradients

Apply gradients to any text effect:

// Set gradient coordinate space first
canvas.SetTextFillGradientSpace(TextGradientSpace.Layout); // 0-1 normalized to text bounds

// Then set the gradient
canvas.SetTextFillLinearGradient(0, 0, 1, 0, Color.red, Color.blue);

// Or use CanvasGradient objects
var gradient = CanvasGradient.CreateLinearGradient(0, 0, 1, 1);
gradient.AddColorStop(0, Color.red);
gradient.AddColorStop(1, Color.blue);
canvas.SetTextFillGradient(gradient);

Gradient Spaces:

  • TextGradientSpace.Em - Normalized to font em-square (per-glyph)
  • TextGradientSpace.Layout - Normalized to text layout bounds
  • TextGradientSpace.World - Pixel coordinates

Text Decorations

// Underline
canvas.SetTextUnderline(true);
canvas.SetTextUnderline(Color.blue); // Enable with color
canvas.SetTextUnderlineGradient(gradient);

// Strikethrough
canvas.SetTextStrikethrough(true);
canvas.SetTextStrikethrough(Color.red);
canvas.SetTextStrikethroughGradient(gradient);

Variable Font Axes

canvas.SetAxisVariation("wght", 700);   // Weight axis
canvas.SetAxisVariation("wdth", 75); // Width axis
canvas.ClearAxisVariations();

Patterns and Textures

Setting a Pattern

canvas.SetPatternTexture(textureId);
canvas.SetPatternMode(
PatternSpace.Bounds, // Coordinate space
PatternSizing.Cover, // How texture maps
PatternRepeat.Repeat // Repeat behavior
);

Pattern Space:

  • Bounds - UVs are [0,1] within shape bounds (CSS-like)
  • World - UVs are world/pixel coordinates

Pattern Sizing:

  • Stretch - Fill bounds, ignore aspect ratio
  • Contain - Fit inside bounds, preserve aspect (letterbox)
  • Cover - Cover bounds, preserve aspect (may crop)
  • Tile - Tile at specified size

Pattern Repeat:

  • NoRepeat - Clamp to edges
  • Repeat - Tile both axes
  • RepeatX - Tile X, clamp Y
  • RepeatY - Tile Y, clamp X

Pattern Properties

canvas.SetPatternTileSize(64, 64);      // For Tile mode
canvas.SetPatternPosition(0.5f, 0.5f); // Origin offset
canvas.SetPatternTransform(transform); // Additional transform
canvas.ClearPattern(); // Remove pattern

Clipping

canvas.BeginPath();
canvas.Circle(100, 100, 50);
canvas.Clip(); // Set as clip region

// Subsequent draws are clipped to circle
canvas.SetFillColor(Color.blue);
canvas.FillRect(0, 0, 200, 200); // Only visible inside circle

Global Alpha

Apply transparency to all drawing operations:

canvas.SetGlobalAlpha(0.5f);             // 50% transparent
canvas.FillRect(0, 0, 100, 100);
canvas.SetGlobalAlpha(1.0f); // Reset to opaque

Shadows (Path)

Generic shadows for path rendering (separate from text shadows):

canvas.SetShadowOffset(4, 4);
canvas.SetShadowColor(new Color32(0, 0, 0, 128));

Fill Rules

canvas.Fill();          // Non-zero winding rule (default)
canvas.FillEvenOdd(); // Even-odd rule for complex paths

Color Space

Canvas uses linear color internally for accurate gradient interpolation in OkLab color space. Input colors in sRGB (standard Color and Color32) are automatically converted. Use AddColorStopLinear() for direct linear color input.

Complete Example

public void Draw(Canvas canvas) {
// Background
canvas.SetFillColor(new Color32(240, 240, 240, 255));
canvas.FillRect(0, 0, 400, 300);

// Gradient rectangle
canvas.Save();
canvas.Translate(50, 50);

var gradient = CanvasGradient.CreateLinearGradient(0, 0, 150, 100);
gradient.AddColorStop(0.0f, new Color32(255, 100, 100, 255));
gradient.AddColorStop(1.0f, new Color32(100, 100, 255, 255));
canvas.SetFillGradient(gradient);
canvas.RoundedRect(0, 0, 150, 100, 10);
canvas.Fill();

// Stroked outline
canvas.SetStrokeColor(Color.black);
canvas.SetLineWidth(2);
canvas.Stroke();
canvas.Restore();

// Text with shadow
canvas.SetFont(titleFont);
canvas.SetFontSize(32);
canvas.SetTextEffect(TextEffect.Fill | TextEffect.Shadow);
canvas.SetTextFillColor(Color.white);
canvas.SetTextShadowColor(new Color32(0, 0, 0, 100));
canvas.SetTextShadowOffset(2, 2);
canvas.Text(50, 200, 300, "Hello Canvas!");
}