整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

把HTML转成PDF的4个方案及实现方法

本文中,我将展示如何使用 Node.js、Puppeteer、headless Chrome 和 Docker 从样式复杂的 React 页面生成 PDF 文档。

背景:几个月前,一个客户要求我们开发一个功能,用户可以得到 PDF 格式的 React 页面内容。该页面基本上是患者病例的报告和数据可视化结果,其中包含许多 SVG。另外还有一些特殊的请求来操纵布局,并对 HTML 元素进行一些重新排列。因此与原始的 React 页面相比,PDF 中应该有不同的样式和额外的内容。

由于这个任务比用简单的 CSS 规则解决要复杂得多,所以我们先探讨了可能的实现方法。我们找到了 3 个主要解决方案。这篇博文将指导你了解它们的可能性并最终实施。

目录:

  • 在客户端还是服务器端生成?
  • 方案1:从 DOM 制作屏幕截图
  • 方案2:仅使用 PDF 库
  • 最终方案3:Node.js、Puppeteer 和 Headless Chrome
  • 样式控制
  • 将文件发送到客户端并保存
  • 在 Docker 中使用 Puppeteer
  • 方案3 +1:CSS打印规则
  • 总结

在客户端还是服务器端生成?

在客户端和服务器端都可以生成PDF文件。但是让后端处理它可能更有意义,因为你并不想耗尽用户浏览器可以提供的所有资源。

即便如此,我仍然会展示这两种方法的解决方案。

方案1:从 DOM 制作屏幕截图

乍一看,这个解决方案似乎是最简单的,事实证明的确是这样,但它有其自身的局限性。如果你没有特殊需求,例如在 PDF 中选择文本或对文本进行搜索,那么这就是一种简单易用的方法。

此方法简单明了:从页面创建屏幕截图,并把它放到 PDF 文件中。非常直截了当。我们可以使用两个包来实现:

  • Html2canvas,根据 DOM 生成截图
  • jsPdf,一个生成PDF的库

开始编码:

npm install html2canvas jspdf

就这样!

请注意 html2canvas 的 onclone方法。当你在截图之前需要操纵 DOM(例如隐藏打印按钮)时,它是非常方便的。我看到过很多使用这个包的项目。但不幸的是,这不是我们想要的,因为我们需要在后端完成对 PDF 的创建工作。

方案2:只使用 PDF 库

NPM上有几个库,如 jsPDF(如上所述)或PDFKit。他们的问题是,如果我想使用这些库,我将不得不重新调整页面结构。这肯定会损害可维护性,因为我需要将所有后续更改应用到 PDF 模板和 React 页面中。

请看下面的代码。你需要亲自手动创建 PDF 文档。你需要遍历 DOM 并找出每个元素并将其转换为 PDF 格式,这是一项繁琐的工作。必须找到一个更简单的方法。

这段代码段来自 PDFKit 文档。但是如果你的目标是直接生成一个 PDF 文件,而不是对一个已经存在的(并且不断变化的)HTML 页面进行转换,它还是很有用的。

最终方案3:基于 Node.js 的 Puppeteer 和 Headless Chrome

什么是 Puppeteer?其文档中写道:

Puppeteer 是一个 Node 库,它提供了一个高级 API 来控制 DevTools 协议上的 Chrome 或 Chromium。 Puppeteer 默认以 headless 模式运行 Chrome 或 Chromium,但其也可以被配置为完整的(non-headless)模式运行。

它本质上是一个可以从 Node.js 运行的浏览器。如果你读过它的文档,其中首先提到的就是你可以用 Puppeteer 来生成页面的截图和PDF。优秀!这正是我们想要的。

先用 npmi i puppeteer 安装 Puppeteer,并实现我们的功能。

这是一个简单的功能,可导航到 URL 并生成站点的 PD F文件。

首先,我们启动浏览器(仅在 headless 模式下支持 PDF 生成),然后打开新页面,设置视口,并导航到提供的URL。

设置 waitUntil:'networkidle0' 选项意味着当至少500毫秒没有网络连接时,Puppeteer 会认为导航已完成。 (可以从 API docs 获取更多信息。)

之后,我们将 PDF 保存为变量,关闭浏览器并返回 PDF。

注意:page.pdf 方法接收 options 对象,你可以使用 'path' 选项将文件保存到磁盘。如果未提供路径,则 PDF 将不会被保存到磁盘,而是会得到缓冲区。(稍后我将讨论如何处理它。)

如果需要先登录才能从受保护的页面生成 PDF,首先你要导航到登录页面,检查表单元素的 ID 或名称,填写它们,然后提交表单:

要始终将登录凭据保存在环境变量中,不要硬编码!

样式控制

Puppeteer 也有这种样式操作的解决方案。你可以在生成 PDF 之前插入样式标记,Puppeteer 将生成具有已修改样式的文件。

await page.addStyleTag({ content: '.nav { display: none} .navbar { border: 0px} #print-button {display: none}' })

将文件发送到客户端并保存

好的,现在你已经在后端生成了一个 PDF 文件。接下来做什么?

如上所述,如果你不把文件保存到磁盘,将会得到一个缓冲区。你只需要把含有适当内容类型的缓冲区发送到前端即可。

现在,你只需在浏览器向服务器发送请求即可得到生成的 PDF。

一旦发送了请求,缓冲区的内容就应该开始下载了。最后一步是将缓冲区数据转换为 PDF 文件。

就这样!如果单击“保存”按钮,那么浏览器将会保存 PDF。

在 Docker 中使用 Puppeteer

我认为这是实施中最棘手的部分 —— 所以让我帮你节省几个小时的百度时间。

官方文档指出*“在 Docker 中使用 headless Chrome 并使其运行起来可能会非常棘手”*。官方文档有疑难解答部分,你可以找到有关用 Docker 安装 puppeteer 的所有必要信息。

如果你在 Alpine 镜像上安装 Puppeteer,请确保在看到页面的这一部分时再向下滚动一点。否则你可能会忽略一个事实:你无法运行最新的 Puppeteer 版本,并且你还需要用一个标记禁用 shm :

const browser = await puppeteer.launch({
 headless: true,
 args: ['--disable-dev-shm-usage']
});

否则,Puppeteer 子进程可能会在正常启动之前耗尽内存。

方案 3 + 1:CSS 打印规则

可能有人认为从开发人员的角度来看,简单地使用 CSS 打印规则很容易。没有 NPM 模块,只有纯 CSS。但是在跨浏览器兼容性方面,它的表现如何呢?

在选择 CSS 打印规则时,你必须在每个浏览器中测试结果,以确保它提供的布局是相同的,并且它不是100%能做到这一点。

例如,在给定元素后面插入一个 break-after 并不是一个多么高深的技术,但是你可能会惊讶的发现要在 Firefox 中使用它需要使用变通方法。

除非你是一位经验丰富的 CSS 大师,在创建可打印页面方面有很多的经验,否则这可能会非常耗时。

如果你可以使打印样式表保持简单,打印规则是很好用的。

让我们来看一个例子吧。

@media print {
 .print-button {
 display: none;
 }
 
 .content div {
 break-after: always;
 }
}

上面的 CSS 隐藏了打印按钮,并在每个 div 之后插入一个分页符,其中包含content 类。有一篇很棒的文章总结了你可以用打印规则做什么,以及它们有什么问题,包括浏览器兼容性。

考虑到所有因素,如果你想从不那么复杂的页面生成 PDF,CSS打印规则非常有效。

总结

让我们快速回顾前面介绍的方案,以便从 HTML 页面生成 PDF 文件:

  • 从 DOM 产生截图:当你需要从页面创建快照时(例如创建缩略图)可能很有用,但是当你需要处理大量数据时就会有些捉襟见肘。
  • 只用 PDF 库:如果你打算从头开始以编程方式创建 PDF 文件,这是一个完美的解决方案。否则,你需要同时维护 HTML 和 PDF 模板,这绝对是一个禁忌。
  • Puppeteer:尽管在 Docker 上工作相对困难,但它为我们的实现提供了最好的结果,而且编写代码也是最简单的。
  • CSS打印规则:如果你的用户受过足够的教育,知道如何把页面内容打印到文件,并且你的页面相对简单,那么它可能是最轻松的解决方案。正如你在我们的案例中所看到的,事实并非如此。

虽然今天是愚人节,但是以上所有内容都是在真的!

作者:疯狂的技术宅

链接:https://juejin.im/post/5ca1dc0251882543d569e075

Java实现根据svg模版动态生成图片

使用场景

需要Java语言动态生成图片

用流程图简单说明下我这边工作中使用的场景

仅供参考

所以这里就需要生成证书了

我先给大家看下最终实现的图片效果

这里要先说明一下

  • 图片上的文字都是动态变化的即不同的订单对应的图片内容都不一样
  • 图片上还可以嵌入图片哦 比如上图的logo图片

下面说下我是如何解决的

通过PhantomJS来实现

这种方式是不能实现这个需求的

这个的原理就是对网页截图 但只能对于静态页面截图 不能根据不同的参数值动态生成图片

所以不提倡使用这种方式

但也介绍下这种使用方式 朋友们根据自己的实际需求情况有选择的使用

通过html代码实现图片的效果 放入web容器(比如nginx)中部署

这是h5代码

test文件夹下面的内容

安装一个docker nginx 将test文件夹加载到nginx容器的/usr/share/nginx/html目录下面

docker run --name nginx80  -p 8000:80  -v /tmp/test:/usr/share/nginx/html -d docker.io/nginx

访问的页面效果

访问该页面进行截图

这张图片是截图生成的图片 但url中的id值并没有传给页面

在h5代码中请求后端接口获取数据动态显示出来也是不可以的

所以这种方式使用局限性很窄

简单介绍下代码原理

大致原理是 通过http请求该url获取该url的文件流然后解析h5代码生成图片

通过SVG模版动态生成

先写svg模版(其实也是h5代码)

读取svg模版 动态传入参数生成图片

其实现原理大致为 读取svg document h5代码 将动态参数map解析到h5代码中 转换成字节数组 生成图片格式

Linux环境图片中文乱码

我本地是mac系统没有这个问题 在发布到测试环境linux系统出现了这个问题

先看下问题的现象

看到了没 生成的图片中文全是乱码

原因是因为linux系统没有中文字体

既然linux系统没有中文字体 那么就安装它嘛 let's 盘它!!!

先看下mac环境的字体情况

  • 安装字体管理工具
brew install fontconfig
  • 查看支持中文
fc-list :lang=zh    (注意‘:’前的空格)

mac环境默认会安装很多中文字体

再看下linux环境

  • 安装字体管理工具
yum -y install fontconfig
  • 查看支持中文
fc-list :lang=zh

果然没有中文字体

开始安装中文字体

将mac环境的宋体上传到linux环境

a 先在mac系统中找到字体安装目录

/System/Library/Fonts

b 找到宋体对应的文件

c 将该文件上传到linux指定的目录下

/usr/share/fonts/chinese

d 赋予文件夹操作权限

chmod -R 755 /usr/share/fonts/chinese

e 安装ttmkfdir来搜索目录中所有的字体信息,并汇总生成fonts.scale文件

yum -y install ttmkfdir

ttmkfdir -e /usr/share/X11/fonts/encodings/encodings.dir

修改字体配置文件

vi /etc/fonts/fonts.conf
添加
<dir>/usr/share/fonts/chinese</dir>

刷新内存中的字体缓存

fc-cache

确认是否安装成功

在jdk中安装该宋体

a 找到jdk所在的安装目录

echo $JAVA_HOME 

b 将宋体文件复制过来

cp /usr/share/fonts/chinese/STHeiti\ Light.ttc /usr/local/software/jdk1.8.0_141/jre/lib/fonts/fallback

fallback代表存放后备语言的文件夹

重启java服务即可

DEMO代码

https://gitee.com/pingfanrenbiji/resource/tree/master/image

注意: 引入的依赖问题

  <!--phantomjs -->
  <dependency>
   <groupId>org.seleniumhq.selenium</groupId>
   <artifactId>selenium-java</artifactId>
   <version>2.53.1</version>
  </dependency>
  <dependency>
   <groupId>com.github.detro</groupId>
   <artifactId>ghostdriver</artifactId>
   <version>2.1.0</version>
  </dependency>

  <!--svg-->
  <dependency>
   <groupId>com.github.hui.media</groupId>
   <artifactId>svg-core</artifactId>
   <version>2.5</version>
  </dependency>

这些依赖jar包我是上传到了公司的私服上了

若是朋友们下拉不下来

我提供给大家这些底层jar包的实现源码

https://gitee.com/pingfanrenbiji/quick-media

自行上传到自己的私服即可

天给大家分享一款超全功能的跨浏览器平台甘特图表库DHTMLXGantt

dhtmlx-gantt DHTMLX公司开源的 JavaScript 甘特图/横道图/条状图类库。用来快速构建显示项目、进度,和随着时间关联的相关进展情况。

说明

dhtmlx-Gantt 由位于俄罗斯圣彼得堡DHTMLX公司开发的甘特图组件,适用于B/S模式的Web应用开发。被广泛应用于项目管理、建筑、IT软件、汽车等领域。

快速创建

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
	<title>gantt demo</title>
	<script src="dhtmlxgantt.js?v=6.0.0"></script>
	<link rel="stylesheet" href="dhtmlxgantt.css?v=6.0.0">
	<style>
		html, body {
			height: 100%;
			padding: 0px;
			margin: 0px;
			overflow: hidden;
		}
	</style>
</head>
<body>
<div id="gantt_here" style='width:100%; height:100%;'></div>
<script>
	var taskList = {
		data: [
			{
				id: 1, text: "Project #2", start_date: "01-04-2018", duration: 18, order: 10,
				progress: 0.4, open: true
			},
			{
				id: 2, text: "Task #1", start_date: "02-04-2018", duration: 8, order: 10,
				progress: 0.6, parent: 1
			},
			{
				id: 3, text: "Task #2", start_date: "11-04-2018", duration: 8, order: 20,
				progress: 0.6, parent: 1
			}
		],
		links: [
			{id: 1, source: 1, target: 2, type: "1"},
			{id: 2, source: 2, target: 3, type: "0"}
		]
	};
	
	gantt.config.date_format = "%Y-%m-%d %H:%i";
	gantt.init("gantt_here");
	gantt.parse(taskList);
</script>
</body>
</html>

为了配置甘特图所需外观,dhtmlxGantt提供了2个对象 configtemplates

gantt.config //日期,比例,控件等的配置选项。
gantt.templates //格式化甘特图中使用的日期和标签的模板。
  • gantt.config 年示图
gantt.config.scale_unit = "year"; //按年显示
gantt.config.step = 1.5;	//设置时间刻度的步长(X轴)
gantt.config.date_scale = "%Y";	//日期尺度按年

gantt.init("gantt_here");

  • gantt.config 月示图
gantt.config.scale_unit = "month"; //按月显示
gantt.config.date_scale = "%F, %Y"; //设置时间刻度的格式(X轴) 多个尺度

gantt.config.scale_height = 50; //设置时间刻度的高度和网格的标题

gantt.config.subscales = [
	{unit: "day", step: 1, date: "%j, %D"}
]; //指定第二个时间刻度

gantt.init("gantt_here");

  • gantt.templates 可用于更改日期和标签的显示。
gantt.templates.task_text=function(start,end,task){
	return "<b>Text:</b> "+task.text+",<b> Holders:</b> "+task.username;
};
gantt.init("gantt_here");

官网提供了丰富的文档示例。

非常棒的一款开源甘特图库,可以让你预测时间、成本、数量及质量上的关联并回溯结果。也能帮助你考虑人力、资源、日期、项目中重复的要素和关键部分,让你更加直观的看到任务进展及资源的利用率等。

# 官网地址
https://dhtmlx.com/docs/products/dhtmlxGantt/

# 仓库地址
https://github.com/DHTMLX/gantt

好了,今天就介绍到这里。大家如果有其它不错的甘特图库,欢迎一起交流讨论!