/// <summary> /// Scan through specified character string checking for valid character /// nominal glyph. /// </summary> /// <param name="charBufferRange">character buffer range</param> /// <param name="emSize">height of Em</param> /// <param name="scalingFactor">This is the factor by which we will scale up /// the metrics. Typically this value to used to convert metrics from the real /// space to the ideal space</param> /// <param name="widthMax">maximum width allowed</param> /// <param name="keepAWord">do not stop arbitrarily in the middle of a word</param> /// <param name="numberSubstitution">digits require complex shaping</param> /// <param name="cultureInfo">CultureInfo of the text</param> /// <param name="textFormattingMode">The TextFormattingMode used (Ideal vs. Display)</param> /// <param name="isSideways">Indicates whether to rotate glyphs.</param> /// <param name="breakOnTabs">Determines whether to stop checking at a tab and /// break the run there</param> /// <param name="stringLengthFit">number of character fit in given width</param> /// <returns>whether the specified string can be optimized by nominal glyph lookup</returns> internal bool CheckFastPathNominalGlyphs( CharacterBufferRange charBufferRange, double emSize, double scalingFactor, double widthMax, bool keepAWord, bool numberSubstitution, CultureInfo cultureInfo, TextFormattingMode textFormattingMode, bool isSideways, bool breakOnTabs, out int stringLengthFit ) { stringLengthFit = 0; if (CachedTypeface.NullFont) { return(false); } GlyphTypeface glyphTypeface = TryGetGlyphTypeface(); if (glyphTypeface == null) { return(false); } double totalWidth = 0; int i = 0; ushort blankGlyph = glyphTypeface.BlankGlyphIndex; ushort glyph = blankGlyph; ushort charFlagsMask = numberSubstitution ? (ushort)(CharacterAttributeFlags.CharacterComplex | CharacterAttributeFlags.CharacterDigit) : (ushort)CharacterAttributeFlags.CharacterComplex; ushort charFlags = 0; ushort charFastTextCheck = (ushort)(CharacterAttributeFlags.CharacterFastText | CharacterAttributeFlags.CharacterIdeo); bool symbolTypeface = glyphTypeface.Symbol; if (symbolTypeface) { // we don't care what code points are present if it's a non-Unicode font such as Symbol or Wingdings; // the code points don't have any standardized meanings, and we always want to bypass shaping charFlagsMask = 0; } bool ignoreWidths = widthMax == double.MaxValue; ushort[] glyphIndices = BufferCache.GetUShorts(charBufferRange.Length); MS.Internal.Text.TextInterface.GlyphMetrics[] glyphMetrics = ignoreWidths ? null : BufferCache.GetGlyphMetrics(charBufferRange.Length); glyphTypeface.GetGlyphMetricsOptimized(charBufferRange, emSize, glyphIndices, glyphMetrics, textFormattingMode, isSideways ); double designToEm = emSize / glyphTypeface.DesignEmHeight; // // This block will advance until one of: // 1. The end of the charBufferRange is reached // 2. The charFlags have some of the charFlagsMask values // 3. The glyph is BlankGlyph (space) // 4. Glyph index is 0 (unless symbol font) // // At this point totalWidth includes all of the widths including the stop character (which fits above) // i indexes the next character (not included in the width) // if (keepAWord) { do { char ch = charBufferRange[i++]; if (ch == TextStore.CharLineFeed || ch == TextStore.CharCarriageReturn || (breakOnTabs && ch == TextStore.CharTab)) { --i; break; } else { int charClass = (int)Classification.GetUnicodeClassUTF16(ch); charFlags = Classification.CharAttributeOf(charClass).Flags; charFastTextCheck &= charFlags; glyph = glyphIndices[i - 1]; if (!ignoreWidths) { totalWidth += TextFormatterImp.RoundDip(glyphMetrics[i - 1].AdvanceWidth * designToEm, textFormattingMode) * scalingFactor; } } } while( i < charBufferRange.Length && ((charFlags & charFlagsMask) == 0) && (glyph != 0 || symbolTypeface) && glyph != blankGlyph ); // i is now at a character immediately following a leading blank } // // This block will advance until one of: // 1. The end of the charBufferRange is reached // 2. The charFlags have some of the charFlagsMask values // 3. Glyph index is 0 (unless symbol font) // 4. totalWidth > widthMax // while ( i < charBufferRange.Length && (ignoreWidths || totalWidth <= widthMax) && ((charFlags & charFlagsMask) == 0) && (glyph != 0 || symbolTypeface) ) { char ch = charBufferRange[i++]; if (ch == TextStore.CharLineFeed || ch == TextStore.CharCarriageReturn || (breakOnTabs && ch == TextStore.CharTab)) { --i; break; } else { int charClass = (int)Classification.GetUnicodeClassUTF16(ch); charFlags = Classification.CharAttributeOf(charClass).Flags; charFastTextCheck &= charFlags; glyph = glyphIndices[i - 1]; if (!ignoreWidths) { totalWidth += TextFormatterImp.RoundDip(glyphMetrics[i - 1].AdvanceWidth * designToEm, textFormattingMode) * scalingFactor; } } } BufferCache.ReleaseUShorts(glyphIndices); glyphIndices = null; BufferCache.ReleaseGlyphMetrics(glyphMetrics); glyphMetrics = null; if (symbolTypeface) { // always optimize for non-Unicode font as we don't support shaping or typographic features; // we also don't fall back from non-Unicode fonts so we don't care if there are missing glyphs stringLengthFit = i; return(true); } if (glyph == 0) { // character is not supported by the font return(false); } if ((charFlags & charFlagsMask) != 0) { // complex character encountered, exclude it Debug.Assert(i > 0); if (--i <= 0) { // first char is complex, fail the call return(false); } } stringLengthFit = i; TypographyAvailabilities typography = glyphTypeface.FontFaceLayoutInfo.TypographyAvailabilities; if ((charFastTextCheck & (byte)CharacterAttributeFlags.CharacterFastText) != 0) { // all input code points are Fast Text if ((typography & (TypographyAvailabilities.FastTextTypographyAvailable | TypographyAvailabilities.FastTextMajorLanguageLocalizedFormAvailable ) ) != 0 ) { // Considered too risky to optimize. It is either because the font // has required features or the font has 'locl' lookup for major languages. return(false); } else if ((typography & TypographyAvailabilities.FastTextExtraLanguageLocalizedFormAvailable) != 0) { // The font has 'locl' lookup for FastText code points for non major languages. // Check whether the input is major langauge. If it is, we are still good to optimize. return(MajorLanguages.Contains(cultureInfo)); } else { // No FastText flags are present, safe to optimize return(true); } } else if ((charFastTextCheck & (byte)CharacterAttributeFlags.CharacterIdeo) != 0) { // The input are all ideographs, check the IdeoTypographyAvailable bit. It is safe if // the bit is not set. return((typography & TypographyAvailabilities.IdeoTypographyAvailable) == 0); } else { // for all the rest of the cases, just check whether there is any required typography // present at all. If none exists, it is optimizable. We might under-optimize here but // it will be non-major languages. return((typography & TypographyAvailabilities.Available) == 0); } }
private void ComputeTypographyAvailabilities() { int glyphBitsLength = (GlyphCount + 31) >> 5; uint[] glyphBits = BufferCache.GetUInts(glyphBitsLength); Array.Clear(glyphBits, 0, glyphBitsLength); ushort minGlyphId = 65535; ushort maxGlyphId = 0; WritingSystem[] complexScripts; TypographyAvailabilities typography = TypographyAvailabilities.None; GsubGposTables GsubGpos = new GsubGposTables(this); // preparing the glyph bits. When the bit is set, it means the corresponding // glyph needs to be checked against. for (int i = 0; i < fastTextRanges.Length; i++) { uint[] codepoints = fastTextRanges[i].GetFullRange(); ushort[] glyphIndices = BufferCache.GetUShorts(codepoints.Length); unsafe { fixed(uint *pCodepoints = &codepoints[0]) { fixed(ushort *pGlyphIndices = &glyphIndices[0]) { CharacterMap.TryGetValues(pCodepoints, checked ((uint)codepoints.Length), pGlyphIndices); } } } for (int j = 0; j < codepoints.Length; j++) { ushort glyphId = glyphIndices[j]; if (glyphId != 0) { glyphBits[glyphId >> 5] |= (uint)(1 << (glyphId % 32)); if (glyphId > maxGlyphId) { maxGlyphId = glyphId; } if (glyphId < minGlyphId) { minGlyphId = glyphId; } } } BufferCache.ReleaseUShorts(glyphIndices); } // // Step 1: call OpenType layout engine to test presence of // 'locl' feature. Based on the returned writing systems, set // FastTextMajorLanguageLocalizedFormAvailable bit and // FastTextExtraLanguageLocalizedFormAvailable bit // OpenTypeLayoutResult result; unsafe { result = OpenTypeLayout.GetComplexLanguageList( GsubGpos, LoclFeature, glyphBits, minGlyphId, maxGlyphId, out complexScripts ); } if (result != OpenTypeLayoutResult.Success) { // The check failed. We abort and don't keep partial results that are not reliable _typographyAvailabilities = TypographyAvailabilities.None; return; } else if (complexScripts != null) { // This is the bits for localized form we would want to set // if both bits for localized form were set, we can end the loop earlier TypographyAvailabilities loclBitsTest = TypographyAvailabilities.FastTextMajorLanguageLocalizedFormAvailable | TypographyAvailabilities.FastTextExtraLanguageLocalizedFormAvailable; for (int i = 0; i < complexScripts.Length && typography != loclBitsTest; i++) { if (MajorLanguages.Contains((ScriptTags)complexScripts[i].scriptTag, (LanguageTags)complexScripts[i].langSysTag)) { typography |= TypographyAvailabilities.FastTextMajorLanguageLocalizedFormAvailable; } else { typography |= TypographyAvailabilities.FastTextExtraLanguageLocalizedFormAvailable; } } } // // step 2: continue to find out whether there is common features availabe // in the font for the unicode ranges and set the FastTextTypographyAvailable bit // unsafe { result = OpenTypeLayout.GetComplexLanguageList( GsubGpos, RequiredTypographyFeatures, glyphBits, minGlyphId, maxGlyphId, out complexScripts ); } if (result != OpenTypeLayoutResult.Success) { // The check failed. We abort and don't keep partial results that are not reliable _typographyAvailabilities = TypographyAvailabilities.None; return; } else if (complexScripts != null) { typography |= TypographyAvailabilities.FastTextTypographyAvailable; } // // Step 3: call OpenType layout engine to find out if there is any feature present for // ideographs. Because there are many ideographs to check for, an alternative is to // check for all scripts with the required features in the font by setting all // glyph bits to 1, then see whether CJKIdeograph is in the returned list. // for (int i = 0; i < glyphBitsLength; i++) { glyphBits[i] = 0xFFFFFFFF; } unsafe { result = OpenTypeLayout.GetComplexLanguageList( GsubGpos, RequiredFeatures, glyphBits, minGlyphId, maxGlyphId, out complexScripts ); } if (result != OpenTypeLayoutResult.Success) { // The check failed. We abort and don't keep partial results that are not reliable _typographyAvailabilities = TypographyAvailabilities.None; return; } else if (complexScripts != null) { for (int i = 0; i < complexScripts.Length; i++) { if (complexScripts[i].scriptTag == (uint)ScriptTags.CJKIdeographic) { typography |= TypographyAvailabilities.IdeoTypographyAvailable; } else { typography |= TypographyAvailabilities.Available; } } } if (typography != TypographyAvailabilities.None) { // if any of the bits were set, set TypographyAvailabilities.Avaialble bit // as well to indicate some lookup is available. typography |= TypographyAvailabilities.Available; } _typographyAvailabilities = typography; // Note: we don't worry about calling ReleaseUInts in case of early out for a failure // above. Releasing glyphBits is a performance optimization that is not necessary // for correctness, and not interesting in the rare failure case. BufferCache.ReleaseUInts(glyphBits); }