ソースコードの行番号を選択できないようにしたい

<!-- # ソースコードの行番号を選択できないようにしたい -->

listings パッケージ等でソースコードを作成する際に行番号を付記することがあります。

しかし、行番号を付してしまうと PDF 上からソースコードを複数行選択する際に、一緒に行番号も選択してしまいます。
これは非常に厄介で、可能であれば選択したくないものです。

そこで本記事では、絶対に行番号を選択させない TikZ-numeral パッケージを作って対応してみようという試みです。

## 絶対に行番号を選択させたくない先行研究

“行番号を選択させたくない” と言う希望は多くの人が持っているようで、次のような方法を確認することが出来ました。しかしながら、これらには問題点があります。

- accsupp パッケージを使って、`ActualText` を空文字にする（[参考][stackexchange-57141]）

    :x: `ActualText` の扱いはビューワに依存するため、行番号が選択できてしまう場合がある。

- TikZ を使って行番号をソースコードの後に別個で番号付けする（[参考][texforum3910]）

    :x: ページ毎にソースコードの後で行番号を追加しているため、ページをまたぐ場合、ページ毎にソースコードを選択する必要がある。

- 行番号の背景に白色を追加する（[参考][migawariw-c4bc95651bf347d34000]）

    :x: macOS の Preview や Safari では有効な手段のようですが、（少なくとも筆者の）Windows 環境では行番号を選択できる

[stackexchange-57141]: https://tex.stackexchange.com/questions/57141/is-there-a-latex-trick-to-prevent-a-pdf-viewer-from-copying-the-line-number "listings - Is there a LaTeX trick to prevent a PDF viewer from copying the line number? - TeX - LaTeX Stack Exchange"
[texforum3910]: https://okumuralab.org/tex/mod/forum/discuss.php?d=3910 "QA: 改ページを伴うlistingsの環境中で行番号をコピーしないようにしたい | TeX"
[migawariw-c4bc95651bf347d34000]: https://qiita.com/migawariw/items/c4bc95651bf347d34000 "【LaTeX,listings】行番号を表示したい！でも行番号ごとコピーしないで…… #LaTeX - Qiita"

と言うことで、現状、完璧に行番号を選択させない方法は無いと言っても過言ではありません。

そこで今回、絶対に行番号を選択させない簡単な方法を提供するパッケージを作成してみました。

## TikZ-numeral パッケージを作ってみた

https://github.com/yarakos95/tikz-numeral

TikZ-numeral パッケージは**数字そのものを TikZ で描画します**。（以降、TikZ 数字と呼称）

これによって、数字は文字としてではなく図として埋め込まれるため、絶対に選択できません。（OCR すればさすがに読まれるとは思いますが）

## 使い方

使い方は簡単です。TikZ-numeral パッケージを読み込むだけです。数字を TikZ 数字に変えるには、`\tikznumeral` を使います。

```latex
\documentclass{article}
\usepackage{tikz-numeral}
\begin{document}

\tikznumeral{0123}

\end{document}
```

listings パッケージと併用する場合は、次のようにします。

```latex
\usepackage{tikz-numeral}
\usepackage{listings}
%% \tikznumeral を使って \thelstnumber を再定義 (cf. p.35, 4.3.7 Line numbers, The Listings Package, Version 1.10c)
\renewcommand*\thelstnumber{\tikznumeral{\the\value{lstnumber}}}
```

あるいは、TikZ-numeral パッケージに `listings` オプションを与えます。これによって、`\thelstnumber` の再定義を自動的に行います。

```latex
\usepackage[listings]{tikz-numeral}
\usepackage{listings}
```

具体例は次のようになります。

```latex
\documentclass{article}
\usepackage{listings}
\usepackage[listings]{tikz-numeral}
\lstset{
  language = Python, backgroundcolor={\color[gray]{.90}}, breaklines = true, breakindent = 10pt, basicstyle = \ttfamily\scriptsize,
  frame = tb, framesep = 5pt, stepnumber = 1, tabsize = 4,
  %%
  %% 行番号を表示するために必須
  numbers = left,
}
\begin{document}

\begin{lstlisting}[gobble=2]
  import random

  inside = 0
  for _ in range(100000):
      x, y = random.random(), random.random()
      if x**2 + y**2 <= 1: inside += 1
  print("Approximation of Pi: ", 4 * inside / 100000)
\end{lstlisting}

\end{document}
```

|    ![image-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/c5e84b98-896d-4454-b0e5-2fa0c496fc08.png)    |
| :----------------------------: |
| ソースコードを選択している様子 |

ソースコードを選択していますが、行番号は選択していません。**絶対に行番号は選択できません**。

### 組み込みフォント

パッケージでは組み込みで 3 つのフォントが `font` パッケージオプションから利用できます。

|          オプション          | フォント                 |
| :--------------------------: | :----------------------- |
| `newcm-rm`<br>（デフォルト） | New Computer Modern      |
|          `newcm-tt`          | New Computer Modern Mono |
|          `newcm-sf`          | New Computer Modern Sans |

もしも追加で別のフォントが使いたい場合は、外部ファイルからフォントを追加する方法があります。詳細は [README][tikz-numeral] を参照してください。

[tikz-numeral]: https://github.com/yarakos95/tikz-numeral "yarakos95/tikz-numeral"

## 実装

実装としては単純で、数字を左から 1 つずつ TikZ による図に置き替えているだけです。

本当は `\arabic` のようにカウンタを直で受け取って変換するように作成するべきだったと思いますが <sup>[[参考]][doratex-1686757665]</sup>、よく分からなかったので expl3 で 1 つずつ置き替える方式を採っています。
その代わり、`\the\value` を使って `\tikznumeral{\the\value{counter}}` とすればカウンタも受け付けます。

[doratex-1686757665]: https://doratex.hatenablog.jp/entry/20230615/1686757665 "カウンタ値を全角数字で出力する完全展開可能な命令を定義する - TeX Alchemist Online"

TikZ 数字は 1 つずつが tikzpicture 環境内にあります。桁が増えると冗長的な気がしましたが、1 つの tikzpicture 環境に 2 桁以上の数字になったとき数字どうしの間隔を上手く保てなかったため、このようなことになっています。
本実装では `\useasboundingbox` を使っていずれの数字も 0～9 の最大幅が同じになるようにしています。

また、tikzpicture 環境のオプションを 0～9 のすべての数字で共通しています。もしも、個別にオプションを変更したい場合、厄介なことになる可能性があります。

### TikZ 数字の取得

TikZ で描かれた数字はフォントから取得しています。Python を使って SVG 経由で TikZ になっています。（これを実行する Python コードは [ここ][font2config] にあります）

[font2config]: https://github.com/yarakos95/tikz-numeral/blob/main/dev/README.md "tikz-numeral/dev/README.md at main · yarakos95/tikz-numeral"

そのため、手動で TikZ 数字を描く必要はなく、任意のフォントを TikZ 化して TikZ-numeral パッケージで利用できます。（ただし、作成した Python コード内で利用するフォント情報が無いフォントには使えません）

## 余談

絶対に行番号を選択させない方法を提供する TikZ-numeral パッケージを作ってみました。

この問題の解決案として、初めは PDF 上の文字を部分的にアウトライン化することを考えていましたが、LaTeX でアウトライン化はそもそも出来ないようでした。であれば、すでにアウトライン化された TikZ 数字を使おうと言うのがモチベーションでした。

TikZ-numeral パッケージは TikZ を使って数字を描画しているので、普通の数字よりもタイプセットに時間がかかるようになります。時間短縮のために PGF で直書きした方が良かったかもしれませんが、単純に PGF へ変換する外部手段がなさそうだったので、TikZ を採用しました。

「動作をもっと早くできる」と思った方は、是非リポジトリをフォークして高速化を目指してください。（単純に数字の変換部分が遅い可能性もある）

実は、パッケージ開発よりフォントから TikZ に変換する Python コードを書く方が時間がかかりました。ChatGPT にも頼りつつ書いています。Python コードは優しい目で見てください。

### expl3 メモ

外部ファイルを読み込む際は、`\file_get:nnNTF` を使います。`set_up` はカテゴリコードを調整するための部分です。

```latex
\file_get:nnNTF { file_name } { set_up } \l_tmpa_tl
{
  %% ファイルが存在したとき
}
{
  %% ファイルが存在しなかったとき
}
```

上の例では、ファイルの内容はすべて `\l_tmpa_tl` に格納されます。

TikZ-numeral パッケージでは、外部ファイルの内容を `\keys_set:` に渡したいため、次のようにしました。

```latex
\keys_set:nV { module } \l_tmpa_tl
```

これで、ファイルの内容が `\keys_set:` に受け渡りました。