From 819ef48d18cf7232056f1f9dfede9a71cad0e9e0 Mon Sep 17 00:00:00 2001 From: kehfan Date: Thu, 22 Aug 2024 15:31:33 +0800 Subject: [PATCH 1/3] get from main-merge --- pdf-generator/pom.xml | 154 ++++++ .../lowcode/pdf/extension/Excel2Pdf.java | 439 ++++++++++++++++++ .../lowcode/pdf/extension/PdfGenerator.java | 298 ++++++++++++ .../itextpdf/FooterIEventHandler.java | 91 ++++ .../itextpdf/HeaderIEventHandler.java | 99 ++++ .../pdf/extension/itextpdf/NodeCreator.java | 342 ++++++++++++++ .../itextpdf/PageNumberIEventHandler.java | 109 +++++ .../pdf/extension/itextpdf/PdfUtils.java | 109 +++++ .../itextpdf/WaterMaskIEventHandler.java | 111 +++++ .../extension/structures/BaseResponse.java | 84 ++++ .../structures/CreateByTemplateRequest.java | 35 ++ .../structures/CreateByXlsxRequest.java | 54 +++ .../extension/structures/CreateRequest.java | 16 + .../extension/structures/FontStructure.java | 32 ++ .../extension/structures/NodeTypeEnum.java | 37 ++ .../pdf/extension/utils/FileUtils.java | 206 ++++++++ .../pdf/extension/utils/FreemarkerUtils.java | 57 +++ .../pdf/extension/utils/JSONObjectUtil.java | 155 +++++++ .../extension/utils/UploadResponseDTO.java | 49 ++ .../main/resources/META-INF/spring.factories | 3 + .../src/main/resources/netease_logo.png | Bin 0 -> 5478 bytes .../src/main/resources/netease_logo2.png | Bin 0 -> 5560 bytes pdf-generator/src/main/resources/pdf.json | 351 ++++++++++++++ .../src/main/resources/requestData.json | 24 + pdf-generator/src/main/resources/result.json | 288 ++++++++++++ .../src/main/resources/template.json | 315 +++++++++++++ .../src/test/java/Excel2PdfTest.java | 35 ++ pdf-generator/src/test/java/FontTest.java | 31 ++ pdf-generator/src/test/java/ImageTest.java | 29 ++ .../src/test/java/JSONObjectUtilTest.java | 22 + .../src/test/java/PdfGenerator2Test.java | 19 + .../src/test/java/PdfGeneratorTest.java | 49 ++ .../src/test/java/PdfGeneratorV2Test.java | 17 + pdf-generator/src/test/java/PdfUtilTest.java | 7 + .../src/test/java/TemplateUtilsTest.java | 81 ++++ 35 files changed, 3748 insertions(+) create mode 100644 pdf-generator/pom.xml create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/Excel2Pdf.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/PdfGenerator.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/FooterIEventHandler.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/HeaderIEventHandler.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/NodeCreator.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/PageNumberIEventHandler.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/PdfUtils.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/WaterMaskIEventHandler.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/BaseResponse.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/CreateByTemplateRequest.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/CreateByXlsxRequest.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/CreateRequest.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/FontStructure.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/NodeTypeEnum.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/FileUtils.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/FreemarkerUtils.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/JSONObjectUtil.java create mode 100644 pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/UploadResponseDTO.java create mode 100644 pdf-generator/src/main/resources/META-INF/spring.factories create mode 100644 pdf-generator/src/main/resources/netease_logo.png create mode 100644 pdf-generator/src/main/resources/netease_logo2.png create mode 100644 pdf-generator/src/main/resources/pdf.json create mode 100644 pdf-generator/src/main/resources/requestData.json create mode 100644 pdf-generator/src/main/resources/result.json create mode 100644 pdf-generator/src/main/resources/template.json create mode 100644 pdf-generator/src/test/java/Excel2PdfTest.java create mode 100644 pdf-generator/src/test/java/FontTest.java create mode 100644 pdf-generator/src/test/java/ImageTest.java create mode 100644 pdf-generator/src/test/java/JSONObjectUtilTest.java create mode 100644 pdf-generator/src/test/java/PdfGenerator2Test.java create mode 100644 pdf-generator/src/test/java/PdfGeneratorTest.java create mode 100644 pdf-generator/src/test/java/PdfGeneratorV2Test.java create mode 100644 pdf-generator/src/test/java/PdfUtilTest.java create mode 100644 pdf-generator/src/test/java/TemplateUtilsTest.java diff --git a/pdf-generator/pom.xml b/pdf-generator/pom.xml new file mode 100644 index 000000000..6ca546d2a --- /dev/null +++ b/pdf-generator/pom.xml @@ -0,0 +1,154 @@ + + + 4.0.0 + + com.netease.lowcode.extension + pdf-generator + 0.0.23 + + + 8 + 8 + UTF-8 + + + + nasl-metadata-collector + com.netease.lowcode + 0.8.0 + true + + + + + com.itextpdf + itextpdf + 5.5.10 + + + com.itextpdf + itext-asian + 5.2.0 + + + + + + com.itextpdf + itext-core + 8.0.4 + pom + + + + org.apache.poi + poi + 4.1.2 + + + org.apache.poi + poi-ooxml + 4.1.2 + + + org.apache.poi + poi-ooxml-schemas + 4.1.2 + + + org.apache.poi + poi-scratchpad + 4.1.2 + + + org.apache.poi + poi-excelant + 4.1.2 + + + + + org.apache.commons + commons-collections4 + 4.4 + + + + org.apache.commons + commons-lang3 + 3.14.0 + + + + com.alibaba.fastjson2 + fastjson2 + 2.0.51 + + + + org.freemarker + freemarker + 2.3.31 + + + com.squareup.okhttp3 + okhttp + 3.14.9 + + + + org.springframework + spring-context + 5.2.8.RELEASE + provided + + + org.springframework + spring-web + 5.2.8.RELEASE + provided + + + org.apache.tomcat.embed + tomcat-embed-core + 9.0.37 + provided + + + + + + + com.netease.lowcode + nasl-metadata-maven-plugin + 1.4.2 + + + org.apache.maven.shared + maven-common-artifact-filters + 3.0.1 + + + org.apache.commons + commons-lang3 + 3.13.0 + + + + + + archive + + + + + + false + false + + + + + \ No newline at end of file diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/Excel2Pdf.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/Excel2Pdf.java new file mode 100644 index 000000000..4118c31cb --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/Excel2Pdf.java @@ -0,0 +1,439 @@ +package com.netease.lowcode.pdf.extension; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.netease.lowcode.core.annotation.NaslLogic; +import com.netease.lowcode.pdf.extension.structures.BaseResponse; +import com.netease.lowcode.pdf.extension.structures.CreateByXlsxRequest; +import com.netease.lowcode.pdf.extension.utils.FileUtils; +import com.netease.lowcode.pdf.extension.utils.JSONObjectUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFColor; +import org.apache.poi.xssf.usermodel.XSSFFont; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + +public class Excel2Pdf { + + + @NaslLogic + public static BaseResponse xlsx2pdf(CreateByXlsxRequest request) { + + try { + if (StringUtils.isBlank(request.getExportFileName()) || !request.getExportFileName().endsWith(".pdf")) { + return BaseResponse.FAIL("exportFileName必须以 .pdf 结尾"); + } + + // 下载模板文件 + File templateFile = FileUtils.downloadFile(request.getTemplateUrl()); + + String fileName = templateFile.getName(); + InputStream inputStream = new FileInputStream(templateFile); + + Workbook wb = null; + if (fileName.endsWith(".xlsx")) { + // 解析 *.xlsx + wb = new XSSFWorkbook(inputStream); + } else { + return BaseResponse.FAIL("仅支持 *.xlsx 文件"); + } + + JSONObject jsonObject = new JSONObject(); + jsonObject.put("fileName",request.getExportFileName()); + // 字体设置 + jsonObject.put("font",new HashMap(){{ + put("fontProgram","STSong-Light"); + put("encoding","UniGB-UCS2-H"); + }}); + // 纸张大小A4 + jsonObject.put("pageSize",request.getPageSize()); + // 纸张方向 + jsonObject.put("rotate",request.getRotate()); + JSONArray nodes = new JSONArray(); + jsonObject.put("nodes",nodes); + + // 读取第0个sheet + Sheet sheet0 = wb.getSheetAt(0); + if (Objects.isNull(sheet0)) { + return BaseResponse.FAIL("读取sheet0为空"); + } + + JSONObject table = new JSONObject(); + table.put("type","Table"); + table.put("width",100); + + JSONArray cells = new JSONArray(); + table.put("cells", cells); + nodes.add(table); + + // 记录整个sheet列宽度 + List sheetColWidthList = new ArrayList<>(); + // 暂存单元格 + List> tmpCells = new ArrayList<>(); + + // 遍历sheet行 + for (int i = 0; i <= sheet0.getLastRowNum(); i++) { + Row row = sheet0.getRow(i); + // 暂存该行单元格 + List curRowTmpCells = new ArrayList<>(); + tmpCells.add(curRowTmpCells); + + if(Objects.isNull(row)){ + // 保留空行 + continue; + } + + List currentRowColWidths = new ArrayList<>(); + // 遍历列,注意模板不要超过A4的宽度 + for (int j = 0; j < row.getLastCellNum(); j++) { + + JSONObject jsonCell = new JSONObject(); + JSONArray elements = new JSONArray(); + JSONObject paragraph = new JSONObject(); + paragraph.put("type","Paragraph"); + elements.add(paragraph); + jsonCell.put("elements", elements); + + curRowTmpCells.add(jsonCell); + + // 获取单元格 + Cell cell = row.getCell(j); + + if (Objects.isNull(cell)) { + currentRowColWidths.add(0); + continue; + } + + currentRowColWidths.add(sheet0.getColumnWidth(j)); + + // 单元格字体 + Font font = wb.getFontAt(cell.getCellStyle().getFontIndexAsInt()); + // 字体颜色 + if (font instanceof XSSFFont) { + XSSFFont xssfFont = (XSSFFont) font; + XSSFColor xssfColor = xssfFont.getXSSFColor(); + if(Objects.nonNull(xssfColor)) { + byte[] rgb = xssfColor.getRGB(); + paragraph.put("rgb", new HashMap() { + { + put("red", (rgb[0] < 0) ? (rgb[0] + 256) : rgb[0]); + put("green", (rgb[1] < 0) ? (rgb[1] + 256) : rgb[1]); + put("blue", (rgb[2] < 0) ? (rgb[2] + 256) : rgb[2]); + } + }); + } + } + // 字体大小 + short fontSize = font.getFontHeightInPoints(); + paragraph.put("fontSize",fontSize); + // 字体是否加粗 + boolean bold = font.getBold(); + paragraph.put("bold", bold); + // 下划线 + byte underline = font.getUnderline(); + + + // 单元格类型 + CellType cellType = cell.getCellType(); + if (CellType.STRING.equals(cellType)) { + paragraph.put("text", cell.getStringCellValue()); + } + + + // 判断水平居中 + HorizontalAlignment alignment = cell.getCellStyle().getAlignment(); + if (HorizontalAlignment.CENTER.equals(alignment)) { + jsonCell.put("textAlignment", "CENTER"); + } + // 判断垂直居中 + VerticalAlignment verticalAlignment = cell.getCellStyle().getVerticalAlignment(); + if (VerticalAlignment.CENTER.equals(verticalAlignment)) { + + } + + // 表格底部边框 + BorderStyle borderBottom = cell.getCellStyle().getBorderBottom(); + if (BorderStyle.THIN.equals(borderBottom)) { + JSONObject value = new JSONObject(); + value.put("width", 1); + jsonCell.put("borderBottom", value); + } + BorderStyle borderTop = cell.getCellStyle().getBorderTop(); + if (BorderStyle.THIN.equals(borderTop)) { + JSONObject value = new JSONObject(); + value.put("width", 1); + jsonCell.put("borderTop", value); + } + BorderStyle borderLeft = cell.getCellStyle().getBorderLeft(); + if (BorderStyle.THIN.equals(borderLeft)) { + JSONObject value = new JSONObject(); + value.put("width", 1); + jsonCell.put("borderLeft", value); + } + BorderStyle borderRight = cell.getCellStyle().getBorderRight(); + if (BorderStyle.THIN.equals(borderRight)) { + JSONObject value = new JSONObject(); + value.put("width", 1); + jsonCell.put("borderRight", value); + } + + } + + // 设置宽度 + if (currentRowColWidths.size() > sheetColWidthList.size()) { + for (int j = 0; j < sheetColWidthList.size(); j++) { + if (sheetColWidthList.get(j) == 0) { + sheetColWidthList.set(j, currentRowColWidths.get(j)); + } + } + int size = sheetColWidthList.size(); + for (int j = size; j < currentRowColWidths.size(); j++) { + sheetColWidthList.add(currentRowColWidths.get(j)); + } + } else { + for (int j = 0; j < currentRowColWidths.size(); j++) { + if (sheetColWidthList.get(j) == 0) { + sheetColWidthList.set(j, currentRowColWidths.get(j)); + } + } + } + } + // 单元格末尾对齐填充 + int maxSize = tmpCells.stream().mapToInt(List::size).max().orElse(0); + for (List tmpRow : tmpCells) { + if (tmpRow.size() < maxSize) { + int diff = maxSize - tmpRow.size(); + for (int i = 0; i < diff; i++) { + JSONObject jsonCell = new JSONObject(); + JSONArray elements = new JSONArray(); + JSONObject paragraph = new JSONObject(); + paragraph.put("type", "Paragraph"); + elements.add(paragraph); + jsonCell.put("elements", elements); + tmpRow.add(jsonCell); + } + } + } + + // 标记合并区域 + List mergedRegions = sheet0.getMergedRegions(); + if(CollectionUtils.isNotEmpty(mergedRegions)){ + for (CellRangeAddress mergedRegion : mergedRegions) { + // 得到的单元格坐标是按照合并前的坐标来计算,因此使用cloneTmpCells + int firstRow = mergedRegion.getFirstRow(); + int lastRow = mergedRegion.getLastRow(); + int firstColumn = mergedRegion.getFirstColumn(); + int lastColumn = mergedRegion.getLastColumn(); + + // 记录合并单元格 + JSONObject mergeCell = tmpCells.get(firstRow).get(firstColumn); + mergeCell.put("rowspan", lastRow - firstRow + 1); + mergeCell.put("colspan", lastColumn - firstColumn + 1); + + // 处理合并单元格边框,仅需处理右侧、底部 + if (tmpCells.get(firstRow).get(lastColumn).containsKey("borderRight")) { + mergeCell.put("borderRight", tmpCells.get(firstRow).get(lastColumn).getJSONObject("borderRight")); + } + if (tmpCells.get(lastRow).get(firstColumn).containsKey("borderLeft")) { + mergeCell.put("borderLeft", tmpCells.get(lastRow).get(firstColumn).getJSONObject("borderLeft")); + } + if (tmpCells.get(lastRow).get(lastColumn).containsKey("borderBottom")) { + mergeCell.put("borderBottom", tmpCells.get(lastRow).get(lastColumn).getJSONObject("borderBottom")); + } + + // 处理合并区域 + for (int i = firstRow; i <= lastRow; i++) { + List list = tmpCells.get(i); + List newList = new ArrayList<>(); + + for (int j = 0; j < list.size(); j++) { + // 当前行 合并区域处理 + if (j >= firstColumn && j <= lastColumn) { + // 合并区域 首行 第一个单元格 填充mergeCell + if (i == firstRow && j == firstColumn) { + newList.add(mergeCell); + continue; + } + // 合并区域 其他单元格 用占位标记,后续统一删除 + JSONObject e = new JSONObject(); + e.put("mergeTagWillBeDeleted", ""); + newList.add(e); + continue; + } + + // 非合并区域 用原始单元格填充 + newList.add(list.get(j)); + } + // 替换新行 + tmpCells.set(i, newList); + } + } + } + // 移除被合并单元格 + tmpCells.removeIf(curRow -> { + curRow.removeIf(next -> next.containsKey("mergeTagWillBeDeleted")); + return curRow.isEmpty(); + }); + + // 从全局设置cell宽度 + int totalColWidth = sheetColWidthList.stream().mapToInt(Integer::intValue).sum(); + for (int i = 0; i < tmpCells.size(); i++) { + List list = tmpCells.get(i); + // 处理该行 + for (int j = 0; j < list.size(); j++) { + JSONObject currentCell = list.get(j); + if (currentCell.containsKey("rowspan") && currentCell.containsKey("colspan")) { + Integer colspan = currentCell.getInteger("colspan"); + float value = 0.0f; + for (int k = j; k < j + colspan; k++) { + value += ((sheetColWidthList.get(k) / (float) totalColWidth) * 100); + } + currentCell.put("width", Math.round(value)); + } else { + float value = (sheetColWidthList.get(j) / (float) totalColWidth) * 100; + currentCell.put("width", Math.round(value)); + } + } + } + + // 处理freemarker list + handleFreemarkerList(tmpCells,request.getJsonData()); + + for (int i = 0; i < tmpCells.size(); i++) { + List list = tmpCells.get(i); + for (JSONObject object : list) { + // 去除单元格默认边框 + object.put("noBorder", true); + } + cells.addAll(list); + } + + // 设置表格列数 + table.put("columnSize",maxSize); + // 由于将整个sheet解析为一个完整的table,因此不再设置chunkSize + // table.put("chunkSize",2); + + BaseResponse response = PdfGenerator.createPDFV2ByStr(request.getJsonData(), jsonObject.toJSONString()); + if (response.getSuccess()) { + return BaseResponse.OK(response.filePath, response.result); + } + + return BaseResponse.FAIL(response.trace, "pdf创建失败:" + response.msg); + } catch (IOException e) { + return BaseResponse.FAIL(Arrays.toString(e.getStackTrace()), e.getMessage()); + } + } + + /** + * 处理freemarker list标签 + * + * @param tmpCells + */ + public static void handleFreemarkerList(List> tmpCells, String jsonData) { + if (CollectionUtils.isEmpty(tmpCells)) { + return; + } + + // 解析参数 + JSONObject requestJsonData = null; + + int i = 0; + while (i < tmpCells.size()) { + // 当前行 + List originRow = tmpCells.get(i); + if (CollectionUtils.isEmpty(originRow)) { + i++; + continue; + } + + // 判断当前行是否包含freemarker list标签 ${list.arr} + boolean hasFreemarkerListTag = false; + for (int j = 0; j < originRow.size(); j++) { + JSONObject originCell = originRow.get(j); + if (isFreemarkerListTag(originCell)) { + hasFreemarkerListTag = true; + break; + } + } + + // 包含list标签,开始处理 + if (hasFreemarkerListTag) { + if (Objects.isNull(requestJsonData)) { + requestJsonData = JSONObject.parseObject(jsonData); + } + List> newRows = JSONObjectUtil.fillListData(originRow, requestJsonData); + // 将originRow 替换为 newRows + for (int j = 0; j < newRows.size(); j++) { + tmpCells.add(i + j, newRows.get(j)); + } + // 移除originRow + tmpCells.remove(i + newRows.size()); + i += newRows.size(); + } else { + i++; + } + } + } + + /** + * 获取cell内paragraph的text字段值 + * + * @param cell + * @return + */ + public static String getCellText(JSONObject cell) { + if (Objects.isNull(cell)) { + return null; + } + if (!cell.containsKey("elements")) { + return null; + } + JSONArray elements = cell.getJSONArray("elements"); + if (Objects.isNull(elements) || elements.isEmpty()) { + return null; + } + JSONObject paragraph = elements.getJSONObject(0); + if (!paragraph.containsKey("text")) { + return null; + } + return paragraph.getString("text"); + } + + /** + * 判断一个cell是否为freemarker 的list 标签 + * 匹配 ${xx.xxx} xx为list变量名,xxx为item属性名 + * + * @param cell + * @return + */ + public static boolean isFreemarkerListTag(JSONObject cell) { + if (Objects.isNull(cell)) { + return false; + } + if (!cell.containsKey("elements")) { + return false; + } + JSONArray elements = cell.getJSONArray("elements"); + if (Objects.isNull(elements) || elements.isEmpty()) { + return false; + } + JSONObject paragraph = elements.getJSONObject(0); + if (!paragraph.containsKey("text")) { + return false; + } + String text = paragraph.getString("text"); + if (StringUtils.isNotBlank(text) && text.startsWith("${") && text.endsWith("}") && text.contains(".")) { + return true; + } + + return false; + } +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/PdfGenerator.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/PdfGenerator.java new file mode 100644 index 000000000..6abdb9226 --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/PdfGenerator.java @@ -0,0 +1,298 @@ +package com.netease.lowcode.pdf.extension; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.itextpdf.forms.PdfAcroForm; +import com.itextpdf.forms.fields.PdfFormField; +import com.itextpdf.kernel.font.PdfFont; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.*; +import com.netease.lowcode.pdf.extension.itextpdf.NodeCreator; +import com.netease.lowcode.pdf.extension.itextpdf.PdfUtils; +import com.netease.lowcode.pdf.extension.structures.BaseResponse; +import com.netease.lowcode.pdf.extension.structures.CreateByTemplateRequest; +import com.netease.lowcode.pdf.extension.structures.CreateRequest; +import com.netease.lowcode.pdf.extension.utils.FileUtils; +import com.netease.lowcode.pdf.extension.utils.FreemarkerUtils; +import com.netease.lowcode.pdf.extension.utils.UploadResponseDTO; +import com.netease.lowcode.core.annotation.NaslLogic; +import freemarker.template.TemplateException; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +// Press Shift twice to open the Search Everywhere dialog and type `show whitespaces`, +// then press Enter. You can now see whitespace characters in your code. +@Component("libraryPdfGenerator") +public class PdfGenerator { + + private static FileUtils fileUtils; + + @Autowired + @Qualifier("pdfGeneratorFileUtils") + public void setFileUtils(FileUtils fileUtils) { + PdfGenerator.fileUtils = fileUtils; + } + + @NaslLogic + public static BaseResponse createPDFByTemplate(CreateByTemplateRequest request) { + String outPath = "data/" + System.currentTimeMillis() + "/"; + try { + // 下载模板文件 + File file = FileUtils.downloadFile(request.templateUrl); + + // 创建暂存目录 + File dir = new File(outPath); + dir.mkdirs(); + + com.itextpdf.kernel.pdf.PdfDocument pdfDocument = + new com.itextpdf.kernel.pdf.PdfDocument( + new com.itextpdf.kernel.pdf.PdfReader(file), + new com.itextpdf.kernel.pdf.PdfWriter(outPath+request.exportFileName) + ); + PdfAcroForm pdfAcroForm = PdfAcroForm.getAcroForm(pdfDocument,true); + + // 获取模板字段 + Map formFields = pdfAcroForm.getAllFormFields(); + // 获取填充值 + Map dataMap = JSON.parseObject(request.jsonData, Map.class); + + formFields.forEach((key,value)->{ + try { + // 处理中文乱码 + PdfFont font = PdfFontFactory.createFont("STSong-Light","UniGB-UCS2-H"); + value.setFont(font); + value.setValue(dataMap.get(key)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + // 设置不可编辑 + pdfAcroForm.flattenFields(); + pdfDocument.close(); + + // 上传文件 + UploadResponseDTO uploadResponseDTO = fileUtils.uploadFileV2(new File(outPath + request.exportFileName)); + + return BaseResponse.OK(uploadResponseDTO.getFilePath(), uploadResponseDTO.getResult()); + + } catch (IOException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { + return BaseResponse.FAIL(Arrays.toString(e.getStackTrace()), e.getMessage()); + } + + } + + /** + * 传入的是模板文件url + * + * @param jsonData + * @param templateUrl + * @return + */ + @NaslLogic + public static BaseResponse createPDFV2(String jsonData, String templateUrl) { + try { + + ByteArrayInputStream byteArrayInputStream = FreemarkerUtils.getFreemarkerContentInputStreamV2(jsonData, templateUrl); + + BufferedReader br = new BufferedReader(new InputStreamReader(byteArrayInputStream)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) { + sb.append(line); + } + + JSONObject jsonObject = JSONObject.parseObject(sb.toString()); +// JSONObject jsonObject = JSONObject.parseObject(PdfUtils.readJson("pdf-generator/src/main/resources/result.json")); + ByteArrayOutputStream byteArrayOutputStream = NodeCreator.node(jsonObject); + ByteArrayInputStream uploadStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); + + UploadResponseDTO uploadResponseDTO = FileUtils.uploadStream(uploadStream, jsonObject.getString("fileName")); + + return BaseResponse.OK(uploadResponseDTO.getFilePath(), uploadResponseDTO.getResult()); + } catch (Exception e) { + return BaseResponse.FAIL(Arrays.toString(e.getStackTrace()), e.getMessage()); + } + } + + /** + * 传入的是模板文件json字符串 + * + * @param jsonData + * @param jsonTemplate + * @return + */ + @NaslLogic + public static BaseResponse createPDFV2ByStr(String jsonData, String jsonTemplate) { + try { + ByteArrayInputStream byteArrayInputStream = FreemarkerUtils.getFreemarkerContentInputStream(jsonData, jsonTemplate); + BufferedReader br = new BufferedReader(new InputStreamReader(byteArrayInputStream)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) { + sb.append(line); + } + JSONObject jsonObject = JSONObject.parseObject(sb.toString()); + ByteArrayOutputStream byteArrayOutputStream = NodeCreator.node(jsonObject); + ByteArrayInputStream uploadStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); + +// TODO:上传文件 + UploadResponseDTO uploadResponseDTO = FileUtils.uploadStream(uploadStream, jsonObject.getString("fileName")); + + return BaseResponse.OK(uploadResponseDTO.getFilePath(), uploadResponseDTO.getResult()); + +// FileOutputStream fos = new FileOutputStream(jsonObject.getString("fileName")); +// byte[] buffer = new byte[1024]; +// int read; +// while ((read = uploadStream.read(buffer)) != -1) { +// fos.write(buffer, 0, read); +// } +// return BaseResponse.OK("",""); + } catch (Exception e) { + return BaseResponse.FAIL(Arrays.toString(e.getStackTrace()), e.getMessage()); + } + } + + @NaslLogic + public static BaseResponse createPDF(CreateRequest request) { + + try{ + // 按模块填充内容 + if(CollectionUtils.isEmpty(request.data)) { + return BaseResponse.FAIL("数据为空!"); + } + if(StringUtils.isBlank(request.fileName)){ + return BaseResponse.FAIL("文件名称不能为空,格式 *.pdf"); + } + + Document document = new Document(); + PdfWriter writer = PdfWriter.getInstance(document,new FileOutputStream(request.fileName)); + document.open(); + + // 支持中文字体 TODO:后续考虑通过参数传入字体名称 + BaseFont chinese = BaseFont.createFont("STSong-Light","UniGB-UCS2-H",BaseFont.NOT_EMBEDDED); + Font chineseFont = new Font(chinese); + + for (Map> item : request.data) { + + if(MapUtils.isEmpty(item)){ + continue; + } + + for (Map.Entry> entry : item.entrySet()) { + + if(CollectionUtils.isEmpty(entry.getValue())){ + continue; + } + + switch (entry.getKey()) { + case "pdf-table": + fillTable(document,entry.getValue(),chineseFont); + break; + case "pdf-image": + fillImage(document,entry.getValue(),chineseFont); + break; + case "pdf-paragraph": + fillParagraph(document,entry.getValue(),chineseFont); + break; + case "pdf-title": + fillTitle(document,entry.getValue(),chineseFont); + break; + default: + return BaseResponse.FAIL("不支持的类型:" + entry.getKey()); + } + } + } + document.close(); + writer.close(); + + // 上传到oss + UploadResponseDTO uploadResponseDTO = fileUtils.uploadFileV2(new File(request.fileName)); + + return BaseResponse.OK(uploadResponseDTO.getFilePath(), uploadResponseDTO.getResult()); + } catch (DocumentException | IOException | InvocationTargetException | NoSuchMethodException | + IllegalAccessException e) { + return BaseResponse.FAIL(Arrays.toString(e.getStackTrace()),e.getMessage()); + } + } + + private static void fillParagraph(Document document,List data,Font font) throws DocumentException { + for (String item : data) { + Paragraph paragraph = new Paragraph(new Paragraph(item,font)); + document.add(paragraph); + } + } + + private static void fillTitle(Document document,List data,Font font) throws DocumentException { + for (String item : data) { + Paragraph paragraph = new Paragraph(item,font); + paragraph.setAlignment(Element.ALIGN_CENTER); + document.add(paragraph); + } + } + + private static void fillImage(Document document,List data,Font font) throws DocumentException, IOException { + for (String item : data) { + // 先下载图片 + File file = FileUtils.downloadFile(item); + + Image image = Image.getInstance(file.getAbsolutePath()); + image.scaleAbsolute(100,100); + document.add(image); + + file.delete(); + } + } + + private static void fillTable(Document document,List data,Font font) throws DocumentException { + + PdfPTable table = null; + List rows = null; + + // 表头取第一行数据,每行数据用,分隔 + // 用户务必控制每行数据的个数统一,空数据同样需要进行占位 + for (String row : data) { + if(StringUtils.isBlank(row)){ + continue; + } + String[] splits = StringUtils.split(row, ","); + PdfPCell cells[] = new PdfPCell[splits.length]; + for (int i = 0; i < splits.length; i++) { + if("-".equals(splits[i])){ + cells[i] = new PdfPCell(new Paragraph(" ",font)); + }else { + cells[i] = new PdfPCell(new Paragraph(splits[i], font)); + } + } + + PdfPRow pRow = new PdfPRow(cells); + + if(Objects.isNull(table)){ + table = new PdfPTable(splits.length); + table.setWidthPercentage(100); + table.setSpacingBefore(10f); + table.setSpacingAfter(10f); + } + if (Objects.isNull(rows)) { + rows = table.getRows(); + } + rows.add(pRow); + } + + if(Objects.nonNull(table)){ + document.add(table); + } + } +} \ No newline at end of file diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/FooterIEventHandler.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/FooterIEventHandler.java new file mode 100644 index 000000000..c8d5262f1 --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/FooterIEventHandler.java @@ -0,0 +1,91 @@ +package com.netease.lowcode.pdf.extension.itextpdf; + +import com.alibaba.fastjson2.JSONObject; +import com.itextpdf.kernel.colors.ColorConstants; +import com.itextpdf.kernel.events.Event; +import com.itextpdf.kernel.events.IEventHandler; +import com.itextpdf.kernel.events.PdfDocumentEvent; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.kernel.geom.PageSize; +import com.itextpdf.kernel.pdf.canvas.PdfCanvas; +import com.itextpdf.layout.properties.TextAlignment; + +import java.io.IOException; + +public class FooterIEventHandler implements IEventHandler { + + private JSONObject jsonObject; + + public FooterIEventHandler(JSONObject jsonObject){ + this.jsonObject = jsonObject; + } + + @Override + public void handleEvent(Event event) { + // 判断是否需要页脚 + if(!jsonObject.containsKey("footer")) { + return; + } + + JSONObject footer = jsonObject.getJSONObject("footer"); + + if(event instanceof PdfDocumentEvent){ + PdfDocumentEvent documentEvent = (PdfDocumentEvent) event; + com.itextpdf.kernel.pdf.PdfDocument document = documentEvent.getDocument(); + // 获取当前处理的页码 + int pageNumber = document.getPageNumber(documentEvent.getPage()); + // 这里已经可以获取所有页数,可在页脚写入 + int numberOfPages = document.getNumberOfPages(); + + // 获取页面尺寸 + PageSize pageSize = PdfUtils.getPageSize(jsonObject.getString("pageSize")); + if (jsonObject.containsKey("rotate") && jsonObject.getBoolean("rotate")) { + pageSize = pageSize.rotate(); + } + + // 创建一个Canvas对象,用于添加水印 + com.itextpdf.layout.Canvas canvas = new com.itextpdf.layout.Canvas( + new PdfCanvas(documentEvent.getPage()), + new PageSize(pageSize) + ); + + if (footer.containsKey("fontColor")) { + canvas.setFontColor(PdfUtils.getColor(footer.getString("fontColor"))); + } else { + canvas.setFontColor(ColorConstants.LIGHT_GRAY); + } + if (footer.containsKey("fontSize")) { + canvas.setFontSize(footer.getInteger("fontSize")); + } else { + canvas.setFontSize(20); + } + // 为了支持中文 + try { + // 目前统一从全局配置取,暂不考虑水印单独配置字体 + if (jsonObject.containsKey("font")) { + String fontProgram = jsonObject.getJSONObject("font").getString("fontProgram"); + String encoding = jsonObject.getJSONObject("font").getString("encoding"); + canvas.setFont(PdfFontFactory.createFont(fontProgram, encoding)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + float x = 0, y = 0; + if (footer.containsKey("marginLeft")) { + x = footer.getFloat("marginLeft"); + } + if (footer.containsKey("marginBottom")) { + y = footer.getFloat("marginBottom"); + } + + TextAlignment textAlignment = TextAlignment.LEFT; + if (footer.containsKey("textAlignment")) { + textAlignment = TextAlignment.valueOf(footer.getString("textAlignment")); + } + + canvas.showTextAligned(footer.getString("text"), x, y, textAlignment); + canvas.close(); + } + } +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/HeaderIEventHandler.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/HeaderIEventHandler.java new file mode 100644 index 000000000..52556e935 --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/HeaderIEventHandler.java @@ -0,0 +1,99 @@ +package com.netease.lowcode.pdf.extension.itextpdf; + +import com.alibaba.fastjson2.JSONObject; +import com.itextpdf.io.image.ImageDataFactory; +import com.itextpdf.kernel.colors.ColorConstants; +import com.itextpdf.kernel.events.Event; +import com.itextpdf.kernel.events.IEventHandler; +import com.itextpdf.kernel.events.PdfDocumentEvent; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.kernel.geom.PageSize; +import com.itextpdf.kernel.pdf.canvas.PdfCanvas; +import com.itextpdf.layout.properties.TextAlignment; + +import java.io.IOException; +import java.net.MalformedURLException; + +public class HeaderIEventHandler implements IEventHandler { + + private JSONObject jsonObject; + + public HeaderIEventHandler(JSONObject jsonObject){ + this.jsonObject = jsonObject; + } + + @Override + public void handleEvent(Event event) { + + // 判断是否需要页眉 + if(!jsonObject.containsKey("header")) { + return; + } + + JSONObject header = jsonObject.getJSONObject("header"); + + if(event instanceof PdfDocumentEvent){ + PdfDocumentEvent documentEvent = (PdfDocumentEvent) event; + com.itextpdf.kernel.pdf.PdfDocument document = documentEvent.getDocument(); + + // 获取页面尺寸 + PageSize pageSize = PdfUtils.getPageSize(jsonObject.getString("pageSize")); + if (jsonObject.containsKey("rotate") && jsonObject.getBoolean("rotate")) { + pageSize = pageSize.rotate(); + } + + // 创建一个Canvas对象,用于添加水印 + com.itextpdf.layout.Canvas canvas = new com.itextpdf.layout.Canvas( + new PdfCanvas(documentEvent.getPage()), + new PageSize(pageSize) + ); + + if (header.containsKey("fontColor")) { + canvas.setFontColor(PdfUtils.getColor(header.getString("fontColor"))); + } else { + canvas.setFontColor(ColorConstants.LIGHT_GRAY); + } + if (header.containsKey("fontSize")) { + canvas.setFontSize(header.getInteger("fontSize")); + } else { + canvas.setFontSize(20); + } + // 为了支持中文 + try { + // 目前统一从全局配置取,暂不考虑水印单独配置字体 + if (jsonObject.containsKey("font")) { + String fontProgram = jsonObject.getJSONObject("font").getString("fontProgram"); + String encoding = jsonObject.getJSONObject("font").getString("encoding"); + canvas.setFont(PdfFontFactory.createFont(fontProgram, encoding)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + float x = 0, y = pageSize.getTop(); + if (header.containsKey("marginLeft")) { + x = header.getFloat("marginLeft"); + } + if (header.containsKey("marginTop")) { + y = pageSize.getTop() - header.getFloat("marginTop"); + } + + TextAlignment textAlignment = TextAlignment.LEFT; + if (header.containsKey("textAlignment")) { + textAlignment = TextAlignment.valueOf(header.getString("textAlignment")); + } + + canvas.showTextAligned(header.getString("text"), x, y, textAlignment); + + // 设置页眉图标 + if(header.containsKey("image")){ + JSONObject image = header.getJSONObject("image"); + + com.itextpdf.layout.element.Image imageElement = NodeCreator.image(image); + canvas.add(imageElement); + + } + canvas.close(); + } + } +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/NodeCreator.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/NodeCreator.java new file mode 100644 index 000000000..559d8b45a --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/NodeCreator.java @@ -0,0 +1,342 @@ +package com.netease.lowcode.pdf.extension.itextpdf; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.itextpdf.forms.fields.properties.CheckBoxType; +import com.itextpdf.forms.form.element.CheckBox; +import com.itextpdf.io.image.ImageDataFactory; +import com.itextpdf.kernel.colors.CalRgb; +import com.itextpdf.kernel.colors.DeviceRgb; +import com.itextpdf.kernel.events.PdfDocumentEvent; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.kernel.geom.PageSize; +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfWriter; +import com.itextpdf.kernel.pdf.canvas.draw.SolidLine; +import com.itextpdf.layout.Document; +import com.itextpdf.layout.borders.Border; +import com.itextpdf.layout.borders.SolidBorder; +import com.itextpdf.layout.element.*; +import com.itextpdf.layout.properties.TextAlignment; +import com.itextpdf.layout.properties.UnitValue; +import com.netease.lowcode.pdf.extension.structures.NodeTypeEnum; +import org.apache.commons.lang3.SerializationUtils; +import org.apache.commons.lang3.StringUtils; +import sun.misc.BASE64Decoder; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.List; +import java.util.Objects; + +public class NodeCreator { + + public static ByteArrayOutputStream node(JSONObject jsonObject) throws IOException { + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + String fileName = jsonObject.getString("fileName"); + if (StringUtils.isBlank(fileName) || !fileName.endsWith(".pdf")) { + throw new RuntimeException("fileName必须以 *.pdf 结尾"); + } + PdfDocument pdfDocument = new PdfDocument(new PdfWriter(byteArrayOutputStream)); + + // 设置纸张大小和方向: 默认纵向,rotate为横向 + PageSize pageSize = PdfUtils.getPageSize(jsonObject.getString("pageSize")); + if (jsonObject.containsKey("rotate") && jsonObject.getBoolean("rotate")) { + pageSize = pageSize.rotate(); + } + pdfDocument.setDefaultPageSize(pageSize); + // 添加水印,页眉页脚 + pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE, new WaterMaskIEventHandler(jsonObject)); + pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE, new HeaderIEventHandler(jsonObject)); + pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE,new FooterIEventHandler(jsonObject)); + pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE,new PageNumberIEventHandler(jsonObject)); + + Document document = new Document(pdfDocument); + + JSONObject fontJSONObject = jsonObject.getJSONObject("font"); + document.setFont(PdfFontFactory.createFont(fontJSONObject.getString("fontProgram"),fontJSONObject.getString("encoding"))); + if (jsonObject.containsKey("fontSize")) { + document.setFontSize(jsonObject.getFloat("fontSize")); + } else { + document.setFontSize(7); + } + + // 设置页边距 + if(jsonObject.containsKey("marginLeft")){ + document.setLeftMargin(jsonObject.getFloat("marginLeft")); + } + if(jsonObject.containsKey("marginRight")){ + document.setRightMargin(jsonObject.getFloat("marginRight")); + } + if(jsonObject.containsKey("marginTop")){ + document.setTopMargin(jsonObject.getFloat("marginTop")); + } + if(jsonObject.containsKey("marginBottom")){ + document.setBottomMargin(jsonObject.getFloat("marginBottom")); + } + + JSONArray nodes = jsonObject.getJSONArray("nodes"); + + if (Objects.isNull(nodes)) { + document.close(); + return byteArrayOutputStream; + } + + nodes.toJavaList(JSONObject.class).forEach(nodeObj -> { + if ("Image".equalsIgnoreCase(nodeObj.getString("type"))) { + // 文档插入图片 + document.add(image(nodeObj)); + } else if ("AreaBreak".equalsIgnoreCase(nodeObj.getString("type"))) { + // 文档分页 + document.add(areaBreak()); + } else { + document.add(NodeTypeEnum.valueOf(nodeObj.getString("type")).exec(nodeObj)); + } + }); + + document.close(); + return byteArrayOutputStream; + } + + public static Paragraph paragraph(JSONObject jsonObject) { + if(Objects.isNull(jsonObject)){ + return null; + } + if(!jsonObject.containsKey("type")){ + return null; + } + Paragraph paragraph = new Paragraph(); + if (jsonObject.containsKey("text")) { + paragraph.add(jsonObject.getString("text")); + } else { + paragraph.add(""); + } + if(jsonObject.containsKey("textAlignment")){ + paragraph.setTextAlignment(TextAlignment.valueOf(jsonObject.getString("textAlignment"))); + } + if(jsonObject.containsKey("fontSize")){ + paragraph.setFontSize(jsonObject.getInteger("fontSize")); + } + if (jsonObject.containsKey("bold") && jsonObject.getBoolean("bold")) { + paragraph.setBold(); + } + if(jsonObject.containsKey("underline")){ + paragraph.setUnderline(); + } + if(jsonObject.containsKey("fontColor")){ + paragraph.setFontColor(PdfUtils.getColor(jsonObject.getString("fontColor"))); + } + // 如果有rgb,将覆盖上面的color + if (jsonObject.containsKey("rgb")) { + JSONObject rgb = jsonObject.getJSONObject("rgb"); + paragraph.setFontColor(new DeviceRgb(rgb.getInteger("red"), rgb.getInteger("green"), rgb.getInteger("blue"))); + } + + if(jsonObject.containsKey("marginLeft")){ + paragraph.setMarginLeft(jsonObject.getInteger("marginLeft")); + } + if(jsonObject.containsKey("marginRight")) { + paragraph.setMarginRight(jsonObject.getInteger("marginRight")); + } + + if (jsonObject.containsKey("elements")) { + JSONArray elements = jsonObject.getJSONArray("elements"); + elements.toJavaList(JSONObject.class).forEach(obj -> { + if("Image".equalsIgnoreCase(obj.getString("type"))){ + paragraph.add(image(obj)); + }else{ + paragraph.add(NodeTypeEnum.valueOf(obj.getString("type")).exec(obj)); + } + }); + } + + return paragraph; + } + + public static CheckBox checkBox(JSONObject jsonObject) { + if(Objects.isNull(jsonObject)){ + return null; + } + if(!jsonObject.containsKey("type")){ + return null; + } + + CheckBox checkBox = new CheckBox(String.valueOf(System.currentTimeMillis())); + if (jsonObject.containsKey("checked")) { + checkBox.setChecked(jsonObject.getBoolean("checked")); + } + if (jsonObject.containsKey("checkBoxType")) { + checkBox.setCheckBoxType(CheckBoxType.valueOf(jsonObject.getString("checkBoxType"))); + } + if (jsonObject.containsKey("border")) { + checkBox.setBorder(new SolidBorder(jsonObject.getJSONObject("border").getInteger("width"))); + } + if (jsonObject.containsKey("size")) { + checkBox.setSize(jsonObject.getInteger("size")); + } + return checkBox; + } + + public static Table table(JSONObject jsonObject){ + if(Objects.isNull(jsonObject)){ + return null; + } + Integer width = jsonObject.getInteger("width"); + // 列的数量 + Integer columnSize = jsonObject.getInteger("columnSize"); + JSONArray cellArray = jsonObject.getJSONArray("cells"); + List cellList = cellArray.toJavaList(JSONObject.class); + // 获取分块数量,表格将在水平方向上扩展 + if (jsonObject.containsKey("chunkSize")) { + + // 进行分块时,cell的填充方向,默认水平顺序填充 + if(jsonObject.containsKey("chunkVertical")){ + // 进行竖直顺序填充 + // TODO: 暂不支持 + } + + Integer chunkSize = jsonObject.getInteger("chunkSize"); + columnSize = columnSize * chunkSize; + + // 末尾对齐 + int cellListSize = cellList.size(); + if (cellListSize % columnSize != 0) { + for (int i = cellListSize % columnSize; i < columnSize; i++) { + // 复制上方表格样式 + JSONObject clone = SerializationUtils.clone(cellList.get(i % jsonObject.getInteger("columnSize"))); + JSONArray cellElements = clone.getJSONArray("elements"); + List elementList = cellElements.toJavaList(JSONObject.class); + for (JSONObject elementObj : elementList) { + String elementType = elementObj.getString("type"); + if (NodeTypeEnum.Paragraph.equals(NodeTypeEnum.valueOf(elementType))) { + elementObj.put("text", ""); + } + } + cellList.add(clone); + } + } + } + + Table table = new Table(columnSize); + table.setWidth(UnitValue.createPercentValue(width)); + cellList.forEach(obj -> table.addCell(NodeCreator.cell(obj))); + return table; + } + + public static Cell cell(JSONObject jsonObject){ + if(Objects.isNull(jsonObject)){ + return null; + } + + if(!jsonObject.containsKey("width")){ + throw new RuntimeException("cell缺少width"); + } + + Cell cell; + if (jsonObject.containsKey("rowspan") && jsonObject.containsKey("colspan")) { + cell = new Cell(jsonObject.getInteger("rowspan"), jsonObject.getInteger("colspan")); + } else { + cell = new Cell(); + } + cell.setWidth(UnitValue.createPercentValue(jsonObject.getInteger("width"))); + + if (jsonObject.containsKey("textAlignment")) { + cell.setTextAlignment(TextAlignment.valueOf(jsonObject.getString("textAlignment"))); + } + + if (!jsonObject.containsKey("elements")) { + return cell; + } + + // 去除默认边框 + if (jsonObject.containsKey("noBorder") && jsonObject.getBoolean("noBorder")) { + cell.setBorder(Border.NO_BORDER); + } + + // 设置单元格边框 + if (jsonObject.containsKey("borderBottom")) { + cell.setBorderBottom(new SolidBorder(jsonObject.getJSONObject("borderBottom").getInteger("width"))); + } + if (jsonObject.containsKey("borderTop")) { + cell.setBorderTop(new SolidBorder(jsonObject.getJSONObject("borderTop").getInteger("width"))); + } + if (jsonObject.containsKey("borderLeft")) { + cell.setBorderLeft(new SolidBorder(jsonObject.getJSONObject("borderLeft").getInteger("width"))); + } + if (jsonObject.containsKey("borderRight")) { + cell.setBorderRight(new SolidBorder(jsonObject.getJSONObject("borderRight").getInteger("width"))); + } + + JSONArray elements = jsonObject.getJSONArray("elements"); + elements.toJavaList(JSONObject.class).forEach(obj -> { + if("Image".equalsIgnoreCase(obj.getString("type"))){ + cell.add(image(obj)); + }else{ + cell.add(NodeTypeEnum.valueOf(obj.getString("type")).exec(obj)); + } + }); + + return cell; + } + + public static LineSeparator lineSeparator(JSONObject jsonObject){ + SolidLine lineDrawer; + if (jsonObject.containsKey("lineWidth")) { + lineDrawer = new SolidLine(jsonObject.getFloat("lineWidth")); + } else { + lineDrawer = new SolidLine(); + } + LineSeparator lineSeparator = new LineSeparator(lineDrawer); + if(jsonObject.containsKey("width")){ + lineSeparator.setWidth(UnitValue.createPercentValue(jsonObject.getInteger("width"))); + } + if(jsonObject.containsKey("marginLeft")){ + lineSeparator.setMarginLeft(jsonObject.getInteger("marginLeft")); + } + + return lineSeparator; + } + + public static Image image(JSONObject jsonObject) { + try { + BASE64Decoder decoder = new BASE64Decoder(); + if(!jsonObject.containsKey("base64")){ + throw new RuntimeException("图片缺少base64编码"); + } + String base64 = jsonObject.getString("base64"); + if (base64.contains("base64,")) { + base64 = base64.substring(base64.indexOf("base64,") + 7); + } + byte[] bytes = decoder.decodeBuffer(base64); + Image image = new Image(ImageDataFactory.create(bytes)); + float fitWidth = 100, fitHeight = 100; + if (jsonObject.containsKey("fitWidth")) { + fitWidth = jsonObject.getFloat("fitWidth"); + } + if (jsonObject.containsKey("fitHeight")) { + fitHeight = jsonObject.getFloat("fitHeight"); + } + image.scaleToFit(fitWidth, fitHeight); + if (jsonObject.containsKey("marginLeft")) { + image.setMarginLeft(jsonObject.getInteger("marginLeft")); + } + if (jsonObject.containsKey("marginTop")) { + image.setMarginTop(jsonObject.getInteger("marginTop")); + } + if (jsonObject.containsKey("opacity")) { + // 透明度 0完全透明 1完全不透明 + image.setOpacity(jsonObject.getFloat("opacity")); + } + return image; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static AreaBreak areaBreak() { + return new AreaBreak(); + } +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/PageNumberIEventHandler.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/PageNumberIEventHandler.java new file mode 100644 index 000000000..712a814c4 --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/PageNumberIEventHandler.java @@ -0,0 +1,109 @@ +package com.netease.lowcode.pdf.extension.itextpdf; + +import com.alibaba.fastjson2.JSONObject; +import com.itextpdf.kernel.colors.ColorConstants; +import com.itextpdf.kernel.events.Event; +import com.itextpdf.kernel.events.IEventHandler; +import com.itextpdf.kernel.events.PdfDocumentEvent; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.kernel.geom.PageSize; +import com.itextpdf.kernel.pdf.canvas.PdfCanvas; +import com.itextpdf.layout.properties.TextAlignment; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; + +public class PageNumberIEventHandler implements IEventHandler { + + private JSONObject jsonObject; + + public PageNumberIEventHandler(JSONObject jsonObject){ + this.jsonObject = jsonObject; + } + + @Override + public void handleEvent(Event event) { + // 判断是否需要页码 + if(!jsonObject.containsKey("pageNumber")){ + return; + } + + JSONObject pageNumber = jsonObject.getJSONObject("pageNumber"); + + if(event instanceof PdfDocumentEvent){ + PdfDocumentEvent documentEvent = (PdfDocumentEvent) event; + com.itextpdf.kernel.pdf.PdfDocument document = documentEvent.getDocument(); + // 获取当前处理的页码 + int pageNo = document.getPageNumber(documentEvent.getPage()); + // 这里已经可以获取所有页数,可在页脚写入 + int numberOfPages = document.getNumberOfPages(); + + // 获取页面尺寸 + PageSize pageSize = PdfUtils.getPageSize(jsonObject.getString("pageSize")); + if (jsonObject.containsKey("rotate") && jsonObject.getBoolean("rotate")) { + pageSize = pageSize.rotate(); + } + + // 创建一个Canvas对象,用于添加水印 + com.itextpdf.layout.Canvas canvas = new com.itextpdf.layout.Canvas( + new PdfCanvas(documentEvent.getPage()), + new PageSize(pageSize) + ); + + if (pageNumber.containsKey("fontColor")) { + canvas.setFontColor(PdfUtils.getColor(pageNumber.getString("fontColor"))); + } else { + canvas.setFontColor(ColorConstants.LIGHT_GRAY); + } + if (pageNumber.containsKey("fontSize")) { + canvas.setFontSize(pageNumber.getInteger("fontSize")); + } else { + canvas.setFontSize(20); + } + // 为了支持中文 + try { + // 目前统一从全局配置取,暂不考虑水印单独配置字体 + if (jsonObject.containsKey("font")) { + String fontProgram = jsonObject.getJSONObject("font").getString("fontProgram"); + String encoding = jsonObject.getJSONObject("font").getString("encoding"); + canvas.setFont(PdfFontFactory.createFont(fontProgram, encoding)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + float x = pageSize.getWidth(), y = pageSize.getBottom(); + if (pageNumber.containsKey("marginRight")) { + x = pageSize.getWidth() - pageNumber.getFloat("marginRight"); + } + if (pageNumber.containsKey("marginBottom")) { + y = pageNumber.getFloat("marginBottom"); + } + + TextAlignment textAlignment = TextAlignment.RIGHT; + if (pageNumber.containsKey("textAlignment")) { + textAlignment = TextAlignment.valueOf(pageNumber.getString("textAlignment")); + } + + String text = pageNumber.getString("text"); + String format = text; + if(StringUtils.isNotBlank(text)){ + String[] split = text.split("%s"); + // 只显示页码 + if (split.length - 1 == 1) { + format = String.format(text, pageNo); + } + // 显示页码+总页数 + else if (split.length - 1 == 2) { + format = String.format(text, pageNo, numberOfPages); + } + else { + throw new RuntimeException("页码占位符%s数量只能为1或2个"); + } + } + + canvas.showTextAligned(format, x, y, textAlignment); + canvas.close(); + } + } +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/PdfUtils.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/PdfUtils.java new file mode 100644 index 000000000..b4107881d --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/PdfUtils.java @@ -0,0 +1,109 @@ +package com.netease.lowcode.pdf.extension.itextpdf; + +import com.itextpdf.kernel.colors.Color; +import com.itextpdf.kernel.colors.ColorConstants; +import com.itextpdf.kernel.geom.PageSize; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.Objects; + +public class PdfUtils { + + public static String readJson(String path) throws IOException { + BufferedReader reader = new BufferedReader(new FileReader(path)); + StringBuilder sb = new StringBuilder(); + String line; + while((line=reader.readLine())!=null){ + sb.append(line).append("\n"); + } + reader.close(); + return sb.toString(); + } + + public static Color getColor(String name){ + switch (name){ + case "RED": + return ColorConstants.RED; + case "BLUE": + return ColorConstants.BLUE; + case "CYAN": + return ColorConstants.CYAN; + case "DARK_GRAY": + return ColorConstants.DARK_GRAY; + case "GRAY": + return ColorConstants.GRAY; + case "GREEN": + return ColorConstants.GREEN; + case "LIGHT_GRAY": + return ColorConstants.LIGHT_GRAY; + case "MAGENTA": + return ColorConstants.MAGENTA; + case "ORANGE": + return ColorConstants.ORANGE; + case "PINK": + return ColorConstants.PINK; + case "WHITE": + return ColorConstants.WHITE; + case "YELLOW": + return ColorConstants.YELLOW; + default: + return ColorConstants.BLACK; + } + } + + public static PageSize getPageSize(String size){ + + if (Objects.isNull(size)) { + return PageSize.A4; + } + + switch (size){ + case "A0": + return PageSize.A0; + case "A1": + return PageSize.A1; + case "A2": + return PageSize.A2; + case "A3": + return PageSize.A3; + case "A5": + return PageSize.A5; + case "A6": + return PageSize.A6; + case "A7": + return PageSize.A7; + case "A8": + return PageSize.A8; + case "A9": + return PageSize.A9; + case "A10": + return PageSize.A10; + case "B0": + return PageSize.B0; + case "B1": + return PageSize.B1; + case "B2": + return PageSize.B2; + case "B3": + return PageSize.B3; + case "B4": + return PageSize.B4; + case "B5": + return PageSize.B5; + case "B6": + return PageSize.B6; + case "B7": + return PageSize.B7; + case "B8": + return PageSize.B8; + case "B9": + return PageSize.B9; + case "B10": + return PageSize.B10; + default: + return PageSize.A4; + } + } +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/WaterMaskIEventHandler.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/WaterMaskIEventHandler.java new file mode 100644 index 000000000..3a152ae92 --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/itextpdf/WaterMaskIEventHandler.java @@ -0,0 +1,111 @@ +package com.netease.lowcode.pdf.extension.itextpdf; + +import com.alibaba.fastjson2.JSONObject; +import com.itextpdf.kernel.colors.ColorConstants; +import com.itextpdf.kernel.events.Event; +import com.itextpdf.kernel.events.IEventHandler; +import com.itextpdf.kernel.events.PdfDocumentEvent; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.kernel.geom.PageSize; +import com.itextpdf.kernel.pdf.canvas.PdfCanvas; +import com.itextpdf.layout.properties.TextAlignment; +import com.itextpdf.layout.properties.VerticalAlignment; + +import java.io.IOException; + +public class WaterMaskIEventHandler implements IEventHandler { + + private JSONObject jsonObject; + + public WaterMaskIEventHandler(JSONObject jsonObject){ + this.jsonObject = jsonObject; + } + + @Override + public void handleEvent(Event event) { + + // 判断是否需要水印 + if(!jsonObject.containsKey("waterMask")) { + return; + } + JSONObject waterMask = jsonObject.getJSONObject("waterMask"); + + if(event instanceof PdfDocumentEvent){ + PdfDocumentEvent documentEvent = (PdfDocumentEvent) event; + com.itextpdf.kernel.pdf.PdfDocument document = documentEvent.getDocument(); + // 获取当前处理的页码 + int pageNumber = document.getPageNumber(documentEvent.getPage()); + + // 获取页面尺寸 + PageSize pageSize = PdfUtils.getPageSize(jsonObject.getString("pageSize")); + if (jsonObject.containsKey("rotate") && jsonObject.getBoolean("rotate")) { + pageSize = pageSize.rotate(); + } + // 创建一个Canvas对象,用于添加水印 + com.itextpdf.layout.Canvas canvas = new com.itextpdf.layout.Canvas( + new PdfCanvas(documentEvent.getPage()), + new PageSize(pageSize)// 需要判断方向 + ); + + if (waterMask.containsKey("fontColor")) { + canvas.setFontColor(PdfUtils.getColor(waterMask.getString("fontColor"))); + } else { + canvas.setFontColor(ColorConstants.LIGHT_GRAY); + } + if (waterMask.containsKey("fontSize")) { + canvas.setFontSize(waterMask.getInteger("fontSize")); + } else { + canvas.setFontSize(20); + } + // 为了支持中文 + try { + // 目前统一从全局配置取,暂不考虑水印单独配置字体 + if (jsonObject.containsKey("font")) { + String fontProgram = jsonObject.getJSONObject("font").getString("fontProgram"); + String encoding = jsonObject.getJSONObject("font").getString("encoding"); + canvas.setFont(PdfFontFactory.createFont(fontProgram, encoding)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + // 获取水印起始坐标 + Float x = 0f; + if (waterMask.containsKey("x")) { + x = waterMask.getFloat("x"); + } + Float y = 0f; + if (waterMask.containsKey("y")) { + y = waterMask.getFloat("y"); + } + // 获取水印旋转角度 (0~360°,逆时针方向) + Double angle = 30.0; + if (waterMask.containsKey("angle")) { + angle = waterMask.getDouble("angle"); + } + + float x_space = pageSize.getWidth(), y_space = pageSize.getHeight(); + if (waterMask.containsKey("xAxisElementSpacing")) { + x_space = waterMask.getFloat("xAxisElementSpacing"); + } + if (waterMask.containsKey("yAxisElementSpacing")) { + y_space = waterMask.getFloat("yAxisElementSpacing"); + } + + // 重复多次,调整坐标即可实现重复水印填充满页 + // x轴方向重复 + for (float xi = x; xi <= pageSize.getWidth() + x_space; xi += x_space) { + // y轴方向重复 + for (float yi = y; yi <= pageSize.getHeight() + y_space; yi += y_space) { + // x 水印的横坐标 + // y 水印的纵坐标 + // radAngle 水印倾斜角度,直接输入0~360°即可,会自动转换为弧度 + canvas.showTextAligned(new com.itextpdf.layout.element.Paragraph(waterMask.getString("text")), + xi, yi, pageNumber, TextAlignment.CENTER, + VerticalAlignment.TOP, (float) Math.toRadians(angle)); + } + } + canvas.close(); + } + } +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/BaseResponse.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/BaseResponse.java new file mode 100644 index 000000000..ba1d51d61 --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/BaseResponse.java @@ -0,0 +1,84 @@ +package com.netease.lowcode.pdf.extension.structures; + +import com.netease.lowcode.core.annotation.NaslStructure; + +@NaslStructure +public class BaseResponse { + public Boolean success; + public String msg; + public String trace; + public String result; + public String filePath; + + public static BaseResponse OK(String filePath,String result) { + BaseResponse response = new BaseResponse(); + response.setSuccess(true); + response.setMsg("OK"); + response.setResult(result); + response.setFilePath(filePath); + return response; + } + + public static BaseResponse FAIL(String msg) { + return FAIL("",msg); + } + + public static BaseResponse FAIL(String trace,String msg) { + BaseResponse response = new BaseResponse(); + response.setSuccess(false); + response.setMsg(msg); + response.setTrace(trace); + return response; + } + + public Boolean getSuccess() { + return success; + } + + public void setSuccess(Boolean success) { + this.success = success; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public String getTrace() { + return trace; + } + + public void setTrace(String trace) { + this.trace = trace; + } + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + @Override + public String toString() { + return "BaseResponse{" + + "success=" + success + + ", msg='" + msg + '\'' + + ", trace='" + trace + '\'' + + ", result='" + result + '\'' + + ", filePath='" + filePath + '\'' + + '}'; + } +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/CreateByTemplateRequest.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/CreateByTemplateRequest.java new file mode 100644 index 000000000..4619b4f0d --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/CreateByTemplateRequest.java @@ -0,0 +1,35 @@ +package com.netease.lowcode.pdf.extension.structures; + +import com.netease.lowcode.core.annotation.NaslStructure; + +@NaslStructure +public class CreateByTemplateRequest { + + public String templateUrl; + public String jsonData; + public String exportFileName; + + public String getTemplateUrl() { + return templateUrl; + } + + public void setTemplateUrl(String templateUrl) { + this.templateUrl = templateUrl; + } + + public String getJsonData() { + return jsonData; + } + + public void setJsonData(String jsonData) { + this.jsonData = jsonData; + } + + public String getExportFileName() { + return exportFileName; + } + + public void setExportFileName(String exportFileName) { + this.exportFileName = exportFileName; + } +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/CreateByXlsxRequest.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/CreateByXlsxRequest.java new file mode 100644 index 000000000..b5a2f4fac --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/CreateByXlsxRequest.java @@ -0,0 +1,54 @@ +package com.netease.lowcode.pdf.extension.structures; + +import com.netease.lowcode.core.annotation.NaslStructure; + +@NaslStructure +public class CreateByXlsxRequest { + public String templateUrl; + public String jsonData; + public String exportFileName; + // 纸张大小,默认A4 + public String pageSize = "A4"; + // 纸张方向,默认纵向 + public Boolean rotate = false; + + public Boolean getRotate() { + return rotate; + } + + public void setRotate(Boolean rotate) { + this.rotate = rotate; + } + + public String getPageSize() { + return pageSize; + } + + public void setPageSize(String pageSize) { + this.pageSize = pageSize; + } + + public String getTemplateUrl() { + return templateUrl; + } + + public void setTemplateUrl(String templateUrl) { + this.templateUrl = templateUrl; + } + + public String getJsonData() { + return jsonData; + } + + public void setJsonData(String jsonData) { + this.jsonData = jsonData; + } + + public String getExportFileName() { + return exportFileName; + } + + public void setExportFileName(String exportFileName) { + this.exportFileName = exportFileName; + } +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/CreateRequest.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/CreateRequest.java new file mode 100644 index 000000000..00c7c8bab --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/CreateRequest.java @@ -0,0 +1,16 @@ +package com.netease.lowcode.pdf.extension.structures; + +import com.netease.lowcode.core.annotation.NaslStructure; + +import java.util.List; +import java.util.Map; + +@NaslStructure +public class CreateRequest { + public String fileName; + /** + * key表示数据类型: 文本、图片、表格 + * value表示具体内容,如果是表格类型,value是包括表头在内的数据集合, + */ + public List>> data; +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/FontStructure.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/FontStructure.java new file mode 100644 index 000000000..6398ced5c --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/FontStructure.java @@ -0,0 +1,32 @@ +package com.netease.lowcode.pdf.extension.structures; + +import com.netease.lowcode.core.annotation.NaslStructure; + +@NaslStructure +public class FontStructure { + + /** + * 字体 + */ + public String fontProgram = "STSong-Light"; + /** + * 字体编码 + */ + public String encoding = "UniGB-UCS2-H"; + + public String getFontProgram() { + return fontProgram; + } + + public void setFontProgram(String fontProgram) { + this.fontProgram = fontProgram; + } + + public String getEncoding() { + return encoding; + } + + public void setEncoding(String encoding) { + this.encoding = encoding; + } +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/NodeTypeEnum.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/NodeTypeEnum.java new file mode 100644 index 000000000..db3675628 --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/structures/NodeTypeEnum.java @@ -0,0 +1,37 @@ +package com.netease.lowcode.pdf.extension.structures; + +import com.alibaba.fastjson2.JSONObject; +import com.itextpdf.layout.element.IBlockElement; +import com.netease.lowcode.pdf.extension.itextpdf.NodeCreator; + +public enum NodeTypeEnum { + + Paragraph{ + public IBlockElement exec(JSONObject obj) { + return NodeCreator.paragraph(obj); + } + }, + Table{ + public IBlockElement exec(JSONObject obj) { + return NodeCreator.table(obj); + } + }, + Cell{ + public IBlockElement exec(JSONObject obj){ + return NodeCreator.cell(obj); + } + }, + CheckBox{ + public IBlockElement exec(JSONObject obj) { + return NodeCreator.checkBox(obj); + } + }, + LineSeparator{ + @Override + public IBlockElement exec(JSONObject obj) { + return NodeCreator.lineSeparator(obj); + } + }; + + public abstract IBlockElement exec(JSONObject obj); +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/FileUtils.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/FileUtils.java new file mode 100644 index 000000000..4daba7fa0 --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/FileUtils.java @@ -0,0 +1,206 @@ +package com.netease.lowcode.pdf.extension.utils; + +import com.alibaba.fastjson2.JSON; +import okhttp3.*; +import org.apache.commons.lang3.time.DateFormatUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +@Component("pdfGeneratorFileUtils") +public class FileUtils { + + @Value("${lcp.upload.sinkType}") + private String sinkType; + @Value("${lcp.upload.sinkPath}") + private String sinkPath; + @Value("${lcp.upload.access}") + private String access; + + @Autowired + private ApplicationContext applicationContext; + + public static String DEFAULT_TEMPLATE_DIR = "/data/template"; + + /** + * 文件上传 + * + * @param file + * @return + * @throws NoSuchMethodException + * @throws InvocationTargetException + * @throws IllegalAccessException + * @throws FileNotFoundException + */ + public UploadResponseDTO uploadFileV2(File file) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, FileNotFoundException { + + FileInputStream fis = new FileInputStream(file); + + Object clientManager = applicationContext.getBean("fileStorageClientManager"); + Method getFileSystemSpi = clientManager.getClass().getMethod("getFileSystemSpi", String.class); + Object fileStorageClient = getFileSystemSpi.invoke(clientManager, sinkType); + + Method upload = fileStorageClient.getClass().getMethod("upload", InputStream.class, String.class, Map.class); + // http://dev.exporttest.defaulttenant.lcap.codewave-dev.163yun.com/upload/app/%E5%A4%A7%E6%95%B0%E6%8D%AE%E5%AF%BC%E5%87%BA%E6%B5%8B%E8%AF%95_20240106093632186.xlsx + + // 只要拼接 sinkPath+fileName+时间+后缀即可。 + String curTime = DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSSS"); + + String fileName = file.getName(); + String fileExt = ""; + if (fileName.contains(".")) { + int i = fileName.lastIndexOf("."); + fileExt = fileName.substring(i); + fileName = fileName.substring(0, i); + } + + String savePath = String.join("/", sinkPath, fileName + "_" + curTime + fileExt); + String filePath = (String) upload.invoke(fileStorageClient, fis, savePath, new HashMap<>()); + + // 组装链接 + UploadResponseDTO responseDTO = new UploadResponseDTO(); + responseDTO.setFilePath("/upload" + filePath); + + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (Objects.nonNull(requestAttributes)) { + HttpServletRequest request = requestAttributes.getRequest(); + responseDTO.setResult(request.getScheme() + "://" + request.getServerName() + + (80 == request.getServerPort() ? "" : ":" + request.getServerPort()) + + "/upload" + filePath); + } else { + responseDTO.setResult(responseDTO.getFilePath()); + } + + return responseDTO; + } + + public static UploadResponseDTO uploadStream(InputStream inputStream, String fileName) throws IOException { + HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + String uploadUrl = httpServletRequest.getScheme() + "://" + httpServletRequest.getServerName() + ":" + + httpServletRequest.getServerPort() + "/upload"; + OkHttpClient client = new OkHttpClient(); + + byte[] fileBytes; + try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { + int read; + byte[] data = new byte[1024]; + while ((read = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, read); + } + buffer.flush(); + fileBytes = buffer.toByteArray(); + } + RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"),fileBytes); + MultipartBody multipartBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", fileName, requestBody) + .build(); + + Request request = new Request.Builder() + .url(uploadUrl) + .post(multipartBody) + .build(); + Call call = client.newCall(request); + Response response = call.execute(); + if (response.isSuccessful()) { + return JSON.parseObject(response.body().string(), UploadResponseDTO.class); + } + throw new RuntimeException(String.format("文件上传失败,%s",response)); + } + + public static File downloadFile(String urlStr) throws IOException { + URL url = new URL(getTrueUrl(urlStr)); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setConnectTimeout(3 * 1000); + connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.82 Safari/537.36"); + InputStream inputStream = url.openStream(); + byte[] getData = readInputStream(inputStream); + // 文件保存位置 + File saveDir = new File(DEFAULT_TEMPLATE_DIR); + if (!saveDir.exists()) { + // 这里可能会应为目录权限 导致无法创建目录。下面逻辑读取文件时报FileNotFoundException + saveDir.mkdirs(); + // 这里增加校验 + if (!saveDir.exists()) { + throw new RuntimeException(String.format("目录创建失败%s,请检查目录权限", DEFAULT_TEMPLATE_DIR)); + } + } + + String fileName = urlStr.substring(urlStr.lastIndexOf("/") + 1, urlStr.indexOf("?") == -1 ? urlStr.length() : urlStr.indexOf("?")); + File file = new File(saveDir + File.separator + fileName); + if (file.exists()) file.delete(); + FileOutputStream fos = new FileOutputStream(file); + fos.write(getData); + if (fos != null) { + fos.close(); + } + if (inputStream != null) { + inputStream.close(); + } + return file; + } + + public static byte[] readInputStream(InputStream inputStream) throws IOException { + byte[] buffer = new byte[1024]; + int len = 0; + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + while ((len = inputStream.read(buffer)) != -1) { + bos.write(buffer, 0, len); + } + bos.close(); + return bos.toByteArray(); + } + + public static String getTrueUrl(String urlStr) throws UnsupportedEncodingException { + int lastIndexOf = urlStr.lastIndexOf("/"); + int queryIndexOf = urlStr.indexOf("?"); + if (queryIndexOf == -1) queryIndexOf = urlStr.length(); + + String prefix = urlStr.substring(0, lastIndexOf); + String suffix = urlStr.substring(queryIndexOf); + String fileName = urlStr.substring(lastIndexOf + 1, queryIndexOf); + + String urlFileName = getTrueFileName(fileName); + String trueUrlStr = prefix + "/" + urlFileName + suffix; + if (!trueUrlStr.startsWith("http") && trueUrlStr.startsWith("/upload")) { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + int port = request.getLocalPort(); + return "http://127.0.0.1:" + port + trueUrlStr; + } + return trueUrlStr; + } + + public static String getTrueFileName(String fileName) throws UnsupportedEncodingException { + if (fileName.equals(URLDecoder.decode(fileName, "UTF-8"))) { + return URLEncoder.encode(fileName, "UTF-8"); + } + return fileName; + } + + public static void delete(File file) { + if(file.isFile()){ + file.delete(); + return; + } + for (File listFile : file.listFiles()) { + delete(listFile); + } + } +} + diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/FreemarkerUtils.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/FreemarkerUtils.java new file mode 100644 index 000000000..e2de18f12 --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/FreemarkerUtils.java @@ -0,0 +1,57 @@ +package com.netease.lowcode.pdf.extension.utils; + +import com.alibaba.fastjson2.JSON; +import freemarker.cache.StringTemplateLoader; +import freemarker.cache.URLTemplateLoader; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +public class FreemarkerUtils { + + private static final String TEMPLATE_NAME = "default"; + + public static ByteArrayInputStream getFreemarkerContentInputStreamV2(String jsonData, String url) throws IOException, TemplateException { + Configuration cfg = new Configuration(Configuration.VERSION_2_3_28); + cfg.setTemplateLoader(new URLTemplateLoader() { + @Override + protected URL getURL(String name) { + try { + return new URL(url); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + }); + + // 获取模板 这里的test无任何含义,任意字符串 + Template template = cfg.getTemplate("test"); + StringWriter swriter = new StringWriter(); + Object jsonNode = JSON.parseObject(jsonData, Object.class); + template.process(jsonNode, swriter); + + return new ByteArrayInputStream(swriter.toString().getBytes(StandardCharsets.UTF_8)); + } + + public static ByteArrayInputStream getFreemarkerContentInputStream(String jsonData, String jsonTemplate) throws IOException, TemplateException { + Configuration cfg = new Configuration(Configuration.VERSION_2_3_28); + StringTemplateLoader templateLoader = new StringTemplateLoader(); + templateLoader.putTemplate(TEMPLATE_NAME, jsonTemplate); + cfg.setTemplateLoader(templateLoader); + + Template template = cfg.getTemplate(TEMPLATE_NAME); + StringWriter swriter = new StringWriter(); + Object jsonNode = JSON.parseObject(jsonData, Object.class); + template.process(jsonNode, swriter); + + return new ByteArrayInputStream(swriter.toString().getBytes(StandardCharsets.UTF_8)); + } + +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/JSONObjectUtil.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/JSONObjectUtil.java new file mode 100644 index 000000000..a143d2a42 --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/JSONObjectUtil.java @@ -0,0 +1,155 @@ +package com.netease.lowcode.pdf.extension.utils; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.netease.lowcode.pdf.extension.Excel2Pdf; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; + +public class JSONObjectUtil { + + /** + * 将originRow中的freemarker list标签填充数据 + * + * @param originRow + * @param requestJsonData + * @return + */ + public static List> fillListData(List originRow, JSONObject requestJsonData) { + + if (Objects.isNull(requestJsonData)) { + return new ArrayList<>(); + } + + // 获取 list标签名称,一行只填充一个list,不支持多个 + String listName = ""; + for (int i = 0; i < originRow.size(); i++) { + if(Excel2Pdf.isFreemarkerListTag(originRow.get(i))){ + String cellText = Excel2Pdf.getCellText(originRow.get(i)); + // 获取list名称 + listName = cellText.substring(2, cellText.indexOf(".")); + } + } + // 未找到list名 + if(StringUtils.isBlank(listName)){ + return new ArrayList<>(); + } + // 请求数据不包含该list + if(!requestJsonData.containsKey(listName)){ + return new ArrayList<>(); + } + JSONArray requestArrayData = requestJsonData.getJSONArray(listName); + if(requestArrayData.isEmpty()){ + return new ArrayList<>(); + } + + List> newRows = new ArrayList<>(); + + for (int i = 0; i < requestArrayData.size(); i++) { + // 开始填充 + List cloneRow = deepCloneList(originRow); + // 记录当前是否填充过该字段,用于横向分块 + String arrHasRead = ""; + // 填充每一个cell + for (int j = 0; j < cloneRow.size(); j++) { + if (Excel2Pdf.isFreemarkerListTag(cloneRow.get(j))) { + String cellText = Excel2Pdf.getCellText(cloneRow.get(j)); + // 属性名 + String arrName = cellText.substring(cellText.indexOf(".") + 1, cellText.length() - 1); + if(StringUtils.equals(arrName,arrHasRead)){ + // 横向分块,用下一组数据填充 + i++; + } + // 判断数据是否越界 + if (i >= requestArrayData.size()) { + // 后续的行填充空数据,结束填充 + for (int k = j; k < cloneRow.size(); k++) { + cloneRow.get(k).getJSONArray("elements").getJSONObject(0).put("text", ""); + } + break; + } + + JSONObject jsonObject = requestArrayData.getJSONObject(i); + + String value = jsonObject.containsKey(arrName) ? jsonObject.getString(arrName) : ""; + cloneRow.get(j).getJSONArray("elements").getJSONObject(0).put("text", value); + + if(StringUtils.isBlank(arrHasRead)){ + arrHasRead = arrName; + } + } + } + newRows.add(cloneRow); + } + + return newRows; + } + + + public static List deepCloneList(List originList) { + if (CollectionUtils.isEmpty(originList)) { + return new ArrayList<>(); + } + List cloneList = new ArrayList<>(); + for (int i = 0; i < originList.size(); i++) { + JSONObject oriJsonObject = originList.get(i); + cloneList.add(JSONObject.parseObject(oriJsonObject.toJSONString())); + } + return cloneList; + } + + public static List> deepCloneList2(List> originList) { + if (CollectionUtils.isEmpty(originList)) { + return new ArrayList<>(); + } + List> cloneList = new ArrayList<>(); + for (int i = 0; i < originList.size(); i++) { + cloneList.add(deepCloneList(originList.get(i))); + } + return cloneList; + } + + /** + * 填充cell + * + * @param cloneCell + * @param jsonArray + * @param arrName + * @param arrHasRead + */ + private static void fillCellData(JSONObject cloneCell, JSONArray jsonArray, String arrName, String arrHasRead) { + + // 判断是否开始处理分块部分 + if (StringUtils.equals(arrName, arrHasRead)) { + // 已经处理过该组数据,移除0 + jsonArrayRemove0(jsonArray); + } + if (jsonArray.isEmpty()) { + return; + } + JSONObject jsonObject = jsonArray.getJSONObject(0); + if (!jsonObject.containsKey(arrName)) { + // 对象中不包含该属性 + return; + } + cloneCell.getJSONArray("elements").getJSONObject(0).put("text", jsonObject.getString(arrName)); + } + + /** + * 移除JSONArray中的第0个元素(如果存在的话) + * + * @param jsonArray + */ + public static void jsonArrayRemove0(JSONArray jsonArray) { + if(Objects.isNull(jsonArray)){ + return; + } + if(jsonArray.isEmpty()){ + return; + } + jsonArray.remove(0); + } + +} diff --git a/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/UploadResponseDTO.java b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/UploadResponseDTO.java new file mode 100644 index 000000000..56a2d60b4 --- /dev/null +++ b/pdf-generator/src/main/java/com/netease/lowcode/pdf/extension/utils/UploadResponseDTO.java @@ -0,0 +1,49 @@ +package com.netease.lowcode.pdf.extension.utils; + +public class UploadResponseDTO { + private int code; + private String msg; + private String result; + private String filePath; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } +} diff --git a/pdf-generator/src/main/resources/META-INF/spring.factories b/pdf-generator/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..6a3326814 --- /dev/null +++ b/pdf-generator/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.netease.lowcode.pdf.extension.utils.FileUtils,\ + com.netease.lowcode.pdf.extension.PdfGenerator \ No newline at end of file diff --git a/pdf-generator/src/main/resources/netease_logo.png b/pdf-generator/src/main/resources/netease_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9e794228da914fb01a2cb44324aaed9fd75bc802 GIT binary patch literal 5478 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 + ,{"width": 4,"textAlignment":"CENTER","elements": [{"type": "Paragraph", "text": "${project.sequence}"}]},{"width": 23,"elements": [{"type": "Paragraph", "text": "${project.name}"}]},{"width": 23,"elements": [{"type": "Paragraph", "text": "${project.standard}"}]} + + ] + }, + { + "type": "Table", + "width": 100, + "columnSize": 1, + "cells": [ + {"width": 100,"elements": [{"type": "Paragraph", "text": "判定依据:按照产品执行标准判定,单项需要确认判定要求。"}]} + ,{"width": 100,"elements": [{"type": "Paragraph", "text": "送检备注:硅胶:123 塑料:234硅胶:123 塑料:234硅胶:123 塑料:234硅胶:123 塑料:234硅胶:123 塑料:234硅胶:123 塑料:234硅胶:123 塑料:234硅胶:123 塑料:234硅胶:123 塑料:234硅胶:123 塑料:234硅胶:123 塑料:234硅胶:123 塑料:234硅胶:123 塑料:234硅胶:123 塑料:234硅胶:123 塑料:234"}]} + ] + }, + { + "type": "Paragraph", + "text": "3.其他" + }, + { + "type": "Table", + "width": 100, + "columnSize": 1, + "cells": [ + {"width": 100,"elements": [ + {"type": "Paragraph", "text": "客户要求:1、资质要求: ","elements": [ + { + "type": "CheckBox", + "checked": true, + "checkBoxType": "CROSS", + "size": 7, + "border": { + "width": 1 + } + },{ + "type": "Paragraph", + "text": "CNAS章", + "marginLeft":3, + "marginRight": 3 + }, + { + "type": "CheckBox", + "checked": true, + "checkBoxType": "CROSS", + "size": 7, + "border": { + "width": 1 + } + }, + { + "type": "Paragraph", + "text": "CMA章", + "marginLeft":3 + }]}, + {"type": "Paragraph", "marginLeft":30,"text": "2、报告语言: ","elements": [ + { + "type": "CheckBox", + "checked": true, + "checkBoxType": "CROSS", + "size": 7, + "border": { + "width": 1 + } + },{ + "type": "Paragraph", + "text": "中文报告", + "marginLeft":3, + "marginRight": 3 + },{ + "type": "CheckBox", + "checked": false, + "checkBoxType": "CROSS", + "size": 7, + "border": { + "width": 1 + } + },{ + "type": "Paragraph", + "text": "英文报告", + "marginLeft":3 + } + ]}, + {"type": "Paragraph", "marginLeft":30,"text": "3、报告出具方式:执行标准一份报告,2C一份报告,只有CMA一份"}, + {"type": "Paragraph", "marginLeft":30,"text": "4、服务类型:","elements": [ + { + "type": "CheckBox", + "checked": true, + "checkBoxType": "CROSS", + "size": 7, + "border": { + "width": 1 + } + },{ + "type": "Paragraph", + "text": "普通", + "marginLeft":3, + "marginRight": 3 + },{ + "type": "CheckBox", + "checked": false, + "checkBoxType": "CROSS", + "size": 7, + "border": { + "width": 1 + } + },{ + "type": "Paragraph", + "text": "加急", + "marginLeft":3 + } + ]}, + {"type": "Paragraph", "marginLeft":30,"text": "5、是否需要判定:","elements": [ + { + "type": "CheckBox", + "checked": true, + "checkBoxType": "CROSS", + "size": 7, + "border": { + "width": 1 + } + },{ + "type": "Paragraph", + "text": "需要", + "marginLeft":3, + "marginRight": 3 + },{ + "type": "CheckBox", + "checked": false, + "checkBoxType": "CROSS", + "size": 7, + "border": { + "width": 1 + } + },{ + "type": "Paragraph", + "text": "不需要", + "marginLeft":3 + } + ]}, + {"type": "Paragraph", "marginLeft":30,"text": "6、是否需要退样:","elements": [ + { + "type": "CheckBox", + "checked": true, + "checkBoxType": "STAR", + "size": 7, + "border": { + "width": 1 + } + },{ + "type": "Paragraph", + "text": "不需要", + "marginLeft":3, + "marginRight": 3 + },{ + "type": "CheckBox", + "checked": false, + "checkBoxType": "STAR", + "size": 7, + "border": { + "width": 1 + } + },{ + "type": "Paragraph", + "text": "需要", + "marginLeft":3 + }, { + "type": "Paragraph", + "text": "退样信息:", + "marginLeft":8 + },{ + "type": "LineSeparator", + "marginLeft": 1, + "width": 10, + "lineWidth": 0.5 + } + ]} + ]} + ] + }, + { + "type": "Table", + "width": 100, + "columnSize": 2, + "cells": [ + {"width": 50,"elements": [ + {"type": "Paragraph", "text": "委托方代表签名:","elements": [{"type": "Paragraph", "marginLeft":90, "text": "日期:"}]}]}, + {"width": 50,"elements": [ + {"type": "Paragraph", "text": "受托方代表签名:","elements": [{"type": "Paragraph", "marginLeft":90, "text": "日期:"}]}]} + ] + }, + { + "type": "Paragraph", + "text": "测试须知", + "bold": true, + "textAlignment": "CENTER", + "fontSize": 21, + "fontColor": "RED" + }, + { + "type": "Paragraph", + "text": "1. 与我司合作过程中,品控部标准送检组人员为唯一对接窗口,在未获得我司书面授权允许的情况下不得以任何形式与其他人员私自对接,进行样品受理、报告修改等与测试相关的业务,如若发生,将停止后续的一切合作,造成不利影响的我司保留追究相关法律责任的权力。" + }, + { + "type": "Paragraph", + "text": "2.委托方披露给受托方或受托方通过双方合作关系主动获悉的任何以及所有以口头或书面,或以其他任何形式披露的信息、资料或数据,包括但不限于产品信息、产品所含知识产权、产品测试内容及测试结果等各类基于甲方商业经营以及测试合作产生的,具有商业价值或涉及第三方个人隐私的内容予以保密,均不得主动向第三人披露。" + }, + { + "type": "Image", + "fileName": "netease_logo2.png", + "base64": "${image}", + "fitWidth": 100, + "fitHeight": 100, + "opacity": 0.5 + } + ] +} \ No newline at end of file diff --git a/pdf-generator/src/test/java/Excel2PdfTest.java b/pdf-generator/src/test/java/Excel2PdfTest.java new file mode 100644 index 000000000..95c795e1d --- /dev/null +++ b/pdf-generator/src/test/java/Excel2PdfTest.java @@ -0,0 +1,35 @@ +import com.netease.lowcode.pdf.extension.Excel2Pdf; +import com.netease.lowcode.pdf.extension.structures.BaseResponse; +import com.netease.lowcode.pdf.extension.structures.CreateByXlsxRequest; + +public class Excel2PdfTest { + + public static void main(String[] args) { + String s = "{\n" + + " \"name\":\"测试名字\",\n" + + " \"list\":[\n" + + " {\n" + + " \"no\":1,\n" + + " \"name\":\"项目1\",\n" + + " \"std\":\"国标1\"\n" + + " },\n" + + " {\n" + + " \"no\":2,\n" + + " \"name\":\"项目2\",\n" + + " \"std\":\"国标2\"\n" + + " },\n" + + " {\n" + + " \"no\":3,\n" + + " \"name\":\"项目3\",\n" + + " \"std\":\"国标3\"\n" + + " }\n" + + " ]\n" + + "}"; + CreateByXlsxRequest request = new CreateByXlsxRequest(); + request.setJsonData(s); + request.setExportFileName("测试.pdf"); + request.setTemplateUrl("https://dev-excel2pdf-kehfan.app.codewave.163.com:443/upload/app/4c1cce32-ed6c-4659-878d-2b5ab749e24d/白贝壳测试模板_20240822114115167.xlsx"); + BaseResponse baseResponse = Excel2Pdf.xlsx2pdf(request); + System.out.println(baseResponse); + } +} diff --git a/pdf-generator/src/test/java/FontTest.java b/pdf-generator/src/test/java/FontTest.java new file mode 100644 index 000000000..10d85853b --- /dev/null +++ b/pdf-generator/src/test/java/FontTest.java @@ -0,0 +1,31 @@ +import com.itextpdf.io.font.FontProgramDescriptor; +import com.itextpdf.io.font.FontProgramDescriptorFactory; +import com.itextpdf.io.font.FontProgramFactory; +import com.itextpdf.kernel.font.PdfFont; +import com.itextpdf.kernel.font.PdfFontFactory; + +import java.awt.*; +import java.io.IOException; +import java.util.Set; + +public class FontTest { + public static void main(String[] args) throws IOException { + + + GraphicsEnvironment e = GraphicsEnvironment.getLocalGraphicsEnvironment(); + Font[] allFonts = e.getAllFonts(); + for (Font font : allFonts) { + System.out.println(); + } + + PdfFont font = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H"); + System.out.println(); + + PdfFont font1 = PdfFontFactory.createFont("HeiseiKakuGo-W5","UniJIS-UCS2-H"); + PdfFont font2 = PdfFontFactory.createFont("HeiseiMin-W3"); + PdfFont font3 = PdfFontFactory.createFont("STSong-Light"); + + System.out.println(FontProgramFactory.getRegisteredFonts()); + System.out.println(FontProgramFactory.getRegisteredFontFamilies()); + } +} diff --git a/pdf-generator/src/test/java/ImageTest.java b/pdf-generator/src/test/java/ImageTest.java new file mode 100644 index 000000000..3c3a88e55 --- /dev/null +++ b/pdf-generator/src/test/java/ImageTest.java @@ -0,0 +1,29 @@ +import sun.misc.BASE64Encoder; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +public class ImageTest { + + public static void main(String[] args) throws IOException { + +// FileInputStream inputStream = new FileInputStream("pdf-generator/src/main/resources/netease_logo2.png"); +// +// BASE64Encoder encoder = new BASE64Encoder(); +// ByteArrayOutputStream bos = new ByteArrayOutputStream(); +// byte[] buffer = new byte[1024]; +// int read; +// while ((read = inputStream.read(buffer)) != -1) { +// bos.write(buffer, 0, read); +// } +// +// String s = encoder.encodeBuffer(bos.toByteArray()); +// System.out.println(s); + + + String s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUwAAAAoCAYAAACVfMOnAA"; + System.out.println(s.substring(s.indexOf("base64,")+7)); + } +} diff --git a/pdf-generator/src/test/java/JSONObjectUtilTest.java b/pdf-generator/src/test/java/JSONObjectUtilTest.java new file mode 100644 index 000000000..2198798c6 --- /dev/null +++ b/pdf-generator/src/test/java/JSONObjectUtilTest.java @@ -0,0 +1,22 @@ +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.netease.lowcode.pdf.extension.utils.JSONObjectUtil; + +public class JSONObjectUtilTest { + + public static void main(String[] args) { + + JSONArray array = new JSONArray(); + JSONObject e1 = new JSONObject(); + e1.put("a","fff"); + array.add(e1); + JSONObject e2 = new JSONObject(); + e2.put("b","fsd"); + array.add(e2); + JSONObject e3 = new JSONObject(); + e3.put("c","fcd"); + array.add(e3); + JSONObjectUtil.jsonArrayRemove0(array); + System.out.println(); + } +} diff --git a/pdf-generator/src/test/java/PdfGenerator2Test.java b/pdf-generator/src/test/java/PdfGenerator2Test.java new file mode 100644 index 000000000..f5b051945 --- /dev/null +++ b/pdf-generator/src/test/java/PdfGenerator2Test.java @@ -0,0 +1,19 @@ +import com.alibaba.fastjson2.JSON; +import com.netease.lowcode.pdf.extension.PdfGenerator; +import com.netease.lowcode.pdf.extension.structures.CreateByTemplateRequest; + +import java.util.HashMap; +import java.util.Map; + +public class PdfGenerator2Test { + + public static void main(String[] args) { + CreateByTemplateRequest request = new CreateByTemplateRequest(); + + Map data = new HashMap<>(); + data.put("title","测试申请书-食品"); + + request.jsonData = JSON.toJSONString(data); + PdfGenerator.createPDFByTemplate(request); + } +} diff --git a/pdf-generator/src/test/java/PdfGeneratorTest.java b/pdf-generator/src/test/java/PdfGeneratorTest.java new file mode 100644 index 000000000..9e5605681 --- /dev/null +++ b/pdf-generator/src/test/java/PdfGeneratorTest.java @@ -0,0 +1,49 @@ +import com.netease.lowcode.pdf.extension.PdfGenerator; +import com.netease.lowcode.pdf.extension.structures.BaseResponse; +import com.netease.lowcode.pdf.extension.structures.CreateRequest; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class PdfGeneratorTest { + + public static void main(String[] args) { + CreateRequest request = new CreateRequest(); + request.fileName = "test.pdf"; + request.data = new ArrayList<>(); + // 第一部分数据 图片 + HashMap> m1 = new HashMap<>(); + ArrayList arr1 = new ArrayList<>(); + arr1.add("实验室时间校准记录"); + m1.put("pdf-paragraph", arr1); + m1.put("pdf-title",arr1); + request.data.add(m1); + // 第二部分数据 段落 + HashMap> m2 = new HashMap<>(); + ArrayList arr2 = new ArrayList<>(); + arr2.add("起草人/日期,审核人/日期,批准人/日期"); + arr2.add("xxx/2024-10-01,xxx/2024-10-01,xxx/2024-10-01"); + arr2.add("-,-,-"); + m2.put("pdf-table",arr2); + request.data.add(m2); + // 第三部分数据 表格 + HashMap> m3 = new HashMap<>(); + ArrayList arr3 = new ArrayList<>(); + arr3.add("化验室: 001号化验室"); + m3.put("pdf-paragraph", arr3); + request.data.add(m3); + // 第四部分 表格 + HashMap> m4 = new HashMap<>(); + ArrayList arr4 = new ArrayList<>(); + arr4.add("设备名称,设备编号/安装位置,设备显示时间,北京时间,检查人/日期,是否校正,校正人/日期,复核人"); + arr4.add("xx,xx,xx,xx,xx,xx,xx/xx,xx"); + m4.put("pdf-table",arr4); + request.data.add(m4); + + BaseResponse response = PdfGenerator.createPDF(request); + + System.out.println(); + } + +} diff --git a/pdf-generator/src/test/java/PdfGeneratorV2Test.java b/pdf-generator/src/test/java/PdfGeneratorV2Test.java new file mode 100644 index 000000000..3df9a02e2 --- /dev/null +++ b/pdf-generator/src/test/java/PdfGeneratorV2Test.java @@ -0,0 +1,17 @@ +import com.netease.lowcode.pdf.extension.PdfGenerator; +import com.netease.lowcode.pdf.extension.itextpdf.PdfUtils; + +import java.io.IOException; + + +public class PdfGeneratorV2Test { + + public static void main(String[] args) throws IOException { + + String s = PdfUtils.readJson("pdf-generator/src/main/resources/requestData.json"); + + PdfGenerator.createPDFV2(s, + "https://dev-fileupload-kehfan.app.codewave.163.com:443/upload/app/e088646d-2b31-40d6-a5a3-bf459b013ea0/template_20240729135630943.json"); + } + +} diff --git a/pdf-generator/src/test/java/PdfUtilTest.java b/pdf-generator/src/test/java/PdfUtilTest.java new file mode 100644 index 000000000..0be3ae571 --- /dev/null +++ b/pdf-generator/src/test/java/PdfUtilTest.java @@ -0,0 +1,7 @@ +import com.netease.lowcode.pdf.extension.itextpdf.PdfUtils; + +public class PdfUtilTest { + public static void main(String[] args) { + System.out.println(PdfUtils.getPageSize("")); + } +} diff --git a/pdf-generator/src/test/java/TemplateUtilsTest.java b/pdf-generator/src/test/java/TemplateUtilsTest.java new file mode 100644 index 000000000..c79b14da0 --- /dev/null +++ b/pdf-generator/src/test/java/TemplateUtilsTest.java @@ -0,0 +1,81 @@ +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.netease.lowcode.pdf.extension.Excel2Pdf; + +import java.util.ArrayList; +import java.util.List; + +public class TemplateUtilsTest { + + public static void main1(String[] args) { + JSONObject cell = new JSONObject(); + JSONArray array = new JSONArray(); + JSONObject paragraph = new JSONObject(); + paragraph.put("text","${list.arr}"); + array.add(paragraph); + cell.put("elements", array); + System.out.println(Excel2Pdf.isFreemarkerListTag(cell)); + } + + public static void main2(String[] args) { + List list = new ArrayList<>(); + list.add("0"); + list.add("1"); + list.add("2"); + list.add("3"); + list.add("4"); + + int i = 2; + list.add(i,"i0"); + list.add(i+1,"i1"); + list.add(i+2,"i2"); + + list.remove(i+3); + System.out.println(); + } + + public static void main(String[] args) { + List> tmpCells = new ArrayList<>(); + ArrayList row1 = new ArrayList<>(); + JSONObject cell1 = new JSONObject(); + JSONArray elements1 = new JSONArray(); + JSONObject para1 = new JSONObject(); + para1.put("text","表头1"); + elements1.add(para1); + cell1.put("elements", elements1); + row1.add(cell1); + tmpCells.add(row1); + + ArrayList row2 = new ArrayList<>(); + JSONObject cell2 = new JSONObject(); + JSONArray elements2 = new JSONArray(); + JSONObject para2 = new JSONObject(); + para2.put("text","${list.arr}"); + elements2.add(para2); + cell2.put("elements", elements2); + row2.add(cell2); + tmpCells.add(row2); + + ArrayList row3 = new ArrayList<>(); + JSONObject cell3 = new JSONObject(); + JSONArray elements3 = new JSONArray(); + JSONObject para3 = new JSONObject(); + para3.put("text","数据2"); + elements3.add(para3); + cell3.put("elements", elements3); + row3.add(cell3); + tmpCells.add(row3); + + JSONObject requestJsonData = new JSONObject(); + JSONArray requestList = new JSONArray(); + JSONObject item1 = new JSONObject(); + item1.put("arr","arr数据1"); + requestList.add(item1); + JSONObject item2 = new JSONObject(); + item2.put("arr","arr数据2"); + requestList.add(item2); + requestJsonData.put("list", requestList); + Excel2Pdf.handleFreemarkerList(tmpCells, requestJsonData.toJSONString()); + System.out.println(); + } +} From 31ccace0b68a105019efae2232004084d0b0ed6c Mon Sep 17 00:00:00 2001 From: kehfan Date: Thu, 22 Aug 2024 15:39:27 +0800 Subject: [PATCH 2/3] add doc --- pdf-generator/README.md | 5 +++++ ...07\346\241\243\350\257\264\346\230\216.docx" | Bin 0 -> 10223 bytes 2 files changed, 5 insertions(+) create mode 100644 pdf-generator/README.md create mode 100644 "pdf-generator/\344\276\235\350\265\226\345\272\223\344\275\277\347\224\250\346\226\207\346\241\243\350\257\264\346\230\216.docx" diff --git a/pdf-generator/README.md b/pdf-generator/README.md new file mode 100644 index 000000000..0866548e7 --- /dev/null +++ b/pdf-generator/README.md @@ -0,0 +1,5 @@ +支持动态生成pdf +支持xlsx转pdf + +文档内容维护在: +https://community.codewave.163.com/CommunityParent/CodeWareMarketLibraryDetail?id=2931140592946432&isLatest=false&isClassics=false \ No newline at end of file diff --git "a/pdf-generator/\344\276\235\350\265\226\345\272\223\344\275\277\347\224\250\346\226\207\346\241\243\350\257\264\346\230\216.docx" "b/pdf-generator/\344\276\235\350\265\226\345\272\223\344\275\277\347\224\250\346\226\207\346\241\243\350\257\264\346\230\216.docx" new file mode 100644 index 0000000000000000000000000000000000000000..1930fbfef3062190d373d34f9db2480e1e2852fd GIT binary patch literal 10223 zcma)i1yr0#v-aR_gNGqOgH3P^?!lel1a~L6y9IZb5Zoa^f&_O-fS|#GyAyQ&A-ntS zF8_D$xqZ&a>8Yo?`suf8s=7*11`Zw-@X$i#SA`xv|7s}EFUEF8ijH>nPE3kWF+6Ao z@^7)B#R5>y8vdq;H8v3LF~mmzx+|WsIIGe>*vc} z>~hUQY~~pspWIzC)Y5wXnCIX8NTct0wDYZDlsM5efW~$UsS++TBL4WL!)4ER?WSnX zpp^IcAmAnHoNnQmf?H7#ya|zcpM-r@(>oT&@YoL+&SEz%Bl{nKrIfwWYLrT9QJoUw zDo6{tw8*B&jEHV1g*1o$2eD0j8IX|VfD5ndvL`F zcaqlX2it1Jsl!QN6(~TyduoQsVL?7atfFTyhuNE{oB!X_$ z2}VR$AH4)Knewk3NZRD`udFu2>r#d@i zg0$4s=Xg|c-ogBPCrUCC52T?xp$FXwoPX|wk)5N-!&bbD7nbj0#umSPwLpF%P?M1S zu{K^6b`n1+mKL_AZqI^*!v#D~y-$s;joDEX%Z(_X9zvl}T@T6O^5v5+;?+bPdtgkj+@2bYi>J|-_{OA=Ow_a5JUu6;P)9|NxQQbP@h57vKMkEs^XT{- zj~vTKFO2xve%?gFl%%-fs{&1fkzXMd0GgnvYWO~QCv>haikD)Oh)(+a_TzU1J_2dkj}wr%;of0V+tNb{o1+K2_m={26O}-BKOa83i^uO zBmA3q!X`}$dMNQYP~vg_Mf_VQXFHn*>I>t9tpb_x`VIrVLt{Nc-Wf4sAqx|ZOERbPJG4>>#wN8l{IH}Wbs*o z1-|!tX^Cfr7@|Vw7jYlY1X~IIu&*wifKbKffk;-NJiQ^$g38}*8m`By$JBnT1YK%0 z+VqJRUQq?trUF^Jyjay`*u7SkgSiCf+9^mz##~Gr`7z|V6a-gj#--;uh`q}4YfCAB zygM@7 zOH~0Xt*MOt7(dmN)r@HG!qX?cx4_l0IT-vAb1Wm$#eo(;7H&qK@s<0HgAz448f7Dp zA=~>p16x3U_hjf6?t$9>ZiEn^)VkU^8b2~XXZ~}#`A`E@Y61XY{Uze$>~3x1^uT

BhL*_yo`A)SE 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 Date: Thu, 22 Aug 2024 15:45:58 +0800 Subject: [PATCH 3/3] alter plugin version 1.4.1 --- pdf-generator/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf-generator/pom.xml b/pdf-generator/pom.xml index 6ca546d2a..c774c2e38 100644 --- a/pdf-generator/pom.xml +++ b/pdf-generator/pom.xml @@ -123,7 +123,7 @@ com.netease.lowcode nasl-metadata-maven-plugin - 1.4.2 + 1.4.1 org.apache.maven.shared