public void Relayout() { var styles = new AssembledStyles(); var box = new MockBox(styles); var fixMap = new Dictionary<Box, Rectangle>(); var layoutInfo = ParaBuilderTests.MakeLayoutInfo(100, m_gm.VwGraphics); var site = new MockSite(); var root = new RootBox(styles); root.Site = site; using (var lcb = new LayoutCallbacks(root)) { Assert.IsFalse(box.Relayout(layoutInfo, fixMap, lcb), "Relayout of box never laid out should return false (can't have old loc)"); } Assert.AreEqual(layoutInfo, box.LastLayoutTransform, "Relayout of box never laid out should call Layout() with same transform"); Assert.AreEqual(0, site.RectsInvalidated.Count, "Relayout of box never laid out should not invalidate anything"); box.LastLayoutTransform = null; using (var lcb = new LayoutCallbacks(root)) Assert.IsFalse(box.Relayout(layoutInfo, fixMap, lcb), "Relayout of box not in map should return false"); Assert.IsNull(box.LastLayoutTransform, "Relayout of box not in map should not call Layout()"); Assert.AreEqual(0, site.RectsInvalidated.Count, "Relayout of box not in map should not invalidate anything"); fixMap[box] = new Rectangle(2,3,4,7); using (var lcb = new LayoutCallbacks(root)) Assert.IsTrue(box.Relayout(layoutInfo, fixMap, lcb), "Relayout of box in map should return true"); Assert.AreEqual(layoutInfo, box.LastLayoutTransform, "Relayout of box in map should call Layout() with same transform"); Assert.AreEqual(1, site.RectsInvalidatedInRoot.Count, "Relayout of box in map should invalidate rect from map"); Assert.AreEqual(new Rectangle(2, 3, 4, 7), site.RectsInvalidatedInRoot[0], "Relayout of box in map should invalidate proper rect"); }
public void Relayout() { var styles = new AssembledStyles(); var box = new MockBox(styles); var fixMap = new Dictionary <Box, Rectangle>(); var layoutInfo = ParaBuilderTests.MakeLayoutInfo(100, m_gm.VwGraphics); var site = new MockSite(); var root = new RootBox(styles); root.Site = site; using (var lcb = new LayoutCallbacks(root)) { Assert.IsFalse(box.Relayout(layoutInfo, fixMap, lcb), "Relayout of box never laid out should return false (can't have old loc)"); } Assert.AreEqual(layoutInfo, box.LastLayoutTransform, "Relayout of box never laid out should call Layout() with same transform"); Assert.AreEqual(0, site.RectsInvalidated.Count, "Relayout of box never laid out should not invalidate anything"); box.LastLayoutTransform = null; using (var lcb = new LayoutCallbacks(root)) Assert.IsFalse(box.Relayout(layoutInfo, fixMap, lcb), "Relayout of box not in map should return false"); Assert.IsNull(box.LastLayoutTransform, "Relayout of box not in map should not call Layout()"); Assert.AreEqual(0, site.RectsInvalidated.Count, "Relayout of box not in map should not invalidate anything"); fixMap[box] = new Rectangle(2, 3, 4, 7); using (var lcb = new LayoutCallbacks(root)) Assert.IsTrue(box.Relayout(layoutInfo, fixMap, lcb), "Relayout of box in map should return true"); Assert.AreEqual(layoutInfo, box.LastLayoutTransform, "Relayout of box in map should call Layout() with same transform"); Assert.AreEqual(1, site.RectsInvalidatedInRoot.Count, "Relayout of box in map should invalidate rect from map"); Assert.AreEqual(new Rectangle(2, 3, 4, 7), site.RectsInvalidatedInRoot[0], "Relayout of box in map should invalidate proper rect"); }
public void LazyBoxExpanded() { var root = new RootBox(new AssembledStyles()); root.RaiseLazyExpanded(new RootBox.LazyExpandedEventArgs()); // make sure harmless with no subscribers root.LazyExpanded += root_LazyExpanded; using (var lc = new LayoutCallbacks(root)) { lc.RaiseLazyExpanded(10, 17, 5); lc.RaiseLazyExpanded(5, 6, -2); Assert.That(m_expandArgs, Is.Empty); } VerifyExpandArgs(0, 10, 17, 5); VerifyExpandArgs(1, 5, 6, -2); }
public void InvalidateInRoot() { var site = new MockSite(); var root = new RootBox(new AssembledStyles()); root.Site = site; using (var lc = new LayoutCallbacks(root)) { lc.InvalidateInRoot(new Rectangle(10, 13, 17, 19)); lc.InvalidateInRoot(new Rectangle(9, 8, 5, 4)); Assert.That(site.RectsInvalidatedInRoot, Is.Empty); } Assert.That(site.RectsInvalidatedInRoot, Has.Member(new Rectangle(10, 13, 17, 19))); Assert.That(site.RectsInvalidatedInRoot, Has.Member(new Rectangle(9, 8, 5, 4))); }
/// <summary> /// Redo the layout of the paragraph given the Source changes indicated in the given details. /// </summary> /// <param name="details"></param> private void Relayout(SourceChangeDetails details) { // We would prefer to keep both sources intact, but we can't just do Source = details.NewSource, // because there is currently no way to change the Source of existing segments we may want to reuse. Source.Copyfrom(details.NewSource); var oldHeight = Height; var oldWidth = Width; using (var gh = Root.Site.DrawingInfo) { // Enhance JohnT: margins: need to adjust MaxWidth for margins and padding of containing boxes. var info = new LayoutInfo(ChildTransformFromRootTransform(gh.Transform), Root.LastLayoutInfo.MaxWidth, gh.VwGraphics, Root.LastLayoutInfo.RendererFactory); var builder = new ParaBuilder(this, info); using (var lcb = new LayoutCallbacks(Root)) { builder.Relayout(details, lcb); if (Height != oldHeight || Width != oldWidth) { RelayoutParents(gh); } } } }
public void DrawingBordersandBackground() { var root = new RootBoxFdo(new AssembledStyles()); SetupFakeRootSite(root); var layoutInfo = HookupTests.MakeLayoutInfo(int.MaxValue / 2, m_gm.VwGraphics, 55); root.RendererFactory = layoutInfo.RendererFactory; var mock1 = new MockData1() {SimpleThree = "This is the first paragraph."}; // The length of the second paragraph is found by experiment to be enough so that // despite its lacking borders it also breaks into 2 lines in the second step. var mock2 = new MockData1() {SimpleThree = "Here is another paragraph. It needs to be a bit longer."}; root.Builder.Show( Paragraph.Containing(Display.Of(() => mock1.SimpleThree)).BackColor(Color.Red) .Margins(1.Points(), 2.Points(), 3.Points(), 4.Points()) .Borders(5.Points(), 6.Points(), 7.Points(), 8.Points(), Color.Blue) .Pads(9.Points(), 10.Points(), 11.Points(), 12.Points()), Paragraph.Containing(Display.Of(() => mock2.SimpleThree)).BackColor(Color.Yellow) .Margins(1.Points(), 2.Points(), 3.Points(), 4.Points())); root.Layout(layoutInfo); // We want to keep track of the sequence of paint operations in all three segments. var drawActions = new List<object>(); var vg = new MockGraphics(); vg.DrawActions = drawActions; var para1 = (ParaBox)root.FirstBox; var stringbox1 = (StringBox)para1.FirstBox; var seg1 = (FakeSegment)stringbox1.Segment; seg1.DrawActions = drawActions; var para2 = (ParaBox)para1.Next; var stringbox2 = (StringBox)para2.FirstBox; var seg2 = (FakeSegment)stringbox2.Segment; seg2.DrawActions = drawActions; var site = (MockSite)root.Site; root.Paint(vg, site.m_transform); var paintTrans = site.m_transform; int position = 0; int red = (int)ColorUtil.ConvertColorToBGR(Color.Red); int margLeft = layoutInfo.MpToPixelsX(1000); Assert.That(margLeft, Is.EqualTo(1)); int bordLeft = layoutInfo.MpToPixelsX(5000); Assert.That(bordLeft, Is.EqualTo(7)); int xOffset = 2 - 100; // how far it is pushed over by the offsets of the layoutInfo int margTop = layoutInfo.MpToPixelsY(2000); Assert.That(margTop, Is.EqualTo(3)); int bordTop = layoutInfo.MpToPixelsY(6000); Assert.That(bordTop, Is.EqualTo(8)); int yOffset = 2 - 200; // how far it is pushed down by the offsets of the layoutInfo int padLeft = layoutInfo.MpToPixelsX(9000); Assert.That(padLeft, Is.EqualTo(12)); int padRight = layoutInfo.MpToPixelsX(11000); Assert.That(padRight, Is.EqualTo(15)); int padTop = layoutInfo.MpToPixelsY(10000); Assert.That(padTop, Is.EqualTo(13)); int padBottom = layoutInfo.MpToPixelsY(12000); Assert.That(padBottom, Is.EqualTo(16)); // First it should draw a background rectangle for the first paragraph. // It is indented by the left margin and the left border, and down by the top margin and border. // The other side is determined by the size of the embedded box and the two pads. VerifyRect(drawActions, ref position, margLeft + bordLeft + xOffset, margTop + bordTop + yOffset, margLeft + bordLeft + xOffset + stringbox1.Width + padLeft + padRight, margTop + bordTop + yOffset + stringbox1.Height + padTop + padBottom, red); int bordBottom = layoutInfo.MpToPixelsY(8000); Assert.That(bordBottom, Is.EqualTo(11)); int blue = (int)ColorUtil.ConvertColorToBGR(Color.Blue); // It's arbitrary what order we draw the borders, and I wish the test didn't specify it, // but in fact the current implementation draws the left border first. VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + yOffset, margLeft + bordLeft + xOffset, margTop + bordTop + yOffset + padTop + stringbox1.Height + padBottom + bordBottom, blue); int bordRight = layoutInfo.MpToPixelsX(7000); Assert.That(bordRight, Is.EqualTo(9)); // Then the top border VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + yOffset, margLeft + bordLeft + xOffset + padLeft + stringbox1.Width + padRight + bordRight, margTop + bordTop + yOffset, blue); // Then the right border VerifyRect(drawActions, ref position, margLeft + bordLeft + xOffset + padLeft + stringbox1.Width + padRight, margTop + yOffset, margLeft + bordLeft + xOffset + padLeft + stringbox1.Width + padRight + bordRight, margTop + bordTop + yOffset + padTop + stringbox1.Height + padBottom + bordBottom, blue); // Then the bottom border VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + bordTop + yOffset + padTop + stringbox1.Height + padBottom, margLeft + bordLeft + xOffset + padLeft + stringbox1.Width + padRight + bordRight, margTop + bordTop + yOffset + padTop + stringbox1.Height + padBottom + bordBottom, blue); // Figure an adjusted y offset for the second paragraph. Everything is down by the height // of the first paragraph, except that the top and bottom margins overlap by the // height of the smaller. int yOffset2 = yOffset + para1.Height - margTop; int yellow = (int)ColorUtil.ConvertColorToBGR(Color.Yellow); // Next a background block for the second paragraph. // (Background color should be reset for the embedded string boxes, so they should not draw their own // background.) VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + yOffset2, margLeft + xOffset + stringbox2.Width, margTop + yOffset2 + stringbox2.Height, yellow); // Verify the position where the text is drawn VerifyDraw(drawActions, ref position, seg1, margLeft + bordLeft + padLeft + 2, margTop + bordTop + padTop + 2); VerifyDraw(drawActions, ref position, seg2, margLeft + 2, para1.Height + 2); //margTop cancels out // And that should be all! Assert.That(position, Is.EqualTo(drawActions.Count)); // Verify that multi-lines in a paragraph are appropriately laid out with margin etc. int maxWidth = para1.Width - FakeRenderEngine.SimulatedWidth("paragraph"); // This maxWidth should force each paragraph to make two segments. layoutInfo = HookupTests.MakeLayoutInfo(maxWidth, m_gm.VwGraphics, 55); root.Layout(layoutInfo); drawActions.Clear(); position = 0; var stringbox1a = (StringBox)para1.FirstBox; var seg1a = (FakeSegment)stringbox1a.Segment; seg1a.DrawActions = drawActions; var stringbox1b = (StringBox)stringbox1a.Next; var seg1b = (FakeSegment)stringbox1b.Segment; seg1b.DrawActions = drawActions; var stringbox2a = (StringBox)para2.FirstBox; var seg2a = (FakeSegment)stringbox2a.Segment; seg2a.DrawActions = drawActions; var stringbox2b = (StringBox)stringbox2a.Next; var seg2b = (FakeSegment)stringbox2b.Segment; seg2b.DrawActions = drawActions; root.Paint(vg, site.m_transform); int margRight = layoutInfo.MpToPixelsX(3000); Assert.That(margRight, Is.EqualTo(4)); // First it should draw a background rectangle for the first paragraph. // It is indented by the left margin and the left border, and down by the top margin and border. // The other side is determined by maxWidth minus the right margin and border. int contentHeight1 = stringbox1a.Height + stringbox1b.Height; VerifyRect(drawActions, ref position, margLeft + bordLeft + xOffset, margTop + bordTop + yOffset, maxWidth - margRight - bordRight + xOffset, margTop + bordTop + yOffset + contentHeight1 + padTop + padBottom, red); // It's arbitrary what order we draw the borders, and I wish the test didn't specify it, // but in fact the current implementation draws the left border first. VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + yOffset, margLeft + bordLeft + xOffset, margTop + bordTop + yOffset + padTop + contentHeight1 + padBottom + bordBottom, blue); // Then the top border VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + yOffset, maxWidth - margRight + xOffset, margTop + bordTop + yOffset, blue); // Then the right border VerifyRect(drawActions, ref position, maxWidth - margRight - bordRight + xOffset, margTop + yOffset, maxWidth - margRight + xOffset, margTop + bordTop + yOffset + padTop + contentHeight1 + padBottom + bordBottom, blue); // Then the bottom border VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + bordTop + yOffset + padTop + contentHeight1 + padBottom, maxWidth - margRight + xOffset, margTop + bordTop + yOffset + padTop + contentHeight1 + padBottom + bordBottom, blue); // Figure an adjusted y offset for the second paragraph. Everything is down by the height // of the first paragraph, except that the top and bottom margins overlap by the // height of the smaller. yOffset2 = yOffset + para1.Height - margTop; // Next a background block for the second paragraph. // (Background color should be reset for the embedded string boxes, so they should not draw their own // background.) VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + yOffset2, maxWidth - margRight + xOffset, margTop + yOffset2 + stringbox2a.Height + stringbox2b.Height, yellow); // Verify the position where the text is drawn VerifyDraw(drawActions, ref position, seg1a, margLeft + bordLeft + padLeft + 2, margTop + bordTop + padTop + 2); VerifyDraw(drawActions, ref position, seg1b, margLeft + bordLeft + padLeft + 2, margTop + bordTop + padTop + 2 + stringbox1a.Height); VerifyDraw(drawActions, ref position, seg2a, margLeft + 2, para1.Height + 2); //margTop cancels out VerifyDraw(drawActions, ref position, seg2b, margLeft + 2, para1.Height + 2 + stringbox2a.Height); //margTop cancels out // And that should be all! Assert.That(position, Is.EqualTo(drawActions.Count)); // A quick check that Relayout puts things in the same places. drawActions.Clear(); position = 0; var fixupMap = new Dictionary<Box, Rectangle>(); fixupMap[para1] = new Rectangle(0, 0, 10, 10); var oldstring1aLeft = stringbox1a.Left; var oldstring1bTop = stringbox1b.Top; using (var lcb = new LayoutCallbacks(root)) root.Relayout(layoutInfo, fixupMap, lcb); Assert.That(drawActions.Count, Is.EqualTo(0)); Assert.That(para1.FirstBox.Left, Is.EqualTo(oldstring1aLeft)); Assert.That(para1.FirstBox.Next.Top, Is.EqualTo(oldstring1bTop)); }
public void PileAndBlock() { var root = new RootBoxFdo(new AssembledStyles()); SetupFakeRootSite(root); var layoutInfo = HookupTests.MakeLayoutInfo(int.MaxValue / 2, m_gm.VwGraphics, 55); root.RendererFactory = layoutInfo.RendererFactory; var mock1 = new MockData1() { SimpleThree = "This is the first paragraph." }; // The length of the second paragraph is found by experiment to be enough so that // despite its lacking borders it also breaks into 2 lines in the second step. var mock2 = new MockData1() { SimpleThree = "Here is another paragraph. It needs to be a bit longer." }; root.Builder.Show( Div.Containing( Display.Block(Color.Red, 25000, 18000).BackColor(Color.Purple) .Margins(3000, 3000, 3000, 3000) .Border(5000, Color.Blue) .Pads(4000, 4000, 4000, 4000), Display.Block(Color.Green, 25000, 18000) ).BackColor(Color.Pink) // these apply to div. .Margins(1000, 1000, 1000, 1000) .Border(2000, Color.Gold) .Pads(6000, 6000, 6000, 6000)); root.Layout(layoutInfo); // We want to keep track of the sequence of paint operations in all three segments. var drawActions = new List<object>(); var vg = new MockGraphics(); vg.DrawActions = drawActions; var site = (MockSite)root.Site; root.Paint(vg, site.m_transform); var paintTrans = site.m_transform; int position = 0; int xOffset = 2 - 100; // how far it is pushed over by the offsets of the layoutInfo int yOffset = 2 - 200; // how far it is pushed down by the offsets of the layoutInfo int red = (int)ColorUtil.ConvertColorToBGR(Color.Red); int pink = (int)ColorUtil.ConvertColorToBGR(Color.Pink); int purple = (int)ColorUtil.ConvertColorToBGR(Color.Purple); int blue = (int)ColorUtil.ConvertColorToBGR(Color.Blue); int green = (int)ColorUtil.ConvertColorToBGR(Color.Green); int gold = (int)ColorUtil.ConvertColorToBGR(Color.Gold); // Technically we could do different conversions in the two directions, but for this test both dpi are the same. int margPile = layoutInfo.MpToPixelsX(1000); int bordPile = layoutInfo.MpToPixelsX(2000); int padPile = layoutInfo.MpToPixelsX(6000); int blockWidth = layoutInfo.MpToPixelsX(25000); int blockHeight = layoutInfo.MpToPixelsX(18000); int margBlock = layoutInfo.MpToPixelsX(3000); int bordBlock = layoutInfo.MpToPixelsX(5000); int padBlock = layoutInfo.MpToPixelsX(4000); // First a background rectangle for the whole pile. var leftPilePad = margPile + bordPile + xOffset; var topPilePad = margPile + bordPile + yOffset; var rightPilePad = margPile + bordPile + 2 * padPile + blockWidth + 2 * margBlock + 2 * bordBlock + 2 * padBlock + xOffset; var bottomPilePad = margPile + bordPile + 2 * padPile + 2 * blockHeight + 2 * margBlock + 2 * bordBlock + 2 * padBlock + yOffset; VerifyRect(drawActions, ref position, leftPilePad, topPilePad, rightPilePad, bottomPilePad, pink); // Left border, whole pile VerifyRect(drawActions, ref position, leftPilePad - bordPile, topPilePad - bordPile, leftPilePad, bottomPilePad + bordPile, gold); // top border, whole pile VerifyRect(drawActions, ref position, leftPilePad - bordPile, topPilePad - bordPile, rightPilePad + bordPile, topPilePad, gold); // right border, whole pile VerifyRect(drawActions, ref position, rightPilePad, topPilePad - bordPile, rightPilePad + bordPile, bottomPilePad + bordPile, gold); // bottom border, whole pile VerifyRect(drawActions, ref position, leftPilePad - bordPile, bottomPilePad, rightPilePad + bordPile, bottomPilePad + bordPile, gold); // background and border for first block. var leftBlockPad = margPile + bordPile + padPile + margBlock + bordBlock + xOffset; var topBlockPad = margPile + bordPile + padPile + margBlock + bordBlock + yOffset; var rightBlockPad = margPile + bordPile + padPile + margBlock + bordBlock + 2 * padBlock + blockWidth + xOffset; var bottomBlockPad = margPile + bordPile + padPile + margBlock + bordBlock + 2 * padBlock + blockHeight + yOffset; VerifyRect(drawActions, ref position, leftBlockPad, topBlockPad, rightBlockPad, bottomBlockPad, purple); // Left border, whole pile VerifyRect(drawActions, ref position, leftBlockPad - bordBlock, topBlockPad - bordBlock, leftBlockPad, bottomBlockPad + bordBlock, blue); // top border, whole pile VerifyRect(drawActions, ref position, leftBlockPad - bordBlock, topBlockPad - bordBlock, rightBlockPad + bordBlock, topBlockPad, blue); // right border, whole pile VerifyRect(drawActions, ref position, rightBlockPad, topBlockPad - bordBlock, rightBlockPad + bordBlock, bottomBlockPad + bordBlock, blue); // bottom border, whole pile VerifyRect(drawActions, ref position, leftBlockPad - bordBlock, bottomBlockPad, rightBlockPad + bordBlock, bottomBlockPad + bordBlock, blue); // The first block itself. VerifyRect(drawActions, ref position, leftBlockPad + padBlock, topBlockPad + padBlock, leftBlockPad + padBlock + blockWidth, topBlockPad + padBlock + blockHeight, red); // The second block itself. var topBlock2 = bottomBlockPad + bordBlock + margBlock; VerifyRect(drawActions, ref position, leftPilePad + padPile, topBlock2, leftPilePad + padPile + blockWidth, topBlock2 + blockHeight, green); // And that should be all! Assert.That(position, Is.EqualTo(drawActions.Count)); // A quick check that Relayout puts things in the same places. drawActions.Clear(); var fixupMap = new Dictionary<Box, Rectangle>(); var div1 = (DivBox) root.FirstBox; var block1 = div1.FirstBox; fixupMap[div1] = new Rectangle(0, 0, 10, 10); fixupMap[block1] = new Rectangle(0, 0, 10, 10); var oldblock1Left = block1.Left; var oldblock1bTop = block1.Top; using (var lcb = new LayoutCallbacks(root)) root.Relayout(layoutInfo, fixupMap, lcb); Assert.That(drawActions.Count, Is.EqualTo(0)); Assert.That(div1.FirstBox.Left, Is.EqualTo(oldblock1Left)); Assert.That(div1.FirstBox.Top, Is.EqualTo(oldblock1bTop)); }
/// <summary> /// Redo layout. Should produce the same segments as FullLayout, but assume that segments for text up to /// details.StartChange may be reused (if not affected by changing line breaks), and segments after /// details.StartChange+details.DeleteCount may be re-used if a line break works out (and after adjusting /// their begin offset). /// </summary> /// <param name="details"></param> internal void Relayout(SourceChangeDetails details, LayoutCallbacks lcb) { m_reuseableLines = m_para.Lines; m_lines = new List<ParaLine>(); m_renderRunIndex = 0; m_ichRendered = 0; IRenderRun last = m_renderRuns[m_renderRuns.Count - 1]; m_ichLim = last.RenderStart + last.RenderLength; m_lastRenderRunIndex = m_renderRuns.Count; Rectangle invalidateRect = m_para.InvalidateRect; int delta = details.InsertCount - details.DeleteCount; int oldHeight = m_para.Height; int oldWidth = m_para.Width; // Make use of details.StartChange to reuse some lines at start. if (m_reuseableLines.Count > 0) { // As long as we have two complete lines before the change, we can certainly reuse the first of them. while (m_reuseableLines.Count > 2 && details.StartChange > m_reuseableLines[2].IchMin) { m_lines.Add(m_reuseableLines[0]); m_reuseableLines.RemoveAt(0); } // If we still have one complete line before the change, we can reuse it provided there is white // space after the end of the line and before the change. if (m_reuseableLines.Count > 1) { int startNextLine = m_reuseableLines[1].IchMin; if (details.StartChange > startNextLine) { bool fGotWhite = false; string line1Text = m_reuseableLines[1].CheckedText; int lim = details.StartChange - startNextLine; var cpe = LgIcuCharPropEngineClass.Create(); for (int ich = 0; ich < lim; ich++) { // Enhance JohnT: possibly we need to consider surrogates here? // Worst case is we don't reuse a line we could have, since a surrogate won't // be considered white. if (cpe.get_IsSeparator(Convert.ToInt32(line1Text[ich]))) { fGotWhite = true; break; } } if (fGotWhite) { m_lines.Add(m_reuseableLines[0]); m_reuseableLines.RemoveAt(0); } } } m_ichRendered = m_reuseableLines[0].IchMin; int topOfFirstDiscardedLine = m_reuseableLines[0].Top; // We don't need to invalidate the lines we're keeping. invalidateRect = new Rectangle(invalidateRect.Left, invalidateRect.Top + topOfFirstDiscardedLine, invalidateRect.Width, invalidateRect.Height - topOfFirstDiscardedLine); } // Figure out which run we need to continue from, to correspond to the start of the first line // we need to rebuild. while (m_renderRunIndex < m_renderRuns.Count && m_renderRuns[m_renderRunIndex].RenderLim <= m_ichRendered) m_renderRunIndex++; while (!Finished) { // Todo: I think we need to adjust available width if this is the first line. BuildALine(); // Drop any initial reusable lines we now determine to be unuseable after all. // If we've used characters beyond the start of this potentially reusable line, we can't reuse it. // Also, we don't reuse empty lines. Typically an empty line is left over from a previously empty // paragraph, and we no longer need the empty segment, even though it doesn't have any of the same // characters (since it has none) as the segment that has replaced it. while (m_reuseableLines.Count > 0 && (m_ichRendered > m_reuseableLines[0].IchMin + delta || m_reuseableLines[0].Length == 0)) { m_reuseableLines.RemoveAt(0); } if (m_reuseableLines.Count > 0) { // See if we can resync. var nextLine = m_reuseableLines[0]; if (m_ichRendered == nextLine.IchMin + delta) { // reuse it. int top = m_gapTop; if (m_lines.Count > 0) { ParaLine previous = m_lines.Last(); previous.LastBox.Next = nextLine.FirstBox; top = TopOfNextLine(previous, nextLine.Ascent); } m_lines.AddRange(m_reuseableLines); if (top != nextLine.Top) { ParaLine previous = null; foreach (var line in m_reuseableLines) { if (previous != null) // first time top has already been computed top = TopOfNextLine(previous, line.Ascent); line.Top = top; // BEFORE ArrangeBoxes, since it gets copied to the individual boxes m_currentLine.ArrangeBoxes(m_para.Style.ParaAlignment, m_gapLeft, m_gapRight, 0, m_layoutInfo.MaxWidth, TopDepth); previous = line; } } else { // reusable lines have not moved, we don't need to invalidate them. invalidateRect.Height -= (m_reuseableLines.Last().Bottom - top); } for (Box box = nextLine.FirstBox; box != null; box = box.Next) { if (box is StringBox) (box as StringBox).IchMin += delta; } break; } } } SetParaInfo(); // if the paragraph got larger, we need to invalidate the extra area. // (But, don't reduce it if it got smaller; we want to invalidate all the old stuff as well as all the new.) if (m_para.Height > oldHeight) invalidateRect.Height += m_para.Height - oldHeight; if (m_para.Width > oldWidth) invalidateRect.Width += m_para.Width - oldWidth; lcb.InvalidateInRoot(invalidateRect); }
public void ParaSequenceTest() { var owner = new MockData1(55, 77); var styles = new AssembledStyles(); var root = new RootBoxFdo(styles); var layoutInfo = MakeLayoutInfo(int.MaxValue/2, m_gm.VwGraphics, 55); SetupFakeRootSite(root); var engine = layoutInfo.RendererFactory.GetRenderer(55, m_gm.VwGraphics) as FakeRenderEngine; MockSite site = new MockSite(); int topLazy = 0; // top of the part of the root box that is occupied by the lazy stuff, relative to the top of the root box itself. root.Site = site; PaintTransform ptrans = new PaintTransform(2, 4, 96, 96, 0, 10, 96, 96); site.m_transform = ptrans; site.m_vwGraphics = m_gm.VwGraphics; root.Builder.Show(LazyDisplay.Of(() => owner.ObjSeq1).Using((bldr, md) => bldr.AddString(() => md.SimpleThree, 55))); var heightOfOneItem = layoutInfo.MpToPixelsY(LazyBox<MockData1>.DefaultItemHeight * 1000); // This does two things: it makes sure the boxes produced by expanding a lazy box item won't be the SAME size, // so we'll get nontrivial changes in root box size; and it makes sure we don't have to expand MORE items than // expected based on the height estimate, which could throw off our predictions of what gets expanded. // Todo: we need a test where we DO have to expand more items after the initial estimate. engine.SegmentHeight = heightOfOneItem + 2; root.Layout(layoutInfo); VerifyParagraphs(root, new string[0]); var child1 = new MockData1(55, 77); var child1String = "Hello world, this is a wide string"; child1.SimpleThree = child1String; owner.InsertIntoObjSeq1(0, child1); Assert.That(root.FirstBox, Is.TypeOf(typeof(LazyBox<MockData1>))); var lazyBox = (LazyBox<MockData1>)root.FirstBox; Assert.That(lazyBox.Width, Is.EqualTo(int.MaxValue / 2)); // no margins, should equal avail width. Assert.That(lazyBox.Height, Is.EqualTo(heightOfOneItem)); var lazyTop = lazyBox.Top; var lazyBottom = lazyBox.Bottom; var oldRootHeight = root.Height; int invalidateWidth = lazyBox.Width + 2 * RootBox.InvalidateMargin; var expectedInvalidate1 = new Rectangle(-RootBox.InvalidateMargin, -RootBox.InvalidateMargin, invalidateWidth, lazyBox.Height + 2 * RootBox.InvalidateMargin); Assert.That(site.RectsInvalidatedInRoot, Has.Member(expectedInvalidate1)); site.RectsInvalidatedInRoot.Clear(); var lazyHookup = ((IHookup) lazyBox).ParentHookup as LazyHookup<MockData1>; Assert.That(lazyHookup, Is.Not.Null); Assert.That(lazyHookup.Children[0], Is.EqualTo(lazyBox)); Assert.That(lazyHookup.Children, Has.Count.EqualTo(1)); root.LazyExpanded += root_LazyExpanded; using (var lc = new LayoutCallbacks(root)) { root.PrepareToPaint(layoutInfo, null, 0, 200); } VerifyParagraphs(root, new [] { child1String }); Assert.That(child1.SimpleThreeHookupCount, Is.EqualTo(1), "expanding lazy box should set up a hookup for the string"); Assert.That(site.RectsInvalidatedInRoot, Is.Empty, "we don't need to invalidate expanding something that's never been painted"); VerifyExpandArgs(0, lazyTop + 2, lazyBottom + 2, root.Height - oldRootHeight); Assert.That(lazyHookup.Children, Has.Count.EqualTo(1)); Assert.That(lazyHookup.Children[0], Is.TypeOf(typeof(ItemHookup)), "the lazy box standing for item hookups should have been replaced"); // Now replace that one object with a list of several. I want to be able to expand two at the start, one at the end, // and one in the middle, and leave two lazy boxes behind. Then expand the rest and make them go away. So I need six. var values = new MockData1[10]; for (int i = 0; i < 10; i++) { values[i] = new MockData1(55, 77); values[i].SimpleThree = i.ToString(); } var newValues = values.Take(6).ToArray(); site.RectsInvalidatedInRoot.Clear(); int phase2RootHeight = root.Height; int phase2RootWidth = root.Width; owner.ReplaceObjSeq1(newValues); var expectedInvalidate2 = new Rectangle(-RootBox.InvalidateMargin, -RootBox.InvalidateMargin, phase2RootWidth + 2 * RootBox.InvalidateMargin, phase2RootHeight + 2 * RootBox.InvalidateMargin); Assert.That(site.RectsInvalidatedInRoot, Has.Member(expectedInvalidate2), "should invalidate the old replaced paragraph."); lazyBox = (LazyBox<MockData1>)root.FirstBox; Assert.That(lazyBox.Width, Is.EqualTo(int.MaxValue / 2)); // no margins, should equal avail width. Assert.That(root.LastBox, Is.EqualTo(lazyBox), "old paragraph should have been replaced"); Assert.That(lazyHookup.Children[0], Is.EqualTo(lazyBox), "after second replace we just have the lazy box"); Assert.That(lazyHookup.Children, Has.Count.EqualTo(1), "should not have anything but lazy box after second replace"); Assert.That(lazyBox.Height, Is.EqualTo(6*heightOfOneItem)); // Make it expand the first two items. site.RectsInvalidatedInRoot.Clear(); m_expandArgs.Clear(); oldRootHeight = root.Height; using (var lc = new LayoutCallbacks(root)) { root.PrepareToPaint(layoutInfo, null, 0, heightOfOneItem * 2 - 2); } VerifyParagraphs(root, new[] { "0", "1", null }); // Should have two paras then lazy box Assert.That(newValues[0].SimpleThreeHookupCount, Is.EqualTo(1), "expanding lazy box should set up a hookup for the string"); Assert.That(newValues[1].SimpleThreeHookupCount, Is.EqualTo(1), "expanding lazy box should set up a hookup for the string"); Assert.That(site.RectsInvalidatedInRoot, Is.Empty, "we don't need to invalidate expanding something that's never been painted"); Assert.That(root.Height, Is.Not.EqualTo(oldRootHeight)); VerifyExpandArgs(0, 2, heightOfOneItem * 2 + 2, root.Height - oldRootHeight); // +2's from root layout offset Assert.That(lazyHookup.Children, Has.Count.EqualTo(3)); Assert.That(lazyHookup.Children[0], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the first expanded item should be inserted"); Assert.That(lazyHookup.Children[1], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the 2nd expanded item should be inserted"); Assert.That(lazyHookup.Children[2], Is.TypeOf(typeof(LazyBox<MockData1>)), "the lazy box standing for item hookups should still be there"); lazyBox = root.FirstBox.Next.Next as LazyBox<MockData1>; Assert.That(lazyBox, Is.Not.Null); Assert.That(lazyBox.Height, Is.EqualTo(heightOfOneItem * 4)); int topOfLastItem = lazyBox.Bottom - heightOfOneItem + 2; // Make it expand the last item. site.RectsInvalidatedInRoot.Clear(); m_expandArgs.Clear(); oldRootHeight = root.Height; using (var lc = new LayoutCallbacks(root)) { root.PrepareToPaint(layoutInfo, null, topOfLastItem + 2, topOfLastItem + 10); } VerifyParagraphs(root, new[] { "0", "1", null, "5" }); // Should have two paras then lazy box then last para Assert.That(newValues[5].SimpleThreeHookupCount, Is.EqualTo(1), "expanding lazy box should set up a hookup for the string"); Assert.That(site.RectsInvalidatedInRoot, Is.Empty, "we don't need to invalidate expanding something that's never been painted"); Assert.That(root.Height, Is.Not.EqualTo(oldRootHeight)); VerifyExpandArgs(0, topOfLastItem, topOfLastItem + heightOfOneItem, root.Height - oldRootHeight); Assert.That(lazyHookup.Children, Has.Count.EqualTo(4)); Assert.That(lazyHookup.Children[0], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the first expanded item should be inserted"); Assert.That(lazyHookup.Children[1], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the 2nd expanded item should be inserted"); Assert.That(lazyHookup.Children[2], Is.TypeOf(typeof(LazyBox<MockData1>)), "the lazy box standing for item hookups should still be there"); Assert.That(lazyHookup.Children[3], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the last expanded item should be inserted"); lazyBox = root.FirstBox.Next.Next as LazyBox<MockData1>; Assert.That(lazyBox, Is.Not.Null); Assert.That(lazyBox.Height, Is.EqualTo(heightOfOneItem * 3)); // Expand middle item in lazy box, leaving two lazy boxes. int topOfMiddleItem = lazyBox.Top + heightOfOneItem + 2; site.RectsInvalidatedInRoot.Clear(); m_expandArgs.Clear(); oldRootHeight = root.Height; using (var lc = new LayoutCallbacks(root)) { root.PrepareToPaint(layoutInfo, null, topOfMiddleItem + 2, topOfMiddleItem + 10); } VerifyParagraphs(root, new[] { "0", "1", null, "3", null, "5" }); // Should have two paras then lazy box then middle para then another lazy then last para Assert.That(newValues[3].SimpleThreeHookupCount, Is.EqualTo(1), "expanding lazy box should set up a hookup for the string"); Assert.That(site.RectsInvalidatedInRoot, Is.Empty, "we don't need to invalidate expanding something that's never been painted"); Assert.That(root.Height, Is.Not.EqualTo(oldRootHeight)); VerifyExpandArgs(0, topOfMiddleItem, topOfMiddleItem + heightOfOneItem, root.Height - oldRootHeight); Assert.That(lazyHookup.Children, Has.Count.EqualTo(6)); Assert.That(lazyHookup.Children[0], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the first expanded item should be inserted"); Assert.That(lazyHookup.Children[1], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the 2nd expanded item should be inserted"); Assert.That(lazyHookup.Children[2], Is.TypeOf(typeof(LazyBox<MockData1>)), "the lazy box standing for item hookups should still be there"); Assert.That(lazyHookup.Children[3], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the last expanded item should be inserted"); Assert.That(lazyHookup.Children[4], Is.TypeOf(typeof(LazyBox<MockData1>)), "the lazy box standing for item hookups should still be there"); Assert.That(lazyHookup.Children[5], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the last expanded item should be inserted"); lazyBox = root.FirstBox.Next.Next as LazyBox<MockData1>; Assert.That(lazyBox, Is.Not.Null); Assert.That(lazyBox.Height, Is.EqualTo(heightOfOneItem)); var lazyBox2 = lazyBox.Next.Next as LazyBox<MockData1>; Assert.That(lazyBox2, Is.Not.Null); Assert.That(lazyBox2.Height, Is.EqualTo(heightOfOneItem)); // Expand lazy box when it is between two other items. (Also verify expanding two lazy boxes in one PrepareToPaint.) int topOfFirstLazy = lazyBox.Top + 2; int topOfLastLazy = lazyBox2.Top + 2; site.RectsInvalidatedInRoot.Clear(); m_expandArgs.Clear(); oldRootHeight = root.Height; using (var lc = new LayoutCallbacks(root)) { root.PrepareToPaint(layoutInfo, null, topOfFirstLazy + 2, topOfLastLazy + 2); } VerifyParagraphs(root, new[] { "0", "1", "2", "3", "4", "5" }); // Should have all the real paragraphs now. Assert.That(newValues[2].SimpleThreeHookupCount, Is.EqualTo(1), "expanding lazy box should set up a hookup for the string"); Assert.That(newValues[4].SimpleThreeHookupCount, Is.EqualTo(1), "expanding lazy box should set up a hookup for the string"); Assert.That(site.RectsInvalidatedInRoot, Is.Empty, "we don't need to invalidate expanding something that's never been painted"); Assert.That(root.Height, Is.Not.EqualTo(oldRootHeight)); var delta = engine.SegmentHeight - heightOfOneItem; VerifyExpandArgs(0, topOfFirstLazy, topOfFirstLazy + heightOfOneItem, delta); VerifyExpandArgs(1, topOfLastLazy + delta, topOfLastLazy + delta + heightOfOneItem, delta); Assert.That(lazyHookup.Children, Has.Count.EqualTo(6)); Assert.That(lazyHookup.Children[0], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the first expanded item should be inserted"); Assert.That(lazyHookup.Children[1], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the 2nd expanded item should be inserted"); Assert.That(lazyHookup.Children[2], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the 3rd expanded item should be inserted"); Assert.That(lazyHookup.Children[3], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the 4th expanded item should be inserted"); Assert.That(lazyHookup.Children[4], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the 5th expanded item should be inserted"); Assert.That(lazyHookup.Children[5], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the last expanded item should be inserted"); // Now try removing the first two items. site.RectsInvalidatedInRoot.Clear(); int heightOfFirst2Paras = root.FirstBox.Next.Bottom - root.FirstBox.Top; int phase3RootWidth = root.Width; int phase3RootHeight = root.Height; var phase3Values = newValues.Skip(2).ToArray(); owner.ReplaceObjSeq1(phase3Values); var expectedInvalidate3 = new Rectangle(-RootBox.InvalidateMargin, -RootBox.InvalidateMargin, phase3RootWidth + 2 * RootBox.InvalidateMargin, phase3RootHeight + 2 * RootBox.InvalidateMargin); Assert.That(site.RectsInvalidatedInRoot, Has.Member(expectedInvalidate3), "should invalidate the whole old root box; everything changes or moves."); VerifyParagraphs(root, new[] {"2", "3", "4", "5" }); // Should have last 4 paragraphs now (not made lazy). // Now try removing the last item. site.RectsInvalidatedInRoot.Clear(); int phase4RootWidth = root.Width; var phase4Values = phase3Values.Take(3).ToArray(); var topOfPara4 = root.FirstBox.Next.Next.Bottom + topLazy; owner.ReplaceObjSeq1(phase4Values); var expectedInvalidate4 = new Rectangle(-RootBox.InvalidateMargin, topOfPara4 - RootBox.InvalidateMargin, phase4RootWidth + 2 * RootBox.InvalidateMargin, root.FirstBox.Height + 2 * RootBox.InvalidateMargin); Assert.That(site.RectsInvalidatedInRoot, Has.Member(expectedInvalidate4), "should invalidate the old replaced paragraphs."); VerifyParagraphs(root, new[] { "2", "3", "4" }); // Should have last 3 paragraphs now (not made lazy). // Now try removing a middle item. site.RectsInvalidatedInRoot.Clear(); int phase5RootWidth = root.Width; var phase5Values = new [] {newValues[2], newValues[4]}; var topOfPara3 = root.FirstBox.Bottom + topLazy; var phase4RootHeight = root.Height; owner.ReplaceObjSeq1(phase5Values); var expectedInvalidate5 = new Rectangle(-RootBox.InvalidateMargin, topOfPara3 - RootBox.InvalidateMargin, phase5RootWidth + 2 * RootBox.InvalidateMargin, phase4RootHeight - topOfPara3 + 2 * RootBox.InvalidateMargin); Assert.That(site.RectsInvalidatedInRoot, Has.Member(expectedInvalidate5), "should invalidate the old replaced paragraphs."); VerifyParagraphs(root, new[] { "2", "4" }); // Should have remaining 2 paragraphs now (not made lazy). // Insert three items at start: 0, 1, 3, 2, 4. site.RectsInvalidatedInRoot.Clear(); int phase6RootWidth = root.Width; var phase6Values = new[] {newValues[0], newValues[1], newValues[3], newValues[2], newValues[4] }; owner.ReplaceObjSeq1(phase6Values); int lazyWidth = root.LastLayoutInfo.MaxWidth; // current standard width for lazy boxes. var expectedInvalidate6 = new Rectangle(-RootBox.InvalidateMargin, topLazy - RootBox.InvalidateMargin, lazyWidth + 2 * RootBox.InvalidateMargin, root.Height + 2 * RootBox.InvalidateMargin); Assert.That(site.RectsInvalidatedInRoot, Has.Member(expectedInvalidate6), "should invalidate everything...all moved or added."); VerifyParagraphs(root, new[] {null, "2", "4" }); // Should have added lazy box at start. VerifyLazyContents(root.FirstBox, new[] {newValues[0], newValues[1], newValues[3]}); // Insert at end: 0, 1, 3, 2, 4, 9. I think we've tested the invalidate rects enough. var phase7Values = new[] { values[0], values[1], values[3], values[2], values[4], values[9] }; owner.ReplaceObjSeq1(phase7Values); VerifyParagraphs(root, new[] { null, "2", "4", null }); // Should have added lazy box at end. VerifyLazyContents(root.LastBox, new[] { values[9] }); // Insert between two non-lazy items: 0, 1, 3, 2, 5, 6, 4, 9. var phase8Values = new[] { values[0], values[1], values[3], values[2], values[5], values[6], values[4], values[9] }; owner.ReplaceObjSeq1(phase8Values); VerifyParagraphs(root, new[] { null, "2", null, "4", null }); // Should have added lazy box in middle. VerifyLazyContents(root.FirstBox.Next.Next, new[] { values[5], values[6] }); // Try a more complex overwrite. We'll replace the last item in the first lazy box and the first one in the second var phase9Values = new[] { values[0], values[1], values[7], values[2], values[8], values[6], values[4], values[9] }; owner.ReplaceObjSeq1(phase9Values); VerifyParagraphs(root, new[] { null, "4", null }); // Should replace first 3 items with new lazy box. VerifyLazyContents(root.FirstBox, new[] { values[0], values[1], values[7], values[2], values[8], values[6] }); }
public void ParaSequenceTest() { var owner = new MockData1(55, 77); var styles = new AssembledStyles(); var root = new RootBoxFdo(styles); var layoutInfo = MakeLayoutInfo(int.MaxValue / 2, m_gm.VwGraphics, 55); SetupFakeRootSite(root); var engine = layoutInfo.RendererFactory.GetRenderer(55, m_gm.VwGraphics) as FakeRenderEngine; MockSite site = new MockSite(); int topLazy = 0; // top of the part of the root box that is occupied by the lazy stuff, relative to the top of the root box itself. root.Site = site; PaintTransform ptrans = new PaintTransform(2, 4, 96, 96, 0, 10, 96, 96); site.m_transform = ptrans; site.m_vwGraphics = m_gm.VwGraphics; root.Builder.Show(LazyDisplay.Of(() => owner.ObjSeq1).Using((bldr, md) => bldr.AddString(() => md.SimpleThree, 55))); var heightOfOneItem = layoutInfo.MpToPixelsY(LazyBox <MockData1> .DefaultItemHeight * 1000); // This does two things: it makes sure the boxes produced by expanding a lazy box item won't be the SAME size, // so we'll get nontrivial changes in root box size; and it makes sure we don't have to expand MORE items than // expected based on the height estimate, which could throw off our predictions of what gets expanded. // Todo: we need a test where we DO have to expand more items after the initial estimate. engine.SegmentHeight = heightOfOneItem + 2; root.Layout(layoutInfo); VerifyParagraphs(root, new string[0]); var child1 = new MockData1(55, 77); var child1String = "Hello world, this is a wide string"; child1.SimpleThree = child1String; owner.InsertIntoObjSeq1(0, child1); Assert.That(root.FirstBox, Is.TypeOf(typeof(LazyBox <MockData1>))); var lazyBox = (LazyBox <MockData1>)root.FirstBox; Assert.That(lazyBox.Width, Is.EqualTo(int.MaxValue / 2)); // no margins, should equal avail width. Assert.That(lazyBox.Height, Is.EqualTo(heightOfOneItem)); var lazyTop = lazyBox.Top; var lazyBottom = lazyBox.Bottom; var oldRootHeight = root.Height; int invalidateWidth = lazyBox.Width + 2 * RootBox.InvalidateMargin; var expectedInvalidate1 = new Rectangle(-RootBox.InvalidateMargin, -RootBox.InvalidateMargin, invalidateWidth, lazyBox.Height + 2 * RootBox.InvalidateMargin); Assert.That(site.RectsInvalidatedInRoot, Has.Member(expectedInvalidate1)); site.RectsInvalidatedInRoot.Clear(); var lazyHookup = ((IHookup)lazyBox).ParentHookup as LazyHookup <MockData1>; Assert.That(lazyHookup, Is.Not.Null); Assert.That(lazyHookup.Children[0], Is.EqualTo(lazyBox)); Assert.That(lazyHookup.Children, Has.Count.EqualTo(1)); root.LazyExpanded += root_LazyExpanded; using (var lc = new LayoutCallbacks(root)) { root.PrepareToPaint(layoutInfo, null, 0, 200); } VerifyParagraphs(root, new [] { child1String }); Assert.That(child1.SimpleThreeHookupCount, Is.EqualTo(1), "expanding lazy box should set up a hookup for the string"); Assert.That(site.RectsInvalidatedInRoot, Is.Empty, "we don't need to invalidate expanding something that's never been painted"); VerifyExpandArgs(0, lazyTop + 2, lazyBottom + 2, root.Height - oldRootHeight); Assert.That(lazyHookup.Children, Has.Count.EqualTo(1)); Assert.That(lazyHookup.Children[0], Is.TypeOf(typeof(ItemHookup)), "the lazy box standing for item hookups should have been replaced"); // Now replace that one object with a list of several. I want to be able to expand two at the start, one at the end, // and one in the middle, and leave two lazy boxes behind. Then expand the rest and make them go away. So I need six. var values = new MockData1[10]; for (int i = 0; i < 10; i++) { values[i] = new MockData1(55, 77); values[i].SimpleThree = i.ToString(); } var newValues = values.Take(6).ToArray(); site.RectsInvalidatedInRoot.Clear(); int phase2RootHeight = root.Height; int phase2RootWidth = root.Width; owner.ReplaceObjSeq1(newValues); var expectedInvalidate2 = new Rectangle(-RootBox.InvalidateMargin, -RootBox.InvalidateMargin, phase2RootWidth + 2 * RootBox.InvalidateMargin, phase2RootHeight + 2 * RootBox.InvalidateMargin); Assert.That(site.RectsInvalidatedInRoot, Has.Member(expectedInvalidate2), "should invalidate the old replaced paragraph."); lazyBox = (LazyBox <MockData1>)root.FirstBox; Assert.That(lazyBox.Width, Is.EqualTo(int.MaxValue / 2)); // no margins, should equal avail width. Assert.That(root.LastBox, Is.EqualTo(lazyBox), "old paragraph should have been replaced"); Assert.That(lazyHookup.Children[0], Is.EqualTo(lazyBox), "after second replace we just have the lazy box"); Assert.That(lazyHookup.Children, Has.Count.EqualTo(1), "should not have anything but lazy box after second replace"); Assert.That(lazyBox.Height, Is.EqualTo(6 * heightOfOneItem)); // Make it expand the first two items. site.RectsInvalidatedInRoot.Clear(); m_expandArgs.Clear(); oldRootHeight = root.Height; using (var lc = new LayoutCallbacks(root)) { root.PrepareToPaint(layoutInfo, null, 0, heightOfOneItem * 2 - 2); } VerifyParagraphs(root, new[] { "0", "1", null }); // Should have two paras then lazy box Assert.That(newValues[0].SimpleThreeHookupCount, Is.EqualTo(1), "expanding lazy box should set up a hookup for the string"); Assert.That(newValues[1].SimpleThreeHookupCount, Is.EqualTo(1), "expanding lazy box should set up a hookup for the string"); Assert.That(site.RectsInvalidatedInRoot, Is.Empty, "we don't need to invalidate expanding something that's never been painted"); Assert.That(root.Height, Is.Not.EqualTo(oldRootHeight)); VerifyExpandArgs(0, 2, heightOfOneItem * 2 + 2, root.Height - oldRootHeight); // +2's from root layout offset Assert.That(lazyHookup.Children, Has.Count.EqualTo(3)); Assert.That(lazyHookup.Children[0], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the first expanded item should be inserted"); Assert.That(lazyHookup.Children[1], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the 2nd expanded item should be inserted"); Assert.That(lazyHookup.Children[2], Is.TypeOf(typeof(LazyBox <MockData1>)), "the lazy box standing for item hookups should still be there"); lazyBox = root.FirstBox.Next.Next as LazyBox <MockData1>; Assert.That(lazyBox, Is.Not.Null); Assert.That(lazyBox.Height, Is.EqualTo(heightOfOneItem * 4)); int topOfLastItem = lazyBox.Bottom - heightOfOneItem + 2; // Make it expand the last item. site.RectsInvalidatedInRoot.Clear(); m_expandArgs.Clear(); oldRootHeight = root.Height; using (var lc = new LayoutCallbacks(root)) { root.PrepareToPaint(layoutInfo, null, topOfLastItem + 2, topOfLastItem + 10); } VerifyParagraphs(root, new[] { "0", "1", null, "5" }); // Should have two paras then lazy box then last para Assert.That(newValues[5].SimpleThreeHookupCount, Is.EqualTo(1), "expanding lazy box should set up a hookup for the string"); Assert.That(site.RectsInvalidatedInRoot, Is.Empty, "we don't need to invalidate expanding something that's never been painted"); Assert.That(root.Height, Is.Not.EqualTo(oldRootHeight)); VerifyExpandArgs(0, topOfLastItem, topOfLastItem + heightOfOneItem, root.Height - oldRootHeight); Assert.That(lazyHookup.Children, Has.Count.EqualTo(4)); Assert.That(lazyHookup.Children[0], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the first expanded item should be inserted"); Assert.That(lazyHookup.Children[1], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the 2nd expanded item should be inserted"); Assert.That(lazyHookup.Children[2], Is.TypeOf(typeof(LazyBox <MockData1>)), "the lazy box standing for item hookups should still be there"); Assert.That(lazyHookup.Children[3], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the last expanded item should be inserted"); lazyBox = root.FirstBox.Next.Next as LazyBox <MockData1>; Assert.That(lazyBox, Is.Not.Null); Assert.That(lazyBox.Height, Is.EqualTo(heightOfOneItem * 3)); // Expand middle item in lazy box, leaving two lazy boxes. int topOfMiddleItem = lazyBox.Top + heightOfOneItem + 2; site.RectsInvalidatedInRoot.Clear(); m_expandArgs.Clear(); oldRootHeight = root.Height; using (var lc = new LayoutCallbacks(root)) { root.PrepareToPaint(layoutInfo, null, topOfMiddleItem + 2, topOfMiddleItem + 10); } VerifyParagraphs(root, new[] { "0", "1", null, "3", null, "5" }); // Should have two paras then lazy box then middle para then another lazy then last para Assert.That(newValues[3].SimpleThreeHookupCount, Is.EqualTo(1), "expanding lazy box should set up a hookup for the string"); Assert.That(site.RectsInvalidatedInRoot, Is.Empty, "we don't need to invalidate expanding something that's never been painted"); Assert.That(root.Height, Is.Not.EqualTo(oldRootHeight)); VerifyExpandArgs(0, topOfMiddleItem, topOfMiddleItem + heightOfOneItem, root.Height - oldRootHeight); Assert.That(lazyHookup.Children, Has.Count.EqualTo(6)); Assert.That(lazyHookup.Children[0], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the first expanded item should be inserted"); Assert.That(lazyHookup.Children[1], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the 2nd expanded item should be inserted"); Assert.That(lazyHookup.Children[2], Is.TypeOf(typeof(LazyBox <MockData1>)), "the lazy box standing for item hookups should still be there"); Assert.That(lazyHookup.Children[3], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the last expanded item should be inserted"); Assert.That(lazyHookup.Children[4], Is.TypeOf(typeof(LazyBox <MockData1>)), "the lazy box standing for item hookups should still be there"); Assert.That(lazyHookup.Children[5], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the last expanded item should be inserted"); lazyBox = root.FirstBox.Next.Next as LazyBox <MockData1>; Assert.That(lazyBox, Is.Not.Null); Assert.That(lazyBox.Height, Is.EqualTo(heightOfOneItem)); var lazyBox2 = lazyBox.Next.Next as LazyBox <MockData1>; Assert.That(lazyBox2, Is.Not.Null); Assert.That(lazyBox2.Height, Is.EqualTo(heightOfOneItem)); // Expand lazy box when it is between two other items. (Also verify expanding two lazy boxes in one PrepareToPaint.) int topOfFirstLazy = lazyBox.Top + 2; int topOfLastLazy = lazyBox2.Top + 2; site.RectsInvalidatedInRoot.Clear(); m_expandArgs.Clear(); oldRootHeight = root.Height; using (var lc = new LayoutCallbacks(root)) { root.PrepareToPaint(layoutInfo, null, topOfFirstLazy + 2, topOfLastLazy + 2); } VerifyParagraphs(root, new[] { "0", "1", "2", "3", "4", "5" }); // Should have all the real paragraphs now. Assert.That(newValues[2].SimpleThreeHookupCount, Is.EqualTo(1), "expanding lazy box should set up a hookup for the string"); Assert.That(newValues[4].SimpleThreeHookupCount, Is.EqualTo(1), "expanding lazy box should set up a hookup for the string"); Assert.That(site.RectsInvalidatedInRoot, Is.Empty, "we don't need to invalidate expanding something that's never been painted"); Assert.That(root.Height, Is.Not.EqualTo(oldRootHeight)); var delta = engine.SegmentHeight - heightOfOneItem; VerifyExpandArgs(0, topOfFirstLazy, topOfFirstLazy + heightOfOneItem, delta); VerifyExpandArgs(1, topOfLastLazy + delta, topOfLastLazy + delta + heightOfOneItem, delta); Assert.That(lazyHookup.Children, Has.Count.EqualTo(6)); Assert.That(lazyHookup.Children[0], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the first expanded item should be inserted"); Assert.That(lazyHookup.Children[1], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the 2nd expanded item should be inserted"); Assert.That(lazyHookup.Children[2], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the 3rd expanded item should be inserted"); Assert.That(lazyHookup.Children[3], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the 4th expanded item should be inserted"); Assert.That(lazyHookup.Children[4], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the 5th expanded item should be inserted"); Assert.That(lazyHookup.Children[5], Is.TypeOf(typeof(ItemHookup)), "a regular item hookup for the last expanded item should be inserted"); // Now try removing the first two items. site.RectsInvalidatedInRoot.Clear(); int heightOfFirst2Paras = root.FirstBox.Next.Bottom - root.FirstBox.Top; int phase3RootWidth = root.Width; int phase3RootHeight = root.Height; var phase3Values = newValues.Skip(2).ToArray(); owner.ReplaceObjSeq1(phase3Values); var expectedInvalidate3 = new Rectangle(-RootBox.InvalidateMargin, -RootBox.InvalidateMargin, phase3RootWidth + 2 * RootBox.InvalidateMargin, phase3RootHeight + 2 * RootBox.InvalidateMargin); Assert.That(site.RectsInvalidatedInRoot, Has.Member(expectedInvalidate3), "should invalidate the whole old root box; everything changes or moves."); VerifyParagraphs(root, new[] { "2", "3", "4", "5" }); // Should have last 4 paragraphs now (not made lazy). // Now try removing the last item. site.RectsInvalidatedInRoot.Clear(); int phase4RootWidth = root.Width; var phase4Values = phase3Values.Take(3).ToArray(); var topOfPara4 = root.FirstBox.Next.Next.Bottom + topLazy; owner.ReplaceObjSeq1(phase4Values); var expectedInvalidate4 = new Rectangle(-RootBox.InvalidateMargin, topOfPara4 - RootBox.InvalidateMargin, phase4RootWidth + 2 * RootBox.InvalidateMargin, root.FirstBox.Height + 2 * RootBox.InvalidateMargin); Assert.That(site.RectsInvalidatedInRoot, Has.Member(expectedInvalidate4), "should invalidate the old replaced paragraphs."); VerifyParagraphs(root, new[] { "2", "3", "4" }); // Should have last 3 paragraphs now (not made lazy). // Now try removing a middle item. site.RectsInvalidatedInRoot.Clear(); int phase5RootWidth = root.Width; var phase5Values = new [] { newValues[2], newValues[4] }; var topOfPara3 = root.FirstBox.Bottom + topLazy; var phase4RootHeight = root.Height; owner.ReplaceObjSeq1(phase5Values); var expectedInvalidate5 = new Rectangle(-RootBox.InvalidateMargin, topOfPara3 - RootBox.InvalidateMargin, phase5RootWidth + 2 * RootBox.InvalidateMargin, phase4RootHeight - topOfPara3 + 2 * RootBox.InvalidateMargin); Assert.That(site.RectsInvalidatedInRoot, Has.Member(expectedInvalidate5), "should invalidate the old replaced paragraphs."); VerifyParagraphs(root, new[] { "2", "4" }); // Should have remaining 2 paragraphs now (not made lazy). // Insert three items at start: 0, 1, 3, 2, 4. site.RectsInvalidatedInRoot.Clear(); int phase6RootWidth = root.Width; var phase6Values = new[] { newValues[0], newValues[1], newValues[3], newValues[2], newValues[4] }; owner.ReplaceObjSeq1(phase6Values); int lazyWidth = root.LastLayoutInfo.MaxWidth; // current standard width for lazy boxes. var expectedInvalidate6 = new Rectangle(-RootBox.InvalidateMargin, topLazy - RootBox.InvalidateMargin, lazyWidth + 2 * RootBox.InvalidateMargin, root.Height + 2 * RootBox.InvalidateMargin); Assert.That(site.RectsInvalidatedInRoot, Has.Member(expectedInvalidate6), "should invalidate everything...all moved or added."); VerifyParagraphs(root, new[] { null, "2", "4" }); // Should have added lazy box at start. VerifyLazyContents(root.FirstBox, new[] { newValues[0], newValues[1], newValues[3] }); // Insert at end: 0, 1, 3, 2, 4, 9. I think we've tested the invalidate rects enough. var phase7Values = new[] { values[0], values[1], values[3], values[2], values[4], values[9] }; owner.ReplaceObjSeq1(phase7Values); VerifyParagraphs(root, new[] { null, "2", "4", null }); // Should have added lazy box at end. VerifyLazyContents(root.LastBox, new[] { values[9] }); // Insert between two non-lazy items: 0, 1, 3, 2, 5, 6, 4, 9. var phase8Values = new[] { values[0], values[1], values[3], values[2], values[5], values[6], values[4], values[9] }; owner.ReplaceObjSeq1(phase8Values); VerifyParagraphs(root, new[] { null, "2", null, "4", null }); // Should have added lazy box in middle. VerifyLazyContents(root.FirstBox.Next.Next, new[] { values[5], values[6] }); // Try a more complex overwrite. We'll replace the last item in the first lazy box and the first one in the second var phase9Values = new[] { values[0], values[1], values[7], values[2], values[8], values[6], values[4], values[9] }; owner.ReplaceObjSeq1(phase9Values); VerifyParagraphs(root, new[] { null, "4", null }); // Should replace first 3 items with new lazy box. VerifyLazyContents(root.FirstBox, new[] { values[0], values[1], values[7], values[2], values[8], values[6] }); }
/// <summary> /// Redo the layout of the paragraph given the Source changes indicated in the given details. /// </summary> /// <param name="details"></param> private void Relayout(SourceChangeDetails details) { // We would prefer to keep both sources intact, but we can't just do Source = details.NewSource, // because there is currently no way to change the Source of existing segments we may want to reuse. Source.Copyfrom(details.NewSource); var oldHeight = Height; var oldWidth = Width; using (var gh = Root.Site.DrawingInfo) { // Enhance JohnT: margins: need to adjust MaxWidth for margins and padding of containing boxes. var info = new LayoutInfo(ChildTransformFromRootTransform(gh.Transform), Root.LastLayoutInfo.MaxWidth, gh.VwGraphics, Root.LastLayoutInfo.RendererFactory); var builder = new ParaBuilder(this, info); using (var lcb = new LayoutCallbacks(Root)) { builder.Relayout(details, lcb); if (Height != oldHeight || Width != oldWidth) RelayoutParents(gh); } } }
/// <summary> /// Redo layout. Should produce the same segments as FullLayout, but assume that segments for text up to /// details.StartChange may be reused (if not affected by changing line breaks), and segments after /// details.StartChange+details.DeleteCount may be re-used if a line break works out (and after adjusting /// their begin offset). /// </summary> /// <param name="details"></param> internal void Relayout(SourceChangeDetails details, LayoutCallbacks lcb) { m_reuseableLines = m_para.Lines; m_lines = new List <ParaLine>(); m_renderRunIndex = 0; m_ichRendered = 0; IRenderRun last = m_renderRuns[m_renderRuns.Count - 1]; m_ichLim = last.RenderStart + last.RenderLength; m_lastRenderRunIndex = m_renderRuns.Count; Rectangle invalidateRect = m_para.InvalidateRect; int delta = details.InsertCount - details.DeleteCount; int oldHeight = m_para.Height; int oldWidth = m_para.Width; // Make use of details.StartChange to reuse some lines at start. if (m_reuseableLines.Count > 0) { // As long as we have two complete lines before the change, we can certainly reuse the first of them. while (m_reuseableLines.Count > 2 && details.StartChange > m_reuseableLines[2].IchMin) { m_lines.Add(m_reuseableLines[0]); m_reuseableLines.RemoveAt(0); } // If we still have one complete line before the change, we can reuse it provided there is white // space after the end of the line and before the change. if (m_reuseableLines.Count > 1) { int startNextLine = m_reuseableLines[1].IchMin; if (details.StartChange > startNextLine) { bool fGotWhite = false; string line1Text = m_reuseableLines[1].CheckedText; int lim = details.StartChange - startNextLine; var cpe = LgIcuCharPropEngineClass.Create(); for (int ich = 0; ich < lim; ich++) { // Enhance JohnT: possibly we need to consider surrogates here? // Worst case is we don't reuse a line we could have, since a surrogate won't // be considered white. if (cpe.get_IsSeparator(Convert.ToInt32(line1Text[ich]))) { fGotWhite = true; break; } } if (fGotWhite) { m_lines.Add(m_reuseableLines[0]); m_reuseableLines.RemoveAt(0); } } } m_ichRendered = m_reuseableLines[0].IchMin; int topOfFirstDiscardedLine = m_reuseableLines[0].Top; // We don't need to invalidate the lines we're keeping. invalidateRect = new Rectangle(invalidateRect.Left, invalidateRect.Top + topOfFirstDiscardedLine, invalidateRect.Width, invalidateRect.Height - topOfFirstDiscardedLine); } // Figure out which run we need to continue from, to correspond to the start of the first line // we need to rebuild. while (m_renderRunIndex < m_renderRuns.Count && m_renderRuns[m_renderRunIndex].RenderLim <= m_ichRendered) { m_renderRunIndex++; } while (!Finished) { // Todo: I think we need to adjust available width if this is the first line. BuildALine(); // Drop any initial reusable lines we now determine to be unuseable after all. // If we've used characters beyond the start of this potentially reusable line, we can't reuse it. // Also, we don't reuse empty lines. Typically an empty line is left over from a previously empty // paragraph, and we no longer need the empty segment, even though it doesn't have any of the same // characters (since it has none) as the segment that has replaced it. while (m_reuseableLines.Count > 0 && (m_ichRendered > m_reuseableLines[0].IchMin + delta || m_reuseableLines[0].Length == 0)) { m_reuseableLines.RemoveAt(0); } if (m_reuseableLines.Count > 0) { // See if we can resync. var nextLine = m_reuseableLines[0]; if (m_ichRendered == nextLine.IchMin + delta) { // reuse it. int top = m_gapTop; if (m_lines.Count > 0) { ParaLine previous = m_lines.Last(); previous.LastBox.Next = nextLine.FirstBox; top = TopOfNextLine(previous, nextLine.Ascent); } m_lines.AddRange(m_reuseableLines); if (top != nextLine.Top) { ParaLine previous = null; foreach (var line in m_reuseableLines) { if (previous != null) // first time top has already been computed { top = TopOfNextLine(previous, line.Ascent); } line.Top = top; // BEFORE ArrangeBoxes, since it gets copied to the individual boxes m_currentLine.ArrangeBoxes(m_para.Style.ParaAlignment, m_gapLeft, m_gapRight, 0, m_layoutInfo.MaxWidth, TopDepth); previous = line; } } else { // reusable lines have not moved, we don't need to invalidate them. invalidateRect.Height -= (m_reuseableLines.Last().Bottom - top); } for (Box box = nextLine.FirstBox; box != null; box = box.Next) { if (box is StringBox) { (box as StringBox).IchMin += delta; } } break; } } } SetParaInfo(); // if the paragraph got larger, we need to invalidate the extra area. // (But, don't reduce it if it got smaller; we want to invalidate all the old stuff as well as all the new.) if (m_para.Height > oldHeight) { invalidateRect.Height += m_para.Height - oldHeight; } if (m_para.Width > oldWidth) { invalidateRect.Width += m_para.Width - oldWidth; } lcb.InvalidateInRoot(invalidateRect); }
public void DrawingBordersandBackground() { var root = new RootBoxFdo(new AssembledStyles()); SetupFakeRootSite(root); var layoutInfo = HookupTests.MakeLayoutInfo(int.MaxValue / 2, m_gm.VwGraphics, 55); root.RendererFactory = layoutInfo.RendererFactory; var mock1 = new MockData1() { SimpleThree = "This is the first paragraph." }; // The length of the second paragraph is found by experiment to be enough so that // despite its lacking borders it also breaks into 2 lines in the second step. var mock2 = new MockData1() { SimpleThree = "Here is another paragraph. It needs to be a bit longer." }; root.Builder.Show( Paragraph.Containing(Display.Of(() => mock1.SimpleThree)).BackColor(Color.Red) .Margins(1.Points(), 2.Points(), 3.Points(), 4.Points()) .Borders(5.Points(), 6.Points(), 7.Points(), 8.Points(), Color.Blue) .Pads(9.Points(), 10.Points(), 11.Points(), 12.Points()), Paragraph.Containing(Display.Of(() => mock2.SimpleThree)).BackColor(Color.Yellow) .Margins(1.Points(), 2.Points(), 3.Points(), 4.Points())); root.Layout(layoutInfo); // We want to keep track of the sequence of paint operations in all three segments. var drawActions = new List <object>(); var vg = new MockGraphics(); vg.DrawActions = drawActions; var para1 = (ParaBox)root.FirstBox; var stringbox1 = (StringBox)para1.FirstBox; var seg1 = (FakeSegment)stringbox1.Segment; seg1.DrawActions = drawActions; var para2 = (ParaBox)para1.Next; var stringbox2 = (StringBox)para2.FirstBox; var seg2 = (FakeSegment)stringbox2.Segment; seg2.DrawActions = drawActions; var site = (MockSite)root.Site; root.Paint(vg, site.m_transform); var paintTrans = site.m_transform; int position = 0; int red = (int)ColorUtil.ConvertColorToBGR(Color.Red); int margLeft = layoutInfo.MpToPixelsX(1000); Assert.That(margLeft, Is.EqualTo(1)); int bordLeft = layoutInfo.MpToPixelsX(5000); Assert.That(bordLeft, Is.EqualTo(7)); int xOffset = 2 - 100; // how far it is pushed over by the offsets of the layoutInfo int margTop = layoutInfo.MpToPixelsY(2000); Assert.That(margTop, Is.EqualTo(3)); int bordTop = layoutInfo.MpToPixelsY(6000); Assert.That(bordTop, Is.EqualTo(8)); int yOffset = 2 - 200; // how far it is pushed down by the offsets of the layoutInfo int padLeft = layoutInfo.MpToPixelsX(9000); Assert.That(padLeft, Is.EqualTo(12)); int padRight = layoutInfo.MpToPixelsX(11000); Assert.That(padRight, Is.EqualTo(15)); int padTop = layoutInfo.MpToPixelsY(10000); Assert.That(padTop, Is.EqualTo(13)); int padBottom = layoutInfo.MpToPixelsY(12000); Assert.That(padBottom, Is.EqualTo(16)); // First it should draw a background rectangle for the first paragraph. // It is indented by the left margin and the left border, and down by the top margin and border. // The other side is determined by the size of the embedded box and the two pads. VerifyRect(drawActions, ref position, margLeft + bordLeft + xOffset, margTop + bordTop + yOffset, margLeft + bordLeft + xOffset + stringbox1.Width + padLeft + padRight, margTop + bordTop + yOffset + stringbox1.Height + padTop + padBottom, red); int bordBottom = layoutInfo.MpToPixelsY(8000); Assert.That(bordBottom, Is.EqualTo(11)); int blue = (int)ColorUtil.ConvertColorToBGR(Color.Blue); // It's arbitrary what order we draw the borders, and I wish the test didn't specify it, // but in fact the current implementation draws the left border first. VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + yOffset, margLeft + bordLeft + xOffset, margTop + bordTop + yOffset + padTop + stringbox1.Height + padBottom + bordBottom, blue); int bordRight = layoutInfo.MpToPixelsX(7000); Assert.That(bordRight, Is.EqualTo(9)); // Then the top border VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + yOffset, margLeft + bordLeft + xOffset + padLeft + stringbox1.Width + padRight + bordRight, margTop + bordTop + yOffset, blue); // Then the right border VerifyRect(drawActions, ref position, margLeft + bordLeft + xOffset + padLeft + stringbox1.Width + padRight, margTop + yOffset, margLeft + bordLeft + xOffset + padLeft + stringbox1.Width + padRight + bordRight, margTop + bordTop + yOffset + padTop + stringbox1.Height + padBottom + bordBottom, blue); // Then the bottom border VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + bordTop + yOffset + padTop + stringbox1.Height + padBottom, margLeft + bordLeft + xOffset + padLeft + stringbox1.Width + padRight + bordRight, margTop + bordTop + yOffset + padTop + stringbox1.Height + padBottom + bordBottom, blue); // Figure an adjusted y offset for the second paragraph. Everything is down by the height // of the first paragraph, except that the top and bottom margins overlap by the // height of the smaller. int yOffset2 = yOffset + para1.Height - margTop; int yellow = (int)ColorUtil.ConvertColorToBGR(Color.Yellow); // Next a background block for the second paragraph. // (Background color should be reset for the embedded string boxes, so they should not draw their own // background.) VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + yOffset2, margLeft + xOffset + stringbox2.Width, margTop + yOffset2 + stringbox2.Height, yellow); // Verify the position where the text is drawn VerifyDraw(drawActions, ref position, seg1, margLeft + bordLeft + padLeft + 2, margTop + bordTop + padTop + 2); VerifyDraw(drawActions, ref position, seg2, margLeft + 2, para1.Height + 2); //margTop cancels out // And that should be all! Assert.That(position, Is.EqualTo(drawActions.Count)); // Verify that multi-lines in a paragraph are appropriately laid out with margin etc. int maxWidth = para1.Width - FakeRenderEngine.SimulatedWidth("paragraph"); // This maxWidth should force each paragraph to make two segments. layoutInfo = HookupTests.MakeLayoutInfo(maxWidth, m_gm.VwGraphics, 55); root.Layout(layoutInfo); drawActions.Clear(); position = 0; var stringbox1a = (StringBox)para1.FirstBox; var seg1a = (FakeSegment)stringbox1a.Segment; seg1a.DrawActions = drawActions; var stringbox1b = (StringBox)stringbox1a.Next; var seg1b = (FakeSegment)stringbox1b.Segment; seg1b.DrawActions = drawActions; var stringbox2a = (StringBox)para2.FirstBox; var seg2a = (FakeSegment)stringbox2a.Segment; seg2a.DrawActions = drawActions; var stringbox2b = (StringBox)stringbox2a.Next; var seg2b = (FakeSegment)stringbox2b.Segment; seg2b.DrawActions = drawActions; root.Paint(vg, site.m_transform); int margRight = layoutInfo.MpToPixelsX(3000); Assert.That(margRight, Is.EqualTo(4)); // First it should draw a background rectangle for the first paragraph. // It is indented by the left margin and the left border, and down by the top margin and border. // The other side is determined by maxWidth minus the right margin and border. int contentHeight1 = stringbox1a.Height + stringbox1b.Height; VerifyRect(drawActions, ref position, margLeft + bordLeft + xOffset, margTop + bordTop + yOffset, maxWidth - margRight - bordRight + xOffset, margTop + bordTop + yOffset + contentHeight1 + padTop + padBottom, red); // It's arbitrary what order we draw the borders, and I wish the test didn't specify it, // but in fact the current implementation draws the left border first. VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + yOffset, margLeft + bordLeft + xOffset, margTop + bordTop + yOffset + padTop + contentHeight1 + padBottom + bordBottom, blue); // Then the top border VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + yOffset, maxWidth - margRight + xOffset, margTop + bordTop + yOffset, blue); // Then the right border VerifyRect(drawActions, ref position, maxWidth - margRight - bordRight + xOffset, margTop + yOffset, maxWidth - margRight + xOffset, margTop + bordTop + yOffset + padTop + contentHeight1 + padBottom + bordBottom, blue); // Then the bottom border VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + bordTop + yOffset + padTop + contentHeight1 + padBottom, maxWidth - margRight + xOffset, margTop + bordTop + yOffset + padTop + contentHeight1 + padBottom + bordBottom, blue); // Figure an adjusted y offset for the second paragraph. Everything is down by the height // of the first paragraph, except that the top and bottom margins overlap by the // height of the smaller. yOffset2 = yOffset + para1.Height - margTop; // Next a background block for the second paragraph. // (Background color should be reset for the embedded string boxes, so they should not draw their own // background.) VerifyRect(drawActions, ref position, margLeft + xOffset, margTop + yOffset2, maxWidth - margRight + xOffset, margTop + yOffset2 + stringbox2a.Height + stringbox2b.Height, yellow); // Verify the position where the text is drawn VerifyDraw(drawActions, ref position, seg1a, margLeft + bordLeft + padLeft + 2, margTop + bordTop + padTop + 2); VerifyDraw(drawActions, ref position, seg1b, margLeft + bordLeft + padLeft + 2, margTop + bordTop + padTop + 2 + stringbox1a.Height); VerifyDraw(drawActions, ref position, seg2a, margLeft + 2, para1.Height + 2); //margTop cancels out VerifyDraw(drawActions, ref position, seg2b, margLeft + 2, para1.Height + 2 + stringbox2a.Height); //margTop cancels out // And that should be all! Assert.That(position, Is.EqualTo(drawActions.Count)); // A quick check that Relayout puts things in the same places. drawActions.Clear(); position = 0; var fixupMap = new Dictionary <Box, Rectangle>(); fixupMap[para1] = new Rectangle(0, 0, 10, 10); var oldstring1aLeft = stringbox1a.Left; var oldstring1bTop = stringbox1b.Top; using (var lcb = new LayoutCallbacks(root)) root.Relayout(layoutInfo, fixupMap, lcb); Assert.That(drawActions.Count, Is.EqualTo(0)); Assert.That(para1.FirstBox.Left, Is.EqualTo(oldstring1aLeft)); Assert.That(para1.FirstBox.Next.Top, Is.EqualTo(oldstring1bTop)); }
public void PileAndBlock() { var root = new RootBoxFdo(new AssembledStyles()); SetupFakeRootSite(root); var layoutInfo = HookupTests.MakeLayoutInfo(int.MaxValue / 2, m_gm.VwGraphics, 55); root.RendererFactory = layoutInfo.RendererFactory; var mock1 = new MockData1() { SimpleThree = "This is the first paragraph." }; // The length of the second paragraph is found by experiment to be enough so that // despite its lacking borders it also breaks into 2 lines in the second step. var mock2 = new MockData1() { SimpleThree = "Here is another paragraph. It needs to be a bit longer." }; root.Builder.Show( Div.Containing( Display.Block(Color.Red, 25000, 18000).BackColor(Color.Purple) .Margins(3000, 3000, 3000, 3000) .Border(5000, Color.Blue) .Pads(4000, 4000, 4000, 4000), Display.Block(Color.Green, 25000, 18000) ).BackColor(Color.Pink) // these apply to div. .Margins(1000, 1000, 1000, 1000) .Border(2000, Color.Gold) .Pads(6000, 6000, 6000, 6000)); root.Layout(layoutInfo); // We want to keep track of the sequence of paint operations in all three segments. var drawActions = new List <object>(); var vg = new MockGraphics(); vg.DrawActions = drawActions; var site = (MockSite)root.Site; root.Paint(vg, site.m_transform); var paintTrans = site.m_transform; int position = 0; int xOffset = 2 - 100; // how far it is pushed over by the offsets of the layoutInfo int yOffset = 2 - 200; // how far it is pushed down by the offsets of the layoutInfo int red = (int)ColorUtil.ConvertColorToBGR(Color.Red); int pink = (int)ColorUtil.ConvertColorToBGR(Color.Pink); int purple = (int)ColorUtil.ConvertColorToBGR(Color.Purple); int blue = (int)ColorUtil.ConvertColorToBGR(Color.Blue); int green = (int)ColorUtil.ConvertColorToBGR(Color.Green); int gold = (int)ColorUtil.ConvertColorToBGR(Color.Gold); // Technically we could do different conversions in the two directions, but for this test both dpi are the same. int margPile = layoutInfo.MpToPixelsX(1000); int bordPile = layoutInfo.MpToPixelsX(2000); int padPile = layoutInfo.MpToPixelsX(6000); int blockWidth = layoutInfo.MpToPixelsX(25000); int blockHeight = layoutInfo.MpToPixelsX(18000); int margBlock = layoutInfo.MpToPixelsX(3000); int bordBlock = layoutInfo.MpToPixelsX(5000); int padBlock = layoutInfo.MpToPixelsX(4000); // First a background rectangle for the whole pile. var leftPilePad = margPile + bordPile + xOffset; var topPilePad = margPile + bordPile + yOffset; var rightPilePad = margPile + bordPile + 2 * padPile + blockWidth + 2 * margBlock + 2 * bordBlock + 2 * padBlock + xOffset; var bottomPilePad = margPile + bordPile + 2 * padPile + 2 * blockHeight + 2 * margBlock + 2 * bordBlock + 2 * padBlock + yOffset; VerifyRect(drawActions, ref position, leftPilePad, topPilePad, rightPilePad, bottomPilePad, pink); // Left border, whole pile VerifyRect(drawActions, ref position, leftPilePad - bordPile, topPilePad - bordPile, leftPilePad, bottomPilePad + bordPile, gold); // top border, whole pile VerifyRect(drawActions, ref position, leftPilePad - bordPile, topPilePad - bordPile, rightPilePad + bordPile, topPilePad, gold); // right border, whole pile VerifyRect(drawActions, ref position, rightPilePad, topPilePad - bordPile, rightPilePad + bordPile, bottomPilePad + bordPile, gold); // bottom border, whole pile VerifyRect(drawActions, ref position, leftPilePad - bordPile, bottomPilePad, rightPilePad + bordPile, bottomPilePad + bordPile, gold); // background and border for first block. var leftBlockPad = margPile + bordPile + padPile + margBlock + bordBlock + xOffset; var topBlockPad = margPile + bordPile + padPile + margBlock + bordBlock + yOffset; var rightBlockPad = margPile + bordPile + padPile + margBlock + bordBlock + 2 * padBlock + blockWidth + xOffset; var bottomBlockPad = margPile + bordPile + padPile + margBlock + bordBlock + 2 * padBlock + blockHeight + yOffset; VerifyRect(drawActions, ref position, leftBlockPad, topBlockPad, rightBlockPad, bottomBlockPad, purple); // Left border, whole pile VerifyRect(drawActions, ref position, leftBlockPad - bordBlock, topBlockPad - bordBlock, leftBlockPad, bottomBlockPad + bordBlock, blue); // top border, whole pile VerifyRect(drawActions, ref position, leftBlockPad - bordBlock, topBlockPad - bordBlock, rightBlockPad + bordBlock, topBlockPad, blue); // right border, whole pile VerifyRect(drawActions, ref position, rightBlockPad, topBlockPad - bordBlock, rightBlockPad + bordBlock, bottomBlockPad + bordBlock, blue); // bottom border, whole pile VerifyRect(drawActions, ref position, leftBlockPad - bordBlock, bottomBlockPad, rightBlockPad + bordBlock, bottomBlockPad + bordBlock, blue); // The first block itself. VerifyRect(drawActions, ref position, leftBlockPad + padBlock, topBlockPad + padBlock, leftBlockPad + padBlock + blockWidth, topBlockPad + padBlock + blockHeight, red); // The second block itself. var topBlock2 = bottomBlockPad + bordBlock + margBlock; VerifyRect(drawActions, ref position, leftPilePad + padPile, topBlock2, leftPilePad + padPile + blockWidth, topBlock2 + blockHeight, green); // And that should be all! Assert.That(position, Is.EqualTo(drawActions.Count)); // A quick check that Relayout puts things in the same places. drawActions.Clear(); var fixupMap = new Dictionary <Box, Rectangle>(); var div1 = (DivBox)root.FirstBox; var block1 = div1.FirstBox; fixupMap[div1] = new Rectangle(0, 0, 10, 10); fixupMap[block1] = new Rectangle(0, 0, 10, 10); var oldblock1Left = block1.Left; var oldblock1bTop = block1.Top; using (var lcb = new LayoutCallbacks(root)) root.Relayout(layoutInfo, fixupMap, lcb); Assert.That(drawActions.Count, Is.EqualTo(0)); Assert.That(div1.FirstBox.Left, Is.EqualTo(oldblock1Left)); Assert.That(div1.FirstBox.Top, Is.EqualTo(oldblock1bTop)); }