tabl-tsp.mkii / last modification: 2020-01-30 14:15
%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 move here from other places.

\unprotect

% only to be used with single tokens (will be prim)

\ifx\htdp\undefined \def\htdp#1{\dimexpr\ht#1+\dp#1\relax} \fi

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

\definenumber
  [\??si]
  [\c!way=\v!by\v!text,
   \c!conversion=\@@siconversion]

\def\setupfloatsplitting
  {\dodoubleargument\getparameters[\??si]}

\newif\ifinsidesplitfloat % will become chardef

\newtoks \everysplitfloatsetup

\def\splitfloat
  {\dosingleempty\dosplitfloat}

\ifx\floatcaptionsuffix\undefined \else
  \let\floatcaptionsuffix\empty % will become \splitfloatcaptionsuffix
\fi

\def\extrasplitfloatlines{0}

\def\dosplitfloat[#1]#2% nog dubbele refs
  {\bgroup
   \global\setfalse\splitfloatdone
   \aftergroup\checksplitfloat
   \insidefloattrue
   \insidesplitfloattrue
   \getparameters[\??si][#1]%
   \resetnumber[\??si]%
   \def\floatcaptionsuffix{\convertednumber[\??si]}%
   \let\extrasplitfloatlines\@@silines
   \the\everysplitfloatsetup
   \def\splitfloatcommand{#2}%
   \global\settrue \onlyonesplitofffloat
   \global\setfalse\somenextplitofffloat
   \dopushsavedfloats
   \@@sibefore
   \let\next} % \bgroup

\def\checksplitfloat
  {\ifconditional\splitfloatdone\else
      \blank{\tttf \getmessage\m!floatblocks{13}\empty}\blank
      \showmessage\m!floatblocks{13}\empty
   \fi}

\settrue \onlyonesplitofffloat
\setfalse\somenextplitofffloat

%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

\newconditional\splitfloatdone

\def\dodowithsplitofffloat
  {\dowithnextbox
     {\forgetall
      \dontcomplain
      \global\settrue\splitfloatdone
      \chardef\nodelocationmode\zerocount % bypass auto-renumbering
      \incrementnumber[\??si]%
      \ifcase\rawnumber[\??si]\or \ifconditional\onlyonesplitofffloat
        \let\floatcaptionsuffix\empty
      \fi \fi
      \bgroup
      \ifconditional\somenextplitofffloat
        \settrue\retainfloatnumber
\notesenabledfalse % best here, experimental, brrr; test with note in caption
      \else
        \setfalse\retainfloatnumber
      \fi
      \splitfloatcommand{\box\nextbox}%
      \egroup
      \ifconditional\somenextplitofffloat
        \doifelsenothing\@@siinbetween
          {\ifconditional\splitfloatfirstdone\else\page\fi}
          \@@siinbetween
      \else
        \@@siafter
        \dopopsavedfloats
        \doflushsavedfloats
      \fi
      \global\settrue\splitfloatfirstdone}%
   \vbox}

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

\def\dochecksplitofffloat#1% box
  {\ifinsidesplitfloat
     \ifdim\ht#1=\zeropoint
       \global\setfalse\somenextplitofffloat
     \else
       \global\settrue \somenextplitofffloat
       \global\setfalse\onlyonesplitofffloat
     \fi
   \fi}

\def\analyzesplitfloatcaption#1% depends on page-flt
  {\doif\extrasplitfloatlines\v!auto
     {\bgroup
      \settrue\retainfloatnumber
      \chardef\nodelocationmode\zerocount
      \forcelocalfloats
      \setuplocalfloats[\c!before=,\c!after=,\c!inbetween=]%
      \splitfloatcommand{\hbox to \wd#1{\strut}}% dummy line
      \setbox\scratchbox\vbox{\flushlocalfloats}%
      \getnoflines{\ht\scratchbox}%
      \resetlocalfloats
      \advance\noflines\minusone % compensate dummy line
      \expanded{\egroup\noexpand\edef\noexpand\extrasplitfloatlines{\the\noflines}}}}

% \def\analyzesplitfloatcaption#1%
%   {\edef\extrasplitfloatlines{11}}

\def\dowithsplitofffloat % nextbox
  {\ifinsidesplitfloat
     \expandafter\dodowithsplitofffloat
   \else
     \expandafter\nodowithsplitofffloat
   \fi}

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

%D Some defaults:

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

%D Table splitter, on top of previous code:

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

\def\resettsplit{% only \def's starting a a new line are seen by the dep checker
   \def\tsplitminimumfreelines{0}%
   \def\tsplitminimumfreespace{0pt}%
   \setbox\tsplitcontent  \vbox{}%
   \setbox\tsplitresult   \vbox{}%
   \setbox\tsplithead     \vbox{}%
   \setbox\tsplitnext     \vbox{}%
   \setbox\tsplittail     \vbox{}%
   \let\tsplitbeforeresult\donothing
   \let\tsplitafterresult \donothing
   \let\tsplitinbetween   \donothing
   \let\tsplitbefore      \donothing
   \let\tsplitafter       \donothing
   \let\postprocesstsplit \donothing
}

\resettsplit

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

\newconditional\splitfloatfirstdone

\def\handletsplit
  {\analyzesplitfloatcaption\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
   \!!doneafalse
   \doloop
     {\ifinsidecolumns
        % brrr, assumes empty columns
        \global\setfalse\splitfloatfirstdone
        \scratchdimen\textheight
        \!!donectrue
      \else
        \ifconditional\splitfloatfirstdone
          \scratchdimen\textheight
          \!!donectrue
        \else\ifdim\pagegoal<\maxdimen
          \scratchdimen\dimexpr\pagegoal-\pagetotal\relax
          \!!donecfalse
        \else
          \scratchdimen\textheight
          \!!donectrue
        \fi\fi
      \fi
      \scratchdimen\dimexpr\scratchdimen-\tsplitinbetweenheight-\tsplitminimumfreespace-\extrasplitfloatlines\lineheight\relax
      \ifdim\htdp\tsplittail>\zeropoint
        \advance\scratchdimen-\htdp\tsplittail
      \fi
      \setbox\tsplitresult\vbox
        {\ifdim\ht\tsplithead>\zeropoint
           \unvcopy\tsplithead
           \tsplitinbetween
         \fi}%
      \if!!donea\else\ifdim\ht\tsplitnext>\zeropoint
        \setbox\tsplithead\box\tsplitnext
      \fi\fi
      \!!doneatrue
      \ifdim\ht\tsplitresult>\zeropoint
        \!!donedtrue  % table head
      \else
        \!!donedfalse % no tablehead
      \fi
      \splittopskip\zeropoint
      \doloop
        {\setbox\scratchbox\vsplit\tsplitcontent to \onepoint % \lineheight
         \setbox\scratchbox\vbox{\unvbox\scratchbox}%
         \ifdim\dimexpr\scratchdimen-\htdp\scratchbox-\htdp\tsplitresult\relax>\zeropoint
           \setbox\tsplitresult\vbox
             {\unvbox\tsplitresult
              \tsplitinbetween
              \unvbox\scratchbox}%
           \ifvoid\tsplitcontent \exitloop \fi
         \else\if!!doned
           % we only have a tablehead so far
           \setbox\tsplitresult\vbox{\unvbox\tsplitresult\unvbox\scratchbox}%
           \exitloop
         \else\if!!donec
           % 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
         \!!donedfalse
         \!!donecfalse}%
      \postprocesstsplit
      \dochecksplitofffloat\tsplitcontent
      \ifvoid\tsplitcontent
        \setbox\tsplitresult\vbox
          {\unvbox\tsplitresult
           \tsplitinbetween
           \unvcopy\tsplittail}%
        \dowithsplitofffloat{\tsplitbeforeresult\box\tsplitresult\tsplitafterresult}%
        \doifnotinsidesplitfloat\tsplitafter
        \endgraf
        \exitloop
      \else
        % hack
        \ifdim\pagegoal<\maxdimen
          \global\pagegoal\dimexpr\pagegoal+\lineheight\relax % etex
        \fi
        % brrr
        \ifdim\ht\tsplitresult>\zeropoint
          \setbox\tsplitresult\vbox
            {\unvbox\tsplitresult
             \tsplitinbetween
             \unvcopy\tsplittail}%
          \dowithsplitofffloat{\tsplitbeforeresult\box\tsplitresult\tsplitafterresult}%
          \doifnotinsidesplitfloat\tsplitafter
          \endgraf
        \fi
        \ifinsidecolumns
          \doifnotinsidesplitfloat\goodbreak
        \else
          \doifnotinsidesplitfloat\page
        \fi
      \fi}%
   \global\setfalse\splitfloatfirstdone} % we can use this one for tests

\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