typoglycemia 1.0.3

A function to convert text to typoglycemic format with a Leet-speak variant. The function takes a string as input and returns a new string where the first and last letters of each word are unchanged, but the middle letters are shuffled randomly. Additionally, certain letters are replaced with their Leet-speak equivalents (e.g., 'a' becomes '4', 'e' becomes '3', etc.). This creates a fun and visually interesting way to obfuscate text while still keeping it somewhat readable.
Documentation

typoglycemia-rs

Typoglycemia implementation for Rust, with a Leet-speak variant.

Background

The phenomenon known as typoglycemia describes the (unproven) cognitive ability to read a word when the first and last letters are stable, but the intermediate letters are scrambled.

"Adncocrig to a rcheeasrer at Cdmgrbaie Usirveinty, it donse't metatr in waht oerdr the ltertes in a wrod are, the olny itapmrnot tinhg is that the frsit and last leettr be at the right palce. The rest can be a toatl mess and you can siltl read it wuihtot prebolm. Tihs is bsauece the haumn mnid deos not raed ervey letetr by isetlf, but the wrod as a wlohe."

"According to a researcher at Cambridge University, it doesn't matter in what order the letters in a word are, the only important thing is that the first and last letter be at the right place. The rest can be a total mess and you can still read it without problem. This is because the human mind does not read every letter by itself, but the word as a whole."

Features

  • Designed primarily for Latinate languages - English, Spanish, French, etc. - but should work well for Germanic languages
  • Standard Typoglycemia functionality, e.g.
    • "Once upon a midnight dreary, while I pondered, weak and weary" => "Ocne upon a mnihdigt derray, wilhe I pernoedd, waek and wraey"
  • Leet-speak function for added complexity, e.g.
    • Only a small subset of Leet substitutions, see lib.rs
    • "Once upon a midnight dreary, while I pondered, weak and weary" => "0cn3 upon a mgd1hn1t dr3ary, whl13 1 podn33rd, wa3k and w3ray"
  • Hyphenated words will retain their hyphen positions, e.g.
    • "Spanish-speaking country" => "Spsniah-siapenkg cnoruty"
  • Same with apostrophes
    • "I wouldn't or I wouldn't've" => "I wulodn't or I wludon't've"
  • For clarity, words beginning with a numeric character, e.g. date, time, colloquialisms, will not be typoglycemified:
    • "12/22/1986" => no change.
    • "1-for-all" => no change.
  • Words with grapheme length <= 3 or > 15 will also not be typoglycemified
    • "a", "the", "and", "but", "or", "for", "a", "I❤️", "antidisestablishmentarianism", etc.

Usage

use typoglycemia::{typoglycemia, typoglycemia_leet};

fn main() {
    let s = "It was the best of times, it was the worst of \
    times, it was the age of wisdom, it was the age of \
    foolishness, it was the epoch of belief, it was the epoch \
    of incredulity, it was the season of Light, it was the \
    season of Darkness, it was the spring of hope, it was \
    the winter of despair...";

    let t = typoglycemia(s);
    println!("{}", t);

    // It was the bset of tiems, it was the wrsot of
    // tiems, it was the age of wdsiom, it was the age of
    // fssenohilos, it was the epoch of beelif, it was the epoch
    // of iledruicnty, it was the seosan of Lhgit, it was the
    // saeosn of Dnaserks, it was the sinprg of hpoe, it was
    // the wtiner of dpaiser...

    let l = typoglycemia_leet(s, 1);
    println!("{}", l);

    // 1t was th3 83st of tm31s, 1t was th3 wrsot of
    // tm31s, 1t was th3 ag3 of wdo1sm, 1t was th3 ag3 of
    // fsoslhon31s, 1t was th3 3ocph of 83l13f, 1t was th3 3poch
    // of 13c1ltrdnuy, 1t was th3 soas3n of Lhg1t, 1t was th3
    // soa3sn of Dkr3nass, 1t was th3 snpr1g of hpo3, 1t was
    // th3 w1n3tr of d1p3asr...

    let e = typoglycemia(
        "Emojis can convey emotions that might be difficult \
        to express through text alone. For example, a smiley \
        face 😊 can show happiness, while a sad face 😞 can \
        express sadness. Emojis can also emphasize certain \
        words or phrases. For example, using a thumbs-up emoji👍 \
        after a positive statement can reinforce the message. \
        It's best to use emojis sparingly to avoid overwhelming \
        the reader and maintain clarity.",
    );
    println!("{}", e);

    // Emijos can cvnoey enitooms taht mhigt be dciulfift
    // to eseprxs tourhgh text anole.  For eamplxe, a simely
    // fcae 😊 can sohw hnpaesips, wihle a sad fcae 😞 can
    // epexrss sasneds. Eojmis can aslo eihmazspe ciaertn
    // wdros or psrheas. For expmlae, using a tubmhs-up emjoi👍
    // after a pvtiiose statenemt can rencrfoie the mgsasee.
    // It's bset to use eiomjs srpgliany to aovid oeenwlvrmhig
    // the reedar and miaiatnn cialrty.
}

Docs/Testing

$> cargo doc --no-deps --document-private-items
$> cargo test -- --show-output # display some examples

Interactive Testing

A cargo example is included for quick manual testing during development.

# plain typoglycemia
$> cargo run --example try -- "your text here"

# leet-speak at default level 1
$> cargo run --example try -- --leet "your text here"

# leet-speak at a specific level (1–3)
$> cargo run --example try -- --leet 2 "your text here"

Example output:

Input:  Once upon a midnight dreary while I pondered weak and weary
Output: Once uopn a mihdgnit dearry while I pondreed weak and weray

Input:  Once upon a midnight dreary
Output: 0cne uopn a mn1ghd1t darery

Input:  Once upon a midnight dreary
Output: 0cи3 upoи a m1h1иdgt da3rry

Benchmarks

Benchmarks are provided via Criterion and cover both individual word shapes and sentence-level throughput.

$> cargo bench

HTML reports with charts are written to target/criterion/ after each run. Successive runs are compared automatically — Criterion will flag regressions and improvements.

What is measured:

Benchmark Input Notes
word/short "the" (≤3 graphemes) Fast-path return, no scrambling
word/plain "country" (7 chars) Typical English word
word/long "antidisestablishmentarianism" (>15 graphemes) Fast-path return, no scrambling
word/apostrophe "doesn't" (U+2019) Smart-tick normalization + split path
word/hyphenated "Spanish-speaking" Two scramble_word calls internally
word/latin boundary "café" Latin-1 boundary character
sentence/typoglycemia ~25-word prose passage End-to-end throughput
sentence/leet level 1 same passage With leet substitution, level 1
sentence/leet level 3 same passage With leet substitution, level 3

Representative results (Apple M1 Pro, release build):

word/short (≤3 graphemes, fast path)   time: [~393 ns]
word/plain 7-char                      time: [~1.3 µs]
word/long (>15 graphemes, fast path)   time: [~2.7 µs]
word/apostrophe (doesn't)              time: [~2.2 µs]
word/hyphenated (Spanish-speaking)     time: [~4.3 µs]
word/latin boundary (café)             time: [~802 ns]
sentence/typoglycemia (~25 words)      time: [~38 µs]
sentence/typoglycemia_leet level 1     time: [~40 µs]
sentence/typoglycemia_leet level 3     time: [~39 µs]

References