static byte[] TryFixNameTable(ref byte[] data) { /* * For a font to render properly in the XAML engine it requires * a FamilyName to be properly set in the font's 'name' table. * It is valid to not have this value set and web fonts may * intentionally leave this blank as most web-font renderers will * work fine without it. However as we need it to get XAML to render * the font we need to manually pull the font name out from somewhere * else and stick it in the expected place. */ using var ms = new MemoryStream(data); using var t = new BEBinaryReader(ms); var version = t.ReadUInt16(); if (version == 0) { var count = t.ReadUInt16(); var offset = t.ReadUInt16(); var recs = Enumerable.Range(0, count).Select(_ => { return(new NameRecord { PlatformId = t.ReadUInt16(), EncodingId = t.ReadUInt16(), LangId = t.ReadUInt16(), NameId = t.ReadUInt16(), Length = t.ReadUInt16(), Offset = t.ReadUInt16() }); }).ToList(); // Debug code to view table data. //var options = recs.Where(r => r.PlatformId == 3).ToList(); //var ns = recs.Where(r => r.NameId == 1).ToList(); //foreach (var rec in recs) //{ // ms.Seek(rec.Offset + offset, SeekOrigin.Begin); // byte[] buf = t.ReadBytes(rec.Length); // Encoding enc2; // if (rec.EncodingId == 3 || rec.EncodingId == 1) // { // enc2 = Encoding.BigEndianUnicode; // } // else // { // enc2 = Encoding.UTF8; // } // string strRet = enc2.GetString(buf, 0, buf.Length); // System.Diagnostics.Debug.WriteLine(strRet); //} var fam = recs.FirstOrDefault(r => r.PlatformId == 3 && r.NameId == 1); var post = recs.FirstOrDefault(r => r.NameId == 6); if (fam is not null && post is not null && fam.Length == 0 && post.Length != 0) { fam.Length = post.Length; fam.Offset = post.Offset; } else { return(null); } using var mso = new MemoryStream(); using var w = new BEBinaryWriter(mso); w.Write(version); w.Write(count); w.Write(offset); foreach (var r in recs) { w.Write(r.PlatformId); w.Write(r.EncodingId); w.Write(r.LangId); w.Write(r.NameId); w.Write(r.Length); w.Write(r.Offset); } w.Flush(); ms.Seek(offset, SeekOrigin.Begin); ms.CopyTo(mso); mso.Flush(); return(mso.ToArray()); }
public static ConversionStatus Convert( Stream input, Stream output) { using var reader = new BEBinaryReader(input); var header = new WoffHeader { Signature = reader.ReadUInt32(), Flavor = reader.ReadUInt32(), Length = reader.ReadUInt32(), TableCount = reader.ReadUInt16(), Reserved = reader.ReadUInt16(), TotalSfntSize = reader.ReadUInt32(), MajorVersion = reader.ReadUInt16(), MinorVersion = reader.ReadUInt16(), MetaOffset = reader.ReadUInt32(), MetaLength = reader.ReadUInt32(), MetaOrignalLength = reader.ReadUInt32(), PrivOffset = reader.ReadUInt32(), PrivLength = reader.ReadUInt32() }; if (header.Signature is not 0x774F4646) { if (header.Signature is 0x774F4632) { return(ConversionStatus.UnsupportedWOFF2); } else { return(ConversionStatus.UnrecognisedFile); } } UInt32 offset = 12; // Read Table Headers List <TableDirectory> entries = new List <TableDirectory>(); for (var i = 0; i < header.TableCount; i++) { var entry = new TableDirectory { Tag = reader.ReadUInt32(), Offset = reader.ReadUInt32(), CompressedLength = reader.ReadUInt32(), OriginalLength = reader.ReadUInt32(), OriginalChecksum = reader.ReadUInt32() }; if (Name(entry.Tag) == "DSIG") // Conversion invalidates { continue; } entries.Add(entry); offset += (4 * 4); } // Amend table count after removing DSIG header.TableCount = (ushort)entries.Count; // Calculate header values UInt16 entrySelector = 0; while (Math.Pow(2, entrySelector) <= header.TableCount) { entrySelector++; } entrySelector--; UInt16 searchRange = (UInt16)(Math.Pow(2, entrySelector) * 16); UInt16 rangeShift = (UInt16)(header.TableCount * 16 - searchRange); // Create writer using var writer = new BEBinaryWriter(output); // Write Font Header writer.Write(header.Flavor); writer.Write(header.TableCount); writer.Write(searchRange); writer.Write(entrySelector); writer.Write(rangeShift); // Write Table Headers foreach (var entry in entries) { writer.Write(entry.Tag); writer.Write(entry.OriginalChecksum); writer.Write(offset); writer.Write(entry.OriginalLength); entry.OutputOffset = offset; offset += entry.OriginalLength; if ((offset % 4) != 0) { offset += 4 - (offset % 4); } } // Write Table contents foreach (var entry in entries) { input.Seek(entry.Offset, SeekOrigin.Begin); byte[] compressed = reader.ReadBytes((int)entry.CompressedLength); byte[] uncompressed; if (entry.CompressedLength != entry.OriginalLength) { // Decompress table using var comp = new MemoryStream(compressed.AsSpan().Slice(2).ToArray()); // Ignore the ZLib header (2 bytes long) using var outs = new MemoryStream(); using var def = new DeflateStream(comp, CompressionMode.Decompress); def.CopyTo(outs); uncompressed = outs.ToArray(); } else { uncompressed = compressed; } if (uncompressed.Length != entry.OriginalLength) { return(ConversionStatus.TableLengthMismatch); } if (entry.ToString() == "name") { if (TryFixNameTable(ref uncompressed) is byte[] ammended) { uncompressed = ammended; } } output.Seek(entry.OutputOffset, SeekOrigin.Begin); writer.Write(uncompressed); offset = (uint)output.Position; if (offset % 4 != 0) { uint padding = 4 - (offset % 4); writer.Write(new byte[padding]); } } writer.Flush(); output.Flush(); return(ConversionStatus.OK); }