如何保留从 LaTeX PDF 复制的 Python 代码中的缩进?

如何保留从 LaTeX PDF 复制的 Python 代码中的缩进?

我尝试将 Python 3.6 代码包含在 LaTeX PDF 文档中,该文档应该可以轻松复制以保存到文件或尝试 Python 代码。

虽然,

\begin{verbatim}
for row in range(1,9):
    for col in range(1,9):
        print(int(str(row)+str(col)))
\end{verbatim}

在 PDF 中显示得很好,但如果我将其复制并粘贴到文本编辑器中,它看起来像:

for row in range(1,9):
for col in range(1,9):
print(int(str(row)+str(col)))

Python 必需的缩进已经消失。

我也尝试使用 listings 包这里的建议:

如何在 LaTeX Listings \lstinputlistings 命令中突出显示 Python 语法

我可以简单地给出 Python 文件的文件名,例如

\pythonexternal{Test.py}

源代码将被包含并着色。但复制粘贴时开头缺少空格的情况也一样。

如果我使用选项“showspaces=true”,我会得到以下结果:

for␣row␣in␣range(1,9):
␣␣␣␣for␣col␣in␣range(1,9):
␣␣␣␣␣␣␣␣print(int(str(row)+str(col)))

这也不适合复制粘贴。好吧,我可以用空格替换所有 ␣...这不是一个真正可行的解决方案。

这里描述了 2011 年发生的一些奇怪的黑客事件:

如何使从 PDF 复制时列表代码缩进保持不变?

从那时起有什么新东西吗……?有什么想法可以在 LaTeX PDF 中实现 Python 代码的复制和粘贴吗?感谢您的建议。

答案1

还有一个解决方案在 Acrobat Reader 和 Chrome 中都可以很好地运行,但仅与 pdfTeX 兼容(不与 XeTeX/LuaTeX 兼容):

\usepackage{transparent}
\makeatletter
\def\@xobeysp{\leavevmode\nobreak\texttransparent{0}{\char32}}
\makeatother

用于\texttransparent使 ⎵ 符号不可见(实际上,在某些字体中,插槽 32 被映射到真实空格字符上,并且这个技巧不是必需的,但上述代码涵盖了所有情况)。

最有可能的是,此代码之所以有效,是因为生成的文件不是基于 Unicode 的,并且 PDF 查看器没有关于代码 32 与实际 Unicode 字符之间匹配的信息,因此它将其“按原样”复制为字符代码 32(即空格字符)。支持这一假设的是,有失败的 XeTeX/LuaTeX 生成的文件和通过/ActualText命令设置 Unicode 对应的尝试(通过替换\char32上述\pdfliteral direct{/Span<</ActualText<FEFF0020>>> BDC}\char32\pdfliteral direct{EMC}代码中的 )。

答案2

pdfTeX 有命令\pdffakespace在文档中插入空格。这个空格是不可见的,但在文本复制过程中会被考虑在内。

因此,我的第一个想法是在每个行首的空格后插入\pdffakespace,但不幸的是,它导致复制的文本中的空格加倍(一个空格来自\pdffakespace,另一个空格由 Acrobat Reader 自动从 TeX 生成\hskip)。

下一个想法是计算行首空格的数量,并\pdffakespace在这些空格后插入所需数量的空格。不幸的是,Acrobat Reader 将这些空格修剪为一个空格(尽管空格命令序列[( )]TJ实际上显示在生成的 PDF 文件中)。

但最终,我找到了一个棘手的解决方案:将空格分组,并用双倍空格宽度的序列替换它们\pdffakespace\hskip从标准序言开始,制作@一封信

\makeatletter

我们通过在命令定义末尾\@verbatim添加来重新定义命令(以处理换行后的文本):\hook@par\par

\def\@verbatim{\trivlist \item\relax
  \if@minipage\else\vskip\parskip\fi
  \leftskip\@totalleftmargin\rightskip\z@skip
  \parindent\z@\parfillskip\@flushglue\parskip\z@skip
  \@@par
  \language\l@nohyphenation
  \@tempswafalse
  \def\par{%
    \if@tempswa
      \leavevmode \null \@@par\penalty\interlinepenalty
    \else
      \@tempswatrue
      \ifhmode\@@par\penalty\interlinepenalty\fi
    \fi\hook@par}% <=== HERE
  \let\do\@makeother \dospecials
  \obeylines \verbatim@font \@noligs
  \everypar \expandafter{\the\everypar \unpenalty}%
}

然后我们创建一个计数器来计算行首的空格数

\newcount\nspaces

在行首重置此计数器,并计算空格的序列

\def\hook@par{\nspaces=0\relax\check@space}
\def\check@space{\futurelet\@let@token\do@check@space}
\def\do@check@space{%
  \ifx\@let@token\@xobeysp%
    \advance\nspaces 1%
    \expandafter\skip@space%
  \else%
    \ifnum\nspaces>0%
      \print@spaces%
    \fi%
  \fi}
\def\skip@space#1{\check@space}

最后,我们打印成对的空格(对奇数个空格有特殊处理,在这种情况下,我们最终会\pdffakespace在单个空格之后输出一个空格\hskip,该空格将与前一个空格合并\hskip,并在文本复制期间产生单个空格)

\def\print@spaces{%
  \leavevmode\nobreak
  \loop%
    \pdffakespace%
    \nobreak\hskip\dimexpr 2\fontdimen2\font\relax%
    \advance\nspaces by -2\relax%
  \unless\ifnum\nspaces<2\repeat%
  \ifnum\nspaces>0%
    \nobreak\hskip\fontdimen2\font\relax%
    \pdffakespace%
  \fi}

最后别忘了恢复@回来:

\makeatother

就这样。 瞧。

答案3

另一种方法是使用hyperref包并创建多行文本字段:

\begin{Form}
  \TextField[multiline=true,disabled,borderwidth=0,
             width=\linewidth,height=3\baselineskip,
             value={%
  for row in range(1,9):\string\n%
  \space\space\space\space for col in range(1,9):\string\n%
  \space\space\space\space\space\space\space\space print(int(str(row)+str(col)))%
  }]{~}
\end{Form}

在这种情况下,您也可以从 Chrome 复制文本,但似乎没有办法调整文本字段的文本字体(它始终是默认的无衬线字体)。而且它在 SumatraPDF 中不起作用(尽管在 SumatraPDF 中复制文本效果很差,即使是普通文本)。

PS. 这只是一个概念验证(因为我猜没人会输入所有这些\space以及\string\n真实的代码片段)。

相关内容