PDF的bookmark和toc实现page定位的方法
WangDongYao opened this issue · 0 comments
首先解释一下这个标题,PDF的bookmark是指在PDF软件中可以点击的,下面称bookmark为书签,而toc是Table of Contents的缩写,是一本书的目录,下面称toc为目录,一般默认生成PDF的书签和目录是紧挨着的,我称这种为精准定位,而有时候我们并不想要精准定位,只想定位到某个page,我称这种为页面定位,下面介绍一下如何实现页面定位。
先介绍一下需要用到包:
1. hyperref:可以实现点击目录中章节进行跳转,当然功能不止于此,可以搞定交叉引用;
2. bookmark:用来添加书签,支持页面定位;
3. geometry:用来设置页面margin;
4. atbegshi:用来设置全局页面计数器。
因为目录的页面定位实现起来相对麻烦一些,如果只需要书签页面定位,会简单很多,下面分书签页面定位和全部页面定位两种来介绍:
1. 书签页面定位:先说实现**,一本书的标准页码应该是封面版权等部分使用大写英文字母A, B, C或者C1, C2, C3,前言目录等部分使用小写罗马数字i, ii, iii(我喜欢大写罗马数字,后面会介绍如何修改),正文部分使用阿拉伯数字1, 2, 3,正文后面的附录参考文献部分一般来说依旧延续阿拉伯数字不变,如果不按标准直接从阿拉伯数字开始没有问题,如果按照标准就有个问题,前面的那些页码是逻辑页码,就会产生和真实的物理页面不一致的情况,就是说把C1, C2, C3当成数字来看其实是1, 2, 3,正文的阿拉伯数字也是1, 2, 3,这样就会产生重复都从1开始,后面正文部分的页码就是错的,想解决这个问题,需要一个全局的页面计数器,每当产生一个页面就记录下来,这样引用这个全局的绝对页码就不会产生问题了,把问题解释清楚之后来看具体实现:
- 先定义一个全局的页面计数器
\RequirePackage{atbegshi}
\begingroup
\let\@addtoreset\ltx@gobbletwo
\newcounter{abspage}%
\endgroup
\setcounter{abspage}{1}%
\AtBeginShipout{%
\stepcounter{abspage}%
}%
- 然后使用bookmark包进行设置,一般情况下只对chapter级别或part级别进行页面定位,下面对chapter举例,part同理可以自己修改,需要重写下chapter方法,或者仿照chapter自己重新定义一个mchapter:
\usepackage{bookmark}
\RequirePackage{hyperref}
\hypersetup{
pageanchor=true,
breaklinks,
unicode,
linktoc=all,
bookmarksnumbered=true,
bookmarksopen=true,
pdfkeywords={MathBook},
colorlinks,
linkcolor=winered,
citecolor=winered,
urlcolor=winered,
plainpages=false,
pdfstartview=Fit,
pdfpagelayout=OneColumn,
pdfborder={0 0 0},
linktocpage
}
\newcommand\mchapter{\if@openright\cleardoublepage\else\clearpage\fi
\thispagestyle{plain}%
\global\@topnum\z@
\@afterindentfalse
\secdef\@mchapter\@smchapter}
\def\@mchapter[#1]#2{\ifnum \c@secnumdepth >\m@ne
\if@mainmatter
\refstepcounter{chapter}%
\typeout{\@chapapp\space\thechapter.}%
% 修复了书签中第1章只显示1的问题,现在使其正确的显示为第1章
\addtocontents{toc}{\protect\contentsline{chapter}{\xchaptertitle\ #1}{\arabic{page}}{page.\arabic{page}}}%
\bookmark[level=chapter,page=\arabic{abspage}]{\xchaptertitle\ #1}
\else
\addcontentsline{toc}{chapter}{\xchaptertitle\ #1}%
\fi
\else
\addcontentsline{toc}{chapter}{\xchaptertitle\ #1}%
\fi
\chaptermark{#1}%
\addtocontents{lof}{\protect\addvspace{10\p@}}%
\addtocontents{lot}{\protect\addvspace{10\p@}}%
\if@twocolumn
\@topnewpage[\@makechapterhead{#2}]%
\else
\@makechapterhead{#2}%
\@afterheading
\fi}
% 修改未编号的chapter*也写入书签和目录,如果不需要可以不写
\def\@smchapter#1#2{
\Hy@MakeCurrentHrefAuto{\Hy@chapapp*}
\Hy@raisedlink{
\hyper@anchorstart{\@currentHref}\hyper@anchorend
}
\typeout{\@chapapp\space\thechapter.}%
\addtocontents{toc}{\protect\contentsline{chapter}{#2}{\Roman{page}}{page.\Roman{page}}}%
\bookmark[level=chapter,page=\arabic{abspage}]{#2}
\markboth{#1}{}
\addtocontents{lof}{\protect\addvspace{10\p@}}%
\addtocontents{lot}{\protect\addvspace{10\p@}}%
\if@twocolumn
\@topnewpage[\@makeschapterhead{#1}]%
\else
\@makeschapterhead{#1}%
\@afterheading
\fi}
2. 全局页面定位:bookmark包只提供了书签页码写入,所以需要借助hyperref提供的pageanchor,先介绍一个概念anchor,中文翻译成锚点之类的,就是用来定位的,而pageanchor顾名思义就是页面锚点,好像和页面定位很像啊,试试能不能实现呢?很可惜设置之后总是差了一点点距离,和书签那种页面定位不一样,这个也是我花了很长时间才研究明白的,究竟如何把差那一点点距离给修复呢?省略中间的艰辛过程,终于找到了实现方法,一切都是由于页面margin导致的,如果把页面margin设置为0,确实可以实现和书签一样的页面定位效果,但文档的样式也被破坏了,该如何对页面进行hook,使锚点位置不含margin又不影响文档样式,经过各种失败的尝试后,终于在hyperref的源码中找到了pageanchor的实现方法,只需要对最关键的那步进行修改就行了,下面是代码实现:
\RequirePackage{geometry}
% 重新定义pageanchor的页面样式,使其margin为0.
\def\Hy@EveryPageAnchor{%
\Hy@DistillerDestFix
\ifHy@pageanchor
\ifHy@hypertexnames
\ifHy@plainpages
\def\Hy@TempPageAnchor{\hyper@@anchor{page.\the\c@page}}%
\Hy@PageAnchorSlidesPlain
\else
\begingroup
\let\@number\@firstofone
\Hy@unicodefalse
\Hy@PageAnchorSlide
\pdfstringdef\@the@H@page{\thepage}%
\endgroup
\EdefUnescapeString\@the@H@page{\@the@H@page}%
\def\Hy@TempPageAnchor{\hyper@@anchor{page.\@the@H@page}}%
\fi
\else
\Hy@GlobalStepCount\Hy@pagecounter
\def\Hy@TempPageAnchor{%
\hyper@@anchor{page.\the\Hy@pagecounter}%
}%
\fi
%在这里进行修改,在其设置box前先把margin设置为0
\newgeometry{margin=0in}
\vbox to 0pt{%
\kern\voffset
\kern\topmargin
\kern-1bp\relax
\hbox to 0pt{%
\kern\hoffset
\kern\ifodd\value{page}%
\oddsidemargin
\else
\evensidemargin
\fi
\kern-1bp\relax
\Hy@TempPageAnchor\relax
\hss
}%
\vss
}%
% 设置完之后在把margin恢复,这样不会影响到其它位置的样式
\restoregeometry
\fi
}
补充一下封面页书签的设置:
\renewcommand{\thepage}{C\arabic{page}} % 封面部分页码以C(Cover)开头
\pagenumbering{Roman} % 也可选择设置为大写字母
% 上面两种选择一种
\bookmark[level=chapter,page=\arabic{abspage}]{封面}
% 如果想把封面加到目录中,可以类似这样操作:
\addtocontents{toc}{\protect\contentsline{chapter}{封面}{\Roman{page}}{page.\Roman{page}}}%
附赠一个如何设置前言目录页为大写罗马数字的方法:
\renewcommand\frontmatter{
\if@openright
\cleardoublepage
\else
\clearpage
\fi
\@mainmatterfalse
\pagenumbering{Roman}
}