texlive[49691] Master/texmf-dist: ducksay (13jan19)

commits+karl at tug.org commits+karl at tug.org
Mon Jan 14 00:31:17 CET 2019


Revision: 49691
          http://tug.org/svn/texlive?view=revision&revision=49691
Author:   karl
Date:     2019-01-14 00:31:16 +0100 (Mon, 14 Jan 2019)
Log Message:
-----------
ducksay (13jan19)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/latex/ducksay/ducksay.pdf
    trunk/Master/texmf-dist/source/latex/ducksay/ducksay.dtx
    trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.animals.tex
    trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.code.v1.tex
    trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.code.v2.tex
    trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.sty

Modified: trunk/Master/texmf-dist/doc/latex/ducksay/ducksay.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/source/latex/ducksay/ducksay.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/ducksay/ducksay.dtx	2019-01-13 23:30:37 UTC (rev 49690)
+++ trunk/Master/texmf-dist/source/latex/ducksay/ducksay.dtx	2019-01-13 23:31:16 UTC (rev 49691)
@@ -63,7 +63,12 @@
 \ProvidesFile{ducksay.dtx}
   [\csname ducksay at date\endcsname\ cowsay for LaTeX]
 \documentclass{l3doc}
+% silence the false positives about internal control sequences in the animals 
+\csname msg_redirect_name:nnn\endcsname{l3doc}{foreign-internal}{none}
 \usepackage[version=2]{ducksay}
+\usepackage{marginnote}
+\newcommand\sinceversion[1]
+  {\marginnote{\footnotesize\textsf{v#1}\kern-\marginparsep}}
 \let\metaORIG\meta
 \protected\def\meta #1{\texttt{\metaORIG{#1}}}
 \DucksayOptions{arg=tab,msg-align=l,vpad=1}
@@ -102,6 +107,16 @@
   }
   {\end{description}}
 \makeatletter
+\renewcommand*\paragraph
+  {%
+    \@startsection
+      {paragraph}
+      {4}
+      {\z@}
+      {-3.25ex \@plus -1ex \@minus -.2ex}
+      {1ex \@plus .2ex}
+      {\normalfont\normalsize\bfseries}%
+  }
 \newcommand*\availableAnimal[1]
   {%
     \@for\cs:=#1\do
@@ -152,7 +167,7 @@
         \hfil
         \makebox[.\marginparwidth][l]
           {%
-            \ducksay[head-in,MSG=\footnotesize,msg-align=c,align=t]
+            \ducksay[head-in,MSG=\footnotesize,msg-align=c]
               {It's always\\good to\\keep the\\overview!}%
           }%
       }%
@@ -173,7 +188,7 @@
     \smash
       {%
         \footnotesize
-        \ducksay[small-yoda,wd=39,ht=3,msg-align=c,rel-align=r]
+        \ducksay[small-yoda,wd=39,ht=3,msg-align=c,body-align=r]
           {Hosted at\\\url{https://github.com/Skillmon/ltx_ducksay}\\it is.}%
       }
     \egroup
@@ -187,8 +202,8 @@
 \NeedsTeXFormat{LaTeX2e}
 \RequirePackage{xparse,l3keys2e}
 
-\def\ducksay at version{2.2.1}
-\def\ducksay at date{2019-01-08}
+\def\ducksay at version{2.3}
+\def\ducksay at date{2019-01-13}
 
 \ProvidesExplPackage
   {ducksay}           {\ducksay at date}
@@ -231,15 +246,17 @@
 %
 % \section{Documentation}\def\DocImp{Documentation}%
 %
-% \subsection{Downward Compatibility Issues}
+% \subsection{Downward Compatibility Issues}\label{sec:down}^^A>>>
 % \marginpar^^A>>>
 %   {
 %     \tiny
-%     \ducksay[snail,MSG=\footnotesize,align=t]{Yep, I screwed up!}
+%     \ducksay[snail,MSG=\footnotesize]{Yep, I screwed up!}
 %   }^^A<<<
 %
 % \begin{itemize}
-%   \item Versions prior to v2.0 did use a regular expression for the option
+%   \item
+%     \sinceversion{2.0}
+%     Versions prior to v2.0 did use a regular expression for the option
 %     |ligatures|, see \autoref{sec:options} for more on this issue. With v2.0
 %     I do refer to the package's version, not the code variant which can be
 %     selected with the |version| option.
@@ -246,16 +263,26 @@
 %   \item In a document created with package versions prior to v2.0 you'll have
 %     to specify the option |version=1| in newer versions to make those old
 %     documents behave like they used to. 
-% \end{itemize}
+%   \item
+%     \sinceversion{2.3}
+%     Since v2.3 \cs{AddAnimal} and \cs{AddColoredAnimal} behave
+%     differently. You no longer have to make sure that in the first three lines
+%     every backslash which is only preceded by spaces is the bubble's tail.
+%     Instead you can specify which symbol should be the tail and how many of
+%     such symbols there are. See \autoref{sec:macros} for more about the
+%     current behaviour.
+%   \item The |add-think| key is deprecated and will throw an error starting
+%     with v2.3. In future versions it will be removed.
+% \end{itemize}^^A<<<
 %
-% \subsection{Shared between versions}
+% \subsection{Shared between versions}^^A>>>
 %
-% \subsubsection{Macros}^^A>>>
+% \subsubsection{Macros}\label{sec:macros}^^A>>>
 % \marginpar^^A>>>
 %   {
 %     \tiny
 %     \hfill
-%     \ducksay[bunny,MSG=\footnotesize,align=t,body-mirrored]
+%     \ducksay[bunny,MSG=\footnotesize,body-mirrored]
 %       {Macros for everyone!}
 %   }^^A<<<
 %
@@ -284,34 +311,33 @@
 %
 % \begin{function}{\AddAnimal}^^A>>>
 %   \begin{syntax}
-%     \cs{AddAnimal}\meta{*}\marg{animal}\meta{ascii-art}
+%     \cs{AddAnimal}\meta{*}\oarg{options}\marg{animal}\meta{ascii-art}
 %   \end{syntax}
 %   adds \anml\ to the known animals. \meta{ascii-art} is multi-line verbatim
 %   and therefore should be delimited either by matching braces or by anything
 %   that works for \cs{verb}. If the star is given \anml\ is the new default.
 %   One space is added to the begin of \anml\ (compensating the opening symbol).
-%   For example, |snowman| is added with:\\[1ex]
+%   The symbols signalizing the speech bubble's tail (in the |hedgehog| example
+%   below the two |s|) can be set using the |tail-symbol| option and only the
+%   first |tail-count| occurrences will be substituted (see
+%   \autoref{sec:addoptions} for more about these options). For example,
+%   |hedgehog| is added with:\\[1ex]
 %   \begin{minipage}{\linewidth}
 %\begin{verbatim}
-% \AddAnimal{snowman}
-% {  \
-%     \ _[_]_
-%        (")
-%     >-( : )-<
-%      (__:__)}
+% \AddAnimal[tail-symbol=s]{hedgehog}
+% {  s    .\|//||\||.
+%     s  |/\/||/|//|/|
+%       /. `|/\\|/||/||
+%      o__,_|//|/||\||'}
 %\end{verbatim}
-%   \end{minipage}
+%   \end{minipage}\\[1ex]
 %   It is not checked whether the animal already exists, you could therefore
-%   redefine existing animals with this macro.\\
-%   The symbols signalizing the speech (in the |snowman| example above the two
-%   backslashes) should at most be used in the first three lines, as they get
-%   replaced by |O| and |o| for \cs{duckthink}. They also shouldn't be preceded
-%   by anything other than a space in that line.
+%   redefine existing animals with this macro.
 % \end{function}^^A<<<
 %
 % \begin{function}{\AddColoredAnimal}^^A>>>
 %   \begin{syntax}
-%     \cs{AddColoredAnimal}\meta{*}\marg{animal}\meta{ascii-art}
+%     \cs{AddColoredAnimal}\meta{*}\oarg{options}\marg{animal}\meta{ascii-art}
 %   \end{syntax}
 %   It does the same as \cs{AddAnimal} but allows three different colouring
 %   syntaxes. You can use \cs{textcolor} in the \meta{ascii-art} with the syntax
@@ -324,7 +350,7 @@
 %   |\bgroup\color{red}RedText\egroupOtherText| is valid syntax). You can't nest
 %   delimited \cs{color}s.\\
 %   Also you can use an undelimited \cs{color}. It affects anything until the
-%   end of the current line (or, if used inside of the \meta{text} of an
+%   end of the current line (or, if used inside of the \meta{text} of a
 %   delimited \cs{color}, anything until the end of that delimited \cs{color}'s
 %   \meta{text}). The syntax would be \cs{color}\marg{color}.\\
 %   The package doesn't load anything providing those colouring commands for you
@@ -333,6 +359,19 @@
 %   normal \cs{AddAnimal}.
 % \end{function}^^A<<<
 %
+% \begin{function}{\AnimalOptions}^^A>>>
+%   \begin{syntax}
+%     \cs{AnimalOptions}\meta{*}\marg{animal}\marg{options}
+%   \end{syntax}
+%   With this macro you can set \anml\ specific \meta{options}. If the star is
+%   given any currently set options for this \anml\ are dropped and only the
+%   ones specified in \meta{options} will be applied, else \meta{options} will
+%   be added to the set options for this \anml. The set \meta{options} can
+%   set the |tail-1| and |tail-2| options and therefore overwrite the effects of
+%   \cs{duckthink}, as \cs{duckthink} really is just \cs{ducksay} with the
+%   |think| option.
+% \end{function}^^A<<<
+%
 %^^A<<<
 %
 % \subsubsection{Options}\label{sec:options}^^A>>>
@@ -339,7 +378,7 @@
 % \marginpar
 %   {%
 %     \vspace*{-2em}\tiny
-%     \ducksay[pig,MSG=\footnotesize,align=t]{Options.\\For every occasion}%
+%     \ducksay[pig,MSG=\footnotesize]{Options.\\For every occasion}%
 %   }
 % The following options are available independent on the used code variant (the
 % value of the |version| key). They might be used as package options -- unless
@@ -369,33 +408,58 @@
 %     |}| so that they can't form ligatures. Giving no argument (or an empty
 %     one) might enhance compilation speed by disabling this replacement. The
 %     formation of ligatures was only observed in combination with
-%     \verb|\usepackage[T1]{fontenc}| by the author of this package. Therefore
-%     giving the option |ligatures| without an argument might enhance the
-%     compilation speed for you without any drawbacks. Initially this is set to
+%     |\usepackage[T1]{fontenc}| by the author of this package. Therefore giving
+%     the option |ligatures| without an argument might enhance the compilation
+%     speed for you without any drawbacks. Initially this is set to
 %     \texttt{\csuse{\detokenize{l_ducksay_ligatures_tl}}}.\\
 %     \textbf{Note:} In earlier releases this option's expected argument was a
 %     regular expression. This means that this option is not fully
 %     downward compatible with older versions. The speed gain however seems
 %     worth it (and I hope the affected documents are few).
-%   \item[add-think=\meta{bool}]
-%     by default the animals for \cs{duckthink} are not created during package
-%     load time, but only when they are really used -- but then they are created
-%     globally so it just has to be done once. This is done because they rely on
-%     a rather slow regular expression. If you set this key to |true| each
-%     \cs{AddAnimal} will also create the corresponding \cs{duckthink} variant
-%     immediately.
+%   \item[no-tail]
+%     Sets |tail-1| and |tail-2| to be a space.
+%   \item[say]
+%     Sets |tail-1| and |tail-2| as backslashes.
+%   \item[tail-1=\meta{token list}]
+%     Sets the first tail symbol in the output to be \meta{token list}. If set
+%     outside of \cs{ducksay} and \cs{duckthink} it will be overwritten inside
+%     of \cs{duckthink} to be |O|.
+%   \item[tail-2=\meta{token list}]
+%     Sets every other tail symbol except the first one in the output to be
+%     \meta{token list}. If set outside of \cs{ducksay} and \cs{duckthink} it
+%     will be overwritten inside of \cs{duckthink} to be |o|.
+%   \item[think]
+%     Sets |tail-1=O| and |tail-2=o|.
 % \end{options}
+%
+% \paragraph{Options for \cs{AddAnimal}}\label{sec:addoptions}
+% The options described here are only available in \cs{AddAnimal} and
+% \cs{AddColoredAnimal}.
+% \begin{options}
+%   \item[tail-count=\meta{int}]
+%     sets the number of tail symbols to be replaced in \cs{AddAnimal} and
+%     \cs{AddColoredAnimal}. Initial value is 2. If the value is negative every
+%     occurrence of |tail-symbol| will be replaced.
+%   \item[tail-symbol=\meta{str}]
+%     the symbol used in \cs{AddAnimal} and \cs{AddColoredAnimal} to mark the
+%     bubble's tail. The argument gets \cs{detokenize}d. Initially a single
+%     backslash.
+% \end{options}
 %^^A<<<
 %
+%^^A<<<
+%
 % \SetVersion{1}%
-% \subsection{Version 1}\label{sec:v1}
+% \subsection{Version 1}\label{sec:v1}^^A>>>
 %
-% \subsubsection{Introduction}
+% \subsubsection{Introduction}^^A>>>
 %
 % This version is included for legacy support (old documents should behave the
 % same without any change to them -- except the usage of |version=1| as an
-% option). For the bleeding edge version of \pkg{ducksay} skip this subsection
-% and read \autoref{sec:v2}.
+% option, for a more or less complete list of downward compatibility related
+% problems see \autoref{sec:down}). For the bleeding edge version of
+% \pkg{ducksay} skip this subsection and read \autoref{sec:v2}.
+%^^A<<<
 %
 % \subsubsection{Macros}\label{sec:macros1}^^A>>>
 % \marginpar
@@ -403,7 +467,7 @@
 %     \rlap
 %       {%
 %         \tiny
-%         \ducksay[yoda,MSG=\footnotesize,align=t]{Use those, you might}%
+%         \ducksay[yoda,MSG=\footnotesize]{Use those, you might}%
 %       }%
 %   }
 % The following is the description of macros which differ in behaviour from
@@ -426,11 +490,9 @@
 %   \end{syntax}
 %   options might include any of the options described in \autoref{sec:options}
 %   and \autoref{sec:options1} if not otherwise specified. Prints an \anml\
-%   thinking \msg. \msg\ is not read in verbatim. It is implemented using
-%   regular expressions replacing a |\| which is only preceded by |\s*| in the
-%   first three lines with |O| and |o|. It is therefore slower than
-%   \cs{ducksay}. Multi-line \msg s are possible using |\\|. |\\| should not be
-%   contained in a macro definition but at toplevel. Else use the option |ht|.
+%   thinking \msg. \msg\ is not read in verbatim. Multi-line \msg s are possible
+%   using |\\|. |\\| should not be contained in a macro definition but at
+%   toplevel. Else use the option |ht|.
 % \end{function}^^A<<<
 %^^A<<<
 %
@@ -439,7 +501,7 @@
 %   {%
 %     \vspace*{-2em}\tiny
 %     \hfill
-%     \ducksay[hedgehog,MSG=\footnotesize,align=t]{Everyone likes\\options}%
+%     \ducksay[hedgehog,MSG=\footnotesize]{Everyone likes\\options}%
 %   }
 % The following options are available to \cs{ducksay}, \cs{duckthink}, and
 % \cs{DucksayOptions} and if not otherwise specified also as package options:
@@ -452,7 +514,7 @@
 %     use \meta{code} in a group right before the body (meaning the \anml).
 %     Might be used as a package option but not all control sequences work out
 %     of the box there. E.g.\@ to right-align the \anml\ to the bubble, use
-%     \verb|body=\hfill|.
+%     |body=\hfill|.
 %   \item[align=\meta{valign}]
 %     use \meta{valign} as the vertical alignment specifier given to the
 %     \env{tabular} which is around the contents of \cs{ducksay} and
@@ -480,31 +542,35 @@
 % \begingroup
 %   \reversemarginpar
 %   \marginpar
-%     {\tiny\hfill\ducksay[frog,MSG=\footnotesize,align=t]{Ohh, no!}}
+%     {\tiny\hfill\ducksay[frog,MSG=\footnotesize,t]{Ohh, no!}}
 % \endgroup
 % \begin{itemize}
 %   \item no automatic line wrapping
 % \end{itemize}^^A<<<
 %
+%^^A<<<
+%
 % \SetVersion{2}%
-% \subsection{Version 2}\label{sec:v2}
+% \subsection{Version 2}\label{sec:v2}^^A>>>
 % \marginpar^^A>>>
 %   {
 %     \fontsize{3.5pt}{3.5pt}\selectfont
-%     \ducksay[unicorn,MSG=\footnotesize,align=t]{Here's all the good stuff!}
+%     \makebox[\marginparwidth]
+%       {\ducksay[unicorn,MSG=\footnotesize]{Here's all the good stuff!}}%
 %   }^^A<<<
 %
 % \subsubsection{Introduction}^^A>>>
 %
-% Version 2 is the current version of \pkg{ducksay}. It features automatic line
+% Version~2 is the current version of \pkg{ducksay}. It features automatic line
 % wrapping (if you specify a fixed width) and in general more options (with some
 % nasty argument parsing).
 %
-% If you're already used to version 1 you should note one important thing: You
-% should only specify the |version|, the |ligatures| and |add-think| during
-% package load time as arguments to \cs{usepackage}. The other keys might not
-% work or do unintended things and only don't throw errors or warnings because
-% of the legacy support of version 1.
+% If you're already used to version~1 you should note one important thing: You
+% should only specify the |version| and the |ligatures| during package load time
+% as arguments to \cs{usepackage}. The other keys might not work or do
+% unintended things and only don't throw errors or warnings because of the
+% legacy support of version~1. After the package is loaded, keys only used for
+% version~1 will throw an error.
 %
 %^^A<<<
 %
@@ -512,7 +578,7 @@
 % \marginpar^^A>>>
 %   {
 %     \tiny
-%     \ducksay[duck-family,MSG=\footnotesize,align=t,body-mirrored]
+%     \ducksay[duck-family,MSG=\footnotesize,t,body-mirrored]
 %       {Look at those, kids!}
 %   }^^A<<<
 %
@@ -543,11 +609,6 @@
 %   \end{syntax}
 %   The only difference to \cs{ducksay} is that in \cs{duckthink} the \anml s
 %   think the \msg\ and don't say it.\\
-%   It is implemented using regular expressions replacing a |\| which is only
-%   preceded by |\s*| (any number of space tokens) in the first three lines with
-%   |O| and |o|. It's first use per \anml\ might therefore be slower than
-%   \cs{ducksay} depending on the |add-think| key (see its description in
-%   \autoref{sec:options}).
 % \end{function}^^A<<<
 %
 %^^A<<<
@@ -556,7 +617,7 @@
 % \marginpar^^A>>>
 %   {
 %     \tiny
-%     \hfill\ducksay[small-rabbit,MSG=\footnotesize,align=t]
+%     \hfill\ducksay[small-rabbit,MSG=\footnotesize]
 %       {Fast, use options!}
 %   }^^A<<<
 % In version 2 the following options are available. Keep in mind that you
@@ -728,9 +789,12 @@
 %     \pkg{array} is used afterwards. The package default is |\raggedleft|. It
 %     might be useful if you want to use \pkg{ragged2e}'s \cs{RaggedLeft} for
 %     example.
-%   \item[msg-to-bubble=\meta{pole}]
+%   \item[msg-to-body=\meta{pole}]
 %     defines the horizontal coffin \meta{pole} to be used as the reference
 %     point for the placement of the \anml\ beneath the \msg. See \PolesInfo.
+%   \item[no-bubble=\meta{bool}]
+%     If |true| the \msg\ will not be surrounded by a bubble. Package default is
+%     of course |false|.
 %   \item[none=\meta{bool}]
 %     One could say this is a special animal. If |true| no animal body will be
 %     used (resulting in just the speech bubble). Package default is of course
@@ -778,6 +842,8 @@
 %
 %^^A<<<
 %
+%^^A<<<
+%
 % \SetVersion{}%
 % \subsection{Dependencies}^^A>>>
 % \marginpar
@@ -787,7 +853,7 @@
 %       {%
 %         \ducksay
 %           [
-%             kangaroo,MSG=\footnotesize,align=t
+%             kangaroo,MSG=\footnotesize
 %             ,body-mirrored,body-to-msg=r,msg-to-body=hc
 %           ]
 %           {We rely on you}%
@@ -805,10 +871,12 @@
 %     \hfill
 %     \makebox[8em][r]
 %       {%
-%         \ducksay[whale,MSG=\footnotesize,align=t]
-%           {I'm the\\new one.}%
+%         \ducksay[knight,MSG=\footnotesize,t]
+%           {I'm the\\new one.}\\
+%         \ducksay[crusader,MSG=\footnotesize,body=\normalsize,t]
+%           {Deus\\vult!\rlap{\footnotemark}}%
 %       }%
-%   }
+%   }\footnotetext{Latin; ``I'm new, too.''}
 % The following animals are provided by this package. I did not create them (but
 % altered some), they belong to their original creators.
 % \bgroup
@@ -837,7 +905,9 @@
 %   ,rabbit^^A
 %   ,snail^^A
 %   ,whale^^A
+%   ,crusader^^A
 %   ,unicorn^^A
+%   ,knight^^A
 % }\end{multicols}\begin{multicols}{2}
 % \availableAnimal{^^A
 %   ,r2d2^^A
@@ -856,7 +926,7 @@
 %     \rlap
 %       {%
 %         \tiny
-%         \ducksay[squirrel,MSG=\footnotesize,align=t]
+%         \ducksay[squirrel,MSG=\footnotesize,t]
 %           {WTFPL would be a\\better license.}%
 %       }%
 %   }
@@ -884,7 +954,7 @@
 %       {%
 %         \tiny
 %         \ducksay
-%           [vader,MSG=\footnotesize,align=t,arg=tab,msg-align=c]
+%           [vader,MSG=\footnotesize,arg=tab,msg-align=c,body-align=c]
 %           {%
 %             Only rebel scum reads\\documentation!\\
 %             Join the dark side,\\read the implementation.%
@@ -904,6 +974,7 @@
 %    \begin{macrocode}
 \int_new:N \l_ducksay_msg_width_int
 \int_new:N \l_ducksay_msg_height_int
+\int_new:N \l_ducksay_tail_symbol_count_int
 %    \end{macrocode}
 % \paragraph{Sequences}
 %    \begin{macrocode}
@@ -911,7 +982,6 @@
 %    \end{macrocode}
 % \paragraph{Token lists}
 %    \begin{macrocode}
-\tl_new:N \l_ducksay_say_or_think_tl
 \tl_new:N \l_ducksay_align_tl
 \tl_new:N \l_ducksay_msg_align_tl
 \tl_new:N \l_ducksay_animal_tl
@@ -918,10 +988,12 @@
 \tl_new:N \l_ducksay_body_tl
 \tl_new:N \l_ducksay_bubble_tl
 \tl_new:N \l_ducksay_tmpa_tl
+\tl_new:N \l_ducksay_tail_symbol_out_one_tl
+\tl_new:N \l_ducksay_tail_symbol_out_two_tl
+\tl_new:N \l_ducksay_tail_symbol_in_tl
 %    \end{macrocode}
 % \paragraph{Boolean}
 %    \begin{macrocode}
-\bool_new:N \l_ducksay_also_add_think_bool
 \bool_new:N \l_ducksay_version_one_bool
 \bool_new:N \l_ducksay_version_two_bool
 %    \end{macrocode}
@@ -933,15 +1005,8 @@
 %^^A<<<
 %
 % \subsubsection{Regular Expressions}^^A>>>
-% Regular expressions for \cs{duckthink}
+% Regular expressions for \cs{AddColoredAnimal}
 %    \begin{macrocode}
-\regex_const:Nn \c_ducksay_first_regex  { \A(.\s*)\\ }
-\regex_const:Nn \c_ducksay_second_regex { \A(.[^\c{null}]*\c{null}\s*)\\ }
-\regex_const:Nn \c_ducksay_third_regex  {
-  \A(.[^\c{null}]*\c{null}[^\c{null}]*\c{null}\s*)\\ }
-%    \end{macrocode}
-% And for \cs{AddColoredAnimal}
-%    \begin{macrocode}
 \regex_const:Nn \c_ducksay_textcolor_regex
   { \cO(?:\\textcolor\{(.*?)\}\{(.*?)\}) }
 \regex_const:Nn \c_ducksay_color_delim_regex
@@ -955,6 +1020,8 @@
 %    \begin{macrocode}
 \msg_new:nnn { ducksay } { load-time-only }
   { The~`#1`~key~is~to~be~used~only~during~package~load~time. }
+\msg_new:nnn { ducksay } { deprecated-key }
+  { The~`\l_keys_key_tl`~key~is~deprecated.~Sorry~for~the~inconvenience. }
 %    \end{macrocode}
 %^^A<<<
 %
@@ -983,7 +1050,17 @@
     ,rel-align .value_required:n = true
     ,ligatures .tl_set:N   = \l_ducksay_ligatures_tl
     ,ligatures .initial:n  = { `<>,'- }
-    ,add-think .bool_set:N = \l_ducksay_also_add_think_bool
+    ,tail-1    .tl_set:N   = \l_ducksay_tail_symbol_out_one_tl
+    ,tail-1    .initial:x  = \c_backslash_str
+    ,tail-2    .tl_set:N   = \l_ducksay_tail_symbol_out_two_tl
+    ,tail-2    .initial:x  = \c_backslash_str
+    ,no-tail   .meta:n     = { tail-1 = { ~ }, tail-2 = { ~ } }
+    ,think     .meta:n     = { tail-1 = { O }, tail-2 = { o } }
+    ,say       .code:n     =
+      {
+        \exp_args:Nx \DucksayOptions
+          { tail-1 = { \c_backslash_str }, tail-2 = { \c_backslash_str } }
+      }
     ,version   .choice:
     ,version / 1 .code:n   = 
       {
@@ -996,6 +1073,7 @@
         \bool_set_true:N  \l_ducksay_version_two_bool
       }
     ,version   .initial:n  = 2
+    ,add-think .code:n     = \msg_error:nn { ducksay } { deprecated-key }
   }
 %    \end{macrocode}
 %
@@ -1011,34 +1089,36 @@
   }
 %    \end{macrocode}
 %
+% \paragraph{Keys for \cs{AddAnimal}}^^A>>>
+% Define keys meant for \cs{AddAnimal} and \cs{AddColoredAnimal} only in their
+% own regime:
+%    \begin{macrocode}
+\keys_define:nn { ducksay / add-animal }
+  {
+    ,tail-symbol .code:n    =
+      \tl_set:Nx \l_ducksay_tail_symbol_in_tl { \tl_to_str:n { #1 } }
+    ,tail-symbol .initial:o = \c_backslash_str
+    ,tail-count  .int_set:N = \l_ducksay_tail_symbol_count_int
+    ,tail-count  .initial:n = 2
+  }
+%    \end{macrocode}
+%
 %^^A<<<
 %
+%^^A<<<
+%
 % \subsubsection{Functions}^^A>>>
 %
 % \paragraph{Generating Variants of External Functions}^^A>>>
 %
 %    \begin{macrocode}
-\cs_generate_variant:Nn \tl_if_eq:nnT { VnT }
+\cs_generate_variant:Nn \tl_replace_once:Nnn { NVn }
+\cs_generate_variant:Nn \tl_replace_all:Nnn { NVn }
 %    \end{macrocode}
 %^^A<<<
 %
 % \paragraph{Internal}^^A>>>
 %
-% \begin{macro}{\ducksay_create_think_animal:n}^^A>>>
-%    \begin{macrocode}
-\cs_new_protected:Npn \ducksay_create_think_animal:n #1
-  {
-    \group_begin:
-      \tl_set_eq:Nc \l_ducksay_tmpa_tl { g_ducksay_animal_say_#1_tl }
-      \regex_replace_once:NnN \c_ducksay_first_regex  { \1O } \l_ducksay_tmpa_tl
-      \regex_replace_once:NnN \c_ducksay_second_regex { \1o } \l_ducksay_tmpa_tl
-      \regex_replace_once:NnN \c_ducksay_third_regex  { \1o } \l_ducksay_tmpa_tl
-      \tl_gset_eq:cN { g_ducksay_animal_think_#1_tl } \l_ducksay_tmpa_tl
-    \group_end:
-  }
-%    \end{macrocode}
-% \end{macro}^^A<<<
-%
 % \begin{macro}{\ducksay_replace_verb_newline:Nn}^^A>>>
 %    \begin{macrocode}
 \cs_new_protected:Npx \ducksay_replace_verb_newline:Nn #1 #2
@@ -1069,27 +1149,65 @@
 %    \end{macrocode}
 % \end{macro}^^A<<<
 %
-% \begin{macro}{\ducksay_add_animal_inner:nn}^^A>>>
+% \begin{macro}{\ducksay_add_animal_inner:nnnn}^^A>>>
 %    \begin{macrocode}
-\cs_new_protected:Npn \ducksay_add_animal_inner:nn #1 #2
+\cs_new_protected:Npn \ducksay_add_animal_inner:nnnn #1 #2 #3 #4
   {
-    \tl_set:Nn \l_ducksay_tmpa_tl { \ #2 }
-    \tl_map_inline:Nn \l_ducksay_ligatures_tl
-      { \tl_replace_all:Nnn \l_ducksay_tmpa_tl { ##1 } { { ##1 } } }
-    \ducksay_replace_verb_newline:Nn \l_ducksay_tmpa_tl { \tabularnewline\null }
-    \tl_gset_eq:cN { g_ducksay_animal_say_#1_tl } \l_ducksay_tmpa_tl
-    \keys_define:nn { ducksay }
+    \group_begin:
+      \keys_set:nn { ducksay / add-animal } { #1 }
+      \tl_set:Nn \l_ducksay_tmpa_tl { \ #3 }
+      \int_compare:nNnTF { \l_ducksay_tail_symbol_count_int } < { \c_zero_int }
+        {
+          \tl_replace_once:NVn
+            \l_ducksay_tmpa_tl
+            \l_ducksay_tail_symbol_in_tl
+            \l_ducksay_tail_symbol_out_one_tl
+          \tl_replace_all:NVn
+            \l_ducksay_tmpa_tl
+            \l_ducksay_tail_symbol_in_tl
+            \l_ducksay_tail_symbol_out_two_tl
+        }
+        {
+          \int_compare:nNnT { \l_ducksay_tail_symbol_count_int } >
+            { \c_zero_int }
+            {
+              \tl_replace_once:NVn
+                \l_ducksay_tmpa_tl
+                \l_ducksay_tail_symbol_in_tl
+                \l_ducksay_tail_symbol_out_one_tl
+              \int_step_inline:nnn { 2 } { \l_ducksay_tail_symbol_count_int }
+                {
+                  \tl_replace_once:NVn
+                    \l_ducksay_tmpa_tl
+                    \l_ducksay_tail_symbol_in_tl
+                    \l_ducksay_tail_symbol_out_two_tl
+                }
+            }
+        }
+      \tl_map_inline:Nn \l_ducksay_ligatures_tl
+        { \tl_replace_all:Nnn \l_ducksay_tmpa_tl { ##1 } { { ##1 } } }
+      \ducksay_replace_verb_newline:Nn \l_ducksay_tmpa_tl
+        { \tabularnewline\null }
+      \exp_args:NNnV
+    \group_end:
+    \tl_set:cn { l_ducksay_animal_#2_tl } \l_ducksay_tmpa_tl
+    \exp_args:Nnx \keys_define:nn { ducksay }
       {
-        #1 .code:n =
+        #2 .code:n =
           {
-            \tl_if_exist:cF
-              { g_ducksay_animal_ \l_ducksay_say_or_think_tl _#1_tl }
-              { \ducksay_create_think_animal:n { #1 } }
-            \tl_set_eq:Nc \l_ducksay_animal_tl
-              { g_ducksay_animal_ \l_ducksay_say_or_think_tl _#1_tl }
+            \exp_not:n { \tl_set_eq:NN \l_ducksay_animal_tl }
+            \exp_after:wN \exp_not:N \cs:w l_ducksay_animal_#2_tl \cs_end:
+            \exp_not:n { \exp_args:NV \DucksayOptions }
+            \exp_after:wN
+            \exp_not:N \cs:w l_ducksay_animal_#2_options_tl \cs_end:
           }
       }
+    \tl_if_exist:cF { l_ducksay_animal_#2_options_tl }
+      { \tl_new:c { l_ducksay_animal_#2_options_tl } }
+    \IfBooleanT { #4 }
+      { \keys_define:nn { ducksay } { default_animal .meta:n = { #2 } } }
   }
+\cs_generate_variant:Nn \ducksay_add_animal_inner:nnnn { nnVn }
 %    \end{macrocode}
 % \end{macro}^^A<<<
 %
@@ -1117,13 +1235,9 @@
 %
 % \begin{macro}{\AddAnimal}^^A>>>
 %    \begin{macrocode}
-\NewDocumentCommand \AddAnimal { s m +v }
+\NewDocumentCommand \AddAnimal { s O{} m +v }
   {
-    \ducksay_add_animal_inner:nn { #2 } { #3 }
-    \bool_if:NT \l_ducksay_also_add_think_bool
-      { \ducksay_create_think_animal:n { #2 } }
-    \IfBooleanT{#1}
-      { \keys_define:nn { ducksay } { default_animal .meta:n = { #2 } } }
+    \ducksay_add_animal_inner:nnnn { #2 } { #3 } { #4 } { #1 }
   }
 %    \end{macrocode}
 % \end{macro}^^A<<<
@@ -1130,26 +1244,39 @@
 %
 % \begin{macro}{\AddColoredAnimal}^^A>>>
 %    \begin{macrocode}
-\NewDocumentCommand \AddColoredAnimal { s m +v }
+\NewDocumentCommand \AddColoredAnimal { s O{} m +v }
   {
-    \ducksay_add_animal_inner:nn { #2 } { #3 }
-    \regex_replace_all:Nnc \c_ducksay_color_delim_regex
+    \tl_set:Nn \l_ducksay_tmpa_tl { #4 }
+    \regex_replace_all:NnN \c_ducksay_color_delim_regex
       { \c{bgroup}\c{color}\cB\{\1\cE\}\2\c{egroup} }
-      { g_ducksay_animal_say_#2_tl }
-    \regex_replace_all:Nnc \c_ducksay_color_regex
+      \l_ducksay_tmpa_tl
+    \regex_replace_all:NnN \c_ducksay_color_regex
       { \c{color}\cB\{\1\cE\} }
-      { g_ducksay_animal_say_#2_tl }
-    \regex_replace_all:Nnc \c_ducksay_textcolor_regex
+      \l_ducksay_tmpa_tl
+    \regex_replace_all:NnN \c_ducksay_textcolor_regex
       { \c{textcolor}\cB\{\1\cE\}\cB\{\2\cE\} }
-      { g_ducksay_animal_say_#2_tl }
-    \bool_if:NT \l_ducksay_also_add_think_bool
-      { \ducksay_create_think_animal:n { #2 } }
-    \IfBooleanT{#1}
-      { \keys_define:nn { ducksay } { default_animal .meta:n = { #2 } } }
+      \l_ducksay_tmpa_tl
+    \ducksay_add_animal_inner:nnVn { #2 } { #3 } \l_ducksay_tmpa_tl { #1 }
   }
 %    \end{macrocode}
 % \end{macro}^^A<<<
 %
+% \begin{macro}{\AnimalOptions}^^A>>>
+%    \begin{macrocode}
+\NewDocumentCommand \AnimalOptions { s m m }
+  {
+    \tl_if_exist:cTF { l_ducksay_animal_#2_options_tl }
+      {
+        \IfBooleanTF { #1 }
+          { \tl_set:cn }
+          { \tl_put_right:cn }
+      }
+      { \tl_set:cn }
+    { l_ducksay_animal_#2_options_tl } { #3, }
+  }
+%    \end{macrocode}
+% \end{macro}^^A<<<
+%
 %^^A<<<
 %
 %^^A<<<
@@ -1308,16 +1435,19 @@
 %    \end{macrocode}
 % \end{macro}^^A<<<
 %
-% \begin{macro}{\ducksay_prepare_say_and_think:n}^^A>>>
+% \begin{macro}{\ducksay_say_and_think:nn}^^A>>>
 %   Reset some variables
 %    \begin{macrocode}
-\cs_new:Npn \ducksay_prepare_say_and_think:n #1
+\cs_new:Npn \ducksay_say_and_think:nn #1 #2
   {
-    \int_set:Nn \l_ducksay_msg_width_int  { -\c_max_int }
-    \int_set:Nn \l_ducksay_msg_height_int { -\c_max_int }
-    \keys_set:nn { ducksay } { #1 }
-    \tl_if_empty:NT \l_ducksay_animal_tl
-      { \keys_set:nn { ducksay } { default_animal } }
+    \group_begin:
+      \int_set:Nn \l_ducksay_msg_width_int  { -\c_max_int }
+      \int_set:Nn \l_ducksay_msg_height_int { -\c_max_int }
+      \keys_set:nn { ducksay } { #1 }
+      \tl_if_empty:NT \l_ducksay_animal_tl
+        { \keys_set:nn { ducksay } { default_animal } }
+      \ducksay_print:nV { #2 } \l_ducksay_rel_align_tl
+    \group_end:
   }
 %    \end{macrocode}
 % \end{macro}^^A<<<
@@ -1329,11 +1459,7 @@
 %    \begin{macrocode}
 \NewDocumentCommand \ducksay { O{} m }
   {
-    \group_begin:
-      \tl_set:Nn \l_ducksay_say_or_think_tl { say }
-      \ducksay_prepare_say_and_think:n { #1 }
-      \ducksay_print:nV { #2 } \l_ducksay_rel_align_tl
-    \group_end:
+    \ducksay_say_and_think:nn { #1 } { #2 }
   }
 %    \end{macrocode}
 % \end{macro}^^A<<<
@@ -1342,11 +1468,7 @@
 %    \begin{macrocode}
 \NewDocumentCommand \duckthink { O{} m }
   {
-    \group_begin:
-      \tl_set:Nn \l_ducksay_say_or_think_tl { think }
-      \ducksay_prepare_say_and_think:n { #1 }
-      \ducksay_print:nV { #2 } \l_ducksay_rel_align_tl
-    \group_end:
+    \ducksay_say_and_think:nn { think, #1 } { #2 }
   }
 %    \end{macrocode}
 % \end{macro}^^A<<<
@@ -1381,6 +1503,8 @@
     The~specified~message~alignment~`\exp_not:n { #1 }`~is~unknown.~
     `l`~is~used~as~fallback.
   }
+\msg_new:nnn { ducksay } { v1-key-only }
+  { The~`\l_keys_key_tl`~key~is~only~available~for~`version=1`. }
 %    \end{macrocode}
 %^^A<<<
 %
@@ -1444,6 +1568,7 @@
     ,wd* .initial:n = -\c_max_dim
     ,wd* .value_required:n = true
     ,none          .bool_set:N = \l_ducksay_no_body_bool
+    ,no-bubble     .bool_set:N = \l_ducksay_no_bubble_bool
     ,body-mirrored .bool_set:N = \l_ducksay_mirrored_body_bool
     ,ignore-body   .bool_set:N = \l_ducksay_ignored_body_bool
     ,body-x      .dim_set:N = \l_ducksay_body_x_offset_dim
@@ -1505,7 +1630,7 @@
     ,bubble-bot-kern  .initial:n = { .2ex }
     ,bubble-bot-kern  .value_required:n = true
     ,bubble-side-kern .tl_set:N  = \l_ducksay_bubble_side_kern_tl
-    ,bubble-side-kern .initial:n = { 0.2em }
+    ,bubble-side-kern .initial:n = { .2em }
     ,bubble-side-kern .value_required:n = true
     ,bubble-delim-top     .tl_set:N  = \l_ducksay_bubble_delim_top_tl
     ,bubble-delim-left-1  .tl_set:N  = \l_ducksay_bubble_delim_left_a_tl
@@ -1529,6 +1654,18 @@
   }
 %    \end{macrocode}
 %
+% Redefine keys only intended for version~1 to throw an error:
+%
+%    \begin{macrocode}
+\clist_map_inline:nn
+  { align, rel-align }
+  {
+    \keys_define:nn { ducksay }
+      { #1 .code:n = \msg_error:nn { ducksay } { v1-key-only } }
+  }
+%    \end{macrocode}
+%
+%
 %^^A<<<
 %
 % \subsubsection{Functions}^^A>>>
@@ -1618,6 +1755,7 @@
 %    \begin{macrocode}
 \cs_new:Npn \ducksay_digest_options:n #1
   {
+    \group_begin:
     \keys_set:nn { ducksay } { #1 }
     \tl_if_empty:NT \l_ducksay_animal_tl
       { \keys_set:nn { ducksay } { default_animal } }
@@ -1672,6 +1810,7 @@
           }
         \cs_set_eq:NN \ducksay_eat_argument:w \ducksay_eat_argument_tabular:w
       }
+    \ducksay_eat_argument:w
   }
 %    \end{macrocode}
 % \end{macro}^^A<<<
@@ -1708,88 +1847,119 @@
 %    \begin{macrocode}
 \cs_new_protected:Npn \ducksay_shipout:
   {
-    \hbox_set:Nn \l_ducksay_tmpa_box
-      { \l_ducksay_bubble_fount_tl \l_ducksay_bubble_delim_top_tl }
-    \int_set:Nn \l_ducksay_msg_width_int
+    \hcoffin_set:Nn \l_ducksay_msg_coffin { \box_use:N \l_ducksay_msg_box }
+    \bool_if:NF \l_ducksay_no_bubble_bool
       {
-        \fp_eval:n
+        \hbox_set:Nn \l_ducksay_tmpa_box
+          { \l_ducksay_bubble_fount_tl \l_ducksay_bubble_delim_top_tl }
+        \int_set:Nn \l_ducksay_msg_width_int
           {
-            ceil 
-              ( \box_wd:N \l_ducksay_msg_box / \box_wd:N \l_ducksay_tmpa_box )
-          }
-      }
-    \group_begin:
-    \l_ducksay_bubble_fount_tl
-    \exp_args:NNNx
-    \group_end:
-    \int_set:Nn \l_ducksay_msg_height_int
-      {
-        \int_max:nn
-          {
             \fp_eval:n
               {
-                ceil
+                ceil 
                   (
-                    (
-                      \box_ht:N \l_ducksay_msg_box
-                      + \box_dp:N \l_ducksay_msg_box
-                    )
-                    / ( \arraystretch * \baselineskip )
+                    \box_wd:N \l_ducksay_msg_box / \box_wd:N \l_ducksay_tmpa_box
                   )
               }
-            + \l_ducksay_vpad_int
           }
-          { \l_ducksay_msg_height_int }
-      }
-    \hcoffin_set:Nn \l_ducksay_bubble_open_coffin
-      {
+        \group_begin:
         \l_ducksay_bubble_fount_tl
-        \begin{tabular}{@{}l@{}}
-          \int_compare:nNnTF { \l_ducksay_msg_height_int } = { \c_one_int }
-            {
-              \l_ducksay_bubble_delim_left_a_tl
-            }
-            {
-              \l_ducksay_bubble_delim_left_b_tl\\
-              \int_step_inline:nnn
-                { 3 } { \l_ducksay_msg_height_int }
+        \exp_args:NNNx
+        \group_end:
+        \int_set:Nn \l_ducksay_msg_height_int
+          {
+            \int_max:nn
+              {
+                \fp_eval:n
+                  {
+                    ceil
+                      (
+                        (
+                          \box_ht:N \l_ducksay_msg_box
+                          + \box_dp:N \l_ducksay_msg_box
+                        )
+                        / ( \arraystretch * \baselineskip )
+                      )
+                  }
+                + \l_ducksay_vpad_int
+              }
+              { \l_ducksay_msg_height_int }
+          }
+        \hcoffin_set:Nn \l_ducksay_bubble_open_coffin
+          {
+            \l_ducksay_bubble_fount_tl
+            \begin{tabular}{@{}l@{}}
+              \int_compare:nNnTF { \l_ducksay_msg_height_int } = { \c_one_int }
                 {
-                  \kern-\l_ducksay_bubble_side_kern_tl
-                  \l_ducksay_bubble_delim_left_c_tl
-                  \\
+                  \l_ducksay_bubble_delim_left_a_tl
                 }
-              \l_ducksay_bubble_delim_left_d_tl
-            }
-        \end{tabular}
-      }
-    \hcoffin_set:Nn \l_ducksay_bubble_close_coffin
-      {
-        \l_ducksay_bubble_fount_tl
-        \begin{tabular}{@{}r@{}}
-          \int_compare:nNnTF { \l_ducksay_msg_height_int } = { \c_one_int }
-            {
-              \l_ducksay_bubble_delim_right_a_tl
-            }
-            {
-              \l_ducksay_bubble_delim_right_b_tl \\
-              \int_step_inline:nnn
-                { 3 } { \l_ducksay_msg_height_int }
                 {
-                  \l_ducksay_bubble_delim_right_c_tl
-                  \kern-\l_ducksay_bubble_side_kern_tl
-                  \\
+                  \l_ducksay_bubble_delim_left_b_tl\\
+                  \int_step_inline:nnn
+                    { 3 } { \l_ducksay_msg_height_int }
+                    {
+                      \kern-\l_ducksay_bubble_side_kern_tl
+                      \l_ducksay_bubble_delim_left_c_tl
+                      \\
+                    }
+                  \l_ducksay_bubble_delim_left_d_tl
                 }
-              \l_ducksay_bubble_delim_right_d_tl
-            }
-        \end{tabular}
+            \end{tabular}
+          }
+        \hcoffin_set:Nn \l_ducksay_bubble_close_coffin
+          {
+            \l_ducksay_bubble_fount_tl
+            \begin{tabular}{@{}r@{}}
+              \int_compare:nNnTF { \l_ducksay_msg_height_int } = { \c_one_int }
+                {
+                  \l_ducksay_bubble_delim_right_a_tl
+                }
+                {
+                  \l_ducksay_bubble_delim_right_b_tl \\
+                  \int_step_inline:nnn
+                    { 3 } { \l_ducksay_msg_height_int }
+                    {
+                      \l_ducksay_bubble_delim_right_c_tl
+                      \kern-\l_ducksay_bubble_side_kern_tl
+                      \\
+                    }
+                  \l_ducksay_bubble_delim_right_d_tl
+                }
+            \end{tabular}
+          }
+        \hcoffin_set:Nn \l_ducksay_bubble_top_coffin
+          {
+            \l_ducksay_bubble_fount_tl
+            \int_step_inline:nn
+              { \l_ducksay_msg_width_int + \l_ducksay_hpad_int }
+              { \l_ducksay_bubble_delim_top_tl }
+          }
+        \dim_set:Nn \l_ducksay_hpad_dim
+          {
+            (
+              \coffin_wd:N \l_ducksay_bubble_top_coffin
+              - \coffin_wd:N \l_ducksay_msg_coffin
+            ) / 2
+          }
+        \coffin_join:NnnNnnnn
+          \l_ducksay_msg_coffin         { l } { vc }
+          \l_ducksay_bubble_open_coffin { r } { vc }
+          { - \l_ducksay_hpad_dim } { \c_zero_dim }
+        \coffin_join:NnnNnnnn
+          \l_ducksay_msg_coffin          { r } { vc }
+          \l_ducksay_bubble_close_coffin { l } { vc }
+          { \l_ducksay_hpad_dim } { \c_zero_dim }
+        \ducksay_set_bubble_top_kern:
+        \ducksay_set_bubble_bottom_kern:
+        \coffin_join:NnnNnnnn
+          \l_ducksay_msg_coffin        { hc } { t }
+          \l_ducksay_bubble_top_coffin { hc } { b }
+          { \c_zero_dim } { \l_ducksay_bubble_top_kern_dim }
+        \coffin_join:NnnNnnnn
+          \l_ducksay_msg_coffin        { hc } { b }
+          \l_ducksay_bubble_top_coffin { hc } { t }
+          { \c_zero_dim } { \l_ducksay_bubble_bottom_kern_dim }
       }
-    \hcoffin_set:Nn \l_ducksay_bubble_top_coffin
-      {
-        \l_ducksay_bubble_fount_tl
-        \int_step_inline:nn { \l_ducksay_msg_width_int + \l_ducksay_hpad_int }
-          { \l_ducksay_bubble_delim_top_tl }
-      }
-    \hcoffin_set:Nn \l_ducksay_msg_coffin { \box_use:N \l_ducksay_msg_box }
     \bool_if:NF \l_ducksay_no_body_bool
       {
         \hcoffin_set:Nn \l_ducksay_body_coffin
@@ -1810,34 +1980,6 @@
                 { r } { \tl_set:Nn \l_ducksay_body_to_msg_align_body_tl { l } }
               }
           }
-      }
-    \dim_set:Nn \l_ducksay_hpad_dim
-      {
-        (
-          \coffin_wd:N \l_ducksay_bubble_top_coffin
-          - \coffin_wd:N \l_ducksay_msg_coffin
-        ) / 2
-      }
-    \coffin_join:NnnNnnnn
-      \l_ducksay_msg_coffin         { l } { vc }
-      \l_ducksay_bubble_open_coffin { r } { vc }
-      { - \l_ducksay_hpad_dim } { \c_zero_dim }
-    \coffin_join:NnnNnnnn
-      \l_ducksay_msg_coffin          { r } { vc }
-      \l_ducksay_bubble_close_coffin { l } { vc }
-      { \l_ducksay_hpad_dim } { \c_zero_dim }
-    \ducksay_set_bubble_top_kern:
-    \ducksay_set_bubble_bottom_kern:
-    \coffin_join:NnnNnnnn
-      \l_ducksay_msg_coffin        { hc } { t }
-      \l_ducksay_bubble_top_coffin { hc } { b }
-      { \c_zero_dim } { \l_ducksay_bubble_top_kern_dim }
-    \coffin_join:NnnNnnnn
-      \l_ducksay_msg_coffin        { hc } { b }
-      \l_ducksay_bubble_top_coffin { hc } { t }
-      { \c_zero_dim } { \l_ducksay_bubble_bottom_kern_dim }
-    \bool_if:NF \l_ducksay_no_body_bool
-      {
         \bool_if:NTF \l_ducksay_ignored_body_bool
           { \coffin_attach:NVnNVnnn }
           { \coffin_join:NVnNVnnn   }
@@ -1956,9 +2098,7 @@
 \cs_generate_variant:Nn \coffin_join:NnnNnnnn { NVnNVnnn }
 \cs_generate_variant:Nn \coffin_attach:NnnNnnnn { NVnNVnnn }
 \cs_generate_variant:Nn \coffin_typeset:Nnnnn { NVVnn }
-\cs_generate_variant:Nn \tl_if_eq:nnT { VnT }
 \cs_generate_variant:Nn \str_case:nn { Vn }
-\cs_generate_variant:Nn \regex_replace_all:NnN { Nnc }
 %    \end{macrocode}
 %
 %^^A<<<
@@ -1971,10 +2111,7 @@
 %    \begin{macrocode}
 \NewDocumentCommand \ducksay { O{} }
   {
-    \group_begin:
-      \tl_set:Nn \l_ducksay_say_or_think_tl { say }
-      \ducksay_digest_options:n { #1 }
-      \ducksay_eat_argument:w
+    \ducksay_digest_options:n { #1 }
   }
 %    \end{macrocode}
 % \end{macro}^^A<<<
@@ -1983,10 +2120,7 @@
 %    \begin{macrocode}
 \NewDocumentCommand \duckthink { O{} }
   {
-    \group_begin:
-      \tl_set:Nn \l_ducksay_say_or_think_tl { think }
-      \ducksay_digest_options:n { #1 }
-      \ducksay_eat_argument:w
+    \ducksay_digest_options:n { think, #1 }
   }
 %    \end{macrocode}
 % \end{macro}^^A<<<
@@ -2077,9 +2211,9 @@
       (")
    >-( : )-<
     (__:__)}%<<<
-\AddAnimal{hedgehog}%>>>
-{  \    .\|//||\||.
-    \  |/\/||/|//|/|
+\AddAnimal[tail-symbol=s]{hedgehog}%>>>
+{  s    .\|//||\||.
+    s  |/\/||/|//|/|
       /. `|/\\|/||/||
      o__,_|//|/||\||'}%<<<
 \AddAnimal{kangaroo}%>>>
@@ -2091,10 +2225,10 @@
             //   \\
           ,/'     `\_,}%<<<
 %^^A http://chris.com/ascii/index.php?art=animals/rabbits
-\AddAnimal{rabbit}%>>>
-{ \     / \`\         __
-   \   |  \ `\      /`/ \
-    \  \_/`\  \-"-/` /\  \
+\AddAnimal[tail-symbol=s,tail-count=3]{rabbit}%>>>
+{ s     / \`\         __
+   s   |  \ `\      /`/ \
+    s  \_/`\  \-"-/` /\  \
             |       |  \  |
             (d     b)   \_/
             /       \
@@ -2117,10 +2251,10 @@
     \ _//
      (')---.
       _/-_( )o}%<<<
-\AddAnimal{dragon}%>>>
-{     \                    / \  //\
-       \    |\___/|      /   \//  \\
-        \   /0  0  \__  /    //  | \ \    
+\AddAnimal[tail-symbol=s,tail-count=3]{dragon}%>>>
+{     s                   / \  //\
+       s    |\___/|      /   \//  \\
+        s   /0  0  \__  /    //  | \ \    
            /     /  \/_/    //   |  \  \  
            @_^_@'/   \/_   //    |   \   \ 
            //_^_/     \/_ //     |    \    \
@@ -2179,15 +2313,15 @@
                                          _//       _//
                                        /_|       /_|}%<<<
 %^^A https://asciiart.website//index.php?art=animals/other%20(water)
-\AddAnimal{whale}%>>>
-{  \                |-.
-    \    .-""-._     \ \.--|
-     \  /       `-..__)  ,-'
+\AddAnimal[tail-count=3,tail-symbol=s]{whale}%>>>
+{  s                |-.
+    s    .-""-._     \ \.--|
+     s  /       `-..__)  ,-'
        |     .          /
         \--.__,   .__.,'
          `-.___'._\_.'}%<<<
 %^^A from http://www.ascii-art.de/ascii/s/starwars.txt :
-\AddAnimal{yoda}%>>>
+\AddAnimal[tail-count=3]{yoda}%>>>
 {   \
      \             ____
       \         _.' :  `._
@@ -2224,7 +2358,7 @@
          :-""-.`./-.'     /    `.___.'
                \ `t  ._  /
                 "-.t-._:'}%<<<
-\AddAnimal{yoda-head}%>>>
+\AddAnimal[tail-count=3]{yoda-head}%>>>
 {   \
      \             ____
       \         _.' :  `._
@@ -2279,6 +2413,36 @@
 /_        \O========O/        _\
   `--...__|`-._  _.-'|__...--'
           |    `'    |}%<<<
+\AddAnimal[tail-symbol=|,tail-count=1]{crusader}%>>>
+{ |
+\[T]/}
+\csname bool_if:cT\endcsname {l_ducksay_version_one_bool}
+  {\AnimalOptions{crusader}{tail-1=|,rel-align=c}}
+\csname bool_if:cT\endcsname {l_ducksay_version_two_bool}
+  {\AnimalOptions{crusader}{tail-1=|,body-align=c}}%<<<
+%^^A http://ascii.co.uk/art/knights
+\AddAnimal[tail-count=3]{knight}%>>>
+{     \
+       \   ,-"""-.
+        \  | === |
+           )  |  (
+        .=='\" "/`==.
+      .'\   (`:')   /`.
+    _/_ |_.-' : `-._|__\_
+   <___>'\    :   / `<___>
+   /  /   >=======<  /  /
+ _/ .'   /  ,-:-.  \/=,'
+/ _/    |__/v^v^v\__) \
+\(\)     |V^V^V^V^V|\_/
+ (\\     \`---|---'/
+   \\     \-._|_,-/
+    \\     |__|__|
+     \\   <___X___>
+      \\   \..|../
+       \\   \ | /
+        \\  /V|V\
+         \|/  |  \
+          '--' `--`}%<<<
 %</animals>
 %    \end{macrocode}^^A<<<
 %

Modified: trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.animals.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.animals.tex	2019-01-13 23:30:37 UTC (rev 49690)
+++ trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.animals.tex	2019-01-13 23:31:16 UTC (rev 49691)
@@ -104,9 +104,9 @@
       (")
    >-( : )-<
     (__:__)}%<<<
-\AddAnimal{hedgehog}%>>>
-{  \    .\|//||\||.
-    \  |/\/||/|//|/|
+\AddAnimal[tail-symbol=s]{hedgehog}%>>>
+{  s    .\|//||\||.
+    s  |/\/||/|//|/|
       /. `|/\\|/||/||
      o__,_|//|/||\||'}%<<<
 \AddAnimal{kangaroo}%>>>
@@ -117,10 +117,10 @@
           \,\ / \\
             //   \\
           ,/'     `\_,}%<<<
-\AddAnimal{rabbit}%>>>
-{ \     / \`\         __
-   \   |  \ `\      /`/ \
-    \  \_/`\  \-"-/` /\  \
+\AddAnimal[tail-symbol=s,tail-count=3]{rabbit}%>>>
+{ s     / \`\         __
+   s   |  \ `\      /`/ \
+    s  \_/`\  \-"-/` /\  \
             |       |  \  |
             (d     b)   \_/
             /       \
@@ -143,10 +143,10 @@
     \ _//
      (')---.
       _/-_( )o}%<<<
-\AddAnimal{dragon}%>>>
-{     \                    / \  //\
-       \    |\___/|      /   \//  \\
-        \   /0  0  \__  /    //  | \ \
+\AddAnimal[tail-symbol=s,tail-count=3]{dragon}%>>>
+{     s                   / \  //\
+       s    |\___/|      /   \//  \\
+        s   /0  0  \__  /    //  | \ \
            /     /  \/_/    //   |  \  \
            @_^_@'/   \/_   //    |   \   \
            //_^_/     \/_ //     |    \    \
@@ -201,14 +201,14 @@
                 \_|                        / /       / /
                                          _//       _//
                                        /_|       /_|}%<<<
-\AddAnimal{whale}%>>>
-{  \                |-.
-    \    .-""-._     \ \.--|
-     \  /       `-..__)  ,-'
+\AddAnimal[tail-count=3,tail-symbol=s]{whale}%>>>
+{  s                |-.
+    s    .-""-._     \ \.--|
+     s  /       `-..__)  ,-'
        |     .          /
         \--.__,   .__.,'
          `-.___'._\_.'}%<<<
-\AddAnimal{yoda}%>>>
+\AddAnimal[tail-count=3]{yoda}%>>>
 {   \
      \             ____
       \         _.' :  `._
@@ -245,7 +245,7 @@
          :-""-.`./-.'     /    `.___.'
                \ `t  ._  /
                 "-.t-._:'}%<<<
-\AddAnimal{yoda-head}%>>>
+\AddAnimal[tail-count=3]{yoda-head}%>>>
 {   \
      \             ____
       \         _.' :  `._
@@ -299,6 +299,35 @@
 /_        \O========O/        _\
   `--...__|`-._  _.-'|__...--'
           |    `'    |}%<<<
+\AddAnimal[tail-symbol=|,tail-count=1]{crusader}%>>>
+{ |
+\[T]/}
+\csname bool_if:cT\endcsname {l_ducksay_version_one_bool}
+  {\AnimalOptions{crusader}{tail-1=|,rel-align=c}}
+\csname bool_if:cT\endcsname {l_ducksay_version_two_bool}
+  {\AnimalOptions{crusader}{tail-1=|,body-align=c}}%<<<
+\AddAnimal[tail-count=3]{knight}%>>>
+{     \
+       \   ,-"""-.
+        \  | === |
+           )  |  (
+        .=='\" "/`==.
+      .'\   (`:')   /`.
+    _/_ |_.-' : `-._|__\_
+   <___>'\    :   / `<___>
+   /  /   >=======<  /  /
+ _/ .'   /  ,-:-.  \/=,'
+/ _/    |__/v^v^v\__) \
+\(\)     |V^V^V^V^V|\_/
+ (\\     \`---|---'/
+   \\     \-._|_,-/
+    \\     |__|__|
+     \\   <___X___>
+      \\   \..|../
+       \\   \ | /
+        \\  /V|V\
+         \|/  |  \
+          '--' `--`}%<<<
 %% 
 %%
 %% End of file `ducksay.animals.tex'.

Modified: trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.code.v1.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.code.v1.tex	2019-01-13 23:30:37 UTC (rev 49690)
+++ trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.code.v1.tex	2019-01-13 23:31:16 UTC (rev 49691)
@@ -125,29 +125,24 @@
     \group_end:
   }
 \cs_generate_variant:Nn \ducksay_print:nn { nV }
-\cs_new:Npn \ducksay_prepare_say_and_think:n #1
+\cs_new:Npn \ducksay_say_and_think:nn #1 #2
   {
-    \int_set:Nn \l_ducksay_msg_width_int  { -\c_max_int }
-    \int_set:Nn \l_ducksay_msg_height_int { -\c_max_int }
-    \keys_set:nn { ducksay } { #1 }
-    \tl_if_empty:NT \l_ducksay_animal_tl
-      { \keys_set:nn { ducksay } { default_animal } }
-  }
-\NewDocumentCommand \ducksay { O{} m }
-  {
     \group_begin:
-      \tl_set:Nn \l_ducksay_say_or_think_tl { say }
-      \ducksay_prepare_say_and_think:n { #1 }
+      \int_set:Nn \l_ducksay_msg_width_int  { -\c_max_int }
+      \int_set:Nn \l_ducksay_msg_height_int { -\c_max_int }
+      \keys_set:nn { ducksay } { #1 }
+      \tl_if_empty:NT \l_ducksay_animal_tl
+        { \keys_set:nn { ducksay } { default_animal } }
       \ducksay_print:nV { #2 } \l_ducksay_rel_align_tl
     \group_end:
   }
+\NewDocumentCommand \ducksay { O{} m }
+  {
+    \ducksay_say_and_think:nn { #1 } { #2 }
+  }
 \NewDocumentCommand \duckthink { O{} m }
   {
-    \group_begin:
-      \tl_set:Nn \l_ducksay_say_or_think_tl { think }
-      \ducksay_prepare_say_and_think:n { #1 }
-      \ducksay_print:nV { #2 } \l_ducksay_rel_align_tl
-    \group_end:
+    \ducksay_say_and_think:nn { think, #1 } { #2 }
   }
 %% 
 %%

Modified: trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.code.v2.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.code.v2.tex	2019-01-13 23:30:37 UTC (rev 49690)
+++ trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.code.v2.tex	2019-01-13 23:31:16 UTC (rev 49691)
@@ -43,6 +43,8 @@
     The~specified~message~alignment~`\exp_not:n { #1 }`~is~unknown.~
     `l`~is~used~as~fallback.
   }
+\msg_new:nnn { ducksay } { v1-key-only }
+  { The~`\l_keys_key_tl`~key~is~only~available~for~`version=1`. }
 \tl_new:N \l_ducksay_msg_align_vbox_tl
 \box_new:N \l_ducksay_msg_box
 \bool_new:N \l_ducksay_eat_arg_box_bool
@@ -76,6 +78,7 @@
     ,wd* .initial:n = -\c_max_dim
     ,wd* .value_required:n = true
     ,none          .bool_set:N = \l_ducksay_no_body_bool
+    ,no-bubble     .bool_set:N = \l_ducksay_no_bubble_bool
     ,body-mirrored .bool_set:N = \l_ducksay_mirrored_body_bool
     ,ignore-body   .bool_set:N = \l_ducksay_ignored_body_bool
     ,body-x      .dim_set:N = \l_ducksay_body_x_offset_dim
@@ -137,7 +140,7 @@
     ,bubble-bot-kern  .initial:n = { .2ex }
     ,bubble-bot-kern  .value_required:n = true
     ,bubble-side-kern .tl_set:N  = \l_ducksay_bubble_side_kern_tl
-    ,bubble-side-kern .initial:n = { 0.2em }
+    ,bubble-side-kern .initial:n = { .2em }
     ,bubble-side-kern .value_required:n = true
     ,bubble-delim-top     .tl_set:N  = \l_ducksay_bubble_delim_top_tl
     ,bubble-delim-left-1  .tl_set:N  = \l_ducksay_bubble_delim_left_a_tl
@@ -159,6 +162,12 @@
     ,bubble-delim-right-4 .initial:n = /
     ,strip-spaces .bool_set:N  = \l_ducksay_msg_strip_spaces_bool
   }
+\clist_map_inline:nn
+  { align, rel-align }
+  {
+    \keys_define:nn { ducksay }
+      { #1 .code:n = \msg_error:nn { ducksay } { v1-key-only } }
+  }
 \cs_new:Npn \ducksay_evaluate_message_alignment_fixed_width_common:
   {
     \str_case:Vn \l_ducksay_msg_align_tl
@@ -210,6 +219,7 @@
   }
 \cs_new:Npn \ducksay_digest_options:n #1
   {
+    \group_begin:
     \keys_set:nn { ducksay } { #1 }
     \tl_if_empty:NT \l_ducksay_animal_tl
       { \keys_set:nn { ducksay } { default_animal } }
@@ -264,6 +274,7 @@
           }
         \cs_set_eq:NN \ducksay_eat_argument:w \ducksay_eat_argument_tabular:w
       }
+    \ducksay_eat_argument:w
   }
 \cs_new:Npn \ducksay_set_bubble_top_kern:
   {
@@ -285,88 +296,119 @@
   }
 \cs_new_protected:Npn \ducksay_shipout:
   {
-    \hbox_set:Nn \l_ducksay_tmpa_box
-      { \l_ducksay_bubble_fount_tl \l_ducksay_bubble_delim_top_tl }
-    \int_set:Nn \l_ducksay_msg_width_int
+    \hcoffin_set:Nn \l_ducksay_msg_coffin { \box_use:N \l_ducksay_msg_box }
+    \bool_if:NF \l_ducksay_no_bubble_bool
       {
-        \fp_eval:n
+        \hbox_set:Nn \l_ducksay_tmpa_box
+          { \l_ducksay_bubble_fount_tl \l_ducksay_bubble_delim_top_tl }
+        \int_set:Nn \l_ducksay_msg_width_int
           {
-            ceil
-              ( \box_wd:N \l_ducksay_msg_box / \box_wd:N \l_ducksay_tmpa_box )
-          }
-      }
-    \group_begin:
-    \l_ducksay_bubble_fount_tl
-    \exp_args:NNNx
-    \group_end:
-    \int_set:Nn \l_ducksay_msg_height_int
-      {
-        \int_max:nn
-          {
             \fp_eval:n
               {
                 ceil
                   (
-                    (
-                      \box_ht:N \l_ducksay_msg_box
-                      + \box_dp:N \l_ducksay_msg_box
-                    )
-                    / ( \arraystretch * \baselineskip )
+                    \box_wd:N \l_ducksay_msg_box / \box_wd:N \l_ducksay_tmpa_box
                   )
               }
-            + \l_ducksay_vpad_int
           }
-          { \l_ducksay_msg_height_int }
-      }
-    \hcoffin_set:Nn \l_ducksay_bubble_open_coffin
-      {
+        \group_begin:
         \l_ducksay_bubble_fount_tl
-        \begin{tabular}{@{}l@{}}
-          \int_compare:nNnTF { \l_ducksay_msg_height_int } = { \c_one_int }
-            {
-              \l_ducksay_bubble_delim_left_a_tl
-            }
-            {
-              \l_ducksay_bubble_delim_left_b_tl\\
-              \int_step_inline:nnn
-                { 3 } { \l_ducksay_msg_height_int }
+        \exp_args:NNNx
+        \group_end:
+        \int_set:Nn \l_ducksay_msg_height_int
+          {
+            \int_max:nn
+              {
+                \fp_eval:n
+                  {
+                    ceil
+                      (
+                        (
+                          \box_ht:N \l_ducksay_msg_box
+                          + \box_dp:N \l_ducksay_msg_box
+                        )
+                        / ( \arraystretch * \baselineskip )
+                      )
+                  }
+                + \l_ducksay_vpad_int
+              }
+              { \l_ducksay_msg_height_int }
+          }
+        \hcoffin_set:Nn \l_ducksay_bubble_open_coffin
+          {
+            \l_ducksay_bubble_fount_tl
+            \begin{tabular}{@{}l@{}}
+              \int_compare:nNnTF { \l_ducksay_msg_height_int } = { \c_one_int }
                 {
-                  \kern-\l_ducksay_bubble_side_kern_tl
-                  \l_ducksay_bubble_delim_left_c_tl
-                  \\
+                  \l_ducksay_bubble_delim_left_a_tl
                 }
-              \l_ducksay_bubble_delim_left_d_tl
-            }
-        \end{tabular}
-      }
-    \hcoffin_set:Nn \l_ducksay_bubble_close_coffin
-      {
-        \l_ducksay_bubble_fount_tl
-        \begin{tabular}{@{}r@{}}
-          \int_compare:nNnTF { \l_ducksay_msg_height_int } = { \c_one_int }
-            {
-              \l_ducksay_bubble_delim_right_a_tl
-            }
-            {
-              \l_ducksay_bubble_delim_right_b_tl \\
-              \int_step_inline:nnn
-                { 3 } { \l_ducksay_msg_height_int }
                 {
-                  \l_ducksay_bubble_delim_right_c_tl
-                  \kern-\l_ducksay_bubble_side_kern_tl
-                  \\
+                  \l_ducksay_bubble_delim_left_b_tl\\
+                  \int_step_inline:nnn
+                    { 3 } { \l_ducksay_msg_height_int }
+                    {
+                      \kern-\l_ducksay_bubble_side_kern_tl
+                      \l_ducksay_bubble_delim_left_c_tl
+                      \\
+                    }
+                  \l_ducksay_bubble_delim_left_d_tl
                 }
-              \l_ducksay_bubble_delim_right_d_tl
-            }
-        \end{tabular}
+            \end{tabular}
+          }
+        \hcoffin_set:Nn \l_ducksay_bubble_close_coffin
+          {
+            \l_ducksay_bubble_fount_tl
+            \begin{tabular}{@{}r@{}}
+              \int_compare:nNnTF { \l_ducksay_msg_height_int } = { \c_one_int }
+                {
+                  \l_ducksay_bubble_delim_right_a_tl
+                }
+                {
+                  \l_ducksay_bubble_delim_right_b_tl \\
+                  \int_step_inline:nnn
+                    { 3 } { \l_ducksay_msg_height_int }
+                    {
+                      \l_ducksay_bubble_delim_right_c_tl
+                      \kern-\l_ducksay_bubble_side_kern_tl
+                      \\
+                    }
+                  \l_ducksay_bubble_delim_right_d_tl
+                }
+            \end{tabular}
+          }
+        \hcoffin_set:Nn \l_ducksay_bubble_top_coffin
+          {
+            \l_ducksay_bubble_fount_tl
+            \int_step_inline:nn
+              { \l_ducksay_msg_width_int + \l_ducksay_hpad_int }
+              { \l_ducksay_bubble_delim_top_tl }
+          }
+        \dim_set:Nn \l_ducksay_hpad_dim
+          {
+            (
+              \coffin_wd:N \l_ducksay_bubble_top_coffin
+              - \coffin_wd:N \l_ducksay_msg_coffin
+            ) / 2
+          }
+        \coffin_join:NnnNnnnn
+          \l_ducksay_msg_coffin         { l } { vc }
+          \l_ducksay_bubble_open_coffin { r } { vc }
+          { - \l_ducksay_hpad_dim } { \c_zero_dim }
+        \coffin_join:NnnNnnnn
+          \l_ducksay_msg_coffin          { r } { vc }
+          \l_ducksay_bubble_close_coffin { l } { vc }
+          { \l_ducksay_hpad_dim } { \c_zero_dim }
+        \ducksay_set_bubble_top_kern:
+        \ducksay_set_bubble_bottom_kern:
+        \coffin_join:NnnNnnnn
+          \l_ducksay_msg_coffin        { hc } { t }
+          \l_ducksay_bubble_top_coffin { hc } { b }
+          { \c_zero_dim } { \l_ducksay_bubble_top_kern_dim }
+        \coffin_join:NnnNnnnn
+          \l_ducksay_msg_coffin        { hc } { b }
+          \l_ducksay_bubble_top_coffin { hc } { t }
+          { \c_zero_dim } { \l_ducksay_bubble_bottom_kern_dim }
       }
-    \hcoffin_set:Nn \l_ducksay_bubble_top_coffin
-      {
-        \l_ducksay_bubble_fount_tl
-        \int_step_inline:nn { \l_ducksay_msg_width_int + \l_ducksay_hpad_int }
-          { \l_ducksay_bubble_delim_top_tl }
-      }
-    \hcoffin_set:Nn \l_ducksay_msg_coffin { \box_use:N \l_ducksay_msg_box }
     \bool_if:NF \l_ducksay_no_body_bool
       {
         \hcoffin_set:Nn \l_ducksay_body_coffin
@@ -387,34 +429,6 @@
                 { r } { \tl_set:Nn \l_ducksay_body_to_msg_align_body_tl { l } }
               }
           }
-      }
-    \dim_set:Nn \l_ducksay_hpad_dim
-      {
-        (
-          \coffin_wd:N \l_ducksay_bubble_top_coffin
-          - \coffin_wd:N \l_ducksay_msg_coffin
-        ) / 2
-      }
-    \coffin_join:NnnNnnnn
-      \l_ducksay_msg_coffin         { l } { vc }
-      \l_ducksay_bubble_open_coffin { r } { vc }
-      { - \l_ducksay_hpad_dim } { \c_zero_dim }
-    \coffin_join:NnnNnnnn
-      \l_ducksay_msg_coffin          { r } { vc }
-      \l_ducksay_bubble_close_coffin { l } { vc }
-      { \l_ducksay_hpad_dim } { \c_zero_dim }
-    \ducksay_set_bubble_top_kern:
-    \ducksay_set_bubble_bottom_kern:
-    \coffin_join:NnnNnnnn
-      \l_ducksay_msg_coffin        { hc } { t }
-      \l_ducksay_bubble_top_coffin { hc } { b }
-      { \c_zero_dim } { \l_ducksay_bubble_top_kern_dim }
-    \coffin_join:NnnNnnnn
-      \l_ducksay_msg_coffin        { hc } { b }
-      \l_ducksay_bubble_top_coffin { hc } { t }
-      { \c_zero_dim } { \l_ducksay_bubble_bottom_kern_dim }
-    \bool_if:NF \l_ducksay_no_body_bool
-      {
         \bool_if:NTF \l_ducksay_ignored_body_bool
           { \coffin_attach:NVnNVnnn }
           { \coffin_join:NVnNVnnn   }
@@ -485,22 +499,14 @@
 \cs_generate_variant:Nn \coffin_join:NnnNnnnn { NVnNVnnn }
 \cs_generate_variant:Nn \coffin_attach:NnnNnnnn { NVnNVnnn }
 \cs_generate_variant:Nn \coffin_typeset:Nnnnn { NVVnn }
-\cs_generate_variant:Nn \tl_if_eq:nnT { VnT }
 \cs_generate_variant:Nn \str_case:nn { Vn }
-\cs_generate_variant:Nn \regex_replace_all:NnN { Nnc }
 \NewDocumentCommand \ducksay { O{} }
   {
-    \group_begin:
-      \tl_set:Nn \l_ducksay_say_or_think_tl { say }
-      \ducksay_digest_options:n { #1 }
-      \ducksay_eat_argument:w
+    \ducksay_digest_options:n { #1 }
   }
 \NewDocumentCommand \duckthink { O{} }
   {
-    \group_begin:
-      \tl_set:Nn \l_ducksay_say_or_think_tl { think }
-      \ducksay_digest_options:n { #1 }
-      \ducksay_eat_argument:w
+    \ducksay_digest_options:n { think, #1 }
   }
 %% 
 %%

Modified: trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.sty
===================================================================
--- trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.sty	2019-01-13 23:30:37 UTC (rev 49690)
+++ trunk/Master/texmf-dist/tex/latex/ducksay/ducksay.sty	2019-01-13 23:31:16 UTC (rev 49691)
@@ -35,8 +35,8 @@
 \NeedsTeXFormat{LaTeX2e}
 \RequirePackage{xparse,l3keys2e}
 
-\def\ducksay at version{2.2.1}
-\def\ducksay at date{2019-01-08}
+\def\ducksay at version{2.3}
+\def\ducksay at date{2019-01-13}
 
 \ProvidesExplPackage
   {ducksay}           {\ducksay at date}
@@ -44,8 +44,8 @@
 
 \int_new:N \l_ducksay_msg_width_int
 \int_new:N \l_ducksay_msg_height_int
+\int_new:N \l_ducksay_tail_symbol_count_int
 \seq_new:N \l_ducksay_msg_lines_seq
-\tl_new:N \l_ducksay_say_or_think_tl
 \tl_new:N \l_ducksay_align_tl
 \tl_new:N \l_ducksay_msg_align_tl
 \tl_new:N \l_ducksay_animal_tl
@@ -52,14 +52,12 @@
 \tl_new:N \l_ducksay_body_tl
 \tl_new:N \l_ducksay_bubble_tl
 \tl_new:N \l_ducksay_tmpa_tl
-\bool_new:N \l_ducksay_also_add_think_bool
+\tl_new:N \l_ducksay_tail_symbol_out_one_tl
+\tl_new:N \l_ducksay_tail_symbol_out_two_tl
+\tl_new:N \l_ducksay_tail_symbol_in_tl
 \bool_new:N \l_ducksay_version_one_bool
 \bool_new:N \l_ducksay_version_two_bool
 \box_new:N \l_ducksay_tmpa_box
-\regex_const:Nn \c_ducksay_first_regex  { \A(.\s*)\\ }
-\regex_const:Nn \c_ducksay_second_regex { \A(.[^\c{null}]*\c{null}\s*)\\ }
-\regex_const:Nn \c_ducksay_third_regex  {
-  \A(.[^\c{null}]*\c{null}[^\c{null}]*\c{null}\s*)\\ }
 \regex_const:Nn \c_ducksay_textcolor_regex
   { \cO(?:\\textcolor\{(.*?)\}\{(.*?)\}) }
 \regex_const:Nn \c_ducksay_color_delim_regex
@@ -68,6 +66,8 @@
   { \cO(?:\\color\{(.*?)\}) }
 \msg_new:nnn { ducksay } { load-time-only }
   { The~`#1`~key~is~to~be~used~only~during~package~load~time. }
+\msg_new:nnn { ducksay } { deprecated-key }
+  { The~`\l_keys_key_tl`~key~is~deprecated.~Sorry~for~the~inconvenience. }
 \keys_define:nn { ducksay }
   {
     ,bubble .tl_set:N      = \l_ducksay_bubble_tl
@@ -91,7 +91,17 @@
     ,rel-align .value_required:n = true
     ,ligatures .tl_set:N   = \l_ducksay_ligatures_tl
     ,ligatures .initial:n  = { `<>,'- }
-    ,add-think .bool_set:N = \l_ducksay_also_add_think_bool
+    ,tail-1    .tl_set:N   = \l_ducksay_tail_symbol_out_one_tl
+    ,tail-1    .initial:x  = \c_backslash_str
+    ,tail-2    .tl_set:N   = \l_ducksay_tail_symbol_out_two_tl
+    ,tail-2    .initial:x  = \c_backslash_str
+    ,no-tail   .meta:n     = { tail-1 = { ~ }, tail-2 = { ~ } }
+    ,think     .meta:n     = { tail-1 = { O }, tail-2 = { o } }
+    ,say       .code:n     =
+      {
+        \exp_args:Nx \DucksayOptions
+          { tail-1 = { \c_backslash_str }, tail-2 = { \c_backslash_str } }
+      }
     ,version   .choice:
     ,version / 1 .code:n   =
       {
@@ -104,6 +114,7 @@
         \bool_set_true:N  \l_ducksay_version_two_bool
       }
     ,version   .initial:n  = 2
+    ,add-think .code:n     = \msg_error:nn { ducksay } { deprecated-key }
   }
 \ProcessKeysOptions { ducksay }
 \keys_define:nn { ducksay }
@@ -110,17 +121,16 @@
   {
     version .code:n = \msg_error:nnn { ducksay } { load-time-only } { version }
   }
-\cs_generate_variant:Nn \tl_if_eq:nnT { VnT }
-\cs_new_protected:Npn \ducksay_create_think_animal:n #1
+\keys_define:nn { ducksay / add-animal }
   {
-    \group_begin:
-      \tl_set_eq:Nc \l_ducksay_tmpa_tl { g_ducksay_animal_say_#1_tl }
-      \regex_replace_once:NnN \c_ducksay_first_regex  { \1O } \l_ducksay_tmpa_tl
-      \regex_replace_once:NnN \c_ducksay_second_regex { \1o } \l_ducksay_tmpa_tl
-      \regex_replace_once:NnN \c_ducksay_third_regex  { \1o } \l_ducksay_tmpa_tl
-      \tl_gset_eq:cN { g_ducksay_animal_think_#1_tl } \l_ducksay_tmpa_tl
-    \group_end:
+    ,tail-symbol .code:n    =
+      \tl_set:Nx \l_ducksay_tail_symbol_in_tl { \tl_to_str:n { #1 } }
+    ,tail-symbol .initial:o = \c_backslash_str
+    ,tail-count  .int_set:N = \l_ducksay_tail_symbol_count_int
+    ,tail-count  .initial:n = 2
   }
+\cs_generate_variant:Nn \tl_replace_once:Nnn { NVn }
+\cs_generate_variant:Nn \tl_replace_all:Nnn { NVn }
 \cs_new_protected:Npx \ducksay_replace_verb_newline:Nn #1 #2
   {
     \tl_replace_all:Nnn #1 { \char_generate:nn { 13 } { 12 } } { #2 }
@@ -136,25 +146,63 @@
     \ducksay_replace_verb_newline_newline:Nn \ProcessedArgument { #2 }
     \ducksay_replace_verb_newline:Nn \ProcessedArgument { #1 }
   }
-\cs_new_protected:Npn \ducksay_add_animal_inner:nn #1 #2
+\cs_new_protected:Npn \ducksay_add_animal_inner:nnnn #1 #2 #3 #4
   {
-    \tl_set:Nn \l_ducksay_tmpa_tl { \ #2 }
-    \tl_map_inline:Nn \l_ducksay_ligatures_tl
-      { \tl_replace_all:Nnn \l_ducksay_tmpa_tl { ##1 } { { ##1 } } }
-    \ducksay_replace_verb_newline:Nn \l_ducksay_tmpa_tl { \tabularnewline\null }
-    \tl_gset_eq:cN { g_ducksay_animal_say_#1_tl } \l_ducksay_tmpa_tl
-    \keys_define:nn { ducksay }
+    \group_begin:
+      \keys_set:nn { ducksay / add-animal } { #1 }
+      \tl_set:Nn \l_ducksay_tmpa_tl { \ #3 }
+      \int_compare:nNnTF { \l_ducksay_tail_symbol_count_int } < { \c_zero_int }
+        {
+          \tl_replace_once:NVn
+            \l_ducksay_tmpa_tl
+            \l_ducksay_tail_symbol_in_tl
+            \l_ducksay_tail_symbol_out_one_tl
+          \tl_replace_all:NVn
+            \l_ducksay_tmpa_tl
+            \l_ducksay_tail_symbol_in_tl
+            \l_ducksay_tail_symbol_out_two_tl
+        }
+        {
+          \int_compare:nNnT { \l_ducksay_tail_symbol_count_int } >
+            { \c_zero_int }
+            {
+              \tl_replace_once:NVn
+                \l_ducksay_tmpa_tl
+                \l_ducksay_tail_symbol_in_tl
+                \l_ducksay_tail_symbol_out_one_tl
+              \int_step_inline:nnn { 2 } { \l_ducksay_tail_symbol_count_int }
+                {
+                  \tl_replace_once:NVn
+                    \l_ducksay_tmpa_tl
+                    \l_ducksay_tail_symbol_in_tl
+                    \l_ducksay_tail_symbol_out_two_tl
+                }
+            }
+        }
+      \tl_map_inline:Nn \l_ducksay_ligatures_tl
+        { \tl_replace_all:Nnn \l_ducksay_tmpa_tl { ##1 } { { ##1 } } }
+      \ducksay_replace_verb_newline:Nn \l_ducksay_tmpa_tl
+        { \tabularnewline\null }
+      \exp_args:NNnV
+    \group_end:
+    \tl_set:cn { l_ducksay_animal_#2_tl } \l_ducksay_tmpa_tl
+    \exp_args:Nnx \keys_define:nn { ducksay }
       {
-        #1 .code:n =
+        #2 .code:n =
           {
-            \tl_if_exist:cF
-              { g_ducksay_animal_ \l_ducksay_say_or_think_tl _#1_tl }
-              { \ducksay_create_think_animal:n { #1 } }
-            \tl_set_eq:Nc \l_ducksay_animal_tl
-              { g_ducksay_animal_ \l_ducksay_say_or_think_tl _#1_tl }
+            \exp_not:n { \tl_set_eq:NN \l_ducksay_animal_tl }
+            \exp_after:wN \exp_not:N \cs:w l_ducksay_animal_#2_tl \cs_end:
+            \exp_not:n { \exp_args:NV \DucksayOptions }
+            \exp_after:wN
+            \exp_not:N \cs:w l_ducksay_animal_#2_options_tl \cs_end:
           }
       }
+    \tl_if_exist:cF { l_ducksay_animal_#2_options_tl }
+      { \tl_new:c { l_ducksay_animal_#2_options_tl } }
+    \IfBooleanT { #4 }
+      { \keys_define:nn { ducksay } { default_animal .meta:n = { #2 } } }
   }
+\cs_generate_variant:Nn \ducksay_add_animal_inner:nnnn { nnVn }
 \NewDocumentCommand \DefaultAnimal { m }
   {
     \keys_define:nn { ducksay } { default_animal .meta:n = { #1 } }
@@ -163,31 +211,35 @@
   {
     \keys_set:nn { ducksay } { #1 }
   }
-\NewDocumentCommand \AddAnimal { s m +v }
+\NewDocumentCommand \AddAnimal { s O{} m +v }
   {
-    \ducksay_add_animal_inner:nn { #2 } { #3 }
-    \bool_if:NT \l_ducksay_also_add_think_bool
-      { \ducksay_create_think_animal:n { #2 } }
-    \IfBooleanT{#1}
-      { \keys_define:nn { ducksay } { default_animal .meta:n = { #2 } } }
+    \ducksay_add_animal_inner:nnnn { #2 } { #3 } { #4 } { #1 }
   }
-\NewDocumentCommand \AddColoredAnimal { s m +v }
+\NewDocumentCommand \AddColoredAnimal { s O{} m +v }
   {
-    \ducksay_add_animal_inner:nn { #2 } { #3 }
-    \regex_replace_all:Nnc \c_ducksay_color_delim_regex
+    \tl_set:Nn \l_ducksay_tmpa_tl { #4 }
+    \regex_replace_all:NnN \c_ducksay_color_delim_regex
       { \c{bgroup}\c{color}\cB\{\1\cE\}\2\c{egroup} }
-      { g_ducksay_animal_say_#2_tl }
-    \regex_replace_all:Nnc \c_ducksay_color_regex
+      \l_ducksay_tmpa_tl
+    \regex_replace_all:NnN \c_ducksay_color_regex
       { \c{color}\cB\{\1\cE\} }
-      { g_ducksay_animal_say_#2_tl }
-    \regex_replace_all:Nnc \c_ducksay_textcolor_regex
+      \l_ducksay_tmpa_tl
+    \regex_replace_all:NnN \c_ducksay_textcolor_regex
       { \c{textcolor}\cB\{\1\cE\}\cB\{\2\cE\} }
-      { g_ducksay_animal_say_#2_tl }
-    \bool_if:NT \l_ducksay_also_add_think_bool
-      { \ducksay_create_think_animal:n { #2 } }
-    \IfBooleanT{#1}
-      { \keys_define:nn { ducksay } { default_animal .meta:n = { #2 } } }
+      \l_ducksay_tmpa_tl
+    \ducksay_add_animal_inner:nnVn { #2 } { #3 } \l_ducksay_tmpa_tl { #1 }
   }
+\NewDocumentCommand \AnimalOptions { s m m }
+  {
+    \tl_if_exist:cTF { l_ducksay_animal_#2_options_tl }
+      {
+        \IfBooleanTF { #1 }
+          { \tl_set:cn }
+          { \tl_put_right:cn }
+      }
+      { \tl_set:cn }
+    { l_ducksay_animal_#2_options_tl } { #3, }
+  }
 \bool_if:NT \l_ducksay_version_one_bool
   { \file_input:n { ducksay.code.v1.tex } }
 \bool_if:NT \l_ducksay_version_two_bool



More information about the tex-live-commits mailing list