了不起的 Unicode!
“BMP之外的字符,例如U+1D306 tetragram for centre (𝌆),在UTF-16编码中只能编码成两个16比特代码单元:0xD834 0xDF06。这种情况称为代理对(surrogate pair)。注意代理对只表示一个字符。 “代理对的第一个字符永远在0xD800到0xDBFF的范围内,称为高位代理,或者叫起始字节代理。代理对的第二个代码单元永远在0xDC00到0xDFFF的范围内,称为低位代理,或者叫末端代理。”
“代理对:一种表示方式,用于表示由两个16比特代码单元组成的单个抽象字符,其中第一个值称为高位代理代码单元,第二个值称为低位代理单位。代理对仅在UTF-16中使用。”
var High_Surrogate = function(Code_Point){ return Math.floor((Code_Point - 0x10000) / 0x400) + 0xD800 };
var Low_Surrogate = function(Code_Point){ return (Code_Point - 0x10000) % 0x400 + 0xDC00 };
// Reverses The Conversion
var Code_Point = function(High_Surrogate, Low_Surrogate){
return (High_Surrogate - 0xD800) * 0x400 + Low_Surrogate - 0xDC00 + 0x10000;
};
> var codepoint = 0x1F4A9; // 0x1F4A9 == 128169
> High_Surrogate(codepoint).toString(16)
"d83d" // 0xD83D == 55357
> Low_Surrogate(codepoint).toString(16)
"dca9" // 0xDCA9 == 56489
> String.fromCharCode( High_Surrogate(codepoint) , Low_Surrogate(codepoint) );
"💩"
> String.fromCodePoint(0x1F4A9)
"💩"
> '\ud83d\udca9'
"💩"
组合和解组合
Unicode只不过是16比特编码。一些人误认为Unicode只不过是16比特编码,每个字符占用16比特,因此一共有65,536个可能的字符。实际上这是不正确的。这样是关于Unicode的最大误解,所以也难怪一些人会这么想。
任何未分配的代码点都可以用于内部用途?错。最终,那些未分配的地方都会被某个字符使用。你应该使用私有用途代码点,或非字符代码点。
每个Unicode代码点都表示一个字符?错。有许多非字符代码点(FFFE,FFFF,1FFFE,……)还有许多代理代码点、私有代码点和未分配的代码点,还有控制和格式“字符(RLM,ZWNJ,……)
字符映射是一对一的?错。映射关系也可能是:
一对多:(ß → SS )
上下文相关:(…Σ ↔ …ς 和 …ΣΤ… ↔ …στ… )
语言相关:( I ↔ ı 和 İ ↔ i )
> var ᅟ = 'foo';
undefined
> ᅟ
'foo'
> var ㅤ= alert;
undefined
> var foo = 'bar'
undefined
> if ( foo ===ㅤ`baz` ){} // alert
undefined
> var varㅤfooㅤ\u{A60C}ㅤπ = 'bar';
undefined
> varㅤfooㅤꘌㅤπ
'bar'
注意:我在Ubuntu和OSX下测试了下述程序的渲染结果:Node,PHP,Ruby,Python3.5,Scala,Vim,Cat,Chrome+GitHub gist。Atom是唯一无法正确渲染,将其显示成空方块的程序。我还没有测试Emacs和Sublime。据我的理解,Unicode联盟不会改变或重命名字符或代码点,但有可能会改变字符属性,如ID_Start或ID_Continue等。
> 'a'
"a"
> 'a\u{0308}'
"ä"
> 'a\u{20DE}\u{0308}'
"a⃞̈"
> 'a\u{20DE}\u{0308}\u{20DD}'
"a⃞̈⃝"
// Modifying Invisible Characters
> '\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}'
""
> '\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}\u{200E}'.length
10
大写变换冲突
小写变换冲突
字符串长度通常由统计代码点的个数来计算。这就是说,代理对会被统计成两个字符。多个声调符号会叠放在同一个字符上,例如a + ̈ == ̈a,从而增加长度,但它们只会产生一个字符。
类似地,字符串翻转通常非常困难。同样,代理对和带有声调符号的字符必须作为整体进行翻转。ES Reverser提供了一个非常好的解决方法。
字符映射是一对一的?错。映射关系也可能是:
一对多:(ß → SS )
上下文相关:(…Σ ↔ …ς 和 …ΣΤ… ↔ …στ… )
语言相关:( I ↔ ı 和 İ ↔ i )
PhantomScript::ghost: :flashlight: 不可见的JavaScript代码执行和社会工程工具。
ESReverser:用JavaScript编写的支持Unicode的字符串翻转。
mimic:Unicode的恶作剧。
python-ftfy:输入Unicode文本,输出更一致、更不容易出现显示错误的表现形式。
vim-troll-stopper:防止Unicode的捣乱字符搞乱你的代码。
Unicode联盟的表情符号表(https://www.unicode.org/emoji/charts/full-emoji-list.html)
Emojipedia(https://emojipedia.org/):关于特定表情符号的信息,新闻博客。
World Translation Foundation(https://www.emojifoundation.com/):宣传、探索,还可以将文本翻译成用表情符号表示的形式。
Can I Emoji? (https://caniemoji.com/android-2/):显示当前iOS、Android和Windows对于表情符号的原生支持情况。
怎样注册一个表情符号URL(https://www.name.com/blog/how-tos/2015/12/want-an-emoji-url-this-is-how-you-register-one/)
“有五个符号修饰字符可以为Unicode 8.0版(2015年中期)中发布的人类的表情符号提供一系列的肤色。这些字符基于Fitzpatrick度量(皮肤学上的著名标准,网上也有许多例子,比如FitzpatrickSkinType.pdf)定义的六种肤色。不同的实现的精确颜色可能不同。”——Unicode联盟的多样性报告
function rand(μ,σ){ ... };
String.prototype.reverseⵑ = function(){..};
Number.prototype.isTrueɁ = function(){..};
var WhatDoesThisDoɁɁɁɁ = 42
下面是Mathias Bynes(https://mathiasbynens.be/notes/javascript-identifiers#examples)提供的一些极富创意的变量名:
// How convenient!
var π = Math.PI;
// Sometimes, you just have to use the Bad Parts of JavaScript:
var ಠ_ಠ = eval;
// Code, Y U NO WORK?!
var ლ_ಠ益ಠ_ლ = 42;
// How about a JavaScript library for functional programming?
var λ = function() {};
// Obfuscate boring variable names for great justice
var \u006C\u006F\u006C\u0077\u0061\u0074 = 'heh';
// …or just make up random ones
var Ꙭൽↈⴱ = 'huh';
// While perfectly valid, this doesn’t work in most browsers:
var foo\u200Cbar = 42;
// This is *not* a bitwise left shift (`<<`):
var 〱〱 = 2;
// This is, though:
〱〱 << 〱〱; // 8
// Give yourself a discount:
var price_9̶9̶_89 = 'cheap';
// Fun with Roman numerals
var Ⅳ = 4;
var Ⅴ = 5;
Ⅳ + Ⅴ; // 9
// Cthulhu was here
var Hͫ̆̒̐ͣ̊̄ͯ͗͏̵̗̻̰̠̬͝ͅE̴̷̬͎̱̘͇͍̾ͦ͊͒͊̓̓̐_̫̠̱̩̭̤͈̑̎̋ͮͩ̒͑̾͋͘Ç̳͕̯̭̱̲̣̠̜͋̍O̴̦̗̯̹̼ͭ̐ͨ̊̈͘͠M̶̝̠̭̭̤̻͓͑̓̊ͣͤ̎͟͠E̢̞̮̹͍̞̳̣ͣͪ͐̈T̡̯̳̭̜̠͕͌̈́̽̿ͤ̿̅̑Ḧ̱̱̺̰̳̹̘̰́̏ͪ̂̽͂̀͠ = 'Zalgo';
下面是David Walsh提供的一些Unicode CSS类名(https://davidwalsh.name/unicode-css-classes):
<!-- place this within the document head -->
<meta charset="UTF-8" />
<!-- error message -->
<div class="ಠ_ಠ">You do not have access to this page.</div>
<!-- success message -->
<div class="❤">Your changes have been saved successfully!</div>
.ಠ_ಠ {
border: 1px solid #f00;
}
.❤ {
background: lightgreen;
}
但要注意,HTML并不会支持所有的Unicode字符。
// U+1160 HANGUL JUNGSEONG FILLER
transformAllTags('ᅠ');
// An actual HTML element node designed to look like a comment node, using the U+01C3 LATIN LETTER RETROFLEX CLICK
// <ǃ-- name="viewport" content="width=device-width"></ǃ-->
transformAllTags('ǃ--');
// or even <ᅠ⃝
transformAllTags('\u{1160}\u{20dd}');
// and for a bonus, all existing tag names will have each character ensquared. h⃞t⃞m⃞l⃞
transformAllTags();
function transformAllTags (newName){
// querySelectorAll doesn't actually return an array.
Array.from(document.querySelectorAll('*'))
.forEach(function(x){
transformTag(x, newName);
});
}
function wonky(str){
return str.split('').join('\u{20de}') + '\u{20de}';
}
function transformTag(tagIdOrElem, tagType){
var elem = (tagIdOrElem instanceof HTMLElement) ? tagIdOrElem : document.getElementById(tagIdOrElem);
if(!elem || !(elem instanceof HTMLElement))return;
var children = elem.childNodes;
var parent = elem.parentNode;
var newNode = document.createElement(tagType||wonky(elem.tagName));
for(var a=0;a<elem.attributes.length;a++){
newNode.setAttribute(elem.attributes[a].nodeName, elem.attributes[a].value);
}
for(var i= 0,clen=children.length;i<clen;i++){
newNode.appendChild(children[0]); //0...always point to the first non-moved element
}
newNode.style.cssText = elem.style.cssText;
parent.replaceChild(newNode,elem);
}
下面是确定能够支持的字符:
function testBegin(str){
try{
eval(`document.createElement( '${str}' );`)
return true;
}
catch(e){ return false; }
}
function testContinue(str){
try{
eval(`document.createElement( 'a${str}' );`)
return true;
}
catch(e){ return false; }
}
下面是一些基本的结果:
// Test if dashes can start an HTML Tag
> testBegin('-')
< false
> testContinue('-')
< true
> testBegin('ᅠ-') // Prepend dash with U+1160 HANGUL JUNGSEONG FILLER
< true
https://en.wikipedia.org/wiki/Unicode_font#List_of_Unicode_fonts
https://www.unifont.org/fontguide/
通用原则——曾经出现过的一切书写系统都应该在标准中体现。
逻辑顺序——在双向文本中,字符以逻辑顺序存储,而不是表现顺序存储。
效率——文档必须是高效的、完整的。
统一——不同文化或语言使用同一个字符时,应该仅存储一次。
记录字符而不是字形——应当对字符进行编码,而不是字形。字形就是字符的实际图形表示。
动态组合——新的字符可以与已有的标准化后的字符进行组合。例如,字符“Ä”可以用字符“A”和分音符“¨”组合而成。
语义——包含的字符必须有明确定义,必须与其他字符有明确的区别。
稳定——字符一旦被定义,就永远不能被移除,其代码点也不能被挪作他用。如果出错,则应该将代码点标记为弃用。
纯文本——标准中的字符应当是纯文本,永远不应该包含标记或元字符。
可转换——每一种编码都应该可以用Unicode编码表示。
热 文 推 荐
☞华为诺亚方舟开源预训练模型“哪吒”,4项任务均达到SOTA