scribe_canvas 0.5.0
scribe_canvas: ^0.5.0 copied to clipboard
A high-performance Flutter multi-page canvas library for handwriting, drawing, and annotation with customizable tools and PDF export.
scribe_canvas #
A high-performance Flutter multi-page canvas library for handwriting, drawing, and annotation. Scribe handles multiple A4-sized pages with smooth variable-width strokes, customizable tools, PDF export, and a clean Controller-based API.
Features #
- đ Multi-page Canvas â Multiple A4-sized pages in a single scrollable view, with dynamic add/insert/delete support.
- đī¸ Variable-Width Drawing â Authentic ink-like strokes with dynamic width sampling, central-difference tangents, Catmull-Rom splines, and multi-pass smoothing filters.
- đ ī¸ Customizable Tools â Cycling pen/eraser sizes, multi-color palette, and an integrated
PageHeadertool bar. - đ Backgrounds & Templates â Load local or network images, or render a full PDF as per-page background templates.
- đŧī¸ Headers & Footers â Persistent banner images applied to every page on export.
- đ Navigation â Pinch-to-zoom and pan with dynamic constraints. No infinite zoom-out.
- âŠī¸ Undo / Redo â Global and per-page undo/redo stacks.
- đž Serialization â Save and restore canvas state as a compact JSON string (variable-width preserved).
- đ¤ PDF Export â Export the full multi-page drawing with backgrounds, headers, and footers.
Installation #
dependencies:
scribe_canvas: ^0.5.0
Quick Start #
All interactions with the canvas are done through a ScribeCanvasController. This follows Flutter's standard controller pattern (like TextEditingController or ScrollController).
import 'package:flutter/material.dart';
import 'package:scribe_canvas/scribe_canvas.dart';
class DrawingPage extends StatefulWidget {
const DrawingPage({super.key});
@override
State<DrawingPage> createState() => _DrawingPageState();
}
class _DrawingPageState extends State<DrawingPage> {
final _controller = ScribeCanvasController();
bool _isEraser = false;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(icon: const Icon(Icons.undo), onPressed: _controller.undo),
IconButton(icon: const Icon(Icons.redo), onPressed: _controller.redo),
IconButton(
icon: Icon(_isEraser ? Icons.edit : Icons.auto_fix_normal),
onPressed: () => setState(() => _isEraser = !_isEraser),
),
IconButton(
icon: const Icon(Icons.picture_as_pdf),
onPressed: _controller.exportToPdf,
),
],
),
body: ScribeCanvas(
controller: _controller, // attach the controller
isEraser: _isEraser,
onToggleEraser: (val) => setState(() => _isEraser = val),
onStrokeEnd: () => setState(() {}),
onUndo: () => setState(() {}),
onRedo: () => setState(() {}),
),
);
}
}
ScribeCanvas Properties #
| Property | Type | Default | Description |
|---|---|---|---|
controller |
ScribeCanvasController? |
â | Required to call any imperative API. |
color |
Color |
Colors.black |
Initial pen color. |
strokeWidth |
double |
4.0 |
Initial pen stroke width. |
strokeSizes |
List<double> |
[2,4,8,16,32] |
Sizes to cycle through in the header. |
isEraser |
bool |
false |
Whether eraser mode is active. |
eraserWidth |
double |
30.0 |
Initial eraser width. |
eraserSizes |
List<double> |
[10,20,30,60,100] |
Eraser sizes to cycle through. |
isPanMode |
bool |
false |
When true, disables drawing and enables panning only. |
multiPage |
bool |
true |
Enables multi-page mode. When false, the canvas is a single fixed page. |
initialColor |
Color |
Colors.black |
Initial color selected in the palette. |
colors |
List<Color> |
(6 colors) | Colors shown in the palette. |
onStrokeStart |
VoidCallback? |
â | Called when a stroke begins. |
onStrokeEnd |
VoidCallback? |
â | Called when a stroke is finalized. Good place to call setState to refresh undo/redo button states. |
onUndo |
VoidCallback? |
â | Called after an undo operation. |
onRedo |
VoidCallback? |
â | Called after a redo operation. |
onStrokeWidthChanged |
ValueChanged<double>? |
â | Called when the user cycles the pen size. |
onEraserWidthChanged |
ValueChanged<double>? |
â | Called when the user cycles the eraser size. |
onToggleEraser |
ValueChanged<bool>? |
â | Called when pen/eraser is toggled from the header. |
onColorChanged |
ValueChanged<Color>? |
â | Called when the user picks a color. |
ScribeCanvasController API #
Undo & Redo #
_controller.undo(); // Undo last global stroke
_controller.redo(); // Redo last global stroke
_controller.undoPage(0); // Undo last stroke on page 0
_controller.redoPage(0); // Redo last stroke on page 0
bool canUndo = _controller.canUndo; // Check before enabling undo button
bool canRedo = _controller.canRedo;
Canvas Operations #
_controller.clear(); // Clear all strokes
_controller.resetView(); // Reset zoom/pan to initial state
Page Management #
_controller.addPage(); // Append a new page at the end
_controller.insertPage(1); // Insert a blank page at index 1
_controller.deletePage(1); // Delete the page at index 1
Backgrounds & Templates #
// Local image bytes as a background for page 0
final bytes = await rootBundle.load('assets/template.png');
await _controller.setBackgroundImage(0, bytes.buffer.asUint8List(), clearOthers: true);
// Network image as background for page 0
await _controller.setNetworkBackgroundImage(0, 'https://example.com/bg.png');
// PDF rendered as per-page background templates
await _controller.loadPdfBackground(pdfBytes);
Headers & Footers #
Headers and footers are rendered on every page during PDF export.
// From local file
await _controller.setHeaderImage(headerBytes);
await _controller.setFooterImage(footerBytes);
// From network URL
await _controller.setNetworkHeaderImage('https://example.com/logo.png');
await _controller.setNetworkFooterImage('https://example.com/footer.png');
Serialization (Save & Load) #
// Save the current drawing state
final String data = _controller.getEncodedData();
await prefs.setString('canvas', data);
// Restore a previously saved drawing
final String? saved = prefs.getString('canvas');
if (saved != null) _controller.loadEncodedData(saved);
// Legacy format (no variable-width data)
final String legacy = _controller.getLegacyEncodedData();
_controller.loadLegacyEncodedData(legacy);
PDF Export #
// Renders all pages with backgrounds, headers, and footers
// and opens the system share / print dialog.
await _controller.exportToPdf();
Additional Information #
Scribe uses InteractiveViewer for navigation and CustomPainter for rendering. It leverages the pdf and printing packages for cross-platform PDF generation, and pdfx for rendering PDF background templates.