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; } }
/// <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> /// Fill a fixed buffer of LsTbd with /// </summary> private unsafe void CreateLsTbds( ParaProp pap, LsTbd *plsTbds, int lsTbdCount ) { for (int i = 0; i < lsTbdCount; i++) { TextTabProperties tab = (TextTabProperties)pap.Tabs[i]; plsTbds[i].lskt = Convert.LsKTabFromTabAlignment(tab.Alignment); plsTbds[i].ur = TextFormatterImp.RealToIdeal(tab.Location); if (tab.TabLeader != 0) { // Note: LS does not currently support surrogate character as tab leader and aligning character plsTbds[i].wchTabLeader = (char)tab.TabLeader; // tab leader requires state at display time for tab leader width fetching _statusFlags |= StatusFlags.KeepState; } plsTbds[i].wchCharTab = (char)tab.AligningCharacter; } }
private unsafe void CreateLsTbds( ParaProp pap, LsTbd* plsTbds, int lsTbdCount ) { for (int i = 0; i < lsTbdCount; i++) { TextTabProperties tab = (TextTabProperties)pap.Tabs[i]; plsTbds[i].lskt = Convert.LsKTabFromTabAlignment(tab.Alignment); plsTbds[i].ur = TextFormatterImp.RealToIdeal(tab.Location); if (tab.TabLeader != 0) { // Note: LS does not currently support surrogate character as tab leader and aligning character plsTbds[i].wchTabLeader = (char)tab.TabLeader; // tab leader requires state at display time for tab leader width fetching _statusFlags |= StatusFlags.KeepState; } plsTbds[i].wchCharTab = (char)tab.AligningCharacter; } }
private double _pixelsPerDip; // PixelsPerDip /// <summary> /// Construct text metrics from full text info /// </summary> /// <remarks> /// /// When the application formats a line of text. It starts from the leading edge of the paragraph - the reference position /// called "Paragraph Start". It gives the width of the paragraph or "Paragraph Width" to TextFormatter as one of the main /// parameters to TextFormatter.FormatLine method. It may also provide additional info about how it wants the line to look /// like. The following are all of such info and how the formatting process is carried on inside TextFormatter. /// /// /// *** Indent/Paragraph Indent *** /// The application may specify "Indent" - the distance from the beginning of the line to the beginning of the text in that /// line. The value is sent to TextFormatter via [TextParagraphProperties.Indent]. It may also specify "Paragraph Indent" /// - the distance from the beginning of the paragraph to the beginning of the line [TextParagraphProperties.ParagraphIndent]. /// The usage of paragraph indent is to offset the beginning of the line relative to the paragraph starting point, while /// indent is to offset the beginning of text realtive to the line starting point. Paragraph indent is not included as part /// of the line width while indent is. /// /// /// *** Text Alignment *** /// "Text Alignment" [TextParagraphProperties.TextAlignment] may be specified to align the leading, center or trailing edge /// of the line to the leading, center or trailing edge of the paragraph excluding paragraph indent. /// /// /// *** Bullet/Auto-numbering *** /// The application may also specify "bullet" (or "marker") for the line. Marker does not affect the layout measurement of the /// line. Line with marker has the same line width with the line that has not. The presence of marker however affects the /// pixel-wise black width of the line. The application specifies the distance from the beginning of the line to the trailing /// edge of the marker symbol via the property [TextMarkerProperties.Offset]. The application can create the visual effect of /// having marker embedded inside the body of paragraph text (so-called "marker inside") by specifying a positive indent so /// that the text starts after the beginning of the line and a positive smaller amount of marker offset to place the marker /// symbol at between the beginning of the line and the beginning of the text. The "marker outside" visual effect can /// also be achieved in a similar manner by specifying zero or positive indent value with negative marker offset value. /// /// /// *** Formatted Line Properties *** /// Once the line formatting process is completed and a line is returned to the application. The application determines the /// distance from the paragraph starting point to the actual beginning of the line by looking at the "Line Start" property of /// the text line [TextLine.Start]. The "Width" of the line can be determined, naturally, from the property [TextLine.Width]. /// The property value [TextLine.OverhangLeading] represents the distance from the beginning of the line, or the line's alignment /// point, to the first leading pixel of that line so-called the "Black Start". The property [TextLine.OverhangTrailing] /// is the distance from the last trailing pixel of the line to the trailing edge alignment point of the line. The application /// uses these "overhang" or "overshoot" values to ensure proper positioning of text that avoids pixel clipping of the /// glyph image. A less sophisticated application may provide reasonable leading and trailing margin around the text line /// and ignores these properties altogether. /// /// /// *** Hit-Testing *** /// The application may also perform hit-testing by calling methods on TextLine. All the distances involved in hit-testing /// operations are distances from the paragraph start, not from the line start. Marker symbol on its own is not hit-testable. /// /// /// *** Tabs *** /// The application may specify tab stops - an array of positions to where text aligns. Each tab stop may have different /// "Tab Alignment". The left, center and right tab alignment aligns the tab stop position to the leading, center and the /// trailing edge of the text following the tab character. "Tab Leader" may also be specified to fill the distance occupied /// by the presence of tab character with the symbol of choice. Tab stops is specified thru the property [TextParagraph.Tabs]. /// In the absence of tab stops, the application may assume an automatic tab stop - so called "Incremental Tab" specified by /// the property [TextParagraphProperties.DefaultIncrementalTab]. The property could be overridden, by default the value /// is set by TextFormatter to 4 em of the paragraph's default font. /// /// /// *** Line Services Properties *** /// TextFormatter relies on LS to calculate the distance from the beginning of the line to the beginning of text or "Text Start" /// and keep it in the private property [this._textStart]. This value is non-zero when 1) the line starts with indentation or /// 2) the line starts with marker - either bullet or auto-numbering symbol. /// /// In case of the line with marker, LS also produces the distance from the beginning of the line to the beginning of the marker /// symbol, but TextFormatter does not retain that distance because marker is outside the line. The application is assumed /// responsibility to make sure the marker symbol is not going to be clipped out. The application achieves that by manipulating /// the indent value along with the marker offset value. /// /// TextFormatter also retains the total "Text Width" value computed by LS in the private property [this._textWidth]. This /// is the distance from the beginning of the text to the end including all trailing whitespaces at the end of the line. The /// similar value but with trailing whitespaces excluded is kept in the private property [this._textWidthAtTrailing]. /// /// TextFormatter starts formatting a LS line by assuming the beginning of the line being at an imaginary origin. It then /// places the starting point of the content depending on whether the line has either marker symbol or indent. The actual /// mechanism for the placement is in FetchLineProps callback where the value [LsLineProps.durLeft] represents the distance /// relative to the line's origin where actual content begins. The distances can either be positive or negative. Negative /// distance runs in the reverse direction from the direction of text flow. When a negative indent or marker offset is /// specified, durLeft is set to negative distance relative to line start. /// /// TextFormatter however does not rely on LS for the whole line's text alignment. It always formats LS as if the line is /// left-aligned. Once the distances of the line are received, it aligns the whole line according to the text alignment setting /// specified by the application, outside the LS call. The result of this aligning process is a distance from the beginning of /// the paragraph to the beginning of text and is kept in a private property [this._paragraphToText]. /// /// </remarks> internal unsafe void Compute( FullTextState fullText, int firstCharIndex, int paragraphWidth, FormattedTextSymbols collapsingSymbol, ref LsLineWidths lineWidths, LsLInfo *plsLineInfo ) { _formatter = fullText.Formatter; TextStore store = fullText.TextStore; _pixelsPerDip = store.Settings.TextSource.PixelsPerDip; // obtain position of important distances _textStart = lineWidths.upStartMainText; _textWidthAtTrailing = lineWidths.upStartTrailing; _textWidth = lineWidths.upLimLine; // append line end collapsing symbol if any if (collapsingSymbol != null) { AppendCollapsingSymbolWidth(TextFormatterImp.RealToIdeal(collapsingSymbol.Width)); } // make all widths relative to text start _textWidth -= _textStart; _textWidthAtTrailing -= _textStart; // keep the newline character count if any _cchNewline = store.CchEol; // count text and dependant characters _lscpLim = plsLineInfo->cpLimToContinue; _lastRun = fullText.CountText(_lscpLim, firstCharIndex, out _cchLength); Debug.Assert(_cchLength > 0); if (plsLineInfo->endr != LsEndRes.endrEndPara && plsLineInfo->endr != LsEndRes.endrSoftCR) { // endrEndPara denotes that the line ends at paragraph end. It is a result of submitting Paragraph Separator to LS. // endrSoftCR denotes end of line but not end of paragraph. This is a result of submitting Line Separator to LS. _cchNewline = 0; if (plsLineInfo->dcpDepend >= 0) { // According to SergeyGe [2/16/2006], dcpDepend reported from LS cannot made precise when considering // the line ending with hyphenation - this is because LS does not have the knowledge about the amount // of text, after the hyphenation point, being examined by its client during the process of finding // the right place to hyphenate. LS client must therefore take into account the number of lookahead // LSCP examined by hyphenator when computing the correct dcpDepend for the line. In our implementation // it would just mean we take the max of the two values. int lscpFirstIndependence = Math.Max( plsLineInfo->cpLimToContinue + plsLineInfo->dcpDepend, fullText.LscpHyphenationLookAhead ); fullText.CountText(lscpFirstIndependence, firstCharIndex, out _cchDepend); _cchDepend -= _cchLength; } } ParaProp pap = store.Pap; if (_height <= 0) { // if height has not been settled, // calculate line height and baseline offset if (pap.LineHeight > 0) { // Host specifies line height, honor it. _height = pap.LineHeight; _baselineOffset = (int)Math.Round( _height * pap.DefaultTypeface.Baseline(pap.EmSize, Constants.DefaultIdealToReal, _pixelsPerDip, fullText.TextFormattingMode) / pap.DefaultTypeface.LineSpacing(pap.EmSize, Constants.DefaultIdealToReal, _pixelsPerDip, fullText.TextFormattingMode) ); } if (plsLineInfo->dvrMultiLineHeight == int.MaxValue) { // Line is empty so text height and text baseline are based on the default typeface; // it doesn't make sense even for an emtpy line to have zero text height _textAscent = (int)Math.Round(pap.DefaultTypeface.Baseline(pap.EmSize, Constants.DefaultIdealToReal, _pixelsPerDip, fullText.TextFormattingMode)); _textHeight = (int)Math.Round(pap.DefaultTypeface.LineSpacing(pap.EmSize, Constants.DefaultIdealToReal, _pixelsPerDip, fullText.TextFormattingMode)); } else { _textAscent = plsLineInfo->dvrAscent; _textHeight = _textAscent + plsLineInfo->dvrDescent; if (fullText.VerticalAdjust) { // Line requires vertical repositioning of text runs store.AdjustRunsVerticalOffset( plsLineInfo->cpLimToContinue - firstCharIndex, _height, _baselineOffset, out _textHeight, out _textAscent ); } } // if the client hasn't specified a line height then the line height and baseline // are the same as the text height and text baseline if (_height <= 0) { _height = _textHeight; _baselineOffset = _textAscent; } } // Text alignment aligns the line to correspondent paragraph alignment start edge switch (pap.Align) { case TextAlignment.Right: // alignment rule: // "The sum of paragraph start to line start and line width is equal to paragraph width" // // PTL + LW = PW // (PTT - LTT) + (LTT + TW) = PW // (thus) PTT = PW - TW _paragraphToText = paragraphWidth - _textWidthAtTrailing; break; case TextAlignment.Center: // alignment rule: // "The sum of paragraph start to line start and half the line width is equal to half the paragraph width" // // PTL + 0.5*LW = 0.5*PW // (PTT - LTT) + 0.5*(LTT + TW) = 0.5*PW // (thus) PTT = 0.5 * (PW + LTT - TW) _paragraphToText = (int)Math.Round((paragraphWidth + _textStart - _textWidthAtTrailing) * 0.5); break; default: // alignment rule: // "Paragraph start to line start is paragraph indent" // // PTL = PI // PTT - LTT = PI // (thus) PTT = PI + LTT _paragraphToText = pap.ParagraphIndent + _textStart; break; } }
/// <summary> /// Returns whether the conditions are met to make it possible to process tabs /// in the simple shaping path. /// </summary> static private bool CanProcessTabsInSimpleShapingPath( ParaProp textParagraphProperties, TextFormattingMode textFormattingMode ) { return (textParagraphProperties.Tabs == null && textParagraphProperties.DefaultIncrementalTab > 0); }