Skip to content

Commit 3144f8d

Browse files
committed
CanvasRenderingContext2D lineJoin property support added
1 parent d1b755d commit 3144f8d

6 files changed

Lines changed: 226 additions & 5 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="Chrome/Edge 150, Bugfixes">
11+
<action type="add" dev="rbri">
12+
CanvasRenderingContext2D lineJoin property support added.
13+
</action>
1114
<action type="add" dev="rbri" issue="#1149">
1215
Checkstyle rule set updated: JavadocStyle -> SummaryJavadoc.
1316
</action>

src/main/java/org/htmlunit/javascript/host/canvas/CanvasRenderingContext2D.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.htmlunit.javascript.host.html.HTMLImageElement;
3636
import org.htmlunit.platform.Platform;
3737
import org.htmlunit.platform.canvas.rendering.RenderingBackend;
38+
import org.htmlunit.platform.canvas.rendering.RenderingBackend.LineJoin;
3839
import org.htmlunit.platform.canvas.rendering.RenderingBackend.WindingRule;
3940
import org.htmlunit.protocol.data.DataURLConnection;
4041
import org.htmlunit.util.MimeType;
@@ -814,4 +815,44 @@ public void translate(final double x, final double y) {
814815
public HTMLCanvasElement getCanvas() {
815816
return canvas_;
816817
}
818+
819+
/**
820+
* Returns the the shape used to join two line segments where they meet.
821+
* There are three possible values for this property: "round", "bevel",
822+
* and "miter". The default is "miter".
823+
*
824+
* @return the the shape used to join two line segments
825+
*/
826+
@JsxGetter
827+
public String getLineJoin() {
828+
switch (getRenderingBackend().getLineJoin()) {
829+
case ROUND:
830+
return "round";
831+
case BEVEL:
832+
return "bevel";
833+
default:
834+
return "miter";
835+
}
836+
}
837+
838+
/**
839+
* Sets the {@code lineJoin} property.
840+
* @param lineJoin the {@code lineJoin} property value
841+
*/
842+
@JsxSetter
843+
public void setLineJoin(final String lineJoin) {
844+
switch (lineJoin) {
845+
case "round":
846+
getRenderingBackend().setLineJoin(LineJoin.ROUND);
847+
break;
848+
case "bevel":
849+
getRenderingBackend().setLineJoin(LineJoin.BEVEL);
850+
break;
851+
case "miter":
852+
getRenderingBackend().setLineJoin(LineJoin.MITER);
853+
break;
854+
default:
855+
// ignore invalid values per spec
856+
}
857+
}
817858
}

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

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public class AwtRenderingBackend implements RenderingBackend {
7272
private Color strokeColor_;
7373

7474
private int lineCap_;
75-
private int lineJoin_;
75+
private LineJoin lineJoin_;
7676
private float miterLimit_;
7777

7878
private final List<Path2D> subPaths_;
@@ -280,7 +280,7 @@ public AwtRenderingBackend(final int imageWidth, final int imageHeight) {
280280
graphics2D_.clearRect(0, 0, imageWidth, imageHeight);
281281

282282
lineCap_ = BasicStroke.CAP_BUTT;
283-
lineJoin_ = BasicStroke.JOIN_MITER;
283+
lineJoin_ = LineJoin.MITER;
284284
miterLimit_ = 10.0f;
285285

286286
subPaths_ = new ArrayList<>();
@@ -946,7 +946,7 @@ public void stroke() {
946946
LOG.debug("[" + id_ + "] stroke()");
947947
}
948948

949-
graphics2D_.setStroke(new BasicStroke(getLineWidth(), lineCap_, lineJoin_, miterLimit_));
949+
graphics2D_.setStroke(new BasicStroke(getLineWidth(), lineCap_, toAwtLineJoin(lineJoin_), miterLimit_));
950950
graphics2D_.setColor(strokeColor_);
951951
for (final Path2D path2d : subPaths_) {
952952
if (hasSegments(path2d)) {
@@ -964,7 +964,7 @@ public void strokeRect(final double x, final double y, final double w, final dou
964964
LOG.debug("[" + id_ + "] strokeRect(" + x + ", " + y + ", " + w + ", " + h + ")");
965965
}
966966

967-
graphics2D_.setStroke(new BasicStroke(getLineWidth(), lineCap_, lineJoin_, miterLimit_));
967+
graphics2D_.setStroke(new BasicStroke(getLineWidth(), lineCap_, toAwtLineJoin(lineJoin_), miterLimit_));
968968
graphics2D_.setColor(strokeColor_);
969969
graphics2D_.draw(rectPath(x, y, w, h));
970970
}
@@ -1066,6 +1066,28 @@ public void closePath() {
10661066
subPaths_.add(nextPath);
10671067
}
10681068

1069+
/**
1070+
* {@inheritDoc}
1071+
*/
1072+
@Override
1073+
public LineJoin getLineJoin() {
1074+
if (LOG.isDebugEnabled()) {
1075+
LOG.debug("[" + id_ + "] getLineJoin()");
1076+
}
1077+
return lineJoin_;
1078+
}
1079+
1080+
/**
1081+
* {@inheritDoc}
1082+
*/
1083+
@Override
1084+
public void setLineJoin(final LineJoin lineJoin) {
1085+
if (LOG.isDebugEnabled()) {
1086+
LOG.debug("[" + id_ + "] setLineJoin(" + lineJoin + ")");
1087+
}
1088+
lineJoin_ = lineJoin;
1089+
}
1090+
10691091
private Path2D getCurrentSubPath() {
10701092
if (subPaths_.isEmpty()) {
10711093
final Path2D subPath = new Path2D.Double();
@@ -1126,6 +1148,14 @@ private static Color toAwtColor(final org.htmlunit.html.impl.Color color) {
11261148
return new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
11271149
}
11281150

1151+
private static int toAwtLineJoin(final LineJoin lineJoin) {
1152+
return switch (lineJoin) {
1153+
case MITER -> BasicStroke.JOIN_MITER;
1154+
case ROUND -> BasicStroke.JOIN_ROUND;
1155+
case BEVEL -> BasicStroke.JOIN_BEVEL;
1156+
};
1157+
}
1158+
11291159
private static final class SaveState {
11301160
private final AffineTransform transformation_;
11311161
private final float globalAlpha_;
@@ -1135,7 +1165,7 @@ private static final class SaveState {
11351165
private final Shape clip_;
11361166

11371167
private final int lineCap_;
1138-
private final int lineJoin_;
1168+
private final LineJoin lineJoin_;
11391169
private final float miterLimit_;
11401170

11411171
private final Font font_;

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,4 +376,25 @@ public void closePath() {
376376
LOG.debug("[" + id_ + "] closePath()");
377377
}
378378
}
379+
380+
/**
381+
* {@inheritDoc}
382+
*/
383+
@Override
384+
public LineJoin getLineJoin() {
385+
if (LOG.isDebugEnabled()) {
386+
LOG.debug("[" + id_ + "] getLineJoin()");
387+
}
388+
return LineJoin.MITER;
389+
}
390+
391+
/**
392+
* {@inheritDoc}
393+
*/
394+
@Override
395+
public void setLineJoin(final LineJoin lineJoin) {
396+
if (LOG.isDebugEnabled()) {
397+
LOG.debug("[" + id_ + "] setLineJoin(" + lineJoin + ")");
398+
}
399+
}
379400
}

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ enum WindingRule {
3636
EVEN_ODD
3737
}
3838

39+
/**
40+
* LineJoin to be used while rendering.
41+
*/
42+
enum LineJoin {
43+
/** LineJoin.MITER. */
44+
MITER,
45+
/** LineJoin.ROUND. */
46+
ROUND,
47+
/** LineJoin.BEVEL. */
48+
BEVEL
49+
}
50+
3951
/**
4052
* Starts a new path by emptying the list of sub-paths.
4153
*/
@@ -322,6 +334,9 @@ void putImageData(byte[] imageDataBytes, int imageDataWidth, int imageDataHeight
322334
void closePath();
323335

324336
/**
337+
* Returns the alpha (transparency) value that is applied to shapes and images
338+
* before they are drawn onto the canvas.
339+
*
325340
* @return the alpha (transparency) value that is applied to shapes and images
326341
* before they are drawn onto the canvas.
327342
*/
@@ -333,4 +348,19 @@ void putImageData(byte[] imageDataBytes, int imageDataWidth, int imageDataHeight
333348
* @param globalAlpha the new alpha
334349
*/
335350
void setGlobalAlpha(double globalAlpha);
351+
352+
/**
353+
* Returns the the shape used to join two line segments where they meet.
354+
* There are three possible values for this property: "round", "bevel",
355+
* and "miter". The default is "miter".
356+
*
357+
* @return the the shape used to join two line segments
358+
*/
359+
LineJoin getLineJoin();
360+
361+
/**
362+
* Sets the {@code lineJoin} property.
363+
* @param lineJoin the {@code lineJoin} property value
364+
*/
365+
void setLineJoin(LineJoin lineJoin);
336366
}

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

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2827,6 +2827,102 @@ public void getImageDataPixelOrder() throws Exception {
28272827
+ "log(s);\n");
28282828
}
28292829

2830+
/**
2831+
* @throws Exception if the test fails
2832+
*/
2833+
@Test
2834+
@Alerts("miter")
2835+
public void lineJoinDefault() throws Exception {
2836+
verify("<canvas id='myCanvas' width='2' height='2'></canvas>\n",
2837+
"log(context.lineJoin);");
2838+
}
2839+
2840+
/**
2841+
* @throws Exception if the test fails
2842+
*/
2843+
@Test
2844+
@Alerts({"miter", "round", "round", "bevel", "bevel", "bevel"})
2845+
public void lineJoinSetGet() throws Exception {
2846+
verify("<canvas id='myCanvas' width='2' height='2'></canvas>\n",
2847+
"log(context.lineJoin);\n"
2848+
+ "context.lineJoin = 'round'\n"
2849+
+ "log(context.lineJoin);\n"
2850+
+ "context.lineJoin = 'BeVEl'\n"
2851+
+ "log(context.lineJoin);\n"
2852+
+ "context.lineJoin = 'bevel'\n"
2853+
+ "log(context.lineJoin);\n"
2854+
+ "context.lineJoin = ' round '\n"
2855+
+ "log(context.lineJoin);\n"
2856+
+ "context.lineJoin = ' unknow '\n"
2857+
+ "log(context.lineJoin);\n");
2858+
}
2859+
2860+
/**
2861+
* @throws Exception if the test fails
2862+
*/
2863+
@Test
2864+
@Alerts("data:image/png;base64,"
2865+
+ "iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABK0lEQVR4AcSTvy4EURjFN2gVGl6CWq2lFZ0HEI0gQoVezwNo"
2866+
+ "xWPoVRoP4B0Ufr+b+Saz98/MFJLdnDP3u+c75+xusru2+OfXygrP+CKSYxxzPuE6FQ8dnRnbmFN4R3y7ozNjG1OFO0TvYcBZ"
2867+
+ "Le7FOVV4S2IDBpzV4l6cY4W7uC9gDjV3uZ7uY4U3yVF/NHetwgN6TmEL7vQU+1bhdeEshaqnVnhC9hBOQY/eJV+t8HLJMX4p"
2868+
+ "vHnhOfl9OBd6zfT+YeEmavGOaOKdh+QoYMZsWgwLr1C2YA3xX67tzJhNu2GhoSPUNzjEE5fPjs6MPfSaMZvEYaHCB49j6O/s"
2869+
+ "i/MHvsCAs5o7PXrNxH6RF8bilWEPPsNvGHC21J2e0PuzVajhl8cjzOHXc5fr6f4HAAD//xGJfzIAAAAGSURBVAMAPK0mKVxT"
2870+
+ "+QwAAAAASUVORK5CYII=")
2871+
public void drawMiter() throws Exception {
2872+
draw("<canvas id='myCanvas' width='20', height='20' style='border: 1px solid red;'></canvas>\n",
2873+
" context.lineJoin = 'miter';\n"
2874+
+ " context.lineWidth = 6;\n"
2875+
+ " context.beginPath();\n"
2876+
+ " context.moveTo(5, 17);\n"
2877+
+ " context.lineTo(10, 8);\n"
2878+
+ " context.lineTo(15, 17);\n"
2879+
+ " context.stroke();\n");
2880+
}
2881+
2882+
/**
2883+
* @throws Exception if the test fails
2884+
*/
2885+
@Test
2886+
@Alerts("data:image/png;base64,"
2887+
+ "iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABLklEQVR4AeySu2oCQRSGl9SBXB4ipEmXJiSkTJMmCUmZJgQS"
2888+
+ "LFS81GqvnYUgaGMliLVPYCdoITY+gOAjCPodcZfZnTPrgpbK/3lmzuXf3dk98478OxkefqBJzvCXy6yhC2WI1T5DMWjuHL6I"
2889+
+ "JZAcQVec4Q0jaYgqQ+IWVMUZyuCVMnVJTmoEWy7DR1pT4NI/hWew5DLMWp12Qu3RDN+Y/YR9eqfB6tMM8zQmVS7aGDX8o+EJ"
2890+
+ "kuqBxtBZm4bnFIugaUCyD5rkiWR2WzMNC2SuQVOFpOuDvqAmpgTPMw1l4JVsD0y12AxhAlUw1WbzAnJBQthQEjIob+6bzQyW"
2891+
+ "UANfDRYLGMMH/MAIApl3GCRZdOAO6jAFX3MWYnpPVM/UZUi/t+IveBTWvuRopObvQ3EDAAD//4EKc+UAAAAGSURBVAMAyMAm"
2892+
+ "KRVYII8AAAAASUVORK5CYII=")
2893+
public void drawRound() throws Exception {
2894+
draw("<canvas id='myCanvas' width='20', height='20' style='border: 1px solid red;'></canvas>\n",
2895+
" context.lineJoin = 'round';\n"
2896+
+ " context.lineWidth = 6;\n"
2897+
+ " context.beginPath();\n"
2898+
+ " context.moveTo(5, 17);\n"
2899+
+ " context.lineTo(10, 8);\n"
2900+
+ " context.lineTo(15, 17);\n"
2901+
+ " context.stroke();\n");
2902+
}
2903+
2904+
/**
2905+
* @throws Exception if the test fails
2906+
*/
2907+
@Test
2908+
@Alerts("data:image/png;base64,"
2909+
+ "iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABIElEQVR4AeySrW4CQRSFN3VNTZs06VM0NU1dbZuAAywCEgQC"
2910+
+ "geDHEH4MBhwCBQZL0DwBjgQEwfAAJDwCgu9CdjO7e2chgaAg59uZOXfuyTC7T86Nf4/A6y/07nfY4Mwa2LqiTvhBS93CO76q"
2911+
+ "qMC22nEyW6ch/LQF/rA1AzblKXxBSLZA6wmMhJox96ZaYIrqP5xTkg0x8EkLrPh2RC+qwXIwsMCGb7hUv2zMgScz8AW3CJqm"
2912+
+ "mBPQVMZ8hqPMwBLOK2hqYsoHzhDSG453TWagNMQpjsHUgMUMltABU0MWfyC9DI5jBoohjfKW0yzWsIMuuOoz2cICEpCFOXgK"
2913+
+ "BrqFEZNP6MEKXG2YSKi8OPVObYH0OXsecncMPsnfk5rPdBcHAAAA//9GRiCmAAAABklEQVQDAFchJilYW2vqAAAAAElFTkSu"
2914+
+ "QmCC")
2915+
public void drawBevel() throws Exception {
2916+
draw("<canvas id='myCanvas' width='20', height='20' style='border: 1px solid red;'></canvas>\n",
2917+
" context.lineJoin = 'bevel';\n"
2918+
+ " context.lineWidth = 6;\n"
2919+
+ " context.beginPath();\n"
2920+
+ " context.moveTo(5, 17);\n"
2921+
+ " context.lineTo(10, 8);\n"
2922+
+ " context.lineTo(15, 17);\n"
2923+
+ " context.stroke();\n");
2924+
}
2925+
28302926
private void draw(final String canvasSetup, final String drawJS) throws Exception {
28312927
final String html = DOCTYPE_HTML
28322928
+ "<html><head>\n"

0 commit comments

Comments
 (0)