/// <summary>
        /// Gets the current build number.
        /// </summary>
        /// <returns>The current build number.</returns>
        public static FontBuildNumber GetCurrentBuildNumber()
        {
            const string BuildNumberKey = "FontBuildNumber";
            string value = Environment.GetEnvironmentVariable(BuildNumberKey, EnvironmentVariableTarget.Process);
            FontBuildNumber current = new FontBuildNumber();

            if (!string.IsNullOrEmpty(value))
            {
                // Gets the current build number from environment successfully.
                current.Parse(value);
            }
            else
            {
                // Fails to get the build number from environment setting, creates one according to the time.
                current.MajorBuildNumber = (((DateTime.Now.Year - 2000) % 5) * 10000) +
                    (DateTime.Now.Month * 100) + DateTime.Now.Day;
                current.MinorBuildNumber = (DateTime.Now.Hour * 100) + DateTime.Now.Minute;
            }

            return current;
        }
 /// <summary>
 /// Save weight table to apl file.
 /// </summary>
 /// <param name="filePath">The target location of the file to save into.</param>
 public void SaveAsApl(string filePath)
 {
     FontBuildNumber buildNumber = new FontBuildNumber();
     SaveAsApl(filePath, buildNumber);
 }
        /// <summary>
        /// Loads the header section from binary reader.
        /// </summary>
        /// <param name="reader">The given binary reader.</param>
        private void LoadHeader(BinaryReader reader)
        {
            uint uintData = reader.ReadUInt32();
            if (uintData != FileTag)
            {
                byte[] bytes = Helper.ToBytes(uintData);
                Debug.Assert(bytes.Length == 4, "uint is 4 bytes length");
                throw new InvalidDataException(Helper.NeutralFormat(
                    "Unsupported file tag \"0x{0}, 0x{1}, 0x{2}, 0x{3}\"",
                    bytes[0].ToString("x", CultureInfo.InvariantCulture),
                    bytes[1].ToString("x", CultureInfo.InvariantCulture),
                    bytes[2].ToString("x", CultureInfo.InvariantCulture),
                    bytes[3].ToString("x", CultureInfo.InvariantCulture)));
            }

            Guid guid = new Guid(reader.ReadBytes(FormatTag.ToByteArray().Length));
            if (guid != FormatTag)
            {
                throw new InvalidDataException(Helper.NeutralFormat("Unsupported GUID \"{0}\"", guid.ToString("D", CultureInfo.InvariantCulture)));
            }

            DataSize = reader.ReadUInt32();
            if (DataSize != reader.BaseStream.Length - reader.BaseStream.Position)
            {
                throw new InvalidDataException("Input stream is corrupt since data size is mismatched");
            }

            uintData = reader.ReadUInt32();
            if (uintData != Version)
            {
                throw new InvalidDataException("Unspported version");
            }

            BuildNumber = new FontBuildNumber(reader.ReadInt32());
            SamplePerFrame = reader.ReadUInt32();
            UnitIndexOffset = reader.ReadUInt32();
            UnitIndexCount = reader.ReadUInt32();

            CandidateDataOffset = reader.ReadUInt32();
            CandidateCount = reader.ReadUInt32();
            StringPoolOffset = reader.ReadUInt32();
            StringPoolSize = reader.ReadUInt32();
        }
        /// <summary>
        /// Save weight table to apl file.
        /// </summary>
        /// <param name="filePath">Target file path to save weight table data.</param>
        /// <param name="buildNumber">Voice font build number.</param>
        public void SaveAsApl(string filePath, FontBuildNumber buildNumber)
        {
            Helper.EnsureFolderExistForFile(filePath);
            if (buildNumber == null)
            {
                throw new ArgumentNullException("buildNumber");
            }

            FileStream fs = new FileStream(filePath, FileMode.Create);
            try
            {
                using (BinaryWriter bw = new BinaryWriter(fs))
                {
                    fs = null;
                    uint size = (uint)Marshal.SizeOf(typeof(WeightTableHeaderSerial));

                    // Write header
                    WeightTableHeaderSerial aplHeader = new WeightTableHeaderSerial();
                    aplHeader.Tag = (uint)FontSectionTag.WeightTable;

                    ////sizeFieldOffset = sizeof(aplHeader.Tag + aplHeader.FormatTag)
                    int sizeFieldOffset = sizeof(uint) + aplHeader.FormatTag.Length;
                    aplHeader.Size = 0; // Update after all other sections have been written.
                    aplHeader.Version = (uint)FormatVersion.Tts30;
                    aplHeader.Build = (uint)buildNumber.ToInt32();
                    aplHeader.LangNumber = 1;
                    aplHeader.DataOffset = size;

                    bw.Write(aplHeader.ToBytes());

                    // Write Section
                    size += SaveSection(bw);

                    // Update "size" field in the header to sizeof(Tag + FormatTag + Size).
                    size -= sizeof(uint) + sizeof(uint) + (uint)aplHeader.FormatTag.Length;
                    bw.Seek(sizeFieldOffset, SeekOrigin.Begin);
                    bw.Write(size);
                }
            }
            finally
            {
                if (null != fs)
                {
                    fs.Dispose();
                }
            }
        }
        /// <summary>
        /// Save domain index file.
        /// </summary>
        /// <param name="filePath">File path.</param>
        /// <param name="buildNumber">Build number.</param>
        public void Save(string filePath, FontBuildNumber buildNumber)
        {
            // Header size
            int headerSize = Marshal.SizeOf(typeof(DomainIndexFileHeaderSerial));

            // Hash table size
            // One hash table item
            //   1. Offset in string pool (uint)
            //   2. Offset in feature data (uint)
            //   3. Index of next item in hash table (int)
            int hashTableSize = _items.Length * (sizeof(uint) + sizeof(uint) + sizeof(int));

            // Feature data size & string pool size
            int featureDataSize = 0;
            int stringPoolSize = 0;
            for (int i = 0; i < _items.Length; i++)
            {
                Debug.Assert(_items[i] != null);
                featureDataSize += _items[i].FeatureItemBinarySize;
                stringPoolSize += _items[i].StringBinarySize;
            }

            DomainIndexFileHeaderSerial header;

            header.Tag = (uint)_tag;
            header.Size = (uint)(headerSize - 8 + hashTableSize + featureDataSize + stringPoolSize);
            header.VersionNumber = (uint)FormatVersion.Tts30;
            header.BuildNumber = (uint)buildNumber.ToInt32();
            header.LanguageId = (uint)_language;
            header.HashTableItemCount = _items.Length;
            header.HashTableOffset = headerSize;
            header.HashTableSize = hashTableSize;
            header.FeatureDataOffset = header.HashTableOffset + hashTableSize;
            header.FeatureDataSize = featureDataSize;
            header.StringPoolOffset = header.FeatureDataOffset + featureDataSize;
            header.StringPoolSize = stringPoolSize;

            FileStream fs = new FileStream(filePath, FileMode.Create);
            try
            {
                using (BinaryWriter bw = new BinaryWriter(fs))
                {
                    fs = null;

                    // Write header
                    bw.Write(header.ToBytes());

                    // Write hash table
                    foreach (DomainIndexItem item in _items)
                    {
                        item.WriteHashtable(bw);
                    }

                    // Write feature data
                    foreach (DomainIndexItem item in _items)
                    {
                        item.WriteFeatureData(bw);
                    }

                    // Write string pool
                    foreach (DomainIndexItem item in _items)
                    {
                        item.WriteWordText(bw);
                    }
                }
            }
            finally
            {
                if (null != fs)
                {
                    fs.Dispose();
                }
            }
        }