网页中canvas预览的pdf打印方案

日常生活中总是需要使用搜索引擎检索一些pdf文档,很多网站通常使用的是pdf文档预览,并且有禁止打印的限制,如果选择手动单张复制图片或者截长图难免费时又费力,本文主要是通过编写油猴脚本代码实现打印网页生成pdf。

文章中pdf打印思路用于前端学习之用。

  • 常见web端的pdf预览方式
1.包含源地址的网页

第一类pdf 预览的网站是在前端html中直接包含了pdf文件的源地址。

最常见的方式在通过页面内嵌入iframe、embed、object标签,标签里会直接包含pdf的源地址。

<iframe src="http://www.xxx.com/test.pdf" width=900 height=900>

针对这种网站,只要使用f12审查元素,直接利用pdf的源地址进行下载即可获取原pdf文件。

在这里插入图片描述

有些网站会使用类似于jquery.media.js的jquery插件实现pdf的预览,但是这种方式还是会将pdf的源地址写在前端网页内。

<script type="text/javascript">  
 $('#handout_wrap_inner').media({
		width: '100%',
		height: '100%',
		autoplay: true,
        src:'http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf',
            }); 
</script>

对于这种方式可以在F12审查元素通过搜索关键词.pdf关键词来判断

在这里插入图片描述

2.网页中不包含pdf源地址

第一种预览pdf的方式通常是针对开放的PDF文档,对于一些有着限制下载的文档预览网站不会直接将PDF的源地址放进前端代码里,一般会选择通过一张张的图片形式甚至一个个canvas的绘制来显示整个pdf文档,很多允许预览pdf的文库通常会使用这种方式展示文档。

<img src="http://www.xxx.com/resource/test.jpg">

或者使用style中的background-image中的url属性来放置图片地址。

<div class="xxx" id="xxx" style="background-image:url("https://www.xxx.com/resource/test.jpg")"></div>

在这里插入图片描述

或者使用canvas绘制一页页的pdf文档如下图所示:

在这里插入图片描述

对于这种形式预览pdf,你可以选择一页页手工截图整理成文档,但是最便捷的方式肯定是使用ctrl+p对网页进行打印,如果可以去除一些没有边栏和其他没有必要的部分打印成pdf将会是最完美的选择,但是现实很残酷,网页会温馨的提示你,该页面禁止打印,或者只能打印出空白的页面,那是因为网站对打印做出了限制。

  • 通过使用外置js脚本打印pdf
1.油猴脚本

油猴脚本(Tampermonkey)是一个非常流行的浏览器扩展,它可以运行由广大社区编写的扩展脚本,来实现各式各样的功能,比如常见的去广告、修改样式文件、甚至是下载视频。

实际上油猴脚本可以在网页加载后再次加载你个人的js脚本,js可以控制整个网页文档的dom元素。你可以控制这些元素的一切来达到你想要的功能。

油猴脚本可以在各大浏览器上进行安装,google浏览器和firebox浏览器可以直接在应用商店搜索下载,但是值得一提的是谷歌浏览器应用商店需要翻墙,火狐浏览器则不需要,谷歌浏览器也可以通过离线安装。不知道如何安装的同学可以百度搜索奥!!

在这里插入图片描述

2.解除禁止打印设置

禁止打印的表现形式可以多样化,可以截断你的打印快捷键ctrl+p,但是大多数的网页主要是通过使用@media属性来限制打印功能,全面禁止打印会比较麻烦,所以只要使用户打印出来的内容为空就可以达到限制打印的功能。

<style> 
@media print{ 
body{display:none} 
} 
</style>

这边以百度文库为例,通过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 = $('<input id="my_download" type="btn" value="下载" style="text-align: center;color:white;position:fixed;left:20px;bottom:20px;cursor: pointer;width: 112px;height: 40px;line-height: 41px;line-height: 40px;background-color: #4e6ef2;border-radius: 10px 10px 10px 10px;font-size: 17px;box-shadow: none;font-weight: 400;border: 0;outline: 0;letter-spacing: normal;">');
$('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>')
$(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 = $('<input id="my_download" type="btn" value="下载" style="text-align: center;color:white;position:fixed;left:20px;bottom:20px;cursor: pointer;width: 112px;height: 40px;line-height: 41px;line-height: 40px;background-color: #4e6ef2;border-radius: 10px 10px 10px 10px;font-size: 17px;box-shadow: none;font-weight: 400;border: 0;outline: 0;letter-spacing: normal;">');
    $('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>')
            $(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 = $('<canvas></canvas>')[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;
}

文章用于记录学习之用。

----东湖大白鹅

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐