本文已加入专栏文章目录,归入「进阶使用」文章系列。

本文可以看作对这个发生于 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

如果使用 pdflatexlualatex,直接使用即可。

不同引擎生成的 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 - 修复行引用的超链接跳转位置相关推荐

  1. uni-app - 文本展开 / 收起折叠功能,支持自定义样式(当文本内容超出规定行数后,展开收起折叠的功能)兼容 H5 / App / 小程序且易用更容易修改的插件组件源码,超详细的示例代码及注释

    前言 网上的组件和教程代码都太乱了,根本无法按照自己的需求修改,而且基本上都有兼容性和功能性 BUG. 本文实现了 多行文本展开与折叠组件,灵活性非常高,只完成了核心功能,可随意自定义样式满足您的需求 ...

  2. JS点击进行展开和折叠的功能代码

    最近开发网站需要用到 一些网页前台特效.需要打开网页时部分内容默认隐藏(折叠),然后点击相应的文字或图片进行显示(展开),再点击进行隐藏(折叠).网上查了资料,类似的代码很多,以下是三种可以实现此功能 ...

  3. Saiku设置展示table数据不隐藏空的行数据信息(二十六)

    Saiku设置展示table数据不隐藏空的行数据信息 saiku有个 非空的字段 按钮,点击这个后,会自动的把空的行数据信息给隐藏掉,这里我们来设置一下让其行数据不隐藏,为空的就为空. 主要更改两个文 ...

  4. Java 报表Apache POI API与实现数据行分组折叠

    官方地址:http://poi.apache.org/apidocs/ Apache POI - Javadocs Apache POI Javadocs 可以在此处在线访问 Apache POI的最 ...

  5. Vue - Element el-table 行的展开与折叠

    GitHub Demo 地址 在线预览 效果图 方式一效果图 方式二效果图 要点 使用el-table的span-method方法配合css样式实现展开与折叠 方式一代码 <template&g ...

  6. Java折叠_[Java教程]Jquery中菜单的展开和折叠

    [Java教程]Jquery中菜单的展开和折叠 0 2018-08-15 16:03:38 标签内容 您好:alee 宿舍管理员 密码管理 修改密码 宿舍管理 学生宿舍查询 学生宿舍新增 学生宿舍分配 ...

  7. 表格分组标签:表格行分组中的隐藏功能

    在程序员的认知中,表格中存在行分组标签,也就是thead,tbody,tfoot三个行分组标签.也许你会认为我在这里还是为大家继续讲解thead,tbody,tfoot三个标签,那就大错特错了 今天除 ...

  8. POI设置和读取excel分组信息,多级分组设置

    一.设置分组信息 1.一级分组信息设置 sheet.groupRow(1, 2); sheet.groupColumn(1, 2); 2.多级分组信息设置 poi中提供的方法是没有级别这个参数,通过查 ...

  9. 热图3:热图行列分组信息注释

    这次我们要让热图更加复杂化. 实际上,有些情况下做热图可能只需要标注对照组.实验组即可.但是大多时候,可以在热图上体现更多信息,比如,除了分组,还有不同得处理.年龄.性别.疾病阶段等等,以及不同功能基 ...

最新文章

  1. python导入包库的两种语句import 和 from … import
  2. Content-type的说明即HTTP请求头的类型整理
  3. JAVAEE框架之Spring AOP
  4. 好程序员技术文档HTML5开发中的javascript闭包
  5. python list 实现原理,Python 列表(List)的底层实现原理分析
  6. 中专生计算机教案,[定稿]计算机基础教案中专V8.1(全文完整版)
  7. 月薪20k的web前端开发程序员,他们都会的这6招
  8. 通过chrome console 快速获取网页连接
  9. 利用3D转换实现旋转木马
  10. 离散数学计算机科学与技术答案,湘潭大学计算机科学与技术刘任任版离散数学课后习题答案---第二学期--图论与组合数学...
  11. 知行功夫为本,找对心中的英雄,过好平凡的生活
  12. SUBMAIL群发邮件API接口-Mail/send
  13. 不是HR,Leader你会面试应聘者吗(如何起好手中的扑克牌)
  14. 根据拼音首字母进行过滤的combobox
  15. 中文分词之维特比算法详解
  16. win10 64位系统 打开光盘出现339错误 缺少mscomctl.ocx组件 解决方案
  17. python实例豆瓣代码_Python实例:通过selenium模拟登陆豆瓣
  18. 怎么有的帖子发不了啊
  19. 【绝对管用】彻底的卸载干净oracle 11g
  20. Python之base64加密解密

热门文章

  1. python3-pandas 缺失数据的处理
  2. python3-numpy IO load()、save()、savez()、loadtxt()、savetxt()、tofile()、fromfile()
  3. mysql 体重 类型 身高_体重较轻,身高较高的身材怎样挑选单板?
  4. 造大专计算机学历,广昌县职业技术学校计算机应用专业助您 掌握一技之长获大专学历...
  5. C语言 数组排序 – 插入法排序 - C语言零基础入门教程
  6. android懒加载单实例,【 Android 10 设计模式 】系列 -- 单例
  7. seata执行闪退_Seata 1.2.0的配置以及踩坑记录
  8. python构建字典实现英文大写字母与ascii编码的转换_Python:将复杂的字符串字典从Unicode转换为ASCII...
  9. PHP即将退出,PHP4即将退出历史舞台
  10. 1个显示器分割2画面_我家房子100㎡,原始设计有2个卫生间,纠结保留1个还是2个...