page-mcl.mkxl /size: 17 Kb    last modification: 2023-12-21 09:44
1%D \module
2%D   [       file=page-mcl,
3%D        version=2020.07.26, % stripped down redone page-mul
4%D          title=\CONTEXT\ Page Macros,
5%D       subtitle=Multicolumns Limited,
6%D         author=Hans Hagen,
7%D           date=\currentdate,
8%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]
9%C
10%C This module is part of the \CONTEXT\ macro||package and is
11%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
12%C details.
13
14\writestatus{loading}{ConTeXt Page Macros / Multicolumns Limited}
15
16\unprotect
17
18%D Columns are kind of hairy in \TEX\ and we would be better if no one needed them.
19%D Anyway, we do need some support and no mechanism can serve all. The original
20%D multicolumn mechanism of \MKII\ was never fully adapted to \MKIV, where mixed
21%D columns and page columns showed up instead. However, for some cases a dumb
22%D mechanism makes sense, so again we introduce multi columns, but without float
23%D hacks. This one will be optimized for mixed usage as for instance itemize needs.
24%D Their main advantage is that they can better deal with notes, which are hairy in
25%D themselves. This will (for now) only happen in \LMTX.
26%D
27%D The code is stripped down ancient \MKII\ code and might become cleaner as it
28%D evolves. Macros keep similar names but take a different namespace. The code is
29%D not yet perfect wrt spacing!
30
31% \enableexperiments[itemize.columns]
32%
33% \starttext
34%
35%     \startmulticolumns
36%         \dorecurse{10}{\samplefile{ward}\footnote{note #1}\par}
37%     \stopmulticolumns
38%
39%     \dorecurse{4}{\samplefile{ward}\par}
40%
41%     \startitemize[packed,columns,two]
42%         \dorecurse{100}{\startitem test #1 \footnote{this is a footnote #1}\stopitem}
43%     \stopitemize
44%
45% \stoptext
46
47\ifdefined \startmulticolumns
48
49    % we're testing and don't want to remake the format
50
51\else
52
53\installcorenamespace {multicolumns}
54
55\installframedcommandhandler \??multicolumns {multicolumns} \??multicolumns
56
57\newdimension   \d_page_mcl_available_width
58\newdimension   \d_page_mcl_distance
59\newdimension   \d_page_mcl_leftskip
60\newdimension   \d_page_mcl_rightskip
61\newdimension   \d_page_mcl_used_width
62\newdimension   \d_page_mcl_temp
63\newdimension   \d_page_mcl_saved_pagetotal  % brrr, still needed ?
64
65\newinteger    \c_page_mcl_balance_minimum
66\newinteger    \c_page_mcl_n_of_lines
67
68\newbox        \b_page_mcl_preceding
69\newdimension  \d_page_mcl_preceding_height
70\newconditional\c_page_mcl_preceding_present
71
72\newbox        \b_page_mcl_rest_of_page
73\newbox        \b_page_mcl_page
74
75\newconditional\c_page_mcl_reverse
76\newconditional\c_page_mcl_balance
77
78\newconstant   \c_page_mcl_routine
79
80\setnewconstant\c_page_mcl_routine_regular   \zerocount
81\setnewconstant\c_page_mcl_routine_intercept \plusone
82\setnewconstant\c_page_mcl_routine_continue  \plustwo
83\setnewconstant\c_page_mcl_routine_balance   \plusthree
84\setnewconstant\c_page_mcl_routine_error     \plusfour
85
86\newbox        \b_page_mcl_balance_content
87\newconstant   \c_page_mcl_balance_tries_max
88\newinteger    \c_page_mcl_balance_tries
89\newdimension  \d_page_mcl_balance_target
90\newdimension  \d_page_mcl_balance_natural_height
91\newdimension  \d_page_mcl_balance_step
92\newconditional\c_page_mcl_balance_possible
93
94\c_page_mcl_balance_tries_max 250 % 100 is too small when floats are involved
95
96%def\m_page_mcl_overshoot_ratio{\ifgridsnapping0\else.5\fi}
97\def\m_page_mcl_overshoot_ratio{.5}
98
99\fi
100
101\protected\def\page_mcl_command_set_hsize
102  {\d_page_mcl_available_width\dimexpr
103      \makeupwidth
104     -\d_page_mcl_leftskip
105     -\d_page_mcl_rightskip
106     -\nofcolumns\d_page_mcl_distance
107     +\d_page_mcl_distance
108   \relax
109   \d_page_mcl_used_width\dimexpr
110      \d_page_mcl_available_width/\nofcolumns
111   \relax
112   \textwidth\d_page_mcl_used_width
113   \hsize\d_page_mcl_used_width}
114
115\protected\def\page_mcl_set_n_of_lines#1%
116  {\d_page_mcl_temp\dimexpr
117     +\textheight
118      \ifdim\d_page_mcl_preceding_height>\zeropoint -\d_page_mcl_preceding_height \fi
119%       \ifdim\ht\b_page_mcl_preceding>\zeropoint -\ht\b_page_mcl_preceding \fi
120     -#1%
121   \relax
122   \getnoflines\d_page_mcl_temp
123   \ifnum\layoutlines>\zerocount \ifnum\noflines>\layoutlines
124     \noflines\layoutlines
125   \fi \fi
126   \c_page_mcl_n_of_lines\noflines}
127
128
129% \protected\def\page_mcl_command_set_vsize
130%   {%%\page_one_command_set_vsize % indeed?
131%    \page_mcl_set_n_of_lines\zeropoint
132%    \d_page_mcl_temp\nofcolumns\dimexpr
133%       \c_page_mcl_n_of_lines\openlineheight
134%     %      +\m_page_mcl_overshoot_ratio\openlineheight % collect enough data
135%    \relax
136%    \global\vsize   \d_page_mcl_temp
137%    \global\pagegoal\d_page_mcl_temp} % let's do it only here, reports maxdimen anyway
138
139\protected\def\page_mcl_command_set_vsize
140  {\global\vsize\dimexpr\nofcolumns\textheight+\nofcolumns\lineheight\relax
141   \pagegoal\dimexpr
142     \vsize
143%   -\d_page_floats_inserted_top    % needs checking
144%   -\d_page_floats_inserted_bottom % needs checking
145    -\c_page_mix_n_of_columns\insertheights
146   \relax}
147
148\protected\def\page_mcl_command_routine
149  {\ifcase\c_page_mcl_routine
150     \page_one_command_routine
151   \or
152     \page_mcl_routine_intercept
153   \or
154     \page_mcl_routine_continue
155   \or
156     \page_mcl_routine_balance
157   \or
158     \page_mcl_routine_error
159   \fi}
160
161\let\page_mcl_command_package_contents\page_one_command_package_contents
162
163\def\page_mcl_routine_intercept
164  {\global\setbox\b_page_mcl_preceding\vbox
165     {\page_otr_command_flush_top_insertions
166      \unvbox\normalpagebox}}
167
168\def\page_mcl_routine_error
169  {\showmessage\m!columns3\empty
170   \page_otr_construct_and_shipout\unvbox\normalpagebox\zerocount} % three arguments
171
172\protected\def\page_mcl_initialize_variables
173  {\reseteverypar % maybe still freeze ....
174   \dontcomplain
175   \settopskip
176   \setmaxdepth
177   \topskip        1\topskip
178   \splittopskip    \topskip
179   \splitmaxdepth   \maxdepth
180   \boxmaxdepth     \maxdepth % dangerous
181   \emergencystretch\zeropoint
182   \relax}
183
184\def\page_mcl_flush_preceding_normal
185  {\unvbox\b_page_mcl_preceding}
186
187\def\page_mcl_flush_preceding_ongrid
188  {\scratchdimen\dimexpr
189     \d_page_mcl_saved_pagetotal
190    -\d_page_mcl_preceding_height
191  % -\topskip % no, this is already part of the saved total
192   \relax
193   \box\b_page_mcl_preceding
194   \kern\scratchdimen}
195
196\def\page_mcl_flush_packaged_columns_continued
197  {\page_mcl_flush_packaged_columns_indeed
198   \box\b_page_mcl_page}
199
200\def\page_mcl_flush_packaged_columns_balanced
201  {\bgroup
202   \page_mcl_flush_packaged_columns_indeed
203   \getnoflines{\htdp\b_page_mcl_page}%
204   \ht\b_page_mcl_page\dimexpr
205     \noflines\openlineheight
206     -\openstrutdepth
207     \ifgridsnapping
208        % quick hack (at least it works with itemize)
209     \else
210       -\openlineheight
211       +\topskip
212     \fi
213   \relax
214   \dp\b_page_mcl_page\openstrutdepth
215   \box\b_page_mcl_page
216   \egroup}
217
218\def\page_mcl_synchronize_marks
219  {\dohandleallcolumns{\page_marks_synchronize_column\plusone\nofcolumns\mofcolumns\currentcolumnbox}}
220
221\def\page_mcl_flush_packaged_columns_indeed
222  {\ifvoid\b_page_mcl_preceding
223     \c_page_mcl_preceding_present\conditionalfalse % will be set elsewhere
224   \else
225     \c_page_mcl_preceding_present\conditionaltrue
226     \page_apply_postprocessors_box\b_page_mcl_preceding
227   \fi
228   \forgetall
229   \page_mcl_initialize_variables
230   \page_mcl_synchronize_marks
231   \setbox\b_page_mcl_page\vpack
232     {\ifconditional\c_page_mcl_reverse\reversehpack\else\naturalhpack\fi to \makeupwidth
233        {\hskip\ifconditional\c_page_mcl_reverse\d_page_mcl_rightskip\else\d_page_mcl_leftskip\fi\relax
234         \dohandleallcolumns
235           {\wd\currentcolumnbox\d_page_mcl_used_width
236            \setbox\scratchbox\hpack{\strut\box\currentcolumnbox}% hm, why strut
237            \anch_mark_column_box\scratchbox\currentcolumn
238            \box\scratchbox
239            \hfil}%
240         \unskip
241         \hskip\ifconditional\c_page_mcl_reverse\d_page_mcl_leftskip\else\d_page_mcl_rightskip\fi}}%
242   \ifconditional\c_page_mcl_preceding_present
243     \c_page_mcl_preceding_present\conditionaltrue
244     \ifgridsnapping
245       \page_mcl_flush_preceding_ongrid % obey grid settings, force on grid
246     \else
247       \page_mcl_flush_preceding_normal % ignore grid settings, not on grid
248     \fi
249   \fi
250   \global\d_page_mcl_preceding_height\zeropoint
251   \page_otr_command_set_vsize
252   \dosomebreak\nobreak % hm, only needed when topstuff
253   \ifgridsnapping \else
254     \ifconditional\c_page_mcl_preceding_present
255       \nointerlineskip
256       \vskip\dimexpr\openstrutheight-\topskip\relax
257     \fi
258   \fi
259   \prevdepth\openstrutdepth
260   \nointerlineskip
261   \dp\b_page_mcl_page\zeropoint}
262
263\def\page_mcl_split_column#1#2% copy or box
264  {\global\setbox\currentcolumnbox\vsplit#1 upto #2}
265
266\def\page_mcl_routine_continue
267  {\bgroup
268   \forgetall
269   \page_mcl_initialize_variables
270   \settotalinsertionheight
271   \page_mcl_set_n_of_lines\totalinsertionheight
272   \d_page_mcl_balance_target\c_page_mcl_n_of_lines\openlineheight
273   \dohandleallcolumns{\page_mcl_split_column\normalpagebox\d_page_mcl_balance_target}%
274   \setbox\b_page_mcl_rest_of_page\vpack{\unvbox\normalpagebox}%
275   \dohandleallcolumns
276     {\global\setbox\currentcolumnbox\vpack to \d_page_mcl_balance_target
277        {\unvbox\currentcolumnbox % wel of niet \unvbox ?
278         \vfill}}%
279   \setbox\b_page_mcl_preceding\vpack{\page_mcl_flush_packaged_columns_continued}%
280   \page_otr_construct_and_shipout\box\b_page_mcl_preceding\zerocount % three arguments
281   \page_otr_command_set_hsize
282   \page_otr_command_set_vsize
283   \unvbox\b_page_mcl_rest_of_page
284   \egroup}
285
286\def\page_mcl_routine_balance
287  {\bgroup
288   % why no \forgetall here
289   \page_mcl_initialize_variables
290   \widowpenalty\zerocount
291   \setbox\b_page_mcl_balance_content\vpack{\unvbox\normalpagebox}%
292   \ifdim\ht\b_page_mcl_balance_content>\openlineheight % at least one line
293     \ifnum\c_page_mcl_balance_minimum<\plustwo % balance anyway
294       \c_page_mcl_balance_possible\conditionaltrue
295     \else % check criterium to available lines
296       \getnoflines{\ht\b_page_mcl_balance_content}%
297       \divideby\noflines \nofcolumns \relax
298       \ifnum\noflines<\c_page_mcl_balance_minimum \relax
299         \ifdim\dimexpr\ht\b_page_mcl_balance_content+\openlineheight\relax>\makeupheight
300           \c_page_mcl_balance_possible\conditionaltrue % column exceeding text height
301         \else
302           \c_page_mcl_balance_possible\conditionalfalse % it seems to fit
303         \fi
304       \else
305         \c_page_mcl_balance_possible\conditionaltrue % balance indeed
306       \fi
307     \fi
308   \else
309     \c_page_mcl_balance_possible\conditionalfalse % balancing does not make sense
310   \fi
311   \ifconditional\c_page_mcl_balance_possible % start balancing, was: \ifdim\ht\b_page_mcl_balance_content>\openlineheight
312     \page_mcl_balance_try_one
313     \page_mcl_balance_try_two
314   \else
315     % a one liner is not properly handled here, so best rewrite the text then
316     \showmessage\m!columns{10}\empty
317     \global\setbox\firstcolumnbox\vpack{\unvbox\b_page_mcl_balance_content}%
318   \fi
319   \c_page_mcl_routine\c_page_mcl_routine_error
320  %\baselinebottom % forces depth in separation rule
321   \page_mcl_flush_packaged_columns_balanced
322  %\allowbreak
323   \egroup}
324
325% \showmakeup
326
327\def\page_mcl_balance_try_one
328  {\d_page_mcl_balance_target\dimexpr\ht\b_page_mcl_balance_content+\topskip-\baselineskip\relax
329   \divideby\d_page_mcl_balance_target \nofcolumns
330   \vbadness\plustenthousand
331   \c_page_mcl_balance_tries\zerocount
332   \bgroup
333   \ifgridsnapping
334     \d_page_mcl_balance_step\lineheight
335   \else
336     \d_page_mcl_balance_step\spacingfactor\onepoint % rubish
337   \fi
338   \doloop\page_mcl_balance_try_one_attempt
339   \global\setbox\b_page_mcl_rest_of_page\box\voidbox
340   \ifnum\c_page_mcl_balance_tries>\c_page_mcl_balance_tries_max\relax
341     \showmessage\m!columns7\empty
342   \else
343     \showmessage\m!columns8{\the\c_page_mcl_balance_tries}%
344   \fi
345   \egroup}
346
347\def\page_mcl_balance_try_one_attempt
348  {\advanceby\c_page_mcl_balance_tries \plusone
349   \global\setbox\b_page_mcl_rest_of_page\copy\b_page_mcl_balance_content\relax
350   \dohandleallcolumns{\page_mcl_split_column\b_page_mcl_rest_of_page\d_page_mcl_balance_target}%
351   \d_page_mcl_balance_natural_height\zeropoint
352   \dohandleallcolumns\page_mcl_balance_try_one_attempt_step
353\advanceby\d_page_mcl_balance_natural_height-33\scaledpoint % some slack
354   \ifnum\c_page_mcl_balance_tries>\c_page_mcl_balance_tries_max\relax
355     \exitloop
356   \orelse\ifdim\ht\b_page_mcl_rest_of_page>\zeropoint
357     \advanceby\d_page_mcl_balance_target\d_page_mcl_balance_step\relax
358   \orelse\ifdim\d_page_mcl_balance_natural_height>\ht\firstcolumnbox\relax
359     \advanceby\d_page_mcl_balance_target\d_page_mcl_balance_step\relax
360   \else
361     \exitloop
362   \fi}
363
364\def\page_mcl_balance_try_one_attempt_step
365  {\ifcase\currentcolumn\or\else
366     \ifdim\ht\currentcolumnbox>\d_page_mcl_balance_natural_height\relax
367       \d_page_mcl_balance_natural_height\ht\currentcolumnbox
368     \fi
369   \fi}
370
371\def\page_mcl_balance_try_two % hm ... can probably go
372  {\dohandleallcolumnscs\page_mcl_balance_try_two_step}
373
374% \def\page_mcl_balance_try_two_step
375%   {\global\setbox\currentcolumnbox\vbox to \ht\firstcolumnbox
376%      {\box\currentcolumnbox
377%       \vfill}}
378
379\def\page_mcl_balance_try_two_step
380  {%\global\setbox\currentcolumnbox\box\currentcolumnbox
381   \ht\currentcolumnbox\ht\firstcolumnbox}
382
383\permanent\tolerant\protected\def\startmulticolumns[#S#1]%
384  {\bgroup
385   \ifinsidecolumns
386     \page_mcl_start_nop
387   \else
388     \setupmulticolumns[#1]%
389     \nofcolumns\multicolumnsparameter\c!n\relax
390     \ifnum\nofcolumns>\plusone
391       \page_mcl_start_yes
392       \nofmulticolumns\nofcolumns
393     \else
394       \page_mcl_start_nop
395     \fi
396   \fi}
397
398\aliased\let\stopmulticolumns\relax
399
400\def\page_mcl_start_nop
401  {\enforced\let\stopmulticolumns\page_mcl_stop_nop}
402
403\permanent\protected\def\page_mcl_stop_nop
404  {\egroup}
405
406\protected\def\page_mcl_start_yes
407  {\whitespace
408   \begingroup
409   \enforced\let\stopmulticolumns\page_mcl_stop_indeed
410   \global\insidecolumnstrue
411   \global\insidemulticolumnstrue
412   %
413   \d_page_mcl_distance\multicolumnsparameter\c!distance\relax
414   %
415   \ifcstok{\multicolumnsparameter\c!direction}\v!right
416     \c_page_mcl_reverse\conditionalfalse
417   \else
418     \c_page_mcl_reverse\conditionaltrue
419   \fi
420   %
421   \ifcstok{\multicolumnsparameter\c!balance}\v!yes
422     \c_page_mcl_balance\conditionaltrue
423   \else
424     \c_page_mcl_balance\conditionalfalse
425   \fi
426   %
427   \usealignparameter\multicolumnsparameter
428   %
429   \edef\p_blank{\multicolumnsparameter\c!blank}%
430   \ifempty\p_blank \else
431       \setupblank[\p_blank]%
432   \fi
433   %
434   \ifdim\s_spac_whitespace_parskip>\zeropoint\relax
435       \setupwhitespace[\p_blank]%
436   \fi
437   \c_page_mcl_balance_minimum\multicolumnsparameter\c!ntop\relax
438   %
439   \begingroup
440   %
441   \d_page_mcl_leftskip \leftskip
442   \d_page_mcl_rightskip\rightskip
443   \leftskip            \zeroskip
444   \rightskip           \zeroskip
445   \hangafter           \zerocount
446   \hangindent          \zeropoint
447   %
448   \widowpenalty        \zerocount % will become option
449   \clubpenalty         \zerocount % will become option
450   %
451   \ifdim\dimexpr\pagetotal+\parskip+\openlineheight\relax<\pagegoal
452     \allowbreak
453   \else
454     \break % sometimes fails
455   \fi
456   \appendtoks
457     \topskip1\topskip % best a switch
458   \to \everybodyfont
459   \expand\everybodyfont  % ugly here
460   \saveinterlinespace % ugly here
461   %
462   \initializecolumns\nofcolumns
463   %
464   \reseteverypar % todo
465   %
466   \ifzeropt\pagetotal\else
467     \verticalstrut
468     \vskip-\struttotal
469   \fi
470   \global\d_page_mcl_saved_pagetotal\pagetotal
471   \setupoutputroutine[\s!multicolumn]%
472   \c_page_mcl_routine\c_page_mcl_routine_intercept
473   \page_otr_trigger_output_routine
474   \global\d_page_mcl_preceding_height\ht\b_page_mcl_preceding
475   \c_page_mcl_routine\c_page_mcl_routine_continue
476   \page_otr_command_set_hsize
477   \page_otr_command_set_vsize}
478
479\permanent\protected\def\page_mcl_stop_indeed
480  {\relax
481   \synchronizeoutput
482   \par
483   \ifconditional\c_page_mcl_balance
484     \c_page_mcl_routine\c_page_mcl_routine_continue
485     \goodbreak
486     \c_page_mcl_routine\c_page_mcl_routine_balance
487   \else
488     \goodbreak
489   \fi
490   % still the multi column routine
491   \page_otr_trigger_output_routine % the prevdepth is important, try e.g. toclist in
492   \prevdepth\zeropoint % columns before some noncolumned text text
493   %
494   \c_page_mcl_routine\c_page_mcl_routine_regular
495   %
496   \ifvoid\b_page_mcl_preceding\else
497     \unvbox\b_page_mcl_preceding
498   \fi
499   \global\d_page_mcl_preceding_height\zeropoint
500   \endgroup % here
501   \nofcolumns\plusone
502   \nofmulticolumns\plusone
503   \page_otr_command_set_vsize
504   \dosomebreak\allowbreak
505   \page_floats_column_pop_saved
506   %
507   \global\insidemulticolumnsfalse
508   \global\insidecolumnsfalse
509   \endgroup
510   \egroup}%
511
512\setupmulticolumns
513  [\c!n=2,
514   \c!ntop=1,
515   \c!direction=\v!right,
516   \c!distance=1.5\bodyfontsize, % influenced by switching
517   \c!balance=\v!yes,
518   \c!align={\v!text,\v!tolerant},
519   \c!blank={\v!line,\v!fixed}]
520
521\defineoutputroutine
522  [\s!multicolumn]
523  [\s!page_otr_command_routine         =\page_mcl_command_routine,
524   \s!page_otr_command_package_contents=\page_mcl_command_package_contents,
525   \s!page_otr_command_set_vsize       =\page_mcl_command_set_vsize,
526   \s!page_otr_command_set_hsize       =\page_mcl_command_set_hsize]
527
528\let\strc_itemgroups_start_columns_old\strc_itemgroups_start_columns
529\let\strc_itemgroups_stop_columns_old \strc_itemgroups_stop_columns
530
531\def\strc_itemgroups_start_columns_new{\startmulticolumns[\c!n=\itemgroupparameter\c!n]}
532\def\strc_itemgroups_stop_columns_new {\stopmulticolumns}
533
534\installtexexperiment
535  {itemize.columns}
536  {\let\strc_itemgroups_start_columns\strc_itemgroups_start_columns_new
537   \let\strc_itemgroups_stop_columns \strc_itemgroups_stop_columns_new}
538  {\let\strc_itemgroups_start_columns\strc_itemgroups_start_columns_old
539   \let\strc_itemgroups_stop_columns \strc_itemgroups_stop_columns_old}
540
541\protect \endinput
542