网页中canvas预览的pdf打印方案
日常生活中总是需要使用搜索引擎检索一些pdf文档,很多网站通常使用的是pdf文档预览,并且有禁止打印的限制,如果选择手动单张复制图片或者截长图难免费时又费力,本文主要是通过编写油猴脚本代码实现打印网页生成pdf。
文章中pdf打印思路用于前端学习之用。
常见web端的pdf预览方式
1.包含源地址的网页
第一类pdf 预览的网站是在前端html中直接包含了pdf文件的源地址。
最常见的方式在通过页面内嵌入iframe、embed、object标签,标签里会直接包含pdf的源地址。
针对这种网站,只要使用f12
审查元素,直接利用pdf的源地址进行下载即可获取原pdf文件。
有些网站会使用类似于jquery.media.js
的jquery插件实现pdf的预览,但是这种方式还是会将pdf的源地址写在前端网页内。
对于这种方式可以在F12
审查元素通过搜索关键词.pdf
关键词来判断
2.网页中不包含pdf源地址
第一种预览pdf的方式通常是针对开放的PDF文档,对于一些有着限制下载的文档预览网站不会直接将PDF的源地址放进前端代码里,一般会选择通过一张张的图片形式甚至一个个canvas的绘制来显示整个pdf文档,很多允许预览pdf的文库通常会使用这种方式展示文档。
或者使用style中的background-image中的url属性来放置图片地址。
或者使用canvas绘制一页页的pdf文档如下图所示:
对于这种形式预览pdf,你可以选择一页页手工截图整理成文档,但是最便捷的方式肯定是使用ctrl+p
对网页进行打印,如果可以去除一些没有边栏和其他没有必要的部分打印成pdf将会是最完美的选择,但是现实很残酷,网页会温馨的提示你,该页面禁止打印,或者只能打印出空白的页面,那是因为网站对打印做出了限制。
通过使用外置js脚本打印pdf
1.油猴脚本
油猴脚本(Tampermonkey)是一个非常流行的浏览器扩展,它可以运行由广大社区编写的扩展脚本,来实现各式各样的功能,比如常见的去广告、修改样式文件、甚至是下载视频。
实际上油猴脚本可以在网页加载后再次加载你个人的js脚本,js可以控制整个网页文档的dom元素。你可以控制这些元素的一切来达到你想要的功能。
油猴脚本可以在各大浏览器上进行安装,google浏览器和firebox浏览器可以直接在应用商店搜索下载,但是值得一提的是谷歌浏览器应用商店需要,火狐浏览器则不需要,谷歌浏览器也可以通过离线安装。不知道如何安装的同学可以百度搜索奥!!
2.解除禁止打印设置
禁止打印的表现形式可以多样化,可以截断你的打印快捷键ctrl+p
,但是大多数的网页主要是通过使用@media属性来限制打印功能,全面禁止打印会比较麻烦,所以只要使用户打印出来的内容为空就可以达到限制打印的功能。
这边以百度文库为例,通过F12
调出调试工具,然后再控制台输入代码window.print()
后回车也可以对网页进行打印,但是会发现整个文档的内容为空。
这是因为网页中使用了@media限制了用户的打印内容输出,此时可以加一部分代码内容,进行再次打印
var style = document.createElement('style');
style.innerHTML = "@media print {body{display:block} @page {margin:0; size:500px 500px;}";
window.document.head.appendChild(style);
window.print();
这里代码内容其实就是嵌入一个@media打印样式,可以将之前禁止打印的样式给覆盖掉,此时可以进行打印,但是会有很多干扰内容,并且最重要的pdf内容不见了,那是因为网页之前的@media内容比较多,我们只是简单地对打印时body的样式进行了修改,但是没有对其他元素进行操作。
3.编写脚本
有了刚刚的操作步骤后,我们可以简单的模拟一个写脚本打印pdf的思路,首先这个打印时对整个网页进行打印,而我们需要的内容只是每一页的pdf信息,所以我们大胆拟定思路,先获取网页中所有pdf的图片,只需要记录图片的真实地址即可。在获得所有pdf页之后,我们直接清空body标签下的所有内容,然后重新将我们的pdf一页页放上去,然后打印这个页面,就可以获取整个pdf内容。
在文库页面打开油猴插件,点击新增脚本,在里面编辑我们的个人脚本代码。这边可以导入jquery库方便使用jquery进行操作dom元素。
// @require http://code.jquery.com/jquery-1.11.0.min.js
我们首先使用jquery按级获取到元素的background-image属性,然后使用字符替换提取url("image_url")中的图片地址,我们简单的使用数组来存储所有图片的地址,为了规范以后打印的纸张,我们在获取图片地址的同时,记录下图片的长宽数据。
let imgs = []
let img_width = 0;
let img_height = 0;
$('#reader-container div .reader-pic-layer .ie-fix').each(function(){
let cur_element = $(this).children('.reader-pic-item:last');
let img_url = $(cur_element).css('backgroundImage');
img_width = parseInt($(cur_element).css('width'));
img_height = parseInt($(cur_element).css('height'));
imgs.push(img_url.replace('url("', '').replace('")', ''));
})
console.log(img_width, img_height);
console.log(imgs);
输出查看数组中存放的图片地址,确认地址无误。
但是我们不能让脚本直接触发,我们先创建一个按钮,在点击按钮之后触发后续的操作。所以我们创建button元素,为他赋予点击事件。
let btn = $('');
$('body').append(btn);
$(btn).on('click', function(){
let imgs = []
let img_width = 0;
let img_height = 0;
$('#reader-container div .reader-pic-layer .ie-fix').each(function(){
let cur_element = $(this).children('.reader-pic-item:last');
let img_url = $(cur_element).css('backgroundImage');
img_width = parseInt($(cur_element).css('width'));
img_height = parseInt($(cur_element).css('height'));
imgs.push(img_url.replace('url("', '').replace('")', ''));
})
console.log(img_width, img_height);
console.log(imgs);
})
这样我们在点击继续阅读之后,点击我们创建的下载按钮之后,输出所有的pdf图片页面,此时输出了所有的pdf页面。
现在我们只需要将页面清空,然后只单纯地放上pdf页面,然后进行触发打印浏览器页面即可。代码中我们每次都要创建一个img元素,将它的src属性设置为数组中存储的图片地址。同时要为body设置纵轴方向居中的flex布局,这样子每页pdf页面与正常看见的pdf一致。代码如下
$('body').empty();
$('body').css('display', 'flex');
$('body').css('flex-direction', 'column');
for(let i = 0; i < imgs.length; i++){
let img = $('')
$(img).attr('src', imgs[i]);
$('body').append(img);
}
此时页面只有我们的pdf页面,最后我们执行打印操作,此时我们需要设置@media中的page纸张大小,用上之前存储图片的长宽数据设置纸张大小,代码如下:
var style = document.createElement('style');
style.innerHTML = "@media print {body{display:block} @page {margin:0; size:" + img_width +"px " + img_height + "px;}";
window.document.head.appendChild(style);
window.print();
代码是为了写这篇文章临时起意写的,主要是为了体现实验思路,对很多不同的页面都不具有通用性,很多地方欠缺考虑性,最后实现效果如下:
温馨提示(打印需要整个页面加载完毕才能触发),最后在这里贴上整段代码,@match是要满足这个匹配的网址,这个油猴脚本才能生效。
// ==UserScript==
// @name print_pdf
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://wenku.baidu.com/view/6cc589a2872458fb770bf78a6529647d272834ac?fr=unit_43520_plan_3938_slotid_47
// @icon https://www.google.com/s2/favicons?domain=baidu.com
// @require http://code.jquery.com/jquery-1.11.0.min.js
// @grant none
// ==/UserScript==
(function() {
'use strict';
let btn = $('');
$('body').append(btn);
$(btn).on('click', function(){
let imgs = []
let img_width = 0;
let img_height = 0;
$('#reader-container div .reader-pic-layer .ie-fix').each(function(){
let cur_element = $(this).children('.reader-pic-item:last');
let img_url = $(cur_element).css('backgroundImage');
img_width = parseInt($(cur_element).css('width'));
img_height = parseInt($(cur_element).css('height'));
imgs.push(img_url.replace('url("', '').replace('")', ''));
})
console.log(img_width, img_height);
console.log(imgs);
$('body').empty();
$('body').css('display', 'flex');
$('body').css('flex-direction', 'column');
for(let i = 0; i < imgs.length; i++){
let img = $('')
$(img).attr('src', imgs[i]);
$('body').append(img);
}
// 进行打印
var style = document.createElement('style');
style.innerHTML = "@media print {body{display:block} @page {margin:0; size:" + img_width +"px " + img_height + "px;}";
window.document.head.appendChild(style);
window.print();
})
})();
总结
整个文章是为了提供禁止打印的一个思路,整个思路是从前端js这个方向出发,手段算不上高明,但是胜在简单容易,也可以考虑从抓包,构造请求出发,但是应该会遇上加密的数据。
有些网页可能并不会将所有的图片都放在网址中,也有可能是动态的增删元素来实现的预览,不同的网站做法都很大的不同,想要写出通用性强的好插件,需要你的代码有很强的适用性。
除了图片形式存储之外,还有使用vanvas元素绘制一页页的pdf,遇上这种形式就不能简单地存储源地址了,这边提供一个思路,可以复制整个canvas元素到数组中。
function save_canvas(oldCanvas){
let newCanvas = $('')[0];
let newCanvasContext = newCanvas.getContext('2d');
//console.log(oldCanvas);
newCanvas.width = oldCanvas.width;
newCanvas.height = oldCanvas.height;
newCanvasContext.drawImage(oldCanvas, 0, 0, oldCanvas.width, oldCanvas.height);
return newCanvas;
}
文章用于记录学习之用。
----东湖大白鹅