From 33d0157b15b1a40f90d74c9162813af66350de47 Mon Sep 17 00:00:00 2001 From: NaitLee Date: Fri, 22 Apr 2022 01:14:40 +0800 Subject: [PATCH] Frontend Big Update --- TODO | 3 - build-android/0-build-android.sh | 2 +- build-android/blacklist.txt | 4 + build-common/0-bundle-all.sh | 1 + printer.py | 32 ++- server.py | 11 + www/_load.html | 93 +++++++- www/about.html | 40 ++++ www/i18n.js | 13 +- www/icon.png | Bin 4212 -> 0 bytes {build-android => www}/icon.svg | 0 www/image.js | 98 +------- www/index.html | 189 ++++++++------- www/jslicense.html | 12 +- www/lang/de-DE.json | 12 +- www/lang/en-US.json | 35 ++- www/lang/list.json | 5 + www/lang/zh-CN.json | 36 ++- www/main.css | 386 +++++++++++++++++++++++++------ www/main.js | 327 +++++++++++++++++--------- 20 files changed, 876 insertions(+), 423 deletions(-) create mode 100644 www/about.html delete mode 100644 www/icon.png rename {build-android => www}/icon.svg (100%) create mode 100644 www/lang/list.json diff --git a/TODO b/TODO index 05ca044..243d2d2 100644 --- a/TODO +++ b/TODO @@ -7,12 +7,9 @@ Note: not ordered. do whatever I/you want + Even Better CLI, e.g. invoke imagemagick if input is not PBM + Even better CUPS/IPP support + Even better frontend usability, more functions -+ A better layout for mobile? + Make a build guide for android: Summary the hacks to p4a, bleak p4a recipe, p4a webview bootstrap, and AdvancedWebView + Try to implement enough without more dependencies -+ Better Canvas mode, (re-)consider fabric.js -+ Implement Document mode, (re-)consider html2canvas.js + ... ? Consider more control to something like 'energy' diff --git a/build-android/0-build-android.sh b/build-android/0-build-android.sh index e942098..994bc39 100755 --- a/build-android/0-build-android.sh +++ b/build-android/0-build-android.sh @@ -1,6 +1,6 @@ #!/bin/sh p4a apk --private .. --dist_name="cat-printer" --package="io.github.naitlee.catprinter" --name="Cat Printer" \ - --icon=icon.png --version="0.2.0" --bootstrap=webview --window --requirements=android,pyjnius,bleak \ + --icon=icon.png --version="0.3.0" --bootstrap=webview --window --requirements=android,pyjnius,bleak \ --blacklist-requirements=sqlite3,openssl --port=8095 --arch=arm64-v8a --blacklist="blacklist.txt" \ --presplash=blank.png --presplash-color=black --add-source="advancedwebview" --orientation=user \ --permission=BLUETOOTH --permission=BLUETOOTH_SCAN --permission=BLUETOOTH_CONNECT \ diff --git a/build-android/blacklist.txt b/build-android/blacklist.txt index dbb68f9..b0ab2c3 100644 --- a/build-android/blacklist.txt +++ b/build-android/blacklist.txt @@ -6,6 +6,10 @@ .pylintrc ?-*.sh +# symlinks +*.pf2 +*.pbm + # junk __pycache__ .directory diff --git a/build-common/0-bundle-all.sh b/build-common/0-bundle-all.sh index aeba9f6..65f77d5 100755 --- a/build-common/0-bundle-all.sh +++ b/build-common/0-bundle-all.sh @@ -1,5 +1,6 @@ #!/bin/sh for i in $(find | grep -E '.*\.pyc'); do rm $i; done +for i in $(find | grep -E '__pycache__'); do rm -d $i; done python3 bundle.py $1 python3 bundle.py -w $1 python3 bundle.py -b $1 diff --git a/printer.py b/printer.py index 01e2d86..28a4a51 100644 --- a/printer.py +++ b/printer.py @@ -304,17 +304,19 @@ class PrinterDriver(Commander): def connect(self, name=None, address=None): ''' Connect to this device, and operate on it ''' + self._pending_data = io.BytesIO() if self.fake: return if (self.device is not None and address is not None and (self.device.address.lower() == address.lower())): return - if self.device is not None and self.device.is_connected: - self.loop( - self.device.stop_notify(self.rx_characteristic), - self.device.disconnect() - ) - else: + try: + if self.device is not None and self.device.is_connected: + self.loop(self.device.stop_notify(self.rx_characteristic)) + self.loop(self.device.disconnect()) + except: # pylint: disable=bare-except + pass + finally: self.device = None if name is None and address is None: return @@ -356,6 +358,7 @@ class PrinterDriver(Commander): devices = self.loop( scanner.discover(self.scan_timeout) ) + devices = [dev for dev in devices if dev.name in Models] if identifier is not None: if identifier in Models: devices = [dev for dev in devices if dev.name == identifier] @@ -371,11 +374,11 @@ class PrinterDriver(Commander): Currently, available modes are `pbm` and `text`. If no devices were connected, scan & connect to one first. ''' + self._pending_data = io.BytesIO() if self.device is None: self.scan(identifier, use_result=True) if self.device is None and not self.fake: error('no-available-devices-found', exception=PrinterError) - self._pending_data = io.BytesIO() if mode == 'pbm' or mode == 'default': printer_data = PrinterData(self.model.paper_width, file) self._print_bitmap(printer_data) @@ -509,6 +512,8 @@ class PrinterDriver(Commander): # CLI procedure +Printer = None + def _main(): 'Main routine for direct command line execution' parser = argparse.ArgumentParser( @@ -542,6 +547,8 @@ def _main(): help=I18n['virtual-run-on-specified-model']) parser.add_argument('-m', '--dump', required=False, action='store_true', help=I18n['dump-the-traffic']) + parser.add_argument('-n', '--nothing', required=False, action='store_true', + help=I18n['do-nothing']) args = parser.parse_args() info(I18n['cat-printer']) printer = PrinterDriver() @@ -556,17 +563,22 @@ def _main(): if args.fake: printer.fake = args.fake printer.model = Models[args.fake] + else: + info(I18n['connecting']) + printer.scan(args.identifier, use_result=True) printer.dump = args.dump if args.file == '-': file = sys.stdin.buffer else: file = open(args.file, 'rb') + if args.nothing: + global Printer + Printer = printer + return try: - info(I18n['connecting']) printer.print( file, - mode = 'text' if args.text else 'pbm', - identifier = args.identifier + mode = 'text' if args.text else 'pbm' ) info(I18n['finished']) except KeyboardInterrupt: diff --git a/server.py b/server.py index 28788ca..fb450c2 100644 --- a/server.py +++ b/server.py @@ -39,6 +39,7 @@ mime_type = { 'txt': 'text/plain;charset=utf-8', 'json': 'application/json;charset=utf-8', 'png': 'image/png', + 'svg': 'image/svg+xml;charset=utf-8', 'octet-stream': 'application/octet-stream' } def mime(url: str): @@ -55,6 +56,7 @@ class PrinterServerHandler(BaseHTTPRequestHandler): settings = DictAsObject({ 'config_path': 'config.json', 'version': 1, + 'first_run': True, 'is_android': False, 'scan_timeout': 5.0, 'dry_run': False @@ -73,6 +75,14 @@ class PrinterServerHandler(BaseHTTPRequestHandler): def log_error(self, *_args): pass + def handle_one_request(self): + try: + # this handler would have only one instance + # broken pipe could make it die. ignore + super().handle_one_request() + except BrokenPipeError: + pass + def do_GET(self): 'Called when server get a GET http request' path = 'www' + self.path @@ -154,6 +164,7 @@ class PrinterServerHandler(BaseHTTPRequestHandler): self.printer.dump = self.settings.dump self.printer.flip_h = self.settings.flip_h self.printer.flip_v = self.settings.flip_v + self.printer.rtl = self.settings.force_rtl if self.settings.printer is not None: name, address = self.settings.printer.split(',') self.printer.connect(name, address) diff --git a/www/_load.html b/www/_load.html index e39148b..4a0b9b1 100644 --- a/www/_load.html +++ b/www/_load.html @@ -4,15 +4,35 @@ - Python Webview Loading + Loading - +
+ +
+ + + +
+ +
\ No newline at end of file diff --git a/www/about.html b/www/about.html new file mode 100644 index 0000000..f344f6a --- /dev/null +++ b/www/about.html @@ -0,0 +1,40 @@ + + + + + + + About + + + + +
+
+

Cat Printer

+

+ Home Page: + GitHub +

+

Contributors

+
+
+ NaitLee +
+
Developer
+
+ frankenstein91 +
+
Developer
+
Translator
+
All testers & users
+
Everyone is awesome!
+
+

License

+

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

+

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

+

You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.

+
+
+ + \ No newline at end of file diff --git a/www/i18n.js b/www/i18n.js index 61b0264..c981429 100644 --- a/www/i18n.js +++ b/www/i18n.js @@ -21,24 +21,27 @@ class I18n { * @param {Languages} language */ useLanguage(language) { - this.language = language; + if (this.language) + this.database[language] = this.database[this.language]; if (!this.database[language]) this.database[language] = {}; + this.language = language; } /** - * Add data as corresponding language, also to - * other (added) languages as fallback + * Add data as corresponding language, + * also to other (added) languages as fallback, + * or override * @param {Languages} language * @param {LanguageData} data */ - add(language, data) { + add(language, data, override = false) { if (!this.database[language]) this.database[language] = {}; for (let key in data) { let value = data[key]; this.database[language][key] = value; for (let lang in this.database) - if (!this.database[lang][key]) + if (override || !this.database[lang][key]) this.database[lang][key] = value; } } diff --git a/www/icon.png b/www/icon.png deleted file mode 100644 index d46ffb1cf2fcb7086c56b0a67ec6a0535953dd46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4212 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 t ? 255 : 0; - o = m - n; + n = m > 128 ? 255 : 0; + o = m - n + t; data[p] = n; adjust(i + 1, j , o * 7 / 16); adjust(i - 1, j + 1, o * 3 / 16); @@ -85,100 +85,6 @@ function monoSteinberg(data, w, h, t) { */ function monoHalftone(data, w, h, t) {} -/** - * My own toy algorithm used in old versions. Not so natual. - * It have 2 pass, horizonally and vertically. - * @param {Uint8ClampedArray} data the grayscale data, mentioned in `monoGrayscale`. **will be modified in-place** - * @param {number} w width of image - * @param {number} h height of image - * @param {number} t threshold - */ -function monoLegacy(data, w, h, t) { - let data_h = data.slice(); - let data_v = data.slice(); - monoLegacyH(data_h, w, h, t); - monoLegacyV(data_v, w, h, t); - for (let i = 0; i < data.length; i++) { - data[i] = data_h[i] & data_v[i]; - } -} -function monoLegacyH(data, w, h, t) { - let v = 0, p; - for (let j = 0; j < h; j++) { - for (let i = 0; i < w; i++) { - p = j * w + i; - v += data[p]; - if (v >= t) { - data[p] = 255; - v = 0; - } else data[p] = 0; - } - v = 0; - } -} -function monoLegacyV(data, w, h, t) { - let v = 0, p; - for (let i = 0; i < w; i++) { - for (let j = 0; j < h; j++) { - p = j * w + i; - v += data[p]; - if (v >= t) { - data[p] = 255; - v = 0; - } else data[p] = 0; - } - v = 0; - } -} - -/** - * Slightly modified from `monoLegacy`, but still messy. - * But, try the horizonal and vertical sub algorithm! - * @param {Uint8ClampedArray} data the grayscale data, mentioned in `monoGrayscale`. **will be modified in-place** - * @param {number} w width of image - * @param {number} h height of image - * @param {number} t threshold - */ -function monoNew(data, w, h, t) { - let data_h = data.slice(); - let data_v = data.slice(); - monoNewH(data_h, w, h, t); - monoNewV(data_v, w, h, t); - for (let i = 0; i < data.length; i++) { - data[i] = data_h[i] & data_v[i]; - } -} -function monoNewH(data, w, h, t) { - t = (t - 127) / 4 + 1; - let v = 0, p; - for (let j = 0; j < w; j++) { - for (let i = 0; i < h; i++) { - p = j * h + i; - v += data[p] + t; - if (v >= 255) { - data[p] = 255; - v -= 255; - } else data[p] = 0; - } - v = 0; - } -} -function monoNewV(data, w, h, t) { - t = (t - 127) / 4 + 1; - let v = -1, p; - for (let i = 0; i < h; i++) { - for (let j = 0; j < w; j++) { - p = j * h + i; - v += data[p] + t; - if (v >= 255) { - data[p] = 255; - v -= 255; - } else data[p] = 0; - } - v = 0; - } -} - /** * Convert a monochrome image data to PBM mono image file data. * Returns a Blob containing the file data. diff --git a/www/index.html b/www/index.html index 248ac8f..0af3da5 100644 --- a/www/index.html +++ b/www/index.html @@ -5,84 +5,63 @@ Cat Printer - + -
-
+
+
-