@dev.raz The feature request has been analyzed by Aspose.Words development team and it was concluded that the required functionality can be achieved using IPageLayoutCallback
. For example see the following code:
private class PreviewCallback : IPageLayoutCallback
{
private readonly HashSet<int> _ReadyPages = new HashSet<int>();
private readonly Action<PageRange, Stream> _ShowPage;
private readonly int _BatchSize;
private Document _Document;
public PreviewCallback(int batchSize, Action<PageRange, Stream> showPage)
{
_ShowPage = showPage;
_BatchSize = batchSize;
}
public void Notify(PageLayoutCallbackArgs args)
{
// This is simplified logic, some scenarios may not trigger callback for updated pages.
// If a document has 7 pages, batch size is 5 and the following rendering order happened:
// 1, 2, 3, 4, 5, 6, 7, 2, 3, 4, 6 then show callback is called for (1-5) and (5-7).
// It is not called for (2-4) and (6-6) after these were updated.
_Document = args.Document;
switch (args.Event)
{
case PageLayoutEvent.BuildFinished:
if (_ReadyPages.Count > 0)
{
var i = (_ReadyPages.Max() / _BatchSize) * _BatchSize;
FlushPages(new PageRange(i, _ReadyPages.Where(v => v < i + _BatchSize).Max()));
}
break;
case PageLayoutEvent.PartReflowFinished:
{
_ReadyPages.Add(args.PageIndex);
if (_ReadyPages.Count >= _BatchSize)
{
var from = (_ReadyPages.Min() / _BatchSize) * _BatchSize;
var to = _ReadyPages.Max();
for (var i = from; i <= to; i += _BatchSize)
if (Enumerable.Range(i, _BatchSize).All(v => _ReadyPages.Contains(v)))
FlushPages(new PageRange(i, _ReadyPages.Where(v => v < i + _BatchSize).Max()));
}
break;
}
}
}
void FlushPages(PageRange pageRange)
{
// Save current page range into pdf stream. It can be PNG or HTML or any other supported output format.
// NOTE It is important to disable field update, otherwise infinite loop may form for certain documents.
// Field update has already happened or will happen as specified when document was saved originally.
var pdfStream = new MemoryStream();
_Document.Save(pdfStream, new PdfSaveOptions { PageSet = new PageSet(pageRange), UpdateFields = false });
pdfStream.Position = 0;
_ShowPage(pageRange, pdfStream);
for (var i = pageRange.From; i < pageRange.From + _BatchSize; i++)
_ReadyPages.Remove(i);
}
}
static void Preview(string fileName, int batchSize)
{
// This technique is only suitable for preview purposes, it is advisable to do
// a proper full uninterrupted save operation on the document to get correct result.
var doc = new Document(fileName);
doc.LayoutOptions.Callback = new PreviewCallback(batchSize, (pageRange, pdfStream) =>
{
// Preview code does whatever necessary to render the pdf document, i.e. serve web response.
// In production it should queue this work on a pool thread to not block layout code.
// NOTE Each page can be rendered multiple times and each time result can be different. This may also happen out of order.
// Here tickcount is added to keep all outputs separate and not overwrite.
using (var pageStream = new FileStream($@"pdf-pages-{pageRange.From + 1}-{pageRange.To + 1} ({Environment.TickCount}).pdf", FileMode.Create))
pdfStream.CopyTo(pageStream);
});
// This builds page layout and updates fields. The output is discarded and format does not matter so long it is fixed page format.
doc.Save(new MemoryStream(), SaveFormat.Xps);
}