Silverlight 版 PDF






4.82/5 (9投票s)
利用 Silverlight 的强大功能查看 PDF 文档和表单
引言
使用 Microsoft Silverlight,开发人员可以为用户提供内容丰富的 Web 应用程序,这些应用程序不仅限于文本和图像,还可以包含复杂的图形和更好的用户交互。在阅读有关 Silverlight 功能以及可用它构建的应用程序类型的 MSDN 文章时,我遇到了 Jeff Prosise 在 2008 年 5 月 MSDN 期刊上的文章。我注意到作者为了演示“翻页”Silverlight 应用程序,已将他的示例文档转换为低质量的 JPEG 图像。对我来说,更自然的 K8s 是使用高质量的 PDF 文档,这些文档将易于用户阅读。现在 PDF 已成为大多数公司文档存储和表单处理的标准格式,我认为 Silverlight 应用程序将受益于能够原生服务 PDF 文档和表单的功能。
在本文中,我将展示如何使用 Amyuni PDF 组件在 Silverlight 控件中动态查看 PDF 文档。我将采用翻页框架来查看 PDF、XPS 或任何类型的文档,而不是 JPEG 图像。文档位于服务器上,客户端在 Silverlight 控件中查看,无需将任何文件或组件下载到客户端。所呈现的框架可以轻松扩展,以便在 PDF 文件包含交互式表单字段时添加交互性。
运行示例需要 Amyuni PDF Creator 和 Converter 版本 4.0 组件,您可以在此处找到它们。也可以直接从同一位置运行示例。
要求(服务器端)
提供的示例基于 ASP,但可以使用任何支持 ActiveX 或 .NET 的编程环境。
要求(客户端)
运行 Silverlight 控件版本 1.0 或更高版本的任何操作系统的任何 Web 浏览器。无需在客户端 PC 上下载或运行其他组件。
实现
乍一看,在 Silverlight 控件中查看 PDF 文档似乎是一项 15 分钟就能完成的工作。将 PDF 文档转换为 XPS(XAML 的派生格式)就足够了,然后将 XPS 提供给 Silverlight 控件,添加一些额外的功能,就可以完成了。使用 Amyuni PDF Creator 可以轻松地将 PDF 或任何其他文档转换为 XPS;唯一需要的是一种将 XPS 输入 Silverlight 控件的方法。然而,我面临着许多挑战。
- Silverlight 只支持读取单个 XAML 文件。没有机制可以单独提供多个页面,也没有方法可以提供该页面使用的所有资源(如图像和字体)。Silverlight 没有像 PDF 和 XPS 那样打包文档的整洁方法,而是要求文档的所有零散部分在服务器上单独创建,并通过调用应用程序以编程方式下载。这排除了动态地将文档从服务器流式传输到客户端的能力,而这几乎是所有应用程序的基本要求。
- Silverlight 对象模型的 K8s 不一致性使得试错成为构建示例中最耗时的部分。不一致的例子
- 假设 `elem` 是 `Canvas` 类型的一个对象,则使用 `elem.Width` 访问画布对象的宽度。使用 `elem.Left` 访问左侧位置会返回错误,应替换为 `elem.GetValue(“Canvas.Left”)`。
- 对于 `Path` 对象等复杂对象,很难弄清楚如何访问特定属性。在以下对象中:`
要访问 `Opacity`,只需使用 `elem.Opacity`。要访问 `ImageSource`,正确的语法是 `elem.Fill.ImageSource`;`ImageBrush` 元素应完全跳过,这对我来说不合理。
- Silverlight 提供的图像对象有限,不允许内联图像数据。指定图像源的唯一方法是使用 URL。要从 PDF 中提取图像并将其设置为 Silverlight 图像对象的源,需要在服务器上将图像提取到临时文件中,并将图像的源设置为临时 URL:这在现实情况下是不可行的解决方案。
使用 Silverlight 构建文档查看器的基本要求将是
- 能够单独提供每一页,以处理大型文档,并避免在查看之前将整个文档下载到客户端 PC。
- 能够将页面资源(如图像和字体)打包在文档内,而无需从服务器存储和检索资源。
- 能够使用未安装在客户端 PC 上但嵌入在源文档中的字体。
- 能够将称为文档元数据的附加信息发送到客户端。
所有这些要求都包含在 XPS 规范中,Microsoft 直接支持 XPS 到 Silverlight 可能是有意义的。由于事实并非如此,我不得不构建一个修改版的 XPS 包,其中包含 Silverlight 支持的 XAML 指令,并编写所有必要的代码来将修改版的 XPS 提供给 Silverlight 组件。
有关完整的 Silverlight PDF 示例,包括测试网页、源代码以及运行示例所需的 Amyuni PDF Suite 产品,请访问此页面。
使用 Downloader 对象
Downloader 对象为我解决了许多要求。此 Downloader 允许将多个对象打包在 ZIP 文件中,并在客户端 PC 上单独提取每个对象。在 ZIP 包内,每个页面描述都可以存储为单独的 XAML 文件,而该页面使用的所有资源都存储在虚拟文件夹中。下载对象也是异步的,这意味着用户不必等待大型文档完全下载。我发现 Downloader 对象最有用的地方在于它能够引用包含脚本的动态页面,而不仅仅是静态文件。这意味着 ZIP 包可以在服务器上动态创建并流式传输回客户端,而无需诉诸临时文件,这正是我所需要的。使用 Downloader 的基本语法如下
function onPageLoad(control, context, root)
{
// Instantiate the downloader object and store a reference for later use
_downloader = _pdfView.createObject('downloader');
// Add an event listener to detect when data is available
_token2 = _downloader.addEventListener('completed', downloadCompleted);
// Request a PDF document from the Server
// The Amyuni PDF Creator will be used on the server to load the PDF
// and return it in a ZIP package similar in format to XPS
_downloader.open('GET', '/PageTurnPdf/pdf.asp?PDFFile=doc.pdf');
// Begin asynchronous download of the zip package
_downloader.send();
}
function downloadCompleted(sender, args)
{
// Retrieve the XAML description of the document from the downloaded package file
var Document = sender.getResponseText('Document/document_1.fdoc');
// Doanload the XAML description of a specific page
var Xaml = _downloader.getResponseText('Pages/page_' + page + '.fpage');
...
提取和渲染图像
将页面的 XAML 描述下载到客户端不足以显示图像。还需要一个额外的步骤来遍历所有图像对象,并将每个图像的源设置为 ZIP 包中的其中一个图像。每次加载新页面时,都会调用 `ProcessElements`。此函数会查找页面上的所有图像并设置它们的 `ImageSource` 属性。
function ProcessElements(elems)
{
// process all page elements to set:
// - the source of all image elements
// - the font of all text elements
//
for ( i = 0; i < elems.Count; i++ )
{
var elem = elems.getItem(i);
var src = elem.Fill.ImageSource;
if ( src != null && src != "" )
{
elem.Fill.setSource(_downloader, src.substring(1));
}
// loop recursively through all children of the current element
ProcessElements(elem.children);
...
}
}
此处未显示 `Try`/`Catch` 块,但它们是必需的,因为我们无法提前知道一个对象是否具有 `ImageSource` 或 `Children` 属性。
渲染文本
文本元素的准确定位和渲染比图形和图像更棘手。PDF 提供了多种定义文本编码和字体类型的方法。XPS 和 XAML 都仅限于 Unicode 文本,XAML 在渲染文本方面的灵活性远不如其对应项。为解决文本问题,Amyuni PDF 组件提供了优化页面文本内容并将其全部转换为 Unicode 的方法。`OptimizeDocument` 方法在服务器上用于在尝试在 Silverlight 控件中渲染文本之前优化文本内容。
' Optimize the document to line level in order to improve the XAML export
objPdf.OptimizeDocument 1
当源文档中使用的字体在客户端 PC 上不可用时,Silverlight 会用不同的字体进行替换,这通常不会产生令人满意 results。Silverlight 提供了一种指定用于显示特定元素的字体的机制,只要字体是 `OpenType` 或 `TrueType` 格式。在这种情况下,可以使用 `Downloader` 对象来打包文档使用的所有字体。字体以部分嵌入字体的形式嵌入到 ZIP 包中,这些字体不能被最终用户使用,以避免字体许可问题。一些字体制造商仍可能限制许可字体的使用,在这种情况下,建议文档不要使用任何许可字体。
与上面描述的图像处理并行,示例查看器会遍历所有文本对象,并将每个对象的字体源设置为 ZIP 包中的一种字体。
// set the font source for all text elements to fonts retrieved from the ZIP package
if ( elem.toString() == "TextBlock" )
{
elem.setFontSource( _downloader );
}
文本元素的 `FontFamily` 属性将文本的字体映射到 ZIP 文件中打包的字体之一。当最终用户系统上找不到请求的字体时,Silverlight 组件会自动完成此操作。
服务器端脚本
将文档转换为 XAML 并构建 ZIP 包的服务器端代码非常直接,它使用了 Amyuni PDF Creator ActiveX。
<%
' Set the returned content type to be a ZIP package
Response.ContentType = “application/x-zip-compressed”
' Get the PDF filename requested by the user
strFilePath = Request.QueryString( “PDFFile” )
' Create the PDF Creator ActiveX object and open the document
' This will throw an exception if the control is not installed
' or the document not found
Set objPdf = Server.CreateObject(“PDFCreactiveX.PDFCreactiveX”)
objPdf.Open Server.MapPath( strFilePath ), ““
' Optimize the document to line level in order to improve the XAML export
objPdf.OptimizeDocument 1
' return the XAML attribute of the document object which is a ZIP package
Response.BinaryWrite objPdf.ObjectAttribute( “Document”, “XAML” )
Set objPdf = Nothing
%>
将示例扩展到查看 PDF 以外的文档
服务器端脚本可以轻松扩展以渲染其他类型的文档。要渲染 XPS 文档,应由 PDF Creator 控件加载文档,并将其保存回 XAML 以使其与 Silverlight 兼容。这可以通过在服务器端脚本中替换 PDF 文件来实现,即无需更改代码。
要渲染其他类型的文档,应首先使用 Amyuni PDF Converter 将它们转换为 PDF,然后作为 ZIP 包流式传输回客户端。PDF Converter 产品可从此页面下载。可以在此处找到在服务器上将文档转换为 PDF 的示例。
- 有关完整的源代码以及文章和示例的更新版本,请访问此页面。