From 2a36344375e1561310a0f061ebadaf1a42004ab5 Mon Sep 17 00:00:00 2001 From: NaitLee Date: Wed, 8 Sep 2021 16:51:48 +0800 Subject: [PATCH] Custom print feature; fixes --- .gitignore | 2 + README.md | 5 +- README.zh-CN.md | 5 +- www/custom-print.html | 49 ++++++++++++++ www/custom-print.js | 138 ++++++++++++++++++++++++++++++++++++++++ www/i18n.js | 2 +- www/main.css | 1 + www/main.d.ts | 4 +- www/print-document.html | 8 +-- www/print-document.js | 8 +-- www/print-image.js | 14 ++-- 11 files changed, 213 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index b3c07e2..4e23deb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ www/fabric.js www/fabric.min.js www/html2canvas.js www/html2canvas.min.js +www/qrcode.js +www/qrcode.min.js __pycache__ # Byte-compiled / optimized / DLL files diff --git a/README.md b/README.md index 4f5fdd3..20abed2 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,8 @@ This application uses server/client module, and have fewest possible dependencie - Python3 & Browser - [fabric.min.js](https://github.com/fabricjs/fabric.js/tree/master/dist) - [html2canvas.min.js](https://html2canvas.hertzen.com/) -- Any css for plain webpage, e.g. [minicss](https://minicss.org/), rename to `skin.css` +- [qrcode.min.js](https://davidshimjs.github.io/qrcodejs/) +- (Optional) Any css for plain webpage, e.g. [minicss](https://minicss.org/), rename to `skin.css` Put any web-related files to folder `www`. @@ -47,7 +48,7 @@ Support for both Windows and GNU/Linux are included. And Windows release package ### Plans -- Support rich edit features as the official app for cat printers (iPrint & 精准学习) +- Smoother mono-color converting - Make remote-print by web interface more standard/compatible/secure Possible features: diff --git a/README.zh-CN.md b/README.zh-CN.md index eb9805d..e260c4c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -37,7 +37,8 @@ TODO - Python3 与浏览器 - [fabric.min.js](https://github.com/fabricjs/fabric.js/tree/master/dist) - [html2canvas.min.js](https://html2canvas.hertzen.com/) -- 任何纯网页可用的 css,如 [minicss](https://minicss.org/),重命名为 `skin.css` +- [qrcode.min.js](https://davidshimjs.github.io/qrcodejs/) +- (可选)任何纯网页可用的 css,如 [minicss](https://minicss.org/),重命名为 `skin.css` 将 web 相关的文件放在 `www` 文件夹中。 @@ -47,7 +48,7 @@ TODO ### 计划 -- 支持高级编辑,如同官方应用 (iPrint & 精准学习) +- 更好的双色转换 - 使 web 界面的远程打印标准化/兼容/安全 可能的功能: diff --git a/www/custom-print.html b/www/custom-print.html index d04fb09..8d1da2a 100644 --- a/www/custom-print.html +++ b/www/custom-print.html @@ -18,6 +18,40 @@ Select device:

+

+ + Operate directly on the box canvas + + +
+ Canvas Height: + +
+ Threshold: + +
+
+ + Insert: + + + +
+ Action: + + + + + +
+

+
+ +
+

+ Preview
+ +

+ diff --git a/www/custom-print.js b/www/custom-print.js index e69de29..9c6a938 100644 --- a/www/custom-print.js +++ b/www/custom-print.js @@ -0,0 +1,138 @@ +/// +/// + +class CustomPrinter { + WIDTH = 384; + threshold = 0.6; + bluetoothMACInput = document.getElementById('bt_mac'); + thresholdInput = document.getElementById('filter_threshold'); + fileSelection = document.createElement('input'); + dummyImage = new Image(); + canvasPreview = document.getElementById('image_preview'); + previewButton = document.getElementById('preview_button'); + printButton = document.getElementById('print_button'); + actionInsertText = document.getElementById('action_insert_text'); + actionInsertImage = document.getElementById('action_insert_image'); + actionInsertQR = document.getElementById('action_insert_qr'); + canvasHeightInput = document.getElementById('canvas_height'); + fabricCanvas = new fabric.Canvas('work_canvas', { + backgroundColor: 'white' + }); + monoMethod = imageDataColorToMonoSquare; + insertImage() { + this.fileSelection.click(); + } + preview() { + let context = this.fabricCanvas.getContext('2d'); + let imagedata = context.getImageData(0, 0, this.WIDTH, this.fabricCanvas.height); + this.canvasPreview.height = this.fabricCanvas.height; + this.canvasPreview.getContext('2d').putImageData(this.monoMethod(imagedata, this.threshold), 0, 0); + } + constructor() { + this.fileSelection.type = 'file'; + this.monoMethod = imageDataColorToMonoSquare; + this.fileSelection.addEventListener('input', this.preview.bind(this)); + this.previewButton.addEventListener('click', this.preview.bind(this)); + this.thresholdInput.onchange = event => { + this.threshold = this.thresholdInput.value; + } + this.canvasHeightInput.onchange = event => { + this.fabricCanvas.setHeight(this.canvasHeightInput.value); + } + this.actionInsertText.addEventListener('click', event => { + let text = new fabric.Textbox(i18N.get('Double click to edit'), { + color: 'black', + fontSize: 24, + }); + this.fabricCanvas.add(text); + }); + this.fileSelection.addEventListener('input', event => { + let reader = new FileReader(); + reader.onload = event1 => { + this.dummyImage.src = event1.target.result; + let fimage = new fabric.Image(this.dummyImage, {}); + fimage.scale(this.WIDTH / this.dummyImage.width); + this.fabricCanvas.add(fimage); + } + reader.readAsDataURL(this.fileSelection.files[0]); + }); + this.actionInsertImage.addEventListener('click', this.insertImage.bind(this)); + this.actionInsertQR.addEventListener('click', event => { + let div = document.createElement('div'); + new QRCode(div, prompt(i18N.get('Content of QRCode:'))); + // QRCode generation is async, currently no better way than wait for a while + setTimeout(() => { + let fimage = new fabric.Image(div.lastChild, {}); + fimage.scale(this.WIDTH / div.lastChild.width); + this.fabricCanvas.add(fimage); + }, 1000); + }); + this.printButton.addEventListener('click', event => { + // this.preview(); + if (this.canvasPreview.height == 0) { + notice(i18N.get('Please preview image first')); + return; + } + let mac_address = this.bluetoothMACInput.value; + if (mac_address == '') { + notice(i18N.get('Please select a device')); + return; + } + notice(i18N.get('Printing, please wait.')); + let context = this.canvasPreview.getContext('2d'); + let pbm_data = imageDataMonoToPBM(context.getImageData(0, 0, this.WIDTH, this.canvasPreview.height)); + let xhr = new XMLHttpRequest(); + xhr.open('POST', '/~print?address=' + mac_address); + xhr.setRequestHeader('Content-Type', 'application-octet-stream'); + xhr.onload = () => { + notice(i18N.get(xhr.responseText)); + } + xhr.send(pbm_data); + }); + let boldFunction = () => { + let object = this.fabricCanvas.getActiveObject(); + if (!object) return; + if (object.type == 'textbox') { + if (object.fontWeight == 'normal') object.fontWeight = 'bold'; + else if (object.fontWeight == 'bold') object.fontWeight = 'normal'; + this.fabricCanvas.renderAll(); + } + } + document.getElementById('action_make_bold').addEventListener('click', boldFunction.bind(this)); + let italicFunction = () => { + let object = this.fabricCanvas.getActiveObject(); + if (!object) return; + if (object.type == 'textbox') { + if (object.fontStyle == 'normal') object.fontStyle = 'italic'; + else if (object.fontStyle == 'italic') object.fontStyle = 'normal'; + this.fabricCanvas.renderAll(); + } + } + document.getElementById('action_make_italic').addEventListener('click', italicFunction.bind(this)); + document.getElementById('action_make_underline').addEventListener('click', event => { + let object = this.fabricCanvas.getActiveObject(); + if (!object) return; + if (object.type == 'textbox') { + object.underline = !object.underline; + // Seems there's a bug in fabric, underline cannot be rendered before changing bold/italic + boldFunction(); + this.fabricCanvas.renderAll(); + boldFunction(); + this.fabricCanvas.renderAll(); + } + }); + document.getElementById('action_delete').addEventListener('click', event => { + let object = this.fabricCanvas.getActiveObject(); + if (!object) return; + this.fabricCanvas.remove(object); + this.fabricCanvas.renderAll(); + }); + this.fabricCanvas.freeDrawingBrush.color = 'black'; + this.fabricCanvas.freeDrawingBrush.width = 6; + document.getElementById('action_switch_paint').addEventListener('click', event => { + this.fabricCanvas.isDrawingMode = !this.fabricCanvas.isDrawingMode; + }) + } +} + +var custom_printer = new CustomPrinter(); diff --git a/www/i18n.js b/www/i18n.js index f33245e..a97199e 100644 --- a/www/i18n.js +++ b/www/i18n.js @@ -67,8 +67,8 @@ } function get(originaltext, language) { - if (lang[language] == undefined) return originaltext; language = language || userlang || defaultlang; + if (lang[language] == undefined) return originaltext; return lang[language][originaltext] || originaltext; } diff --git a/www/main.css b/www/main.css index 7fa62d9..91a9268 100644 --- a/www/main.css +++ b/www/main.css @@ -15,6 +15,7 @@ main.mainpage p a { display: inline-block; font-size: larger; width: 10em; + padding: 0.5em 0; } a:link, a:visited { diff --git a/www/main.d.ts b/www/main.d.ts index 60f34b2..dd53c0a 100644 --- a/www/main.d.ts +++ b/www/main.d.ts @@ -13,7 +13,7 @@ declare interface ImagePrinter { bluetoothMACInput: HTMLInputElement; fileSelection: HTMLInputElement; dummyImage: HTMLImageElement; - imagePreview: HTMLCanvasElement; + canvasPreview: HTMLCanvasElement; previewButton: HTMLButtonElement; printButton: HTMLButtonElement; monoMethod: Function; @@ -24,7 +24,7 @@ declare interface DocumentPrinter { bluetoothMACInput: HTMLInputElement; container: HTMLDivElement; printButton: HTMLButtonElement; - imagePreview: HTMLDivElement; + canvasPreview: HTMLDivElement; monoMethod: Function; } diff --git a/www/print-document.html b/www/print-document.html index e793bf1..0061187 100644 --- a/www/print-document.html +++ b/www/print-document.html @@ -28,11 +28,9 @@
-

From MS Office/WPS/LibreOffice to here, pasted data shall be formatted

+

From Webpage/MS Office/WPS/LibreOffice to here, pasted data shall be formatted

Preview

-
-
-
+