Skip to content

Line.getSelfRect() wrong for multi-segment bezier (and degenerate symmetric segments) #2060

Description

@Xuepoo

Line.getSelfRect() (and therefore getClientRect(), Transformer selection bounds, and cache bounds) is wrong for a bezier Line with more than one cubic segment — it can collapse to a degenerate zero-height/zero-width box while the actual drawn curve is much larger.

Reproduction

const line = new Konva.Line({
  bezier: true,
  // 2 + 6*2 = 14 points: a starting point + two cubic segments.
  points: [0, 50, 0, 150, 100, 150, 100, 50, 100, 50, 200, 150, 300, 50],
  stroke: 'red',
});
layer.add(line);
layer.draw();

console.log(line.getSelfRect());
// {x: 0, y: 50, width: 300, height: 0} -- height 0!

The curve itself renders correctly (dips down to about y=125 in this example); getSelfRect() just doesn't know about it.

Root cause

function getBezierExtremaPoints(points) {
  const axisPoints = [
    [points[0], points[2], points[4], points[6]],
    [points[1], points[3], points[5], points[7]],
  ];
  ...
}

This only ever reads points[0..7] — the first cubic segment — no matter how many segments the line actually has. _sceneFunc draws every points[n..n+7] group of 6 in a loop (context.bezierCurveTo(...) while n < length), so any segment after the first is invisible to the bounds calculation.

A second bug in the same function

While fixing the above I found the extrema-finding math also silently drops an axis when the cubic's derivative degenerates from quadratic to linear (a === 0, the discriminant branch's a coefficient) — which happens for a segment with symmetric control points, a fairly common shape. The if (a !== 0) guard skips that axis's extremum entirely instead of solving the resulting linear equation b*t + c = 0. This affects the original single-segment code too, not just multi-segment lines — you can reproduce it with a single symmetric segment: points: [0, 50, 0, 150, 100, 150, 100, 50].

I have a fix (rewrites getBezierExtremaPoints to iterate every segment and handles the degenerate case) + a regression test, verified against the real rendered pixels (not just the formula), and will open a PR against this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions