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
| Command | Description |
|---|---|
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 capFlat- Flat cap at endpointSquare- Square cap extending past endpointRound- Rounded capTriangle- Triangular cap
Line Join Types:
Miter- Sharp corners (with miter limit fallback to bevel)Bevel- Beveled cornersRound- 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:
- Primary font - The font set via
SetFont() - Chain fonts - If the primary font is a chain, try each font in order
- Global fallback - If set via
SetGlobalFallbackFont() - Missing glyph - A placeholder glyph (typically a box or question mark)
Font Parameters
| Parameter | Description |
|---|---|
filePath | Path to the font file (.ttf, .otf, .woff, etc.) |
fontName | Name to identify the font. If null, uses the filename. |
scale | Scale factor applied to the font (default: 1.0) |
offset | Vertical 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 textTextEffect.Shadow- Drop shadow beneathTextEffect.Outline- Expanded outline beneathTextEffect.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 boundsTextGradientSpace.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 ratioContain- Fit inside bounds, preserve aspect (letterbox)Cover- Cover bounds, preserve aspect (may crop)Tile- Tile at specified size
Pattern Repeat:
NoRepeat- Clamp to edgesRepeat- Tile both axesRepeatX- Tile X, clamp YRepeatY- 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!");
}