Java-生成二维码带多行文字-支持自动换行-支持tab缩进效果-支持高度自适应
1. 样式1 :
2.1 效果图 :
忽略红色,最外层的红色只是方便展示当前图片的宽高
2.2 代码
package cn.jiangjiesheng.utils;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 或者换个思路,写html导出pdf 或者导出png ,不知道能否行得通?参考 eduinp 生产pdf和zip打包的逻辑的逻辑。
* <dependency>
* <groupId>com.google.zxing</groupId>
* <artifactId>core</artifactId>
* <version>3.4.1</version>
* </dependency>
* <dependency>
* <groupId>com.google.zxing</groupId>
* <artifactId>javase</artifactId>
* <version>3.4.1</version>
* </dependency>
*/
public class QRCodeWithTextGenerator {
private final static Logger log = LoggerFactory.getLogger(QRCodeWithTextGenerator.class);
private static boolean IS_DEBUG = true;
private static final String OUTPUT_FILE = "qrcode.png"; // 输出文件名
private static final String SPACE = " "; // 输出文件名
private static int QRCODE_WIDTH = 200;
private static final int FONT_SIZE = 12;
private static final int EXPANSION_FACTOR = 5; // textInfo 扩大倍数 ,按 \\n 换行(一条属性信息),一条属性信息可能比较长,还会继续换行,最终来裁切图片,此值大致表示每条属性信息最多可能的换行条数
private static final int SPACE_COUNT = 14; // 换行时左侧的空格占位
private static Font FONT = new Font("Microsoft YaHei", Font.PLAIN, FONT_SIZE);
public static void main(String[] args) {
try {
//main 方法测试使用 直接使用 static 默认值就行
//init();
String QR_CODE_TEXT = "http://code.jiangjiesheng.cn";
String textInfo = "设备名称: xxxx\n所属机构: 123456789123456789123456789\n所属测点: xxxx\n所属区域: 江苏省南京市江北新区安徽省安庆市\n其他信息: 联系人 xxx \n";
BufferedImage image = generateQRCodeWithText(QR_CODE_TEXT, QRCODE_WIDTH, textInfo);
saveImageToFile(image, OUTPUT_FILE);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
@PostConstruct
public void init() throws IOException, FontFormatException {
// 加载系统字体
// FONT = new Font("Microsoft YaHei", Font.PLAIN, FONT_SIZE);
// 加载自定义字体
try {
writeFontFile(this.getClass(), "Microsoft YaHei-normal.ttc", false);
String fontPath = getFontFilePath4Read("Microsoft YaHei-normal.ttc");
FONT = Font.createFont(Font.TRUETYPE_FONT, new File(fontPath)).deriveFont(Font.PLAIN, FONT_SIZE);
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
ge.registerFont(FONT);
} catch (IOException | FontFormatException e) {
log.error("加载自定义字体出错", e);
}
}
private static BufferedImage generateQRCodeWithText(String codeContent, int qrcodeWidthHeight, String textInfo) throws WriterException {
// 设置二维码参数
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.MARGIN, 1); // 边距
QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode(codeContent, BarcodeFormat.QR_CODE, qrcodeWidthHeight, qrcodeWidthHeight, hints);
int qrWidth = bitMatrix.getWidth();
int qrHeight = bitMatrix.getHeight();
// 创建二维码图像
BufferedImage qrImage = new BufferedImage(qrWidth, qrHeight, BufferedImage.TYPE_INT_ARGB);
for (int x = 0; x < qrWidth; x++) {
for (int y = 0; y < qrHeight; y++) {
qrImage.setRGB(x, y, bitMatrix.get(x, y) ? Color.BLACK.getRGB() : Color.WHITE.getRGB());
}
}
// 创建一个新的图像,包含二维码和文本
int textPadding = 10; // 文本区域的内边距
int imageWidth = qrcodeWidthHeight + textPadding; // 文本区域的内边距
int lineHeight = FONT_SIZE + 10; // 行高
//默认每一行不需要换行的高度
int textHeightNoWrap = (int) (lineHeight * (countLines(textInfo))); // +1 最后一行后也保留个空行,模拟padding
int textMaxHeightWrapped = textHeightNoWrap * EXPANSION_FACTOR;
// 计算总的宽度和高度
int totalWidth = qrWidth;
// int totalHeightNoWrap = qrHeight + textHeightNoWrap + textPadding * 2; // 上下各增加内边距
int totalMaxHeightWrapped = qrHeight + textMaxHeightWrapped + textPadding * 2; // 上下各增加内边距
// 创建包含二维码和文本的图像
BufferedImage fullImage = new BufferedImage(totalWidth, totalMaxHeightWrapped, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = fullImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//设置整个背景白色
Graphics g = fullImage.getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, totalWidth, totalMaxHeightWrapped);
// 绘制二维码
g2d.drawImage(qrImage, 0, 0, null);
// 绘制文本区域背景
g2d.setColor(Color.WHITE);
g2d.fillRect(0, qrHeight + textPadding, totalWidth, textHeightNoWrap + textPadding);
// 绘制文本
g2d.setColor(Color.BLACK);
//Font font = Font.createFont(Font.TRUETYPE_FONT, new File("path/to/font.ttf")); eduinp 有加载字体的逻辑,有关于.的处理
g2d.setFont(FONT); // 使用支持中文的字体
String[] lines = textInfo.split("\n");
int textY = (int) (qrHeight + textPadding * 0); // 文本起始位置
int textX = textPadding; // 文本起始位置 新增的
FontRenderContext affineTransform = g2d.getFontRenderContext();
//因为每一条信息多次换行会带来高度的变化,这里记录下必不换行时新增的行数
AtomicInteger addedLineCount = new AtomicInteger();
for (String line : lines) {
// 只管单行
// g2d.drawString(line, textPadding, textY);
// textY += lineHeight;
String currentLine = "";
for (char word : line.toCharArray()) {
// 尝试添加单词到当前行
String newLine = !currentLine.isEmpty() ? (currentLine + word) : String.valueOf(word);
TextLayout layout = new TextLayout(newLine, FONT, affineTransform);
double width = layout.getBounds().getWidth();
if (width > imageWidth - (textPadding * 3)) { //这里 x 3 是为了右侧更宽点
// 如果新行超过了图片宽度,则绘制当前行并移到下一行
drawText(g2d, currentLine, textX, textY, affineTransform);
textY += lineHeight;
currentLine = getSpaceString() + word;
addedLineCount.addAndGet(1);
} else {
// 否则继续添加单词
currentLine = newLine;
}
}
if (!currentLine.isEmpty()) {
drawText(g2d, currentLine, textX, textY, affineTransform);
textY += lineHeight;
}
}
g2d.dispose();
// 添加边框【调试使用,方便debug时显示BufferedImage】
int borderWidth = 0;
if (IS_DEBUG) {
borderWidth = 2;
Color borderColor = new Color(255, 0, 0); // 浅灰色
fullImage = addBorder(fullImage, borderWidth, borderColor);
}
//裁切
//整体多出的高度
int textHeightRealHeight = textHeightNoWrap + lineHeight * addedLineCount.get();
//这里正常都应该是大于0的,还是多层判断吧
//- (int)(lineHeight * 0.5) 为了让底部空白小点
int totalHeightRealHeight = totalMaxHeightWrapped - (textMaxHeightWrapped - textHeightRealHeight) - (int) (lineHeight * 0.5);
if (totalHeightRealHeight > 0) {
// 使用 getSubimage 方法裁剪图像
fullImage = fullImage.getSubimage(0, 0, totalWidth + borderWidth * 2, totalHeightRealHeight);
}
//最后画个框
fullImage = addBorder(fullImage, 1, Color.BLACK);
return fullImage;
}
/**
* 换行时左侧的空格占位
*
* @return
*/
private static String getSpaceString() {
StringBuilder str = new StringBuilder();
for (int i = 0; i < SPACE_COUNT; i++) {
str.append(SPACE);
}
return str.toString();
}
private static void drawText(Graphics2D g2d, String text, int x, int y, FontRenderContext affineTransform) {
TextLayout layout = new TextLayout(text, g2d.getFont(), affineTransform);
layout.draw(g2d, x, y + layout.getAscent());
}
// 添加边框的方法
private static BufferedImage addBorder(BufferedImage originalImage, int borderWidthHeight, Color borderColor) {
// 创建一个带边框的新图像
BufferedImage borderedImage = new BufferedImage(originalImage.getWidth() + borderWidthHeight * 2,
originalImage.getHeight() + borderWidthHeight * 2,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = borderedImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 绘制边框
g2d.setColor(borderColor);
g2d.fillRect(0, 0, borderedImage.getWidth(), borderedImage.getHeight());
// 绘制原始图像到新图像的中心
g2d.drawImage(originalImage, borderWidthHeight, borderWidthHeight, null);
g2d.dispose();
return borderedImage;
}
private static int countLines(String text) {
return text.split("\n").length;
}
private static void saveImageToFile(BufferedImage image, String filePath) throws IOException {
boolean result = ImageIO.write(image, "png", new File(filePath));
if (!result) {
System.out.println("Failed to write the image file.");
} else {
System.out.println("Image file written successfully.");
}
}
public static void writeFontFile(Class clazz, String fontName, boolean showLog) {
try {
String targetFilePath = getFontFilePath(fontName, true);
File file = new File(targetFilePath);
if (file.exists()) {
if (showLog) {
log.info("writeFontFile 已存在,不再写入");
}
return;
}
file.createNewFile();
file.setWritable(true);
InputStream inputStream = clazz.getResourceAsStream("/font/" + fontName);
copyInputStreamToFile(inputStream, file);
log.info("writeFontFile 已写入到jar包当前文件夹下的./fonts/下");
} catch (IOException e) {
log.error("writeFontFile 已写入到jar包当前文件夹下的./fonts/出现异常,异常:{}", e);
}
}
public static void copyInputStreamToFile(InputStream in, File file) throws IOException {
try (OutputStream out = Files.newOutputStream(file.toPath())) {
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
} finally {
// 确保关闭输入流以释放资源
if (in != null) {
in.close();
}
}
}
public static String getFontFilePath4Read(String fontName) {
return getFontFilePath(fontName, false);
}
private static String getFontFilePath(String fontName, boolean is4Write) {
if (is4Write) {
return getFontFileHolder() + (fontName);
}
// return getFontFileHolder() + (fontName +",0");
return getFontFileHolder() + (fontName);
}
private static String getFontFileHolder() {
return "." + File.separatorChar;//+ "fonts"+ File.separatorChar; 放一个多级目录创建文件夹失败了
}
}
2. 样式2
2.1 效果
2.2 代码
package cn.jiangjiesheng.utils;
import com.envtoday.ecp.core.utils.QRCodeUtil;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import java.util.Hashtable;
/**
* <dependency>
* <groupId>com.google.zxing</groupId>
* <artifactId>core</artifactId>
* <version>3.4.1</version>
* </dependency>
* <dependency>
* <groupId>com.google.zxing</groupId>
* <artifactId>javase</artifactId>
* <version>3.4.1</version>
* </dependency>
*/
@Component
@Slf4j
public class QRCodeGenerator {
private final static int FONT_SIZE = 16;
private final static String TEXT_SUFFIX = "...";
private final static String FONT_FILE_NAME = "Microsoft YaHei-normal.ttc";
private static Font FONT = null;
private static final int DPI = 300;
@PostConstruct
public void init() throws IOException, FontFormatException {
// 加载当前运行系统已有字体
// FONT = new Font("Microsoft YaHei", Font.PLAIN, FONT_SIZE);
// 加载自定义字体
try {
writeFontFile(this.getClass(), FONT_FILE_NAME, false);
String fontPath = getFontFilePath4Read(FONT_FILE_NAME);
FONT = Font.createFont(Font.TRUETYPE_FONT, new File(fontPath)).deriveFont(Font.PLAIN, FONT_SIZE);
// 必要,否则不生效
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
ge.registerFont(FONT);
} catch (IOException | FontFormatException e) {
log.error("加载自定义字体出错", e);
}
}
public static void main(String[] args) {
String text = "https://code.jiangjiesheng.cn";
String filePath = "qrcode.png";
try {
int totalImageWH = QRCodeUtil.convertPrintSize2Pixel(4, DPI);
// int qrCodeWH = convertPrintSize2Pixel(3, 300);
int qrCodeWH = QRCodeUtil.convertPrintSize2Pixel(3.5f, DPI);
int rectMargin = QRCodeUtil.convertPrintSize2Pixel(0.5f, DPI);
//BufferedImage image = createQRCodeWithText(text, "江苏今世缘酒业股份有限公司", "南厂区清下水排口", totalImageWH, qrCodeWH, rectMargin);
BufferedImage image = createQRCodeWithText(text, "xxx20日下午在xxx接见第33届夏季奥林匹克运动会中国体育代表团全体成员", "南厂区清下水排口", totalImageWH, qrCodeWH, rectMargin);
image = addBorder(image, 1, Color.BLACK);
ImageIO.write(image, "png", new File(filePath));
System.out.println("QR code generated and saved to: " + filePath);
} catch (WriterException | IOException e) {
e.printStackTrace();
}
}
/**
* 创建带文字区域的二维码图片
*
* @param qrText 二维码内容
* @param firstLine 本文区域第一行
* @param secondLine 本文区域第二行
* @param totalImageWH 图片整体宽高
* @param qrCodeWH 二维码宽高
* @param rectMargin 二维码边框距离图片边缘的距离
* @return BufferedImage
* @throws WriterException 异常
*/
public static BufferedImage createQRCodeWithText(String qrText, String firstLine, String secondLine,
int totalImageWH, int qrCodeWH, int rectMargin)
throws WriterException {
QRCodeWriter qrCodeWriter = new QRCodeWriter();
Hashtable<EncodeHintType, ErrorCorrectionLevel> hints = new Hashtable<>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
//二维码自身
BitMatrix bitMatrix = qrCodeWriter.encode(qrText, BarcodeFormat.QR_CODE, qrCodeWH, qrCodeWH, hints);
int matrixWidth = bitMatrix.getWidth();
// int textHeight = 50;
// int qrcodeRectWidth = 20;// 10*2
//整个图像的
// int totalImageWidth = matrixWidth + qrcodeRectWidth;
// int totalImageHeight = matrixWidth + textHeight;
int totalImageWidth = totalImageWH;
int totalImageHeight = totalImageWH;
BufferedImage image = new BufferedImage(totalImageWidth, totalImageHeight, BufferedImage.TYPE_INT_RGB);
image.createGraphics();
//设置整个背景白色
Graphics2D graphics = (Graphics2D) image.getGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, totalImageWidth, totalImageHeight);
// 二维码周围画一个边框
graphics.setColor(new Color(200, 200, 200)); // Light gray color
//int rectWH = totalImageWidth - rectMargin * 2 + 2;
int rectWH = totalImageWidth - rectMargin * 2 + 2;
// graphics.drawRect(rectMargin, rectMargin, matrixWidth + 2, matrixWidth + 2);
graphics.drawRect(rectMargin, rectMargin / 2, rectWH, rectWH);
// Draw the QR code bits
graphics.setColor(Color.BLACK);
int xStart = (totalImageWH - qrCodeWH) / 2;
int yStart = xStart / 2 - 12; // - 12 是动态调整
for (int i = 0; i < matrixWidth; i++) {
for (int j = 0; j < matrixWidth; j++) {
if (bitMatrix.get(i, j)) {
graphics.fillRect(i + xStart, j + yStart, 1, 1);
}
}
}
// 在二维码下方增加文字区域
graphics.setFont(FONT);
graphics.setColor(Color.BLACK);
int yStartText = matrixWidth + 10;
FontRenderContext affineTransform = graphics.getFontRenderContext();
//下方显示机构名称+测点名称,各占一行(如果文字字数过多,超二维码宽度,则显示...,不要换行)
firstLine = getShortText(matrixWidth, firstLine, affineTransform, graphics.getFontMetrics());
secondLine = getShortText(matrixWidth, secondLine, affineTransform, graphics.getFontMetrics());
graphics.drawString(firstLine, (totalImageWH - graphics.getFontMetrics().stringWidth(firstLine)) / 2, yStartText);
graphics.drawString(secondLine, (totalImageWH - graphics.getFontMetrics().stringWidth(secondLine)) / 2, yStartText + 25);
return image;
}
/**
* 如果文字字数过多,超二维码宽度,则显示...,不要换行
* 实时测量计算 + 递归调用
*
* @param maxWidth 文本行允许的最大像素宽度
* @param text 文本内容
* @param affineTransform affineTransform 测量使用
* @param fontMetrics fontMetrics 测量使用
* @return 超长文子后面显示...
*/
private static String getShortText(int maxWidth, String text, FontRenderContext affineTransform, FontMetrics fontMetrics) {
double textWidth = measureTextWidth(text, affineTransform, fontMetrics);
int subCount = text.length() - 1 - TEXT_SUFFIX.length();
if (textWidth > maxWidth && subCount >= 0) {
text = text.substring(0, subCount);
text += TEXT_SUFFIX;
return getShortText(maxWidth, text, affineTransform, fontMetrics);
}
return text;
}
/**
* 测量文字长度
*
* @param text 文本
* @param affineTransform 测量使用
* @param fontMetrics 测量使用
* @return 文本占用的像素宽度
*/
private static double measureTextWidth(String text, FontRenderContext affineTransform, FontMetrics fontMetrics) {
// 两种方案,结果相差不大
// 方案1:
// TextLayout layout = new TextLayout(text, FONT, affineTransform);
// return layout.getBounds().getWidth();
// 方案2:
return fontMetrics.stringWidth(text);
}
/**
* 目标打印尺寸转成像素:
* 像素 = (3cm / 2.54cm/inch) x 300DPI
* 1英寸等于2.54厘米。
*
* @param length 长度,单位cm
* @param dpi 打印机dpi
* @return 像素
*/
public static int convertPrintSize2Pixel(float length, int dpi) {
return (int) (length / 2.54 * dpi);
}
/**
* 压缩分辨率
* 入参改成File,图片内容就是固化的,
* BufferedImage在修改尺寸或dpi后,图片内容也会发生变动(由大图变成小图时)
*
* @param width 目标宽高
* @param dpi 目标dpi,打印常见推荐至少 >= 300
* @return BufferedImage
*/
public static BufferedImage compressImage(BufferedImage bufferedImage, int width, int dpi) {
int newWidth = convertPrintSize2Pixel(width, dpi);
Image scaledImage = bufferedImage.getScaledInstance(newWidth, newWidth, Image.SCALE_SMOOTH);
bufferedImage = new BufferedImage(newWidth, newWidth, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = bufferedImage.createGraphics();
graphics2D.drawImage(scaledImage, 0, 0, null);
graphics2D.dispose();
return bufferedImage;
}
/**
* 添加边框的方法
* @param originalImage BufferedImage
* @param borderWidthHeight 边框的宽高
* @param borderColor 边框的颜色
* @return BufferedImage
*/
private static BufferedImage addBorder(BufferedImage originalImage, int borderWidthHeight, Color borderColor) {
// 创建一个带边框的新图像
BufferedImage borderedImage = new BufferedImage(originalImage.getWidth() + borderWidthHeight * 2,
originalImage.getHeight() + borderWidthHeight * 2,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = borderedImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 绘制边框
g2d.setColor(borderColor);
g2d.fillRect(0, 0, borderedImage.getWidth(), borderedImage.getHeight());
// 绘制原始图像到新图像的中心
g2d.drawImage(originalImage, borderWidthHeight, borderWidthHeight, null);
g2d.dispose();
return borderedImage;
}
/**
* BufferedImage存入本地File文件
*
* @param image BufferedImage
* @param filePath 文件路径
* @throws IOException IO异常
*/
private static void saveImageToFile(BufferedImage image, String filePath) throws IOException {
boolean result = ImageIO.write(image, "png", new File(filePath));
if (!result) {
log.error("图片写入本地失败,file path :{} ", filePath);
} else {
log.info("图片写入本地成功,file path :{} ", filePath);
}
}
/**
* 将 BufferedImage 转换为 InputStream
*
* @param image BufferedImage
* @param format 格式
* @return 输入流
* @throws Exception 异常
*/
public static InputStream bufferedImageToInputStream(BufferedImage image, String format) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, format, baos);
baos.flush();
byte[] imageBytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes);
baos.close();
return bais;
}
/**
* 写入字体到本地
*
* @param clazz clazz
* @param fontName 自定义字体名称
* @param showLog 是否显示日日志
*/
public static void writeFontFile(Class<?> clazz, String fontName, boolean showLog) {
try {
String targetFilePath = getFontFilePath(fontName, true);
File file = new File(targetFilePath);
if (file.exists()) {
if (showLog) {
log.info("writeFontFile 已存在,不再写入");
}
return;
}
boolean newFile = file.createNewFile();
boolean writable = file.setWritable(true);
InputStream inputStream = clazz.getResourceAsStream("/font/" + fontName);
if (null == inputStream) {
throw new FileNotFoundException("字体文件未找到");
}
copyInputStreamToFile(inputStream, file);
log.info("writeFontFile 已写入到jar包当前文件夹下的./fonts/下");
} catch (IOException e) {
log.error("writeFontFile 已写入到jar包当前文件夹下的./fonts/出现异常,异常:", e);
}
}
/**
* 输入流写入到文件
*
* @param in 输入流
* @param file 文件
* @throws IOException io异常
*/
public static void copyInputStreamToFile(InputStream in, File file) throws IOException {
try (OutputStream out = Files.newOutputStream(file.toPath())) {
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
} finally {
// 确保关闭输入流以释放资源
if (in != null) {
in.close();
}
}
}
/**
* 获取自定义字体的路径
*
* @param fontName 自定义字体名称
* @return 自定义字体路径
*/
public static String getFontFilePath4Read(String fontName) {
return getFontFilePath(fontName, false);
}
private static String getFontFilePath(String fontName, boolean is4Write) {
if (is4Write) {
return getFontFileHolder() + (fontName);
}
// 如果是itextpdf服务,需要这样写
// return getFontFileHolder() + (fontName +",0");
return getFontFileHolder() + (fontName);
}
/**
* 获取自定义字体文件夹
*
* @return 自定义字体文件夹
*/
private static String getFontFileHolder() {
// 放一个多级目录创建文件夹失败了
// return "." + File.separatorChar + "fonts"+ File.separatorChar;
return "." + File.separatorChar;
}
}
3. 样式3
长方形二维码,文本区域左下侧,代码来自zxx
3.1 代码
package cn.jiangjiesheng.common;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import lombok.extern.slf4j.Slf4j;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
/**
* @Description:
* @Date 2024/8/21 13:36
* @Author martin
*/
@Slf4j
public class QRCodeWithText {
private static boolean IS_DEBUG = false;
private static final String OUTPUT_FILE = "qrcode.png"; // 输出文件名
private static final String SPACE = " "; // 输出文件名
private static int DEFAULT_QRCODE_WIDTH = 200;
private static final int FONT_SIZE = 14;
// textInfo 扩大倍数 ,按 \\n 换行(一条属性信息),一条属性信息可能比较长,还会继续换行,最终来裁切图片,此值大致表示每条属性信息最多可能的换行条数
private static final int EXPANSION_FACTOR = 5;
// 换行时左侧的空格占位
// textInfo = "设备名称: xxxx\n所属机构: 123456789123456789123456789\n所属测点: xxxx\n所属区域: 江苏省南京市江北新区安徽省安庆市\n其他信息: 联系人 xxx \n";
private static final int SPACE_COUNT = 0;
private static Font FONT = null;
public static void main(String[] args) {
try {
initFont();
// 调用方法并传入动态参数
generateQRCodeWithTable(
"Sample QR Code Data",
"南京江北新区",
"江苏联通监测点",
"123.png"
);
System.out.println("打印图片结束");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void initFont() throws IOException, FontFormatException {
// writeFontFile(this.getClass(),"Microsoft YaHei-normal.ttc",false);
String fontPath = getFontFilePath4Read("Microsoft YaHei-normal.ttc");
// 加载系统字体
FONT = new Font("Microsoft YaHei", Font.PLAIN, FONT_SIZE);
// 加载自定义字体
try {
FONT = Font.createFont(Font.TRUETYPE_FONT, new File(fontPath)).deriveFont(Font.PLAIN, FONT_SIZE);
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
ge.registerFont(FONT);
} catch (IOException | FontFormatException e) {
log.error("加载自定义字体出错", e);
}
}
public static String getFontFilePath4Read(String fontName) {
return getFontFilePath(fontName, false);
}
private static String getFontFilePath(String fontName, boolean is4Write) {
if (is4Write) {
return getFontFileHolder() + (fontName);
}
// return getFontFileHolder() + (fontName +",0");
return getFontFileHolder() + (fontName);
}
private static String getFontFileHolder() {
return "." + File.separatorChar;//+ "fonts"+ File.separatorChar; 放一个多级目录创建文件夹失败了
}
public static void generateQRCodeWithTable(String data, String orgName, String monitorName, String outputFilePath) throws WriterException, IOException {
int qrCodeWidth = 300; // 二维码的宽度
int qrCodeHeight = 300; // 二维码的高度
int tableWidth = 200; // 表格宽度
int tableHeight = 180; // 表格高度
int fixedColWidth = 60; // 第一列固定列宽度
int imageWidth = qrCodeWidth + tableWidth; // 图像宽度:二维码宽度 + 表格宽度
int imageHeight = Math.max(qrCodeHeight, tableHeight); // 图像高度:二维码高度和表格高度的最大值
int titleHeight = qrCodeHeight - tableHeight - 60; // 标题高度
// 创建二维码
QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode(data, BarcodeFormat.QR_CODE, qrCodeWidth, qrCodeHeight);
BufferedImage qrCodeImage = new BufferedImage(qrCodeWidth, qrCodeHeight, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < qrCodeWidth; x++) {
for (int y = 0; y < qrCodeHeight; y++) {
qrCodeImage.setRGB(x, y, bitMatrix.get(x, y) ? Color.BLACK.getRGB() : Color.WHITE.getRGB());
}
}
// 创建最终图像
BufferedImage finalImage = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = finalImage.createGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, imageWidth, imageHeight);
// 绘制表格
int tableX = 5; // 表格X坐标,图像左边缘
int tableY = imageHeight - tableHeight - 40; // 表格Y坐标,与底部对齐
int numRows = 3; // 表格行数
int numCols = 2; // 表格列数
int cellWidth = (tableWidth - fixedColWidth) / (numCols - 1); // 单元格宽度(排除第一列自定义的宽度)
int cellHeight = tableHeight / numRows; // 单元格高度
// 绘制表格网格
graphics.setColor(Color.BLACK);
for (int i = 0; i <= numCols; i++) {
int colWidth = i == 1 ? fixedColWidth : cellWidth; // 第一列固定宽度,第二列开始自适应宽度
graphics.drawLine(tableX + i * colWidth, tableY, tableX + i * colWidth, tableY + tableHeight);
}
for (int i = 0; i <= numRows; i++) {
graphics.drawLine(tableX, tableY + i * cellHeight, tableX + tableWidth, tableY + i * cellHeight);
}
// 绘制转置后的表格内容
graphics.setFont(new Font("Microsoft YaHei", Font.PLAIN, 12)); // 使用系统自带的中文字体
graphics.setColor(Color.BLACK);
String[][] tableData = {
{"测点名称", "机构名称", "所属区域"},
{orgName, monitorName, "江苏省南京市浦口区"}
};
// 转置表格数据
String[][] transposedData = transposeData(tableData);
// 绘制转置后的数据
FontMetrics metrics = graphics.getFontMetrics();
for (int row = 0; row < numRows; row++) {
for (int col = 0; col < numCols; col++) {
int colWidth = col == 0 ? fixedColWidth : cellWidth; // 第一列固定宽度,第二列开始自适应宽度
String cellText = transposedData[row][col]; // 转置后的数据
java.util.List<String> lines = wrapText(cellText, metrics, colWidth);
int textY = tableY + row * cellHeight + (cellHeight - lines.size() * metrics.getHeight()) / 2 + metrics.getAscent();
// 绘制每一行文本
for (String lineText : lines) {
int textX = tableX + col * colWidth + (colWidth - metrics.stringWidth(lineText)) / 2;
graphics.drawString(lineText, textX, textY);
textY += metrics.getHeight();
}
}
}
// 绘制二维码
// graphics.drawImage(qrCodeImage, tableWidth, 0, null);
// 绘制“监测点”文本在表格中间上方
String titleText = "监测点位";
Font titleFont = new Font("Microsoft YaHei", Font.BOLD, 24); // 设置标题字体和大小
graphics.setFont(titleFont);
FontMetrics fontMetrics = graphics.getFontMetrics();
int textWidth = fontMetrics.stringWidth(titleText);
int textX1 = tableX + (tableWidth - textWidth) / 2; // 中心对齐X坐标
int textY1 = tableY + 15; // 上方的Y坐标(可以调整)
graphics.drawString(titleText, textX1, titleHeight);
graphics.dispose();
// 保存图像
ImageIO.write(finalImage, "png", new File(outputFilePath));
}
// 转置二维数组
private static String[][] transposeData(String[][] data) {
int rows = data.length;
int cols = data[0].length;
String[][] transposed = new String[cols][rows];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
transposed[j][i] = data[i][j];
}
}
return transposed;
}
// 将文本分行以适应单元格宽度
private static java.util.List<String> wrapText(String text, FontMetrics metrics, int cellWidth) {
java.util.List<String> lines = new ArrayList<>();
StringBuilder line = new StringBuilder();
int lineWidth = 0;
for (char c : text.toCharArray()) {
int charWidth = metrics.charWidth(c);
if (lineWidth + charWidth > cellWidth) {
lines.add(line.toString());
line.setLength(0);
lineWidth = 0;
}
line.append(c);
lineWidth += charWidth;
}
if (line.length() > 0) {
lines.add(line.toString());
}
return lines;
}
}
正文到此结束