mirror of
https://github.com/NaitLee/Cat-Printer.git
synced 2025-05-18 00:00:18 -07:00
Merge pull request #31 from sync1211/main
Added the option to print text from the web interface.
This commit is contained in:
commit
947cb9a5b9
3
www/icons/text-center.svg
Normal file
3
www/icons/text-center.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="62px" height="51px" viewBox="-0.5 -0.5 62 51"><defs/><g><rect x="0" y="0" width="60" height="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="10" y="20" width="40" height="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="20" y="40" width="20" height="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/></g></svg>
|
After Width: | Height: | Size: 645 B |
3
www/icons/text-left.svg
Normal file
3
www/icons/text-left.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="62px" height="51px" viewBox="-0.5 -0.5 62 51"><defs/><g><rect x="0" y="0" width="60" height="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="0" y="20" width="40" height="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="0" y="40" width="20" height="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/></g></svg>
|
After Width: | Height: | Size: 643 B |
3
www/icons/text-right.svg
Normal file
3
www/icons/text-right.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="62px" height="51px" viewBox="-0.5 -0.5 62 51"><defs/><g><rect x="0" y="0" width="60" height="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="20" y="20" width="40" height="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><rect x="40" y="40" width="20" height="10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/></g></svg>
|
After Width: | Height: | Size: 645 B |
@ -120,6 +120,7 @@
|
|||||||
<div id="control-overlay">
|
<div id="control-overlay">
|
||||||
<div>
|
<div>
|
||||||
<button id="insert-picture" data-i18n="insert-picture" data-key="Enter">Insert Picture</button>
|
<button id="insert-picture" data-i18n="insert-picture" data-key="Enter">Insert Picture</button>
|
||||||
|
<button id="insert-text" data-i18n="insert-text" data-key="">Insert Text</button>
|
||||||
<p data-i18n="or-drag-file-to-below" class="hide-on-android">Or drag file to below</p>
|
<p data-i18n="or-drag-file-to-below" class="hide-on-android">Or drag file to below</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -176,6 +177,48 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="text-input">
|
||||||
|
<h1 data-i18n="insert-text">Insert Text</h1>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<div id="text-settings">
|
||||||
|
<div name="font-and-size" class="text-settings-group">
|
||||||
|
|
||||||
|
<select id="text-font">
|
||||||
|
<option value="Arial">Arial (sans-serif)</option>
|
||||||
|
<option value="Brush Script MT">Brush Script MT (cursive)</option>
|
||||||
|
<option value="Courier New">Courier New (monospace)</option>
|
||||||
|
<option value="Garamond">Garamond (serif)</option>
|
||||||
|
<option value="Georgia">Georgia (serif)</option>
|
||||||
|
<option value="Helvetica" selected>Helvetica (sans-serif)</option>
|
||||||
|
<option value="Tahoma">Tahoma (sans-serif)</option>
|
||||||
|
<option value="Times New Roman">Times New Roman (serif)</option>
|
||||||
|
<option value="Trebuchet MS">Trebuchet MS (sans-serif)</option>
|
||||||
|
<option value="Verdana">Verdana (sans-serif)</option>
|
||||||
|
</select>
|
||||||
|
<input id="text-size" type="number" name="text-size" min="1" max="100" value="20" data-key style="margin: 0px;"/>
|
||||||
|
</div>
|
||||||
|
<div name="wrap-and-align" class="text-settings-group">
|
||||||
|
<label data-i18n="wrap-by-space" name="wrap-by-space-label"><input type="checkbox" name="wrap-by-space" data-key checked />Wrap text</label>
|
||||||
|
<span class="text-align-container">
|
||||||
|
<input type="radio" name="text-align" value="left" checked/>
|
||||||
|
<span class="text-align-checkmark text-align-left"></span>
|
||||||
|
</span>
|
||||||
|
<span class="text-align-container">
|
||||||
|
<input type="radio" name="text-align" value="center"/>
|
||||||
|
<span class="text-align-checkmark text-align-center"></span>
|
||||||
|
</span>
|
||||||
|
<span class="text-align-container">
|
||||||
|
<input type="radio" name="text-align" value="right"/>
|
||||||
|
<span class="text-align-checkmark text-align-right"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="text-textarea">
|
||||||
|
<textarea id="insert-text-area"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<iframe id="frame" src="about:blank" name="frame" title="frame" data-key="v"></iframe>
|
<iframe id="frame" src="about:blank" name="frame" title="frame" data-key="v"></iframe>
|
||||||
</div>
|
</div>
|
||||||
<div id="dialog" class="hidden">
|
<div id="dialog" class="hidden">
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"canvas": "Leinwand",
|
"canvas": "Leinwand",
|
||||||
"document": "Dokument",
|
"document": "Dokument",
|
||||||
"insert-picture": "Bild einfügen",
|
"insert-picture": "Bild einfügen",
|
||||||
|
"insert-text": "Text einfügen",
|
||||||
"help": "Hilfe",
|
"help": "Hilfe",
|
||||||
"javascript-license-information": "Informationen zur JavaScript-Lizenz",
|
"javascript-license-information": "Informationen zur JavaScript-Lizenz",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
@ -102,5 +103,9 @@
|
|||||||
"high-contrast": "High Contrast",
|
"high-contrast": "High Contrast",
|
||||||
"copyright-and-license": "Copyright and License",
|
"copyright-and-license": "Copyright and License",
|
||||||
"welcome": "Welcome!",
|
"welcome": "Welcome!",
|
||||||
"some-rights-reserved": "Einige Rechte sind vorbehalten."
|
"some-rights-reserved": "Einige Rechte sind vorbehalten.",
|
||||||
|
"text-font": "Schriftart",
|
||||||
|
"text-size": "Textgröße",
|
||||||
|
"enter-text": "Text eingeben",
|
||||||
|
"wrap-by-space": "Autom. Zeilenumbruch"
|
||||||
}
|
}
|
@ -8,6 +8,7 @@
|
|||||||
"canvas": "Canvas",
|
"canvas": "Canvas",
|
||||||
"document": "Document",
|
"document": "Document",
|
||||||
"insert-picture": "Insert Picture",
|
"insert-picture": "Insert Picture",
|
||||||
|
"insert-text": "Insert Text",
|
||||||
"help": "Help",
|
"help": "Help",
|
||||||
"javascript-license-information": "JavaScript License Information",
|
"javascript-license-information": "JavaScript License Information",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
@ -127,5 +128,9 @@
|
|||||||
"cat-face-toward": "Cat Face Toward",
|
"cat-face-toward": "Cat Face Toward",
|
||||||
"quality-": "Quality:",
|
"quality-": "Quality:",
|
||||||
"print-quality": "Print quality",
|
"print-quality": "Print quality",
|
||||||
"show-more-options": "Show More Options"
|
"show-more-options": "Show More Options",
|
||||||
|
"text-font": "Font",
|
||||||
|
"text-size": "Size",
|
||||||
|
"enter-text": "Enter text",
|
||||||
|
"wrap-by-space": "Wrap words by spaces"
|
||||||
}
|
}
|
57
www/main.css
57
www/main.css
@ -660,3 +660,60 @@ body.high-contrast #control-overlay { background-color: var(--shade); }
|
|||||||
font-family: 'Unifont';
|
font-family: 'Unifont';
|
||||||
src: local('Unifont') url('unifont.ttf') url('unifont.otf');
|
src: local('Unifont') url('unifont.ttf') url('unifont.otf');
|
||||||
}
|
}
|
||||||
|
#insert-text-area {
|
||||||
|
white-space: pre;
|
||||||
|
height: 50vh;
|
||||||
|
width: var(--paper-width);
|
||||||
|
overflow: hidden auto;
|
||||||
|
white-space: break-spaces;
|
||||||
|
resize:none;
|
||||||
|
padding-top: .65ex;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-align-container span {
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.text-align-container input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 25px;
|
||||||
|
width: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-align-checkmark {
|
||||||
|
/* position: absolute; */
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: url("icons/text-left.svg)");
|
||||||
|
background-position: center;
|
||||||
|
background-size: 90%;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
min-width: 25px;
|
||||||
|
border: var(--border) solid var(--fore-color);
|
||||||
|
padding: var(--span-half) var(--span);
|
||||||
|
}
|
||||||
|
.text-align-container input:checked ~ .text-align-checkmark {
|
||||||
|
background-color: whitesmoke;
|
||||||
|
}
|
||||||
|
.text-align-left { background-image: url("icons/text-left.svg")}
|
||||||
|
.text-align-center { background-image: url("icons/text-center.svg")}
|
||||||
|
.text-align-right { background-image: url("icons/text-right.svg")}
|
||||||
|
|
||||||
|
input[name="wrap-by-space"] { margin-right: 5px; }
|
||||||
|
#text-settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.text-settings-group {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
.text-settings-group[name="wrap-and-align"] {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
label[name="wrap-by-space-label"] { padding-right: 2%; }
|
||||||
|
#text-font { height: 100%; margin: 0px; }
|
125
www/main.js
125
www/main.js
@ -305,10 +305,20 @@ class CanvasController {
|
|||||||
this.preview = document.getElementById('preview');
|
this.preview = document.getElementById('preview');
|
||||||
this.canvas = document.getElementById('canvas');
|
this.canvas = document.getElementById('canvas');
|
||||||
this.controls = document.getElementById('control-overlay');
|
this.controls = document.getElementById('control-overlay');
|
||||||
|
this.textSize = document.getElementById("text-size");
|
||||||
|
this.textFont = document.getElementById("text-font");
|
||||||
|
this.textArea = document.getElementById("insert-text-area");
|
||||||
|
this.wrapBySpace = document.querySelector('input[name="wrap-by-space"]');
|
||||||
|
this.textAlgorithm = document.querySelector('input[name="algo"][value="algo-direct"]');
|
||||||
this.height = CanvasController.defaultHeight;
|
this.height = CanvasController.defaultHeight;
|
||||||
this._thresholdRange = document.querySelector('[name="threshold"]');
|
this._thresholdRange = document.querySelector('[name="threshold"]');
|
||||||
this._energyRange = document.querySelector('[name="energy"]');
|
this._energyRange = document.querySelector('[name="energy"]');
|
||||||
this.imageUrl = null;
|
this.imageUrl = null;
|
||||||
|
this.textAlign = "left";
|
||||||
|
|
||||||
|
for (let elem of document.querySelectorAll("input[name=text-align]")){
|
||||||
|
if (elem.checked) { this.textAlign = elem.value; }
|
||||||
|
}
|
||||||
|
|
||||||
const prevent_default = (event) => {
|
const prevent_default = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -318,12 +328,33 @@ class CanvasController {
|
|||||||
this.canvas.addEventListener('dragover', prevent_default);
|
this.canvas.addEventListener('dragover', prevent_default);
|
||||||
this.canvas.addEventListener('dragenter', prevent_default);
|
this.canvas.addEventListener('dragenter', prevent_default);
|
||||||
this.canvas.addEventListener('drop', (event) => {
|
this.canvas.addEventListener('drop', (event) => {
|
||||||
this.insertPicture(event.dataTransfer.files);
|
if (event.dataTransfer?.files[0]?.type.split("/")[0] == "text") {
|
||||||
|
let file_reader = new FileReader();
|
||||||
|
file_reader.onload = () => {
|
||||||
|
this.textArea.value = file_reader.result;
|
||||||
|
Dialog.alert("#text-input", () => this.insertText(this.textArea.value));
|
||||||
|
};
|
||||||
|
file_reader.readAsText(event.dataTransfer.files[0]);
|
||||||
|
} else {
|
||||||
|
this.insertPicture(event.dataTransfer.files);
|
||||||
|
}
|
||||||
return prevent_default(event);
|
return prevent_default(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.textArea.style["font-size"] = this.textSize.value + "px";
|
||||||
|
this.textArea.style["font-family"] = this.textFont.value;
|
||||||
|
this.textArea.style["word-break"] = this.wrapBySpace.checked ? "break-word" : "break-all";
|
||||||
|
|
||||||
putEvent('input[name="algo"]', 'change', (event) => this.useAlgorithm(event.currentTarget.value), this);
|
putEvent('input[name="algo"]', 'change', (event) => this.useAlgorithm(event.currentTarget.value), this);
|
||||||
putEvent('#insert-picture' , 'click', () => this.insertPicture(), this);
|
putEvent('#insert-picture' , 'click', () => this.insertPicture(), this);
|
||||||
|
putEvent('#insert-text' , 'click', () => Dialog.alert("#text-input", () => this.insertText(this.textArea.value)));
|
||||||
|
putEvent('#text-size' , 'change', () => this.textArea.style["font-size"] = this.textSize.value + "px");
|
||||||
|
putEvent('#text-font' , 'change', () => this.textArea.style["font-family"] = this.textFont.value);
|
||||||
|
putEvent('input[name="text-align"]', 'change', (event) => {
|
||||||
|
this.textAlign = event.currentTarget.value
|
||||||
|
this.textArea.style["text-align"] = this.textAlign;
|
||||||
|
}, this);
|
||||||
|
putEvent('input[name="wrap-by-space"]' , 'change', () => this.textArea.style["word-break"] = this.wrapBySpace.checked ? "break-word" : "break-all");
|
||||||
putEvent('#button-preview' , 'click', this.activatePreview , this);
|
putEvent('#button-preview' , 'click', this.activatePreview , this);
|
||||||
putEvent('#button-reset' , 'click', this.reset , this);
|
putEvent('#button-reset' , 'click', this.reset , this);
|
||||||
putEvent('#canvas-expand' , 'click', this.expand , this);
|
putEvent('#canvas-expand' , 'click', this.expand , this);
|
||||||
@ -444,6 +475,92 @@ class CanvasController {
|
|||||||
input.click();
|
input.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
insertText(text) {
|
||||||
|
if (text == null || text.trim() == "") { return; }
|
||||||
|
|
||||||
|
const text_size = parseInt(this.textSize.value);
|
||||||
|
const text_font = this.textFont.value;
|
||||||
|
const y_step = text_size;
|
||||||
|
|
||||||
|
const ctx = this.canvas.getContext("2d");
|
||||||
|
let canvas_font = text_size + "px " + text_font;
|
||||||
|
const max_width = this.canvas.width - 10;
|
||||||
|
|
||||||
|
// Use Word Wrap to split the text over multiple lines
|
||||||
|
let lines = [];
|
||||||
|
// Calculate the aproximate maximum length of a string
|
||||||
|
// taking font and text size in account
|
||||||
|
const get_max_chars_per_line = (text, ctx) => {
|
||||||
|
let text_width = ctx.measureText(text).width;
|
||||||
|
let textIndex = max_width / text_width;
|
||||||
|
|
||||||
|
if (textIndex > 1) { return text.length}
|
||||||
|
return Math.floor(textIndex * text.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the text if it does not fit on a single line
|
||||||
|
const wrap_text = (text, max_length) => {
|
||||||
|
let split_pos = max_length;
|
||||||
|
let newline_index = text.indexOf("\n");
|
||||||
|
if (newline_index > 0 && newline_index < max_length) {
|
||||||
|
return [text.slice(0, newline_index), text.slice(newline_index, text.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.wrapBySpace.checked) {
|
||||||
|
split_pos = text.lastIndexOf(" ", max_length);
|
||||||
|
if (split_pos <= 0) { split_pos = max_length; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return [text.slice(0, split_pos), text.slice(split_pos, text.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.font = canvas_font;
|
||||||
|
while (ctx.measureText(text).width > max_width) {
|
||||||
|
let line;
|
||||||
|
let max_chars = get_max_chars_per_line(text, ctx);
|
||||||
|
if (max_chars == 0) {
|
||||||
|
lines.push(text.slice(0, 1));
|
||||||
|
text = text.slice(1, text.length);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
[line, text] = wrap_text(text, max_chars);
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let split of text.split("\n")) {
|
||||||
|
lines.push(split);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.height = (lines.length * y_step) + (y_step / 2);
|
||||||
|
ctx.font = canvas_font; // Setting this.height resets the font.
|
||||||
|
|
||||||
|
let y_pos = y_step;
|
||||||
|
for (let line of lines) {
|
||||||
|
let x_pos = 0;
|
||||||
|
// Text-alignment
|
||||||
|
if (this.textAlign.value == "right") {
|
||||||
|
x_pos = Math.max(max_width - ctx.measureText(line).width, 0)
|
||||||
|
} else if (this.textAlign.value == "center") {
|
||||||
|
x_pos = Math.max(max_width - ctx.measureText(line).width, 0) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fillText(line, x_pos, y_pos);
|
||||||
|
y_pos += y_step;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.crop();
|
||||||
|
|
||||||
|
this.textAlgorithm.checked = true;
|
||||||
|
this.textAlgorithm.dispatchEvent(new Event("change"));
|
||||||
|
|
||||||
|
this.imageUrl = this.canvas.toDataURL();
|
||||||
|
this.activatePreview();
|
||||||
|
|
||||||
|
this.controls.classList.add('hidden');
|
||||||
|
|
||||||
|
hint('#button-print');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
reset() {
|
reset() {
|
||||||
let canvas = this.canvas;
|
let canvas = this.canvas;
|
||||||
canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
|
canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
|
||||||
@ -451,6 +568,11 @@ class CanvasController {
|
|||||||
this.activatePreview();
|
this.activatePreview();
|
||||||
this.imageUrl = null;
|
this.imageUrl = null;
|
||||||
this.controls.classList.remove('hidden');
|
this.controls.classList.remove('hidden');
|
||||||
|
|
||||||
|
// Reset hinted button
|
||||||
|
for (let elem of document.getElementsByClassName("hint")) {
|
||||||
|
elem.classList.remove("hint");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
makePbm() {
|
makePbm() {
|
||||||
let blob = mono2pbm(this.previewData, this.preview.width, this.preview.height);
|
let blob = mono2pbm(this.previewData, this.preview.width, this.preview.height);
|
||||||
@ -590,6 +712,7 @@ class Main {
|
|||||||
(value) => this.settings['text_mode'] = (value === 'algo-direct')
|
(value) => this.settings['text_mode'] = (value === 'algo-direct')
|
||||||
);
|
);
|
||||||
this.attachSetter('[name="transparent-as-white"]', 'change', 'transparent_as_white');
|
this.attachSetter('[name="transparent-as-white"]', 'change', 'transparent_as_white');
|
||||||
|
this.attachSetter('[name="wrap-by-space"]', 'change', 'wrap_by_space');
|
||||||
this.attachSetter('[name="dry-run"]', 'change', 'dry_run',
|
this.attachSetter('[name="dry-run"]', 'change', 'dry_run',
|
||||||
(checked) => checked && Notice.note('dry-run-test-print-process-only')
|
(checked) => checked && Notice.note('dry-run-test-print-process-only')
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user