文章目录
1.引入字体
2.Windows环境下
如果在Windows上安装JAVA环境时,没有安装单独的jre1.8.0_141的话。那么字体就只放到\jdk1.8.0_141\jre\lib\fonts目前下。
3. Linux环境下
cat /etc/profile
找到上方的JAVA_HOME路径
root@iZ2zeddfx87fxxx4m4dlxu8dZ fonts\]# pwd #进入以下目录下 /home/jdk/jdk1.8.0_181/jre/lib/fonts \[root@iZ2zeddfx87fxxx4m4dlxu8dZ fonts\]# mkdir fallback #创建一个空目录,用于放置外部字体  然后执行 \[root@iZ2zeddfx87fw4m4dlxu8dZ fallback\]# mkfontscale #不知道不执行是否可以 \[root@iZ2zeddfx87fw4m4dlxu8dZ fallback\]# mkfontdir #这个必须执行  这样就在fallback目录下多出了两个文件。注意不执行上方的mkfontscale和mkfontdir。即使把字体放到了指定的fonts目录下了。在CentOS下使用字体也会找不到的。会使用其他默认字体代替。这一点和Window上的不同。 我们可以通过比较两者fonts目录下的文件可以发现。  ## 生成印章 Seal类代码: ```java package cn.com.yuanquanyun.bjca_scan; import lombok.Builder; import lombok.Getter; import org.springframework.util.ResourceUtils; import javax.imageio.ImageIO; import java.awt.*; import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @Getter @Builder public class Seal { // 起始位置 private static final int INIT_BEGIN = 5; // 尺寸 private Integer size; // 颜色 private Color color; // 主字 private SealFont mainFont; // 副字 private SealFont viceFont; // 抬头 private SealFont titleFont; // 中心字 private SealFont centerFont; // 边线圆 private SealCircle borderCircle; // 内边线圆 private SealCircle borderInnerCircle; // 内线圆 private SealCircle innerCircle; // 边线框 private Integer borderSquare; // 加字 private String stamp; /** * 画公章 */ public byte[] draw(String imgFmt) throws Exception { if (borderSquare != null) { return draw2(imgFmt); // 画私章 } //1.画布 BufferedImage bi = new BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR); //2.画笔 Graphics2D g2d = bi.createGraphics(); //2.1抗锯齿设置 //文本不抗锯齿,否则圆中心的文字会被拉长 RenderingHints hints = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); //其他图形抗锯齿 hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHints(hints); //2.2设置背景透明度 g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0)); //2.3填充矩形 g2d.fillRect(0, 0, size, size); //2.4重设透明度,开始画图 g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 1)); //2.5设置画笔颜色 g2d.setPaint(color == null ? Color.RED : color); //3.画边线圆 if (borderCircle != null) { drawCircle(g2d, borderCircle, INIT_BEGIN, INIT_BEGIN); } else { throw new Exception("BorderCircle can not null!"); } int borderCircleWidth = borderCircle.getWidth(); int borderCircleHeight = borderCircle.getHeight(); //4.画内边线圆 if (borderInnerCircle != null) { int x = INIT_BEGIN + borderCircleWidth - borderInnerCircle.getWidth(); int y = INIT_BEGIN + borderCircleHeight - borderInnerCircle.getHeight(); drawCircle(g2d, borderInnerCircle, x, y); } //5.画内环线圆 if (innerCircle != null) { int x = INIT_BEGIN + borderCircleWidth - innerCircle.getWidth(); int y = INIT_BEGIN + borderCircleHeight - innerCircle.getHeight(); drawCircle(g2d, innerCircle, x, y); } //6.画弧形主文字 if (borderCircleHeight != borderCircleWidth) { drawArcFont4Oval(g2d, borderCircle, mainFont, true); } else { drawArcFont4Circle(g2d, borderCircleHeight, mainFont, true); } //7.画弧形副文字 if (borderCircleHeight != borderCircleWidth) { drawArcFont4Oval(g2d, borderCircle, viceFont, false); } else { drawArcFont4Circle(g2d, borderCircleHeight, viceFont, false); } //8.画中心字 drawFont(g2d, (borderCircleWidth + INIT_BEGIN) * 2, (borderCircleHeight + INIT_BEGIN) * 2, centerFont); //9.画抬头文字 drawFont(g2d, (borderCircleWidth + INIT_BEGIN) * 2, (borderCircleHeight + INIT_BEGIN) * 2, titleFont); g2d.dispose(); // ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); boolean png = ImageIO.write(bi, imgFmt, outputStream); byte[] imgBytes = outputStream.toByteArray(); outputStream.flush(); outputStream.close(); return imgBytes; } public byte[] draw(String pngPath,String imgFmt) throws Exception {//如果只是生成印章字节数组,不进行输出的话,传入的pngPath,无用 byte[] imgBytes = draw(imgFmt); return imgBytes; // FileOutputStream fileOutputStream = new FileOutputStream(new File(pngPath)); // fileOutputStream.write(imgBytes); } /** * 绘制圆弧形文字 */ private static void drawArcFont4Circle(Graphics2D g2d, int circleRadius, SealFont font, boolean isTop) throws IOException, FontFormatException { if (font == null) { return; } //1.字体长度 int textLen = font.getText().length(); //2.字体大小,默认根据字体长度动态设定 int size = font.getSize() == null ? (55 - textLen * 2) : font.getSize(); //3.字体样式 int style = font.getBold() ? Font.BOLD : Font.PLAIN; //4.构造字体 // Font f = new Font(font.getFamily(), style, size);//默认从JRE中的fonts目录下读取 // File file = new File("config/font/gzkzzt.ttf");//如果如CSDN上方描述的那样已经把字体放到jre下了。这里就不用写死指定了 // System.out.println("已读取字体文件"); Font f = Font.createFont(Font.TRUETYPE_FONT, file); f = f.deriveFont(style,size); FontRenderContext context = g2d.getFontRenderContext(); Rectangle2D rectangle = f.getStringBounds(font.getText(), context); //5.文字之间间距,默认动态调整 double space; if (font.getSpace() != null) { space = font.getSpace(); } else { if (textLen == 1) { space = 0; } else { space = rectangle.getWidth() / (textLen - 1) * 0.9; } } //6.距离外圈距离 int margin = font.getMargin() == null ? INIT_BEGIN : font.getMargin(); //7.写字 double newRadius = circleRadius + rectangle.getY() - margin; double radianPerInterval = 2 * Math.asin(space / (2 * newRadius)); double fix = 0.04; if (isTop) { fix = 0.18; } double firstAngle; if (!isTop) { if (textLen % 2 == 1) { firstAngle = Math.PI + Math.PI / 2 - (textLen - 1) * radianPerInterval / 2.0 - fix; } else { firstAngle = Math.PI + Math.PI / 2 - ((textLen / 2.0 - 0.5) * radianPerInterval) - fix; } } else { if (textLen % 2 == 1) { firstAngle = (textLen - 1) * radianPerInterval / 2.0 + Math.PI / 2 + fix; } else { firstAngle = (textLen / 2.0 - 0.5) * radianPerInterval + Math.PI / 2 + fix; } } for (int i = 0; i < textLen; i++) { double theta; double thetaX; double thetaY; if (!isTop) { theta = firstAngle + i * radianPerInterval; thetaX = newRadius * Math.sin(Math.PI / 2 - theta); thetaY = newRadius * Math.cos(theta - Math.PI / 2); } else { theta = firstAngle - i * radianPerInterval; thetaX = newRadius * Math.sin(Math.PI / 2 - theta); thetaY = newRadius * Math.cos(theta - Math.PI / 2); } AffineTransform transform; if (!isTop) { transform = AffineTransform.getRotateInstance(Math.PI + Math.PI / 2 - theta); } else { transform = AffineTransform.getRotateInstance(Math.PI / 2 - theta + Math.toRadians(8)); } Font f2 = f.deriveFont(transform); g2d.setFont(f2); g2d.drawString(font.getText().substring(i, i + 1), (float) (circleRadius + thetaX + INIT_BEGIN), (float) (circleRadius - thetaY + INIT_BEGIN)); } } /** * 绘制椭圆弧形文字 */ private static void drawArcFont4Oval(Graphics2D g2d, SealCircle sealCircle, SealFont font, boolean isTop) { if (font == null) { return; } float radiusX = sealCircle.getWidth(); float radiusY = sealCircle.getHeight(); float radiusWidth = radiusX + sealCircle.getLine(); float radiusHeight = radiusY + sealCircle.getLine(); //1.字体长度 int textLen = font.getText().length(); //2.字体大小,默认根据字体长度动态设定 int size = font.getSize() == null ? 25 + (10 - textLen) / 2 : font.getSize(); //3.字体样式 int style = font.getBold() ? Font.BOLD : Font.PLAIN; //4.构造字体 Font f = new Font(font.getFamily(), style, size); //5.总的角跨度 double totalArcAng = font.getSpace() * textLen; //6.从边线向中心的移动因子 float minRat = 0.90f; double startAngle = isTop ? -90f - totalArcAng / 2f : 90f - totalArcAng / 2f; double step = 0.5; int alCount = (int) Math.ceil(totalArcAng / step) + 1; double[] angleArr = new double[alCount]; double[] arcLenArr = new double[alCount]; int num = 0; double accArcLen = 0.0; angleArr[num] = startAngle; arcLenArr[num] = accArcLen; num++; double angR = startAngle * Math.PI / 180.0; double lastX = radiusX * Math.cos(angR) + radiusWidth; double lastY = radiusY * Math.sin(angR) + radiusHeight; for (double i = startAngle + step; num < alCount; i += step) { angR = i * Math.PI / 180.0; double x = radiusX * Math.cos(angR) + radiusWidth, y = radiusY * Math.sin(angR) + radiusHeight; accArcLen += Math.sqrt((lastX - x) * (lastX - x) + (lastY - y) * (lastY - y)); angleArr[num] = i; arcLenArr[num] = accArcLen; lastX = x; lastY = y; num++; } double arcPer = accArcLen / textLen; for (int i = 0; i < textLen; i++) { double arcL = i * arcPer + arcPer / 2.0; double ang = 0.0; for (int p = 0; p < arcLenArr.length - 1; p++) { if (arcLenArr[p] <= arcL && arcL <= arcLenArr[p + 1]) { ang = (arcL >= ((arcLenArr[p] + arcLenArr[p + 1]) / 2.0)) ? angleArr[p + 1] : angleArr[p]; break; } } angR = (ang * Math.PI / 180f); Float x = radiusX * (float) Math.cos(angR) + radiusWidth; Float y = radiusY * (float) Math.sin(angR) + radiusHeight; double qxang = Math.atan2(radiusY * Math.cos(angR), -radiusX * Math.sin(angR)); double fxang = qxang + Math.PI / 2.0; int subIndex = isTop ? i : textLen - 1 - i; String c = font.getText().substring(subIndex, subIndex + 1); //获取文字高宽 FontMetrics fm = sun.font.FontDesignMetrics.getMetrics(f); int w = fm.stringWidth(c), h = fm.getHeight(); if (isTop) { x += h * minRat * (float) Math.cos(fxang); y += h * minRat * (float) Math.sin(fxang); x += -w / 2f * (float) Math.cos(qxang); y += -w / 2f * (float) Math.sin(qxang); } else { x += (h * minRat) * (float) Math.cos(fxang); y += (h * minRat) * (float) Math.sin(fxang); x += w / 2f * (float) Math.cos(qxang); y += w / 2f * (float) Math.sin(qxang); } // 旋转 AffineTransform affineTransform = new AffineTransform(); affineTransform.scale(0.8, 1); if (isTop) affineTransform.rotate(Math.toRadians((fxang * 180.0 / Math.PI - 90)), 0, 0); else affineTransform.rotate(Math.toRadians((fxang * 180.0 / Math.PI + 180 - 90)), 0, 0); Font f2 = f.deriveFont(affineTransform); g2d.setFont(f2); g2d.drawString(c, x.intValue() + INIT_BEGIN, y.intValue() + INIT_BEGIN); } } /** * 画文字 */ private static void drawFont(Graphics2D g2d, int circleWidth, int circleHeight, SealFont font) { if (font == null) { return; } //1.字体长度 int textLen = font.getText().length(); //2.字体大小,默认根据字体长度动态设定 int size = font.getSize() == null ? (55 - textLen * 2) : font.getSize(); //3.字体样式 int style = font.getBold() ? Font.BOLD : Font.PLAIN; //4.构造字体 Font f = new Font(font.getFamily(), style, size); g2d.setFont(f); FontRenderContext context = g2d.getFontRenderContext(); String[] fontTexts = font.getText().split("\n"); if (fontTexts.length > 1) { int y = 0; for (String fontText : fontTexts) { y += Math.abs(f.getStringBounds(fontText, context).getHeight()); } //5.设置上边距 float margin = INIT_BEGIN + (float) (circleHeight / 2 - y / 2); for (String fontText : fontTexts) { Rectangle2D rectangle2D = f.getStringBounds(fontText, context); g2d.drawString(fontText, (float) (circleWidth / 2 - rectangle2D.getCenterX()), margin); margin += Math.abs(rectangle2D.getHeight()); } } else { Rectangle2D rectangle2D = f.getStringBounds(font.getText(), context); //5.设置上边距,默认在中心 float margin = font.getMargin() == null ? (float) (circleHeight / 2 - rectangle2D.getCenterY()) : (float) (circleHeight / 2 - rectangle2D.getCenterY()) + (float) font.getMargin(); g2d.drawString(font.getText(), (float) (circleWidth / 2 - rectangle2D.getCenterX()), margin); } } /** * 画圆 */ private static void drawCircle(Graphics2D g2d, SealCircle circle, int x, int y) { if (circle == null) { return; } //1.圆线条粗细默认是圆直径的1/35 int lineSize = circle.getLine() == null ? circle.getHeight() * 2 / (35) : circle.getLine(); //2.画圆 g2d.setStroke(new BasicStroke(lineSize)); g2d.drawOval(x, y, circle.getWidth() * 2, circle.getHeight() * 2); } /** * 画私章 */ public byte[] draw2(String imgFmt) throws Exception { if (mainFont == null || mainFont.getText().length() < 2 || mainFont.getText().length() > 4) { throw new IllegalArgumentException("请输入2-4个字"); } int fixH = 18; int fixW = 2; //1.画布 BufferedImage bi = new BufferedImage(size, size / 2, BufferedImage.TYPE_4BYTE_ABGR); //2.画笔 Graphics2D g2d = bi.createGraphics(); //2.1设置画笔颜色 g2d.setPaint(Color.RED); //2.2抗锯齿设置 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); //3.写签名 int marginW = fixW + borderSquare; float marginH; FontRenderContext context = g2d.getFontRenderContext(); Rectangle2D rectangle; Font f; if (mainFont.getText().length() == 2) { if (stamp != null && stamp.trim().length() > 0) { bi = drawThreeFont(bi, g2d, mainFont.append(stamp), borderSquare, size, fixH, fixW, true); } else { f = new Font(mainFont.getFamily(), Font.BOLD, mainFont.getSize()); g2d.setFont(f); rectangle = f.getStringBounds(mainFont.getText().substring(0, 1), context); marginH = (float) (Math.abs(rectangle.getCenterY()) * 2 + marginW) + fixH - 4; g2d.drawString(mainFont.getText().substring(0, 1), marginW, marginH); marginW += Math.abs(rectangle.getCenterX()) * 2 + (mainFont.getSpace() == null ? INIT_BEGIN : mainFont.getSpace()); g2d.drawString(mainFont.getText().substring(1), marginW, marginH); //拉伸 BufferedImage nbi = new BufferedImage(size, size, bi.getType()); Graphics2D ng2d = nbi.createGraphics(); ng2d.setPaint(Color.RED); ng2d.drawImage(bi, 0, 0, size, size, null); //画正方形 ng2d.setStroke(new BasicStroke(borderSquare)); ng2d.drawRect(0, 0, size, size); ng2d.dispose(); bi = nbi; } } else if (mainFont.getText().length() == 3) { if (stamp != null && stamp.trim().length() > 0) { bi = drawFourFont(bi, mainFont.append(stamp), borderSquare, size, fixH, fixW); } else { bi = drawThreeFont(bi, g2d, mainFont, borderSquare, size, fixH, fixW, false); } } else { bi = drawFourFont(bi, mainFont, borderSquare, size, fixH, fixW); } g2d.dispose(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); boolean png = ImageIO.write(bi,imgFmt, outputStream); byte[] imgBytes = outputStream.toByteArray(); outputStream.flush(); outputStream.close(); return imgBytes; } /** * 画三字 */ private static BufferedImage drawThreeFont(BufferedImage bi, Graphics2D g2d, SealFont font, int lineSize, int imageSize, int fixH, int fixW, boolean isWithYin) { fixH -= 9; int marginW = fixW + lineSize; //设置字体 Font f = new Font(font.getFamily(), Font.BOLD, font.getSize()); g2d.setFont(f); FontRenderContext context = g2d.getFontRenderContext(); Rectangle2D rectangle = f.getStringBounds(font.getText().substring(0, 1), context); float marginH = (float) (Math.abs(rectangle.getCenterY()) * 2 + marginW) + fixH; int oldW = marginW; if (isWithYin) { g2d.drawString(font.getText().substring(2, 3), marginW, marginH); marginW += rectangle.getCenterX() * 2 + (font.getSpace() == null ? INIT_BEGIN : font.getSpace()); } else { marginW += rectangle.getCenterX() * 2 + (font.getSpace() == null ? INIT_BEGIN : font.getSpace()); g2d.drawString(font.getText().substring(0, 1), marginW, marginH); } //拉伸 BufferedImage nbi = new BufferedImage(imageSize, imageSize, bi.getType()); Graphics2D ng2d = nbi.createGraphics(); ng2d.setPaint(Color.RED); ng2d.drawImage(bi, 0, 0, imageSize, imageSize, null); //画正方形 ng2d.setStroke(new BasicStroke(lineSize)); ng2d.drawRect(0, 0, imageSize, imageSize); ng2d.dispose(); bi = nbi; g2d = bi.createGraphics(); g2d.setPaint(Color.RED); g2d.setFont(f); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (isWithYin) { g2d.drawString(font.getText().substring(0, 1), marginW, marginH += fixH); rectangle = f.getStringBounds(font.getText(), context); marginH += Math.abs(rectangle.getHeight()); g2d.drawString(font.getText().substring(1), marginW, marginH); } else { g2d.drawString(font.getText().substring(1, 2), oldW, marginH += fixH); rectangle = f.getStringBounds(font.getText(), context); marginH += Math.abs(rectangle.getHeight()); g2d.drawString(font.getText().substring(2, 3), oldW, marginH); } return bi; } /** * 画四字 */ private static BufferedImage drawFourFont(BufferedImage bi, SealFont font, int lineSize, int imageSize, int fixH, int fixW) throws IOException, FontFormatException { int marginW = fixW + lineSize; //拉伸 BufferedImage nbi = new BufferedImage(imageSize, imageSize, bi.getType()); Graphics2D ng2d = nbi.createGraphics(); ng2d.setPaint(Color.RED); ng2d.drawImage(bi, 0, 0, imageSize, imageSize, null); //画正方形 ng2d.setStroke(new BasicStroke(lineSize)); ng2d.drawRect(0, 0, imageSize, imageSize); ng2d.dispose(); bi = nbi; Graphics2D g2d = bi.createGraphics(); g2d.setPaint(Color.RED); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); FontRenderContext context = g2d.getFontRenderContext(); //Font f = new Font(font.getFamily(), Font.BOLD, font.getSize());//默认读取JDK 下的font字体 File file = new File("config/font/simsun.ttf"); System.out.println("已读取字体文件"); Font f = Font.createFont(Font.TRUETYPE_FONT, file); f = f.deriveFont(Font.BOLD, font.getSize()); g2d.setFont(f); Rectangle2D rectangle = f.getStringBounds(font.getText().substring(0, 1), context); float marginH = (float) (Math.abs(rectangle.getCenterY()) * 2 + marginW) + fixH; g2d.drawString(font.getText().substring(2, 3), marginW, marginH); int oldW = marginW; marginW += Math.abs(rectangle.getCenterX()) * 2 + (font.getSpace() == null ? INIT_BEGIN : font.getSpace()); g2d.drawString(font.getText().substring(0, 1), marginW, marginH); marginH += Math.abs(rectangle.getHeight()); g2d.drawString(font.getText().substring(3, 4), oldW, marginH); g2d.drawString(font.getText().substring(1, 2), marginW, marginH); return bi; } public Integer getSize() { return size; } public void setSize(Integer size) { this.size = size; } public Color getColor() { return color; } public void setColor(Color color) { this.color = color; } public SealFont getMainFont() { return mainFont; } public void setMainFont(SealFont mainFont) { this.mainFont = mainFont; } public SealFont getViceFont() { return viceFont; } public void setViceFont(SealFont viceFont) { this.viceFont = viceFont; } public SealFont getTitleFont() { return titleFont; } public void setTitleFont(SealFont titleFont) { this.titleFont = titleFont; } public SealFont getCenterFont() { return centerFont; } public void setCenterFont(SealFont centerFont) { this.centerFont = centerFont; } public SealCircle getBorderCircle() { return borderCircle; } public void setBorderCircle(SealCircle borderCircle) { this.borderCircle = borderCircle; } public SealCircle getBorderInnerCircle() { return borderInnerCircle; } public void setBorderInnerCircle(SealCircle borderInnerCircle) { this.borderInnerCircle = borderInnerCircle; } public SealCircle getInnerCircle() { return innerCircle; } public void setInnerCircle(SealCircle innerCircle) { this.innerCircle = innerCircle; } public Integer getBorderSquare() { return borderSquare; } public void setBorderSquare(Integer borderSquare) { this.borderSquare = borderSquare; } public String getStamp() { return stamp; } public void setStamp(String stamp) { this.stamp = stamp; } } ``` SealCircle类: ```java package cn.com.yuanquanyun.bjca_scan; import lombok.Builder; import lombok.Getter; @Getter @Builder public class SealCircle { private Integer line; private Integer width; private Integer height; public Integer getLine() { return line; } public void setLine(Integer line) { this.line = line; } public Integer getWidth() { return width; } public void setWidth(Integer width) { this.width = width; } public Integer getHeight() { return height; } public void setHeight(Integer height) { this.height = height; } } ``` SealFont类: ```java package cn.com.yuanquanyun.bjca_scan; import lombok.Builder; import lombok.Getter; @Getter @Builder public class SealFont { private String text; private String family; private Integer size; private Boolean bold; private Double space; private Integer margin; public String getText() { return text; } public void setText(String text) { this.text = text; } public void setFamily(String family) { this.family = family; } public Integer getSize() { return size; } public void setSize(Integer size) { this.size = size; } public void setBold(Boolean bold) { this.bold = bold; } public Double getSpace() { return space; } public void setSpace(Double space) { this.space = space; } public Integer getMargin() { return margin; } public void setMargin(Integer margin) { this.margin = margin; } public String getFamily() { return family == null ? "宋体" : family; } public boolean getBold() { return bold == null ? true : bold; } public SealFont append(String text) { this.text += text; return this; } } ``` 和印章生成相关的主要是以上这三个类。 ## 测试类 ```java public static void myTestCompanySeal() throws Exception { String mainText = "北京某某科技有限公司"; String titleText = "合同专用章"; String viceText = "0192834567"; String keZhang = "公章刻章字体";//它会根据字体名称 去 jre下的font里去找这个字体。没有的话,会用默认字体。可以灵活控制Font的字体。上方的 Seal类中,采用的是读取本地的字体文件。而不是去jre 下的font里去找。两种方式都可以。 String songTi = "宋体";//同上 // 使用工具生成印章图片 try { Seal.SealBuilder builder = Seal.builder(); builder.size(432); builder.borderCircle(SealCircle.builder().line(10).width(210).height(210).build()); if (mainText != null) { // 根据主文字长度,设置字体大小及边距 int length = mainText.length(); int size = 70; double space = 50; if (length > 0 && length <= 8) { space = 70; } else if (length == 9) { space = 60; } else if (length == 10) { space = 55; } else if (length == 11) { space = 50; } else if (length == 12) { space = 45; } else if (length == 13) { space = 40; } else if (length == 14) { space = 40; } else if (length == 15) { space = 37; } else if (length == 16) { size = 65; space = 35; } else if (length == 17) { size = 65; space = 35; } else if (length == 18) { size = 63; space = 33; } else if (length == 19) { size = 61; space = 31; } else if (length == 20) { size = 60; space = 30; } else if (length == 21) { size = 58; space = 29; } else if (length == 22) { size = 56; space = 28; } else if (length == 23) { size = 54; space = 27; } else if (length == 24) { size = 54; space = 27; } else if (length == 25) { size = 52; space = 26; } else { size = 60 - (length - 20) * 2; space = 30 - (length - 20) * 0.5; } builder.mainFont(SealFont.builder().text(mainText).family(keZhang).size(size).space(space).margin(10).build()); } // builder.centerFont(SealFont.builder().family(songTi).text("★").size(140).build()); // if (titleText != null) { builder.titleFont(SealFont.builder().family(keZhang).text(titleText).size(50).space(0.0).margin(100).build()); } // 副文字 数字编号 if (viceText != null) { builder.viceFont(SealFont.builder().family(songTi).text(viceText).size(30).space(20.0).margin(-10).build()); } // Seal build = builder.build(); byte[] draw = build.draw("png"); FileUtil.writeBytes(draw, "C:\\Users\\Administrator\\Desktop\\seal.jpg"); } catch (Exception e) { throw new CustomException("生成印章失败"); } } public static void main(String[] args) throws Exception { // myTestCompanySeal();//生成企业圆章 // String legalName="王大拿";//三个字 String legalName="王大拿印";//四个字 String imgFmt = "png"; String fontType = "宋体"; makeLegalSeal(legalName,imgFmt,fontType);//生成个人私章 } ``` 效果:如下  测试私人印章类: ```java private static byte[] makeLegalSeal(String legalName,String imgFmt,String fontType) throws Exception { // 加载刻章字体 // String keZhang = "公章刻章字体";//这里的名字是字体名字,可以通过双击STXINGKA.ttf文件可以看到第一行的 字体名称: 公章刻章字体 华文行楷 String keZhang = fontType;//这里的名字是字体名字,可以通过双击STXINGKA.ttf文件可以看到第一行的 字体名称: 公章刻章字体 华文行楷 "Lucida Bright" byte[] draw =Seal.builder() .size(300) .borderSquare(16) .mainFont(SealFont.builder().text(legalName).family(keZhang).size(120).build()) .build() .draw(imgFmt); FileUtil.writeBytes(draw, "C:\\Users\\Administrator\\Desktop\\privateSeal.jpg"); return draw; } ```   ## 绘制方章 SquareSeal类: ```java package cn.com.yuanquanyun.utils.seal; import cn.hutool.core.io.IoUtil; import lombok.Builder; import lombok.Getter; import javax.imageio.ImageIO; import java.awt.*; import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @Builder @Getter public class SquareSeal { // 起始位置 private static final int INIT_BEGIN = 5; // 尺寸 private Integer size; // 颜色 private Color color; // 主字 private SealFont mainFont; // 副字 private SealFont viceFont; // 抬头 private SealFont titleFont; // 中心字 private SealFont centerFont; // 边线圆 private SealCircle borderCircle; // 内边线圆 private SealCircle borderInnerCircle; // 内线圆 private SealCircle innerCircle; // 边线框 private Integer borderSquare; // 加字 private String stamp; /** * 画公章 */ public byte[] draw() throws Exception { if (borderSquare != null) { return draw2(); // 画私章 } //1.画布 BufferedImage bi = new BufferedImage(size, size, BufferedImage.TYPE_4BYTE_ABGR); //2.画笔 Graphics2D g2d = bi.createGraphics(); //2.1抗锯齿设置 //文本不抗锯齿,否则圆中心的文字会被拉长 RenderingHints hints = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); //其他图形抗锯齿 hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHints(hints); //2.2设置背景透明度 g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0)); //2.3填充矩形 g2d.fillRect(0, 0, size, size); //2.4重设透明度,开始画图 g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 1)); //2.5设置画笔颜色 g2d.setPaint(color == null ? Color.RED : color); //3.画边线圆 if (borderCircle != null) { drawCircle(g2d, borderCircle, INIT_BEGIN, INIT_BEGIN); } else { throw new Exception("BorderCircle can not null!"); } int borderCircleWidth = borderCircle.getWidth(); int borderCircleHeight = borderCircle.getHeight(); //4.画内边线圆 if (borderInnerCircle != null) { int x = INIT_BEGIN + borderCircleWidth - borderInnerCircle.getWidth(); int y = INIT_BEGIN + borderCircleHeight - borderInnerCircle.getHeight(); drawCircle(g2d, borderInnerCircle, x, y); } //5.画内环线圆 if (innerCircle != null) { int x = INIT_BEGIN + borderCircleWidth - innerCircle.getWidth(); int y = INIT_BEGIN + borderCircleHeight - innerCircle.getHeight(); drawCircle(g2d, innerCircle, x, y); } //6.画弧形主文字 if (borderCircleHeight != borderCircleWidth) { drawArcFont4Oval(g2d, borderCircle, mainFont, true); } else { drawArcFont4Circle(g2d, borderCircleHeight, mainFont, true); } //7.画弧形副文字 if (borderCircleHeight != borderCircleWidth) { drawArcFont4Oval(g2d, borderCircle, viceFont, false); } else { drawArcFont4Circle(g2d, borderCircleHeight, viceFont, false); } //8.画中心字 drawFont(g2d, (borderCircleWidth + INIT_BEGIN) * 2, (borderCircleHeight + INIT_BEGIN) * 2, centerFont); //9.画抬头文字 drawFont(g2d, (borderCircleWidth + INIT_BEGIN) * 2, (borderCircleHeight + INIT_BEGIN) * 2, titleFont); g2d.dispose(); // ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); boolean png = ImageIO.write(bi, "PNG", outputStream); byte[] imgBytes = outputStream.toByteArray(); outputStream.flush(); outputStream.close(); return imgBytes; } public void draw(String pngPath) throws Exception { byte[] imgBytes = draw(); FileOutputStream fileOutputStream = new FileOutputStream(new File(pngPath)); fileOutputStream.write(imgBytes); IoUtil.close(fileOutputStream); } /** * 绘制圆弧形文字 */ private static void drawArcFont4Circle(Graphics2D g2d, int circleRadius, SealFont font, boolean isTop) { if (font == null) { return; } //1.字体长度 int textLen = font.getText().length(); //2.字体大小,默认根据字体长度动态设定 int size = font.getSize() == null ? (55 - textLen * 2) : font.getSize(); //3.字体样式 int style = font.getBold() ? Font.BOLD : Font.PLAIN; //4.构造字体 Font f = new Font(font.getFamily(), style, size); FontRenderContext context = g2d.getFontRenderContext(); Rectangle2D rectangle = f.getStringBounds(font.getText(), context); //5.文字之间间距,默认动态调整 double space; if (font.getSpace() != null) { space = font.getSpace(); } else { if (textLen == 1) { space = 0; } else { space = rectangle.getWidth() / (textLen - 1) * 0.9; } } //6.距离外圈距离 int margin = font.getMargin() == null ? INIT_BEGIN : font.getMargin(); //7.写字 double newRadius = circleRadius + rectangle.getY() - margin; double radianPerInterval = 2 * Math.asin(space / (2 * newRadius)); double fix = 0.04; if (isTop) { fix = 0.18; } double firstAngle; if (!isTop) { if (textLen % 2 == 1) { firstAngle = Math.PI + Math.PI / 2 - (textLen - 1) * radianPerInterval / 2.0 - fix; } else { firstAngle = Math.PI + Math.PI / 2 - ((textLen / 2.0 - 0.5) * radianPerInterval) - fix; } } else { if (textLen % 2 == 1) { firstAngle = (textLen - 1) * radianPerInterval / 2.0 + Math.PI / 2 + fix; } else { firstAngle = (textLen / 2.0 - 0.5) * radianPerInterval + Math.PI / 2 + fix; } } for (int i = 0; i < textLen; i++) { double theta; double thetaX; double thetaY; if (!isTop) { theta = firstAngle + i * radianPerInterval; thetaX = newRadius * Math.sin(Math.PI / 2 - theta); thetaY = newRadius * Math.cos(theta - Math.PI / 2); } else { theta = firstAngle - i * radianPerInterval; thetaX = newRadius * Math.sin(Math.PI / 2 - theta); thetaY = newRadius * Math.cos(theta - Math.PI / 2); } AffineTransform transform; if (!isTop) { transform = AffineTransform.getRotateInstance(Math.PI + Math.PI / 2 - theta); } else { transform = AffineTransform.getRotateInstance(Math.PI / 2 - theta + Math.toRadians(8)); } Font f2 = f.deriveFont(transform); g2d.setFont(f2); g2d.drawString(font.getText().substring(i, i + 1), (float) (circleRadius + thetaX + INIT_BEGIN), (float) (circleRadius - thetaY + INIT_BEGIN)); } } /** * 绘制椭圆弧形文字 */ private static void drawArcFont4Oval(Graphics2D g2d, SealCircle sealCircle, SealFont font, boolean isTop) { if (font == null) { return; } float radiusX = sealCircle.getWidth(); float radiusY = sealCircle.getHeight(); float radiusWidth = radiusX + sealCircle.getLine(); float radiusHeight = radiusY + sealCircle.getLine(); //1.字体长度 int textLen = font.getText().length(); //2.字体大小,默认根据字体长度动态设定 int size = font.getSize() == null ? 25 + (10 - textLen) / 2 : font.getSize(); //3.字体样式 int style = font.getBold() ? Font.BOLD : Font.PLAIN; //4.构造字体 Font f = new Font(font.getFamily(), style, size); //5.总的角跨度 double totalArcAng = font.getSpace() * textLen; //6.从边线向中心的移动因子 float minRat = 0.90f; double startAngle = isTop ? -90f - totalArcAng / 2f : 90f - totalArcAng / 2f; double step = 0.5; int alCount = (int) Math.ceil(totalArcAng / step) + 1; double[] angleArr = new double[alCount]; double[] arcLenArr = new double[alCount]; int num = 0; double accArcLen = 0.0; angleArr[num] = startAngle; arcLenArr[num] = accArcLen; num++; double angR = startAngle * Math.PI / 180.0; double lastX = radiusX * Math.cos(angR) + radiusWidth; double lastY = radiusY * Math.sin(angR) + radiusHeight; for (double i = startAngle + step; num < alCount; i += step) { angR = i * Math.PI / 180.0; double x = radiusX * Math.cos(angR) + radiusWidth, y = radiusY * Math.sin(angR) + radiusHeight; accArcLen += Math.sqrt((lastX - x) * (lastX - x) + (lastY - y) * (lastY - y)); angleArr[num] = i; arcLenArr[num] = accArcLen; lastX = x; lastY = y; num++; } double arcPer = accArcLen / textLen; for (int i = 0; i < textLen; i++) { double arcL = i * arcPer + arcPer / 2.0; double ang = 0.0; for (int p = 0; p < arcLenArr.length - 1; p++) { if (arcLenArr[p] <= arcL && arcL <= arcLenArr[p + 1]) { ang = (arcL >= ((arcLenArr[p] + arcLenArr[p + 1]) / 2.0)) ? angleArr[p + 1] : angleArr[p]; break; } } angR = (ang * Math.PI / 180f); Float x = radiusX * (float) Math.cos(angR) + radiusWidth; Float y = radiusY * (float) Math.sin(angR) + radiusHeight; double qxang = Math.atan2(radiusY * Math.cos(angR), -radiusX * Math.sin(angR)); double fxang = qxang + Math.PI / 2.0; int subIndex = isTop ? i : textLen - 1 - i; String c = font.getText().substring(subIndex, subIndex + 1); //获取文字高宽 FontMetrics fm = sun.font.FontDesignMetrics.getMetrics(f); int w = fm.stringWidth(c), h = fm.getHeight(); if (isTop) { x += h * minRat * (float) Math.cos(fxang); y += h * minRat * (float) Math.sin(fxang); x += -w / 2f * (float) Math.cos(qxang); y += -w / 2f * (float) Math.sin(qxang); } else { x += (h * minRat) * (float) Math.cos(fxang); y += (h * minRat) * (float) Math.sin(fxang); x += w / 2f * (float) Math.cos(qxang); y += w / 2f * (float) Math.sin(qxang); } // 旋转 AffineTransform affineTransform = new AffineTransform(); affineTransform.scale(0.8, 1); if (isTop) affineTransform.rotate(Math.toRadians((fxang * 180.0 / Math.PI - 90)), 0, 0); else affineTransform.rotate(Math.toRadians((fxang * 180.0 / Math.PI + 180 - 90)), 0, 0); Font f2 = f.deriveFont(affineTransform); g2d.setFont(f2); g2d.drawString(c, x.intValue() + INIT_BEGIN, y.intValue() + INIT_BEGIN); } } /** * 画文字 */ private static void drawFont(Graphics2D g2d, int circleWidth, int circleHeight, SealFont font) { if (font == null) { return; } //1.字体长度 int textLen = font.getText().length(); //2.字体大小,默认根据字体长度动态设定 int size = 18; if (font.getSize() != null){ size = font.getSize(); }else { if (textLen > 13){ size = 18 - (textLen - 13) < 12 ? 12 : 18 - (textLen - 13); } } //3.字体样式 int style = font.getBold() ? Font.BOLD : Font.PLAIN; //4.构造字体 Font f = new Font(font.getFamily(), style, size); g2d.setFont(f); FontRenderContext context = g2d.getFontRenderContext(); String[] fontTexts = font.getText().split("\n"); if (fontTexts.length > 1) {//当文字是多行时的处理方式 int y = 0; for (String fontText : fontTexts) { y += Math.abs(f.getStringBounds(fontText, context).getHeight()); } //5.设置上边距 float margin = INIT_BEGIN*5 + (float) (circleHeight / 2 - y / 2); // System.out.println("1.margin==="+margin); for (String fontText : fontTexts) { Rectangle2D rectangle2D = f.getStringBounds(fontText, context); g2d.drawString(fontText, (float) (circleWidth / 2 - rectangle2D.getCenterX()), margin); margin += Math.abs(rectangle2D.getHeight()); // System.out.println("2.margin==="+margin); } // System.out.println("3.margin==="+margin); } else {//当文字是单行时的处理方式 Rectangle2D rectangle2D = f.getStringBounds(font.getText(), context); //5.设置上边距,默认在中心 float margin = font.getMargin() == null ? (float) (circleHeight / 2 - rectangle2D.getCenterY()) : (float) (circleHeight / 2 - rectangle2D.getCenterY()) + (float) font.getMargin(); g2d.drawString(font.getText(), (float) (circleWidth / 2 - rectangle2D.getCenterX()), margin); } } /** * 画圆 */ private static void drawCircle(Graphics2D g2d, SealCircle circle, int x, int y) { if (circle == null) { return; } //1.圆线条粗细默认是圆直径的1/35 int lineSize = circle.getLine() == null ? circle.getHeight() * 2 / (35) : circle.getLine(); //2.画圆 g2d.setStroke(new BasicStroke(lineSize)); g2d.drawOval(x, y, circle.getWidth() * 2, circle.getHeight() * 2); } /** * 画矩形 */ private static void drawRectangle(Graphics2D g2d, SealCircle circle, int x, int y) { if (circle == null) { return; } //1.圆线条粗细默认是圆直径的1/35 int lineSize = circle.getLine() == null ? circle.getHeight() * 2 / (35) : circle.getLine(); //2.画矩形 g2d.setStroke(new BasicStroke(lineSize)); g2d.drawRect(x, y, 289, circle.getHeight() * 1); } /** * 画私章 */ public byte[] draw2() throws Exception { //1.画布 BufferedImage bi = new BufferedImage(size, 106, BufferedImage.TYPE_4BYTE_ABGR); //2.画笔 Graphics2D g2d = bi.createGraphics(); //2.1抗锯齿设置 //文本不抗锯齿,否则圆中心的文字会被拉长 RenderingHints hints = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); //其他图形抗锯齿 hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHints(hints); //2.2设置背景透明度 g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0)); //2.3填充矩形 g2d.fillRect(0, 0, size, size); //2.4重设透明度,开始画图 g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 1)); //2.5设置画笔颜色 g2d.setPaint(color == null ? Color.RED : color); //3.画边线圆 if (borderCircle != null) { drawRectangle(g2d, borderCircle, INIT_BEGIN, INIT_BEGIN); } else { throw new Exception("BorderCircle can not null!"); } //4.画上行文字 drawFont(g2d, INIT_BEGIN * 60, INIT_BEGIN * 5, mainFont); //5.画横向文字 drawFont(g2d, INIT_BEGIN * 60, INIT_BEGIN * 20, viceFont); //5.画下行文字 drawFont(g2d, INIT_BEGIN * 60, INIT_BEGIN * 5, titleFont); g2d.dispose(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); boolean png = ImageIO.write(bi, "PNG", outputStream); byte[] imgBytes = outputStream.toByteArray(); outputStream.flush(); outputStream.close(); return imgBytes; } /** * 画三字 */ private static BufferedImage drawThreeFont(BufferedImage bi, Graphics2D g2d, SealFont font, int lineSize, int imageSize, int fixH, int fixW, boolean isWithYin) { fixH -= 9; int marginW = fixW + lineSize; //设置字体 Font f = new Font(font.getFamily(), Font.BOLD, font.getSize()); g2d.setFont(f); FontRenderContext context = g2d.getFontRenderContext(); Rectangle2D rectangle = f.getStringBounds(font.getText().substring(0, 1), context); float marginH = (float) (Math.abs(rectangle.getCenterY()) * 2 + marginW) + fixH; int oldW = marginW; if (isWithYin) { g2d.drawString(font.getText().substring(2, 3), marginW, marginH); marginW += rectangle.getCenterX() * 2 + (font.getSpace() == null ? INIT_BEGIN : font.getSpace()); } else { marginW += rectangle.getCenterX() * 2 + (font.getSpace() == null ? INIT_BEGIN : font.getSpace()); g2d.drawString(font.getText().substring(0, 1), marginW, marginH); } //拉伸 BufferedImage nbi = new BufferedImage(imageSize, imageSize, bi.getType()); Graphics2D ng2d = nbi.createGraphics(); ng2d.setPaint(Color.RED); ng2d.drawImage(bi, 0, 0, imageSize, imageSize, null); //画正方形 ng2d.setStroke(new BasicStroke(lineSize)); ng2d.drawRect(0, 0, imageSize, imageSize); ng2d.dispose(); bi = nbi; g2d = bi.createGraphics(); g2d.setPaint(Color.RED); g2d.setFont(f); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (isWithYin) { g2d.drawString(font.getText().substring(0, 1), marginW, marginH += fixH); rectangle = f.getStringBounds(font.getText(), context); marginH += Math.abs(rectangle.getHeight()); g2d.drawString(font.getText().substring(1), marginW, marginH); } else { g2d.drawString(font.getText().substring(1, 2), oldW, marginH += fixH); rectangle = f.getStringBounds(font.getText(), context); marginH += Math.abs(rectangle.getHeight()); g2d.drawString(font.getText().substring(2, 3), oldW, marginH); } return bi; } /** * 画四字 */ private static BufferedImage drawFourFont(BufferedImage bi, SealFont font, int lineSize, int imageSize, int fixH, int fixW) { int marginW = fixW + lineSize; //拉伸 BufferedImage nbi = new BufferedImage(imageSize, imageSize, bi.getType()); Graphics2D ng2d = nbi.createGraphics(); ng2d.setPaint(Color.RED); ng2d.drawImage(bi, 0, 0, imageSize, imageSize, null); //画正方形 ng2d.setStroke(new BasicStroke(lineSize)); ng2d.drawRect(0, 0, imageSize, imageSize); ng2d.dispose(); bi = nbi; Graphics2D g2d = bi.createGraphics(); g2d.setPaint(Color.RED); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); FontRenderContext context = g2d.getFontRenderContext(); Font f = new Font(font.getFamily(), Font.BOLD, font.getSize()); g2d.setFont(f); Rectangle2D rectangle = f.getStringBounds(font.getText().substring(0, 1), context); float marginH = (float) (Math.abs(rectangle.getCenterY()) * 2 + marginW) + fixH; g2d.drawString(font.getText().substring(2, 3), marginW, marginH); int oldW = marginW; marginW += Math.abs(rectangle.getCenterX()) * 2 + (font.getSpace() == null ? INIT_BEGIN : font.getSpace()); g2d.drawString(font.getText().substring(0, 1), marginW, marginH); marginH += Math.abs(rectangle.getHeight()); g2d.drawString(font.getText().substring(3, 4), oldW, marginH); g2d.drawString(font.getText().substring(1, 2), marginW, marginH); return bi; } } ``` ## 测试类 ```java /** * 创建方形企业章 * * @param companyName 企业名称:xxx企业 * @param title 抬头:合同专用章 * @param code 编号:1234567890123 * @return * @throws Exception */ public static byte[] makeCompanySquareSeal (String companyName, String title, String code) throws Exception { byte[] img = null; if(companyName.length() <= 19){ img = SquareSeal.builder() .size(300) .borderCircle(SealCircle.builder().line(4).width(95).height(95).build()) .borderSquare(10) .mainFont(SealFont.builder().text(companyName).space(12.0).margin(10).build()) .viceFont(SealFont.builder().text(title).size(16).space(15.0).margin(0).build()) .titleFont(SealFont.builder().text(code).size(12).space(9.0).margin(64).build()) .color(Color.RED) .build() .draw(); }else {//支持印章字体换行操作 int len = companyName.length(); companyName = companyName.substring(0,19)+"\n"+companyName.substring(19,len); img = SquareSeal.builder() .size(300) .borderCircle(SealCircle.builder().line(4).width(95).height(95).build()) .borderSquare(10) .mainFont(SealFont.builder().text(companyName).space(12.0).margin(10).build()) .viceFont(SealFont.builder().text(title).size(16).space(15.0).margin(10).build())//margin 越大viceFont位置越往下,margin 为0时,位于中间位置 .titleFont(SealFont.builder().text(code).size(12).space(9.0).margin(75).build()) //margin 越大titleFont位置越往下 .color(Color.RED) .build() .draw(); } return img; } public static void main(String[] args) throws Exception { byte[] bytes = makeCompanySquareSeal("中国某某某股份有限公司某某某某分公司", "招标专用章", "1234567890123"); System.out.println("data:image/png;base64,"+ Base64.encode(bytes)); } ```  主文字超过19个字,就换行。 