使用itextpdf把HTML转为pdf文件的问题,占位符${}的长度问题,导致的换行,使模板和签约的合同位置坐标不一样
从下图往后面就越差越多,导致盖章的位置也不一样。(html那些是加的表格之类的样式)
请问这种问题应该怎么解决。使模板和填写的内容的合同完全一样
@Component
public class PDFTemplateUtil {
private static Logger logger = LoggerFactory.getLogger(PDFTemplateUtil.class);
@Autowired
private SysOssService sysOssService;
@Value("${config.pdfUrl}")
private String pdfUrl;
/**
* classpath路径
*/
private String classpath = getClass().getResource("/").getPath();
/**
* 指定FreeMarker模板文件的位置
*/
private String templatePath = "/pdf";
/**
* 字体资源文件 存放路径 图片路径
*/
private String fontPath = "template/";
/**
* 字体 [宋体][simsun.ttc] [黑体][simhei.ttf]
*/
private String font = "simsun.ttc";
/**
* 指定编码
*/
private String encoding = "UTF-8";
/**
* 获取Freemarker渲染后的内容
*
* @param content 模板内容
* @param params 参数
*/
private String getFreemarkerContent(String content, Map<String, Object> params) throws Exception {
if (MapUtil.isEmpty(params)) {
return content;
}
//模板
StringReader reader = new StringReader(content);
Template template = new Template("mail", reader, null, "utf-8");
//渲染模板
StringWriter sw = new StringWriter();
template.process(params, sw);
content = sw.toString();
IOUtils.closeQuietly(sw);
return content;
}
/**
* 生成pdf
*
* @param content 模板内容
* @param params 传入到freemarker模板里的数据
*/
public String createPDF(String content, String params) throws FileNotFoundException {
String url = "";
UUID uuid = UUID.randomUUID();
LocalDate date = LocalDate.now();
String filePath = pdfUrl + fontPath + date + uuid + ".pdf";
FileOutputStream out = new FileOutputStream(new File(filePath));
// 创建一个FreeMarker实例, 负责管理FreeMarker模板的Configuration实例
Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
// 指定FreeMarker模板文件的位置
cfg.setClassForTemplateLoading(getClass(), templatePath);
ITextRenderer renderer = new ITextRenderer();
try {
String freemarkerContent = "";
if (params != null) {
Map map = JSONObject.parseObject(params, Map.class);
freemarkerContent = getFreemarkerContent(content, map);
} else {
freemarkerContent = content;
}
//设置utf-8编码
//EmailConfig config = sysParamsService.getValueObject(Constant.MAIL_CONFIG_KEY, EmailConfig.class);
//JavaMailSenderImpl mailSender = createMailSender(config);
//MimeMessage mimeMessage = mailSender.createMimeMessage();
//MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
//messageHelper.setText(content, true);
//freemarkerContent = freemarkerContent.replace("<br>", "<br></br>");
//div格式转换过程中,有几率会使内容消失
freemarkerContent = freemarkerContent.replaceAll("<div", "<span");
freemarkerContent = freemarkerContent.replaceAll("/div>", "/span><br/>");
//关闭未封闭式标签br
freemarkerContent = freemarkerContent.replaceAll("<br[^>]*>", "<br/>");
//处理样式background-color: transparent; IE模式下存在兼容问题,火狐谷歌正常
freemarkerContent = freemarkerContent.replaceAll("background-color: transparent;", " ");
//freemarkerContent = freemarkerContent.replaceAll(" ", "\u00a0");
freemarkerContent = freemarkerContent.replaceAll("\t","\u00a0\u00a0\u00a0\u00a0");
// 不替换标签中的空格
freemarkerContent = textString(freemarkerContent);
freemarkerContent = freemarkerContent.replaceAll("><col width=\"", "/><col width=\"");
freemarkerContent = freemarkerContent.replaceAll("></colgroup>", "/></colgroup>");
freemarkerContent = freemarkerContent.replaceAll("<col width=\"100\">", "<col width=\"100\"/>");
freemarkerContent = freemarkerContent.replaceAll("<colgroup/>", "<colgroup>");
StringBuffer html = new StringBuffer();
html.append("<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN/' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>");
html.append("<html xmlns='http://www.w3.org/1999/xhtml'>");
html.append("<head><style>body{font-family:SimSun;font-size:14px;}</style> ");//此处一定要加,不然字体也不会显示,不要加line-height属性
html.append("<title>" + "无标题文档" + "</title>");
html.append("<style type=\"text/css\">");
html.append(".qlbt-operation-menu {\n" +
" background-color: #fff;\n" +
" box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n" +
" font-size: 14px;\n" +
" z-index: 100;\n" +
" overflow: hidden; }\n" +
" .qlbt-operation-menu .qlbt-operation-menu-dividing {\n" +
" height: 1px;\n" +
" background-color: #efefef; }\n" +
" .qlbt-operation-menu .qlbt-operation-menu-subtitle {\n" +
" color: #999;\n" +
" font-size: 14px;\n" +
" padding: 5px 16px; }\n" +
" .qlbt-operation-menu .qlbt-operation-color-picker {\n" +
" display: flex;\n" +
" align-items: center;\n" +
" flex-wrap: wrap;\n" +
" padding: 0 16px 10px;\n" +
" background-color: #fff;\n" +
" overflow: hidden; }\n" +
" .qlbt-operation-menu .qlbt-operation-color-picker .qlbt-operation-color-picker-item {\n" +
" width: 20px;\n" +
" height: 20px;\n" +
" border: 1px solid #595959;\n" +
" margin-right: 5px;\n" +
" margin-bottom: 5px;\n" +
" cursor: pointer; }\n" +
" .qlbt-operation-menu .qlbt-operation-menu-item {\n" +
" display: flex;\n" +
" align-items: center;\n" +
" padding: 10px 16px;\n" +
" background-color: #fff;\n" +
" cursor: pointer;\n" +
" color: #595959;\n" +
" overflow: hidden;\n" +
" text-overflow: ellipsis; }\n" +
" .qlbt-operation-menu .qlbt-operation-menu-item:hover {\n" +
" background-color: #efefef; }\n" +
" .qlbt-operation-menu .qlbt-operation-menu-item .qlbt-operation-menu-icon {\n" +
" margin-right: 8px;\n" +
" height: 20px;\n" +
" width: 20px;\n" +
" font-size: 0; }");
html.append(".qlbt-col-tool {\n" +
" position: absolute;\n" +
" display: flex;\n" +
" align-items: flex-end;\n" +
" overflow: hidden;\n" +
" z-index: 99;\n" +
" height: 16px; }\n" +
" .qlbt-col-tool .qlbt-col-tool-cell {\n" +
" position: relative;\n" +
" background-color: #fff;\n" +
" border-top: 1px solid #000;\n" +
" border-right: 1px solid #000;\n" +
" border-bottom: 1px solid #000; }\n" +
" .qlbt-col-tool .qlbt-col-tool-cell:first-child {\n" +
" border-left: 1px solid #000; }\n" +
" .qlbt-col-tool .qlbt-col-tool-cell-holder {\n" +
" position: absolute;\n" +
" right: -1px;\n" +
" top: 0;\n" +
" bottom: 0;\n" +
" z-index: 3;\n" +
" width: 1px;\n" +
" background-color: transparent;\n" +
" cursor: ew-resize; }\n" +
" .qlbt-col-tool .qlbt-col-tool-cell-holder:hover {\n" +
" background-color: #0589f3; }\n" +
" .qlbt-col-tool .qlbt-col-tool-cell-holder::before {\n" +
" content: \"\";\n" +
" position: absolute;\n" +
" top: 0;\n" +
" left: -6px;\n" +
" display: block;\n" +
" width: 8px;\n" +
" height: 100%; }\n" +
" .qlbt-col-tool .qlbt-col-tool-cell-holder::after {\n" +
" content: \"\";\n" +
" position: absolute;\n" +
" top: 0;\n" +
" right: -6px;\n" +
" display: block;\n" +
" width: 8px;\n" +
" height: 100%; }\n");
html.append(".quill-better-table-wrapper {\n" +
" overflow-x: auto; }");
html.append("table.quill-better-table {\n" +
" table-layout: fixed;\n" +
" border-collapse: collapse; }\n" +
" table.quill-better-table td {\n" +
" border: 1px solid #000;\n" +
" padding: 2px 5px; }");
/*html.append("table{\n" +
" page-break-inside:auto;-fs-table-paginate:paginate;border-spacing:0;table-layout:fixed; " +
"word-break:break-strict; cellspacing:0;cellpadding:0; padding: 2px 2px;}");*/
html.append("tr { page-break-inside:avoid; page-break-after:auto;}");
html.append(".ql-align-center {\n" +
"text-align: center;\n" +
"}");
html.append(".no-border td {\n" +
" border: 0;\n" +
"}");
html.append("</style>");
html.append("</head>");
html.append("<body>");
html.append("<div>" + freemarkerContent + "</div>");//这里根据实际情况替换成你的HTML代码就OK了
html.append("</body></html>");
// 把html代码传入渲染器中
renderer.setDocumentFromString(html.toString());
// 解决图片的相对路径问题 ##必须在设置document后再设置图片路径,不然不起作用
// 如果使用绝对路径依然有问题,可以在路径前面加"file:/"
// 设置 css中 的字体样式(暂时仅支持宋体和黑体)
ITextFontResolver fontResolver = renderer.getFontResolver();
fontResolver.addFont(pdfUrl + fontPath + font, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
// 解决中文支持问题
renderer.getSharedContext().setBaseURL(pdfUrl + fontPath);
renderer.layout();
renderer.createPDF(out, false);
renderer.finishPDF();
out.flush();
out.close();
File file = new File(filePath);
FileInputStream fileInputStream = new FileInputStream(file);
MultipartFile multipartFile = new MockMultipartFile(file.getName(), file.getName(),
ContentType.APPLICATION_OCTET_STREAM.toString(), fileInputStream);
//上传文件
String extension = FilenameUtils.getExtension(multipartFile.getOriginalFilename());
url = OSSFactory.build().uploadSuffix(multipartFile.getBytes(), extension);
//保存文件信息
SysOssEntity ossEntity = new SysOssEntity();
ossEntity.setUrl(url);
ossEntity.setCreateDate(new Date());
sysOssService.insert(ossEntity);
file.delete();
} catch (Exception e) {
logger.error("表格生成失败 - {}" + e);
e.printStackTrace();
}
return url;
}
public static String textString(String str) {
String result = "";
char[] c = str.toCharArray();
int length = c.length;
int i = 0;
while (i < length) {
String temp = "";
while (i < length && c[i] != '<') {
temp += c[i++];
}
result += temp.replaceAll(" ", "\u00a0");
while (i < length && c[i] != '>') {
result += c[i++];
}
}
return result;
}
private JavaMailSenderImpl createMailSender(EmailConfig config) {
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost(config.getSmtp());
sender.setPort(config.getPort());
sender.setUsername(config.getUsername());
sender.setPassword(config.getPassword());
sender.setDefaultEncoding("Utf-8");
Properties p = new Properties();
p.setProperty("mail.smtp.timeout", "10000");
p.setProperty("mail.smtp.auth", "false");
p.setProperty("mail.smtp.ssl.enable", "true");
sender.setJavaMailProperties(p);
return sender;
}
public void setClasspath(String classpath) {
this.classpath = classpath;
}
public void setTemplatePath(String templatePath) {
this.templatePath = templatePath;
}
public void setFontPath(String fontPath) {
this.fontPath = fontPath;
}
public void setFont(String font) {
this.font = font;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
@Override
public String toString() {
return "[templatePath] : " + templatePath + " \r\n "
+ "[fontPath] : " + fontPath + " \r\n "
+ "[font] : " + font + " \r\n "
+ "[encoding] : " + encoding;
}
}