每次在Web项目中遇到PDF预览需求时,开发者通常面临两种选择:依赖浏览器原生支持或引入第三方库。我经历过直接用Adobe插件导致客户端兼容性灾难,也试过用iTextSharp服务端渲染性能堪忧。直到三年前某个政务项目紧急需求,让我真正体会到pdf.js的价值——当时需要在老旧IE浏览器上实现盖章公文的无插件预览,pdf.js只用200KB的JS文件就解决了所有问题。
技术选型对比值得展开说说。与服务端渲染方案相比,pdf.js的核心优势在于:
在ASP.NET MVC环境中集成时,有个容易被忽略的细节:IIS默认会阻止对.webmanifest文件的访问。去年帮客户排查问题时发现,这会导致pdf.js的字体加载失败,页面出现大量"□□□"符号。解决方法是在web.config中添加如下配置:
xml复制<system.webServer>
<staticContent>
<mimeMap fileExtension=".webmanifest" mimeType="application/manifest+json" />
</staticContent>
</system.webServer>
新手最常问的问题就是:"能不能给我个最简单可运行的demo?"这里分享我验证过数十次的极简集成方案。首先通过NuGet获取资源包:
powershell复制Install-Package Pdf.js -Version 2.14.305
然后在Views文件夹新建PDFViewer.cshtml:
html复制@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>PDF预览</title>
<style>
#pdf-container { width:100%; height:90vh; border:1px solid #ccc }
</style>
</head>
<body>
<div id="pdf-container">
<iframe id="pdf-viewer"
src="~/Content/pdf.js/web/viewer.html?file=@Url.Content("~/Content/sample.pdf")"
width="100%"
height="100%"
frameborder="0"></iframe>
</div>
</body>
</html>
这个方案有几点需要注意:
csharp复制var encodedName = HttpUtility.UrlEncode("中文文档.pdf");
csharp复制Response.Headers.Add("Access-Control-Allow-Origin", "*");
Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
当标准viewer.html无法满足UI需求时,就需要动用pdf.js的完整API了。去年为某医疗系统开发电子病历阅读器时,我总结出这套高性能渲染方案:
首先在_Layout.cshtml中初始化核心资源:
html复制<script src="~/Scripts/pdf.js/build/pdf.min.js"></script>
<script>
pdfjsLib.GlobalWorkerOptions.workerSrc = '@Url.Content("~/Scripts/pdf.js/build/pdf.worker.min.js")';
</script>
然后是带分页控制的完整实现:
javascript复制// 在TypeScript中会更优雅,但考虑读者面用ES5写法
var currentPage = 1;
var pdfDoc = null;
var pageRendering = false;
function renderPage(num) {
pageRendering = true;
pdfDoc.getPage(num).then(function(page) {
var viewport = page.getViewport({ scale: 1.5 });
var canvas = document.getElementById('pdf-canvas');
var ctx = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
var renderContext = {
canvasContext: ctx,
viewport: viewport
};
page.render(renderContext).promise.then(function(){
pageRendering = false;
document.getElementById('page-num').textContent = num;
});
});
}
function loadPdf(url) {
pdfjsLib.getDocument(url).promise.then(function(pdf) {
pdfDoc = pdf;
document.getElementById('page-count').textContent = pdf.numPages;
renderPage(currentPage);
});
}
// 控制器方法
public ActionResult GetPdf(string id)
{
var path = Server.MapPath("~/App_Data/" + id + ".pdf");
return File(path, "application/pdf");
}
性能优化要点:
基础打印用window.print()就能实现,但遇到带页眉页脚、批量打印等需求时,就需要更专业的方案。通过jQuery.print.js与pdf.js配合,可以做到:
javascript复制$('#print-btn').click(function() {
$.print("#pdf-container", {
globalStyles: false,
mediaPrint: false,
stylesheet: '/Content/print.css',
iframe: true,
append: '<div class="footer">机密文件</div>'
});
});
打印水印的进阶技巧:
css复制@media print {
body:after {
content: "内部资料";
opacity: 0.3;
font-size: 60px;
transform: rotate(-45deg);
position: fixed;
top: 50%;
left: 30%;
}
}
csharp复制using (var reader = new PdfReader(inputPath))
using (var stamper = new PdfStamper(reader, new FileStream(outputPath, FileMode.Create)))
{
var watermark = new PdfGState { FillOpacity = 0.3f };
for (var i = 1; i <= reader.NumberOfPages; i++)
{
var page = reader.GetPageSize(i);
var canvas = stamper.GetOverContent(i);
canvas.SetGState(watermark);
canvas.BeginText();
canvas.SetFontAndSize(BaseFont.CreateFont(), 60);
canvas.ShowTextAligned(Element.ALIGN_CENTER, "内部文件",
page.Width / 2, page.Height / 2, 45);
canvas.EndText();
}
}
在超过20个项目的实战中,我整理出这些血泪经验:
IIS部署三大坑:
移动端适配技巧:
javascript复制function checkMobile() {
return /Android|webOS|iPhone|iPad/i.test(navigator.userAgent);
}
if(checkMobile()) {
PDFViewerApplicationOptions.set('disableTextLayer', true);
PDFViewerApplicationOptions.set('maxCanvasPixels', 4096*4096);
}
缓存策略建议:
csharp复制// 在Global.asax中
protected void Application_BeginRequest()
{
if(Request.Url.AbsolutePath.Contains("pdf.js/web/viewer.html")) {
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetMaxAge(TimeSpan.FromDays(30));
}
}
最近在.NET Core项目中,我发现用WebPack集成更高效。安装pdfjs-dist包后,通过模块化加载可以减小30%体积:
javascript复制import * as pdfjsLib from 'pdfjs-dist/webpack';
pdfjsLib.GlobalWorkerOptions.workerSrc = require('pdfjs-dist/build/pdf.worker.min.js');
遇到跨域问题时,试试这个经过验证的CORS配置:
csharp复制services.AddCors(options => {
options.AddPolicy("PdfPolicy", builder => {
builder.WithOrigins("https://domain.com")
.WithMethods("GET")
.WithHeaders("Content-Type")
.SetPreflightMaxAge(TimeSpan.FromHours(1));
});
});