w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.52k stars 673 forks source link

[css-shapes-1] Degenerate polygons with positive shape-margin #2375

Closed bradwerth closed 2 months ago

bradwerth commented 6 years ago

https://www.w3.org/TR/css-shapes-1/#funcdef-polygon

At least three vertices are required to define a polygon with an area. This means that (for this specification) polygons with less than three vertices (or with three or more vertices arranged to enclose no area) result in an empty float area.

If a degenerate polygon has a positive shape-margin, it is practically useful for it to NOT have an empty float area. One example is an animated shape-outside: polygon with a shape-margin where the points in the polygon are momentarily made colinear. If the float area becomes empty in this case, the float area would collapse, only to abruptly reappear when the animation moved the points into an area-enclosing shape.

It would be useful for the spec to define the float area in the cases where the polygon is degenerate, but has a positive shape-margin. There are three cases:

  1. Only 1 point specified: the float area is a circle of radius shape-margin centered at the specified point.
  2. Only 2 points specified: the float area is a capsule of width shape-margin with endpoints of the two specified points.
  3. 3 or more co-linear points: the float area is a capsule of width shape-margin with endpoints of the ends of the line segment defined by the specified points.
astearns commented 6 years ago

It was useful in the implementation to quickly identify empty float areas. Do you have a use case for animating a shape where points are momentarily made colinear, and/or use cases for the 1- and 2-point polygon definitions above?

AmeliaBR commented 6 years ago

@astearns As far as identifying empty floats goes, it makes it easier, since you'd only need to check the shape's geometry if the shape-margin is 0.

I created a test case: https://codepen.io/AmeliaBR/pen/rJRyyw?editors=1100 Chrome and Safari both follow the current spec wording, and treat the straight-line polygon as an empty shape, so that the text runs completely over the first SVG. (Firefox and Edge do not support shape-outside at all; the second SVG is there to test basic support.)

I agree with @bradwerth that it would be preferable to apply the margin to a zero-area shape, in the same manner as stroking that shape with a stroke twice the margin's width.

In fact, for shape-outside, it would even make sense to still use a straight-line shape without the margin, because you're only filling in floated text on one side of the shape, anyway. (In my demo, that would mean text starting from the middle of the diagonal line.)

astearns commented 6 years ago

So the use case from @AmeliaBR is to be able to specify a shape that matches the inked area of an SVG path? That works for a simple line (non-dash, etc.) case, but not generally. For this case it might be better to get the inked area as an image and wrap around that.

There are also degenerate circles, ellipses and inset rects (which might need a larger value of shape-margin than merely >0 to amount to non-empty)

astearns commented 6 years ago

I'm open to considering the change if there's a good reason to take it. We did consider this case in the early stages of the spec, and went back and forth a few times over it (as I recall).

One case we considered is when a shape has a zero-width strut (for whatever reason). Say you have a triangle with a zero-width spur coming out from one of the vertices. If you animate shape-margin from zero to some positive value, then it's a bit surprising to have content jump away from what was previously an invisible part of the shape. It might be better to consider zero-width lines as contributing to the shape-outside wrapping effect to avoid that discontinuity.

AmeliaBR commented 6 years ago

So the use case from @AmeliaBR is to be able to specify a shape that matches the inked area of an SVG path?

I wasn't really thinking of that as a driving use case, more as a way to conceptualize & visualize it.

I agree that the main use case is to have a consistent transition from an almost-linear shape to an exactly linear shape, or for a shape being animated down/up from point (if you really wanted to animate it down to nothing, you could always animate down the margin to 0, to create a smooth transition).

It might be better to consider zero-width lines as contributing to the shape-outside wrapping effect to avoid that discontinuity.

Yes, I think so. The more I play around with it, the more it makes sense to use any edge as the boundary, regardless of fill area.

I added two more samples to my test case, both using a cross shape. In the first, the cross is exact, so that there is zero fill area and the float layout ignores the polygon. In the second, a few points have been shifted by a tenth of a pixel and the layout is completely different (although the difference in the SVG visualization of the same shape is imperceptible to my eyes). Both the vertical and the horizontal lines of the cross affect the layout.

tabatkins commented 6 years ago

Yeah, the presence of shape-margin definitely puts me on @AmeliaBR and @bradwerth's side. This is identical to the problem SVG has (had?) where a gradient won't render if the shape it's assigned to is zero-width/height, even if it's being used to paint the stroke which is larger and non-zero area.

astearns commented 6 years ago

I see two options, each with minor drawbacks (and I consider the current state of the spec to have a minor drawback as well, shown by this issue)

1) A shape-area is defined by the area enclosed by the combination of shape and shape-margin. If, after accounting for shape-margin there is no area enclosed, then the shape (or portion of the shape) does not contribute to the float area. This can result in discontinuities when shape-margin goes from zero to a positive value

  1. A shape-area is defined by all of the edges, whether they enclose an area or not. So in the codepen above, the diagonal line will cause content to wrap even if shape-margin is zero. The drawbacks here are implementation difficulty and the fact that some shapes use zero-width struts to join pieces that are meant to be separate. In this pen, this change would mean the text would move 50px to the right in-between the diamonds: https://codepen.io/astearns/pen/yvrYjd . That seems like a surprising result to me.
AmeliaBR commented 6 years ago

Interesting counter-example. I have used "zero-width struts" in clip-path shapes, but I would never have expected them to be transparent in a shape-outside situation. You could always construct that shape so that the connecting strut is on the far edge, but then it wouldn't be convertible for the opposite float direction. Either way, I think the better solution for that use case is to support path() notation, which doesn't need any hacks to create multiple sub-paths.

bradwerth commented 6 years ago
  1. A shape-area is defined by all of the edges, whether they enclose an area or not.

This seems most consistent and intuitive, considering that floats are in some sense checking for the edges of the shape, not checking for a nearby area associated with the shape. Adopting this into the spec would resolve things for circle and ellipse (with a 0 radius they would collapse to a point or a line). It would simplify the implementation for polygon though require some clarification about polygons with 1 or 2 vertices (do they define a point / line). The challenging one will be inset. If box is inset more than 100%, it either should be treated as a point or line (where?) or as an inside-out polygon. Neither option is particularly intuitive, but this all seems more intuitive than a shape disappearing when it has no positive area.

bradwerth commented 6 years ago

We're shipping support for shape-outside in Firefox 62. On this issue of shape areas versus edges, we are not compliant with the spec as written. We decided to ship our implementation enforcing these principles:

AmeliaBR commented 6 years ago

I think it's time to send this to the WG for a resolution.

Can any Chromium/WebKit devs comment on the points in @bradwerth's final comment, would it be difficult to change implementations to match?

AmeliaBR commented 6 years ago

Insets deflated beyond a point singularity are treated as rectangles in the same shape as the inverted rectangle.

I'm not too keen on this point. Could result in weird rounding errors where it is difficult to shrink the rectangle to exactly zero. I would rather limit the inset rectangle to a minimum of zero width/zero height, using rules similar to how border-radius values that add to greater than 100% are adjusted.

bradwerth commented 6 years ago

I'm not too keen on this point. Could result in weird rounding errors where it is difficult to shrink the rectangle to exactly zero. I would rather limit the inset rectangle to a minimum of zero width/zero height, using rules similar to how border-radius values that add to greater than 100% are adjusted.

I would support that as long as the spec defines the location of the point. Where should it be in a case like inset(100% 100% 50% 50%)? The two choices I would find most intuitive are either at the center of the original rect, or at the center of the inverted rect.

AmeliaBR commented 6 years ago

I would support that as long as the spec defines the location of the point.

My suggestion to use border-radius rules was specifically the calculation for scaling down all the measurements proportionally (I've updated the link above to go to the precise section):

For inset rectangles, the calculation would be simpler,* because you're not worried about maintaining the aspect ratio of a corner curve:

That means that if the insets on opposite sides are equal, the collapsed point/line would be in the exact middle of the reference box. If one inset is 9 times the opposite inset, the point would be 1/10th of the distance across.

Where should it be in a case like inset(100% 100% 50% 50%)?

It would be at the same point as inset(66.67% 66.67% 33.33% 33.33%), with all the insets being scaled down equally to fit in the 100% width/height available.


* Unless you wanted to also scale down the border-radius curves proportionally, in which case things could get complicated. But if I'm reading the function definition correctly, the border radiuses apply after the insetting happens (e.g., a border-radius of 10% would be 10% of the inset shape, not the original box), so it's probably best to keep that as a separate step, with its own adjustments.

css-meeting-bot commented 6 years ago

The Working Group just discussed Degenerate polygons with positive shape-margin, and agreed to the following:

The full IRC log of that discussion <dael> Topic: Degenerate polygons with positive shape-margin
<dael> github: https://github.com/w3c/csswg-drafts/issues/2375
<dael> TabAtkins: The current shape spec has test about degenerate polygons. It makes them not create a shape, text can pierce through.
<dael> TabAtkins: It doesn't have a special case for a positive shape margin where actual exclusion area is non-0. We should ammend the spec so things with positive shape margin are treated as having a postive area
<bradk> btw
<dael> TabAtkins: Issue goes further that the ruling about Degenerate polygons is wrong and we should remove. SVG has similar issue where if trying to stroke a path and size of box for stroking is determined by fill rectangle. Can be 0 area and triggers special cases where it isn't stroked and causes strange cases.
<dael> TabAtkins: Get similar issues where if animating polygon and if all points lin up you temp get entire layout to shift as it no longer excludes. Issue argues we remove special casing
<dael> TabAtkins: I looked at our code with iank_ and he supports it b/c it's removing special case
<dael> TabAtkins: Only complication is you can have negative margins and it can shrink spacing to 0. With degenrate rule you didn't have to worry about where 0 is. AmeliaBR suggestion sounds reasonable at first glance to solve. It's relatively corner case.
<dael> TabAtkins: Larger is should 0 area be an exlucsion
<dael> TabAtkins: Chrome supports the change
<dael> Rossen: Other prospective?
<myles> what is inverted space
<dael> astearns: I support what's discussed in issue. q: you can have polygons with inverted areas, not just a line but they're crossing and inverted area inside. If you have a positive shape margin in that case does it have to overcome the inverted space or do we define inverted space to uninvert
<myles> winding order?
<dael> TabAtkins: Good q, don't have best answer. Related to whe you over-deflate. Shoudl a really big neg margin cause positive shape. Should they
<tantek> there's a similar question / issue with negative outlines
<dael> fantasai: They don't. We won't change that.
<dael> TabAtkins: Why?
<dael> fantasai: Right now if you have a float with neg margin on both sides that won't turn into a positive shape
<dael> TabAtkins: It's a shape-maargin not a normal one.
<dael> fantasai: shape-margin is positive only?
<dael> TabAtkins: Nope
<dael> fantasai: I still think same principle should apply. Why can't you create a rectangle with the same behavior as a float.
<dael> TabAtkins: There's a lot of difference with shape and shape-margin so I don't know yet. I'm happy to figure it out as we go along. DOn't think we need to resolve to resolve this issue
<tantek> regarding what astearns said about inverted areas, here is another way we end up with inverted areas (and incompat) https://github.com/w3c/csswg-drafts/issues/2892
<dael> astearns: Clarification: It sounded like polygons with jsut lines and a positive shape margin will add their shape to exclusion area. Also polygons that are just lines will include line edge even w/o margins
<dael> TabAtkins: Correct
<dael> TabAtkins: There's a special case in our shape code and it skips by it currently. We just remove that check.
<dael> astearns: I propse we take what's in the issue, put in spec, and attempt to spec inverted areas and bring to group
<dael> iank_: One other thing, I filed an issue, but there will be bugs if we do just a paragraph. When we do this change I'd prefer we define how a line-box intrudes into a float with a shape outside and write out algo.
<dael> iank_: Otherwise someone will do something slightly different and we get compat bugs
<dael> iank_: It's issue [looking]
<dael> iank_: #2949
<dael> iank_: I looked at Moz code and they have same concept in their code
<dael> astearns: Thanks
<dael> Rossen: Going back to this issue
<dael> Rossen: Can we take the resolution proposed by TabAtkins?
<dael> astearns: I'm fine
<dael> TabAtkins: proposal: we take what's in the issue, put in spec, and attempt to spec inverted areas and bring to group
<dael> Rossen: Objections?
<dael> astearns: Agree with issue and bring it into the spec to match
<dael> Rossen: Accept the changes as stated in the issue. Objections?
<dael> RESOLVED: Accept the changes as stated in the issue
astearns commented 5 years ago

I'm starting to look at the changes needed for this. One point in the minutes about was about negative shape-margin, which isn't actually an issue. The property only takes positive values (and that's consistently implemented)

astearns commented 5 years ago

I've made an initial commit for these changes. I expect I'll need to do more

https://github.com/w3c/csswg-drafts/commit/d7d823919725b08df51560a47cc6382dff0adedc

I'm not sure about this - floated elements already are unaffected by the reduced float area. What else needs to be done?

Zero-area shapes will not affect floated elements that are block adjacent -- there is no minimum of 1 pixel area applied to the shape.

And so far I'm only handling degenerate insets using @AmeliaBR's suggestion above. I'm not entirely sure what to do with polygons and paths with winding rules that create edges enclosing no area or 'negative' area. Perhaps winding rules should only be relevant for clip-path and we should use all of the edges in shape-outside?

astearns commented 5 years ago

@bradwerth could you clarify what you mean by the block adjacent note above?

astearns commented 2 months ago

Closing this as I did not get any further responses. If there is anything more that needs to be done here please open a new issue