Merge XPS documents

Basicly I want to merge 1-n xps-documents into a new package, either just add all source-documents or just add all the pages of the source-documents into the new package...

I've tried out with following code, which writes all the

source-documents-pages into a new xps-document,however this is very

very slow!!!

public void CreateXPSStreamPages(string targetDocument, List<string> list)
{
Package container = Package.Open(targetDocument, FileMode.Create);
XpsDocument xpsDoc = new XpsDocument(container);
XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(xpsDoc);

SerializerWriterCollator vxpsd = writer.CreateVisualsCollator();
vxpsd.BeginBatchWrite();
foreach (string sourceDocument in list)
{
AddXPSDocument(sourceDocument, vxpsd);
}
vxpsd.EndBatchWrite();
container.Close();
}

public void AddXPSDocument(string sourceDocument, SerializerWriterCollator vxpsd)
{
XpsDocument xpsOld = new XpsDocument(sourceDocument, FileAccess.Read);
FixedDocumentSequence seqOld = xpsOld.GetFixedDocumentSequence();
foreach (DocumentReference r in seqOld.References)
{
FixedDocument d = r.GetDocument(false);
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);
fixedPage.Width = width;
fixedPage.Height = height;
fixedPage.Measure(sz);
fixedPage.Arrange(new Rect(new Point(), sz));
//fixedPage.UpdateLayout();

ContainerVisual newPage = new ContainerVisual();
newPage.Children.Add(fixedPage);
//test: add Watermark from Feng Yuan sample
//newPage.Children.Add(CreateWatermark(width, height, "Watermark"));
vxpsd.Write(newPage);
}
}
xpsOld.Close();
}

guess it's slow due the following method calls:

fixedPage.Measure(sz);
fixedPage.Arrange(new Rect(new Point(), sz));

So what is the best/fastest way to merge 1-n XPS-documents into a new xps-document/package?

any help/ideas/sample code would be fine...
thanx in advance for help...

kind regards,
Jo

[2374 byte] By [Jo0815] at [2007-12-27]
# 1
Hi,

I would assume that merging the documents in a single package will be more efficient, than extracting every page and inserting them in a single doc. Xps has the ability to contain multiple docs.

Do not know how to do this with the WPF api, I'm not an expert on that. I could help you doing this with C/C++, contact me direct if this would interest you.

regards,
nixps

nixps at 2007-9-4 > top of Msdn Tech,Software Development for Windows Vista,XML Paper Specification (XPS)...
# 2

well, I know XPS has ability to contain multiple docs... the code above was just one of my tests I did and it just creates a package containing a single document with ALL the pages from the source-documents...

I've also tried to create a package which contains multiple documents (hence all the source-documents) instead of a single document, however the merging was in both ways really slow and though I'm not sure if I've done correctly and if it's best way to use WPF-API for it?!?

Ofcourse I'm interested in how you do that without WPF-API, all help is welcome and I'll for sure will contact you! :)

Anyone got some ideas how to merge complete docs (and it's resources) to a new package (without recreating each page)?

Anyone got some more ideas? (c;

Jo0815 at 2007-9-4 > top of Msdn Tech,Software Development for Windows Vista,XML Paper Specification (XPS)...
# 3
Ok, I have done some more tests and post another method for streaming xps-documents into a new package, maybe it's helpful for others also... feedback would be nice!

However this was just a quick hack and could be optimized for sure, but

it shows that this way is a bit faster than my first try... The

trickiest part was moving an obfuscated font... (c;
Also do note that moving other resources like images is not implemented in that example, but could be done the same way...

Here are some results on my test-machine (which isn't the fastest) and the documents only contained font-resources and I used same documents (so this is not a "real, serious" measurment to rely on, but shows it's faster - just test on your machine with your own document):

7 documents, total of 35 pages:

method #1 : 2634ms / avg. 75,25ms/page
method #2 : 801ms / avg. 22,88ms/page

70 documents, total of 350 pages:

method #1 : 21s / avg. 0,06s/page

method #2 : 5s / avg. 0,014s/page

700 documents, total of 3500 pages:

method #1 : 421s / avg. 0,12s/page

method #2 : 214s / avg. 0,06s/page

so it seems that the 2nd method is faster, because it doesn't need to instantiate drawing primitivies and rendering etc...

Still I have a few questions about, which I hope someone of the Microsoft XPS-Team (or any other) could answer:

- Is this a "legitim" way of merging documents?
- Is there a way to find out which font a XpsFont-resource is? I only saw GUIDS... This would be helpful for optimizing resources in the new stream... (why add an Arial-font x-times when for example the same font is used by several documents...?)
- Same for images?

any ideas, feedback etc. would be nice...
regards from Germany
-Jo

public void CreateStream(string targetDocument, List<string> list)
{
if (File.Exists(targetDocument))
{
File.Delete(targetDocument);
}

// create global fontList to keep track of added font-resources
fontList = new Hashtable();

XpsDocument newXpsDoc = new XpsDocument(targetDocument, FileAccess.ReadWrite);
IXpsFixedDocumentSequenceWriter docSeqWriter = newXpsDoc.AddFixedDocumentSequence();

foreach (string sourceDocument in list)
{
AddToStream(sourceDocument, docSeqWriter);
}

docSeqWriter.Commit();
newXpsDoc.Close();
}

public void AddToStream(string sourceDocument, IXpsFixedDocumentSequenceWriter docSeqWriter)
{
string fontUri;
string newFontUri;

// Open original document for reading...
XpsDocument xpsOld = new XpsDocument(sourceDocument, FileAccess.Read);
IXpsFixedDocumentSequenceReader fixedDocSeqReader = xpsOld.FixedDocumentSequenceReader;

foreach (IXpsFixedDocumentReader docReader in fixedDocSeqReader.FixedDocuments)
{
// Add new document to target stream
IXpsFixedDocumentWriter docWriter = docSeqWriter.AddFixedDocument();

foreach (IXpsFixedPageReader fixedPageReader in docReader.FixedPages)
{
// read xml of source-page
fixedPageReader.XmlReader.Read();
string pageContent = fixedPageReader.XmlReader.ReadOuterXml();

// Add new page to target stream
IXpsFixedPageWriter pageWriter = docWriter.AddFixedPage();

#region Copy Font resources to target
// Add Font Resources...
foreach (XpsFont font in fixedPageReader.Fonts)
{
// check if we have already added that font...
fontUri = font.Uri.ToString();
if (!fontList.ContainsKey(fontUri))
{
// ...no, then add the font
XpsFont newFont = pageWriter.AddFont(font.IsObfuscated, font.IsRestricted);
newFontUri = newFont.Uri.ToString();
if (font.IsObfuscated)
{
CopyObfuscatedStream(fontUri, newFontUri, font.GetStream(), newFont.GetStream());
}
else
{
CopyStream(font.GetStream(), newFont.GetStream());
}
newFont.Commit();

fontList.Add(fontUri, newFontUri);
}
else
{
//yes, so just add a reference to the font resource we already have added
newFontUri = (string) fontList[fontUri];
pageWriter.AddResource(typeof(XpsFont), new Uri(newFontUri, UriKind.RelativeOrAbsolute));
}

// update font resource in source-page
pageContent = pageContent.Replace(fontUri, newFontUri);
}
#endregion

//
// at this point do the same with other resources like images etc.
//

// now when done, write updated xml to new page
pageWriter.XmlWriter.WriteRaw(pageContent);
pageWriter.Commit();
}
docWriter.Commit();
}
}

private byte[] ConvertGuidToByteArray(string resourceName)
{
// Get the GUID byte from the resource name. Typical Font name:
// /Resources/Fonts/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.ODTTF
int startPos = resourceName.LastIndexOf('/') + 1;
int length = resourceName.LastIndexOf('.') - startPos;
string g = resourceName.Substring(startPos, length);

Guid guid = new Guid(g);
string guidString = guid.ToString("N");

// Parsing the guid string and coverted into byte value
byte[] guidBytes = new byte[16];
for (int i = 0; i < guidBytes.Length; i++)
{
guidBytesIdea = Convert.ToByte(guidString.Substring(i * 2, 2), 16);
}

return guidBytes;
}

private void CopyObfuscatedStream(string srcResource, string dstResource, Stream srcStream, Stream dstSteam)
{
int bufSize = 0x1000;
int guidByteSize = 16;
int obfuscatedByte = 32;

// extract Guids from resourceName and convert guid to byte array
byte[] obfuscatedBytes = ConvertGuidToByteArray(srcResource);
byte[] guidBytes = ConvertGuidToByteArray(dstResource);

// XOR the first 32 byte of the source resource stream with guid byte
byte[] buf = new byte[obfuscatedByte];
srcStream.Read(buf, 0, obfuscatedByte);

for (int i = 0; i < obfuscatedByte; i++)
{
int guidBytesPos = guidByteSize - (i % guidBytes.Length) - 1;
// xor with old guid to get original
bufIdea ^= obfuscatedBytes[guidBytesPos];
// xor with new guid to obfuscate
bufIdea ^= guidBytes[guidBytesPos];
}
dstSteam.Write(buf, 0, obfuscatedByte);

// copy remaining stream from source without obfuscation
buf = new byte[bufSize];

int bytesRead = 0;
while ((bytesRead = srcStream.Read(buf, 0, bufSize)) > 0)
{
dstSteam.Write(buf, 0, bytesRead);
}
}

public static void CopyStream(Stream srcStream, Stream dstSteam)
{
int arraySz = 1024 * 1024;
byte[] buffer = new byte[arraySz]; //1MB
long bytesRemaining = srcStream.Length;

while (bytesRemaining > 0)
{
long copyLng = (bytesRemaining > arraySz) ? arraySz : bytesRemaining;
srcStream.Read(buffer, 0, (int)copyLng);
dstSteam.Write(buffer, 0, (int)copyLng);
bytesRemaining -= copyLng;
}
}

Jo0815 at 2007-9-4 > top of Msdn Tech,Software Development for Windows Vista,XML Paper Specification (XPS)...
# 4

Code for images recourse... Copying the images from old document to new document..

#region Copy Image resources to target

foreach (XpsImage image in page.Images)

{

// check if we have already added that image...

imageUri = image.Uri.ToString();

if (!imageList.ContainsKey(imageUri))

{

// ...no, then add the image

XpsImage newImage = pageWriter.AddImage(XpsImageType.JpegImageType);

newImageUri = newImage.Uri.ToString();

CopyStream(image.GetStream(), newImage.GetStream());

newImage.Commit();

imageList.Add(imageUri, newImageUri);

}

else

{

//yes, so just add a reference to the image resource we already have added

newImageUri = (string)imageList[imageUri];

pageWriter.AddResource(typeof(XpsImage), new Uri(newImageUri, UriKind.RelativeOrAbsolute));

}

// update image resource in source-page

pageContent = pageContent.Replace(imageUri, newImageUri);

}

#endregion

Siong at 2007-9-4 > top of Msdn Tech,Software Development for Windows Vista,XML Paper Specification (XPS)...
# 5

As it was helpful for some others, I'll post it here again to keep the MergeXPS-Samples in one thread...

This one is much faster than my

other merge-samples, as it's just adding original documents without

modifying page/document-content, but should keep original documents...

public void CreateXPSStream(string targetDocument, List<string> list)
{
if (File.Exists(targetDocument))
{
File.Delete(targetDocument);
}

Package container = Package.Open(targetDocument, FileMode.Create);
XpsDocument xpsDoc = new XpsDocument(container);

FixedDocumentSequence seqNew = new FixedDocumentSequence();

foreach (string sourceDocument in list)
{
AddXPSDocuments(sourceDocument, seqNew);
}

XpsDocumentWriter xpsWriter = XpsDocument.CreateXpsDocumentWriter(xpsDoc);
xpsWriter.Write(seqNew);
xpsDoc.Close();
container.Close();
}

public void AddXPSDocuments(string sourceDocument, FixedDocumentSequence seqNew)
{
XpsDocument xpsOld = new XpsDocument(sourceDocument, FileAccess.Read);
FixedDocumentSequence seqOld = xpsOld.GetFixedDocumentSequence();

foreach (DocumentReference r in seqOld.References)
{
DocumentReference newRef = new DocumentReference();
(newRef as IUriContext).BaseUri = (r as IUriContext).BaseUri;
newRef.Source = r.Source;
seqNew.References.Add(newRef);
}
}

Jo0815 at 2007-9-4 > top of Msdn Tech,Software Development for Windows Vista,XML Paper Specification (XPS)...
# 6

Hi,

I am having a set of fixeddocuments which are generated from individual xaml files. I am adding each fixed document to a dictionary collection. From the dictionay i need to get some of the fixeddocuments and print that or save as a XPS document. I have to iterate through the dictionary a number of times to generate the fixeddocumentsequnce since each time i have to form a fixeddocumentsequnce differenly. Mostly it will contain repeated documents. Once if i add a fixeddocument from the dictionary to a fixeddocumentsequnce and not able to add the same document again. There are no clonable or copy options available for this. When i try to add that document it throws an exception "Specifiied element is already logical of other" .But the conversion of XAML to a fixeddocument takes time so for each iteration and the performance is decreasing.

Can anybody suggest how to make a copy of the fixeddocument?

HariharanN at 2007-9-4 > top of Msdn Tech,Software Development for Windows Vista,XML Paper Specification (XPS)...
# 7
how do you add the FixedDocument to your new FixedDocumentSequence?

If you add document references it shouldn't be a problem to add the same references several times to a FixedDocumentSequence...

Code Snippet

// create a new FixedDocumentSequence
FixedDocumentSequence myFixedDocSeq = new FixedDocumentSequence();

// open a test-document for this example...
using (XpsDocument xpsDoc = new XpsDocument("20pages.xps", FileAccess.Read))
{
// and get the FixedDocumentSequence of it...
FixedDocumentSequence fixedDocSeq = xpsDoc.GetFixedDocumentSequence();

// get the DocumentReference to first document (or just iterate over all)
DocumentReference docRef = fixedDocSeq.References[0];
// create a new documentReference and set BaseUr/Source from original document reference
DocumentReference newRef = new DocumentReference();
(newRef as IUriContext).BaseUri = (docRef as IUriContext).BaseUri;
newRef.Source = docRef.Source;

// add DocumentReference to our new fixedDocumentSequence
myFixedDocSeq.References.Add(newRef);
// and some more to prove it works using same document references in the FixedDocumentSequence
myFixedDocSeq.References.Add(newRef);
myFixedDocSeq.References.Add(newRef);
}

// get default PrintQueue
PrintQueue printQueue = LocalPrintServer.GetDefaultPrintQueue();
// get a XpsDocumentWriter
XpsDocumentWriter xpsWriter = PrintQueue.CreateXpsDocumentWriter(printQueue);
// and print out our new FixedDocumentSequence, which holds 3 references to the same
// document now, hence it prints the document 3 times also... :P
xpsWriter.Write(myFixedDocSeq);


Jo0815 at 2007-9-4 > top of Msdn Tech,Software Development for Windows Vista,XML Paper Specification (XPS)...

Software Development for Windows Vista

Site Classified