OpenLayers如何打印指定区域为PDF并实现多页打印?
OpenLayers 指定区域多页PDF打印问题
在OpenLayers中,需要实现指定区域(非整个Canvas)的多页/多区域PDF打印,但目前遇到两个问题:
- 使用
map.once("rendercomplete")方法渲染耗时较长; - 循环调用该方法实现多页打印时,仅能输出一页。
以下是当前实现的打印方法:
async generatePDF() { try { let items = this.workspaceFacade.source.getFeatures(); let finalOutput = []; let allExtent = []; let printArea = []; if (items.length > 0) { items.forEach((item) => { if (item.getGeometry().getType() == "Polygon") { item.setStyle(frameStylePrint()); let extent = item.getGeometry().getExtent(); allExtent.push(extent); }}); } allExtent.map(async (item, index) => { this.workspaceFacade.map .getView() .fit(item, [1000, 900], { padding: [-10, 10, 0, -10] }); let legendurl = this.LEGEND_URL; let htmlcont = this.htmlContent; let svgString, htmlButton, htmlLegend, htmlString; this.workspaceFacade.map.once("rendercomplete", function () { const mapCanvas = document.createElement("canvas"); mapCanvas.width = 1000; mapCanvas.height = 900; const mapContext = mapCanvas.getContext("2d"); Array.prototype.forEach.call( document.querySelectorAll(".ol-layer canvas, canvas.ol-layer"), function (canvas) { if (canvas.width > 0) { const opacity = canvas.parentNode.style.opacity || canvas.style.opacity; mapContext.globalAlpha = opacity === "" ? 1 : Number(opacity); let matrix; const transform = canvas.style.transform; if (transform) { // Get the transform parameters from the style's transform matrix matrix = transform .match(/^matrix\(([^\(]*)\)$/)[1] .split(",") .map(Number); } else { matrix = [ parseFloat(canvas.style.width) / canvas.width, 0, 0, parseFloat(canvas.style.height) / canvas.height, 0, 0, ]; } // Apply the transform to the export map context CanvasRenderingContext2D.prototype.setTransform.apply( mapContext, matrix ); const backgroundColor = canvas.parentNode.style.backgroundColor; if (backgroundColor) { mapContext.fillRect(0, 0, canvas.width, canvas.height); } mapContext.drawImage(canvas, 0, 0); } } ); mapContext.globalAlpha = 1; mapContext.setTransform(1, 0, 0, 1, 0, 0); mapCanvas.toBlob((blob) => { const url = URL.createObjectURL(blob); console.log("fourth", url, item); htmlcont = `<div><img width="500" height="700" src="${url}" style="float:left; border: 1px solid black"></div>`; console.log(htmlcont); svgString = `<div>${htmlcont} </div>`; htmlButton = `<div><input onclick="window.print()" id="button" type="button" value="Generate pdf" style="background-color: #5E7B8D;` + `color: #FFFFFF; border-radius: 10px; position: relative;"></div>`; htmlLegend = `<div><img width="200" height="700" src="${legendurl}" style="float:left; border: 1px solid black"></div>`; console.log(svgString); htmlString = `<div id="planimetric"> ${htmlButton} ${svgString} ${htmlLegend} </div>`; printArea[index] = htmlString; const planimetricView = window.open('', '_blank'); planimetricView.document.write(htmlString); planimetricView.document.title = 'Preliminary Project - Secondary Network Elements'; planimetricView.document.close(); console.log(printArea); }); }); // return printArea; } // ) ); // console.log(printArea1); console.log(this.LEGEND_URL); } catch (error) { throw error; } }
问题原因分析
- 并发渲染冲突:使用
map()循环结合async时,所有视图拟合操作会同时触发,rendercomplete回调会被后续操作覆盖,最终仅执行最后一次渲染。 - 多窗口干扰:每次渲染完成后直接打开新窗口,不仅可能被浏览器拦截,也无法将多页内容整合为单个PDF文件。
- 异步逻辑未闭环:
toBlob是异步操作,但未做等待处理,导致内容收集不完整。
解决方案与优化代码
通过串行处理打印任务、统一收集内容后生成单窗口打印页面,解决多页失效问题,同时优化渲染效率:
async generatePDF() { try { const items = this.workspaceFacade.source.getFeatures(); const allExtent = []; const printContents = []; const legendUrl = this.LEGEND_URL; // 收集所有需要打印的多边形范围 if (items.length > 0) { items.forEach(item => { if (item.getGeometry().getType() === "Polygon") { item.setStyle(frameStylePrint()); allExtent.push(item.getGeometry().getExtent()); } }); } // 串行处理每个打印区域,避免渲染冲突 for (const [index, extent] of allExtent.entries()) { // 等待视图拟合并渲染完成 await new Promise(resolve => { this.workspaceFacade.map.getView().fit(extent, [1000, 900], { padding: [-10, 10, 0, -10] }); this.workspaceFacade.map.once("rendercomplete", resolve); }); // 导出当前视图为Canvas图片 const mapCanvas = document.createElement("canvas"); mapCanvas.width = 1000; mapCanvas.height = 900; const mapContext = mapCanvas.getContext("2d"); Array.prototype.forEach.call( document.querySelectorAll(".ol-layer canvas, canvas.ol-layer"), canvas => { if (canvas.width <= 0) return; const opacity = canvas.parentNode.style.opacity || canvas.style.opacity; mapContext.globalAlpha = opacity === "" ? 1 : Number(opacity); let matrix; const transform = canvas.style.transform; if (transform) { matrix = transform.match(/^matrix\(([^\(]*)\)$/)[1].split(",").map(Number); } else { matrix = [ parseFloat(canvas.style.width) / canvas.width, 0, 0, parseFloat(canvas.style.height) / canvas.height, 0, 0, ]; } mapContext.setTransform(...matrix); const backgroundColor = canvas.parentNode.style.backgroundColor; if (backgroundColor) { mapContext.fillStyle = backgroundColor; mapContext.fillRect(0, 0, canvas.width, canvas.height); } mapContext.drawImage(canvas, 0, 0); } ); mapContext.globalAlpha = 1; mapContext.setTransform(1, 0, 0, 1, 0, 0); // 等待Blob生成完成 const blob = await new Promise(resolve => mapCanvas.toBlob(resolve)); const url = URL.createObjectURL(blob); // 组装单页打印内容,添加分页标记 const pageContent = ` <div style="page-break-after: always; margin: 20px 0;"> <img width="500" height="700" src="${url}" style="border: 1px solid black;"> <img width="200" height="700" src="${legendUrl}" style="border: 1px solid black; margin-left: 20px;"> </div> `; printContents.push(pageContent); } // 生成包含所有页面的打印HTML const printHtml = ` <html> <head> <title>Preliminary Project - Secondary Network Elements</title> <style> @media print { .print-btn { display: none; } } </style> </head> <body> <button class="print-btn" onclick="window.print()" style="background-color: #5E7B8D; color: #FFFFFF; border-radius: 10px; padding: 8px 16px;">生成PDF</button> ${printContents.join('')} </body> </html> `; // 打开统一打印窗口 const printWindow = window.open('', '_blank'); printWindow.document.write(printHtml); printWindow.document.close(); } catch (error) { console.error('打印PDF失败:', error); throw error; } }
优化说明
- 串行处理:使用
for...of循环结合Promise,确保每个区域的渲染、导出完成后再处理下一个,避免回调覆盖。 - 统一生成PDF:收集所有页面内容后一次性生成HTML,确保所有页面在同一个窗口中,支持浏览器原生的多页PDF打印。
- 渲染优化:简化Canvas变换逻辑,修复背景色填充的样式缺失问题,减少无效渲染操作。
- 分页控制:添加
page-break-after样式,确保每个打印区域自动分页。
内容的提问来源于stack exchange,提问作者Surender Rengaraj
相关产品推荐
相关产品推荐

