Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is "M0,0L100,0ZL100,50L200,200ZL,0,200Z" 1 subpath or 3? #866

Closed
tatarize opened this issue Sep 19, 2021 · 8 comments
Closed

Is "M0,0L100,0ZL100,50L200,200ZL,0,200Z" 1 subpath or 3? #866

tatarize opened this issue Sep 19, 2021 · 8 comments

Comments

@tatarize
Copy link

9.3.3. The "moveto" commands
The "moveto" commands (M or m) must establish a new initial point and a new current point. The effect is as if the "pen" were lifted and moved to a new location. A path data segment (if there is one) must begin with a "moveto" command. Subsequent "moveto" commands (i.e., when the "moveto" is not the first command) represent the start of a new subpath

Here it is clearly laid out that move to establishes the new initial point and starts a subpath. This appears to be rather unambiguous.

9.3.4. The "closepath" command
The "closepath" (Z or z) ends the current subpath by connecting it back to its initial point. ...
If a "closepath" is followed immediately by a "moveto", then the "moveto" identifies the start point of the next subpath. If a "closepath" is followed immediately by any other command, then the next subpath starts at the same initial point as the current subpath.

Then 9.3.4 says closepath if followed by a moveto then the moveto identifies the start of the next subpath. But, this would have already been established by the definition of moveto that it always starts a subpath. This seems to open the question of what happens when a closepath is followed by something other than a moveto.

If a "closepath" is followed immediately by a "moveto", then the "moveto" identifies the start point of the next subpath. If a "closepath" is followed immediately by any other command, then the next subpath must start at the same initial point as the current subpath.

Now, this explanation of what closepath followed by a non-moveto command is increasingly confusing. The next subpath? So did the Z start that subpath or is it still in the subpath that started with the initial moveto. Does a subsequent closepath point to the previous closepath's destination or the previous moveto's destination.

Keep in mind the first closepath destination is the same as the previous moveto destination. The implementations here do not make any functional difference for Chrome. Markers, stroke-linejoin, and fills do not reveal functional differences. The utility of the moveto with compound paths is draw donut-like shapes but without moving a self-intersecting shape is drawn identically.

If z does not start a new path and were seen as merely a lineto initial point when followed by non-move commands, then the language in 9.3.4 would seem to be a problem since the next subpath could start anywhere your next moveto says it starts, and would not start "at the same initial point as the current subpath". Also if closepath doesn't actually close the path then some other language is problematic.

"A closed subpath must be closed with a "closepath" command, this "joins" the first and last path segments. Any other path is an open subpath." -- Would imply you could close the path then reopen it with a different lineto, unless the first Z marks the end of that subpath.

@BigBadaboom
Copy link
Contributor

BigBadaboom commented Sep 20, 2021

It's three.

  1. M0,0 L100,0 Z
  2. (M0,0) L100,50 L200,200 Z
  3. (M0,0) L,0,200 Z

So did the Z start that subpath or is it still in the subpath that started with the initial moveto.

Z just closes a sub-path. It doesn't start a new one.

Does a subsequent closepath point to the previous closepath's destination or the previous moveto's destination.

If the subsequent subpath had no M, then both of those locations would be the same.

If a "closepath" is followed immediately by any other command, then the next subpath must start at the same initial point as the current subpath.

Is your confusion around the use of "current" in the above sentence? Remember the Z does not start a new path. So "current" refers to the first sub-path still.

Update

Reading your linked bug, it might be this line that is cousing confusion.

| A path data segment (if there is one) must begin with a "moveto" command.

This could be better worded. You should read this as "if the path data has any segments, then it must start with a "moveto" command."

@tatarize
Copy link
Author

tatarize commented Sep 20, 2021

Z closes a sub-path but then any new segment appears to start a new subpath. The first full path must start with M but later subpaths do not have the same requirement.

Functionally I can find nothing to determine any difference here. Markers, stroke-linejoin, and fills all work identically as simple and compound paths. The only implementation of this is within Inkscape which interprets closepath as ending a path and any segment after that close, when the paths are broken apart.

Having read it several times I must conclude that it is 3 subpaths. Z does not start the new path but it does terminate the old one. M0,0ZZZZZZZZZZ is 10 different highly unusual subpaths most of them consisting of just a close statement.

Part of the confusion is the use of "next subpath" and "current subpath" when we're talking about closepath followed by a non-moveto command. We are not just talking about a Z but a Z followed immediately by any other command. The additional Z didn't start a new path, but the additional command after did start a new subpath.

"If a "closepath" is followed immediately by a "moveto", then the "moveto" identifies the start point of the next subpath. If a "closepath" is followed immediately by any other command, then the next subpath starts at the same initial point as the current subpath."

If a "closepath" is followed by any command it begins a new subpath. If it begins with a move, this new subpath begins at the point specified by the moveto. If it begins with any other command, the new subpath began at the same initial point as the previous subpath. The first subpath (if there is one) must begin with a moveto.

There's enough details to piece this together but it took a bit of reading between the lines.

@verdy-p
Copy link

verdy-p commented Feb 9, 2022

"M0,0ZZZZZZZZZZ" just creates a single subpath "M0,0Z", the extra Z's dont start any new subpath, they terminate an existing open path... if there's one. But it does not drops the initial point of the last processed subpath (so the last "M/m" position is preserved as the last "current point", not really the last "initial position" for the last subpath).

A new subpath however is created by "M/m" or other non-"z/Z" commands: only these commands will see if there's an open path with a "current point": even if the path was closed, there's still the last value of the "current point" resulting from the last processed path.

If you want to have multiple subpaths (because you want to use that path to derive other paths for superposing markers, possibly half-transparent), don't write "M1,2ZZZ" which just generates 1 subpath, but separate each Z by a relative moveto(0,0) or zero-length lineto, for example "M1,2Zh0Zh0Z" which generates 3 subpaths.

@verdy-p
Copy link

verdy-p commented Feb 9, 2022

An in fact, even the leading "M" command could be (or should be?) optional:

all paths should behave as if there was an implicit "M0,0" prepended to it, and then "L1,2" would still be a valid path, equivalent to "M0,0L1,2" (both generating a single subpath).

Butr may be a path like "l1,2" whose first command is relative should behave as if there was a prepended relative moveto, i.e. like "m0,0l1,2": the result would be a single relative path, that would still need to be positioned.

As well all paths should have an implicit leading horizontal positive direction, as if in fact there was a "M-1,0M0.0", each moveto (absolute or relative sets a new direction if this effectively moves the current point to a different direction, otherwise the bearing direction would be undefined, caiusing path commands depending on the bearing to be non-significant and not rendered, and possibly raise some internal exception with some "debugging flags" enabled in the engine)

@tatarize
Copy link
Author

tatarize commented Feb 9, 2022

"M0,0ZZZZZZZZZZ" just creates a single subpath "M0,0Z", --- Such is lossy and really should be 10 paths. We could pretty easily divide them up and without making the Z into their own paths or ignoring them in that fashion they would get lost from wrongly simplifying the structure. It also matters for somethings like the markers where we'd have far more end point markers there. But, not if we said it was an identical to a differentially expressed path.

@BigBadaboom
Copy link
Contributor

BigBadaboom commented Feb 10, 2022

Philippe Verdy wrote:

"M0,0ZZZZZZZZZZ" just creates a single subpath "M0,0Z",

This is not accurate. There is nothing in the spec that corroborates that statement.

The spec states:

If a "closepath" is followed immediately by a "moveto", then the "moveto" identifies the start point of the next subpath. If a "closepath" is followed immediately by any other command, then the next subpath must start at the same initial point as the current subpath.

It doesn't distinguish close-path commands from other commands. So each extra Z should create a new sub-path and immediately close it.

As proof, try the following test case in Firefox: https://jsfiddle.net/msqyx1k5/

<svg viewBox="-50 -50 100 100" width="400">
  <defs>
    <marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5" 
            markerUnits="strokeWidth" markerWidth="4" markerHeight="3"
            orient="auto">
      <path d="M 0 0 L 10 5 L 0 10 z" fill="rgba(0,0,0, 0.2)"/>
    </marker>
  </defs>
  
  <path d="M-40,0Z" marker-end="url(#Triangle)" stroke="red" stroke-width="4"/>
  <path d="M40,0ZZZZZ" marker-mid="url(#Triangle)" stroke="red" stroke-width="4"/>
</svg>

You can see that the 20%-black mid markers stack on top of each other to make a darker triangle.
So Firefox at least creates multiple sub-paths.

Note: Chrome renders this test case incorrectly. Markers are drawn in incorrect places. But it does draw multiple markers indicating that it also thinks there should be multiple sub-paths. I'm going to file a Chrome bug for the redering issue.

@tatarize
Copy link
Author

In Chrome adding z to marker mid adds in darkness to the marker, adding it to the marker-end causes the marker to disappear and to start it makes no change change in the overall marker.

At a minimum this implies that Chrome is absolutely not ignoring additional z commands like they are meaningless.

@BigBadaboom
Copy link
Contributor

Just FFR, Chrome bug here: https://bugs.chromium.org/p/chromium/issues/detail?id=1295995

@tatarize tatarize closed this as completed Dec 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants