let's restart from scratch

This commit is contained in:
NaitLee 2023-09-20 10:29:45 +08:00
parent 0f2a9a7fc9
commit 72abb40be8
87 changed files with 113 additions and 7492 deletions

47
.gitignore vendored
View File

@ -1,47 +0,0 @@
# python cache
__pycache__
# Compatibility version of script, for old-old webView,
# generated by typescript tsc
www/main.comp.js
# https://www.npmjs.com/package/vconsole
www/vconsole.js
# https://github.com/delight-im/Android-AdvancedWebView
build-android/advancedwebview
# cd wasm && npm install
wasm/node_modules
# python bytecode
*.pyc
# releases
build-android/dist
*.apk
*.apk.*
cat-printer*.zip
cat-printer-sha256-*.txt
# bleak, the bare pip package as a folder
build-common/bleak
# python embeddable package, historically with bleak_winrt inside
build-common/python-win32*
build-common/python-w32*
# bleak_winrt is now outside python-w32
build-common/bleak_winrt
# local pf2
pf2
pf2.zip
*.pf2
# archlinux package build files
cat-printer-git
pkg
*.pkg.tar.zst
# dev config
config.json
# dev backup
*.bak
# test files
*.dump
*.pbm
test.png
# some other junk
.directory
thumbs.db
thumbs.db:encryptable
env/

View File

@ -1,21 +0,0 @@
# Apply this pylint-rc for better experience
# Configurable in VSCode settings `python.linting.pylintArgs`
# $ pylint --rcfile=.pylint-rc
[MASTER]
jobs=4
[BASIC]
class-const-naming-style=PascalCase
const-naming-style=PascalCase
[MESSAGES CONTROL]
disable=broad-except,
global-statement,
fixme, too-few-public-methods,
import-outside-toplevel
[BASIC]
good-names=i, j, k, ex, x, y, _, e, b, u, s,
Run, do_GET, do_POST, do_HEAD, do_PUT

View File

@ -1,8 +0,0 @@
#!/bin/sh
cd zh-conv
echo "Convert Chinese Language with OpenCC"
./0-convert.sh
cd ../www
echo "tsc bundle scripts..."
./0-transpile.sh
cd ..

View File

@ -1,20 +0,0 @@
#!/bin/sh
# For ultimate laziness!
rm -rf cat-printer-*.zip cat-printer-*.apk* cat-printer-sha256-*.txt
echo -n 'Version tag: '
read version
echo -n $version > version
echo -n 'Key file for signing apk: '
read signkey
echo 'Building common editions...'
cd ./build-common/
./0-bundle-all.sh
echo 'Building for Android...'
cd ../build-android/
./3-formal-build.sh > /dev/null
echo 'Signing apk with keyfile...'
./4-sign.sh $signkey
cd ../
echo 'SHA256 Hash...'
sha256sum cat-printer-* > cat-printer-sha256-$version.txt
echo 'Complete!'

View File

@ -1,47 +0,0 @@
# Maintainer :
pkgname=cat-printer-git
pkgver=r153.85cb5a8
pkgrel=1
pkgdesc="A project that provides support to some Bluetooth Cat Printer models, on many platforms!"
arch=('any')
url="https://github.com/NaitLee/Cat-Printer"
license=('GPL3')
depends=('python' 'bluez' 'python-bleak')
optdepends=('bluez-utils' 'ghostscript' 'imagemagick')
makedepends=('git' 'unzip')
provides=("cat-printer=${pkgver}")
source=("$pkgname::git+https://github.com/NaitLee/Cat-Printer.git")
md5sums=('SKIP')
sha256sums=('SKIP')
options=(!strip emptydirs)
pkgver() {
cd "$pkgname"
printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
}
build() {
cd "$pkgname/build-common"
for i in $(find | grep -E '.*\.pyc'); do rm $i; done
python3 bundle.py -b "$pkgver"
}
package() {
mkdir -p "$pkgdir/usr/bin"
mkdir -p "$pkgdir/usr/share/"
mkdir -p "$pkgdir/usr/lib/systemd/system/"
unzip "$srcdir/cat-printer-git/cat-printer-bare-$pkgver.zip" -d "$pkgdir/usr/share/"
ln -s /usr/share/grub/unicode.pf2 "$pkgdir/usr/share/cat-printer/font.pf2"
install -m644 "$srcdir/cat-printer-git/systemd/cat-printer.service" "$pkgdir/usr/lib/systemd/system/"
cat <<EOF > "$pkgdir/usr/bin/cat-printer"
#!/bin/sh
cd /usr/share/cat-printer
python3 printer.py "\$@"
EOF
chmod +x "$pkgdir/usr/bin/cat-printer"
cat <<EOF > "$pkgdir/usr/bin/cat-printer-server"
#!/bin/sh
cd /usr/share/cat-printer
python3 server.py "\$@"
EOF
chmod +x "$pkgdir/usr/bin/cat-printer-server"
chmod +x "$pkgdir/usr/bin/cat-printer"
}

28
TODO
View File

@ -1,28 +0,0 @@
Note: not ordered. do whatever I/you want
+ Cookbook of basic things
+ Write good help/manual
+ Some sort of Wiki
+ Make error notice short while let users see detailed help/manual for what-to-do
+ Even better CUPS/IPP support
+ Even better frontend, language-friendly text printing
+ Tcl/Tk frontend. More in dev-diary.txt, July 7th 2022.
+ Try to implement enough without more dependencies
+ More funny "languages" (now there's lolcat; I think the next is 文言)
+ Arch Linux package / AUR, package for other distros
+ Service for other init systems (a systemd unit file is there)
+ ...
? Optimize PF2 text printing? It seems a bit slow (in bit processing)
? Fix feeding command for MX05/MX06
? Use something else as server part of backend? This can boost things up, and build some (essential) image manipulation in, quicker. And strip some way-too-big Python libs away (for smaller Windows/Android dist)
? Built-in PostScript (Even if very basic)
? Data compression for GB03 alike. Optional
? Plugin, for including community features (that involves usefulness but also bloatness)
It's usually messy. Try forking in your own way, at the moment.
? Process picture with WebAssembly? (Web frontend only)
Tried, Not as efficient as pure JavaScript
? Put Android APP on F-Droid? But it needs automatic build system...
Android guys can help this!
? ... Or put to APKPure? But make frontend well before doing all of these

View File

@ -1,22 +0,0 @@
--private ..
--dist_name "cat-printer"
--package "io.github.naitlee.catprinter"
--name "Cat Printer"
--icon icon.png
--bootstrap webview
--window
--blacklist-requirements sqlite3,openssl
--port 8095
--arch arm64-v8a
--blacklist "blacklist.txt"
--presplash blank.png
--presplash-color black
--add-source "advancedwebview"
--manifest-orientation user
--android_api 30
--permission BLUETOOTH
--permission BLUETOOTH_SCAN
--permission BLUETOOTH_CONNECT
--permission BLUETOOTH_ADMIN
--permission ACCESS_FINE_LOCATION
--permission ACCESS_COARSE_LOCATION

View File

@ -1,4 +0,0 @@
#!/bin/sh
version=`cat ../version`
p4a apk --requirements "`cat build-deps.txt`" --version "$version" $@

View File

@ -1,2 +0,0 @@
#!/bin/sh
adb install cat-printer*.apk

View File

@ -1,2 +0,0 @@
#!/bin/sh
p4a clean_builds && p4a clean_dists

View File

@ -1,7 +0,0 @@
#!/bin/sh
version=`cat ../version`
rm -rf "dist"
unzip -q "../cat-printer-bare-$version.zip"
mv "cat-printer" "dist"
p4a apk --version="$version" --requirements="`cat build-deps.txt`" --release $@

View File

@ -1,13 +0,0 @@
#!/bin/sh
version=`cat ../version`
unsigned_apk=cat-printer-release-unsigned-$version.apk
signed_apk=cat-printer-android-$version.apk
if {
$ANDROIDSDK/build-tools/*/zipalign 4 $unsigned_apk $signed_apk &&
$ANDROIDSDK/build-tools/*/apksigner sign --ks $1 $signed_apk;
}; then
echo "Complete! Moving APK..."
mv $signed_apk $signed_apk.idsig ../
rm *.apk
fi

View File

@ -1,3 +0,0 @@
#!/bin/sh
./0-build-android.sh
./1-adb-install.sh

View File

@ -1,2 +0,0 @@
#!/bin/sh
adb logcat | grep -E 'python|chromium'

View File

@ -1,8 +0,0 @@
# Build for Android
The build environment shall be setup manually, since its a bit complex, and Im not so familiar with Android development.
See [Manual Steps](./manual-steps.md) to get started.
Android enthusiasts may help with automated build procedure, then for example F-Droid release would be possible.

View File

@ -1,116 +0,0 @@
# dev
.vscode
.git
.gitignore
.pylintrc
?-*.sh
# symlinks
font.pf2
dump.pbm
test.png
# junk
__pycache__
.directory
thumbs.db
thumbs.db:encryptable
# other dist
cat-printer-windows-*.zip
cat-printer-pure-*.zip
cat-printer-bare-*.zip
build*/*
# prevent user to include invalid extensions
*.apk
*.aab
*.apks
*.pxd
# eggs
*.egg-info
# unit test
unittest/*
# python config
config/makesetup
# unused kivy files (platform specific)
kivy/input/providers/wm_*
kivy/input/providers/mactouch*
kivy/input/providers/probesysfs*
kivy/input/providers/mtdev*
kivy/input/providers/hidinput*
kivy/core/camera/camera_videocapture*
kivy/core/spelling/*osx*
kivy/core/video/video_pyglet*
kivy/tools
kivy/tests/*
kivy/*/*.h
kivy/*/*.pxi
# unused encodings
lib-dynload/*codec*
encodings/cp*.pyo
encodings/tis*
encodings/shift*
encodings/bz2*
encodings/iso*
encodings/undefined*
encodings/johab*
encodings/p*
encodings/m*
encodings/euc*
encodings/k*
encodings/unicode_internal*
encodings/quo*
encodings/gb*
encodings/big5*
encodings/hp*
encodings/hz*
# unused python modules
bsddb/*
wsgiref/*
hotshot/*
pydoc_data/*
tty.pyo
anydbm.pyo
nturl2path.pyo
LICENCE.txt
macurl2path.pyo
dummy_threading.pyo
audiodev.pyo
antigravity.pyo
dumbdbm.pyo
sndhdr.pyo
__phello__.foo.pyo
sunaudio.pyo
os2emxpath.pyo
multiprocessing/dummy*
# unused binaries python modules
lib-dynload/termios.so
lib-dynload/_lsprof.so
lib-dynload/*audioop.so
lib-dynload/_hotshot.so
lib-dynload/_heapq.so
lib-dynload/_json.so
lib-dynload/grp.so
lib-dynload/resource.so
lib-dynload/pyexpat.so
lib-dynload/_ctypes_test.so
lib-dynload/_testcapi.so
# odd files
plat-linux3/regen
#>sqlite3
# conditionnal include depending if some recipes are included or not.
sqlite3/*
lib-dynload/_sqlite3.so
#<sqlite3

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

View File

@ -1 +0,0 @@
async_timeout,typing-extensions,android,pyjnius,bleak

View File

@ -1,32 +0,0 @@
''' Some casual code to fix those alias files
in Android NDK llvm bin to symlinks instead
'''
import os
import sys
MAX_LENGTH = 256
ndk_path = sys.argv[1] if len(sys.argv) > 1 else input('Android NDK path: ')
bin_path = os.path.join(ndk_path, 'toolchains/llvm/prebuilt/linux-x86_64/bin/')
workdir = os.getcwd()
os.chdir(bin_path)
try:
for path in os.listdir():
# with this encoding it won't error when reading binary
file = open(path, 'r', encoding='iso8859-1')
data = file.read(MAX_LENGTH).strip()
file.close()
# inside the alias file is the filename that should be executed
# let's see if there is one
if os.path.isfile(data):
print('Will fix %s -> %s' % (path, data))
#os.remove(path)
os.rename(path, path + '.alias')
os.symlink(data, path)
finally:
os.chdir(workdir)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -1,379 +0,0 @@
# Android Build Env Manual Setup
Expecting to cost about half a day.
Worthy to work on! This gives possibility to everything about Android in your mind!
See [#Troubleshooting](#troubleshooting) for some problems you may meet.
Note: not being confirmed to 100% work yet. Be the first bird to try! Or bookmark this, arrange your time & come back later.
## Prepare
First, think about what will be your build environment. I choose to use a Docker container with a newer GNU/Linux distribution.
<details>
<summary>Expand Notes</summary>
Notes:
1. In theory you can just use your existing system, if you dont afraid of messing it up.
2. Rolling distribution have newer packages offered.
It may give great experience in daily use, but will heavily bloat the update if many development packages are installed alltogether.
3. By operating in an isolated environment, a mess taking place inside wont affect the host.
4. Good candidates are: Arch, Artix, OpenSUSE Leap, and their neighbors.
5. Some say Docker isnt intended to be “stateful”. But nothing is better in my mere knowledge.
</details>
Before creating the build environment, lets prepare requirements of the outside.
### Space
Leave enough space for the isolated environment.
For Docker, maybe keep 3 GiB free in root directory.
Locate somewhere with at least 8 GiB free space,
Make 3 folders inside:
- `git-repo`, for git clones
- `android`, for Android SDK
- `p4a`, for manipulating python-for-android intermediate data
### Git Repositories
```bash
# Define your build directories
DIR_BUILD="/mnt/data/@"
DIR_GIT="$DIR_BUILD/git-repo/"
# Make sure both directories exist
mkdir -p $DIR_BUILD
mkdir -p $DIR_GIT
cd $DIR_GIT
# Cat-Printer
git clone https://github.com/NaitLee/Cat-Printer.git
# Bleak, we need some Java code from its source
# git clone https://github.com/hbldh/bleak.git
# Use an older version for being compatible with p4a recipes (setup.py)
wget https://files.pythonhosted.org/packages/e6/b4/e63829826a157d180831a1c5d3720e75d613c1290cb239510d148b906836/bleak-0.19.5.tar.gz
tar -xzf bleak-0.19.5.tar.gz && rm bleak-0.19.5.tar.gz
mv bleak-0.19.5 bleak
# AdvancedWebView, in order to give Android WebView capability to use <input type="file" />
git clone https://github.com/delight-im/Android-AdvancedWebView.git
# Let AdvancedWebView source code be in Cat-Printer building directory
ln -s ../../Android-AdvancedWebView/Source/library/src/main/java Cat-Printer/build-android/advancedwebview
```
### Android SDK
For most cases, following [python-for-android guide](https://python-for-android.readthedocs.io/en/latest/quickstart/#basic-sdk-install) will just work.
But Note: required by newer Gradle, use Android **platform 30** (or above) rather than 27.
After that, continue to [Fix the NDK](#fix-the-ndk).
----
If Google isnt available there, or you prefer manual setup:
- Pick a working mirror. Currently theres [Tencent Cloud](https://mirrors.cloud.tencent.com/AndroidSDK/).
- Pay attention to [required version of Android NDK](https://python-for-android.readthedocs.io/en/latest/quickstart/#basic-sdk-install), this will increase by time. Its currently r25b, but lets use r25c.
- Fetch & extract some archives, as shown in this table:
| Archive file | Top-level dir inside | Target directory |
| ----------------------------------- | ----------------------- | ----------------------------------- |
| `android-ndk-r25c-linux.zip` | `android-ndk-r25c` | `android/android-ndk-r25c` |
| `build-tools_r33-linux.zip` | `android-13` | `android/build-tools/33.0.0` |
| `commandlinetools-linux-8512546_latest.zip` | `cmdline-tools` | `android/cmdline-tools/latest` |
| `platform-30_r03.zip` | `android-11` | `android/platforms/android-30` |
| `platform-tools_r33.0.3-linux.zip` | `platform-tools` | `android/platform-tools` |
For example, you get `build-tools_r33-linux.zip`, see a folder `android-13` inside;
then you create `build-tools/` in `android/`, extract `android-13` there and rename it as `33.0.0`
So after that you will get:
```
android
├── android-ndk-r25c
├── build-tools
│   └── 33.0.0
├── cmdline-tools
│   └── latest
├── platforms
│   └── android-30
└── platform-tools
```
### Fix the NDK
The NDK (particularly, the llvm/clang binary directory) have some files that contain a path to other executable as their data.
System doesnt understand it. Lets replace them as symlinks:
```bash
# you may already have these from p4a guide
ANDROIDSDK="$DIR_BUILD/android"
ANDROIDNDK="$DIR_BUILD/android/android-ndk-r25c"
# feel free to check this script
python3 $DIR_GIT/Cat-Printer/build-android/fix-ndk-execs.py $ANDROIDNDK
```
## Setup
### Environment
```bash
# Install Docker Engine. This is for Arch-based OS
sudo pacman -Syu docker
# (I didnt try Docker Desktop)
```
For China Mainland users, configuring a mirror may be helpful. See https://mirrors.sjtug.sjtu.edu.cn/docs/docker-registry
after that, restart docker service, or reboot.
----
```bash
# lets create the container by first pulling the image
docker pull archlinux:latest
# please, pass previously mentioned directories (or their parent) via -v parameter, we will access them here
# example: `-v /source1/android:/target1/android -v /source2/git-repo:/target2/git-repo`
docker create --name catbuild -v /mnt/data:/mnt/data --tty -i archlinux
# From now on, start the container like this
docker start -i catbuild
```
----
OK, we are now inside the container shell. Set it up:
```bash
# (Optional) use a repository mirror:
# cd /etc/pacman.d; mv mirrorlist mirrorlist.bak; echo 'Server = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch' >mirrorlist
pacman -Syuu jdk-openjdk python3 python-pip git cython zip nano vim tar wget unzip base-devel clang lld libffi
# find python-for-android dependencies here:
# https://python-for-android.readthedocs.io/en/latest/quickstart/#installing-dependencies
# there should be command for Arch/Ubuntu that you can directly run
# (Optional) use a pypi mirror:
# pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install python-for-android cython bleak
```
### Source Code
The most tricky fact is that none of these things work out-of-the-box.
We should glue them up by hand.
```bash
# append target paths and environment variables required by python-for-android (adjust if necessary)
echo '
export DIR_BUILD="/mnt/data/@"
export ANDROIDSDK="$DIR_BUILD/android"
export ANDROIDNDK="$DIR_BUILD/android/android-ndk-r25c"
export ANDROIDAPI="30"
export NDKAPI="21"' >> .bashrc
# reload environment variables
source ~/.bashrc
```
----
```bash
# define shortcut(s). use your target paths!
DIR_GIT="$DIR_BUILD/git-repo/"
# note this involves python version, change as needed
DIR_P4A="/usr/local/lib/python3.10/dist-packages/pythonforandroid/"
# p4a will generate some intermediate data. “expose” this to the host for convenient manipulation.
mkdir -p ~/.local/share/
ln -s $DIR_BUILD/p4a/ ~/.local/share/python-for-android
# give p4a the bleak recipe. fortunately, p4a will resolve this symlink
ln -s $DIR_GIT/bleak/bleak/backends/p4android/recipes/bleak $DIR_P4A/recipes/bleak
```
----
At this point, do some code patch.
AdvancedWebView have a deprecated function override that fails the compile. Remove it.
```bash
cd $DIR_GIT/Android-AdvancedWebView/Source/library/src/main/java/im/delight/android/webview/
# any editor is okay. you can do it at host side with graphical editor.
nano AdvancedWebView.java
# search for `public void onUnhandledInputEvent`, remove (or comment out) the *entire function body*
```
Modify p4a webview bootstrap to use AdvancedWebView instead
```bash
# copy source file to somewhere easy to access
cd $DIR_GIT
cp $DIR_P4A/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java ./
# some sed script doing the dirty work
sed -i 's/import android.webkit.WebView;/import im.delight.android.webview.AdvancedWebView;/' PythonActivity.java
sed -i -r 's/\bWebView\b/AdvancedWebView/g' PythonActivity.java
```
Not the end yet —
You really want to use a graphical editor now, except if you enjoy vim or emacs...
- (At host side) Open the file with an editor
- Search & remove these two `@Override` decorators:
```java
//@Override
public boolean shouldOverrideUrlLoading
//@Override
public void onPageFinished
```
- Add this after the line `protected void onActivityResult`:
```java
// pass this activity to AdvancedWebView instance, to get <input type="file" /> really work
if ( requestCode == 51426 ) {
mWebView.onActivityResult(requestCode, resultCode, intent);
return;
}
```
- (Optional) Remove the problematic “Tap again to close the app” behavior:
Find function `public boolean onKeyDown`, remove everything inside except the `return` clause.
----
```bash
# save the modification, overwrite the original
# (you can make a backup if you feel it right)
cp $DIR_GIT/PythonActivity.java $DIR_P4A/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java
# customize the loading page with Cat-Printer assets
cp $DIR_GIT/Cat-Printer/www/_load.html $DIR_GIT/Cat-Printer/www/icon.svg $DIR_P4A/bootstraps/webview/build/webview_includes/
```
## Build
### Debug Build
*\*Phew\**, it should be ready. Now try to build a debug version:
```bash
# always cd here
cd $DIR_GIT/Cat-Printer/build-android/
# <dot><slash><0><tab>
./0-build-android.sh
# again, feel free to check this file
```
The initial build will cost some time.
p4a will do:
- Download some source code from Internet, notably `python3` and some essential packages like `pyjnius`, `pyffi`, etc.
- Build all of them
- Build Cat-Printer code to Cython, gather everything together
- Download a Gradle package, give those assets to Gradle to complete the build.
TODO: find a way to get Gradle in China Mainland ~~without any circumvention~~
----
It worked? Congratulations! Now test your built package with an Android phone.
(Note that if youve previously installed my distribution, uninstall it first, to solve signature conflict.)
It didnt? **Dont panic!** Check the message to see whats wrong, try to fix it.
Get a problem? **Say whats up in Issue/Discussion.**
Build process messed up? Changes not applied? Execute `./2-clean-up-build.sh` to clean up, then redo the build.
Other scripts inside `build-android/` may be helpful too.
### Release Build
Its best to publish a release build. In contrast to debug build, release build have smaller size, optimized, and signed for authority.
Before start, read [development.md](../development.md) to setup for a “pure” bundle, and build one.
Okay, now lets generate your key, to be used to sign the apk:
```bash
# keytool is of Java. use from your build environment.
# keep this file secret! put outside of git directory, dont lose it.
keytool -genkey -v -keystore mykeyfile.key -keyalg RSA -keysize 2048 -validity 18250 -alias mykey
```
```bash
# always cd here
cd $DIR_GIT/Cat-Printer/build-android/
# <dot><slash><3><tab>
# pass the path to keyfile as parameter
./3-formal-build.sh mykeyfile.key # On later versions of python-for-android, the keystore needs to be specified via "--keystore mykeyfile.key --signkey mykey"
# again, feel free to check this file
```
This will cost a bit more time than debug build.
Note: Im unsure if (another or the first) Gradle is being downloaded in this step.
If it also worked, congrats again!
(On Android, the debug build conflicts with a signed release build. Uninstall one to install the other.)
Try the ultimate helper `1-build.sh`, if you also have everything in [development.md](../development.md) done.
## Troubleshooting
### Common
| Error message | Fix |
|-|-|
| path may not be null or empty string. path='null' | Keystore needs to be specified via `--keystore <keyfile>` |
| `/usr/bin/javac` is missing | JDK needs to be installed and selected as active java installation. |
| setuptools could not be imported | Fixed by specifying a different python version by adding `--requirements=pip,setuptools,wheel,hostpython3==3.9.16,python3==3.9.16,$(cat build-deps.txt)` |
| No such file or directory `bleak/setup.py` | Run `2-clean-up-build.sh` or [download the bleak source code from PyPi](https://pypi.org/project/bleak/#files) |
| No such file or directory `build-android/dist` | Create a bare bundle before creating an APK |
| JAVA_HOME is not set and no 'java' command could be found in your PATH. | Install a JDK (e.g. `openjdk-19-jdk`) |
### Special
Something like these:
(pardon me for not memorizing the log well)
```
… platform is . …
is unsupported, assuming android-19 …
……
……
……
crtbegin… … not found
crtend… … not found
```
Change `$ANDROIDNDK/build/gmsl/__gmsl` line 512:
```
int_encode = $(__gmsl_tr1)$(wordlist 1,$1,$(__gmsl_input_int))
```
to this:
```
int_encode = $(__gmsl_tr1)$(wordlist 1,$(if $1, $1,0),$(__gmsl_input_int))
```
(Thanks to a comment around [here](https://stackoverflow.com/questions/10285242/openssl-using-androids-ndk-problems#answer-14369078))
## The End
You made it! You now have ability to contribute much more, outside of Cat-Printer. Try to bring an app in your mind to reality, with just Python, Web, and this build environment.

View File

@ -1,7 +0,0 @@
#!/bin/sh
export version=`cat ../version`
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 $version
# python3 bundle.py -w $version
python3 bundle.py -b $version

View File

@ -1,127 +0,0 @@
'Bundle script'
import os
import sys
import datetime
import re
import zipfile
bundle_name = 'cat-printer-%s-%s.zip'
edition = 'pure'
version = 'dev'
bundle_sub_dir = 'cat-printer'
if '-w' in sys.argv:
edition = 'windows'
elif '-b' in sys.argv:
edition = 'bare'
if not sys.argv[-1].startswith('-'):
version = sys.argv[-1]
bundle_name %= (edition, version)
ignore_whitelist = (
'www/main.comp.js'
)
additional_ignore = (
# prevent recurse
bundle_name,
# non-production (yet)
'PKGBUILD', 'systemd',
# build helpers
'build-*', '?-*.sh',
# no need
'.git', '.gitignore',
'.vscode', '.pylintrc',
'dev-diary.txt', 'TODO',
# cache
'*.pyc',
# other
'.directory',
'thumbs.db',
'thumbs.db:encryptable'
)
def wildcard_to_regexp(wildcard):
'Turn a "wildcard" string to a regular expression string'
return (
wildcard
.replace('/', os.path.sep.replace('\\', '\\\\'))
.replace('.', r'\.')
.replace('*', r'.*')
.replace('?', r'.?')
)
os.chdir('../')
ignored = []
for i in additional_ignore:
ignored.append(
re.compile(
wildcard_to_regexp(i)
)
)
if os.path.isfile('.gitignore'):
with open('.gitignore', 'r', encoding='utf-8') as file:
while True:
line = file.readline()
if not line:
break
line = line.strip()
if (
line.startswith('#') or
line in ignore_whitelist or
not line
):
continue
pattern = re.compile(
wildcard_to_regexp(line)
)
ignored.append(pattern)
with zipfile.ZipFile(bundle_name, 'w', zipfile.ZIP_DEFLATED) as bundle:
for path, dirs, files in os.walk('.'):
for name in files:
fullpath = os.path.join(path, name)
if name == bundle_name:
continue
for pattern in ignored:
if re.search(pattern, fullpath) is not None:
break
else: # if didn't break
bundle.write(fullpath, os.path.join(bundle_sub_dir, fullpath))
os.chdir('build-common')
if edition != 'bare':
for path, dirs, files in os.walk('bleak'):
if path.endswith('__pycache__'):
continue
for name in files:
fullpath = os.path.join(path, name)
bundle.write(fullpath, os.path.join(bundle_sub_dir, fullpath))
if edition == 'windows':
for path, dirs, files in os.walk('bleak_winrt'):
if path.endswith('__pycache__'):
continue
for name in files:
fullpath = os.path.join(path, name)
bundle.write(fullpath, os.path.join(bundle_sub_dir, fullpath))
os.chdir('python-w32')
for path, dirs, files in os.walk('.'):
if path.endswith('__pycache__'):
continue
for name in files:
fullpath = os.path.join(path, name)
bundle.write(fullpath, os.path.join(bundle_sub_dir, fullpath))
os.chdir('..')
bundle.write('start.bat')
bundle.comment = (
b'Cat Printer "%s" bundle\n%s' % (
edition.encode('utf-8'),
str(datetime.datetime.now()).encode('utf-8'),
)
)
bundle.close()

View File

@ -1,5 +0,0 @@
@echo off
color f0
title Cat Printer - Console
cd cat-printer
python server.py

View File

@ -1,5 +0,0 @@
'For python-for-android entry point'
from server import serve
serve()

View File

@ -0,0 +1,53 @@
MODIFIED SIL OPEN FONT LICENSE
(Original Version 1.1 - 26 February 2007)
(Modified Version 06 June 2015)
PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole, must be distributed under this license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION This license becomes null and void if any of the above conditions are not met.
DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
BITSTREAM VERA LICENSE
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera".
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names.
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.

View File

@ -0,0 +1,38 @@
—————————————————————————————-
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
—————————————————————————————-
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS
“Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
“Reserved Font Name” refers to any names specified as such after the copyright statement(s).
“Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s).
“Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
“Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.

BIN
pf2/fantasque-24.pf2 Normal file

Binary file not shown.

22
pf2/font-credits.txt Normal file
View File

@ -0,0 +1,22 @@
Fantasque Sans Mono
SIL Open Font License
Copyright (c) 2013-2016, Jany Belluz (jany.belluz@hotmail.fr)
https://github.com/belluzj/fantasque-sans
fantasque-24.pf2
Happy Times at the IKOB
SIL OFL 1.1 and Bitstream Vera v0.00
https://www.fontsquirrel.com/fonts/happy-times-at-the-ikob
times-24.pf2
times-24-i.pf2
Noto Sans
Copyright 2012 Google Inc. All Rights Reserved.
https://www.fontsquirrel.com/fonts/noto-sans
noto-sans-24.pf2
Unifont
GNU GPLv2+ with the GNU Font Embedding Exception and the SIL Open Font License version 1.1
http://unifoundry.com/unifont/index.html
unifont-16.pf2
unifont-16-s.pf2 "-r 32-1327"

BIN
pf2/noto-sans-24.pf2 Normal file

Binary file not shown.

BIN
pf2/pf2.zip Normal file

Binary file not shown.

BIN
pf2/roboto-24.pf2 Normal file

Binary file not shown.

BIN
pf2/times-24-i.pf2 Normal file

Binary file not shown.

BIN
pf2/times-24.pf2 Normal file

Binary file not shown.

BIN
pf2/unicode.pf2 Normal file

Binary file not shown.

View File

@ -1,825 +0,0 @@
'''
Cat-Printer Core
Copyright © 2021-2023 NaitLee Soft. All rights reserved.
License GPL-3.0-or-later: https://www.gnu.org/licenses/gpl-3.0.html
'''
import os
import io
import sys
import argparse
import subprocess
import asyncio
import platform
import zipfile
class ExitCodes():
'Exit codes'
Success = 0
GeneralError = 1
InvalidArgument = 2
PrinterError = 64
IncompleteProgram = 128
MissingDependency = 129
UserInterrupt = 254
def info(*args, **kwargs):
'Just `print` to `stdout`'
print(*args, **kwargs, file=sys.stdout, flush=True)
def error(*args, exception=None, **kwargs):
'`print` to `stderr`, or optionally raise an exception'
if exception is not None:
raise exception(*args)
else:
print(*args, **kwargs, file=sys.stderr, flush=True)
def fatal(*args, code=ExitCodes.GeneralError, **kwargs):
'`print` to `stderr`, and exit with `code`'
print(*args, **kwargs, file=sys.stderr, flush=True)
sys.exit(code)
# Do i18n first
try:
from printer_lib.i18n import I18nLib
for path in ('www/lang', 'lang'):
if os.path.exists(path):
i18n = I18nLib(path).translate
break
else: # if didn't break
error('Warning: No languages were found', exception=None)
except ImportError:
fatal(
'Folder "printer_lib" is incomplete or missing, please check.',
code=ExitCodes.IncompleteProgram
)
# Test if `pyobjc` is there on MacOS
if platform.system() == 'macOS':
try:
import CoreBluetooth # pylint: disable=import-error,unused-import
except ImportError:
fatal(
i18n('please-install-pyobjc-via-pip'),
' $ pip3 install pyobjc',
code=ExitCodes.MissingDependency,
sep='\n'
)
# Test if `bleak` is there
try:
from bleak import BleakClient, BleakScanner
from bleak.backends.device import BLEDevice
from bleak.exc import BleakError, BleakDBusError
except ImportError as error:
raise error
fatal(
i18n('please-install-bleak-via-pip'),
' $ pip3 install bleak',
code=ExitCodes.MissingDependency,
sep='\n'
)
# Import essential basic parts
try:
from printer_lib.models import Models, Model
from printer_lib.commander import Commander, reverse_bits
from printer_lib.text_print import TextCanvas
except ImportError:
fatal(
i18n('folder-printer_lib-is-incomplete-or-missing-please-check'),
code=ExitCodes.IncompleteProgram
)
# Helpers
def flip(buffer, width, height, horizontally=False, vertically=True, *, overwrite=False):
'Flip the bitmap data'
buffer.seek(0)
if not horizontally and not vertically:
return buffer
data_width = width // 8
result_0 = io.BytesIO()
if horizontally:
while data := buffer.read(data_width):
data = bytearray(map(reverse_bits, data))
data.reverse()
result_0.write(data)
result_0.seek(0)
else:
result_0 = buffer
result_1 = io.BytesIO()
if vertically:
for i in range(height - 1, -1, -1):
result_0.seek(i * data_width)
data = result_0.read(data_width)
result_1.write(data)
result_1.seek(0)
else:
result_1 = result_0
buffer.seek(0)
if overwrite:
while data := result_1.read(data_width):
buffer.write(data)
buffer.seek(0)
return result_1
# Classes
class PrinterError(Exception):
'Exception raised when something went wrong during printing'
message: str
message_localized: str
def __init__(self, *args):
super().__init__(*args)
self.message = args[0]
self.message_localized = i18n(*args)
class PrinterData():
''' The image data to be used by `PrinterDriver`.
Optionally give an io `file` to read PBM image data from it.
To read the bitmap data, simply do `io` operation with attribute `data`
'''
buffer = 4 * 1024 * 1024
width: int
'Constant width'
_data_width: int
'Amount of data bytes per line'
height: int
'Total height of bitmap data'
data: bytearray
'Monochrome bitmap data `io`, of size `width * height // 8`'
pages: list
'Height of every page in a `list`'
max_size: int
'Max size of `data`'
full: bool
'Whether the data is full (i.e. have reached max size)'
def __init__(self, width, file: io.BufferedIOBase=None, max_size=64 * 1024 * 1024):
self.width = width
self._data_width = width // 8
self.height = 0
self.max_size = max_size
self.max_height = max_size // self._data_width
self.full = False
self.data = io.BytesIO()
self.pages = []
if file is not None:
self.from_pbm(file)
def write(self, data: bytearray):
''' Directly write bitmap data to `data` directly. For memory safety,
will overwrite earliest data if going to reach `max_size`.
returns the io position after writing.
'''
data_len = len(data)
if self.data.tell() + data_len > self.max_size:
self.full = True
self.data.seek(0)
self.data.write(data)
position = self.data.tell()
if not self.full:
self.height = position // self._data_width
return position
def read(self, length=-1):
''' Read the bitmap data entirely, in chunks.
`yield` the resulting data.
Will finally put seek point to `0`
'''
self.data.seek(0)
while chunk := self.data.read(length):
yield chunk
self.data.seek(0)
def from_pbm(self, file: io.BufferedIOBase):
''' Read from buffer `file` that have PBM image data.
Concatenating multiple files *is* allowed.
Calling multiple times is also possible,
before or after yielding `read`, not between.
Will put seek point to last byte written.
'''
while signature := file.readline():
if signature != b'P4\n':
error('input-is-not-pbm-image', exception=PrinterError)
while True:
# There can be comments. Skip them
line = file.readline()[0:-1]
if line[0:1] != b'#':
break
width, height = map(int, line.split(b' '))
if width != self.width:
error(
'unsuitable-image-width-expected-0-got-1',
self.width, width,
exception=PrinterError
)
self.pages.append(height)
self.height += height
total_size = 0
expected_size = self._data_width * height
while raw_data := file.read(
min(self.buffer, expected_size - total_size)):
total_size += len(raw_data)
self.write(raw_data)
if self.full:
self.pages.pop(0)
if total_size != expected_size:
error('broken-pbm-image', exception=PrinterError)
if file is not sys.stdin.buffer:
file.close()
def to_pbm(self, *, merge_pages=False):
''' `yield` the pages as PBM image data,
optionally just merge to one page.
Will restore the previous seek point.
'''
pointer = self.data.tell()
self.data.seek(0)
if merge_pages:
yield bytearray(
b'P4\n%i %i\n' % (self.width, self.height)
) + self.data.read()
else:
for i in self.pages:
yield bytearray(
b'P4\n%i %i\n' % (self.width, i)
) + self.data.read(self._data_width * i)
self.data.seek(pointer)
def __del__(self):
self.data.truncate(0)
self.data.close()
del self.data
# The driver
class PrinterDriver(Commander):
'The core driver of Cat-Printer'
device: BleakClient = None
'The connected printer device.'
model: Model = None
'The printer model'
scan_time: float = 4.0
connection_timeout : float = 5.0
font_family: str = 'font'
text_canvas: TextCanvas = None
flip_h: bool = False
flip_v: bool = False
wrap: bool = False
rtl: bool = False
font_scale: int = 1
energy: int = None
'Thermal strength of printer, range 0x0000 to 0xffff'
speed: int = 32
mtu: int = 200
tx_characteristic = '0000ae01-0000-1000-8000-00805f9b34fb'
rx_characteristic = '0000ae02-0000-1000-8000-00805f9b34fb'
dry_run: bool = False
'Test print process only, will not waste paper'
fake: bool = False
'Test data logic only, will not waste time'
dump: bool = False
'Dump traffic data, and if it\'s text printing, the resulting PBM image'
_loop: asyncio.AbstractEventLoop = None
_traffic_dump: io.FileIO = None
_paused: bool = False
_pending_data: io.BytesIO = None
def __init__(self):
self._loop = asyncio.get_event_loop_policy().new_event_loop()
def loop(self, *futures):
''' Run coroutines in order in current event loop until complete,
return its result directly, or their result as tuple.
This 1) ensures exiting gracefully (futures always get completed before exiting),
and 2) avoids function colors (use of "await", especially outside this script)
'''
results = []
for future in futures:
results.append(self._loop.run_until_complete(future))
return results[0] if len(results) == 1 else tuple(results)
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
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
self.model = Models.get(name, Models['_ZZ00'])
self.device = BleakClient(address)
def notify(_char, data):
if data == self.data_flow_pause:
self._paused = True
elif data == self.data_flow_resume:
self._paused = False
self.loop(
self.device.connect(timeout=self.connection_timeout),
self.device.start_notify(self.rx_characteristic, notify)
)
def scan(self, identifier: str=None, *, use_result=False, everything=False):
''' Scan for supported devices, optionally filter with `identifier`,
which can be device model (bluetooth name), and optionally MAC address, after a comma.
If `use_result` is True, connect to the first available device to driver instantly.
If `everything` is True, return all bluetooth devices found.
Note: MAC address doesn't work on Apple MacOS. In place with it,
You need an UUID of BLE device dynamically given by MacOS.
'''
if self.fake:
return []
if everything:
devices = self.loop(BleakScanner.discover(self.scan_time))
return devices
if identifier:
if identifier.find(',') != -1:
name, address = identifier.split(',')
if name not in Models:
error('model-0-is-not-supported-yet', name, exception=PrinterError)
# TODO: is this logic correct?
if address[2::3] != ':::::' and len(address.replace('-', '')) != 32:
error('invalid-address-0', address, exception=PrinterError)
if use_result:
self.connect(name, address)
return [BLEDevice(address, name)]
if (identifier not in Models and
identifier[2::3] != ':::::' and len(identifier.replace('-', '')) != 32):
error('model-0-is-not-supported-yet', identifier, exception=PrinterError)
# scanner = BleakScanner()
devices = [x for x in self.loop(
BleakScanner.discover(self.scan_time)
) if x.name in Models]
if identifier:
if identifier in Models:
devices = [dev for dev in devices if dev.name == identifier]
else:
devices = [dev for dev in devices if dev.address.lower() == identifier.lower()]
if use_result and len(devices) != 0:
self.connect(devices[0].name, devices[0].address)
return devices
def print(self, file: io.BufferedIOBase, *, mode='default',
identifier: str=None):
''' Print data of `file`.
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)
if mode in ('pbm', 'default'):
printer_data = PrinterData(self.model.paper_width, file)
self._print_bitmap(printer_data)
elif mode == 'text':
self._print_text(file)
else:
... # TODO: other?
def flush(self):
'Send pending data instantly, but will block if paused'
self._pending_data.seek(0)
while chunk := self._pending_data.read(self.mtu):
while self._paused:
self.loop(asyncio.sleep(0.2))
self.loop(
self.device.write_gatt_char(self.tx_characteristic, chunk),
asyncio.sleep(0.02)
)
self._pending_data.seek(0)
self._pending_data.truncate()
def send(self, data):
''' Pend `data`, send if enough size is reached.
You can manually `flush` to send data instantly,
and should do `flush` at the end of printing.
'''
if self.dump:
if self._traffic_dump is None:
self._traffic_dump = open('traffic.dump', 'wb')
self._traffic_dump.write(data)
if self.fake:
return
self._pending_data.write(data)
if self._pending_data.tell() > self.mtu * 16 and not self._paused:
self.flush()
def _prepare(self):
self.get_device_state()
if self.model.is_new_kind:
self.start_printing_new()
else:
self.start_printing()
self.set_dpi_as_200()
if self.speed: # well, slower makes stable heating
self.set_speed(self.speed)
if self.energy is not None:
self.set_energy(self.energy)
self.apply_energy()
self.update_device()
self.flush()
self.start_lattice()
def _finish(self):
self.end_lattice()
self.set_speed(8)
if self.model.problem_feeding:
for _ in range(128):
self.draw_bitmap(bytes(self.model.paper_width // 8))
else:
self.feed_paper(128)
self.get_device_state()
self.flush()
def _print_bitmap(self, data: PrinterData):
paper_width = self.model.paper_width
flip(data.data, data.width, data.height, self.flip_h, self.flip_v, overwrite=True)
self._prepare()
# TODO: consider compression on new devices
for chunk in data.read(paper_width // 8):
if self.dry_run:
chunk = b'\x00' * len(chunk)
self.draw_bitmap(chunk)
if self.dump:
with open('dump.pbm', 'wb') as dump_pbm:
dump_pbm.write(next(data.to_pbm(merge_pages=True)))
self._finish()
def _get_pf2(self, path: str):
''' Get file io of a PF2 font in several ways
'''
path += '.pf2'
file = None
parents = ('', 'pf2/')
if not path:
path = 'unifont'
for parent in parents:
if os.path.exists(full_path := os.path.join(parent, path)):
file = open(full_path, 'rb')
break
else: # if didn't break
if os.path.exists('pf2.zip'):
with zipfile.ZipFile('pf2.zip') as pf2zip:
for name in pf2zip.namelist():
if name == path:
with pf2zip.open(name) as f:
file = io.BytesIO(f.read())
break
return file
def _print_text(self, file: io.BufferedIOBase):
paper_width = self.model.paper_width
text_io = io.TextIOWrapper(file, encoding='utf-8')
if self.text_canvas is None:
self.text_canvas = TextCanvas(paper_width, wrap=self.wrap,
rtl=self.rtl, font_path=self.font_family + '.pf2',
font_data_io=self._get_pf2(self.font_family), scale=self.font_scale)
if self.text_canvas.broken:
error(i18n('pf2-font-not-found-or-broken-0', self.font_family), exception=PrinterError)
# with stdin you maybe trying out a typewriter
# so print a "ruler", indicating max characters in one line
if file is sys.stdin.buffer:
pf2 = self.text_canvas.pf2
info(i18n('font-size-0', pf2.point_size))
# get character width
width_stats = {}
for char in ' imMAa0+':
width_stats[char] = pf2[char].width
average = pf2.point_size // 2
if (width_stats[' '] == width_stats['i'] ==
width_stats['m'] == width_stats['M']):
# monospace
average = width_stats['A']
else:
# variable width, use a rough average
average = (width_stats['a'] + width_stats['A'] +
width_stats['0'] + width_stats['+']) // 4
# ruler
info('-------+' * (paper_width // average // 8) +
'-' * (paper_width // average % 8))
self._prepare()
printer_data = PrinterData(paper_width)
buffer = io.BytesIO()
try:
while line := text_io.readline():
if '\x00' in line:
error('input-is-not-text-file', exception=PrinterError)
line_count = 0
for data in self.text_canvas.puttext(line):
buffer.write(data)
line_count += 1
flip(buffer, self.text_canvas.width, self.text_canvas.height * line_count,
self.flip_h, self.flip_v, overwrite=True)
while chunk := buffer.read(paper_width // 8):
printer_data.write(chunk)
if self.dry_run:
chunk = b'\x00' * len(chunk)
self.draw_bitmap(chunk)
buffer.seek(0)
buffer.truncate()
self.flush()
except UnicodeDecodeError:
error('input-is-not-text-file', exception=PrinterError)
if self.dump:
with open('dump.pbm', 'wb') as dump_pbm:
dump_pbm.write(next(printer_data.to_pbm(merge_pages=True)))
self._finish()
def unload(self):
''' Unload this instance, disconnect device and clean up.
'''
if self.device is not None:
info(i18n('disconnecting-from-printer'))
try:
self.loop(
self.device.stop_notify(self.rx_characteristic),
self.device.disconnect()
)
except (BleakError, EOFError):
self.device = None
if self._traffic_dump is not None:
self._traffic_dump.close()
self._loop.close()
# CLI procedure
def fallback_program(*programs):
'Return first specified program that exists in PATH'
for i in os.environ['PATH'].split(os.pathsep):
for j in programs:
if os.path.isfile(os.path.join(i, j)):
return j
return None
_MagickExe = fallback_program('magick', 'magick.exe', 'convert', 'convert.exe')
def magick_text(stdin, image_width, font_size, font_family):
'Pipe an io to ImageMagick for processing text to image, return output io'
if _MagickExe is None:
fatal(i18n("imagemagick-not-found"), code=ExitCodes.MissingDependency)
read_fd, write_fd = os.pipe()
subprocess.Popen([_MagickExe, '-background', 'white', '-fill', 'black',
'-size', f'{image_width}x', '-font', font_family, '-pointsize',
str(font_size), 'caption:@-', 'pbm:-'],
stdin=stdin, stdout=io.FileIO(write_fd, 'w'))
return io.FileIO(read_fd, 'r')
def magick_image(stdin, image_width, dither):
'Pipe an io to ImageMagick for processing "usual" image to pbm, return output io'
if _MagickExe is None:
fatal(i18n("imagemagick-not-found"), code=ExitCodes.MissingDependency)
read_fd, write_fd = os.pipe()
subprocess.Popen([_MagickExe, '-', '-fill', 'white', '-opaque', 'transparent',
'-resize', f'{image_width}x', '-dither', dither, '-monochrome', 'pbm:-'],
stdin=stdin, stdout=io.FileIO(write_fd, 'w'))
return io.FileIO(read_fd, 'r')
class HelpFormatterI18n(argparse.HelpFormatter):
'How dare the author of this thing hardcode strings and a colon?'
class _Section(argparse.HelpFormatter._Section): # pylint: disable=protected-access
'For removing trailing hardcoded colon. Many cultures have their own'
def format_help(self):
lines = super().format_help().split('\n')
if len(lines) > 1 and lines[1].endswith(':'):
lines[1] = lines[1][:-1] + '\n'
return '\n'.join(lines)
def _format_usage(self, usage, actions, groups, prefix=None):
return super()._format_usage(usage, actions, groups, i18n('usage-'))
class ArgumentParserI18n(argparse.ArgumentParser):
'For using our i18n instead of gettext'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs, formatter_class=HelpFormatterI18n, add_help=False)
del self._positionals
del self._optionals
add_group = self.add_argument_group
self._positionals = add_group(i18n('positional-arguments-'))
self._optionals = add_group(i18n('options-'))
def add_argument(self, *args, **kwargs):
if 'required' not in kwargs and len(args) > 1 and args[1].startswith('-'):
kwargs['required'] = False
super().add_argument(*args, **kwargs)
Printer = None
def _main():
'Main routine for direct command line execution'
parser = ArgumentParserI18n(
description=' '.join([
i18n('print-to-cat-printer'),
i18n('supported-models-'),
str((*Models, ))
])
)
# TODO: group some switches to dedicated help
parser.add_argument('-h', '--help', action='store_true',
help=i18n('show-this-help-message'))
parser.add_argument('file', default='-', metavar='File', type=str,
help=i18n('path-to-input-file-dash-for-stdin'))
parser.add_argument('-s', '--scan', metavar='Time[,XY01[,MacAddress]]', default='4', type=str,
help=i18n('scan-for-a-printer'))
parser.add_argument('-c', '--convert', metavar='text|image', type=str, default='',
help=i18n('convert-input-image-with-imagemagick'))
parser.add_argument('-p', '--image', metavar='flip|fliph|flipv', type=str, default='',
help=i18n('image-printing-options'))
parser.add_argument('-t', '--text', metavar='Size[,FontFamily][,pf2][,nowrap][,rtl]', type=str,
default='', help=i18n('text-printing-mode-with-options'))
parser.add_argument('-e', '--energy', metavar='0.0-1.0', type=float, default=None,
help=i18n('control-printer-thermal-strength'))
parser.add_argument('-q', '--quality', metavar='1-4', type=int, default=3,
help=i18n('print-quality'))
parser.add_argument('-d', '--dry', action='store_true',
help=i18n('dry-run-test-print-process-only'))
parser.add_argument('-u', '--unknown', action='store_true',
help=i18n('try-to-print-through-an-unknown-device'))
parser.add_argument('-0', '--0th', action='store_true',
help=i18n('no-prompt-for-multiple-devices'))
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-traffic'))
parser.add_argument('-n', '--nothing', action='store_true',
help=i18n('do-nothing'))
if len(sys.argv) < 2 or '-h' in sys.argv or '--help' in sys.argv:
parser.print_help()
sys.exit(0)
args = parser.parse_args()
printer = PrinterDriver()
scan_param = args.scan.split(',')
printer.scan_time = float(scan_param[0])
identifier = ','.join(scan_param[1:])
if args.energy is not None:
printer.energy = int(args.energy * 0xffff)
elif args.convert == 'text' or args.text:
printer.energy = 0x6000
else:
printer.energy = 0x4000
if args.quality is not None:
printer.speed = 4 * (args.quality + 5)
image_param = args.image.split(',')
if 'flip' in image_param:
printer.flip_h = True
printer.flip_v = True
elif 'fliph' in image_param:
printer.flip_h = True
elif 'flipv' in image_param:
printer.flip_v = True
if args.text:
text_param = args.text.split(',')
font_size = int(text_param[0]) if len(text_param) > 0 else None
font_family = text_param[1] if len(text_param) > 1 else None
printer.wrap = 'nowrap' not in text_param
printer.rtl = 'rtl' in text_param
info(i18n('cat-printer'))
if args.file == '-':
file = sys.stdin.buffer
else:
file = open(args.file, 'rb')
mode = 'pbm'
# Connect to printer
if args.dry:
info(i18n('dry-run-test-print-process-only'))
printer.dry_run = True
if args.fake:
printer.fake = True
printer.model = Models[args.fake]
else:
info(i18n('scanning-for-devices'))
devices = printer.scan(identifier, everything=args.unknown)
printer.dump = args.dump
if args.nothing:
global Printer
Printer = printer
return
if len(devices) == 0:
error(i18n('no-available-devices-found'), exception=PrinterError)
if len(devices) == 1 or getattr(args, '0th'):
info(i18n('connecting'))
printer.connect(devices[0].name, devices[0].address)
else:
info(i18n('there-are-multiple-devices-'))
for i in range(len(devices)):
d = devices[i]
n = str(d.name) + "-" + d.address[3:5] + d.address[0:2]
info('%4i\t%s' % (i, n))
choice = 0
try:
choice = int(input(i18n('choose-which-one-0-', choice)))
except KeyboardInterrupt:
raise
except:
pass
info(i18n('connecting'))
printer.connect(devices[choice].name, devices[choice].address)
# Prepare image / text
if args.text:
info(i18n('text-printing-mode'))
printer.font_family = font_family or 'font'
if 'pf2' not in text_param:
file = magick_text(file, printer.model.paper_width,
font_size, font_family)
else:
printer.font_scale = font_size
mode = 'text'
elif args.convert:
file = magick_image(file, printer.model.paper_width, (
'None'
if args.convert == 'text'
else 'FloydSteinberg')
)
try:
printer.print(file, mode=mode)
info(i18n('finished'))
finally:
file.close()
printer.unload()
def main():
'Run the `_main` routine while catching exceptions'
try:
_main()
except BleakError as e:
error_message = str(e)
if (
'not turned on' in error_message or
'No powered Bluetooth adapter' in error_message or
(isinstance(e, BleakDBusError) and
getattr(e, 'dbus_error') == 'org.bluez.Error.NotReady')
):
fatal(i18n('please-enable-bluetooth'), code=ExitCodes.GeneralError)
else:
raise
except PrinterError as e:
fatal(e.message_localized, code=ExitCodes.PrinterError)
except RuntimeError as e:
if 'no running event loop' in str(e):
pass # ignore this
else:
raise
except KeyboardInterrupt:
fatal(i18n('stopping'), code=ExitCodes.UserInterrupt)
if __name__ == '__main__':
main()

View File

@ -1,157 +0,0 @@
'''
Cat-Printer Commander(s), binary interface(s) to communicate with cat printers
No rights reserved.
License CC0-1.0-only: https://directory.fsf.org/wiki/License:CC0
'''
from abc import ABCMeta, abstractmethod
crc8_table = [
0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31,
0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65,
0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9,
0xfc, 0xfb, 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd,
0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, 0xaf, 0xa6, 0xa1,
0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2,
0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe,
0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a,
0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16,
0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42,
0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 0x89, 0x8e, 0x87, 0x80,
0x95, 0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4,
0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, 0xc1, 0xc6, 0xcf, 0xc8,
0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c,
0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10,
0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34,
0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f,
0x6a, 0x6d, 0x64, 0x63, 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b,
0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7,
0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83,
0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, 0xe6, 0xe1, 0xe8, 0xef,
0xfa, 0xfd, 0xf4, 0xf3
]
def crc8(data):
'crc8 checksum'
crc = 0
for byte in data:
crc = crc8_table[(crc ^ byte) & 0xff]
return crc & 0xff
def reverse_bits(i: int):
'Reverse the bits of this byte (as `int`)'
i = ((i & 0b10101010) >> 1) | ((i & 0b01010101) << 1)
i = ((i & 0b11001100) >> 2) | ((i & 0b00110011) << 2)
return ((i & 0b11110000) >> 4) | ((i & 0b00001111) << 4)
def int_to_bytes(i: int, big_endian=False):
''' Turn `int` into `bytearray`, that have
least bytes possible to represent the int
'''
result = bytearray()
while i != 0:
result.append(i & 0xff)
i >>= 8
if big_endian:
result.reverse()
return result
class Commander(metaclass=ABCMeta):
''' Semi-abstract class, to be inherited by `PrinterDriver`
Contains binary data communication interface for individual functions
"Commander" of kind of printers like GB0X, GT01
Class structure is not guaranteed to be stable
'''
dry_run: bool = False
data_flow_pause = b'\x51\x78\xae\x01\x01\x00\x10\x70\xff'
data_flow_resume = b'\x51\x78\xae\x01\x01\x00\x00\x00\xff'
def make_command(self, command_bit, payload: bytearray, *,
prefix=bytearray(), suffix=bytearray()):
'Make bytes that to be used to control printer'
payload_size = len(payload)
if payload_size > 0xff:
raise ValueError(f'Command payload too big ({payload_size} > 255)')
return prefix + bytearray(
[ 0x51, 0x78, command_bit, 0x00, payload_size, 0x00 ]
) + payload + bytearray( [ crc8(payload), 0xff ] ) + suffix
def start_printing(self):
'Start printing'
self.send( bytearray([0x51, 0x78, 0xa3, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff]) )
def start_printing_new(self):
'Start printing on newer printers'
self.send( bytearray([0x12, 0x51, 0x78, 0xa3, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff]) )
def apply_energy(self):
''' Apply previously set energy to printer
'''
self.send( self.make_command(0xbe, int_to_bytes(0x01)) )
def get_device_state(self):
'(unknown). seems it could refresh device state & apply config'
self.send( self.make_command(0xa3, int_to_bytes(0x00)) )
def get_device_info(self):
'(unknown). seems it could refresh device state & apply config'
self.send( self.make_command(0xa8, int_to_bytes(0x00)) )
def update_device(self):
'(unknown). seems it could refresh device state & apply config'
self.send( self.make_command(0xa9, int_to_bytes(0x00)) )
def set_dpi_as_200(self):
'(unknown)'
self.send( self.make_command(0xa4, int_to_bytes(50)) )
def start_lattice(self):
'Mark the start of printing'
self.send( self.make_command(0xa6, bytearray(
[0xaa, 0x55, 0x17, 0x38, 0x44, 0x5f, 0x5f, 0x5f, 0x44, 0x38, 0x2c]
)) )
def end_lattice(self):
'Mark the end of printing'
self.send( self.make_command(0xa6, bytearray(
[ 0xaa, 0x55, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17 ]
)) )
def retract_paper(self, pixels: int):
'Retract the paper for some pixels'
self.send( self.make_command(0xa0, int_to_bytes(pixels)) )
def feed_paper(self, pixels: int):
'Feed the paper for some pixels'
self.send( self.make_command(0xa1, int_to_bytes(pixels)) )
def set_speed(self, value: int):
''' Set how quick to feed/retract paper. **The lower, the quicker.**
My printer with value < 4 set would make it unable to feed/retract,
maybe it's way too quick.
Speed also affects the quality, for heat time/stability reasons.
'''
self.send( self.make_command(0xbd, int_to_bytes(value)) )
def set_energy(self, amount: int):
''' Set thermal energy, max to `0xffff`
By default, it's seems around `0x3000` (1 / 5)
'''
self.send( self.make_command(0xaf, int_to_bytes(amount)) )
def draw_bitmap(self, bitmap_data: bytearray):
'Print `bitmap_data`. Also does the bit-reversing job.'
data = bytearray( map(reverse_bits, bitmap_data) )
self.send( self.make_command(0xa2, data) )
def draw_compressed_bitmap(self, bitmap_data: bytearray):
'TODO. Print `bitmap_data`, compress if worthy so'
self.draw_bitmap(bitmap_data)
@abstractmethod
def send(self, data):
'Send data to device, or whatever'
...

View File

@ -1,48 +0,0 @@
'''
Minimal internationalization
No rights reserved.
License CC0-1.0-only: https://directory.fsf.org/wiki/License:CC0
'''
import os
import json
import locale
class I18nLib():
''' Minimal implementation of current frontend i18n in Python.
Yet incomplete.
'''
lang: str
fallback: str
data: dict = {}
def __init__(self, search_path='lang', lang=None, fallback=None):
self.fallback = fallback or 'en-US'
self.lang = lang or (locale.getdefaultlocale()[0] or self.fallback).replace('_', '-')
with open(os.path.join(search_path, self.fallback + '.json'),
'r', encoding='utf-8') as file:
self.data = json.load(file)
path = self.lang + '.json'
if path in os.listdir(search_path):
with open(os.path.join(search_path, path), 'r', encoding='utf-8') as file:
data = json.load(file)
for key in data:
self.data[key] = data[key]
def translate(self, *keys):
'Translate something'
string = self.data.get(keys[0], keys[0])
if len(keys) > 1:
if isinstance(keys[-1], dict):
string = string.format(*keys[1:-1], **keys[-1])
else:
string = string.format(*keys[1:])
return string
def __getitem__(self, keys):
if isinstance(keys, tuple):
return self.translate(*keys)
else:
return self.translate(keys)

View File

@ -1,97 +0,0 @@
'''
Provide *very* basic CUPS/IPP support
No rights reserved.
License CC0-1.0-only: https://directory.fsf.org/wiki/License:CC0
'''
import io
import platform
import subprocess
from .pf2 import int16be, int32be
def int8(b: bytes):
'Translate 1 byte as signed 8-bit int'
u = b[0]
return u - ((u >> 7 & 0b1) << 8)
class IPP():
'https://datatracker.ietf.org/doc/html/rfc8010'
server = None
def __init__(self, server):
self.server = server
def handle_ipp(self):
'Handle an IPP protocol request'
server = self.server
content_length = int(server.headers.get('Content-Length'))
buffer = io.BytesIO(server.rfile.read(content_length))
_ipp_version = (int8(buffer.read(1)), int8(buffer.read(1)))
_ipp_operation_id = int16be(buffer.read(2))
_ipp_request_id = int32be(buffer.read(4))
ipp_operation_attributes_tag = int8(buffer.read(1))
attributes = {}
data = b''
if ipp_operation_attributes_tag == 0x01:
while int8(buffer.read(1)) != 0x03:
buffer.seek(-1, 1)
tag = int8(buffer.read(1))
if tag < 0x10: # delimiter-tag
continue
name = buffer.read(int16be(buffer.read(2)))
value = buffer.read(int16be(buffer.read(2)))
attributes[name] = (tag, value)
data = buffer.read()
# there are hard coded minimal response. this "just works" on cups
if data == b'':
try:
server.send_response(200)
server.send_header('Content-Type', 'application/ipp')
server.end_headers()
server.wfile.write(
b'\x01\x01\x00\x00\x00\x00\x00\x01\x01\x03'
)
except BrokenPipeError:
pass
return
if data.startswith(b'%!PS-Adobe'):
self.handle_postscript(data)
else:
identifier = server.path[1:]
server.printer.print(io.BytesIO(data), mode='text', identifier=identifier)
def handle_postscript(self, data):
'Print PostScript data to printer, converting to PBM first with GhostScript `gs`'
server = self.server
platform_system = platform.system()
# https://ghostscript.com/doc/9.54.0/Use.htm#Output_device
if platform_system == 'Windows':
gsexe = 'gswin32c.exe'
elif platform_system == 'OS/2':
gsexe = 'gsos2'
else:
gsexe = 'gs'
gsproc = subprocess.Popen([
gsexe,
'-q', '-sDEVICE=pbmraw', '-dNOPAUSE', '-dBATCH', '-dSAFER',
'-dFIXEDMEDIA', '-g384x543', '-r46.4441219158x46.4441219158',
'-dFitPage', '-dFitPage',
'-sOutputFile=-', '-'
], executable=gsexe, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
pbm_data, _ = gsproc.communicate(data)
try:
if gsproc.wait() == 0:
identifier = server.path[1:]
# TODO: Make IPP can report some errors
server.printer.print(io.BytesIO(pbm_data), mode='pbm', identifier=identifier)
else:
raise Exception('Error on invoking Ghostscript')
server.send_response(200)
server.send_header('Content-Type', 'application/ipp')
server.end_headers()
server.wfile.write(
b'\x01\x01\x00\x00\x00\x00\x00\x01\x01\x03'
)
except Exception as _:
server.send_response(500)
server.send_header('Content-Type', 'application/ipp')
server.end_headers()

View File

@ -1,31 +0,0 @@
'''
Printer model specifications.
No rights reserved.
License CC0-1.0-only: https://directory.fsf.org/wiki/License:CC0
'''
class Model():
''' A printer model
`paper_width`: pixels per line for the model/paper
`is_new_kind`: some models have new "start print" command and can understand compressed data.
the algorithm isn't implemented in Cat-Printer yet, but this should be no harm.
`problem_feeding`: didn't yet figure out MX05/MX06 bad behavior giving feed command, use workaround for them
'''
paper_width: int = 384
is_new_kind: bool = False
problem_feeding: bool = False
Models = {}
# all known supported models
for name in '_ZZ00 GB01 GB02 GB03 GT01 MX05 MX06 MX08 MX09 YT01'.split(' '):
Models[name] = Model()
# that can receive compressed data
for name in 'GB03'.split(' '):
Models[name].is_new_kind = True
# that have problem giving feed command
for name in 'MX05 MX06 MX08 MX09'.split(' '):
Models[name].problem_feeding = True

View File

@ -1,193 +0,0 @@
'''
Python snippet for reading PF2 font files: http://grub.gibibit.com/New_font_format
No rights reserved.
License CC0-1.0-only: https://directory.fsf.org/wiki/License:CC0
Don't forget to see how it's used in `text_print.py`
'''
import io
from typing import Dict, Tuple
def uint32be(b: bytes):
'Translate 4 bytes as unsigned big-endian 32-bit int'
return (
(b[0] << 24) +
(b[1] << 16) +
(b[2] << 8) +
b[3]
)
def int32be(b: bytes):
'Translate 4 bytes as signed big-endian 32-bit int'
u = uint32be(b)
return u - ((u >> 31 & 0b1) << 32)
def uint16be(b: bytes):
'Translate 2 bytes as big-endian unsigned 16-bit int'
return (b[0] << 8) + b[1]
def int16be(b: bytes):
'Translate 2 bytes as big-endian signed 16-bit int'
u = uint16be(b)
return u - ((u >> 15 & 0b1) << 16)
class Character():
'A PF2 character'
width: int
height: int
x_offset: int
y_offset: int
device_width: int
bitmap_data: bytes
def get_bit(self, x, y):
'Get the bit at (x, y) of this character\'s raster glyph'
char_byte = (self.width * y + x) // 8
char_bit = 7 - (self.width * y + x) % 8
return self.bitmap_data[char_byte] & (0b1 << char_bit)
class PF2():
'The PF2 class, for serializing a PF2 font file'
broken: bool = False
'Sets to True if the font file is bad'
missing_character_code: int
in_memory: bool
font_name: str
family: str
weight: str
slant: str
point_size: int
max_width: int
max_height: int
ascent: int
descent: int
character_index: Dict[int, Tuple[int, int]]
data_offset: int
data_io: io.BufferedIOBase = None
def __init__(self, file: io.BufferedIOBase, *, read_to_mem=True, missing_character: str='?'):
self.missing_character_code = ord(missing_character)
self.in_memory = read_to_mem
if read_to_mem:
self.data_io = io.BytesIO(file.read())
file.close()
file = self.data_io
self.is_pf2 = (file.read(12) == b'FILE\x00\x00\x00\x04PFF2')
if not self.is_pf2:
return
while True:
name = file.read(4)
data_length = int32be(file.read(4))
if name == b'CHIX':
self.character_index = {}
for _ in range(data_length // (4 + 1 + 4)):
code_point = int32be(file.read(4))
compression = file.read(1)[0]
offset = int32be(file.read(4))
self.character_index[code_point] = (
compression, offset
)
continue
elif name == b'DATA':
file.seek(0)
break
data = file.read(data_length)
if name == b'NAME':
self.font_name = data
elif name == b'FAMI':
self.family = data
elif name == b'WEIG':
self.weight = data
elif name == b'SLAN':
self.slant = data
elif name == b'PTSZ':
self.point_size = uint16be(data)
elif name == b'MAXW':
self.max_width = uint16be(data)
elif name == b'MAXH':
self.max_height = uint16be(data)
elif name == b'ASCE':
self.ascent = uint16be(data)
elif name == b'DESC':
self.descent = uint16be(data)
def get_char(self, char: str):
'Get a character, returning a `Character` instance'
char_point = ord(char)
info = self.character_index.get(char_point)
if info is None:
info = self.character_index[self.missing_character_code]
_compression, offset = info
data = self.data_io
data.seek(offset)
char = Character()
char.width = uint16be(data.read(2))
char.height = uint16be(data.read(2))
char.x_offset = int16be(data.read(2))
char.y_offset = int16be(data.read(2))
char.device_width = int16be(data.read(2))
char.bitmap_data = data.read(
(char.width * char.height + 7) // 8
)
return char
__getitem__ = get_char
def __del__(self):
if self.data_io is not None:
self.data_io.close()
class CharacterS(Character):
'A "scaled" character'
scale: int = 1
def get_bit(self, x, y):
'Get the bit at (x, y) of this character\'s raster glyph'
scale = self.scale
width = self.width // scale
x //= scale
y //= scale
char_byte = (width * y + x) // 8
char_bit = 7 - (width * y + x) % 8
return (self.bitmap_data[char_byte] &
(0b1 << char_bit)) >> char_bit
class PF2S(PF2):
'PF2 class with glyph scaling support'
scale: int = 1
def __init__(self, *args, scale: int=1, **kwargs):
super().__init__(*args, **kwargs)
if self.broken:
return
self.scale = scale
self.point_size *= scale
self.max_width *= scale
self.max_height *= scale
self.ascent *= scale
self.descent *= scale
def get_char(self, char):
scale = self.scale
char = super().get_char(char)
chars = CharacterS()
chars.scale = scale
chars.width = char.width * scale
chars.height = char.height * scale
chars.device_width = char.device_width * scale
chars.x_offset = char.x_offset * scale
chars.y_offset = char.y_offset * scale
chars.bitmap_data = char.bitmap_data
return chars
__getitem__ = get_char

View File

@ -1,92 +0,0 @@
'Things used by Text Printing feature. License CC0-1.0-only'
from .pf2 import PF2S
class TextCanvas():
'Canvas for text printing, requires PF2 lib'
broken: bool = False
width: int
height: int
canvas: bytearray = None
rtl: bool
wrap: bool
scale: int
pf2 = None
def __init__(self, width, *, wrap=False, rtl=False,
font_path='font.pf2', font_data_io=None, scale=1):
if font_data_io is None:
font_data_io = open(font_path, 'rb')
self.pf2 = PF2S(font_data_io, scale=scale)
if self.pf2.broken:
self.broken = True
return
self.width = width
self.wrap = wrap
self.rtl = rtl
self.scale = scale
self.height = self.pf2.max_height + self.pf2.descent
self.flush_canvas()
def flush_canvas(self):
'Flush the canvas, returning the canvas data'
if self.canvas is None:
pbm_data = None
else:
pbm_data = bytearray(self.canvas)
self.canvas = bytearray(self.width * self.height // 8)
return pbm_data
def puttext(self, text):
''' Put the specified text to canvas.
It's a generator, will `yield` the data produced, per line.
'''
text = text.replace('\t', ' ' * 4)
canvas_length = len(self.canvas)
pf2 = self.pf2
current_width = 0
last_space_at = -1
width_at_last_space = 0
break_points = set()
characters = {}
for i, s in enumerate(text):
if s not in characters:
characters[s] = pf2[s]
char = characters[s]
if s == ' ':
last_space_at = i
width_at_last_space = current_width
if (current_width > self.width and
last_space_at != -1):
break_points.add(last_space_at)
current_width -= width_at_last_space
if s == '\n':
current_width = 0
continue
current_width += pf2.point_size // 2 # + char.x_offset
current_width = 0
for i, s in enumerate(text):
char = characters[s]
if ((self.wrap and i in break_points) or s == '\n' or
current_width + char.width > self.width):
# print(current_width, end=' ')
yield self.flush_canvas()
current_width = 0
if s in ' ':
continue
if ord(s) in range(0x00, 0x20): # glyphs that should not be printed out
continue
for x in range(char.width):
for y in range(char.height):
rtl_current_width = self.width - current_width - char.width - 1
target_x = x + char.x_offset
target_y = pf2.ascent + (y - char.height) - char.y_offset
canvas_byte = (self.width * target_y + current_width + target_x) // 8
canvas_bit = 7 - (self.width * target_y + current_width + target_x) % 8
if self.rtl:
canvas_byte = (self.width * target_y + rtl_current_width + target_x) // 8
canvas_bit = 7 - (self.width * target_y + rtl_current_width + target_x) % 8
else:
canvas_byte = (self.width * target_y + current_width + target_x) // 8
canvas_bit = 7 - (self.width * target_y + current_width + target_x) % 8
if canvas_byte < 0 or canvas_byte >= canvas_length:
continue
self.canvas[canvas_byte] |= char.get_bit(x, y) << canvas_bit
current_width += char.device_width

369
server.py
View File

@ -1,369 +0,0 @@
'''
Cat-Printer: Web Interface Server
Copyright © 2021-2023 NaitLee Soft. All rights reserved.
License GPL-3.0-or-later: https://www.gnu.org/licenses/gpl-3.0.html
'''
# if pylint is annoying you, see file .pylintrc
import os
import io
import sys
import json
import warnings
import webbrowser
# For now we can't use `ThreadingHTTPServer`
from http.server import HTTPServer, BaseHTTPRequestHandler
# import `printer` first, to diagnostic some common errors
from printer import PrinterDriver, PrinterError, i18n, info
from bleak.exc import BleakDBusError, BleakError # pylint: disable=wrong-import-order
from printer_lib.ipp import IPP
# Supress non-sense asyncio warnings
warnings.simplefilter('ignore', RuntimeWarning, 0, True)
IsAndroid = (os.environ.get("P4A_BOOTSTRAP") is not None)
class DictAsObject(dict):
""" Let you use a dict like an object in JavaScript.
"""
def __getattr__(self, key):
return self.get(key, None)
def __setattr__(self, key, value):
self[key] = value
class PrinterServerError(PrinterError):
'Error of PrinterServer'
mime_type = {
'html': 'text/html;charset=utf-8',
'css': 'text/css;charset=utf-8',
'js': 'text/javascript;charset=utf-8',
'txt': 'text/plain;charset=utf-8',
'json': 'application/json;charset=utf-8',
'png': 'image/png',
'svg': 'image/svg+xml;charset=utf-8',
'wasm': 'application/wasm',
'octet-stream': 'application/octet-stream'
}
def mime(url: str):
'Get pre-defined MIME type of a certain url by extension name'
return mime_type.get(url.rsplit('.', 1)[-1], mime_type['octet-stream'])
def concat_files(*paths, prefix_format='', buffer=4 * 1024 * 1024) -> bytes:
'Generator, that yields buffered file content, with optional prefix'
for path in paths:
yield prefix_format.format(path).encode('utf-8')
with open(path, 'rb') as file:
while data := file.read(buffer):
yield data
class PrinterServerHandler(BaseHTTPRequestHandler):
'(Local) server handler for Cat Printer Web interface'
buffer = 4 * 1024 * 1024
max_payload = buffer * 16
settings = DictAsObject({
'config_path': 'config.json',
'version': 4,
'first_run': True,
'is_android': False,
'scan_time': 4.0,
'dry_run': False,
'energy': 64,
'quality': 36
})
_settings_blacklist = (
'printer', 'is_android'
)
all_script: list = []
printer: PrinterDriver = PrinterDriver()
ipp: IPP = None
def log_request(self, _code=200, _size=0):
pass
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 got a GET http request'
# prepare
path, _, _args = self.path.partition('?')
if '/..' in path or '../' in path:
return
if path == '/':
path += 'index.html'
# special
if path.startswith('/~'):
action = path[2:]
if action == 'every.js':
self.send_response(200)
self.send_header('Content-Type', mime(path))
self.end_headers()
for data in concat_files(*(self.all_script), prefix_format='\n// {0}\n'):
self.wfile.write(data)
return
path = 'www' + path
# not found
if not os.path.isfile(path):
self.send_response(404)
self.send_header('Content-Type', mime('txt'))
self.end_headers()
return
# static
self.send_response(200)
self.send_header('Content-Type', mime(path))
# self.send_header('Content-Size', str(os.stat(path).st_size))
self.end_headers()
with open(path, 'rb') as file:
while True:
chunk = file.read(self.buffer)
if not self.wfile.write(chunk):
break
return
def api_success(self, body_json=None):
'Called when an API call is being considered successful'
self.send_response(200)
self.send_header('Content-Type', mime('json'))
self.end_headers()
if body_json is None:
self.wfile.write(b'{}')
else:
self.wfile.write(json.dumps(body_json).encode('utf-8'))
def api_fail(self, error_json):
'Called when an API call is failed'
self.send_response(500)
self.send_header('Content-Type', mime('json'))
self.end_headers()
self.wfile.write(json.dumps(error_json).encode('utf-8'))
self.wfile.flush()
def load_config(self):
'Load config file, or if not exist, create one with default'
if IsAndroid:
self.settings['is_android'] = True
from android.storage import app_storage_path # pylint: disable=import-error
settings_path = app_storage_path()
os.makedirs(settings_path, exist_ok=True)
self.settings['config_path'] = os.path.join(
settings_path, 'config.json'
)
if os.path.exists(self.settings.config_path):
with open(self.settings.config_path, 'r', encoding='utf-8') as file:
settings = DictAsObject(json.load(file))
if (settings.version is None or
settings.version < self.settings.version):
# Version too old, start over
# TODO: selective?
self.save_config()
return
for key in settings:
self.settings[key] = settings[key]
else:
if os.name in ('posix',) or IsAndroid:
self.settings['scan_time'] = 2.0
self.save_config()
def save_config(self):
'Save config file'
with open(self.settings.config_path, 'w', encoding='utf-8') as file:
settings = {}
for i in self.settings:
if i not in self._settings_blacklist:
settings[i] = self.settings[i]
json.dump(settings, file, indent=4)
def update_printer(self):
'Update `PrinterDriver` state/config'
self.printer.dry_run = self.settings.dry_run
self.printer.scan_time = self.settings.scan_time
self.printer.fake = self.settings.fake
self.printer.dump = self.settings.dump
if self.settings.energy is not None:
self.printer.energy = int(self.settings.energy) * 0x100
if self.settings.quality is not None:
self.printer.speed = int(self.settings.quality)
self.printer.flip_h = self.settings.flip_h or self.settings.flip
self.printer.flip_v = self.settings.flip_v or self.settings.flip
self.printer.rtl = self.settings.force_rtl
def handle_api(self):
'Handle API request from POST'
content_length = int(self.headers.get('Content-Length'))
body = self.rfile.read(content_length)
api = self.path[1:]
if api == 'print':
self.update_printer()
self.printer.print(io.BytesIO(body))
self.api_success()
return
data = DictAsObject(json.loads(body))
if api == 'devices':
self.printer.connect(None)
devices_list = [{
'name': device.name,
'address': device.address
} for device in self.printer.scan(everything=data.get('everything'))]
self.api_success({
'devices': devices_list
})
return
if api == 'query':
self.load_config()
self.api_success(self.settings)
return
if api == 'set':
for key in data:
self.settings[key] = data[key]
self.save_config()
self.update_printer()
self.api_success()
return
if api == 'connect':
name, address = data['device'].split(',')
self.printer.connect(name, address)
self.api_success()
if api == 'exit':
self.api_success()
self.exit()
def exit(self):
'Stop correctly & cleanly'
self.save_config()
self.printer.unload()
sys.exit(0)
def do_POST(self):
'Called when server got a POST http request'
content_length = int(self.headers.get('Content-Length', -1))
if (content_length < -1 or
content_length > self.max_payload
):
return
if self.headers.get('Content-Type') == 'application/ipp':
if self.ipp is None:
self.ipp = IPP(self)
self.ipp.handle_ipp()
return
try:
self.handle_api()
return
except BleakDBusError as e:
# TODO: better error reporting
self.api_fail({
'name': e.dbus_error,
'details': e.dbus_error_details
})
except BleakError as e:
self.api_fail({
'name': 'BleakError',
'details': str(e)
})
except EOFError as e:
# mostly, device disconnected but not by this program
self.api_fail({
'name': 'EOFError',
'details': ''
})
except RuntimeError as e:
self.api_fail({
'name': 'RuntimeError',
'details': str(e)
})
except PrinterError as e:
self.api_fail({
'name': e.message,
'details': e.message_localized
})
except Exception as e:
self.api_fail({
'name': 'Exception',
'details': str(e)
})
raise
class PrinterServer(HTTPServer):
''' (local) server for Cat Printer Web Interface
The reason to override is to only init the handler once,
avoiding confliction, and stop cleanly
'''
handler_class = None
handler: PrinterServerHandler = None
def __init__(self, server_address, RequestHandlerClass):
self.handler_class = RequestHandlerClass
super().__init__(server_address, RequestHandlerClass)
def finish_request(self, request, client_address):
if self.handler is None:
self.handler = self.handler_class(request, client_address, self)
self.handler.load_config()
with open(os.path.join('www', 'all-scripts.txt'), 'r', encoding='utf-8') as file:
for path in file.read().split('\n'):
if path != '':
self.handler.all_script.append(os.path.join('www', path))
return
self.handler.__init__(request, client_address, self)
def server_close(self):
if self.handler is not None:
self.handler.exit()
super().server_close()
def serve():
'Start server'
address, port = '127.0.0.1', 8095
listen_all = False
if '-a' in sys.argv:
info(i18n('will-listen-on-all-addresses'))
listen_all = True
server = PrinterServer(('' if listen_all else address, port), PrinterServerHandler)
service_url = f'http://{address}:{port}/'
info(i18n('serving-at-0', service_url))
if '-s' not in sys.argv and not IsAndroid:
webbrowser.open(service_url)
# Request required bluetooth permissions (Android 12+)
if IsAndroid:
from android.permissions import request_permissions, Permission
try:
request_permissions([Permission.BLUETOOTH_SCAN, Permission.BLUETOOTH_CONNECT])
except Exception:
print('Exception on requesting Android Permissions. Continuing', file=sys.stderr)
pass
from android.app import Activity
from android.content import Intent
print(Intent.getIntent().getType())
try:
server.serve_forever()
except KeyboardInterrupt:
server.server_close()
if __name__ == '__main__':
serve()

View File

@ -1,14 +0,0 @@
[Unit]
Description=A project that provides support to some Bluetooth Cat Printer models
After=bluetooth.target network.target
Requires=bluetooth.target network.target
StartLimitIntervalSec=5
[Service]
Type=simple
ExecStart=/usr/bin/cat-printer-server -s
Restart=on-failure
DynamicUser=true
[Install]
WantedBy=multi-user.target

View File

@ -1 +0,0 @@
0.6.3.0

View File

@ -1,6 +0,0 @@
#!/bin/sh
if [[ $1 == 1 ]]; then
npm run asbuild:release;
else
npm run asbuild:debug;
fi

View File

@ -1,24 +0,0 @@
{
"targets": {
"debug": {
"outFile": "../www/image-wasm.wasm",
"textFile": "../www/image-wasm.wat",
"sourceMap": true,
"debug": true
},
"release": {
"outFile": "../www/image-wasm.wasm",
"textFile": "../www/image-wasm.wat",
"sourceMap": true,
"optimizeLevel": 3,
"shrinkLevel": 0,
"converge": false,
"noAssert": false
}
},
"options": {
"exportRuntime": true,
"baseDir": ".",
"bindings": "esm"
}
}

5
wasm/image.d.ts vendored
View File

@ -1,5 +0,0 @@
type i32 = number;
type u8 = number;
type f32 = number;
type f64 = number;
type bool = boolean;

View File

@ -1,117 +0,0 @@
/// <reference path="./image.d.ts" />
export function monoGrayscale(rgba: Uint32Array, brightness: i32, alpha_as_white: bool): Uint8ClampedArray {
const mono = new Uint8ClampedArray(rgba.length);
let r: f32 = 0.0, g: f32 = 0.0, b: f32 = 0.0, a: f32 = 0.0, m: f32 = 0.0, n: i32 = 0;
for (let i: i32 = 0; i < mono.length; ++i) {
n = rgba[i];
// little endian
r = <f32>(n & 0xff), g = <f32>(n >> 8 & 0xff), b = <f32>(n >> 16 & 0xff);
a = <f32>(n >> 24 & 0xff) / 0xff;
if (a < 1 && alpha_as_white) {
a = 1 - a;
r += (0xff - r) * a;
g += (0xff - g) * a;
b += (0xff - b) * a;
} else { r *= a; g *= a; b *= a; }
m = r * 0.2125 + g * 0.7154 + b * 0.0721;
m += <f32>(brightness - 0x80) * (<f32>1 - m / 0xff) * (m / 0xff) * 2;
mono[i] = <u8>m;
}
return mono;
}
/** Note: returns a `Uint32Array` */
export function monoToRgba(mono: Uint8ClampedArray): Uint32Array {
const rgba = new Uint32Array(mono.length);
for (let i: i32 = 0; i < mono.length; ++i) {
// little endian
rgba[i] = 0xff000000 | (mono[i] << 16) | (mono[i] << 8) | mono[i];
}
return rgba;
}
export function monoDirect(mono: Uint8ClampedArray, _w: i32, _h:i32): Uint8ClampedArray {
for (let i: i32 = 0; i < mono.length; ++i) {
mono[i] = mono[i] > 0x80 ? 0xff : 0x00;
}
return mono;
}
export function monoSteinberg(mono: Uint8ClampedArray, w: i32, h: i32): Uint8ClampedArray {
let p: i32 = 0, m: i32, n: i32, o: i32;
for (let j: i32 = 0; j < h; ++j) {
for (let i: i32 = 0; i < w; ++i) {
m = mono[p];
n = mono[p] > 0x80 ? 0xff : 0x00;
o = m - n;
mono[p] = n;
if (i >= 0 && i < w - 1 && j >= 0 && j < h)
mono[p + 1] += <u8>(o * 7 / 16);
if (i >= 1 && i < w && j >= 0 && j < h - 1)
mono[p + w - 1] += <u8>(o * 3 / 16);
if (i >= 0 && i < w && j >= 0 && j < h - 1)
mono[p + w] += <u8>(o * 5 / 16);
if (i >= 0 && i < w - 1 && j >= 0 && j < h - 1)
mono[p + w + 1] += <u8>(o * 1 / 16);
++p;
}
}
return mono;
}
export function monoHalftone(mono: Uint8ClampedArray, w: i32, h: i32): Uint8ClampedArray {
const spot: i32 = 4;
const spot_h: i32 = spot / 2 + 1;
// const spot_d: i32 = spot * 2;
const spot_s: i32 = spot * spot;
let i: i32, j: i32, x: i32, y: i32, o: f64 = 0.0;
for (j = 0; j < h - spot; j += spot) {
for (i = 0; i < w - spot; i += spot) {
for (x = 0; x < spot; ++x)
for (y = 0; y < spot; ++y)
o += mono[(j + y) * w + i + x];
o = (1 - o / spot_s / 0xff) * spot;
for (x = 0; x < spot; ++x)
for (y = 0; y < spot; ++y) {
mono[(j + y) * w + i + x] = Math.abs(x - spot_h) >= o || Math.abs(y - spot_h) >= o ? 0xff : 0x00;
// mono[(j + y) * w + i + x] = Math.abs(x - spot_h) + Math.abs(y - spot_h) >= o ? 0xff : 0x00;
}
}
for (; i < w; ++i) mono[j * w + i] = 0xff;
}
for (; j < h; ++j)
for (i = 0; i < w; ++i) mono[j * w + i] = 0xff;
return mono;
}
export function monoToPbm(data: Uint8ClampedArray): Uint8ClampedArray {
const length: i32 = (data.length / 8) | 0;
const result = new Uint8ClampedArray(length);
for (let i: i32 = 0, p: i32 = 0; i < data.length; ++p) {
result[p] = 0;
for (let d: u8 = 0; d < 8; ++i, ++d)
result[p] |= data[i] & (0b10000000 >> d);
result[p] ^= 0b11111111;
}
return result;
}
/** Note: takes & gives `Uint32Array` */
export function rotateRgba(before: Uint32Array, w: i32, h: i32): Uint32Array {
/**
* w h
* o------+ +---o
* h | | | | w
* +------+ | | after
* before +---+
*/
const after = new Uint32Array(before.length);
for (let j: i32 = 0; j < h; j++) {
for (let i: i32 = 0; i < w; i++) {
after[j * w + i] = before[(w - i - 1) * h + j];
}
}
return after;
}

64
wasm/package-lock.json generated
View File

@ -1,64 +0,0 @@
{
"name": "wasm",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"dependencies": {
"assemblyscript": "^0.20.13"
}
},
"node_modules/assemblyscript": {
"version": "0.20.13",
"resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.20.13.tgz",
"integrity": "sha512-F4ACXdBdXCBnPEzRCl/ovFFPGmKXQPvWW4cM6U21eRezaCoREqxA0hjSUOYTz7txY3MJclyBfjwMSEJ5e9HKsw==",
"dependencies": {
"binaryen": "108.0.0-nightly.20220528",
"long": "^5.2.0"
},
"bin": {
"asc": "bin/asc.js",
"asinit": "bin/asinit.js"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/assemblyscript"
}
},
"node_modules/binaryen": {
"version": "108.0.0-nightly.20220528",
"resolved": "https://registry.npmjs.org/binaryen/-/binaryen-108.0.0-nightly.20220528.tgz",
"integrity": "sha512-9biG357fx3NXmJNotIuY9agZBcCNHP7d1mgOGaTlPYVHZE7/61lt1IyHCXAL+W5jUOYgmFZ260PR4IbD19RKuA==",
"bin": {
"wasm-opt": "bin/wasm-opt",
"wasm2js": "bin/wasm2js"
}
},
"node_modules/long": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz",
"integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w=="
}
},
"dependencies": {
"assemblyscript": {
"version": "0.20.13",
"resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.20.13.tgz",
"integrity": "sha512-F4ACXdBdXCBnPEzRCl/ovFFPGmKXQPvWW4cM6U21eRezaCoREqxA0hjSUOYTz7txY3MJclyBfjwMSEJ5e9HKsw==",
"requires": {
"binaryen": "108.0.0-nightly.20220528",
"long": "^5.2.0"
}
},
"binaryen": {
"version": "108.0.0-nightly.20220528",
"resolved": "https://registry.npmjs.org/binaryen/-/binaryen-108.0.0-nightly.20220528.tgz",
"integrity": "sha512-9biG357fx3NXmJNotIuY9agZBcCNHP7d1mgOGaTlPYVHZE7/61lt1IyHCXAL+W5jUOYgmFZ260PR4IbD19RKuA=="
},
"long": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz",
"integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w=="
}
}
}

View File

@ -1,10 +0,0 @@
{
"dependencies": {
"assemblyscript": "^0.20.13"
},
"scripts": {
"asbuild:debug": "asc image.ts --target debug && tsc",
"asbuild:release": "asc image.ts --target release && tsc",
"asbuild": "npm run asbuild:release"
}
}

View File

@ -1,12 +0,0 @@
{
"extends": "assemblyscript/std/assembly.json",
"include": [
"./**/*.ts"
],
"compilerOptions": {
"experimentalDecorators": true,
"outDir": "../www/",
"module": "None",
"declaration": true
}
}

View File

@ -1,2 +0,0 @@
#!/bin/sh
npx tsc $@ --allowJs --outFile main.comp.js $(cat all-scripts.txt)

View File

@ -1,125 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<!-- For replacing python-for-android webview bootstrap _load.html -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loading</title>
<style>
:root {
--fore-color: #111;
--back-color: #eee;
--notice-error: rgba(255, 0, 0, 0.2);
--font-size: 1rem;
--line-height: 1.8em;
--anim-time: 0.5s;
--span: 8px;
--span-double: calc(var(--span) * 2);
}
body {
background-color: var(--back-color);
color: var(--fore-color);
font-size: var(--font-size);
line-height: var(--line-height);
}
a:link, a:visited {
color: #33f;
}
a:hover, a:active {
color: #22f;
}
@media (prefers-reduced-motion) {
body {
transition-duration: 0ms !important;
transition: none;
animation-timing-function: steps(1);
animation-duration: 0ms !important;
}
}
@media (prefers-color-scheme: dark) {
:root {
--fore-color: #eee;
--back-color: #333;
}
}
@keyframes jump {
0% { transform: translateY(0); }
50% { transform: translateY(var(--font-size)); }
100% { transform: translateY(0); }
}
#loading-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--back-color);
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
z-index: 1;
opacity: 1;
transition: opacity var(--anim-time) var(--curve);
}
.logo {
background-image: url('icon.svg');
background-position: center;
background-repeat: no-repeat;
background-size: contain;
width: 80%;
max-width: 16em;
height: 80%;
max-height: 16em;
margin: 0 auto;
}
#loading-screen.hidden {
opacity: 0;
}
#loading-screen>.dots {
display: flex;
flex-direction: row;
justify-content: center;
}
#loading-screen>.dots>span {
display: inline-block;
width: var(--font-size);
height: var(--font-size);
margin: var(--font-size);
background-color: var(--fore-color);
border-radius: calc(var(--font-size) / 2);
animation: jump 1s ease 0s infinite;
}
#loading-screen>.dots>span:nth-child(1) { animation-delay: 0s; }
#loading-screen>.dots>span:nth-child(2) { animation-delay: 0.3s; }
#loading-screen>.dots>span:nth-child(3) { animation-delay: 0.6s; }
.noscript {
margin: var(--span-double);
text-align: center;
background-color: var(--notice-error);
display: block;
}
a {
transition: all var(--anim-time) ease-out;
}
</style>
</head>
<body>
<div id="loading-screen">
<div class="logo"></div>
<div class="dots">
<span></span>
<span></span>
<span></span>
</div>
<noscript>
<p class="noscript">
<span>Please enable JavaScript!</span><br />
<a href="jslicense.html" data-i18n="javascript-license-information"
data-jslicense="1" >JavaScript License Information</a>
</p>
</noscript>
</div>
</body>
</html>

View File

@ -1,70 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>About</title>
<link rel="stylesheet" href="main.css" />
<link rel="icon" href="icon.svg" />
</head>
<body>
<main>
<div>
<h1 data-i18n="cat-printer">Cat Printer</h1>
<p>
<span data-i18n="home-page-">Home Page:</span>
<a target="_blank" href="https://github.com/NaitLee/Cat-Printer">GitHub</a>
</p>
<h2 data-i18n="contributors">Contributors</h2>
<div class="contributors">
<dl>
<dt>
<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>
<a target="_blank" href="https://github.com/frankenstein91">frankenstein91</a>
</dt>
<dd data-i18n="collaborator">Collaborator</dd>
<dd data-i18n="translator">Translator</dd>
</dl>
<dl>
<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="collaborator">Collaborator</dd>
<dd data-i18n="translator">Translator</dd>
</dl>
<dl>
<dt>
<a target="_blank" href="https://github.com/nodlek-ctrl">nodlek-ctrl</a>
</dt>
<dd data-i18n="minor-tweaks">Minor Tweaks</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>
<h2 data-i18n="copyright-and-license">Copyright</h2>
<p>
<span>Copyright &copy; 2021-2023 NaitLee Soft.</span>
<span data-i18n="some-rights-reserved">Some rights reserved.</span>
</p>
<p class="force-ltr">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.</p>
<p class="force-ltr">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.</p>
<p class="force-ltr">You should have received a copy of the GNU General Public License along with this program. If not, see &lt;<a target="_blank" href="https://www.gnu.org/licenses/">https://www.gnu.org/licenses/</a>&gt;.</p>
</div>
</main>
</body>
</html>

View File

@ -1,124 +0,0 @@
`
No rights reserved.
License CC0-1.0-only: https://directory.fsf.org/wiki/License:CC0
`;
'use strict';
function isHidden(element) {
let parents = [element];
while (parents[0].parentElement)
parents.unshift(parents[0].parentElement);
return parents.some(e => {
let rect = e.getBoundingClientRect();
return (
e.classList.contains('hidden') ||
e.classList.contains('hard-hidden') ||
e.style.display == 'none' ||
rect.width == 0 || rect.height == 0 ||
// rect.x < 0 || rect.y < 0 ||
e.style.visibility == 'none' ||
e.style.opacity == '0'
);
});
}
function toLocaleKey(key) {
if (typeof i18n === 'undefined') return key;
const qwerty = '1234567890qwertyuiopasdfghjklzxcvbnm';
let keys, index;
if (key.length !== 1 ||
(keys = i18n('KeyboardLayout')) === 'KeyboardLayout' ||
(index = qwerty.indexOf(key)) === -1
) return key;
return keys[index];
}
function keyToLetter(key) {
const map = {
' ': 'SPACE',
',': 'COMMA',
'.': "DOT"
};
return map[key] || key;
}
function keyFromCode(code) {
const map = {
9: 'Tab'
};
return map[code] || String.fromCharCode(code);
}
function initKeyboardShortcuts() {
const layer = document.getElementById('keyboard-shortcuts-layer');
const dialog = document.getElementById('dialog');
const keys = 'qwertyuiopasdfghjklzxcvbnm';
let focusing = false, started = false;
let shortcuts = {};
let focus, inputs;
const mark_keys = () => {
document.querySelectorAll(':focus').forEach(e => e.isSameNode(focus) || e.blur());
let index, key;
if (dialog.classList.contains('hidden'))
inputs = Array.from(document.querySelectorAll('*[data-key]'));
else inputs = Array.from(document.querySelectorAll('#dialog *[data-key]'));
/** @type {{ [key: string]: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement }} */
let keys2 = keys.split('');
shortcuts = {};
if (focusing) shortcuts = { 'ESC': focus };
else
for (let input of inputs) {
if (isHidden(input)) continue;
key = input.getAttribute('data-key');
if ((index = keys2.indexOf(key)) !== -1) keys2.splice(index, 1);
key = toLocaleKey(key || keys2.shift());
shortcuts[key] = input;
}
// Array.from(layer.children).forEach(e => e.remove());
for (let i = layer.children.length; i <= inputs.length; i++) {
let span = document.createElement('span');
layer.appendChild(span);
}
index = 0;
for (key in shortcuts) {
let span = layer.children[index++];
let input = shortcuts[key];
let position = input.getBoundingClientRect();
let text = i18n(keyToLetter(key.toUpperCase()));
if (span.innerText !== text) span.innerText = text;
span.style.top = (position.y || position.top) + 'px';
span.style.left = (position.x || position.left) + 'px';
span.style.display = '';
}
for (let i = index; i < layer.children.length; i++) {
layer.children[i].style.display = 'none';
}
}
const start = () => setInterval(mark_keys, 1000);
const types_to_click = ['submit', 'file', 'checkbox', 'radio', 'A'];
document.body.addEventListener('keyup', (event) => {
let key = event.key || keyFromCode(event.keyCode);
if (!started) {
if (key !== 'Tab') return;
mark_keys();
start();
started = true;
}
requestAnimationFrame(mark_keys)
let input = shortcuts[key];
if (input) {
if (types_to_click.includes(input.type || input.tagName))
input.dispatchEvent(new MouseEvent(event.shiftKey ? 'contextmenu' : 'click'));
else {
input.focus();
focusing = true;
}
focus = input;
} else if ((key === 'Escape' || !event.isTrusted) && focus) {
focus.blur();
focusing = !focusing;
}
});
}

View File

@ -1,6 +0,0 @@
polyfill.js
i18n-ext.js
i18n.js
image.js
accessibility.js
main.js

View File

@ -1,147 +0,0 @@
`
No rights reserved.
License CC0-1.0-only: https://directory.fsf.org/wiki/License:CC0
`;
///<reference path="i18n.d.ts" />
/**
* Methods to know which string to use, per language
* Regard other as examples, and make your own!
*/
var I18nExtensions = (function() {
// Don't forget to register your extension first!
var registers = {
'en-US': English,
'zh-CN': Chinese,
'de-DE': German,
'nl-NL': Dutch,
'ar': Arabic
};
/**
* Here's especially useful for showing what can be done!
* @type {ExtensionOf<'en-US'>}
*/
function English(things, conditions) {
if (typeof conditions === 'string')
return conditions;
for (let key in things) {
let value = things[key];
if (typeof value === 'number') {
if (conditions['nth']) {
// You can also replace what would be shown!
if (value < 10 || value > 20) {
if (value % 10 === 1) things[key] = value + 'st';
else if (value % 10 === 2) things[key] = value + 'nd';
else if (value % 10 === 3) things[key] = value + 'rd';
else things[key] = value + 'th';
} else things[key] = value + 'th';
return conditions['nth'];
} else {
if (value == 1) return conditions['single'];
else return conditions['multiple'];
}
} else {
if (conditions['an']) {
if ('aeiouAEIOU'.includes(value[0]))
things[key] = 'an ' + things[key];
else things[key] = 'a ' + things[key];
return conditions['an'];
}
}
// There are many thing else. Eg. Floor counting of en-US versus en-GB
// But in a project we should take just enough
}
}
/**
* @type {ExtensionOf<'zh-CN'>}
*/
function Chinese(things, conditions) {
if (typeof conditions === 'string')
return conditions;
for (let key in things) {
function prepend(value) { things[key] = value + things[key]; }
let value = things[key];
if (typeof value === 'number') {
// 精辟。不解释。
return conditions;
} else {
if (conditions['measure']) {
// 不过要说量词的话……
switch (value) {
case '狗':
case '蛇':
prepend('条'); break;
case '猫':
case '鸡':
prepend('只'); break;
case '牛':
case '猪':
prepend('头'); break;
case '马':
case '狼':
prepend('匹'); break;
case '香蕉':
case '烧烤':
prepend('串'); break;
case '股债':
prepend('屁'); break;
default:
prepend('个');
}
// 还有很多。最好放在另一个文件
return conditions['measure'];
}
}
}
}
/**
* @type {ExtensionOf<'de-DE'>}
*/
function German(things, conditions) {
if (typeof conditions === 'string')
return conditions;
for (let index in things) {
let value = things[index];
if (typeof value === 'number') {
if (value == 1) return conditions['single'];
else return conditions['multiple'];
}
}
}
/**
* @type {ExtensionOf<'nl-NL'>}
*/
function Dutch(things, conditions) {
if (typeof conditions === 'string')
return conditions;
for (let index in things) {
let value = things[index];
if (typeof value === 'number') {
if (value == 1) return conditions['single'];
else return conditions['multiple'];
}
}
}
/**
* @type {ExtensionOf<'ar'>}
*/
function Arabic(things, conditions) {
// Another example of replacement
// const arabic_numbers = '٠١٢٣٤٥٦٧٨٩';
for (let key in things) {
let value = things[key];
if (typeof value === 'number')
things[key] = value.toLocaleString('ar');
}
return conditions;
}
return registers;
})();

43
www/i18n.d.ts vendored
View File

@ -1,43 +0,0 @@
type DictOf<T> = { [key: string]: T };
type Conditions = DictOf<string>;
type ConditionsOf<K extends Languages> = AllConditions[K];
type LanguageData = DictOf<Conditions | string>;
type Things = { [index: number | string]: number | string | boolean | null } | Array<number | string>;
interface Extension {
(things: Things, conditions: Conditions): string;
}
interface ExtensionOf<K extends Languages> {
(things: Things, conditions: ConditionsOf<K> | string): string;
}
type Languages = keyof AllConditions;
/**
* All known possible condition keys, per language
× These are what will be used in langauge files
* Please add more as you do in extensions
*/
type AllConditions = {
'en-US': {
'single': string,
'multiple': string,
'nth': string,
'a': string,
'an': string
},
'de-DE': {
'single': string,
'multiple': string
},
'nl-NL': {
'single': string,
'multiple': string
},
'zh-CN': {
'measure': string
}
};
interface I18nCallable extends I18n {
(text: string, things: Things, can_change_things: boolean): string;
}

View File

@ -1,114 +0,0 @@
`
No rights reserved.
License CC0-1.0-only: https://directory.fsf.org/wiki/License:CC0
`;
'use strict';
/**
* Yet another i18n solution
*/
class I18n {
/** @type {DictOf<LanguageData>} */
database;
/** @type {DictOf<Extension>} */
extensions;
/** @type {Languages} */
language;
constructor(language = 'en') {
this.database = {};
this.extensions = {};
this.useLanguage(language);
}
/**
* Use this language as main language
* @param {Languages} language
*/
useLanguage(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,
* or override
* @param {Languages} language
* @param {LanguageData} 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 (override || !this.database[lang][key])
this.database[lang][key] = value;
}
}
/**
* Use extension in the language
* @param {Languages} language
* @param {Extension} extension
*/
extend(language, extension) {
this.extensions[language] = extension;
}
/**
* Alias a language code to another, usually formal/more used/as fallback
* @param {DictOf<Languages>} aliases
*/
alias(aliases) {
for (let alt_code in aliases) {
let code = aliases[alt_code];
this.database[alt_code] = this.database[code];
this.extensions[alt_code] = this.extensions[code];
}
}
/**
* Translate a string ("text"), using "things" such as numbers
* @param {string} text
* @param {Things} things
*/
translate(text, things) {
let conditions = this.database[this.language][text] || text;
if (!things) return conditions;
if (this.extensions[this.language])
text = this.extensions[this.language](things, conditions);
else text = conditions;
for (let key in things) {
text = text.replace(`{${key}}`, things[key].toString());
}
return text;
}
}
/**
* An i18n instance that is directly callable
* @type {I18nCallable}
*/
var i18n = (function() {
let instance = new I18n();
/**
* @param {string} text
* @param {Things} things
*/
let i18n_callable = function(text, things) {
return instance.translate.call(i18n_callable, text, things);
}
Object.setPrototypeOf(i18n_callable, instance);
if (typeof I18nExtensions === 'object') {
for (let key in I18nExtensions)
instance.extend(key, I18nExtensions[key]);
}
return i18n_callable;
})();

View File

@ -1,98 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
inkscape:export-ydpi="255.49001"
inkscape:export-xdpi="255.49001"
inkscape:export-filename="/home/nait/图片/aaaa.png"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
sodipodi:docname="aaaa.svg"
id="svg3"
version="1.1"
viewBox="0 0 300 300"
height="96"
width="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs7" />
<sodipodi:namedview
id="namedview5"
pagecolor="#eeeeee"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:document-units="in"
showgrid="false"
inkscape:zoom="5.6568543"
inkscape:cx="46.934212"
inkscape:cy="39.067649"
inkscape:window-width="1920"
inkscape:window-height="999"
inkscape:window-x="0"
inkscape:window-y="40"
inkscape:window-maximized="1"
inkscape:current-layer="svg3"
inkscape:snap-global="false"
units="px" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect25408"
width="300"
height="300"
x="0"
y="0"
ry="30" />
<path
style="fill:none;stroke:#101010;stroke-width:3.125px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
d="m 129.17447,162.72362 c -0.12,14.38 13.76,23.38 20.88,4.13 8.38,17.25 22.25,13.37 21.62,-5.13"
id="path857" />
<path
style="fill:#101010;fill-opacity:1;stroke:#101010;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 97.971924,155.06364 c -1.859329,-0.289 -4.633499,-1.13426 -6.045924,-1.84212 -7.029665,-3.52301 -11.163386,-11.67658 -10.120473,-19.96215 0.903676,-7.17941 4.27553,-12.60514 9.533554,-15.34067 6.115005,-3.18137 15.694519,-2.92063 21.600319,0.58794 5.79394,3.44211 8.94004,10.65859 8.28563,19.00543 -0.72596,9.2594 -5.83859,15.36854 -14.43623,17.25001 -1.78814,0.39131 -7.075946,0.57217 -8.816876,0.30156 z m 4.785856,-6.90573 c 0.92722,-0.49623 1.31028,-2.47068 0.70603,-3.63918 -0.49167,-0.95078 -1.86663,-1.27278 -3.04573,-0.71326 -0.819556,0.38891 -1.125521,1.02825 -1.125521,2.35191 0,0.85861 0.09491,1.11686 0.593895,1.61584 0.485846,0.48585 0.765496,0.59389 1.537166,0.59389 0.51879,0 1.11917,-0.0941 1.33416,-0.20919 z m -8.826554,-0.49733 c 2.299279,-0.68281 3.532763,-2.60527 3.532763,-5.50601 0,-2.43596 -1.106359,-4.00165 -3.284807,-4.64858 -2.182585,-0.64816 -4.281211,-0.32469 -5.684062,0.87609 -3.641992,3.11742 -1.315378,9.47948 3.49707,9.56264 0.510513,0.009 1.383079,-0.11903 1.939036,-0.28415 z m 18.617794,-3.13527 c 3.30127,-1.28241 5.47639,-5.31708 5.15803,-9.56772 -0.21891,-2.9227 -1.17891,-4.91372 -3.04749,-6.32037 -2.73438,-2.05843 -7.72392,-2.13228 -10.68031,-0.1581 -2.60847,1.74187 -4.055476,5.28086 -3.47178,8.49098 0.70013,3.85038 2.77955,6.66135 5.73815,7.75685 1.48641,0.55039 4.626,0.44995 6.3034,-0.20165 z"
id="path1376"
inkscape:export-filename="/home/nait/图片/path1376.png"
inkscape:export-xdpi="300.00012"
inkscape:export-ydpi="300.00012" />
<path
style="fill:#101010;fill-opacity:1;stroke:#101010;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 202.99373,155.06364 c 1.85933,-0.289 4.63349,-1.13426 6.04592,-1.84212 7.02967,-3.52301 11.16339,-11.67658 10.12047,-19.96215 -0.90367,-7.17941 -4.27553,-12.60514 -9.53355,-15.34067 -6.11501,-3.18137 -15.69453,-2.92063 -21.60032,0.58794 -5.79394,3.44211 -8.94005,10.65859 -8.28563,19.00543 0.72596,9.2594 5.83858,15.36854 14.43623,17.25001 1.78814,0.39131 7.07595,0.57217 8.81688,0.30156 z m -4.78586,-6.90573 c -0.92723,-0.49623 -1.31029,-2.47068 -0.70603,-3.63918 0.49167,-0.95078 1.86663,-1.27278 3.04573,-0.71326 0.81956,0.38891 1.12552,1.02825 1.12552,2.35191 0,0.85861 -0.0949,1.11686 -0.5939,1.61584 -0.48584,0.48585 -0.7655,0.59389 -1.53716,0.59389 -0.5188,0 -1.11917,-0.0941 -1.33416,-0.20919 z m 8.82655,-0.49733 c -2.29927,-0.68281 -3.53276,-2.60527 -3.53276,-5.50601 0,-2.43596 1.10636,-4.00165 3.28481,-4.64858 2.18258,-0.64816 4.28121,-0.32469 5.68406,0.87609 3.64199,3.11742 1.31538,9.47948 -3.49707,9.56264 -0.51051,0.009 -1.38308,-0.11903 -1.93904,-0.28415 z m -18.61779,-3.13527 c -3.30128,-1.28241 -5.4764,-5.31708 -5.15803,-9.56772 0.2189,-2.9227 1.17891,-4.91372 3.04748,-6.32037 2.73439,-2.05843 7.72393,-2.13228 10.68032,-0.1581 2.60847,1.74187 4.05548,5.28086 3.47178,8.49098 -0.70013,3.85038 -2.77955,6.66135 -5.73816,7.75686 -1.48641,0.55038 -4.626,0.44994 -6.30339,-0.20166 z"
id="path1569" />
<path
id="path1570"
fill="none"
stroke="#000000"
stroke-width="1"
d="m 59.000064,91.161504 c 11.999998,-34 48.499996,-50.5 76.499996,-7 m 30,0 c 17.5,-30.5 60.5,-38.5 75.5,5.5"
style="stroke:#101010;stroke-width:6;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:#101010;fill-opacity:1;stroke:#101010;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 175.11938,86.255381 c 13.12597,-23.368892 45.37837,-29.498438 56.62922,4.214061 z"
id="path1754"
sodipodi:nodetypes="ccc" />
<path
style="fill:#101010;fill-opacity:1;stroke:#101010;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 69.03556,92.038425 c 9.000668,-26.050567 36.3777,-38.692754 57.37925,-5.363351 z"
id="path1635"
sodipodi:nodetypes="ccc" />
<g
id="g24602"
transform="translate(22.00006,20.818541)">
<rect
style="fill:none;fill-opacity:1;stroke:#101010;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect22570"
width="206.15567"
height="7.2529359"
x="24.922165"
y="172.40291" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#cccccc;stroke-width:0.6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect22674"
width="180.68272"
height="48.131855"
x="37.658638"
y="175.78719" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -1,3 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 645 B

View File

@ -1,3 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 643 B

View File

@ -1,3 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 645 B

9
www/image.d.ts vendored
View File

@ -1,9 +0,0 @@
export declare function monoGrayscale(rgba: Uint32Array, brightness: i32, alpha_as_white: bool): Uint8ClampedArray;
/** Note: returns a `Uint32Array` */
export declare function monoToRgba(mono: Uint8ClampedArray): Uint32Array;
export declare function monoDirect(mono: Uint8ClampedArray, w: i32, h: i32): Uint8ClampedArray;
export declare function monoSteinberg(mono: Uint8ClampedArray, w: i32, h: i32): Uint8ClampedArray;
export declare function monoHalftone(mono: Uint8ClampedArray, w: i32, h: i32): Uint8ClampedArray;
export declare function monoToPbm(data: Uint8ClampedArray): Uint8ClampedArray;
/** Note: takes & gives `Uint32Array` */
export declare function rotateRgba(before: Uint32Array, w: i32, h: i32): Uint32Array;

View File

@ -1,101 +0,0 @@
// deno-fmt-ignore-file
// deno-lint-ignore-file
// This code was bundled using `deno bundle` and it's not recommended to edit it manually
function monoGrayscale(rgba, brightness, alpha_as_white) {
const mono = new Uint8ClampedArray(rgba.length);
let r = 0.0, g = 0.0, b = 0.0, a = 0.0, m = 0.0, n = 0;
for(let i = 0; i < mono.length; ++i){
n = rgba[i];
r = n & 0xff, g = n >> 8 & 0xff, b = n >> 16 & 0xff;
a = (n >> 24 & 0xff) / 0xff;
if (a < 1 && alpha_as_white) {
a = 1 - a;
r += (0xff - r) * a;
g += (0xff - g) * a;
b += (0xff - b) * a;
} else {
r *= a;
g *= a;
b *= a;
}
m = r * 0.2125 + g * 0.7154 + b * 0.0721;
m += (brightness - 0x80) * (1 - m / 0xff) * (m / 0xff) * 2;
mono[i] = m;
}
return mono;
}
function monoToRgba(mono) {
const rgba = new Uint32Array(mono.length);
for(let i = 0; i < mono.length; ++i){
rgba[i] = 0xff000000 | mono[i] << 16 | mono[i] << 8 | mono[i];
}
return rgba;
}
function monoDirect(mono, _w, _h) {
for(let i = 0; i < mono.length; ++i){
mono[i] = mono[i] > 0x80 ? 0xff : 0x00;
}
return mono;
}
function monoSteinberg(mono, w, h) {
let p = 0, m, n, o;
for(let j = 0; j < h; ++j){
for(let i = 0; i < w; ++i){
m = mono[p];
n = mono[p] > 0x80 ? 0xff : 0x00;
o = m - n;
mono[p] = n;
if (i >= 0 && i < w - 1 && j >= 0 && j < h) mono[p + 1] += o * 7 / 16;
if (i >= 1 && i < w && j >= 0 && j < h - 1) mono[p + w - 1] += o * 3 / 16;
if (i >= 0 && i < w && j >= 0 && j < h - 1) mono[p + w] += o * 5 / 16;
if (i >= 0 && i < w - 1 && j >= 0 && j < h - 1) mono[p + w + 1] += o * 1 / 16;
++p;
}
}
return mono;
}
function monoHalftone(mono, w, h) {
const spot = 4;
const spot_h = 4 / 2 + 1;
const spot_s = 4 * 4;
let i, j, x, y, o = 0.0;
for(j = 0; j < h - 4; j += spot){
for(i = 0; i < w - 4; i += spot){
for(x = 0; x < 4; ++x)for(y = 0; y < 4; ++y)o += mono[(j + y) * w + i + x];
o = (1 - o / spot_s / 0xff) * spot;
for(x = 0; x < 4; ++x)for(y = 0; y < 4; ++y){
mono[(j + y) * w + i + x] = Math.abs(x - spot_h) >= o || Math.abs(y - spot_h) >= o ? 0xff : 0x00;
}
}
for(; i < w; ++i)mono[j * w + i] = 0xff;
}
for(; j < h; ++j)for(i = 0; i < w; ++i)mono[j * w + i] = 0xff;
return mono;
}
function monoToPbm(data) {
const length = data.length / 8 | 0;
const result = new Uint8ClampedArray(length);
for(let i = 0, p = 0; i < data.length; ++p){
result[p] = 0;
for(let d = 0; d < 8; ++i, ++d)result[p] |= data[i] & 0b10000000 >> d;
result[p] ^= 0b11111111;
}
return result;
}
function rotateRgba(before, w, h) {
const after = new Uint32Array(before.length);
for(let j = 0; j < h; j++){
for(let i = 0; i < w; i++){
after[j * w + i] = before[(w - i - 1) * h + j];
}
}
return after;
}
// export { monoGrayscale as monoGrayscale };
// export { monoToRgba as monoToRgba };
// export { monoDirect as monoDirect };
// export { monoSteinberg as monoSteinberg };
// export { monoHalftone as monoHalftone };
// export { monoToPbm as monoToPbm };
// export { rotateRgba as rotateRgba };

View File

@ -1,259 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title data-i18n="cat-printer">Cat Printer</title>
<link rel="stylesheet" href="main.css" />
<link rel="icon" href="icon.svg" />
</head>
<body class="hard-animation">
<main class="hard-hidden">
<div class="menu-side">
<h1 id="title" data-i18n="cat-printer">Cat Printer</h1>
<div id="notice">
</div>
<div class="menu">
<div class="panel" id="panel-print" data-default>
<div data-hide-as="print">
<label for="device-options" data-i18n="device-">Device:</label>
<select id="device-options" data-key>
</select>
<button id="device-refresh" data-i18n="scan" data-key>Scan</button>
<hr />
</div>
<div class="input-group">
<span class="label-span-input">
<span data-i18n="process-as-">Process as:</span>
<span>
<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-halftone" data-key />
<span data-i18n="pattern">Pattern</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" -->
<label class="label-span-input">
<span data-i18n="brightness-">Brightness:</span>
<input type="range" min="0" max="256" value="86" step="1" name="threshold" data-key data-default />
</label>
<!-- Thermal "Strength", so-called "darkness", or internally "energy" -->
<label class="label-span-input" data-hide-as="print">
<span data-i18n="strength-">Strength:</span>
<input type="range" min="0" max="256" value="64" step="32" name="energy" data-key data-default />
</label>
<!-- "Quality", just speed of paper feeding, but slower makes better heating -->
<label class="label-span-input" data-hide-as="print">
<span data-i18n="quality-">Quality:</span>
<input type="range" min="28" max="40" value="36" 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>
</label>
<div class="center">
<button data-i18n="show-more-options" data-show="print" data-once data-key>Show More Options</button>
</div>
</div>
<div class="panel active" id="panel-help">
<div>
<p data-i18n="coming-soon">Coming Soon...</p>
<p>
<a href="about.html" target="frame" data-i18n="about" data-key>About</a>
<a href="jslicense.html" data-jslicense="1" target="frame"
data-i18n="javascript-license-information" data-key>JavaScript License Information</a>
</p>
</div>
</div>
<div class="panel" id="panel-settings">
<div class="input-group">
<label class="label-span-input">
<span data-i18n="scan-time-">Scan Time:</span>
<input type="number" name="scan-time" min="1" max="10" value="4" data-key />
<span data-i18n="-seconds">seconds</span>
</label>
</div>
<label class="label-input-span">
<input type="checkbox" name="flip" data-key />
<span data-i18n="cat-face-toward">Cat Face Toward</span>
</label>
<!-- <hr /> -->
<label class="label-input-span">
<input type="checkbox" name="dry-run" data-key />
<span data-i18n="dry-run">Dry Run</span>
</label>
<!-- <label class="label-input-span">
<input type="checkbox" name="dump" data-key />
<span data-i18n="dump-traffic">Dump Traffic</span>
</label> -->
<button id="set-accessibility" data-key>
<span>🌎</span>
<span data-i18n="accessibility">Accessibility</span>
</button>
<button id="test-unknown-device" data-i18n="test-unknown-device" data-key>Test Unknown Device</button>
<button class="hidden" data-panel="panel-error" data-i18n="error-message" data-key>Error Message</button>
<div class="center">
<button id="button-exit" data-i18n="exit" data-key>Exit</button>
</div>
</div>
<div class="panel" id="panel-error">
<p>
<span data-i18n="you-can-seek-for-help-with-detailed-info-below">You can seek for help with detailed info below.</span>
</p>
<div id="error-record" class="selectable"></div>
</div>
</div>
<div class="compact-menu">
<button class="compact-button" data-panel="panel-print" data-i18n="print" data-key="z">Print</button>
<button class="compact-button active" data-panel="panel-help" data-i18n="help" data-key="x">Help</button>
<button class="compact-button" data-panel="panel-settings" data-i18n="settings" data-key="c">Settings</button>
</div>
<div class="center">
<!-- -->
</div>
</div>
<div class="canvas-side">
<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>
<p data-i18n="or-drag-file-to-below" class="hide-on-android">Or drag file to below</p>
</div>
</div>
<div class="canvas-group">
<canvas id="canvas" width="384" height="384"></canvas>
<canvas id="preview" width="384" height="384"></canvas>
</div>
<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-reset" data-i18n="reset" data-key>Reset</button>
<button id="button-print" data-i18n="print" data-key=" ">Print</button>
</div>
<div class="blank"></div>
</div>
</main>
<div id="hidden" class="hard-hidden">
<!-- Hidden area for putting dynamic elements -->
<input type="file" id="file" />
<img src="" id="img" alt="hidden-image" />
<div id="accessibility">
<div>
<h2>
<span>🌎</span>
<span data-i18n="language">Language</span>
</h2>
<select multiple id="select-language" data-key="a">
</select>
<br />
<span id="hint-tab-control" class="hide-on-android"
data-i18n="to-enter-keyboard-mode-press-tab">To enter Keyboard Mode, press Tab</span>
<br />
</div>
<div>
<h2 data-i18n="layout">Layout</h2>
<label class="label-input-span">
<input type="checkbox" name="dark-theme" data-key />
<span data-i18n="dark-theme">Dark Theme</span>
</label>
<label class="label-input-span">
<input type="checkbox" name="high-contrast" data-key />
<span data-i18n="high-contrast">High Contrast</span>
</label>
<label class="label-input-span">
<input type="checkbox" name="no-animation" data-key />
<span data-i18n="disable-animation">Disable Animation</span>
</label>
<label class="label-input-span">
<input type="checkbox" name="force-rtl" data-key />
<span data-i18n="right-to-left-text-order">Right-to-left text order</span>
</label>
<label class="label-input-span">
<input type="checkbox" name="large-font" data-key />
<span data-i18n="large-font">Large Font</span>
</label>
</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" 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>
<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" 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" 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" data-key />
<span class="text-align-checkmark text-align-right"></span>
</span>
</div>
</div>
<div id="text-textarea">
<textarea id="insert-text-area" data-key="/"></textarea>
</div>
</div>
</div>
<iframe id="frame" src="about:blank" name="frame" title="frame" data-key="v"></iframe>
</div>
<div id="dialog" class="hidden">
<div class="shade"></div>
<div class="content">
<div id="dialog-content">
<!-- Dialog content -->
</div>
<div id="dialog-choices">
<input id="dialog-input" type="text" placeholder="" data-key="m">
<hr />
</div>
</div>
</div>
<div id="loading-screen">
<div class="logo"></div>
<div class="dots">
<span></span>
<span></span>
<span></span>
</div>
<noscript>
<p class="noscript">
<span>Please enable JavaScript!</span><br />
<a href="jslicense.html" data-i18n="javascript-license-information"
data-jslicense="1" tabindex="1" >JavaScript License Information</a>
</p>
</noscript>
</div>
<div id="keyboard-shortcuts-layer"></div>
<script src="loader.js"></script>
</body>
</html>

View File

@ -1,102 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript License Information</title>
<link rel="stylesheet" href="main.css" />
<link rel="icon" href="icon.svg" />
</head>
<body>
<main>
<div>
<h1 data-i18n="javascript-license-information">JavaScript License Information</h1>
<p data-i18n="you-can-see-all-javascript-programs-used">You can see all JavaScript programs used along with this application are Free Software.<sup><a href="#footer-1">[1]</sup></a></p>
<!-- https://www.gnu.org/software/librejs/manual/html_node/Free-Licenses-Detection.html -->
<div class="table-wrap">
<table id="jslicense-labels1">
<thead>
<tr>
<td data-i18n="javascript-resource">Resource</td>
<td data-i18n="javascript-license">License</td>
<td data-i18n="javascript-source">Source</td>
<td data-i18n="javascript-description">Description</td>
</tr>
</thead>
<tbody>
<tr>
<td><a href="~every.js">~every.js</a></td>
<td><a href="https://www.gnu.org/licenses/gpl-3.0.html">GPL-3.0</a></td>
<td><a href="main.js">main.js</a></td>
<td data-i18n="javascript-everyjs-description">Dynamic concatenation of all development scripts</td>
</tr>
<tr>
<td><a href="main.comp.js">main.comp.js</a></td>
<td><a href="https://www.gnu.org/licenses/gpl-3.0.html">GPL-3.0</a></td>
<td><a href="main.js">main.js</a></td>
<td data-i18n="javascript-maincompjs-description">All following development scripts, transpiled for compatibility.</td>
</tr>
<tr>
<td><a href="loader.js">loader.js</a></td>
<td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0</a></td>
<td><a href="loader.js">loader.js</a></td>
<td data-i18n="javascript-loaderjs-description">For dynamically loading other scripts, and fallback if there are problems.</td>
</tr>
<tr>
<td><a href="polyfill.js">polyfill.js</a></td>
<td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0</a></td>
<td><a href="polyfill.js">polyfill.js</a></td>
<td data-i18n="javascript-polyfilljs-description">Adds features that are not supported by old browsers.</td>
</tr>
<tr>
<td><a href="i18n-ext.js">i18n-ext.js</a></td>
<td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0</a></td>
<td><a href="i18n-ext.js">i18n-ext.js</a></td>
<td data-i18n="javascript-i18nextjs-description">I18n "extensions"</td>
</tr>
<tr>
<td><a href="i18n.js">i18n.js</a></td>
<td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0</a></td>
<td><a href="i18n.js">i18n.js</a></td>
<td data-i18n="javascript-i18njs-description">For internationalization (language support)</td>
</tr>
<tr>
<td><a href="image.js">image.js</a></td>
<td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0</a></td>
<td><a href="image.js">image.js</a></td>
<td data-i18n="javascript-imagejs-description">For canvas image manipulation</td>
</tr>
<tr>
<td><a href="accessibility.js">accessibility.js</a></td>
<td><a href="http://creativecommons.org/publicdomain/zero/1.0/legalcode">CC0-1.0</a></td>
<td><a href="accessibility.js">accessibility.js</a></td>
<td data-i18n="javascript-accessibilityjs-description">Accessibility features</td>
</tr>
<tr>
<td><a href="main.js">main.js</a></td>
<td><a href="https://www.gnu.org/licenses/gpl-3.0.html">GPL-3.0</a></td>
<td><a href="main.js">main.js</a></td>
<td data-i18n="javascript-catprinter-description">The main script for Cat-Printer</td>
</tr>
</tbody>
</table>
</div>
</div>
</main>
<hr />
<footer>
<dl id="footer-1">
<dt>
<span>[1]</span>
<a data-i18n="free-software" href="https://www.gnu.org/philosophy/free-sw.html">Free Software</a>
</dt>
<dd>
<span data-i18n="free-software-description">Software which respects your computing freedom.</span>
</dd>
</dl>
</footer>
</body>
</html>

View File

@ -1,152 +0,0 @@
{
"$language": "Deutsch",
"KeyboardLayout": "1234567890qwertzuiopasdfghjklyxcvbnm",
"cat-printer": "Cat Printer",
"printer": "Drucker",
"device-": "Gerät:",
"refresh": "Aktualisieren",
"scan": "Scannen",
"mode-": "Modus:",
"canvas": "Leinwand",
"document": "Dokument",
"insert-picture": "Bild einfügen",
"insert-text": "Text einfügen",
"help": "Hilfe",
"javascript-license-information": "Informationen zur JavaScript-Lizenz",
"settings": "Einstellungen",
"image": "Bild",
"threshold-": "Schwellenwert",
"transmission-speed-": "Übertragungsgeschwindigkeit:",
"low": "niedrig",
"moderate": "moderat",
"high": "hoch",
"transparent-as-white": "Transparenz als weiß",
"misc": "Sonstiges",
"system": "System",
"disable-animation": "Animationen deaktivieren",
"exit": "Beenden",
"error-message": "Fehlermeldung",
"preview": "Vorschau",
"print": "Drucken",
"expand": "Erweitern",
"crop": "Zuschneiden",
"scanning-for-devices": "Suche nach Geräten…",
"scan-time-": "Scanzeit:",
"-seconds": "Sekunden",
"no-available-devices-found": "Keine verfügbaren Geräte gefunden",
"found-0-available-devices": {
"single": "{0} verfügbares Gerät gefunden",
"multiple": "{0} verfügbare Geräte gefunden"
},
"please-check-if-the-printer-is-down": "Bitte prüfe, ob der Drucker eingeschaltet ist",
"printing": "Druckt…",
"finished": "Fertiggestellt",
"coming-soon": "Demnächst verfügbar…",
"dry-run": "Testlauf",
"dry-run-test-print-process-only": "Testlauf: nur Probedruckvorgang",
"you-can-close-this-page-manually": "Sie können diese Seite manuell schließen",
"please-enable-bluetooth": "Bitte aktivieren Sie Bluetooth",
"error-happened-please-check-error-message": "Fehler aufgetreten, bitte Fehlermeldung einsehen",
"you-can-seek-for-help-with-detailed-info-below": "Sie können mit den nachstehenden ausführlichen Informationen Hilfe bekommen",
"or-try-to-scan-longer": "Oder erhöhen Sie die Scanzeit",
"print-to-cat-printer": "PBM-Bild auf Cat Printer drucken",
"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",
"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",
"folder-printer_lib-is-incomplete-or-missing-please-check": "Die Dateien im Ordner `printer_lib` konnten nicht gefunden werden. Bitte überprüfen Sie, ob der Ordner existiert und die benötigten Dateien enthält.",
"input-is-not-pbm-image": "Die Eingabe ist kein PBM-Bild",
"unsuitable-image-width-expected-0-got-1": "Ungeeignete Bildbreite, erwartet {0}, erhalten {1}",
"broken-pbm-image": "Defektes PBM-Bild",
"input-is-not-text-file": "Eingabe ist keine Textdatei",
"match-printer-with-this-name-or-address": "Drucker mit diesem Namen oder dieser Adresse abgleichen",
"virtual-run-on-specified-model": "Virtueller Prozess für ein bestimmtes Modell",
"font-size-0": "Schriftgrad {0}",
"stopping": "stoppe",
"connecting": "verbinde",
"model-0-is-not-supported-yet": "Modell '{0}' wird noch nicht unterstützt",
"invalid-address-0": "Ungültige Adresse: '{0}'",
"will-listen-on-all-addresses": "Akzeptiert alle Adressen",
"serving-at-0": "Bereitgestellt auf der Adresse {0}",
"disconnecting-from-printer": "Verbindung zum Drucker trennen",
"connected-to-0-1": "Mit {0} {1} verbunden",
"flip-horizontally": "Horizontal spiegeln",
"flip-vertically": "Vertikal spiegeln",
"dump-traffic": "Datenverkehr ausgeben",
"right-to-left-text-order": "Text von rechts-nach-links ausrichten",
"auto-wrap-line": "automatischer Zeilenumbruch",
"wrap-text": "Textumbruch",
"process-as-": "Verarbeiten als:",
"text": "Text",
"picture": "Bild",
"pattern": "Muster",
"large-font": "Große Schrift",
"accessibility": "Barrierefreiheit",
"language": "Sprache",
"layout": "Layout",
"ok": "OK",
"cancel": "Abbrechen",
"yes": "Ja",
"no": "Nein",
"about": "Über",
"home-page-": "Homepage:",
"contributors": "Mitwirkende",
"developer": "Entwickler",
"collaborator": "Collaborator",
"translator": "Übersetzer",
"all-users-and-developers": "Alle Tester & Benutzer",
"everyone-is-awesome": "Ihr seid alle fantastisch",
"license": "Lizenz",
"exiting": "Beende…",
"dark-theme": "Dunkles Design",
"high-contrast": "Hoher Kontrast",
"copyright-and-license": "Urheberrecht und Lizenz",
"welcome": "Willkommen!",
"some-rights-reserved": "Einige Rechte sind vorbehalten.",
"text-font": "Schriftart",
"text-size": "Textgröße",
"enter-text": "Text eingeben",
"show-more-options": "Mehr Optionen anzeigen",
"strength-": "Stärke:",
"reset": "Zurücksetzen",
"brightness-": "Helligkeit:",
"quality-": "Qualität:",
"or-drag-file-to-below": "Oder Datei hier ablegen",
"to-enter-keyboard-mode-press-tab": "Drücken Sie TAB, um den Tastaturmodus zu aktivieren",
"you-can-see-all-javascript-programs-used": "Wie Sie sehen können, verwendet dieses Programm nur Freie Software.",
"javascript-resource": "Bibliothek",
"javascript-license": "Lizenz",
"javascript-source": "Verwendet in",
"javascript-description": "Beschreibung",
"javascript-everyjs-description": "Dynamisches Zusammenführen der Entwicklungs-Skripte.",
"javascript-maincompjs-description": "Transpilieren der Entwicklungsskripte für verbesserte Kompatibilität.",
"javascript-loaderjs-description": "Dynamisches Laden von Skripten und Fallbacks.",
"javascript-polyfilljs-description": "Nachrüsten von Funktionen in nicht unterstützten Browsern",
"javascript-i18nextjs-description": "I18n „Erweiterungen“",
"javascript-i18njs-description": "Lokalisierung und Übersetzung",
"javascript-imagejs-description": "Bildbearbeitung",
"javascript-accessibilityjs-description": "Barrierefreiheit",
"javascript-catprinter-description": "Die Hauptskripte von Cat-Printer",
"cat-face-toward": "An Katzengesicht ausrichten",
"free-software": "Freie Software",
"free-software-description": "Software, die Ihre Freiheit und Gemeinschaft respektiert",
"wrap-words-by-spaces": "Autom. Zeilenumbruch",
"minor-tweaks": "Kleine Anpassungen",
"serif": "Serif",
"sans-serif": "Sans Serif",
"monospace": "Monospace",
"rotate-image": "Bild drehen",
"test-unknown-device": "Unbekanntes Gerät testen",
"now-will-scan-for-all-bluetooth-devices-nearby": "Der Scan sucht jetzt nach allen Bluetooth-Geräten in der Nähe",
"pf2-font-not-found-or-broken-0": "PF2 font nicht gefunden oder defekt: '{0}'",
"try-to-print-through-an-unknown-device": "Versuch mit unbekanntem Gerät zu drucken",
"scanning-for-all-bluetooth-devices-nearby": "Scanne nach allen Bluertooth-Geräten in der Nähe…",
"there-are-multiple-devices-": "Mehrere Geräte gefunden:",
"choose-which-one-0-": "Geräteauswahl? [{0}]: ",
"multiple-devices-found-please-specify-one": "Mehrere Geräte gefunden, bitte Auswahl treffen",
"no-prompt-for-multiple-devices": "keine Aufforderung für mehrere Geräte"
}

View File

@ -1,16 +0,0 @@
{
"$language": "English (US) i18n example",
// plural
"0-apples": {
"single": "{0} apple",
"multiple": "{0} apples"
},
// order
"0th-apple": {
"nth": "{0} apple"
},
// a or an
"there-is-a-0": {
"an": "There is {0}"
}
}

View File

@ -1,169 +0,0 @@
{
"$language": "English (US)",
"cat-printer": "Cat Printer",
"printer": "Printer",
"device-": "Device:",
"refresh": "Refresh",
"mode-": "Mode:",
"canvas": "Canvas",
"document": "Document",
"insert-picture": "Insert Picture",
"insert-text": "Insert Text",
"help": "Help",
"javascript-license-information": "JavaScript License Information",
"settings": "Settings",
"image": "Image",
"threshold-": "Threshold",
"transmission-speed-": "Transmission Speed:",
"low": "Low",
"moderate": "Moderate",
"high": "High",
"transparent-as-white": "Transparent as White",
"misc": "Misc",
"system": "System",
"disable-animation": "Disable Animation",
"exit": "Exit",
"error-message": "Error Message",
"preview": "Preview",
"print": "Print",
"expand": "Expand",
"crop": "Crop",
"scanning-for-devices": "Scanning for devices…",
"scan-time-": "Scan time:",
"-seconds": "seconds",
"no-available-devices-found": "No available devices found",
"found-0-available-devices": {
"single": "Found {0} available device",
"multiple": "Found {0} available devices"
},
"please-check-if-the-printer-is-down": "Please check if the printer is down",
"printing": "Printing…",
"finished": "Finished",
"coming-soon": "Coming Soon…",
"dry-run": "Dry Run",
"dry-run-test-print-process-only": "Dry Run: test print process only",
"you-can-close-this-page-manually": "You can close this page manually",
"please-enable-bluetooth": "Please enable Bluetooth",
"error-happened-please-check-error-message": "Error happened, please check error message",
"you-can-seek-for-help-with-detailed-info-below": "You can seek for help with detailed info below",
"or-try-to-scan-longer": "Or try to scan longer",
"print-to-cat-printer": "Print to Cat Printer",
"supported-models-": "Supported models:",
"path-to-input-file-dash-for-stdin": "Path to input file. '-' for stdin",
"dump-the-traffic": "Dump the traffic",
"please-install-pyobjc-via-pip": "Please install `pyobjc` via pip",
"please-install-bleak-via-pip": "Please install `bleak` via pip",
"folder-printer_lib-is-incomplete-or-missing-please-check": "Folder `printer_lib` is incomplete or missing, please check",
"input-is-not-pbm-image": "Input is not PBM image",
"unsuitable-image-width-expected-0-got-1": "Unsuitable image width, expected {0}, got {1}",
"broken-pbm-image": "Broken PBM image",
"input-is-not-text-file": "Input is not text file",
"match-printer-with-this-name-or-address": "Match printer with this name or address",
"virtual-run-on-specified-model": "Virtual run on specified model",
"font-size-0": "Font size {0}",
"stopping": "Stopping",
"connecting": "Connecting",
"model-0-is-not-supported-yet": "Model '{0}' is not supported yet",
"invalid-address-0": "Invalid address: '{0}'",
"will-listen-on-all-addresses": "Will listen on ALL addresses",
"serving-at-0": "Serving at {0}",
"disconnecting-from-printer": "Disconnecting from printer",
"connected-to-0-1": "Connected to {0} {1}",
"flip-horizontally": "Flip Horizontally",
"flip-vertically": "Flip Vertically",
"dump-traffic": "Dump Traffic",
"right-to-left-text-order": "Right-to-left text order",
"auto-wrap-line": "Auto wrap line",
"process-as-": "Process as:",
"text": "Text",
"picture": "Picture",
"pattern": "Pattern",
"large-font": "Large Font",
"accessibility": "Accessibility",
"language": "Language",
"layout": "Layout",
"ok": "OK",
"cancel": "Cancel",
"yes": "Yes",
"no": "No",
"about": "About",
"home-page-": "Home Page:",
"contributors": "Contributors",
"developer": "Developer",
"collaborator": "Collaborator",
"translator": "Translator",
"all-users-and-developers": "All users & developers",
"everyone-is-awesome": "Everyone is awesome!",
"license": "License",
"exiting": "Exiting…",
"dark-theme": "Dark Theme",
"high-contrast": "High Contrast",
"welcome": "Welcome!",
"copyright-and-license": "Copyright and License",
"some-rights-reserved": "Some rights reserved.",
"ENTER": "Enter",
"SPACE": "Space",
"ESCAPE": "Esc",
"TAB": "Tab",
"COMMA": "Comma",
"DOT": "Dot",
"to-enter-keyboard-mode-press-tab": "To enter Keyboard Mode, press Tab",
"usage-": "Usage:",
"positional-arguments-": "Positional arguments:",
"options-": "Options:",
"show-this-help-message": "Show this help message",
"do-nothing": "Do nothing",
"scan-for-a-printer": "Scan for a printer",
"text-printing-mode-with-options": "Text printing mode with options",
"image-printing-options": "Image printing options",
"convert-input-image-with-imagemagick": "Convert input image with ImageMagick",
"reset-configuration-": "Reset configuration?",
"brightness-": "Brightness:",
"text-printing-mode": "Text Printing Mode",
"internal-error-please-see-terminal": "Internal error, please see terminal",
"control-printer-thermal-strength": "Control printer thermal strength",
"strength-": "Strength:",
"or-drag-file-to-below": "Or drag file to below",
"reset": "Reset",
"cat-face-toward": "Cat Face Toward",
"quality-": "Quality:",
"print-quality": "Print quality",
"show-more-options": "Show More Options",
"text-font": "Font",
"text-size": "Size",
"enter-text": "Enter text",
"wrap-text": "Wrap text",
"you-can-see-all-javascript-programs-used": "You can see all JavaScript programs used along with this application are Free Software.",
"javascript-resource": "Resource",
"javascript-license": "License",
"javascript-source": "Source",
"javascript-description": "Description",
"javascript-everyjs-description": "Dynamic concatenation of all development scripts",
"javascript-maincompjs-description": "All following development scripts, transpiled for compatibility.",
"javascript-loaderjs-description": "For dynamically loading other scripts, and fallback if there are problems.",
"javascript-polyfilljs-description": "Add features which are not supported by old browsers.",
"javascript-i18nextjs-description": "I18n “extensions”",
"javascript-i18njs-description": "For internationalization (language support)",
"javascript-imagejs-description": "For canvas image manipulation",
"javascript-accessibilityjs-description": "Accessibility features",
"javascript-catprinter-description": "The main script for Cat-Printer",
"free-software": "Free Software",
"free-software-description": "Software which respects your computing freedom.",
"wrap-words-by-spaces": "Wrap words by spaces",
"minor-tweaks": "Minor Tweaks",
"serif": "Serif",
"sans-serif": "Sans Serif",
"monospace": "Monospace",
"rotate-image": "Rotate Image",
"test-unknown-device": "Test Unknown Device",
"scan": "Scan",
"pf2-font-not-found-or-broken-0": "PF2 font not found or broken: '{0}'",
"imagemagick-not-found": "ImageMagick not found, please install it and retry.",
"try-to-print-through-an-unknown-device": "Try to print through an unknown device",
"scanning-for-all-bluetooth-devices-nearby": "Scanning for all bluetooth devices nearby…",
"there-are-multiple-devices-": "There are multiple devices:",
"choose-which-one-0-": "Choose which one? [{0}]: ",
"multiple-devices-found-please-specify-one": "Multiple devices found, please specify one",
"no-prompt-for-multiple-devices": "No prompt for multiple devices"
}

View File

@ -1,11 +0,0 @@
{
"en-US": "English (US)",
"de-DE": "Deutsch",
"pl-PL": "Polski (Polska)",
"zh-CN": "中文(简体字)",
"zh-TW": "中文(正體字)",
"zh-HK": "中文(香港字)",
"lolcat": "LOLCAT",
"zh-Hant-CN": "中文(傳統字)"
}

View File

@ -1,148 +0,0 @@
{
"$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 2 HOM",
"error-message": "SOMTHIN WRONG",
"preview": "LOOK",
"print": "PAW STEP",
"expand": "MORE",
"crop": "LESS",
"scanning-for-devices": "LUKIN 4 KITTEZ",
"scan-time-": "HOW LONG 2 FIND",
"-seconds": "SECS",
"no-available-devices-found": "NO KITTE FINDZ",
"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 2 FIND BIT LONGER",
"print-to-cat-printer": "PAWS 4 KITTEZ 2 STEP!!",
"supported-models-": "KNOWN KITTEZ>",
"path-to-input-file-dash-for-stdin": "WHAT FILE 2 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 2 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",
"collaborator": "FRIENZ",
"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 2 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 2 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",
"test-unknown-device": "I HAV STRENGE KITTE",
"now-will-scan-for-all-bluetooth-devices-nearby": "WIL FIND ALL THINY KITTE OR NOT",
"scan": "FIND",
"pf2-font-not-found-or-broken-0": "KITTE FONT LOST OR DEAD> {0}",
"image-magick-not-found": "IMAGEMAGICK NOT FINDZ, PLZ INSTALL IT AND HAVES ANODA GO.",
"try-to-print-through-an-unknown-device": "STEP WIZ STRENGE KITTE",
"scanning-for-all-bluetooth-devices-nearby": "LOOKIN 4 KITTEZ…",
"there-are-multiple-devices-": "MANY KITTEZ!!",
"choose-which-one-0-": "ADOPT A KITTE PLZ [{0}]: ",
"multiple-devices-found-please-specify-one": "MANY KITTEZ!! ADOPT ONE PLZ",
"no-prompt-for-multiple-devices": "ADOPT KITTE ONCE FINDZ ONE"
}

View File

@ -1,152 +0,0 @@
{
"$language": "nederlands",
"KeyboardLayout": "1234567890qwertyuiopasdfghjklzxcvbnm",
"cat-printer": "Cat Printer",
"printer": "printer",
"device-": "apparaat:",
"refresh": "vernieuwen",
"scan": "scannen",
"mode-": "Modus:",
"canvas": "canvas",
"document": "document",
"insert-picture": "afbeelding invoegen",
"insert-text": "tekst invoegen",
"help": "Hulp",
"javascript-license-information": "informatie over de javascript-licentie",
"settings": "instellingen",
"image": "afbeelding",
"threshold-": "drempelwaarde",
"transmission-speed-": "transmissie snelheid:",
"low": "laag",
"moderate": "gematigt",
"high": "hoog",
"transparent-as-white": "transparant als wit",
"misc": "gemengt",
"system": "systeem",
"disable-animation": "animatie uitschakelen",
"exit": "beëindigen",
"error-message": "foutmeldingen",
"preview": "voorbeeld",
"print": "afdrukken",
"expand": "uitbreiden",
"crop": "Bijsnijden",
"scanning-for-devices": "scannen naar apparaten…",
"scan-time-": "scan tijd:",
"-seconds": "Seconden",
"no-available-devices-found": "geen beschikbare apparaten gevonden",
"found-0-available-devices": {
"single": "{0} beschikbaar apparaat gevonden",
"multiple": "{0} beschikbaar apparaten gevonden"
},
"please-check-if-the-printer-is-down": "controleer of de printer is ingeschakeld",
"printing": "afdrukken…",
"finished": "afgerond",
"coming-soon": "binnenkort beschikbaar…",
"dry-run": "proefdraaien",
"dry-run-test-print-process-only": "proefdraaien: alleen testafdrukproces",
"you-can-close-this-page-manually": "U kunt deze pagina handmatig sluiten",
"please-enable-bluetooth": "Schakel Bluetooth in",
"error-happened-please-check-error-message": "Er is een fout opgetreden, zie de foutmelding",
"you-can-seek-for-help-with-detailed-info-below": "U kunt hulp krijgen met de gedetailleerde informatie hieronder",
"or-try-to-scan-longer": "Of verhoog de scantijd",
"print-to-cat-printer": "PBM-afbeelding afdrukken naar Cat Printer",
"supported-models-": "ondersteunde modellen:",
"path-to-input-file-dash-for-stdin": "pad naar bestand '-' for stdin",
"scan-for-specified-seconds": "zoekloop voor de opgegeven duur",
"text-printing-mode": "tekstafdrukmodus",
"please-install-pyobjc-via-pip": "installeer `pyobjc` via pip",
"please-install-bleak-via-pip": "installeer `bleak` via pip",
"folder-printer_lib-is-incomplete-or-missing-please-check": "De bestanden in de map `printer_lib` konden niet worden gevonden. Controleer of de map bestaat en de vereiste bestanden bevat.",
"input-is-not-pbm-image": "de invoer is geen pbm-afbeelding",
"unsuitable-image-width-expected-0-got-1": "Onjuiste afbeeldingsbreedte, verwacht {0}, kreeg {1}",
"broken-pbm-image": "gebroken PBM-Beeld",
"input-is-not-text-file": "Invoer is geen tekstbestand",
"match-printer-with-this-name-or-address": "Koppel de printer aan deze naam of dit adres",
"virtual-run-on-specified-model": "Virtueel proces voor een specifiek model",
"font-size-0": "lettertypegrootte {0}",
"stopping": "stoppen",
"connecting": "aansluiten",
"model-0-is-not-supported-yet": "Model '{0}' nog niet ondersteund",
"invalid-address-0": "Ungültige Adresse: '{0}'",
"will-listen-on-all-addresses": "zal luisteren op alle adressen",
"serving-at-0": "vermeld op het adres {0}",
"disconnecting-from-printer": "Koppel de printer los",
"connected-to-0-1": "Met {0} {1} verbonden",
"flip-horizontally": "Horizontaal omkeren",
"flip-vertically": "Verticaal omkeren",
"dump-traffic": "dump dataverkeer",
"right-to-left-text-order": "Tekst van rechts naar links uitlijnen",
"auto-wrap-line": "automatisch regeleinde",
"wrap-text": "tekstomloop",
"process-as-": "Verwerken als:",
"text": "Tekst",
"picture": "Beeld",
"pattern": "Patroon",
"large-font": "groot lettertype",
"accessibility": "toegankelijkheid",
"language": "Taal",
"layout": "Layout",
"ok": "OK",
"cancel": "annuleren",
"yes": "Ja",
"no": "Nee",
"about": "over",
"home-page-": "Startpagina:",
"contributors": "bijdragers",
"developer": "ontwikkelaar",
"collaborator": "Collaborator",
"translator": "vertaler",
"all-users-and-developers": "alle testers en gebruikers",
"everyone-is-awesome": "iedereen is geweldig",
"license": "licentie",
"exiting": "verlaten…",
"dark-theme": "donker thema",
"high-contrast": "hoog contrast",
"copyright-and-license": "Auteursrecht en licentiez",
"welcome": "Welkom!",
"some-rights-reserved": "Sommige rechten zijn voorbehouden.",
"text-font": "lettertype",
"text-size": "lettergrootte",
"enter-text": "Tekst invoegen",
"show-more-options": "Toon meer opties",
"strength-": "kracht:",
"reset": "resetten",
"brightness-": "helderheid:",
"quality-": "kwaliteit:",
"or-drag-file-to-below": "Of zet het bestand hier neer",
"to-enter-keyboard-mode-press-tab": "Druk op TAB om de toetsenbordmodus te activeren",
"you-can-see-all-javascript-programs-used": "Zoals u kunt zien, gebruikt dit programma alleen Vrije Software.",
"javascript-resource": "bibliotheek",
"javascript-license": "licentie",
"javascript-source": "gebruikt in",
"javascript-description": "Beschrijving",
"javascript-everyjs-description": "Dynamische samenvoeging van ontwikkelscripts.",
"javascript-maincompjs-description": "Transpileer de ontwikkelingsscripts voor verbeterde compatibiliteit.",
"javascript-loaderjs-description": "Dynamisch laden van scripts en fallbacks.",
"javascript-polyfilljs-description": "Functies achteraf inbouwen in niet-ondersteunde browsers",
"javascript-i18nextjs-description": "I18n „uitbreidingen“",
"javascript-i18njs-description": "Lokalisatie en vertaling",
"javascript-imagejs-description": "beeldbewerking",
"javascript-accessibilityjs-description": "Toegankelijkheid",
"javascript-catprinter-description": "De hoofdscripts van Cat-Printer",
"cat-face-toward": "Lijn uit met kattengezicht",
"free-software": "vrije Software",
"free-software-description": "Software die uw vrijheid en gemeenschap respecteert",
"wrap-words-by-spaces": "Automatische regeleinde",
"minor-tweaks": "kleine aanpassingen",
"serif": "Serif",
"sans-serif": "Sans Serif",
"monospace": "Monospace",
"rotate-image": "afbeelding roteren",
"test-unknown-device": "onbekend apparaat testen",
"now-will-scan-for-all-bluetooth-devices-nearby": "De scan zoekt nu naar alle Bluetooth-apparaten in de buurt",
"pf2-font-not-found-or-broken-0": "PF2 font niet gevonden of defect: '{0}'",
"try-to-print-through-an-unknown-device": "probeer af te drukken met een onbekend apparaat",
"scanning-for-all-bluetooth-devices-nearby": "Scannen naar alle Bluetooth-apparaten in de buurt…",
"there-are-multiple-devices-": "meerdere apparaten gevonden:",
"choose-which-one-0-": "apparaat selectie? [{0}]: ",
"multiple-devices-found-please-specify-one": "Meerdere apparaten gevonden, maak een keuze",
"no-prompt-for-multiple-devices": "geen prompt voor meerdere apparaten"
}

View File

@ -1,169 +0,0 @@
{
"$language": "Polski (Polska)",
"cat-printer": "Kocia Drukarka",
"printer": "Drukarka",
"device-": "Urządzenie:",
"refresh": "Odśwież",
"mode-": "Tryb:",
"canvas": "Płótno",
"document": "Dokument",
"insert-picture": "Wstaw obraz",
"insert-text": "Wstaw tekst",
"help": "Pomoc",
"javascript-license-information": "O Licencji JavaScript",
"settings": "Ustawienia",
"image": "Obraz",
"threshold-": "Próg",
"transmission-speed-": "Prędkość Transmisji:",
"low": "Niska",
"moderate": "Średnia",
"high": "Wysoka",
"transparent-as-white": "Prezroczysty jako biały",
"misc": "Inne",
"system": "System",
"disable-animation": "Wyłącz animacje",
"exit": "Wyjdź",
"error-message": "Kod błędu",
"preview": "Podgląd",
"print": "Drukuj",
"expand": "Powiększ",
"crop": "Przytnij",
"scanning-for-devices": "Szukanie urządzeń…",
"scan-time-": "Czas szukania:",
"-seconds": "sekund",
"no-available-devices-found": "Nie znaleziono urządzeń",
"found-0-available-devices": {
"single": "Znaleziono {0} dostępne urządzenie",
"multiple": "Znaleziono {0} dostępnych urządzeń"
},
"please-check-if-the-printer-is-down": "Sprawdź, czy drukarka działa",
"printing": "Drukowanie…",
"finished": "Gotowe",
"coming-soon": "Już Niedługo…",
"dry-run": "Na sucho",
"dry-run-test-print-process-only": "Na sucho: przetestuj funkcję druku",
"you-can-close-this-page-manually": "Możesz zamknąć tą kartę",
"please-enable-bluetooth": "Proszę włączyć Bluetooth",
"error-happened-please-check-error-message": "Wystąpił problem, sprawdź kod błędu.",
"you-can-seek-for-help-with-detailed-info-below": "Szukaj pomocy dla poniższych detali",
"or-try-to-scan-longer": "Albo spróbuj skanować dłużej",
"print-to-cat-printer": "Wydrukuj na Kociej Drukarce",
"supported-models-": "Wspierane modele:",
"path-to-input-file-dash-for-stdin": "Ścieżka do pliku. '-' dla stdin",
"dump-the-traffic": "Zrzut ruchu",
"please-install-pyobjc-via-pip": "Proszę zainstalować `pyobjc` przez pip",
"please-install-bleak-via-pip": "Proszę zainstalować `bleak` przez pip",
"folder-printer_lib-is-incomplete-or-missing-please-check": "Folder `printer_lib` jest niekompletny lub brakujący, proszę sprawdzić.",
"input-is-not-pbm-image": "Dane wejściowe nie są obrazem PBM",
"unsuitable-image-width-expected-0-got-1": "Złe wymiary obrazu, spodziewano się {0}, jest {1}",
"broken-pbm-image": "Uszkodzony obraz PBM",
"input-is-not-text-file": "Dane wejściowe nie są plikiem tekstowym.",
"match-printer-with-this-name-or-address": "Dopasuj drukarkę do tej nazwy lub adresu",
"virtual-run-on-specified-model": "Wirtualny przebieg na wybranym modelu",
"font-size-0": "Rozmiar czcionki {0}",
"stopping": "Zatrzymywanie",
"connecting": "Łączenie",
"model-0-is-not-supported-yet": "Model '{0}' nie jest jeszcze wspierany",
"invalid-address-0": "Niewłaściwy adres: '{0}'",
"will-listen-on-all-addresses": "Szukanie na WSZYSTKICH adresach",
"serving-at-0": "Podawanie na {0}",
"disconnecting-from-printer": "Rozłączanie drukarki",
"connected-to-0-1": "Połączone z {0} {1}",
"flip-horizontally": "Odwróć poziomo",
"flip-vertically": "Odwróć pionowo",
"dump-traffic": "Zrzut ruchu",
"right-to-left-text-order": "Tekst od prawej do lewej",
"auto-wrap-line": "Automatycznie zawijaj linię",
"process-as-": "Przetwórz jako:",
"text": "Tekst",
"picture": "Obraz",
"pattern": "Wzór",
"large-font": "Duża czcionka",
"accessibility": "Ułatwienia dostępności",
"language": "Język",
"layout": "Układ klawiatury",
"ok": "OK",
"cancel": "Anuluj",
"yes": "Tak",
"no": "Nie",
"about": "Informacje",
"home-page-": "Strona główna:",
"contributors": "Współtwórcy",
"developer": "Deweloper",
"collaborator": "Współpracownik",
"translator": "Tłumacz",
"all-users-and-developers": "Wszyscy użytkownicy i deweloperzy",
"everyone-is-awesome": "Wszyscy są wspaniali!",
"license": "Licencja",
"exiting": "Opuszczanie…",
"dark-theme": "Ciemny tryb",
"high-contrast": "Wysoki kontrast",
"welcome": "Witaj!",
"copyright-and-license": "Prawa autorskie i licencja",
"some-rights-reserved": "Niektóre prawa zastrzeżone..",
"ENTER": "Enter",
"SPACE": "Spacja",
"ESCAPE": "Esc",
"TAB": "Tab",
"COMMA": "Przecinek",
"DOT": "Kropka",
"to-enter-keyboard-mode-press-tab": "Aby przejść w tryb klawiatury naciśnij Tab",
"usage-": "Użycie:",
"positional-arguments-": "Argumenty pozycjonalne:",
"options-": "Options:",
"show-this-help-message": "Pokaż tą wiadomość",
"do-nothing": "Nie rob nic",
"scan-for-a-printer": "Scan for a printer",
"text-printing-mode-with-options": "Tryb drukowania tekstu z opcjami",
"image-printing-options": "Opcje drukowania obrazu",
"convert-input-image-with-imagemagick": "Przekonwertuj plik wejściowy (obraz) dzięki ImageMagick",
"reset-configuration-": "Zresetować konfigurację?",
"brightness-": "Jasność:",
"text-printing-mode": "Tryb drukowania tekstu",
"internal-error-please-see-terminal": "Błąd wewnętrzny, proszę sprawdzić terminal",
"control-printer-thermal-strength": "Kontroluj moc swojej drukarki termicznej",
"strength-": "Moc:",
"or-drag-file-to-below": "Albo upuść obraz poniżej",
"reset": "Resetuj",
"cat-face-toward": "Mordką kota do przodu",
"quality-": "Jakość:",
"print-quality": "Jakość druku",
"show-more-options": "Pokaż więcej opcji",
"text-font": "Czcionka",
"text-size": "Rozmiar",
"enter-text": "Wpisz tekst",
"wrap-text": "Zawijaj tekst",
"you-can-see-all-javascript-programs-used": "Możesz zobaczyć, że wszystkie programy JavaScript używane z tą aplikacją są Darmowym Oprogramowaniem.",
"javascript-resource": "Zasób",
"javascript-license": "Licencja",
"javascript-source": "Żródło",
"javascript-description": "Opis",
"javascript-everyjs-description": "Dynamiczne powiązanie wszystkich skryptów deweloperskich",
"javascript-maincompjs-description": "Wszystkie następujące skrypty deweloperskie, transpilowane dla kompatybilności.",
"javascript-loaderjs-description": "Dla dynamicznego ładowania skryptów, i ratunek jeśli wystąpią problemy.",
"javascript-polyfilljs-description": "Add features which are not supported by old browsers.",
"javascript-i18nextjs-description": "“rozszerzenia” I18n",
"javascript-i18njs-description": "Dla międzynarodowości (wsparcie językowe)",
"javascript-imagejs-description": "Dla manipulacji obrazu na płótnie",
"javascript-accessibilityjs-description": "Ułatwienia dostępu",
"javascript-catprinter-description": "Główny skrypt dla Cat-Printer",
"free-software": "Darmowe Oprogramowanie",
"free-software-description": "Oprogramowanie, które szanuje twoją cyfrową wolność.",
"wrap-words-by-spaces": "Zawijaj słowa wg. spacji",
"minor-tweaks": "Drobne poprawki",
"serif": "Serif",
"sans-serif": "Sans Serif",
"monospace": "Monospace",
"rotate-image": "Obróć obraz",
"test-unknown-device": "Przetestuj nieznane urządzenie",
"scan": "Skanuj",
"pf2-font-not-found-or-broken-0": "Font PF2 nieznaleziony bądź uszkodzony: '{0}'",
"imagemagick-not-found": "ImageMagick nieznaleziony, proszę zainstalować i spróbować jeszcze raz.",
"try-to-print-through-an-unknown-device": "Spróbuj wydrukować przez nieznane urządzenie",
"scanning-for-all-bluetooth-devices-nearby": "Skanowanie wszystkich pobliskich urządzeń Bluetooth…",
"there-are-multiple-devices-": "Znaleziono wiele urządzeń:",
"choose-which-one-0-": "Które? [{0}]: ",
"multiple-devices-found-please-specify-one": "Znaleziono wiele urządzeń, proszę wybrać jedno",
"no-prompt-for-multiple-devices": "Brak monitu o wiele urządzeń"
}

View File

@ -1,10 +0,0 @@
{
"$language": "中文(简体)国际化样例",
//
"0-apples": "{0} 个苹果",
"0th-apple": "第 {0} 个苹果",
//
"there-is-a-0": {
"measure": "有一{0}"
}
}

View File

@ -1,160 +0,0 @@
{
"$language": "中文(简体字)",
"cat-printer": "猫咪打印机",
"printer": "打印机",
"device-": "设备:",
"refresh": "刷新",
"mode-": "模式:",
"canvas": "画布",
"document": "文档",
"insert-picture": "插入图片",
"insert-text": "输入文本",
"help": "帮助",
"javascript-license-information": "JavaScript 许可证信息",
"settings": "设置",
"image": "图像",
"threshold-": "阈值:",
"transparent-as-white": "透明为白色",
"misc": "杂项",
"system": "系统",
"disable-animation": "禁用动画",
"exit": "退出",
"error-message": "错误消息",
"preview": "预览",
"print": "打印",
"expand": "扩大",
"crop": "裁减",
"scanning-for-devices": "正在扫描设备……",
"scan-time-": "扫描时间:",
"-seconds": "秒",
"no-available-devices-found": "未发现可用设备",
"found-0-available-devices": "发现 {0} 个可用设备",
"please-check-if-the-printer-is-down": "请检查打印机是否已关闭",
"printing": "打印中……",
"finished": "完成",
"coming-soon": "即将到来……",
"dry-run": "干运行",
"dry-run-test-print-process-only": "干运行:仅测试打印流程",
"you-can-close-this-page-manually": "您可手动关闭此页面",
"please-enable-bluetooth": "请启用蓝牙",
"error-happened-please-check-error-message": "发生错误,请检查错误消息",
"you-can-seek-for-help-with-detailed-info-below": "您可以使用以下详细信息寻求帮助",
"or-try-to-scan-longer": "或者尝试延长扫描时间",
"print-to-cat-printer": "打印到猫咪打印机。",
"supported-models-": "支持的型号:",
"path-to-input-file-dash-for-stdin": "输入文件的位置。使用 '-' 作为标准输入",
"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` 不完整或丢失,请检查",
"input-is-not-pbm-image": "输入不是 PBM 图像",
"unsuitable-image-width-expected-0-got-1": "不适合的图像宽度,需要 {0}, 输入为 {1}",
"broken-pbm-image": "损坏的 PBM 图像",
"input-is-not-text-file": "输入不是文本文件",
"match-printer-with-this-name-or-address": "使用符合此名称或地址的打印机",
"virtual-run-on-specified-model": "在指定的型号模拟运行",
"font-size-0": "字体大小 {0}",
"stopping": "停止中",
"connecting": "正在连接",
"model-0-is-not-supported-yet": "型号 '{0}' 仍未支持",
"invalid-address-0": "无效的地址:'{0}'",
"will-listen-on-all-addresses": "将接受所有地址的连接",
"serving-at-0": "服务器在 {0}",
"disconnecting-from-printer": "正在从打印机断开连接",
"flip-horizontally": "水平翻转",
"flip-vertically": "垂直翻转",
"dump-traffic": "转储数据",
"right-to-left-text-order": "从右到左的文字顺序",
"auto-wrap-line": "自动折行",
"process-as-": "处理方式:",
"text": "文本",
"picture": "照片",
"pattern": "图案",
"large-font": "大字体",
"accessibility": "无障碍",
"language": "语言",
"layout": "布局",
"ok": "确定",
"cancel": "取消",
"yes": "是",
"no": "否",
"about": "关于",
"home-page-": "主页:",
"contributors": "贡献者",
"developer": "开发者",
"collaborator": "合作",
"translator": "翻译",
"all-users-and-developers": "每位用户及开发者",
"everyone-is-awesome": "每个人都是好样的!",
"license": "授权",
"exiting": "退出中……",
"dark-theme": "深色主题",
"high-contrast": "高对比度",
"welcome": "欢迎!",
"copyright-and-license": "版权与许可",
"some-rights-reserved": "保留一些权利。",
"ENTER": "回车",
"SPACE": "空格",
"ESCAPE": "ESC",
"TAB": "Tab",
"COMMA": "逗号",
"DOT": "句号",
"to-enter-keyboard-mode-press-tab": "要进入键盘模式,请按 Tab",
"usage-": "用法:",
"positional-arguments-": "参数:",
"options-": "选项:",
"show-this-help-message": "显示此帮助信息",
"do-nothing": "什么也不做",
"scan-for-a-printer": "扫描打印机",
"text-printing-mode-with-options": "启用文字打印并指定选项",
"image-printing-options": "图片打印选项",
"convert-input-image-with-imagemagick": "使用 ImageMagick 转换输入图片",
"reset-configuration-": "要重置配置吗?",
"brightness-": "亮度:",
"text-printing-mode": "文字打印模式",
"internal-error-please-see-terminal": "内部错误,请检查终端",
"control-printer-thermal-strength": "控制打印力度",
"strength-": "力度:",
"or-drag-file-to-below": "或拖拽文件至下方",
"reset": "重置",
"cat-face-toward": "猫脸朝上",
"quality-": "质量:",
"print-quality": "打印质量",
"show-more-options": "显示更多选项",
"text-font": "字体",
"text-size": "大小",
"enter-text": "在此处输入文本",
"wrap-words-by-spaces": "空格处换行(不建议用于中文)",
"minor-tweaks": "细节调整",
"serif": "衬线字体",
"sans-serif": "无衬线字体",
"monospace": "等宽字体",
"rotate-image": "旋转图像",
"test-unknown-device": "测试未知设备",
"now-will-scan-for-all-bluetooth-devices-nearby": "现在将搜索附近所有设备。",
"scan": "扫描",
"you-can-see-all-javascript-programs-used": "您可以看到此程序使用的 JavaScript 脚本均为自由软件。",
"javascript-resource": "资源",
"javascript-license": "许可",
"javascript-source": "源",
"javascript-description": "详述",
"javascript-everyjs-description": "所有开发脚本的动态联接",
"javascript-maincompjs-description": "经过转译的所有以下开发脚本包,起兼容作用",
"javascript-loaderjs-description": "动态加载其余脚本,并在出现问题时回退",
"javascript-polyfilljs-description": "添加不被旧浏览器支持的功能",
"javascript-i18nextjs-description": "国际化 (i18n)“扩展”",
"javascript-i18njs-description": "用于国际化(语言支持)",
"javascript-imagejs-description": "用于画布 (canvas) 上的图像处理",
"javascript-accessibilityjs-description": "一些无障碍功能",
"javascript-catprinter-description": "猫咪打印机 (Cat-Printer) 主脚本",
"free-software": "自由软件",
"free-software-description": "尊重您计算自由的软件。",
"pf2-font-not-found-or-broken-0": "PF2 字体丢失或损坏:'{0}'",
"imagemagick-not-found": "未找到 ImageMagick请安装并重试。",
"try-to-print-through-an-unknown-device": "试着用未知的设备打印",
"scanning-for-all-bluetooth-devices-nearby": "正在搜索附近所有蓝牙设备……",
"there-are-multiple-devices-": "有多个设备:",
"choose-which-one-0-": "选择哪一个?[{0}]",
"multiple-devices-found-please-specify-one": "找到多个设备,请指定",
"no-prompt-for-multiple-devices": "发现多个设备时不提示选择"
}

View File

@ -1,160 +0,0 @@
{
"$language": "中文(香港字)",
"cat-printer": "貓咪打印機",
"printer": "打印機",
"device-": "設備:",
"refresh": "刷新",
"mode-": "模式:",
"canvas": "畫布",
"document": "文檔",
"insert-picture": "插入圖片",
"insert-text": "輸入文本",
"help": "幫助",
"javascript-license-information": "JavaScript 許可證信息",
"settings": "設置",
"image": "圖像",
"threshold-": "閾值:",
"transparent-as-white": "透明為白色",
"misc": "雜項",
"system": "系統",
"disable-animation": "禁用動畫",
"exit": "退出",
"error-message": "錯誤消息",
"preview": "預覽",
"print": "打印",
"expand": "擴大",
"crop": "裁減",
"scanning-for-devices": "正在掃描設備……",
"scan-time-": "掃描時間:",
"-seconds": "秒",
"no-available-devices-found": "未發現可用設備",
"found-0-available-devices": "發現 {0} 個可用設備",
"please-check-if-the-printer-is-down": "請檢查打印機是否已關閉",
"printing": "打印中……",
"finished": "完成",
"coming-soon": "即將到來……",
"dry-run": "乾運行",
"dry-run-test-print-process-only": "乾運行:僅測試打印流程",
"you-can-close-this-page-manually": "您可手動關閉此頁面",
"please-enable-bluetooth": "請啟用藍牙",
"error-happened-please-check-error-message": "發生錯誤,請檢查錯誤消息",
"you-can-seek-for-help-with-detailed-info-below": "您可以使用以下詳細信息尋求幫助",
"or-try-to-scan-longer": "或者嘗試延長掃描時間",
"print-to-cat-printer": "打印到貓咪打印機。",
"supported-models-": "支持的型號:",
"path-to-input-file-dash-for-stdin": "輸入文件的位置。使用 '-' 作為標準輸入",
"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` 不完整或丟失,請檢查",
"input-is-not-pbm-image": "輸入不是 PBM 圖像",
"unsuitable-image-width-expected-0-got-1": "不適合的圖像寬度,需要 {0}, 輸入為 {1}",
"broken-pbm-image": "損壞的 PBM 圖像",
"input-is-not-text-file": "輸入不是文本文件",
"match-printer-with-this-name-or-address": "使用符合此名稱或地址的打印機",
"virtual-run-on-specified-model": "在指定的型號模擬運行",
"font-size-0": "字體大小 {0}",
"stopping": "停止中",
"connecting": "正在連接",
"model-0-is-not-supported-yet": "型號 '{0}' 仍未支持",
"invalid-address-0": "無效的地址:'{0}'",
"will-listen-on-all-addresses": "將接受所有地址的連接",
"serving-at-0": "服務器在 {0}",
"disconnecting-from-printer": "正在從打印機斷開連接",
"flip-horizontally": "水平翻轉",
"flip-vertically": "垂直翻轉",
"dump-traffic": "轉儲數據",
"right-to-left-text-order": "從右到左的文字順序",
"auto-wrap-line": "自動折行",
"process-as-": "處理方式:",
"text": "文本",
"picture": "照片",
"pattern": "圖案",
"large-font": "大字體",
"accessibility": "無障礙",
"language": "語言",
"layout": "佈局",
"ok": "確定",
"cancel": "取消",
"yes": "是",
"no": "否",
"about": "關於",
"home-page-": "主頁:",
"contributors": "貢獻者",
"developer": "開發者",
"collaborator": "合作",
"translator": "翻譯",
"all-users-and-developers": "每位用户及開發者",
"everyone-is-awesome": "每個人都是好樣的!",
"license": "授權",
"exiting": "退出中……",
"dark-theme": "深色主題",
"high-contrast": "高對比度",
"welcome": "歡迎!",
"copyright-and-license": "版權與許可",
"some-rights-reserved": "保留一些權利。",
"ENTER": "回車",
"SPACE": "空格",
"ESCAPE": "ESC",
"TAB": "Tab",
"COMMA": "逗號",
"DOT": "句號",
"to-enter-keyboard-mode-press-tab": "要進入鍵盤模式,請按 Tab",
"usage-": "用法:",
"positional-arguments-": "參數:",
"options-": "選項:",
"show-this-help-message": "顯示此幫助信息",
"do-nothing": "什麼也不做",
"scan-for-a-printer": "掃描打印機",
"text-printing-mode-with-options": "啟用文字打印並指定選項",
"image-printing-options": "圖片打印選項",
"convert-input-image-with-imagemagick": "使用 ImageMagick 轉換輸入圖片",
"reset-configuration-": "要重置配置嗎?",
"brightness-": "亮度:",
"text-printing-mode": "文字打印模式",
"internal-error-please-see-terminal": "內部錯誤,請檢查終端",
"control-printer-thermal-strength": "控制打印力度",
"strength-": "力度:",
"or-drag-file-to-below": "或拖拽文件至下方",
"reset": "重置",
"cat-face-toward": "貓臉朝上",
"quality-": "質量:",
"print-quality": "打印質量",
"show-more-options": "顯示更多選項",
"text-font": "字體",
"text-size": "大小",
"enter-text": "在此處輸入文本",
"wrap-words-by-spaces": "空格處換行(不建議用於中文)",
"minor-tweaks": "細節調整",
"serif": "襯線字體",
"sans-serif": "無襯線字體",
"monospace": "等寬字體",
"rotate-image": "旋轉圖像",
"test-unknown-device": "測試未知設備",
"now-will-scan-for-all-bluetooth-devices-nearby": "現在將搜索附近所有設備。",
"scan": "掃描",
"you-can-see-all-javascript-programs-used": "您可以看到此程序使用的 JavaScript 腳本均為自由軟件。",
"javascript-resource": "資源",
"javascript-license": "許可",
"javascript-source": "源",
"javascript-description": "詳述",
"javascript-everyjs-description": "所有開發腳本的動態聯接",
"javascript-maincompjs-description": "經過轉譯的所有以下開發腳本包,起兼容作用",
"javascript-loaderjs-description": "動態加載其餘腳本,並在出現問題時回退",
"javascript-polyfilljs-description": "添加不被舊瀏覽器支持的功能",
"javascript-i18nextjs-description": "國際化 (i18n)“擴展”",
"javascript-i18njs-description": "用於國際化(語言支持)",
"javascript-imagejs-description": "用於畫布 (canvas) 上的圖像處理",
"javascript-accessibilityjs-description": "一些無障礙功能",
"javascript-catprinter-description": "貓咪打印機 (Cat-Printer) 主腳本",
"free-software": "自由軟件",
"free-software-description": "尊重您計算自由的軟件。",
"pf2-font-not-found-or-broken-0": "PF2 字體丟失或損壞:'{0}'",
"imagemagick-not-found": "未找到 ImageMagick請安裝並重試。",
"try-to-print-through-an-unknown-device": "試着用未知的設備打印",
"scanning-for-all-bluetooth-devices-nearby": "正在搜索附近所有藍牙設備……",
"there-are-multiple-devices-": "有多個設備:",
"choose-which-one-0-": "選擇哪一個?[{0}]",
"multiple-devices-found-please-specify-one": "找到多個設備,請指定",
"no-prompt-for-multiple-devices": "發現多個設備時不提示選擇"
}

View File

@ -1,10 +0,0 @@
{
"$language": "中文國際化樣例",
//
"0-apples": "{0} 個蘋果",
"0th-apple": "第 {0} 個蘋果",
//
"there-is-a-0": {
"measure": "有一{0}"
}
}

View File

@ -1,160 +0,0 @@
{
"$language": "中文(傳統字)",
"cat-printer": "貓咪打印機",
"printer": "打印機",
"device-": "設備:",
"refresh": "刷新",
"mode-": "模式:",
"canvas": "畫布",
"document": "文檔",
"insert-picture": "插入圖片",
"insert-text": "輸入文本",
"help": "幫助",
"javascript-license-information": "JavaScript 許可證信息",
"settings": "設置",
"image": "圖像",
"threshold-": "閾值:",
"transparent-as-white": "透明爲白色",
"misc": "雜項",
"system": "系統",
"disable-animation": "禁用動畫",
"exit": "退出",
"error-message": "錯誤消息",
"preview": "預覽",
"print": "打印",
"expand": "擴大",
"crop": "裁減",
"scanning-for-devices": "正在掃描設備……",
"scan-time-": "掃描時間:",
"-seconds": "秒",
"no-available-devices-found": "未發現可用設備",
"found-0-available-devices": "發現 {0} 個可用設備",
"please-check-if-the-printer-is-down": "請檢查打印機是否已關閉",
"printing": "打印中……",
"finished": "完成",
"coming-soon": "即將到來……",
"dry-run": "乾運行",
"dry-run-test-print-process-only": "乾運行:僅測試打印流程",
"you-can-close-this-page-manually": "您可手動關閉此頁面",
"please-enable-bluetooth": "請啓用藍牙",
"error-happened-please-check-error-message": "發生錯誤,請檢查錯誤消息",
"you-can-seek-for-help-with-detailed-info-below": "您可以使用以下詳細信息尋求幫助",
"or-try-to-scan-longer": "或者嘗試延長掃描時間",
"print-to-cat-printer": "打印到貓咪打印機。",
"supported-models-": "支持的型號:",
"path-to-input-file-dash-for-stdin": "輸入文件的位置。使用 '-' 作爲標準輸入",
"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` 不完整或丟失,請檢查",
"input-is-not-pbm-image": "輸入不是 PBM 圖像",
"unsuitable-image-width-expected-0-got-1": "不適合的圖像寬度,需要 {0}, 輸入爲 {1}",
"broken-pbm-image": "損壞的 PBM 圖像",
"input-is-not-text-file": "輸入不是文本文件",
"match-printer-with-this-name-or-address": "使用符合此名稱或地址的打印機",
"virtual-run-on-specified-model": "在指定的型號模擬運行",
"font-size-0": "字體大小 {0}",
"stopping": "停止中",
"connecting": "正在連接",
"model-0-is-not-supported-yet": "型號 '{0}' 仍未支持",
"invalid-address-0": "無效的地址:'{0}'",
"will-listen-on-all-addresses": "將接受所有地址的連接",
"serving-at-0": "服務器在 {0}",
"disconnecting-from-printer": "正在從打印機斷開連接",
"flip-horizontally": "水平翻轉",
"flip-vertically": "垂直翻轉",
"dump-traffic": "轉儲數據",
"right-to-left-text-order": "從右到左的文字順序",
"auto-wrap-line": "自動折行",
"process-as-": "處理方式:",
"text": "文本",
"picture": "照片",
"pattern": "圖案",
"large-font": "大字體",
"accessibility": "無障礙",
"language": "語言",
"layout": "佈局",
"ok": "確定",
"cancel": "取消",
"yes": "是",
"no": "否",
"about": "關於",
"home-page-": "主頁:",
"contributors": "貢獻者",
"developer": "開發者",
"collaborator": "合作",
"translator": "翻譯",
"all-users-and-developers": "每位用戶及開發者",
"everyone-is-awesome": "每個人都是好樣的!",
"license": "授權",
"exiting": "退出中……",
"dark-theme": "深色主題",
"high-contrast": "高對比度",
"welcome": "歡迎!",
"copyright-and-license": "版權與許可",
"some-rights-reserved": "保留一些權利。",
"ENTER": "回車",
"SPACE": "空格",
"ESCAPE": "ESC",
"TAB": "Tab",
"COMMA": "逗號",
"DOT": "句號",
"to-enter-keyboard-mode-press-tab": "要進入鍵盤模式,請按 Tab",
"usage-": "用法:",
"positional-arguments-": "參數:",
"options-": "選項:",
"show-this-help-message": "顯示此幫助信息",
"do-nothing": "什麼也不做",
"scan-for-a-printer": "掃描打印機",
"text-printing-mode-with-options": "啓用文字打印並指定選項",
"image-printing-options": "圖片打印選項",
"convert-input-image-with-imagemagick": "使用 ImageMagick 轉換輸入圖片",
"reset-configuration-": "要重置配置嗎?",
"brightness-": "亮度:",
"text-printing-mode": "文字打印模式",
"internal-error-please-see-terminal": "內部錯誤,請檢查終端",
"control-printer-thermal-strength": "控制打印力度",
"strength-": "力度:",
"or-drag-file-to-below": "或拖拽文件至下方",
"reset": "重置",
"cat-face-toward": "貓臉朝上",
"quality-": "質量:",
"print-quality": "打印質量",
"show-more-options": "顯示更多選項",
"text-font": "字體",
"text-size": "大小",
"enter-text": "在此處輸入文本",
"wrap-words-by-spaces": "空格處換行(不建議用於中文)",
"minor-tweaks": "細節調整",
"serif": "襯線字體",
"sans-serif": "無襯線字體",
"monospace": "等寬字體",
"rotate-image": "旋轉圖像",
"test-unknown-device": "測試未知設備",
"now-will-scan-for-all-bluetooth-devices-nearby": "現在將搜索附近所有設備。",
"scan": "掃描",
"you-can-see-all-javascript-programs-used": "您可以看到此程序使用的 JavaScript 腳本均爲自由軟件。",
"javascript-resource": "資源",
"javascript-license": "許可",
"javascript-source": "源",
"javascript-description": "詳述",
"javascript-everyjs-description": "所有開發腳本的動態聯接",
"javascript-maincompjs-description": "經過轉譯的所有以下開發腳本包,起兼容作用",
"javascript-loaderjs-description": "動態加載其餘腳本,並在出現問題時回退",
"javascript-polyfilljs-description": "添加不被舊瀏覽器支持的功能",
"javascript-i18nextjs-description": "國際化 (i18n)“擴展”",
"javascript-i18njs-description": "用於國際化(語言支持)",
"javascript-imagejs-description": "用於畫布 (canvas) 上的圖像處理",
"javascript-accessibilityjs-description": "一些無障礙功能",
"javascript-catprinter-description": "貓咪打印機 (Cat-Printer) 主腳本",
"free-software": "自由軟件",
"free-software-description": "尊重您計算自由的軟件。",
"pf2-font-not-found-or-broken-0": "PF2 字體丟失或損壞:'{0}'",
"imagemagick-not-found": "未找到 ImageMagick請安裝並重試。",
"try-to-print-through-an-unknown-device": "試着用未知的設備打印",
"scanning-for-all-bluetooth-devices-nearby": "正在搜索附近所有藍牙設備……",
"there-are-multiple-devices-": "有多個設備:",
"choose-which-one-0-": "選擇哪一個?[{0}]",
"multiple-devices-found-please-specify-one": "找到多個設備,請指定",
"no-prompt-for-multiple-devices": "發現多個設備時不提示選擇"
}

View File

@ -1,160 +0,0 @@
{
"$language": "中文(正體字)",
"cat-printer": "貓咪印表機",
"printer": "印表機",
"device-": "裝置:",
"refresh": "重新整理",
"mode-": "模式:",
"canvas": "畫布",
"document": "文件",
"insert-picture": "插入圖片",
"insert-text": "輸入文字",
"help": "幫助",
"javascript-license-information": "JavaScript 許可證資訊",
"settings": "設定",
"image": "影象",
"threshold-": "閾值:",
"transparent-as-white": "透明為白色",
"misc": "雜項",
"system": "系統",
"disable-animation": "禁用動畫",
"exit": "退出",
"error-message": "錯誤訊息",
"preview": "預覽",
"print": "列印",
"expand": "擴大",
"crop": "裁減",
"scanning-for-devices": "正在掃描裝置……",
"scan-time-": "掃描時間:",
"-seconds": "秒",
"no-available-devices-found": "未發現可用裝置",
"found-0-available-devices": "發現 {0} 個可用裝置",
"please-check-if-the-printer-is-down": "請檢查印表機是否已關閉",
"printing": "列印中……",
"finished": "完成",
"coming-soon": "即將到來……",
"dry-run": "乾執行",
"dry-run-test-print-process-only": "乾執行:僅測試列印流程",
"you-can-close-this-page-manually": "您可手動關閉此頁面",
"please-enable-bluetooth": "請啟用藍芽",
"error-happened-please-check-error-message": "發生錯誤,請檢查錯誤訊息",
"you-can-seek-for-help-with-detailed-info-below": "您可以使用以下詳細資訊尋求幫助",
"or-try-to-scan-longer": "或者嘗試延長掃描時間",
"print-to-cat-printer": "列印到貓咪印表機。",
"supported-models-": "支援的型號:",
"path-to-input-file-dash-for-stdin": "輸入檔案的位置。使用 '-' 作為標準輸入",
"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` 不完整或丟失,請檢查",
"input-is-not-pbm-image": "輸入不是 PBM 影象",
"unsuitable-image-width-expected-0-got-1": "不適合的影象寬度,需要 {0}, 輸入為 {1}",
"broken-pbm-image": "損壞的 PBM 影象",
"input-is-not-text-file": "輸入不是文字檔案",
"match-printer-with-this-name-or-address": "使用符合此名稱或地址的印表機",
"virtual-run-on-specified-model": "在指定的型號模擬執行",
"font-size-0": "字型大小 {0}",
"stopping": "停止中",
"connecting": "正在連線",
"model-0-is-not-supported-yet": "型號 '{0}' 仍未支援",
"invalid-address-0": "無效的地址:'{0}'",
"will-listen-on-all-addresses": "將接受所有地址的連線",
"serving-at-0": "伺服器在 {0}",
"disconnecting-from-printer": "正在從印表機斷開連線",
"flip-horizontally": "水平翻轉",
"flip-vertically": "垂直翻轉",
"dump-traffic": "轉儲資料",
"right-to-left-text-order": "從右到左的文字順序",
"auto-wrap-line": "自動折行",
"process-as-": "處理方式:",
"text": "文字",
"picture": "照片",
"pattern": "圖案",
"large-font": "大字型",
"accessibility": "無障礙",
"language": "語言",
"layout": "佈局",
"ok": "確定",
"cancel": "取消",
"yes": "是",
"no": "否",
"about": "關於",
"home-page-": "主頁:",
"contributors": "貢獻者",
"developer": "開發者",
"collaborator": "合作",
"translator": "翻譯",
"all-users-and-developers": "每位用家及開發者",
"everyone-is-awesome": "每個人都是好樣的!",
"license": "授權",
"exiting": "退出中……",
"dark-theme": "深色主題",
"high-contrast": "高對比度",
"welcome": "歡迎!",
"copyright-and-license": "版權與許可",
"some-rights-reserved": "保留一些權利。",
"ENTER": "回車",
"SPACE": "空格",
"ESCAPE": "ESC",
"TAB": "Tab",
"COMMA": "逗號",
"DOT": "句號",
"to-enter-keyboard-mode-press-tab": "要進入鍵盤模式,請按 Tab",
"usage-": "用法:",
"positional-arguments-": "引數:",
"options-": "選項:",
"show-this-help-message": "顯示此幫助資訊",
"do-nothing": "什麼也不做",
"scan-for-a-printer": "掃描印表機",
"text-printing-mode-with-options": "啟用文字列印並指定選項",
"image-printing-options": "圖片列印選項",
"convert-input-image-with-imagemagick": "使用 ImageMagick 轉換輸入圖片",
"reset-configuration-": "要重置配置嗎?",
"brightness-": "亮度:",
"text-printing-mode": "文字列印模式",
"internal-error-please-see-terminal": "內部錯誤,請檢查終端",
"control-printer-thermal-strength": "控制列印力度",
"strength-": "力度:",
"or-drag-file-to-below": "或拖拽檔案至下方",
"reset": "重置",
"cat-face-toward": "貓臉朝上",
"quality-": "質量:",
"print-quality": "列印質量",
"show-more-options": "顯示更多選項",
"text-font": "字型",
"text-size": "大小",
"enter-text": "在此處輸入文字",
"wrap-words-by-spaces": "空格處換行(不建議用於中文)",
"minor-tweaks": "細節調整",
"serif": "襯線字型",
"sans-serif": "無襯線字型",
"monospace": "等寬字型",
"rotate-image": "旋轉影象",
"test-unknown-device": "測試未知裝置",
"now-will-scan-for-all-bluetooth-devices-nearby": "現在將搜尋附近所有裝置。",
"scan": "掃描",
"you-can-see-all-javascript-programs-used": "您可以看到此程式使用的 JavaScript 指令碼均為自由軟體。",
"javascript-resource": "資源",
"javascript-license": "許可",
"javascript-source": "源",
"javascript-description": "詳述",
"javascript-everyjs-description": "所有開發指令碼的動態聯接",
"javascript-maincompjs-description": "經過轉譯的所有以下開發指令碼包,起相容作用",
"javascript-loaderjs-description": "動態載入其餘指令碼,並在出現問題時回退",
"javascript-polyfilljs-description": "新增不被舊瀏覽器支援的功能",
"javascript-i18nextjs-description": "國際化 (i18n)“擴充套件”",
"javascript-i18njs-description": "用於國際化(語言支援)",
"javascript-imagejs-description": "用於畫布 (canvas) 上的影象處理",
"javascript-accessibilityjs-description": "一些無障礙功能",
"javascript-catprinter-description": "貓咪印表機 (Cat-Printer) 主指令碼",
"free-software": "自由軟體",
"free-software-description": "尊重您計算自由的軟體。",
"pf2-font-not-found-or-broken-0": "PF2 字型丟失或損壞:'{0}'",
"imagemagick-not-found": "未找到 ImageMagick請安裝並重試。",
"try-to-print-through-an-unknown-device": "試著用未知的裝置列印",
"scanning-for-all-bluetooth-devices-nearby": "正在搜尋附近所有藍芽裝置……",
"there-are-multiple-devices-": "有多個裝置:",
"choose-which-one-0-": "選擇哪一個?[{0}]",
"multiple-devices-found-please-specify-one": "找到多個裝置,請指定",
"no-prompt-for-multiple-devices": "發現多個裝置時不提示選擇"
}

View File

@ -1,10 +0,0 @@
{
"en-US": "English (US)",
"de-DE": "Deutsch",
"nl-NL": "Nederlands",
"zh-CN": "中文(简体字)",
"zh-TW": "中文(正體字)",
"zh-HK": "中文(香港字)",
"lolcat": "LOLCAT",
"zh-Hant-CN": "中文(傳統字)"
}

View File

@ -1,46 +0,0 @@
`
No rights reserved.
License CC0-1.0-only: https://directory.fsf.org/wiki/License:CC0
`;
window.exports = {};
/**
* Satisfy both development and old-old webView need
*/
(function() {
var fallbacks;
if (location.href.indexOf('?debug') !== -1)
fallbacks = ['i18n-ext.js', 'i18n.js', 'image.js', 'accessibility.js', 'main.js'];
else
fallbacks = ['~every.js', 'main.comp.js'];
var trial_count = 0;
/**
* Try to load next "fallback" script,
* until we see the "main" variable (ie. success)
* fail if nothing is left to load.
* This is recursive. Just call once.
*/
function try_load() {
var script = document.createElement('script');
script.addEventListener('load', function() {
if (typeof main === 'undefined') {
// the script can't be 'unrun', though
script.remove();
try_load();
} else {
console.log('Success');
}
});
var path = fallbacks[trial_count++];
if (!path) throw new Error('All fallback scripts were tried');
script.src = path;
console.log('Trying script: ' + path);
document.body.appendChild(script);
}
try_load();
})();

View File

@ -1,739 +0,0 @@
:root {
--font-size: 1.2rem;
/* --dpi-zoom: 0.96; */
--deco-size: calc(var(--font-size) / 2);
--line-height: calc(var(--font-size) / 2 * 3);
--compact-menu-height: 2em;
--span: 8px;
--span-half: calc(var(--span) / 2);
--span-double: calc(var(--span) * 2);
--border: 1px;
--border-double: calc(var(--border) * 2);
--paper-width: 384px;
--anim-time: 0.5s;
--fore-color: #111;
--back-color: #eee;
--canvas-back: #fff;
--panel-height: 20em;
--target-color: rgba(0, 255, 255, 0.2);
--notice-wait: rgba(0, 128, 255, 0.2);
--notice-note: rgba(0, 255, 0, 0.2);
--notice-warn: rgba(255, 128, 0, 0.2);
--notice-error: rgba(255, 0, 0, 0.2);
--shade: rgba(238, 238, 238, 0.5);
}
@media (prefers-color-scheme: dark) {
:root { --fore-color: #eee; --back-color: #333; --shade: rgba(51, 51, 51, 0.5); }
a:link, a:visited { color: #66f; }
a:hover, a:active { color: #77f; }
.canvas-group, .logo { opacity: 0.6; }
#control-overlay { background-color: var(--shade); }
}
/* so silly... */
body.dark-theme { --fore-color: #eee; --back-color: #333; --shade: rgba(51, 51, 51, 0.5); }
body.dark-theme a:link, body.dark-theme a:visited { color: #66f; }
body.dark-theme a:hover, body.dark-theme a:active { color: #77f; }
body.dark-theme .canvas-group, body.dark-theme .logo { opacity: 0.6; }
body.dark-theme #control-overlay { background-color: var(--shade); }
body {
border: none;
background-color: var(--back-color);
color: var(--fore-color);
font-size: var(--font-size);
line-height: var(--line-height);
font-family: 'Noto Sans', 'Segoe UI', sans-serif;
overflow: auto;
margin: 1em 0;
user-select: none;
}
body.android .hide-on-android {
display: none;
}
* {
box-sizing: border-box;
transition-property: background-color, transform,/* box-shadow,*/ flex-grow, opacity;
transition-delay: 0s;
transition-duration: var(--anim-time);
transition-timing-function: ease-out;
}
button, input[type="number"], input[type="text"], select,
#dialog>.shade, #dialog>.content, #canvas {
transition-timing-function: cubic-bezier(.08,.82,.17,1);
}
#dialog>.shade {
transition-duration: calc(var(--anim-time) / 2);
}
a {
transition: color var(--anim-time) ease-out;
}
.selectable {
user-select: all;
}
h1, h2 {
font-weight: normal;
margin: var(--span-half) 0;
}
h1 { font-size: 1.5em; }
h2 { font-size: 1.2em; }
a:link, a:visited {
color: #33f;
}
a:hover, a:active {
color: #22f;
}
a+a {
margin-left: var(--font-size);
}
.center {
text-align: center;
}
.right {
text-align: end;
}
button, input, select, textarea, label {
font: inherit;
color: var(--fore-color);
/* background-color: var(--back-color); */
background-color: transparent;
display: inline-block;
}
select[multiple] {
width: 8em;
padding: var(--border);
margin: var(--span-half) var(--span);
}
button, input[type="number"], input[type="text"], select {
margin: var(--span-half) var(--span);
border: var(--border) solid var(--fore-color);
padding: var(--span-half) var(--span);
cursor: pointer;
min-width: 6em;
line-height: calc(var(--font-size) + var(--span));
}
input[type="number"], input[type="text"] {
width: 6em;
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);
}
.input-group {
display: table;
width: 100%;
}
.label-span-input {
display: table-row;
margin: var(--span-half) var(--span);
}
.label-span-input>:nth-child(1) {
display: table-cell;
min-width: 5em;
padding: 0 var(--span);
text-align: end;
}
.label-span-input>:nth-child(2) {
display: table-cell;
min-width: 6em;
text-align: start;
}
.label-input-span {
display: block;
margin: var(--span-half) var(--span);
}
.label-input-span>:nth-child(1) {
display: inline-block;
padding: 0 var(--span);
text-align: end;
}
.label-input-span>:nth-child(2) {
display: inline-block;
text-align: start;
}
@keyframes notice-fade {
to { background-color: transparent; border-color: transparent; }
}
@keyframes notice-wait {
50% { background-color: transparent; border-color: transparent; }
}
#notice {
min-height: var(--font-size);
margin: var(--span-half) 0;
}
#notice span {
display: block;
}
#notice .note {
background-color: var(--notice-note);
animation: notice-fade 1s ease-out 1s 1 forwards;
}
#notice .wait {
background-color: var(--notice-wait);
animation: notice-wait 2s ease-in-out 0s infinite forwards;
}
#notice .warn {
background-color: var(--notice-warn);
animation: notice-fade 1s ease-out 1s 1 forwards;
}
#notice .error {
background-color: var(--notice-error);
animation: notice-fade 1s ease-out 1s 1 forwards;
}
#button-exit {
background-color: var(--notice-warn);
}
.noscript {
margin: var(--span-double);
text-align: center;
background-color: var(--notice-error);
display: block;
}
main, header, footer {
max-width: 45em;
margin: 1em auto;
display: flex;
justify-content: space-between;
flex-direction: row;
/* overflow-x: hidden; */ /* this causes sticky position not work */
}
.canvas-side {
flex-grow: 0;
width: var(--paper-width);
margin: var(--span) calc((50% - var(--paper-width)) / 2);
}
.canvas-side>* {
text-align: center;
}
.menu-side {
flex-grow: 1;
position: sticky;
top: 0;
height: 100%;
/* overflow: auto; */
margin: var(--span) 0;
min-width: 20em;
/* width: 50%; */
}
.menu-side>.menu {
border: var(--border) solid var(--fore-color);
border-bottom: none;
margin-top: var(--span);
}
.canvas-side>.buttons {
position: sticky;
bottom: 0;
padding: var(--span) 0;
background-color: var(--back-color);
z-index: 1;
}
.compact-menu {
display: flex;
flex-direction: row;
justify-content: space-around;
background-color: var(--back-color);
}
.compact-button {
height: var(--compact-menu-height);
line-height: var(--compact-menu-height);
text-align: center;
cursor: pointer;
border: none;
border-top: var(--border) solid var(--fore-color);
border-bottom: var(--border) solid transparent;
padding: 0;
margin: 0;
flex-grow: 1;
}
.compact-button:hover {
transform: unset;
flex-grow: 1.2;
}
.compact-button.active {
border: var(--border) solid var(--fore-color);
border-top: var(--border) solid transparent;
}
#canvas, #preview {
border: var(--border) solid var(--fore-color);
background-color: var(--canvas-back);
width: var(--paper-width);
display: inline-block;
}
#preview {
z-index: 0;
}
#canvas {
position: absolute;
opacity: 0;
z-index: 1;
}
#canvas:hover {
opacity: 1;
}
.canvas-group>*.disabled {
display: none;
}
#control-overlay {
position: absolute;
z-index: 2;
display: inline-block;
/* width: calc(var(--paper-width) + var(--border-double)); */
width: var(--paper-width);
margin-top: 0.5em;
}
p {
margin: var(--span) 0;
}
.panel {
overflow: hidden;
height: 0;
}
.panel.active {
height: calc(var(--panel-height) - var(--compact-menu-height));
padding: var(--span-double) var(--span);
overflow-y: auto;
}
.panel.sub.active {
height: calc(var(--panel-height) / 2);
}
input[type="range"] {
width: 10em;
vertical-align: middle;
content: attr(value);
}
@keyframes hint {
0% { box-shadow: 0 0 var(--span-) inset transparent; }
50% { box-shadow: 0 0 var(--span) inset var(--fore-color); }
100% { box-shadow: 0 0 var(--span) inset transparent; }
}
.hint {
animation: hint 3s ease-out 0.1s infinite;
}
.hidden {
/* visibility: hidden; */
height: 0;
overflow: hidden;
opacity: 0;
pointer-events: none;
}
#hidden, .hard-hidden {
display: none;
}
#hint-tab-control {
position: relative;
}
#error-record {
font-family: 'DejaVu Sans Mono', 'Consolas', monospace;
width: 100%;
font-size: 1rem;
line-height: initial;
overflow: auto;
white-space: pre;
height: calc(var(--panel-height) - var(--border-double) * 4);
}
#error-record *::selection {
background-color: var(--notice-wait);
}
.table-wrap {
overflow-x: auto;
width: 100%;
}
table#jslicense-labels1 {
min-width: 40em;
}
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);
}
dl {
margin: var(--span) 0;
display: block;
}
dt { display: inline-block; min-width: 6em; text-align: end; }
dd { display: inline-block; margin: 0 calc(var(--font-size) / 4); }
.contributors dt {
min-width: 12em;
text-align: center;
padding: 0 calc(var(--font-size) / 4);
border-right: var(--border) solid var(--fore-color);
}
hr {
border: none;
border-top: var(--border) solid var(--fore-color);
}
iframe#frame {
width: 100%;
height: 60vh;
border: none;
background-color: transparent;
}
.blank {
height: 0em;
}
.shade {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: var(--back-color);
opacity: 0.95;
z-index: -1;
}
#dialog {
position: fixed;
width: 100%;
height: 100%;
top: 0;
text-align: center;
z-index: 2;
opacity: 1;
}
#dialog>.content {
max-width: 100%;
width: 42em;
max-height: 100vh;
margin: 12vh auto;
border: var(--border) solid var(--fore-color);
transform-origin: center 33%;
}
#dialog.hidden {
opacity: 0;
}
#dialog.hidden>.content {
transform: scaleY(0);
}
#dialog-content {
margin: auto;
padding: var(--span-double);
padding-bottom: 0;
max-height: calc(76vh - 1em);
overflow-y: auto;
}
#dialog-choices {
margin: auto;
padding: var(--span);
padding-top: 0;
position: sticky;
bottom: 0;
}
#choice-input {
max-width: 100%;
width: 16em;
}
#accessibility {
text-align: initial;
display: flex;
flex-direction: row;
justify-content: space-around;
flex-wrap: wrap;
}
#select-language {
/* width: calc(100% - var(--span-double)); */
width: 100%;
height: 8em;
border: var(--border) solid var(--fore-color);
padding: var(--span);
margin: 0 var(--span);
overflow: auto;
}
#select-language option {
cursor: pointer;
}
#select-language option:hover {
text-decoration: underline;
}
#accessibility>* {
flex-grow: 0;
min-width: 16em;
white-space: nowrap;
margin: 1em;
}
@keyframes jump {
0% { transform: translateY(0); }
50% { transform: translateY(var(--font-size)); }
100% { transform: translateY(0); }
}
#loading-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--back-color);
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
z-index: 3;
opacity: 1;
transition-duration: 0.2s;
}
.logo {
background-image: url('icon.svg');
background-position: center;
background-repeat: no-repeat;
background-size: contain;
width: 80%;
max-width: 16em;
height: 80%;
max-height: 16em;
margin: 0 auto;
}
#loading-screen.hidden {
opacity: 0;
}
#loading-screen>.dots {
display: flex;
flex-direction: row;
justify-content: center;
}
#loading-screen>.dots>span {
display: inline-block;
width: var(--font-size);
height: var(--font-size);
margin: var(--font-size);
background-color: var(--fore-color);
border-radius: var(--font-size);
animation: jump 1s ease 0s infinite;
}
#loading-screen>.dots>span:nth-child(1) { animation-delay: 0s; }
#loading-screen>.dots>span:nth-child(2) { animation-delay: 0.3s; }
#loading-screen>.dots>span:nth-child(3) { animation-delay: 0.6s; }
#keyboard-shortcuts-layer {
position: fixed;
top: 0;
left: 0;
width: 100%;
overflow: visible;
pointer-events: all;
z-index: 2;
}
#keyboard-shortcuts-layer span {
display: inline-block;
position: absolute;
/* border: var(--border) dotted var(--fore-color); */
background-color: var(--shade);
padding: var(--span-half) var(--span);
white-space: pre;
line-height: 1em;
font-family: 'DejaVu Sans Mono', 'Consolas', monospace;
transform: translate(-1em, calc(var(--font-size) * -1));
}
@keyframes delay-scrollable {
from { overflow: hidden; }
to { overflow: auto; }
}
@media (max-height: 520px) {
body, main { margin: 0 auto; }
}
@media (max-width: 767.5px) {
/* My test shows it's Just 768 fit best */
:root {
--panel-height: 16em;
--font-size: 1em;
}
main {
flex-direction: column;
}
#title { display: none; }
.canvas-side {
min-width: unset;
margin: 0;
width: 100%;
overflow-x: hidden;
overflow-y: auto;
position: fixed;
top: calc(var(--line-height) + var(--span));
height: calc(100% - var(--panel-height) - var(--compact-menu-height));
z-index: 0;
}
.canvas-side>.buttons,
.menu-side>.buttons {
width: 100%;
}
#control-overlay {
width: 100%;
margin: 0;
}
.menu-side {
overflow-x: hidden;
overflow-y: auto;
position: fixed;
background-color: var(--back-color);
top: unset;
bottom: 0;
left: 0;
height: var(--panel-height);
margin: 0;
width: 100%;
box-sizing: border-box;
}
.menu-side>.menu {
height: calc(var(--panel-height) - var(--compact-menu-height));
margin: 0;
}
#notice {
position: fixed;
top: 0;
width: 100%;
}
.menu-side>.compact-menu {
position: fixed;
bottom: 0;
width: 100%;
z-index: 0;
}
#dialog-content {
padding: var(--span) 0;
}
.blank {
height: var(--compact-menu-height);
}
}
@media (max-width: 385px) {
#preview, #canvas, #control-overlay, .canvas-side>* {
width: 100%;
border: none;
box-sizing: border-box;
}
}
@media (prefers-reduced-motion) {
body *,
body *::before,
body *::after {
transition-duration: 0s !important;
animation-duration: 0s !important;
transition-timing-function: steps(1) !important;
animation-timing-function: steps(1) !important;
}
}
body.no-animation,
body.hard-animation,
body.no-animation *,
body.hard-animation *:not(#loading-screen, #loading-screen *),
body.no-animation *::before,
body.no-animation *::after {
transition-duration: 0s !important;
animation-duration: 0s !important;
transition-timing-function: steps(1) !important;
animation-timing-function: steps(1) !important;
}
body.large-font,
#large-font+label {
font-size: calc(var(--font-size) * 1.2);
line-height: calc(var(--line-height) * 1.2);
}
body.force-rtl,
#force-rtl+label {
direction: rtl;
}
.force-ltr {
direction: ltr;
}
body.high-contrast {
--border: 2px;
--fore-color: #fff;
--back-color: #000;
--target-color: transparent;
--notice-wait: transparent;
--notice-note: transparent;
--notice-warn: transparent;
--notice-error: transparent;
--shade: rgba(0, 0, 0, 0.8);
transition-duration: 0s;
}
body.high-contrast .shade { transition-duration: 0s; opacity: 1; }
/* body.high-contrast * { background-color: var(--back-color); } */
body.high-contrast .logo, body.high-contrast .canvas-group { opacity: 1 !important; }
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: calc(60vh - 8em);
width: var(--paper-width);
overflow: hidden auto;
white-space: break-spaces;
resize: none;
padding-top: .65ex;
line-height: 1.25;
border: var(--border) solid currentColor;
}
.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-words-by-spaces"] { 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-words-by-spaces-label"] { padding-right: 2%; }
#text-font { height: 100%; margin: 0px; }

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +0,0 @@
`
No rights reserved.
License CC0-1.0-only: https://directory.fsf.org/wiki/License:CC0
`;
// Polyfills
// home-made minimal fetch
// Note: only useful with this application. Extend (or remove) it as needed.
// In fact I wanted to support Otter Browser for resource-concerning people
if (!window.fetch) window.fetch = function(url, options) {
options = options || {};
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(options.method || 'GET', url);
function response(is_binary) {
return new Promise(function(resolve, reject) {
resolve(is_binary ? xhr.response : xhr.responseText);
});
}
xhr.onload = function() {
resolve({
text: function() { return response(false); },
json: function() { return response(false).then(t => JSON.parse(t)); },
ok: Math.floor(xhr.status / 100) === 2
});
}
xhr.send(options.body || null);
});
}
// and this
if (!NodeList.prototype.forEach) NodeList.prototype.forEach = Array.prototype.forEach;