RowCache.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Framework / MS / Internal / documents / RowCache.cs / 1 / RowCache.cs

                            //---------------------------------------------------------------------------- 
//
// 
//    Copyright (C) Microsoft Corporation.  All rights reserved.
//  
//
// 
// Description: RowCache caches information about Row layouts used by DocumentGrid. 
//
// History: 
// 10/21/04 - jdersch created
//
//---------------------------------------------------------------------------
 
using System;
using System.Collections; 
using System.Collections.Generic; 
using System.Diagnostics;
using System.Windows; 
using System.Windows.Documents;

namespace MS.Internal.Documents
{ 

 	///  
	/// RowCache computes and caches layouts of an entire document's worth of pages. 
	/// 
    /// http://d2/DRX/default.aspx 
    internal class RowCache
	{
        //-----------------------------------------------------
        // 
        //  Constructors
        // 
        //----------------------------------------------------- 

        #region Constructors 
        /// 
        /// Default RowCache constructor
        /// 
        public RowCache() 
        {
            //Create the List which will hold our cached data. 
            _rowCache = new List(_defaultRowCacheSize); 
        }
 
        #endregion Constructors

        //------------------------------------------------------
        // 
        //  Public Properties
        // 
        //----------------------------------------------------- 

        #region Public Properties 

        /// 
        /// The PageCache used by this RowCache to retrieve cached Page Size information.
        ///  
        /// 
        public PageCache PageCache 
        { 
 	        set
	        {									 
                //Clear our Cache.
                _rowCache.Clear();
                _isLayoutCompleted = false;
                _isLayoutRequested = false; 

                //If the old PageCache is non-null, we need to 
                //remove the old event handlers before we assign the new one. 
                if (_pageCache != null)
                { 
                    _pageCache.PageCacheChanged -= new PageCacheChangedEventHandler(OnPageCacheChanged);
                    _pageCache.PaginationCompleted -= new EventHandler(OnPaginationCompleted);
                }
 
                _pageCache = value;
 
                //Attach our event handlers if the new content is non-null. 
                if (_pageCache != null)
                { 
                    _pageCache.PageCacheChanged += new PageCacheChangedEventHandler(OnPageCacheChanged);
                    _pageCache.PaginationCompleted += new EventHandler(OnPaginationCompleted);
                }			
            } 

            get 
            { 
                return _pageCache;
            } 
        }

        /// 
        /// The number of Rows in the current layout. 
        /// 
        ///  
        public int RowCount 
        {
            get 
            {
                return _rowCache.Count;
            }
        } 

        ///  
        /// The amount of space (in pixel units) to put between pages in the layout, vertically. 
        /// When this value is changed, the current Row Layout will be updated to accomodate this space.
        ///  
        /// 
        public double VerticalPageSpacing
        {
            set 
            {
                if (value < 0) 
                { 
                    value = 0;
                } 

                if (value != _verticalPageSpacing)
                {
                    _verticalPageSpacing = value; 
                    RecalcLayoutForScaleOrSpacing();
                } 
            } 

            get 
            {
 	            return _verticalPageSpacing;
            }
        } 

        ///  
        /// The amount of space (in pixel units) to put between pages in the layout, horizontally. 
        /// When this value is changed, the current Row Layout will be updated to accomodate this space.
        ///  
        /// 
        public double HorizontalPageSpacing
        {
            set 
            {
                if (value < 0) 
                { 
                    value = 0;
                } 

                if (value != _horizontalPageSpacing)
                {
                    _horizontalPageSpacing = value; 
                    RecalcLayoutForScaleOrSpacing();
                } 
            } 

            get 
            {
                return _horizontalPageSpacing;
            }
        } 

        ///  
        /// The scale factor to be applied to the row layout.  When this value is changed, the 
        /// current Row Layout will be updated to reflect the new scale.
        ///  
        /// 
        public double Scale
        {
            set 
            {
                if (_scale != value) 
                { 
                    _scale = value;
                    RecalcLayoutForScaleOrSpacing(); 
                }
            }

            get 
            {
                return _scale; 
            } 
        }
 

        /// 
        /// The Height of the currently computed document layout.
        ///  
        /// 
        public double ExtentHeight 
        { 
            get
            { 
                return _extentHeight;
            }
        }
 
        /// 
        /// The Width of the currently computed document layout. 
        ///  
        /// 
        public double ExtentWidth 
        {
            get
            {
                return _extentWidth; 
            }
        } 
 
        public bool HasValidLayout
        { 
            get
            {
                return _hasValidLayout;
            } 
        }
 
 
        #endregion Public Properties
 
        //------------------------------------------------------
        //
        //  Public Events
        // 
        //------------------------------------------------------
 
        #region Public Events 

        ///  
        /// Fired when the RowCache has been changed for any reason.
        /// 
        public event RowCacheChangedEventHandler RowCacheChanged;
 
        /// 
        /// Fired when a new RowLayout has been calculated. 
        ///  
        public event RowLayoutCompletedEventHandler RowLayoutCompleted;
 
        #endregion Public Events

        //-----------------------------------------------------
        // 
        //  Public Methods
        // 
        //------------------------------------------------------	 

        #region Public Methods 

        /// 
        /// Returns the row at the specified index.
        /// Throws an ArgumentOutOfRange exception if the specified index is out of range. 
        /// 
        /// The index of the row to return 
        /// The requested row. 
        public RowInfo GetRow(int index)
        { 
            if (index < 0 || index > _rowCache.Count)
            {
                throw new ArgumentOutOfRangeException("index");
            } 

            return _rowCache[index]; 
        } 

        ///  
        /// Returns the row that contains the given page.
        /// Throws an exception if the given page does not exist in the current layout.
        /// 
        /// The page number to find the row for. 
        /// The requested row.
        public RowInfo GetRowForPageNumber(int pageNumber) 
        { 
            if (pageNumber < 0 || pageNumber > LastPageInCache)
            { 
                throw new ArgumentOutOfRangeException("pageNumber");
            }

            return _rowCache[GetRowIndexForPageNumber(pageNumber)]; 
        }
 
        ///  
        /// Returns the index of the row that contains the given page.
        /// Throws an exception if the given page does not exist in the current layout. 
        /// 
        /// The page number to find the row index for.
        /// The requested row index
        public int GetRowIndexForPageNumber(int pageNumber ) 
        {
            if (pageNumber < 0 || pageNumber > LastPageInCache) 
            { 
                throw new ArgumentOutOfRangeException("pageNumber");
            } 

            //Search our cache for the row that contains the page.
            //NOTE: Future perf item:
            //This search can be re-written as a binary search, which will be O(log(N)) 
            //instead of O(N).
            for (int i = 0; i < _rowCache.Count; i++) 
            { 
                RowInfo rowInfo = _rowCache[i];
                if (pageNumber >= rowInfo.FirstPage && 
                    pageNumber < rowInfo.FirstPage + rowInfo.PageCount)
                {
                    //We found the row, return the index.
                    return i; 
                }
            } 
 
            //We didn't find it.  Something is very likely wrong with our layout.
            //We'll throw, as this is an indicator that our layout cannot be trusted. 
            throw new InvalidOperationException(SR.Get(SRID.RowCachePageNotFound));
        }

        ///  
        /// Returns the row that lives at the specified vertical offset.
        ///  
        /// The vertical offset to find the corresponding row for 
        /// The index of the row that lives at the offset.
        public int GetRowIndexForVerticalOffset(double offset) 
        {
            if (offset < 0 || offset > ExtentHeight)
            {
                throw new ArgumentOutOfRangeException("offset"); 
            }
 
            //If we have no rows we'll return 0 (the top of the non-existent document) 
            if (_rowCache.Count == 0)
            { 
                return 0;
            }

            //We round the offsets and dimensions to the nearest 1/100th 
            //of a pixel to avoid decimal roundoff errors
            //that can result from scaling operations. 
            double roundedOffset = Math.Round(offset, _findOffsetPrecision); 

            //Search our cache for the Row that occupies the specified offset. 
            //NOTE: Future perf item:
            //This search can be re-written as a binary search, which will be O(log(N))
            //instead of O(N).
            for(int i=0;i<_rowCache.Count;i++) 
            {
                double rowOffset = Math.Round(_rowCache[i].VerticalOffset, _findOffsetPrecision); 
                double rowHeight = Math.Round(_rowCache[i].RowSize.Height, _findOffsetPrecision); 
                bool rowHasZeroHeight = false;
 
                //Check to see if this row has zero height (or if it appears that way due to
                //decimal precision errors with very large (1.0x10^19, for example) page heights).
                if (DoubleUtil.AreClose(rowOffset, rowOffset + rowHeight))
                { 
                    rowHasZeroHeight = true;
                } 
 
                //If the current row has Zero height (or decimal precision makes it seem that way)
                //then we'll only return this page if the offset is equal to the offset of this row. 
                //Note that when decimial precision issues cause rowHasZeroHeight to be set then
                //offset and rowOffset will also be "equal" if the offset points to this row.
                if (rowHasZeroHeight && DoubleUtil.AreClose(roundedOffset, rowOffset))
                { 
                    return i;
                } 
                //Otherwise if this row contains the passed in offset, we'll return it. 
                else if ( roundedOffset >= rowOffset &&
                          roundedOffset < rowOffset + rowHeight) 
                {
                    //We check that the bottom of the row would actually be visible -- if it won't be we use the next.
                    //If this is the last row, we use it anyway.
                    if (WithinVisibleDelta((rowOffset + rowHeight), roundedOffset) || 
                        i == _rowCache.Count - 1)
                    { 
                        return i; 
                    }
                    else 
                    {
                        //The last row was just barely not visible, so we know the next one is the one we're looking for.
                        return i + 1;
                    } 
                }
            } 
 
            //If the offset is equal to the height of the document then we can just return the
            //last row in the document. 
            //This handles the edge case caused by the fact that each row technically begins
            //"overlapping" the last -- a document with two rows of height 500.0 actually has
            //the first row extend from 0.0 to 499.999999...9 and the second starts at 500.0 and
            //goes to 999.999999...9.  For all intents and purposes, the rows don't actually overlap, 
            //but it does mean that if the offset passed in is exactly equal to ExtentHeight then
            //our algorithm won't find the row (the last row ends at ExtentHeight-0.000000...1, not ExtentHeight.) 
            if (DoubleUtil.AreClose(offset, ExtentHeight) ) 
            {
                return _rowCache.Count - 1; 
            }


            //We didn't find it.  Something is very likely wrong here, but it is not fatal. 
            //We will just return the last page in the document.
            return _rowCache.Count - 1; 
        } 

        ///  
        /// Returns the starting index and the number of rows following that occupy the specified
        /// range of vertical offsets.
        /// 
        /// The starting offset 
        /// The ending offset
        /// Returns the first visible row. 
        /// Returns the number of visible rows. 
        public void GetVisibleRowIndices(double startOffset, double endOffset, out int startRowIndex, out int rowCount)
        { 
            startRowIndex = 0;
            rowCount = 0;

            if (endOffset < startOffset) 
            {
                throw new ArgumentOutOfRangeException("endOffset"); 
            } 

            //If the offsets we're given are out of range we'll just return now 
            //because we have no rows to find at this offset.
            if (startOffset < 0 || startOffset > ExtentHeight)
            {
                return; 
            }
 
            //If we have no rows we'll return 0 for the start and count. 
            if (_rowCache.Count == 0)
            { 
                return;
            }

            //Get the first row that's visible. 
            startRowIndex = GetRowIndexForVerticalOffset(startOffset);
            rowCount = 1; 
 
            //We round the offsets and dimensions to the nearest 1/100th
            //of a pixel to avoid decimal roundoff errors 
            //that can result from scaling operations.
            startOffset = Math.Round(startOffset, _findOffsetPrecision);
            endOffset = Math.Round(endOffset, _findOffsetPrecision);
 
            //Now we continue downward until we either reach the end of the document
            //Or find a row that's outside the end offset (or close enough to it that it's not actually visible) 
            for (int i = startRowIndex + 1; i < _rowCache.Count; i++) 
            {
                double rowOffset = Math.Round(_rowCache[i].VerticalOffset, _findOffsetPrecision); 

                if ( rowOffset >= endOffset ||
                     !WithinVisibleDelta( endOffset, rowOffset ) )
                { 
                    //We've found a row that isn't visible so we're done.
                    break; 
                } 

                //This row is visible, add it to the count. 
                rowCount++;
            }
        }
 
        /// 
        /// Recalculates the current row layout by applying the current Scale 
        /// and PageSpacing to the current row layout.  It does not change the 
        /// contents of the rows.
        ///  
        public void RecalcLayoutForScaleOrSpacing()
        {
            //Throw execption if we have no PageCache
            if (PageCache == null) 
            {
                throw new InvalidOperationException(SR.Get(SRID.RowCacheRecalcWithNoPageCache)); 
            } 

            //Reset the extents 
            _extentWidth = 0.0;
            _extentHeight = 0.0;

            //Walk through each row and based on the pages on the row, 
            //recalculate the width and height of the row.
            double currentOffset = 0.0; 
            for (int i = 0; i < _rowCache.Count; i++) 
            {
                //Get this row and save off the page count. 
                RowInfo currentRow = _rowCache[i];
                int pageCount = currentRow.PageCount;

                //Clear the pages so we can add the new, rescaled ones. 
                currentRow.ClearPages();
                currentRow.VerticalOffset = currentOffset; 
 
                //Add each page to the row.
                for (int j = currentRow.FirstPage; j < currentRow.FirstPage + pageCount; j++) 
                {
                    Size pageSize = GetScaledPageSize(j);
                    currentRow.AddPage(pageSize);
                } 

                //Adjust the extent width if necessary 
                _extentWidth = Math.Max(currentRow.RowSize.Width, _extentWidth); 

                currentOffset += currentRow.RowSize.Height; 
                _extentHeight += currentRow.RowSize.Height;
                _rowCache[i] = currentRow;
            }
 
            //Fire off our RowCacheChanged event indicating that all rows have changed.
            List changes = new List(1); 
            changes.Add(new RowCacheChange(0, _rowCache.Count)); 
            RowCacheChangedEventArgs args = new RowCacheChangedEventArgs(changes);
            RowCacheChanged(this, args); 
        }

        /// 
        /// Recalculates the row layout given a starting page, and the number of pages to lay out 
        /// on each row.
        /// In the event that there aren't currently enough pages to accomplish the layout, RowCache 
        /// will wait until the pages are available and then fire off a RowLayoutCompleted event. 
        /// 
        /// The page to build the rows around 
        /// The number of columns in the "pivot row"	
        public void RecalcRows(int pivotPage, int columns)
        {
 
            //Throw execption if we have no PageCache
            if (PageCache == null) 
            { 
                throw new InvalidOperationException(SR.Get(SRID.RowCacheRecalcWithNoPageCache));
            } 

            //Throw exception for illegal values
            if (pivotPage < 0 || pivotPage > PageCache.PageCount)
            { 
                throw new ArgumentOutOfRangeException("pivotPage");
            } 
 
            //Can't lay out fewer than 1 column of pages.
            if (columns < 1) 
            {
                throw new ArgumentOutOfRangeException("columns");
            }
 
            //Store off the requested layout parameters.
            _layoutColumns = columns; 
            _layoutPivotPage = pivotPage; 

            //We've started a new layout, reset the valid layout flag. 
            _hasValidLayout = false;

            //We can't do anything here if we haven't gotten enough pages to create the first row yet.
            //But we'll save off the specified columns & pivot page (above) 
            //And as soon as we get some pages (in OnPageCacheChanged)
            //we'll start laying them out in the requested manner. 
            if (PageCache.PageCount < _layoutColumns) 
            {
                //If pagination is still happening or if we have no pages in our content 
                //we need to wait until later.
                if (!PageCache.IsPaginationCompleted || PageCache.PageCount == 0)
                {
                    //Reset our LayoutComputed flag, as we've been tasked to create a new one 
                    //but can't do it yet.
                    _isLayoutRequested = true; 
                    _isLayoutCompleted = false; 
                    return;
                } 
                //If pagination has completed, we trim down the column count and continue.
                else
                {
                    //We're done paginating, but we don't have enough 
                    //pages to do the requested layout.  So we'll need to trim down the column count, with a
                    //lower bound of 1 column. 
                    _layoutColumns = Math.Min(_layoutColumns, PageCache.PageCount); 
                    _layoutColumns = Math.Max(1, _layoutColumns);
 
                    //The pivot page is always the first page in this instance
                    _layoutPivotPage = 0;
                }
            } 

            //Reset the document extents, which will be recalculated by the RecalcRows methods. 
            _extentHeight = 0.0; 
            _extentWidth = 0.0;
 
            //Now call the specific RecalcRows... method for our document type.
            if (PageCache.DynamicPageSizes)
            {
                _pivotRowIndex = RecalcRowsForDynamicPageSizes(_layoutPivotPage, _layoutColumns); 
            }
            else 
            { 
                _pivotRowIndex = RecalcRowsForFixedPageSizes(_layoutPivotPage, _layoutColumns);
            } 

            _isLayoutCompleted = true;
            _isLayoutRequested = false;
 
            //Set the valid layout flag now that we're done
            _hasValidLayout = true; 
 
            //We've computed the layout, so we'll fire off our RowLayoutCompleted event.
            //We pass along the pivotRow's Index so the listener can keep the pivot row visible. 
            RowLayoutCompletedEventArgs args = new RowLayoutCompletedEventArgs(_pivotRowIndex);
            RowLayoutCompleted(this, args);

            //Fire off our RowCacheChanged event indicating that all rows have changed. 
            List changes = new List(1);
            changes.Add(new RowCacheChange(0, _rowCache.Count)); 
            RowCacheChangedEventArgs args2 = new RowCacheChangedEventArgs(changes); 
            RowCacheChanged(this, args2);
 
        }

        #endregion Public Methods
 
        //-----------------------------------------------------
        // 
        //  Private Properties 
        //
        //-----------------------------------------------------	 
        #region Private Properties

        /// 
        /// LastPageInCache returns the last page in the current Row layout. 
        /// If there is no layout, returns -1.
        ///  
        private int LastPageInCache 
        {
            get 
            {
                //If we have no rows, then we have no pages
                //at all in the cache.
                if (_rowCache.Count == 0) 
                {
                    return -1; 
                } 
                else
                { 
                    //Get the last row in the cache and return its
                    //last page.
                    RowInfo lastRow = _rowCache[_rowCache.Count-1];
                    return lastRow.FirstPage + lastRow.PageCount - 1; 
                }
            } 
        } 

 
        #endregion Private Properties

        //-----------------------------------------------------
        // 
        //  Private Methods
        // 
        //------------------------------------------------------	 

        ///  
        /// Helper method to determine whether two offsets are visibly different
        /// (whether they're more than a certain fraction of a pixel apart).
        /// 
        ///  
        /// 
        private bool WithinVisibleDelta(double offset1, double offset2) 
        { 
            return offset1 - offset2 > _visibleDelta;
        } 

        /// 
        /// Recalculates the row layout given the assumption that page sizes in the document may vary from
        /// page to page. 
        /// 
        /// The page which other rows are laid out around 
        /// The number of columns to fit on the row containing the pivot page. 
        /// The index of the row that contains the pivot page.
        private int RecalcRowsForDynamicPageSizes(int pivotPage, int columns) 
        {
            //Throw exception for illegal values
            if (pivotPage < 0 || pivotPage >= PageCache.PageCount)
            { 
                throw new ArgumentOutOfRangeException("pivotPage");
            } 
 
            //Can't lay out fewer than 1 column of pages.
            if (columns < 1) 
            {
                throw new ArgumentOutOfRangeException("columns");
            }
 
            //Adjust the pivot page as necessary so that the to-be-computed layout has the specified number
            //of columns on the row. 
            //(If the pivot page is the last page in the document, for example, we need to move it back 
            //by the column specification.)
            if (pivotPage + columns > PageCache.PageCount) 
            {
                pivotPage = Math.Max(0, PageCache.PageCount - columns);
            }
 
            //Clear our cache, since we're calculating a new layout.
            _rowCache.Clear(); 
 
            //Calculate this row so we can get the row width information
            //we need for the other rows. 
            RowInfo pivotRow = CreateFixedRow(pivotPage, columns);
            double pivotRowWidth = pivotRow.RowSize.Width;
            int pivotRowIndex = 0;
 
            //We work our way back up to the top of the document from the pivot
            //and recalc the rows along the way. 
            //This is necessary because page sizes vary and 
            //we want to guarantee that the pages that have been fit
            //on the pivot row are the ones displayed in that row.  If we were to start 
            //at the top and work our way down, we might not end up with the
            //same pages on this row.

            //Store off the rows we calculate here, we�ll need them later. 
            List tempRows = new List(pivotPage / columns);
            int currentPage = pivotPage; 
            while (currentPage > 0) 
            {
                //Create our row, specifying a backwards direction 
                RowInfo newRow = CreateDynamicRow(currentPage - 1, pivotRowWidth, false /* backwards */);
                currentPage = newRow.FirstPage;
                tempRows.Add(newRow);
            } 

            //We�ve made it to the top. 
            //Now we can calculate the offsets of each row and add them to the Row cache. 
            for (int i = tempRows.Count-1; i >= 0; i--)
            { 
                AddRow(tempRows[i]);
            }

            //Save off the index of this row. 
            pivotRowIndex = _rowCache.Count;
 
            //Add the pivot row (calculated earlier) 
            AddRow(pivotRow);
 
            //Now we continue working down from after the pivot to the end of the
            //document.
            currentPage = pivotPage + columns;
            while (currentPage < PageCache.PageCount) 
            {
                //Create our row, specifying a forward direction 
                RowInfo newRow = CreateDynamicRow(currentPage, pivotRowWidth, true /*forwards */); 
                currentPage += newRow.PageCount;
                AddRow(newRow); 
            }

            //And we�re done.  Whew.
            return pivotRowIndex; 
        }
 
        ///  
        /// Creates a "dynamic" row -- that is, given a maximum width for the row, it will fit as
        /// many pages on said row as possible. 
        /// 
        /// The first page to put on this row.
        /// The requested width of this row.
        /// Whether to create this row using the next N pages or the 
        /// previous N.
        ///  
        private RowInfo CreateDynamicRow(int startPage, double rowWidth, bool createForward) 
        {
            if (startPage >= PageCache.PageCount) 
            {
                throw new ArgumentOutOfRangeException("startPage");
            }
 
            //Given a starting page for this row, and the specified
            //width for each row, figure out how many pages will fit on this row 
            //and return the resulting RowInfo object. 

            //Populate the struct with initial data. 
            RowInfo newRow = new RowInfo();

            //Each row is guaranteed to have at least one page, even if it�s wider
            //than the allotted size, so we add it here.	 
            Size pageSize = GetScaledPageSize( startPage );
            newRow.AddPage(pageSize); 
 
            //Keep adding pages until we either:
            // - run out of pages to add 
            // - run out of space in the row
            for(;;)
            {
                if( createForward ) 
                {
                    //Grab the next page. 
                    pageSize = GetScaledPageSize(startPage + newRow.PageCount); 

                    //We�re out of pages, or out of space. 
                    if (startPage + newRow.PageCount >= PageCache.PageCount ||
                        newRow.RowSize.Width + pageSize.Width > rowWidth )
                    {
                        break; 
                    }
                } 
                else 
                {
                    //Grab the previous page. 
                    pageSize = GetScaledPageSize( startPage - newRow.PageCount );

                    //We�re out of pages, or out of space.
                    if( startPage - newRow.PageCount < 0 || 
                        newRow.RowSize.Width + pageSize.Width > rowWidth )
                    { 
                        break; 
                    }
                } 
                newRow.AddPage(pageSize);

                //If we've hit the hard upper limit for pages on a row then we're done with this row.
                if (newRow.PageCount == DocumentViewerConstants.MaximumMaxPagesAcross) 
                {
                    break; 
                } 
            }
 
            if (!createForward)
            {
                newRow.FirstPage = startPage - (newRow.PageCount - 1);
            } 
            else
            { 
                newRow.FirstPage = startPage; 
            }
 
            return newRow;
        }

        ///  
        /// Recalculates the row for content that does not have varying page sizes.
        ///  
        /// The first page on this row 
        /// The number of columns on this row
        /// The index of the row that contains the starting page. 
        private int RecalcRowsForFixedPageSizes(int startPage, int columns)
        {
            //Throw exception for illegal values
            if (startPage < 0 || startPage > PageCache.PageCount) 
            {
                throw new ArgumentOutOfRangeException("startPage"); 
            } 

            //Can't lay out fewer than 1 column of pages. 
            if (columns < 1)
            {
                throw new ArgumentOutOfRangeException("columns");
            } 

            //Since all pages in the document have been determined to be 
            //the same size, we can use a simple algorithm to create our row layout. 

            //We start at the top (no need to specify a pivot) 
            //and calculate the rows from there.
            //Each row will have exactly "columns" pages on it.	
            _rowCache.Clear();
 
            for (int i = 0; i < PageCache.PageCount; i += columns)
            { 
                RowInfo newRow = CreateFixedRow(i, columns); 
                AddRow(newRow);
            } 

            //Find the row the start page lives on and return its index.
            return GetRowIndexForPageNumber(startPage);
        }		 

        ///  
        /// Creates a "fixed" row -- that is, a row with a specific number of columns on it. 
        /// 
        /// The first page to live on this row. 
        /// The number of columns on this row.
        /// 
        private RowInfo CreateFixedRow(int startPage, int columns)
        { 
            if (startPage >= PageCache.PageCount)
            { 
                throw new ArgumentOutOfRangeException("startPage"); 
            }
 
            if (columns < 1 )
            {
                throw new ArgumentOutOfRangeException("columns");
            } 

            //Given a starting page for this row and the number of columns in the row 
            //calculate the width & height and return the resulting RowInfo struct 

            //Populate the struct with initial data 
            RowInfo newRow = new RowInfo();
            newRow.FirstPage = startPage;

            //Keep adding pages until we either: 
            // - run out of pages to add
            // - add the appropriate number of pages 
            for (int i = startPage; i < startPage + columns; i++) 
            {
                //We�re out of pages. 
                if (i > PageCache.PageCount - 1)
                    break;

                //Get the size of the page 
                Size pageSize = GetScaledPageSize(i);
 
                //Add this page to the row 
                newRow.AddPage(pageSize);
            } 

            return newRow;
        }
 
        /// 
        /// Given a range of pages, adds the pages to the existing row cache, 
        /// adding new rows where necessary. 
        /// 
        /// The first page to add to the layout 
        /// The number of pages to add.		
        private RowCacheChange AddPageRange(int startPage, int count)
        {
            if (!_isLayoutCompleted) 
            {
                throw new InvalidOperationException(SR.Get(SRID.RowCacheCannotModifyNonExistentLayout)); 
            } 

            int currentPage = startPage; 
            int lastPage = startPage + count;

            int startRow = 0;
            int rowCount = 0; 

            //First we check to see if startPage is such that we'd end up skipping 
            //pages in the document -- that is, if the last page in our layout is currently 
            //10 and start is 15, we need to fill in pages 11-14 as well.
            if (startPage > LastPageInCache + 1) 
            {
                currentPage = LastPageInCache + 1;
            }
 
            //Get the last row in the layout
            RowInfo lastRow = _rowCache[_rowCache.Count - 1]; 
 
            //Now we need to check to see if we can add any pages to this row
            //without exceeding the current layout's pivot row width. 

            //Get the size of the page to add
            Size pageSize = GetScaledPageSize(currentPage);
 
            //Get the pivot row
            RowInfo pivotRow = GetRow(_pivotRowIndex); 
 
            bool lastRowUpdated = false;
 
            //Add new pages to the last row until we run out of pages or space.
            while (currentPage < lastPage &&
                lastRow.RowSize.Width + pageSize.Width <= pivotRow.RowSize.Width)
            { 
                //Add the current page
                lastRow.AddPage(pageSize); 
                currentPage++; 

                //Get the size of the next page. 
                pageSize = GetScaledPageSize(currentPage);

                //Note that we updated this row so we'll update the cache when we're done here.
                lastRowUpdated = true; 
            }
 
            //If we actually made a change to the last row, then we need to update the row cache. 
            if (lastRowUpdated)
            { 
                startRow = _rowCache.Count - 1;
                //Update the last row
                UpdateRow(startRow, lastRow);
            } 
            else
            { 
                startRow = _rowCache.Count; 
            }
 
            //Now we add more rows to the layout, if we have any pages left.
            while (currentPage < lastPage)
            {
                //Build a new row. 
                RowInfo newRow = new RowInfo();
                newRow.FirstPage = currentPage; 
 
                //Add pages until we either run out of pages or need to start a new row.
                do 
                {
                    //Get the size of the next page
                    pageSize = GetScaledPageSize(currentPage);
 
                    //Add it.
                    newRow.AddPage(pageSize); 
                    currentPage++; 

                } while (newRow.RowSize.Width + pageSize.Width <= pivotRow.RowSize.Width 
                    && currentPage < lastPage);

                //Add this new row to our cache
                AddRow(newRow); 
                rowCount++;
            } 
 
            return new RowCacheChange(startRow, rowCount);
        } 

        /// 
        /// Adds a new row onto the end of the layout and updates the current layout
        /// measurements. 
        /// 
        /// The new row to add to the layout 
        private void AddRow(RowInfo newRow) 
        {
            //If this is the first row in the document we just put it at the beginning 
            if (_rowCache.Count == 0)
            {
                newRow.VerticalOffset = 0.0;
 
                //The width of the document is just the width of the first row.
                _extentWidth = newRow.RowSize.Width; 
            } 
            else
            { 
                //This is not the first row, so we put it at the end of the last row.
                RowInfo lastRow = _rowCache[_rowCache.Count - 1];

                //The new row needs to be positioned at the bottom of the last row 
                newRow.VerticalOffset = lastRow.VerticalOffset + lastRow.RowSize.Height;
 
                //Update the document's width 
                _extentWidth = Math.Max(newRow.RowSize.Width, _extentWidth);
            } 

            //Update the layout
            _extentHeight += newRow.RowSize.Height;
 
            //Add the row.
            _rowCache.Add(newRow); 
        } 

        ///  
        /// Given a preexisting page range, updates the dimensions of the rows containing said
        /// pages from the Page Cache.
        /// If any row's height changes as a result, all rows below have their offsets updated.
        /// 		 
        /// The first page changed
        /// The number of pages changed 
        private RowCacheChange UpdatePageRange(int startPage, int count) 
        {
            if (!_isLayoutCompleted) 
            {
                throw new InvalidOperationException(SR.Get(SRID.RowCacheCannotModifyNonExistentLayout));
            }
 
            //Get the row that contains the first page
            int startRowIndex = GetRowIndexForPageNumber(startPage); 
            int rowIndex = startRowIndex; 

            int currentPage = startPage; 

            //Recalculate the rows affected by the changed pages.
            while (currentPage < startPage + count && rowIndex < _rowCache.Count)
            { 
                //Get the current row
                RowInfo currentRow = _rowCache[rowIndex]; 
                //Create a new row and copy pertinent data 
                //from the old one.
                RowInfo updatedRow = new RowInfo(); 
                updatedRow.VerticalOffset = currentRow.VerticalOffset;
                updatedRow.FirstPage = currentRow.FirstPage;

                //Now rebuild this row, thus recalculating the row's size 
                //based on the new page sizes.
                for (int i = currentRow.FirstPage; i < currentRow.FirstPage + currentRow.PageCount; i++) 
                { 
                    //Get the updated page size and add it to our updated row
                    Size pageSize = GetScaledPageSize(i); 
                    updatedRow.AddPage(pageSize);
                }				

                //Update the row layout with this new row. 
                UpdateRow(rowIndex, updatedRow);
 
                //Move to the next group of pages. 
                currentPage = updatedRow.FirstPage + updatedRow.PageCount;
 
                //Move to the next row.
                rowIndex++;
            }
 
            return new RowCacheChange(startRowIndex, rowIndex - startRowIndex);
        } 
 
        /// 
        /// Updates the cache entry at the given index with the new RowInfo supplied. 
        /// If the new row has a different height than the old one, we need to update
        /// the offsets of all the rows below this one and adjust the vertical extent appropriately.
        /// If it has a different width, we just need to update the horizontal extent appropriately.
        ///  
        /// The index of the row to update
        /// The new RowInfo to replace the old 
        private void UpdateRow(int index, RowInfo newRow) 
        {
            if (!_isLayoutCompleted) 
            {
                throw new InvalidOperationException(SR.Get(SRID.RowCacheCannotModifyNonExistentLayout));
            }
 
            //Check for invalid indices.  If it's out of range then we just return.
            if (index > _rowCache.Count) 
            { 
                Debug.Assert(false, "Requested to update a non-existent row.");
                return; 
            }

 	        //Get the current entry.
	        RowInfo oldRowInfo = _rowCache[index]; 

            //Replace the old with the new. 
            _rowCache[index] = newRow; 

            //Compare the heights -- if they differ we need to update the rows beneath it. 
            if (oldRowInfo.RowSize.Height != newRow.RowSize.Height)
            {
                //The new row has a different height, so we add the delta
                //between the old and the new to every row below this one. 
                double delta = newRow.RowSize.Height - oldRowInfo.RowSize.Height;
                for(int i = index + 1; i < _rowCache.Count; i++) 
                { 
                    RowInfo row = _rowCache[i];
                    row.VerticalOffset += delta; 
                    _rowCache[i] = row;
                }

                //Add the delta for this row to our vertical extent. 
                _extentHeight += delta;				
            } 
 
            //If the new row is wider than the current document's width, then we
            //can just update the document width now. 
            if (newRow.RowSize.Width > _extentWidth)
            {
                _extentWidth = newRow.RowSize.Width;
            } 
 	        //Otherwise, if the widths differ we need to recalculate the width
            //of the document again. 
            //(The logic is that this particular row could have defined the extent width, 
            //and now that it's changed size the extent may change as well.)
            else if (oldRowInfo.RowSize.Width != newRow.RowSize.Width) 
            {
                //The width of this row has changed.
                //This means we need to recalculate the width of the entire document
                //by walking through each row. 
                _extentWidth = 0;
                for (int i = 0; i < _rowCache.Count; i++) 
                { 
                    RowInfo row = _rowCache[i];
                    //Update the extent width. 
                    _extentWidth = Math.Max(row.RowSize.Width, _extentWidth);
                }
            }
        } 

        ///  
        /// Trims any pages (and rows) from the end of the layout, starting with the specified page. 
        /// 
        /// The first page to remove. 
        private RowCacheChange TrimPageRange(int startPage)
        {
            //First, we find the row the last page is on.
            int rowIndex = GetRowIndexForPageNumber(startPage); 

            //Now we replace this row with a new row that has the deleted pages 
            //removed, if there are pages on this row that aren't going to be deleted. 
            RowInfo oldRow = GetRow(rowIndex);
 
            if (oldRow.FirstPage < startPage)
            {
                RowInfo updatedRow = new RowInfo();
                updatedRow.VerticalOffset = oldRow.VerticalOffset; 
                updatedRow.FirstPage = oldRow.FirstPage;
 
                for (int i = oldRow.FirstPage; i < startPage; i++) 
                {
                    //Get the page we're interested in. 
                    Size pageSize = GetScaledPageSize(i);
                    //Add it.
                    updatedRow.AddPage(pageSize);
                } 

                UpdateRow(rowIndex, updatedRow); 
 
                //Increment the rowIndex, since we're going to keep this row.
                rowIndex++; 
            }

            int removeCount = _rowCache.Count - rowIndex;
 
            //Now remove all the rows below this one.
            if(rowIndex < _rowCache.Count ) 
            { 
                _rowCache.RemoveRange( rowIndex, removeCount);
            } 

            //Update our extents
            _extentHeight = oldRow.VerticalOffset;
 
            return new RowCacheChange(rowIndex, removeCount);
        } 
 
        /// 
        /// Helper function that returns the dimensions of a page 
        /// with the current Scale factor and Spacing applied.
        /// 
        /// The page to retrieve page size info about.
        /// The padded and scaled size of the given page. 
        private Size GetScaledPageSize(int pageNumber)
        { 
            //GetPageSize will return (0,0) for out-of-range pages. 
            Size pageSize = PageCache.GetPageSize(pageNumber);
 
            if (pageSize.IsEmpty)
            {
                pageSize = new Size(0, 0);
            } 

            pageSize.Width *= Scale; 
            pageSize.Height *= Scale; 
            pageSize.Width += HorizontalPageSpacing;
            pageSize.Height += VerticalPageSpacing; 

            return pageSize;
        }
 
        /// 
        /// Event Handler for the PageCacheChanged event.  Called whenever an entry in the 
        /// PageCache is changed.  When this happens, the RowCache needs to Add, Remove, or 
        /// Update row entries corresponding to the pages changed.
        ///  
        /// The object that sent this event.
        /// The associated arguments.
        private void OnPageCacheChanged(object sender, PageCacheChangedEventArgs args)
        { 
            //If we have a computed layout then we'll add/remove/frob rows to conform
            //to that layout. 
            if (_isLayoutCompleted) 
            {
                List changes = new List(args.Changes.Count); 
                for (int i = 0; i < args.Changes.Count; i++)
                {
                    PageCacheChange pageChange = args.Changes[i];
                    switch (pageChange.Type) 
                    {
                        case PageCacheChangeType.Add: 
                        case PageCacheChangeType.Update: 
                            if (pageChange.Start > LastPageInCache)
                            { 
                                //Completely new pages, so we add new cache entries
                                RowCacheChange change = AddPageRange(pageChange.Start, pageChange.Count);
                                if (change != null)
                                { 
                                    changes.Add(change);
                                } 
                            } 
                            else
                            { 
                                if (pageChange.Start + pageChange.Count - 1 <= LastPageInCache)
                                {
                                    //All pre-existing pages, so we just update the current cache entries.
                                    RowCacheChange change = UpdatePageRange(pageChange.Start, pageChange.Count); 
                                    if (change != null)
                                    { 
                                        changes.Add(change); 
                                    }
                                } 
                                else
                                {
                                    //Some pre-existing pages, some new.
                                    RowCacheChange change; 
                                    change = UpdatePageRange(pageChange.Start, LastPageInCache - pageChange.Start);
                                    if (change != null) 
                                    { 
                                        changes.Add(change);
                                    } 
                                    change = AddPageRange(LastPageInCache + 1, pageChange.Count - (LastPageInCache - pageChange.Start));
                                    if (change != null)
                                    {
                                        changes.Add(change); 
                                    }
                                } 
                            } 
                            break;
 
                        case PageCacheChangeType.Remove:
                            //If PageCount is now less than the size of our cache due to repagination
                            //we remove the corresponding entries from our row cache.
                            if (PageCache.PageCount - 1 < LastPageInCache) 
                            {
                                //Remove pages starting at the first no-longer-existent page. 
                                //(PageCache.PageCount now points to the first dead page) 
                                RowCacheChange change = TrimPageRange(PageCache.PageCount);
                                if (change != null) 
                                {
                                    changes.Add(change);
                                }
                            } 

                            //If because of the above trimming we have fewer pages left 
                            //in the document than the columns that were initially requested 
                            //We'll need to recalc our layout from scratch.
                            //First we check to see if we have one or fewer rows left. 
                            if (_rowCache.Count <= 1)
                            {
                                //If we have either no rows left or the remaining row has
                                //less than _layoutColumns pages on it, we need to recalc from the first page. 
                                if (_rowCache.Count == 0 || _rowCache[0].PageCount < _layoutColumns )
                                { 
                                    RecalcRows(0, _layoutColumns); 
                                }
                            } 
                            break;

                        default:
                            throw new ArgumentOutOfRangeException("args"); 
                    }
                } 
 
                RowCacheChangedEventArgs newArgs = new RowCacheChangedEventArgs(changes);
                RowCacheChanged(this, newArgs); 
            }
            else if (_isLayoutRequested)
            {
                //We've had a request to create a layout previously, but didn't have enough pages to do so before. 
                //Try it now.
                RecalcRows(_layoutPivotPage, _layoutColumns); 
            } 
        }
 
        /// 
        /// Handler for the OnPaginationCompleted event.  If we still have an unfulfilled
        /// layout request, we'll call RecalcRows to ensure that we get a layout (though possibly
        /// with fewer columns than requested.) 
        /// 
        /// The sender of this event 
        /// The arguments associated with this event 
        private void OnPaginationCompleted(object sender, EventArgs args)
        { 
            if (_isLayoutRequested)
            {
                //We've had a request to create a layout previously, but we don't have enough
                //pages to do it.  We'll call RecalcRows here which will now properly trim down 
                //the column count.
                RecalcRows(_layoutPivotPage, _layoutColumns); 
            } 
        }
 
        //-----------------------------------------------------
        //
        //  Private Fields
        // 
        //------------------------------------------------------
 
        //The List that contains our row cache. 
        private List       _rowCache;
 
        //Default Row Layout parameters:
        private int                 _layoutPivotPage;
        private int                 _layoutColumns;
 
        //The index of the pivot row
        private int                 _pivotRowIndex; 
 
        //Reference to our PageCache
        private PageCache			_pageCache;		 

        //Flag indicating if we've been asked for a row layout.
        private bool                _isLayoutRequested;
        private bool                _isLayoutCompleted; 

        //Data for CLR properties. 
        private double              _verticalPageSpacing; 
        private double              _horizontalPageSpacing;
        private double              _scale=1.0; 
        private double              _extentHeight;
        private double              _extentWidth;
        private bool                _hasValidLayout;
 
        private readonly int        _defaultRowCacheSize = 32;
 
        //This is the number of digits of precision we round to when searching for Rows given an offset. 
        //We do this to avoid returning extraneous pages which are "visible" only by a few hundredths of
        //a pixel (i.e. not really visible). 
        private readonly int        _findOffsetPrecision = 2;

        //This is the fraction of a pixel of overlap required for a given offset to be considered "visible."
        private readonly double     _visibleDelta = 0.5; 

        } 
 
        /// 
        /// The RowInfo class represents a single row in the document 
        /// layout and contains the necessary data to compute the location
        /// and render a given row of pages.
        /// 
        internal class RowInfo 
        {
        ///  
        /// Constructor for a RowInfo object. 
        /// 
        public RowInfo() 
        {
            //Create our RowSize, which is (0,0) by default.
            _rowSize = new Size(0,0);
        } 

        ///  
        /// Adds a page to this Row. 
        /// Increments the PageCount and updates the RowSize.
        ///  
        /// 
        public void AddPage( Size pageSize )
        {
            //Add the page to this row. 
            _pageCount++;
 
            //Update the row's dimensions 
            _rowSize.Width += pageSize.Width;
            _rowSize.Height = Math.Max(pageSize.Height, _rowSize.Height); 
        }

        /// 
        /// Removes all pages from this Row. 
        /// 
        public void ClearPages() 
        { 
            _pageCount = 0;
            _rowSize.Width = 0.0; 
            _rowSize.Height = 0.0;
        }

        ///  
        /// The dimensions of this row.
        ///  
        public Size RowSize 
        {
            get 
            {
                return _rowSize;
            }
        } 

        ///  
        /// The offset at which this row appears in the document 
        /// 
        public double VerticalOffset 
        {
            get
            {
                return _verticalOffset; 
            }
 
            set 
            {
                _verticalOffset = value; 
            }
        }

        ///  
        /// The first page on this row.
        ///  
        public int FirstPage 
        {
            get 
            {
                return _firstPage;
            }
 
            set
            { 
                _firstPage = value; 
            }
        } 

        /// 
        /// The number of pages on this row.
        ///  
        public int PageCount
        { 
            get 
            {
                return _pageCount; 
            }
        }

        private Size    _rowSize; 
        private double  _verticalOffset;
        private int     _firstPage; 
        private int     _pageCount; 

    } 

    /// 
    ///RowCacheChanged event handler.
    ///  
    internal delegate void RowCacheChangedEventHandler(object sender, RowCacheChangedEventArgs e);
 
    ///  
    ///RowLayoutCompleted event handler.
    ///  
    internal delegate void RowLayoutCompletedEventHandler(object sender, RowLayoutCompletedEventArgs e);

    /// 
    /// Event arguments for the RowLayoutCompleted event. 
    /// 
    internal class RowLayoutCompletedEventArgs : EventArgs 
    { 
        /// 
        /// Constructor. 
        /// 
        ///The index of the row to keep visible (the pivot row)
        public RowLayoutCompletedEventArgs(int pivotRowIndex)
        { 
            _pivotRowIndex = pivotRowIndex;
        } 
 
        /// 
        /// The index of the row to be kept visible by DocumentGrid. 
        /// 
        public int PivotRowIndex
        {
            get 
            {
                return _pivotRowIndex; 
            } 
        }
 
        private readonly int _pivotRowIndex;
    }

    ///  
    /// Event arguments for the RowCacheChanged event.
    ///  
    internal class RowCacheChangedEventArgs : EventArgs 
    {
        ///  
        /// Constructor.
        /// 
        /// The changes corresponding to this event
        public RowCacheChangedEventArgs(List changes) 
        {
            _changes = changes; 
        } 

        ///  
        /// The changes corresponding to this event.
        /// 
        public List Changes
        { 
            get
            { 
                return _changes; 
            }
        } 

        private readonly List _changes;
    }
 
    /// 
    /// Represents a single change to the RowCache 
    ///  
    internal class RowCacheChange
    { 
        /// 
        /// Constructor.
        /// 
        /// The first row changed 
        /// The number of rows changed
        public RowCacheChange(int start, int count) 
        { 
            _start = start;
            _count = count; 
        }

        /// 
        /// Zero-based page number for this first row that has changed. 
        /// 
        public int Start 
        { 
            get
            { 
                return _start;
            }
        }
 
        /// 
        /// Number of continuous rows changed. 
        ///  
        public int Count
        { 
            get
            {
                return _count;
            } 
        }
 
        private readonly int _start; 
        private readonly int _count;
    } 



} 

// 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: RowCache caches information about Row layouts used by DocumentGrid. 
//
// History: 
// 10/21/04 - jdersch created
//
//---------------------------------------------------------------------------
 
using System;
using System.Collections; 
using System.Collections.Generic; 
using System.Diagnostics;
using System.Windows; 
using System.Windows.Documents;

namespace MS.Internal.Documents
{ 

 	///  
	/// RowCache computes and caches layouts of an entire document's worth of pages. 
	/// 
    /// http://d2/DRX/default.aspx 
    internal class RowCache
	{
        //-----------------------------------------------------
        // 
        //  Constructors
        // 
        //----------------------------------------------------- 

        #region Constructors 
        /// 
        /// Default RowCache constructor
        /// 
        public RowCache() 
        {
            //Create the List which will hold our cached data. 
            _rowCache = new List(_defaultRowCacheSize); 
        }
 
        #endregion Constructors

        //------------------------------------------------------
        // 
        //  Public Properties
        // 
        //----------------------------------------------------- 

        #region Public Properties 

        /// 
        /// The PageCache used by this RowCache to retrieve cached Page Size information.
        ///  
        /// 
        public PageCache PageCache 
        { 
 	        set
	        {									 
                //Clear our Cache.
                _rowCache.Clear();
                _isLayoutCompleted = false;
                _isLayoutRequested = false; 

                //If the old PageCache is non-null, we need to 
                //remove the old event handlers before we assign the new one. 
                if (_pageCache != null)
                { 
                    _pageCache.PageCacheChanged -= new PageCacheChangedEventHandler(OnPageCacheChanged);
                    _pageCache.PaginationCompleted -= new EventHandler(OnPaginationCompleted);
                }
 
                _pageCache = value;
 
                //Attach our event handlers if the new content is non-null. 
                if (_pageCache != null)
                { 
                    _pageCache.PageCacheChanged += new PageCacheChangedEventHandler(OnPageCacheChanged);
                    _pageCache.PaginationCompleted += new EventHandler(OnPaginationCompleted);
                }			
            } 

            get 
            { 
                return _pageCache;
            } 
        }

        /// 
        /// The number of Rows in the current layout. 
        /// 
        ///  
        public int RowCount 
        {
            get 
            {
                return _rowCache.Count;
            }
        } 

        ///  
        /// The amount of space (in pixel units) to put between pages in the layout, vertically. 
        /// When this value is changed, the current Row Layout will be updated to accomodate this space.
        ///  
        /// 
        public double VerticalPageSpacing
        {
            set 
            {
                if (value < 0) 
                { 
                    value = 0;
                } 

                if (value != _verticalPageSpacing)
                {
                    _verticalPageSpacing = value; 
                    RecalcLayoutForScaleOrSpacing();
                } 
            } 

            get 
            {
 	            return _verticalPageSpacing;
            }
        } 

        ///  
        /// The amount of space (in pixel units) to put between pages in the layout, horizontally. 
        /// When this value is changed, the current Row Layout will be updated to accomodate this space.
        ///  
        /// 
        public double HorizontalPageSpacing
        {
            set 
            {
                if (value < 0) 
                { 
                    value = 0;
                } 

                if (value != _horizontalPageSpacing)
                {
                    _horizontalPageSpacing = value; 
                    RecalcLayoutForScaleOrSpacing();
                } 
            } 

            get 
            {
                return _horizontalPageSpacing;
            }
        } 

        ///  
        /// The scale factor to be applied to the row layout.  When this value is changed, the 
        /// current Row Layout will be updated to reflect the new scale.
        ///  
        /// 
        public double Scale
        {
            set 
            {
                if (_scale != value) 
                { 
                    _scale = value;
                    RecalcLayoutForScaleOrSpacing(); 
                }
            }

            get 
            {
                return _scale; 
            } 
        }
 

        /// 
        /// The Height of the currently computed document layout.
        ///  
        /// 
        public double ExtentHeight 
        { 
            get
            { 
                return _extentHeight;
            }
        }
 
        /// 
        /// The Width of the currently computed document layout. 
        ///  
        /// 
        public double ExtentWidth 
        {
            get
            {
                return _extentWidth; 
            }
        } 
 
        public bool HasValidLayout
        { 
            get
            {
                return _hasValidLayout;
            } 
        }
 
 
        #endregion Public Properties
 
        //------------------------------------------------------
        //
        //  Public Events
        // 
        //------------------------------------------------------
 
        #region Public Events 

        ///  
        /// Fired when the RowCache has been changed for any reason.
        /// 
        public event RowCacheChangedEventHandler RowCacheChanged;
 
        /// 
        /// Fired when a new RowLayout has been calculated. 
        ///  
        public event RowLayoutCompletedEventHandler RowLayoutCompleted;
 
        #endregion Public Events

        //-----------------------------------------------------
        // 
        //  Public Methods
        // 
        //------------------------------------------------------	 

        #region Public Methods 

        /// 
        /// Returns the row at the specified index.
        /// Throws an ArgumentOutOfRange exception if the specified index is out of range. 
        /// 
        /// The index of the row to return 
        /// The requested row. 
        public RowInfo GetRow(int index)
        { 
            if (index < 0 || index > _rowCache.Count)
            {
                throw new ArgumentOutOfRangeException("index");
            } 

            return _rowCache[index]; 
        } 

        ///  
        /// Returns the row that contains the given page.
        /// Throws an exception if the given page does not exist in the current layout.
        /// 
        /// The page number to find the row for. 
        /// The requested row.
        public RowInfo GetRowForPageNumber(int pageNumber) 
        { 
            if (pageNumber < 0 || pageNumber > LastPageInCache)
            { 
                throw new ArgumentOutOfRangeException("pageNumber");
            }

            return _rowCache[GetRowIndexForPageNumber(pageNumber)]; 
        }
 
        ///  
        /// Returns the index of the row that contains the given page.
        /// Throws an exception if the given page does not exist in the current layout. 
        /// 
        /// The page number to find the row index for.
        /// The requested row index
        public int GetRowIndexForPageNumber(int pageNumber ) 
        {
            if (pageNumber < 0 || pageNumber > LastPageInCache) 
            { 
                throw new ArgumentOutOfRangeException("pageNumber");
            } 

            //Search our cache for the row that contains the page.
            //NOTE: Future perf item:
            //This search can be re-written as a binary search, which will be O(log(N)) 
            //instead of O(N).
            for (int i = 0; i < _rowCache.Count; i++) 
            { 
                RowInfo rowInfo = _rowCache[i];
                if (pageNumber >= rowInfo.FirstPage && 
                    pageNumber < rowInfo.FirstPage + rowInfo.PageCount)
                {
                    //We found the row, return the index.
                    return i; 
                }
            } 
 
            //We didn't find it.  Something is very likely wrong with our layout.
            //We'll throw, as this is an indicator that our layout cannot be trusted. 
            throw new InvalidOperationException(SR.Get(SRID.RowCachePageNotFound));
        }

        ///  
        /// Returns the row that lives at the specified vertical offset.
        ///  
        /// The vertical offset to find the corresponding row for 
        /// The index of the row that lives at the offset.
        public int GetRowIndexForVerticalOffset(double offset) 
        {
            if (offset < 0 || offset > ExtentHeight)
            {
                throw new ArgumentOutOfRangeException("offset"); 
            }
 
            //If we have no rows we'll return 0 (the top of the non-existent document) 
            if (_rowCache.Count == 0)
            { 
                return 0;
            }

            //We round the offsets and dimensions to the nearest 1/100th 
            //of a pixel to avoid decimal roundoff errors
            //that can result from scaling operations. 
            double roundedOffset = Math.Round(offset, _findOffsetPrecision); 

            //Search our cache for the Row that occupies the specified offset. 
            //NOTE: Future perf item:
            //This search can be re-written as a binary search, which will be O(log(N))
            //instead of O(N).
            for(int i=0;i<_rowCache.Count;i++) 
            {
                double rowOffset = Math.Round(_rowCache[i].VerticalOffset, _findOffsetPrecision); 
                double rowHeight = Math.Round(_rowCache[i].RowSize.Height, _findOffsetPrecision); 
                bool rowHasZeroHeight = false;
 
                //Check to see if this row has zero height (or if it appears that way due to
                //decimal precision errors with very large (1.0x10^19, for example) page heights).
                if (DoubleUtil.AreClose(rowOffset, rowOffset + rowHeight))
                { 
                    rowHasZeroHeight = true;
                } 
 
                //If the current row has Zero height (or decimal precision makes it seem that way)
                //then we'll only return this page if the offset is equal to the offset of this row. 
                //Note that when decimial precision issues cause rowHasZeroHeight to be set then
                //offset and rowOffset will also be "equal" if the offset points to this row.
                if (rowHasZeroHeight && DoubleUtil.AreClose(roundedOffset, rowOffset))
                { 
                    return i;
                } 
                //Otherwise if this row contains the passed in offset, we'll return it. 
                else if ( roundedOffset >= rowOffset &&
                          roundedOffset < rowOffset + rowHeight) 
                {
                    //We check that the bottom of the row would actually be visible -- if it won't be we use the next.
                    //If this is the last row, we use it anyway.
                    if (WithinVisibleDelta((rowOffset + rowHeight), roundedOffset) || 
                        i == _rowCache.Count - 1)
                    { 
                        return i; 
                    }
                    else 
                    {
                        //The last row was just barely not visible, so we know the next one is the one we're looking for.
                        return i + 1;
                    } 
                }
            } 
 
            //If the offset is equal to the height of the document then we can just return the
            //last row in the document. 
            //This handles the edge case caused by the fact that each row technically begins
            //"overlapping" the last -- a document with two rows of height 500.0 actually has
            //the first row extend from 0.0 to 499.999999...9 and the second starts at 500.0 and
            //goes to 999.999999...9.  For all intents and purposes, the rows don't actually overlap, 
            //but it does mean that if the offset passed in is exactly equal to ExtentHeight then
            //our algorithm won't find the row (the last row ends at ExtentHeight-0.000000...1, not ExtentHeight.) 
            if (DoubleUtil.AreClose(offset, ExtentHeight) ) 
            {
                return _rowCache.Count - 1; 
            }


            //We didn't find it.  Something is very likely wrong here, but it is not fatal. 
            //We will just return the last page in the document.
            return _rowCache.Count - 1; 
        } 

        ///  
        /// Returns the starting index and the number of rows following that occupy the specified
        /// range of vertical offsets.
        /// 
        /// The starting offset 
        /// The ending offset
        /// Returns the first visible row. 
        /// Returns the number of visible rows. 
        public void GetVisibleRowIndices(double startOffset, double endOffset, out int startRowIndex, out int rowCount)
        { 
            startRowIndex = 0;
            rowCount = 0;

            if (endOffset < startOffset) 
            {
                throw new ArgumentOutOfRangeException("endOffset"); 
            } 

            //If the offsets we're given are out of range we'll just return now 
            //because we have no rows to find at this offset.
            if (startOffset < 0 || startOffset > ExtentHeight)
            {
                return; 
            }
 
            //If we have no rows we'll return 0 for the start and count. 
            if (_rowCache.Count == 0)
            { 
                return;
            }

            //Get the first row that's visible. 
            startRowIndex = GetRowIndexForVerticalOffset(startOffset);
            rowCount = 1; 
 
            //We round the offsets and dimensions to the nearest 1/100th
            //of a pixel to avoid decimal roundoff errors 
            //that can result from scaling operations.
            startOffset = Math.Round(startOffset, _findOffsetPrecision);
            endOffset = Math.Round(endOffset, _findOffsetPrecision);
 
            //Now we continue downward until we either reach the end of the document
            //Or find a row that's outside the end offset (or close enough to it that it's not actually visible) 
            for (int i = startRowIndex + 1; i < _rowCache.Count; i++) 
            {
                double rowOffset = Math.Round(_rowCache[i].VerticalOffset, _findOffsetPrecision); 

                if ( rowOffset >= endOffset ||
                     !WithinVisibleDelta( endOffset, rowOffset ) )
                { 
                    //We've found a row that isn't visible so we're done.
                    break; 
                } 

                //This row is visible, add it to the count. 
                rowCount++;
            }
        }
 
        /// 
        /// Recalculates the current row layout by applying the current Scale 
        /// and PageSpacing to the current row layout.  It does not change the 
        /// contents of the rows.
        ///  
        public void RecalcLayoutForScaleOrSpacing()
        {
            //Throw execption if we have no PageCache
            if (PageCache == null) 
            {
                throw new InvalidOperationException(SR.Get(SRID.RowCacheRecalcWithNoPageCache)); 
            } 

            //Reset the extents 
            _extentWidth = 0.0;
            _extentHeight = 0.0;

            //Walk through each row and based on the pages on the row, 
            //recalculate the width and height of the row.
            double currentOffset = 0.0; 
            for (int i = 0; i < _rowCache.Count; i++) 
            {
                //Get this row and save off the page count. 
                RowInfo currentRow = _rowCache[i];
                int pageCount = currentRow.PageCount;

                //Clear the pages so we can add the new, rescaled ones. 
                currentRow.ClearPages();
                currentRow.VerticalOffset = currentOffset; 
 
                //Add each page to the row.
                for (int j = currentRow.FirstPage; j < currentRow.FirstPage + pageCount; j++) 
                {
                    Size pageSize = GetScaledPageSize(j);
                    currentRow.AddPage(pageSize);
                } 

                //Adjust the extent width if necessary 
                _extentWidth = Math.Max(currentRow.RowSize.Width, _extentWidth); 

                currentOffset += currentRow.RowSize.Height; 
                _extentHeight += currentRow.RowSize.Height;
                _rowCache[i] = currentRow;
            }
 
            //Fire off our RowCacheChanged event indicating that all rows have changed.
            List changes = new List(1); 
            changes.Add(new RowCacheChange(0, _rowCache.Count)); 
            RowCacheChangedEventArgs args = new RowCacheChangedEventArgs(changes);
            RowCacheChanged(this, args); 
        }

        /// 
        /// Recalculates the row layout given a starting page, and the number of pages to lay out 
        /// on each row.
        /// In the event that there aren't currently enough pages to accomplish the layout, RowCache 
        /// will wait until the pages are available and then fire off a RowLayoutCompleted event. 
        /// 
        /// The page to build the rows around 
        /// The number of columns in the "pivot row"	
        public void RecalcRows(int pivotPage, int columns)
        {
 
            //Throw execption if we have no PageCache
            if (PageCache == null) 
            { 
                throw new InvalidOperationException(SR.Get(SRID.RowCacheRecalcWithNoPageCache));
            } 

            //Throw exception for illegal values
            if (pivotPage < 0 || pivotPage > PageCache.PageCount)
            { 
                throw new ArgumentOutOfRangeException("pivotPage");
            } 
 
            //Can't lay out fewer than 1 column of pages.
            if (columns < 1) 
            {
                throw new ArgumentOutOfRangeException("columns");
            }
 
            //Store off the requested layout parameters.
            _layoutColumns = columns; 
            _layoutPivotPage = pivotPage; 

            //We've started a new layout, reset the valid layout flag. 
            _hasValidLayout = false;

            //We can't do anything here if we haven't gotten enough pages to create the first row yet.
            //But we'll save off the specified columns & pivot page (above) 
            //And as soon as we get some pages (in OnPageCacheChanged)
            //we'll start laying them out in the requested manner. 
            if (PageCache.PageCount < _layoutColumns) 
            {
                //If pagination is still happening or if we have no pages in our content 
                //we need to wait until later.
                if (!PageCache.IsPaginationCompleted || PageCache.PageCount == 0)
                {
                    //Reset our LayoutComputed flag, as we've been tasked to create a new one 
                    //but can't do it yet.
                    _isLayoutRequested = true; 
                    _isLayoutCompleted = false; 
                    return;
                } 
                //If pagination has completed, we trim down the column count and continue.
                else
                {
                    //We're done paginating, but we don't have enough 
                    //pages to do the requested layout.  So we'll need to trim down the column count, with a
                    //lower bound of 1 column. 
                    _layoutColumns = Math.Min(_layoutColumns, PageCache.PageCount); 
                    _layoutColumns = Math.Max(1, _layoutColumns);
 
                    //The pivot page is always the first page in this instance
                    _layoutPivotPage = 0;
                }
            } 

            //Reset the document extents, which will be recalculated by the RecalcRows methods. 
            _extentHeight = 0.0; 
            _extentWidth = 0.0;
 
            //Now call the specific RecalcRows... method for our document type.
            if (PageCache.DynamicPageSizes)
            {
                _pivotRowIndex = RecalcRowsForDynamicPageSizes(_layoutPivotPage, _layoutColumns); 
            }
            else 
            { 
                _pivotRowIndex = RecalcRowsForFixedPageSizes(_layoutPivotPage, _layoutColumns);
            } 

            _isLayoutCompleted = true;
            _isLayoutRequested = false;
 
            //Set the valid layout flag now that we're done
            _hasValidLayout = true; 
 
            //We've computed the layout, so we'll fire off our RowLayoutCompleted event.
            //We pass along the pivotRow's Index so the listener can keep the pivot row visible. 
            RowLayoutCompletedEventArgs args = new RowLayoutCompletedEventArgs(_pivotRowIndex);
            RowLayoutCompleted(this, args);

            //Fire off our RowCacheChanged event indicating that all rows have changed. 
            List changes = new List(1);
            changes.Add(new RowCacheChange(0, _rowCache.Count)); 
            RowCacheChangedEventArgs args2 = new RowCacheChangedEventArgs(changes); 
            RowCacheChanged(this, args2);
 
        }

        #endregion Public Methods
 
        //-----------------------------------------------------
        // 
        //  Private Properties 
        //
        //-----------------------------------------------------	 
        #region Private Properties

        /// 
        /// LastPageInCache returns the last page in the current Row layout. 
        /// If there is no layout, returns -1.
        ///  
        private int LastPageInCache 
        {
            get 
            {
                //If we have no rows, then we have no pages
                //at all in the cache.
                if (_rowCache.Count == 0) 
                {
                    return -1; 
                } 
                else
                { 
                    //Get the last row in the cache and return its
                    //last page.
                    RowInfo lastRow = _rowCache[_rowCache.Count-1];
                    return lastRow.FirstPage + lastRow.PageCount - 1; 
                }
            } 
        } 

 
        #endregion Private Properties

        //-----------------------------------------------------
        // 
        //  Private Methods
        // 
        //------------------------------------------------------	 

        ///  
        /// Helper method to determine whether two offsets are visibly different
        /// (whether they're more than a certain fraction of a pixel apart).
        /// 
        ///  
        /// 
        private bool WithinVisibleDelta(double offset1, double offset2) 
        { 
            return offset1 - offset2 > _visibleDelta;
        } 

        /// 
        /// Recalculates the row layout given the assumption that page sizes in the document may vary from
        /// page to page. 
        /// 
        /// The page which other rows are laid out around 
        /// The number of columns to fit on the row containing the pivot page. 
        /// The index of the row that contains the pivot page.
        private int RecalcRowsForDynamicPageSizes(int pivotPage, int columns) 
        {
            //Throw exception for illegal values
            if (pivotPage < 0 || pivotPage >= PageCache.PageCount)
            { 
                throw new ArgumentOutOfRangeException("pivotPage");
            } 
 
            //Can't lay out fewer than 1 column of pages.
            if (columns < 1) 
            {
                throw new ArgumentOutOfRangeException("columns");
            }
 
            //Adjust the pivot page as necessary so that the to-be-computed layout has the specified number
            //of columns on the row. 
            //(If the pivot page is the last page in the document, for example, we need to move it back 
            //by the column specification.)
            if (pivotPage + columns > PageCache.PageCount) 
            {
                pivotPage = Math.Max(0, PageCache.PageCount - columns);
            }
 
            //Clear our cache, since we're calculating a new layout.
            _rowCache.Clear(); 
 
            //Calculate this row so we can get the row width information
            //we need for the other rows. 
            RowInfo pivotRow = CreateFixedRow(pivotPage, columns);
            double pivotRowWidth = pivotRow.RowSize.Width;
            int pivotRowIndex = 0;
 
            //We work our way back up to the top of the document from the pivot
            //and recalc the rows along the way. 
            //This is necessary because page sizes vary and 
            //we want to guarantee that the pages that have been fit
            //on the pivot row are the ones displayed in that row.  If we were to start 
            //at the top and work our way down, we might not end up with the
            //same pages on this row.

            //Store off the rows we calculate here, we�ll need them later. 
            List tempRows = new List(pivotPage / columns);
            int currentPage = pivotPage; 
            while (currentPage > 0) 
            {
                //Create our row, specifying a backwards direction 
                RowInfo newRow = CreateDynamicRow(currentPage - 1, pivotRowWidth, false /* backwards */);
                currentPage = newRow.FirstPage;
                tempRows.Add(newRow);
            } 

            //We�ve made it to the top. 
            //Now we can calculate the offsets of each row and add them to the Row cache. 
            for (int i = tempRows.Count-1; i >= 0; i--)
            { 
                AddRow(tempRows[i]);
            }

            //Save off the index of this row. 
            pivotRowIndex = _rowCache.Count;
 
            //Add the pivot row (calculated earlier) 
            AddRow(pivotRow);
 
            //Now we continue working down from after the pivot to the end of the
            //document.
            currentPage = pivotPage + columns;
            while (currentPage < PageCache.PageCount) 
            {
                //Create our row, specifying a forward direction 
                RowInfo newRow = CreateDynamicRow(currentPage, pivotRowWidth, true /*forwards */); 
                currentPage += newRow.PageCount;
                AddRow(newRow); 
            }

            //And we�re done.  Whew.
            return pivotRowIndex; 
        }
 
        ///  
        /// Creates a "dynamic" row -- that is, given a maximum width for the row, it will fit as
        /// many pages on said row as possible. 
        /// 
        /// The first page to put on this row.
        /// The requested width of this row.
        /// Whether to create this row using the next N pages or the 
        /// previous N.
        ///  
        private RowInfo CreateDynamicRow(int startPage, double rowWidth, bool createForward) 
        {
            if (startPage >= PageCache.PageCount) 
            {
                throw new ArgumentOutOfRangeException("startPage");
            }
 
            //Given a starting page for this row, and the specified
            //width for each row, figure out how many pages will fit on this row 
            //and return the resulting RowInfo object. 

            //Populate the struct with initial data. 
            RowInfo newRow = new RowInfo();

            //Each row is guaranteed to have at least one page, even if it�s wider
            //than the allotted size, so we add it here.	 
            Size pageSize = GetScaledPageSize( startPage );
            newRow.AddPage(pageSize); 
 
            //Keep adding pages until we either:
            // - run out of pages to add 
            // - run out of space in the row
            for(;;)
            {
                if( createForward ) 
                {
                    //Grab the next page. 
                    pageSize = GetScaledPageSize(startPage + newRow.PageCount); 

                    //We�re out of pages, or out of space. 
                    if (startPage + newRow.PageCount >= PageCache.PageCount ||
                        newRow.RowSize.Width + pageSize.Width > rowWidth )
                    {
                        break; 
                    }
                } 
                else 
                {
                    //Grab the previous page. 
                    pageSize = GetScaledPageSize( startPage - newRow.PageCount );

                    //We�re out of pages, or out of space.
                    if( startPage - newRow.PageCount < 0 || 
                        newRow.RowSize.Width + pageSize.Width > rowWidth )
                    { 
                        break; 
                    }
                } 
                newRow.AddPage(pageSize);

                //If we've hit the hard upper limit for pages on a row then we're done with this row.
                if (newRow.PageCount == DocumentViewerConstants.MaximumMaxPagesAcross) 
                {
                    break; 
                } 
            }
 
            if (!createForward)
            {
                newRow.FirstPage = startPage - (newRow.PageCount - 1);
            } 
            else
            { 
                newRow.FirstPage = startPage; 
            }
 
            return newRow;
        }

        ///  
        /// Recalculates the row for content that does not have varying page sizes.
        ///  
        /// The first page on this row 
        /// The number of columns on this row
        /// The index of the row that contains the starting page. 
        private int RecalcRowsForFixedPageSizes(int startPage, int columns)
        {
            //Throw exception for illegal values
            if (startPage < 0 || startPage > PageCache.PageCount) 
            {
                throw new ArgumentOutOfRangeException("startPage"); 
            } 

            //Can't lay out fewer than 1 column of pages. 
            if (columns < 1)
            {
                throw new ArgumentOutOfRangeException("columns");
            } 

            //Since all pages in the document have been determined to be 
            //the same size, we can use a simple algorithm to create our row layout. 

            //We start at the top (no need to specify a pivot) 
            //and calculate the rows from there.
            //Each row will have exactly "columns" pages on it.	
            _rowCache.Clear();
 
            for (int i = 0; i < PageCache.PageCount; i += columns)
            { 
                RowInfo newRow = CreateFixedRow(i, columns); 
                AddRow(newRow);
            } 

            //Find the row the start page lives on and return its index.
            return GetRowIndexForPageNumber(startPage);
        }		 

        ///  
        /// Creates a "fixed" row -- that is, a row with a specific number of columns on it. 
        /// 
        /// The first page to live on this row. 
        /// The number of columns on this row.
        /// 
        private RowInfo CreateFixedRow(int startPage, int columns)
        { 
            if (startPage >= PageCache.PageCount)
            { 
                throw new ArgumentOutOfRangeException("startPage"); 
            }
 
            if (columns < 1 )
            {
                throw new ArgumentOutOfRangeException("columns");
            } 

            //Given a starting page for this row and the number of columns in the row 
            //calculate the width & height and return the resulting RowInfo struct 

            //Populate the struct with initial data 
            RowInfo newRow = new RowInfo();
            newRow.FirstPage = startPage;

            //Keep adding pages until we either: 
            // - run out of pages to add
            // - add the appropriate number of pages 
            for (int i = startPage; i < startPage + columns; i++) 
            {
                //We�re out of pages. 
                if (i > PageCache.PageCount - 1)
                    break;

                //Get the size of the page 
                Size pageSize = GetScaledPageSize(i);
 
                //Add this page to the row 
                newRow.AddPage(pageSize);
            } 

            return newRow;
        }
 
        /// 
        /// Given a range of pages, adds the pages to the existing row cache, 
        /// adding new rows where necessary. 
        /// 
        /// The first page to add to the layout 
        /// The number of pages to add.		
        private RowCacheChange AddPageRange(int startPage, int count)
        {
            if (!_isLayoutCompleted) 
            {
                throw new InvalidOperationException(SR.Get(SRID.RowCacheCannotModifyNonExistentLayout)); 
            } 

            int currentPage = startPage; 
            int lastPage = startPage + count;

            int startRow = 0;
            int rowCount = 0; 

            //First we check to see if startPage is such that we'd end up skipping 
            //pages in the document -- that is, if the last page in our layout is currently 
            //10 and start is 15, we need to fill in pages 11-14 as well.
            if (startPage > LastPageInCache + 1) 
            {
                currentPage = LastPageInCache + 1;
            }
 
            //Get the last row in the layout
            RowInfo lastRow = _rowCache[_rowCache.Count - 1]; 
 
            //Now we need to check to see if we can add any pages to this row
            //without exceeding the current layout's pivot row width. 

            //Get the size of the page to add
            Size pageSize = GetScaledPageSize(currentPage);
 
            //Get the pivot row
            RowInfo pivotRow = GetRow(_pivotRowIndex); 
 
            bool lastRowUpdated = false;
 
            //Add new pages to the last row until we run out of pages or space.
            while (currentPage < lastPage &&
                lastRow.RowSize.Width + pageSize.Width <= pivotRow.RowSize.Width)
            { 
                //Add the current page
                lastRow.AddPage(pageSize); 
                currentPage++; 

                //Get the size of the next page. 
                pageSize = GetScaledPageSize(currentPage);

                //Note that we updated this row so we'll update the cache when we're done here.
                lastRowUpdated = true; 
            }
 
            //If we actually made a change to the last row, then we need to update the row cache. 
            if (lastRowUpdated)
            { 
                startRow = _rowCache.Count - 1;
                //Update the last row
                UpdateRow(startRow, lastRow);
            } 
            else
            { 
                startRow = _rowCache.Count; 
            }
 
            //Now we add more rows to the layout, if we have any pages left.
            while (currentPage < lastPage)
            {
                //Build a new row. 
                RowInfo newRow = new RowInfo();
                newRow.FirstPage = currentPage; 
 
                //Add pages until we either run out of pages or need to start a new row.
                do 
                {
                    //Get the size of the next page
                    pageSize = GetScaledPageSize(currentPage);
 
                    //Add it.
                    newRow.AddPage(pageSize); 
                    currentPage++; 

                } while (newRow.RowSize.Width + pageSize.Width <= pivotRow.RowSize.Width 
                    && currentPage < lastPage);

                //Add this new row to our cache
                AddRow(newRow); 
                rowCount++;
            } 
 
            return new RowCacheChange(startRow, rowCount);
        } 

        /// 
        /// Adds a new row onto the end of the layout and updates the current layout
        /// measurements. 
        /// 
        /// The new row to add to the layout 
        private void AddRow(RowInfo newRow) 
        {
            //If this is the first row in the document we just put it at the beginning 
            if (_rowCache.Count == 0)
            {
                newRow.VerticalOffset = 0.0;
 
                //The width of the document is just the width of the first row.
                _extentWidth = newRow.RowSize.Width; 
            } 
            else
            { 
                //This is not the first row, so we put it at the end of the last row.
                RowInfo lastRow = _rowCache[_rowCache.Count - 1];

                //The new row needs to be positioned at the bottom of the last row 
                newRow.VerticalOffset = lastRow.VerticalOffset + lastRow.RowSize.Height;
 
                //Update the document's width 
                _extentWidth = Math.Max(newRow.RowSize.Width, _extentWidth);
            } 

            //Update the layout
            _extentHeight += newRow.RowSize.Height;
 
            //Add the row.
            _rowCache.Add(newRow); 
        } 

        ///  
        /// Given a preexisting page range, updates the dimensions of the rows containing said
        /// pages from the Page Cache.
        /// If any row's height changes as a result, all rows below have their offsets updated.
        /// 		 
        /// The first page changed
        /// The number of pages changed 
        private RowCacheChange UpdatePageRange(int startPage, int count) 
        {
            if (!_isLayoutCompleted) 
            {
                throw new InvalidOperationException(SR.Get(SRID.RowCacheCannotModifyNonExistentLayout));
            }
 
            //Get the row that contains the first page
            int startRowIndex = GetRowIndexForPageNumber(startPage); 
            int rowIndex = startRowIndex; 

            int currentPage = startPage; 

            //Recalculate the rows affected by the changed pages.
            while (currentPage < startPage + count && rowIndex < _rowCache.Count)
            { 
                //Get the current row
                RowInfo currentRow = _rowCache[rowIndex]; 
                //Create a new row and copy pertinent data 
                //from the old one.
                RowInfo updatedRow = new RowInfo(); 
                updatedRow.VerticalOffset = currentRow.VerticalOffset;
                updatedRow.FirstPage = currentRow.FirstPage;

                //Now rebuild this row, thus recalculating the row's size 
                //based on the new page sizes.
                for (int i = currentRow.FirstPage; i < currentRow.FirstPage + currentRow.PageCount; i++) 
                { 
                    //Get the updated page size and add it to our updated row
                    Size pageSize = GetScaledPageSize(i); 
                    updatedRow.AddPage(pageSize);
                }				

                //Update the row layout with this new row. 
                UpdateRow(rowIndex, updatedRow);
 
                //Move to the next group of pages. 
                currentPage = updatedRow.FirstPage + updatedRow.PageCount;
 
                //Move to the next row.
                rowIndex++;
            }
 
            return new RowCacheChange(startRowIndex, rowIndex - startRowIndex);
        } 
 
        /// 
        /// Updates the cache entry at the given index with the new RowInfo supplied. 
        /// If the new row has a different height than the old one, we need to update
        /// the offsets of all the rows below this one and adjust the vertical extent appropriately.
        /// If it has a different width, we just need to update the horizontal extent appropriately.
        ///  
        /// The index of the row to update
        /// The new RowInfo to replace the old 
        private void UpdateRow(int index, RowInfo newRow) 
        {
            if (!_isLayoutCompleted) 
            {
                throw new InvalidOperationException(SR.Get(SRID.RowCacheCannotModifyNonExistentLayout));
            }
 
            //Check for invalid indices.  If it's out of range then we just return.
            if (index > _rowCache.Count) 
            { 
                Debug.Assert(false, "Requested to update a non-existent row.");
                return; 
            }

 	        //Get the current entry.
	        RowInfo oldRowInfo = _rowCache[index]; 

            //Replace the old with the new. 
            _rowCache[index] = newRow; 

            //Compare the heights -- if they differ we need to update the rows beneath it. 
            if (oldRowInfo.RowSize.Height != newRow.RowSize.Height)
            {
                //The new row has a different height, so we add the delta
                //between the old and the new to every row below this one. 
                double delta = newRow.RowSize.Height - oldRowInfo.RowSize.Height;
                for(int i = index + 1; i < _rowCache.Count; i++) 
                { 
                    RowInfo row = _rowCache[i];
                    row.VerticalOffset += delta; 
                    _rowCache[i] = row;
                }

                //Add the delta for this row to our vertical extent. 
                _extentHeight += delta;				
            } 
 
            //If the new row is wider than the current document's width, then we
            //can just update the document width now. 
            if (newRow.RowSize.Width > _extentWidth)
            {
                _extentWidth = newRow.RowSize.Width;
            } 
 	        //Otherwise, if the widths differ we need to recalculate the width
            //of the document again. 
            //(The logic is that this particular row could have defined the extent width, 
            //and now that it's changed size the extent may change as well.)
            else if (oldRowInfo.RowSize.Width != newRow.RowSize.Width) 
            {
                //The width of this row has changed.
                //This means we need to recalculate the width of the entire document
                //by walking through each row. 
                _extentWidth = 0;
                for (int i = 0; i < _rowCache.Count; i++) 
                { 
                    RowInfo row = _rowCache[i];
                    //Update the extent width. 
                    _extentWidth = Math.Max(row.RowSize.Width, _extentWidth);
                }
            }
        } 

        ///  
        /// Trims any pages (and rows) from the end of the layout, starting with the specified page. 
        /// 
        /// The first page to remove. 
        private RowCacheChange TrimPageRange(int startPage)
        {
            //First, we find the row the last page is on.
            int rowIndex = GetRowIndexForPageNumber(startPage); 

            //Now we replace this row with a new row that has the deleted pages 
            //removed, if there are pages on this row that aren't going to be deleted. 
            RowInfo oldRow = GetRow(rowIndex);
 
            if (oldRow.FirstPage < startPage)
            {
                RowInfo updatedRow = new RowInfo();
                updatedRow.VerticalOffset = oldRow.VerticalOffset; 
                updatedRow.FirstPage = oldRow.FirstPage;
 
                for (int i = oldRow.FirstPage; i < startPage; i++) 
                {
                    //Get the page we're interested in. 
                    Size pageSize = GetScaledPageSize(i);
                    //Add it.
                    updatedRow.AddPage(pageSize);
                } 

                UpdateRow(rowIndex, updatedRow); 
 
                //Increment the rowIndex, since we're going to keep this row.
                rowIndex++; 
            }

            int removeCount = _rowCache.Count - rowIndex;
 
            //Now remove all the rows below this one.
            if(rowIndex < _rowCache.Count ) 
            { 
                _rowCache.RemoveRange( rowIndex, removeCount);
            } 

            //Update our extents
            _extentHeight = oldRow.VerticalOffset;
 
            return new RowCacheChange(rowIndex, removeCount);
        } 
 
        /// 
        /// Helper function that returns the dimensions of a page 
        /// with the current Scale factor and Spacing applied.
        /// 
        /// The page to retrieve page size info about.
        /// The padded and scaled size of the given page. 
        private Size GetScaledPageSize(int pageNumber)
        { 
            //GetPageSize will return (0,0) for out-of-range pages. 
            Size pageSize = PageCache.GetPageSize(pageNumber);
 
            if (pageSize.IsEmpty)
            {
                pageSize = new Size(0, 0);
            } 

            pageSize.Width *= Scale; 
            pageSize.Height *= Scale; 
            pageSize.Width += HorizontalPageSpacing;
            pageSize.Height += VerticalPageSpacing; 

            return pageSize;
        }
 
        /// 
        /// Event Handler for the PageCacheChanged event.  Called whenever an entry in the 
        /// PageCache is changed.  When this happens, the RowCache needs to Add, Remove, or 
        /// Update row entries corresponding to the pages changed.
        ///  
        /// The object that sent this event.
        /// The associated arguments.
        private void OnPageCacheChanged(object sender, PageCacheChangedEventArgs args)
        { 
            //If we have a computed layout then we'll add/remove/frob rows to conform
            //to that layout. 
            if (_isLayoutCompleted) 
            {
                List changes = new List(args.Changes.Count); 
                for (int i = 0; i < args.Changes.Count; i++)
                {
                    PageCacheChange pageChange = args.Changes[i];
                    switch (pageChange.Type) 
                    {
                        case PageCacheChangeType.Add: 
                        case PageCacheChangeType.Update: 
                            if (pageChange.Start > LastPageInCache)
                            { 
                                //Completely new pages, so we add new cache entries
                                RowCacheChange change = AddPageRange(pageChange.Start, pageChange.Count);
                                if (change != null)
                                { 
                                    changes.Add(change);
                                } 
                            } 
                            else
                            { 
                                if (pageChange.Start + pageChange.Count - 1 <= LastPageInCache)
                                {
                                    //All pre-existing pages, so we just update the current cache entries.
                                    RowCacheChange change = UpdatePageRange(pageChange.Start, pageChange.Count); 
                                    if (change != null)
                                    { 
                                        changes.Add(change); 
                                    }
                                } 
                                else
                                {
                                    //Some pre-existing pages, some new.
                                    RowCacheChange change; 
                                    change = UpdatePageRange(pageChange.Start, LastPageInCache - pageChange.Start);
                                    if (change != null) 
                                    { 
                                        changes.Add(change);
                                    } 
                                    change = AddPageRange(LastPageInCache + 1, pageChange.Count - (LastPageInCache - pageChange.Start));
                                    if (change != null)
                                    {
                                        changes.Add(change); 
                                    }
                                } 
                            } 
                            break;
 
                        case PageCacheChangeType.Remove:
                            //If PageCount is now less than the size of our cache due to repagination
                            //we remove the corresponding entries from our row cache.
                            if (PageCache.PageCount - 1 < LastPageInCache) 
                            {
                                //Remove pages starting at the first no-longer-existent page. 
                                //(PageCache.PageCount now points to the first dead page) 
                                RowCacheChange change = TrimPageRange(PageCache.PageCount);
                                if (change != null) 
                                {
                                    changes.Add(change);
                                }
                            } 

                            //If because of the above trimming we have fewer pages left 
                            //in the document than the columns that were initially requested 
                            //We'll need to recalc our layout from scratch.
                            //First we check to see if we have one or fewer rows left. 
                            if (_rowCache.Count <= 1)
                            {
                                //If we have either no rows left or the remaining row has
                                //less than _layoutColumns pages on it, we need to recalc from the first page. 
                                if (_rowCache.Count == 0 || _rowCache[0].PageCount < _layoutColumns )
                                { 
                                    RecalcRows(0, _layoutColumns); 
                                }
                            } 
                            break;

                        default:
                            throw new ArgumentOutOfRangeException("args"); 
                    }
                } 
 
                RowCacheChangedEventArgs newArgs = new RowCacheChangedEventArgs(changes);
                RowCacheChanged(this, newArgs); 
            }
            else if (_isLayoutRequested)
            {
                //We've had a request to create a layout previously, but didn't have enough pages to do so before. 
                //Try it now.
                RecalcRows(_layoutPivotPage, _layoutColumns); 
            } 
        }
 
        /// 
        /// Handler for the OnPaginationCompleted event.  If we still have an unfulfilled
        /// layout request, we'll call RecalcRows to ensure that we get a layout (though possibly
        /// with fewer columns than requested.) 
        /// 
        /// The sender of this event 
        /// The arguments associated with this event 
        private void OnPaginationCompleted(object sender, EventArgs args)
        { 
            if (_isLayoutRequested)
            {
                //We've had a request to create a layout previously, but we don't have enough
                //pages to do it.  We'll call RecalcRows here which will now properly trim down 
                //the column count.
                RecalcRows(_layoutPivotPage, _layoutColumns); 
            } 
        }
 
        //-----------------------------------------------------
        //
        //  Private Fields
        // 
        //------------------------------------------------------
 
        //The List that contains our row cache. 
        private List       _rowCache;
 
        //Default Row Layout parameters:
        private int                 _layoutPivotPage;
        private int                 _layoutColumns;
 
        //The index of the pivot row
        private int                 _pivotRowIndex; 
 
        //Reference to our PageCache
        private PageCache			_pageCache;		 

        //Flag indicating if we've been asked for a row layout.
        private bool                _isLayoutRequested;
        private bool                _isLayoutCompleted; 

        //Data for CLR properties. 
        private double              _verticalPageSpacing; 
        private double              _horizontalPageSpacing;
        private double              _scale=1.0; 
        private double              _extentHeight;
        private double              _extentWidth;
        private bool                _hasValidLayout;
 
        private readonly int        _defaultRowCacheSize = 32;
 
        //This is the number of digits of precision we round to when searching for Rows given an offset. 
        //We do this to avoid returning extraneous pages which are "visible" only by a few hundredths of
        //a pixel (i.e. not really visible). 
        private readonly int        _findOffsetPrecision = 2;

        //This is the fraction of a pixel of overlap required for a given offset to be considered "visible."
        private readonly double     _visibleDelta = 0.5; 

        } 
 
        /// 
        /// The RowInfo class represents a single row in the document 
        /// layout and contains the necessary data to compute the location
        /// and render a given row of pages.
        /// 
        internal class RowInfo 
        {
        ///  
        /// Constructor for a RowInfo object. 
        /// 
        public RowInfo() 
        {
            //Create our RowSize, which is (0,0) by default.
            _rowSize = new Size(0,0);
        } 

        ///  
        /// Adds a page to this Row. 
        /// Increments the PageCount and updates the RowSize.
        ///  
        /// 
        public void AddPage( Size pageSize )
        {
            //Add the page to this row. 
            _pageCount++;
 
            //Update the row's dimensions 
            _rowSize.Width += pageSize.Width;
            _rowSize.Height = Math.Max(pageSize.Height, _rowSize.Height); 
        }

        /// 
        /// Removes all pages from this Row. 
        /// 
        public void ClearPages() 
        { 
            _pageCount = 0;
            _rowSize.Width = 0.0; 
            _rowSize.Height = 0.0;
        }

        ///  
        /// The dimensions of this row.
        ///  
        public Size RowSize 
        {
            get 
            {
                return _rowSize;
            }
        } 

        ///  
        /// The offset at which this row appears in the document 
        /// 
        public double VerticalOffset 
        {
            get
            {
                return _verticalOffset; 
            }
 
            set 
            {
                _verticalOffset = value; 
            }
        }

        ///  
        /// The first page on this row.
        ///  
        public int FirstPage 
        {
            get 
            {
                return _firstPage;
            }
 
            set
            { 
                _firstPage = value; 
            }
        } 

        /// 
        /// The number of pages on this row.
        ///  
        public int PageCount
        { 
            get 
            {
                return _pageCount; 
            }
        }

        private Size    _rowSize; 
        private double  _verticalOffset;
        private int     _firstPage; 
        private int     _pageCount; 

    } 

    /// 
    ///RowCacheChanged event handler.
    ///  
    internal delegate void RowCacheChangedEventHandler(object sender, RowCacheChangedEventArgs e);
 
    ///  
    ///RowLayoutCompleted event handler.
    ///  
    internal delegate void RowLayoutCompletedEventHandler(object sender, RowLayoutCompletedEventArgs e);

    /// 
    /// Event arguments for the RowLayoutCompleted event. 
    /// 
    internal class RowLayoutCompletedEventArgs : EventArgs 
    { 
        /// 
        /// Constructor. 
        /// 
        ///The index of the row to keep visible (the pivot row)
        public RowLayoutCompletedEventArgs(int pivotRowIndex)
        { 
            _pivotRowIndex = pivotRowIndex;
        } 
 
        /// 
        /// The index of the row to be kept visible by DocumentGrid. 
        /// 
        public int PivotRowIndex
        {
            get 
            {
                return _pivotRowIndex; 
            } 
        }
 
        private readonly int _pivotRowIndex;
    }

    ///  
    /// Event arguments for the RowCacheChanged event.
    ///  
    internal class RowCacheChangedEventArgs : EventArgs 
    {
        ///  
        /// Constructor.
        /// 
        /// The changes corresponding to this event
        public RowCacheChangedEventArgs(List changes) 
        {
            _changes = changes; 
        } 

        ///  
        /// The changes corresponding to this event.
        /// 
        public List Changes
        { 
            get
            { 
                return _changes; 
            }
        } 

        private readonly List _changes;
    }
 
    /// 
    /// Represents a single change to the RowCache 
    ///  
    internal class RowCacheChange
    { 
        /// 
        /// Constructor.
        /// 
        /// The first row changed 
        /// The number of rows changed
        public RowCacheChange(int start, int count) 
        { 
            _start = start;
            _count = count; 
        }

        /// 
        /// Zero-based page number for this first row that has changed. 
        /// 
        public int Start 
        { 
            get
            { 
                return _start;
            }
        }
 
        /// 
        /// Number of continuous rows changed. 
        ///  
        public int Count
        { 
            get
            {
                return _count;
            } 
        }
 
        private readonly int _start; 
        private readonly int _count;
    } 



} 

// 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