V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
krjt
V2EX  ›  分享创造

pdf.tocgen - 一个能够为 PDF 自动生成目录的命令行工具集

  •  
  •   krjt · 2020-07-29 08:46:58 +08:00 · 3916 次点击
    这是一个创建于 1360 天前的主题,其中的信息可能已经有所发展或是发生改变。
                              in.pdf
                                |
                                |
         +----------------------+--------------------+
         |                      |                    |
         V                      V                    V
    +----------+          +-----------+         +----------+
    |          |  recipe  |           |   ToC   |          |
    | pdfxmeta +--------->| pdftocgen +-------->| pdftocio +---> out.pdf
    |          |          |           |         |          |
    +----------+          +-----------+         +----------+
    

    pdf.tocgen 是一套能够自动为 PDF 文件生成目录( ToC )的开源命令行工具。它能够利用 PDF 文件中标题的字体与位置信息来生成一个基本的大纲。它的源码可以在 GitHub 上找到,但是对它的介绍请阅读项目的主页

    我写这套工具的主要目的是为了让阅读讲义公开 PDF 教材以及 arXiv 上的论文更加方便。它应该对使用 pdftex (以及 pdflatex, pdfxetex 等)从 TeX 生成的 PDF 支持最好,但是任何软件生成的 PDF都应该是支持的(即,它并不支持扫描的 PDF ),比如 troff/groff 、Adobe InDesign 、Microsoft Word 之类软件中导出的 PDF 。

    安装

    pdf.tocgen 是使用 Python 3 写成的,我自己是在 Linux 下用 Python 3.8 运行的,但是测试显示最低版本应该是 3.7 。你可以直接使用

    $ pip install -U pdf.tocgen
    

    在系统上安装 pdf.tocgen 的最新版本。你也可以用 pipx 或者

    $ pip install -U --user pdf.tocgen
    

    来为当前用户安装,防止污染系统的 Python 环境。

    使用流程

    pdf.tocgen 的设计受到 Unix 哲学 的影响。我刻意将它拆分成三个程序。它们能够互相协作,但是你也可以将它们分开来使用。它们代表了生成目录所需要的三个步骤:

    1. pdfxmeta: 从 PDF 中提取标题的元数据(字体和位置),用来制作一个 recipe
    2. pdftocgen: 使用 recipe 从 PDF 中提取目录
    3. pdftocio: 将目录导入 PDF 文件

    如果你想要一个正式的介绍,请阅读主页中的例子。这里我只做一个简单的概览。

    这里我使用的例子是 Paul Graham 在他网站上提供的 On Lisp 的 PDF 版本,PDF 本身是没有内嵌目录的。

    首先,使用 pdfxmeta 搜索标题的元数据:

    $ pdfxmeta -p 14 onlisp.pdf "The Extensible"
    The Extensible Language:
        font.name = "Times-Bold"
        font.size = 19.92530059814453
        font.color = 0x000000
        font.superscript = false
        font.italic = false
        font.serif = true
        font.monospace = false
        font.bold = true
        bbox.left = 138.60000610351562
        bbox.top = 306.947998046875
        bbox.right = 354.4866638183594
        bbox.bottom = 334.5445251464844
    

    这就是这个 PDF 中这个标题所对应的元数据,我们可以利用它来制作一个 recipe 。它是一个 TOML 格式的文件,用来告诉 pdftocgen 标题应该所具有的属性:

    [[heading]]
    # 第一级标题
    level = 1
    # 所应该具有的元数据
    font.name = "Times-Bold"
    font.size = 19.92530059814453
    

    你可以把 pdfxmeta 的输出都导出到一个文件里,方便之后修改

    $ pdfxmeta -p 14 onlisp.pdf "The Extensible" >> recipe.toml
    $ pdfxmeta -p 14 -i onlisp.pdf "^design" >> recipe.toml
    $ vim recipe.toml # edit
    

    修改过后的 recipe.toml 可以是这样子的:

    [[heading]]
    level = 1
    font.name = "Times-Bold"
    font.size = 19.92530059814453
    
    [[heading]]
    level = 2
    font.name = "Times-Bold"
    font.size = 11.9552001953125
    

    具体的属性需要你自己来测试,recipes 文件夹里有一些已经制作好的 recipe,你也可以发 pull request 或者 patch 贡献更多的 recipe 。

    有了这个 recipe 我们就能直接用 pdftocgen 来输出一个目录了。

    $ pdftocgen in.pdf < recipe.toml
    "Preface" 5
        "Bottom-up Design" 5
        "Plan of the Book" 7
        "Examples" 9
        "Acknowledgements" 9
    "Contents" 11
    "The Extensible Language" 14
        "1.1 Design by Evolution" 14
        "1.2 Programming Bottom-Up" 16
        "1.3 Extensible Software" 18
        "1.4 Extending Lisp" 19
        "1.5 Why Lisp (or When)" 21
    "Functions" 22
        "2.1 Functions as Data" 22
        "2.2 Defining Functions" 23
        "2.3 Functional Arguments" 26
        "2.4 Functions as Properties" 28
        "2.5 Scope" 29
        "2.6 Closures" 30
        "2.7 Local Functions" 34
        "2.8 Tail-Recursion" 35
        "2.9 Compilation" 37
        "2.10 Functions from Lists" 40
    "Functional Programming" 41
        "3.1 Functional Design" 41
        "3.2 Imperative Outside-In" 46
        "3.3 Functional Interfaces" 48
        "3.4 Interactive Programming" 50
    [--snip--]
    

    它的输出可以直接用 pipe 导入到 pdftocio,将目录导入到 PDF 中

    $ pdftocgen onlisp.pdf < recipe.toml | pdftocio onlisp.pdf
    

    你也可以将它导入到一个文件中,修改生成中出现的错误:

    $ pdftocgen onlisp.pdf < recipe.toml > toc
    $ vim toc # edit
    $ pdftocio onlisp.pdf < toc
    

    toc 文件的具体格式请见主页中的 例子,它的设计是为了 Vim 里的易编辑性,因为你有很大可能会想微调 pdftocgen 所输出的目录。

    每个程序都有一些附加的功能,请使用 -h flag 显示所有的选项。

    License

    pdf.tocgen 是一个自由软件,它采用的是 GPLv3 License,所有的衍生软件必须保证用户的自由。recipes 目录中的所有 recipe 都采用 CC BY-NC-SA 4.0 License 以避免任何商业用途,它们没有附带在安装包里。

    Support

    你可以免费使用 pdf.tocgen 以及自由地修改它的源码,但如果你觉得这个工具有帮助到你,并且想支持我或者这个工程,我也接受一些匿名的捐款,但是我不会保证我会优先考虑你的请求。

    16 条回复    2021-07-30 10:34:42 +08:00
    whileFalse
        1
    whileFalse  
       2020-07-29 10:08:41 +08:00
    为了避免“太长不看”,建议提供一个小脚本,以便在仅需要极少参数的情况下能够执行主要功能。
    这个脚本可能具有以下参数设置:
    Usage: pdftoc input [output] [options]
    -t
    print toc to stdout
    -i, --in-place
    edit files in place
    -d, --directory
    input and output are directories.

    pdftoc source.pdf target.pdf # 根据 source.pdf 生成带目录的目标文件 target.pdf
    pdftoc source.pdf -t # 提取 source.pdf 中的 toc,打印到 stdout
    pdftoc -d sources targets # 从 sources 目录中提取每个 pdf 文件,注入目录后输出到 targets 中
    krjt
        2
    krjt  
    OP
       2020-07-29 10:26:32 +08:00
    @whileFalse 你可能理解错 pdf.tocgen 所做的事情了哦,你所要做的事情 pdftocio 一个工具就能完成

    $ pdftocio in.pdf # 打印 in.pdf 已有的 ToC
    $ pdftocio in.pdf > toc # 将 in.pdf 已有的目录输出到 toc 中
    $ pdftocio out.pdf < toc # 将 toc 中的目录导入到 out.pdf 中
    whileFalse
        3
    whileFalse  
       2020-07-29 10:30:53 +08:00
    @krjt 所以就是 pdftocio in.pdf | pdftocio out.pdf 酱紫?
    krjt
        4
    krjt  
    OP
       2020-07-29 10:31:41 +08:00   ❤️ 1
    @whileFalse pdf.tocgen 的重点不是 输入 /输出 目录,能做到它的工具太多了。它的重点是为没有自带目录的 PDF 通过指定的 字体 /位置 信息自动生成一个目录,这是没有那么简单的。
    krjt
        5
    krjt  
    OP
       2020-07-29 10:33:51 +08:00
    @whileFalse 是的,这样会更简便
    freefcw
        6
    freefcw  
       2020-07-29 10:46:28 +08:00
    感觉不错,没有仔细研究

    问一下楼主,这个工具能为图片 pdf 生成目录吗?或者说,我能用一个指定格式编辑 toc 文件,输入 title,page 信息,用这个工具生成 toc
    whileFalse
        7
    whileFalse  
       2020-07-29 10:51:44 +08:00
    @krjt 明白了,这玩意儿不能自动化的提取目录,总是需要人工参与。
    krjt
        8
    krjt  
    OP
       2020-07-29 11:06:08 +08:00   ❤️ 1
    @freefcw 扫描的 PDF 是不可以的,因为这样的 PDF 内部结构非常混乱,没有能够利用的信息。

    但是第二点是可以的。我刻意将这个功能拆分成了 pdftocio 这个程序,使用流程可以见 2 楼。目录文件的格式可以见这里:

    https://krasjet.com/voice/pdf.tocgen/#step-2-generate-table-of-contents

    它是 csv 的一个变种,唯一的区别是

    1. 每 1 级目录 = 4 个空格缩进
    2. 分隔用的是一个空格,不是逗号
    3. 标题需要用双引号(")括起来

    这个格式主要的目的是为了能够在 Vim 里编辑很容易,vi" 能选中整个标题,<<或者>>能够调整标题的层级。
    matrix67
        9
    matrix67  
       2020-07-29 11:25:45 +08:00
    之前都是用的 pdf 补丁丁,不过效果也不太好。

    他貌似是基于样式扫描来实现的。

    https://www.cnblogs.com/pdfpatcher/p/8452025.html
    krjt
        10
    krjt  
    OP
       2020-07-29 13:34:15 +08:00
    @matrix67 看起来只能在 Windows 下运行,自己目前没有运行 Windows 的电脑,不过原理应该是差不多的,最大的区别可能是 GUI vs CLI,Unix philosophy 以及是否开源的区别。GUI 的操作会更直观一点,不过我觉得在修复 ToC 时可编辑性上是没有能比过纯文本的。
    freefcw
        11
    freefcw  
       2020-07-29 13:42:52 +08:00
    @krjt 挺好的,我看你的介绍感觉可以这么用,就确认一下,以后可以这么加
    JYL888
        12
    JYL888  
       2020-08-30 17:06:55 +08:00
    github 上有个用 java 写的,和你的功能一样
    JYL888
        13
    JYL888  
       2020-08-30 17:17:34 +08:00
    大佬能不能帮我转下啊~
    wasmetqall
        14
    wasmetqall  
       2021-02-22 00:36:48 +08:00
    @krjt 老哥,我按这个来搞好像没法把目录导进去呀,out_out.pdf 是出来了,
    运行
    pdftocio out_out.pdf
    命令也能出现对应的目录,但是用 pdf expert 还有预览打开的时候都显示没有目录。
    wasmetqall
        15
    wasmetqall  
       2021-02-22 01:01:42 +08:00
    @krjt 知道原因了,pymupdf 本身的问题导致的。原作者表示无解。
    https://github.com/pymupdf/PyMuPDF/issues/183
    Elliota
        16
    Elliota  
       2021-07-30 10:34:42 +08:00
    太牛了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5680 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 02:39 · PVG 10:39 · LAX 19:39 · JFK 22:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.