Hi,
Let’s talk about margin boxes. (Warning: long email)
First, I would like to ask a question to all implementations that have
margin boxes: What do you do about section 6.3.2, Variable Dimension
Computation Rules? Do you implement it exactly as specified? Maybe only
in some cases? Or do you do something else entirely?
(Please CC more people if you know of other affected implementations.)
Short version
=============
I think that − as currently spec’ed − 6.3.2 is unreasonably hard to
implement, under-specified and not even sufficient for the desired
behavior. I would like to get consensus that it needs to be re-written.
If we can establish that a replacement is needed, I have a proposal
below that we can discuss.
Longer version
==============
(You can skip this if you agree and don’t need to be convinced.)
The Variable Dimension Computation Rules determine how, for example, the
width is distributed between top-left, top-center and top-right margin
boxes.
The current spec is hard to implement
-------------------------------------
The specified behavior is a quadratic optimization problem of up to 9
variables under both equality and inequality constraints.
I studied for a while Karesh–Kuhn–Tucker, modified Simplex algorithms
and other stuff that looked like it could solve this kind of problem.
But this is just beyond my reach, although I have a decent understanding
of university-level math.
The alternative is to use an existing solver. These are often part of
big numerical libraries that are not otherwise needed for CSS, contain
more code than my whole layout engine, and are generally hard to
integrate. I’m not even talking of the runtime cost of such algorithms.
I understand how quadratic optimization can give attractive results, but
I argue that the burden on implementations is simply to high. It does
not compare to anything else in CSS. (I mean, implementing 3D transforms
from scratch is pretty hard, but this is about sizing three simple boxes!)
The current spec is under-specified
-----------------------------------
Let us take a document where we can set all margins to zero while
staying under the various constraints. This is obviously the optimal
solution to the quadratic problem. But since the widths do not appear at
all in the goal function, we may still have an infinity of solutions!
In other words, the optimization does not tell us how to set the used
values for 'width'.
The current spec does not encode the desired behavior
-----------------------------------------------------
Quoted from Example 18:
> As the intrinsic width of the top-left contents is approximately
> twice the intrinsic width of that of the top-right, the top-left
> margin box is approximately twice as wide as the top-right margin
> box.
This "proportional" behavior seems interesting to have, but it is
nowhere to be found in normative text!
Related to the previous section: when multiple optimal solutions exist,
general-purpose solvers will only give one. There is no guarantee that
it will widths anywhere near proportional.
(Side note: maybe avoid to mix the "intrinsic" and "preferred"
terminologies)
http://dbaron.org/css/intrinsic/http://www.w3.org/TR/CSS21/visudet.html#float-width
Proposed replacement
====================
Even without the quadratic stuff, what the current spec tries to do with
'auto' margins is still very complicated. 9 properties that may or may
not be 'auto' yield a lot of combinations.
Therefore, I suggest the following rule:
| If the margin-left or margin-right property of any of the
| three boxes computes to 'auto', the used value is zero.
It *is* a loss a functionality, but I think the trade-off is worth it.
This change is essential in my proposal, which is still nowhere near
Fixed Dimension Computation Rules’s simplicity.
Remember that this is three simple boxes with (most often) very small
texts. Any fancy layout is probably overkill.
Other than that, a side effect is that margin boxes with 'width: auto'
will never grow larger than their preferred width. I don’t know if this
is a problem; authors can always use percentage widths if 'auto' does
not give the desired behavior.
"is instantiated" is used with reference to 6.2 instead of "is not empty
or if its computed width is not ‘auto’".
Indentation matters in the algorithm below. I don’t know if there is
a way to reduce nesting while keeping the text clear enough.
===== Start of proposed text =====
6.3.2. Margin Box Variable Dimension Computation Rules
The following rules apply to ‘top-left’, ‘top-center’ and ‘top-right’
margin boxes, which are referred to in this section as A, B, and C,
respectively.
If the margin-left or margin-right property of any of the three boxes
computes to 'auto', the used value is zero.
The width of the containing block is known at this point, so percentages
can be resolved. Only the used values for 'auto' widths remain to
be determined.
Definitions:
*preferred outer width* of a box
If width is 'auto', the preferred width plus left and right margins,
borders and padding.
Otherwise, the outer width.
*preferred minimum outer width* of a box
If width is 'auto', the preferred minimum width plus left and right
margins, borders and padding.
Otherwise, the outer width.
*overall available width*
the width of the containing block minus the left and right margins,
borders and padding of all three boxes.
Algorithm:
* If B is not <a href="#populating-margin-boxes">instantiated</a>
* If both A’s and C’s widths are 'auto'
* If the sum of their preferred minimum outer widths is greater
than the width of the containing block, their used width are
their respective preferred minimum widths.
* Otherwise, if the sum of their preferred outer widths is less
than the width of the containing block, their used width are
their respective preferred widths.
* Otherwise, their used widths are chosen to add up to the
overall available width, proportionally to their respective
preferred width.
* Otherwise, if one of A’s or C’s width is 'auto', its used width is
the "shrink-to-fit" width with the available width set to:
* the containing block’s width
* minus the outer width of the other box.
* minus the box’s margins, borders and padding
* Otherwise (if B *is* instantiated)
* If B’s width is 'auto'
* If neither A’s width or C’s width are 'auto', B’s used width is
the "shrink-to-fit" width with the available width set to:
* the containing block’s width
* minus the greater of A’s and C’s outer widths
* Otherwise
* Define AC-minimum as the greater of A’s and C’s preferred
minimum outer width, and AC-preferred as the greater of their
preferred outer width.
* If B’s preferred minimum outer width plus AC-minimum is greater
than the width of the containing block, the used width for B
is its preferred minimum width.
* Otherwise, if B’s preferred outer width plus AC-preferred is
less than the width of the containing block, the used width
for B is its preferred minimum width.
* Otherwise, set any 'auto' width proportionally to the
corresponding preferred width so that they add up to the
overall available width minus any non-'auto' width.
* If A’s width or C’s width or both are still 'auto', their
respective used width are the "shrink-to-fit" width with the
available width set to:
* half of the containing block’s width
* minus half of the outer width of B.
* minus the box’s (A or C) margins, borders and padding
Now that all three boxes have a determined outer width, they are
positioned so that:
* The left outer edge of A is flush with the left edge of the
containing block
* The outer area of B is centered in the containing block.
* The right outer edge of C is flush with the right edge of the
containing block.
Although the rules above try to avoid it, the boxes may end up
overlapping each other.
===== End of proposed text =====
The note on negative widths and what follows (how the rules translate
for margin boxes on the bottom, left and right sides) are kept as-is.
A, B and C could be replaced by "the {left,center,right} box" in the
text, whichever is a better style.
That’s it (!)
Thoughts?
--
Simon Sapin