Disable copying text from minted listing

First, I fully agree with Jesse Knight:

  • Whoever made this requirement at your faculty is a sadist

But if you use LuaTeX you can destroy the glyph to unicode mapping in the PDF file by creating a special font feature:

% lualatex -shell-escape minted.tex

\begin{filecontents*}{test.rb}
# this should not be copyable
def hello
  "world"
end
\end{filecontents*}

\documentclass[12pt,twoside,openright,a4paper]{report}
\usepackage[T1]{fontenc}
\usepackage{fontspec}
\usepackage[dvipsnames]{xcolor}
\usepackage{minted}
\setmainfont{Times New Roman}
\usepackage{luacode}
\begin{luacode*}
  local function nocopy(tfmdata)
    for _, c in next, tfmdata.characters do
      c.tounicode = nil
    end
  end
  fonts.handlers.otf.features.register {
    name        = "nocopy",
    description = "disallow copying for this font",
    manipulators = {
        base = nocopy,
        node = nocopy,
        plug = nocopy,
    }
  }
\end{luacode*}
\setmonofont[RawFeature=nocopy]{Latin Modern Mono}
\definecolor{bg}{rgb}{0.95,0.95,0.95}
\begin{document}
\chapter{Foo}
\section{Bar}
This should be copyable.
\inputminted[linenos, numbersep=5pt, tabsize=2, bgcolor=bg]{ruby}{test.rb}
And this again should be copyable.
\end{document}

With this approach you can not use the same font in the copyable and the not copyable part, so if you load any font once with nocopy and once without, only the first setting will be respected.

If you do not want to affect "regular" \texttt text and verbatim material, you can select a special font for minted blocks:

% lualatex -shell-escape minted.tex

\begin{filecontents*}{test.rb}
# this should not be copyable
def hello
  "world"
end
\end{filecontents*}

\documentclass[12pt,twoside,openright,a4paper]{report}
\usepackage[T1]{fontenc}
\usepackage{fontspec}
\usepackage[dvipsnames]{xcolor}
\usepackage{minted}
\setmainfont{Times New Roman}
\usepackage{luacode}
\begin{luacode*}
  local function nocopy(tfmdata)
    for _, c in next, tfmdata.characters do
      c.tounicode = nil
    end
  end
  fonts.handlers.otf.features.register {
    name        = "nocopy",
    description = "disallow copying for this font",
    manipulators = {
        base = nocopy,
        node = nocopy,
        plug = nocopy,
    }
  }
\end{luacode*}
\newfontfamily\nocopyfont[RawFeature=nocopy]{Courier New}
\renewcommand\myFont{\nocopyfont}
\fvset{fontfamily=myFont}
\definecolor{bg}{rgb}{0.95,0.95,0.95}
\begin{document}
\chapter{Foo}
\section{Bar}
This should be copyable.
\inputminted[linenos, numbersep=5pt, tabsize=2, bgcolor=bg]{ruby}{test.rb}
And this again should be copyable (\texttt{This too}).
{
\tracingall\nocopyfont
}
\end{document}

Solution

Perhaps my biggest hack yet ...

main.tex: compile with --shell-escape to allow \write18 commend

\documentclass[12pt,twoside,openright,a4paper]{report}
\usepackage{graphicx}
\newcommand{\nocopycode}[2]{%
  \immediate\write18{./code.sh #1 #2}
  \\[\textfloatsep]
  \includegraphics[width=\linewidth]{{#2}.png}%
  \\[\textfloatsep]
}
\usepackage{listings}
\begin{document}
\chapter{Foo}
\section{Bar}
This should be copyable.
\nocopycode{ruby}{hello.rb}
And this again should be copyable.
\end{document}

code.sh: you may have to chmod +x code.sh to allow the script to be executed

echo \\def\\xstyle{$1} \\def\\xfile{$2} > code-config.tex
pdflatex -interaction=nonstopmode code.tex
convert -trim -density 300 code.pdf $2.png

code.tex: where convert is imagemagick

\documentclass{article}
\usepackage{listings}
\usepackage{xcolor}
\lstset{
  basicstyle=\ttfamily,
  backgroundcolor=\color{lightgray},
}
\input{code-config.tex}
\begin{document}
  \pagenumbering{gobble}
  \lstinputlisting[language={\xstyle}]{\xfile}
\end{document}

... producing main.pdf:

output

Notes:

  • Feel free to play around with code.tex to get the styling you like
  • You may find you have aliasing artifacts in the code images, not much we can do
  • If you have a lot of code, your document size is going to be large
  • Don't ask me to get this working on Windows
  • Whoever made this requirement at your faculty is a sadist

If I were in your situation, I would follow these 3 steps:

  1. Export typeset minted listing to a pdf (standalone if it fits on same page or use another documentclass if listing spans multiple pages),
  2. Run ghostscript to convert text to vector paths,
  3. Import the vector path pdf produced by ghostscript in position of minted environment.

Here's an example (with standalone):

  1. Step-1: Lets say we have every minted code in a standalone file (there might be other ways to export inline code to a separate pdf, I don't know how)
% minted.tex
\documentclass{standalone}
\usepackage{minted}
\begin{document}
\begin{minipage}[t]{0.5\textwidth}
\begin{minted}{bash}
echo "Hello, world!"
\end{minted}
\end{minipage}
\end{document}

Run your favorite latex engine, I used >>lualatex --shell-escape minted.tex. It will produce minted.pdf that looks like: copyable text

  1. Run following script using command: >>./fonttopath.sh minted.pdf mintedpath.pdf
% fonttopath.sh
#!/bin/sh
if [ "x$1" = "x" -o "x$2" = "x" ]; then
        echo Usage: `basename "$0"` "<input.pdf>" "<output.pdf>" >&2
        exit 1
fi
gs -o "$2" -sDEVICE=pdfwrite -dNOCACHE -dNoOutputFonts -dNOSUBSTDEVICECOLORS -c 60000000 setvmthreshold -f "$1"
  1. Import mintedpath.pdf in place where you were planning to have your minted code.*

Advantage of this solution: 1. nothing will be rasterized, 2. file size will be smaller compared to exporting & importing image formats like jpeg, etc, 3. print quality won't degrade, 4. As requested in your comments, this also works for long code blocks that span multiple pages, for that use documentclass like article instead of standalone, and set geometry of page to match that of your report. Ghostscript can convert a pdf spanning pages to an equivalent vector path pdf spanning multiple pages that can then be imported as if you were adding another pdf document in your latex code.

  • Note: Depending on the pdf viewer you might still be able to select these vector paths, but try pasting them into a word processor or editor to realize that it doesn't copy any text (because no text is left at this point).