[Tex/LaTex] MS Word-style Comments

mswordtikz-pgftodonotes

I'm not a fan of Microsoft Word, but I have missed the ability to comment on papers that I'm working on with friends using comments that appear as little bubbles in the margins. I've managed to create something that's pretty close to what I'm looking for, but there are some bugs that I haven't been able to resolve.

Here's a (hopefully) minimal working example document using the code that I've created:

\documentclass[draft]{article}

% For conditional control flow in draft mode
\usepackage{ifdraft}

% For larger margins when in draft mode
\ifdraft{\usepackage[rmargin=3in,marginparwidth=2.75in]{geometry}}{}

% For todo notes in the margins
\usepackage{todonotes}

% For highlighting in MS Word-like comments
\usepackage{soul,color}

% This is needed to ensure the line spacing of the margin notes is correct.
\usepackage{setspace}

% For conditional use of the \comment command based on the options provided.
\usepackage{ifthen}

% BEGIN HIGHLIGHTING: The following code provides a highlighting command, which is 
% used in my custom commenting system below.  If you want more information about 
% this code, see the following URL:
%   https://tex.stackexchange.com/questions/5959/cool-text-highlighting-in-latex
\usepackage{tikz}
\usetikzlibrary{calc}
\usetikzlibrary{decorations.pathmorphing}

\makeatletter

\newcommand{\defhighlighter}[3][]{%
  \tikzset{every highlighter/.style={color=#2, fill opacity=#3, #1}}%
}

\defhighlighter{yellow}{.5}

\newcommand{\highlight@DoHighlight}{
  \fill [ decoration = {random steps, amplitude=1pt, segment length=15pt}
        , outer sep = -15pt, inner sep = 0pt, decorate
        , every highlighter, this highlighter ]
        ($(begin highlight)+(0,8pt)$) rectangle ($(end highlight)+(0,-3pt)$) ;
}

\newcommand{\highlight@BeginHighlight}{
  \coordinate (begin highlight) at (0,0) ;
}

\newcommand{\highlight@EndHighlight}{
  \coordinate (end highlight) at (0,0) ;
}

\newdimen\highlight@previous
\newdimen\highlight@current

\DeclareRobustCommand*\highlight[1][]{%
  \tikzset{this highlighter/.style={#1}}%
  \SOUL@setup
  %
  \def\SOUL@preamble{%
    \begin{tikzpicture}[overlay, remember picture]
      \highlight@BeginHighlight
      \highlight@EndHighlight
    \end{tikzpicture}%
  }%
  %
  \def\SOUL@postamble{%
    \begin{tikzpicture}[overlay, remember picture]
      \highlight@EndHighlight
      \highlight@DoHighlight
    \end{tikzpicture}%
  }%
  %
  \def\SOUL@everyhyphen{%
    \discretionary{%
      \SOUL@setkern\SOUL@hyphkern
      \SOUL@sethyphenchar
      \tikz[overlay, remember picture] \highlight@EndHighlight ;%
    }{%
    }{%
      \SOUL@setkern\SOUL@charkern
    }%
  }%
  %
  \def\SOUL@everyexhyphen##1{%
    \SOUL@setkern\SOUL@hyphkern
    \hbox{##1}%
    \discretionary{%
      \tikz[overlay, remember picture] \highlight@EndHighlight ;%
    }{%
    }{%
      \SOUL@setkern\SOUL@charkern
    }%
  }%
  %
  \def\SOUL@everysyllable{%
    \begin{tikzpicture}[overlay, remember picture]
      \path let \p0 = (begin highlight), \p1 = (0,0) in \pgfextra
        \global\highlight@previous=\y0
        \global\highlight@current =\y1
      \endpgfextra (0,0) ;
      \ifdim\highlight@current < \highlight@previous
        \highlight@DoHighlight
        \highlight@BeginHighlight
      \fi
    \end{tikzpicture}%
    \the\SOUL@syllable
    \tikz[overlay, remember picture] \highlight@EndHighlight ;%
  }%
  \SOUL@
}
\makeatother
% END HIGHLIGHTING

% BEGIN MS WORD-STYLE COMMENTS
% This is the main function for providing MS Word-style comments.  There are two 
% ways to use it.  First, you can do the following: 
% 
%     \comment[akm]{This is comment text that will appear in the margin.}{This is 
%         body text that will appear in the main output.}
%
% Second, you can do the following, which will appear only as a note in the margin:
%
%     \comment[akm]{Here's a comment that won't highlight any text.  However, it 
%         should still point to the place in the text where it appears.}
% 
% Third, if you're Aaron, then you can leave out the initials.  Obviously, anyone 
% else reading this should consider altering the command for their own default 
% initials.
%
%     \comment{Here's a margin note using the default initials.}
\newcounter{akmctr}
\newcommand{\comment}[3][akm]{%
    % initials of the author (optional) + note in the margin
    \ifdraft{\refstepcounter{akmctr}%
    {%
        \setstretch{1.0}% line spacing
        \todo[color={red!100!green!33},size=\small,fancyline]{%
            \textbf{Comment [\uppercase{#1} \arabic{akmctr}]:}~#2}
        \ifthenelse{\equal{#3}{}}{}{\highlight[red!100!green!33]{#3}}%
        }%
    }{}%
}
% END MS WORD-STYLE COMMENTS

\begin{document}

\section{Introduction} % (fold)
\label{sec:introduction}
Let's start testing the comments.  Here's a comment with the default initials and no highlighted text.

Alice was beginning to get very \comment{These sample paragraphs are from the first chapter of Lewis Carroll's \emph{Alice in Wonderland}.  Please note that the original text shouldn't have weird spacing at the place where the comment was inserted, but it does.}tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, `and what is the use of a book,' thought Alice `without pictures or conversation?'

here's a comment that uses non-default initials and still doesn't highlight text.

So she was \comment[pno]{This comment should have a different counter.  Is it possible to setup counters based on the initials passed?}considering in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid), whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly a White Rabbit with pink eyes ran close by her.

Here's a comment that uses the default initials and highlights some text.

There was nothing so VERY remarkable in that; nor did Alice think it so VERY much out of the way to hear the Rabbit say to itself, `Oh dear! Oh dear! I shall be late!' (when she thought it over afterwards, \comment{This should also have no strange artifacts at the place where it was inserted.  It should also have a fancyline pointing to the highlighted portion of the text.}{it occurred to her that she ought to have wondered at this}, but at the time it all seemed quite natural); but when the Rabbit actually TOOK A WATCH OUT OF ITS WAISTCOAT-POCKET, and looked at it, and then hurried on, Alice started to her feet, for it flashed across her mind that she had never before seen a rabbit with either a waistcoat-pocket, or a watch to take out of it, and burning with curiosity, she ran across the field after it, and fortunately was just in time to see it pop down a large rabbit-hole under the hedge.

In another moment down went Alice after it, \comment[pno]{Basically, this is the same as before, but it should be for a different author, preferably with a different counter and a different color text.}{never once considering how in the world she was to get out again}.

It's probably also a good idea to test these things in lists.  Let's try making a few comments in a list environment.

\begin{enumerate}
    \item This is the first item in the list.
    \item This is the second item in the list.
    \item This is the \comment{This should be the third item in the list!}{fourth} item in the list.
    \item This is the fourth item in the list.
    \item This is the \comment{Here's a comment in a list with no highlighting.}final item in the list.
\end{enumerate}
% section introduction (end)

Obviously, there are many other environments that we should test these things in, but this is already going to be a huge post.  I'll stop here.

\end{document}

In particular, the section of code that creates the new command for commenting is this one:

% BEGIN MS WORD-STYLE COMMENTS
% This is the main function for providing MS Word-style comments.  There are two 
% ways to use it.  First, you can do the following: 
% 
%     \comment[akm]{This is comment text that will appear in the margin.}{This is 
%         body text that will appear in the main output.}
%
% Second, you can do the following, which will appear only as a note in the margin:
%
%     \comment[akm]{Here's a comment that won't highlight any text.  However, it 
%         should still point to the place in the text where it appears.}
% 
% Third, if you're Aaron, then you can leave out the initials.  Obviously, anyone 
% else reading this should consider altering the command for their own default 
% initials.
%
%     \comment{Here's a margin note using the default initials.}
\newcounter{akmctr}
\newcommand{\comment}[3][akm]{%
    % initials of the author (optional) + note in the margin
    \ifdraft{\refstepcounter{akmctr}%
    {%
        \setstretch{1.0}% line spacing
        \todo[color={red!100!green!33},size=\small,fancyline]{%
            \textbf{Comment [\uppercase{#1} \arabic{akmctr}]:}~#2}
        \ifthenelse{\equal{#3}{}}{}{\highlight[red!100!green!33]{#3}}%
        }%
    }{}%
}
% END MS WORD-STYLE COMMENTS

There are a couple of big issues and a couple of minor issues. Here's a list of things I haven't been able to figure out:

  1. [ANSWERED – See Yiannis's answer] Why are there extraneous spaces added to the body text when a comment is added?
  2. Why do some comments that aren't using text highlighting creating a little triangle of highlighting?
  3. Is there any way to get a separate counter for each author?
  4. Is there any way to get a separate color for both the comment bubbles and the highlighting for each author?

The first two problems are much more important to me than the other two.

I should also add that I don't really need the other change tracking features in MS Word. I use a source code control system (git) that meets all my needs for that. Also, I have read some other, very helpful posts on collaborating with non-LaTeX users, including What is a good strategy for obtaining comments on a LaTeX document from non-LaTeX using collaborators? and Workflow for reviewing PDFs Generated from TeX. Both are great, but I need comment bubbles to appear in PDFs because my research group regularly reading early drafts on paper that need some additional explanations.

Best Answer

I think it would have been better to focus your MWE and your questions in separate questions to enable both you and the persons answering to provide focus comments.

The answer to your first question is that you need to add the (%) to ensure that no redundant empty spaces occur.

This corrects the first problem.

\newcommand{\comment}[3][akm]{%
    % initials of the author (optional) + note in the margin
    \ifdraft{\refstepcounter{akmctr}%
    {%
        \setstretch{1.0}% line spacing
        \todo[color={red!100!green!33},size=\small,fancyline]{%
            \textbf{Comment [\uppercase{#1} \arabic{akmctr}]:}~#2}
        \ifthenelse{\equal{#3}{}}{}{\highlight[red!100!green!33]{#3}}%
        }%
    }{}%
}

See also this https://tex.stackexchange.com/a/19927/963.

I must also congratulate you on what looks like a very nicely set of to-do notes. I will come back and add the rest of the answers unless you want to split them as per my suggestion and please reduce your minimal by 80%.

How do we add counters for more users? Firstly, we need to define a method to add a user, as well as decide on the type of datastructure to hold the user names.

A comma delimited list would be a suitable data structure and one which we can manipulate via LaTeX kernel commands or the etoolbox package or even write our own commands to do so. This list is simply defined in a macro:

  \def\users{yiannis,egreg,martin}

To add more users, we can just manually enter another name or create another command for it, opting for the latter we use the LaTeX kernel \g@addto@macro command to do so.

 \def\adduser#1{\g@addto@macro{\users}{,#1}}

To add a user use \adduser{Mary} and if you want to print the list of users, just expand the list by typing \users.

The next step is to create the counters automatically, since we have a list we will use a for-loop, within a macro we call \counterfactory.

\def\counterfactory#1{\@for\next:=#1\do{%
\ifcsname c@\next\endcsname%
 \else
   \newcounter{\next}%
   \setcounter\next{0}
\fi
}}
\AtBeginDocument{\counterfactory\users}

When LaTeX allocates a counter for example foo the counter it creates is c@counter. The \ifcsname part of the code in the for-loop just checks if the counter was created earlier and if not then goes on and creates the counter and sets it to zero.

Including the above code fragments in your preamble and adjusting the relevant parts of your \comment, macro such as \ifdraft{\refstepcounter{akmctr} to \ifdraft{\refstepcounter{#1} etc would do the trick. Here is a minimal to see the counter part operating on its own.

\documentclass{article}
\makeatletter
\def\users{egreg,martin}
\def\adduser#1{\g@addto@macro{\users}{,#1}}
\def\counterfactory#1{\@for\next:=#1\do{%
\ifcsname c@\next\endcsname%
 \else
   \newcounter{\next}%
   \setcounter\next{0}
\fi
}}
% activate at beginning of document
\AtBeginDocument{\counterfactory\users}
% add a few users   
\adduser{aaron}
\adduser{yiannis}
\adduser{george}
\begin{document}
This documented commented by \users. \\
\stepcounter{yiannis}
\theyiannis\\% 1
\theaaron\\ %0
\setcounter{aaron}{100}\\ %100
\theaaron\\
\end{document}

One could use a similar technique to add a specific user color. We can modify the \adduser macro to take two parameters, the first would be the user name and the second to be the color.

 \def\adduser#1#2{%
   \g@addto@macro{\users}{,#1}%
   \expandafter\def\csname #1@color\endcsname{#2}
  }

Here is another MWE to test the changes.

\documentclass{article}
\usepackage{xcolor}
\makeatletter
\def\users{egreg,martin}
\def\adduser#1#2{%
   \g@addto@macro{\users}{,#1}%adds user to list
   \expandafter\def\csname #1@color\endcsname{#2} %holds the color name in `\name@color`
  }
\def\counterfactory#1{\@for\next:=#1\do{%
\ifcsname c@\next\endcsname%
 \else
   \newcounter\next%
   \setcounter\next{0}
\fi
}}
\AtBeginDocument{\counterfactory\users}

\adduser{aaron}{red}
\adduser{yiannis}{blue}
\adduser{george}{orange}
\begin{document}
This documented commented by \users. \\
\stepcounter{yiannis}
\theyiannis\\% 1
\theaaron\\ %0
\setcounter{aaron}{100}\\ %100
\theaaron\\

\color{\yiannis@color} This text is printed in blue, 
 whereas Aaron's color is printed in \color{\aaron@color} red.
\end{document}

So where you say \highlight[red!100!green!33], you can replace with the user's color command. This would hopefully help you iterate your code and get what you want, as well as understand a bit better what went wrong.