Commit 78cbb375 authored by Andrey's avatar Andrey

Added PrinterNetworkManager. Updated examples.

parent 6fb75107
{"_info":"// This is a generated file; do not edit or check into version control.","dependencyGraph":[{"name":"flutter_bluetooth_basic","dependencies":[]},{"name":"wifi","dependencies":[]}]}
\ No newline at end of file
#
# NOTE: This podspec is NOT to be published. It is only used as a local source!
#
Pod::Spec.new do |s|
s.name = 'Flutter'
s.version = '1.0.0'
s.summary = 'High-performance, high-fidelity mobile apps.'
s.description = <<-DESC
Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS.
DESC
s.homepage = 'https://flutter.io'
s.license = { :type => 'MIT' }
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
s.ios.deployment_target = '8.0'
s.vendored_frameworks = 'Flutter.framework'
end
......@@ -2,9 +2,10 @@
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/andrey/Documents/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/andrey/Documents/Tablemi_v2/esc_pos_printer/example/discover_printers"
export "FLUTTER_TARGET=lib/main.dart"
export "FLUTTER_TARGET=/Users/andrey/Documents/Tablemi_v2/esc_pos_printer/example/discover_printers/lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "SYMROOT=${SOURCE_ROOT}/../build/ios"
export "FLUTTER_FRAMEWORK_DIR=/Users/andrey/Documents/flutter/bin/cache/artifacts/engine/ios"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "TRACK_WIDGET_CREATION=true"
PODS:
- Flutter (1.0.0)
- flutter_bluetooth_basic (0.0.1):
- Flutter
- wifi (0.0.1):
- Flutter
DEPENDENCIES:
- Flutter (from `.symlinks/flutter/ios`)
- flutter_bluetooth_basic (from `.symlinks/plugins/flutter_bluetooth_basic/ios`)
- wifi (from `.symlinks/plugins/wifi/ios`)
EXTERNAL SOURCES:
Flutter:
:path: ".symlinks/flutter/ios"
flutter_bluetooth_basic:
:path: ".symlinks/plugins/flutter_bluetooth_basic/ios"
wifi:
:path: ".symlinks/plugins/wifi/ios"
SPEC CHECKSUMS:
Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
flutter_bluetooth_basic: 0e4e27e22b50b3a25cc1d1e131953feb4af414f4
wifi: d7d77c94109e36c4175d845f0a5964eadba71060
PODFILE CHECKSUM: aff02bfeed411c636180d6812254b2daeea14d09
COCOAPODS: 1.7.5
COCOAPODS: 1.8.4
......@@ -63,7 +63,7 @@ class _MyHomePageState extends State<MyHomePage> {
}
print('subnet:\t$subnet, port:\t$port');
final stream = NetworkAnalyzer.discover(subnet, port);
final stream = NetworkAnalyzer.discover2(subnet, port);
stream.listen((NetworkAddress addr) {
if (addr.exists) {
......@@ -87,64 +87,83 @@ class _MyHomePageState extends State<MyHomePage> {
});
}
void testPrint(String printerIp, BuildContext ctx) {
Printer.connect(printerIp,
port: int.parse(portController.text), timeout: Duration(seconds: 5))
.then((printer) async {
printer.println('Normal text');
printer.println('Bold text', styles: PosStyles(bold: true));
printer.println('Reverse text', styles: PosStyles(reverse: true));
printer.println('Underlined text',
styles: PosStyles(underline: true), linesAfter: 1);
printer.println('Align left',
styles: PosStyles(align: PosTextAlign.left));
printer.println('Align center',
styles: PosStyles(align: PosTextAlign.center));
printer.println('Align right',
styles: PosStyles(align: PosTextAlign.right), linesAfter: 1);
printer.printRow([
PosColumn(
text: 'col3',
width: 3,
styles: PosStyles(align: PosTextAlign.center, underline: true),
),
PosColumn(
text: 'col6',
width: 6,
styles: PosStyles(align: PosTextAlign.center, underline: true),
),
PosColumn(
text: 'col3',
width: 3,
styles: PosStyles(align: PosTextAlign.center, underline: true),
),
]);
printer.println('Text size 200%',
styles: PosStyles(
height: PosTextSize.size2,
width: PosTextSize.size2,
));
// Print image
final ByteData data = await rootBundle.load('assets/logo.png');
final Uint8List bytes = data.buffer.asUint8List();
final Image image = decodeImage(bytes);
printer.printImage(image);
printer.cut();
printer.disconnect();
final snackBar =
SnackBar(content: Text('Success', textAlign: TextAlign.center));
Scaffold.of(ctx).showSnackBar(snackBar);
}).catchError((dynamic e) {
print('exception');
final snackBar =
SnackBar(content: Text('Fail', textAlign: TextAlign.center));
Scaffold.of(ctx).showSnackBar(snackBar);
});
Future<Ticket> testTicket() async {
final Ticket ticket = Ticket(PaperSize.mm80);
ticket.text(
'Regular: aA bB cC dD eE fF gG hH iI jJ kK lL mM nN oO pP qQ rR sS tT uU vV wW xX yY zZ');
ticket.text('Special 1: àÀ èÈ éÉ ûÛ üÜ çÇ ôÔ',
styles: PosStyles(codeTable: PosCodeTable.westEur));
ticket.text('Special 2: blåbærgrød',
styles: PosStyles(codeTable: PosCodeTable.westEur));
ticket.text('Bold text', styles: PosStyles(bold: true));
ticket.text('Reverse text', styles: PosStyles(reverse: true));
ticket.text('Underlined text',
styles: PosStyles(underline: true), linesAfter: 1);
ticket.text('Align left', styles: PosStyles(align: PosTextAlign.left));
ticket.text('Align center', styles: PosStyles(align: PosTextAlign.center));
ticket.text('Align right',
styles: PosStyles(align: PosTextAlign.right), linesAfter: 1);
ticket.row([
PosColumn(
text: 'col3',
width: 3,
styles: PosStyles(align: PosTextAlign.center, underline: true),
),
PosColumn(
text: 'col6',
width: 6,
styles: PosStyles(align: PosTextAlign.center, underline: true),
),
PosColumn(
text: 'col3',
width: 3,
styles: PosStyles(align: PosTextAlign.center, underline: true),
),
]);
ticket.text('Text size 200%',
styles: PosStyles(
height: PosTextSize.size2,
width: PosTextSize.size2,
));
// Print image
final ByteData data = await rootBundle.load('assets/logo.png');
final Uint8List bytes = data.buffer.asUint8List();
final Image image = decodeImage(bytes);
ticket.image(image);
// Print image using an alternative (obsolette) command
// ticket.imageRaster(image);
// Print barcode
final List<int> barData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 4];
ticket.barcode(Barcode.upcA(barData));
// Print mixed (chinese + latin) text. Only for printers supporting Kanji mode
// ticket.text(
// 'hello ! 中文字 # world @ éphémère &',
// styles: PosStyles(codeTable: PosCodeTable.westEur),
// containsChinese: true,
// );
ticket.feed(2);
ticket.cut();
return ticket;
}
void testPrint(String printerIp, BuildContext ctx) async {
final PrinterNetworkManager printerManager = PrinterNetworkManager();
printerManager.selectPrinter(printerIp, port: 9100);
final PosPrintResult res =
await printerManager.printTicket(await testTicket());
final snackBar = SnackBar(
content: Text(PosPrintResult.msg(res), textAlign: TextAlign.center));
Scaffold.of(ctx).showSnackBar(snackBar);
}
@override
......
......@@ -20,7 +20,7 @@ dependencies:
flutter:
sdk: flutter
wifi: ^0.1.5
ping_discover_network: ^0.1.2
ping_discover_network: ^0.2.0+1
image: ^2.1.4
esc_pos_printer:
path: ../../
......
import 'dart:io';
import 'package:esc_pos_printer/esc_pos_printer.dart';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:image/image.dart';
import 'package:esc_pos_printer/esc_pos_printer.dart';
void main() {
// To discover existing printers in your subnet, consider using
void main() async {
final PrinterNetworkManager printerManager = PrinterNetworkManager();
// To discover network printers in your subnet, consider using
// ping_discover_network package (https://pub.dev/packages/ping_discover_network).
// Note that most of ESC/POS printers by default listen on port 9100.
Printer.connect('192.168.0.123', port: 9100).then((printer) {
printer.println(
'Regular: aA bB cC dD eE fF gG hH iI jJ kK lL mM nN oO pP qQ rR sS tT uU vV wW xX yY zZ');
printer.println('Special 1: àÀ èÈ éÉ ûÛ üÜ çÇ ôÔ',
styles: PosStyles(codeTable: PosCodeTable.westEur));
printer.println('Special 2: blåbærgrød',
styles: PosStyles(codeTable: PosCodeTable.westEur));
// Note that most of ESC/POS printers are available on port 9100 by default.
printerManager.selectPrinter('192.168.0.123', port: 9100);
final PosPrintResult res =
await printerManager.printTicket(await testTicket());
print('Print result: ${PosPrintResult.msg(res)}');
}
Future<Ticket> testTicket() async {
final Ticket ticket = Ticket(PaperSize.mm80);
ticket.text(
'Regular: aA bB cC dD eE fF gG hH iI jJ kK lL mM nN oO pP qQ rR sS tT uU vV wW xX yY zZ');
ticket.text('Special 1: àÀ èÈ éÉ ûÛ üÜ çÇ ôÔ',
styles: PosStyles(codeTable: PosCodeTable.westEur));
ticket.text('Special 2: blåbærgrød',
styles: PosStyles(codeTable: PosCodeTable.westEur));
ticket.text('Bold text', styles: PosStyles(bold: true));
ticket.text('Reverse text', styles: PosStyles(reverse: true));
ticket.text('Underlined text',
styles: PosStyles(underline: true), linesAfter: 1);
ticket.text('Align left', styles: PosStyles(align: PosTextAlign.left));
ticket.text('Align center', styles: PosStyles(align: PosTextAlign.center));
ticket.text('Align right',
styles: PosStyles(align: PosTextAlign.right), linesAfter: 1);
ticket.row([
PosColumn(
text: 'col3',
width: 3,
styles: PosStyles(align: PosTextAlign.center, underline: true),
),
PosColumn(
text: 'col6',
width: 6,
styles: PosStyles(align: PosTextAlign.center, underline: true),
),
PosColumn(
text: 'col3',
width: 3,
styles: PosStyles(align: PosTextAlign.center, underline: true),
),
]);
ticket.text('Text size 200%',
styles: PosStyles(
height: PosTextSize.size2,
width: PosTextSize.size2,
));
printer.println('Bold text', styles: PosStyles(bold: true));
printer.println('Reverse text', styles: PosStyles(reverse: true));
printer.println('Underlined text',
styles: PosStyles(underline: true), linesAfter: 1);
printer.println('Align left', styles: PosStyles(align: PosTextAlign.left));
printer.println('Align center',
styles: PosStyles(align: PosTextAlign.center));
printer.println('Align right',
styles: PosStyles(align: PosTextAlign.right), linesAfter: 1);
printer.printRow([
PosColumn(
text: 'col3',
width: 3,
styles: PosStyles(align: PosTextAlign.center, underline: true),
),
PosColumn(
text: 'col6',
width: 6,
styles: PosStyles(align: PosTextAlign.center, underline: true),
),
PosColumn(
text: 'col3',
width: 3,
styles: PosStyles(align: PosTextAlign.center, underline: true),
),
]);
printer.println('Text size 200%',
styles: PosStyles(
height: PosTextSize.size2,
width: PosTextSize.size2,
));
// Print image
final ByteData data = await rootBundle.load('assets/logo.png');
final Uint8List bytes = data.buffer.asUint8List();
final Image image = decodeImage(bytes);
ticket.image(image);
// Print image using an alternative (obsolette) command
// ticket.imageRaster(image);
// Print image
const String filename = './logo.png';
final Image image = decodeImage(File(filename).readAsBytesSync());
printer.printImage(image);
// Print image using an alternative (obsolette) command
// printer.printImageRaster(image);
// Print barcode
final List<int> barData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 4];
ticket.barcode(Barcode.upcA(barData));
// Print barcode
final List<int> barData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 4];
printer.printBarcode(Barcode.upcA(barData));
// Print mixed (chinese + latin) text. Only for printers supporting Kanji mode
// ticket.text(
// 'hello ! 中文字 # world @ éphémère &',
// styles: PosStyles(codeTable: PosCodeTable.westEur),
// containsChinese: true,
// );
// Print mixed (chinese + latin) text. Only for printers supporting Kanji mode
// printer.println(
// 'hello ! 中文字 # world @ éphémère &',
// styles: PosStyles(codeTable: PosCodeTable.westEur),
// );
ticket.feed(2);
printer.cut();
printer.disconnect();
});
ticket.cut();
return ticket;
}
......@@ -11,6 +11,6 @@ export './src/barcode.dart';
export './src/enums.dart';
export './src/pos_column.dart';
export './src/pos_styles.dart';
export './src/printer.dart';
export './src/printer_bluetooth_manager.dart';
export './src/printer_network_manager.dart';
export './src/ticket.dart';
......@@ -10,6 +10,29 @@ enum PosTextAlign { left, center, right }
enum PosCutMode { full, partial }
enum PosFontType { fontA, fontB }
class PosPrintResult {
const PosPrintResult._internal(this.value);
final int value;
static const success = PosPrintResult._internal(1);
static const timeout = PosPrintResult._internal(2);
static const printerNotSelected = PosPrintResult._internal(3);
static const ticketEmpty = PosPrintResult._internal(4);
static String msg(PosPrintResult val) {
if (val == PosPrintResult.success) {
return 'Success';
} else if (val == PosPrintResult.timeout) {
return 'Error. Printer connection timeout';
} else if (val == PosPrintResult.printerNotSelected) {
return 'Error. Printer not selected';
} else if (val == PosPrintResult.ticketEmpty) {
return 'Error. Ticket is empty';
} else {
return 'Unknown error';
}
}
}
class PosTextSize {
const PosTextSize._internal(this.value);
final int value;
......
/*
* esc_pos_printer
* Created by Andrey Ushakov
*
* Copyright (c) 2019-2020. All rights reserved.
* See LICENSE for distribution and usage details.
*/
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:gbk_codec/gbk_codec.dart';
import 'package:hex/hex.dart';
import 'package:image/image.dart';
import 'barcode.dart';
import 'commands.dart';
import 'enums.dart';
import 'pos_column.dart';
import 'pos_styles.dart';
/// Network printer
class Printer {
Printer._internal(this._socket) {
reset();
}
final Socket _socket;
PosCodeTable _codeTable;
/// Creates a new socket connection to the network printer.
///
/// The argument [timeout] is used to specify the maximum allowed time to wait
/// for a connection to be established.
static Future<Printer> connect(
String host, {
int port = 9100,
Duration timeout,
}) {
return Socket.connect(host, port, timeout: timeout).then((socket) {
return Printer._internal(socket);
});
}
/// Disconnect from the printer
void disconnect() {
_socket.destroy();
}
/// Set global code table which will be used instead of the default printer's code table
void setGlobalCodeTable(PosCodeTable codeTable) {
_codeTable = codeTable;
if (codeTable != null) {
_socket.add(
Uint8List.fromList(
List.from(cCodeTable.codeUnits)..add(codeTable.value),
),
);
}
}
double _colIndToPosition(int colInd) {
return colInd == 0 ? 0 : (512 * colInd / 11 - 1);
}
/// Generic print for internal use
///
/// [colInd] range: 0..11
void _print(
String text, {
PosStyles styles = const PosStyles(),
int colInd = 0,
bool kanjiOff = true,
int colWidth = 12,
}) {
const charLen = 11.625; // 48 symbols per line
double fromPos = _colIndToPosition(colInd);
// Align
if (colWidth == 12) {
_socket.write(styles.align == PosTextAlign.left
? cAlignLeft
: (styles.align == PosTextAlign.center ? cAlignCenter : cAlignRight));
} else {
final double toPos = _colIndToPosition(colInd + colWidth) - 5;
final double textLen = text.length * charLen;
if (styles.align == PosTextAlign.right) {
fromPos = toPos - textLen;
} else if (styles.align == PosTextAlign.center) {
fromPos = fromPos + (toPos - fromPos) / 2 - textLen / 2;
}
}
final hexStr = fromPos.round().toRadixString(16).padLeft(3, '0');
final hexPair = HEX.decode(hexStr);
_socket.write(styles.bold ? cBoldOn : cBoldOff);
_socket.write(styles.turn90 ? cTurn90On : cTurn90Off);
_socket.write(styles.reverse ? cReverseOn : cReverseOff);
_socket.write(styles.underline ? cUnderline1dot : cUnderlineOff);
_socket.write(styles.fontType == PosFontType.fontA ? cFontA : cFontB);
// Text size
_socket.add(
Uint8List.fromList(
List.from(cSizeGSn.codeUnits)
..add(PosTextSize.decSize(styles.height, styles.width)),
),
);
// Position
_socket.add(
Uint8List.fromList(
List.from(cPos.codeUnits)..addAll([hexPair[1], hexPair[0]]),
),
);
// Cancel Kanji mode
if (kanjiOff) {
_socket.write(cKanjiOff);
} else {
_socket.write(cKanjiOn);
}
// Set local code table
if (styles.codeTable != null) {
_socket.add(
Uint8List.fromList(
List.from(cCodeTable.codeUnits)..add(styles.codeTable.value),
),
);
}
if (kanjiOff) {
_socket.add(latin1.encode(text));
} else {
_socket.add(gbk_bytes.encode(text));
}
}
/// Sens raw command(s)
void sendRaw(List<int> cmd, {bool kanjiOff = true}) {
if (kanjiOff) {
_socket.write(cKanjiOff);
}
_socket.add(Uint8List.fromList(cmd));
}
/// Prints one line of styled text
void println(
String text, {
PosStyles styles = const PosStyles(),
int linesAfter = 0,
bool containsChinese = false,
}) {
if (!containsChinese) {
_print(
text,
styles: styles,
kanjiOff: !containsChinese,
);
_socket.writeln();
emptyLines(linesAfter);
reset();
} else {
_printlnMixedKanji(text, styles: styles, linesAfter: linesAfter);
}
}
/// Break text into chinese/non-chinese lexemes
List _getLexemes(String text) {
bool _isChinese(String ch) {
return ch.codeUnitAt(0) > 255 ? true : false;
}
final List<String> lexemes = [];
final List<bool> isLexemeChinese = [];
int start = 0;
int end = 0;
bool curLexemeChinese = _isChinese(text[0]);
for (var i = 1; i < text.length; ++i) {
if (curLexemeChinese == _isChinese(text[i])) {
end += 1;
} else {
lexemes.add(text.substring(start, end + 1));
isLexemeChinese.add(curLexemeChinese);
start = i;
end = i;
curLexemeChinese = !curLexemeChinese;
}
}
lexemes.add(text.substring(start, end + 1));
isLexemeChinese.add(curLexemeChinese);
return <dynamic>[lexemes, isLexemeChinese];
}
/// Prints one line of styled mixed (chinese and latin symbols) text
void _printlnMixedKanji(
String text, {
PosStyles styles = const PosStyles(),
int linesAfter = 0,
}) {
final list = _getLexemes(text);
final List<String> lexemes = list[0];
final List<bool> isLexemeChinese = list[1];
// Print each lexeme using codetable OR kanji
for (var i = 0; i < lexemes.length; ++i) {
_print(
lexemes[i],
styles: styles,
kanjiOff: !isLexemeChinese[i],
);
}
_socket.writeln();
emptyLines(linesAfter);
reset();
}
/// Print selected code table.
///
/// If [codeTable] is null, global code table is used.
/// If global code table is null, default printer code table is used.
void printCodeTable({PosCodeTable codeTable}) {
_socket.write(cKanjiOff);
if (codeTable != null) {
_socket.add(
Uint8List.fromList(
List.from(cCodeTable.codeUnits)..add(codeTable.value),
),
);
}
final List<int> list = [];
for (int i = 0; i < 256; i++) {
list.add(i);
}
_socket.add(
Uint8List.fromList(list),
);
// Back to initial code table
setGlobalCodeTable(_codeTable);
}
/// Print a row.
///
/// A row contains up to 12 columns. A column has a width between 1 and 12.
/// Total width of columns in one row must be equal 12.
void printRow(List<PosColumn> cols) {
final validSum = cols.fold(0, (int sum, col) => sum + col.width) == 12;
if (!validSum) {
throw Exception('Total columns width must be equal to 12');
}
for (int i = 0; i < cols.length; ++i) {
final colInd =
cols.sublist(0, i).fold(0, (int sum, col) => sum + col.width);
if (!cols[i].containsChinese) {
_print(
cols[i].text,
styles: cols[i].styles,
colInd: colInd,
colWidth: cols[i].width,
);
} else {
final list = _getLexemes(cols[i].text);
final List<String> lexemes = list[0];
final List<bool> isLexemeChinese = list[1];
// Print each lexeme using codetable OR kanji
for (var j = 0; j < lexemes.length; ++j) {
_print(
lexemes[j],
styles: cols[i].styles,
colInd: colInd,
colWidth: cols[i].width,
kanjiOff: !isLexemeChinese[j],
);
}
}
}
_socket.writeln();
reset();
}
/// Beeps [n] times
///
/// Beep [duration] could be between 50 and 450 ms.
void beep({int n = 3, PosBeepDuration duration = PosBeepDuration.beep450ms}) {
if (n <= 0) {
return;
}
int beepCount = n;
if (beepCount > 9) {
beepCount = 9;
}
_socket.add(
Uint8List.fromList(
List.from(cBeep.codeUnits)..addAll([beepCount, duration.value]),
),
);
beep(n: n - 9, duration: duration);
}
/// Clear the buffer and reset text styles
void reset() {
_socket.write(cInit);
setGlobalCodeTable(_codeTable);
}
/// Skips [n] lines
///
/// Similar to [feed] but uses an alternative command
void emptyLines(int n) {
if (n > 0) {
_socket.write(List.filled(n, '\n').join());
}
}
/// Skips [n] lines
///
/// Similar to [emptyLines] but uses an alternative command
void feed(int n) {
if (n >= 0 && n <= 255) {
_socket.add(
Uint8List.fromList(
List.from(cFeedN.codeUnits)..add(n),
),
);
}
}
/// Reverse feed for [n] lines (if supported by the priner)
void reverseFeed(int n) {
_socket.add(
Uint8List.fromList(
List.from(cReverseFeedN.codeUnits)..add(n),
),
);
}
/// Cut the paper
///
/// [mode] is used to define the full or partial cut (if supported by the priner)
void cut({PosCutMode mode = PosCutMode.full}) {
_socket.write('\n\n\n\n\n');
if (mode == PosCutMode.partial) {
_socket.write(cCutPart);
} else {
_socket.write(cCutFull);
}
}
/// Generate multiple bytes for a number: In lower and higher parts, or more parts as needed.
///
/// [value] Input number
/// [bytesNb] The number of bytes to output (1 - 4)
List<int> _intLowHigh(int value, int bytesNb) {
final dynamic maxInput = 256 << (bytesNb * 8) - 1;
if (bytesNb < 1 || bytesNb > 4) {
throw Exception('Can only output 1-4 bytes');
}
if (value < 0 || value > maxInput) {
throw Exception(
'Number too large. Can only output up to $maxInput in $bytesNb bytes');
}
final List<int> res = <int>[];
int buf = value;
for (int i = 0; i < bytesNb; ++i) {
res.add(buf % 256);
buf = buf ~/ 256;
}
return res;
}
/// Replaces a single bit in a 32-bit unsigned integer.
int _transformUint32Bool(int uint32, int shift, bool newValue) {
return ((0xFFFFFFFF ^ (0x1 << shift)) & uint32) |
((newValue ? 1 : 0) << shift);
}
/// Merges each 8 values (bits) into one byte
List<int> _packBitsIntoBytes(List<int> bytes) {
const pxPerLine = 8;
final List<int> res = <int>[];
const threshold = 127; // set the greyscale -> b/w threshold here
for (int i = 0; i < bytes.length; i += pxPerLine) {
int newVal = 0;
for (int j = 0; j < pxPerLine; j++) {
newVal = _transformUint32Bool(
newVal,
pxPerLine - j,
bytes[i + j] > threshold,
);
}
res.add(newVal ~/ 2);
}
return res;
}
/// Print image using (ESC *) command
///
/// [image] is an instanse of class from [Image library](https://pub.dev/packages/image)
void printImage(Image imgSrc) {
final Image image = Image.from(imgSrc); // make a copy
const bool highDensityHorizontal = true;
const bool highDensityVertical = true;
invert(image);
flip(image, Flip.horizontal);
final Image imageRotated = copyRotate(image, 270);
const int lineHeight = highDensityVertical ? 3 : 1;
final List<List<int>> blobs = _toColumnFormat(imageRotated, lineHeight * 8);
// Compress according to line density
// Line height contains 8 or 24 pixels of src image
// Each blobs[i] contains greyscale bytes [0-255]
// const int pxPerLine = 24 ~/ lineHeight;
for (int blobInd = 0; blobInd < blobs.length; blobInd++) {
blobs[blobInd] = _packBitsIntoBytes(blobs[blobInd]);
}
final int heightPx = imageRotated.height;
const int densityByte =
(highDensityHorizontal ? 1 : 0) + (highDensityVertical ? 32 : 0);
final List<int> header = List.from(cBitImg.codeUnits);
header.add(densityByte);
header.addAll(_intLowHigh(heightPx, 2));
// Adjust line spacing (for 16-unit line feeds): ESC 3 0x10 (HEX: 0x1b 0x33 0x10)
sendRaw([27, 51, 16]);
for (int i = 0; i < blobs.length; ++i) {
sendRaw(List.from(header)..addAll(blobs[i])..addAll('\n'.codeUnits));
}
// Reset line spacing: ESC 2 (HEX: 0x1b 0x32)
sendRaw([27, 50]);
}
/// Extract slices of an image as equal-sized blobs of column-format data.
///
/// [image] Image to extract from
/// [lineHeight] Printed line height in dots
List<List<int>> _toColumnFormat(Image imgSrc, int lineHeight) {
final Image image = Image.from(imgSrc); // make a copy
// Determine new width: closest integer that is divisible by lineHeight
final int widthPx = (image.width + lineHeight) - (image.width % lineHeight);
final int heightPx = image.height;
// Create a black bottom layer
final biggerImage = copyResize(image, width: widthPx, height: heightPx);
fill(biggerImage, 0);
// Insert source image into bigger one
drawImage(biggerImage, image, dstX: 0, dstY: 0);
int left = 0;
final List<List<int>> blobs = [];
while (left < widthPx) {
final Image slice = copyCrop(biggerImage, left, 0, lineHeight, heightPx);
final Uint8List bytes = slice.getBytes(format: Format.luminance);
blobs.add(bytes);
left += lineHeight;
}
return blobs;
}
/// Print image using (GS v 0) obsolete command
///
/// [image] is an instanse of class from [Image library](https://pub.dev/packages/image)
void printImageRaster(
Image imgSrc, {
bool highDensityHorizontal = true,
bool highDensityVertical = true,
}) {
final Image image = Image.from(imgSrc); // make a copy
final int widthPx = image.width;
final int heightPx = image.height;
final int widthBytes = (widthPx + 7) ~/ 8;
final int densityByte =
(highDensityVertical ? 0 : 1) + (highDensityHorizontal ? 0 : 2);
final List<int> header = List.from(cRasterImg.codeUnits);
header.add(densityByte);
header.addAll(_intLowHigh(widthBytes, 2));
header.addAll(_intLowHigh(heightPx, 2));
grayscale(image);
invert(image);
// R/G/B channels are same -> keep only one channel
final List<int> oneChannelBytes = [];
final List<int> buffer = image.getBytes(format: Format.rgba);
for (int i = 0; i < buffer.length; i += 4) {
oneChannelBytes.add(buffer[i]);
}
// Add some empty pixels at the end of each line (to make the width divisible by 8)
final targetWidth = (widthPx + 8) - (widthPx % 8);
final missingPx = targetWidth - widthPx;
final extra = Uint8List(missingPx);
for (int i = 0; i < heightPx; i++) {
final pos = (i * widthPx + widthPx) + i * missingPx;
oneChannelBytes.insertAll(pos, extra);
}
// Pack bits into bytes
final List<int> res = _packBitsIntoBytes(oneChannelBytes);
sendRaw(List.from(header)..addAll(res));
}
/// Print barcode
///
/// [width] range and units are different depending on the printer model.
/// [height] range: 1 - 255. The units depend on the printer model.
/// Width, height, font, text position settings are effective until performing of ESC @, reset or power-off.
void printBarcode(
Barcode barcode, {
int width,
int height,
BarcodeFont font,
BarcodeText textPos = BarcodeText.below,
}) {
// Set text position
sendRaw(cBarcodeSelectPos.codeUnits + [textPos.value]);
// Set font
if (font != null) {
sendRaw(cBarcodeSelectFont.codeUnits + [font.value]);
}
// Set width
if (width != null && width >= 0) {
sendRaw(cBarcodeSetW.codeUnits + [width]);
}
// Set height
if (height != null && height >= 1 && height <= 255) {
sendRaw(cBarcodeSetH.codeUnits + [height]);
}
// Print barcode
final header = cBarcodePrint.codeUnits + [barcode.type.value];
sendRaw(header + barcode.data + [0]);
}
}
......@@ -94,7 +94,6 @@ class PrinterBluetoothManager {
_bluetoothManager.state.listen((state) async {
switch (state) {
case BluetoothManager.CONNECTED:
// print('********************* CONNECTED');
// To avoid double call
if (!_isConnected) {
await _bluetoothManager.writeData(bytes);
......@@ -102,14 +101,12 @@ class PrinterBluetoothManager {
}
// TODO sending disconnect signal should be event-based
_runDelayed(3).then((dynamic v) async {
// print('DISCONNECTING......');
await _bluetoothManager.disconnect();
_isPrinting = false;
});
_isConnected = true;
break;
case BluetoothManager.DISCONNECTED:
// print('********************* DISCONNECTED');
_isConnected = false;
break;
default:
......@@ -127,7 +124,7 @@ class PrinterBluetoothManager {
}
Future<void> printTicket(Ticket ticket) async {
if (ticket.bytes.isNotEmpty) {
if (ticket != null && ticket.bytes.isNotEmpty) {
final Future<void> res = writeBytes(ticket.bytes);
return res;
} else {
......
/*
* esc_pos_printer
* Created by Andrey Ushakov
*
* Copyright (c) 2019-2020. All rights reserved.
* See LICENSE for distribution and usage details.
*/
import 'dart:io';
import './enums.dart';
import './ticket.dart';
/// Printer Network Manager
class PrinterNetworkManager {
String _host;
int _port;
Duration _timeout;
/// Select a network printer
///
/// [timeout] is used to specify the maximum allowed time to wait
/// for a connection to be established.
void selectPrinter(
String host, {
int port = 9100,
Duration timeout = const Duration(seconds: 5),
}) {
_host = host;
_port = port;
_timeout = timeout;
}
Future<PosPrintResult> printTicket(Ticket ticket) {
if (_host == null || _port == null) {
return Future<PosPrintResult>.value(PosPrintResult.printerNotSelected);
} else if (ticket == null || ticket.bytes.isEmpty) {
return Future<PosPrintResult>.value(PosPrintResult.ticketEmpty);
}
return Socket.connect(_host, _port, timeout: _timeout)
.then((Socket socket) {
socket.add(ticket.bytes);
socket.destroy();
return Future<PosPrintResult>.value(PosPrintResult.success);
}).catchError((dynamic e) {
return Future<PosPrintResult>.value(PosPrintResult.timeout);
});
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment