如何修改knitr的\label{}行为以添加PDF超链接锚点?
先理一理你的需求演变:最开始你想修改knitr在生成LaTeX figure环境时的行为,把默认的\label{}替换成自定义的\alabel{}(你定义的这个命令会同时执行\label{foo}和\hypertarget{foo}{},用来实现PDF里的精准跳转),后来你调整了思路,转而通过解析LaTeX的辅助文件来生成跳转链接,最后采用了Werner的方案,同时还给出了一套无需hypertarget的R实现方案。下面我把这些内容整理清楚:
一、如果要修改knitr生成\label的行为(最初需求)
如果你还是想尝试在figure环境里直接添加\hypertarget{},可以通过重写knitr的figure钩子函数来实现。这个钩子可以控制figure环境的开头和结尾输出内容,我们可以在输出默认\label{}的同时追加\hypertarget{}:
knit_hooks$set(figure = function(before, options, envir) { if (before) { # 输出figure环境的开头部分 paste0('\\begin{figure}\n', '\\centering\n') } else { # 处理标签部分:如果有label,就同时输出hypertarget和label label_content <- if (!is.null(options$label)) { paste0('\\hypertarget{', options$label, '}{}\n', '\\label{', options$label, '}\n') } else "" # 输出figure环境的结尾 paste0(label_content, '\\end{figure}\n') } })
把这段代码放在你的.Rnw文档开头的R代码块里,knitr生成figure时就会自动添加对应的\hypertarget{}了。
二、解析LaTeX辅助文件生成跳转链接(调整后的方案)
你后来觉得不生成hypertarget,而是通过解析.aux和.pag文件来生成带物理页码的PDF链接更合适,这个方案还能处理物理页码和逻辑页码不匹配的情况(比如用“章节号-页码”这种逻辑编号),具体步骤如下:
1. 添加LaTeX代码生成页码映射文件
在你的.Rnw文档的preamble部分(或者单独的.sty文件里)加入这段LaTeX代码,它会生成一个.pag文件,记录逻辑页码到物理页码的对应关系:
% Creates .pag file mapping absolute page numbers to logical page % numbers; works with R function latexRef \newwrite\pgfile \immediate\openout\pgfile=\jobname.pag \newcounter{abspage} \setcounter{abspage}{0} \usepackage{everypage} \AddEverypageHook{% \addtocounter{abspage}{1} \immediate\write\pgfile{\thepage, \theabspage}% } \AtEndDocument{\clearpage\immediate\closeout\pgfile}
2. 使用R函数解析辅助文件生成链接
下面的latexRef函数可以读取.aux和.pag文件,提取标签对应的页码,然后生成Markdown或LaTeX格式的跳转链接:
## Create hyperlink to appropriate physical page in a pdf document ## created by pdflatex given the .aux and .pag file. Absolute and ## named page numbers are store in the .pag file created by hslide.sty latexRef <- function(label, base, name, path='doc/', blogpath='/home/harrelfe/R/blog/blogdown/static/doc/', lang=c('markdown', 'latex')) { lang <- match.arg(lang) aux <- paste0(blogpath, base, '.aux') if(! file.exists(aux)) stop(paste('no file named', aux)) path <- paste0(path, base, '.pdf') pag <- paste0(blogpath, base, '.pag') pagemap <- NULL if(file.exists(pag)) { p <- read.table(pag, sep=',') pagemap <- trimws(p[[2]]) names(pagemap) <- trimws(p[[1]]) } r <- readLines(aux) w <- paste0('\\\\newlabel\\{', label, '\\}') i <- grepl(w, r) if(! any(i)) stop(paste('no label =', label)) r <- r[i][1] r <- gsub('\\{', ',', r) r <- gsub('\\}', ',', r) x <- scan(text=r, sep=',', what=character(0), quiet=TRUE) section <- x[5] if(section != '') section <- paste0(' Section ', section) page <- trimws(x[7]) if(length(pagemap)) page <- pagemap[page] url <- paste0('http://fharrell.com/', path, '#page=', page) switch(lang, markdown = paste0('[', name, section, '](', url, ')'), latex = paste0('\\href{', url, '}{', name, section, '}') ) }
3. 使用示例
比如你要生成一个指向标签fig:scatter-plot的Markdown链接,只需要在R代码块里调用:
latexRef(label = 'fig:scatter-plot', base = 'my-report', name = 'Scatter Plot', lang = 'markdown')
它会返回类似[Scatter Plot Section 3](http://fharrell.com/doc/my-report.pdf#page=8)的链接,直接插入到你的文档里就行。
三、最终采用的方案:Werner的方法
你提到最终采用了Werner的方案,运行效果完美。这个方案应该是在LaTeX层面直接解决了figure的跳转定位问题,不需要额外的R解析步骤,是更简洁的原生解决方案。
内容的提问来源于stack exchange,提问作者Frank Harrell

