| File: MS\Internal\FontFace\CompositeFontInfo.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Contents: Composite font info parsed from composite font file using System.Windows.Markup; using System.Windows.Media; namespace MS.Internal.FontFace { /// <summary> /// Composite font info /// </summary> internal sealed class CompositeFontInfo { private LanguageSpecificStringDictionary _familyNames; private double _baseline; private double _lineSpacing; private FamilyTypefaceCollection _familyTypefaces; private FontFamilyMapCollection _familyMaps; private ushort[] _defaultFamilyMapRanges; private Dictionary<XmlLanguage, ushort[]> _familyMapRangesByLanguage; private const int InitialCultureCount = 1; // at least a familyMap for one locale available private const int InitialTargetFamilyCount = 1; /// <summary> /// Construct a composite font /// </summary> internal CompositeFontInfo() { _familyNames = new LanguageSpecificStringDictionary(new Dictionary<XmlLanguage,string>(InitialCultureCount)); _familyMaps = new FontFamilyMapCollection(this); _defaultFamilyMapRanges = EmptyFamilyMapRanges; } /// <summary> /// Called by FontFamilyMapCollection when a FontFamilyMap is being added. /// </summary> internal void PrepareToAddFamilyMap(FontFamilyMap familyMap) { // Validate parameters. ArgumentNullException.ThrowIfNull(familyMap); if (string.IsNullOrEmpty(familyMap.Target)) throw new ArgumentException(SR.FamilyMap_TargetNotSet); // If it's culture-specific make sure it's in the hash table. if (familyMap.Language != null) { if (_familyMapRangesByLanguage == null) { _familyMapRangesByLanguage = new Dictionary<XmlLanguage, ushort[]>(InitialCultureCount); _familyMapRangesByLanguage.Add(familyMap.Language, EmptyFamilyMapRanges); } else if (!_familyMapRangesByLanguage.ContainsKey(familyMap.Language)) { _familyMapRangesByLanguage.Add(familyMap.Language, EmptyFamilyMapRanges); } } } #region family map ranges (skip lists) /// <summary> /// FontFamilyMap ranges (aka. skip lists) are an optimization to speed up family map lookup. /// /// OBSERVABLE BEHAVIOR /// /// The observable behavior of family map lookup should be as if we traverse the /// list of family maps sequentially and return the first one that matches both /// the text culture and the code point. /// /// The language matches if the family map language is null, or is equal to the text /// language, or if the family map language's "range" includes the text culture. /// This logic is implemented by FontFamilyMap.MatchCulture() and /// FontFamilyMap.MatchLanguage, which call XmlLanguage.RangeIncludes(). /// /// /// SKIP LISTS /// /// Skip lists allow us to avoid doing the culture comparisons described above on /// every character lookup. Instead, we generate a skip list once the first time we /// use a particular culture, and then use the skip list to determine which family /// maps to look at and which to skip. /// /// A skip list is an array of ushort. The first array member represents the size of /// the family map list and is used to determine whether the skip list is invalid /// (see Invalidating Skip Lists). The remainder of the skip list (beginning at /// at index FirstFamilyMapRange) consists of pairs of ushort values. Each pair /// denotes a range of family maps in the family maps list; the first member of the /// pair is the index of the first element in the range, and the second is the index /// one past the last element in the range. Collectively, these ranges include all of /// the family maps that should be included in the lookup for a culture, i.e., the /// culture associated with the skip list. /// /// Following is an example of a family map list and the corresponding skip lists: /// /// 0 1 2 3 4 5 6 7 8 /// +-------+-------+-------+-------+-------+-------+-------+-------+ /// | ja | ja | ko | ko | zh-CHT| zh-CHS| any | any | /// +-------+-------+-------+-------+-------+-------+-------+-------+ /// /// "ja" -> (0,2) (6,8) /// "ko" -> (2,4) (6,8) /// "zh-CHT" -> (4,5) (6,8) /// "zh-CHS" -> (5,8) /// default -> (6,8) /// /// INVALIDATING SKIP LISTS /// /// A skip list becomes invalid whenever the family map list changes. To avoid /// recreating skip lists every time a family map is added, skip lists are created /// lazily. Skip lists are added to the _familyMapRangesByLanguage hash table as /// family maps are added, but each skip list is initialized by EmptyFamilyMapRanges. /// /// After a skip list has been created, a may be rendered invalid by subsequent /// changes to the family map list. We have two mechanisms to detect this. /// /// (1) Each skip list includes (as its first member) the size of the family /// map when the skip list was created. Additions to or insertions into /// the list can therefore be detected because the sizes no longer match. /// /// (2) For all other changes (removing items changing items), the FamilyMaps /// list calls InvalidateFamilyMapRanges(), which setes all skip lists to /// EmptyFamilyMapRanges. /// /// </summary> private static readonly ushort[] EmptyFamilyMapRanges = new ushort[] { 0 }; private const int InitialFamilyMapRangesCapacity = 7; // count + 3 ranges internal const int FirstFamilyMapRange = 1; /// <summary> /// Called by FontFamilyMapCollection when a change occurs that renders all /// family map ranges potentially invalid. /// </summary> internal void InvalidateFamilyMapRanges() { _defaultFamilyMapRanges = EmptyFamilyMapRanges; if (_familyMapRangesByLanguage != null) { Dictionary<XmlLanguage, ushort[]> table = new Dictionary<XmlLanguage, ushort[]>(_familyMapRangesByLanguage.Count); foreach (XmlLanguage language in _familyMapRangesByLanguage.Keys) { table.Add(language, EmptyFamilyMapRanges); } _familyMapRangesByLanguage = table; } } /// <summary> /// Returns information about which family maps apply to the specified culture. /// The return value is used by GetFamilyMapOfChar. /// </summary> internal ushort[] GetFamilyMapsOfLanguage(XmlLanguage language) { ushort[] ranges = null; // Look for a family map range for the specified language or one of its matching languages if (_familyMapRangesByLanguage != null && language != null) { foreach (XmlLanguage matchingLanguage in language.MatchingLanguages) { // break out of loop to handle default list of ranges if (matchingLanguage.IetfLanguageTag.Length == 0) break; if (_familyMapRangesByLanguage.TryGetValue(matchingLanguage, out ranges)) { // Recreate the list of ranges if we've added more family maps. if (!IsFamilyMapRangesValid(ranges)) { ranges = CreateFamilyMapRanges(matchingLanguage); _familyMapRangesByLanguage[matchingLanguage] = ranges; } return ranges; } } } // Use the default list of ranges (containing only family maps that match // any culture); recreate it if we've added more family maps. if (!IsFamilyMapRangesValid(_defaultFamilyMapRanges)) { _defaultFamilyMapRanges = CreateFamilyMapRanges(null); } return _defaultFamilyMapRanges; } /// <summary> /// Gets the first FontFamilyMap that matches the specified Unicode scalar value. /// </summary> /// <param name="familyMapRanges">Return value of GetFamilyMapsOfCulture.</param> /// <param name="ch">Character to map.</param> /// <returns>FontFamilyMap or null.</returns> internal FontFamilyMap GetFamilyMapOfChar(ushort[] familyMapRanges, int ch) { Debug.Assert(IsFamilyMapRangesValid(familyMapRanges)); // Iterate over the ushort pairs in the skip list. for (int i = FirstFamilyMapRange; i < familyMapRanges.Length; i += 2) { // Each pair specifies a range in the family map list. int begin = familyMapRanges[i]; int end = familyMapRanges[i + 1]; Debug.Assert(begin < end && end <= _familyMaps.Count); // Iterate over the family maps in the specified range. for (int j = begin; j < end; ++j) { FontFamilyMap familyMap = _familyMaps[j]; Invariant.Assert(familyMap != null); if (familyMap.InRange(ch)) return familyMap; } } return FontFamilyMap.Default; } private bool IsFamilyMapRangesValid(ushort[] familyMapRanges) { return familyMapRanges[0] == _familyMaps.Count; } private ushort[] CreateFamilyMapRanges(XmlLanguage language) { // We could use an ArrayList, but a ushort[] is not much more code // and requires many fewer boxed objects. ushort[] ranges = new ushort[InitialFamilyMapRangesCapacity]; ranges[0] = (ushort)_familyMaps.Count; int count = 1; Debug.Assert(count == FirstFamilyMapRange); for (int i = 0; i < _familyMaps.Count; ++i) { if (FontFamilyMap.MatchLanguage(_familyMaps[i].Language, language)) { // grow ranges if necessary. if (count + 2 > ranges.Length) { ushort[] temp = new ushort[ranges.Length * 2 - FirstFamilyMapRange]; ranges.CopyTo(temp, 0); ranges = temp; } // beginning of range ranges[count++] = (ushort)i; ++i; while (i < _familyMaps.Count && FontFamilyMap.MatchLanguage(_familyMaps[i].Language, language)) { ++i; } // end of range, i.e., last index + 1 ranges[count++] = (ushort)i; } } // reallocate ranges to the exact size required if (count < ranges.Length) { ushort[] temp = new ushort[count]; Array.Copy(ranges, temp, count); ranges = temp; } return ranges; } #endregion /// <summary> /// List of typefaces; can be null. /// </summary> internal FamilyTypefaceCollection FamilyTypefaces { get { return _familyTypefaces; } } /// <summary> /// Gets the list of family typefaces, creating it if necessary. /// </summary> internal FamilyTypefaceCollection GetFamilyTypefaceList() { if (_familyTypefaces == null) _familyTypefaces = new FamilyTypefaceCollection(); return _familyTypefaces; } /// <summary> /// Distance from character cell top to English baseline relative to em size. /// </summary> internal double Baseline { get { return _baseline; } set { CompositeFontParser.VerifyNonNegativeMultiplierOfEm(nameof(Baseline), ref value); _baseline = value; } } /// <summary> /// Additional line spacing after Height relative to em size /// </summary> internal double LineSpacing { get { return _lineSpacing; } set { CompositeFontParser.VerifyPositiveMultiplierOfEm(nameof(LineSpacing), ref value); _lineSpacing = value; } } /// <summary> /// Dictionary of names by culture. /// </summary> internal LanguageSpecificStringDictionary FamilyNames { get { return _familyNames; } } /// <summary> /// List of family maps. /// </summary> internal FontFamilyMapCollection FamilyMaps { get { return _familyMaps; } } /// <summary> /// Collection of cultures associated with family maps; can be null /// if all family maps are culture-independent. /// </summary> internal ICollection<XmlLanguage> FamilyMapLanguages { get { if (_familyMapRangesByLanguage != null) return _familyMapRangesByLanguage.Keys; else return null; } } } }