Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / wpf / src / Core / CSharp / MS / Internal / FontFace / FontDriver.cs / 2 / FontDriver.cs
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Security; using System.Text; using System.Windows; using System.Windows.Markup; // for XmlLanguage using System.Windows.Media; using MS.Internal; using MS.Internal.PresentationCore; using MS.Utility; using MS.Internal.FontCache; using MS.Internal.FontRasterization; using Adobe.CffRasterizer; // 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.FontFace { ////// Font technology. /// internal enum FontTechnology { // this enum need to be kept in order of preference that we want to use with duplicate font face, // highest value will win in case of duplicate PostscriptOpenType, TrueType, TrueTypeCollection } internal class TrueTypeFontDriver { #region Font constants, structures and enumerations /* OS/2 table */ private const int OFF_OS2_version = 0; // private const int OFF_OS2_xAvgCharWidth = 2; private const int OFF_OS2_usWeightClass = 4; private const int OFF_OS2_usWidthClass = 6; private const int OFF_OS2_fsType = 8; // private const int OFF_OS2_ySubscriptXSize = 10; // private const int OFF_OS2_ySubscriptYSize = 12; // private const int OFF_OS2_ySubscriptXOffset = 14; // private const int OFF_OS2_ySubscriptYOffset = 16; // private const int OFF_OS2_ySuperScriptXSize = 18; // private const int OFF_OS2_ySuperScriptYSize = 20; // private const int OFF_OS2_ySuperScriptXOffset = 22; // private const int OFF_OS2_ySuperScriptYOffset = 24; private const int OFF_OS2_yStrikeOutSize = 26; private const int OFF_OS2_yStrikeOutPosition = 28; // private const int OFF_OS2_sFamilyClass = 30; // private const int OFF_OS2_Panose = 32; // private const int OFF_OS2_ulCharRange = 42; // private const int OFF_OS2_achVendID = 58; private const int OFF_OS2_usSelection = 62; // private const int OFF_OS2_usFirstChar = 64; // private const int OFF_OS2_usLastChar = 66; private const int OFF_OS2_sTypoAscender = 68; private const int OFF_OS2_sTypoDescender = 70; private const int OFF_OS2_sTypoLineGap = 72; private const int OFF_OS2_usWinAscent = 74; private const int OFF_OS2_usWinDescent = 76; // private const int OFF_OS2_ulCodePageRange1 = 78; // private const int OFF_OS2_ulCodePageRange2 = 82; // fields below are valid when version >= 2 private const int OFF_OS2_sxHeight = 86; private const int OFF_OS2_sCapHeight = 88; [Flags] private enum Os2SelectionFlags { Italic = 1, Underscore = 2, Negative = 4, Outlined = 8, Strikeout = 16, Bold = 32, Regular = 64, DontUseWinLineMetrics = 128, WeightWidthSlopeOnly = 256, Oblique = 512 } private static class Os2EmbeddingFlags { public const ushort RestrictedLicense = 0x0002; public const ushort PreviewAndPrint = 0x0004; public const ushort Editable = 0x0008; // The font is installable if all bits in the InstallableMask are set to zero. public const ushort InstallableMask = RestrictedLicense | PreviewAndPrint | Editable; public const ushort NoSubsetting = 0x0100; public const ushort BitmapOnly = 0x0200; } /* head table */ private const int OFF_head_flags = 16; private const int OFF_head_unitsPerEm = 18; private const int OFF_head_macStyle = 44; private const int OFF_head_indexToLocFormat = 50; [Flags] private enum HeadFlags { OptimizedForClearType = 8192 // bit 13 } /* hhea table */ private const int OFF_hhea_ascender = 4; private const int OFF_hhea_descender = 6; private const int OFF_hhea_lineGap = 8; private const int OFF_hhea_numberOfHMetrics = 34; // maxp table private const int OFF_maxp_numGlyphs = 4; /* post table */ private const int OFF_post_underlinePosition = 8; private const int OFF_post_underlineThickness = 10; /* PCLT table */ private const int OFF_PCLT_xHeight = 10; private const int OFF_PCLT_CapHeight = 16; // glyf table private const int OFF_glyf_xMin = 2; private const int OFF_glyf_yMin = 4; private const int OFF_glyf_xMax = 6; private const int OFF_glyf_yMax = 8; // vhea table private const int OFF_vhea_numOfLongVerMetrics = 34; // eblc table private const int OFF_eblc_numSizes = 4; private const int OFF_eblc_bitmapSizeTables = 8; private const int EblcSizeOfBitmapSizeTable = 48; // within bitmapSizeTable private const int EblcIndexSubTableArrayOffset = 0; private const int EblcNumberOfIndexSubTables = 8; // within index subtable private const int EblcFirstGlyph = 0; private const int EblcLastGlyph = 2; private const int EblcSizeOfIndexSubTable = 8; private class EastAsianLanguageCandidate { public EastAsianLanguageCandidate(int languageIndex) { _languageIndex = languageIndex; } public void AddCodepoint(int index) { _codepointsFound |= (1 << index); } public int CodePoints { get { return _codepointsFound; } } public int LanguageIndex { get { return _languageIndex; } } private int _languageIndex; private int _codepointsFound; } [Flags] private enum MacStyleFlags { Bold = 1, Italic = 2, Underline = 4, Outline = 8, Shadow = 16, Condensed = 32, Extended = 64 } internal struct ParsedNameTable { // LocalizedName arrays generated by FontDriver are sorted by OriginalLCID value. internal LocalizedName[] familyNames; internal LocalizedName[] win32FamilyNames; internal LocalizedName[] faceNames; internal LocalizedName[] win32faceNames; internal LocalizedName[] versionStrings; internal LocalizedName[] copyrights; internal LocalizedName[] manufacturerNames; internal LocalizedName[] trademarks; internal LocalizedName[] designerNames; internal LocalizedName[] descriptions; internal LocalizedName[] vendorUrls; internal LocalizedName[] designerUrls; internal LocalizedName[] licenseDescriptions; internal LocalizedName[] sampleTexts; internal double version; } ////// BasicFontFaceInfo contains properties required for font enumeration and style matching /// internal struct BasicFontFaceInfo { internal ParsedNameTable nameTable; internal int faceIndex; internal FontStyle style; internal FontWeight weight; internal FontStretch stretch; internal bool symbol; internal ushort designEmHeight; internal ushort designCellAscent; internal ushort designCellDescent; internal int designLineSpacing; } private struct DirectoryEntry { internal TrueTypeTags tag; internal CheckedPointer pointer; } private enum TrueTypeTags : int { CharToIndexMap = 0x636d6170, /* 'cmap' */ ControlValue = 0x63767420, /* 'cvt ' */ BitmapData = 0x45424454, /* 'EBDT' */ BitmapLocation = 0x45424c43, /* 'EBLC' */ BitmapScale = 0x45425343, /* 'EBSC' */ Editor0 = 0x65647430, /* 'edt0' */ Editor1 = 0x65647431, /* 'edt1' */ Encryption = 0x63727970, /* 'cryp' */ FontHeader = 0x68656164, /* 'head' */ FontProgram = 0x6670676d, /* 'fpgm' */ GridfitAndScanProc = 0x67617370, /* 'gasp' */ GlyphDirectory = 0x67646972, /* 'gdir' */ GlyphData = 0x676c7966, /* 'glyf' */ HoriDeviceMetrics = 0x68646d78, /* 'hdmx' */ HoriHeader = 0x68686561, /* 'hhea' */ HorizontalMetrics = 0x686d7478, /* 'hmtx' */ IndexToLoc = 0x6c6f6361, /* 'loca' */ Kerning = 0x6b65726e, /* 'kern' */ LinearThreshold = 0x4c545348, /* 'LTSH' */ MaxProfile = 0x6d617870, /* 'maxp' */ NamingTable = 0x6e616d65, /* 'name' */ OS_2 = 0x4f532f32, /* 'OS/2' */ Postscript = 0x706f7374, /* 'post' */ PreProgram = 0x70726570, /* 'prep' */ VertDeviceMetrics = 0x56444d58, /* 'VDMX' */ VertHeader = 0x76686561, /* 'vhea' */ VerticalMetrics = 0x766d7478, /* 'vmtx' */ PCLT = 0x50434C54, /* 'PCLT' */ TTO_GSUB = 0x47535542, /* 'GSUB' */ TTO_GPOS = 0x47504F53, /* 'GPOS' */ TTO_GDEF = 0x47444546, /* 'GDEF' */ TTO_BASE = 0x42415345, /* 'BASE' */ TTO_JSTF = 0x4A535446, /* 'JSTF' */ OTTO = 0x4f54544f, // Adobe OpenType 'OTTO' TTC_TTCF = 0x74746366 // 'ttcf' } // Name table constants: http://www.microsoft.com/typography/otspec/name.htm private enum NameTableNameID { CopyrightNotice = 0, FontFamilyName = 1, FontSubfamilyName = 2, UniqueFontIdentifier = 3, FullFontName = 4, Version = 5, PostscriptName = 6, Trademark = 7, ManufacturerName = 8, Designer = 9, Description = 10, URLVendor = 11, URLDesigner = 12, LicenseDescription = 13, LicenseInfoURL = 14, Reserved = 15, PreferredFamily = 16, PreferredSubfamily = 17, CompatibleFullName = 18, SampleText = 19, PostScriptCIDName = 20 } private enum PlatformID : ushort { Unicode = 0, Macintosh = 1, // Only MacRoman character set supported ISO = 2, // Deprecated since OpenType 1.3, not supported Microsoft = 3, Custom = 4 // Not supported } private enum NameTableMicrosoftEncodingID : ushort { Symbol = 0, UnicodeBMPOnly = 1, ShiftJIS = 2, PRC = 3, Big5 = 4, Wansung = 5, Johab = 6, // the gap is intentional, values from 7 to 9 are reserved per OpenType spec Unicode = 10 } ////// CharacterEncoding represents all supported platform and encoding combinations. /// Platforms and encodings are used by both the name and cmap tables, /// although not all combinations are used by both tables. /// /// Where multiple cmaps are available, higher numbered encodings are chosen /// in preference to lower numbered encodings. /// private enum CharacterEncoding { // plat enc cmap range Unknown = 0, // ==== === ========== MacRoman = 1, // 1 0 Unicode = 2, // 0 any MsSymbol = 3, // 3 0 up to 2^8 characters at U+F000 MsShiftJis = 4, // 3 2 up to 2^16 characters MsPrc = 5, // 3 3 up to 2^16 characters MsBig5 = 6, // 3 4 up to 2^16 characters MsWansung = 7, // 3 5 up to 2^16 characters MsUcs2 = 8, // 3 1 up to 2^16 characters, doesn't include surrogates MsUcs4 = 9, // 3 10 full Unicode repertoire, includes surrogates } private struct MetricSearchList { internal static readonly ushort[,] xHeight = { { 0x78, 100 }, // basic Latin, lower case letter x { 0x3C4, 100 }, // Greek, small letter tau { 0x433, 100 }, // Cyrillic, small letter ghe { 0x578, 100 }, // Armenian, small letter vo { 0x5DD, 75 }, // Hebrew, letter final mem { 0x62F, 100 }, // Arabic, letter dal { 0x717, 100 }, // Syriac, letter he { 0x784, 100 }, // Thaana, letter baa { 0x930, 50 }, // Devaganari, 1/2 of letter ra { 0xA30, 50 }, // Gurmukhi, 1/2 of letter ra { 0xAA0, 50 }, // Gujarati, 1/2 of letter ttha { 0xB20, 50 }, // Oriya, 1/2 of letter ttha { 0xBB0, 50 }, // Tamil, 1/2 of letter ra { 0xC10, 100 }, // Telugo, letter ai { 0xC89, 50 }, // Kannada, 1/2 of letter U { 0xD30, 66 }, // Malayalam, 2/3 of letter ra { 0xDB0, 50 }, // Sinhala, 1/2 of letter mahaapraanadayanna { 0xE20, 75 }, // Thai, 75% of letter pho samphao { 0xEC0, 50 }, // Lao, 1/2 of vowel sign E { 0xF40, 100 }, // Tibetan, letter ka { 0x1010, 100 }, // Myanmar, letter ta { 0x10F2, 100 }, // Georgian, letter hie { 0x1100, 50 }, // Hangul Jamo, 1/2 of choseong kieok { 0x1210, 50 }, // Ethiopic, 1/2 of syllable hha { 0x13A0, 50 }, // Cherokee, 1/2 of letter A { 0x14C0, 100 }, // Unified Canadian Aboriginal Syllabics, ne { 0x1681, 100 }, // Ogham, letter beith { 0x17A0, 50 }, // Khmer, 1/2 of letter ha { 0x1884, 100 } // Mongolian, letter ali gali inverted ubadama }; internal static readonly ushort[,] capsHeight = { { 0x48, 100 }, // basic Latin, upper case letter H }; } private struct EastAsianLanguages { internal static readonly char[,] RepresentativeCodepoints = { // Hiragana { '\u3044', '\u3046', '\u3093', '\u3057', '\u306E', '\u304B' }, // Hangul { '\uC774', '\uB2E4', '\uB294', '\uC758', '\uC5D0', '\uD558' }, // CHS { '\u6C49', '\u5B57', '\u4E2D', '\u7684', '\u4E2A', '\u4EEC' }, // CHT { '\u6F22', '\u5011', '\u4E09', '\u4E86', '\u5B78', '\u7232' } }; } ////// Font families that look too thin with default text contrast settings. /// For such fonts we increase text contrast value by 2. /// The list must be sorted alphabetically. /// private static readonly string[] _thinFontFamilyNames = { "Courier New", "Fixed Miriam Transparent", "Miriam Fixed", "Rod", "Rod Transparent", "Simplified Arabic Fixed" }; private static readonly CultureInfo EnglishUSCulture = System.Windows.Markup.TypeConverterHelper.EnglishUSCulture; #endregion #region Byte, Short, Long etc. accesss to CheckedPointers ////// The follwoing APIs extract OpenType variable types from OpenType font /// files. OpenType variables are stored big-endian, and the type are named /// as follows: /// Byte - signed 8 bit /// UShort - unsigned 16 bit /// Short - signed 16 bit /// ULong - unsigned 32 bit /// Long - signed 32 bit /// /// ////// Critical: Calls into probe which is critical and also has unsafe code blocks /// TreatAsSafe: This code is Ok to expose /// [SecurityCritical,SecurityTreatAsSafe] private static byte ReadOpenTypeByte(CheckedPointer pointer) { unsafe { byte * readBuffer = (byte *)pointer.Probe(0, 1); return readBuffer[0]; } } ////// Critical: Calls into probe which is critical and also has unsafe code blocks /// TreatAsSafe: This code is Ok to expose /// [SecurityCritical,SecurityTreatAsSafe] private static ushort ReadOpenTypeUShort(CheckedPointer pointer) { unsafe { byte * readBuffer = (byte *)pointer.Probe(0, 2); ushort result = (ushort)((readBuffer[0] << 8) + readBuffer[1]); return result; } } ////// Critical: Calls into probe which is critical and also has unsafe code blocks /// TreatAsSafe: This code is Ok to expose /// [SecurityCritical,SecurityTreatAsSafe] private static short ReadOpenTypeShort(CheckedPointer pointer) { unsafe { byte * readBuffer = (byte *)pointer.Probe(0, 2); short result = (short)((readBuffer[0] << 8) + readBuffer[1]); return result; } } ////// Critical: Calls into probe which is critical and also has unsafe code blocks /// TreatAsSafe: This code IS Ok to expose /// [SecurityCritical,SecurityTreatAsSafe] private static int ReadOpenTypeLong(CheckedPointer pointer) { unsafe { byte * readBuffer = (byte *)pointer.Probe(0, 4); int result = (int)((((((readBuffer[0] << 8) + readBuffer[1]) << 8) + readBuffer[2]) << 8) + readBuffer[3]); return result; } } ////// Critical: Calls into probe which is critical and also has unsafe code blocks /// TreatAsSafe: This code is ok to expose /// [SecurityCritical,SecurityTreatAsSafe] private static uint ReadOpenTypeULong(CheckedPointer pointer) { unsafe { byte * readBuffer = (byte *)pointer.Probe(0, 4); uint result = (uint)((((((readBuffer[0] << 8) + readBuffer[1]) << 8) + readBuffer[2]) << 8) + readBuffer[3]); return result; } } #endregion Byte, Short, Long etc. accesss to CheckedPointers #region Constructor and general helpers ////// Critical: constructs data for a checked pointer. /// [SecurityCritical] internal TrueTypeFontDriver(UnmanagedMemoryStream unmanagedMemoryStream, Uri sourceUri) { _sourceUri = sourceUri; _unmanagedMemoryStream = unmanagedMemoryStream; _fileStream = new CheckedPointer(unmanagedMemoryStream); try { CheckedPointer seekPosition = _fileStream; TrueTypeTags typeTag = (TrueTypeTags)ReadOpenTypeLong(seekPosition); seekPosition += 4; if (typeTag == TrueTypeTags.TTC_TTCF) { // this is a TTC file, we need to decode the ttc header _technology = FontTechnology.TrueTypeCollection; seekPosition += 4; // skip version _numFaces = ReadOpenTypeLong(seekPosition); } else if (typeTag == TrueTypeTags.OTTO) { _technology = FontTechnology.PostscriptOpenType; _numFaces = 1; } else { _technology = FontTechnology.TrueType; _numFaces = 1; } } catch (ArgumentOutOfRangeException e) { // convert exceptions from CheckedPointer to FileFormatException throw new FileFormatException(SourceUri, e); } } internal void SetFace(int faceIndex) { if (_technology == FontTechnology.TrueTypeCollection) { if (faceIndex < 0 || faceIndex >= _numFaces) throw new ArgumentOutOfRangeException("faceIndex"); } else { if (faceIndex != 0) throw new ArgumentOutOfRangeException("faceIndex", SR.Get(SRID.FaceIndexValidOnlyForTTC)); } try { CheckedPointer seekPosition = _fileStream + 4; if (_technology == FontTechnology.TrueTypeCollection) { // this is a TTC file, we need to decode the ttc header // skip version, num faces, OffsetTable array seekPosition += (4 + 4 + 4 * faceIndex); _directoryOffset = ReadOpenTypeLong(seekPosition); seekPosition = _fileStream + (_directoryOffset + 4); // 4 means that we skip the version number } _faceIndex = faceIndex; int numTables = ReadOpenTypeUShort(seekPosition); seekPosition += 2; // quick check for malformed fonts, see if numTables is too large // file size should be >= sizeof(offset table) + numTables * (sizeof(directory entry) + minimum table size (4)) long minimumFileSize = (4 + 2 + 2 + 2 + 2) + numTables * (4 + 4 + 4 + 4 + 4); if (_fileStream.Size < minimumFileSize) { throw new FileFormatException(SourceUri); } _tableDirectory = new DirectoryEntry[numTables]; // skip searchRange, entrySelector and rangeShift seekPosition += 6; // I can't use foreach here because C# disallows modifying the current value for (int i = 0; i < _tableDirectory.Length; ++i) { _tableDirectory[i].tag = (TrueTypeTags)ReadOpenTypeLong(seekPosition); seekPosition += 8; // skip checksum int offset = ReadOpenTypeLong(seekPosition); seekPosition += 4; int length = ReadOpenTypeLong(seekPosition); seekPosition += 4; _tableDirectory[i].pointer = _fileStream.CheckedProbe(offset, length); } } catch (ArgumentOutOfRangeException e) { // convert exceptions from CheckedPointer to FileFormatException throw new FileFormatException(SourceUri, e); } } ////// Critical: This code calls to create a checked pointer and might return a null checked pointer /// All consumers need to validate is null before using it. /// TreatAsSafe: This information is ok to expose /// [SecurityCritical,SecurityTreatAsSafe] private CheckedPointer GetTable(TrueTypeTags tag) { foreach (DirectoryEntry directoryEntry in _tableDirectory) { if (tag == directoryEntry.tag) return directoryEntry.pointer; } return new CheckedPointer(); } #endregion #region Public methods and properties internal int NumFaces { get { return _numFaces; } } private Uri SourceUri { get { return _sourceUri; } } ////// Create font subset that includes glyphs in the input collection. /// ////// TreatAsSafe: This API could be public in terms of security as it demands unmanaged code /// Critical: Does an elevation by calling TrueTypeSubsetter which we are treating as equivalent to /// unsafe native methods /// [SecurityCritical, SecurityTreatAsSafe] internal byte[] ComputeFontSubset(ICollectionglyphs) { SecurityHelper.DemandUnmanagedCode(); int fileSize = _fileStream.Size; unsafe { void * fontData = _fileStream.Probe(0, fileSize); // Since we currently don't have a way to subset CFF fonts, just return a copy of the font. if (_technology == FontTechnology.PostscriptOpenType) { byte[] fontCopy = new byte[fileSize]; Marshal.Copy((IntPtr)fontData, fontCopy, 0, fileSize); return fontCopy; } ushort [] glyphArray; if (glyphs == null || glyphs.Count == 0) glyphArray = null; else { glyphArray = new ushort[glyphs.Count]; glyphs.CopyTo(glyphArray, 0); } return TrueTypeSubsetter.ComputeSubset(fontData, fileSize, SourceUri, _directoryOffset, glyphArray); } } internal void GetBasicFontFaceInfo(ref BasicFontFaceInfo basicFontFaceInfo, out bool skipFontDifferentiation) { try { CheckedPointer headTable = GetTable(TrueTypeTags.FontHeader); CheckedPointer os2Table = GetTable(TrueTypeTags.OS_2); CheckedPointer hheaTable = GetTable(TrueTypeTags.HoriHeader); if (headTable.IsNull || hheaTable.IsNull) { throw new FileFormatException(SourceUri); } basicFontFaceInfo.faceIndex = _faceIndex; ReadStyles(os2Table, headTable, out basicFontFaceInfo.style, out basicFontFaceInfo.weight, out basicFontFaceInfo.stretch, out skipFontDifferentiation); DecodeNameTable(ref basicFontFaceInfo.nameTable); basicFontFaceInfo.symbol = DecodeCmapTable(null); ReadBasicMetrics( headTable, os2Table, hheaTable, out basicFontFaceInfo.designEmHeight, out basicFontFaceInfo.designCellAscent, out basicFontFaceInfo.designCellDescent, out basicFontFaceInfo.designLineSpacing ); } catch (ArgumentOutOfRangeException e) { // convert exceptions from CheckedPointer to FileFormatException throw new FileFormatException(SourceUri, e); } } /// /// Critical: This code sets glyphcount which is used to index into unmanaged pointers /// TreatAsSafe: The value is computed from cache and gettable entries all of which are tracked /// [SecurityCritical,SecurityTreatAsSafe] internal void GetLayoutFontFaceInfo(FontFaceLayoutInfo cache) { try { cache.FontTechnology = _technology; CheckedPointer hheaTable = GetTable(TrueTypeTags.HoriHeader); CheckedPointer headTable = GetTable(TrueTypeTags.FontHeader); CheckedPointer os2Table = GetTable(TrueTypeTags.OS_2); CheckedPointer hmtxTable = GetTable(TrueTypeTags.HorizontalMetrics); CheckedPointer postTable = GetTable(TrueTypeTags.Postscript); if (headTable.IsNull || hheaTable.IsNull || hmtxTable.IsNull) { throw new FileFormatException(SourceUri); } ushort numberOfHMetrics = ReadOpenTypeUShort(hheaTable + OFF_hhea_numberOfHMetrics); if (numberOfHMetrics == 0) throw new FileFormatException(SourceUri); // deduce the number of glyphs from the size of the hmtxTable, should be the same as from maxp cache.GlyphCount = (ushort)((hmtxTable.Size - numberOfHMetrics * 2) / 2); FontStyle fontStyle; FontWeight fontWeight; FontStretch fontStretch; bool skipFontDifferentiation; ReadStyles(os2Table, headTable, out fontStyle, out fontWeight, out fontStretch, out skipFontDifferentiation); cache.Style = fontStyle; cache.Weight = fontWeight; cache.Stretch = fontStretch; // cmap decoding and initialization of special glyph values cache.Symbol = DecodeCmapTable(cache); ushort blankGlyph; cache.CharacterMap.TryGetValue(' ', out blankGlyph); // If the font doesn't support space character, we'll use glyph index zero, // which has a square box shape in most fonts. cache.BlankGlyph = blankGlyph; ushort invalidGlyph; // dotted circle if (!cache.CharacterMap.TryGetValue(0x25CC, out invalidGlyph)) { // dotted circle not found, try NBSP if (!cache.CharacterMap.TryGetValue(0x00A0, out invalidGlyph)) { invalidGlyph = cache.BlankGlyph; // default to the blank glyph } } cache.InvalidGlyph = invalidGlyph; ushort unitsPerEm, designCellAscent, designCellDescent; int designLineSpacing; ReadBasicMetrics( headTable, os2Table, hheaTable, out unitsPerEm, out designCellAscent, out designCellDescent, out designLineSpacing ); cache.DesignEmHeight = unitsPerEm; cache.DesignCellAscent = designCellAscent; cache.DesignCellDescent = designCellDescent; if (!postTable.IsNull) { cache.UnderlinePosition = ReadOpenTypeShort(postTable + OFF_post_underlinePosition); ushort underlineThickness = ReadOpenTypeUShort(postTable + OFF_post_underlineThickness); // Correct zero underline thickness (happens with "Bodoni MT Condensed") // to a reasonable default value taken from Arial. if (underlineThickness == 0) underlineThickness = (ushort)((unitsPerEm + 7)/14); cache.UnderlineThickness = underlineThickness; } else { // correct misssing underline metrics to reasonable default values from Arial cache.UnderlinePosition = (short)(-((unitsPerEm + 5) / 10)); cache.UnderlineThickness = (ushort)((unitsPerEm + 7)/14); } ushort typoAscent, typoDescent; if (!os2Table.IsNull) { typoAscent = ReadOpenTypeUShort(os2Table + OFF_OS2_sTypoAscender); int temp = -ReadOpenTypeShort(os2Table + OFF_OS2_sTypoDescender); if (temp < 0) { /* a few existing fonts have the sign for this value reversed */ temp = - temp; } typoDescent = (ushort)temp; // some fonts have invalid values for typoAscent and typoDescent if (typoAscent > ushort.MaxValue / 2 || typoDescent > ushort.MaxValue / 2) { typoAscent = cache.DesignCellAscent; typoDescent = cache.DesignCellDescent; } ushort strikeThroughThickness = ReadOpenTypeUShort(os2Table + OFF_OS2_yStrikeOutSize); if (strikeThroughThickness == 0) strikeThroughThickness = cache.UnderlineThickness; cache.StrikethroughThickness = strikeThroughThickness; cache.StrikethroughPosition = (short)ReadOpenTypeUShort(os2Table + OFF_OS2_yStrikeOutPosition); } else { typoAscent = cache.DesignCellAscent; typoDescent = cache.DesignCellDescent; cache.StrikethroughThickness = cache.UnderlineThickness; cache.StrikethroughPosition = (short)(unitsPerEm / 3); } // we don't use normalized em ascent and descent yet // but we want to keep this code in case we need it // design em descent and ascent come from typo ascent and descent // // normalization of designEmAscent and designEmDescent to make their sum correspond to designEmHeight as per CSS3 spec // // if ((designEmDescent + cache.DesignEmAscent) != 0) // { // designEmDescent = (ushort)((designEmDescent * unitsPerEm) / // (designEmDescent + cache.DesignEmAscent)); // } // else // { // /* in this rare malformed font case, default to 20% for the descender */ // designEmDescent = (ushort)(unitsPerEm * 20 / 100); // } // cache.DesignEmAscent = (ushort)(unitsPerEm - designEmDescent); ushort indexToLocFormat = ReadOpenTypeUShort(headTable + OFF_head_indexToLocFormat); ReadAdvances(cache, hmtxTable, numberOfHMetrics, indexToLocFormat, typoAscent, typoDescent); ReadRenderingHints(headTable, cache); cache.EmbeddingRights = ReadFontEmbeddingRights(os2Table); ComputeHeights(os2Table, unitsPerEm, cache); ParsedNameTable nameTable = new ParsedNameTable(); DecodeNameTable(ref nameTable); cache.AddLocalizedNames(ref nameTable, skipFontDifferentiation); ComputeFontContrastAdjustment(ref nameTable, cache); } catch (ArgumentOutOfRangeException e) { // convert exceptions from CheckedPointer to FileFormatException throw new FileFormatException(SourceUri, e); } } ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] internal void GetShapingFontFaceInfo(FontFaceLayoutInfo cache) { try { cache.SetGsub(GetTable(TrueTypeTags.TTO_GSUB)); cache.SetGpos(GetTable(TrueTypeTags.TTO_GPOS)); cache.SetGdef(GetTable(TrueTypeTags.TTO_GDEF)); cache.SetJstf(GetTable(TrueTypeTags.TTO_JSTF)); } catch (ArgumentOutOfRangeException e) { // convert exceptions from CheckedPointer to FileFormatException throw new FileFormatException(SourceUri, e); } } #endregion Public methods and properties #region Character mapping and language encoding handling ////// /// TrueType/OpenType fonts support a wide range of encodings for both /// names and character to glyph maps. /// /// We provide one class (CharacterToUnicodeMapper) that encapsulates all /// decoding required by 'name' and 'cmap' tables. /// /// Fonts identify which encoding is being used for a name or a cmap through /// a pair of enums: 'PlatformId' and 'EncodingId'. The static method /// 'PlatformIdAndEncodingIdToCharacterEncoding' maps from supported combinations /// of PatformId and EncodingId to a single internal enum 'CharacterEncoding'. /// /// Returns CharacterEncoding.Unknown for unrecognised/unsupported combinations /// of platform id and encoding id. /// /// private static CharacterEncoding PlatformIdAndEncodingIdToCharacterEncoding( PlatformID currentPlatform, ushort currentEncoding) { switch (currentPlatform) { case PlatformID.Microsoft: switch ((NameTableMicrosoftEncodingID)currentEncoding) { case NameTableMicrosoftEncodingID.Symbol: return CharacterEncoding.MsSymbol; case NameTableMicrosoftEncodingID.UnicodeBMPOnly: return CharacterEncoding.MsUcs2; case NameTableMicrosoftEncodingID.Unicode: return CharacterEncoding.MsUcs4; case NameTableMicrosoftEncodingID.ShiftJIS: return CharacterEncoding.MsShiftJis; case NameTableMicrosoftEncodingID.PRC: return CharacterEncoding.MsPrc; case NameTableMicrosoftEncodingID.Big5: return CharacterEncoding.MsBig5; case NameTableMicrosoftEncodingID.Wansung: return CharacterEncoding.MsWansung; } break; case PlatformID.Unicode: return CharacterEncoding.Unicode; case PlatformID.Macintosh: if (currentEncoding == 0) // Macintosh Roman encoding return CharacterEncoding.MacRoman; break; } return CharacterEncoding.Unknown; } private static CultureInfo MapTrueTypeLangIdToCulture(PlatformID platformID, ushort languageID) { switch (platformID) { case PlatformID.Macintosh: // handle English Macintosh language ID if (languageID == 0) return EnglishUSCulture; // ignore the rest of the language IDs return null; case PlatformID.Microsoft: // in Microsoft case we have 1 to 1 mapping if (languageID == 0x0F00) { // There is an invalid entry with this lang id in times.ttf on Windows XP and before. // Don't throw a first chance exception in that case. return null; } try { return CultureInfo.GetCultureInfo(languageID); } catch (ArgumentException) { // don't skip the font because of incorrect culture IDs return null; } case PlatformID.ISO: case PlatformID.Custom: case PlatformID.Unicode: default: // these 3 platforms cannot have name table entries and therefore don't have language ID return null; } } // MacRomanUpperToUnicode mapping table - for Mac Roman encoded cmaps and names. // MacRoman 0-127 is identical to Unicode 0-127. // MacRoman 128-255 uses a mapping specified by Apple and published on // the Unicode web site at: // http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT // // Note that although NLS provides a mapping for Mac Roman using the magic // encoding number 10000, that mapping is wrong for Apple codepoint 0xBD: // it maps Greek omega to electronic symbol 'ohms'. private static readonly int[] MacRomanUpperToUnicode = new int[] { 0x0000C4, 0x0000C5, 0x0000C7, 0x0000C9, 0x0000D1, 0x0000D6, 0x0000DC, 0x0000E1, // 0x80 - 0x87 0x0000E0, 0x0000E2, 0x0000E4, 0x0000E3, 0x0000E5, 0x0000E7, 0x0000E9, 0x0000E8, // 0x88 - 0x8F 0x0000EA, 0x0000EB, 0x0000ED, 0x0000EC, 0x0000EE, 0x0000EF, 0x0000F1, 0x0000F3, // 0x90 - 0x97 0x0000F2, 0x0000F4, 0x0000F6, 0x0000F5, 0x0000FA, 0x0000F9, 0x0000FB, 0x0000FC, // 0x98 - 0x9F 0x002020, 0x0000B0, 0x0000A2, 0x0000A3, 0x0000A7, 0x002022, 0x0000B6, 0x0000DF, // 0xA0 - 0xA7 0x0000AE, 0x0000A9, 0x002122, 0x0000B4, 0x0000A8, 0x002260, 0x0000C6, 0x0000D8, // 0xA8 - 0xAF 0x00221E, 0x0000B1, 0x002264, 0x002265, 0x0000A5, 0x0000B5, 0x002202, 0x002211, // 0xB0 - 0xB7 0x00220F, 0x0003C0, 0x00222B, 0x0000AA, 0x0000BA, 0x0003A9, 0x0000E6, 0x0000F8, // 0xB8 - 0xBF 0x0000BF, 0x0000A1, 0x0000AC, 0x00221A, 0x000192, 0x002248, 0x002206, 0x0000AB, // 0xC0 - 0xC7 0x0000BB, 0x002026, 0x0000A0, 0x0000C0, 0x0000C3, 0x0000D5, 0x000152, 0x000153, // 0xC8 - 0xCF 0x002013, 0x002014, 0x00201C, 0x00201D, 0x002018, 0x002019, 0x0000F7, 0x0025CA, // 0xD0 - 0xD7 0x0000FF, 0x000178, 0x002044, 0x0020AC, 0x002039, 0x00203A, 0x00FB01, 0x00FB02, // 0xD8 - 0xDF 0x002021, 0x0000B7, 0x00201A, 0x00201E, 0x002030, 0x0000C2, 0x0000CA, 0x0000C1, // 0xE0 - 0xE7 0x0000CB, 0x0000C8, 0x0000CD, 0x0000CE, 0x0000CF, 0x0000CC, 0x0000D3, 0x0000D4, // 0xE8 - 0xEF 0x00F8FF, 0x0000D2, 0x0000DA, 0x0000DB, 0x0000D9, 0x000131, 0x0002C6, 0x0002DC, // 0xF0 - 0xF7 0x0000AF, 0x0002D8, 0x0002D9, 0x0002DA, 0x0000B8, 0x0002DD, 0x0002DB, 0x0002C7 // 0xF8 - 0xFF }; ////// Subclasses of CharacterToUnicodeMapper are passed to string conversion /// and cmap parser routines to translate characters represented in various /// codepages in font files to standard Unicode encoding. /// abstract internal class CharacterToUnicodeMapper { ////// MapCharacterToUnicode - used by the cmap table parsers to determine /// which entry in the Avalon Unicode character map to update for /// a given cmap codepoint to glyph mapping. /// Returns -1 when no mapping exists /// public abstract int MapCharacterToUnicode(int character); ////// MapBytesToUtf16 - used by the name table parsers to map a /// name string to UTF-16. /// Returns null if any part of the string could not be mapped. /// public abstract string MapBytesToUtf16(byte[] bytes, int length); } ////// NullMapper - handles data that is already nominally in Unicode form. /// Byte arrays are expected to contain UTF-16 data in big endian /// format. /// internal class NullMapper : CharacterToUnicodeMapper { public NullMapper(){} public override int MapCharacterToUnicode(int character) { return character; } // Name tables that already using Unicode are handled directly in ParseUtf16Name. public override string MapBytesToUtf16(byte[] bytes, int length) { Invariant.Assert(false); return null; } } ////// MacRomanToUnicodeMapper - Handle Mac Roman encoded cmaps and names. /// MacRoman 0-127 is identical to Unicode 0-127. /// MacRoman 128-255 uses a mapping specified by Apple and published on /// the Unicode web site at: /// http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT /// /// Note that although NLS provides a mapping for Mac Roman using the magic /// encoding number 10000, that mapping is wrong for Apple codepoint 0xBD. /// internal class MacRomanToUnicodeMapper : CharacterToUnicodeMapper { public MacRomanToUnicodeMapper(){} public override int MapCharacterToUnicode(int character) { if (character < 0 || character >= 256) return -1; if (character < 128) { return character; } else { return MacRomanUpperToUnicode[character-128]; } } public override string MapBytesToUtf16(byte[] bytes, int length) { char[] result = new char[length]; for (int i=0; i/// WindowsAnsiToUnicodeMapper - Handle ansi Windows platform cmaps and names. /// All ansi Windows platform codepages are handled through the /// System.Text.Encoding NLS class. /// internal class WindowsAnsiToUnicodeMapper : CharacterToUnicodeMapper { private Encoding _encoding; private DecoderFallbackWithFailureFlag _decoderFallback; public WindowsAnsiToUnicodeMapper(int codepage) { try { _decoderFallback = new DecoderFallbackWithFailureFlag(); _encoding = Encoding.GetEncoding(codepage, EncoderFallback.ExceptionFallback, _decoderFallback); } catch (ArgumentException) { _encoding = null; } catch (NotSupportedException) { _encoding = null; } } public override int MapCharacterToUnicode(int character) { if (_encoding == null || character < 0 || character >= 65536) { return -1; } _decoderFallback.HasFailed = false; byte[] bytes; if (character < 256) { bytes = new byte[1]; bytes[0] = (byte)character; } else { bytes = new byte[2]; bytes[0] = (byte)(character >> 8); bytes[1] = (byte)(character & 0xff); } char[] chars = _encoding.GetChars(bytes); if (_decoderFallback.HasFailed) { // return missing characters as -1 (not a valid unicode codepoint) return -1; } else { if (chars.Length == 1) { return (int)chars[0]; } else { // Not in the basic multilingual plane. // For a good conversion this will be a surrogate pair, from which // we need to determine the UTF-32 codepoint. // For a bad conversion, instead of failing, NLS may just copy the 2 input // bytes over as 2 output chars. We treat this case as a failure. if ( (chars.Length != 2) || (chars[0] < 0xD800) || (chars[0] > 0xDBFF) || (chars[1] < 0xDC00) || (chars[1] > 0xDFFF)) { // Not a valid Unicode codepoint return -1; } else { return 0x10000 + ((((int)chars[0]) & 0x3FF) << 10) + (((int)chars[1]) & 0x3FF); } } } } public override string MapBytesToUtf16(byte[] bytes, int length) { if (_encoding == null) { return null; } _decoderFallback.HasFailed = false; string convertedString = _encoding.GetString(bytes, 0, length); if (_decoderFallback.HasFailed) { return null; } else { return convertedString; } } } private static CharacterToUnicodeMapper GetCharacterToUnicodeMapperForCharacterEncoding(CharacterEncoding characterEncoding) { switch (characterEncoding) { case CharacterEncoding.MsUcs2: case CharacterEncoding.MsUcs4: case CharacterEncoding.Unicode: case CharacterEncoding.MsSymbol: return new NullMapper(); case CharacterEncoding.MacRoman: return new MacRomanToUnicodeMapper(); case CharacterEncoding.MsShiftJis: return new WindowsAnsiToUnicodeMapper(932); case CharacterEncoding.MsPrc: return new WindowsAnsiToUnicodeMapper(936); case CharacterEncoding.MsBig5: return new WindowsAnsiToUnicodeMapper(950); case CharacterEncoding.MsWansung: return new WindowsAnsiToUnicodeMapper(949); } Invariant.Assert(false); return null; } private void LookForBestEncoding( CheckedPointer table, ushort encodingCount, out int glyphTableOffset, out CharacterEncoding characterEncoding) { PlatformID platform = PlatformID.Unicode; ushort encoding = 0; glyphTableOffset = 0; characterEncoding = CharacterEncoding.Unknown; const int offsetToPlatform = 4; const int offsetToEncoding = 6; const int offsetToOffset = 8; const int recordSize = 8; for (int i = 0; i < encodingCount; i++) { PlatformID currentPlatform = (PlatformID)ReadOpenTypeUShort(table + offsetToPlatform + i * recordSize); ushort currentEncoding = ReadOpenTypeUShort(table + offsetToEncoding + i * recordSize); int offset = ReadOpenTypeLong(table + offsetToOffset + i * recordSize); // Look for the best available table in the order of priority from the CharacterEncoding enum CharacterEncoding candidateTableType = PlatformIdAndEncodingIdToCharacterEncoding(currentPlatform, currentEncoding); if (candidateTableType > characterEncoding) { characterEncoding = candidateTableType; glyphTableOffset = offset; platform = currentPlatform; encoding = currentEncoding; } } } #endregion Character mapping and language encoding handling #region OS/2 and head decoding private void ReadStyles( CheckedPointer os2Table, CheckedPointer headTable, out FontStyle fontStyle, out FontWeight fontWeight, out FontStretch fontStretch, out bool skipFontDifferentiation ) { fontStyle = FontStyles.Normal; fontWeight = FontWeights.Normal; fontStretch = FontStretches.Normal; skipFontDifferentiation = false; if (!os2Table.IsNull) { Os2SelectionFlags os2SelectionFlags = (Os2SelectionFlags)ReadOpenTypeUShort(os2Table + OFF_OS2_usSelection); if ((os2SelectionFlags & Os2SelectionFlags.Oblique) != 0) { fontStyle = FontStyles.Oblique; } else if ((os2SelectionFlags & Os2SelectionFlags.Italic) != 0) { fontStyle = FontStyles.Italic; } if ((os2SelectionFlags & Os2SelectionFlags.WeightWidthSlopeOnly) != 0) { skipFontDifferentiation = true; } int usWeightClass = ReadOpenTypeUShort(os2Table + OFF_OS2_usWeightClass); if (1 <= usWeightClass && usWeightClass <= 9) { // Numerous existing fonts have the weight wrong, a value between 1 and 9 instead of between 100 and 900. usWeightClass *= 100; } fontWeight = FontWeight.FromOpenTypeWeight(usWeightClass); ushort usWidthClass = ReadOpenTypeUShort(os2Table + OFF_OS2_usWidthClass); fontStretch = FontStretch.FromOpenTypeStretch(usWidthClass); } else { MacStyleFlags macStyleFlags = (MacStyleFlags)ReadOpenTypeUShort(headTable + OFF_head_macStyle); if ((macStyleFlags & MacStyleFlags.Italic) != 0) { fontStyle = FontStyles.Italic; } if ((macStyleFlags & MacStyleFlags.Bold) != 0) { fontWeight = FontWeights.Bold; } if ((macStyleFlags & MacStyleFlags.Condensed) != 0) { fontStretch = FontStretches.Condensed; } if ((macStyleFlags & MacStyleFlags.Extended) != 0) { fontStretch = FontStretches.Expanded; } } } private static ushort IntToUshort(int n) { if (n >= 0 && n <= ushort.MaxValue) return (ushort)n; else if (n < 0) return 0; else return ushort.MaxValue; } private void ReadBasicMetrics( CheckedPointer headTable, CheckedPointer os2Table, CheckedPointer hheaTable, out ushort designEmHeight, out ushort designCellAscent, out ushort designCellDescent, out int designLineSpacing ) { designEmHeight = ReadOpenTypeUShort(headTable + OFF_head_unitsPerEm); if (designEmHeight == 0) { throw new FileFormatException(SourceUri); } if (!os2Table.IsNull && ((Os2SelectionFlags)ReadOpenTypeUShort(os2Table + OFF_OS2_usSelection) & Os2SelectionFlags.DontUseWinLineMetrics) != 0) { // The font specifies that the sTypoAscender, sTypoDescender, and sTypoLineGap fields are valid and // should be used instead of winAscent and winDescent. int typoAscender = ReadOpenTypeShort(os2Table + OFF_OS2_sTypoAscender); int typoDescender = ReadOpenTypeShort(os2Table + OFF_OS2_sTypoDescender); int typoLineGap = ReadOpenTypeShort(os2Table + OFF_OS2_sTypoLineGap); // We include the line gap in the ascent so that white space is distributed above the line. (Note that // the typo line gap is a different concept than "external leading".) designCellAscent = IntToUshort(typoAscender + typoLineGap); // Typo descent is a signed value where the positive direction is up. It is therefore typically negative. // A signed typo descent would be quite unusual as it would indicate the descender was above the baseline. designCellDescent = IntToUshort(-typoDescender); designLineSpacing = typoAscender + typoLineGap - typoDescender; } else { // get the ascender field int ascender = ReadOpenTypeUShort(hheaTable + OFF_hhea_ascender); // get the descender field; this is measured in the same direction as ascender and is therefore // normally negative whereas we want a positive value; however some fonts get the sign wrong // so instead of just negating we take the absolute value. int descender = Math.Abs((int)ReadOpenTypeShort(hheaTable + OFF_hhea_descender)); // get the lineGap field and make sure it's >= 0 int lineGap = Math.Max(0, (int)ReadOpenTypeShort(hheaTable + OFF_hhea_lineGap)); if (!os2Table.IsNull) { // we could use sTypoAscender, sTypoDescender, and sTypoLineGap which are supposed to represent // optimal typographic values not constrained by backwards compatibility; however, many fonts get // these fields wrong or get them right only for Latin text; therefore we use the more reliable // platform-specific Windows values. We take the absolute value of the win32descent in case some // fonts get the sign wrong. int winAscent = ReadOpenTypeUShort(os2Table + OFF_OS2_usWinAscent); int winDescent = Math.Abs((int)ReadOpenTypeShort(os2Table + OFF_OS2_usWinDescent)); designCellAscent = (ushort)winAscent; designCellDescent = (ushort)winDescent; // The following calculation for designLineSpacing is per DBrown. The default line spacing // should be the sum of the Mac ascender, descender, and lineGap unless the resulting value would // be less than the cell height (winAscent + winDescent) in which case we use the cell height. // See also http://www.microsoft.com/typography/otspec/recom.htm. // // Note that in theory it's valid for the baseline-to-baseline distance to be less than the cell // height. However, Windows has never allowed this for Truetype fonts, and fonts built for Windows // sometimes rely on this behavior and get the hha values wrong or set them all to zero. // designLineSpacing = Math.Max( lineGap + ascender + descender, winAscent + winDescent ); } else { designCellAscent = (ushort)ascender; designCellDescent = (ushort)descender; designLineSpacing = ascender + descender + lineGap; } } } /// /// Decode gasp table and decide whether the font is a legacy East Asian font. /// This is need to determine the best rendering method. /// ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadRenderingHints(CheckedPointer headTable, FontFaceLayoutInfo cache) { CheckedPointer gaspTable = GetTable(TrueTypeTags.GridfitAndScanProc); if (!gaspTable.IsNull) { // skip table version gaspTable += 2; // read number of GASP ranges ushort numRanges = ReadOpenTypeUShort(gaspTable); gaspTable += 2; FontFaceLayoutInfo.GaspRange [] gaspRanges = new FontFaceLayoutInfo.GaspRange[numRanges]; for (int i = 0; i < numRanges; ++i) { gaspRanges[i].ppem = ReadOpenTypeUShort(gaspTable); gaspTable += 2; gaspRanges[i].flags = (FontFaceLayoutInfo.GaspFlags)ReadOpenTypeUShort(gaspTable); gaspTable += 2; } cache.GaspRanges = gaspRanges; } ListlanguagesFound = new List (4); // inspect cmap to determine whether the font is East Asian or not for (int i = 0; i < EastAsianLanguages.RepresentativeCodepoints.GetLength(0); ++i) { bool allCodePointsPresent = true; for (int j = 0; j < EastAsianLanguages.RepresentativeCodepoints.GetLength(1); ++j) { if (!cache.CharacterMap.ContainsKey(EastAsianLanguages.RepresentativeCodepoints[i, j])) { allCodePointsPresent = false; break; } } if (allCodePointsPresent) languagesFound.Add(new EastAsianLanguageCandidate(i)); } if (languagesFound.Count == 0) { // this is not an East Asian font cache.FontRenderingHints = FontFaceLayoutInfo.RenderingHints.Regular; return; } Debug.Assert(languagesFound.Count <= EastAsianLanguages.RepresentativeCodepoints.GetLength(0)); ushort headFlags = ReadOpenTypeUShort(headTable + OFF_head_flags); if ((headFlags & (ushort)HeadFlags.OptimizedForClearType) != 0) { // this is a ClearType hinted East Asian font cache.FontRenderingHints = FontFaceLayoutInfo.RenderingHints.Regular; return; } CheckedPointer eblcTable = GetTable(TrueTypeTags.BitmapLocation); if (eblcTable.IsNull) { // this is a regular font cache.FontRenderingHints = FontFaceLayoutInfo.RenderingHints.Regular; return; } // extract all glyph ranges that have embedded bitmaps int numBitmapSizeTables = (int)ReadOpenTypeULong(eblcTable + OFF_eblc_numSizes); CheckedPointer bitmapSizeTable = eblcTable + OFF_eblc_bitmapSizeTables; for (int i = 0; i < numBitmapSizeTables; ++i, bitmapSizeTable += EblcSizeOfBitmapSizeTable) { int numberOfIndexSubTables = (int)ReadOpenTypeULong(bitmapSizeTable + EblcNumberOfIndexSubTables); int indexSubTableArrayOffset = (int)ReadOpenTypeULong(bitmapSizeTable + EblcIndexSubTableArrayOffset); CheckedPointer subTable = eblcTable + indexSubTableArrayOffset; for (int j = 0; j < numberOfIndexSubTables; ++j, subTable += EblcSizeOfIndexSubTable) { ushort firstGlyphIndex = ReadOpenTypeUShort(subTable + EblcFirstGlyph); ushort lastGlyphIndex = ReadOpenTypeUShort(subTable + EblcLastGlyph); // now we know that glyph range [firstGlyphIndex, lastGlyphIndex] has embedded bitmaps in it foreach (EastAsianLanguageCandidate lang in languagesFound) { for (int k = 0; k < EastAsianLanguages.RepresentativeCodepoints.GetLength(1); ++k) { char c = EastAsianLanguages.RepresentativeCodepoints[lang.LanguageIndex, k]; // PERF: we could perform cmap lookup in advance in case doing it on every // iteration is determined to be too slow. ushort g; // finally, check if the glyph is in the embedded bitmap range if (cache.CharacterMap.TryGetValue(c, out g) && firstGlyphIndex <= g && g <= lastGlyphIndex) { lang.AddCodepoint(k); } } } } } // now, if the font has enough embedded bitmaps to represent a language // we treat it as a legacy East Asian font. Otherwise, it's a Regular font. foreach (EastAsianLanguageCandidate lang in languagesFound) { Debug.Assert(EastAsianLanguages.RepresentativeCodepoints.GetLength(1) == 6); // If all of lower 6 bits are set, this means that all of the 6 codepoints // have embedded bitmaps. We treat such fonts as legacy East Asian fonts. if (lang.CodePoints == 63) { cache.FontRenderingHints = FontFaceLayoutInfo.RenderingHints.LegacyEastAsian; return; } } cache.FontRenderingHints = FontFaceLayoutInfo.RenderingHints.Regular; return; } /// /// Computes the contrast adjustment value for this font. /// The value is used to fine tune the text contrast value used by glyph rendering code. /// ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ComputeFontContrastAdjustment(ref ParsedNameTable nameTable, FontFaceLayoutInfo cache) { if (_technology == FontTechnology.PostscriptOpenType) cache.FontContrastAdjustment = -1; else { // For "Courier New" and similar fonts, bump up the contrast value by 2. // This is similar to what GDI and GDI+ ClearType code does to achieve more readable glyphs for these thin fonts. // One difference is that we do this for the whole font family, not just for regular faces. // This achieves more consistent look across different weights. short fontContrastAdjustment = 0; LocalizedName[] familyNames = nameTable.familyNames; if (familyNames == null) familyNames = nameTable.win32FamilyNames; if (familyNames != null) { foreach (LocalizedName name in familyNames) { if (Array.BinarySearch(_thinFontFamilyNames, name.Name, StringComparer.OrdinalIgnoreCase) >= 0) fontContrastAdjustment = 1; } } cache.FontContrastAdjustment = fontContrastAdjustment; } } /// /// Analyzes os/2 fsType value and construct FontEmbeddingRight enum value from it. /// ////// Critical: This code writes critical information into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] FontEmbeddingRight ReadFontEmbeddingRights(CheckedPointer os2Table) { // If there is no os/2 table, default to restricted font. // This is the precedence that has been set by T2Embed, Word, etc. // No one has complained about this because these fonts are generally lower in quality and are less likely to be embedded. FontEmbeddingRight rights = FontEmbeddingRight.RestrictedLicense; if (!os2Table.IsNull) { ushort fsType = ReadOpenTypeUShort(os2Table + OFF_OS2_fsType); // Start with the most restrictive flags. // In case a font uses conflicting flags, // expose the least restrictive combination in order to be compatible with existing applications. if ((fsType & Os2EmbeddingFlags.InstallableMask) == 0) { // The font is installable if all bits in the InstallableMask are set to zero. switch (fsType & (Os2EmbeddingFlags.NoSubsetting | Os2EmbeddingFlags.BitmapOnly)) { case 0: rights = FontEmbeddingRight.Installable; break; case Os2EmbeddingFlags.NoSubsetting: rights = FontEmbeddingRight.InstallableButNoSubsetting; break; case Os2EmbeddingFlags.BitmapOnly: rights = FontEmbeddingRight.InstallableButWithBitmapsOnly; break; case Os2EmbeddingFlags.NoSubsetting | Os2EmbeddingFlags.BitmapOnly: rights = FontEmbeddingRight.InstallableButNoSubsettingAndWithBitmapsOnly; break; } } else if ((fsType & Os2EmbeddingFlags.Editable) != 0) { switch (fsType & (Os2EmbeddingFlags.NoSubsetting | Os2EmbeddingFlags.BitmapOnly)) { case 0: rights = FontEmbeddingRight.Editable; break; case Os2EmbeddingFlags.NoSubsetting: rights = FontEmbeddingRight.EditableButNoSubsetting; break; case Os2EmbeddingFlags.BitmapOnly: rights = FontEmbeddingRight.EditableButWithBitmapsOnly; break; case Os2EmbeddingFlags.NoSubsetting | Os2EmbeddingFlags.BitmapOnly: rights = FontEmbeddingRight.EditableButNoSubsettingAndWithBitmapsOnly; break; } } else if ((fsType & Os2EmbeddingFlags.PreviewAndPrint) != 0) { switch (fsType & (Os2EmbeddingFlags.NoSubsetting | Os2EmbeddingFlags.BitmapOnly)) { case 0: rights = FontEmbeddingRight.PreviewAndPrint; break; case Os2EmbeddingFlags.NoSubsetting: rights = FontEmbeddingRight.PreviewAndPrintButNoSubsetting; break; case Os2EmbeddingFlags.BitmapOnly: rights = FontEmbeddingRight.PreviewAndPrintButWithBitmapsOnly; break; case Os2EmbeddingFlags.NoSubsetting | Os2EmbeddingFlags.BitmapOnly: rights = FontEmbeddingRight.PreviewAndPrintButNoSubsettingAndWithBitmapsOnly; break; } } else { // Otherwise, the font either has Os2EmbeddingFlags.RestrictedLicense set, or // it has a reserved bit 0 set, which is invalid per specification. // Either way, rights should remain FontEmbeddingRight.RestrictedLicense. } } return rights; } #endregion OS/2 and head decoding #region Name table decoding private class NameCollection { internal class LocalizedNameCandidate { // key internal CultureInfo culture; // culture info for the name internal CharacterEncoding characterEncoding; // the glyph encoding of the candidate // data internal int offset; // offset to the name in the file internal int length; // length of the name in the file } private static string ParseUtf16Name(LocalizedNameCandidate name, CheckedPointer nameTable) { int stringLength = name.length / 2; StringBuilder sb = new StringBuilder(stringLength); int offsetToString = ReadOpenTypeUShort(nameTable + 4) + name.offset; for (int i = 0; i < stringLength; i ++) { ushort c = ReadOpenTypeUShort(nameTable + (offsetToString + i * 2)); if (c == 0) break; sb.Append((char)c); } return sb.ToString(); } ////// Critical: This code calls into probe and also has an unsafe code block /// TreatAsSafe: Converts a string to ANSI and checks for NUll value of checked pointer /// [SecurityCritical,SecurityTreatAsSafe] private static string Parse8BitCodepageName(LocalizedNameCandidate name, CheckedPointer nameTable) { CharacterToUnicodeMapper mapper = GetCharacterToUnicodeMapperForCharacterEncoding(name.characterEncoding); int offsetToString = ReadOpenTypeUShort(nameTable + 4) + name.offset; // for DBCS legacy encoding, the name may be encoded in a format where every character takes two bytes // and single byte character have zero for the high byte, we need to skip all those null bytes before // calling the Ansi to Unicode conversion byte[] bytes = new byte[name.length]; int ansiLength = 0; unsafe { byte * nameTablePointer = (byte *)nameTable.Probe(offsetToString, name.length); for (int originalIndex = 0; originalIndex < name.length; originalIndex++) { if (nameTablePointer[originalIndex] != 0) { bytes[ansiLength] = nameTablePointer[originalIndex]; ansiLength++; } } } return mapper.MapBytesToUtf16(bytes, ansiLength); } internal void AddNameEntry( PlatformID platformID, ushort encodingID, ushort languageID, int tableIndex, CheckedPointer nameTable ) { LocalizedNameCandidate newName = new LocalizedNameCandidate(); newName.culture = MapTrueTypeLangIdToCulture(platformID, languageID); if (newName.culture == null) // we don't recognize this LCID { return; } newName.characterEncoding = PlatformIdAndEncodingIdToCharacterEncoding(platformID, encodingID); if (newName.characterEncoding == CharacterEncoding.Unknown) { return; } int index = _names.BinarySearch(newName, _candidateComparer); if (index < 0) { // no previous entry with this lcid was found, add a new one in sorted order _names.Insert(~index, newName); } else { LocalizedNameCandidate oldName = _names[index]; // see if we have a better glyph table type match if (newName.characterEncoding <= oldName.characterEncoding) return; // replace the old name with a better one _names[index] = newName; } newName.length = ReadOpenTypeUShort(nameTable + (14 + tableIndex * 12)); newName.offset = ReadOpenTypeUShort(nameTable + (16 + tableIndex * 12)); } ////// ConvertString converts a string from its format in a font name table to UTF-16. /// Returns null if the glyph table type is invalid. /// private string ParseNameString(LocalizedNameCandidate name, CheckedPointer nameTable) { switch (name.characterEncoding) { // The following encodongs all imply that names are stored in UTF16 case CharacterEncoding.MsUcs4: case CharacterEncoding.MsSymbol: case CharacterEncoding.MsUcs2: case CharacterEncoding.Unicode: case CharacterEncoding.MsShiftJis: // ShiftJis appears in this list because GDI is decoding the name table of shiftjis fonts // as Unicode (even though it decodes the name table for Big5, Wansung, GB fonts as DBCS Ansi). // Since GDI is doing like this and legacy fonts are build like this, I need to continue the legacy return ParseUtf16Name(name, nameTable); case CharacterEncoding.MacRoman: case CharacterEncoding.MsPrc: case CharacterEncoding.MsBig5: case CharacterEncoding.MsWansung: // Single or double byte 8-bit based character set return Parse8BitCodepageName(name, nameTable); default: return null; // Unknown encoding } } internal string ConvertVersion(CheckedPointer nameTable) { LocalizedNameCandidate nameCandidate = null; foreach (LocalizedNameCandidate name in _names) { nameCandidate = name; if (name.culture.Equals(EnglishUSCulture)) break; } if (nameCandidate == null) return null; return ParseNameString(nameCandidate, nameTable); } internal LocalizedName [] ConvertAllNames(CheckedPointer nameTable) { if (_names.Count == 0) return null; // Count how many strings are in MS supported formats. int supportedNameCount=0; for (int i=0; i<_names.Count; i++) { if (ParseNameString(_names[i], nameTable) != null) { supportedNameCount++; } } if (supportedNameCount == 0) return null; // Allocate and fill in the localizedNames array. LocalizedName [] localizedNames = new LocalizedName[supportedNameCount]; int j=0; for (int i=0; i<_names.Count; i++) { string convertedString = ParseNameString(_names[i], nameTable); if (convertedString != null) { // Conversion succeeded: add converted name to publicly accessible names. // Make sure to pass the original culture LCID, as we rely on the LocalizedNameCandidate being sorted by it // later on in the FontFaceLayoutInfo code when we perform binary search. Please see ConvertNames and FindLCID methods. localizedNames[j++] = new LocalizedName(XmlLanguage.GetLanguage(_names[i].culture.IetfLanguageTag), convertedString, _names[i].culture.LCID); } } return localizedNames; } private class CandidateComparer : IComparer{ #region IComparer Members int IComparer .Compare(LocalizedNameCandidate x, LocalizedNameCandidate y) { // The sort function below is used only to detect duplicate CultureInfo objects. int xlcid = x.culture.LCID; int ylcid = y.culture.LCID; return xlcid - ylcid; } #endregion } // the list contains LocalizedNameCandidate entries sorted by LCID private List _names = new List (2); private static CandidateComparer _candidateComparer = new CandidateComparer(); } // this function is written in the way compatible with the legacy code // because version number in the name table can be written in many ways // please don't try to "optimize" or "fix" it without talking // to one of TrueType experts private double VersionToDouble(string versionString) { if (versionString == null) return 0.0; double version = 0.0; string subString; int i = 0; int start = versionString.Length; while (i < versionString.Length) { if (Char.IsDigit(versionString, i)) { start = i; break; } i++; } if (start < versionString.Length) { i++; while ((i < versionString.Length) && Char.IsDigit(versionString, i)) { i++; } if ((i < versionString.Length) && (versionString[i] == '.')) { i++; while ((i < versionString.Length) && Char.IsDigit(versionString, i)) { i++; } } subString = versionString.Substring(start, i - start); // the version string is always formatted using English number format if (!double.TryParse(subString, NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out version)) version = 0.0; } return version; } private void DecodeNameTable(ref ParsedNameTable parsedNameTable) { // extracting information from the name table CheckedPointer nameTable = GetTable(TrueTypeTags.NamingTable); if (nameTable.IsNull) { throw new FileFormatException(SourceUri); } ushort numberOfNameRecords = ReadOpenTypeUShort(nameTable + 2); if (numberOfNameRecords == 0) { throw new FileFormatException(SourceUri); } NameCollection fontFamilyName = new NameCollection(); NameCollection fontSubfamilyName = new NameCollection(); NameCollection win32FamilyName = new NameCollection(); NameCollection win32SubfamilyName = new NameCollection(); NameCollection versionStrings = new NameCollection(); NameCollection copyrights = new NameCollection(); NameCollection manufacturerNames = new NameCollection(); NameCollection trademarks = new NameCollection(); NameCollection designerNames = new NameCollection(); NameCollection descriptions = new NameCollection(); NameCollection vendorUrls = new NameCollection(); NameCollection designerUrls = new NameCollection(); NameCollection licenseDescriptions = new NameCollection(); NameCollection sampleTexts = new NameCollection(); for (int i = 0; i < numberOfNameRecords; i++) { PlatformID platformID = (PlatformID)ReadOpenTypeUShort(nameTable + (6 + i * 12)); ushort specificID = ReadOpenTypeUShort(nameTable + (8 + i * 12)); ushort languageID = ReadOpenTypeUShort(nameTable + (10 + i * 12)); ushort nameID = ReadOpenTypeUShort(nameTable + (12 + i * 12)); switch ((NameTableNameID)nameID) { case NameTableNameID.FontFamilyName: win32FamilyName.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.FontSubfamilyName: win32SubfamilyName.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.PreferredFamily: fontFamilyName.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.PreferredSubfamily: fontSubfamilyName.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.Version: versionStrings.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.CopyrightNotice: copyrights.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.Trademark: trademarks.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.ManufacturerName: manufacturerNames.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.Designer: designerNames.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.Description: descriptions.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.URLVendor: vendorUrls.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.URLDesigner: designerUrls.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.LicenseDescription: licenseDescriptions.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.SampleText: sampleTexts.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; } } parsedNameTable.familyNames = fontFamilyName.ConvertAllNames(nameTable); parsedNameTable.win32FamilyNames = win32FamilyName.ConvertAllNames(nameTable); parsedNameTable.faceNames = fontSubfamilyName.ConvertAllNames(nameTable); parsedNameTable.win32faceNames = win32SubfamilyName.ConvertAllNames(nameTable); parsedNameTable.versionStrings = versionStrings.ConvertAllNames(nameTable); parsedNameTable.copyrights = copyrights.ConvertAllNames(nameTable); parsedNameTable.manufacturerNames = manufacturerNames.ConvertAllNames(nameTable); parsedNameTable.trademarks = trademarks.ConvertAllNames(nameTable); parsedNameTable.designerNames = designerNames.ConvertAllNames(nameTable); parsedNameTable.descriptions = descriptions.ConvertAllNames(nameTable); parsedNameTable.vendorUrls = vendorUrls.ConvertAllNames(nameTable); parsedNameTable.designerUrls = designerUrls.ConvertAllNames(nameTable); parsedNameTable.licenseDescriptions = licenseDescriptions.ConvertAllNames(nameTable); parsedNameTable.sampleTexts = sampleTexts.ConvertAllNames(nameTable); parsedNameTable.version = VersionToDouble(versionStrings.ConvertVersion(nameTable)); } #endregion Name table decoding #region Cmap table decoding // CMAP format parsers /// /// Format 0 - a simple array of 256 bytes /// ////// Critical: This code writes into cmap. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadCmapFormat0( CheckedPointer cmapTable, int glyphTableOffset, CharacterToUnicodeMapper mapper, FontFaceLayoutInfo.IntMap characterMap ) { int bytesOffset = glyphTableOffset + 6; for (int ch=0; ch<255; ch++) { int glyph = ReadOpenTypeByte(cmapTable + bytesOffset + ch); int unicode = mapper.MapCharacterToUnicode(ch); if (unicode != -1) // Skip codepoints not representable in Unicode { characterMap.SetCharacterEntry(unicode, unchecked((ushort)glyph)); } } } ////// Critical: This code writes into cmap. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadCmapFormat2( CheckedPointer cmapTable, int glyphTableOffset, CharacterToUnicodeMapper mapper, FontFaceLayoutInfo.IntMap characterMap ) { int subHeaderOffset = glyphTableOffset + 6 + (256 * 2); for (int ii = 0; ii < 256; ii++) { int jj = ReadOpenTypeUShort(cmapTable + (glyphTableOffset + 6 + ii * 2)); // SubHeaderKeys int firstCode = ReadOpenTypeUShort(cmapTable + (subHeaderOffset + jj)); int entryCount = ReadOpenTypeUShort(cmapTable + (subHeaderOffset + 2 + jj)); short idDelta = ReadOpenTypeShort (cmapTable + (subHeaderOffset + 4 + jj)); int idRangeOffset = ReadOpenTypeUShort(cmapTable + (subHeaderOffset + 6 + jj)); int glyphArrayOffset = subHeaderOffset + jj + 6 + idRangeOffset; if (jj == 0) { int unicode = mapper.MapCharacterToUnicode(ii); // Special case: single bte codepoint not valid for this codepage. // Fonts sometimes provide glyphs in these areas, for example the // control code areas in CP950 are reserved, but fonts may provide // a representation. if (unicode == -1) { // No mapping required. unicode = ii; } ushort hGlyph = ReadOpenTypeUShort(cmapTable + (glyphArrayOffset + (ii - firstCode) * 2)); if (hGlyph != 0) { characterMap.SetCharacterEntry(unicode, hGlyph); } } else { for( jj = firstCode ; jj < firstCode + entryCount ; jj++ ) { int unicode = mapper.MapCharacterToUnicode((ii<<8)+jj); if (unicode != -1) // Skip CMAP entries that don't map to Unicode { int hGlyph = ReadOpenTypeUShort(cmapTable + (glyphArrayOffset + (jj - firstCode) * 2)); if( hGlyph != 0 ) { characterMap.SetCharacterEntry(unicode, unchecked((ushort)(hGlyph + idDelta))); } } } } } } //// MapCmapFormat4Glyph - Interpret Truetype CMAP type 4 range details // // Implements format 4 of the TrueType cmap table - 'Segment // mapping to delta values' described in chapter 2 of the 'TrueType // 1.0 Font Files Rev. 1.66' document. private ushort MapCmapFormat4Glyph( CheckedPointer cmapTable, int wc, // Character int offsetToCurrentIdRange, int idRangeOffset, int startCount, short idDelta ) { int g; int offsetToGlyph; if (wc >= 0xffff) { // Don't map U+0FFFF as some fonts (Pristina) don't map it // correctly and cause an AV in a subsequent lookup. return 0; } if (idRangeOffset != 0) { offsetToGlyph = idRangeOffset + (wc - startCount) * 2 + offsetToCurrentIdRange; g = ReadOpenTypeUShort(cmapTable + offsetToGlyph); if (g != 0) { g += idDelta; } } else { g = wc + idDelta; } return unchecked((ushort)g); } ////// Critical: This code writes into cmap. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadCmapFormat4( CheckedPointer cmapTable, int glyphTableOffset, CharacterToUnicodeMapper mapper, FontFaceLayoutInfo.IntMap characterMapInfo ) { ushort segCount = (ushort)(ReadOpenTypeUShort(cmapTable + (glyphTableOffset + 6)) / 2); int endCountOffset = glyphTableOffset + 14; int startCountOffset = endCountOffset + (segCount + 1) * 2; int idDeltaOffset = startCountOffset + (segCount * 2); int idRangeTableOffset = idDeltaOffset + (segCount * 2); // Loop through the segments mapping glyphs int i; int endCount = ReadOpenTypeUShort(cmapTable + endCountOffset); for (i=0; i0 && startCount < endCount) { startCount = endCount + 1; } endCount = ReadOpenTypeUShort(cmapTable + (endCountOffset + i * 2)); // Some font tools generate an final invalid mapping from codepoint FFFF. // Since FFFF is invalid in all codepages, we ignore it. if (endCount == 0xffff) { endCount--; } for (int characterCode = startCount; characterCode <= endCount; characterCode++) { int unicode = mapper.MapCharacterToUnicode(characterCode); if (unicode != -1) // Skip codepoints not representable in Unicode { characterMapInfo.SetCharacterEntry( unicode, MapCmapFormat4Glyph(cmapTable, characterCode, idRangeTableOffset + (i*2), idRangeOffset, startCount, idDelta)); } } } } /// /// Format 6: Trimmed table mapping /// /// Type Name Description /// USHORT format Format number is set to 6. /// USHORT length This is the length in bytes of the subtable. /// USHORT language Please see "Note on the language field in 'cmap' subtables" in this document. /// USHORT firstCode First character code of subrange. /// USHORT entryCount Number of character codes in subrange. /// USHORT glyphIdArray [entryCount] Array of glyph index values for character codes in the range. /// /// /// The firstCode and entryCount values specify a subrange (beginning at /// firstCode,length = entryCount) within the range of possible character /// codes. Codes outside of this subrange are mapped to glyph index 0. The /// offset of the code (from the first code) within this subrange is used as /// index to the glyphIdArray, which provides the glyph index value. /// /// ////// Critical: This code writes into cmap. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadCmapFormat6( CheckedPointer cmapTable, int glyphTableOffset, CharacterToUnicodeMapper mapper, FontFaceLayoutInfo.IntMap characterMap ) { int firstCode = ReadOpenTypeUShort(cmapTable + glyphTableOffset + 6); int entryCount = ReadOpenTypeUShort(cmapTable + glyphTableOffset + 8); for (int i = 0; i < entryCount; i++) { int unicode = mapper.MapCharacterToUnicode(firstCode + i); if (unicode != -1) // Skip codepoints not representable in Unicode { ushort glyph = ReadOpenTypeUShort(cmapTable + glyphTableOffset + 10 + (firstCode+i)*2); if (glyph != 0) { characterMap.SetCharacterEntry(unicode, glyph); } } } } ////// Critical: This code writes into cmap. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadCmapFormat12( CheckedPointer cmapTable, int glyphTableOffset, FontFaceLayoutInfo.IntMap characterMapInfo ) { int groupCount = ReadOpenTypeLong(cmapTable + (glyphTableOffset + 12)); // Iterate through groups filling in cmap table for (int i=0; i < groupCount; i++) { int startCharCode = ReadOpenTypeLong(cmapTable + (glyphTableOffset + 16 + i * 12)); int endCharCode = ReadOpenTypeLong(cmapTable + (glyphTableOffset + 20 + i * 12)); int startGlyphID = ReadOpenTypeLong(cmapTable + (glyphTableOffset + 24 + i * 12)); for (int unicode = startCharCode; unicode <= endCharCode; unicode++) { characterMapInfo.SetCharacterEntry(unicode, unchecked((ushort)(startGlyphID + unicode - startCharCode))); } } } ////// CrossPopulateSymbolFontCodepoints - duplicate codepoints between ranges /// 0-ff and f000-f0ff. /// /// Any codepoints present in one but not both ranges are copied /// into the other range. /// /// The OpenType standard recommends but does not require that symbol fonts /// are encoded from f000 to f0ff. GDI duplicates these codepoints into the /// range 0-ff since most apps (and therefore most textual data) expect to /// use 0-ff rather than f000-f0ff. /// /// However, since most apps and OS APIs process carriage return (U+000d), /// line feed (U+000a) and maybe tab (U+0009) specially, any symbol at these /// positions is difficult to display, so apps needing access to such symbols /// generally use the f000-f0ff range when displaying symbols. For this /// reason, to support symbol fonts that present their symbols in the /// range 0-ff, it is also necessary to copy codepoints 0-ff up to f000-f0ff. /// /// This code never overwrites a codepont already defined by the font cmap table, /// only codepoints that are undefined (i.e. with glyph index zero). /// ////// Critical: This code writes into cmap. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void CrossPopulateSymbolFontCodepoints( FontFaceLayoutInfo.IntMap characterMap ) { for (int i=0; i<256; i++) { ushort lowGlyphIndex; bool lowExists = characterMap.TryGetValue(i, out lowGlyphIndex); ushort highGlyphIndex; bool highExists = characterMap.TryGetValue(i + 0xf000, out highGlyphIndex); if (highExists && !lowExists) { characterMap.SetCharacterEntry(i, highGlyphIndex); } else if (lowExists && !highExists) { characterMap.SetCharacterEntry(i + 0xf000, lowGlyphIndex); } } } ////// Decodes the cmap table /// /// Font cache structure to fill in ///Whether the font has Symbol encoding ////// Critical: This code calls into SetGlypCount which is used as an index into /// an unmanaged structure /// TreatAsSafe: It retrieves this from cache.GlyphCount which is tracked /// [SecurityCritical,SecurityTreatAsSafe] private bool DecodeCmapTable(FontFaceLayoutInfo cache) { CheckedPointer cmapTable = GetTable(TrueTypeTags.CharToIndexMap); if (cmapTable.IsNull) { throw new FileFormatException(SourceUri); } ushort encodingCount = ReadOpenTypeUShort(cmapTable + 2); int glyphTableOffset; CharacterEncoding characterEncoding; LookForBestEncoding( cmapTable, encodingCount, out glyphTableOffset, out characterEncoding ); // when we're interested in only basic font face info, cache is null if (cache == null) { return characterEncoding == CharacterEncoding.MsSymbol; } FontFaceLayoutInfo.IntMap characterMapInfo = cache.CharacterMap; characterMapInfo.SetGlyphCount(cache.GlyphCount); ushort format = ReadOpenTypeUShort(cmapTable + glyphTableOffset); switch(characterEncoding) { case CharacterEncoding.Unknown: throw new FileFormatException(SourceUri); case CharacterEncoding.MsSymbol: case CharacterEncoding.MsUcs2: case CharacterEncoding.MsUcs4: case CharacterEncoding.Unicode: switch(format) { case 4: ReadCmapFormat4(cmapTable, glyphTableOffset, GetCharacterToUnicodeMapperForCharacterEncoding(characterEncoding), characterMapInfo); break; case 12: ReadCmapFormat12(cmapTable, glyphTableOffset, characterMapInfo); break; default: throw new FileFormatException(SourceUri); } break; case CharacterEncoding.MsShiftJis: case CharacterEncoding.MsPrc: case CharacterEncoding.MsBig5: case CharacterEncoding.MsWansung: switch(format) { case 2: ReadCmapFormat2(cmapTable, glyphTableOffset, GetCharacterToUnicodeMapperForCharacterEncoding(characterEncoding), characterMapInfo); break; case 4: ReadCmapFormat4(cmapTable, glyphTableOffset, GetCharacterToUnicodeMapperForCharacterEncoding(characterEncoding), characterMapInfo); break; default: throw new FileFormatException(SourceUri); } break; case CharacterEncoding.MacRoman: switch(format) { case 0: ReadCmapFormat0(cmapTable, glyphTableOffset, new MacRomanToUnicodeMapper(), characterMapInfo); break; case 6: ReadCmapFormat6(cmapTable, glyphTableOffset, new MacRomanToUnicodeMapper(), characterMapInfo); break; default: throw new FileFormatException(SourceUri); } break; default: throw new FileFormatException(SourceUri); } // Symbol fonts have codepoints cross-populated between 0-ff and f000-f0ff. if (characterEncoding == CharacterEncoding.MsSymbol) { CrossPopulateSymbolFontCodepoints(characterMapInfo); } return characterEncoding == CharacterEncoding.MsSymbol; } #endregion #region Horizontal and vertical metrics private bool ValidatexHeight(ushort designEmHeight, ref short xHeight) { if (xHeight <= designEmHeight * 10 / 100 || xHeight >= designEmHeight * 90 / 100) { xHeight = 0; return false; } return true; } private bool ValidateCapsHeight(ushort designEmHeight, ref short capsHeight) { if (capsHeight <= designEmHeight * 10 / 100 || capsHeight > designEmHeight) { capsHeight = 0; return false; } return true; } private bool ValidateHeights(ushort designEmHeight, ref short xHeight, ref short capsHeight) { return ValidatexHeight(designEmHeight, ref xHeight) && ValidateCapsHeight(designEmHeight, ref capsHeight); } ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ComputeHeights(CheckedPointer os2Table, ushort unitsPerEm, FontFaceLayoutInfo cache) { short xHeight = 0; short capsHeight = 0; bool validHeights = false; // first, try to get xHeight and capsHeight from OS/2 if (!os2Table.IsNull) { ushort os2version = ReadOpenTypeUShort(os2Table + OFF_OS2_version); if (os2version >= 2) { xHeight = ReadOpenTypeShort(os2Table + OFF_OS2_sxHeight); capsHeight = ReadOpenTypeShort(os2Table + OFF_OS2_sCapHeight); validHeights = ValidateHeights(unitsPerEm, ref xHeight, ref capsHeight); } } // in case there was a problem, try to get xHeight and capsHeight from PCLT if (!validHeights) { CheckedPointer pcltTable = GetTable(TrueTypeTags.PCLT); if (!pcltTable.IsNull) { if (xHeight == 0) xHeight = unchecked((short)ReadOpenTypeUShort(pcltTable + OFF_PCLT_xHeight)); if (capsHeight == 0) capsHeight = unchecked((short)ReadOpenTypeUShort(pcltTable + OFF_PCLT_CapHeight)); validHeights = ValidateHeights(unitsPerEm, ref xHeight, ref capsHeight); } } if (!validHeights && !cache.Symbol) { if (xHeight == 0) { for (int i = 0; i < MetricSearchList.xHeight.GetLength(0); i++) { ushort glyphIndex; if (!cache.CharacterMap.TryGetValue((int)MetricSearchList.xHeight[i, 0], out glyphIndex)) continue; // bsb = ah - (tsb + ymax - ymin) implies // ymax = ah - bsb - tsb + ymin, and ymin = -baseline long yMax = (long) cache.GetAdvanceHeight(glyphIndex) - cache.GetBottomSidebearing(glyphIndex) - cache.GetTopSidebearing(glyphIndex) - cache.GetBaseline(glyphIndex); yMax = yMax * MetricSearchList.xHeight[i, 1] / 100; xHeight = unchecked((short)yMax); if (ValidatexHeight(unitsPerEm, ref xHeight)) break; } } if (capsHeight == 0) { for (int i = 0; i < MetricSearchList.capsHeight.GetLength(0); i++) { ushort glyphIndex; if (!cache.CharacterMap.TryGetValue((int)MetricSearchList.capsHeight[i, 0], out glyphIndex)) continue; // bsb = ah - (tsb + ymax - ymin) implies // ymax = ah - bsb - tsb + ymin, and ymin = -baseline long yMax = (long) cache.GetAdvanceHeight(glyphIndex) - cache.GetBottomSidebearing(glyphIndex) - cache.GetTopSidebearing(glyphIndex) - cache.GetBaseline(glyphIndex); yMax = yMax * MetricSearchList.capsHeight[i, 1] / 100; capsHeight = unchecked((short)yMax); if (ValidateCapsHeight(unitsPerEm, ref capsHeight)) break; } } } // set the values to reasonable defaults // if still unable to obtain them from the font if (xHeight == 0) { // times.ttf is 45%, arial.ttf is 51%, micross.ttf is 52% xHeight = unchecked((short)(unitsPerEm * 50 / 100)); } if (capsHeight == 0) { // times.ttf is 66%, arial.ttf is 71%, micross.ttf is 72% capsHeight = unchecked((short)(unitsPerEm * 70 / 100)); } cache.xHeight = xHeight; cache.CapsHeight = capsHeight; } ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadHmtx(FontFaceLayoutInfo cache, CheckedPointer hmtxTable, ushort numberOfMetrics) { for (ushort i = 0; i < numberOfMetrics; i++) { cache.SetAdvanceWidth(i, ReadOpenTypeUShort(hmtxTable)); hmtxTable += 2; cache.SetLeftSidebearing(i, ReadOpenTypeShort(hmtxTable)); hmtxTable += 2; } ushort fixedAdvance = cache.GetAdvanceWidth(unchecked((ushort)(numberOfMetrics - 1))); for (ushort i = numberOfMetrics; i < cache.GlyphCount; i++) { cache.SetAdvanceWidth(i, fixedAdvance); cache.SetLeftSidebearing(i, ReadOpenTypeShort(hmtxTable)); hmtxTable += 2; } } ////// Read metrics from vmtx if it's present /// /// Font cache structure ///Whether Vmtx was present in the font ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private bool ReadVmtx(FontFaceLayoutInfo cache) { CheckedPointer vheaTable = GetTable(TrueTypeTags.VertHeader); CheckedPointer vmtxTable = GetTable(TrueTypeTags.VerticalMetrics); if (vheaTable.IsNull || vmtxTable.IsNull) return false; ushort numberOfMetrics = ReadOpenTypeUShort(vheaTable + OFF_vhea_numOfLongVerMetrics); for (ushort i = 0; i < numberOfMetrics; i++) { cache.SetAdvanceHeight(i, ReadOpenTypeUShort(vmtxTable)); vmtxTable += 2; cache.SetTopSidebearing(i, ReadOpenTypeShort(vmtxTable)); vmtxTable += 2; } ushort fixedAdvance = cache.GetAdvanceHeight(unchecked((ushort)(numberOfMetrics - 1))); for (ushort i = numberOfMetrics; i < cache.GlyphCount; i++) { cache.SetAdvanceHeight(i, fixedAdvance); cache.SetTopSidebearing(i, ReadOpenTypeShort(vmtxTable)); vmtxTable += 2; } return true; } ////// Prevent JIT from inlining this method, so that PresentationCFFRasterizer.dll and PresentationCFFRasterizerNative.dll are loaded on demand. /// ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] [MethodImpl(MethodImplOptions.NoInlining)] private void ReadCFFMetrics( FontFaceLayoutInfo cache, bool vmtxPresent, ushort typoAscent, ushort typoDescent) { using (OTFRasterizer otfRasterizer = new OTFRasterizer()) { ushort numberOfGlyphs = otfRasterizer.NewFont(_unmanagedMemoryStream, SourceUri, _faceIndex); if (numberOfGlyphs != cache.GlyphCount) throw new FileFormatException(SourceUri); MS.Internal.FontRasterization.Transform tform = new MS.Internal.FontRasterization.Transform(); tform.a01 = tform.a10 = 0; tform.a00 = tform.a11 = cache.DesignEmHeight * 0x10000; otfRasterizer.NewTransform( 12, tform, OverscaleMode.None, RenderingFlags.None); GlyphMetrics glyphMetrics = new GlyphMetrics(); for (ushort i = 0; i < numberOfGlyphs; ++i) { otfRasterizer.NewGlyph(i); otfRasterizer.GetMetrics(out glyphMetrics); // per http://www.microsoft.com/typography/otspec/hmtx.htm // rsb = aw - (lsb + xmax - xmin) short rsb = (short)(cache.GetAdvanceWidth(i) - ((int)cache.GetLeftSidebearing(i) + glyphMetrics.width)); cache.SetRightSidebearing(i, rsb); int yMax = glyphMetrics.horizontalOrigin.y; int yMin = yMax - glyphMetrics.height; if (!vmtxPresent) { // There's no vmtx - fallback appropriately // Win 9x uses the typographic height (typo ascender - typo descender), // but NT uses the cell height (cell ascender + cell descender). // Which shall we use? The problem with the cell height is that in a // multilingual font it may be much taller than the East Asian glyphs, // causing the common case (East Asian vertical text) to appear too // widely spaced. The problem with the typographic height is that it // includes little or no extra space for diacritic marks. // Choice: use the Typographic height: It is best for FE, and the font // can fix non East Asian diacritic cases if it wishes by providing a vmtx. cache.SetAdvanceHeight(i, (ushort)(typoAscent + typoDescent)); cache.SetTopSidebearing(i, unchecked((short)(typoAscent - yMax))); } // bsb = ah - (tsb + ymax - ymin) short bsb = unchecked((short)(cache.GetAdvanceHeight(i) - ((int)cache.GetTopSidebearing(i) + glyphMetrics.height))); cache.SetBottomSidebearing(i, bsb); short baseline = unchecked((short)-yMin); cache.SetBaseline(i, baseline); } } } ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadGlyfMetrics( FontFaceLayoutInfo cache, ushort indexToLocFormat, bool vmtxPresent, ushort typoAscent, ushort typoDescent) { CheckedPointer locaTable = GetTable(TrueTypeTags.IndexToLoc); CheckedPointer glyphTable = GetTable(TrueTypeTags.GlyphData); // For TrueType fonts we can get ideal glyph metrics from 'glyf' table. // For CFF fonts we need to call into the CFF rasterizer. if (locaTable.IsNull || glyphTable.IsNull) { ReadCFFMetrics(cache, vmtxPresent, typoAscent, typoDescent); return; } for (ushort i = 0; i < cache.GlyphCount; ++i) { short xMin, xMax, yMin, yMax; try { // extract glyph bounding box int offset, nextGlyphOffset; if (indexToLocFormat == 0) // short format { offset = 2 * ReadOpenTypeUShort(locaTable + i * 2); nextGlyphOffset = 2 * ReadOpenTypeUShort(locaTable + (i + 1) * 2); } else // long format { offset = ReadOpenTypeLong(locaTable + i * 4); nextGlyphOffset = ReadOpenTypeLong(locaTable + (i + 1) * 4); } // per http://www.microsoft.com/typography/otspec/loca.htm // a glyph without an outline has the same value for its offset // as the next glyph if (nextGlyphOffset == offset) { xMin = xMax = yMin = yMax = 0; } else { xMin = ReadOpenTypeShort(glyphTable + (offset + OFF_glyf_xMin)); xMax = ReadOpenTypeShort(glyphTable + (offset + OFF_glyf_xMax)); yMin = ReadOpenTypeShort(glyphTable + (offset + OFF_glyf_yMin)); yMax = ReadOpenTypeShort(glyphTable + (offset + OFF_glyf_yMax)); } } catch (ArgumentOutOfRangeException) { // loca table entry points outside of glyf table // fill metrics with zeroes xMin = xMax = yMin = yMax = 0; } // per http://www.microsoft.com/typography/otspec/hmtx.htm // rsb = aw - (lsb + xmax - xmin) short rsb = (short)(cache.GetAdvanceWidth(i) - ((int)cache.GetLeftSidebearing(i) + xMax - xMin)); cache.SetRightSidebearing(i, rsb); if (!vmtxPresent) { // There's no vmtx - fallback appropriately // Win 9x uses the typographic height (typo ascender - typo descender), // but NT uses the cell height (cell ascender + cell descender). // Which shall we use? The problem with the cell height is that in a // multilingual font it may be much taller than the East Asian glyphs, // causing the common case (East Asian vertical text) to appear too // widely spaced. The problem with the typographic height is that it // includes little or no extra space for diacritic marks. // Choice: use the Typographic height: It is best for FE, and the font // can fix non East Asian diacritic cases if it wishes by providing a vmtx. cache.SetAdvanceHeight(i, (ushort)(typoAscent + typoDescent)); cache.SetTopSidebearing(i, unchecked((short)(typoAscent - yMax))); } // bsb = ah - (tsb + ymax - ymin) short bsb = unchecked((short)(cache.GetAdvanceHeight(i) - ((int)cache.GetTopSidebearing(i) + yMax - yMin))); cache.SetBottomSidebearing(i, bsb); short baseline = unchecked((short)-yMin); cache.SetBaseline(i, baseline); } } // reads glyph advance widths and sidebearings from font tables // see http://www.microsoft.com/typography/otspec/hmtx.htm for details private void ReadAdvances( FontFaceLayoutInfo cache, CheckedPointer hmtxTable, ushort numberOfMetrics, ushort indexToLocFormat, ushort typoAscent, ushort typoDescent ) { cache.CreateAdvanceWidthsArray(); // fill in advanceWidth and left sidebearing ReadHmtx(cache, hmtxTable, numberOfMetrics); // fill in advanceHeight and top sidebearing bool vmtxPresent = ReadVmtx(cache); // fill the rest from 'glyf' table in TrueType case ReadGlyfMetrics(cache, indexToLocFormat, vmtxPresent, typoAscent, typoDescent); } #endregion #region Fields // file-specific state private CheckedPointer _fileStream; private UnmanagedMemoryStream _unmanagedMemoryStream; private Uri _sourceUri; private int _numFaces; private FontTechnology _technology; // face-specific state private int _faceIndex; private int _directoryOffset; // table directory offset for TTC, 0 for TTF private DirectoryEntry[] _tableDirectory; #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Security; using System.Text; using System.Windows; using System.Windows.Markup; // for XmlLanguage using System.Windows.Media; using MS.Internal; using MS.Internal.PresentationCore; using MS.Utility; using MS.Internal.FontCache; using MS.Internal.FontRasterization; using Adobe.CffRasterizer; // 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.FontFace { ////// Font technology. /// internal enum FontTechnology { // this enum need to be kept in order of preference that we want to use with duplicate font face, // highest value will win in case of duplicate PostscriptOpenType, TrueType, TrueTypeCollection } internal class TrueTypeFontDriver { #region Font constants, structures and enumerations /* OS/2 table */ private const int OFF_OS2_version = 0; // private const int OFF_OS2_xAvgCharWidth = 2; private const int OFF_OS2_usWeightClass = 4; private const int OFF_OS2_usWidthClass = 6; private const int OFF_OS2_fsType = 8; // private const int OFF_OS2_ySubscriptXSize = 10; // private const int OFF_OS2_ySubscriptYSize = 12; // private const int OFF_OS2_ySubscriptXOffset = 14; // private const int OFF_OS2_ySubscriptYOffset = 16; // private const int OFF_OS2_ySuperScriptXSize = 18; // private const int OFF_OS2_ySuperScriptYSize = 20; // private const int OFF_OS2_ySuperScriptXOffset = 22; // private const int OFF_OS2_ySuperScriptYOffset = 24; private const int OFF_OS2_yStrikeOutSize = 26; private const int OFF_OS2_yStrikeOutPosition = 28; // private const int OFF_OS2_sFamilyClass = 30; // private const int OFF_OS2_Panose = 32; // private const int OFF_OS2_ulCharRange = 42; // private const int OFF_OS2_achVendID = 58; private const int OFF_OS2_usSelection = 62; // private const int OFF_OS2_usFirstChar = 64; // private const int OFF_OS2_usLastChar = 66; private const int OFF_OS2_sTypoAscender = 68; private const int OFF_OS2_sTypoDescender = 70; private const int OFF_OS2_sTypoLineGap = 72; private const int OFF_OS2_usWinAscent = 74; private const int OFF_OS2_usWinDescent = 76; // private const int OFF_OS2_ulCodePageRange1 = 78; // private const int OFF_OS2_ulCodePageRange2 = 82; // fields below are valid when version >= 2 private const int OFF_OS2_sxHeight = 86; private const int OFF_OS2_sCapHeight = 88; [Flags] private enum Os2SelectionFlags { Italic = 1, Underscore = 2, Negative = 4, Outlined = 8, Strikeout = 16, Bold = 32, Regular = 64, DontUseWinLineMetrics = 128, WeightWidthSlopeOnly = 256, Oblique = 512 } private static class Os2EmbeddingFlags { public const ushort RestrictedLicense = 0x0002; public const ushort PreviewAndPrint = 0x0004; public const ushort Editable = 0x0008; // The font is installable if all bits in the InstallableMask are set to zero. public const ushort InstallableMask = RestrictedLicense | PreviewAndPrint | Editable; public const ushort NoSubsetting = 0x0100; public const ushort BitmapOnly = 0x0200; } /* head table */ private const int OFF_head_flags = 16; private const int OFF_head_unitsPerEm = 18; private const int OFF_head_macStyle = 44; private const int OFF_head_indexToLocFormat = 50; [Flags] private enum HeadFlags { OptimizedForClearType = 8192 // bit 13 } /* hhea table */ private const int OFF_hhea_ascender = 4; private const int OFF_hhea_descender = 6; private const int OFF_hhea_lineGap = 8; private const int OFF_hhea_numberOfHMetrics = 34; // maxp table private const int OFF_maxp_numGlyphs = 4; /* post table */ private const int OFF_post_underlinePosition = 8; private const int OFF_post_underlineThickness = 10; /* PCLT table */ private const int OFF_PCLT_xHeight = 10; private const int OFF_PCLT_CapHeight = 16; // glyf table private const int OFF_glyf_xMin = 2; private const int OFF_glyf_yMin = 4; private const int OFF_glyf_xMax = 6; private const int OFF_glyf_yMax = 8; // vhea table private const int OFF_vhea_numOfLongVerMetrics = 34; // eblc table private const int OFF_eblc_numSizes = 4; private const int OFF_eblc_bitmapSizeTables = 8; private const int EblcSizeOfBitmapSizeTable = 48; // within bitmapSizeTable private const int EblcIndexSubTableArrayOffset = 0; private const int EblcNumberOfIndexSubTables = 8; // within index subtable private const int EblcFirstGlyph = 0; private const int EblcLastGlyph = 2; private const int EblcSizeOfIndexSubTable = 8; private class EastAsianLanguageCandidate { public EastAsianLanguageCandidate(int languageIndex) { _languageIndex = languageIndex; } public void AddCodepoint(int index) { _codepointsFound |= (1 << index); } public int CodePoints { get { return _codepointsFound; } } public int LanguageIndex { get { return _languageIndex; } } private int _languageIndex; private int _codepointsFound; } [Flags] private enum MacStyleFlags { Bold = 1, Italic = 2, Underline = 4, Outline = 8, Shadow = 16, Condensed = 32, Extended = 64 } internal struct ParsedNameTable { // LocalizedName arrays generated by FontDriver are sorted by OriginalLCID value. internal LocalizedName[] familyNames; internal LocalizedName[] win32FamilyNames; internal LocalizedName[] faceNames; internal LocalizedName[] win32faceNames; internal LocalizedName[] versionStrings; internal LocalizedName[] copyrights; internal LocalizedName[] manufacturerNames; internal LocalizedName[] trademarks; internal LocalizedName[] designerNames; internal LocalizedName[] descriptions; internal LocalizedName[] vendorUrls; internal LocalizedName[] designerUrls; internal LocalizedName[] licenseDescriptions; internal LocalizedName[] sampleTexts; internal double version; } ////// BasicFontFaceInfo contains properties required for font enumeration and style matching /// internal struct BasicFontFaceInfo { internal ParsedNameTable nameTable; internal int faceIndex; internal FontStyle style; internal FontWeight weight; internal FontStretch stretch; internal bool symbol; internal ushort designEmHeight; internal ushort designCellAscent; internal ushort designCellDescent; internal int designLineSpacing; } private struct DirectoryEntry { internal TrueTypeTags tag; internal CheckedPointer pointer; } private enum TrueTypeTags : int { CharToIndexMap = 0x636d6170, /* 'cmap' */ ControlValue = 0x63767420, /* 'cvt ' */ BitmapData = 0x45424454, /* 'EBDT' */ BitmapLocation = 0x45424c43, /* 'EBLC' */ BitmapScale = 0x45425343, /* 'EBSC' */ Editor0 = 0x65647430, /* 'edt0' */ Editor1 = 0x65647431, /* 'edt1' */ Encryption = 0x63727970, /* 'cryp' */ FontHeader = 0x68656164, /* 'head' */ FontProgram = 0x6670676d, /* 'fpgm' */ GridfitAndScanProc = 0x67617370, /* 'gasp' */ GlyphDirectory = 0x67646972, /* 'gdir' */ GlyphData = 0x676c7966, /* 'glyf' */ HoriDeviceMetrics = 0x68646d78, /* 'hdmx' */ HoriHeader = 0x68686561, /* 'hhea' */ HorizontalMetrics = 0x686d7478, /* 'hmtx' */ IndexToLoc = 0x6c6f6361, /* 'loca' */ Kerning = 0x6b65726e, /* 'kern' */ LinearThreshold = 0x4c545348, /* 'LTSH' */ MaxProfile = 0x6d617870, /* 'maxp' */ NamingTable = 0x6e616d65, /* 'name' */ OS_2 = 0x4f532f32, /* 'OS/2' */ Postscript = 0x706f7374, /* 'post' */ PreProgram = 0x70726570, /* 'prep' */ VertDeviceMetrics = 0x56444d58, /* 'VDMX' */ VertHeader = 0x76686561, /* 'vhea' */ VerticalMetrics = 0x766d7478, /* 'vmtx' */ PCLT = 0x50434C54, /* 'PCLT' */ TTO_GSUB = 0x47535542, /* 'GSUB' */ TTO_GPOS = 0x47504F53, /* 'GPOS' */ TTO_GDEF = 0x47444546, /* 'GDEF' */ TTO_BASE = 0x42415345, /* 'BASE' */ TTO_JSTF = 0x4A535446, /* 'JSTF' */ OTTO = 0x4f54544f, // Adobe OpenType 'OTTO' TTC_TTCF = 0x74746366 // 'ttcf' } // Name table constants: http://www.microsoft.com/typography/otspec/name.htm private enum NameTableNameID { CopyrightNotice = 0, FontFamilyName = 1, FontSubfamilyName = 2, UniqueFontIdentifier = 3, FullFontName = 4, Version = 5, PostscriptName = 6, Trademark = 7, ManufacturerName = 8, Designer = 9, Description = 10, URLVendor = 11, URLDesigner = 12, LicenseDescription = 13, LicenseInfoURL = 14, Reserved = 15, PreferredFamily = 16, PreferredSubfamily = 17, CompatibleFullName = 18, SampleText = 19, PostScriptCIDName = 20 } private enum PlatformID : ushort { Unicode = 0, Macintosh = 1, // Only MacRoman character set supported ISO = 2, // Deprecated since OpenType 1.3, not supported Microsoft = 3, Custom = 4 // Not supported } private enum NameTableMicrosoftEncodingID : ushort { Symbol = 0, UnicodeBMPOnly = 1, ShiftJIS = 2, PRC = 3, Big5 = 4, Wansung = 5, Johab = 6, // the gap is intentional, values from 7 to 9 are reserved per OpenType spec Unicode = 10 } ////// CharacterEncoding represents all supported platform and encoding combinations. /// Platforms and encodings are used by both the name and cmap tables, /// although not all combinations are used by both tables. /// /// Where multiple cmaps are available, higher numbered encodings are chosen /// in preference to lower numbered encodings. /// private enum CharacterEncoding { // plat enc cmap range Unknown = 0, // ==== === ========== MacRoman = 1, // 1 0 Unicode = 2, // 0 any MsSymbol = 3, // 3 0 up to 2^8 characters at U+F000 MsShiftJis = 4, // 3 2 up to 2^16 characters MsPrc = 5, // 3 3 up to 2^16 characters MsBig5 = 6, // 3 4 up to 2^16 characters MsWansung = 7, // 3 5 up to 2^16 characters MsUcs2 = 8, // 3 1 up to 2^16 characters, doesn't include surrogates MsUcs4 = 9, // 3 10 full Unicode repertoire, includes surrogates } private struct MetricSearchList { internal static readonly ushort[,] xHeight = { { 0x78, 100 }, // basic Latin, lower case letter x { 0x3C4, 100 }, // Greek, small letter tau { 0x433, 100 }, // Cyrillic, small letter ghe { 0x578, 100 }, // Armenian, small letter vo { 0x5DD, 75 }, // Hebrew, letter final mem { 0x62F, 100 }, // Arabic, letter dal { 0x717, 100 }, // Syriac, letter he { 0x784, 100 }, // Thaana, letter baa { 0x930, 50 }, // Devaganari, 1/2 of letter ra { 0xA30, 50 }, // Gurmukhi, 1/2 of letter ra { 0xAA0, 50 }, // Gujarati, 1/2 of letter ttha { 0xB20, 50 }, // Oriya, 1/2 of letter ttha { 0xBB0, 50 }, // Tamil, 1/2 of letter ra { 0xC10, 100 }, // Telugo, letter ai { 0xC89, 50 }, // Kannada, 1/2 of letter U { 0xD30, 66 }, // Malayalam, 2/3 of letter ra { 0xDB0, 50 }, // Sinhala, 1/2 of letter mahaapraanadayanna { 0xE20, 75 }, // Thai, 75% of letter pho samphao { 0xEC0, 50 }, // Lao, 1/2 of vowel sign E { 0xF40, 100 }, // Tibetan, letter ka { 0x1010, 100 }, // Myanmar, letter ta { 0x10F2, 100 }, // Georgian, letter hie { 0x1100, 50 }, // Hangul Jamo, 1/2 of choseong kieok { 0x1210, 50 }, // Ethiopic, 1/2 of syllable hha { 0x13A0, 50 }, // Cherokee, 1/2 of letter A { 0x14C0, 100 }, // Unified Canadian Aboriginal Syllabics, ne { 0x1681, 100 }, // Ogham, letter beith { 0x17A0, 50 }, // Khmer, 1/2 of letter ha { 0x1884, 100 } // Mongolian, letter ali gali inverted ubadama }; internal static readonly ushort[,] capsHeight = { { 0x48, 100 }, // basic Latin, upper case letter H }; } private struct EastAsianLanguages { internal static readonly char[,] RepresentativeCodepoints = { // Hiragana { '\u3044', '\u3046', '\u3093', '\u3057', '\u306E', '\u304B' }, // Hangul { '\uC774', '\uB2E4', '\uB294', '\uC758', '\uC5D0', '\uD558' }, // CHS { '\u6C49', '\u5B57', '\u4E2D', '\u7684', '\u4E2A', '\u4EEC' }, // CHT { '\u6F22', '\u5011', '\u4E09', '\u4E86', '\u5B78', '\u7232' } }; } ////// Font families that look too thin with default text contrast settings. /// For such fonts we increase text contrast value by 2. /// The list must be sorted alphabetically. /// private static readonly string[] _thinFontFamilyNames = { "Courier New", "Fixed Miriam Transparent", "Miriam Fixed", "Rod", "Rod Transparent", "Simplified Arabic Fixed" }; private static readonly CultureInfo EnglishUSCulture = System.Windows.Markup.TypeConverterHelper.EnglishUSCulture; #endregion #region Byte, Short, Long etc. accesss to CheckedPointers ////// The follwoing APIs extract OpenType variable types from OpenType font /// files. OpenType variables are stored big-endian, and the type are named /// as follows: /// Byte - signed 8 bit /// UShort - unsigned 16 bit /// Short - signed 16 bit /// ULong - unsigned 32 bit /// Long - signed 32 bit /// /// ////// Critical: Calls into probe which is critical and also has unsafe code blocks /// TreatAsSafe: This code is Ok to expose /// [SecurityCritical,SecurityTreatAsSafe] private static byte ReadOpenTypeByte(CheckedPointer pointer) { unsafe { byte * readBuffer = (byte *)pointer.Probe(0, 1); return readBuffer[0]; } } ////// Critical: Calls into probe which is critical and also has unsafe code blocks /// TreatAsSafe: This code is Ok to expose /// [SecurityCritical,SecurityTreatAsSafe] private static ushort ReadOpenTypeUShort(CheckedPointer pointer) { unsafe { byte * readBuffer = (byte *)pointer.Probe(0, 2); ushort result = (ushort)((readBuffer[0] << 8) + readBuffer[1]); return result; } } ////// Critical: Calls into probe which is critical and also has unsafe code blocks /// TreatAsSafe: This code is Ok to expose /// [SecurityCritical,SecurityTreatAsSafe] private static short ReadOpenTypeShort(CheckedPointer pointer) { unsafe { byte * readBuffer = (byte *)pointer.Probe(0, 2); short result = (short)((readBuffer[0] << 8) + readBuffer[1]); return result; } } ////// Critical: Calls into probe which is critical and also has unsafe code blocks /// TreatAsSafe: This code IS Ok to expose /// [SecurityCritical,SecurityTreatAsSafe] private static int ReadOpenTypeLong(CheckedPointer pointer) { unsafe { byte * readBuffer = (byte *)pointer.Probe(0, 4); int result = (int)((((((readBuffer[0] << 8) + readBuffer[1]) << 8) + readBuffer[2]) << 8) + readBuffer[3]); return result; } } ////// Critical: Calls into probe which is critical and also has unsafe code blocks /// TreatAsSafe: This code is ok to expose /// [SecurityCritical,SecurityTreatAsSafe] private static uint ReadOpenTypeULong(CheckedPointer pointer) { unsafe { byte * readBuffer = (byte *)pointer.Probe(0, 4); uint result = (uint)((((((readBuffer[0] << 8) + readBuffer[1]) << 8) + readBuffer[2]) << 8) + readBuffer[3]); return result; } } #endregion Byte, Short, Long etc. accesss to CheckedPointers #region Constructor and general helpers ////// Critical: constructs data for a checked pointer. /// [SecurityCritical] internal TrueTypeFontDriver(UnmanagedMemoryStream unmanagedMemoryStream, Uri sourceUri) { _sourceUri = sourceUri; _unmanagedMemoryStream = unmanagedMemoryStream; _fileStream = new CheckedPointer(unmanagedMemoryStream); try { CheckedPointer seekPosition = _fileStream; TrueTypeTags typeTag = (TrueTypeTags)ReadOpenTypeLong(seekPosition); seekPosition += 4; if (typeTag == TrueTypeTags.TTC_TTCF) { // this is a TTC file, we need to decode the ttc header _technology = FontTechnology.TrueTypeCollection; seekPosition += 4; // skip version _numFaces = ReadOpenTypeLong(seekPosition); } else if (typeTag == TrueTypeTags.OTTO) { _technology = FontTechnology.PostscriptOpenType; _numFaces = 1; } else { _technology = FontTechnology.TrueType; _numFaces = 1; } } catch (ArgumentOutOfRangeException e) { // convert exceptions from CheckedPointer to FileFormatException throw new FileFormatException(SourceUri, e); } } internal void SetFace(int faceIndex) { if (_technology == FontTechnology.TrueTypeCollection) { if (faceIndex < 0 || faceIndex >= _numFaces) throw new ArgumentOutOfRangeException("faceIndex"); } else { if (faceIndex != 0) throw new ArgumentOutOfRangeException("faceIndex", SR.Get(SRID.FaceIndexValidOnlyForTTC)); } try { CheckedPointer seekPosition = _fileStream + 4; if (_technology == FontTechnology.TrueTypeCollection) { // this is a TTC file, we need to decode the ttc header // skip version, num faces, OffsetTable array seekPosition += (4 + 4 + 4 * faceIndex); _directoryOffset = ReadOpenTypeLong(seekPosition); seekPosition = _fileStream + (_directoryOffset + 4); // 4 means that we skip the version number } _faceIndex = faceIndex; int numTables = ReadOpenTypeUShort(seekPosition); seekPosition += 2; // quick check for malformed fonts, see if numTables is too large // file size should be >= sizeof(offset table) + numTables * (sizeof(directory entry) + minimum table size (4)) long minimumFileSize = (4 + 2 + 2 + 2 + 2) + numTables * (4 + 4 + 4 + 4 + 4); if (_fileStream.Size < minimumFileSize) { throw new FileFormatException(SourceUri); } _tableDirectory = new DirectoryEntry[numTables]; // skip searchRange, entrySelector and rangeShift seekPosition += 6; // I can't use foreach here because C# disallows modifying the current value for (int i = 0; i < _tableDirectory.Length; ++i) { _tableDirectory[i].tag = (TrueTypeTags)ReadOpenTypeLong(seekPosition); seekPosition += 8; // skip checksum int offset = ReadOpenTypeLong(seekPosition); seekPosition += 4; int length = ReadOpenTypeLong(seekPosition); seekPosition += 4; _tableDirectory[i].pointer = _fileStream.CheckedProbe(offset, length); } } catch (ArgumentOutOfRangeException e) { // convert exceptions from CheckedPointer to FileFormatException throw new FileFormatException(SourceUri, e); } } ////// Critical: This code calls to create a checked pointer and might return a null checked pointer /// All consumers need to validate is null before using it. /// TreatAsSafe: This information is ok to expose /// [SecurityCritical,SecurityTreatAsSafe] private CheckedPointer GetTable(TrueTypeTags tag) { foreach (DirectoryEntry directoryEntry in _tableDirectory) { if (tag == directoryEntry.tag) return directoryEntry.pointer; } return new CheckedPointer(); } #endregion #region Public methods and properties internal int NumFaces { get { return _numFaces; } } private Uri SourceUri { get { return _sourceUri; } } ////// Create font subset that includes glyphs in the input collection. /// ////// TreatAsSafe: This API could be public in terms of security as it demands unmanaged code /// Critical: Does an elevation by calling TrueTypeSubsetter which we are treating as equivalent to /// unsafe native methods /// [SecurityCritical, SecurityTreatAsSafe] internal byte[] ComputeFontSubset(ICollectionglyphs) { SecurityHelper.DemandUnmanagedCode(); int fileSize = _fileStream.Size; unsafe { void * fontData = _fileStream.Probe(0, fileSize); // Since we currently don't have a way to subset CFF fonts, just return a copy of the font. if (_technology == FontTechnology.PostscriptOpenType) { byte[] fontCopy = new byte[fileSize]; Marshal.Copy((IntPtr)fontData, fontCopy, 0, fileSize); return fontCopy; } ushort [] glyphArray; if (glyphs == null || glyphs.Count == 0) glyphArray = null; else { glyphArray = new ushort[glyphs.Count]; glyphs.CopyTo(glyphArray, 0); } return TrueTypeSubsetter.ComputeSubset(fontData, fileSize, SourceUri, _directoryOffset, glyphArray); } } internal void GetBasicFontFaceInfo(ref BasicFontFaceInfo basicFontFaceInfo, out bool skipFontDifferentiation) { try { CheckedPointer headTable = GetTable(TrueTypeTags.FontHeader); CheckedPointer os2Table = GetTable(TrueTypeTags.OS_2); CheckedPointer hheaTable = GetTable(TrueTypeTags.HoriHeader); if (headTable.IsNull || hheaTable.IsNull) { throw new FileFormatException(SourceUri); } basicFontFaceInfo.faceIndex = _faceIndex; ReadStyles(os2Table, headTable, out basicFontFaceInfo.style, out basicFontFaceInfo.weight, out basicFontFaceInfo.stretch, out skipFontDifferentiation); DecodeNameTable(ref basicFontFaceInfo.nameTable); basicFontFaceInfo.symbol = DecodeCmapTable(null); ReadBasicMetrics( headTable, os2Table, hheaTable, out basicFontFaceInfo.designEmHeight, out basicFontFaceInfo.designCellAscent, out basicFontFaceInfo.designCellDescent, out basicFontFaceInfo.designLineSpacing ); } catch (ArgumentOutOfRangeException e) { // convert exceptions from CheckedPointer to FileFormatException throw new FileFormatException(SourceUri, e); } } /// /// Critical: This code sets glyphcount which is used to index into unmanaged pointers /// TreatAsSafe: The value is computed from cache and gettable entries all of which are tracked /// [SecurityCritical,SecurityTreatAsSafe] internal void GetLayoutFontFaceInfo(FontFaceLayoutInfo cache) { try { cache.FontTechnology = _technology; CheckedPointer hheaTable = GetTable(TrueTypeTags.HoriHeader); CheckedPointer headTable = GetTable(TrueTypeTags.FontHeader); CheckedPointer os2Table = GetTable(TrueTypeTags.OS_2); CheckedPointer hmtxTable = GetTable(TrueTypeTags.HorizontalMetrics); CheckedPointer postTable = GetTable(TrueTypeTags.Postscript); if (headTable.IsNull || hheaTable.IsNull || hmtxTable.IsNull) { throw new FileFormatException(SourceUri); } ushort numberOfHMetrics = ReadOpenTypeUShort(hheaTable + OFF_hhea_numberOfHMetrics); if (numberOfHMetrics == 0) throw new FileFormatException(SourceUri); // deduce the number of glyphs from the size of the hmtxTable, should be the same as from maxp cache.GlyphCount = (ushort)((hmtxTable.Size - numberOfHMetrics * 2) / 2); FontStyle fontStyle; FontWeight fontWeight; FontStretch fontStretch; bool skipFontDifferentiation; ReadStyles(os2Table, headTable, out fontStyle, out fontWeight, out fontStretch, out skipFontDifferentiation); cache.Style = fontStyle; cache.Weight = fontWeight; cache.Stretch = fontStretch; // cmap decoding and initialization of special glyph values cache.Symbol = DecodeCmapTable(cache); ushort blankGlyph; cache.CharacterMap.TryGetValue(' ', out blankGlyph); // If the font doesn't support space character, we'll use glyph index zero, // which has a square box shape in most fonts. cache.BlankGlyph = blankGlyph; ushort invalidGlyph; // dotted circle if (!cache.CharacterMap.TryGetValue(0x25CC, out invalidGlyph)) { // dotted circle not found, try NBSP if (!cache.CharacterMap.TryGetValue(0x00A0, out invalidGlyph)) { invalidGlyph = cache.BlankGlyph; // default to the blank glyph } } cache.InvalidGlyph = invalidGlyph; ushort unitsPerEm, designCellAscent, designCellDescent; int designLineSpacing; ReadBasicMetrics( headTable, os2Table, hheaTable, out unitsPerEm, out designCellAscent, out designCellDescent, out designLineSpacing ); cache.DesignEmHeight = unitsPerEm; cache.DesignCellAscent = designCellAscent; cache.DesignCellDescent = designCellDescent; if (!postTable.IsNull) { cache.UnderlinePosition = ReadOpenTypeShort(postTable + OFF_post_underlinePosition); ushort underlineThickness = ReadOpenTypeUShort(postTable + OFF_post_underlineThickness); // Correct zero underline thickness (happens with "Bodoni MT Condensed") // to a reasonable default value taken from Arial. if (underlineThickness == 0) underlineThickness = (ushort)((unitsPerEm + 7)/14); cache.UnderlineThickness = underlineThickness; } else { // correct misssing underline metrics to reasonable default values from Arial cache.UnderlinePosition = (short)(-((unitsPerEm + 5) / 10)); cache.UnderlineThickness = (ushort)((unitsPerEm + 7)/14); } ushort typoAscent, typoDescent; if (!os2Table.IsNull) { typoAscent = ReadOpenTypeUShort(os2Table + OFF_OS2_sTypoAscender); int temp = -ReadOpenTypeShort(os2Table + OFF_OS2_sTypoDescender); if (temp < 0) { /* a few existing fonts have the sign for this value reversed */ temp = - temp; } typoDescent = (ushort)temp; // some fonts have invalid values for typoAscent and typoDescent if (typoAscent > ushort.MaxValue / 2 || typoDescent > ushort.MaxValue / 2) { typoAscent = cache.DesignCellAscent; typoDescent = cache.DesignCellDescent; } ushort strikeThroughThickness = ReadOpenTypeUShort(os2Table + OFF_OS2_yStrikeOutSize); if (strikeThroughThickness == 0) strikeThroughThickness = cache.UnderlineThickness; cache.StrikethroughThickness = strikeThroughThickness; cache.StrikethroughPosition = (short)ReadOpenTypeUShort(os2Table + OFF_OS2_yStrikeOutPosition); } else { typoAscent = cache.DesignCellAscent; typoDescent = cache.DesignCellDescent; cache.StrikethroughThickness = cache.UnderlineThickness; cache.StrikethroughPosition = (short)(unitsPerEm / 3); } // we don't use normalized em ascent and descent yet // but we want to keep this code in case we need it // design em descent and ascent come from typo ascent and descent // // normalization of designEmAscent and designEmDescent to make their sum correspond to designEmHeight as per CSS3 spec // // if ((designEmDescent + cache.DesignEmAscent) != 0) // { // designEmDescent = (ushort)((designEmDescent * unitsPerEm) / // (designEmDescent + cache.DesignEmAscent)); // } // else // { // /* in this rare malformed font case, default to 20% for the descender */ // designEmDescent = (ushort)(unitsPerEm * 20 / 100); // } // cache.DesignEmAscent = (ushort)(unitsPerEm - designEmDescent); ushort indexToLocFormat = ReadOpenTypeUShort(headTable + OFF_head_indexToLocFormat); ReadAdvances(cache, hmtxTable, numberOfHMetrics, indexToLocFormat, typoAscent, typoDescent); ReadRenderingHints(headTable, cache); cache.EmbeddingRights = ReadFontEmbeddingRights(os2Table); ComputeHeights(os2Table, unitsPerEm, cache); ParsedNameTable nameTable = new ParsedNameTable(); DecodeNameTable(ref nameTable); cache.AddLocalizedNames(ref nameTable, skipFontDifferentiation); ComputeFontContrastAdjustment(ref nameTable, cache); } catch (ArgumentOutOfRangeException e) { // convert exceptions from CheckedPointer to FileFormatException throw new FileFormatException(SourceUri, e); } } ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] internal void GetShapingFontFaceInfo(FontFaceLayoutInfo cache) { try { cache.SetGsub(GetTable(TrueTypeTags.TTO_GSUB)); cache.SetGpos(GetTable(TrueTypeTags.TTO_GPOS)); cache.SetGdef(GetTable(TrueTypeTags.TTO_GDEF)); cache.SetJstf(GetTable(TrueTypeTags.TTO_JSTF)); } catch (ArgumentOutOfRangeException e) { // convert exceptions from CheckedPointer to FileFormatException throw new FileFormatException(SourceUri, e); } } #endregion Public methods and properties #region Character mapping and language encoding handling ////// /// TrueType/OpenType fonts support a wide range of encodings for both /// names and character to glyph maps. /// /// We provide one class (CharacterToUnicodeMapper) that encapsulates all /// decoding required by 'name' and 'cmap' tables. /// /// Fonts identify which encoding is being used for a name or a cmap through /// a pair of enums: 'PlatformId' and 'EncodingId'. The static method /// 'PlatformIdAndEncodingIdToCharacterEncoding' maps from supported combinations /// of PatformId and EncodingId to a single internal enum 'CharacterEncoding'. /// /// Returns CharacterEncoding.Unknown for unrecognised/unsupported combinations /// of platform id and encoding id. /// /// private static CharacterEncoding PlatformIdAndEncodingIdToCharacterEncoding( PlatformID currentPlatform, ushort currentEncoding) { switch (currentPlatform) { case PlatformID.Microsoft: switch ((NameTableMicrosoftEncodingID)currentEncoding) { case NameTableMicrosoftEncodingID.Symbol: return CharacterEncoding.MsSymbol; case NameTableMicrosoftEncodingID.UnicodeBMPOnly: return CharacterEncoding.MsUcs2; case NameTableMicrosoftEncodingID.Unicode: return CharacterEncoding.MsUcs4; case NameTableMicrosoftEncodingID.ShiftJIS: return CharacterEncoding.MsShiftJis; case NameTableMicrosoftEncodingID.PRC: return CharacterEncoding.MsPrc; case NameTableMicrosoftEncodingID.Big5: return CharacterEncoding.MsBig5; case NameTableMicrosoftEncodingID.Wansung: return CharacterEncoding.MsWansung; } break; case PlatformID.Unicode: return CharacterEncoding.Unicode; case PlatformID.Macintosh: if (currentEncoding == 0) // Macintosh Roman encoding return CharacterEncoding.MacRoman; break; } return CharacterEncoding.Unknown; } private static CultureInfo MapTrueTypeLangIdToCulture(PlatformID platformID, ushort languageID) { switch (platformID) { case PlatformID.Macintosh: // handle English Macintosh language ID if (languageID == 0) return EnglishUSCulture; // ignore the rest of the language IDs return null; case PlatformID.Microsoft: // in Microsoft case we have 1 to 1 mapping if (languageID == 0x0F00) { // There is an invalid entry with this lang id in times.ttf on Windows XP and before. // Don't throw a first chance exception in that case. return null; } try { return CultureInfo.GetCultureInfo(languageID); } catch (ArgumentException) { // don't skip the font because of incorrect culture IDs return null; } case PlatformID.ISO: case PlatformID.Custom: case PlatformID.Unicode: default: // these 3 platforms cannot have name table entries and therefore don't have language ID return null; } } // MacRomanUpperToUnicode mapping table - for Mac Roman encoded cmaps and names. // MacRoman 0-127 is identical to Unicode 0-127. // MacRoman 128-255 uses a mapping specified by Apple and published on // the Unicode web site at: // http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT // // Note that although NLS provides a mapping for Mac Roman using the magic // encoding number 10000, that mapping is wrong for Apple codepoint 0xBD: // it maps Greek omega to electronic symbol 'ohms'. private static readonly int[] MacRomanUpperToUnicode = new int[] { 0x0000C4, 0x0000C5, 0x0000C7, 0x0000C9, 0x0000D1, 0x0000D6, 0x0000DC, 0x0000E1, // 0x80 - 0x87 0x0000E0, 0x0000E2, 0x0000E4, 0x0000E3, 0x0000E5, 0x0000E7, 0x0000E9, 0x0000E8, // 0x88 - 0x8F 0x0000EA, 0x0000EB, 0x0000ED, 0x0000EC, 0x0000EE, 0x0000EF, 0x0000F1, 0x0000F3, // 0x90 - 0x97 0x0000F2, 0x0000F4, 0x0000F6, 0x0000F5, 0x0000FA, 0x0000F9, 0x0000FB, 0x0000FC, // 0x98 - 0x9F 0x002020, 0x0000B0, 0x0000A2, 0x0000A3, 0x0000A7, 0x002022, 0x0000B6, 0x0000DF, // 0xA0 - 0xA7 0x0000AE, 0x0000A9, 0x002122, 0x0000B4, 0x0000A8, 0x002260, 0x0000C6, 0x0000D8, // 0xA8 - 0xAF 0x00221E, 0x0000B1, 0x002264, 0x002265, 0x0000A5, 0x0000B5, 0x002202, 0x002211, // 0xB0 - 0xB7 0x00220F, 0x0003C0, 0x00222B, 0x0000AA, 0x0000BA, 0x0003A9, 0x0000E6, 0x0000F8, // 0xB8 - 0xBF 0x0000BF, 0x0000A1, 0x0000AC, 0x00221A, 0x000192, 0x002248, 0x002206, 0x0000AB, // 0xC0 - 0xC7 0x0000BB, 0x002026, 0x0000A0, 0x0000C0, 0x0000C3, 0x0000D5, 0x000152, 0x000153, // 0xC8 - 0xCF 0x002013, 0x002014, 0x00201C, 0x00201D, 0x002018, 0x002019, 0x0000F7, 0x0025CA, // 0xD0 - 0xD7 0x0000FF, 0x000178, 0x002044, 0x0020AC, 0x002039, 0x00203A, 0x00FB01, 0x00FB02, // 0xD8 - 0xDF 0x002021, 0x0000B7, 0x00201A, 0x00201E, 0x002030, 0x0000C2, 0x0000CA, 0x0000C1, // 0xE0 - 0xE7 0x0000CB, 0x0000C8, 0x0000CD, 0x0000CE, 0x0000CF, 0x0000CC, 0x0000D3, 0x0000D4, // 0xE8 - 0xEF 0x00F8FF, 0x0000D2, 0x0000DA, 0x0000DB, 0x0000D9, 0x000131, 0x0002C6, 0x0002DC, // 0xF0 - 0xF7 0x0000AF, 0x0002D8, 0x0002D9, 0x0002DA, 0x0000B8, 0x0002DD, 0x0002DB, 0x0002C7 // 0xF8 - 0xFF }; ////// Subclasses of CharacterToUnicodeMapper are passed to string conversion /// and cmap parser routines to translate characters represented in various /// codepages in font files to standard Unicode encoding. /// abstract internal class CharacterToUnicodeMapper { ////// MapCharacterToUnicode - used by the cmap table parsers to determine /// which entry in the Avalon Unicode character map to update for /// a given cmap codepoint to glyph mapping. /// Returns -1 when no mapping exists /// public abstract int MapCharacterToUnicode(int character); ////// MapBytesToUtf16 - used by the name table parsers to map a /// name string to UTF-16. /// Returns null if any part of the string could not be mapped. /// public abstract string MapBytesToUtf16(byte[] bytes, int length); } ////// NullMapper - handles data that is already nominally in Unicode form. /// Byte arrays are expected to contain UTF-16 data in big endian /// format. /// internal class NullMapper : CharacterToUnicodeMapper { public NullMapper(){} public override int MapCharacterToUnicode(int character) { return character; } // Name tables that already using Unicode are handled directly in ParseUtf16Name. public override string MapBytesToUtf16(byte[] bytes, int length) { Invariant.Assert(false); return null; } } ////// MacRomanToUnicodeMapper - Handle Mac Roman encoded cmaps and names. /// MacRoman 0-127 is identical to Unicode 0-127. /// MacRoman 128-255 uses a mapping specified by Apple and published on /// the Unicode web site at: /// http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT /// /// Note that although NLS provides a mapping for Mac Roman using the magic /// encoding number 10000, that mapping is wrong for Apple codepoint 0xBD. /// internal class MacRomanToUnicodeMapper : CharacterToUnicodeMapper { public MacRomanToUnicodeMapper(){} public override int MapCharacterToUnicode(int character) { if (character < 0 || character >= 256) return -1; if (character < 128) { return character; } else { return MacRomanUpperToUnicode[character-128]; } } public override string MapBytesToUtf16(byte[] bytes, int length) { char[] result = new char[length]; for (int i=0; i/// WindowsAnsiToUnicodeMapper - Handle ansi Windows platform cmaps and names. /// All ansi Windows platform codepages are handled through the /// System.Text.Encoding NLS class. /// internal class WindowsAnsiToUnicodeMapper : CharacterToUnicodeMapper { private Encoding _encoding; private DecoderFallbackWithFailureFlag _decoderFallback; public WindowsAnsiToUnicodeMapper(int codepage) { try { _decoderFallback = new DecoderFallbackWithFailureFlag(); _encoding = Encoding.GetEncoding(codepage, EncoderFallback.ExceptionFallback, _decoderFallback); } catch (ArgumentException) { _encoding = null; } catch (NotSupportedException) { _encoding = null; } } public override int MapCharacterToUnicode(int character) { if (_encoding == null || character < 0 || character >= 65536) { return -1; } _decoderFallback.HasFailed = false; byte[] bytes; if (character < 256) { bytes = new byte[1]; bytes[0] = (byte)character; } else { bytes = new byte[2]; bytes[0] = (byte)(character >> 8); bytes[1] = (byte)(character & 0xff); } char[] chars = _encoding.GetChars(bytes); if (_decoderFallback.HasFailed) { // return missing characters as -1 (not a valid unicode codepoint) return -1; } else { if (chars.Length == 1) { return (int)chars[0]; } else { // Not in the basic multilingual plane. // For a good conversion this will be a surrogate pair, from which // we need to determine the UTF-32 codepoint. // For a bad conversion, instead of failing, NLS may just copy the 2 input // bytes over as 2 output chars. We treat this case as a failure. if ( (chars.Length != 2) || (chars[0] < 0xD800) || (chars[0] > 0xDBFF) || (chars[1] < 0xDC00) || (chars[1] > 0xDFFF)) { // Not a valid Unicode codepoint return -1; } else { return 0x10000 + ((((int)chars[0]) & 0x3FF) << 10) + (((int)chars[1]) & 0x3FF); } } } } public override string MapBytesToUtf16(byte[] bytes, int length) { if (_encoding == null) { return null; } _decoderFallback.HasFailed = false; string convertedString = _encoding.GetString(bytes, 0, length); if (_decoderFallback.HasFailed) { return null; } else { return convertedString; } } } private static CharacterToUnicodeMapper GetCharacterToUnicodeMapperForCharacterEncoding(CharacterEncoding characterEncoding) { switch (characterEncoding) { case CharacterEncoding.MsUcs2: case CharacterEncoding.MsUcs4: case CharacterEncoding.Unicode: case CharacterEncoding.MsSymbol: return new NullMapper(); case CharacterEncoding.MacRoman: return new MacRomanToUnicodeMapper(); case CharacterEncoding.MsShiftJis: return new WindowsAnsiToUnicodeMapper(932); case CharacterEncoding.MsPrc: return new WindowsAnsiToUnicodeMapper(936); case CharacterEncoding.MsBig5: return new WindowsAnsiToUnicodeMapper(950); case CharacterEncoding.MsWansung: return new WindowsAnsiToUnicodeMapper(949); } Invariant.Assert(false); return null; } private void LookForBestEncoding( CheckedPointer table, ushort encodingCount, out int glyphTableOffset, out CharacterEncoding characterEncoding) { PlatformID platform = PlatformID.Unicode; ushort encoding = 0; glyphTableOffset = 0; characterEncoding = CharacterEncoding.Unknown; const int offsetToPlatform = 4; const int offsetToEncoding = 6; const int offsetToOffset = 8; const int recordSize = 8; for (int i = 0; i < encodingCount; i++) { PlatformID currentPlatform = (PlatformID)ReadOpenTypeUShort(table + offsetToPlatform + i * recordSize); ushort currentEncoding = ReadOpenTypeUShort(table + offsetToEncoding + i * recordSize); int offset = ReadOpenTypeLong(table + offsetToOffset + i * recordSize); // Look for the best available table in the order of priority from the CharacterEncoding enum CharacterEncoding candidateTableType = PlatformIdAndEncodingIdToCharacterEncoding(currentPlatform, currentEncoding); if (candidateTableType > characterEncoding) { characterEncoding = candidateTableType; glyphTableOffset = offset; platform = currentPlatform; encoding = currentEncoding; } } } #endregion Character mapping and language encoding handling #region OS/2 and head decoding private void ReadStyles( CheckedPointer os2Table, CheckedPointer headTable, out FontStyle fontStyle, out FontWeight fontWeight, out FontStretch fontStretch, out bool skipFontDifferentiation ) { fontStyle = FontStyles.Normal; fontWeight = FontWeights.Normal; fontStretch = FontStretches.Normal; skipFontDifferentiation = false; if (!os2Table.IsNull) { Os2SelectionFlags os2SelectionFlags = (Os2SelectionFlags)ReadOpenTypeUShort(os2Table + OFF_OS2_usSelection); if ((os2SelectionFlags & Os2SelectionFlags.Oblique) != 0) { fontStyle = FontStyles.Oblique; } else if ((os2SelectionFlags & Os2SelectionFlags.Italic) != 0) { fontStyle = FontStyles.Italic; } if ((os2SelectionFlags & Os2SelectionFlags.WeightWidthSlopeOnly) != 0) { skipFontDifferentiation = true; } int usWeightClass = ReadOpenTypeUShort(os2Table + OFF_OS2_usWeightClass); if (1 <= usWeightClass && usWeightClass <= 9) { // Numerous existing fonts have the weight wrong, a value between 1 and 9 instead of between 100 and 900. usWeightClass *= 100; } fontWeight = FontWeight.FromOpenTypeWeight(usWeightClass); ushort usWidthClass = ReadOpenTypeUShort(os2Table + OFF_OS2_usWidthClass); fontStretch = FontStretch.FromOpenTypeStretch(usWidthClass); } else { MacStyleFlags macStyleFlags = (MacStyleFlags)ReadOpenTypeUShort(headTable + OFF_head_macStyle); if ((macStyleFlags & MacStyleFlags.Italic) != 0) { fontStyle = FontStyles.Italic; } if ((macStyleFlags & MacStyleFlags.Bold) != 0) { fontWeight = FontWeights.Bold; } if ((macStyleFlags & MacStyleFlags.Condensed) != 0) { fontStretch = FontStretches.Condensed; } if ((macStyleFlags & MacStyleFlags.Extended) != 0) { fontStretch = FontStretches.Expanded; } } } private static ushort IntToUshort(int n) { if (n >= 0 && n <= ushort.MaxValue) return (ushort)n; else if (n < 0) return 0; else return ushort.MaxValue; } private void ReadBasicMetrics( CheckedPointer headTable, CheckedPointer os2Table, CheckedPointer hheaTable, out ushort designEmHeight, out ushort designCellAscent, out ushort designCellDescent, out int designLineSpacing ) { designEmHeight = ReadOpenTypeUShort(headTable + OFF_head_unitsPerEm); if (designEmHeight == 0) { throw new FileFormatException(SourceUri); } if (!os2Table.IsNull && ((Os2SelectionFlags)ReadOpenTypeUShort(os2Table + OFF_OS2_usSelection) & Os2SelectionFlags.DontUseWinLineMetrics) != 0) { // The font specifies that the sTypoAscender, sTypoDescender, and sTypoLineGap fields are valid and // should be used instead of winAscent and winDescent. int typoAscender = ReadOpenTypeShort(os2Table + OFF_OS2_sTypoAscender); int typoDescender = ReadOpenTypeShort(os2Table + OFF_OS2_sTypoDescender); int typoLineGap = ReadOpenTypeShort(os2Table + OFF_OS2_sTypoLineGap); // We include the line gap in the ascent so that white space is distributed above the line. (Note that // the typo line gap is a different concept than "external leading".) designCellAscent = IntToUshort(typoAscender + typoLineGap); // Typo descent is a signed value where the positive direction is up. It is therefore typically negative. // A signed typo descent would be quite unusual as it would indicate the descender was above the baseline. designCellDescent = IntToUshort(-typoDescender); designLineSpacing = typoAscender + typoLineGap - typoDescender; } else { // get the ascender field int ascender = ReadOpenTypeUShort(hheaTable + OFF_hhea_ascender); // get the descender field; this is measured in the same direction as ascender and is therefore // normally negative whereas we want a positive value; however some fonts get the sign wrong // so instead of just negating we take the absolute value. int descender = Math.Abs((int)ReadOpenTypeShort(hheaTable + OFF_hhea_descender)); // get the lineGap field and make sure it's >= 0 int lineGap = Math.Max(0, (int)ReadOpenTypeShort(hheaTable + OFF_hhea_lineGap)); if (!os2Table.IsNull) { // we could use sTypoAscender, sTypoDescender, and sTypoLineGap which are supposed to represent // optimal typographic values not constrained by backwards compatibility; however, many fonts get // these fields wrong or get them right only for Latin text; therefore we use the more reliable // platform-specific Windows values. We take the absolute value of the win32descent in case some // fonts get the sign wrong. int winAscent = ReadOpenTypeUShort(os2Table + OFF_OS2_usWinAscent); int winDescent = Math.Abs((int)ReadOpenTypeShort(os2Table + OFF_OS2_usWinDescent)); designCellAscent = (ushort)winAscent; designCellDescent = (ushort)winDescent; // The following calculation for designLineSpacing is per DBrown. The default line spacing // should be the sum of the Mac ascender, descender, and lineGap unless the resulting value would // be less than the cell height (winAscent + winDescent) in which case we use the cell height. // See also http://www.microsoft.com/typography/otspec/recom.htm. // // Note that in theory it's valid for the baseline-to-baseline distance to be less than the cell // height. However, Windows has never allowed this for Truetype fonts, and fonts built for Windows // sometimes rely on this behavior and get the hha values wrong or set them all to zero. // designLineSpacing = Math.Max( lineGap + ascender + descender, winAscent + winDescent ); } else { designCellAscent = (ushort)ascender; designCellDescent = (ushort)descender; designLineSpacing = ascender + descender + lineGap; } } } /// /// Decode gasp table and decide whether the font is a legacy East Asian font. /// This is need to determine the best rendering method. /// ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadRenderingHints(CheckedPointer headTable, FontFaceLayoutInfo cache) { CheckedPointer gaspTable = GetTable(TrueTypeTags.GridfitAndScanProc); if (!gaspTable.IsNull) { // skip table version gaspTable += 2; // read number of GASP ranges ushort numRanges = ReadOpenTypeUShort(gaspTable); gaspTable += 2; FontFaceLayoutInfo.GaspRange [] gaspRanges = new FontFaceLayoutInfo.GaspRange[numRanges]; for (int i = 0; i < numRanges; ++i) { gaspRanges[i].ppem = ReadOpenTypeUShort(gaspTable); gaspTable += 2; gaspRanges[i].flags = (FontFaceLayoutInfo.GaspFlags)ReadOpenTypeUShort(gaspTable); gaspTable += 2; } cache.GaspRanges = gaspRanges; } ListlanguagesFound = new List (4); // inspect cmap to determine whether the font is East Asian or not for (int i = 0; i < EastAsianLanguages.RepresentativeCodepoints.GetLength(0); ++i) { bool allCodePointsPresent = true; for (int j = 0; j < EastAsianLanguages.RepresentativeCodepoints.GetLength(1); ++j) { if (!cache.CharacterMap.ContainsKey(EastAsianLanguages.RepresentativeCodepoints[i, j])) { allCodePointsPresent = false; break; } } if (allCodePointsPresent) languagesFound.Add(new EastAsianLanguageCandidate(i)); } if (languagesFound.Count == 0) { // this is not an East Asian font cache.FontRenderingHints = FontFaceLayoutInfo.RenderingHints.Regular; return; } Debug.Assert(languagesFound.Count <= EastAsianLanguages.RepresentativeCodepoints.GetLength(0)); ushort headFlags = ReadOpenTypeUShort(headTable + OFF_head_flags); if ((headFlags & (ushort)HeadFlags.OptimizedForClearType) != 0) { // this is a ClearType hinted East Asian font cache.FontRenderingHints = FontFaceLayoutInfo.RenderingHints.Regular; return; } CheckedPointer eblcTable = GetTable(TrueTypeTags.BitmapLocation); if (eblcTable.IsNull) { // this is a regular font cache.FontRenderingHints = FontFaceLayoutInfo.RenderingHints.Regular; return; } // extract all glyph ranges that have embedded bitmaps int numBitmapSizeTables = (int)ReadOpenTypeULong(eblcTable + OFF_eblc_numSizes); CheckedPointer bitmapSizeTable = eblcTable + OFF_eblc_bitmapSizeTables; for (int i = 0; i < numBitmapSizeTables; ++i, bitmapSizeTable += EblcSizeOfBitmapSizeTable) { int numberOfIndexSubTables = (int)ReadOpenTypeULong(bitmapSizeTable + EblcNumberOfIndexSubTables); int indexSubTableArrayOffset = (int)ReadOpenTypeULong(bitmapSizeTable + EblcIndexSubTableArrayOffset); CheckedPointer subTable = eblcTable + indexSubTableArrayOffset; for (int j = 0; j < numberOfIndexSubTables; ++j, subTable += EblcSizeOfIndexSubTable) { ushort firstGlyphIndex = ReadOpenTypeUShort(subTable + EblcFirstGlyph); ushort lastGlyphIndex = ReadOpenTypeUShort(subTable + EblcLastGlyph); // now we know that glyph range [firstGlyphIndex, lastGlyphIndex] has embedded bitmaps in it foreach (EastAsianLanguageCandidate lang in languagesFound) { for (int k = 0; k < EastAsianLanguages.RepresentativeCodepoints.GetLength(1); ++k) { char c = EastAsianLanguages.RepresentativeCodepoints[lang.LanguageIndex, k]; // PERF: we could perform cmap lookup in advance in case doing it on every // iteration is determined to be too slow. ushort g; // finally, check if the glyph is in the embedded bitmap range if (cache.CharacterMap.TryGetValue(c, out g) && firstGlyphIndex <= g && g <= lastGlyphIndex) { lang.AddCodepoint(k); } } } } } // now, if the font has enough embedded bitmaps to represent a language // we treat it as a legacy East Asian font. Otherwise, it's a Regular font. foreach (EastAsianLanguageCandidate lang in languagesFound) { Debug.Assert(EastAsianLanguages.RepresentativeCodepoints.GetLength(1) == 6); // If all of lower 6 bits are set, this means that all of the 6 codepoints // have embedded bitmaps. We treat such fonts as legacy East Asian fonts. if (lang.CodePoints == 63) { cache.FontRenderingHints = FontFaceLayoutInfo.RenderingHints.LegacyEastAsian; return; } } cache.FontRenderingHints = FontFaceLayoutInfo.RenderingHints.Regular; return; } /// /// Computes the contrast adjustment value for this font. /// The value is used to fine tune the text contrast value used by glyph rendering code. /// ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ComputeFontContrastAdjustment(ref ParsedNameTable nameTable, FontFaceLayoutInfo cache) { if (_technology == FontTechnology.PostscriptOpenType) cache.FontContrastAdjustment = -1; else { // For "Courier New" and similar fonts, bump up the contrast value by 2. // This is similar to what GDI and GDI+ ClearType code does to achieve more readable glyphs for these thin fonts. // One difference is that we do this for the whole font family, not just for regular faces. // This achieves more consistent look across different weights. short fontContrastAdjustment = 0; LocalizedName[] familyNames = nameTable.familyNames; if (familyNames == null) familyNames = nameTable.win32FamilyNames; if (familyNames != null) { foreach (LocalizedName name in familyNames) { if (Array.BinarySearch(_thinFontFamilyNames, name.Name, StringComparer.OrdinalIgnoreCase) >= 0) fontContrastAdjustment = 1; } } cache.FontContrastAdjustment = fontContrastAdjustment; } } /// /// Analyzes os/2 fsType value and construct FontEmbeddingRight enum value from it. /// ////// Critical: This code writes critical information into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] FontEmbeddingRight ReadFontEmbeddingRights(CheckedPointer os2Table) { // If there is no os/2 table, default to restricted font. // This is the precedence that has been set by T2Embed, Word, etc. // No one has complained about this because these fonts are generally lower in quality and are less likely to be embedded. FontEmbeddingRight rights = FontEmbeddingRight.RestrictedLicense; if (!os2Table.IsNull) { ushort fsType = ReadOpenTypeUShort(os2Table + OFF_OS2_fsType); // Start with the most restrictive flags. // In case a font uses conflicting flags, // expose the least restrictive combination in order to be compatible with existing applications. if ((fsType & Os2EmbeddingFlags.InstallableMask) == 0) { // The font is installable if all bits in the InstallableMask are set to zero. switch (fsType & (Os2EmbeddingFlags.NoSubsetting | Os2EmbeddingFlags.BitmapOnly)) { case 0: rights = FontEmbeddingRight.Installable; break; case Os2EmbeddingFlags.NoSubsetting: rights = FontEmbeddingRight.InstallableButNoSubsetting; break; case Os2EmbeddingFlags.BitmapOnly: rights = FontEmbeddingRight.InstallableButWithBitmapsOnly; break; case Os2EmbeddingFlags.NoSubsetting | Os2EmbeddingFlags.BitmapOnly: rights = FontEmbeddingRight.InstallableButNoSubsettingAndWithBitmapsOnly; break; } } else if ((fsType & Os2EmbeddingFlags.Editable) != 0) { switch (fsType & (Os2EmbeddingFlags.NoSubsetting | Os2EmbeddingFlags.BitmapOnly)) { case 0: rights = FontEmbeddingRight.Editable; break; case Os2EmbeddingFlags.NoSubsetting: rights = FontEmbeddingRight.EditableButNoSubsetting; break; case Os2EmbeddingFlags.BitmapOnly: rights = FontEmbeddingRight.EditableButWithBitmapsOnly; break; case Os2EmbeddingFlags.NoSubsetting | Os2EmbeddingFlags.BitmapOnly: rights = FontEmbeddingRight.EditableButNoSubsettingAndWithBitmapsOnly; break; } } else if ((fsType & Os2EmbeddingFlags.PreviewAndPrint) != 0) { switch (fsType & (Os2EmbeddingFlags.NoSubsetting | Os2EmbeddingFlags.BitmapOnly)) { case 0: rights = FontEmbeddingRight.PreviewAndPrint; break; case Os2EmbeddingFlags.NoSubsetting: rights = FontEmbeddingRight.PreviewAndPrintButNoSubsetting; break; case Os2EmbeddingFlags.BitmapOnly: rights = FontEmbeddingRight.PreviewAndPrintButWithBitmapsOnly; break; case Os2EmbeddingFlags.NoSubsetting | Os2EmbeddingFlags.BitmapOnly: rights = FontEmbeddingRight.PreviewAndPrintButNoSubsettingAndWithBitmapsOnly; break; } } else { // Otherwise, the font either has Os2EmbeddingFlags.RestrictedLicense set, or // it has a reserved bit 0 set, which is invalid per specification. // Either way, rights should remain FontEmbeddingRight.RestrictedLicense. } } return rights; } #endregion OS/2 and head decoding #region Name table decoding private class NameCollection { internal class LocalizedNameCandidate { // key internal CultureInfo culture; // culture info for the name internal CharacterEncoding characterEncoding; // the glyph encoding of the candidate // data internal int offset; // offset to the name in the file internal int length; // length of the name in the file } private static string ParseUtf16Name(LocalizedNameCandidate name, CheckedPointer nameTable) { int stringLength = name.length / 2; StringBuilder sb = new StringBuilder(stringLength); int offsetToString = ReadOpenTypeUShort(nameTable + 4) + name.offset; for (int i = 0; i < stringLength; i ++) { ushort c = ReadOpenTypeUShort(nameTable + (offsetToString + i * 2)); if (c == 0) break; sb.Append((char)c); } return sb.ToString(); } ////// Critical: This code calls into probe and also has an unsafe code block /// TreatAsSafe: Converts a string to ANSI and checks for NUll value of checked pointer /// [SecurityCritical,SecurityTreatAsSafe] private static string Parse8BitCodepageName(LocalizedNameCandidate name, CheckedPointer nameTable) { CharacterToUnicodeMapper mapper = GetCharacterToUnicodeMapperForCharacterEncoding(name.characterEncoding); int offsetToString = ReadOpenTypeUShort(nameTable + 4) + name.offset; // for DBCS legacy encoding, the name may be encoded in a format where every character takes two bytes // and single byte character have zero for the high byte, we need to skip all those null bytes before // calling the Ansi to Unicode conversion byte[] bytes = new byte[name.length]; int ansiLength = 0; unsafe { byte * nameTablePointer = (byte *)nameTable.Probe(offsetToString, name.length); for (int originalIndex = 0; originalIndex < name.length; originalIndex++) { if (nameTablePointer[originalIndex] != 0) { bytes[ansiLength] = nameTablePointer[originalIndex]; ansiLength++; } } } return mapper.MapBytesToUtf16(bytes, ansiLength); } internal void AddNameEntry( PlatformID platformID, ushort encodingID, ushort languageID, int tableIndex, CheckedPointer nameTable ) { LocalizedNameCandidate newName = new LocalizedNameCandidate(); newName.culture = MapTrueTypeLangIdToCulture(platformID, languageID); if (newName.culture == null) // we don't recognize this LCID { return; } newName.characterEncoding = PlatformIdAndEncodingIdToCharacterEncoding(platformID, encodingID); if (newName.characterEncoding == CharacterEncoding.Unknown) { return; } int index = _names.BinarySearch(newName, _candidateComparer); if (index < 0) { // no previous entry with this lcid was found, add a new one in sorted order _names.Insert(~index, newName); } else { LocalizedNameCandidate oldName = _names[index]; // see if we have a better glyph table type match if (newName.characterEncoding <= oldName.characterEncoding) return; // replace the old name with a better one _names[index] = newName; } newName.length = ReadOpenTypeUShort(nameTable + (14 + tableIndex * 12)); newName.offset = ReadOpenTypeUShort(nameTable + (16 + tableIndex * 12)); } ////// ConvertString converts a string from its format in a font name table to UTF-16. /// Returns null if the glyph table type is invalid. /// private string ParseNameString(LocalizedNameCandidate name, CheckedPointer nameTable) { switch (name.characterEncoding) { // The following encodongs all imply that names are stored in UTF16 case CharacterEncoding.MsUcs4: case CharacterEncoding.MsSymbol: case CharacterEncoding.MsUcs2: case CharacterEncoding.Unicode: case CharacterEncoding.MsShiftJis: // ShiftJis appears in this list because GDI is decoding the name table of shiftjis fonts // as Unicode (even though it decodes the name table for Big5, Wansung, GB fonts as DBCS Ansi). // Since GDI is doing like this and legacy fonts are build like this, I need to continue the legacy return ParseUtf16Name(name, nameTable); case CharacterEncoding.MacRoman: case CharacterEncoding.MsPrc: case CharacterEncoding.MsBig5: case CharacterEncoding.MsWansung: // Single or double byte 8-bit based character set return Parse8BitCodepageName(name, nameTable); default: return null; // Unknown encoding } } internal string ConvertVersion(CheckedPointer nameTable) { LocalizedNameCandidate nameCandidate = null; foreach (LocalizedNameCandidate name in _names) { nameCandidate = name; if (name.culture.Equals(EnglishUSCulture)) break; } if (nameCandidate == null) return null; return ParseNameString(nameCandidate, nameTable); } internal LocalizedName [] ConvertAllNames(CheckedPointer nameTable) { if (_names.Count == 0) return null; // Count how many strings are in MS supported formats. int supportedNameCount=0; for (int i=0; i<_names.Count; i++) { if (ParseNameString(_names[i], nameTable) != null) { supportedNameCount++; } } if (supportedNameCount == 0) return null; // Allocate and fill in the localizedNames array. LocalizedName [] localizedNames = new LocalizedName[supportedNameCount]; int j=0; for (int i=0; i<_names.Count; i++) { string convertedString = ParseNameString(_names[i], nameTable); if (convertedString != null) { // Conversion succeeded: add converted name to publicly accessible names. // Make sure to pass the original culture LCID, as we rely on the LocalizedNameCandidate being sorted by it // later on in the FontFaceLayoutInfo code when we perform binary search. Please see ConvertNames and FindLCID methods. localizedNames[j++] = new LocalizedName(XmlLanguage.GetLanguage(_names[i].culture.IetfLanguageTag), convertedString, _names[i].culture.LCID); } } return localizedNames; } private class CandidateComparer : IComparer{ #region IComparer Members int IComparer .Compare(LocalizedNameCandidate x, LocalizedNameCandidate y) { // The sort function below is used only to detect duplicate CultureInfo objects. int xlcid = x.culture.LCID; int ylcid = y.culture.LCID; return xlcid - ylcid; } #endregion } // the list contains LocalizedNameCandidate entries sorted by LCID private List _names = new List (2); private static CandidateComparer _candidateComparer = new CandidateComparer(); } // this function is written in the way compatible with the legacy code // because version number in the name table can be written in many ways // please don't try to "optimize" or "fix" it without talking // to one of TrueType experts private double VersionToDouble(string versionString) { if (versionString == null) return 0.0; double version = 0.0; string subString; int i = 0; int start = versionString.Length; while (i < versionString.Length) { if (Char.IsDigit(versionString, i)) { start = i; break; } i++; } if (start < versionString.Length) { i++; while ((i < versionString.Length) && Char.IsDigit(versionString, i)) { i++; } if ((i < versionString.Length) && (versionString[i] == '.')) { i++; while ((i < versionString.Length) && Char.IsDigit(versionString, i)) { i++; } } subString = versionString.Substring(start, i - start); // the version string is always formatted using English number format if (!double.TryParse(subString, NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out version)) version = 0.0; } return version; } private void DecodeNameTable(ref ParsedNameTable parsedNameTable) { // extracting information from the name table CheckedPointer nameTable = GetTable(TrueTypeTags.NamingTable); if (nameTable.IsNull) { throw new FileFormatException(SourceUri); } ushort numberOfNameRecords = ReadOpenTypeUShort(nameTable + 2); if (numberOfNameRecords == 0) { throw new FileFormatException(SourceUri); } NameCollection fontFamilyName = new NameCollection(); NameCollection fontSubfamilyName = new NameCollection(); NameCollection win32FamilyName = new NameCollection(); NameCollection win32SubfamilyName = new NameCollection(); NameCollection versionStrings = new NameCollection(); NameCollection copyrights = new NameCollection(); NameCollection manufacturerNames = new NameCollection(); NameCollection trademarks = new NameCollection(); NameCollection designerNames = new NameCollection(); NameCollection descriptions = new NameCollection(); NameCollection vendorUrls = new NameCollection(); NameCollection designerUrls = new NameCollection(); NameCollection licenseDescriptions = new NameCollection(); NameCollection sampleTexts = new NameCollection(); for (int i = 0; i < numberOfNameRecords; i++) { PlatformID platformID = (PlatformID)ReadOpenTypeUShort(nameTable + (6 + i * 12)); ushort specificID = ReadOpenTypeUShort(nameTable + (8 + i * 12)); ushort languageID = ReadOpenTypeUShort(nameTable + (10 + i * 12)); ushort nameID = ReadOpenTypeUShort(nameTable + (12 + i * 12)); switch ((NameTableNameID)nameID) { case NameTableNameID.FontFamilyName: win32FamilyName.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.FontSubfamilyName: win32SubfamilyName.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.PreferredFamily: fontFamilyName.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.PreferredSubfamily: fontSubfamilyName.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.Version: versionStrings.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.CopyrightNotice: copyrights.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.Trademark: trademarks.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.ManufacturerName: manufacturerNames.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.Designer: designerNames.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.Description: descriptions.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.URLVendor: vendorUrls.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.URLDesigner: designerUrls.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.LicenseDescription: licenseDescriptions.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; case NameTableNameID.SampleText: sampleTexts.AddNameEntry(platformID, specificID, languageID, i, nameTable); break; } } parsedNameTable.familyNames = fontFamilyName.ConvertAllNames(nameTable); parsedNameTable.win32FamilyNames = win32FamilyName.ConvertAllNames(nameTable); parsedNameTable.faceNames = fontSubfamilyName.ConvertAllNames(nameTable); parsedNameTable.win32faceNames = win32SubfamilyName.ConvertAllNames(nameTable); parsedNameTable.versionStrings = versionStrings.ConvertAllNames(nameTable); parsedNameTable.copyrights = copyrights.ConvertAllNames(nameTable); parsedNameTable.manufacturerNames = manufacturerNames.ConvertAllNames(nameTable); parsedNameTable.trademarks = trademarks.ConvertAllNames(nameTable); parsedNameTable.designerNames = designerNames.ConvertAllNames(nameTable); parsedNameTable.descriptions = descriptions.ConvertAllNames(nameTable); parsedNameTable.vendorUrls = vendorUrls.ConvertAllNames(nameTable); parsedNameTable.designerUrls = designerUrls.ConvertAllNames(nameTable); parsedNameTable.licenseDescriptions = licenseDescriptions.ConvertAllNames(nameTable); parsedNameTable.sampleTexts = sampleTexts.ConvertAllNames(nameTable); parsedNameTable.version = VersionToDouble(versionStrings.ConvertVersion(nameTable)); } #endregion Name table decoding #region Cmap table decoding // CMAP format parsers /// /// Format 0 - a simple array of 256 bytes /// ////// Critical: This code writes into cmap. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadCmapFormat0( CheckedPointer cmapTable, int glyphTableOffset, CharacterToUnicodeMapper mapper, FontFaceLayoutInfo.IntMap characterMap ) { int bytesOffset = glyphTableOffset + 6; for (int ch=0; ch<255; ch++) { int glyph = ReadOpenTypeByte(cmapTable + bytesOffset + ch); int unicode = mapper.MapCharacterToUnicode(ch); if (unicode != -1) // Skip codepoints not representable in Unicode { characterMap.SetCharacterEntry(unicode, unchecked((ushort)glyph)); } } } ////// Critical: This code writes into cmap. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadCmapFormat2( CheckedPointer cmapTable, int glyphTableOffset, CharacterToUnicodeMapper mapper, FontFaceLayoutInfo.IntMap characterMap ) { int subHeaderOffset = glyphTableOffset + 6 + (256 * 2); for (int ii = 0; ii < 256; ii++) { int jj = ReadOpenTypeUShort(cmapTable + (glyphTableOffset + 6 + ii * 2)); // SubHeaderKeys int firstCode = ReadOpenTypeUShort(cmapTable + (subHeaderOffset + jj)); int entryCount = ReadOpenTypeUShort(cmapTable + (subHeaderOffset + 2 + jj)); short idDelta = ReadOpenTypeShort (cmapTable + (subHeaderOffset + 4 + jj)); int idRangeOffset = ReadOpenTypeUShort(cmapTable + (subHeaderOffset + 6 + jj)); int glyphArrayOffset = subHeaderOffset + jj + 6 + idRangeOffset; if (jj == 0) { int unicode = mapper.MapCharacterToUnicode(ii); // Special case: single bte codepoint not valid for this codepage. // Fonts sometimes provide glyphs in these areas, for example the // control code areas in CP950 are reserved, but fonts may provide // a representation. if (unicode == -1) { // No mapping required. unicode = ii; } ushort hGlyph = ReadOpenTypeUShort(cmapTable + (glyphArrayOffset + (ii - firstCode) * 2)); if (hGlyph != 0) { characterMap.SetCharacterEntry(unicode, hGlyph); } } else { for( jj = firstCode ; jj < firstCode + entryCount ; jj++ ) { int unicode = mapper.MapCharacterToUnicode((ii<<8)+jj); if (unicode != -1) // Skip CMAP entries that don't map to Unicode { int hGlyph = ReadOpenTypeUShort(cmapTable + (glyphArrayOffset + (jj - firstCode) * 2)); if( hGlyph != 0 ) { characterMap.SetCharacterEntry(unicode, unchecked((ushort)(hGlyph + idDelta))); } } } } } } //// MapCmapFormat4Glyph - Interpret Truetype CMAP type 4 range details // // Implements format 4 of the TrueType cmap table - 'Segment // mapping to delta values' described in chapter 2 of the 'TrueType // 1.0 Font Files Rev. 1.66' document. private ushort MapCmapFormat4Glyph( CheckedPointer cmapTable, int wc, // Character int offsetToCurrentIdRange, int idRangeOffset, int startCount, short idDelta ) { int g; int offsetToGlyph; if (wc >= 0xffff) { // Don't map U+0FFFF as some fonts (Pristina) don't map it // correctly and cause an AV in a subsequent lookup. return 0; } if (idRangeOffset != 0) { offsetToGlyph = idRangeOffset + (wc - startCount) * 2 + offsetToCurrentIdRange; g = ReadOpenTypeUShort(cmapTable + offsetToGlyph); if (g != 0) { g += idDelta; } } else { g = wc + idDelta; } return unchecked((ushort)g); } ////// Critical: This code writes into cmap. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadCmapFormat4( CheckedPointer cmapTable, int glyphTableOffset, CharacterToUnicodeMapper mapper, FontFaceLayoutInfo.IntMap characterMapInfo ) { ushort segCount = (ushort)(ReadOpenTypeUShort(cmapTable + (glyphTableOffset + 6)) / 2); int endCountOffset = glyphTableOffset + 14; int startCountOffset = endCountOffset + (segCount + 1) * 2; int idDeltaOffset = startCountOffset + (segCount * 2); int idRangeTableOffset = idDeltaOffset + (segCount * 2); // Loop through the segments mapping glyphs int i; int endCount = ReadOpenTypeUShort(cmapTable + endCountOffset); for (i=0; i0 && startCount < endCount) { startCount = endCount + 1; } endCount = ReadOpenTypeUShort(cmapTable + (endCountOffset + i * 2)); // Some font tools generate an final invalid mapping from codepoint FFFF. // Since FFFF is invalid in all codepages, we ignore it. if (endCount == 0xffff) { endCount--; } for (int characterCode = startCount; characterCode <= endCount; characterCode++) { int unicode = mapper.MapCharacterToUnicode(characterCode); if (unicode != -1) // Skip codepoints not representable in Unicode { characterMapInfo.SetCharacterEntry( unicode, MapCmapFormat4Glyph(cmapTable, characterCode, idRangeTableOffset + (i*2), idRangeOffset, startCount, idDelta)); } } } } /// /// Format 6: Trimmed table mapping /// /// Type Name Description /// USHORT format Format number is set to 6. /// USHORT length This is the length in bytes of the subtable. /// USHORT language Please see "Note on the language field in 'cmap' subtables" in this document. /// USHORT firstCode First character code of subrange. /// USHORT entryCount Number of character codes in subrange. /// USHORT glyphIdArray [entryCount] Array of glyph index values for character codes in the range. /// /// /// The firstCode and entryCount values specify a subrange (beginning at /// firstCode,length = entryCount) within the range of possible character /// codes. Codes outside of this subrange are mapped to glyph index 0. The /// offset of the code (from the first code) within this subrange is used as /// index to the glyphIdArray, which provides the glyph index value. /// /// ////// Critical: This code writes into cmap. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadCmapFormat6( CheckedPointer cmapTable, int glyphTableOffset, CharacterToUnicodeMapper mapper, FontFaceLayoutInfo.IntMap characterMap ) { int firstCode = ReadOpenTypeUShort(cmapTable + glyphTableOffset + 6); int entryCount = ReadOpenTypeUShort(cmapTable + glyphTableOffset + 8); for (int i = 0; i < entryCount; i++) { int unicode = mapper.MapCharacterToUnicode(firstCode + i); if (unicode != -1) // Skip codepoints not representable in Unicode { ushort glyph = ReadOpenTypeUShort(cmapTable + glyphTableOffset + 10 + (firstCode+i)*2); if (glyph != 0) { characterMap.SetCharacterEntry(unicode, glyph); } } } } ////// Critical: This code writes into cmap. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadCmapFormat12( CheckedPointer cmapTable, int glyphTableOffset, FontFaceLayoutInfo.IntMap characterMapInfo ) { int groupCount = ReadOpenTypeLong(cmapTable + (glyphTableOffset + 12)); // Iterate through groups filling in cmap table for (int i=0; i < groupCount; i++) { int startCharCode = ReadOpenTypeLong(cmapTable + (glyphTableOffset + 16 + i * 12)); int endCharCode = ReadOpenTypeLong(cmapTable + (glyphTableOffset + 20 + i * 12)); int startGlyphID = ReadOpenTypeLong(cmapTable + (glyphTableOffset + 24 + i * 12)); for (int unicode = startCharCode; unicode <= endCharCode; unicode++) { characterMapInfo.SetCharacterEntry(unicode, unchecked((ushort)(startGlyphID + unicode - startCharCode))); } } } ////// CrossPopulateSymbolFontCodepoints - duplicate codepoints between ranges /// 0-ff and f000-f0ff. /// /// Any codepoints present in one but not both ranges are copied /// into the other range. /// /// The OpenType standard recommends but does not require that symbol fonts /// are encoded from f000 to f0ff. GDI duplicates these codepoints into the /// range 0-ff since most apps (and therefore most textual data) expect to /// use 0-ff rather than f000-f0ff. /// /// However, since most apps and OS APIs process carriage return (U+000d), /// line feed (U+000a) and maybe tab (U+0009) specially, any symbol at these /// positions is difficult to display, so apps needing access to such symbols /// generally use the f000-f0ff range when displaying symbols. For this /// reason, to support symbol fonts that present their symbols in the /// range 0-ff, it is also necessary to copy codepoints 0-ff up to f000-f0ff. /// /// This code never overwrites a codepont already defined by the font cmap table, /// only codepoints that are undefined (i.e. with glyph index zero). /// ////// Critical: This code writes into cmap. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void CrossPopulateSymbolFontCodepoints( FontFaceLayoutInfo.IntMap characterMap ) { for (int i=0; i<256; i++) { ushort lowGlyphIndex; bool lowExists = characterMap.TryGetValue(i, out lowGlyphIndex); ushort highGlyphIndex; bool highExists = characterMap.TryGetValue(i + 0xf000, out highGlyphIndex); if (highExists && !lowExists) { characterMap.SetCharacterEntry(i, highGlyphIndex); } else if (lowExists && !highExists) { characterMap.SetCharacterEntry(i + 0xf000, lowGlyphIndex); } } } ////// Decodes the cmap table /// /// Font cache structure to fill in ///Whether the font has Symbol encoding ////// Critical: This code calls into SetGlypCount which is used as an index into /// an unmanaged structure /// TreatAsSafe: It retrieves this from cache.GlyphCount which is tracked /// [SecurityCritical,SecurityTreatAsSafe] private bool DecodeCmapTable(FontFaceLayoutInfo cache) { CheckedPointer cmapTable = GetTable(TrueTypeTags.CharToIndexMap); if (cmapTable.IsNull) { throw new FileFormatException(SourceUri); } ushort encodingCount = ReadOpenTypeUShort(cmapTable + 2); int glyphTableOffset; CharacterEncoding characterEncoding; LookForBestEncoding( cmapTable, encodingCount, out glyphTableOffset, out characterEncoding ); // when we're interested in only basic font face info, cache is null if (cache == null) { return characterEncoding == CharacterEncoding.MsSymbol; } FontFaceLayoutInfo.IntMap characterMapInfo = cache.CharacterMap; characterMapInfo.SetGlyphCount(cache.GlyphCount); ushort format = ReadOpenTypeUShort(cmapTable + glyphTableOffset); switch(characterEncoding) { case CharacterEncoding.Unknown: throw new FileFormatException(SourceUri); case CharacterEncoding.MsSymbol: case CharacterEncoding.MsUcs2: case CharacterEncoding.MsUcs4: case CharacterEncoding.Unicode: switch(format) { case 4: ReadCmapFormat4(cmapTable, glyphTableOffset, GetCharacterToUnicodeMapperForCharacterEncoding(characterEncoding), characterMapInfo); break; case 12: ReadCmapFormat12(cmapTable, glyphTableOffset, characterMapInfo); break; default: throw new FileFormatException(SourceUri); } break; case CharacterEncoding.MsShiftJis: case CharacterEncoding.MsPrc: case CharacterEncoding.MsBig5: case CharacterEncoding.MsWansung: switch(format) { case 2: ReadCmapFormat2(cmapTable, glyphTableOffset, GetCharacterToUnicodeMapperForCharacterEncoding(characterEncoding), characterMapInfo); break; case 4: ReadCmapFormat4(cmapTable, glyphTableOffset, GetCharacterToUnicodeMapperForCharacterEncoding(characterEncoding), characterMapInfo); break; default: throw new FileFormatException(SourceUri); } break; case CharacterEncoding.MacRoman: switch(format) { case 0: ReadCmapFormat0(cmapTable, glyphTableOffset, new MacRomanToUnicodeMapper(), characterMapInfo); break; case 6: ReadCmapFormat6(cmapTable, glyphTableOffset, new MacRomanToUnicodeMapper(), characterMapInfo); break; default: throw new FileFormatException(SourceUri); } break; default: throw new FileFormatException(SourceUri); } // Symbol fonts have codepoints cross-populated between 0-ff and f000-f0ff. if (characterEncoding == CharacterEncoding.MsSymbol) { CrossPopulateSymbolFontCodepoints(characterMapInfo); } return characterEncoding == CharacterEncoding.MsSymbol; } #endregion #region Horizontal and vertical metrics private bool ValidatexHeight(ushort designEmHeight, ref short xHeight) { if (xHeight <= designEmHeight * 10 / 100 || xHeight >= designEmHeight * 90 / 100) { xHeight = 0; return false; } return true; } private bool ValidateCapsHeight(ushort designEmHeight, ref short capsHeight) { if (capsHeight <= designEmHeight * 10 / 100 || capsHeight > designEmHeight) { capsHeight = 0; return false; } return true; } private bool ValidateHeights(ushort designEmHeight, ref short xHeight, ref short capsHeight) { return ValidatexHeight(designEmHeight, ref xHeight) && ValidateCapsHeight(designEmHeight, ref capsHeight); } ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ComputeHeights(CheckedPointer os2Table, ushort unitsPerEm, FontFaceLayoutInfo cache) { short xHeight = 0; short capsHeight = 0; bool validHeights = false; // first, try to get xHeight and capsHeight from OS/2 if (!os2Table.IsNull) { ushort os2version = ReadOpenTypeUShort(os2Table + OFF_OS2_version); if (os2version >= 2) { xHeight = ReadOpenTypeShort(os2Table + OFF_OS2_sxHeight); capsHeight = ReadOpenTypeShort(os2Table + OFF_OS2_sCapHeight); validHeights = ValidateHeights(unitsPerEm, ref xHeight, ref capsHeight); } } // in case there was a problem, try to get xHeight and capsHeight from PCLT if (!validHeights) { CheckedPointer pcltTable = GetTable(TrueTypeTags.PCLT); if (!pcltTable.IsNull) { if (xHeight == 0) xHeight = unchecked((short)ReadOpenTypeUShort(pcltTable + OFF_PCLT_xHeight)); if (capsHeight == 0) capsHeight = unchecked((short)ReadOpenTypeUShort(pcltTable + OFF_PCLT_CapHeight)); validHeights = ValidateHeights(unitsPerEm, ref xHeight, ref capsHeight); } } if (!validHeights && !cache.Symbol) { if (xHeight == 0) { for (int i = 0; i < MetricSearchList.xHeight.GetLength(0); i++) { ushort glyphIndex; if (!cache.CharacterMap.TryGetValue((int)MetricSearchList.xHeight[i, 0], out glyphIndex)) continue; // bsb = ah - (tsb + ymax - ymin) implies // ymax = ah - bsb - tsb + ymin, and ymin = -baseline long yMax = (long) cache.GetAdvanceHeight(glyphIndex) - cache.GetBottomSidebearing(glyphIndex) - cache.GetTopSidebearing(glyphIndex) - cache.GetBaseline(glyphIndex); yMax = yMax * MetricSearchList.xHeight[i, 1] / 100; xHeight = unchecked((short)yMax); if (ValidatexHeight(unitsPerEm, ref xHeight)) break; } } if (capsHeight == 0) { for (int i = 0; i < MetricSearchList.capsHeight.GetLength(0); i++) { ushort glyphIndex; if (!cache.CharacterMap.TryGetValue((int)MetricSearchList.capsHeight[i, 0], out glyphIndex)) continue; // bsb = ah - (tsb + ymax - ymin) implies // ymax = ah - bsb - tsb + ymin, and ymin = -baseline long yMax = (long) cache.GetAdvanceHeight(glyphIndex) - cache.GetBottomSidebearing(glyphIndex) - cache.GetTopSidebearing(glyphIndex) - cache.GetBaseline(glyphIndex); yMax = yMax * MetricSearchList.capsHeight[i, 1] / 100; capsHeight = unchecked((short)yMax); if (ValidateCapsHeight(unitsPerEm, ref capsHeight)) break; } } } // set the values to reasonable defaults // if still unable to obtain them from the font if (xHeight == 0) { // times.ttf is 45%, arial.ttf is 51%, micross.ttf is 52% xHeight = unchecked((short)(unitsPerEm * 50 / 100)); } if (capsHeight == 0) { // times.ttf is 66%, arial.ttf is 71%, micross.ttf is 72% capsHeight = unchecked((short)(unitsPerEm * 70 / 100)); } cache.xHeight = xHeight; cache.CapsHeight = capsHeight; } ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadHmtx(FontFaceLayoutInfo cache, CheckedPointer hmtxTable, ushort numberOfMetrics) { for (ushort i = 0; i < numberOfMetrics; i++) { cache.SetAdvanceWidth(i, ReadOpenTypeUShort(hmtxTable)); hmtxTable += 2; cache.SetLeftSidebearing(i, ReadOpenTypeShort(hmtxTable)); hmtxTable += 2; } ushort fixedAdvance = cache.GetAdvanceWidth(unchecked((ushort)(numberOfMetrics - 1))); for (ushort i = numberOfMetrics; i < cache.GlyphCount; i++) { cache.SetAdvanceWidth(i, fixedAdvance); cache.SetLeftSidebearing(i, ReadOpenTypeShort(hmtxTable)); hmtxTable += 2; } } ////// Read metrics from vmtx if it's present /// /// Font cache structure ///Whether Vmtx was present in the font ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private bool ReadVmtx(FontFaceLayoutInfo cache) { CheckedPointer vheaTable = GetTable(TrueTypeTags.VertHeader); CheckedPointer vmtxTable = GetTable(TrueTypeTags.VerticalMetrics); if (vheaTable.IsNull || vmtxTable.IsNull) return false; ushort numberOfMetrics = ReadOpenTypeUShort(vheaTable + OFF_vhea_numOfLongVerMetrics); for (ushort i = 0; i < numberOfMetrics; i++) { cache.SetAdvanceHeight(i, ReadOpenTypeUShort(vmtxTable)); vmtxTable += 2; cache.SetTopSidebearing(i, ReadOpenTypeShort(vmtxTable)); vmtxTable += 2; } ushort fixedAdvance = cache.GetAdvanceHeight(unchecked((ushort)(numberOfMetrics - 1))); for (ushort i = numberOfMetrics; i < cache.GlyphCount; i++) { cache.SetAdvanceHeight(i, fixedAdvance); cache.SetTopSidebearing(i, ReadOpenTypeShort(vmtxTable)); vmtxTable += 2; } return true; } ////// Prevent JIT from inlining this method, so that PresentationCFFRasterizer.dll and PresentationCFFRasterizerNative.dll are loaded on demand. /// ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] [MethodImpl(MethodImplOptions.NoInlining)] private void ReadCFFMetrics( FontFaceLayoutInfo cache, bool vmtxPresent, ushort typoAscent, ushort typoDescent) { using (OTFRasterizer otfRasterizer = new OTFRasterizer()) { ushort numberOfGlyphs = otfRasterizer.NewFont(_unmanagedMemoryStream, SourceUri, _faceIndex); if (numberOfGlyphs != cache.GlyphCount) throw new FileFormatException(SourceUri); MS.Internal.FontRasterization.Transform tform = new MS.Internal.FontRasterization.Transform(); tform.a01 = tform.a10 = 0; tform.a00 = tform.a11 = cache.DesignEmHeight * 0x10000; otfRasterizer.NewTransform( 12, tform, OverscaleMode.None, RenderingFlags.None); GlyphMetrics glyphMetrics = new GlyphMetrics(); for (ushort i = 0; i < numberOfGlyphs; ++i) { otfRasterizer.NewGlyph(i); otfRasterizer.GetMetrics(out glyphMetrics); // per http://www.microsoft.com/typography/otspec/hmtx.htm // rsb = aw - (lsb + xmax - xmin) short rsb = (short)(cache.GetAdvanceWidth(i) - ((int)cache.GetLeftSidebearing(i) + glyphMetrics.width)); cache.SetRightSidebearing(i, rsb); int yMax = glyphMetrics.horizontalOrigin.y; int yMin = yMax - glyphMetrics.height; if (!vmtxPresent) { // There's no vmtx - fallback appropriately // Win 9x uses the typographic height (typo ascender - typo descender), // but NT uses the cell height (cell ascender + cell descender). // Which shall we use? The problem with the cell height is that in a // multilingual font it may be much taller than the East Asian glyphs, // causing the common case (East Asian vertical text) to appear too // widely spaced. The problem with the typographic height is that it // includes little or no extra space for diacritic marks. // Choice: use the Typographic height: It is best for FE, and the font // can fix non East Asian diacritic cases if it wishes by providing a vmtx. cache.SetAdvanceHeight(i, (ushort)(typoAscent + typoDescent)); cache.SetTopSidebearing(i, unchecked((short)(typoAscent - yMax))); } // bsb = ah - (tsb + ymax - ymin) short bsb = unchecked((short)(cache.GetAdvanceHeight(i) - ((int)cache.GetTopSidebearing(i) + glyphMetrics.height))); cache.SetBottomSidebearing(i, bsb); short baseline = unchecked((short)-yMin); cache.SetBaseline(i, baseline); } } } ////// Critical: This code writes into FontFaceLayoutInfo. /// TreatAsSafe: It does this only using font data and not user defined parameters. /// [SecurityCritical, SecurityTreatAsSafe] private void ReadGlyfMetrics( FontFaceLayoutInfo cache, ushort indexToLocFormat, bool vmtxPresent, ushort typoAscent, ushort typoDescent) { CheckedPointer locaTable = GetTable(TrueTypeTags.IndexToLoc); CheckedPointer glyphTable = GetTable(TrueTypeTags.GlyphData); // For TrueType fonts we can get ideal glyph metrics from 'glyf' table. // For CFF fonts we need to call into the CFF rasterizer. if (locaTable.IsNull || glyphTable.IsNull) { ReadCFFMetrics(cache, vmtxPresent, typoAscent, typoDescent); return; } for (ushort i = 0; i < cache.GlyphCount; ++i) { short xMin, xMax, yMin, yMax; try { // extract glyph bounding box int offset, nextGlyphOffset; if (indexToLocFormat == 0) // short format { offset = 2 * ReadOpenTypeUShort(locaTable + i * 2); nextGlyphOffset = 2 * ReadOpenTypeUShort(locaTable + (i + 1) * 2); } else // long format { offset = ReadOpenTypeLong(locaTable + i * 4); nextGlyphOffset = ReadOpenTypeLong(locaTable + (i + 1) * 4); } // per http://www.microsoft.com/typography/otspec/loca.htm // a glyph without an outline has the same value for its offset // as the next glyph if (nextGlyphOffset == offset) { xMin = xMax = yMin = yMax = 0; } else { xMin = ReadOpenTypeShort(glyphTable + (offset + OFF_glyf_xMin)); xMax = ReadOpenTypeShort(glyphTable + (offset + OFF_glyf_xMax)); yMin = ReadOpenTypeShort(glyphTable + (offset + OFF_glyf_yMin)); yMax = ReadOpenTypeShort(glyphTable + (offset + OFF_glyf_yMax)); } } catch (ArgumentOutOfRangeException) { // loca table entry points outside of glyf table // fill metrics with zeroes xMin = xMax = yMin = yMax = 0; } // per http://www.microsoft.com/typography/otspec/hmtx.htm // rsb = aw - (lsb + xmax - xmin) short rsb = (short)(cache.GetAdvanceWidth(i) - ((int)cache.GetLeftSidebearing(i) + xMax - xMin)); cache.SetRightSidebearing(i, rsb); if (!vmtxPresent) { // There's no vmtx - fallback appropriately // Win 9x uses the typographic height (typo ascender - typo descender), // but NT uses the cell height (cell ascender + cell descender). // Which shall we use? The problem with the cell height is that in a // multilingual font it may be much taller than the East Asian glyphs, // causing the common case (East Asian vertical text) to appear too // widely spaced. The problem with the typographic height is that it // includes little or no extra space for diacritic marks. // Choice: use the Typographic height: It is best for FE, and the font // can fix non East Asian diacritic cases if it wishes by providing a vmtx. cache.SetAdvanceHeight(i, (ushort)(typoAscent + typoDescent)); cache.SetTopSidebearing(i, unchecked((short)(typoAscent - yMax))); } // bsb = ah - (tsb + ymax - ymin) short bsb = unchecked((short)(cache.GetAdvanceHeight(i) - ((int)cache.GetTopSidebearing(i) + yMax - yMin))); cache.SetBottomSidebearing(i, bsb); short baseline = unchecked((short)-yMin); cache.SetBaseline(i, baseline); } } // reads glyph advance widths and sidebearings from font tables // see http://www.microsoft.com/typography/otspec/hmtx.htm for details private void ReadAdvances( FontFaceLayoutInfo cache, CheckedPointer hmtxTable, ushort numberOfMetrics, ushort indexToLocFormat, ushort typoAscent, ushort typoDescent ) { cache.CreateAdvanceWidthsArray(); // fill in advanceWidth and left sidebearing ReadHmtx(cache, hmtxTable, numberOfMetrics); // fill in advanceHeight and top sidebearing bool vmtxPresent = ReadVmtx(cache); // fill the rest from 'glyf' table in TrueType case ReadGlyfMetrics(cache, indexToLocFormat, vmtxPresent, typoAscent, typoDescent); } #endregion #region Fields // file-specific state private CheckedPointer _fileStream; private UnmanagedMemoryStream _unmanagedMemoryStream; private Uri _sourceUri; private int _numFaces; private FontTechnology _technology; // face-specific state private int _faceIndex; private int _directoryOffset; // table directory offset for TTC, 0 for TTF private DirectoryEntry[] _tableDirectory; #endregion } } // 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
- RotateTransform3D.cs
- SpeechRecognizer.cs
- FastEncoder.cs
- CodeSnippetExpression.cs
- DrawingContext.cs
- TransformPattern.cs
- Win32.cs
- GraphicsContainer.cs
- GeneralTransform.cs
- SchemaImporterExtensionElementCollection.cs
- Scripts.cs
- UnsafeMethods.cs
- PointLight.cs
- NativeObjectSecurity.cs
- LateBoundBitmapDecoder.cs
- SymmetricAlgorithm.cs
- Int32Collection.cs
- KeySplineConverter.cs
- OdbcConnection.cs
- ValidationError.cs
- CommaDelimitedStringAttributeCollectionConverter.cs
- ZipIOCentralDirectoryFileHeader.cs
- COM2IDispatchConverter.cs
- ErrorTableItemStyle.cs
- Utilities.cs
- DbParameterCollectionHelper.cs
- HostingMessageProperty.cs
- ComplexObject.cs
- DbProviderFactory.cs
- CleanUpVirtualizedItemEventArgs.cs
- BrowsableAttribute.cs
- ErrorView.xaml.cs
- ControlBindingsConverter.cs
- TextEditor.cs
- ProcessHostFactoryHelper.cs
- StatusBarItemAutomationPeer.cs
- MSAAEventDispatcher.cs
- DependencyProperty.cs
- UnsafeCollabNativeMethods.cs
- GridItemPatternIdentifiers.cs
- PropertyPathWorker.cs
- UndirectedGraph.cs
- SchemaImporterExtensionsSection.cs
- AttributeUsageAttribute.cs
- DataGridViewCellParsingEventArgs.cs
- RenderOptions.cs
- ProfilePropertySettings.cs
- AuthorizationRule.cs
- HtmlTableRowCollection.cs
- ArraySubsetEnumerator.cs
- LineServicesCallbacks.cs
- XDeferredAxisSource.cs
- SafeMILHandle.cs
- UpDownBase.cs
- GotoExpression.cs
- DbDataAdapter.cs
- PasswordPropertyTextAttribute.cs
- XmlSchemaComplexType.cs
- ByteStream.cs
- CodeTypeReferenceCollection.cs
- PolygonHotSpot.cs
- ListControl.cs
- AuthenticationSection.cs
- PagerStyle.cs
- DataListDesigner.cs
- AssociationTypeEmitter.cs
- LogReserveAndAppendState.cs
- MapPathBasedVirtualPathProvider.cs
- HtmlHistory.cs
- QueueProcessor.cs
- HMACSHA384.cs
- BitStack.cs
- DocumentXPathNavigator.cs
- Function.cs
- PolicyFactory.cs
- StubHelpers.cs
- LambdaReference.cs
- StaticFileHandler.cs
- ItemDragEvent.cs
- XsdValidatingReader.cs
- ClickablePoint.cs
- DebugController.cs
- CodeConditionStatement.cs
- linebase.cs
- BadImageFormatException.cs
- SQLInt64Storage.cs
- AlignmentXValidation.cs
- ISAPIRuntime.cs
- MDIWindowDialog.cs
- HostExecutionContextManager.cs
- ConnectionPointConverter.cs
- TextTreeInsertElementUndoUnit.cs
- PageContent.cs
- SizeAnimationBase.cs
- DragEventArgs.cs
- WorkflowTraceTransfer.cs
- StrongNameIdentityPermission.cs
- ImpersonationContext.cs
- httpstaticobjectscollection.cs
- Listbox.cs