Пример #1
0
        /// <summary>
        /// Segment mapping to delta values
        /// </summary>
        private void ReadFormat4(ByteReader reader)
        {
            Length   = reader.ReadUShortBE();
            Language = reader.ReadUShortBE();

            // segCount is stored x 2.
            SegCount = reader.ReadUShortBE() >> 1;

            // Skip searchRange, entrySelector, rangeShift.
            reader.ReadUShortBE();
            reader.ReadUShortBE();
            reader.ReadUShortBE();

            // The "unrolled" mapping from character codes to glyph indices.
            GlyphIndexMap = new Dictionary <uint, uint>();
            using ByteReader endCountReader      = reader.Branch(14, true);
            using ByteReader startCountReader    = reader.Branch(16 + SegCount * 2, true);
            using ByteReader idDeltaReader       = reader.Branch(16 + SegCount * 4, true);
            using ByteReader idRangeOffsetReader = reader.Branch(16 + SegCount * 6, true);
            int idRangeRelativeOffset = 16 + SegCount * 6;

            for (uint i = 0; i < SegCount - 1; i += 1)
            {
                ushort endCount      = endCountReader.ReadUShortBE();
                ushort startCount    = startCountReader.ReadUShortBE();
                short  idDelta       = idDeltaReader.ReadShortBE();
                ushort idRangeOffset = idRangeOffsetReader.ReadUShortBE();

                for (ushort c = startCount; c <= endCount; c += 1)
                {
                    uint glyphIndex;
                    if (idRangeOffset != 0)
                    {
                        // The idRangeOffset is relative to the current position in the idRangeOffset array.
                        // Take the current offset in the idRangeOffset array.
                        int glyphIndexOffset = idRangeOffsetReader.Position + idRangeRelativeOffset - 2;

                        // Add the value of the idRangeOffset, which will move us into the glyphIndex array.
                        glyphIndexOffset += idRangeOffset;

                        // Then add the character index of the current segment, multiplied by 2 for USHORTs.
                        glyphIndexOffset += (c - startCount) * 2;
                        reader.Position   = glyphIndexOffset;
                        glyphIndex        = reader.ReadUShortBE();
                        if (glyphIndex != 0)
                        {
                            glyphIndex = (glyphIndex + (uint)idDelta) & 0xFFFF;
                        }
                    }
                    else
                    {
                        glyphIndex = (uint)(c + idDelta) & 0xFFFF;
                    }

                    GlyphIndexMap[c] = glyphIndex;
                }
            }
        }
Пример #2
0
        // Returns a list of "Top DICT"s found using an INDEX list.
        // Used to read both the usual high-level Top DICTs and also the FDArray
        // discovered inside CID-keyed fonts.  When a Top DICT has a reference to
        // a Private DICT that is read and saved into the Top DICT.
        //
        // In addition to the expected/optional values as outlined in TOP_DICT_META
        // the following values might be saved into the Top DICT.
        //
        //    _subrs []        array of local CFF subroutines from Private DICT
        //    _subrsBias       bias value computed from number of subroutines
        //                      (see calcCFFSubroutineBias() and parseCFFCharstring())
        //    _defaultWidthX   default widths for CFF characters
        //    _nominalWidthX   bias added to width embedded within glyph description
        //
        //    _privateDict     saved copy of parsed Private DICT from Top DICT
        private Dictionary <string, object>[] GatherCffTopDicts(ByteReader reader, byte[][] index)
        {
            var topDicts = new Dictionary <string, object> [index.Length];

            for (var i = 0; i < index.Length; i++)
            {
                Dictionary <string, object> topDict = ParseCffTopDict(new ByteReader(index[i]), _topDictMeta);

                var privateData = (object[])topDict["private"];

                var privateSize   = Convert.ToInt32(privateData[0]);
                var privateOffset = Convert.ToInt32(privateData[1]);

                if (privateSize != 0 && privateOffset != 0)
                {
                    Dictionary <string, object> privateDict = ParseCffTopDict(reader.Branch(privateOffset, true, privateSize), _privateDictMeta);
                    topDict["_defaultWidthX"] = privateDict["defaultWidthX"];
                    topDict["_nominalWidthX"] = privateDict["nominalWidthX"];

                    var subrs = Convert.ToInt32(privateDict["subrs"]);
                    if (subrs != 0)
                    {
                        int      subROffset = privateOffset + subrs;
                        byte[][] subrIndex  = ReadIndexArray <byte[]>(reader.Branch(subROffset, true));
                        topDict["_subrs"]     = subrIndex;
                        topDict["_subrsBias"] = CalculateSubroutineBias(subrIndex);
                    }
                    else
                    {
                        topDict["_subrs"]     = new object[0];
                        topDict["_subrsBias"] = 0;
                    }

                    topDict["_privateDict"] = privateDict;
                }

                topDicts[i] = topDict;
            }

            return(topDicts);
        }
Пример #3
0
        /// <summary>
        /// Parse the `CMap` table. This table stores the mappings from characters to glyphs.
        /// </summary>
        public CMapTable(ByteReader reader)
        {
            Version = reader.ReadUShortBE();

            if (Version != 0)
            {
                Engine.Log.Warning("CMap table version should be 0.", MessageSource.FontParser);
            }

            // The CMap table can contain many sub-tables, each with their own format.
            // We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table.
            NumTables = reader.ReadUShortBE();
            int tableOffset = -1;

            for (var i = 0; i < NumTables; i += 1)
            {
                ushort platformId = reader.ReadUShortBE();
                ushort encodingId = reader.ReadUShortBE();
                var    offset     = (int)reader.ReadULongBE();
                if ((platformId != 3 || encodingId != 0 && encodingId != 1 && encodingId != 10) &&
                    (platformId != 0 || encodingId != 0 && encodingId != 1 && encodingId != 2 && encodingId != 3 && encodingId != 4))
                {
                    continue;
                }
                tableOffset = offset;
                break;
            }

            if (tableOffset == -1)
            {
                Engine.Log.Warning("No valid CMap sub-tables found.", MessageSource.FontParser);
                return;
            }

            ByteReader subReader = reader.Branch(tableOffset, true);

            Format = subReader.ReadUShortBE();

            if (Format == 12)
            {
                ReadFormat12(subReader);
            }
            else if (Format == 4)
            {
                ReadFormat4(subReader);
            }
            else
            {
                Engine.Log.Warning($"Unsupported CMap format - {Format}", MessageSource.FontParser);
            }
        }
Пример #4
0
        public static Dictionary <string, Dictionary <string, string> > ParseName(ByteReader reader, FontTable ltagTable)
        {
            var name = new Dictionary <string, Dictionary <string, string> >();

            ushort format = reader.ReadUShortBE();
            ushort count  = reader.ReadUShortBE();

            // The reader used to read strings from the data.
            int        stringReaderOffset = reader.ReadUShortBE();
            ByteReader stringReader       = reader.Branch(0, true);

            for (ushort i = 0; i < count; i++)
            {
                ushort platformId = reader.ReadUShortBE();
                ushort encodingId = reader.ReadUShortBE();
                ushort languageId = reader.ReadUShortBE();
                ushort nameId     = reader.ReadUShortBE();
                string property   = _nameTableNames.Length - 1 > nameId ? _nameTableNames[nameId] : nameId.ToString();
                ushort byteLength = reader.ReadUShortBE();
                ushort readOffset = reader.ReadUShortBE();
                string language   = GetLanguageCode(platformId, languageId, ltagTable);
                string encoding   = GetEncoding(platformId, encodingId, languageId);
                if (encoding == null || language == null)
                {
                    continue;
                }

                string text;
                stringReader.Position = stringReaderOffset + readOffset;
                if (encoding == "utf-16")
                {
                    byte[] chars = stringReader.ReadBytes(byteLength);
                    text = Encoding.BigEndianUnicode.GetString(chars);
                }
                else
                {
                    byte[] chars = stringReader.ReadBytes(byteLength);
                    text = MacString(chars, encoding);
                }

                if (string.IsNullOrEmpty(text))
                {
                    continue;
                }

                Dictionary <string, string> translations;
                if (!name.ContainsKey(property))
                {
                    translations = new Dictionary <string, string>();
                    name.Add(property, translations);
                }
                else
                {
                    translations = name[property];
                }

                translations[language] = text;
            }

            if (format == 1)
            {
                // todo: Microsoft's 'name' table 1.
            }

            return(name);
        }
Пример #5
0
        public PngChunk(ByteReader stream)
        {
            // Read chunk length.
            var lengthBuffer = new byte[4];
            int numBytes     = stream.Read(lengthBuffer, 0, 4);

            if (numBytes >= 1 && numBytes <= 3)
            {
                Engine.Log.Warning($"Chunk length {numBytes} is not valid!", MessageSource.ImagePng);
                return;
            }

            Array.Reverse(lengthBuffer);
            var length = BitConverter.ToInt32(lengthBuffer, 0);

            // Invalid chunk or after end chunk.
            if (numBytes == 0)
            {
                return;
            }

            // Read the chunk type.
            var typeBuffer         = new byte[4];
            int typeBufferNumBytes = stream.Read(typeBuffer, 0, 4);

            if (typeBufferNumBytes >= 1 && typeBufferNumBytes <= 3)
            {
                throw new Exception("ImagePng: Chunk type header is not valid!");
            }
            var chars = new char[4];

            chars[0] = (char)typeBuffer[0];
            chars[1] = (char)typeBuffer[1];
            chars[2] = (char)typeBuffer[2];
            chars[3] = (char)typeBuffer[3];
            Type     = new string(chars);

            ChunkReader = stream.Branch(0, false, length);
            stream.Seek(length, SeekOrigin.Current);

            // Read compressed chunk.
            var crcBuffer           = new byte[4];
            int numBytesCompression = stream.Read(crcBuffer, 0, 4);

            if (numBytesCompression >= 1 && numBytesCompression <= 3)
            {
                throw new Exception("ImagePng: Compressed data header is not valid!");
            }
            Array.Reverse(crcBuffer);
            Crc = BitConverter.ToUInt32(crcBuffer, 0);

#if DEBUG
            var crc = new Crc32();
            crc.Update(typeBuffer);
            crc.Update(ChunkReader.Data.Span);

            // PNGs saved with Gimp spam the log with warnings.
            // https://gitlab.gnome.org/GNOME/gimp/-/issues/2111
            //if (crc.Value != Crc)
            //    Engine.Log.Warning($"CRC Error. PNG Image chunk {Type} is corrupt!", "ImagePng");
#endif
            Valid = true;
        }
Пример #6
0
        public static Glyph[] ParseGlyf(ByteReader reader, int[] locaOffsets)
        {
            var glyphs = new Glyph[locaOffsets.Length - 1];

            // Go through all glyphs and resolve them, leaving composite glyphs for later.
            var compositeGlyphParse = new List <CompositeGlyphRequest>();

            ParallelWork.FastLoops(glyphs.Length, (start, end) =>
            {
                for (int i = start; i < end; i++)
                {
                    var current = new Glyph();
                    glyphs[i]   = current;

                    if (i > locaOffsets.Length)
                    {
                        continue;
                    }
                    int glyphOffset = locaOffsets[i];
                    int nextOffset  = locaOffsets[i + 1];

                    // No data for glyph.
                    if (glyphOffset == nextOffset || glyphOffset >= reader.Data.Length)
                    {
                        current.Vertices = new GlyphVertex[0];
                        continue;
                    }

                    ByteReader glyphData = reader.Branch(glyphOffset, true);
                    int numberOfContours = glyphData.ReadShortBE();
                    current.XMin         = glyphData.ReadShortBE();
                    current.YMin         = glyphData.ReadShortBE();
                    current.XMax         = glyphData.ReadShortBE();
                    current.YMax         = glyphData.ReadShortBE();
                    // Non-composite
                    if (numberOfContours > 0)
                    {
                        ResolveTtfGlyph(numberOfContours, glyphData, current);
                    }
                    // Composite
                    else if (numberOfContours == -1)
                    {
                        lock (compositeGlyphParse)
                        {
                            compositeGlyphParse.Add(new CompositeGlyphRequest(glyphData, current));
                        }
                    }

                    // 0 is an invalid value.
                }
            }).Wait();

            // ReSharper disable once ImplicitlyCapturedClosure
            ParallelWork.FastLoops(compositeGlyphParse.Count, (start, end) =>
            {
                for (int i = start; i < end; i++)
                {
                    CompositeGlyphRequest request = compositeGlyphParse[i];
                    ResolveCompositeTtfGlyph(request.Reader, request.Glyph, glyphs);
                }
            });

            Debug.Assert(glyphs.All(x => x != null));

            return(glyphs);
        }
Пример #7
0
        public CffTable(ByteReader reader)
        {
            // Cff header
            FormatMajor = reader.ReadByte();
            FormatMinor = reader.ReadByte();
            Size        = reader.ReadByte();
            OffsetSize  = reader.ReadByte();

            ByteReader b = reader.Branch(0, false);

            NameIndex    = ReadIndexArray <string>(b);
            TopDictIndex = ReadIndexArray <byte[]>(b);
            StringIndex  = ReadIndexArray <string>(b);

            GlobalSubrIndex = ReadIndexArray <byte[]>(b);
            GlobalBias      = CalculateSubroutineBias(GlobalSubrIndex);

            Dictionary <string, object>[] topDicts = GatherCffTopDicts(reader.Branch(0, true), TopDictIndex);

            if (topDicts.Length > 1)
            {
                Engine.Log.Warning($"CFF table has many fonts in 'FontSet' - {topDicts.Length} Only the first will be parsed.", MessageSource.FontParser);
            }
            else if (topDicts.Length == 0)
            {
                Engine.Log.Warning("CFF table has no fonts in 'FontSet' - broken font?", MessageSource.FontParser);
                return;
            }

            TopDict = topDicts[0];
            if (TopDict.ContainsKey("_defaultWidthX"))
            {
                DefaultWidthX = Convert.ToInt32(TopDict["_defaultWidthX"]);
            }
            if (TopDict.ContainsKey("_nominalWidthX"))
            {
                NominalWidthX = Convert.ToInt32(TopDict["_nominalWidthX"]);
            }

            var ros = (object[])TopDict["ros"];

            if (ros[0] != null && ros[1] != null)
            {
                IsCidFont = true;
            }

            if (IsCidFont)
            {
                var fdArrayOffset  = Convert.ToInt32(TopDict["fdArray"]);
                var fdSelectOffset = Convert.ToInt32(TopDict["fdSelect"]);

                if (fdArrayOffset == 0 || fdSelectOffset == 0)
                {
                    Engine.Log.Warning("Font is marked as a CID font, but FDArray and/or FDSelect is missing.", MessageSource.FontParser);
                    return;
                }

                // todo
                // ByteReader r = reader.Branch(fdArrayOffset, true);
                // byte[][] fdArrayIndex = ReadIndexArray<byte[]>(r);
                // Dictionary<string, object>[] fd = GatherCffTopDicts(reader.Branch(0, true), fdArrayIndex);

                Debugger.Break();
            }
            else
            {
                FdSelect = Array.Empty <byte>();
                // todo
            }

            var offsets = (object[])TopDict["private"];

            if (offsets.Length > 0)
            {
                var        privateDictOffset            = Convert.ToInt32(offsets[1]);
                ByteReader privateDictReader            = reader.Branch(privateDictOffset, true, Convert.ToInt32(offsets[0]));
                Dictionary <string, object> privateDict = ParseCffTopDict(privateDictReader, _privateDictMeta);

                DefaultWidthX = Convert.ToInt32(privateDict["defaultWidthX"]);
                NominalWidthX = Convert.ToInt32(privateDict["nominalWidthX"]);

                var subrs = Convert.ToInt32(privateDict["subrs"]);

                if (subrs != 0)
                {
                    int      subrOffset = privateDictOffset + subrs;
                    byte[][] subrIndex  = ReadIndexArray <byte[]>(reader.Branch(subrOffset, true));
                    Subr     = subrIndex;
                    SubrBias = CalculateSubroutineBias(subrIndex);
                }
                else
                {
                    Subr     = Array.Empty <byte[]>();
                    SubrBias = 0;
                }
            }
            else
            {
                Engine.Log.Warning("Missing second private dictionary.", MessageSource.FontParser);
                return;
            }

            var charStringsOffset = Convert.ToInt32(TopDict["charStrings"]);

            CharStringIndex = ReadIndexArray <byte[]>(reader.Branch(charStringsOffset, true));
            NumberOfGlyphs  = CharStringIndex.Length;

            var charSetOffset = Convert.ToInt32(TopDict["charset"]);

            Charset = ParseCffCharset(reader.Branch(charSetOffset, true));

            var encoding = Convert.ToInt32(TopDict["encoding"]);

            switch (encoding)
            {
            // standard
            case 0:
                Encoding = CffStandardEncoding;
                break;

            // expert
            case 1:
                Encoding = CffExpertEncoding;
                break;

            // wtf
            default:
                CustomEncoding = ParseCustomCffEncoding(reader.Branch(encoding, true));
                break;
            }
        }