pure_ui 0.1.6 copy "pure_ui: ^0.1.6" to clipboard
pure_ui: ^0.1.6 copied to clipboard

'A pure Dart implementation of Canvas API compatible with dart:ui.'

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: ParagraphBuilderParagraphCanvas.drawParagraph() — full pipeline
  • Async Operations: picture.toImage() and image.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
  • ParagraphBuilder style 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

7
likes
130
points
369
downloads

Documentation

API reference

Publisher

verified publishernormidar.com

Weekly Downloads

'A pure Dart implementation of Canvas API compatible with dart:ui.'

Repository (GitHub)
View/report issues

Topics

#image #dart-ui #canvas #pure-dart #ui

License

MIT (license)

Dependencies

collection, ffi, image, meta, vector_math

More

Packages that depend on pure_ui