如果您想考虑unicode 组合字符(旨在修改其他字符的字符,例如重音´
+ e
= é
),Andrew Sellers对此要点有一个有趣的看法。
它首先列出所有组合变音符号 (CDM)的 Unicode 块范围(包含最常见组合字符的Unicode 块)
- 常客(继承),所以通常的◌̀ ◌́ ◌̂ ◌̃ ◌̄ ◌̅ ◌̆ ◌̇ ◌̈, ...;
- 扩展(包含德语方言中使用的变音符号 - Teuthonista)
- 补充(或乌拉尔音标、中世纪符号和德国方言——同样,Teuthonista)
- 用于符号(用于修改符号字符的箭头、圆点、外壳和覆盖)
- 半标记(用于跨越多个字符的变音符号部分,如此处所示)
var combining = &unicode.RangeTable{
R16: []unicode.Range16{
{0x0300, 0x036f, 1}, // combining diacritical marks
{0x1ab0, 0x1aff, 1}, // combining diacritical marks extended
{0x1dc0, 0x1dff, 1}, // combining diacritical marks supplement
{0x20d0, 0x20ff, 1}, // combining diacritical marks for symbols
{0xfe20, 0xfe2f, 1}, // combining half marks
},
}
然后,您可以在符文之后读取初始字符串:
sv := []rune(s)
但是如果你以相反的顺序这样做,你会首先遇到组合变音符号(CDM) ,而那些需要保持它们的顺序,不被颠倒
for ix := len(sv) - 1; ix >= 0; ix-- {
r := sv[ix]
if unicode.In(r, combining) {
cv = append(cv, r)
fmt.Printf("Detect combining diacritical mark ' %c'\n", r)
}
%c
(注意组合符文周围的空格:'%c'
没有空格意味着将标记与第一个'ͤ'
:而不是'ͤ'结合起来。我尝试使用CGJ Combining Grapheme Joiner \u034F
,但这不起作用)
如果最终遇到常规符文,则需要与这些 CDM 结合,然后将其添加到反向最终符文数组中。
} else {
rrv := make([]rune, 0, len(cv)+1)
rrv = append(rrv, r)
rrv = append(rrv, cv...)
fmt.Printf("regular mark '%c' (with '%d' combining diacritical marks '%s') => '%s'\n", r, len(cv), string(cv), string(rrv))
rv = append(rv, rrv...)
cv = make([]rune, 0)
}
更复杂的地方是表情符号,例如最近的修饰符,如中深肤色,菲茨帕特里克肤色等级中的第 5 类。
如果忽略,反向 '⚖️' 将给出 '️⚖',失去最后两个表情符号的肤色。
不要让我开始使用零宽度连接器( 200D
),它从 开始Wisdom/Awesome-Unicode
强制将相邻字符连接在一起(例如,阿拉伯字符或支持的表情符号)。它可以用来组成顺序组合的表情符号。
以下是组合表情符号的两个示例,其内部元素顺序在“反转”时应保持相同顺序:
单独是(从Unicode到代码点转换器):
这些应该保持完全相同的顺序。
“字符”“法官”(意为“法官”语义值的抽象概念)可以用几个字形或一个字形来表示。
⚖️实际上是一个组合字形(这里由两个表情符号组成),代表法官。该顺序不应颠倒。
下面的程序正确地检测到“零宽度连接器”并且不反转它组合的表情符号。
如果您检查该表情符号,您会发现它由以下部分组成:
同样,需要保留该序列顺序。
注意:实际评委表情 ⚖️</a> 使用的是MAN (1F468),而不是Adult (1F9D1)(加上上面列出的其他字符:深色皮肤、ZWJ、比例),因此表示为一个字形,而不是一簇字素。
含义:单个字形,“法官”的官方表情符号,需要将“man”与“scale”组合(产生一个字形⚖️)而不是“adult”+“scale”。
后者,“成人”+“比例”,仍然被认为是“一个字符”:你不能只选择比例,因为 ZWJ(零宽度连接器)。但是那个“字符”被表示为一个组合字形⚖️,两个字形,每个字形通过代码点+字体
具体书面表示一个对应的字形)
显然,使用第一个组合(“man”+“scale”)会产生更具表现力的角色 ⚖️。
见“文字表示的字素与抽象字符的关系”
字形和正字形字符是相当具体的对象,因为普通用户(非专家)熟悉它们,通常从他们第一次学习“ABC”(或从他们的写作中等效)开始就被教导使用它们系统,当然)。
然而,在信息系统领域,我们对字符有不同的理解:抽象字符是给定系统中文本表示的最小单位。
实际上,这些在两个重要的意义上是抽象的:
- 首先,这些抽象字符中的一些可能不对应于正字法中的任何具体字符,正如我们在上面的水平选项卡中看到的那样。
- 其次,具体的书写对象(字形和正字形)可以用一种以上的抽象字符表示,而不一定是一对一的,正如我们在上面“ô”表示的情况下看到的那样。通过一个序列<O, CIRCUMFLEX>。
然后:“从字形到代码点再到字形”:

- 字素是用户通常习惯于思考的单位。
- 然而,在计算机内部,过程是根据字符来完成的。
我们不会在字形和字形之间建立任何直接联系。
正如我们在这里定义的这两个概念一样,它们之间没有直接的联系。它们只能通过抽象字符间接关联。
这是一个要掌握的关键点:抽象字符是其他元素相关的共同元素。
Go Playground中的完整示例。
Reverse 'Hello, World' => 'dlroW ,olleH'
Reverse '⃠' => '⃠'
Reverse '⚖️' => '⚖️'
Reverse 'aͤoͧiͤ š́ž́ʟ́' => 'ʟ́ž́š́ iͤoͧaͤ'
Reverse 'H̙̖ell͔o̙̟͚͎̗̹̬ ̯W̖͝ǫ̬̞̜rḷ̦̣̪d̰̲̗͈' => 'd̰̲̗͈ḷ̦̣̪rǫ̬̞̜W̖͝ ̯o̙̟͚͎̗̹̬l͔leH̙̖'