如何让气泡中的每一行都像聊天对话一样?

如何让气泡中的每一行都像聊天对话一样?

我曾经有过一次简单的聊天对话,需要以类似于 Messenger 中的设计方式显示它们。由于这是一次长时间的对话,我需要一种方法来一次性完成。我知道这可能无法全部实现,所以我只需要尽可能地模仿它。

  • 左对齐灰色气泡(103、184、104),带有黑色文字;
    右对齐绿色气泡(241、240、240),带有白色文字
  • 气泡仅在一段话之后才会拆分(相当于聊天时按回车键)。多行的长消息将保留在一个气泡中。
  • 左右边缘均为半圆
  • 相同颜色气泡之间的边距较小,不同颜色气泡之间的边距较大。


供参考:哪些文字处理工具可以处理复杂的文本框?平面设计专业
如何在 Word 中使用自定义文本框包装每个段落?在超级用户中

答案1

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}

在此处输入图片描述

\bubble如果我把的定义改为

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

输出如下:

在此处输入图片描述

改良版

您可以通过设置 来定义气泡之间的间距\bubblesep。不同颜色的气泡被封闭在flushleftflushright环境中,因此它们之间会有\topsep间隙。

\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}

在此处输入图片描述

答案2

在纯 TeX(使用 pdfTeX)中我们可以这样做:

\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

编辑:我修改了代码,以便能够打印两种变体:左对齐(如果\messenger l使用)和右对齐(如果\messenger r使用)。正如您的评论所述。

结果:

信使2

编辑在我的第一个结果发布在这里之后,OP 的规范发生了变化。首先,颜色为蓝色,气泡右对齐。其次,气泡也必须左对齐。规范的最后一个变化包括对颜色和段落格式的新要求。我认为当我的代码发布后,对段落格式或颜色进行细微更改对 OP 来说只是简单的任务。但可能无法指望,因为 OP 不知道这是什么意思\bye。此外,egreg 在这里基于特定的 Expl3 语言提出了第二种解决方案。他有优势,因为他知道 OP 的最新规范。我不能只保留 Expl3 解决方案,因为在我看来,它是晦涩难懂的语言,无法帮助教授基本的 TeX 原理。所以,我有了第二个解决方案,使用 TeX 原语并接受 OP 的新规范。我希望 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

信使3

相关内容