Strokes

Stroking is the process of drawing a shape's outline.

BasicStroke

Java provides a BasicStroke, which allows you to adjust a line's thickness, end shape and whether it is dashed or not. A BasicStroke object holds information about the line width, join style, end-cap style, and dash style.

BasicStroke(float   width,        // width of the line               
            int     cap,          // end of line segment CAP_BUTT, CAP_ROUND or CAP_SQUARE        
            int     join,         // between line segments JOIN_BEVEL, JOIN_MITER or JOIN_ROUND          
            float   miterlimit,   //                   
            float[] dash          // an array showing the size of dashes and gaps           
            float   dash_phase)   // an index into dash, stateing start of sequence

There are various other, simplier, BasicStroke constructors.

 

BasicStrokeDemo Example: (Run Applet)

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Stroke_BasicStroke_Demo extends JApplet
{
    @Override
    public void init()
    {
        this.setContentPane(new View());
    }

    public class View extends JPanel implements ActionListener
    {
        private final float THICK_LINE = 30.0f;
        private final float THIN_LINE = 10.0f;
        private final float dash[] =
        {
            30.0f, 50.0f
        };  // interval for dashed lines
        private int cap = BasicStroke.CAP_SQUARE;
        private int join = BasicStroke.JOIN_ROUND;
        private float lineThickness = THICK_LINE;
        private boolean isDashed = false; // dashed or solid line
        private final JMenuItem capButt = new JMenuItem("CAP_BUTT");
        private final JMenuItem capRound = new JMenuItem("CAP_ROUND");
        private final JMenuItem capSquare = new JMenuItem("CAP_SQUARE");
        private final JMenuItem joinMiter = new JMenuItem("JOIN_MITER");
        private final JMenuItem joinBevel = new JMenuItem("JOIN_BEVEL");
        private final JMenuItem joinRound = new JMenuItem("JOIN_ROUND");
        private final JCheckBoxMenuItem dashed = new JCheckBoxMenuItem("Dashed", this.isDashed);
        private final JCheckBoxMenuItem thickLine = new JCheckBoxMenuItem("Thick Lines", true);

        public View()
        {
            super();

            final JMenuBar menuBar = new JMenuBar();
            final JMenu capMenu = new JMenu("CAP");
            final JMenu joinMenu = new JMenu("JOIN");
            final JMenu otherMenu = new JMenu("Other");

            setJMenuBar(menuBar);
            menuBar.add(capMenu);
            menuBar.add(joinMenu);
            menuBar.add(otherMenu);

            capMenu.add(capButt);
            capMenu.add(capRound);
            capMenu.add(capSquare);

            joinMenu.add(this.joinMiter);
            joinMenu.add(this.joinBevel);
            joinMenu.add(this.joinRound);

            otherMenu.add(this.dashed);
            otherMenu.add(this.thickLine);

            this.capButt.addActionListener(this);
            this.capRound.addActionListener(this);
            this.capSquare.addActionListener(this);
            this.joinBevel.addActionListener(this);
            this.joinMiter.addActionListener(this);
            this.joinRound.addActionListener(this);
            this.dashed.addActionListener(this);
            this.thickLine.addActionListener(this);
        }

        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            final Graphics2D g2 = (Graphics2D) g;

            BasicStroke basicStroke = null;
            if (this.isDashed)
            {
                basicStroke = new BasicStroke(this.lineThickness, this.cap, this.join, 1.0f, dash, 0.0f);
            }
            else
            {
                basicStroke = new BasicStroke(this.lineThickness, this.cap, this.join);
            }

            g2.setStroke(basicStroke);

            g2.drawLine(30, 30, 150, 30);
            g2.drawLine(150, 30, 30, 150);

            g2.drawRect(200, 30, 250, 100);
        }

        @Override
        public void actionPerformed(ActionEvent e)
        {
            final Object source = e.getSource();
            if (source == this.capButt)
            {
                this.cap = BasicStroke.CAP_BUTT;
            }
            else if (source == this.capRound)
            {
                this.cap = BasicStroke.CAP_ROUND;
            }
            else if (source == this.capSquare)
            {
                this.cap = BasicStroke.CAP_SQUARE;
            }
            else if (source == this.joinBevel)
            {
                this.join = BasicStroke.JOIN_BEVEL;
            }
            else if (source == this.joinMiter)
            {
                this.join = BasicStroke.JOIN_MITER;
            }
            else if (source == this.joinRound)
            {
                this.join = BasicStroke.JOIN_ROUND;
            }
            else if (source == this.thickLine)
            {
                if (this.thickLine.isSelected())
                {
                    this.lineThickness = THICK_LINE;
                }
                else
                {
                    this.lineThickness = THIN_LINE;
                }
            }
            else if (source == this.dashed)
            {
                this.isDashed = this.dashed.isSelected();
            }
            this.repaint();
        }
    }
}

Placing a Texture onto a Stroke

Textures can be placed onto strokes, as shown in the example below.

Stroke_Texture_Demo Example: (Run Applet)

import java.awt.*;
import java.awt.image.*;
import javax.swing.*;

public class Stroke_Texture_Demo extends JApplet
{
    @Override
    public void init()
    {
        this.setContentPane(new View());
    }

    public class View extends JPanel
    {
        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);

            final Image image = new ImageIcon(getClass().getClassLoader().getResource("images/smiley.jpg")).getImage();
            final Graphics2D g2 = (Graphics2D) g;

            // texture
            final int textureWidth = 30;
            final int textureHeight = 30;
            final BufferedImage textureImg = new BufferedImage(textureWidth, textureHeight, BufferedImage.TYPE_INT_RGB);
            final Graphics2D textureG = textureImg.createGraphics();
            textureG.drawImage(image, 0, 0, textureWidth, textureHeight, this);

            final Rectangle rectangle = new Rectangle(0, 0, textureWidth, textureHeight);
            final TexturePaint texturePaint = new TexturePaint(textureImg, rectangle);
            g2.setPaint(texturePaint);

            // stroke
            final BasicStroke stroke = new BasicStroke(15f);
            g2.setStroke(stroke);

            g2.drawArc(30, 50, 250, 100, 180, -360);
        }
    }
}

Placing a Gradient onto a Stroke

Gradients can be placed onto strokes, as shown in the example below.

Stroke_Gradient_Demo Example: (Run Applet)

import java.awt.*;
import javax.swing.*;

public class Stroke_Gradient_Demo extends JApplet
{
    @Override
    public void init()
    {
        this.setContentPane(new View());
    }

    public class View extends JPanel
    {
        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            final Graphics2D g2 = (Graphics2D) g;

            // gradient
            final GradientPaint gradient = new GradientPaint(0, 0, Color.yellow, getWidth(), getHeight(), Color.blue);
            g2.setPaint(gradient);

            // stroke
            final BasicStroke stroke = new BasicStroke(15f);
            g2.setStroke(stroke);

            g2.drawArc(30, 50, 250, 100, 180, -360);
        }
    }
}

User Defined Strokes

Java provides a Stroke interface. This comes with one method:

public Shape createStrokedShape(Shape shape)

This method returns a Shape that can be used as a stroke.

Composite Stroke

Stroke_Composite_Demo Example: (Run Applet)

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

public class Stroke_Composite_Demo extends JApplet
{
    @Override
    public void init()
    {
        this.setContentPane(new View());
    }

    public class View extends JPanel
    {
        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            final Graphics2D g2 = (Graphics2D) g;

            final CompositeStroke compositeStroke = new CompositeStroke(new BasicStroke(15f), new BasicStroke(5.0f));
            g2.setStroke(compositeStroke);

            g2.drawArc(30, 50, 250, 100, 180, -180);
            g2.drawArc(30, 90, 250, 100, 180, 180);
        }
    }

    public class CompositeStroke implements Stroke
    {
        private Stroke mainStroke;
        private Stroke minusStroke;

        public CompositeStroke(Stroke mainStroke, Stroke minusStroke)
        {
            this.mainStroke = mainStroke;
            this.minusStroke = minusStroke;
        }

        @Override
        public Shape createStrokedShape(Shape shape)
        {
            final Area mainArea = new Area(this.mainStroke.createStrokedShape(shape));
            final Area minusArea = new Area(this.minusStroke.createStrokedShape(shape));

            mainArea.subtract(minusArea);
            return mainArea;
        }
    }
}

Pattern Stroke

Stroke_Pattern_Demo Example: (Run Applet)

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

public class Stroke_Pattern_Demo extends JApplet
{
    @Override
    public void init()
    {
        this.setContentPane(new View());
    }

    public class View extends JPanel
    {
        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            final Graphics2D g2 = (Graphics2D) g;

            // set up the shapeStroke
            final Rectangle rectangle = new Rectangle(0, 0, 20, 20);
            final Ellipse2D ellipse = new Ellipse2D.Float(0, 0, 20, 50);
            final Shape shape[] = new Shape[]
            {
                rectangle, ellipse
            };
            final PatternStroke shapeStroke = new PatternStroke(shape, 15.0f);

            g2.setStroke(shapeStroke);
            g2.drawArc(30, 50, 250, 100, 180, -180);
            g2.drawArc(30, 90, 250, 100, 180, 180);
        }
    }

    public class PatternStroke implements Stroke
    {
        private float resolution;                 // the distance between shapes
        private boolean repeat = true;
        private final Shape shapes[];
        private final AffineTransform affineTransform = new AffineTransform();

        public PatternStroke(Shape shapes[], float resolution)
        {
            this.resolution = resolution;                         // the distance between shapes
            this.shapes = new Shape[shapes.length];

            for (int i = 0; i < this.shapes.length; i++)
            {
                final Rectangle2D bounds = shapes[i].getBounds2D();
                this.affineTransform.setToTranslation(-bounds.getCenterX(), -bounds.getCenterY());
                this.shapes[i] = this.affineTransform.createTransformedShape(shapes[i]);
            }
        }

        @Override
        public Shape createStrokedShape(Shape shape)
        {
            final GeneralPath result = new GeneralPath();
            final PathIterator pathIterator = new FlatteningPathIterator(shape.getPathIterator(null), 1.0);
            float points[] = new float[2];
            float moveX = 0;
            float moveY = 0;
            float curX = 0;
            float curY = 0;
            float nextX = 0;
            float nextY = 0;
            float distanceToNext = 0;
            final int length = this.shapes.length;
            int segmentType = 0;
            int currentPos = 0;

            while ((currentPos < length) && (!pathIterator.isDone()))
            {
                segmentType = pathIterator.currentSegment(points);
                switch (segmentType)
                {
                    case PathIterator.SEG_MOVETO:  // starting location for a new subpath
                    {
                        moveX = nextX = points[0];
                        moveY = nextY = points[1];
                        result.moveTo(moveX, moveY);
                        distanceToNext = 0;
                        break;
                    }

                    case PathIterator.SEG_LINETO:   // reached end of a subpath
                    {
                        curX = points[0];
                        curY = points[1];
                        // each subpath needs to be closed, so do not break here
                        // instead fall into SEG_CLOSE code below
                    }

                    case PathIterator.SEG_CLOSE:  // close off the subpath
                    {
                        points[0] = moveX;
                        points[1] = moveY;
                        final float dx = curX - nextX;
                        final float dy = curY - nextY;
                        final float distance = (float) (Math.sqrt((dx * dx) + (dy * dy)));
                        if (distance >= distanceToNext)
                        {
                            final float ratio = 1.0f / distance;
                            final float angle = (float) (Math.atan2(dy, dx));
                            while ((currentPos < length) && (distance >= distanceToNext))
                            {
                                final float x = nextX + distanceToNext * dx * ratio;
                                final float y = nextY + distanceToNext * dy * ratio;
                                this.affineTransform.setToTranslation(x, y);
                                this.affineTransform.rotate(angle);
                                result.append(this.affineTransform.createTransformedShape(this.shapes[currentPos]), false);
                                distanceToNext += this.resolution;
                                currentPos++;
                                if (this.repeat)
                                {
                                    currentPos %= length;
                                }
                            }
                        }
                        distanceToNext -= distance;
                        nextX = curX;
                        nextY = curY;
                        break;
                    }
                }
                pathIterator.next();
            }
            return result;
        }
    }
}

Composite with Pattern Stroke

Stroke_CompositeWithPattern_Demo Example: (Run Applet)

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

public class Stroke_CompositeWithPattern_Demo extends JApplet
{
    @Override
    public void init()
    {
        this.setContentPane(new View());
    }

    public class View extends JPanel
    {
        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            final Graphics2D g2 = (Graphics2D) g;

            final Rectangle rectangle = new Rectangle(0, 0, 15, 15);
            final Ellipse2D ellipse = new Ellipse2D.Float(0, 0, 15, 15);
            final Shape shape[] = new Shape[]
            {
                rectangle, ellipse
            };
            final PatternStroke shapeStroke = new PatternStroke(shape, 20.0f);

            final CompositeStroke compositeStroke = new CompositeStroke(new BasicStroke(25f), shapeStroke);
            g2.setStroke(compositeStroke);

            g2.drawArc(30, 50, 250, 100, 180, -180);
            g2.drawArc(30, 90, 250, 100, 180, 180);
        }
    }

    public class CompositeStroke implements Stroke
    {
        private Stroke mainStroke;
        private Stroke minusStroke;

        public CompositeStroke(Stroke mainStroke, Stroke minusStroke)
        {
            this.mainStroke = mainStroke;
            this.minusStroke = minusStroke;
        }

        public Shape createStrokedShape(Shape shape)
        {
            final Area mainArea = new Area(this.mainStroke.createStrokedShape(shape));
            final Area minusArea = new Area(this.minusStroke.createStrokedShape(shape));

            mainArea.subtract(minusArea);
            return mainArea;
        }
    }

    public class PatternStroke implements Stroke
    {
        private float resolution;                 // the distance between shapes
        private boolean repeat = true;
        private final Shape shapes[];
        private final AffineTransform affineTransform = new AffineTransform();

        public PatternStroke(Shape shapes[], float resolution)
        {
            this.resolution = resolution;                         // the distance between shapes
            this.shapes = new Shape[shapes.length];

            for (int i = 0; i < this.shapes.length; i++)
            {
                final Rectangle2D bounds = shapes[i].getBounds2D();
                this.affineTransform.setToTranslation(-bounds.getCenterX(), -bounds.getCenterY());
                this.shapes[i] = this.affineTransform.createTransformedShape(shapes[i]);
            }
        }

        public Shape createStrokedShape(Shape shape)
        {
            final GeneralPath result = new GeneralPath();
            final PathIterator pathIterator = new FlatteningPathIterator(shape.getPathIterator(null), 1.0);
            float points[] = new float[2];
            float moveX = 0;
            float moveY = 0;
            float curX = 0;
            float curY = 0;
            float nextX = 0;
            float nextY = 0;
            float distanceToNext = 0;
            int length = this.shapes.length;
            int segmentType = 0;
            int currentPos = 0;

            while ((currentPos < length) && (!pathIterator.isDone()))
            {
                segmentType = pathIterator.currentSegment(points);
                switch (segmentType)
                {
                    case PathIterator.SEG_MOVETO:  // starting location for a new subpath
                    {
                        moveX = nextX = points[0];
                        moveY = nextY = points[1];
                        result.moveTo(moveX, moveY);
                        distanceToNext = 0;
                        break;
                    }

                    case PathIterator.SEG_LINETO:   // reached end of a subpath
                    {
                        curX = points[0];
                        curY = points[1];
                        // each subpath needs to be closed, so do not break here
                        // instead fall into SEG_CLOSE code below
                    }

                    case PathIterator.SEG_CLOSE:  // close off the subpath
                    {
                        points[0] = moveX;
                        points[1] = moveY;
                        final float dx = curX - nextX;
                        final float dy = curY - nextY;
                        final float distance = (float) (Math.sqrt((dx * dx) + (dy * dy)));
                        if (distance >= distanceToNext)
                        {
                            final float ratio = 1.0f / distance;
                            final float angle = (float) (Math.atan2(dy, dx));
                            while ((currentPos < length) && (distance >= distanceToNext))
                            {
                                final float x = nextX + distanceToNext * dx * ratio;
                                final float y = nextY + distanceToNext * dy * ratio;
                                this.affineTransform.setToTranslation(x, y);
                                this.affineTransform.rotate(angle);
                                result.append(this.affineTransform.createTransformedShape(this.shapes[currentPos]), false);
                                distanceToNext += this.resolution;
                                currentPos++;
                                if (this.repeat)
                                {
                                    currentPos %= length;
                                }
                            }
                        }
                        distanceToNext -= distance;
                        nextX = curX;
                        nextY = curY;
                        break;
                    }
                }
                pathIterator.next();
            }
            return result;
        }
    }
}

Rough Stroke

Stroke_Rough_Demo Example: (Run Applet)

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

public class Stroke_Rough_Demo extends JApplet
{
    @Override
    public void init()
    {
        this.setContentPane(new View());
    }

    public class View extends JPanel
    {
        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            final Graphics2D g2 = (Graphics2D) g;

            g2.setStroke(new RoughStroke(20, 10, 2));
            g2.drawArc(30, 50, 250, 100, 180, -180);
            g2.drawArc(30, 90, 250, 100, 180, 180);
        }
    }

    public class RoughStroke implements Stroke
    {
        private int strokeWidth;
        private float resolution;
        private float amplitude;

        public RoughStroke(int strokeSize, float resolution, float amplitude)
        {
            this.strokeWidth = strokeSize;
            this.resolution = resolution;
            this.amplitude = amplitude;
        }

        public Shape createStrokedShape(Shape shape)
        {
            shape = new BasicStroke(this.strokeWidth).createStrokedShape(shape);

            final GeneralPath result = new GeneralPath();
            final PathIterator pathIterator = new FlatteningPathIterator(shape.getPathIterator(null), 1.0);
            float points[] = new float[2];
            float moveX = 0;
            float moveY = 0;
            float curX = 0;
            float curY = 0;
            float nextX = 0;
            float nextY = 0;
            float distanceToNext = 0;
            int segmentType = 0;

            while (!pathIterator.isDone())
            {
                segmentType = pathIterator.currentSegment(points);
                switch (segmentType)
                {
                    case PathIterator.SEG_MOVETO:  // starting location for a new subpath
                    {
                        moveX = nextX = randomize(points[0]);
                        moveY = nextY = randomize(points[1]);
                        result.moveTo(moveX, moveY);
                        distanceToNext = 0;
                        break;
                    }
                    case PathIterator.SEG_LINETO:
                    {
                        curX = randomize(points[0]);
                        curY = randomize(points[1]);
                        // each subpath needs to be closed, so do not break here
                        // instead fall into SEG_CLOSE code below
                    }

                    case PathIterator.SEG_CLOSE:  //
                    {
                        points[0] = moveX;
                        points[1] = moveY;
                        final float dx = curX - nextX;
                        final float dy = curY - nextY;
                        final float distance = (float) (Math.sqrt(dx * dx + dy * dy));
                        if (distance >= distanceToNext)
                        {
                            final float ratio = 1.0f / distance;
                            while (distance >= distanceToNext)
                            {
                                final float x = nextX + distanceToNext * dx * ratio;
                                final float y = nextY + distanceToNext * dy * ratio;
                                result.lineTo(randomize(x), randomize(y));
                                distanceToNext += this.resolution;
                            }
                        }
                        distanceToNext -= distance;
                        nextX = curX;
                        nextY = curY;
                        break;
                    }
                }
                pathIterator.next();
            }
            return result;
        }

        private float randomize(float x)
        {
            return x + (float) (Math.random() * this.amplitude * 2 - 1);
        }
    }
}

String Stroke

Stroke_String_Demo Example: (Run Applet)

import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import javax.swing.*;

public class Stroke_String_Demo extends JApplet
{
    @Override
    public void init()
    {
        this.setContentPane(new View());
    }

    public class View extends JPanel
    {
        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            final Graphics2D g2 = (Graphics2D) g;

            g2.setStroke(new StringStroke("Dundalk I.T.", new Font("Times New Roman", Font.BOLD + Font.ITALIC, 50), StringStroke.ALIGN_CENTER));
            g2.drawArc(30, 50, 250, 100, 180, -180);

            g2.setStroke(new StringStroke("DkIT", new Font("Times New Roman", Font.BOLD, 30), StringStroke.REPEAT));
            g2.drawArc(30, 90, 250, 100, 180, 180);
        }
    }

    public class StringStroke implements Stroke
    {
        public final static int ALIGN_LEFT = 0;
        public final static int ALIGN_RIGHT = 1;
        public final static int ALIGN_CENTER = 2;
        public final static int REPEAT = 3;
        public final static int STRETCH = 4;
        private int layout = 0;
        private String text;
        private Font font;

        public StringStroke(String text, Font font, int layout)
        {
            this.text = text;
            this.font = font;
            this.layout = layout;

            if (this.layout == StringStroke.REPEAT)
            {
                this.text += "   "; // place gap between repeating text
            }
            else if ((this.layout == StringStroke.ALIGN_CENTER))
            {
                this.text = " " + this.text + " ";
            }
            else if ((this.layout == StringStroke.ALIGN_RIGHT))
            {
                this.text = " " + this.text;
            }
        }

        public Shape createStrokedShape(Shape shape)
        {
            final FontRenderContext fontRenderContext = new FontRenderContext(null, true, true);
            final GlyphVector glyphVector = this.font.createGlyphVector(fontRenderContext, this.text);
            final GeneralPath result = new GeneralPath();
            int length = glyphVector.getNumGlyphs();

            if (length == 0) // string length == 0
            {
                return result;
            }

            final PathIterator pathIterator = new FlatteningPathIterator(shape.getPathIterator(null), 1.0);
            final AffineTransform affineTransform = new AffineTransform();
            float points[] = new float[2];
            float moveX = 0;
            float moveY = 0;
            float curX = 0;
            float curY = 0;
            float nextX = 0;
            float nextY = 0;
            float distanceToNext = 0;
            int segmentType = 0;
            int curCharacterPos = 0;

            // calculate the length of the path
            final float pathLength = measurePathLength(shape);
            final double stringLength = glyphVector.getLogicalBounds().getWidth();

            float factor;
            if (this.layout == StringStroke.STRETCH)
            {
                factor = pathLength / (float) stringLength;
            }
            else
            {
                factor = 1.0f;
            }

            float offset = 0;
            if (this.layout == StringStroke.ALIGN_CENTER)
            {
                offset = (pathLength - (float) stringLength) / 2.0f;
            }
            else if (this.layout == StringStroke.ALIGN_RIGHT)
            {
                offset = (pathLength - (float) stringLength);
            }

            float nextAdvance = 0;
            while ((curCharacterPos < length) && (!pathIterator.isDone()))
            {
                segmentType = pathIterator.currentSegment(points);
                switch (segmentType)
                {
                    case PathIterator.SEG_MOVETO: // starting location for a new subpath
                    {
                        curX = points[0];
                        curY = points[1];

                        moveX = curX;
                        moveY = curY;
                        result.moveTo(moveX, moveY);
                        nextAdvance = glyphVector.getGlyphMetrics(curCharacterPos).getAdvance() * 0.5f;

                        distanceToNext = nextAdvance;
                        break;
                    }

                    case PathIterator.SEG_LINETO:   // reached end of a subpath
                    {
                        nextX = points[0];
                        nextY = points[1];
                        // each subpath needs to be closed, so do not break here
                        // instead fall into SEG_CLOSE code below
                    }

                    case PathIterator.SEG_CLOSE: // close off the subpath
                    {
                        points[0] = moveX;
                        points[1] = moveY;

                        final float dx = nextX - curX;
                        final float dy = nextY - curY;
                        final float distance = (float) Math.sqrt(dx * dx + dy * dy);
                        if (distance >= distanceToNext)
                        {
                            final float ratio = 1.0f / distance;
                            final float angle = (float) Math.atan2(dy, dx);
                            while ((curCharacterPos < length) && (distance >= distanceToNext))
                            {
                                final Shape glyph = glyphVector.getGlyphOutline(curCharacterPos);
                                final Point2D p = glyphVector.getGlyphPosition(curCharacterPos);
                                final float px = (float) p.getX();
                                final float py = (float) p.getY();
                                final float x = curX + distanceToNext * dx * ratio;
                                final float y = curY + distanceToNext * dy * ratio;

                                float advance = nextAdvance;
                                if ((curCharacterPos < length - 1))
                                {
                                    nextAdvance = glyphVector.getGlyphMetrics(curCharacterPos + 1).getAdvance() * 0.5f;
                                }
                                else
                                {
                                    nextAdvance = 0.0f;
                                }

                                // allow for offset to CENTER or RIGHT
                                if (curCharacterPos == 0)
                                {
                                    advance += offset;
                                }

                                affineTransform.setToTranslation(x, y);
                                affineTransform.rotate(angle);
                                affineTransform.translate(-px - advance, -py);
                                result.append(affineTransform.createTransformedShape(glyph), false);
                                distanceToNext += (advance + nextAdvance) * factor;
                                curCharacterPos++;
                                if ((this.layout == StringStroke.REPEAT) && (curCharacterPos == length))
                                {
                                    curCharacterPos = 0;
                                    nextAdvance = glyphVector.getGlyphMetrics(curCharacterPos).getAdvance() * 0.5f;
                                }
                            }
                        }
                        distanceToNext -= distance;
                        curX = nextX;
                        curY = nextY;
                        break;
                    }
                }
                pathIterator.next();
            }
            return result;
        }

        public float measurePathLength(Shape shape)
        {
            final PathIterator pathIterator = new FlatteningPathIterator(shape.getPathIterator(null), 1.0);
            float points[] = new float[2];
            float moveX = 0;
            float moveY = 0;
            float nextX = 0;
            float nextY = 0;
            float curX = 0;
            float curY = 0;
            float total = 0;
            int segmentType = 0;

            while (!pathIterator.isDone())
            {
                segmentType = pathIterator.currentSegment(points);
                switch (segmentType)
                {
                    case PathIterator.SEG_MOVETO:   // starting location for a new subpath
                    {
                        moveX = nextX = points[0];
                        moveY = nextY = points[1];
                        break;
                    }

                    case PathIterator.SEG_LINETO:   // reached end of a subpath
                    {
                        curX = points[0];
                        curY = points[1];
                        // each subpath needs to be closed, so do not break here
                        // instead fall into SEG_CLOSE code below
                    }

                    case PathIterator.SEG_CLOSE:  // close off the subpath
                    {
                        points[0] = moveX;
                        points[1] = moveY;
                        final float dx = curX - nextX;
                        final float dy = curY - nextY;
                        total += (float) Math.sqrt((dx * dx) + (dy * dy));
                        nextX = curX;
                        nextY = curY;
                        break;
                    }
                }
                pathIterator.next();
            }
            return total;
        }
    }
}
 
<div align="center"><a href="../versionC/index.html" title="DKIT Lecture notes homepage for Derek O&#39; Reilly, Dundalk Institute of Technology (DKIT), Dundalk, County Louth, Ireland. Copyright Derek O&#39; Reilly, DKIT." target="_parent" style='font-size:0;color:white;background-color:white'>&nbsp;</a></div>