views:

1065

answers:

8

I would like to generate (and then print or save) big XPS documents (>400 pages) from my WPF application. We have some large amount of in-memory data that needs to be written to XPS.

How can this be done without getting an OutOfMemoryException? Is there a way I can write the document in chunks? How is this usually done? Should I not be using XPS for large files in the first place?

The root cause of the OutOfMemoryException seems to be the creation of the huge FlowDocument. I am creating the full FlowDocument and then sending it to the XPS document writer. Is this the wrong approach?

+3  A: 

How do you do it? You didn't show any code.

I use an XpsDocumentWriter to write in chunks, like this:

FlowDocument flowDocument =  . .. ..;

// write the XPS document
using (XpsDocument doc = new XpsDocument(fileName, FileAccess.ReadWrite))
{
    XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(doc);
    DocumentPaginator paginator = ((IDocumentPaginatorSource)flowDocument).DocumentPaginator;

    // Change the PageSize and PagePadding for the document
    // to match the CanvasSize for the printer device.
    paginator.PageSize = new Size(816, 1056);
    copy.PagePadding = new Thickness(72);  
    copy.ColumnWidth = double.PositiveInfinity;
    writer.Write(paginator);
}

Does this not work for you?

Cheeso
Where are the chunks. Do you mean that this example could potentially write multiple XPS documents to the same flowDocument? Wehn I reuse the same paginator all XPS documents will they end up in the same XPS file or in the same printed document?
bitbonk
The chunks: you could use writer.Write() in a loop. Even without that, I would guess that writer.Write() uses a streamed approach on the paginator, and nothing needs to be accumulated into memory. I don't know what you mean by "use the same paginator." you need to show your code.
Cheeso
This would me I would need to generate multiple FlowDocument instances, correct? Since the paginator comes from the FlowDocument instance.
bitbonk
Yes, I suppose so. I haven't tried it.
Cheeso
This doesn't work. We still get OutOfMemoryExcpetions. There must be a best practise how to generate really big XPS (>400 Pages) documents from code.
bitbonk
show your code?
Cheeso
+2  A: 

I can confirm that XPS does not throw out-of-memory on long documents. Both in theory (because operations on XPS are page-based, it doesn't try to load whole document in memory), and in practice (I use XPS-based reporting, and seen run-away error messages add up to many thousands of pages).

Could it be that the problem is in a single particularly large page? A huge image, for example? Large page with high DPI resolution? If single object in document is too big to be allocated at once, it will lead to out-of-memory exception.

ima
How am I supposed to setup the pages? I thought the paginator does that for me.
bitbonk
We did not specify a page size maybe that is the root of all evil ...
bitbonk
I didn't take any special measures, just followed examples. Like others have noted, you provided far too little information. There must be something unusual about your code or system, but how can we guess what it is - without call-stack or snippets of code?
ima
A: 

Have you used sos to find out what is using up all the memory?

It could be that managed or unmanaged objects are being created during the production of your document, and they're not being released until the document is finished (or not at all).

Tracking down managed memory leaks by Rico Mariani could be of help.

marklam
The OutOfMemeoryExcpetion is most likely caused by creating the huge FlowDocument in Memory. How am I supposed to create the XPS when I can't create the FixedDocument for it?
bitbonk
If your FlowDocument isn't holding on to more objects than it needs, then maybe you could create a custom DocumentPaginator which gerenates the pages on demand? There doesn't seem to be a lot of info about the subject though.
marklam
A: 

The OutOfMemeoryExcpetion is most likely caused by...

So in other words, you haven't actually checked. We need more information to tell you what the problem is.

Chris Ridenour
A: 

Hi Bitbonk,

like you say: probably the in-memory FixedDocument is consuming too much memory.

Maybe an approach in which you write out the XPS pages each individually (and make sure the FixedDocument gets released each time), and then use a merger afterwards could prove fruitful.

Are you able to write each page separately?

Nick.

ps. Feel free to concact me directly ([email protected]); we do a lot of XPS stuff at NiXPS, and I'm very interested in helping you getting this issue resolved.

nixps
+3  A: 

Speaking from perfect ignorance of the specific system involved, might I suggest using the Wolf Fence in Alaska debugging technique to identify the source of the problem? I'm suggesting this because other responders are not reporting the same problem you are experiencing. When working with easy-to-reproduce bugs Wolf Fence is dead simple to do (it doesn't work so well with race conditions and the like).

  1. Pick a midpoint in your input data and try to generate your output document from only that data, no more.
  2. If it succeeds, pick a point about 75% into the input and try again, otherwise pick a point at about 25% of the way into the input and try again.
  3. Lather, rinse, repeat, each time narrowing the window to where the works/fails line is.
  4. You may find that you fairly quickly identify one specific page -- or perhaps one specific object on that page -- that is "funny." Note: you only have to do this log2(N) times, or in this case 9 times given the 400 pages you mention.

Now you probably have something you can attack directly. Good luck.

Peter Rowell
A: 

I used to follow this article, it work for me.

In The Pink
+1  A: 

You cannot use a single FlowDocument for generating large documents because you will run out of memory. However if it is possible to generate your output as a sequence of FlowDocument or as an extremely tall ItemsControl, it is possible.

I've found the easiest way to do this is to subclass DocumentPaginator and pass an instance of my subclass to XpsDocumentWriter.Write:

var document = new XpsDocument(...); var writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument); writer.Write(new WidgetPaginator { Widget = widgetBeingPrinted, PageSize = ... });

The WidgetPaginator class itself is quite simple:

class WidgetPaginator : DocumentPaginator, IDocumentPaginatorSource { Size _pageSize;

 public Widget Widget { get; set; }

 public override Size PageSize { get { return _pageSize; } set { _pageSize = value; } }
 public override bool IsPageCountValid { return true; }
 public override IDocumentPaginatorSource Source { return this; }
 public override DocumentPaginator DocumentPaginator { return this; }
 public override int PageCount
 {
   get
   {
     return ...; // Compute page count
   }
 }
 public override DocumentPage GetPaget(int pageNumber)
 {
   var visual = ...; // Compute page visual

   Rect box = new Rect(0,0,_pageSize.With, _pageSize.Height);
   return new DocumentPage(visual, _pageSize, box, box);
 }

Of course you still have to write the code that actually creates the pages.

If you want to use a series of FlowDocuments to create your document

If you're using a sequence of FlowDocuments to lay out your document one section at a time instead of all at once, your custom paginator can work in two passes:

  • The first pass occurs as the paginator is constructed. It creates a FlowDocument for each section, then gets a DocumentPaginator to retrieve the number of pages. Each section's FlowDocument is discarded after the pages are counted.
  • The second pass occurs during actual document output: If the number passed to GetPage() is in the most recent FlowDocument created, GetPage() simply calls that document's paginator to get the appropriate page. Otherwise it discards that FlowDocument and creates a FlowDocument for the new section, gets its paginator, then calls GetPage() on the paginator.

This strategy allows you to continue to use FlowDocuments as you have been, as long as you can break the data into "sections" each with its own document. Your custom paginator then effectively treats all the individual FlowDocuments as one big document. This is similar to Word's "Master Document" feature.

If you can render your data as a sequence of vertically-stacked visuals

In this case, the same technique can be used. During the first pass, all visuals are generated in order and measured to see how many will fit on a page. A data structure is built to indicate which range of visuals (by index or whatever) are found on a given page. During this process each time a page fills up, the next visual is placed on a new page. Headers and footers would be handled in the obvious way.

During the actual document generation, the GetPage() method is implemented to regenerate the visuals previously decided to be on a given page and combine them using a vertical DockPanel or other panel of your choice.

I've found this technique more flexible in the long run because you don't have to deal with the limitations of FlowDocument.

Ray Burns