1 {$INCLUDE ../shared/a_modes.inc}
6 set all 'temp-flex' flags for controls to 'flex'
7 reset all 'laywrap' flags for controls
8 build group arrays; for each group: find max size for group, adjust 'wantsize' controls to group max size
9 call 'calc max size' for top-level control
14 calcluate desired sizes (process flexes) using 'wantsize', set 'desiredsize' and 'desiredpos'
15 if control has children, call 'second pass' recursively with this control as parent
17 'group-element-changed', if any group element size was changed
18 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
21 if 'group-element-changed':
22 for each group: adjust controls to max desired size (wantsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
23 for other controls: if 'desiredsize' > 'maxsize', set 'wantsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
24 if 'second-again' or 'wrapping-changed':
26 reset 'wrapping-changed'
31 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
35 set 'wantsize' to max(size, maxsize, 0)
36 if 'size' is negative:
37 set 'temp-flex' flag to 0
39 call 'calc max size' for each child
40 set 'desiredmax' to 'wantsize'
41 do lines, don't distribute space (i.e. calc only wrapping),
42 for each complete line, set 'desiredmax' to max(desiredmax, linesize)
44 set 'desiredmax' to min(desiredmax, maxsize)
45 set 'wantsize' to 'desiredmax'
50 try to stuff controls in line until line width is less or equal to maxsize
51 distribute flex for filled line
52 continue until we still has something to stuff
56 we'll hold 'laywrap' flag for each control; it will be set if this control
57 starts a new line (even if this is the first control in line, as it is obviously
60 on redoing second pass, if 'laywrap' flag changed, set 'wrapping-changed' flag
66 function getDefSize (): TLaySize; // default size; <0: use max size
67 function getMaxSize (): TLaySize; // max size; <0: set to some huge value
68 function getFlex (): Integer; // <=0: not flexible
69 function isHorizBox (): Boolean; // horizontal layout for children?
70 function canWrap (): Boolean; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
71 function isLineStart (): Boolean; // `true` if this ctl should start a new line; ignored for vertical boxes
72 function getAlign (): Integer; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
73 function getExpand (): Boolean; // expanding in non-main direction: `true` will ignore align and eat all available space
74 procedure setActualSizePos (constref apos: TLayPos; constref asize: TLaySize);
75 function getHGroup (): AnsiString; // empty: not grouped
76 function getVGroup (): AnsiString; // empty: not grouped
77 function nextSibling (): ControlT;
78 function firstChild (): ControlT;
87 // ////////////////////////////////////////////////////////////////////////// //
89 generic TFlexLayouterBase
<ControlT
> = class
94 type LayControlIdx
= Integer;
99 FlagHorizBox
= LongWord(1) shl 0; // horizontal layout for children
100 FlagLineStart
= LongWord(1) shl 1;
101 FlagLineCanWrap
= LongWord(1) shl 2;
103 FlagLineDidWrap
= LongWord(1) shl 3; // will be set when line was wrapped
104 FlagInGroup
= LongWord(1) shl 4; // set if this control is a member of any group
105 FlagExpand
= LongWord(1) shl 5;
106 FlagLineFirst
= LongWord(1) shl 6;
110 PLayControl
= ^TLayControl
;
113 myidx
: LayControlIdx
;
115 flags
: LongWord; // see below
117 wantsize
, desiredsize
, maxsize
: TLaySize
;
120 parent
: LayControlIdx
; // = -1;
121 firstChild
: LayControlIdx
; // = -1;
122 nextSibling
: LayControlIdx
; // = -1;
125 function getDidWrap (): Boolean; inline;
126 procedure setDidWrap (v
: Boolean); inline;
129 procedure initialize (); inline;
131 function horizBox (): Boolean; inline;
132 function lineStart (): Boolean; inline;
133 function canWrap (): Boolean; inline;
134 function inGroup (): Boolean; inline;
135 function expand (): Boolean; inline;
136 function firstInLine (): Boolean; inline;
139 property didWrap
: Boolean read getDidWrap write setDidWrap
;
142 PLayGroup
= ^TLayGroup
;
145 ctls
: array of LayControlIdx
;
148 TLayCtlArray
= array of TLayControl
;
149 TLayGrpArray
= array of TLayGroup
;
152 ctlist
: TLayCtlArray
;
153 groups
: array[0..1] of TLayGrpArray
; // horiz, vert
156 groupElementChanged
: Boolean;
157 wrappingChanged
: Boolean;
160 procedure fixFlags (cidx
: LayControlIdx
);
161 procedure doChildren (parent
: LayControlIdx
; child
: ControlT
);
162 procedure appendToGroup (const gname
: AnsiString;cidx
: LayControlIdx
;gidx
: Integer);
163 procedure setupGroups ();
165 // this also sets `tempFlex`
166 procedure calcMaxSizeInternal (cidx
: LayControlIdx
);
168 procedure fixLine (me
: PLayControl
; i0
, i1
: LayControlIdx
; cury
: Integer; var spaceLeft
: Single; var flexTotal
: Integer; var flexBoxCount
: Integer);
169 // do box layouting; call `layBox()` recursively if necessary
170 procedure layBox (boxidx
: LayControlIdx
);
172 procedure firstPass ();
173 procedure secondPass ();
174 procedure thirdPass ();
175 procedure fourthPass ();
177 procedure dumpList (cidx
: LayControlIdx
; indent
: Integer);
181 TChildrenEnumerator
= record
187 constructor Create (constref actls
: TLayCtlArray
; acur
: Integer);
188 function moveNext (): Boolean; inline;
189 function getCurrent (): PLayControl
; inline;
190 function getEnumerator (): TChildrenEnumerator
; inline;
191 property current
: PLayControl read getCurrent
;
195 constructor Create ();
196 destructor Destroy (); override;
201 // build control and group lists
202 procedure setup (root
: ControlT
);
204 function forChildren (cidx
: LayControlIdx
): TChildrenEnumerator
; inline;
208 procedure dumpFlat ();
219 // ////////////////////////////////////////////////////////////////////////// //
220 procedure TFlexLayouterBase
.TLayControl
.initialize (); inline;
222 FillChar(self
, 0, sizeof(self
));
228 function TFlexLayouterBase
.TLayControl
.horizBox (): Boolean; inline; begin result
:= ((flags
and FlagHorizBox
) <> 0); end;
229 function TFlexLayouterBase
.TLayControl
.lineStart (): Boolean; inline; begin result
:= ((flags
and FlagLineStart
) <> 0); end;
230 function TFlexLayouterBase
.TLayControl
.canWrap (): Boolean; inline; begin result
:= ((flags
and FlagLineCanWrap
) <> 0); end;
231 function TFlexLayouterBase
.TLayControl
.inGroup (): Boolean; inline; begin result
:= ((flags
and FlagInGroup
) <> 0); end;
232 function TFlexLayouterBase
.TLayControl
.expand (): Boolean; inline; begin result
:= ((flags
and FlagExpand
) <> 0); end;
233 function TFlexLayouterBase
.TLayControl
.firstInLine (): Boolean; inline; begin result
:= ((flags
and FlagLineFirst
) <> 0); end;
235 function TFlexLayouterBase
.TLayControl
.getDidWrap (): Boolean; inline; begin result
:= ((flags
and FlagLineDidWrap
) <> 0); end;
236 procedure TFlexLayouterBase
.TLayControl
.setDidWrap (v
: Boolean); inline; begin if (v
) then flags
:= flags
or FlagLineDidWrap
else flags
:= flags
and (not FlagLineDidWrap
); end;
239 // ////////////////////////////////////////////////////////////////////////// //
240 constructor TFlexLayouterBase
.TChildrenEnumerator
.Create (constref actls
: TLayCtlArray
; acur
: Integer);
247 function TFlexLayouterBase
.TChildrenEnumerator
.moveNext (): Boolean; inline;
251 if (cur
>= 0) and (cur
< Length(ctls
)) then cur
:= ctls
[cur
].firstChild
else cur
:= -1;
256 cur
:= ctls
[cur
].nextSibling
;
258 result
:= (cur
>= 0);
261 function TFlexLayouterBase
.TChildrenEnumerator
.getCurrent (): PLayControl
; inline;
263 result
:= @ctls
[cur
];
266 function TFlexLayouterBase
.TChildrenEnumerator
.getEnumerator (): TChildrenEnumerator
; inline;
272 // ////////////////////////////////////////////////////////////////////////// //
273 constructor TFlexLayouterBase
.Create ();
280 groupElementChanged
:= false;
281 wrappingChanged
:= false;
285 destructor TFlexLayouterBase
.Destroy ();
292 function TFlexLayouterBase
.forChildren (cidx
: LayControlIdx
): TChildrenEnumerator
; inline;
294 result
:= TChildrenEnumerator
.Create(ctlist
, cidx
);
298 procedure TFlexLayouterBase
.clear ();
306 procedure TFlexLayouterBase
.fixFlags (cidx
: LayControlIdx
);
310 assert((cidx
>= 0) and (cidx
< Length(ctlist
)));
313 if (lc
.ctl
.isHorizBox
) then lc
.flags
:= lc
.flags
or FlagHorizBox
;
314 if (lc
.ctl
.isLineStart
) then lc
.flags
:= lc
.flags
or FlagLineStart
;
315 if (lc
.ctl
.canWrap
) then lc
.flags
:= lc
.flags
or FlagLineCanWrap
;
316 if (lc
.ctl
.getExpand
) then lc
.flags
:= lc
.flags
or FlagExpand
;
317 lc
.aligndir
:= lc
.ctl
.getAlign
;
321 procedure TFlexLayouterBase
.doChildren (parent
: LayControlIdx
; child
: ControlT
);
323 cidx
: LayControlIdx
= -1;
326 assert((parent
>= 0) and (parent
< Length(ctlist
)));
327 assert(ctlist
[parent
].firstChild
= -1);
328 while (child
<> nil) do
330 SetLength(ctlist
, Length(ctlist
)+1);
331 lc
:= @ctlist
[High(ctlist
)];
334 cidx
:= LayControlIdx(High(ctlist
));
335 ctlist
[parent
].firstChild
:= cidx
;
336 // first child is always linestart
337 lc
.flags
:= lc
.flags
or FlagLineStart
or FlagLineFirst
;
341 ctlist
[cidx
].nextSibling
:= LayControlIdx(High(ctlist
));
342 cidx
:= LayControlIdx(High(ctlist
));
348 doChildren(cidx
, child
.firstChild
);
349 child
:= child
.nextSibling
;
354 procedure TFlexLayouterBase
.appendToGroup (const gname
: AnsiString; cidx
: LayControlIdx
; gidx
: Integer);
358 if (Length(gname
) = 0) then exit
;
359 assert((cidx
>= 0) and (cidx
< Length(ctlist
)));
360 assert((gidx
= 0) or (gidx
= 1));
361 ctlist
[cidx
].flags
:= ctlist
[cidx
].flags
or FlagInGroup
;
362 for f
:= 0 to High(groups
[gidx
]) do
364 if (groups
[gidx
][f
].name
= gname
) then
366 SetLength(groups
[gidx
][f
].ctls
, Length(groups
[gidx
][f
].ctls
)+1);
367 groups
[gidx
][f
].ctls
[High(groups
[gidx
][f
].ctls
)] := cidx
;
372 f
:= Length(groups
[gidx
]);
373 SetLength(groups
[gidx
], f
+1);
374 groups
[gidx
][f
].name
:= gname
;
375 SetLength(groups
[gidx
][f
].ctls
, Length(groups
[gidx
][f
].ctls
)+1);
376 groups
[gidx
][f
].ctls
[High(groups
[gidx
][f
].ctls
)] := cidx
;
380 procedure TFlexLayouterBase
.setupGroups ();
385 for idx
:= 0 to High(ctlist
) do
388 appendToGroup(lc
.ctl
.getHGroup
, LayControlIdx(idx
), 0);
389 appendToGroup(lc
.ctl
.getVGroup
, LayControlIdx(idx
), 1);
394 // build control and group lists
395 procedure TFlexLayouterBase
.setup (root
: ControlT
);
398 if (root
= nil) then exit
;
400 SetLength(ctlist
, 1);
401 ctlist
[0].myidx
:= 0;
402 ctlist
[0].ctl
:= root
;
404 ctlist
[0].flags
:= ctlist
[0].flags
or FlagLineStart
or FlagLineFirst
;
405 doChildren(0, root
.firstChild
);
414 // this also sets `tempFlex`
415 procedure TFlexLayouterBase
.calcMaxSizeInternal (cidx
: LayControlIdx
);
419 negw
{, negh}: Boolean;
420 curwdt
, curhgt
, totalhgt
: Integer;
423 if (cidx
< 0) or (cidx
>= Length(ctlist
)) then exit
;
426 msz
:= lc
.ctl
.getMaxSize
;
427 //lc.wantsize := lc.ctl.getDefSize;
428 negw
:= (lc
.wantsize
.w
<= 0);
429 //negh := (lc.wantsize.h <= 0);
431 //if (lc.wantsize.w < msz.w) lc.wantsize.w := msz.w;
432 //if (lc.wantsize.h < msz.h) lc.wantsize.h := msz.h;
434 //writeln('calcsize #', cidx, '; wantsize=', lc.wantsize, '; ctl.maxsize=', msz);
436 lc
.tempFlex
:= lc
.ctl
.getFlex
;
438 for c
in forChildren(cidx
) do calcMaxSizeInternal(c
.myidx
);
440 if (lc
.horizBox
) then
443 if (negw
) then lc
.tempFlex
:= 0; // size is negative: don't expand
447 for c
in forChildren(cidx
) do
450 doWrap
:= (not c
.firstInLine
) and (c
.lineStart
);
452 if (not doWrap
) and (lc
.canWrap
) and (c
.canWrap
) and (msz
.w
> 0) and (curwdt
+c
.wantsize
.w
> lc
.wantsize
.w
) then doWrap
:= true;
456 if (lc
.wantsize
.w
< curwdt
) then lc
.wantsize
.w
:= curwdt
;
460 curwdt
+= c
.wantsize
.w
;
461 if (curhgt
< c
.wantsize
.h
) then curhgt
:= c
.wantsize
.h
;
464 if (lc
.wantsize
.w
< curwdt
) then lc
.wantsize
.w
:= curwdt
;
465 if (lc
.wantsize
.h
< totalhgt
) then lc
.wantsize
.h
:= totalhgt
;
471 for c
in forChildren(cidx
) do
473 if (lc
.wantsize
.w
< c
.wantsize
.w
) then lc
.wantsize
.w
:= c
.wantsize
.w
;
474 curhgt
+= c
.wantsize
.h
;
476 if (lc
.wantsize
.h
< curhgt
) then lc
.wantsize
.h
:= curhgt
;
478 if (lc
.wantsize
.w
< 1) then lc
.wantsize
.w
:= 1;
479 if (lc
.wantsize
.h
< 1) then lc
.wantsize
.h
:= 1;
481 if (lc
.maxsize
.w
< lc
.wantsize
.w
) then lc
.maxsize
.w
:= lc
.wantsize
.w
;
482 if (lc
.maxsize
.h
< lc
.wantsize
.h
) then lc
.maxsize
.h
:= lc
.wantsize
.h
;
486 procedure TFlexLayouterBase
.firstPass ();
489 needRecalcMaxSize
: Boolean;
495 // reset all 'laywrap' flags for controls, set initial 'wantsize'
496 for f
:= 0 to High(ctlist
) do
498 ctlist
[f
].didWrap
:= false;
499 ctlist
[f
].wantsize
:= ctlist
[f
].ctl
.getDefSize
;
502 calcMaxSizeInternal(0); // this also sets `tempFlex`
503 // find max size for group, adjust 'wantsize' controls to group max size
504 needRecalcMaxSize
:= false;
505 for gtype
:= 0 to 1 do
507 for f
:= 0 to High(groups
[gtype
]) do
509 grp
:= @groups
[gtype
][f
];
511 for c
:= 0 to High(grp
.ctls
) do
514 if (maxsz
< ctlist
[cidx
].wantsize
[gtype
]) then maxsz
:= ctlist
[cidx
].wantsize
[gtype
];
516 for c
:= 0 to High(grp
.ctls
) do
519 if (maxsz
<> ctlist
[cidx
].wantsize
[gtype
]) then
521 needRecalcMaxSize
:= true;
522 ctlist
[cidx
].wantsize
[gtype
] := maxsz
;
527 // recalc maxsize if necessary
528 if (needRecalcMaxSize
) then calcMaxSizeInternal(0);
534 procedure TFlexLayouterBase
.fixLine (me
: PLayControl
; i0
, i1
: LayControlIdx
; cury
: Integer; var spaceLeft
: Single; var flexTotal
: Integer; var flexBoxCount
: Integer);
545 osz
:= lc
.desiredsize
;
546 lc
.desiredsize
:= lc
.wantsize
;
547 lc
.desiredpos
.x
:= curx
;
548 lc
.desiredpos
.y
:= cury
;
549 curx
+= lc
.desiredsize
.w
;
551 if (lc
.tempFlex
> 0) and (spaceLeft
> 0) then
553 toadd
:= trunc(spaceLeft
*lc
.tempFlex
/flexTotal
+0.5);
557 lc
.desiredsize
.w
+= toadd
;
559 // compensate (crudely) rounding errors
560 if (curx
> me
.desiredsize
.w
) then begin lc
.desiredsize
.w
-= 1; curx
-= 1; end;
562 layBox(lc
.firstChild
);
565 if (lc
.inGroup
) and (not lc
.desiredsize
.equals(osz
)) then groupElementChanged
:= true;
566 i0
:= lc
.nextSibling
;
570 spaceLeft
:= me
.wantsize
.w
;
574 // do box layouting; call `layBox()` recursively if necessary
575 procedure TFlexLayouterBase
.layBox (boxidx
: LayControlIdx
);
578 flexTotal
: Integer; // total sum of flex fields
579 flexBoxCount
: Integer; // number of boxes
582 maxwdt
, maxhgt
: Integer;
583 lineStartIdx
: LayControlIdx
;
588 if (boxidx
< 0) or (boxidx
>= Length(ctlist
)) then exit
;
589 me
:= @ctlist
[boxidx
];
591 // if we have no children, just set desired size and exit
592 me
.desiredsize
:= me
.wantsize
;
593 if (me
.firstChild
= -1) then exit
;
595 // first, layout all children; also, gather some flex data
596 for lc
in forChildren(boxidx
) do layBox(lc
.myidx
);
598 // second, layout lines, distribute flex data
599 if (me
.horizBox
) then
605 fixLine(me
, -1, -1, cury
, spaceLeft
, flexTotal
, flexBoxCount
); //HACK!
607 lineStartIdx
:= me
.firstChild
;
609 for lc
in forChildren(boxidx
) do
612 doWrap
:= (not lc
.firstInLine
) and (lc
.lineStart
);
614 if (not doWrap
) and (lc
.canWrap
) and (lc
.canWrap
) and (lc
.desiredsize
.w
> 0) and (spaceLeft
< lc
.desiredsize
.w
) then doWrap
:= true;
617 // new line, fix this one
618 if (not lc
.didWrap
) then
620 wrappingChanged
:= true;
623 fixLine(me
, lineStartIdx
, lc
.myidx
, cury
, spaceLeft
, flexTotal
, flexBoxCount
);
625 lineStartIdx
:= lc
.myidx
;
631 wrappingChanged
:= true;
635 spaceLeft
-= lc
.desiredsize
.w
;
636 if (maxhgt
< lc
.desiredsize
.h
) then maxhgt
:= lc
.desiredsize
.h
;
637 if (lc
.tempFlex
> 0) then
639 flexTotal
+= lc
.tempFlex
;
644 fixLine(me
, lineStartIdx
, -1, cury
, spaceLeft
, flexTotal
, flexBoxCount
);
652 spaceLeft
:= me
.wantsize
.h
;
655 for lc
in forChildren(boxidx
) do
657 spaceLeft
-= lc
.desiredsize
.h
;
658 if (maxwdt
< lc
.desiredsize
.w
) then maxwdt
:= lc
.desiredsize
.w
;
659 if (lc
.tempFlex
> 0) then
661 flexTotal
+= lc
.tempFlex
;
668 for lc
in forChildren(boxidx
) do
670 lc
.desiredsize
:= lc
.wantsize
;
671 lc
.desiredpos
.x
:= 0;
672 lc
.desiredpos
.y
:= cury
;
673 cury
+= lc
.desiredsize
.h
;
675 if (lc
.tempFlex
> 0) and (spaceLeft
> 0) then
677 toadd
:= trunc(spaceLeft
*lc
.tempFlex
/flexTotal
+0.5);
681 lc
.desiredsize
.h
+= toadd
;
683 // compensate (crudely) rounding errors
684 if (cury
> me
.desiredsize
.h
) then begin lc
.desiredsize
.h
-= 1; cury
-= 1; end;
686 layBox(lc
.firstChild
);
696 calcluate desired sizes (process flexes) using 'wantsize', set 'desiredsize' and 'desiredpos'
697 if control has children, call 'second pass' recursively with this control as parent
699 'group-element-changed', if any group element size was changed
700 'wrapping-changed', if not 'firsttime', and wrapping was changed (i.e. first pass will not set the flag)
702 procedure TFlexLayouterBase
.secondPass ();
705 groupElementChanged
:= false;
706 wrappingChanged
:= false;
708 if (Length(ctlist
) > 0) then
710 ctlist
[0].desiredpos
:= TLayPos
.Create(0, 0);
714 // fix 'wrapping-changed' flag
715 if (firstTime
) then begin wrappingChanged
:= false; firstTime
:= false; end;
721 if 'group-element-changed':
722 for each group: adjust controls to max desired size (wantsize), set 'temp-flex' flags to 0 for 'em, set 'second-again' flag
723 for other controls: if 'desiredsize' > 'maxsize', set 'wantsize' to 'maxsize', set 'temp-flex' flag to 0, set 'second-again' flag
724 if 'second-again' or 'wrapping-changed':
726 reset 'wrapping-changed'
730 procedure TFlexLayouterBase
.thirdPass ();
732 secondAgain
: Boolean;
736 secondAgain
:= false;
737 if (groupElementChanged
) then
741 if (not secondAgain
) and (not wrappingChanged
) then break
;
750 set 'actualsize' and 'actualpos' to 'desiredsize' and 'desiredpos'
753 procedure TFlexLayouterBase
.fourthPass ();
757 for f
:= 0 to High(ctlist
) do
759 ctlist
[f
].ctl
.setActualSizePos(ctlist
[f
].desiredpos
, ctlist
[f
].desiredsize
);
764 procedure TFlexLayouterBase
.layout ();
773 procedure TFlexLayouterBase
.dumpFlat ();
779 for f
:= 0 to High(ctlist
) do
782 ds
:= lc
.ctl
.getDefSize
;
783 ms
:= lc
.ctl
.getMaxSize
;
784 writeln(lc
.myidx
, ': wantsize:', lc
.wantsize
.toString(), '; desiredsize=', lc
.desiredsize
.toString(), '; maxsize=', lc
.maxsize
.toString(), '; tempFlex=', lc
.tempFlex
, '; flags=', lc
.flags
,
785 '; parent=', lc
.parent
, '; next=', lc
.nextSibling
, '; child=', lc
.firstChild
, '; ctl.size=', ds
.toString(), '; ctl.maxsize=', ms
.toString());
790 procedure TFlexLayouterBase
.dumpList (cidx
: LayControlIdx
; indent
: Integer);
798 for f
:= 0 to High(indent
) do write(' ');
799 writeln(lc
.myidx
, ': wantsize:', lc
.wantsize
.toString
, '; desiredsize=', lc
.desiredsize
.toString
, '; maxsize=', lc
.maxsize
.toString
, '; tempFlex=', lc
.tempFlex
, '; despos=', lc
.desiredpos
.toString
);
800 dumpList(lc
.firstChild
, indent
+2);
801 cidx
:= lc
.nextSibling
;
806 procedure TFlexLayouterBase
.dump ();
812 // ////////////////////////////////////////////////////////////////////////// //
815 auto win := new GuiControl();
816 (win ~= new GuiControl()).mSize := TLaySize(10, 5);
817 (win ~= new GuiControl()).mSize := TLaySize(16, 8);
819 //win.mSize := TLaySize(40, 20);
821 auto lay := TFlexLayouterBase!GuiControl();
824 writeln('============================');
827 writeln('=== initial ===');
830 //lay.calcMaxSizeInternal(0);
833 writeln('=== after first pass ===');
837 writeln('=== after second pass ===');
841 writeln('=== final ===');