📝
LuaLaTeXの力で"クリップボードにコピー"ボタンをPDFに作る
前回までのあらすじ
こんなことしてました.
今回やること
クリップボードにコピーするボタンを作ります.Zenn
をはじめをした技術系のブログや記事、サイトではコードブロック、コードスニペットにクリックするだけでクリップボードにソースコードがコピーされるボタンがありますよね?
こんなかんじで。(コードスニペットにマウスホバーで出現)↗️
それを作ります.ソースコードででない任意の文字も,クリップボードにコピーさせることができます.しかし,以下の制約が必要です.
-
Adobe Acrobat
を使用していること -
Adobe Acrobat
で必要な設定を行っていること - 日本語を含まないこと(日本語があると文字化けします)
必要な設定とは以下の内容です.
-
Adobe Acrobat
の環境設定を開く -
JavaScript
の項目からAcrobat JavaScriptを使用
にチェックをいれる -
セキュリティ(拡張)
の項目のセキュリティ特権の場所
にこの PDF ファイルまたはそれが格納されたフォルダを追加する
発想
この質問を見つけたのでこれがベースになります.大まかな流れは以下です.
-
VerbatimOut
コマンドでソースコードをありのままファイル(今回はtemp.txt
)に書き出す -
lstinputlisting
でソースコードを表示 - ボタンが押されたら PDF 内に新しい
field
を追加してluaLaTeX
でファイルを読み込んだ中身をセット - ユーザのカーソルを
field
にフォーカス -
すべて選択
をさせる -
コピー
をさせる -
field
を抹消
3 以降はAcrobat Javascript API
(正式名称不明)を利用してJavascript
で書きます.5,6 はメニューにある機能を発火させるexecMenuItem
を使います.また,発火のタイミングをずらすためにsetTimeOut
を用います.このコマンドではTeX
,lua
,Javascript
の 3 言語を使うことになります.
なぜこんな回りくどいやり方をする(とくに,なぜlua
を使用する)のかといいますと,TeX
に特殊文字が多いのと,コマンドがいつ「展開」されるかがわからないからです.そのため,'
(Javascript
において文字列リテラルを表す),#
(TeX
において引数を表す),%
(TeX
においてコメントを表す),
(スペース),改行などの特殊文字をlua
でファイルを読む際に変な文字列(ABC<space>XYZ
など)で置換して,Javascript
で元に戻しています.こうすることで,tex
のファイルにはこの様な特殊文字が一切現れない形になります.
完成品
ソースコードでない文字はこれでできます.
\usepackage{media9}
\mediabutton[%
jsaction={
var fld = this.addField("ToCopy", "text", \thepage-1, this.getPageBox({nPage: \thepage-1}));
fld.textSize = 0.1;
fld.fillColor = color.transparent;
fld.multiline = true;
fld.value = 'copy text'; % ここがコピーされるテキストです.
fld.setFocus();
app.setTimeOut("app.execMenuItem('SelectAll');", 100);
app.setTimeOut("app.execMenuItem('Copy');", 200);
app.setTimeOut("this.removeField('ToCopy');", 300);
}]{
\fbox{Copy to Clipboard}% この部分を変更すれば見た目を変更できます.
}
数週間頑張った結果がこちらです.
\usepackage{luacode}
\usepackage{verbatim}
\usepackage{listings}
\usepackage{media9}
\usepackage{fancyvrb}
\usepackage{xparse}
\begin{luacode*}
function readtxt(filename, suffix)
local body = ""
local suffix = suffix or ""
local firstline = true
local sharp_char = string.char(35)
local percent_char = string.char(37)
local single_quote = string.char(39)
local space_char = string.char(32)
for line in io.lines(filename) do
line = string.gsub(line, sharp_char, "ABC<sharp>XYZ")
line = string.gsub(line, percent_char..percent_char, "ABC<percent>XYZ")
line = string.gsub(line, single_quote, "ABC<quote>XYZ")
line = string.gsub(line, space_char, "ABC<space>XYZ")
if not firstline then
body = body .. "ABC<br>XYZ"
else
firstline = false
end
body = body .. line
end
return body
end
\end{luacode*}
\NewDocumentEnvironment{zennlisting}{O{Python}}
{\VerbatimOut{temp.txt}}
{\endVerbatimOut\lstinputlisting[language=#1]{temp.txt}%
\mediabutton[%
jsaction={
var fld = this.addField("ToCopy", "text", \thepage-1, this.getPageBox({nPage: \thepage-1}));
fld.textSize = 0.1;
fld.fillColor = color.transparent;
fld.multiline = true;
fld.value = '\directlua{tex.sprint(readtxt('temp.txt'))}'.replace(/ABC<br>XYZ/g, String.fromCharCode(10)).replace(/ABC<sharp>XYZ/g, String.fromCharCode(35)).replace(/ABC<quote>XYZ/g, String.fromCharCode(39)).replace(/ABC<space>XYZ/g, String.fromCharCode(32));
fld.setFocus();
app.setTimeOut("app.execMenuItem('SelectAll');", 100);
app.setTimeOut("app.execMenuItem('Copy');", 200);
app.setTimeOut("this.removeField('ToCopy');", 300);
}]{
\fbox{Copy to Clipboard}% この部分を変更すれば見た目を変更できます.
}
}
使い方
\begin{zennlisting}[Python]
# your code
\end{zennlisting}
Discussion