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

\registerctxluafile{spac-par}{autosuffix}

\unprotect

% TODO: \appendtoks \strut \wrapuppar{\strut}\to \everypar

% this might move to syst-ini.mkxl

\setnewconstant\frozenparagraphdefault\numexpr
    \adjustfrozenparcode
  + \brokenpenaltyfrozenparcode
  + \clubpenaltyfrozenparcode
  + \demeritsfrozenparcode
  + \displaypenaltyfrozenparcode
  + \emergencyfrozenparcode
  + \fitnessclassesfrozenparcode
  + \hangfrozenparcode
  + \hsizefrozenparcode
  + \hyphenationfrozenparcode
  + \hyphenpenaltyfrozenparcode
  + \indentfrozenparcode
  + \lastlinefrozenparcode
  + \linebreakchecksfrozenparcode
  % \linefrozenparcode
  + \linepenaltyfrozenparcode
  + \loosenessfrozenparcode
  % \mathpenaltyfrozenparcode
  + \orphanpenaltyfrozenparcode
  + \parfillfrozenparcode
  + \parpassesfrozenparcode
  + \protrudefrozenparcode
  + \shapefrozenparcode
  + \shapingpenaltyfrozenparcode
  + \singlelinepenaltyfrozenparcode
  + \skipfrozenparcode
  + \stretchfrozenparcode
  + \toddlerpenaltyfrozenparcode
  + \tolerancefrozenparcode
  + \twindemeritsfrozenparcode
  + \widowpenaltyfrozenparcode
  % \linesnappingfrozenparcode
\relax

\setnewconstant\paragraphupdatecodes\numexpr
    \tolerancefrozenparcode
  + \loosenessfrozenparcode
  + \linepenaltyfrozenparcode
  + \widowpenaltyfrozenparcode
  + \clubpenaltyfrozenparcode
  + \brokenpenaltyfrozenparcode
  + \demeritsfrozenparcode
  + \orphanpenaltyfrozenparcode
  + \toddlerpenaltyfrozenparcode
  + \shapingpenaltyfrozenparcode
  + \singlelinepenaltyfrozenparcode
  + \hyphenpenaltyfrozenparcode
  + \linebreakchecksfrozenparcode
  + \twindemeritsfrozenparcode
\relax

\setnewconstant\paragraphpenaltycodes\numexpr
    \linepenaltyfrozenparcode
  + \widowpenaltyfrozenparcode
  + \clubpenaltyfrozenparcode
  + \brokenpenaltyfrozenparcode
  + \shapingpenaltyfrozenparcode
  + \orphanpenaltyfrozenparcode
  + \toddlerpenaltyfrozenparcode
  + \singlelinepenaltyfrozenparcode
  + \hyphenpenaltyfrozenparcode
  + \linebreakchecksfrozenparcode
\relax

\setnewconstant\paragraphdemeritcodes\numexpr
    \demeritsfrozenparcode
  + \twindemeritsfrozenparcode
\relax

\setnewconstant\paragraphshapecodes\numexpr
    \hangfrozenparcode
  + \skipfrozenparcode
  + \parfillfrozenparcode
  + \shapingpenaltyfrozenparcode
  + \shapefrozenparcode
\relax

\setnewconstant\paragraphlinecodes\numexpr
    \linefrozenparcode
\relax

\setnewconstant\paragraphhyphenationcodes\numexpr
    \hyphenationfrozenparcode
\relax

\setnewconstant\paragraphpassescodes\numexpr
    \parpassesfrozenparcode
\relax

\bitwiseflip\glyphoptions\checktoddlerglyphoptioncode
\bitwiseflip\glyphoptions\checktwinglyphoptioncode

% \linefrozenparcode % lineskip

\appendtoks
    \parpasses\zerocount
  % \noexpansion
  % \noprotrusion
\to \everyforgetall

\permanent\protected\def\freezeparagraphproperties {\snapshotpar\frozenparagraphdefault}
\permanent\protected\def\defrostparagraphproperties{\snapshotpar\zerocount}

\permanent\protected\def\updateparagraphproperties{\ifhmode\snapshotpar\paragraphupdatecodes \fi}
\permanent\protected\def\updateparagraphpenalties {\ifhmode\snapshotpar\paragraphpenaltycodes\fi}
\permanent\protected\def\updateparagraphdemerits  {\ifhmode\snapshotpar\paragraphdemeritcodes\fi}
\permanent\protected\def\updateparagraphshapes    {\ifhmode\snapshotpar\paragraphshapecodes  \fi}
\permanent\protected\def\updateparagraphlines     {\ifhmode\snapshotpar\paragraphlinecodes   \fi}
\permanent\protected\def\updateparagraphpasses    {\ifhmode\snapshotpar\paragraphpassescodes \fi}

% so far

\let\spac_paragraph_freeze\relax

\permanent\protected\def\setparagraphfreezing   {\enforced\let\spac_paragraph_freeze\freezeparagraphproperties}
\permanent\protected\def\forgetparagraphfreezing{\enforced\let\spac_paragraph_freeze\relax}

\installcorenamespace {bparwrap}
\installcorenamespace {eparwrap}
\installcorenamespace {parwrapbefore}
\installcorenamespace {parwrapafter}
\installcorenamespace {parwrapcount}

\lettonothing\spac_paragraph_wrap

\newinteger\c_spac_paragraph_group_level

\protected\def\spac_paragraph_update
  {\c_spac_paragraph_group_level\currentgrouplevel\relax
   \ifcsname\??bparwrap\the\c_spac_paragraph_group_level\endcsname
     \the\lastnamedcs
     \relax
     \dontleavehmode % just in case
     \wrapuppar{\the\csname\??eparwrap\the\c_spac_paragraph_group_level\endcsname\relax}%
   \fi}

\permanent\protected\def\registerparwrapper       {\spac_register_par_wrapper\toksapp\tokspre}
\permanent\protected\def\registerparwrapperreverse{\spac_register_par_wrapper\tokspre\toksapp}

\protected\def\spac_paragraph_install
  {\expandafter\newtoks\csname\??bparwrap\the\currentgrouplevel\endcsname
   \expandafter\newtoks\csname\??eparwrap\the\currentgrouplevel\endcsname}

\def\spac_paragraph_install_count#1%
  {\expandafter\newinteger\csname\??parwrapcount#1\endcsname}

\def\spac_paragraph_install_pair#1#2#3%
  {\expandafter\newtoks\csname\??parwrapbefore#3\endcsname
   \expandafter\newtoks\csname\??parwrapafter #3\endcsname
   #1\csname\??bparwrap\the\currentgrouplevel\endcsname{\the\csname\??parwrapbefore#3\endcsname}%
   #2\csname\??eparwrap\the\currentgrouplevel\endcsname{\the\csname\??parwrapafter #3\endcsname}}

\def\spac_register_par_wrapper_yes#1#2#3#4#5%
  {\ifcsname\??bparwrap\the\currentgrouplevel\endcsname \else
     \spac_paragraph_install
   \fi
   \ifcsname\??parwrapcount#3\endcsname \else
     \spac_paragraph_install_count{#3}%
   \fi
   \ifcsname\??parwrapbefore#3\endcsname \else
     \spac_paragraph_install_pair#1#2{#3}%
   \fi
   #1\csname\??parwrapbefore#3\endcsname{\global\advanceby\csname\??parwrapcount#3\endcsname\plusone\relax % global, see (!)
                                         \clf_setparwrapper{#3}#4}%
   #2\csname\??parwrapafter #3\endcsname{#5}%
   \clf_newparwrapper{#3}%
   \let\spac_paragraph_wrap\spac_paragraph_update}

\def\spac_register_par_wrapper_nop#1#2#3#4#5%
  {\ifcsname\??parwrapcount#3\endcsname \else
     \spac_paragraph_install_count{#3}%
   \fi
   \global\csname\??parwrapcount#3\endcsname\plusone % global, see (!)
   #4\wrapuppar{#5}}

\protected\def\spac_register_par_wrapper
  {\ifhmode
     \expandafter\spac_register_par_wrapper_nop
   \else
     \expandafter\spac_register_par_wrapper_yes
   \fi}

\permanent\protected\def\forgetparwrapper
  {\csname\??bparwrap\the\currentgrouplevel\endcsname\emptytoks
   \csname\??eparwrap\the\currentgrouplevel\endcsname\emptytoks}

\permanent\protected\def\unregisterparwrapper#1%
  {\global\csname\??parwrapcount#1\endcsname\zerocount % global, see (!)
   \ifcsname\??parwrapbefore#1\endcsname
     \lastnamedcs\emptytoks
     \csname\??parwrapafter#1\endcsname\emptytoks
   \fi}

\permanent\def\directparwrapper#1#2%
  {#1\wrapuppar{#2}}

\permanent\protected\def\doifelseparwrapper#1%
  {\unless\ifcsname\??parwrapcount#1\endcsname
     \expandafter\secondoftwoarguments
   \orelse\ifcase\lastnamedcs
     \expandafter\secondoftwoarguments
   \else
     \expandafter\firstoftwoarguments
   \fi}

% \getparwrapper  % defined in lua
% \lastparwrapper % defined in lua

\permanent\protected\def\showparwrapperstate#1%
  {\begingroup
   \infofont ¶#1\hilo
     {\smallinfofont\getparwrapper {#1}}%
     {\smallinfofont\lastparwrapper{#1}}%
   \endgroup}

%appendtoks\updateparwrapperindeed\to\everypar
%appendtoks\spac_paragraph_wrap   \to\everypar
%appendtoks\spac_paragraph_freeze \to\everypar

% (!) testcase for global setting of count
%
% \starttext
%     \hsize3cm
%     Aaa\wordright{Aaa}\par
%     \sc{Bbb\wordright{Bbb}}\par
%     {\sc Ccc\wordright{Ccc}}\par
%     \sc{Ddd}\wordright{\sc{Ddd}}\par
% \stoptext

\setparagraphfreezing

\appendtoks\lettonothing\spac_paragraph_wrap\to\everyforgetall

%D In due time, the code below will be upgraded using the above mechanisms.

%D The dreadful sequence \type {\bgroup} \unknown\ \type {\carryoverpar} \unknown\
%D \type {\egroup} is needed when for instance sidefloats are used in combination
%D with something that starts with a group. This is because otherwise the
%D indentation as set (by the output routine) inside the group are forgotten
%D afterwards. (I must not forget its existence).

\mutable\lettonothing\currentparagraphproperties % visible for tracing

\permanent\def\carryoverpar#1% #1 can be \endgroup or \egroup or ... expandable !
  {\expanded
     {\noexpand#1%
      \hangindent\the\hangindent
      \hangafter \the\hangafter
      \parskip   \the\parskip
      \leftskip  \the\leftskip
      \rightskip \the\rightskip
      \relax}}

\permanent\protected\def\pushparagraphproperties
  {\edef\currentparagraphproperties{\carryoverpar\relax}%
   \pushmacro\currentparagraphproperties}

\permanent\protected\def\popparagraphproperties
  {\popmacro\currentparagraphproperties
   \currentparagraphproperties}

\permanent\protected\def\flushparagraphproperties
  {\popmacro\currentparagraphproperties}

%D Beware, changing this will break some code (like pos/backgrounds) but it has been
%D changed anyway so let's see where things go wrong.

\installcorenamespace{paragraphintro}

\newtoks\t_spac_paragraphs_intro_first
\newtoks\t_spac_paragraphs_intro_next
\newtoks\t_spac_paragraphs_intro_each

\newconditional\c_spac_paragraphs_intro_first
\newconditional\c_spac_paragraphs_intro_next
\newconditional\c_spac_paragraphs_intro_each

\lettonothing\spac_paragraphs_flush_intro
\lettonothing\spac_paragraphs_intro_step

\permanent\protected\tolerant\def\setupparagraphintro[#1]#*[#2]%
  {\def\spac_paragraphs_intro_step##1%
     {\csname\??paragraphintro\ifcsname\??paragraphintro##1\endcsname##1\fi\endcsname{#2}}%
   \processcommacommand[#1]\spac_paragraphs_intro_step}

\letcsname\??paragraphintro\empty\endcsname\gobbleoneargument

\defcsname\??paragraphintro\v!reset\endcsname#1%
  {\global\c_spac_paragraphs_intro_first\conditionalfalse
   \global\c_spac_paragraphs_intro_next\conditionalfalse
   \global\c_spac_paragraphs_intro_each\conditionalfalse
   \global\t_spac_paragraphs_intro_first\emptytoks
   \global\t_spac_paragraphs_intro_next \emptytoks
   \global\t_spac_paragraphs_intro_each \emptytoks
   \glettonothing\spac_paragraphs_flush_intro}

\defcsname\??paragraphintro\v!first\endcsname#1%
  {\global\c_spac_paragraphs_intro_first\conditionaltrue
   \gtoksapp\t_spac_paragraphs_intro_first{#1}%
   \glet\spac_paragraphs_flush_intro\spac_paragraphs_flush_intro_indeed}

\defcsname\??paragraphintro\v!next\endcsname#1%
  {\global\c_spac_paragraphs_intro_next\conditionaltrue
   \gtoksapp\t_spac_paragraphs_intro_next{#1}%
   \glet\spac_paragraphs_flush_intro\spac_paragraphs_flush_intro_indeed}

\defcsname\??paragraphintro\v!each\endcsname#1%
  {\global\c_spac_paragraphs_intro_each\conditionaltrue
   \gtoksapp\t_spac_paragraphs_intro_each{#1}%
   \glet\spac_paragraphs_flush_intro\spac_paragraphs_flush_intro_indeed}

%D We can say:
%D
%D \starttyping
%D \setupparagraphintro[first][\index{Knuth}]
%D \stoptyping
%D
%D Maybe more convenient is:
%D
%D \starttyping
%D \flushatparagraph{\index{Zapf}}
%D \stoptyping
%D
%D \starttyping
%D \setupparagraphintro[first][\hbox to 3.5em{\tt FIRST \hss}]
%D \setupparagraphintro[first][\hbox to 3.5em{\tt TSRIF \hss}]
%D \setupparagraphintro[next] [\hbox to 3.5em{\tt NEXT  \hss}]
%D \setupparagraphintro[next] [\hbox to 3.5em{\tt TXEN  \hss}]
%D \setupparagraphintro[each] [\hbox to 3.0em{\tt EACH  \hss}]
%D \setupparagraphintro[each] [\hbox to 3.0em{\tt HCEA  \hss}]
%D
%D some paragraph \par
%D some paragraph \par
%D some paragraph \par
%D some paragraph \par
%D
%D \setupparagraphintro[first][\hbox to 3.5em{\tt FIRST \hss}]
%D \setupparagraphintro[first][\hbox to 3.5em{\tt TSRIF \hss}]
%D
%D some paragraph \par
%D some paragraph \par
%D
%D \setupparagraphintro[reset]
%D
%D some paragraph \par
%D \stoptyping

\permanent\protected\def\flushatparagraph#1%
  {\global\c_spac_paragraphs_intro_first\plusone
   \gtoksapp\t_spac_paragraphs_intro_first{#1}%
   \glet\spac_paragraphs_flush_intro\spac_paragraphs_flush_intro_indeed}

% see strc-reg.mkxl: when we have a nested note this can happen esp when we
% bookmark tagged crap

\appendtoks
    \enforced\let\doflushatpar\gobbleoneargument
\to \everypreroll

%D We don't for instance to flush indices or trigger a side float:

\prependtoks
    \forgetall
\to \everypreroll

%D Here comes the flusher (we misuse the one level expansion of token registers to
%D feed a nice stream into the paragraph.)

\protected\def\spac_paragraphs_flush_intro_indeed % we make sure that the token lists expand directly
  {\expanded{%                              % after another so the first code is there twice
     \ifconditional\c_spac_paragraphs_intro_each
       \ifconditional\c_spac_paragraphs_intro_next
         \glet\spac_paragraphs_flush_intro\spac_paragraphs_flush_intro_next
       \else
         \glet\spac_paragraphs_flush_intro\spac_paragraphs_flush_intro_each
       \fi
       \ifconditional\c_spac_paragraphs_intro_first
         \global\c_spac_paragraphs_intro_first\conditionalfalse
         \global\t_spac_paragraphs_intro_first\emptytoks
         \expand\t_spac_paragraphs_intro_first
       \fi
       \expand\t_spac_paragraphs_intro_each
     \else
       \ifconditional\c_spac_paragraphs_intro_next
         \glet\spac_paragraphs_flush_intro\spac_paragraphs_flush_intro_next
       \fi
       \ifconditional\c_spac_paragraphs_intro_first
         \global\c_spac_paragraphs_intro_first\conditionalfalse
         \global\t_spac_paragraphs_intro_first\emptytoks
         \expand\t_spac_paragraphs_intro_first
       \fi
     \fi}}

\protected\def\spac_paragraphs_flush_intro_next
  {\expanded{%
     \global\c_spac_paragraphs_intro_next\conditionalfalse
     \global\t_spac_paragraphs_intro_next\emptytoks
     \ifconditional\c_spac_paragraphs_intro_each
       \glet\spac_paragraphs_flush_intro\spac_paragraphs_flush_intro_each
       \expand\t_spac_paragraphs_intro_next
       \expand\t_spac_paragraphs_intro_each
     \else
       \glettonothing\spac_paragraphs_flush_intro
       \expand\t_spac_paragraphs_intro_next
     \fi}}

\protected\def\spac_paragraphs_flush_intro_each
  {\expand\t_spac_paragraphs_intro_each}

%D \macros
%D   {flushatnextpar}
%D
%D This macro collects data that will be flushed at the next paragraph. By using
%D this macro you can avoid interfering nodes (writes, etc).

\lettonothing\flushpostponednodedata % hook into everypar

\newbox    \b_spac_postponed_data
%newinteger\c_spac_postponed_data

% \installcorenamespace {postponednodesstack}
%
% \initializeboxstack\??postponednodesstack
%
% \protected\def\pushpostponednodedata
%   {\global\advanceby\c_spac_postponed_data\plusone
%    \savebox\??postponednodesstack{\the\c_spac_postponed_data}{\box\b_spac_postponed_data}}
%
% \protected\def\poppostponednodedata
%   {\global\setbox\b_spac_postponed_data\hbox{\foundbox\??postponednodesstack{\the\c_spac_postponed_data}}%
%    \global\advanceby\c_spac_postponed_data\minusone
%    \ifvoid\b_spac_postponed_data\else
%      \enforced\glet\flushpostponednodedata\spac_postponed_data_flush
%    \fi}

\newtoks\everyflushatnextpar

\permanent\protected\def\pushpostponednodedata
  {\globalpushbox\b_spac_postponed_data}

\permanent\protected\def\poppostponednodedata
  {\globalpopbox\b_spac_postponed_data
   \ifvoid\b_spac_postponed_data\else
     \enforced\glet\flushpostponednodedata\spac_postponed_data_flush
   \fi}

\permanent\protected\def\flushatnextpar % maybe we need a blocker that keep the data for a next par
  {\begingroup
   \expand\everyflushatnextpar
   \enforced\glet\flushpostponednodedata\spac_postponed_data_flush
   \dowithnextboxcs\spac_postponed_data_finish\hpack}

% \def\spac_postponed_data_finish
%   {\global\setbox\b_spac_postponed_data\hpack % to\zeropoint
%      {\box\b_spac_postponed_data\box\nextbox}%
%    \endgroup}
%
% This is better when used with protrusion which does not like too deeply nested
% boxes:
%
% \def\spac_postponed_data_finish
%   {\dontcomplain
%    \global\setbox\b_spac_postponed_data\hpack to \zeropoint
%      {\unhbox\b_spac_postponed_data\unhbox\nextbox}%
%    \endgroup}
%
% We could do this if there is content with widths ... not that is should be used
% that way, but this way we don't overflow:

\def\spac_postponed_data_finish
  {\dontcomplain
   \global\setbox\b_spac_postponed_data\hpack % to \zeropoint
     {\unhbox\b_spac_postponed_data
      \scratchdimen\wd\nextbox
      \unhbox\nextbox
      \ifcase\scratchdimen\else\kern-\scratchdimen\fi}%
   \endgroup}

\def\spac_postponed_data_flush
  {%\iftrialtypesetting \else
     \ifvoid\b_spac_postponed_data\else
       \hpack{\smashedbox\b_spac_postponed_data}% \box\b_spac_postponed_data
     \fi
     \enforced\glettonothing\flushpostponednodedata
   }%\fi}

\permanent\protected\def\doflushatpar % might be renamed
  {\ifvmode
     \expandafter\flushatnextpar
   \else
     \expandafter\firstofoneargument
   \fi}

%D Experiment:
%D
%D \starttyping
%D \startcombination[4*1]
%D     {\vtop{           \hsize #1 \setupalign[tolerant]                             \samplefile{ward}}} {tolerant}
%D     {\vtop{\darkblue  \hsize #1 \setupalign[tolerant,stretch]                     \samplefile{ward}}} {tolerant,stretch}
%D     {\vtop{\darkgreen \hsize #1 \setupalign[tolerant]        \optimize[flushleft] \samplefile{ward}}} {tolerant,fuzzy}
%D     {\vtop{\darkred   \hsize #1 \setupalign[tolerant]        \optimize[normal]    \samplefile{ward}}} {tolerant,lessfuzzy}
%D \stopcombination
%D \stoptyping

\installcorenamespace {optimize}

\installcommandhandler \??optimize {optimize} \??optimize

\setupoptimize
  [\c!lines=\zerocount,
   \c!left=\zeropoint,
   \c!right=\zeropoint,
   \c!distance=\zeropoint]

\permanent\protected\def\optimize[#S#1]%
  {\begingroup
   \ifhastok={#1}%
     \setupcurrentoptimize[#1]%
   \else
     \cdef\currentoptimize{#1}%
   \fi
   \scratchdimenone  {\optimizeparameter\c!left }%
   \scratchdimentwo  {\optimizeparameter\c!right}%
   \scratchcounterone{\optimizeparameter\c!lines}%
   \scratchdistance  {\optimizeparameter\c!distance}%
   \expanded
     {\endgroup
      \looseness\the\scratchcounterone\relax
      \emergencyextrastretch\the\scratchdistance\relax
      \ifzerodim\scratchdimenone\else
        \emergencyleftskip \zeropoint \s!plus \the\scratchdimenone\relax
      \fi
      \ifzerodim\scratchdimentwo\else
        \emergencyrightskip\zeropoint \s!plus \the\scratchdimentwo\relax
      \fi}%
   \ignorespaces}

\defineoptimize[+\v!line][\c!lines=\plusone]
\defineoptimize[-\v!line][\c!lines=\minusone]

\defineoptimize[\v!flushleft ][\c!right=2\bodyfontsize]
\defineoptimize[\v!flushright][\c!left=2\bodyfontsize]
\defineoptimize[\v!normal    ][\c!right=2\bodyfontsize,\c!distance=\bodyfontsize]

\protect \endinput
