diff --git a/pom.xml b/pom.xml
index 141b40c..9951b26 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@
org.marlin
marlin
jar
- 0.8.1.1-Unsafe
+ 0.8.1.2-Unsafe
Marlin software rasterizer
https://github.com/bourgesl/marlin-renderer
diff --git a/src/main/java/org/marlin/pisces/DHelpers.java b/src/main/java/org/marlin/pisces/DHelpers.java
index 6b63064..3b322b5 100644
--- a/src/main/java/org/marlin/pisces/DHelpers.java
+++ b/src/main/java/org/marlin/pisces/DHelpers.java
@@ -431,13 +431,6 @@ static void subdivideAt(double t, double[] src, int srcoff,
// From sun.java2d.loops.GeneralRenderer:
- static final int OUTCODE_TOP = 1;
- static final int OUTCODE_BOTTOM = 2;
- static final int OUTCODE_LEFT = 4;
- static final int OUTCODE_RIGHT = 8;
- static final int OUTCODE_MASK_T_B = OUTCODE_TOP | OUTCODE_BOTTOM;
- static final int OUTCODE_MASK_L_R = OUTCODE_LEFT | OUTCODE_RIGHT;
-
static int outcode(final double x, final double y,
final double[] clipRect)
{
diff --git a/src/main/java/org/marlin/pisces/DMarlinRenderingEngine.java b/src/main/java/org/marlin/pisces/DMarlinRenderingEngine.java
index 3ecf526..e014ac6 100644
--- a/src/main/java/org/marlin/pisces/DMarlinRenderingEngine.java
+++ b/src/main/java/org/marlin/pisces/DMarlinRenderingEngine.java
@@ -92,8 +92,6 @@ abstract PathIterator getNormalizingPathIterator(DRendererContext rdrCtx,
static final boolean DO_CLIP_FILL = true;
- static final boolean SKIP_WINDING_RULE_EVEN_ODD = false;
-
/**
* Public constructor
*/
@@ -858,20 +856,19 @@ public AATileGenerator getAATileGenerator(Shape s,
clip.getWidth(), clip.getHeight(),
windingRule);
- if (!SKIP_WINDING_RULE_EVEN_ODD) {
- DPathConsumer2D pc2d = r;
+ DPathConsumer2D pc2d = r;
- if (DO_CLIP_FILL && (windingRule == WIND_NON_ZERO) && rdrCtx.doClip) {
- if (DO_TRACE_PATH) {
- // trace Input:
- pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);
- }
- pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d);
+ if (DO_CLIP_FILL && (windingRule == WIND_NON_ZERO) && rdrCtx.doClip) {
+ if (DO_TRACE_PATH) {
+ // trace Input:
+ pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);
}
-
- // TODO: subdivide quad/cubic curves into monotonic curves ?
- pathTo(rdrCtx, pi, pc2d);
+ pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d);
}
+
+ // TODO: subdivide quad/cubic curves into monotonic curves ?
+ pathTo(rdrCtx, pi, pc2d);
+
} else {
// draw shape with given stroke:
r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
diff --git a/src/main/java/org/marlin/pisces/DTransformingPathConsumer2D.java b/src/main/java/org/marlin/pisces/DTransformingPathConsumer2D.java
index 1db4136..841bfda 100644
--- a/src/main/java/org/marlin/pisces/DTransformingPathConsumer2D.java
+++ b/src/main/java/org/marlin/pisces/DTransformingPathConsumer2D.java
@@ -495,6 +495,9 @@ static final class PathClipFilter implements DPathConsumer2D {
// the current outcode of the current sub path
private int cOutCode = 0;
+ // the cumulated (and) outcode of the complete path
+ private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
+
private boolean outside = false;
private double cx0, cy0;
@@ -524,7 +527,8 @@ PathClipFilter init(final DPathConsumer2D out) {
_clipRect[2] -= margin - rdrOffX;
_clipRect[3] += margin + rdrOffX;
- init_corners = true;
+ this.init_corners = true;
+ this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
return this; // fluent API
}
@@ -539,6 +543,8 @@ void dispose() {
@Override
public void pathDone() {
+ finishPath();
+
out.pathDone();
// TODO: fix possible leak if exception happened
@@ -548,35 +554,45 @@ public void pathDone() {
@Override
public void closePath() {
- if (outside) {
- this.outside = false;
+ finishPath();
- if (sOutCode == 0) {
+ out.closePath();
+ this.cOutCode = sOutCode;
+ }
+
+ private void finishPath() {
+ if (outside) {
+ // criteria: inside or totally outside ?
+ if (gOutCode == 0) {
finish();
} else {
+ this.outside = false;
stack.reset();
}
}
- out.closePath();
- this.cOutCode = sOutCode;
}
private void finish() {
+ this.outside = false;
+
if (!stack.isEmpty()) {
if (init_corners) {
init_corners = false;
+
+ final double[] _corners = corners;
+ final double[] _clipRect = clipRect;
// Top Left (0):
- corners[0] = clipRect[2];
- corners[1] = clipRect[0];
+ _corners[0] = _clipRect[2];
+ _corners[1] = _clipRect[0];
// Bottom Left (1):
- corners[2] = clipRect[2];
- corners[3] = clipRect[1];
+ _corners[2] = _clipRect[2];
+ _corners[3] = _clipRect[1];
// Top right (2):
- corners[4] = clipRect[3];
- corners[5] = clipRect[0];
+ _corners[4] = _clipRect[3];
+ _corners[5] = _clipRect[0];
// Bottom Right (3):
- corners[6] = clipRect[3];
- corners[7] = clipRect[1];
+ _corners[6] = _clipRect[3];
+ _corners[7] = _clipRect[1];
}
stack.pullAll(corners, out);
}
@@ -601,7 +617,10 @@ public void lineTo(final double xe, final double ye) {
final int sideCode = (outcode0 & outcode1);
// basic rejection criteria:
- if (sideCode != 0) {
+ if (sideCode == 0) {
+ this.gOutCode = 0;
+ } else {
+ this.gOutCode &= sideCode;
// keep last point coordinate before entering the clip again:
this.outside = true;
this.cx0 = xe;
@@ -611,7 +630,6 @@ public void lineTo(final double xe, final double ye) {
return;
}
if (outside) {
- this.outside = false;
finish();
}
// clipping disabled:
@@ -624,25 +642,25 @@ private void clip(final int sideCode,
{
// corner or cross-boundary on left or right side:
if ((outcode0 != outcode1)
- && ((sideCode & DHelpers.OUTCODE_MASK_T_B) != 0))
+ && ((sideCode & MarlinConst.OUTCODE_MASK_T_B) != 0))
{
// combine outcodes:
final int mergeCode = (outcode0 | outcode1);
- final int tbCode = mergeCode & DHelpers.OUTCODE_MASK_T_B;
- final int lrCode = mergeCode & DHelpers.OUTCODE_MASK_L_R;
- // add corners to outside stack:
- final int off = (lrCode == DHelpers.OUTCODE_LEFT) ? 0 : 2;
+ final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
+ final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
+ final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
+ // add corners to outside stack:
switch (tbCode) {
- case DHelpers.OUTCODE_TOP:
+ case MarlinConst.OUTCODE_TOP:
stack.push(off); // top
return;
- case DHelpers.OUTCODE_BOTTOM:
+ case MarlinConst.OUTCODE_BOTTOM:
stack.push(off + 1); // bottom
return;
default:
// both TOP / BOTTOM:
- if ((outcode0 & DHelpers.OUTCODE_TOP) != 0) {
+ if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
// top to bottom
stack.push(off); // top
stack.push(off + 1); // bottom
@@ -666,9 +684,12 @@ public void curveTo(final double x1, final double y1,
int sideCode = outcode0 & outcode3;
- if (sideCode != 0) {
+ if (sideCode == 0) {
+ this.gOutCode = 0;
+ } else {
sideCode &= DHelpers.outcode(x1, y1, clipRect);
sideCode &= DHelpers.outcode(x2, y2, clipRect);
+ this.gOutCode &= sideCode;
// basic rejection criteria:
if (sideCode != 0) {
@@ -682,7 +703,6 @@ public void curveTo(final double x1, final double y1,
}
}
if (outside) {
- this.outside = false;
finish();
}
// clipping disabled:
@@ -699,8 +719,11 @@ public void quadTo(final double x1, final double y1,
int sideCode = outcode0 & outcode2;
- if (outcode2 != 0) {
+ if (sideCode == 0) {
+ this.gOutCode = 0;
+ } else {
sideCode &= DHelpers.outcode(x1, y1, clipRect);
+ this.gOutCode &= sideCode;
// basic rejection criteria:
if (sideCode != 0) {
@@ -714,7 +737,6 @@ public void quadTo(final double x1, final double y1,
}
}
if (outside) {
- this.outside = false;
finish();
}
// clipping disabled:
diff --git a/src/main/java/org/marlin/pisces/Helpers.java b/src/main/java/org/marlin/pisces/Helpers.java
index ab27c7f..8f01d14 100644
--- a/src/main/java/org/marlin/pisces/Helpers.java
+++ b/src/main/java/org/marlin/pisces/Helpers.java
@@ -437,13 +437,6 @@ static void subdivideAt(float t, float[] src, int srcoff,
// From sun.java2d.loops.GeneralRenderer:
- static final int OUTCODE_TOP = 1;
- static final int OUTCODE_BOTTOM = 2;
- static final int OUTCODE_LEFT = 4;
- static final int OUTCODE_RIGHT = 8;
- static final int OUTCODE_MASK_T_B = OUTCODE_TOP | OUTCODE_BOTTOM;
- static final int OUTCODE_MASK_L_R = OUTCODE_LEFT | OUTCODE_RIGHT;
-
static int outcode(final float x, final float y,
final float[] clipRect)
{
diff --git a/src/main/java/org/marlin/pisces/MarlinConst.java b/src/main/java/org/marlin/pisces/MarlinConst.java
index d2849c1..512a403 100644
--- a/src/main/java/org/marlin/pisces/MarlinConst.java
+++ b/src/main/java/org/marlin/pisces/MarlinConst.java
@@ -163,4 +163,12 @@ interface MarlinConst {
*/
public static final int CAP_SQUARE = 2;
+ // Out codes
+ static final int OUTCODE_TOP = 1;
+ static final int OUTCODE_BOTTOM = 2;
+ static final int OUTCODE_LEFT = 4;
+ static final int OUTCODE_RIGHT = 8;
+ static final int OUTCODE_MASK_T_B = OUTCODE_TOP | OUTCODE_BOTTOM;
+ static final int OUTCODE_MASK_L_R = OUTCODE_LEFT | OUTCODE_RIGHT;
+ static final int OUTCODE_MASK_T_B_L_R = OUTCODE_MASK_T_B | OUTCODE_MASK_L_R;
}
diff --git a/src/main/java/org/marlin/pisces/MarlinRenderingEngine.java b/src/main/java/org/marlin/pisces/MarlinRenderingEngine.java
index eae2a7f..46170b8 100644
--- a/src/main/java/org/marlin/pisces/MarlinRenderingEngine.java
+++ b/src/main/java/org/marlin/pisces/MarlinRenderingEngine.java
@@ -93,8 +93,6 @@ abstract PathIterator getNormalizingPathIterator(RendererContext rdrCtx,
static final boolean DO_CLIP_FILL = true;
- static final boolean SKIP_WINDING_RULE_EVEN_ODD = false;
-
/**
* Public constructor
*/
@@ -856,21 +854,20 @@ public AATileGenerator getAATileGenerator(Shape s,
clip.getWidth(), clip.getHeight(),
windingRule);
- if (!SKIP_WINDING_RULE_EVEN_ODD) {
- PathConsumer2D pc2d = r;
+ PathConsumer2D pc2d = r;
- if (DO_CLIP_FILL && (windingRule == WIND_NON_ZERO) && rdrCtx.doClip) {
- if (DO_TRACE_PATH) {
- // trace Input:
- pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);
- }
- pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d);
+ if (DO_CLIP_FILL && (windingRule == WIND_NON_ZERO) && rdrCtx.doClip) {
+ if (DO_TRACE_PATH) {
+ // trace Input:
+ pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);
}
-
- // TODO: subdivide quad/cubic curves into monotonic curves ?
- pathTo(rdrCtx, pi, pc2d);
+ pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d);
}
- } else {
+
+ // TODO: subdivide quad/cubic curves into monotonic curves ?
+ pathTo(rdrCtx, pi, pc2d);
+
+ } else {
// draw shape with given stroke:
r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),
clip.getWidth(), clip.getHeight(),
diff --git a/src/main/java/org/marlin/pisces/MarlinTileGenerator.java b/src/main/java/org/marlin/pisces/MarlinTileGenerator.java
index e621738..2759358 100644
--- a/src/main/java/org/marlin/pisces/MarlinTileGenerator.java
+++ b/src/main/java/org/marlin/pisces/MarlinTileGenerator.java
@@ -32,6 +32,8 @@
final class MarlinTileGenerator implements AATileGenerator, MarlinConst {
+ private static final boolean DISABLE_BLEND = false;
+
private static final int MAX_TILE_ALPHA_SUM = TILE_W * TILE_H * MAX_AA_ALPHA;
private static final int TH_AA_ALPHA_FILL_EMPTY = ((MAX_AA_ALPHA + 1) / 3); // 33%
@@ -142,6 +144,10 @@ public int getTileHeight() {
*/
@Override
public int getTypicalAlpha() {
+ if (DISABLE_BLEND) {
+ // always return empty tiles to disable blending operations
+ return 0x00;
+ }
int al = cache.alphaSumInTile(x);
// Note: if we have a filled rectangle that doesn't end on a tile
// border, we could still return 0xff, even though al!=maxTileAlphaSum
diff --git a/src/main/java/org/marlin/pisces/TransformingPathConsumer2D.java b/src/main/java/org/marlin/pisces/TransformingPathConsumer2D.java
index e7d94c9..94bc58e 100644
--- a/src/main/java/org/marlin/pisces/TransformingPathConsumer2D.java
+++ b/src/main/java/org/marlin/pisces/TransformingPathConsumer2D.java
@@ -496,6 +496,9 @@ static final class PathClipFilter implements PathConsumer2D {
// the current outcode of the current sub path
private int cOutCode = 0;
+ // the cumulated (and) outcode of the complete path
+ private int gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
+
private boolean outside = false;
private float cx0, cy0;
@@ -525,7 +528,8 @@ PathClipFilter init(final PathConsumer2D out) {
_clipRect[2] -= margin - rdrOffX;
_clipRect[3] += margin + rdrOffX;
- init_corners = true;
+ this.init_corners = true;
+ this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
return this; // fluent API
}
@@ -540,6 +544,8 @@ void dispose() {
@Override
public void pathDone() {
+ finishPath();
+
out.pathDone();
// TODO: fix possible leak if exception happened
@@ -549,35 +555,45 @@ public void pathDone() {
@Override
public void closePath() {
- if (outside) {
- this.outside = false;
+ finishPath();
- if (sOutCode == 0) {
+ out.closePath();
+ this.cOutCode = sOutCode;
+ }
+
+ private void finishPath() {
+ if (outside) {
+ // criteria: inside or totally outside ?
+ if (gOutCode == 0) {
finish();
} else {
+ this.outside = false;
stack.reset();
}
}
- out.closePath();
- this.cOutCode = sOutCode;
}
private void finish() {
+ this.outside = false;
+
if (!stack.isEmpty()) {
if (init_corners) {
init_corners = false;
+
+ final float[] _corners = corners;
+ final float[] _clipRect = clipRect;
// Top Left (0):
- corners[0] = clipRect[2];
- corners[1] = clipRect[0];
+ _corners[0] = _clipRect[2];
+ _corners[1] = _clipRect[0];
// Bottom Left (1):
- corners[2] = clipRect[2];
- corners[3] = clipRect[1];
+ _corners[2] = _clipRect[2];
+ _corners[3] = _clipRect[1];
// Top right (2):
- corners[4] = clipRect[3];
- corners[5] = clipRect[0];
+ _corners[4] = _clipRect[3];
+ _corners[5] = _clipRect[0];
// Bottom Right (3):
- corners[6] = clipRect[3];
- corners[7] = clipRect[1];
+ _corners[6] = _clipRect[3];
+ _corners[7] = _clipRect[1];
}
stack.pullAll(corners, out);
}
@@ -602,7 +618,10 @@ public void lineTo(final float xe, final float ye) {
final int sideCode = (outcode0 & outcode1);
// basic rejection criteria:
- if (sideCode != 0) {
+ if (sideCode == 0) {
+ this.gOutCode = 0;
+ } else {
+ this.gOutCode &= sideCode;
// keep last point coordinate before entering the clip again:
this.outside = true;
this.cx0 = xe;
@@ -612,7 +631,6 @@ public void lineTo(final float xe, final float ye) {
return;
}
if (outside) {
- this.outside = false;
finish();
}
// clipping disabled:
@@ -625,25 +643,25 @@ private void clip(final int sideCode,
{
// corner or cross-boundary on left or right side:
if ((outcode0 != outcode1)
- && ((sideCode & DHelpers.OUTCODE_MASK_T_B) != 0))
+ && ((sideCode & MarlinConst.OUTCODE_MASK_T_B) != 0))
{
// combine outcodes:
final int mergeCode = (outcode0 | outcode1);
- final int tbCode = mergeCode & DHelpers.OUTCODE_MASK_T_B;
- final int lrCode = mergeCode & DHelpers.OUTCODE_MASK_L_R;
- // add corners to outside stack:
- final int off = (lrCode == DHelpers.OUTCODE_LEFT) ? 0 : 2;
+ final int tbCode = mergeCode & MarlinConst.OUTCODE_MASK_T_B;
+ final int lrCode = mergeCode & MarlinConst.OUTCODE_MASK_L_R;
+ final int off = (lrCode == MarlinConst.OUTCODE_LEFT) ? 0 : 2;
+ // add corners to outside stack:
switch (tbCode) {
- case DHelpers.OUTCODE_TOP:
+ case MarlinConst.OUTCODE_TOP:
stack.push(off); // top
return;
- case DHelpers.OUTCODE_BOTTOM:
+ case MarlinConst.OUTCODE_BOTTOM:
stack.push(off + 1); // bottom
return;
default:
// both TOP / BOTTOM:
- if ((outcode0 & DHelpers.OUTCODE_TOP) != 0) {
+ if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
// top to bottom
stack.push(off); // top
stack.push(off + 1); // bottom
@@ -667,9 +685,12 @@ public void curveTo(final float x1, final float y1,
int sideCode = outcode0 & outcode3;
- if (sideCode != 0) {
+ if (sideCode == 0) {
+ this.gOutCode = 0;
+ } else {
sideCode &= Helpers.outcode(x1, y1, clipRect);
sideCode &= Helpers.outcode(x2, y2, clipRect);
+ this.gOutCode &= sideCode;
// basic rejection criteria:
if (sideCode != 0) {
@@ -683,7 +704,6 @@ public void curveTo(final float x1, final float y1,
}
}
if (outside) {
- this.outside = false;
finish();
}
// clipping disabled:
@@ -700,8 +720,11 @@ public void quadTo(final float x1, final float y1,
int sideCode = outcode0 & outcode2;
- if (outcode2 != 0) {
+ if (sideCode == 0) {
+ this.gOutCode = 0;
+ } else {
sideCode &= Helpers.outcode(x1, y1, clipRect);
+ this.gOutCode &= sideCode;
// basic rejection criteria:
if (sideCode != 0) {
@@ -715,7 +738,6 @@ public void quadTo(final float x1, final float y1,
}
}
if (outside) {
- this.outside = false;
finish();
}
// clipping disabled:
diff --git a/src/main/java/test/FillClipBugTest.java b/src/main/java/test/FillClipBugTest.java
new file mode 100644
index 0000000..92eb113
--- /dev/null
+++ b/src/main/java/test/FillClipBugTest.java
@@ -0,0 +1,110 @@
+package test;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.geom.Path2D;
+import java.awt.image.BufferedImage;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.border.LineBorder;
+
+public class FillClipBugTest {
+
+ public static void main(String[] args) {
+
+ // First display which renderer is tested:
+ // JDK9 only:
+ System.setProperty("sun.java2d.renderer.verbose", "true");
+ System.out.println("Testing renderer: ");
+ // Other JDK:
+ String renderer = "undefined";
+ try {
+ renderer = sun.java2d.pipe.RenderingEngine.getInstance().getClass().getName();
+ System.out.println(renderer);
+ } catch (Throwable th) {
+ // may fail with JDK9 jigsaw (jake)
+ if (false) {
+ System.err.println("Unable to get RenderingEngine.getInstance()");
+ th.printStackTrace();
+ }
+ }
+
+ JFrame frame = new JFrame("JFrame Example");
+ JPanel panel = new MyPanel();
+ panel.setPreferredSize(new Dimension(MyPanel.max, MyPanel.max));
+ panel.setBorder(new LineBorder(Color.BLUE));
+ frame.add(panel);
+ frame.setLocationRelativeTo(null);
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ frame.pack();
+ frame.setVisible(true);
+ }
+
+ static class MyPanel extends JPanel {
+
+ final static int max = 250 - 1;
+ final static int decalX = 200;
+ final static int decalY = 200;
+
+ private Path2D.Double rect1 = createRectOutsideBounds(400, 300);
+ private Path2D.Double rect2 = createRectOverBounds();
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+
+ final int w = getWidth();
+ final int h = getHeight();
+
+ final BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE);
+ final Graphics2D g2d = (Graphics2D) bi.getGraphics();
+
+ g2d.setBackground(Color.WHITE);
+ g2d.clearRect(0, 0, w, h);
+
+ g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ g2d.setColor(Color.blue);
+ g2d.fill(rect1);
+
+ g2d.setColor(Color.red);
+ g2d.fill(rect2);
+
+ g.drawImage(bi, 0, 0, this);
+
+ g2d.dispose();
+ }
+
+ private static Path2D.Double createRectOutsideBounds(final int w, final int h) {
+ final Path2D.Double shape = new Path2D.Double();
+ shape.moveTo(-decalX, -decalY);
+ shape.lineTo(w + decalX, -decalY); // go right
+ shape.lineTo(w + decalX, h + decalY); // go down
+ shape.lineTo(-decalX, h + decalY); // go left
+
+ if (false) {
+ shape.closePath();
+ }
+ return shape;
+ }
+
+ private static Path2D.Double createRectOverBounds() {
+ final Path2D.Double shape = new Path2D.Double();
+ shape.moveTo(0 + decalX, 50 + decalY);
+ shape.lineTo(0 + decalX, 0 + decalY); // go up
+ shape.lineTo(50 + decalX, 0 + decalY); // go right
+ shape.lineTo(50 + decalX, 50 + decalY); // go down
+ shape.lineTo(0 + decalX, 50 + decalY); // go left
+
+ if (false) {
+ shape.closePath();
+ }
+ return shape;
+ }
+ }
+
+}