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 veryvery 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]
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++)
{
guidBytes
= 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
buf
^= obfuscatedBytes[guidBytesPos];
// xor with new guid to obfuscate
buf
^= 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;
}
}