internal FormatSettings( TextFormatterImp formatter, TextSource textSource, TextRunCacheImp runCache, ParaProp pap, TextLineBreak previousLineBreak, bool isSingleLineFormatting, TextFormattingMode textFormattingMode, bool isSideways ) { _isSideways = isSideways; _textFormattingMode = textFormattingMode; _formatter = formatter; _textSource = textSource; _runCache = runCache; _pap = pap; _digitState = new DigitState(); _previousLineBreak = previousLineBreak; _maxLineWidth = Constants.IdealInfiniteWidth; if (isSingleLineFormatting) { // Apply text indent on each line in single line mode _textIndent = _pap.Indent; } }
// ------------------------------------------------------------------ // Constructor. // // wrappingWidth - wrapping width for the line // length - number or characters in the line // width - width of the line // height - height of the line // baseline - baseline of the line // hasInlineObjects - has inline objects? // ------------------------------------------------------------------ internal LineMetrics( #if DEBUG double wrappingWidth, #endif int length, double width, double height, double baseline, bool hasInlineObjects, TextLineBreak textLineBreak) { #if DEBUG _wrappingWidth = wrappingWidth; #endif _start = 0; _width = width; _height = height; _baseline = baseline; _textLineBreak = textLineBreak; _packedData = ((uint) length & LengthMask) | (hasInlineObjects ? HasInlineObjectsMask : 0); }
/// <summary> /// Client to format a text line that fills a paragraph in the document. /// </summary> /// <param name="textSource">an object representing text layout clients text source for TextFormatter.</param> /// <param name="firstCharIndex">character index to specify where in the source text the line starts</param> /// <param name="paragraphWidth">width of paragraph in which the line fills</param> /// <param name="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param> /// <param name="previousLineBreak">text formatting state at the point where the previous line in the paragraph /// was broken by the text formatting process, as specified by the TextLine.LineBreak property for the previous /// line; this parameter can be null, and will always be null for the first line in a paragraph.</param> /// <param name="textRunCache">an object representing content cache of the client.</param> /// <returns>object representing a line of text that client interacts with. </returns> public abstract TextLine FormatLine( TextSource textSource, int firstCharIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache );
internal static IList<TextBreakpoint> CreateMultiple( TextParagraphCache paragraphCache, int firstCharIndex, int maxLineWidth, TextLineBreak previousLineBreak, IntPtr penaltyRestriction, out int bestFitIndex ) { Invariant.Assert(paragraphCache != null); // grab full text state from paragraph cache FullTextState fullText = paragraphCache.FullText; Invariant.Assert(fullText != null); FormatSettings settings = fullText.TextStore.Settings; Invariant.Assert(settings != null); // update formatting parameters at line start settings.UpdateSettingsForCurrentLine( maxLineWidth, previousLineBreak, (firstCharIndex == fullText.TextStore.CpFirst) ); Invariant.Assert(settings.Formatter != null); // acquiring LS context TextFormatterContext context = settings.Formatter.AcquireContext(fullText, IntPtr.Zero); IntPtr previousBreakRecord = IntPtr.Zero; if (settings.PreviousLineBreak != null) previousBreakRecord = settings.PreviousLineBreak.BreakRecord.Value; // need not consider marker as tab since marker does not affect line metrics and it wasnt drawn. fullText.SetTabs(context); LsBreaks lsbreaks = new LsBreaks(); LsErr lserr = context.CreateBreaks( fullText.GetBreakpointInternalCp(firstCharIndex), previousBreakRecord, paragraphCache.Ploparabreak.Value, // para breaking session penaltyRestriction, ref lsbreaks, out bestFitIndex ); // get the exception in context before it is released Exception callbackException = context.CallbackException; // release the context context.Release(); if(lserr != LsErr.None) { if(callbackException != null) { // rethrow exception thrown in callbacks throw callbackException; } else { // throw with LS error codes TextFormatterContext.ThrowExceptionFromLsError(SR.Get(SRID.CreateBreaksFailure, lserr), lserr); } } // keep context alive at least till here GC.KeepAlive(context); TextBreakpoint[] breakpoints = new TextBreakpoint[lsbreaks.cBreaks]; for (int i = 0; i < lsbreaks.cBreaks; i++) { breakpoints[i] = new FullTextBreakpoint( fullText, firstCharIndex, maxLineWidth, ref lsbreaks, i // the current break ); } return breakpoints; }
/// <summary> /// Update formatting parameters at line start /// </summary> internal void UpdateSettingsForCurrentLine( int maxLineWidth, TextLineBreak previousLineBreak, bool isFirstLineInPara ) { _previousLineBreak = previousLineBreak; _digitState = new DigitState(); // reset digit state _maxLineWidth = Math.Max(maxLineWidth, 0); if (isFirstLineInPara) { _textIndent = _pap.Indent; } else { // Do not apply text indentation to all but the first line in para _textIndent = 0; } }
public TextLine FormatLine(TextSource textSource, int firstCharIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak) { return formatter.FormatLine(textSource, firstCharIndex, paragraphWidth, paragraphProperties, previousLineBreak); }
// ------------------------------------------------------------------ // Create and format text line. // // lineStartIndex - index of the first character in the line // width - wrapping width of the line // lineProperties - properties of the line // textRunCache - run cache used by text formatter // showParagraphEllipsis - true if paragraph ellipsis is shown // at the end of the line // ------------------------------------------------------------------ internal void Format(int dcp, double width, TextParagraphProperties lineProperties, TextLineBreak textLineBreak, TextRunCache textRunCache, bool showParagraphEllipsis) { #if TEXTPANELLAYOUTDEBUG TextPanelDebug.IncrementCounter("Line.Format", TextPanelDebug.Category.TextView); #endif _mirror = (lineProperties.FlowDirection == FlowDirection.RightToLeft); _dcp = dcp; _showParagraphEllipsis = showParagraphEllipsis; _wrappingWidth = width; _line = _owner.TextFormatter.FormatLine(this, dcp, width, lineProperties, textLineBreak, textRunCache); }
internal LineEnumerator(FormattedText text) { _previousHeight = 0; _previousLength = 0; _previousLineBreak = null; _textStorePosition = 0; _lineCount = 0; _totalHeight = 0; _currentLine = null; _nextLine = null; _formatter = TextFormatter.FromCurrentDispatcher(text._textFormattingMode); _that = text; if (_that._textSourceImpl == null) _that._textSourceImpl = new TextSourceImplementation(_that); }
internal override TextParagraphCache CreateParagraphCache( #endif TextSource textSource, int firstCharIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache ) { // prepare formatting settings FormatSettings settings = PrepareFormatSettings( textSource, firstCharIndex, paragraphWidth, paragraphProperties, previousLineBreak, textRunCache, true, // optimalBreak false, // !isSingleLineFormatting _textFormattingMode ); // // Optimal paragraph formatting session specific check // if (!settings.Pap.Wrap && settings.Pap.OptimalBreak) { // Optimal paragraph must wrap. throw new ArgumentException(SR.Get(SRID.OptimalParagraphMustWrap)); } // create paragraph content cache object return new TextParagraphCache( settings, firstCharIndex, RealToIdeal(paragraphWidth) ); }
/// <summary> /// Format and produce a text line either with or without previously known /// line break point. /// </summary> private TextLine FormatLineInternal( TextSource textSource, int firstCharIndex, int lineLength, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache ) { EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordText, EventTrace.Level.Verbose, EventTrace.Event.WClientStringBegin, "TextFormatterImp.FormatLineInternal Start"); // prepare formatting settings FormatSettings settings = PrepareFormatSettings( textSource, firstCharIndex, paragraphWidth, paragraphProperties, previousLineBreak, textRunCache, (lineLength != 0), // Do optimal break if break is given true, // isSingleLineFormatting _textFormattingMode ); TextLine textLine = null; if ( !settings.Pap.AlwaysCollapsible && previousLineBreak == null && lineLength <= 0 ) { // simple text line. textLine = SimpleTextLine.Create( settings, firstCharIndex, RealToIdealFloor(paragraphWidth) ) as TextLine; } if (textLine == null) { // content is complex, creating complex line textLine = new TextMetrics.FullTextLine( settings, firstCharIndex, lineLength, RealToIdealFloor(paragraphWidth), LineFlags.None ) as TextLine; } EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordText, EventTrace.Level.Verbose, EventTrace.Event.WClientStringEnd, "TextFormatterImp.FormatLineInternal End"); return textLine; }
internal override TextLine RecreateLine( #endif TextSource textSource, int firstCharIndex, int lineLength, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache ) { return FormatLineInternal( textSource, firstCharIndex, lineLength, paragraphWidth, paragraphProperties, previousLineBreak, textRunCache ); }
/// <summary> /// Client to format a text line that fills a paragraph in the document. /// </summary> /// <param name="textSource">an object representing text layout clients text source for TextFormatter.</param> /// <param name="firstCharIndex">character index to specify where in the source text the line starts</param> /// <param name="paragraphWidth">width of paragraph in which the line fills</param> /// <param name="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param> /// <param name="previousLineBreak">LineBreak property of the previous text line, or null if this is the first line in the paragraph</param> /// <param name="textRunCache">an object representing content cache of the client.</param> /// <returns>object representing a line of text that client interacts with. </returns> public override TextLine FormatLine( TextSource textSource, int firstCharIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache ) { return FormatLineInternal( textSource, firstCharIndex, 0, // lineLength paragraphWidth, paragraphProperties, previousLineBreak, textRunCache ); }
/// <summary> /// Client to format a text line that fills a paragraph in the document. /// </summary> /// <param name="textSource">an object representing text layout clients text source for TextFormatter.</param> /// <param name="firstCharIndex">character index to specify where in the source text the line starts</param> /// <param name="paragraphWidth">width of paragraph in which the line fills</param> /// <param name="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param> /// <param name="previousLineBreak">LineBreak property of the previous text line, or null if this is the first line in the paragraph</param> /// <returns>object representing a line of text that client interacts with. </returns> public override TextLine FormatLine( TextSource textSource, int firstCharIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak ) { return FormatLineInternal( textSource, firstCharIndex, 0, // lineLength paragraphWidth, paragraphProperties, previousLineBreak, new TextRunCache() // local cache, only live within this call ); }
/// <summary> /// Client to format all feasible breakpoints for a line started at the specified character position. /// The number of breakpoints returned is restricted by penalty restr /// </summary> /// <remarks> /// This method is provided for direct access of PTS during optimal paragraph process. The breakpoint /// restriction handle is passed as part of the parameter to PTS callback pfnFormatLineVariants. The /// value comes from earlier steps in PTS code to compute the accumulated penalty of the entire paragraph /// using 'best-fit' algorithm. /// </remarks> internal IList<TextBreakpoint> FormatBreakpoints( int firstCharIndex, TextLineBreak previousLineBreak, IntPtr breakpointRestrictionHandle, double maxLineWidth, out int bestFitIndex ) { // format all potential breakpoints starting from the specified firstCharIndex. // The number of breakpoints returned is restricted by penaltyRestriction. return FullTextBreakpoint.CreateMultiple( this, firstCharIndex, VerifyMaxLineWidth(maxLineWidth), previousLineBreak, breakpointRestrictionHandle, out bestFitIndex ); }
[FriendAccessAllowed] // used by Framework internal abstract TextParagraphCache CreateParagraphCache( #endif TextSource textSource, int firstCharIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache );
/// <summary> /// Wrapper of TextFormatter.FormatLine that auto-collapses the line if needed. /// </summary> private TextLine FormatLine(TextSource textSource, int textSourcePosition, double maxLineLength, TextParagraphProperties paraProps, TextLineBreak lineBreak) { TextLine line = _formatter.FormatLine( textSource, textSourcePosition, maxLineLength, paraProps, lineBreak ); if (_that._trimming != TextTrimming.None && line.HasOverflowed && line.Length > 0) { // what I really need here is the last displayed text run of the line // textSourcePosition + line.Length - 1 works except the end of paragraph case, // where line length includes the fake paragraph break run Debug.Assert(_that._text.Length > 0 && textSourcePosition + line.Length <= _that._text.Length + 1); SpanRider thatFormatRider = new SpanRider( _that._formatRuns, _that._latestPosition, Math.Min(textSourcePosition + line.Length - 1, _that._text.Length - 1) ); GenericTextRunProperties lastRunProps = thatFormatRider.CurrentElement as GenericTextRunProperties; TextCollapsingProperties trailingEllipsis; if (_that._trimming == TextTrimming.CharacterEllipsis) trailingEllipsis = new TextTrailingCharacterEllipsis(maxLineLength, lastRunProps); else { Debug.Assert(_that._trimming == TextTrimming.WordEllipsis); trailingEllipsis = new TextTrailingWordEllipsis(maxLineLength, lastRunProps); } TextLine collapsedLine = line.Collapse(trailingEllipsis); if (collapsedLine != line) { line.Dispose(); line = collapsedLine; } } return line; }
/// <summary> /// Validate all the relevant text formatting initial settings and package them /// </summary> private FormatSettings PrepareFormatSettings( TextSource textSource, int firstCharIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache, bool useOptimalBreak, bool isSingleLineFormatting, TextFormattingMode textFormattingMode ) { VerifyTextFormattingArguments( textSource, firstCharIndex, paragraphWidth, paragraphProperties, textRunCache ); if (textRunCache.Imp == null) { // No run cache object available, create one textRunCache.Imp = new TextRunCacheImp(); } // initialize formatting settings return new FormatSettings( this, textSource, textRunCache.Imp, new ParaProp(this, paragraphProperties, useOptimalBreak), previousLineBreak, isSingleLineFormatting, textFormattingMode, false ); }
/// <summary> /// Advances the enumerator to the next text line of the collection /// </summary> /// <returns>true if the enumerator was successfully advanced to the next element; /// false if the enumerator has passed the end of the collection</returns> public bool MoveNext() { if (_currentLine == null) { // this is the first line if (_that._text.Length == 0) return false; _currentLine = FormatLine( _that._textSourceImpl, _textStorePosition, MaxLineLength(_lineCount), _that._defaultParaProps, null // no previous line break ); // check if this line fits the text height if (_totalHeight + _currentLine.Height > _that._maxTextHeight) { _currentLine.Dispose(); _currentLine = null; return false; } Debug.Assert(_nextLine == null); } else { // there is no next line or it didn't fit // either way we're finished if (_nextLine == null) return false; _totalHeight += _previousHeight; _textStorePosition += _previousLength; ++_lineCount; _currentLine = _nextLine; _nextLine = null; } TextLineBreak currentLineBreak = _currentLine.GetTextLineBreak(); // this line is guaranteed to fit the text height Debug.Assert(_totalHeight + _currentLine.Height <= _that._maxTextHeight); // now, check if the next line fits, we need to do this on this iteration // because we might need to add ellipsis to the current line // as a result of the next line measurement // maybe there is no next line at all if (_textStorePosition + _currentLine.Length < _that._text.Length) { bool nextLineFits; if (_lineCount + 1 >= _that._maxLineCount) nextLineFits = false; else { _nextLine = FormatLine( _that._textSourceImpl, _textStorePosition + _currentLine.Length, MaxLineLength(_lineCount + 1), _that._defaultParaProps, currentLineBreak ); nextLineFits = (_totalHeight + _currentLine.Height + _nextLine.Height <= _that._maxTextHeight); } if (!nextLineFits) { // next line doesn't fit if (_nextLine != null) { _nextLine.Dispose(); _nextLine = null; } if (_that._trimming != TextTrimming.None && !_currentLine.HasCollapsed) { // recreate the current line with ellipsis added // Note: Paragraph ellipsis is not supported today. We'll workaround // it here by faking a non-wrap text on finite column width. TextWrapping currentWrap = _that._defaultParaProps.TextWrapping; _that._defaultParaProps.SetTextWrapping(TextWrapping.NoWrap); if (currentLineBreak != null) currentLineBreak.Dispose(); _currentLine.Dispose(); _currentLine = FormatLine( _that._textSourceImpl, _textStorePosition, MaxLineLength(_lineCount), _that._defaultParaProps, _previousLineBreak ); currentLineBreak = _currentLine.GetTextLineBreak(); _that._defaultParaProps.SetTextWrapping(currentWrap); } } } _previousHeight = _currentLine.Height; _previousLength = _currentLine.Length; if (_previousLineBreak != null) _previousLineBreak.Dispose(); _previousLineBreak = currentLineBreak; return true; }
// ----------------------------------------------------------------- // // Internal Methods // // ------------------------------------------------------------------ #region Internal Methods /// <summary> /// Create and format text line. /// </summary> /// <param name="ctx"> /// Line formatting context. /// </param> /// <param name="dcp"> /// Character position where the line starts. /// </param> /// <param name="width"> /// Requested width of the line. /// </param> /// <param name="trackWidth"> /// Requested width of track. /// </param> /// <param name="lineProps"> /// Line properties. /// </param> /// <param name="textLineBreak"> /// Line break object. /// </param> internal void Format(FormattingContext ctx, int dcp, int width, int trackWidth, TextParagraphProperties lineProps, TextLineBreak textLineBreak) { // Set formatting context _formattingContext = ctx; _dcp = dcp; _host.Context = this; _wrappingWidth = TextDpi.FromTextDpi(width); _trackWidth = TextDpi.FromTextDpi(trackWidth); _mirror = (lineProps.FlowDirection == FlowDirection.RightToLeft); _indent = lineProps.Indent; try { // Create line object if(ctx.LineFormatLengthTarget == -1) { _line = _host.TextFormatter.FormatLine(_host, dcp, _wrappingWidth, lineProps, textLineBreak, ctx.TextRunCache); } else { _line = _host.TextFormatter.RecreateLine(_host, dcp, ctx.LineFormatLengthTarget, _wrappingWidth, lineProps, textLineBreak, ctx.TextRunCache); } _runs = _line.GetTextRunSpans(); Invariant.Assert(_runs != null, "Cannot retrieve runs collection."); // Submit inline objects (only in measure mode) if (_formattingContext.MeasureMode) { List<InlineObject> inlineObjects = new List<InlineObject>(1); int dcpRun = _dcp; // Enumerate through all runs in the current line and retrieve // all inline objects. // If there are any figures / floaters, store this information for later use. foreach (TextSpan<TextRun> textSpan in _runs) { TextRun run = (TextRun)textSpan.Value; if (run is InlineObjectRun) { inlineObjects.Add(new InlineObject(dcpRun, ((InlineObjectRun)run).UIElementIsland, (TextParagraph)_paraClient.Paragraph)); } else if (run is FloatingRun) { if (((FloatingRun)run).Figure) { _hasFigures = true; } else { _hasFloaters = true; } } // Do not use TextRun.Length, because it gives total length of the run. // So, if the run is broken between lines, it gives incorrect value. // Use length of the TextSpan instead, which gives the correct length here. dcpRun += textSpan.Length; } // Submit inline objects to the paragraph cache if (inlineObjects.Count == 0) { inlineObjects = null; } TextParagraph.SubmitInlineObjects(dcp, dcp + ActualLength, inlineObjects); } } finally { // Clear formatting context _host.Context = null; } }
// ------------------------------------------------------------------ // Constructor. // // PtsContext - Context // TextLineBreak - Contained line break // ------------------------------------------------------------------ internal LineBreakRecord(PtsContext ptsContext, TextLineBreak textLineBreak) : base(ptsContext) { _textLineBreak = textLineBreak; }
public TextLine FormatLine(TextSource textSource, int firstCharIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak) { var runs = new List<Tuple<TextRun, GlyphRun, int, double>>(); int index = firstCharIndex; double x = paragraphProperties.Indent, height = 0, baseline = 0; double trailWhitespaceWidth = 0; while (true) { var run = textSource.GetTextRun(index); var textProps = run.Properties ?? paragraphProperties.DefaultTextRunProperties; var fontSize = textProps.FontRenderingEmSize; var len = run.Length; if (textProps != null) { height = Math.Max(height, (int)(textProps.Typeface.FontFamily.LineSpacing * fontSize)); baseline = Math.Max(baseline, (int)(textProps.Typeface.FontFamily.Baseline * fontSize)); } if (run is TextEndOfLine || run == null) { index += len; runs.Add(Tuple.Create(run, (GlyphRun)null, 0, 0.0)); break; } else if (run is TextCharacters) { var chrs = (TextCharacters)run; var charBuf = getCharBuf(chrs.CharacterBufferReference); var charOffset = getCharOffset(chrs.CharacterBufferReference); GlyphTypeface gl; if (!textProps.Typeface.TryGetGlyphTypeface(out gl)) throw new Exception("GlyphTypeface does not exists for font '" + textProps.Typeface.FontFamily + "'."); ushort[] glyphIndexes = new ushort[len]; double[] advanceWidths = new double[len]; double totalWidth = 0; int trailWhitespace = 0; trailWhitespaceWidth = 0; for (int n = 0; n < len; n++) { var c = charBuf[charOffset + n]; ushort glyphIndex; double width; if (c == '\t') { glyphIndex = gl.CharacterToGlyphMap[' ']; width = paragraphProperties.DefaultIncrementalTab - x % paragraphProperties.DefaultIncrementalTab; } else { if (!gl.CharacterToGlyphMap.TryGetValue(c, out glyphIndex)) glyphIndex = gl.CharacterToGlyphMap['?']; width = gl.AdvanceWidths[glyphIndex] * fontSize; } glyphIndexes[n] = glyphIndex; advanceWidths[n] = width; if (char.IsWhiteSpace(c)) { trailWhitespace++; trailWhitespaceWidth += width; } else { totalWidth += trailWhitespaceWidth + width; trailWhitespaceWidth = 0; trailWhitespace = 0; } } var origin = new Point(x, 0); var glyphRun = new GlyphRun( gl, 0, false, fontSize, glyphIndexes, origin, advanceWidths, null, null, null, null, null, null); runs.Add(Tuple.Create(run, glyphRun, trailWhitespace, trailWhitespaceWidth)); x += totalWidth + trailWhitespaceWidth; index += len; } else if (run is TextEmbeddedObject) { var obj = (TextEmbeddedObject)run; var metrics = obj.Format(paragraphWidth - x); runs.Add(Tuple.Create(run, (GlyphRun)null, 0, metrics.Width)); height = Math.Max(height, obj.Format(paragraphWidth - x).Height); x += metrics.Width; index += len; } } return new GlyphRunLine { entries = runs.ToArray(), baseline = baseline, width = x - trailWhitespaceWidth, height = height, mode = mode }; }