跳轉到內容

LaTeX/Plain TeX

來自華夏公益教科書,開放的書籍,開放的世界

LaTeX

入門
  1. 簡介
  2. 安裝
  3. 安裝額外的軟體包
  4. 基礎
  5. 如何獲取幫助

常用元素

  1. 文件結構
  2. 文字格式
  3. 段落格式
  4. 顏色
  5. 字型
  6. 列表結構
  7. 特殊字元
  8. 國際化
  9. 旋轉
  10. 表格
  11. 標題建立
  12. 頁面佈局
  13. 自定義頁面頁首和頁尾‎
  14. 匯入圖形
  15. 浮動體、圖形和標題
  16. 腳註和邊注
  17. 超連結
  18. 標籤和交叉引用
  19. 首字母

機制

  1. 錯誤和警告
  2. 長度
  3. 計數器
  4. 盒子
  5. 規則和撐杆

技術文字

  1. 數學
  2. 高階數學
  3. 定理
  4. 化學圖形
  5. 演算法
  6. 原始碼清單
  7. 語言學

特殊頁面

  1. 索引
  2. 術語表
  3. 參考文獻管理
  4. 更多參考文獻

特殊文件

  1. 科學報告(學士報告、碩士論文、博士論文)
  2. 信件
  3. 簡報
  4. 教師專區
  5. 簡歷
  6. 學術期刊(MLA、APA 等)

建立圖形

  1. 介紹過程化圖形
  2. MetaPost
  3. Picture
  4. PGF/TikZ
  5. PSTricks
  6. Xy-pic
  7. 建立 3D 圖形

程式設計

  1. Plain TeX
  2. 建立軟體包
  3. 建立軟體包文件
  4. 主題

其他

  1. 模組化文件
  2. 協作編寫 LaTeX 文件
  3. 匯出到其他格式

幫助和建議

  1. 常見問題解答
  2. 技巧和竅門

附錄

  1. 作者
  2. 連結
  3. 軟體包參考
  4. LaTeX 文件示例
  5. 索引
  6. 命令詞彙表

編輯此框編輯目錄


當你使用 LaTeX 宏時,你會發現它非常有限。你可能會好奇,你每天使用的所有這些軟體包是如何用這麼少的程式碼實現的。事實上,LaTeX 是一套 Plain TeX 宏,大多數軟體包都使用 Plain TeX 程式碼。Plain TeX 的級別要低得多,它有更多功能,但學習曲線陡峭,程式設計複雜。

除了少數例外,你可以在有效的 LaTeX 文件中使用完整的 Plain TeX 語言,反之則不然。

詞彙

[edit | edit source]

為了避免混淆,有必要解釋一些術語。

  • 一個 是在開括號之後和匹配的閉括號之前的任何內容。
  • 一個 標記 是一個字元、一個控制序列或一個組。
  • 一個 控制序列 是任何以 \ 開頭的內容。它不會按原樣列印,而是根據其型別由 TeX 引擎進行擴充套件。
  • 一個 命令 (或 函式)是一個控制序列,它可能擴充套件為文字,控制序列的(重新)定義等。
  • 一個 原語 是一個在 TeX 引擎中硬編碼的命令, 它不是用 Plain TeX 編寫的。
  • 一個 暫存器 是 TeX 處理變數的方式。它們的數量是有限的(在經典 TeX 中,每種型別的暫存器有 256 個,在 e-TeX 中有 32767 個)。
  • 一個 長度 是一個包含長度的控制序列(一個數字後跟一個單位)。參見 長度
  • 一個 字型 是一個引用字型檔案的控制序列。參見 字型
  • 一個 盒子 是一個用於列印的物件。出現在紙張上的任何內容都是一個盒子:字母、段落、頁面......參見 盒子
  • 一個 膠水 是一個特定的空間量,當盒子被連線在一起時,它被放置在盒子之間。
  • 一個 計數器 是一個包含數字的暫存器。參見 計數器

可能還有更多的術語,但我們希望現在已經足夠了。

類別碼

[edit | edit source]

在 TeX 中,一些字元具有特殊的含義,而不是列印相關的字形。例如,\ 用於引入控制序列,預設情況下不會列印反斜槓。

為了區分字元的不同含義,TeX 將它們分成 類別碼,簡稱 類別碼。TeX 中有 16 種類別碼。

TeX 的一項強大功能是它能夠重新定義語言本身,因為有一個 \catcode 函式,它可以讓你更改任何字元的類別碼。

然而,不建議這樣做,因為它會使程式碼難以閱讀。如果你在一個類或樣式檔案中重新定義了任何類別碼,請確保在檔案末尾將其恢復。

如果你在文件中重新定義了類別碼,請確保在序言之後進行,以防止與軟體包載入衝突。

程式碼 描述 預設集
0 跳脫字元和控制序列 \
1 組的開始 {
2 組的結束 }
3 數學轉移 $
4 對齊製表符 &
5 行尾 ^^M (ASCII 回車)
6 宏引數 #
7 上標 ^^^K
8 下標 _^^A
9 忽略字元 ^^@ (ASCII 空字元)
10 空格 ^^I (ASCII 水平製表符)
11 字母 A...Za...z
12 其他字元 不在其他類別碼中列出的所有內容。最值得注意的是 @。
13 活動字元 ~^^L (ASCII 換頁符)
14 註釋字元 %
15 無效字元 ^^? (ASCII 刪除)

活動字元

[edit | edit source]

活動字元類似於宏:它們是單個字元,將在任何其他命令之前進行擴充套件。

\catcode`| = 13
\def|{\TeX}
...
This is a stupid example of |.

這是一個關於 TeX 的愚蠢示例。

請注意,活動字元需要直接後跟定義,否則編譯將失敗。

示例

[edit | edit source]
Texinfo

Texinfo 使用類似於 TeX 的語法,但有一個主要區別:所有函式都以 @ 而不是 \ 開頭。這並非偶然:它實際上使用 TeX 列印檔案的 PDF 版本。它基本做的就是輸入texinfo.tex它重新定義了控制序列字元。可能的實現

\catcode`\@=0
@def@@{@char64} % To write '@' character.
\catcode`\\=13 @def\{{@tt @char92}}

The @TeX command was previously written '\TeX'. It is now written '@@TeX'.

TeX 命令以前寫成 '\TeX'。現在寫成 '@TeX'。

透過這種重新定義,'@' 現在應該引入每個命令,而 '\' 實際上將列印一個反斜槓字元。

專案符號

有些人可能發現 LaTeX 列表環境的語法有點繁瑣。這裡有一個快速定義類似維基的專案符號的方法

\catcode`| = 13
\def|{\item {--}}
\def\itemize#1{{\leftskip = 40 pt #1 \par}}

\itemize{
| First item
| Second item
}
美元符號和數學

如果您要列印很多“美元”符號,您可能最好更改數學移位字元。

\catcode`$ = 11
\catcode`| = 3

It costs $100.
Let's do the math: |50+50=100|. Let's highlight it:
||50+50=100||

\makeatletter\makeatother

[edit | edit source]

如果您進行了一些 LaTeX 程式設計,您一定遇到過這兩個命令,\makeatletter\makeatother

在 TeX 中,'@' 字元預設屬於類別碼 11 字母。這意味著您可以將其用於宏名稱。LaTeX 利用類別碼來指定規則:所有非公共的、內部的宏,其名稱中至少包含一個 '@' 字元,這些宏不應由終端使用者訪問。在文件中,LaTeX 將 '@' 的類別碼更改為 12,即 其他

這就是為什麼當您需要訪問 LaTeX 內部函式時,必須將所有訪問私有函式的命令括在 \makeatletter\makeatother 之間。它們所做的只是更改類別碼

\def\makeatletter{\catcode`@ = 11}
\def\makeatother{\catcode`@ = 12}

普通 TeX 宏

[edit | edit source]

\newcommand\renewcommand 是 LaTeX 特定的控制序列。它們檢查沒有現有的命令被新定義所覆蓋。

在普通 TeX 中,用於宏定義的原語不會對可能的覆蓋進行檢查。您需要確保沒有破壞任何東西。

語法是

\def<macroname>#1<sep1>#2<sep2>{macro content, use of argument #1, blah, #2 ...}

您可以在引數之間使用(幾乎)任何字元序列。例如,讓我們編寫一個簡單的宏,它將小數點分隔符從點更改為逗號。首先嚐試

\def\pointtocomma #1.#2{(#1,#2)}
%%...

\pointtocomma 123.456

這將列印 (123,4)56。我們添加了括號只是為了突出顯示這裡的問題。每個引數是最短的可能的輸入序列,與宏定義匹配,包括分隔符。因此 #1 匹配直到第一個點的所有字元,而 #2 僅匹配第一個標記, 第一個字元,因為它之後沒有分隔符。

解決方案:新增第二個分隔符。空格可能看起來很方便

\def\pointtocomma #1.#2 {(#1,#2)}

一般來說,每當您希望使用特定分隔符獲得多個引數時,都要考慮最後一個分隔符。如果您不想使用分隔符,那麼普通 TeX 宏的使用方式與 LaTeX 宏相同(沒有預設引數)

\def\mymacro#1#2#3{{\bf #1}#2{\bf #3}}
%% ...
\mymacro{word1}{word2 word3}{!!!}

擴充套件定義

[edit | edit source]

TeX 還有另一個定義命令:\edef,它代表 擴充套件定義。語法保持不變

\edef<macroname><argumentslist>{<expanded content>}

內容在使用 \edef 的地方被擴充套件(但不會執行, 列印),而不是在定義的宏被使用的地方。宏擴充套件並不總是顯而易見的...

示例

\def\intro{Example}
\edef\example#1{\intro~---~#1}
\def\intro{Exercise}

\example{This is an example}

這裡 \intro 的重新定義對 \example 不會有任何影響。

全域性定義

[edit | edit source]

定義僅限於其範圍。但是,有時將宏定義在一個組中,使其在該組之外以及直到文件結束時仍然有效,這可能很方便。這就是我們所說的 全域性定義

{
\def\LocalTeX{Local\TeX}
\global\def\GlobalTeX{Global\TeX}
}
I can still access the \GlobalTeX{} macro here.

您也可以將 \global 命令與 \gdef 結合使用。

這兩個命令都有快捷方式

  • \gdef 用於 \global\def
  • \xdef 用於 \global\edef

長定義

[edit | edit source]

之前的定義命令不允許您在多個段落中使用它們, 包含 \par 命令(或雙行換行符)的文字。

您可以在定義之前加上 \long 命令,以允許使用多段落引數。

示例

\long\def\dummy#1{#1}
\dummy{First paragraph\par Second paragraph}

外部定義

[edit | edit source]

此字首宏阻止定義在某些上下文中使用。它有助於合併宏並使其因錯誤的上下文而更不容易出錯。外部宏 旨在在任何上下文之外使用,因此得名。

例如,以下程式碼將失敗

\outer\def\test{a test}
\def\failure{\test}

外部宏不允許出現在

  • 宏引數
  • 跳過的條件
  • ...

letfuturelet

[edit | edit source]

\let<csname><token>\expandafter\def\expandafter<csname>\expandafter{<content>} 相同。它定義了一個新的控制序列名稱,該名稱等效於指定的 token。該 token 通常是另一個控制序列。

請注意,\let 只會擴充套件 token 一次,這與 \edef 相反,\edef 將遞迴擴充套件,直到不再可能進一步擴充套件。

示例[1]

Using let:\par
\def\txt{a}
\def\foo{\txt}
\let\bar\foo
\bar % Prints a
\def\txt{b}
\bar % Prints b

Using edef:\par
\def\txt{a}
\def\foo{\txt}
\edef\bar{\foo}
\bar % Prints a
\def\txt{b}
\bar % Prints a

\futurelet<csname><token1><token2>... 的工作方式略有不同。首先,token2 被分配給 csname,然後 TeX 處理 <token1><token2>... 序列。因此,\futurelet 允許您在使用標記後立即分配它。

特殊的控制序列名稱

[edit | edit source]

某些宏的名稱可能無法直接寫入。對於由宏名稱組成的宏名稱,情況就是如此。示例

\def\status{full}
\def\varempty{This is empty}
\def\varfull{This is full}

\csname var\status \endcsname

最後一行將根據 \status 列印一個句子。

此命令實際上與 \string 相反,\string 會列印一個控制序列名稱,而不會擴充套件它

{\tt \string\TeX}

\TeX

控制擴充套件

[edit | edit source]

\expandafter{token1}{token2} 將在 token1 之前擴充套件 token2。當需要擴充套件 token2 但由於 token1 而無法擴充套件時,這有時是必需的。

{\tt \expandafter\string\csname TeX\endcsname}

\TeX

\noexpand 有助於對 \edef 中擴充套件的內容進行細粒度控制。示例

\def\intro{Example}
\def\separator{~---~}
\edef\example#1{\intro\noexpand\separator#1} 

\example{no expand makes the separator dynamic in an {\tt \string\edef}.}

\def\intro{For instance}
\def\separator{~:~}

\example{the separator changed, but not the first word.}


\the 控制序列可以讓你看到各種 TeX 型別的內容

  • 類別碼
  • 字元定義
  • 字型引數
  • 內部引數
  • 長度
  • 暫存器
  • ...

示例

Text dimensions: $ \the\hsize \times \the\vsize $

暫存器

[編輯 | 編輯原始碼]

暫存器是一種型別的變數。它們的數量有限,從 0 到 255。共有 6 種不同的型別

型別 描述
盒子 一個盒子
計數器 一個整數
尺寸 一個長度
粘性 (mu 單位) 一個粘性 (mu 單位)
粘性 一個粘性
令牌 一個令牌序列

TeX 在內部使用一些暫存器,所以最好不要使用它們。

保留暫存器列表

  • \box255 用於頁面的內容
  • \count0-\count9 用於頁碼編號

臨時暫存器(可自由使用)

  • \box0-\box254
  • \count255
  • \dimen0-\dimen9
  • \muskip0-\muskip9
  • \skip0-\skip9

使用 '=' 控制字元分配暫存器。對於盒子暫存器,請使用 \setbox 命令。

\count255=17
\setbox\mybox=\hbox{blah}

你可以使用以下保留宏之一來防止任何衝突

\newbox
\newcount
\newdimen
\newmuskip
\newskip
\newtoks

這些宏使用以下語法:\new*<csname>。例如

\newbox\mybox
\setbox\mybox=\hbox{blah}

這些命令不能在宏內部使用,否則每次呼叫宏都會保留另一個暫存器。

你可以使用 \the 命令列印暫存器。對於計數器,請改用 \number 命令。對於盒子,請使用 \box 命令。

\the\hsize
\number\count255
\box\mybox

TeX 的算術功能非常有限,雖然這個基礎足以擴充套件到一些有趣的功能。三個主要功能

\advance <register> by <number>
\multiply <register> by <number>
\divide <register> by <number>

register 可以是計數器、尺寸、粘性 (mu 單位) 或粘性型別。它對盒子和令牌沒有意義。

條件語句

[編輯 | 編輯原始碼]

基本語法是

\if* <test><true action>\fi
\if* <test><true action>\else<false action>\fi

其中 \if* 是以下命令之一。

控制序列 描述
\if <a><b> 如果兩個字元碼相等,則為真。
\ifcat <a><b> 如果兩個類別碼相等,則為真。
\ifdim <a><rel><b> 尺寸關係,要麼<, >要麼=.
\ifeof 如果檔案末尾或不存在的檔案,則為真。
\iffalse 始終為假。
\ifhbox <reg> 如果盒子暫存器包含水平盒子,則為真。
\ifhmode 如果在水平模式下,則為真。
\ifinner 如果在內部模式下,則為真。
\ifmmode 如果在數學模式下,則為真。
\ifnum <a><rel><b> 數字關係,要麼<, >要麼=.
\ifodd <num> 如果數字是奇數,則為真。
\iftrue 始終為真。
\ifvbox <reg> 如果盒子暫存器包含垂直盒子,則為真。
\ifvmode 如果在垂直模式下,則為真。
\ifvoid <reg> 如果盒子暫存器為空,則為真。
\ifx <a><b> 如果兩個宏展開為相同,或者如果兩個字元碼相等,或者如果兩個類別碼相等,則為真。

示例

\ifnum 5>6
This is true
\else
This is false
\fi

這是假的


自定義條件語句

[編輯 | 編輯原始碼]

你可以使用 \newif 命令建立新的條件語句(作為一種 布林變數)。透過這些自定義條件語句,你可以以一種優雅的方式控制程式碼的輸出。說明條件語句用法的最佳方法是透過一個例子。

必須生成兩個版本的文件。一個版本是針對 A 組的,另一個版本是針對其他所有人的(即不屬於 A 組的)。

1. 我們使用 \newif 來定義我們的條件語句(即布林變數)。

\newif\ifgroupA

2. 以下方式為我們的條件語句設定一個值(真或假)

\groupAtrue % or
\groupAfalse

也就是說

\<conditionalsname>true
\<conditionalsname>false

取決於我們希望在條件語句中設定哪個值。

3. 現在我們可以在之後的任何地方使用我們的條件語句,在 if 控制結構 中。

\ifgroupA
  % Here we write the code of the document that is
  % intended for the group A
\else
  % Here we write the code of the document that is 
  % intended for the rest of the people
\fi

一個完整的例子是

\newif\ifdirector 

%I set the conditional to false
\directorfalse

\ifdirector
 I write something for the director.
\else
 I write something for common people.
\fi

我寫一些針對普通人的東西。

情況語句

[編輯 | 編輯原始碼]

語法是 \ifcase <number><case0>\or<case1>\or...\else<defaultcase>\fi。如果 number 等於情況編號,它的內容將被列印。注意,它從 0 開始。

\ifcase 2 a\or b\or c\or d\else e\fi

c

\else 用於指定預設情況(當之前的任何情況都沒有匹配時)。

基本語法是

\loop <content> \if*<condition><true action>\repeat

與往常一樣,contenttrue action 是任意的 TeX 內容。\if* 指的是任何 條件語句。注意,沒有 false action,你不能在 \if*\repeat 之間放置 \else。在某些情況下,這將與你想要的結果相反;你需要更改條件或使用 \newif 定義一個新的條件語句。例如

\count255 = 1
\loop
  \TeX
\ifnum\count255 < 10
\advance\count255 by 1
\repeat

上面的程式碼將列印十次 TeX。

什麼都不做

[編輯 | 編輯原始碼]

有時,告訴 TeX 你什麼都不想做可能很有用。有兩個命令可以做到這一點:\relax\empty

經典示例

\def\myspace{\hskip 25pt\relax}
\myspace{} plus 10pt

如果在命令之後遇到 plusminus\relax 將阻止出現不希望的行為。

\empty\relax 之間的區別在於展開:\empty 在宏展開後會消失。

TeX 字元

[編輯 | 編輯原始碼]

我們可以使用 \char {charcode} 命令列印所有字元。charcode 實際上是位元組值。例如

\char65 = \char `A = \char `\A

大多數字符對應於 ASCII 值(例如 A-Za-z),一些字元替換了 ASCII 中的不可列印字元。

chardefmathchardef

[編輯 | 編輯原始碼]

你可以定義控制序列以展開為特定字元。語法是 \chardef<control sequence>=<charcode>。以下序列執行相同的操作。

\chardef\myA=65
\chardef\myA=`A
\chardef\myA=`\A

示例

\mathchardef\alphachar = "010B
$\alphachar$

字型編碼對映

[編輯 | 編輯原始碼]

我們可以使用上面的原語列印字型編碼對映。

\count255 = 0
\loop
  [\number\count255 =\char\number\count255]
\ifnum\count255 < 127
\advance\count255 by 1
\repeat

另一個版本,使用不同的字型,每行一個條目

\count255 = 0
\loop
  [\number\count255 =
    \char\number\count255 \ 
    {\tt \char\number\count255}
    {\it \char\number\count255}
  ]
  \hfil\break
\ifnum\count255 < 127
\advance\count255 by 1
\repeat

逐字文字行和空格

[編輯 | 編輯原始碼]

發現 (La)TeX 將所有空白都視為同一型別的間距粘性,這一點令人困惑。Plain TeX 提供了一些命令來保留你寫的間距和換行符

\begingroup
\obeylines
\obeyspaces
Relevant text here
\endgroup

這意味著你可能需要組合自己的逐字文字環境和你的命令

\newenvironment{myverbatim}{\begingroup \obeylines \obeyspaces}{\endgroup}
\newcommand{\mycommand}[n]{do something with #1 .. #n}

然後在你的 tex 檔案中

\begin{myverbatim}
\mycommand{
whichever text it is important you
preserve the spacing and newslines
for, like when you want to generate
a verbatim block later on.
}
\end{myverbatim}

定義宏的宏

[編輯 | 編輯原始碼]

在某些情況下這很有用,例如定義語言命令,如多語言版本中所述,終端使用者可以編寫

\en{some english text}
\de{etwas deutscher Text}

並確保它切換到相應的 Babel 語言。

讓我們定義一個宏,它將定義語言命令,例如。這些命令很簡單:如果引數是\locale變數的值,則相應的宏直接列印其內容。否則,它什麼也不做。

基本上,我們想做的事情非常簡單:定義一堆像這樣的宏

\newcommand{\de}[1]{#1}
\newcommand{\en}[1]{}
\newcommand{\fr}[1]{}

在前面的程式碼片段中,只有\de命令將輸出其內容,\en\fr將什麼也不列印。這就是我們想要的。當您想自動化任務時,或者您有許多語言,並且想要更改語言選擇時,問題就出現了。您只需要移動#1,但這很不方便,而且無法從命令列選擇 Babel 語言。仔細考慮一下...

我們將做的是根據\locale變數的值(或您選擇的任何變數)動態定義語言命令。因此使用來自ifthen包的\equal命令。

由於用 LaTeX 幾乎不可能寫出來,我們將使用一些 Plain TeX。

\def\locale{de}

\def\localedef#1{
  \ifthenelse{ \equal{\locale}{#1} }{
    %% Set the Babel language.
    %% Define the command to print the content.
  }{
    %% Define the command to print nothing.
  }
}

另一個問題出現了:如何定義一個名稱是變數的命令?在大多數程式語言中,這根本不可能。我們可以嘗試寫的是

\def\#1 #1{#1}

它將因兩個原因而失敗。

  1. 最後兩個“#1”應該指的是宏的引數,但它們首先擴充套件到\localedef宏的第一個引數,因為它們在該宏的正文中。
  2. \#1擴充套件為兩個標記:“#”和“1”,而\def命令將失敗,因為它需要有效的控制序列名稱。

問題 1 的解決方案很簡單:使用“##1”,它將在宏執行時擴充套件為“#1”。

對於問題 2,它有點棘手。有可能告訴tex某個特定標記是控制序列。這就是\csname...\endcsname的用途。但是

\def\csname#1\endcsname ##1{##1}

將失敗,因為它將重新定義\csname為“#1”,這不是我們想要的,那麼tex將遇到\endcsname,這將導致錯誤。

我們需要延遲\def的擴充套件,告訴tex首先擴充套件\csname內容,然後對其應用\def。有一個命令可以做到這一點:\expandafter{token1}{token2}。它將在{token1}之前擴充套件{token2}

最後,如果我們想從命令列設定語言,我們必須能夠設定\locale變數,以便原始碼中的變數是預設值,可以被命令列中的變數覆蓋。這可以透過\providecommand來實現

\providecommand\locale{fr}

最終程式碼是

%% Required package.
\usepackage{ifthen}
 
%% TeX function that generates the language commands.
\def\localedef#1#2{
  \ifthenelse{ \equal{\locale}{#1} }{
    \selectlanguage{#2}
    \expandafter\def\csname#1\endcsname ##1{##1}
  }{
    \expandafter\def\csname#1\endcsname ##1{}
  }
}
 
%% Selected language. Can be placed anywhere before the language commands.
\providecommand\locale{fr}
 
%% Language commands.
\localedef{de}{ngerman}
\localedef{en}{english}
\localedef{fr}{frenchb}
%% ...

您可以使用以下命令進行編譯

latex '\providecommand\locale{en}\input{mydocument.tex}'

筆記和參考資料

[編輯 | 編輯原始碼]
  1. 來自 tex.stackexchange.com: \let 和 \edef 之間的區別是什麼?
進一步閱讀


上一節:宏 索引 下一節:建立包
華夏公益教科書