/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is thebes gfx code. * * The Initial Developer of the Original Code is Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2007-2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Stuart Parmenter * John Daggett * Jonathan Kew * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "gfxFontUtils.h" #include "nsServiceManagerUtils.h" #include "mozilla/Preferences.h" #include "nsIStreamBufferAccess.h" #include "nsIUUIDGenerator.h" #include "nsMemory.h" #include "nsICharsetConverterManager.h" #include "plbase64.h" #include "woff.h" #ifdef XP_MACOSX #include #endif #define NO_RANGE_FOUND 126 // bit 126 in the font unicode ranges is required to be 0 #define UNICODE_BMP_LIMIT 0x10000 using namespace mozilla; // for the AutoSwap_* types /* Unicode subrange table * from: http://msdn.microsoft.com/en-us/library/dd374090 * * Edit the text to extend the initial digit, then use something like: * perl -pi -e 's/^(\d+)\t([\dA-Fa-f]+)\s+-\s+([\dA-Fa-f]+)\s+\b([a-zA-Z0-9\(\)\- ]+)/ { \1, 0x\2, 0x\3, \"\4\" },/' < unicoderange.txt * to generate the below list. */ struct UnicodeRangeTableEntry { PRUint8 bit; PRUint32 start; PRUint32 end; const char *info; }; static struct UnicodeRangeTableEntry gUnicodeRanges[] = { { 0, 0x0000, 0x007F, "Basic Latin" }, { 1, 0x0080, 0x00FF, "Latin-1 Supplement" }, { 2, 0x0100, 0x017F, "Latin Extended-A" }, { 3, 0x0180, 0x024F, "Latin Extended-B" }, { 4, 0x0250, 0x02AF, "IPA Extensions" }, { 4, 0x1D00, 0x1D7F, "Phonetic Extensions" }, { 4, 0x1D80, 0x1DBF, "Phonetic Extensions Supplement" }, { 5, 0x02B0, 0x02FF, "Spacing Modifier Letters" }, { 5, 0xA700, 0xA71F, "Modifier Tone Letters" }, { 6, 0x0300, 0x036F, "Combining Diacritical Marks" }, { 6, 0x1DC0, 0x1DFF, "Combining Diacritical Marks Supplement" }, { 7, 0x0370, 0x03FF, "Greek and Coptic" }, { 8, 0x2C80, 0x2CFF, "Coptic" }, { 9, 0x0400, 0x04FF, "Cyrillic" }, { 9, 0x0500, 0x052F, "Cyrillic Supplement" }, { 9, 0x2DE0, 0x2DFF, "Cyrillic Extended-A" }, { 9, 0xA640, 0xA69F, "Cyrillic Extended-B" }, { 10, 0x0530, 0x058F, "Armenian" }, { 11, 0x0590, 0x05FF, "Hebrew" }, { 12, 0xA500, 0xA63F, "Vai" }, { 13, 0x0600, 0x06FF, "Arabic" }, { 13, 0x0750, 0x077F, "Arabic Supplement" }, { 14, 0x07C0, 0x07FF, "NKo" }, { 15, 0x0900, 0x097F, "Devanagari" }, { 16, 0x0980, 0x09FF, "Bengali" }, { 17, 0x0A00, 0x0A7F, "Gurmukhi" }, { 18, 0x0A80, 0x0AFF, "Gujarati" }, { 19, 0x0B00, 0x0B7F, "Oriya" }, { 20, 0x0B80, 0x0BFF, "Tamil" }, { 21, 0x0C00, 0x0C7F, "Telugu" }, { 22, 0x0C80, 0x0CFF, "Kannada" }, { 23, 0x0D00, 0x0D7F, "Malayalam" }, { 24, 0x0E00, 0x0E7F, "Thai" }, { 25, 0x0E80, 0x0EFF, "Lao" }, { 26, 0x10A0, 0x10FF, "Georgian" }, { 26, 0x2D00, 0x2D2F, "Georgian Supplement" }, { 27, 0x1B00, 0x1B7F, "Balinese" }, { 28, 0x1100, 0x11FF, "Hangul Jamo" }, { 29, 0x1E00, 0x1EFF, "Latin Extended Additional" }, { 29, 0x2C60, 0x2C7F, "Latin Extended-C" }, { 29, 0xA720, 0xA7FF, "Latin Extended-D" }, { 30, 0x1F00, 0x1FFF, "Greek Extended" }, { 31, 0x2000, 0x206F, "General Punctuation" }, { 31, 0x2E00, 0x2E7F, "Supplemental Punctuation" }, { 32, 0x2070, 0x209F, "Superscripts And Subscripts" }, { 33, 0x20A0, 0x20CF, "Currency Symbols" }, { 34, 0x20D0, 0x20FF, "Combining Diacritical Marks For Symbols" }, { 35, 0x2100, 0x214F, "Letterlike Symbols" }, { 36, 0x2150, 0x218F, "Number Forms" }, { 37, 0x2190, 0x21FF, "Arrows" }, { 37, 0x27F0, 0x27FF, "Supplemental Arrows-A" }, { 37, 0x2900, 0x297F, "Supplemental Arrows-B" }, { 37, 0x2B00, 0x2BFF, "Miscellaneous Symbols and Arrows" }, { 38, 0x2200, 0x22FF, "Mathematical Operators" }, { 38, 0x27C0, 0x27EF, "Miscellaneous Mathematical Symbols-A" }, { 38, 0x2980, 0x29FF, "Miscellaneous Mathematical Symbols-B" }, { 38, 0x2A00, 0x2AFF, "Supplemental Mathematical Operators" }, { 39, 0x2300, 0x23FF, "Miscellaneous Technical" }, { 40, 0x2400, 0x243F, "Control Pictures" }, { 41, 0x2440, 0x245F, "Optical Character Recognition" }, { 42, 0x2460, 0x24FF, "Enclosed Alphanumerics" }, { 43, 0x2500, 0x257F, "Box Drawing" }, { 44, 0x2580, 0x259F, "Block Elements" }, { 45, 0x25A0, 0x25FF, "Geometric Shapes" }, { 46, 0x2600, 0x26FF, "Miscellaneous Symbols" }, { 47, 0x2700, 0x27BF, "Dingbats" }, { 48, 0x3000, 0x303F, "CJK Symbols And Punctuation" }, { 49, 0x3040, 0x309F, "Hiragana" }, { 50, 0x30A0, 0x30FF, "Katakana" }, { 50, 0x31F0, 0x31FF, "Katakana Phonetic Extensions" }, { 51, 0x3100, 0x312F, "Bopomofo" }, { 50, 0x31A0, 0x31BF, "Bopomofo Extended" }, { 52, 0x3130, 0x318F, "Hangul Compatibility Jamo" }, { 53, 0xA840, 0xA87F, "Phags-pa" }, { 54, 0x3200, 0x32FF, "Enclosed CJK Letters And Months" }, { 55, 0x3300, 0x33FF, "CJK Compatibility" }, { 56, 0xAC00, 0xD7AF, "Hangul Syllables" }, { 57, 0xD800, 0xDFFF, "Non-Plane 0" }, { 58, 0x10900, 0x1091F, "Phoenician" }, { 59, 0x2E80, 0x2EFF, "CJK Radicals Supplement" }, { 59, 0x2F00, 0x2FDF, "Kangxi Radicals" }, { 59, 0x2FF0, 0x2FFF, "Ideographic Description Characters" }, { 59, 0x3190, 0x319F, "Kanbun" }, { 59, 0x3400, 0x4DBF, "CJK Unified Ideographs Extension A" }, { 59, 0x4E00, 0x9FFF, "CJK Unified Ideographs" }, { 59, 0x20000, 0x2A6DF, "CJK Unified Ideographs Extension B" }, { 60, 0xE000, 0xF8FF, "Private Use Area" }, { 61, 0x31C0, 0x31EF, "CJK Strokes" }, { 61, 0xF900, 0xFAFF, "CJK Compatibility Ideographs" }, { 61, 0x2F800, 0x2FA1F, "CJK Compatibility Ideographs Supplement" }, { 62, 0xFB00, 0xFB4F, "Alphabetic Presentation Forms" }, { 63, 0xFB50, 0xFDFF, "Arabic Presentation Forms-A" }, { 64, 0xFE20, 0xFE2F, "Combining Half Marks" }, { 65, 0xFE10, 0xFE1F, "Vertical Forms" }, { 65, 0xFE30, 0xFE4F, "CJK Compatibility Forms" }, { 66, 0xFE50, 0xFE6F, "Small Form Variants" }, { 67, 0xFE70, 0xFEFF, "Arabic Presentation Forms-B" }, { 68, 0xFF00, 0xFFEF, "Halfwidth And Fullwidth Forms" }, { 69, 0xFFF0, 0xFFFF, "Specials" }, { 70, 0x0F00, 0x0FFF, "Tibetan" }, { 71, 0x0700, 0x074F, "Syriac" }, { 72, 0x0780, 0x07BF, "Thaana" }, { 73, 0x0D80, 0x0DFF, "Sinhala" }, { 74, 0x1000, 0x109F, "Myanmar" }, { 75, 0x1200, 0x137F, "Ethiopic" }, { 75, 0x1380, 0x139F, "Ethiopic Supplement" }, { 75, 0x2D80, 0x2DDF, "Ethiopic Extended" }, { 76, 0x13A0, 0x13FF, "Cherokee" }, { 77, 0x1400, 0x167F, "Unified Canadian Aboriginal Syllabics" }, { 78, 0x1680, 0x169F, "Ogham" }, { 79, 0x16A0, 0x16FF, "Runic" }, { 80, 0x1780, 0x17FF, "Khmer" }, { 80, 0x19E0, 0x19FF, "Khmer Symbols" }, { 81, 0x1800, 0x18AF, "Mongolian" }, { 82, 0x2800, 0x28FF, "Braille Patterns" }, { 83, 0xA000, 0xA48F, "Yi Syllables" }, { 83, 0xA490, 0xA4CF, "Yi Radicals" }, { 84, 0x1700, 0x171F, "Tagalog" }, { 84, 0x1720, 0x173F, "Hanunoo" }, { 84, 0x1740, 0x175F, "Buhid" }, { 84, 0x1760, 0x177F, "Tagbanwa" }, { 85, 0x10300, 0x1032F, "Old Italic" }, { 86, 0x10330, 0x1034F, "Gothic" }, { 87, 0x10400, 0x1044F, "Deseret" }, { 88, 0x1D000, 0x1D0FF, "Byzantine Musical Symbols" }, { 88, 0x1D100, 0x1D1FF, "Musical Symbols" }, { 88, 0x1D200, 0x1D24F, "Ancient Greek Musical Notation" }, { 89, 0x1D400, 0x1D7FF, "Mathematical Alphanumeric Symbols" }, { 90, 0xFF000, 0xFFFFD, "Private Use (plane 15)" }, { 90, 0x100000, 0x10FFFD, "Private Use (plane 16)" }, { 91, 0xFE00, 0xFE0F, "Variation Selectors" }, { 91, 0xE0100, 0xE01EF, "Variation Selectors Supplement" }, { 92, 0xE0000, 0xE007F, "Tags" }, { 93, 0x1900, 0x194F, "Limbu" }, { 94, 0x1950, 0x197F, "Tai Le" }, { 95, 0x1980, 0x19DF, "New Tai Lue" }, { 96, 0x1A00, 0x1A1F, "Buginese" }, { 97, 0x2C00, 0x2C5F, "Glagolitic" }, { 98, 0x2D30, 0x2D7F, "Tifinagh" }, { 99, 0x4DC0, 0x4DFF, "Yijing Hexagram Symbols" }, { 100, 0xA800, 0xA82F, "Syloti Nagri" }, { 101, 0x10000, 0x1007F, "Linear B Syllabary" }, { 101, 0x10080, 0x100FF, "Linear B Ideograms" }, { 101, 0x10100, 0x1013F, "Aegean Numbers" }, { 102, 0x10140, 0x1018F, "Ancient Greek Numbers" }, { 103, 0x10380, 0x1039F, "Ugaritic" }, { 104, 0x103A0, 0x103DF, "Old Persian" }, { 105, 0x10450, 0x1047F, "Shavian" }, { 106, 0x10480, 0x104AF, "Osmanya" }, { 107, 0x10800, 0x1083F, "Cypriot Syllabary" }, { 108, 0x10A00, 0x10A5F, "Kharoshthi" }, { 109, 0x1D300, 0x1D35F, "Tai Xuan Jing Symbols" }, { 110, 0x12000, 0x123FF, "Cuneiform" }, { 110, 0x12400, 0x1247F, "Cuneiform Numbers and Punctuation" }, { 111, 0x1D360, 0x1D37F, "Counting Rod Numerals" }, { 112, 0x1B80, 0x1BBF, "Sundanese" }, { 113, 0x1C00, 0x1C4F, "Lepcha" }, { 114, 0x1C50, 0x1C7F, "Ol Chiki" }, { 115, 0xA880, 0xA8DF, "Saurashtra" }, { 116, 0xA900, 0xA92F, "Kayah Li" }, { 117, 0xA930, 0xA95F, "Rejang" }, { 118, 0xAA00, 0xAA5F, "Cham" }, { 119, 0x10190, 0x101CF, "Ancient Symbols" }, { 120, 0x101D0, 0x101FF, "Phaistos Disc" }, { 121, 0x10280, 0x1029F, "Lycian" }, { 121, 0x102A0, 0x102DF, "Carian" }, { 121, 0x10920, 0x1093F, "Lydian" }, { 122, 0x1F000, 0x1F02F, "Mahjong Tiles" }, { 122, 0x1F030, 0x1F09F, "Domino Tiles" } }; #pragma pack(1) typedef struct { AutoSwap_PRUint16 format; AutoSwap_PRUint16 reserved; AutoSwap_PRUint32 length; AutoSwap_PRUint32 language; AutoSwap_PRUint32 numGroups; } Format12CmapHeader; typedef struct { AutoSwap_PRUint32 startCharCode; AutoSwap_PRUint32 endCharCode; AutoSwap_PRUint32 startGlyphId; } Format12Group; #pragma pack() nsresult gfxFontUtils::ReadCMAPTableFormat12(const PRUint8 *aBuf, PRUint32 aLength, gfxSparseBitSet& aCharacterMap) { // Ensure table is large enough that we can safely read the header NS_ENSURE_TRUE(aLength >= sizeof(Format12CmapHeader), NS_ERROR_GFX_CMAP_MALFORMED); // Sanity-check header fields const Format12CmapHeader *cmap12 = reinterpret_cast(aBuf); NS_ENSURE_TRUE(PRUint16(cmap12->format) == 12, NS_ERROR_GFX_CMAP_MALFORMED); NS_ENSURE_TRUE(PRUint16(cmap12->reserved) == 0, NS_ERROR_GFX_CMAP_MALFORMED); PRUint32 tablelen = cmap12->length; NS_ENSURE_TRUE(tablelen >= sizeof(Format12CmapHeader) && tablelen <= aLength, NS_ERROR_GFX_CMAP_MALFORMED); NS_ENSURE_TRUE(cmap12->language == 0, NS_ERROR_GFX_CMAP_MALFORMED); // Check that the table is large enough for the group array const PRUint32 numGroups = cmap12->numGroups; NS_ENSURE_TRUE((tablelen - sizeof(Format12CmapHeader)) / sizeof(Format12Group) >= numGroups, NS_ERROR_GFX_CMAP_MALFORMED); // The array of groups immediately follows the subtable header. const Format12Group *group = reinterpret_cast(aBuf + sizeof(Format12CmapHeader)); // Check that groups are in correct order and do not overlap, // and record character coverage in aCharacterMap. PRUint32 prevEndCharCode = 0; for (PRUint32 i = 0; i < numGroups; i++, group++) { const PRUint32 startCharCode = group->startCharCode; const PRUint32 endCharCode = group->endCharCode; NS_ENSURE_TRUE((prevEndCharCode < startCharCode || i == 0) && startCharCode <= endCharCode && endCharCode <= CMAP_MAX_CODEPOINT, NS_ERROR_GFX_CMAP_MALFORMED); aCharacterMap.SetRange(startCharCode, endCharCode); prevEndCharCode = endCharCode; } aCharacterMap.mBlocks.Compact(); return NS_OK; } nsresult gfxFontUtils::ReadCMAPTableFormat4(const PRUint8 *aBuf, PRUint32 aLength, gfxSparseBitSet& aCharacterMap) { enum { OffsetFormat = 0, OffsetLength = 2, OffsetLanguage = 4, OffsetSegCountX2 = 6 }; NS_ENSURE_TRUE(ReadShortAt(aBuf, OffsetFormat) == 4, NS_ERROR_GFX_CMAP_MALFORMED); PRUint16 tablelen = ReadShortAt(aBuf, OffsetLength); NS_ENSURE_TRUE(tablelen <= aLength, NS_ERROR_GFX_CMAP_MALFORMED); NS_ENSURE_TRUE(tablelen > 16, NS_ERROR_GFX_CMAP_MALFORMED); // This field should normally (except for Mac platform subtables) be zero according to // the OT spec, but some buggy fonts have lang = 1 (which would be English for MacOS). // E.g. Arial Narrow Bold, v. 1.1 (Tiger), Arial Unicode MS (see bug 530614). // So accept either zero or one here; the error should be harmless. NS_ENSURE_TRUE((ReadShortAt(aBuf, OffsetLanguage) & 0xfffe) == 0, NS_ERROR_GFX_CMAP_MALFORMED); PRUint16 segCountX2 = ReadShortAt(aBuf, OffsetSegCountX2); NS_ENSURE_TRUE(tablelen >= 16 + (segCountX2 * 4), NS_ERROR_GFX_CMAP_MALFORMED); const PRUint16 segCount = segCountX2 / 2; const PRUint16 *endCounts = reinterpret_cast(aBuf + 14); const PRUint16 *startCounts = endCounts + 1 /* skip one uint16 for reservedPad */ + segCount; const PRUint16 *idDeltas = startCounts + segCount; const PRUint16 *idRangeOffsets = idDeltas + segCount; PRUint16 prevEndCount = 0; for (PRUint16 i = 0; i < segCount; i++) { const PRUint16 endCount = ReadShortAt16(endCounts, i); const PRUint16 startCount = ReadShortAt16(startCounts, i); const PRUint16 idRangeOffset = ReadShortAt16(idRangeOffsets, i); // sanity-check range NS_ENSURE_TRUE((startCount > prevEndCount || i == 0 || startCount == 0xFFFF) && startCount <= endCount, NS_ERROR_GFX_CMAP_MALFORMED); prevEndCount = endCount; if (idRangeOffset == 0) { aCharacterMap.SetRange(startCount, endCount); } else { // const PRUint16 idDelta = ReadShortAt16(idDeltas, i); // Unused: self-documenting. for (PRUint32 c = startCount; c <= endCount; ++c) { if (c == 0xFFFF) break; const PRUint16 *gdata = (idRangeOffset/2 + (c - startCount) + &idRangeOffsets[i]); NS_ENSURE_TRUE((PRUint8*)gdata > aBuf && (PRUint8*)gdata < aBuf + aLength, NS_ERROR_GFX_CMAP_MALFORMED); // make sure we have a glyph if (*gdata != 0) { // The glyph index at this point is: // glyph = (ReadShortAt16(idDeltas, i) + *gdata) % 65536; aCharacterMap.set(c); } } } } aCharacterMap.mBlocks.Compact(); return NS_OK; } nsresult gfxFontUtils::ReadCMAPTableFormat14(const PRUint8 *aBuf, PRUint32 aLength, PRUint8*& aTable) { enum { OffsetFormat = 0, OffsetTableLength = 2, OffsetNumVarSelectorRecords = 6, OffsetVarSelectorRecords = 10, SizeOfVarSelectorRecord = 11, VSRecOffsetVarSelector = 0, VSRecOffsetDefUVSOffset = 3, VSRecOffsetNonDefUVSOffset = 7, SizeOfDefUVSTable = 4, DefUVSOffsetStartUnicodeValue = 0, DefUVSOffsetAdditionalCount = 3, SizeOfNonDefUVSTable = 5, NonDefUVSOffsetUnicodeValue = 0, NonDefUVSOffsetGlyphID = 3 }; NS_ENSURE_TRUE(aLength >= OffsetVarSelectorRecords, NS_ERROR_GFX_CMAP_MALFORMED); NS_ENSURE_TRUE(ReadShortAt(aBuf, OffsetFormat) == 14, NS_ERROR_GFX_CMAP_MALFORMED); PRUint32 tablelen = ReadLongAt(aBuf, OffsetTableLength); NS_ENSURE_TRUE(tablelen <= aLength, NS_ERROR_GFX_CMAP_MALFORMED); NS_ENSURE_TRUE(tablelen >= OffsetVarSelectorRecords, NS_ERROR_GFX_CMAP_MALFORMED); const PRUint32 numVarSelectorRecords = ReadLongAt(aBuf, OffsetNumVarSelectorRecords); NS_ENSURE_TRUE((tablelen - OffsetVarSelectorRecords) / SizeOfVarSelectorRecord >= numVarSelectorRecords, NS_ERROR_GFX_CMAP_MALFORMED); const PRUint8 *records = aBuf + OffsetVarSelectorRecords; for (PRUint32 i = 0; i < numVarSelectorRecords; i++, records += SizeOfVarSelectorRecord) { const PRUint32 varSelector = ReadUint24At(records, VSRecOffsetVarSelector); const PRUint32 defUVSOffset = ReadLongAt(records, VSRecOffsetDefUVSOffset); const PRUint32 nonDefUVSOffset = ReadLongAt(records, VSRecOffsetNonDefUVSOffset); NS_ENSURE_TRUE(varSelector <= CMAP_MAX_CODEPOINT && defUVSOffset <= tablelen - 4 && nonDefUVSOffset <= tablelen - 4, NS_ERROR_GFX_CMAP_MALFORMED); if (defUVSOffset) { const PRUint32 numUnicodeValueRanges = ReadLongAt(aBuf, defUVSOffset); NS_ENSURE_TRUE((tablelen - defUVSOffset) / SizeOfDefUVSTable >= numUnicodeValueRanges, NS_ERROR_GFX_CMAP_MALFORMED); const PRUint8 *tables = aBuf + defUVSOffset + 4; PRUint32 prevEndUnicode = 0; for (PRUint32 j = 0; j < numUnicodeValueRanges; j++, tables += SizeOfDefUVSTable) { const PRUint32 startUnicode = ReadUint24At(tables, DefUVSOffsetStartUnicodeValue); const PRUint32 endUnicode = startUnicode + tables[DefUVSOffsetAdditionalCount]; NS_ENSURE_TRUE((prevEndUnicode < startUnicode || j == 0) && endUnicode <= CMAP_MAX_CODEPOINT, NS_ERROR_GFX_CMAP_MALFORMED); prevEndUnicode = endUnicode; } } if (nonDefUVSOffset) { const PRUint32 numUVSMappings = ReadLongAt(aBuf, nonDefUVSOffset); NS_ENSURE_TRUE((tablelen - nonDefUVSOffset) / SizeOfNonDefUVSTable >= numUVSMappings, NS_ERROR_GFX_CMAP_MALFORMED); const PRUint8 *tables = aBuf + nonDefUVSOffset + 4; PRUint32 prevUnicode = 0; for (PRUint32 j = 0; j < numUVSMappings; j++, tables += SizeOfNonDefUVSTable) { const PRUint32 unicodeValue = ReadUint24At(tables, NonDefUVSOffsetUnicodeValue); NS_ENSURE_TRUE((prevUnicode < unicodeValue || j == 0) && unicodeValue <= CMAP_MAX_CODEPOINT, NS_ERROR_GFX_CMAP_MALFORMED); prevUnicode = unicodeValue; } } } aTable = new PRUint8[tablelen]; memcpy(aTable, aBuf, tablelen); return NS_OK; } // Windows requires fonts to have a format-4 cmap with a Microsoft ID (3). On the Mac, fonts either have // a format-4 cmap with Microsoft platform/encoding id or they have one with a platformID == Unicode (0) // For fonts with two format-4 tables, the first one (Unicode platform) is preferred on the Mac. #if defined(XP_MACOSX) #define acceptableFormat4(p,e,k) (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDMicrosoft && !(k)) || \ ((p) == PLATFORM_ID_UNICODE)) #define acceptableUCS4Encoding(p, e, k) \ (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDUCS4ForMicrosoftPlatform) && (k) != 12 || \ ((p) == PLATFORM_ID_UNICODE && \ ((e) == EncodingIDDefaultForUnicodePlatform || (e) >= EncodingIDUCS4ForUnicodePlatform))) #else #define acceptableFormat4(p,e,k) ((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDMicrosoft) #define acceptableUCS4Encoding(p, e, k) \ ((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDUCS4ForMicrosoftPlatform) #endif #define acceptablePlatform(p) ((p) == PLATFORM_ID_UNICODE || (p) == PLATFORM_ID_MICROSOFT) #define isSymbol(p,e) ((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDSymbol) #define isUVSEncoding(p, e) ((p) == PLATFORM_ID_UNICODE && (e) == EncodingIDUVSForUnicodePlatform) PRUint32 gfxFontUtils::FindPreferredSubtable(const PRUint8 *aBuf, PRUint32 aBufLength, PRUint32 *aTableOffset, PRUint32 *aUVSTableOffset, PRBool *aSymbolEncoding) { enum { OffsetVersion = 0, OffsetNumTables = 2, SizeOfHeader = 4, TableOffsetPlatformID = 0, TableOffsetEncodingID = 2, TableOffsetOffset = 4, SizeOfTable = 8, SubtableOffsetFormat = 0 }; enum { EncodingIDSymbol = 0, EncodingIDMicrosoft = 1, EncodingIDDefaultForUnicodePlatform = 0, EncodingIDUCS4ForUnicodePlatform = 3, EncodingIDUVSForUnicodePlatform = 5, EncodingIDUCS4ForMicrosoftPlatform = 10 }; if (aUVSTableOffset) { *aUVSTableOffset = nsnull; } if (!aBuf || aBufLength < SizeOfHeader) { // cmap table is missing, or too small to contain header fields! return 0; } // PRUint16 version = ReadShortAt(aBuf, OffsetVersion); // Unused: self-documenting. PRUint16 numTables = ReadShortAt(aBuf, OffsetNumTables); if (aBufLength < PRUint32(SizeOfHeader + numTables * SizeOfTable)) { return 0; } // save the format we want here PRUint32 keepFormat = 0; const PRUint8 *table = aBuf + SizeOfHeader; for (PRUint16 i = 0; i < numTables; ++i, table += SizeOfTable) { const PRUint16 platformID = ReadShortAt(table, TableOffsetPlatformID); if (!acceptablePlatform(platformID)) continue; const PRUint16 encodingID = ReadShortAt(table, TableOffsetEncodingID); const PRUint32 offset = ReadLongAt(table, TableOffsetOffset); if (aBufLength - 2 < offset) { // this subtable is not valid - beyond end of buffer return 0; } const PRUint8 *subtable = aBuf + offset; const PRUint16 format = ReadShortAt(subtable, SubtableOffsetFormat); if (isSymbol(platformID, encodingID)) { keepFormat = format; *aTableOffset = offset; *aSymbolEncoding = PR_TRUE; break; } else if (format == 4 && acceptableFormat4(platformID, encodingID, keepFormat)) { keepFormat = format; *aTableOffset = offset; *aSymbolEncoding = PR_FALSE; } else if (format == 12 && acceptableUCS4Encoding(platformID, encodingID, keepFormat)) { keepFormat = format; *aTableOffset = offset; *aSymbolEncoding = PR_FALSE; if (platformID > PLATFORM_ID_UNICODE || !aUVSTableOffset || *aUVSTableOffset) { break; // we don't want to try anything else when this format is available. } } else if (format == 14 && isUVSEncoding(platformID, encodingID) && aUVSTableOffset) { *aUVSTableOffset = offset; if (keepFormat == 12) { break; } } } return keepFormat; } nsresult gfxFontUtils::ReadCMAP(const PRUint8 *aBuf, PRUint32 aBufLength, gfxSparseBitSet& aCharacterMap, PRUint32& aUVSOffset, PRPackedBool& aUnicodeFont, PRPackedBool& aSymbolFont) { PRUint32 offset; PRBool symbol; PRUint32 format = FindPreferredSubtable(aBuf, aBufLength, &offset, &aUVSOffset, &symbol); if (format == 4) { if (symbol) { aUnicodeFont = PR_FALSE; aSymbolFont = PR_TRUE; } else { aUnicodeFont = PR_TRUE; aSymbolFont = PR_FALSE; } return ReadCMAPTableFormat4(aBuf + offset, aBufLength - offset, aCharacterMap); } if (format == 12) { aUnicodeFont = PR_TRUE; aSymbolFont = PR_FALSE; return ReadCMAPTableFormat12(aBuf + offset, aBufLength - offset, aCharacterMap); } return NS_ERROR_FAILURE; } #pragma pack(1) typedef struct { AutoSwap_PRUint16 format; AutoSwap_PRUint16 length; AutoSwap_PRUint16 language; AutoSwap_PRUint16 segCountX2; AutoSwap_PRUint16 searchRange; AutoSwap_PRUint16 entrySelector; AutoSwap_PRUint16 rangeShift; AutoSwap_PRUint16 arrays[1]; } Format4Cmap; typedef struct { AutoSwap_PRUint16 format; AutoSwap_PRUint32 length; AutoSwap_PRUint32 numVarSelectorRecords; typedef struct { AutoSwap_PRUint24 varSelector; AutoSwap_PRUint32 defaultUVSOffset; AutoSwap_PRUint32 nonDefaultUVSOffset; } VarSelectorRecord; VarSelectorRecord varSelectorRecords[1]; } Format14Cmap; typedef struct { AutoSwap_PRUint32 numUVSMappings; typedef struct { AutoSwap_PRUint24 unicodeValue; AutoSwap_PRUint16 glyphID; } UVSMapping; UVSMapping uvsMappings[1]; } NonDefUVSTable; #pragma pack() PRUint32 gfxFontUtils::MapCharToGlyphFormat4(const PRUint8 *aBuf, PRUnichar aCh) { const Format4Cmap *cmap4 = reinterpret_cast(aBuf); PRUint16 segCount; const AutoSwap_PRUint16 *endCodes; const AutoSwap_PRUint16 *startCodes; const AutoSwap_PRUint16 *idDelta; const AutoSwap_PRUint16 *idRangeOffset; PRUint16 probe; PRUint16 rangeShiftOver2; PRUint16 index; segCount = (PRUint16)(cmap4->segCountX2) / 2; endCodes = &cmap4->arrays[0]; startCodes = &cmap4->arrays[segCount + 1]; // +1 for reserved word between arrays idDelta = &startCodes[segCount]; idRangeOffset = &idDelta[segCount]; probe = 1 << (PRUint16)(cmap4->entrySelector); rangeShiftOver2 = (PRUint16)(cmap4->rangeShift) / 2; if ((PRUint16)(startCodes[rangeShiftOver2]) <= aCh) { index = rangeShiftOver2; } else { index = 0; } while (probe > 1) { probe >>= 1; if ((PRUint16)(startCodes[index + probe]) <= aCh) { index += probe; } } if (aCh >= (PRUint16)(startCodes[index]) && aCh <= (PRUint16)(endCodes[index])) { PRUint16 result; if ((PRUint16)(idRangeOffset[index]) == 0) { result = aCh; } else { PRUint16 offset = aCh - (PRUint16)(startCodes[index]); const AutoSwap_PRUint16 *glyphIndexTable = (const AutoSwap_PRUint16*)((const char*)&idRangeOffset[index] + (PRUint16)(idRangeOffset[index])); result = glyphIndexTable[offset]; } // note that this is unsigned 16-bit arithmetic, and may wrap around result += (PRUint16)(idDelta[index]); return result; } return 0; } PRUint32 gfxFontUtils::MapCharToGlyphFormat12(const PRUint8 *aBuf, PRUint32 aCh) { const Format12CmapHeader *cmap12 = reinterpret_cast(aBuf); // We know that numGroups is within range for the subtable size // because it was checked by ReadCMAPTableFormat12. PRUint32 numGroups = cmap12->numGroups; // The array of groups immediately follows the subtable header. const Format12Group *groups = reinterpret_cast(aBuf + sizeof(Format12CmapHeader)); // For most efficient binary search, we want to work on a range that // is a power of 2 so that we can always halve it by shifting. // So we find the largest power of 2 that is <= numGroups. // We will offset this range by rangeOffset so as to reach the end // of the table, provided that doesn't put us beyond the target // value from the outset. PRUint32 powerOf2 = mozilla::FindHighestBit(numGroups); PRUint32 rangeOffset = numGroups - powerOf2; PRUint32 range = 0; PRUint32 startCharCode; if (groups[rangeOffset].startCharCode <= aCh) { range = rangeOffset; } // Repeatedly halve the size of the range until we find the target group while (powerOf2 > 1) { powerOf2 >>= 1; if (groups[range + powerOf2].startCharCode <= aCh) { range += powerOf2; } } // Check if the character is actually present in the range and return // the corresponding glyph ID startCharCode = groups[range].startCharCode; if (startCharCode <= aCh && groups[range].endCharCode >= aCh) { return groups[range].startGlyphId + aCh - startCharCode; } // Else it's not present, so return the .notdef glyph return 0; } PRUint16 gfxFontUtils::MapUVSToGlyphFormat14(const PRUint8 *aBuf, PRUint32 aCh, PRUint32 aVS) { const Format14Cmap *cmap14 = reinterpret_cast(aBuf); // binary search in varSelectorRecords PRUint32 min = 0; PRUint32 max = cmap14->numVarSelectorRecords; PRUint32 nonDefUVSOffset = 0; while (min < max) { PRUint32 index = (min + max) >> 1; PRUint32 varSelector = cmap14->varSelectorRecords[index].varSelector; if (aVS == varSelector) { nonDefUVSOffset = cmap14->varSelectorRecords[index].nonDefaultUVSOffset; break; } if (aVS < varSelector) { max = index; } else { min = index + 1; } } if (!nonDefUVSOffset) { return 0; } const NonDefUVSTable *table = reinterpret_cast (aBuf + nonDefUVSOffset); // binary search in uvsMappings min = 0; max = table->numUVSMappings; while (min < max) { PRUint32 index = (min + max) >> 1; PRUint32 unicodeValue = table->uvsMappings[index].unicodeValue; if (aCh == unicodeValue) { return table->uvsMappings[index].glyphID; } if (aCh < unicodeValue) { max = index; } else { min = index + 1; } } return 0; } PRUint32 gfxFontUtils::MapCharToGlyph(const PRUint8 *aBuf, PRUint32 aBufLength, PRUint32 aCh) { PRUint32 offset; PRBool symbol; PRUint32 format = FindPreferredSubtable(aBuf, aBufLength, &offset, nsnull, &symbol); switch (format) { case 4: return aCh < UNICODE_BMP_LIMIT ? MapCharToGlyphFormat4(aBuf + offset, PRUnichar(aCh)) : 0; case 12: return MapCharToGlyphFormat12(aBuf + offset, aCh); default: return 0; } } PRUint8 gfxFontUtils::CharRangeBit(PRUint32 ch) { const PRUint32 n = sizeof(gUnicodeRanges) / sizeof(struct UnicodeRangeTableEntry); for (PRUint32 i = 0; i < n; ++i) if (ch >= gUnicodeRanges[i].start && ch <= gUnicodeRanges[i].end) return gUnicodeRanges[i].bit; return NO_RANGE_FOUND; } void gfxFontUtils::GetPrefsFontList(const char *aPrefName, nsTArray& aFontList) { const PRUnichar kComma = PRUnichar(','); aFontList.Clear(); // get the list of single-face font families nsAdoptingString fontlistValue = Preferences::GetString(aPrefName); if (!fontlistValue) { return; } // append each font name to the list nsAutoString fontname; const PRUnichar *p, *p_end; fontlistValue.BeginReading(p); fontlistValue.EndReading(p_end); while (p < p_end) { const PRUnichar *nameStart = p; while (++p != p_end && *p != kComma) /* nothing */ ; // pull out a single name and clean out leading/trailing whitespace fontname = Substring(nameStart, p); fontname.CompressWhitespace(PR_TRUE, PR_TRUE); // append it to the list aFontList.AppendElement(fontname); ++p; } } // produce a unique font name that is (1) a valid Postscript name and (2) less // than 31 characters in length. Using AddFontMemResourceEx on Windows fails // for names longer than 30 characters in length. #define MAX_B64_LEN 32 nsresult gfxFontUtils::MakeUniqueUserFontName(nsAString& aName) { nsCOMPtr uuidgen = do_GetService("@mozilla.org/uuid-generator;1"); NS_ENSURE_TRUE(uuidgen, NS_ERROR_OUT_OF_MEMORY); nsID guid; NS_ASSERTION(sizeof(guid) * 2 <= MAX_B64_LEN, "size of nsID has changed!"); nsresult rv = uuidgen->GenerateUUIDInPlace(&guid); NS_ENSURE_SUCCESS(rv, rv); char guidB64[MAX_B64_LEN] = {0}; if (!PL_Base64Encode(reinterpret_cast(&guid), sizeof(guid), guidB64)) return NS_ERROR_FAILURE; // all b64 characters except for '/' are allowed in Postscript names, so convert / ==> - char *p; for (p = guidB64; *p; p++) { if (*p == '/') *p = '-'; } aName.Assign(NS_LITERAL_STRING("uf")); aName.AppendASCII(guidB64); return NS_OK; } // TrueType/OpenType table handling code // need byte aligned structs #pragma pack(1) // name table stores set of name record structures, followed by // large block containing all the strings. name record offset and length // indicates the offset and length within that block. // http://www.microsoft.com/typography/otspec/name.htm struct NameRecordData { PRUint32 offset; PRUint32 length; }; #pragma pack() static PRBool IsValidSFNTVersion(PRUint32 version) { // normally 0x00010000, CFF-style OT fonts == 'OTTO' and Apple TT fonts = 'true' // 'typ1' is also possible for old Type 1 fonts in a SFNT container but not supported return version == 0x10000 || version == TRUETYPE_TAG('O','T','T','O') || version == TRUETYPE_TAG('t','r','u','e'); } // copy and swap UTF-16 values, assume no surrogate pairs, can be in place static void CopySwapUTF16(const PRUint16 *aInBuf, PRUint16 *aOutBuf, PRUint32 aLen) { const PRUint16 *end = aInBuf + aLen; while (aInBuf < end) { PRUint16 value = *aInBuf; *aOutBuf = (value >> 8) | (value & 0xff) << 8; aOutBuf++; aInBuf++; } } static PRBool ValidateKernTable(const PRUint8 *aKernTable, PRUint32 aKernLength) { // -- kern table can cause crashes if invalid, so do some basic sanity-checking const KernTableVersion0 *kernTable0 = reinterpret_cast(aKernTable); if (aKernLength < sizeof(KernTableVersion0)) { return PR_FALSE; } if (PRUint16(kernTable0->version) == 0) { if (aKernLength < sizeof(KernTableVersion0) + PRUint16(kernTable0->nTables) * sizeof(KernTableSubtableHeaderVersion0)) { return PR_FALSE; } // at least the table is big enough to contain the subtable headers; // we could go further and check the actual subtable sizes.... // for now, assume this is OK return PR_TRUE; } const KernTableVersion1 *kernTable1 = reinterpret_cast(aKernTable); if (aKernLength < sizeof(KernTableVersion1)) { return PR_FALSE; } if (kernTable1->version == 0x00010000) { if (aKernLength < sizeof(KernTableVersion1) + kernTable1->nTables * sizeof(KernTableSubtableHeaderVersion1)) { return PR_FALSE; } // at least the table is big enough to contain the subtable headers; // we could go further and check the actual subtable sizes.... // for now, assume this is OK return PR_TRUE; } // neither the old Windows version nor the newer Apple one; refuse to use it return PR_FALSE; } static PRBool ValidateLocaTable(const PRUint8* aLocaTable, PRUint32 aLocaLen, PRUint32 aGlyfLen, PRInt16 aLocaFormat, PRUint16 aNumGlyphs) { if (aLocaFormat == 0) { if (aLocaLen < PRUint32(aNumGlyphs + 1) * sizeof(PRUint16)) { return PR_FALSE; } const AutoSwap_PRUint16 *p = reinterpret_cast(aLocaTable); PRUint32 prev = 0; for (PRUint32 i = 0; i <= aNumGlyphs; ++i) { PRUint32 current = PRUint16(*p++) * 2; if (current < prev || current > aGlyfLen) { return PR_FALSE; } prev = current; } return PR_TRUE; } if (aLocaFormat == 1) { if (aLocaLen < (aNumGlyphs + 1) * sizeof(PRUint32)) { return PR_FALSE; } const AutoSwap_PRUint32 *p = reinterpret_cast(aLocaTable); PRUint32 prev = 0; for (PRUint32 i = 0; i <= aNumGlyphs; ++i) { PRUint32 current = *p++; if (current < prev || current > aGlyfLen) { return PR_FALSE; } prev = current; } return PR_TRUE; } return PR_FALSE; } gfxUserFontType gfxFontUtils::DetermineFontDataType(const PRUint8 *aFontData, PRUint32 aFontDataLength) { // test for OpenType font data // problem: EOT-Lite with 0x10000 length will look like TrueType! if (aFontDataLength >= sizeof(SFNTHeader)) { const SFNTHeader *sfntHeader = reinterpret_cast(aFontData); PRUint32 sfntVersion = sfntHeader->sfntVersion; if (IsValidSFNTVersion(sfntVersion)) { return GFX_USERFONT_OPENTYPE; } } // test for WOFF if (aFontDataLength >= sizeof(AutoSwap_PRUint32)) { const AutoSwap_PRUint32 *version = reinterpret_cast(aFontData); if (PRUint32(*version) == TRUETYPE_TAG('w','O','F','F')) { return GFX_USERFONT_WOFF; } } // tests for other formats here return GFX_USERFONT_UNKNOWN; } PRBool gfxFontUtils::ValidateSFNTHeaders(const PRUint8 *aFontData, PRUint32 aFontDataLength) { NS_ASSERTION(aFontData, "null font data"); PRUint64 dataLength(aFontDataLength); // read in the sfnt header if (sizeof(SFNTHeader) > aFontDataLength) { NS_WARNING("invalid font (insufficient data)"); return PR_FALSE; } const SFNTHeader *sfntHeader = reinterpret_cast(aFontData); PRUint32 sfntVersion = sfntHeader->sfntVersion; if (!IsValidSFNTVersion(sfntVersion)) { NS_WARNING("invalid font (SFNT version)"); return PR_FALSE; } // iterate through the table headers to find the head, name and OS/2 tables PRBool foundHead = PR_FALSE, foundOS2 = PR_FALSE, foundName = PR_FALSE; PRBool foundGlyphs = PR_FALSE, foundCFF = PR_FALSE, foundKern = PR_FALSE; PRBool foundLoca = PR_FALSE, foundMaxp = PR_FALSE; PRUint32 headOffset = 0, headLen, nameOffset = 0, nameLen, kernOffset = 0, kernLen = 0, glyfLen = 0, locaOffset = 0, locaLen = 0, maxpOffset = 0, maxpLen; PRUint32 i, numTables; numTables = sfntHeader->numTables; PRUint32 headerLen = sizeof(SFNTHeader) + sizeof(TableDirEntry) * numTables; if (headerLen > aFontDataLength) { NS_WARNING("invalid font (table directory)"); return PR_FALSE; } // table directory entries begin immediately following SFNT header const TableDirEntry *dirEntry = reinterpret_cast(aFontData + sizeof(SFNTHeader)); PRUint32 checksum = 0; // checksum for font = (checksum of header) + (checksum of tables) const AutoSwap_PRUint32 *headerData = reinterpret_cast(aFontData); // header length is in bytes, checksum calculated in longwords for (i = 0; i < (headerLen >> 2); i++, headerData++) { checksum += *headerData; } for (i = 0; i < numTables; i++, dirEntry++) { // sanity check on offset, length values if (PRUint64(dirEntry->offset) + PRUint64(dirEntry->length) > dataLength) { NS_WARNING("invalid font (table directory entry)"); return PR_FALSE; } checksum += dirEntry->checkSum; switch (dirEntry->tag) { case TRUETYPE_TAG('h','e','a','d'): foundHead = PR_TRUE; headOffset = dirEntry->offset; headLen = dirEntry->length; if (headLen < sizeof(HeadTable)) { NS_WARNING("invalid font (head table length)"); return PR_FALSE; } break; case TRUETYPE_TAG('k','e','r','n'): foundKern = PR_TRUE; kernOffset = dirEntry->offset; kernLen = dirEntry->length; break; case TRUETYPE_TAG('n','a','m','e'): foundName = PR_TRUE; nameOffset = dirEntry->offset; nameLen = dirEntry->length; break; case TRUETYPE_TAG('O','S','/','2'): foundOS2 = PR_TRUE; break; case TRUETYPE_TAG('g','l','y','f'): // TrueType-style quadratic glyph table foundGlyphs = PR_TRUE; glyfLen = dirEntry->length; break; case TRUETYPE_TAG('l','o','c','a'): // glyph location table foundLoca = PR_TRUE; locaOffset = dirEntry->offset; locaLen = dirEntry->length; break; case TRUETYPE_TAG('m','a','x','p'): // max profile foundMaxp = PR_TRUE; maxpOffset = dirEntry->offset; maxpLen = dirEntry->length; if (maxpLen < sizeof(MaxpTableHeader)) { NS_WARNING("invalid font (maxp table length)"); return PR_FALSE; } break; case TRUETYPE_TAG('C','F','F',' '): // PS-style cubic glyph table foundCFF = PR_TRUE; break; default: break; } } // simple sanity checks // -- fonts need head, name, maxp tables if (!foundHead || !foundName || !foundMaxp) { NS_WARNING("invalid font (missing head/name/maxp table)"); return PR_FALSE; } // -- on Windows need OS/2 table #ifdef XP_WIN if (!foundOS2) { NS_WARNING("invalid font (missing OS/2 table)"); return PR_FALSE; } #endif // -- head table data const HeadTable *headData = reinterpret_cast(aFontData + headOffset); if (headData->tableVersionNumber != HeadTable::HEAD_VERSION) { NS_WARNING("invalid font (head table version)"); return PR_FALSE; } if (headData->magicNumber != HeadTable::HEAD_MAGIC_NUMBER) { NS_WARNING("invalid font (head magic number)"); return PR_FALSE; } if (headData->checkSumAdjustment != (HeadTable::HEAD_CHECKSUM_CALC_CONST - checksum)) { NS_WARNING("invalid font (bad checksum)"); // Bug 483459 - warn about a bad checksum but allow the font to be // used, since a small percentage of fonts don't calculate this // correctly and font systems aren't fussy about this // return PR_FALSE; } // need glyf or CFF table based on sfnt version if (sfntVersion == TRUETYPE_TAG('O','T','T','O')) { if (!foundCFF) { NS_WARNING("invalid font (missing CFF table)"); return PR_FALSE; } } else { if (!foundGlyphs || !foundLoca) { NS_WARNING("invalid font (missing glyf or loca table)"); return PR_FALSE; } // sanity-check 'loca' offsets const MaxpTableHeader *maxpData = reinterpret_cast(aFontData + maxpOffset); if (!ValidateLocaTable(aFontData + locaOffset, locaLen, glyfLen, headData->indexToLocFormat, maxpData->numGlyphs)) { NS_WARNING("invalid font (loca table offsets)"); return PR_FALSE; } } // -- name table data const NameHeader *nameHeader = reinterpret_cast(aFontData + nameOffset); PRUint32 nameCount = nameHeader->count; // -- sanity check the number of name records if (PRUint64(nameCount) * sizeof(NameRecord) + PRUint64(nameOffset) > dataLength) { NS_WARNING("invalid font (name records)"); return PR_FALSE; } // -- iterate through name records const NameRecord *nameRecord = reinterpret_cast (aFontData + nameOffset + sizeof(NameHeader)); PRUint64 nameStringsBase = PRUint64(nameOffset) + PRUint64(nameHeader->stringOffset); for (i = 0; i < nameCount; i++, nameRecord++) { PRUint32 namelen = nameRecord->length; PRUint32 nameoff = nameRecord->offset; // offset from base of string storage if (nameStringsBase + PRUint64(nameoff) + PRUint64(namelen) > dataLength) { NS_WARNING("invalid font (name table strings)"); return PR_FALSE; } } // -- sanity-check the kern table, if present (see bug 487549) if (foundKern) { if (!ValidateKernTable(aFontData + kernOffset, kernLen)) { NS_WARNING("invalid font (kern table)"); return PR_FALSE; } } // everything seems consistent return PR_TRUE; } nsresult gfxFontUtils::RenameFont(const nsAString& aName, const PRUint8 *aFontData, PRUint32 aFontDataLength, FallibleTArray *aNewFont) { NS_ASSERTION(aNewFont, "null font data array"); PRUint64 dataLength(aFontDataLength); // new name table static const PRUint32 neededNameIDs[] = {NAME_ID_FAMILY, NAME_ID_STYLE, NAME_ID_UNIQUE, NAME_ID_FULL, NAME_ID_POSTSCRIPT}; // calculate new name table size PRUint16 nameCount = NS_ARRAY_LENGTH(neededNameIDs); // leave room for null-terminator PRUint16 nameStrLength = (aName.Length() + 1) * sizeof(PRUnichar); // round name table size up to 4-byte multiple PRUint32 nameTableSize = (sizeof(NameHeader) + sizeof(NameRecord) * nameCount + nameStrLength + 3) & ~3; if (dataLength + nameTableSize > PR_UINT32_MAX) return NS_ERROR_FAILURE; // bug 505386 - need to handle unpadded font length PRUint32 paddedFontDataSize = (aFontDataLength + 3) & ~3; PRUint32 adjFontDataSize = paddedFontDataSize + nameTableSize; // create new buffer: old font data plus new name table if (!aNewFont->AppendElements(adjFontDataSize)) return NS_ERROR_OUT_OF_MEMORY; // copy the old font data PRUint8 *newFontData = reinterpret_cast(aNewFont->Elements()); // null the last four bytes in case the font length is not a multiple of 4 memset(newFontData + aFontDataLength, 0, paddedFontDataSize - aFontDataLength); // copy font data memcpy(newFontData, aFontData, aFontDataLength); // null out the last 4 bytes for checksum calculations memset(newFontData + adjFontDataSize - 4, 0, 4); NameHeader *nameHeader = reinterpret_cast(newFontData + paddedFontDataSize); // -- name header nameHeader->format = 0; nameHeader->count = nameCount; nameHeader->stringOffset = sizeof(NameHeader) + nameCount * sizeof(NameRecord); // -- name records PRUint32 i; NameRecord *nameRecord = reinterpret_cast(nameHeader + 1); for (i = 0; i < nameCount; i++, nameRecord++) { nameRecord->platformID = PLATFORM_ID_MICROSOFT; nameRecord->encodingID = ENCODING_ID_MICROSOFT_UNICODEBMP; nameRecord->languageID = LANG_ID_MICROSOFT_EN_US; nameRecord->nameID = neededNameIDs[i]; nameRecord->offset = 0; nameRecord->length = nameStrLength; } // -- string data, located after the name records, stored in big-endian form PRUnichar *strData = reinterpret_cast(nameRecord); const PRUnichar *nameStr = aName.BeginReading(); const PRUnichar *nameStrEnd = aName.EndReading(); while (nameStr < nameStrEnd) { PRUnichar ch = *nameStr++; *strData++ = NS_SWAP16(ch); } *strData = 0; // add null termination // adjust name table header to point to the new name table SFNTHeader *sfntHeader = reinterpret_cast(newFontData); // table directory entries begin immediately following SFNT header TableDirEntry *dirEntry = reinterpret_cast(newFontData + sizeof(SFNTHeader)); PRUint32 numTables = sfntHeader->numTables; PRBool foundName = PR_FALSE; for (i = 0; i < numTables; i++, dirEntry++) { if (dirEntry->tag == TRUETYPE_TAG('n','a','m','e')) { foundName = PR_TRUE; break; } } // function only called if font validates, so this should always be true NS_ASSERTION(foundName, "attempt to rename font with no name table"); // note: dirEntry now points to name record // recalculate name table checksum PRUint32 checkSum = 0; AutoSwap_PRUint32 *nameData = reinterpret_cast (nameHeader); AutoSwap_PRUint32 *nameDataEnd = nameData + (nameTableSize >> 2); while (nameData < nameDataEnd) checkSum = checkSum + *nameData++; // adjust name table entry to point to new name table dirEntry->offset = paddedFontDataSize; dirEntry->length = nameTableSize; dirEntry->checkSum = checkSum; // fix up checksums PRUint32 checksum = 0; // checksum for font = (checksum of header) + (checksum of tables) PRUint32 headerLen = sizeof(SFNTHeader) + sizeof(TableDirEntry) * numTables; const AutoSwap_PRUint32 *headerData = reinterpret_cast(newFontData); // header length is in bytes, checksum calculated in longwords for (i = 0; i < (headerLen >> 2); i++, headerData++) { checksum += *headerData; } PRUint32 headOffset = 0; dirEntry = reinterpret_cast(newFontData + sizeof(SFNTHeader)); for (i = 0; i < numTables; i++, dirEntry++) { if (dirEntry->tag == TRUETYPE_TAG('h','e','a','d')) { headOffset = dirEntry->offset; } checksum += dirEntry->checkSum; } NS_ASSERTION(headOffset != 0, "no head table for font"); HeadTable *headData = reinterpret_cast(newFontData + headOffset); headData->checkSumAdjustment = HeadTable::HEAD_CHECKSUM_CALC_CONST - checksum; return NS_OK; } // This is only called after the basic validity of the downloaded sfnt // data has been checked, so it should never fail to find the name table // (though it might fail to read it, if memory isn't available); // other checks here are just for extra paranoia. nsresult gfxFontUtils::GetFullNameFromSFNT(const PRUint8* aFontData, PRUint32 aLength, nsAString& aFullName) { aFullName.AssignLiteral("(MISSING NAME)"); // should always get replaced NS_ENSURE_TRUE(aLength >= sizeof(SFNTHeader), NS_ERROR_UNEXPECTED); const SFNTHeader *sfntHeader = reinterpret_cast(aFontData); const TableDirEntry *dirEntry = reinterpret_cast(aFontData + sizeof(SFNTHeader)); PRUint32 numTables = sfntHeader->numTables; NS_ENSURE_TRUE(aLength >= sizeof(SFNTHeader) + numTables * sizeof(TableDirEntry), NS_ERROR_UNEXPECTED); PRBool foundName = PR_FALSE; for (PRUint32 i = 0; i < numTables; i++, dirEntry++) { if (dirEntry->tag == TRUETYPE_TAG('n','a','m','e')) { foundName = PR_TRUE; break; } } // should never fail, as we're only called after font validation succeeded NS_ENSURE_TRUE(foundName, NS_ERROR_NOT_AVAILABLE); PRUint32 len = dirEntry->length; NS_ENSURE_TRUE(aLength > len && aLength - len >= dirEntry->offset, NS_ERROR_UNEXPECTED); FallibleTArray nameTable; if (!nameTable.SetLength(len)) { return NS_ERROR_OUT_OF_MEMORY; } memcpy(nameTable.Elements(), aFontData + dirEntry->offset, len); return GetFullNameFromTable(nameTable, aFullName); } nsresult gfxFontUtils::GetFullNameFromTable(FallibleTArray& aNameTable, nsAString& aFullName) { nsAutoString name; nsresult rv = gfxFontUtils::ReadCanonicalName(aNameTable, gfxFontUtils::NAME_ID_FULL, name); if (NS_SUCCEEDED(rv) && !name.IsEmpty()) { aFullName = name; return NS_OK; } rv = gfxFontUtils::ReadCanonicalName(aNameTable, gfxFontUtils::NAME_ID_FAMILY, name); if (NS_SUCCEEDED(rv) && !name.IsEmpty()) { nsAutoString styleName; rv = gfxFontUtils::ReadCanonicalName(aNameTable, gfxFontUtils::NAME_ID_STYLE, styleName); if (NS_SUCCEEDED(rv) && !styleName.IsEmpty()) { name.AppendLiteral(" "); name.Append(styleName); aFullName = name; } return NS_OK; } return NS_ERROR_NOT_AVAILABLE; } enum { #if defined(XP_MACOSX) CANONICAL_LANG_ID = gfxFontUtils::LANG_ID_MAC_ENGLISH, PLATFORM_ID = gfxFontUtils::PLATFORM_ID_MAC #else CANONICAL_LANG_ID = gfxFontUtils::LANG_ID_MICROSOFT_EN_US, PLATFORM_ID = gfxFontUtils::PLATFORM_ID_MICROSOFT #endif }; nsresult gfxFontUtils::ReadNames(FallibleTArray& aNameTable, PRUint32 aNameID, PRInt32 aPlatformID, nsTArray& aNames) { return ReadNames(aNameTable, aNameID, LANG_ALL, aPlatformID, aNames); } nsresult gfxFontUtils::ReadCanonicalName(FallibleTArray& aNameTable, PRUint32 aNameID, nsString& aName) { nsresult rv; nsTArray names; // first, look for the English name (this will succeed 99% of the time) rv = ReadNames(aNameTable, aNameID, CANONICAL_LANG_ID, PLATFORM_ID, names); NS_ENSURE_SUCCESS(rv, rv); // otherwise, grab names for all languages if (names.Length() == 0) { rv = ReadNames(aNameTable, aNameID, LANG_ALL, PLATFORM_ID, names); NS_ENSURE_SUCCESS(rv, rv); } #if defined(XP_MACOSX) // may be dealing with font that only has Microsoft name entries if (names.Length() == 0) { rv = ReadNames(aNameTable, aNameID, LANG_ID_MICROSOFT_EN_US, PLATFORM_ID_MICROSOFT, names); NS_ENSURE_SUCCESS(rv, rv); // getting really desperate now, take anything! if (names.Length() == 0) { rv = ReadNames(aNameTable, aNameID, LANG_ALL, PLATFORM_ID_MICROSOFT, names); NS_ENSURE_SUCCESS(rv, rv); } } #endif // return the first name (99.9% of the time names will // contain a single English name) if (names.Length()) { aName.Assign(names[0]); return NS_OK; } return NS_ERROR_FAILURE; } // Charsets to use for decoding Mac platform font names. // This table is sorted by {encoding, language}, with the wildcard "ANY" being // greater than any defined values for each field; we use a binary search on both // fields, and fall back to matching only encoding if necessary // Some "redundant" entries for specific combinations are included such as // encoding=roman, lang=english, in order that common entries will be found // on the first search. #define ANY 0xffff const gfxFontUtils::MacFontNameCharsetMapping gfxFontUtils::gMacFontNameCharsets[] = { { ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ENGLISH, "x-mac-roman" }, { ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ICELANDIC, "x-mac-icelandic" }, { ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_TURKISH, "x-mac-turkish" }, { ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_POLISH, "x-mac-ce" }, { ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_ROMANIAN, "x-mac-romanian" }, { ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_CZECH, "x-mac-ce" }, { ENCODING_ID_MAC_ROMAN, LANG_ID_MAC_SLOVAK, "x-mac-ce" }, { ENCODING_ID_MAC_ROMAN, ANY, "x-mac-roman" }, { ENCODING_ID_MAC_JAPANESE, LANG_ID_MAC_JAPANESE, "Shift_JIS" }, { ENCODING_ID_MAC_JAPANESE, ANY, "Shift_JIS" }, { ENCODING_ID_MAC_TRAD_CHINESE, LANG_ID_MAC_TRAD_CHINESE, "Big5" }, { ENCODING_ID_MAC_TRAD_CHINESE, ANY, "Big5" }, { ENCODING_ID_MAC_KOREAN, LANG_ID_MAC_KOREAN, "EUC-KR" }, { ENCODING_ID_MAC_KOREAN, ANY, "EUC-KR" }, { ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_ARABIC, "x-mac-arabic" }, { ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_URDU, "x-mac-farsi" }, { ENCODING_ID_MAC_ARABIC, LANG_ID_MAC_FARSI, "x-mac-farsi" }, { ENCODING_ID_MAC_ARABIC, ANY, "x-mac-arabic" }, { ENCODING_ID_MAC_HEBREW, LANG_ID_MAC_HEBREW, "x-mac-hebrew" }, { ENCODING_ID_MAC_HEBREW, ANY, "x-mac-hebrew" }, { ENCODING_ID_MAC_GREEK, ANY, "x-mac-greek" }, { ENCODING_ID_MAC_CYRILLIC, ANY, "x-mac-cyrillic" }, { ENCODING_ID_MAC_DEVANAGARI, ANY, "x-mac-devanagari"}, { ENCODING_ID_MAC_GURMUKHI, ANY, "x-mac-gurmukhi" }, { ENCODING_ID_MAC_GUJARATI, ANY, "x-mac-gujarati" }, { ENCODING_ID_MAC_SIMP_CHINESE, LANG_ID_MAC_SIMP_CHINESE, "GB2312" }, { ENCODING_ID_MAC_SIMP_CHINESE, ANY, "GB2312" } }; const char* gfxFontUtils::gISOFontNameCharsets[] = { /* 0 */ "us-ascii" , /* 1 */ nsnull , /* spec says "ISO 10646" but does not specify encoding form! */ /* 2 */ "ISO-8859-1" }; const char* gfxFontUtils::gMSFontNameCharsets[] = { /* [0] ENCODING_ID_MICROSOFT_SYMBOL */ "" , /* [1] ENCODING_ID_MICROSOFT_UNICODEBMP */ "" , /* [2] ENCODING_ID_MICROSOFT_SHIFTJIS */ "Shift_JIS" , /* [3] ENCODING_ID_MICROSOFT_PRC */ nsnull , /* [4] ENCODING_ID_MICROSOFT_BIG5 */ "Big5" , /* [5] ENCODING_ID_MICROSOFT_WANSUNG */ nsnull , /* [6] ENCODING_ID_MICROSOFT_JOHAB */ "x-johab" , /* [7] reserved */ nsnull , /* [8] reserved */ nsnull , /* [9] reserved */ nsnull , /*[10] ENCODING_ID_MICROSOFT_UNICODEFULL */ "" }; #define ARRAY_SIZE(A) (sizeof(A) / sizeof(A[0])) // Return the name of the charset we should use to decode a font name // given the name table attributes. // Special return values: // "" charset is UTF16BE, no need for a converter // nsnull unknown charset, do not attempt conversion const char* gfxFontUtils::GetCharsetForFontName(PRUint16 aPlatform, PRUint16 aScript, PRUint16 aLanguage) { switch (aPlatform) { case PLATFORM_ID_UNICODE: return ""; case PLATFORM_ID_MAC: { PRUint32 lo = 0, hi = ARRAY_SIZE(gMacFontNameCharsets); MacFontNameCharsetMapping searchValue = { aScript, aLanguage, nsnull }; for (PRUint32 i = 0; i < 2; ++i) { // binary search; if not found, set language to ANY and try again while (lo < hi) { PRUint32 mid = (lo + hi) / 2; const MacFontNameCharsetMapping& entry = gMacFontNameCharsets[mid]; if (entry < searchValue) { lo = mid + 1; continue; } if (searchValue < entry) { hi = mid; continue; } // found a match return entry.mCharsetName; } // no match, so reset high bound for search and re-try hi = ARRAY_SIZE(gMacFontNameCharsets); searchValue.mLanguage = ANY; } } break; case PLATFORM_ID_ISO: if (aScript < ARRAY_SIZE(gISOFontNameCharsets)) { return gISOFontNameCharsets[aScript]; } break; case PLATFORM_ID_MICROSOFT: if (aScript < ARRAY_SIZE(gMSFontNameCharsets)) { return gMSFontNameCharsets[aScript]; } break; } return nsnull; } // convert a raw name from the name table to an nsString, if possible; // return value indicates whether conversion succeeded PRBool gfxFontUtils::DecodeFontName(const PRUint8 *aNameData, PRInt32 aByteLen, PRUint32 aPlatformCode, PRUint32 aScriptCode, PRUint32 aLangCode, nsAString& aName) { NS_ASSERTION(aByteLen > 0, "bad length for font name data"); const char *csName = GetCharsetForFontName(aPlatformCode, aScriptCode, aLangCode); if (!csName) { // nsnull -> unknown charset #ifdef DEBUG char warnBuf[128]; if (aByteLen > 64) aByteLen = 64; sprintf(warnBuf, "skipping font name, unknown charset %d:%d:%d for <%.*s>", aPlatformCode, aScriptCode, aLangCode, aByteLen, aNameData); NS_WARNING(warnBuf); #endif return PR_FALSE; } if (csName[0] == 0) { // empty charset name: data is utf16be, no need to instantiate a converter PRUint32 strLen = aByteLen / 2; #ifdef IS_LITTLE_ENDIAN aName.SetLength(strLen); CopySwapUTF16(reinterpret_cast(aNameData), reinterpret_cast(aName.BeginWriting()), strLen); #else aName.Assign(reinterpret_cast(aNameData), strLen); #endif return PR_TRUE; } nsresult rv; nsCOMPtr ccm = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get charset converter manager"); if (NS_FAILED(rv)) { return PR_FALSE; } nsCOMPtr decoder; rv = ccm->GetUnicodeDecoderRawInternal(csName, getter_AddRefs(decoder)); if (NS_FAILED(rv)) { NS_WARNING("failed to get the decoder for a font name string"); return PR_FALSE; } PRInt32 destLength; rv = decoder->GetMaxLength(reinterpret_cast(aNameData), aByteLen, &destLength); if (NS_FAILED(rv)) { NS_WARNING("decoder->GetMaxLength failed, invalid font name?"); return PR_FALSE; } // make space for the converted string aName.SetLength(destLength); rv = decoder->Convert(reinterpret_cast(aNameData), &aByteLen, aName.BeginWriting(), &destLength); if (NS_FAILED(rv)) { NS_WARNING("decoder->Convert failed, invalid font name?"); return PR_FALSE; } aName.Truncate(destLength); // set the actual length return PR_TRUE; } nsresult gfxFontUtils::ReadNames(FallibleTArray& aNameTable, PRUint32 aNameID, PRInt32 aLangID, PRInt32 aPlatformID, nsTArray& aNames) { PRUint32 nameTableLen = aNameTable.Length(); NS_ASSERTION(nameTableLen != 0, "null name table"); if (nameTableLen == 0) return NS_ERROR_FAILURE; PRUint8 *nameTable = aNameTable.Elements(); // -- name table data const NameHeader *nameHeader = reinterpret_cast(nameTable); PRUint32 nameCount = nameHeader->count; // -- sanity check the number of name records if (PRUint64(nameCount) * sizeof(NameRecord) > nameTableLen) { NS_WARNING("invalid font (name table data)"); return NS_ERROR_FAILURE; } // -- iterate through name records const NameRecord *nameRecord = reinterpret_cast(nameTable + sizeof(NameHeader)); PRUint64 nameStringsBase = PRUint64(nameHeader->stringOffset); PRUint32 i; for (i = 0; i < nameCount; i++, nameRecord++) { PRUint32 platformID; // skip over unwanted nameID's if (PRUint32(nameRecord->nameID) != aNameID) continue; // skip over unwanted platform data platformID = nameRecord->platformID; if (aPlatformID != PLATFORM_ALL && PRUint32(nameRecord->platformID) != PLATFORM_ID) continue; // skip over unwanted languages if (aLangID != LANG_ALL && PRUint32(nameRecord->languageID) != PRUint32(aLangID)) continue; // add name to names array // -- calculate string location PRUint32 namelen = nameRecord->length; PRUint32 nameoff = nameRecord->offset; // offset from base of string storage if (nameStringsBase + PRUint64(nameoff) + PRUint64(namelen) > nameTableLen) { NS_WARNING("invalid font (name table strings)"); return NS_ERROR_FAILURE; } // -- decode if necessary and make nsString nsAutoString name; nsresult rv; rv = DecodeFontName(nameTable + nameStringsBase + nameoff, namelen, platformID, PRUint32(nameRecord->encodingID), PRUint32(nameRecord->languageID), name); if (NS_FAILED(rv)) continue; PRUint32 k, numNames; PRBool foundName = PR_FALSE; numNames = aNames.Length(); for (k = 0; k < numNames; k++) { if (name.Equals(aNames[k])) { foundName = PR_TRUE; break; } } if (!foundName) aNames.AppendElement(name); } return NS_OK; } #ifdef XP_WIN // Embedded OpenType (EOT) handling // needed for dealing with downloadable fonts on Windows // // EOT version 0x00020001 // based on http://www.w3.org/Submission/2008/SUBM-EOT-20080305/ // // EOT header consists of a fixed-size portion containing general font // info, followed by a variable-sized portion containing name data, // followed by the actual TT/OT font data (non-byte values are always // stored in big-endian format) // // EOT header is stored in *little* endian order!! #pragma pack(1) struct EOTFixedHeader { PRUint32 eotSize; // Total structure length in PRUint8s (including string and font data) PRUint32 fontDataSize; // Length of the OpenType font (FontData) in PRUint8s PRUint32 version; // Version number of this format - 0x00010000 PRUint32 flags; // Processing Flags PRUint8 panose[10]; // The PANOSE value for this font - See http://www.microsoft.com/typography/otspec/os2.htm#pan PRUint8 charset; // In Windows this is derived from TEXTMETRIC.tmCharSet. This value specifies the character set of the font. DEFAULT_CHARSET (0x01) indicates no preference. - See http://msdn2.microsoft.com/en-us/library/ms534202.aspx PRUint8 italic; // If the bit for ITALIC is set in OS/2.fsSelection, the value will be 0x01 - See http://www.microsoft.com/typography/otspec/os2.htm#fss PRUint32 weight; // The weight value for this font - See http://www.microsoft.com/typography/otspec/os2.htm#wtc PRUint16 fsType; // Type flags that provide information about embedding permissions - See http://www.microsoft.com/typography/otspec/os2.htm#fst PRUint16 magicNumber; // Magic number for EOT file - 0x504C. Used to check for data corruption. PRUint32 unicodeRange1; // OS/2.UnicodeRange1 (bits 0-31) - See http://www.microsoft.com/typography/otspec/os2.htm#ur PRUint32 unicodeRange2; // OS/2.UnicodeRange2 (bits 32-63) - See http://www.microsoft.com/typography/otspec/os2.htm#ur PRUint32 unicodeRange3; // OS/2.UnicodeRange3 (bits 64-95) - See http://www.microsoft.com/typography/otspec/os2.htm#ur PRUint32 unicodeRange4; // OS/2.UnicodeRange4 (bits 96-127) - See http://www.microsoft.com/typography/otspec/os2.htm#ur PRUint32 codePageRange1; // CodePageRange1 (bits 0-31) - See http://www.microsoft.com/typography/otspec/os2.htm#cpr PRUint32 codePageRange2; // CodePageRange2 (bits 32-63) - See http://www.microsoft.com/typography/otspec/os2.htm#cpr PRUint32 checkSumAdjustment; // head.CheckSumAdjustment - See http://www.microsoft.com/typography/otspec/head.htm PRUint32 reserved[4]; // Reserved - must be 0 PRUint16 padding1; // Padding to maintain long alignment. Padding value must always be set to 0x0000. enum { EOT_VERSION = 0x00020001, EOT_MAGIC_NUMBER = 0x504c, EOT_DEFAULT_CHARSET = 0x01, EOT_EMBED_PRINT_PREVIEW = 0x0004, EOT_FAMILY_NAME_INDEX = 0, // order of names in variable portion of EOT header EOT_STYLE_NAME_INDEX = 1, EOT_VERSION_NAME_INDEX = 2, EOT_FULL_NAME_INDEX = 3, EOT_NUM_NAMES = 4 }; }; #pragma pack() // EOT headers are only used on Windows // EOT variable-sized header (version 0x00020001 - contains 4 name // fields, each with the structure): // // // number of bytes in the name array // PRUint16 size; // // array of UTF-16 chars, total length = bytes // // note: english version of name record string // PRUint8 name[size]; // // This structure is used for the following names, each separated by two // bytes of padding (always 0 with no padding after the rootString): // // familyName - based on name ID = 1 // styleName - based on name ID = 2 // versionName - based on name ID = 5 // fullName - based on name ID = 4 // rootString - used to restrict font usage to a specific domain // #if DEBUG static void DumpEOTHeader(PRUint8 *aHeader, PRUint32 aHeaderLen) { PRUint32 offset = 0; PRUint8 *ch = aHeader; printf("\n\nlen == %d\n\n", aHeaderLen); while (offset < aHeaderLen) { printf("%7.7x ", offset); int i; for (i = 0; i < 16; i++) { printf("%2.2x ", *ch++); } printf("\n"); offset += 16; } } #endif nsresult gfxFontUtils::MakeEOTHeader(const PRUint8 *aFontData, PRUint32 aFontDataLength, FallibleTArray *aHeader, FontDataOverlay *aOverlay) { NS_ASSERTION(aFontData && aFontDataLength != 0, "null font data"); NS_ASSERTION(aHeader, "null header"); NS_ASSERTION(aHeader->Length() == 0, "non-empty header passed in"); NS_ASSERTION(aOverlay, "null font overlay struct passed in"); aOverlay->overlaySrc = 0; if (!aHeader->AppendElements(sizeof(EOTFixedHeader))) return NS_ERROR_OUT_OF_MEMORY; EOTFixedHeader *eotHeader = reinterpret_cast(aHeader->Elements()); memset(eotHeader, 0, sizeof(EOTFixedHeader)); PRUint32 fontDataSize = aFontDataLength; // set up header fields eotHeader->fontDataSize = fontDataSize; eotHeader->version = EOTFixedHeader::EOT_VERSION; eotHeader->flags = 0; // don't specify any special processing eotHeader->charset = EOTFixedHeader::EOT_DEFAULT_CHARSET; eotHeader->fsType = EOTFixedHeader::EOT_EMBED_PRINT_PREVIEW; eotHeader->magicNumber = EOTFixedHeader::EOT_MAGIC_NUMBER; // read in the sfnt header if (sizeof(SFNTHeader) > aFontDataLength) return NS_ERROR_FAILURE; const SFNTHeader *sfntHeader = reinterpret_cast(aFontData); if (!IsValidSFNTVersion(sfntHeader->sfntVersion)) return NS_ERROR_FAILURE; // iterate through the table headers to find the head, name and OS/2 tables PRBool foundHead = PR_FALSE, foundOS2 = PR_FALSE, foundName = PR_FALSE, foundGlyphs = PR_FALSE; PRUint32 headOffset, headLen, nameOffset, nameLen, os2Offset, os2Len; PRUint32 i, numTables; numTables = sfntHeader->numTables; if (sizeof(SFNTHeader) + sizeof(TableDirEntry) * numTables > aFontDataLength) return NS_ERROR_FAILURE; PRUint64 dataLength(aFontDataLength); // table directory entries begin immediately following SFNT header const TableDirEntry *dirEntry = reinterpret_cast(aFontData + sizeof(SFNTHeader)); for (i = 0; i < numTables; i++, dirEntry++) { // sanity check on offset, length values if (PRUint64(dirEntry->offset) + PRUint64(dirEntry->length) > dataLength) return NS_ERROR_FAILURE; switch (dirEntry->tag) { case TRUETYPE_TAG('h','e','a','d'): foundHead = PR_TRUE; headOffset = dirEntry->offset; headLen = dirEntry->length; if (headLen < sizeof(HeadTable)) return NS_ERROR_FAILURE; break; case TRUETYPE_TAG('n','a','m','e'): foundName = PR_TRUE; nameOffset = dirEntry->offset; nameLen = dirEntry->length; break; case TRUETYPE_TAG('O','S','/','2'): foundOS2 = PR_TRUE; os2Offset = dirEntry->offset; os2Len = dirEntry->length; break; case TRUETYPE_TAG('g','l','y','f'): // TrueType-style quadratic glyph table foundGlyphs = PR_TRUE; break; case TRUETYPE_TAG('C','F','F',' '): // PS-style cubic glyph table foundGlyphs = PR_TRUE; break; default: break; } if (foundHead && foundName && foundOS2 && foundGlyphs) break; } // require these three tables on Windows if (!foundHead || !foundName || !foundOS2) return NS_ERROR_FAILURE; // at this point, all table offset/length values are within bounds // read in the data from those tables // -- head table data const HeadTable *headData = reinterpret_cast(aFontData + headOffset); if (headData->tableVersionNumber != HeadTable::HEAD_VERSION || headData->magicNumber != HeadTable::HEAD_MAGIC_NUMBER) { return NS_ERROR_FAILURE; } eotHeader->checkSumAdjustment = headData->checkSumAdjustment; // -- name table data // -- first, read name table header const NameHeader *nameHeader = reinterpret_cast(aFontData + nameOffset); PRUint32 nameStringsBase = PRUint32(nameHeader->stringOffset); PRUint32 nameCount = nameHeader->count; // -- sanity check the number of name records if (PRUint64(nameCount) * sizeof(NameRecord) + PRUint64(nameOffset) > dataLength) return NS_ERROR_FAILURE; // -- iterate through name records, look for specific name ids with // matching platform/encoding/etc. and store offset/lengths NameRecordData names[EOTFixedHeader::EOT_NUM_NAMES] = {0}; const NameRecord *nameRecord = reinterpret_cast(aFontData + nameOffset + sizeof(NameHeader)); PRUint32 needNames = (1 << EOTFixedHeader::EOT_FAMILY_NAME_INDEX) | (1 << EOTFixedHeader::EOT_STYLE_NAME_INDEX) | (1 << EOTFixedHeader::EOT_FULL_NAME_INDEX) | (1 << EOTFixedHeader::EOT_VERSION_NAME_INDEX); for (i = 0; i < nameCount; i++, nameRecord++) { // looking for Microsoft English US name strings, skip others if (PRUint32(nameRecord->platformID) != PLATFORM_ID_MICROSOFT || PRUint32(nameRecord->encodingID) != ENCODING_ID_MICROSOFT_UNICODEBMP || PRUint32(nameRecord->languageID) != LANG_ID_MICROSOFT_EN_US) continue; switch ((PRUint32)nameRecord->nameID) { case NAME_ID_FAMILY: names[EOTFixedHeader::EOT_FAMILY_NAME_INDEX].offset = nameRecord->offset; names[EOTFixedHeader::EOT_FAMILY_NAME_INDEX].length = nameRecord->length; needNames &= ~(1 << EOTFixedHeader::EOT_FAMILY_NAME_INDEX); break; case NAME_ID_STYLE: names[EOTFixedHeader::EOT_STYLE_NAME_INDEX].offset = nameRecord->offset; names[EOTFixedHeader::EOT_STYLE_NAME_INDEX].length = nameRecord->length; needNames &= ~(1 << EOTFixedHeader::EOT_STYLE_NAME_INDEX); break; case NAME_ID_FULL: names[EOTFixedHeader::EOT_FULL_NAME_INDEX].offset = nameRecord->offset; names[EOTFixedHeader::EOT_FULL_NAME_INDEX].length = nameRecord->length; needNames &= ~(1 << EOTFixedHeader::EOT_FULL_NAME_INDEX); break; case NAME_ID_VERSION: names[EOTFixedHeader::EOT_VERSION_NAME_INDEX].offset = nameRecord->offset; names[EOTFixedHeader::EOT_VERSION_NAME_INDEX].length = nameRecord->length; needNames &= ~(1 << EOTFixedHeader::EOT_VERSION_NAME_INDEX); break; default: break; } if (needNames == 0) break; } // the Version name is allowed to be null if ((needNames & ~(1 << EOTFixedHeader::EOT_VERSION_NAME_INDEX)) != 0) { return NS_ERROR_FAILURE; } // -- expand buffer if needed to include variable-length portion PRUint32 eotVariableLength = 0; eotVariableLength = (names[EOTFixedHeader::EOT_FAMILY_NAME_INDEX].length & (~1)) + (names[EOTFixedHeader::EOT_STYLE_NAME_INDEX].length & (~1)) + (names[EOTFixedHeader::EOT_FULL_NAME_INDEX].length & (~1)) + (names[EOTFixedHeader::EOT_VERSION_NAME_INDEX].length & (~1)) + EOTFixedHeader::EOT_NUM_NAMES * (2 /* size */ + 2 /* padding */) + 2 /* null root string size */; if (!aHeader->AppendElements(eotVariableLength)) return NS_ERROR_OUT_OF_MEMORY; // append the string data to the end of the EOT header PRUint8 *eotEnd = aHeader->Elements() + sizeof(EOTFixedHeader); PRUint32 strOffset, strLen; for (i = 0; i < EOTFixedHeader::EOT_NUM_NAMES; i++) { PRUint32 namelen = names[i].length; PRUint32 nameoff = names[i].offset; // offset from base of string storage // sanity check the name string location if (PRUint64(nameOffset) + PRUint64(nameStringsBase) + PRUint64(nameoff) + PRUint64(namelen) > dataLength) { return NS_ERROR_FAILURE; } strOffset = nameOffset + nameStringsBase + nameoff; // output 2-byte str size strLen = namelen & (~1); // UTF-16 string len must be even *((PRUint16*) eotEnd) = PRUint16(strLen); eotEnd += 2; // length is number of UTF-16 chars, not bytes CopySwapUTF16(reinterpret_cast(aFontData + strOffset), reinterpret_cast(eotEnd), (strLen >> 1)); eotEnd += strLen; // add 2-byte zero padding to the end of each string *eotEnd++ = 0; *eotEnd++ = 0; // Note: Microsoft's WEFT tool produces name strings which // include an extra null at the end of each string, in addition // to the 2-byte zero padding that separates the string fields. // Don't think this is important to imitate... } // append null root string size *eotEnd++ = 0; *eotEnd++ = 0; NS_ASSERTION(eotEnd == aHeader->Elements() + aHeader->Length(), "header length calculation incorrect"); // bug 496573 -- fonts with a fullname that does not begin with the // family name cause the EOT font loading API to hiccup PRUint32 famOff = names[EOTFixedHeader::EOT_FAMILY_NAME_INDEX].offset; PRUint32 famLen = names[EOTFixedHeader::EOT_FAMILY_NAME_INDEX].length; PRUint32 fullOff = names[EOTFixedHeader::EOT_FULL_NAME_INDEX].offset; PRUint32 fullLen = names[EOTFixedHeader::EOT_FULL_NAME_INDEX].length; const PRUint8 *nameStrings = aFontData + nameOffset + nameStringsBase; // assure that the start of the fullname matches the family name if (famLen <= fullLen && memcmp(nameStrings + famOff, nameStrings + fullOff, famLen)) { aOverlay->overlaySrc = nameOffset + nameStringsBase + famOff; aOverlay->overlaySrcLen = famLen; aOverlay->overlayDest = nameOffset + nameStringsBase + fullOff; } // -- OS/2 table data const OS2Table *os2Data = reinterpret_cast(aFontData + os2Offset); memcpy(eotHeader->panose, os2Data->panose, sizeof(eotHeader->panose)); eotHeader->italic = (PRUint16) os2Data->fsSelection & 0x01; eotHeader->weight = os2Data->usWeightClass; eotHeader->unicodeRange1 = os2Data->unicodeRange1; eotHeader->unicodeRange2 = os2Data->unicodeRange2; eotHeader->unicodeRange3 = os2Data->unicodeRange3; eotHeader->unicodeRange4 = os2Data->unicodeRange4; eotHeader->codePageRange1 = os2Data->codePageRange1; eotHeader->codePageRange2 = os2Data->codePageRange2; eotHeader->eotSize = aHeader->Length() + fontDataSize; // DumpEOTHeader(aHeader->Elements(), aHeader->Length()); return NS_OK; } /* static */ PRBool gfxFontUtils::IsCffFont(const PRUint8* aFontData, PRBool& hasVertical) { // this is only called after aFontData has passed basic validation, // so we know there is enough data present to allow us to read the version! const SFNTHeader *sfntHeader = reinterpret_cast(aFontData); PRUint32 i; PRUint32 numTables = sfntHeader->numTables; const TableDirEntry *dirEntry = reinterpret_cast(aFontData + sizeof(SFNTHeader)); hasVertical = PR_FALSE; for (i = 0; i < numTables; i++, dirEntry++) { if (dirEntry->tag == TRUETYPE_TAG('v','h','e','a')) { hasVertical = PR_TRUE; break; } } return (sfntHeader->sfntVersion == TRUETYPE_TAG('O','T','T','O')); } #endif