PageCache.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Framework / MS / Internal / documents / PageCache.cs / 1 / PageCache.cs

                            //---------------------------------------------------------------------------- 
//
// 
//    Copyright (C) Microsoft Corporation.  All rights reserved.
//  
//
// 
// Description: PageCache caches information about individual pages in a document. 
//
// History: 
// 10/21/04 - jdersch created
//
//---------------------------------------------------------------------------
 
using System;
using System.Collections; 
using System.Collections.Generic; 
using System.Diagnostics;
using System.Threading; 
using System.Windows;
using System.Windows.Threading;
using System.Windows.Documents;
 
namespace MS.Internal.Documents
{ 
 
    /// 
    /// PageCache acts as both a page-dimension cache and a proxy for an DocumentPaginator document. 
    /// It doles out pages to DocumentGrid and keeps the cache in [....] with the DocumentPaginator.
    /// 
    /// http://d2/DRX/default.aspx
    internal class PageCache 
    {
        //----------------------------------------------------- 
        // 
        //  Constructors
        // 
        //-----------------------------------------------------

        #region Constructors
 
        /// 
        /// Default constructor for a PageCache object. 
        /// Creates our internal cache, represented as a Generic List. 
        /// 
        public PageCache() 
        {
            //Create the internal representation of our Cache with a default size.
            //This cache is a dynamic List which will expand to accommodate larger documents.
            _cache = new List(_defaultCacheSize); 

            //Create the PageDestroyedWatcher which will keep track of what DocumentPages 
            //have been destroyed. 
            _pageDestroyedWatcher = new PageDestroyedWatcher();
        } 

        #endregion Constructors

        //------------------------------------------------------ 
        //
        //  Public Properties 
        // 
        //-----------------------------------------------------
 
        #region Public Properties

        /// 
        /// The DocumentPaginator Content tree we're interested in caching information about. 
        /// 
        ///  
        public DynamicDocumentPaginator Content 
        {
            set 
            {
                //If the content is actually changing we update our paginator here.
                if (_documentPaginator != value)
                { 
                    //Reset our flags and default page size.
                    _dynamicPageSizes = false; 
                    _defaultPageSize = _initialDefaultPageSize; 
                    _isDefaultSizeKnown = false;
                    _isPaginationCompleted = false; 

                    //If the old DocumentPaginator is non-null, we need to
                    //remove the old event handlers before we assign the new one.
                    if (_documentPaginator != null) 
                    {
                        _documentPaginator.PagesChanged -= new PagesChangedEventHandler(OnPagesChanged); 
                        _documentPaginator.GetPageCompleted -= new GetPageCompletedEventHandler(OnGetPageCompleted); 
                        _documentPaginator.PaginationProgress -= new PaginationProgressEventHandler(OnPaginationProgress);
                        _documentPaginator.PaginationCompleted -= new EventHandler(OnPaginationCompleted); 

                        //Reset the Background Pagination flag to its original state.
                        _documentPaginator.IsBackgroundPaginationEnabled = _originalIsBackgroundPaginationEnabled;
                    } 

                    //Now assign the new paginator. 
                    _documentPaginator = value; 

                    //Clear our cache. 
                    ClearCache();

                    //Attach our event handlers and set relevant properties if the new content is non-null.
                    if (_documentPaginator != null) 
                    {
                        _documentPaginator.PagesChanged += new PagesChangedEventHandler(OnPagesChanged); 
                        _documentPaginator.GetPageCompleted += new GetPageCompletedEventHandler(OnGetPageCompleted); 
                        _documentPaginator.PaginationProgress += new PaginationProgressEventHandler(OnPaginationProgress);
                        _documentPaginator.PaginationCompleted += new EventHandler(OnPaginationCompleted); 

                        //Set the paginator's PageSize so the new content will reflow to fit in the requested space.
                        _documentPaginator.PageSize = _defaultPageSize;
 
                        //We save off the original value so we can restore it when new content is assigned.
                        _originalIsBackgroundPaginationEnabled = _documentPaginator.IsBackgroundPaginationEnabled; 
 
                        //Enable background pagination, and set the paginator's PageSize so the new content will
                        //reflow to fit in the requested space. 
                        _documentPaginator.IsBackgroundPaginationEnabled = true;

                        //Determine content flow direction, if the content has one specified.
                        //We look for the FrameworkElement.FlowDirection property. 
                        //If it doesn't have one then we assume the content is Left-To-Right.
                        //(Note: FlowDirection is a value type and thus cannot be null; 
                        //DependencyObject.GetValue will never return null for this even if the 
                        //DocumentPaginator does not have this property set -- it will just
                        //return the default value for FlowDirectionProperty.) 
                        if (_documentPaginator.Source is DependencyObject)
                        {
                            FlowDirection flowDirection = (FlowDirection)((DependencyObject)_documentPaginator.Source).GetValue(FrameworkElement.FlowDirectionProperty);
                            if (flowDirection == FlowDirection.LeftToRight) 
                            {
                                _isContentRightToLeft = false; 
                            } 
                            else
                            { 
                                _isContentRightToLeft = true;
                            }
                        }
                    } 

                    //If the content is already paginated (as is the case for certain Fixed content) 
                    //we'll call OnPaginationProgress here to let the Cache (and any listeners) know that 
                    //some or all the pages are available.
                    if ( _documentPaginator != null ) 
                    {
                        if (_documentPaginator.PageCount > 0)
                        {
                            OnPaginationProgress(_documentPaginator, 
                                                 new PaginationProgressEventArgs(0, _documentPaginator.PageCount));
                        } 
 
                        if ( _documentPaginator.IsPageCountValid )
                        { 
                            OnPaginationCompleted(_documentPaginator, EventArgs.Empty);
                        }
                    }
                } 
            }
            get 
            { 
                //Just return our current paginator.
                return _documentPaginator; 
            }
        }

        ///  
        /// The number of pages in the cache.
        ///  
        ///  
        public int PageCount
        { 
            get
            {
                return _cache.Count;
            }			 
        }
 
        ///  
        /// Based on current knowledge, reports whether the document consists
        /// entirely of pages with the same dimensions or whether page sizes vary 
        /// (i.e. are 'Dynamic')
        /// 
        /// 
        public bool DynamicPageSizes 
        {
            get 
            { 
                return _dynamicPageSizes;
            } 
        }

        /// 
        /// Indicates whether the content has an RTL flowdirection or not. 
        /// 
        ///  
        public bool IsContentRightToLeft 
        {
            get 
            {
                return _isContentRightToLeft;
            }
        } 

        public bool IsPaginationCompleted 
        { 
            get
            { 
                return _isPaginationCompleted;
            }
        }
 
        #endregion Public Properties
 
        //------------------------------------------------------ 
        //
        //  Public Events 
        //
        //------------------------------------------------------

        #region Public Events 

        ///  
        /// Fired when one ore more pages in the document have been paginated. 
        /// 
        public event PaginationProgressEventHandler PaginationProgress; 

        /// 
        /// Fired when the document is finished paginating.
        ///  
        public event EventHandler PaginationCompleted;
 
        ///  
        /// Fired when one or more pages in the document have changed.
        ///  
        public event PagesChangedEventHandler PagesChanged;

        /// 
        /// Fired when a requested page has been retrieved. 
        /// 
        public event GetPageCompletedEventHandler GetPageCompleted; 
 
        /// 
        /// Fired when one or more entries in the PageCache have been updated for any reason 
        /// (Due to pagination, a new default page size, page retrieval, etc...)
        /// 
        public event PageCacheChangedEventHandler PageCacheChanged;
 
        #endregion Public Events
 
        //----------------------------------------------------- 
        //
        //  Public Methods 
        //
        //------------------------------------------------------

        #region Public Methods 

        ///  
        /// Retrieves the cached size of a page in the document, even if the page is 
        /// marked as "dirty."  Pages are only un-dirtied when GetPage() is called
        /// (that is, as pages are actually retrieved from the IDocumentFormatter). 
        /// 
        /// The pagenumber to return the dimensions of.
        /// The dimensions of the requested page, or (0,0) for nonexistent pages.
        public Size GetPageSize(int pageNumber) 
        {
            if (pageNumber >= 0 && pageNumber < _cache.Count) 
            { 
                Size pageSize = _cache[pageNumber].PageSize;
                Invariant.Assert(pageSize != Size.Empty, "PageCache entry's PageSize is Empty."); 
                return pageSize;
            }
            else
            { 
                return new Size(0, 0);
            } 
        } 

        // ///  
        // /// --- Commenting out this method as it is currently unused. ---
        // /// Retrieves a page from the DocumentPaginator Asynchronously.
        // /// Caller will receive a GetPageCompleted event when the page is actually available,
        // /// and the Cache will be updated to reflect the true page dimensions at that time.		 
        // /// 
        // /// The page to retrieve from the DocumentPaginator 
        // /// Nothing. 
        // public void GetPage(int pageNumber)
        // { 
        //     if (_documentPaginator != null)
        //     {
        //         _documentPaginator.GetPageAsync(pageNumber, (object)pageNumber);
        //     } 
        // }
 
        ///  
        /// Retrieves the "Dirty" bit for the associated page.
        ///  
        /// The page to retrive the Dirty bit for
        /// The dirty bit
        public bool IsPageDirty(int pageNumber)
        { 
            if (pageNumber >= 0 && pageNumber < _cache.Count)
            { 
                return _cache[pageNumber].Dirty; 
            }
            else 
            {
                return true;
            }
        } 

        #endregion Public Methods 
 
        //-----------------------------------------------------
        // 
        //  Private Methods
        //
        //-----------------------------------------------------		
 
        #region Private Methods
 
        ///  
        /// Handler for the OnPaginationProgress event fired by the DocumentPaginator.
        ///  
        /// 
        /// 
        private void OnPaginationProgress(object sender, PaginationProgressEventArgs args)
        { 
            //Since handling the PaginationProgress event entails a bit of work, we'll
            //have our dispatcher call the PaginationProgress event at Normal priority. 
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal, 
                   new DispatcherOperationCallback(PaginationProgressDelegate), args);
        } 

        /// 
        /// Asynchronously handles the PaginationProgress event.
        /// This means that one or more pages have been added to the document, so we 
        /// add any new pages to the cache, mark them as dirty, and fire off our PaginationProgress
        /// event. 
        ///  
        /// 
        ///  
        private object PaginationProgressDelegate(object parameter)
        {
            PaginationProgressEventArgs args = parameter as PaginationProgressEventArgs;
 
            if (args == null)
            { 
                throw new InvalidOperationException("parameter"); 
            }
 
            //Validate incoming parameters.
            ValidatePaginationArgs(args.Start, args.Count);

            if (_isPaginationCompleted) 
            {
                if (args.Start == 0) 
                { 
                    //Since we've started repaginating from the beginning of the document
                    //after pagination was completed, we can't assume we know 
                    //the default page size anymore.
                    _isDefaultSizeKnown = false;
                    _dynamicPageSizes = false;
                } 

                //Reset our IsPaginationCompleted flag since we just got a pagination event. 
                _isPaginationCompleted = false; 
            }
 
            //Check for integer overflow.
            if (args.Start + args.Count < 0)
            {
                throw new ArgumentOutOfRangeException("args"); 
            }
 
            //Create our list of changes.  We allocate space for 2 changes here 
            //as we can have as many as two changes resulting from a Pagination event.
            List changes = new List(2); 
            PageCacheChange change;

            //If we have pages to add or modify, do so now.
            if (args.Count > 0) 
            {
                //If pagination has added new pages onto the end of the document, we 
                //add new entries to our cache. 
                if (args.Start >= _cache.Count)
                { 
                    //Completely new pages, so we add new cache entries
                    change = AddRange(args.Start, args.Count);
                    if (change != null)
                    { 
                        changes.Add(change);
                    } 
                } 
                else
                { 
                    //Pagination has updated some currently existing pages, so we'll
                    //update our entries.
                    if (args.Start + args.Count < _cache.Count)
                    { 
                        //All pre-existing pages, so we just dirty the current cache entries.
                        change = DirtyRange(args.Start, args.Count); 
                        if (change != null) 
                        {
                            changes.Add(change); 
                        }
                    }
                    else
                    { 
                        //Some pre-existing pages, some new.
                        change = DirtyRange(args.Start, _cache.Count - args.Start); 
                        if (change != null) 
                        {
                            changes.Add(change); 
                        }

                        change = AddRange(_cache.Count, args.Count - (_cache.Count - args.Start) + 1);
                        if (change != null) 
                        {
                            changes.Add(change); 
                        } 
                    }
                } 
            }

            //If the document's PageCount is now less than the size of our cache due to repagination
            //we remove the extra entries. 
            int pageCount = _documentPaginator != null ? _documentPaginator.PageCount : 0;
 
            if (pageCount < _cache.Count) 
            {
                change = new PageCacheChange(pageCount, _cache.Count - pageCount, PageCacheChangeType.Remove); 
                changes.Add(change);

                //Remove the pages from the cache.
                _cache.RemoveRange(pageCount, _cache.Count - pageCount); 
            }
 
            //Fire off our PageCacheChanged event. 
            FirePageCacheChangedEvent(changes);
 
            //Fire the PaginationProgress event.
            if (PaginationProgress != null)
            {
                PaginationProgress(this, args); 
            }
 
            return null; 
        }
 
        /// 
        /// Handler for the OnPaginationCompleted event.  Merely fires off our own event.
        /// 
        /// The sender of this event 
        /// The arguments associated with this event
        private void OnPaginationCompleted(object sender, EventArgs args) 
        { 
            //Since handling the PaginationCompleted event entails a bit of work, we'll
            //have our dispatcher call the PaginationCompleted event at Normal priority. 
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal,
                   new DispatcherOperationCallback(PaginationCompletedDelegate), args);
        }
 
        /// 
        /// Asynchronously handles the PaginationCompleted event. 
        ///  
        /// 
        ///  
        private object PaginationCompletedDelegate(object parameter)
        {
            EventArgs args = parameter as EventArgs;
 
            if (args == null)
            { 
                throw new ArgumentOutOfRangeException("parameter"); 
            }
 
            //set our IsPaginationCompleted flag since we're done paginating.
            _isPaginationCompleted = true;

            //Fire the PaginationProgress event. 
            if (PaginationCompleted != null)
            { 
                PaginationCompleted(this, args); 
            }
 
            return null;
        }

        ///  
        /// Handler for the OnPagesChanged event fired by the DocumentPaginator.
        ///  
        ///  
        /// 
        private void OnPagesChanged(object sender, PagesChangedEventArgs args) 
        {
            //Since handling the PagesChanged event entails a bit of work, we'll
            //have our dispatcher call the PagesChangedDelegate at Normal priority.
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal, 
                   new DispatcherOperationCallback(PagesChangedDelegate), args);
 
        } 

        ///  
        /// Asynchronously handles the PagesChanged event.
        /// This means that one or more pages have been invalidated so we
        /// dirty their cache entries and fire off our PagesChanged event.
        ///  
        /// 
        ///  
        private object PagesChangedDelegate(object parameter) 
        {
            PagesChangedEventArgs args = parameter as PagesChangedEventArgs; 

            if (args == null)
            {
                throw new ArgumentOutOfRangeException("parameter"); 
            }
 
            //Validate incoming parameters 
            ValidatePaginationArgs(args.Start, args.Count);
 
            //Start values outside the range of current pages are invalid.
            //if (args.Start >= _cache.Count)
            //{
            //    throw new ArgumentOutOfRangeException("args"); 
            //}
 
            //If the last page specified in the change is out of the range of currently-known 
            //pages we make the assumption that the IDP means to invalidate all pages and so
            //we clip the count into range. 
            //We also take into account integer overflow... if the sum of Start+Count is less than
            //zero then we've overflowed.
            int adjustedCount = args.Count;
            if (args.Start + args.Count >= _cache.Count || 
                args.Start + args.Count < 0)
            { 
                adjustedCount = _cache.Count - args.Start; 
            }
 
            //Create our list of changes.  We can have at most one.
            List changes = new List(1);

            //Now make the change if there is one to make. 
            if (adjustedCount > 0)
            { 
                PageCacheChange change = DirtyRange(args.Start, adjustedCount); 
                if (change != null)
                { 
                    changes.Add(change);
                }

                //Fire off our PageCacheChanged event. 
                FirePageCacheChangedEvent(changes);
            } 
 
            //Fire the PagesChanged event.
            if (PagesChanged != null) 
            {
                PagesChanged(this, args);
            }
 
            return null;
        } 
 
        /// 
        /// Handler for the GetPageCompleted event fired by the DocumentPaginator. 
        /// 
        /// 
        /// 
        private void OnGetPageCompleted(object sender, GetPageCompletedEventArgs args) 
        {
            if (!args.Cancelled && args.Error == null && args.DocumentPage != DocumentPage.Missing) 
            { 
                //Add the page to the Watcher so we can determine if the page has been
                //destroyed in our Delegate. 
                _pageDestroyedWatcher.AddPage(args.DocumentPage);

                //Since handling the GetPageCompleted event entails a bit of work, we'll
                //have our dispatcher call the GetPageCompletedDelegate at Normal priority. 
                Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal,
                       new DispatcherOperationCallback(GetPageCompletedDelegate), args); 
            } 
        }
 
        /// 
        /// Asynchronously handles the GetPageCompleted event.
        /// This means that a requested page is available, so we
        /// update the page's cache entry, mark it as clean and fire off our 
        /// GetPageCompleted event.
        ///  
        ///  
        /// 
        private object GetPageCompletedDelegate(object parameter) 
        {
            GetPageCompletedEventArgs args = parameter as GetPageCompletedEventArgs;

            if (args == null) 
            {
                throw new ArgumentOutOfRangeException("parameter"); 
            } 

            //Check to see if the page has been destroyed, and remove it from the Watcher. 
            bool pageDestroyed = _pageDestroyedWatcher.IsDestroyed(args.DocumentPage);
            _pageDestroyedWatcher.RemovePage(args.DocumentPage);

            //The page was destroyed, return early. 
            if (pageDestroyed)
            { 
                return null; 
            }
 
            //We only update the entry if the GetPageAsync call was not canceled,
            //points to a valid page (i.e. is not DocumentPage.Missing)
            //and did not result in an Error condition.
            if (!args.Cancelled && args.Error == null && args.DocumentPage != DocumentPage.Missing ) 
            {
 
                if (args.DocumentPage.Size == Size.Empty) 
                {
                    throw new ArgumentOutOfRangeException("args"); 
                }

                //Update the cache.
                PageCacheEntry newEntry; 
                newEntry.PageSize = args.DocumentPage.Size;
                newEntry.Dirty = false; 
 
                //Create our list of changes.  We can have at most two.
                List changes = new List(2); 
                PageCacheChange change;

                //If we add pages such that there's going to be a gap
                //in the cache (for example if we get PaginationProgress events 
                //for pages 1-3 and then 7-10), we need to fill in the gap with entries.
                if (args.PageNumber > _cache.Count - 1) 
                { 
                    //Add the new page (this will cause any pages we
                    //skipped over to be filled in) 
                    change = AddRange(args.PageNumber, 1);
                    if (change != null)
                    {
                        changes.Add(change); 
                    }
 
                    //Update the just-retrieved-page to reflect the actual page size. 
                    change = UpdateEntry(args.PageNumber, newEntry);
                    if (change != null) 
                    {
                        changes.Add(change);
                    }
                } 
                else
                { 
                    //Just update the retrieved page's cache entry. 
                    change = UpdateEntry(args.PageNumber, newEntry);
                    if (change != null) 
                    {
                        changes.Add(change);
                    }
                } 

                //If this page is a different size than the last-retrieved page, then we have a 
                //dynamic document. 
                if (_isDefaultSizeKnown && newEntry.PageSize != _lastPageSize)
                { 
                    _dynamicPageSizes = true;
                }

                _lastPageSize = newEntry.PageSize; 

                //If this is the first page in the document that we've actually retrieved from 
                //the DocumentPaginator, we'll use this page's size as the default size and 
                //update any dirty pages in the cache with this default size.
                //(Technically there should be no non-dirty pages in the document at this point, but 
                //if we want to change our heuristic (for example, use the "most common page size")
                //to define the default then we don't want to clobber known pages.)
                if (!_isDefaultSizeKnown)
                { 
                    _defaultPageSize = newEntry.PageSize;
                    _isDefaultSizeKnown = true; 
 
                    SetDefaultPageSize(true);
                } 

                //Fire off our PageCacheChanged event.
                FirePageCacheChangedEvent(changes);
            } 

            //Fire the GetPageCompleted event. 
            if (GetPageCompleted != null) 
            {
                GetPageCompleted(this, args); 
            }

            return null;
        } 

        ///  
        /// Checks that the start and count parameters passed by a 
        /// Pagination event are valid.
        ///  
        /// 
        /// 
        private void ValidatePaginationArgs(int start, int count)
        { 
            if (start < 0)
            { 
                throw new ArgumentOutOfRangeException("start"); 
            }
 
            if (count <= 0)
            {
                throw new ArgumentOutOfRangeException("count");
            } 
        }
 
        ///  
        /// Updates entries in the cache to have the current default page size.
        ///  
        private void SetDefaultPageSize(bool dirtyOnly)
        {
            //Create our list of changes.  We can potentially have as many
            //changes as there are pages in the document. 
            List changes = new List(PageCount);
 
            Invariant.Assert(_defaultPageSize != Size.Empty, "Default Page Size is Empty."); 

            for (int i = 0; i < _cache.Count; i++) 
            {
                if (_cache[i].Dirty || !dirtyOnly)
                {
                    PageCacheEntry newEntry; 
                    newEntry.PageSize = _defaultPageSize;
                    newEntry.Dirty = true; 
 
                    PageCacheChange change = UpdateEntry(i, newEntry);
                    if (change != null) 
                    {
                        changes.Add(change);
                    }
                } 
            }
 
            //Fire off our PageCacheChanged event. 
            FirePageCacheChangedEvent(changes);
        } 

        /// 
        /// Fires the PageCacheChanged event with the specified changelist.
        ///  
        /// The changes to pass along with the event.
        private void FirePageCacheChangedEvent(List changes) 
        { 
            Debug.Assert(changes != null, "Attempt to fire PageCacheChangedEvent with null change set.");
 
            //Fire off our PageCacheChangedEvent if we have any changes
            if (PageCacheChanged != null && changes != null && changes.Count > 0)
            {
                PageCacheChangedEventArgs args = new PageCacheChangedEventArgs(changes); 
                PageCacheChanged(this, args);
            } 
        } 

        ///  
        /// Adds a range of dirty cache entries to the cache.
        /// 
        /// The starting index for the entries.
        /// The number of entries to add. 
        private PageCacheChange AddRange(int start, int count)
        { 
            //Make sure we're in range. 
            if (start < 0 )
            { 
                throw new ArgumentOutOfRangeException("start");
            }

            if (count < 1) 
            {
                throw new ArgumentOutOfRangeException("count"); 
            } 

            Invariant.Assert(_defaultPageSize != Size.Empty, "Default Page Size is Empty."); 

            //If we add pages such that there's going to be a gap
            //in the cache (for example if we get PaginationProgress events
            //for pages 1-3 and then 7-10), we need to fill in the gap with entries. 
            if( start >= _cache.Count )
            { 
                count += (start - _cache.Count); 
                start = _cache.Count;				
            } 

            //Add the new entries.
            //Each entry is marked as dirty and is assumed to have the default page size.
            for (int i = start; i < start+count; i++) 
            {
                PageCacheEntry newEntry; 
                newEntry.PageSize = _defaultPageSize; 
                newEntry.Dirty = true;
                _cache.Add(newEntry);		 
            }

            return new PageCacheChange(start, count, PageCacheChangeType.Add);
        } 

        ///  
        /// Updates the cache entry at the specified index with a new entry. 
        /// 
        ///  
        /// 
        private PageCacheChange UpdateEntry(int index, PageCacheEntry newEntry)
        {
            //Make sure we're in range. 
            if (index >= _cache.Count || index < 0)
            { 
                throw new ArgumentOutOfRangeException("index"); 
            }
 
            Invariant.Assert(newEntry.PageSize != Size.Empty, "Updated entry newEntry has Empty PageSize.");

            //Check to see if the entry has changed.
            if (newEntry.PageSize != _cache[index].PageSize || 
                newEntry.Dirty != _cache[index].Dirty)
            { 
                //Update the cache entry 
                _cache[index] = newEntry;
 
                return new PageCacheChange(index, 1, PageCacheChangeType.Update);
            }

            return null; 
        }
 
        ///  
        /// Dirties the cache entries for a range of pages that have been
        /// modified.  Adds new entries where necessary. 
        /// 
        /// 
        /// 
        private PageCacheChange DirtyRange(int start, int count) 
        {
            //Make sure we're in range. 
            if( start >= _cache.Count ) 
            {
                throw new ArgumentOutOfRangeException("start"); 
            }

            if (start + count > _cache.Count || count < 1 )
            { 
                throw new ArgumentOutOfRangeException("count");
            } 
 
            Invariant.Assert(_defaultPageSize != Size.Empty, "Default Page Size is Empty.");
 
            for (int i = start; i < start+count; i++)
            {
                //Dirty the pages in the range of invalidated pages.
                //This entails setting the dirty bit and 
                //setting the page size to the default.
                //We'll add new entries if necessary. 
                PageCacheEntry newEntry; 
                newEntry.Dirty = true;
                newEntry.PageSize = _defaultPageSize; 
                _cache[i] = newEntry;									
            }

            return new PageCacheChange(start, count, PageCacheChangeType.Update); 
        }
 
        ///  
        /// Clears out the current cache and lets listeners know of the change.
        ///  
        private void ClearCache()
        {
            if (_cache.Count > 0)
            { 
                //Build our list of changes
                List changes = new List(1); 
                //This change indicates that all of the pages were removed from the cache. 
                PageCacheChange change = new PageCacheChange(0, _cache.Count, PageCacheChangeType.Remove);
                changes.Add(change); 

                //Clear the cache
                _cache.Clear();
 
                //Fire the event.
                FirePageCacheChangedEvent(changes); 
            } 
        }
 


        #endregion Public Methods
 
        //-----------------------------------------------------
        // 
        //  Private Fields 
        //
        //------------------------------------------------------ 

        #region Private Fields

        //The List that contains our cache. 
        private List	_cache;
 
        //The PageDestroyedWatcher that keeps track of the Destroyed-state of DocumentPages. 
        private PageDestroyedWatcher    _pageDestroyedWatcher;
 
        //Our document
        private DynamicDocumentPaginator _documentPaginator;

        //The original state of IDP.IsBackgroundPaginationEnabled 
        private bool                    _originalIsBackgroundPaginationEnabled;
 
        //Whether the page sizes are uniform or not 
        private bool					_dynamicPageSizes;
 
        //Whether our content has a FlowDirection of RTL or not.
        private bool                    _isContentRightToLeft;

        //Whether pagination has finished or is still in progress. 
        private bool                    _isPaginationCompleted;
 
        //Flags related to the "default" size 
        private bool					_isDefaultSizeKnown;
        private Size					_defaultPageSize; 

        //The last page size retrieved from the DocumentPaginator.
        private Size                    _lastPageSize;
 
        //The _default_ default page size.  We don't want the default to be (0,0) because:
        //a) (0,0) is nearly never a valid page size. 
        //b) it causes page layout to end up with all pages initially visible which causes a huge perf hit. 
        //Our initial default page size is 8.5"x11", which is 816x1056 pixels (at 1/96")
        private readonly Size           _initialDefaultPageSize = new Size(816, 1056); 

        //The default size of our List cache.
        private readonly int            _defaultCacheSize = 64;
 
        #endregion Private Fields
    } 
 

    #region PageDestroyedWatcher 

    /// 
    /// PageDestroyedWatcher is used to keep track of whether one or more
    /// DocumentPages have been Destroyed.  This is necessary because 
    /// DocumentPage does not expose a property that indicates whether it has
    /// been destroyed, only a PageDestroyed event.  PageDestroyedWatcher 
    /// listens for this event and keeps track of which DocumentPages have been 
    /// destroyed.
    ///  
    internal class PageDestroyedWatcher
    {
        /// 
        /// Instantiates a new PageDestroyedWatcher 
        /// 
        public PageDestroyedWatcher() 
        { 
            _table = new Hashtable(16);
        } 

        /// 
        /// Adds a new DocumentPage to the Watcher
        ///  
        /// The page to add
        public void AddPage(DocumentPage page) 
        { 
            if (!_table.Contains(page))
            { 
                _table.Add(page, false);
                page.PageDestroyed += new EventHandler(OnPageDestroyed);
            }
            else 
            {
                _table[page] = false; 
            } 
        }
 
        /// 
        /// Removes an existing page from the Watcher
        /// 
        ///  
        public void RemovePage(DocumentPage page)
        { 
            if (_table.Contains(page)) 
            {
                _table.Remove(page); 
                page.PageDestroyed -= new EventHandler(OnPageDestroyed);
            }
        }
 
        /// 
        /// Indicates whether the specified DocumentPage has been destroyed. 
        ///  
        /// 
        ///  
        public bool IsDestroyed(DocumentPage page)
        {
            if (_table.Contains(page))
            { 
                return (bool)_table[page];
            } 
            else 
            {
                return true; 
            }
        }

        ///  
        /// Handles the OnPageDestroyed event and updates the Destroyed state of the pages
        /// associated with this Watcher. 
        ///  
        /// 
        ///  
        private void OnPageDestroyed(object sender, EventArgs e)
        {
            DocumentPage page = sender as DocumentPage;
            Invariant.Assert(page != null, "Invalid type in PageDestroyedWatcher"); 

            if (_table.Contains(page)) 
            { 
                _table[page] = true;
            } 
        }

        //Hashtable to associate Destroyed states with DocumentPages.
        private Hashtable _table; 

    } 
    #endregion PageDestroyedWatcher 

 
    #region PageCacheEntry
    /// 
    /// An entry in our PageCache
    ///  
    internal struct PageCacheEntry
    { 
        ///  
        /// The size of the given page
        ///  
        public Size PageSize;		

        /// 
        /// Whether the above PageSize is up to date. 
        /// 
        public bool Dirty; 
    } 

    #endregion PageCacheEntry 


    #region PageCacheChangedEvent
 
    /// 
    /// PageCacheChanged event handler. 
    ///  
    internal delegate void PageCacheChangedEventHandler(object sender, PageCacheChangedEventArgs e);
 
    /// 
    /// Event arguments for the PageCacheChanged event.
    /// 
    internal class PageCacheChangedEventArgs : EventArgs 
    {
        ///  
        /// Constructor. 
        /// 
        /// The changes corresponding to this event 
        public PageCacheChangedEventArgs(List changes)
        {
            _changes = changes;
        } 

        ///  
        /// The list of changes associated with this event. 
        /// 
        public List Changes 
        {
            get
            {
                return _changes; 
            }
        } 
 
        private readonly List _changes;
    } 


    /// 
    /// Represents a single change to the PageCache 
    /// 
    internal class PageCacheChange 
    { 

        ///  
        /// Constructor.
        /// 
        /// The first page changed.
        /// The number of pages changed. 
        /// The type of changed incurred.
        public PageCacheChange(int start, int count, PageCacheChangeType type) 
        { 
            _start = start;
            _count = count; 
            _type = type;
        }

        ///  
        /// Zero-based page number for this first page that has changed.
        ///  
        public int Start 
        {
            get 
            {
                return _start;
            }
        } 

        ///  
        /// Number of continuous pages changed. 
        /// 
        public int Count 
        {
            get
            {
                return _count; 
            }
        } 
 
        /// 
        /// The type of change that occurred. 
        /// 
        public PageCacheChangeType Type
        {
            get 
            {
                return _type; 
            } 
        }
 
        private readonly int _start;
        private readonly int _count;
        private readonly PageCacheChangeType _type;
    } 

    internal enum PageCacheChangeType 
    { 
        /// 
        /// Pages were added to the cache. 
        /// 
        Add=0,

        ///  
        /// Pages were removed from the cache.
        ///  
        Remove, 

        ///  
        /// Pages in the cache were updated.
        /// 
        Update
    } 

    #endregion PageCacheChangedEvent 
} 

// 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.
//  
//
// 
// Description: PageCache caches information about individual pages in a document. 
//
// History: 
// 10/21/04 - jdersch created
//
//---------------------------------------------------------------------------
 
using System;
using System.Collections; 
using System.Collections.Generic; 
using System.Diagnostics;
using System.Threading; 
using System.Windows;
using System.Windows.Threading;
using System.Windows.Documents;
 
namespace MS.Internal.Documents
{ 
 
    /// 
    /// PageCache acts as both a page-dimension cache and a proxy for an DocumentPaginator document. 
    /// It doles out pages to DocumentGrid and keeps the cache in [....] with the DocumentPaginator.
    /// 
    /// http://d2/DRX/default.aspx
    internal class PageCache 
    {
        //----------------------------------------------------- 
        // 
        //  Constructors
        // 
        //-----------------------------------------------------

        #region Constructors
 
        /// 
        /// Default constructor for a PageCache object. 
        /// Creates our internal cache, represented as a Generic List. 
        /// 
        public PageCache() 
        {
            //Create the internal representation of our Cache with a default size.
            //This cache is a dynamic List which will expand to accommodate larger documents.
            _cache = new List(_defaultCacheSize); 

            //Create the PageDestroyedWatcher which will keep track of what DocumentPages 
            //have been destroyed. 
            _pageDestroyedWatcher = new PageDestroyedWatcher();
        } 

        #endregion Constructors

        //------------------------------------------------------ 
        //
        //  Public Properties 
        // 
        //-----------------------------------------------------
 
        #region Public Properties

        /// 
        /// The DocumentPaginator Content tree we're interested in caching information about. 
        /// 
        ///  
        public DynamicDocumentPaginator Content 
        {
            set 
            {
                //If the content is actually changing we update our paginator here.
                if (_documentPaginator != value)
                { 
                    //Reset our flags and default page size.
                    _dynamicPageSizes = false; 
                    _defaultPageSize = _initialDefaultPageSize; 
                    _isDefaultSizeKnown = false;
                    _isPaginationCompleted = false; 

                    //If the old DocumentPaginator is non-null, we need to
                    //remove the old event handlers before we assign the new one.
                    if (_documentPaginator != null) 
                    {
                        _documentPaginator.PagesChanged -= new PagesChangedEventHandler(OnPagesChanged); 
                        _documentPaginator.GetPageCompleted -= new GetPageCompletedEventHandler(OnGetPageCompleted); 
                        _documentPaginator.PaginationProgress -= new PaginationProgressEventHandler(OnPaginationProgress);
                        _documentPaginator.PaginationCompleted -= new EventHandler(OnPaginationCompleted); 

                        //Reset the Background Pagination flag to its original state.
                        _documentPaginator.IsBackgroundPaginationEnabled = _originalIsBackgroundPaginationEnabled;
                    } 

                    //Now assign the new paginator. 
                    _documentPaginator = value; 

                    //Clear our cache. 
                    ClearCache();

                    //Attach our event handlers and set relevant properties if the new content is non-null.
                    if (_documentPaginator != null) 
                    {
                        _documentPaginator.PagesChanged += new PagesChangedEventHandler(OnPagesChanged); 
                        _documentPaginator.GetPageCompleted += new GetPageCompletedEventHandler(OnGetPageCompleted); 
                        _documentPaginator.PaginationProgress += new PaginationProgressEventHandler(OnPaginationProgress);
                        _documentPaginator.PaginationCompleted += new EventHandler(OnPaginationCompleted); 

                        //Set the paginator's PageSize so the new content will reflow to fit in the requested space.
                        _documentPaginator.PageSize = _defaultPageSize;
 
                        //We save off the original value so we can restore it when new content is assigned.
                        _originalIsBackgroundPaginationEnabled = _documentPaginator.IsBackgroundPaginationEnabled; 
 
                        //Enable background pagination, and set the paginator's PageSize so the new content will
                        //reflow to fit in the requested space. 
                        _documentPaginator.IsBackgroundPaginationEnabled = true;

                        //Determine content flow direction, if the content has one specified.
                        //We look for the FrameworkElement.FlowDirection property. 
                        //If it doesn't have one then we assume the content is Left-To-Right.
                        //(Note: FlowDirection is a value type and thus cannot be null; 
                        //DependencyObject.GetValue will never return null for this even if the 
                        //DocumentPaginator does not have this property set -- it will just
                        //return the default value for FlowDirectionProperty.) 
                        if (_documentPaginator.Source is DependencyObject)
                        {
                            FlowDirection flowDirection = (FlowDirection)((DependencyObject)_documentPaginator.Source).GetValue(FrameworkElement.FlowDirectionProperty);
                            if (flowDirection == FlowDirection.LeftToRight) 
                            {
                                _isContentRightToLeft = false; 
                            } 
                            else
                            { 
                                _isContentRightToLeft = true;
                            }
                        }
                    } 

                    //If the content is already paginated (as is the case for certain Fixed content) 
                    //we'll call OnPaginationProgress here to let the Cache (and any listeners) know that 
                    //some or all the pages are available.
                    if ( _documentPaginator != null ) 
                    {
                        if (_documentPaginator.PageCount > 0)
                        {
                            OnPaginationProgress(_documentPaginator, 
                                                 new PaginationProgressEventArgs(0, _documentPaginator.PageCount));
                        } 
 
                        if ( _documentPaginator.IsPageCountValid )
                        { 
                            OnPaginationCompleted(_documentPaginator, EventArgs.Empty);
                        }
                    }
                } 
            }
            get 
            { 
                //Just return our current paginator.
                return _documentPaginator; 
            }
        }

        ///  
        /// The number of pages in the cache.
        ///  
        ///  
        public int PageCount
        { 
            get
            {
                return _cache.Count;
            }			 
        }
 
        ///  
        /// Based on current knowledge, reports whether the document consists
        /// entirely of pages with the same dimensions or whether page sizes vary 
        /// (i.e. are 'Dynamic')
        /// 
        /// 
        public bool DynamicPageSizes 
        {
            get 
            { 
                return _dynamicPageSizes;
            } 
        }

        /// 
        /// Indicates whether the content has an RTL flowdirection or not. 
        /// 
        ///  
        public bool IsContentRightToLeft 
        {
            get 
            {
                return _isContentRightToLeft;
            }
        } 

        public bool IsPaginationCompleted 
        { 
            get
            { 
                return _isPaginationCompleted;
            }
        }
 
        #endregion Public Properties
 
        //------------------------------------------------------ 
        //
        //  Public Events 
        //
        //------------------------------------------------------

        #region Public Events 

        ///  
        /// Fired when one ore more pages in the document have been paginated. 
        /// 
        public event PaginationProgressEventHandler PaginationProgress; 

        /// 
        /// Fired when the document is finished paginating.
        ///  
        public event EventHandler PaginationCompleted;
 
        ///  
        /// Fired when one or more pages in the document have changed.
        ///  
        public event PagesChangedEventHandler PagesChanged;

        /// 
        /// Fired when a requested page has been retrieved. 
        /// 
        public event GetPageCompletedEventHandler GetPageCompleted; 
 
        /// 
        /// Fired when one or more entries in the PageCache have been updated for any reason 
        /// (Due to pagination, a new default page size, page retrieval, etc...)
        /// 
        public event PageCacheChangedEventHandler PageCacheChanged;
 
        #endregion Public Events
 
        //----------------------------------------------------- 
        //
        //  Public Methods 
        //
        //------------------------------------------------------

        #region Public Methods 

        ///  
        /// Retrieves the cached size of a page in the document, even if the page is 
        /// marked as "dirty."  Pages are only un-dirtied when GetPage() is called
        /// (that is, as pages are actually retrieved from the IDocumentFormatter). 
        /// 
        /// The pagenumber to return the dimensions of.
        /// The dimensions of the requested page, or (0,0) for nonexistent pages.
        public Size GetPageSize(int pageNumber) 
        {
            if (pageNumber >= 0 && pageNumber < _cache.Count) 
            { 
                Size pageSize = _cache[pageNumber].PageSize;
                Invariant.Assert(pageSize != Size.Empty, "PageCache entry's PageSize is Empty."); 
                return pageSize;
            }
            else
            { 
                return new Size(0, 0);
            } 
        } 

        // ///  
        // /// --- Commenting out this method as it is currently unused. ---
        // /// Retrieves a page from the DocumentPaginator Asynchronously.
        // /// Caller will receive a GetPageCompleted event when the page is actually available,
        // /// and the Cache will be updated to reflect the true page dimensions at that time.		 
        // /// 
        // /// The page to retrieve from the DocumentPaginator 
        // /// Nothing. 
        // public void GetPage(int pageNumber)
        // { 
        //     if (_documentPaginator != null)
        //     {
        //         _documentPaginator.GetPageAsync(pageNumber, (object)pageNumber);
        //     } 
        // }
 
        ///  
        /// Retrieves the "Dirty" bit for the associated page.
        ///  
        /// The page to retrive the Dirty bit for
        /// The dirty bit
        public bool IsPageDirty(int pageNumber)
        { 
            if (pageNumber >= 0 && pageNumber < _cache.Count)
            { 
                return _cache[pageNumber].Dirty; 
            }
            else 
            {
                return true;
            }
        } 

        #endregion Public Methods 
 
        //-----------------------------------------------------
        // 
        //  Private Methods
        //
        //-----------------------------------------------------		
 
        #region Private Methods
 
        ///  
        /// Handler for the OnPaginationProgress event fired by the DocumentPaginator.
        ///  
        /// 
        /// 
        private void OnPaginationProgress(object sender, PaginationProgressEventArgs args)
        { 
            //Since handling the PaginationProgress event entails a bit of work, we'll
            //have our dispatcher call the PaginationProgress event at Normal priority. 
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal, 
                   new DispatcherOperationCallback(PaginationProgressDelegate), args);
        } 

        /// 
        /// Asynchronously handles the PaginationProgress event.
        /// This means that one or more pages have been added to the document, so we 
        /// add any new pages to the cache, mark them as dirty, and fire off our PaginationProgress
        /// event. 
        ///  
        /// 
        ///  
        private object PaginationProgressDelegate(object parameter)
        {
            PaginationProgressEventArgs args = parameter as PaginationProgressEventArgs;
 
            if (args == null)
            { 
                throw new InvalidOperationException("parameter"); 
            }
 
            //Validate incoming parameters.
            ValidatePaginationArgs(args.Start, args.Count);

            if (_isPaginationCompleted) 
            {
                if (args.Start == 0) 
                { 
                    //Since we've started repaginating from the beginning of the document
                    //after pagination was completed, we can't assume we know 
                    //the default page size anymore.
                    _isDefaultSizeKnown = false;
                    _dynamicPageSizes = false;
                } 

                //Reset our IsPaginationCompleted flag since we just got a pagination event. 
                _isPaginationCompleted = false; 
            }
 
            //Check for integer overflow.
            if (args.Start + args.Count < 0)
            {
                throw new ArgumentOutOfRangeException("args"); 
            }
 
            //Create our list of changes.  We allocate space for 2 changes here 
            //as we can have as many as two changes resulting from a Pagination event.
            List changes = new List(2); 
            PageCacheChange change;

            //If we have pages to add or modify, do so now.
            if (args.Count > 0) 
            {
                //If pagination has added new pages onto the end of the document, we 
                //add new entries to our cache. 
                if (args.Start >= _cache.Count)
                { 
                    //Completely new pages, so we add new cache entries
                    change = AddRange(args.Start, args.Count);
                    if (change != null)
                    { 
                        changes.Add(change);
                    } 
                } 
                else
                { 
                    //Pagination has updated some currently existing pages, so we'll
                    //update our entries.
                    if (args.Start + args.Count < _cache.Count)
                    { 
                        //All pre-existing pages, so we just dirty the current cache entries.
                        change = DirtyRange(args.Start, args.Count); 
                        if (change != null) 
                        {
                            changes.Add(change); 
                        }
                    }
                    else
                    { 
                        //Some pre-existing pages, some new.
                        change = DirtyRange(args.Start, _cache.Count - args.Start); 
                        if (change != null) 
                        {
                            changes.Add(change); 
                        }

                        change = AddRange(_cache.Count, args.Count - (_cache.Count - args.Start) + 1);
                        if (change != null) 
                        {
                            changes.Add(change); 
                        } 
                    }
                } 
            }

            //If the document's PageCount is now less than the size of our cache due to repagination
            //we remove the extra entries. 
            int pageCount = _documentPaginator != null ? _documentPaginator.PageCount : 0;
 
            if (pageCount < _cache.Count) 
            {
                change = new PageCacheChange(pageCount, _cache.Count - pageCount, PageCacheChangeType.Remove); 
                changes.Add(change);

                //Remove the pages from the cache.
                _cache.RemoveRange(pageCount, _cache.Count - pageCount); 
            }
 
            //Fire off our PageCacheChanged event. 
            FirePageCacheChangedEvent(changes);
 
            //Fire the PaginationProgress event.
            if (PaginationProgress != null)
            {
                PaginationProgress(this, args); 
            }
 
            return null; 
        }
 
        /// 
        /// Handler for the OnPaginationCompleted event.  Merely fires off our own event.
        /// 
        /// The sender of this event 
        /// The arguments associated with this event
        private void OnPaginationCompleted(object sender, EventArgs args) 
        { 
            //Since handling the PaginationCompleted event entails a bit of work, we'll
            //have our dispatcher call the PaginationCompleted event at Normal priority. 
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal,
                   new DispatcherOperationCallback(PaginationCompletedDelegate), args);
        }
 
        /// 
        /// Asynchronously handles the PaginationCompleted event. 
        ///  
        /// 
        ///  
        private object PaginationCompletedDelegate(object parameter)
        {
            EventArgs args = parameter as EventArgs;
 
            if (args == null)
            { 
                throw new ArgumentOutOfRangeException("parameter"); 
            }
 
            //set our IsPaginationCompleted flag since we're done paginating.
            _isPaginationCompleted = true;

            //Fire the PaginationProgress event. 
            if (PaginationCompleted != null)
            { 
                PaginationCompleted(this, args); 
            }
 
            return null;
        }

        ///  
        /// Handler for the OnPagesChanged event fired by the DocumentPaginator.
        ///  
        ///  
        /// 
        private void OnPagesChanged(object sender, PagesChangedEventArgs args) 
        {
            //Since handling the PagesChanged event entails a bit of work, we'll
            //have our dispatcher call the PagesChangedDelegate at Normal priority.
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal, 
                   new DispatcherOperationCallback(PagesChangedDelegate), args);
 
        } 

        ///  
        /// Asynchronously handles the PagesChanged event.
        /// This means that one or more pages have been invalidated so we
        /// dirty their cache entries and fire off our PagesChanged event.
        ///  
        /// 
        ///  
        private object PagesChangedDelegate(object parameter) 
        {
            PagesChangedEventArgs args = parameter as PagesChangedEventArgs; 

            if (args == null)
            {
                throw new ArgumentOutOfRangeException("parameter"); 
            }
 
            //Validate incoming parameters 
            ValidatePaginationArgs(args.Start, args.Count);
 
            //Start values outside the range of current pages are invalid.
            //if (args.Start >= _cache.Count)
            //{
            //    throw new ArgumentOutOfRangeException("args"); 
            //}
 
            //If the last page specified in the change is out of the range of currently-known 
            //pages we make the assumption that the IDP means to invalidate all pages and so
            //we clip the count into range. 
            //We also take into account integer overflow... if the sum of Start+Count is less than
            //zero then we've overflowed.
            int adjustedCount = args.Count;
            if (args.Start + args.Count >= _cache.Count || 
                args.Start + args.Count < 0)
            { 
                adjustedCount = _cache.Count - args.Start; 
            }
 
            //Create our list of changes.  We can have at most one.
            List changes = new List(1);

            //Now make the change if there is one to make. 
            if (adjustedCount > 0)
            { 
                PageCacheChange change = DirtyRange(args.Start, adjustedCount); 
                if (change != null)
                { 
                    changes.Add(change);
                }

                //Fire off our PageCacheChanged event. 
                FirePageCacheChangedEvent(changes);
            } 
 
            //Fire the PagesChanged event.
            if (PagesChanged != null) 
            {
                PagesChanged(this, args);
            }
 
            return null;
        } 
 
        /// 
        /// Handler for the GetPageCompleted event fired by the DocumentPaginator. 
        /// 
        /// 
        /// 
        private void OnGetPageCompleted(object sender, GetPageCompletedEventArgs args) 
        {
            if (!args.Cancelled && args.Error == null && args.DocumentPage != DocumentPage.Missing) 
            { 
                //Add the page to the Watcher so we can determine if the page has been
                //destroyed in our Delegate. 
                _pageDestroyedWatcher.AddPage(args.DocumentPage);

                //Since handling the GetPageCompleted event entails a bit of work, we'll
                //have our dispatcher call the GetPageCompletedDelegate at Normal priority. 
                Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal,
                       new DispatcherOperationCallback(GetPageCompletedDelegate), args); 
            } 
        }
 
        /// 
        /// Asynchronously handles the GetPageCompleted event.
        /// This means that a requested page is available, so we
        /// update the page's cache entry, mark it as clean and fire off our 
        /// GetPageCompleted event.
        ///  
        ///  
        /// 
        private object GetPageCompletedDelegate(object parameter) 
        {
            GetPageCompletedEventArgs args = parameter as GetPageCompletedEventArgs;

            if (args == null) 
            {
                throw new ArgumentOutOfRangeException("parameter"); 
            } 

            //Check to see if the page has been destroyed, and remove it from the Watcher. 
            bool pageDestroyed = _pageDestroyedWatcher.IsDestroyed(args.DocumentPage);
            _pageDestroyedWatcher.RemovePage(args.DocumentPage);

            //The page was destroyed, return early. 
            if (pageDestroyed)
            { 
                return null; 
            }
 
            //We only update the entry if the GetPageAsync call was not canceled,
            //points to a valid page (i.e. is not DocumentPage.Missing)
            //and did not result in an Error condition.
            if (!args.Cancelled && args.Error == null && args.DocumentPage != DocumentPage.Missing ) 
            {
 
                if (args.DocumentPage.Size == Size.Empty) 
                {
                    throw new ArgumentOutOfRangeException("args"); 
                }

                //Update the cache.
                PageCacheEntry newEntry; 
                newEntry.PageSize = args.DocumentPage.Size;
                newEntry.Dirty = false; 
 
                //Create our list of changes.  We can have at most two.
                List changes = new List(2); 
                PageCacheChange change;

                //If we add pages such that there's going to be a gap
                //in the cache (for example if we get PaginationProgress events 
                //for pages 1-3 and then 7-10), we need to fill in the gap with entries.
                if (args.PageNumber > _cache.Count - 1) 
                { 
                    //Add the new page (this will cause any pages we
                    //skipped over to be filled in) 
                    change = AddRange(args.PageNumber, 1);
                    if (change != null)
                    {
                        changes.Add(change); 
                    }
 
                    //Update the just-retrieved-page to reflect the actual page size. 
                    change = UpdateEntry(args.PageNumber, newEntry);
                    if (change != null) 
                    {
                        changes.Add(change);
                    }
                } 
                else
                { 
                    //Just update the retrieved page's cache entry. 
                    change = UpdateEntry(args.PageNumber, newEntry);
                    if (change != null) 
                    {
                        changes.Add(change);
                    }
                } 

                //If this page is a different size than the last-retrieved page, then we have a 
                //dynamic document. 
                if (_isDefaultSizeKnown && newEntry.PageSize != _lastPageSize)
                { 
                    _dynamicPageSizes = true;
                }

                _lastPageSize = newEntry.PageSize; 

                //If this is the first page in the document that we've actually retrieved from 
                //the DocumentPaginator, we'll use this page's size as the default size and 
                //update any dirty pages in the cache with this default size.
                //(Technically there should be no non-dirty pages in the document at this point, but 
                //if we want to change our heuristic (for example, use the "most common page size")
                //to define the default then we don't want to clobber known pages.)
                if (!_isDefaultSizeKnown)
                { 
                    _defaultPageSize = newEntry.PageSize;
                    _isDefaultSizeKnown = true; 
 
                    SetDefaultPageSize(true);
                } 

                //Fire off our PageCacheChanged event.
                FirePageCacheChangedEvent(changes);
            } 

            //Fire the GetPageCompleted event. 
            if (GetPageCompleted != null) 
            {
                GetPageCompleted(this, args); 
            }

            return null;
        } 

        ///  
        /// Checks that the start and count parameters passed by a 
        /// Pagination event are valid.
        ///  
        /// 
        /// 
        private void ValidatePaginationArgs(int start, int count)
        { 
            if (start < 0)
            { 
                throw new ArgumentOutOfRangeException("start"); 
            }
 
            if (count <= 0)
            {
                throw new ArgumentOutOfRangeException("count");
            } 
        }
 
        ///  
        /// Updates entries in the cache to have the current default page size.
        ///  
        private void SetDefaultPageSize(bool dirtyOnly)
        {
            //Create our list of changes.  We can potentially have as many
            //changes as there are pages in the document. 
            List changes = new List(PageCount);
 
            Invariant.Assert(_defaultPageSize != Size.Empty, "Default Page Size is Empty."); 

            for (int i = 0; i < _cache.Count; i++) 
            {
                if (_cache[i].Dirty || !dirtyOnly)
                {
                    PageCacheEntry newEntry; 
                    newEntry.PageSize = _defaultPageSize;
                    newEntry.Dirty = true; 
 
                    PageCacheChange change = UpdateEntry(i, newEntry);
                    if (change != null) 
                    {
                        changes.Add(change);
                    }
                } 
            }
 
            //Fire off our PageCacheChanged event. 
            FirePageCacheChangedEvent(changes);
        } 

        /// 
        /// Fires the PageCacheChanged event with the specified changelist.
        ///  
        /// The changes to pass along with the event.
        private void FirePageCacheChangedEvent(List changes) 
        { 
            Debug.Assert(changes != null, "Attempt to fire PageCacheChangedEvent with null change set.");
 
            //Fire off our PageCacheChangedEvent if we have any changes
            if (PageCacheChanged != null && changes != null && changes.Count > 0)
            {
                PageCacheChangedEventArgs args = new PageCacheChangedEventArgs(changes); 
                PageCacheChanged(this, args);
            } 
        } 

        ///  
        /// Adds a range of dirty cache entries to the cache.
        /// 
        /// The starting index for the entries.
        /// The number of entries to add. 
        private PageCacheChange AddRange(int start, int count)
        { 
            //Make sure we're in range. 
            if (start < 0 )
            { 
                throw new ArgumentOutOfRangeException("start");
            }

            if (count < 1) 
            {
                throw new ArgumentOutOfRangeException("count"); 
            } 

            Invariant.Assert(_defaultPageSize != Size.Empty, "Default Page Size is Empty."); 

            //If we add pages such that there's going to be a gap
            //in the cache (for example if we get PaginationProgress events
            //for pages 1-3 and then 7-10), we need to fill in the gap with entries. 
            if( start >= _cache.Count )
            { 
                count += (start - _cache.Count); 
                start = _cache.Count;				
            } 

            //Add the new entries.
            //Each entry is marked as dirty and is assumed to have the default page size.
            for (int i = start; i < start+count; i++) 
            {
                PageCacheEntry newEntry; 
                newEntry.PageSize = _defaultPageSize; 
                newEntry.Dirty = true;
                _cache.Add(newEntry);		 
            }

            return new PageCacheChange(start, count, PageCacheChangeType.Add);
        } 

        ///  
        /// Updates the cache entry at the specified index with a new entry. 
        /// 
        ///  
        /// 
        private PageCacheChange UpdateEntry(int index, PageCacheEntry newEntry)
        {
            //Make sure we're in range. 
            if (index >= _cache.Count || index < 0)
            { 
                throw new ArgumentOutOfRangeException("index"); 
            }
 
            Invariant.Assert(newEntry.PageSize != Size.Empty, "Updated entry newEntry has Empty PageSize.");

            //Check to see if the entry has changed.
            if (newEntry.PageSize != _cache[index].PageSize || 
                newEntry.Dirty != _cache[index].Dirty)
            { 
                //Update the cache entry 
                _cache[index] = newEntry;
 
                return new PageCacheChange(index, 1, PageCacheChangeType.Update);
            }

            return null; 
        }
 
        ///  
        /// Dirties the cache entries for a range of pages that have been
        /// modified.  Adds new entries where necessary. 
        /// 
        /// 
        /// 
        private PageCacheChange DirtyRange(int start, int count) 
        {
            //Make sure we're in range. 
            if( start >= _cache.Count ) 
            {
                throw new ArgumentOutOfRangeException("start"); 
            }

            if (start + count > _cache.Count || count < 1 )
            { 
                throw new ArgumentOutOfRangeException("count");
            } 
 
            Invariant.Assert(_defaultPageSize != Size.Empty, "Default Page Size is Empty.");
 
            for (int i = start; i < start+count; i++)
            {
                //Dirty the pages in the range of invalidated pages.
                //This entails setting the dirty bit and 
                //setting the page size to the default.
                //We'll add new entries if necessary. 
                PageCacheEntry newEntry; 
                newEntry.Dirty = true;
                newEntry.PageSize = _defaultPageSize; 
                _cache[i] = newEntry;									
            }

            return new PageCacheChange(start, count, PageCacheChangeType.Update); 
        }
 
        ///  
        /// Clears out the current cache and lets listeners know of the change.
        ///  
        private void ClearCache()
        {
            if (_cache.Count > 0)
            { 
                //Build our list of changes
                List changes = new List(1); 
                //This change indicates that all of the pages were removed from the cache. 
                PageCacheChange change = new PageCacheChange(0, _cache.Count, PageCacheChangeType.Remove);
                changes.Add(change); 

                //Clear the cache
                _cache.Clear();
 
                //Fire the event.
                FirePageCacheChangedEvent(changes); 
            } 
        }
 


        #endregion Public Methods
 
        //-----------------------------------------------------
        // 
        //  Private Fields 
        //
        //------------------------------------------------------ 

        #region Private Fields

        //The List that contains our cache. 
        private List	_cache;
 
        //The PageDestroyedWatcher that keeps track of the Destroyed-state of DocumentPages. 
        private PageDestroyedWatcher    _pageDestroyedWatcher;
 
        //Our document
        private DynamicDocumentPaginator _documentPaginator;

        //The original state of IDP.IsBackgroundPaginationEnabled 
        private bool                    _originalIsBackgroundPaginationEnabled;
 
        //Whether the page sizes are uniform or not 
        private bool					_dynamicPageSizes;
 
        //Whether our content has a FlowDirection of RTL or not.
        private bool                    _isContentRightToLeft;

        //Whether pagination has finished or is still in progress. 
        private bool                    _isPaginationCompleted;
 
        //Flags related to the "default" size 
        private bool					_isDefaultSizeKnown;
        private Size					_defaultPageSize; 

        //The last page size retrieved from the DocumentPaginator.
        private Size                    _lastPageSize;
 
        //The _default_ default page size.  We don't want the default to be (0,0) because:
        //a) (0,0) is nearly never a valid page size. 
        //b) it causes page layout to end up with all pages initially visible which causes a huge perf hit. 
        //Our initial default page size is 8.5"x11", which is 816x1056 pixels (at 1/96")
        private readonly Size           _initialDefaultPageSize = new Size(816, 1056); 

        //The default size of our List cache.
        private readonly int            _defaultCacheSize = 64;
 
        #endregion Private Fields
    } 
 

    #region PageDestroyedWatcher 

    /// 
    /// PageDestroyedWatcher is used to keep track of whether one or more
    /// DocumentPages have been Destroyed.  This is necessary because 
    /// DocumentPage does not expose a property that indicates whether it has
    /// been destroyed, only a PageDestroyed event.  PageDestroyedWatcher 
    /// listens for this event and keeps track of which DocumentPages have been 
    /// destroyed.
    ///  
    internal class PageDestroyedWatcher
    {
        /// 
        /// Instantiates a new PageDestroyedWatcher 
        /// 
        public PageDestroyedWatcher() 
        { 
            _table = new Hashtable(16);
        } 

        /// 
        /// Adds a new DocumentPage to the Watcher
        ///  
        /// The page to add
        public void AddPage(DocumentPage page) 
        { 
            if (!_table.Contains(page))
            { 
                _table.Add(page, false);
                page.PageDestroyed += new EventHandler(OnPageDestroyed);
            }
            else 
            {
                _table[page] = false; 
            } 
        }
 
        /// 
        /// Removes an existing page from the Watcher
        /// 
        ///  
        public void RemovePage(DocumentPage page)
        { 
            if (_table.Contains(page)) 
            {
                _table.Remove(page); 
                page.PageDestroyed -= new EventHandler(OnPageDestroyed);
            }
        }
 
        /// 
        /// Indicates whether the specified DocumentPage has been destroyed. 
        ///  
        /// 
        ///  
        public bool IsDestroyed(DocumentPage page)
        {
            if (_table.Contains(page))
            { 
                return (bool)_table[page];
            } 
            else 
            {
                return true; 
            }
        }

        ///  
        /// Handles the OnPageDestroyed event and updates the Destroyed state of the pages
        /// associated with this Watcher. 
        ///  
        /// 
        ///  
        private void OnPageDestroyed(object sender, EventArgs e)
        {
            DocumentPage page = sender as DocumentPage;
            Invariant.Assert(page != null, "Invalid type in PageDestroyedWatcher"); 

            if (_table.Contains(page)) 
            { 
                _table[page] = true;
            } 
        }

        //Hashtable to associate Destroyed states with DocumentPages.
        private Hashtable _table; 

    } 
    #endregion PageDestroyedWatcher 

 
    #region PageCacheEntry
    /// 
    /// An entry in our PageCache
    ///  
    internal struct PageCacheEntry
    { 
        ///  
        /// The size of the given page
        ///  
        public Size PageSize;		

        /// 
        /// Whether the above PageSize is up to date. 
        /// 
        public bool Dirty; 
    } 

    #endregion PageCacheEntry 


    #region PageCacheChangedEvent
 
    /// 
    /// PageCacheChanged event handler. 
    ///  
    internal delegate void PageCacheChangedEventHandler(object sender, PageCacheChangedEventArgs e);
 
    /// 
    /// Event arguments for the PageCacheChanged event.
    /// 
    internal class PageCacheChangedEventArgs : EventArgs 
    {
        ///  
        /// Constructor. 
        /// 
        /// The changes corresponding to this event 
        public PageCacheChangedEventArgs(List changes)
        {
            _changes = changes;
        } 

        ///  
        /// The list of changes associated with this event. 
        /// 
        public List Changes 
        {
            get
            {
                return _changes; 
            }
        } 
 
        private readonly List _changes;
    } 


    /// 
    /// Represents a single change to the PageCache 
    /// 
    internal class PageCacheChange 
    { 

        ///  
        /// Constructor.
        /// 
        /// The first page changed.
        /// The number of pages changed. 
        /// The type of changed incurred.
        public PageCacheChange(int start, int count, PageCacheChangeType type) 
        { 
            _start = start;
            _count = count; 
            _type = type;
        }

        ///  
        /// Zero-based page number for this first page that has changed.
        ///  
        public int Start 
        {
            get 
            {
                return _start;
            }
        } 

        ///  
        /// Number of continuous pages changed. 
        /// 
        public int Count 
        {
            get
            {
                return _count; 
            }
        } 
 
        /// 
        /// The type of change that occurred. 
        /// 
        public PageCacheChangeType Type
        {
            get 
            {
                return _type; 
            } 
        }
 
        private readonly int _start;
        private readonly int _count;
        private readonly PageCacheChangeType _type;
    } 

    internal enum PageCacheChangeType 
    { 
        /// 
        /// Pages were added to the cache. 
        /// 
        Add=0,

        ///  
        /// Pages were removed from the cache.
        ///  
        Remove, 

        ///  
        /// Pages in the cache were updated.
        /// 
        Update
    } 

    #endregion PageCacheChangedEvent 
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK