@Override protected void paintIcon( java.awt.Component c, java.awt.Graphics2D g2, int width, int height, java.awt.Paint fillPaint, java.awt.Paint drawPaint) { java.awt.Font prevFont = g2.getFont(); g2.setFont(font); String text = "A"; java.awt.FontMetrics fm = g2.getFontMetrics(); int messageWidth = fm.stringWidth(text); int ascent = fm.getMaxAscent(); int descent = fm.getMaxDescent(); int x = (width / 2) - (messageWidth / 2); int y = ((height / 2) + (ascent / 2)) - (descent / 2); java.awt.font.GlyphVector glyphVector = font.createGlyphVector(g2.getFontRenderContext(), text); java.awt.Shape outline = glyphVector.getOutline(x, y); g2.setPaint(drawPaint); g2.draw(outline); g2.setPaint(fillPaint); g2.fill(outline); // g2.drawString( text, x, y ); g2.setFont(prevFont); }
public static Shape generateShapeFromText(Font font, String string) { BufferedImage img = GraphicsUtilities.createCompatibleTranslucentImage(1, 1); Graphics2D g2 = img.createGraphics(); try { GlyphVector vect = font.createGlyphVector(g2.getFontRenderContext(), string); Shape shape = vect.getOutline(0f, (float) -vect.getVisualBounds().getY()); return shape; } finally { g2.dispose(); } }
/** * This deals with a bug/peculiarity for the default Mac font: several pixels of the ascent are * actually empty. This screws up certain measurements which assume the font is actually a few * pixels taller than it really is. */ private static int getUnusedAscent(FontMetrics fm, Font font) { Integer value = ascentTable.get(font); if (value == null) { int recordedAscent = fm.getAscent(); FontRenderContext frc = new FontRenderContext(new AffineTransform(), false, false); GlyphVector gv = font.createGlyphVector(frc, "XYZ"); Rectangle2D bounds = ShapeBounds.getBounds(gv.getOutline()); int observedAscent = (int) (Math.ceil(bounds.getHeight()) + .5); value = new Integer(recordedAscent - observedAscent); ascentTable.put(font, value); } return value.intValue(); }
private Shape generateShapeFromText() { Font font = new Font(fontFamily, Font.PLAIN, fontSize); BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = img.createGraphics(); try { GlyphVector vect = font.createGlyphVector(g2.getFontRenderContext(), str); float dispX = x; float dispY = (float) (y - vect.getVisualBounds().getY()); Shape shape = vect.getOutline(dispX, dispY); return shape; } finally { g2.dispose(); } }
private static BufferedImage createImage(String s, boolean valid) { FontRenderContext frc = new FontRenderContext(null, true, true); Font font = new Font("dialog", Font.BOLD, 12); GlyphVector glyphs = font.createGlyphVector(frc, s); Shape shape = glyphs.getOutline(); Rectangle bounds = shape.getBounds(); int imageW = bounds.width; int imageH = bounds.height; BufferedImage image = new BufferedImage(imageW, imageH, BufferedImage.TYPE_INT_ARGB); Graphics2D g = (Graphics2D) image.getGraphics(); g.setColor(valid ? Color.blue : Color.red); g.translate(bounds.x, -bounds.y); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g.fill(shape); return image; }
/** * Draws the specified glyph vector at the location {@code (x, y)}. * * @param g the glyph vector ({@code null} not permitted). * @param x the x-coordinate. * @param y the y-coordinate. */ @Override public void drawGlyphVector(GlyphVector g, float x, float y) { fill(g.getOutline(x, y)); }
@Override public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; int pad = 5; Polygon polyLeft = new Polygon( new int[] {pad, getWidth() / 2, getWidth() / 2, pad}, new int[] {pad, pad, getHeight() - pad, getHeight() - pad}, 4); Polygon polyRight = new Polygon( new int[] {getWidth() / 2, getWidth() - pad, getWidth() - pad, getWidth() / 2}, new int[] {pad, pad, getHeight() - pad, getHeight() - pad}, 4); g2d.setColor(Color.red.darker()); if (isSelected()) g2d.setColor(new Color(172, 225, 175)); else g2d.setColor(Color.gray); g2d.fill(polyLeft); if (isSelected()) g2d.setColor(Color.gray); else g2d.setColor(new Color(172, 225, 175)); g2d.fill(polyRight); Area round1 = new Area(new RoundRectangle2D.Double(0, 0, getWidth() - 1, getHeight() - 1, 8, 8)); Area round2 = new Area(new RoundRectangle2D.Double(4, 4, getWidth() - 1 - 8, getHeight() - 1 - 8, 6, 6)); Area round3 = new Area( new RoundRectangle2D.Double(6, 6, getWidth() - 1 - 12, getHeight() - 1 - 12, 5, 5)); Area round4 = new Area( new Ellipse2D.Float( -getWidth() / 2, -getHeight(), getWidth() * 2, getHeight() * 3 / 2)); round4.intersect(round2); round1.exclusiveOr(round2); round2.exclusiveOr(round3); int width = getWidth(); int height = getHeight(); int d1 = 6; int d2 = 8; Area outBorder = new Area( new Polygon( new int[] { d1, width - 1 - d1, width - 1 - d1, width - 1, width - 1, width - 1 - d1, width - 1 - d1, d1, d1, 0, 0, d1 }, new int[] { 0, 0, d1, d1, height - 1 - d1, height - 1 - d1, height - 1, height - 1, height - 1 - d1, height - 1 - d1, d1, d1 }, 12)); outBorder.subtract( new Area( new Polygon( new int[] { d2, width - 1 - d2, width - 1 - d2, width - 1 - (d2 - d1), width - 1 - (d2 - d1), width - 1 - d2, width - 1 - d2, d2, d2, d2 - d1, d2 - d1, d2 }, new int[] { d2 - d1, d2 - d1, d2, d2, height - 1 - d2, height - 1 - d2, height - 1 - (d2 - d1), height - 1 - (d2 - d1), height - 1 - d2, height - 1 - d2, d2, d2 }, 12))); g2d.setColor(new Color(0x993333)); g2d.fill(outBorder); int d3 = 3; int d4 = 5; Area midBorder = new Area(new Rectangle2D.Double(d3, d3, width - 1 - d3 * 2, height - 1 - d3 * 2)); midBorder.subtract( new Area(new Rectangle2D.Double(d4, d4, width - 1 - d4 * 2, height - 1 - d4 * 2))); g2d.fill(midBorder); GlyphVector glyphVectorLeft = new Font("宋体", Font.PLAIN, fontsize) .createGlyphVector(new FontRenderContext(null, true, true), onText); Shape shapeTextLeft = glyphVectorLeft.getOutline( -(float) (glyphVectorLeft.getVisualBounds().getMinX() - d3 - (getWidth() / 2 - glyphVectorLeft.getVisualBounds().getWidth()) / 2 + 1), -(float) (glyphVectorLeft.getVisualBounds().getMinY() - (getHeight() - glyphVectorLeft.getVisualBounds().getHeight()) / 2) - 1); GlyphVector glyphVectorRight = new Font("宋体", Font.PLAIN, fontsize) .createGlyphVector(new FontRenderContext(null, true, true), offText); Shape shapeTextRight = glyphVectorRight.getOutline( -(float) (glyphVectorRight.getVisualBounds().getMinX() + d3 - (getWidth() * 3 / 2 - glyphVectorRight.getVisualBounds().getWidth()) / 2 + 1), -(float) (glyphVectorRight.getVisualBounds().getMinY() - (getHeight() - glyphVectorRight.getVisualBounds().getHeight()) / 2) - 1); g2d.setColor(new Color(23, 54, 93)); g2d.draw(shapeTextLeft); g2d.setColor(new Color(23, 54, 93)); g2d.draw(shapeTextRight); }
// Triangulate glyph with 'unicode' if not already done. GeometryArrayRetained triangulateGlyphs(GlyphVector gv, char c) { Character ch = new Character(c); GeometryArrayRetained geo = geomHash.get(ch); if (geo == null) { // Font Y-axis is downwards, so send affine transform to flip it. Rectangle2D bnd = gv.getVisualBounds(); AffineTransform aTran = new AffineTransform(); double tx = bnd.getX() + 0.5 * bnd.getWidth(); double ty = bnd.getY() + 0.5 * bnd.getHeight(); aTran.setToTranslation(-tx, -ty); aTran.scale(1.0, -1.0); aTran.translate(tx, -ty); Shape shape = gv.getOutline(); PathIterator pIt = shape.getPathIterator(aTran, tessellationTolerance); int flag = -1, numContours = 0, numPoints = 0, i, j, k, num = 0, vertCnt; UnorderList coords = new UnorderList(100, Point3f.class); float tmpCoords[] = new float[6]; float lastX = .0f, lastY = .0f; float firstPntx = Float.MAX_VALUE, firstPnty = Float.MAX_VALUE; GeometryInfo gi = null; NormalGenerator ng = new NormalGenerator(); FastVector contours = new FastVector(10); float maxY = -Float.MAX_VALUE; int maxYIndex = 0, beginIdx = 0, endIdx = 0, start = 0; boolean setMaxY = false; while (!pIt.isDone()) { Point3f vertex = new Point3f(); flag = pIt.currentSegment(tmpCoords); if (flag == PathIterator.SEG_CLOSE) { if (num > 0) { if (setMaxY) { // Get Previous point beginIdx = start; endIdx = numPoints - 1; } contours.addElement(num); num = 0; numContours++; } } else if (flag == PathIterator.SEG_MOVETO) { vertex.x = tmpCoords[0]; vertex.y = tmpCoords[1]; lastX = vertex.x; lastY = vertex.y; if ((lastX == firstPntx) && (lastY == firstPnty)) { pIt.next(); continue; } setMaxY = false; coords.add(vertex); firstPntx = lastX; firstPnty = lastY; if (num > 0) { contours.addElement(num); num = 0; numContours++; } num++; numPoints++; // skip checking of first point, // since the last point will repeat this. start = numPoints; } else if (flag == PathIterator.SEG_LINETO) { vertex.x = tmpCoords[0]; vertex.y = tmpCoords[1]; // Check here for duplicate points. Code // later in this function can not handle // duplicate points. if ((vertex.x == lastX) && (vertex.y == lastY)) { pIt.next(); continue; } if (vertex.y > maxY) { maxY = vertex.y; maxYIndex = numPoints; setMaxY = true; } lastX = vertex.x; lastY = vertex.y; coords.add(vertex); num++; numPoints++; } pIt.next(); } // No data(e.g space, control characters) // Two point can't form a valid contour if (numPoints == 0) { return null; } // Determine font winding order use for side triangles Point3f p1 = new Point3f(), p2 = new Point3f(), p3 = new Point3f(); boolean flip_side_orient = true; Point3f vertices[] = (Point3f[]) coords.toArray(false); if (endIdx - beginIdx > 0) { // must be true unless it is a single line // define as "MoveTo p1 LineTo p2 Close" which is // not a valid font definition. if (maxYIndex == beginIdx) { p1.set(vertices[endIdx]); } else { p1.set(vertices[maxYIndex - 1]); } p2.set(vertices[maxYIndex]); if (maxYIndex == endIdx) { p3.set(vertices[beginIdx]); } else { p3.set(vertices[maxYIndex + 1]); } if (p3.x != p2.x) { if (p1.x != p2.x) { // Use the one with smallest slope if (Math.abs((p2.y - p1.y) / (p2.x - p1.x)) > Math.abs((p3.y - p2.y) / (p3.x - p2.x))) { flip_side_orient = (p3.x > p2.x); } else { flip_side_orient = (p2.x > p1.x); } } else { flip_side_orient = (p3.x > p2.x); } } else { // p1.x != p2.x, otherwise all three // point form a straight vertical line with // the middle point the highest. This is not a // valid font definition. flip_side_orient = (p2.x > p1.x); } } // Build a Tree of Islands int startIdx = 0; IslandsNode islandsTree = new IslandsNode(-1, -1); int contourCounts[] = contours.getData(); for (i = 0; i < contours.getSize(); i++) { endIdx = startIdx + contourCounts[i]; islandsTree.insert(new IslandsNode(startIdx, endIdx), vertices); startIdx = endIdx; } coords = null; // Free memory contours = null; contourCounts = null; // Compute islandCounts[][] and outVerts[][] UnorderList islandsList = new UnorderList(10, IslandsNode.class); islandsTree.collectOddLevelNode(islandsList, 0); IslandsNode nodes[] = (IslandsNode[]) islandsList.toArray(false); int islandCounts[][] = new int[islandsList.arraySize()][]; Point3f outVerts[][] = new Point3f[islandCounts.length][]; int nchild, sum; IslandsNode node; for (i = 0; i < islandCounts.length; i++) { node = nodes[i]; nchild = node.numChild(); islandCounts[i] = new int[nchild + 1]; islandCounts[i][0] = node.numVertices(); sum = 0; sum += islandCounts[i][0]; for (j = 0; j < nchild; j++) { islandCounts[i][j + 1] = node.getChild(j).numVertices(); sum += islandCounts[i][j + 1]; } outVerts[i] = new Point3f[sum]; startIdx = 0; for (k = node.startIdx; k < node.endIdx; k++) { outVerts[i][startIdx++] = vertices[k]; } for (j = 0; j < nchild; j++) { endIdx = node.getChild(j).endIdx; for (k = node.getChild(j).startIdx; k < endIdx; k++) { outVerts[i][startIdx++] = vertices[k]; } } } islandsTree = null; // Free memory islandsList = null; vertices = null; contourCounts = new int[1]; int currCoordIndex = 0, vertOffset = 0; ArrayList<GeometryArray> triangData = new ArrayList<GeometryArray>(); Point3f q1 = new Point3f(), q2 = new Point3f(), q3 = new Point3f(); Vector3f n1 = new Vector3f(), n2 = new Vector3f(); numPoints = 0; // Now loop thru each island, calling triangulator once per island. // Combine triangle data for all islands together in one object. for (i = 0; i < islandCounts.length; i++) { contourCounts[0] = islandCounts[i].length; numPoints += outVerts[i].length; gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY); gi.setCoordinates(outVerts[i]); gi.setStripCounts(islandCounts[i]); gi.setContourCounts(contourCounts); ng.generateNormals(gi); GeometryArray ga = gi.getGeometryArray(false, false, false); vertOffset += ga.getVertexCount(); triangData.add(ga); } // Multiply by 2 since we create 2 faces of the font // Second term is for side-faces along depth of the font if (fontExtrusion == null) vertCnt = vertOffset; else { if (fontExtrusion.shape == null) vertCnt = vertOffset * 2 + numPoints * 6; else { vertCnt = vertOffset * 2 + numPoints * 6 * (fontExtrusion.pnts.length - 1); } } // XXXX: Should use IndexedTriangleArray to avoid // duplication of vertices. To create triangles for // side faces, every vertex is duplicated currently. TriangleArray triAry = new TriangleArray(vertCnt, GeometryArray.COORDINATES | GeometryArray.NORMALS); boolean flip_orient[] = new boolean[islandCounts.length]; boolean findOrient; // last known non-degenerate normal Vector3f goodNormal = new Vector3f(); for (j = 0; j < islandCounts.length; j++) { GeometryArray ga = triangData.get(j); vertOffset = ga.getVertexCount(); findOrient = false; // Create the triangle array for (i = 0; i < vertOffset; i += 3, currCoordIndex += 3) { // Get 3 points. Since triangle is known to be flat, normal // must be same for all 3 points. ga.getCoordinate(i, p1); ga.getNormal(i, n1); ga.getCoordinate(i + 1, p2); ga.getCoordinate(i + 2, p3); if (!findOrient) { // Check here if triangles are wound incorrectly and need // to be flipped. if (!getNormal(p1, p2, p3, n2)) { continue; } if (n2.z >= EPS) { flip_orient[j] = false; } else if (n2.z <= -EPS) { flip_orient[j] = true; } else { continue; } findOrient = true; } if (flip_orient[j]) { // New Triangulator preserves contour orientation. If contour // input is wound incorrectly, swap 2nd and 3rd points to // sure all triangles are wound correctly for j3d. q1.x = p2.x; q1.y = p2.y; q1.z = p2.z; p2.x = p3.x; p2.y = p3.y; p2.z = p3.z; p3.x = q1.x; p3.y = q1.y; p3.z = q1.z; n1.x = -n1.x; n1.y = -n1.y; n1.z = -n1.z; } if (fontExtrusion != null) { n2.x = -n1.x; n2.y = -n1.y; n2.z = -n1.z; triAry.setCoordinate(currCoordIndex, p1); triAry.setNormal(currCoordIndex, n2); triAry.setCoordinate(currCoordIndex + 1, p3); triAry.setNormal(currCoordIndex + 1, n2); triAry.setCoordinate(currCoordIndex + 2, p2); triAry.setNormal(currCoordIndex + 2, n2); q1.x = p1.x; q1.y = p1.y; q1.z = p1.z + fontExtrusion.length; q2.x = p2.x; q2.y = p2.y; q2.z = p2.z + fontExtrusion.length; q3.x = p3.x; q3.y = p3.y; q3.z = p3.z + fontExtrusion.length; triAry.setCoordinate(currCoordIndex + vertOffset, q1); triAry.setNormal(currCoordIndex + vertOffset, n1); triAry.setCoordinate(currCoordIndex + 1 + vertOffset, q2); triAry.setNormal(currCoordIndex + 1 + vertOffset, n1); triAry.setCoordinate(currCoordIndex + 2 + vertOffset, q3); triAry.setNormal(currCoordIndex + 2 + vertOffset, n1); } else { triAry.setCoordinate(currCoordIndex, p1); triAry.setNormal(currCoordIndex, n1); triAry.setCoordinate(currCoordIndex + 1, p2); triAry.setNormal(currCoordIndex + 1, n1); triAry.setCoordinate(currCoordIndex + 2, p3); triAry.setNormal(currCoordIndex + 2, n1); } } if (fontExtrusion != null) { currCoordIndex += vertOffset; } } // Now add side triangles in both cases. // Since we duplicated triangles with different Z, make sure // currCoordIndex points to correct location. if (fontExtrusion != null) { if (fontExtrusion.shape == null) { boolean smooth; // we'll put a crease if the angle between the normals is // greater than 44 degrees float threshold = (float) Math.cos(44.0 * Math.PI / 180.0); float cosine; // need the previous normals to check for smoothing Vector3f pn1 = null, pn2 = null; // need the next normals to check for smoothing Vector3f n3 = new Vector3f(), n4 = new Vector3f(); // store the normals for each point because they are // the same for both triangles Vector3f p1Normal = new Vector3f(); Vector3f p2Normal = new Vector3f(); Vector3f p3Normal = new Vector3f(); Vector3f q1Normal = new Vector3f(); Vector3f q2Normal = new Vector3f(); Vector3f q3Normal = new Vector3f(); for (i = 0; i < islandCounts.length; i++) { for (j = 0, k = 0, num = 0; j < islandCounts[i].length; j++) { num += islandCounts[i][j]; p1.x = outVerts[i][num - 1].x; p1.y = outVerts[i][num - 1].y; p1.z = 0.0f; q1.x = p1.x; q1.y = p1.y; q1.z = p1.z + fontExtrusion.length; p2.z = 0.0f; q2.z = p2.z + fontExtrusion.length; for (int m = 0; m < num; m++) { p2.x = outVerts[i][m].x; p2.y = outVerts[i][m].y; q2.x = p2.x; q2.y = p2.y; if (getNormal(p1, q1, p2, n1)) { if (!flip_side_orient) { n1.negate(); } goodNormal.set(n1); break; } } for (; k < num; k++) { p2.x = outVerts[i][k].x; p2.y = outVerts[i][k].y; p2.z = 0.0f; q2.x = p2.x; q2.y = p2.y; q2.z = p2.z + fontExtrusion.length; if (!getNormal(p1, q1, p2, n1)) { n1.set(goodNormal); } else { if (!flip_side_orient) { n1.negate(); } goodNormal.set(n1); } if (!getNormal(p2, q1, q2, n2)) { n2.set(goodNormal); } else { if (!flip_side_orient) { n2.negate(); } goodNormal.set(n2); } // if there is a previous normal, see if we need to smooth // this normal or make a crease if (pn1 != null) { cosine = n1.dot(pn2); smooth = cosine > threshold; if (smooth) { p1Normal.x = (pn1.x + pn2.x + n1.x); p1Normal.y = (pn1.y + pn2.y + n1.y); p1Normal.z = (pn1.z + pn2.z + n1.z); normalize(p1Normal); q1Normal.x = (pn2.x + n1.x + n2.x); q1Normal.y = (pn2.y + n1.y + n2.y); q1Normal.z = (pn2.z + n1.z + n2.z); normalize(q1Normal); } // if smooth else { p1Normal.x = n1.x; p1Normal.y = n1.y; p1Normal.z = n1.z; q1Normal.x = n1.x + n2.x; q1Normal.y = n1.y + n2.y; q1Normal.z = n1.z + n2.z; normalize(q1Normal); } // else } // if pn1 != null else { pn1 = new Vector3f(); pn2 = new Vector3f(); p1Normal.x = n1.x; p1Normal.y = n1.y; p1Normal.z = n1.z; q1Normal.x = (n1.x + n2.x); q1Normal.y = (n1.y + n2.y); q1Normal.z = (n1.z + n2.z); normalize(q1Normal); } // else // if there is a next, check if we should smooth normal if (k + 1 < num) { p3.x = outVerts[i][k + 1].x; p3.y = outVerts[i][k + 1].y; p3.z = 0.0f; q3.x = p3.x; q3.y = p3.y; q3.z = p3.z + fontExtrusion.length; if (!getNormal(p2, q2, p3, n3)) { n3.set(goodNormal); } else { if (!flip_side_orient) { n3.negate(); } goodNormal.set(n3); } if (!getNormal(p3, q2, q3, n4)) { n4.set(goodNormal); } else { if (!flip_side_orient) { n4.negate(); } goodNormal.set(n4); } cosine = n2.dot(n3); smooth = cosine > threshold; if (smooth) { p2Normal.x = (n1.x + n2.x + n3.x); p2Normal.y = (n1.y + n2.y + n3.y); p2Normal.z = (n1.z + n2.z + n3.z); normalize(p2Normal); q2Normal.x = (n2.x + n3.x + n4.x); q2Normal.y = (n2.y + n3.y + n4.y); q2Normal.z = (n2.z + n3.z + n4.z); normalize(q2Normal); } else { // if smooth p2Normal.x = n1.x + n2.x; p2Normal.y = n1.y + n2.y; p2Normal.z = n1.z + n2.z; normalize(p2Normal); q2Normal.x = n2.x; q2Normal.y = n2.y; q2Normal.z = n2.z; } // else } else { // if k+1 < num p2Normal.x = (n1.x + n2.x); p2Normal.y = (n1.y + n2.y); p2Normal.z = (n1.z + n2.z); normalize(p2Normal); q2Normal.x = n2.x; q2Normal.y = n2.y; q2Normal.z = n2.z; } // else // add pts for the 2 tris // p1, q1, p2 and p2, q1, q2 if (flip_side_orient) { triAry.setCoordinate(currCoordIndex, p1); triAry.setNormal(currCoordIndex, p1Normal); currCoordIndex++; triAry.setCoordinate(currCoordIndex, q1); triAry.setNormal(currCoordIndex, q1Normal); currCoordIndex++; triAry.setCoordinate(currCoordIndex, p2); triAry.setNormal(currCoordIndex, p2Normal); currCoordIndex++; triAry.setCoordinate(currCoordIndex, p2); triAry.setNormal(currCoordIndex, p2Normal); currCoordIndex++; triAry.setCoordinate(currCoordIndex, q1); triAry.setNormal(currCoordIndex, q1Normal); currCoordIndex++; } else { triAry.setCoordinate(currCoordIndex, q1); triAry.setNormal(currCoordIndex, q1Normal); currCoordIndex++; triAry.setCoordinate(currCoordIndex, p1); triAry.setNormal(currCoordIndex, p1Normal); currCoordIndex++; triAry.setCoordinate(currCoordIndex, p2); triAry.setNormal(currCoordIndex, p2Normal); currCoordIndex++; triAry.setCoordinate(currCoordIndex, q1); triAry.setNormal(currCoordIndex, q1Normal); currCoordIndex++; triAry.setCoordinate(currCoordIndex, p2); triAry.setNormal(currCoordIndex, p2Normal); currCoordIndex++; } triAry.setCoordinate(currCoordIndex, q2); triAry.setNormal(currCoordIndex, q2Normal); currCoordIndex++; pn1.x = n1.x; pn1.y = n1.y; pn1.z = n1.z; pn2.x = n2.x; pn2.y = n2.y; pn2.z = n2.z; p1.x = p2.x; p1.y = p2.y; p1.z = p2.z; q1.x = q2.x; q1.y = q2.y; q1.z = q2.z; } // for k // set the previous normals to null when we are done pn1 = null; pn2 = null; } // for j } // for i } else { // if shape int m, offset = 0; Point3f P2 = new Point3f(), Q2 = new Point3f(), P1 = new Point3f(); Vector3f nn = new Vector3f(), nn1 = new Vector3f(), nn2 = new Vector3f(), nn3 = new Vector3f(); Vector3f nna = new Vector3f(), nnb = new Vector3f(); float length; boolean validNormal = false; // fontExtrusion.shape is specified, and is NOT straight line for (i = 0; i < islandCounts.length; i++) { for (j = 0, k = 0, offset = num = 0; j < islandCounts[i].length; j++) { num += islandCounts[i][j]; p1.x = outVerts[i][num - 1].x; p1.y = outVerts[i][num - 1].y; p1.z = 0.0f; q1.x = p1.x; q1.y = p1.y; q1.z = p1.z + fontExtrusion.length; p3.z = 0.0f; for (m = num - 2; m >= 0; m--) { p3.x = outVerts[i][m].x; p3.y = outVerts[i][m].y; if (getNormal(p3, q1, p1, nn1)) { if (!flip_side_orient) { nn1.negate(); } goodNormal.set(nn1); break; } } for (; k < num; k++) { p2.x = outVerts[i][k].x; p2.y = outVerts[i][k].y; p2.z = 0.0f; q2.x = p2.x; q2.y = p2.y; q2.z = p2.z + fontExtrusion.length; getNormal(p1, q1, p2, nn2); p3.x = outVerts[i][(k + 1) == num ? offset : (k + 1)].x; p3.y = outVerts[i][(k + 1) == num ? offset : (k + 1)].y; p3.z = 0.0f; if (!getNormal(p3, p2, q2, nn3)) { nn3.set(goodNormal); } else { if (!flip_side_orient) { nn3.negate(); } goodNormal.set(nn3); } // Calculate normals at the point by averaging normals // of two faces on each side of the point. nna.x = (nn1.x + nn2.x); nna.y = (nn1.y + nn2.y); nna.z = (nn1.z + nn2.z); normalize(nna); nnb.x = (nn3.x + nn2.x); nnb.y = (nn3.y + nn2.y); nnb.z = (nn3.z + nn2.z); normalize(nnb); P1.x = p1.x; P1.y = p1.y; P1.z = p1.z; P2.x = p2.x; P2.y = p2.y; P2.z = p2.z; Q2.x = q2.x; Q2.y = q2.y; Q2.z = q2.z; for (m = 1; m < fontExtrusion.pnts.length; m++) { q1.z = q2.z = fontExtrusion.pnts[m].x; q1.x = P1.x + nna.x * fontExtrusion.pnts[m].y; q1.y = P1.y + nna.y * fontExtrusion.pnts[m].y; q2.x = P2.x + nnb.x * fontExtrusion.pnts[m].y; q2.y = P2.y + nnb.y * fontExtrusion.pnts[m].y; if (!getNormal(p1, q1, p2, n1)) { n1.set(goodNormal); } else { if (!flip_side_orient) { n1.negate(); } goodNormal.set(n1); } if (flip_side_orient) { triAry.setCoordinate(currCoordIndex, p1); triAry.setNormal(currCoordIndex, n1); currCoordIndex++; triAry.setCoordinate(currCoordIndex, q1); triAry.setNormal(currCoordIndex, n1); currCoordIndex++; } else { triAry.setCoordinate(currCoordIndex, q1); triAry.setNormal(currCoordIndex, n1); currCoordIndex++; triAry.setCoordinate(currCoordIndex, p1); triAry.setNormal(currCoordIndex, n1); currCoordIndex++; } triAry.setCoordinate(currCoordIndex, p2); triAry.setNormal(currCoordIndex, n1); currCoordIndex++; if (!getNormal(p2, q1, q2, n1)) { n1.set(goodNormal); } else { if (!flip_side_orient) { n1.negate(); } goodNormal.set(n1); } if (flip_side_orient) { triAry.setCoordinate(currCoordIndex, p2); triAry.setNormal(currCoordIndex, n1); currCoordIndex++; triAry.setCoordinate(currCoordIndex, q1); triAry.setNormal(currCoordIndex, n1); currCoordIndex++; } else { triAry.setCoordinate(currCoordIndex, q1); triAry.setNormal(currCoordIndex, n1); currCoordIndex++; triAry.setCoordinate(currCoordIndex, p2); triAry.setNormal(currCoordIndex, n1); currCoordIndex++; } triAry.setCoordinate(currCoordIndex, q2); triAry.setNormal(currCoordIndex, n1); currCoordIndex++; p1.x = q1.x; p1.y = q1.y; p1.z = q1.z; p2.x = q2.x; p2.y = q2.y; p2.z = q2.z; } // for m p1.x = P2.x; p1.y = P2.y; p1.z = P2.z; q1.x = Q2.x; q1.y = Q2.y; q1.z = Q2.z; nn1.x = nn2.x; nn1.y = nn2.y; nn1.z = nn2.z; } // for k offset = num; } // for j } // for i } // if shape } // if fontExtrusion geo = (GeometryArrayRetained) triAry.retained; geomHash.put(ch, geo); } return geo; }
/** @see Graphics2D#drawGlyphVector(GlyphVector, float, float) */ public void drawGlyphVector(GlyphVector g, float x, float y) { Shape s = g.getOutline(x, y); fill(s); }
private TessOutput tesselateString(String s) { GlyphVector gv = _font.createGlyphVector(_frc, s); Shape shape = gv.getOutline(); // AffineTransform at = new AffineTransform(); at.scale(1, -1); PathIterator pIt = shape.getPathIterator(at, _font.getSize() / 200.0); // Create a GLU tesselator GLUtessellator tess = GLU.gluNewTess(); CharTesselator tessAdapt = new CharTesselator(); GLU.gluTessCallback(tess, GLU.GLU_TESS_VERTEX, tessAdapt); GLU.gluTessCallback(tess, GLU.GLU_TESS_BEGIN, tessAdapt); GLU.gluTessCallback(tess, GLU.GLU_TESS_END, tessAdapt); GLU.gluTessCallback(tess, GLU.GLU_TESS_COMBINE, tessAdapt); GLU.gluTessCallback(tess, GLU.GLU_TESS_ERROR, tessAdapt); int winding = pIt.getWindingRule(); if (winding == PathIterator.WIND_EVEN_ODD) GLU.gluTessProperty(tess, GLU.GLU_TESS_WINDING_RULE, GLU.GLU_TESS_WINDING_ODD); else if (winding == PathIterator.WIND_NON_ZERO) GLU.gluTessProperty(tess, GLU.GLU_TESS_WINDING_RULE, GLU.GLU_TESS_WINDING_NONZERO); else assert (false); // PathIterator should only return these two winding rules GLU.gluBeginPolygon(tess); GLU.gluTessNormal(tess, 0, 0, 1); double[] first = null; double[] v; while (!pIt.isDone()) { v = new double[3]; int type = pIt.currentSegment(v); v[2] = 0.0; if (type == PathIterator.SEG_MOVETO) { first = v; GLU.gluNextContour(tess, GLU.GLU_UNKNOWN); GLU.gluTessVertex(tess, v, 0, v); } else if (type == PathIterator.SEG_LINETO) { GLU.gluTessVertex(tess, v, 0, v); } else if (type == PathIterator.SEG_CLOSE) { assert (first != null); // If this is true, there is an error in the AWT path iterator GLU.gluTessVertex(tess, first, 0, first); first = null; } else { assert (false); // The path itertor should not return other path types here } pIt.next(); } GLU.gluEndPolygon(tess); int numVerts = tessAdapt.getVerts().size(); double[] verts = new double[numVerts]; int count = 0; for (double d : tessAdapt.getVerts()) { verts[count++] = d; } TessOutput ret = new TessOutput(); ret.verts = verts; ret.bounds = gv.getVisualBounds(); ret.advances = new double[s.length()]; for (int i = 0; i < s.length(); ++i) { ret.advances[i] = gv.getGlyphMetrics(i).getAdvance(); } return ret; }
static void renderTextWithJavaFonts( GraphicsState gs, DynamicVectorRenderer current, int streamType, ParserOptions parserOptions, PdfFont currentFontData, GlyphData glyphData, final int Tmode, final float currentWidth, final boolean isTextShifted, final PdfJavaGlyphs glyphs, final float[][] Trm) { final float actualWidth = glyphData.getActualWidth(); /** set values used if rendering as well */ Object transformedGlyph2; AffineTransform glyphAt = null; final int rawInt = glyphData.getRawInt(); // { // render now final boolean isSTD = actualWidth > 0 || DecoderOptions.isRunningOnMac || streamType == ValueTypes.FORM || StandardFonts.isStandardFont(currentFontData.getBaseFontName(), false) || currentFontData.isBrokenFont(); /** flush cache if needed */ // if(!DynamicVectorRenderer.newCode2){ if (glyphs.lastTrm[0][0] != Trm[0][0] || glyphs.lastTrm[1][0] != Trm[1][0] || glyphs.lastTrm[0][1] != Trm[0][1] || glyphs.lastTrm[1][1] != Trm[1][1]) { glyphs.lastTrm = Trm; glyphs.flush(); } // } // either calculate the glyph to draw or reuse if already drawn Area glyph = glyphs.getCachedShape(rawInt); glyphAt = glyphs.getCachedTransform(rawInt); if (glyph == null) { double dY = -1, dX = 1, x3 = 0, y3 = 0; // allow for text running up the page if ((Trm[1][0] < 0 && Trm[0][1] >= 0) || (Trm[0][1] < 0 && Trm[1][0] >= 0)) { dX = 1f; dY = -1f; } if (isSTD) { glyph = glyphs.getGlyph(rawInt, glyphData.getDisplayValue(), currentWidth); // hack to fix problem with Java Arial font if (glyph != null && rawInt == 146 && glyphs.isArialInstalledLocally) { y3 = -(glyph.getBounds().height - glyph.getBounds().y); } } else { // remap font if needed String xx = glyphData.getDisplayValue(); GlyphVector gv1 = null; // do not show CID fonts as Lucida unless match if (!glyphs.isCIDFont || glyphs.isCorrupted() || glyphs.isFontInstalled) { gv1 = glyphs.getUnscaledFont().createGlyphVector(PdfJavaGlyphs.frc, xx); } if (gv1 != null) { glyph = new Area(gv1.getOutline()); // put glyph into display position double glyphX = gv1.getOutline().getBounds2D().getX(); // ensure inside box x3 = 0; if (glyphX < 0) { glyphX = -glyphX; x3 = glyphX * 2; // System.out.println(x3+" "+displayTrm[0][0]+" "+displayTrm[0][0]); if (Trm[0][0] > Trm[0][1]) { x3 *= Trm[0][0]; } else { x3 *= Trm[0][1]; } // glyphAt =AffineTransform.getTranslateInstance(x3,0); } final double glyphWidth = gv1.getVisualBounds().getWidth() + (glyphX * 2); final double scaleFactor = currentWidth / glyphWidth; if (scaleFactor < 1) { dX *= scaleFactor; } if (x3 > 0) { x3 *= dX; } } } glyphAt = new AffineTransform( dX * Trm[0][0], dX * Trm[0][1], dY * Trm[1][0], dY * Trm[1][1], x3, y3); // create shape for text using transformation to make correct size // glyphAt =new AffineTransform(dX* displayTrm[0][0],dX* displayTrm[0][1],dY* // displayTrm[1][0],dY* displayTrm[1][1] ,x3, y3); // save so we can reuse if it occurs again in this TJ command glyphs.setCachedShape(rawInt, glyph, glyphAt); } if (glyph != null && Tmode == GraphicsState.CLIPTEXT && glyph.getBounds().width > 0) { /** support for TR7 */ final Area glyphShape = (Area) glyph.clone(); // we need to apply to make it all work glyphShape.transform(glyphAt); // if its already generated we just need to move it if (parserOptions.renderDirectly()) { final AffineTransform at2 = AffineTransform.getTranslateInstance(Trm[2][0], (Trm[2][1])); glyphShape.transform(at2); } gs.addClip(glyphShape); // current.drawClip(gs,null,false); // if(parserOptions.renderDirectly()) { glyph = null; // } } transformedGlyph2 = glyph; } if (transformedGlyph2 != null) { final double[] textTrans = new double[6]; glyphAt.getMatrix(textTrans); final int fontSize = glyphData.getFontSize(); if (parserOptions.useJavaFX()) { current.drawEmbeddedText( Trm, fontSize, null, null, DynamicVectorRenderer.TEXT, gs, textTrans, glyphData.getUnicodeValue(), currentFontData, -100); } else // add to renderer if (parserOptions.renderDirectly()) { current.drawEmbeddedText( Trm, fontSize, null, transformedGlyph2, DynamicVectorRenderer.TEXT, gs, textTrans, glyphData.getUnicodeValue(), currentFontData, -100); } else { if (isTextShifted) { current.drawEmbeddedText( Trm, -fontSize, null, transformedGlyph2, DynamicVectorRenderer.TEXT, gs, null, glyphData.getUnicodeValue(), currentFontData, -100); } else { current.drawEmbeddedText( Trm, fontSize, null, transformedGlyph2, DynamicVectorRenderer.TEXT, gs, null, glyphData.getUnicodeValue(), currentFontData, -100); } } } }
@Override protected void createChars(final BufferedImage theCharImage, final Graphics2D theGraphics) { _myTesselator = new CCVectorFontTesselator(); // six element array received from the Java2D path iterator float textPoints[] = new float[6]; // array passed to createGylphVector char textArray[] = new char[1]; final Graphics2D myGraphics = theGraphics; final FontRenderContext frc = myGraphics.getFontRenderContext(); int index = 0; for (int i = 0; i < _myCharCount; i++) { char c = _myCharSet.chars()[i]; if (!_myFont.canDisplay(c) || _myFontMetrics.charWidth(c) <= 0) { continue; } if (c < 128) { _myAsciiLookUpTable[c] = index; } _myCharCodes[index] = c; textArray[0] = c; final GlyphVector myGlyphVector = _myFont.createGlyphVector(frc, textArray); final Shape myShape = myGlyphVector.getOutline(); final PathIterator myPathIterator = myShape.getPathIterator(null, 0.05); final CC3DChar my3DChar = new CC3DChar(c, myGlyphVector.getGlyphCode(0), charWidth(c), height(), _mySize, _myDepth); _myTesselator.beginPolygon(my3DChar); float lastX = 0; float lastY = 0; while (!myPathIterator.isDone()) { int type = myPathIterator.currentSegment(textPoints); switch (type) { case PathIterator.SEG_MOVETO: // 1 point (2 vars) in textPoints _myTesselator.beginContour(); my3DChar.beginPath(); case PathIterator.SEG_LINETO: // 1 point _myTesselator.vertex(textPoints[0], textPoints[1] + _myFontMetrics.getAscent(), 0); my3DChar.addOutlineVertex(textPoints[0], textPoints[1] + _myFontMetrics.getAscent()); lastX = textPoints[0]; lastY = textPoints[1]; break; case PathIterator.SEG_QUADTO: // 2 points for (int j = 1; j < _myBezierDetail; j++) { float t = (float) j / _myBezierDetail; _myTesselator.vertex( CCMath.bezierPoint(lastX, textPoints[0], textPoints[2], textPoints[2], t), CCMath.bezierPoint(lastY, textPoints[1], textPoints[3], textPoints[3], t) + _myFontMetrics.getAscent(), 0); my3DChar.addOutlineVertex( CCMath.bezierPoint(lastX, textPoints[0], textPoints[2], textPoints[2], t), CCMath.bezierPoint(lastY, textPoints[1], textPoints[3], textPoints[3], t) + _myFontMetrics.getAscent()); } lastX = textPoints[2]; lastY = textPoints[3]; break; case PathIterator.SEG_CUBICTO: // 3 points for (int j = 1; j < _myBezierDetail; j++) { float t = (float) j / _myBezierDetail; _myTesselator.vertex( CCMath.bezierPoint(lastX, textPoints[0], textPoints[2], textPoints[4], t), CCMath.bezierPoint(lastY, textPoints[1], textPoints[3], textPoints[5], t) + _myFontMetrics.getAscent(), 0); my3DChar.addOutlineVertex( CCMath.bezierPoint(lastX, textPoints[0], textPoints[2], textPoints[4], t), CCMath.bezierPoint(lastY, textPoints[1], textPoints[3], textPoints[5], t) + _myFontMetrics.getAscent()); } lastX = textPoints[4]; lastY = textPoints[5]; break; case PathIterator.SEG_CLOSE: _myTesselator.endContour(); my3DChar.endPath(); break; } myPathIterator.next(); } _myTesselator.endPolygon(); _myChars[index] = my3DChar; index++; } }