%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 a mkiv cleanup.
%D As it mostly targets at tables the code lives in the tabl and page namespaces.

% work in progress

\unprotect

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

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

%D When \type {inbetween} is made empty instead of the default \type {\page}, we
%D will get delayed flushing 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   \onlyonesplitofffloat\conditionaltrue

\newif   \ifinsidesplitfloat   % will become conditional
\newcount\noffloatssplits
\newtoks \everysplitfloatsetup

\mutable\let\extrasplitfloatlines \!!zerocount
\mutable\let\splitfloatfinalizer  \relax

\mutable\lettonothing\splitfloatcommand
\mutable\lettonothing\floatcaptionsuffix

\permanent\tolerant\protected\def\splitfloat[#S#1]#:#2% nog dubbele refs
  {\bgroup
   \global\splitfloatdone\conditionalfalse
   \aftergroup\page_split_float_check
   \insidefloattrue
   \insidesplitfloattrue
   \setupcurrentfloatsplitting[#1]%
   \global\noffloatssplits\zerocount
   \let\floatcaptionsuffix\page_split_float_suffix
   \edef\extrasplitfloatlines{\floatsplittingparameter\c!lines}%
   \expand\everysplitfloatsetup
   \def\splitfloatcommand{#2}%
   \global\onlyonesplitofffloat\conditionaltrue
   \global\somenextsplitofffloat\conditionalfalse
   \page_floats_push_saved
   \floatsplittingparameter\c!before
   \let\next} % \bgroup

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

\protected\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
   \splitfloatfinalizer} % a weird place (could interfere with flushing)

\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\splitfloatdone\conditionaltrue
 % \nodelocationmode\zerocount % bypass auto-renumbering
   \global\advanceby\noffloatssplits\plusone
   \ifcase\noffloatssplits\relax \or
     \ifconditional\onlyonesplitofffloat
       \lettonothing\floatcaptionsuffix
     \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}%
     \ifempty\p_inbetween
       \ifconditional\splitfloatfirstdone\else\page\fi
     \else
       \p_inbetween
     \fi
   \else
     \floatsplittingparameter\c!after
     \page_floats_pop_saved
     \page_floats_flush_saved
   \fi
   \global\splitfloatfirstdone\conditionaltrue}

\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\splitfloatfirstdone\conditionaltrue}

\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\somenextsplitofffloat\conditionalfalse
     \else
       \global\somenextsplitofffloat\conditionaltrue
       \global\onlyonesplitofffloat\conditionalfalse
     \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
      \advanceby\noflines\minusone % compensate dummy line
      \expanded{\egroup\noexpand\edef\noexpand\extrasplitfloatlines{\the\noflines}}%
      \global\usesamefloatnumber\conditionaltrue
   \orelse\ifchknum\extrasplitfloatlines\else
     \def\extrasplitfloatlines{1}
   \fi}

\permanent\protected\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        \b_split_content
\newbox        \b_split_result    % watch out, semi public, accessed in cs-*
\newbox        \b_split_head
\newbox        \b_split_next
\newbox        \b_split_tail

\newtoks       \t_split_before_result
\newtoks       \t_split_after_result
\newtoks       \t_split_before
\newtoks       \t_split_inbetween
\newtoks       \t_split_after
\newtoks       \t_split_section
\newtoks       \everyresettsplit

\newinteger    \c_split_minimum_free_lines

\newdimension  \d_split_minimum_free_space
\newdimension  \d_split_available_height
\newdimension  \d_split_inbetween_height

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

\newconditional\tabl_split_forced_page

% \permanent\protected\def\lastsplithtdp{\htdp\b_split_result}

\appendtoks
   \c_split_minimum_free_lines\zerocount
   \d_split_minimum_free_space\zeropoint
   \setbox\b_split_content    \emptyvbox
   \setbox\b_split_result     \emptyvbox
   \setbox\b_split_head       \emptyvbox
   \setbox\b_split_next       \emptyvbox
   \setbox\b_split_tail       \emptyvbox
   \t_split_before_result     \emptytoks
   \t_split_after_result      \emptytoks
   \t_split_inbetween         \emptytoks
   \t_split_before            \emptytoks
   \t_split_after             \emptytoks
   \t_split_section           \emptytoks
   \let\postprocesstsplit     \donothing
\to \everyresettsplit

\mutable\let\postprocesstsplit\donothing

\permanent\protected\def\resettsplit
  {\expand\everyresettsplit}

\resettsplit

\mutable\def\tsplitdirectwidth{\hsize}

\permanent\protected\def\handletsplit
  {\page_split_float_check_caption{\wd\b_split_content}%
   \global\splitfloatfirstdone\conditionalfalse
   \testpagesync % new, sync, but still tricky
     [\the\c_split_minimum_free_lines]
     [\dimexpr\d_split_minimum_free_space+\extrasplitfloatlines\lineheight\relax]%
   \setbox\scratchbox\vbox{\expand\t_split_inbetween}%
   \d_split_inbetween_height\htdp\scratchbox
   \c_tabl_split_done\conditionalfalse
%    \localcontrolledrepeating
   \localcontrolledendless
     {\tabl_split_loop_body}%
   \global\usesamefloatnumber\conditionalfalse % new, prevent next increment
   \global\splitfloatfirstdone\conditionalfalse} % we can use this one for tests

\newbox\b_split_result_saved

\def\tabl_split_loop_body
  {\ifinsidecolumns
     % brrr, assumes empty columns
     \global\splitfloatfirstdone\conditionalfalse
     \d_split_available_height\textheight
     \c_tabl_split_full\conditionaltrue
   \else
     \ifconditional\splitfloatfirstdone
       \d_split_available_height\textheight
       \c_tabl_split_full\conditionaltrue
     \orelse\ifdim\pagegoal<\maxdimen
       \d_split_available_height{\pagegoal-\pagetotal}%
       \c_tabl_split_full\conditionalfalse
     \else
       \d_split_available_height\textheight
       \c_tabl_split_full\conditionaltrue
     \fi
   \fi
   \d_split_available_height {%
      \d_split_available_height
     -\d_split_inbetween_height
     -\d_split_minimum_free_space
     -\extrasplitfloatlines\lineheight
   }%
   \ifdim\htdp\b_split_tail>\zeropoint
     \advanceby\d_split_available_height-\htdp\b_split_tail
   \fi
   \setbox\b_split_result\vbox
     {\ifdim\ht\b_split_head>\zeropoint
        \unvcopy\b_split_head
        \expand\t_split_section
        \expand\t_split_inbetween
      \fi}%
   \ifconditional\c_tabl_split_done \else
     \ifdim\ht\b_split_next>\zeropoint
       \setbox\b_split_head\box\b_split_next
     \fi
   \fi
   \c_tabl_split_done\conditionaltrue
   \ifdim\ht\b_split_result>\zeropoint
     \c_tabl_split_head\conditionaltrue  % table head
   \else
     \c_tabl_split_head\conditionalfalse % no tablehead
   \fi
   \splittopskip\zeroskip
   \ifvoid\b_split_result_saved\else
     \setbox\b_split_result\box\b_split_result_saved
     \c_tabl_split_head\conditionaltrue % no tablehead
     \global\tabl_split_forced_page\conditionalfalse
   \fi
   % we get a lot of splittopskips here .. todo: ignore when zero
%    \localcontrolledrepeating
   \localcontrolledendless
     {\setbox\scratchbox\vsplit\b_split_content 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\tabl_split_forced_page\conditionaltrue
            \fi}}%
      \ifconditional\tabl_split_forced_page
        \global\tabl_split_forced_page\conditionalfalse
        \setbox\b_split_result\vbox
          {\unvbox\b_split_result
           \expand\t_split_inbetween
           \unvbox\scratchbox}%
        \quitloop
      \orelse\ifdim{\d_split_available_height-\htdp\scratchbox-\htdp\b_split_result}>\zeropoint
        \setbox\b_split_result\vbox
          {\unvbox\b_split_result
           \expand\t_split_inbetween
           \unvbox\scratchbox}%
        \ifvoid\b_split_content
          \quitloop
        \fi
      \orelse\ifconditional\c_tabl_split_head
        % we only have a tablehead so far
        \global\setbox\b_split_result_saved\vbox{\unvbox\b_split_result\unvbox\scratchbox}% \vpack
        \quitloop
      \orelse\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\b_split_result\vbox
          {\unvbox\b_split_result
           \expand\t_split_inbetween
           \unvbox\scratchbox}%
        \quitloop
      \else
        \setbox\b_split_content\vbox
          {\unvbox\scratchbox
           \expand\t_split_inbetween
           \ifvoid\b_split_content\else\unvbox\b_split_content\fi}%
        \quitloop
      \fi
      \c_tabl_split_head\conditionalfalse
      \c_tabl_split_full\conditionalfalse}%
   \postprocesstsplit
   \page_split_float_check_content\b_split_content
   \ifvoid\b_split_content
     \setbox\b_split_result\vbox
       {\unvbox\b_split_result
        \expand\t_split_inbetween
        \unvcopy\b_split_tail}%
     \page_split_float_process{\expand\t_split_before_result\box\b_split_result\expand\t_split_after_result}%
     \doifnotinsidesplitfloat{\expand\t_split_after}%
     \endgraf
     \quitloop
   \else
     % hack
     \ifdim\pagegoal<\maxdimen
       \pagegoal{\pagegoal+\lineheight}% etex
     \fi
     % brrr
     \ifdim\ht\b_split_result>\zeropoint
       \setbox\b_split_result\vbox
         {\unvbox\b_split_result
          \expand\t_split_inbetween
          \unvcopy\b_split_tail}%
       \page_split_float_process{\expand\t_split_before_result\box\b_split_result\expand\t_split_after_result}%
       \doifnotinsidesplitfloat{\expand\t_split_after}%
       \endgraf
       \global\usesamefloatnumber\conditionaltrue % 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 used in
%D xtables.

\aliased\let\resetdirecttsplit\resettsplit

\mutable\let\tsplitdirectsplitter\relax

\permanent\protected\def\handledirecttsplit
  {\page_split_float_check_caption{\tsplitdirectwidth}%
   \global\splitfloatfirstdone\conditionalfalse
   \testpagesync % new, sync, but still tricky
     [\the\c_split_minimum_free_lines]
     [\dimexpr\d_split_minimum_free_space+\extrasplitfloatlines\lineheight\relax]%
%    \localcontrolledrepeating
   \localcontrolledendless
     {\tabl_split_direct_loop_body}%
   \global\usesamefloatnumber\conditionalfalse   % new, prevent next increment
   \global\splitfloatfirstdone\conditionalfalse} % we can use this one for tests

\newconditional\splitfloatfixedheight

\def\splitfloatheight{\textheight}

\def\tabl_split_direct_loop_body
  {\ifconditional\splitfloatfixedheight
     \d_split_available_height\splitfloatheight
   \orelse\ifinsidecolumns
     \global\splitfloatfirstdone\conditionalfalse
     \d_split_available_height\textheight
   \orelse\ifconditional\splitfloatfirstdone
     \d_split_available_height\textheight
   \orelse\ifdim\pagegoal<\maxdimen
     \d_split_available_height{\pagegoal-\pagetotal}%
   \else
     \d_split_available_height\textheight
   \fi
   \d_split_available_height{%
      \d_split_available_height
     -\d_split_minimum_free_space
     -\extrasplitfloatlines\lineheight
   }%
   \tsplitdirectsplitter\d_split_available_height % also sets state
   \ifdim\ht\b_split_result>\zeropoint
     \ifconditional\somenextsplitofffloat
       \global\onlyonesplitofffloat\conditionalfalse
     \fi
     \ifconditional\splitfloatfixedheight
       % not relevant
     \orelse\ifdim\pagegoal<\maxdimen
       \pagegoal{\pagegoal+\lineheight}%
     \fi
     \page_split_float_process{\expand\t_split_before_result\box\b_split_result\expand\t_split_after_result}%
     \global\usesamefloatnumber\conditionaltrue % new, prevent next increment
     \endgraf
     \ifconditional\somenextsplitofffloat
       \ifconditional\splitfloatfixedheight
         \page
       \orelse\ifinsidecolumns
         \goodbreak
       \else
         \page
       \fi
     \fi
     \global\splitfloatfirstdone\conditionaltrue
   \orelse\ifconditional\somenextsplitofffloat
     \ifconditional\splitfloatfixedheight
       \page
     \orelse\ifinsidecolumns
       \goodbreak
     \else
       \page % no room
     \fi
   \else
     \quitloop
   \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

\permanent\def\handlesplittext#1%
  {\setbox\b_split_result\vbox
     {\vsplit\b_split_content to {#1-\lineheight}}}

\permanent\protected\def\startsplittext
  {\begingroup
   \resettsplit
   \c_split_minimum_free_lines\zerocount
   \d_split_minimum_free_space\zeropoint
   \let\extrasplitfloatlines  \!!plusone
   \let\tsplitdirectsplitter  \handlesplittext
   \setbox\b_split_content\vbox\bgroup
   \insidefloattrue}

\permanent\protected\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
