🍊 portakal_flutter

Universal printer language SDK for Flutter & Dart — TSC, ZPL, EPL, ESC/POS, CPCL, DPL, IPL, SBPL, Star PRNT and more.

Text, images, printer-native barcode/QR commands, and Flutter preview widgets.

Tests Dart License

Note: Barcode/QR generation is NOT in this package. Use etiket for barcode & QR code generation (SVG/PNG). Portakal sends printer-native barcode/QR commands (the printer's built-in encoder) OR accepts pre-rendered images for pixel-perfect output.


Features

  • đŸ–¨ī¸ 9 printer languages — TSC, ZPL, EPL, ESC/POS, CPCL, DPL, IPL, SBPL, Star PRNT
  • 🔄 Cross-compilation — convert between any supported languages
  • đŸˇī¸ Fluent label builder — design labels with a clean, chainable API
  • 📝 HTML-like markup — <label><text>Hello</text></label> DSL
  • 🔍 Parsers — parse raw printer commands back to structured data
  • đŸ‘ī¸ SVG preview — render labels as SVG strings for visual inspection
  • 📱 Flutter preview widget — preview labels in-app before sending to printer
  • ✅ Validation — check command ordering, parameter ranges, and structure
  • đŸ–ŧī¸ Image processing — RGBA to monochrome with 4 dithering algorithms
  • 📇 17 printer profiles — Epson, Zebra, TSC, Star, Bixolon, Citizen, SATO, Honeywell
  • đŸ’¯ Works across Flutter targets — mobile, desktop, and web

Installation

dependencies:
  portakal_flutter: ^0.1.6

Example

dart run example/main.dart
flutter run -t example/flutter_preview.dart

Quick Start

Build & compile a label

import 'package:portakal_flutter/portakal_flutter.dart';

// Build a label with the fluent API
final myLabel = label(LabelConfig(width: 40, height: 30, unit: Unit.mm))
    .text('Hello World', TextOptions(x: 10, y: 10, font: '2', size: 2))
    .box(BoxOptions(x: 5, y: 5, width: 310, height: 230, thickness: 2))
    .circle(CircleOptions(x: 250, y: 150, diameter: 60));

// Compile to TSC
final tscCode = tsc.compile(myLabel);
// SIZE 40 mm,30 mm
// GAP 3 mm,0 mm
// CLS
// TEXT 10,10,"2",0,2,2,"Hello World"
// BOX 5,5,315,235,2
// CIRCLE 250,150,60,1
// PRINT 1

// Same label → ZPL
final zplCode = zpl.compile(myLabel);
// ^XA
// ^FO10,10^A0N,60,60^FDHello World^FS
// ^FO5,5^GB310,230,2^FS
// ^FO250,150^GC60,1^FS
// ^XZ

HTML-like markup

import 'package:portakal_flutter/portakal_flutter.dart';

final code = tsc.compile(markup('''
  <label width="100mm" height="150mm" dpi="203">
    <text x="10" y="10" size="2" bold>FROM: Warehouse A</text>
    <text x="10" y="40" size="3" bold>TO: John Doe</text>
    <text x="10" y="80">123 Main St, New York, NY 10001</text>
    <line x1="5" y1="110" x2="780" y2="110" thickness="2" />
    <box x="5" y="5" width="780" height="1170" border="3" />
  </label>
'''));

Cross-compilation

import 'package:portakal_flutter/portakal_flutter.dart';

// Convert TSC code to ZPL
final result = convert(tscCode, 'tsc', 'zpl');
print(result.output); // ZPL string

// Convert ZPL to ESC/POS (binary)
final receipt = convert(zplCode, 'zpl', 'escpos');
print(receipt.output); // Uint8List

Parse printer commands

import 'package:portakal_flutter/portakal_flutter.dart';

// Parse TSC
final tscResult = tsc.parse('SIZE 40 mm,30 mm\nCLS\nTEXT 10,10,"2",0,1,1,"Hello"\nPRINT 1');
print(tscResult.commands.length); // 4
print(tscResult.widthDots);       // 320
print(tscResult.elements.length); // 1 (TextElement)

// Parse ZPL
final zplResult = zpl.parse('^XA^FO10,10^A0N,30,30^FDHello^FS^XZ');
print(zplResult.commands.length); // 6

SVG preview

import 'package:portakal_flutter/portakal_flutter.dart';

final svg = tsc.preview(
  label(LabelConfig(width: 40, height: 30))
      .text('Preview Test', TextOptions(x: 10, y: 10, size: 2))
      .box(BoxOptions(x: 5, y: 5, width: 310, height: 230, thickness: 2)),
);
// Returns SVG string — render in a WebView or save to file

Flutter widget preview

import 'package:flutter/material.dart';
import 'package:portakal_flutter/portakal_flutter.dart';

class LabelPreviewCard extends StatelessWidget {
  const LabelPreviewCard({super.key});

  @override
  Widget build(BuildContext context) {
    final myLabel = label(LabelConfig(width: 40, height: 30))
        .text('Preview before print', TextOptions(x: 12, y: 20, size: 2))
        .box(BoxOptions(x: 8, y: 8, width: 300, height: 200, thickness: 2));

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: LabelPreview(label: myLabel),
      ),
    );
  }
}

Validate commands

import 'package:portakal_flutter/portakal_flutter.dart';

// Validate TSC
final r = tsc.validate('SIZE 40 mm,30 mm\nCLS\nTEXT 10,10,"2",0,1,1,"Hi"\nPRINT 1');
print(r.valid);    // true
print(r.errors);   // 0

// Catch errors
final bad = tsc.validate('TEXT 10,10,"2",0,1,1,"No CLS"\nPRINT 1');
print(bad.valid);  // false
print(bad.issues); // [CLS must appear before label elements]

Image processing

import 'dart:typed_data';
import 'package:portakal_flutter/portakal_flutter.dart';

// Convert RGBA pixels to monochrome bitmap
final rgba = Uint8List(100 * 100 * 4); // 100x100 RGBA image
final bitmap = imageToMonochrome(rgba, 100, 100);
// bitmap.data — 1-bit packed bitmap
// bitmap.bytesPerRow — bytes per scanline

// Use with label builder
final myLabel = label(LabelConfig(width: 40, height: 30))
    .image(bitmap, ImageOptions(x: 10, y: 10));

Printer profiles

import 'package:portakal_flutter/portakal_flutter.dart';

// Get a specific profile
final profile = getProfile('zebra-zd420');
print(profile?.dpi);        // 203
print(profile?.language);   // zpl
print(profile?.dotsPerLine); // 864

// Find by USB vendor ID
final epsonPrinters = findByVendorId(0x04B8);

// Find by language
final zplPrinters = findByLanguage('zpl');

// Use with label builder — auto-sets DPI
final myLabel = label(LabelConfig(
  width: 40, height: 30,
  printer: 'tsc-te310', // Uses 300 DPI from profile
));

Receipt formatting (ESC/POS)

import 'package:portakal_flutter/portakal_flutter.dart';

// Format a receipt line
final line = formatPair('Hamburger', '\$12.99', 32);
// "Hamburger                 $12.99"

// Word wrap
final lines = wordWrap('This is a long text that needs wrapping', 20);
// ["This is a long text", "that needs wrapping"]

// Table
final rows = formatTable(
  [Column(width: 20), Column(width: 8, align: 'right'), Column(width: 4, align: 'right')],
  [['Hamburger', '\$12.99', 'x2'], ['Fries', '\$4.99', 'x1']],
  32,
);

Supported Languages

Language Compile Parse Preview Validate
TSC/TSPL2 ✅ ✅ ✅ ✅ Full
ZPL II ✅ ✅ ✅ ✅ Full
EPL2 ✅ ✅ ✅ â„šī¸ Basic
ESC/POS ✅ ✅ ✅ â„šī¸ Basic
CPCL ✅ ✅ ✅ â„šī¸ Basic
DPL ✅ ✅ ✅ â„šī¸ Basic
IPL ✅ ✅ ✅ â„šī¸ Basic
SBPL ✅ ✅ ✅ â„šī¸ Basic
Star PRNT ✅ ✅ ✅ â„šī¸ Basic

Label Elements

Element Description TSC ZPL EPL ESC/POS CPCL DPL IPL SBPL Star PRNT
text Text with font, size, rotation ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
image Monochrome bitmap ✅ ✅ ✅ ✅ ✅ — — ✅ ✅
box Rectangle with optional radius ✅ ✅ ✅ — ✅ ✅ ✅ ✅ —
line Line between two points ✅ ✅ âš ī¸ — ✅ âš ī¸ âš ī¸ âš ī¸ —
circle Circle with thickness ✅ ✅ — — — — — — —
ellipse Ellipse ✅ — — — — — — — —
reverse Invert colors in region ✅ — — — — — — — —
erase Clear region to white ✅ ✅ — — — — — — —
raw Raw command passthrough ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅

âš ī¸ indicates a best-effort approximation (for example, non-diagonal-only line behavior).

Dithering Algorithms

Four algorithms for converting images to 1-bit monochrome:

Algorithm Best For
threshold Simple graphics, logos, barcodes
floydSteinberg Photos, gradients (best quality)
atkinson Retro aesthetic, lighter output
ordered Fast, predictable patterns

API Reference

Language Modules

Each language module (tsc, zpl, epl, escpos, cpcl, dpl, ipl, sbpl, starprnt) exports:

String    compile(LabelBuilder builder)  // Label → printer commands
Result    parse(String code)             // Printer commands → structured data
String    preview(LabelBuilder builder)  // Label → SVG string
Result    validate(String code)          // Check command validity

Core Functions

LabelBuilder  label(LabelConfig config)                           // Create label builder
LabelBuilder  markup(String source)                               // Parse HTML-like markup
ConvertResult convert(String code, String from, String to, {...}) // Cross-compile
String        renderPreview(ResolvedLabel label)                  // SVG preview
Result        validate(String code, String language)              // Validate commands

Development

# Run tests
dart test

# Run a specific test
dart test test/tsc_test.dart

# Static analysis
dart analyze

Credits

Dart/Flutter port of portakal by productdevbook.

License

MIT

Libraries

portakal_flutter
Portakal Flutter — Universal printer language SDK.