Best way to wrap text around figures in expl3

If you really want the syntax shown in the question, see my original answer below.

This edit revises the user interface and eliminates the need to use \cutright before \putright. This version also produces a tidier result and is, I think, less incorrect than my first attempt.

The new version defines only the following commands:

\putright[<options>]{<content for cut-out>}
\putleft[<options>]{<content for cut-out>}
\resetindents
\xwrapfigsetup{<options>}

<options> are defined using a key-value interface. \xwrapfigsetup{} knows exactly one key, cutout which itself takes a list of default options for cutouts created with \putright and \putleft. \putright and \putleft know about slightly more keys:

hmargin=<dimension>

which specifies the total horizontal space left to the left and right of the content of the cutout (default is 2ex);

skip lines=<integer>

which specifies the number of lines to allow for the cutout in addition to those required to fit the content (default is 1);

top lines=<integer>

which specifies the number of unaltered lines to leave at the top of the paragraph before the cutout (default is 2).

The rest is handled automatically or not at all, with the rather large exception of restoring standard paragraph shape after the cutout is typeset.

\putright/\putleft calculate an appropriate number of lines to indent and the width required for the indents. They then add the cutout to the current paragraph and finally typeset the content of the cutout.

This means that simply saying

\putright{\includegraphics[width=.3\textwidth]{duck}}
\lipsum

produces

duck out on right

Similarly,

\putleft[top lines=3]{\includegraphics[width=.3\textwidth]{duck}}
\kant[3]

duck left

The additional line skipped allows a little head and tail room for the duck. Obviously, this can be amended if you would prefer a tighter or looser fit.

Complete code (earlier version in my answer here:

\documentclass{article}
\usepackage{kantlipsum,xgalley,tikz}
\ExplSyntaxOn
\box_new:N \l_xwrapfig_fig_box
\box_new:N \l_xwrapfig_vfig_box
\box_new:N \l_xwrapfig_hfig_box
\dim_new:N \l_xwrapfig_wd_dim
\dim_new:N \l_xwrapfig_totalht_dim
\clist_new:N \l_xwrapfig_indents_clist
\int_new:N \l_xwrapfig_lines_int
\int_new:N \l_xwrapfig_totallines_int
\fp_new:N \l_xwrapfig_adjfig_fp
\keys_define:nn { xwrapfig }
{
  cutout .code:n = {
    \keys_set:nn { xwrapfig / cutout } { #1 }
  }
}
\keys_define:nn { xwrapfig / cutout }
{
  hmargin .dim_set:N = \l_xwrapfig_adjwd_dim,
  hmargin .initial:n = { 2ex },
  skip~lines .int_set:N = \l_xwrapfig_adjlines_int,
  skip~lines .initial:n = { 1 },
  top~lines .int_set:N = \l_xwrapfig_toplines_int,
  top~lines .initial:n = { 2 },
}
\cs_generate_variant:Nn \galley_cutout_right:nn { nV }
\cs_generate_variant:Nn \galley_cutout_left:nn { nV }
\cs_new_protected:Nn \xwrapfig_prewrap:n
{
  \clist_clear:N \l_xwrapfig_indents_clist
  \clist_clear:N \l_xwrapfig_zindents_clist
  \vbox_set:Nn \l_xwrapfig_vfig_box { #1 }
  \dim_set:Nn \l_xwrapfig_totalht_dim { \box_ht:N \l_xwrapfig_vfig_box + \box_dp:N \l_xwrapfig_vfig_box }
  \hbox_set:Nn \l_xwrapfig_hfig_box { #1 }
  \dim_set:Nn \l_xwrapfig_wd_dim { \box_wd:N \l_xwrapfig_hfig_box + \l_xwrapfig_adjwd_dim }
  \int_set:Nn \l_xwrapfig_totallines_int { ( \l_xwrapfig_totalht_dim / \baselineskip ) + \l_xwrapfig_adjlines_int }
  \int_zero:N \l_xwrapfig_lines_int
  \int_do_while:nn { \l_xwrapfig_lines_int < \l_xwrapfig_totallines_int }
  {
    \int_incr:N \l_xwrapfig_lines_int
    \clist_put_right:Nn \l_xwrapfig_indents_clist { \l_xwrapfig_wd_dim }
  }
}
\cs_new_protected:Nn \xwrapfig_postwrap:
{
  \box_set_ht:Nn \l_xwrapfig_fig_box { 0pt }
  \box_set_dp:Nn \l_xwrapfig_fig_box { 0pt }
  \skip_vertical:n { -\baselineskip }
  \box_use:N \l_xwrapfig_fig_box
}
\cs_new_protected:Nn \xwrapfig_putright:nn
{
  \xwrapfig_prewrap:n { #2 }
  \galley_cutout_right:nV { #1 } \l_xwrapfig_indents_clist
  \vbox_set:Nn \l_xwrapfig_fig_box
  {
    \fp_set:Nn \l_xwrapfig_adjfig_fp { ( #1 + .5\l_xwrapfig_adjlines_int ) * \baselineskip }
    \skip_vertical:n  { \fp_to_dim:N \l_xwrapfig_adjfig_fp }
    \hbox_to_wd:nn { \linewidth } { \skip_horizontal:n { \linewidth - \l_xwrapfig_wd_dim + .5\l_xwrapfig_adjwd_dim } #2 }
  }
  \xwrapfig_postwrap:
}
\cs_new_protected:Nn \xwrapfig_putleft:nn
{
  \xwrapfig_prewrap:n { #2 }
  \galley_cutout_left:nV { #1 } \l_xwrapfig_indents_clist
  \vbox_set:Nn \l_xwrapfig_fig_box
  {
    \fp_set:Nn \l_xwrapfig_adjfig_fp { ( #1 + .5\l_xwrapfig_adjlines_int ) * \baselineskip }
    \skip_vertical:n  { \fp_to_dim:N \l_xwrapfig_adjfig_fp }
    \hbox_to_wd:nn { \l_xwrapfig_wd_dim } { \skip_horizontal:n { .5\l_xwrapfig_adjwd_dim } #2 }
  }
  \xwrapfig_postwrap:
}
\cs_generate_variant:Nn \xwrapfig_putleft:nn { Vn }
\cs_generate_variant:Nn \xwrapfig_putright:nn { Vn }
\NewDocumentCommand\putright { O { } +m }
{
  \keys_set:nn { xwrapfig / cutout } { #1 }
  \xwrapfig_putright:Vn \l_xwrapfig_toplines_int { #2 }
}
\NewDocumentCommand\putleft { O { } +m }
{
  \keys_set:nn { xwrapfig / cutout } { #1 }
  \xwrapfig_putleft:Vn \l_xwrapfig_toplines_int { #2 }
}
\NewDocumentCommand\resetindents { }
{
  \galley_parshape_set_multi:nnnN { 0 } { 0pt } { 0pt } \c_true_bool
}
\NewDocumentCommand\xwrapfigsetup { m }
{
  \keys_set:nn { xwrapfig } { #1 }
}
\ExplSyntaxOff

\begin{document}
\kant[1]

\putright[top lines=0]{%
  \begin{tikzpicture}
    \newcommand*\len{1.5}
    \draw [thick] (0,0,\len) coordinate (a) \foreach \i/\j in {(\len,0,\len)/b,(\len,0,0)/c,(\len,\len,0)/g,(0,\len,0)/h,(0,\len,\len)/e,(\len,\len,\len)/f} {-- \i coordinate (\j)} -- (g) (f) -- (b) (a) -- (e);
    \coordinate (d) at (0,0,0);
    \draw [gray] (a) -- (d) edge (c) -- (h);
    \foreach \i/\j in {a/left,b/right,c/right,d/left,e/left,f/right,g/above,h/above} \node at (\i) [\j] {\i};
  \end{tikzpicture}}
Let $G S_8$ and $X = \{\{a,g\},\{b,h\},\{c,e\},\{d,f\}\}$
\kant[1-2]

\kant[3]
\clearpage

\putright[top lines=0]{%
  \begin{tikzpicture}
    \newcommand*\len{1.5}
    \draw [thick] (0,0,\len) coordinate (a) \foreach \i/\j in {(\len,0,\len)/b,(\len,0,0)/c,(\len,\len,0)/g,(0,\len,0)/h,(0,\len,\len)/e,(\len,\len,\len)/f} {-- \i coordinate (\j)} -- (g) (f) -- (b) (a) -- (e);
    \coordinate (d) at (0,0,0);
    \draw [gray] (a) -- (d) edge (c) -- (h);
    \foreach \i/\j in {a/left,b/right,c/right,d/left,e/left,f/right,g/above,h/above} \node at (\i) [\j] {\i};
  \end{tikzpicture}}
Let $G S_8$ and $X = \{\{a,g\},\{b,h\},\{c,e\},\{d,f\}\}$
\kant[1-2]

\putleft[top lines=3]{\includegraphics[width=.3\textwidth]{duck}}
\kant[3]
\clearpage

\putleft[top lines=0]{%
  \begin{tikzpicture}
    \newcommand*\len{1.5}
    \draw [thick] (0,0,\len) coordinate (a) \foreach \i/\j in {(\len,0,\len)/b,(\len,0,0)/c,(\len,\len,0)/g,(0,\len,0)/h,(0,\len,\len)/e,(\len,\len,\len)/f} {-- \i coordinate (\j)} -- (g) (f) -- (b) (a) -- (e);
    \coordinate (d) at (0,0,0);
    \draw [gray] (a) -- (d) edge (c) -- (h);
    \foreach \i/\j in {a/left,b/right,c/right,d/left,e/left,f/right,g/above,h/above} \node at (\i) [\j] {\i};
  \end{tikzpicture}}
Let $G S_8$ and $X = \{\{a,g\},\{b,h\},\{c,e\},\{d,f\}\}$\par
\kant[4]

\putright{\includegraphics[width=.3\textwidth]{duck}}
\kant[5]
\resetindents

\xwrapfigsetup{%
  cutout={%
    skip lines=3,
    hmargin=5ex,
    top lines=1,
  }
}

\kant[6]

\kant[7]

\putright{\includegraphics[width=.3\textwidth]{duck}}
\kant[5]


\end{document}

ducks and cubes

Note that it isn't clear to me how to apply this only to the current block of text. There is something very odd about the way this works. I tried to use \galley_parshape_set_single:nVVN but I could only get that to work properly for \putleft.

\resetindents is, therefore, provided to reset the paragraph indentation. This needs to come after the \putleft or \putright is used.

What I don't understand is why the second paragraph of, say, \kant[1-2] isn't affected, but a subsequent \kant[3] is (as wipet points out) or why a left cutout space is repeated several paragraphs after it was last used.

I suspect there's something fundamental about how this is meant to be done which I just don't get at the moment.

Original answer

Do you want something like this?

\documentclass{article}
\usepackage{lipsum,xgalley,graphicx}
\ExplSyntaxOn
% don't use \cs_new_eq for document-level macros (which necessarily fail to conform to expl3 syntax) - use xparse
\NewDocumentCommand\cutright { m m }
{
  \galley_cutout_right:nn { #1 } { #2 }
}
\coffin_new:N \l_gaussler_fig_coffin
\box_new:N \l_gaussler_fig_box
\NewDocumentCommand\putright { m m }
{
  \vbox_set:Nn \l_tmpa_box { #2 }
  \dim_set:Nn \l_tmpa_dim { \box_dp:N \l_tmpa_box + \box_ht:N \l_tmpa_box }
  \int_set:Nn \l_tmpa_int { ( #1 - ( \l_tmpa_dim / \baselineskip ) ) /2 }
  \skip_vertical:n { #1\baselineskip - \l_tmpa_dim +\l_tmpa_int\baselineskip }
  \skip_horizontal:N \linewidth
  \vbox_to_zero:n
  {
    \hbox_overlap_left:n { #2 }
  }
  \skip_vertical:n { -#1\baselineskip + \l_tmpa_dim - \l_tmpa_int\baselineskip  }
}
\ExplSyntaxOff

\begin{document}


\cutright{4}{.35\textwidth,.35\textwidth,.35\textwidth,.35\textwidth,.35\textwidth,.35\textwidth,.35\textwidth,.35\textwidth,.35\textwidth}
\putright{9}{\includegraphics[width=.3\textwidth]{duck}}
\lipsum

\end{document}

confused duck


I don't understand two things. Why do you need explicitly expl3 solution when solution based on TeX primitives is more simple and more straightforward. And why you are using different data in \cutright and \putright. First parameter (4) denotes the number of untouched lines but second (12) -- I don't know what it is.

I am able to help you with the second thing. Define:

\def\putright#1#2{\vskip\parskip
   \hbox to\hsize{\hss
      \vbox to0pt{\kern#1\baselineskip\kern-.8\baselineskip \hbox{#2}\vss}}
   \nobreak\vskip-\parskip\vskip-\baselineskip
}

Then you can use the same data for both: \cutright{4}{...}\putright{4}{...}.