%D \module
%D   [       file=page-mcl,
%D        version=2020.07.26, % stripped down redone page-mul
%D          title=\CONTEXT\ Page Macros,
%D       subtitle=Multicolumns Limited,
%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 Page Macros / Multicolumns Limited}

\unprotect

%D Columns are kind of hairy in \TEX\ and we would be better if no one needed them.
%D Anyway, we do need some support and no mechanism can serve all. The original
%D multicolumn mechanism of \MKII\ was never fully adapted to \MKIV, where mixed
%D columns and page columns showed up instead. However, for some cases a dumb
%D mechanism makes sense, so again we introduce multi columns, but without float
%D hacks. This one will be optimized for mixed usage as for instance itemize needs.
%D Their main advantage is that they can better deal with notes, which are hairy in
%D themselves. This will (for now) only happen in \LMTX.
%D
%D The code is stripped down ancient \MKII\ code and might become cleaner as it
%D evolves. Macros keep similar names but take a different namespace. The code is
%D not yet perfect wrt spacing!

% \enableexperiments[itemize.columns]
%
% \starttext
%
%     \startmulticolumns
%         \dorecurse{10}{\samplefile{ward}\footnote{note #1}\par}
%     \stopmulticolumns
%
%     \dorecurse{4}{\samplefile{ward}\par}
%
%     \startitemize[packed,columns,two]
%         \dorecurse{100}{\startitem test #1 \footnote{this is a footnote #1}\stopitem}
%     \stopitemize
%
% \stoptext

\ifdefined \startmulticolumns

    % we're testing and don't want to remake the format

\else

\installcorenamespace {multicolumns}

\installframedcommandhandler \??multicolumns {multicolumns} \??multicolumns

\newdimension   \d_page_mcl_available_width
\newdimension   \d_page_mcl_distance
\newdimension   \d_page_mcl_leftskip
\newdimension   \d_page_mcl_rightskip
\newdimension   \d_page_mcl_used_width
\newdimension   \d_page_mcl_temp
\newdimension   \d_page_mcl_saved_pagetotal  % brrr, still needed ?

\newinteger    \c_page_mcl_balance_minimum
\newinteger    \c_page_mcl_n_of_lines

\newbox        \b_page_mcl_preceding
\newdimension  \d_page_mcl_preceding_height
\newconditional\c_page_mcl_preceding_present

\newbox        \b_page_mcl_rest_of_page
\newbox        \b_page_mcl_page

\newconditional\c_page_mcl_reverse
\newconditional\c_page_mcl_balance

\newconstant   \c_page_mcl_routine

\setnewconstant\c_page_mcl_routine_regular   \zerocount
\setnewconstant\c_page_mcl_routine_intercept \plusone
\setnewconstant\c_page_mcl_routine_continue  \plustwo
\setnewconstant\c_page_mcl_routine_balance   \plusthree
\setnewconstant\c_page_mcl_routine_error     \plusfour

\newbox        \b_page_mcl_balance_content
\newconstant   \c_page_mcl_balance_tries_max
\newinteger    \c_page_mcl_balance_tries
\newdimension  \d_page_mcl_balance_target
\newdimension  \d_page_mcl_balance_natural_height
\newdimension  \d_page_mcl_balance_step
\newconditional\c_page_mcl_balance_possible

\c_page_mcl_balance_tries_max 250 % 100 is too small when floats are involved

%def\m_page_mcl_overshoot_ratio{\ifgridsnapping0\else.5\fi}
\def\m_page_mcl_overshoot_ratio{.5}

\fi

\protected\def\page_mcl_command_set_hsize
  {\d_page_mcl_available_width\dimexpr
      \makeupwidth
     -\d_page_mcl_leftskip
     -\d_page_mcl_rightskip
     -\nofcolumns\d_page_mcl_distance
     +\d_page_mcl_distance
   \relax
   \d_page_mcl_used_width\dimexpr
      \d_page_mcl_available_width/\nofcolumns
   \relax
   \textwidth\d_page_mcl_used_width
   \hsize\d_page_mcl_used_width}

\protected\def\page_mcl_set_n_of_lines#1%
  {\d_page_mcl_temp\dimexpr
     +\textheight
      \ifdim\d_page_mcl_preceding_height>\zeropoint -\d_page_mcl_preceding_height \fi
%       \ifdim\ht\b_page_mcl_preceding>\zeropoint -\ht\b_page_mcl_preceding \fi
     -#1%
   \relax
   \getnoflines\d_page_mcl_temp
   \ifnum\layoutlines>\zerocount \ifnum\noflines>\layoutlines
     \noflines\layoutlines
   \fi \fi
   \c_page_mcl_n_of_lines\noflines}


% \protected\def\page_mcl_command_set_vsize
%   {%%\page_one_command_set_vsize % indeed?
%    \page_mcl_set_n_of_lines\zeropoint
%    \d_page_mcl_temp\nofcolumns\dimexpr
%       \c_page_mcl_n_of_lines\openlineheight
%     %      +\m_page_mcl_overshoot_ratio\openlineheight % collect enough data
%    \relax
%    \global\vsize   \d_page_mcl_temp
%    \global\pagegoal\d_page_mcl_temp} % let's do it only here, reports maxdimen anyway

\protected\def\page_mcl_command_set_vsize
  {\global\vsize\dimexpr\nofcolumns\textheight+\nofcolumns\lineheight\relax
   \pagegoal\dimexpr
     \vsize
%   -\d_page_floats_inserted_top    % needs checking
%   -\d_page_floats_inserted_bottom % needs checking
    -\c_page_mix_n_of_columns\insertheights
   \relax}

\protected\def\page_mcl_command_routine
  {\ifcase\c_page_mcl_routine
     \page_one_command_routine
   \or
     \page_mcl_routine_intercept
   \or
     \page_mcl_routine_continue
   \or
     \page_mcl_routine_balance
   \or
     \page_mcl_routine_error
   \fi}

\let\page_mcl_command_package_contents\page_one_command_package_contents

\def\page_mcl_routine_intercept
  {\global\setbox\b_page_mcl_preceding\vbox
     {\page_otr_command_flush_top_insertions
      \unvbox\normalpagebox}}

\def\page_mcl_routine_error
  {\showmessage\m!columns3\empty
   \page_otr_construct_and_shipout\unvbox\normalpagebox\zerocount} % three arguments

\protected\def\page_mcl_initialize_variables
  {\reseteverypar % maybe still freeze ....
   \dontcomplain
   \settopskip
   \setmaxdepth
   \topskip        1\topskip
   \splittopskip    \topskip
   \splitmaxdepth   \maxdepth
   \boxmaxdepth     \maxdepth % dangerous
   \emergencystretch\zeropoint
   \relax}

\def\page_mcl_flush_preceding_normal
  {\unvbox\b_page_mcl_preceding}

\def\page_mcl_flush_preceding_ongrid
  {\scratchdimen\dimexpr
     \d_page_mcl_saved_pagetotal
    -\d_page_mcl_preceding_height
  % -\topskip % no, this is already part of the saved total
   \relax
   \box\b_page_mcl_preceding
   \kern\scratchdimen}

\def\page_mcl_flush_packaged_columns_continued
  {\page_mcl_flush_packaged_columns_indeed
   \box\b_page_mcl_page}

\def\page_mcl_flush_packaged_columns_balanced
  {\bgroup
   \page_mcl_flush_packaged_columns_indeed
   \getnoflines{\htdp\b_page_mcl_page}%
   \ht\b_page_mcl_page\dimexpr
     \noflines\openlineheight
     -\openstrutdepth
     \ifgridsnapping
        % quick hack (at least it works with itemize)
     \else
       -\openlineheight
       +\topskip
     \fi
   \relax
   \dp\b_page_mcl_page\openstrutdepth
   \box\b_page_mcl_page
   \egroup}

\def\page_mcl_synchronize_marks
  {\dohandleallcolumns{\page_marks_synchronize_column\plusone\nofcolumns\mofcolumns\currentcolumnbox}}

\def\page_mcl_flush_packaged_columns_indeed
  {\ifvoid\b_page_mcl_preceding
     \c_page_mcl_preceding_present\conditionalfalse % will be set elsewhere
   \else
     \c_page_mcl_preceding_present\conditionaltrue
     \page_apply_postprocessors_box\b_page_mcl_preceding
   \fi
   \forgetall
   \page_mcl_initialize_variables
   \page_mcl_synchronize_marks
   \setbox\b_page_mcl_page\vpack
     {\ifconditional\c_page_mcl_reverse\reversehpack\else\naturalhpack\fi to \makeupwidth
        {\hskip\ifconditional\c_page_mcl_reverse\d_page_mcl_rightskip\else\d_page_mcl_leftskip\fi\relax
         \dohandleallcolumns
           {\wd\currentcolumnbox\d_page_mcl_used_width
            \setbox\scratchbox\hpack{\strut\box\currentcolumnbox}% hm, why strut
            \anch_mark_column_box\scratchbox\currentcolumn
            \box\scratchbox
            \hfil}%
         \unskip
         \hskip\ifconditional\c_page_mcl_reverse\d_page_mcl_leftskip\else\d_page_mcl_rightskip\fi}}%
   \ifconditional\c_page_mcl_preceding_present
     \c_page_mcl_preceding_present\conditionaltrue
     \ifgridsnapping
       \page_mcl_flush_preceding_ongrid % obey grid settings, force on grid
     \else
       \page_mcl_flush_preceding_normal % ignore grid settings, not on grid
     \fi
   \fi
   \global\d_page_mcl_preceding_height\zeropoint
   \page_otr_command_set_vsize
   \dosomebreak\nobreak % hm, only needed when topstuff
   \ifgridsnapping \else
     \ifconditional\c_page_mcl_preceding_present
       \nointerlineskip
       \vskip\dimexpr\openstrutheight-\topskip\relax
     \fi
   \fi
   \prevdepth\openstrutdepth
   \nointerlineskip
   \dp\b_page_mcl_page\zeropoint}

\def\page_mcl_split_column#1#2% copy or box
  {\global\setbox\currentcolumnbox\vsplit#1 upto #2}

\def\page_mcl_routine_continue
  {\bgroup
   \forgetall
   \page_mcl_initialize_variables
   \settotalinsertionheight
   \page_mcl_set_n_of_lines\totalinsertionheight
   \d_page_mcl_balance_target\c_page_mcl_n_of_lines\openlineheight
   \dohandleallcolumns{\page_mcl_split_column\normalpagebox\d_page_mcl_balance_target}%
   \setbox\b_page_mcl_rest_of_page\vpack{\unvbox\normalpagebox}%
   \dohandleallcolumns
     {\global\setbox\currentcolumnbox\vpack to \d_page_mcl_balance_target
        {\unvbox\currentcolumnbox % wel of niet \unvbox ?
         \vfill}}%
   \setbox\b_page_mcl_preceding\vpack{\page_mcl_flush_packaged_columns_continued}%
   \page_otr_construct_and_shipout\box\b_page_mcl_preceding\zerocount % three arguments
   \page_otr_command_set_hsize
   \page_otr_command_set_vsize
   \unvbox\b_page_mcl_rest_of_page
   \egroup}

\def\page_mcl_routine_balance
  {\bgroup
   % why no \forgetall here
   \page_mcl_initialize_variables
   \widowpenalty\zerocount
   \setbox\b_page_mcl_balance_content\vpack{\unvbox\normalpagebox}%
   \ifdim\ht\b_page_mcl_balance_content>\openlineheight % at least one line
     \ifnum\c_page_mcl_balance_minimum<\plustwo % balance anyway
       \c_page_mcl_balance_possible\conditionaltrue
     \else % check criterium to available lines
       \getnoflines{\ht\b_page_mcl_balance_content}%
       \divideby\noflines \nofcolumns \relax
       \ifnum\noflines<\c_page_mcl_balance_minimum \relax
         \ifdim\dimexpr\ht\b_page_mcl_balance_content+\openlineheight\relax>\makeupheight
           \c_page_mcl_balance_possible\conditionaltrue % column exceeding text height
         \else
           \c_page_mcl_balance_possible\conditionalfalse % it seems to fit
         \fi
       \else
         \c_page_mcl_balance_possible\conditionaltrue % balance indeed
       \fi
     \fi
   \else
     \c_page_mcl_balance_possible\conditionalfalse % balancing does not make sense
   \fi
   \ifconditional\c_page_mcl_balance_possible % start balancing, was: \ifdim\ht\b_page_mcl_balance_content>\openlineheight
     \page_mcl_balance_try_one
     \page_mcl_balance_try_two
   \else
     % a one liner is not properly handled here, so best rewrite the text then
     \showmessage\m!columns{10}\empty
     \global\setbox\firstcolumnbox\vpack{\unvbox\b_page_mcl_balance_content}%
   \fi
   \c_page_mcl_routine\c_page_mcl_routine_error
  %\baselinebottom % forces depth in separation rule
   \page_mcl_flush_packaged_columns_balanced
  %\allowbreak
   \egroup}

% \showmakeup

\def\page_mcl_balance_try_one
  {\d_page_mcl_balance_target\dimexpr\ht\b_page_mcl_balance_content+\topskip-\baselineskip\relax
   \divideby\d_page_mcl_balance_target \nofcolumns
   \vbadness\plustenthousand
   \c_page_mcl_balance_tries\zerocount
   \bgroup
   \ifgridsnapping
     \d_page_mcl_balance_step\lineheight
   \else
     \d_page_mcl_balance_step\spacingfactor\onepoint % rubish
   \fi
   \doloop\page_mcl_balance_try_one_attempt
   \global\setbox\b_page_mcl_rest_of_page\box\voidbox
   \ifnum\c_page_mcl_balance_tries>\c_page_mcl_balance_tries_max\relax
     \showmessage\m!columns7\empty
   \else
     \showmessage\m!columns8{\the\c_page_mcl_balance_tries}%
   \fi
   \egroup}

\def\page_mcl_balance_try_one_attempt
  {\advanceby\c_page_mcl_balance_tries \plusone
   \global\setbox\b_page_mcl_rest_of_page\copy\b_page_mcl_balance_content\relax
   \dohandleallcolumns{\page_mcl_split_column\b_page_mcl_rest_of_page\d_page_mcl_balance_target}%
   \d_page_mcl_balance_natural_height\zeropoint
   \dohandleallcolumns\page_mcl_balance_try_one_attempt_step
\advanceby\d_page_mcl_balance_natural_height-33\scaledpoint % some slack
   \ifnum\c_page_mcl_balance_tries>\c_page_mcl_balance_tries_max\relax
     \exitloop
   \orelse\ifdim\ht\b_page_mcl_rest_of_page>\zeropoint
     \advanceby\d_page_mcl_balance_target\d_page_mcl_balance_step\relax
   \orelse\ifdim\d_page_mcl_balance_natural_height>\ht\firstcolumnbox\relax
     \advanceby\d_page_mcl_balance_target\d_page_mcl_balance_step\relax
   \else
     \exitloop
   \fi}

\def\page_mcl_balance_try_one_attempt_step
  {\ifcase\currentcolumn\or\else
     \ifdim\ht\currentcolumnbox>\d_page_mcl_balance_natural_height\relax
       \d_page_mcl_balance_natural_height\ht\currentcolumnbox
     \fi
   \fi}

\def\page_mcl_balance_try_two % hm ... can probably go
  {\dohandleallcolumnscs\page_mcl_balance_try_two_step}

% \def\page_mcl_balance_try_two_step
%   {\global\setbox\currentcolumnbox\vbox to \ht\firstcolumnbox
%      {\box\currentcolumnbox
%       \vfill}}

\def\page_mcl_balance_try_two_step
  {%\global\setbox\currentcolumnbox\box\currentcolumnbox
   \ht\currentcolumnbox\ht\firstcolumnbox}

\permanent\tolerant\protected\def\startmulticolumns[#S#1]%
  {\bgroup
   \ifinsidecolumns
     \page_mcl_start_nop
   \else
     \setupmulticolumns[#1]%
     \nofcolumns\multicolumnsparameter\c!n\relax
     \ifnum\nofcolumns>\plusone
       \page_mcl_start_yes
       \nofmulticolumns\nofcolumns
     \else
       \page_mcl_start_nop
     \fi
   \fi}

\aliased\let\stopmulticolumns\relax

\def\page_mcl_start_nop
  {\enforced\let\stopmulticolumns\page_mcl_stop_nop}

\permanent\protected\def\page_mcl_stop_nop
  {\egroup}

\protected\def\page_mcl_start_yes
  {\whitespace
   \begingroup
   \enforced\let\stopmulticolumns\page_mcl_stop_indeed
   \global\insidecolumnstrue
   \global\insidemulticolumnstrue
   %
   \d_page_mcl_distance\multicolumnsparameter\c!distance\relax
   %
   \ifcstok{\multicolumnsparameter\c!direction}\v!right
     \c_page_mcl_reverse\conditionalfalse
   \else
     \c_page_mcl_reverse\conditionaltrue
   \fi
   %
   \ifcstok{\multicolumnsparameter\c!balance}\v!yes
     \c_page_mcl_balance\conditionaltrue
   \else
     \c_page_mcl_balance\conditionalfalse
   \fi
   %
   \usealignparameter\multicolumnsparameter
   %
   \edef\p_blank{\multicolumnsparameter\c!blank}%
   \ifempty\p_blank \else
       \setupblank[\p_blank]%
   \fi
   %
   \ifdim\s_spac_whitespace_parskip>\zeropoint\relax
       \setupwhitespace[\p_blank]%
   \fi
   \c_page_mcl_balance_minimum\multicolumnsparameter\c!ntop\relax
   %
   \begingroup
   %
   \d_page_mcl_leftskip \leftskip
   \d_page_mcl_rightskip\rightskip
   \leftskip            \zeroskip
   \rightskip           \zeroskip
   \hangafter           \zerocount
   \hangindent          \zeropoint
   %
   \widowpenalty        \zerocount % will become option
   \clubpenalty         \zerocount % will become option
   %
   \ifdim\dimexpr\pagetotal+\parskip+\openlineheight\relax<\pagegoal
     \allowbreak
   \else
     \break % sometimes fails
   \fi
   \appendtoks
     \topskip1\topskip % best a switch
   \to \everybodyfont
   \expand\everybodyfont  % ugly here
   \saveinterlinespace % ugly here
   %
   \initializecolumns\nofcolumns
   %
   \reseteverypar % todo
   %
   \ifzeropt\pagetotal\else
     \verticalstrut
     \vskip-\struttotal
   \fi
   \global\d_page_mcl_saved_pagetotal\pagetotal
   \setupoutputroutine[\s!multicolumn]%
   \c_page_mcl_routine\c_page_mcl_routine_intercept
   \page_otr_trigger_output_routine
   \global\d_page_mcl_preceding_height\ht\b_page_mcl_preceding
   \c_page_mcl_routine\c_page_mcl_routine_continue
   \page_otr_command_set_hsize
   \page_otr_command_set_vsize}

\permanent\protected\def\page_mcl_stop_indeed
  {\relax
   \synchronizeoutput
   \par
   \ifconditional\c_page_mcl_balance
     \c_page_mcl_routine\c_page_mcl_routine_continue
     \goodbreak
     \c_page_mcl_routine\c_page_mcl_routine_balance
   \else
     \goodbreak
   \fi
   % still the multi column routine
   \page_otr_trigger_output_routine % the prevdepth is important, try e.g. toclist in
   \prevdepth\zeropoint % columns before some noncolumned text text
   %
   \c_page_mcl_routine\c_page_mcl_routine_regular
   %
   \ifvoid\b_page_mcl_preceding\else
     \unvbox\b_page_mcl_preceding
   \fi
   \global\d_page_mcl_preceding_height\zeropoint
   \endgroup % here
   \nofcolumns\plusone
   \nofmulticolumns\plusone
   \page_otr_command_set_vsize
   \dosomebreak\allowbreak
   \page_floats_column_pop_saved
   %
   \global\insidemulticolumnsfalse
   \global\insidecolumnsfalse
   \endgroup
   \egroup}%

\setupmulticolumns
  [\c!n=2,
   \c!ntop=1,
   \c!direction=\v!right,
   \c!distance=1.5\bodyfontsize, % influenced by switching
   \c!balance=\v!yes,
   \c!align={\v!text,\v!tolerant},
   \c!blank={\v!line,\v!fixed}]

\defineoutputroutine
  [\s!multicolumn]
  [\s!page_otr_command_routine         =\page_mcl_command_routine,
   \s!page_otr_command_package_contents=\page_mcl_command_package_contents,
   \s!page_otr_command_set_vsize       =\page_mcl_command_set_vsize,
   \s!page_otr_command_set_hsize       =\page_mcl_command_set_hsize]

\let\strc_itemgroups_start_columns_old\strc_itemgroups_start_columns
\let\strc_itemgroups_stop_columns_old \strc_itemgroups_stop_columns

\def\strc_itemgroups_start_columns_new{\startmulticolumns[\c!n=\itemgroupparameter\c!n]}
\def\strc_itemgroups_stop_columns_new {\stopmulticolumns}

\installtexexperiment
  {itemize.columns}
  {\let\strc_itemgroups_start_columns\strc_itemgroups_start_columns_new
   \let\strc_itemgroups_stop_columns \strc_itemgroups_stop_columns_new}
  {\let\strc_itemgroups_start_columns\strc_itemgroups_start_columns_old
   \let\strc_itemgroups_stop_columns \strc_itemgroups_stop_columns_old}

\protect \endinput

