// FontOutlineSystem.pde // Dave Bollinger, Aug 2007 // purpose: to convert a string of text in a specified font into // a usable list of points represeting its outline // revisions: // Nov 2007 - interpolations // (so this is now essentially equivalent to textCharImplShape() from PGraphicsOpenGL) import java.awt.Font; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.Graphics2D; import java.awt.geom.Point2D; import java.awt.geom.PathIterator; import java.awt.Image.BufferedImage; import java.awt.Shape; /** * Represents one stroke of a glyph. Glyphs typically * look like MOVE,DRAW,DRAW,DRAW,...,DRAW,CLOSE - no * need to distinguish between draw and close as they're * both effectively draw strokes. */ public class FontPoint { static final int MOVE = PathIterator.SEG_MOVETO; static final int DRAW = PathIterator.SEG_LINETO; public float x, y; public int mode; public FontPoint() { this(0f,0f,MOVE); } public FontPoint(float x, float y) { this(x,y,MOVE); } public FontPoint(float x, float y, int mode) { this.x = x; this.y = y; this.mode = mode; } } /** * utility class for converting text outlines into a list of points */ public class FontOutlineSystem { private PApplet applet; private Font font; private Graphics2D g2d; private BufferedImage img; private FontRenderContext frc; public int linearDetail=1; public int bezierDetail=2; /** * simple constructor * * @param applet a reference to the PApplet that created this instance */ public FontOutlineSystem(PApplet applet) { this(applet,"",12); } /** * preferred constructor * * @param applet a reference to the PApplet that created this instance * @param fontName the name of the font to load * @param fontSize the size of the font */ public FontOutlineSystem(PApplet applet, String fontName, int fontSize) { this.applet = applet; // we need a Graphics2D... if (applet.g.getClass().getName().equals("PGraphicsJava2D")) { // JAVA2D has one of it's own already: g2d = ((PGraphicsJava2D)g).g2; } else { // P3D, OPENGL don't have one, so make one: img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); g2d = img.createGraphics(); } frc = g2d.getFontRenderContext(); loadFont(fontName, fontSize); } /** * load a font * * @param name the name of the font * (note that if running as an applet on the web then you can only * really be sure that the fonts bundled with the JRE are available, * if running locally then any TrueType font should work) * @param size the size of the font */ void loadFont(String name, int size) { font = new Font(name, Font.PLAIN, size); } /** * specify the number of interpolation steps per segment * * @param linearDetail number of steps to interpolate linear segments (default 1) * @param bezierDetail number of steps to interpolate bezier curve segments (default 2) */ void setDetail(int linearDetail, int bezierDetail) { this.linearDetail = linearDetail; this.bezierDetail = bezierDetail; } /** * Returns an array list containing FontPoint's that * represent the outline of the specified text at * specified origin. * * @param text the text to be converted * @param xo the x-coordinate of the origin * @param yo the y-coordinate of the origin */ ArrayList convert(String text, float xo, float yo) { ArrayList al = new ArrayList(); if (font==null) return al; float [] seg = new float[6]; float x=0, y=0, lx=0, ly=0, mx=0, my=0; GlyphVector gv = font.createGlyphVector(frc, text); Shape glyph = gv.getOutline(xo, yo); PathIterator pi = glyph.getPathIterator(null); while (!pi.isDone()) { int segtype = pi.currentSegment(seg); switch(segtype) { case PathIterator.SEG_MOVETO: x = lx = mx = seg[0]; y = ly = my = seg[1]; al.add(new FontPoint(x,y,FontPoint.MOVE)); break; case PathIterator.SEG_LINETO: for (int i=1; i<=linearDetail; i++) { float t = (float)(i) / (float)(linearDetail); x = lerp(lx, seg[0], t); y = lerp(ly, seg[1], t); al.add(new FontPoint(x,y,FontPoint.DRAW)); } lx = seg[0]; ly = seg[1]; break; case PathIterator.SEG_QUADTO: for (int i=1; i<=bezierDetail; i++) { float t = (float)(i) / (float)(bezierDetail); x = bezierPoint(lx, seg[0], seg[2], seg[2], t); y = bezierPoint(ly, seg[1], seg[3], seg[3], t); al.add(new FontPoint(x,y,FontPoint.DRAW)); } lx = seg[2]; ly = seg[3]; break; case PathIterator.SEG_CUBICTO: for (int i=1; i<=bezierDetail; i++) { float t = (float)(i) / (float)(bezierDetail); x = bezierPoint(lx, seg[0], seg[2], seg[4], t); y = bezierPoint(ly, seg[1], seg[3], seg[5], t); al.add(new FontPoint(x,y,FontPoint.DRAW)); } lx = seg[4]; ly = seg[5]; break; case PathIterator.SEG_CLOSE: x = lx = mx; y = ly = my; al.add(new FontPoint(x,y,FontPoint.DRAW)); break; } // switch pi.next(); } // while return al; } // convert }