From 5ede2507d7c3750e5e7fec6f790811ef15796e60 Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Wed, 16 Sep 2026 16:50:55 -0700 Subject: [PATCH] Release ZWUS-7 as the default encoding --- index.js | 25 ++++++++++++++++--------- package.json | 2 +- readme.md | 10 ++++++---- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/index.js b/index.js index 8b2e2a1..037cb32 100644 --- a/index.js +++ b/index.js @@ -4,37 +4,44 @@ const zwus = { 3: {unifier: "\u{00AD}", 0: "\u{180E}", 1: "\u{200B}", 2: "\u{200D}"}, 6: {unifier: "\u{200C}", 0: "\u{200D}", 1: "\u{200F}", 2: "\u{00AD}", 3: "\u{2060}", 4: "\u{200B}", 5: "\u{200E}"}, - 8: {unifier: "\u{200C}", 0: "\u{200D}", 1: "\u{200F}", 2: "\u{00AD}", 3: "\u{2060}", 4: "\u{200B}", 5: "\u{200E}", 6: "\u{180E}", 7: "\u{FEFF}"}, + 7: {unifier: "\u{200C}", 0: "\u{200D}", 1: "\u{200F}", 2: "\u{00AD}", 3: "\u{2060}", 4: "\u{200B}", 5: "\u{200E}", 6: "\u{FEFF}"}, /** * Encodes a string into a sequence of zero-width characters. * @param {string} text - The input text to encode. - * @param {number} base - The numerical base for encoding. Defaults to 6. Options: 3, 6, 8. Larger the base, the smaller the output, but the more likely the zero width will be detectable by sight. + * @param {number} base - The numerical base for encoding. Defaults to 7. Options: 3, 6, 7. * @returns {string} The encoded string. */ - encodeString: (text, base = 6) => Array.from(text, u => u.codePointAt(0).toString(base).split('').map(x => zwus[base][x]).join('')).join(zwus[base].unifier), + encodeString: (text, base = 7) => Array.from(text, u => (+base === 7 ? rankOf(u) : u.codePointAt(0)).toString(base).split('').map(x => zwus[base][x]).join('')).join(zwus[base].unifier), /** * Encodes an array of numbers into a sequence of zero-width characters. * @param {Array} arr - The array of numbers to encode. - * @param {number} base - The numerical base for encoding. Defaults to 6. Options: 3, 6, 8. Larger the base, the smaller the output, but the more likely the zero width will be detectable by sight. + * @param {number} base - The numerical base for encoding. Defaults to 7. Options: 3, 6, 7. * @returns {string} The encoded array. */ - encodeNumberArray: (arr, base = 6) => arr.map(n => n.toString(base).split('').map(x => zwus[base][x]).join('')).join(zwus[base].unifier), + encodeNumberArray: (arr, base = 7) => arr.map(n => n.toString(base).split('').map(x => zwus[base][x]).join('')).join(zwus[base].unifier), /** * Decodes a string of zero-width characters back into the original string. * NOTE: Decoding accuracy is contingent upon the original encoding base and alphabet. * @param {string} text - The encoded text to decode. - * @param {number} base - The numerical base for decoding. Defaults to 6. Must match the base used for encoding. + * @param {number} base - The numerical base for decoding. Defaults to 7. Must match the base used for encoding. * @returns {string} The decoded string. */ - decodeToString: (text, base = 6) => text.split(zwus[base].unifier).map(x => String.fromCodePoint(parseInt(Array.from(x).map(z => Object.keys(zwus[base]).find(k => zwus[base][k] === z)).join(''), base))).join(''), + decodeToString: (text, base = 7) => zwus.decodeToNumberArray(text, base).map(n => String.fromCodePoint(+base === 7 ? pointOf(n) : n)).join(''), /** * Decodes a string of zero-width characters back into the original array of numbers. * NOTE: Decoding accuracy is contingent upon the original encoding base and alphabet. * @param {string} text - The encoded text to decode. - * @param {number} base - The numerical base for decoding. Defaults to 6. Must match the base used for encoding. + * @param {number} base - The numerical base for decoding. Defaults to 7. Must match the base used for encoding. * @returns {Array} The decoded array of numbers. */ - decodeToNumberArray: (text, base = 6) => text.split(zwus[base].unifier).map(x => parseInt(Array.from(x).map(z => Object.keys(zwus[base]).find(k => zwus[base][k] === z)).join(''), base)), + decodeToNumberArray: (text, base = 7) => text.split(zwus[base].unifier).map(x => Array.from(x).map(z => Object.keys(zwus[base]).find(k => zwus[base][k] === z)).join('')).filter(Boolean).map(x => parseInt(x, base)), }; +// Printable ASCII gets short base-7 values; other code points keep their value. +const priority = "te aoinshrdlucmfwypvbgkjqxz.,!?'-:;()0123456789ETAOINSHRDLUCMFWYPVBGKJQXZ"; +const order = [...new Set([...priority, ...Array.from({length: 95}, (_, i) => String.fromCharCode(i + 32))])]; +const ranks = new Map(order.map((char, rank) => [char, rank])); +const rankOf = char => ranks.get(char) ?? (char.codePointAt(0) < 32 ? char.codePointAt(0) + 95 : char.codePointAt(0)); +const pointOf = rank => rank < 95 ? order[rank].codePointAt(0) : rank < 127 ? rank - 95 : rank; + export default zwus; diff --git a/package.json b/package.json index 99c77ec..efc3a04 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zwus", - "version": "2.3.0", + "version": "3.0.0", "type": "module", "homepage": "https://zwus.pages.dev", "license": "WTFPL", diff --git a/readme.md b/readme.md index 2f3dabe..29a32b7 100644 --- a/readme.md +++ b/readme.md @@ -22,20 +22,22 @@ const decoded = zwus.decodeToNumberArray(encoded); ### Base -Higher base = shorter output, but more likely visible in some renderers. +Base 7 ranks printable ASCII so common English characters use one or two digits. Base 6 keeps Unicode code points as numbers. Some platforms strip particular zero-width characters. ```js zwus.encodeString("hi", 3) // safest -zwus.encodeString("hi", 6) // default, compact -zwus.encodeString("hi", 8) // most compact +zwus.encodeString("hi", 6) // code point encoding +zwus.encodeString("hi", 7) // default, compact for ordinary English text ``` Decode must match the encode base: ```js -zwus.decodeToString(encoded, 6) +zwus.decodeToString(zwus.encodeString("hi", 7), 7) ``` +Number arrays use ordinary base digits in every standard. Base 7's frequency ranking applies only to strings. Decoding ignores visible text mixed into a payload. + ## Interop Encoded output is byte-identical to the [Rust crate](https://crates.io/crates/zwus), so you can encode in JS and decode in Rust or vice versa.