private FontFamilyMap GetTargetFamilyMap( CharacterBufferRange unicodeString, CultureInfo culture, CultureInfo digitCulture, out int cchAdvance ) { DigitMap digitMap = new DigitMap(digitCulture); ushort[] familyMaps = _fontInfo.GetFamilyMapsOfLanguage(XmlLanguage.GetLanguage(culture.IetfLanguageTag)); int sizeofChar = 0; int ch = 0; // skip all the leading joinder characters. They need to be shaped with the // surrounding strong characters. cchAdvance = Classification.AdvanceWhile(unicodeString, ItemClass.JoinerClass); if (cchAdvance >= unicodeString.Length) { // It is rare that the run only contains joiner characters. // If it really happens, just map them to the initial family map. return _fontInfo.GetFamilyMapOfChar( familyMaps, Classification.UnicodeScalar(unicodeString, out sizeofChar) ); } // // If the run starts with combining marks, we will not be able to find base characters for them // within the run. These combining marks will be mapped to their best fonts as normal characters. // ch = Classification.UnicodeScalar( new CharacterBufferRange(unicodeString, cchAdvance, unicodeString.Length - cchAdvance), out sizeofChar ); bool hasBaseChar = !Classification.IsCombining(ch); ch = digitMap[ch]; FontFamilyMap familyMap = _fontInfo.GetFamilyMapOfChar(familyMaps, ch); Invariant.Assert(familyMap != null); for (cchAdvance += sizeofChar; cchAdvance < unicodeString.Length; cchAdvance += sizeofChar) { ch = Classification.UnicodeScalar( new CharacterBufferRange(unicodeString, cchAdvance, unicodeString.Length - cchAdvance), out sizeofChar ); if (Classification.IsJoiner(ch)) continue; // continue to advance if current char is a joiner if (!Classification.IsCombining(ch)) { hasBaseChar = true; } else if (hasBaseChar) { continue; // continue to advance for combining mark with base char } ch = digitMap[ch]; if (_fontInfo.GetFamilyMapOfChar(familyMaps, ch) != familyMap) break; } return familyMap; }
/// <summary> /// Cache index to the list of scaled shapeable typeface /// </summary> private void CacheScaledTypefaceMap( CharacterBufferRange unicodeString, CultureInfo culture, CultureInfo digitCulture, SpanVector scaledTypefaceSpans, ref SpanVector<int> cachedScaledTypefaceIndexSpans, int ichItem ) { IntMap map; if (!_intMaps.TryGetValue(culture, out map)) { map = new IntMap(); _intMaps.Add(culture, map); } DigitMap digitMap = new DigitMap(digitCulture); SpanRider typefaceSpanRider = new SpanRider(scaledTypefaceSpans); int ich = 0; while(ich < unicodeString.Length) { typefaceSpanRider.At(ich); int cch = Math.Min(unicodeString.Length - ich, typefaceSpanRider.Length); int index = IndexOfScaledTypeface((ScaledShapeTypeface)typefaceSpanRider.CurrentElement); Debug.Assert(index >= 0, "Invalid scaled shapeable typeface index spans"); cachedScaledTypefaceIndexSpans.Set(ichItem + ich, cch, index); // we keep index + 1 in the map, so that we leave map entry zero // to indicate uninitialized entry. index++; int sizeofChar; for (int c = 0; c < cch; c += sizeofChar) { int ch = digitMap[ Classification.UnicodeScalar( new CharacterBufferRange(unicodeString, ich + c, unicodeString.Length - ich - c), out sizeofChar ) ]; // only cache typeface map index for base characters if(!Classification.IsCombining(ch) && !Classification.IsJoiner(ch)) { // Dump values of local variables when the condition fails for better debuggability. // We use "if" to avoid the expensive string.Format() in normal case. if (map[ch] != 0 && map[ch] != index) { Invariant.Assert( false, string.Format( CultureInfo.InvariantCulture, "shapeable cache stores conflicting info, ch = {0}, map[ch] = {1}, index = {2}", ch, map[ch], index ) ); } map[ch] = (ushort)index; } } ich += cch; } }
/// <summary> /// Map characters by font family /// </summary> /// <remarks> /// Advance: /// number of characters not mapped to missing glyph /// /// NextValid: /// Offset to the nearest first character not mapped to missing glyph /// /// [Number of invalid characters following valid ones] = NextValid - Advance /// /// A B C D E F G H x x x x x F G H I J /// ---------------> /// Advance /// /// -------------------------> /// NextValid /// /// </remarks> private int MapByFontFamily( CharacterBufferRange unicodeString, CultureInfo culture, CultureInfo digitCulture, IFontFamily fontFamily, CanonicalFontFamilyReference canonicalFamilyReference, FontStyle canonicalStyle, FontWeight canonicalWeight, FontStretch canonicalStretch, ref PhysicalFontFamily firstValidFamily, ref int firstValidLength, IDeviceFont deviceFont, double scaleInEm, int recursionDepth, SpanVector scaledTypefaceSpans, int firstCharIndex, out int nextValid ) { // This is the *one* place where we check for the font mapping depths of the font linking // process. This protects the linking process against extremely long chain of linking or // circular dependencies in the composite fonts. if (recursionDepth >= MaxTypefaceMapDepths) { // Stop the recursion. In effect, this FontFamily does not map any of the input. // Higher-level code must map the input text to some other FontFamily, or to the // "null font" if there is no valid FontFamily. nextValid = 0; return 0; } // If a device font is not already specified higher up the stack, look for a device font // for this font family that matches the typeface style, weight, and stretch. if (deviceFont == null) { deviceFont = fontFamily.GetDeviceFont(_canonicalStyle, _canonicalWeight, _canonicalStretch); } DigitMap digitMap = new DigitMap(digitCulture); int advance = 0; int cchAdvance; int cchNextValid; int ich = 0; nextValid = 0; bool terminated = false; while (ich < unicodeString.Length && !terminated) { // Determine length of run with consistent mapping. Start by assuming we'll be able to // use the whole string, then reduce to the length that can be mapped consistently. int cchMap = unicodeString.Length - ich; // Determine whether the run is using a device font, and limit the run to the // first boundary between device/non-device font usage. bool useDeviceFont = false; if (deviceFont != null) { // Determine whether the first run uses a device font by inspecting the first character. // We do not support device fonts for codepoints >= U+10000 (aka surrogates), so we // don't need to call Classification.UnicodeScalar. useDeviceFont = deviceFont.ContainsCharacter(digitMap[unicodeString[ich]]); // Advance as long as 'useDeviceFont' remains unchanged. int i = ich + 1; while ( (i < unicodeString.Length) && (useDeviceFont == deviceFont.ContainsCharacter(digitMap[unicodeString[i]]))) { i++; } cchMap = i - ich; } // Map as many characters to a family as we can up to the limit (cchMap) just determined. string targetFamilyName; double mapSizeInEm; bool isCompositeFontFamily = fontFamily.GetMapTargetFamilyNameAndScale( new CharacterBufferRange( unicodeString, ich, cchMap ), culture, digitCulture, scaleInEm, out cchMap, out targetFamilyName, out mapSizeInEm ); Debug.Assert(cchMap <= unicodeString.Length - ich); CharacterBufferRange mappedString = new CharacterBufferRange( unicodeString, ich, cchMap ); if (!isCompositeFontFamily) { // not a composite font family cchAdvance = MapByFontFaceFamily( mappedString, culture, digitCulture, fontFamily, canonicalStyle, canonicalWeight, canonicalStretch, ref firstValidFamily, ref firstValidLength, useDeviceFont ? deviceFont : null, false, // nullFont mapSizeInEm, scaledTypefaceSpans, firstCharIndex + ich, false, // ignoreMissing out cchNextValid ); } else if (!string.IsNullOrEmpty(targetFamilyName)) { // The base Uri used for resolving target family names is the Uri of the composite font. Uri baseUri = (canonicalFamilyReference != null) ? canonicalFamilyReference.LocationUri : null; // map to the target of the family map cchAdvance = MapByFontFamilyName( mappedString, culture, digitCulture, targetFamilyName, baseUri, ref firstValidFamily, ref firstValidLength, useDeviceFont ? deviceFont : null, mapSizeInEm, recursionDepth + 1, // increment the depth scaledTypefaceSpans, firstCharIndex + ich, out cchNextValid ); } else { // family map lookup returned no target family cchAdvance = 0; cchNextValid = cchMap; } int cchValid = cchMap; int cchInvalid = 0; cchValid = cchAdvance; cchInvalid = cchNextValid; if(cchValid < cchMap) { terminated = true; } advance += cchValid; nextValid = ich + cchInvalid; ich += cchValid; } return advance; }
/// <summary> /// Get spans of index to the list of scaled shapeable typeface of the specified /// character string from the map table /// </summary> private bool GetCachedScaledTypefaceMap( CharacterBufferRange unicodeString, CultureInfo culture, CultureInfo digitCulture, ref SpanVector<int> cachedScaledTypefaceIndexSpans, int ichItem ) { IntMap map; if (!_intMaps.TryGetValue(culture, out map)) { return false; } DigitMap digitMap = new DigitMap(digitCulture); int ich = 0; while (ich < unicodeString.Length) { // Get map entry for first character. int sizeofChar; int ch = digitMap[ Classification.UnicodeScalar( new CharacterBufferRange(unicodeString, ich, unicodeString.Length - ich), out sizeofChar ) ]; ushort firstIndex = map[ch]; if (firstIndex == 0) return false; // Advance past subsequent characters with the same mapping. int cchSpan = sizeofChar; for (; ich + cchSpan < unicodeString.Length; cchSpan += sizeofChar) { ch = digitMap[ Classification.UnicodeScalar( new CharacterBufferRange(unicodeString, ich + cchSpan, unicodeString.Length - ich - cchSpan), out sizeofChar ) ]; if (map[ch] != firstIndex && !Classification.IsCombining(ch) && !Classification.IsJoiner(ch)) break; } // map entry is stored in index+1, since 0 indicates uninitialized entry cachedScaledTypefaceIndexSpans.Set(ichItem + ich, cchSpan, firstIndex - 1); ich += cchSpan; } return true; }