int TopOfNextLine(ParaLine previous, int nextAscent) { int lineHeight = m_layoutInfo.MpToPixelsY(m_para.Style.LineHeight); // where the top has to be if we go by the LineHeight. int topNext = previous.Top + previous.Ascent + Math.Abs(lineHeight) - nextAscent; if (lineHeight >= 0) { return(Math.Max(topNext, previous.Bottom)); // don't allow overlap unless doing exact layout. } return(topNext); }
/// <summary> /// Compute the height of the box, not counting surrounding gaps, once its lines have been laid out. /// </summary> int ComputeHeight() { if (m_lines.Count == 0) { return(0); } ParaLine lastLine = m_lines[m_lines.Count - 1]; var chrp = m_para.Style.Chrp; m_layoutInfo.VwGraphics.SetupGraphics(ref chrp); int bottomOfPara = TopOfNextLine(lastLine, m_layoutInfo.VwGraphics.FontAscent); return(bottomOfPara - m_gapTop); }
private void BuildALine() { m_currentLine = new ParaLine(); m_spaceLeftOnCurrentLine = m_layoutInfo.MaxWidth - m_surroundWidth; if (m_lines.Count == 0) { m_currentLine.Top = m_gapTop; m_spaceLeftOnCurrentLine -= m_layoutInfo.MpToPixelsY(m_para.Style.FirstLineIndent); } m_lines.Add(m_currentLine); m_lineSegTypes.Clear(); while (!Finished) { if (!AddSomethingToLine()) { break; } } while (!FinalizeLine()) { if (!Backtrack()) { break; } } if (m_lines.Count > 1) { ParaLine previous = m_lines[m_lines.Count - 2]; previous.LastBox.Next = m_currentLine.FirstBox; m_currentLine.Top = TopOfNextLine(previous, m_currentLine.Ascent); } m_currentLine.ArrangeBoxes(m_para.Style.ParaAlignment, m_gapLeft, m_gapRight, m_lines.Count == 1 ? m_layoutInfo.MpToPixelsY(m_para.Style.FirstLineIndent) : 0, m_layoutInfo.MaxWidth, TopDepth); }
int TopOfNextLine(ParaLine previous, int nextAscent) { int lineHeight = m_layoutInfo.MpToPixelsY(m_para.Style.LineHeight); // where the top has to be if we go by the LineHeight. int topNext = previous.Top + previous.Ascent + Math.Abs(lineHeight) - nextAscent; if (lineHeight >= 0) return Math.Max(topNext, previous.Bottom); // don't allow overlap unless doing exact layout. return topNext; }
private void BuildALine() { m_currentLine = new ParaLine(); m_spaceLeftOnCurrentLine = m_layoutInfo.MaxWidth - m_surroundWidth; if (m_lines.Count == 0) { m_currentLine.Top = m_gapTop; m_spaceLeftOnCurrentLine -= m_layoutInfo.MpToPixelsY(m_para.Style.FirstLineIndent); } m_lines.Add(m_currentLine); m_lineSegTypes.Clear(); while (!Finished) if (!AddSomethingToLine()) break; while (!FinalizeLine()) if (!Backtrack()) break; if (m_lines.Count > 1) { ParaLine previous = m_lines[m_lines.Count - 2]; previous.LastBox.Next = m_currentLine.FirstBox; m_currentLine.Top = TopOfNextLine(previous, m_currentLine.Ascent); } m_currentLine.ArrangeBoxes(m_para.Style.ParaAlignment, m_gapLeft, m_gapRight, m_lines.Count == 1 ? m_layoutInfo.MpToPixelsY(m_para.Style.FirstLineIndent) : 0, m_layoutInfo.MaxWidth, TopDepth); }
public void OrderedBoxes() { var styles = new AssembledStyles(); // Nothing is reversed when everything is at level 0. var line = new ParaLine(); line.Add(MakeStringBox(styles, 0, false)); line.Add(MakeStringBox(styles, 0, false)); line.Add(MakeStringBox(styles, 0, false)); var boxes = new List<Box>(line.OrderedBoxes(0)); VerifyBoxOrder(line, boxes, new[] { 0, 1, 2 }); // Also, three adjacent LTRs in an RTL paragraph are not reversed. boxes = new List<Box>(line.OrderedBoxes(1)); VerifyBoxOrder(line, boxes, new[] { 0, 1, 2 }); // Everything is reversed when everything is at level 1 (ordinary RTL text). line = new ParaLine(); line.Add(MakeStringBox(styles, 1, false)); line.Add(MakeStringBox(styles, 1, false)); line.Add(MakeStringBox(styles, 1, false)); boxes = new List<Box>(line.OrderedBoxes(0)); VerifyBoxOrder(line, boxes, new[] { 2, 1, 0 }); // Also, three adjacent RTL boxes in an LTR paragraph are reversed. boxes = new List<Box>(line.OrderedBoxes(1)); VerifyBoxOrder(line, boxes, new[] { 2, 1, 0 }); // In an RTL paragraph with two adjacent upstream boxes, they preserve their order. line = new ParaLine(); line.Add(MakeStringBox(styles, 1, false)); line.Add(MakeStringBox(styles, 2, false)); line.Add(MakeStringBox(styles, 2, false)); line.Add(MakeStringBox(styles, 1, false)); boxes = new List<Box>(line.OrderedBoxes(0)); VerifyBoxOrder(line, boxes, new[] { 3, 1, 2, 0 }); // In an RTL paragraph with two adjacent upstream boxes, where one is weak, it goes downstream. line = new ParaLine(); line.Add(MakeStringBox(styles, 1, false)); line.Add(MakeStringBox(styles, 2, false)); line.Add(MakeStringBox(styles, 2, true)); line.Add(MakeStringBox(styles, 1, false)); boxes = new List<Box>(line.OrderedBoxes(0)); VerifyBoxOrder(line, boxes, new[] { 3, 2, 1, 0 }); // In an RTL paragraph with three adjacent upstream boxes, where the middle one is weak, it goes upstream. line = new ParaLine(); line.Add(MakeStringBox(styles, 1, false)); line.Add(MakeStringBox(styles, 2, false)); line.Add(MakeStringBox(styles, 2, true)); line.Add(MakeStringBox(styles, 2, false)); line.Add(MakeStringBox(styles, 1, false)); boxes = new List<Box>(line.OrderedBoxes(0)); VerifyBoxOrder(line, boxes, new[] { 4, 1, 2, 3, 0 }); }
/// <summary> /// Verify the re-ordering of the boxes in the line in the list. /// The box at position n in boxes should be the one originally at index[n] in boxes. /// </summary> private void VerifyBoxOrder(ParaLine line, List<Box> boxes, int[] indexes) { var original = new List<Box>(line.Boxes); for (int i = 0; i < original.Count; i++) Assert.That(boxes[i], Is.EqualTo(original[indexes[i]])); }
public void ReverseUpstreamBoxes() { var line = new ParaLine(); var styles = new AssembledStyles(); // no boxes (pathological) line.ReverseUpstreamBoxes(0, new List<Box>(), 0); // one box line = new ParaLine(); line.Add(MakeStringBox(styles, 3, false)); var boxes = new List<Box>(line.Boxes); line.ReverseUpstreamBoxes(0, boxes, 0); VerifyBoxOrder(line, boxes, new [] {0}); // two boxes: should re-order only if depth is small enough. line = new ParaLine(); line.Add(MakeStringBox(styles, 2, false)); line.Add(MakeStringBox(styles, 1, false)); boxes = new List<Box>(line.Boxes); line.ReverseUpstreamBoxes(3, boxes, 0); VerifyBoxOrder(line, boxes, new[] { 0, 1 }); // not re-ordered, all depths too small line.ReverseUpstreamBoxes(2, boxes, 0); VerifyBoxOrder(line, boxes, new[] { 0, 1 }); // not re-ordered, just one that could be line.ReverseUpstreamBoxes(1, boxes, 0); VerifyBoxOrder(line, boxes, new[] { 1, 0 }); // re-ordered, all <= depth line.ReverseUpstreamBoxes(0, boxes, 0); VerifyBoxOrder(line, boxes, new[] { 0, 1 }); // re-ordered again, all < depth // quite a mixture! line = new ParaLine(); MockDirectionSegment.NextIndex = 0; line.Add(MakeStringBox(styles, 2, false)); line.Add(MakeStringBox(styles, 3, false)); line.Add(MakeStringBox(styles, 1, false)); line.Add(MakeStringBox(styles, 2, false)); line.Add(MakeStringBox(styles, 4, false)); line.Add(MakeStringBox(styles, 4, false)); line.Add(MakeStringBox(styles, 4, false)); line.Add(MakeStringBox(styles, 4, false)); line.Add(MakeStringBox(styles, 3, false)); line.Add(MakeStringBox(styles, 4, false)); line.Add(MakeStringBox(styles, 5, false)); line.Add(MakeStringBox(styles, 5, false)); boxes = new List<Box>(line.Boxes); line.ReverseUpstreamBoxes(0, boxes, 0); VerifyBoxOrder(line, boxes, new[] { 11,10,9,8,7,6,5,4,3,2,1,0 }); // reverse everything line.ReverseUpstreamBoxes(1, boxes, 0); VerifyBoxOrder(line, boxes, new[] { 0,1,2,3,4,5,6,7,8,9,10,11 }); // back again // now the level 1 box at index 2 does not move line.ReverseUpstreamBoxes(2, boxes, 0); VerifyBoxOrder(line, boxes, new[] { 1, 0, 2, 11, 10, 9, 8, 7, 6, 5, 4, 3 }); line.ReverseUpstreamBoxes(2, boxes, 0); // put them back! VerifyBoxOrder(line, boxes, new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }); // back again line.ReverseUpstreamBoxes(4, boxes, 0); // only the groups with more 4 or more reverse VerifyBoxOrder(line, boxes, new[] { 0, 1, 2, 3, 7, 6, 5, 4, 8, 11, 10, 9 }); // back again boxes = new List<Box>(line.Boxes); line.ReverseUpstreamBoxes(1, boxes, 0); VerifyBoxOrder(line, boxes, new[] { 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }); line.ReverseUpstreamBoxes(2, boxes, 0); VerifyBoxOrder(line, boxes, new[] { 3, 4, 5, 6, 7, 8, 9, 10, 11, 2, 0, 1 }); line.ReverseUpstreamBoxes(3, boxes, 0); VerifyBoxOrder(line, boxes, new[] { 3, 11, 10, 9, 8, 7, 6, 5, 4, 2, 0, 1 }); line.ReverseUpstreamBoxes(4, boxes, 0); VerifyBoxOrder(line, boxes, new[] { 3, 9, 10, 11, 8, 4, 5, 6, 7, 2, 0, 1 }); line.ReverseUpstreamBoxes(5, boxes, 0); VerifyBoxOrder(line, boxes, new[] { 3, 9, 11, 10, 8, 4, 5, 6, 7, 2, 0, 1 }); boxes = new List<Box>(line.OrderedBoxes(0)); // This should do all in one step the reversals indicated in the previous test // sequence. The results above indicate how the final sequence is arrived at. VerifyBoxOrder(line, boxes, new[] { 3, 9, 11, 10, 8, 4, 5, 6, 7, 2, 0, 1 }); line = new ParaLine(); line.Add(new BlockBox(styles, Color.Red, 200, 300)); line.Add(MakeStringBox(styles, 2, true)); line.Add(MakeStringBox(styles, 2, true)); line.Add(new BlockBox(styles, Color.Red, 200, 300)); boxes = new List<Box>(line.Boxes); line.ReverseUpstreamBoxes(2, boxes, 1); VerifyBoxOrder(line, boxes, new[] { 0, 2, 1, 3}); // reverse just the string boxes }
private void VerifyDepth(ParaLine line, int index, int expected) { int depth; ((StringBox) line.Boxes.Skip(index).First()).Segment.get_DirectionDepth(0, out depth); Assert.That(depth, Is.EqualTo(expected)); }
public void SetWeakDirections() { var line = new ParaLine(); var styles = new AssembledStyles(); // no boxes (pathological) line.SetWeakDirections(0); // no weak boxes line = new ParaLine(); line.Add(MakeStringBox(styles, 3, false)); line.SetWeakDirections(0); VerifyDepth(line, 0, 3); // one weak box alone: no change line = new ParaLine(); line.Add(MakeStringBox(styles, 3, true)); line.SetWeakDirections(1); VerifyDepth(line, 0, 1); // one at start, followed by a non-weak line = new ParaLine(); line.Add(MakeStringBox(styles, 2, true)); line.Add(MakeStringBox(styles, 1, false)); line.SetWeakDirections(0); VerifyDepth(line, 0, 0); // adjacent to paragraph boundary, topDepth wins line.SetWeakDirections(2); VerifyDepth(line, 0, 1); // adjacent box has lower depth. // two at start, followed by non-weak; also two at end and four in middle. line = new ParaLine(); line.Add(MakeStringBox(styles, 2, true)); line.Add(MakeStringBox(styles, 3, true)); line.Add(MakeStringBox(styles, 1, false)); line.Add(MakeStringBox(styles, 2, false)); line.Add(MakeStringBox(styles, 4, true)); line.Add(MakeStringBox(styles, 4, true)); line.Add(MakeStringBox(styles, 4, true)); line.Add(MakeStringBox(styles, 4, true)); line.Add(MakeStringBox(styles, 3, false)); line.Add(MakeStringBox(styles, 4, false)); line.Add(MakeStringBox(styles, 5, true)); line.Add(MakeStringBox(styles, 5, true)); line.SetWeakDirections(6); // let the adjacent boxes rather than the paragraph depth win. VerifyDepth(line, 0, 1); // first two set to depth of following box VerifyDepth(line, 1, 1); VerifyDepth(line, 4, 2); // middle four set to depth of preceding box VerifyDepth(line, 5, 2); VerifyDepth(line, 6, 2); VerifyDepth(line, 7, 2); VerifyDepth(line, 10, 4); // last two set to depth of preceding VerifyDepth(line, 11, 4); line.SetWeakDirections(0); // let the adjacent boxes rather than the paragraph depth win. VerifyDepth(line, 0, 0); // topdepth from para boundary VerifyDepth(line, 1, 0); VerifyDepth(line, 4, 2); // middle four set to depth of preceding box VerifyDepth(line, 5, 2); VerifyDepth(line, 6, 2); VerifyDepth(line, 7, 2); VerifyDepth(line, 10, 0); // topdepth from para boundary VerifyDepth(line, 11, 0); line= new ParaLine(); line.Add(MakeStringBox(styles, 2, false)); line.Add(MakeStringBox(styles, 3, true)); line.Add(new BlockBox(styles, Color.Red, 200, 300)); line.SetWeakDirections(1); // The block box is considered to have depth 1 and wins VerifyDepth(line, 1, 1); }
public void ArrangeBoxes() { var styles = new AssembledStyles(); // Ordinary layout var line = new ParaLine(); line.Add(MakeStringBox(styles, 0, false)); var box2 = MakeStringBox(styles, 0, false); line.Add(box2); line.Add(MakeStringBox(styles, 0, false)); ((MockDirectionSegment) box2.Segment).SimulatedWidth = 20; var layoutInfo = MakeLayoutInfo(); foreach (var box in line.Boxes) box.Layout(layoutInfo); line.ArrangeBoxes(FwTextAlign.ktalLeft, 7, 0, 0, 100, 0); Assert.That(line.Boxes.First().Left, Is.EqualTo(7)); Assert.That(line.Boxes.Skip(1).First().Left, Is.EqualTo(17)); // past first default(10)-width box Assert.That(line.Boxes.Skip(2).First().Left, Is.EqualTo(37)); // past second, 20-pixel box. // Still LTR, but aligned right line.ArrangeBoxes(FwTextAlign.ktalRight, 7, 3, 10, 100, 0); Assert.That(line.Boxes.Skip(2).First().Left, Is.EqualTo(100 - 3 - 10)); // from maxwidth, minus gapright, minus 10 pix width. Assert.That(line.Boxes.Skip(1).First().Left, Is.EqualTo(100 - 3 - 10 - 20)); // further left by 20 pix width of middle default-width box Assert.That(line.Boxes.First().Left, Is.EqualTo(100 -3 - 10 - 20 - 10)); // still further by 10 pix width of first box // Still LTR, but aligned center line.ArrangeBoxes(FwTextAlign.ktalCenter, 7, 3, 10, 100, 0); int sumBoxWidth = 40; int available = 100 - 7 - 3 - 10; // the width in which we can center int start = 7 + 10 + (available - sumBoxWidth)/2; Assert.That(line.Boxes.First().Left, Is.EqualTo(start)); Assert.That(line.Boxes.Skip(1).First().Left, Is.EqualTo(start + 10)); // past first default(10)-width box Assert.That(line.Boxes.Skip(2).First().Left, Is.EqualTo(start + 30)); // past second, 20-pixel box. // Enhance: add test for justified. // Now simulate RTL paragraph with similar contents. Boxes are in opposite order. line = new ParaLine(); line.Add(MakeStringBox(styles, 1, false)); box2 = MakeStringBox(styles, 1, false); line.Add(box2); line.Add(MakeStringBox(styles, 1, false)); ((MockDirectionSegment)box2.Segment).SimulatedWidth = 30; foreach (var box in line.Boxes) box.Layout(layoutInfo); line.ArrangeBoxes(FwTextAlign.ktalLeft, 7, 0, 0, 100, 1); Assert.That(line.Boxes.Skip(2).First().Left, Is.EqualTo(7)); // last box is now leftmost. Assert.That(line.Boxes.Skip(1).First().Left, Is.EqualTo(17)); // second is left of first by width of first Assert.That(line.Boxes.First().Left, Is.EqualTo(47)); // first is now on the right. // Verify that it works correctly for align right (obey firstLineIndent!) and center. line.ArrangeBoxes(FwTextAlign.ktalRight, 7, 3, 15, 100, 1); var rightOfFirstBox = 100 - 3 - 15; Assert.That(line.Boxes.First().Right, Is.EqualTo(rightOfFirstBox)); Assert.That(line.Boxes.Skip(1).First().Right, Is.EqualTo(rightOfFirstBox - 10)); // past first default-width box Assert.That(line.Boxes.Skip(2).First().Right, Is.EqualTo(rightOfFirstBox - 10 - 30)); // past second, 30-pixel box. }
/// <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); }