A First Look at APIs For Creating XML Paper Specification Documents (浅谈生成XPS文件的API)

Electronic documents have changed the way people publish and exchange information. But while convenient, the process isn't flawless. With current software, documents often appear one way when viewed on the screen and another when printed. To make matters worse, many documents depend on locally installed software, fonts, and other resources. When a document is viewed on different computers, aspects of the document, such as page layout, image color, and font style, can change, altering the document's appearance. Consequently, a document's appearance depends greatly on where and how the document is viewed.

 

The appearance of the printed version of a document can also vary, depending on the printer used. Unfortunately, there is currently no guarantee that users with different printers attached to different machines will see the exact same document printed on paper as when the document is viewed on the monitor screen. In fact, it is almost guaranteed that each version of the document will have a different appearance.

Microsoft is addressing these issues with an improved document technology that will be included in Windows Vista. An integral part of this new technology is the XML Paper Specification (XPS). This specification is designed to provide users with a consistent document appearance regardless of where and how the document is viewed.

This article provides an overview of the XPS Document format and how you can create and access XPS Documents programmatically. Using sample code, I'll show you a couple of different ways to create an XPS Document from an application based on Windows® Presentation Foundation (WPF). I'll also discuss how you can navigate through the components of an XPS Document. This article should give you the necessary information to get started using XPS Documents in your own applications in anticipation of this technology becoming available in Windows Vista.


What is XPS?

XPS Documents maintain a consistent appearance for documents—despite environmental variables—through the use of a fixed page layout and new technologies such as Windows Presentation Foundation (WPF), the Windows Color System, the Open Packaging Conventions, the XPS print path, and XPS Viewer. Essentially, XPS-based technologies allow an author to be much more certain that the next person to view or print their document will see it exactly as the author intended.

XPS technologies offer benefits to users throughout the document workflow, starting with authoring and viewing the document and continuing through to storing and archiving. For starters, XPS Documents are fixed-format documents described by an XML-based language. This means the document layout is fixed, just as it would be if it were printed on a piece of paper. As a result, XPS Viewer and the XPS print path can present the document in the same way to the user whether it is viewed in a window or on a piece of paper. For more on this, take a look at the sidebar "Three Paths to High Fidelity."

XPS Documents also support security features, such as digital signatures, to provide greater document security. When applied to a document, these digital signatures can help ensure the identity of the author and the validity of the document content.

Custom resources and other application-specific metadata can also be included in an XPS Document, allowing applications to create and use XPS Document packages. These packages offer numerous benefits over other file formats, including the ability to store and archive files with all the content and design details in tact. XPS Document packages describe their contents using a plain-text, XML-based data format (rather than a proprietary binary format). And they contain all the information related to the document and its particular application. The XML Paper Specification describes this format in an open and published specification provided to users and developers by Microsoft under a royalty-free license.

The XPS Document package is a compressed ZIP archive that allows the resulting file to regain some of the space efficiency that is lost by using an XML-based language. The XPS Document package complies with the Open Packaging Conventions. And the ZIP archive format described in the Open Packaging Conventions is a published, open specification that is included under the XPS license. Documents created by the next version of Microsoft® Office System (codenamed "Office 12") will also comply with the Open Packaging Conventions. Using open specifications for both the content and the storage format helps ensure that the document's contents will be accessible long into the future. Further details and downloads regarding XPS and the Open Packaging Conventions can be found on the Microsoft Web site at XML Paper Specification.

Back to top

Inside the XPS Document

The XPS Document API closely follows the logical organization of an XPS Document, so I'll present a brief overview of how an XPS Document is organized. Understanding the internal configuration of the document will make it easier to understand how the XPS Document API works.

XPS Documents are stored in a file, called a package, that conforms to the Open Packaging Conventions and are composed of a set of document components, known as parts. A package has a physical and a logical organization. The physical organization consists of the document parts and folders inside the package, and the logical organization is a hierarchy described by the document parts. The XML Paper Specification applies a specific organization and naming convention to the logical layer for XPS Documents.

The parts of an XPS Document are organized in a logical hierarchy with the FixedDocumentSequence part at the top. An XPS Document package may contain more than one document and the sequence of these documents is described by the FixedDocumentSequence part. The FixedDocumentSequence part references the FixedDocument parts that, in turn, reference the pages of each document within the package.

Each FixedDocument part references the pages of that document as FixedPage parts. Each FixedPage part contains the text markup and layout of a page in the document as well as references to images, fonts, and other custom resources used in the page. Resources such as images and fonts are stored in the package but outside of the FixedPage part, allowing them to be shared by other pages. This is especially useful for font resources, but it could also be useful for any image resources that are used on more than one page, such as a watermark or letterhead logo.

The logical hierarchy of an XPS Document is illustrated in Figure 1. This example shows the contents of a package that contains two separate documents, each containing two pages. The package in this example could be a presentation where FixedDocument_1 contains the slides while FixedDocument_2 contains the background information.

Figure 1 Logical Hierarchy of an XPS Document
Figure 1  Logical Hierarchy of an XPS Document

The XPS Document API manages the physical storage of the document parts and presents them to the application in the logical hierarchy, While it is not necessary to manipulate the package contents directly, it can be useful to know what is stored inside the package, if only to confirm your program is creating the document parts correctly.

The easiest way to view the physical contents of an XPS Document package is with Windows Explorer. The XPS Document API creates XPS Document packages in the ZIP archive file format. The package contents may be stored in a compressed or uncompressed format. In either case, they retain the ZIP organization. Simply changing the file extension of an XPS Document package to ZIP allows the XPS Document to be opened in the file explorer as if it were a standard ZIP archive. Figure 2 shows what a sample XPS Document looks like when viewed in Windows Explorer.

Figure 2 SampleDocXPS Physical Hierarchy in Windows Explorer
Figure 2  SampleDocXPS Physical Hierarchy in Windows Explorer

While the different physical parts of the document are displayed as files and folders in the Windows Explorer window, the logical hierarchy of the document parts is not apparent. The logical organization of the document part hierarchy is illustrated in Figure 3.

Figure 3 SampleDocXPS Logical Hierarchy
Figure 3  SampleDocXPS Logical Hierarchy

The logical hierarchy is described in the document parts of the package. In every XPS Document, the first part in the hierarchy is described as a target in the only part that has a predefined name: /_rels/.rels. Aside from the /_rels/.rels part, the names of the other parts and folders in an XPS Document are listed in the contents of the document parts. In the case of this sample XPS Document, the first document part listed in the /_rels/.rels file is the FixedDocumentSequence.fdseq part, which is located in the document root.

The FixedDocumentSequence.fdseq part describes the next level in the logical hierarchy of the XPS Document, the FixedDocument parts. The contents of the FixedDocumentSequence.fdseq part looks like the following:

<FixedDocumentSequence xmlns="http://schemas.microsoft.com/xps/2005/06">
    <DocumentReference Source="Documents/FixedDocument_1.fdoc" />
</FixedDocumentSequence>
Essentially, it describes where the FixedDocuments of this package are located. In this particular document, there is only one FixedDocument so there is only one DocumentReference element. XPS Document packages containing more documents have more DocumentReference elements listing the sequence in which they should appear.

 

The FixedPage parts for the FixedDocument of this XPS Document package are listed in the FixedDocument_1.fdoc part in the Documents folder. The Fixed Document part looks like this:

<FixedDocument xmlns="http://schemas.microsoft.com/xps/2005/06">
    <PageContent Source="../Pages/FixedPage_1.fpage" />
</FixedDocument>
Again, this is a very simple case with only one page contained in the XPS Document, so the listing is very short. The FixedPage_1.fpage part is in the Pages folder (along with the _rels folder) and contains the layout of that page in the document. The part describing the resources used by this page is found in the Pages/_rels folder. Furthermore, logical document parts can be interleaved and stored in more than one physical part. Interleaving is described further in the XML Paper Specification.

 

Also visible in the Windows Explorer display of the sample file are the folders containing the resources stored in the document. In this sample document, the page contains a photo and uses a font, both of which were stored as resources for this document. This image can be found in the /Resources/Images folder of the XPS Document package, while the font is located in the /Resources/Fonts folder.

Back to top

XPS Document Programming

In an application that uses Windows Presentation Foundation, writing the application's data as an XPS Document is straightforward. Windows Presentation Foundation applications describe their graphical content using Extensible Application Markup Language (XAML) code. XPS Documents use a subset of this code as their markup. A WPF application can create an XPS document by calling a method that flattens the application XAML elements of the page into to the XPS page markup.

A managed code application can also create the individual parts of an XPS Document by using the XPS Document API in the System.Windows.Xps.Serialization namespace. This may be a better option for retaining precise control over how the XPS Document is constructed or for adding additional metadata to the document. An application can also create an XPS Document directly, even if the application was not built using Windows Presentation Foundation. This can be done by creating the markup for the individual XPS Document parts, but the classes and methods described here that actually create the XPS Document only run in a managed code environment.

The XamlStreamToXps method from the sample code shown in Figure 4 can enable your WPF application to create an XPS Document. XamlStreamToXps accepts as its arguments the file name of the XPS Document to be created and a Stream containing XAML, which describes the document. XamlStreamToXps will read the input stream and create an XPS Document.

The first thing the XamlStreamToXps method does is create an empty XpsDocument object with the desired file name and file access. Because the method is creating a new document for the purpose of writing new content into the resulting XPS Document package, it needs write access to the file. Read access is also required to perform this action.

The XpsPackagingPolicy object is used to describe how to package the data in the XPS Document. The packaging policy describes to the serialization manager various aspects of the document package and whether to share resources when it writes the XPS Document. This information is used by the serialization manager that is created in the following line. In this example, the default packaging policy of no resource sharing will be used.

The XpsSerializationManager object is the object that actually does the conversion and writes the XPS Document once the XAML file has been parsed. This object can be configured to write the data to the XPS Document package as it is processed or as a batch. When adding multiple pages to the document, they can be added as a batch by setting the second argument of the constructor to true. Using batch mode requires the commit method to be called before the parsed data is written to disk. The following code snippet illustrates adding an array of streams to a document:

object [] parsedDocObject = ...; // get the objects to be saved
XpsSerializationManager serializationMgr =
    new XpsSerializationManager(packagePolicy, true);
for (int i = 0; i < numObjects; i++ )
{
    serializationMgr.SaveAsXaml(parsedDocObject[i]);
}
serializationMgr.Commit();

 

The XpsSerializationManager object has other configuration parameters that define the font subsetting policy. In this case, however, the default policy values and non-batch mode will work fine.

The next few lines in the method create the parsed object containing the XAML code from the input stream. The context argument is used only if the stream is a FileStream, as would be the case if the XAML for these pages came from an external file. If the stream is an internal stream from the application, the context argument is null. This option allows the method to work with both file streams and internal streams from an application.

Here is a method used to read the XAML from a file and have it parsed by the XamlStreamToXps method:

void XamlFileToXps(string srcXamlFile, string destXpsFile)
{
   using(Stream fileStream = File.OpenRead(srcXamlFile))
   {
      ParserContext context = new ParserContext();
      context.BaseUri = new Uri(Directory.GetCurrentDirectory() + "//");
      XamlStreamToXps(fileStream, context, destXpsFile);
   }
}
With the XAML code parsed into the parsedDocObject object, the XpsSerializationManager now has all it needs to create the XPS Document. Rest assured that even though the SaveAsXaml method was called, the end result will be an XPS Document package because the method is part of the XpsSerializationManager object (apparently this method was named before the XPS documentation had been published).

 

Any resources referenced by the XAML document must be available to this method so they can be opened and read into the resulting XPS Document. This means they must be present and not locked by another application or user. If there is a problem, the serialization method will throw an exception. The method finishes by calling the Close method on the document before exiting.

Figure 5 Sample Document
Figure 5  Sample Document

To follow this process as it acts on a document, let's start with the document shown in Figure 5. The XAML code that describes this document in my application is shown in Figure 6. Note this XAML code has been simplified greatly and uses default values for many of the attributes. The XAML code used in a real application would be much more explicit.

Passing the parsed XAML code to the XpsSerializationManager creates the XPS Document. The serialization routines read in the XAML code from the Windows Presentation Foundation application and create the corresponding parts in the XPS Document. As an example, an excerpt of the FixedPage part that corresponds to the sample page is shown in Figure 7.The XPS Document markup code is similar, but not identical, to the XAML used by Windows Presentation Foundation applications.

Back to top

Building an XPS Document

Your application can create the elements of the XPS Document directly and thereby maintain more control over the final output. With this approach, your application can also embed metadata resources in the document.

An application written to use Windows Presentation Foundation will have access to the XAML used to format and lay out the document pages. However, an existing application not written for WPF can still create XPS Documents by adding in the routines to create the markup that represents the text and graphics layout. Whether it is better to add these functions to your application or rewrite the application for Windows Presentation Foundation depends on your specific requirements.

Note that the examples here use only a subset of the commands provided by XPS. For a complete listing of XPS commands, see the specifications found at XML Paper Specification.

To create a document from scratch, the program needs to create the document parts and their interfaces using the following methods from the System.Windows.Xps.Packaging namespace. It can then start filling the document parts with the contents derived from the application:

// create a new document object
XpsDocument document = new
    XpsDocument(destFileName,FileAccess.ReadWrite);

// create a new FixedDocumentSequence object in the document
IXpsFixedDocumentSequenceWriter docSeqWriter = 
document.AddFixedDocumentSequence();

// create a new FixedDocument object in in the document sequence
IXpsFixedDocumentWriter docWriter = docSeqWriter.AddFixedDocument();

// add a new FixedPage to the FixedDocument
IXpsFixedPageWriter pageWriter = docWriter.AddFixedPage();

 

Once the document package is opened for writing and the necessary interfaces are instantiated, the application can begin writing the application content to the XPS Document. Notice how the interfaces "nest," with the IXpsFixedDocumentSequenceWriter interface created from the document object, the IXpsFixedDocumentWriter interface created from the IXpsFixedDocumentSequenceWriter, and so on. This structure reflects the logical hierarchy of the XPS Document and is important to keep in mind when planning how to adapt your app's document content to the XPS Document.

In a more practical application, a program would most likely call these methods in a manner similar to the pseudo code shown in Figure 8. When the document object is closed, the resulting XPS Document is stored in a self-contained package that includes the resources necessary to render and display the document exactly as it was stored. So, in addition to the text and layout, the non-text resources are also written to the document.

Back to top

Storing Text and Resources

The text for each page in an XPS Document is stored in FixedPage objects that contain the markup describing the layout of each page. Your application needs to generate this markup for your application's documents. The precise method of doing this depends on the type of layout and formatting your application needs to produce.

Take my sample document, for example. The IXpsFixedPageWriter interface writes the markup for this page to the document as a raw, unformatted string. For this method, the application needs this text in a string variable that can be passed to the XmlWriter object. The code shown in this example uses a string named pageContents, which contains the FixedPage description. The following code will add markup to create a page in the XPS Document:

string pageContents = ...; // XPS formatted description of page content
XmlWriter xmlWriter = pageWriter.XmlWriter;
xmlWriter.WriteRaw(pageContents);
pageWriter.Commit();
xmlWriter.Close();

 

In this sample code, the document is described by an XML file that contains the page markup, as well as the image and font resources. The raw string for this page markup is provided as CDATA in an element of the XML file. It describes the document and is assigned to the pageContents variable.

The first line obtains the XML writer object for this page. The markup string is then written to the FixedPage object, the content is committed to the document, and the writer is closed.

The last path elements in the document, shown in the following code sample, describe a colored rectangle and one that is created and filled with an image. The image referenced in the second path element is located in the Images folder and needs to be saved as a resource in the document to completely render this page. Here is what the code looks like:

<Path 
    Fill="#FFFFA500" 
    RenderTransform="1, 0, 0, 1, 60, 120" 
    Data="F0 M 0, 0 L 150, 0 150, 150 0, 150Z" />
<Path 
    Fill="{StaticResource b0}" 
    RenderTransform="1, 0, 0, 1, 40, 140" 
    Data="F0 M 0, 0 L 150, 0 150, 150 0, 150Z" />
All the fonts used in the document must be embedded in the document in a similar fashion. Additionally, custom resources can be created by an app and written to the XPS Document package.

 

In this example, the file names of the images used on a page are stored with the page content in the sample application document. The application adds these images to the XPS Document as it creates each page using the method shown in Figure 9. This method receives the page writer interface from the document, which is passed as an argument along with the file name of the image. Inside the method, a new image resource is added to the FixedPage part, and an image file stream is obtained from the object. The file stream from the image is then opened and copied using the local stream copy method shown in Figure 10. The data is then committed to the document package, the local stream objects are closed, and the method returns.

Fonts used in this document are saved into the XPS Document package in a similar fashion. The method in Figure 11 stores a font to a FixedPage part of an XPS Document.

The AddFont method of the page writer interface allows you to obfuscate the font when it is stored in the document for security and intellectual property protection. This method is intended to prevent the casual user from extracting the font and installing it on their system. While it is not considered a form of strong encryption, it is an obstacle that prevents a user from using the ZIP archive functionality of the operating system to extract a font file and install it on their system. To obfuscate the font, set the argument in the AddFont method to true. In my example, this argument is set to false and the fonts will be stored without obfuscation.

When all document parts have been added to the document, the following code flushes all the buffers and closes the objects:

pageWriter.Commit();
docWriter.Commit();
docSeqWriter.Commit();
document.Close();

 

Back to top

Signing an XPS Document

Digital signatures can be applied to XPS Documents, which is similar to physically signing a document. In some ways, though, this is even better than a physical, pen-and-ink signature. In addition to having a verifiable identity, digital signatures in XPS Documents also indicate the time and date the signature was applied and whether the document has changed since the signature was applied. An XPS Document can also have more than one digital signature.

Attaching a digital signature to an XPS Document is very straightforward, as shown in the following code sample:

void SignDocument(string srcXpsDocument, string certFilename)
{
    XpsDocument document = 
        new XpsDocument(srcXpsDocument, FileAccess.ReadWrite);

    X509Certificate certificate = 
        X509Certificate.CreateFromCertFile(certFilename);
    document.SignDigitally(certificate, true, 
        XpsDigSigPartAlteringRestrictions.None);

    document.Close();
}

 

The file name of the XPS Document and the file name of the digital certificate are passed as arguments, and the XPS Document is opened for read and write access. Then a certificate object is created from the certificate file. Calling the SignDigitally method on the XpsDocument object applies the digital signature to the document by adding the signature to the document's collection of digital signatures. The arguments of the SignDigitally method include the certificate object, a flag indicating that the certificate should be embedded in the document, and a flag indicating that altering the core components of the digital signature should not be restricted. Using the flags of the XpsDigSigPartAlteringRestrictions can be used to restrict changes to the core document components such as the Core Metadata, the Annotations, or the Signature Origin properties of the certificate embedded in the document.

At this point, the document has been signed, but it may be useful to see what other digital signatures have also been applied to this document. You can view the digital signatures in an XPS Document by viewing the document in the XPS Viewer. Or you can use the following sample method to list all the digital signatures that have been applied to the document:

void ListSignatures(string srcXpsDocument)
{
    XpsDocument document = 
        new XpsDocument(srcXpsDocument, FileAccess.Read);
    foreach (XpsDigitalSignature digitalSignature in document.Signatures)
    {
        Console.WriteLine(digitalSignature.SigningTime.ToString() + " " +
            digitalSignature.SignerCertificate.Issuer + " " +
            digitalSignature.SignerCertificate.Subject);
    }
    document.Close();
}

 

In this method, the file name of an XPS Document is passed in the argument list. The XPS Document is opened and the method iterates through the collection of digital signatures, writing the date and time the signature was applied as well as some information on the source of the certificate used for the signature. After iterating through the collection of signatures, the document object is closed and the function returns.

Back to top

Navigating Through an XPS Document

An application already written for the Windows Presentation Foundation can save its document as an XPS Document using the serialization methods I've described. However, if the application has additional, custom resources, they can also call explicit XPS Document methods to add those resources to the document.

Up to this point, I have focused on how to create an XPS Document. But at some point, it will probably be necessary to access XPS Documents in order to read their contents. It should be noted here that an XPS Document is a fixed document and is not intended to be modified directly. For that reason the XPS Document API is divided into a set of writing interfaces and methods, and a separate set of reading interfaces and methods. To modify an XPS Document, an application must read in the current document, modify the contents, and write the modified version as a new document from the application.

As shown previously, Windows Explorer can be used to view and extract the contents of an XPS Document, and XPS Viewer can be used to view the document in its intended form. However, other programs can be used to navigate through XPS Documents by using the XPS Document API.

The method shown in Figure 12 shows how you can navigate through an XPS document. In this example, the code iterates through all the different document parts in order to list the image resource references, returning the count as an integer. The ListImagesInXpsDoc method accepts the file name of an XPS Document as the argument, lists the image references to the console, and returns a count of the image resource references found in that document. This example is admittedly of little practical use by itself, but it demonstrates the nested and hierarchical organization of the XPS Document API, as well as the XPS Document, itself. Using this skeleton as a starting point, more interesting methods can be created.

So let's take a closer look at what occurs here. The document is opened with read access and an IXpsFixedDocumentSequenceReader interface is obtained from the document object to begin navigating through the parts of the XPS Document. The code then iterates through all the fixed pages of all fixed document parts found in this document, and counts the image resource references found in the document.

An IXpsFixedDocumentReader interface is obtained for each fixed document in the fixed document sequence. Using the fixed document reader, the code then iterates through the fixed document, obtaining an IXpsFixedPageReader interface for each page. Image and font resources of a fixed document are referenced by the page in which they are used. Using the page reader, the code iterates through the image resource references found in the page, incrementing the count and writing the Uniform Resource Identifier (URI) of each image reference that is found. The method ends by closing the document and returning the count of image resource references found in the document. The nesting of the reading methods used in this example is the same as the writing functions used previously.

While this sample illustrates how a program can navigate through the hierarchy of document parts in an XPS Document, remember the physical storage hierarchy of these parts does not necessarily reflect the logical hierarchy used to access them. Consequently, the approach used to find an individual part in a document will vary depending on how they need to be accessed.

Back to top

Closing Thoughts

In this article, I have demonstrated some of the most basic ways to read and write XPS Documents. While this is enough knowledge to get started using the XPS Document format in an application, there is much more to this new technology. For starters, look at what else can be done within the FixedPage. As shown in this article, font and image resources can be added to the document. However, XPS Documents can also include custom resources.

Document printing intentions and preferences can also be added to the individual parts of an XPS Document, as well as to the document as a whole, by including XML PrintTicket objects. The information in an XML PrintTicket includes many printer settings, such as page orientation and page size, and can be applied to an entire fixed document sequence or individually to a fixed page in the document.

The list goes on, but these topics will have to wait for another article. In the meantime, you can get started creating and using XPS Documents in your application right away and make use of the new XPS print path and XPS Viewer while "future-proofing" your application's documents at the same time.

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值