Silverlight 版 PDF:利用 Silverlight 的强大功能查看 PDF 文档和表单





0/5 (0投票)
本文将介绍如何使用 Amyuni PDF 组件在 Silverlight 控件内动态查看 PDF 文档。我们将讨论如何创建一个 Silverlight 3 控件来显示和交互 PDF 文档。同一个控件可用于查看 PDF、XPS 或任何文档类型。
利用 Silverlight 的强大功能查看 PDF 文档和表单(针对 Silverlight 3 和 4 更新)
使用 Microsoft Silverlight,开发人员可以为用户提供内容丰富的 Web 应用程序,这些应用程序现在可以包含复杂的图形和更好的用户交互。Amyuni PDF for Silverlight 能够显示高质量的 PDF 文档,用户易于阅读,相比之下,显示文档为 JPEG 图像效果更佳。
本文将向您展示
- 如何使用 Amyuni PDF 组件在 Silverlight 控件内动态查看 PDF 文档
- 如何创建一个 Silverlight 3 控件来显示和交互 PDF 文档。
- 如何使用同一个控件查看 PDF、XPS 或任何类型的文档
现在,PDF 已成为大多数公司文档存储和表单处理的标准格式,您的 Silverlight 应用程序可以受益于能够原生提供 PDF 文档和表单的功能。
要求(服务器端)
将文档转换为 XAML 并构建 ZIP 软件包的服务器端代码非常直接(内置于 HttpHandler 中),并使用 Amyuni PDF Creator .NET
要求(客户端)
运行 Silverlight 控件版本 3.0 或更高版本的任何操作系统上的任何 Web 浏览器。Sample PDFSilverlightControl 将由 Silverlight 框架自动下载到客户端。无需在客户端 PC 上下载或运行任何其他组件。
实现
乍一看,在 Silverlight 控件内查看 PDF 文档似乎是一项 15 分钟就能完成的任务。将 PDF 文档转换为 XPS(XAML 的派生格式),将 XPS 提供给 Silverlight 控件,添加一些锦上添花的功能,我们就完成了。使用 Amyuni PDF Creator 可以轻松地将 PDF 或任何文档转换为 XPS;唯一需要的是一种将 XPS 输入 Silverlight 控件的方法。然而,我们面临了许多挑战
- Silverlight 仅支持读取单个 XAML 文件。没有机制可以单独馈送多个页面,也没有办法馈送该页面使用的所有资源,例如图像和字体。Silverlight 没有像 PDF 和 XPS 那样对文档进行打包的简洁方法,而是要求文档的所有零散部分在服务器上单独创建,并通过调用应用程序以编程方式下载。这排除了动态地将文档从服务器流式传输到客户端的能力,而这是大多数应用程序的基本要求。
- Sliverlight 的 Javascript API 提供了一个 Downloader 对象,可用于异步下载 XAML 文件和其他组件(如字体或图像)从服务器。Downloader 仅通过 Javascript 可用,并且不能在通常用 C# 编写的 Silverlight 控件中使用。我们不得不结合使用 WebClient 对象向 Web 服务器发送异步请求以检索 XAML 文件,然后使用强大的 Application.GetResourceStream() 方法从 zip 文件中提取部分内容。这些都是 Silverlight 版本 3 和 4 支持的新功能。
另一个已开发出的领域是服务器端的 Amyuni PDF Creator 控件。PDF Creator 控件用于返回 XAML 文件,它会处理整个源 PDF 文件,然后返回文件中所有页面的 XAML。当 PDF 文件相当大时,此过程很耗时,并且客户端应用程序只能等待,直到服务器开始将响应写回客户端,而没有任何进度通知。Amyuni PDF Creator 控件的 4.5 版本可以返回原始文档的总页数的一个子集,而不是一次性在 XAML 文件中返回所有页面。这可以通过将“PageSequence”文档属性设置为请求页面的数组或将“PageSequenceStr”设置为请求页面的字符串表示来控制。
- Silverlight 提供的图像对象有限,它不允许内联图像数据。指定图像源的唯一方法是使用 URL。要从 PDF 中提取图像并将其设置为 Silverlight 图像对象的源,需要在服务器上将图像提取到临时文件中,并将图像的源设置为临时 URL - 这种解决方案在任何实际情况下都不可行。
使用 Silverlight 构建文档查看器的基本要求是
- 能够单独馈送每页,以处理大型文档并避免在查看前将整个文档下载到客户端 PC
- 能够将页面资源(如图像和字体)打包在文档内,而无需从服务器存储和检索资源
- 能够使用未安装在客户端 PC 上但已嵌入源文档中的字体
- 能够将附加信息(称为 _文档元数据_)发送到客户端
使用 WebClient 对象和 Application.GetResourceStream() 方法
在 Silverlight 3 和 4 中,WebClient 对象和静态 Application.GetResourceStream() 方法的组合为我们解决了从 Web 服务器检索 zip 包并将其在 Silverlight 用户控件的客户端单独提取所有对象的问题。WebClient 下载由服务器端的 PDF Creator 控件准备的 XAML zip 包。下载的 zip 包存储在我们的 acPDFSilverlightControl 类的成员变量中,形式为 Stream。
在 ZIP 包中,每个页面描述都可以存储为一个单独的 XAML 文件,并且该页面使用的所有资源都存储在一个虚拟文件夹中。
下载 ZIP 包并从中提取对象可以是异步的,ZIP 包可以在服务器上动态创建并流式传输回客户端,而无需使用临时文件,这正是我们所需要的。使用 WebClient 对象的基本语法如下:
public void LoadFromUrl(string url)
{
// Retrieves the XAML of document packaged in a ZIP format located at the specified URL
// which can be something like:
// "http://www.amyuni.ca/PageTurnPdf/pdf.asp?PDFFile=doc.pdf" or
// "http://www.amyuni.ca/PageTurnPdf/myXAMLpackage.zip"
...
// start web download using the WebClient object
Uri uri = new Uri( url );
WebClient webClient = new WebClient();
webClient.AllowReadStreamBuffering = true; //speeds application response time
// add event listener to detect when data is available
webClient.OpenReadCompleted += new
OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
// add event handler for catching download progress notifications
webClient.DownloadProgressChanged += new
DownloadProgressChangedEventHandler(webClient_DownloadProgressChanged);
// start request
webClient.OpenReadAsync(uri, webClient);
}
private void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
// Start loading the document
// e.Result will contain the XAML ZIP Stream
m_zipStream = e.Result;
...
// get XAML of document
Stream stream = GetPartFromZipPackage("Document/document_1.fdoc", m_zipStream);
StreamReader sreader = new StreamReader(stream);
string sDoc = sreader.ReadToEnd();
...
// get XAML of page from the ZIP package
Stream stream = GetPartFromZipPackage("Pages/page_" + (pageNumber + 1).ToString() +
".fpage", m_zipStream);
// convert to string
StreamReader sreader = new StreamReader(stream);
string sPageXaml = sreader.ReadToEnd();
...
提取和渲染图像
将页面的 XAML 描述下载到客户端不足以显示图像。需要额外的步骤来循环遍历所有图像对象,并将每个图像的源设置为 ZIP 包中的一个图像。每次加载新页面时,都会调用“ProcessElements”。此函数搜索页面上的所有图像并设置它们的“ImageSource”属性。
private void ProcessElements(object _elems)
{
// process all page elements to set:
// - the source of all image elements
// - the font of all text elements
//
for ( int i = 0; i < elems.Count; i++ )
{
UIElement elem = elems[i]; //a child UIElment
Image img = elem as Image;
...
// the Source would be a string
if (img.Source != null && img.Source.ToString() != "")
{
var src = img.Source;
if (src != null)
{
string s = src.ToString();
// set the source after removing the leading slash
Stream stream = GetPartFromZipPackage(s.Substring(1),
m_zipStream);
BitmapImage bitmap = new BitmapImage();
bitmap.SetSource(stream);
img.Source = bitmap;
}
}
...
// loop recursively through all children of the current element
ProcessElements(elem.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 会使用不同的字体进行替换,这通常会产生不令人满意的结果。Silverlight 提供了一种指定用于显示特定元素的字体的方法,只要字体是 OpenType 或 TrueType 格式。在这种情况下,可以使用 Downloader 对象将文档使用的所有字体打包。字体作为部分嵌入式字体嵌入到 ZIP 包中,这些字体是最终用户无法使用的,以避免字体许可问题。
与上面描述的图像处理并行,示例查看器会循环遍历所有文本对象,并将每个对象的字体源设置为 ZIP 包中的一种字体。
// set the font source for all text elements to fonts retrieved from the ZIP package
if ( elem.GetType() == typeof(TextBlock) )
{
TextBlock tb = (TextBlock) elem;
// set the FontSource
tb.FontSource = new FontSource( m_zipStream );
...
文本元素的“FontFamily”属性将文本的字体映射到 ZIP 文件中打包的一种字体。当请求的字体在最终用户系统上找不到时,Silverlight 组件会自动执行此操作。
服务器端脚本
将文档转换为 XAML 并构建 ZIP 软件包的服务器端代码非常直接,并使用 Amyuni PDF Creator .NET。
<%@ WebHandler Language="C#" Class="PdfHandlerNet" %>
using System;using System.Web;
using System.Web.Services;
using System.IO;
using Amyuni.PDFCreator;
[WebService(Namespace = "http://www.amyuni.com/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class PdfHandlerNet : IHttpHandler
{
public void ProcessRequest (HttpContext context)
{
context.Response.ContentType = "application/x-zip-compressed";
var strFilePath = context.Request.QueryString["PDFFile"];
var strPages = context.Request.QueryString["Pages"];
// Create the PDF Creator ActiveX object
// This will throw an exception if the control is not installed
// or the document not found
acPDFCreatorLib.Initialize();
var acpdf = Activator.CreateInstance<Amyuni.PDFCreator.IacDocument>();
//Use a valid evaluation license code
acpdf.SetLicenseKey("Silverlight Evaluation", "1234..3445");
FileStream fIn = new FileStream(context.Server.MapPath(strFilePath),
FileMode.Open,
FileAccess.Read);
acpdf.OpenEx(fIn, string.Empty);
// Set the page sequence. If QueryString("Pages")
//did not find that parameter, then strPages will be ""
//and setting the attribute will clear the array of pages
acpdf.Attribute("PageSequenceStr").Value = strPages;
// Optimize the document to line level in order to improve the XAML export
acpdf.OptimizeDocument(1);
// return the XAML attribute of the document object which is a ZIP package
var o = acpdf.Attribute("XAML").Value;
context.Response.BinaryWrite((byte[]) o);
acpdf.Dispose();
fIn.Close();
fIn.Dispose();
acPDFCreatorLib.Terminate();
}
public bool IsReusable
{
get
{
return false;
}
}
}
将示例扩展到查看 PDF 以外的文档
服务器端脚本可以轻松扩展以渲染其他类型的文档。要渲染 XPS 文档,应由 PDF Creator 控件加载文档,然后将其保存回 XAML,以便与 Silverlight 兼容。这可以通过在服务器端脚本中替换 PDF 文件为 XPS 来完成。
要渲染其他类型的文档,应首先使用 Amyuni PDF Converter 将其转换为 PDF,然后作为 ZIP 包流式传输回客户端。
PDF Converter 产品可从以下网址下载:http://www.amyuni.com/en/developer/pdfconverter。
有关在服务器上将文档转换为 PDF 的示例,请访问:http://www.amyuni.com/en/resources/technicalnotes/