1
0
Fork 0
No description
  • Rust 85.5%
  • Python 10.6%
  • JavaScript 2.9%
  • Shell 1%
Find a file
2026-10-29 15:23:42 -06:00
.kilo Add debian packaging support 2026-10-29 15:23:42 -06:00
data Phase 7 EN, really 2026-10-24 13:02:02 -06:00
debian Add debian packaging support 2026-10-29 15:23:42 -06:00
docs Add debian packaging support 2026-10-29 15:23:42 -06:00
resources Phase 7 EN, really 2026-10-24 13:02:02 -06:00
scripts Add debian packaging support 2026-10-29 15:23:42 -06:00
src Optimizations, more lean 2026-10-25 12:12:59 -06:00
tests Add debian packaging support 2026-10-29 15:23:42 -06:00
vendor/hanzi_lookup Optimizations, more lean 2026-10-25 12:12:59 -06:00
.gitignore Add debian packaging support 2026-10-29 15:23:42 -06:00
AGENTS.md Add debian packaging support 2026-10-29 15:23:42 -06:00
cargo-sources.json Phase 7 EN, really 2026-10-24 13:02:02 -06:00
Cargo.lock Phase 7 EN, really 2026-10-24 13:02:02 -06:00
Cargo.toml Add debian packaging support 2026-10-29 15:23:42 -06:00
LICENSE Implement phase 1 2026-10-23 18:31:47 -06:00
LICENSE-APL Implement phase 1 2026-10-23 18:31:47 -06:00
LICENSE-FrequencyWords Implement phase 1 2026-10-23 18:31:47 -06:00
LICENSE-LGPL-3.0.txt Implement phase 1 2026-10-23 18:31:47 -06:00
LICENSE-rusqlite Phase 7 EN, really 2026-10-24 13:02:02 -06:00
LICENSE-UNICODE Phase 7 EN, really 2026-10-24 13:02:02 -06:00
LICENSE-unicode-normalization-MIT Phase 7 EN, really 2026-10-24 13:02:02 -06:00
org.spacecruft.HanziDraw.json Phase 7 EN, really 2026-10-24 13:02:02 -06:00
README.md Add debian packaging support 2026-10-29 15:23:42 -06:00
UPSTREAM.md Add debian packaging support 2026-10-29 15:23:42 -06:00

Hanzi Draw Rust

A fully offline native GTK4 + libadwaita port of HanziDraw, targeting the Purism Librem 5 and distributed as a Flatpak. It provides handwriting candidates, single-character and single-syllable pinyin lookup, explicit English → Mandarin full-word lookup, CC-CEDICT definitions, and stroke-order animation.

The lookup selector defaults to Mandarin. Selecting English searches definitions and returns complete single- and multi-character Mandarin entries, including their raw pinyin and definitions. This does not add direct multi-Hanzi word input or multi-syllable-pinyin word search; both remain out of scope, and Mandarin keeps its existing single-character/single-syllable behavior. English search is lexical lookup, not machine translation.

The Svelte app under tmp/repos/hanzi-draw/ is a read-only reference clone, not part of the native build or Flatpak. Its raw words.json is used only when regenerating the English database and is not shipped.

Prerequisites

  • Rust stable via rustup, >= 1.92 (PureOS crimson's rustc 1.63 is too old).
  • For native builds, GTK >= 4.8 and libadwaita >= 1.2 development files with pkg-config. Crimson's GTK 4.8.3 and libadwaita 1.2.2 satisfy these requirements.
  • Node.js >= 20.19 only for the data/parity maintenance scripts.
  • For Flatpak builds: flatpak-builder, org.gnome.Platform//49, org.gnome.Sdk//49, and org.freedesktop.Sdk.Extension.rust-stable. Regenerating Cargo sources also needs Python 3 and flatpak-cargo-generator.py from flatpak-builder-tools.

See docs/PORT-rust.md for the platform constraints and rationale.

Native build and run

cargo build
glib-compile-schemas resources/
GSETTINGS_SCHEMA_DIR="$PWD/resources" cargo run

glib-compile-schemas generates resources/gschemas.compiled; rerun it after changing the schema. If Debian does not expose the compiler on PATH, use its multiarch path, such as /usr/lib/x86_64-linux-gnu/glib-2.0/glib-compile-schemas on x86_64. GSETTINGS_SCHEMA_DIR tells development runs where to find the compiled schema; the Flatpak uses its installed system schema directory instead.

Without PKGDATADIR, stroke data loads from data/strokes-shards/ and the English database loads from data/english-dictionary.sqlite in the checkout. With PKGDATADIR, the corresponding packaged paths are $PKGDATADIR/strokes-shards/ and $PKGDATADIR/english-dictionary.sqlite; the Flatpak sets PKGDATADIR=/app/share/hanzi-draw.

The English database is a separate read-only file, not embedded in the executable. Startup and Mandarin lookup do not check or open it. The first English selection starts one per-window worker and opens the database asynchronously; subsequent mode changes reuse that bounded connection until the window closes. There is no runtime corpus parsing, index generation, or download. A missing or corrupt English database is an English-mode error and must not silently switch to pinyin results.

Native Debian package

PureOS crimson packaging uses a staged source tree, locked Cargo vendoring, and offline compilation. See docs/packaging.md for dependencies, payload details, release rules, and validation boundaries.

With Debian-packaged Rust/Cargo 1.92 or newer and the remaining build dependencies installed, build from the repository root:

sh scripts/build-native.sh

When rustup supplies the toolchain outside dpkg, use the explicit dependency- check bypass (all actual tools and libraries are still required):

sh scripts/build-native.sh -d

The exact .deb is written below build/native-source/. The application is compiled with PKGDATADIR=/usr/share/hanzi-draw; only the committed English SQLite database and 99 stroke shards are installed as external lookup data. The package depends on fonts-noto-cjk and retains bundled SQLite/FTS5.

Inspect and test the candidate without installing it:

candidate=build/native-source/hanzi-draw_VERSION_ARCH.deb
lintian "$candidate"
PYTHONDONTWRITEBYTECODE=1 python3 tests/debian_package_smoke.py "$candidate"

Package builds, installed-package checks, and Librem 5 acceptance are separate validation stages recorded in docs/DEBIAN-VALIDATION.md.

Tests and maintenance data

cargo test
cargo test --features dictionary-generator
cargo test --manifest-path vendor/hanzi_lookup/Cargo.toml
cargo fmt --package hanzi-draw -- --check
cargo clippy --package hanzi-draw --all-targets --features dictionary-generator -- -D warnings

The feature-enabled test and clippy commands include the English database generator as well as the shared normalization/search code. The differential engine-parity test is ignored during normal runs. Run it after engine or parity-fixture changes (a full debug run takes about nine minutes):

cargo test -- --include-ignored

Regenerate-only maintenance data

Never hand-edit generated stroke shards, parity fixtures, data/word-freq.json, or data/english-dictionary.sqlite. Regenerate them only when their locked inputs or generators change, and keep UPSTREAM.md provenance and the separate validation evidence current. Do not run generators inside the reference clone.

With the read-only reference clone present, regenerate the older stroke and parity artifacts only when their inputs change:

node scripts/build-strokes-shards.mjs  # upstream stroke data changed
node scripts/parity-expectations.mjs   # JS baseline/parity corpus changed

English search uses two additional generated artifacts:

  • data/word-freq.json contains derived whole-headword frequency counts. It is a generation-time input and is not installed in the Flatpak.
  • data/english-dictionary.sqlite contains the complete single- and multi-character entries plus derived English search indexes. It is committed and installed as the only English lookup runtime data file; raw words.json and word-freq.json are not installed.

The FrequencyWords input has a new native PHASE 7 pin, not a claimed reconstruction of the reference project's historical raw input. See UPSTREAM.md for its revision, path, hash, and licensing. The frequency generator's native cache is scripts/.cache/zh_cn_50k.txt, which is gitignored and outside the reference clone. With no source argument, the script uses a valid cached file or downloads the locked input as a generation-time-only operation. It verifies the downloaded bytes' SHA-256, writes scripts/.cache/zh_cn_50k.txt.part, and atomically renames that file before use; neither the application nor the Flatpak performs this download:

node scripts/build-word-freq.mjs

To disallow network access during maintenance, supply an existing copy of the locked input explicitly. The script verifies the same hash before deriving data/word-freq.json:

node scripts/build-word-freq.mjs /path/to/zh_cn_50k.txt

After either frequency command, regenerate the database. This command reads native data/cedict.json, generated data/word-freq.json, and tmp/repos/hanzi-draw/public/data/words.json from the read-only reference:

cargo run --features dictionary-generator --bin build_english_dict

Use these same commands after changing the shared normalizer, schema, or generator. Review the generator's actual counts, integrity checks, output hash, and reproducibility evidence; do not infer them from the locked source totals. Record final generation evidence in UPSTREAM.md and PHASE 7 validation rather than treating a completed command as installed-Flatpak or device validation.

Flatpak build, run, and checks

cargo-sources.json is generated from the committed Cargo.lock; the in-tree vendor/hanzi_lookup path dependency needs no separate source entry. Regenerate it only when Cargo.lock changes, not for source-only edits (adjust the generator script path for your installation):

python3 flatpak-cargo-generator.py Cargo.lock -o cargo-sources.json

Build and install for the current user, then run and inspect permissions:

flatpak-builder --user --install --force-clean build-dir org.spacecruft.HanziDraw.json
flatpak run org.spacecruft.HanziDraw
flatpak info --show-permissions org.spacecruft.HanziDraw

Confirm that Wayland/fallback-X11 and IPC are present and that no network permission is granted. Check packaged shard and English-database loading and GSettings theme persistence. The Flatpak installs only /app/share/hanzi-draw/english-dictionary.sqlite for English search: it must not contain raw words.json or word-freq.json, SQLite journal/WAL files, Node, the generator, or reference-clone paths. Validate the metadata:

appstreamcli validate --explain resources/org.spacecruft.HanziDraw.metainfo.xml
desktop-file-validate resources/org.spacecruft.HanziDraw.desktop

For aarch64, run the same flatpak-builder command on the Librem 5; there is no cross-compilation setup. Host and device checks have separate evidence sections in the PHASE 6 validation report. PHASE 7 has its own host, staged-Flatpak, and Librem 5 validation report. Native and staged checks do not replace installed-Flatpak or device evidence, and PHASE 7 work does not complete any pending PHASE 6 row. These instructions do not assert that those checks have passed.

Flathub submission is a separate user-owned PR. The manifest preserves the PHASE 6 locked local-build inputs and is not yet submission-ready; review the current Flathub follow-ups in the PHASE 6 validation report before preparing a submission.

License and provenance

The app is GPL-3.0-or-later. Bundled components retain their own licenses: hanzi_lookup is LGPL-3.0, recognition/stroke data is under the Arphic Public License, CC-CEDICT and adapted English dictionary/search data are CC BY-SA 4.0, and FrequencyWords plus derived frequency data are MIT. See UPSTREAM.md for source pins, the final generation record, SQLite/normalization dependency notices, engine-equivalence parity results, attribution and license links, reference-only assets, and the scoped provenance review of the identified hanzidict-derived material.