/* -*- 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 <stuart@mozilla.com>
 *   John Daggett <jdaggett@mozilla.com>
 *   Jonathan Kew <jfkthame@gmail.com>
 *
 * 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 "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsIPrefLocalizedString.h"
#include "nsISupportsPrimitives.h"
#include "nsIStreamBufferAccess.h"
#include "nsIUUIDGenerator.h"
#include "nsMemory.h"
#include "nsICharsetConverterManager.h"

#include "plbase64.h"

#include "woff.h"

#ifdef XP_MACOSX
#include <CoreFoundation/CoreFoundation.h>
#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<const Format12CmapHeader*>(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<const Format12Group*>(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<const PRUint16*>(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<const Format4Cmap*>(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<const Format12CmapHeader*>(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<const Format12Group*>(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<const Format14Cmap*>(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<const NonDefUVSTable*>
                                      (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<nsString>& aFontList)
{
    const PRUnichar kComma = PRUnichar(',');
    
    aFontList.Clear();
    
    // get the list of single-face font families
    nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));

    nsAutoString fontlistValue;
    if (prefs) {
        nsCOMPtr<nsISupportsString> prefString;
        prefs->GetComplexValue(aPrefName, NS_GET_IID(nsISupportsString), getter_AddRefs(prefString));
        if (!prefString) 
            return;
        prefString->GetData(fontlistValue);
    }
    
    // append each font name to the list
    nsAutoString fontname;
    nsPromiseFlatString fonts(fontlistValue);
    const PRUnichar *p, *p_end;
    fonts.BeginReading(p);
    fonts.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<nsIUUIDGenerator> 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<char*>(&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<const KernTableVersion0*>(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<const KernTableVersion1*>(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<const AutoSwap_PRUint16*>(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<const AutoSwap_PRUint32*>(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<const SFNTHeader*>(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<const AutoSwap_PRUint32*>(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<const SFNTHeader*>(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<const TableDirEntry*>(aFontData + sizeof(SFNTHeader));
    PRUint32 checksum = 0;
    
    // checksum for font = (checksum of header) + (checksum of tables)
    const AutoSwap_PRUint32 *headerData = 
        reinterpret_cast<const AutoSwap_PRUint32*>(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<const HeadTable*>(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<const MaxpTableHeader*>(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<const NameHeader*>(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<const NameRecord*>
                                       (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<PRUint8> *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<PRUint8*>(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<NameHeader*>(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<NameRecord*>(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<PRUnichar*>(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<SFNTHeader*>(newFontData);

    // table directory entries begin immediately following SFNT header
    TableDirEntry *dirEntry = 
        reinterpret_cast<TableDirEntry*>(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<AutoSwap_PRUint32*> (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<const AutoSwap_PRUint32*>(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<TableDirEntry*>(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<HeadTable*>(newFontData + headOffset);

    headData->checkSumAdjustment = HeadTable::HEAD_CHECKSUM_CALC_CONST - checksum;

    return NS_OK;
}

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<PRUint8>& aNameTable, PRUint32 aNameID, 
                        PRInt32 aPlatformID, nsTArray<nsString>& aNames)
{
    return ReadNames(aNameTable, aNameID, LANG_ALL, aPlatformID, aNames);
}

nsresult
gfxFontUtils::ReadCanonicalName(FallibleTArray<PRUint8>& aNameTable, PRUint32 aNameID, 
                                nsString& aName)
{
    nsresult rv;
    
    nsTArray<nsString> 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<const PRUint16*>(aNameData),
                      reinterpret_cast<PRUint16*>(aName.BeginWriting()), strLen);
#else
        aName.Assign(reinterpret_cast<const PRUnichar*>(aNameData), strLen);
#endif    
        return PR_TRUE;
    }

    nsresult rv;
    nsCOMPtr<nsICharsetConverterManager> 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<nsIUnicodeDecoder> 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<const char*>(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<const char*>(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<PRUint8>& aNameTable, PRUint32 aNameID, 
                        PRInt32 aLangID, PRInt32 aPlatformID,
                        nsTArray<nsString>& 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<const NameHeader*>(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<const NameRecord*>(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 = <size> 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<PRUint8> *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<EOTFixedHeader*>(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<const SFNTHeader*>(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<const TableDirEntry*>(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<const HeadTable*>(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<const NameHeader*>(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<const NameRecord*>(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<const PRUint16*>(aFontData + strOffset),
                      reinterpret_cast<PRUint16*>(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<const OS2Table*>(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)
{
    // 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<const SFNTHeader*>(aFontData);
    return (sfntHeader->sfntVersion == TRUETYPE_TAG('O','T','T','O'));
}

#endif
