文精心挑選了許多優(yōu)秀的Unicode小技巧、軟件包和資源。
譯者 | 彎月,責(zé)編 | 郭芮
出品 | CSDN(ID:CSDNnews)
以下為譯文:
Unicode非常了不起!在Unicode出現(xiàn)之前,國際交流是一團(tuán)糟——每個人都在ASCII碼表的后半部分區(qū)域(稱為“代碼頁”)定義了自己的擴(kuò)展和字符集,從而導(dǎo)致各種沖突。想想就知道,德國人要與韓國人只使用127個字符組成的代碼頁進(jìn)行交流會有多么困難。
——幸虧有了Unicode標(biāo)準(zhǔn)和統(tǒng)一的交流規(guī)范。
Unicode 8.0根據(jù)129多種書寫體系,標(biāo)準(zhǔn)化了超過120,000個字符,其中包括現(xiàn)代字符、古代字符,甚至還包括人類尚未解密的文字。Unicode能處理從左到右和從右到左兩種書寫方式,支持組合標(biāo)記,還支持多種文化、政治、宗教方面的字符,甚至還有表情符號。
Unicode太了不起了,我們對它的崇拜猶如滔滔江水綿綿不絕。
Unicode的背景
Unicode標(biāo)準(zhǔn)支持什么字符?
Unicode標(biāo)準(zhǔn)定義了今日所有主流的書寫語言中用到的字符。Unicode支持的書寫體系包括歐洲的語系、中東的從右至左書寫的語系,以及亞洲的多種語系。
Unicode標(biāo)準(zhǔn)還包含了標(biāo)點(diǎn)符號、聲調(diào)符號、數(shù)學(xué)符號、科技符號、箭頭、各種圖形符號、表情符號,等等。Unicode為聲調(diào)符號(用來改變其他字符的符號,如波浪線~)單獨(dú)提供了代碼,這些代碼可以與基礎(chǔ)字符組合使用,來表示有聲調(diào)的字符(如?)。Unicode標(biāo)準(zhǔn)9.0版總共提供了128,172個字符的代碼,其中包括了全世界的字符、圖形和符號。
絕大部分的常用字符都能映射到最前面的64K個代碼點(diǎn)上,這一區(qū)域叫做基本多文種平面(basic multilingual plane,簡稱為BMP)。還有十六個補(bǔ)充平面用來編碼其他字符,目前尚有850,000個未使用的代碼點(diǎn)。人們還在考慮在以后的版本中添加更多的字符。
Unicode標(biāo)準(zhǔn)還保留了一些代碼點(diǎn)供私人使用。供應(yīng)商或最終用戶可以在內(nèi)部利用這些代碼點(diǎn)表示他們自己的字符和符號,或者通過特殊的字體來使用。BMP上有6,400個私有代碼點(diǎn),如果不夠的話,補(bǔ)充平面上還有131,068個私有代碼點(diǎn)可供使用。
Unicode字符編碼
字符編碼標(biāo)準(zhǔn)不僅定義了每個字符的唯一標(biāo)識(即字符的數(shù)字值,或者叫做代碼點(diǎn)),也定義了怎樣用比特來表示這個值。
Unicode標(biāo)準(zhǔn)定義了三種編碼形式,允許同一個數(shù)據(jù)以一字節(jié)、兩字節(jié)或四字節(jié)的格式來傳輸(即每個代碼單元可以是8比特、16比特或32比特)。同一個字符集可以使用所有三種編碼形式,它們之間可以互相轉(zhuǎn)換,而不會丟失數(shù)據(jù)。Unicode聯(lián)盟建議根據(jù)實(shí)際需要,選擇任何一種方便的編碼方式來實(shí)現(xiàn)Unicode標(biāo)準(zhǔn)。
UTF-8在HTML和類似協(xié)議上非常常用。UTF-8使用變長編碼。它的優(yōu)點(diǎn)是,對應(yīng)于ASCII字符集的那些Unicode字符的字節(jié)值與它們在ASCII中的值完全相同,因此使用UTF-8編碼的Unicode字符可以在絕大多數(shù)已有軟件上使用,無需對軟件做出任何修改。
UTF-16在許多需要平衡性能和存儲效率的環(huán)境中非常常用。它足夠緊湊,所有常用的字符都可以用一個16比特的代碼單元來表示,其他字符可以使用一對16比特代碼單元來表示。
UTF-32在無需顧慮內(nèi)存空間的情況下使用,它是定長編碼,每個字符只有一個代碼單元。每個Unicode字符編碼成一個32比特代碼單元。
在所有三種編碼中,每個字符最多需要4個字節(jié)(32比特)表示。
數(shù)字問題
Unicode字符集被分成17個核心段,稱為“平面”,每個平面又被分成若干區(qū)塊。每個平面的空間足夠容納65,536(216)個代碼點(diǎn),因此總共有1,114,112個代碼點(diǎn)。還有兩個“私有區(qū)域”平面(#16和#17),可以按照使用者的意愿定義。這兩個私有平面共包含131,072個代碼點(diǎn)。
第一個平面叫做“基本多文種平面”,或者稱為BMP。它包含代碼點(diǎn)U+0000到U+FFFF,這個范圍內(nèi)包含了絕大部分常用字符。另外16個平面(U+010000到U+10FFFF)稱為補(bǔ)充平面。
UTF-16代理對
“BMP之外的字符,例如U+1D306 tetragram for centre (),在UTF-16編碼中只能編碼成兩個16比特代碼單元:0xD834 0xDF06。這種情況稱為代理對(surrogate pair)。注意代理對只表示一個字符。
“代理對的第一個字符永遠(yuǎn)在0xD800到0xDBFF的范圍內(nèi),稱為高位代理,或者叫起始字節(jié)代理。代理對的第二個代碼單元永遠(yuǎn)在0xDC00到0xDFFF的范圍內(nèi),稱為低位代理,或者叫末端代理。”
——Mathias Bynens
“代理對:一種表示方式,用于表示由兩個16比特代碼單元組成的單個抽象字符,其中第一個值稱為高位代理代碼單元,第二個值稱為低位代理單位。代理對僅在UTF-16中使用。”
——Unicode 8.0 第3.9章,代理對(參見Unicode編碼)
計(jì)算代理對
代理字符 Pile of Poo (U+1F4A9) 在UTF-16中必須編碼成代理對,即兩個代理。要將代碼點(diǎn)轉(zhuǎn)換成代理對,可以使用以下算法(用JavaScript編寫)。注意我們使用的是十六進(jìn)制表示。
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包括了一種修改字符形狀的機(jī)制,大幅擴(kuò)展了Unicode支持的字符量。使用聲調(diào)符號進(jìn)行組合就是其中一種方式。聲調(diào)符號寫在主字符的后面。多個聲調(diào)符號可以疊在同一個字符上。對于絕大部分常用的字母聲調(diào)組合,Unicode還包括了預(yù)先組合好的版本。
特定的字符序列也可以用單個字符表示,稱為“預(yù)組合字符”(或者叫組合字符,可以解組合的字符)。例如,字符“ü”可以編碼成單個代碼單元U+00FC “ü”,也可以編碼成基本字符U+0075 “u”后接無空白字符U+0308 “¨”。Unicode標(biāo)準(zhǔn)中設(shè)置的預(yù)組合字符是為了兼容Latin 1等標(biāo)準(zhǔn),后者包含了許多預(yù)組合字符,如“ü”和“?”。
預(yù)組合字符可以進(jìn)行接組合,以保持一致性,或用于分析。例如,需要將一組名稱轉(zhuǎn)換為英文字母時,可以將字符“ü”解組合為“u”后接非空白字符“¨”。解組合后的結(jié)果很容易處理,因?yàn)樵摻M合字符可以處理成“u”后接一個修飾字符。這樣很容易進(jìn)行按字母順序排序等,因?yàn)樾揎椬址粫绊懽帜疙樞颉nicode標(biāo)準(zhǔn)為所有預(yù)組合字符定義了解組合方式(https://unicode.org/versions/Unicode8.0.0/ch03.pdf#page=44)。它還定義了正規(guī)化的方式,以便為字符提供唯一的表示方法。
Unicode之謎
來自Mark Davis的《Unicode之謎》幻燈片(https://macchiato.com/slides/UnicodeMyths.pdf)。
Unicode只不過是16比特編碼。一些人誤認(rèn)為Unicode只不過是16比特編碼,每個字符占用16比特,因此一共有65,536個可能的字符。實(shí)際上這是不正確的。這樣是關(guān)于Unicode的最大誤解,所以也難怪一些人會這么想。
任何未分配的代碼點(diǎn)都可以用于內(nèi)部用途?錯。最終,那些未分配的地方都會被某個字符使用。你應(yīng)該使用私有用途代碼點(diǎn),或非字符代碼點(diǎn)。
每個Unicode代碼點(diǎn)都表示一個字符?錯。有許多非字符代碼點(diǎn)(FFFE,F(xiàn)FFF,1FFFE,……)還有許多代理代碼點(diǎn)、私有代碼點(diǎn)和未分配的代碼點(diǎn),還有控制和格式“字符(RLM,ZWNJ,……)
字符映射是一對一的?錯。映射關(guān)系也可能是:
一對多:(? → SS )
上下文相關(guān):(…Σ ? …? 和 …ΣΤ… ? …στ… )
語言相關(guān):( I ? ? 和 ? ? i )
實(shí)用Unicode編碼手冊
編碼類型編碼
神奇的字符列表
特殊字符
詳情可以參照Unicode聯(lián)盟發(fā)布的《通用標(biāo)點(diǎn)符號表》(https://www.unicode.org/charts/PDF/U2000.pdf)。
等等,你說什么?
變量標(biāo)識符可以包含空白!
U+3164 HANGUL FILLER 字符顯示為占據(jù)空間的空白字符。如果渲染器不支持,則會渲染成完全不可見(也不會占據(jù)任何空間,即“零寬度”)。這就是說,永遠(yuǎn)不會看到丑陋的字符替代符號。
我不知道為什么U+3164被設(shè)計(jì)成這種行為。有意思的是,U+3164是在Unicode 1.1版本(1993年)加入的,所以聯(lián)盟一定是花了很多時間思考它。下面是幾個例子:
> 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下測試了下述程序的渲染結(jié)果:Node,PHP,Ruby,Python3.5,Scala,Vim,Cat,Chrome+GitHub gist。Atom是唯一無法正確渲染,將其顯示成空方塊的程序。我還沒有測試Emacs和Sublime。據(jù)我的理解,Unicode聯(lián)盟不會改變或重命名字符或代碼點(diǎn),但有可能會改變字符屬性,如ID_Start或ID_Continue等。
修飾符
零寬度連接符(ZWJ)是個不可打印字符,用于某些復(fù)雜語系的計(jì)算機(jī)排版系統(tǒng)中,如阿拉伯語系、印度語系等。將ZWJ放在兩個本來不會連接的字符之間,將會導(dǎo)致它們以連接的形式打印。
零寬度不連接符(ZWNJ)是個不可打印字符,那些使用連接的書寫系統(tǒng)的計(jì)算機(jī)化。將ZWNJ放在兩個本來會連接在一起的字符之間,會導(dǎo)致它們以本來的形式打印。空格字符也有同樣的效果,但ZWNJ的作用是它能保證輸出的兩個字符盡可能靠近,或者連接一個詞及其語素。
> 'a'
"a"
> 'a\u{0308}'
"a?"
> '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
大寫變換沖突
小寫變換沖突
奇怪現(xiàn)象和排查方法
字符串長度通常由統(tǒng)計(jì)代碼點(diǎn)的個數(shù)來計(jì)算。這就是說,代理對會被統(tǒng)計(jì)成兩個字符。多個聲調(diào)符號會疊放在同一個字符上,例如a + ?==?a,從而增加長度,但它們只會產(chǎn)生一個字符。
類似地,字符串翻轉(zhuǎn)通常非常困難。同樣,代理對和帶有聲調(diào)符號的字符必須作為整體進(jìn)行翻轉(zhuǎn)。ES Reverser提供了一個非常好的解決方法。
字符映射是一對一的?錯。映射關(guān)系也可能是:
一對多:(? → SS )
上下文相關(guān):(…Σ ? …? 和 …ΣΤ… ? …στ… )
語言相關(guān):( I ? ? 和 ? ? i )
一對多映射
絕大多數(shù)字符,在大寫的時候表示一對多關(guān)系;另一些字符小寫的時候表示一對多關(guān)系。
優(yōu)秀的軟件包和庫
PhantomScript::ghost: :flashlight: 不可見的JavaScript代碼執(zhí)行和社會工程工具。
ESReverser:用JavaScript編寫的支持Unicode的字符串翻轉(zhuǎn)。
mimic:Unicode的惡作劇。
python-ftfy:輸入U(xiǎn)nicode文本,輸出更一致、更不容易出現(xiàn)顯示錯誤的表現(xiàn)形式。
vim-troll-stopper:防止Unicode的搗亂字符搞亂你的代碼。
表情符號
Unicode聯(lián)盟的表情符號表(https://www.unicode.org/emoji/charts/full-emoji-list.html)
Emojipedia(https://emojipedia.org/):關(guān)于特定表情符號的信息,新聞博客。
World Translation Foundation(https://www.emojifoundation.com/):宣傳、探索,還可以將文本翻譯成用表情符號表示的形式。
Can I Emoji? (https://caniemoji.com/android-2/):顯示當(dāng)前iOS、Android和Windows對于表情符號的原生支持情況。
怎樣注冊一個表情符號URL(https://www.name.com/blog/how-tos/2015/12/want-an-emoji-url-this-is-how-you-register-one/)
多樣性
Unicode聯(lián)盟在支持人類的多樣性和多元文化方面做出了很多努力。這里是聯(lián)盟提供的多樣性報(bào)告(https://unicode.org/reports/tr51/#Diversity)。
現(xiàn)在表情符號已經(jīng)支持混合型別,比如同性家庭、握手、接吻等。真正轟動的是表情符號組合序列。基本上來說:
此外,表情符號現(xiàn)在還支持膚色修飾字符了。
“有五個符號修飾字符可以為Unicode 8.0版(2015年中期)中發(fā)布的人類的表情符號提供一系列的膚色。這些字符基于Fitzpatrick度量(皮膚學(xué)上的著名標(biāo)準(zhǔn),網(wǎng)上也有許多例子,比如FitzpatrickSkinType.pdf)定義的六種膚色。不同的實(shí)現(xiàn)的精確顏色可能不同。”
——Unicode聯(lián)盟的多樣性報(bào)告
只需要在所需的表情符號后面接上膚色修飾字符 \u{1F466}\u{1F3FE} 即可。
有創(chuàng)意的變量名和方法名
示例采用JavaScript(ES6)編寫。
一般而言,帶有ID_START屬性的字符可以用在變量名開頭,而帶有ID_CONTINUE屬性的字符可以用在變量名中除了首字符之外的其他位置。
function rand(μ,σ){ ... };
String.prototype.reverse?=function{..};
Number.prototype.isTrue?=function{..};
var WhatDoesThisDo????=42
下面是Mathias Bynes(https://mathiasbynens.be/notes/javascript-identifiers#examples)提供的一些極富創(chuàng)意的變量名:
// 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標(biāo)簽重命名腳本
如果你想把所有HTML標(biāo)簽重命名,使之看上去像什么都沒有,那么可以使用以下的腳本。
但要注意,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);
});
}
functionwonky(str){
return str.split('').join('\u{20de}') + '\u{20de}';
}
functiontransformTag(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; }
}
下面是一些基本的結(jié)果:
// Test if dashes can start an HTML Tag
> testBegin('-')
< false
> testContinue('-')
< true
> testBegin('?-') // Prepend dash with U+1160 HANGUL JUNGSEONG FILLER
< true
Unicode字體
單一的TrueType / OpenType 字體格式無法支持所有UTF-8字符,因?yàn)樽煮w文件有最大65535個字形的限制。UTF-8的字形超過了110萬,因此你需要一個font-family才能覆蓋所有字體。
https://en.wikipedia.org/wiki/Unicode_font#List_of_Unicode_fonts
https://www.unifont.org/fontguide/
Unicode標(biāo)準(zhǔn)的原則
Unicode標(biāo)準(zhǔn)設(shè)定了下述基本原則:
通用原則——曾經(jīng)出現(xiàn)過的一切書寫系統(tǒng)都應(yīng)該在標(biāo)準(zhǔn)中體現(xiàn)。
邏輯順序——在雙向文本中,字符以邏輯順序存儲,而不是表現(xiàn)順序存儲。
效率——文檔必須是高效的、完整的。
統(tǒng)一——不同文化或語言使用同一個字符時,應(yīng)該僅存儲一次。
記錄字符而不是字形——應(yīng)當(dāng)對字符進(jìn)行編碼,而不是字形。字形就是字符的實(shí)際圖形表示。
動態(tài)組合——新的字符可以與已有的標(biāo)準(zhǔn)化后的字符進(jìn)行組合。例如,字符“?”可以用字符“A”和分音符“¨”組合而成。
語義——包含的字符必須有明確定義,必須與其他字符有明確的區(qū)別。
穩(wěn)定——字符一旦被定義,就永遠(yuǎn)不能被移除,其代碼點(diǎn)也不能被挪作他用。如果出錯,則應(yīng)該將代碼點(diǎn)標(biāo)記為棄用。
純文本——標(biāo)準(zhǔn)中的字符應(yīng)當(dāng)是純文本,永遠(yuǎn)不應(yīng)該包含標(biāo)記或元字符。
可轉(zhuǎn)換——每一種編碼都應(yīng)該可以用Unicode編碼表示。
原文:https://wisdom.engineering/awesome-unicode/
本文為 CSDN 翻譯,轉(zhuǎn)載請注明來源出處。
【End】
Python系列學(xué)習(xí)成長課來了!15年經(jīng)驗(yàn)專家、CSDN特級講師親自授課,還等什么?立即掃碼報(bào)名學(xué)習(xí):
nicode 聯(lián)盟(Unicode Consortium)
Unicode 聯(lián)盟(Unicode Consortium)開發(fā)了 Unicode 標(biāo)準(zhǔn)(Unicode Standard)。他們的目標(biāo)是使用標(biāo)準(zhǔn)的 Unicode 轉(zhuǎn)換格式(即 UTF,全稱 Unicode Transformation Format)取代現(xiàn)有的字符集。
Unicode 標(biāo)準(zhǔn)是一個成功的創(chuàng)舉,在 HTML、XML、Java、JavaScript、E-mail、ASP、PHP 中都得到實(shí)現(xiàn)。Unicode 標(biāo)準(zhǔn)也得到許多操作系統(tǒng)和所有現(xiàn)代瀏覽器的支持。
Unicode 聯(lián)盟與領(lǐng)先的標(biāo)準(zhǔn)開發(fā)組織合作,這些組織有 ISO、W3C 和 ECMA。
Unicode 字符集
Unicode 可以由不同的字符集實(shí)現(xiàn)。最常用的編碼是 UTF-8 和 UTF-16:
字符集 | 描述 |
---|---|
UTF-8 | UTF8 中的字符可以是 1 到 4 字節(jié)長。UTF-8 可以代表 Unicode 標(biāo)準(zhǔn)中的任何字符。UTF-8 向后兼容 ASCII。UTF-8 是電子郵件和網(wǎng)頁的首選編碼。 |
UTF-16 | 16 位 Unicode 轉(zhuǎn)換格式是一種可變長度的 Unicode 字符編碼,能夠編碼整個 Unicode 指令表。UTF-16 主要用于操作系統(tǒng)和環(huán)境,如 Microsoft Windows、Java 和 .NET。 |
提示:Unicode 的前 128 個字符(與 ASCII 一一對應(yīng))使用一個與 ASCII二進(jìn)制值相同的八位組進(jìn)行編碼,使有效的 ASCII 文本在進(jìn)行 UTF-8 編碼時也是有效的。
提示:所有的 HTML 4 處理器支持 UTF-8,所有的 HTML 5 和 XML 處理器支持 UTF-8 和 UTF-16!
HTML5 標(biāo)準(zhǔn):Unicode UTF-8
因?yàn)?ISO-8859 中字符集大小是有限的,且在多語言環(huán)境中不兼容,所以 Unicode 聯(lián)盟開發(fā)了 Unicode 標(biāo)準(zhǔn)。
Unicode 標(biāo)準(zhǔn)覆蓋了(幾乎)所有的字符、標(biāo)點(diǎn)符號和符號。
Unicode 使文本的處理、存儲和運(yùn)輸,獨(dú)立于平臺和語言。
HTML-5 中默認(rèn)的字符編碼是 UTF-8。
下面列出了一些 HTML5 支持的 UTF-8 字符集:
字符集 | 十進(jìn)制 | 十六進(jìn)制 |
---|---|---|
C0 控制與基本的 Latin(C0 Controls and Basic Latin) | 0-127 | 0000-007F |
C1 控制與 Latin-1 的補(bǔ)充(C1 Controls and Latin-1 Supplement) | 128-255 | 0080-00FF |
Latin 擴(kuò)展 A(Latin Extended-A) | 256-383 | 0100-017F |
Latin 擴(kuò)展 B(Latin Extended-B) | 384-591 | 0180-024F |
如果 HTML5 網(wǎng)頁使用不同于 UTF-8 的字符,則需要在 <meta> 標(biāo)簽中指定,如下:
實(shí)例
<meta charset="ISO-8859-1">
如您還有不明白的可以在下面與我留言或是與我探討QQ群308855039,我們一起飛!
周的時候,朋友圈的直升飛機(jī)不知道為什么就火了,很多朋友開著各種花式飛機(jī)帶著起飛。
圖片來自網(wǎng)絡(luò)
還沒來得及了解咋回事來著,這個直升飛機(jī)就到的微博熱搜。
圖片來自網(wǎng)絡(luò)
后面越來越多人開來他們的直升飛機(jī),盤旋在朋友圈上方。于是很多朋友開來他們的坦克,專打直升飛機(jī),一轟一個準(zhǔn)。
圖片來自網(wǎng)絡(luò)
好了,說回正題!
程序員朋友應(yīng)該都很熟悉 Unicode (萬國碼),它幾乎包含世界上所有符號,比如組成直升飛機(jī)這幾個特殊符號對應(yīng)的 Unicode 碼分別為:
ps:推薦一個網(wǎng)站,可以根據(jù)符號搜對應(yīng)的 Unicode 碼:https://unicode.yunser.com/unicode
除了這些正常字符以外,Unicode 還包含著各種各樣的奇葩字符。
除了正常的我們熟知的文字以外,Unicode 中還有一些奇怪的文字,比如下面這些文字
這咋讀?某少?
世代?
恩?超出認(rèn)知范圍
除了這些奇怪文字以外,Unicode 還有一些奇葩的的符號。
例如下面一整套麻將牌:
一整套的撲克牌:
一整套國際象棋:
image-20200725215319183
除了這些,通過組合符合,我們還可以造出各種各樣的顏文字(??????)??、
另外 Unicode 還收錄著我們常用的 Emoji 。
除了這些之外,Unicode 中還有一些特殊字符的,利用這些字符,我們還可以玩出很多有趣的騷操作。
Unicode 有一類字符稱為組合字符,它可以附加在前一個非組合字符上,從而使整體看起來像是一個字符。
組合字符原來目的是為了解決一些地區(qū)語言、文字特殊的需要,比如說泰文聲調(diào)符號與母音符號。
正常使用的情況下,這些組合字符數(shù)量都會有一些限制。但是在 Unicode 組合字符設(shè)計(jì)上,并沒有加這種限制,這樣使我們可以無限加這類組合字符。
利用這個特性,可以達(dá)到一些惡搞效果,比如「擊穿天花板」與「鑿穿地板」的效果。
上面實(shí)現(xiàn)原理其是利用以下兩個組合字符:
上翻字符
下翻字符
只要復(fù)制這兩個字符相應(yīng)的 HTML 代碼,跟在正常的字符后面,就可以使這兩個字符附加在普通字符上,比如下面實(shí)現(xiàn)效果為
黑??
Unicode 碼值通常使用 U+N(16 進(jìn)制N 代表碼值),比如 A 的碼值為 U+0041。
在 HTML 中 Unicode 可以使用 N;(十進(jìn)制,N 代表碼值)表示
在 JS 中 Unicode 中需要使用] \uN(16 進(jìn)制N 代表碼值)表示
只要我們在普通字符多復(fù)制幾個這類附加字符,就可以形成上述「擊穿」效果。
還記得上面說的泰文嗎,曾經(jīng)有一段時間貼吧,很流行一種噴射文,比如下面的效果。
向左噴
向右噴
左右互噴
這種噴射文實(shí)際原理就是利用泰文中聲調(diào)符號附加在其他正常符號上。
不過現(xiàn)在這個效果貌似已經(jīng)沒辦法再復(fù)現(xiàn)了,現(xiàn)在我們只能看到這樣的效果:
在一些老版本的系統(tǒng)/瀏覽器可能還能看到這種效果,知道的小伙伴留言區(qū)可以告知一下。
Unicode 中還有一類格式字符,不可見,不可打印,主要作用于調(diào)整字符的顯示格式,所以我們將其稱為零寬字符。
零寬字符主要有以下幾類:
零寬度空格符 (zero-width space) U+200B : 用于較長單詞的換行分隔
零寬度非斷空格符 (zero width no-break space) U+FEFF : 用于阻止特定位置的換行分隔
零寬度連字符 (zero-width joiner) U+200D : 用于阿拉伯文與印度語系等文字中,使不會發(fā)生連字的字符間產(chǎn)生連字效果
零寬度斷字符 (zero-width non-joiner) U+200C : 用于阿拉伯文,德文,印度語系等文字中,阻止會發(fā)生連字的字符間的連字效果
左至右符 (left-to-right mark) U+200E : 用于在混合文字方向的多種語言文本中(例:混合左至右書寫的英語與右至左書寫的希伯來語),規(guī)定排版文字書寫方向?yàn)樽笾劣?/p>
右至左符 (right-to-left mark) U+200F : 用于在混合文字方向的多種語言文本中,規(guī)定排版文字書寫方向?yàn)橛抑磷?/p>
利用零寬字符不不可見的特性,我們也可以玩出一些騷效果。
發(fā)布微博的時候,如果內(nèi)容都是空格,將沒辦法發(fā)布。
但是如果我們將零寬字符,比如說「零寬度空格符 U+200B」復(fù)制到微博,這樣我們就可以發(fā)布空白微博。
我們可以利用 Chrome 瀏覽器的控制臺復(fù)制零寬字符,操作方式如下:
發(fā)布效果如下:
真的沒有改 HTML 導(dǎo)致的.jpg
對于一些內(nèi)部論壇或者說小說網(wǎng)站來說,可以通過零寬字符在帖子或小說內(nèi)容嵌入隱形水印。
當(dāng)這些內(nèi)容被一些爬蟲復(fù)制到其他網(wǎng)站時,我們就可以通過隱形水印,輕松查找時那位用戶泄漏內(nèi)容。
隱形水印主要原理就是將用戶信息比如用戶名,通過一定算法轉(zhuǎn)成零寬字符,這樣普通用戶瀏覽時完全看不到這個水印。
如果內(nèi)容被復(fù)制到其他網(wǎng)站,隱形誰贏也被復(fù)制,只要找到這個水印,將這些零寬字符反轉(zhuǎn)成用戶名即可。
下面展示一種轉(zhuǎn)換方法,JS 代碼主要參考以下 Github 項(xiàng)目:
https://github.com/umpox/zero-width-detection
隱形水印生成方法
第一步我們需要將明文字符串每個字符都轉(zhuǎn)成二進(jìn)制串。
// 每個字符轉(zhuǎn)為二進(jìn)制,用空格分隔
const textToBinary = username => (
username
.split('')
// charCodeAt 將字符轉(zhuǎn)成相應(yīng)的 Unicode 碼值
.map(char => char.charCodeAt(0).toString(2))
.join(' ')
);
示例如下:
第二步,將二進(jìn)制串轉(zhuǎn)為零度字符串,轉(zhuǎn)換規(guī)則如下:
const binaryToZeroWidth = binary => (
binary.split('').map((binaryNum) => {
const num = parseInt(binaryNum, 10);
if (num === 1) {
return '\u200b'; // \u200b 零寬度字符(zero-width space)
} else if(num===0) {
return '\u200c'; // \u200c 零寬度斷字符(zero-width non-joiner)
}
return '\u200d'; // \u200d 零寬度連字符 (zero-width joiner)
}).join('\ufeff') // \ufeff 零寬度非斷空格符 (zero width no-break space)
);
最終加密方法如下:
const encode = username => {
const binaryUsername = textToBinary(username);
const zeroWidthUsername = binaryToZeroWidth(binaryUsername);
return zeroWidthUsername;
};
使用加密方法將明文字符串加密之后,加密字符串肉眼是看不到了,但是實(shí)際還是存在的。
實(shí)際上,如果我們將加密之后字符串復(fù)制到 BEJSON 網(wǎng)站,就可以看到字符。
image-20200722083507869
另外你還可以把加密字符串復(fù)制到 IDEA 中,可以看到相應(yīng)的 Unicode 編碼值。
解密隱形水印
知道了加密的方式,解密其實(shí)就很簡單,我們只要按照相反步驟的來就可以了。
第一步,將隱形水印按照以下規(guī)則轉(zhuǎn)換為二進(jìn)制串。轉(zhuǎn)換規(guī)則如下:
const zeroWidthToBinary = string => (
string.split('\ufeff').map((char) => { // \ufeff 零寬度非斷空格符 (zero width no-break space)
if (char === '\u200b') { // \u200b 零寬度字符(zero-width space)
return '1';
} else if(char === '\u200c') { // \u200c 零寬度斷字符(zero-width non-joiner)
return '0';
}
return ' ';
}).join('')
);
調(diào)用該方法,隱形水印轉(zhuǎn)成二進(jìn)制串。
第二步,將二進(jìn)制再轉(zhuǎn)為相應(yīng)的字符。
const binaryToText = string => (
// fromCharCode 二進(jìn)制轉(zhuǎn)化
string.split(' ').map(num => String.fromCharCode(parseInt(num, 2))).join('')
);
最終解密方法如下:
const decode = zeroWidthUsername => {
const binaryUsername = zeroWidthToBinary(zeroWidthUsername);
const textUsername = binaryToText(binaryUsername);
return textUsername;
};
解密示例如下:
?
我們常用的短網(wǎng)址,域名后面會跟上一串隨機(jī)串,從而實(shí)現(xiàn)短網(wǎng)址到長網(wǎng)址的映射。比如以下網(wǎng)址:
https://sourl.cn/iLyn9S
然而我們可以利用零寬字符也可以實(shí)現(xiàn)短網(wǎng)址的效果,,比如下面這個網(wǎng)站,就可以生成這類短網(wǎng)址。
https://zws.im/
可以看到這個短網(wǎng)址后面看不到任何字符,實(shí)際上這后面跟著一串零寬字符。當(dāng)瀏覽器訪問該短網(wǎng)址時,后端程序只要反解密的后面零寬字符,拿到相應(yīng)的網(wǎng)址,然后在做跳轉(zhuǎn)就可以到指定的網(wǎng)站。
反解密的原理可以參考上面隱形水印的代碼
日常開發(fā)過程中,我們有時需要從一些文件中讀取文本內(nèi)容,然后做相應(yīng)的處理。
有時候我們可能會碰到一些詭異的現(xiàn)象,比如我們之前碰到的例子。
后臺程序從 Excel 讀取文本內(nèi)容,然后程序中判斷是讀取的文本內(nèi)容是否與指定的字符串相等。
然后當(dāng)我們讀取一份 Excel 內(nèi)容后,返現(xiàn)這段比較邏輯怎么也通過不了。本來以為是 Excel 內(nèi)容存在空格什么的,但是打開 Excel 仔細(xì)一看,跟指定字符串一模一樣,并沒有什么其他字符。
第一次碰到這種例子,沒有什么經(jīng)驗(yàn),真的排查了很久,到最后都有點(diǎn)懷疑人生了。最后無意間將文本內(nèi)容復(fù)制到了 IDEA 中,才發(fā)現(xiàn)整理混雜著零寬字符!
如果各位小伙伴也碰到這類問題,不妨將復(fù)制文本內(nèi)容,然后到 IDEA 中查看是否存在某些看不見字符~
這兩個星期一直很忙,一直都在 9106 的節(jié)奏,真的是累,所以斷更了一周!
所幸最近項(xiàng)目提測,稍微輕松了一點(diǎn),能有點(diǎn)劃水時間來寫寫文章。不過再提起筆來寫文章,就有點(diǎn)斷節(jié)奏了!
這篇文章墨跡了很久才水出來,下周開始再次恢復(fù)周更的節(jié)奏,再忙再累,每周都來一篇。
歡迎各位小伙伴,每周來這里蹲我,Gank 我!!!
好了,我是樓下小黑哥,下周見!!!
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。