better css; image rotation; lolcat i18n; update about

the works are still incomplete. expecting more code cleaning
This commit is contained in:
NaitLee 2022-07-10 01:31:23 +08:00
parent 947cb9a5b9
commit 502a572183
13 changed files with 423 additions and 173 deletions

1
TODO
View File

@ -12,6 +12,7 @@ Note: not ordered. do whatever I/you want
+ 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
+ More funny i18n
+ Arch Linux package / AUR, package for other distros
+ Service for other init systems (a systemd unit file is there)
+ ...

View File

@ -153,3 +153,34 @@ That may involve another (partial) rewrite.
Afraid not, experiment always worth it. And remember: it's all about idea, everyone can make use of then.
Yawn... bed time...
9th
Did some stylesheet fix.
Then go for image rotation. It shouldn't be that hard:
0 1 2 3 16 17 18 19 32 33 34 35 48 49 50 51
4 5 6 7 20 21 22 23 36 37 38 39 52 53 54 55
8 9 10 11 24 25 26 27 40 41 42 43 56 57 58 59
12 13 14 15 28 29 30 31 44 45 46 47 60 61 62 63
16 17 18 19 32 33 34 35 48 49 50 51 64 65 66 67
20 21 22 23 36 37 38 39 52 53 54 55 68 69 70 71
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
Think about HTML canvas ImageData, one dimensional [R,G,B,A,...] array.
The "big" problem is just make a procedure to transform from the first matrix to the second.
(and flip the result horizontally)
Know what happened? I produced a right procedure from very start, but the image screwed up.
Tried to "fix" it, used at least 4 hours, finally found it's a matter of didn't floor a floating number.
(Height is got by multiplying aspect ratio)
So the Internet JavaScript memes are damned true.
https://programmerhumor.io/javascript-memes/why-is-it-like-this-2/
https://programmerhumor.io/javascript-memes/sorry-dad-_-2/

View File

@ -635,7 +635,7 @@ def _main():
parser.add_argument('-f', '--fake', metavar='XY01', type=str, default='',
help=i18n('virtual-run-on-specified-model'))
parser.add_argument('-m', '--dump', action='store_true',
help=i18n('dump-the-traffic'))
help=i18n('dump-traffic'))
parser.add_argument('-n', '--nothing', action='store_true',
help=i18n('do-nothing'))

View File

@ -1 +1 @@
0.5.2
0.6.0.1

View File

@ -23,6 +23,7 @@
<a target="_blank" href="https://github.com/NaitLee">NaitLee</a>
</dt>
<dd data-i18n="developer">Developer</dd>
<dd data-i18n="translator">Translator</dd>
</dl>
<dl>
<dt>
@ -32,7 +33,19 @@
<dd data-i18n="translator">Translator</dd>
</dl>
<dl>
<dt data-i18n="all-testers-and-users">All testers & users</dt>
<dt>
<a target="_blank" href="https://github.com/andypiper">andypiper</a>
</dt>
<dd data-i18n="minor-tweaks">Minor Tweaks</dd>
</dl>
<dl>
<dt>
<a target="_blank" href="https://github.com/sync1211">sync1211</a>
</dt>
<dd data-i18n="developer">Developer</dd>
</dl>
<dl>
<dt data-i18n="all-users-and-developers">All testers & users</dt>
<dd data-i18n="everyone-is-awesome">Everyone is awesome!</dd>
</dl>
</div>

View File

@ -26,14 +26,14 @@
<span class="label-span-input">
<span data-i18n="process-as-">Process as:</span>
<span>
<label>
<input type="radio" name="algo" value="algo-direct" data-key />
<span data-i18n="text">Text</span>
</label>
<label>
<input type="radio" name="algo" value="algo-steinberg" data-key checked />
<span data-i18n="picture">Picture</span>
</label>
<label>
<input type="radio" name="algo" value="algo-direct" data-key />
<span data-i18n="text">Text</span>
</label>
</span>
</span>
<!-- "brightness" is historically "threshold" -->
@ -52,6 +52,10 @@
<input type="range" min="24" max="36" value="32" step="4" name="quality" data-key data-default />
</label>
</div>
<label class="label-input-span">
<input type="checkbox" name="rotate" data-key />
<span data-i18n="rotate-image">Rotate Image</span>
</label>
<label class="label-input-span" data-hide-as="print">
<input type="checkbox" name="transparent-as-white" data-key checked />
<span data-i18n="transparent-as-white">Transparent as White</span>
@ -120,7 +124,7 @@
<div id="control-overlay">
<div>
<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>
<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>
</div>
</div>
@ -131,7 +135,6 @@
<div class="center buttons">
<!-- <button id="canvas-expand" data-i18n="expand">Expand</button>
<button id="canvas-crop" data-i18n="crop">Crop</button> -->
<!-- <button id="button-preview" data-i18n="preview">Preview</button> -->
<button id="button-reset" data-i18n="reset" data-key>Reset</button>
<button id="button-print" data-i18n="print" data-key=" ">Print</button>
</div>
@ -141,6 +144,7 @@
<div id="hidden" class="hard-hidden">
<!-- Hidden area for putting elements -->
<input type="file" id="file" />
<img src="" id="img" alt="hidden-image" />
<div id="accessibility">
<div>
<h2>
@ -180,42 +184,37 @@
<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 id="text-font" contenteditable="true" data-key>
<option value="serif" data-i18n="serif">Serif</option>
<option value="sans-serif" data-i18n="sans-serif" selected>Sans Serif</option>
<option value="monospace" data-i18n="monospace">Monospace</option>
<option value="Unifont" data-i18n="unifont">Unifont</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>
<label>
<input type="checkbox" name="wrap-words-by-spaces" data-key checked />
<span data-i18n="wrap-words-by-spaces">Wrap words by spaces</span>
</label>
<span class="text-align-container">
<input type="radio" name="text-align" value="left" checked/>
<input type="radio" name="text-align" value="left" data-key checked />
<span class="text-align-checkmark text-align-left"></span>
</span>
<span class="text-align-container">
<input type="radio" name="text-align" value="center"/>
<input type="radio" name="text-align" value="center" data-key />
<span class="text-align-checkmark text-align-center"></span>
</span>
<span class="text-align-container">
<input type="radio" name="text-align" value="right"/>
<input type="radio" name="text-align" value="right" data-key />
<span class="text-align-checkmark text-align-right"></span>
</span>
</div>
</div>
<div id="text-textarea">
<textarea id="insert-text-area"></textarea>
<textarea id="insert-text-area" data-key="/"></textarea>
</div>
</div>
</div>

View File

@ -52,7 +52,6 @@
"supported-models-": "Unterstützte Modelle:",
"path-to-input-file-dash-for-stdin": "Pfad zur Datei. '-' für stdin",
"scan-for-specified-seconds": "Suchlauf für die angegebenen Sekunden",
"dump-the-traffic": "Den Datenverkehr auf dem Drucker ausgeben und PBM-Bild beim Textdruck",
"text-printing-mode": "Textdruckmodus",
"please-install-pyobjc-via-pip": "Bitte installieren Sie `pyobjc` über pip",
"please-install-bleak-via-pip": "Bitte installieren Sie `bleak` über pip",
@ -94,7 +93,7 @@
"contributors": "Beitragende",
"developer": "Entwickler",
"translator": "Übersetzer",
"all-testers-and-users": "All Tester & Benutzer",
"all-users-and-developers": "All Tester & Benutzer",
"everyone-is-awesome": "Jeder ist super",
"license": "Lizenz",
"exiting": "Exiting…",
@ -107,5 +106,5 @@
"text-font": "Schriftart",
"text-size": "Textgröße",
"enter-text": "Text eingeben",
"wrap-by-space": "Autom. Zeilenumbruch"
"wrap-words-by-spaces": "Autom. Zeilenumbruch"
}

View File

@ -91,7 +91,7 @@
"contributors": "Contributors",
"developer": "Developer",
"translator": "Translator",
"all-testers-and-users": "All testers & users",
"all-users-and-developers": "All users & developers",
"everyone-is-awesome": "Everyone is awesome!",
"license": "License",
"exiting": "Exiting…",
@ -132,5 +132,10 @@
"text-font": "Font",
"text-size": "Size",
"enter-text": "Enter text",
"wrap-by-space": "Wrap words by spaces"
"wrap-words-by-spaces": "Wrap words by spaces",
"minor-tweaks": "Minor Tweaks",
"serif": "Serif",
"sans-serif": "Sans Serif",
"monospace": "Monospace",
"rotate-image": "Rotate Image"
}

View File

@ -1,5 +1,6 @@
{
"en-US": "English (US)",
"zh-CN": "中文(简体)",
"de-DE": "Deutsch"
"de-DE": "Deutsch",
"lolcat": "LOLCAT"
}

136
www/lang/lolcat.json Normal file
View File

@ -0,0 +1,136 @@
{
"$language": "LOLCAT",
"cat-printer": "KITTE PAWS 🐾",
"printer": "PAWS",
"device-": "KITTE>",
"refresh": "FIND",
"mode-": "HOW>",
"canvas": "VIEW",
"document": "DOC",
"insert-picture": "PUT CAT PIC",
"insert-text": "SAY MEOW",
"help": "HALP",
"javascript-license-information": "BROWSR JUNK",
"settings": "CONFIGUR",
"image": "PIC",
"threshold-": "BLAK OR WHITE",
"transparent-as-white": "DONT POLUT PIC",
"misc": "OTHR",
"system": "SYS",
"disable-animation": "NO MOTION",
"exit": "BAK TO HOM",
"error-message": "SOMTHIN WRONG",
"preview": "LOOK",
"print": "PAW STEP",
"expand": "MORE",
"crop": "LESS",
"scanning-for-devices": "LUKIN FOR KITTEZ",
"scan-time-": "HOW LONG TO FIND",
"-seconds": "SECS",
"no-available-devices-found": "NO KITTE FOUND",
"found-0-available-devices": {
"single": "THER IS {0} KITTE",
"multiple": "THER R {0} KITTEZ"
},
"please-check-if-the-printer-is-down": "CHEK IF KITTE IS SLIPIN",
"printing": "KITTE WALKIN…",
"finished": "K BYE!!",
"coming-soon": "COMIN SOON…",
"dry-run": "DONT STEP WITH INK",
"dry-run-test-print-process-only": "WONT REILLY STEP NOW",
"you-can-close-this-page-manually": "KITTE SLEP YA MAY GO",
"please-enable-bluetooth": "OPEN YA BLUETOOTH PLZ",
"error-happened-please-check-error-message": "SOMTHIN WRONG CHK ERR LOG PLZ",
"you-can-seek-for-help-with-detailed-info-below": "ASK OTHR KITTE WITH THEZ",
"or-try-to-scan-longer": "TRY TO FIND BIT LONGER",
"print-to-cat-printer": "PAWS TO STEP ON PAPR OF KITTE PRINTER!",
"supported-models-": "KNOWN KITTEZ>",
"path-to-input-file-dash-for-stdin": "WHAT FILE TO STEP '-' MEAN STDIN",
"please-install-pyobjc-via-pip": "INSTAL `pyobjc` VIA pip",
"please-install-bleak-via-pip": "INSTAL `bleak` VIA pip",
"folder-printer_lib-is-incomplete-or-missing-please-check": "DIR `printer_lib` HAV PROBLM CHK PLZ",
"input-is-not-pbm-image": "INPUT ISNT PBM PIC",
"unsuitable-image-width-expected-0-got-1": "WRONG PIC WIDZ {0} WANT {1}",
"broken-pbm-image": "BAD PBM PIC",
"input-is-not-text-file": "SAY MEOW MEOW NOT PIC",
"match-printer-with-this-name-or-address": "PICK TIS KITTE NAM,ADDR",
"virtual-run-on-specified-model": "DREAM ABOUT TIS KITTE YA DONT HAVE",
"font-size-0": "PAW BIG AS {0}",
"stopping": "LEAVIN",
"connecting": "IM COMIN",
"model-0-is-not-supported-yet": "KITTE '{0}' UNKNON",
"invalid-address-0": "THER ISNT A KITTE AT '{0}'",
"will-listen-on-all-addresses": "GATHER CONN ON ALL ADDRS",
"serving-at-0": "IM AT {0}",
"disconnecting-from-printer": "LEAVIN KITTE",
"connected-to-0-1": "PICKD TIS {0} {1}",
"flip-horizontally": "FLIP <>",
"flip-vertically": "FLIP ^v",
"dump-traffic": "WACH KITTE PAWS",
"right-to-left-text-order": "YA READ RTL",
"auto-wrap-line": "WRAP MEOW",
"process-as-": "HOW TO STEP>",
"text": "MEOW",
"picture": "PICS",
"pattern": "SPOTS",
"large-font": "BIGGR PAW",
"accessibility": "IM SPECIAL",
"language": "WHAT YA SAY",
"layout": "WHAT YA LOOK",
"ok": "GO",
"cancel": "BACK",
"yes": "YEAH",
"no": "NOPE",
"about": "KITTE INFO",
"home-page-": "CAT HOME>",
"contributors": "GOOD PEEPL",
"developer": "H4CKR",
"translator": "LOLCAT",
"all-users-and-developers": "ALL LITL N BIG CATS",
"everyone-is-awesome": "YA LL AWSOM!!",
"license": "SERIOS THINY",
"exiting": "STOPIN…",
"dark-theme": "BLAK EYES",
"high-contrast": "WEAK EYES",
"welcome": "HAI THER!!",
"copyright-and-license": "SERIOS THINY",
"some-rights-reserved": "Some rights reserved.",
"ENTER": "ENTR",
"SPACE": "SPAC",
"ESCAPE": "ESC",
"TAB": "TAB",
"COMMA": "COMA",
"DOT": "DOT",
"to-enter-keyboard-mode-press-tab": "KEYBORD G33KS PRES TAB",
"usage-": "HOW TO USE>",
"positional-arguments-": "ARGS>",
"options-": "OPTS>",
"show-this-help-message": "SHOW WHAT YA LOOKIN NOW",
"do-nothing": "SLEEPY KITTE",
"scan-for-a-printer": "FIND A KITTE",
"text-printing-mode-with-options": "NO STEP PIC WANT MEOW",
"image-printing-options": "HOW TO STEP A PIC",
"convert-input-image-with-imagemagick": "HLP WITH ImageMagick",
"reset-configuration-": "RLY SCREW CFG?",
"brightness-": "BLAK OR WHITE>",
"text-printing-mode": "MEOW MEOW",
"internal-error-please-see-terminal": "ERR IN CONSOL PLZ SI",
"control-printer-thermal-strength": "HOW MUCH INK",
"strength-": "HOW MUCH INK>",
"or-drag-file-to-below": "THRW PIC OR MEOW HERE",
"reset": "STRT OVR",
"cat-face-toward": "WANT KITTE FACE NOT BUTT",
"quality-": "CAREFUL STEP>",
"print-quality": "HOW CAREFUL R STEPS",
"show-more-options": "LOT CFGS HERE",
"text-font": "PAW SHAP",
"text-size": "BIG OR SMAL",
"enter-text": "MEOW HERE",
"wrap-words-by-spaces": "NO HAF A WORD",
"minor-tweaks": "LITTL TRIKS",
"serif": "SHARP PAW",
"sans-serif": "SOFT PAW",
"monospace": "H4CKY PAW",
"rotate-image": "ROLL PIC"
}

View File

@ -8,14 +8,12 @@
"canvas": "画布",
"document": "文档",
"insert-picture": "插入图片",
"insert-text": "输入文本",
"help": "帮助",
"javascript-license-information": "JavaScript 许可证信息",
"settings": "设置",
"image": "图像",
"threshold-": "阈值:",
"transmission-speed-": "传输速度:",
"low": "低",
"moderate": "适中",
"high": "高",
"transparent-as-white": "透明为白色",
"misc": "杂项",
"system": "系统",
@ -45,7 +43,6 @@
"print-to-cat-printer": "打印到猫咪打印机。",
"supported-models-": "支持的型号:",
"path-to-input-file-dash-for-stdin": "输入文件的位置。使用 '-' 作为标准输入",
"dump-the-traffic": "转储数据",
"please-install-pyobjc-via-pip": "请从 pip 安装 `pyobjc`",
"please-install-bleak-via-pip": "请从 pip 安装 `bleak`",
"folder-printer_lib-is-incomplete-or-missing-please-check": "文件夹 `printer_lib` 不完整或丢失,请检查",
@ -85,7 +82,7 @@
"contributors": "贡献者",
"developer": "开发者",
"translator": "翻译",
"all-testers-and-users": "所有测试及正式用户",
"all-users-and-developers": "每位用户及开发者",
"everyone-is-awesome": "每个人都是好样的!",
"license": "许可证",
"exiting": "退出中……",
@ -122,5 +119,14 @@
"cat-face-toward": "猫脸朝上",
"quality-": "质量:",
"print-quality": "打印质量",
"show-more-options": "显示更多选项"
"show-more-options": "显示更多选项",
"text-font": "字体",
"text-size": "大小",
"enter-text": "在此处输入文本",
"wrap-words-by-spaces": "空格处换行(不建议用于汉语)",
"minor-tweaks": "小优化",
"serif": "衬线字体",
"sans-serif": "无衬线字体",
"monospace": "等宽字体",
"rotate-image": "旋转图像"
}

View File

@ -36,6 +36,9 @@ body {
margin: 1em 0;
user-select: none;
}
* {
box-sizing: border-box;
}
body.android .hide-on-android {
display: none;
}
@ -90,9 +93,12 @@ input[type="number"], input[type="text"] {
cursor: text;
}
button:hover {
/*
margin: 0;
padding: var(--span) var(--span-double);
min-width: calc(6em + var(--span-double));
*/
transform: scale(1.1);
}
button:active {
box-shadow: 0 0 var(--span) inset var(--fore-color);
@ -103,7 +109,6 @@ button:active {
}
.label-span-input {
display: table-row;
box-sizing: border-box;
margin: var(--span-half) var(--span);
}
.label-span-input>:nth-child(1) {
@ -119,7 +124,6 @@ button:active {
}
.label-input-span {
display: block;
box-sizing: border-box;
margin: var(--span-half) var(--span);
}
.label-input-span>:nth-child(1) {
@ -173,11 +177,14 @@ main, header, footer {
max-width: 45em;
margin: 1em auto;
display: flex;
justify-content: space-between;
flex-direction: row;
overflow-x: hidden;
}
.canvas-side {
flex-grow: 0;
min-width: calc(var(--paper-width) + var(--border-double) + var(--span-double));
width: var(--paper-width);
margin: var(--span) calc((50% - var(--paper-width)) / 2);
}
.canvas-side>* {
text-align: center;
@ -188,8 +195,9 @@ main, header, footer {
top: 0;
height: 100%;
/* overflow: auto; */
margin: var(--span);
min-width: 12em;
margin: var(--span) 0;
min-width: 20em;
/* width: 50%; */
}
.menu-side>.menu {
border: var(--border) solid var(--fore-color);
@ -216,6 +224,7 @@ main, header, footer {
margin: 0;
}
.compact-button:hover {
transform: unset;
margin: 0;
padding: 0 var(--span) calc(var(--span-double));
min-width: 6em;
@ -249,9 +258,9 @@ main, header, footer {
position: absolute;
z-index: 2;
display: inline-block;
width: calc(var(--paper-width) + var(--border-double));
margin: var(--span);
margin-bottom: 0;
/* width: calc(var(--paper-width) + var(--border-double)); */
width: var(--paper-width);
margin-top: 0.5em;
}
p {
margin: var(--span) 0;
@ -263,7 +272,6 @@ p {
.panel.active {
height: calc(var(--panel-height) - var(--compact-menu-height));
padding: var(--span-double) var(--span);
box-sizing: border-box;
overflow-y: auto;
}
.panel.sub.active {
@ -313,6 +321,11 @@ table#jslicense-labels1 {
}
table#jslicense-labels1 td {
padding: var(--span-half) var(--span);
white-space: nowrap;
}
table#jslicense-labels1 td:nth-child(4) {
white-space: normal;
width: 50vw;
}
*:target {
background-color: var(--target-color);
@ -335,7 +348,7 @@ hr {
}
iframe#frame {
width: 100%;
height: 12em;
height: 60vh;
border: none;
background-color: transparent;
}
@ -355,33 +368,32 @@ iframe#frame {
#dialog {
position: fixed;
width: 100%;
top: 16%;
height: 100%;
top: 0;
text-align: center;
z-index: 2;
opacity: 2;
opacity: 1;
transition: opacity var(--anim-time) var(--curve);
}
#dialog>.content {
max-width: 100%;
width: 40em;
margin: auto;
box-sizing: border-box;
width: 42em;
max-height: 80vh;
margin: 12vh auto;
border: var(--border) solid var(--fore-color);
transition: transform var(--anim-time) var(--curve);
transform-origin: center 33%;
}
#dialog.hidden {
opacity: 0;
height: unset;
}
#dialog.hidden>.content {
transform: scaleY(0);
}
#dialog-content {
/* height: 12em; */
max-height: 640px;
max-height: 60vh;
margin: auto;
padding: var(--span);
padding: var(--span-double);
padding-bottom: 0;
display: flex;
flex-direction: column;
@ -405,12 +417,12 @@ iframe#frame {
flex-grow: 1;
}
#select-language {
width: calc(100% - var(--span-double));
/* width: calc(100% - var(--span-double)); */
width: 12em;
height: 8em;
border: var(--border) solid var(--fore-color);
padding: var(--span);
margin: 0 var(--span);
box-sizing: border-box;
}
#select-language option {
cursor: pointer;
@ -438,7 +450,7 @@ iframe#frame {
flex-direction: column;
justify-content: center;
text-align: center;
z-index: 1;
z-index: 3;
opacity: 1;
transition: opacity var(--anim-time) var(--curve);
}
@ -467,7 +479,7 @@ iframe#frame {
height: var(--font-size);
margin: var(--font-size);
background-color: var(--fore-color);
border-radius: calc(var(--font-size) / 2);
border-radius: var(--font-size);
animation: jump 1s ease 0s infinite;
}
#loading-screen>.dots>span:nth-child(1) { animation-delay: 0s; }
@ -519,6 +531,7 @@ a {
#title { display: none; }
.canvas-side {
min-width: unset;
margin: 0;
width: 100%;
overflow-x: hidden;
overflow-y: auto;
@ -530,7 +543,7 @@ a {
.canvas-side>.buttons,
.menu-side>.buttons {
position: sticky;
bottom: var(--compact-menu-height);
bottom: var(--span);
width: 100%;
z-index: 1;
}
@ -570,6 +583,9 @@ a {
width: 100%;
z-index: 0;
}
#dialog-content {
padding: var(--span) 0;
}
.blank {
height: var(--compact-menu-height);
}
@ -656,19 +672,22 @@ body.high-contrast #notice * { border: var(--border) dashed var(--fore-color); }
body.high-contrast a:any-link { color: #00f; }
body.high-contrast #control-overlay { background-color: var(--shade); }
/*
@font-face {
font-family: 'Unifont';
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: pre;
height: calc(60vh - 8em);
width: var(--paper-width);
overflow: hidden auto;
white-space: break-spaces;
resize:none;
resize: none;
padding-top: .65ex;
line-height: 1.25;
border: var(--border) solid currentColor;
}
.text-align-container span {
@ -703,7 +722,7 @@ body.high-contrast #control-overlay { background-color: var(--shade); }
.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; }
input[name="wrap-words-by-spaces"] { margin-right: 5px; }
#text-settings {
display: flex;
flex-direction: column;
@ -715,5 +734,5 @@ input[name="wrap-by-space"] { margin-right: 5px; }
display: flex;
justify-content: center;
}
label[name="wrap-by-space-label"] { padding-right: 2%; }
label[name="wrap-words-by-spaces-label"] { padding-right: 2%; }
#text-font { height: 100%; margin: 0px; }

View File

@ -274,32 +274,38 @@ class CanvasController {
canvas;
imageUrl;
algorithm;
_height;
_threshold;
_energy;
_thresholdRange;
_energyRange;
algoElements;
textFont;
textSize;
textArea;
transparentAsWhite;
previewData;
rotate;
#height;
#threshold;
#energy;
#thresholdRange;
#energyRange;
#rotateCheck;
static defaultHeight = 384;
static defaultThreshold = 256 / 3;
get threshold() {
return this._threshold;
return this.#threshold;
}
set threshold(value) {
this._threshold = this._thresholdRange.value = value;
this.#threshold = this.#thresholdRange.value = value;
}
get energy() {
return this._energy;
return this.#energy;
}
set energy(value) {
this._energy = this._energyRange.value = value;
this.#energy = this.#energyRange.value = value;
}
get height() {
return this._height;
return this.#height;
}
set height(value) {
this.canvas.height = this.preview.height = this._height = value;
this.canvas.height = this.preview.height = this.#height = value;
}
constructor() {
this.preview = document.getElementById('preview');
@ -308,13 +314,13 @@ class CanvasController {
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.wrapBySpace = document.querySelector('input[name="wrap-words-by-spaces"]');
this.height = CanvasController.defaultHeight;
this._thresholdRange = document.querySelector('[name="threshold"]');
this._energyRange = document.querySelector('[name="energy"]');
this.#thresholdRange = document.querySelector('[name="threshold"]');
this.#energyRange = document.querySelector('[name="energy"]');
this.imageUrl = null;
this.textAlign = "left";
this.textAlign = "left";
this.rotate = false;
for (let elem of document.querySelectorAll("input[name=text-align]")){
if (elem.checked) { this.textAlign = elem.value; }
@ -336,7 +342,7 @@ class CanvasController {
};
file_reader.readAsText(event.dataTransfer.files[0]);
} else {
this.insertPicture(event.dataTransfer.files);
this.useFiles(event.dataTransfer.files);
}
return prevent_default(event);
});
@ -345,8 +351,10 @@ class CanvasController {
this.textArea.style["font-family"] = this.textFont.value;
this.textArea.style["word-break"] = this.wrapBySpace.checked ? "break-word" : "break-all";
this.algoElements = document.querySelectorAll('input[name="algo"]');
putEvent('input[name="algo"]', 'change', (event) => this.useAlgorithm(event.currentTarget.value), this);
putEvent('#insert-picture' , 'click', () => this.insertPicture(), this);
putEvent('#insert-picture' , 'click', () => this.useFiles(), 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);
@ -354,11 +362,13 @@ class CanvasController {
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('input[name="wrap-words-by-spaces"]' , 'change', () => this.textArea.style["word-break"] = this.wrapBySpace.checked ? "break-word" : "break-all");
putEvent('#button-preview' , 'click', this.activatePreview , this);
putEvent('#button-reset' , 'click', this.reset , this);
putEvent('#canvas-expand' , 'click', this.expand , this);
putEvent('#canvas-crop' , 'click', this.crop , this);
putEvent('[name="rotate"]' , 'change', e => this.setRotate(e.currentTarget.checked), this);
this.#rotateCheck = document.querySelector('[name="rotate"]');
putEvent('[name="threshold"]', 'change', (event) => {
this.threshold = parseInt(event.currentTarget.value);
@ -374,17 +384,27 @@ class CanvasController {
}, this);
}
useAlgorithm(name) {
for (let e of this.algoElements) {
e.checked = e.value === name;
}
this.algorithm = name;
this.threshold = CanvasController.defaultThreshold;
this._thresholdRange.dispatchEvent(new Event('change'));
this.#thresholdRange.dispatchEvent(new Event('change'));
this.energy = name == 'algo-direct' ? 96 : 64;
this._energyRange.dispatchEvent(new Event('change'));
this.#energyRange.dispatchEvent(new Event('change'));
this.activatePreview();
}
expand(length = CanvasController.defaultHeight) {
this.height += length;
}
crop() {}
crop() {
// STUB
}
setRotate(value) {
this.#rotateCheck.checked = value;
this.rotate = value;
if (this.imageUrl !== null) this.putImage(this.imageUrl);
}
visualEnergy(amount) {
let rate = amount / 256;
let brightness = Math.max(1.6 - rate * 1.5, 0.75),
@ -437,46 +457,68 @@ class CanvasController {
this.previewData = mono_data;
context_p.putImageData(new_data, 0, 0);
}
insertPicture(files) {
const put_image = (url) => {
this.imageUrl = url;
let img = document.createElement('img');
img.src = url;
hidden_area.appendChild(img);
img.addEventListener('load', () => {
let canvas = this.canvas;
let rate = img.height / img.width;
this.height = canvas.width * rate;
let context = canvas.getContext('2d');
context.drawImage(img, 0, 0, canvas.width, canvas.height);
this.crop();
this.activatePreview();
hint('#button-print');
});
}
let use_files = (files) => {
putImage(url) {
let img = document.getElementById('img');
img.src = url;
img.addEventListener('load', () => {
let canvas = this.canvas;
let ctx = canvas.getContext('2d');
if (this.rotate) {
let intermediate_canvas = document.createElement('canvas');
/**
* w h
* +------+ +---+
* h | | | | w
* +------+ | | intermediate_canvas
* canvas +---+
*/
let w = canvas.width;
let h = this.height = Math.floor(canvas.width * img.width / img.height);
intermediate_canvas.width = h;
intermediate_canvas.height = w;
let i_ctx = intermediate_canvas.getContext('2d');
i_ctx.drawImage(img, 0, 0, h, w);
let i_data = i_ctx.getImageData(0, 0, h, w);
let data = ctx.createImageData(w, h);
for (let j = 0; j < h; j++) {
for (let i = 0; i < w; i++) {
for (let d = 0; d < 4; d++)
data.data[(i * 4 + d) + (j * w * 4)] = i_data.data[(j * 4 + d) + ((w - i) * 4 * h)];
}
}
ctx.putImageData(data, 0, 0);
} else {
this.height = Math.floor(canvas.width * img.height / img.width);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
}
this.crop();
this.activatePreview();
hint('#button-print');
});
}
useFiles(files) {
const use_files = (files) => {
let file = files[0];
if (!file) return;
let url = URL.createObjectURL(file);
put_image(url);
this.imageUrl = url;
this.putImage(url);
this.controls.classList.add('hidden');
}
if (files) use_files(files);
else {
document.querySelectorAll('.dummy').forEach(e => e.remove());
let input = document.createElement('input');
input.classList.add('dummy');
input.type = 'file';
input.accept = 'image/*';
input.addEventListener('change', () => {
use_files(input.files);
});
hidden_area.appendChild(input);
input.click();
}
if (files) { use_files(files); return; }
document.querySelectorAll('.dummy').forEach(e => e.remove());
let input = document.createElement('input');
input.classList.add('dummy');
input.type = 'file';
input.accept = 'image/*';
input.addEventListener('change', () => {
use_files(input.files);
});
hidden_area.appendChild(input);
input.click();
}
insertText(text) {
if (text == null || text.trim() == "") { return; }
if (text == null || text.trim() == "") { return false; }
const text_size = parseInt(this.textSize.value);
const text_font = this.textFont.value;
@ -550,12 +592,12 @@ class CanvasController {
this.crop();
this.textAlgorithm.checked = true;
this.textAlgorithm.dispatchEvent(new Event("change"));
this.imageUrl = this.canvas.toDataURL();
this.activatePreview();
this.useAlgorithm('algo-direct');
this.threshold = 16;
this.setRotate(false);
this.putImage(this.imageUrl = this.canvas.toDataURL());
this.controls.classList.add('hidden');
hint('#button-print');
@ -597,7 +639,7 @@ function applyI18nToDom(doc) {
async function initI18n(current_language) {
if (typeof i18n === 'undefined') return;
/** @type {HTMLSelectElement} */
let language_options = document.getElementById('select-language');
let language_select = document.getElementById('select-language');
/** @type {{ [code: string]: string }} */
let list = await fetch('/lang/list.json').then(r => r.json());
let use_language = async (value) => {
@ -605,6 +647,7 @@ async function initI18n(current_language) {
i18n.add(value, await fetch(`/lang/${value}.json`).then(r => r.json()), true);
applyI18nToDom();
}
language_select.addEventListener('change', () => use_language(language_select.value));
for (let code in list) {
let option = document.createElement('option');
option.value = code;
@ -614,22 +657,22 @@ async function initI18n(current_language) {
let option = event.currentTarget;
let value = option.value;
option.selected = true;
language_options.selectedIndex = option.index;
language_select.selectedIndex = option.index;
use_language(value);
Notice.note('welcome');
});
language_options.appendChild(option);
language_select.appendChild(option);
}
if (!navigator.languages) {
if (!navigator.language) return;
else navigator.languages = [navigator.language, 'en-US'];
}
if (current_language) {
for (let option of language_options.children)
for (let option of language_select.children)
if (option.value === current_language)
option.click();
} else for (let code of navigator.languages)
if (list[code]) for (let option of language_options.children)
if (list[code]) for (let option of language_select.children)
if (option.value === code) {
option.setAttribute('data-default', '');
if (!current_language) option.click();
@ -697,22 +740,22 @@ class Main {
await initI18n(this.settings['language']);
this.canvasController = new CanvasController();
putEvent('#button-exit', 'click', () => this.exit(false), this);
putEvent('#button-exit', 'contextmenu',
(event) => (event.preventDefault(), this.exit(true)), this);
putEvent('#button-print', 'click', this.print, this);
putEvent('#device-refresh', 'click', this.searchDevices, this);
putEvent('#button-exit' , 'click', () => this.exit(false), this);
putEvent('#button-print' , 'click', this.print, this);
putEvent('#device-refresh' , 'click', this.searchDevices, this);
putEvent('#button-exit' , 'contextmenu', (event) => (event.preventDefault(), this.exit(true)), this);
putEvent('#set-accessibility', 'click', () => Dialog.alert('#accessibility'));
this.attachSetter('#device-options', 'input', 'printer',
(value) => callApi('/connect', { device: value })
);
putEvent('a[target="frame"]', 'click', () => Dialog.alert('#frame'));
this.attachSetter('[name="scan-time"]', 'change', 'scan_timeout');
this.attachSetter('input[name="algo"]', 'change', 'mono_algorithm',
this.attachSetter('[name="scan-time"]' , 'change', 'scan_timeout');
this.attachSetter('[name="rotate"]' , 'change', 'rotate');
this.attachSetter('input[name="algo"]' , 'change', 'mono_algorithm',
(value) => this.settings['text_mode'] = (value === 'algo-direct')
);
this.attachSetter('[name="transparent-as-white"]', 'change', 'transparent_as_white');
this.attachSetter('[name="wrap-by-space"]', 'change', 'wrap_by_space');
this.attachSetter('[name="wrap-words-by-spaces"]', 'change', 'wrap_by_space');
this.attachSetter('[name="dry-run"]', 'change', 'dry_run',
(checked) => checked && Notice.note('dry-run-test-print-process-only')
);
@ -731,16 +774,13 @@ class Main {
this.attachSetter('[name="high-contrast"]', 'change', 'high_contrast',
(checked) => apply_class('high-contrast', checked)
);
this.attachSetter('[name="threshold"]', 'change', 'threshold');
this.attachSetter('[name="energy"]', 'change', 'energy');
this.attachSetter('[name="quality"]', 'change', 'quality');
this.attachSetter('[name="flip"]', 'change', 'flip');
// this.attachSetter('[name="flip-h"]', 'change', 'flip_h');
// this.attachSetter('[name="flip-v"]', 'change', 'flip_v');
// this.attachSetter('[name="dump"]', 'change', 'dump');
this.attachSetter('[name="threshold"]' , 'change', 'threshold');
this.attachSetter('[name="energy"]' , 'change', 'energy');
this.attachSetter('[name="quality"]' , 'change', 'quality');
this.attachSetter('[name="flip"]' , 'change', 'flip');
await this.activateConfig();
// one exception
this.attachSetter('#select-language option', 'click', 'language');
this.attachSetter('#select-language', 'change', 'language');
if (this.settings['is_android']) {
document.body.classList.add('android');
@ -856,24 +896,29 @@ class Main {
Notice.note('you-can-close-this-page-manually');
}
/** @param {Response} response */
async bluetoothProblemHandler(response) {
async handleBluetoothProblem(response) {
// Not complete yet, it's different across other platforms
let error_details = await response.json();
if (
error_details.name === 'org.bluez.Error.NotReady' ||
error_details.name === 'org.freedesktop.DBus.Error.UnknownObject' ||
error_details.name === 'org.bluez.Error.NotReady' ||
error_details.details.includes('not turned on') ||
error_details.details.includes('WinError -2147020577')
) Notice.warn('please-enable-bluetooth');
else if (
error_details.details.includes('no running event loop')
) Notice.error('internal-error-please-see-terminal');
else throw new Error('Unknown Bluetooth Problem');
else
ErrorHandler.report(
new Error('API Failure'),
JSON.stringify(await response.json(), undefined, 4)
)
return null;
}
async searchDevices() {
Notice.wait('scanning-for-devices');
let search_result = await callApi('/devices', null, this.bluetoothProblemHandler);
let search_result = await callApi('/devices', null, this.handleBluetoothProblem);
if (search_result === null) return false;
let devices = search_result.devices;
Array.from(this.deviceOptions.children).forEach(e => e.remove());
@ -901,23 +946,18 @@ class Main {
method: 'POST',
body: this.canvasController.makePbm()
}).then(async (response) => {
if (response.ok) Notice.note('finished')
else {
let json = response.json();
response.json = () => json;
let error_data = await response.json();
if (/address.+not found|Not connected/.test(error_data.details) ||
error_data.name === 'EOFError') {
if (await this.searchDevices()) this.print();
else Notice.error('please-check-if-the-printer-is-down');
} else if (error_data.name === 'no-available-devices-found')
Notice.warn('no-available-devices-found');
else
ErrorHandler.report(
new Error('API Failure'),
JSON.stringify(await response.json(), undefined, 4)
)
}
if (response.ok) { Notice.note('finished'); return; }
let json = response.json();
response.json = () => json;
let error_data = await response.json();
if (/address.+not found|Not connected/.test(error_data.details) ||
error_data.name === 'EOFError') {
if (await this.searchDevices()) this.print();
else Notice.error('please-check-if-the-printer-is-down');
} else if (error_data.name === 'no-available-devices-found')
Notice.warn('no-available-devices-found');
else
this.handleBluetoothProblem(response);
});
}
}