> For the complete documentation index, see [llms.txt](https://overleaf-pro.ayaka.space/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://overleaf-pro.ayaka.space/latex/zh-tw/shen-ru-wen-zhang/10-an-overview-of-technologies-supporting-the-use-of-colour-emoji-fonts-in-latex.md).

# 支援在 LaTeX 中使用彩色表情符號字型的技術概述

## 簡介

本文概述了各種 [背景主題](#which-topics-do-we-cover) 與在 LaTeX 中使用 OpenType 彩色字型為彩色 emoji 排版相關的內容。我們嘗試提供廣泛的材料，以照顧各種不同的興趣與專業程度。為了讓本文保持可讀性，我們對某些主題的涵蓋省略了許多技術細節，但我們希望仍有足夠的內容，能引導你探索在 LaTeX 中為彩色 emoji 排版的相關內容。

**更新（2023 年 7 月）**: 本文最初於 2021 年 8 月發表，並於 2023 年 7 月修訂，以更新關於 [使用以 SVG 為基礎的 OpenType 彩色字型與 LuaHBTeX](#using-svg-based-opentype-color-fonts-with-luahbtex).

### 我們涵蓋哪些主題？

本文涵蓋以下一般主題：

* Unicode：將 emoji 編碼為字元，並將它們在文字處理與排版應用中的預期行為正式規範化的標準。
* OpenType 彩色字型：可在你的 LaTeX 文件中呈現 emoji 字元彩色外觀的專用字型。
* 文字排版：介紹排版複雜腳本語言與 emoji 的關鍵元件。
* HarfBuzz：LuaHBTeX 的組件，它能實現進階的多語言排版，並使用 OpenType 彩色字型在 LaTeX 中排版 emoji。
* 不同的 TeX 引擎：探討它們對 OpenType 彩色字型的支援，並選擇要使用哪一個 TeX 引擎。
* LuaHBTeX 的 HarfBuzz API：介紹「魔法」背後的 [文字排版](#the-concept-of-text-shaping) 在 LuaHBTeX 中。

### 為彩色 emoji 排版的三種方式

可使用 LaTeX 透過三種主要方法為彩色 emoji 排版：

1. 使用標準的 LaTeX 圖形工具，例如 TikZ、MetaPost 或 Asymptote 來繪製 emoji。
2. 使用預先準備好的 emoji 圖形，並將其儲存在外部檔案中插入 emoji。
3. 將 emoji 視為 Unicode 編碼文字，並使用 [文字排版](#the-concept-of-text-shaping) 與 [OpenType 彩色字型](#opentype-color-fonts) 來為它們排版。

在你的 LaTeX 文件中加入彩色 emoji 的實際選項，取決於用來編譯該文件的 TeX 引擎；也就是說，你是否使用：

* pdfLaTeX：pdfTeX 引擎 + LaTeX；
* XeLaTeX：XeTeX 引擎 + LaTeX；
* LuaLaTeX：LuaHBTeX 引擎（自 TeX Live 2020 起）+ LaTeX。

這三種 TeX 引擎都可以使用 LaTeX 工具或套件來繪製 emoji，或使用 `\includegraphics{...}` 來插入儲存在外部圖形檔案中的 emoji。當你需要一個不依賴於用來編譯 LaTeX 文件之 TeX 引擎的解決方案時，繪製或匯入圖形是為 emoji 排版的理想技術。

不過，如果你的工作流程允許你選擇特定的 TeX 引擎，而且你偏好使用 OpenType 彩色字型與基於 Unicode 的文字處理，那麼最新版的 LuaTeX，也就是 LuaHBTeX，就是你所需要的。從 TeX Live 2020 開始，LuaHBTeX 用於編譯基於 LuaLaTeX 格式的 LaTeX 文件。

## Unicode 與 emoji 字元的背景

### 字元編碼

電腦會使用一連串數值（整數）來儲存、傳輸與處理文字，這些數值代表文字的構成 *字元*。可靠的文字處理需要文字的產生者與使用者就該使用哪些整數值來代表文字流中的各個字元達成一致。換句話說，那段文字的 *字元* *編碼是什麼？* 編碼是一組約定好的整數值，用來代表某一組特定字元：在所使用的編碼中，每個字元都由一個整數值來表示。

### Unicode 登場

歷史上，在 8 位元文字的時代，使用了許多不同的字元編碼，總是帶來 *編碼不一致*：文字的產生者與使用者誤以為彼此使用的是不同編碼，因而導致文字處理錯誤。任何與 TeX/LaTeX 打交道多年的人，都可能遇過輸入文字與用於排版文件的字型之間的編碼不一致。如果文件字型設定所使用的編碼與文字的編碼不同，那麼產生的排版 PDF 中很可能會出現遺漏或錯誤的字元。

這些歷史性的編碼問題可以透過一項為世界所有字元編碼的國際標準來解決：Unicode。Unicode 標準並非靜態，而是會定期更新，以將更多字元與腳本（書寫系統）納入其編碼方案中。還有一個 [正式的新字元提案審查流程](http://www.unicode.org/pending/proposals.html) 以及一套特定的 [新 emoji 字元方案](https://www.unicode.org/emoji/proposals.html).

### Unicode 有多少字元？

Unicode 理論上最多可編碼 1,114,112 個字元。這 1,114,112 個整數值中的每一個都稱為一個 *碼位*：用來識別每個字元所分配的整數值。然而，基於各種技術原因，只有 [1,112,064 個碼位](https://en.wikipedia.org/wiki/Unicode#Architecture_and_terminology) 可以分配給實際字元：有 2048 個碼位不可分配，且禁止在符合 Unicode 的文字中使用。

撰寫本文時（本文第一版），Unicode 標準第 13 版共將 143,859 個碼位分配給實際字元，包括 [3304 個現已編碼為 emoji 的字元](https://www.unicode.org/L2/L2020/20114r-family-emoji-explor.pdf) （見該文件第 2 頁）。Unicode 所編碼字元數量的成長，可在文章 [Unicode 有多少字元？](https://www.babelstone.co.uk/Unicode/HowMany.html) 以及 [維基百科條目](https://en.wikipedia.org/wiki/Unicode#Versions).

### Unicode 平面

全部 1,114,112 個 Unicode 碼位被分組為 17 個所謂的平面：從平面 0 到平面 16，每個平面包含 65536 個碼位值，共計 $$17\times2^{16} = 1,114,112$$ 個字元。平面 0，稱為 [基本多文種平面](https://en.wikipedia.org/wiki/Plane_\(Unicode\)#Basic_Multilingual_Plane)，編碼最常用的字元。平面 1–16 稱為 [補充平面](http://unicode.org/glossary/#supplementary_planes).

### emoji 的興起

新的字元會隨著人類溝通方式的改變而出現，而行動電話技術催生了這類字元之一：emoji，它在 1990 年代後期於日本演化而來。難怪 [Unicode Emoji 常見問題](https://unicode.org/faq/emoji_dingbats.html) 指出

> 「emoji 一詞來自日文 [絵](http://www.unicode.org/cgi-bin/GetUnihanData.pl?codepoint=%E7%B5%B5) （e ≅ 圖畫）+ [文字](http://www.unicode.org/cgi-bin/GetUnihanData.pl?codepoint=%E6%96%87) （moji ≅ 書寫字元）。」

對 emoji 的背景與歷史發展有興趣的讀者，可能會對這篇 [Unicode 簡介](https://unicode.org/reports/tr51/#Introduction) 或這篇文章 [I second that emoji: emoji 的標準、結構與社會生產](https://firstmonday.org/ojs/index.php/fm/article/view/9381).

直到 2010 年，隨著 [Unicode 標準第 6.0 版的發布](https://www.unicode.org/versions/Unicode6.0.0/)，許多 emoji 才被正式視為 *字元* 獨立存在的字元。Unicode 13.0 將 [3304 個字元編碼為 emoji](https://www.unicode.org/L2/L2020/20114r-family-emoji-explor.pdf) （見該文件第 2 頁），而 Unicode 13.1 則列出 [列出 3521 個 emoji](https://unicode.org/emoji/charts/emoji-counts.html).

### emoji 存在於更高的平面

Unicode 將許多 emoji 字元分配到基本多文種平面（BMP）之外的碼位，編碼 [在平面 1 中](https://en.wikibooks.org/wiki/Unicode/Character_reference/1F000-1FFFF) 其碼位範圍為 1F000–1FFFF——這對任何希望 *複製並貼上* 將 emoji 字元貼入 Overleaf 編輯器（Code Editor 或 Visual Editor）的人來說，會產生重要影響。Overleaf 的文字編輯器目前只能處理基本多文種平面內的字元，雖然我們希望未來的升級能加入對非 BMP 字元的支援。請注意，這項限制只影響那些貼入並將透過 Overleaf 編輯器編輯之檔案中的非 BMP 字元。還有其他方式可以存取 emoji 字元：

* 使用原始命令 `\char"<code point>` 或 `\Uchar"<code point>` （見 [本節](#optional-detail-luatexluahbtex-char-vs-uchar) 的內容）。
* 使用包含 emoji 字元、以 UTF-8 格式儲存的輸入文字檔。
* 使用會插入 emoji 字元的 LaTeX 命令（巨集）。

#### 將 emoji 與其他非 BMP 字元貼入 Overleaf

如果你將一個 emoji 字元，例如 😀，貼入 Overleaf Code Editor，目前它會被轉換成字元 ��。

![由於將非 BMP 字元複製 + 貼上到 Overleaf 編輯器而產生的錯誤](/files/fc976f4d61a9033c0684ce36bf8bbafc42c8ce68)

� 字元的 Unicode 碼位是 FFFD，其正式名稱為「替代字元」，用於「[取代未知、無法辨識或無法表示的字元](https://en.wikipedia.org/wiki/Specials_\(Unicode_block\))」。

### 在 LuaLaTeX 中使用 Unicode 碼位（U+）

Unicode 文件以以下記法表示碼位值： `U+<十六進位值>`——例如 `U+1F600`，其中 `1F600` 是 `<十六進位值>` 這個 😀 emoji 字元的 Unicode 碼位的十六進位值。若要在 LuaLaTeX 中使用這些碼位值，你要刪去 `U+` 並寫成 `\char"<十六進位值>` 或 `\Uchar"<十六進位值>`。 `"` 字元告訴 TeX 引擎所提供的數字是以十六進位表示。例如，若要使用 😀 emoji，你會寫： `\char"1F600` 或 `\Uchar"1F600`——並使用一種能夠為其排版的字型。

使用 `\char` 和 `\Uchar` 來為 😀 emoji 字元排版的 LuaLaTeX 最小範例可能如下：

```latex
\documentclass{article}
\usepackage{fontspec}
\begin{document}
\newfontfamily\emojifont[Renderer=Harfbuzz,SizeFeatures={Size=20}]{NotoColorEmoji.ttf}
% 在群組中使用 \emojifont，以將其效果限制在局部
{\emojifont
\Uchar"1F600
\char"1F600}
\end{document}
```

[在 Overleaf 中開啟此 LuaLaTeX 範例](https://www.overleaf.com/docs?engine=lualatex\&snip_name=Test+using+LuaLaTeX\&snip=%5Cdocumentclass%7Barticle%7D%0A%5Cusepackage%7Bfontspec%7D%0A%5Cbegin%7Bdocument%7D%0A%5Cnewfontfamily%5Cemojifont%5BRenderer%3DHarfbuzz%5D%7BNotoColorEmoji.ttf%7D%0A%25Use+%5Cemojifont+in+a+group+to+keep+its+effects+local%0A%7B%5Cemojifont+%0A%5CUchar%221F600%0A%5Cchar%221F600%7D%0A%5Cend%7Bdocument%7D)

**（可選細節）LuaTeX/LuaHBTeX：\char 與 \Uchar**

除了傳統的 `\char<字元碼>` 用於為特定 `<字元碼>`、並使用目前字型進行排版的 \char 指令外，LuaTeX、LuaHBTeX 與 XeTeX 引擎也提供 `\Uchar<字元碼>` 從使用者的角度來看， `\char` 和 `\Uchar` 看起來一樣，但這些命令的運作方式有細微差異，如下所述。

**關鍵差異：展開**

`\Uchar` 是一種所謂的 [可展開命令](/latex/zh-tw/shen-ru-wen-zhang/22-how-does-expandafter-work-the-meaning-of-expansion.md#expansion-a-general-term-for-a-set-of-operations) 而 `\char` 則不可展開。當某個 `\char<字元碼>` 或 `\Uchar<字元碼>` 命令正在「執行」時——也就是說，該命令並未作為巨集或其他記號列表的一部分被儲存——TeX 引擎內會發生以下動作：

* **`\char<字元碼>`** 會指示 TeX 引擎立即插入一個字元記號，代表 `<字元碼>`，進入其目前正在排版的任何內容。
* 相較之下， **`\Uchar<字元碼>`** 具有兩個不同的處理步驟：

1. 該 `\Uchar<字元碼>` 命令會 *展開*，且 `<字元碼>` 會被轉換成一個臨時記號列表，其中包含一個單一的 [字元 token](/latex/zh-tw/shen-ru-wen-zhang/19-how-does-expandafter-work-an-introduction-to-tex-tokens.md#tex-tokens-101-28and-notions-of-expansion29) ，其代表 `<字元碼>`.
2. 現在這個單字元記號列表已 *可供使用* 作為 TeX 引擎下一個輸入來源。實際上，TeX 引擎會「暫時轉移視線」，將這個單一記號列表用作其下一個輸入項目（記號）的位置。預設情況下，TeX 引擎會簡單地回頭讀取（輸入）該記號並排版相應的字元，重現 `\char` 指令。 **然而**，因為那個 `<字元碼>` 並未立即排版，而是暫時 *儲存在* （儲存）為單一記號，所以原始 TeX 命令或 LaTeX 巨集可以利用（吸收）該記號——它不必立即排版，而是可以在後續處理中按需要使用。

實際上， `\char<字元碼>` 表示「把這個 `<字元碼>` 現在就排版」，而 `\Uchar<字元碼>` 則透過建立一個儲存的字元記號並將其作為下一個輸入項目（記號）提供，具有某種「延遲動作」的形式。該記號可以被 TeX 命令與巨集使用（吸收），也可以被 TeX 引擎重新讀取並排版。

### Unicode（編碼）並非全部故事

能夠在 Unicode 編碼文字中使用 emoji 字元，只是 emoji 成功故事的一部分。emoji 使用量的激增，也得益於 [OpenType 字型技術](https://learn.microsoft.com/en-us/typography/opentype/)——其字形資料（字元設計）可以包含 [彩色資料](https://learn.microsoft.com/en-us/typography/opentype/spec/colr)：所謂的 [OpenType 彩色字型](#opentype-color-fonts).

除了合適的字型之外，使用彩色 emoji 還需要額外的軟體組件，其工作包括：

* 預處理（「[排版](#the-concept-of-text-shaping)」）Unicode 編碼文字， *準備* 以便使用特定字型顯示它；
* *將* 字型的彩色 emoji *字形* 渲染並顯示到裝置螢幕上。

#### 字形 vs. 字元：它們不是同一回事嗎？

「字形」與「字元」這兩個詞常被當作可互換使用——彷彿指的是同一個基本概念——但它們在含義上其實有一個細微卻重要的差別。

Unicode [將「字元」定義為](http://www.unicode.org/glossary/#character) ：

> 「具有語意價值的書寫語言最小組成單位；指的是抽象的意義和／或形狀，而非特定形狀……」

相較之下，「字形」是 *特定的* *字形* （設計），用於 *視覺呈現* 某個特定 *字元*.

當在各種軟體系統／平台上檢視含有大量 emoji 的文字時，字元與字形之間的問題很容易被看出，例如在你的 iOS 或 Android 手機，或 Windows 桌上型電腦上閱讀相同文字。無論使用哪種裝置或平台，底層文字（字元序列）都會包含相同的 Unicode 編碼 *emoji* *字元*。真正造成差異的是裝置特定的能力，涉及 *預處理* 該文字之後的 *渲染* 和 *顯示* 與結果顯示，可能會使用裝置專用字型，從而產生不同的字形（字元設計）來代表相同的 emoji 字元。

Unicode 的 [完整 Emoji 清單](https://unicode.org/emoji/charts/full-emoji-list.html) 提供代表每個 Unicode emoji 字元的範例圖像——展示不同技術供應商所使用的各種字形。不僅字型設計者會採用各自特定的設計（字形）來代表 emoji 字元，個別字型所支援的 emoji 字元數量（包含哪些字形）也不盡相同，而且可能包含或不包含 Unicode emoji 規範中所納入的較進階 emoji 文字處理功能。

字元的概念，以及其語意與編碼，構成了 Unicode 世界的基礎：Unicode 處理的是字元。個別字元作為字形的設計與視覺呈現，則屬於字型技術與字型設計工藝的範疇。

#### Unicode emoji：遠不只是文字編碼

Unicode 的核心角色是提供一個全球性的編碼標準，定義應使用哪一個整數值，也就是所謂的 *碼位，* 來代表 Unicode 編碼文字流中的每個字元，包括 emoji。

Unicode 對 emoji 的規範也定義了 *處理行為* 針對某些 *序列* 出現在 Unicode 編碼文字流中的 emoji 字元。透過一種稱為 [文字排版](#the-concept-of-text-shaping) 的程序，已定義的 emoji 字元序列可以被「合併」以產生單一的最終（「複合」）emoji 字形——該單一字形將由裝置的作業系統用來代表文字中原本存在的字元序列。

Unicode 關於 [Unicode Emoji](https://unicode.org/reports/tr51/) 的技術報告，記錄了軟體若想提供符合 Unicode 的 emoji 字元處理時可用的豐富功能集合。舉例來說，Unicode 定義（編碼）了一種稱為 [emoji 修飾符](http://www.unicode.org/reports/tr51/#Emoji_Modifiers_Table) 的字元，它們可用來產生 *變體* ，讓「基礎」emoji 字元出現變化，例如 [基於 Fitzpatrick 量表的膚色](http://www.unicode.org/reports/tr51/#Diversity)。請注意，基礎 emoji 字元集合與適用的修飾符，都是整體 [Unicode emoji 標準](http://www.unicode.org/reports/tr51).

Unicode 頁面 [Emoji 序列](http://unicode.org/emoji/charts/emoji-sequences.html) 提供目前 Unicode 規範所提供之序列的圖表。將滑鼠游標移到任何 emoji 字形圖像上，即可看到一個小型彈出提示，告訴你產生該字形的底層 Unicode emoji 字元序列：

![EmojiSequenceChart.png](/files/b5b28312b983b6d01227cf6086668d8676e169c0)

例如，這個 emoji 字形：

![HandMediumSkinTone.png](/files/4f4413b00c2b0f7da56dfbc6c78d3686572dd9a4)

列在 [修飾符序列區段](http://unicode.org/emoji/charts/emoji-sequences.html#modifier_sequences) ，並由兩字元序列 U+1F44B U+1F3FD 產生。這些組成字元是：

U+1F44B：![UnicodeWavingHandDefault.png](/files/0434c34324bfb5eb82a65878895c78c7ec67bbd3) （揮手）

U+1F3FD：![FitzPatrick3.png](/files/91c188a8f63452958b8091e4e2dbe40203da1c92) （EMOJI 修飾符 Fitzpatrick 第 4 型）

**在 LuaHBTeX 中使用膚色修飾符**

以下範例使用 LuaHBTeX 來示範 emoji 修飾符的使用：

```latex
\documentclass{article}
\usepackage{fontspec}
\begin{document}
\newfontfamily\emojifont[Renderer=HarfBuzz,SizeFeatures={Size=20}]{NotoColorEmoji.ttf}
獨立的揮手：{\emojifont\Uchar"1F44B}\par
獨立的修飾符：{\emojifont\Uchar"1F3FD}\par
組合結果：{\emojifont\Uchar"1F44B\Uchar"1F3FD}
\end{document}
```

[在 Overleaf 中開啟此 LuaLaTeX emoji 修飾符範例](https://www.overleaf.com/docs?engine=lualatex\&snip_name=Emoji+modifiers+using+LuaLaTeX\&snip=%5Cdocumentclass%7Barticle%7D%0A%5Cusepackage%7Bfontspec%7D%0A%5Cbegin%7Bdocument%7D%0A%5Cnewfontfamily%5Cemojifont%5BRenderer%3DHarfBuzz%2CSizeFeatures%3D%7BSize%3D20%7D%5D%7BNotoColorEmoji.ttf%7D%0AIsolated+waving+hand%3A+%7B%5Cemojifont%5CUchar%221F44B%7D%5Cpar%0AIsolated+modifier%3A+%7B%5Cemojifont%5CUchar%221F3FD%7D%5Cpar+%0ACombined+result%3A+%7B%5Cemojifont%5CUchar%221F44B%5CUchar%221F3FD%7D%0A%5Cend%7Bdocument%7D)

此範例會產生以下輸出：

![ModifiersInLuaHBTeX.png](/files/d2e2472ddd36501d7aaa576ca77e89b860ccaac8)

#### UTF-8：它在儲存 Unicode 文字中的角色

你在 Overleaf 的 Code Editor（或 Visual editor）中輸入或貼上的任何文字或程式碼，都會以 UTF-8 格式儲存，因此我們將簡要回顧 UTF-8 究竟是什麼意思。UTF 代表 Unicode Transformation Format，而 UTF-8 在儲存或傳輸 Unicode 編碼文字中的角色，可由「轉換 *格式*」。

Unicode 的碼位值範圍從 0 到最大 1,114,111，因此不可能只用一個 8 位元位元組來表示所有 Unicode 字元值，因為它最多只能儲存 256 個不同的值：0 到 255。然而，可以使用一個 *連續序列* 由位元組大小的值組成的序列來表示任何 Unicode 碼位整數——這就是 UTF-8 背後的原理。

UFT-8 提供一個「配方」來 *轉換* （也就是「編碼」或「轉換」）將 Unicode 整數碼位值轉換為由 1 到 4 個連續位元組大小整數所組成的唯一序列：所需的連續位元組數量取決於該碼位整數的值。因此，你可能會看到 UTF-8 將 Unicode 字元儲存為 *多位元組序列* ，因為單一 Unicode 字元（碼位整數）在 UTF-8 中是以 1 到 4 個連續位元組的序列來表示。

當然，以 UTF-8 儲存的文字可以轉回其原本的 Unicode 碼位整數值序列——這正是 XeTeX 或 LuaTeX/LuaHBTeX 在讀取以 UTF-8 格式儲存的 LaTeX 輸入檔時必須做的事。這些 TeX 引擎需要先知道輸入的 Unicode 碼位（字元）值，才能排版文字。請注意，pdfTeX 沒有內建的 UTF-8 解碼能力，因此必須仰賴 TeX 巨集來處理（解碼）以 UTF-8 格式編碼的輸入文字。

**一些 UTF-8 範例**

* 阿拉伯字元 ش（「sheen」）的 Unicode 碼位為十六進位 0634，或十進位 1588。在 UTF-8 中，ش 以 2 個（十六進位）值 D8 和 B4 表示，因此字元 ش 會作為兩個連續位元組 D8B4 儲存在 UTF-8 編碼文字中。
* emoji 字元 😀 的 Unicode 碼位為十六進位 1F600，或十進位 128512。在 UTF-8 中，😀 以 4 個（十六進位）值 F0、9F、98 和 80 表示，因此字元 😀 會作為 4 個連續位元組 F09F9880 儲存在 UTF-8 文字檔中。

#### Unicode 基礎 emoji 文字處理中使用的特殊字元

並非每個在 Unicode 中編碼的字元都打算透過字型中的字形來進行視覺呈現：有些已編碼字元被指定為 *不可列印字元* ，其用途是協助專門的文字處理功能（在支援該功能的軟體中）。不同軟體應用程式對 Unicode 中編碼的不可列印字元提供的支援程度不一，因此結果會取決於所使用的軟體環境——應用程式與字型。

**兩個值得認識的不可列印字元**

* **零寬連接符（ZWJ）**，碼位 200D（十六進位），顧名思義，是用來觸發輸入字元的「連接行為」——但前提是這些輸入字元 *具有* 已定義的連接行為。
* **零寬不連接符（ZWNJ）**，碼位 200C（十六進位），是用來 *防止* 抑制輸入字元原本可能表現出的「連接行為」。例如，你可以使用 ZWNJ 來防止連續阿拉伯字元的連接行為；這些字元通常會被處理（排版）成其連接形式。

Unicode 已發布一份 [建議的 Emoji ZWJ 序列](https://unicode.org/emoji/charts/emoji-zwj-sequences.html) ，其中使用 U+200D 零寬連接符（ZWJ）將 emoji 字元序列組合成單一複合 emoji 字形——前提是所使用的字型中有提供該字形。

**零寬不連接符的使用範例**

以下最小程式片段使用 TeX Live 內附的 Scheherazade OpenType 字型，來定義一個名為 `\arabicfont` 的 LaTeX 字型，我們可用它來為一些阿拉伯文排版。這一行

```latex
{\arabicfont Non-joining:\textdir TRT\Uchar"0644\Uchar"200C\Uchar"0627}
```

透過 `\Uchar"200C`，使用零寬不連接符字元來阻止兩個阿拉伯字母 ل（lam）和 ا（alef）的正常連接行為。請注意 `\textdir TRT` 用來將文字方向設定為由右至左：

```latex
\documentclass{article}
\usepackage{fontspec}
\begin{document}
\newfontfamily\arabicfont[Script=Arabic,Renderer=Harfbuzz,SizeFeatures={Size=40}]{Scheherazade}
{\arabicfont 連接：\textdir TRT\Uchar"0644\Uchar"0627}\par
{\arabicfont Non-joining:\textdir TRT\Uchar"0644\Uchar"200C\Uchar"0627}
\end{document}
```

[在 Overleaf 中開啟此 LuaLaTeX 範例](https://www.overleaf.com/docs?engine=lualatex\&snip_name=Zero+width+non-joiner+using+LuaLaTeX\&snip=%5Cdocumentclass%7Barticle%7D%0A%5Cusepackage%7Bfontspec%7D%0A%5Cbegin%7Bdocument%7D%0A%5Cnewfontfamily%5Carabicfont%5BScript%3DArabic%2CRenderer%3DHarfbuzz%2CSizeFeatures%3D%7BSize%3D40%7D%5D%7BScheherazade%7D%0A%7B%5Carabicfont+Joining%3A%5Ctextdir+TRT%5CUchar%220644%5CUchar%220627%7D%5Cpar%0A%7B%5Carabicfont+Non-joining%3A%5Ctextdir+TRT%5CUchar%220644%5CUchar%22200C%5CUchar%220627%7D%0A%5Cend%7Bdocument%7D)

此範例會產生以下輸出：

![NonJoiner.png](/files/ee8c2bdf269965cd1b1675f79859313005541354)

## 「文字排版」的概念

讓我們從一個視覺範例開始，使用「educational」一詞的烏爾都語翻譯。烏爾都語譯文的文字可能是透過鍵盤或觸控裝置輸入，並會以 Unicode 阿拉伯字元的簡單線性序列建立。然而，當該文字被排版，或顯示在裝置螢幕上的 [納斯塔利克風格](https://en.wikipedia.org/wiki/Nastaliq)時，結果會是一種複雜的二維字形排列。

以我們的烏爾都語範例來說，下圖比較了 Unicode 阿拉伯文字的線性輸入 *字元* 與以納斯塔利克風格排版的輸出，其包含一個二維排列的 *字形* 存在於（免費）字型中的 [Awami Nastaliq](https://software.sil.org/awami/download/):

![](/files/e8a5c1f9743457390708778957520cf2f87e581f)

將輸入字元「轉譯」成一組位置正確的輸出字形的過程稱為 *文字排版*，並且是顯示或排版文字之前處理文字的重要組成部分。我們的範例使用烏爾都語（阿拉伯字母）文字，因為其排版結果非常明顯；相較之下，使用拉丁字母的語言，例如英文，排版效果就不那麼顯著——例如只產生簡單連字。

在使用如下這類腳本（書寫系統）時，文字排版至關重要： [阿拉伯文](https://en.wikipedia.org/wiki/Arabic), [希伯來語](https://en.wikipedia.org/wiki/Hebrew_language), [天城文](https://en.wikipedia.org/wiki/Devanagari) 或 [馬拉雅拉姆語](https://en.wikipedia.org/wiki/Malayalam)，這只是所謂 *複雜腳本*的四個範例。為了確保這些腳本及其所用語言中的文字能正確呈現，排版過程需要仔細處理特定腳本與語言組合中的任何排版規則與細微差異。例如，有些語言需要多個輸入字元才能產生特定的輸出字形，或者在準確放置變音符號與字形之間的重新排列方面有複雜要求，以確保各個字形彼此之間的位置正確。

一般而言，為一段文字進行排版需要若干資訊：

* 書寫系統或 *書寫系統* 文字所使用的語言。
* 特定的 *語言* 所使用的。個別文字腳本可用於多種語言，而每一種腳本—語言組合都有其各自的塑形細節／細微差異。
* 書寫 *方向* —例如由右至左或由左至右。
* 一個 *字型* 它提供用來表示塑形後文字所需的字形，並且（可選地）包含額外的「塑形規則」，用以引導文字塑形過程。

文字塑形的需求，尤其是對於複雜文字腳本及其相關語言而言，可能極為細緻且富有層次，這表示需要能套用可能非常複雜的文字塑形「規則」的專用軟體。不令人意外地，這類軟體確實存在，並被稱為 *文字塑形引擎*；我們要討論的那一個稱為 [Harfbuzz](https://en.wikipedia.org/wiki/HarfBuzz)，其文件值得一讀——例如 [為什麼我需要塑形引擎？](https://harfbuzz.github.io/why-do-i-need-a-shaping-engine.html).

**關於文字塑形的延伸閱讀**

強烈推薦以下這些簡短導讀：

* [什麼是文字塑形？](https://harfbuzz.github.io/what-is-harfbuzz.html#what-is-text-shaping)
* [為什麼我需要塑形引擎？](https://harfbuzz.github.io/why-do-i-need-a-shaping-engine.html)

**TeX 技術註記：多種塑形技術（模型）**

HarfBuzz 文字塑形引擎支援多種「塑形技術」，它們在實作塑形過程的方式上各不相同——每一種實作都稱為一個 *塑形器*，包括在 `luaotfload` 文件中。本文章的主要焦點是 OpenType 塑形，但另一種可免費使用的技術是 [Graphite](https://scripts.sil.org/cms/scripts/page.php?site_id=projects\&item_id=graphite_aboutOT)，由 [SIL International](https://www.sil.org/)。 [Apple Advanced Typography（AAT）](https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6AATIntro.html)—支援 AAT 的字型通常用於 Apple 技術平台。

**使用 Graphite 塑形器的範例**

以下範例使用一款名為 [Awami Nastaliq](https://software.sil.org/awami/download/)的字型來排印一些烏都文，該字型支援 Graphite 塑形，並可在 Overleaf 上使用。Awami Nastaliq 由 [SIL International](https://www.sil.org/)創建，該組織負責開發 Graphite 技術。

以下範例展示了基於 Graphite 的字型所具備的進階塑形能力——請注意 `luaotfload` 字型宣告如何使用 `shaper=graphite2`.

```latex
\documentclass{article}
\usepackage{luaotfload}
\begin{document}

\font\urdutest={file:AwamiNastaliq-Regular.ttf:mode=harf;shaper=graphite2} at 100bp
% 技術
\pardir TRT\textdir TRT \urdutest ٹیکنالوجی

\vskip 75bp

% 教育
\pardir TRT\textdir TRT \urdutest تعلیمی
\end{document}
```

[在 Overleaf 中開啟此範例。](https://www.overleaf.com/docs?engine=lualatex\&snip_name=Typesetting+Urdu+using+the+Graphite+shaper\&snip=%5Cdocumentclass%7Barticle%7D%0A%5Cusepackage%7Bluaotfload%7D%0A%5Cbegin%7Bdocument%7D%0A%0A%5Cfont%5Curdutest%3D%7Bfile%3AAwamiNastaliq-Regular.ttf%3Amode%3Dharf%3Bshaper%3Dgraphite2%7D+at+100bp%0A%25+Technology%0A%5Cpardir+TRT%5Ctextdir+TRT+%5Curdutest+%D9%B9%DB%8C%DA%A9%D9%86%D8%A7%D9%84%D9%88%D8%AC%DB%8C%0A%0A%5Cvskip+75bp%0A%0A%25+Educational%0A%5Cpardir+TRT%5Ctextdir+TRT+%5Curdutest+%D8%AA%D8%B9%D9%84%DB%8C%D9%85%DB%8C%0A%5Cend%7Bdocument%7D)

此範例會產生以下輸出：

![](/files/9e3d60b786805b5491425dc05b2137ca14a6a79b)

#### 表情符號與文字塑形

文字塑形已透過複雜文字腳本語言烏都文的範例加以介紹。然而，得知正確渲染 emoji 字形需要對包含 emoji 字元序列的 Unicode 文字套用文字塑形，可能會令人驚訝——[正如 HarfBuzz 的首席開發者所指出的](https://github.com/harfbuzz/harfbuzz/issues/2428#issuecomment-639108677):

> ...用 HarfBuzz 來塑形 emoji 完全在處理範圍內，而且要得到家庭 emoji、膚色等效果，這實際上是必要的。

我們將查看相關範例。

### 責任分工：文字塑形引擎 + OpenType 字型

實務上，文字塑形是一項「聯合作業」，也就是在文字塑形引擎內建的邏輯與規則，和所使用字型中內建的額外塑形規則與資料之間的分工——從現在起，我們只討論基於 OpenType 的塑形 *而已*.

要進行塑形時，文字塑形引擎通常會接收一些 Unicode 文字、指定的腳本與語言、可能還有書寫方向，以及最重要的、在塑形過程中使用的 OpenType 字型——字型將提供輸出：一組字形與定位資料。若有要求，塑形引擎可以套用額外規則（[OpenType 功能](https://en.wikipedia.org/wiki/List_of_typographic_features#OpenType_typographic_features)）這些規則包含在所使用的 OpenType 字型中——要套用哪些規則，通常可由使用者從該字型支援的功能清單中選擇。

塑形過程的結果是一個 *字形清單* ，其內容來自 OpenType 字型，並搭配 *字形間* 定位資料。該定位資料與 *塑形後字形的相對擺放*有關；它並不是指在排版頁面或其他媒體／內容（如網頁、推文等）中的絕對位置。繪製軟體（排版引擎、網頁瀏覽器等）會使用字形間的定位資訊，以確保字形在組合並納入最終輸出後，彼此之間能正確定位。

#### 什麼是字形清單？

在內部，OpenType 字型中的每個字形都會被指派一個數值識別碼，也就是一個整數值，稱為字形索引——也稱為字形識別碼或 GID。完成塑形工作後，文字塑形引擎會將結果回傳為一個 *字形識別碼清單* 加上 *定位資料* ，供那些字形使用。

OpenType 字型中的個別字形由字型建立者指派索引（識別碼），因此這是一個高度依字型而定且帶有任意性的值——在某一特定字型的不同版本之間也可能有所差異。你絕不要假設在不同字型中，外觀「相似」的字形會使用相同的 GID 值；幾乎肯定不會。若你擁有由塑形引擎提供的字形識別碼清單，你只能用它們來存取取得它們的那個字型中的字形。

#### 什麼是 OpenType 字型？

網路上 *充斥著* OpenType 字型的說明與細節，因此我們只作簡短介紹。該 [OpenType 規格](https://docs.microsoft.com/en-us/typography/opentype/spec/) 是一份為開發者設計的複雜文件，但本質上，它定義了一種檔案格式，或說容器，用來存放字型資料。OpenType 字型包含描述字形輪廓形狀的資料，以及其支援的腳本與語言資訊、關於字型的中繼資料，還有各種定義 [排印功能](https://en.wikipedia.org/wiki/List_of_typographic_features#OpenType_typographic_features) 的表格。

文字塑形引擎通常可以被指示在塑形過程中選擇性套用（使用）字型的功能，藉此套用特定的排版效果（「規則」），從而選出字型中適當的一組字形。所選字型必須支援並提供文字塑形引擎被要求套用的任何功能所需的字形。

#### 已編碼與未編碼的「字形」

OpenType 字型包含一個稱為 [cmap](https://docs.microsoft.com/en-us/typography/opentype/spec/cmap) （Character to Glyph Index Mapping，字元到字形索引對應），它會將字型所支援的 Unicode 字元集合對應到該字型中相應的字形索引。以下影片可讓你簡略一窺名為 `lmmono10-regiular.otf` （包含於 TeX Live 中）。

{% embed url="<https://videos.ctfassets.net/nrgyaltdicpt/2537Y9gOUMWgd0t1guqt0X/482c53a9d8112ecae3d622aa7e00eef8/openType_cmap.mp4>" %}

然而，字型通常包含許多不代表任何特定 Unicode 字元、也未包含在該 cmap 表中的字形。因此，OpenType 字型中的字形集合可分為兩大類：

* 代表 Unicode 字元的已編碼字形；
* 不代表 Unicode 字元的未編碼字形。

已編碼字形可透過在文字中加入適當的 Unicode 字元來存取——那麼未編碼字形呢？它們如何使用／存取？這些字形通常用來提供文字塑形作業的輸出，包括套用字型功能以產生特定視覺／排印效果。

### OpenType 彩色字型

emoji 字元預期應以全彩顯示／渲染——黑白 emoji 多少無法提供「完整的 emoji 體驗」。然而，在 Unicode 最初編碼 emoji 的時候， [OpenType 字型規格](https://docs.microsoft.com/en-us/typography/opentype/spec/) 並沒有任何適合用來嵌入 *colorful*字形資料到 OpenType 字型中的機制。OpenType 的這個「缺口」促使主要技術／平台供應商尋找解決方案，而隨後的「競賽」帶來了 [各種擴充 OpenType 的提案](https://www.fontlab.com/news/color-font-format-proposals/) 以支援 OpenType 彩色字型——不只是顯示彩色 emoji 字元（字形），而是能將任何字形以彩色呈現。

#### OpenType 彩色字型的四種形式

[Adobe、Microsoft、Google 與 Apple 各自提交了提案](https://www.fontlab.com/news/color-font-format-proposals/) 以擴充 OpenType 來支援全彩 OpenType 字型，最終有四項提案被採納並納入正式的 OpenType 規格。為了方便，我們可大致將這四種變體分為向量型與點陣型——但如這 [GitHub 儲存庫](https://github.com/simoncozens/test-fonts)所示，OpenType 規格具有足夠的彈性，可支援結合這四種基礎技術的 OpenType 彩色字型檔。

* **向量式 OpenType 字型：**
* **Microsoft**：字形形狀是使用一種分層彩色向量的形式來描述（[COLR](https://docs.microsoft.com/en-us/typography/opentype/spec/colr) 和 [CPAL](https://docs.microsoft.com/en-us/typography/opentype/spec/cpal) 表格）。
* [**Adobe 與 Mozilla**](https://www.w3.org/2013/10/SVG_in_OpenType/) ([SVG 表格](https://docs.microsoft.com/en-us/typography/opentype/spec/svg)）：字形形狀是使用 SVG 繪製的，而 SVG 支援由向量 *與點陣影像*。 [Adobe 關於 SVG 字型的使用指南](https://helpx.adobe.com/fonts/user-guide.html/fonts/using/ot-svg-color-fonts.ug.html).
* **點陣式 OpenType 字型：**
* **Google**：字形是以嵌入字型中的彩色 PNG 影像表示（[CBDT](https://docs.microsoft.com/en-us/typography/opentype/spec/cbdt) 和 [CBLC](https://docs.microsoft.com/en-us/typography/opentype/spec/cblc) 表格）。
* **Apple**：字形同樣是以嵌入字型中的彩色影像表示。除了 PNG 之外，Apple 的機制（[sbix 表格](https://docs.microsoft.com/en-us/typography/opentype/spec/colr)）也支援 JPEG 和 TIFF。

因此，支援 OpenType 彩色字型的作業系統與應用軟體需要面對今日這種混合技術的環境。此外，你應該知道，個別 OpenType 彩色字型——以及 *版本* 的同一字型版本——會：

* 對完整的 [Unicode emoji 字元具有不同的涵蓋範圍；](https://unicode.org/emoji/charts/emoji-list.html)—也就是說，該字型為多少 emoji 字元提供字形；
* 使用不同的字形設計來表示個別 emoji 字元；
* 在為支援 Unicode 標準更進階用途所提供的功能上有所不同，例如 [emoji 修飾符](https://unicode.org/reports/tr51/#Emoji_Modifiers_Table)， [Unicode 技術標準 #51：Unicode Emoji](https://unicode.org/reports/tr51/).

#### 關於 HarfBuzz 的熱議

我們已提及需要一個 *文字塑形引擎*：一種軟體，它會取得某些輸入的 Unicode 文字，該文字以特定的腳本與語言組合撰寫，並使用指定的字型，將文字塑形成一串字形，同時附帶可用於排版原始輸入文字的定位資料。

[HarfBuzz](https://harfbuzz.github.io/) 就是這樣一個文字塑形引擎：它是 [一個開源程式庫](https://github.com/harfbuzz/harfbuzz) ，也是十多年研究與開發的成果——至今仍持續積極開發，並作為許多軟體產品的一部分部署。HarfBuzz 本身不執行「排版」，而是為選擇整合它的軟體提供「文字塑形服務」，包括 XeTeX、LuaHBTeX、 [Adobe PhotoShop 與 Adobe InDesign](https://en.wikipedia.org/wiki/HarfBuzz).

透過納入 HarfBuzz，TeX 引擎可以利用其進階文字塑形能力，提供非常精細的多語言排版，尤其適用於阿拉伯文、希伯來文、天城文以及許多其他複雜文字腳本。也請注意，HarfBuzz 也用於處理並塑形 Unicode emoji 文字字元，我們將更詳細地探討這一點。

下圖總結了當 HarfBuzz 與 XeTeX 或 LuaHBTeX 等軟體整合、用於排版阿拉伯文等複雜文字腳本文字時所扮演的角色：

![使用 HarfBuzz 進行阿拉伯文文字塑形概覽](/files/81bbc5310d4f913f61e6ab508dcaa170fe61bf57)

**探索 HarfBuzz**

任何有興趣進一步了解 HarfBuzz 以及它為 XeTeX 與 LuaHBTeX 提供的 OpenType 塑形服務的人，都可以 [下載 HarfBuzz 的二進位發行版](https://github.com/harfbuzz/harfbuzz/releases) 其中包含 HarfBuzz 程式庫（供程式設計師使用）以及命令列工具 `hb-view` 和 `hb-shape`.

**範例：如何使用 hb-view**

在你喜愛且支援 UTF-8 的文字編輯器中建立一個新檔，並將下列六個 emoji 字元 👋👋🏻👋🏼👋🏽👋🏾👋🏿 複製／貼上到該文字檔中，然後以 UTF-8 格式另存為名為，例如， `emoji.txt`.

請注意，你的文字編輯器可能會顯示（替代性）黑白版本的 emoji，因為它無法（或未被程式設定為）渲染彩色字形。這 6 個 emoji 一旦儲存後，檔案 `emoji.txt` 中應該包含下列 Unicode emoji 字元序列的 UTF-8 資料——我們以逗號分隔 emoji 修飾符僅為 *方便閱讀而已*:

* `1F44B` 以產生 👋
* `1F44B`, `1F3FB` 以產生 👋🏻
* `1F44B`, `1F3FC` 以產生 👋🏼
* `1F44B`, `1F3FD` 以產生 👋🏽
* `1F44B`, `1F3FE` 以產生 👋🏾
* `1F44B`, `1F3FF` 以產生 👋🏿

總共應該有 **11** 個 Unicode 字元，每個字元產生 4 個位元組的 UTF-8 資料，因此結果的 `emoji.txt` 檔案長度應為 44 位元組，不包括 emoji 所在行末使用的任何行尾標記。

該 `hb-view` 工具可使用該檔案 `emoji.txt`，再搭配你選擇的一款合適的 OpenType 彩色字型，例如 `NotoColorEmoji.ttf`，以產生 HarfBuzz 塑形輸出的 SVG 檔案。以下命令列範例，必須 **在同一行輸入** 於你的終端機中，將會產生 SVG 檔案 `emoji.svg`:

```latex
hb-view --font-size=20 --output-file="emoji.svg"
--output-format=svg --text-file=emoji.txt
--font-file=NotoColorEmoji.ttf
```

成功執行後，檔案 `emoji.svg`，由 `hb-view`產生，可用 Inkscape 開啟，外觀應如下所示：

![Hbvieemoji.png](/files/2b9d68d7459944e4d622b32b9c3ebb917419add0)

`hb-view` 可用來探索 HarfBuzz 對任何合適的 Unicode 文字檔與 OpenType 字型的塑形效果——當然不僅限於 emoji！輸入

```latex
hb-view --help-all
```

即可查看這個強大又方便的工具所提供的豐富命令列選項。塑形愉快！

## 文字塑形與 TeX 引擎

在這裡，我們將回顧 XeTeX 與 LuaTeX 系列 TeX 引擎的文字塑形能力。

### XeTeX

XeTeX 開發於 2000 年代初期，並在以 TeX 為基礎的排版中開創了多項創新，最顯著的是 *內建的* 對以下項目的支援：

* 以 UTF-8 格式讀取 Unicode 文字；
* 使用 OpenType 字型；
* 用於多語言排版的文字塑形；
* 基於 OpenType 的數學排版。

XeTeX 能夠輕鬆且方便地排版複雜文字腳本語言，歸功於其內建的文字塑形能力——最初是基於現已棄用的 [ICU LayoutEngine](http://userguide.icu-project.org/layoutengine)。多虧 Khaled Hosny 的工作，XeTeX 轉而使用 HarfBuzz 進行文字塑形，如 [2013 年 3 月](https://tug.org/pipermail/xetex/2013-March/024118.html)的公告中所述。對於任何希望排版多語言文字的人來說，XeTeX 通常被視為首選的 TeX 引擎——但現在還有另一個選擇：LuaHBTeX，我們將進一步探討。

### LuaTeX 與 LuaHBTeX

LuaTeX 的開發約始於 2005 年，但其設計哲學與 XeTeX 相當不同，後者是將新功能 *直接納入* XeTeX 軟體中。與 XeTeX 不同，LuaTeX 的開發者選擇「……提供最少量的工具，不提供解決方案。」（見 [LuaTeX 參考手冊](https://www.pragma-ade.com/general/manuals/luatex.pdf)）。與其在 *內建於* LuaTeX 系列引擎中的一整套額外功能，LuaTeX 引擎的內部機制被開放出來，使開發者與熟練使用者能利用整合的 Lua 腳本語言建立自己的解決方案。

例如，與 XeTeX 不同，LuaTeX 引擎不能 *直接* 直接使用 OpenType 字型；相反地，OpenType 字型必須透過以 Lua 程式碼撰寫的字型載入函式來載入並「準備使用」。那些字型載入函式稱為 *回呼* 函式：當有載入字型的請求時，LuaTeX 會呼叫（「執行」）的 Lua 程式碼。

此外，LuaTeX 引擎不提供任何 *內建的* 文字塑形能力——這些也必須由外部程式碼提供，讓 LuaTeX 引擎可以呼叫以取得文字塑形服務。這再次與 XeTeX 引擎形成對比，後者將文字塑形能力整合在核心軟體中。

#### luaotfload：在 LuaTeX/LuaHBTeX 中使用 OpenType 字型不可或缺

LuaTeX 用於字型載入的回呼機制提供了極大的彈性，儘管代價是需要額外程式設計。所幸對 LuaLaTeX 使用者而言，TeX 社群已開發出一個名為 `luaotfload`的套件，它是 [TeX Live 年度發行版](https://www.tug.org/texlive/) 的一部分，當然也可供 Overleaf 使用者使用。

`luaotfload` 是 [可在 CTAN 取得](https://ctan.org/pkg/luaotfload?lang=en) 並且有一個 [在 GitHub 上的開發儲存庫](https://github.com/latex3/luaotfload) 你可以在那裡追蹤最新進展並 [新版本](https://github.com/latex3/luaotfload/releases).

`luaotfload` 可透過以下方式直接載入到 LaTeX 文件導言區：

```latex
\usepackage{luaotfload}
```

請注意 `luaotfload` 是 LaTeX 的 *套件*，也就是說它的檔名是 `luaotfload.sty`。如果你想使用 `luaotfload` 於 plain TeX，你可以加入以下這一行來實現

```latex
\input luaotfload.sty
```

到你的 plain TeX 文件中。

通常，LuaLaTeX 使用者——也就是使用 LuaTeX/LuaHBTeX 來排版 LaTeX 的人——不需要直接接觸 `luaotfload` ，因為 [`fontspec` 套件](https://ctan.org/pkg/fontspec) 將會載入 `luaotfload` 套件，並透過 `fontspec` 套件。

### LuaHBTeX：文字塑形的新選項

`luaotfload` 是一個成熟且強大的 Lua 程式庫，提供 LuaTeX 對 OpenType 字型的處理——同時也為多種語言與文字腳本提供文字塑形服務。最初， `luaotfload` 的文字塑形功能是以純 Lua 程式碼實作，但 TeX Live 2020 的發行帶來了另一個主流的文字塑形選項——一個名為 LuaHBTeX 的新 LuaTeX 引擎。

LuaHBTeX 中的「HB」代表 HarfBuzz——本質上，LuaHBTeX 就是原始的 LuaTeX 引擎 *加上* 加上整合的 HarfBuzz 文字塑形引擎。符合 LuaTeX 的設計哲學，HarfBuzz 的可用性並不會 *自動地* 保證文字會由 LuaHBTeX 來塑形：HarfBuzz 只是另一個可用來建立文字塑形解決方案的工具。

LuaHBTeX 對 HarfBuzz 的整合是 [可透過 Lua 程式碼進行程式化控制](#introduction-to-the-luahbtex-harfbuzz-api)，這使得 `luaotfload`其開發者得以加入基於 HarfBuzz 的文字塑形解決方案。因此， [自 3.1 版開始，於 2019 年 11 月 5 日發布](https://github.com/latex3/luaotfload/releases/tag/v3.1), `luaotfload` 已加強以善用 HarfBuzz——讓一般使用者更容易存取 HarfBuzz 的文字塑形能力。

有興趣了解 HarfBuzz 與 LuaTeX 整合技術細節的讀者可閱讀這篇 [Khaled Hosny 撰寫的論文](https://www.tug.org/TUGboat/tb40-1/tb124hosny-harfbuzz.pdf).

### luaotfload：文字塑形的兩種選項（何時使用 HarfBuzz？）

LuaLaTeX 使用者現在有兩種文字塑形選項：

* `luaotfload`的原始（基於節點的）文字塑形實作，完全以 Lua 編寫；
* `luaotfload`基於 HarfBuzz 的塑形——透過呼叫 HarfBuzz 文字塑形函式的 Lua 程式碼來存取。

`luaotfload` 透過其「`mode`」參數來存取這兩種塑形系統——不過多數使用者會使用等效的 `fontspec` “`Renderer`」選項，而不是直接使用 `luaotfload`.

各自 `luaotfload`的文字塑形解決方案都有其優點與（目前的）缺點，但你應該使用哪一個，又該在何時使用？以下是一些需要考慮的要點：

* `luaotfload`的原生基於節點處理可能耗費大量記憶體，尤其是大型 CJK OpenType 字型。對 CJK 文字使用 HarfBuzz 進行塑形可提升速度並降低記憶體使用量。
* 對複雜文字腳本使用 HarfBuzz，因為它「……大幅改善印度文與阿拉伯文腳本的渲染，且非常推薦用於這類腳本。」（見 `luaotfload` 手冊）。
* HarfBuzz 被整合進 `luaotfload` 仍然相對較新，且仍在進一步開發中。在撰寫本文時（2021 年 7 月），建議使用 luaotfload 的內建塑形（設定 `mode=node`） [GitHub 問題](https://github.com/latex3/luaotfload/issues/175#issue-801120377)，其中總結了相關問題與討論。如果你想試驗，可以使用 `luaotfload` 來載入字型檔，並建立兩個 LaTeX 字型：一個使用基於 HarfBuzz 的塑形，另一個使用基於 Lua 的塑形。Overleaf 建立了一個 [範例專案](#sample-project-arabic-shaping)，用來示範這一點。
* 不要用 HarfBuzz 來處理數學字型。正如 tex.stackexchange 上的開發者所討論的，HarfBuzz [並不是為處理數學排版字型而設計的](https://tex.stackexchange.com/questions/544881/does-luahbtex-with-harfbuzz-renderer-completely-supports-math-formating) ，所以不要將它用於該目的。

**範例專案：阿拉伯文塑形**

這裡有一個 Overleaf 專案，使用多種高品質阿拉伯文字體來比較 `luaotfload`的基於節點的文字塑形服務（`mode=node`）與 HarfBuzz 的（`mode=harf`):

* <https://www.overleaf.com/latex/examples/complex-script-shaping-using-luaotfload-and-harfbuzz/gfssprnhfddn>

此專案包含如下圖所示的輸出：

![阿拉伯文排版](/files/efe850f37e4ec7cfb21aab907f7519ee88311d0a)

### 在 fontspec 中選擇「Renderer」

如其 [說明文件](https://mirror.ox.ac.uk/sites/ctan.org/macros/unicodetex/latex/fontspec/fontspec.pdf), `fontspec` 「……允許 XeTeX 或 LuaTeX 的使用者在 LaTeX 文件中載入 OpenType 字型」。如果你使用 LuaTeX 或 LuaHBTeX 引擎， `fontspec` 將會載入 `luaotfload` 程式庫，並且另外提供一套方便的使用者層級命令，以減少直接接觸 `luaotfload`的低階功能。

那麼，你該如何在 HarfBuzz 的塑形與 `luaotfload`所提供的內建塑形之間做選擇？答案可在優秀的 [`fontspec` 說明文件](https://mirror.ox.ac.uk/sites/ctan.org/macros/unicodetex/latex/fontspec/fontspec.pdf)中找到，尤其是第 VI 部分：僅適用於 LuaTeX 的字型功能。 `fontspec` 提供一個名為 `Renderer` 的設定，可在透過 `fontspec`. `Renderer` 定義字型時設定，該設定控制字型的低階處理。我們關心的兩個選項是

* `Renderer = Node`：排版 OpenType 字型時的預設「mode」——這會使用 `luaotfload`以純 Lua 實作的文字塑形函式。
* `Renderer = Harfbuzz`：此「mode」會定義／載入字型，以供 HarfBuzz 文字塑形引擎使用。 `luaotfload` 使用 LuaHBTeX 的 API 來呼叫 HarfBuzz 中的函式。

如需更多資訊，請參閱 [`fontspec` 說明文件](https://mirror.ox.ac.uk/sites/ctan.org/macros/unicodetex/latex/fontspec/fontspec.pdf).

## TeX 引擎、HarfBuzz 與彩色 emoji

雖然 XeTeX 與 LuaHBTeX 都整合了 HarfBuzz，但它們對 HarfBuzz 某些更進階功能的支援程度不同——最明顯的是載入與使用 OpenType 彩色字型。

### XeTeX 與 OpenType 彩色字型

如前所述，依據用來儲存字型字形的資料格式，OpenType 彩色字型可分為兩類：向量式與點陣式。

#### XeTeX 與點陣式 OpenType 彩色字型

XeTeX 無法載入基於點陣圖的 OpenType 彩色字型——例如 Google 的 [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) ，它隨 TeX Live 2020 一併提供。例如，如果你嘗試載入 Noto Color Emoji（NotoColorEmoji.ttf），XeLaTeX 會失敗，並可能出現誤導性的錯誤，聲稱找不到 Noto Color Emoji。以下使用 XeLaTeX 排版的 LaTeX 程式碼， *無法運作*:

```latex
\documentclass{article}
\usepackage{fontspec}
\begin{document}
\newfontfamily\emojifont{NotoColorEmoji.ttf}
\newcommand{\smiley}{{\emojifont\char"1F600}}
\smiley
\end{document}
```

[在 Overleaf 中開啟這段 XeLaTeX 程式碼（它 ***不會*** 無法運作）。](https://www.overleaf.com/docs?engine=xelatex\&snip_name=XeTeX+failure\&snip=%5Cdocumentclass%7Barticle%7D%0A%5Cusepackage%7Bfontspec%7D%0A%5Cbegin%7Bdocument%7D%0A%5Cnewfontfamily%5Cemojifont%7BNotoColorEmoji.ttf%7D%0A%5Cnewcommand%7B%5Csmiley%7D%7B%7B%5Cemojifont%5Cchar%221F600%7D%7D%0A%5Csmiley%0A%5Cend%7Bdocument%7D)

它會以以下錯誤失敗：

```
! Package fontspec Error: The font "NotoColorEmoji" cannot be found.
```

同樣地，一個由 XeTeX 處理的簡單 Plain TeX 範例也會失敗

```latex
\font\emojifont="[NotoColorEmoji.ttf]" at 12pt
\emojifont \char"1F600
\bye
```

[在 Overleaf 中開啟這個 Plain TeX（XeTeX）範例（它 ***不會*** 無法運作）。](https://www.overleaf.com/docs?engine=latex_dvipdf\&snip_name\[]=main.tex\&snip\[]=%25%5Ctitle%7Bdummy+title%7D%0A%5Cfont%5Cemojifont%3D%22%5BNotoColorEmoji.ttf%5D%22+at+12pt%0A%5Cemojifont+%5Cchar%221F600%0A%5Cbye\&snip_name\[]=readme\&snip\[]=This+project+uses+a+latexmkrc+file+to+run+xetex+not+xelatex\&snip_name\[]=latexmkrc\&snip\[]=%24latex+%3D+%27xetex%25O+%25S%27%3B+%23+to+use+the+xetex+engine\&main_document=main.tex)

這個 Plain TeX 範例會回報一則類似但不同的錯誤訊息：

```
! Font \emojifont=[NotoColorEmoji.ttf] at 12.0pt not loadable: Metric (TFM) fil
e or installed font not found.
l.1 \font\emojifont="[NotoColorEmoji.ttf]" at 12pt

我無法讀取這個字型的大小資料，
因此我將忽略該字型規格。
[Wizards can fix TFM files using TFtoPL/PLtoTF.]
你也許可以嘗試插入不同的字型規格；
例如，輸入 `I\font<same font id>=<substitute font name>'.
```

**Plain LuaHBTeX 範例**

作為比較，這裡有一個使用 LuaHBTeX 編譯的最小 Plain TeX 範例

```latex
\input luaotfload.sty
\font\emojifont=NotoColorEmoji.ttf:mode=harf at 12pt
\emojifont \Uchar"1F600
\bye
```

[在 Overleaf 中開啟這個 Plain TeX（LuaHBTeX）範例（它編譯成功）。](https://www.overleaf.com/docs?engine=latex_dvipdf\&snip_name\[]=main.tex\&snip\[]=%25%5Ctitle%7BPlain+TeX+with+LuaHBTeX%7D%0A%5Cinput+luaotfload.sty%0A%5Cfont%5Cemojifont%3DNotoColorEmoji.ttf%3Amode%3Dharf+at+12pt%0A%5Cemojifont+%5CUchar%221F600%0A%5Cbye\&snip_name\[]=readme\&snip\[]=This+project+uses+a+latexmkrc+file+to+run+luahbtex+not+lualatex\&snip_name\[]=latexmkrc\&snip\[]=%24latex+%3D+%27luahbtex+%25O+%25S%27%3B+%23+to+use+the+luahbtex+engine\&main_document=main.tex)

#### XeTeX 失敗的真正原因

XeTeX 提供的錯誤訊息部分掩蓋了問題的真正原因：OpenType 彩色字型，尤其是基於點陣圖的變體， *不* 支援於 XeTeX。實際上，XeTeX（Kpathsea）可以 *找到* Noto Color Emoji 字型，但 XeTeX 無法完全 *載入* 該字型，也無法初始化用於將該字型排版所需的內部字型資料表。就內部而言，XeTeX *開始* 字型載入程序並測試其「可縮放性」（使用 FreeType 對「可縮放性」的定義），但該測試失敗，因此 XeTeX 會發出一則標準、但可說是誤導性的 TeX 引擎錯誤訊息。

**TeX 技術註記**

我們透過編譯 XeTeX 可執行檔的除錯版本，研究了 XeTeX 對 NotoColorEmoji.ttf 的處理。接著使用 Eclipse IDE 在 XeTeX 函式上設置中斷點 `creatFontFromFile(filename, index, pointsize)`，然後逐步執行程式碼以觀察後續處理。

#### XeTeX 與基於向量的 OpenType 彩色字型

XeTeX 可以 *載入* 處理基於向量的 OpenType 彩色字型，但不會在產生的 PDF 中產生彩色表情符號——如果 XeTeX 真的能產生 PDF 的話。不同於 LuaTeX、LuaHBTeX 和 pdfTeX，XeTeX 不會 *直接* 以 PDF 格式輸出排版文件。XeTeX 反而會輸出一個中介的 `.xdv` （e**x**tended **dv**i）檔案格式，再由名為 `xdvipdfmx`的工具轉換為 PDF。在撰寫本文時， `xdvipdfmx` 無法將適當的彩色表情符號字形資料嵌入 PDF，因此，最好的情況下，你會在 PDF 中看到單色表情符號——也就是「fallback」結果——或也可能什麼都沒有，這取決於所使用的字型。

這裡有一個 XeLaTeX 範例，使用 OpenType 彩色字型 [TwemojiMozilla.ttf](https://ctan.org/tex-archive/fonts/twemoji-colr)，它可在 TeX Live 中取得。TwemojiMozilla.ttf 使用 Microsoft 的 COLR/CPAL 向量格式來儲存彩色字形，並隨 TeX Live 2020 一併提供。在這個範例中，XeTeX 能夠載入該字型，產生一個 `.xdv` 以及 PDF 檔，但表情符號字形並未出現在排版後的 PDF 中：

```latex
\documentclass{article}
\usepackage{fontspec}
\begin{document}
\newfontfamily\emojifont{TwemojiMozilla.ttf}
\newcommand{\smiley}{{\emojifont\char"1F600}}
這裡有一個笑臉：\smiley
\end{document}
```

[在 Overleaf 中開啟這段 XeLaTeX 程式碼（它無法成功運作）。](https://www.overleaf.com/docs?engine=xelatex\&snip_name=XeTeX+failure\&snip=%5Cdocumentclass%7Barticle%7D%0A%5Cusepackage%7Bfontspec%7D%0A%5Cbegin%7Bdocument%7D%0A%5Cnewfontfamily%5Cemojifont%7BTwemojiMozilla.ttf%7D%0A%5Cnewcommand%7B%5Csmiley%7D%7B%7B%5Cemojifont%5Cchar%221F600%7D%7D%0AHere+is+a+smiley%3A+%5Csmiley%0A%5Cend%7Bdocument%7D)

相較之下，如果定義 `\emojifont` 使用 `fontspec` 設定 `[Renderer=HarfBuzz]`:

```latex
\documentclass{article}
\usepackage{fontspec}
\begin{document}
\newfontfamily\emojifont{TwemojiMozilla.ttf}[Renderer=HarfBuzz]
\newcommand{\smiley}{{\emojifont\char"1F600}}
這裡有一個笑臉：\smiley
\end{document}
```

[在 Overleaf 中開啟這段 LuaLaTeX 程式碼（它可以運作）。](https://www.overleaf.com/docs?engine=lualatex\&snip_name=LuaLaTeX+emoji+example\&snip=%5Cdocumentclass%7Barticle%7D%0A%5Cusepackage%7Bfontspec%7D%0A%5Cbegin%7Bdocument%7D%0A%5Cnewfontfamily%5Cemojifont%7BTwemojiMozilla.ttf%7D%5BRenderer%3DHarfBuzz%5D%0A%5Cnewcommand%7B%5Csmiley%7D%7B%7B%5Cemojifont%5Cchar%221F600%7D%7D%0AHere+is+a+smiley%3A+%5Csmiley%0A%5Cend%7Bdocument%7D)

### LuaHBTeX 與 OpenType 彩色字型

透過其整合的 HarfBuzz 造型引擎以及 `luaoftload` 函式庫，LuaHBTeX 支援四種 OpenType 彩色字型的全部變體。LuaLaTeX 使用者可以充分利用包含表情符號字元的文字之基於 Unicode 的處理，或單純使用 OpenType 彩色字型為文件增添繽紛色彩。

如前所述，OpenType 彩色字型的四種變體可分成兩組：

* 包含點陣圖格式字形，例如 PNG 的；
* 其他使用 SVG 或 Microsoft COLR/CPAL 機制的向量格式。

基於向量的字形格式的優點是可縮放：能在任何字級大小產生銳利的字形圖形。

**在 LuaHBTeX 中使用 Microsoft COLR/CPAL 彩色字型**

如果你想為你的 OpenType 彩色表情符號字型使用向量格式，請看看字型 [TwemojiMozilla.ttf](https://ctan.org/tex-archive/fonts/twemoji-colr?lang=en)，它基於 Microsoft 的 COLR/CPAL 格式。TwemojiMozilla.ttf 已包含在 TeX Live 中，但你可以從它的 [GitHub 儲存庫](https://github.com/mozilla/twemoji-colr/releases) 並上傳到你的 Overleaf 專案中。

這裡有一個小型的、 `fontspec`-based、範例，使用 `Renderer=Harfbuzz`，它會排版一隻很大的（向量）表情鴨：

```latex
\documentclass{article}
\usepackage{fontspec}
\title{Duck demo}
\begin{document}
\newfontfamily\emojifont[Renderer=Harfbuzz,SizeFeatures={Size=400}]{TwemojiMozilla.ttf}
\emojifont\Uchar"1F986
\end{document}
```

[在 Overleaf 中開啟這個 LuaLaTeX 範例，以排版一隻向量鴨。](https://www.overleaf.com/docs?engine=lualatex\&snip_name=Typesetting+an+emoji+duck\&snip=%5Cdocumentclass%7Barticle%7D%0A%5Cusepackage%7Bfontspec%7D%0A%5Ctitle%7BDuck+demo%7D%0A%5Cbegin%7Bdocument%7D%0A%5Cnewfontfamily%5Cemojifont%5BRenderer%3DHarfbuzz%2CSizeFeatures%3D%7BSize%3D400%7D%5D%7BTwemojiMozilla.ttf%7D%0A%5Cemojifont%5CUchar%221F986%0A%5Cend%7Bdocument%7D)

以下是由上面範例產生的（向量）鴨：

![](/files/5ab35b353c3ce363adfe12cd4f744623809f38c2)

#### 使用以 SVG 為基礎的 OpenType 彩色字型與 LuaHBTeX

在撰寫本文更新版（2023 年 7 月）時，關於在 LuaLaTeX 中使用 SVG 風格的 OpenType 彩色字型，正式文件很少。一些 [線上討論中的評論](https://github.com/latex3/luaotfload/issues/96) 建議使用 `fontspec`的 `RawFeature`，如下面的偽程式碼所示。將 `*your SVG font file name here*` 替換為你的 LaTeX 程式碼可存取的某個基於 SVG 的字型檔名稱：

```latex
\documentclass{article}
\usepackage{fontspec}
\begin{document}
\newfontfamily\emoji[RawFeature={+svg},SizeFeatures={Size=20}]{your SVG font file name here}
\emoji 你的表情符號在這裡...
\end{document}
```

如果你省略 `fontspec` 並直接載入 `luaotfload` ，你可能需要以下列方式宣告並指定字型——我們的實驗顯示，為了讓這個方法運作，你需要省略 `mode=harf` 選項：

```latex
\font\emoji=[your SVG font file name here]:+svg;
```

**一些注意事項**

有興趣使用 SVG 風格 OpenType 彩色字型的讀者應注意：

* 包含大量字形的 SVG 風格 OpenType 字型可能會對 LuaLaTeX 而言 [在運算上相當昂貴](#processing-svg-glyph-data) ，可能導致 [Overleaf 逾時](/latex/zh-tw/zhi-shi-ku/038-fixing-and-preventing-compile-timeouts.md).
* LuaLaTeX 對這些字型的支援可能 [被視為實驗性](https://github.com/latex3/luaotfload/issues/96#issuecomment-530317399)：結果可能因你的專案所使用的 TeX Live 發行版而異；因此，建議進行實驗並謹慎操作。

**處理 SVG 字形資料**

SVG 讓設計者能製作代表字型字形的複雜且色彩豐富的設計——但需受限於某些 SVG 限制 [，這些限制記載於 OpenType 規格中](https://docs.microsoft.com/en-us/typography/opentype/spec/svg)。然而，TeX 引擎（包括 LuaHBTeX）無法直接匯入（使用）SVG 檔案或資料——例如用於描述 SVG 風格 OpenType 彩色字型中字形形狀的 SVG 資料。字形的 SVG 資料必須轉換成 PDF 格式，因為 LuaHBTeX 可以用它來排版字形並產生最終的 PDF 文件。這個 SVG 轉 PDF 的轉換是由 `luaoftload`中的 Lua 程式碼處理的：每個字形的 SVG 資料都會從字型檔中擷取出來，儲存為暫存的 `.svg` 檔，並透過 Inkscape 的命令列轉換為 PDF。擷取 SVG 資料並將其轉換為 PDF 會帶來一些處理開銷，導致文件編譯時間可能相當長——特別是使用包含數千個表情符號字形的大型 SVG 字型的文件。

#### 基於點陣圖的 OpenType 彩色字型

**在 LuaHBTeX 中使用 Google 的 CBDT/CBLC OpenType 彩色字型格式**

[Noto Color Emoji](https://fonts.google.com/noto/specimen/Noto+Color+Emoji) 是 TeX Live 中包含的一種 OpenType 彩色字型，因此很容易在 Overleaf 專案中使用。由於 Noto Color Emoji 使用 PNG 格式圖形來表示表情符號字形，我們可以用它來排版一隻大型（點陣圖）鴨子表情符號——如下例所示。再次注意， `fontspec` 字型宣告（`\emojifont`）使用 `Renderer=Harfbuzz`.

```latex
\documentclass{article}
\usepackage{fontspec}
\title{Duck demo}
\begin{document}
\newfontfamily\emojifont[Renderer=Harfbuzz,SizeFeatures={Size=400}]{NotoColorEmoji.ttf}
\emojifont\Uchar"1F986
\end{document}
```

[在 Overleaf 中開啟這個 LuaLaTeX 範例，以排版一隻點陣圖鴨子。](https://www.overleaf.com/docs?engine=lualatex\&snip_name=Typesetting+a+large+raster+duck\&snip=%5Cdocumentclass%7Barticle%7D%0A%5Cusepackage%7Bfontspec%7D%0A%5Ctitle%7BDuck+demo%7D%0A%5Cbegin%7Bdocument%7D%0A%5Cnewfontfamily%5Cemojifont%5BRenderer%3DHarfbuzz%2CSizeFeatures%3D%7BSize%3D400%7D%5D%7BNotoColorEmoji.ttf%7D%0A%5Cemojifont%5CUchar%221F986%0A%5Cend%7Bdocument%7D)

以下是由上面範例產生的點陣圖鴨子：

![由 LaTeX 排版的點陣圖鴨子表情符號](/files/56f1cfbc0426d7e44a2b0364ed38bd69d0afbccb)

如果你嘗試使用 `NotoColorEmoji.ttf` 但省略 `[Renderer=Harfbuzz]` 來自 `fontspec` 宣告，LuaHBTeX 會失敗，並在嘗試寫出 PDF 檔時發出錯誤訊息：

```latex
! error:  (file /usr/local/texlive/2020/texmf-dist/fonts/truetype/google/noto-em
oji/NotoColorEmoji.ttf) (ttf): loca table not found
```

這個錯誤在 [loca 表](https://docs.microsoft.com/en-us/typography/opentype/spec/loca) 是 [中的原因已在 GitHub 上說明](https://github.com/latex3/luaotfload/issues/98#issuecomment-531610153).

**在 LuaHBTeX 中使用 Apple 的 sbix OpenType 彩色字型格式**

離線測試顯示 LuaHBTeX 支援 OpenType 彩色字型的 `sbix` 變體，但截至撰寫本文時，我們尚未找到一個具有適當授權的 `sbix`-變體彩色表情符號字型可用來示範排版一隻鴨子。請 [與我們聯絡](https://www.overleaf.com/contact) 如果你知道有這樣的字型，告訴我們，我們會很快更新本文以使用它。

## LuaHBTeX HarfBuzz API 簡介

![Db.gif](/files/9a66e4a34af5bd3948cb7c3722d59c90576d9aa4) ![Db.gif](/files/9a66e4a34af5bd3948cb7c3722d59c90576d9aa4)

文字造型，特別是對複雜文字系統語言，甚至表情符號而言，本質上就是一項困難的工作，所以毫不意外地，HarfBuzz 是一個相當精密的函式庫，若你不熟悉文字造型操作，使用起來會很棘手。在本最後一節中，我們將探討 LuaHBTeX 對 HarfBuzz 的整合，以及如何透過 `\directlua`.

我們的範例使用相當基本的程式碼來示範 LuaHBTeX HarfBuzz API。這有點刻意安排，並非生產等級品質，也不太實用，因為它唯一的目的就是介紹一些核心概念。我們把 Lua 程式碼分成兩個 `\directlua` 區塊：第一個載入 `luaharfbuzz` 函式庫，並建立一些全域變數，我們會在第二個 `\directlua` 區塊中使用它們，在那裡我們定義一個名為 `\codestoemoji`.

採用 Knuth 使用雙重危險彎曲符號的作法似乎很合適（圖片由 [這個網站](http://www.truetex.com/db.htm)提供）」，因為內容有點偏底層，且「窺探內部結構」——雖然我們希望這對更有冒險精神的讀者會有興趣。LuaHBTeX 對 HarfBuzz 的整合源自 GitHub 上的 [luaharfbuzz 專案](https://github.com/ufyTeX/luaharfbuzz/wiki#projects-using-luaharfbuzz) ，你可以在那裡找到 [專案簡介](https://github.com/ufyTeX/luaharfbuzz/wiki) 以及 [luaharfbuzz API 清單](http://ufytex.github.io/luaharfbuzz/).

### 第一步：載入 luaharfbuzz 函式庫並尋找字型

要使用 LuaHBTeX 的 HarfBuzz API，我們首先需要載入名為 `luaharfbuzz`的函式庫（模組），它內建於 LuaHBTeX，並將回傳的表格儲存在我們將稱為的（全域）變數中 `hblib`:

```latex
hblib=require("luaharfbuzz")
```

接著，我們需要定位一個合適的表情符號 OpenType 彩色字型：我們將使用 Noto Color Emoji——注意我們這裡非常偷懶，沒有做任何錯誤檢查，以防找不到它！為了找到它，我們將使用 `kpse` （Kpathsea）函式庫，它也是 LuaTeX/LuaHBTeX 的一部分：

```latex
pathtofontfile=kpse.find_file("NotoColorEmoji.ttf","truetype fonts")
```

現在我們已透過變數 `hblib`存取 HarfBuzz 函式庫，並取得適合字型的路徑（`pathtofontfile`），我們就可以開始使用 `hblib`。首先，我們會建立一個 HarfBuzz font 與 HarfBuzz face，供第二個 `\directlua` 程式碼區塊中定義巨集時使用。

```latex
%從 Noto Color Emoji 建立 HarfBuzz face 和 HarfBuzz font
hbface = hblib.Face.new(pathtofontfile)
hbfont = hblib.Font.new(hbface)
```

#### HarfBuzz font 與 HarfBuzz face：它們是什麼？

一個 [HarfBuzz face 物件](https://harfbuzz.github.io/fonts-and-faces.html) 代表從字型檔載入的字體，但尚未設定特定參數（例如大小）。一個 [HarfBuzz font 物件](https://harfbuzz.github.io/fonts-and-faces.html) 代表 *特定實例* 的 HarfBuzz face；因此，可以從單一 HarfBuzz face 衍生出不同的 HarfBuzz font 物件：每個 HarfBuzz font 的屬性，例如大小，都可以設定為不同的值。HarfBuzz face 的抽象層級比 HarfBuzz font 更高。

### 使用字型字形建立 PNG 檔案

我們第一個 `\directlua` 區塊的最後部分是一個名為 `writePNGglyph(hbfontobject, glyphID)` 的函式，我們用它來證明某些 OpenType 彩色字型，例如 Noto Color Emoji，會使用 PNG 圖形來表示其所包含的表情符號字形。

這個函式使用 LuaHBTeX 的 HarfBuzz API 從字形中擷取 PNG 資料，並將該資料寫入 `.png` 中名為 `Graphics<glyphID>.png`檔。該 `.png` 檔案名稱會回傳供 `\includegraphics` 使用，以便在我們排版好的 PDF 中嵌入 PNG 字形影像。

有了 `writePNGglyph(hbfontobject, glyphID)` 之後，我們的第一個 `\directlua` 程式碼區塊如下：

```latex
\directlua{

% 從 LuaHBTeX 載入 luaharfbuzz 函式庫
hblib=require("luaharfbuzz")

% 在 Overleaf 的伺服器上定位 Noto Color Emoji 字型
pathtofontfile=kpse.find_file("NotoColorEmoji.ttf","truetype fonts")

% 從 Noto Color Emoji 建立 HarfBuzz face 和 HarfBuzz font
hbface = hblib.Face.new(pathtofontfile)
hbfont = hblib.Font.new(hbface)

% 此函式接受一個字型與一個字形 ID：
% 它會擷取字形的 PNG 資料並寫入
% 將其輸出為 .png 檔

function writePNGglyph(hbfontobject, glyphID)

    % 取得字形 PNG 資料
    local pngblob=hbfontobject:ot_color_glyph_get_png(glyphID)
    local pngdata=pngblob:get_data()

    % 為我們的 .png 檔建構檔名
    local fname="Glyph"..glyphID..".png"

    % 寫出 .png 檔並回傳檔名
    local output = assert(io.open(fname, "wb"))
    output:write(pngdata)
    output:close()

    % 回傳供 \includegraphics 使用的檔名
    return fname
end
}
```

### 第二個 \directlua 區塊：建立巨集 \codestoemoji

目標是定義一個巨集 `\codestoemoji` ，我們可以用一段包含表情符號字元代碼的文字來呼叫它，並希望 HarfBuzz 將其造型處理。具體來說，我們會使用 `\Uchar<字元碼>` 來代表每個表情符號字元；例如：

```latex
\codestoemoji{\Uchar"1F3F4\Uchar"E0067\Uchar"E0062\Uchar"E0065\Uchar"E006E\Uchar"E0067\Uchar"E007F}
```

在 `\codestoemoji` 的定義中有很多事情在進行，我們會在下方說明，但這個定義看起來像這樣：

```latex
\newcommand{\codestoemoji}[1]{%
\directlua{

local str="#1"
local hbbuffer = hblib.Buffer.new()
hbbuffer:add_utf8(str)

hbbuffer:set_direction(hblib.Direction.new("ltr"))
local res = hblib.shape_full(hbfont, hbbuffer, {},{})

if (res) then
    local hbglyphs=hbbuffer:get_glyphs()
    % glyphs 表格 hbglyphs 是以 1 為起始索引
    local i = 1
    while hbglyphs[i] \noexpand~= nil do
        local glyph = hbglyphs[i]
        i = i + 1
        local fname=writePNGglyph(hbfont, glyph.codepoint)
        % 縮小我們匯入的 PNG 影像尺寸
        local s = 0.75
        local scal="[scale="..tostring(s).."]"
        tex.print([[\noexpand\includegraphics]]..scal..[[{]]..fname..[[}]])
     end
end
}}
```

#### 理解巨集 \codestoemoji 的定義

該 `\codestoemoji` 巨集主要是包含在 `\directlua`中的 Lua 程式碼，所以如果你想更了解 *如何* `\directlua` 如何運作，請查看 Overleaf 文章 [Understanding `\directlua`](/latex/zh-tw/shen-ru-wen-zhang/09-an-introduction-to-luatex-part-2-understanding-directlua.md). 它解釋了當 TeX/LaTeX 指令包含在 Lua 程式碼中時，LuaTeX 與 LuaHBTeX 如何處理 `\directlua` ，特別是需要使用 `\noexpand` 和 `\unexpanded`.

**處理巨集參數："#1"**

這個巨集以以下三行開始：

```latex
local str="#1"
local hbbuffer = hblib.Buffer.new()
hbbuffer:add_utf8(str)
```

它們執行下列工作：

* `local str="#1"`：這會從巨集傳入的輸入建立一個 Lua 字串；
* `local hbbuffer = hblib.Buffer.new()`：這會使用 HarfBuzz API 建立一個緩衝區，用來存放我們希望 HarfBuzz 造型處理的文字；
* `hbbuffer:add_utf8(str)`：這會將一個由我們巨集輸入建立的 UTF-8 格式字串加入 HarfBuzz 緩衝區中。

第一行程式碼

```latex
local str="#1"
```

看起來相當直接，但其運作其實涉及相當多的複雜性，值得稍微更詳細地探討。

如果我們考慮第三行程式碼

```latex
hbbuffer:add_utf8(str)
```

我們會看到它使用了我們的 `str` 變數，為 HarfBuzz 緩衝區提供一個以 UTF-8 格式的 Unicode 字串。要做到這一點，變數 `str` 本身必須包含以 UTF-8 格式編碼的 Unicode 文字；因此問題來了： *如何* LuaHBTeX 是否將巨集參數 `"#1"`，其中包含 `\Uchar` 指令，轉換成了 Lua 字串變數 `str` ，以供 HarfBuzz 使用的 UTF-8 文字？

如果我們看看我們預期如何使用這個 `\codestoemoji` 巨集：

```latex
\codestoemoji{\Uchar"1F3F4\Uchar"E0067\Uchar"E0062\Uchar"E0065\Uchar"E006E\Uchar"E0067\Uchar"E007F}
```

輸入，例如 `\Uchar"1F3F4\Uchar"E0067\Uchar"E0062\Uchar"E0065...`，看起來完全不像是一連串以 UTF-8 編碼的表情符號字元。此外，HarfBuzz 對 TeX 指令一無所知。不知怎地，原始的 TeX 輸入，包括 `\Uchar` 指令，被轉換成了 HarfBuzz 可用、以 UTF-8 編碼的 Unicode 字元，但 *如何*?

答案在於 `\Uchar` 指令的行為：嘗試呼叫 `\codestoemoji` ，使用 `\char` 而不是 `\Uchar` 會失敗，但 *為什麼*?

**\Uchar: 在 \directlua 中展開**

當 `\codestoemoji` 巨集被呼叫時，儲存在巨集定義中的 `\directlua` 指令必須為傳送到 LuaHBTeX 內建 Lua 解譯器準備 Lua 程式碼。這個程式碼準備過程的一部分，是展開原始 Lua 程式碼中出現的任何 TeX/LaTeX 指令，以及使用者提供的任何巨集參數。這個展開過程會產生一個 token 清單，接著再轉回文字，生成傳送給 Lua 解譯器的 Lua 程式碼。為了方便起見，我們重製 Overleaf 文章中的一張圖 [Understanding `\directlua`](/latex/zh-tw/shen-ru-wen-zhang/09-an-introduction-to-luatex-part-2-understanding-directlua.md):

![\directlua 的機制](/files/bc99ac2ea7edadbfe1af2f6e7849ac6e0b80d04a)

這個巨集 `\codestoemoji` 預期透過 `\Uchar` 指令來呼叫，並且， [如本文前面所述](#the-key-difference-expansion), `\Uchar` 是一個可展開的指令，其展開會生成一個字元 token。在 `\directlua`的處理活動中，LuaHBTeX 會展開每個 `\Uchar<字元碼>` 指令，並將 *移除* 每個 `\Uchar<字元碼>` 從輸入中移除，並 *將其替換為* 它替換為相應的展開值：一個代表 `<字元碼>`.

的字元 token。 `\directlua` 在處理的最後階段，最初由 *產生的 token 清單會轉回文字* ，成為送往 Lua 解譯器的 Lua 程式碼（見上圖）。由展開 `\Uchar` 所產生的所有字元 token 也會 *轉回文字*：這種將字元 token 轉為文字的轉換會生成原始 `<字元碼>` 值的 UTF-8 表示。

在我們的範例中，當 Lua 程式碼被生成並準備送往 Lua 解譯器時，"#1" 的巨集輸入已被轉換為一串 UTF-8 文字： `str` 變數現在是一個 UTF-8 文字字串，可以安全地加入 HarfBuzz 緩衝區。

**為什麼 \char 不行？**

直接的答案是因為 `\char` 是 *不* 一個可擴展的指令。不同於 `\Uchar` 指令， `\char` 命令 *不會被移除* 在輸入中於 `\directlua`的初始處理過程中，為了產生標記清單，它們會「穿透」並被納入由 `\directlua`所建立的標記清單中。例如，如果傳給 `\codestoemoji` 的參數包含 `\char"1F3F4` ，LuaHBTeX 會將其轉換為一串標記，並將它們儲存為正在產生的整體標記清單的一部分。

在下一階段的處理，也就是將標記轉回文字時，產生的 Lua 程式碼會包含 *字面字串* `\char"1F3F4` ，位於用來定義我們變數的文字中 `str`。當 `str` 的內容被加入 HarfBuzz 緩衝區時，它不會包含代表表情符號字元「1F3F4」的 UTF-8 編碼序列；它會包含字面字串 `\char"1F3F4`，HarfBuzz 會嘗試對其進行形狀化，而就我們的目的而言，不會產生表情符號字形。順帶一提，字串 `\char"1F3F4` 也會產生 Lua 語法錯誤，除非它是以「長括號字串」建立的——請參見 [什麼是 Lua 跳脫序列](/latex/zh-tw/shen-ru-wen-zhang/09-an-introduction-to-luatex-part-2-understanding-directlua.md#what-are-e2809clua-escape-sequencese2809d3f) 以了解該問題的背景。

如果我們嘗試使用 `\codestoemoji` 取代，改成一個 `\char` 指令，例如：

```latex
\codestoemoji{\char"1F3F4\Uchar"E0067\Uchar"E0062\Uchar"E0065\Uchar"E006E\Uchar"E0067\Uchar"E007F}
```

LuaHBTeX 會失敗並回報類似這樣的語法錯誤：

```latex
[\directlua]:1: 在 '"\c' 附近有無效的跳脫序列。
\codestoemoji ...ing \includegraphics }.}]]) end }

l.75 ...r"E0065\Uchar"E006E\Uchar"E0067\Uchar"E007F}

Lua 直譯器遇到了問題，因此
此 Lua 區塊的其餘部分將被忽略。
```

#### 呼叫 HarfBuzz 形狀化函式

**設定緩衝區參數**

HarfBuzz 有時需要更多關於它要形狀化之文字的資訊。你可以透過設定你的 `<緩衝區變數>` ，使用 *緩衝區方法*，例如：

* `<緩衝區變數>:set_direction(*HarfBuzz 方向*)`;
* `<緩衝區變數>:set_language(*HarfBuzz 語言*)`;
* `<緩衝區變數>:set_script(*HarfBuzz 腳本*)`.

例如，我們需要告知 HarfBuzz，我們的表情符號文字方向是由左至右。為此，我們使用 `set_direction()` 方法在我們的 `<緩衝區變數>` （稱為 `hbbuffer`）上，寫成：

```latex
hbbuffer:set_direction(hblib.Direction.new("ltr"))
```

其中 `hblib.Direction.new("ltr")` 會建立一個「方向物件」，適合透過 Lua 傳遞給 HarfBuzz 引擎。

**執行形狀化**

當緩衝區已適當初始化後，我們就可以透過函式 `shape_full()`請求 HarfBuzz 執行實際的形狀化。在我們的範例中，我們寫：

```latex
local res = hblib.shape_full(hbfont, hbbuffer, {},{})
```

的第 3 與第 4 個參數 `shape_full()` 函式必須是 Lua 表格——我們為兩個參數都使用了空表格「`{}`」。 `shape_full()` 的一般形式如下：

```latex
shape_full(Harfbuzz 字型, Harfbuzz 緩衝區, {字型特性}, {"shaper"}
```

* **`{"shaper"}`**：通常不需要設定，但可用的選項有 `{"ot"}` 或 `{"graphite2"}`。關於「shaper」概念的更多資訊可見於 [HarfBuzz 文件](https://harfbuzz.github.io/shaping-and-shape-plans.html)——注意這裡說明的是底層 C API，而不是以 Lua 為基礎的 `luaharfbuzz` 綁定（實作）。
* **`{font features}`**：這是一個列出 [OpenType 功能](https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist)——由字型支援——你希望 HarfBuzz 在形狀化時套用的特性。

你想使用的任何字型特性都需要透過 `luaharfbuzz` 程式庫函式

```latex
library_instance.Feature.new(feature_string)
```

其中

* `library_instance` 是你的 `luaharfbuzz` 程式庫實例變數（`hblib` 在我們的範例中）；
* `feature_string` 使用 [語法來定義特性](https://github.com/ufytex/luaharfbuzz/wiki/Feature-Strings)。例如有 `+smcp` 用來啟用小型大寫字母，或 `-kern` 用來停用字偶距。

例如：

```latex
local dosmcp = hblib.Feature.new("+smcp")
local nokern = hblib.Feature.new("-kern")
% 像這樣使用你的字型特性
local res = hblib.shape_full(hbfont, hbbuffer, {dosmcp,nokern},{})
```

#### 存取結果：取得字形

最後，如果形狀化操作成功，形狀化後的字形會以緩衝區變數 `hbbuffer` 傳回，我們先前已在程式碼中建立它。

我們透過緩衝區方法存取字形 `get_glyphs()` ，並使用迴圈逐一取得每個字形。請注意，保存字形的 Lua 表格 `hbglyphs` ，在我們的範例中，是從 1 開始索引，而不是從 0。

每個字形的 *字形識別碼* （令人困惑地稱為 `碼點`），以及 HarfBuzz 字型（`hbfont`）會傳遞給 `writePNGglyph()` 函式，該函式會使用字型對該字形的點陣圖表示來建立 PNG 檔案。

`writePNGglyph()` 會輸出一個 PNG 檔名，並回傳該檔名，接著透過 `\includegraphics[scale=0.75]{<fname>}`將（縮放後的）PNG 檔匯入我們的 LaTeX 文件。注意我們如何可以在 Lua 程式碼中直接使用 `\includegraphics` 。

```latex
if (res) then
    local hbglyphs=hbbuffer:get_glyphs()
    % glyphs 表格 hbglyphs 是以 1 為起始索引
    local i = 1
    while hbglyphs[i] \noexpand~= nil do
        local glyph = hbglyphs[i]
        i = i + 1
        local fname=writePNGglyph(hbfont, glyph.codepoint)
        % 縮小我們匯入的 PNG 影像尺寸
        local s = 0.75
        local scal="[scale="..tostring(s).."]"
        tex.print([[\noexpand\includegraphics]]..scal..[[{]]..fname..[[}]])
     end
end
```

### 你可以在 Overleaf 中開啟的完整程式碼

```latex
\documentclass{article}
\usepackage{graphicx}
\begin{document}
\directlua{

% 從 LuaHBTeX 載入 luaharfbuzz 函式庫
hblib=require("luaharfbuzz")

% 在 Overleaf 的伺服器上找到 Noto Color Emoji 字型
pathtofontfile=kpse.find_file("NotoColorEmoji.ttf","truetype fonts")

% 從 Noto Color Emoji 建立 HarfBuzz face 和 HarfBuzz font
hbface = hblib.Face.new(pathtofontfile)
hbfont = hblib.Font.new(hbface)

% 此函式接受一個字型與一個字形 ID：
% 它會擷取字形的 PNG 資料並寫入
% 將其輸出為 .png 檔

function writePNGglyph(hbfontobject, glyphID)

    % 取得字形 PNG 資料
    local pngblob=hbfontobject:ot_color_glyph_get_png(glyphID)
    local pngdata=pngblob:get_data()

    % 為我們的 .png 檔建構檔名
    local fname="Glyph"..glyphID..".png"

    % 寫出 .png 檔並回傳檔名
    local output = assert(io.open(fname, "wb"))
    output:write(pngdata)
    output:close()

    % 回傳供 \includegraphics 使用的檔名
    return fname
end
}

\newcommand{\codestoemoji}[1]{%
\directlua{

local str="#1"
local hbbuffer = hblib.Buffer.new()
hbbuffer:add_utf8(str)

hbbuffer:set_direction(hblib.Direction.new("ltr"))
local res = hblib.shape_full(hbfont, hbbuffer, {},{})

if (res) then
    local hbglyphs=hbbuffer:get_glyphs()
    % 字形表 hbglyphs 是以 1 為起始索引。
    local i = 1
    while hbglyphs[i] \noexpand~= nil do
        local glyph = hbglyphs[i]
        i = i + 1
        local fname=writePNGglyph(hbfont, glyph.codepoint)
        % 縮小我們匯入的 PNG 影像尺寸
        local s = 0.75
        local scal="[scale="..tostring(s).."]"
        tex.print([[\noexpand\includegraphics]]..scal..[[{]]..fname..[[}]])
     end
end
}}

鴨子： \codestoemoji{\Uchar"1F986}

旗幟： \codestoemoji{\Uchar"1F3F4\Uchar"E0067\Uchar"E0062\Uchar"E0065\Uchar"E006E\Uchar"E0067\Uchar"E007F}
\end{document}
```

[在 Overleaf 中開啟這個 luaharfbuzz API 範例。](/latex/zh-tw/shen-ru-wen-zhang/10-an-overview-of-technologies-supporting-the-use-of-colour-emoji-fonts-in-latex.md)

此範例會產生以下輸出：

![Harfbuzzexample.png](/files/e2ad092d8fca9402fff6987c39607ebff8f3a968)

## 加碼段落：表情符號數學趣味

為了以輕鬆的語氣作結，Overleaf 團隊的一名成員使用了 [`emoji` LaTeX 套件](https://ctan.org/pkg/emoji?lang=en) 來建立一個有趣的範例：

```latex
\documentclass{article}
\usepackage{emoji}
\usepackage{unicode-math,fontspec}
\setmainfont{STIX}
\setmathfont{STIX Two Math}
\begin{document}
\newcommand{\emomath}[1]{\text{\emoji{#1}}}
\[
e^{\emomath{droplet} \ln\emomath{smile}}=\emomath{sweat-smile}
\]
\[
e^{\emomath{eye}\emomath{pie}}=-1
\]
\end{document}
```

[在 Overleaf 中開啟這個有趣的範例](https://www.overleaf.com/docs?engine=lualatex\&snip_name=Fun+with+emoji+math\&snip=%5Cdocumentclass%7Barticle%7D%0A%5Cusepackage%7Bemoji%7D%0A%5Cusepackage%7Bunicode-math%2Cfontspec%7D%0A%5Csetmainfont%7BSTIX%7D%0A%5Csetmathfont%7BSTIX+Two+Math%7D%0A%5Cbegin%7Bdocument%7D%0A%5Cnewcommand%7B%5Cemomath%7D%5B1%5D%7B%5Ctext%7B%5Cemoji%7B%231%7D%7D%7D%0A%5C%5B%0Ae%5E%7B%5Cemomath%7Bdroplet%7D+%5Cln%5Cemomath%7Bsmile%7D%7D%3D%5Cemomath%7Bsweat-smile%7D%0A%5C%5D%0A%5C%5B%0Ae%5E%7B%5Cemomath%7Beye%7D%5Cemomath%7Bpie%7D%7D%3D-1%0A%5C%5D%0A%5Cend%7Bdocument%7D)

此範例會產生以下輸出：

![Emojimath2.png](/files/36af52744b2f0c8df1917a42813615cb77d3163b)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://overleaf-pro.ayaka.space/latex/zh-tw/shen-ru-wen-zhang/10-an-overview-of-technologies-supporting-the-use-of-colour-emoji-fonts-in-latex.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
