internal void FullLayout() { m_lines = new List <ParaLine>(); m_renderRunIndex = 0; m_ichRendered = 0; m_lastRenderRunIndex = m_renderRuns.Count; if (m_renderRuns.Count != 0) { IRenderRun last = m_renderRuns[m_renderRuns.Count - 1]; m_ichLim = last.RenderStart + last.RenderLength; while (!Finished) { BuildALine(); } } else { m_ichLim = 0; } SetParaInfo(); }
/// <summary> /// Add something to the line. Return true if we should keep trying to add more. (That is, all of the current thing fit, /// and there is still room left; this routine is not responsible to determine whether there IS anything else to add.) /// If there is not yet anything in the line, this routine MUST add something; otherwise, it is allowed to fail, /// returning false without changing anything. /// </summary> private bool AddSomethingToLine() { m_currentRenderRun = m_renderRuns[m_renderRunIndex]; Box boxToAdd = m_currentRenderRun.Box; if (boxToAdd != null) { // The current run works out to a single box; add it if it fits. Add it anyway if the line // is currently empty. boxToAdd.Layout(m_layoutInfo); if (m_currentLine.FirstBox != null && boxToAdd.Width > m_spaceLeftOnCurrentLine) return false; m_spaceLeftOnCurrentLine -= boxToAdd.Width; AddBoxToLine(boxToAdd, LgEndSegmentType.kestOkayBreak); // always OK to break after non-string. boxToAdd.Container = m_para; m_ichRendered = m_currentRenderRun.RenderLim; m_renderRunIndex++; } else { // current run is not a simple box. Make a text box out of part or all of it, or possibly also subsequent // runs that are not boxes and use the same renderer. IRenderEngine renderer = m_layoutInfo.GetRenderer(m_currentRenderRun.Ws); // If our text source doesn't yet know about the writing system factory, make sure it does. if (m_para.Source.GetWsFactory() == null) m_para.Source.SetWsFactory(renderer.WritingSystemFactory); int ichRunLim = GetLimitOfRunWithSameRenderer(renderer, m_renderRunIndex); ILgSegment seg; int dichLim, dxWidth; LgEndSegmentType est; bool mustGetSomeSeg = m_currentLine.FirstBox == null; var twsh = GetNextTwsh(); renderer.FindBreakPoint(m_layoutInfo.VwGraphics, m_para.Source, null, m_ichRendered, ichRunLim, ichRunLim, false, mustGetSomeSeg, m_spaceLeftOnCurrentLine, (mustGetSomeSeg ? LgLineBreak.klbClipBreak : LgLineBreak.klbWordBreak), m_currentLine.FirstBox == null ? LgLineBreak.klbClipBreak : LgLineBreak.klbWordBreak, twsh, false, out seg, out dichLim, out dxWidth, out est, null); if (est == LgEndSegmentType.kestNothingFit && twsh != LgTrailingWsHandling.ktwshAll) { // Nothing of the one we were trying for, try for the other. twsh = GetNextTwsh(); renderer.FindBreakPoint(m_layoutInfo.VwGraphics, m_para.Source, null, m_ichRendered, ichRunLim, ichRunLim, false, mustGetSomeSeg, m_spaceLeftOnCurrentLine, (mustGetSomeSeg ? LgLineBreak.klbClipBreak : LgLineBreak.klbWordBreak), m_currentLine.FirstBox == null ? LgLineBreak.klbClipBreak : LgLineBreak.klbWordBreak, twsh, false, out seg, out dichLim, out dxWidth, out est, null); } switch (est) { case LgEndSegmentType.kestNoMore: case LgEndSegmentType.kestOkayBreak: case LgEndSegmentType.kestMoreLines: case LgEndSegmentType.kestWsBreak: case LgEndSegmentType.kestMoreWhtsp: boxToAdd = new StringBox(m_para.Style, seg, m_ichRendered); boxToAdd.Layout(m_layoutInfo); m_spaceLeftOnCurrentLine -= boxToAdd.Width; m_ichRendered += dichLim; boxToAdd.Container = m_para; // If we get NoMore, we should also check, because it might really be a ws break at the end // of the run. if (est == LgEndSegmentType.kestNoMore && ichRunLim < m_para.Source.Length && ichRunLim > 0 && WsInSource(ichRunLim - 1, m_para.Source) != WsInSource(ichRunLim, m_para.Source)) { // The renderer failed to detect it because not told to look further in the source, // but there is in fact a writing system break. // However, if the next character is a box, we want to treat it as a definitely good break. if (m_para.Source.IsThereABoxAt(ichRunLim)) est = LgEndSegmentType.kestOkayBreak; else est = LgEndSegmentType.kestWsBreak; } AddBoxToLine(boxToAdd, est); m_renderRunIndex = AdvanceRenderRunIndexToIch(m_ichRendered, m_renderRunIndex); // We want to return true if more could be put on this line. // Of the cases that take this branch, NoMore means no more input at all, so we can't add any more; // MoreLines means this line is full. So for both of those we return false. OkayBreak allows us to try // to put more segments on this line, as does ws break, and moreWhtsp. return est == LgEndSegmentType.kestOkayBreak || est == LgEndSegmentType.kestWsBreak || est == LgEndSegmentType.kestMoreWhtsp; case LgEndSegmentType.kestNothingFit: Debug.Assert(m_currentLine.FirstBox != null, "Making segment must not return kestNothingFit if line contains nothing"); return false; //case LgEndSegmentType.kestHardBreak: // if() // return true; default: Debug.Assert(false); break; } } return true; }
void VerifyRenderRun(IRenderRun run, int start, int length, string label) { Assert.AreEqual(start, run.RenderStart, label + " - start"); Assert.AreEqual(length, run.RenderLength, label + " - length"); }
void VerifyRenderRun(IRenderRun run, int start, int length, string label) { Assert.AreEqual(start, run.RenderStart, label + " - start"); Assert.AreEqual(length, run.RenderLength, label + " - length"); }
/// <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); }
/// <summary> /// Add something to the line. Return true if we should keep trying to add more. (That is, all of the current thing fit, /// and there is still room left; this routine is not responsible to determine whether there IS anything else to add.) /// If there is not yet anything in the line, this routine MUST add something; otherwise, it is allowed to fail, /// returning false without changing anything. /// </summary> private bool AddSomethingToLine() { m_currentRenderRun = m_renderRuns[m_renderRunIndex]; Box boxToAdd = m_currentRenderRun.Box; if (boxToAdd != null) { // The current run works out to a single box; add it if it fits. Add it anyway if the line // is currently empty. boxToAdd.Layout(m_layoutInfo); if (m_currentLine.FirstBox != null && boxToAdd.Width > m_spaceLeftOnCurrentLine) { return(false); } m_spaceLeftOnCurrentLine -= boxToAdd.Width; AddBoxToLine(boxToAdd, LgEndSegmentType.kestOkayBreak); // always OK to break after non-string. boxToAdd.Container = m_para; m_ichRendered = m_currentRenderRun.RenderLim; m_renderRunIndex++; } else { // current run is not a simple box. Make a text box out of part or all of it, or possibly also subsequent // runs that are not boxes and use the same renderer. IRenderEngine renderer = m_layoutInfo.GetRenderer(m_currentRenderRun.Ws); // If our text source doesn't yet know about the writing system factory, make sure it does. if (m_para.Source.GetWsFactory() == null) { m_para.Source.SetWsFactory(renderer.WritingSystemFactory); } int ichRunLim = GetLimitOfRunWithSameRenderer(renderer, m_renderRunIndex); ILgSegment seg; int dichLim, dxWidth; LgEndSegmentType est; bool mustGetSomeSeg = m_currentLine.FirstBox == null; var twsh = GetNextTwsh(); renderer.FindBreakPoint(m_layoutInfo.VwGraphics, m_para.Source, null, m_ichRendered, ichRunLim, ichRunLim, false, mustGetSomeSeg, m_spaceLeftOnCurrentLine, (mustGetSomeSeg ? LgLineBreak.klbClipBreak : LgLineBreak.klbWordBreak), m_currentLine.FirstBox == null ? LgLineBreak.klbClipBreak : LgLineBreak.klbWordBreak, twsh, false, out seg, out dichLim, out dxWidth, out est, null); if (est == LgEndSegmentType.kestNothingFit && twsh != LgTrailingWsHandling.ktwshAll) { // Nothing of the one we were trying for, try for the other. twsh = GetNextTwsh(); renderer.FindBreakPoint(m_layoutInfo.VwGraphics, m_para.Source, null, m_ichRendered, ichRunLim, ichRunLim, false, mustGetSomeSeg, m_spaceLeftOnCurrentLine, (mustGetSomeSeg ? LgLineBreak.klbClipBreak : LgLineBreak.klbWordBreak), m_currentLine.FirstBox == null ? LgLineBreak.klbClipBreak : LgLineBreak.klbWordBreak, twsh, false, out seg, out dichLim, out dxWidth, out est, null); } switch (est) { case LgEndSegmentType.kestNoMore: case LgEndSegmentType.kestOkayBreak: case LgEndSegmentType.kestMoreLines: case LgEndSegmentType.kestWsBreak: case LgEndSegmentType.kestMoreWhtsp: boxToAdd = new StringBox(m_para.Style, seg, m_ichRendered); boxToAdd.Layout(m_layoutInfo); m_spaceLeftOnCurrentLine -= boxToAdd.Width; m_ichRendered += dichLim; boxToAdd.Container = m_para; // If we get NoMore, we should also check, because it might really be a ws break at the end // of the run. if (est == LgEndSegmentType.kestNoMore && ichRunLim < m_para.Source.Length && ichRunLim > 0 && WsInSource(ichRunLim - 1, m_para.Source) != WsInSource(ichRunLim, m_para.Source)) { // The renderer failed to detect it because not told to look further in the source, // but there is in fact a writing system break. // However, if the next character is a box, we want to treat it as a definitely good break. if (m_para.Source.IsThereABoxAt(ichRunLim)) { est = LgEndSegmentType.kestOkayBreak; } else { est = LgEndSegmentType.kestWsBreak; } } AddBoxToLine(boxToAdd, est); m_renderRunIndex = AdvanceRenderRunIndexToIch(m_ichRendered, m_renderRunIndex); // We want to return true if more could be put on this line. // Of the cases that take this branch, NoMore means no more input at all, so we can't add any more; // MoreLines means this line is full. So for both of those we return false. OkayBreak allows us to try // to put more segments on this line, as does ws break, and moreWhtsp. return(est == LgEndSegmentType.kestOkayBreak || est == LgEndSegmentType.kestWsBreak || est == LgEndSegmentType.kestMoreWhtsp); case LgEndSegmentType.kestNothingFit: Debug.Assert(m_currentLine.FirstBox != null, "Making segment must not return kestNothingFit if line contains nothing"); return(false); //case LgEndSegmentType.kestHardBreak: // if() // return true; default: Debug.Assert(false); break; } } return(true); }