page-mix.mkiv / last modification: 2019-11-14 17:16
%D \module
%D   [       file=page-mix,
%D        version=2012.07.12,
%D          title=\CONTEXT\ Page Macros,
%D       subtitle=Mixed Columns,
%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 / Mixed Columns}

%D This is a very experimental module. Eventually it will replace the current
%D multi column mechanism (that then will be an instance). The \LUA\ part of
%D the interface will quite probably change so don't use that one directly
%D (yet).

% todo:
%
% consult note class
% notes per page
% notes in each column
% notes in last column
% notes local/global
% top and bottom inserts
% wide floats
% move floats
% offsets (inner ones, so we change the hsize  ... needed with backgrounds
% when no content we currently loose the page

% luatex buglet:
%
% \ctxlua{tex.setbox("global",0,node.hpack(nodes.pool.glyph("a",font.current())))}\box0

\registerctxluafile{page-mix}{}

\unprotect

%D The mixed output routine replaces the traditional multi column handler that
%D started out in \MKII. One of the complications of a routine is that it needs
%D to align nicely when mixed in a single column layout. Instead of using all
%D kind of shift juggling in this mechanism we simply switch to grid mode
%D locally. After all, columns don't look nice when not on a. As the grid
%D snapper in \MKIV\ is more advanced not that much extra code is needed.

%D We use the command handler but the parent settings are not to be changed.
%D Instead we could have used a dedicated root setup, but it's not worth the
%D trouble.

\installcorenamespace{mixedcolumns}

\installframedcommandhandler \??mixedcolumns {mixedcolumns} \??mixedcolumns

% old multicolumns mechanism
%
% \c!ntop=1,
% \c!rule=\v!off, : now separator=rule
% \c!height=,
% \c!blank={\v!line,\v!fixed},
% \c!rulethickness=\linewidth,
% \c!offset=.5\bodyfontsize,

\setupmixedcolumns
  [\c!distance=1.5\bodyfontsize,
   \c!n=\plustwo,
  %\c!align=, % inherit (also replaces tolerance)
  %\c!before=,
  %\c!after=,
  %\c!separator=\v!none,
  %\c!setups=,
  %\c!balance=\v!no,
  %\c!blank={\v!line,\v!fixed}, yes or no
   \c!frame=\v!off,
   \c!strut=\v!no,
   \c!offset=\v!overlay,
   \c!alternative=\v!local,
   \c!maxheight=\textheight,
   \c!maxwidth=\makeupwidth,
   \c!grid=\v!tolerant,
   \c!internalgrid=\v!line,
   \c!step=.25\lineheight, % needs some experimenting
  %\c!splitmethod=\v!fixed, % will be default
   \c!direction=\v!normal, % new (also todo in the new columnsets)
   \c!notes=\v!yes,
   \c!method=\ifinner\s!box\else\s!otr\fi] % automatic as suggested by WS

\let\startmixedcolumns\relax % defined later
\let\stopmixedcolumns \relax % defined later

\appendtoks % could become an option
    \setuevalue{\e!start\currentmixedcolumns}{\startmixedcolumns[\currentmixedcolumns]}%
    \setuevalue{\e!stop \currentmixedcolumns}{\stopmixedcolumns}%
\to \everydefinemixedcolumns

%D In order to avoid a mixup we use quite some local registers.

\newdimen      \d_page_mix_column_width
\newdimen      \d_page_mix_max_height
\newdimen      \d_page_mix_max_width
\newdimen      \d_page_mix_distance
\newcount      \c_page_mix_n_of_columns
\newdimen      \d_page_mix_threshold
\newdimen      \d_page_mix_leftskip
\newdimen      \d_page_mix_rightskip

\newdimen      \d_page_mix_balance_step
\setnewconstant\c_page_mix_balance_cycles   500

\setnewconstant\c_page_mix_break_forced    -123

\newbox        \b_page_mix_preceding
\newdimen      \d_page_mix_preceding_height

\newbox        \b_page_mix_collected

\newconstant   \c_page_mix_routine

\setnewconstant\c_page_mix_routine_regular  \zerocount
\setnewconstant\c_page_mix_routine_intercept\plusone
\setnewconstant\c_page_mix_routine_continue \plustwo
\setnewconstant\c_page_mix_routine_balance  \plusthree
\setnewconstant\c_page_mix_routine_error    \plusfour

\newconditional\c_page_mix_process_notes

%D The main environment is called as follows:
%D
%D \starttyping
%D \startmixedcolumns[instance][settings]
%D \startmixedcolumns[instance]
%D \startmixedcolumns[settings]
%D \stoptyping
%D
%D However, best is not to use this one directly but define an instance and
%D use that one.

% % For the moment only on my machine:
%
% \definemixedcolumns
%   [\v!columns]
%
% \unexpanded\def\setupcolumns
%   {\setupmixedcolumns[\v!columns]}

%D In itemizations we also need columns, so let's define a apecial instance
%D for them. These need to work well in situations like this:
%D
%D \starttyping
%D \input zapf
%D
%D \startnarrower
%D     \startitemize[columns,two,packed][before=,after=]
%D         \dorecurse{10}{\startitem item #1 \stopitem}
%D     \stopitemize
%D \stopnarrower
%D
%D \input zapf
%D
%D \startnarrower
%D     \startitemize[columns,two][before=,after=]
%D         \dorecurse{10}{\startitem item #1 \stopitem}
%D     \stopitemize
%D \stopnarrower
%D
%D \input zapf
%D
%D \startnarrower
%D     \startitemize[columns,two]
%D         \dorecurse{10}{\startitem item #1 \stopitem}
%D     \stopitemize
%D \stopnarrower
%D
%D \input zapf
%D \stoptyping

\ifdefined\s!itemgroupcolumns \else \def\s!itemgroupcolumns{itemgroupcolumns} \fi

\definemixedcolumns
  [\s!itemgroupcolumns]
  [\c!n=\itemgroupparameter\c!n,
   \c!direction=\itemgroupparameter\c!direction,
   \c!separator=\v!none,
   \c!splitmethod=\v!none,
   \c!grid=\v!tolerant,
   \c!internalgrid=\v!halfline, % new, we may still revert to \v!line
   \c!balance=\v!yes,
   \c!notes=\v!no] % kind of hidden

% better

\setupmixedcolumns
  [\s!itemgroupcolumns]
  [\c!splitmethod=\v!fixed,
   \c!grid=\v!yes,
   \c!internalgrid=\v!line]

% even better:

\setupitemgroup
  [\c!grid=\v!tolerant:10] % 10 pct tolerance in columns snapping

\setupmixedcolumns
  [\s!itemgroupcolumns]
  [\c!grid=\itemgroupparameter\c!grid]

% the fast hooks:

\unexpanded\def\strc_itemgroups_start_columns
  {\startmixedcolumns[\s!itemgroupcolumns]} % we could have a fast one

\unexpanded\def\strc_itemgroups_stop_columns
  {\stopmixedcolumns}

%D The mixed output routine can be in different states. First we need to intercept
%D the already present content. This permits mixed single and multi column usage.
%D Then we have the continuous routine, one that intercepts pages in sequence.
%D Finally, when we finish the mixed columns mode, we can (optionally) balance the
%D last page.

\unexpanded\def\page_mix_command_routine
  {\ifcase\c_page_mix_routine
     \page_one_command_routine
   \or
     \page_mix_routine_intercept
   \or
     \page_mix_routine_continue
   \or
     \page_mix_routine_balance
   \or
     \page_mix_routine_error
   \fi}

%D The interceptor is quite simple, at least for the moment.

\def\page_mix_routine_intercept
  {\ifdim\pagetotal>\pagegoal
     % testcase: preceding-001 ... if we don't do this, text can disappear as
     % preceding is overwritten ... needs to be figured out some day
     \page_one_command_routine
   \fi
   \global\setbox\b_page_mix_preceding\vbox % pack ?
     {\forgetall
      \page_otr_command_flush_top_insertions
      \ifdim\htdp\b_page_mix_preceding=\zeropoint \else
        \writestatus\m!columns{preceding error}%
        \unvbox\b_page_mix_preceding
      \fi
      \unvbox\normalpagebox}}

%D The error routine is there but unlikely to be called. It is a left-over from
%D the traditional routine that might come in handy some day.

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

%D Some settings (and actions) depend on the current output routine and setting the
%D hsize and vsize is among them. The calculation of the hsize is done elsewhere.

\unexpanded\def\page_mix_command_set_hsize
  {\hsize\d_page_mix_column_width
   \columnwidth\d_page_mix_column_width}

%D When setting the vsize we make sure that we collect a few more lines than needed
%D so that we have enough to split over the columns. Collecting too much is somewhat
%D tricky as they will spill over to the next page.

\unexpanded\def\page_mix_command_set_vsize
  {\vsize\dimexpr\c_page_mix_n_of_columns\textheight+\c_page_mix_n_of_columns\lineheight\relax
   \pagegoal\vsize}

%D As we use \LUA\ there is the usual amount of tracing at that end. At the tex end
%D we only visualize boxes.

\let\page_mix_hbox\hbox
\let\page_mix_vbox\vbox

\installtextracker
  {mixedcolumns.boxes}
  {\let\page_mix_hbox\ruledhbox
   \let\page_mix_vbox\ruledvbox}
  {\let\page_mix_hbox\hbox
   \let\page_mix_vbox\vbox}

%D We provide a few column break options. Interesting is that while forcing a new
%D column in the traditional mechanism was a pain, here it works quite well.

\installcolumnbreakmethod \s!mixedcolumn \v!preference
  {\goodbreak}

\installcolumnbreakmethod \s!mixedcolumn \v!yes
  {\par
   \penalty\c_page_mix_break_forced\relax}

%D As we operate in grid snapping mode, we use a dedicated macro to enable this
%D mechamism.

\def\page_mix_enable_grid_snapping
  {\edef\p_grid{\mixedcolumnsparameter\c!grid}%
   \ifx\p_grid\empty
     % just follow the default grid settings
   \else
     \gridsnappingtrue
     \setsystemmode\v!grid
     \spac_grids_snap_value_set\p_grid
   \fi}

%D Between columns there is normally just spacing unless one enforces a rule.
%D
%D \starttyping
%D \input zapf
%D
%D \startnarrower
%D   \startmixedcolumns[n=2,background=color,backgroundcolor=red,rulethickness=1mm,rulecolor=green,separator=rule]
%D     \input zapf
%D   \stopmixedcolumns
%D \stopnarrower
%D
%D \input zapf
%D \stoptyping

\installcorenamespace{mixedcolumnsseparator}

\unexpanded\def\installmixedcolumnseparator#1#2%
  {\setvalue{\??mixedcolumnsseparator#1}{#2}}

\installmixedcolumnseparator\v!rule
  {\vrule
     \s!width \mixedcolumnsparameter\c!rulethickness
     \s!height\mixedcolumnseparatorheight
     \s!depth \mixedcolumnseparatordepth
   \relax}

\unexpanded\def\page_mix_command_inject_separator
  {\begingroup
   \setbox\scratchbox\hbox to \zeropoint \bgroup
     \hss
     \starttextproperties
     \usemixedcolumnscolorparameter\c!rulecolor
     \begincsname\??mixedcolumnsseparator\p_separator\endcsname % was \c!rule
     \stoptextproperties
     \hss
   \egroup
   \ht\scratchbox\zeropoint
   \dp\scratchbox\zeropoint
   \hss
   \box\scratchbox
   \hss
   \endgroup}

%D We've now arrived at the real code. The start command mostly sets up the
%D environment and variables that are used in the splitter. One of the last
%D things happening at the start is switching over to the mixed continuous
%D routine.

\installcorenamespace{mixedcolumnsbefore}
\installcorenamespace{mixedcolumnsstart}
\installcorenamespace{mixedcolumnsstop}
\installcorenamespace{mixedcolumnsafter}

%D For practical reasons there is always a first argument needed that
%D indicates the class.
%D
%D \starttyping
%D \startmixedcolumns[n=3,alternative=global]
%D   \dorecurse{200}{Zomaar wat #1 met een footnote\footnote{note #1}. }
%D \stopmixedcolumns
%D \stoptyping

\let\currentmixedcolumnsmethod\empty

\installmacrostack\currentmixedcolumns
\installmacrostack\currentmixedcolumnsmethod

\unexpanded\def\startmixedcolumns
  {\dodoubleempty\page_mix_start_columns}

\def\page_mix_start_columns_checked#1#2%
  {\edef\currentmixedcolumnsmethod{\mixedcolumnsparameter\c!method}%
   \ifx\currentmixedcolumnsmethod\v!box
     \singleexpandafter#1%
   \else\ifinsidecolumns
     \doubleexpandafter#2%
   \else
     \doubleexpandafter#1%
   \fi\fi}

\unexpanded\def\page_mix_start_columns
  {\push_macro_currentmixedcolumns
   \push_macro_currentmixedcolumnsmethod
   \ifsecondargument
     \singleexpandafter\page_mix_start_columns_a
   \else\iffirstargument
     \doubleexpandafter\page_mix_start_columns_b
   \else
     \doubleexpandafter\page_mix_start_columns_c
   \fi\fi}

\def\page_mix_start_columns_a[#1]% [#2]%
  {\edef\currentmixedcolumns{#1}%
   \page_mix_start_columns_checked
     \page_mix_start_columns_a_yes
     \page_mix_start_columns_a_nop}

\def\page_mix_start_columns_a_yes[#1]%
  {\mixedcolumnsparameter\c!before\relax
   \begincsname\??mixedcolumnsbefore\currentmixedcolumnsmethod\endcsname\relax
   \begingroup
   \setupcurrentmixedcolumns[#1]%
   \page_mix_initialize_columns
   \begincsname\??mixedcolumnsstart\currentmixedcolumnsmethod\endcsname
   \let\stopmixedcolumns\page_mix_columns_stop_yes}

\def\page_mix_start_columns_a_nop[#1]%
  {\begingroup
   \let\stopmixedcolumns\page_mix_columns_stop_nop}

\def\page_mix_start_columns_b[#1][#2]%
  {\doifelseassignment{#1}%
     {\let\currentmixedcolumns\empty
      \page_mix_error_b}
     {\edef\currentmixedcolumns{#1}%
      \firstargumentfalse}%
   \page_mix_start_columns_checked
     \page_mix_start_columns_b_yes
     \page_mix_start_columns_b_nop
   [#1]}

\def\page_mix_start_columns_b_yes[#1]%
  {\mixedcolumnsparameter\c!before\relax % so, it doesn't listen to local settings !
   \begincsname\??mixedcolumnsbefore\currentmixedcolumnsmethod\endcsname\relax
   \begingroup
   \iffirstargument
     \setupcurrentmixedcolumns[#1]%
   \fi
   \page_mix_initialize_columns
   \begincsname\??mixedcolumnsstart\currentmixedcolumnsmethod\endcsname % no \relax
   \let\stopmixedcolumns\page_mix_columns_stop_yes}

\def\page_mix_start_columns_b_nop[#1]%
  {\begingroup
   \let\stopmixedcolumns\page_mix_columns_stop_nop}

\def\page_mix_error_b
  {\writestatus\m!columns{best use an instance of mixed columns}}

\def\page_mix_start_columns_c[#1][#2]%
  {\let\currentmixedcolumns\empty
   \page_mix_start_columns_checked
     \page_mix_start_columns_c_yes
     \page_mix_start_columns_c_nop}

\def\page_mix_start_columns_c_yes
  {\mixedcolumnsparameter\c!before\relax
   \begincsname\??mixedcolumnsbefore\currentmixedcolumnsmethod\endcsname\relax
   \begingroup
   \page_mix_initialize_columns
   \begincsname\??mixedcolumnsstart\currentmixedcolumnsmethod\endcsname
   \let\stopmixedcolumns\page_mix_columns_stop_yes}

\def\page_mix_start_columns_c_nop
  {\begingroup
   \let\stopmixedcolumns\page_mix_columns_stop_nop}

\unexpanded\def\page_mix_fast_columns_start#1%
  {\push_macro_currentmixedcolumns
   \push_macro_currentmixedcolumnsmethod
   \edef\currentmixedcolumns{#1}%
   \edef\currentmixedcolumnsmethod{\mixedcolumnsparameter\c!method}%
   \mixedcolumnsparameter\c!before\relax % so, it doesn't listen to local settings !
   \begincsname\??mixedcolumnsbefore\currentmixedcolumnsmethod\endcsname\relax
   \begingroup
   \page_mix_initialize_columns
   \begincsname\??mixedcolumnsstart\currentmixedcolumnsmethod\endcsname % no \relax
   \let\page_mix_fast_columns_stop\page_mix_columns_stop_yes}

%D When we stop, we switch over to the balancing routine. After we're done we
%D make sure to set the sizes are set, a somewhat redundant action when we
%D already have flushed but better be safe.

\let\page_mix_fast_columns_stop\relax

\newtoks\t_page_mix_at_the_end

\def\page_mix_finalize_columns
  {\ifconditional\c_page_mix_process_notes \else
     \global\t_page_mix_at_the_end{\stoppostponingnotes}%
   \fi}

\unexpanded\def\page_mix_columns_stop_yes
  {\begincsname\??mixedcolumnsstop\currentmixedcolumnsmethod\endcsname % no \relax
   \page_mix_finalize_columns
   \endgroup
   \begincsname\??mixedcolumnsafter\currentmixedcolumnsmethod\endcsname\relax
   \mixedcolumnsparameter\c!after\relax
   \pop_macro_currentmixedcolumnsmethod
   \pop_macro_currentmixedcolumns
   \the\t_page_mix_at_the_end\global\t_page_mix_at_the_end\emptytoks}

\unexpanded\def\page_mix_columns_stop_nop
  {\page_mix_finalize_columns
   \endgroup
   \pop_macro_currentmixedcolumnsmethod
   \pop_macro_currentmixedcolumns
   \the\t_page_mix_at_the_end\global\t_page_mix_at_the_end\emptytoks}

% \unexpanded\def\page_mix_columns_stop_yes
%   {\begincsname\??mixedcolumnsstop \currentmixedcolumnsmethod\endcsname % no \relax
%    \endgroup
%    \begincsname\??mixedcolumnsafter\currentmixedcolumnsmethod\endcsname\relax
%    \mixedcolumnsparameter\c!after\relax
% \ifx\currentmixedcolumnsmethod\s!otr
%    \pop_macro_currentmixedcolumnsmethod
%    \pop_macro_currentmixedcolumns
%    \synchronizeoutput % brrr, otherwise sometimes issues in itemize
% \else
%    \pop_macro_currentmixedcolumnsmethod
%    \pop_macro_currentmixedcolumns
% \fi
%    }

%D This is how the fast one is used:

\unexpanded\def\strc_itemgroups_start_columns
  {\page_mix_fast_columns_start\s!itemgroupcolumns}

\unexpanded\def\strc_itemgroups_stop_columns
  {\page_mix_fast_columns_stop} % set by start

% not used nor documented so commented:
%
% \setupmixedcolumns
%   [\s!itemgroupcolumns]
%   [\c!grid=\itemgroupparameter\c!grid]
%
% \setupitemgroup
%   [\c!grid=\v!yes] % we need a value

% better

%D The common initialization:

\def\page_mix_initialize_columns
  {\page_mix_enable_grid_snapping
   %
   \d_page_mix_distance    \mixedcolumnsparameter\c!distance
   \c_page_mix_n_of_columns\mixedcolumnsparameter\c!n
   \d_page_mix_max_height  \mixedcolumnsparameter\c!maxheight
   \d_page_mix_max_width   \mixedcolumnsparameter\c!maxwidth
   \d_page_mix_balance_step\mixedcolumnsparameter\c!step
   %
   \d_page_mix_max_width\dimexpr\d_page_mix_max_width-\leftskip-\rightskip\relax
   \d_page_mix_leftskip \leftskip
   \d_page_mix_rightskip\rightskip
   \leftskip \zeropoint
   \rightskip\zeropoint
   %
   \doifelse{\mixedcolumnsparameter\c!notes}\v!yes\settrue\setfalse\c_page_mix_process_notes
   \ifconditional\c_page_mix_process_notes \else
     \startpostponingnotes
   \fi
   %
   \d_page_mix_threshold\zeropoint
   %
   \d_page_mix_column_width\dimexpr(\d_page_mix_max_width-\d_page_mix_distance*\numexpr(\c_page_mix_n_of_columns-\plusone)\relax)/\c_page_mix_n_of_columns\relax
   %
   \columnwidth   \d_page_mix_column_width
   \columndistance\d_page_mix_distance
   \nofcolumns    \c_page_mix_n_of_columns
   \textwidth     \d_page_mix_column_width % kind of redundant but we had it so ...
   %
   \usemixedcolumnscolorparameter\c!color
   %
   \insidecolumnstrue % new
   %
   \usealignparameter  \mixedcolumnsparameter
   \useblankparameter  \mixedcolumnsparameter
   \useprofileparameter\mixedcolumnsparameter % new
   %
   \nofcolumns\c_page_mix_n_of_columns} % public

%D The otr method related hooks are defined next:

% \setvalue{\??mixedcolumnsbefore\s!otr}%
%   {\par
%    \ifdim\pagetotal=\zeropoint \else
%      \verticalstrut     % probably no longer needed
%      \vskip-\struttotal % probably no longer needed
%    \fi}

\newcount\c_page_mix_otr_nesting

% \setvalue{\??mixedcolumnsbefore\s!otr}%
%   {\par
%    \global\advance\c_page_mix_otr_nesting\plusone
%    \ifcase\c_page_mix_otr_nesting\or
%      \ifdim\pagetotal=\zeropoint \else
%        \obeydepth % we could handle this in pre material
%      \fi
%    \fi}

\setvalue{\??mixedcolumnsbefore\s!otr}%
  {\par
   \global\advance\c_page_mix_otr_nesting\plusone
   \ifcase\c_page_mix_otr_nesting\or
     \ifdim\pagetotal=\zeropoint \else
       % make sure that whitespace an dblanks are done
       \strut
       \vskip-\lineheight
      % no, bad spacing: \obeydepth % we could handle this in pre material
     \fi
   \fi}

\setvalue{\??mixedcolumnsstart\s!otr}%
  {\ifcase\c_page_mix_otr_nesting\or
     \scratchwidth\textwidth
     \setupoutputroutine[\s!mixedcolumn]%
     \c_page_mix_routine\c_page_mix_routine_intercept
     \page_otr_trigger_output_routine
     %
     \holdinginserts\maxdimen
     %
     \ifvoid\b_page_mix_preceding \else
       % moved here, before the packaging
       \page_postprocessors_linenumbers_deepbox\b_page_mix_preceding
       % we need to avoid unvboxing with successive balanced on one page
       \global\setbox\b_page_mix_preceding\vpack{\box\b_page_mix_preceding}%
       \wd\b_page_mix_preceding\scratchwidth % \makeupwidth
       \page_grids_add_to_one\b_page_mix_preceding
     \fi
     \global\d_page_mix_preceding_height\ht\b_page_mix_preceding
     \c_page_mix_routine\c_page_mix_routine_continue
     %
     \page_mix_command_set_vsize
     \page_mix_command_set_hsize
   \fi
   \usealignparameter\mixedcolumnsparameter
   \usesetupsparameter\mixedcolumnsparameter}

% \setvalue{\??mixedcolumnsstop\s!otr}%
%   {\par
%    \ifcase\c_page_mix_otr_nesting\or
%      \c_page_mix_routine\c_page_mix_routine_balance
%      \page_otr_trigger_output_routine
%    \fi}

\setvalue{\??mixedcolumnsstop\s!otr}%
  {\par
   \ifcase\c_page_mix_otr_nesting\or
     \doifelse{\mixedcolumnsparameter\c!balance}\v!yes
       {\c_page_mix_routine\c_page_mix_routine_balance}%
       {\penalty-\plustenthousand}% weird hack, we need to trigger the otr sometimes (new per 20140306, see balancing-001.tex)
     \page_otr_trigger_output_routine
     \ifvoid\b_page_mix_preceding \else
        % empty columns so we need to make sure pending content is flushed
        \unvbox\b_page_mix_preceding % new per 2014.10.25
     \fi
   \fi}

\setvalue{\??mixedcolumnsafter\s!otr}%
  {\ifcase\c_page_mix_otr_nesting\or
     \prevdepth\strutdp
     \page_otr_command_set_vsize
     \page_otr_command_set_hsize
   \fi
   \global\advance\c_page_mix_otr_nesting\minusone}

%D The splitting and therefore balancing is done at the \LUA\ end. This gives
%D more readable code and also makes it easier to deal with insertions like
%D footnotes. Eventually we will have multiple strategies available.

\unexpanded\def\page_mix_routine_construct#1%
  {\d_page_mix_max_height\mixedcolumnsparameter\c!maxheight % can have changed due to header=high
   \ifconditional\c_page_mix_process_notes
     \totalnoteheight\zeropoint
   \else
     \settotalinsertionheight
   \fi
   \clf_mixsetsplit
       box          \b_page_mix_collected
       nofcolumns   \c_page_mix_n_of_columns
       maxheight    \d_page_mix_max_height
       noteheight   \totalnoteheight
       step         \d_page_mix_balance_step
       cycles       \c_page_mix_balance_cycles
       preheight    \d_page_mix_preceding_height
       prebox       \b_page_mix_preceding
       strutht      \strutht
       strutdp      \strutdp
       threshold    \d_page_mix_threshold
       splitmethod  {\mixedcolumnsparameter\c!splitmethod}%
       balance      {#1}%
       alternative  {\mixedcolumnsparameter\c!alternative}%
       internalgrid {\mixedcolumnsparameter\c!internalgrid}%
       grid         \ifgridsnapping tru\else fals\fi e %
       notes        \ifconditional\c_page_mix_process_notes tru\else fals\fi e %
   \relax
   \deadcycles\zerocount}

\newdimen\mixedcolumnseparatorheight
\newdimen\mixedcolumnseparatordepth
\newdimen\mixedcolumnseparatorwidth

\def\page_mix_routine_package_step
  {% needs packaging anyway
   \setbox\scratchbox\page_mix_command_package_column
   \page_lines_add_numbers_to_box\scratchbox\recurselevel\c_page_mix_n_of_columns\plusone % new
   \page_marks_synchronize_column\plusone\c_page_mix_n_of_columns\recurselevel\scratchbox
   % backgrounds
   \anch_mark_column_box\scratchbox\recurselevel
   % for the moment a quick and dirty patch .. we need to go into the box (hence the \plusone) .. a slowdowner
   % moved to start: \page_lines_add_numbers_to_box\scratchbox\recurselevel\c_page_mix_n_of_columns\plusone
   % the framed needs a reset of strut, align, setups etc
   \mixedcolumnseparatorheight\ht\scratchbox
   \mixedcolumnseparatordepth \dp\scratchbox
   \inheritedmixedcolumnsframedbox\currentmixedcolumns\scratchbox}

\def\page_mix_routine_package_separate
  {\ifcsname\??mixedcolumnsseparator\p_separator\endcsname
     \page_mix_command_inject_separator
   \else
     \hss
   \fi}

\unexpanded\def\page_mix_routine_package
  {\clf_mixfinalize
   \setbox\b_page_mix_collected\vbox \bgroup
     \ifvoid\b_page_mix_preceding \else
     % \page_postprocessors_linenumbers_deepbox\b_page_mix_preceding % already done
       \vpack\bgroup
         \box\b_page_mix_preceding
       \egroup
       \global\d_page_mix_preceding_height\zeropoint
       \nointerlineskip
       % no no:
       % \prevdepth\strutdepth
     \fi
     \hskip\d_page_mix_leftskip
     \page_mix_hbox to \d_page_mix_max_width \bgroup
       \edef\p_separator{\mixedcolumnsparameter\c!separator}%
       \mixedcolumnseparatorwidth\d_page_mix_distance % \mixedcolumnsparameter\c!rulethickness\relax
       \edef\p_direction{\mixedcolumnsparameter\c!direction}%
       \ifx\p_direction\v!reverse
         \dostepwiserecurse\c_page_mix_n_of_columns\plusone\minusone
            {\page_mix_routine_package_step
             \ifnum\recurselevel>\plusone
               \page_mix_routine_package_separate
             \fi}%
       \else
         \dorecurse\c_page_mix_n_of_columns
            {\page_mix_routine_package_step
             \ifnum\recurselevel<\c_page_mix_n_of_columns
               \page_mix_routine_package_separate
             \fi}%
       \fi
     \egroup
     \hskip\d_page_mix_rightskip
   \egroup
   \wd\b_page_mix_collected\dimexpr
     \d_page_mix_max_width
    +\d_page_mix_rightskip
    +\d_page_mix_leftskip
   \relax }

\unexpanded\def\page_mix_command_package_column
  {\page_mix_hbox to \d_page_mix_column_width \bgroup
     % maybe intercept empty
     \clf_mixgetsplit\recurselevel\relax
     \hskip-\d_page_mix_column_width
     \vbox \bgroup
       \hsize\d_page_mix_column_width
       \ifconditional\c_page_mix_process_notes
         \placenoteinserts
       \fi
     \egroup
     \hss
   \egroup}

% \unexpanded\def\page_mix_command_package_column
%   {\page_mix_hbox to \d_page_mix_column_width \bgroup
%      % maybe intercept empty
%      \ruledhpack\bgroup
%        \clf_mixgetsplit\recurselevel\relax
%      \egroup
%      \hskip-\d_page_mix_column_width
%      \ruledhpack \bgroup
%        \hsize\d_page_mix_column_width
%        \ifconditional\c_page_mix_process_notes
%          \placenoteinserts
%        \fi
%      \egroup
%      \hss
%    \egroup}


\unexpanded\def\page_mix_routine_continue
  {\bgroup
   \forgetall
   \dontcomplain
   \setbox\b_page_mix_collected\vpack{\unvbox\normalpagebox}% brrr we need to make a tight box (combine this in lua)
   \page_mix_routine_construct\v!no
   \page_mix_routine_package
   \page_otr_construct_and_shipout\box\b_page_mix_collected\zerocount % three arguments
   \clf_mixflushrest
   \clf_mixcleanup
   \egroup}

\unexpanded\def\page_mix_routine_balance
  {\bgroup
   \forgetall
   \dontcomplain
   \setbox\b_page_mix_collected\vpack{\unvbox\normalpagebox}% brrr we need to make a tight box (combine this in lua)
   \doloop
     {%writestatus\m!columns{construct continue (\the\htdp\b_page_mix_collected)}%
      \page_mix_routine_construct\v!no
      \ifcase\clf_mixstate\relax
        % 0 = okay, we can balance
        \setbox\b_page_mix_collected\vpack{\clf_mixflushlist}% we could avoid this
        %writestatus\m!columns{construct balance}%
        \page_mix_routine_construct\v!yes
        \page_mix_routine_package
        \c_page_mix_routine\c_page_mix_routine_regular
      % \setupoutputroutine[\s!singlecolumn]%
        \page_otr_command_set_vsize
        \page_otr_command_set_hsize
        \par
        %writestatus\m!columns{flush balance}%
        \page_grids_add_to_mix\b_page_mix_collected % no linenumbers here
        \box\b_page_mix_collected
        \vskip\zeropoint % triggers recalculation of page stuff (weird that this is needed but it *is* needed, see mixed-001.tex)
        \par
        \nointerlineskip
        \prevdepth\strutdp
        \clf_mixflushrest% rubish
        \clf_mixcleanup  % rubish
        \exitloop
      \or
        % 1 = we have stuff left, so flush and rebalance
        %writestatus\m!columns{flush continue}%
        \page_mix_routine_package
        \page_otr_construct_and_shipout\box\b_page_mix_collected\zerocount % three arguments
        \setbox\b_page_mix_collected\vpack{\clf_mixflushrest}% we could avoid this
        \clf_mixcleanup
        \ifdim\ht\b_page_mix_collected=\zeropoint
            \exitloop
        \fi
      \fi}%
   \egroup}

%D We also implement a variant compatible with the so called simple columns
%D mechanism:
%D
%D \starttyping
%D \startboxedcolumns
%D   \input zapf
%D \stopboxedcolumns
%D \stoptyping
%D
%D This is a rather mininimalistic variant.

% Maybe we also need a variant with obeydepth before and prevdepth after so
% that we get a nice spacing.

\definemixedcolumns
  [boxedcolumns]
  [\c!balance=\v!yes,
   \c!n=2,
   \c!method=\s!box,
   \c!strut=\v!yes,
   \c!maxwidth=\availablehsize]

%D Boxed columns can be used nested:
%D
%D \starttyping
%D \setupmixedcolumns
%D   [boxedcolumns]
%D   [n=2,
%D    background=color,
%D    backgroundcolor=darkred,
%D    color=white,
%D    backgroundoffset=1mm]
%D
%D \definemixedcolumns
%D   [nestedboxedcolumns]
%D   [boxedcolumns]
%D   [n=2,
%D    background=color,
%D    backgroundcolor=white,
%D    color=darkred,
%D    strut=yes,
%D    backgroundoffset=0mm]
%D
%D \startboxedcolumns
%D     \input zapf \par \input ward \par \obeydepth
%D     \startnestedboxedcolumns
%D         \input zapf
%D     \stopnestedboxedcolumns
%D     \par \input zapf \par \obeydepth
%D     \startnestedboxedcolumns
%D         \input zapf
%D     \stopnestedboxedcolumns
%D     \par \input zapf
%D \stopboxedcolumns
%D \stoptyping

%D Next we define the hooks:

\letvalue{\??mixedcolumnsbefore\s!box}\donothing
\letvalue{\??mixedcolumnsafter \s!box}\donothing

\setvalue{\??mixedcolumnsstart\s!box}%
  {\edef\p_page_mix_strut{\mixedcolumnsparameter\c!strut}%
   \setbox\b_page_mix_collected\vbox \bgroup
     \let\currentoutputroutine\s!mixedcolumn % makes \column work
     \forgetall
     \usegridparameter\mixedcolumnsparameter
   % \useprofileparameter\mixedcolumnsparameter
     \page_mix_command_set_hsize
     \ifx\p_page_mix_strut\v!yes
       \begstrut
       \ignorespaces
     \fi}

\setvalue{\??mixedcolumnsstop\s!box}%
  {\ifx\p_page_mix_strut\v!yes
     \removeunwantedspaces
     \endstrut
   \fi
   \egroup
   \edef\p_profile{\mixedcolumnsparameter\c!profile}%
   \ifx\p_profile\empty \else
      % this can never be ok because we cheat with depth and height
      % and glue in between and when we're too large we run into issues
      % so mayb best limit correction to one line
      \profilegivenbox\p_profile\b_page_mix_collected
      \setbox\b_page_mix_collected\vpack{\unvbox\b_page_mix_collected}%
      % tracing
      % \addprofiletobox\b_page_mix_collected
   \fi
   \page_mix_box_balance}

%D The related balancer is only a few lines:

\unexpanded\def\page_mix_box_balance
  {\bgroup
   \dontcomplain
   \page_mix_routine_construct\v!yes
   \page_mix_routine_package
   \dontleavehmode\box\b_page_mix_collected
   \clf_mixflushrest
   \clf_mixcleanup
   \egroup}

%D As usual, floats complicates matters and this is where experimental code
%D starts.

\let\page_mix_command_package_contents\page_one_command_package_contents
\let\page_mix_command_flush_float_box \page_one_command_flush_float_box

\unexpanded\def\page_mix_command_check_if_float_fits
  {\ifpostponecolumnfloats
     \global\setfalse\c_page_floats_room
   \else\ifconditional\c_page_floats_not_permitted
     \global\setfalse\c_page_floats_room
   \else
%        \bgroup
%        \getcolumnstatus{\count255}{\dimen0}{\dimen2}%
%        \page_floats_get_info\s!text
%        \setbox\scratchbox\vbox % tricky met objecten ?
%          {\blank[\rootfloatparameter\c!spacebefore]
%           \snaptogrid\vbox{\vskip\floatheight}}% copy?
%        \advance\dimen0\dimexpr\ht\scratchbox+2\openlineheight+.5\lineheight\relax\relax % needed because goal a bit higher
%        \ifdim\dimen0>\dimen2
%          \global\setfalse\c_page_floats_room
%    \else
      \global\settrue\c_page_floats_room
   \fi\fi
   \ifdim\floatwidth>\hsize
     \showmessage\m!columns{11}\empty
     \global\setfalse\c_page_floats_room
   \fi}

\unexpanded\def\page_mix_command_flush_floats
  {\page_one_command_flush_floats}

\unexpanded\def\page_mix_command_flush_saved_floats
  {\page_one_command_flush_saved_floats}

% \unexpanded\def\page_mix_command_flush_top_insertions
%   {\page_one_command_flush_top_insertions}

\unexpanded\def\page_mix_place_float_top
  {\showmessage\m!columns4\empty\page_one_place_float_here}

\unexpanded\def\page_mix_place_float_bottom
  {\showmessage\m!columns5\empty\page_one_place_float_here}

\unexpanded\def\page_mix_place_float_here
  {\page_one_place_float_here}

\unexpanded\def\page_mix_place_float_force
  {\page_one_place_float_force}

\unexpanded\def\page_mix_command_side_float_output
  {\page_otr_construct_and_shipout\unvbox\normalpagebox\zerocount} % three arguments

\unexpanded\def\page_mix_command_synchronize_side_floats
  {\page_sides_forget_floats}

\unexpanded\def\page_mix_command_flush_side_floats
  {\page_sides_forget_floats}

\unexpanded\def\page_mix_command_next_page
  {\page_otr_eject_page}

\unexpanded\def\page_mix_command_next_page_and_inserts
  {\page_otr_eject_page_and_flush_inserts}

%D Moved here and dedicated:

\unexpanded\def\page_mix_command_test_column
  {\dodoubleempty\page_mix_command_test_column_indeed}

\unexpanded\def\page_mix_command_test_column_indeed[#1][#2]% works on last column
  {\par
   \begingroup
   \scratchdimen\dimexpr#1\lineheight\ifsecondargument+#2\fi\relax
   \ifdim\scratchdimen>\zeropoint
     \attribute\checkedbreakattribute\number\scratchdimen
     \penalty\c_page_mix_break_forced\relax
   \fi
   \endgroup}

%D We need to hook some handlers into the output routine and we define
%D a dedicated one:

\let\page_mix_command_flush_all_floats\page_one_command_flush_all_floats

\defineoutputroutine
  [\s!mixedcolumn]
  [\s!page_otr_command_routine                =\page_mix_command_routine,
   \s!page_otr_command_package_contents       =\page_mix_command_package_contents,
   \s!page_otr_command_set_vsize              =\page_mix_command_set_vsize,
   \s!page_otr_command_set_hsize              =\page_mix_command_set_hsize,
 % \s!page_otr_command_synchronize_hsize      =\page_mix_command_synchronize_hsize,
   \s!page_otr_command_next_page              =\page_mix_command_next_page,
   \s!page_otr_command_next_page_and_inserts  =\page_mix_command_next_page_and_inserts,
 % \s!page_otr_command_set_top_insertions     =\page_mix_command_set_top_insertions,
 % \s!page_otr_command_set_bottom_insertions  =\page_mix_command_set_bottom_insertions,
 % \s!page_otr_command_flush_top_insertions   =\page_mix_command_flush_top_insertions,
 % \s!page_otr_command_flush_bottom_insertions=\page_mix_command_flush_bottom_insertions,
   \s!page_otr_command_check_if_float_fits    =\page_mix_command_check_if_float_fits,
 % \s!page_otr_command_set_float_hsize        =\page_mix_command_set_float_hsize,
   \s!page_otr_command_flush_float_box        =\page_mix_command_flush_float_box,
   \s!page_otr_command_side_float_output      =\page_mix_command_side_float_output,
   \s!page_otr_command_synchronize_side_floats=\page_mix_command_synchronize_side_floats,
   \s!page_otr_command_flush_floats           =\page_mix_command_flush_floats,
   \s!page_otr_command_flush_side_floats      =\page_mix_command_flush_side_floats,
   \s!page_otr_command_flush_saved_floats     =\page_mix_command_flush_saved_floats,
   \s!page_otr_command_flush_all_floats       =\page_mix_command_flush_all_floats,
 % \s!page_otr_command_flush_margin_blocks    =\page_mix_command_flush_margin_blocks, % not used
   \s!page_otr_command_test_column            =\page_mix_command_test_column
  ]

%D Only a few float placement options are supported:

\installfloatmethod \s!mixedcolumn  \v!here   \page_mix_place_float_here
\installfloatmethod \s!mixedcolumn  \v!force  \page_mix_place_float_force
\installfloatmethod \s!mixedcolumn  \v!top    \page_mix_place_float_top
\installfloatmethod \s!mixedcolumn  \v!bottom \page_mix_place_float_bottom

\installfloatmethod \s!mixedcolumn  \v!local  \somelocalfloat

%D It ends here.

\protect \endinput