📝
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