#import "/template/template.typ": web-page-template
#import "/template/components.typ": note
#import "/template/lang.typ": arabic, devanagari

#import "/lib/glossary.typ": tr

#show: web-page-template

// ### Mapping and reordering
=== 映射和重排序

// The first thing the shaper does is map the Unicode characters in the input into a series of glyph IDs, internal to the font. (I call this resulting series the "glyph stream", but that's not a common expression. Shaper implementers may call it the *buffer*.) For some scripts, this mapping is easy. You simply uses the character map (`cmap` table) to turn Unicode characters into glyph IDs internal to the font. Most scripts, however, need a bit of help when moving from the Unicode world of characters to the OpenType world of glyphs. This “help” is the logic provided by complex shapers; there are a bunch of “complex shapers” as part of an OpenType shaping engine, each handling text in a different script or family of scripts.
#tr[shaping]工作的第一步是#tr[shaper]将输入的Unicode#tr[character]映射为在字体内部使用的#tr[glyph]ID序列。我通常会将这个序列称为“#tr[glyph]流”，但这并不是通用术语，#tr[shaper]的某些实现可能就称其为“缓冲区”。对于某些#tr[scripts]来说，这种映射非常简单，只需要直接使用#tr[character]映射表（`cmap` 表）即可。但绝大多数#tr[scripts]在这一步骤中都需要一些额外的帮助。这些“帮助”通常由支持复杂#tr[scripts]的#tr[shaper]提供。基于OpenType技术的#tr[shaping]引擎内部通常会有各种复杂#tr[scripts]#tr[shaper]，每个#tr[shaper]用于支持一种（或一个家族的）#tr[script]。

// So, for example, if your text is in Arabic, it will come in as a series of codepoints which don’t contain any “topographic” information: the string ججج is made up of the same Unicode code point three times (U+062C ARABIC LETTER JEEM). But it needs to come out as three different glyphs, one for “initial jeem”, one for “medial jeem” and one for “final jeem”. In this case, there’s a part of the shaping engine which specifically knows how to help process Arabic, and it goes through the Unicode input annotating it with what position the glyphs need to be in. It knows how Arabic “works”: it knows that if you have جاج (JEEM ALIF JEEM), the first JEEM goes in initial form because it links to the ALIF but the second JEEM stays how it is because the letter ALIF does not join to its left. After it has done this annotation, it will apply the rules you specify for initial form substitutions *only* to those parts of the glyph stream which are marked as being in initial form, and so on for medial, final and isolated forms.
假设输入文本是阿拉伯文，它首先会被转化为不包含任何#tr[typography]信息的一串#tr[codepoint]。举例来说，字符串 #arabic[ججج] 由同一个Unicode#tr[codepoint]（`U+062C ARABIC LETTER JEEM`）重复三次组成，但在后续流程中它们需要被映射为三个不同的#tr[glyph]。第一个#tr[codepoint]映射为“首部JEEM”，第二个则是“中部JEEM”，最后的是“尾部JEEM”。在此情境中，#tr[shaping]引擎需要知道如何处理阿拉伯文，它映射出的#tr[glyph]需要附带上其所处位置的信息。这也就是说，#tr[shaping]引擎了解有关阿拉伯文的书写规则。比如它知道在文本 #arabic[جاج]（JEEM ALIF JEEM）中，第一个JEEM因为和 ALIF 连接，所以它需要是首部形式。而第二个 JEEM 则不变形，因为 ALIF 不应继续向左连接。在#tr[shaping]引擎进行了类似这样的位置标注之后，代码中的只针对首部样式的#tr[substitution]规则才能只应用于对应位置的#tr[glyph]们。中部、尾部以及独立样式也同理。

// Other scripts require different kinds of help to move from the Unicode world to the OpenType world. The way that Unicode defines the encoding of scripts is sometimes a little bit different from the order that those scripts are written in. As a simple example, the Devanagari sequence कि (“ki”) is encoded with the consonant ka (क) first and then the vowel i (ि) second. But visually - when you type or print - the vowel needs to appear first. So the shaping engine has to again “help” the font by reordering the glyph stream: it puts any vowels which need to visually appear first - "pre-base vowels" - before the base consonant. This is just a convenience; it’s much easier for us as the engineer to handle the glyphs `iMatra-deva ka-deva` than it would be to be handed `ka-deva iMatra-deva` as a straight Unicode-to-glyph conversion, and then be left having to shuffle the glyphs around in your font’s OpenType rules.
其他#tr[scripts]各自需要不同类型的帮助才能顺利地从Unicode世界来到OpenType世界。Unicode定义#tr[encoding]的方式和有时会和#tr[script]本身的书写顺序不太一致。一个简单的例子是天城文中的ki，写作 #devanagari[कि] 。它被#tr[encoding]为辅音ka（#devanagari[क]）加上元音i（#devanagari[ि]）。但当你书写或打印这个#tr[character]时，在视觉上却是元音i先出现。这就又到了#tr[shaping]引擎施以援手的时候了，它会将#tr[glyph]流重新排序，将这种前置元音挪动到对应的基本辅音之前。这会使我们作为工程师的工作更加简单，毕竟比起处理 `ka-deva iMatra-deva` 这种从Unicode直接转换而来的形式，调整过的 `iMatra-deva ka-deva` 就不需要我们自己写OpenType规则来调换它们的位置了。

// Notice also that when I showed you the vowel i on its own like this - ि - it was displayed with a dotted circle. The vowel mark can’t normally appear on its own - it needs to be attached to some consonant - so I have typed something that is orthographically impossible. To denote the missing consonant and to try and display something sensible, the shaping engine has inserted the dotted circle; that’s another job of the complex shaper. It knows what is a valid syllable and what isn’t, and adds dotted circles to tell you when a syllable is broken. (So if you ever see a dotted circle in the printed world, the input text was wrong.)
请注意，当我像 #devanagari[ि] 这样单独展示元音 i 时，右边会有一个虚线描绘的圆。这表示这个元音不应该单独出现，它需要附加到某些辅音上。所以这种展示方式其实是构造了违反正字法的文本。为了让显示的内容有意义，提示这里缺少了辅音，#tr[shaping]引擎会插入一个虚线圆。这就是复杂#tr[scripts]#tr[shaper]的另一个工作内容了。它知道哪些音节是有效的，哪些是不完整的。所以只要你看到了这个虚线圆，就表示输入的文本是有问题的。
