diff --git a/src/main/java/xyz/wbsite/jmacro/util/TableData.java b/src/main/java/xyz/wbsite/jmacro/util/TableData.java new file mode 100644 index 0000000..10667c2 --- /dev/null +++ b/src/main/java/xyz/wbsite/jmacro/util/TableData.java @@ -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模式实现 + *

+ * 用法: + * 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 head = new ArrayList<>(); + + /** + * 表格数据行 + */ + private final List> 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 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 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; + } + } +}