[Tex/LaTex] How to make listings code indentation remain unchanged when copied from PDF

copy/pasteindentationlistings

So I am using listings package for code examples. But there is a problem. When I copy the code examples from the final PDF document, they are usually not the same – the indentation is gone, there are random spaces after parenthesis, etc. Does anyone know how to make sure that the code that I copy from the final document is the same as I wrote it in the LaTeX document?

Here is what I mean. Compile this document:

\documentclass[12pt,oneside]{memoir}

\usepackage{listings}
\usepackage[T1]{fontenc}
\usepackage{xcolor}
\usepackage{textcomp}

\definecolor{codebg}{HTML}{EEEEEE}
\definecolor{codeframe}{HTML}{CCCCCC}

\lstset{language=Awk}
\lstset{backgroundcolor=\color{codebg}}
\lstset{frame=single}
\lstset{framesep=10pt}
\lstset{rulecolor=\color{codeframe}}
\lstset{upquote=true}
\lstset{basicstyle=\ttfamily}
\lstset{showstringspaces=false}

\begin{document}

This code example prints out all users on your system:

\begin{lstlisting}[language=c]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LINE_LEN 1024

int main() {
    char line[MAX_LINE_LEN];
    FILE *in = fopen("/etc/passwd", "r");
    if (!in) exit(EXIT_FAILURE);

    while (fgets(line, MAX_LINE_LEN, in) != NULL) {
        char *sep = strchr(line, ':');
        if (!sep) exit(EXIT_FAILURE);
        *sep = '\0';
        printf("%s\n", line);
    }
    fclose(in);
    return EXIT_SUCCESS;
}
\end{lstlisting}

\end{document}

Now compile it, open it, select and copy the code example and paste it in a text editor. Here is the result I get:

# include <stdio .h>
# include <stdlib .h>
# include <string .h>
# define MAX_LINE_LEN 1024
int main () {
char line [ MAX_LINE_LEN ];
FILE *in = fopen ("/ etc / passwd ", "r");
if (! in) exit ( EXIT_FAILURE );
while ( fgets (line , MAX_LINE_LEN , in) != NULL ) {
char * sep = strchr (line , ':');
if (! sep ) exit ( EXIT_FAILURE );
* sep = '\0 ';
printf ("%s\n", line );
}
fclose (in );
return EXIT_SUCCESS ;
}

Jeez! What's this… The indentation is gone. There are spaces in included headers <stdio .h>, there are spaces after parenthesis [ MAX_LINE_LEN ].

Anyone knows how to fix this? I'd like the code examples in my book to be copy/paste-able into a text editor so people can try them out easily.

Best Answer

To prevent random spaces when copying the text from a listing, you need to use

\lstset{columns=flexible}

But you will now note that the text is not neatly aligned anymore; to solve this, you need to also use

\lstset{keepspaces=true}

This will not solve your problem with spaces disappearing at the beginning of lines when copying. The following hack will produce visible spaces and then make them invisible by coloring them in the background color:

\makeatletter
\def\lst@outputspace{{\ifx\lst@bkgcolor\empty\color{white}\else\lst@bkgcolor\fi\lst@visiblespace}}
\makeatother

This hack is not perfect, however, as the typesetted character is really a visible space, not a space (so searching the pdf for char line will not work) and some PDF readers (like Mac's preview) will copy a visible space. It works under Acrobat Reader and it's extremely pleasant to be able to quickly copy/paste code without problem (perhaps the problem can be circumvented by writing direct PDF code to tell that it's a space, I've never had the time to try). It might also not work with all typewriter fonts.

Here's the full code of your example:

\documentclass[12pt,oneside]{memoir}

\usepackage{listings}
\usepackage[T1]{fontenc}
\usepackage{xcolor}
\usepackage{textcomp}

\definecolor{codebg}{HTML}{EEEEEE}
\definecolor{codeframe}{HTML}{CCCCCC}

\lstset{language=Awk}
\lstset{backgroundcolor=\color{codebg}}
\lstset{frame=single}
\lstset{framesep=10pt}
\lstset{rulecolor=\color{codeframe}}
\lstset{upquote=true}
\lstset{basicstyle=\ttfamily}
\lstset{showstringspaces=false}

\lstset{columns=flexible}
\lstset{keepspaces=true}
\makeatletter
\def\lst@outputspace{{\ifx\lst@bkgcolor\empty\color{white}\else\lst@bkgcolor\fi\lst@visiblespace}}
\makeatother

\begin{document}

This code example prints out all users on your system:

\begin{lstlisting}[language=c]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LINE_LEN 1024

int main() {
    char line[MAX_LINE_LEN];
    FILE *in = fopen("/etc/passwd", "r");
    if (!in) exit(EXIT_FAILURE);

    while (fgets(line, MAX_LINE_LEN, in) != NULL) {
        char *sep = strchr(line, ':');
        if (!sep) exit(EXIT_FAILURE);
        *sep = '\0';
        printf("%s\n", line);
    }
    fclose(in);
    return EXIT_SUCCESS;
}
\end{lstlisting}

\end{document}