/// <summary> /// Given a range of pages, adds the pages to the existing row cache, /// adding new rows where necessary. /// </summary> /// <param name="startPage">The first page to add to the layout</param> /// <param name="count">The number of pages to add.</param> 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); }
/// <summary> /// Creates a "dynamic" row -- that is, given a maximum width for the row, it will fit as /// many pages on said row as possible. /// </summary> /// <param name="startPage">The first page to put on this row.</param> /// <param name="rowWidth">The requested width of this row.</param> /// <param name="createForward">Whether to create this row using the next N pages or the /// previous N.</param> /// <returns></returns> 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; }
/// <summary> /// Creates a "fixed" row -- that is, a row with a specific number of columns on it. /// </summary> /// <param name="startPage">The first page to live on this row.</param> /// <param name="columns">The number of columns on this row.</param> /// <returns></returns> 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; }
/// <summary> /// 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. /// </summary> /// <param name="index">The index of the row to update</param> /// <param name="newRow">The new RowInfo to replace the old</param> 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); } } }
/// <summary> /// Trims any pages (and rows) from the end of the layout, starting with the specified page. /// </summary> /// <param name="startPage">The first page to remove.</param> 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); }
/// <summary> /// Adds a new row onto the end of the layout and updates the current layout /// measurements. /// </summary> /// <param name="newRow">The new row to add to the layout</param> 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); }
/// <summary> /// 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. /// </summary> /// <param name="startPage">The first page changed</param> /// <param name="count">The number of pages changed</param> 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); }
/// <summary> /// Helper function that calculates the Horizontal offset of the given page. /// </summary> /// <param name="row">The row which the desired page lives on</param> /// <param name="pageNumber">The page to find the offset for</param> /// <returns>The Horizontal offset of the page in the document.</returns> private double GetHorizontalOffsetForPage( RowInfo row, int pageNumber ) { if (row == null) { throw new ArgumentNullException("row"); } if (pageNumber < row.FirstPage || pageNumber > row.FirstPage + row.PageCount) { throw new ArgumentOutOfRangeException("pageNumber"); } //Rows are centered if the content has varying page sizes, //Left-aligned otherwise. double horizontalOffset = _pageCache.DynamicPageSizes ? Math.Max(0.0, (ExtentWidth - row.RowSize.Width) / 2.0) : 0.0; //Add the widths of the pages (and spacing) prior to this one on the row for (int i = row.FirstPage; i < pageNumber; i++) { Size pageSize = _pageCache.GetPageSize(i); horizontalOffset += pageSize.Width * Scale + HorizontalPageSpacing; } return horizontalOffset; }
private double CalculateScaleFactor(RowInfo pivotRow) { //Determine the dimensions of this row minus any spacing between the pages. //We use this as the baseline for our scale factor as page spacing does not scale. double rowWidth; //If the page sizes vary, we use the width of the pivot row, //otherwise we use the overall width of the document (ExtentWidth). //(For uniform page sizes, we always use the width of the document, even //for the last row which may not have the same width as the rest of the document). if (_pageCache.DynamicPageSizes) { rowWidth = pivotRow.RowSize.Width - pivotRow.PageCount * HorizontalPageSpacing; } else { rowWidth = ExtentWidth - MaxPagesAcross * HorizontalPageSpacing; } double rowHeight = pivotRow.RowSize.Height - VerticalPageSpacing; //If we have row dimensions of zero or less, there's no reason to scale anything. //So just return 1.0 to indicate no change. if (rowWidth <= 0.0 || rowHeight <= 0.0) { return 1.0; } //The dimensions of our Viewport minus any spacing. We use this as the baseline for our //scale factor as page spacing does not scale. double compensatedViewportWidth; if (_pageCache.DynamicPageSizes) { compensatedViewportWidth = ViewportWidth - pivotRow.PageCount * HorizontalPageSpacing; } else { compensatedViewportWidth = ViewportWidth - MaxPagesAcross * HorizontalPageSpacing; } double compensatedViewportHeight = ViewportHeight - VerticalPageSpacing; //If we have no space to display pages, there's nothing to scale. //So just return 1.0 to indicate no change. if (compensatedViewportWidth <= 0.0 || compensatedViewportHeight <= 0.0) { return 1.0; } double scaleFactor = 1.0; //Based on the previously determined ViewMode (set in SetColumns(), FitToWidth(), etc.. //scale the pages appropriately. switch (_documentLayout.ViewMode) { case ViewMode.SetColumns: //We leave the scale factor as is -- this is not a "page-fit" mode. break; case ViewMode.FitColumns: //Update the scale factor so that the pivot row is completely visible. scaleFactor = Math.Min(compensatedViewportWidth / rowWidth, compensatedViewportHeight / rowHeight); break; case ViewMode.PageWidth: //Update the scale factor so that the pivot row is as wide as the viewport. scaleFactor = compensatedViewportWidth / rowWidth; break; case ViewMode.PageHeight: //Update the scale factor so that the pivot row is as tall as the viewport. scaleFactor = compensatedViewportHeight / rowHeight; break; case ViewMode.Thumbnails: //Update the scale factor so that the _entire layout_ is completely visible. As in previous //cases we must compensate for the fact that the spacing between pages does not scale. //However, unlike in previous cases, we must exclude the space between all rows rather //merely one space, so we must recalculate the compensated values. Furthermore we must //also compensate for the ExtentHeight as well since it includes the spaces. double thumbnailCompensatedExtentHeight = ExtentHeight - VerticalPageSpacing * _rowCache.RowCount; double thumbnailCompensatedViewportHeight = ViewportHeight - VerticalPageSpacing * _rowCache.RowCount; //If we have no space to display pages, there's nothing to scale. //So just return 1.0 to indicate no change. if (thumbnailCompensatedViewportHeight <= 0.0) { scaleFactor = 1.0; } else { scaleFactor = Math.Min(compensatedViewportWidth / rowWidth, thumbnailCompensatedViewportHeight / thumbnailCompensatedExtentHeight); } break; case ViewMode.Zoom: //We will not change the scale here, as this is not a "page-fit" mode. break; default: throw new InvalidOperationException(SR.Get(SRID.DocumentGridInvalidViewMode)); } return scaleFactor; }
/// <summary> /// Given a pivot row and a previously set ViewMode, the scale is adjusted so as /// to cause the pivot row to be fit based on the specified ViewMode. /// </summary> /// <param name="pivotRow"></param> private void ApplyViewParameters(RowInfo pivotRow) { //Update our MaxPagesAcross property to the number of rows on the pivot row //if page sizes vary. (If page sizes are uniform, this value will not change as a result of //a layout change) if (_pageCache.DynamicPageSizes) { _maxPagesAcross = pivotRow.PageCount; } //Get the scale factor necessary to fit the given row into the Viewport. double scaleFactor = CalculateScaleFactor(pivotRow); //Calculate the new scale. We multiply our scale factor by the old scale factor to cancel out any //previously applied scale. double newScale = scaleFactor * _rowCache.Scale; //Clip the value into the acceptable range newScale = Math.Max(newScale, CurrentMinimumScale); newScale = Math.Min(newScale, DocumentViewerConstants.MaximumScale); //Update the Row Layout's scale. UpdateLayoutScale(newScale); }
/// <summary> /// Checks that the current scale factor is optimal for the passed in row. /// </summary> /// <param name="pivotRow">The Row to pass to the Delegate</param> private void EnsureFit(RowInfo pivotRow) { //Get the scale factor necessary to fit this row into view. //If the result is not 1.0 (within a certain margin of error) //then we need to re-layout, alas. double neededScaleFactor = CalculateScaleFactor(pivotRow); double newScale = neededScaleFactor * _rowCache.Scale; //If the neededScaleFactor would require DocumentGrid scale the pages //below the minimum allowed zoom, or above the maximum, then we won't //do anything here. if (newScale < CurrentMinimumScale || newScale > DocumentViewerConstants.MaximumScale) { return; } if (!DoubleUtil.AreClose(1.0, neededScaleFactor)) { //Rescale the row. ApplyViewParameters( pivotRow ); //Make the row visible again -- the offsets may have //changed due to the above rescaling. SetVerticalOffsetInternal(pivotRow.VerticalOffset); } }
/// <summary> /// Helper function that indicates whether all the pages on the specified /// row point to clean cache entries. /// </summary> /// <param name="row"></param> /// <returns></returns> private bool RowIsClean(RowInfo row) { bool clean = true; for (int i = row.FirstPage; i < row.FirstPage + row.PageCount; i++) { if (_pageCache.IsPageDirty(i)) { clean = false; break; } } return clean; }