internal TextParagraphCache( FormatSettings settings, int firstCharIndex, int paragraphWidth ) { Invariant.Assert(settings != null); // create full text _finiteFormatWidth = settings.GetFiniteFormatWidth(paragraphWidth); _fullText = FullTextState.Create(settings, firstCharIndex, _finiteFormatWidth); // acquiring LS context TextFormatterContext context = settings.Formatter.AcquireContext(_fullText, IntPtr.Zero); _fullText.SetTabs(context); IntPtr ploparabreakValue = IntPtr.Zero; LsErr lserr = context.CreateParaBreakingSession( firstCharIndex, _finiteFormatWidth, // breakrec is not needed before the first cp of para cache // since we handle Bidi break ourselves. IntPtr.Zero, ref ploparabreakValue, ref _penalizedAsJustified ); // get the exception in context before it is released Exception callbackException = context.CallbackException; // release the context context.Release(); if(lserr != LsErr.None) { GC.SuppressFinalize(this); if(callbackException != null) { // rethrow exception thrown in callbacks throw new InvalidOperationException(SR.Get(SRID.CreateParaBreakingSessionFailure, lserr), callbackException); } else { // throw with LS error codes TextFormatterContext.ThrowExceptionFromLsError(SR.Get(SRID.CreateParaBreakingSessionFailure, lserr), lserr); } } _ploparabreak.Value = ploparabreakValue; // keep context alive till here GC.KeepAlive(context); }
/// <summary> /// Set tab stops /// </summary> internal void SetTabs(TextFormatterContext context) { unsafe { ParaProp pap = _store.Pap; FormatSettings settings = _store.Settings; // set up appropriate tab stops int incrementalTab = TextFormatterImp.RealToIdeal(pap.DefaultIncrementalTab); int lsTbdCount = pap.Tabs != null ? pap.Tabs.Count : 0; LsTbd[] lsTbds; if (_markerStore != null) { if (pap.Tabs != null && pap.Tabs.Count > 0) { lsTbdCount = pap.Tabs.Count + 1; lsTbds = new LsTbd[lsTbdCount]; lsTbds[0].ur = settings.TextIndent; // marker requires a tab stop at text start position fixed(LsTbd *plsTbds = &lsTbds[1]) { CreateLsTbds(pap, plsTbds, lsTbdCount - 1); context.SetTabs(incrementalTab, plsTbds - 1, lsTbdCount); } } else { LsTbd markerRequiredLsTbd = new LsTbd(); markerRequiredLsTbd.ur = settings.TextIndent; // marker requires a tab stop at text start position context.SetTabs(incrementalTab, &markerRequiredLsTbd, 1); } } else { if (pap.Tabs != null && pap.Tabs.Count > 0) { lsTbds = new LsTbd[lsTbdCount]; fixed(LsTbd *plsTbds = &lsTbds[0]) { CreateLsTbds(pap, plsTbds, lsTbdCount); context.SetTabs(incrementalTab, plsTbds, lsTbdCount); } } else { // work with only incremental tab context.SetTabs(incrementalTab, null, 0); } } } }
/// <summary> /// Create a fulltext state object from formatting info for subsequent fulltext formatting /// e.g. fulltext line, mix/max computation, potential breakpoints for optimal paragraph formatting. /// </summary> internal static FullTextState Create( FormatSettings settings, int cpFirst, int finiteFormatWidth ) { // prepare text stores TextStore store = new TextStore( settings, cpFirst, 0, settings.GetFormatWidth(finiteFormatWidth) ); ParaProp pap = settings.Pap; TextStore markerStore = null; if (pap.FirstLineInParagraph && pap.TextMarkerProperties != null && pap.TextMarkerProperties.TextSource != null) { // create text store specifically for marker markerStore = new TextStore( // create specialized settings for marker store e.g. with marker text source new FormatSettings( settings.Formatter, pap.TextMarkerProperties.TextSource, new TextRunCacheImp(), // no cross-call run cache available for marker store pap, // marker by default use the same paragraph properties null, // not consider previousLineBreak true, // isSingleLineFormatting settings.TextFormattingMode, settings.IsSideways ), 0, // marker store always started with cp == 0 TextStore.LscpFirstMarker, // first lscp value for marker text Constants.IdealInfiniteWidth // formatWidth ); } // construct a new fulltext state object return(new FullTextState(store, markerStore, settings.IsSideways)); }
/// <summary> /// Create a fulltext state object from formatting info for subsequent fulltext formatting /// e.g. fulltext line, mix/max computation, potential breakpoints for optimal paragraph formatting. /// </summary> internal static FullTextState Create( FormatSettings settings, int cpFirst, int finiteFormatWidth ) { // prepare text stores TextStore store = new TextStore( settings, cpFirst, 0, settings.GetFormatWidth(finiteFormatWidth) ); ParaProp pap = settings.Pap; TextStore markerStore = null; if( pap.FirstLineInParagraph && pap.TextMarkerProperties != null && pap.TextMarkerProperties.TextSource != null) { // create text store specifically for marker markerStore = new TextStore( // create specialized settings for marker store e.g. with marker text source new FormatSettings( settings.Formatter, pap.TextMarkerProperties.TextSource, new TextRunCacheImp(), // no cross-call run cache available for marker store pap, // marker by default use the same paragraph properties null, // not consider previousLineBreak true, // isSingleLineFormatting settings.TextFormattingMode, settings.IsSideways ), 0, // marker store always started with cp == 0 TextStore.LscpFirstMarker, // first lscp value for marker text Constants.IdealInfiniteWidth // formatWidth ); } // construct a new fulltext state object return new FullTextState(store, markerStore, settings.IsSideways); }
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> /// Client to ask for the possible smallest and largest paragraph width that can fully contain the passing text content /// </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="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param> /// <param name="textRunCache">an object representing content cache of the client.</param> /// <returns>min max paragraph width</returns> public override MinMaxParagraphWidth FormatMinMaxParagraphWidth( TextSource textSource, int firstCharIndex, TextParagraphProperties paragraphProperties, TextRunCache textRunCache ) { // prepare formatting settings FormatSettings settings = PrepareFormatSettings( textSource, firstCharIndex, 0, // infinite paragraphWidth paragraphProperties, null, // always format the whole paragraph - no previousLineBreak textRunCache, false, // optimalBreak true, // isSingleLineFormatting _textFormattingMode ); // create specialized line specifically for min/max calculation TextMetrics.FullTextLine line = new TextMetrics.FullTextLine( settings, firstCharIndex, 0, // lineLength 0, // paragraph width has no significant meaning in min/max calculation (LineFlags.KeepState | LineFlags.MinMax) ); // line width in this case is the width of a line when the entire paragraph is laid out // as a single long line. MinMaxParagraphWidth minMax = new MinMaxParagraphWidth(line.MinWidth, line.Width); line.Dispose(); return(minMax); }
/// <summary> /// Fetch cached textrun /// </summary> internal TextRun FetchTextRun( FormatSettings settings, int cpFetch, int cpFirst, out int offsetToFirstCp, out int runLength ) { SpanRider textRunSpanRider = new SpanRider(_textRunVector, _latestPosition, cpFetch); _latestPosition = textRunSpanRider.SpanPosition; TextRun textRun = (TextRun)textRunSpanRider.CurrentElement; if(textRun == null) { // run not already cached, fetch new run and cache it textRun = settings.TextSource.GetTextRun(cpFetch); if (textRun.Length < 1) { throw new ArgumentOutOfRangeException("textRun.Length", SR.Get(SRID.ParameterMustBeGreaterThanZero)); } Plsrun plsrun = TextRunInfo.GetRunType(textRun); if (plsrun == Plsrun.Text || plsrun == Plsrun.InlineObject) { TextRunProperties properties = textRun.Properties; if (properties == null) throw new ArgumentException(SR.Get(SRID.TextRunPropertiesCannotBeNull)); if (properties.FontRenderingEmSize <= 0) throw new ArgumentException(SR.Get(SRID.PropertyOfClassMustBeGreaterThanZero, "FontRenderingEmSize", "TextRunProperties")); double realMaxFontRenderingEmSize = Constants.RealInfiniteWidth / Constants.GreatestMutiplierOfEm; if (properties.FontRenderingEmSize > realMaxFontRenderingEmSize) throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeGreaterThan, "FontRenderingEmSize", "TextRunProperties", realMaxFontRenderingEmSize)); CultureInfo culture = CultureMapper.GetSpecificCulture(properties.CultureInfo); if (culture == null) throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeNull, "CultureInfo", "TextRunProperties")); if (properties.Typeface == null) throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeNull, "Typeface", "TextRunProperties")); } // // TextRun is specifial to SpanVector because TextRun also encodes position which needs to be // consistent with the positions encoded by SpanVector. In run cache, the begining of a span // should always correspond to the begining of a cached text run. If the end of the currently fetched // run overlaps with the begining of an already cached run, the begining of the cached run needs to be // adjusted as well as its span. Because we can't gurantee the correctness of the overlapped range // so we'll simply remove the overlapped runs here. // // Move the rider to the end of the current run textRunSpanRider.At(cpFetch + textRun.Length - 1); _latestPosition = textRunSpanRider.SpanPosition; if (textRunSpanRider.CurrentElement != _textRunVector.Default) { // The end overlaps with one or more cached runs, clear the range from the // begining of the current fetched run to the end of the last overlapped cached run. _latestPosition = _textRunVector.SetReference( cpFetch, textRunSpanRider.CurrentPosition + textRunSpanRider.Length - cpFetch, _textRunVector.Default, _latestPosition ); } _latestPosition = _textRunVector.SetReference(cpFetch, textRun.Length, textRun, _latestPosition); // Refresh the rider's SpanPosition following previous SpanVector.SetReference calls textRunSpanRider.At(_latestPosition, cpFetch); } offsetToFirstCp = textRunSpanRider.CurrentPosition - textRunSpanRider.CurrentSpanStart; runLength = textRunSpanRider.Length; Debug.Assert(textRun != null && runLength > 0, "Invalid run!"); bool isText = textRun is ITextSymbols; if (isText) { // Chop text run to optimal length so we dont spend forever analysing // them all at once. int looseCharLength = TextStore.TypicalCharactersPerLine - cpFetch + cpFirst; if(looseCharLength <= 0) { // this line already exceeds typical line length, incremental fetch goes // about a quarter of the typical length. looseCharLength = (int)Math.Round(TextStore.TypicalCharactersPerLine * 0.25); } if(runLength > looseCharLength) { if (TextRunInfo.GetRunType(textRun) == Plsrun.Text) { // // When chopping the run at the typical line length, // - don't chop in between of higher & lower surrogate // - don't chop combining mark away from its base character // - don't chop joiner from surrounding characters // // Starting from the initial chopping point, we look ahead to find a safe position. We stop at // a limit in case the run consists of many combining mark & joiner. That is rare and doesn't make // much sense in shaping already. // CharacterBufferReference charBufferRef = textRun.CharacterBufferReference; // We look ahead by one more line at most. It is not normal to have // so many combining mark or joiner characters in a row. It doesn't make sense to // look further if so. int lookAheadLimit = Math.Min(runLength, looseCharLength + TextStore.TypicalCharactersPerLine); int sizeOfChar = 0; int endOffset = 0; bool canBreakAfterPrecedingChar = false; for (endOffset = looseCharLength - 1; endOffset < lookAheadLimit; endOffset += sizeOfChar) { CharacterBufferRange charString = new CharacterBufferRange( charBufferRef.CharacterBuffer, charBufferRef.OffsetToFirstChar + offsetToFirstCp + endOffset, runLength - endOffset ); int ch = Classification.UnicodeScalar(charString, out sizeOfChar); // We can only safely break if the preceding char is not a joiner character (i.e. can-break-after), // and the current char is not combining or joiner (i.e. can-break-before). if (canBreakAfterPrecedingChar && !Classification.IsCombining(ch) && !Classification.IsJoiner(ch) ) { break; } canBreakAfterPrecedingChar = !Classification.IsJoiner(ch); } looseCharLength = Math.Min(runLength, endOffset); } runLength = looseCharLength; } } Debug.Assert( // valid run found runLength > 0 // non-text run always fetched at run start && ( isText || textRunSpanRider.CurrentSpanStart - textRunSpanRider.CurrentPosition == 0) // span rider of both text and format point to valid position && (textRunSpanRider.Length > 0 && textRunSpanRider.CurrentElement != null), "Text run fetching error!" ); return textRun; }
/// <summary> /// Constructing a FullTextLine /// </summary> /// <param name="settings">text formatting settings</param> /// <param name="cpFirst">Line's first cp</param> /// <param name="lineLength">character length of the line</param> /// <param name="paragraphWidth">paragraph width</param> /// <param name="lineFlags">line formatting control flags</param> internal FullTextLine( FormatSettings settings, int cpFirst, int lineLength, int paragraphWidth, LineFlags lineFlags ) : this(settings.TextFormattingMode, settings.Pap.Justify) { if ( (lineFlags & LineFlags.KeepState) != 0 || settings.Pap.AlwaysCollapsible) { _statusFlags |= StatusFlags.KeepState; } int finiteFormatWidth = settings.GetFiniteFormatWidth(paragraphWidth); FullTextState fullText = FullTextState.Create(settings, cpFirst, finiteFormatWidth); // formatting the line FormatLine( fullText, cpFirst, lineLength, fullText.FormatWidth, finiteFormatWidth, paragraphWidth, lineFlags, null // collapsingSymbol ); }
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> /// Fetch cached textrun /// </summary> internal TextRun FetchTextRun( FormatSettings settings, int cpFetch, int cpFirst, out int offsetToFirstCp, out int runLength ) { SpanRider textRunSpanRider = new SpanRider(_textRunVector, _latestPosition, cpFetch); _latestPosition = textRunSpanRider.SpanPosition; TextRun textRun = (TextRun)textRunSpanRider.CurrentElement; if (textRun == null) { // run not already cached, fetch new run and cache it textRun = settings.TextSource.GetTextRun(cpFetch); if (textRun.Length < 1) { throw new ArgumentOutOfRangeException("textRun.Length", SR.Get(SRID.ParameterMustBeGreaterThanZero)); } Plsrun plsrun = TextRunInfo.GetRunType(textRun); if (plsrun == Plsrun.Text || plsrun == Plsrun.InlineObject) { TextRunProperties properties = textRun.Properties; if (properties == null) { throw new ArgumentException(SR.Get(SRID.TextRunPropertiesCannotBeNull)); } if (properties.FontRenderingEmSize <= 0) { throw new ArgumentException(SR.Get(SRID.PropertyOfClassMustBeGreaterThanZero, "FontRenderingEmSize", "TextRunProperties")); } double realMaxFontRenderingEmSize = Constants.RealInfiniteWidth / Constants.GreatestMutiplierOfEm; if (properties.FontRenderingEmSize > realMaxFontRenderingEmSize) { throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeGreaterThan, "FontRenderingEmSize", "TextRunProperties", realMaxFontRenderingEmSize)); } CultureInfo culture = CultureMapper.GetSpecificCulture(properties.CultureInfo); if (culture == null) { throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeNull, "CultureInfo", "TextRunProperties")); } if (properties.Typeface == null) { throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeNull, "Typeface", "TextRunProperties")); } } // // TextRun is specifial to SpanVector because TextRun also encodes position which needs to be // consistent with the positions encoded by SpanVector. In run cache, the begining of a span // should always correspond to the begining of a cached text run. If the end of the currently fetched // run overlaps with the begining of an already cached run, the begining of the cached run needs to be // adjusted as well as its span. Because we can't gurantee the correctness of the overlapped range // so we'll simply remove the overlapped runs here. // // Move the rider to the end of the current run textRunSpanRider.At(cpFetch + textRun.Length - 1); _latestPosition = textRunSpanRider.SpanPosition; if (textRunSpanRider.CurrentElement != _textRunVector.Default) { // The end overlaps with one or more cached runs, clear the range from the // begining of the current fetched run to the end of the last overlapped cached run. _latestPosition = _textRunVector.SetReference( cpFetch, textRunSpanRider.CurrentPosition + textRunSpanRider.Length - cpFetch, _textRunVector.Default, _latestPosition ); } _latestPosition = _textRunVector.SetReference(cpFetch, textRun.Length, textRun, _latestPosition); // Refresh the rider's SpanPosition following previous SpanVector.SetReference calls textRunSpanRider.At(_latestPosition, cpFetch); } // If the TextRun was obtained from the cache, make sure it has the right PixelsPerDip set on its properties. if (textRun.Properties != null) { textRun.Properties.PixelsPerDip = settings.TextSource.PixelsPerDip; } offsetToFirstCp = textRunSpanRider.CurrentPosition - textRunSpanRider.CurrentSpanStart; runLength = textRunSpanRider.Length; Debug.Assert(textRun != null && runLength > 0, "Invalid run!"); bool isText = textRun is ITextSymbols; if (isText) { // Chop text run to optimal length so we dont spend forever analysing // them all at once. int looseCharLength = TextStore.TypicalCharactersPerLine - cpFetch + cpFirst; if (looseCharLength <= 0) { // this line already exceeds typical line length, incremental fetch goes // about a quarter of the typical length. looseCharLength = (int)Math.Round(TextStore.TypicalCharactersPerLine * 0.25); } if (runLength > looseCharLength) { if (TextRunInfo.GetRunType(textRun) == Plsrun.Text) { // // When chopping the run at the typical line length, // - don't chop in between of higher & lower surrogate // - don't chop combining mark away from its base character // - don't chop joiner from surrounding characters // // Starting from the initial chopping point, we look ahead to find a safe position. We stop at // a limit in case the run consists of many combining mark & joiner. That is rare and doesn't make // much sense in shaping already. // CharacterBufferReference charBufferRef = textRun.CharacterBufferReference; // We look ahead by one more line at most. It is not normal to have // so many combining mark or joiner characters in a row. It doesn't make sense to // look further if so. int lookAheadLimit = Math.Min(runLength, looseCharLength + TextStore.TypicalCharactersPerLine); int sizeOfChar = 0; int endOffset = 0; bool canBreakAfterPrecedingChar = false; for (endOffset = looseCharLength - 1; endOffset < lookAheadLimit; endOffset += sizeOfChar) { CharacterBufferRange charString = new CharacterBufferRange( charBufferRef.CharacterBuffer, charBufferRef.OffsetToFirstChar + offsetToFirstCp + endOffset, runLength - endOffset ); int ch = Classification.UnicodeScalar(charString, out sizeOfChar); // We can only safely break if the preceding char is not a joiner character (i.e. can-break-after), // and the current char is not combining or joiner (i.e. can-break-before). if (canBreakAfterPrecedingChar && !Classification.IsCombining(ch) && !Classification.IsJoiner(ch)) { break; } canBreakAfterPrecedingChar = !Classification.IsJoiner(ch); } looseCharLength = Math.Min(runLength, endOffset); } runLength = looseCharLength; } } Debug.Assert( // valid run found runLength > 0 // non-text run always fetched at run start && (isText || textRunSpanRider.CurrentSpanStart - textRunSpanRider.CurrentPosition == 0) // span rider of both text and format point to valid position && (textRunSpanRider.Length > 0 && textRunSpanRider.CurrentElement != null), "Text run fetching error!" ); return(textRun); }
/// <summary> /// Creating a lightweight text line /// </summary> /// <param name="settings">text formatting settings</param> /// <param name="cpFirst">First cp of the line</param> /// <param name="paragraphWidth">paragraph width</param> /// <returns>TextLine instance</returns> /// <remarks> /// This method breaks line using Ideal width such that it will be /// consistent with FullTextLine /// </remarks> static public TextLine Create( FormatSettings settings, int cpFirst, int paragraphWidth ) { ParaProp pap = settings.Pap; if( pap.RightToLeft || pap.Justify || ( pap.FirstLineInParagraph && pap.TextMarkerProperties != null) || settings.TextIndent != 0 || pap.ParagraphIndent != 0 || pap.LineHeight > 0 || pap.AlwaysCollapsible || (pap.TextDecorations != null && pap.TextDecorations.Count != 0) ) { // unsupported paragraph properties return null; } int cp = cpFirst; int nonHiddenLength = 0; // length of non-hidden runs seen so far // paragraphWidth == 0 means the format width is unlimited. int widthLeft = (pap.Wrap && paragraphWidth > 0) ? paragraphWidth : int.MaxValue; int idealRunOffsetUnRounded = 0; SimpleRun prev = null; SimpleRun run = SimpleRun.Create( settings, cp, cpFirst, widthLeft, paragraphWidth, idealRunOffsetUnRounded ); if(run == null) { // fail to create run e.g. complex content encountered return null; } else if(!run.EOT && run.IdealWidth <= widthLeft) { // create next run cp += run.Length; widthLeft -= run.IdealWidth; idealRunOffsetUnRounded += run.IdealWidth; prev = run; run = SimpleRun.Create( settings, cp, cpFirst, widthLeft, paragraphWidth, idealRunOffsetUnRounded ); if(run == null) { return null; } } int trailing = 0; ArrayList runs = new ArrayList(2); if(prev != null) { AddRun(runs, prev, ref nonHiddenLength); } do { if(!run.EOT && run.IdealWidth > widthLeft) { // linebreaking required, even simple text requires classification-based linebreaking, // we'll now let LS handle this line. return null; } AddRun(runs, run, ref nonHiddenLength); // As a security mitigation, we impose a limit on the length of a single line // (see comments for TextStore.MaxCharactersPerLine) - only non-hidden // runs count against this limit. If the line exceeds the limit, // use FullTextLine instead of SimpleTextLine - this assures consistency // in cases such as collapsing a line. if (nonHiddenLength >= TextStore.MaxCharactersPerLine) { return null; } prev = run; cp += run.Length; widthLeft -= run.IdealWidth; idealRunOffsetUnRounded += run.IdealWidth; if(run.EOT) { // we're done break; } run = SimpleRun.Create( settings, cp, cpFirst, widthLeft, paragraphWidth, idealRunOffsetUnRounded ); if( run == null || ( run.Underline != null && prev != null && prev.Underline != null && !prev.IsUnderlineCompatible(run)) ) { // fail to create run or // runs cannot support averaging underline return null; } } while(true); int trailingSpaceWidth = 0; CollectTrailingSpaces( runs, settings.Formatter, ref trailing, ref trailingSpaceWidth ); // create a simple line return new SimpleTextLine( settings, cpFirst, paragraphWidth, runs, ref trailing, ref trailingSpaceWidth ) as TextLine; }
/// <summary> /// Constructing a lightweight text line /// </summary> /// <param name="settings">text formatting settings</param> /// <param name="cpFirst">line first cp</param> /// <param name="paragraphWidth">paragraph width</param> /// <param name="runs">collection of simple runs</param> /// <param name="trailing">line trailing spaces</param> /// <param name="trailingSpaceWidth">line trailing spaces width</param> /// <Remarks> /// SimpleTextLine is constructed with Ideal width such that the line breaking /// behavior is consistent with the FullTextLine /// </Remarks> public SimpleTextLine( FormatSettings settings, int cpFirst, int paragraphWidth, ArrayList runs, ref int trailing, ref int trailingSpaceWidth ) { // Compute line metrics int count = 0; _settings = settings; double realAscent = 0; double realDescent = 0; double realHeight = 0; ParaProp pap = settings.Pap; TextFormatterImp formatter = settings.Formatter; int idealWidth = 0; while(count < runs.Count) { SimpleRun run = (SimpleRun)runs[count]; if(run.Length > 0) { if(run.EOT) { // EOT run has no effect on height, it is part of trailing spaces trailing += run.Length; _cpLengthEOT += run.Length; } else { realHeight = Math.Max(realHeight, run.Height); realAscent = Math.Max(realAscent, run.Baseline); realDescent = Math.Max(realDescent, run.Height - run.Baseline); } _cpLength += run.Length; idealWidth += run.IdealWidth; } count++; } // Roundtrip run baseline and height to take its precision back to the specified formatting resolution. // // We have to do this to guarantee sameness of line alignment metrics produced by fast and full path. // This is critical for TextBlock/TextFlow. They rely on the fact that line created during Measure must // yield the same metrics as one created during Render, while there is no guarantee that the paragraph // properties of that same line remains the same in both timings e.g. Measure may not specify // justification (which results in us formatting the line in fast path), while Render might // (which results in us formatting that same line in full path). _baselineOffset = formatter.IdealToReal(TextFormatterImp.RealToIdeal(realAscent)); if (realAscent + realDescent == realHeight) { _height = formatter.IdealToReal(TextFormatterImp.RealToIdeal(realHeight)); } else { _height = formatter.IdealToReal(TextFormatterImp.RealToIdeal(realAscent) + TextFormatterImp.RealToIdeal(realDescent)); } if(_height <= 0) { // line is empty (containing only EOP) // we need to work out the line height // It needs to be exactly the same as in full path. _height = formatter.IdealToReal((int)Math.Round(pap.DefaultTypeface.LineSpacing(pap.EmSize, Constants.DefaultIdealToReal, Util.PixelsPerDip, _settings.TextFormattingMode))); _baselineOffset = formatter.IdealToReal((int)Math.Round(pap.DefaultTypeface.Baseline(pap.EmSize, Constants.DefaultIdealToReal, Util.PixelsPerDip, _settings.TextFormattingMode))); } // Initialize the array of runs and set the TrimTrailingUnderline flag // for runs that contain trailing spaces at the end of the line. _runs = new SimpleRun[count]; for(int i = count - 1, t = trailing; i >= 0; --i) { SimpleRun run = (SimpleRun)runs[i]; if (t > 0) { run.TrimTrailingUnderline = true; t -= run.Length; } _runs[i] = run; } _cpFirst = cpFirst; _trailing = trailing; int idealWidthAtTrailing = idealWidth - trailingSpaceWidth; if(pap.Align != TextAlignment.Left) { switch(pap.Align) { case TextAlignment.Right: _idealOffsetUnRounded = paragraphWidth - idealWidthAtTrailing; _offset = formatter.IdealToReal(_idealOffsetUnRounded); break; case TextAlignment.Center: // exactly consistent with FullTextLine _idealOffsetUnRounded = (int)Math.Round((paragraphWidth - idealWidthAtTrailing) * 0.5); _offset = formatter.IdealToReal(_idealOffsetUnRounded); break; } } // converting all the ideal values to real values _width = formatter.IdealToReal(idealWidth); _widthAtTrailing = formatter.IdealToReal(idealWidthAtTrailing); _paragraphWidth = formatter.IdealToReal(paragraphWidth); // paragraphWidth == 0 means format width is unlimited and hence not overflowable. // we keep paragraphWidth for alignment calculation if (paragraphWidth > 0 && _widthAtTrailing > _paragraphWidth) { _statusFlags |= StatusFlags.HasOverflowed; } }
/// <summary> /// Returns a simple text run that represents a Tab. /// </summary> /// <param name="settings">text formatting settings</param> /// <param name="textRun">text run</param> /// <param name="idealRunOffsetUnRounded">run's offset from the beginning of the line</param> static private SimpleRun CreateSimpleRunForTab( FormatSettings settings, TextRun textRun, int idealRunOffsetUnRounded ) { if (settings == null || textRun == null || textRun.Properties == null || textRun.Properties.Typeface == null) { return null; } GlyphTypeface glyphTypeface = textRun.Properties.Typeface.TryGetGlyphTypeface(); // Check whether the font has the space character. If not then we have to go through // font fallback. // We are not calling CreateSimpleTextRun() because CheckFastPathNominalGlyphs() // can fail if a font has TypographicAvailabilities. We are simply rendering a space // so we don't realy care about TypographicFeatures. This is a perf optimization. if (glyphTypeface == null || !glyphTypeface.HasCharacter(' ')) { return null; } // The full shaping path converts tabs to spaces. // Note: In order to get exactly the same metrics as we did in FullTextLine (specifically ink bounding box) // we need to "Draw" a space in place of a Tab (previously we were just ignoring the Tab and rendering nothing) // which turned out to give different overhang and extent values than those returned using the full shaping path. // So in order to avoid vertical jiggling when a line is changed from SimpleTextLine to FullTextLine by adding/removing // a complex character, we need to do the same thing as the full shaping path and draw a space for each tab. TextRun modifedTextRun = new TextCharacters(" ", textRun.Properties); CharacterBufferRange characterBufferRange = new CharacterBufferRange(modifedTextRun); SimpleRun run = new SimpleRun(1, modifedTextRun, Flags.Tab, settings.Formatter); run.CharBufferReference = characterBufferRange.CharacterBufferReference; run.TextRun.Properties.Typeface.GetCharacterNominalWidthsAndIdealWidth( characterBufferRange, run.EmSize, TextFormatterImp.ToIdeal, settings.Formatter.TextFormattingMode, false, out run.NominalAdvances ); int idealIncrementalTab = TextFormatterImp.RealToIdeal(settings.Pap.DefaultIncrementalTab); // Here we get the next tab stop without snapping the metrics to pixels. // We do the pixel snapping on the final position of the tab stop (and not on the IncrementalTab) // to achieve the same results as those in full shaping. int idealNextTabStopUnRounded = ((idealRunOffsetUnRounded / idealIncrementalTab) + 1) * idealIncrementalTab; run.IdealWidth = run.NominalAdvances[0] = idealNextTabStopUnRounded - idealRunOffsetUnRounded; return run; }
/// <summary> /// Creating a simple text run /// </summary> /// <param name="settings">text formatting settings</param> /// <param name="charString">character string associated to textrun</param> /// <param name="textRun">text run</param> /// <param name="cp">first cp of the run</param> /// <param name="cpFirst">first cp of the line</param> /// <param name="runLength">run length</param> /// <param name="widthLeft">maximum run width</param> /// <param name="idealRunOffsetUnRounded">run's offset from the beginning of the line</param> /// <returns>a SimpleRun object</returns> static public SimpleRun Create( FormatSettings settings, CharacterBufferRange charString, TextRun textRun, int cp, int cpFirst, int runLength, int widthLeft, int idealRunOffsetUnRounded ) { SimpleRun run = null; if (textRun is TextCharacters) { if ( textRun.Properties.BaselineAlignment != BaselineAlignment.Baseline || (textRun.Properties.TextEffects != null && textRun.Properties.TextEffects.Count != 0) ) { // fast path does not handle the following conditions // o non-default baseline alignment // o text drawing effect ( return null; } TextDecorationCollection textDecorations = textRun.Properties.TextDecorations; if ( textDecorations != null && textDecorations.Count != 0 && !textDecorations.ValueEquals(TextDecorations.Underline)) { // we only support a single underline return null; } settings.DigitState.SetTextRunProperties(textRun.Properties); if (settings.DigitState.RequiresNumberSubstitution) { // don't support number substitution in fast path return null; } bool canProcessTabsInSimpleShapingPath = CanProcessTabsInSimpleShapingPath( settings.Pap, settings.Formatter.TextFormattingMode ); if (charString[0] == TextStore.CharCarriageReturn) { // CR in the middle of text stream treated as explicit paragraph break // simple hard line break runLength = 1; if (charString.Length > 1 && charString[1] == TextStore.CharLineFeed) { runLength = 2; } // This path handles the case where the backing store breaks the text run in between // a Carriage Return and a Line Feed. So we fetch the next run to check whether the next // character is a line feed. else if (charString.Length == 1) { // Prefetch to check for line feed. TextRun newRun; int newRunLength; CharacterBufferRange newBufferRange = settings.FetchTextRun( cp + 1, cpFirst, out newRun, out newRunLength ); if (newBufferRange.Length > 0 && newBufferRange[0] == TextStore.CharLineFeed) { // Merge the 2 runs. int lengthOfRun = 2; char[] characterArray = new char[lengthOfRun]; characterArray[0] = TextStore.CharCarriageReturn; characterArray[1] = TextStore.CharLineFeed; TextRun mergedTextRun = new TextCharacters(characterArray, 0, lengthOfRun, textRun.Properties); return new SimpleRun(lengthOfRun, mergedTextRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } } return new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } else if (charString[0] == TextStore.CharLineFeed) { // LF in the middle of text stream treated as explicit paragraph break // simple hard line break runLength = 1; return new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } else if (canProcessTabsInSimpleShapingPath && charString[0] == TextStore.CharTab) { return CreateSimpleRunForTab(settings, textRun, idealRunOffsetUnRounded); } // attempt to create a simple run for text run = CreateSimpleTextRun( charString, textRun, settings.Formatter, widthLeft, settings.Pap.EmergencyWrap, canProcessTabsInSimpleShapingPath ); if (run == null) { // fail to create simple text run, the run content is too complex return null; } // Check for underline condition if (textDecorations != null && textDecorations.Count == 1 ) { run.Underline = textDecorations[0]; } } else if (textRun is TextEndOfLine) { run = new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } else if (textRun is TextHidden) { // hidden run run = new SimpleRun(runLength, textRun, Flags.Ghost, settings.Formatter); } return run; }
/// <summary> /// Creating a simple text run /// </summary> /// <param name="settings">text formatting settings</param> /// <param name="cp">first cp of the run</param> /// <param name="cpFirst">first cp of the line</param> /// <param name="widthLeft">maxium run width</param> /// <param name="widthMax">maximum column width</param> /// <param name="idealRunOffsetUnRounded">run's offset from the beginning of the line</param> /// <returns>a SimpleRun object</returns> static public SimpleRun Create( FormatSettings settings, int cp, int cpFirst, int widthLeft, int widthMax, int idealRunOffsetUnRounded ) { TextRun textRun; int runLength; CharacterBufferRange charBufferRange = settings.FetchTextRun( cp, cpFirst, out textRun, out runLength ); return Create( settings, charBufferRange, textRun, cp, cpFirst, runLength, widthLeft, idealRunOffsetUnRounded ); }
/// <summary> /// Creating a lightweight text line /// </summary> /// <param name="settings">text formatting settings</param> /// <param name="cpFirst">First cp of the line</param> /// <param name="paragraphWidth">paragraph width</param> /// <returns>TextLine instance</returns> /// <remarks> /// This method breaks line using Ideal width such that it will be /// consistent with FullTextLine /// </remarks> static public TextLine Create( FormatSettings settings, int cpFirst, int paragraphWidth ) { ParaProp pap = settings.Pap; if( pap.RightToLeft || pap.Justify || ( pap.FirstLineInParagraph && pap.TextMarkerProperties != null) || settings.TextIndent != 0 || pap.ParagraphIndent != 0 || pap.LineHeight > 0 || pap.AlwaysCollapsible || (pap.TextDecorations != null && pap.TextDecorations.Count != 0) ) { // unsupported paragraph properties return null; } int cp = cpFirst; // paragraphWidth == 0 means the format width is unlimited. int widthLeft = (pap.Wrap && paragraphWidth > 0) ? paragraphWidth : int.MaxValue; SimpleRun prev = null; SimpleRun run = SimpleRun.Create( settings, cp, cpFirst, widthLeft, paragraphWidth ); if(run == null) { // fail to create run e.g. complex content encountered return null; } else if(!run.EOT && run.IdealWidth <= widthLeft) { // create next run cp += run.Length; widthLeft -= run.IdealWidth; prev = run; run = SimpleRun.Create( settings, cp, cpFirst, widthLeft, paragraphWidth ); if(run == null) { return null; } } int trailing = 0; ArrayList runs = new ArrayList(2); if(prev != null) { AddRun(runs, prev, null); } do { if(!run.EOT && run.IdealWidth > widthLeft) { // linebreaking required, even simple text requires classification-based linebreaking, // we'll now let LS handle this line. return null; } AddRun(runs, run, null); prev = run; cp += run.Length; widthLeft -= run.IdealWidth; if(run.EOT) { // we're done break; } run = SimpleRun.Create( settings, cp, cpFirst, widthLeft, paragraphWidth ); if( run == null || ( run.Underline != null && prev != null && prev.Underline != null && !prev.IsUnderlineCompatible(run)) ) { // fail to create run or // runs cannot support averaging underline return null; } } while(true); int trailingSpaceWidth = 0; CollectTrailingSpaces( runs, settings.Formatter, ref trailing, ref trailingSpaceWidth ); // create a simple line return new SimpleTextLine( settings, cpFirst, paragraphWidth, runs, ref trailing, ref trailingSpaceWidth ) as TextLine; }