Skip to content

Commit f8e8fef

Browse files
committed
CanvasRenderingContext2D arc() and ellipse() start point handling fixed
1 parent 3fa8814 commit f8e8fef

3 files changed

Lines changed: 86 additions & 2 deletions

File tree

src/changes/changes.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
<body>
1010
<release version="5.3.0" date="July xx, 2026" description="Bugfixes">
11+
<action type="fix" dev="rbri">
12+
CanvasRenderingContext2D arc() and ellipse() start point handling fixed.
13+
</action>
1114
<action type="update" dev="rbri">
1215
Improved image comparison for our test cases; this shows a bunch of problems that are hard to identify.
1316
</action>

src/main/java/org/htmlunit/platform/canvas/rendering/AwtRenderingBackend.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,20 @@ public void ellipse(final double x, final double y,
348348
transformation.rotate(rotation, p.getX(), p.getY());
349349
final Arc2D arc = new Arc2D.Double(p.getX() - radiusX, p.getY() - radiusY, radiusX * 2, radiusY * 2,
350350
startAngleDegree, extendAngle * -1, Arc2D.OPEN);
351-
getCurrentSubPath(startX, startY).append(transformation.createTransformedShape(arc), true);
351+
352+
// connect=true only if there is already a current point (implicit lineTo behaviour);
353+
// connect=false when the subpath is new so we don't get a spurious line from (0,0)
354+
// or from the moveTo seed point to the arc's own geometric start.
355+
final boolean hasCurrentPoint;
356+
if (subPaths_.isEmpty()) {
357+
final Path2D subPath = new Path2D.Double();
358+
subPaths_.add(subPath);
359+
hasCurrentPoint = false;
360+
}
361+
else {
362+
hasCurrentPoint = subPaths_.get(subPaths_.size() - 1).getCurrentPoint() != null;
363+
}
364+
getCurrentSubPath().append(transformation.createTransformedShape(arc), hasCurrentPoint);
352365
}
353366

354367
/**
@@ -392,7 +405,20 @@ public void arc(final double x, final double y, final double radius, final doubl
392405
}
393406
final Arc2D arc = new Arc2D.Double(p.getX() - radius, p.getY() - radius, radius * 2, radius * 2,
394407
startAngleDegree, extendAngle * -1, Arc2D.OPEN);
395-
getCurrentSubPath(startX, startY).append(arc, true);
408+
409+
// connect=true only if there is already a current point (implicit lineTo behaviour);
410+
// connect=false when the subpath is new so we don't get a spurious line from (0,0)
411+
// or from the moveTo seed point to the arc's own geometric start.
412+
final boolean hasCurrentPoint;
413+
if (subPaths_.isEmpty()) {
414+
final Path2D subPath = new Path2D.Double();
415+
subPaths_.add(subPath);
416+
hasCurrentPoint = false;
417+
}
418+
else {
419+
hasCurrentPoint = subPaths_.get(subPaths_.size() - 1).getCurrentPoint() != null;
420+
}
421+
getCurrentSubPath().append(arc, hasCurrentPoint);
396422
}
397423

398424
/**

src/test/java/org/htmlunit/javascript/host/canvas/CanvasRenderingContext2DTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,40 @@ public void moveToBezierCurveToStroke() throws Exception {
13261326
"context.moveTo(2, 2); context.bezierCurveTo(4, 34, 2, 8, 38, 36); context.stroke();\n");
13271327
}
13281328

1329+
/**
1330+
* @throws Exception if the test fails
1331+
*/
1332+
@Test
1333+
@Alerts(DEFAULT = "data:image/png;base64,"
1334+
+ "iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAABxUlEQVR4AeyVPShFYRjHT0oZZUGKfJVkkJSPDFJkEBkQUjLI"
1335+
+ "hEGZyOhjko9FRNmwKJHvJCwGyWAwYDEYLMx+z8kdvO4517nvueee4b39f+fp3vs+7/u///ue96RYIX8Zg7p/kEnQJKibgG6/"
1336+
+ "2YMmQd0EdPvNHjQJ6iag22/2oElQNwHdfrMHTYI/CZRQe2EMxmEIWqAYEqpYe7Ca1Q/hBFohCzKgAobhAp5hEwYhD3yVm8F2"
1337+
+ "VrqGLciBLogkKGaaeJ8NdXAE9fAIO9AGvsjJYC2zb4OYWKG66dWyrA0G9EAa7MEEXEE3aMnJ4BKzDoAkQ/GkNUZXwjTINjij"
1338+
+ "NkNcimZQJn1iNkmFErd26ayBVViGdSgET4pmUJJb9DSL+2C5gQoY8gJ3MAL/lmowl85MOAe/NcmEVSDH0wG1HGJKNVhKh9yF"
1339+
+ "lITogVkbYR9uYRRcpRqUX5jq2uHPl/NMI2HI2SqBOJ6fqsF0Gt8gCMmZ2cBC93ADHfBHqsEPRrxDkJpisT6YhRn4JdXgHN8u"
1340+
+ "QNA6ZsEyyAd5tBZRbakGv+xPk3P5ZNlOOIVLsKUatD9M8kWeQP0RD2E0KN7knJRqhdWgbU4uoTf4DQAA//9d0N0DAAAABklE"
1341+
+ "QVQDAD1/O1HLUOolAAAAAElFTkSuQmCC",
1342+
FF = "data:image/png;base64,"
1343+
+ "iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAABgklEQVRYR+3Vv0tWURzH8ZdEGvUXBE4W0RARCK1B4OYUtAQt"
1344+
+ "BYUS2BCUJgj9GC1oC2lqqggagobGDKJ/oCVag9RawqFJDhy+2FGfHjs+dYfzHj/nXs77fs6PO6TjDHXcrwlW05a4ltZgLa3B"
1345+
+ "WlqDtbQGa2kN1tIarKU1WEtrsJZ+GxzGaRzHfvzCV3zA93hqAPQjuIiz+Ik3GM3CSXQSq3iZx97FW3tEL8GRPOlrPIh0K8dw"
1346+
+ "CtdwGE+whLV4ooKdBA/gI05G0h9HcRkn8Bm3sR6jf8FOgsu4ifeR7J7ruI+HmI90l2wneA5nMBNJHanFu5jC40j7ZDvBZ3hU"
1347+
+ "2V5JmmchH6qL+BQjf6AUPIgVHIpkbxnHU7zCXKQ9KAXT0l7A1UgGwyym81w9r6ZS8BKO5H0zaNJ9egffcCvSglIwfdk+3Itk"
1348+
+ "8KTb4jwm8KOcrhS8kX9j6ZD8S9LefJu31ovNE5eC/5vnucU4A10TTFzJP4l0FjopmBjDly4LBl1c4t/ovOAGUngyKRdIPigA"
1349+
+ "AAAQZGVCR0ZGNEIxQjE4N0Q5NUY1Qjb0UOldAAAAAElFTkSuQmCC",
1350+
FF_ESR = "data:image/png;base64,"
1351+
+ "iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAABnElEQVRYR+2Yu0oDQRSGE8QL+gSClYqkEBEEW0GwsxJsBBsF"
1352+
+ "gyJoIXgFwUupgp2IlZWKYBGwsFRBfAEbsRW8NmJh5XdgCMmQXWZzkmWKGfhIsefy558zs7DZjOcr67m+TBCo3aHgYHBQ64A2"
1353+
+ "P8xgcFDrgDY/zGBwUOuANj/MYHBQ64A2P8xgWg420WgQctAIf/AKD/ClFRGX77LFexQYhh+4hg4QwSJ0FD7g0jy7rbXYOIHN"
1354+
+ "pmmB3/2Yxj0864d5aIcTOIbPWoiNEthC8UfoS9ikm/hp6IVnWIffhDXKwqME3hG1DPeK4ovk7sIBbFRbp5LAMYoNwUK1Ra08"
1355+
+ "cXEbZuEoac1KAs8ocqh0z9YhfTZBDtUkPLkKtQW2kvgOba4FEsYNEH8KV7DmkmsLlK2dgLxLsiJmldw50yv2arIFTpHUBTI3"
1356+
+ "9V5yn27BG6xENbMFyj9rgJ16qyupL7fFOIzAt93XFrhEgLzG5JCkuWQ2b8xoXZQ2dnnVpSn03LhYPAO+CRQzZkC2Xc6Ct19Y"
1357+
+ "O9H24rPA4lj5uMVlM++9wH9SeDIp/dhlMwAAAABJRU5ErkJggg==")
1358+
public void bezierCurveToWithoutMoveToUsesControlPoint1AsStart() throws Exception {
1359+
draw("<canvas id='myCanvas' width='40' height='40'></canvas>\n",
1360+
"context.beginPath(); context.bezierCurveTo(4, 34, 2, 8, 38, 36); context.stroke();\n");
1361+
}
1362+
13291363
/**
13301364
* @throws Exception if the test fails
13311365
*/
@@ -1351,6 +1385,27 @@ public void moveToQuadraticCurveToStroke() throws Exception {
13511385
"context.moveTo(2, 2); context.quadraticCurveTo(19, 4, 19, 17); context.stroke();\n");
13521386
}
13531387

1388+
/**
1389+
* @throws Exception if the test fails
1390+
*/
1391+
@Test
1392+
@Alerts(DEFAULT = "data:image/png;base64,"
1393+
+ "iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAh0lEQVR4AezWQQqAMBBDUfHkHt10l003yQhCv0xxEFKHZxfe"
1394+
+ "188vBmw/0FGCj7TW0m2ujhKcY7OdEDSMqEUwYrMQgoYRtQhGbBZC0DCidlJw/SisFQ2yC00OuHtH9ZwBKz6FERRCVQhWfAoP"
1395+
+ "CWqnj4oBW1gEEWwF2jxnEMFWoM1zBhFsBdr8CwAA//8Lay2uAAAABklEQVQDAKHoAlHKHpf2AAAAAElFTkSuQmCC",
1396+
FF = "data:image/png;base64,"
1397+
+ "iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAd0lEQVRYR+3SMQqAMBBE0YknFrHXWsQz24S9wJ9AIPPKLWT9"
1398+
+ "2abJtcn3y4KY84lPSZukpyYGzgWv/r27JgZLLXj0J37r9w2WKpgbpPZ+g19NDHKDVApSSxUcwllwiCxIpSCVglQKUilIpSCV"
1399+
+ "glQKUilIpSA1fcEfX7kNKdMLfVYAAAAQZGVCRzY3NDMxMzk2NDAxRTJCQjmxPy2iAAAAAElFTkSuQmCC",
1400+
FF_ESR = "data:image/png;base64,"
1401+
+ "iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAiUlEQVRYR+3VMQqAMBBE0eiJRey1FvHMRkhhYbU/gaBfsJww"
1402+
+ "vF2SIXX+DZ33SxakE6opuOQyY/53WuqZr1lwzQff520WDArMZcRHMP8ac8RU81eCU9nBk6p5zSgYFPAlCcK1jdW8B5s0tSBl"
1403+
+ "VVBBKkDz7qCCVIDm3UEFqQDNu4MKUgGadwc/L3gBX7kNKeTInzcAAAAASUVORK5CYII=")
1404+
public void quadraticCurveToWithoutMoveToUsesControlPointAsStart() throws Exception {
1405+
draw("<canvas id='myCanvas' width='40' height='40'></canvas>\n",
1406+
"context.beginPath(); context.quadraticCurveTo(19, 4, 19, 17); context.stroke();\n");
1407+
}
1408+
13541409
/**
13551410
* @throws Exception if the test fails
13561411
*/

0 commit comments

Comments
 (0)