alphaFeedback

WorksComputer Science

No Tokenizer, Still a Gap: Cross-Lingual Fairness of Homoglyph and Invisible-Character Watermarking

AI contributions
Not recorded for this paper
View details

Abstract

Homoglyph and invisible-character watermarks are edit-based watermarks. Instead of changing how a model generates text, they hide a signal in the text after it is written. They swap some characters for homoglyphs, which look the same, or very similar, but have different Unicode code points, or they insert characters that do not display, all without access to the model that wrote the text. Generation-time watermarks work less well for lower-resourced languages due to how tokenizers split their text. Because they work on individual characters, these watermarks never touch a tokenizer, so we ask whether they escape this gap. Across 40 language varieties in 24 scripts, we measure capacity (the hidden bits each character can carry), how much of it survives routine handling and targeted removal, and how much text a keyed detector needs. Capacity varies 3.3-fold and script accounts for 96% of that variation. Within Latin script, resource level makes no detectable difference (ρ=+0.095ρ=+0.095, p=0.78p=0.78), but across scripts capacity rises with it (ρ=+0.54ρ=+0.54, 95% CI [+0.14,+0.83][+0.14,+0.83]), because lower-resourced languages tend to use scripts with few homoglyphs. Ordinary copy-paste, which strips invisible characters, widens the gap: a detector then needs about 2 code points of English or Persian but 93 of Lao. Removing the tokenizer does not remove the inequity, but instead only changes its source.

Introduction

In April 2025, the education company Rumi reported that OpenAI’s o3 and o4-mini models were leaving narrow no-break spaces (U+202F) in long answers ([23]). These characters looked like ordinary spaces, survived pasting into Google Docs, and were taken for a hidden watermark. OpenAI said they were a side effect of training, but the case shows that people already treat disguised characters as evidence of where text came from.

Edit-based watermarks are built on such characters as they mark text after it is written, instead of while a model generates it. They rely on two kinds of character. The first is the homoglyph, a character that looks the same as another, but has a different Unicode code point. The letter aa as a Latin letter (U+0061) and a Cyrillic letter (U+0430), for example, look identical in most fonts, yet a computer stores them as different characters (Figure 1). The second is the invisible character, such as the zero-width space (U+200B), which takes up no room on screen.

Plain and marked strings that render alike but differ in code points

Figure 1. Two strings that render alike but differ in code points; the highlighted edits are two Cyrillic homoglyphs and a zero-width space.

A watermark swaps some characters for homoglyphs or inserts invisible ones, with a secret key deciding which edits to make ([22, 25, 21, 10]). A detector holding the key can later check for that pattern, while the text still reads the same to a person. No access to the model that wrote the text is needed. Generation-time watermarks, by contrast, bias a model’s sampling ([13, 16] (Dathathri et al. 2024)), and post-hoc schemes that avoid model access still re-word the text ([4]).

Generation-time watermarks lose strength across languages as they do not survive translation ([9]) and weaken in low- and medium-resource languages ([15]). The usual explanation is the tokenizer, which splits lower-resourced languages into more and smaller pieces and so shifts the statistics a detector relies on [20, 1, 24]. Edit-based watermarks never touch a tokenizer, which raises a simple question: without a tokenizer, does the gap remain? This has not been examined. Edit-based work reports capacity in aggregate or for a single script [26, 10], and work on watermark fairness covers statistical schemes only [18].

We cover 40 language varieties in 24 scripts. For each, we measure how many hidden bits each character can carry (its capacity), how much of that survives as the text is handled, how much text the detector needs, and how visible the edits are. Since the watermark is added after writing, it does not matter who wrote the text, so we use a human-translated parallel corpus where every language carries the same sentences, and differences reflect script and spelling, not content.

Marked text can change in two ways. Some changes are routine, such as copy-paste or the Unicode normalization many systems apply. They happen whether or not anyone is after the watermark, so if they erase more of it in some languages, that is a fairness problem. Other changes are deliberate. Security research uses the same characters to attack NLP systems and hide code [3, 2], and Unicode specifies defenses that detect them or map them to a shared form [7]; we treat these defenses as the adversary. Combining that mapping with compatibility and whitespace folding erases the watermark in every language, so we report this floor rather than claim robustness.

Capacity turns out to be a property of the script. It varies more than threefold across languages and script accounts for almost all of that variation. Within Latin script, where enough languages exist for a direct test, resource level [12] makes no detectable difference to capacity. Across scripts, however, lower-resourced languages tend to be written in scripts with few homoglyphs, so they receive less capacity. Ordinary copy-paste, which strips invisible characters, widens the gap: afterwards the same detector needs about 2 code points of English or Persian to find the watermark, but 93 of Lao. Removing the tokenizer does not remove the inequity; it moves its source into the Unicode standard. Our contributions are as follows:

  1. A per-language framework for the capacity, survival and visibility of edit-based watermarks across 40 language varieties in 24 scripts.

  1. A transformation ladder from routine handling to deliberate removal, with survival broken down by the kind of homoglyph or invisible character.

  1. A keyed measurement instrument showing that capacity predicts how much text a detector needs.

  1. A fairness analysis that treats scripts, not languages, as the unit of evidence, and shows that the disparity is organized by script yet still falls on lower-resourced languages.

Related Work

Generation-time watermarks across languages. Generation-time watermarks bias which tokens a model samples [13, 14, 16] (Datathatri et al. 2024), and they do not work equally well in every language. He et al. found that the signal does not survive translation and turned this into a removal attack [9]. Mohamed and Gubri found that watermarks billed as multilingual fail in low- and medium-resource languages, and recover much of their strength when the text is routed through a better-supported language [15]. The usual explanation is the tokenizer, which splits lower-resourced languages into more and smaller pieces [20, 1, 24] and so changes the per-token statistics a detector relies on. The watermarks we study never touch a tokenizer, so any gap they show must come from somewhere else.

Edit-based watermarks. Edit-based watermarks change individual characters in finished text. Rizzo et al. watermark English text by homoglyph substitution [22]. Sato et al.’s EasyMark includes a variant for Chinese, Japanese and Korean text that uses variation selectors [25], so the field already knows the channel depends on script, but no comparison across scripts follows. Por et al. hide bits in the spaces between words [21] and Hellmeier et al. benchmark ten whitespace methods on a million Wikipedia articles but report capacity as a single aggregate number (Hellmeier et al., [10]). Shirali-Shahreza and Shirali-Shahreza hide data in Persian and Arabic text with zero-width joiner and non-joiner characters, within that one script ([26]). None of this work weights capacity by the characters a language actually uses, which is where the per-language gap appears.

Homoglyphs as an attack. Security research treats the same characters as a threat. Boucher et al. showed that a few homoglyphs or invisible characters can break deployed NLP systems ([3]) and hide logic in source code (Boucher and Anderson, [2]). Unicode’s own defense is older, UTS #39, written to catch spoofed identifiers such as domain names, lists confusables (Unicode’s term for homoglyphs) and specifies mixed-script detection and skeleton matching, which maps homoglyphs to a shared form (Davis and Suignard, [7]). We use UTS #39 in both directions, utilizing its confusables list as our raw material and its detectors as the removal attacks in our ladder.

Fairness in watermark evaluation. Nemecek et al. argue that watermark benchmarks should report results across languages and populations ([18 b]), but they cover statistical and frequency-domain watermarks and measure detection performance only. We study a character-level channel, measure its capacity directly, and trace its gap to Unicode’s uneven supply of homoglyphs.

Measurement Framework

We treat an edit-based watermark as a channel and ask what it can carry in each language. An embedder marks finished text with a secret key that decides which characters to swap and which gaps receive an invisible character; the edits aim to leave the text looking the same. A detector holding the same key later checks a text. The detector also holds the original text, which makes it cover-informed. It needs the original because a homoglyph does not reveal which character it replaced. Stated plainly, without the original, the detector cannot tell which substitutes a position had to choose from. A cover-informed detector is therefore an upper bound on one without it. This section defines the substitution sets, capacity and the transformations we apply. The sections that follow measure capacity and ask what organizes its differences, then measure how much survives, how much text a detector needs and how visible

the edits are.

Data. We use the FLORES+ devtest split (NLLB Team et al., [19]), 1,012 sentences per language, for the 40 varieties listed in Table 3 (Appendix § 10). Character data are frozen at Unicode 17.0.0 (The Unicode Consortium, [28]; Davis and Suignard [7]) and CLDR 48.2.0.

CodeLanguageScriptClass
eng_LatnEnglishLatin5
spa_LatnSpanish (Lat. Am.)Latin5†
fra_LatnFrenchLatin5
deu_LatnGermanLatin5
por_LatnPortuguese (Brazilian)Latin4†
vie_LatnVietnameseLatin4
tur_LatnTurkishLatin4
swh_LatnSwahiliLatin2
ind_LatnIndonesianLatin3
yor_LatnYorubaLatin2
hau_LatnHausaLatin2
rus_CyrlRussianCyrillic4
ukr_CyrlUkrainianCyrillic3
srp_CyrlSerbianCyrillic4
kaz_CyrlKazakhCyrillic3
ell_GrekGreekGreek3
hye_ArmnArmenianArmenian1
kat_GeorGeorgianGeorgian3
heb_HebrHebrewHebrew3
arb_ArabModern Standard ArabicArabic5†
pes_ArabWestern PersianArabic4†
urd_ArabUrduArabic3
cmn_HansMandarin ChineseHan (Simpl.)5†
cmn_HantMandarin ChineseHan (Trad.)5†
jpn_JpanJapaneseHan and kana5
kor_HangKoreanHangul4
hin_DevaHindiDevanagari4
mar_DevaMarathiDevanagari2
ben_BengBengaliBengali3
tam_TamlTamilTamil3
tel_TeluTeluguTelugu1
guj_GujrGujaratiGujarati1
pan_GuruEastern PanjabiGurmukhi2†
kan_KndaKannadaKannada1
mal_MlymMalayalamMalayalam1
tha_ThaiThaiThai3
lao_LaooLaoLao2
mya_MymrBurmeseMyanmar1
khm_KhmrKhmerKhmer1
amh_EthiAmharicEthiopic2

Table 3. The 40 language varieties. Class is the resource class of Joshi et al. [12], from 0 (least resourced) to 5 (most); † marks a class assigned by a judgement call rather than an exact name match.

Two tracks and four families. The watermark is written on two tracks. The substitution track replaces a character with a homoglyph in place; the insertion track puts an invisible character into a gap between two characters. Prior systems use one track at a time (§2); we model both. The substitution set S(c)S(c) of a character cc holds cc itself and every character that can replace it, drawn from separate Unicode tables so that we can tell where capacity comes from. Family (i), cross-script, holds homoglyphs from another script, such as the Cyrillic а (U+0430) for the Latin a. These come from the UTS #39 confusables list, where two characters count as confusable when they map to the same skeleton (Davis and Suignard, [7]). Family (ii), compatibility, holds characters that Unicode’s compatibility decomposition folds to cc, such as the fullwidth A (U+FF21) for A. Family (iv), whitespace, holds the other White_Space characters when cc is itself whitespace, such as the narrow no-break space (U+202F) from §1. Same-script confusables that are not compatibility variants form a residual class, tracked separately. Family (iii), the invisible characters, is the insertion alphabet MM rather than a substitution family; invisible characters never enter any S(c)S(c), so the two tracks do not overlap. A substitute that fits several families is counted once, in the order iv, ii, i, residual. Homoglyphs of multi-character sequences cannot be swapped one for one and are excluded; Appendix § 11 gives the counts. Only family (i) and the residual come from the hand-curated confusables list, which is known to be sparse for CJK and Indic scripts; families (ii) and (iv) come from systematic property tables.

Admissibility. Not every homoglyph suits every language, so three nested policies limit S(c)S(c). Policy πintra\pi_{\mathrm{intra}} admits only substitutes in cc’s own script; πlang\pi_{\mathrm{lang}} also admits any script the language normally uses, taken from its CLDR exemplar characters (The Unicode Consortium, [29]); and πnone\pi_{\mathrm{none}} admits everything. Script-neutral characters (Script=Common or Inherited) pass under every policy. Because πlang\pi_{\mathrm{lang}} keeps the same-script clause, which matters for text embedded from another script such as Latin inside Japanese, the policies nest, Sintra(c)⊆Slang(c)⊆Snone(c)S_{\mathrm{intra}}(c) \subseteq S_{\mathrm{lang}}(c) \subseteq S_{\mathrm{none}}(c), and capacity can only grow from one to the next. We report πnone\pi_{\mathrm{none}} unless stated otherwise. The policies limit capacity; they do not guarantee stealth, which Appendix § 13 tests.

Capacity

Capacity is the number of hidden bits one character of real text can carry, on average. A character with kk admissible forms (itself and k−1k-1 substitutes) lets the key choose among kk options, which is log⁡2k\log_2 k bits. Averaging over the language’s character frequencies pL(c)p_L(c) gives the substitution capacity, in bits per character:

κsub(L,π)=∑cpL(c)log⁡2∣Sπ(c)∣.(1)\kappa_{\mathrm{sub}}(L,\pi)=\sum_c p_L(c)\log_2\lvert S_\pi(c)\rvert. \tag*{(1)}

Frequencies come from the raw corpus text, so spaces, punctuation, digits and characters borrowed from other scripts count at their true rate. A character with no substitute carries 0 bits, since S(c)S(c) always contains cc. Each gap between characters can hold nothing or one of the invisible characters in MLM_L, so the insertion track carries

κins(L)=gLlog⁡2(∣ML∣+1),(2)\kappa_{\mathrm{ins}}(L)=g_L\log_2(\lvert M_L\rvert+1), \tag*{(2)}

where gL≈0.99g_L \approx0.99 is the number of gaps per character. MLM_L holds ZERO WIDTH SPACE, ZERO WIDTH NON-JOINER, ZERO WIDTH JOINER and WORD JOINER, minus the two joiners in Arabic and in the Brahmic scripts that form conjuncts, where they change how letters join ([11, 28]); ∣ML∣\lvert M_L\rvert is therefore 4 or 2. Total capacity is κ=κsub+κins\kappa=\kappa_{\mathrm{sub}}+\kappa_{\mathrm{ins}}; for English at πnone\pi_{\mathrm{none}} it is 7.09 bits per character (4.78 from substitution and 2.30 from insertion, each rounded). Per-family capacities use each family’s own set and do not add up to κsub\kappa_{\mathrm{sub}}, because the logarithm of a union is not a sum of logarithms.

Transformations. We also measure how much capacity survives the transformations in Table 1. Routine ones need no adversary: Unicode normalization (NFC, NFKC) and a lossy copy-paste, which we model as NFC plus the removal of zero-width characters. Some tools keep zero-width characters, so this level is the pessimistic case for the insertion track. Deliberate ones are Unicode’s own defenses ([7]), used by someone who wants the watermark gone while keeping the text usable: mixed-script detection, applied as detect-and-strip to each token (a run of non-whitespace characters), and the skeleton mapping, which folds homoglyphs into a shared form. The full UTS #39 skeleton also deletes default-ignorable characters, which include all four insertion characters; we keep the two steps apart so that each rung mainly hits one track, and the ladder ends with the full skeleton plus compatibility and whitespace folding, removing both tracks.

TransformationOperationRemoves
Routine
identitynonenothing
nfccanonical compositionalmost nothing
copy_pasteNFC, drop zero-widthinsertion track
nfkccompatibility compositioncompat. variants
Deliberate (UTS #39)
mixed_script_hrdetect-and-strip (HR)cross-script swaps
mixed_script_ssdetect-and-strip (SS)+ embedded Latin
skeletonskeleton, keep ignorableslisted confusables
combinedcanonical foldboth tracks

Table 1. Transformation ladder ϕ\phi. Routine transformations need no adversary; deliberate transformations use Unicode’s own defenses.

Capacity

Figure 2 shows capacity by family for every language before any transformation, and Table 4 (Appendix § 11) gives the full numbers by language, policy and track. Total capacity runs from 7.09 bits per character for Swahili and English down to 2.14 for Khmer, a 3.31-fold range (Gini coefficient 0.21). The range follows script lines since the mean is 6.81 bits per character for the 11 Latin-script languages, 3.21 for Chinese, Japanese and Korean, and 2.93 for the nine South Asian languages written in Brahmic scripts. These differences are not sampling noise, the bootstrap 95% interval on English total capacity is [7.082, 7.089] (Appendix § 11).

Heatmap of untransformed capacity by language, script, and family

Figure 2. Untransformed capacity under πnone\pi_{\mathrm{none}}, in bits per character, by language (rows, grouped by script) and family (columns); the insertion column uses each language’s safe invisible characters. Family values are computed separately and do not add up to the total.

Codeπintra\pi_{\mathrm{intra}}πlang\pi_{\mathrm{lang}}πnone\pi_{\mathrm{none}}κins\kappa_{\mathrm{ins}}κ\kappa
eng_Latn4.504.504.782.307.09
spa_Latn4.464.464.752.317.06
fra_Latn4.384.384.642.316.95
deu_Latn4.394.394.632.316.94
por_Latn4.394.394.702.317.00
vie_Latn3.503.503.682.315.98
tur_Latn4.344.344.602.306.90
swh_Latn4.544.544.792.307.09
ind_Latn4.494.494.712.317.01
yor_Latn3.383.393.602.305.90
hau_Latn4.474.474.692.316.99
rus_Cyrl3.163.163.792.316.10
ukr_Cyrl3.223.223.852.306.15
srp_Cyrl3.413.413.992.306.30
kaz_Cyrl3.113.113.562.305.87
ell_Grek3.583.584.072.316.38
hye_Armn1.991.992.232.314.54
kat_Geor0.690.690.952.313.25
heb_Hebr2.422.422.642.304.94
arb_Arab3.813.833.941.575.51
pes_Arab3.913.924.061.575.63
urd_Arab4.104.114.231.575.80
cmn_Hans0.850.850.902.273.16
cmn_Hant0.750.750.782.263.04
jpn_Jpan0.620.650.672.282.95
kor_Hang1.311.321.382.293.67
hin_Deva1.011.011.471.573.05
mar_Deva0.750.751.191.572.76
ben_Beng0.830.831.531.573.10
tam_Taml1.101.101.661.573.24
tel_Telu0.900.901.371.572.94
guj_Gujr0.940.941.181.572.75
pan_Guru1.001.001.461.573.03
kan_Knda0.700.701.171.572.74
mal_Mlym0.940.941.161.572.74
tha_Thai0.480.480.762.303.07
lao_Laoo0.500.500.742.303.05
mya_Mymr0.530.530.831.582.40
khm_Khmr0.450.450.571.572.14
amh_Ethi1.001.001.072.293.37

Table 4. Undisturbed capacity in bits per character (real-text view, no attack). κ=κsub+κins\kappa= \kappa_{\mathrm{sub}} + \kappa_{\mathrm{ins}} at πnone\pi_{\mathrm{none}}, the identity column of Figure 5.

The gap comes from the substitution track. Insertion depends only on how many invisible characters a language can safely use, not on its letters which are about 2.30 bits per character, or 1.57 in Arabic and in the scripts that form conjuncts, which must leave out the two joiners (§ 2). Substitution, by contrast, runs from under one bit per character to almost five (4.78 for English), because it depends on how many homoglyphs a language’s frequent characters have.

Within substitution, compatibility variants (family ii) do most of the separating. They are worth more than three bits per character for most Latin-script languages, whose letters and digits have many fullwidth and mathematical forms, and almost nothing for the Brahmic scripts. Cross-script homoglyphs (family i) appear in every script but carry far less outside Latin, Cyrillic and Greek, three alphabets that share many letter shapes. Most conjunct-forming scripts are short on both tracks, which is why Khmer (2.14) and Myanmar (2.40) end lowest. Arabic, Persian and Urdu pay the same insertion penalty but still reach 5.5 to 5.8 bits per character, because Unicode encodes many Arabic presentation forms as compatibility variants.

Chinese, Japanese and Korean follow a different pattern. Their native characters have almost no homoglyphs, and characters outside their native character sets, such as Latin letters, digits and punctuation, carry 49 to 65% of the substitution capacity of Chinese and Japanese and all of Korean’s, against 18% for English.

These capacities are high because πnone\pi_{\mathrm{none}} counts every substitute, including many that a cautious embedder would avoid. Dropping the cross-script substitutes that a mixed-script detector would flag (πintra\pi_{\mathrm{intra}}), such as a Cyrillic a in English text, leaves Latin-script languages 93 to 95% of their substitution capacity but the Brahmic and South-East Asian ones only 54 to 81%. Keeping only substitutes that UTS #39 marks as Allowed in identifiers leaves English 1.42 of its 4.78 substitution bits.

Curation explains part of the gap, as § 2 notes, family (i) and the residual come from a hand-curated list that is sparse for CJK and Indic scripts. But family (ii), which comes from a systematic table, splits by script just as sharply. Unicode added compatibility variants largely to stay compatible with older character sets and to support mathematical styling, needs that covered Latin, Greek, Arabic and East Asian forms but hardly any Brahmic letters. The gap is therefore a property of the Unicode standard, not of our method.

Fairness

Our unit of evidence is the script, not the language. The 11 Latin-script languages are close copies of one another, so counting them as 11 independent observations would overstate the evidence; every statement about scripts therefore rests on 24 script groups. We measure how much of the variation in capacity lies between scripts with the intra-class correlation ICC(1) ([27]), which is 1 when languages that share a script have identical capacity and near 0 when script tells us nothing (Appendix § 10). Confidence intervals resample whole scripts rather than languages ([8]).

Script explains almost all of the variation. The ICC is 0.963, so the variance between scripts is about 25 times the variance within them, and none of 10,000 shuffles of languages across scripts, with group sizes fixed, reached it (p<10−4p < 10^{-4}; Figure 3). The ICC stays between 0.954 and 0.978 under each transformation in Figure 5 and between 0.963 and 0.967 across the three policies so the result does not rely on one setting.

Effective capacity under transformations for each language

Figure 5. Effective capacity under πnone\pi_{\mathrm{none}}, in bits per character with both tracks summed, for each language and transformation. The first column is the untransformed total of Table 4; the skeleton column keeps invisible characters (Table 1).

The four scripts with several languages show how little room is left inside a script. Arabic, Persian and Urdu sit in three different resource classes, yet their capacities span only 0.29 bits per character. The 11 Latin-script languages span 1.19 bits, against about 5 bits between scripts, and most of that spread comes from Vietnamese and Yoruba,

Untransformed capacity under $\pi_{\mathrm{none}}$ by script group

caption: Untransformed capacity under πnone\pi_{\mathrm{none}} by script group, ordered by group median. Bars span the languages of the four scripts that have several; open markers are scripts with one language.

which write many accented letters with few substitutes. Changing only the characters, by contrast, moves capacity, for example, Mandarin in Simplified and in Traditional characters, one language in one resource class, differs by 1.04-fold, because insertion is the same for both, and by 1.24-fold once copy-paste removes insertion.

To ask whether lower-resourced languages receive less capacity, we use the resource classes of Joshi et al. [12], from 0 (least resourced) to 5, listed in Table 3 (Appendix § 10). Only Latin script has enough languages for a direct test inside one script. Before looking at any class values, we required that test to cover at least three classes spanning at least two levels; the 11 Latin-script languages cover four classes spanning three. The rank correlation between capacity and class is ρ=+0.095\rho= +0.095 (permutation p=0.78p = 0.78), and the class means of 6.66, 7.01, 6.63 and 7.01 bits per character for classes 2 to 5 show no trend.

Across scripts, a script’s median capacity rises with its median resource class (ρ=+0.54\rho= +0.54, 95% interval [+0.14,+0.83][+0.14, +0.83] over 24 scripts; Figure 4). Low-capacity scripts, such as the Brahmic, South-East Asian and Ethiopic ones, are used mostly by languages in classes 1 to 3, while Latin, Cyrillic

and Arabic hold most of the languages in classes 4 and 5. Chinese, Japanese and Korean, in classes 4 and 5 but with little capacity, are the main exception, which keeps the correlation moderate. Table 2 collects these results.

QuantityValueEvidence base
ICC, untransformed0.963, p<10−4p < 10^{-4}24 scripts
ICC, per transformation0.954–0.97824 scripts
Mandarin, two orthographies1.04× (copy-paste 1.24×)1 contrast
Within Latin, ρ\rho vs. class+0.095, p=0.78p = 0.7811 languages
Script level, ρ\rho vs. class+0.544 [+0.135, +0.826]24 scripts
Language level, ρ\rho vs. class+0.487 [+0.185, +0.740]40 languages

Table 2. Fairness results. ρ\rho is the rank correlation between capacity and resource class. Between-script rows count script groups as the evidence; intervals are 95% bootstraps that resample whole scripts, and the language-level row clusters its 40 languages by script.

Untransformed capacity under $\pi_{\mathrm{none}}$ against the resource class of Joshi et al. (2020)

caption: Untransformed capacity under πnone\pi_{\mathrm{none}} against the resource class of Joshi et al. [12]. Horizontal offsets within a class only separate the points.

Taken together, script organizes the gap, and resource level lines up with script. At the script level the two cannot be separated, because each script contributes a single point. We therefore do not claim that resource level is irrelevant, only that its link to capacity appears between scripts and not within them. This answers the question from § 1 since without a tokenizer the gap remains, but its source is Unicode’s uneven supply of homoglyphs rather than tokenization.

Robustness

A transformation can turn two different substitutes into the same character and the key can then no longer tell them apart. Effective capacity therefore counts only the distinct results that survive. For a transformation ϕ\phi from Table 1, applied character by character,

κsubeff(L,π,ϕ)=∑cpL(c)log⁡2∣{ϕ(c′):c′∈Sπ(c)}∣,(3)\kappa_{\mathrm{sub}}^{\mathrm{eff}}(L,\pi,\phi) = \sum_{c} p_L(c)\log_2 \left|\left\{\phi(c') : c' \in S_{\pi}(c)\right\}\right|, \tag*{(3)}

and likewise κinseff=gLlog⁡2∣{ϕ(m):m∈ML}∪{∅}∣\kappa_{\mathrm{ins}}^{\mathrm{eff}} = g_L \log_2 \left|\{\phi(m):m \in M_L\} \cup\{\varnothing\}\right|, where a deleted invisible character counts as no insertion. Survival s=κeff/κs = \kappa^{\mathrm{eff}}/\kappa is the share that remains. Figure 5 gives κeff\kappa^{\mathrm{eff}} for every language and transformation under πnone\pi_{\mathrm{none}}, and Table 5 (Appendix § 12) gives ss for each track.

Code\multicolumn{6}{c}{Substitution track, ssubs_{\mathrm{sub}}}\multicolumn{6}{c}{Both tracks, stotals_{\mathrm{total}}}
NFCC-PNFKCMS-HRMS-SSSkel.NFCC-PNFKCMS-HRMS-SSSkel.
eng_Latn1.001.000.590.940.940.471.000.670.720.960.960.64
spa_Latn1.001.000.610.940.940.471.000.670.740.960.960.64
fra_Latn1.001.000.600.950.950.471.000.660.730.960.960.65
deu_Latn1.001.000.580.950.950.471.000.660.720.970.970.65
por_Latn1.001.000.610.940.940.471.000.670.740.960.960.64
vie_Latn0.990.990.580.960.960.491.000.610.740.970.970.69
tur_Latn1.001.000.590.950.950.441.000.660.730.960.960.63
swh_Latn1.001.000.580.950.950.471.000.670.710.970.970.64
ind_Latn1.001.000.550.960.960.481.000.670.700.970.970.65
yor_Latn0.990.990.610.940.940.480.990.600.760.960.960.68
hau_Latn0.990.990.570.960.960.491.000.670.710.970.970.66
rus_Cyrl1.001.000.770.840.840.421.000.620.860.900.900.64
ukr_Cyrl1.001.000.770.840.840.411.000.620.860.900.900.63
srp_Cyrl1.001.000.750.860.860.411.000.630.840.910.910.63
kaz_Cyrl1.001.000.750.870.870.431.000.600.850.920.920.65
ell_Grek1.001.000.680.880.880.161.000.640.800.920.920.46
hye_Armn0.990.990.680.890.890.191.000.490.840.950.950.60
kat_Geor0.990.990.770.730.730.471.000.290.930.920.920.84
heb_Hebr0.990.990.680.910.910.210.990.530.830.950.950.58
arb_Arab0.990.990.470.970.970.131.000.710.620.980.980.38
pes_Arab0.990.990.510.970.970.151.000.720.640.980.980.39
urd_Arab0.990.990.530.970.970.151.000.720.660.980.980.38
cmn_Hans0.890.890.490.970.840.460.970.250.860.990.960.85
cmn_Hant0.830.830.410.980.930.460.960.210.851.000.980.86
jpn_Jpan0.920.920.360.970.780.600.980.210.860.990.950.91
kor_Hang0.980.980.720.960.890.590.990.370.900.990.960.85
hin_Deva0.980.980.820.660.660.420.990.480.910.840.840.72
mar_Deva0.990.990.830.610.610.380.990.430.930.830.830.73
ben_Beng0.990.990.870.530.530.300.990.490.930.770.770.66
tam_Taml0.990.990.910.530.530.221.000.510.950.760.760.60
tel_Telu0.990.990.820.660.660.320.990.460.920.840.840.69
guj_Gujr0.980.980.800.770.770.450.990.420.920.900.900.76
pan_Guru0.980.980.820.660.660.410.990.470.910.840.840.72
kan_Knda0.990.990.840.610.610.331.000.420.930.830.830.72
mal_Mlym0.990.990.830.810.810.281.000.420.930.920.920.70
tha_Thai1.001.000.900.640.630.181.000.250.980.910.910.79
lao_Laoo0.990.990.770.660.660.371.000.240.940.920.920.85
mya_Mymr0.990.990.810.610.610.241.000.340.930.870.870.74
khm_Khmr0.990.990.780.730.730.351.000.260.940.930.930.83
amh_Ethi0.980.980.740.950.950.560.990.310.920.980.980.86

Table 5. Survival s=κeff/κs = \kappa^{\mathrm{eff}}/\kappa by language and attack, policy πnone\pi_{\mathrm{none}}. C-P: copy-paste re-encoding; MS-HR and MS-SS: the mixed-script detector at the Highly Restrictive and Single Script levels; Skel.: skeleton mapping, which keeps invisible characters (Table 1). Insertion survival is not tabulated because it is constant: 1.00 for every language under every attack shown except copy-paste, where it is 0.00 for every language.

The two tracks fail under different transformations. Copy-paste deletes every inserted character, so the insertion track falls to zero in all 40 languages, while visible substitutes pass through. The skeleton mapping folds cross-script and residual homoglyphs into one shared form but keeps invisible characters (§ 2). On average, total capacity keeps 0.99 of its value under NFC, 0.93 under mixed-script detection, 0.83 under NFKC, 0.68 under skeleton mapping and 0.51 under copy-paste, but which one hurts most depends on the language. The combined attack of Table 1 erases the mark in every language (§ 6).

NFKC is usually assumed to neutralize homoglyphs, but it folds only compatibility variants (Whistler, [30]). It removes 96% of compatibility pairs yet keeps 93% of cross-script pairs, since a Cyrillic a is a separate letter, not a variant of the Latin one. Languages lose in proportion to how much of their capacity came from compatibility variants (Pearson r=−0.84r = -0.84): Chinese, Japanese and the Arabic-script languages keep 0.36 to 0.53 of their substitution capacity, the Latin-script languages 0.55 to 0.61, and the Brahmic and South-East Asian scripts, with few such variants to lose, 0.77 to 0.91.

Mixed-script detection is a defense aimed at homoglyphs which might be expected to hurt homoglyph-rich Latin text most, yet at the Highly Restrictive level it keeps 0.94 to 0.98 of the substitution capacity of Latin, Arabic and CJK text but only about 0.65 for the Brahmic and South-East Asian scripts. Brahmic homoglyphs are mostly twins from sibling scripts, such as Bengali and Tirhuta, and swapping in a sibling-script letter is exactly the mixing the detector strips.

The disparity across all 40 languages can be compared even though no single robustness ranking fits all. NFKC and skeleton mapping narrow the 3.31-fold range to 2.63 and 2.59, because they remove what made Latin and Arabic rich; mixed-script detection widens it slightly, to 3.45. Copy-paste widens it most, to 8.49 (Gini 0.34), because insertion is most of what substitution-poor languages have and removing it costs Khmer 74% of its capacity but Swahili only 33%.

Stealth. Appendix § 13 checks three ways a substitute can be noticed: a mixed-script detector, Unicode’s identifier status, and the rendered glyph, which depends on the font (Figure 8). As with capacity, the cost of staying hidden follows script, for instance, Tamil pays through Unicode’s script assignments, Bengali through a sibling script that readers’ fonts lack, and Arabic through how one shaping engine treats invisible characters.

Perceptual safety across fonts and cross-script letter twins

Figure 8. Perceptual safety is font-relative. A genuine cross-script letter twin is a pair with the same Unicode letter name and case in two of Latin, Cyrillic and Greek; there are 21, each counted in both directions (42 in all), and each row is a panel font that contains at least one. Left: mean normalized ink difference between the two glyphs of a twin, over all twins (bar) and by script pair (dots). Right: the fraction of the font’s renderable twins that render bit-identically. Glyphs are rendered in isolation, in the same font, at 48 pixels.

Keyed Validation

Bits per character are an accounting quantity until a detector caches them. We therefore build a keyed instrument on the measured channel and ask how much text a detector needs. The instrument per-slot alphabets are exactly the admissible sets of § 2. A PRNG seeded by the key draws one symbol for each substitution slot and each insertion gap. The detector holds the key and the cover (§ 2), rebuilds the schedule from the received text, and counts matching slots, much as generation-time detectors count green-list tokens [13]. It thresholds the count’s z-score at the 99th percentile of null trials; Appendix E describes the canonical keying and the test.

Sweep. For each language we score spans of 1 to 256 code points under clean text, copy-paste, the mixed-script detector and NFKC, at policy πlang\pi_{\mathrm{lang}}; a span with no usable slot counts as zero evidence rather than being skipped. nmin⁡n_{\min} is the shortest span reaching a true-positive rate of 95% at a false-positive rate of 1% (Figure 6 and Figure 7; Table 7). Here κeff\kappa^{\mathrm{eff}} is evaluated at πlang\pi_{\mathrm{lang}} and at the gap rate of a span of length nmin⁡n_{\min}, because that is what the detector sees; for English it is 5.63 bits per character rather than the 7.09 of §4.

Detection AUC against span length for eight languages under four attacks

Figure 6. Detection AUC against span length for eight languages across the capacity range, under four attacks.

Measured $n_{\min}$ against $\kappa^{\mathrm{eff}}$ for four attacks

Figure 7. Measured nmin⁡n_{\min} (TPR 95% at FPR 1%) against κeff\kappa^{\mathrm{eff}} at that length, one point per language. Lines are k/κeffk/\kappa^{\mathrm{eff}} with kk fitted once per attack (policy πlang\pi_{\mathrm{lang}}).

CodeCleanCopy-pasteMixed-script (HR)NFKC
eng_Latn1.95 [1.93, 1.96]1.96 [1.94, 1.98]1.95 [1.93, 1.96]4.56 [3.94, 4.98]
spa_Latn1.90 [1.87, 1.93]2.05 [1.88, 2.93]1.90 [1.87, 1.93]3.97 [3.94, 4.47]
fra_Latn1.93 [1.91, 1.94]1.94 [1.92, 2.89]1.93 [1.91, 1.94]3.94 [2.94, 3.97]
deu_Latn1.92 [1.91, 1.93]1.93 [1.92, 1.97]1.92 [1.91, 1.93]2.96 [2.94, 3.48]
por_Latn1.95 [1.88, 1.98]1.96 [1.89, 2.00]1.95 [1.88, 1.98]3.95 [3.90, 3.97]
vie_Latn1.96 [1.90, 2.76]2.80 [1.98, 2.96]1.96 [1.90, 2.76]3.85 [2.95, 3.95]
tur_Latn1.98 [1.94, 2.91]2.35 [1.98, 3.46]1.98 [1.94, 2.91]10.57 [2.99, 12.82]
swh_Latn1.87 [1.83, 1.92]1.88 [1.84, 1.94]1.87 [1.83, 1.92]3.78 [2.95, 3.90]
ind_Latn1.85 [1.00, 1.87]1.85 [1.00, 1.88]1.85 [1.00, 1.87]2.93 [2.91, 3.93]
yor_Latn2.42 [1.96, 2.56]4.32 [3.90, 4.73]2.56 [2.00, 2.66]3.94 [3.86, 3.96]
hau_Latn1.95 [1.93, 1.98]1.96 [1.93, 2.06]1.95 [1.93, 1.98]2.95 [2.94, 3.95]
rus_Cyrl1.87 [1.86, 2.70]3.70 [3.50, 4.66]1.87 [1.86, 2.70]2.35 [2.00, 2.87]
ukr_Cyrl2.42 [1.99, 2.82]3.05 [2.94, 3.59]2.45 [1.99, 2.82]2.84 [2.71, 3.00]
srp_Cyrl1.91 [1.88, 2.10]2.98 [2.88, 4.04]1.91 [1.88, 2.10]2.61 [2.00, 2.77]
kaz_Cyrl2.94 [2.83, 3.51]3.87 [3.67, 5.76]2.94 [2.83, 3.46]2.93 [2.83, 3.35]
ell_Grek1.90 [1.88, 2.15]2.50 [2.29, 2.90]1.90 [1.88, 2.15]3.81 [3.63, 3.95]
hye_Armn2.73 [2.67, 2.79]5.60 [4.23, 5.93]2.74 [2.68, 2.80]2.87 [2.85, 3.74]
kat_Geor3.93 [2.92, 3.95]22.40 [20.73, 23.88]3.93 [2.92, 3.95]3.96 [3.87, 4.95]
heb_Hebr2.72 [2.61, 2.88]4.66 [3.87, 5.21]2.83 [2.78, 3.10]3.92 [3.86, 4.93]
arb_Arab1.90 [1.87, 1.94]1.96 [1.91, 2.38]1.92 [1.87, 1.95]3.86 [3.80, 4.05]
pes_Arab1.85 [1.84, 1.87]1.85 [1.84, 2.32]1.85 [1.84, 1.87]4.67 [3.87, 4.87]
urd_Arab1.92 [1.92, 1.93]2.67 [2.42, 2.83]1.92 [1.92, 1.93]3.70 [3.57, 3.77]
cmn_Hans3.89 [3.74, 3.91]43.95 [32.82, 45.16]3.89 [3.74, 3.91]3.94 [3.94, 4.69]
cmn_Hant3.76 [2.91, 3.89]31.65 [28.82, 38.63]3.76 [2.91, 3.90]3.94 [3.94, 4.67]
jpn_Jpan3.91 [2.93, 3.94]36.99 [30.95, 41.34]3.91 [2.93, 3.94]3.95 [3.94, 3.96]
kor_Hang3.83 [2.90, 3.88]7.99 [7.89, 11.52]3.84 [2.90, 3.89]3.96 [3.93, 3.97]
hin_Deva3.86 [3.84, 5.66]11.77 [11.26, 15.25]3.86 [3.84, 4.80]4.92 [4.71, 5.70]
mar_Deva4.88 [4.85, 4.91]15.82 [14.83, 15.96]4.90 [4.87, 5.88]4.97 [4.93, 5.96]
ben_Beng4.82 [3.89, 5.67]15.42 [14.61, 18.48]4.84 [3.89, 5.76]5.71 [4.92, 5.91]
tam_Taml3.78 [3.59, 3.97]11.33 [10.67, 13.57]3.87 [3.68, 4.68]3.86 [3.67, 4.14]
tel_Telu4.91 [4.88, 4.95]12.75 [11.87, 20.41]4.91 [4.88, 4.95]4.95 [4.90, 5.93]
guj_Gujr4.73 [3.83, 4.77]14.26 [11.49, 15.53]4.76 [3.85, 4.79]4.92 [4.67, 4.93]
pan_Guru3.87 [3.86, 4.79]13.03 [11.84, 13.93]3.88 [3.87, 4.84]4.93 [3.93, 4.94]
kan_Knda4.91 [3.91, 4.94]19.69 [15.62, 23.08]4.91 [3.91, 4.93]4.92 [3.95, 5.88]
mal_Mlym4.82 [3.88, 4.85]12.37 [11.54, 14.74]4.82 [3.88, 4.85]4.89 [4.60, 4.91]
tha_Thai3.92 [3.92, 3.94]43.01 [37.08, 45.12]3.92 [3.92, 3.94]3.93 [3.92, 3.94]
lao_Laoo3.94 [3.93, 4.94]92.96 [84.16, 141.66]3.94 [3.93, 4.94]3.95 [3.94, 4.96]
mya_Mymr4.92 [4.91, 5.89]29.78 [22.74, 32.82]4.92 [4.91, 5.89]4.92 [4.91, 5.90]
khm_Khmr4.95 [4.92, 8.00]61.74 [57.26, 80.69]4.96 [4.93, 8.50]4.96 [4.93, 8.50]
amh_Ethi3.85 [2.90, 3.87]12.74 [11.86, 13.61]3.85 [2.90, 3.87]3.94 [3.90, 4.90]

Table 7. Detection length nmin⁡n_{\min} in code points, the shortest span that reaches a true-positive rate of 95% at a false-positive rate of 1%, with 95% bootstrap intervals. Policy πlang\pi_{\mathrm{lang}}, canonical keying.

Capacity predicts detection length. On clean text nmin⁡n_{\min} runs from 1.85 code points (Persian) to 4.95 (Khmer). A single constant reproduces this as nmin⁡≈k/κeffn_{\min} \approx k/\kappa^{\mathrm{eff}} with k=10.02k = 10.02 bits, the median of nmin⁡κeffn_{\min}\kappa^{\mathrm{eff}} on clean text, and the measured-to-predicted ratio runs from 0.79 to 1.36. The rank correlation between nmin⁡n_{\min} and 1/κeff1/\kappa^{\mathrm{eff}} is 0.877 clean, 0.969 under copy-paste and 0.863 under the mixed-script detector; under NFKC it is 0.552, with Turkish an outlier at 10.57 code points (interval 2.99 to 12.82). Under copy-paste the nmin⁡n_{\min} range opens to 50×, English and Persian still need about 2 code points, but Lao needs 92.96.

Floor. The combined attack of Table 1 is a true floor with detection at chance for all 40 languages (largest AUC 0.589).

Conclusion

Homoglyph and invisible-character watermarks need no tokenizer, yet they do not escape the re-source gap of generation-time watermarks. Across 40 language varieties in 24 scripts, script accounts for 96% of the variation in capacity and for over 95% under every attack but the floor. Resource level makes no detectable difference within Latin script, but lower-resourced languages tend to use scripts for which Unicode lists few homoglyphs, hence less capacity. Capacity predicts the text a detector needs for a copy-paste that strips invisible characters widens the gap from 3.3-fold to 8.5-fold; the detector then needs about 2 code points of English or Persian but 93 of Lao. An adversary can remove the watermark in any language; our concern is unequal protection under routine handling. Narrowing the gap would require more complete confusables data for under-served scripts, or a post-hoc channel independent of script-specific inventories.

Limitations

Design. Twenty of the 24 script groups hold a single language, so within-script variance is estimated from four groups and 16 residual degrees of freedom. The ICC is high partly because most groups cannot contribute within-group variance at all; the permutation test preserves group sizes and accounts for this, but the limitation remains. At the script level, each script contributes one median, so script identity and resource class cannot be separated there, and the positive script-level association cannot be attributed to either. The only within-script test (Latin, n=11n = 11, four classes) meets the coverage rule we fixed before examining any class values (§ 4), but at that size a null result can only bound an effect, not rule one out.

Resource taxonomy. We use one published classification (Joshi et al., [12]), matched to our languages by name. Seven of the 40 matches are judgment calls, and one exact match, Serbian, also has a plausible lower-class alternative (Appendix § 10). A different taxonomy or matching could change the resource classes, but not the grouping by script.

Corpus. We use one parallel corpus in one register. Parallel text makes the languages directly comparable but limits how far the results generalize. As a check, recomputing on the FLORES+ dev split changes substitution capacity by 0.012 bits per character on average and by at most 0.060.

Snapshot. All capacities are computed from Unicode 17.0.0, CLDR 48.2.0 and FLORES+ 4.5. The confusables list grows between versions, so every capacity here is specific to these versions. Python’s normalization library uses older Unicode data (version 14.0); 158 of the 8,275 code points in our substitution sets are newer than that, including Cyrillic modifier letters from Unicode 15 that substitute for frequent Cyrillic letters. The library’s NFKC cannot fold these newer variants, which is why it removes only 96% of compatibility pairs (§ 5) and leaves the four Cyrillic-script languages more NFKC capacity than Unicode 17.0 normalization would. We keep the library for the NFKC attack, so that its results match what the library does in practice, and use the Unicode 17.0.0 decompositions for the combined floor attack.

Renderer dependence. Every perceptual number depends on the fonts used. When a font stack draws a substitute with a different font than the original character, the measured distance reflects the stack’s fixed fallback order rather than any one device. Whether an invisible character stays invisible depends on the shaping engine, most sharply for Arabic (1.57 bits per character under OpenType and Apple’s CoreText, 0 when HarfBuzz shapes Apple’s AAT fonts), and 178 of the 320 combinations of language, engine and character are not measured: 32 are set by rule and 146 are assumed safe without a test. The 0.02 threshold for eye-invisible substitutes is a choice, and results are stable only over a narrow range around it, so Table 6 also reports bit-identical substitutes, which need no threshold. No human perceptual study was run; all perceptual claims are computational.

CodeDetector: tokens flagged (%) loudDetector: tokens flagged (%) quietDetector: tokens flagged (%) loud, πintra\pi_{\mathrm{intra}}Identifier restrictedDistance typicalDistance nearestCoverage renderedEye-invisible share ≤0.02\le0.02Eye-invisible share identical
eng_Latn94.70.52.20.700.620.140.750.160.15
spa_Latn79.30.40.70.690.610.110.750.160.15
fra_Latn81.40.46.00.710.610.130.750.150.14
deu_Latn94.40.62.30.720.620.140.730.150.14
por_Latn82.00.51.40.700.600.110.750.170.15
vie_Latn93.60.51.20.760.580.140.750.220.21
tur_Latn97.00.54.50.700.620.150.740.140.12
swh_Latn90.20.61.50.710.630.140.730.160.14
ind_Latn98.40.72.10.730.630.160.720.140.12
yor_Latn87.20.311.90.740.620.200.760.190.17
hau_Latn92.10.52.70.740.620.150.730.150.14
rus_Cyrl80.50.52.90.720.420.120.680.080.07
ukr_Cyrl80.10.63.10.720.430.120.680.110.07
srp_Cyrl71.80.81.70.720.420.100.680.100.09
kaz_Cyrl90.00.52.40.740.430.120.640.110.08
ell_Grek83.20.61.00.670.440.120.690.170.15
hye_Armn84.70.59.00.730.490.210.690.110.11
kat_Geor74.30.82.90.710.610.400.920.250.24
heb_Hebr76.30.646.30.710.630.440.880.120.12
arb_Arab59.50.55.80.780.520.030.790.290.29
pes_Arab64.60.41.30.760.510.020.800.300.30
urd_Arab56.10.35.90.730.530.020.830.310.31
cmn_Hans36.45.818.60.830.420.180.840.260.25
cmn_Hant25.04.49.90.860.380.190.850.290.28
jpn_Jpan63.70.313.90.850.640.460.900.120.11
kor_Hang18.00.13.70.920.500.120.880.330.33
hin_Deva76.20.420.20.720.410.180.900.260.26
mar_Deva87.60.317.40.660.400.210.900.230.23
ben_Beng81.20.414.30.870.380.160.580.190.19
tam_Taml93.90.580.10.570.470.380.940.190.18
tel_Telu92.90.73.10.560.580.450.910.190.18
guj_Gujr62.30.416.80.740.470.190.910.270.27
pan_Guru82.10.418.80.720.440.230.900.260.26
kan_Knda93.70.72.30.540.570.460.930.200.20
mal_Mlym88.90.63.20.560.560.360.900.160.16
tha_Thai87.22.75.50.270.510.460.940.080.08
lao_Laoo79.82.120.10.560.650.470.860.150.14
mya_Mymr88.00.816.30.560.570.380.860.150.15
khm_Khmr84.82.539.50.590.630.450.870.170.17
amh_Ethi23.20.52.30.920.470.140.880.360.36

Table 6. Stealth per language, πnone\pi_{\mathrm{none}} unless marked. Detector: % of tokens flagged at the Highly Restrictive level, substituting everywhere (loud) or skipping token-breaking substitutes (quiet). Identifier: share of substitution capacity in Restricted substitutes. On the typical-reader font stack: Distance, mean normalized ink difference to a typical and the nearest substitute (0 = identical); Coverage, share of substitution capacity whose substitutes render; Eye-invisible, share in substitutes within distance 0.02 of the original, and in bit-identical ones. Both eye-invisible shares come mostly from substitutes for the space character, and the check counts CARRIAGE RETURN, a line break in plain text, as identical to a space.

Channel scope and detector. We exclude the 1,207 code points that are confusable only with multi-character sequences, since they cannot be swapped one for one, and we leave variation selectors out of the capacity totals. The token-level detector is the weakest reasonable one; the per-pair bound differs from it by at most 0.03 bits per character. The keyed detector is cover-informed, so it is an upper bound on a detector without the original text; within that setting, its threshold is set empirically from null trials, and Appendix § 14 reports its false-positive rate on fresh keys. In 22 languages, the text already contains some of the invisible characters we insert, so some insertions cannot be told apart from native ones; this matters only for Khmer.

Ethical Considerations

Homoglyph and zero-width text manipulation is publicly known and documented in the security literature and in Unicode’s own standards (Boucher et al., [3]; Boucher and Anderson, [2]; Davis and Suignard, [7]); our substitution sets are derived from published Unicode data files. This paper contributes a measurement framework and a validation instrument, not a watermarking product or an attack tool: the keyed instrument uses only the character sets defined in § 2, so it carries exactly the capacity we measure and nothing more. The harm we document is unequal protection rather than a new exploit. Text in low-capacity scripts is harder to mark and harder to verify, so provenance tools built on this channel silently protect some language communities less than others; documenting that gap is the point of this paper. The transformation ladder and our detector models are directly useful to those building normalization and detection defenses, and the per-language numbers show deployers where coverage is thin. We use only public, licensed data: the FLORES+ corpus (CC BY-SA 4.0) and public Unicode and CLDR files. No personal data, user content or human subjects are involved; the objects of study are scripts and code points, not communities. At the corpus provider’s request, we do not redistribute raw corpus sentences; every released artifact is a derived statistic that can be rebuilt exactly from fixed data versions, and we document how the resource classes were matched. Nothing here recommends deploying this channel or certifies any configuration as safe.

Languages and resource classes

Table 3 lists the 40 varieties of §3. The code is the FLORES+ identifier; it labels the rows of Figure 2 and Figure 5 and of every table below, and its suffix is the script, which gives the 24 script groups of §5. The last column is the resource class used there (Joshi et al., [12]). Classes are joined by language name: 33 are exact matches and seven, marked †, are judgement calls. Two drop a regional qualifier (Spanish, Portuguese); three match across a naming difference (Modern Standard Arabic to Arabic, Western Persian to Persian, Eastern Panjabi to Eastern Punjabi); and both Mandarin varieties take the single Mandarin row, so that pair shares a class by construction. Serbian takes its exact row (class 4), although Serbo-Croatian rows of lower class also exist.

Fairness analysis. We use the one-way random-effects intraclass correlation for unbalanced groups Shrout and Fleiss, [27],

ICC⁡(1)=MSB−MSWMSB+(k0−1)MSW,\operatorname{ICC}(1) = \frac{\mathrm{MSB} - \mathrm{MSW}}{\mathrm{MSB} + (k_0 - 1)\mathrm{MSW}},

where MSB and MSW are the between- and within-script mean squares and k0k_0 is the adjusted mean group size. Permutation tests shuffle the language-to-script labels while keeping group sizes; seeds are listed in Appendix § 15. The variance between scripts is 3.31 and the variance within them 0.13, a ratio of about 25. The ICC stays high under every transformation because each transformation removes particular kinds of homoglyph, and languages that share a script share those kinds, so their capacities rise and fall together.

Descriptive covariates. Three script-level quantities, read from the frozen Unicode tables and never from the corpus or the substitution sets, describe the scripts further. We exclude any quantity built from the substitution sets, since explaining capacity with the same confusables data that defines it would be circular; these covariates therefore describe the picture rather than confirm the mechanism. Against script-median capacity over 24 scripts, script size (code points assigned) has Spearman +0.35 [+0.01, +0.63]; the five bicameral scripts, which have upper and lower case, exceed the rest by 3.08 bits per character in median [+0.21, +3.95]; and combining-mark density against substitution capacity is −0.06 [−0.48, +0.39]. The three are correlated with one another (script size against mark density, −0.56), so they should be read as one picture rather than three findings.

Substitution sets and capacity by language

Over the frozen snapshot, the substitution sets contain 2,245 skeleton classes and 36,066 cross-script, 2,645 compatibility, 600 whitespace and 28,145 residual substitution pairs. The 1,207 code points that are confusable only with multi-character sequences cannot be swapped one for one and are excluded.

Besides the real-text capacity of §3, we report an alphabet view: the plain mean of log⁡2∣Sπ(c)∣\log_2 |S_{\pi}(c)| over a language’s native exemplar characters. It asks how rich the writing system is rather than how rich the running text is. The two views differ: English carries 4.78 bits per character of substitution capacity in real text but 2.09 over its alphabet, because frequent characters such as the space and the common vowels have many substitutes. Table 4 gives the numbers behind §4: substitution capacity under each admissibility policy of § 2, insertion capacity, and their total. The policies nest, so κsub\kappa_{\rm sub} never decreases from left to right. Insertion capacity does not depend on the policy; it is about 2.3 bits per character where all four invisible characters are safe and about 1.57 where the two joiners are dropped.

Stability. The bootstrap 95% interval on English total capacity is [7.082, 7.089]. Recomputing every language on the FLORES+ dev split (997 sentences) moves substitution capacity by 0.012 bits per character on average and by at most 0.060, for Yoruba.

Characters outside the native set. Characters outside each language’s native character set carry 65% of the substitution capacity of Simplified Chinese, 55% of Traditional Chinese, 49% of Japanese and all of Korean’s, against 18% for English. Foreign letters alone, such as Latin words inside Chinese text, carry 27% of the substitution capacity of Simplified Chinese, 7% of Japanese and 10% of Korean, against none for English.

Cross-script twins. Cross-script letter twins, such as a Cyrillic a in English text, form a channel worth 1.99 bits per character on its own for English and 2.12 for Russian. Removing them under πintra\pi_{\rm intra} lowers substitution capacity only from 4.78 to 4.50 bits per character for English and from 3.79 to 3.16 for Russian (Table 4), because per-family capacities do not add (§ 2): English letters keep their compatibility variants, which πintra\pi_{\rm intra} admits.

Survival under attack

Table 5 gives the survival s=κeff/κs = \kappa^{\rm eff}/\kappa of § 5 under each attack of Figure 5, for the substitution track and for the two tracks combined. The insertion track needs no column: it survives each of them intact except copy-paste, which removes it for all 40 languages. The table also carries the stricter Single Script level of the mixed-script detector, which the main text does not discuss; its substitution survival differs from the Highly Restrictive level by more than 0.01 only for Chinese, Japanese and Korean.

Each transformation removes a different kind of substitute, so each hits a different group hardest. In substitution survival, NFKC hits Chinese, Japanese and the Arabic-script languages hardest (0.36 to 0.53), mixed-script detection the Brahmic and South-East Asian scripts (0.53 to 0.81), and skeleton mapping the Arabic-script languages, Greek, Thai, Armenian and Hebrew (0.13 to 0.21), while the Latin-script languages keep 0.44 to 0.49 under skeleton mapping.

Mixed-script detection permits Latin combined with Han, kana or Hangul at the Highly Restrictive level, so the characters outside the native set that carry much of CJK capacity (§4) pass untouched. Under πintra\pi_{\rm intra}, which admits no other-script substitutes, the detector finds almost nothing to remove (mean survival 0.98, against 0.84 under πnone\pi_{\rm none}); the exception is Tamil (0.79), for the reason given in Appendix § 13.

Skeleton mapping splits the languages by their native script. Twenty of the 40 lose all substitution capacity from their native characters: 15 write in scripts with no compatibility variants, and in Arabic, Persian, Urdu, Hebrew and Korean the compatibility variants are themselves listed as confusables and fold too. The other 20 keep the compatibility and whitespace variants that the mapping does not merge. Every language still keeps some capacity from embedded Latin text, and all of its insertion track. The full skeleton, which also deletes default-ignorables (§ 2), removes that track too: survival then averages 0.19, below every other single transformation in all 40 languages, and the range between languages widens to 17.0×, with Thai lowest.

Stealth

This appendix gives the stealth analysis summarized in § 5. Capacity counts every substitute, but a substitute only helps if no one notices it. We check three ways it can be noticed: a mixed-script detector, which reads script labels and never a font; Unicode’s identifier status, which marks some characters as Restricted ([7]); and the reader’s eye, which sees glyphs that depend on the installed fonts. We keep the checks apart because they disagree. A cross-script twin (family i) can look identical to the original yet trip the detector, since it carries a foreign script label; a compatibility variant (family ii) never trips the detector, but is often drawn wider or styled. A language can look stealthy under one check and loud under another, and its rank among the 40 changes with the check, so no single stealth score can describe it. Table 6 reports all three checks per language; its eye-invisible share is given both at a 0.02 threshold and for bit-identical substitutes, so that no conclusion rests on the threshold. Against the detector, the embedder’s choices matter most. Substituting at every possible position under πnone\pi_{\mathrm{none}} gets 94.7% of English tokens flagged at the Highly Restrictive level, against none for unmarked text, because almost every word ends up mixing scripts; skipping substitutes that would break a token into two scripts brings this down to 0.5%. The strict policy alone is not enough. Under πintra\pi_{\mathrm{intra}}, substituting everywhere still gets 0.7% to 80.1% of tokens flagged, worst for Tamil: the virama sign, 14.4% of Tamil characters, has an Inherited-script homoglyph that πintra\pi_{\mathrm{intra}} admits but whose Script_Extensions do not list Tamil, so any word containing it stops being single-script. Scripts that lean on combining marks are exposed in this way through Unicode’s script assignments, not through anything about the languages. The exposure is avoidable: skipping token-breaking substitutes keeps every language within 0.001 of unmarked text under πintra\pi_{\mathrm{intra}}.

Identifier status cuts the other way. Most of English’s substitution capacity already sits in substitutes that UTS #39 marks as Restricted (§4), and the stealthier policies make this worse: dropping cross-script substitutes removes the Allowed foreign letters and keeps the Restricted compatibility variants. An embedder that avoids the mixed-script detector therefore puts more of its bits into exactly the characters an identifier filter would flag.

To a reader, homoglyphs are less identical than their name suggests. We render each character and its substitute and measure how much their ink differs, from 0 for identical outlines; whitespace, which has no ink, is compared by width. On a stack that models a typical reader, Noto fonts for the scripts UAX #31 recommends ([6]) plus CJK, the mean difference is between 0.60 and 0.65 for every family, because many substitutes are drawn in another typeface or style. Picking the closest substitute for each character helps a great deal, cutting English from 0.62 to 0.14 and Arabic from 0.52 to 0.03. These distances compare

glyphs drawn in isolation, which flatters Arabic: its nearest substitutes are mostly isolated presentation forms, which match a letter drawn alone but not one joined inside a word. Eye-invisible substitutes, within 0.02 of the original, are rare and uneven: on a stack with full Noto coverage they carry 13.2% of the cross-script channel for English and 2.8% for Russian, but 0.2% for Bengali and Arabic, because fonts reuse one drawing for two characters mainly across Latin, Cyrillic and Greek. Even this depends on the font: among the three fonts in Figure 8 that render every twin, between 19% and 52% are drawn identically, so “visually identical” only means something for a named font.

A substitute the reader’s fonts cannot draw appears as an empty box, so it is not usable capacity either. On the typical-reader stack, coverage keeps up to 0.94 of substitution capacity for the best-covered languages, but 0.75 for English and only 0.58 for Bengali. What is lost differs by script. Bengali loses mainly one sibling script, Tirhuta, which the stack lacks because UAX #31 does not recommend it; Tirhuta alone accounts for 0.57 bits per character, 89% of Bengali’s loss. English loses mainly styled symbols from the Common script, 92% of its loss.

The insertion track faces a similar test. Beyond the joiners already excluded (§ 2), whether an invisible character stays invisible depends on the software that shapes the text. ZERO WIDTH SPACE and WORD JOINER leave an Arabic word joined under OpenType shaping and in Apple’s own CoreText engine, but break the join in every Apple AAT font we tested when the fonts are shaped by HarfBuzz, which our renderer (Pillow with libraqm) uses. Arabic insertion capacity is therefore 1.57 bits per character under OpenType and CoreText, and 0 under HarfBuzz’s AAT shaping. As with capacity, the cost of staying hidden follows script: Tamil pays through Unicode’s script assignments, Bengali through a sibling script that readers’ fonts lack, and Arabic through how one shaping engine treats invisible characters.

Keyed Instrument and Detection Length

The embedder walks the text, visiting at each position ii a substitution slot with alphabet Sπ(ci)S_{\pi}(c_i) and then the insertion gap after ii with alphabet ML∪{∅}M_L \cup\{\varnothing\}; index 0 is the identity option on both tracks. A PRNG seeded by the key (HMAC-SHA256 with rejection sampling, so that every symbol is exactly equiprobable) and labelled by the language, policy, track, position and the five preceding cover characters draws one symbol per slot. Content-keying the label matters: a position-only label makes the schedule a function of the key alone, so every document draws the same symbols and the null variance collapses; only false-positive calibration exposed this. Because the per-slot alphabets are exactly the admissible sets of § 2, summing log⁡2q\log_2 q over the instrument’s own slot walk reproduces κ\kappa and κeff\kappa^{\mathrm{eff}} to within 10−1410^{-14}. The detector is cover-informed by necessity rather than convenience: substitutes carry their own admissible sets, so a blind walk mis-derives the alphabet at up to 85% of substitutable positions in homoglyph-rich scripts.

Under the null of “not watermarked with this key” each slot matches with probability pi=1/qip_i = 1/q_i; under an attack ϕ\phi, pip_i is the share of the alphabet that ϕ\phi maps onto the identity’s image, so a destroyed slot has pi=1p_i = 1 and contributes no evidence. We declare a watermark when the zz-score of the match count XX, z=(X−∑ipi)/∑ipi(1−pi)z = (X - \sum_i p_i)/\sqrt{\sum_i p_i(1-p_i)}, exceeds the 99th percentile of null zz-scores on unwatermarked text, set separately for each language, condition and span length from 400 to 1,000 trials. We use this empirical threshold rather than a normal quantile because the normal approximation is anti-conservative at short lengths: a normal 1% threshold gives a false-positive rate of 0.054 for Arabic at two code points on clean text. On fresh keys the thresholds give a mean false-positive rate of 1.01% (0.96% to 1.04% across conditions), and 0.98% to 1.32% across conditions at the span length nearest each language’s nmin⁡n_{\min}. Single cells scatter around this, up to 5.0% for Indonesian at 128 code points under mixed-script detection, because each threshold is set, and each rate measured, on 400 to 1,000 trials.

The detector rebuilds its schedule from the received document, and an attack changes the characters the labels are computed from. Keying labels on the raw received characters is self-defeating: 13.9% of labels survive attack, the detection statistic drops by 19.8 standard units, and the required length is censored beyond 256 code points for 25 of the 40 languages. Keying on the skeleton keeps 59.7% of labels at a cost of 9.9 units. Keying on the canonical fold keeps 98.5% at a cost of 0.36, and we use this canonical keying throughout. The fold decomposes each character with the frozen Unicode 17.0.0 compatibility decompositions, maps it to its UTS #39 skeleton prototype and turns every White_Space character into U+0020; default-ignorable characters are deleted, which removes all four insertion characters.

Spans are sampled by equal-width strata across the document, and spans with no usable slot are scored at zero evidence rather than skipped, since skipping them would condition on the text being watermarkable. In § 6, κeff\kappa^{\mathrm{eff}} is evaluated at πlang\pi_{\mathrm{lang}} and at the gap rate of a span of length LL, (L−1)/L(L-1)/L, at the measured nmin⁡n_{\min}; §4–§ 5 use πnone\pi_{\mathrm{none}} and the corpus gap rate. For English the two are related exactly: 7.09 →\to 6.80 by the policy step and then →\to 5.63 by the gap-rate step. Each is correct for its analysis, and no table or figure mixes them. Under this convention the between-language spread of κeff\kappa^{\mathrm{eff}} is 3.28× on clean text, 10.11× under copy-paste and 2.52× under NFKC.

On clean text the median nmin⁡n_{\min} is 2.83 code points, English needs 1.95, and no language is censored: a 2.7× span of detection length against a 3.3×3.3 \times span of capacity. One might expect family (ii) share (family (ii)’s capacity as a fraction of rsubr_{\mathrm{sub}}) to explain the weaker NFKC correlation, but that is only half right: at πlang\pi_{\mathrm{lang}} it predicts the capacity loss strongly (Spearman −0.834-0.834 with substitution survival) but the ratio of measured to predicted nmin⁡n_{\min} only moderately (+0.437+0.437).

The combined attack applies the canonical fold to each character; its largest mean zz is 0.288 and its largest AUC 0.589. Skeleton mapping alone is not a floor, because the insertion track and some compatibility and whitespace variants survive. Two nulls exist, unwatermarked text and watermarked text read with the wrong key, and the thresholds use only the first. The two coincide on clean text, where no language has a collapsed image class, and stay close under copy-paste and mixed-script detection, but under NFKC the thresholds flag far more wrong-key text in Armenian and the four Cyrillic-script languages. Finally, 22 of the 40 languages already write with characters from the insertion alphabet, which makes an inserted invisible next to a native one unattributable; the decoder resolves runs rather than characters, and the effect is material only for Khmer, which writes word breaks with ZERO WIDTH SPACE and pays a 5.03% discount on decodable capacity. Persian has 1,739 native non-joiners and no ambiguous gap, because the joiner rule already removed the non-joiner from its safe set.

Table 7 gives nmin⁡n_{\min} with 95% bootstrap intervals, under the four attacks of Figure 7. Every value uses policy πlang\pi_{\mathrm{lang}} and canonical keying, the convention of § 6, and no cell is censored.

Reproducibility

We use Unicode 17.0.0 data files [28, 7], the CLDR 48.2.0 exemplar characters [29], and FLORES+ 4.5. The devtest split (1,012 sentences per language) gives every reported number and the dev split (997 sentences) the stability check of Appendix § 11. The Noto and DejaVu fonts behind the perceptual measures are pinned by release and file hash; the three system fonts in Figure 8 are recorded by hash but cannot be redistributed. Resampling is seeded: the capacity and detection-length bootstraps use seed 12345, the permutation tests of § 4 use 10,000 permutations at each of the seeds 20260801 and 314159, the cluster bootstrap uses 10,000 resamples at seed 271828, and the fresh-key check of Appendix § 14 uses seed 20261011.

References

  1. [1]Orevaoghene Ahia, Sachin Kumar, Hila Gonen, Jungo Kasai, David R Mortensen, Noah A Smith, and Yulia Tsvetkov. 2023. Do all languages cost the same? tokenization in the era of commercial language models. In Proceedings of the 2023 Conference on empirical methods in natural language processing, pages 9904–9923.
  2. [2]Nicholas Boucher and Ross Anderson. 2023. Trojan source: Invisible vulnerabilities. In 32nd USENIX security symposium (USENIX Security 23), pages 6507–6524.
  3. [3]Nicholas Boucher, Ilia Shumailov, Ross Anderson, and Nicolas Papernot. 2022. Bad characters: Imperceptible nlp attacks. In 2022 IEEE symposium on security and privacy (SP), pages 1987–2004. IEEE.arxiv.org/abs/2106.09898
  4. [4]Yapei Chang, Kalpesh Krishna, Amir Housamnad, John Frederick Weiting, and Mohit Iyyer. 2024. Postmark: A robust blackbox watermark for large language models. In Proceedings of the 2024 Conference on Empirical Methods in Natural Language Processing, pages 8969–8987.
  5. [6]Mark Davis and Robin Leroy. 2025. Unicode Standard Annex #31: Unicode Identifiers and Syntax. Unicode Standard Annex 31, The Unicode Consortium. Version 17.0.0.
  6. [7]Mark Davis and Michel Suignard. 2025. Unicode Technical Standard #39: Unicode Security Mechanisms. Unicode Technical Standard 39, The Unicode Consortium. Version 17.0.0.
  7. [8]Christopher A Field and Alan H Welsh. 2007. Bootstrapping clustered data. Journal of the Royal Statistical Society Series B: Statistical Methodology, 69(3):369–390.DOI
  8. [9]Zhiwei He, Binglin Zhou, Hongkun Hao, Aiwei Liu, Xing Wang, Zhaopeng Tu, Zhuosheng Zhang, and Rui Wang. 2024. Can watermarks survive translation? on the cross-lingual consistency of text watermark for large language models. In Proceedings of the 62nd Annual Meeting of the Association for Computational Linguistics (Volume 1: Long Papers), pages 4115–4129, Bangkok, Thailand. Association for Computational Linguistics.arxiv.org/abs/2402.14007
  9. [10]Malte Hellmeier, Hendrik Norkowski, Ernst-Christoph Schrewe, Haydar Qarawlus, and Falk Howar. 2025. Innamark: A whitespace replacement information-hiding method. IEEE access, 13:123120–123135.DOI
  10. [11]Richard Ishida. 2025. Arabic & persian layout requirements. W3c group draft note, World Wide Web Consortium (W3C).
  11. [12]Pratik Joshi, Sebastian Santy, Amar Budhiraja, Kalika Bali, and Monojit Choudhury. 2020. The state and fate of linguistic diversity and inclusion in the nlp world. In Proceedings of the 58th annual meeting of the association for computational linguistics, pages 6282–6293.arxiv.org/abs/2004.09095
  12. [13]John Kirchenbauer, Jonas Geiping, Yuxin Wen, Jonathan Katz, Ian Miers, and Tom Goldstein. 2023. A watermark for large language models. In International conference on machine learning, pages 17061–17084. PMLR.arxiv.org/abs/2301.10226
  13. [14]Rohith Kuditipudi, John Thickstun, Tatsunori Hashimoto, and Percy Liang. 2023. Robust distortion-free watermarks for language models. arXiv preprint arXiv:2307.15593.arxiv.org/abs/2307.15593
  14. [15]Asim Mohamed and Martin Gubri. 2025. Is multilingual llm watermarking truly multilingual? scaling robustness to 100+ languages via back-translation. arXiv preprint arXiv:2510.18019.
  15. [16]Alexander Nemecek, Yuzhou Jiang, and Erman Aydıay. 2026a. Topic-based watermarks for large language models. In Findings of the Association for Computational Linguistics: ACL 2026, pages 24372–24402, San Diego, California, United States. Association for Computational Linguistics.arxiv.org/abs/2404.02138
  16. [18]Alexander Nemecek, Osama Zafar, Yuqiao Xu, Wenbiao Li, and Erman Aydıay. 2026c. Who gets flagged? the pluralistic evaluation gap in ai content watermarking. arXiv preprint arXiv:2604.13776.arxiv.org/abs/2604.13776
  17. [19]NLLB Team, Marta R. Costa-jussà, James Cross, Onur Çelebi, Maha Elbayad, Kenneth Heafield, Kevin Heffernan, Elahe Kallabassi, Janice Lam, Daniel Licht, Jean Maillard, Anna Sun, Skyler Wang, Guillaume Wenzek, Al Youngblood, Bapi Akula, Loic Barrault, Gabriel Mejia Gonzalez, Prangthip Hansanti, and 20 others. 2024. Scaling neural machine translation to 200 languages. Nature, 630(8018):841–846.
  18. [20]Aleksandar Petrov, Emanuele La Malfa, Philip Torr, and Adel Bibi. 2023. Language model tokenizers introduce unfairness between languages. Advances in neural information processing systems, 36:36963–36990.arxiv.org/abs/2305.15425
  19. [21]Lip Yee Por, KokSheik Wong, and Kok Omn Chee. 2012. Unispach: A text-based data hiding method using unicode space characters. Journal of Systems and Software, 85(5):1075–1082.DOI
  20. [22]Stefano Giovanni Rizzo, Flavio Bertini, and Danilo Montesi. 2016. Content-preserving text watermarking through unicode homoglyph substitution. In Proceedings of the 20th International Database Engineering & Applications Symposium, pages 97–104.DOI
  21. [23]Rumi Technologies. 2025. New ChatGPT models seem to leave watermarks on text. Blog post, April 20, 2025; updated April 22, 2025 with OpenAI’s reply. Accessed October 9, 2026.
  22. [24]Phillip Rust, Jonas Pfeiffer, Ivan Vulić, Sebastian Ruder, and Iryna Gurevych. 2021. How good is your tokenizer? on the monolingual performance of multilingual language models. In Proceedings of the 59th Annual Meeting of the Association for Computational Linguistics and the 11th International Joint Conference on Natural Language Processing (Volume 1: Long Papers), pages 3118–3135.
  23. [25]Ryoma Sato, Yuki Takezawa, Han Bao, Kenta Niwa, and Makoto Yamada. 2023. Embarrassingly simple text watermarks. arXiv preprint arXiv:2310.08920.arxiv.org/abs/2310.08920
  24. [26]M Hassan Shirali-Shahreza and Mohammad Shirali-Shahreza. 2008. Steganography in persian and arabic unicode texts using pseudo-space and pseudo-connection characters. Journal of Theoretical and Applied Information Technology, 4(8):682–687.
  25. [27]Patrick E Shrout and Joseph L Fleiss. 1979. Intraclass correlations: uses in assessing rater reliability. Psychological bulletin, 86(2):420.pubmed.ncbi.nlm.nih.gov/18839484
  26. [28]The Unicode Consortium. 2025. The Unicode Standard, Version 17.0.0. The Unicode Consortium, South San Francisco.
  27. [29]The Unicode Consortium. 2026. Unicode Common Locale Data Repository (CLDR), version 48.2. https://github.com/unicode-org/cldr-json/releases/tag/48.2.0.
  28. [30]Ken Whistler. 2025. Unicode normalization forms. Unicode Standard Annex #15, The Unicode Consortium. Revision 57, Unicode 17.0.0.

Paper details

vlnc:W_svq73au5
v1 · latestHistory (1)

published · Fresh — added to the Hub within the last 30 days

added ·

Usage

views · 0

downloads · 0

A view counts once per browser per day, a download each time the PDF is requested.

CC BY 4.0

Original source

  • Computer Science

Not recorded for this paper · View statement →

Looking for related papers…
Contents