Windows Platform Foundation has provided easy APIs and solutions for XPS document generation, visualization and printing. But often time, after XPS documents are generated, we would like to modify it in certain way. The scenario I'm trying to demonstrate here is adding a watermark to each page within an XPS document.
There are multiple ways to modify an XPS document. The lowest level would be working from container and XPS markup directly. But if you're changing font/image resources, it can get quite complicated.
Another way is using WPF to load each page in an XPS document as a Visual, modify the Visual, and then write it back as a new XPS document. We will take the second approach here.
The first step is to create a Visual for watermak:
Visual CreateWatermark(double width, double height, string message)
{
DrawingVisual watermark = new DrawingVisual();
using (DrawingContext ctx = watermark.RenderOpen())
{
FormattedText text = new FormattedText(message,
System.Globalization.CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Times New Roman"), 96 * 1.5,
new SolidColorBrush(Color.FromScRgb(0.3f, 1f, 0f, 0f))
);
// Rotate transform, keep center
{
double diag = Math.Sqrt(width * width + height * height);
double cX = width / 2;
double cY = height / 2;
double sin = height / diag;
double cos = width / diag;
double dx = (cX * (1.0 - cos)) + (cY * sin);
double dy = (cY * (1.0 - cos)) - (cX * sin);
Transform mat = new MatrixTransform(cos, sin, -sin, cos, dx, dy);
ctx.PushTransform(mat);
}
// Centerlize
double x = (width - text.Width) / 2;
double y = (height - text.Height) / 2;
ctx.DrawText(text, new Point(x, y));
ctx.Pop();
}
return watermark;
}
The CreateWatermark routine accepts three parameters, page width, page height, and a text string. It creates a DrawingVisual by drawing to the DrawingContext interface. To make it more like a real watermark, text is rotated according to page dimension and centered.
Once we have code for watermark, we can use XPS-related API to load a document and create a new document:
void AddWatermark(string filename){
// Open original XPS document
XpsDocument xpsOld = new XpsDocument(filename, FileAccess.Read);
FixedDocumentSequence seqOld = xpsOld.GetFixedDocumentSequence();
// Create new XPS document
Package container = Package.Open("new_" + filename, FileMode.Create);
XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(new XpsDocument(container));
// Needed for writing multiple pages
SerializerWriterCollator vxpsd = writer.CreateVisualsCollator();
int pageno = 1;
foreach (DocumentReference r in seqOld.References)
{
FixedDocument d = r.GetDocument(false);
// Walk through each page
foreach (PageContent pc in d.Pages)
{
FixedPage fixedPage = pc.GetPageRoot(false);
double width = fixedPage.Width;
double height = fixedPage.Height;
Size sz = new Size(width, height);
// Convert to WPF Visual
fixedPage.Measure(sz);
fixedPage.Arrange(new Rect(new Point(), sz));
fixedPage.UpdateLayout();
ContainerVisual newpage = new ContainerVisual();
newpage.Children.Add(fixedPage);
newpage.Children.Add(CreateWatermark(width, height, "Confidential (" + pageno + ")"));
pageno ++;
// Write out modified page
vxpsd.Write(newpage);
}
}
vxpsd.EndBatchWrite();
container.Close();
xpsOld.Close();
}
There are a few things to notice here:
Finally, here is a sample output: