Reading a file in dart and split the string has different results in console that in vscode - dart

I'm new in dart, I'm trying to read information from a txt file and use the data to create objects from a class (in this case about pokemon), but when I run my program in the terminal it doesn't prints the correct information, and when I run the program in vscode (whit the dart extension, the "run" button) it prints in the debug console the correct information. What is the problem?
When I run the program in vscode I get in my print method (printP) this (which is what I want)
vscode:
Print method:
1+: Bulbasaur GRASS | POISON
but when I run the program in the terminal I get this.
Terminal:
Print method:
| POISONsaur
Here is the dart code.
main.dart
import 'dart:io';
import 'pokemon.dart';
void main() {
var file = new File("/home/ariel/Documents/script/pokemon.txt");
String str = file.readAsStringSync();
var pokes = str.split("[");
pokes = pokes.sublist(1, pokes.length);
getPokemon(pokes[0]).printP();
}
Pokemon getPokemon(String str) {
Pokemon p = new Pokemon();
print("string: " + str);
var aux = str.split("\n");
print(aux.length);
for (var i in aux) {
print("line: " + i);
}
p.number = int.parse(aux[0].split("]")[0]);
p.name = aux[1].split("=")[1];
p.type1 = aux[3].split("=")[1];
p.type2 = aux[4].split("=")[1];
return p;
}
pokemon.dart
class Pokemon {
String _name, _type1, _type2;
int _number;
Pokemon() {
this._name = "";
this._number = 0;
this._type1 = "";
this._type2 = "";
}
void printP() {
print("Print method:");
print("${this._number}+: ${this._name} ${this._type1} | ${this._type2}");
}
void set number(int n) {
this._number = n;
}
void set name(String nm) {
this._name = nm;
}
void set type1(String t) {
this._type1 = t;
}
void set type2(String t) {
this._type2 = t;
}
}
And here is the txt file
pokemon.txt
[1]
Name=Bulbasaur
InternalName=BULBASAUR
Type1=GRASS
Type2=POISON
BaseStats=45,49,49,45,65,65
GenderRate=FemaleOneEighth
GrowthRate=Parabolic
BaseEXP=64
EffortPoints=0,0,0,0,1,0
Rareness=45
Happiness=70
Abilities=OVERGROW
HiddenAbility=CHLOROPHYLL
Moves=1,TACKLE,3,GROWL,7,LEECHSEED,9,VINEWHIP,13,POISONPOWDER,13,SLEEPPOWDER,15,TAKEDOWN,19,RAZORLEAF,21,SWEETSCENT,25,GROWTH,27,DOUBLEEDGE,31,WORRYSEED,33,SYNTHESIS,37,SEEDBOMB
EggMoves=AMNESIA,CHARM,CURSE,ENDURE,GIGADRAIN,GRASSWHISTLE,INGRAIN,LEAFSTORM,MAGICALLEAF,NATUREPOWER,PETALDANCE,POWERWHIP,SKULLBASH,SLUDGE
Compatibility=Monster,Grass
StepsToHatch=5355
Height=0.7
Weight=6.9
Color=Green
Habitat=Grassland
Kind=Seed
Pokedex=Almacena energía en el bulbo de su espalda para alimentarse durante épocas de escasez de recursos o para atacar liberándola de golpe.
BattlerPlayerY=0
BattlerEnemyY=25
BattlerAltitude=0
Evolutions=IVYSAUR,Level,16

Your code are dependent on the newline format of your txt file. I will recommend you are using the LineSplitter class from dart:convert to split your lines.
The problem is that Windows newlines contains both '\n' and '\r' but you are only removing the '\n' part. '\r' are essential meaning the terminal should set the cursor back to the beginning of the line.
You can read this like a typewriter where you first move the head back and set move the paper to the next line. And can read a lot more about is topic here: https://en.wikipedia.org/wiki/Newline
The purpose of the LineSplitter class is to abstract all of this logic and get some behavior which will work on all platforms.
So import dart:convert and change this line:
var aux = str.split("\n");
Into:
var aux = LineSplitter.split(str).toList();

Related

How to run 2 process and exchange input/output between them multiple times?

I'm trying to do something like this:
Future<String> getOutAndAnswer(testcase) async {
Process python = await Process.start('python', ['tasks/histogram/run.py']);
Process java = await Process.start('java', ['solutions/Histogram.java']);
String results = "";
for(int i = 0; i < testcase; i++){
final String out = await python.stdout.transform(utf8.decoder).first;
java.stdin.writeln(out);
final String answer = await java.stdout.transform(utf8.decoder).first;
python.stdin.writeln(answer);
results += "($out, $answer)";
}
return results;
}
Basically, the python program is responsible for generating the input of each test case, then the java program will take the input and return the answer, which is sent to the python program to check if it's correct or not, and so on for every test case.
But when I try to use the above code I get an error saying I've already listened to the stream once:
Exception has occurred.
StateError (Bad state: Stream has already been listened to.)
Python program:
import os
CASE_DIR = os.path.join(os.path.dirname(__file__), "cases")
test_cases = next(os.walk(CASE_DIR))[2]
print(len(test_cases))
for case in sorted(test_cases):
with open(os.path.join(CASE_DIR, case), 'r') as f:
print(f.readline(), end='', flush=True)
f.readline()
expected_output = f.readline()
user_output = input()
if expected_output != user_output:
raise ValueError("Wrong answer!")
print("EXIT", flush=True)
Java program:
public class Histogram {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int t = scanner.nextInt();
for (int i = 0; i < t; i++) {
String input = scanner.nextLine();
String answer = calculateAnswer(input);
System.out.println(answer);
}
}
}
Your issue is with .first which is going to listen to the stream, get the first element, and then immediately stop listening to the stream. See the documentation here: https://api.dart.dev/stable/2.17.3/dart-async/Stream/first.html
You should instead listen once and define an onData method to perform the steps. See the documentation for .listen() here: https://api.dart.dev/stable/2.17.3/dart-async/Stream/listen.html
You could try wrapping the stdout streams in StreamIterator<String>. You will have to give it a try to verify, but I think this is what you are looking for.
Future<String> getOutAndAnswer(int testcase) async {
Process python = await Process.start('python', ['tasks/histogram/run.py']);
Process java = await Process.start('java', ['solutions/Histogram.java']);
String results = "";
StreamIterator<String> pythonIterator = StreamIterator(
python.stdout.transform(utf8.decoder).transform(LineSplitter()));
StreamIterator<String> javaIterator = StreamIterator(
java.stdout.transform(utf8.decoder).transform(LineSplitter()));
for (int i = 0; i < testcase; i++) {
if (await pythonIterator.moveNext()) {
final String out = pythonIterator.current;
if (out == 'EXIT') {
break;
}
java.stdin.writeln(out);
if (await javaIterator.moveNext()) {
final String answer = javaIterator.current;
python.stdin.writeln(answer);
results += "($out, $answer)";
}
}
}
await pythonIterator.cancel();
await javaIterator.cancel();
return results;
}
You may need to add the following imports:
import 'dart:async';
import 'dart:convert';

CodeMirror is not updated source on change

I am creating a salix webapp and I am trying to use codeMirror as per the salix examples.
I am unable to execute the codeMirror example in the salix.demo.basic, even though I can easily execute all the other examples in basic, so I'm wondering if anything has changed since these were written.
The code I am using:
module salix::demo::basic::CodeMirror
import salix::HTML;
import salix::App;
import salix::lib::CodeMirror;
import IO;
alias Model = tuple[list[Msg] changes, str src];
SalixApp[str] cmApp(str id = "root") = makeApp(id, init, view, update, parser=parseMsg);
App[str] cmWebApp()
= webApp(
cmApp(),
|project://projectName/src/salix/demo/basic/index.html|,
|project://projectName/src|
);
Model init()
= <[], "function hello() {\n console.log(\'Hello world\');\n}">;
data Msg
= myChange(int, int, int, int, str, str)
;
Model update(Msg msg, Model model) {
switch (msg) {
case m:myChange(int _, int _, int _, int _, str _, str _):{
println("Update called");
model.changes += [m];
}
}
return model;
}
void view(Model model) {
div(() {
h2("Code Mirror demo");
div(() {
codeMirror("cm", style(("height": "auto")), onChange(myChange),
mode("javascript"), lineNumbers(true), \value(model.src));
});
for (Msg m <- model.changes) {
text(m);
text("; ");
}
});
}
In my app I have managed to display the codeMirror example as so:
codeMirror("cm", onChange(cmChange), style(("height": "50%")),
lineNumbers(true), \value(model.projectViewInfo.src), lineWrapping(true), class("cm-s-3024-night"));
but the change message is never sent (cmChange) and the source is never updated. So it is partially working for me.
This example can also be found in the salix library
https://github.com/usethesource/salix/tree/master/src/salix/demo/basic

How to delete/detect any emoji as a whole from a string in Flutter?

I would like to simulate a keyboard backspace delete event from a string in Flutter (or Dart). Something like:
String str = "hello🇵🇬你们😀😀👨‍👩‍👦"
myBackspace(str) // will return "hello🇵🇬你们😀😀"
myBackspace(str) // will return "hello🇵🇬你们😀"
myBackspace(str) // will return "hello🇵🇬你们"
myBackspace(str) // will return "hello🇵🇬你"
myBackspace(str) // will return "hello🇵🇬"
myBackspace(str) // will return "hello"
myBackspace(str) // will return "hell"
Update
Dart team released a helper package that helps achieving this. String.characters.skipLast(1) should be able to do what you expect.
Old answer
First, let's get to some definitions. According to this page:
Bytes: 8-bit. The number of bytes that a Unicode string will take up in memory or storage depends on the encoding.
Code Units: The smallest bit combination that can be used to express a single unit in text encoding. For example 1 code unit in UTF-8 would be 1 byte, 2 bytes in UTF-16, 4 bytes in UTF-32.
Code Points [or rune]: Unicode character. A single integer value (from U+0000-U+10FFFF) on a Unicode space.
Grapheme clusters: A single character perceived by the user. 1 grapheme cluster consists of several code points.
When you remove the last char using substring, you're actually removing the last code unit. If you run print(newStr.codeUnits) and print(str.codeUnits), you'll see that the rune 128512 is equivalent to the joint of the code units 55357 and 56832, so 55357 is actually valid, but doesn't represent anything without the "help" of another code unit.
In fact, you don't want to use substring() when there's non-ASCII chars in your String (like emojis or arabic letters). It'll never work. What you have to do is remove the last grapheme cluster. Something as simple as that:
str.graphemeClusters.removeLast()
However, Dart doesn't support this yet. There are several issues around this point. Some of those:
https://github.com/dart-lang/language/issues/34
https://github.com/dart-lang/language/issues/49
This lack of support seams to result in some other of issues, like the one you mentioned and this one:
https://github.com/flutter/flutter/issues/31818
String formatText(String str) {
final RegExp regExp = RegExp(r'(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])');
if(str.contains(regExp)){
str = str.replaceAll(regExp,'');
}
return str; }
Ex: Go to https://dartpad.dev/ to test:
String str = "ThaiKV受け行くけどよね😞😞😍😰😒😜" => ThaiKV受け行くけどよね
This answer still has problem
Since dart does not provide the data type 'Grapheme Cluster', I try to use method channel to do this using kotlin:
Step 1: Create a new 'Flutter Plugin' project, name the project 'gmc01', 2 files will be created automatically, namely 'gmc01.dart' and 'main.dart'.
Step 2: replace the codes in gmc01.dart with the following:
import 'dart:async';
import 'package:flutter/services.dart';
class Gmc01 {
static const MethodChannel _channel =
const MethodChannel('gmc01');
static Future<String> removeLastGMC(String strOriginal) async {
final String version = await _channel.invokeMethod('removeLastGMC', strOriginal);
return version;
}
}
Step 3: Replace the codes in main.dart with the following:
import 'package:gmc01/gmc01.dart';
void main() async {
String strTemp = '12345678我们5🇵🇬你😀👨‍👩‍👦';
strTemp = await Gmc01.removeLastGMC(strTemp);
print(strTemp);
strTemp = await Gmc01.removeLastGMC(strTemp);
print(strTemp);
strTemp = await Gmc01.removeLastGMC(strTemp);
print(strTemp);
strTemp = await Gmc01.removeLastGMC(strTemp);
print(strTemp);
strTemp = await Gmc01.removeLastGMC(strTemp);
print(strTemp);
strTemp = await Gmc01.removeLastGMC(strTemp);
print(strTemp);
strTemp = await Gmc01.removeLastGMC(strTemp);
print(strTemp);
}
Step 4: Inside android/build.gradle, change the minSdkVersion from 16 to 24.
Step 5: Inside example/android/app/build.gradle, change the minSdkVersion from 16 to 24.
Step 6: Click File->Open, select gmc01->android, then click 'OK', the kotlin part of the plugin will be opened (In another Window).
Step 7: Replace the codes in Gmc01Plugin.kt with the following: (Replace the first line with your own package name)
package com.example.gmc01 // replace the left with your own package name
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
import android.icu.text.BreakIterator
class Gmc01Plugin: MethodCallHandler {
companion object {
#JvmStatic
fun registerWith(registrar: Registrar) {
val channel = MethodChannel(registrar.messenger(), gmc01)
channel.setMethodCallHandler(Gmc01Plugin())
}
}
override fun onMethodCall(call: MethodCall, result: Result) {
var strArg: String
strArg = call.arguments.toString()
var boundary = BreakIterator.getWordInstance()
boundary.setText(strArg);
when (call.method) {
removeLastGMC -> {
result.success(removeLastGMC(boundary, strArg))
}
else -> {
result.notImplemented()
}
}
}
fun removeLastGMC(boundary: BreakIterator, source: String):String {
val end = boundary.last()
val start = boundary.previous()
return source.substring(0, start)
}
}
Step 8: Go back to the window of the plugin, and click 'Run'.
Here are the output in the console:
I/flutter (22855): 12345678我们5🇵🇬你😀
I/flutter (22855): 12345678我们5🇵🇬你
I/flutter (22855): 12345678我们5🇵🇬
I/flutter (22855): 12345678我们5
I/flutter (22855): 12345678我们
I/flutter (22855): 12345678
I/flutter (22855):
As you can see, the 'Family Emoji', 'Face Emoji' and 'Country Flag' emoji are removed correctly, but the Chinese 2 chars '我们' and the digits '12345678' are removed by using a single removeLastGMC, so still need to figure out how to distinguish Chinese Double Bytes characters / English Chars / Emojis.
BTW, I don't know how to do the Swift part, can someone help?
Its a bit unclear to what you want to check. I suggest you remove the -1 from the substring because it will break the emoji's code snip
void main() {
var str = "abc😀";
var newStr = str.substring(0, str.length); // i removed it here
print(newStr);
print(newStr.runes);
print(str.runes);
}
This will give the output of
abc😀
(97, 98, 99, 128512)
(97, 98, 99, 128512)
Tested in https://dartpad.dartlang.org/
The code is not working
The code is not working properly. I just put here for reference.
Trial 1
Problem: can not handle 🇵🇬 and 👨‍👩‍👦 properly.
String myBackspace(String str) {
Runes strRunes = str.runes;
str = String.fromCharCodes(strRunes, 0, strRunes.length - 1);
print(str);
return str;
}
Trial 2
Problem: can not handle connected emoji sequence 😀😀 and 👨‍👩‍👦 properly.
Based on the link
String myBackspace(String str) {
int i = 0;
while (str.length > 0) {
i++;
int removedCharCode = str.codeUnitAt(str.length - 1);
if (isWellFormattedUTF16(removedCharCode)) break;
str = str.substring(0, str.length - 1);
}
if (i == 1) str = str.substring(0, str.length - 1);
print(str);
return str;
}
bool isWellFormattedUTF16(int charCode) {
int surrogateLeadingStart = 0xD800;
int surrogateLeadingEnd = 0xDBFF;
int surrogateTrailingStart = 0xDC00;
int surrogateTrailingEnd = 0xDFFF;
if (!(charCode >= surrogateLeadingStart && charCode <= surrogateLeadingEnd) &&
!(charCode >= surrogateTrailingStart && charCode <= surrogateTrailingEnd)) return true;
return false;
}
if someone need simple solution to remove emojies from string try this.
String str = "hello🇵🇬你们😀😀👨‍👩‍👦"İ
final RegExp REGEX_EMOJI = RegExp(r'(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])');
if(str.contains(REGEX_EMOJI)){
str = str.replaceAll(REGEX_EMOJI,'');
}
With RegExp and replaceAll:
final regex = RegExp(
"(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-
\udfff]|\ud83e[\ud000-\udfff])");
final textReplace = String.replaceAll(regex, '');
You can do a method like this one
bool isValid(String prevString, String newString){
if (prevString == newString)
return true;
else return false;
}
then in your keyboard you validate with an onChange property
TextField(
onChanged: (text) {
isValid(varYouHad ,text); //validate
},
);

How to pipe to a process using vala/glib

I'm trying to pipe output from echo into a command using GLib's spawn_command_line_sync method. The problem I've run into is echo is interpreting the entire command as the argument.
To better explain, I run this in my code:
string command = "echo \"" + some_var + "\" | command";
Process.spawn_command_line_sync (command.escape (),
out r, out e, out s);
I would expect the variable to be echoed to the pipe and the command run with the data piped, however when I check on the result it's just echoing everything after echo like this:
"some_var's value" | command
I think I could just use the Posix class to run the command but I like having the result, error and status values to listen to that the spawn_command_line_sync method provides.
The problem is that you are providing shell syntax to what is essentially the kernel’s exec() syscall. The shell pipe operator redirects the stdout of one process to the stdin of the next. To implement that using Vala, you need to get the file descriptor for the stdin of the command process which you’re running, and write some_var to it manually.
You are combining two subprocesses into one. Instead echo and command should be treated separately and have a pipe set up between them. For some reason many examples on Stack Overflow and other sites use the Process.spawn_* functions, but using GSubprocess is an easier syntax.
This example pipes the output of find . to sort and then prints the output to the console. The example is a bit longer because it is a fully working example and makes use of a GMainContext for asynchronous calls. GMainContext is used by GMainLoop, GApplication and GtkApplication:
void main () {
var mainloop = new MainLoop ();
SourceFunc quit = ()=> {
mainloop.quit ();
return Source.REMOVE;
};
read_piped_commands.begin ("find .", "sort", quit);
mainloop.run ();
}
async void read_piped_commands (string first_command, string second_command, SourceFunc quit) {
var output = splice_subprocesses (first_command, second_command);
try {
string? line = null;
do {
line = yield output.read_line_async ();
print (#"$(line ?? "")\n");
}
while (line != null);
} catch (Error error) {
print (#"Error: $(error.message)\n");
}
quit ();
}
DataInputStream splice_subprocesses (string first_command, string second_command) {
InputStream end_pipe = null;
try {
var first = new Subprocess.newv (first_command.split (" "), STDOUT_PIPE);
var second = new Subprocess.newv (second_command.split (" "), STDIN_PIPE | STDOUT_PIPE);
second.get_stdin_pipe ().splice (first.get_stdout_pipe (), CLOSE_TARGET);
end_pipe = second.get_stdout_pipe ();
} catch (Error error) {
print (#"Error: $(error.message)\n");
}
return new DataInputStream (end_pipe);
}
It is the splice_subprocesses function that answers your question. It takes the STDOUT from the first command as an InputStream and splices it with the OutputStream (STDIN) for the second command.
The read_piped_commands function takes the output from the end of the pipe. This is an InputStream that has been wrapped in a DataInputStream to give access to the read_line_async convenience method.
Here's the full, working implementation:
try {
string[] command = {"command", "-options", "-etc"};
string[] env = Environ.get ();
Pid child_pid;
string some_string = "This is what gets piped to stdin"
int stdin;
int stdout;
int stderr;
Process.spawn_async_with_pipes ("/",
command,
env,
SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD,
null,
out child_pid,
out stdin,
out stdout,
out stderr);
FileStream input = FileStream.fdopen (stdin, "w");
input.write (some_string.data);
/* Make sure we close the process using it's pid */
ChildWatch.add (child_pid, (pid, status) => {
Process.close_pid (pid);
});
} catch (SpawnError e) {
/* Do something w the Error */
}
I guess playing with the FileStream is what really made it hard to figure this out. Turned out to be pretty straightforward.
Based on previous answers probably an interesting case is to use program arguments to have a general app to pipe any input on it:
pipe.vala:
void main (string[] args) {
try {
string command = args[1];
var subproc = new Subprocess(STDIN_PIPE | STDOUT_PIPE, command);
var data = args[2].data;
var input = new MemoryInputStream.from_data(data, GLib.free);
subproc.get_stdin_pipe ().splice (input, CLOSE_TARGET);
var end_pipe = subproc.get_stdout_pipe ();
var output = new DataInputStream (end_pipe);
string? line = null;
do {
line = output.read_line();
print (#"$(line ?? "")\n");
} while (line != null);
} catch (Error error) {
print (#"Error: $(error.message)\n");
}
}
build:
$ valac --pkg gio-2.0 pipe.vala
and run:
$ ./pipe sort "cc
ab
aa
b
"
Output:
aa
ab
b
cc

Print barcodes from web page to Zebra printer

We're trying to print barcodes from a web page to our Zebra printer.
I'm wondering if there's a way to print them using the printer's own font perhaps using web fonts or if I knew the font name used?
I have been trying to use php barcode generators, that basically generates images containing the barcode. I have in fact been trying this approach for a few days already, without success.
The problem is when I print them it's not readable by the scanners. I have tried to change the image resolution to match that of the printer (203dpi), also tried playing with the image size and formats, but the barcodes after printed still can't be scanned.
So does anybody have experience with this?
Printer: Zebra TLP 2844
Barcodes required per page:
01 Code39 horizontal (scanable only if printed at very specific size and browser)
01 Code128 vertical (still can't get it to work, print is always very blurry and won't get scanned)
===========
I've made a little bit of progress, I found out this printer supports EPL2 language, so I'm trying to use it to print out the barcodes.
First I needed to enable pass through mode, I did that on Printer Options > Advanced Setup > Miscellaneous.
Now I'm able to print barcodes impeccably using the printer's built-in font :D using this command:
ZPL:
B10,10,0,1,2,2,60,N,"TEXT-GOES-HERE"
:ZPL
But I can only print it from Notepad, I'm still unable to print this from a browser... It's probably a problem with LF being replaced with CR+LF...
How to overcome this problem??
===========
The label I'm trying to print actually has a bit of text before the barcode, with some html tables formatting it nicely. So I need to print this first, and in the middle I need to stick in a nice label and then add some more text.
So I can't use pure EPL2 to print the whole thing, I'm wondering if I can use some of both html + EPL2 + html to achieve my goal or is that not allowed?? =/
You are running into a few obstacles:
1) When you print through the OS installed printer driver, the printer driver is trying to take the data that is sent to it and (re)rasterize or scale it for the output device (the Zebra printer). Since the printer is a relatively low resolution at 203dpi, then it does not take too much for the scaling the print driver is having to do for it to loose some integrity in the quality of the barcode. This is why barcodes generated using the direct ZPL commands are much more reliable.
2) Due to the security that web browsers purposefully provide by not allowing access to the client computer, you cannot directly communicate with the client connected printer. This sandboxing is what helps to protect users from malware so that nefarious websites cannot do things like write files to the client machine or send output directly to devices such as printers. So you are not able to directly send the ZPL commands through the browser to the client connected printer.
However, there is a way to do what you describe. The steps necessary are typically only going to be useful if you have some degree of control over the client computer accessing the site that is trying to print to the Zebra printers. For example this is only going to be used by machines on your company network, or by clients who are willing to install a small application that you need to write. To do this, you will need to look at the following steps:
A) You need to make up your own custom MIME type. This is basically just any name you want to use that is not going to collide with any registered MIME types.
B) Next you will define a filename extension that will map to your custom MIME type. To do this, you typically will need to configure your web server (steps for this depend on what web server you are using) to allow the new MIME type you want to define and what file extension is used for these types of files.
C) Then on your web application, when you want to output the ZPL data, you write it to a file using a filename extension that is mapped to your new MIME type. Then once the file is generated, you can either provide an HTML link to it, or redirect the client browser to the file. You can test if your file is working correctly at this point by manually copying the file you created directly to the raw printer port.
D) Next you need to write a small application which can be installed on the client. When the application is installed, you need to have it register itself as a valid consuming application for your custom MIME type. If a browser detects that there is an installed application for a file of the specified MIME type, it simply writes the file to a temporary directory on the client machine and then attempts to launch the application of the same registered MIME type with the temporary file as a parameter to the application. Thus your application now just reads the file that the browser passed to it and then it attempts to dump it directly to the printer.
This is an overview of what you need to do in order to accomplish what you are describing. Some of the specific steps will depend on what type of web server you are using and what OS your clients machines are. But this is the high level overview that will let you accomplish what you are attempting.
If you'd consider loading a java applet, qz-print (previously jzebra) can do exactly what you are describing and works nicely with the LP2844 mentioned in the comments.
https://code.google.com/p/jzebra/
What we did for our web app :
1) Download the free printfile app http://www.lerup.com/printfile/
"PrintFile is a freeware MS Windows utility program that will enable you to print files fast and easily. The program recognizes plain text, PostScript, Encapsulated PostScript (EPS) and binary formats. Using this program can save you a lot of paper and thereby also saving valuable natural resources."
When you first run PrintFile, go into the advanced options and enable "send to printer directly".
2) Setup the ZEBRA printer in windows as a Generic Text Printer.
2) Generate a file.prt file in the web app which is just a plain text EPL file.
3) Double clicking on the downloaded file will instantly print the barcode. Works like a charm. You can even setup PrintFile so that you don't even see a gui.
I am using QZ Tray to print labels from a web page to Zebra thermal printer.
In the demo/js folder of QZ Tray there are three JavaScript files that are required to communicate with QZ Tray application - dependencies/rsvp-3.1.0.min.js, dependencies/sha-256.min.js and qz-tray.js.
Include these JavaScript files in your project as follows:
<script type="text/javascript" src="/lib/qz-tray/rsvp-3.1.0.min.js"></script>
<script type="text/javascript" src="/lib/qz-tray/sha-256.min.js"></script>
<script type="text/javascript" src="/lib/qz-tray/qz-tray.js"></script>
The most simple way to print a label to Zebra thermal printer is shown below.
<script type="text/javascript">
qz.websocket.connect().then(function() {
// Pass the printer name into the next Promise
return qz.printers.find("zebra");
}).then(function(printer) {
// Create a default config for the found printer
var config = qz.configs.create(printer);
// Raw ZPL
var data = ['^XA^FO50,50^ADN,36,20^FDRAW ZPL EXAMPLE^FS^XZ'];
return qz.print(config, data);
}).catch(function(e) { console.error(e); });
</script>
See How to print labels from a web page to Zebra thermal printer for more information.
You can also send the ZPL commands in a text file (you can pack multiple labels in a single file) and have the user open and print the file via windows notepad. The only caveat is that they have to remove the default header and footer (File --> Page Setup).
Its a bit of user training, but may be acceptable if you don't have control over the client machines.
I'm developing something similar here.
I need to print in a LP2844 from my webapp. The problem is that my webapp is in a remote server in the cloud (Amazon EC2) and the printer is going to be in a warehouse desk.
My solution:
The webapp generates the EPL2 code for the label with the barcodes, then publish a PubNub message.
I wrote a little C# program that runs in the computer where the printer is connected. The program receives the message and then send the code to the printer.
I followed the idea proposed by "Tres Finocchiaro" on my application based on:
ASP.NET 4.0
IIS
Chrome, IExplorer, Firefox
Zebra TLP 2844
EPL protocolo
Unfortunatly the jzebra needs some improvements to work corectly due to the issues of security of current browser.
Installing jzebra
Downlod jzebdra and from dist directory I copy into your directory (eg. mydir):
web
mydir
js
..
deployJava.js
lib
..
qz-print.jar
qz-print_jnlp.jnlp
Create your print.html
<html>
<script type="text/javascript" src="js/deployJava.js"></script>
<script type="text/javascript">
/**
* Optionally used to deploy multiple versions of the applet for mixed
* environments. Oracle uses document.write(), which puts the applet at the
* top of the page, bumping all HTML content down.
*/
deployQZ();
/** NEW FUNCTION **/
function initPrinter() {
findPrinters();
useDefaultPrinter();
}
/** NEW FUNCTION **/
function myalert(txt) {
alert(txt);
}
/**
* Deploys different versions of the applet depending on Java version.
* Useful for removing warning dialogs for Java 6. This function is optional
* however, if used, should replace the <applet> method. Needed to address
* MANIFEST.MF TrustedLibrary=true discrepency between JRE6 and JRE7.
*/
function deployQZ() {
var attributes = {id: "qz", code:'qz.PrintApplet.class',
archive:'qz-print.jar', width:1, height:1};
var parameters = {jnlp_href: 'qz-print_jnlp.jnlp',
cache_option:'plugin', disable_logging:'false',
initial_focus:'false'};
if (deployJava.versionCheck("1.7+") == true) {}
else if (deployJava.versionCheck("1.6+") == true) {
delete parameters['jnlp_href'];
}
deployJava.runApplet(attributes, parameters, '1.5');
}
/**
* Automatically gets called when applet has loaded.
*/
function qzReady() {
// Setup our global qz object
window["qz"] = document.getElementById('qz');
var title = document.getElementById("title");
if (qz) {
try {
title.innerHTML = title.innerHTML + " " + qz.getVersion();
document.getElementById("content").style.background = "#F0F0F0";
} catch(err) { // LiveConnect error, display a detailed meesage
document.getElementById("content").style.background = "#F5A9A9";
alert("ERROR: \nThe applet did not load correctly. Communication to the " +
"applet has failed, likely caused by Java Security Settings. \n\n" +
"CAUSE: \nJava 7 update 25 and higher block LiveConnect calls " +
"once Oracle has marked that version as outdated, which " +
"is likely the cause. \n\nSOLUTION: \n 1. Update Java to the latest " +
"Java version \n (or)\n 2. Lower the security " +
"settings from the Java Control Panel.");
}
}
}
/**
* Returns whether or not the applet is not ready to print.
* Displays an alert if not ready.
*/
function notReady() {
// If applet is not loaded, display an error
if (!isLoaded()) {
return true;
}
// If a printer hasn't been selected, display a message.
else if (!qz.getPrinter()) {
/** CALL TO NEW FUNCTION **/
initPrinter();
return false;
}
return false;
}
/**
* Returns is the applet is not loaded properly
*/
function isLoaded() {
if (!qz) {
alert('Error:\n\n\tPrint plugin is NOT loaded!');
return false;
} else {
try {
if (!qz.isActive()) {
alert('Error:\n\n\tPrint plugin is loaded but NOT active!');
return false;
}
} catch (err) {
alert('Error:\n\n\tPrint plugin is NOT loaded properly!');
return false;
}
}
return true;
}
/**
* Automatically gets called when "qz.print()" is finished.
*/
function qzDonePrinting() {
// Alert error, if any
if (qz.getException()) {
alert('Error printing:\n\n\t' + qz.getException().getLocalizedMessage());
qz.clearException();
return;
}
// Alert success message
alert('Successfully sent print data to "' + qz.getPrinter() + '" queue.');
}
/***************************************************************************
* Prototype function for finding the "default printer" on the system
* Usage:
* qz.findPrinter();
* window['qzDoneFinding'] = function() { alert(qz.getPrinter()); };
***************************************************************************/
function useDefaultPrinter() {
if (isLoaded()) {
// Searches for default printer
qz.findPrinter();
// Automatically gets called when "qz.findPrinter()" is finished.
window['qzDoneFinding'] = function() {
// Alert the printer name to user
var printer = qz.getPrinter();
myalert(printer !== null ? 'Default printer found: "' + printer + '"':
'Default printer ' + 'not found');
// Remove reference to this function
window['qzDoneFinding'] = null;
};
}
}
/***************************************************************************
* Prototype function for finding the closest match to a printer name.
* Usage:
* qz.findPrinter('zebra');
* window['qzDoneFinding'] = function() { alert(qz.getPrinter()); };
***************************************************************************/
function findPrinter(name) {
// Get printer name from input box
var p = document.getElementById('printer');
if (name) {
p.value = name;
}
if (isLoaded()) {
// Searches for locally installed printer with specified name
qz.findPrinter(p.value);
// Automatically gets called when "qz.findPrinter()" is finished.
window['qzDoneFinding'] = function() {
var p = document.getElementById('printer');
var printer = qz.getPrinter();
// Alert the printer name to user
alert(printer !== null ? 'Printer found: "' + printer +
'" after searching for "' + p.value + '"' : 'Printer "' +
p.value + '" not found.');
// Remove reference to this function
window['qzDoneFinding'] = null;
};
}
}
/***************************************************************************
* Prototype function for listing all printers attached to the system
* Usage:
* qz.findPrinter('\\{dummy_text\\}');
* window['qzDoneFinding'] = function() { alert(qz.getPrinters()); };
***************************************************************************/
function findPrinters() {
if (isLoaded()) {
// Searches for a locally installed printer with a bogus name
qz.findPrinter('\\{bogus_printer\\}');
// Automatically gets called when "qz.findPrinter()" is finished.
window['qzDoneFinding'] = function() {
// Get the CSV listing of attached printers
var printers = qz.getPrinters().split(',');
for (i in printers) {
myalert(printers[i] ? printers[i] : 'Unknown');
}
// Remove reference to this function
window['qzDoneFinding'] = null;
};
}
}
/***************************************************************************
* Prototype function for printing raw EPL commands
* Usage:
* qz.append('\nN\nA50,50,0,5,1,1,N,"Hello World!"\n');
* qz.print();
***************************************************************************/
function print() {
if (notReady()) { return; }
// Send characters/raw commands to qz using "append"
// This example is for EPL. Please adapt to your printer language
// Hint: Carriage Return = \r, New Line = \n, Escape Double Quotes= \"
qz.append('\nN\n');
qz.append('q609\n');
qz.append('Q203,26\n');
qz.append('B5,26,0,1A,3,7,152,B,"1234"\n');
qz.append('A310,26,0,3,1,1,N,"SKU 00000 MFG 0000"\n');
qz.append('A310,56,0,3,1,1,N,"QZ PRINT APPLET"\n');
qz.append('A310,86,0,3,1,1,N,"TEST PRINT SUCCESSFUL"\n');
qz.append('A310,116,0,3,1,1,N,"FROM SAMPLE.HTML"\n');
qz.append('A310,146,0,3,1,1,N,"QZINDUSTRIES.COM"');
// Append the rest of our commands
qz.append('\nP1,1\n');
// Tell the applet to print.
qz.print();
}
/***************************************************************************
* Prototype function for logging a PostScript printer's capabilites to the
* java console to expose potentially new applet features/enhancements.
* Warning, this has been known to trigger some PC firewalls
* when it scans ports for certain printer capabilities.
* Usage: (identical to appendImage(), but uses html2canvas for png rendering)
* qz.setLogPostScriptFeatures(true);
* qz.appendHTML("<h1>Hello world!</h1>");
* qz.printPS();
***************************************************************************/
function logFeatures() {
if (isLoaded()) {
var logging = qz.getLogPostScriptFeatures();
qz.setLogPostScriptFeatures(!logging);
alert('Logging of PostScript printer capabilities to console set to "' + !logging + '"');
}
}
/***************************************************************************
****************************************************************************
* * HELPER FUNCTIONS **
****************************************************************************
***************************************************************************/
function getPath() {
var path = window.location.href;
return path.substring(0, path.lastIndexOf("/")) + "/";
}
/**
* Fixes some html formatting for printing. Only use on text, not on tags!
* Very important!
* 1. HTML ignores white spaces, this fixes that
* 2. The right quotation mark breaks PostScript print formatting
* 3. The hyphen/dash autoflows and breaks formatting
*/
function fixHTML(html) {
return html.replace(/ /g, " ").replace(/’/g, "'").replace(/-/g,"‑");
}
/**
* Equivelant of VisualBasic CHR() function
*/
function chr(i) {
return String.fromCharCode(i);
}
/***************************************************************************
* Prototype function for allowing the applet to run multiple instances.
* IE and Firefox may benefit from this setting if using heavy AJAX to
* rewrite the page. Use with care;
* Usage:
* qz.allowMultipleInstances(true);
***************************************************************************/
function allowMultiple() {
if (isLoaded()) {
var multiple = qz.getAllowMultipleInstances();
qz.allowMultipleInstances(!multiple);
alert('Allowing of multiple applet instances set to "' + !multiple + '"');
}
}
</script>
<input type="button" onClick="print()" />
</body>
</html>
the code provided is based on "jzebra_installation/dist/sample.html".
try creating a websocket that controls the print on the client side and send data with ajax from the page to localhost.
/// websocket
using System;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
namespace Server
{
class Program
{
public static WebsocketServer ws;
static void Main(string[] args)
{
ws = new Server.WebsocketServer();
ws.LogMessage += Ws_LogMessage;
ws.Start("http://localhost:2645/service/");
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
private static void Ws_LogMessage(object sender, WebsocketServer.LogMessageEventArgs e)
{
Console.WriteLine(e.Message);
}
}
public class WebsocketServer
{
public event OnLogMessage LogMessage;
public delegate void OnLogMessage(Object sender, LogMessageEventArgs e);
public class LogMessageEventArgs : EventArgs
{
public string Message { get; set; }
public LogMessageEventArgs(string Message)
{
this.Message = Message;
}
}
public bool started = false;
public async void Start(string httpListenerPrefix)
{
HttpListener httpListener = new HttpListener();
httpListener.Prefixes.Add(httpListenerPrefix);
httpListener.Start();
LogMessage(this, new LogMessageEventArgs("Listening..."));
started = true;
while (started)
{
HttpListenerContext httpListenerContext = await httpListener.GetContextAsync();
if (httpListenerContext.Request.IsWebSocketRequest)
{
ProcessRequest(httpListenerContext);
}
else
{
httpListenerContext.Response.StatusCode = 400;
httpListenerContext.Response.Close();
LogMessage(this, new LogMessageEventArgs("Closed..."));
}
}
}
public void Stop()
{
started = false;
}
private async void ProcessRequest(HttpListenerContext httpListenerContext)
{
WebSocketContext webSocketContext = null;
try
{
webSocketContext = await httpListenerContext.AcceptWebSocketAsync(subProtocol: null);
LogMessage(this, new LogMessageEventArgs("Connected"));
}
catch (Exception e)
{
httpListenerContext.Response.StatusCode = 500;
httpListenerContext.Response.Close();
LogMessage(this, new LogMessageEventArgs(String.Format("Exception: {0}", e)));
return;
}
WebSocket webSocket = webSocketContext.WebSocket;
try
{
while (webSocket.State == WebSocketState.Open)
{
ArraySegment<Byte> buffer = new ArraySegment<byte>(new Byte[8192]);
WebSocketReceiveResult result = null;
using (var ms = new System.IO.MemoryStream())
{
do
{
result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
ms.Write(buffer.Array, buffer.Offset, result.Count);
}
while (!result.EndOfMessage);
ms.Seek(0, System.IO.SeekOrigin.Begin);
if (result.MessageType == WebSocketMessageType.Text)
{
using (var reader = new System.IO.StreamReader(ms, Encoding.UTF8))
{
var r = System.Text.Encoding.UTF8.GetString(ms.ToArray());
var t = Newtonsoft.Json.JsonConvert.DeserializeObject<Datos>(r);
bool valid = true;
byte[] toBytes = Encoding.UTF8.GetBytes(""); ;
if (t != null)
{
if (t.printer.Trim() == string.Empty)
{
var printers = "";
foreach (var imp in System.Drawing.Printing.PrinterSettings.InstalledPrinters)
{
printers += imp + "\n";
}
toBytes = Encoding.UTF8.GetBytes("No se Indicó la Impresora\nLas Impresoras disponibles son: " + printers);
valid = false;
}
if (t.name.Trim() == string.Empty)
{
toBytes = Encoding.UTF8.GetBytes("No se Indicó el nombre del Documento");
valid = false;
}
if (t.code == null)
{
toBytes = Encoding.UTF8.GetBytes("No hay datos para enviar a la Impresora");
valid = false;
}
if (valid)
{
print.RawPrinter.SendStringToPrinter(t.printer, t.code, t.name);
toBytes = Encoding.UTF8.GetBytes("Correcto...");
}
await webSocket.SendAsync(new ArraySegment<byte>(toBytes, 0, int.Parse(toBytes.Length.ToString())), WebSocketMessageType.Binary, result.EndOfMessage, CancellationToken.None);
}
else
{
toBytes = Encoding.UTF8.GetBytes("Error...");
await webSocket.SendAsync(new ArraySegment<byte>(toBytes, 0, int.Parse(toBytes.Length.ToString())), WebSocketMessageType.Binary, result.EndOfMessage, CancellationToken.None);
}
}
}
}
}
}
catch (Exception e)
{
LogMessage(this, new LogMessageEventArgs(String.Format("Exception: {0} \nLinea:{1}", e, e.StackTrace)));
}
finally
{
if (webSocket != null)
webSocket.Dispose();
}
}
}
public class Datos
{
public string name { get; set; }
public string code { get; set; }
public string printer { get; set; } = "";
}
}
raw Print:
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.IO;
namespace print
{
public class RawPrinter
{
// Structure and API declarions:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class DOCINFOA
{
[MarshalAs(UnmanagedType.LPStr)]
public string pDocName;
[MarshalAs(UnmanagedType.LPStr)]
public string pOutputFile;
[MarshalAs(UnmanagedType.LPStr)]
public string pDataType;
}
[DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)]
string szPrinter, ref IntPtr hPriknter, IntPtr pd);
[DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool ClosePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In(), MarshalAs(UnmanagedType.LPStruct)]
DOCINFOA di);
[DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndDocPrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, ref Int32 dwWritten);
// SendBytesToPrinter()
// When the function is given a printer name and an unmanaged array
// of bytes, the function sends those bytes to the print queue.
// Returns true on success, false on failure.
public static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, Int32 dwCount, string DocName = "")
{
Int32 dwError = 0;
Int32 dwWritten = 0;
IntPtr hPrinter = new IntPtr(0);
DOCINFOA di = new DOCINFOA();
bool bSuccess = false;
// Assume failure unless you specifically succeed.
di.pDocName = string.IsNullOrEmpty(DocName) ? "My C#.NET RAW Document" : DocName;
di.pDataType = "RAW";
// Open the printer.
if (OpenPrinter(szPrinterName.Normalize(), ref hPrinter, IntPtr.Zero))
{
// Start a document.
if (StartDocPrinter(hPrinter, 1, di))
{
// Start a page.
if (StartPagePrinter(hPrinter))
{
// Write your bytes.
bSuccess = WritePrinter(hPrinter, pBytes, dwCount, ref dwWritten);
EndPagePrinter(hPrinter);
}
EndDocPrinter(hPrinter);
}
ClosePrinter(hPrinter);
}
// If you did not succeed, GetLastError may give more information
// about why not.
if (bSuccess == false)
{
dwError = Marshal.GetLastWin32Error();
}
return bSuccess;
}
public static bool SendFileToPrinter(string szPrinterName, string szFileName)
{
// Open the file.
FileStream fs = new FileStream(szFileName, FileMode.Open);
// Create a BinaryReader on the file.
BinaryReader br = new BinaryReader(fs);
// Dim an array of bytes big enough to hold the file's contents.
Byte[] bytes = new Byte[fs.Length];
bool bSuccess = false;
// Your unmanaged pointer.
IntPtr pUnmanagedBytes = new IntPtr(0);
int nLength = 0;
nLength = Convert.ToInt32(fs.Length);
// Read the contents of the file into the array.
bytes = br.ReadBytes(nLength);
// Allocate some unmanaged memory for those bytes.
pUnmanagedBytes = Marshal.AllocCoTaskMem(nLength);
// Copy the managed byte array into the unmanaged array.
Marshal.Copy(bytes, 0, pUnmanagedBytes, nLength);
// Send the unmanaged bytes to the printer.
bSuccess = SendBytesToPrinter(szPrinterName, pUnmanagedBytes, nLength);
// Free the unmanaged memory that you allocated earlier.
Marshal.FreeCoTaskMem(pUnmanagedBytes);
return bSuccess;
}
public static bool SendStringToPrinter(string szPrinterName, string szString, string DocName = "")
{
IntPtr pBytes = default(IntPtr);
Int32 dwCount = default(Int32);
// How many characters are in the string?
dwCount = szString.Length;
// Assume that the printer is expecting ANSI text, and then convert
// the string to ANSI text.
pBytes = Marshal.StringToCoTaskMemAnsi(szString);
// Send the converted ANSI string to the printer.
SendBytesToPrinter(szPrinterName, pBytes, dwCount, DocName);
Marshal.FreeCoTaskMem(pBytes);
return true;
}
}
}
html page:
<!DOCTYPE html>
<html>
<head>
</head>
<body ng-app="myapp">
<div ng-controller="try as ctl">
<input ng-model="ctl.ticket.nombre">
<textarea ng-model="ctl.ticket.code"></textarea>
<button ng-click="ctl.send()">Enviar</button>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script>
var ws = new WebSocket("ws://localhost:2645/service");
ws.binaryType = "arraybuffer";
ws.onopen = function () {
console.log('connection is opened!!!');
};
ws.onmessage = function (evt) {
console.log(arrayBufferToString(evt.data))
};
ws.onclose = function () {
console.log("Connection is Closed...")
};
function arrayBufferToString(buffer) {
var arr = new Uint8Array(buffer);
var str = String.fromCharCode.apply(String, arr);
return decodeURIComponent(escape(str));
}
var app = angular.module('myapp', []);
app.controller('try', function () {
this.ticket= {nombre:'', estado:''}
this.send = () => {
var toSend= JSON.stringify(this.ticket);
ws.send(toSend);
}
});
</script>
</body>
</html>
then send a ZPL code from html(write this on textarea code);
^XA
^FO200,50^BY2^B3N,N,80,Y,N^FD0123456789^FS
^PQ1^XZ

Resources