% % randtext.sty % % Charles Duan (2004/12/20) % % Provides one useful macro, \randomize{TEXT}. Result is a typeset box that % looks, on paper, like TEXT, but whose letters have in fact been placed in % random order so that they are not copiable from the file directly. % % In other words, typing: % % This is a \randomize{random-text} test. % % would produce output that looks like: % % This is a random-text test. % % but if you tried to copy-paste it from the output file, you would probably get % % This is a mdoxt-etnra test. % % The function of this odd macro is to obfuscate e-mail addresses, say on a PDF % document put online, so that the human reader sees the address as expected, % but e-mail address harvesters and spambots cannot determine the address. Since % this macro is done entirely using TeX typesetting commands, it requires no % external image generation or anything, and the typeset result is just as % high-quality as if no obfuscation had taken place. % % This macro does take into account kerning between character pairs, but it does % not account for ligatures. To make appropriate ligatures, surround the % ligature characters with braces. % % This package supersedes "switcheml.sty", written by the same author. (That is % the reason that the internal macros begin with "se@". % % Requires the file "random.tex", by Donald Arseneau. I believe that this file % comes as part of a standard TeX distribution. % % \ProvidesPackage{randtext}[2004/12/20 Obfuscate text] % % We use the random package to pick random numbers. \input random % % \se@count stores the number of letters/groups to typeset. For instance, % \randomize{abc{de}fg{hij}kl} % contains 9 letters/groups. \newcount\se@count % % \se@random is a random number register. \newcount\se@random % % \se@pos is used to store the width of each section of the randomized text. \newdimen\se@pos % % Randomize the text of the argument. \def\randomize#1{% % Do nothing for empty argument. \def\se@tmp{#1}\ifx\se@tmp\@empty\else % Make our work local \begingroup % In case this is the first thing in the paragraph, go to horizontal % mode \leavevmode % Initialize \se@count=1, \se@pos=0pt \se@count\@ne \se@pos\z@ % Now iterate over the argument text. The \relax is put at the end % as an untypeset token. \se@randomize#1\relax\@stop \endgroup \fi } % % Iterator function for \randomize. Essentially, this function will be executed % once for each token in the list. \def\se@randomize#1#2#3\@stop{% % Define a macro \pos@[\se@count]. Essentially, this makes a unique, % numbered macro for each letter/group. \expandafter\edef\csname pos@\the\se@count\endcsname{% % The macro produces a zero-width box that jumps forward \se@pos and % then typesets the letter/group. Note the \the\se@pos, to force current % evaluation of the distance to move forward. \noexpand\rlap{\hskip\the\se@pos\relax#1}% }% % Advance \se@pos forward by the width of the first group and the % immediately subsequent group, and then backward by the width of the second % group. The effect is to advance \se@pos by the first group and the kern % between the first and second groups. \setbox\z@\hbox{#1#2}\advance\se@pos\wd\z@ \setbox\z@\hbox{#2}\advance\se@pos-\wd\z@ % % If there's nothing left to iterate over, then: % \se@pos is the width of the whole text % \se@count is the number of letters/groups, or the highest \pos@[number] % so we make a box of width \se@pos, place our random letters in it with % \se@rand@place, and fill up the box for good measure. % % Otherwise, increment \se@count and run the function again. % % Note two things: % 1. We put braces around #2 but not #3. Since there's an extra \relax at % the end of #3 at all times, we don't have the problem of TeX stripping any % braces from it. % 2. The other reason we needed that \relax was that, since we read % letters/groups in pairs, the last letter/group would only be read if there % was an extra token at the end (i.e., the \relax). \def\se@tmp{#3}\ifx\se@tmp\@empty \expandafter\@firstoftwo \else \expandafter\@secondoftwo \fi {\hb@xt@\se@pos{\se@rand@place\hfill}}% {\advance\se@count\@ne\se@randomize{#2}#3\@stop}% } % % Now all of our \pos@[number] functions are set, and we just need to randomly % select and place. \def\se@rand@place{% % % Pick a random number between one and \se@num. (If \se@num>3, then ensure % that the number is over three. This way, the first two elements will % always be typeset last, guaranteeing at least some contortion.) \ifnum\se@count>\z@ \ifnum\se@count>\thr@@ \setrannum\se@random\thr@@\se@count \else \setrannum\se@random\@ne\se@count \fi % Execute \pos@[\se@random]. \csname pos@\the\se@random\endcsname % Now replace \pos@[\se@random] with \pos@[\se@count] and decrement % \se@count. This way, the next random number will be guaranteed to % typeset an unselected value. \ifnum\se@random=\se@count\else \edef\reserved@a{% \let\expandafter\noexpand\csname pos@\the\se@random\endcsname \expandafter\noexpand\csname pos@\the\se@count\endcsname }\reserved@a \fi \advance\se@count\m@ne % Repeat \expandafter\se@rand@place \fi } \endinput