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

Merge XPS documents
vcboy
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
Spanglishone
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
Tom Janssen
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 addednewImageUri = (
string)imageList[imageUri];pageWriter.AddResource(
typeof(XpsImage), new Uri(newImageUri, UriKind.RelativeOrAbsolute));}
// update image resource in source-pagepageContent = pageContent.Replace(imageUri, newImageUri);
}
#endregion
smilertoo
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);
}
}
Siddartha Pal
If you add document references it shouldn't be a problem to add the same references several times to a FixedDocumentSequence...
// 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);
Mosesm
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 !
Anyone got some ideas how to merge complete docs (and it's resources) to a new package (without recreating each page)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 more ideas (c;
calvinkwoo
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)
= Convert.ToByte(guidString.Substring(i * 2, 2), 16);
^= obfuscatedBytes[guidBytesPos];
^= guidBytes[guidBytesPos];
{
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++)
{
guidBytes
}
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
buf
// xor with new guid to obfuscate
buf
}
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;
}
}