13

As remarked in another question from today, I am trying to find a more flexible way to get text wrapped around figures than standard solutions like wrapfig or manual use of \parshape, and my eyes are currently focused on the experimental expl3 package xgalley.

One of the issues is how to place the figure on the right of the text. My first attempts, shown below, use a not very pure mixture of plain TeX commands and expl3. Is there a more “correct” and “pure”, preferably expl3-based approach to placing the figure on the right?

There are alse several problems with the current construction, including the fact that it uses \vadjust, which is likely to mess up the mechanisms from xgalley. Also, I was unable to make the content of \putright top-aligned (\vtop messed everything up), so the first argument of \putright (the number of lines to lower the figure) has to be higher than the one passed to \cutright.

\documentclass{article}

\usepackage{lipsum,xgalley,graphicx}

\ExplSyntaxOn
\cs_new_eq:NN \cutright \galley_cutout_right:nn
\ExplSyntaxOff

\newcommand\putright[2]{% #1 = number of lines to lower the figure, #2 = content
    \leavevmode
    \vadjust{%
        \hfill
        \smash{%
            \lower\dimexpr#1\baselineskip-2.5\lineskip\relax\llap{#2}%
        }%
    }\ignorespaces
}

\begin{document}


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

\end{document}

enter image description here

10
  • 'LaTeX3' is nothing like available: presumably you want some expl3-based code to do what you have in primitives.
    – Joseph Wright
    May 12, 2016 at 16:41
  • You might consider this a “best practises” question.
    – Gaussler
    May 12, 2016 at 16:45
  • 2
    A better wrapfig would be neat. I once tried to adapt it to have some full lines before the cut and had a lot of problems. May 12, 2016 at 17:09
  • @UlrikeFischer As I am sure you know much more about than I, you can get quite far with \parshape, if you are willing to make thousands of small manual adjustments.
    – Gaussler
    May 12, 2016 at 17:10
  • 3
    @Gaussler Thank for inspiration. I've created a new OPmac trick 0154 petr.olsak.net/opmac-tricks-e.html#setparshape and 0155 petr.olsak.net/opmac-tricks-e.html#putshape where the problem is solved.
    – wipet
    Jun 8, 2016 at 19:34

2 Answers 2

5
+50

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

27
  • I think part of why I split \cutright and \putright into two macros in the MWE was to keep things simple and “minimal”. I certainly planned to put both into a single wrapper command in my own preamble. Obviously, we also need a \putleft command, but I assume that is just a matter of modifying your code. Thanks for the great answer!
    – Gaussler
    Jun 8, 2016 at 19:48
  • @Gaussler Just be a little careful. Because, I assume, of the way xgalley works, it isn't possible to use a group within the definition of \putright to limit the effects of the code. This is why I created so many \l_gausserl_... variables rather than using the \l_tmp... versions, even when the latter seemed quite sufficient to the purpose. I thought it might be safer not to change them in case you used them elsewhere.
    – cfr
    Jun 8, 2016 at 21:52
  • @Gaussler You might want to use the latest version above. It seems slightly more reliable.
    – cfr
    Jun 9, 2016 at 1:55
  • 1
    I tried your "complete code" and I don't understand two things. 1 why there is a vertical space between paragraphs? 2. When I remove (comment out) the \putleft[3]{...} between \kant[1-2] and \kant[3], then the vertical space is removed, but there is an accidental cropping of the text which repeates the cropping from the beginning of the document.
    – wipet
    Jun 9, 2016 at 6:07
  • 1
    @Gaussler Please see edit. I'm an idiot. I realised where the vertical spacing is coming from so I made the correction. (At least, I think I understand where it is coming from.)
    – cfr
    Jun 9, 2016 at 14:22
4

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}{...}.

7
  • Well, part of why I wanted an expl3 solution was because xgalley actually supports figures spanning multilple paragraphs, something which is very hard to do in 2e.
    – Gaussler
    Jun 8, 2016 at 19:39
  • @Gaussler I think the point is that wipet would recommend not using LaTeX of any kind. Though I do think that 'simple' and 'straightforward' are very much in the eyes of the beholder: what is simplest and most straightforward for anybody tends to be what that person is most familiar with ;).
    – cfr
    Jun 8, 2016 at 21:54
  • @Gaussler And, of course, part of the ultimate goal of L3 is, as I understand it, to provide a language which means you don't need to use the lower level TeX stuff, as you do with 2e. So the simple/straightforward aspect will change a bit when L3 matures as LaTeX will then be more of a genuine alternative, rather than needing to be a convenient addition. At least, I guess that's part of the idea. (Of course, it will still ultimately rely on TeX.)
    – cfr
    Jun 8, 2016 at 21:57
  • @cfr The main problem of L3 is the goal described in your comment: to give different language than TeX but to use TeX. Why not to use the same language as understands the used interpreter? If you learn only a different language than used in the interpreter then you will never understand the interpreter well (tracing, error messages etc.). My idea is: use only the same language as the interpreter is using and you will be happy without any problems. As me. This is more "straightforward" (independent of the beholder). Unfortunately, L2e is very bad example of mixing various approaches.
    – wipet
    Jun 9, 2016 at 6:19
  • But you are very, very, very experienced with TeX. I'm not and the code is very difficult to learn because there is no pattern to it in the way there is to L3. You can't guess the name or meaning of a macro, for example. It is great that you are so comfortable with TeX. But it is not at all straightforward for the rest of us!
    – cfr
    Jun 9, 2016 at 12:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .