同步操作将从 nop/bookjs-eazy 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
渲染机制:
1. 将PDF页面内容元素放置body>#content-box节点下(参考:PDF内容设计)
2. 程序会检查全局变量window.bookConfig.start 的值(参考:配置页面参数)
直到此值为true时,才开始渲染将 #content-box 节点的内容渲染为PDF样式。
**重要**:如果你的页面是动态的,就先将默认值设为false,当内容准备好后,在将其设为true。
3. 高度页面溢出检测原理:
页面内容节点.nop-page-content,是一个弹性高度的容器节点。
在向页面加入内容时会引起容器节点的高度变化。
计算页面的是否溢出,就时通过计算它高度得到的。
**注意**:
a. display: float, position: absolute; overflow样式的元素的插入不会页面容器高度变化。可能造成页面溢出而检测不到。
b. 因为 margin样式的元素 无法撑开.nop-page-content 大小,造成.nop-page-content位置偏移,很容易造成页面出现溢出的现象,所以控制相对位置尽量使用padding
<script>
bookConfig = {
// !!重要!!重要!!
// 当这个值为true时,页面才开始渲染。如果你的页面是动态的,
// 就先将默认值设为false,当下节所述中的#content-box节点内容**准备好**后,在将其设为true
// **准备好**是指:所有html渲染完毕。例如:有多个异步请求,有图表等。参考:eazy-1.html
// bookConfig.start = true; // 开始bookjs渲染,差分页面。
// 除了这个配置项外其他参数都是可选的!!!
start : true,
/** 全部纸张类型,未全量测试,常用ISO_A4
ISO_A0、ISO_A1、ISO_A2、ISO_A3、ISO_A4、ISO_A5
ISO_B0、ISO_B1、ISO_B2、ISO_B3、ISO_B4、ISO_B5、ISO_B6、ISO_B7、ISO_B8、ISO_B9、ISO_B10
ISO_C0、ISO_C1、ISO_C2、ISO_C3、ISO_C4、ISO_C5、ISO_C6、ISO_C7、ISO_DL、ISO_C7_6
JIS_B0、JIS_B1、JIS_B2、JIS_B3、JIS_B4、JIS_B5、JIS_B6、JIS_B7、JIS_B8、JIS_B9
NA_LEGAL、NA_LETTER、NA_LEDGER、NA_EXECUTIVE、NA_INVOICE、
BIG_K32
**/
// 定义纸张大小,两种方式,可选,默认:ISO_A4
pageSize : 'ISO_A4',
orientation : 'landscape', // portrait/landscape 定义纸张是竖屏/横屏放置
/** pageSizeConfig 和 pageSize/orientation组合 ,只选其一即可 **/
pageSizeOption : {
width : '15cm', // 自定义宽高
height : '20cm',
}
// 可选,边距,所列选项为默认值
padding : "31.8mm 25.4mm 31.8mm 25.4mm",
//
// -- !!!! 看到这里可以先去《PDF内容设计》章节,有需要时再来详细了解下面的参数 !!!! --//
// 可选,高度修复选项。
// 不同的浏览器在打印时PDF有可能出现每页都多出空白页
// 常用于自定义未适配的纸张。
// 通过此值调节修正
pageFixedHeightOffset : -1.0, // 单位mm,一般为负值
// 可选,强制打印背景页,所列选项为默认值
forcePrintBackground : true,
// 可选,文本内容在跨页差分时,不会出现在段首的字符,所列选项为默认值
textNoBreakChars : [',','。',':','”','!','?','、',';','》','】','…','.',',','!',']','}','}'],
// 可选,毫秒,生成PDF延时时间,(此配置项不影响预览)。有些页面包含一些异步不可控因素。调整此值保证页面打印正常。可以适当调节此值以优化服务端生成PDF的速度
printDelay : 1000,
// 简易页码插件,可选(默认未开启),所列选项为开启时的默认值
simplePageNum : {
// 从第几页开始页码编号,默认值:0 表示从第一页开始。如果不确定开始页,也可以指定一个css选择器如:".first_page",表示从包含选择器节点的页面开始编号
pageBegin : 0,
// 从第几页结束页码编号,默认值:-1 表示最后一页结束。如果不确定结束页,也可以为一个css选择器如:".end_page",表示从包含选择器节点的页面结束编号
pageEnd : -1,
// 页面部件, 可选
pendant : '<div class="page-num-simple"><span style="">$' + '{PAGE} / $' + '{TOTAL_PAGE}</span></div>',
},
// 目录/书签插件,可选(默认未开启),所列选项为开启时的默认值
simpleCatalog : {
// 当前版本,如果需要生成PDF书签,toolBar.serverPrint.wkHtmlToPdf 必须为true
// titlesSelector不要修改,使用h1-h6标记书签
titlesSelector : 'h1,h2,h3,h4,h5,h6', // 可选,作为目录标题的选择器,按目录级别依次
/** 目录相关选项 **/
showCatalog : true, // 可选,是否在页面中插入目录,默认,插入目录到页面
header : '<div class="catalog-title">目 录</div>', // 可选,目录页Header部分,放入你想加入的一切
itemFillChar : '…', // 可选,目录项填充字符, ""空字符串,不填充,使用自定义makeItem时,忽略该选项配置
positionSelector : '.nop-page-item-pagenum-1', //可选,目录位置会插入在匹配页的之前,默认为第一个编号页前
// 可选,自定义目录项。
makeItem : function(itemEl,itemInfo) {
/**
* @var itemEl jQuery Element
* @var object itemInfo PS: {title, pageNum, level,linkId}
**/
return '<div>自定义的目录项html内容,根据itemInfo自己构造</div>';
},
/** 侧边栏(PDF书签)相关选项 **/
showSlide : true, // 可选,是否显示侧边栏,目录导航,工具栏按钮顺序index: 200, 在bookConfig.toolBar选项为false时无效
slideOn : false, // 可选,目录导航,默认是否打开状态
slideHeader : '<div class="title">目 录</div>', // 可选,侧边栏标题
slideClassName : '', // 可选,侧边栏自定义class
slidePosition : 'left', // 可选,位置 left、right
slideMakeContent : null, // 自定义侧边栏内容处理函数,为null时,默认行为:使用目录内容填充, function(){ return '侧边栏内容';}
},
// 工具栏插件,可选(默认开启),object|false, false会不显示工具栏, 所列选项为开启时的默认值
toolBar : {
// Web打印按钮功能可选,默认true, 按钮顺序index: 100
webPrint : true,
/**
* HTML保存按钮,可选,bool|object,默认false:禁用保存HTML功能,true:启用并使用默认选项
* 按钮顺序index: 300
* saveHtml : {
* // 可选,保存的文件名,默认值: document.title + '.html'
* fileName : 'output.html',
* // 可选,自定义下载保存。可用于混合APP内下载时用
* save : function(getStaticHtmlPromiseFunc,fileName){
* getStaticHtmlPromiseFunc().then(function(htmlBlob){
* ...
* })
* }
* }
*/
saveHtml : false,
/**
* 服务端打印下载按钮,按钮顺序index: 400
* 可选,bool|object,默认false:不启用,true:启用并使用默认选项,object:使用自定义的服务端打印
* true等效的object的配置:serverPrint : { serverUrl : '/' },
* 要使用serverPrint,必须server能访问到你的网页。网页不要使用登录状态授权,建议通过URL参数传递临时授权
* 如果使用官方的server进行打印,则需公网上可正确访问你用bookjs-eazy构造的网页
*
* serverPrint : {
* // 可选,打印服务器地址,按钮顺序index: 400
* serverUrl : '/',
*
* // 可选,true时使用wkHtmlPdf制作,false:默认使用chrome headless
* // **注意**:wkhtmltopdf不支持es6,缺失一些web新特性,好处在于可以生成PDF目录书签。
* // 为了更好的调试发现问题:请下载QtWeb浏览器,其内核于wkhtmltopdf是一样的。
* // 菜单->工具->启用网页检查器,右键页面内容,选择检查 进入debug工具栏
* // 以便发现各种兼容问题
* // 在QtWeb浏览器中默认为打印模式的样式。不显示页间隔,及工具栏
* // 下载链接:http://www.qtweb.net/download.html
* wkHtmlToPdf : false,
*
* // 可选,保存的文件名,默认值 document.title + '.pdf'
* fileName : 'output.pdf',
* // 可选,打印附属参数
* params : {
* // 打印超时时间
* timeout : 30000,
* // 页面渲染完成后,打印前延时
* delay : 1000,
* },
* // 可选,自定义下载。可用于混合APP内下载时用
* save : function(pdfUrl, serverPrintOption){
*
* }
* }
*/
serverPrint : false,
buttons : [
// 这里可以自定义工具栏按钮
// {
// id : 'cloudPrint',
// index : 1, // 按钮位置顺序,小的显示在前面,系统内置按钮index值,见各配置项说明。
// icon : 'https://xxxx.../aa.png'
// onClick : function(){ console.log("...do some thing"); }
// }
],
className: '', // 额外自定义的class属性
position : 'right',// 位置:right、left
}
}
</script>
<body>
<div id="content-box">
<p data-op-type="text">Hello World!</p>
</div>
</body>
<div data-op-type="block">...</div>
#content-box> 下的一级节点
[data-op-type=mix-box] .nop-fill-box> 混合盒子容器节点下的一级节点
[data-op-type=table] tbody td> 表格的单元格的
<p data-op-type="text"> long text...</p>
<p data-op-type="text"> long text2...</p>
#content-box> 下的一级节点
[data-op-type=mix-box] .nop-fill-box> 混合盒子容器节点下的一级节点
[data-op-type=table] tbody td> 表格的单元格的一级节点
<div data-op-type="new-page">仅仅是一个标记节点,这里的内容是不会渲染的</div>
#content-box> 下的一级节点
[data-op-type=mix-box] .nop-fill-box> 混合盒子容器节点下的一级节点
[data-op-type=table] tbody>tr 表格的tbody下的tr节点,(与被标记到其他位置不同,被标记的tr节点会保留不会从页面删除)
#content-box> 下的一级节点
<div data-op-type="pendants">
<div style="top: 50px;left: 100px;width: 100px;height: 100px"><img src="...." alt="logo"></div>
<div style="bottom: 50px;left: 400px;width: 100%;height: 30px"><span>页尾:章节:xxx</span></div>
</div>
<div data-op-type="mix-box"><!-- 跨页时:这个节点会被复制到下一页,除nop-fill-box内所有的内容都会被复用,一个data-op-type里只可以有一个容器节点(class:nop-fill-box),容器节点可以在data-op-type="mix-box"里的任意位置 -->
<div class="title">布局1</div>
<div class="nop-fill-box">
<!-- 跨页时,class: nop-fill-box 里的内容会接着上一页页继续填充 -->
<span data-op-type="text">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</span>
<span data-op-type="new-page"></span><!-- 插入新页 -->
<span data-op-type="text" sytle="color:red">BBBBBBBBBBBBBBBBBBBBBBB</span>
<a data-op-type="text" target="_blank" href="https://baijiahao.baidu.com/s?id=1726750581584920901&wfr=spider&for=pc">文章链接...</a><!-- 这里的链接文字:如果跨页最两页里都会有超链接 -->
</div>
<div class="title">布局2</div>
<div class="title">布局3</div>
</div>
#content-box> 下的一级节点
#content-box> 下的一级节点
<table data-op-type="table" class="nop-simple-table-2">
<thead>
<tr>
<td width="100">生物种类</td>
<td width="100">子类别</td>
<td width="500">详解介绍</td>
</tr>
</thead>
<tbody>
<tr>
<td data-split-repeat="true" rowspan="2">动物</td>
<td data-split-repeat="true">爬行动物</td>
<td>
<p data-op-type="text">long text1 ...<p>
<img data-op-type="block" src="..." />
<p data-op-type="text">long text2 ...</p>
<div data-op-type="block">... </div>
</td>
</tr>
<tr>
<td data-split-repeat="true">哺乳动物</td>
<td>
<p data-op-type="text">long text1 ...</p>
<img data-op-type="block" src="..." />
<p data-op-type="text">long text2 ...</p>
<div data-op-type="block">... </div>
</td>
</tr>
<tr>
<td>植物</td>
<td>蕨类</td>
<td>
<p>long text...</p>
</td>
</tr>
</tbody>
</table>
#content-box> 下的一级节点
<table data-op-type="block-box" style="font-size: 16px">
<thead>
<tr><th>ID</th><th>name</th></tr>
</thead>
<tbody class="nop-fill-box">
<tr><td>1</td><td>name1</td></tr>
<tr><td>2</td><td>name2</td></tr>
<tr><td>...</td><td>...</td></tr>
</tbody>
</table>
<div id="content-box" style="display: none">
<div data-op-type="pendants"><!-- 定义页面部件(页眉/页脚/页标签/水印背景等) -->
<div class='pendant-title'>第一章:块盒子</div>
</div>
<h1 data-op-type='block'>第1章 块盒子</h1><!-- 块 -->
<table data-op-type="block-box" class="nop-simple-table-2" border="1"><!-- 块盒子 -->
<thead>
<tr><th>ID</th><th>姓名</th><th>年龄</th></tr>
</thead>
<tbody class="nop-fill-box"><!-- 子块列表,程序会自动差分 -->
<tr><td>1</td><td>张三</td><td>12</td></tr>
...
</tbody>
<tfoot>
<tr><td colspan="3">表格尾部</td></tr>
</tfoot>
</table>
<div data-op-type="new-page"></div><!-- 新页面标记,强制从新页开始 -->
<div data-op-type="pendants"><!-- 定义页面部件(页眉/页脚/书签/水印背景等) -->
<div class='pendant-title'>第二章:文本盒子</div>
</div>
<h1 data-op-type='block'>第2章 文本盒子</h1><!-- 块 -->
<p data-op-type="text-box"><!-- 文本盒子 -->
<span class="nop-fill-box">1234566....(很长的文字)</span><!-- 大文本,程序会自动差分 -->
</p>
<div data-op-type="new-page"></div><!-- 新页面标记,强制从新页开始 -->
<div data-op-type="pendants"><!-- 定义页面部件(页眉/页脚/书签/水印背景等) -->
<div class='pendant-title'>第三章:混合盒子</div>
</div>
<h1 data-op-type='block'>第3章 混合盒子</h1><!-- 块 -->
<div data-op-type="mix-box"><!-- 混合盒子 -->
<div class="nop-fill-box" style='font-size: 14px;line-height: 1.5;color: white'><!-- 文本或块列表,程序会自动差分 -->
<div data-op-type='block' style='background-color: red;height: 300px;'>red</div>
<div data-op-type='block' style='background-color: green;height: 300px;'>green</div>
<div data-op-type='block' style='background-color: blue;height: 300px;'>blue</div>
<span data-op-type='text' style='color: red'>ABCDEFGHIJKLMNOPQRSTUVWXYZ...</span>
</div>
</div>
</div>
<style>
@font-face {
font-family: YH;
src: url(./fonts/msyh.ttf);
font-weight: 400;
font-style: normal
}
body {
font-family: "Microsoft YaHei", YH, sans-serif;
font-weight: normal;
}
</style>
/**
* BOOK渲染完成前 book.before-complete
**/
$(document).on('book.before-complete',function (e,info) {
/**
* info:
* {
* "PAGE_BEGIN_INDEX": 0, // 开始页码标记的页面序号
* "PAGE_END_INDEX": 2, // 结束页码标记的页面序号
* "TOTAL_PAGE": 3 // 总页数
* }
**/
});
class: nop-no-print 被标记的节点在打印时不显示 class: nop-force-background 被标记的节点强制打印背景,在forcePrintBackground选项为false时可用
同一段落文本,被分到下一页的文本部分节点,会被class: nop-link-last 进行标记。可以根据此class,进行缩进特殊处理
text-box 、block-box、mix-box 内容被分割部分也同样会被class: nop-link-last 标记
<meta name="author" content="nop">
<meta name="description" content="bookjs-eazy so eazy!!">
<meta name="keywords" content="PDF、bookjs">
# 自己docker打印服务的命令
# ./docker-start.sh [WEB端口,默认3000]
./docker-start.sh
# 运行打印服务
# 会以dist为根目录,创建一个web站点。可通过http://127.0.0.1:3000/eazy-1.html访问示例,并可使用服务端打印
#
# 你可以在dist根目录下用bookjs-eazy创建book.html(参考示例:eazy-1.html)。
# 即可访问 http://127.0.0.1:3000/book.html 进行预览/打印下载
# 生成的pdf会存在dist/pdf/ 目录下。
详细内容见,wuxue107/screenshot-api-server项目
# 需预先先安装nodejs 环境,并安装yarn
git clone https://gitee.com/wuxue107/screenshot-api-server.git
cd screenshot-api-server
# 安装依赖
yarn
# 启动服务,默认服务端口号3000
# 指定端口号启动
# 启动时可指定的环境变量
# MAX_BROWSER=1 最大的puppeteer实例数,忽略选项则默认值: [可用内存]/200M
# PDF_KEEP_DAY=0 自动删除num天之前产生的文件目录,默认0: 不删除文件
# PORT=3001 监听端口,默认:3000
yarn start
指定配置bookConfig.toolBar.serverPrint.serverUrl值为: '//your-screenshot-api-server[:PORT]/'
# 首次使用时,安装bin/html2pdf的依赖包
yarn install
# 安装过后,执行命令
# 示例:
bin/html2pdf print --output eazy-2-1.pdf "https://bookjs.zhouwuxue.com/eazy-2.html"
#
# 命令行说明:
# Usage: html2pdf print [options] <url>
#
# Options:
# -o --output [outputfile] 保存PDF文件的路径 (default: "output.pdf")
# -t --timeout [type] 超时时间 (default: 60000)
# -a --agent [agent] 指定转换引擎chrome-headless|puppeteer,默认:puppeteer (default: "puppeteer")
# -d --printDelay [delay] 打印前等待延迟(毫秒) (default: 1000)
# -c --checkJs [jsExpression] 检查是否渲染完成的js表达式 (default: "window.status === 'PDFComplete'")
# "window.document.readyState === 'complete'" 这个表达式可以用作非bookjs-eazy构建的网页
# -h, --help display help for command
#
#
bin/pdf-a4-landscape "https://bookjs.zhouwuxue.com/eazy-2.html" eazy-2-2.pdf
#
# 在bin目录下,有数个同类脚本文件。
#
# bin/pdf-[纸张]-[纸张方向] [预览的链接] [输出文件]
#
# **注意**:如果使用wkhtmltopdf方式的自定义尺寸,不用担心,浏览器渲染完毕后,在Console上会输出wkhtmltopdf的PDF配套生成命令
服务端打印失效:
内容超出页面:
页面出现多余空白:
每页都多出一个空白页
打印出来是空白页
字体无法显示:
生成的PDF与预览有一些差异
iframe 嵌入网页失效:不能点击无法下载打印:
页面事件绑定失效:
找不到wkhtmltopdf
使用data-op-type="table" 表格合并单元格分页显示不正确。建议:
下载PDF超时
页面卡死,CPU超高
部件无法显示
<span> <span>
的空文本节点。此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。