page-brk.mkiv / last modification: 2020-01-30 14:16
%D \module
%D   [       file=page-brk,   % moved from page-ini
%D        version=2011.12.07, % 2000.10.20,
%D          title=\CONTEXT\ Page Macros,
%D       subtitle=Breaks,
%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 / Breaks}

\unprotect

\ifdefined\resetcurrentstructuremarkswithpage \else \let\resetcurrentstructuremarkswithpage\relax \fi
\ifdefined\noheaderandfooterlines             \else \let\noheaderandfooterlines            \relax \fi

%D Page breaks.

% \definepagebreak
%   [chapter]
%   [yes,header,right]
%
% \setuphead
%   [chapter]
%   [page=chapter,
%    header=empty,
%    footer=chapter]
%
% \definepagebreak % untested
%   [lastpage]
%   [left,{empty,right},{empty,left}]

% public page handler, beware: definepage already in use (core-ref)
%
% \definepagebreak[instance][forsure]
% \definepagebreak[forsure][yes,+4]

\newconditional\c_page_breaks_enabled   \settrue\c_page_breaks_enabled
\newcount      \c_page_breaks_prevpage

\newtoks\everybeforepagebreak
\newtoks\everyafterpagebreak

\let\page_breaks_current_option \empty
\let\page_breaks_current_options\empty

\installcorenamespace{pagebreakmethod}
\installcorenamespace{pagebreaks}

\def\page_breaks_handle#1%
  {\edef\page_breaks_current_options{#1}% handy for tracing
   \processcommacommand[\page_breaks_current_options]\page_breaks_handle_step}

\def\page_breaks_handle_step#1%
  {\edef\page_breaks_current_option{#1}% can be used in handler
   \ifcsname\??pagebreakmethod\page_breaks_current_option\endcsname
     \lastnamedcs
   \else\ifcsname\??pagebreaks\page_breaks_current_option\endcsname
     \expandafter\page_breaks_handle\lastnamedcs
   \else
     \page_breaks_unknown
   \fi\fi}

\def\page_breaks_handle_direct#1%
 %{\csname\??pagebreakmethod#1\endcsname}
  {\begincsname\??pagebreakmethod#1\endcsname}

\unexpanded\def\installpagebreakmethod#1#2% low level definer
  {\setvalue{\??pagebreakmethod#1}{#2}}

\let\installpagebreakhandler\installpagebreakmethod % will go

\unexpanded\def\definepagebreak
  {\dodoubleargument\page_breaks_define}

\def\page_breaks_define[#1][#2]%
  {\setvalue{\??pagebreaks#1}{#2}}

\unexpanded\def\pagebreak
  {\par % else no vertical penalties
   \dosingleempty\page_breaks_process}

\let\page\pagebreak

\appendtoks
    \flushnotes
\to \everybeforepagebreak

\def\page_breaks_process[#1]% so, page ornaments are reset after a pagebreak command, unless set
  {\par % always before group so that we clear hangs etc (as in side floats)
   \ifvmode % extra check
     \begingroup
     \the\everybeforepagebreak
     \c_page_breaks_prevpage\realpageno
     \ifcase\pageornamentstate \or
       % disable reset after shipout
       \global\pageornamentstate\plustwo
     \fi
     \iffirstargument % or if empty i.e. []
       \page_breaks_handle{#1}%
     \else % so, no pagebreak when \pagebreak[] ! ! !
       \page_breaks_handle_direct\v!yes
     \fi
     \relax
     \ifnum\c_page_breaks_prevpage<\realpageno
       \global\pageornamentstate\zerocount
     \fi
     \the\everyafterpagebreak
     \endgroup
   \fi}

\unexpanded\def\usepageparameter#1%
  {\edef\m_page_breaks_asked{#1\c!page}%
   \ifx\m_page_breaks_asked\empty\else
     \firstargumenttrue
     \page_breaks_process[\m_page_breaks_asked]%
   \fi}

\unexpanded\def\dousepageparameter#1%
  {\edef\m_page_breaks_asked{#1}%
   \ifx\m_spac_align_asked\empty\else
     \firstargumenttrue
     \page_breaks_process[\m_page_breaks_asked]%
   \fi}

\unexpanded\def\resetpagebreak % used elsewhere too
  {\global\settrue\c_page_breaks_enabled}

\unexpanded\def\simplifypagebreak % to be used grouped !
  {\def\page_breaks_process[##1]{\goodbreak}}

\unexpanded\def\disablepagebreaks % to be used grouped !
  {\def\page_breaks_process[##1]{}}

\installpagebreakmethod \s!dummy
  {\page_otr_command_flush_all_floats
   \page_otr_command_next_page
   \page_otr_insert_dummy_page}

\installpagebreakmethod \v!frame
  {\page
   \begingroup
   \showframe
   \page[\v!empty]
   \endgroup}

\unexpanded\def\page_breaks_unknown % how often called ?
  {\doifelseinstring{+}\page_breaks_current_option
     {\page_otr_command_flush_all_floats
      \page_otr_command_next_page
      \dorecurse\page_breaks_current_option\page_otr_insert_dummy_page}
     {\doifelsenumber\page_breaks_current_option
        {\page_otr_command_flush_all_floats
         \page_otr_command_next_page
         \doloop
           {\ifnum\userpageno<\page_breaks_current_option\relax
              \page_otr_insert_dummy_page
            \else
              \exitloop
            \fi}}
        {}}}

\installpagebreakmethod \s!unknown
 {\page_breaks_unknown}

\installpagebreakmethod \s!default
  {} % do nothing if empty

\installpagebreakmethod \v!reset
  {% better not: \global\pageornamentstate\zerocount
   \resetpagebreak}

\installpagebreakmethod \v!disable
  {\global\setfalse\c_page_breaks_enabled}

\installpagebreakmethod \v!yes
  {\ifconditional\c_page_breaks_enabled
     \page_otr_command_flush_all_floats
     \page_otr_command_next_page
     \ifinsidecolumns       % this will move to MUL
       \page_otr_eject_page % otherwise sometimes no change
     \fi
   \fi}

\installpagebreakmethod \v!makeup
  {\ifconditional\c_page_breaks_enabled
     \page_otr_fill_and_eject_page
   \fi}

\installpagebreakmethod \v!blank
  {\ifcase\pageornamentstate
     \global\pageornamentstate\plusone
   \fi}

% also needed: \page \doifoddpageelse\relax{\page[\v!blank,\v!right]

\installpagebreakmethod \v!no
  {\ifconditional\c_page_breaks_enabled
     \dosomebreak\nobreak
   \fi}

\installpagebreakmethod \v!preference
  {\ifconditional\c_page_breaks_enabled
     \ifinsidecolumns % this will move to MUL
       \dosomebreak\goodbreak
     \else
       \testpage[3][\zeropoint]%
     \fi
   \fi}

\installpagebreakmethod \v!bigpreference
  {\ifconditional\c_page_breaks_enabled
     \ifinsidecolumns % this will move to MUL
       \dosomebreak\goodbreak
     \else
       \testpage[5][\zeropoint]%
     \fi
   \fi}

% \installpagebreakmethod \v!empty {} % defined in page-txt.mkiv
% \installpagebreakmethod \v!header{} % defined in page-txt.mkiv
% \installpagebreakmethod \v!footer{} % defined in page-txt.mkiv

\def\page_reset_marks_and_insert_dummy
  {\resetcurrentstructuremarkswithpage\page_otr_insert_dummy_page}

\installpagebreakmethod \v!left
  {\page_otr_command_flush_all_floats
   \page_otr_command_next_page_and_inserts
   \doifbothsidesoverruled\donothing\page_reset_marks_and_insert_dummy\donothing}

\installpagebreakmethod \v!right
  {\page_otr_command_flush_all_floats
   \page_otr_command_next_page_and_inserts
   \doifbothsidesoverruled\donothing\donothing\page_reset_marks_and_insert_dummy}

\installpagebreakmethod \v!even
  {\page
   \doifelseoddpage\page_reset_marks_and_insert_dummy\donothing}

\installpagebreakmethod \v!odd
  {\page
   \doifelseoddpage\donothing\page_reset_marks_and_insert_dummy}

% \installpagebreakmethod \v!quadruple % not yet ok inside columnsets
%   {\ifdoublesided
%      \ifnum\numexpr\realpageno/\plusfour\relax=\numexpr\realpageno/\plustwo\relax\else
%        \page_breaks_handle_direct\v!yes
%        \page_breaks_handle_direct\v!empty
%        \page_breaks_handle_direct\v!empty
%      \fi
%    \fi}

\installpagebreakmethod \v!quadruple % not yet ok inside columnsets
  {\ifdoublesided
     \ifcase\modulonumber\plusfour\realpageno\else
       \page_breaks_handle_direct\v!yes
       \doloop
         {\ifcase\modulonumber\plusfour\realpageno\relax
            \exitloop
          \else
            \page_breaks_handle_direct\v!empty
          \fi}%
     \fi
   \fi}

\installpagebreakmethod \v!last
  {\page_otr_command_flush_all_floats
   \page_otr_command_next_page_and_inserts
   \relax
   \doifbothsidesoverruled
     \page_facings_flush % hm
     \donothing
     {\noheaderandfooterlines
      \page_otr_insert_dummy_page}%
   \filluparrangedpages}

\installpagebreakmethod \v!lastpage % handy for backpage preceded by empty pages
  {\page_breaks_handle_direct\v!yes
   \ifdoublesided
     \page_breaks_handle_direct\v!left
     \page_breaks_handle_direct\v!empty
     \page_breaks_handle_direct\v!empty
   \fi}

\installpagebreakmethod \v!start {\global\settrue \c_otr_shipout_enabled}
\installpagebreakmethod \v!stop  {\global\setfalse\c_otr_shipout_enabled}

% Column breaks.

\installcorenamespace{columnbreakmethod}
\installcorenamespace{columnbreaks}

\newtoks\everybeforecolumnbreak
\newtoks\everyaftercolumnbreak
\newtoks\everysynchronizecolumn

\let\page_breaks_columns_current_option \empty
\let\page_breaks_columns_current_options\empty

\def\page_breaks_columns_handle#1%
  {\edef\page_breaks_columns_current_options{#1}%
   \processcommacommand[#1]\page_breaks_columns_handle_step}

\def\page_breaks_columns_handle_step#1%
  {\edef\page_breaks_columns_current_option{#1}%
   \ifcsname\??columnbreakmethod\currentoutputroutine:\page_breaks_columns_current_option\endcsname
     \lastnamedcs
   \else\ifcsname\??columnbreaks\page_breaks_columns_current_option\endcsname
    %\expandafter\csname\page_breaks_columns_handle\??columnbreaks\page_breaks_columns_current_option\endcsname
     \lastnamedcs
   \else\ifcsname\??columnbreakmethod\currentoutputroutine:\s!unknown\endcsname
     \lastnamedcs
   \fi\fi\fi}

\def\page_breaks_columns_handle_direct#1%
 %{\csname\??columnbreakmethod\currentoutputroutine:#1\endcsname}
  {\begincsname\??columnbreakmethod\currentoutputroutine:#1\endcsname}

\unexpanded\def\installcolumnbreakmethod#1#2#3% #1=otr-id #2=tag #3=action
  {\setvalue{\??columnbreakmethod#1:#2}{#3}}

\let\installcolumnbreakhandler\installcolumnbreakmethod % will go

\unexpanded\def\definecolumnbreak
  {\dodoubleargument\page_break_columns_define}

\def\page_break_columns_define[#1][#2]%
  {\setvalue{\??columnbreaks#1}{#2}}

\unexpanded\def\columnbreak
  {\par % else no vertical penalties
   \dosingleempty\page_breaks_columns_process}

\let\column\columnbreak

\def\page_breaks_columns_process[#1]% so, page ornaments are reset after a pagebreak command, unless set
  {\begingroup
   \the\everybeforecolumnbreak
   \iffirstargument
     \page_breaks_columns_handle{#1}%
   \else
     \page_breaks_columns_handle_direct\v!yes
   \fi
   \relax
   \the\everyaftercolumnbreak
   \endgroup
   % outside group e.g. setting hsize
   \the\everysynchronizecolumn}

\appendtoks
    \page_otr_command_set_hsize
\to \everysynchronizecolumn

%D Test page breaks.

% \newdimen   \d_page_tests_test
% \newconstant\c_page_tests_mode

\newconstant\testpagemethod  % old
\newconstant\testpagetrigger % old

% \unexpanded\def\testpage    {\c_page_tests_mode\plusone  \dodoubleempty\page_tests_test} %
% \unexpanded\def\testpageonly{\c_page_tests_mode\plustwo  \dodoubleempty\page_tests_test} % no penalties added to the mvl
% \unexpanded\def\testpagesync{\c_page_tests_mode\plusthree\dodoubleempty\page_tests_test} % force sync
%
% \def\page_tests_test[#1][#2]% don't change, only add more methods
%   {\relax % needed before \if
%    \ifconditional\c_page_breaks_enabled
%      % new from here
%      \ifcase\testpagetrigger
%        \endgraf
%      \or\ifvmode
%        \dosomebreak\allowbreak
%      \else % indeed?
%        \vadjust{\allowbreak}%
%        \endgraf
%      \fi\fi
%      % till here
%      \ifdim\pagegoal<\maxdimen \relax
%        \ifdim\pagetotal<\pagegoal \relax
%          \d_page_tests_test\dimexpr
%             #1\lineheight
%            +\pagetotal
%            \ifdim\lastskip<\parskip+\parskip\fi
%            \ifsecondargument+#2\fi
%          \relax
%          \ifcase\testpagemethod
%            \ifdim\d_page_tests_test>.99\pagegoal
%              \penalty-\plustenthousand
%            \fi
%          \or
%            \ifdim\dimexpr\d_page_tests_test-\pagegoal\relax>-\lineheight
%              \penalty-\plustenthousand
%            \fi
%          \or
%            \getnoflines\pagegoal
%            \ifdim\dimexpr\d_page_tests_test-\noflines\lineheight\relax>-\lineheight
%              \penalty-\plustenthousand
%            \fi
%          \or % same as 0 but more accurate
%            \ifdim\dimexpr\d_page_tests_test-10\scaledpoint\relax>\pagegoal
%              \penalty-\plustenthousand
%            \fi
%          \fi
%        \else\ifnum\c_page_tests_mode=\plusthree
%          \page_tests_flush_so_far
%        \fi\fi
%      \else\ifnum\c_page_tests_mode=\plusone
%        \goodbreak
%      \fi\fi
%    \else
%      \endgraf
%    \fi}
%
% \def\page_tests_flush_so_far
%   {\endgraf
%    \ifdim\pagetotal>\pagegoal
%      \ifdim\dimexpr\pagetotal-\pageshrink\relax>\pagegoal
%        \goodbreak
%      \else
%        \page
%      \fi
%    \fi}

\installcorenamespace {pagechecker}
\installcorenamespace {pagecheckermethod}

\installcommandhandler \??pagechecker {pagechecker} \??pagechecker

\setuppagechecker
  [\c!method=1,
   \c!before=,
   \c!after=,
   \c!inbetween=,
   \c!lines=\plusthree,
   \c!offset=\zeropoint]

\def\page_check_amount
  {\dimexpr
      \pagecheckerparameter\c!lines\lineheight
     +\pagetotal
     \ifdim\lastskip<\parskip+\parskip\fi
     +\pagecheckerparameter\c!offset
   \relax}

\unexpanded\def\checkpage
  {\dodoubleempty\page_check}

\def\page_check[#1][#2]%
  {\relax % needed before \if
   \endgraf
   \triggerpagebuilder
   \relax
   \ifconditional\c_page_breaks_enabled
     \begingroup
     \edef\currentpagechecker{#1}%
     \ifsecondargument\setupcurrentpagechecker[#2]\fi
     \csname\??pagecheckermethod\pagecheckerparameter\c!method\endcsname
     \endgroup
   \fi}

\setvalue{\??pagecheckermethod 0}%
  {\ifdim\pagegoal<\maxdimen \relax
     \ifdim\pagetotal<\pagegoal \relax
       \ifdim\page_check_amount>.99\pagegoal
         \pagecheckerparameter\c!before
         \penalty-\plustenthousand
         \pagecheckerparameter\c!after
       \else
         \pagecheckerparameter\c!inbetween
       \fi
     \else
       \pagecheckerparameter\c!inbetween
     \fi
   \else
     \pagecheckerparameter\c!inbetween
   \fi}

\setvalue{\??pagecheckermethod 1}%
  {\ifdim\pagegoal<\maxdimen \relax
     \ifdim\pagetotal<\pagegoal \relax
       \ifdim\dimexpr\page_check_amount-\pagegoal\relax>-\lineheight
         \pagecheckerparameter\c!before
         \penalty-\plustenthousand
         \pagecheckerparameter\c!after
       \else
         \pagecheckerparameter\c!inbetween
       \fi
     \else
       \pagecheckerparameter\c!inbetween
     \fi
   \else
     \goodbreak
     \pagecheckerparameter\c!inbetween
   \fi}

\setvalue{\??pagecheckermethod 2}%
  {\ifdim\pagegoal<\maxdimen \relax
     \ifdim\pagetotal<\pagegoal \relax
       \getnoflines\pagegoal
       \ifdim\dimexpr\page_check_amount-\noflines\lineheight\relax>-\lineheight
         \pagecheckerparameter\c!before
         \penalty-\plustenthousand
         \pagecheckerparameter\c!after
       \else
         \pagecheckerparameter\c!inbetween
       \fi
     \else
       \pagecheckerparameter\c!inbetween
     \fi
   \else
     \pagecheckerparameter\c!inbetween
   \fi}

\setvalue{\??pagecheckermethod 3}%
  {\ifdim\pagegoal<\maxdimen \relax
     \ifdim\pagetotal<\pagegoal \relax
       \ifdim\dimexpr\page_check_amount-10\scaledpoint\relax>\pagegoal
         \pagecheckerparameter\c!before
         \penalty-\plustenthousand
         \pagecheckerparameter\c!after
       \else
         \pagecheckerparameter\c!inbetween
       \fi
     \else
       \ifdim\pagetotal>\pagegoal
         \ifdim\dimexpr\pagetotal-\pageshrink\relax>\pagegoal
           \goodbreak
           \pagecheckerparameter\c!inbetween
         \else
           \pagecheckerparameter\c!before
           \page
           \pagecheckerparameter\c!after
         \fi
       \else
         \pagecheckerparameter\c!inbetween
       \fi
     \fi
   \else
     \pagecheckerparameter\c!inbetween
   \fi}

\definepagechecker[\s!unknown:0]              [\c!method=0,\c!before=,\c!after=,\c!inbetween=]
\definepagechecker[\s!unknown:1][\s!unknown:0][\c!method=1]
\definepagechecker[\s!unknown:2][\s!unknown:0][\c!method=2]
\definepagechecker[\s!unknown:3][\s!unknown:0][\c!method=3]

\def\page_tests_test_a[#1][#2]{\normalexpanded{\checkpage[\s!unknown:1][\c!lines=#1,\c!offset=\ifsecondargument#2\else\zeropoint\fi]}}
\def\page_tests_test_b[#1][#2]{\normalexpanded{\checkpage[\s!unknown:2][\c!lines=#1,\c!offset=\ifsecondargument#2\else\zeropoint\fi]}}
\def\page_tests_test_c[#1][#2]{\normalexpanded{\checkpage[\s!unknown:3][\c!lines=#1,\c!offset=\ifsecondargument#2\else\zeropoint\fi]}}

\unexpanded\def\testpage    {\dodoubleempty\page_tests_test_a} %
\unexpanded\def\testpageonly{\dodoubleempty\page_tests_test_b} % no penalties added to the mvl
\unexpanded\def\testpagesync{\dodoubleempty\page_tests_test_c} % force sync

%D Test column breaks.

\unexpanded\def\testcolumn
  {\dodoubleempty\page_tests_columns_test}

\def\page_tests_columns_test[#1][#2]%
  {\ifdefined\page_otr_command_test_column
     \ifsecondargument
        \page_otr_command_test_column[#1][#2]%
     \else
        \page_otr_command_test_column[#1][\zeropoint]%
     \fi
   \fi}

\protect \endinput