/// <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; } } }
// 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); }
/// <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); } }
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); }
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; }
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); }
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; } }