Skip to content

fix: skip degenerate element when a dash transition lands on a vertex - #613

Closed
Yuren0312 wants to merge 1 commit into
linebender:mainfrom
Yuren0312:fix-dash-transition-on-vertex-577
Closed

Yuren0312 wants to merge 1 commit into
linebender:mainfrom
Yuren0312:fix-dash-transition-on-vertex-577

Conversation

@Yuren0312

Copy link
Copy Markdown

Summary

Fixes #577: when a dash transition lands exactly on a path vertex, the dash iterator emits a zero-length LineTo at the corner instead of cleanly transitioning to the next dash.

Root cause

In DashIterator::step, a dash transition is taken whenever dash_remaining < seg_remaining, at t1 = seg.inv_arclen(dash_remaining, DASH_ACCURACY). When a dash ends exactly at the end of a segment, dash_remaining reaches exactly 0 as the segment is exhausted; the first step on the next segment then takes the transition branch with t1 == inv_arclen(0) == 0, so the dash's "final piece" is seg.subsegment(0.0..0.0) — a zero-length subsegment, emitted as a zero-length LineTo.

The same situation occurs at subpath starts: the dash phase is reset per subpath (reset_phase), and when it resets to exactly zero remaining (e.g. a dash_offset equal to the first dash length), the zero-length piece is emitted as a LineTo from the previous subpath's end to the new subpath's start — a visible line joining two disjoint subpaths. The third regression test covers this variant.

Fix

When the transition lands exactly at the current point (t1 == 0.0) while a dash is active, consume the transition without producing an element: flip to the gap, advance the dash cycle, and advance the state to Working at exactly the point where the old code did, so the stashing behavior (and therefore the non-stable dash order) is unchanged. The next step emits the following gap's MoveTo as usual.

For the issue's example (using line_tos, as the issue's expected output implies — a path consisting only of move_tos has no segments to dash, so those look like a typo):

before: MoveTo(0,0), LineTo(3,0), LineTo(3,0), MoveTo(3,1), LineTo(3,3)
after:  MoveTo(0,0), LineTo(3,0),           MoveTo(3,1), LineTo(3,3)

Tests

  • dash_transition_on_path_vertex — the issue's case, stable dash order.
  • dash_transition_on_path_vertex_unstable_order — same case, non-stable dash order (guards the stash interleaving, which is the subtle part of this change).
  • dash_transition_on_subpath_start — phase reset to exactly zero at a subpath boundary (guards against the subpath-joining line).

Verification

I was unable to execute cargo test in my local environment, so I validated the change by porting the iterator's logic 1:1 and differential-testing old vs. new behavior: the port reproduces every pre-existing dash test in this file exactly, and across 50,000 randomized paths × dash patterns × offsets (both stable and non-stable order) the new output is identical to the old except that the degenerate elements are gone — no other behavioral change. Locally cargo build -p kurbo --lib, cargo clippy -p kurbo --lib (no warnings) and rustfmt --check all pass. Please rely on CI for the actual test run.

When a dash ends exactly on a path vertex (dash_remaining reaches 0
exactly as the segment is exhausted), the next step of the dash iterator
sees dash_remaining == 0 < seg_remaining and inv_arclen(0) == 0, so the
dash's final piece is a zero-length subsegment. That piece was emitted
as a zero-length LineTo at the corner, or - when the vertex is a
subpath start, where the dash phase resets to exactly zero remaining -
as a LineTo joining the previous subpath to the new one.

Consume such a transition without producing an element and let the
next step emit the following gap's MoveTo. Advancing the state to
Working at the same point as before keeps the stashing behavior (and
therefore the non-stable dash order) unchanged.

Fixes linebender#577
@RobertBrewitz

Copy link
Copy Markdown
Member

Although the solution seemingly solve the issue at hand, deeper thought is required to come up with an appropriate solution for the ecosystem and surrounding standards, e.g. SVG 1.1.

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

Successfully merging this pull request may close these issues.

Dash iterator zero-length LineTo noise

2 participants