当前位置 博文首页 > bp001haha的博客:关于Java后台 HTML+CSS3 转换生成PDF文件问题

    bp001haha的博客:关于Java后台 HTML+CSS3 转换生成PDF文件问题

    作者:[db:作者] 时间:2021-08-01 17:59

    关于Java后台 HTML+CSS3 转换生成PDF文件问题求助!

    公司需求:HTML+CSS3(CSS样式是直接写在HTML文件当中)转换为PDF文件,并上传阿里云OSS供用户下载

    • 本身这个需求并不难,功能做完后不管是PDF质量还是生成速度都还不错,其中也遇到某些CSS样式不支持的问题,都用其他支持的样式替代

    • 接下来问题就出现了,文本中突然出现竖行文本CSS样式标签为:writing-mode,生成的PDF中文本并没有竖行显示,明显是itext解析时并不认识这个标签。

    • 由于业务逻辑要求,不可采用控制文本区宽度强制文字换行和其他物理方式来实现,目前查了大量资料,所有在HTML转换PDF这个问题上,对CSS都不太友好。

    • 后试过用Java调用虚拟打印机、先转word在转pdf、wkhtmltopdf、html2pdf等均没成功

    • 总结来说当html中出现竖行文本时,转换PDF希望所见即所得。

    • 现在求助有这方面经验的大神能给点解决思路,或者实现方式,小弟在此不胜感激,后面附上源码。

    	<!-- Itext Html To PDF -->
    	<dependency>  
    		<groupId>org.freemarker</groupId>  
    		<artifactId>freemarker</artifactId>  
    		<version>2.3.23</version>  
    	</dependency>  
    	
    	<dependency>
    		<groupId>org.apache.pdfbox</groupId>
    		<artifactId>pdfbox</artifactId>
    		<version>2.0.2</version>
    	</dependency>
    	
    	<dependency>
    		<groupId>org.xhtmlrenderer</groupId>
    		<artifactId>flying-saucer-pdf-itext5</artifactId>
    		<version>9.1.6</version>
    	</dependency>
    	
    	<dependency>
    		<groupId>net.sf.jtidy</groupId>
    		<artifactId>jtidy</artifactId>
    		<version>r938</version>
    	</dependency>
    	
    	<dependency>
    		<groupId>com.aliyun.oss</groupId>
    		<artifactId>aliyun-sdk-oss</artifactId>
    		<version>2.7.0</version>
    	</dependency>
    
    	@Override
    	public void createPDF(HttpServletRequest request,String bookId) {
    		
    		Book book = bookDao.get(bookId);
    		List<Map<String,Object>> listProvider = bookHtmlDao.getProviderBy(bookId);
    		bookEditionDao.updateEditionNo(bookId, "1");
    		String providerName = listProvider.get(0).get("PROVIDER_NAME").toString();
    		String providerId = listProvider.get(0).get("PROVIDER_ID").toString();
    	
    		// 初始化目录地址
    		String ftlPath = request.getSession().getServletContext().getRealPath("/WEB-INF/classes/cn/jointat/mfb/service/toPDF");
    		String ftlName = "hzSur";
    		String outputPath = "D:/diskPDF/"+providerId+"_"+providerName+"/"+book.getBookName()+"/"+ftlName;
    		
    		// 有水印跟无水印版本分文件夹存
    		String watermarkYesPath = outputPath+"/watermarkYes";
    		String watermarkNoPath = outputPath+"/watermarkNo";
    		
    		System.out.println("====>服务商 【"+providerName+"】 请求生成PDF");
    		System.out.println("========================= PDF开始生成 【"+book.getBookName()+"】 =========================");
    		// 遍历Html代码块集合
    		List<BookHtml> listBookHtml = bookHtmlDao.getBookHtml(bookId);
    		List<String> filesInFolder = new ArrayList<String>();
    		for (int i = 0; i < listBookHtml.size(); i++) {
    			String htmlString = listBookHtml.get(i).getHtmlContent();		//书页html内容
    			// 将图片oss地址转换为内网地址
    	        	htmlString = htmlString.replace("oss-cn-hangzhou.aliyuncs.com", "oss-cn-hangzhou-internal.aliyuncs.com");
    			String outputName = book.getBookName()+"_"+(i+1);
    			// 组装待合并的PDF路径
    			filesInFolder.add(outputName+".pdf");
    			
    			// 有水印版本
    			if(!PDFUtil.createPDF(ftlPath, ftlName, watermarkYesPath, outputName, htmlString, true)) {
    				System.out.println("====>出现异常,导致错误的原因可能是html源码格式错误或模板错误,请联系管理员。");
    			}
    			// 无水印版本
    			if(!PDFUtil.createPDF(ftlPath, ftlName, watermarkNoPath, outputName, htmlString, false)) {
    				System.out.println("====>出现异常,导致错误的原因可能是html源码格式错误或模板错误,请联系管理员。");
    			}
    			System.out.println("====>第 【"+(i+1)+"】 张生成完成");
    			// 封面页PDF生成图片base64
    			if(i==0) {
    				String base64 = PDFUtil.transPDFBase64(watermarkNoPath,outputName, 20);
    				book.setBookLogoImg(base64);
    				bookDao.update(book);
    			}
    		}
    
    		System.out.println("====>开始合并");
    		PDFUtil.mergePDF(watermarkYesPath, filesInFolder, book.getBookName()+"_清样.pdf");
    		PDFUtil.mergePDF(watermarkNoPath, filesInFolder, book.getBookName()+"_原稿.pdf");
    		System.out.println("========================= PDF结束生成 【"+book.getBookName()+"】 =========================");
    		
    		// 得到本地合并后的pdf路径
    		String editionDownloadUrlYes = watermarkYesPath+"/"+book.getBookName()+"_清样.pdf";
    		String editionDownloadUrlNo = watermarkNoPath+"/"+book.getBookName()+"_原稿.pdf";
    		
    		// 清样上传到阿里云oss
    		System.out.println("====>清样上传到阿里云oss");
    		String keyYes = "generatePDF/"+providerId+"_"+providerName+"/"+ftlName+"/Watermarks/"+book.getBookName()+"_清样.pdf";
    		File fileYes = new File(editionDownloadUrlYes);
    		Map<String,Object> mapYes = OssUtil.uploadFileOss("jointat-generate", keyYes, fileYes);
    		
    		// 原稿上传到阿里云oss
    		System.out.println("====>原稿上传到阿里云oss");
    		String keyNo = "generatePDF/"+providerId+"_"+providerName+"/"+ftlName+"/Manuscript/"+book.getBookName()+"_原稿.pdf";
    		File fileNo = new File(editionDownloadUrlNo);
    		Map<String,Object> mapNo = OssUtil.uploadFileOss("jointat-generate", keyNo, fileNo);
    		
    		// 更新PDF为可下载状态、下载地址
    		String linkYes = mapYes.get("link").toString();
    		String linkNo = mapNo.get("link").toString();
    		if(!linkYes.equals("false") && !linkNo.equals("false")) {
    			System.out.println("====>更新谱书为可下载状态");
    			bookEditionDao.updateEditionYes(bookId, "1", linkYes, linkNo);
    		}
    		
    		// 删除文件夹
    		PDFUtil.delAllFile(watermarkYesPath);
    		PDFUtil.delAllFile(watermarkNoPath);
    	}
    
    
    	 * html生成pdf
    	 * @param ftlPath 		ftl模板目录路径
    	 * @param ftlName		ftl模板名称
    	 * @param outputPath		pdf输出路径
    	 * @param outputName		pdf输出名称
    	 * @param htmlString		html源码
    	 * @param watermark		是否添加水印【true-添加、false=不添加】
    	 * @return
    	public static boolean createPDF(String ftlPath,String ftlName,String outputPath,String outputName,String htmlString,boolean watermark) {
    
    		FileOutputStream os = null;
    		try {
    			// 创建一个freemarker.template.Configuration实例,它是存储 FreeMarker 
    			Configuration cf = new Configuration(Configuration.VERSION_2_3_22);  								// 指定版本号
    	        	cf.setDirectoryForTemplateLoading(new File(ftlPath));  	   											// 设置模板目录 
    	        	cf.setDefaultEncoding("UTF-8");  																	// 设置默认编码格式
    	        	Template temp = cf.getTemplate(ftlName+".ftl"); 													// 从设置的目录中获得模板 
    	      
    	        	// html转换为xhtml
    			ByteArrayInputStream inputStream = new ByteArrayInputStream(htmlString.getBytes("UTF-8"));
    	        	ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    	        
    		        // 使用jtidy解析
    		        Tidy tidy = new Tidy();							// 实例化Tidy对象
    		        tidy.setInputEncoding("UTF-8");						// 设置输入
    		        tidy.setQuiet(false);							// 如果是true 不输出注释,警告和错误信息
    		        tidy.setShowWarnings(false);						// 不显示警告信息
    		        tidy.setIndentContent(false);						// 缩进适当的标签内容。
    		        tidy.setSmartIndent(false);						// 内容缩进
    		        tidy.setIndentAttributes(false);
    		        tidy.setPrintBodyOnly(true);						// 只输出body内部的内容
    		        tidy.setWraplen(1024);							// 多长换行
    		        tidy.setXHTML(true);							// 输出为xhtml
    		        tidy.setTrimEmptyElements(false);               			// 不输出空元素
    		        tidy.setMakeClean(false);						// 去掉没用的标签
    		        tidy.setWord2000(false);						// 清洗word2000的内容
    		        tidy.setErrout(new PrintWriter(System.out));				// 设置错误输出信息
    		        tidy.parse(inputStream, outputStream);
    
    			// 从模板生成html文件
    			String fileHtml = "hzSur.html";
    			File file = new File(fileHtml);
    		        if (!file.exists()) {  
    		            file.createNewFile();  
    		        }  
    	        
    		        // 是否构造水印模板
    		        String htmlWatermark = "";
    		        if(watermark) {
    		        	htmlWatermark = "<div class='outFlows'>";
    			        for (int i = 0; i < 10; i++) {
    			        	htmlWatermark += "<div></div>";
    					}
    			        htmlWatermark +="</div>";
    		        }
    		        
    		        // 将数据写入模板
    		        Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file),"UTF-8"));  
    		        Map<String, Object> product = new HashMap<String, Object>();  
    		        product.put("htmlWatermark", htmlWatermark);
    		        product.put("htmlString", outputStream.toString());
    		        temp.process(product, out);  
    		        out.close();  
    		  
    		        // 定义输出目录路径及文件名称
    		        File outDir =new File(outputPath);
    		        outDir.mkdirs();
    		        ITextRenderer renderer = new ITextRenderer();  
    	 	        os = new FileOutputStream(outputPath+"/"+outputName+".pdf");  
    	 	        renderer.setDocument(new File(fileHtml).toURI().toURL().toString());  
    	        
    		        // 获取中文字体  
    		        ITextFontResolver fontResolver = renderer.getFontResolver();  
    	        	fontResolver.addFont(ftlPath+"/MSYH.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
    			fontResolver.addFont(ftlPath+"/SIMSUN.TTC", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);	        
    			fontResolver.addFont(ftlPath+"/SIMSUN.TTC", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
    			fontResolver.addFont(ftlPath+"/STKAITI.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
    			fontResolver.addFont(ftlPath+"/SIMLI.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
    			fontResolver.addFont(ftlPath+"/simfang.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
    			fontResolver.addFont(ftlPath+"/FZSTK.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
    			fontResolver.addFont(ftlPath+"/FZYTK.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
    			fontResolver.addFont(ftlPath+"/simhei.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
    			fontResolver.addFont(ftlPath+"/SIMYOU.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
    
    			// 输出
    		        renderer.layout();  
    		        renderer.createPDF(os);  
    		        renderer.finishPDF();  
    		        renderer = null;  
    		        os.close();
    		        return true;
    		} catch (Exception e) {
    			System.out.println(e);
    			e.printStackTrace();
    			return false;
    		}
    	}
    
    cs