Code:
/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / MS / Internal / documents / FlowDocumentPaginator.cs / 1305600 / FlowDocumentPaginator.cs
//----------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
// File: FlowDocumentPaginator.cs
//
// Description: DynamicDocumentPaginator associated with FlowDocument.
//
// History:
// 08/29/2005 : [....] - created.
//
//---------------------------------------------------------------------------
using System; // Object
using System.Collections.Generic; // List
using System.ComponentModel; // AsyncCompletedEventArgs
using System.Windows; // Size
using System.Windows.Documents; // DocumentPaginator
using System.Windows.Media; // Visual
using System.Windows.Threading; // DispatcherOperationCallback
using MS.Internal.PtsHost; // BreakRecordTable, FlowDocumentPage
using MS.Internal.Text; // DynamicPropertyReader
namespace MS.Internal.Documents
{
///
/// Delegate indicating when entire break record has been invalidated.
///
internal delegate void BreakRecordTableInvalidatedEventHandler(object sender, EventArgs e);
///
/// DynamicDocumentPaginator associated with FlowDocument.
///
internal class FlowDocumentPaginator : DynamicDocumentPaginator, IServiceProvider, IFlowDocumentFormatter
{
//-------------------------------------------------------------------
//
// Constructors
//
//-------------------------------------------------------------------
#region Constructors
///
/// Constructor
///
internal FlowDocumentPaginator(FlowDocument document)
{
_pageSize = _defaultPageSize;
_document = document;
_brt = new BreakRecordTable(this);
_dispatcherObject = new CustomDispatcherObject();
// Background pagination by default is enabled.
_backgroundPagination = true;
InitiateNextAsyncOperation();
}
#endregion Constructors
//--------------------------------------------------------------------
//
// Public Methods
//
//-------------------------------------------------------------------
#region Public Methods
///
/// Async version of
///
/// Page number.
/// Unique identifier for the asynchronous task.
///
/// Throws ArgumentOutOfRangeException if PageNumber is negative.
///
public override void GetPageAsync(int pageNumber, object userState)
{
// Page number cannot be negative.
if (pageNumber < 0)
{
throw new ArgumentOutOfRangeException("pageNumber", SR.Get(SRID.IDPNegativePageNumber));
}
// Reentrancy check.
if (_document.StructuralCache.IsFormattingInProgress)
{
throw new InvalidOperationException(SR.Get(SRID.FlowDocumentFormattingReentrancy));
}
if (_document.StructuralCache.IsContentChangeInProgress)
{
throw new InvalidOperationException(SR.Get(SRID.TextContainerChangingReentrancyInvalid));
}
DocumentPage page = null;
if(!_backgroundPagination)
{
page = GetPage(pageNumber);
}
else
{
// If entire content has been already pre-paginated (BreakRecordTable is clean)
// and requesting non-existing page number, return DocumentPage.Missing.
if (_brt.IsClean && !_brt.HasPageBreakRecord(pageNumber))
{
page = DocumentPage.Missing;
}
if(_brt.HasPageBreakRecord(pageNumber))
{
page = GetPage(pageNumber);
}
if(page == null)
{
_asyncRequests.Add(new GetPageAsyncRequest(pageNumber, userState, this));
InitiateNextAsyncOperation();
}
}
if(page != null)
{
OnGetPageCompleted(new GetPageCompletedEventArgs(page, pageNumber, null, false, userState));
}
}
///
/// Retrieves the DocumentPage for the given page number. PageNumber
/// is zero-based.
///
/// Page number.
///
/// Returns DocumentPage.Missing if the given page does not exist.
///
///
/// Multiple requests for the same page number may return the same
/// object (this is implementation specific).
///
///
/// Throws ArgumentOutOfRangeException if PageNumber is negative.
///
public override DocumentPage GetPage(int pageNumber)
{
DocumentPage page;
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
// Page number cannot be negative.
if (pageNumber < 0)
{
throw new ArgumentOutOfRangeException("pageNumber", SR.Get(SRID.IDPNegativePageNumber));
}
// Reentrancy check.
if (_document.StructuralCache.IsFormattingInProgress)
{
throw new InvalidOperationException(SR.Get(SRID.FlowDocumentFormattingReentrancy));
}
if (_document.StructuralCache.IsContentChangeInProgress)
{
throw new InvalidOperationException(SR.Get(SRID.TextContainerChangingReentrancyInvalid));
}
// Disable processing of the queue during blocking operations to prevent unrelated reentrancy.
using (_document.Dispatcher.DisableProcessing())
{
_document.StructuralCache.IsFormattingInProgress = true; // Set reentrancy flag.
try
{
// If entire content has been already pre-paginated (BreakRecordTable is clean)
// and requesting non-existing page number, return DocumentPage.Missing.
if (_brt.IsClean && !_brt.HasPageBreakRecord(pageNumber))
{
page = DocumentPage.Missing;
}
else
{
// If the DocumentPage is cached in the BreakRecordTable, use it.
page = _brt.GetCachedDocumentPage(pageNumber);
if (page == null)
{
// If requested page number does not have pre-calculated BreakRecord,
// do synchronous pagination up to the requested page number.
// [Synchronous pagination is done here, because GetPage is a [....] operation.]
if (!_brt.HasPageBreakRecord(pageNumber))
{
page = FormatPagesTill(pageNumber);
}
// If requested page number does have pre-calculated BreakRecord,
// format a single page.
else
{
page = FormatPage(pageNumber);
}
}
}
}
finally
{
_document.StructuralCache.IsFormattingInProgress = false; // Clear reentrancy flag.
}
}
return page;
}
///
/// Async version of
///
/// Content position.
/// Unique identifier for the asynchronous task.
///
/// Throws ArgumentException if the ContentPosition does not exist within
/// this element’s tree.
///
public override void GetPageNumberAsync(ContentPosition contentPosition, object userState)
{
// Content position cannot be null.
if (contentPosition == null)
{
throw new ArgumentNullException("contentPosition");
}
// Content position cannot be Missing.
if (contentPosition == ContentPosition.Missing)
{
throw new ArgumentException(SR.Get(SRID.IDPInvalidContentPosition), "contentPosition");
}
// ContentPosition must be of appropriate type and must be part of
// the content.
TextPointer flowContentPosition = contentPosition as TextPointer;
if (flowContentPosition == null)
{
throw new ArgumentException(SR.Get(SRID.IDPInvalidContentPosition), "contentPosition");
}
if (flowContentPosition.TextContainer != _document.StructuralCache.TextContainer)
{
throw new ArgumentException(SR.Get(SRID.IDPInvalidContentPosition), "contentPosition");
}
int pageNumber = 0;
if(!_backgroundPagination)
{
pageNumber = GetPageNumber(contentPosition);
OnGetPageNumberCompleted(new GetPageNumberCompletedEventArgs(contentPosition, pageNumber, null, false, userState));
}
else
{
if(_brt.GetPageNumberForContentPosition(flowContentPosition, ref pageNumber))
{
OnGetPageNumberCompleted(new GetPageNumberCompletedEventArgs(contentPosition, pageNumber, null, false, userState));
}
else
{
_asyncRequests.Add(new GetPageNumberAsyncRequest(flowContentPosition, userState, this));
InitiateNextAsyncOperation();
}
}
}
///
/// Returns the page number on which the ContentPosition appears.
///
/// Content position.
///
/// Returns the page number on which the ContentPosition appears.
///
///
/// Throws ArgumentException if the ContentPosition does not exist within
/// this element's tree.
///
public override int GetPageNumber(ContentPosition contentPosition)
{
TextPointer flowContentPosition;
int pageNumber;
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
// ContentPosition cannot be null.
if (contentPosition == null)
{
throw new ArgumentNullException("contentPosition");
}
// ContentPosition must be of appropriate type and must be part of
// the content.
flowContentPosition = contentPosition as TextPointer;
if (flowContentPosition == null)
{
throw new ArgumentException(SR.Get(SRID.IDPInvalidContentPosition), "contentPosition");
}
if (flowContentPosition.TextContainer != _document.StructuralCache.TextContainer)
{
throw new ArgumentException(SR.Get(SRID.IDPInvalidContentPosition), "contentPosition");
}
// We are about to perform synchronous pagination, so need to check for
// reentrancy.
if (_document.StructuralCache.IsFormattingInProgress)
{
throw new InvalidOperationException(SR.Get(SRID.FlowDocumentFormattingReentrancy));
}
if (_document.StructuralCache.IsContentChangeInProgress)
{
throw new InvalidOperationException(SR.Get(SRID.TextContainerChangingReentrancyInvalid));
}
// Disable processing of the queue during blocking operations to prevent unrelated reentrancy.
using (_document.Dispatcher.DisableProcessing())
{
_document.StructuralCache.IsFormattingInProgress = true; // Set reentrancy flag.
pageNumber = 0;
try
{
while (!_brt.GetPageNumberForContentPosition(flowContentPosition, ref pageNumber))
{
// If failed to get PageNumber and BreakRecordTable is clean,
// the input ContentPosition does not belong to the content.
// But according to check above, it does belong to the content.
// Break and return -1 in this case
if (_brt.IsClean)
{
pageNumber = -1;
break;
}
// Do synchronous pagination for the next missing page number.
FormatPage(pageNumber);
}
}
finally
{
_document.StructuralCache.IsFormattingInProgress = false; // Clear reentrancy flag.
}
}
return pageNumber;
}
///
/// Returns the ContentPosition for the given page.
///
/// Document page.
/// Returns the ContentPosition for the given page.
///
/// Throws ArgumentException if the page is not valid.
///
public override ContentPosition GetPagePosition(DocumentPage page)
{
FlowDocumentPage flowDocumentPage;
ITextView textView;
ITextPointer position;
Point point, newPoint;
MatrixTransform transform;
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
// ContentPosition cannot be null.
if (page == null)
{
throw new ArgumentNullException("page");
}
// DocumentPage must be of appropriate type.
flowDocumentPage = page as FlowDocumentPage;
if (flowDocumentPage == null || flowDocumentPage.IsDisposed)
{
return ContentPosition.Missing;
}
// DocumentPage.Visual for printing scenarions needs to be always returned
// in LeftToRight FlowDirection. Hence, if the document is RightToLeft,
// mirroring transform need to be applied to the content of DocumentPage.Visual.
point = new Point(0, 0);
if (_document.FlowDirection == FlowDirection.RightToLeft)
{
transform = new MatrixTransform(-1.0, 0.0, 0.0, 1.0, flowDocumentPage.Size.Width, 0.0);
transform.TryTransform(point, out newPoint);
point = newPoint;
}
// Get TextView for DocumentPage. Position of the page is calculated through hittesting
// the top-left of the page. If position cannot be found, the start position of
// the first range for TextView is treated as ContentPosition for the page.
textView = (ITextView)((IServiceProvider)flowDocumentPage).GetService(typeof(ITextView));
Invariant.Assert(textView != null, "Cannot access ITextView for FlowDocumentPage.");
Invariant.Assert(textView.TextSegments.Count > 0, "Page cannot be empty.");
position = textView.GetTextPositionFromPoint(point, true);
if (position == null)
{
position = textView.TextSegments[0].Start;
}
return (position is TextPointer) ? (ContentPosition)position : ContentPosition.Missing;
}
///
/// Returns the ContentPosition for an object within the content.
///
/// Object within this element's tree.
/// Returns the ContentPosition for an object within the content.
///
/// Throws ArgumentException if the object does not exist within this element's tree.
///
public override ContentPosition GetObjectPosition(Object o)
{
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
// Object cannot be null.
if (o == null)
{
throw new ArgumentNullException("o");
}
return _document.GetObjectPosition(o);
}
///
/// Cancels all asynchronous calls made with the given userState.
/// If userState is null, all asynchronous calls are cancelled.
///
/// Unique identifier for the asynchronous task.
public override void CancelAsync(object userState)
{
if(userState == null)
{
CancelAllAsyncOperations();
}
else
{
for(int index = 0; index < _asyncRequests.Count; index++)
{
AsyncRequest asyncRequest = _asyncRequests[index];
if(asyncRequest.UserState == userState)
{
asyncRequest.Cancel();
_asyncRequests.RemoveAt(index);
index--;
}
}
}
}
#endregion Public Methods
//--------------------------------------------------------------------
//
// Public Properties
//
//--------------------------------------------------------------------
#region Public Properties
///
/// Whether PageCount is currently valid. If False, then the value of
/// PageCount is the number of pages that have currently been formatted.
///
///
/// This value may revert to False after being True, in cases where
/// PageSize or content changes, forcing a repagination.
///
public override bool IsPageCountValid
{
get
{
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
return _brt.IsClean;
}
}
///
/// If IsPageCountValid is True, this value is the number of pages
/// of content. If False, this is the number of pages that have
/// currently been formatted.
///
///
/// Value may change depending upon changes in PageSize or content changes.
///
public override int PageCount
{
get
{
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
return _brt.Count;
}
}
///
/// The suggested size for formatting pages.
///
///
/// Note that the paginator may override the specified page size. Users
/// should check DocumentPage.Size.
///
public override Size PageSize
{
get
{
return _pageSize;
}
set
{
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
Size newPageSize = value;
if (DoubleUtil.IsNaN(newPageSize.Width))
{
newPageSize.Width = _defaultPageSize.Width;
}
if (DoubleUtil.IsNaN(newPageSize.Height))
{
newPageSize.Height = _defaultPageSize.Height;
}
Size oldActualSize = ComputePageSize();
_pageSize = newPageSize;
Size newActualSize = ComputePageSize();
if (!DoubleUtil.AreClose(oldActualSize, newActualSize))
{
// Detect invalid content change operations.
if (_document.StructuralCache.IsFormattingInProgress)
{
_document.StructuralCache.OnInvalidOperationDetected();
throw new InvalidOperationException(SR.Get(SRID.FlowDocumentInvalidContnetChange));
}
// Any change of page metrics invalidates entire break record table.
// Hence page metrics change is treated in the same way as ContentChanged
// spanning entire content.
// NOTE: May execute external code, so it is possible to get
// an exception here.
InvalidateBRT();
}
}
}
///
/// Whether content is paginated in the background.
/// When True, the Paginator will paginate its content in the background,
/// firing the PaginationCompleted and PaginationProgress events as appropriate.
/// Background pagination begins immediately when set to True. If the
/// PageSize is modified and this property is set to True, then all pages
/// will be repaginated and existing pages may be destroyed.
/// The default value is False.
///
public override bool IsBackgroundPaginationEnabled
{
get
{
return _backgroundPagination;
}
set
{
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
if (value != _backgroundPagination)
{
_backgroundPagination = value;
InitiateNextAsyncOperation();
}
if(!_backgroundPagination)
{
CancelAllAsyncOperations();
}
}
}
///
///
///
public override IDocumentPaginatorSource Source
{
get { return _document; }
}
#endregion Public Properties
//-------------------------------------------------------------------
//
// Internal Methods
//
//--------------------------------------------------------------------
#region Internal Methods
///
/// Initiate the next async operation.
///
internal void InitiateNextAsyncOperation()
{
// Do background pagination if it is enabled and BreakRecordTable is not clean or async requests are pending
if (_backgroundPagination && _backgroundPaginationOperation == null && (!_brt.IsClean || _asyncRequests.Count > 0))
{
_backgroundPaginationOperation = _document.Dispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(OnBackgroundPagination), this);
}
}
///
/// Cancels all pending async operations.
///
internal void CancelAllAsyncOperations()
{
for(int index = 0; index < _asyncRequests.Count; index++)
{
_asyncRequests[index].Cancel();
}
_asyncRequests.Clear();
}
///
/// Raise PagesChanged event.
///
internal void OnPagesChanged(int pageStart, int pageCount)
{
OnPagesChanged(new PagesChangedEventArgs(pageStart, pageCount));
}
///
/// Asynchronously raise PaginationProgress event.
///
///
///
internal void OnPaginationProgress(int pageStart, int pageCount)
{
OnPaginationProgress(new PaginationProgressEventArgs(pageStart, pageCount));
}
///
/// Asynchronously raise PaginationCompleted event.
///
internal void OnPaginationCompleted()
{
OnPaginationCompleted(EventArgs.Empty);
}
#endregion Internal Methods
#region Internal Events
///
/// Fired when all break records in the BreakRecordTable are invalidated.
///
internal event BreakRecordTableInvalidatedEventHandler BreakRecordTableInvalidated;
#endregion Internal Events
//-------------------------------------------------------------------
//
// Private Methods
//
//-------------------------------------------------------------------
#region Private Methods
///
/// Invalidates the content of the entire break record table.
///
private void InvalidateBRT()
{
if(BreakRecordTableInvalidated != null)
{
BreakRecordTableInvalidated(this, EventArgs.Empty);
}
_brt.OnInvalidateLayout();
}
///
/// Invalidates a subset of the break record table, no event is fired
///
private void InvalidateBRTLayout(ITextPointer start, ITextPointer end)
{
_brt.OnInvalidateLayout(start, end);
}
///
/// Format pages up to a page identified by the pageNumber parameter.
///
private DocumentPage FormatPagesTill(int pageNumber)
{
// Pre-calculate all BreakRecords up to the point where the input
// BreakRecord for specified pageNumber is available.
// Stop when required BreakRecord is available or entire BreakRecordTable is clean.
while (!_brt.HasPageBreakRecord(pageNumber) && !_brt.IsClean)
{
// Get the first invalid entry in the BreakRecordTable, calculate
// BreakRecord for it and update BreakRecordTable with the calculated
// value.
FormatPage(_brt.Count);
}
// If entire BreakRecordTable is clean, the page is not available.
if (_brt.IsClean)
{
return DocumentPage.Missing;
}
// The input BreakRecord for the specified page number is already available.
// Format the requested page.
return FormatPage(pageNumber);
}
///
/// Format the page identified by the pageNumber parameter.
///
private DocumentPage FormatPage(int pageNumber)
{
FlowDocumentPage page;
PageBreakRecord breakRecordIn, breakRecordOut;
Thickness pageMargin;
Size pageSize;
Invariant.Assert(_brt.HasPageBreakRecord(pageNumber), "BreakRecord for specified page number does not exist.");
breakRecordIn = _brt.GetPageBreakRecord(pageNumber);
page = new FlowDocumentPage(_document.StructuralCache);
pageSize = ComputePageSize();
pageMargin = _document.ComputePageMargin();
breakRecordOut = page.FormatFinite(pageSize, pageMargin, breakRecordIn);
page.Arrange(pageSize);
// NOTE: May execute external code, so it is possible to get
// an exception here.
_brt.UpdateEntry(pageNumber, page, breakRecordOut, page.DependentMax);
return page;
}
///
/// Partially fill out BreakRecordTable by pre-calculating BreakRecords.
/// This callback is invoked when background pagination is enabled and
/// BreakRecordTable is not completely updated yet.
///
private object OnBackgroundPagination(object arg)
{
DateTime dtStart = DateTime.Now;
DateTime dtStop;
_backgroundPaginationOperation = null; // Clear out pending request.
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
// Detect reentrancy.
if (_document.StructuralCache.IsFormattingInProgress)
{
throw new InvalidOperationException(SR.Get(SRID.FlowDocumentFormattingReentrancy));
}
// Ignore this formatting request, if the element was already disposed.
if (_document.StructuralCache.PtsContext.Disposed)
{
return null;
}
// Disable processing of the queue during blocking operations to prevent unrelated reentrancy.
using (_document.Dispatcher.DisableProcessing())
{
_document.StructuralCache.IsFormattingInProgress = true; // Set reentrancy flag
try
{
for(int index = 0; index < _asyncRequests.Count; index++)
{
AsyncRequest asyncRequest = _asyncRequests[index];
if(asyncRequest.Process())
{
_asyncRequests.RemoveAt(index);
index--; // Offset the index add
}
}
dtStop = DateTime.Now;
if (_backgroundPagination && !_brt.IsClean)
{
// Calculate BreakRecords until entire content is calculated or
// specific time span has been exceeded (_paginationTimeout).
while (!_brt.IsClean)
{
// Get the first invalid entry in the BreakRecordTable, calculate
// BreakRecord for it and update BreakRecordTable with the calculated
// value.
FormatPage(_brt.Count);
// Update time span.
dtStop = DateTime.Now;
long timeSpan = (dtStop.Ticks - dtStart.Ticks) / TimeSpan.TicksPerMillisecond;
if(timeSpan > _paginationTimeout)
{
break;
}
}
// Initiate the next async operation.
InitiateNextAsyncOperation();
}
}
finally
{
_document.StructuralCache.IsFormattingInProgress = false; // Clear reentrancy flag.
}
}
return null;
}
///
/// Compute size for the page.
///
private Size ComputePageSize()
{
double max, min;
Size pageSize = new Size(_document.PageWidth, _document.PageHeight);
if (DoubleUtil.IsNaN(pageSize.Width))
{
pageSize.Width = _pageSize.Width;
max = _document.MaxPageWidth;
if (pageSize.Width > max)
{
pageSize.Width = max;
}
min = _document.MinPageWidth;
if (pageSize.Width < min)
{
pageSize.Width = min;
}
}
if (DoubleUtil.IsNaN(pageSize.Height))
{
pageSize.Height = _pageSize.Height;
max = _document.MaxPageHeight;
if (pageSize.Height > max)
{
pageSize.Height = max;
}
min = _document.MinPageHeight;
if (pageSize.Height < min)
{
pageSize.Height = min;
}
}
return pageSize;
}
#endregion Private Methods
//-------------------------------------------------------------------
//
// Private Fields
//
//--------------------------------------------------------------------
#region Private Fields
///
/// FlowDocument associated with the paginator.
///
private readonly FlowDocument _document;
///
/// Provides mechanism to ensure usage from just one Dispatcher.
///
private readonly CustomDispatcherObject _dispatcherObject;
///
/// BreakRecordTable
///
private readonly BreakRecordTable _brt;
///
/// Page size.
///
private Size _pageSize;
///
/// Whether content is paginated in the background.
///
private bool _backgroundPagination;
///
/// Pagination timeout time.
///
private const int _paginationTimeout = 30;
///
/// Default page size if none is specified.
///
private static Size _defaultPageSize = new Size(8.5d * 96d, 11.0d * 96d);
///
/// Async request list
///
List _asyncRequests = new List(0);
///
/// Background pagination dispatcher operation.
///
DispatcherOperation _backgroundPaginationOperation;
#endregion Private Fields
#region Private Classes
///
/// Base class for all async requests.
///
private abstract class AsyncRequest
{
internal AsyncRequest(object userState, FlowDocumentPaginator paginator)
{
UserState = userState;
Paginator = paginator;
}
///
/// Cancels this async request, responsible for firing appropriate events.
///
internal abstract void Cancel();
///
/// Processes this request. Returns true if processing completed. Responsible for firing appropriate events.
///
internal abstract bool Process();
///
/// User state - Needs to be internal for cancel.
///
internal readonly object UserState;
protected readonly FlowDocumentPaginator Paginator;
}
///
/// GetPage async request.
///
private class GetPageAsyncRequest : AsyncRequest
{
internal GetPageAsyncRequest(int pageNumber, object userState, FlowDocumentPaginator paginator) : base(userState, paginator)
{
PageNumber = pageNumber;
}
///
///
///
internal override void Cancel()
{
Paginator.OnGetPageCompleted(new GetPageCompletedEventArgs(null, PageNumber, null, true, UserState));
}
///
///
///
internal override bool Process()
{
if(!Paginator._brt.HasPageBreakRecord(PageNumber))
{
return false;
}
DocumentPage page = Paginator.FormatPage(PageNumber);
Paginator.OnGetPageCompleted(new GetPageCompletedEventArgs(page, PageNumber, null, false, UserState));
return true;
}
internal readonly int PageNumber;
}
///
/// GetPageNumber async request.
///
private class GetPageNumberAsyncRequest : AsyncRequest
{
internal GetPageNumberAsyncRequest(TextPointer textPointer, object userState, FlowDocumentPaginator paginator) : base(userState, paginator)
{
TextPointer = textPointer;
}
///
///
///
internal override void Cancel()
{
Paginator.OnGetPageNumberCompleted(new GetPageNumberCompletedEventArgs(TextPointer, -1, null, true, UserState));
}
///
///
///
internal override bool Process()
{
int pageNumber = 0;
if(!Paginator._brt.GetPageNumberForContentPosition(TextPointer, ref pageNumber))
{
return false;
}
Paginator.OnGetPageNumberCompleted(new GetPageNumberCompletedEventArgs(TextPointer, pageNumber, null, false, UserState));
return true;
}
internal readonly TextPointer TextPointer;
}
#endregion Private Classes
//-------------------------------------------------------------------
//
// Private Types
//
//--------------------------------------------------------------------
#region Private Types
///
/// Provides mechanism to ensure usage from just one Dispatcher.
///
private class CustomDispatcherObject : DispatcherObject { }
#endregion Private Types
//--------------------------------------------------------------------
//
// IServiceProvider Members
//
//-------------------------------------------------------------------
#region IServiceProvider Members
///
/// Returns service objects associated with this control.
///
/// Specifies the type of service object to get.
object IServiceProvider.GetService(Type serviceType)
{
return ((IServiceProvider)_document).GetService(serviceType);
}
#endregion IServiceProvider Members
//--------------------------------------------------------------------
//
// IFlowDocumentFormatter Members
//
//-------------------------------------------------------------------
#region IFlowDocumentFormatter Members
///
/// Responds to change affecting entire content of associated FlowDocument.
///
/// Whether change affects layout.
void IFlowDocumentFormatter.OnContentInvalidated(bool affectsLayout)
{
if (affectsLayout)
{
InvalidateBRT();
}
else
{
_brt.OnInvalidateRender();
}
}
///
/// Responds to change affecting entire content of associated FlowDocument.
///
/// Whether change affects layout.
/// Start of the affected content range.
/// End of the affected content range.
void IFlowDocumentFormatter.OnContentInvalidated(bool affectsLayout, ITextPointer start, ITextPointer end)
{
if (affectsLayout)
{
InvalidateBRTLayout(start, end);
}
else
{
_brt.OnInvalidateRender(start, end);
}
}
///
/// Suspend formatting.
///
void IFlowDocumentFormatter.Suspend()
{
IsBackgroundPaginationEnabled = false;
InvalidateBRT();
}
///
/// Is layout data in a valid state.
///
bool IFlowDocumentFormatter.IsLayoutDataValid
{
get
{
return !_document.StructuralCache.IsContentChangeInProgress;
}
}
#endregion IFlowDocumentFormatter Members
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
// File: FlowDocumentPaginator.cs
//
// Description: DynamicDocumentPaginator associated with FlowDocument.
//
// History:
// 08/29/2005 : [....] - created.
//
//---------------------------------------------------------------------------
using System; // Object
using System.Collections.Generic; // List
using System.ComponentModel; // AsyncCompletedEventArgs
using System.Windows; // Size
using System.Windows.Documents; // DocumentPaginator
using System.Windows.Media; // Visual
using System.Windows.Threading; // DispatcherOperationCallback
using MS.Internal.PtsHost; // BreakRecordTable, FlowDocumentPage
using MS.Internal.Text; // DynamicPropertyReader
namespace MS.Internal.Documents
{
///
/// Delegate indicating when entire break record has been invalidated.
///
internal delegate void BreakRecordTableInvalidatedEventHandler(object sender, EventArgs e);
///
/// DynamicDocumentPaginator associated with FlowDocument.
///
internal class FlowDocumentPaginator : DynamicDocumentPaginator, IServiceProvider, IFlowDocumentFormatter
{
//-------------------------------------------------------------------
//
// Constructors
//
//-------------------------------------------------------------------
#region Constructors
///
/// Constructor
///
internal FlowDocumentPaginator(FlowDocument document)
{
_pageSize = _defaultPageSize;
_document = document;
_brt = new BreakRecordTable(this);
_dispatcherObject = new CustomDispatcherObject();
// Background pagination by default is enabled.
_backgroundPagination = true;
InitiateNextAsyncOperation();
}
#endregion Constructors
//--------------------------------------------------------------------
//
// Public Methods
//
//-------------------------------------------------------------------
#region Public Methods
///
/// Async version of
///
/// Page number.
/// Unique identifier for the asynchronous task.
///
/// Throws ArgumentOutOfRangeException if PageNumber is negative.
///
public override void GetPageAsync(int pageNumber, object userState)
{
// Page number cannot be negative.
if (pageNumber < 0)
{
throw new ArgumentOutOfRangeException("pageNumber", SR.Get(SRID.IDPNegativePageNumber));
}
// Reentrancy check.
if (_document.StructuralCache.IsFormattingInProgress)
{
throw new InvalidOperationException(SR.Get(SRID.FlowDocumentFormattingReentrancy));
}
if (_document.StructuralCache.IsContentChangeInProgress)
{
throw new InvalidOperationException(SR.Get(SRID.TextContainerChangingReentrancyInvalid));
}
DocumentPage page = null;
if(!_backgroundPagination)
{
page = GetPage(pageNumber);
}
else
{
// If entire content has been already pre-paginated (BreakRecordTable is clean)
// and requesting non-existing page number, return DocumentPage.Missing.
if (_brt.IsClean && !_brt.HasPageBreakRecord(pageNumber))
{
page = DocumentPage.Missing;
}
if(_brt.HasPageBreakRecord(pageNumber))
{
page = GetPage(pageNumber);
}
if(page == null)
{
_asyncRequests.Add(new GetPageAsyncRequest(pageNumber, userState, this));
InitiateNextAsyncOperation();
}
}
if(page != null)
{
OnGetPageCompleted(new GetPageCompletedEventArgs(page, pageNumber, null, false, userState));
}
}
///
/// Retrieves the DocumentPage for the given page number. PageNumber
/// is zero-based.
///
/// Page number.
///
/// Returns DocumentPage.Missing if the given page does not exist.
///
///
/// Multiple requests for the same page number may return the same
/// object (this is implementation specific).
///
///
/// Throws ArgumentOutOfRangeException if PageNumber is negative.
///
public override DocumentPage GetPage(int pageNumber)
{
DocumentPage page;
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
// Page number cannot be negative.
if (pageNumber < 0)
{
throw new ArgumentOutOfRangeException("pageNumber", SR.Get(SRID.IDPNegativePageNumber));
}
// Reentrancy check.
if (_document.StructuralCache.IsFormattingInProgress)
{
throw new InvalidOperationException(SR.Get(SRID.FlowDocumentFormattingReentrancy));
}
if (_document.StructuralCache.IsContentChangeInProgress)
{
throw new InvalidOperationException(SR.Get(SRID.TextContainerChangingReentrancyInvalid));
}
// Disable processing of the queue during blocking operations to prevent unrelated reentrancy.
using (_document.Dispatcher.DisableProcessing())
{
_document.StructuralCache.IsFormattingInProgress = true; // Set reentrancy flag.
try
{
// If entire content has been already pre-paginated (BreakRecordTable is clean)
// and requesting non-existing page number, return DocumentPage.Missing.
if (_brt.IsClean && !_brt.HasPageBreakRecord(pageNumber))
{
page = DocumentPage.Missing;
}
else
{
// If the DocumentPage is cached in the BreakRecordTable, use it.
page = _brt.GetCachedDocumentPage(pageNumber);
if (page == null)
{
// If requested page number does not have pre-calculated BreakRecord,
// do synchronous pagination up to the requested page number.
// [Synchronous pagination is done here, because GetPage is a [....] operation.]
if (!_brt.HasPageBreakRecord(pageNumber))
{
page = FormatPagesTill(pageNumber);
}
// If requested page number does have pre-calculated BreakRecord,
// format a single page.
else
{
page = FormatPage(pageNumber);
}
}
}
}
finally
{
_document.StructuralCache.IsFormattingInProgress = false; // Clear reentrancy flag.
}
}
return page;
}
///
/// Async version of
///
/// Content position.
/// Unique identifier for the asynchronous task.
///
/// Throws ArgumentException if the ContentPosition does not exist within
/// this element’s tree.
///
public override void GetPageNumberAsync(ContentPosition contentPosition, object userState)
{
// Content position cannot be null.
if (contentPosition == null)
{
throw new ArgumentNullException("contentPosition");
}
// Content position cannot be Missing.
if (contentPosition == ContentPosition.Missing)
{
throw new ArgumentException(SR.Get(SRID.IDPInvalidContentPosition), "contentPosition");
}
// ContentPosition must be of appropriate type and must be part of
// the content.
TextPointer flowContentPosition = contentPosition as TextPointer;
if (flowContentPosition == null)
{
throw new ArgumentException(SR.Get(SRID.IDPInvalidContentPosition), "contentPosition");
}
if (flowContentPosition.TextContainer != _document.StructuralCache.TextContainer)
{
throw new ArgumentException(SR.Get(SRID.IDPInvalidContentPosition), "contentPosition");
}
int pageNumber = 0;
if(!_backgroundPagination)
{
pageNumber = GetPageNumber(contentPosition);
OnGetPageNumberCompleted(new GetPageNumberCompletedEventArgs(contentPosition, pageNumber, null, false, userState));
}
else
{
if(_brt.GetPageNumberForContentPosition(flowContentPosition, ref pageNumber))
{
OnGetPageNumberCompleted(new GetPageNumberCompletedEventArgs(contentPosition, pageNumber, null, false, userState));
}
else
{
_asyncRequests.Add(new GetPageNumberAsyncRequest(flowContentPosition, userState, this));
InitiateNextAsyncOperation();
}
}
}
///
/// Returns the page number on which the ContentPosition appears.
///
/// Content position.
///
/// Returns the page number on which the ContentPosition appears.
///
///
/// Throws ArgumentException if the ContentPosition does not exist within
/// this element's tree.
///
public override int GetPageNumber(ContentPosition contentPosition)
{
TextPointer flowContentPosition;
int pageNumber;
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
// ContentPosition cannot be null.
if (contentPosition == null)
{
throw new ArgumentNullException("contentPosition");
}
// ContentPosition must be of appropriate type and must be part of
// the content.
flowContentPosition = contentPosition as TextPointer;
if (flowContentPosition == null)
{
throw new ArgumentException(SR.Get(SRID.IDPInvalidContentPosition), "contentPosition");
}
if (flowContentPosition.TextContainer != _document.StructuralCache.TextContainer)
{
throw new ArgumentException(SR.Get(SRID.IDPInvalidContentPosition), "contentPosition");
}
// We are about to perform synchronous pagination, so need to check for
// reentrancy.
if (_document.StructuralCache.IsFormattingInProgress)
{
throw new InvalidOperationException(SR.Get(SRID.FlowDocumentFormattingReentrancy));
}
if (_document.StructuralCache.IsContentChangeInProgress)
{
throw new InvalidOperationException(SR.Get(SRID.TextContainerChangingReentrancyInvalid));
}
// Disable processing of the queue during blocking operations to prevent unrelated reentrancy.
using (_document.Dispatcher.DisableProcessing())
{
_document.StructuralCache.IsFormattingInProgress = true; // Set reentrancy flag.
pageNumber = 0;
try
{
while (!_brt.GetPageNumberForContentPosition(flowContentPosition, ref pageNumber))
{
// If failed to get PageNumber and BreakRecordTable is clean,
// the input ContentPosition does not belong to the content.
// But according to check above, it does belong to the content.
// Break and return -1 in this case
if (_brt.IsClean)
{
pageNumber = -1;
break;
}
// Do synchronous pagination for the next missing page number.
FormatPage(pageNumber);
}
}
finally
{
_document.StructuralCache.IsFormattingInProgress = false; // Clear reentrancy flag.
}
}
return pageNumber;
}
///
/// Returns the ContentPosition for the given page.
///
/// Document page.
/// Returns the ContentPosition for the given page.
///
/// Throws ArgumentException if the page is not valid.
///
public override ContentPosition GetPagePosition(DocumentPage page)
{
FlowDocumentPage flowDocumentPage;
ITextView textView;
ITextPointer position;
Point point, newPoint;
MatrixTransform transform;
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
// ContentPosition cannot be null.
if (page == null)
{
throw new ArgumentNullException("page");
}
// DocumentPage must be of appropriate type.
flowDocumentPage = page as FlowDocumentPage;
if (flowDocumentPage == null || flowDocumentPage.IsDisposed)
{
return ContentPosition.Missing;
}
// DocumentPage.Visual for printing scenarions needs to be always returned
// in LeftToRight FlowDirection. Hence, if the document is RightToLeft,
// mirroring transform need to be applied to the content of DocumentPage.Visual.
point = new Point(0, 0);
if (_document.FlowDirection == FlowDirection.RightToLeft)
{
transform = new MatrixTransform(-1.0, 0.0, 0.0, 1.0, flowDocumentPage.Size.Width, 0.0);
transform.TryTransform(point, out newPoint);
point = newPoint;
}
// Get TextView for DocumentPage. Position of the page is calculated through hittesting
// the top-left of the page. If position cannot be found, the start position of
// the first range for TextView is treated as ContentPosition for the page.
textView = (ITextView)((IServiceProvider)flowDocumentPage).GetService(typeof(ITextView));
Invariant.Assert(textView != null, "Cannot access ITextView for FlowDocumentPage.");
Invariant.Assert(textView.TextSegments.Count > 0, "Page cannot be empty.");
position = textView.GetTextPositionFromPoint(point, true);
if (position == null)
{
position = textView.TextSegments[0].Start;
}
return (position is TextPointer) ? (ContentPosition)position : ContentPosition.Missing;
}
///
/// Returns the ContentPosition for an object within the content.
///
/// Object within this element's tree.
/// Returns the ContentPosition for an object within the content.
///
/// Throws ArgumentException if the object does not exist within this element's tree.
///
public override ContentPosition GetObjectPosition(Object o)
{
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
// Object cannot be null.
if (o == null)
{
throw new ArgumentNullException("o");
}
return _document.GetObjectPosition(o);
}
///
/// Cancels all asynchronous calls made with the given userState.
/// If userState is null, all asynchronous calls are cancelled.
///
/// Unique identifier for the asynchronous task.
public override void CancelAsync(object userState)
{
if(userState == null)
{
CancelAllAsyncOperations();
}
else
{
for(int index = 0; index < _asyncRequests.Count; index++)
{
AsyncRequest asyncRequest = _asyncRequests[index];
if(asyncRequest.UserState == userState)
{
asyncRequest.Cancel();
_asyncRequests.RemoveAt(index);
index--;
}
}
}
}
#endregion Public Methods
//--------------------------------------------------------------------
//
// Public Properties
//
//--------------------------------------------------------------------
#region Public Properties
///
/// Whether PageCount is currently valid. If False, then the value of
/// PageCount is the number of pages that have currently been formatted.
///
///
/// This value may revert to False after being True, in cases where
/// PageSize or content changes, forcing a repagination.
///
public override bool IsPageCountValid
{
get
{
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
return _brt.IsClean;
}
}
///
/// If IsPageCountValid is True, this value is the number of pages
/// of content. If False, this is the number of pages that have
/// currently been formatted.
///
///
/// Value may change depending upon changes in PageSize or content changes.
///
public override int PageCount
{
get
{
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
return _brt.Count;
}
}
///
/// The suggested size for formatting pages.
///
///
/// Note that the paginator may override the specified page size. Users
/// should check DocumentPage.Size.
///
public override Size PageSize
{
get
{
return _pageSize;
}
set
{
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
Size newPageSize = value;
if (DoubleUtil.IsNaN(newPageSize.Width))
{
newPageSize.Width = _defaultPageSize.Width;
}
if (DoubleUtil.IsNaN(newPageSize.Height))
{
newPageSize.Height = _defaultPageSize.Height;
}
Size oldActualSize = ComputePageSize();
_pageSize = newPageSize;
Size newActualSize = ComputePageSize();
if (!DoubleUtil.AreClose(oldActualSize, newActualSize))
{
// Detect invalid content change operations.
if (_document.StructuralCache.IsFormattingInProgress)
{
_document.StructuralCache.OnInvalidOperationDetected();
throw new InvalidOperationException(SR.Get(SRID.FlowDocumentInvalidContnetChange));
}
// Any change of page metrics invalidates entire break record table.
// Hence page metrics change is treated in the same way as ContentChanged
// spanning entire content.
// NOTE: May execute external code, so it is possible to get
// an exception here.
InvalidateBRT();
}
}
}
///
/// Whether content is paginated in the background.
/// When True, the Paginator will paginate its content in the background,
/// firing the PaginationCompleted and PaginationProgress events as appropriate.
/// Background pagination begins immediately when set to True. If the
/// PageSize is modified and this property is set to True, then all pages
/// will be repaginated and existing pages may be destroyed.
/// The default value is False.
///
public override bool IsBackgroundPaginationEnabled
{
get
{
return _backgroundPagination;
}
set
{
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
if (value != _backgroundPagination)
{
_backgroundPagination = value;
InitiateNextAsyncOperation();
}
if(!_backgroundPagination)
{
CancelAllAsyncOperations();
}
}
}
///
///
///
public override IDocumentPaginatorSource Source
{
get { return _document; }
}
#endregion Public Properties
//-------------------------------------------------------------------
//
// Internal Methods
//
//--------------------------------------------------------------------
#region Internal Methods
///
/// Initiate the next async operation.
///
internal void InitiateNextAsyncOperation()
{
// Do background pagination if it is enabled and BreakRecordTable is not clean or async requests are pending
if (_backgroundPagination && _backgroundPaginationOperation == null && (!_brt.IsClean || _asyncRequests.Count > 0))
{
_backgroundPaginationOperation = _document.Dispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(OnBackgroundPagination), this);
}
}
///
/// Cancels all pending async operations.
///
internal void CancelAllAsyncOperations()
{
for(int index = 0; index < _asyncRequests.Count; index++)
{
_asyncRequests[index].Cancel();
}
_asyncRequests.Clear();
}
///
/// Raise PagesChanged event.
///
internal void OnPagesChanged(int pageStart, int pageCount)
{
OnPagesChanged(new PagesChangedEventArgs(pageStart, pageCount));
}
///
/// Asynchronously raise PaginationProgress event.
///
///
///
internal void OnPaginationProgress(int pageStart, int pageCount)
{
OnPaginationProgress(new PaginationProgressEventArgs(pageStart, pageCount));
}
///
/// Asynchronously raise PaginationCompleted event.
///
internal void OnPaginationCompleted()
{
OnPaginationCompleted(EventArgs.Empty);
}
#endregion Internal Methods
#region Internal Events
///
/// Fired when all break records in the BreakRecordTable are invalidated.
///
internal event BreakRecordTableInvalidatedEventHandler BreakRecordTableInvalidated;
#endregion Internal Events
//-------------------------------------------------------------------
//
// Private Methods
//
//-------------------------------------------------------------------
#region Private Methods
///
/// Invalidates the content of the entire break record table.
///
private void InvalidateBRT()
{
if(BreakRecordTableInvalidated != null)
{
BreakRecordTableInvalidated(this, EventArgs.Empty);
}
_brt.OnInvalidateLayout();
}
///
/// Invalidates a subset of the break record table, no event is fired
///
private void InvalidateBRTLayout(ITextPointer start, ITextPointer end)
{
_brt.OnInvalidateLayout(start, end);
}
///
/// Format pages up to a page identified by the pageNumber parameter.
///
private DocumentPage FormatPagesTill(int pageNumber)
{
// Pre-calculate all BreakRecords up to the point where the input
// BreakRecord for specified pageNumber is available.
// Stop when required BreakRecord is available or entire BreakRecordTable is clean.
while (!_brt.HasPageBreakRecord(pageNumber) && !_brt.IsClean)
{
// Get the first invalid entry in the BreakRecordTable, calculate
// BreakRecord for it and update BreakRecordTable with the calculated
// value.
FormatPage(_brt.Count);
}
// If entire BreakRecordTable is clean, the page is not available.
if (_brt.IsClean)
{
return DocumentPage.Missing;
}
// The input BreakRecord for the specified page number is already available.
// Format the requested page.
return FormatPage(pageNumber);
}
///
/// Format the page identified by the pageNumber parameter.
///
private DocumentPage FormatPage(int pageNumber)
{
FlowDocumentPage page;
PageBreakRecord breakRecordIn, breakRecordOut;
Thickness pageMargin;
Size pageSize;
Invariant.Assert(_brt.HasPageBreakRecord(pageNumber), "BreakRecord for specified page number does not exist.");
breakRecordIn = _brt.GetPageBreakRecord(pageNumber);
page = new FlowDocumentPage(_document.StructuralCache);
pageSize = ComputePageSize();
pageMargin = _document.ComputePageMargin();
breakRecordOut = page.FormatFinite(pageSize, pageMargin, breakRecordIn);
page.Arrange(pageSize);
// NOTE: May execute external code, so it is possible to get
// an exception here.
_brt.UpdateEntry(pageNumber, page, breakRecordOut, page.DependentMax);
return page;
}
///
/// Partially fill out BreakRecordTable by pre-calculating BreakRecords.
/// This callback is invoked when background pagination is enabled and
/// BreakRecordTable is not completely updated yet.
///
private object OnBackgroundPagination(object arg)
{
DateTime dtStart = DateTime.Now;
DateTime dtStop;
_backgroundPaginationOperation = null; // Clear out pending request.
// Ensure usage from just one Dispatcher object.
// FlowDocumentPaginator runs its own layout, hence there is a need
// to protect it from random access from other threads.
_dispatcherObject.VerifyAccess();
// Detect reentrancy.
if (_document.StructuralCache.IsFormattingInProgress)
{
throw new InvalidOperationException(SR.Get(SRID.FlowDocumentFormattingReentrancy));
}
// Ignore this formatting request, if the element was already disposed.
if (_document.StructuralCache.PtsContext.Disposed)
{
return null;
}
// Disable processing of the queue during blocking operations to prevent unrelated reentrancy.
using (_document.Dispatcher.DisableProcessing())
{
_document.StructuralCache.IsFormattingInProgress = true; // Set reentrancy flag
try
{
for(int index = 0; index < _asyncRequests.Count; index++)
{
AsyncRequest asyncRequest = _asyncRequests[index];
if(asyncRequest.Process())
{
_asyncRequests.RemoveAt(index);
index--; // Offset the index add
}
}
dtStop = DateTime.Now;
if (_backgroundPagination && !_brt.IsClean)
{
// Calculate BreakRecords until entire content is calculated or
// specific time span has been exceeded (_paginationTimeout).
while (!_brt.IsClean)
{
// Get the first invalid entry in the BreakRecordTable, calculate
// BreakRecord for it and update BreakRecordTable with the calculated
// value.
FormatPage(_brt.Count);
// Update time span.
dtStop = DateTime.Now;
long timeSpan = (dtStop.Ticks - dtStart.Ticks) / TimeSpan.TicksPerMillisecond;
if(timeSpan > _paginationTimeout)
{
break;
}
}
// Initiate the next async operation.
InitiateNextAsyncOperation();
}
}
finally
{
_document.StructuralCache.IsFormattingInProgress = false; // Clear reentrancy flag.
}
}
return null;
}
///
/// Compute size for the page.
///
private Size ComputePageSize()
{
double max, min;
Size pageSize = new Size(_document.PageWidth, _document.PageHeight);
if (DoubleUtil.IsNaN(pageSize.Width))
{
pageSize.Width = _pageSize.Width;
max = _document.MaxPageWidth;
if (pageSize.Width > max)
{
pageSize.Width = max;
}
min = _document.MinPageWidth;
if (pageSize.Width < min)
{
pageSize.Width = min;
}
}
if (DoubleUtil.IsNaN(pageSize.Height))
{
pageSize.Height = _pageSize.Height;
max = _document.MaxPageHeight;
if (pageSize.Height > max)
{
pageSize.Height = max;
}
min = _document.MinPageHeight;
if (pageSize.Height < min)
{
pageSize.Height = min;
}
}
return pageSize;
}
#endregion Private Methods
//-------------------------------------------------------------------
//
// Private Fields
//
//--------------------------------------------------------------------
#region Private Fields
///
/// FlowDocument associated with the paginator.
///
private readonly FlowDocument _document;
///
/// Provides mechanism to ensure usage from just one Dispatcher.
///
private readonly CustomDispatcherObject _dispatcherObject;
///
/// BreakRecordTable
///
private readonly BreakRecordTable _brt;
///
/// Page size.
///
private Size _pageSize;
///
/// Whether content is paginated in the background.
///
private bool _backgroundPagination;
///
/// Pagination timeout time.
///
private const int _paginationTimeout = 30;
///
/// Default page size if none is specified.
///
private static Size _defaultPageSize = new Size(8.5d * 96d, 11.0d * 96d);
///
/// Async request list
///
List _asyncRequests = new List(0);
///
/// Background pagination dispatcher operation.
///
DispatcherOperation _backgroundPaginationOperation;
#endregion Private Fields
#region Private Classes
///
/// Base class for all async requests.
///
private abstract class AsyncRequest
{
internal AsyncRequest(object userState, FlowDocumentPaginator paginator)
{
UserState = userState;
Paginator = paginator;
}
///
/// Cancels this async request, responsible for firing appropriate events.
///
internal abstract void Cancel();
///
/// Processes this request. Returns true if processing completed. Responsible for firing appropriate events.
///
internal abstract bool Process();
///
/// User state - Needs to be internal for cancel.
///
internal readonly object UserState;
protected readonly FlowDocumentPaginator Paginator;
}
///
/// GetPage async request.
///
private class GetPageAsyncRequest : AsyncRequest
{
internal GetPageAsyncRequest(int pageNumber, object userState, FlowDocumentPaginator paginator) : base(userState, paginator)
{
PageNumber = pageNumber;
}
///
///
///
internal override void Cancel()
{
Paginator.OnGetPageCompleted(new GetPageCompletedEventArgs(null, PageNumber, null, true, UserState));
}
///
///
///
internal override bool Process()
{
if(!Paginator._brt.HasPageBreakRecord(PageNumber))
{
return false;
}
DocumentPage page = Paginator.FormatPage(PageNumber);
Paginator.OnGetPageCompleted(new GetPageCompletedEventArgs(page, PageNumber, null, false, UserState));
return true;
}
internal readonly int PageNumber;
}
///
/// GetPageNumber async request.
///
private class GetPageNumberAsyncRequest : AsyncRequest
{
internal GetPageNumberAsyncRequest(TextPointer textPointer, object userState, FlowDocumentPaginator paginator) : base(userState, paginator)
{
TextPointer = textPointer;
}
///
///
///
internal override void Cancel()
{
Paginator.OnGetPageNumberCompleted(new GetPageNumberCompletedEventArgs(TextPointer, -1, null, true, UserState));
}
///
///
///
internal override bool Process()
{
int pageNumber = 0;
if(!Paginator._brt.GetPageNumberForContentPosition(TextPointer, ref pageNumber))
{
return false;
}
Paginator.OnGetPageNumberCompleted(new GetPageNumberCompletedEventArgs(TextPointer, pageNumber, null, false, UserState));
return true;
}
internal readonly TextPointer TextPointer;
}
#endregion Private Classes
//-------------------------------------------------------------------
//
// Private Types
//
//--------------------------------------------------------------------
#region Private Types
///
/// Provides mechanism to ensure usage from just one Dispatcher.
///
private class CustomDispatcherObject : DispatcherObject { }
#endregion Private Types
//--------------------------------------------------------------------
//
// IServiceProvider Members
//
//-------------------------------------------------------------------
#region IServiceProvider Members
///
/// Returns service objects associated with this control.
///
/// Specifies the type of service object to get.
object IServiceProvider.GetService(Type serviceType)
{
return ((IServiceProvider)_document).GetService(serviceType);
}
#endregion IServiceProvider Members
//--------------------------------------------------------------------
//
// IFlowDocumentFormatter Members
//
//-------------------------------------------------------------------
#region IFlowDocumentFormatter Members
///
/// Responds to change affecting entire content of associated FlowDocument.
///
/// Whether change affects layout.
void IFlowDocumentFormatter.OnContentInvalidated(bool affectsLayout)
{
if (affectsLayout)
{
InvalidateBRT();
}
else
{
_brt.OnInvalidateRender();
}
}
///
/// Responds to change affecting entire content of associated FlowDocument.
///
/// Whether change affects layout.
/// Start of the affected content range.
/// End of the affected content range.
void IFlowDocumentFormatter.OnContentInvalidated(bool affectsLayout, ITextPointer start, ITextPointer end)
{
if (affectsLayout)
{
InvalidateBRTLayout(start, end);
}
else
{
_brt.OnInvalidateRender(start, end);
}
}
///
/// Suspend formatting.
///
void IFlowDocumentFormatter.Suspend()
{
IsBackgroundPaginationEnabled = false;
InvalidateBRT();
}
///
/// Is layout data in a valid state.
///
bool IFlowDocumentFormatter.IsLayoutDataValid
{
get
{
return !_document.StructuralCache.IsContentChangeInProgress;
}
}
#endregion IFlowDocumentFormatter Members
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- EntityClientCacheEntry.cs
- DataObjectFieldAttribute.cs
- EmptyReadOnlyDictionaryInternal.cs
- HeaderedContentControl.cs
- TypeSystemHelpers.cs
- SemanticAnalyzer.cs
- BidPrivateBase.cs
- BitSet.cs
- MsmqIntegrationSecurityMode.cs
- XmlObjectSerializer.cs
- FollowerQueueCreator.cs
- SimpleWebHandlerParser.cs
- SafeJobHandle.cs
- List.cs
- FileDataSource.cs
- KnownIds.cs
- DefaultValidator.cs
- BitmapEffectDrawing.cs
- CellRelation.cs
- CapabilitiesSection.cs
- HashCodeCombiner.cs
- Propagator.cs
- XmlValidatingReader.cs
- BooleanAnimationBase.cs
- ToolStripRendererSwitcher.cs
- CryptoApi.cs
- PropertyToken.cs
- NavigationExpr.cs
- ComponentResourceKey.cs
- BrowserDefinition.cs
- SafeRegistryHandle.cs
- OutputWindow.cs
- SqlRecordBuffer.cs
- UserMapPath.cs
- HttpListenerPrefixCollection.cs
- DoubleAnimationUsingPath.cs
- cookieexception.cs
- WinHttpWebProxyFinder.cs
- ResetableIterator.cs
- Function.cs
- PropertyKey.cs
- CalloutQueueItem.cs
- SnapLine.cs
- SmtpAuthenticationManager.cs
- controlskin.cs
- SemanticAnalyzer.cs
- ObjectStateEntry.cs
- SqlDependencyUtils.cs
- ClientApiGenerator.cs
- NonNullItemCollection.cs
- GridViewPageEventArgs.cs
- RsaKeyGen.cs
- SafeTimerHandle.cs
- WindowsFormsHostAutomationPeer.cs
- Frame.cs
- WebPartConnectionsCancelEventArgs.cs
- DataGridViewBindingCompleteEventArgs.cs
- StackOverflowException.cs
- SoapParser.cs
- ValidationError.cs
- CTreeGenerator.cs
- XmlSchemaExternal.cs
- PrintPageEvent.cs
- TemplateBindingExtension.cs
- RawStylusInputCustomDataList.cs
- LightweightCodeGenerator.cs
- SpotLight.cs
- OdbcInfoMessageEvent.cs
- InvalidOleVariantTypeException.cs
- DocumentPaginator.cs
- ExpressionBindings.cs
- BehaviorEditorPart.cs
- SoapProtocolReflector.cs
- ComboBox.cs
- RichTextBoxConstants.cs
- PathFigureCollection.cs
- sqlser.cs
- RSAPKCS1KeyExchangeDeformatter.cs
- StaticResourceExtension.cs
- ColorIndependentAnimationStorage.cs
- Typography.cs
- DeflateEmulationStream.cs
- CacheOutputQuery.cs
- StatusBarPanel.cs
- Control.cs
- login.cs
- ConfigurationProperty.cs
- SQLStringStorage.cs
- GridView.cs
- SystemUdpStatistics.cs
- ByteFacetDescriptionElement.cs
- StateFinalizationDesigner.cs
- GridViewAutoFormat.cs
- WindowPattern.cs
- DataGridViewRowHeightInfoPushedEventArgs.cs
- CLRBindingWorker.cs
- DropTarget.cs
- ImmutableCollection.cs
- ReferencedAssembly.cs
- GroupLabel.cs