Rotate text 180 degrees. Reverse the character order, flip the glyphs, or both.
Type on the left, get the flipped result on the right, copy it, paste it anywhere. Runs on Windows, Linux, macOS, and Android. Interface in English and Simplified Chinese.
- What it does
- The two toggles
- Download
- Runtime support matrix
- macOS: the first-run allow step
- Windows: WebView2
- Linux: AppImage and FUSE
- Chinese, Japanese, and Korean text
- Offline and no telemetry
- Known gaps
Hello becomes ollǝH. The transform runs in Rust, not in the browser layer, so
the character counter and the result can never disagree about what a character is:
both count grapheme clusters, computed once by the same code.
Input is capped at 20 000 grapheme clusters. Past that the app tells you the count and refuses rather than freezing.
| Toggle | What it does |
|---|---|
| Reverse character order | Reads the text back to front, so the last character comes first. |
| Flip glyphs | Swaps each character for its rotated Unicode counterpart where one exists. |
Both default to on, and both on is the only combination that produces a true 180-degree rotation. Turn one off and you get half the effect: reversed but upright, or flipped in place but still left to right. That is sometimes what you want, which is why they are separate switches instead of one.
There is no download page yet. The AWS pipeline builds all four platforms and writes each one to its own S3 prefix, and that is currently the only place the artifacts live. Publishing them under a git tag is a deliberate deferral, not an oversight, so until that lands you get a build from whoever ran the pipeline. No installer is required on Linux or macOS, and on Windows only if your machine is missing the WebView2 Runtime (see below).
| Platform | Artifact |
|---|---|
| Windows | .exe (portable) and an NSIS installer. Both are built. |
| Linux | .AppImage, x86_64. |
| macOS | .app bundle, universal. |
| Android | .apk signed with the Android debug keystore. Installable for testing. |
"Install-free" is not the same as "runs anywhere". Here is the real floor for each platform, measured rather than assumed.
| Platform | Architecture | Runtime requirement | Notes |
|---|---|---|---|
| Windows | x86_64 | Microsoft Edge WebView2 Runtime | Bundled with Windows 11. Not guaranteed on Windows 10. The portable .exe needs the Runtime already present and cannot install it; use the NSIS installer on machines without it. |
| Linux | x86_64 | glibc 2.35 or newer | Measured floor, set by the bundled WebKitGTK, Cairo, and JavaScriptCore libraries. Needs FUSE to mount, or run with APPIMAGE_EXTRACT_AND_RUN=1 if FUSE is unavailable. |
| macOS | universal: arm64 + x86_64 |
none beyond the OS | Ad-hoc signed, so the first launch is blocked until you allow it in System Settings. Both Apple Silicon and Intel Macs are covered. |
| Android | aarch64 only |
Android 7.0, minSdk 24 |
The APK carries aarch64 only, so 32-bit ARM and x86 devices are not covered. It is signed with the Android debug keystore, which is enough to install and run it but is not a distributable build. |
| iOS | not applicable | not applicable | Out of scope. No iOS artifact is built or published. |
The Linux figure comes from build/linux-runtime-floor.txt, produced by scanning
every ELF in the AppImage payload, and scripts/check-readme.mjs fails the build
if the number above stops matching it. That file also records a sha256 of the
artifact it measured. That hash is provenance for the measurement, not a download
checksum: the AppImage is not byte-reproducible, so a fresh build has the same
size and a different hash. Do not use it to verify a download.
The Android APK is signed with the Android debug keystore, and the build reads the certificate back off the finished archive to confirm that is what it got. That keystore is public and shared by every SDK install, so the signature proves nothing about who built the file. Treat the APK as a test build: it installs, it runs, and it must not be handed out as a shipped app. A real upload keystore, held outside this repository, is the follow-up that changes that.
The .app bundle is ad-hoc signed. That is deliberate, and it changes what
Gatekeeper says rather than silencing it. Instead of "is damaged and can't be
opened", which is what an unsigned bundle gets and which reads like a corrupt
download, you get a "blocked" message. The prompt does not go away. You allow
the app once:
- Double-click the app. macOS blocks it.
- Open System Settings > Privacy & Security.
- Scroll to the security section. The blocked app is named there.
- Click Open Anyway and confirm.
After that the app opens normally. There is no notarized build, so this step is required on every machine, once.
The app renders through the Microsoft Edge WebView2 Runtime. Windows 11 ships it. Windows 10 may or may not have it, depending on how the machine was set up and what else is installed, so treat its presence as unknown there.
The portable .exe cannot fix a missing Runtime. Every available bootstrap mode
in the build configuration is installer-only, which means the standalone
executable has no path to fetch or install it and simply fails to render. That is
why both artifacts ship: use the .exe if the Runtime is present, and the NSIS
installer if it is not or if you cannot tell.
The AppImage mounts itself with FUSE. On a host without /dev/fuse or without the
capability to mount, the runtime prints:
fuse: device not found, try 'modprobe fuse' first
Cannot mount AppImage, please check your FUSE setup.
and exits 127. The app never starts. This was measured on such a host, not inferred, so the matrix says AppImage is not universally install-free.
The same file runs fine without FUSE if you let it unpack itself first:
APPIMAGE_EXTRACT_AND_RUN=1 ./UpToDown_0.1.0_amd64.AppImageThat is slower to start because it extracts on every launch, and it is the right answer inside containers and on locked-down hosts.
The preview rotates. The clipboard does not. This is the one place where what you see and what you copy genuinely differ, so it is worth stating plainly.
Han characters have no rotated form in Unicode. This is not a feature that was
skipped: there is no codepoint to map them to. Every Han character in
UnicodeData.txt and Blocks.txt carries Bidi_Mirrored=N, without exception, so
per-character flipping is impossible rather than merely unimplemented. Latin
letters get a to ɐ because someone once encoded ɐ. Nobody encoded an
upside-down 说.
So CJK text can only be reordered. Punctuation that does have a mirrored form is swapped:
| Input | 他说(真的) |
| Copied result | (的真)说他 |
That result is real text. You can paste it into any editor and it survives.
The rotated preview panel is separate. It takes the original text and turns it 180 degrees on screen with a CSS transform, which looks like what you probably wanted. It is a visual effect only. The characters underneath are unchanged, and selecting or copying from that panel gives you the upright original.
The app makes no network request of any kind. There is no telemetry, no analytics, no crash reporting, no update check, and nothing is collected or transmitted. The transform is local computation on local text. Unplug the machine and it works identically.
Four claims in this project cannot be verified from the Linux machine that builds and tests it. They are gaps, not guarantees, and they are listed here rather than quietly assumed.
The middle column below states only what is actually checked in this tree today, and for one of the four the honest answer is nothing. An empty-handed cell is the point of this section, not a hole in it.
| Gap | Checked instead | Future path |
|---|---|---|
The portable Windows .exe launching standalone |
scripts/check-tauri-config.mjs asserts bundle.targets is an explicit non-empty list rather than the string "all", and asserts webviewInstallMode is absent from both configs. So the raw .exe can never be configured to look as though it bootstraps a Runtime it cannot install. That the list happens to contain nsis is not itself asserted, and nothing launches the binary. |
A run on a real Windows host, and a Windows CodeBuild job that launches the binary. |
| The NSIS installer bootstrapping WebView2 on a Runtime-less machine | Nothing. The install-mode setting is deliberately out of scope and is mechanically forbidden by the config checker, so the installer relies on Tauri's default behavior, which cannot be observed from Linux at all. | A Windows VM image with the Runtime removed, driven in CI. |
| A macOS double-click succeeding after the Gatekeeper allow | Two things, neither of them the double-click. A config fact: scripts/check-readme.mjs asserts src-tauri/tauri.conf.json sets "signingIdentity": "-", which is what makes the bundle ad-hoc signed and therefore what turns "is damaged" into "blocked". And a build receipt: the macOS job writes build/macos-verification.json from values it measured on that build, so a Linux host can read the architectures lipo reported, a codesign --verify --deep --strict status on the bundle and again on a copy re-extracted from the shipped archive, and whether the main executable kept its exec bit through that round-trip. Nobody has opened the app. A zero exit from codesign is a signature that parses, not a window. |
A macOS runner that performs the allow and launches the app, and moving the signing assertion into scripts/check-tauri-config.mjs alongside the other bundle checks. |
| The Android WebView actually applying the embedded WOFF2 subset | Asserted: scripts/check-font-subset.mjs checks the subset's codepoint coverage against the closure of the flip mapping, so the font is known to contain every glyph the app can emit. True but unasserted: the subset is byte-reproducible by construction, because scripts/build-font-subset.mjs passes --no-recalc-timestamp for exactly that reason; a rebuild does reproduce the committed bytes, but no target enforces that it stays so. Not checked at all: whether the WebView loads the font. |
A Device Farm run on real hardware, checking the rendered glyphs, and wiring the reproducibility check up as a git diff --exit-code -- src/assets/fonts/ after a rebuild. |
The build never claims any of these four as verified, and no acceptance check in
this project depends on a person looking at a screen. scripts/check-readme.mjs
also pins the claims earlier drafts of this file made and had to retract: one about
the WebView2 install-mode setting being checked for a value, and one that folded the
font subset's reproducibility into the list of things the font checker asserts.
Neither was true. Three more pins were added after this file was caught a third
time: the Android artifact may not be described as a shipped one, the download
section may not send you to a page of tagged downloads while no tag and no release
workflow exist, and no cell may go back to denying that the macOS build job exists.
That last claim was true when it was written and stopped being true when the job
landed, which is the failure mode a pinned sentence catches and a careful author does
not.