spac-ver.mkiv / last modification: 2020-01-30 14:16
%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

\registerctxluafile{spac-ver}{optimize}

% 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

\newskip \s_spac_vspacing_temp \s_spac_vspacing_temp\bigskipamount

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

\def\normalskipamount
  {\openlineheight
     \ifgridsnapping \else \ifblankflexible
       \s!plus \skipgluefactor\openlineheight
       \s!minus\skipgluefactor\openlineheight
     \fi \fi
   \relax}

\ifdefined\bodyfontinterlinespace \else
  \let\bodyfontinterlinespace\empty
\fi

\unexpanded\def\presetnormallineheight % each bodyfont
  {\edef\normallineheight{\interlinespaceparameter\c!line}%
   \iflocalinterlinespace \else
     \edef\m_spac_normallineheight{\bodyfontinterlinespace}%
     \ifx\m_spac_normallineheight\empty \else
       \let\normallineheight\m_spac_normallineheight
     \fi
   \fi}

\unexpanded\def\setupspecifiedinterlinespace[#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\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}

\let\setrelativeinterlinespace    \relax % used elsewhere
\let\currentrelativeinterlinespace\empty

\setvalue{\??interlinespacerelative\v!on   }{\oninterlineskip}
\setvalue{\??interlinespacerelative\v!off  }{\offinterlineskip}
\setvalue{\??interlinespacerelative\v!reset}{\let\currentrelativeinterlinespace\empty
                                             \let\setrelativeinterlinespace\relax
                                             \setfontparameters}
\setvalue{\??interlinespacerelative\v!auto }{\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}}

\unexpanded\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}

\def\spac_linespacing_set_relative_interlinespace
  {\ifx\currentrelativeinterlinespace\empty\else
     \spacing\currentrelativeinterlinespace
   \fi}

\unexpanded\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}

\unexpanded\def\useinterlinespaceparameter#1% see footnotes
  {\edef\m_spac_interlinespace{#1\c!interlinespace}%
   \ifx\m_spac_interlinespace\empty \else
     \spac_linespacing_setup_use
   \fi}

\newtoks\everysetupglobalinterlinespace
\newtoks\everysetuplocalinterlinespace

\newconditional\interlinespaceisset

\installcorenamespace{interlinespace}

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

\installmacrostack\currentinterlinespace

\unexpanded\def\setupinterlinespace
  {\dodoubleempty\spac_linespacing_setup}

\ifdefined\setupinterlinespace_double \else
    \let\setupinterlinespace_double\setup_interlinespace % for a while
\fi

\def\spac_linespacing_setup[#1][#2]%
  {\settrue\interlinespaceisset % reset has to be done when needed
   \ifsecondargument
     \setupinterlinespace_double[#1][#2]%
   \else\iffirstargument
     \ifcsname\namedinterlinespacehash{#1}\s!parent\endcsname
        \edef\currentinterlinespace{#1}%
        \spac_linespacing_setup_specified_interline_space
       %\dosetupspecifiedinterlinespaceindeed
     \else
       \spac_linespacing_setup_specified_or_relative[#1]%
     \fi
   \else
     \let\currentinterlinespace\empty
     \spac_linespacing_synchronize_local
   \fi\fi}

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

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

\unexpanded\def\dosetupcheckedinterlinespace#1% often a chain
  {\edef\p_spac_checked_interlinespace{#1}%
   \ifx\p_spac_checked_interlinespace\empty
     \spac_linespacing_synchronize_local
   \else\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
       \the\everysetuplocalinterlinespace
     \else
       \localinterlinespacetrue
       \the\everysetuplocalinterlinespace
       \localinterlinespacefalse
     \fi
     \pop_macro_currentinterlinespace
   \else
     \normalexpanded{\noexpand\doifelseassignment{\p_spac_checked_interlinespace}%
       \setupspecifiedinterlinespace\setuprelativeinterlinespace[\p_spac_checked_interlinespace]}%
     \iflocalinterlinespace
       \the\everysetuplocalinterlinespace
     \else
       \localinterlinespacetrue
       \the\everysetuplocalinterlinespace
       \localinterlinespacefalse
     \fi
   \fi\fi}

\unexpanded\def\setuplocalinterlinespace[#1]%
  {\localinterlinespacetrue
   \push_macro_currentinterlinespace
   \setupinterlinespace[#1]%
   \pop_macro_currentinterlinespace
   \localinterlinespacefalse}

\let\switchtointerlinespace\setuplocalinterlinespace

%D Helpers

\newskip \s_spac_lastskip
\newdimen\d_spac_prevdepth
\newcount\c_spac_spacefactor
\newdimen\d_spac_prevcontent % set by lua

\unexpanded\def\removelastskip
  {\ifvmode\ifzeropt\lastskip\else\vskip-\lastskip\fi\fi}

\def\doifoutervmode
  {\ifvmode
     \ifinner
       \doubleexpandafter\gobbleoneargument
     \else
       \doubleexpandafter\firstofoneargument
     \fi
   \else
     \expandafter\gobbleoneargument
   \fi}

\unexpanded\def\dosomebreak#1%
  {\doifoutervmode
     {\s_spac_lastskip\lastskip
      \removelastskip
      #1\relax
      \ifzeropt\s_spac_lastskip
        % avoid interference with footnotes
      \else
        \vskip\s_spac_lastskip
      \fi}}

\unexpanded\def\packed
  {\nointerlineskip}

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

\unexpanded\def\smallskip{\vskip\smallskipamount}
\unexpanded\def\medskip  {\vskip\medskipamount}
\unexpanded\def\bigskip  {\vskip\bigskipamount}

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

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

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

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

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

%D Made slightly more readable:

\unexpanded\def\vglue  {\afterassignment\spac_helpers_vglue_indeed\s_spac_lastskip=}
\unexpanded\def\hglue  {\afterassignment\spac_helpers_hglue_indeed\s_spac_lastskip=}
\unexpanded\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:

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

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

\installcorenamespace{whitespacemethod}

\newskip       \s_spac_whitespace_parskip   \s_spac_whitespace_parskip\zeropoint
\newconditional\c_spac_whitespace_flexible  \settrue\c_spac_whitespace_flexible
\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

\unexpanded\def\setupwhitespace
  {\doifelsenextoptionalcs\spac_whitespace_setup_yes\spac_whitespace_setup_nop}

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

\let\synchronizewhitespace\spac_whitespace_setup_nop

\def\spac_whitespace_setup_yes[#1]%
  {\edef\m_spac_whitespace_asked{#1}%
   \ifx\m_spac_whitespace_asked\empty
     \spac_whitespace_setup_nop
   \else
     \let\v_spac_whitespace_current\m_spac_whitespace_asked
     \spac_whitespace_setup
   \fi}

\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
  {\setfalse\c_spac_whitespace_flexible
   \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}

\unexpanded\def\installwhitespacemethod#1#2%
  {\setvalue{\??whitespacemethod#1}{#2}}

\installwhitespacemethod \v!fix         {}
\installwhitespacemethod \v!fixed       {\setfalse\c_spac_whitespace_flexible}
\installwhitespacemethod \v!flexible    {\settrue \c_spac_whitespace_flexible}

\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   \zeropoint}
\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}

\unexpanded\def\forgetparskip
  {\s_spac_whitespace_parskip\zeropoint
   \parskip\zeropoint
   \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}

\unexpanded\def\nowhitespace{\directcheckedvspacing\v!nowhite} % {\vspacing[\v!nowhite]}
\unexpanded\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 \settrue\c_spac_packed_blank
\newcount      \c_spac_packed_level

\unexpanded\def\startpacked
  {\dosingleempty\spac_packed_start}

\def\spac_packed_start[#1]% nesting afvangen
  {\global\advance\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
     \doifelse{#1}\v!blank\settrue\setfalse\c_spac_packed_blank
     \setupwhitespace[\v!none]% or \forgetparskip
   \fi \fi}

\unexpanded\def\stoppacked
  {\par
   \ifnum\c_spac_packed_level=\plusone \ifvmode
     \endgroup
   \fi \fi
   \global\advance\c_spac_packed_level\minusone}

\unexpanded\def\startunpacked
  {\directdefaultvspacing % \blank
   \begingroup}

\unexpanded\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}
  {\let\spac_lines_vbox\ruledvbox}
  {\let\spac_lines_vbox\vbox}

\let\v_spac_lines_around_action_set\relax
\let\m_spac_lines_around           \empty

\newconstant\c_spac_lines_correction_mode

\setvalue{\??linesaround\v!blank  }{\blank}
\letvalue{\??linesaround\empty    }\relax
\setvalue{\??linesaround\s!unknown}{\directcheckedvspacing\m_spac_lines_around} % \blank[\m_spac_lines_around]}

\def\spac_lines_action_around % we used to let this one but it's cleaner this way
  {\csname\??linesaround      % i.e. do it twice
     \ifcsname\??linesaround\m_spac_lines_around\endcsname\m_spac_lines_around\else\s!unknown\fi
   \endcsname}

\unexpanded\def\startlinecorrection
  {\endgraf
   \begingroup
   \setconstant\c_spac_lines_correction_mode\plusone
   \dosingleempty\spac_lines_start_correction}

\unexpanded\def\startlocallinecorrection
  {\endgraf
   \begingroup
   \setconstant\c_spac_lines_correction_mode\plustwo
   \dosingleempty\spac_lines_start_correction}

\unexpanded\def\spac_lines_start_correction[#1]%
  {\edef\m_spac_lines_around{#1}%
   \spac_lines_action_around
   \d_spac_prevdepth\prevdepth
   \spac_lines_initialize_corrections
   \offbaselinecorrection % ???
   \setbox\scratchbox\spac_lines_vbox\bgroup
     \ifcase\c_spac_lines_correction_mode
       % nothing
     \or
       % global
     \or
       % local
       \setlocalhsize
       \hsize\localhsize
       \forgetbothskips
     \fi
     \ignorespaces}

\unexpanded\def\spac_lines_stop_correction
  {\removeunwantedspaces
   \egroup
   \ifgridsnapping
     \spac_lines_stop_correction_ongrid
   \else
     \spac_lines_stop_correction_normal
   \fi
   \endgroup}

\unexpanded\def\spac_lines_stop_correction_ongrid
  {\directcheckedvspacing\v!white % \blank[\v!white]%
   \spac_lines_action_around
   \snaptogrid\hpack{\box\scratchbox}%
   \directcheckedvspacing\v!white
   \spac_lines_action_around}

\unexpanded\def\spac_lines_stop_correction_normal
  {\directcheckedvspacing\v!nowhite % \blank[\v!nowhite]%
   \ifdim\parskip>\zeropoint
      % too fuzzy otherwise
   \else
      % doesn't like whitespace
     \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 % 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}

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

% todo:

\unexpanded\def\correctwhitespace
  {\dowithnextboxcs\correctwhitespacefinish\vbox}

\unexpanded\def\correctwhitespacefinish
  {\startbaselinecorrection
   \flushnextbox
   \stopbaselinecorrection}

\unexpanded\def\verticalstrut  {\vpack{\hsize\zeropoint\forgetall\strut}}
\unexpanded\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}!

\newdimen\strutdimen
\newdimen\lineheight
\newdimen\openlineheight
\newdimen\openstrutheight
\newdimen\openstrutdepth
\newdimen\topskipgap
\newdimen\struttotal

\def\strutheightfactor      {.72}
\def\strutdepthfactor       {.28}

\def\baselinefactor         {2.8}

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

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

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

\def\strutheight            {\zeropoint}
\def\strutdepth             {\zeropoint}
\def\strutwidth             {\zeropoint}

\let\spacingfactor          \plusone

\def\topskipfactor          {1.0}
\def\maxdepthfactor         {0.5}

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

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

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

\unexpanded\def\topskipcorrection
  {\simpletopskipcorrection
   \vskip-\struttotal
   \verticalstrut}

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

\unexpanded\def\settopskip % the extra test is needed for the lbr family
  {\topskip
     \ifgridsnapping
       \zeropoint
     \else
       \systemtopskipfactor\globalbodyfontsize
       \ifcase\bottomraggednessmode % ragged bottom
         \s!plus5\globalbodyfontsize
       \fi
   \fi
   %\relax
   \topskipgap\topskip
   \advance\topskipgap -\openstrutheight\relax
   \ifdim\minimumstrutheight>\zeropoint
     \ifdim\topskip<\minimumstrutheight
       \topskip\minimumstrutheight\relax
     \fi
   \else
     \ifdim\topskip<\strutheightfactor\openlineheight
       \topskip\strutheightfactor\openlineheight\relax
     \fi
   \fi}

\unexpanded\def\setmaxdepth
  {\maxdepth\systemmaxdepthfactor\globalbodyfontsize}

\let\normalbaselineskip \relax \newskip \normalbaselineskip  % these got lost in the transition to mkiv due
\let\normallineskip     \relax \newskip \normallineskip      % to auto-\normal* definitions and registers
\let\normallineskiplimit\relax \newdimen\normallineskiplimit % being protected

\unexpanded\def\normalbaselines
  {\baselineskip \normalbaselineskip
   \lineskip     \normallineskip
   \lineskiplimit\normallineskiplimit}

\unexpanded\def\flexiblebaselines
  {\baselineskip \normalbaselineskip
   \lineskip     1\normallineskip \s!plus 1\s!fill
   \lineskiplimit\normallineskiplimit}

\unexpanded\def\setnormalbaselines
  {\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
   \normalbaselineskip\openlineheight
     \ifgridsnapping\else
       \s!plus \m_spac_vertical_baseline_stretch_factor\openlineheight
       \s!minus\m_spac_vertical_baseline_shrink_factor \openlineheight
     \fi
   \normallineskip\minimumlinedistance\relax % \onepoint\relax
   \normallineskiplimit\zeropoint\relax
   \normalbaselines}

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

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

\unexpanded\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

\let\restoreinterlinespace\relax

\unexpanded\def\saveinterlinespace
  {\unexpanded\edef\restoreinterlinespace
     {\lineheight         \the\lineheight
      \openstrutheight    \the\openstrutheight
      \openstrutdepth     \the\openstrutdepth
      \openlineheight     \the\openlineheight
      \normalbaselineskip \the\normalbaselineskip
      \normallineskip     \the\normallineskip
      \normallineskiplimit\the\normallineskiplimit
      \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\strutbox

\setbox\strutbox\hpack{\vrule\s!height8.5pt\s!depth3.5pt\s!width\zeropoint} % just a start

\def\strut{\relax\ifmmode\copy\else\unhcopy\fi\strutbox}

% \unexpanded\def\strut
%   {\relax
%    \ifmmode\copy\else\dontleavehmode\unhcopy\fi\strutbox}

\let\normalstrut\strut

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

\let\strutht\undefined \newdimen\strutht
\let\strutdp\undefined \newdimen\strutdp

\unexpanded\def\setstrut
  {\ifgridsnapping
     \setstrutgridyes
   \else
     \setstrutgridnop
   \fi}

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

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

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

\unexpanded\def\settightstrut
  {\setcharstrut{(}}

\unexpanded\def\setfontstrut
  {\setcharstrut{(gplQT}}

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

%D Handy for math (used in mathml):

\unexpanded\def\charhtstrut
  {\begingroup
   \setcharstrut{GJY}%
   \vrule\s!width\zeropoint\s!depth\zeropoint\s!height\strutht
   \endgroup}

\unexpanded\def\chardpstrut
  {\begingroup
   \setcharstrut{gjy}%
   \vrule\s!width\zeropoint\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.

\unexpanded\def\dosetstrut
  {\let\strut\normalstrut
   \ifabsnum\dimexpr\strutht+\strutdp-\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
     \struttotal\lineheight
   \else
     \struttotal\dimexpr\strutht+\strutdp\relax
   \fi
   \edef\strutheight{\the\strutht}%
   \edef\strutdepth {\the\strutdp}%
   \ifdim\strutwidth=\zeropoint
      \spac_struts_set_hide
   \else
      \spac_struts_set_vide
   \fi}

\def\spac_struts_set_hide
  {\setbox\strutbox\hpack
     {\vrule
        \s!width \zeropoint
        \s!height\strutht
        \s!depth \strutdp}}

\newconstant\c_strut_visual_mode

\def\spac_struts_set_vide
  {\setbox\strutbox\hpack  % at some time this extra wrapping was needed
     {\spac_struts_vide_hbox to \zeropoint
        {\ifcase\c_strut_visual_mode
           \spac_struts_black
         \or
           \spac_struts_color
         \else
           \spac_struts_black
         \fi}}}

\def\spac_struts_black
  {\vrule
     \s!width \strutwidth
     \s!height\strutht
     \s!depth \strutdp
   \hss}

\def\spac_struts_color
  {\hss % new, will be option
   \scratchwidth.1\struthtdp
   \begingroup
   \directcolor[f:b:t]%
   \vrule
     \s!width \scratchwidth
     \s!height\strutht
     \s!depth \strutdp
   \kern-\scratchwidth
   \vrule
     \s!width \scratchwidth
     \s!height\zeropoint
     \s!depth \strutdp
   \endgroup
   \kern-.625\scratchwidth
   \vrule
     \s!width .25\scratchwidth
     \s!height\strutht
     \s!depth \strutdp
   \hss}

\let\spac_struts_vide_hbox\hbox % overloaded in trac-vis.mkiv

%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):

\newbox\b_spac_struts_empty \setbox\b_spac_struts_empty\emptyhbox

\def\spac_struts_set_hide
  {\setbox\strutbox\copy\b_spac_struts_empty
   \ht\strutbox\strutht
   \dp\strutbox\strutdp}

\unexpanded\def\strut % still callbacks for \hbox{\strut}
  {\relax
   \dontleavehmode
   \copy\strutbox}

% \unexpanded\def\strut % slightly faster
%   {\relax
%    \ifmmode\copy\else\dontleavehmode\unhcopy\fi\strutbox}

\let\normalstrut\strut

\unexpanded\def\halfstrut
  {\relax
   \dontleavehmode
   \begingroup
   \setbox\scratchbox\copy\strutbox
   \ht\scratchbox\dimexpr\strutht/\plustwo\relax
   \dp\scratchbox\dimexpr\strutdp/\plustwo\relax
   \box\scratchbox
   \endgroup}

\unexpanded\def\quarterstrut
  {\relax
   \dontleavehmode
   \begingroup
   \setbox\scratchbox\copy\strutbox
   \ht\scratchbox\dimexpr\strutht/\plusfour\relax
   \dp\scratchbox\dimexpr\strutdp/\plusfour\relax
   \box\scratchbox
   \endgroup}

\unexpanded\def\depthstrut
  {\relax
   \dontleavehmode
   \begingroup
   \setbox\scratchbox\copy\strutbox
   \ht\scratchbox\dimexpr\strutht-\struthtdp/\plustwo\relax % assumes that ht > lineheight/2
   \box\scratchbox
   \endgroup}

\unexpanded\def\halflinestrut
  {\relax
   \dontleavehmode
   \begingroup
   \setbox\scratchbox\copy\strutbox
   \ht\scratchbox\dimexpr\strutht-.5\strutht-.5\strutdp\relax
   \box\scratchbox
   \endgroup}

\unexpanded\def\noheightstrut
  {\relax
   \dontleavehmode
   \begingroup
   \setbox\scratchbox\copy\strutbox
   \ht\scratchbox\zeropoint
   \box\scratchbox
   \endgroup}

%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}

\unexpanded\def\setupstrut
  {\dosingleempty\spac_struts_setup}

\def\spac_struts_setup[#1]%
  {\edef\m_strut{#1}%
   \ifcsname\??struts\m_strut\endcsname
     \lastnamedcs
   \else
     \setcharstrut\m_strut
   \fi}

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

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

\unexpanded\def\showstruts % adapts .. is wrong
  {\c_strut_visual_mode\zerocount
   \setteststrut
   \settestcrlf}

\unexpanded\def\showcolorstruts % adapts .. is wrong
  {\c_strut_visual_mode\plusone
   \setteststrut
   \settestcrlf}

\unexpanded\def\setteststrut
  {\def\strutwidth{.8pt}%
   \setstrut}

\unexpanded\def\dontshowstruts
  {\unsetteststrut
   \settestcrlf}

\unexpanded\def\unsetteststrut
  {\let\strutwidth\zeropoint
   \setstrut}

\def\autostrutfactor{1.1}

\unexpanded\def\setautostrut
  {\begingroup
   \setbox\scratchbox\copy\strutbox
   \setstrut
   \ifdim\strutht>\autostrutfactor\ht\scratchbox
     \endgroup \setstrut
   \else\ifdim\strutdp>\autostrutfactor\dp\scratchbox
     \endgroup \setstrut
   \else
     \endgroup
   \fi\fi}

\newbox\nostrutbox \setbox\nostrutbox\emptyhbox

\newtoks\everysetnostrut

\unexpanded\def\setnostrut
  {\the\everysetnostrut}

\appendtoks
    \setbox\strutbox\copy\nostrutbox
    \let\strut\empty
    \let\endstrut\empty
    \let\begstrut\empty
\to \everysetnostrut

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

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

\newsignal\strutsignal \setfalse\sigstruts

\unexpanded\def\begstrut
  {\relax\ifcase\strutht
     % \ignorespaces % maybe
   \else
     \spac_struts_beg
   \fi}

\def\spac_struts_beg
  {\ifconditional\sigstruts
     \spac_struts_beg_signal
   \else
     \spac_struts_beg_normal
   \fi
   \ignorespaces}

\def\spac_struts_beg_signal
  {\noindent\horizontalstrut
   \penalty\plustenthousand
   \hskip-\strutsignal
   \hskip\strutsignal}

\def\spac_struts_beg_normal
  {\boundary\plusone
   \strut
  %\boundary\plusone
   \penalty\plustenthousand
  %\boundary\plusone
   \hskip\zeropoint}

\unexpanded\def\endstrut
  {\relax\ifhmode
     \ifcase\strutht
       % \removeunwantedspaces % maybe
     \else
       \spac_struts_end
     \fi
   \fi}

\def\spac_struts_end
  {\ifconditional\sigstruts
     \spac_struts_end_signal
   \else
     \spac_struts_end_normal
   \fi}

\def\spac_struts_end_signal
  {\ifdim\lastskip=\strutsignal
     \unskip
     \unskip
     \unpenalty
     \setbox\scratchbox\lastbox
   \else
     \penalty\plustenthousand
     \hskip\zeropoint
     \strut
   \fi}

\def\spac_struts_end_normal
  {\removeunwantedspaces
   \penalty\plustenthousand
  %\boundary\plustwo
   \hskip\zeropoint
  %\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:

\unexpanded\def\pseudostrut
  {\noindent} % better: \dontleavehmode

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

\unexpanded\def\resetteststrut
  {\def\strutwidth{\zeropoint}% no let
   \setstrut}

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

%D Keyword based strutting:

\letvalue{\??struts\v!yes    }\setstrut
\letvalue{\??struts\v!auto   }\setautostrut
\letvalue{\??struts\v!no     }\setnostrut
\letvalue{\??struts\v!cap    }\setcapstrut
\letvalue{\??struts\v!fit    }\setfontstrut
\letvalue{\??struts\v!line   }\setstrut
\letvalue{\??struts\s!default}\setstrut
\letvalue{\??struts\empty    }\setstrut

%D Handy:

\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.

\unexpanded\def\offinterlineskip
  {\baselineskip-\thousandpoint
   \lineskip     \zeropoint
   \lineskiplimit\maxdimen
   % We also need this here now; thanks to taco for figuring that out!
   \def\minimumlinedistance{\zeropoint}}

\unexpanded\def\nointerlineskip
  {\prevdepth-\thousandpoint}

\let\normaloffinterlineskip\offinterlineskip % knuth's original

%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

\unexpanded\def\page_otr_synchronize_page_yes
  {\aftergroup\page_otr_synchronize_page_indeed
   \glet\page_otr_synchronize_page\relax}

% \unexpanded\def\page_otr_synchronize_page_indeed
%   {\clf_synchronizepage
%    \glet\page_otr_synchronize_page\page_otr_synchronize_page_yes}
%
% This has to become an otr method: \s!page_otr_command_synchonize_page

\unexpanded\def\page_otr_synchronize_page_indeed
  {\ifx\currentoutputroutine\s!multicolumn\else\clf_synchronizepage\fi
   \glet\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:

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

\unexpanded\def\spac_helpers_push_interlineskip_nop
  {\let\oninterlineskip\setnormalbaselines}

\unexpanded\def\offinterlineskip
  {\ifdim\baselineskip>\zeropoint
     \spac_helpers_push_interlineskip_yes
   \else
     \spac_helpers_push_interlineskip_nop
   \fi
   \normaloffinterlineskip}

\let\oninterlineskip\relax

\unexpanded\def\leaveoutervmode
  {\ifvmode\ifinner\else
     \leavevmode
   \fi\fi}

\unexpanded\def\resetpenalties#1%
  {\ifdefined#1%
     #1\minusone
   \fi}

\unexpanded\def\setpenalties#1#2#3%
  {\ifdefined#1% space before #3 prevents lookahead problems, needed when #3=text
     #1\numexpr#2+\plusone\relax\space\doexpandedrecurse{\the\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

\newcount\c_spac_keep_lines_together
\let\restoreinterlinepenalty\relax

\unexpanded\def\spac_penalties_restore
  {\glet\restoreinterlinepenalty\relax
   \global\resetpenalties\interlinepenalties
   \global\c_spac_keep_lines_together\zerocount}

\unexpanded\def\keeplinestogether#1%
  {\ifnum#1>\c_spac_keep_lines_together
     \global\c_spac_keep_lines_together#1%
     \global\setpenalties\interlinepenalties\c_spac_keep_lines_together\plustenthousand
     \glet\restoreinterlinepenalty\spac_penalties_restore
   \fi}

\def\defaultdisplaywidowpenalty        {50}
\def\defaultwidowpenalty             {2000} % was: 1000
\def\defaultclubpenalty              {2000} % was:  800
\def\defaultbrokenpenalty             {100}
\def\defaultdoublehyphendemerits    {10000}
\def\defaultfinalhyphendemerits      {5000}
\def\defaultadjdemerits             {10000}

\def\defaultgriddisplaywidowpenalty     {0}
\def\defaultgridwidowpenalty            {0}
\def\defaultgridclubpenalty             {0}
\def\defaultgridbrokenpenalty           {0}
\def\defaultgriddoublehyphendemerits{10000} % always was so
\def\defaultgridfinalhyphendemerits  {5000} % always was so
\def\defaultgridadjdemerits         {10000} % always was so

\unexpanded\def\nopenalties
  {\widowpenalty        \zerocount
   \clubpenalty         \zerocount
   \brokenpenalty       \zerocount
   \doublehyphendemerits\zerocount
   \finalhyphendemerits \zerocount
   \adjdemerits         \zerocount}

\unexpanded\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}

    \widowpenalty        \defaultwidowpenalty
    \clubpenalty         \defaultclubpenalty
    \displaywidowpenalty \defaultdisplaywidowpenalty
    \brokenpenalty       \defaultbrokenpenalty
    \doublehyphendemerits\defaultdoublehyphendemerits
    \finalhyphendemerits \defaultfinalhyphendemerits
    \adjdemerits         \defaultadjdemerits

\stopsetups

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

    \directsetup{\systemsetupsprefix\s!reset}

    \widowpenalty        \defaultgridwidowpenalty
    \clubpenalty         \defaultgridclubpenalty
    \displaywidowpenalty \defaultgriddisplaywidowpenalty
    \brokenpenalty       \defaultgridbrokenpenalty
    \doublehyphendemerits\defaultgriddoublehyphendemerits
    \finalhyphendemerits \defaultgridfinalhyphendemerits
    \adjdemerits         \defaultgridadjdemerits

\stopsetups

%D As an illustration:

\startsetups [\systemsetupsprefix\v!strict]

    \directsetup{\systemsetupsprefix\s!reset}

    \setpenalties \widowpenalties      \plustwo \maxdimen
    \setpenalties \clubpenalties       \plustwo \maxdimen
                  \brokenpenalty                \maxdimen
                  \doublehyphendemerits         \defaultdoublehyphendemerits
                  \finalhyphendemerits          \defaultfinalhyphendemerits
                  \adjdemerits                  \defaultadjdemerits

\stopsetups

\setdefaultpenalties % will happen later in \setuplayout

%D To be checked:

\newbox\b_spac_struts_saved

\unexpanded\def\savestrut {\setbox\b_spac_struts_saved\copy\strutbox}
\unexpanded\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}

\newskip  \bodyfontlineheight
\newdimen \bodyfontstrutheight
\newdimen \bodyfontstrutdepth

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

\def\snappedvboxattribute{\ifgridsnapping attr       \snapvboxattribute \attribute\snapmethodattribute\fi}
\def\setlocalgridsnapping{\ifgridsnapping \attribute \snapvboxattribute \attribute\snapmethodattribute\fi}

\def\spac_grids_set_local_snapping#1%
  {\ifgridsnapping
     \doifsomething{#1}%
       {\spac_grids_snap_value_set{#1}%
        \attribute \snapvboxattribute \attribute\snapmethodattribute}%
   \fi}

% \unexpanded\def\installsnapvalues#1#2% todo: a proper define
%   {\edef\currentsnapper{#1:#2}%
%    \ifcsname\??gridsnapperattributes\currentsnapper\endcsname \else
%      \setevalue{\??gridsnapperattributes\currentsnapper}{\clf_definesnapmethod{#1}{#2}}%
%    \fi
%    \setevalue{\??gridsnappers#1}{\attribute\snapmethodattribute\csname\??gridsnapperattributes\currentsnapper\endcsname\space}}

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

\unexpanded\def\installsnapvalues#1#2%
  {\let\m_spac_snapper\empty
   \rawprocesscommacommand[#2]\spac_grids_expand_snapper
   \edef\currentsnapper{#1:\m_spac_snapper}%
   \ifcsname\??gridsnapperattributes\currentsnapper\endcsname
     \scratchcounter\lastnamedcs % already defined
   \else
     \scratchcounter\clf_definesnapmethod{#1}{\m_spac_snapper}%
     \setevalue{\??gridsnapperattributes\currentsnapper}{\the\scratchcounter}%
   \fi
   \setevalue{\??gridsnappers#1}{\attribute\snapmethodattribute\the\scratchcounter\relax}%
   \letvalue{\??gridsnappersets#1}\m_spac_snapper}

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

\unexpanded\def\usegridparameter#1% no checking here
  {\edef\m_spac_grid_asked{#1\c!grid}%
   \ifx\m_spac_grid_asked\empty
     \attribute \snapvboxattribute\attributeunsetvalue
   \else
     \spac_grids_snap_value_set\m_spac_grid_asked
     \attribute \snapvboxattribute\attribute\snapmethodattribute
   \fi}

\unexpanded\def\definegridsnapping
  {\dodoubleargument\spac_grids_define}

\def\spac_grids_define[#1][#2]%
  {\installsnapvalues{#1}{#2}}

\edef\spac_grids_snap_value_reset
  {%\gridsnappingfalse
   \attribute\snapmethodattribute\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]

\unexpanded\def\synchronizelocallinespecs
  {\bodyfontlineheight \normallineheight
   \bodyfontstrutheight\strutht
   \bodyfontstrutdepth \strutdp}

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

\appendtoks
    \synchronizegloballinespecs
    \synchronizelocallinespecs
\to \everysetupglobalinterlinespace

\appendtoks
    \synchronizelocallinespecs
\to \everysetuplocalinterlinespace

%D We still have to synchronize these:

\unexpanded\def\synchronizeskipamounts
  {\bigskipamount
                 \skipfactor\baselineskip
      \s!plus\skipgluefactor\baselineskip
     \s!minus\skipgluefactor\baselineskip
   \relax
   \medskipamount  \bigskipamount \divide\medskipamount  \plustwo
   \smallskipamount\bigskipamount \divide\smallskipamount\plusfour}

%D Snapping.

\newif\ifgridsnapping

%unexpanded\def\moveongrid {\dosingleempty\spac_grids_move_on}
\unexpanded\def\snaptogrid {\dosingleempty\spac_grids_snap_to}
\unexpanded\def\placeongrid{\dosingleempty\spac_grids_place_on}

\unexpanded\def\startgridsnapping
  {\dosingleempty\spac_grids_start_snapping}

\unexpanded\def\spac_grids_start_snapping[#1]%
  {\snaptogrid[#1]\vbox\bgroup}

\unexpanded\def\stopgridsnapping
  {\egroup}

% \def\spac_grids_move_on[#1]%
%   {[obsolete]} % gone, unless we set an attribute

\def\spac_grids_place_on[#1]%
  {\snaptogrid[#1]\vbox} % mark as done

\def\spac_grids_snap_to[#1]% list or predefined
  {\ifgridsnapping
     \expandafter\spac_grids_snap_to_indeed
   \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
   \doifelsenothing{#1}{\spac_grids_snap_value_set\v!normal}{\spac_grids_snap_value_set{#1}}%
   \clf_vspacingsnap\nextbox\attribute\snapmethodattribute\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\askedgridmode}

\unexpanded\def\synchronizegridsnapping
  {\edef\askedgridmode{\layoutparameter\c!grid}%
   \ifx\askedgridmode\v!no       % official
     \spac_grids_check_nop
   \else\ifx\askedgridmode\v!off % for taco and luigi
     \spac_grids_check_nop
   \else\ifx\askedgridmode\empty % to be sure
     \spac_grids_check_nop
   \else
     \spac_grids_check_yes
   \fi\fi\fi}

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

\unexpanded\def\checkgridmethod#1%
  {\edef\p_grid{#1}%
   \ifx\p_grid\empty
     \let\checkedgridmethod\empty
     \let\checkedgridscope \v!local
   \else
     \splitatcolon\p_grid\checkedgridscope\checkedgridmethod
     \ifx\checkedgridmethod\empty
       \ifx\checkedgridscope\v!local\else\ifx\checkedgridscope\v!global\else
         \let\checkedgridmethod\checkedgridscope
         \let\checkedgridscope \v!local
       \fi\fi
     \fi
   \fi}

\unexpanded\def\applygridmethod#1#2#3% content localsettings (used in head rendering)
   {\checkgridmethod{#1}%
    \ifx\checkedgridscope\v!global
      \ifx\checkedgridmethod\empty \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
        \ifx\checkedgridmethod\empty\else
          \ifconditional\headisdisplay
            #2%
          \fi
        \fi
        \snaptogrid[\checkedgridmethod]\hbox{#3}%
      \egroup
    \fi}

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

\unexpanded\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]

\unexpanded\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

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

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

\unexpanded\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
         \dorecurse\noflines
           {\strut
            \hskip-.5\lineheight\relax
            \ifcase\gridboxlinenomode\or
              \rlap
                {\hskip\dimexpr.2\bodyfontsize+\scratchdimen\relax
                 \infofont\hbox to \emwidth{\hss\recurselevel}}%
            \or
              \llap
                {\infofont\hbox to \emwidth{\hss\recurselevel}%
                 \hskip.2\bodyfontsize}%
            \fi
            \vrule
              \s!height \gridboxwidth
              \s!depth  \gridboxwidth
              \s!width  \scratchdimen
            \par}}
      \vfill}}

%D This has become obsolete:

\def\fuzzysnappedbox#1#2% \box<n> \unvbox<n>
  {#1#2}

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

%D Helper:

\unexpanded\def\spac_helpers_assign_skip#1#2% ook nog \v!halfline+fuzzysnap
  {\doifelse{#2}\v!line
     {#1\ifgridsnapping
        \bodyfontlineheight
      \else
        \openlineheight
      \fi}
     {\ifgridsnapping
        \assigndimension{#2}{#1}{.25\bodyfontlineheight}{.5\bodyfontlineheight}\bodyfontlineheight
      \else
        \assigndimension{#2}{#1}\smallskipamount\medskipamount\bigskipamount
      \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{vspacingamount}

\unexpanded\def\definevspacingamount
  {\dotripleempty\spac_vspacing_define_amount}

\def\spac_vspacing_define_amount[#1][#2][#3]% can be combined
  {\ifthirdargument
     \setvalue{\??vspacingamount#1}{\ifgridsnapping#3\else#2\fi}%
   \else\ifsecondargument
     \setvalue{\??vspacingamount#1}{\ifgridsnapping\lineheight\else#2\fi}%
   \else
     \setvalue{\??vspacingamount#1}{\lineheight}%
   \fi\fi
   \clf_vspacingsetamount{#1}}

\def\spac_vspacing_no_topskip % use grouped
  {\attribute\skipcategoryattribute\plusten}

% \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}}

\unexpanded\def\definevspacing
  {\dodoubleempty\spac_vspacing_define}

\def\spac_vspacing_define[#1][#2]%
  {\clf_vspacingdefine{#1}{#2}}

%D The injector code (generated at the \LUA\ end):

\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
    \attribute\skipcategoryattribute\plusone
    \attribute\skippenaltyattribute \attributeunsetvalue
    \attribute\skiporderattribute   \attributeunsetvalue
    \ifgridsnapping
        \settrue\c_space_vspacing_fixed
    \else
        \setfalse\c_space_vspacing_fixed
    \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

\unexpanded\def\setblankpacked
  {\settrue\c_space_ignore_parskip}

\unexpanded\def\setblankcategory#1%
  {\settrue\c_space_vspacing_done
   \attribute\skipcategoryattribute#1\relax}

\unexpanded\def\setblankorder#1%
  {\attribute\skiporderattribute#1\relax}

\unexpanded\def\fixedblankskip
  {\settrue\c_space_vspacing_fixed}

\unexpanded\def\flexibleblankskip
  {\setfalse\c_space_vspacing_fixed}

% \unexpanded\def\addblankskip#1#2#3%
%   {\settrue\c_space_vspacing_done
%    \advance\s_spac_vspacing_temp#1\dimexpr\ifgridsnapping#3\else#2\fi\relax\relax}

\unexpanded\def\setblankpenalty#1%
  {\flushblankhandling
   \settrue\c_space_vspacing_done
   \attribute\skipcategoryattribute \plusthree
   \attribute\skippenaltyattribute #1\relax
   \flushblankhandling}

\unexpanded\def\startblankhandling % move this to \vspacing
  {\par
   \ifvmode
     \expandafter\dostartblankhandling
   \else
     \expandafter\nostartblankhandling
   \fi}

\unexpanded\def\nostartblankhandling#1\stopblankhandling
  {}

\def\dostartblankhandling
  {\begingroup
   \setfalse\c_space_vspacing_done
   \setfalse\c_space_ignore_parskip
   \the\everybeforeblankhandling}

\unexpanded\def\stopblankhandling
  {\the\everyafterblankhandling
   \ifconditional\c_space_vspacing_done
     \vskip\s_spac_vspacing_temp
   \fi
   \ifconditional\c_space_ignore_parskip
     \endgroup\ignoreparskip
   \else
     \endgroup
   \fi}

\unexpanded\def\flushblankhandling
  {\the\everyafterblankhandling
   \ifconditional\c_space_vspacing_done
     \vskip\s_spac_vspacing_temp
   \fi
   \setfalse\c_space_vspacing_done
   \the\everybeforeblankhandling}

\unexpanded\def\addpredefinedblankskip#1#2%
  {\settrue\c_space_vspacing_done
   \advance\s_spac_vspacing_temp#1\dimexpr\csname\??vspacingamount#2\endcsname\relax}

% \unexpanded\def\addpredefinedblankskip#1#2%
%   {\settrue\c_space_vspacing_done
%    \advance\s_spac_vspacing_temp#1\dimexpr\the\csname\ifgridsnapping g\else n\fi>#2\endcsname\relax}

\unexpanded\def\addaskedblankskip#1#2%
  {\settrue\c_space_vspacing_done
   \advance\s_spac_vspacing_temp#1\dimexpr#2\relax}

% The main spacer:

\unexpanded\def\vspacing
  {\doifelsenextoptionalcs\spac_vspacing_yes\spac_vspacing_nop}

\def\spac_vspacing_yes
  {\ifinpagebody % somewhat weird
     \singleexpandafter\spac_vspacing_yes_indeed
   \else\ifconditional\c_spac_packed_blank
     \doubleexpandafter\spac_vspacing_yes_indeed
   \else
     \doubleexpandafter\spac_vspacing_yes_ignore
   \fi\fi}

\def\spac_vspacing_nop
  {\ifinpagebody % somewhat weird
     \singleexpandafter\spac_vspacing_nop_indeed
   \else\ifconditional\c_spac_packed_blank
     \doubleexpandafter\spac_vspacing_nop_indeed
   \else
     \doubleexpandafter\spac_vspacing_nop_ignore
   \fi\fi}

\def\spac_vspacing_yes_indeed[#1]%
  {\ifmmode\else\par\clf_vspacing{#1}\fi}

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

\def\spac_vspacing_nop_indeed
  {\ifmmode\else\par\clf_vspacing{\currentvspacing}\fi}

\def\spac_vspacing_nop_ignore
  {\ifmmode\else\par\fi}

% \unexpanded\def\directvspacing#1%
%   {\par\clf_vspacing{#1}}
%
%
% \def\directdefaultvspacing
%   {\ifinpagebody % somewhat weird
%      \clf_vspacing{\currentvspacing}%
%    \else\ifconditional\c_spac_packed_blank
%      \clf_vspacing{\currentvspacing}%
%    \fi\fi}
%
% \unexpanded\def\useblankparameter#1% faster local variant
%   {\edef\m_spac_blank_asked{#1\c!blank}%
%    \ifx\m_spac_blank_asked\empty\else
%      \clf_vspacing{\m_spac_blank_asked}
%    \fi}

\installcorenamespace{vspacing}

\unexpanded\def\directvspacing#1%
  {\par
   \ifcsname\??vspacing#1\endcsname
     \lastnamedcs
   \else
     \spac_vspacing_yes_preset{#1}%
   \fi}

\def\spac_vspacing_yes_preset#1%
  {\setxvalue{\??vspacing#1}{\clf_vspacing{#1}}%
  %\writestatus{}{}%
  %\writestatus{#1}{\expandafter\meaning\csname\??vspacing#1\endcsname}%
  %\writestatus{}{}%
   \csname\??vspacing#1\endcsname}

\def\spac_vspacing_yes_indeed[#1]%
  {\ifmmode\else
     \directvspacing{#1}%
   \fi}

\def\spac_vspacing_nop_indeed
  {\ifmmode\else
     \directvspacing\currentvspacing
   \fi}

\def\directdefaultvspacing
  {\ifinpagebody % somewhat weird
     \directvspacing\currentvspacing
   \else\ifconditional\c_spac_packed_blank
     \directvspacing\currentvspacing
   \fi\fi}

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

\unexpanded\def\usespecificblankparameter#1#2% faster local variant
  {\edef\m_spac_blank_asked{#1#2}%
   \ifx\m_spac_blank_asked\empty\else
     \directvspacing\m_spac_blank_asked
   \fi}

\unexpanded\def\useblankparameter#1% faster local variant
  {\usespecificblankparameter#1\c!blank}

%D Handy (and faster):

\unexpanded\def\directvpenalty#1%
  {\begingroup
   \attribute\skipcategoryattribute \plusthree
   \attribute\skippenaltyattribute  #1\relax
   \attribute\skiporderattribute    \attributeunsetvalue
   \vskip\zeropoint
   \endgroup}

\unexpanded\def\directvskip#1%
  {\begingroup
   \attribute\skipcategoryattribute \plusone
   \attribute\skippenaltyattribute  \attributeunsetvalue
   \attribute\skiporderattribute    \attributeunsetvalue
   \vskip#1\relax
   \endgroup}

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

\unexpanded\def\setupvspacing
  {\doifelsenextoptionalcs\setupvspacing_yes\setupvspacing_nop}

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

\def\setupvspacing_yes[#1]%
  {\edef\currentvspacing{#1}%
   \spac_whitespace_setup_nop % yes or no, was forgotten
  }

\def\setupvspacing_nop
  {\ifx\empty\currentvspacing % mistakenly had an \else
     \let\currentvspacing\s!default
   \fi
   \spac_whitespace_setup_nop}

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

\let\synchronizevspacing\setupvspacing_nop

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

\definevspacingamount[\v!none]        [\zeropoint]         [\zeropoint]
\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

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

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

\newdimen\d_spac_overlay

\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

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

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

\unexpanded\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}

\unexpanded\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}

\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
%
% \unexpanded\def\inhibitblank
%   {\vspacing[\v!disable]}
%
% but use the following more efficient variant instead:

\unexpanded\def\inhibitblank
  {\ifvmode
     \begingroup
     \attribute\skipcategoryattribute\plusfive
     \vskip\zeropoint
     \endgroup
   \fi}

\let\doinhibitblank\inhibitblank % keep this command, used in styles

\let\defineblank      \definevspacing
\let\setupblank       \setupvspacing
\let\blank            \vspacing
\let\synchronizeblank \synchronizevspacing
\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}

\unexpanded\def\definevspace
  {\dotripleempty\spac_vspace_define}

\def\spac_vspace_define[#1][#2][#3]%
  {\ifthirdargument
     \setvalue{\??vspace#1:#2}{#3}%
   \else
     \setvalue{\??vspace:#1}{#2}%
   \fi}

\letvalue{\??vspace:\s!unknown}\zeropoint

\unexpanded\def\vspace
  {\dodoubleempty\spac_vspace_inject}

% \def\spac_vspace_inject[#1][#2]% use \lastnamedcs
%   {\par
%    \ifvmode
%      \removelastskip
%      \vskip
%        \csname\??vspace
%          \ifsecondargument
%            \ifcsname\??vspace#1:#2\endcsname
%              #1:#2%
%            \else\ifcsname\??vspace:#2\endcsname
%              :#2%
%            \else
%              :\s!unknown
%            \fi\fi
%          \else\iffirstargument
%            \ifcsname\??vspace:#1\endcsname
%              :#1%
%            \else
%              :\s!unknown
%            \fi
%          \else
%            \ifcsname\??vspace:\s!default\endcsname
%              :\s!default
%            \else
%              :\s!unknown
%            \fi
%          \fi\fi
%        \endcsname
%      \relax
%    \fi}

\def\spac_vspace_unknown
  {\csname\??vspace:\s!unknown\endcsname}

\def\spac_vspace_inject[#1][#2]% use \lastnamedcs
  {\par
   \ifvmode
     \removelastskip
     \vskip
       \ifsecondargument
         \ifcsname\??vspace#1:#2\endcsname
           \lastnamedcs
         \else\ifcsname\??vspace:#2\endcsname
           \lastnamedcs
         \else
           \spac_vspace_unknown
         \fi\fi
       \else\iffirstargument
         \ifcsname\??vspace:#1\endcsname
           \lastnamedcs
         \else
           \spac_vspace_unknown
         \fi
       \else
         \ifcsname\??vspace:\s!default\endcsname
           \lastnamedcs
         \else
           \spac_vspace_unknown
         \fi
       \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]

\unexpanded\def\starthanging
  {\dontleavehmode\bgroup
   \dosingleempty\spac_hanging_start}

\unexpanded\def\stophanging
  {\endgraf
   \egroup}

\let\m_spac_hanging_location\empty

\def\spac_hanging_start[#1]%
  {\doifelseassignment{#1}
     {\let\m_spac_hanging_location\empty
      \setupcurrenthanging[#1]}%
     {\edef\m_spac_hanging_location{#1}}%
   \ifx\m_spac_hanging_location\empty
     \edef\m_spac_hanging_location{\directhangingparameter\c!location}%
   \fi
   \dowithnextboxcs\spac_hanging_finish\hbox}

\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
     \hangafter-\scratchcounter
   \else
     \getboxheight\scratchdimen\of\box\nextbox
     \getnoflines\scratchdimen
     \hangafter-\noflines
   \fi
   \ht\nextbox\strutht
   \dp\nextbox\strutdp
   \scratchwidth\dimexpr\wd\nextbox+\scratchdistance\relax
   \ifx\m_spac_hanging_location\v!right
     \hangindent\ifconditional\displaylefttoright-\fi\scratchwidth
     \rlap{\hskip\dimexpr\hsize-\leftskip-\wd\nextbox\relax\box\nextbox}% \leftskip is new
   \else
     \hangindent\ifconditional\displaylefttoright\else-\fi\scratchwidth
     \llap{\box\nextbox\hskip\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}

\unexpanded\def\startfixed
  {\bgroup
   \dosingleempty\typo_fixed_start}

\def\typo_fixed_start
  {\ifhmode
     \expandafter\typo_fixed_start_h
   \else
     \expandafter\typo_fixed_start_v
   \fi}

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

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

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

\unexpanded\def\typo_fixed_stop_v
  {\stopbaselinecorrection
   \egroup}

\letvalue{\??fixedalternatives   \v!high}\bbox
\letvalue{\??fixedalternatives    \v!low}\tbox
\letvalue{\??fixedalternatives \v!middle}\vcenter
\letvalue{\??fixedalternatives   \v!lohi}\vcenter
\letvalue{\??fixedalternatives\s!unknown}\tbox
\letvalue{\??fixedalternatives\s!default}\tbox

\unexpanded\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:

\unexpanded\def\checkedblank[#1]%
  {\edef\p_blank{#1}%
   \ifx\p_blank\empty
     % ignore
   \else\ifx\p_blank\v!none
     % ignore
   \else
     \blank[\p_blank]%
   \fi\fi}

% \unexpanded\def\applycheckedblankwith#1#2#3%
%   {\edef\p_blank{#2}%
%    \ifx\p_blank\empty
%      % ignore
%    \else\ifx\p_blank\v!none
%      % ignore
%    \else
%      \edef\p_before{#1}%
%      \edef\p_after {#3}%
%      \blank[\ifx\p_before\empty\else\p_before,\fi\p_blank\ifx\p_after\empty\else,\p_after\fi]%
%    \fi\fi}

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

\newskip\prerolledblank

\unexpanded\def\prerollblank[#1]%
  {\begingroup
   \edef\p_blank{#1}%
   \ifx\p_blank\empty
     \global\prerolledblank\zeropoint
   \else\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
     \advance\scratchskip-\ht\scratchbox
     \global\prerolledblank-\scratchskip
   \fi\fi
   \endgroup}

\newcount\c_spac_vspacing_ignore_parskip

% \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

\unexpanded\def\ignoreparskip{\c_spac_vspacing_ignore_parskip\plusone}

\protect \endinput