datatables 行分组信息展开与折叠的功能实现_[LaTeX 尝试] fancyvrb - 修复行引用的超链接跳转位置
本文已加入專欄文章目錄,歸入「進階使用」文章系列。
本文可以看作對這個發生于 2019 年 7 月中旬的 TeX-SX 上自問自答的展開說明。那個回答中避免了 python 的使用,而是利用 zref 宏包把位置信息以文本形式在 pdf 中呈現,好處是不用引入 python,壞處是如果寫成文章,需要額外介紹 zref 的使用。
問題的引入
fancyvrb 宏包提供了高度可配置的抄錄環境,功能大致上和 listings 相當。
有些配置項提供了「跳出抄錄環境,回到一般 latex」的功能,例如 commandchars。它接受一串三個符號組成的值,分別代表命令開始、左側分組、右側分組(實際可以歸結到 catcode,此處略過)。
直接看 fancyvrb 文檔 Sec. 4.1.12 的例子
文檔截圖中的第二個例子展示了一種使用方式,利用 commandchars 為抄錄環境的某一行增加標簽(label),然后在正文中引用(ref)它來獲得行號。特別地,hyperref 包還會自動為行號添加超鏈接,點擊行號就能跳轉到抄錄環境中的對應行。
上一段的最后一個分句,只是描述了我們期望的行為,實際的編譯和測試結果不是這樣的,點擊引用(ref)得到的行號后,無法跳轉到對應行。
工具和示例準備
除了靠手去點超鏈接,然后根據閱讀器跳轉的位置來判斷和分析,還可以借助工具直接讀取 PDF 文件里的超鏈接跳轉位置。例如,使用 Python 的 PyPDF2 庫,
from PyPDF2 import PdfFileReaderfname = 'xxx.pdf' pdf = PdfFileReader(fname) named_dests = pdf.namedDestinations.items()print('Coordinates of named destinations') for k, v in named_dests:print(k, [v['/Left'], v['/Top']])top = None print('nVertical distances between labels of line numbers') for k, v in named_dests:if 'FancyVerbLine' in k or 'lstnumber' in k:curr = v['/Top']if top is not None:print(k, float(top - curr) / 72 * 72.27, 'pt')top = curr有關 PDF 格式的補充說明:
- 「超鏈接跳轉位置」在 PDF 格式中稱為 named destination
- 每個 named destination 擁有一個全文檔唯一的名稱
- 它的內容,在本文中我們關心的是橫縱坐標信息,有時也關心它的目標頁面
- 它的使用,是成為某個 annotation(例如 hyperref 自動添加的)的跳轉目標
有關上述 python 腳本的說明:
- 第一組 print,輸出文檔內所有 named destinations 的名稱和坐標
- 第二組 print,僅輸出與 fancyvrb(和 listings,用于對照) 有關的相鄰 named destinations 的縱坐標差值
同時,使用以下 latex 示例文檔
(注意,示例中的 newpagenull 是特意添加的,為的是保證 pdf 閱讀器有跳轉,也就是把第一頁往上翻,的空間)
documentclass{article} usepackage{fancyvrb} usepackage{hyperref}% <possible config appears here>begin{document} begin{Verbatim}[numbers=left, commandchars={}] firstlabel{vrb:1} secondlabel{vrb:2} thirdlabel{vrb:3} forthlabel{vrb:4} fifthlabel{vrb:5} sixthlabel{vrb:6} aend{Verbatim}ref{vrb:1}, ref{vrb:2}, ref{vrb:3}, ref{vrb:4}, ref{vrb:5}, and ref{vrb:6} newpagenull end{document}最后,需要留意示例文檔的編譯方式
如果使用 xelatex,因為默認情況下 xdvipdfmx 會去掉未使用的 named destinations,并簡化所有 named destinations 的名稱,所以需要通過選項讓 xdvipdfmx 不對 named destinations 自動優化。
xelatex -no-pdf xxx xelatex -no-pdf xxx xdvipdfmx -C 0x0010 xxx如果使用 pdflatex 或 lualatex,直接使用即可。
不同引擎生成的 pdf 中,named destination 的信息有微小差異。本文默認使用 xelatex。
初步嘗試
編譯 latex 示例文檔生成 pdf,點擊那六個超鏈接,可以發現它們都跳轉到同一位置。
示例文檔生成的 pdf執行 python 腳本讀取這個 pdf 里的信息,會獲得如下輸出
Coordinates of named destinations Doc-Start [133.77, 667.2] page.1 [132.77, 705.06] page.2 [132.77, 705.06]Vertical distances between labels of line numbers似乎六個 label 根本沒有生成六個不同的跳轉目標,連一個也沒有生成。如果直接使用 xelatex xxx.tex,生成的 pdf 里就只有一條記錄
Coordinates of named destinations 0 [133.77, 667.2]如果繼續使用 PyPDF2 的功能去看第一頁的所有 annotations 的跳轉目標(此處略去代碼),就可以完全確定:六個 label 完全沒有生成新跳轉目標,六個 ref 都跳轉去了當前頁的開始處(具體位置是 page.1 跳轉目標標記的、頁面版心的左上角)。
以上是從 pdf 一側進行的分析和探索。如果從 latex 一側進行,從相關宏包的源碼入手,則能了解到以下事實:
- 在 fancyvrb 內部負責遞增行號的宏 FV@refstepcounter 的定義中,重寫了一遍 latex2e 中 refstepcounter 的原始定義,刻意避免了直接使用 refstepcounter
- hyperref 重定義后的 refstepcounter 會在展開時插入新的跳轉目的地, 并把該目的地儲存在 @currentHref 中以供 label 在內部引用(這則「事實」的展開介紹,可能需要額外的一篇或多篇文章,此處略過)
這樣,因為fancyvrb 在遞增行號時沒有使用 refstepcounter,所以對應于新行號的跳轉位置無法生成,@currentHref 得不到更新,label 關聯的就變成了上一次更新過的 @currentHref 信息,也即 hyperref 在每一頁開頭默認插入的跳轉目標。
第一步嘗試很簡單,讓 FV@refstepcounter 成為 refstepcounter
letFV@refstepcounterrefstepcounter繼續嘗試
修改保存、編譯 tex 文件、執行 python,會發現問題沒有完全解決。
Coordinates of named destinations Doc-Start [133.77, 667.2] FancyVerbLine.1 [133.77, 667.2] FancyVerbLine.2 [133.77, 657.18] FancyVerbLine.3 [133.77, 657.18] FancyVerbLine.4 [133.77, 645.22] FancyVerbLine.5 [133.77, 633.22] FancyVerbLine.6 [133.77, 621.31] page.1 [132.77, 705.06] page.2 [132.77, 705.06]Vertical distances between labels of line numbers FancyVerbLine.2 10.057574999999998 pt FancyVerbLine.3 0.0 pt FancyVerbLine.4 12.004850000000001 pt FancyVerbLine.5 12.044999999999998 pt FancyVerbLine.6 11.954662499999998 pt從 python 腳本的輸出可以看出,雖然現在每個 label 都對應了不同的跳轉目標,但是目標之間的縱坐標差異并不一致。
- 預期輸出是,每兩個相鄰目標,在縱坐標上都相差 12pt(對應 latex 中 baselineskip 儲存的值,也即行距)
- 實際得到的是,
- line 2 和 line 1 只差了 10pt(與字號有關,與行距無關,例如用 fontsize{10}{50}selectfont 修改行距后仍然是 10pt),
- line 3 和 line 2 差 0pt,
- 后面的正常。
推斷,FV@refstepcounter 展開的位置有問題。
根據對類似示例代碼的手動展開(見項目 muzimuzhi/latex-expansion 中以 fancyvrb 打頭的文件),判斷縱坐標差異應該源于 fancyvrb 對抄錄環境前三行的特殊處理(可能是為了控制在環境中間換頁的條件)具體涉及命令 FV@ListProcessLine@(i|ii|iii|iv)。這幾個宏的具體作用,限于時間和水平筆者還沒能了解清楚。
筆者采取了一個討巧(但可能帶來其他未知問題)的解決方案:把 FV@refstepcounter(具體是調用它的 FV@StepLineNo 宏 )的展開位置延遲到抄錄行文本剛要輸出之前,以保證通過 refstepcounter 遞增行號并插入新跳轉目標時,所處高度和抄錄文本行一致。
這樣,要做的修改就很簡單:把 FV@StepLineNo 從原來的位置刪掉,再在一個新的位置插入。
usepackage{etoolbox}% move FV@StepLineNo into FV@ListProcessLine patchcmdFV@@PreProcessLine{FV@StepLineNo}{}{}{fail}patchcmdFV@ListProcessLine{kernleftmargin}{FV@StepLineNokernleftmargin}{}{fail}從 pdf 閱讀器里的點擊跳轉效果,和 python 腳本的輸出看,問題似乎修好了。
其他
- 包含修改代碼的 tex 文檔,見項目 muzimuzhi/latex-examples 中的文件 fancyvrb-improvements.tex。文件中還包含修改行號引用風格的代碼,會在后續文章里介紹。
- 最困難的部分可能是定位問題和知道可以把 FV@StepLineNo 挪到哪,筆者主要是通過手動展開來探索的。
- fancyvrb 被其他一些宏包依賴,依賴關系比較深的是 tcolorbox -> minted -> fvextra -> fancyvrb,文中介紹的嘗試,并未經過充分測試。
總結
以上是生活随笔為你收集整理的datatables 行分组信息展开与折叠的功能实现_[LaTeX 尝试] fancyvrb - 修复行引用的超链接跳转位置的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 完成数独的算法 python_pytho
- 下一篇: 电脑怎么老重启 电脑频繁重启该怎么办