latex表插入的位置不对_[LaTeX 尝试] 以插图形式输出 PDF 文件的所有页面

本文介绍了如何在LaTeX中使用graphicx宏包和pdfpages宏包,分别实现将多页PDF作为单一图形插入及合并PDF文件。重点在于提供一种方法,通过循环插入所有PDF页面,并给出了获取PDF总页数的代码。此外,还展示了如何自定义宏`includeAllPages`一次性插入多页PDF的所有页面。
摘要由CSDN通过智能技术生成

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

插入 PDF 的两种效果

用 latex 生成 pdf 时,插图功能(通常通过 graphicx 宏包的 includegraphics 命令)除了支持 eps、jpg 等常见图片格式,还支持 pdf 格式。

插入 pdf 图片,有两种效果

  1. 像插图那样,一页 pdf 就像是一个 eps 或 jpg 图片,插一张图就像是输入了一个大号的字符。这是 includegraphics 提供的功能。
  2. 像合并 pdf 文件那样,相当于先用 latex 生成 main.pdf,然后把另一个 pdf 文件(的一部分)和 main.pdf 合并。这是 pdfpages 宏包提供的功能。

两者的差异是,在最终输出的 pdf 里,

  • graphicx 插入的 pdf 页面,角色是某页里的一个大一点的符号,这一页里还能包含其他内容;
  • pdfpages 插入的 pdf 页面,角色是一个整页。

本文提供一种尝试,使得在用第一种效果插入 pdf 时,能一次插入所有页面。

指定页面的选项

pdfpages 提供的 pages 选项, 允许用户指定要插入

  • 哪一页 pages=2
  • 哪几页 pages=1,3,5
  • 哪个页面范围 pages=1-3,4-

includegraphics[<options>]{<image file>} 也有 page 选项,从选项命名上可以看出,它只接受一个数字,即每次只插入一页。

获取 PDF 文件的总页码

插图终究需要引擎的底层支持,而 xetex 和 pdftex 引擎,

  • 都只支持一次插入一页 pdf,这意味着需要在 latex 层面做「输入所有页面」的循环
  • 也都提供了获取 pdf 文件总页数的 primitive

TeX-SX 上的问答 Get number of pages of external PDF 提供了在 pdftex、xetex 和 luatex 三种引擎下的页码获取方法。我们参考它,做一个只支持 pdftex 和 xetex 的

usepackage{iftex}

% Store number of pages of pdf file #2 in command #1.
%   Usage: @getPageNumOfPdf{@pagecount}{image.pdf}
%   Ref: https://tex.stackexchange.com/q/198091
def@getPageNumOfPdf#1#2{%
  ifPDFTeX
    pdfximage{#2}%
    xdef#1{thepdflastximagepages}%
  elseifXeTeX
    % trailing space in `"#2" ` is necessary
    xdef#1{theXeTeXpdfpagecount"#2" }%
  else
    PackageError{TEST}{Support engines pdftex and xetex only.}{}%
  fi
}
makeatother

获取 PDF 文件的完整文件名

通过 primitive 获取 PDF 的总页数时,有一个限制:必须提供完整的 PDF 文件名。这里对「完整」的要求,包含两个部分

  • 文件的拓展名必须完整。
    • pdf-file.pdf,正确;
    • pdf-file,错误。
  • 文件的路径必须完整。假设我们设置了 graphicspath{{figures/}{images/}},那么获取一张只存储于 figures 子文件夹里的 PDF 的页码时,使用
    • figures/pdf-file.pdf,正确;
    • images/pdf-file.pdf,错误 ;
    • pdf-file.pdf,错误。

插图 primitive 对完整文件名的要求一直存在,而我们感知不到,是因为 includegraphics 内部帮我们做了这样的事:补全文件拓展名、补全文件相对路径。

这意味着,在 includegraphics 展开到某个中间状态时,我们能获取 PDF 文件的完整文件名。这个状态,第一次出现于宏 Ginclude@graphics 的内部。我们在这个位置做 patch,把完整文件名记录下来。

% detokenized ".pdf", all four characters have category code 12 (other).
edefGin@ext@pdf{detokenize{.pdf}}

ifxGin@extGin@ext@pdf
  xdefGin@full{Gin@baseGin@ext}
fi

一些说明

  • 真正用于记录文件名的代码是 xdefGin@full{Gin@baseGin@ext}
  • 因为 xetex 下获取 pdf 页数的 primitive XeTeXpdfpagecount 只接受 PDF 文件名,出于通用性的考虑,额外判断「图片拓展名是否为 PDF」
  • 图片的拓展名(例如 .pdf.jpg)储存在 Gin@ext 里,
    • 如果直接定义 defGin@ext@pdf{.pdf},那么ifx 总是 false 的。
    • 这是因为 Gin@ext 内部每个符号的 category code 都是 12(other) ,而在 defGin@ext@pdf{.pdf} 里,. 是 12,pdf 三个符号是 11(letter)。
    • 所以这里使用了 etoolbox 宏包提供的、忽略 category code 的 ifstrequal 来比较字符。
      (已调整为在定义中也使用 detokenize,这是 ifstrequal 内部和 Ginclude@graphics 里的做法)
  • 完整的 patch,是对 Ginclude@graphics 的重定义(见下方)。
    • 因为 Ginclude@graphics 的原始定义中包含修改 category code 的 primitive detokenize,所以无法使用 etoolbox/xpatch 等宏包提供的 patchcmd/xpatchcmd(这种 patch 方式,要写的代码行数较少)
    • 这样,我们在重定义时,就不得不把原始定义抄一遍
% detokenized ".pdf", all four characters have category code 12 (other).
edefGin@ext@pdf{detokenize{.pdf}}

defpatch@Ginclude@graphics{%
  defGinclude@graphics##1{%
    ifxdetokenize@undefinedelse
      edefGin@extensions{detokenizeexpandafter{Gin@extensions}}%
    fi
    begingroup
    letinput@pathGinput@path
    set@curr@file{##1}%
    edefuq@curr@file{expandafterunquote@nameexpandafter{@curr@file}}%
    expandafterfilename@parseexpandafter{uq@curr@file}%
    edeffilename@area{expandafterquote@nameexpandafter{filename@area}}%
    edeffilename@base{expandafterquote@nameexpandafter{filename@base}}%
    ifxfilename@extrelax
      @forGin@temp:=Gin@extensionsdo{%
        ifxGin@extrelax
          Gin@getbaseGin@temp
        fi}%
    else
      Gin@getbase{Gin@sepdefaultfilename@ext}%
      ifxGin@extrelax
         @warning{File `##1' not found}%
         defGin@base{filename@areafilename@base}%
         edefGin@ext{Gin@sepdefaultfilename@ext}%
      fi
    fi
    %% PATCH BEGIN
    %  - Base and ext part (e.g., ".eps") of an image are not known until now.
    %  - Store full file name (dir + basename + ext) in Gin@full if extension
    %    equals to the detokenized ".pdf".
    ifxGin@extGin@ext@pdf
      xdefGin@full{Gin@baseGin@ext}
    fi
    %% PATCH END
    ifxGin@extrelax
         @latex@error{File `##1' not found}%
         {I could not locate the file with any of these extensions:^^J%
          Gin@extensions^^J@ehc}%
    else
       @ifundefined{Gin@rule@Gin@ext}%
         {ifxGin@rule@*@undefined
            @latex@error{Unknown graphics extension: Gin@ext}@ehc
          else
            expandafterGin@setfileGin@rule@*{Gin@baseGin@ext}%
           fi}%
         {expandafterexpandafterexpandafterGin@setfile
             csname Gin@rule@Gin@extendcsname{Gin@baseGin@ext}}%
    fi
  endgroup}%
}

写个循环,输出所有页面

主体是一个循环,例如用 tikz 的子包 pgffor 提供的循环功能

foreach i in {1, ..., @pagecount} {
  includegraphics[<other options>, page=i]{Gin@full}
}

大致思路是,

  • 执行一次 patch 后的 includegraphics,记录完整文件名
  • 执行一次 @getPageNumOfPdf,记录总页码
  • 对页码循环,输出所有页面
% Typeset an image like includegraphics[<options>]{<image>}. If it is
% a PDF file containing multiple pages, typeset all of them.
% #1 = <options> passed to includegraphics
% #2 = image file name
newcommand{includeAllPages}[2][]{%
  %% init
  defGin@count{1}%
  letGin@full@empty
  %% get page number
  savebox@tempboxa{% pre-expand the typesetting of first page
    begingroup
    patch@Ginclude@graphics
    includegraphics[#1]{#2}%
    endgroup
  }%
  ifxGin@full@empty
  else
    @getPageNumOfPdf{Gin@count}{Gin@full}%
  fi
  usebox{@tempboxa}% typeset the first page
  ifnumGin@count>1% typeset the rest pages if there exist.
    foreach i in {2, ..., Gin@count} {%
      allowbreakincludegraphics[#1, page=i]{Gin@full}%
    }%
  fi
}

一些说明

  • 用分组限制 patch 的范围
  • 先把第一页的结果存储在盒子里的做法并非必须,但这样能让后面的循环变量从 1 开始,在进行某些扩展时可能带来少许便利。
  • allowbreak 是为了允许在图片之间换行,并非必须。

其他

可能的功能扩展

  • 一种需求是,希望在两张图之间插入分隔符(例如换行换段),形成<page 1> <sep> <page 2> <sep> ... <page n> 的输出。
    上面的例子中,includeAllPages 定义里的 allowbreak 就是一种分隔符。
  • 更一般的,是允许用户自定义宏pageFilter{<current page num>}{<min num>}{<max num>}{<code>}
  • 另一方面的扩展,可能是提供类似 pgfpagespages 选项,即允许接受一个页面范围列表 page_range_list,列表接受的语法为
page_range_list  ::= page_range ("," page_range)*
page_range       ::= int | [int] "-" [int]

关于 patch 的位置

  • patch 的位置不唯一,本文使用的是最早的位置
  • patch Ginclude@graphics 好处是,代码与引擎无关
  • 一个和引擎有关的 patch 位置是宏 Ginclude@pdf(这是最晚的 patch 位置,一种实现见下面提到的 graphicx-output-every-page.tex 文件)。
    • 会展开这个宏,说明拓展名已经确定是 .pdf,patch 时只需记录完整路径。
    • 但因为引擎相关,所以需要 patch 每个引擎下的 Ginclude@pdf

完整实现,见项目muzimuzhi/latex-examples 中的文件

  • graphicx-output-every-page.*,包含完整实现;
  • figures/demo-multipage-pdf.* ,用于生成多页的 pdf 小文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值