|
|
|
@ -0,0 +1,329 @@
|
|
|
|
|
|
|
|
package xyz.wbsite.jmacro.util;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import javax.imageio.ImageIO;
|
|
|
|
|
|
|
|
import javax.swing.*;
|
|
|
|
|
|
|
|
import java.awt.*;
|
|
|
|
|
|
|
|
import java.awt.datatransfer.Clipboard;
|
|
|
|
|
|
|
|
import java.awt.datatransfer.DataFlavor;
|
|
|
|
|
|
|
|
import java.awt.datatransfer.Transferable;
|
|
|
|
|
|
|
|
import java.awt.datatransfer.UnsupportedFlavorException;
|
|
|
|
|
|
|
|
import java.awt.image.BufferedImage;
|
|
|
|
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 表格工具 - Builder模式实现
|
|
|
|
|
|
|
|
* <p>
|
|
|
|
|
|
|
|
* 用法:
|
|
|
|
|
|
|
|
* TableData.create()
|
|
|
|
|
|
|
|
* ㅤ.addHead("姓名", "年龄", "职业")
|
|
|
|
|
|
|
|
* ㅤ.addData("张三", "25", "工程师")
|
|
|
|
|
|
|
|
* ㅤ.addData("李四", "30", "产品经理")
|
|
|
|
|
|
|
|
* ㅤ.writeToFile(new File("table.png"));
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @author wangbing
|
|
|
|
|
|
|
|
* @version 0.0.1
|
|
|
|
|
|
|
|
* @since 1.8
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public class TableData {
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 默认字体
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private static final Font DEFAULT_FONT = new Font(null, Font.PLAIN, 14);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 表格表头
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private final List<String> head = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 表格数据行
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private final List<List<String>> body = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 私有构造函数
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private TableData() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 创建TableData构建器实例
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @return TableData.Builder实例
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public static TableData create() {
|
|
|
|
|
|
|
|
return new TableData();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 添加表头
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @param headers 表头数组
|
|
|
|
|
|
|
|
* @return Builder实例
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public TableData addHead(String... headers) {
|
|
|
|
|
|
|
|
this.head.addAll(Arrays.asList(headers));
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 添加数据行
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @param rowData 行数据数组
|
|
|
|
|
|
|
|
* @return Builder实例
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public TableData addData(String... rowData) {
|
|
|
|
|
|
|
|
List<String> row = new ArrayList<>(Arrays.asList(rowData));
|
|
|
|
|
|
|
|
this.body.add(row);
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String[][] convertToArrays() {
|
|
|
|
|
|
|
|
// 验证数据完整性
|
|
|
|
|
|
|
|
if (head.isEmpty()) {
|
|
|
|
|
|
|
|
throw new IllegalStateException("必须添加表头");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int rows = this.body.size() + 1;
|
|
|
|
|
|
|
|
int cols = this.head.size();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String[][] data = new String[rows][cols];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 填充表头
|
|
|
|
|
|
|
|
for (int j = 0; j < cols; j++) {
|
|
|
|
|
|
|
|
data[0][j] = this.head.get(j);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 填充数据行
|
|
|
|
|
|
|
|
for (int i = 0; i < this.body.size(); i++) {
|
|
|
|
|
|
|
|
List<String> row = this.body.get(i);
|
|
|
|
|
|
|
|
for (int j = 0; j < cols; j++) {
|
|
|
|
|
|
|
|
data[i + 1][j] = row.get(j);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 计算每列的宽度
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @param data 表格数据
|
|
|
|
|
|
|
|
* @return 每列的宽度数组
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private int[] calculateColumnWidths(String[][] data) {
|
|
|
|
|
|
|
|
int rows = data.length;
|
|
|
|
|
|
|
|
int cols = data[0].length;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 创建临时Graphics2D对象用于测量文本
|
|
|
|
|
|
|
|
BufferedImage tempImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
|
|
|
|
|
|
|
|
Graphics2D tempG2D = tempImage.createGraphics();
|
|
|
|
|
|
|
|
tempG2D.setFont(DEFAULT_FONT);
|
|
|
|
|
|
|
|
FontMetrics metrics = tempG2D.getFontMetrics();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算每列的最大宽度
|
|
|
|
|
|
|
|
int[] colWidths = new int[cols];
|
|
|
|
|
|
|
|
for (int j = 0; j < cols; j++) {
|
|
|
|
|
|
|
|
int maxWidth = 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < rows; i++) {
|
|
|
|
|
|
|
|
int textWidth = metrics.stringWidth(data[i][j]);
|
|
|
|
|
|
|
|
if (textWidth > maxWidth) {
|
|
|
|
|
|
|
|
maxWidth = textWidth;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
colWidths[j] = maxWidth + 20; // 添加内边距
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tempG2D.dispose();
|
|
|
|
|
|
|
|
tempImage.flush();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return colWidths;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 根据表格内容计算合适的图像尺寸
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @return 推荐的图像尺寸
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private Dimension calculateTableDimension() {
|
|
|
|
|
|
|
|
// 将数据转换为二维数组格式
|
|
|
|
|
|
|
|
String[][] data = convertToArrays();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int rows = data.length;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算每列的宽度
|
|
|
|
|
|
|
|
int[] colWidths = calculateColumnWidths(data);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 创建临时Graphics2D对象用于测量文本高度
|
|
|
|
|
|
|
|
BufferedImage tempImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
|
|
|
|
|
|
|
|
Graphics2D tempG2D = tempImage.createGraphics();
|
|
|
|
|
|
|
|
tempG2D.setFont(DEFAULT_FONT);
|
|
|
|
|
|
|
|
FontMetrics metrics = tempG2D.getFontMetrics();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算每行的高度(基于字体高度) 添加一些内边距
|
|
|
|
|
|
|
|
int rowHeight = metrics.getHeight() + 10;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算总宽度和高度
|
|
|
|
|
|
|
|
int totalWidth = 0;
|
|
|
|
|
|
|
|
for (int width : colWidths) {
|
|
|
|
|
|
|
|
totalWidth += width;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int totalHeight = rowHeight * rows;
|
|
|
|
|
|
|
|
// 确保最小尺寸
|
|
|
|
|
|
|
|
totalWidth = Math.max(totalWidth, 100) + 1;
|
|
|
|
|
|
|
|
totalHeight = Math.max(totalHeight, 100) + 1;
|
|
|
|
|
|
|
|
tempG2D.dispose();
|
|
|
|
|
|
|
|
tempImage.flush();
|
|
|
|
|
|
|
|
return new Dimension(totalWidth, totalHeight);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 绘制表格图片
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @return 表格图片
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private BufferedImage drawTable() {
|
|
|
|
|
|
|
|
// 将数据转换为二维数组格式
|
|
|
|
|
|
|
|
String[][] data = convertToArrays();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算自适应的图像尺寸
|
|
|
|
|
|
|
|
Dimension dimension = calculateTableDimension();
|
|
|
|
|
|
|
|
int width = dimension.width;
|
|
|
|
|
|
|
|
int height = dimension.height;
|
|
|
|
|
|
|
|
// 创建图像
|
|
|
|
|
|
|
|
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
|
|
|
|
|
|
|
Graphics2D g2d = image.createGraphics();
|
|
|
|
|
|
|
|
// 设置背景色为白色
|
|
|
|
|
|
|
|
g2d.setColor(Color.WHITE);
|
|
|
|
|
|
|
|
g2d.fillRect(0, 0, width, height);
|
|
|
|
|
|
|
|
// 设置画笔颜色(边框和文字)
|
|
|
|
|
|
|
|
g2d.setColor(Color.BLACK);
|
|
|
|
|
|
|
|
g2d.setFont(DEFAULT_FONT);
|
|
|
|
|
|
|
|
// 计算行列数
|
|
|
|
|
|
|
|
int rows = data.length;
|
|
|
|
|
|
|
|
int cols = data[0].length;
|
|
|
|
|
|
|
|
// 计算单元格宽度(按内容比例分配)
|
|
|
|
|
|
|
|
int[] colWidths = calculateColumnWidths(data);
|
|
|
|
|
|
|
|
int totalPreferredWidth = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int widthValue : colWidths) {
|
|
|
|
|
|
|
|
totalPreferredWidth += widthValue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果总推荐宽度大于实际宽度,则按比例调整
|
|
|
|
|
|
|
|
if (totalPreferredWidth > width) {
|
|
|
|
|
|
|
|
for (int j = 0; j < cols; j++) {
|
|
|
|
|
|
|
|
colWidths[j] = (int) ((double) colWidths[j] / totalPreferredWidth * width);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算单元格高度(平均分配)
|
|
|
|
|
|
|
|
int cellHeight = height / rows;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制表格边框和内容
|
|
|
|
|
|
|
|
int y = 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < rows; i++) {
|
|
|
|
|
|
|
|
int x = 0;
|
|
|
|
|
|
|
|
for (int j = 0; j < cols; j++) {
|
|
|
|
|
|
|
|
int cellWidth = colWidths[j];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制单元格边框
|
|
|
|
|
|
|
|
g2d.drawRect(x, y, cellWidth, cellHeight);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制单元格内容(居中显示)
|
|
|
|
|
|
|
|
String text = data[i][j];
|
|
|
|
|
|
|
|
FontMetrics fm = g2d.getFontMetrics();
|
|
|
|
|
|
|
|
int textX = x + (cellWidth - fm.stringWidth(text)) / 2;
|
|
|
|
|
|
|
|
int textY = y + (cellHeight + fm.getAscent()) / 2 - fm.getDescent();
|
|
|
|
|
|
|
|
g2d.drawString(text, textX, textY);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
x += cellWidth;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
y += cellHeight;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
g2d.dispose();
|
|
|
|
|
|
|
|
return image;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 将图片写入文件
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @param file 目标文件
|
|
|
|
|
|
|
|
* @throws IOException 文件写入异常
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public void writeToFile(File file) throws IOException {
|
|
|
|
|
|
|
|
BufferedImage tableImage = drawTable();
|
|
|
|
|
|
|
|
ImageIO.write(tableImage, "png", file);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 将图片复制到系统剪贴板
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @throws Exception 复制到剪贴板异常
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public void writeToClipboard() throws Exception {
|
|
|
|
|
|
|
|
BufferedImage tableImage = drawTable();
|
|
|
|
|
|
|
|
copyImageToClipboard(tableImage);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 将图片转换为字节数组
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @return 图片字节数组
|
|
|
|
|
|
|
|
* @throws IOException 转换异常
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public byte[] writeToByteArray() throws IOException {
|
|
|
|
|
|
|
|
BufferedImage tableImage = drawTable();
|
|
|
|
|
|
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
|
|
|
|
|
|
ImageIO.write(tableImage, "png", baos);
|
|
|
|
|
|
|
|
return baos.toByteArray();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 将图片复制到系统剪贴板
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @param image 要复制的图片
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private static void copyImageToClipboard(BufferedImage image) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
// 将BufferedImage转为ImageIcon(剪贴板支持的格式)
|
|
|
|
|
|
|
|
ImageIcon imageIcon = new ImageIcon(image);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 创建剪贴板内容
|
|
|
|
|
|
|
|
Transferable transferable = new Transferable() {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public DataFlavor[] getTransferDataFlavors() {
|
|
|
|
|
|
|
|
return new DataFlavor[]{DataFlavor.imageFlavor};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public boolean isDataFlavorSupported(DataFlavor flavor) {
|
|
|
|
|
|
|
|
return DataFlavor.imageFlavor.equals(flavor);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
|
|
|
|
|
|
|
|
if (DataFlavor.imageFlavor.equals(flavor)) {
|
|
|
|
|
|
|
|
return imageIcon.getImage();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new UnsupportedFlavorException(flavor);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取系统剪贴板并设置内容
|
|
|
|
|
|
|
|
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
|
|
|
|
|
|
|
clipboard.setContents(transferable, null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
throw e;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|