Code:
/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Core / System / Windows / Media / GlyphRun.cs / 2 / GlyphRun.cs
//---------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // Description: The GlyphRun object represents a sequence of glyphs from a single face of a single font at a single size, // and with a single rendering style. // // See specs at // [....]/sites/Avalon/Specs/Glyphs%20element%20and%20GlyphRun%20object.htm // [....]/sites/Avalon/Specs/Glyph%20Run%20hit%20testing%20and%20caret%20placement%20API.htm // // // History: // 02/18/2003 : [....] - Created // //--------------------------------------------------------------------------- // Enable presharp pragma warning suppress directives. #pragma warning disable 1634, 1691 using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Text; using System.Windows; using System.Windows.Media; using System.Windows.Media.Converters; using System.Windows.Media.Composition; using System.Windows.Media.TextFormatting; using System.Windows.Markup; using System.Runtime.InteropServices; using MS.Internal; using MS.Internal.FontCache; using MS.Internal.FontFace; using MS.Internal.TextFormatting; using MS.Utility; using System.Security; using System.Security.Permissions; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; namespace System.Windows.Media { ////// The GlyphRun object represents a sequence of glyphs from a single face of a single font at a single size, /// and with a single rendering style. /// // public class GlyphRun : DUCE.IResource, ISupportInitialize { //----------------------------------------------------- // // Constructors // //----------------------------------------------------- #region Constructors ////// Construct an uninitialized GlyphRun object. Caller should call ISupportInitialize.BeginInit() /// to begin initialization and call ISupportInitialize.EndInit() to finish the initialization. /// The GlyphRun does not support all the operations until it is fully initialized. /// public GlyphRun() { } ////// Constructs a new GlyphRun object. /// /// GlyphTypeface of the GlyphRun object /// Bidi level of the GlyphRun object /// Set to true to display the GlyphRun sideways /// Font rendering size in drawing surface units (96ths of an inch). /// The list of font indices that represent glyphs in this run. /// Origin of the first glyph in the run. /// The glyph is placed so that the leading edge of its advance vector /// and its baseline intersect this point. /// /// The list of advance widths, one for each glyph in GlyphIndices. /// The nominal origin of the nth glyph (n > 0) in the run is the nominal origin /// of the n-1th glyph plus the n-1th advance width added along the runs advance vector. /// Base glyphs generally have a non-zero advance width, combining glyphs generally have a zero advance width. /// /// The list of glyph offsets. Added to the nominal glyph origin calculated above to generate the final origin for the glyph. /// Base glyphs generally have a glyph offset of (0,0), combining glyphs generally have an offset /// that places them correctly on top of the nearest preceeding base glyph. /// /// Characters represented by this glyphrun /// /// Identifies a specific device font for which the GlyphRun has been optimized. When a GlyphRun is /// being rendered on a device that has built-in support for this named font, then the GlyphRun should be rendered using a /// possibly device specific mechanism for selecting that font, and by sending the Unicode codepoints rather than the /// glyph indices. When rendering onto a device that does not include built-in support for the named font, /// this property should be ignored. /// /// The list that maps characters in the glyph run to glyph indices. /// There is one entry per character in Characters list. /// Each value gives the offset of the first glyph in GlyphIndices /// that represents the corresponding character in Characters. /// Where multiple characters map to a single glyph, or to a glyph group /// that cannot be broken down to map exactly to individual characters, /// the entries for all the characters have the same value: /// the offset of the first glyph that represents this group of characters. /// If the list is null or empty, sequential 1 to 1 mapping is assumed. /// /// A list of caret stops for the glyphs /// Language of the GlyphRun [CLSCompliant(false)] public GlyphRun( GlyphTypeface glyphTypeface, int bidiLevel, bool isSideways, double renderingEmSize, IListglyphIndices, Point baselineOrigin, IList advanceWidths, IList glyphOffsets, IList characters, string deviceFontName, IList clusterMap, IList caretStops, XmlLanguage language ) { // Suppress PRESharp warning that glyphIndices and advanceWidths are not validated and can be null. // They can indeed be null, but that's perfectly OK. An explicit null check in the constructor is // not required. #pragma warning suppress 56506 Initialize( glyphTypeface, bidiLevel, isSideways, renderingEmSize, glyphIndices, baselineOrigin, advanceWidths, glyphOffsets, characters, deviceFontName, clusterMap, caretStops, language, true // throwIfOverflow ); // GlyphRunFlags.CacheInkBounds enanbles ink bounding box caching. Bounding box caching would cost // 32 bytes per GlyphRun. We do not want to enable it for all cases possible working set increase. // For Line layout, ink bounding box is only used a few times, so caching is disabled because it will // go through TryCreate below. Memory cost: 1 pointer. // For loading XPS in which bounding box calculation are called a lot in hit testing, Glyphs.cs will // call this constructor, which enables caching. Memory cost: 1 pointer + boxed Rect. // If we late decide it's worthwhile to cache for all, memory cost can be reduced to one Rect (32-bytes). // If we decide single precision is good enough, it can be reduced to 16 bytes. _flags |= GlyphRunFlags.CacheInkBounds; } /// /// Creates a new GlyphRun object. This method is similar to the constructor with /// the same argument list except that it returns null instead of throwing an /// exception if the GlyphRun area or a coordinate exceed the maximum value. /// internal static GlyphRun TryCreate( GlyphTypeface glyphTypeface, int bidiLevel, bool isSideways, double renderingEmSize, IListglyphIndices, Point baselineOrigin, IList advanceWidths, IList glyphOffsets, IList characters, string deviceFontName, IList clusterMap, IList caretStops, XmlLanguage language ) { GlyphRun glyphRun = new GlyphRun(); // Suppress PRESharp warning that glyphIndices and advanceWidths are not validated and can be null. // They can indeed be null, but that's perfectly OK. An explicit null check in the constructor is // not required. #pragma warning suppress 56506 glyphRun.Initialize( glyphTypeface, bidiLevel, isSideways, renderingEmSize, glyphIndices, baselineOrigin, advanceWidths, glyphOffsets, characters, deviceFontName, clusterMap, caretStops, language, false // throwIfOverflow ); if (glyphRun.IsInitialized) return glyphRun; else return null; } private void Initialize( GlyphTypeface glyphTypeface, int bidiLevel, bool isSideways, double renderingEmSize, IList glyphIndices, Point baselineOrigin, IList advanceWidths, IList glyphOffsets, IList characters, string deviceFontName, IList clusterMap, IList caretStops, XmlLanguage language, bool throwOnOverflow ) { // The default branch prediction rules for modern processors specify that forward branches // are not to be taken. If the branch is in fact taken, all of the speculatively executed code // must be discarded, the processor pipeline flushed, and then reloaded. This results in a // processor stall of at least 42 cycles for the P4 Northwood for each mis-predicted branch. // The deeper the processor pipeline the higher the cost, i.e. Prescott processors. // Checking for multiple incorrect parameters in a method with high call count like this one can // easily add significant overhead for no reason. Note that the C# compiler should be able to make // reasonable assumptions about branches that throw exceptions, but the current whidbey // implemenation is weak in this regard. Also the current IBC tools are unable to add branch // prediction hints to improve behavior based on run time information. Also note that adding // branch prediction hints increases code size by a byte per branch and doing this in every // method that is coded without default branch prediction behavior in mind would add an // unacceptable amount of working set. if ((glyphTypeface != null) && (glyphIndices != null) && (advanceWidths != null) && (renderingEmSize >= 0.0) && (glyphIndices.Count > 0) && (glyphIndices.Count <= MaxGlyphCount) && (advanceWidths.Count == glyphIndices.Count) && ((glyphOffsets == null) || ((glyphOffsets != null) && (glyphOffsets.Count != 0) && (glyphOffsets.Count == glyphIndices.Count)))) { // Set member variables here, // so that GlyphRun properties can be calculated in advanced validation code. _glyphIndices = glyphIndices; _characters = characters; _clusterMap = clusterMap; _baselineOrigin = baselineOrigin; _renderingEmSize = renderingEmSize; _advanceWidths = advanceWidths; _glyphOffsets = glyphOffsets; _glyphTypeface = glyphTypeface; _flags = (isSideways ? GlyphRunFlags.IsSideways : GlyphRunFlags.None); _bidiLevel = bidiLevel; _caretStops = caretStops; _language = language; _deviceFontName = deviceFontName; if (characters != null && characters.Count != 0) { if (clusterMap != null && clusterMap.Count != 0) { if (clusterMap.Count == characters.Count) { // Perform some simple cluster map validation. // First entry should be zero, the entries should be monotonic and shouldn't point outside of the glyph indices range. if (clusterMap[0] == 0) { int glyphCount = GlyphCount; int mapCount = clusterMap.Count; ushort previous = clusterMap[0]; for (int i = 1; i < mapCount; ++i) { ushort current = clusterMap[i]; if ((current >= previous) && (current < glyphCount)) { previous = current; } else { if (clusterMap[i] < clusterMap[i - 1]) throw new ArgumentException(SR.Get(SRID.ClusterMapEntriesShouldNotDecrease), "clusterMap"); if (clusterMap[i] >= GlyphCount) throw new ArgumentException(SR.Get(SRID.ClusterMapEntryShouldPointWithinGlyphIndices), "clusterMap"); } } } else { throw new ArgumentException(SR.Get(SRID.ClusterMapFirstEntryMustBeZero), "clusterMap"); } } else { throw new ArgumentException(SR.Get(SRID.CollectionNumberOfElementsShouldBeEqualTo, characters.Count), "clusterMap"); } } else { if (GlyphCount != characters.Count) throw new ArgumentException(SR.Get(SRID.CollectionNumberOfElementsShouldBeEqualTo, GlyphCount), "clusterMap"); } } if (caretStops != null && caretStops.Count != 0) { if (caretStops.Count != CodepointCount + 1) throw new ArgumentException(SR.Get(SRID.CollectionNumberOfElementsShouldBeEqualTo, CodepointCount + 1), "caretStops"); } if (isSideways && (bidiLevel & 1) != 0) throw new ArgumentException(SR.Get(SRID.SidewaysRTLTextIsNotSupported)); { // Check given arguments against big numbers. // The purpose of this block is to ensure that we'll // never get arithmetic overflow on rendering, and reduce // the risk of memory overflow. // Too big numbers will be discovered right here and cause // throwing the exception. // Coordinates are submitted to rendering in normalized form, // i.e. divided by renderingEmSize. // On rendering, coordinate values are multiplied by rasterization scale // that is known not to exceed sc_uGeometryThreshold * sc_uMaxOvescale. // Obtained values may be shifted by Origin and Advance values taken from // GlyphBitmap. The latter two values are shifted 16-bit short. The sum is then // converted to integer value using CFloatFPU::SmallRound routine // that accepts numbers in range -OverscaledCoordMax <= N <= OverscaledCoordMax. // Hence we have available maximum for given coordinate values: double coordMax = renderingEmSize * ((double)(OverscaledCoordMax - 0x10000)) / (GeometryThreshold * MaxOvescale); double glyphXMin = 0.0; double glyphXMax = 0.0; double glyphYMin = 0.0; double glyphYMax = 0.0; // First, calculate the smallest rectangle containing anchor points // of all of the glyphs. if (glyphOffsets != null) { double glyphX = glyphOffsets[0].X; double glyphY = glyphOffsets[0].Y; glyphXMin = glyphX; glyphXMax = glyphX; glyphYMin = glyphY; glyphYMax = glyphY; if ((Math.Abs(glyphX) <= coordMax) && (Math.Abs(glyphY) <= coordMax)) { double positionX = 0; int glyphCount = GlyphCount; for (int i = 1; i < glyphCount; i++) { positionX += advanceWidths[i - 1]; if (Math.Abs(positionX) <= coordMax) { glyphX = positionX; glyphY = 0; double glyphOffsetX = glyphOffsets[i].X; double glyphOffsetY = glyphOffsets[i].Y; if (Math.Abs(glyphOffsetX) <= coordMax && (Math.Abs(glyphOffsetY) <= coordMax)) { glyphX += glyphOffsetX; glyphY += glyphOffsetY; if (glyphX < glyphXMin) glyphXMin = glyphX; else if (glyphX > glyphXMax) glyphXMax = glyphX; if (glyphY < glyphYMin) glyphYMin = glyphY; else if (glyphY > glyphYMax) glyphYMax = glyphY; // Continue looping, no error continue; } } // We will get here if either range check above fails if (throwOnOverflow) ReportCoordinateOverflow(i, renderingEmSize, coordMax); else return; } } else { if (throwOnOverflow) ReportCoordinateOverflow(0, renderingEmSize, coordMax); else return; } } else { double glyphX = 0.0; int glyphCount = GlyphCount; for (int i = 1; i < glyphCount; i++) { glyphX += advanceWidths[i - 1]; if (Math.Abs(glyphX) <= coordMax) { if (glyphX < glyphXMin) glyphXMin = glyphX; else if (glyphX > glyphXMax) glyphXMax = glyphX; } else if (throwOnOverflow) { ReportCoordinateOverflow(i, renderingEmSize, coordMax); } else { return; } } } // We have the rectangle bounds (glyphXMin, glyphXMax, glyphYMin, glyphYMax) // in local coordinate space. Area occupied by glyph run is bigger because // glyphs have sizes. Note that glyph anchor point might be out of glyph // bounding rectangle, due to overhang. We allow 2*renderingEmSize offset from // glyph anchor point to any of four lines that compose glyph bounding rectangle. // // Now we can estimate the area occupied by glyph run. double relativeWidth = (glyphXMax - glyphXMin) / renderingEmSize + 4; double relativeHeight = (glyphYMax - glyphYMin) / renderingEmSize + 4; double relativeArea = relativeWidth * relativeHeight; double scaleMax = GeometryThreshold * MaxOvescale; double relativeAreaMax = MaxOverscaledBitsInGlyphRun / (scaleMax * scaleMax); // Reject the glyph run that occupies too big area. if (relativeArea > relativeAreaMax) { if (throwOnOverflow) ReportAreaOverflow(relativeArea, relativeAreaMax); else return; } } } else { if (DoubleUtil.IsNaN(renderingEmSize)) throw new ArgumentOutOfRangeException("renderingEmSize", SR.Get(SRID.ParameterValueCannotBeNaN)); if (renderingEmSize < 0.0) throw new ArgumentOutOfRangeException("renderingEmSize", SR.Get(SRID.ParameterValueCannotBeNegative)); if (glyphTypeface == null) throw new ArgumentNullException("glyphTypeface"); if (glyphIndices == null) throw new ArgumentNullException("glyphIndices"); if (glyphIndices.Count <= 0) throw new ArgumentException(SR.Get(SRID.CollectionNumberOfElementsMustBeGreaterThanZero), "glyphIndices"); if (glyphIndices.Count > MaxGlyphCount) { throw new ArgumentException(SR.Get(SRID.CollectionNumberOfElementsMustBeLessOrEqualTo, MaxGlyphCount), "glyphIndices"); } if (advanceWidths == null) throw new ArgumentNullException("advanceWidths"); if (advanceWidths.Count != glyphIndices.Count) throw new ArgumentException(SR.Get(SRID.CollectionNumberOfElementsShouldBeEqualTo, glyphIndices.Count), "advanceWidths"); if (glyphOffsets != null && glyphOffsets.Count != 0 && glyphOffsets.Count != glyphIndices.Count) throw new ArgumentException(SR.Get(SRID.CollectionNumberOfElementsShouldBeEqualTo, glyphIndices.Count), "glyphOffsets"); // We should've caught all invalid cases above and thrown appropriate exceptions. Invariant.Assert(false); } _parentCount = 0; IsInitialized = true; // The glyphrun is completely initialized } private void ReportCoordinateOverflow(int index, double renderingEmSize, double coordMax) { string message = SR.Get(SRID.GlyphCoordinateTooBig); message = string.Format(CultureInfo.CurrentCulture, message, index, renderingEmSize, coordMax); throw new OverflowException(message); } private void ReportAreaOverflow(double relativeArea, double relativeAreaMax) { string message = SR.Get(SRID.GlyphAreaTooBig); message = string.Format(CultureInfo.CurrentCulture, message, relativeArea, relativeAreaMax); throw new OverflowException(message); } #endregion Constructors //------------------------------------------------------ // // Public Methods // //----------------------------------------------------- #region Public Methods /// /// Given a character hit, computes the offset from the leading edge of the glyph run /// to the leading or trailing edge of a caret stop containing the character hit. /// If the glyph run is not hit testable, the distance of 0.0 is returned. /// /// Character hit to compute the distance to. ///The offset from the leading edge of the glyph run /// to the leading or trailing edge of a caret stop containing the character hit. ////// The input character hit is outside of the range specified by the glyph run Unicode string. /// public double GetDistanceFromCaretCharacterHit(CharacterHit characterHit) { CheckInitialized(); // This can only be called on fully initialized GlyphRun IListcaretStops = CaretStops != null && CaretStops.Count != 0 ? CaretStops : new DefaultCaretStopList(CodepointCount); if (characterHit.FirstCharacterIndex < 0 || characterHit.FirstCharacterIndex > CodepointCount) throw new ArgumentOutOfRangeException("characterHit"); int caretStopIndex, codePointsUntilNextStop; FindNearestCaretStop( characterHit.FirstCharacterIndex, caretStops, out caretStopIndex, out codePointsUntilNextStop); // Not a hit testable glyph run. if (caretStopIndex == -1) return 0.0; // Trailing edge of a caret stop that doesn't have a corresponding valid next caret stop. if (codePointsUntilNextStop == -1 && characterHit.TrailingLength != 0) { return 0.0; } // Code point we are measuring the distance to. int caretCodePoint = characterHit.TrailingLength == 0 ? caretStopIndex : caretStopIndex + codePointsUntilNextStop; double distance = 0.0; // Sum up glyph advance widths until the caret code point. IList clusterMap = ClusterMap; if (clusterMap == null) clusterMap = new DefaultClusterMap(CodepointCount); int clusterCodepointStart = 0; int currentCodepoint = clusterCodepointStart; IList advances = AdvanceWidths; for (;;) { ++currentCodepoint; if (currentCodepoint >= clusterMap.Count || clusterMap[currentCodepoint] != clusterMap[clusterCodepointStart]) { // We reached the beginning of the next cluster or the end of the glyph run. // If the codepoint is within the cluster, calculate the partial width and abort the loop. // If the codepoint is past the cluster, accumulate the whole cluster advance width and move on. double clusterWidth = 0; int clusterGlyphEnd; if (currentCodepoint >= clusterMap.Count) clusterGlyphEnd = advances.Count; else clusterGlyphEnd = clusterMap[currentCodepoint]; for (int i = clusterMap[clusterCodepointStart]; i < clusterGlyphEnd; ++i) clusterWidth += advances[i]; if (caretCodePoint < currentCodepoint || currentCodepoint >= clusterMap.Count) { // The caret code point is within a cluster or we are past the end of the run, // sum all glyph advance widths in the cluster // and multiply the result by (caretCodePoint / number of codepoints in the cluster). clusterWidth *= (double)(caretCodePoint - clusterCodepointStart) / (currentCodepoint - clusterCodepointStart); distance += clusterWidth; break; } // The codepoint is past the cluster, accumulate the whole cluster advance width and move on. distance += clusterWidth; clusterCodepointStart = currentCodepoint; } } return distance; } /// /// Given an offset from the leading edge of the glyph run, computes the caret character hit /// that contains the offset. The out bool IsInside parameter describes whether the character hit /// is inside the glyph run. If the hit is outside the glyph run, the character hit represents /// the closest caret character hit within the glyph run. /// /// Distance to compute character hit for. /// isInside is set to true when the character hit /// is inside the glyph run, and to false otherwise. ///The character hit that is closest to the input distance. public CharacterHit GetCaretCharacterHitFromDistance(double distance, out bool isInside) { CheckInitialized(); // This can only be called on fully initialized GlyphRun // Navigate the caret stop array and find a pair of caret stops that contains the distance. IListadvances = AdvanceWidths; IList caretStops = CaretStops != null && CaretStops.Count != 0 ? CaretStops : new DefaultCaretStopList(CodepointCount); IList clusterMap = ClusterMap; if (clusterMap == null) clusterMap = new DefaultClusterMap(CodepointCount); // The following two variables describe the closest caret stop to the left of the input distance. int firstStopIndex = -1; double firstStopAdvance = 0.0; // The following variable describes the closest caret stop to the right of the input distance. int secondStopIndex = -1; // Accumulated advance width just before the current cluster. double currentAdvance = 0.0; // Start index of the cluster we're in. int currentClusterStart = 0; // Since the caretStops array contains clusterMap.Count + 1 elements, // we need to be careful before dereferencing i in the loop body. for (int i = 1; i < caretStops.Count; ++i) { if (i < clusterMap.Count && clusterMap[i] == clusterMap[currentClusterStart]) continue; // We reached the end of an (n:m) cluster. // First, accumulate the overall cluster advance width by summing m glyph advances. ushort lastGlyphInCluster = i < clusterMap.Count ? clusterMap[i] : (ushort)advances.Count; Debug.Assert(clusterMap[currentClusterStart] < lastGlyphInCluster); double clusterAdvance = 0.0; for (int j = clusterMap[currentClusterStart]; j < lastGlyphInCluster; ++j) clusterAdvance += advances[j]; // The overall advance is divided evenly by n code points. clusterAdvance /= i - currentClusterStart; // Go through the individual caret stops and compare them against the input distance for (int j = currentClusterStart; j < i; ++j) { if (caretStops[j]) { if (currentAdvance <= distance) { firstStopIndex = j; firstStopAdvance = currentAdvance; } else { // We found a caret stop to the right of the input distance, // so we're done with enumerating. secondStopIndex = j; goto SecondStopFound; } } currentAdvance += clusterAdvance; } currentClusterStart = i; } // The last iteration is interesting. Because inside the above loop we only look at the caret stops up until i-1, // and there may or may not be a caret stop at the end of a glyph run, // we need to check the last caret stop value and the distance. // The code before SecondStopFound is essentially the reduced version of the loop body above when i == caretStops.Count. // We could modify the loop, but this would result in additional special cases. if (caretStops[caretStops.Count - 1]) { if (currentAdvance > distance) secondStopIndex = caretStops.Count - 1; } SecondStopFound: // First stop is described by firstStopIndex, firstStopAdvance. // Second stop is described by secondStopIndex, currentAdvance. // If both indices are equal to -1, then all caret stop entries except the very last one are set to false. // If the last one is also set to false, the glyph run is not hit testable at all. // If the last one is set to true, we return CharacterHit corresponding to that last caret stop. if (firstStopIndex == -1 && secondStopIndex == -1) { isInside = false; if (caretStops[caretStops.Count - 1]) return new CharacterHit(caretStops.Count - 1, 0); else return new CharacterHit(0, 0); } // Check for case when the first stop is not valid. // This happens when the hit is to the left of the first caret stop. if (firstStopIndex == -1) { isInside = false; // Leading edge of the second stop. return new CharacterHit(secondStopIndex, 0); } // Check for case when the second stop is not valid. // This happens when the hit is to the right of the last caret stop. if (secondStopIndex == -1) { isInside = false; // Trailing edge of the first stop. return new CharacterHit(firstStopIndex, caretStops.Count - 1 - firstStopIndex); } isInside = true; if (distance <= (firstStopAdvance + currentAdvance) / 2.0) { // Leading edge of the first stop. return new CharacterHit(firstStopIndex, 0); } else { // Trailing edge of the first stop. return new CharacterHit(firstStopIndex, secondStopIndex - firstStopIndex); } } /// /// Computes the next valid caret character hit in the logical direction. /// If no further navigation is possible, the returned hit result is the same as input value. /// /// The character hit to compute next hit value for. ///The next valid caret character hit in the logical direction, or /// the input value if no further navigation is possible. public CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit) { CheckInitialized(); // This can only be called on fully initialized GlyphRun IListcaretStops = CaretStops != null && CaretStops.Count != 0 ? CaretStops : new DefaultCaretStopList(CodepointCount); if (characterHit.FirstCharacterIndex < 0 || characterHit.FirstCharacterIndex > CodepointCount) throw new ArgumentOutOfRangeException("characterHit"); int caretStopIndex, codePointsUntilNextStop; FindNearestCaretStop( characterHit.FirstCharacterIndex, caretStops, out caretStopIndex, out codePointsUntilNextStop); // Not a hit testable run, or no next caret code point. if (caretStopIndex == -1 || codePointsUntilNextStop == -1) return characterHit; // If we are at the leading edge, move to the trailing edge of the same code point. if (characterHit.TrailingLength == 0) return new CharacterHit(caretStopIndex, codePointsUntilNextStop); // If the next caret stop is within the glyph run, // move to the trailing edge of it. int nextCaretStopIndex, nextCodePointsUntilNextStop; FindNearestCaretStop( caretStopIndex + codePointsUntilNextStop, caretStops, out nextCaretStopIndex, out nextCodePointsUntilNextStop); // See if the next caret stop is within the glyph run. // If not, no navigation is possible. if (nextCodePointsUntilNextStop == -1) return characterHit; return new CharacterHit(nextCaretStopIndex, nextCodePointsUntilNextStop); } /// /// Computes the previous valid caret character hit in the logical direction. /// If no further navigation is possible, the returned hit result is the same as input value. /// /// The character hit to compute previous hit value for. ///The previous valid caret character hit in the logical direction, or /// the input value if no further navigation is possible. public CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit) { CheckInitialized(); // This can only be called on fully initialized GlyphRun IListcaretStops = CaretStops != null && CaretStops.Count != 0 ? CaretStops : new DefaultCaretStopList(CodepointCount); if (characterHit.FirstCharacterIndex < 0 || characterHit.FirstCharacterIndex > CodepointCount) throw new ArgumentOutOfRangeException("characterHit"); int caretStopIndex, codePointsUntilNextStop; FindNearestCaretStop( characterHit.FirstCharacterIndex, caretStops, out caretStopIndex, out codePointsUntilNextStop); if (caretStopIndex == -1) return characterHit; // If we are at the trailing edge, move to the leading edge of the same code point. if (characterHit.TrailingLength != 0) return new CharacterHit(caretStopIndex, 0); // Find the previous caret stop. int previousCaretStopIndex; FindNearestCaretStop( caretStopIndex - 1, caretStops, out previousCaretStopIndex, out codePointsUntilNextStop); // No previous hit, return the original one. if (previousCaretStopIndex == -1 || previousCaretStopIndex == caretStopIndex) return characterHit; return new CharacterHit(previousCaretStopIndex, 0); } #endregion Public Methods //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ #region Public Properties /// /// Advance width from origin of first glyph to far alignment edge of last glyph. /// private double AdvanceWidth { get { double advance = 0; if (_advanceWidths != null) { foreach(double glyphAdvance in _advanceWidths) advance += glyphAdvance; } return advance; } } ////// Distance from the GlyphRun origin to the top of the alignment box. /// private double Ascent { get { // for sideways text, origin is in the middle of the character cell if (IsSideways) return _renderingEmSize * _glyphTypeface.Height / 2.0; return _glyphTypeface.Baseline * _renderingEmSize; } } ////// Distance from top to bottom of alignment box. /// private double Height { get { return _glyphTypeface.Height * _renderingEmSize; } } ////// The baseline origin of the glyph run /// public Point BaselineOrigin { get { CheckInitialized(); return _baselineOrigin; } set { CheckInitializing(); // This can only be set during initialization. _baselineOrigin = value; } } ////// Em size used for rendering. /// public double FontRenderingEmSize { get { CheckInitialized(); return _renderingEmSize; } set { CheckInitializing(); // This can only be set during initialization. _renderingEmSize = value; } } ////// Returns GlyphTypeface for this object. /// public GlyphTypeface GlyphTypeface { get { CheckInitialized(); return _glyphTypeface; } set { CheckInitializing(); // This can only be set during initialization. if (value == null) { throw new ArgumentNullException("value"); } _glyphTypeface = value; } } ////// Determines LTR/RTL reading order and bidi nesting. /// ///The value of bidirectional nesting level. public int BidiLevel { get { CheckInitialized(); return _bidiLevel; } set { CheckInitializing(); // This can only be set during initialization. _bidiLevel = value; } } ////// Returns whether the glyph run is left to right or right to left. /// ///true for LTR, false for RTL. private bool IsLeftToRight { get { return (_bidiLevel & 1) == 0; } } ////// Specifies whether to rotate characters/glyphs 90 degrees anti-clockwise /// and use vertical baseline positioning metrics. /// ///true if the rotation should be applied, false otherwise. public bool IsSideways { get { CheckInitialized(); return (_flags & GlyphRunFlags.IsSideways) != 0; } set { CheckInitializing(); // This can only be set during initialization. if (value) { _flags |= GlyphRunFlags.IsSideways; } else { _flags &= (~GlyphRunFlags.IsSideways); } } } ////// Returns caret stops list for this GlyphRun or null if there is a caret stop for every UTF16 codepoint. /// [CLSCompliant(false)] [TypeConverter(typeof(BoolIListConverter))] public IListCaretStops { get { CheckInitialized(); return _caretStops; } set { CheckInitializing(); // This can only be set during initialization. // The list can be null, empty or non-empty list. // The consistency with other lists would be checked at EndInit() time. _caretStops = value; } } /// /// Returns whether there are any valid caret character hits within the glyph run. /// public bool IsHitTestable { get { CheckInitialized(); // This can only be called on fully initialized GlyphRun if (CaretStops == null || CaretStops.Count == 0) { // When CaretStops property is omitted, there is a caret stop for every UTF16 code point. return true; } foreach (bool caretStop in CaretStops) { if (caretStop) return true; } return false; } } ////// The list that maps characters in the glyph run to glyph indices. /// There is one entry per character in Characters list. /// Each value gives the offset of the first glyph in GlyphIndices /// that represents the corresponding character in Characters. /// Where multiple characters map to a single glyph, or to a glyph group /// that cannot be broken down to map exactly to individual characters, /// the entries for all the characters have the same value: /// the offset of the first glyph that represents this group of characters. /// If the list is null or empty, sequential 1 to 1 mapping is assumed. /// [CLSCompliant(false)] [TypeConverter(typeof(UShortIListConverter))] public IListClusterMap { get { CheckInitialized(); return _clusterMap; } set { CheckInitializing(); // This can only be set during initialization. // The list can be null, empty or non-empty list. // The consistency with other lists would be checked at EndInit() time. _clusterMap = value; } } /// /// Returns the list of UTF16 code points that represent the Unicode content of the glyph run. /// [CLSCompliant(false)] [TypeConverter(typeof(CharIListConverter))] public IListCharacters { get { CheckInitialized(); return _characters; } set { CheckInitializing(); // This can only be set during initialization. // The list can be null, empty or non-empty list. // The consistency with other lists would be checked at EndInit() time. _characters = value; } } /// /// Array of 16 bit glyph numbers that represent this run. /// [CLSCompliant(false)] [TypeConverter(typeof(UShortIListConverter))] public IListGlyphIndices { get { CheckInitialized(); return _glyphIndices; } set { CheckInitializing(); // This can only be set during initialization. // The list must be non-empty list. // The consistency with other lists would be checked at EndInit() time. if (value == null) throw new ArgumentNullException("value"); if (value.Count <= 0) throw new ArgumentException(SR.Get(SRID.CollectionNumberOfElementsMustBeGreaterThanZero), "GlyphIndices"); _glyphIndices = value; } } /// /// The list of advance widths, one for each glyph in GlyphIndices. /// The nominal origin of the nth glyph in the run (n>0) is the nominal origin /// of the n-1th glyph plus the n-1th advance width added along the runs advance vector. /// Base glyphs generally have a non-zero advance width, combining glyphs generally have a zero advance width. /// [CLSCompliant(false)] [TypeConverter(typeof(DoubleIListConverter))] public IListAdvanceWidths { get { CheckInitialized(); return _advanceWidths; } set { CheckInitializing(); // This can only be set during initialization. // The list must be non-empty list. // The consistency with other lists would be checked at EndInit() time. if (value == null) throw new ArgumentNullException("value"); if (value.Count <= 0) throw new ArgumentException(SR.Get(SRID.CollectionNumberOfElementsMustBeGreaterThanZero), "AdvanceWidths"); _advanceWidths = value; } } /// /// Array of glyph offsets. Added to the nominal glyph origin calculated above to generate the final origin for the glyph. /// Base glyphs generally have a glyph offset of (0,0), combining glyphs generally have an offset /// that places them correctly on top of the nearest preceeding base glyph. /// [CLSCompliant(false)] [TypeConverter(typeof(PointIListConverter))] public IListGlyphOffsets { get { CheckInitialized(); return _glyphOffsets; } set { CheckInitializing(); // This can only be set during initialization. // The list can be null, empty or non-empty list. // The consistency with other lists would be checked at EndInit() time. _glyphOffsets = value; } } /// /// Returns the language associated with the glyph run. /// public XmlLanguage Language { get { CheckInitialized(); return _language; } set { CheckInitializing(); // This can only be set during initialization. _language = value; } } ////// Identifies a specific device font for which the GlyphRun has been optimized. When a GlyphRun is /// being rendered on a device that has built-in support for this named font, then the GlyphRun should be rendered using a /// possibly device specific mechanism for selecting that font, and by sending the Unicode codepoints rather than the /// glyph indices. When rendering onto a device that does not include built-in support for the named font, /// this property should be ignored. /// public string DeviceFontName { get { CheckInitialized(); return _deviceFontName; } set { CheckInitializing(); // This can only be set during initialization. _deviceFontName = value; } } #endregion Public Properties ////// Increments the number of renderdata instructions that are using this glyphrun /// for multiple path detection /// internal bool IncrementParentCount() { return (_parentCount++ > 1); } ////// Decrements the number of renderdata instructions that are using this glyphrun /// for multiple path detection /// internal void DecrementParentCount() { Debug.Assert(_parentCount > 0); _parentCount--; } ////// Returns whether this glyphrun has multiple parents /// internal bool HasMultipleParents { get { return (_parentCount > 1); } } ////// Glyph offsets /// The array is indexed starting with InitialGlyph /// internal Point GetGlyphOffset(int i) { if (_glyphOffsets == null || _glyphOffsets.Count == 0) return new Point(0, 0); return _glyphOffsets[i]; } ////// Returns whether GlyphRun contains anything to be drawn. /// Examples of GlyphRun's that don't contain ink are: /// - GlyphRun containing only spaces (" ") /// - GlyphRun wiithout any glyphs in it /// ///true if GlyphRun contains ink, false if it doesn't. private bool HasInk { get { if ((_flags & GlyphRunFlags.HasInkIsCached) != 0) { // // We've already calculated this property before // -- we just need to retrieve the cached bit. // return (_flags & GlyphRunFlags.HasInk) != 0; } else { // // Check if the glyph run contains anything to be drawn. // Cache the result and mark the property as cached. // bool hasInk = _glyphIndices.Count != 0 && !ComputeInkBoundingBox().IsEmpty; _flags |= (hasInk ? GlyphRunFlags.HasInk : GlyphRunFlags.None) | GlyphRunFlags.HasInkIsCached; return hasInk; } } } internal int GlyphCount { get { return _glyphIndices.Count; } } internal int CodepointCount { get { if (_characters != null && _characters.Count != 0) return _characters.Count; if (_clusterMap != null && _clusterMap.Count != 0) return _clusterMap.Count; return _glyphIndices.Count; } } #region Drawing and measurements ////// Computes ink bounding box for the glyph run. /// The rectangle is relative to the glyph run origin. /// ///The ink bounding box of the glyph run public Rect ComputeInkBoundingBox() { CheckInitialized(); // This can only be called on fully initialized GlyphRun if ((_flags & GlyphRunFlags.CacheInkBounds) != 0) { if (_inkBoundingBox != null) { return (Rect) _inkBoundingBox; } } bool italicSimulation = (_glyphTypeface.StyleSimulations & StyleSimulations.ItalicSimulation) != 0; // Special casing Left to Right layout with no italics allows an implementation that is // 12 times faster than the general case. Other combinations of Left to Right and // sideways layout also presents optimization opportunities that need to be implemented. // Italics is used infrequently, so adding the additional 8 routines necessary to handle italics // in combination with the other 4 routines is not justified. if (IsLeftToRight && !IsSideways && !italicSimulation) { Rect rect = ComputeInkBoundingBoxLtoR(); if ((_flags & GlyphRunFlags.CacheInkBounds) != 0) { _inkBoundingBox = rect; } return rect; } double accAdvance = 0; // We don't use Rect and Rect.Union to accumulate the bounding box // because this function is a hot spot and Rect methods perform extra checks that we don't need. double accLeft = double.PositiveInfinity; double accTop = double.PositiveInfinity; double accRight = double.NegativeInfinity; double accBottom = double.NegativeInfinity; for (int i = 0; i < GlyphCount; ++i) { double aw, ah, lsb, rsb, tsb, bsb, baseline; _glyphTypeface.GetGlyphMetrics( _glyphIndices[i], _renderingEmSize, out aw, out ah, out lsb, out rsb, out tsb, out bsb, out baseline ); Point glyphOffset = GetGlyphOffset(i); double originX; if (IsLeftToRight) { originX = accAdvance + glyphOffset.X; } else { // no languages support sideways and right to left in the same run Debug.Assert(!IsSideways); originX = -accAdvance - (aw + glyphOffset.X); } accAdvance += _advanceWidths[i]; double horBaselineOriginY = -glyphOffset.Y; double left, right, bottom, top; if (IsSideways) { horBaselineOriginY += aw / 2.0; bottom = horBaselineOriginY - lsb; top = horBaselineOriginY - aw + rsb; left = originX + tsb; right = left + ah - tsb - bsb; } else { left = originX + lsb; right = originX + aw - rsb; bottom = horBaselineOriginY + baseline; top = bottom - ah + tsb + bsb; } // extend the ink box for the glyph to account for italic simulation if (italicSimulation) { // the italic simulation is always applied relative to horizontal baseline right += Sin20 * (horBaselineOriginY - top); left -= Sin20 * (bottom - horBaselineOriginY); } // skip blank glyphs, as they don't contain ink if (left + InkMetricsEpsilon >= right || top + InkMetricsEpsilon >= bottom) continue; if (accLeft > left) accLeft = left; if (accTop > top) accTop = top; if (accRight < right) accRight = right; if (accBottom < bottom) accBottom = bottom; } Rect bounds; if (accLeft > accRight) { bounds = Rect.Empty; } else { bounds = new Rect( accLeft, accTop, accRight - accLeft, accBottom - accTop ); } if ((_flags & GlyphRunFlags.CacheInkBounds) != 0) { _inkBoundingBox = bounds; } return bounds; } private Rect ComputeInkBoundingBoxLtoR() { // We don't use Rect and Rect.Union to accumulate the bounding box // because this function is a hot spot and Rect methods perform extra checks that we don't need. double accLeft = double.PositiveInfinity; double accTop = double.PositiveInfinity; double accRight = double.NegativeInfinity; double accBottom = double.NegativeInfinity; double accAdvance = 0; int glyphCount = GlyphCount; if (GlyphOffsets != null) { for (int i = 0; i < glyphCount; ++i) { double aw, ah, lsb, rsb, tsb, bsb, baseline; _glyphTypeface.GetGlyphMetrics( _glyphIndices[i], _renderingEmSize, out aw, out ah, out lsb, out rsb, out tsb, out bsb, out baseline ); Point glyphOffset = GetGlyphOffset(i); double originX = accAdvance + glyphOffset.X; accAdvance += _advanceWidths[i]; double horBaselineOriginY = -glyphOffset.Y; double left, right, bottom, top; left = originX + lsb; right = originX + aw - rsb; bottom = horBaselineOriginY + baseline; top = bottom - ah + tsb + bsb; // skip blank glyphs, as they don't contain ink if (left + InkMetricsEpsilon >= right || top + InkMetricsEpsilon >= bottom) continue; if (accLeft > left) accLeft = left; if (accTop > top) accTop = top; if (accRight < right) accRight = right; if (accBottom < bottom) accBottom = bottom; } } else { for (int i = 0; i < glyphCount; ++i) { double aw, ah, lsb, rsb, tsb, bsb, baseline; _glyphTypeface.GetGlyphMetrics( _glyphIndices[i], _renderingEmSize, out aw, out ah, out lsb, out rsb, out tsb, out bsb, out baseline ); double left, right, top; left = accAdvance + lsb; right = accAdvance + aw - rsb; top = baseline - ah + tsb + bsb; accAdvance += _advanceWidths[i]; // skip blank glyphs, as they don't contain ink if (left + InkMetricsEpsilon >= right || top + InkMetricsEpsilon >= baseline) continue; if (accLeft > left) accLeft = left; if (accTop > top) accTop = top; if (accRight < right) accRight = right; if (accBottom < baseline) accBottom = baseline; } } if (accLeft > accRight) return Rect.Empty; return new Rect( accLeft, accTop, accRight - accLeft, accBottom - accTop ); } ////// Obtains geometry for the glyph run. /// ///The geometry returned contains the combined geometry of all glyphs in the glyph run. /// Overlapping contours are merged by performing a Boolean union operation. public Geometry BuildGeometry() { CheckInitialized(); // This can only be called on fully initialized GlyphRun GeometryGroup accumulatedGeometry = null; double accAdvance = 0; for (int i = 0; i < GlyphCount; ++i) { ushort glyphIndex = _glyphIndices[i]; double originX; if (IsLeftToRight) { originX = accAdvance; originX += GetGlyphOffset(i).X; } else { // no languages support sideways and right to left in the same run Debug.Assert(!IsSideways); double nominalAdvance = _glyphTypeface.GetAdvanceWidth(glyphIndex) * _renderingEmSize; originX = -accAdvance; originX -= (nominalAdvance + GetGlyphOffset(i).X); } accAdvance += _advanceWidths[i]; double originY = -GetGlyphOffset(i).Y; Geometry glyphGeometry = _glyphTypeface.ComputeGlyphOutline(glyphIndex, IsSideways, _renderingEmSize, DefaultFontHintingSize); if (glyphGeometry.IsEmpty()) continue; // transform glyphGeometry to the glyph origin glyphGeometry.Transform = new TranslateTransform(originX + _baselineOrigin.X, originY + _baselineOrigin.Y); if (accumulatedGeometry == null) { accumulatedGeometry = new GeometryGroup(); accumulatedGeometry.FillRule = FillRule.Nonzero; } accumulatedGeometry.Children.Add(glyphGeometry.GetOutlinedPathGeometry(RelativeFlatteningTolerance, ToleranceType.Relative)); } // Make sure to always return Geometry.Empty from public methods for empty geometries. if (accumulatedGeometry == null || accumulatedGeometry.IsEmpty()) return Geometry.Empty; return accumulatedGeometry; } ////// Computes the alignment box for the glyph run. /// The alignment box is relative to origin. /// ///The alignment box for the glyph run. public Rect ComputeAlignmentBox() { CheckInitialized(); // This can only be called on fully initialized GlyphRun if (IsLeftToRight) { return new Rect( 0, -Ascent, AdvanceWidth, Height ); } else { // cache AdvanceWidth value in a local variable because it involves a loop double advanceWidth = AdvanceWidth; return new Rect( -advanceWidth, -Ascent, advanceWidth, Height ); } } ////// Temporary helper to draw a glyph run background. /// We hope to remove all uses of it, as fundamentally this is not the right way /// to handle background drawing. /// internal void EmitBackground(DrawingContext dc, Brush backgroundBrush) { if (backgroundBrush != null) { Rect backgroundRect; if (IsLeftToRight) { backgroundRect = new Rect( _baselineOrigin.X, _baselineOrigin.Y - Ascent, AdvanceWidth, Height ); } else { backgroundRect = new Rect( _baselineOrigin.X - AdvanceWidth, _baselineOrigin.Y - Ascent, AdvanceWidth, Height ); } dc.DrawRectangle( backgroundBrush, null, backgroundRect ); } } #endregion Drawing and measurements #region DUCE.IResource implementation internal class Realization { internal Realization _next; internal uint _realizationHandle; internal bool _inUse; internal bool _hintingSuppressed; internal bool _suppressHintingHasNoEffect; internal IList_glyphIndices; internal double _scaleX; internal double _scaleY; internal int _faceHandle; }; /// /// A structure to keep two scaling ratios fetched from given Matrix. /// internal struct Scale { internal Scale(ref Matrix matrix) { double m11 = matrix.M11; double m12 = matrix.M12; double m21 = matrix.M21; double m22 = matrix.M22; // Calculate redundant data. _baseVectorX = Math.Sqrt(m11 * m11 + m12 * m12); // Check for wrong matrix. // if (DoubleUtil.IsNaN(_baseVectorX)) _baseVectorX = 0; _baseVectorY = _baseVectorX == 0 ? 0 : Math.Abs(m11 * m22 - m12 * m21) / _baseVectorX; if (DoubleUtil.IsNaN(_baseVectorY)) _baseVectorY = 0; } internal bool IsValid { get { return _baseVectorX != 0 && _baseVectorY != 0; } } internal bool IsSame(ref Scale scale) { // // allow some imprecision that can appear because // of matrix computations. // return _baseVectorX * 0.999999999 <= scale._baseVectorX && _baseVectorX * 1.000000001 >= scale._baseVectorX && _baseVectorY * 0.999999999 <= scale._baseVectorY && _baseVectorY * 1.000000001 >= scale._baseVectorY; } internal double _baseVectorX, _baseVectorY; } ////// Struct VisibleInstance keeps the data related to one particular /// "visible instance" of glyphs run. /// "Visible instance" is what we see on the screen (or what is presented on /// another render target, if it is not on the screen). /// Visible instance corresponds to some path in visual tree. /// We are referring to "multi-path scenario" when there are several pathes /// from root to this glyph run (and, correspondingly, same glyph run /// glyph run is rendered several times onto the target, possibly with /// different scale/brush/etc.). /// internal struct VisibleInstance { private const uint _animationDetectionTimeThreshold = 400; ////// Update matrix, register change time. /// See how frequently the changes come, set _inAnimation correspondingly. /// /// internal void UpdateState(RealizationContext realizationContext) { Matrix matrix = realizationContext.TransformStack.Peek(); Scale scale = new Scale(ref matrix); if (!scale.IsValid) { _stateKnown = false; return; } _3DMode = realizationContext.IsIn3DMode(); if (_3DMode) { // in 3D mode we need not animation detection _scale = scale; _stateKnown = true; return; } int currentTime = realizationContext.CurrentTime; if (!_stateKnown) { // The instance has not been initialized yet; // just store the data. _scale = scale; _recentChangeTime = currentTime; _inAnimation = false; _stateKnown = true; return; } // // See whether scaling has been changed. // We are interested only in scaling animation, because translation // does not require glyphs re-rasterization. // bool scalingChanged = !scale.IsSame(ref _scale); if (scalingChanged) { // // Scale ratios have changed since last rendering pass, // hence we are in animation state. // _inAnimation = true; _recentChangeTime = currentTime; } else if (_inAnimation) { // // Scale ratios have not been changed since last rendering pass; // see how long they are staying immoveable. // // // Issue: overflow in time values. // We measure time using 32-bit values so we get overflow // with some period T, approximately equal to 50 days. // We'll mistakenly conclude "in animation" when the time // passed between two consequtive updates is N*T+t, where // "t" is in the range 0 <= t < _animationDetectionTimeThreshold. // No protection againt this issue, in favor of code simplicity. // uint timePassed = (uint)(currentTime - _recentChangeTime); _inAnimation = timePassed < _animationDetectionTimeThreshold; } if (_inAnimation) { // Ensure that we'll be called again to detect animation finish. realizationContext.ScheduleAdditionalFrames(); } _scale = scale; } internal double BaseVectorX { get { return _scale._baseVectorX; } } internal double BaseVectorY { get { return _scale._baseVectorY; } } internal Scale _scale; private int _recentChangeTime; // _stateKnown == false means that either instance has been just created // or inacceptable transformation matrix was given recently. // _stateKnown == true means that _scale, _recentChangeTime and _inAnimation // really contain data. internal bool _stateKnown; // _inAnimation == true when scale animation is detected. internal bool _inAnimation; // The flag _3DMode marks instances intended for 3D scenes which // need not pixel snapping and hinting. internal bool _3DMode; internal VisibleInstanceContainer _next; }; ////// class wrapper above structure VisibleInstance /// internal class VisibleInstanceContainer { internal VisibleInstance _vi; }; ////// struct VisibleInstanceCollection holds the recent state /// of all visible instances of glyph run. /// /// The implementation is optimized for typical case when we have /// only one visible instance of GlyphRun on any involved channel. /// /// The slot to keep this only visible instance is implemented /// as "VisibleInstance _first" that is struct so we /// need not dynamic memory allocations. /// /// Other instances are stored in the chain of VisibleInstanceContainer /// instances that starts in _first._next. /// /// internal struct VisibleInstanceCollection { ////// Store given matrix in consecutive visible instance. /// Note that we so far identify visible instances solely by /// the number of call to MarkVisibleRealization after last /// ExecuteRealizationsUpdate call. /// This is getting virtually wrong when scene is rebuild. /// Recent schema used to compare matrices to establish better /// correspondence, but it turned out that in many multiple-paths /// scenarios this creates more troubles than use. /// The matter is different pathes may relate to different /// coordinate spaces so that coinciding or similar coordinates /// should not be accepted as a hint of referring to the same instance. /// /// Taking into account the considerations above, we consciously /// ignore possible artefacts when scene rebuilds, in favor of /// good behavior when it does not. /// /// /// internal void RegisterInstance(RealizationContext realizationContext, int index) { if (index == 0) { _first.UpdateState(realizationContext); } else { if (_first._next == null) { _first._next = new VisibleInstanceContainer(); } VisibleInstanceContainer vic = _first._next; int i = 1; // index that corresponds to "vic" while (index != i) { if (vic._vi._next == null) { vic._vi._next = new VisibleInstanceContainer(); } vic = vic._vi._next; i++; } vic._vi.UpdateState(realizationContext); } } ////// Handle accumulated requests: /// Remove unused visible instances from the list. /// For each one that is in use, call proxy that'll take care of realizations. /// internal void Traverse(ChannelProxy proxy, int count) { Debug.Assert(count > 0); if (_first._stateKnown) { proxy.EnsureRealization(ref _first); } if (count == 1) { _first._next = null; return; } VisibleInstanceContainer vic = _first._next; int i = 1; // index that corresponds to "vic" proxy.EnsureRealization(ref vic._vi); while(++i != count) { vic = vic._vi._next; proxy.EnsureRealization(ref vic._vi); } // cut off the tail containing unused visible instances vic._vi._next = null; } // The instance "_first" is included explicitly as a member // of collection to avoid memory fragmentation on typical // scenarios that have just one visible instance per glyph run. private VisibleInstance _first; }; ////// ChannelProxy reflects the state of single /// instance of slave glyph run resource /// internal class ChannelProxy : IRealizationContextClient { public GlyphRun _glyphRun; public DUCE.Channel _channel; private Realization _realizationList; private DUCE.IResource _geometry; private DUCE.ResourceHandle _geometryHandle; private bool _slaveHasGeometry; private bool _geometryInUse; private VisibleInstanceCollection _visibleInstances; private int _MVRCount; public ChannelProxy(GlyphRun glyphRun, DUCE.Channel channel) { _glyphRun = glyphRun; _channel = channel; _realizationList = null; _geometry = null; _geometryHandle = DUCE.ResourceHandle.Null; _slaveHasGeometry = false; _visibleInstances = new VisibleInstanceCollection(); } ////// Store given transformation matrix, defer real realization handling. /// internal void MarkVisibleRealization(RealizationContext realizationContext) { _visibleInstances.RegisterInstance(realizationContext, _MVRCount); if (_MVRCount++ == 0) { // Schedule this proxy for realization update. // This call will result with invoking ExecuteRealizationsUpdate // after every path in scene graph will be traversed, // so that we'll have complete set of required transformations // in _visibleInstances. realizationContext.ScheduleForRealizationsUpdate(this); } } ////// Executes the realization updates in correspondence with accumulated /// collection of requests. /// public void ExecuteRealizationsUpdate() { _visibleInstances.Traverse(this, _MVRCount); DeleteUnusedRealizations(); _MVRCount = 0; } ////// Look whether we have proper realization for given visible instance. /// If not, then create new. /// /// internal void EnsureRealization(ref VisibleInstance vi) { // If the size is "big", then use geometry based rendering. // The sense of the word "big" is following. // Consider the glyph that is nothing but square, // with width and height equal to font face "Em" size. // The transformation will convert this square to parallelogramm. // The base of parallelogramm corresponds to the horizontal edge of square // (that's directed along text base line). // The size of parallelogramm base shows how much // the glyphs should be stretched horizontally. // We consider the size too big for bitmap-based // rendering when the size of parallelogramm's base // exceeds GeometryThreshold. // Another thing to check is the height of the parallelogramm. // Its size shows how much the glyphs will be stretched vertically. // When parallelogramm's height exceeds GeometryThreshold, we also // conclude the size is too big for bitmap-based rendering. // // Note: this check should be kept in-sync with the one in GlyphRunSlave.cpp. // // double factor = _glyphRun._renderingEmSize; double scaleX = vi.BaseVectorX * factor; // parallelogramm's base (see comment above) double scaleY = vi.BaseVectorY * factor; // parallelogramm's height bool shouldUseGeometry = scaleX > GeometryThreshold || scaleY > GeometryThreshold; if (shouldUseGeometry) { _geometryInUse = true; if (_geometry == null) { // compose glyph run vector representation Debug.Assert(_geometryHandle.IsNull); _geometry = _glyphRun.GetGeometry(); _geometryHandle = _geometry.AddRefOnChannel(_channel); } if (!_slaveHasGeometry) { MarshalGeometry(true); } } else { // Disallow too severe scaling down. // We need this because of only reason: // when effective font size is so small that // glyph occupy just one or few pixels, rasterizer // produce nothing good that results in a dark picture. double minScale = ScaleGrid[0]; if (scaleX < minScale) scaleX = minScale; if (scaleY < minScale) scaleY = minScale; for (Realization r = _realizationList; r != null; r = r._next) { if (r._scaleX <= scaleX * 1.001 && r._scaleX >= scaleX * 0.999 && r._scaleY <= scaleY * 1.001 && r._scaleY >= scaleY * 0.999 && (r._suppressHintingHasNoEffect || r._hintingSuppressed == vi._inAnimation)) { r._inUse = true; return; } } if (!vi._inAnimation && !vi._3DMode) { // For regular instance, just create new realization. MakeupNewRealization(scaleX, scaleY, false); } else { // For animated instance, be smarter. // The problem is performance. // Creating the realization is expensive. // To decrease the burden, we shoud re-rasterize // seldom, when desized scale differs from // current realization scale significantly. // Also, we should take care of FontCache misses // that might appear because of random scales. // To avoid this, we use only scales // enumerated in ScaleGrid array. double scaleGridX = SnapToScaleGrid(scaleX); double scaleGridY = SnapToScaleGrid(scaleY); bool found = false; for (Realization r = _realizationList; r != null; r = r._next) { // // we are interested only in realizations suitable for animation // if (r._suppressHintingHasNoEffect || r._hintingSuppressed) { if (r._scaleX == scaleGridX && r._scaleY == scaleGridY) { r._inUse = true; found = true; } } } if (!found) { MakeupNewRealization(scaleGridX, scaleGridY, true); } } } } ////// Choose the number in ScaleGrid closest to given one. /// /// given number ///private static double SnapToScaleGrid(double x) { // check for extreme values int a = 0; double va = ScaleGrid[a]; if (x <= va) return va; int b = ScaleGrid.Length - 1; double vb = ScaleGrid[b]; if (x >= vb) return vb; // Binary search to detect the range // (ScaleGrid[a], ScaleGrid[a+1]) // that contains given value. while ((b - a) > 1) { int c = (a + b) / 2; double vc = ScaleGrid[c]; if (x >= vc) { a = c; va = vc; } else { b = c; vb = vc; } } // Given x lays in between of (va,vb); choose // the best of them using logarithmic measure. return x*x > va*vb ? vb : va; } /// /// Create new realization for given scale factors. /// /// horizontal scale /// vertical scale /// suppress hinting to get smooth scaling animation private void MakeupNewRealization(double scaleX, double scaleY, bool suppressHinting) { // Create new realization Realization newRealization = CreateRealization(scaleX, scaleY, suppressHinting); newRealization._inUse = true; // Store realization handle and put realization into list newRealization._realizationHandle = FindFreeRealizationHandle(); newRealization._next = _realizationList; _realizationList = newRealization; // Tell slave that it has new realization AddRealizationToSlave(newRealization); } ////// Delete realizations that are not marked "_inUse". /// Along the way, unmark all so that the list will be prepared for /// accumulating "in use" marks on next frame. /// private void DeleteUnusedRealizations() { Realization list = _realizationList; _realizationList = null; while (list != null) { Realization r = list; list = r._next; if (r._inUse) { // put back to the list of realizations belonging to the proxy r._next = _realizationList; _realizationList = r; r._inUse = false; } else { // Tell slave that this realization is no longer valid RemoveRealizationFromSlave(r); // Give the realization to glyph cache, to destroy. _channel.GlyphCache.RemoveRealization(r); } } if (!_geometryInUse && _slaveHasGeometry) { MarshalGeometry(false); } _geometryInUse = false; } private uint FindFreeRealizationHandle() { for (uint baseIdx = 0; ; baseIdx += 32) { uint mask = 0; for (Realization r = _realizationList; r != null; r = r._next) { uint d = r._realizationHandle - baseIdx; if (d < 32) { mask |= (uint)(1 << (int)d); } } if (mask != 0xFFFFFFFF) { for (int i = 0; i < 32; i++) { if ((mask & (uint)(1 << i)) == 0) { return (uint)(baseIdx + i); } } } } } ////// Critical: This code acceses FontSource. /// TreatAsSafe: This only computes glyph bitmaps or outlines without exposing other font information. /// The font Uri string is wrapped in SecurityCriticalData. /// [SecurityCritical, SecurityTreatAsSafe] private Realization CreateRealization(double scaleX, double scaleY, bool suppressHinting) { UInt16 flags = _glyphRun.ComposeFlags(); // // When preparing glyph bitmaps for animated scenarios (i.e. suppressHinting == true) // we need also 6x5 ovescale that's controlled by MilGlyphRunForceVAA flag. // The lack of doing so causes visible shape jerks. // bool suppressHintingHasNoEffect = ((flags & (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunHinting) == 0) && ((flags & (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunForceVAA) != 0); if (!suppressHintingHasNoEffect && suppressHinting) { flags &= (UInt16)(~MIL_GLYPHRUN_FLAGS.MilGlyphRunHinting); flags |= (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunForceVAA; } Realization realization = _channel.GlyphCache.CreateRealization( new SecurityCriticalData(_glyphRun._glyphTypeface.FontSource.GetUriString()), _glyphRun._glyphTypeface.FaceIndex, flags, scaleX, scaleY, DefaultFontHintingSize, _glyphRun.GlyphIndices ); realization._hintingSuppressed = suppressHinting; realization._suppressHintingHasNoEffect = suppressHintingHasNoEffect; return realization; } /// /// Critical: This code acceses an unsafe code block /// TreatAsSafe: This does not expose any data and the unsafe code block /// needs to be verified for correctness. Sending a pointer to channel is safe /// [SecurityCritical,SecurityTreatAsSafe] private unsafe void AddRealizationToSlave(Realization realization) { // add realization to slave glyph run DUCE.MILCMD_GLYPHRUN_ADDREALIZATION command; command.Type = MILCMD.MilCmdGlyphRunAddRealization; command.Handle = _glyphRun._mcr.GetHandle(_channel); command.RealizationHandle = (UInt32)realization._realizationHandle; // FontFaceHandle above 0xFFFF would be rejected on rendering side; // checked conversion here will fire the exception earlier. command.FontFaceHandle = checked((UInt16)realization._faceHandle); command.ScaleX = (float)realization._scaleX; command.ScaleY = (float)realization._scaleY; _channel.SendCommand( (byte*)&command, sizeof(DUCE.MILCMD_GLYPHRUN_ADDREALIZATION) ); } ////// Critical: This code acceses an unsafe code block /// TreatAsSafe: This does not expose any data and the unsafe code block /// needs to be verified for correctness. Sending a pointer to channel is safe /// [SecurityCritical,SecurityTreatAsSafe] private unsafe void RemoveRealizationFromSlave(Realization realization) { // notify slave DUCE.MILCMD_GLYPHRUN_REMOVEREALIZATION command; command.Type = MILCMD.MilCmdGlyphRunRemoveRealization; command.Handle = _glyphRun._mcr.GetHandle(_channel); command.RealizationHandle = (UInt32)realization._realizationHandle; _channel.SendCommand( (byte*)&command, sizeof(DUCE.MILCMD_GLYPHRUN_REMOVEREALIZATION) ); } ////// Update slave glyph run resource with /// geometry information. When shouldUseGeometry is false, /// this means prohibiting geometry mode (bitmap /// realizations will be used instead) /// ////// Critical: This code acceses an unsafe code block /// TreatAsSafe: This does not expose any data and the unsafe code block /// needs to be verified for correctness. Sending a pointer to channel is safe /// [SecurityCritical, SecurityTreatAsSafe] private void MarshalGeometry(bool shouldUseGeometry) { DUCE.MILCMD_GLYPHRUN_GEOMETRY command; command.Type = MILCMD.MilCmdGlyphRunGeometry; command.Handle = _glyphRun._mcr.GetHandle(_channel); command.hGeometry = shouldUseGeometry ? _geometryHandle : DUCE.ResourceHandle.Null; unsafe { _channel.SendCommand( (byte*)&command, sizeof(DUCE.MILCMD_GLYPHRUN_GEOMETRY) ); } _slaveHasGeometry = shouldUseGeometry; } ////// Free dependents /// public void Destroy() { if (!_geometryHandle.IsNull) { _geometry.ReleaseOnChannel(_channel); _geometryHandle = DUCE.ResourceHandle.Null; } if (_geometry != null) { _glyphRun.ReleaseGeometry(); _geometry = null; } // Cleanup existing bitmap realizations but don't notify slave glyph run, // since it is final destruction while (_realizationList != null) { Realization realization = _realizationList; _realizationList = realization._next; realization._next = null; _channel.GlyphCache.RemoveRealization(realization); } } } private DUCE.MultiChannelResource _mcr = new DUCE.MultiChannelResource(); private FrugalStructList_proxyList = new FrugalStructList (); private Geometry _geometry = null; private int _geometryRefCount = 0; private DUCE.IResource GetGeometry() { if (_geometry == null) { Debug.Assert(_geometryRefCount == 0); _geometry = BuildGeometry(); } _geometryRefCount++; return _geometry; } private void ReleaseGeometry() { Debug.Assert(_geometryRefCount > 0 && _geometry != null); if (--_geometryRefCount == 0) { _geometry = null; } } /// /// Seek for a proxy for a given channel. /// If found, return index in the proxy list. /// Otherwise return -1. /// private int FindProxy(DUCE.Channel channel) { int idx = _proxyList.Count; while(--idx >= 0) { if (_proxyList[idx]._channel == channel) break; } return idx; } ////// Generate a series of requests to create or update /// slave glyph run resource and all depending data. /// DUCE.ResourceHandle DUCE.IResource.AddRefOnChannel(DUCE.Channel channel) { CheckInitialized(); // This can only be called on fully initialized GlyphRun using (CompositionEngineLock.Acquire()) { if (_mcr.CreateOrAddRefOnChannel(channel, DUCE.ResourceType.TYPE_GLYPHRUN)) { Debug.Assert(FindProxy(channel) == -1, "Just created resource should not have proxy"); ChannelProxy proxy = new ChannelProxy(this, channel); _proxyList.Add(proxy); CreateOnChannel(channel); } return _mcr.GetHandle(channel); } } ////// Generates request to delete slave glyph run resource. /// void DUCE.IResource.ReleaseOnChannel(DUCE.Channel channel) { CheckInitialized(); // This can only be called on fully initialized GlyphRun using (CompositionEngineLock.Acquire()) { if (_mcr.ReleaseOnChannel(channel)) { int idx = FindProxy(channel); Debug.Assert(idx >= 0); ChannelProxy proxy = _proxyList[idx]; Debug.Assert(proxy != null); proxy.Destroy(); _proxyList.RemoveAt(idx); } } } ////// This is only implemented by Visual and Visual3D. /// void DUCE.IResource.RemoveChildFromParent(DUCE.IResource parent, DUCE.Channel channel) { throw new NotImplementedException(); } ////// This is only implemented by Visual and Visual3D. /// DUCE.ResourceHandle DUCE.IResource.Get3DHandle(DUCE.Channel channel) { throw new NotImplementedException(); } ////// Adds a realization request for this glyph run. /// /// /// The current realization context. /// internal void MarkVisibleRealization(RealizationContext realizationContext) { CheckInitialized(); // This can only be called on fully initialized GlyphRun // Do nothing if glyph run has no content if (!HasInk) return; using (CompositionEngineLock.Acquire()) { Listchannels = realizationContext.Channels; foreach (DUCE.ChannelSet channelSet in channels) { DUCE.Channel channel = channelSet.Channel; Debug.Assert(_mcr.IsOnChannel(channel)); int idx = FindProxy(channel); Debug.Assert(idx >= 0); ChannelProxy proxy = _proxyList[idx]; Debug.Assert(proxy != null); proxy.MarkVisibleRealization(realizationContext); } } } /// /// Returns current resource handle, allocated recently by AddRefOnChannel. /// DUCE.ResourceHandle DUCE.IResource.GetHandle(DUCE.Channel channel) { CheckInitialized(); // This can only be called on fully initialized GlyphRun return _mcr.GetHandle(channel); } int DUCE.IResource.GetChannelCount() { return _mcr.GetChannelCount(); } DUCE.Channel DUCE.IResource.GetChannel(int index) { return _mcr.GetChannel(index); } ////// Send to channel command sequence to create slave resource. /// ////// Critical: This code acceses an unsafe code block /// TreatAsSafe: This does not expose any data and the unsafe code blocks /// needs to be verified for correctness /// [SecurityCritical,SecurityTreatAsSafe] private void CreateOnChannel(DUCE.Channel channel) { int glyphCount = GlyphCount; DUCE.MILCMD_GLYPHRUN_CREATE command; command.Type = MILCMD.MilCmdGlyphRunCreate; command.Handle = _mcr.GetHandle(channel); command.GlyphCount = checked((UInt16)glyphCount); command.Flags = ComposeFlags(); command.Origin.X = (float)_baselineOrigin.X; command.Origin.Y = (float)_baselineOrigin.Y; command.MuSize = (float)_renderingEmSize; command.hGlyphCache = channel.GlyphCache.Handle; unsafe { // calculate variable data size int varDataSize = 0; if (_glyphOffsets != null && _glyphOffsets.Count != 0) { varDataSize += glyphCount * (2 * sizeof(float)); // positionXY } else { varDataSize += (glyphCount - 1) * sizeof(float); // positionX } varDataSize += glyphCount * sizeof(ushort); // glyph indices channel.BeginCommand( (byte*)&command, sizeof(DUCE.MILCMD_GLYPHRUN_CREATE), varDataSize ); { // fill variable data block double rcmuSize = 1.0 / _renderingEmSize; if (Double.IsInfinity(rcmuSize)) { // Protect against extremely small _renderingEmSize: // denormalized numbers like 5E-324 can cause overflow on // calculating reciprocal value. rcmuSize = 0; } double xRatio = ((command.Flags & (uint)MIL_GLYPHRUN_FLAGS.MilGlyphRunIsLeftToRight) != 0) ? rcmuSize : -rcmuSize; if (_glyphOffsets != null && _glyphOffsets.Count != 0) { double yRatio = -rcmuSize; double accAdvances = 0; if (glyphCount <= MaxStackAlloc / (2 * sizeof(float))) { // glyph count small enough, send all data at once float* pGlyphPositions = stackalloc float[glyphCount * 2]; for (int i = 0; i < glyphCount; i++) { double x = _glyphOffsets[i].X + accAdvances; double y = _glyphOffsets[i].Y; accAdvances += _advanceWidths[i]; pGlyphPositions[2 * i] = (float)(x * xRatio); pGlyphPositions[2 * i + 1] = (float)(y * yRatio); } channel.AppendCommandData((byte*)pGlyphPositions, glyphCount * (2 * sizeof(float))); } else { // glyph count is not small, use per-glyph transmitting float* pGlyphPositions = stackalloc float[2]; for (int i = 0; i < glyphCount; i++) { double x = _glyphOffsets[i].X + accAdvances; double y = _glyphOffsets[i].Y; accAdvances += _advanceWidths[i]; pGlyphPositions[0] = (float)(x * xRatio); pGlyphPositions[1] = (float)(y * yRatio); channel.AppendCommandData((byte*)pGlyphPositions, 2 * sizeof(float)); } } } else if (glyphCount > 1) { // accumulate advance widths and convert them to "glyph space" double accAdvances = 0; if (glyphCount <= MaxStackAlloc / sizeof(float)) { // glyph count small enough, send all data at once float* pPositionX = stackalloc float[glyphCount - 1]; // skipping first glyph position that's always zero for (int i = 0; i < (glyphCount - 1); ++i) { accAdvances += _advanceWidths[i]; pPositionX[i] = (float)(accAdvances * xRatio); } channel.AppendCommandData((byte*)pPositionX, (glyphCount - 1) * sizeof(float)); } else { // glyph count is not small, use per-glyph transmitting for (int i = 0; i < (glyphCount - 1); ++i) { accAdvances += _advanceWidths[i]; float positionX = (float)(accAdvances * xRatio); channel.AppendCommandData((byte*)&positionX, sizeof(float)); } } } { // transmit glyph indices if (glyphCount <= MaxStackAlloc / sizeof(ushort)) { // glyph count small enough, send all data at once ushort* pGlyphIndices = stackalloc ushort[glyphCount]; for (int i = 0; i < glyphCount; ++i) { pGlyphIndices[i] = _glyphIndices[i]; } channel.AppendCommandData((byte*)pGlyphIndices, glyphCount * sizeof(ushort)); } else { // glyph count is not small, use per-glyph transmitting for (int i = 0; i < glyphCount; ++i) { ushort glyphIndex = _glyphIndices[i]; channel.AppendCommandData((byte*)&glyphIndex, sizeof(ushort)); } } } } channel.EndCommand(); } } ////// Gather flags that affect: /// - glyph run rendering /// - glyph rasterization /// - the way how glyph run data is packed /// private UInt16 ComposeFlags() { UInt16 flags = 0; if (_glyphTypeface.FontTechnology != FontTechnology.PostscriptOpenType) { flags |= (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunIsTrueType; FontFaceLayoutInfo.RenderingHints renderingHints = _glyphTypeface.RenderingHints; // When dealing with East Asian fonts containing embedded bitmaps // we apply a special 6x5 mode with hinting at em square and enhanced constract algorithms. if (renderingHints == FontFaceLayoutInfo.RenderingHints.LegacyEastAsian) { flags |= (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunForceVAA | (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunVerticalDropOut | (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunOverContrast; } else { flags |= (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunHinting; } } else { // For CFF fonts we always use 6x5 overscale hinted mode, because stem thickening happens in the CFF rasterizer. flags |= (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunHinting | (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunForceVAA; } StyleSimulations styleSimulations = _glyphTypeface.StyleSimulations; if ((styleSimulations & StyleSimulations.BoldSimulation) != 0) flags |= (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunBoldSimulation; if ((styleSimulations & StyleSimulations.ItalicSimulation) != 0) flags |= (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunItalicSimulation; if (IsSideways) flags |= (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunSideways; // if ((_bidiLevel & 1) == 0) flags |= (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunIsLeftToRight; if (_glyphOffsets != null && _glyphOffsets.Count != 0) flags |= (UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunHasYPositions; // Encode font contrast adjustment into 3 bits masked by MilGlyphRunPrecontrastLevel. UInt16 fontContrastAdjustment = (UInt16)(_glyphTypeface.FontContrastAdjustment + (int)MIL_GLYPHRUN_FLAGS.MilGlyphRunPrecontrastOffset); // If the resulting value spills outside the mask, this indicates a code bug in the GlyphTypeface code. Debug.Assert(((fontContrastAdjustment << (int)MIL_GLYPHRUN_FLAGS.MilGlyphRunPrecontrastShift) & ~(UInt16)MIL_GLYPHRUN_FLAGS.MilGlyphRunPrecontrastLevel) == 0); flags |= (UInt16)(fontContrastAdjustment << (int)MIL_GLYPHRUN_FLAGS.MilGlyphRunPrecontrastShift); return flags; } #endregion DUCE.IResource implementation #region Hit testing ////// Given a code point index in the caret stop array, finds the nearest pair of caret stops. /// /// Character index to start the search from. Doesn't have to be snapped. /// GlyphRun CaretStops array. Guaranteed to be non-null. /// Nearest caret stop index, or -1 if there are no caret stops. /// Code points until the next caret stop, or -1 if there is no next caret stop. private void FindNearestCaretStop( int characterIndex, IListcaretStops, out int caretStopIndex, out int codePointsUntilNextStop) { caretStopIndex = -1; codePointsUntilNextStop = -1; if (characterIndex < 0 || characterIndex >= caretStops.Count) return; // Find the closest caret stop at the character index or to the left of it. for (int i = characterIndex; i >= 0; --i) { if (caretStops[i]) { caretStopIndex = i; break; } } // Couldn't find a caret stop at the character index or to the left of it. // Search to the right. if (caretStopIndex == -1) { for (int i = characterIndex + 1; i < caretStops.Count; ++i) { if (caretStops[i]) { caretStopIndex = i; break; } } } // No caret stops found, the glyph run is not hit testable. if (caretStopIndex == -1) { return; } for (int lastStop = caretStopIndex + 1; lastStop < caretStops.Count; ++lastStop) { if (caretStops[lastStop]) { // There is a next caret stop. codePointsUntilNextStop = lastStop - caretStopIndex; return; } } // There is no next caret stop. } /// /// This class implements behavior of a Boolean list that contains all true values. /// This allows us to have a single code path in hit testing API. /// private class DefaultCaretStopList : IList{ public DefaultCaretStopList(int codePointCount) { _count = codePointCount + 1; } #region IList Members public int IndexOf(bool item) { throw new NotSupportedException(); } public void Insert(int index, bool item) { throw new NotSupportedException(); } public bool this[int index] { get { return true; } set { throw new NotSupportedException(); } } public void RemoveAt(int index) { throw new NotSupportedException(); } #endregion #region ICollection Members public void Add(bool item) { throw new NotSupportedException(); } public void Clear() { throw new NotSupportedException(); } public bool Contains(bool item) { throw new NotSupportedException(); } public void CopyTo(bool[] array, int arrayIndex) { throw new NotSupportedException(); } public int Count { get { return _count; } } public bool IsReadOnly { get { return true; } } public bool Remove(bool item) { throw new NotSupportedException(); } #endregion #region IEnumerable Members IEnumerator IEnumerable .GetEnumerator() { throw new NotSupportedException(); } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { throw new NotSupportedException(); } #endregion private int _count; } /// /// This class implements behavior of a 1:1 cluster map. /// This allows us to have a single code path in hit testing API. /// private class DefaultClusterMap : IList{ public DefaultClusterMap(int count) { _count = count; } #region IList Members public int IndexOf(ushort item) { throw new NotSupportedException(); } public void Insert(int index, ushort item) { throw new NotSupportedException(); } public ushort this[int index] { get { return (ushort)index; } set { throw new NotSupportedException(); } } public void RemoveAt(int index) { throw new NotSupportedException(); } #endregion #region ICollection Members public void Add(ushort item) { throw new NotSupportedException(); } public void Clear() { throw new NotSupportedException(); } public bool Contains(ushort item) { throw new NotSupportedException(); } public void CopyTo(ushort[] array, int arrayIndex) { throw new NotSupportedException(); } public int Count { get { return _count; } } public bool IsReadOnly { get { return true; } } public bool Remove(ushort item) { throw new NotSupportedException(); } #endregion #region IEnumerable Members IEnumerator IEnumerable .GetEnumerator() { throw new NotSupportedException(); } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { throw new NotSupportedException(); } #endregion private int _count; } #endregion Hit testing #region ISupportInitialize interface for Xaml serialization void ISupportInitialize.BeginInit() { if (IsInitialized) { // Cannot initialize a GlyphRun that is completely initialized. throw new InvalidOperationException(SR.Get(SRID.OnlyOneInitialization)); } if (IsInitializing) { // Cannot initialize a GlyphRun that is already being initialized. throw new InvalidOperationException(SR.Get(SRID.InInitialization)); } IsInitializing = true; } void ISupportInitialize.EndInit() { if (!IsInitializing) { // Cannot EndInit a GlyphRun that is not being initialized. throw new InvalidOperationException(SR.Get(SRID.NotInInitialization)); } // // Fully initilize the GlyphRun. The method will check for consistency // between all the properties. // Initialize( _glyphTypeface, _bidiLevel, (_flags & GlyphRunFlags.IsSideways) != 0, _renderingEmSize, _glyphIndices, _baselineOrigin, (_advanceWidths == null ? null : new ThousandthOfEmRealDoubles(_renderingEmSize, _advanceWidths)), (_glyphOffsets == null ? null : new ThousandthOfEmRealPoints(_renderingEmSize, _glyphOffsets)), _characters, _deviceFontName, _clusterMap, _caretStops, _language, true // throwIfOverflow ); // User should be able to fix errors that are only caught at EndInit() time. So set Initializing flag to // false after Initialization succeeds. IsInitializing = false; } private void CheckInitialized() { if (!IsInitialized) { throw new InvalidOperationException(SR.Get(SRID.InitializationIncomplete)); } // Ensure the bits are set consistently. The object cannot be in both states. Debug.Assert(!IsInitializing); } private void CheckInitializing() { if (!IsInitializing) { throw new InvalidOperationException(SR.Get(SRID.NotInInitialization)); } // Ensure the bits are set consistently. The object cannot be in both states. Debug.Assert(!IsInitialized); } private bool IsInitializing { get { return (_flags & GlyphRunFlags.IsInitializing) != 0; } set { if (value) { _flags |= GlyphRunFlags.IsInitializing; } else { _flags &= (~GlyphRunFlags.IsInitializing); } } } private bool IsInitialized { get { return (_flags & GlyphRunFlags.IsInitialized) != 0; } set { if (value) { _flags |= GlyphRunFlags.IsInitialized; } else { _flags &= (~GlyphRunFlags.IsInitialized); } } } #endregion //----------------------------------------------------- // // Private Enumerations // //------------------------------------------------------ #region Private Enumerations /// /// Glyph run flags. /// [Flags] private enum GlyphRunFlags : byte { ////// No flags set. /// It also represents the state in which the GlyphRun has not been initialized. /// At this state, all operations on the object would cause InvalidOperationException. /// The object can only transit to 'IsInitializing' state with BeginInit() call. /// None = 0x00, ////// Set to display the GlyphRun sideways. /// IsSideways = 0x01, ////// Set if the glyph run contains anything to be drawn. /// HasInk = 0x02, ////// Set if the HasInk bit above has already been calculated and cached. /// HasInkIsCached = 0x04, ////// The state in which the GlyphRun object is fully initialized. At this state the object /// is fully functional. There is no valid transition out of the state. /// IsInitialized = 0x08, ////// The state in which the GlyphRun is being initialized. At this state, user can /// set values into the required properties. The object can only transit to 'IsInitialized' state /// with EndInit() call. /// IsInitializing = 0x10, ////// Caching ink bounds /// CacheInkBounds = 0x20, } #endregion Private Enumerations //----------------------------------------------------- // // Private Fields // //----------------------------------------------------- #region Private Fields private Point _baselineOrigin; private GlyphRunFlags _flags; private double _renderingEmSize; private IList_glyphIndices; private IList _advanceWidths; private IList _glyphOffsets; private int _bidiLevel; private GlyphTypeface _glyphTypeface; private IList _characters; private IList _clusterMap; private IList _caretStops; private XmlLanguage _language; private string _deviceFontName; private object _inkBoundingBox; // Used when CacheInkBounds is on // the sine of 20 degrees private const double Sin20 = 0.34202014332566873304409961468226; // This is the precision that is used to decide that glyph metrics are equal, // for example when detecting blank glyphs. // The chosen value is greater than typical floating point precision loss // but smaller than typical design font unit (1/1024th or 1/2048th). private const double InkMetricsEpsilon = 0.0000001; // Dummy font hinting size private const double DefaultFontHintingSize = 12.0; // Tolerance for flattening Bezier curves when calling GetOutlinedPathGeometry. private const double RelativeFlatteningTolerance = 0.01; // The constants that delimit glyph run size. // Should correspond to unmanaged ones in GlyphRunCore.h. internal const int MaxGlyphCount = 0xFFFF; internal const int OverscaledCoordMax = 0xFFFFF; internal const int GeometryThreshold = 100; internal const int MaxOvescale = 8; internal const int MaxOverscaledBitsInGlyphRun = 800000000; // 800 MBits = 100 MBytes internal const int MaxStackAlloc = 1024; // ScaleGrid: allowed rasterization scales for scale animation. // Numbers are choosen heuristically. // Previous heuristic used powers of two (4, 8, 16, 32, 64). // It turned out that the row above does not provide desired quality. // Transition between neighbouring values on small scales are visible // as sudden "blur blast". To suppress it, we need to decrease grid // step. However we don't want to increase the burden of extra // rasterization on high end. The row below was obtained by following // formulas: // ScaleGrid[0] = 5; // ScaleGrid[i+1] = ScaleGrid[i] * (1.3 + 0.1*i); private static readonly double[] ScaleGrid = { 5, 6.5, 9.1, 13.7, 21.8, 37.1, 66.8 }; private int _parentCount; #endregion Private Fields } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- ReadOnlyNameValueCollection.cs
- GridViewUpdatedEventArgs.cs
- GlyphingCache.cs
- HttpHeaderCollection.cs
- MLangCodePageEncoding.cs
- DataGridViewRowPostPaintEventArgs.cs
- NumericPagerField.cs
- SmtpDigestAuthenticationModule.cs
- SignatureDescription.cs
- CodeTypeDelegate.cs
- COSERVERINFO.cs
- BoundConstants.cs
- Identity.cs
- WebAdminConfigurationHelper.cs
- WebEvents.cs
- ObjectHelper.cs
- ImageSource.cs
- LabelLiteral.cs
- PopupRoot.cs
- Keyboard.cs
- Constant.cs
- ProxyGenerator.cs
- Certificate.cs
- _NetworkingPerfCounters.cs
- ReturnValue.cs
- HtmlInputSubmit.cs
- XmlIncludeAttribute.cs
- ImageDesigner.cs
- MemoryRecordBuffer.cs
- SecurityUniqueId.cs
- AnnotationStore.cs
- GeneratedContractType.cs
- DbConnectionOptions.cs
- StringFreezingAttribute.cs
- XhtmlBasicObjectListAdapter.cs
- XmlSequenceWriter.cs
- TransportDefaults.cs
- TreeViewItemAutomationPeer.cs
- iisPickupDirectory.cs
- XmlSchemaFacet.cs
- SessionStateContainer.cs
- SmtpCommands.cs
- SR.Designer.cs
- Range.cs
- AdobeCFFWrapper.cs
- GeometryValueSerializer.cs
- SqlHelper.cs
- TableItemStyle.cs
- InstanceHandleConflictException.cs
- messageonlyhwndwrapper.cs
- TemplateBuilder.cs
- SqlUserDefinedAggregateAttribute.cs
- SafeEventLogReadHandle.cs
- ConnectionStringsExpressionBuilder.cs
- FlowDocumentPage.cs
- CqlLexer.cs
- Command.cs
- SafeLibraryHandle.cs
- ApplicationSecurityManager.cs
- FormsAuthenticationCredentials.cs
- PolyQuadraticBezierSegment.cs
- ValidationError.cs
- SharedStatics.cs
- RegistrationServices.cs
- EditorBrowsableAttribute.cs
- ResourceAttributes.cs
- XmlChildEnumerator.cs
- DynamicDataRouteHandler.cs
- ViewUtilities.cs
- KnownTypesHelper.cs
- EventLogQuery.cs
- LocationSectionRecord.cs
- EmbossBitmapEffect.cs
- IdentityReference.cs
- QilReference.cs
- XsltException.cs
- WmlMobileTextWriter.cs
- CellNormalizer.cs
- DeviceContexts.cs
- NumericUpDown.cs
- DragEventArgs.cs
- TokenBasedSet.cs
- PropertyItemInternal.cs
- PrePrepareMethodAttribute.cs
- InternalDispatchObject.cs
- XmlBoundElement.cs
- WindowsListViewItem.cs
- ConfigurationPropertyCollection.cs
- StorageMappingFragment.cs
- TypeCacheManager.cs
- XPathArrayIterator.cs
- ToolStripDropDownButton.cs
- XmlName.cs
- XmlUtf8RawTextWriter.cs
- ButtonFieldBase.cs
- DynamicQueryableWrapper.cs
- ManipulationDelta.cs
- ExtendedPropertyCollection.cs
- MaskedTextBox.cs
- LicenseManager.cs