打印总结

303 阅读4分钟

一、相关pdf库

1、查看

  • vue-pdf

  • iframe

  • pdfjs-dist

2、生成

  • pdfjs

  • electron生成pdf

  • puppeteer

  • bookjs-eazy

3、打印

  • vue-pdf(pdf)

  • window.print(html)

  • iframe+window.pirnt(pdf)

  • vue-esay-print(html)

  • vue-print-nb(html)

二、相关pdf库的详细介绍

1、vue-pdf(www.npmjs.com/package/vue…

根据url查看和打印pdf

<template>
  <pdf src="./path/to/static/relativity.pdf"></pdf>
</template>

<script>
import pdf from 'vue-pdf'

export default {
  components: {
    pdf
  }
}

2、pdfjs-dist (pdfjs)(mozilla.github.io/pdf.js/exam…

pdfjs-dist 是 PDF.js 的一个编译后的版本,适用于那些需要在旧浏览器中使用的场景中查看pdf

  • 优点
    • 官方提供,底层控件,当iframe查看不了pdf的时候, pdfjs-dist能够支持查看
  • 缺点
    • 使用相对麻烦
    • 仅提供查看功能, 打印功能不提供
<template>
        <div class="interviewVideo_main" id="videoContainer">
        <!--此处根据pdf的页数动态生成相应数量的canvas画布-->
        <canvas
          v-for="pageIndex in pdfPages"
          :id="`pdf-canvas-` + pageIndex"
          :key="pageIndex"
          style="display: block"
        ></canvas>
       </div>
</template>
<script setup>
import { ref } from "vue";
import * as pdfjsLib from "pdfjs-dist/build/pdf";
let pdfDoc = reactive({}); // 保存加载的pdf文件流
let pdfPages = ref(0); // pdf文件的页数
let pdfUrl = ref(""); //pdf文件的链接
let pdfScale = ref(1.0); // 缩放比例
//调用loadFile方法
onMounted(() => {
  loadFile(pdfUrl.value);
});
//获取pdf文档流与pdf文件的页数
const loadFile = async (url) => {
  pdfjsLib.GlobalWorkerOptions.workerSrc =
    "../../node_modules/pdfjs-dist/build/pdf.worker.min.js";
  const loadingTask = pdfjsLib.getDocument(url);
  loadingTask.promise.then((pdf) => {
    console.log(pdf);
    pdfDoc = pdf;
    pdfPages.value = pdf.numPages;
    nextTick(() => {
      renderPage(1);
    });
  });
};
//渲染pdf文件
const renderPage = (num) => {
  pdfDoc.getPage(num).then((page) => {
    const canvasId = "pdf-canvas-" + num;
    const canvas = document.getElementById(canvasId);
    const ctx = canvas.getContext("2d");
    const viewport = page.getViewport({ scale: pdfScale.value });
    canvas.width = viewport.width ;
    canvas.height = viewport.height;
    const renderContext = {
      canvasContext: ctx,
      viewport: viewport,
    };
    page.render(renderContext);
    if (num < pdfPages.value) {
      renderPage(num + 1);
    }
  });
};
</script>
<style>
#videoContainer {
  height: 842px;
}
</style>

3、jspdf(mrrio.github.io/)

生成pdf, 可以保存pdf, 或者导出buffer流

  async triggerPrint() {
  			const canvas1 = await this.transformPDF('canvas1Ref')
  			const canvas2 = await this.transformPDF('canvas2Ref')
  			const pageCanvas = this.dealCanvas([canvas1, canvas2]) // 每个canvas能在新一页pdf中展示
  			this.downloadPDF(pageCanvas) // 导出
  			this.printPDF(pageCanvas) // 打印
  		},
  		transformPDF(dom) {
  			const element = this.$refs[dom]
  			return html2canvas(element, {
  				useCORS: true, // 允许跨域
  				allowTaint: false,
  				logging: false,
  				letterRendering: true,
  				ddpi: window.devicePixelRatio * 4, // 将分辨率提高到特定的DPI 提高四倍
  				scale: 2, // 按比例增加分辨率
  				background: '#fff', // pdf背景色为白色,默认是黑色的
  				ignoreElements: (e) => {
  					if (e.contains(element) || element.contains(e) || e.nodeName === 'HEAD' || e.nodeName === 'STYLE' || e.nodeName === 'LINK') {
  						return false
  					}
  					return true
  				} // 优化html2canvas的性能
  			})
  		},
  		dealCanvas(canvasArr) {
  			const pageCanvas = []
  			const { a4h, a4w } = this.canvasParam
  			canvasArr.forEach((canvas) => {
  				const ctx = canvas.getContext('2d')
  				const imgHeight = Math.floor((a4h * canvas.width) / a4w) // 按A4显示比例换算一页图像的像素高度
  				let renderedHeight = 0
  				const page = document.createElement('canvas')
  				page.width = canvas.width
  				while (renderedHeight < canvas.height) {
  					console.log(renderedHeight, canvas.height)
  					page.height = Math.min(imgHeight, canvas.height - renderedHeight) // 可能内容不足一页
  					// 用getImageData剪裁指定区域,并画到前面创建的canvas对象中
  					page.getContext('2d').putImageData(ctx.getImageData(0, renderedHeight, canvas.width, page.height), 0, 0)
  					pageCanvas.push({
  						dataUrl: page.toDataURL('image/jpeg', 0.2),
  						renderHeight: Math.min(a4h, (a4w * page.height) / page.width)
  					})
  					renderedHeight += imgHeight
  				}
  			})
  			return pageCanvas
  		},
  		downloadPDF(pageCanvas) {
  			const { a4h, a4w } = this.canvasParam
  			// eslint-disable-next-line
  				const PDF = new jsPDF({
  				orientation: 'p', // 参数: l:横向  p:纵向
  				unit: 'mm', // 参数:测量单位("pt","mm", "cm", "m", "in" or "px")
  				format: 'a4' // A4纸
  			})
  			pageCanvas.forEach((item, idx) => {
  				PDF.addImage(item.dataUrl, 'JPEG', 10, 10, a4w, item.renderHeight)
  				const pageNumberText = `第${idx + 1}页,共${pageCanvas.length}页`
  				// 设置中文字体
  				PDF.setFont('simhei')
  				// 设置文字大小
  				PDF.setFontSize(8)
  				// 设置文字颜色
  				PDF.setTextColor('#999999')
  				// 在页脚添加页码
  				PDF.text(pageNumberText, a4w / 2, a4h + 18)
  				if (idx !== pageCanvas.length - 1) PDF.addPage()
  			})
  			 
  			PDF.save('pdf.pdf')
  		},
  		printPDF(pageCanvas) {
  			const { a4h, a4w } = this.canvasParam
  			// eslint-disable-next-line
  				const PDF = new jsPDF({
  				orientation: 'p', // 参数: l:横向  p:纵向
  				unit: 'mm', // 参数:测量单位("pt","mm", "cm", "m", "in" or "px")
  				format: 'a4' // A4纸
  			})
  			pageCanvas.forEach((item, idx) => {
  				PDF.addImage(item.dataUrl, 'JPEG', 10, 10, a4w, item.renderHeight)
  				const pageNumberText = `第${idx + 1}页,共${pageCanvas.length}页`
  				// 设置中文字体
  				PDF.setFont('simhei')
  				// 设置文字大小
  				PDF.setFontSize(8)
  				// 设置文字颜色
  				PDF.setTextColor('#999999')
  				// 在页脚添加页码
  				PDF.text(pageNumberText, a4w / 2, a4h + 18)
  				if (idx !== pageCanvas.length - 1) PDF.addPage()
  			})
  			const pdfBlob = PDF.output('blob')
  			const reader = new FileReader()
  			reader.readAsDataURL(pdfBlob)
  			reader.onload = (e) => {
  				this.pdfURL = e.target.result // 通过vue-pdf加载url,就可以调用vue-pdf的print功能进行打印
  			}
  		}

4、electron生成pdf(www.electronjs.org/zh/docs/lat…

提供html转化成为pdf

  • 优点

    • 原生electron自带功能, 不需要下载插件
    • 可以使用原来的样式效果, 不需要重新编写
  • 缺点

    • 部分样式失效,图片边框和字体样式能生效, 背景色效果显示不出来
    • 打印的是整个页面, 如果该页面存在弹窗这些, 也会一齐打印出来

electron部分

	htmlToPdf() {
		const win = new BrowserWindow({
			autoHideMenuBar: true,
			show: false, // 不调试时隐藏窗口
			 
			webPreferences: {
				nodeIntegration: true,
				webSecurity: false,
				devTools: false
			}
		})
		let ipcMainEvent
		ipcMain.on('htmlToPdf', (event, arg) => {
			const { url } = arg
			if (!url) return
			ipcMainEvent = event
			win.loadURL(global.terminalInfo.winURL + url)
		})
		win.webContents.on('did-finish-load', () => {
			setTimeout(async () => {
				const data = await win.webContents.printToPDF({})
				ipcMainEvent.reply('replyHtmlToPdf', Buffer.from(data).toString('base64'))
			})
		})
  }

网页部分

	base64ToBlob(base64, mimeType) {
			// 移除 Base64 字符串中的填充字符(如果存在)
			const base64WithoutPadding = base64.replace(/\s+/g, '')

			// 解码 Base64 字符串
			const byteCharacters = atob(base64WithoutPadding)

			// 创建一个 8-bit unsigned 整数数组
			const byteNumbers = new Array(byteCharacters.length)
			for (let i = 0; i < byteCharacters.length; i++) {
				byteNumbers[i] = byteCharacters.charCodeAt(i)
			}

			// 将整数数组转换为 Uint8Array
			const byteArray = new Uint8Array(byteNumbers)

			// 使用 Uint8Array 创建 Blob 对象
			return new Blob([byteArray], { type: mimeType })
		},
        // 导出pdf
		triggerDownload() {
			ipcRenderer.send('htmlToPdf', { url: `#/analysis_preview/${this.id}` })
			ipcRenderer.on('replyHtmlToPdf', (_event, data) => {
				const blob = this.base64ToBlob(data, 'application/pdf')
				const blobUrl = URL.createObjectURL(blob)

				// 创建一个隐藏的 <a> 元素
				const link = document.createElement('a')
				link.style.display = 'none'
				link.href = blobUrl
				link.download = 'download' // 设置下载文件的默认名称

				// 将 <a> 元素添加到文档中
				document.body.appendChild(link)

				// 模拟点击 <a> 元素来触发下载
				link.click()

				// 下载完成后,撤销 URL 对象以释放内存
				document.body.removeChild(link)
				URL.revokeObjectURL(blobUrl)
			})
		},
		// 打印pdf, 需要借助vue-pdf
		triggerPrint() {
			ipcRenderer.send('htmlToPdf', { url: `#/analysis_preview/${this.id}` })
			ipcRenderer.on('replyHtmlToPdf', (_event, data) => {
		       // 该pdf可以提供给vue-pdf预览打印
			   this.pdfUlr = 'data:application/pdf;filename=generated.pdf;base64,'+data; 
			})
		}

5、iframe打印

提供页面局部区域打印

  • 优点
    • 原生控件, 不需要下载插件
  • 缺点
    • 之前用了编译后的样式,打印的时候这部分样式不生效,需要重写样式
    • 部分样式会失效, 如float等
triggerPrint() {
			try {
				// 获取需要打印区域的id
				const oldStr = document.getElementById('analysisPDF')
				const newStr = document.createElement('IFRAME')
				document.body.appendChild(newStr)
				const doc = newStr.contentWindow.document
				// 这里可以自定义样式
				doc.write(
					`<html>
            <head>
              <meta charset="utf-8">
              <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
            </head>
             <!--zoom缩放页面适配打印纸张大小的效果-->
            <body style="zoom: 0.7;">
              <div id="printchart">
                ${oldStr.innerHTML}
              </div>
            </body>
          </html>`
				)
				doc.close()
				// 为iframe添加Id方便后续操作
				newStr.setAttribute('id', 'printIframe')
				newStr.setAttribute('frameborder', '0')
				newStr.setAttribute('style', 'position:absolute;width:0;height:0;left:-500px;top:-500px;')
				newStr.contentWindow.focus()
				newStr.contentWindow.onload = function () {
					newStr.contentWindow.print()
					// 去除iframe元素
					document.body.removeChild(newStr)
				}
			} catch (error) {
				console.log(error)
			}
		},

6、puppeteer

Puppeteer抓取HTML生成 PDF 的最佳实践

  • 优点
    • 完整的 Chrome 渲染引擎,支持所有现代 HTML、CSS 和 JavaScript。
    • 强大的功能,支持页面操作、截图、生成 PDF 等。
  • 缺点,
    • 需要较大的服务器端资源。
    • 配置和使用相对复杂。

7、bookjs-eazy(gitcode.com/gh_mirrors/…

记录上百页html生成pdf的历程和坑(bookjs-easy解决:生成、拼接pdf、接收服务端pdf、自定义pdf字体

  • 优点
    • 完美解决分页问题
  • 缺点
    • 不支持现代js框架 VUE、React等单页面多路由场景,需要在html中用script标签直接引入,不能在import引入再经过编译
    • 不支持动态刷新,重新渲染需要刷新整个页面
    • PDF页面需要单独的html文件入口
    • 如果想嵌入应用网页内部,可使用iframe方式

8、vue-easy-print(www.npmjs.com/package/vue…

  • 优点
    • 提供打印功能
  • 缺点
    • 需要拷贝一个打印模块到插槽中
<template>
  <div id="app">
      <button @click="printDemo">测试打印</button>
      <vue-easy-print ref="easyPrint" >
           打印内容
      </vue-easy-print>
  </div>
</template>

<script>
import vueEasyPrint from "vue-easy-print";
export default {
name: "App",
methods:{
  printDemo(){
    this.$refs.easyPrint.print()
  }
},
components: {
  vueEasyPrint,
}
};
</script> 

9、vue-print-nb(www.npmjs.com/package/vue…

  • 优点
    • 无需像vue-easy-print一样拷贝一个html渠道vue-easy-print中
    • 支持vue2和vue3
    • 支持id和url来选择打印内容,
  • 缺点
    • url存在跨域问题
    • 部分样式失效, 如果float, background等
   <div id="printMe" style="background:red;">
        <p>葫芦娃,葫芦娃</p>
        <p>一根藤上七朵花 </p>
        <p>小小树藤是我家 啦啦啦啦 </p>
        <p>叮当当咚咚当当 浇不大</p>
        <p> 叮当当咚咚当当 是我家</p>
        <p> 啦啦啦啦</p>
        <p>...</p>
    </div>

    <button v-print="'#printMe'">Print local range</button>

二、相关doc预览方案

最全的docx,pptx,xlsx(excel),pdf文件预览方案
vue-office-docx、docx-preview、mammoth.js效果对比
前端使用docx-preview展示docx + 后端doc转docx

1、@vue-office(www.npmjs.com/package/doc…)

支持docx、pdf、excel

支持url查看

样式还原度一般,间距太大,分页也有问题

2、docx-preview(www.npmjs.com/package/mam…

仅支持docx

仅支持 Blob | ArrayBuffer | Uint8Array查看

样式还原度一般,无分页

startRender() {
			this.getFileBuffer(this.url).then((file) => {
				this.renderFile(file)
			})
		},
getFileBuffer(url) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest()
            xhr.open('GET', url, true)
            xhr.responseType = 'arraybuffer'
            xhr.onload = () => {
                if (xhr.status === 200) {
                    resolve(xhr.response)
                } else {
                    reject()
                }
            }
            xhr.onloadend = () => {
                reject()
            }
            xhr.send()
        })
    },
 renderFile(file) {
        renderAsync(file, this.$refs.viewRef, null, {})
    }

3、mammoth(www.npmjs.com/package/mam…

样式有问题

浏览器执行的时候, 同样需要调用上面的getFileBuffer方法来进行读取二进制流来展示

4、kkFileView(kkfileview.keking.cn/zh-cn/index…)

需要后台支持, 文件种类支持最全,但是仅支持查看, 不适合过多的拓展

5、onlyOffice(www.onlyoffice.com/zh/)

需要后台支持, 支持文件种类没有kkFileView多, 对office三件套有很好的支持、支持对文件进行查看和编辑

三、打印样式

关于CSS 打印你应该知道的样式配置
文档打印强制分页以及指定元素避免分页

  • page-break-before : 元素之前插入分页符

  • page-break-after : 元素之后插入分页符

  • page-break-inside: 避免将元素分割到不同的页面

  • @page 规则和 size 属性来定义打印页面的大小

    @media print {
      @page {
        size: A4; /* 可以使用常见的纸张尺寸如 A4、Letter、Legal 等 */
      }
    }
    
  • 隐藏链接的 URL

    @media print {
      a::after {
        content: none;
      }
    }
    
  • 调整页眉和页脚

    @media print {
      @page {
        @top-left {
          content: "页眉内容";
        }
        
        @bottom-center {
          content: "页脚内容";
        }
      }
    }