/// ------------------------------------------------------------------------------------ /// <summary> /// Inserts a new page after the given one. /// </summary> /// <param name="ypOffsetFromTopOfDiv">The offset from top of this division to the /// start of the page to insert.</param> /// <param name="pageToInsertAfter">The page to insert after.</param> /// <returns>The newly inserted page</returns> /// ------------------------------------------------------------------------------------ private Page InsertPage(int ypOffsetFromTopOfDiv, Page pageToInsertAfter) { return m_publication.InsertPage(m_publication.IndexOfDiv(this), ypOffsetFromTopOfDiv, pageToInsertAfter); }
/// ------------------------------------------------------------------------------------ /// <summary> /// Find the page following the given page. This is broken out as a method in case /// we decide to change to a linked list or something more complex. /// </summary> /// <param name="page"></param> /// <returns></returns> /// ------------------------------------------------------------------------------------ public Page PageAfter(Page page) { CheckDisposed(); int iPage = IndexOfPage(page) + 1; if (iPage >= m_pages.Count) return null; return m_pages[iPage]; }
/// ------------------------------------------------------------------------------------ /// <summary> /// Add the page element. /// </summary> /// <param name="page">The page.</param> /// <param name="dysSpaceUsedOnPage">The amount of vertical space taken up by this /// element on this page.</param> /// <param name="currentColumn">The 1-based index of the current column.</param> /// <param name="numberColumns">The total number columns for the stream.</param> /// <param name="leftMargin">The left margin.</param> /// <param name="offsetFromTopOfDiv">The offset from top of division.</param> /// <param name="columnHeight">The height of the column.</param> /// <param name="dypOverlapWithPreviousElement"></param> /// ------------------------------------------------------------------------------------ protected void AddElement(Page page, int dysSpaceUsedOnPage, int currentColumn, int numberColumns, int leftMargin, int offsetFromTopOfDiv, int columnHeight, int dypOverlapWithPreviousElement) { Debug.Assert(!page.IsDisposed); Rectangle rect = page.GetElementBounds(this, dysSpaceUsedOnPage, currentColumn, numberColumns, leftMargin, offsetFromTopOfDiv, columnHeight); page.AddPageElement(this, MainLayoutStream, false, rect, offsetFromTopOfDiv, true, currentColumn, numberColumns, ColumnGapWidthInPrinterPixels, columnHeight, dypOverlapWithPreviousElement, MainStreamIsRightToLeft, true); }
/// ------------------------------------------------------------------------------------ /// <summary> /// Add page header and footer elements to the given page. /// </summary> /// <param name="page">The page to add the header to</param> /// <param name="xpLeftMargin">Left margin in printer pixels (we could recalc this, but /// since the caller already has it, it's faster to just pass it)</param> /// ------------------------------------------------------------------------------------ private void AddHeaderAndFooter(Page page, int xpLeftMargin) { // Create the header stream IHeaderFooterConfigurer hfconfig = m_configurer.HFConfigurer; if (hfconfig == null) { // This configurer doesn't think we need headers and footers at all (test only?) return; } // TODO: re-use existing header/footer on page. Currently we create a new one // everytime we layout the page (e.g. after inserting a footnote). // TODO (TE-5845): If this is the first page in the division and this division starts // on a new page, treat it as the first page for the purpose of deciding which // PubHeaderFooter to use for laying out. int hvoHdrRoot = hfconfig.GetHvoRoot(page.PageNumber, true, m_fDifferentFirstHF, m_fDifferentEvenHF); if (hvoHdrRoot > 0) { int dypHdrHeight; var hfVc = hfconfig.MakeHeaderVc(page); m_CreatedHfVcs.Add(hfVc); IVwLayoutStream hdrStream = CreateHeaderOrFooter(hfVc, hvoHdrRoot, xpLeftMargin, m_dympHeaderPos, out dypHdrHeight); PublicationControl.SetAccessibleStreamName(hdrStream, Publication.AccessibleName + "_Header"); // Add the header to the page's collection of elements int ypHeaderPosInPrinterPixels = (int)(m_dympHeaderPos * Publication.DpiYPrinter / MiscUtils.kdzmpInch) - dypHdrHeight; Rectangle locationOnPage = new Rectangle(xpLeftMargin, ypHeaderPosInPrinterPixels, AvailablePageWidthInPrinterPixels, dypHdrHeight); page.AddPageElement(this, hdrStream, true, locationOnPage, 0, false, 1, 1, 0, dypHdrHeight, 0, MainStreamIsRightToLeft, false); } int hvoFtrRoot = hfconfig.GetHvoRoot(page.PageNumber, false, m_fDifferentFirstHF, m_fDifferentEvenHF); if (hvoFtrRoot > 0) { int dypFtrHeight; var hfVc = hfconfig.MakeFooterVc(page); m_CreatedHfVcs.Add(hfVc); IVwLayoutStream ftrStream = CreateHeaderOrFooter(hfVc, hvoFtrRoot, xpLeftMargin, m_dympFooterPos, out dypFtrHeight); PublicationControl.SetAccessibleStreamName(ftrStream, Publication.AccessibleName + "_Footer"); // Add the footer to the page's collection of elements int ypFooterPosInPrinterPixels = Publication.PageHeightInPrinterPixels - (int)(m_dympFooterPos * Publication.DpiYPrinter / MiscUtils.kdzmpInch); Rectangle locationOnPage = new Rectangle(xpLeftMargin, ypFooterPosInPrinterPixels, AvailablePageWidthInPrinterPixels, dypFtrHeight); page.AddPageElement(this, ftrStream, true, locationOnPage, 0, false, 1, 1, 0, dypFtrHeight, 0, MainStreamIsRightToLeft, false); } }
/// ------------------------------------------------------------------------------------ /// <summary> /// Scroll the given or current selection into view. /// </summary> /// <param name="sel"></param> /// <param name="ssoFlag"></param> /// ------------------------------------------------------------------------------------ public bool ScrollSelectionIntoView(IVwSelection sel, VwScrollSelOpts ssoFlag) { CheckDisposed(); IVwSelection vwsel = sel; if (vwsel == null) { if (FocusedRootBox != null) vwsel = FocusedRootBox.Selection; } if (vwsel == null) return false; // nothing to do, no selection IVwGraphics vg = PrinterGraphics; try { // Loop until we have figured a scroll offset and confirmed the pages that // are visible there are laid out ready to draw. for (; ; ) { // We pass this as source AND destination rectangle to get the selection // location relative to the whole document in printer coordinates. // Rect rcSrc = new Rect(0, 0, (int)DpiXPrinter, (int)DpiYPrinter); bool fEndBeforeAnchor; Rectangle rcSelPrinter; int clientWidthPrinter = ConvertScreenDistanceToPrinterX(ClientRectangle.Width, DpiXScreen); rcSelPrinter = GetSelectionRectangle(vwsel, vg, out fEndBeforeAnchor); // At this point, rcIdeal is a rectangle that we want on the screen, in // printer coordinates relative to the top of the document. IVwLayoutStream stream = (IVwLayoutStream)(vwsel.RootBox); int iDivTop; // index of division containing top of ideal rectangle int dysPageTopScreen; // top of page to top of rectangle in screen coords. bool layedOutPage; Page pageTop = PageFromPrinterY(rcSelPrinter.Left, rcSelPrinter.Top, false, stream, out dysPageTopScreen, out iDivTop, out layedOutPage); if (layedOutPage) continue; int iDivBottom; // index of division containing bottom of ideal rectangle int dysPageBottomScreen; // top of page to bottom of rectangle in screen coords. Page pageBottom = PageFromPrinterY(rcSelPrinter.Right, rcSelPrinter.Bottom, true, stream, out dysPageBottomScreen, out iDivBottom, out layedOutPage); if (layedOutPage) continue; // Enhance: this happens when moving within a secondary stream when the page that // will eventually contain the footnote (or whatever) has not been laid out. // This generic code has no idea which page will eventually contain the anchor for // this object. // Options: // 1. Leave it like this...fail to make the selection visible. (Maybe dialog?) // 2. Create real pages sequentially through the document until PageFromPrinterY // succeeds. (This might take a while!). // 3. Make use of a virtual function, the default version of which fails, // which can be asked, given a selection in a secondary stream, for a selection at // the corresponding anchor. (This might be useful for other purposes...could be a // method of the configurer.) Then we lay out the page that contains the anchor, // after which we should be able to retry PageFromPrinterY successfully. if (pageTop == null || pageBottom == null) return false; // If those aren't properly laid-out pages, make them so and try again. // We repeat all the logic, because making them real may have expanded lazy // boxes and invalidated all the positions we worked out. // The process must terminate, because eventually (in a worst case) all pages are // laid out, all lazy boxes expanded. In practice it should terminate much sooner. if (EnsureRealBoxAt(pageTop) || EnsureRealBoxAt(pageBottom)) continue; Page oldPageBeingLaidOut = m_pageBeingLaidOut; try { m_pageBeingLaidOut = pageTop; if (pageTop.LayOutIfNeeded()) continue; m_pageBeingLaidOut = pageBottom; if (pageBottom.LayOutIfNeeded()) continue; } finally { // We need to restore the page that was being laid out if we got here // during a layout in another place like PrepareToDrawPages() m_pageBeingLaidOut = oldPageBeingLaidOut; } int indexOfTopPage = IndexOfPage(pageTop); int indexOfBottomPage = indexOfTopPage; if (pageTop != pageBottom) indexOfBottomPage = IndexOfPage(pageBottom); int ysTopOfTopPageScreen = indexOfTopPage * PageHeightPlusGapInScreenPixels; int ysTopOfBottomPageScreen = indexOfBottomPage * PageHeightPlusGapInScreenPixels; int ysTopOfIdealScreen = dysPageTopScreen + ysTopOfTopPageScreen; int ysBottomOfIdealScreen = dysPageBottomScreen + ysTopOfBottomPageScreen; int scrollPosScreen = -AutoScrollPosition.Y; int clientHeightScreen = ClientRectangle.Height; // The amount we need to scroll by. This gets subtracted from the distance // we are scrolled (autoscrollposition.Y = -autoscrollposition.Y - // dysScrollPos), so a positive value causes the document to move up in the // window (thumb moves down). int dysScrollPosScreen = 0; // distance we need to scroll (positive = down) // The screen height of the rectangle we want to be visible. int dysIdealHeightScreen = ysBottomOfIdealScreen - ysTopOfIdealScreen; // The amount of space to spare...what we have to play with as we // decide where on the screen to try to place the rectangle we want to see. int dysSlackScreen = clientHeightScreen - dysIdealHeightScreen; // Reduce the height of the rectangle we want to make visible,if necessary, so // it fits on the client window. Reduce it at the 'anchor' not the 'end'. if (dysSlackScreen < 0) { if (!fEndBeforeAnchor) { ysTopOfIdealScreen -= dysSlackScreen; } else ysBottomOfIdealScreen += dysSlackScreen; dysIdealHeightScreen = clientHeightScreen; dysSlackScreen = 0; } if (rcSelPrinter.Width > clientWidthPrinter) { int delta = rcSelPrinter.Width - clientWidthPrinter; rcSelPrinter.Width = rcSelPrinter.Width - delta; } if (scrollPosScreen + clientHeightScreen < ysBottomOfIdealScreen) { // need to move doc up in window: dysScrollPos is negative dysScrollPosScreen = scrollPosScreen + clientHeightScreen - ysBottomOfIdealScreen; } else if (scrollPosScreen > ysTopOfIdealScreen) { // need to move doc down in window: dysScrollPos is positive. dysScrollPosScreen = scrollPosScreen - ysTopOfIdealScreen; } if (dysScrollPosScreen == 0) { // No need to move!! Unless...maybe we moved a long way, and redrawing // is going to expand a lazy box above our selection on the screen, and // it will grow so much we can't see the selection after all... // This ensures that everything in the client rectangle is real boxes. PrepareToDrawPages(0, clientHeightScreen); if (!vwsel.IsValid) { // At least one case where it might not be is typing in a footnote using smushing. // If the height of the footnote paragraph changes it gets regenerated. A new substitute // selection is created, but the old actual selection object isn't valid. // Of course, it's POSSIBLE that the selection we're trying to scroll isn't the // focussed root box's one, but that's the best guess I can see to make. It's right // for the only case so far where this has happened. vwsel = FocusedRootBox.Selection; } Rect rcPrimary2 = GetSelectionRectangle(vwsel, vg, out fEndBeforeAnchor); if (rcPrimary2.top != rcSelPrinter.Top) continue; // PrepareToDrawPages expanded something, try again. return true; } if (dysScrollPosScreen > 0) { // doc moving down. dysScrollPos as currently computed will put the rectangle // we want to see at the very top of the screen. That is good both for // case default (it's the smallest movement that works) and for a request // to put it at the top. Since it's at the top, no previous page can be // visible above it, so we will just use this value. // It we wanted to, say, center it, we would add half of dysSlack, then // make sure no previous page is showing by taking the min of that and // the distance to move the top of page to top of screen. } else { // doc moving up. (-dysScrollPos) is the distance to align the // bottom of the ideal rectangle with the bottom of the screen. // That is our default. If aligning to the top, we want to move more. if (ssoFlag == VwScrollSelOpts.kssoNearTop) { // To align the tops instead, make dysScrollPos more negative // by the difference between the rectangle heights. dysScrollPosScreen -= dysSlackScreen; } // Now we need to check that no previous page is visible. We avoid showing // a previous page partly because it tends to put a lot of white space // on the screen that may not be helpful to the user, but mainly because // it is possible that the previous page has not been laid out, and // laying it out will expand lazy boxes and mess up our prediction of // where the ideal rectangle will appear. We could all PrepareToDraw to // check whether this happens and then 'continue' to try again if // necessary, but we suspect it would make things jerky. int ysTopOfTopPageScrn = ysTopOfTopPageScreen + AutoScrollPosition.Y; if (ysTopOfTopPageScrn + dysScrollPosScreen > 0) { // Need to scroll more so that no previous page shows. // Scroll to put top of page exactly at top of screen. dysScrollPosScreen = -ysTopOfTopPageScrn; } } // OK, moving by dysScrollPos should make the selection visible // without expanding anything lazy above it. Do it and end the loop! AutoScrollPosition = new Point(-AutoScrollPosition.X, -AutoScrollPosition.Y - dysScrollPosScreen); return true; } } finally { ReleaseGraphics(vg); } }
/// ------------------------------------------------------------------------------------ /// <summary> /// Layout a page :-) /// </summary> /// <param name="page">The page to lay out</param> /// <returns><c>true</c> if the remainder of this division completely fits on this page; /// <c>false</c> if there is additional material from this division to lay out on /// subsequent pages.</returns> /// ------------------------------------------------------------------------------------ internal bool LayoutPage(Page page) { CheckDisposed(); Debug.Assert(!page.IsDisposed); using (new SuspendDrawing(Publication)) using (new WaitCursor(Publication)) { int offsetFromTopOfDiv = page.OffsetFromTopOfDiv(this); int ysStartNextPage; int leftMargin = LeftMarginInPrinterPixels(page); int dysSpaceUsedOnPage; IVwGraphics vg = Publication.PrinterGraphics; MainLayoutStream.LayoutPage(vg, AvailableMainStreamColumWidthInPrinterPixels, page.FreeSpace.Height, ref offsetFromTopOfDiv, page.Handle, m_numberMainStreamColumns, out dysSpaceUsedOnPage, out ysStartNextPage); Publication.ReleaseGraphics(vg); // It's possible that the layout deleted the page (TE-7839) if (page.IsDisposed) { Debug.WriteLine("Page got deleted in MainLayoutStream.LayoutPage."); return true; } if (dysSpaceUsedOnPage == 0) { Debug.WriteLine(string.Format("Division '{0}' didn't use any space on page {1}", Name, page.PageNumber)); return true; } // Layout and add column page elements in main layout stream. for (int iColumn = 0; iColumn < m_numberMainStreamColumns; iColumn++) { int heightThisColumn = MainLayoutStream.ColumnHeight(iColumn); int overlapThisColumn = MainLayoutStream.ColumnOverlapWithPrevious(iColumn); AddElement(page, dysSpaceUsedOnPage, iColumn + 1, m_numberMainStreamColumns, leftMargin, offsetFromTopOfDiv, heightThisColumn, overlapThisColumn); offsetFromTopOfDiv += heightThisColumn; } bool fCompletedDivision = (ysStartNextPage <= 0); if (!fCompletedDivision) { // We need more pages for this division; nothing more on this page Page nextPage = Publication.PageAfter(page); if (nextPage != null && Publication.Divisions[nextPage.FirstDivOnPage] == this) { // There is already another page for this division; adjust its start position. nextPage.AdjustOffsetFromTopOfDiv(ysStartNextPage); } else { // There are no more pages for this division; insert one. nextPage = InsertPage(ysStartNextPage, page); } } else { // Delete any subsequent pages for this same division. for (Page nextPage = Publication.PageAfter(page); nextPage != null; ) { // If nextPage belongs to another division, we're all done. if (nextPage.FirstDivOnPage < Publication.Divisions.Count && Publication.Divisions[nextPage.FirstDivOnPage] != this) break; Page delPage = nextPage; nextPage = Publication.PageAfter(nextPage); Publication.DeletePage(delPage); } } return fCompletedDivision; } }
/// ------------------------------------------------------------------------------------ /// <summary> /// Figure out what is going to be placed on each page and where, for the given area. /// </summary> /// <param name="dypTop">offset in screen pixels to the top of the rectangle that is /// about to be laid out (relative to the client rectangle top)</param> /// <param name="dypBottom">offset in screen pixels to the bottom of the rectangle that /// is about to be laid out (relative to the client rectangle top)</param> /// <returns>A list of pages to be drawn</returns> /// ------------------------------------------------------------------------------------ public List<Page> PrepareToDrawPages(int dypTop, int dypBottom) { CheckDisposed(); if (dypBottom <= dypTop || PageHeight <= 0 || m_pages.Count == 0) return null; lock (m_pages) { int dypPageHeight = PageHeightPlusGapInScreenPixels; Debug.WriteLine("dyPageHeight=" + dypPageHeight); List<Page> pages = new List<Page>(); // Before we start laying out the pages in detail, make sure there are real boxes // at least at the top of the area needing to be laid out. // Note that side effects of this (happening in the AdjustScrollPosition callback // to DivisionLayoutMgr) may change the collection of pages and/or the scroll // position. If that happens, the method returns true, and we need to recompute // which is the first page we want to lay out, and make sure that we don't have any // lazy boxes at that position. Any time that EnsureRealBoxAt returns true, something // has been converted from lazy to real, so the process is sure to terminate. while ((dypTop - ScrollPosition.Y) / dypPageHeight < m_pages.Count && EnsureRealBoxAt(m_pages[(dypTop - ScrollPosition.Y) / dypPageHeight])) ; // We need to call LayoutIfNeeded for any page that intersects the client // rectangle, not just the clip rectangle. This is because, in the case of 'broken' // pages that have been messed up by editing in the view, the Layout method of the // page may actually detect additional screen areas requiring redrawing. // On the other hand, if the clip rectangle is larger than the client rectangle, // we need to use those dimensions...mainly because we pass large rectangles // for testing purposes. dypTop = 0; dypBottom = Math.Max(dypBottom, this.ClientRectangle.Bottom); // The last page to be laid out will be the last page whose top is less than // the bottom of the rectangle being painted. // Note: do not calculate limit allowing for m_pages.Count, as that may increase // during the process of laying out pages. int limPage = (dypBottom - ScrollPosition.Y + dypPageHeight - 1) / dypPageHeight; Debug.WriteLine(string.Format("dypBottom={0}, limPage={1}, startPage={2}, ScrollPos.Y={3}, pages.Count={4}", dypBottom, limPage, (dypTop - ScrollPosition.Y) / (dypPageHeight), ScrollPosition.Y, m_pages.Count)); // The first page to be laid out will be the first page whose bottom is greater than // or equal to the top of the rectangle being painted. for (int iPage = (dypTop - ScrollPosition.Y) / (dypPageHeight); iPage < limPage && iPage < m_pages.Count; iPage++) { // We need to store the page in a temp variable because it is, theoretically, // possible that something else like ScrollSelectionIntoView() might get // called during the layout (probably another problem) and set // m_pageBeingLaidOut to null before we add it to the page list. Page page = m_pageBeingLaidOut = m_pages[iPage]; Debug.WriteLine(string.Format("Laying out page {0} (iPage={1})", m_pageBeingLaidOut.PageNumber, iPage)); page.LayOutIfNeeded(); // page.ReadyForDrawing = true; pages.Add(page); } m_pageBeingLaidOut = null; Debug.WriteLine("Done preparing pages to draw"); return pages; } }
/// ------------------------------------------------------------------------------------ /// <summary> /// Delete the given page. This is broken out as a method in case /// we decide to change to a linked list or something more complex. /// </summary> /// <param name="page"></param> /// ------------------------------------------------------------------------------------ public void DeletePage(Page page) { CheckDisposed(); lock (m_pages) { int pageIndex = IndexOfPage(page); m_pages.Remove(page); AdjustPagesAfter(pageIndex - 1); AdjustAutoScrollRange(); // TODO: If autoscroll position changes, need to invalidate page.Dispose(); } }
public void Pub_ThreeDivisions_EachDivStartsNewPage() { DummyDivision secondDiv = new DummyDivision( new DummyPrintConfigurer(Cache, m_subStream), 2); DummyDivision thirdDiv = new DummyDivision( new DummyPrintConfigurer(Cache, m_subStream), 1); thirdDiv.StartAt = secondDiv.StartAt = m_firstDivision.StartAt = DivisionStartOption.NewPage; m_pub.AddDivision(secondDiv); m_pub.AddDivision(thirdDiv); m_pub.PageHeight = 72000 * 11; // 11 inches m_pub.PageWidth = 72000 * 8; // 8 inches thirdDiv.TopMargin = m_firstDivision.TopMargin = 36000; // Half inch thirdDiv.BottomMargin = m_firstDivision.BottomMargin = 18000; // Quarter inch thirdDiv.InsideMargin = m_firstDivision.InsideMargin = 9000; // 1/8 inch thirdDiv.OutsideMargin = m_firstDivision.OutsideMargin = 4500; // 1/16 inch m_pub.CreatePages(); Assert.AreEqual(4, m_pub.Pages.Count); Page firstPage = m_pub.Pages[0]; Assert.AreEqual(m_firstDivision, m_pub.Divisions[firstPage.FirstDivOnPage]); Page origPage2 = m_pub.PageAfter(firstPage); Assert.AreEqual(m_pub.Pages[1], origPage2); Assert.AreEqual(secondDiv, m_pub.Divisions[origPage2.FirstDivOnPage]); Assert.AreEqual(0, m_pub.IndexOfPage(firstPage)); Page lastPage = m_pub.Pages[3]; Assert.AreEqual(thirdDiv, m_pub.Divisions[lastPage.FirstDivOnPage]); Assert.AreEqual(3, m_pub.IndexOfPage(lastPage), "IndexOfPage returned wrong value for last page"); Page extraPage = new Page(m_pub, 1, 0, 2, m_firstDivision.TopMarginInPrinterPixels, m_firstDivision.BottomMarginInPrinterPixels); m_pub.InsertPageAfter(firstPage, extraPage); Assert.AreEqual(m_pub.Pages[1], extraPage); Assert.AreEqual(m_pub.Pages[2], origPage2); Assert.AreEqual(m_pub.Pages[4], lastPage); Assert.AreEqual(firstPage, m_pub.FindPage(firstPage.Handle)); Assert.AreEqual(extraPage, m_pub.FindPage(extraPage.Handle)); Assert.AreEqual(origPage2, m_pub.FindPage(origPage2.Handle)); Assert.AreEqual(lastPage, m_pub.FindPage(lastPage.Handle)); m_pub.DeletePage(extraPage); Assert.AreEqual(m_pub.Pages[1], origPage2); Assert.IsNull(m_pub.PageAfter(lastPage)); m_pub.PrepareToDrawPages(0, 99999); Assert.AreEqual(7, m_pub.Pages.Count); Assert.AreEqual(2, firstPage.PageElements.Count); Assert.AreEqual(3, origPage2.PageElements.Count, "Should have one element per column, plus one for footnotes."); Assert.AreEqual(2, lastPage.PageElements.Count); Assert.AreEqual(m_firstDivision.MainRootBox.Height, thirdDiv.MainRootBox.Height); Assert.AreNotEqual(m_firstDivision.MainRootBox.Height, secondDiv.MainRootBox.Height); }
/// ------------------------------------------------------------------------------------ /// <summary> /// Checks that the page elements don't overlap. /// </summary> /// <param name="page">The page.</param> /// ------------------------------------------------------------------------------------ private static void CheckThatPageElementsDontOverlap(Page page) { for (int i = 0; i < page.PageElements.Count; i++) { PageElement pe = page.PageElements[i]; for (int j = i + 1; j < page.PageElements.Count; j++) { PageElement otherPe = page.PageElements[j]; Assert.IsFalse(pe.LocationOnPage.IntersectsWith(otherPe.LocationOnPage)); } } }
public void PublicationPageCollection() { m_pub.PageHeight = 144000; // 2 inches m_pub.PageWidth = 216000; // 3 inches m_firstDivision.TopMargin = 36000; // Half inch m_firstDivision.BottomMargin = 18000; // Quarter inch m_firstDivision.InsideMargin = 9000; // 1/8 inch m_firstDivision.OutsideMargin = 4500; // 1/16 inch m_pub.CreatePages(); Assert.IsTrue(m_pub.Pages.Count >= 1); Page firstPage = m_pub.Pages[0]; Page origPage2 = m_pub.PageAfter(firstPage); Assert.AreEqual(0, m_pub.IndexOfPage(firstPage)); Page lastPage = m_pub.Pages[m_pub.Pages.Count - 1]; Assert.AreEqual(m_pub.Pages.Count - 1, m_pub.IndexOfPage(lastPage), "IndexOfPage returned wrong value for last page"); Assert.AreEqual(m_pub.Pages[1], origPage2); Page extraPage = new Page(m_pub, 0, 0, 2, m_firstDivision.TopMarginInPrinterPixels, m_firstDivision.BottomMarginInPrinterPixels); m_pub.InsertPageAfter(firstPage, extraPage); Assert.AreEqual(m_pub.Pages[1], extraPage); Assert.AreEqual(m_pub.Pages[2], origPage2); Assert.AreEqual(firstPage, m_pub.FindPage(firstPage.Handle)); Assert.AreEqual(extraPage, m_pub.FindPage(extraPage.Handle)); Assert.AreEqual(origPage2, m_pub.FindPage(origPage2.Handle)); Assert.AreEqual(lastPage, m_pub.FindPage(lastPage.Handle)); m_pub.DeletePage(extraPage); Assert.AreEqual(m_pub.Pages[1], origPage2); Assert.IsNull(m_pub.PageAfter(lastPage)); }
/// ------------------------------------------------------------------------------------ /// <summary> /// Add the page element. /// </summary> /// <param name="page">The page.</param> /// <param name="dysSpaceUsedOnPage">The amount of vertical space taken up by this /// element on this page.</param> /// <param name="currentColumn">The 1-based index of the current column.</param> /// <param name="numberColumns">The total number columns for the stream.</param> /// <param name="leftMargin">The left margin.</param> /// <param name="offsetFromTopOfDiv">The offset from top of division.</param> /// <param name="columnHeight">The height of the column.</param> /// ------------------------------------------------------------------------------------ internal void CallAddElement(Page page, int dysSpaceUsedOnPage, int currentColumn, int numberColumns, int leftMargin, int offsetFromTopOfDiv, int columnHeight) { base.AddElement(page, dysSpaceUsedOnPage, currentColumn, numberColumns, leftMargin, offsetFromTopOfDiv, columnHeight, 0); }
/// ------------------------------------------------------------------------------------ /// <summary> /// Add page header and footer elements to the given page. /// </summary> /// <param name="page">The page to add the header to</param> /// ------------------------------------------------------------------------------------ internal void AddHeaderAndFooter(Page page) { int leftMargin = LeftMarginInPrinterPixels(page); AddHeaderAndFooter(page, leftMargin); }
/// ------------------------------------------------------------------------------------ /// <summary> /// Find the page preceding the given page. This is broken out as a method in case /// we decide to change to a linked list or something more complex. /// </summary> /// <param name="page"></param> /// <returns></returns> /// ------------------------------------------------------------------------------------ public Page PageBefore(Page page) { CheckDisposed(); int iPage = IndexOfPage(page) - 1; if (iPage < 0) return null; return m_pages[iPage]; }
/// ------------------------------------------------------------------------------------ /// <summary> /// Ensure that the data corresponding to the location of the given page consists of /// real boxes, not lazy ones. /// </summary> /// <param name="page"></param> /// <returns>true if it had to do anything...the caller should recheck the scroll /// position and which pages need drawing, and call again.</returns> /// ------------------------------------------------------------------------------------ public bool EnsureRealBoxAt(Page page) { CheckDisposed(); bool result = false; DivisionLayoutMgr div = m_divisions[page.FirstDivOnPage]; // ENHANCE (TE-5866): Do this for all divisions on the page. IVwGraphics vg = PrinterGraphics; try { int offset = page.OffsetFromTopOfDiv(div); Rect rcPage = new Rect(0, offset, PageWidthInPrinterPixels, offset + PageHeightInPrinterPixels); ((IVwGraphicsWin32)vg).SetClipRect(ref rcPage); Rect rcTrans = new Rect(0, 0, (int)DpiXPrinter, (int)DpiYPrinter); // Make sure we have real data in this section of the root. if (div.MainRootBox != null) { result = div.MainRootBox.PrepareToDraw(vg, rcTrans, rcTrans) != VwPrepDrawResult.kxpdrNormal; } } finally { ReleaseGraphics(vg); } return result; }
/// ------------------------------------------------------------------------------------ /// <summary> /// Insert a page before the given page. This is broken out as a method in case /// we decide to change to a linked list or something more complex. /// </summary> /// <param name="page"></param> /// <param name="newPage"></param> /// ------------------------------------------------------------------------------------ public void InsertPageBefore(Page page, Page newPage) { CheckDisposed(); lock (m_pages) { int index = IndexOfPage(page); m_pages.Insert(index, newPage); AdjustPagesAfter(index - 1); AdjustAutoScrollRange(); } }
/// ------------------------------------------------------------------------------------ /// <summary> /// Inserts a new page after the given one. /// </summary> /// <param name="indexOfDiv">The index of this division in the publication</param> /// <param name="ypOffsetFromTopOfDiv">The offset from top of this division to the /// start of the page to insert.</param> /// <param name="pageToInsertAfter">The page to insert after.</param> /// <returns>The newly inserted page</returns> /// ------------------------------------------------------------------------------------ internal Page InsertPage(int indexOfDiv, int ypOffsetFromTopOfDiv, Page pageToInsertAfter) { DivisionLayoutMgr div = Divisions[indexOfDiv]; Page newPage = CreatePage(this, indexOfDiv, ypOffsetFromTopOfDiv, pageToInsertAfter.PageNumber + 1, div.TopMarginInPrinterPixels, div.BottomMarginInPrinterPixels); InsertPageAfter(pageToInsertAfter, newPage); return newPage; }
/// ------------------------------------------------------------------------------------ /// <summary> /// Return coordinate transformation suitable for screen drawing in the specified /// element of the specified page. /// </summary> /// <param name="page"></param> /// <param name="pe"></param> /// <param name="rcSrcRoot"></param> /// <param name="rcDstRoot"></param> /// ------------------------------------------------------------------------------------ protected void GetCoordRectsForElt(Page page, PageElement pe, out Rect rcSrcRoot, out Rect rcDstRoot) { // The origin of this rectangle is the offset from the origin of this rootbox's // data to the origin of this element (in printer pixels). Size is DPI. Rectangle rectSrc = new Rectangle(0, pe.OffsetToTopPageBoundary - pe.OverlapWithPreviousElement, (int)(DpiXPrinter), (int)(DpiYPrinter)); rcSrcRoot = new Rect(rectSrc.Left, rectSrc.Top, rectSrc.Right, rectSrc.Bottom); Rectangle rectElement = pe.PositionInLayoutForScreen(IndexOfPage(page), this, DpiXScreen, DpiYScreen); // The origin of this rectangle is the offset from the origin of this element // to the origin of the clip rectangle (the part of the rc that // actually pertains to this element) (in screen pixels) Rectangle rectDst = new Rectangle(rectElement.Left, rectElement.Top, (int)(DpiXScreen * Zoom), (int)(DpiYScreen * Zoom)); rcDstRoot = new Rect(rectDst.Left, rectDst.Top, rectDst.Right, rectDst.Bottom); }
/// ------------------------------------------------------------------------------------- /// <summary> /// Inserts additonal pages if needed at dydPosition to approximately match the new /// estimated height when a lazy box has been expanded. /// </summary> /// <param name="changedSizePage">The last page whose top doesn't move (i.e., the one the /// lazy box is actually on). We may need to insert pages just after this.</param> /// <param name="strm">The IVwLayoutStream interface of the rootbox that is the main /// stream on at least some of the pages of this publication (i.e., this publication /// should be the IVwRootsite of the given rootbox).</param> /// <param name="dydSize">The amount the rootbox grew, in printer pixels. This is /// guaranteed to be positive.</param> /// <param name="dydPosition">The top of the lazy box which was (partially) expanded, /// split, etc (in printer pixels). Or the position of the top of the real box that got /// relazified. /// </param> /// <returns>true if there are not enough pages for the AutoScrollPosition to increase as /// much as required.</returns> /// ------------------------------------------------------------------------------------- private bool InsertAdditionalPages(Page changedSizePage, IVwLayoutStream strm, int dydSize, int dydPosition) { Debug.Assert(changedSizePage != null); Debug.Assert(dydSize > 0); Page nextPage = PageAfter(changedSizePage); int heightOfChangedPage; // Current height, to next page or estimated end of doc. int iDiv = DivisionIndexForMainStream(strm); DivisionLayoutMgr divMgr = Divisions[iDiv]; if (nextPage == null) { heightOfChangedPage = dydSize; // An empty page may have nothing from the main stream - can happen when project is // empty or filter excludes all content PageElement pe = changedSizePage.GetFirstElementForStream(strm); if (pe != null) heightOfChangedPage -= pe.OffsetToTopPageBoundary; } else { heightOfChangedPage = nextPage.OffsetFromTopOfDiv(divMgr) - changedSizePage.OffsetFromTopOfDiv(divMgr); } int desiredPageHeight = divMgr.AvailablePageHeightInPrinterPixels; // This computes the number of pages to add, if any, so that // if the tops of the pages are more than a page and a half apart we // insert enough pages so no page is more than an ideal page and a half long. // Subtracting half the desired page height accounts both for rounding and // the fact that one page (rather than zero) is the desired page height. int numberOfPagesToAdd = (heightOfChangedPage - desiredPageHeight / 2) / desiredPageHeight; // Pathologically, it might be possible for that calculation to produce // a negative number, if this page has shrunk to less than a half page from // previous lazy box expansions. if (numberOfPagesToAdd < 0) numberOfPagesToAdd = 0; Page pageToInsertAfter = changedSizePage; for (int i = 0; i < numberOfPagesToAdd; i++) { // Insert a new page. pageToInsertAfter = InsertPage(iDiv, pageToInsertAfter.OffsetFromTopOfDiv(divMgr) + desiredPageHeight, pageToInsertAfter); } // If we're increasing the autoscroll position, do it AFTER we possibly adjust // the autoscroll range, otherwise, the increased value might be greater than // the old range and get truncated. // Remember: Microsoft's idea of how to set AutoScrollPosition is brain-dead. We read // it as negative and have to set it as positive. int dydPosScreen = (int)(dydPosition * DpiYScreen / DpiYPrinter); if (-AutoScrollPosition.Y > dydPosScreen) { int dydDesiredAutoScrollY = (int)(AutoScrollPosition.Y - (dydSize * DpiYScreen / DpiYPrinter)); // larger negative AutoScrollPosition = new Point( -AutoScrollPosition.X, -dydDesiredAutoScrollY); // Something messy happened if we can't achieve that position (presumably // it is out of range, we didn't add enough pages). return (AutoScrollPosition.Y != dydDesiredAutoScrollY); } return false; // We get here only if we didn't have to adjust scroll position at all. }
/// ------------------------------------------------------------------------------------ /// <summary> /// If it's an odd page, return the inside margin; even page, return the outside margin. /// If this is a right-bound publication, then do the opposite. /// </summary> /// <param name="page">The <see cref="Page"/> for which the margin is needed. Needed so /// we know whether to use the inside or outside margin.</param> /// <returns>If it's an odd page, returns the inside margin; if an even page, returns /// the outside margin. If this is a right-bound publication, then does the opposite. /// </returns> /// ------------------------------------------------------------------------------------ public int LeftMarginInPrinterPixels(Page page) { CheckDisposed(); Debug.Assert(!page.IsDisposed); return LeftMarginInPrinterPixels(page.PageNumber); }
/// ------------------------------------------------------------------------------------ /// <summary> /// Invalidate the specified rectangle, which is in printer coordinates relative /// to the top left of the page. /// </summary> /// <param name="page"></param> /// <param name="rect"></param> /// ------------------------------------------------------------------------------------ internal void InvalidatePrintRect(Page page, Rectangle rect) { CheckDisposed(); int indexOfPage = this.IndexOfPage(page); int topOfPage = indexOfPage * PageHeightPlusGapInScreenPixels; // Relative to page, but in screen coordinates. Rectangle rectOnPageScreen = Rectangle.FromLTRB( this.ConvertPrintDistanceToTargetX(rect.Left, DpiXScreen), this.ConvertPrintDistanceToTargetY(rect.Top, DpiYScreen), this.ConvertPrintDistanceToTargetX(rect.Right, DpiXScreen), this.ConvertPrintDistanceToTargetY(rect.Bottom, DpiYScreen)); Rectangle rectToInvert = rectOnPageScreen; rectToInvert.Offset(ScrollPosition.X, ScrollPosition.Y + topOfPage); rectToInvert.Inflate(0, 2); // for out-of-bounds diacritics and descenders, etc. this.Invalidate(rectToInvert, true); }
/// ------------------------------------------------------------------------------------ /// <summary> /// Inserts a new page after the given one. /// </summary> /// <param name="ypOffsetFromTopOfDiv">The offset from top of this division to the /// start of the page to insert.</param> /// <param name="pageToInsertAfter">The page to insert after.</param> /// <returns>The newly inserted page</returns> /// ------------------------------------------------------------------------------------ private Page InsertPage(int ypOffsetFromTopOfDiv, Page pageToInsertAfter) { Debug.Assert(!pageToInsertAfter.IsDisposed); return Publication.InsertPage(Publication.IndexOfDiv(this), ypOffsetFromTopOfDiv, pageToInsertAfter); }
/// ------------------------------------------------------------------------------------ /// <summary> /// Find the element which contains the specified point. /// This should be a point that is actually visible on the screen, so that lazy /// stuff doesn't have to be expanded. /// If there is no element at the specified point we'll look for the first page /// element with the same Y position. If there is none with the same Y position /// we return <c>null</c>. /// </summary> /// <param name="pt">Point at display resolution relative to top left of client area</param> /// <param name="scrollPos">AutoScrollPosition (an argument for testing purposes).</param> /// <param name="ptLayout">Point at layout resolution relative to top left of /// document (for the element's stream).</param> /// <param name="page">The page.</param> /// <returns>Element at position <paramref name="pt"/> (or with the same Y-position), /// or <c>null</c> if there is no element at position <paramref name="pt"/>.</returns> /// <remarks>ENHANCE: find closest element instead of getting first element with same /// Y position if there is no element at specified position.</remarks> /// ------------------------------------------------------------------------------------ public PageElement ElementFromPoint(Point pt, Point scrollPos, out Point ptLayout, out Page page) { CheckDisposed(); ptLayout = pt; // arbitrary, but compiler insists. page = null; if (Pages == null || Pages.Count == 0) return null; Point ptDoc = pt; ptDoc.Offset(-scrollPos.X, -scrollPos.Y); // relative to whole seq of pages int ipage = ptDoc.Y / PageHeightPlusGapInScreenPixels; if (ipage < 0 || ipage >= Pages.Count) return null; Point ptPageScrn = ptDoc; // relative to page in screen pixels ptPageScrn.Offset(0, -ipage * PageHeightPlusGapInScreenPixels); Point ptPage = new Point(ConvertScreenDistanceToPrinterX(ptPageScrn.X, DpiXScreen), ConvertScreenDistanceToPrinterY(ptPageScrn.Y, DpiYScreen)); page = Pages[ipage] as Page; if (page.NeedsLayout) return null; foreach (PageElement pe in page.PageElements) { if (pe.LocationOnPage.Contains(ptPage)) { ptLayout = ptPage; ptLayout.Offset(-pe.LocationOnPage.Left, pe.OffsetToTopPageBoundary - pe.LocationOnPage.Top); return pe; } } // No page element at the given point. Is there one to the right or left // of it? PageElement bestChoice = null; foreach (PageElement pe in page.PageElements) { if (pe.LocationOnPage.Top <= ptPage.Y && ptPage.Y <= pe.Bottom) { if (bestChoice == null || DistanceXFromRect(bestChoice.LocationOnPage, ptPage) > DistanceXFromRect(pe.LocationOnPage, ptPage)) { bestChoice = pe; } } } if (bestChoice != null) { ptLayout = ptPage; ptLayout.Offset(-bestChoice.LocationOnPage.Left, bestChoice.OffsetToTopPageBoundary - bestChoice.LocationOnPage.Top); } return bestChoice; }
/// ------------------------------------------------------------------------------------ /// <summary> /// Add page header and footer elements to the given page. /// </summary> /// <param name="page">The page to add the header to</param> /// ------------------------------------------------------------------------------------ internal void AddHeaderAndFooter(Page page) { Debug.Assert(!page.IsDisposed); int leftMargin = LeftMarginInPrinterPixels(page); AddHeaderAndFooter(page, leftMargin); }
/// ------------------------------------------------------------------------------------ /// <summary> /// Find index of the given page. This is broken out as a method in case /// we decide to change to a linked list or something more complex. /// </summary> /// <param name="page"></param> /// <returns></returns> /// ------------------------------------------------------------------------------------ public int IndexOfPage(Page page) { CheckDisposed(); return m_pages.IndexOf(page); }
/// ------------------------------------------------------------------------------------ /// <summary> /// If it's an odd page, return the inside margin; even page, return the outside margin. /// If this is a right-bound publication, then do the opposite. /// </summary> /// <param name="page">The <see cref="Page"/> for which the margin is needed. Needed so /// we know whether to use the inside or outside margin.</param> /// <returns>If it's an odd page, returns the inside margin; if an even page, returns /// the outside margin. If this is a right-bound publication, then does the opposite. /// </returns> /// ------------------------------------------------------------------------------------ public int LeftMarginInPrinterPixels(Page page) { CheckDisposed(); return LeftMarginInPrinterPixels(page.PageNumber); }
// /// ------------------------------------------------------------------------------------ // /// <summary> // /// Determine the gap between the columns. // /// </summary> // /// <param name="numberColumns">The total number of columns.</param> // /// <returns> // /// If the element spans the whole width of the page, the column gap is 0. // /// Otherwise, it is ColumnGapWidthInPrinterPixels; // /// </returns> // /// ------------------------------------------------------------------------------------ // private int ColumnGap(int numberColumns) // { // return (numberColumns > 1) ? ColumnGapWidthInPrinterPixels : 0; // } /// ------------------------------------------------------------------------------------ /// <summary> /// Adds or adjusts the page element. Note that this is used ONLY for dependent objects, /// it arranges them from the bottom up. These elements don't overlap, so no overlap is /// passed. /// </summary> /// <param name="page">The page.</param> /// <param name="ysTopOfPrevElement">The top of the previous element.</param> /// <param name="ysStreamHeight">Height of the stream.</param> /// <param name="stream">The stream.</param> /// <param name="pagePosition">The page position.</param> /// <returns></returns> /// ------------------------------------------------------------------------------------ private int AddOrAdjustPageElement(Page page, int ysTopOfPrevElement, int ysStreamHeight, IVwLayoutStream stream, int pagePosition) { Debug.Assert(!page.IsDisposed); if (ysStreamHeight <= 0) return ysTopOfPrevElement; int ysTopOfThisElement = ysTopOfPrevElement - ysStreamHeight; Rectangle rectLocationOnPage = new Rectangle(LeftMarginInPrinterPixels(page), ysTopOfThisElement, AvailablePageWidthInPrinterPixels, ysStreamHeight); PageElement element = page.GetFirstElementForStream(stream); if (element == null) { // Create page element page.AddPageElement(this, stream, false, rectLocationOnPage, pagePosition, false, 1, 1, 0, ysStreamHeight, 0, MainStreamIsRightToLeft, false); } else { // Increase size of page element if (element.LocationOnPage.Height != ysStreamHeight) { // Increase size of/move location page.AdjustPageElement(element, rectLocationOnPage, false); } } return ysTopOfThisElement; }