Pure UI - Pure Dart Alternative to Flutter's dart:ui
Pure UI is a Canvas API implemented in pure Dart without Flutter dependencies. It provides a fully compatible API with Flutter's dart:ui, enabling image processing, drawing operations, and text rendering anywhere Dart runs.
🎯 Why Choose Pure UI?
- Use dart:ui APIs outside Flutter projects
- Migrate existing Flutter code seamlessly
- Perfect for server-side and CLI image generation
- Render text from TTF fonts with no Flutter engine required
Key Features
- 🚀 Flutter Independent: Implemented in pure Dart, usable outside Flutter projects
- 🔄 dart:ui Full Compatibility: Use Flutter Canvas code as-is with complete API compatibility
- 🖼️ PNG Output: Export drawings in PNG format
- 📐 Vector Graphics: Path-based drawing support
- ✍️ Text Rendering: Full
ParagraphBuilder/Canvas.drawParagraph()pipeline powered by TTF font parsing - ⚡ Server-Side Ready: Image generation for web servers and batch processing
Migrating from dart:ui
Migrating Flutter Canvas code to Pure UI requires minimal changes:
// Before: Using dart:ui in Flutter projects
import 'dart:ui' as ui;
void drawInFlutter() {
final recorder = ui.PictureRecorder();
final canvas = ui.Canvas(recorder);
// ... drawing code ...
final picture = recorder.endRecording();
final image = picture.toImage(200, 200); // Sync in Flutter
}
// After: Using Pure UI (almost identical!)
import 'package:pure_ui/pure_ui.dart' as ui;
void drawWithPureUI() async { // Add async
final recorder = ui.PictureRecorder();
final canvas = ui.Canvas(recorder, const ui.Rect.fromLTWH(0, 0, 200, 200)); // Add bounds
// ... same drawing code ...
final picture = recorder.endRecording();
final image = await picture.toImage(200, 200); // Add await
}
Quick Start
import 'dart:io';
import 'package:pure_ui/pure_ui.dart';
void main() async {
// Create recorder and canvas
final recorder = PictureRecorder();
final canvas = Canvas(recorder, const Rect.fromLTWH(0, 0, 200, 200));
// Draw a red circle
final paint = Paint()
..color = const Color(0xFFFF0000)
..style = PaintingStyle.fill;
canvas.drawCircle(const Offset(100, 100), 50, paint);
// Save as PNG
final picture = recorder.endRecording();
final image = await picture.toImage(200, 200);
final bytes = await image.toByteData(format: ImageByteFormat.png);
await File('circle.png').writeAsBytes(bytes!.buffer.asUint8List());
// Clean up
image.dispose();
picture.dispose();
}
Text Rendering
Pure UI includes a full TTF text rendering pipeline compatible with Flutter's ParagraphBuilder API.
Setup
Register your TTF font file with FontLoader before building paragraphs:
import 'dart:io';
import 'package:pure_ui/pure_ui.dart';
void main() async {
// Register a TTF font (supports weight / style variants)
FontLoader.load('MyFont', File('path/to/MyFont-Regular.ttf').readAsBytesSync());
FontLoader.load('MyFont', File('path/to/MyFont-Bold.ttf').readAsBytesSync(),
weight: FontWeight.bold);
Basic Text Rendering
// Build a paragraph
final para = (ParagraphBuilder(ParagraphStyle(
fontFamily: 'MyFont',
fontSize: 32,
))
..pushStyle(TextStyle(
fontFamily: 'MyFont',
fontSize: 32,
color: const Color(0xFF222222),
))
..addText('Hello, World!')
..pop())
.build()
..layout(const ParagraphConstraints(width: 600));
// Draw it on a canvas
final recorder = PictureRecorder();
final canvas = Canvas(recorder, const Rect.fromLTWH(0, 0, 700, 100));
canvas.drawRect(
const Rect.fromLTWH(0, 0, 700, 100),
Paint()..color = const Color(0xFFFFFFFF),
);
canvas.drawParagraph(para, const Offset(20, 10));
// Export
final image = await recorder.endRecording().toImage(700, 100);
final bytes = await image.toByteData(format: ImageByteFormat.png);
await File('hello.png').writeAsBytes(bytes!.buffer.asUint8List());
image.dispose();
}
Multi-Style Spans
final para = (ParagraphBuilder(ParagraphStyle(fontFamily: 'MyFont', fontSize: 24))
..pushStyle(TextStyle(fontFamily: 'MyFont', fontSize: 24,
color: const Color(0xFF000000)))
..addText('Normal ')
..pushStyle(TextStyle(color: const Color(0xFFCC0000))) // inherits font/size
..addText('Red ')
..pop()
..pushStyle(TextStyle(fontWeight: FontWeight.bold))
..addText('Bold')
..pop()
..pop())
.build()
..layout(const ParagraphConstraints(width: 500));
Text Features
| Feature | API |
|---|---|
| Font size | TextStyle(fontSize: 24) |
| Color | TextStyle(color: Color(0xFFRRGGBB)) |
| Bold / italic | TextStyle(fontWeight: FontWeight.bold) |
| Underline | TextStyle(decoration: TextDecoration.underline) |
| Strikethrough | TextStyle(decoration: TextDecoration.lineThrough) |
| Letter spacing | TextStyle(letterSpacing: 2.0) |
| Word spacing | TextStyle(wordSpacing: 4.0) |
| Shadow | TextStyle(shadows: [Shadow(color: ..., offset: Offset(2, 2))]) |
| Text align | ParagraphStyle(textAlign: TextAlign.center) |
| Max lines | ParagraphStyle(maxLines: 2) |
| Ellipsis | ParagraphStyle(maxLines: 1, ellipsis: '...') |
| Line wrapping | Automatic greedy word-wrap |
| Hard line break | \n in text |
Unicode and Japanese Text
Any language supported by your TTF font works out of the box:
FontLoader.load('ArialUnicode', File('/Library/Fonts/Arial Unicode.ttf').readAsBytesSync());
final para = (ParagraphBuilder(ParagraphStyle(fontFamily: 'ArialUnicode', fontSize: 64))
..pushStyle(TextStyle(fontFamily: 'ArialUnicode', fontSize: 64,
color: const Color(0xFF222222)))
..addText('君、いいね')
..pop())
.build()
..layout(const ParagraphConstraints(width: 600));
Usage Examples
Basic Drawing
import 'dart:io';
import 'package:pure_ui/pure_ui.dart';
void main() async {
final recorder = PictureRecorder();
final canvas = Canvas(recorder, const Rect.fromLTWH(0, 0, 400, 300));
// Background
canvas.drawRect(const Rect.fromLTWH(0, 0, 400, 300),
Paint()..color = const Color.fromARGB(255, 240, 240, 255));
// Circle
canvas.drawCircle(const Offset(200, 150), 80,
Paint()..color = const Color.fromARGB(255, 255, 0, 0));
// Stroked rectangle
canvas.drawRect(const Rect.fromLTRB(50, 50, 350, 250),
Paint()
..color = const Color.fromARGB(255, 0, 0, 255)
..style = PaintingStyle.stroke
..strokeWidth = 4);
// Path
final path = Path()
..moveTo(50, 150)
..lineTo(150, 250)
..lineTo(250, 50)
..lineTo(350, 150);
canvas.drawPath(path,
Paint()
..color = const Color.fromARGB(255, 0, 180, 0)
..style = PaintingStyle.stroke
..strokeWidth = 3);
final image = await recorder.endRecording().toImage(400, 300);
final bytes = await image.toByteData(format: ImageByteFormat.png);
await File('output.png').writeAsBytes(bytes!.buffer.asUint8List());
image.dispose();
}
Complex Drawing with Transformations
import 'dart:io';
import 'dart:math' as math;
import 'package:pure_ui/pure_ui.dart';
void main() async {
final recorder = PictureRecorder();
final canvas = Canvas(recorder, const Rect.fromLTWH(0, 0, 300, 300));
canvas.drawRect(const Rect.fromLTWH(0, 0, 300, 300),
Paint()..color = const Color(0xFF1a1a2e));
canvas.save();
canvas.translate(150, 150);
for (int i = 0; i < 36; i++) {
canvas.save();
canvas.rotate(i * math.pi / 18);
final path = Path()
..moveTo(0, -50)
..quadraticBezierTo(20, -30, 0, -10)
..quadraticBezierTo(-20, -30, 0, -50)
..close();
canvas.drawPath(path,
Paint()
..color = Color.fromARGB(200,
(255 * math.sin(i * math.pi / 18)).abs().round(),
(255 * math.cos(i * math.pi / 12)).abs().round(),
255 - (i * 5))
..style = PaintingStyle.fill);
canvas.restore();
}
canvas.restore();
final image = await recorder.endRecording().toImage(300, 300);
final bytes = await image.toByteData(format: ImageByteFormat.png);
await File('artistic_pattern.png').writeAsBytes(bytes!.buffer.asUint8List());
image.dispose();
}
Simplified Export with exportImage
import 'dart:io';
import 'package:pure_ui/pure_ui.dart';
void main() async {
await exportImage(
canvasFunction: (canvas) {
canvas.drawRect(const Rect.fromLTWH(0, 0, 300, 200),
Paint()..color = const Color(0xFFFFFFFF));
canvas.drawCircle(const Offset(150, 100), 50,
Paint()..color = const Color(0xFFFF0000));
},
size: const Size(300, 200),
exportFile: File('simple_drawing.png'),
);
}
dart:ui Compatible Classes
Pure UI provides complete reimplementations of Flutter's dart:ui core classes:
- Canvas: Main drawing operations class with full transformation support
- Path: Shape path definition with Bézier curves and complex paths
- Paint: Drawing style with colors, stroke width, and painting styles
- Color: Color representation with ARGB support
- Rect: Rectangle operations and utilities
- Offset: 2D coordinate points
- Picture: Drawing operation recording with async image conversion
- PictureRecorder: Drawing recording management
- Image: Image representation with PNG export capabilities
- ParagraphBuilder: Multi-span text building with style stack
- Paragraph: Laid-out text with metrics (
height,longestLine,computeLineMetrics(), …) - TextStyle: Per-span style (font, size, color, decoration, shadows, …)
- ParagraphStyle: Paragraph-level style (alignment, maxLines, ellipsis, …)
- FontLoader: Font registry for TTF/OTF files
Key API features:
- Transformations:
save(),restore(),translate(),rotate(),scale() - Clipping:
clipRect(),clipPath()with proper clipping boundaries - Advanced Drawing: Paths with quadratic/cubic Bézier curves
- Text:
ParagraphBuilder→Paragraph→Canvas.drawParagraph()— full pipeline - Async Operations:
picture.toImage()andimage.toByteData()return Futures - Memory Management: Proper
dispose()methods for resource cleanup
Implemented Features
Canvas & Drawing
- Complete Canvas API — all major drawing operations
- Advanced transformations (rotation, translation, scaling, save/restore stack)
- Path drawing with quadratic and cubic Bézier curves
- Clipping (rectangle and path-based)
- PNG export via
ImageByteFormat.png - Color management with full ARGB / transparency support
Text Rendering
- TTF binary parser (cmap, glyf, hmtx, kern tables)
- Glyph rasterisation via TrueType quadratic-Bézier outlines + scanline fill
- Text shaper: advance widths, pair kerning, letter/word spacing
- Layout engine: greedy word-wrap, hard line breaks (
\n),maxLines, ellipsis,TextAlign ParagraphBuilderstyle stack with proper inheritance (pushStyle/pop)- Font weight and style variants (
FontWeight.bold,FontStyle.italic) - Text decorations: underline, overline, line-through
- Text shadows
- Unicode support (Latin, Japanese, CJK, and any script your font covers)
- Performance caches: parsed font cache + glyph polygon cache (skips Bézier re-tessellation)
Why Pure UI is Needed?
dart:ui Limitations
- Only usable within Flutter applications
- Cannot be used in CLI tools or batch processing
- Not available for web application backends
Pure UI Solutions
- Works Everywhere: Runs anywhere Dart VM is available
- Server-Side Image Generation: Chart/graph/label generation for web servers
- CLI Tools: Command-line image processing and text rendering
With Pure UI, you can leverage Flutter's excellent Canvas API anywhere!
License
MIT License
Libraries
- pure_ui
- Built-in types and core primitives for a Flutter application.