mirror of
https://github.com/NaitLee/Cat-Printer.git
synced 2025-05-16 07:10:30 -07:00
Custom print feature; fixes
This commit is contained in:
parent
f9194a03f8
commit
2a36344375
2
.gitignore
vendored
2
.gitignore
vendored
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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 界面的远程打印标准化/兼容/安全
|
||||
|
||||
可能的功能:
|
||||
|
@ -18,6 +18,40 @@
|
||||
<input style="display: none;" type="text" id="bt_mac" value="" />
|
||||
<span>Select device:</span><select id="device_selection"></select><button id="refresh_device">Refresh</button>
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
<span>Operate directly on the box canvas</span>
|
||||
<button id="preview_button">Preview</button>
|
||||
<button id="print_button">Print</button>
|
||||
<br />
|
||||
<span>Canvas Height:</span>
|
||||
<input type="number" value="400" min="0" max="114514" step="50" id="canvas_height" />
|
||||
<br />
|
||||
<span>Threshold:</span>
|
||||
<input type="range" min="0" max="2.0" step="0.05" value="0.6" id="filter_threshold" />
|
||||
</span>
|
||||
<br />
|
||||
<span>
|
||||
<span>Insert:</span>
|
||||
<button id="action_insert_text">Text</button>
|
||||
<button id="action_insert_image">Image</button>
|
||||
<button id="action_insert_qr">QRCode</button>
|
||||
<br />
|
||||
<span>Action:</span>
|
||||
<button id="action_make_bold">Bold</button>
|
||||
<button id="action_make_italic">Italic</button>
|
||||
<button id="action_make_underline">Underline</button>
|
||||
<button id="action_switch_paint">Switch Paint</button>
|
||||
<button id="action_delete">Delete</button>
|
||||
</span>
|
||||
</p>
|
||||
<div style="margin: auto; width: 386px;">
|
||||
<canvas id="work_canvas" style="border: 1px solid currentColor; width: 384px; min-height: 3em;" width="384" height="384"></canvas>
|
||||
</div>
|
||||
<p>
|
||||
<span>Preview</span><br />
|
||||
<canvas id="image_preview" style="width: 384px;" width="384" height="0"></canvas>
|
||||
</p>
|
||||
</main>
|
||||
<textarea id="i18-N" style="display: none;">
|
||||
[zh-CN]
|
||||
@ -25,6 +59,7 @@ Custom Print=自定义打印
|
||||
Javascript should be enabled=需启用 Javascript
|
||||
Select device:=选择设备:
|
||||
Refresh=刷新
|
||||
Operate directly on the box canvas=直接在框内画布操作
|
||||
Preview=预览
|
||||
Print=打印
|
||||
Threshold:=阈值:
|
||||
@ -33,8 +68,22 @@ Printing, please wait.=打印中,请稍候。
|
||||
Please preview image first=请先预览图像
|
||||
Searching devices. Please wait for 5 seconds.=正在查找设备。请等候 5 秒。
|
||||
OK=完成
|
||||
Insert:=插入:
|
||||
Text=文本
|
||||
Image=图像
|
||||
QRCode=二维码
|
||||
Double click to edit=双击以修改
|
||||
Action:=操作:
|
||||
Bold=粗体
|
||||
Italic=斜体
|
||||
Underline=下划线
|
||||
Delete=删除
|
||||
Switch Paint=切换绘画
|
||||
Canvas Height:=画布高度:
|
||||
Content of QRCode:=二维码的内容:
|
||||
</textarea>
|
||||
<script src="i18n.js"></script>
|
||||
<script src="qrcode.min.js"></script>
|
||||
<script src="fabric.min.js"></script>
|
||||
<script src="main.js"></script>
|
||||
<script src="custom-print.js"></script>
|
||||
|
@ -0,0 +1,138 @@
|
||||
///<reference path="main.js" />
|
||||
///<reference path="main.d.ts" />
|
||||
|
||||
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();
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ main.mainpage p a {
|
||||
display: inline-block;
|
||||
font-size: larger;
|
||||
width: 10em;
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
|
||||
a:link, a:visited {
|
||||
|
4
www/main.d.ts
vendored
4
www/main.d.ts
vendored
@ -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;
|
||||
}
|
||||
|
||||
|
@ -28,11 +28,9 @@
|
||||
<div class="reset_styles" contenteditable="true" style="width: 384px; min-height: 3em;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="font-style: italic; color: gray;">From MS Office/WPS/LibreOffice to here, pasted data shall be formatted</p>
|
||||
<p style="font-style: italic; color: gray;">From Webpage/MS Office/WPS/LibreOffice to here, pasted data shall be formatted</p>
|
||||
<p>Preview</p>
|
||||
<div style="margin: auto; border: 1px solid gray; width: 384px; min-height: 3em;">
|
||||
<div id="image_preview" style="width: 384px; min-height: 3em;"></div>
|
||||
</div>
|
||||
<div id="image_preview" style="width: 384px; margin: auto;"></div>
|
||||
</main>
|
||||
<textarea id="i18-N" style="display: none;">
|
||||
[zh-CN]
|
||||
@ -44,7 +42,7 @@ Copy & paste document to box=复制粘贴文档至框内
|
||||
Preview=预览
|
||||
Print=打印
|
||||
Threshold:=阈值:
|
||||
From MS Office/WPS/LibreOffice to here, pasted data shall be formatted=从 MS Office/WPS/LibreOffice 到这里,粘贴的数据格式会保留
|
||||
From Webpage/MS Office/WPS/LibreOffice to here, pasted data shall be formatted=从网页/MS Office/WPS/LibreOffice 到这里,粘贴的数据格式会保留
|
||||
Please select a device=请选择一个设备
|
||||
Printing, please wait.=打印中,请稍候。
|
||||
Please preview image first=请先预览图像
|
||||
|
@ -9,7 +9,7 @@ class DocumentPrinter {
|
||||
printButton = document.getElementById('print_button');
|
||||
previewButton = document.getElementById('preview_button');
|
||||
threshold = 0.2;
|
||||
imagePreview = document.getElementById('image_preview');
|
||||
canvasPreview = document.getElementById('image_preview');
|
||||
monoMethod = imageDataColorToMonoSquare;
|
||||
constructor() {
|
||||
this.thresholdInput.onchange = event => {
|
||||
@ -21,7 +21,7 @@ class DocumentPrinter {
|
||||
notice(i18N.get('Please select a device'));
|
||||
return;
|
||||
}
|
||||
if (this.imagePreview.children.length == 0) {
|
||||
if (this.canvasPreview.children.length == 0) {
|
||||
notice(i18N.get('Please preview image first'));
|
||||
return;
|
||||
}
|
||||
@ -42,13 +42,13 @@ class DocumentPrinter {
|
||||
});
|
||||
});
|
||||
this.previewButton.addEventListener('click', event => {
|
||||
if (this.imagePreview.children[0] != null) this.imagePreview.children[0].remove();
|
||||
if (this.canvasPreview.children[0] != null) this.canvasPreview.children[0].remove();
|
||||
html2canvas(this.container).then(canvas => {
|
||||
let context = canvas.getContext('2d');
|
||||
let imagedata = context.getImageData(0, 0, this.WIDTH, canvas.height);
|
||||
let mono_imagedata = this.monoMethod(imagedata, this.threshold);
|
||||
context.putImageData(mono_imagedata, 0, 0);
|
||||
this.imagePreview.appendChild(canvas);
|
||||
this.canvasPreview.appendChild(canvas);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ class ImagePrinter {
|
||||
thresholdInput = document.getElementById('filter_threshold');
|
||||
fileSelection = document.getElementById('file_selection');
|
||||
dummyImage = new Image();
|
||||
imagePreview = document.getElementById('image_preview');
|
||||
canvasPreview = document.getElementById('image_preview');
|
||||
previewButton = document.getElementById('preview_button');
|
||||
printButton = document.getElementById('print_button');
|
||||
preview() {
|
||||
@ -16,9 +16,9 @@ class ImagePrinter {
|
||||
reader.onload = event1 => {
|
||||
this.dummyImage.src = event1.target.result;
|
||||
let height = this.WIDTH / this.dummyImage.width * this.dummyImage.height;
|
||||
this.imagePreview.width = this.WIDTH;
|
||||
this.imagePreview.height = height;
|
||||
let context = this.imagePreview.getContext('2d');
|
||||
this.canvasPreview.width = this.WIDTH;
|
||||
this.canvasPreview.height = height;
|
||||
let context = this.canvasPreview.getContext('2d');
|
||||
context.drawImage(this.dummyImage, 0, 0, this.WIDTH, height);
|
||||
let data = context.getImageData(0, 0, this.WIDTH, height);
|
||||
context.putImageData(this.monoMethod(data, this.threshold), 0, 0);
|
||||
@ -34,7 +34,7 @@ class ImagePrinter {
|
||||
}
|
||||
this.printButton.addEventListener('click', event => {
|
||||
// this.preview();
|
||||
if (this.imagePreview.height == 0) {
|
||||
if (this.canvasPreview.height == 0) {
|
||||
notice(i18N.get('Please preview image first'));
|
||||
return;
|
||||
}
|
||||
@ -44,8 +44,8 @@ class ImagePrinter {
|
||||
return;
|
||||
}
|
||||
notice(i18N.get('Printing, please wait.'));
|
||||
let context = this.imagePreview.getContext('2d');
|
||||
let pbm_data = imageDataMonoToPBM(context.getImageData(0, 0, this.WIDTH, this.imagePreview.height));
|
||||
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');
|
||||
|
Loading…
x
Reference in New Issue
Block a user