15

I've had a plain chat conversation and need display them similarly to how they are designed in Messenger. Since this is a long conversation, I need a method to do it at once. I know this might be impossible to have all, so I just need to mimic it as close as possible.

  • Left-aligned gray bubbles (103, 184, 104) with black text;
    Right-aligned green bubbles (241, 240, 240) with white text
  • Bubbles are only split after a paragraph (equivalent to an enter press when chatting). Long message with multiple lines will be kept in one bubble.
  • Left and right edges are half circle
  • Margins between same color bubbles are small, margins between different color bubbles are large.


FYI: What word processing tools that can work with complicate text boxing? in Graphic Design
How to wrap every paragraph with a custom text box in Word? in Super User

Ooker
  • 1,450
  • 1
  • 12
  • 26

2 Answers2

30

With tcolorbox:

\documentclass{article}
\usepackage[many]{tcolorbox}
\usepackage{xcolor}
\usepackage{varwidth}
\usepackage{environ}
\usepackage{xparse}

\newlength{\bubblewidth}
\AtBeginDocument{\setlength{\bubblewidth}{.75\textwidth}}
\definecolor{bubblegreen}{RGB}{103,184,104}
\definecolor{bubblegray}{RGB}{241,240,240}

\newcommand{\bubble}[4]{%
  \tcbox[
    colback=#1,
    colframe=#1,
    #2,
  ]{\color{#3}\begin{varwidth}{\bubblewidth}#4\end{varwidth}}%
}

\ExplSyntaxOn
\seq_new:N \l__ooker_bubbles_seq
\tl_new:N \l__ooker_bubbles_first_tl
\tl_new:N \l__ooker_bubbles_last_tl

\NewEnviron{rightbubbles}
 {
  \raggedleft\sffamily
  \seq_set_split:NnV \l__ooker_bubbles_seq { \par } \BODY
  \int_compare:nTF { \seq_count:N \l__ooker_bubbles_seq < 2 }
   {
    \bubble{bubblegreen}{rounded~corners}{white}{\BODY}
   }
   {
    \seq_pop_left:NN \l__ooker_bubbles_seq \l__ooker_bubbles_first_tl
    \seq_pop_right:NN \l__ooker_bubbles_seq \l__ooker_bubbles_last_tl
    \bubble{bubblegreen}{sharp~corners=southeast}{white}{\l__ooker_bubbles_first_tl}\par
    \seq_map_inline:Nn \l__ooker_bubbles_seq
     {
      \bubble{bubblegreen}{sharp~corners=east}{white}{##1}\par
     }
    \bubble{bubblegreen}{sharp~corners=northeast}{white}{\l__ooker_bubbles_last_tl}\par
   }
 }
\NewEnviron{leftbubbles}
 {
  \raggedright\sffamily
  \seq_set_split:NnV \l__ooker_bubbles_seq { \par } \BODY
  \int_compare:nTF { \seq_count:N \l__ooker_bubbles_seq < 2 }
   {
    \bubble{bubblegray}{rounded~corners}{black}{\BODY}
   }
   {
    \seq_pop_left:NN \l__ooker_bubbles_seq \l__ooker_bubbles_first_tl
    \seq_pop_right:NN \l__ooker_bubbles_seq \l__ooker_bubbles_last_tl
    \bubble{bubblegray}{sharp~corners=southwest}{black}{\l__ooker_bubbles_first_tl}\par
    \seq_map_inline:Nn \l__ooker_bubbles_seq
     {
      \bubble{bubblegray}{sharp~corners=west}{black}{##1}\par
     }
    \bubble{bubblegray}{sharp~corners=northwest}{black}{\l__ooker_bubbles_last_tl}\par
   }
 }
\ExplSyntaxOff

\begin{document}

\begin{rightbubbles}
Left-aligned gray bubbles (241, 240, 240) with black text

Right-aligned green bubbles (103, 184,104) with white text

Bubbles only break after a paragraph (equivalent to an enter press 
when chatting). Long message with multiple lines will be kept in one bubble.

Left and right edges are round.
\end{rightbubbles}

\begin{leftbubbles}
Left-aligned gray bubbles (241, 240, 240) with black text

Right-aligned green bubbles (103, 184,104) with white text

Bubbles only break after a paragraph (equivalent to an enter press 
when chatting). Long message with multiple lines will be kept in one bubble.

Left and right edges are round.
\end{leftbubbles}

\begin{rightbubbles}
Single
\end{rightbubbles}

\begin{leftbubbles}
End
\end{leftbubbles}

\end{document}

enter image description here

If I change the definition of \bubble to

\newcommand{\bubble}[4]{%
  \tcbox[
    arc=5mm,
    colback=#1,
    colframe=#1,
    #2,
  ]{\color{#3}\begin{varwidth}{\bubblewidth}#4\end{varwidth}}%
}

then the output is as follows:

enter image description here

Improved version

You can define the spacing between bubbles by setting \bubblesep. Different color bubbles are enclosed in flushleft and flushright environments, so there will be \topsep space between them.

\documentclass{article}
\usepackage[many]{tcolorbox}
\usepackage{xcolor}
\usepackage{varwidth}
\usepackage{environ}
\usepackage{xparse}

\newlength{\bubblesep}
\newlength{\bubblewidth}
\setlength{\bubblesep}{2pt}
\AtBeginDocument{\setlength{\bubblewidth}{.75\textwidth}}
\definecolor{bubblegreen}{RGB}{103,184,104}
\definecolor{bubblegray}{RGB}{241,240,240}

\newcommand{\bubble}[4]{%
  \tcbox[
    on line,
    arc=4.5mm,
    colback=#1,
    colframe=#1,
    #2,
  ]{\color{#3}\begin{varwidth}{\bubblewidth}#4\end{varwidth}}%
}

\ExplSyntaxOn
\seq_new:N \l__ooker_bubbles_seq
\tl_new:N \l__ooker_bubbles_first_tl
\tl_new:N \l__ooker_bubbles_last_tl

\NewEnviron{rightbubbles}
 {
  \begin{flushright}
  \sffamily
  \seq_set_split:NnV \l__ooker_bubbles_seq { \par } \BODY
  \int_compare:nTF { \seq_count:N \l__ooker_bubbles_seq < 2 }
   {
    \bubble{bubblegreen}{rounded~corners}{white}{\BODY}\par
   }
   {
    \seq_pop_left:NN \l__ooker_bubbles_seq \l__ooker_bubbles_first_tl
    \seq_pop_right:NN \l__ooker_bubbles_seq \l__ooker_bubbles_last_tl
    \bubble{bubblegreen}{sharp~corners=southeast}{white}{\l__ooker_bubbles_first_tl}
    \par\nointerlineskip
    \addvspace{\bubblesep}
    \seq_map_inline:Nn \l__ooker_bubbles_seq
     {
      \bubble{bubblegreen}{sharp~corners=east}{white}{##1}
      \par\nointerlineskip
      \addvspace{\bubblesep}
     }
    \bubble{bubblegreen}{sharp~corners=northeast}{white}{\l__ooker_bubbles_last_tl}
    \par
   }
   \end{flushright}
 }
\NewEnviron{leftbubbles}
 {
  \begin{flushleft}
  \sffamily
  \seq_set_split:NnV \l__ooker_bubbles_seq { \par } \BODY
  \int_compare:nTF { \seq_count:N \l__ooker_bubbles_seq < 2 }
   {
    \bubble{bubblegray}{rounded~corners}{black}{\BODY}\par
   }
   {
    \seq_pop_left:NN \l__ooker_bubbles_seq \l__ooker_bubbles_first_tl
    \seq_pop_right:NN \l__ooker_bubbles_seq \l__ooker_bubbles_last_tl
    \bubble{bubblegray}{sharp~corners=southwest}{black}{\l__ooker_bubbles_first_tl}
    \par\nointerlineskip
    \addvspace{\bubblesep}
    \seq_map_inline:Nn \l__ooker_bubbles_seq
     {
      \bubble{bubblegray}{sharp~corners=west}{black}{##1}
      \par\nointerlineskip
      \addvspace{\bubblesep}
     }
    \bubble{bubblegray}{sharp~corners=northwest}{black}{\l__ooker_bubbles_last_tl}\par
   }
  \end{flushleft}
 }
\ExplSyntaxOff

\begin{document}

\begin{rightbubbles}
Left-aligned gray bubbles (241, 240, 240) with black text

Right-aligned green bubbles (103, 184,104) with white text

Bubbles only break after a paragraph (equivalent to an enter press 
when chatting). Long message with multiple lines will be kept in one bubble.

Left and right edges are round.
\end{rightbubbles}

\begin{leftbubbles}
Left-aligned gray bubbles (241, 240, 240) with black text

Right-aligned green bubbles (103, 184,104) with white text

Bubbles only break after a paragraph (equivalent to an enter press 
when chatting). Long message with multiple lines will be kept in one bubble.

Left and right edges are round.
\end{leftbubbles}

\begin{rightbubbles}
Single
\end{rightbubbles}

\begin{leftbubbles}
End
\end{leftbubbles}

\end{document}

enter image description here

egreg
  • 1,121,712
  • thanks. Seems like there's no way to make the edges rounder, right? And why do you decide to use __ooker_bubbles_ string? It's nice to have my name, but isn't it long? – Ooker Dec 08 '17 at 15:57
  • 2
    It's an expl3 convention that functions an variables should have a common prefix and a name. For the rounder edges I added an example. – egreg Dec 08 '17 at 16:13
  • Thanks. If we use arc=4.5mm the edges will be more natural. One more step: can the space between the same color bubbles be smaller? Another step: can the space between two different color bubble be larger? – Ooker Dec 08 '17 at 17:07
  • I want to use \setlength{\parskip}{10pt} for normal text but it affects the bubble space. Do you know how to prevent this? – Ooker Dec 13 '17 at 11:11
  • did you mean \color{#3}\setlength{\parskip}{0pt} in \newcommand{\bubble}? It doesn't work for me – Ooker Dec 13 '17 at 12:04
  • @Ooker Sorry, it should be added at the beginning of both leftbubbles and rightbubbles. Place it just after \begin{flushright} or \begin{flushleft} – egreg Dec 13 '17 at 12:06
  • {\color{#3}\begin{varwidth}{\bubblewidth}#4\end{varwidth}}\setlength{\parskip}{0pt} works. Is there any potential harm for this, or for what you just suggest? – Ooker Dec 13 '17 at 12:10
  • @Ooker No, that's wrong. – egreg Dec 13 '17 at 12:12
  • but it just... works? – Ooker Dec 13 '17 at 12:15
  • Yes, but for the wrong reason. The right place is where I suggested. – egreg Dec 13 '17 at 12:16
16

In plain TeX (using pdfTeX) we can do this:

\font\ssf=cmssdc10
\def\Black{\pdfliteral{0 g}}
\def\Blue{\pdfliteral{0 0 1 rg}}
\def\White{\pdfliteral{1 g}}

\def\bptopt#1{.9963 0 0 .9963 0 0 cm 17 0 0 #117 0 3.5 cm }
\def\circle{.5 0 m .5 .276 .276 .5 0 .5 c -.276 .5 -.5 .276 -.5 0 c
           -.5 -.276 -.276 -.5 0 -.5 c .276 -.5 .5 -.276 .5 0 c }
\def\hcircle{.5 .276 .276 .5 0 .5 c -.276 .5 -.5 .276 -.5 0 c }
\def\fullround{\pdfliteral{q \bptopt+ \circle f Q}}
\def\halfround{%
   \ifnum\count255=13
      \pdfliteral{q \bptopt+ -.5 -.5 m .5 -.5 l .5 0 l \hcircle  h f Q}%
   \else \ifnum\count255=14
      \pdfliteral{q \bptopt- -.5 -.5 m .5 -.5 l .5 0 l \hcircle  h f Q}%
   \else
      \pdfliteral{q \bptopt+ -.5 -.5 m -.5 .5 l .5 .5 l .5 -.5 l h f Q}%
   \fi\fi
}
\def\xround#1#2{\if #1#2\halfround \else \fullround\fi}

\def\messenger#1#2{\medskip\setbox0=\vbox{\penalty13
   \widowpenalty=14 \clubpenalty=0 \interlinepenalty=0
   \def\\{\unskip\break}\rightskip=0pt plus 1fil\parindent=0pt \ssf #2\par
   \setbox1=\box2
   \loop \setbox1=\lastbox
       \unless\ifvoid1
          \unskip\unskip \count255=\lastpenalty \unpenalty
          \setbox1=\hbox{\unhbox1\unskip\unskip}
          \setbox1=\hbox to\hsize{\hss\Blue\xround l#1%
              \rlap{\vrule height12pt depth5pt width\wd1 \xround r#1}\White 
              \box1 \Black \if l#1\hfill\fi}
          \global\setbox2=\vbox{\box1 \vskip1pt \unvbox2}
   \repeat
   }
   \unvbox2
   \medskip
}


\hsize=9cm

This is test:

\messenger r {
This is the native format from messenger \\
Notice, how the borders of the sides are circular, not straight and the last
notice is here.
}
\messenger l {
Second message \\
is here.
}

\bye

Edit: I modified the code in order to enable to print two variants: left aligned, if \messenger l is used and right aligned, if \messenger r is used. As your comment says.

The result:

messenger2

Edit The specifications from OP were changed after my first result was published here. First, the colour was blue and bubbles were right aligned. Second, bubbles must be left aligned too. The last change of the specification includes a new request for colours and for formatting the paragraphs. I supposed that when there is my code published then slight changes of formatting a paragraphs or changing colours is just simple task for OP. But probably it cannot be expected because OP does not know what does mean \bye for example. Moreover, egreg presented second solution here based on a specific Expl3 language. He was in advantage because he knew newest specification from OP. I cannot keep only Expl3 solution here, because it is obscure language from my point of view which cannot help to teach the basics TeX principles. So, there is my second solution using TeX primitives and accepting the new specification form OP. I hope that there will be no more specification changes from OP.

\font\ssf=cmssdc10
\def\Black{\pdfliteral{0 g}}
\def\Blue{\pdfliteral{0 0 1 rg}}
\def\White{\pdfliteral{1 g}}
\def\Gray{\pdfliteral{.945 .941 .941 rg}}
\def\Green{\pdfliteral{.404 .722 .408 rg}}
\def\bcolor#1{\if r#1\Green \else \Gray \fi}
\def\fcolor#1{\if r#1\White \else \Black \fi}

\def\bptopt#1{.9963 0 0 .9963 0 0 cm 17 0 0 #117 0 3.5 cm }
\def\circle{.5 0 m .5 .276 .276 .5 0 .5 c -.276 .5 -.5 .276 -.5 0 c
           -.5 -.276 -.276 -.5 0 -.5 c .276 -.5 .5 -.276 .5 0 c }
\def\hcircle{.5 .276 .276 .5 0 .5 c -.276 .5 -.5 .276 -.5 0 c }
\def\fullround{\pdfliteral{q \bptopt+ \circle f Q}}
\def\halfround{%
   \ifcase\count255
       \pdfliteral{q \bptopt+ -.5 -.5 m -.5 .5 l .5 .5 l .5 -.5 l h f Q}%
   \or \pdfliteral{q \bptopt+ -.5 -.5 m .5 -.5 l .5 0 l \hcircle  h f Q}%
   \or \pdfliteral{q \bptopt- -.5 -.5 m .5 -.5 l .5 0 l \hcircle  h f Q}%
   \or \fullround \fi
}
\def\xround#1#2{\ifdim\ht1>12pt
   \dimen0=\ht1 \advance\dimen0 by-12pt
   \hbox to0pt{\hss\vbox{\xroundA#1#2\kern\dimen0 \xroundA#1#2}%
      \rlap{\kern-8.5pt\raise4.5pt\vbox{\hrule width17pt height\dimen0}}\hss}% 
   \else \xroundA#1#2\fi}
\def\xroundA#1#2{\if #1#2\halfround \else \fullround\fi}

\long\def\messenger#1#2{\medskip\setbox0=\vbox{\penalty13
   \widowpenalty=0 \clubpenalty=0 \interlinepenalty=0
   \everypar={\setbox0=\lastbox\endgraf\vbox\bgroup
      \everypar={\vrule height12pt width0pt}}
   \def\par{\ifhmode\endgraf\egroup\fi}
   \def\\{\unskip\break}\rightskip=0pt plus 1fil\parindent=0pt \ssf #2\par
   \everypar={}\let\par=\endgraf \setbox1=\box2
   \loop \setbox1=\lastbox
       \unless\ifvoid1
          \unskip\unskip 
          \count255=\ifvoid2 \ifnum\lastpenalty=13 3\else 2\fi 
                    \else    \ifnum\lastpenalty=13 1\else 0\fi \fi
          \unpenalty
          \ifdim\ht1>12pt \else
             \setbox0=\vbox{\unvbox1 \lastbox \setbox1=\lastbox 
                      \global\setbox1=\hbox{\unhbox1\unskip\unskip}}
          \fi
          \setbox1=\hbox to\hsize{\hss\bcolor#1\xround l#1%
              \rlap{\vrule height\ht1 depth5pt width\wd1 \xround r#1}%
              \fcolor#1\box1 \Black \if l#1\hfill\fi}
          \global\setbox2=\vbox{\box1 \vskip2pt \unvbox2}
   \repeat
   }
   \unvbox2
   \medskip
}

\hsize=9cm

This is test:

\messenger r {
Left-aligned gray bubbles (241, 240, 240) with black text

Right-aligned green bubbles (103, 184,104) with white text

Bubbles only break after a paragraph (equivalent to an enter press 
when chatting). Long message with multiple lines will be kept in one bubble.

Left and right edges are round.
}
\messenger l {
Left-aligned gray bubbles (241, 240, 240) with black text

Right-aligned green bubbles (103, 184,104) with white text

Bubbles only break after a paragraph (equivalent to an enter press 
when chatting). Long message with multiple lines will be kept in one bubble.
}
\messenger r {
Single
}
\messenger l {
End
}

\bye

messenger3

wipet
  • 74,238
  • I've update my question. Can you make different color for different bubbles, and long message with multiple lines will be kept in one bubble? Thanks – Ooker Dec 08 '17 at 11:57
  • also, what does \bye do? I actually have to delete it to run the script – Ooker Dec 08 '17 at 13:53
  • 2
    \bye is defined in plain TeX. I mentioned that this was tested in plain TeX in my answer. I don't use LaTeX. If you are using LaTeX then you must remove \bye (of course) and you must add tons of another lines of code and you must pray that it will work. – wipet Dec 08 '17 at 14:36
  • 1
    I am very happy to see you around, Petr! :) – Paulo Cereda Dec 08 '17 at 16:41
  • 1
    you are using e-TeX's \unless? that's a bit modernistic ;-) –  Dec 08 '17 at 16:41
  • 1
    @jfbu The \pdfliteral seems to be modernistic too. On the other hand, LaTeX kernel seems to be a bit ancient from my point of view. ;-) – wipet Dec 08 '17 at 19:31
  • 1
    It's also nice that (the first version of) this solution is half the length of the (first) expl3 one - but neither is particularly easy to understand :) – Marijn Dec 09 '17 at 10:57
  • 1
    Of course, both languages are a bit obscure. If neither of them is particularly easy to understand for you then I strongly recommend to start with study of TeX primitives -- native TeX language, no Epl3. Because without knowledge of principles of TeX you are unable to do nothing with TeX. And Expl3 cannot help you with such understanding. – wipet Dec 10 '17 at 05:36
  • Putting Expl3 aside for a while, why don't you use XeTex or ConTeXt? – Ooker Dec 11 '17 at 09:45