diff --git a/.dockerignore b/.dockerignore index eaf6ab3..d69f7c6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -29,6 +29,13 @@ web/tmp/** __pycache__/** __pycache__ -# Remove test scripts +# Remove test scripts and test files tests/** -tests \ No newline at end of file +tests +barcodes.bin +receipt-with-logo.bin +receipt-with-qrcode.bin + +# Remove pybabel config and translation scripts from the runtime container +babel.cfg +*_babel_*.sh \ No newline at end of file diff --git a/README.md b/README.md index 6179e6e..218f465 100644 --- a/README.md +++ b/README.md @@ -10,35 +10,119 @@ The printer emulates a 80mm roll of paper. ## Limits This docker image is not to be exposed on a public network (see [known issues](#known-issues)) -A print cannot last longer than 10 seconds. This timeout could be changed at some point, or made configurable. +A print cannot last longer than 10 seconds. This timeout could be changed in the code, or made configurable at some later point. ## Quick start This project requires: - A Docker installation (kubernetes should work, but is untested.) -To install v3.0 from source: +### Use the prebuilt container +A prebuilt container of this project is avaliable on [Docker Hub](https://hub.docker.com/repository/docker/gilbertfl/escpos-netprinter). + +To run the prebuilt container: ```bash -wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs/tags/3.0.zip -unzip 3.0.zip -cd escpos-netprinter-3.0 -docker build -t escpos-netprinter:3.0 . +docker run -d \ + -p 515:515/tcp \ + -p 80:80/tcp \ + -p 9100:9100/tcp \ + --mount source=receiptVolume,target=/home/escpos-emu/web \ + gilbertfl/escpos-netprinter:3.2 +``` +### Once started +Once started, the container will accept prints by JetDirect on the default port(9100) and by lpd on the default port(515). You can access all received receipts with the web application at port 80. + +The receipts are kept on a docker volume, so they will be kept if the container is restarted. To make the prints temporary, simply remove the `--mount` line from the run command. + +Version 3.2 is capable of dealing with all status requests from POS systems as described in the Epson APG. + +## Working on the code + +### Building from source + +To install v3.2 from source: + +```bash +wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs/tags/3.2.zip +unzip 3.2.zip +cd escpos-netprinter-3.2 +docker build -t escpos-netprinter:3.2 . ``` To run the resulting container: ```bash -docker run -d --rm --name escpos_netprinter -p 515:515/tcp -p 80:80/tcp -p 9100:9100/tcp escpos-netprinter:3.0 +docker run -d \ + -p 515:515/tcp \ + -p 80:80/tcp \ + -p 9100:9100/tcp \ + --mount source=receiptVolume,target=/home/escpos-emu/web \ + escpos-netprinter:3.2 +``` + +### Debugging +Two interfaces have been made avaliable to help debugging. + +There is also a general environment flag that make the Docker logs verbose: set ```ESCPOS_DEBUG=True``` (case-sensitive) +```bash +docker run -d \ + -p 515:515/tcp \ + -p 80:80/tcp \ + -p 9100:9100/tcp \ + --mount source=receiptVolume,target=/home/escpos-emu/web \ + --env ESCPOS_DEBUG=True \ + escpos-netprinter:3.2 +``` +Setting this variable will generate logs from 3 different sources: +- The CUPS printer driver +- Jetdirect requests +- The ESC/POS to HTML conversion itself +- Browsing the web interface + +If you have problems with the CUPS interface, you can add port 631 to access the CUPS administrator interface. The CUPS administrative username is `cupsadmin` and the password is `123456`; you can change that in the dockerfile or at runtime inside the administrator interface. +```bash +docker run -d \ + -p 515:515/tcp \ + -p 80:80/tcp \ + -p 9100:9100/tcp \ + -p 631:631/tcp + --mount source=receiptVolume,target=/home/escpos-emu/web \ + escpos-netprinter:3.2 ``` -It should now accept prints by JetDirect on the default port(9100) and by lpd on the default port(515), and you can visualize it with the web application at port 80. -For debugging, you can add port 631 to access the CUPS interface. The CUPS administrative username is `cupsadmin` and the password is `123456`. -As of version 2.0, this has been tested to work with a regular POS program without adapting it. +### Runtime Directory Structure + +The following directories inside the container are useful: +- `/home/escpos-emu/web/`: Stores all the printed receipts and printer state (including logs) +- `/home/escpos-emu/web/receipts`: Stores the HTML receipts +- `/home/escpos-emu/web/tmp`: Stores temporary files during processing (for debugging only) +- `/home/escpos-emu/web/receipt_list.csv`: Created at runtime, this file contains the list of the printed receipts with the file location. + +## Translations + +This project supports internationalization using [Flask-Babel](https://python-babel.github.io/flask-babel). All translation files are in the `/translations` subdirectory, including the `messages.pot` file. + +You can create a new locale using the following scripts: +- `init_babel_locale.sh`: to create a new locale file NOTE: you will need to add the locale in `babel_config.py` manually after this. +- `compile_babel_translations.sh`: to compile all locales and make them available to Flask. This should be launched after every change to the translations, not immediately after creating a locale. + +Other helpful scripts for developers : +- `update_babel_messages.sh`: If you add more translatable text in the UI or code, use this script to make Babel extract and add them to the locale files +- `extract_babel_messages.sh`: If you ever want to generate the `messages.pot` file without updating any locale, this is the script. + +## Configuration Options -Version 3.0 is now dealing with all status requests from POS systems as described in the Epson APG. +The following environment variables can be configured: +| Variable | Default | Description | +|----------|---------|-------------| +| ESCPOS_DEBUG | false | Enable debug mode for detailed logs | +| PRINTER_PORT | 9100 | JetDirect port for printer communication | +| FLASK_RUN_DEBUG | false | Enable Flask debug mode | +| FLASK_RUN_PORT | 80 | Sets the listening port for the web interface | +| ESCPOS_TIMEZONE | "America/Montreal" | Sets the timezone for all datetime formatting | ## Known issues -While version 3.0 is no longer a beta version, it has known defects: +While version 3.2 is ready for production, it has known defects: - It still uses the Flask development server, so it is unsafe for public networks. - While it works with simple drivers, for example the one for the MUNBYN ITPP047 printers, the [Epson utilities](https://download.epson-biz.com/modules/pos/) refuse to speak to it. diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..a76950c --- /dev/null +++ b/babel.cfg @@ -0,0 +1,2 @@ +[python: ./*.py] +[jinja2: templates/**.html.j2] \ No newline at end of file diff --git a/babel_config.py b/babel_config.py new file mode 100644 index 0000000..1c029ac --- /dev/null +++ b/babel_config.py @@ -0,0 +1,4 @@ +# This is the flask-babel runtime config file + +class Config: + LANGUAGES = ['fr', 'en'] \ No newline at end of file diff --git a/barcodes.bin b/barcodes.bin new file mode 100644 index 0000000..6342322 --- /dev/null +++ b/barcodes.bin @@ -0,0 +1,21 @@ +@ +.ta!8Code128: +Hh-kI012z !., +.ta!8Code93: +Hh-kH012.ABZ +.ta!8Codabar: +Hh-kG A012$+-./:A +.ta!8Code39: +Hh-kEABCZ012 +.ta!8Ean8: +Hh-kD0123456 +.ta!8Ean13: +Hh-kC 012345678901 +.ta!8UpcE: +Hh-kB1234567 +.ta!8UpcA: +Hh-kA 036000291452 +.ta!8ITF: +Hh-kF +0123456789 +d \ No newline at end of file diff --git a/compile_babel_translations.sh b/compile_babel_translations.sh new file mode 100755 index 0000000..1ea05da --- /dev/null +++ b/compile_babel_translations.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# This script compiles all locales and makes them usable by Python. + +pybabel compile -d translations \ No newline at end of file diff --git a/composer.json b/composer.json index 3c22314..7bb88e4 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,8 @@ "squizlabs/php_codesniffer" : "^2.8", "mike42/escpos-php": "^1.5", "chillerlan/php-qrcode": "^5.0.3", - "zbateson/mb-wrapper": "2.0.1" + "zbateson/mb-wrapper": "2.0.1", + "picqer/php-barcode-generator": "^3.2" }, "autoload" : { "psr-4" : { diff --git a/doc/esc2html.md b/doc/esc2html.md index 3dd09aa..6390eab 100644 --- a/doc/esc2html.md +++ b/doc/esc2html.md @@ -14,12 +14,21 @@ This utility is included with escpos-tools. See the ## Usage +Basic usage: ``` php esc2html FILE ``` +Optional debugging: +``` +php esc2html --debug FILE +``` +In the debug mode, the debugging messages are sent to the debug console while the output is the same as in basic mode. + + ## Example +### Basic example ``` $ php esc2html.php receipt-with-logo.bin > receipt-with-logo.html ``` @@ -38,6 +47,15 @@ $ cat receipt-with-logo.bin > /dev/usb/lp0 The input file used as an example here was generated by [escpos-php](https://github.com/mike42/escpos-php), and is available [here](https://raw.githubusercontent.com/receipt-print-hq/escpos-tools/master/receipt-with-logo.bin). +### Saving debugging output separately from the real output + +``` +$ php esc2html.php receipt-with-qrcode.bin 1>test.html 2>log.txt +``` + +The HTML representation of the receipt is saved to test.html, and the debugging output to log.txt. + + ## Further conversions This utility will create a formatted HTML file. This can be converted accurately to PDF diff --git a/dockerfile b/dockerfile index 3ae81d7..204bb85 100644 --- a/dockerfile +++ b/dockerfile @@ -12,6 +12,7 @@ RUN install-php-extensions mbstring @composer imagick #Install Flask RUN apt-get update RUN apt-get install -y python3-flask +RUN apt-get install -y python3-flask-babel RUN apt-get install -y python3-lxml #Install CUPS @@ -54,6 +55,11 @@ ENV FLASK_RUN_DEBUG=false # To activate the netprinter debug mode, set at True (case-sensitive) ENV ESCPOS_DEBUG=false +#Localization support environment variables +ENV ESCPOS_TIMEZONE="America/Montreal" + + +# Expose all necessary ports EXPOSE ${PRINTER_PORT} EXPOSE ${FLASK_RUN_PORT} #Expose the lpd port diff --git a/esc2html.php b/esc2html.php index 1879f8d..3b9c9c4 100644 --- a/esc2html.php +++ b/esc2html.php @@ -18,7 +18,7 @@ exit(1); } else { - if ($argv[1]=='--debug'){ + if ($argv[1]=='--debug'){ $debugMode = true; if (!isset($argv[2])) { print("Usage: php " . $argv[0] . " [--debug] filename ". $argc-1 . " arguments received\n"); @@ -46,7 +46,7 @@ if ( !$fp ) { error_log("File ". $targetFilename . "not found."); exit(1); -} +} $parser = new Parser(); $parser -> addFile($fp); @@ -60,6 +60,9 @@ $imgNo = 0; $skipLineBreak = false; $code2dStorage = new Code2DStateStorage(); +$barcodeHeight = null; +$barcodeWidth = null; +$barcodeHri = null; foreach ($commands as $cmd) { if ($debugMode) error_log("". get_class($cmd) ."", 0); //Output the command class in the debug console @@ -121,7 +124,7 @@ error_log("Data size:". $sub->getDataSize() ."",0); error_log("Data: " . $sub->get_data() ."",0); } - if($sub->isAvailableAs('QRCodeSubCommand')){ + if($sub->isAvailableAs('QRCodeSubCommand')){ switch ($sub->get_fn()) { case 65: //set model $code2dStorage->setQRModel($sub->get_data()); @@ -158,13 +161,83 @@ $qrcodeData = $code2dStorage->getQRCodeData(); $outp[] = "
\"$qrcodeData\"
"; } - + break; case 82: //Transmit size information of symbol storage data. # TODO: maybe implement by printing the info? break; } } + }if ($cmd -> isAvailableAs('SetBarcodeHeightCmd')) { + $barcodeHeight = $cmd -> getHeight(); + } else if ($cmd -> isAvailableAs('SetBarcodeWidthCmd')) { + $barcodeWidth = $cmd -> getWidth(); + } else if ($cmd -> isAvailableAs('SelectBarCodeHriCmd')) { + $barcodeHri = $cmd -> getHRI(); + } else if ($cmd -> isAvailableAs('PrintBarcodeCmd')) { + $types = [ // download4.epson.biz/sec_pubs/pos/reference_en/escpos/gs_lk.html + 0 => 'TypeUpcA', + 65 => 'TypeUpcA', + 1 => 'TypeUpcE', + 66 => 'TypeUpcE', + 2 => 'TypeEan13', + 67 => 'TypeEan13', + 3 => 'TypeEan8', + 68 => 'TypeEan8', + 4 => 'TypeCode39', + 69 => 'TypeCode39', + 6 => 'TypeCodabar', + 71 => 'TypeCodabar', + 72 => 'TypeCode93', + 73 => 'TypeCode128', + 5 => 'NoTypePreview', + 70 => 'NoTypePreview', + 74 => 'NoTypePreview', + 75 => 'NoTypePreview', + 76 => 'NoTypePreview', + 77 => 'NoTypePreview', + 78 => 'NoTypePreview', + 79 => 'NoTypePreview', + ]; + $type = $types[$cmd->getType()] ?? null; + $data = $cmd -> subCommand()->getData(); + $classes = getBlockClasses($formatting); + $classesStr = implode(" ", $classes); + if ($type){ //A valid barcode system type has been specified + if($type !== 'NoTypePreview'){ + $renderer = new \Picqer\Barcode\Renderers\PngRenderer(); + $renderer->setBackgroundColor([255, 255, 255]); + if (!class_exists(\Imagick::class)) { + $renderer->useGd(); + } + else { + $renderer->useImagick(); + } + $type = '\\Picqer\\Barcode\\Types\\' . $type; + $barcode = (new $type)->getBarcode($data); + $imgSrc = base64_encode($renderer->render($barcode, $barcodeWidth ?? $barcode->getWidth(), $barcodeHeight ?? 40)); + $lineHtml = "\"{$data}\""; + } + else { // A valid type that cannot be rendered by the library. + $classesStr .= ' esc-line-command'; + $lineHtml = "BARCODE {$cmd->getType()} (NO PREVIEW AVAILABLE)" . (in_array($barcodeHri, [1, 2, 3]) ? '' : " [$data]") . ""; + } + } + else { // Missing or invalid barcode system type + $classesStr .= ' esc-line-command'; + $lineHtml = "BARCODE {$cmd->getType()} (UNKNOWN TYPE)" . (in_array($barcodeHri, [1, 2, 3]) ? '' : " [$data]") . ""; + $barcodeWidth = $barcodeHeight = $barcodeHri = null; + continue; + } + if (in_array($barcodeHri, [1, 3])) { + $lineHtml = "
$data
" . $lineHtml; + } + if (in_array($barcodeHri, [2, 3])) { + $lineHtml = $lineHtml . "
$data
"; + } + $outp[] = wrapInline("
", "
", wrapInline("", "", $lineHtml)); + $lineHtml = ""; // flush buffer + $barcodeWidth = $barcodeHeight = $barcodeHri = null; } } diff --git a/escpos-netprinter.py b/escpos-netprinter.py index 1d76ab2..1ca5ca6 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -1,5 +1,5 @@ import os -from flask import Flask, redirect, render_template, request, url_for +from flask import Flask, redirect, render_template, request, session, url_for from os import getenv from io import BufferedWriter import csv @@ -9,11 +9,12 @@ from lxml import html, etree from datetime import datetime from zoneinfo import ZoneInfo +from flask_babel import Babel +import babel_config import threading import socketserver - #Network ESC/pos printer server class ESCPOSServer(socketserver.TCPServer): @@ -1409,7 +1410,7 @@ def print_toHTML(self, binfile:BufferedWriter, bin_filename:PurePath): # append the error output to the log file with open(PurePath('web','tmp', 'esc2html_log'), mode='at') as log: log.write(f"Error while converting a JetDirect print: {err.returncode}") - log.write(datetime.now(tz=ZoneInfo("Canada/Eastern")).strftime('%Y%b%d %X.%f %Z')) + log.write(datetime.now(tz=ZoneInfo(os.environ['ESCPOS_TIMEZONE'])).strftime('%Y%b%d %X.%f %Z')) log.write(err.stderr) log.close() @@ -1421,13 +1422,13 @@ def print_toHTML(self, binfile:BufferedWriter, bin_filename:PurePath): print (f"Receipt decoded", flush=True) with open(PurePath('web','tmp', 'esc2html_log'), mode='at') as log: log.write("Successful JetDirect print\n") - log.write(datetime.now(tz=ZoneInfo("Canada/Eastern")).strftime('%Y%b%d %X.%f %Z\n\n')) + log.write(datetime.now(tz=ZoneInfo(os.environ['ESCPOS_TIMEZONE'])).strftime('%Y%b%d %X.%f %Z\n\n')) log.write(recu.stderr) log.close() #print(recu.stdout, flush=True) #Ajouter un titre au reçu - heureRecept = datetime.now(tz=ZoneInfo("Canada/Eastern")) + heureRecept = datetime.now(tz=ZoneInfo(os.environ['ESCPOS_TIMEZONE'])) recuConvert = self.add_html_title(heureRecept, recu.stdout) #print(etree.tostring(theHead), flush=True) @@ -1483,8 +1484,39 @@ def add_receipt_to_directory(new_filename: str, self=None) -> None: writer.writerow([next_fileID, new_filename]) +## Printer UI + +# Flask startup app = Flask(__name__) +# Babel setup + +def get_locale() -> str | None: + lang = request.cookies.get('lang') + if lang in babel_config.Config.LANGUAGES : + return lang + else : + return request.accept_languages.best_match(babel_config.Config.LANGUAGES) + +babel = Babel(app, locale_selector=get_locale) + +@app.context_processor +def inject_conf_var(): + return dict(AVAILABLE_LANGUAGES=babel_config.Config.LANGUAGES, + CURRENT_LANGUAGE=get_locale()) + +@app.route('/language=') +def set_language(language=None): + resp = redirect(url_for('accueil')) + # Check if the desired translation is available. If not, get the best match to what the browser desires. + if language in babel_config.Config.LANGUAGES: + resp.set_cookie('lang', language) + else: + resp.set_cookie('lang', request.accept_languages.best_match(babel_config.Config.LANGUAGES) ) # Ignore the request and guess the preferred language of the browser. + return resp + + +# Flask routes for the UI @app.route("/") def accueil(): return render_template('accueil.html.j2', host = request.host.split(':')[0], @@ -1534,14 +1566,14 @@ def show_receipt(fileID:int): with open(PurePath('web', 'receipts', filename), mode='rt') as receipt: receipt_html = receipt.read() # Read the file content receipt_html = receipt_html.replace('', '
') - receipt_html = receipt_html.replace('', '
' + render_template('footer.html') + '') # Append the footer + receipt_html = receipt_html.replace('', '' + render_template('footer.html.j2') + '') # Append the footer return receipt_html @app.route("/newReceipt") def publish_receipt_from_CUPS(): """ Get the receipt from the CUPS temp directory and publish it in the web/receipts directory and add the corresponding log to our permanent logfile""" - heureRecept = datetime.now(tz=ZoneInfo("Canada/Eastern")) + heureRecept = datetime.now(tz=ZoneInfo(os.environ['ESCPOS_TIMEZONE'])) #TODO: make this configurable from the dockerfile. #NOTE: on set dans cups-files.conf le répertoire TempDir: #Extraire le répertoire temporaire de CUPS de cups-files.conf source_dir=PurePath('/var', 'spool', 'cups', 'tmp') @@ -1570,9 +1602,9 @@ def publish_receipt_from_CUPS(): #Load the log file from /var/spool/cups/tmp/ and append it in web/tmp/esc2html_log logfile_filename = os.environ['LOG_FILENAME'] # print(logfile_filename) - log = open(PurePath('web','tmp', 'esc2html_log'), mode='wt') + log = open(PurePath('web','tmp', 'esc2html_log'), mode='a') source_log = open(source_dir.joinpath(logfile_filename), mode='rt') - log.write(f"CUPS print received at {datetime.now(tz=ZoneInfo('Canada/Eastern')).strftime('%Y%b%d %X.%f %Z')}\n") + log.write(f"CUPS print received at {datetime.now(tz=ZoneInfo(os.environ['ESCPOS_TIMEZONE'])).strftime('%Y%b%d %X.%f %Z')}\n") log.write(source_log.read()) log.close() source_log.close() @@ -1580,6 +1612,10 @@ def publish_receipt_from_CUPS(): #send an http acknowledgement return "OK" +## End printer UI + + + def launchPrintServer(printServ:ESCPOSServer): #Recevoir des connexions, une à la fois, pour l'éternité. Émule le protocle HP JetDirect diff --git a/extract_babel_messages.sh b/extract_babel_messages.sh new file mode 100755 index 0000000..4bef0bf --- /dev/null +++ b/extract_babel_messages.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# This script creates messages.pot from the project files + +pybabel extract -F babel.cfg --copyright-holder="Francois-Leonard Gilbert" --project="escpos-netprinter" --msgid-bugs-address="github.com/gilbertfl/escpos-netprinter/issues" --version="3.2" -o translations/messages.pot . \ No newline at end of file diff --git a/init_babel_locale.sh b/init_babel_locale.sh new file mode 100755 index 0000000..4527a3d --- /dev/null +++ b/init_babel_locale.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# This script creates a new locale for translation + +# Argument validation check +if [ "$#" -ne 1 ]; then + echo "This initializes a new locale for Flask-Babel" + echo "Usage: $0 " + exit 1 +fi + +pybabel init -i translations/messages.pot -d translations -l ${1} \ No newline at end of file diff --git a/src/Parser/Command/BarcodeAData.php b/src/Parser/Command/BarcodeAData.php index a01355b..5e6332a 100644 --- a/src/Parser/Command/BarcodeAData.php +++ b/src/Parser/Command/BarcodeAData.php @@ -19,4 +19,8 @@ public function addChar($char) $this -> data .= $char; } } + + public function getData() { + return $this -> data; + } } diff --git a/src/Parser/Command/BarcodeBData.php b/src/Parser/Command/BarcodeBData.php index f21ad18..c5a38f9 100644 --- a/src/Parser/Command/BarcodeBData.php +++ b/src/Parser/Command/BarcodeBData.php @@ -20,4 +20,8 @@ public function addChar($char) } return false; } + + public function getData() { + return $this -> data; + } } diff --git a/src/Parser/Command/CommandFiveArgs.php b/src/Parser/Command/CommandFiveArgs.php index 8b0c54c..c45c8f4 100644 --- a/src/Parser/Command/CommandFiveArgs.php +++ b/src/Parser/Command/CommandFiveArgs.php @@ -5,11 +5,11 @@ class CommandFiveArgs extends EscposCommand { - private $arg1 = null; - private $arg2 = null; - private $arg3 = null; - private $arg4 = null; - private $arg5 = null; + private ?int $arg1 = null; + private ?int $arg2 = null; + private ?int $arg3 = null; + private ?int $arg4 = null; + private ?int $arg5 = null; public function addChar($char) { @@ -31,4 +31,24 @@ public function addChar($char) } return false; } + + protected function getArg1():?int{ + return $this->arg1; + } + + protected function getArg2():?int{ + return $this->arg2; + } + + protected function getArg3():?int{ + return $this->arg3; + } + + protected function getArg4():?int{ + return $this->arg4; + } + + protected function getArg5():?int{ + return $this->arg5; + } } diff --git a/src/Parser/Command/CommandOneArg.php b/src/Parser/Command/CommandOneArg.php index d854067..5683830 100644 --- a/src/Parser/Command/CommandOneArg.php +++ b/src/Parser/Command/CommandOneArg.php @@ -5,7 +5,7 @@ class CommandOneArg extends EscposCommand { - private $arg = null; + private ?int $arg = null; public function addChar($char) { @@ -17,7 +17,7 @@ public function addChar($char) } } - protected function getArg() + protected function getArg(): ?int { return $this -> arg; } diff --git a/src/Parser/Command/CommandThreeArgs.php b/src/Parser/Command/CommandThreeArgs.php index c50d7ed..47f22f2 100644 --- a/src/Parser/Command/CommandThreeArgs.php +++ b/src/Parser/Command/CommandThreeArgs.php @@ -5,9 +5,9 @@ class CommandThreeArgs extends EscposCommand { - private $arg1 = null; - private $arg2 = null; - private $arg3 = null; + private ?int $arg1 = null; + private ?int $arg2 = null; + private ?int $arg3 = null; public function addChar($char) { @@ -23,4 +23,19 @@ public function addChar($char) } return false; } + + protected function getArg1(): ?int + { + return $this -> arg1; + } + + protected function getArg2(): ?int + { + return $this -> arg2; + } + + protected function getArg3(): ?int + { + return $this -> arg3; + } } diff --git a/src/Parser/Command/CommandTwoArgs.php b/src/Parser/Command/CommandTwoArgs.php index 1949cb1..8534cb7 100644 --- a/src/Parser/Command/CommandTwoArgs.php +++ b/src/Parser/Command/CommandTwoArgs.php @@ -5,8 +5,8 @@ class CommandTwoArgs extends EscposCommand { - private $arg1 = null; - private $arg2 = null; + private ?int $arg1 = null; + private ?int $arg2 = null; public function addChar($char) { @@ -19,4 +19,14 @@ public function addChar($char) } return false; } + + protected function getArg1(): ?int + { + return $this -> arg1; + } + + protected function getArg2(): ?int + { + return $this -> arg2; + } } diff --git a/src/Parser/Command/PrintBarcodeCmd.php b/src/Parser/Command/PrintBarcodeCmd.php index a72e19a..80b983e 100644 --- a/src/Parser/Command/PrintBarcodeCmd.php +++ b/src/Parser/Command/PrintBarcodeCmd.php @@ -24,4 +24,14 @@ public function addChar($char) } return $this -> subCommand -> addChar($char); } + + public function subCommand() + { + // TODO rename and take getSubCommand() name. + return $this -> subCommand; + } + + public function getType() { + return $this -> m; + } } diff --git a/src/Parser/Command/Printout.php b/src/Parser/Command/Printout.php index 030fe43..f656f00 100755 --- a/src/Parser/Command/Printout.php +++ b/src/Parser/Command/Printout.php @@ -80,7 +80,7 @@ class Printout extends Command 'I' => 'TransmitPrinterID', 'h' => 'SetBarcodeHeightCmd', 'w' => 'SetBarcodeWidthCmd', - 'H' => 'SelectHriPrintPosCmd', + 'H' => 'SelectBarCodeHriCmd', 'k' => 'PrintBarcodeCmd', 'v' => array( '0' => 'PrintRasterBitImageCmd' diff --git a/src/Parser/Command/SelectHriPrintPosCmd.php b/src/Parser/Command/SelectBarCodeHriCmd.php similarity index 51% rename from src/Parser/Command/SelectHriPrintPosCmd.php rename to src/Parser/Command/SelectBarCodeHriCmd.php index a4cadd8..3bbcc96 100644 --- a/src/Parser/Command/SelectHriPrintPosCmd.php +++ b/src/Parser/Command/SelectBarCodeHriCmd.php @@ -3,7 +3,9 @@ use ReceiptPrintHq\EscposTools\Parser\Command\CommandOneArg; -class SelectHriPrintPosCmd extends CommandOneArg +class SelectBarCodeHriCmd extends CommandOneArg { - + public function getHRI():?int{ + return $this->getArg(); + } } diff --git a/src/Parser/Command/SetBarcodeHeightCmd.php b/src/Parser/Command/SetBarcodeHeightCmd.php index 35d455a..b7cfb94 100644 --- a/src/Parser/Command/SetBarcodeHeightCmd.php +++ b/src/Parser/Command/SetBarcodeHeightCmd.php @@ -5,5 +5,7 @@ class SetBarcodeHeightCmd extends CommandOneArg { - + public function getHeight(): ?int{ + return $this->getArg(); + } } diff --git a/src/Parser/Command/SetBarcodeWidthCmd.php b/src/Parser/Command/SetBarcodeWidthCmd.php index a1c63cd..05bc479 100755 --- a/src/Parser/Command/SetBarcodeWidthCmd.php +++ b/src/Parser/Command/SetBarcodeWidthCmd.php @@ -5,5 +5,7 @@ class SetBarcodeWidthCmd extends CommandOneArg { - + public function getWidth():?int{ + return $this->getArg(); + } } diff --git a/src/Parser/Command/TextCmd.php b/src/Parser/Command/TextCmd.php index 4fbdd90..dc70381 100644 --- a/src/Parser/Command/TextCmd.php +++ b/src/Parser/Command/TextCmd.php @@ -33,7 +33,10 @@ public function addChar($char) public function getText(InlineFormatting $context = new InlineFormatting): array|bool|string { $text = ""; - if($context->charCodeTable == "auto"){ + if(mb_detect_encoding((string) $this->str, 'UTF-8', true)){ + $text = $this->str; + } + else if($context->charCodeTable == "auto"){ # This charset is unknown to MbWrapper, so we try mbstring's "auto" as a last resort. $text = mb_convert_encoding(string: $this->str, to_encoding: "UTF-8", from_encoding: "auto"); } diff --git a/src/resources/esc2html.css b/src/resources/esc2html.css index 31253cf..9931881 100644 --- a/src/resources/esc2html.css +++ b/src/resources/esc2html.css @@ -73,6 +73,21 @@ body { font-size: 75% } +.esc-line-command{ + text-align: center; + font-weight: bold; + background: linear-gradient(180deg, + rgba(0,0,0,0) calc(50% - 1px), + rgba(192,192,192,1) calc(50%), + rgba(0,0,0,0) calc(50% + 1px) + ); +} + +.esc-line-command .command{ + background-color: white; + padding: 1px 10px 1px 10px; +} + span { display: inline-block; } diff --git a/templates/accueil.html.j2 b/templates/accueil.html.j2 index 4b85020..78973c3 100644 --- a/templates/accueil.html.j2 +++ b/templates/accueil.html.j2 @@ -1,22 +1,26 @@ - Imprimante virtuelle + {{ _("Imprimante virtuelle")}} +
-

État de l'imprimante:

+{% include 'lang_select.html.j2'%} + +

{{ _("État de l'imprimante:")}}

    -
  • En ligne
  • -
  • Adresse de cette imprimante: {{host}}
  • -
  • Ports d'impression: {{jetDirectPort}} (Jetdirect), 515 (lpd)
  • +
  • {{ _("En ligne")}}
  • +
  • {{ _("Adresse de cette imprimante:")}} {{host}}
  • +
  • {{ _("Ports d'impression:")}} {{jetDirectPort}} (Jetdirect), 515 (lpd)
  • {%if debug == 'True'%} -
  • Mode débogage activé
  • +
  • {{ _("Mode débogage activé")}}
  • {%endif%}
- Consulter la liste des reçus imprimés + {{ _("Consulter la liste des reçus imprimés")}}
-{% include 'footer.html' %} + +{% include 'footer.html.j2' %} \ No newline at end of file diff --git a/templates/footer.html b/templates/footer.html.j2 similarity index 57% rename from templates/footer.html rename to templates/footer.html.j2 index 7cdb201..249ea30 100644 --- a/templates/footer.html +++ b/templates/footer.html.j2 @@ -2,5 +2,6 @@ \ No newline at end of file diff --git a/templates/lang_select.html.j2 b/templates/lang_select.html.j2 new file mode 100644 index 0000000..7d994d7 --- /dev/null +++ b/templates/lang_select.html.j2 @@ -0,0 +1,12 @@ + + +
+{{ _("Changer de langue:")}} +{% for language in AVAILABLE_LANGUAGES %} + {% if CURRENT_LANGUAGE == language %} + {{ language }} + {% else %} + {{ language }} + {% endif %} +{% endfor %} +
\ No newline at end of file diff --git a/templates/receiptList.html.j2 b/templates/receiptList.html.j2 index 075c09a..3706df6 100644 --- a/templates/receiptList.html.j2 +++ b/templates/receiptList.html.j2 @@ -1,22 +1,22 @@ - Tous les reçus imprimés + {{ _("Tous les reçus imprimés")}}
{%if receiptlist|length > 0 %} -

{{receiptlist|length}} recu{%if receiptlist|length > 1%}s{%endif%} disponible{%if receiptlist|length > 1%}s{%endif%}

+

{{receiptlist|length}} {%if receiptlist|length > 1%}{{_("recus")}}{%else%}{{_("recu")}}{%endif%} {%if receiptlist|length > 1%}{{_("disponibles")}}{%else%}{{_("disponible")}}{%endif%}

{% else %} -

Aucun reçu trouvé!

+

{{ _("Aucun reçu trouvé!")}}

{% endif %} {# a comment #}
-{% include 'footer.html' %} +{% include 'footer.html.j2' %} \ No newline at end of file diff --git a/translations/en/LC_MESSAGES/messages.mo b/translations/en/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..621574b Binary files /dev/null and b/translations/en/LC_MESSAGES/messages.mo differ diff --git a/translations/en/LC_MESSAGES/messages.po b/translations/en/LC_MESSAGES/messages.po new file mode 100644 index 0000000..3cd80e6 --- /dev/null +++ b/translations/en/LC_MESSAGES/messages.po @@ -0,0 +1,85 @@ +# English translations for escpos-netprinter. +# Copyright (C) 2025 Francois-Leonard Gilbert +# This file is distributed under the same license as the escpos-netprinter +# project. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: escpos-netprinter 3.2\n" +"Report-Msgid-Bugs-To: github.com/gilbertfl/escpos-netprinter/issues\n" +"POT-Creation-Date: 2025-11-07 03:45+0000\n" +"PO-Revision-Date: 2025-11-07 01:34+0000\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: templates/accueil.html.j2:4 +msgid "Imprimante virtuelle" +msgstr "ESC/POS virtual printer" + +#: templates/accueil.html.j2:10 +msgid "État de l'imprimante:" +msgstr "Printer state:" + +#: templates/accueil.html.j2:12 +msgid "En ligne" +msgstr "Online" + +#: templates/accueil.html.j2:13 +msgid "Adresse de cette imprimante:" +msgstr "Printer IP address:" + +#: templates/accueil.html.j2:14 +msgid "Ports d'impression:" +msgstr "Printer ports:" + +#: templates/accueil.html.j2:16 +msgid "Mode débogage activé" +msgstr "Debug mode activated" + +#: templates/accueil.html.j2:20 +msgid "Consulter la liste des reçus imprimés" +msgstr "Open printed receipt list" + +#: templates/footer.html.j2:5 +msgid "Accueil" +msgstr "Main" + +#: templates/footer.html.j2:5 +msgid "Liste reçus" +msgstr "Receipt list" + +#: templates/lang_select.html.j2:4 +msgid "Changer de langue:" +msgstr "Select language:" + +#: templates/receiptList.html.j2:4 +msgid "Tous les reçus imprimés" +msgstr "All printed receipts" + +#: templates/receiptList.html.j2:9 +msgid "recus" +msgstr "receipts" + +#: templates/receiptList.html.j2:9 +msgid "recu" +msgstr "receipt" + +#: templates/receiptList.html.j2:9 +msgid "disponibles" +msgstr "available" + +#: templates/receiptList.html.j2:9 +msgid "disponible" +msgstr "available" + +#: templates/receiptList.html.j2:16 +msgid "Aucun reçu trouvé!" +msgstr "No receipt available!" + diff --git a/translations/fr/LC_MESSAGES/messages.mo b/translations/fr/LC_MESSAGES/messages.mo new file mode 100644 index 0000000..6ae5365 Binary files /dev/null and b/translations/fr/LC_MESSAGES/messages.mo differ diff --git a/translations/fr/LC_MESSAGES/messages.po b/translations/fr/LC_MESSAGES/messages.po new file mode 100644 index 0000000..01d05cc --- /dev/null +++ b/translations/fr/LC_MESSAGES/messages.po @@ -0,0 +1,85 @@ +# French translations for escpos-netprinter. +# Copyright (C) 2025 Francois-Leonard Gilbert +# This file is distributed under the same license as the escpos-netprinter +# project. +# FIRST AUTHOR , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: escpos-netprinter 3.2\n" +"Report-Msgid-Bugs-To: github.com/gilbertfl/escpos-netprinter/issues\n" +"POT-Creation-Date: 2025-11-07 03:45+0000\n" +"PO-Revision-Date: 2025-11-07 01:27+0000\n" +"Last-Translator: FULL NAME \n" +"Language: fr\n" +"Language-Team: fr \n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: templates/accueil.html.j2:4 +msgid "Imprimante virtuelle" +msgstr "Imprimante virtuelle ESC/POS" + +#: templates/accueil.html.j2:10 +msgid "État de l'imprimante:" +msgstr "État de l'imprimante:" + +#: templates/accueil.html.j2:12 +msgid "En ligne" +msgstr "En ligne" + +#: templates/accueil.html.j2:13 +msgid "Adresse de cette imprimante:" +msgstr "Adresse de cette imprimante:" + +#: templates/accueil.html.j2:14 +msgid "Ports d'impression:" +msgstr "Ports d'impression:" + +#: templates/accueil.html.j2:16 +msgid "Mode débogage activé" +msgstr "Mode débogage activé" + +#: templates/accueil.html.j2:20 +msgid "Consulter la liste des reçus imprimés" +msgstr "Consulter la liste des reçus imprimés" + +#: templates/footer.html.j2:5 +msgid "Accueil" +msgstr "Accueil" + +#: templates/footer.html.j2:5 +msgid "Liste reçus" +msgstr "Liste reçus" + +#: templates/lang_select.html.j2:4 +msgid "Changer de langue:" +msgstr "Changer de langue:" + +#: templates/receiptList.html.j2:4 +msgid "Tous les reçus imprimés" +msgstr "Tous les reçus imprimés" + +#: templates/receiptList.html.j2:9 +msgid "recus" +msgstr "recus" + +#: templates/receiptList.html.j2:9 +msgid "recu" +msgstr "recu" + +#: templates/receiptList.html.j2:9 +msgid "disponibles" +msgstr "disponibles" + +#: templates/receiptList.html.j2:9 +msgid "disponible" +msgstr "disponible" + +#: templates/receiptList.html.j2:16 +msgid "Aucun reçu trouvé!" +msgstr "Aucun reçu trouvé!" + diff --git a/translations/messages.pot b/translations/messages.pot new file mode 100644 index 0000000..2f33b89 --- /dev/null +++ b/translations/messages.pot @@ -0,0 +1,84 @@ +# Translations template for escpos-netprinter. +# Copyright (C) 2025 Francois-Leonard Gilbert +# This file is distributed under the same license as the escpos-netprinter +# project. +# FIRST AUTHOR , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: escpos-netprinter 3.2\n" +"Report-Msgid-Bugs-To: github.com/gilbertfl/escpos-netprinter/issues\n" +"POT-Creation-Date: 2025-11-07 03:45+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.17.0\n" + +#: templates/accueil.html.j2:4 +msgid "Imprimante virtuelle" +msgstr "" + +#: templates/accueil.html.j2:10 +msgid "État de l'imprimante:" +msgstr "" + +#: templates/accueil.html.j2:12 +msgid "En ligne" +msgstr "" + +#: templates/accueil.html.j2:13 +msgid "Adresse de cette imprimante:" +msgstr "" + +#: templates/accueil.html.j2:14 +msgid "Ports d'impression:" +msgstr "" + +#: templates/accueil.html.j2:16 +msgid "Mode débogage activé" +msgstr "" + +#: templates/accueil.html.j2:20 +msgid "Consulter la liste des reçus imprimés" +msgstr "" + +#: templates/footer.html.j2:5 +msgid "Accueil" +msgstr "" + +#: templates/footer.html.j2:5 +msgid "Liste reçus" +msgstr "" + +#: templates/lang_select.html.j2:4 +msgid "Changer de langue:" +msgstr "" + +#: templates/receiptList.html.j2:4 +msgid "Tous les reçus imprimés" +msgstr "" + +#: templates/receiptList.html.j2:9 +msgid "recus" +msgstr "" + +#: templates/receiptList.html.j2:9 +msgid "recu" +msgstr "" + +#: templates/receiptList.html.j2:9 +msgid "disponibles" +msgstr "" + +#: templates/receiptList.html.j2:9 +msgid "disponible" +msgstr "" + +#: templates/receiptList.html.j2:16 +msgid "Aucun reçu trouvé!" +msgstr "" + diff --git a/update_babel_messages.sh b/update_babel_messages.sh new file mode 100755 index 0000000..b819939 --- /dev/null +++ b/update_babel_messages.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# This script creates messages.pot from the project files, then updates the existing translations with any new texts + +pybabel extract -F babel.cfg --copyright-holder="Francois-Leonard Gilbert" --project="escpos-netprinter" --msgid-bugs-address="github.com/gilbertfl/escpos-netprinter/issues" --version="3.2" -o translations/messages.pot . +pybabel update -i translations/messages.pot -d translations \ No newline at end of file