Code:
/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Core / MS / Internal / FontCache / FamilyCollection.cs / 1 / FamilyCollection.cs
//---------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // Description: FamilyCollection font cache element class is responsible for // storing the mapping between a folder and font families in it. // // History: // 07/23/2003 : [....] - Big rewrite to change cache structure. // 03/04/2004 : [....] - Cache layout and interface changes for font enumeration. // 11/04/2005 : [....] - Refactoring to support font disambiguation. // //--------------------------------------------------------------------------- using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Security; using System.Security.Permissions; using System.Windows; using System.Windows.Markup; // for XmlLanguage using System.Windows.Media; using MS.Win32; using MS.Utility; using MS.Internal; using MS.Internal.FontFace; using MS.Internal.PresentationCore; // Since we disable PreSharp warnings in this file, we first need to disable warnings about unknown message numbers and unknown pragmas. #pragma warning disable 1634, 1691 namespace MS.Internal.FontCache { ////// FamilyCollection font cache element class is responsible for /// storing the mapping between a folder and font families in it /// [FriendAccessAllowed] internal class FamilyCollection : IFontCacheElement { //----------------------------------------------------- // // Constructors // //----------------------------------------------------- #region Constructors ////// Creates a font family collection cache element from a canonical font family reference. /// /// Absolute Uri of a folder /// Indicates folderUri is constructed internally by WPF code /// and specifies the Windows Fonts folder or a file in the Windows Fonts folder. ////// Critical - The ability to control the place fonts are loaded from is critical. /// /// The folderUri parameter is critical as it may contain privileged information /// (i.e., the location of Windows Fonts); it is passed to the FontSourceCollection /// constructor which is declared critical and guarantees not to disclose the URI. /// /// The isWindowsFonts parameter is critical for set as it is used to make a /// security decision (i.e., whether to assert read access); it is passed to the /// FontSourceCollection constructor and also assigned to the _isWindowsFonts /// field which is declared critical. /// /// Callers should only specify isWindowsFonts=true if the URI comes from internal /// WPF code, NOT if it comes from the client. E.g., we want FontFamily="Arial" and /// FontFamily="arial.ttf#Arial" to work in partial trust, therefore isWindowsFonts /// is true so we assert read access. But FontFamily="file:///c:/windows/fonts/#Arial" /// should NOT work in partial trust (even -- or especially -- if the URI is right), /// as this would enable partial trust clients to guess the location of Windows Fonts /// through trial and error. /// [SecurityCritical] internal FamilyCollection(Uri folderUri, bool isWindowsFonts) { _isWindowsFonts = isWindowsFonts; _fontFolder = new FontSourceCollection(folderUri, isWindowsFonts); _fontFolderUriString = _fontFolder.GetUriString(); } ////// This contructor is for shared cache reconstructing element from a key. /// /// Key containing folder name. ////// Critical - Calls into the critical RetrieveKey method. /// [SecurityCritical] internal FamilyCollection(CheckedPointer key) { RetrieveKey(key); Debug.Assert(_fontFolder != null); } #endregion Constructors //------------------------------------------------------ // // Internal Methods // //----------------------------------------------------- #region Internal methods ////// Critical: Calls into probe which is critical and also has unsafe code blocks /// although functionally it simply does a string compare /// TreatAsSafe: This code is safe to call functionally it /// is ok to expose since it only compares two pointers also Probe does the pointer checks /// [SecurityCritical,SecurityTreatAsSafe] bool IFontCacheElement.Match(CheckedPointer p) { unsafe { Layout * l = (Layout *)p.Probe(0, sizeof(Layout)); if (Util.StringSize(_fontFolderUriString) != l->folderUriSize) return false; if (_fontFolder.TimeStamp != l->timeStamp) return false; if (_isWindowsFonts != l->isDefaultSystemFontsFolder) return false; return Util.StringEqualIgnoreCase(p + sizeof(Layout), _fontFolderUriString); } } ////// Critical: This code reads from the critical member _fontFolderUriString. /// TreatAsSafe: This code is a part of the internal font cache code that sends miss reports /// to the service process. The critical value is not exposed via public managed APIs. /// [SecurityCritical,SecurityTreatAsSafe] private void StoreKeyInternal(CheckedPointer d, out int realSize) { d.WriteBool(_isWindowsFonts); d += (Layout.OffsetToFolderUriSize - Layout.OffsetToKey); realSize = (Layout.OffsetToFolderUriSize - Layout.OffsetToKey) + Util.StringAndLengthCopyToCheckedPointer(d, _fontFolderUriString); } public void StoreKey(CheckedPointer d, out int realSize) { Debug.Assert(!_fontFolder.IsAppSpecific); StoreKeyInternal(d, out realSize); } ////// Critical: Calls critical GetUriString method. /// [SecurityCritical] public void RetrieveKey(CheckedPointer p) { _isWindowsFonts = p.ReadBool(); p += (Layout.OffsetToFolderUriSize - Layout.OffsetToKey); string folderName = Util.StringAndLengthCopyFromCheckedPointer(p); _fontFolder = new FontSourceCollection(new Uri(folderName, UriKind.Absolute), _isWindowsFonts); _fontFolderUriString = _fontFolder.GetUriString(); } int IFontCacheElement.Type { get { return 2; } } bool IFontCacheElement.IsAppSpecific { get { return _fontFolder.IsAppSpecific; } } ////// Critical: Calls into probe which is critical and also has unsafe code blocks /// TreatAsSafe: This code is safe to call functionally it /// is ok to expose since it only compares two pointers also Probe does the pointer checks /// [SecurityCritical,SecurityTreatAsSafe] void IFontCacheElement.GetData(CheckedPointer block, ElementCacher cacher) { unsafe { _data = (Layout *)block.Probe(0, sizeof(Layout)); } _cacher = cacher; } ////// Critical: Accesses _fontFolderUriString critical field. /// TreatAsSafe: The field is used only to compute its size. /// int IFontCacheElement.Size { [SecurityCritical, SecurityTreatAsSafe] get { unsafe { return sizeof(Layout) + Util.StringSize(_fontFolderUriString); } } } int IFontCacheElement.GetHashCode() { return HashFn.HashScramble(_fontFolder.GetHashCode()); } ////// Critical:This code calls into unsafe code blocks. /// TreatAsSafe: This code is ok to expose.Also the call to probe is boundary checked. /// [SecurityCritical,SecurityTreatAsSafe] void IFontCacheElement.AddToCache(CheckedPointer newPointer, ElementCacher cacher) { _cacher = cacher; Invariant.Assert(!newPointer.IsNull); unsafe { _data = (Layout*)newPointer.Probe(0, sizeof(Layout)); Debug.Assert(_fontFolder.TimeStamp != 0); _data->timeStamp = _fontFolder.TimeStamp; int keySize; StoreKeyInternal(newPointer + Layout.OffsetToKey, out keySize); // Make sure folderUriSize doesn't get out of sync with OffsetToKey. // We use Debug.Assert instead of Invariant.Assert because // this condition will result in an immediate crash on debug builds. Debug.Assert(_data->folderUriSize == Util.StringSize(_fontFolderUriString)); // This list is needed to pass intermediate family collection from BuildFamilyList to AddFamiliesToCache. // The keys are of type LocalizedName, the values are of type BaseFamily. SortedDictionaryfamilyNameList; // This dictionary contains mappings from strings to cache offsets that point to them. // The goal is to reduce cache size by having strings stored only once. // This forces us to save all used strings on the 1st pass (BuildFamilyList), // so that we have all names in the cache by the time we add individual families there. // The strings come from 3 different places: // 1. Family names (both composite and physical). // 2. Physical face names. // 3. Composite FontFamilyMap tables. // PERF: The values in the dictionary are of type 'int', we use 'object' to avoid // instantiating SortedList template with the value type int. // We can move from object to int if CLR pre-instantiates SortedList of object, int in its own DLLs. // We have to use sorted list instead of a regular dictionary because we want to be able to update values // without reordering the list or creating new elements. SortedList frequentStrings; // List of physical and composite family objects. List familyList; BuildFamilyList(out familyList, out familyNameList, out frequentStrings); if (familyList == null) { _data->offsetToCachedFamiliesAndNames = _cacher.Alloc( CachedFamiliesAndNames.GetTotalSize(0, 0)); CachedFamiliesAndNames* cachedFamiliesAndNames = GetCachedFamiliesAndNames(); cachedFamiliesAndNames->numberOfFamilyNames = 0; cachedFamiliesAndNames->numberOfFontFamilies = 0; } else { FontDifferentiator.ResolveFaceConflicts(familyList, frequentStrings, this); _data->offsetToCachedFamiliesAndNames = _cacher.Alloc( CachedFamiliesAndNames.GetTotalSize(familyList.Count, familyNameList.Count)); CachedFamiliesAndNames* cachedFamiliesAndNames = GetCachedFamiliesAndNames(); cachedFamiliesAndNames->numberOfFamilyNames = familyNameList.Count; cachedFamiliesAndNames->numberOfFontFamilies = familyList.Count; Debug.Assert(cachedFamiliesAndNames->numberOfFamilyNames >= cachedFamiliesAndNames->numberOfFontFamilies); AddFamiliesToCache( CachedFamiliesAndNames.GetCachedFamilyNames(cachedFamiliesAndNames), CachedFamiliesAndNames.GetCachedFamilies(cachedFamiliesAndNames), familyList.Count, familyNameList, frequentStrings); } } } /// /// Critical: This function accesses critical ElementCacher object. /// TreatAsSafe: It uses the object only to obtain the obsolete flag. /// internal bool IsObsolete { [SecurityCritical, SecurityTreatAsSafe] get { return _cacher.IsObsolete(); } } ////// Critical:This function has unsafe code blocks and returns cached font families. It also accesses cacher. /// TreatAsSafe: This information is ok to give out /// [SecurityCritical,SecurityTreatAsSafe] internal unsafe CachedFontFamily LookupFamily( string familyName, ref FontStyle fontStyle, ref FontWeight fontWeight, ref FontStretch fontStretch ) { if (familyName == null || familyName.Length == 0) return new CachedFontFamily(this, null, 0); CachedName * cachedFamilyNames; int familyNameCount; GetCachedFamilyNames(out cachedFamilyNames, out familyNameCount); int familyNameIndex = 0; int familyOffset = LookupLongestName( familyName, ref familyNameIndex, cachedFamilyNames, familyNameCount ); if (familyOffset == Util.nullOffset) return new CachedFontFamily(this, null, 0); // The function guarantees that familyNameIndex is valid before calling LookupLongestName, // and LookupLongestName guarantees that familyNameIndex is either within the string or at the end of it // by tracking searchNameIndex and longestPartialMatch variables. // This is a performance sensitive code path, and we perform redundant checks only in the debug version. Debug.Assert(0 <= familyNameIndex && familyNameIndex <= familyName.Length); CheckedPointer mapping = _cacher.Mapping; CachedFamily* family = (CachedFamily*)mapping.Probe(familyOffset, sizeof(CachedFamily)); if (familyNameIndex == familyName.Length) return new CachedFontFamily(this, family, mapping.Size - familyOffset); // see if the input string contains style information // The face name must be separated from family name by a single space. // We set the same requirement as GDI. if (familyName[familyNameIndex] == ' ' && family->familyType == FamilyCollection.FamilyType.Physical) { ++familyNameIndex; CachedPhysicalFamily* cachedPhysicalFamily = (CachedPhysicalFamily*)family; CachedPhysicalFace* cachedFace = LookupFace( familyName, ref familyNameIndex, cachedPhysicalFamily ); // There should be no trailing characters after the style name. if (cachedFace != null && familyNameIndex == familyName.Length) { fontStyle = cachedFace->style; fontWeight = cachedFace->weight; fontStretch = cachedFace->stretch; return new CachedFontFamily(this, family, mapping.Size - familyOffset); } } return new CachedFontFamily(this, null, 0); } ////// Critical:This code accesses a pointer and passes it to a critical function /// [SecurityCritical] internal unsafe string GetFamilyName(CachedFamily * cachedFamily) { return new LocalizedNameDictionary(this, cachedFamily).OrdinaryName; } private unsafe struct FamilyEnumerator : IEnumerator, IEnumerable { private int _familyCount; /// /// Critical: This code holds reference to a pointer /// [SecurityCritical] private unsafe int * _familyPointers; private FamilyCollection _familyCollection; private int _currentFamily; ////// Critical: This code stores reference to a pointer and accesses _Data which is critical /// TreatAsSafe: The pointer is not exposed and the member that holds the pointer is critical /// [SecurityCritical,SecurityTreatAsSafe] internal FamilyEnumerator(FamilyCollection familyCollection) { _familyCollection = familyCollection; _currentFamily = -1; unsafe { familyCollection.GetCachedFamilies(out _familyPointers, out _familyCount); } } #region IEnumeratorMembers public bool MoveNext() { ++_currentFamily; if (_currentFamily >= _familyCount) { // prevent cycling _currentFamily = _familyCount; return false; } return true; } /// /// Critical: This function uses pointers and calls critical CachedFontFamily constructor /// TreatAsSafe: This information is ok to expose /// CachedFontFamily IEnumerator.Current { [SecurityCritical,SecurityTreatAsSafe] get { // Disable "Property get methods should not throw exceptions" because IEnumerator<>.Current semantics // are to throw an exception if enumerator is positioned before the first or after the last element. #pragma warning disable 6503 if (_currentFamily < 0 || _currentFamily >= _familyCount) { throw new InvalidOperationException(); } CheckedPointer familyPointer = _familyCollection._cacher.Mapping + _familyPointers[_currentFamily]; unsafe { return new CachedFontFamily( _familyCollection, (CachedFamily*)familyPointer.Probe(0, sizeof(CachedFamily)), familyPointer.Size ); } #pragma warning restore 6503 } } #endregion #region IEnumerator Members object IEnumerator.Current { get { return ((IEnumerator )this).Current; } } public void Reset() { _currentFamily = -1; } #endregion #region IDisposable Members public void Dispose() {} #endregion #region IEnumerable Members IEnumerator IEnumerable .GetEnumerator() { return this as IEnumerator ; } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable )this).GetEnumerator(); } #endregion } internal IEnumerable GetFontFamilies() { return new FamilyEnumerator(this); } /// /// Critical: This function has an unsafe code block. /// SecurityTreatAsSafe: This function only returns the number of font families in a folder. /// internal int FamilyCount { [SecurityCritical, SecurityTreatAsSafe] get { unsafe { return GetCachedFamiliesAndNames()->numberOfFontFamilies; } } } ////// Critical:This function has unsafe code blocks and returns cached font face. It also accesses cacher. /// It returns a pointer /// [SecurityCritical] private unsafe CachedPhysicalFace * LookupFace( string styleName, ref int styleNameIndex, CachedPhysicalFamily * cachedPhysicalFamily ) { Debug.Assert(cachedPhysicalFamily->familyType == FamilyType.Physical); int faceOffset = LookupLongestName( styleName, ref styleNameIndex, (CachedName *)((byte *)cachedPhysicalFamily + sizeof(CachedPhysicalFamily)), cachedPhysicalFamily->numberOfFaceNames ); if (faceOffset == Util.nullOffset) return null; CachedPhysicalFace * face = (CachedPhysicalFace *)_cacher.Mapping.Probe(faceOffset,sizeof(CachedPhysicalFace)); return face; } ////// Critical: This accesses a pointer and is unsafe because it returns a URI /// [SecurityCritical] internal unsafe Uri GetFontUri(CachedPhysicalFace * cachedFace) { string fontUri = Util.StringCopyFromUncheckedPointer( (byte*)cachedFace + sizeof(CachedPhysicalFace), cachedFace->fontUriSize ); return Util.CombineUriWithFaceIndex(fontUri, cachedFace->faceIndex); } ////// Critical:This function has unsafe code blocks and returns cached font face. It also accesses cacher. /// TreatAsSafe: This infomation is ok to give out. /// CachedFontFamily cannot be created with Null family. Which ensures validity of the pointer. /// Taking that into account there is also the risk of dereferencing from elementcacher which is mitigated /// by bounds checking in element cacher /// [SecurityCritical,SecurityTreatAsSafe] internal unsafe CachedFontFace GetCachedFace(CachedFontFamily cachedFamily, int faceIndex) { Invariant.Assert(faceIndex < cachedFamily.NumberOfFaces); if (cachedFamily.IsPhysical) { CachedPhysicalFamily * cachedPhysicalFamily = cachedFamily.PhysicalFamily; int * facePointers = (int *)((byte *)cachedPhysicalFamily + sizeof(CachedPhysicalFamily) + cachedPhysicalFamily->numberOfFaceNames * sizeof(CachedName) ); CheckedPointer facePointer = _cacher.Mapping + facePointers[faceIndex]; return new CachedFontFace( this, (CachedFace*)facePointer.Probe(0, sizeof(CachedFace)), facePointer.Size ); } Debug.Assert(cachedFamily.IsComposite); CachedCompositeFamily * cachedCompositeFamily = cachedFamily.CompositeFamily; int faceOffset = cachedCompositeFamily->OffsetToFamilyTypeface + (faceIndex * sizeof(CachedCompositeFace)); CheckedPointer cachedFace = cachedFamily.CheckedPointer + faceOffset; return new CachedFontFace( this, (CachedFace*)cachedFace.Probe(0, sizeof(CachedFace)), cachedFace.Size ); } ////// Critical:This function has unsafe code blocks . It also accesses cacher and returns a pointer. /// Functionally it looks up the CachedFamilyMap and retrieves the ranges that match the criteria. /// The risk here is of being called with bogus pointers since there is minimal pointer checking while dereferencing. /// The risk is substantially mitigated by the bounds checking employed in ElementCacher for the return value. /// [SecurityCritical] internal unsafe ushort* GetFamilyMapRanges(CachedCompositeFamily* cachedFamily, CultureInfo culture, out int lengthOfRanges) { CachedFamilyMapRangeList* rangeList = (CachedFamilyMapRangeList*)((byte*)cachedFamily + cachedFamily->OffsetToFamilyMapRangeLists); CachedFamilyMapRangeList* defaultList = rangeList + (cachedFamily->numberOfFamilyMapRangeLists - 1); // Look for the first range list with a matching culture, according to the usual matching rules. // The lists are sorted such that the most specific cultures come first. for (; rangeList != defaultList; ++rangeList) { XmlLanguage familyMapLanguage = GetFamilyMapLanguageFromOffset(rangeList->offsetToIetfLanguageTag); if (familyMapLanguage.RangeIncludes(culture)) break; } // We either found a matching culture or rangeList == defaultList. lengthOfRanges = rangeList->lengthOfRanges; return (ushort*)((byte*)rangeList + rangeList->offsetToRanges); } ////// Critical:This function has unsafe code blocks and returns a pointer /// Functionally it returns that family map for a character. The risk is /// in dereferencing the ranges and CachedFamilyPointers since they could be /// bogus. The calls lower down to InRange are inherently safe. /// [SecurityCritical] internal unsafe CachedFamilyMap* GetFamilyMapOfChar( CachedCompositeFamily* cachedFamily, ushort* ranges, int lengthOfRanges, int ch) { CachedFamilyMap* familyMaps = (CachedFamilyMap*)((byte*)cachedFamily + cachedFamily->OffsetToFamilyMaps); for (int i = 0; i < lengthOfRanges; i += 2) { int begin = ranges[i]; int end = ranges[i + 1]; for (int j = begin; j < end; ++j) { if (InRange(familyMaps + j, ch)) return familyMaps + j; } } return null; } ////// Critical:This code accesses a pointer and passes it to a critical function /// [SecurityCritical] internal unsafe IDictionaryGetLocalizedNameDictionary(CachedFamily* cachedFamily) { return new LocalizedNameDictionary(this, cachedFamily); } #endregion Internal methods //------------------------------------------------------ // // Private Methods // //------------------------------------------------------ #region Private Methods /// /// Critical - as this calls FontSource.GetStream which can read files in Windows /// fonts directory under an elevation. /// Safe - as this just adds the data to the font cache. /// [SecurityCritical, SecurityTreatAsSafe] private void AddCompositeFamilyToList(FontSource fontSource, ListfamilyList, SortedDictionary familyNameList, SortedList frequentStrings) { CompositeFontInfo fontInfo; using (Stream fileStream = fontSource.GetStream()) { fontInfo = CompositeFontParser.LoadXml(fileStream); } foreach (KeyValuePair familyEntry in fontInfo.FamilyNames) { LocalizedName familyName = new LocalizedName(familyEntry.Key, familyEntry.Value); // If there is already a font with the same familyName, ignore the subsequent ones. if (familyNameList.ContainsKey(familyName)) return; } CompositeFamily compositeFamily = new CompositeFamily(); familyList.Add(compositeFamily); compositeFamily.compositeFontInfo = fontInfo; // add family names to the list foreach (KeyValuePair familyEntry in fontInfo.FamilyNames) { LocalizedName familyName = new LocalizedName(familyEntry.Key, familyEntry.Value); familyNameList.Add(familyName, compositeFamily); SaveLocalizedString(familyName, frequentStrings); compositeFamily.familyNames.Add(familyName, null); } // add cmap table cultures to the culture list; FamilyMapLanguages may be // null if all the family maps are culture-independent if (fontInfo.FamilyMapLanguages != null) { foreach (XmlLanguage familyMapLanguage in fontInfo.FamilyMapLanguages) { SaveRegularString(familyMapLanguage.IetfLanguageTag, frequentStrings); } } } internal void SaveLocalizedString(LocalizedName name, SortedList frequentStrings) { SaveRegularString(name.Language.IetfLanguageTag, frequentStrings); SaveRegularString(name.Name, frequentStrings); } private void SaveRegularString(string name, SortedList frequentStrings) { frequentStrings[name] = Util.nullOffset; } /// /// Critical - as this obtains UnmanagedMemoryStream from FontSource which is critical data. /// Safe - as this just adds the data to the font cache. /// [SecurityCritical,SecurityTreatAsSafe] private void AddPhysicalFamilyToList(FontSource fontSource, ListfamilyList, SortedDictionary familyNameList, SortedList frequentStrings) { // get all the faces out of this file and add them to the list TrueTypeFontDriver.BasicFontFaceInfo basicFontFaceInfo = new TrueTypeFontDriver.BasicFontFaceInfo(); UnmanagedMemoryStream pinnedFontSource = fontSource.GetUnmanagedStream(); try { TrueTypeFontDriver ttd = new TrueTypeFontDriver(pinnedFontSource, fontSource.Uri); for (int currentFace = 0; currentFace < ttd.NumFaces; ++currentFace) { ttd.SetFace(currentFace); // figure out font family and style bool skipFontDifferentiation; ttd.GetBasicFontFaceInfo(ref basicFontFaceInfo, out skipFontDifferentiation); // Name and style adjustment. if (!skipFontDifferentiation) { FontDifferentiator.AdjustFamilyAndFaceInformation( ref basicFontFaceInfo.nameTable, ref basicFontFaceInfo.style, ref basicFontFaceInfo.weight, ref basicFontFaceInfo.stretch ); } LocalizedName[] familyNames = basicFontFaceInfo.nameTable.familyNames; LocalizedName[] faceNames = basicFontFaceInfo.nameTable.faceNames; if (familyNames == null) { // If a font doesn't have preferred family names, // per OpenType spec we have to use legacy family names. familyNames = basicFontFaceInfo.nameTable.win32FamilyNames; if (familyNames == null) { // A valid font should contain at least one family name. return; } } if (faceNames == null) { // If a font doesn't have preferred face names, // per OpenType spec we have to use legacy face names. faceNames = basicFontFaceInfo.nameTable.win32faceNames; if (faceNames == null) { // A valid font should contain at least one face name. return; } } // See if other faces from this font family were already present. // If there was a physical face from this family, PhysicalFamily object // should have already been created, and we can use it instead of creating a new one. PhysicalFamily family = null; { // If there were any composite fonts using the same family name, // remove such composite font families from the cache altogether, // as we prefer physical to composite font families in case both are present. List compositeFamiliesToRemove = null; foreach (LocalizedName familyName in familyNames) { BaseFamily baseFamily; if (familyNameList.TryGetValue(familyName, out baseFamily)) { if (baseFamily is PhysicalFamily) { if (family == null) family = (PhysicalFamily)baseFamily; } else { Debug.Assert(baseFamily is CompositeFamily); CompositeFamily compositeFamily = (CompositeFamily)baseFamily; if (compositeFamiliesToRemove == null) compositeFamiliesToRemove = new List (); else { // Make sure we count each composite family only once. foreach (CompositeFamily olderFamily in compositeFamiliesToRemove) { if (Object.ReferenceEquals(compositeFamily, olderFamily)) continue; } } compositeFamiliesToRemove.Add(compositeFamily); } } } // Remove composite families that had the same family name. if (compositeFamiliesToRemove != null) { foreach (CompositeFamily compositeFamily in compositeFamiliesToRemove) { foreach (LocalizedName compositeFamilyName in compositeFamily.familyNames.Keys) { familyNameList.Remove(compositeFamilyName); } bool removed = familyList.Remove(compositeFamily); Debug.Assert(removed); } } } // this is the first time we encountered this family // create a new object to hold it if (family == null) { family = new PhysicalFamily(); familyList.Add(family); } // add new family names (if any) to the list foreach (LocalizedName familyName in familyNames) { familyNameList[familyName] = family; SaveLocalizedString(familyName, frequentStrings); family.familyNames[familyName] = null; } // now, add a new face to the family PhysicalFace face = new PhysicalFace(); face.fontUri = fontSource.GetUriString(); face.faceIndex = basicFontFaceInfo.faceIndex; // The original face is not simulated, we'll add simulated faces if necessary after analyzing all faces in this family. face.styleSimulations = StyleSimulations.None; face.style = basicFontFaceInfo.style; face.weight = basicFontFaceInfo.weight; face.stretch = basicFontFaceInfo.stretch; face.version = basicFontFaceInfo.nameTable.version; face.timestamp = fontSource.GetLastWriteTimeUtc(); face.symbol = basicFontFaceInfo.symbol; face.designEmHeight = basicFontFaceInfo.designEmHeight; face.designCellAscent = basicFontFaceInfo.designCellAscent; face.designCellDescent = basicFontFaceInfo.designCellDescent; face.designLineSpacing = basicFontFaceInfo.designLineSpacing; face.names = faceNames; family.typefaces.Add(face); } } catch (SEHException e) { throw Util.ConvertInPageException(fontSource, e); } finally { pinnedFontSource.Close(); } } /// /// Critical - as this call GetFiles which can read files in Windows /// fonts directory under an elevation. /// Safe - as this doesn't expose this information. This only works with /// font files in the windows fonts directory and that information /// is considered safe to expose. /// [SecurityCritical, SecurityTreatAsSafe] private void BuildFamilyList( out ListfamilyList, out SortedDictionary familyNameList, out SortedList frequentStrings) { familyNameList = null; frequentStrings = null; familyList = null; foreach (FontSource fontSource in _fontFolder) { // verify that we're dealing with a supported font extension string extension = Util.GetUriExtension(fontSource.Uri); bool isComposite; if (!Util.IsSupportedFontExtension(extension, out isComposite)) continue; // PERF: We create these data structures on demand because application specific font collection is often empty. if (familyNameList == null) { familyNameList = new SortedDictionary (LocalizedName.NameComparer); frequentStrings = new SortedList (StringComparer.OrdinalIgnoreCase); familyList = new List (); } // Parse the file and add data from it to the family list. // Note that for both physical and composite fonts // we eat certain exceptions in order to ignore malformed fonts. // Disable corresponding Presharp warning. #pragma warning disable 6502 try { if (isComposite) { AddCompositeFamilyToList(fontSource, familyList, familyNameList, frequentStrings); } else { AddPhysicalFamilyToList(fontSource, familyList, familyNameList, frequentStrings); } } // Skip malformed fonts for both local and shared cases. catch (FileFormatException) { } // Skip inaccessible fonts for local cache, re-throw for shared cache to make sure incomplete information // is not shared to the clients. catch (IOException) { if (_cacher.IsShared) throw; } catch (UnauthorizedAccessException) { if (_cacher.IsShared) throw; } catch (System.Net.WebException) { if (_cacher.IsShared) throw; } #pragma warning restore 6502 } } /// /// Critical:This function has unsafe code blocks ,it accesses element cacher and returns /// the next character in cache /// [SecurityCritical] private unsafe char GetCachedChar( CachedName* cachedName, int searchNameIndex) { byte* name = _cacher[cachedName->offsetToName]; int nameLength = *(int*)name; if (nameLength <= searchNameIndex * sizeof(char)) return '\0'; char c = *((char*)(name + sizeof(int)) + searchNameIndex); return Char.ToUpperInvariant(c); } ////// Critical:This function has unsafe code blocks ,it accesses element cacher and returns /// the next character in cache /// [SecurityCritical] private unsafe bool LookupNextCharacter( string searchName, int initialSearchNameIndex, int searchNameIndex, CachedName* cachedNames, ref int startIndex, ref int endIndex ) { char s = Char.ToUpperInvariant(searchName[searchNameIndex]); for (; ; ) { // we only have 1 or zero matches left if (startIndex + 1 >= endIndex) return false; int median = (startIndex + endIndex) / 2; char d = GetCachedChar(cachedNames + median, searchNameIndex - initialSearchNameIndex); int res = s - d; if (res < 0) { endIndex = median; } else if (res > 0) { startIndex = median + 1; } else { // we found a match, let's see if there are other matches int i; // move up to find a first non-match i = median; while (--i >= startIndex) { char c = GetCachedChar(cachedNames + i, searchNameIndex - initialSearchNameIndex); Debug.Assert(c <= s); if (c < s) break; } startIndex = i + 1; // move down to find a first non-match i = median; while (++i < endIndex) { char c = GetCachedChar(cachedNames + i, searchNameIndex - initialSearchNameIndex); Debug.Assert(c >= s); if (c > s) break; } endIndex = i; return true; } } } ////// This function performs incremental binary search until we reach no more than one match. /// /// /// The index of the current character in the search string. /// /// ///Offset to the data found. ////// Critical:This function has unsafe code blocks ,it accesses element cacher /// also it takes a pointer that it passes on without validating /// [SecurityCritical] private unsafe int LookupLongestName( string searchName, ref int searchNameIndex, CachedName* cachedNames, int numberOfCachedNames ) { Invariant.Assert(searchNameIndex >= 0); // startIndex is inclusive int startIndex = 0; // endIndex is exclusive int endIndex = numberOfCachedNames; // remember the initial value of searchNameIndex int initialSearchNameIndex = searchNameIndex; int longestPartialMatchIndex = searchNameIndex; int longestPartialMatchOffset = Util.nullOffset; for (; ; ) { // we ran out of characters in the search string if (searchNameIndex == searchName.Length) break; if (!LookupNextCharacter( searchName, initialSearchNameIndex, searchNameIndex, cachedNames, ref startIndex, ref endIndex )) break; ++searchNameIndex; // see if the first match is null terminated // in this case we already have a partial match char nextCharacter = GetCachedChar(cachedNames + startIndex, searchNameIndex - initialSearchNameIndex); if (nextCharacter == '\0') { longestPartialMatchIndex = searchNameIndex; longestPartialMatchOffset = cachedNames[startIndex].data; } } Debug.Assert(startIndex <= endIndex); if (endIndex == startIndex) { searchNameIndex = longestPartialMatchIndex; return longestPartialMatchOffset; } // only the item with startIndex can be exact match // to prove it, there are 2 cases here: // a) searchNameIndex == searchName.Length // in this case all items are either of the same length as search string // or longer. The item of the same length will be first in the list. // b) searchNameIndex < searchName.Length // in this case no more than 1 match is left // Note that in both cases we must compare strings // starting with searchNameIndex for (; ; ) { char d = GetCachedChar(cachedNames + startIndex, searchNameIndex - initialSearchNameIndex); // see if we reached the end of the input string if (searchNameIndex == searchName.Length) { // here we have an exact match only if we reached the end of the cached string as well if (d == '\0') return cachedNames[startIndex].data; // Otherwise, we have no match. searchNameIndex = longestPartialMatchIndex; return longestPartialMatchOffset; } char s = Char.ToUpperInvariant(searchName[searchNameIndex]); if (s != d) { // See if we reached the end of the cached string (partial match). if (d == '\0') return cachedNames[startIndex].data; // Otherwise, we have no match. searchNameIndex = longestPartialMatchIndex; return longestPartialMatchOffset; } ++searchNameIndex; } } ////// Critical:This code calls into Alloc and also yields unverifiable code. It also acceses /// unsafe code to operate on pointers. /// TreatAsSafe: Adding a physical family to cache is a safe operation.Also it uses primitives that /// are bounds checked. /// [SecurityCritical,SecurityTreatAsSafe] private unsafe int AddPhysicalFamilyToCache(PhysicalFamily physicalFamily, SortedListfrequentStrings) { int familyCacheOffset = _cacher.Alloc( checked( sizeof(CachedPhysicalFamily) + physicalFamily.faceNames.Count * sizeof(CachedName) + physicalFamily.typefaces.Count * sizeof(int) ) ); CachedPhysicalFamily* cachedFamily = (CachedPhysicalFamily*)_cacher.Mapping.Probe(familyCacheOffset, sizeof(CachedPhysicalFamily)); cachedFamily->familyType = FamilyType.Physical; CachedName* cachedFaceNames = (CachedName*)((byte*)cachedFamily + sizeof(CachedPhysicalFamily)); int* facePointers = (int*)(cachedFaceNames + physicalFamily.faceNames.Count); cachedFamily->numberOfFaceNames = physicalFamily.faceNames.Count; cachedFamily->numberOfTypefaces = 0; bool symbol = false; // the following 4 variables set up the state for regular face matching PhysicalFace bestFace = null; MatchingStyle regular = new MatchingStyle(FontStyles.Normal, FontWeights.Normal, FontStretches.Normal); MatchingStyle bestMatch = new MatchingStyle(); // put all the faces to the cache int j = 0; foreach (KeyValuePair pair in physicalFamily.faceNames) { CachedName * cachedFaceName = cachedFaceNames + j; LocalizedName faceName = pair.Key; PhysicalFace face = pair.Value; if (face.cacheOffset == Util.nullOffset) { int fontUriSize = Util.StringSize(face.fontUri); face.cacheOffset = _cacher.Alloc(checked(sizeof(CachedPhysicalFace) + fontUriSize)); CheckedPointer cachedFacePointer = _cacher.GetCheckedPointer(face.cacheOffset); CachedPhysicalFace* cachedFace = (CachedPhysicalFace*)cachedFacePointer.Probe(0, sizeof(CachedPhysicalFace)); cachedFace->fontUriSize = fontUriSize; cachedFace->faceIndex = face.faceIndex; cachedFace->style = face.style; cachedFace->weight = face.weight; cachedFace->stretch = face.stretch; cachedFace->styleSimulations = face.styleSimulations; Util.StringCopyToCheckedPointer(cachedFacePointer + sizeof(CachedPhysicalFace), face.fontUri); // if any of faces is non-Unicode, the whole family is treated as non-Unicode if (face.symbol) symbol = true; // add the face to the face list facePointers[cachedFamily->numberOfTypefaces] = face.cacheOffset; ++cachedFamily->numberOfTypefaces; // see if the face is a better match for regular face MatchingStyle current = new MatchingStyle(face.style, face.weight, face.stretch); if (MatchingStyle.IsBetterMatch( regular, bestMatch, ref current ) || bestFace == null) { bestFace = face; bestMatch = current; } } // now that we ensured the face is constructed, fill out CachedName cachedFaceName->offsetToIetfLanguageTag = GetOffsetToIetfLanguageTag(faceName.Language, frequentStrings); cachedFaceName->offsetToName = GetOffsetToString(faceName.Name, frequentStrings); cachedFaceName->data = face.cacheOffset; ++j; } cachedFamily->symbol = symbol; // obtain baseline and height from regular face Debug.Assert(bestFace != null); Debug.Assert(cachedFamily->numberOfTypefaces <= physicalFamily.typefaces.Count); double emsPerDesignUnit = 1.0 / bestFace.designEmHeight; // The ascent is from the top of the cell. Per [....], we want baseline to be relative to the // top of a logical line (represented by lineSpacing) in which the cell is vertically centered. // Thus we want half the external leading to be above the cell. The external leading is equal // to (lineSpacing - (ascent + descent)), giving us the following formula: // // baseline = ascent + (lineSpacing - (ascent + descent)) * 0.5 // = ascent + lineSpacing * 0.5 - ascent * 0.5 - descent * 0.5 // = ascent * 0.5 + lineSpacing * 0.5 - descent * 0.5 // = (ascent + lineSpacing - descent) * 0.5 // cachedFamily->baseline = (bestFace.designCellAscent + bestFace.designLineSpacing - bestFace.designCellDescent) * 0.5 * emsPerDesignUnit; cachedFamily->lineSpacing = bestFace.designLineSpacing * emsPerDesignUnit; AddFamilyNames((CachedFamily*)cachedFamily, physicalFamily, frequentStrings); return familyCacheOffset; } /// /// Critical:This code calls into Alloc and also yields unverifiable code /// TreatAsSafe: Adding a composite family to cache is a safe operation.Also it uses primitives that /// are bounds checked. /// [SecurityCritical, SecurityTreatAsSafe] private unsafe int AddCompositeFamilyToCache(CompositeFamily compositeFamily, SortedListfrequentStrings) { int familyCacheOffset; CachedCompositeFamily* cachedFamily = new CompositeFamilyCacher().Allocate( compositeFamily.compositeFontInfo, this, frequentStrings, out familyCacheOffset ); AddFamilyNames((CachedFamily*)cachedFamily, compositeFamily, frequentStrings); return familyCacheOffset; } /// /// Comparer for sorting XmlLanguages in decreasing order of specificity. /// private class LanguageComparer : IComparer { internal static readonly LanguageComparer Comparer = new LanguageComparer(); public int Compare(object x, object y) { int dx = ((XmlLanguage) x).GetSpecificity(); int dy = ((XmlLanguage) y).GetSpecificity(); return (dx > dy) ? -1 : // dx is more specific -> sort before (dx < dy) ? 1 : // dx is less specific -> sort after string.CompareOrdinal(x.ToString(), y.ToString()); // no need for case-insensitivity; only requirement is stability } } ////// Add a list of family names sorted by culture to a family. /// /// Cached representation of a font family. /// Temporary in-memory representation of a font family. /// String cache structure to optimize cache space. ////// Critical:This code calls dereferences pointers without any validation. /// [SecurityCritical] private unsafe void AddFamilyNames(CachedFamily* cachedFamily, BaseFamily family, SortedListfrequentStrings) { cachedFamily->numberOfFamilyNames = family.familyNames.Count; Invariant.Assert(cachedFamily->numberOfFamilyNames >= 1); cachedFamily->offsetToFamilyNames = _cacher.Alloc(2 * sizeof(int) * cachedFamily->numberOfFamilyNames); int* familyNamePointers = (int*)_cacher[cachedFamily->offsetToFamilyNames]; foreach (LocalizedName familyName in family.familyNames.Keys) { familyNamePointers[0] = (int)frequentStrings[familyName.Language.IetfLanguageTag]; familyNamePointers[1] = (int)frequentStrings[familyName.Name]; Invariant.Assert(familyNamePointers[0] != Util.nullOffset && familyNamePointers[0] != 0); Invariant.Assert(familyNamePointers[1] != Util.nullOffset && familyNamePointers[1] != 0); familyNamePointers += 2; } Invariant.Assert(familyNamePointers - (int*)_cacher[cachedFamily->offsetToFamilyNames] == 2 * cachedFamily->numberOfFamilyNames); } /// /// Fills in family list and family name list, constructing cache family object as needed. /// /// Family name list. /// Family list. /// Number of elements in the family list. /// Mapping from family names to family objects. /// String cache structure to optimize cache space. ////// Critical:This code calls into AddPhysicalFamilyToCache and also yields unverifiable code /// [SecurityCritical] private unsafe void AddFamiliesToCache( CachedName* cachedNames, int* cachedFamilies, int familyCount, SortedDictionaryfamilyNameList, SortedList frequentStrings) { int i; // Store frequently used strings only once, and use offsets to refer to them. for (i = 0; i < frequentStrings.Count; ++i) { string name = frequentStrings.Keys[i]; frequentStrings[name] = StoreString(name); } int numberOfConstructedFamilies = 0; i = 0; foreach (KeyValuePair pair in familyNameList) { CachedName * cachedFamilyName = cachedNames + i; LocalizedName familyName = pair.Key; BaseFamily family = pair.Value; try { if (family.cacheOffset == Util.nullOffset) { // create family structure in case it doesn't exist PhysicalFamily physicalFamily = family as PhysicalFamily; if (physicalFamily != null) { family.cacheOffset = AddPhysicalFamilyToCache(physicalFamily, frequentStrings); } else { family.cacheOffset = AddCompositeFamilyToCache((CompositeFamily)family, frequentStrings); } Invariant.Assert(numberOfConstructedFamilies < familyCount); cachedFamilies[numberOfConstructedFamilies] = family.cacheOffset; ++numberOfConstructedFamilies; } // now that we ensured the family is constructed, fill out CachedName cachedFamilyName->offsetToIetfLanguageTag = GetOffsetToIetfLanguageTag(familyName.Language, frequentStrings); cachedFamilyName->offsetToName = GetOffsetToString(familyName.Name, frequentStrings); cachedFamilyName->data = family.cacheOffset; } catch (OverflowException) { // Extreme numbers in the font 'name' table can cause arithmetic overflows in // AddPhysicalFamilyToCache. // We want to ignore such fonts and continue creating entries for well formed fonts. } ++i; } } /// /// Critical: This code calls into cacher. and results in unverifiable code /// TreatAsSafe: This code is safe to call since the cacher function is bounds checked., /// [SecurityCritical,SecurityTreatAsSafe] private int StoreString(string name) { unsafe { int newOffset = _cacher.Alloc(sizeof(int) + Util.StringSize(name)); Util.StringAndLengthCopyToCheckedPointer(_cacher.GetCheckedPointer(newOffset), name); return newOffset; } } private int GetOffsetToString(string name, SortedListfrequentStrings) { int offset = (int)frequentStrings[name]; Debug.Assert(offset != Util.nullOffset && offset != 0); return offset; } private int GetOffsetToIetfLanguageTag(XmlLanguage language, SortedList frequentStrings) { return GetOffsetToString(language.IetfLanguageTag, frequentStrings); } private string GetStringFromOffset(int offsetToName) { return Util.StringAndLengthCopyFromCheckedPointer(_cacher.GetCheckedPointer(offsetToName)); } private XmlLanguage GetFamilyMapLanguageFromOffset(int offsetToIetfLanguageTag) { return XmlLanguage.GetLanguage(GetStringFromOffset(offsetToIetfLanguageTag)); } /// /// Critical: This function acceses _cacher[], it derefernces a pointer without any checking /// [SecurityCritical] private unsafe bool InRange(CachedFamilyMap* cmap, int ch) { CachedCharacterRange* range = (CachedCharacterRange*)((byte*)cmap + cmap->offsetToRanges); for (int i = 0; i < cmap->numberOfRanges; i++) { // clever code from Word meaning: "ch >= _first && ch <= _last", // this is done with one test and branch. if ((uint)(ch - range->first) <= range->delta) return true; ++range; } return false; } ////// Critical: This code calls into unsafe code blocks and exposes a pointer. /// [SecurityCritical] private unsafe CachedFamiliesAndNames* GetCachedFamiliesAndNames() { return (CachedFamiliesAndNames*)_cacher[_data->offsetToCachedFamiliesAndNames]; } ////// Critical: This code calls into unsafe code blocks and exposes a pointer. /// [SecurityCritical] private unsafe void GetCachedFamilyNames(out CachedName* cachedFamilyNames, out int familyNameCount) { CachedFamiliesAndNames* cachedFamiliesAndNames = GetCachedFamiliesAndNames(); familyNameCount = cachedFamiliesAndNames->numberOfFamilyNames; cachedFamilyNames = CachedFamiliesAndNames.GetCachedFamilyNames(cachedFamiliesAndNames); } ////// Critical: This code calls into unsafe code blocks and exposes a pointer. /// [SecurityCritical] private unsafe void GetCachedFamilies(out int* cachedFamilies, out int familyCount) { CachedFamiliesAndNames* cachedFamiliesAndNames = GetCachedFamiliesAndNames(); familyCount = cachedFamiliesAndNames->numberOfFontFamilies; cachedFamilies = CachedFamiliesAndNames.GetCachedFamilies(cachedFamiliesAndNames); } #endregion Private Methods //----------------------------------------------------- // // Private Types // //------------------------------------------------------ #region Private Types ////// Memory layout: Layout struct | folder name string. /// [StructLayout(LayoutKind.Explicit, Size = 20)] internal struct Layout { [FieldOffset(0)] internal long timeStamp; [FieldOffset(8)] internal int offsetToCachedFamiliesAndNames; ////// isDefaultSystemFontsFolder and folderUriSize should be the last members, /// as they are stored along with the name string as a key for cache miss reports. /// [FieldOffset(OffsetToKey)] internal bool isDefaultSystemFontsFolder; [FieldOffset(OffsetToFolderUriSize)] internal int folderUriSize; internal const int OffsetToKey = 12; internal const int OffsetToFolderUriSize = 16; } ////// Memory layout: CachedFamiliesAndNames | /// unsorted array [numberOfFontFamilies] of font family pointers | /// sorted array [numberOfFamilyNames] of CachedName family entries /// Unsorted array of font family pointers is used for font family enumeration. /// Sorted array of font family names is used for font family lookup by name. /// The structure should be aligned on a 4 byte bounary. /// This guarantees that arrays are also aligned on 4 byte boundaries. /// ////// Critical - as this class contains sizes and offsets which are used to allocate /// unamanged memory and perform pointer arithmetic; font cache corruption /// could result if any of the calculations are wrong. /// /// The entire class is declared critical because *all* of its members are /// critical, and because declaring the critical members individually would /// not result in any more transparent code. /// [SecurityCritical(SecurityCriticalScope.Everything)] [StructLayout(LayoutKind.Explicit, Size = 8)] private struct CachedFamiliesAndNames { [FieldOffset(0)] internal int numberOfFontFamilies; [FieldOffset(4)] internal int numberOfFamilyNames; // We cannot use sizeof to assign to a const variable. public const int OffsetToCachedFamilies = 8; public unsafe static int* GetCachedFamilies(CachedFamiliesAndNames* This) { return (int*)((byte*)This + OffsetToCachedFamilies); } public static int GetTotalSize(int familyCount, int familyNameCount) { unsafe { return GetOffsetToCachedFamilyNames(familyCount) + sizeof(CachedName) * familyNameCount; } } public static int GetOffsetToCachedFamilyNames(int familyCount) { unsafe { return OffsetToCachedFamilies + sizeof(int) * familyCount; } } public unsafe static CachedName* GetCachedFamilyNames(CachedFamiliesAndNames* This) { return (CachedName*)((byte*)This + GetOffsetToCachedFamilyNames(This->numberOfFontFamilies)); } } ////// The structure should be aligned on 4 byte boundary. /// [StructLayout(LayoutKind.Explicit, Size = 12)] private struct CachedName { ////// Offset to the name string culture /// [FieldOffset(0)] internal int offsetToIetfLanguageTag; ////// Offset to the actual name string /// [FieldOffset(4)] internal int offsetToName; ////// Offset to PhysicalFace or Family /// [FieldOffset(8)] internal int data; }; internal enum FamilyType { Composite = 0, Physical = 1 }; ////// Base structure for CachedPhysicalFamily and CachedCompositeFamily. /// Since we cannot use inheritance here, we simply repeat the fields in derivees. /// [StructLayout(LayoutKind.Explicit, Size = 32)] internal struct CachedFamily { ////// Composite or physical /// [FieldOffset(0)] internal FamilyType familyType; ////// Number of typefaces in this family. /// [FieldOffset(4)] internal int numberOfTypefaces; [FieldOffset(8)] internal double baseline; [FieldOffset(16)] internal double lineSpacing; ////// Number of localized names for this family. /// [FieldOffset(24)] internal int numberOfFamilyNames; ////// Pointer to the CachedName[numberOfFamilyNames] array of localized names for this family, /// sorted by culture info string. /// [FieldOffset(28)] internal int offsetToFamilyNames; }; /// Allocates and initializes a CachedCompositeFamily structure and related structures. The /// memory layout is as follows. /// /// 1. CachedCompositeFamily struct /// /// 2. at CachedCompositeFamily.OffsetToFamilyMaps: /// array [numberOfFamilyMaps] of CachedFamilyMap in lookup order. /// /// 3. at CachedCompositeFamily.OffsetToFamilyMapRangeLists: /// array [numberOfFamilyMapRangeLists] of CachedFamilyMapRangeList in decreasing /// order of cultural specificity. /// /// 4. at CachedCompositeFamily.OffsetToFamilyTypeface: /// array[numberOfTypefaces] of CachedCompositeFace. /// /// 5. at CachedFamily.OffsetToTargetFamilyNameStrings /// one or more size-prefixed strings, each with the following layout: /// int representing the size of the string in bytes /// char[] containing characters of the string /// optional padding for integer alignment /// Note: The first string is always the target of the first FontFamilyMap. Subsequent /// strings may be in any order. If there are no FamilyMaps then a single string /// of length zero is added. /// /// 6. at _offsetToFamilyMapRanges: /// array[] of CachedCharacterRange. /// /// 7. at _offsetToRangeListArrays: /// array[] of ushort, referenced by CachedFamilyMapRangeList. /// /// 8. at _offsetToCachedDeviceFonts: /// zero or more of the following: /// CachedDeviceFont structure /// at CachedDeviceFont.OffsetToLengthPrefixedName: length prefixed string /// at CachedDeviceFont.OffsetToCharacterMap: array[] of int /// /// 9. at _offsetToCharacterMetricsArray: /// array[] of CachedCharacterMetrics /// ////// Critical - as this class contains sizes and offsets which are used to allocate /// unamanged memory and perform pointer arithmetic; font cache corruption /// could result if any of the calculations are wrong. /// /// The entire class is declared critical because *all* of its members are /// critical, and because declaring the critical members individually would /// not result in any more transparent code. /// [SecurityCritical(SecurityCriticalScope.Everything)] private struct CompositeFamilyCacher { private CompositeFontInfo _compositeFontInfo; private CachedCompositeFamily _cachedFamily; // Family map information. // AnalyzeFamilyMaps initializes these fields. private Hashtable _targetFamilyNamesTable; private int _sizeOfTargetFamilyNameStrings; private int _sizeOfFamilyMapRanges; private string _firstTargetFamilyName; // Family map language / range lists information. // AnalyzeFamilyMapLanguages initializes these fields. private XmlLanguage[] _familyMapLanguages; private ushort[][] _familyMapRangeLists; private int _sizeOfRangeListArrays; // Device font information. // AnalyzeDeviceFonts initializes these fields. private Hashtable _characterMetricsTable; private int _sizeOfCachedDeviceFonts; private int _sizeOfCharacterMetrics; // Other offsets, based on the above sizes. private int _offsetToFamilyMapRanges; private int _offsetToRangeListArrays; private int _offsetToCachedDeviceFonts; private int _offsetToCharacterMetricsArray; private int _allocationSize; internal unsafe CachedCompositeFamily* Allocate( CompositeFontInfo compositeFontInfo, FamilyCollection collection, SortedListfrequentStrings, out int familyCacheOffset) { AnalyzeCompositeFontInfo(compositeFontInfo); // allocate memory for the CachedCompositeFamily and subsequent data familyCacheOffset = collection._cacher.Alloc(_allocationSize); // create a checked pointer for bounds-checked access using the allocation size CheckedPointer cachedFamily = collection._cacher.Mapping.CheckedProbe( familyCacheOffset, _allocationSize ); // fill in basic information CachedCompositeFamily* familyPointer = (CachedCompositeFamily*)(cachedFamily.Probe(0, sizeof(CachedCompositeFamily))); *familyPointer = _cachedFamily; // fill in family maps WriteFamilyMaps(collection, cachedFamily, frequentStrings); WriteFamilyTypefaces(cachedFamily); return familyPointer; } private void AnalyzeCompositeFontInfo(CompositeFontInfo compositeFontInfo) { _compositeFontInfo = compositeFontInfo; _cachedFamily = new CachedCompositeFamily(); // Partially initialize _cachedFamily. _cachedFamily.familyType = FamilyType.Composite; _cachedFamily.baseline = compositeFontInfo.Baseline; _cachedFamily.lineSpacing = compositeFontInfo.LineSpacing; // Calculate other sizes and offsets, including some _cachedFamily fields. AnalyzeFamilyMaps(); AnalyzeFamilyTypefaces(); // We've now initialized _cachedFamily to the point where we can rely on the values // returned by its OffsetToXXX properties. Calculate additional offsets based on the // previously computed sizes. // array of CachedCharacterRange; 8-byte alignment required _offsetToFamilyMapRanges = Util.Align8( _cachedFamily.OffsetToTargetFamilyNameStrings + _sizeOfTargetFamilyNameStrings ); // array of ushort; ushort alignment is sufficient _offsetToRangeListArrays = _offsetToFamilyMapRanges + _sizeOfFamilyMapRanges; // zero or more of ( CachedDeviceFont + array of char + array of int ) _offsetToCachedDeviceFonts = Util.Align8( _offsetToRangeListArrays + _sizeOfRangeListArrays ); // array of CachedCharacterMetrics; 8-byte alignment is required _offsetToCharacterMetricsArray = Util.Align8( _offsetToCachedDeviceFonts + _sizeOfCachedDeviceFonts ); _allocationSize = Util.Align8( _offsetToCharacterMetricsArray + _sizeOfCharacterMetrics ); } private void AnalyzeFamilyMaps() { unsafe { FontFamilyMapCollection familyMaps = _compositeFontInfo.FamilyMaps; if (familyMaps != null && familyMaps.Count != 0) { // Remember the number of family maps. _cachedFamily.numberOfFamilyMaps = _compositeFontInfo.FamilyMaps.Count; // We're going to create a mapping of target family names to unique indexes // and also remember the first target family name. _targetFamilyNamesTable = new Hashtable(_cachedFamily.numberOfFamilyMaps); _firstTargetFamilyName = familyMaps[0].Target; foreach (FontFamilyMap map in familyMaps) { // Is this the first occurrence of this family name? if (!_targetFamilyNamesTable.ContainsKey(map.Target)) { // Associated each unique string with a unique index. _targetFamilyNamesTable.Add(map.Target, _targetFamilyNamesTable.Count); // Reserve room for length prefix + characters + any padding. _sizeOfTargetFamilyNameStrings += GetPrefixedStringSize(map.Target); } _sizeOfFamilyMapRanges += map.Ranges.Length * sizeof(CachedCharacterRange); } // Analyze the family map cultures and the corresponding range lists. AnalyzeFamilyMapLanguages(); } // If there are no target family names we'll add a zero length string so that // CachedCompositeFamily.OffsetToTargetFamilyNameStrings always points to a // valid length-prefixed string. if (_sizeOfTargetFamilyNameStrings == 0) { // All we need is the length prefix with value zero. _sizeOfTargetFamilyNameStrings = GetPrefixedStringSize(String.Empty); } } } private static int GetPrefixedStringSize(string s) { unsafe { return Util.Align4(sizeof(int) + Util.StringSize(s)); } } private unsafe void WriteFamilyMaps(FamilyCollection collection, CheckedPointer cachedFamily, SortedList frequentStrings) { CheckedPointer[] namePointers = null; CheckedPointer nameStrings = cachedFamily + _cachedFamily.OffsetToTargetFamilyNameStrings; CheckedPointer nameStringsEnd = nameStrings; // fill in target family names if (_targetFamilyNamesTable != null) { namePointers = new CheckedPointer[_targetFamilyNamesTable.Count]; // The first target family name must always come first. namePointers[0] = nameStringsEnd; Util.StringAndLengthCopyToCheckedPointer(nameStringsEnd, _firstTargetFamilyName); nameStringsEnd += GetPrefixedStringSize(_firstTargetFamilyName); // The other target family names can be added in whatever order the // hashtable enumerates them. foreach (DictionaryEntry entry in _targetFamilyNamesTable) { // We've already added the first target family name so skip this // entry if nameIndex is zero. int nameIndex = (int)entry.Value; if (nameIndex != 0) { namePointers[nameIndex] = nameStringsEnd; string familyName = (string)entry.Key; Util.StringAndLengthCopyToCheckedPointer(nameStringsEnd, familyName); nameStringsEnd += GetPrefixedStringSize(familyName); } } } else { // No family names. Store an empty string so that CachedCompositeFamily.OffsetToTargetFamilyNameStrings // always is the offset of a valid length-prefixed string. Util.StringAndLengthCopyToCheckedPointer(nameStringsEnd, String.Empty); nameStringsEnd += GetPrefixedStringSize(String.Empty); } // Make sure the calculated size agrees with the actual size. Invariant.Assert(nameStringsEnd.PointerEquals(nameStrings + _sizeOfTargetFamilyNameStrings)); // fill in the CachedFamilyMap and CachedCharacterRange arrays if (_cachedFamily.numberOfFamilyMaps != 0) { CheckedPointer cachedFamilyMaps = cachedFamily + _cachedFamily.OffsetToFamilyMaps; CheckedPointer cachedRanges = cachedFamily + _offsetToFamilyMapRanges; CheckedPointer cachedRangesEnd = cachedRanges; foreach (FontFamilyMap cmap in _compositeFontInfo.FamilyMaps) { CachedFamilyMap* familyMap = (CachedFamilyMap*)cachedFamilyMaps.Probe(0, sizeof(CachedFamilyMap)); familyMap->scaleInEm = cmap.Scale; CheckedPointer namePointer = namePointers[(int)_targetFamilyNamesTable[cmap.Target]]; familyMap->targetFamilyNameOffset = cachedFamilyMaps.OffsetOf(namePointer); familyMap->numberOfRanges = cmap.Ranges.Length; familyMap->offsetToRanges = cachedFamilyMaps.OffsetOf(cachedRangesEnd); foreach (FontFamilyMap.Range range in cmap.Ranges) { CachedCharacterRange* cachedRange = (CachedCharacterRange*)cachedRangesEnd.Probe(0, sizeof(CachedCharacterRange)); cachedRange->first = range.First; cachedRange->delta = range.Delta; cachedRangesEnd += sizeof(CachedCharacterRange); } cachedFamilyMaps += sizeof(CachedFamilyMap); } // Make sure the calculated size agrees with the actual size. Invariant.Assert(cachedRangesEnd.PointerEquals(cachedRanges + _sizeOfFamilyMapRanges)); // Write the family map cultures and their associated range lists. WriteFamilyMapLanguages(collection, cachedFamily, frequentStrings); } } private void AnalyzeFamilyMapLanguages() { unsafe { int languageCount = _compositeFontInfo.FamilyMapLanguages != null ? _compositeFontInfo.FamilyMapLanguages.Count : 0; // We create one range list per culture, plus one for "any language", i.e., null. _cachedFamily.numberOfFamilyMapRangeLists = languageCount + 1; // Create the array of cultures (including "any") and sort in decreasing order of specificity. _familyMapLanguages = new XmlLanguage[_cachedFamily.numberOfFamilyMapRangeLists]; if (languageCount > 0) { _compositeFontInfo.FamilyMapLanguages.CopyTo(_familyMapLanguages, 0); Array.Sort(_familyMapLanguages, 0, languageCount, LanguageComparer.Comparer); } // Get the range list for each culture (including "any") and calculate their total size. _familyMapRangeLists = new ushort[_cachedFamily.numberOfFamilyMapRangeLists][]; for (int i = 0; i < _familyMapLanguages.Length; ++i) { ushort[] ranges = _compositeFontInfo.GetFamilyMapsOfLanguage(_familyMapLanguages[i]); _familyMapRangeLists[i] = ranges; _sizeOfRangeListArrays += (ranges.Length - CompositeFontInfo.FirstFamilyMapRange) * sizeof(ushort); } } } private unsafe void WriteFamilyMapLanguages(FamilyCollection collection, CheckedPointer cachedFamily, SortedList frequentStrings) { // fill in the family map range lists CheckedPointer rangeLists = cachedFamily + _cachedFamily.OffsetToFamilyMapRangeLists; CheckedPointer rangeListArrays = cachedFamily + _offsetToRangeListArrays; CheckedPointer rangeListArraysEnd = rangeListArrays; for (int i = 0; i < _familyMapLanguages.Length; ++i) { XmlLanguage language = _familyMapLanguages[i]; ushort[] ranges = _familyMapRangeLists[i]; CachedFamilyMapRangeList* rangeList = (CachedFamilyMapRangeList*)rangeLists.Probe(0, sizeof(CachedFamilyMapRangeList)); rangeList->offsetToIetfLanguageTag = (language != null) ? collection.GetOffsetToIetfLanguageTag(language, frequentStrings) : 0; rangeList->lengthOfRanges = ranges.Length - CompositeFontInfo.FirstFamilyMapRange; rangeList->offsetToRanges = rangeLists.OffsetOf(rangeListArraysEnd); int arraySizeInBytes = Util.ArrayCopyToCheckedPointer( rangeListArraysEnd, ranges, CompositeFontInfo.FirstFamilyMapRange, rangeList->lengthOfRanges ); rangeListArraysEnd += arraySizeInBytes; rangeLists += sizeof(CachedFamilyMapRangeList); } // Make sure the calculated size agrees with the actual size. Invariant.Assert(rangeListArrays.OffsetOf(rangeListArraysEnd) == _sizeOfRangeListArrays); } private void AnalyzeFamilyTypefaces() { FamilyTypefaceCollection familyTypefaces = _compositeFontInfo.FamilyTypefaces; if (familyTypefaces == null) return; _cachedFamily.numberOfTypefaces = familyTypefaces.Count; AnalyzeDeviceFonts(); } private unsafe void WriteFamilyTypefaces(CheckedPointer cachedFamily) { if (_cachedFamily.numberOfTypefaces == 0) return; // fill in the CachedCompositeFace array CachedCompositeFace* cachedFamilyTypeface = (CachedCompositeFace*)cachedFamily.Probe( _cachedFamily.OffsetToFamilyTypeface, _cachedFamily.numberOfTypefaces * sizeof(CachedCompositeFace) ); foreach (FamilyTypeface familyTypeface in _compositeFontInfo.FamilyTypefaces) { cachedFamilyTypeface->capsHeight = familyTypeface.CapsHeight; cachedFamilyTypeface->fontStyle = familyTypeface.Style; cachedFamilyTypeface->fontWeight = familyTypeface.Weight; cachedFamilyTypeface->fontStretch = familyTypeface.Stretch; cachedFamilyTypeface->strikeThroughPosition = familyTypeface.StrikethroughPosition; cachedFamilyTypeface->strikeThroughThickness = familyTypeface.StrikethroughThickness; cachedFamilyTypeface->underlinePosition = familyTypeface.UnderlinePosition; cachedFamilyTypeface->underlineThickness = familyTypeface.UnderlineThickness; cachedFamilyTypeface->xHeight = familyTypeface.XHeight; ++cachedFamilyTypeface; } // fill in device font information WriteDeviceFonts(cachedFamily); } private void AnalyzeDeviceFonts() { unsafe { for (int i = 0; i < _cachedFamily.numberOfTypefaces; ++i) { // Does this FamilyTypeface have device font information? FamilyTypeface face = _compositeFontInfo.FamilyTypefaces[i]; if (face.DeviceFontName != null && face.DeviceFontCharacterMetrics.Count != 0) { // We'll store only one copy of each unique CharacterMetrics so use a hash table // to map character metrics (the key) to integers (indexes). if (_characterMetricsTable == null) { _characterMetricsTable = new Hashtable(_cachedFamily.numberOfTypefaces); } // Determine the number of "pages" of integers after the CachedDeviceFont structure. CharacterMetricsDictionary metricsDictionary = face.DeviceFontCharacterMetrics; int pageCount = 0; for (int j = 0; j < CharacterMetricsDictionary.PageCount; ++j) { // Iterate over pages, where j is the page index CharacterMetrics[] page = metricsDictionary.GetPage(j); if (page != null) { // The page exists so increment the page count ++pageCount; // Add each unique CharacterMetrics object to the hash table for (int k = 0; k < page.Length; ++k) { if (page[k] != null && !_characterMetricsTable.ContainsKey(page[k])) { _characterMetricsTable.Add(page[k], _characterMetricsTable.Count); } } } } // Add the total size of this device font including the CachedDeviceFont structure itself // plus the string and character metrics array. _sizeOfCachedDeviceFonts += new CachedDeviceFont(face.DeviceFontName, pageCount).SizeOfCachedDeviceFont; } } // Add room for the array of character metrics. if (_characterMetricsTable != null) { _sizeOfCharacterMetrics = _characterMetricsTable.Count * sizeof(CachedCharacterMetrics); } } } private unsafe void WriteDeviceFonts(CheckedPointer cachedFamily) { if (_characterMetricsTable == null) return; // fill in the CachedCharacterMetrics array CheckedPointer cachedCharacterMetricsArray = cachedFamily.CheckedProbe( _offsetToCharacterMetricsArray, _characterMetricsTable.Count * sizeof(CachedCharacterMetrics) ); foreach (DictionaryEntry entry in _characterMetricsTable) { CharacterMetrics metrics = (CharacterMetrics)(entry.Key); CachedCharacterMetrics* cachedMetrics = (CachedCharacterMetrics*)cachedCharacterMetricsArray.Probe( (int)(entry.Value) * sizeof(CachedCharacterMetrics), sizeof(CachedCharacterMetrics) ); cachedMetrics->blackBoxWidth = metrics.BlackBoxWidth; cachedMetrics->blackBoxHeight = metrics.BlackBoxHeight; cachedMetrics->baseline = metrics.Baseline; cachedMetrics->leftSideBearing = metrics.LeftSideBearing; cachedMetrics->rightSideBearing = metrics.RightSideBearing; cachedMetrics->topSideBearing = metrics.TopSideBearing; cachedMetrics->bottomSideBearing = metrics.BottomSideBearing; } // iterate over the typefaces to fill in device fonts CheckedPointer cachedDeviceFonts = cachedFamily + _offsetToCachedDeviceFonts; CheckedPointer cachedDeviceFontsEnd = cachedDeviceFonts; CheckedPointer currentFamilyTypeface = cachedFamily + _cachedFamily.OffsetToFamilyTypeface; foreach (FamilyTypeface familyTypeface in _compositeFontInfo.FamilyTypefaces) { // Does this typeface have device font information? string deviceFontName = familyTypeface.DeviceFontName; if (deviceFontName != null && familyTypeface.DeviceFontCharacterMetrics.Count != 0) { // Initialize the offset from the typeface to the corresponding device font. CachedCompositeFace* typefacePtr = (CachedCompositeFace*)currentFamilyTypeface.Probe(0, sizeof(CachedCompositeFace)); typefacePtr->offsetToDeviceFont = currentFamilyTypeface.OffsetOf(cachedDeviceFontsEnd); // Count the non-empty pages in the character metrics dictionary. CharacterMetricsDictionary metricsDictionary = familyTypeface.DeviceFontCharacterMetrics; int pageCount = 0; for (int i = 0; i < CharacterMetricsDictionary.PageCount; ++i) { if (metricsDictionary.GetPage(i) != null) ++pageCount; } // Point to the device font structure and fill in the page count and name. CachedDeviceFont* deviceFontPtr = (CachedDeviceFont*)cachedDeviceFontsEnd.Probe(0, sizeof(CachedDeviceFont)); deviceFontPtr->numberOfPages = pageCount; Util.StringAndLengthCopyToCheckedPointer( cachedDeviceFontsEnd + CachedDeviceFont.OffsetToLengthPrefixedName, deviceFontName ); // Create a checked pointer for accessing the character map; this ensures we won't // overwrite the memory we reserved for this device font. int characterMapOffset = deviceFontPtr->OffsetToCharacterMap; int deviceFontSize = deviceFontPtr->SizeOfCachedDeviceFont; CheckedPointer characterMap = cachedDeviceFontsEnd.CheckedProbe( characterMapOffset, deviceFontSize - characterMapOffset ); // Point to the page table. int* cachedPageTable = (int*)characterMap.Probe(0, CharacterMetricsDictionary.PageCount * sizeof(int)); // Integer index of current page from start of page table. int currentPageIndex = CharacterMetricsDictionary.PageCount; // Iterate over the pages in the CharacterMetricsDictionary. for (int i = 0; i < CharacterMetricsDictionary.PageCount; ++i) { // Is there a page? CharacterMetrics[] page = metricsDictionary.GetPage(i); if (page != null) { // Point to the page. int* cachedPage = (int*)characterMap.Probe( currentPageIndex * sizeof(int), CharacterMetricsDictionary.PageSize * sizeof(int) ); // Set the page table entry to the index of the beginning of the page. cachedPageTable[i] = currentPageIndex; // Initialize the page contents. for (int j = 0; j < CharacterMetricsDictionary.PageSize; ++j) { if (page[j] != null) { // Use the hash table to find the array index of the CachedCharacterMetrics object. int metricsIndex = (int)_characterMetricsTable[page[j]]; // Set the page entry to the offset of the CachedCharacterMetrics object. cachedPage[j] = cachedDeviceFontsEnd.OffsetOf( cachedCharacterMetricsArray + (metricsIndex * sizeof(CachedCharacterMetrics)) ); } } // Update the current page index. currentPageIndex += CharacterMetricsDictionary.PageSize; } } // Point to where the next CachedDeviceFont will go (if any). cachedDeviceFontsEnd += deviceFontSize; } currentFamilyTypeface += sizeof(CachedCompositeFace); } // Make sure the total memory we filled in for all device fonts matches the calculated size. Invariant.Assert(cachedDeviceFonts.OffsetOf(cachedDeviceFontsEnd) == _sizeOfCachedDeviceFonts); } } /// See comment for struct CompositeFamilyCacher for memory layout. /// We never store this in a sequential array so we don't have to 8 byte align its size. [StructLayout(LayoutKind.Explicit, Size = 40)] internal struct CachedCompositeFamily { /// /// Composite or physical /// [FieldOffset(0)] internal FamilyType familyType; ////// Number of typefaces in this family. /// [FieldOffset(4)] internal int numberOfTypefaces; [FieldOffset(8)] internal double baseline; [FieldOffset(16)] internal double lineSpacing; ////// Number of localized names for this family. /// [FieldOffset(24)] internal int numberOfFamilyNames; ////// Pointer to the CachedName[numberOfFamilyNames] array of localized names for this family, /// sorted by culture info string. /// [FieldOffset(28)] internal int offsetToFamilyNames; ////// Number of family maps in the family maps list. /// [FieldOffset(32)] internal int numberOfFamilyMaps; ////// Number of per-culture family map ranges lists. The range lists should be sorted in /// order of decreasing cultural-specificity, with the default range list (for "any" /// culture) coming last. /// [FieldOffset(36)] internal int numberOfFamilyMapRangeLists; // numberOfFamilyMaps structures of type CachedFamilyMap internal int OffsetToFamilyMaps { get { unsafe { return sizeof(CachedCompositeFamily); } } } // numberOfFamilyMapRangeLists structures of type CachedFamilyMapRangeList internal int OffsetToFamilyMapRangeLists { get { unsafe { return OffsetToFamilyMaps + numberOfFamilyMaps * sizeof(CachedFamilyMap); } } } // [8 byte align barrier] numberOfTypefaces structures of type CachedCompositeFace internal int OffsetToFamilyTypeface { get { unsafe { return Util.Align8( OffsetToFamilyMapRangeLists + numberOfFamilyMapRangeLists * sizeof(CachedFamilyMapRangeList) ); } } } // variable-length array of char containing null-terminated family names internal int OffsetToTargetFamilyNameStrings { get { unsafe { return OffsetToFamilyTypeface + numberOfTypefaces * sizeof(CachedCompositeFace); } } } } ////// CachedFace is a base for CachedPhysicalFace and CachedCompositeFace /// We can't use inheritance here, so we much keep the three structures in sync. /// [StructLayout(LayoutKind.Explicit, Size = 12)] internal struct CachedFace { [FieldOffset(0)] internal FontStyle style; [FieldOffset(4)] internal FontWeight weight; [FieldOffset(8)] internal FontStretch stretch; } ////// Memory layout: PhysicalFace struct | font Uri string /// [StructLayout(LayoutKind.Explicit, Size = 24)] internal struct CachedPhysicalFace { [FieldOffset(0)] internal FontStyle style; [FieldOffset(4)] internal FontWeight weight; [FieldOffset(8)] internal FontStretch stretch; // the Uri of the font face [FieldOffset(12)] internal int fontUriSize; [FieldOffset(16)] internal int faceIndex; [FieldOffset(20)] internal StyleSimulations styleSimulations; } // since we have doubles in this structure, // always make its size aligned on 8 byte boundary [StructLayout(LayoutKind.Explicit, Size = 64)] internal struct CachedCompositeFace { [FieldOffset(0)] internal FontStyle fontStyle; [FieldOffset(4)] internal FontWeight fontWeight; [FieldOffset(8)] internal FontStretch fontStretch; [FieldOffset(12)] internal int offsetToDeviceFont; [FieldOffset(16)] internal double underlinePosition; [FieldOffset(24)] internal double underlineThickness; [FieldOffset(32)] internal double strikeThroughPosition; [FieldOffset(40)] internal double strikeThroughThickness; [FieldOffset(48)] internal double capsHeight; [FieldOffset(56)] internal double xHeight; }; // Memory layout: CachedDeviceFont | string | array of int. The size of // the string and the integer array is variable, but we never store this structure in an // array; its offset from the corresponding CachedCompositeFace is specified by the latter // structure's offsetToDeviceFont field. 8-byte alignment is required. [StructLayout(LayoutKind.Explicit, Size = 8)] internal struct CachedDeviceFont { internal CachedDeviceFont(string name, int pageCount) { numberOfPages = pageCount; sizeOfDeviceFontName = Util.StringSize(name); } // Number of pages of integers in the character map. [FieldOffset(0)] internal int numberOfPages; // Size of the name in bytes; must be the last field in the structure // as the device font name immediately follows [FieldOffset(4)] internal int sizeOfDeviceFontName; // Offset of the sizeOfDeviceFontName field, which is the last field in // this structure and is immediately followed by character data internal const int OffsetToLengthPrefixedName = 4; // Offset to an array of integers. The first CharacterMetricsDictionary.PageCount // integers compose the "page table". It is followed by one or more "pages" each // CharacterMetricsDictionary.PageSize integers. // // The high-order byte of a Unicode scalar value is an index into the page table, // and the integer at that index is either zero (if the character is not mapped) // or the index of the first element of the correct page for that range of characters. // The low-order byte of the Unicode value is an index into the page, and the integer // at that index is either zero (if the character is not mapped) or the byte offset // of a CachedCharacterMetrics structure relative to the CachedDeviceFont. internal int OffsetToCharacterMap { get { unsafe { return Util.Align4( sizeof(CachedDeviceFont) + sizeOfDeviceFontName ); } } } // Gets the total size of the cached device font, including the device font name // and character map, padded to an 8-byte boundary. The padding is so the the // return value can be used as the offset to a subsequent device font. internal int SizeOfCachedDeviceFont { get { unsafe { // The character map consist of a "page table" of PageCount elements // followed by a variable number of "pages" of PageSize elements. int characterMapLength = CharacterMetricsDictionary.PageCount + (numberOfPages * CharacterMetricsDictionary.PageSize); return Util.Align8(OffsetToCharacterMap + (characterMapLength * sizeof(int))); } } } } // since we have doubles in this structure, // always make its size aligned on 8 byte boundary [StructLayout(LayoutKind.Explicit, Size = 56)] internal struct CachedCharacterMetrics { [FieldOffset(0)] internal double blackBoxWidth; [FieldOffset(8)] internal double blackBoxHeight; [FieldOffset(16)] internal double baseline; [FieldOffset(24)] internal double leftSideBearing; [FieldOffset(32)] internal double rightSideBearing; [FieldOffset(40)] internal double topSideBearing; [FieldOffset(48)] internal double bottomSideBearing; } // since we have doubles in this structure, // always make its size aligned on 8 byte boundary [StructLayout(LayoutKind.Explicit, Size = 24)] internal struct CachedFamilyMap { [FieldOffset(0)] internal double scaleInEm; [FieldOffset(8)] internal int targetFamilyNameOffset; [FieldOffset(12)] internal int numberOfRanges; [FieldOffset(16)] internal int offsetToRanges; }; [StructLayout(LayoutKind.Explicit, Size = 8)] internal struct CachedCharacterRange { [FieldOffset(0)] internal int first; [FieldOffset(4)] internal uint delta; }; [StructLayout(LayoutKind.Explicit, Size = 12)] internal struct CachedFamilyMapRangeList { [FieldOffset(0)] internal int offsetToIetfLanguageTag; [FieldOffset(4)] internal int lengthOfRanges; [FieldOffset(8)] internal int offsetToRanges; }; [StructLayout(LayoutKind.Explicit, Size = 40)] internal struct CachedPhysicalFamily { ////// Composite or physical /// [FieldOffset(0)] internal FamilyType familyType; ////// Number of typefaces in this family. /// [FieldOffset(4)] internal int numberOfTypefaces; [FieldOffset(8)] internal double baseline; [FieldOffset(16)] internal double lineSpacing; ////// Number of localized names for this family. /// [FieldOffset(24)] internal int numberOfFamilyNames; ////// Pointer to the CachedName[numberOfFamilyNames] array of localized names for this family, /// sorted by culture info string. /// [FieldOffset(28)] internal int offsetToFamilyNames; [FieldOffset(32)] internal int numberOfFaceNames; [FieldOffset(36)] internal bool symbol; // numberOfFaceNames face names of type CachedName follow // this sorted list is used for lookup by name // numberOfTypefaces face pointers of type int follow // this list is used for face enumeration }; internal class BaseFamily { ////// cacheOffset is used to reference the cached representation of the family /// internal int cacheOffset = Util.nullOffset; ////// A sorted list of LocalizedStrings containing all names for this family. /// Values don't matter. /// internal SortedDictionaryfamilyNames = new SortedDictionary (LocalizedName.LanguageComparer); } internal class PhysicalFamily : BaseFamily { // PhysicalFamily contains less members than CachedPhysicalFamily // because they can be computed only when we have the full list of families and faces internal SortedDictionary faceNames; internal List typefaces = new List (1); } private class CompositeFamily : BaseFamily { internal CompositeFontInfo compositeFontInfo; } internal class PhysicalFace { /// /// cacheOffset is used to reference the cached representation of the face /// internal int cacheOffset = Util.nullOffset; ////// Critical - fontUri can contain information about local file system. /// [SecurityCritical] internal string fontUri; internal int faceIndex; internal StyleSimulations styleSimulations; internal FontStyle style; internal FontWeight weight; internal FontStretch stretch; internal double version; internal DateTime timestamp; internal bool symbol; internal ushort designEmHeight; internal ushort designCellAscent; internal ushort designCellDescent; internal int designLineSpacing; internal LocalizedName[] names; ////// Critical: accesses critical fontUri. /// TreatAsSafe: fontUri is copied into another critical member. /// [SecurityCritical, SecurityTreatAsSafe] internal PhysicalFace Clone() { PhysicalFace clone = new PhysicalFace(); clone.fontUri = this.fontUri; clone.faceIndex = this.faceIndex; clone.styleSimulations = this.styleSimulations; clone.style = this.style; clone.weight = this.weight; clone.stretch = this.stretch; clone.version = this.version; clone.timestamp = this.timestamp; clone.symbol = this.symbol; clone.designEmHeight = this.designEmHeight; clone.designCellAscent = this.designCellAscent; clone.designCellDescent = this.designCellDescent; clone.designLineSpacing = this.designLineSpacing; clone.names = this.names; return clone; } } private unsafe struct LocalizedNameDictionary : IDictionary{ private FamilyCollection _familyCollection; private int _numberOfFamilyNames; /// /// Critical: This holds reference to a pointer variable /// [SecurityCritical] private int* _offsetToFamilyNames; ////// Critical: This function acceses _cacher[] and dereferences a pointer /// [SecurityCritical] internal LocalizedNameDictionary(FamilyCollection familyCollection, CachedFamily* cachedFamily) { Invariant.Assert(cachedFamily!=null); _numberOfFamilyNames = cachedFamily->numberOfFamilyNames; _familyCollection = familyCollection; _offsetToFamilyNames = (int*)_familyCollection._cacher[cachedFamily->offsetToFamilyNames]; Invariant.Assert(_numberOfFamilyNames >= 1); Invariant.Assert(*_offsetToFamilyNames != 0 && *_offsetToFamilyNames != Util.nullOffset); } ////// Returns a name that uniquely identifies this family. /// The name culture doesn't matter, as the name is supposed to be used only /// for FontFamily construction. /// ////// /// Critical:This code acceses _offsetToFamilyName /// TreatAsSafe: This information is ok to expose and the pointer it accesses is critical /// and all creation is tracked /// internal string OrdinaryName { [SecurityCritical,SecurityTreatAsSafe] get { return _familyCollection.GetStringFromOffset(*(_offsetToFamilyNames + 1)); } } #region IDictionaryMembers bool IDictionary .Remove(XmlLanguage key) { throw new NotSupportedException(); } void IDictionary .Add(XmlLanguage key, string value) { throw new NotSupportedException(); } ICollection IDictionary .Keys { get { // Disable PreSharp warning "Property get methods should not throw exceptions." // Presumably PreSharp is reporting this because the getter allocates memory, but // it is not unreasable for a getter that returns a collection to allocate a new // object. #pragma warning disable 6503 // OK, this is very slow, but semantically correct. // Keys are not used that often anyway. XmlLanguage[] keys = new XmlLanguage[_numberOfFamilyNames]; int i = 0; foreach (KeyValuePair pair in this) { keys[i++] = pair.Key; } return keys; #pragma warning restore 6503 } } string IDictionary .this[XmlLanguage key] { get { // the number of family name localizations is usually small // if we find important families that behave otherwise // we can always change the linear algorithm below to binary search // since the list of family names is already sorted by culture name foreach (KeyValuePair pair in this) { if (pair.Key.Equals(key)) return pair.Value; } return null; } set { throw new NotSupportedException(); } } bool IDictionary .TryGetValue(XmlLanguage key, out string value) { // the number of family name localizations is usually small // if we find important families that behave otherwise // we can always change the linear algorithm below to binary search // since the list of family names is already sorted by culture name foreach (KeyValuePair pair in this) { if (pair.Key.Equals(key)) { value = pair.Value; return true; } } value = null; return false; } ICollection IDictionary .Values { get { // Disable PreSharp warning "Property get methods should not throw exceptions." // Presumably PreSharp is reporting this because the getter allocates memory, but // it is not unreasable for a getter that returns a collection to allocate a new // object. #pragma warning disable 6503 // OK, this is very slow, but semantically correct. // Values are not used that often anyway. string[] values = new string[_numberOfFamilyNames]; int i = 0; foreach (KeyValuePair pair in this) { values[i++] = pair.Value; } return values; #pragma warning restore 6503 } } bool IDictionary .ContainsKey(XmlLanguage key) { // the number of family name localizations is usually small // if we find important families that behave otherwise // we can always change the linear algorithm below to binary search // since the list of family names is already sorted by culture name foreach (KeyValuePair pair in this) { if (pair.Key.Equals(key)) return true; } return false; } #endregion #region ICollection > Members bool ICollection >.Contains(KeyValuePair item) { // the number of family name localizations is usually small // if we find important families that behave otherwise // we can always change the linear algorithm below to binary search // since the list of family names is already sorted by culture name string value = item.Value; foreach (KeyValuePair pair in this) { // We don't use KeyValuePair.Equals because it is inherited from ValueType.Equals // and involves reflection. if (pair.Key.Equals(item.Key) && String.Compare(pair.Value, value, StringComparison.OrdinalIgnoreCase) == 0) return true; } return false; } void ICollection >.Add(KeyValuePair item) { throw new NotSupportedException(); } bool ICollection >.Remove(KeyValuePair item) { throw new NotSupportedException(); } bool ICollection >.IsReadOnly { get { return true; } } void ICollection >.Clear() { throw new NotSupportedException(); } int ICollection >.Count { get { return _numberOfFamilyNames; } } void ICollection >.CopyTo(KeyValuePair [] array, int arrayIndex) { int index = arrayIndex; foreach (KeyValuePair pair in this) { array[index] = pair; ++index; } } #endregion #region IEnumerable > Members IEnumerator > IEnumerable >.GetEnumerator() { return new Enumerator(this); } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(this); } #endregion private unsafe struct Enumerator : IEnumerator > { private int _currentFamilyName; private LocalizedNameDictionary _parent; public Enumerator(LocalizedNameDictionary parent) { _parent = parent; _currentFamilyName = -1; } private void FailIfOutOfRange() { if (_currentFamilyName < 0) throw new InvalidOperationException(SR.Get(SRID.Enumerator_NotStarted)); if (_currentFamilyName >= _parent._numberOfFamilyNames) throw new InvalidOperationException(SR.Get(SRID.Enumerator_ReachedEnd)); } /// /// Critical:This code acceses _offsetToFamilyNames /// TreatAsSafe: This information is ok to expose and the pointer it accesses is critical /// and all creation is tracked /// [SecurityCritical,SecurityTreatAsSafe] private XmlLanguage GetCurrentLanguage() { string languageName = _parent._familyCollection.GetStringFromOffset(*(_parent._offsetToFamilyNames + 2 * _currentFamilyName)); return XmlLanguage.GetLanguage(languageName); } ////// Critical:This code acceses _offsetToFamilyNames /// TreatAsSafe: This information is ok to expose and the pointer it accesses is critical /// and all creation is tracked /// [SecurityCritical, SecurityTreatAsSafe] private string GetCurrentName() { return _parent._familyCollection.GetStringFromOffset(*(_parent._offsetToFamilyNames + 2 * _currentFamilyName + 1)); } #region IEnumerator> Members public bool MoveNext() { ++_currentFamilyName; if (_currentFamilyName >= _parent._numberOfFamilyNames) { // prevent cycling _currentFamilyName = _parent._numberOfFamilyNames; return false; } return true; } /// /// Critical: This function acceses _cacher[] /// TreatAsSafe: This information is ok to expose /// KeyValuePairIEnumerator >.Current { [SecurityCritical,SecurityTreatAsSafe] get { FailIfOutOfRange(); return new KeyValuePair (GetCurrentLanguage(), GetCurrentName()); } } object IEnumerator.Current { get { return ((IEnumerator >)this).Current; } } public void Reset() { _currentFamilyName = -1; } #endregion #region IDisposable Members public void Dispose() { } #endregion } } #endregion Private Types //----------------------------------------------------- // // Private Fields // //----------------------------------------------------- #region Private Fields private FontSourceCollection _fontFolder; /// /// Critical - this value is used to make security decisions (i.e., whether FontSourceCollection /// does an assert) so it can only be set by critical code. /// [SecurityCritical] private bool _isWindowsFonts; ////// Cached result of calling _fontFolder.GetUriString(). /// This string is expensive to recompute and is used very frequently. /// ////// Critical: This function stores the absolute font Uri, which is critical data. /// [SecurityCritical] private string _fontFolderUriString; ////// Critical: This holds reference to a pointer object /// [SecurityCritical] private unsafe Layout* _data; private ElementCacher _cacher; #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
- DocumentViewerHelper.cs
- ToolBarButtonDesigner.cs
- XmlSchemaChoice.cs
- Mappings.cs
- StorageFunctionMapping.cs
- FileSystemEventArgs.cs
- Stacktrace.cs
- PathSegment.cs
- PointUtil.cs
- NonBatchDirectoryCompiler.cs
- WeakKeyDictionary.cs
- InputProcessorProfilesLoader.cs
- DataObjectMethodAttribute.cs
- _IPv4Address.cs
- LoaderAllocator.cs
- TaskHelper.cs
- OutputCacheModule.cs
- VectorValueSerializer.cs
- ImageIndexConverter.cs
- FileDialogCustomPlace.cs
- CutCopyPasteHelper.cs
- CompilationLock.cs
- Win32Interop.cs
- FixedSOMPageElement.cs
- ClientSideQueueItem.cs
- DateTimeFormat.cs
- GenericWebPart.cs
- FlowDocumentFormatter.cs
- ValidationHelper.cs
- AuthenticationModuleElement.cs
- PersonalizationProviderHelper.cs
- CodeTypeMemberCollection.cs
- XmlDocumentFragment.cs
- PrivateFontCollection.cs
- XmlConvert.cs
- DrawingCollection.cs
- System.Data.OracleClient_BID.cs
- WorkflowApplicationUnloadedException.cs
- XmlText.cs
- EventArgs.cs
- SmtpReplyReader.cs
- XmlSerializationGeneratedCode.cs
- Transactions.cs
- BindingContext.cs
- EnumType.cs
- EpmCustomContentSerializer.cs
- CodeCommentStatementCollection.cs
- CoreSwitches.cs
- ClrPerspective.cs
- WinEventQueueItem.cs
- RadioButtonBaseAdapter.cs
- CompilerErrorCollection.cs
- PrePrepareMethodAttribute.cs
- IResourceProvider.cs
- AssociationSet.cs
- TemplatedWizardStep.cs
- __ComObject.cs
- OpenFileDialog.cs
- ProfilePropertySettings.cs
- DataError.cs
- CheckBoxStandardAdapter.cs
- UserControlBuildProvider.cs
- XsltLoader.cs
- DbExpressionBuilder.cs
- MenuAdapter.cs
- _PooledStream.cs
- Int64Animation.cs
- PageThemeBuildProvider.cs
- IResourceProvider.cs
- SiteOfOriginContainer.cs
- Opcode.cs
- DataAdapter.cs
- SQLResource.cs
- PageContentAsyncResult.cs
- SoapTypeAttribute.cs
- Button.cs
- FontSource.cs
- UserControlDocumentDesigner.cs
- RulePatternOps.cs
- EllipseGeometry.cs
- ToolStripLabel.cs
- SyndicationDeserializer.cs
- DataViewSetting.cs
- PropertyMapper.cs
- SchemaNamespaceManager.cs
- Bind.cs
- SequenceFullException.cs
- Selection.cs
- CookieProtection.cs
- TableRowCollection.cs
- NetworkInterface.cs
- Itemizer.cs
- EvidenceBase.cs
- ToolboxBitmapAttribute.cs
- HttpCapabilitiesEvaluator.cs
- MULTI_QI.cs
- Substitution.cs
- CodeTryCatchFinallyStatement.cs
- DbgCompiler.cs
- ManagementObjectCollection.cs