Skip to content

fix(painter): implement SVG export for bezier curves#5568

Open
kartikktripathi wants to merge 2 commits intosugarlabs:masterfrom
kartikktripathi:savefix
Open

fix(painter): implement SVG export for bezier curves#5568
kartikktripathi wants to merge 2 commits intosugarlabs:masterfrom
kartikktripathi:savefix

Conversation

@kartikktripathi
Copy link
Contributor

Summary

Painter.doBezier() rendered curves correctly on the canvas but did not generate SVG output, leaving exported artwork incomplete and inconsistent with on-screen drawings. This patch implements SVG path generation for Bézier curves and extends hollow stroke handling to match the behaviour already used by line and arc rendering. The change ensures that exported SVG faithfully reproduces canvas output.


Changes

  1. Add cubic Bézier SVG path output
    Generate SVG C commands using scaled turtle coordinates:
this._svgOutput +=
  "C " +
  cx1 * turtlesScale + "," + cy1 * turtlesScale + " " +
  cx2 * turtlesScale + "," + cy2 * turtlesScale + " " +
  fx * turtlesScale + "," + fy * turtlesScale + " ";
  1. Implement hollow stroke (thick line) geometry
    Replicates the same outline logic used in _move() and _arc():
  • forward offset bezier
  • end cap arc
  • reverse offset bezier
  • start cap arc
  • close path
    This preserves stroke thickness in SVG instead of exporting a centerline.
  1. Correct path lifecycle
  • Open path only once (_svgPath)
  • Close paths via closeSVG()
  • Preserve pen up/down separation
  • Avoid redundant M commands
  1. Remove obsolete FIXME: removed.
// FIXME: Add SVG output
  1. Prettier changes for format check.

Testing

  1. Rendering parity
    Verified that canvas output matches exported SVG:
  • Single Bézier curve
  • Connected curves (continuous path)
  • Pen up/down separation (independent paths)
  • Thick strokes export as outline geometry
  • High curvature maintains correct caps
  • Rotational symmetry preserved
npm run test

Thanks!

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

✅ All Jest tests passed! This PR is ready to merge.

@kartikktripathi
Copy link
Contributor Author

Hey @walterbender, I’ve opened a small fix that implements the missing SVG export in Painter.doBezier() (note the FIXME). It keeps behaviour unchanged and only makes exported SVG match the canvas output. Would appreciate a quick look whenever you have time.
Thanks!

@walterbender
Copy link
Member

Hmm. Something is not quite right.

Screenshot From 2026-02-06 13-59-00

and

Screenshot From 2026-02-06 13-59-12

^^ inkscape rendering of the saved output.

<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" width="1536" height="843"><g transform="scale(1,1)"><path d="M 768,421.5 C 968,346.5 868,321.5 768,321.5 " style="stroke-linecap:round;fill:none;stroke:rgb(255,0,249);stroke-opacity:1;stroke-width:5pt;" /></g></svg>

@kartikktripathi
Copy link
Contributor Author

Thanks, I see the issue. The SVG curve is being generated using turtle-space angles, while the canvas Bézier uses screen-space coordinates, which causes a geometry mismatch in vector renderers like Inkscape. I’ll adjust the control-point/heading calculations to use consistent coordinate space so the exported SVG matches the canvas exactly.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

✅ All Jest tests passed! This PR is ready to merge.

@kartikktripathi
Copy link
Contributor Author

The previous SVG continued the Bézier from the prior path segment, which changed the starting tangent.
The export now explicitly begins a new path at the turtle’s current position before emitting the cubic command, so the SVG represents the intended cubic defined by the control points.
The small remaining visual difference is due to SVG showing the exact vector curvature, while the canvas rasterisation smooths it slightly. Please let me know if any changes are still required. Thanks!

@walterbender
Copy link
Member

Screenshot From 2026-02-06 15-48-27 Screenshot From 2026-02-06 15-48-02

@walterbender
Copy link
Member

The exported SVG seemed to assume an X,Y starting position of 0,0 in both cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants