40

This is a follow-up to the recent xkcd comic:

enter image description here

Here is a simple version without justification:

\documentclass{article}
\usepackage[text={1.2in,3in}]{geometry}
\raggedright
\begin{document}
\noindent Their famous paper on the relationship between deindustrialization and the growth of ecological \ldots
\end{document}

enter image description here

How to do a snake in LaTeX? To make the question more specific, how could I get LaTeX to automatically add a long underscore and then a letter 's' to each line that does not reach the end of the column? I am assuming with that knowledge I would be able to create the body and the head of the snake.

  • 3
    +1 Interesting question, but maybe you could provide us with an MWE to start with and compare possible solutions? – Daniel May 4 '16 at 6:59
  • Care to explain in one line why snakes are used at all? Maybe a link to some resources? – Matsmath May 5 '16 at 7:39
  • 3
    @Matsmath I'm not sure querying the ultimate authority of xkcd is allowed:-) – David Carlisle May 5 '16 at 9:54
  • 1
    Rats, I am not good at jokes. I thought this is some ancient typographic stuff :-). – Matsmath May 5 '16 at 9:59
  • 1
    Some Arabic fonts can do kashida, which seems not entirely dissimilar... – zwol May 5 '16 at 20:49
21

this is lualatex only and draws a PDF line w points wide to pad out each line, if you are feeling artistic replace w 0 l by something that draws a snake of the same length

enter image description here

\documentclass{article}

\directlua{
PDF_LITERAL = node.subtype("pdf_literal")
WS=node.id("whatsit")
GLUE  = node.id("glue")
snakefill=function(head)
  for line in node.traverse_id(0,head) do
  for n in node.traverse_id(GLUE,line.head) do
  if n.subtype==9 then
  print('  glue: ' .. n.subtype .. ' ' ..node.effective_glue(n,line) )
  local w = node.effective_glue(n,line) / 65536
local sn = node.new(WS,PDF_LITERAL)
sn.data="0 G 0 0 m  " .. w .. " 0 l S 0 g"
  line=node.insert_before(line,n,sn)
    end
   end
  end
return head
end
}
\def\snakefill{%
\directlua{
luatexbase.add_to_callback("post_linebreak_filter",snakefill,"snake fill")
}%
}
\def\offsnakefill{%
\directlua{
luatexbase.remove_from_callback("post_linebreak_filter","snake fill")
}%
}

\begin{document}

\raggedright\parfillskip=0pt
\snakefill
one two three four five
one two three four five
one two three four fivexxxxxxxxxxxx
one two three four five
one two three four five
one two three four five
one two three four five
one two three four five
one two three four five
one two three four five
one two three four five
one two three four five
one two three four five
\end{document}

Or a version that's more snake like

enter image description here

% http://images.clipartpanda.com/cute-snake-clipart-black-and-white-30511wall.png

\directlua{
GLUE  = node.id("glue")
snakefill=function(head)
  for line in node.traverse_id(0,head) do
  for n in node.traverse_id(GLUE,line.head) do
  if n.subtype==9 then
  print('  glue: ' .. n.subtype .. ' ' ..node.effective_glue(n,line) )
  local w = node.effective_glue(n,line)
local sni = img.node{filename="cute-snake-clipart-black-and-white-30511wall.png"}
sni.height=500000
sni.width=w
  line=node.insert_before(line,n,sni)
    end
   end
  end
return head
end
}
  • 2
    Argh there goes snake, snake, it goes snake, oh it's a snake! :) If this link is not enough, click here :) – Paulo Cereda May 4 '16 at 14:14
  • I take it that texlive's current release (2015) is not sufficiently new for this? It complains about node.effective_glue being nil. To even get this far, I needed to \usepackage{luatextra}. – wrtlprnft May 5 '16 at 9:39
  • @wrtlprnft ah I should check. was 9 \rightskip in the previous luatex (I couldn't get it to use a symbolic name unlike the GLUE and WHATSIT) You shouldn't need a package with tl2015 as long as it is updated so that the latex format is 2015/10/01 or later when more luatex support was added – David Carlisle May 5 '16 at 9:46
  • @wrtlprnft 9 was \rightskip in luatex 0.80, but effective_glue is apparently new sorry, I didn't spot that, you'd need to get the glue stretch settings off line and the lower level glue spec components from n and calculate the actual length. I may add that later if I get time. – David Carlisle May 5 '16 at 9:52
  • Ok, ditching gentoo's texlive packages and using the installer removed the need for luatexextra. Manually calculating effective_glue unfortunately goes beyond my nonexisting knowledge of lua(tex). – wrtlprnft May 5 '16 at 10:54
29

Really is not a snake, but ...

enter image description here

\documentclass{article}
\usepackage[text={1.2in,3in}]{geometry}
\def\snake{\cleaders\hbox to .5em{\hss\ensuremath\sim\hss%
\hspace{-.3em minus -.2em}%
}\hfill$^{_\diamond}$\break}
\raggedright
\begin{document}
\noindent Their famous paper on the relationship between \snake
deindustrialization and \snake the growth of the main ecological  \snake
\end{document}
29

While I couldn't get David Carlisle's answer to work, I made a simple tielable vector version of the xkcd snake:

result

\documentclass{article}
\usepackage[text={1.2in,3in}]{geometry}
\usepackage[none]{hyphenat}

\directlua{
    PDF_LITERAL = node.subtype("pdf_literal")
    WS = node.id("whatsit")
    GLUE = node.id("glue")
    add_image = function(line, n, width, height, name)
        local sni = img.node{filename=name}
        sni.height = height
        sni.width = width
        return node.insert_before(line,n,sni)
    end
    add_snake = function(line, n, width, height)
        local segments_middle = 0;
        local segments_wave = 0;
        local unstretched_width = height * (1.0 + 1.5)

        while unstretched_width + height * 1.5 < width do
            unstretched_width = unstretched_width + height * 1.5
            segments_wave = segments_wave + 1
        end

        while unstretched_width + height * 0.5 < width do
            unstretched_width = unstretched_width + height * 0.5
            segments_middle = segments_middle + 1
        end

        local stretch = width / unstretched_width

        line = add_image(line, n, height * 1.0 * stretch, height, "snake_tail.pdf")

        for i = 1, segments_wave do
            line = add_image(line, n, height * 1.5 * stretch, height, "snake_wave.pdf")
        end

        for i = 1, segments_middle do
            line = add_image(line, n, height * 0.5 * stretch, height, "snake_middle.pdf")
        end

        line = add_image(line, n, height * 1.5 * stretch, height, "snake_head.pdf")

        return line
    end
    local w = 1250000 % remove when effective_glue works!
    snakefill = function(head)
        for line in node.traverse_id(0,head) do
            for n in node.traverse_id(GLUE,line.head) do
                if n.subtype == 9 then
                    %local w = node.effective_glue(n,line) / 65536
                    w = w * 1.1 % remove when effective_glue works!
                    local h = 500000
                    line = add_snake(line, n, w, 500000)
                end
            end
        end
        return head
    end
}
\def\snakefill{%
    \directlua{
        luatexbase.add_to_callback("post_linebreak_filter",snakefill,"snake fill")
    }%
}
\def\offsnakefill{%
    \directlua{
        luatexbase.remove_from_callback("post_linebreak_filter","snake fill")
    }%
}

\begin{document}
    \raggedright
    \snakefill
    \noindent Their famous paper on the relationship between deindustrialization and the growth of ecological\ldots\par
    \noindent Their famous paper on the relationship between deindustrialization and the growth of ecological\ldots\par
    \noindent Their famous paper on the relationship between deindustrialization and the growth of ecological\ldots\par
\end{document}

The snake images can be obtained from this gist. I'll update the post if I find a way to get effective_glue to work.

19

The snake could be improved. Also I never know how to do calculations with tikz coordinates, so I use zref instead.

Edit

Actually I think that my first example didn't work in all cases as the begin and end mark doesn't need to be at the same position even if they were on the same line. So it is better to test if the second mark is at the end of the line.

Some explanations: Unlike the other answers my text doesn't use \raggedright but is fully justificated. As is it currently the text has to be one one page, but as tikzmark stores the page number, tests for the page could be added. One has to add "\optsnake" everywhere but this could probably be done with luatex throught a callback.

\documentclass{article}
\textwidth=2.9cm
\usepackage[none]{hyphenat}
\usepackage{tikz}
\usepackage{zref-savepos}
\usetikzlibrary{tikzmark}
\usetikzlibrary{decorations.pathmorphing}



\newcounter{snakefill}
\newcommand\optsnake{%
 \stepcounter{snakefill}%
 \pgfmark{snakemarkbegin\the\value{snakefill}}%
 \zsavepos{snakemarkbegin\the\value{snakefill}}%
 \hfil
 \zsavepos{snakemarkend\the\value{snakefill}}%
 \pgfmark{snakemarkend\the\value{snakefill}}%
 \penalty50
 \hfilneg
 }

\newcommand\printsnakes{%
  \begin{tikzpicture}[overlay,remember picture]
  \foreach \x in {1,...,\the\value{snakefill}}
  {
   \ifnum  \zposx{snakemarkend\x}=\zposx{snakeendofline}
    \draw [->,decorate,decoration=snake] (pic cs:snakemarkbegin\x) -- (pic cs:snakemarkend\x);
   \fi
  }
  \end{tikzpicture}}
\begin{document}
\mbox{}\hfill\zsavepos{snakeendofline}% get the end of line value

\noindent
on the \optsnake relationship \optsnake
between \optsnake
Deindustrialization \optsnake
on the \optsnake relationship \optsnake
between \optsnake
Deindustrialization \optsnake
on the blblb \optsnake relationship \optsnake
between \optsnake
Deindustrialization \optsnake


\printsnakes

\end{document}

enter image description here

  • Since you draw a single snake, it is unclear to me whether your solution is automatic, or you need to do this magic every time you want to include a snake. Care to clarify your answer? – Matsmath May 5 '16 at 7:55
  • @Matsmath: I edited my answer – Ulrike Fischer May 5 '16 at 11:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.