tabl-tsp.mkiv / last modification: 2020-01-30 14:16
%D \module
%D   [       file=tabl-tsp,
%D        version=2000.10.20,
%D          title=\CONTEXT\ Table Macros,
%D       subtitle=Splitting,
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

\writestatus{loading}{ConTeXt Table Macros / Splitting}

%D The code in this file is moved here from other places and needs
%D a mkiv cleanup. As it mostly targets at tables the code lives in
%D the tabl and page namespaces.

% work in progress

\unprotect

%D Although the name resembles floats, and therefore this should be
%D a page module, we decided to make it core functionality because the
%D table code depends on it. Othrwise there would be too much
%D overloading afterwards involved. Actually, the float part is rather
%D generic and not that related to floats.

% \splitfloat [settings] {\placetable[optional args]{test}} {content}

%D When \type {inbetween} is made empty instead of the
%D default \type {\page}, we will get delayed flushing
%D and text may continue below the graphic.
%D
%D \starttyping
%D \dorecurse{2}{\input tufte }
%D
%D \splitfloat[lines=auto,inbetween=]
%D   {\placetable{\dorecurse{5}{test\recurselevel\endgraf}}}
%D   {\bTABLE[split=yes]
%D    \bTR \bTD 11 \eTD \bTD \input tufte \eTD \eTR
%D    \bTR \bTD 12 \eTD \bTD \input zapf \eTD \eTR
%D    \bTR \bTD 13 \eTD \bTD \input bryson \eTD \eTR
%D    \bTR \bTD 14 \eTD \bTD test  \eTD \eTR
%D    \bTR \bTD 21 \eTD \bTD \input tufte \eTD \eTR
%D    \bTR \bTD 22 \eTD \bTD \input zapf \eTD \eTR
%D    \bTR \bTD 23 \eTD \bTD \input bryson \eTD \eTR
%D    \bTR \bTD 24 \eTD \bTD test  \eTD \eTR
%D    \bTR \bTD 31 \eTD \bTD \input tufte \eTD \eTR
%D    \bTR \bTD 32 \eTD \bTD \input zapf \eTD \eTR
%D    \bTR \bTD 33 \eTD \bTD \input bryson \eTD \eTR
%D    \bTR \bTD 34 \eTD \bTD test  \eTD \eTR
%D    \eTABLE}
%D
%D \dorecurse{10}{\input tufte }
%D \stoptyping

\installcorenamespace{floatsplitting}

\installdirectcommandhandler \??floatsplitting {floatsplitting} % \??floatsplitting

\setupfloatsplitting
  [\c!conversion=\v!character, % \v!romannumerals
   \c!lines=3,
   \c!before=,
   \c!inbetween=\page,
   \c!after=]

\newconditional\splitfloatfirstdone
\newconditional\somenextsplitofffloat
\newconditional\splitfloatdone
\newconditional\onlyonesplitofffloat   \settrue\onlyonesplitofffloat

\newif         \ifinsidesplitfloat   % will become conditional

\newcount      \noffloatssplits
\newtoks       \everysplitfloatsetup

\let           \extrasplitfloatlines \!!zerocount
\let           \splitfloatfinalizer  \relax
\let           \floatcaptionsuffix   \empty

\unexpanded\def\splitfloat
  {\dosingleempty\page_split_float}

\def\page_split_float[#1]#2% nog dubbele refs
  {\bgroup
   \global\setfalse\splitfloatdone
   \aftergroup\page_split_float_check
   \insidefloattrue
   \insidesplitfloattrue
   \setupcurrentfloatsplitting[#1]%
   \global\noffloatssplits\zerocount
   \let\floatcaptionsuffix\page_split_float_suffix
   \edef\extrasplitfloatlines{\floatsplittingparameter\c!lines}%
   \the\everysplitfloatsetup
   \def\splitfloatcommand{#2}%
   \global\settrue \onlyonesplitofffloat
   \global\setfalse\somenextsplitofffloat
   \page_floats_push_saved
   \floatsplittingparameter\c!before
   \let\next} % \bgroup

\unexpanded\def\page_split_float_suffix
  {\begingroup
   \usefloatsplittingstyleandcolor\c!style\c!color % only the suffix
   \convertnumber{\floatsplittingparameter\c!conversion}\noffloatssplits
   \endgroup}

\unexpanded\def\page_split_float_check
  {\ifconditional\splitfloatdone
     \splitfloatfinalizer % a weird place (could interfere with flushing)
   \else
     \blank
     \begingroup
     \tttf \dontleavehmode \getmessage\m!floatblocks{13}\empty
     \endgroup
     \blank
     \showmessage\m!floatblocks{13}\empty
   \fi}

\def\page_split_float_process % nextbox
  {\ifinsidesplitfloat
     \expandafter\page_split_float_process_yes
   \else
     \expandafter\page_split_float_process_nop
   \fi}

\def\page_split_float_process_yes
  {\dowithnextboxcs\page_split_float_process_finish\vbox}

\def\page_split_float_process_finish
  {\forgetall
   \dontcomplain
   \global\settrue\splitfloatdone
 % \nodelocationmode\zerocount % bypass auto-renumbering
   \global\advance\noffloatssplits\plusone
   \ifcase\noffloatssplits\relax \or
     \ifconditional\onlyonesplitofffloat
       \let\floatcaptionsuffix\empty
     \fi
   \fi
   \bgroup
     \ifconditional\somenextsplitofffloat
       \notesenabledfalse % best here, experimental, brrr; test with note in caption
     \fi
     \splitfloatcommand{\box\nextbox}%
   \egroup
   \ifconditional\somenextsplitofffloat
     \edef\p_inbetween{\floatsplittingparameter\c!inbetween}%
     \ifx\p_inbetween\empty
       \ifconditional\splitfloatfirstdone\else\page\fi
     \else
       \p_inbetween
     \fi
   \else
     \floatsplittingparameter\c!after
     \page_floats_pop_saved
     \page_floats_flush_saved
   \fi
   \global\settrue\splitfloatfirstdone}

\def\page_split_float_process_nop
  {\dowithnextboxcs\page_split_float_process_nop_finish\vbox}

\def\page_split_float_process_nop_finish
  {\forgetall
   \dontcomplain
   \box\nextbox % maybe an option to unvbox
   \global\settrue\splitfloatfirstdone}

\def\page_split_float_check_content#1% box
  {\ifinsidesplitfloat
   % \ifdim\ht#1=\zeropoint % funny: \ifcase does not check for overflow
     \ifcase\ht#1\relax
       \global\setfalse\somenextsplitofffloat
     \else
       \global\settrue \somenextsplitofffloat
       \global\setfalse\onlyonesplitofffloat
     \fi
   \fi}

\def\page_split_float_check_caption#1% depends on page-flt .. pretty messy
  {\edef\extrasplitfloatlines{\extrasplitfloatlines}%
   \ifx\extrasplitfloatlines\v!auto
      \bgroup
      \forcelocalfloats
      \setuplocalfloats[\c!before=,\c!after=,\c!inbetween=]%
      % This controls samepage resetting too but it also messes up the numbering
      % so I need another fix.
%       \settrialtypesetting
      \splitfloatcommand{\hbox to #1{\strut}}% dummy line
%       \resettrialtypesetting
      \setbox\scratchbox\vbox{\flushlocalfloats}% \vpack ?
      \getnoflines{\ht\scratchbox}%
      \resetlocalfloats
      \advance\noflines\minusone % compensate dummy line
      \normalexpanded{\egroup\noexpand\edef\noexpand\extrasplitfloatlines{\the\noflines}}%
      \global\settrue\usesamefloatnumber
   \else
      \doifelsenumber\extrasplitfloatlines\donothing{\def\extrasplitfloatlines{1}}%
   \fi}

\unexpanded\def\doifnotinsidesplitfloat
  {\ifinsidesplitfloat
     \expandafter\gobbleoneargument
   \fi}

%D Table splitter, on top of previous code:

% todo: keep tail to rest, so we need a lookahead

\newbox\tsplitcontent
\newbox\tsplitresult
\newbox\tsplithead
\newbox\tsplitnext
\newbox\tsplittail

\newtoks\everyresettsplit

\appendtoks
   \let   \tsplitminimumfreelines\!!zerocount
   \let   \tsplitminimumfreespace\!!zeropoint
   \setbox\tsplitcontent         \emptyvbox
   \setbox\tsplitresult          \emptyvbox
   \setbox\tsplithead            \emptyvbox
   \setbox\tsplitnext            \emptyvbox
   \setbox\tsplittail            \emptyvbox
   \let   \tsplitbeforeresult    \donothing
   \let   \tsplitafterresult     \donothing
   \let   \tsplitinbetween       \donothing
   \let   \tsplitbefore          \donothing
   \let   \tsplitafter           \donothing
   \let   \postprocesstsplit     \donothing
\to \everyresettsplit

\unexpanded\def\resettsplit
  {\the\everyresettsplit}

\resettsplit

\def\tsplitdirectwidth{\hsize}

\newconditional\c_tabl_split_done
\newconditional\c_tabl_split_head
\newconditional\c_tabl_split_full

\newdimen      \d_tabl_split_available

\unexpanded\def\handletsplit
  {\page_split_float_check_caption{\wd\tsplitcontent}%
   \global\setfalse\splitfloatfirstdone
   \testpagesync % new, sync, but still tricky
     [\tsplitminimumfreelines]
     [\dimexpr\tsplitminimumfreespace+\extrasplitfloatlines\lineheight\relax]%
   \setbox\scratchbox\vbox{\tsplitinbetween}%
   \edef\tsplitinbetweenheight{\the\htdp\scratchbox}% etex
   \setfalse\c_tabl_split_done
   \doloop\tabl_split_loop_body
   \global\setfalse\usesamefloatnumber % new, prevent next increment
   \global\setfalse\splitfloatfirstdone} % we can use this one for tests

\newconditional\tabl_split_forced_page

\def\tabl_split_loop_body
  {\ifinsidecolumns
     % brrr, assumes empty columns
     \global\setfalse\splitfloatfirstdone
     \d_tabl_split_available\textheight
     \settrue\c_tabl_split_full
   \else
     \ifconditional\splitfloatfirstdone
       \d_tabl_split_available\textheight
       \settrue\c_tabl_split_full
     \else\ifdim\pagegoal<\maxdimen
       \d_tabl_split_available\dimexpr\pagegoal-\pagetotal\relax
       \setfalse\c_tabl_split_full
     \else
       \d_tabl_split_available\textheight
       \settrue\c_tabl_split_full
     \fi\fi
   \fi
   \d_tabl_split_available \dimexpr
      \d_tabl_split_available
     -\tsplitinbetweenheight
     -\tsplitminimumfreespace
     -\extrasplitfloatlines\lineheight
   \relax
   \ifdim\htdp\tsplittail>\zeropoint
     \advance\d_tabl_split_available-\htdp\tsplittail
   \fi
   \setbox\tsplitresult\vbox
     {\ifdim\ht\tsplithead>\zeropoint
        \unvcopy\tsplithead
        \tsplitinbetween
      \fi}%
   \ifconditional\c_tabl_split_done \else
     \ifdim\ht\tsplitnext>\zeropoint
       \setbox\tsplithead\box\tsplitnext
     \fi
   \fi
   \settrue\c_tabl_split_done
   \ifdim\ht\tsplitresult>\zeropoint
     \settrue\c_tabl_split_head  % table head
   \else
     \setfalse\c_tabl_split_head % no tablehead
   \fi
   \splittopskip\zeropoint
   \doloop % inner loop
     {\setbox\scratchbox\vsplit\tsplitcontent to \onepoint % \lineheight
      \setbox\scratchbox\vbox % \vpack
        {\unvbox\scratchbox
         \setbox\scratchbox\vbox % \vpack
           {\splitdiscards
            \ifnum\lastpenalty>-\plustenthousand\else
               % so that \bTR[before=\page] works
               \global\settrue\tabl_split_forced_page
            \fi}}%
      \ifconditional\tabl_split_forced_page
        \global\setfalse\tabl_split_forced_page
        \setbox\tsplitresult\vbox
          {\unvbox\tsplitresult
           \tsplitinbetween
           \unvbox\scratchbox}%
        \exitloop
      \else\ifdim\dimexpr\d_tabl_split_available-\htdp\scratchbox-\htdp\tsplitresult\relax>\zeropoint
        \setbox\tsplitresult\vbox
          {\unvbox\tsplitresult
           \tsplitinbetween
           \unvbox\scratchbox}%
        \ifvoid\tsplitcontent \exitloop \fi
      \else\ifconditional\c_tabl_split_head
        % we only have a tablehead so far
        \setbox\tsplitresult\vbox{\unvbox\tsplitresult\unvbox\scratchbox}% \vpack
        \exitloop
      \else\ifconditional\c_tabl_split_full
        % we have text height available, but the (one) cell is too
        % large to fit, so, in order to avoid loops/deadcycles we do:
        \setbox\tsplitresult\vbox
          {\unvbox\tsplitresult
           \tsplitinbetween
           \unvbox\scratchbox}%
        \exitloop
      \else
        \setbox\tsplitcontent\vbox
          {\unvbox\scratchbox
           \tsplitinbetween
           \ifvoid\tsplitcontent\else\unvbox\tsplitcontent\fi}%
        \exitloop
      \fi\fi\fi\fi
      \setfalse\c_tabl_split_head
      \setfalse\c_tabl_split_full}%
   \postprocesstsplit
   \page_split_float_check_content\tsplitcontent
   \ifvoid\tsplitcontent
     \setbox\tsplitresult\vbox
       {\unvbox\tsplitresult
        \tsplitinbetween
        \unvcopy\tsplittail}%
     \page_split_float_process{\tsplitbeforeresult\box\tsplitresult\tsplitafterresult}%
     \doifnotinsidesplitfloat\tsplitafter
     \endgraf
     \exitloop
   \else
     % hack
     \ifdim\pagegoal<\maxdimen
       \pagegoal\dimexpr\pagegoal+\lineheight\relax % etex
     \fi
     % brrr
     \ifdim\ht\tsplitresult>\zeropoint
       \setbox\tsplitresult\vbox
         {\unvbox\tsplitresult
          \tsplitinbetween
          \unvcopy\tsplittail}%
       \page_split_float_process{\tsplitbeforeresult\box\tsplitresult\tsplitafterresult}%
       \doifnotinsidesplitfloat\tsplitafter
       \endgraf
       \global\settrue\usesamefloatnumber % new, prevent next increment
     \fi
     \ifinsidecolumns
       \goodbreak % was \doifnotinsidesplitfloat\goodbreak
     \else
       \page      % was \doifnotinsidesplitfloat\page
     \fi
   \fi}

%D The next one assumes that the split takes place elsewhere. This is
%D used in xtables.

\let\resetdirecttsplit\resettsplit

\unexpanded\def\handledirecttsplit
  {\page_split_float_check_caption{\tsplitdirectwidth}%
   \global\setfalse\splitfloatfirstdone
   \testpagesync % new, sync, but still tricky
     [\tsplitminimumfreelines]
     [\dimexpr\tsplitminimumfreespace+\extrasplitfloatlines\lineheight\relax]%
   \doloop\tabl_split_direct_loop_body
   \global\setfalse\usesamefloatnumber   % new, prevent next increment
   \global\setfalse\splitfloatfirstdone} % we can use this one for tests

\def\tabl_split_direct_loop_body
  {\ifinsidecolumns
     \global\setfalse\splitfloatfirstdone
     \d_tabl_split_available\textheight
   \else\ifconditional\splitfloatfirstdone
     \d_tabl_split_available\textheight
   \else\ifdim\pagegoal<\maxdimen
     \d_tabl_split_available\dimexpr\pagegoal-\pagetotal\relax
   \else
     \d_tabl_split_available\textheight
   \fi\fi\fi
   \d_tabl_split_available\dimexpr
      \d_tabl_split_available
     -\tsplitminimumfreespace
     -\extrasplitfloatlines\lineheight
   \relax
   \tsplitdirectsplitter\d_tabl_split_available % also sets state
   \ifdim\ht\tsplitresult>\zeropoint
     \ifconditional\somenextsplitofffloat
       \global\setfalse\onlyonesplitofffloat
     \fi
     \ifdim\pagegoal<\maxdimen
       \pagegoal\dimexpr\pagegoal+\lineheight\relax % etex
     \fi
     \page_split_float_process{\tsplitbeforeresult\box\tsplitresult\tsplitafterresult}%
     \global\settrue\usesamefloatnumber % new, prevent next increment
     \endgraf
     \ifconditional\somenextsplitofffloat
       \ifinsidecolumns
         \goodbreak
       \else
         \page
       \fi
     \fi
     \global\settrue\splitfloatfirstdone
   \else\ifconditional\somenextsplitofffloat
     \ifinsidecolumns
       \goodbreak
     \else
       \page % no room
     \fi
   \else
      \exitloop
   \fi\fi}

%D Maybe handy:
%D
%D \starttyping
%D \splitfloat
%D   {\placefigure{some caption}}
%D   {\startsplittext
%D    \typefile[option=TEX,before=,after=]{oeps.tex}
%D    \stopsplittext}
%D \stoptyping

\def\handlesplittext#1%
  {\setbox\tsplitresult\vbox
     {\vsplit\tsplitcontent to \dimexpr#1-\lineheight\relax}}

\unexpanded\def\startsplittext
  {\begingroup
   \resettsplit
   \let\tsplitminimumfreelines\!!zerocount
   \let\tsplitminimumfreespace\!!zeropoint
   \let\extrasplitfloatlines  \!!plusone
   \let\tsplitdirectsplitter  \handlesplittext
   \setbox\tsplitcontent\vbox\bgroup
   \insidefloattrue}

\unexpanded\def\stopsplittext
  {\egroup
   \handledirecttsplit
   \endgroup}

\protect \endinput

% test cases

% \setupTABLE[split=repeat]
%
% \input tufte \endgraf
% \splitfloat[lines=11]
%   {\placetable{\dorecurse{10}{test\recurselevel\endgraf}}}
%   {\bTABLE\dorecurse{100}{\bTR \bTD test \eTD \eTR}\eTABLE}
% \input tufte \page
%
% \input tufte \endgraf
% \splitfloat[lines=0]
%   {}
%   {\bTABLE\dorecurse{100}{\bTR \bTD test \eTD \eTR}\eTABLE}
% \input tufte \endgraf \page
%
% \input tufte \endgraf
% \bTABLE\dorecurse{100}{\bTR \bTD test \eTD \eTR}\eTABLE
% \input tufte \page

% \setuptabulate[split=yes]
%
% \input tufte \endgraf
% \splitfloat[lines=11]
%   {\placetable{\dorecurse{10}{test\recurselevel\endgraf}}}
%   {\starttabulate\dorecurse{200}{\NC test \NC test \NC \NR}\stoptabulate}
% \input tufte \page
%
% \input tufte \endgraf
% \splitfloat[lines=0]
%   {}
%   {\starttabulate\dorecurse{200}{\NC test \NC test \NC \NR}\stoptabulate}
% \input tufte \page
%
% \input tufte \endgraf
% \starttabulate\dorecurse{200}{\NC test \NC test \NC \NR}\stoptabulate
% \input tufte \page

% \setuptables[split=yes]
%
% \newtoks\TestToks
%
% \TestToks\emptytoks
% \appendtoks\starttablehead\to\TestToks
% \dorecurse{3}{\appendtoks\VL head \VL head \VL \SR\to\TestToks}
% \appendtoks\stoptablehead\to\TestToks
% \appendtoks\starttabletail\to\TestToks
% \dorecurse{3}{\appendtoks\VL tail \VL tail \VL \SR\to\TestToks}
% \appendtoks\stoptabletail\to\TestToks
% \appendtoks\starttables[|c|c|]\to\TestToks
% \dorecurse{100}{\appendtoks\VL test \VL test \VL \SR\to\TestToks}
% \appendtoks\stoptables\to\TestToks
%
% \input tufte \endgraf
% \splitfloat[lines=auto] % [lines=11]
%   {\placetable{\dorecurse{10}{test\recurselevel\endgraf}}}
%   {\the\TestToks}
% \input tufte \page
%
% \input tufte \endgraf
% \splitfloat[lines=0]
%   {}
%   {\the\TestToks}
% \input tufte \page
%
% \input tufte \endgraf
% \the\TestToks
% \input tufte \page
%
% multiple floats
%
% \starttext
%   \dorecurse{3}{\input tufte } \endgraf
%   \dorecurse{5}{\placefigure{}{\framed[height=.5\textheight]{}}}
%   \splitfloat[lines=auto,inbetween=]
%     {\placetable{\dorecurse{5}{test\recurselevel\endgraf}}}
%     {\bTABLE[split=yes]
%      \bTR \bTD 11 \eTD \bTD \input tufte \eTD \eTR
%      \bTR \bTD 12 \eTD \bTD \input zapf \eTD \eTR
%      \bTR \bTD 13 \eTD \bTD \input bryson \eTD \eTR
%      \bTR \bTD 14 \eTD \bTD test  \eTD \eTR
%      \bTR \bTD 21 \eTD \bTD \input tufte \eTD \eTR
%      \bTR \bTD 22 \eTD \bTD \input zapf \eTD \eTR
%      \bTR \bTD 23 \eTD \bTD \input bryson \eTD \eTR
%      \bTR \bTD 24 \eTD \bTD test  \eTD \eTR
%      \bTR \bTD 31 \eTD \bTD \input tufte \eTD \eTR
%      \bTR \bTD 32 \eTD \bTD \input zapf \eTD \eTR
%      \bTR \bTD 33 \eTD \bTD \input bryson \eTD \eTR
%      \bTR \bTD 34 \eTD \bTD test  \eTD \eTR
%      \eTABLE}
%   \dorecurse{10}{\input tufte }
% \stoptext