%D \module
%D   [       file=spac-ver,
%D        version=2009.10.16, % 1997.03.31, was core-spa.tex
%D          title=\CONTEXT\ Spacing Macros,
%D       subtitle=Vertical,
%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 Spacing Macros / Vertical}

\unprotect

\newgluespec\bodyfontlineheight        % why a skip
\newdimen   \bodyfontstrutheight
\newdimen   \bodyfontstrutdepth

\newgluespec\globalbodyfontlineheight  % why a skip
\newdimen   \globalbodyfontstrutheight
\newdimen   \globalbodyfontstrutdepth

\newgluespec \s_spac_vspacing_predefined
\newdimension\d_spac_overlay
\newinteger  \c_spac_vspacing_ignore_parskip

% \overloaded\let\strutht  \undefined \newdimen\strutht   % already defined
% \overloaded\let\strutdp  \undefined \newdimen\strutdp
% \overloaded\let\struthtdp\undefined \newdimen\struthtdp

\registerctxluafile{spac-ver}{autosuffix}

% todo: use usernodes ?

% todo: itemize : intro ... only when there is one or two lines preceding and then
% keep these together i.e. \blank[intro]

% Isn't it about time to get rid of topskip i.e. make it equivalent to
% \openstrutheight so that we can remove delta code.
%
% There might be more namespace protection.

%D There are two ways to influence the interline spacing. The most general and often
%D most consistent way is using
%D
%D \showsetup{setupinterlinespace}
%D
%D For instance
%D
%D \starttyping
%D \setupinterlinespace[line=2.8ex]
%D \stoptyping
%D
%D This setting adapts itself to the bodyfontsize, while for instance saying
%D
%D \starttyping
%D \setupinterlinespace[line=12pt]
%D \stoptyping
%D
%D sets things fixed for all sizes, which is definitely not what we want. Therefore
%D one can also say:
%D
%D \starttyping
%D \definebodyfontenvironment[9pt][interlinespace=11pt]
%D \stoptyping
%D
%D One can still use \type {\setupinterlinespace} (without arguments) to set the
%D interline space according to the current font, e.g. a \type {\bfa}.

% will be cleaned up but it will stay messy because we accept so
% many variants

\newif\iflocalinterlinespace

\newgluespec\s_spac_vspacing_temp \s_spac_vspacing_temp\bigskipamount

\mutable\def\skipfactor    {.75}
\mutable\def\skipgluefactor{.25}

\permanent\def\normalskipamount
  {\openlineheight
     \ifgridsnapping \orelse \ifconditional\c_spac_whitespace_flexible
       \s!plus \skipgluefactor\openlineheight
       \s!minus\skipgluefactor\openlineheight
     \fi
   \relax}

\ifdefined\bodyfontinterlinespace \else
  \lettonothing\bodyfontinterlinespace
\fi

% \permanent\protected\def\presetnormallineheight % each bodyfont
%   {\edef\normallineheight{\interlinespaceparameter\c!line}%
%    \iflocalinterlinespace \else
%      \edef\m_spac_normallineheight{\bodyfontinterlinespace}%
%      \ifempty\m_spac_normallineheight \else
%        \let\normallineheight\m_spac_normallineheight
%      \fi
%    \fi}

\permanent\protected\def\presetnormallineheight % each bodyfont
  {\edef\normallineheight{\interlinespaceparameter\c!line}%
   \iflocalinterlinespace \else
     \edef\m_spac_normallineheight{\bodyfontinterlinespace}%
     \ifempty\m_spac_normallineheight
     \orelse\ifchkdimension\m_spac_normallineheight\or
       \let\normallineheight\m_spac_normallineheight
     \else
       \edef\normallineheight{\todimension\m_spac_normallineheight\dimexpr\fontbody\relax}%
     \fi
   \fi}

\permanent\protected\def\setupspecifiedinterlinespace[#S#1]%
  {\setupcurrentinterlinespace[#1]%
   \spac_linespacing_setup_specified_interline_space}

\def\spac_linespacing_setup_specified_interline_space
  {\edef\strutheightfactor    {\interlinespaceparameter\c!height   }%
   \edef\strutdepthfactor     {\interlinespaceparameter\c!depth    }%
   \edef\minimumstrutheight   {\interlinespaceparameter\c!minheight}%
   \edef\minimumstrutdepth    {\interlinespaceparameter\c!mindepth }%
   \edef\minimumlinedistance  {\interlinespaceparameter\c!distance }%
   \edef\normallineheight     {\interlinespaceparameter\c!line     }%
   \edef\topskipfactor        {\interlinespaceparameter\c!top      }%
   \edef\ntopskipfactor       {\interlinespaceparameter\c!ntop     }%
   \edef\maxdepthfactor       {\interlinespaceparameter\c!bottom   }%
   \edef\m_spac_vertical_baseline_stretch_factor{\interlinespaceparameter\c!stretch}%
   \edef\m_spac_vertical_baseline_shrink_factor {\interlinespaceparameter\c!shrink }%
   % often topskip does more bad than good, so:
   \ifx\topskipfactor\v!height
     \let\topskipfactor\strutheightfactor
   \fi
   \setfontparameters % redundant, can be \setstrut, test first
   \updateraggedskips} % yes indeed

\installcorenamespace{interlinespacerelative}

\aliased\let\setrelativeinterlinespace\relax % used elsewhere

\mutable\lettonothing\currentrelativeinterlinespace

\defcsname\??interlinespacerelative\v!on   \endcsname{\oninterlineskip}
\defcsname\??interlinespacerelative\v!off  \endcsname{\offinterlineskip}
\defcsname\??interlinespacerelative\v!reset\endcsname{\enforced\lettonothing\currentrelativeinterlinespace
                                                      \enforced\let\setrelativeinterlinespace\relax
                                                      \setfontparameters}
\defcsname\??interlinespacerelative\v!auto \endcsname{\enforced\let\setrelativeinterlinespace\spac_linespacing_set_relative_interlinespace}

\def\spac_linespacing_set_specified_relative_interlinespace#1% fragile?
  {\doifelsedimenstring{#1}%
     {\setupspecifiedinterlinespace[\c!line=#1]}%
     {\assignvalue{#1}\currentrelativeinterlinespace{1.00}{1.25}{1.50}%
      \spacing\currentrelativeinterlinespace}}

\permanent\protected\def\setuprelativeinterlinespace[#1]%
  {\processcommalist[#1]\spac_linespacing_setup_relative_interlinespace}

\def\spac_linespacing_setup_relative_interlinespace#1%
  {\ifcsname\??interlinespacerelative#1\endcsname
     \lastnamedcs
   \else
     \spac_linespacing_set_specified_relative_interlinespace{#1}%
   \fi}

\permanent\protected\def\spac_linespacing_set_relative_interlinespace
  {\ifempty\currentrelativeinterlinespace\else
     \spacing\currentrelativeinterlinespace
   \fi}

\protected\def\spac_linespacing_setup_use
  {\ifcsname\namedinterlinespacehash\m_spac_interlinespace\s!parent\endcsname
     \let\currentinterlinespace\m_spac_interlinespace
     \spac_linespacing_setup_specified_interline_space
 % \else
     % we only support named interlinespaces
   \fi}

\permanent\protected\def\useinterlinespaceparameter#1% see footnotes
  {\edef\m_spac_interlinespace{#1\c!interlinespace}%
   \ifempty\m_spac_interlinespace \else
     \spac_linespacing_setup_use
   \fi}

\newtoks\everysetupglobalinterlinespace
\newtoks\everysetuplocalinterlinespace

\newconditional\interlinespaceisset

\installcorenamespace{interlinespace}

\installcommandhandler \??interlinespace {interlinespace} \??interlinespace

\installmacrostack\currentinterlinespace

\permanent\overloaded\tolerant\protected\def\setupinterlinespace[#S#1]%
  {\ifarguments
     \interlinespaceisset\conditionaltrue
     \spac_linespacing_synchronize_local
   \else
     \interlinespaceisset\conditionaltrue
     \ifcsname\namedinterlinespacehash{#1}\s!parent\endcsname
        \cdef\currentinterlinespace{#1}%
        \spac_linespacing_setup_specified_interline_space
       %\dosetupspecifiedinterlinespaceindeed
     \else
       \spac_linespacing_setup_specified_or_relative[#1]%
     \fi
   \fi}

\def\spac_linespacing_setup_specified_or_relative[#S#1]%
  {\doifelseassignment{#1}\setupspecifiedinterlinespace\setuprelativeinterlinespace[#1]%
   \expandtoks\iflocalinterlinespace\everysetuplocalinterlinespace\else\everysetupglobalinterlinespace\fi}

\def\spac_linespacing_synchronize_local % adapts to the font
  {\localinterlinespacetrue
   \setfontparameters
   \updateraggedskips % funny one here
   \expandtoks\everysetuplocalinterlinespace
   \localinterlinespacefalse}

\permanent\protected\def\dosetupcheckedinterlinespace#1% often a chain
  {\edef\p_spac_checked_interlinespace{#1}%
   \ifempty\p_spac_checked_interlinespace
     \spac_linespacing_synchronize_local
   \orelse\ifcsname\namedinterlinespacehash\p_spac_checked_interlinespace\s!parent\endcsname % we could have a \s!check
     \push_macro_currentinterlinespace
     \let\currentinterlinespace\p_spac_checked_interlinespace
     \spac_linespacing_setup_specified_interline_space % \dosetupspecifiedinterlinespaceindeed
     \iflocalinterlinespace
       \expandtoks\everysetuplocalinterlinespace
     \else
       \localinterlinespacetrue
       \expandtoks\everysetuplocalinterlinespace
       \localinterlinespacefalse
     \fi
     \pop_macro_currentinterlinespace
   \else
     \normalexpanded{\noexpand\doifelseassignment{\p_spac_checked_interlinespace}%
       \setupspecifiedinterlinespace\setuprelativeinterlinespace[\p_spac_checked_interlinespace]}%
     \iflocalinterlinespace
       \expandtoks\everysetuplocalinterlinespace
     \else
       \localinterlinespacetrue
       \expandtoks\everysetuplocalinterlinespace
       \localinterlinespacefalse
     \fi
   \fi}

\permanent\protected\def\setuplocalinterlinespace[#S#1]%
  {\localinterlinespacetrue
   \push_macro_currentinterlinespace
   \setupinterlinespace[#1]%
   \pop_macro_currentinterlinespace
   \localinterlinespacefalse}

\aliased\let\switchtointerlinespace\setuplocalinterlinespace

%D Helpers

\newgluespec \s_spac_lastskip
\newdimension\d_spac_prevdepth
\newinteger  \c_spac_spacefactor
%newdimension\d_spac_prevcontent % set by lua (no longer it seems)

% \permanent\overloaded\protected\def\removelastskip
%   {\ifvmode\ifzeropt\lastskip\else\vskip-\lastskip\fi\fi}

\permanent\def\doifoutervmode
  {\unless\ifvmode
     \expandafter\gobbleoneargument
   \orelse\ifinner
     \expandafter\gobbleoneargument
   \else
     \expandafter\firstofoneargument
   \fi}

\permanent\protected\def\dosomebreak#1% should be replaced by something \blank
  {\doifoutervmode
     {\s_spac_lastskip\lastskip
      \removelastskip
      #1\relax
      \ifzeropt\s_spac_lastskip
        % avoid interference with footnotes
      \else
        \vskip\s_spac_lastskip
      \fi}}

\permanent\protected\def\packed
  {\nointerlineskip}

\permanent\protected\def\godown[#1]%
  {\relax
   \ifhmode\endgraf\fi
   \ifvmode\nointerlineskip\vskip#1\relax\fi}

\permanent\protected\def\smallskip{\vskip\smallskipamount}
\permanent\protected\def\medskip  {\vskip\medskipamount}
\permanent\protected\def\bigskip  {\vskip\bigskipamount}

\permanent\protected\def\smallbreak
  {\par
   \ifvmode\ifdim\lastskip<\smallskipamount
     \removelastskip
     \penalty-\plusfifty
     \smallskip
   \fi\fi}

\permanent\protected\def\medbreak
  {\par
   \ifvmode\ifdim\lastskip<\medskipamount
     \removelastskip
     \penalty-\plusonehundred
     \medskip
   \fi\fi}

\permanent\protected\def\bigbreak
  {\par
   \ifvmode\ifdim\lastskip<\bigskipamount
     \removelastskip
     \penalty-\plustwohundred
     \bigskip
   \fi\fi}

\permanent\protected\def\break     {\penalty-\plustenthousand} % can be hmode or vmode
\permanent\protected\def\nobreak   {\penalty \plustenthousand} % can be hmode or vmode
\permanent\protected\def\allowbreak{\penalty \zerocount}       % can be hmode or vmode

\permanent\protected\def\goodbreak {\par\ifvmode\penalty-\plusfivehundred\relax\fi}       % forces vmode
\permanent\protected\def\filbreak  {\par\ifvmode\vfil\penalty-\plustwohundred\vfilneg\fi} % forces vmode

%D Made slightly more readable:

\permanent\protected\def\vglue  {\afterassignment\spac_helpers_vglue_indeed\s_spac_lastskip=}
\permanent\protected\def\hglue  {\afterassignment\spac_helpers_hglue_indeed\s_spac_lastskip=}
\permanent\protected\def\topglue{\par\ifvmode\nointerlineskip\vglue-\topskip\vglue\fi}

\def\spac_helpers_vglue_indeed
  {\par
   \ifvmode
     \d_spac_prevdepth\prevdepth
     \hrule\s!height\zeropoint
     \nobreak
     \vskip\s_spac_lastskip
     \prevdepth\d_spac_prevdepth
   \fi}

\def\spac_helpers_hglue_indeed
  {\dontleavehmode
   \c_spac_spacefactor\spacefactor
   \vrule\s!width\zeropoint
   \nobreak
   \hskip\s_spac_lastskip
   \spacefactor\c_spac_spacefactor}

%D We adapt plain's \type {\removelastskip} a bit:

\pushoverloadmode

\permanent\overloaded\protected\def\removelastskip % also in supp-box
  {\ifvmode\ifzeropt\lastskip\else\vskip-\lastskip\fi\fi}

\popoverloadmode

% The whitespace handler. We could cache settings but normally there are not
% that many in a set.

\installcorenamespace{whitespacemethod}

\newgluespec   \s_spac_whitespace_parskip
\newconditional\c_spac_whitespace_flexible  \c_spac_whitespace_flexible\conditionaltrue
\newconstant   \c_spac_whitespace_grid_mode % option in layout / 1=permit_half_lines

%def\v_spac_whitespace_current{\zeropoint}
\let\v_spac_whitespace_current\v!none

\permanent\tolerant\protected\def\setupwhitespace[#1]%
  {\ifarguments
     \spac_whitespace_setup_nop
   \orelse\ifempty{#1}%
     \spac_whitespace_setup_nop
   \else
     \edef\v_spac_whitespace_current{#1}%
     \spac_whitespace_setup
   \fi}

\permanent\protected\def\spac_whitespace_setup_nop
  {\ifx\v_spac_whitespace_current\v!none\else
     \spac_whitespace_setup
   \fi}

\aliased\let\synchronizewhitespace\spac_whitespace_setup_nop

\def\spac_whitespace_setup % quick test for no list
  {\ifcsname\??whitespacemethod\v_spac_whitespace_current\endcsname
     \lastnamedcs
   \else
     \expandafter\processcommalist\expandafter[\v_spac_whitespace_current]\spac_whitespace_setup_method % can be raw
   \fi\relax
   \ifgridsnapping
     \spac_whitespace_setup_grid
   \else
     \spac_whitespace_setup_normal
   \fi
   \parskip\s_spac_whitespace_parskip}

\def\spac_whitespace_setup_normal
  {\ifconditional\c_spac_whitespace_flexible \else
     \s_spac_whitespace_parskip\plusone\s_spac_whitespace_parskip
   \fi}

\def\spac_whitespace_setup_grid
  {\c_spac_whitespace_flexible\conditionalfalse
   \ifdim\s_spac_whitespace_parskip>\zeropoint
     \s_spac_whitespace_parskip
       \ifcase\c_spac_whitespace_grid_mode
         \baselineskip
       \or
         \ifdim\scratchdimen=\baselineskip % maybe range
           \baselineskip
         \else
           \numexpr\s_spac_whitespace_parskip/\dimexpr.5\lineheight\relax\relax\dimexpr.5\lineheight\relax
         \fi
       \else
         \baselineskip
       \fi
   \fi}

\permanent\protected\def\installwhitespacemethod#1#2%
  {\defcsname\??whitespacemethod#1\endcsname{#2}}

\installwhitespacemethod \v!fix         {}
\installwhitespacemethod \v!fixed       {\c_spac_whitespace_flexible\conditionalfalse}
\installwhitespacemethod \v!flexible    {\c_spac_whitespace_flexible\conditionaltrue}

\installwhitespacemethod \v!line        {\s_spac_whitespace_parskip   \baselineskip}
\installwhitespacemethod \v!halfline    {\s_spac_whitespace_parskip .5\baselineskip}
\installwhitespacemethod \v!quarterline {\s_spac_whitespace_parskip.25\baselineskip}
\installwhitespacemethod \v!none        {\s_spac_whitespace_parskip   \zeroskip}
\installwhitespacemethod \v!big         {\s_spac_whitespace_parskip   \bigskipamount}
\installwhitespacemethod \v!medium      {\s_spac_whitespace_parskip   \medskipamount}
\installwhitespacemethod \v!small       {\s_spac_whitespace_parskip   \smallskipamount}

\installwhitespacemethod \s!default     {\spac_whitespace_setup_nop} % also covers none

\def\spac_whitespace_setup_method#1%
  {\ifcsname\??whitespacemethod#1\endcsname
     \lastnamedcs
   \else
     \s_spac_whitespace_parskip#1%
   \fi\relax}

\permanent\protected\def\forgetparskip
  {\s_spac_whitespace_parskip\zeroskip
   \parskip\zeroskip
   \let\v_spac_whitespace_current\v!none}

\appendtoks
    \forgetparskip
\to \everyforgetall

% \installwhitespacemethod \s!unknown  {\s_spac_whitespace_parskip\commalistelement\relax}
%
% \def\spac_whitespace_setup_method#1%
%   {\csname\??whitespacemethod\ifcsname\??whitespacemethod#1\endcsname#1\else\s!unknown\endcsname\relax}

\permanent\protected\def\nowhitespace{\directcheckedvspacing\v!nowhite} % {\vspacing[\v!nowhite]}
\permanent\protected\def\whitespace  {\directcheckedvspacing\v!white}   % {\vspacing[\v!white]}

\setupwhitespace
  [\v!none]

% Packed:

% todo: when packed blocks blank, we need to enable forced

\newconditional\c_spac_packed_blank \c_spac_packed_blank\conditionaltrue
\newinteger    \c_spac_packed_level

\permanent\tolerant\protected\def\startpacked[#1]%
  {\global\advanceby\c_spac_packed_level\plusone
   \par
   \ifnum\c_spac_packed_level=\plusone \ifvmode
     \begingroup
     \whitespace % not combined
     \directcheckedvspacing\v!disable % \blank[\v!disable]% or \inhibitblank
     \ifcstok{#1}\v!blank
       \c_spac_packed_blank\conditionaltrue
     \else
       \c_spac_packed_blank\conditionalfalse
     \fi
     \setupwhitespace[\v!none]% or \forgetparskip
   \fi \fi}

\permanent\protected\def\stoppacked
  {\par
   \ifnum\c_spac_packed_level=\plusone \ifvmode
     \endgroup
   \fi \fi
   \global\advanceby\c_spac_packed_level\minusone}

\permanent\protected\def\startunpacked
  {\directdefaultvspacing % \blank
   \begingroup}

\permanent\protected\def\stopunpacked
  {\endgroup
   \directdefaultvspacing}% \blank}

% \prevdepth crosses pageboundaries!
%
% todo: a version that works ok inside a box

% global : outer hsize + keep skips
% local  : inner hsize + reset skips

\installcorenamespace{linesaround}

\let\spac_lines_vbox\vbox

\installtextracker
  {linecorrection.boxes}
  {\enforced\let\spac_lines_vbox\ruledvbox}
  {\enforced\let\spac_lines_vbox\vbox}

\let\v_spac_lines_around_action_set\relax

\lettonothing\spac_lines_action_around_before
\lettonothing\spac_lines_action_around_after

\newconstant\c_spac_lines_correction_mode

\installcorenamespace{linecorrection}

\installsetuponlycommandhandler \??linecorrection {linecorrection}

\permanent\tolerant\protected\def\startlinecorrection     [#S#1]{\spac_lines_start_correction\plusone{#1}}
\permanent\tolerant\protected\def\startlocallinecorrection[#S#1]{\spac_lines_start_correction\plustwo{#1}}

\permanent\protected\def\spac_lines_start_correction_box
  {\setbox\scratchbox\spac_lines_vbox\bgroup
     \ifcase\c_spac_lines_correction_mode
       % nothing
     \or
       % global
     \or
       % local
       \setlocalhsize
       \hsize\localhsize
       \forgetbothskips
     \fi
     \ignorespaces}

\permanent\protected\def\spac_lines_stop_correction_box
  {\removeunwantedspaces
   \egroup}

\permanent\protected\def\spac_lines_start_correction#1#2%
  {\endgraf
   \begingroup
   \c_spac_lines_correction_mode#1%
   % a bit of history
   \ifhastok={#2}%
     \setupcurrentlinecorrection[#2]%
     \def\spac_lines_action_around_before{\linecorrectionparameter\c!before}%
     \def\spac_lines_action_around_after {\linecorrectionparameter\c!after}%
   \orelse\ifempty{#2}%
     \def\spac_lines_action_around_before{\linecorrectionparameter\c!before}%
     \def\spac_lines_action_around_after {\linecorrectionparameter\c!after}%
   \orelse\ifcstok{#2}\v!blank
     \def\spac_lines_action_around_before{\blank}%
     \let\spac_lines_action_around_after \spac_lines_action_around_before
   \else
     \def\spac_lines_action_around_before{\blank[#2]}%
     \let\spac_lines_action_around_after \spac_lines_action_around_before
   \fi
   \ifgridsnapping
     \spac_lines_start_correction_ongrid
   \else
     \spac_lines_start_correction_normal
   \fi}

\permanent\protected\def\spac_lines_stop_correction
  {\ifgridsnapping
     \spac_lines_stop_correction_ongrid
   \else
     \spac_lines_stop_correction_normal
   \fi
   \endgroup}

\permanent\protected\def\spac_lines_start_correction_normal
  {\spac_lines_action_around_before
   \d_spac_prevdepth\prevdepth
   \spac_lines_initialize_corrections
   \offbaselinecorrection % ???
   \spac_lines_start_correction_box}

\newconstant\linecorrectionmode

% \linecorrectionmode\plusone % better for end of column and depth alignment

\permanent\protected\def\spac_lines_stop_correction_normal
  {\spac_lines_stop_correction_box
   \ifnum\linecorrectionmode=\plusone
     \forcestrutdepth
     \iftrue
       \penalty\zerocount % works too
     \else
         %\tracingpages\plusone \tracingonline\plustwo
       \pageboundary\plustenthousand % becomes a penalty (after triggering the callback) (experimental!)
         %\tracingpages\zerocount
     \fi
   \else
     \directcheckedvspacing\v!nowhite % \blank[\v!nowhite]%
     \ifdim\parskip>\zeropoint
        % too fuzzy otherwise
     \orelse\ifdim\d_spac_prevdepth<\maxdimen
       \unless\ifdim\d_spac_prevdepth<\zeropoint
         \ifdim\d_spac_prevdepth<\strutdp \relax
           \pushlastnode
           \ifdim\d_spac_prevdepth>\zeropoint
               \kern-\d_spac_prevdepth
           \fi
           \kern\strutdp
           \prevdepth\strutdp
           \poplastnode
         \fi
       \fi
     \fi
   \fi
   \ifdim\pagegoal<\maxdimen
   % \blank[\v!white,\the\d_spac_lines_correction_before]% \blank[\v!white]\dotopbaselinecorrection
     \directcheckedvspacing{\v!white,\the\d_spac_lines_correction_before}% \blank[\v!white]\dotopbaselinecorrection
   \fi
   \nointerlineskip % relatively new
   \noindent % not \dontleavehmode !
   \ifcase\c_spac_lines_correction_mode
     % nothing
   \or
     % global
     \hskip-\leftskip % more tricky would be hangindent so we ignore that one
   \or
     % local
   \fi
   \box\scratchbox
   \endgraf
   %
   % eventually i'll get it right ... (i also need to check all whitespace code elsewhere)
   %
 % \blank[\the\d_spac_lines_correction_after]% \dobotbaselinecorrection
 % \directcheckedvspacing{\the\d_spac_lines_correction_after}% \dobotbaselinecorrection
   \directcheckedvspacing{\v!white,\the\d_spac_lines_correction_after}% \dobotbaselinecorrection
 % \allowbreak % new, otherwise problems when many in a row
   \prevdepth\strutdp
   \spac_lines_action_around_after}

% this needs checking: a double before .. use lmtx tricks

\permanent\protected\def\spac_lines_start_correction_ongrid
  {\spac_lines_action_around_before
   \d_spac_prevdepth\prevdepth
   \spac_lines_initialize_corrections
   \offbaselinecorrection % ???
   \spac_lines_start_correction_box}

\permanent\protected\def\spac_lines_stop_correction_ongrid
  {\spac_lines_stop_correction_box
   \directcheckedvspacing\v!white % \blank[\v!white]%
   \spac_lines_action_around_before
   \snaptogrid\hpack{\box\scratchbox}%
   \directcheckedvspacing\v!white
   \spac_lines_action_around_after}

\aliased\let\stoplinecorrection     \spac_lines_stop_correction
\aliased\let\stoplocallinecorrection\spac_lines_stop_correction

% todo:

\permanent\protected\def\correctwhitespace
  {\dowithnextboxcs\correctwhitespacefinish\vbox}

\permanent\protected\def\correctwhitespacefinish
  {\startbaselinecorrection
   \flushnextbox
   \stopbaselinecorrection}

\permanent\protected\def\verticalstrut  {\vpack{\hsize\zeropoint\forgetall\strut}}
\permanent\protected\def\horizontalstrut{\hpack                          {\strut}}

%D Here follow some presets related to interline spacing and therefore also struts.
%D The values 2.8, 0.07, 0.72 and 0.28 originate in \INRSTEX, a package that we used
%D a while after we decided that \LATEX\ was not flexible enough. After that
%D \CONTEXT\ evolved, from some wrapper code around (old) \LATEX\ (on a floppy
%D disk), to using modules from \INRSTEX\ (which also fit on a floppy) and finally
%D all written from scratch. I simply didn't understand all that \TEX\ code at that
%D time, and it was easier to figure it out myself. But \unknown\ some settings
%D stayed, as the height|/|depth ratios, and they never proved to be bad ones! The
%D same is true for the font size relations.

%D \starttabulate
%D \NC  \type {\lineheight}        \NC the height of a line \NC \NR
%D \NC  \type {\spacing{number}}   \NC adapting the interline space \NC \NR
%D \NC  \type {\normalbaselines}   \NC initialize the interline spacing \NC \NR
%D \NC  \type {\setstrut}          \NC initialize \type {\strut} \NC \NR
%D \NC  \type {\setnostrut}        \NC disable the \type {\strut}, \type {\endstrut}, \type {\begstrut} \NC \NR
%D \NC  \type {\setteststrut}      \NC initialize the visual \type {\strut} \NC \NR
%D \NC  \type {\resetteststrut}    \NC disable the visual \type {\strut} \NC \NR
%D \NC  \type {\setfontparameters} \NC synchronize parameters with foints \NC \NR
%D \stoptabulate
%D
%D \unknown\ and many more (this is a decades old list).
%D
%D The lineheight is the sum of the height and depth of \type {strut}, which is
%D an invisible blob that can be used to enforce the proper dimensions.
%D
%D Such a blob, when placed at the beginning of a paragraph can have side effects
%D that can be prevented with \type {\dontleavehmode}. Never use \type
%D {\leavevmode}!

\newdimension\strutdimen        % not used
\newdimension\lineheight
\newdimension\openlineheight
\newdimension\openstrutheight
\newdimension\openstrutdepth
\newdimension\topskipgap

\newdimension\strutheight
\newdimension\strutdepth
\newdimension\struttotal
\newdimension\strutwidth

\let\m_spac_vertical_baseline_stretch_factor \zerocount
\let\m_spac_vertical_baseline_shrink_factor  \zerocount

\mutable\def\strutheightfactor   {.72}
\mutable\def\strutdepthfactor    {.28}
\mutable\def\baselinefactor      {2.8}
\mutable\def\topskipfactor       {1.0}
\mutable\def\maxdepthfactor      {0.5}
\mutable\let\ntopskipfactor      \minusone

\mutable\def\minimumstrutheight  {\zeropoint}
\mutable\def\minimumstrutdepth   {\zeropoint}

\mutable\def\normallineheight    {\baselinefactor\exheight}
\mutable\def\minimumlinedistance {\lineskip}

\mutable\let\spacingfactor        \plusone

\mutable\def\systemtopskipfactor {\topskipfactor}
\mutable\def\systemmaxdepthfactor{\maxdepthfactor}

\ifdefined\globalbodyfontsize \else
    \newdimension\globalbodyfontsize
    \globalbodyfontsize=12pt
\fi

\ifdefined\normalizedbodyfontsize \else
    \def\normalizedbodyfontsize{12pt}
\fi

\permanent\protected\def\topskipcorrection
  {\simpletopskipcorrection
   \vskip-\struttotal
   \verticalstrut}

\permanent\protected\def\simpletopskipcorrection
  {\ifdim\topskip>\openstrutheight
     % == \vskip\topskipgap
     \vskip\topskip
     \vskip-\openstrutheight
   \fi}

% \permanent\protected\def\settopskip % the extra test is needed for the lbr family
%   {\topskip
%      \ifgridsnapping
%        \zeropoint
%      \else
%        \systemtopskipfactor\globalbodyfontsize
%        \ifcase\bottomraggednessmode % ragged bottom
%          \s!plus
%          \ifnum\ntopskipfactor<\zerocount
%            5\globalbodyfontsize % old hard coded value
%          \else
%             \ntopskipfactor\openlineheight
%          \fi
%        \fi
%    \fi
%    %\relax
%    \topskipgap\topskip
%    \advanceby\topskipgap -\openstrutheight\relax
%    \ifdim\minimumstrutheight>\zeropoint
%      \ifdim\topskip<\minimumstrutheight
%        \topskip\minimumstrutheight\relax
%      \fi
%    \else
%      \ifdim\topskip<\strutheightfactor\openlineheight
%        \topskip\strutheightfactor\openlineheight\relax
%      \fi
%    \fi}

\permanent\protected\def\settopskip % the extra test is needed for the lbr family
  {\topskip
     \ifgridsnapping
       \zeroskip
       \initialpageskip\zeroskip
     \else
       \systemtopskipfactor\globalbodyfontsize
       \relax
       \initialpageskip\zeropoint
         \ifcase\bottomraggednessmode % ragged bottom
           \s!plus \ntopskipfactor\openlineheight
         \fi
       \relax
   \fi
   \topskipgap\topskip
   \advanceby\topskipgap -\openstrutheight\relax
   \ifdim\minimumstrutheight>\zeropoint
     \ifdim\topskip<\minimumstrutheight
       \topskip\minimumstrutheight\relax
     \fi
   \else
     \ifdim\topskip<\strutheightfactor\openlineheight
       \topskip\strutheightfactor\openlineheight\relax
     \fi
   \fi}

\permanent\protected\def\setmaxdepth
  {\maxdepth\systemmaxdepthfactor\globalbodyfontsize}

\newgluespec \usedbaselineskip  % These used to be \normal... but that isn't pretty
\newgluespec \usedlineskip      % in the token interface, so these few now have new
\newdimension\usedlineskiplimit % names. They are public but not really user commands.

\permanent\protected\def\normalbaselines
  {\baselineskip \usedbaselineskip
   \lineskip     \usedlineskip
   \lineskiplimit\usedlineskiplimit}

% \permanent\protected\def\flexiblebaselines
%   {\baselineskip  \usedbaselineskip
%    \lineskip     1\usedlineskip \s!plus 1\s!fill
%    \lineskiplimit \usedlineskiplimit}

\permanent\protected\def\flexiblebaselines
  {\baselineskip 1\usedbaselineskip \s!plus 1\s!fill
   \lineskip      \usedlineskip
   \lineskiplimit \usedlineskiplimit}

\permanent\protected\def\setnormalbaselines % used in overload
  {\ifdim\normallineheight>\zeropoint
     \lineheight\normallineheight
   \fi
   \openlineheight\spacingfactor\lineheight
   \openstrutheight \ifdim\minimumstrutheight>\zeropoint
     \minimumstrutheight % new
   \else
     \strutheightfactor\openlineheight
   \fi
   \openstrutdepth \ifdim\minimumstrutdepth>\zeropoint
     \minimumstrutdepth % new
   \else
     \strutdepthfactor \openlineheight
   \fi
   \ifdim\dimexpr\minimumstrutdepth+\minimumstrutheight\relax>\zeropoint
     \openlineheight\dimexpr\openstrutheight+\openstrutdepth\relax % new
   \fi
   \usedbaselineskip\openlineheight
     \ifgridsnapping\else
       \s!plus \m_spac_vertical_baseline_stretch_factor\openlineheight
       \s!minus\m_spac_vertical_baseline_shrink_factor \openlineheight
     \fi
   \usedlineskip\minimumlinedistance\relax % \onepoint\relax
   \usedlineskiplimit\zeropoint\relax
   \normalbaselines}

\permanent\protected\def\spacing#1% vertical
  {\ifgridsnapping
     \let\spacingfactor\plusone
   \else
     \permanent\edef\spacingfactor{#1}%
   \fi
   \edef\systemtopskipfactor {\thewithoutunit\dimexpr#1\dimexpr\topskipfactor \points}%
   \edef\systemmaxdepthfactor{\thewithoutunit\dimexpr#1\dimexpr\maxdepthfactor\points}%
   \setnormalbaselines
   \setstrut}

% \protected\def\forgetverticalstretch % \forgetspacing
%   {\spacing\plusone}

\permanent\protected\def\forgetverticalstretch
  {\let\spacingfactor       \plusone
   \let\systemtopskipfactor \topskipfactor
   \let\systemmaxdepthfactor\maxdepthfactor
   \setnormalbaselines
   \setstrut}

\appendtoks
    \forgetverticalstretch
\to \everyforgetall % needed in otr

%D Sometimes one needs to freeze the interlinespacing
%D
%D \starttyping
%D \rm \saveinterlinespace .... {\ss \restoreinterlinespace .... \endgraf}
%D \stoptyping
%D
%D This is no longer needed.

\aliased\let\restoreinterlinespace\relax

\permanent\protected\def\saveinterlinespace
  {\enforced\permanent\protected\edef\restoreinterlinespace
     {\lineheight         \the\lineheight
      \openstrutheight    \the\openstrutheight
      \openstrutdepth     \the\openstrutdepth
      \openlineheight     \the\openlineheight
      \usedbaselineskip   \the\usedbaselineskip
      \usedlineskip       \the\usedlineskip
      \usedlineskiplimit  \the\usedlineskiplimit
      \noexpand\def\noexpand\normallineheight{\the\dimexpr\normallineheight}%
      \noexpand\normalbaselines}}

%D This is the plain definition:
%D
%D \starttyping
%D \def\strut{\relax\ifmmode\copy\strutbox\else\unhcopy\strutbox\fi}
%D \stoptyping
%D
%D which could be:
%D
%D \starttyping
%D \def\strut{\relax\ifmmode\copy\else\unhcopy\fi\strutbox}
%D \stoptyping
%D
%D But we do things differently.

\newbox\b_strut_box
\newbox\b_strut_tmp

\permanent\protected\def\strutbox % not to be used but this is sort of an alias
  {\beginlocalcontrol
   \setbox\b_strut_tmp\hpack{\normalsrule\s!height\strutht\s!depth\strutdp}% just a start
   \endlocalcontrol
   \b_strut_tmp}

%D The double \type {\hbox} construction enables us to backtrack boxes.

% \overloaded\let\strutht  \undefined \newdimension\strutht    % see above
% \overloaded\let\strutdp  \undefined \newdimension\strutdp
% \overloaded\let\struthtdp\undefined \newdimension\struthtdp

\permanent\protected\def\setstrut
  {\ifgridsnapping
     \setstrutgridyes
   \else
     \setstrutgridnop
   \fi}

\permanent\protected\def\setstrutgridyes
  {\strutht\spacingfactor\dimexpr
     \ifdim\minimumstrutheight>\zeropoint
       \minimumstrutheight
     \else
       \strutheightfactor\dimexpr\normallineheight
     \fi
   \relax
   \strutdp\dimexpr
     \ifdim\minimumstrutdepth>\zeropoint
       \minimumstrutdepth
     \else
       \normallineheight-\strutht
     \fi
   \relax
   \dosetstrut}

\permanent\protected\def\setstrutgridnop
  {\strutht\spacingfactor\dimexpr
     \ifdim\minimumstrutheight>\zeropoint
       \minimumstrutheight
     \else
       \strutheightfactor\dimexpr\normallineheight
     \fi
   \relax
   \strutdp\spacingfactor\dimexpr
     \ifdim\minimumstrutdepth>\zeropoint
       \minimumstrutdepth
     \else
       \strutdepthfactor\dimexpr\normallineheight
     \fi
   \relax
   \dosetstrut}

\permanent\protected\def\setcharstrut#1%
  {\setbox\b_strut_box\hbox{#1}% no \hpack, in case we have smallcaps
   \strutht\ht\b_strut_box
   \strutdp\dp\b_strut_box
   \dosetstrut}

\permanent\protected\def\settightstrut
  {\setcharstrut{(}}

\permanent\protected\def\setfontstrut
  {\setcharstrut{(gplQT}}

\permanent\protected\def\setcapstrut% could be M, but Q has descender
  {\setcharstrut{Q}}

%D Handy for math (used in mathml):

\permanent\protected\def\charhtstrut
  {\begingroup
   \setcharstrut{GJY}%
   \normalsrule
     \s!depth \zeropoint
     \s!height\strutht
   \endgroup}

\permanent\protected\def\chardpstrut
  {\begingroup
   \setcharstrut{gjy}%
   \normalsrule
     \s!depth \strutdp
     \s!height\zeropoint
   \endgroup}

%D Because of all the callbacks in mkiv, we avoid unnecessary boxes ... maybe use an
%D attribute so that we can tag boxes that don't need a treatment; tests with using
%D an attribute so far have shown that it's slower because testing the attribute
%D takes time too.

\permanent\protected\def\dosetstrut
  {\enforced\let\strut\normalstrut
   \struthtdp\dimexpr\strutht+\strutdp\relax
   \ifabsnum\dimexpr\struthtdp-\lineheight\relax<\plustwo
     % compensate rounding error /- 1sp to avoid too many
     % 1sp baselineskips in for instance verbatim
     % \strutht\dimexpr\lineheight-\strutdp\relax
     % better:
     \strutdp\dimexpr\lineheight-\strutht\relax
     \struthtdp\lineheight
   \fi
   \strutheight\strutht
   \strutdepth \strutdp
   \struttotal \struthtdp}

\newconstant\c_strut_visual_mode

%D The dimen \type {\struttotal} holds the exact size of the strut; occasionally a
%D one scaled point difference can show up with the lineheight. This is more
%D efficient (less callbacks):

\permanent\protected\def\strut % still callbacks for \hbox{\strut}
  {\relax
   \dontleavehmode
   \normalsrule
      \s!height\strutht
      \s!depth \strutdp
   \relax}

\aliased\let\normalstrut\strut

\permanent\protected\def\halfstrut
  {\relax
   \dontleavehmode
   \normalsrule
      \s!height.5\strutht
      \s!depth .5\strutdp
   \relax}

\permanent\protected\def\quarterstrut
  {\relax
   \dontleavehmode
   \normalsrule
      \s!height.25\strutht
      \s!depth .25\strutdp
   \relax}

\permanent\protected\def\depthstrut
  {\relax
   \dontleavehmode
   \normalsrule
      \s!height\dimexpr\strutht-\struthtdp/\plustwo\relax
      \s!depth \strutdp
   \relax}

\permanent\protected\def\halflinestrut
  {\relax
   \dontleavehmode
   \normalsrule
      \s!height\dimexpr\strutht-.5\struthtdp\relax
      \s!depth \strutdp
   \relax}

\permanent\protected\def\noheightstrut
  {\relax
   \dontleavehmode
   \normalsrule
      \s!height\zeropoint
      \s!depth \strutdp
   \relax}

\permanent\protected\def\nodepthstrut
  {\relax
   \dontleavehmode
   \normalsrule
      \s!height\strutht
      \s!depth \zeropoint
   \relax}

%D Sometimes a capstrut comes in handy
%D
%D \starttabulate[|Tl|l|l|]
%D \NC yes          \NC normal strut               \NC {\showstruts\setupstrut[yes]\strut}  \NC \NR
%D \NC no           \NC no strut                   \NC {\showstruts\setupstrut[no]\strut}  \NC \NR
%D \NC kap          \NC a capital strut (i.e. Q)   \NC {\showstruts\setupstrut[cap]\strut} \NC \NR
%D \NC A B \unknown \NC a character strut (e.g. A) \NC {\showstruts\setupstrut[A]\strut}   \NC \NR
%D \NC              \NC a normal strut             \NC {\showstruts\setupstrut\strut}      \NC \NR
%D \stoptabulate
%D
%D Beware: using an unknown value results in char struts.

\installcorenamespace{struts}

\permanent\tolerant\protected\def\setupstrut[#1]%
  {\edef\m_strut{#1}%
   \ifcsname\??struts\m_strut\endcsname
     \lastnamedcs
   \else
     \setcharstrut\m_strut
   \fi}

\permanent\protected\def\synchronizestrut#1% no [] parsing, faster for internal
  {\edef\m_strut{#1}%
   \ifcsname\??struts\m_strut\endcsname
     \lastnamedcs
   \else
     \setcharstrut\m_strut
   \fi}

\permanent\protected\def\dosynchronizestrut#1% no [] parsing, faster for internal
  {\ifcsname\??struts#1\endcsname
     \lastnamedcs
   \else
     \setcharstrut{#1}%
   \fi}

\permanent\protected\def\showstruts % adapts .. is wrong
  {\showmakeup[strut]%
   \settestcrlf}

\aliased\let\showcolorstruts\showstruts

\mutable\def\autostrutfactor{1.1}

\permanent\protected\def\setautostrut
  {\begingroup
   \setbox\scratchbox\copy\b_strut_box
   \setstrut
   \ifdim\strutht>\autostrutfactor\ht\scratchbox
     \endgroup \setstrut
   \orelse\ifdim\strutdp>\autostrutfactor\dp\scratchbox
     \endgroup \setstrut
   \else
     \endgroup
   \fi}

\newbox\nostrutbox \setbox\nostrutbox\emptyhbox

\newtoks\everysetnostrut

\permanent\protected\def\setnostrut
  {\expandtoks\everysetnostrut}

\appendtoks
 %  \setbox\strutbox\copy\nostrutbox
    \enforced\lettonothing\strut
    \enforced\lettonothing\endstrut
    \enforced\lettonothing\begstrut
\to \everysetnostrut

%D When enabled, sigstruts will remove themselves if nothing goes inbetween. For
%D practical reasons we define some boundary characters here.

\permanent\protected\def\leftboundary   {\protrusionboundary\plusone}
\permanent\protected\def\rightboundary  {\protrusionboundary\plustwo}
\permanent\protected\def\signalcharacter{\boundary\plusone\char\zerocount\boundary\plustwo} % not the same as strut signals

%D \starttyping
%D $ \ifhmode H1\fi x \ifhmode H2\fi $\par
%D $ \ifmmode M1\fi x \ifmmode M2\fi $\par
%D $$\ifvmode H1\fi x \ifvmode H2\fi$$\par
%D $$\ifvmode M1\fi x \ifvmode M2\fi$$\par
%D \stoptyping

\permanent\protected\def\begstrut
  {\ifmmode
     \strut
   \else
     \dontleavehmode
     \ifcase\struthtdp\else
       \spac_struts_beg
     \fi
   \fi
   \ignorespaces}

\def\spac_struts_beg
  {\boundary\plusone
   \strut
  %\boundary\plusone
   \penalty\plustenthousand
  %\boundary\plusone
   \hskip\zeroskip}

\permanent\protected\def\endstrut
  {% \ifmmode M\fi
   % \ifinner I\fi
   \relax\ifhmode
    %\removeunwantedspaces
     \spac_helpers_remove_unwantedspace
     \ifcase\struthtdp\else
       \spac_struts_end
     \fi
   \orelse\ifmmode
    %\removeunwantedspaces hmode only anyway
     \spac_helpers_remove_unwantedspace
     \strut
   \fi}

\def\spac_struts_end
  {\penalty\plustenthousand
  %\boundary\plustwo
   \hskip\zeroskip
  %\boundary\plustwo
   \strut
   \boundary\plustwo}

% unsave:
%
% \def\pseudostrut
%   {\bgroup
%    \setnostrut
%    \normalstrut
%    \egroup}
%
% try:
%
% \startchemie
%   \chemie[ONE,Z0,SB15,MOV1,SB15,Z0][C,C]
% \stopchemie
%
% so:

\permanent\protected\def\pseudostrut
  {\noindent} % better: \dontleavehmode

\aliased\let\pseudobegstrut\pseudostrut
\aliased\let\pseudoendstrut\removeunwantedspaces

\permanent\protected\def\resetteststrut
  {\strutwidth\zeropoint
   \setstrut}

\ifdefined\setfontparameters \else
    \def\setfontparameters{\expandtoks\everybodyfont}
\fi

%D Keyword based strutting:

\letcsname\??struts\v!yes    \endcsname\setstrut
\letcsname\??struts\v!auto   \endcsname\setautostrut
\letcsname\??struts\v!no     \endcsname\setnostrut
\letcsname\??struts\v!cap    \endcsname\setcapstrut
\letcsname\??struts\v!fit    \endcsname\setfontstrut
\letcsname\??struts\v!line   \endcsname\setstrut
\letcsname\??struts\s!default\endcsname\setstrut
\letcsname\??struts\empty    \endcsname\setstrut

%D Handy:

\permanent\def\baselinedistance{\the\lineheight}

%D We need \type {\normaloffinterlineskip} because the new definition contains an
%D assignment, and |<|don't ask me why|>| this assignment gives troubles in for
%D instance the visual debugger.

\pushoverloadmode

\overloaded\permanent\protected\def\offinterlineskip % later we will overload this with a push pop
  {\baselineskip \ignoredepthcriterion % -\thousandpoint
   \lineskip     \zeroskip
   \lineskiplimit\maxdimen
   % We also need this here now; thanks to taco for figuring that out!
   \let\minimumlinedistance\zeropoint}

\overloaded\permanent\protected\def\nointerlineskip
  {\prevdepth\ignoredepthcriterion}% -\thousandpoint

\aliased\let\normaloffinterlineskip\offinterlineskip % knuth's original

\popoverloadmode

%D This is tricky. The prevdepth value is still set to the last one even if there is
%D nothing on the page. The same is true for prevgraf, which doesn't resemble the
%D value on the current page.
%D
%D So, here we kick in a checker but it has to happen after the output group and it
%D only has to be done once (output can trigger itself!).
%D
%D However, prevgraf is somehow bound to hangindent so we can get very nasty side
%D effects. So, in tne end we use our own variable!

\ifdefined\getnofpreviouslines
    % defined public at the lua end
\else
    \let\getnofpreviouslines\!!zerocount
\fi

\protected\def\page_otr_synchronize_page_yes
  {\aftergroup\page_otr_synchronize_page_indeed
   \global\enforced\let\page_otr_synchronize_page\relax}

% This has to become an otr method: \s!page_otr_command_synchonize_page

\protected\def\page_otr_synchronize_page_indeed
  {\ifx\currentoutputroutine\s!multicolumn\else\clf_synchronizepage\fi
   \global\enforced\let\page_otr_synchronize_page\page_otr_synchronize_page_yes}

\let\page_otr_synchronize_page\page_otr_synchronize_page_yes

\appendtoks
    \page_otr_synchronize_page
\to \everyaftershipout

%D My own one:

\protected\def\spac_helpers_push_interlineskip_yes
  {\enforced\edef\oninterlineskip
     {\baselineskip \the\baselineskip
      \lineskip     \the\lineskip
      \lineskiplimit\the\lineskiplimit
      \noexpand\edef\noexpand\minimumlinedistance{\the\dimexpr\minimumlinedistance}%
      \enforced\let\noexpand\offinterlineskip\noexpand\normaloffinterlineskip}} % \noexpand not needed

\protected\def\spac_helpers_push_interlineskip_nop
  {\enforced\let\oninterlineskip\setnormalbaselines}

\pushoverloadmode

\overloaded\permanent\protected\def\offinterlineskip
  {\ifdim\baselineskip>\zeropoint
     \spac_helpers_push_interlineskip_yes
   \else
     \spac_helpers_push_interlineskip_nop
   \fi
   \normaloffinterlineskip}

\permanent\let\oninterlineskip\relax

\popoverloadmode

\permanent\protected\def\resetpenalties#1%
  {\ifdefined#1%
     \frozen#1\minusone
   \fi}

\permanent\protected\def\setpenalties#1#2#3%
  {\ifdefined#1% space before #3 prevents lookahead problems, needed when #3=text
     \frozen#1\numexpr#2+\plusone\relax\space\expandedrepeat\numexpr#2\relax{ #3}\zerocount\relax
   \fi}

%D \macros
%D   {keeplinestogether}
%D
%D Dirty hack, needed in margin content that can run of a page.

% just before margintexts ... will eventually be done differently in mkiv using
% attributes

\newinteger\c_spac_keep_lines_together

\permanent\protected\lettonothing\restoreinterlinepenalty

\protected\def\spac_penalties_restore
  {\enforced\protected\glettonothing\restoreinterlinepenalty
   \overloaded\global\resetpenalties\interlinepenalties
   \global\c_spac_keep_lines_together\zerocount}

\permanent\protected\def\keeplinestogether#1%
  {\ifnum#1>\c_spac_keep_lines_together
     \global\c_spac_keep_lines_together#1%
     \overloaded\global\setpenalties\interlinepenalties\c_spac_keep_lines_together\plustenthousand
     \global\enforced\let\restoreinterlinepenalty\spac_penalties_restore
   \fi}

%D Here now only deal with penalties that relate to vertical breaking; the
%D horizontal ones have been moved to spac-pas.

\immutable\integerdef\defaultdisplaywidowpenalty       50
\immutable\integerdef\defaultwidowpenalty            2000 % was: 1000
\immutable\integerdef\defaultclubpenalty             2000 % was:  800
\immutable\integerdef\defaultbrokenpenalty            100

\immutable\integerdef\defaultgriddisplaywidowpenalty \zerocount
\immutable\integerdef\defaultgridwidowpenalty        \zerocount
\immutable\integerdef\defaultgridclubpenalty         \zerocount
\immutable\integerdef\defaultgridbrokenpenalty       \zerocount

% \linebreakcriterion\optimallinebreakcriterion
% \divide \adjdemerits \plustwo

\permanent\protected\def\nopenalties
  {\frozen\widowpenalty \zerocount
   \frozen\clubpenalty  \zerocount
   \frozen\brokenpenalty\zerocount}

\permanent\protected\def\setdefaultpenalties
  {\directsetup{\systemsetupsprefix\s!default}}

\startsetups [\systemsetupsprefix\s!reset]
    \resetpenalties\widowpenalties
    \resetpenalties\clubpenalties
    \resetpenalties\interlinepenalties
\stopsetups

%D We use \directsetup because it's faster and we know there is no csl:

\startsetups [\systemsetupsprefix\s!default]

    \directsetup{\systemsetupsprefix\s!reset}

    \frozen\widowpenalty        \defaultwidowpenalty
    \frozen\clubpenalty         \defaultclubpenalty
    \frozen\displaywidowpenalty \defaultdisplaywidowpenalty
    \frozen\brokenpenalty       \defaultbrokenpenalty

\stopsetups

\startsetups [\v!grid] [\systemsetupsprefix\s!default]

    \directsetup{\systemsetupsprefix\s!reset}

    \frozen\widowpenalty        \defaultgridwidowpenalty
    \frozen\clubpenalty         \defaultgridclubpenalty
    \frozen\displaywidowpenalty \defaultgriddisplaywidowpenalty
    \frozen\brokenpenalty       \defaultgridbrokenpenalty

\stopsetups

%D As an illustration:

\startsetups [\systemsetupsprefix\v!strict]

    \directsetup{\systemsetupsprefix\s!reset}

    \setpenalties \widowpenalties      \plustwo  \maxdimen
    \setpenalties \clubpenalties       \plustwo  \maxdimen
    \setpenalties \orphanpenalties     \zerocount
          \frozen \brokenpenalty                 \maxdimen

\stopsetups

%D To be checked:

% \newbox\b_spac_struts_saved
%
% \permanent\protected\def\savestrut {\setbox\b_spac_struts_saved\copy\b_strut_box}
% \permanent\protected\def\savedstrut{\copy  \b_spac_struts_saved}

%D Good old blank redone:

%definesystemattribute[kernchars]    [public]
\definesystemattribute[skipcategory] [public]
\definesystemattribute[skippenalty]  [public]
\definesystemattribute[skiporder]    [public]
\definesystemattribute[snapmethod]   [public]
\definesystemattribute[snapvbox]     [public]
%definesystemattribute[snapcategory] [public]

% TODO: NAMED SNAPPERS

\installcorenamespace{gridsnappers}
\installcorenamespace{gridsnapperattributes}
\installcorenamespace{gridsnappersets}

% \newgluespec \bodyfontlineheight  % see top
% \newdimension\bodyfontstrutheight % see top
% \newdimension\bodyfontstrutdepth  % see top

% \newgluespec \globalbodyfontlineheight  % see top
% \newdimension\globalbodyfontstrutheight % see top
% \newdimension\globalbodyfontstrutdepth  % see top

\permanent\def\snappedvboxattribute{\ifgridsnapping attr\snapvboxattribute\c_attr_snapmethod\fi}
\permanent\def\setlocalgridsnapping{\ifgridsnapping \c_attr_snapvbox \c_attr_snapmethod\fi}

\def\spac_grids_set_local_snapping#1%
  {\ifgridsnapping
     \ifempty{#1}\else
       \spac_grids_snap_value_set{#1}%
       \c_attr_snapvbox\c_attr_snapmethod
     \fi
   \fi}

\def\spac_grids_expand_snapper#1%
  {\edef\m_spac_snapper
     {\ifempty\m_spac_snapper\else\m_spac_snapper,\fi
      \ifcsname\??gridsnappersets#1\endcsname\lastnamedcs\else#1\fi}}

\permanent\protected\def\installsnapvalues#1#2%
  {\lettonothing\m_spac_snapper
   \rawprocesscommacommand[#2]\spac_grids_expand_snapper
   \edef\p_spac_snapper{#1:\m_spac_snapper}%
   \ifcsname\??gridsnapperattributes\p_spac_snapper\endcsname
     \scratchcounter\lastnamedcs % already defined
   \else
     \scratchcounter\clf_definesnapmethod{#1}{\m_spac_snapper}%
     \edefcsname\??gridsnapperattributes\p_spac_snapper\endcsname{\the\scratchcounter}%
   \fi
   \edefcsname\??gridsnappers#1\endcsname{\c_attr_snapmethod\the\scratchcounter\relax}%
   \letcsname\??gridsnappersets#1\endcsname\m_spac_snapper}

\permanent\def\theexpandedsnapperset#1{\begincsname\??gridsnappersets#1\endcsname} % only for manuals

\permanent\protected\def\usegridparameter#1% no checking here
  {\edef\m_spac_grid_asked{#1\c!grid}%
   \ifempty\m_spac_grid_asked
     \c_attr_snapvbox\attributeunsetvalue
   \else
     \spac_grids_snap_value_set\m_spac_grid_asked
     \c_attr_snapvbox\c_attr_snapmethod
   \fi}

\permanent\tolerant\protected\def\definegridsnapping[#1]#*[#2]%
  {\installsnapvalues{#1}{#2}}

\edef\spac_grids_snap_value_reset
  {%\gridsnappingfalse
   \c_attr_snapmethod\attributeunsetvalue}

\def\spac_grids_snap_value_set#1%
  {%\gridsnappingtrue
   \begincsname\??gridsnappers#1\endcsname}

% maybe:
%
% \def\spac_grids_snap_value_set#1%
%   {%\gridsnappingtrue
%    \ifcsname\??gridsnappers#1\endcsname
%      \lastnamedcs
%    \else
%      \definegridsnapping[#1][#1]%
%      \begincsname\??gridsnappers#1\endcsname
%    \fi}

\def\spac_grids_snap_value_auto#1%
  {\ifcsname\??gridsnappers#1\endcsname
     \lastnamedcs
   \else
     \installsnapvalues\s!dummy{#1}%
     \csname\??gridsnappers\s!dummy\endcsname
   \fi}

% \installsnapvalues{loose} {\v!maxdepth:0.8,\v!maxheight:0.8,\v!strut}
% \installsnapvalues{normal}{\v!maxdepth:1.0,\v!maxheight:1.0,\v!strut}
% \installsnapvalues{tight} {\v!maxdepth:1.2,\v!maxheight:1.2,\v!strut}

% none             don't enlarge
% halfline         enlarge by halfline/halfline
% line             enlarge by line/line
% strut            enlarge by ht/dp (default)
% first            align to top line
% last             align to bottom line
% mindepth         round depth down
% maxdepth         round depth up
% minheight        round height down
% maxheight        round height up
% local            use local interline space
% offset:-3tp      vertical shift within box
% bottom:lines
% top:lines
% box              centers a box rounded upwards (box:.5 -> tolerance)
% min              centers a box rounded downwards
% max              centers a box rounded upwards

%D We're not downward compatible with \MKII ! Not yet in interface file:

\definegridsnapping[\v!normal]           [\v!maxheight,\v!maxdepth,\v!strut]
\definegridsnapping[\v!standard]         [\v!maxheight,\v!maxdepth,\v!strut]
\definegridsnapping[\v!yes]              [\v!maxheight,\v!maxdepth,\v!strut]

\definegridsnapping[\v!strict]           [\v!maxdepth:0.8,\v!maxheight:0.8,\v!strut]
\definegridsnapping[\v!tolerant]         [\v!maxdepth:1.2,\v!maxheight:1.2,\v!strut]
\definegridsnapping[\v!verytolerant]     [\v!maxdepth:1.4,\v!maxheight:1.4,\v!strut]

\definegridsnapping[\v!tolerant:10]      [\v!maxdepth:1.1,\v!maxheight:1.1,\v!strut] % 10 pct tolerance
\definegridsnapping[\v!tolerant:20]      [\v!maxdepth:1.2,\v!maxheight:1.2,\v!strut] % 20 pct tolerance
\definegridsnapping[\v!tolerant:30]      [\v!maxdepth:1.3,\v!maxheight:1.3,\v!strut] % 30 pct tolerance
\definegridsnapping[\v!tolerant:40]      [\v!maxdepth:1.4,\v!maxheight:1.4,\v!strut] % 40 pct tolerance

\definegridsnapping[\v!top]              [\v!minheight,\v!maxdepth,\v!strut]
\definegridsnapping[\v!bottom]           [\v!maxheight,\v!mindepth,\v!strut]
\definegridsnapping[\v!both]             [\v!minheight,\v!mindepth,\v!strut]

\definegridsnapping[\v!broad]            [\v!maxheight,\v!maxdepth,\v!strut,0.8] % maybe 0.85
\definegridsnapping[\v!fit]              [\v!maxheight,\v!maxdepth,\v!strut,1.2] % tight 0.15

\definegridsnapping[\v!first]            [\v!first]
\definegridsnapping[\v!last]             [\v!last]
\definegridsnapping[\v!high]             [\v!minheight,\v!maxdepth,\v!none]
\definegridsnapping[\v!one]              [\v!minheight,\v!mindepth]
\definegridsnapping[\v!low]              [\v!maxheight,\v!mindepth,\v!none]
\definegridsnapping[\v!none]             [\v!none]
\definegridsnapping[\v!line]             [\v!line]
\definegridsnapping[\v!strut]            [\v!strut]
\definegridsnapping[\v!box]              [\v!box]
\definegridsnapping[\v!min]              [\v!min]
\definegridsnapping[\v!max]              [\v!max]

\definegridsnapping[\v!middle]           [\v!maxheight,\v!maxdepth] % used in placement

\definegridsnapping[\v!math]             [\v!maxdepth:1.05,\v!maxheight:1.05,\v!strut] % experimental, maybe 1.1
\definegridsnapping[\v!math:\v!line]     [\v!math,\v!line,\v!split]
\definegridsnapping[\v!math:\v!halfline] [\v!math,\v!halfline,\v!split]
\definegridsnapping[\v!math:-\v!line]    [\v!math,-\v!line,\v!split]
\definegridsnapping[\v!math:-\v!halfline][\v!math,-\v!halfline,\v!split]

\permanent\protected\def\synchronizelocallinespecs
  {\bodyfontlineheight \normallineheight
   \bodyfontstrutheight\strutht
   \bodyfontstrutdepth \strutdp}

\permanent\protected\def\synchronizegloballinespecs
  {\global\globalbodyfontlineheight \normallineheight
   \global\globalbodyfontstrutheight\strutht
   \global\globalbodyfontstrutdepth \strutdp}

\appendtoks
    \synchronizegloballinespecs
    \synchronizelocallinespecs
\to \everysetupglobalinterlinespace

\appendtoks
    \synchronizelocallinespecs
\to \everysetuplocalinterlinespace

%D More might be added here:

\def\restoreglobalinterlinespace
  {\mutable\let\normallineheight\globalbodyfontlineheight
   \bodyfontlineheight \globalbodyfontlineheight
   \bodyfontstrutheight\globalbodyfontstrutheight
   \bodyfontstrutdepth \globalbodyfontstrutdepth}

% \appendtoks
%     \restoreglobalinterlinespace % done elsewhere
% \to \everybeforepagebody

%D We still have to synchronize these:

\permanent\protected\def\synchronizeskipamounts
  {\bigskipamount
                 \skipfactor\baselineskip
      \s!plus\skipgluefactor\baselineskip
     \s!minus\skipgluefactor\baselineskip
   \relax
   \medskipamount  \bigskipamount \divideby\medskipamount  \plustwo
   \smallskipamount\bigskipamount \divideby\smallskipamount\plusfour}

%D Snapping.

% \newif\ifgridsnapping % already defined

\permanent\tolerant\protected\def\startgridsnapping[#1]%
  {\snaptogrid[#1]\vbox\bgroup}

\permanent\protected\def\stopgridsnapping
  {\egroup}

\permanent\tolerant\protected\def\placeongrid[#1]%
  {\snaptogrid[#1]\vbox} % mark as done

\permanent\tolerant\protected\def\snaptogrid[#1]% list or predefined
  {\ifgridsnapping
     \expandafter\spac_grids_snap_to_indeed % todo: move inline
   \else
     \expandafter\gobbleoneargument
   \fi{#1}}

\def\spac_grids_snap_to_indeed#1%
  {\bgroup
   \spac_grids_snap_value_reset
   \dowithnextbox{\spac_grids_snap_to_finish{#1}}}

% eventually there will always be a line snap

\def\spac_grids_snap_to_finish#1%
  {\ifvbox\nextbox % this will go away
     \clf_vspacingcollapse\nextbox\relax % isn't that already done?
   \fi
   \ifempty{#1}%
     \spac_grids_snap_value_set\v!normal
   \else
     \spac_grids_snap_value_set{#1}%
   \fi
   \clf_vspacingsnap\nextbox\c_attr_snapmethod\relax
   \ifvbox\nextbox\vbox\else\hbox\fi attr \snapmethodattribute \zerocount {\box\nextbox}% no pack (?), we snap
   \egroup}

\def\spac_grids_check_nop
  {\gridsnappingfalse
   \resetsystemmode\v!grid
   \spac_grids_snap_value_reset}

\def\spac_grids_check_yes
  {\gridsnappingtrue
   \setsystemmode\v!grid
   \spac_grids_snap_value_set\p_grid}

\permanent\protected\def\synchronizegridsnapping
  {\edef\p_grid{\layoutparameter\c!grid}%
   \ifx\p_grid\v!no         % official
     \spac_grids_check_nop
   \orelse\ifx\p_grid\v!off % for taco and luigi
     \spac_grids_check_nop
   \orelse\ifempty\p_grid   % to be sure
     \spac_grids_check_nop
   \else
     \spac_grids_check_yes
   \fi}

\permanent\protected\def\setupgridsnapping[#1]% less overhead than setuplayout (needs testing)
  {\setlayoutparameter\c!grid{#1}\synchronizegridsnapping}

\mutable\lettonothing\checkedgridmethod % this can become private

\mutable\let\checkedgridscope\v!local % this can become private

\permanent\protected\def\checkgridmethod#1%
  {\edef\p_grid{#1}%
   \ifempty\p_grid
     \lettonothing\checkedgridmethod
     \let\checkedgridscope\v!local
   \else
     \splitatcolon\p_grid\checkedgridscope\checkedgridmethod
     \ifempty\checkedgridmethod
       \ifx\checkedgridscope\v!local\orelse\ifx\checkedgridscope\v!global\else
         \let\checkedgridmethod\checkedgridscope
         \let\checkedgridscope \v!local
       \fi
     \fi
   \fi}

\permanent\protected\def\applygridmethod#1#2#3% content localsettings (used in head rendering)
   {\checkgridmethod{#1}%
    \ifx\checkedgridscope\v!global
      \ifempty\checkedgridmethod \else
        % we assume that the call is grouped because grouping here has the side
        % effect that the eventually constructed line will get the value outside
        % the group
        %
        % overkill: \setupgridsnapping[\checkedgridmethod]%
        % maybe   : \spac_grids_snap_value_auto\checkedgridmethod
        \spac_grids_snap_value_set\checkedgridmethod
      \fi
      \hbox{#3}%
    \else
      % the extra hbox will trigger the global snapper on top of the local and
      % we really need that in this case (compatibility etc etc) so here we don't
      % het an already done hit (otherwise we would not snap)
      \hbox\bgroup
        \ifempty\checkedgridmethod\orelse\ifconditional\headisdisplay
          #2%
        \fi
        \snaptogrid[\checkedgridmethod]\hbox{#3}%
      \egroup
    \fi}

\protected\gdef\page_layouts_calculate_overshoot
  {\ifgridsnapping\ifcase\layoutlines
     \getnoflines\textheight
     \textovershoot\dimexpr\noflines\globalbodyfontlineheight-\textheight\relax
   \fi\fi}

\protected\def\page_layouts_report_overshoot
  {\page_layouts_calculate_overshoot
   \ifdim\textovershoot>\zeropoint
     \writestatus\m!layouts{gridmode,\space
       noflines: \the\noflines,\space
       textheight: \the\textheight,\space
       textovershoot: \the\textovershoot\space
       (maybe set number of lines instead)%
    }%
  \fi
  \glet\page_layouts_report_overshoot\page_layouts_calculate_overshoot}

\appendtoks
    \page_layouts_report_overshoot
\to \everybeforepagebody

%D Visualization:

\definepalet
  [grid]
  [  one=red,
     two=green,
   three=blue,
    four=gray]

\permanent\protected\def\setgridtracebox#1[#2]% % maybe reverse the order
  {\setbox\nextbox#1%
     {\hbox
        {\hbox to \zeropoint
           {\setlayoutcomponentattribute{\v!grid:\v!test}%
            \color[grid:#2]{\ruledhbox \layoutcomponentboxattribute {\fakebox\nextbox}}%
            \hss}%
         \flushnextbox}}}

\setnewconstant\gridboxlinenomode\plusone % 0:nothing 1:all 2:lines 3:frame 4:l/r
\setnewconstant\gridboxlinemode  \plusone

\permanent\protected\def\gridboxvbox
  {\ifcase\gridboxlinemode
     \vpack
   \or
     \ruledvpack
   \or
     \vpack
   \or
     \ruledvpack
   \else
     \ruledvpack
   \fi}

\permanent\def\gridboxwidth{\ifcase\gridboxlinemode0\or.5\or.5\or0\else.5\fi\linewidth}

\permanent\protected\def\setgridbox#1#2#3% maybe ifgridsnapping at outer level
  {\setbox#1\gridboxvbox to #3 % given size
     {\forgetall
      \resetvisualizers
      \resetteststrut
      \offinterlineskip
      \hsize#2%
      \ifcase\gridboxlinenomode\or\or\or
        \gridboxlinenomode\doifoddpageelse\plusone\plustwo % 3: outer
      \or
        \gridboxlinenomode\doifoddpageelse\plustwo\plusone % 4: inner
      \fi
      \topskipcorrection
      \gridboxvbox % calculated size
        {\getrawnoflines{#3}% \getnoflines{#3}%
         \scratchdimen\dimexpr#2+\lineheight\relax
         \localcontrolledrepeat\noflines
           {\strut
            \hskip-.5\lineheight\relax
            \ifcase\gridboxlinenomode\or
              \rlap
                {\kern\dimexpr.2\bodyfontsize+\scratchdimen\relax
                 \infofont\hbox to \emwidth{\hss\the\currentloopiterator}}%
            \or
              \llap
                {\infofont\hbox to \emwidth{\hss\the\currentloopiterator}%
                 \kern.2\bodyfontsize}%
            \fi
            \vrule
              \s!height \gridboxwidth
              \s!depth  \gridboxwidth
              \s!width  \scratchdimen
            \par}}%
      \vfill}}

%D This has become obsolete:

% \def\moveboxontogrid#1#2#3% will become obsolete, but it needs checking
%   {}

%D Helper:

\protected\def\spac_helpers_assign_skip#1#2% ook nog \v!halfline+fuzzysnap
  {\ifcstok{#2}\v!line
     #1\ifgridsnapping
       \bodyfontlineheight
     \else
       \openlineheight
     \fi
   \else
     \ifgridsnapping
       \assigndimension{#2}{#1}{.25\bodyfontlineheight}{.5\bodyfontlineheight}\bodyfontlineheight
     \else
       \assigndimension{#2}{#1}\smallskipamount\medskipamount\bigskipamount
     \fi
   \fi
   \relax}

% \start \dosetstretch{.25em} \setuptolerance[tolerant,stretch] \input tufte \endgraf \stop
% \start \dosetstretch{.5em} effe flink doorfietsen \stop

% experimental code, not yet interfaced:

% category:
%
%  0 == discard                             discard
%  1 == only if larger                      largest
%  2 == force even if smaller               force
%  3 == only take penalty component         penalty
%  4 == add to existing skip                add
%  5 == disable (ignore following)          disable
%  6 == kill whitespace                     nowhite
%  7 == discard previous                    back
% 10 == no topskip
%
% penalty: larger wins
% order: larger wins
% category:2,order:5,penalty:10000,skip:value|kw
%
% \defineblankmethod  [\v!joinedup] {\ifvmode\nointerlineskip\fi}

% todo, in grid mode: builders.vspacing.fixed = false
%
% \ifgridsnapping will go

\installcorenamespace{vspacing}
\installcorenamespace{vspacingamount}

\permanent\tolerant\protected\def\definevspacingamount[#1]#*[#2]#*[#3]% can be combined
  {\ifarguments
     %defcsname\??vspacingamount#1\endcsname{\lineheight}%
   \or
     \defcsname\??vspacingamount#1\endcsname{\lineheight}%
   \or
     \defcsname\??vspacingamount#1\endcsname{\ifgridsnapping\lineheight\else#2\fi}%
   \or
     \defcsname\??vspacingamount#1\endcsname{\ifgridsnapping#3\else#2\fi}%
   \fi
   \clf_vspacingsetamount{#1}}

\def\spac_vspacing_no_topskip % use grouped
  {\c_attr_skipcategory\pluseleven}

%permanent\def\vspacingfromscratchtoks  {\scratchdimen\dimexpr\csname\??vspacingamount\the\scratchtoks\endcsname\relax}
\permanent\def\vspacingpredefinedvalue#1{\s_spac_vspacing_predefined\glueexpr\csname\??vspacingamount#1\endcsname\relax}
%permanent\def\vspacingfromtempstring   {\scratchdimen\dimexpr\csname\??vspacingamount\tempstring\endcsname\relax}

% \installcorenamespace{vspacingamountnormal}
% \installcorenamespace{vspacingamountgrid}

% \def\spac_vspacing_define_amount[#1][#2][#3]% can be combined
%   {\ifcsname n>#1\endcsname\else
%      \expandafter\newtoks\csname n>#1\endcsname
%      \expandafter\newtoks\csname g>#1\endcsname
%    \fi
%    \csname n>#1\endcsname{#2}%
%    \csname g>#1\endcsname{#3}%
%    \clf_vspacingsetamount{#1}}

\permanent\tolerant\protected\def\definevspacing[#1]#*[#2]%
  {\clf_vspacingdefine{#1}{#2}}

%D The injector code (generated at the \LUA\ end). This will go away!

% \newtoks\everybeforeblankhandling
% \newtoks\everyafterblankhandling
%
% \newconditional\c_space_vspacing_done
% \newconditional\c_space_vspacing_fixed
% \newconditional\c_space_ignore_parskip
%
% \appendtoks
%     \s_spac_vspacing_temp\zeropoint
%     \c_attr_skipcategory\plusone
%     \c_attr_skippenalty \attributeunsetvalue
%     \c_attr_skiporder   \attributeunsetvalue
%     \ifgridsnapping
%         \c_space_vspacing_fixed\conditionaltrue
%     \else
%         \c_space_vspacing_fixed\conditionalfalse
%     \fi
% \to \everybeforeblankhandling
%
% \appendtoks
%     \s_spac_vspacing_temp\plusone\s_spac_vspacing_temp
%       \ifconditional\c_space_vspacing_fixed \else
%         \s!plus \skipgluefactor\s_spac_vspacing_temp
%         \s!minus\skipgluefactor\s_spac_vspacing_temp
%       \fi
%     \relax
% \to \everyafterblankhandling

% The main spacer:

\permanent\tolerant\protected\def\vspacing[#1]%
  {\ifarguments
     \spac_vspacing_yes[\currentvspacing]% in the new mechanism no [] needed
   \else
     \spac_vspacing_yes[#1]% in the new mechanism no [] needed
   \fi}

\def\spac_vspacing_yes
  {\ifinpagebody % somewhat weird
     \expandafter\spac_vspacing_yes_indeed
   \orelse\ifconditional\c_spac_packed_blank
     \expandafter\spac_vspacing_yes_indeed
   \else
     \expandafter\spac_vspacing_yes_ignore
   \fi}

\def\spac_vspacing_nop
  {\ifinpagebody % somewhat weird
     \expandafter\spac_vspacing_nop_indeed
   \orelse\ifconditional\c_spac_packed_blank
     \expandafter\spac_vspacing_nop_indeed
   \else
     \expandafter\spac_vspacing_nop_ignore
   \fi}

\def\spac_vspacing_yes_indeed[#1]{\ifmmode\else\par\ifvmode\clf_injectvspacing\ifgridsnapping\plusone\else\zerocount\fi{#1}\fi\fi}
\def\spac_vspacing_nop_indeed    {\ifmmode\else\par\ifvmode\clf_injectvspacing\ifgridsnapping\plusone\else\zerocount\fi{\currentvspacing}\fi\fi}

\def\spac_vspacing_yes_ignore[#1]{\ifmmode\else\par\fi}
\def\spac_vspacing_nop_ignore    {\ifmmode\else\par\fi}

\permanent\protected\def\directvspacing#1%
 {\ifmmode\else\par\ifvmode\clf_injectvspacing\ifgridsnapping\plusone\else\zerocount\fi{\ifempty{#1}\currentvspacing\else#1\fi}\fi\fi}

\permanent\protected\def\directdefaultvspacing
  {\ifinpagebody % somewhat weird
     \directvspacing\currentvspacing
   \orelse\ifconditional\c_spac_packed_blank
     \directvspacing\currentvspacing
   \fi}

\permanent\protected\def\directcheckedvspacing
  {\ifinpagebody % somewhat weird
     \expandafter\directvspacing
   \orelse\ifconditional\c_spac_packed_blank
     \expandafter\directvspacing
   \else
     \expandafter\gobbleoneargument
   \fi}

\permanent\protected\def\useblankparameter#1% faster local variant
  {\edef\m_spac_blank_asked{#1\c!blank}%
   \ifempty\m_spac_blank_asked\else
     \directvspacing\m_spac_blank_asked
   \fi}

%D Handy (and faster):

\permanent\protected\def\directvpenalty#1{\ifmmode\else\par\ifvmode\clf_injectvpenalty#1\relax\fi\fi}
\permanent\protected\def\directvskip   #1{\ifmmode\else\par\ifvmode\clf_injectvskip   #1\relax\fi\fi}

%D These depend on bigskipamount cum suis so we'd better sync them:

\mutable\let\currentvspacing\s!default % hm, default, standard ...

\permanent\tolerant\protected\def\setupvspacing[#1]%
  {\ifarguments\else
     \edef\currentvspacing{#1}%
   \fi
   \synchronizevspacing}

\permanent\protected\def\synchronizevspacing
  {\ifempty\currentvspacing % mistakenly had an \else
     \let\currentvspacing\s!default
   \fi
   \spac_whitespace_setup_nop}

\permanent\protected\def\restorestandardblank % or default ?
  {\let\currentvspacing\v!standard}

%D The \type {category:4} is default.

\definevspacingamount[\v!none]        [\zeroskip]          [\zeroskip]
\definevspacingamount[\v!big]         [\bigskipamount]     [\bodyfontlineheight]
\definevspacingamount[\v!medium]      [\medskipamount]     [.5\bodyfontlineheight]
\definevspacingamount[\v!small]       [\smallskipamount]   [.25\bodyfontlineheight]
\definevspacingamount[\v!line]        [\openlineheight]    [\bodyfontlineheight]
\definevspacingamount[\v!halfline]    [.5\openlineheight]  [.5\bodyfontlineheight]
\definevspacingamount[\v!quarterline] [.25\openlineheight] [.25\bodyfontlineheight]
\definevspacingamount[\v!formula]     [\medskipamount]     [.5\bodyfontlineheight]
\definevspacingamount[\v!white]       [\parskip]           [\bodyfontwhitespace]
\definevspacingamount[\v!height]      [\strutht]           [\bodyfontstrutheight]
\definevspacingamount[\v!depth]       [\strutdp]           [\bodyfontstrutdepth]

\definevspacingamount[\v!standard]    [.75\openlineheight] [.75\openlineheight] % mkii compatible

\permanent\def\bodyfontwhitespace
  {\dimexpr
     \ifzeropt\parskip
       \zeropoint
     \orelse\ifgridsnapping
       \bodyfontlineheight
     \else
       \parskip
     \fi
   \relax}

%D used in itemize \unknown\ always test this:

\def\spac_overlay_lines
  {\directcheckedvspacing{\v!back,\v!overlay}% \blank[\v!back,\v!overlay]%
   \nointerlineskip}

% \startitemize[n]
%     \item \input zapf
%     \item \startitemize[a]
%         \item \input knuth
%     \stopitemize
% \stopitemize
%
% \strut \hfill first line \blank[overlay] second line \hfill \strut
%
% \ruledvbox {
%     \strut \hfill line 1 \blank[overlay]
%     line 2 \hfill \strut \blank[overlay]
%     \strut \hfill line 3 \hfill \strut
% }
%
% \dorecurse{50}
%   {\startitemize[n] \startitem \startitemize[a] \item #1 \stopitemize \stopitem \stopitemize}

\definevspacing[\v!preference][penalty:-500]  % goodbreak
\definevspacing[\v!samepage]  [penalty:10000] % nobreak

\definevspacing[\v!always]    [category:0] % hm, internally it's discard
\definevspacing[\v!max]       [category:1]
\definevspacing[\v!force]     [category:2]
\definevspacing[\v!disable]   [category:5]
\definevspacing[\v!nowhite]   [category:6]
\definevspacing[\v!back]      [category:7]
\definevspacing[\v!packed]    [category:8]  % noparskip (kind of special)
\definevspacing[\v!overlay]   [category:9]
\definevspacing[\v!enable]    [category:10]

%definevspacing[\v!noparskip] [category:8]
%definevspacing[\v!notopskip] [category:11]

\definevspacing[\v!weak]      [order:0]
\definevspacing[\v!strong]    [order:100]

\definevspacing[\s!default]   [\v!white] % was big for a while

\newinteger\c_spac_vspacing_special_base \c_spac_vspacing_special_base = 32250 % 4000
\newinteger\c_spac_vspacing_special_step \c_spac_vspacing_special_step =    10 %  250
\newinteger\c_spac_vspacing_special_done

% 2019-05-31 : upgraded a bit to more distinctive samepage-[level]-[0|1|2] names

\protected\def\spac_vspacing_define_same_step#1#2% alternatively we could have samepage-n-m
  {\begingroup
   \scratchcounterone\numexpr\plusthree*#1+#2\relax
   \scratchcountertwo\numexpr\c_spac_vspacing_special_base+\c_spac_vspacing_special_step*\scratchcounterone\relax
  %\writestatus{defined}{\v!samepage-\number#1-\number#2\space=>\space penalty:\the\scratchcountertwo}%
   \normalexpanded{\definevspacing[\v!samepage-\number#1-\number#2][penalty:\the\scratchcountertwo]}%
   \endgroup}

% \protected\def\spac_vspacing_define_same_page#1%
%   {\dostepwiserecurse\c_spac_vspacing_special_done{#1}\plusone
%      {\spac_vspacing_define_same_step\recurselevel\zerocount % before
%       \spac_vspacing_define_same_step\recurselevel\plusone   % after
%       \spac_vspacing_define_same_step\recurselevel\plustwo}% % whatever
%    \global\c_spac_vspacing_special_done#1\relax}

\protected\def\spac_vspacing_define_same_page#1%
  {\localcontrolledloop\c_spac_vspacing_special_done=#1=\plusone
     {\spac_vspacing_define_same_step\currentloopiterator\zerocount % before
      \spac_vspacing_define_same_step\currentloopiterator\plusone   % after
      \spac_vspacing_define_same_step\currentloopiterator\plustwo}% % whatever
   \global\c_spac_vspacing_special_done#1\relax}

\spac_vspacing_define_same_page{12} % 12 levels should be more than enough as a start

\def\spac_vspacing_same_page#1#2% level offset (starts at 0)
  {\ifnum#1>\c_spac_vspacing_special_done
     \spac_vspacing_define_same_page{#1}%
   \fi
  %\writestatus{used}{\v!samepage-\number#1-\number#2}%
   \vspacing[\v!samepage-\number#1-\number#2]}

\definevspacing[\v!default]  [\v!big]      % todo: needs to adapt to \setupblank
\definevspacing[\v!before]   [\v!default]  % but we need to avoid circular references
\definevspacing[\v!inbetween][\v!default]  % then
\definevspacing[\v!after]    [\v!before]

\setupvspacing
  [\v!big] % alternatively [\v!standard]

%D Maybe at some point we will differ between \type {\vspacing} and \type {\blank}
%D (we needed the first one while playing with the new code).

% We keep this one as reference
%
% \protected\def\inhibitblank
%   {\vspacing[\v!disable]}
%
% but use the following more efficient variant instead:

\permanent\protected\def\inhibitblank{\ifmmode\else\par\ifvmode\clf_injectdisable\fi\fi}

\aliased\let\doinhibitblank\inhibitblank % keep this command for a while, used in styles

%D We use \type {vspacing} because at some point we had the blank mechanism alongside
%D a new experimental variant and I aliased it locally till it all worked out well. So,
%D we're kind of stuck with synonyms now. Some day we will drop the vspacing and use the
%D old aliases instead.

\aliased\let\defineblank      \definevspacing
\aliased\let\setupblank       \setupvspacing
\aliased\let\blank            \vspacing
\aliased\let\synchronizeblank \synchronizevspacing
\aliased\let\defineblankmethod\definevspacingamount

%D The following command is for Wolfgang. It has to be used with care as it does
%D {\em not} work in tandem with the other spacing commands.

\installcorenamespace{vspace}

\permanent\tolerant\protected\def\definevspace[#1]#*[#2]#*[#3]%
  {\ifarguments
   \or
     \letcsname\??vspace:#1\endcsname\empty
   \or
     \defcsname\??vspace:#1\endcsname{#2}%
   \or
     \defcsname\??vspace#1:#2\endcsname{#3}%
   \fi}

\letcsname\??vspace:\s!unknown\endcsname\zeropoint

\def\spac_vspace_unknown
  {\csname\??vspace:\s!unknown\endcsname} % why not a let?

\permanent\tolerant\protected\def\vspace[#1]#*[#2]%
  {\par
   \ifvmode
     \removelastskip
     \vskip
       \ifparameter#2\or
         \ifcsname\??vspace#1:#2\endcsname
           \lastnamedcs
         \orelse\ifcsname\??vspace:#2\endcsname
           \lastnamedcs
         \else
           \spac_vspace_unknown
         \fi
       \orelse\ifparameter#1\or
         \ifcsname\??vspace:#1\endcsname
           \lastnamedcs
         \else
           \spac_vspace_unknown
         \fi
       \else
         \ifcsname\??vspace:\s!default\endcsname
           \lastnamedcs
         \else
           \spac_vspace_unknown
         \fi
       \fi
     \relax
   \fi}

%D Some preliminary code: a simple and fast hanger, for usage in macros.

\installcorenamespace {hanging}

\installdirectcommandhandler \??hanging {hanging}

\setuphanging
  [\c!distance=.5\emwidth,
   \c!location=\v!left,
   \c!n=\zerocount]

\lettonothing\m_spac_hanging_location

\permanent\tolerant\protected\def\starthanging[#1]%
  {\dontleavehmode\bgroup
   \ifhastok={#1}%
     \lettonothing\m_spac_hanging_location
     \setupcurrenthanging[#1]%
   \else
     \edef\m_spac_hanging_location{#1}%
   \fi
   \ifempty\m_spac_hanging_location
     \edef\m_spac_hanging_location{\directhangingparameter\c!location}%
   \fi
   \dowithnextboxcs\spac_hanging_finish\hbox}

\permanent\protected\def\stophanging
  {\endgraf
   \egroup}

\def\spac_hanging_finish
  {\scratchdistance\directhangingparameter\c!distance\relax
   \ifdim\ht\nextbox>\strutht
     \setbox\nextbox\tbox{\box\nextbox}%
   \fi
   \scratchcounter\directhangingparameter\c!n\relax
   \ifnum\scratchcounter>\zerocount
     \frozen\hangafter-\scratchcounter
   \else
     \getboxheight\scratchdimen\of\box\nextbox
     \getnoflines\scratchdimen
     \frozen\hangafter-\noflines
   \fi
   \ht\nextbox\strutht
   \dp\nextbox\strutdp
   \scratchwidth\dimexpr\wd\nextbox+\scratchdistance\relax
   \ifx\m_spac_hanging_location\v!right
     \frozen\hangindent\ifconditional\displaylefttoright-\fi\scratchwidth
     \rlap{\kern\dimexpr\hsize-\leftskip-\wd\nextbox\relax\box\nextbox}% \leftskip is new
   \else
     \frozen\hangindent\ifconditional\displaylefttoright\else-\fi\scratchwidth
     \llap{\box\nextbox\kern\scratchdistance}%
   \fi
   \ignorespaces}

%D \macros
%D   {startfixed}
%D
%D \starttyping
%D \startitemize
%D     \startitem \externalfigure[cow][height=1cm] \stopitem
%D     \startitem \externalfigure[cow][height=1cm] \stopitem
%D
%D     \startitem \startfixed      \externalfigure[cow][height=1cm]\stopfixed \stopitem
%D     \startitem \startfixed[high]\externalfigure[cow][height=1cm]\stopfixed \stopitem
%D     \startitem \startfixed[low] \externalfigure[cow][height=1cm]\stopfixed \stopitem
%D     \startitem \startfixed[lohi]\externalfigure[cow][height=1cm]\stopfixed \stopitem
%D
%D     \startitem test \par \startfixed      \externalfigure[koe][height=1cm]\stopfixed \stopitem
%D     \startitem test \par \startfixed[high]\externalfigure[koe][height=1cm]\stopfixed \stopitem
%D     \startitem test \par \startfixed[low] \externalfigure[koe][height=1cm]\stopfixed \stopitem
%D     \startitem test \par \startfixed[lohi]\externalfigure[koe][height=1cm]\stopfixed \stopitem
%D \stopitemize
%D \stopbuffer
%D
%D \typebuffer \getbuffer

\installcorenamespace{fixedalternatives}

\permanent\protected\lettonothing\stopfixed

\permanent\protected\def\startfixed
  {\bgroup
   \ifhmode
     \expandafter\typo_fixed_start_h
   \else
     \expandafter\typo_fixed_start_v
   \fi}

\tolerant\def\typo_fixed_start_h[#1]%
  {\enforced\let\stopfixed\typo_fixed_stop_h
   \dowithnextbox{\typo_fixed_finish{#1}}%
   \vbox\bgroup
   %ignorespaces
   \setlocalhsize}

\protected\def\typo_fixed_stop_h
  {%removeunwantedspaces
   \egroup
   \egroup}

\tolerant\def\typo_fixed_start_v[#1]%
  {\enforced\let\stopfixed\typo_fixed_stop_v
   \startbaselinecorrection}

\protected\def\typo_fixed_stop_v
  {\stopbaselinecorrection
   \egroup}

\letcsname\??fixedalternatives   \v!high\endcsname\bbox
\letcsname\??fixedalternatives    \v!low\endcsname\tbox
\letcsname\??fixedalternatives \v!middle\endcsname\vcenter
\letcsname\??fixedalternatives   \v!lohi\endcsname\vcenter
\letcsname\??fixedalternatives\s!unknown\endcsname\tbox
\letcsname\??fixedalternatives\s!default\endcsname\tbox

\protected\def\typo_fixed_finish#1%
  {\expandnamespacevalue\??fixedalternatives{#1}\s!default{\box\nextbox}}

% %D Forgotten already:
%
% \def\shapefill{\vskip\zeropoint\s!plus\lineheight\s!minus\lineheight\relax}

%D Nasty:

% \writestatus{1}{\the\prevdepth} \blank[force,5*big] { \writestatus{1}{\the\prevdepth} \baselineskip1cm xxxxxxxxx \par } \page
% \writestatus{2}{\the\prevdepth} \blank[force,5*big] { \writestatus{2}{\the\prevdepth} \baselineskip1cm xxxxxxxxx \par } \page
% \writestatus{3}{\the\prevdepth} \blank[force,5*big] { \writestatus{3}{\the\prevdepth} \baselineskip5cm xxxxxxxxx \par } \page
% \writestatus{4}{\the\prevdepth} \input tufte                                                                            \page
% \writestatus{5}{\the\prevdepth} \input tufte                                                                            \page
% \writestatus{6}{\the\prevdepth} \blank[force,5*big] { \writestatus{6}{\the\prevdepth} \baselineskip1cm xxxxxxxxx \par } \page

% \writestatus{1}{\the\prevdepth} \null\vskip4cm      { \writestatus{1}{\the\prevdepth} \baselineskip1cm xxxxxxxxx \par } \page
% \writestatus{2}{\the\prevdepth} \null\vskip4cm      { \writestatus{2}{\the\prevdepth} \baselineskip1cm xxxxxxxxx \par } \page
% \writestatus{3}{\the\prevdepth} \null\vskip4cm      { \writestatus{3}{\the\prevdepth} \baselineskip5cm xxxxxxxxx \par } \page
% \writestatus{4}{\the\prevdepth} \input tufte                                                                            \page
% \writestatus{5}{\the\prevdepth} \input tufte                                                                            \page
% \writestatus{6}{\the\prevdepth} \null\vskip4cm      { \writestatus{6}{\the\prevdepth} \baselineskip1cm xxxxxxxxx \par } \page

\appendtoks
    \ifvmode\prevdepth\zeropoint\fi % consistent, else first page -1000pt .. needed for fixed,3*big first/successive pages consistency
\to \everystarttext

\prevdepth\zeropoint

%D Helper:

\permanent\protected\def\checkedblank[#1]%
  {\edef\p_blank{#1}%
   \ifempty\p_blank
     % ignore
   \orelse\ifx\p_blank\v!none
     % ignore
   \else
     \blank[\p_blank]%
   \fi}

% \setupwhitespace[line]
% \prerollblank[2*line]  \the\prerolledblank
% \prerollblank[-2*line] \the\prerolledblank

\newgluespec\prerolledblank

\permanent\protected\def\prerollblank[#1]%
  {\begingroup
   \edef\p_blank{#1}%
   \ifempty\p_blank
     \global\prerolledblank\zeropoint
   \orelse\ifx\p_blank\v!none
     \global\prerolledblank\zeropoint
   \else
     % don't mess with \arskip here!
     \scratchskip\plusten\lineheight
     \setbox\scratchbox\vbox
       {\vskip\scratchskip
        \kern\zeropoint
        \blank[\p_blank]}%
     % \dimexpr doesn't work well with skips
     \advanceby\scratchskip-\ht\scratchbox
     \global\prerolledblank-\scratchskip
   \fi
   \endgroup}

% \setupwhitespace[line]
% \setuphead[subject][after={\blank[packed]},style=\bfb]
% \subject{foo}
% test \par
% test \par
% \blank[packed] % \ignoreparskip
% test \par
% test \par
% \ignoreparskip
% test \par
% test \par
% \setuphead[subject][after={\blank[nowhite]},style=\bfb]
% \subject{foo}
% test \par
% test \par

\permanent\protected\def\ignoreparskip{\c_spac_vspacing_ignore_parskip\plusone}

%D New, use with care:
%D
%D \starttyping
%D test test test \hfill\break
%D test \blankbefore test
%D test \blankbefore[2*line] test
%D test \blankbefore test
%D test test test \hfill\break
%D test \blankbefore test
%D test \blankbefore test
%D test \blankbefore test
%D test test test \hfill\break
%D test \blankafter test
%D test \blankafter test
%D test \blankafter test
%D test test test \hfill\break
%D test test test \hfill\break
%D \stoptyping

\permanent\tolerant\protected\def\blankbefore[#1]{\vadjust pre {\ifempty{#1}\blank\else\blank[#1]\fi}}
\permanent\tolerant\protected\def\blankafter [#1]{\vadjust post{\ifempty{#1}\blank\else\blank[#1]\fi}}

%D Experimental:

\installcorenamespace{textdisplay}

\installcommandhandler \??textdisplay {textdisplay} \??textdisplay

\appendtoks
    \frozen\protected\edefcsname\e!start\currenttextdisplay\endcsname{\spac_textdisplay_start{\currenttextdisplay}}%
    \frozen\protected\edefcsname\e!stop \currenttextdisplay\endcsname{\spac_textdisplay_stop}%
\to \everydefinetextdisplay

\setuptextdisplay
  [\c!factor=.5,
   \c!after=\textdisplayparameter\c!before]

\tolerant\protected\def\spac_textdisplay_start#1#:[#S#2]%
  {\par
   \begingroup
   \whitespace
   \forgetall
   \edef\currenttextdisplay{#1}%
   \setupcurrenttextdisplay[#2]%
   \textdisplayparameter\c!before
   \dpack
   \bgroup
   \strut
   \vskip-\lineheight
   \vpack
   \s!yoffset -\textdisplayparameter\c!factor\strutdp
   \bgroup}

\protected\def\spac_textdisplay_stop
  {\egroup
   \vskip-\lineheight
   \strut
   \egroup
   \textdisplayparameter\c!after
   \endgroup
   \par}

\definetextdisplay[\v!textdisplay]

% \useMPlibrary[dum]
%
% \definetextdisplay[steppedbystep][before={\blank[halfline]}]
%
% \starttext \showmakeup[line]
%   \dostepwiserecurse{10}{25}{1}{
%       xxxxxxxxxxxxxxxx\par
%       \starttextdisplay
%         \externalfigure[dummydummy][height=#1mm,text=no]%
%       \stoptextdisplay
%       xxxxxxxxxxxxxxxx\par
%       \starttextdisplay[before=\blank,after=\blank] % factor=.5
%         \externalfigure[dummydummy][height=#1mm,text=no]%
%       \stoptextdisplay
%       xxxxxxxxxxxxxxxx\par
%       \startsteppedbystep
%         \externalfigure[dummydummy][height=#1mm,text=no]%
%       \stopsteppedbystep
%       xxxxxxxxxxxxxxxx\par
%       \page
%   }
% \stoptext

\protect \endinput

