19 using System.Collections.Generic;
22 using System.Runtime.CompilerServices;
32 private static readonly Dictionary<string, TrueTypeFile> FontCache =
new Dictionary<string, TrueTypeFile>();
33 private static readonly Dictionary<Stream, TrueTypeFile> StreamFontCache =
new Dictionary<Stream, TrueTypeFile>();
34 private static object FontCacheLock =
new object();
35 internal uint ScalarType {
get; }
36 internal ushort NumTables {
get; }
37 internal ushort SearchRange {
get; }
38 internal ushort EntrySelector {
get; }
39 internal ushort RangeShift {
get; }
40 internal Dictionary<string, TrueTypeTableOffset> TableOffsets {
get; } =
new Dictionary<string, TrueTypeTableOffset>();
41 internal Dictionary<string, ITrueTypeTable> Tables {
get; } =
new Dictionary<string, ITrueTypeTable>();
55 string keyString =
null;
60 foreach (KeyValuePair<string, TrueTypeFile> kvp
in FontCache)
62 if (kvp.Value ==
this)
70 if (!
string.IsNullOrEmpty(keyString))
72 FontCache.Remove(keyString);
75 Stream keyStream =
null;
77 foreach (KeyValuePair<Stream, TrueTypeFile> kvp
in StreamFontCache)
79 if (kvp.Value ==
this)
85 if (keyStream !=
null)
87 StreamFontCache.Remove(keyStream);
92 this.TableOffsets.Clear();
96 internal TrueTypeFile(Dictionary<string, ITrueTypeTable> tables)
100 this.ScalarType = (uint)65536;
101 this.NumTables = (ushort)tables.Count;
102 this.EntrySelector = (ushort)Math.Floor(Math.Log(tables.Count, 2));
103 this.SearchRange = (ushort)(16 * (1 << this.EntrySelector));
104 this.RangeShift = (ushort)(tables.Count * 16 -
this.SearchRange);
106 uint offset = 12 + 16 * (uint)tables.Count;
108 uint fontChecksum =
this.ScalarType;
109 fontChecksum += ((uint)this.NumTables << 16) + (uint)this.SearchRange;
110 fontChecksum += ((uint)this.EntrySelector << 16) + (uint)this.RangeShift;
113 List<string> tableOrdering =
new List<string>()
115 "OS/2",
"cmap",
"glyf",
"head",
"hhea",
"hmtx",
"loca",
"maxp",
"name",
"post"
118 Dictionary<string, byte[]> allBytes =
new Dictionary<string, byte[]>();
120 foreach (KeyValuePair<string, ITrueTypeTable> kvp
in from el
in Tables let ind = tableOrdering.IndexOf(el.Key) orderby (ind >= 0 ? ind : tableOrdering.Count) ascending select el)
122 if (kvp.Key ==
"head")
124 ((TrueTypeHeadTable)kvp.Value).ChecksumAdjustment = 0;
127 byte[] tableBytes = kvp.Value.GetBytes();
128 allBytes.Add(kvp.Key, tableBytes);
129 int length = tableBytes.Length;
133 for (
int i = 0; i < tableBytes.Length; i += 4)
135 byte b1 = tableBytes[i];
136 byte b2 = (i + 1 < tableBytes.Length) ? tableBytes[i + 1] : (
byte)0;
137 byte b3 = (i + 2 < tableBytes.Length) ? tableBytes[i + 2] : (
byte)0;
138 byte b4 = (i + 3 < tableBytes.Length) ? tableBytes[i + 3] : (
byte)0;
140 checksum += (uint)((b1 << 24) + (b2 << 16) + (b3 << 8) + b4);
143 TableOffsets.Add(kvp.Key,
new TrueTypeTableOffset(checksum, offset, (uint)length));
145 fontChecksum += checksum;
147 fontChecksum += (uint)(((
byte)kvp.Key[0] << 24) + ((
byte)kvp.Key[1] << 16) + ((
byte)kvp.Key[2] << 8) + (
byte)kvp.Key[3]);
148 fontChecksum += offset;
149 fontChecksum += checksum;
150 fontChecksum += (uint)length;
152 offset += (uint)length;
168 ((TrueTypeHeadTable)this.Tables[
"head"]).ChecksumAdjustment = 0xB1B0AFBA - fontChecksum;
178 foreach (KeyValuePair<string, ITrueTypeTable> kvp
in from el
in Tables let ind = tableOrdering.IndexOf(el.Key) orderby (ind >= 0 ? ind : tableOrdering.Count) ascending select el)
180 FontStream.Write(Encoding.ASCII.GetBytes(kvp.Key), 0, 4);
181 FontStream.WriteUInt(this.TableOffsets[kvp.Key].Checksum);
182 FontStream.WriteUInt(this.TableOffsets[kvp.Key].Offset);
183 FontStream.WriteUInt(this.TableOffsets[kvp.Key].Length);
186 foreach (KeyValuePair<string, ITrueTypeTable> kvp
in from el
in Tables let ind = tableOrdering.IndexOf(el.Key) orderby (ind >= 0 ? ind : tableOrdering.Count) ascending select el)
188 FontStream.Write(allBytes[kvp.Key], 0, allBytes[kvp.Key].Length);
189 switch (allBytes[kvp.Key].Length % 4)
211 internal static TrueTypeFile CreateTrueTypeFile(
string fileName)
215 if (!FontCache.ContainsKey(fileName))
217 FontCache.Add(fileName,
new TrueTypeFile(fileName));
220 return FontCache[fileName];
224 internal static TrueTypeFile CreateTrueTypeFile(Stream fontStream)
228 if (!StreamFontCache.ContainsKey(fontStream))
230 StreamFontCache.Add(fontStream,
new TrueTypeFile(fontStream));
233 return StreamFontCache[fontStream];
237 private TrueTypeFile(
string fileName) : this(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
242 private TrueTypeFile(Stream fs)
245 this.ScalarType = fs.ReadUInt();
246 this.NumTables = fs.ReadUShort();
247 this.SearchRange = fs.ReadUShort();
248 this.EntrySelector = fs.ReadUShort();
249 this.RangeShift = fs.ReadUShort();
251 for (
int i = 0; i < NumTables; i++)
253 this.TableOffsets.Add(fs.ReadString(4),
new TrueTypeTableOffset(fs.ReadUInt(), fs.ReadUInt(), fs.ReadUInt()));
256 fs.Seek(this.TableOffsets[
"head"].Offset, SeekOrigin.Begin);
258 Tables.Add(
"head",
new TrueTypeHeadTable()
260 Version = fs.ReadFixed(),
261 FontRevision = fs.ReadFixed(),
262 ChecksumAdjustment = fs.ReadUInt(),
263 MagicNumber = fs.ReadUInt(),
264 Flags = fs.ReadUShort(),
265 UnitsPerEm = fs.ReadUShort(),
266 Created = fs.ReadDate(),
267 Modified = fs.ReadDate(),
268 XMin = fs.ReadShort(),
269 YMin = fs.ReadShort(),
270 XMax = fs.ReadShort(),
271 YMax = fs.ReadShort(),
272 MacStyle = fs.ReadUShort(),
273 LowestRecPPEM = fs.ReadUShort(),
274 FontDirectionInt = fs.ReadShort(),
275 IndexToLocFormat = fs.ReadShort(),
276 GlyphDataFormat = fs.ReadShort()
279 fs.Seek(this.TableOffsets[
"hhea"].Offset, SeekOrigin.Begin);
281 Tables.Add(
"hhea",
new TrueTypeHHeaTable()
283 Version = fs.ReadFixed(),
284 Ascent = fs.ReadShort(),
285 Descent = fs.ReadShort(),
286 LineGap = fs.ReadShort(),
287 AdvanceWidthMax = fs.ReadUShort(),
288 MinLeftSideBearing = fs.ReadShort(),
289 MinRightSideBearing = fs.ReadShort(),
290 XMaxExtent = fs.ReadShort(),
291 CaretSlopeRise = fs.ReadShort(),
292 CaretSlopeRun = fs.ReadShort(),
293 CaretOffset = fs.ReadShort()
301 ((TrueTypeHHeaTable)Tables[
"hhea"]).MetricDataFormat = fs.ReadShort();
302 ((TrueTypeHHeaTable)Tables[
"hhea"]).NumOfLongHorMetrics = fs.ReadUShort();
304 fs.Seek(this.TableOffsets[
"maxp"].Offset, SeekOrigin.Begin);
306 Tables.Add(
"maxp",
new TrueTypeMaxpTable()
308 Version = fs.ReadFixed(),
309 NumGlyphs = fs.ReadUShort()
312 if (((TrueTypeMaxpTable)Tables[
"maxp"]).Version.Bits == 65536)
314 ((TrueTypeMaxpTable)Tables[
"maxp"]).MaxPoints = fs.ReadUShort();
315 ((TrueTypeMaxpTable)Tables[
"maxp"]).MaxContours = fs.ReadUShort();
316 ((TrueTypeMaxpTable)Tables[
"maxp"]).MaxComponentPoints = fs.ReadUShort();
317 ((TrueTypeMaxpTable)Tables[
"maxp"]).MaxComponentContours = fs.ReadUShort();
318 ((TrueTypeMaxpTable)Tables[
"maxp"]).MaxZones = fs.ReadUShort();
319 ((TrueTypeMaxpTable)Tables[
"maxp"]).MaxTwilightPoints = fs.ReadUShort();
320 ((TrueTypeMaxpTable)Tables[
"maxp"]).MaxStorage = fs.ReadUShort();
321 ((TrueTypeMaxpTable)Tables[
"maxp"]).MaxFunctionDefs = fs.ReadUShort();
322 ((TrueTypeMaxpTable)Tables[
"maxp"]).MaxInstructionDefs = fs.ReadUShort();
323 ((TrueTypeMaxpTable)Tables[
"maxp"]).MaxStackElements = fs.ReadUShort();
324 ((TrueTypeMaxpTable)Tables[
"maxp"]).MaxSizeOfInstructions = fs.ReadUShort();
325 ((TrueTypeMaxpTable)Tables[
"maxp"]).MaxComponentElements = fs.ReadUShort();
326 ((TrueTypeMaxpTable)Tables[
"maxp"]).MaxComponentDepth = fs.ReadUShort();
329 int totalGlyphs = ((TrueTypeMaxpTable)Tables[
"maxp"]).NumGlyphs;
330 int numMetrics = ((TrueTypeHHeaTable)Tables[
"hhea"]).NumOfLongHorMetrics;
332 fs.Seek(this.TableOffsets[
"hmtx"].Offset, SeekOrigin.Begin);
333 Tables.Add(
"hmtx",
new TrueTypeHmtxTable() { HMetrics =
new LongHorFixed[numMetrics], LeftSideBearing =
new short[totalGlyphs - numMetrics] });
335 for (
int i = 0; i < numMetrics; i++)
337 ((TrueTypeHmtxTable)Tables[
"hmtx"]).HMetrics[i] =
new TrueTypeFile.LongHorFixed(fs.ReadUShort(), fs.ReadShort());
340 for (
int i = 0; i < totalGlyphs - numMetrics; i++)
342 ((TrueTypeHmtxTable)Tables[
"hmtx"]).LeftSideBearing[i] = fs.ReadShort();
345 fs.Seek(this.TableOffsets[
"cmap"].Offset, SeekOrigin.Begin);
346 Tables.Add(
"cmap",
new TrueTypeCmapTable() { Version = fs.ReadUShort(), NumberSubTables = fs.ReadUShort() });
348 ((TrueTypeCmapTable)Tables[
"cmap"]).SubTables =
new CmapSubTable[((TrueTypeCmapTable)Tables[
"cmap"]).NumberSubTables];
349 ((TrueTypeCmapTable)Tables[
"cmap"]).ActualCmapTables =
new ICmapTable[((TrueTypeCmapTable)Tables[
"cmap"]).NumberSubTables];
351 for (
int i = 0; i < ((TrueTypeCmapTable)Tables[
"cmap"]).NumberSubTables; i++)
353 ((TrueTypeCmapTable)Tables[
"cmap"]).SubTables[i] =
new TrueTypeFile.CmapSubTable(fs.ReadUShort(), fs.ReadUShort(), fs.ReadUInt());
357 for (
int i = 0; i < ((TrueTypeCmapTable)Tables[
"cmap"]).NumberSubTables; i++)
359 fs.Seek(((TrueTypeCmapTable)Tables[
"cmap"]).SubTables[i].Offset + TableOffsets[
"cmap"].Offset, SeekOrigin.Begin);
361 ushort format = fs.ReadUShort();
362 ushort length = fs.ReadUShort();
363 ushort language = fs.ReadUShort();
367 byte[] glyphIndexArray =
new byte[256];
368 fs.Read(glyphIndexArray, 0, 256);
369 ((TrueTypeCmapTable)Tables[
"cmap"]).ActualCmapTables[i] =
new CmapTable0(format, length, language, glyphIndexArray);
371 else if (format == 4)
373 ((TrueTypeCmapTable)Tables[
"cmap"]).ActualCmapTables[i] =
new CmapTable4(format, length, language, fs);
377 if (this.TableOffsets.ContainsKey(
"OS/2"))
379 fs.Seek(this.TableOffsets[
"OS/2"].Offset, SeekOrigin.Begin);
381 TrueTypeOS2Table os2 =
new TrueTypeOS2Table()
383 Version = fs.ReadUShort(),
384 XAvgCharWidth = fs.ReadShort(),
385 UsWeightClass = fs.ReadUShort(),
386 UsWidthClass = fs.ReadUShort(),
387 FsType = fs.ReadShort(),
388 YSubscriptXSize = fs.ReadShort(),
389 YSubscriptYSize = fs.ReadShort(),
390 YSubscriptXOffset = fs.ReadShort(),
391 YSubscriptYOffset = fs.ReadShort(),
392 YSuperscriptXSize = fs.ReadShort(),
393 YSuperscriptYSize = fs.ReadShort(),
394 YSuperscriptXOffset = fs.ReadShort(),
395 YSuperscriptYOffset = fs.ReadShort(),
396 YStrikeoutSize = fs.ReadShort(),
397 YStrikeoutPosition = fs.ReadShort(),
398 SFamilyClass = (byte)fs.ReadByte(),
399 SFamilySubClass = (byte)fs.ReadByte(),
400 Panose =
new TrueTypeOS2Table.PANOSE((
byte)fs.ReadByte(), (byte)fs.ReadByte(), (byte)fs.ReadByte(), (byte)fs.ReadByte(), (byte)fs.ReadByte(), (byte)fs.ReadByte(), (byte)fs.ReadByte(), (byte)fs.ReadByte(), (byte)fs.ReadByte(), (byte)fs.ReadByte()),
401 UlUnicodeRange =
new uint[] { fs.ReadUInt(), fs.ReadUInt(), fs.ReadUInt(), fs.ReadUInt() },
402 AchVendID =
new byte[] { (byte)fs.ReadByte(), (byte)fs.ReadByte(), (byte)fs.ReadByte(), (byte)fs.ReadByte() },
403 FsSelection = fs.ReadUShort(),
404 FsFirstCharIndex = fs.ReadUShort(),
405 FsLastCharIndex = fs.ReadUShort()
408 if (os2.Version > 0 || os2.Version == 0 &&
this.TableOffsets[
"OS/2"].Length > 68)
410 os2.STypoAscender = fs.ReadShort();
411 os2.STypoDescender = fs.ReadShort();
412 os2.STypoLineGap = fs.ReadShort();
413 os2.UsWinAscent = fs.ReadUShort();
414 os2.UsWinDescent = fs.ReadUShort();
416 if (os2.Version >= 1)
418 os2.UlCodePageRange =
new uint[] { fs.ReadUInt(), fs.ReadUInt() };
420 if (os2.Version >= 2)
422 os2.SxHeight = fs.ReadShort();
423 os2.SCapHeight = fs.ReadShort();
424 os2.UsDefaultChar = fs.ReadUShort();
425 os2.UsBreakChar = fs.ReadUShort();
426 os2.UsMaxContext = fs.ReadUShort();
428 if (os2.Version >= 5)
430 os2.UsLowerOpticalPointSize = fs.ReadUShort();
431 os2.UsUpperOpticalPointSize = fs.ReadUShort();
438 Tables.Add(
"OS/2", os2);
441 if (this.TableOffsets.ContainsKey(
"post"))
443 fs.Seek(this.TableOffsets[
"post"].Offset, SeekOrigin.Begin);
445 TrueTypePostTable post =
new TrueTypePostTable()
447 Version = fs.ReadFixed(),
448 ItalicAngle = fs.ReadFixed(),
449 UnderlinePosition = fs.ReadShort(),
450 UnderlineThickness = fs.ReadShort(),
452 MinMemType42 = fs.ReadUInt(),
453 MaxMemType42 = fs.ReadUInt(),
454 MinMemType1 = fs.ReadUInt(),
455 MaxMemType1 = fs.ReadUInt()
458 if (post.Version.Bits == 0x00020000)
460 post.NumGlyphs = fs.ReadUShort();
461 post.GlyphNameIndex =
new ushort[post.NumGlyphs];
463 int numberNewGlyphs = 0;
465 for (
int i = 0; i < post.NumGlyphs; i++)
467 post.GlyphNameIndex[i] = fs.ReadUShort();
468 if (post.GlyphNameIndex[i] >= 258)
474 post.Names =
new string[numberNewGlyphs];
476 for (
int i = 0; i < numberNewGlyphs; i++)
478 post.Names[i] = fs.ReadPascalString();
481 Tables.Add(
"post", post);
484 if (this.TableOffsets.ContainsKey(
"name"))
486 fs.Seek(this.TableOffsets[
"name"].Offset, SeekOrigin.Begin);
487 Tables.Add(
"name",
new TrueTypeNameTable(this.TableOffsets[
"name"].Offset, fs));
490 fs.Seek(this.TableOffsets[
"loca"].Offset, SeekOrigin.Begin);
491 Tables.Add(
"loca",
new TrueTypeLocaTable(fs, ((TrueTypeMaxpTable)this.Tables[
"maxp"]).NumGlyphs, ((TrueTypeHeadTable)this.Tables[
"head"]).IndexToLocFormat == 0));
493 if (this.TableOffsets.ContainsKey(
"cvt "))
495 fs.Seek(this.TableOffsets[
"cvt "].Offset, SeekOrigin.Begin);
496 Tables.Add(
"cvt ",
new TrueTypeRawTable(fs, this.TableOffsets[
"cvt "].Length));
499 if (this.TableOffsets.ContainsKey(
"prep"))
501 fs.Seek(this.TableOffsets[
"prep"].Offset, SeekOrigin.Begin);
502 Tables.Add(
"prep",
new TrueTypeRawTable(fs, this.TableOffsets[
"prep"].Length));
505 if (this.TableOffsets.ContainsKey(
"fpgm"))
507 fs.Seek(this.TableOffsets[
"fpgm"].Offset, SeekOrigin.Begin);
508 Tables.Add(
"fpgm",
new TrueTypeRawTable(fs, this.TableOffsets[
"fpgm"].Length));
511 if (this.TableOffsets.ContainsKey(
"GPOS"))
513 fs.Seek(this.TableOffsets[
"GPOS"].Offset, SeekOrigin.Begin);
514 Tables.Add(
"GPOS",
new TrueTypeGPOSTable(fs));
517 Glyph[] glyphs =
new Glyph[((TrueTypeMaxpTable)this.Tables[
"maxp"]).NumGlyphs];
519 for (
int i = 0; i < ((TrueTypeMaxpTable)this.Tables[
"maxp"]).NumGlyphs; i++)
521 uint offset = ((TrueTypeLocaTable)Tables[
"loca"]).GetOffset(i);
522 uint length = ((TrueTypeLocaTable)Tables[
"loca"]).Lengths[i];
526 fs.Seek(this.TableOffsets[
"glyf"].Offset + offset, SeekOrigin.Begin);
527 glyphs[i] = Glyph.Parse(fs);
531 glyphs[i] =
new EmptyGlyph();
536 Tables.Add(
"glyf",
new TrueTypeGlyfTable() { Glyphs = glyphs });
542 private double[] GlyphWidthsCache =
new double[256];
543 private VerticalMetrics[] VerticalMetricsCache =
new VerticalMetrics[256];
544 private Bearings[] BearingsCache =
new Bearings[256];
545 private void BuildGlyphCache()
547 for (
int i = 0; i < 256; i++)
550 VerticalMetricsCache[i] = this.Get1000EmGlyphVerticalMetrics(
GetGlyphIndex((
char)i));
551 BearingsCache[i] = this.Get1000EmGlyphBearings(
GetGlyphIndex((
char)i));
562 public TrueTypeFile SubsetFont(
string charactersToInclude,
bool consolidateAt32 =
false, Dictionary<char, char> outputEncoding =
null)
564 if (!this.HasCmap4Table())
571 TrueTypeHeadTable head = (TrueTypeHeadTable)this.Tables[
"head"];
573 TrueTypeHHeaTable originalHhea = (TrueTypeHHeaTable)this.Tables[
"hhea"];
574 TrueTypeMaxpTable originalMaxp = (TrueTypeMaxpTable)this.Tables[
"maxp"];
575 TrueTypeCmapTable originalCmap = (TrueTypeCmapTable)this.Tables[
"cmap"];
576 TrueTypeLocaTable originalLoca = (TrueTypeLocaTable)this.Tables[
"loca"];
577 TrueTypeGlyfTable originalGlyf = (TrueTypeGlyfTable)this.Tables[
"glyf"];
579 CmapTable4 cmap4 =
null;
581 int platformSpecificID = -1;
583 for (
int i = 0; i < originalCmap.ActualCmapTables.Length; i++)
585 if (originalCmap.ActualCmapTables[i] !=
null && originalCmap.ActualCmapTables[i].Format == 4 && originalCmap.SubTables[i].PlatformID == 3 && originalCmap.SubTables[i].PlatformSpecificID == 1)
587 cmap4 = (CmapTable4)originalCmap.ActualCmapTables[i];
588 platformId = originalCmap.SubTables[i].PlatformID;
589 platformSpecificID = originalCmap.SubTables[i].PlatformSpecificID;
599 List<int> characterCodes =
new List<int>();
601 for (
int i = 0; i < charactersToInclude.Length; i++)
603 characterCodes.Add((
int)charactersToInclude[i]);
606 characterCodes.Sort();
608 List<(int, int)> segments =
new List<(
int,
int)>();
610 for (
int i = 0; i < characterCodes.Count; i++)
612 if (segments.Count > 0 && segments.Last().Item1 + segments.Last().Item2 == characterCodes[i])
614 segments[segments.Count - 1] = (segments[segments.Count - 1].Item1, segments[segments.Count - 1].Item2 + 1);
618 segments.Add((characterCodes[i], 1));
622 segments.Add((cmap4.StartCode.Last(), cmap4.EndCode.Last() - cmap4.StartCode.Last() + 1));
624 int totalGlyphIndexCount = characterCodes.Count + 1;
626 segments.Sort((a, b) => a.Item1 + a.Item2 - b.Item1 - b.Item2);
628 ushort entrySelector = (ushort)Math.Floor(Math.Log(segments.Count, 2));
631 CmapTable4 newCmap4 =
new CmapTable4()
634 Length = (ushort)(16 + 8 * segments.Count),
635 Language = cmap4.Language,
636 SegCountX2 = (ushort)(2 * segments.Count),
637 SearchRange = (ushort)(2 * (1 << entrySelector)),
638 EntrySelector = entrySelector,
639 RangeShift = (ushort)(2 * segments.Count - (2 * (1 << entrySelector))),
640 EndCode =
new ushort[segments.Count],
641 ReservedPad = cmap4.ReservedPad,
642 StartCode =
new ushort[segments.Count],
643 IdDelta =
new ushort[segments.Count],
644 IdRangeOffset =
new ushort[segments.Count],
645 GlyphIndexArray =
new ushort[0]
648 List<char> includedGlyphs =
new List<char>
653 List<ushort> glyphIndexArray =
new List<ushort>();
655 if (!consolidateAt32)
657 for (
int i = 0; i < segments.Count; i++)
659 newCmap4.EndCode[i] = (ushort)(segments[i].Item1 + segments[i].Item2 - 1);
660 newCmap4.StartCode[i] = (ushort)segments[i].Item1;
662 newCmap4.IdRangeOffset[i] = 0;
664 if (i < segments.Count - 1)
666 newCmap4.IdDelta[i] = (ushort)(includedGlyphs.Count - newCmap4.StartCode[i]);
668 for (
int j = newCmap4.StartCode[i]; j <= newCmap4.EndCode[i]; j++)
670 includedGlyphs.Add((
char)j);
675 newCmap4.IdDelta[i] = 1;
676 newCmap4.IdRangeOffset[i] = 0;
682 for (
int i = 0; i < segments.Count - 1; i++)
684 newCmap4.EndCode[i] = (ushort)(32 + includedGlyphs.Count + segments[i].Item2 - 1);
685 newCmap4.StartCode[i] = (ushort)(32 + includedGlyphs.Count);
686 newCmap4.IdRangeOffset[i] = 0;
688 if (i < segments.Count - 1)
690 newCmap4.IdDelta[i] = (ushort)(includedGlyphs.Count - newCmap4.StartCode[i]);
692 for (
int j = segments[i].Item1; j < segments[i].Item1 + segments[i].Item2; j++)
694 includedGlyphs.Add((
char)j);
699 newCmap4.IdDelta[i] = 1;
700 newCmap4.IdRangeOffset[i] = 0;
705 int i = segments.Count - 1;
706 newCmap4.EndCode[i] = (ushort)(segments[i].Item1 + segments[i].Item2 - 1);
707 newCmap4.StartCode[i] = (ushort)segments[i].Item1;
708 newCmap4.IdRangeOffset[i] = 0;
710 if (i < segments.Count - 1)
712 newCmap4.IdDelta[i] = (ushort)(includedGlyphs.Count - newCmap4.StartCode[i]);
714 for (
int j = newCmap4.StartCode[i]; j <= newCmap4.EndCode[i]; j++)
716 includedGlyphs.Add((
char)j);
721 newCmap4.IdDelta[i] = 1;
722 newCmap4.IdRangeOffset[i] = 0;
726 if (outputEncoding !=
null)
728 for (
int i = 1; i < includedGlyphs.Count; i++)
730 outputEncoding.Add(includedGlyphs[i], (
char)(32 + i));
736 newCmap4.GlyphIndexArray = glyphIndexArray.ToArray();
737 newCmap4.Length += (ushort)(newCmap4.GlyphIndexArray.Length * 2);
739 TrueTypeCmapTable cmap =
new TrueTypeCmapTable() { ActualCmapTables =
new ICmapTable[] { newCmap4 }, NumberSubTables = 1, Version = 0, SubTables =
new CmapSubTable[] {
new CmapSubTable((ushort)platformId, (ushort)platformSpecificID, 12) } };
742 List<Glyph> glyphs =
new List<Glyph>();
743 List<int> originalGlyphIndices =
new List<int>();
745 for (
int i = 0; i < includedGlyphs.Count; i++)
748 originalGlyphIndices.Add(index);
749 glyphs.Add(originalGlyf.Glyphs[index].Clone());
752 for (
int i = 0; i < glyphs.Count; i++)
754 if (glyphs[i] is CompositeGlyph)
756 for (
int j = 0; j < ((CompositeGlyph)glyphs[i]).GlyphIndex.Length; j++)
758 if (originalGlyphIndices.Contains(((CompositeGlyph)glyphs[i]).GlyphIndex[j]))
760 ((CompositeGlyph)glyphs[i]).GlyphIndex[j] = (ushort)originalGlyphIndices.IndexOf(((CompositeGlyph)glyphs[i]).GlyphIndex[j]);
764 originalGlyphIndices.Add(((CompositeGlyph)glyphs[i]).GlyphIndex[j]);
765 glyphs.Add(originalGlyf.Glyphs[((CompositeGlyph)glyphs[i]).GlyphIndex[j]]);
766 ((CompositeGlyph)glyphs[i]).GlyphIndex[j] = (ushort)originalGlyphIndices.IndexOf(((CompositeGlyph)glyphs[i]).GlyphIndex[j]);
772 TrueTypeGlyfTable glyf =
new TrueTypeGlyfTable() { Glyphs = glyphs.ToArray() };
774 TrueTypeLocaTable loca =
new TrueTypeLocaTable(glyphs.Count, head.IndexToLocFormat == 0);
776 for (
int i = 0; i < glyphs.Count + 1; i++)
780 loca.SetOffset(0, 0);
781 loca.Lengths[i] = (uint)(glyphs[i].GetBytes()).Length;
785 if (i < glyphs.Count)
787 loca.Lengths[i] = (uint)(glyphs[i].GetBytes()).Length;
790 loca.SetOffset(i, loca.GetOffset(i - 1) + loca.Lengths[i - 1]);
794 TrueTypeHHeaTable hhea =
new TrueTypeHHeaTable()
796 Version = originalHhea.Version,
797 Ascent = originalHhea.Ascent,
798 Descent = originalHhea.Descent,
799 LineGap = originalHhea.LineGap,
800 AdvanceWidthMax = originalHhea.AdvanceWidthMax,
801 MinLeftSideBearing = originalHhea.MinLeftSideBearing,
802 MinRightSideBearing = originalHhea.MinRightSideBearing,
803 XMaxExtent = originalHhea.XMaxExtent,
804 CaretSlopeRise = originalHhea.CaretSlopeRise,
805 CaretSlopeRun = originalHhea.CaretSlopeRun,
806 CaretOffset = originalHhea.CaretOffset,
807 MetricDataFormat = originalHhea.MetricDataFormat,
808 NumOfLongHorMetrics = (ushort)glyphs.Count
812 LongHorFixed[] metrics =
new LongHorFixed[glyphs.Count];
814 for (
int i = 0; i < glyphs.Count; i++)
816 metrics[i] = this.GetGlyphMetrics(originalGlyphIndices[i]);
819 TrueTypeHmtxTable hmtx =
new TrueTypeHmtxTable()
821 LeftSideBearing =
new short[0],
825 TrueTypeMaxpTable maxp =
new TrueTypeMaxpTable()
827 Version = originalMaxp.Version,
828 NumGlyphs = (ushort)glyphs.Count,
829 MaxPoints = originalMaxp.MaxPoints,
830 MaxContours = originalMaxp.MaxContours,
831 MaxComponentPoints = originalMaxp.MaxComponentPoints,
832 MaxComponentContours = originalMaxp.MaxComponentContours,
833 MaxZones = originalMaxp.MaxZones,
834 MaxTwilightPoints = originalMaxp.MaxTwilightPoints,
835 MaxStorage = originalMaxp.MaxStorage,
836 MaxFunctionDefs = originalMaxp.MaxFunctionDefs,
837 MaxInstructionDefs = originalMaxp.MaxInstructionDefs,
838 MaxStackElements = originalMaxp.MaxStackElements,
839 MaxSizeOfInstructions = originalMaxp.MaxSizeOfInstructions,
840 MaxComponentElements = originalMaxp.MaxComponentElements,
841 MaxComponentDepth = originalMaxp.MaxComponentDepth
844 Dictionary<string, ITrueTypeTable> newTables =
new Dictionary<string, ITrueTypeTable>() {
850 if (Tables.ContainsKey(
"OS/2"))
852 TrueTypeOS2Table originalOS2 = (TrueTypeOS2Table)Tables[
"OS/2"];
854 TrueTypeOS2Table os2 =
new TrueTypeOS2Table()
856 AchVendID = originalOS2.AchVendID,
857 FsSelection = originalOS2.FsSelection,
858 FsType = originalOS2.FsType,
859 Panose = originalOS2.Panose,
860 SCapHeight = originalOS2.SCapHeight,
861 SFamilyClass = originalOS2.SFamilyClass,
862 SFamilySubClass = originalOS2.SFamilySubClass,
863 STypoAscender = originalOS2.STypoAscender,
864 STypoDescender = originalOS2.STypoDescender,
865 STypoLineGap = originalOS2.STypoLineGap,
866 SxHeight = originalOS2.SxHeight,
867 UlCodePageRange = originalOS2.UlCodePageRange,
868 UlUnicodeRange = originalOS2.UlUnicodeRange,
869 UsLowerOpticalPointSize = originalOS2.UsLowerOpticalPointSize,
870 UsBreakChar = originalOS2.UsBreakChar,
871 UsDefaultChar = originalOS2.UsDefaultChar,
872 UsMaxContext = originalOS2.UsMaxContext,
873 UsUpperOpticalPointSize = originalOS2.UsUpperOpticalPointSize,
874 UsWeightClass = originalOS2.UsWeightClass,
875 UsWidthClass = originalOS2.UsWidthClass,
876 UsWinAscent = originalOS2.UsWinAscent,
877 UsWinDescent = originalOS2.UsWinDescent,
878 Version = originalOS2.Version,
879 XAvgCharWidth = originalOS2.XAvgCharWidth,
880 YStrikeoutPosition = originalOS2.YStrikeoutPosition,
881 YStrikeoutSize = originalOS2.YStrikeoutSize,
882 YSubscriptXOffset = originalOS2.YSubscriptXOffset,
883 YSubscriptXSize = originalOS2.YSubscriptXSize,
884 YSubscriptYOffset = originalOS2.YSubscriptYOffset,
885 YSubscriptYSize = originalOS2.YSubscriptYSize,
886 YSuperscriptXOffset = originalOS2.YSuperscriptXOffset,
887 YSuperscriptXSize = originalOS2.YSuperscriptXSize,
888 YSuperscriptYOffset = originalOS2.YSuperscriptYOffset,
889 YSuperscriptYSize = originalOS2.YSuperscriptYSize,
890 FsFirstCharIndex = (ushort)characterCodes[0],
891 FsLastCharIndex = (ushort)characterCodes[characterCodes.Count - 1]
894 newTables.Add(
"OS/2", os2);
897 newTables.Add(
"hmtx", hmtx);
898 newTables.Add(
"cmap", cmap);
899 if (Tables.ContainsKey(
"fpgm"))
901 TrueTypeRawTable fpgm = (TrueTypeRawTable)Tables[
"fpgm"];
902 newTables.Add(
"fpgm", fpgm);
904 if (Tables.ContainsKey(
"prep"))
906 TrueTypeRawTable prep = (TrueTypeRawTable)Tables[
"prep"];
907 newTables.Add(
"prep", prep);
909 if (Tables.ContainsKey(
"cvt "))
911 TrueTypeRawTable cvt = (TrueTypeRawTable)Tables[
"cvt "];
912 newTables.Add(
"cvt ", cvt);
914 newTables.Add(
"loca", loca);
915 newTables.Add(
"glyf", glyf);
917 if (Tables.ContainsKey(
"name"))
919 TrueTypeNameTable name = (TrueTypeNameTable)Tables[
"name"];
920 newTables.Add(
"name", name);
923 if (Tables.ContainsKey(
"post"))
925 TrueTypePostTable oldPost = (TrueTypePostTable)Tables[
"post"];
927 TrueTypePostTable post =
new TrueTypePostTable()
930 Version =
new Fixed(0x00030000, 16),
931 ItalicAngle = oldPost.ItalicAngle,
932 UnderlinePosition = oldPost.UnderlinePosition,
933 UnderlineThickness = oldPost.UnderlineThickness,
935 MinMemType42 = oldPost.MinMemType42,
936 MaxMemType42 = oldPost.MaxMemType42,
937 MinMemType1 = oldPost.MinMemType1,
938 MaxMemType1 = oldPost.MaxMemType1
941 newTables.Add(
"post", post);
951 internal class TrueTypeGlyfTable : ITrueTypeTable
953 public Glyph[] Glyphs;
955 public byte[] GetBytes()
957 using (MemoryStream ms =
new MemoryStream())
959 for (
int i = 0; i < Glyphs.Length; i++)
961 byte[] glyph = Glyphs[i].GetBytes();
962 ms.Write(glyph, 0, glyph.Length);
970 internal abstract class Glyph
972 public short NumberOfContours {
get;
set; }
973 public short XMin {
get;
set; }
974 public short YMin {
get;
set; }
975 public short XMax {
get;
set; }
976 public short YMax {
get;
set; }
978 public abstract byte[] GetBytes();
979 public abstract Glyph Clone();
980 public static Glyph Parse(Stream sr)
982 short numOfContours = sr.ReadShort();
983 if (numOfContours >= 0)
985 return new SimpleGlyph(sr, numOfContours);
989 return new CompositeGlyph(sr, numOfContours);
993 public abstract TrueTypePoint[][]
GetGlyphPath(
double size,
int emSize, Glyph[] glyphCollection);
996 internal class EmptyGlyph : Glyph
998 public override byte[] GetBytes()
1003 public override Glyph Clone()
1005 return new EmptyGlyph();
1008 public override TrueTypePoint[][]
GetGlyphPath(
double size,
int emSize, Glyph[] glyphCollection)
1010 return new TrueTypePoint[0][];
1013 internal class CompositeGlyph : Glyph
1015 public ushort[] Flags {
get;
set; }
1016 public ushort[] GlyphIndex {
get;
set; }
1017 public byte[][] Argument1 {
get;
set; }
1018 public byte[][] Argument2 {
get;
set; }
1019 public byte[][] TransformationOption {
get;
set; }
1020 public ushort NumInstructions {
get;
set; }
1021 public byte[] Instructions {
get;
set; }
1023 private CompositeGlyph() { }
1024 public override Glyph Clone()
1026 CompositeGlyph tbr =
new CompositeGlyph()
1028 NumberOfContours = this.NumberOfContours,
1034 Flags =
new ushort[this.Flags.Length],
1036 GlyphIndex =
new ushort[this.GlyphIndex.Length],
1037 Argument1 =
new byte[this.Argument1.Length][],
1038 Argument2 =
new byte[this.Argument2.Length][],
1039 TransformationOption =
new byte[this.TransformationOption.Length][],
1040 NumInstructions = this.NumInstructions
1042 this.Flags.CopyTo(tbr.Flags, 0);
1044 this.GlyphIndex.CopyTo(tbr.GlyphIndex, 0);
1046 for (
int i = 0; i < this.Argument1.Length; i++)
1048 tbr.Argument1[i] = (
byte[])this.Argument1[i].Clone();
1052 for (
int i = 0; i < this.Argument2.Length; i++)
1054 tbr.Argument2[i] = (
byte[])this.Argument2[i].Clone();
1058 for (
int i = 0; i < this.TransformationOption.Length; i++)
1060 tbr.TransformationOption[i] = (
byte[])this.TransformationOption[i].Clone();
1065 if (this.Instructions !=
null)
1067 tbr.Instructions =
new byte[this.Instructions.Length];
1068 this.Instructions.CopyTo(tbr.Instructions, 0);
1072 tbr.Instructions =
null;
1077 public CompositeGlyph(Stream sr,
short numberOfContours) : base()
1079 this.NumberOfContours = numberOfContours;
1080 this.XMin = sr.ReadShort();
1081 this.YMin = sr.ReadShort();
1082 this.XMax = sr.ReadShort();
1083 this.YMax = sr.ReadShort();
1085 List<ushort> flags =
new List<ushort>();
1086 List<ushort> glyphIndex =
new List<ushort>();
1087 List<byte[]> argument1 =
new List<byte[]>();
1088 List<byte[]> argument2 =
new List<byte[]>();
1089 List<byte[]> transformationOption =
new List<byte[]>();
1091 bool moreComponents =
true;
1093 while (moreComponents)
1095 flags.Add(sr.ReadUShort());
1097 moreComponents = (flags.Last() & 0x0020) != 0;
1098 glyphIndex.Add(sr.ReadUShort());
1100 if ((flags.Last() & 0x0001) != 0)
1102 argument1.Add(
new byte[] { (byte)sr.ReadByte(), (byte)sr.ReadByte() });
1103 argument2.Add(
new byte[] { (byte)sr.ReadByte(), (byte)sr.ReadByte() });
1107 argument1.Add(
new byte[] { (byte)sr.ReadByte() });
1108 argument2.Add(
new byte[] { (byte)sr.ReadByte() });
1111 if ((flags.Last() & 0x0008) != 0)
1113 transformationOption.Add(
new byte[] { (byte)sr.ReadByte(), (byte)sr.ReadByte() });
1115 else if ((flags.Last() & 0x0040) != 0)
1117 transformationOption.Add(
new byte[] { (byte)sr.ReadByte(), (byte)sr.ReadByte(), (byte)sr.ReadByte(), (byte)sr.ReadByte() });
1119 else if ((flags.Last() & 0x0080) != 0)
1121 transformationOption.Add(
new byte[] { (byte)sr.ReadByte(), (byte)sr.ReadByte(), (byte)sr.ReadByte(), (byte)sr.ReadByte(), (byte)sr.ReadByte(), (byte)sr.ReadByte(), (byte)sr.ReadByte(), (byte)sr.ReadByte() });
1125 transformationOption.Add(
new byte[] { });
1129 this.Flags = flags.ToArray();
1130 this.GlyphIndex = glyphIndex.ToArray();
1131 this.Argument1 = argument1.ToArray();
1132 this.Argument2 = argument2.ToArray();
1133 this.TransformationOption = transformationOption.ToArray();
1135 if ((flags.Last() & 0x0100) != 0)
1137 this.NumInstructions = sr.ReadUShort();
1138 this.Instructions =
new byte[this.NumInstructions];
1139 sr.Read(this.Instructions, 0, this.NumInstructions);
1143 this.NumInstructions = 0;
1144 this.Instructions =
null;
1148 public override byte[] GetBytes()
1150 using (MemoryStream ms =
new MemoryStream())
1152 ms.WriteShort(this.NumberOfContours);
1153 ms.WriteShort(this.XMin);
1154 ms.WriteShort(this.YMin);
1155 ms.WriteShort(this.XMax);
1156 ms.WriteShort(this.YMax);
1158 for (
int i = 0; i < this.Flags.Length; i++)
1160 ms.WriteUShort(this.Flags[i]);
1161 ms.WriteUShort(this.GlyphIndex[i]);
1162 ms.Write(this.Argument1[i], 0, this.Argument1[i].Length);
1163 ms.Write(this.Argument2[i], 0, this.Argument2[i].Length);
1164 ms.Write(this.TransformationOption[i], 0, this.TransformationOption[i].Length);
1167 if (this.NumInstructions > 0)
1169 ms.WriteUShort(this.NumInstructions);
1170 ms.Write(this.Instructions, 0, this.NumInstructions);
1173 if (ms.Length % 2 != 0)
1178 return ms.ToArray();
1182 public override TrueTypePoint[][]
GetGlyphPath(
double size,
int emSize, Glyph[] glyphCollection)
1184 List<short> argument1 =
new List<short>();
1185 List<short> argument2 =
new List<short>();
1187 for (
int i = 0; i < this.Argument1.Length; i++)
1189 if (this.Argument1[i].Length == 1)
1191 argument1.Add(this.Argument1[i][0]);
1193 else if (this.Argument1[i].Length == 2)
1195 argument1.Add((
short)((this.Argument1[i][0] << 8) + this.Argument1[i][1]));
1199 for (
int i = 0; i < this.Argument2.Length; i++)
1201 if (this.Argument2[i].Length == 1)
1203 argument2.Add(this.Argument2[i][0]);
1205 else if (this.Argument2[i].Length == 2)
1207 argument2.Add((
short)((this.Argument2[i][0] << 8) + this.Argument2[i][1]));
1211 List<double[]> transformationOption =
new List<double[]>();
1213 for (
int i = 0; i < this.TransformationOption.Length; i++)
1215 if (this.TransformationOption[i].Length == 0)
1217 transformationOption.Add(
new double[] { });
1219 else if (this.TransformationOption[i].Length == 2)
1221 double val = (double)((this.TransformationOption[i][0] << 8) + this.TransformationOption[i][1]) / (1 << 14);
1222 transformationOption.Add(
new double[] { val });
1224 else if (this.TransformationOption[i].Length == 4)
1226 double val1 = (double)((this.TransformationOption[i][0] << 8) + this.TransformationOption[i][1]) / (1 << 14);
1227 double val2 = (double)((this.TransformationOption[i][2] << 8) + this.TransformationOption[i][3]) / (1 << 14);
1228 transformationOption.Add(
new double[] { val1, val2 });
1230 else if (this.TransformationOption[i].Length == 8)
1232 double val1 = (double)((this.TransformationOption[i][0] << 8) + this.TransformationOption[i][1]) / (1 << 14);
1233 double val2 = (double)((this.TransformationOption[i][2] << 8) + this.TransformationOption[i][3]) / (1 << 14);
1234 double val3 = (double)((this.TransformationOption[i][4] << 8) + this.TransformationOption[i][5]) / (1 << 14);
1235 double val4 = (double)((this.TransformationOption[i][6] << 8) + this.TransformationOption[i][7]) / (1 << 14);
1236 transformationOption.Add(
new double[] { val1, val2, val3, val4 });
1240 List<TrueTypePoint[]> tbr =
new List<TrueTypePoint[]>();
1243 for (
int i = 0; i < this.GlyphIndex.Length; i++)
1245 TrueTypePoint[][] componentContours = glyphCollection[this.GlyphIndex[i]].GetGlyphPath(size, emSize, glyphCollection);
1247 double[,] transformMatrix =
new double[,] { { 1, 0 }, { 0, 1 } };
1249 if ((Flags[i] & 0x0008) != 0)
1251 transformMatrix[0, 0] = transformationOption[i][0];
1252 transformMatrix[1, 1] = transformationOption[i][0];
1254 else if ((Flags[i] & 0x0040) != 0)
1256 transformMatrix[0, 0] = transformationOption[i][0];
1257 transformMatrix[1, 1] = transformationOption[i][1];
1259 else if ((Flags[i] & 0x0080) != 0)
1261 transformMatrix[0, 0] = transformationOption[i][0];
1262 transformMatrix[0, 1] = transformationOption[i][1];
1263 transformMatrix[1, 0] = transformationOption[i][2];
1264 transformMatrix[1, 1] = transformationOption[i][3];
1270 if ((Flags[i] & 0x0002) != 0)
1272 deltaX = argument1[i] * size / emSize;
1273 deltaY = argument2[i] * size / emSize;
1275 if ((Flags[i] & 0x0800) != 0 && (Flags[i] & 0x1000) == 0)
1277 deltaX *= Magnitude(Multiply(transformMatrix,
new double[] { 1, 0 }));
1278 deltaY *= Magnitude(Multiply(transformMatrix,
new double[] { 0, 1 }));
1283 TrueTypePoint reference = GetNthElementWhere(tbr, argument1[i], el => el.IsDefinedPoint);
1284 TrueTypePoint destination = GetNthElementWhere(componentContours, argument2[i], el => el.IsDefinedPoint);
1286 deltaX = reference.X - destination.X;
1287 deltaY = reference.Y - destination.Y;
1296 for (
int j = 0; j < componentContours.Length; j++)
1298 for (
int k = 0; k < componentContours[j].Length; k++)
1300 double[] transformed = Multiply(transformMatrix,
new double[] { componentContours[j][k].X, componentContours[j][k].Y });
1302 componentContours[j][k] =
new TrueTypePoint(transformed[0] + deltaX, transformed[1] + deltaY, componentContours[j][k].IsOnCurve, componentContours[j][k].IsDefinedPoint);
1306 tbr.AddRange(componentContours);
1309 return tbr.ToArray();
1313 private static double Magnitude(
double[] vector)
1317 for (
int i = 0; i < vector.Length; i++)
1319 tbr += vector[i] * vector[i];
1322 return Math.Sqrt(tbr);
1325 private static double[] Multiply(
double[,] matrix,
double[] vector)
1327 double[] tbr =
new double[2];
1329 tbr[0] = matrix[0, 0] * vector[0] + matrix[0, 1] * vector[1];
1330 tbr[1] = matrix[1, 0] * vector[0] + matrix[1, 1] * vector[1];
1335 private static T GetNthElementWhere<T>(IEnumerable<IEnumerable<T>> array,
int n, Func<T, bool> condition)
1339 foreach (IEnumerable<T> arr1
in array)
1341 foreach (T el
in arr1)
1354 throw new IndexOutOfRangeException();
1377 internal bool IsDefinedPoint;
1379 internal TrueTypePoint(
double x,
double y,
bool onCurve,
bool isDefinedPoint)
1383 this.IsOnCurve = onCurve;
1384 this.IsDefinedPoint = isDefinedPoint;
1388 internal class SimpleGlyph : Glyph
1390 public ushort[] EndPtsOfContours {
get;
set; }
1391 public ushort InstructionLength {
get;
set; }
1392 public byte[] Instructions {
get;
set; }
1393 public byte[] Flags {
get;
set; }
1394 public byte[] XCoordinates {
get;
set; }
1395 public byte[] YCoordinates {
get;
set; }
1397 private SimpleGlyph() { }
1398 public override Glyph Clone()
1400 SimpleGlyph tbr =
new SimpleGlyph
1402 NumberOfContours = this.NumberOfContours,
1408 EndPtsOfContours =
new ushort[this.EndPtsOfContours.Length],
1409 InstructionLength = this.InstructionLength,
1410 Instructions =
new byte[this.Instructions.Length],
1411 Flags =
new byte[this.Flags.Length],
1412 XCoordinates =
new byte[this.XCoordinates.Length],
1413 YCoordinates =
new byte[this.YCoordinates.Length]
1416 this.EndPtsOfContours.CopyTo(tbr.EndPtsOfContours, 0);
1417 this.Instructions.CopyTo(tbr.Instructions, 0);
1418 this.Flags.CopyTo(tbr.Flags, 0);
1419 this.XCoordinates.CopyTo(tbr.XCoordinates, 0);
1420 this.YCoordinates.CopyTo(tbr.YCoordinates, 0);
1425 public SimpleGlyph(Stream sr,
short numberOfContours) : base()
1427 this.NumberOfContours = numberOfContours;
1428 this.XMin = sr.ReadShort();
1429 this.YMin = sr.ReadShort();
1430 this.XMax = sr.ReadShort();
1431 this.YMax = sr.ReadShort();
1433 this.EndPtsOfContours =
new ushort[this.NumberOfContours];
1434 for (
int i = 0; i < this.NumberOfContours; i++)
1436 this.EndPtsOfContours[i] = sr.ReadUShort();
1439 this.InstructionLength = sr.ReadUShort();
1440 this.Instructions =
new byte[this.InstructionLength];
1441 for (
int i = 0; i < this.InstructionLength; i++)
1443 this.Instructions[i] = (byte)sr.ReadByte();
1446 List<byte> logicalFlags =
new List<byte>();
1447 List<byte> flags =
new List<byte>();
1449 int totalPoints = this.EndPtsOfContours[this.NumberOfContours - 1] + 1;
1451 int countedPoints = 0;
1453 while (countedPoints < totalPoints)
1455 flags.Add((
byte)sr.ReadByte());
1456 logicalFlags.Add(flags.Last());
1458 if ((flags.Last() & 0x08) != 0)
1460 byte repeats = (byte)sr.ReadByte();
1461 for (
int i = 0; i < repeats; i++)
1463 logicalFlags.Add(flags.Last());
1470 this.Flags = flags.ToArray();
1472 List<byte> xCoordinates =
new List<byte>();
1474 for (
int i = 0; i < totalPoints; i++)
1476 bool isByte = (logicalFlags[i] & 0x02) != 0;
1480 xCoordinates.Add((
byte)sr.ReadByte());
1482 else if ((logicalFlags[i] & 0x10) == 0)
1484 xCoordinates.Add((
byte)sr.ReadByte());
1485 xCoordinates.Add((
byte)sr.ReadByte());
1489 this.XCoordinates = xCoordinates.ToArray();
1491 List<byte> yCoordinates =
new List<byte>();
1493 List<int> yCoordinateLengths =
new List<int>();
1495 for (
int i = 0; i < totalPoints; i++)
1497 bool isByte = (logicalFlags[i] & 0x04) != 0;
1501 yCoordinates.Add((
byte)sr.ReadByte());
1502 yCoordinateLengths.Add(1);
1504 else if ((logicalFlags[i] & 0x20) == 0)
1506 yCoordinates.Add((
byte)sr.ReadByte());
1507 yCoordinates.Add((
byte)sr.ReadByte());
1508 yCoordinateLengths.Add(2);
1512 yCoordinateLengths.Add(0);
1516 this.YCoordinates = yCoordinates.ToArray();
1519 public override byte[] GetBytes()
1521 using (MemoryStream ms =
new MemoryStream())
1523 ms.WriteShort(this.NumberOfContours);
1524 ms.WriteShort(this.XMin);
1525 ms.WriteShort(this.YMin);
1526 ms.WriteShort(this.XMax);
1527 ms.WriteShort(this.YMax);
1529 for (
int i = 0; i < this.EndPtsOfContours.Length; i++)
1531 ms.WriteUShort(this.EndPtsOfContours[i]);
1534 ms.WriteUShort(this.InstructionLength);
1535 ms.Write(this.Instructions, 0, this.InstructionLength);
1536 ms.Write(this.Flags, 0, this.Flags.Length);
1537 ms.Write(this.XCoordinates, 0, this.XCoordinates.Length);
1538 ms.Write(this.YCoordinates, 0, this.YCoordinates.Length);
1540 if (ms.Length % 2 != 0)
1545 return ms.ToArray();
1549 public override TrueTypePoint[][]
GetGlyphPath(
double size,
int emSize, Glyph[] glyphCollection)
1551 List<TrueTypePoint[]> contours =
new List<TrueTypePoint[]>();
1553 List<TrueTypePoint> currentContour =
new List<TrueTypePoint>();
1556 List<byte> logicalFlags =
new List<byte>();
1558 int totalPoints = this.EndPtsOfContours[this.NumberOfContours - 1] + 1;
1560 int countedPoints = 0;
1564 while (countedPoints < totalPoints)
1566 logicalFlags.Add(this.Flags[index]);
1569 if ((logicalFlags.Last() & 0x08) != 0)
1571 byte repeats = this.Flags[index];
1573 for (
int i = 0; i < repeats; i++)
1575 logicalFlags.Add(logicalFlags.Last());
1581 List<short> xCoordinates =
new List<short>();
1585 for (
int i = 0; i < totalPoints; i++)
1587 bool isByte = (logicalFlags[i] & 0x02) != 0;
1591 if ((logicalFlags[i] & 0x10) != 0)
1593 xCoordinates.Add(this.XCoordinates[index]);
1597 xCoordinates.Add((
short)(-this.XCoordinates[index]));
1602 else if ((logicalFlags[i] & 0x10) == 0)
1604 xCoordinates.Add((
short)((this.XCoordinates[index] << 8) + this.XCoordinates[index + 1]));
1609 xCoordinates.Add(0);
1613 List<short> yCoordinates =
new List<short>();
1617 for (
int i = 0; i < totalPoints; i++)
1619 bool isByte = (logicalFlags[i] & 0x04) != 0;
1623 if ((logicalFlags[i] & 0x20) != 0)
1625 yCoordinates.Add(this.YCoordinates[index]);
1629 yCoordinates.Add((
short)(-this.YCoordinates[index]));
1633 else if ((logicalFlags[i] & 0x20) == 0)
1635 yCoordinates.Add((
short)((this.YCoordinates[index] << 8) + this.YCoordinates[index + 1]));
1640 yCoordinates.Add(0);
1644 int[] previousPoint =
new int[2] { 0, 0 };
1646 for (
int i = 0; i < totalPoints; i++)
1648 int absoluteX = xCoordinates[i] + previousPoint[0];
1649 int absoluteY = yCoordinates[i] + previousPoint[1];
1651 previousPoint[0] = absoluteX;
1652 previousPoint[1] = absoluteY;
1654 bool onCurve = (logicalFlags[i] & 0x01) != 0;
1658 currentContour.Add(
new TrueTypePoint(size * absoluteX / emSize, size * absoluteY / emSize, onCurve,
true));
1662 if (currentContour.Count > 0)
1664 if (currentContour.Last().IsOnCurve)
1666 currentContour.Add(
new TrueTypePoint(size * absoluteX / emSize, size * absoluteY / emSize, onCurve,
true));
1670 double newX = size * absoluteX / emSize;
1671 double newY = size * absoluteY / emSize;
1673 currentContour.Add(
new TrueTypePoint((newX + currentContour.Last().X) * 0.5, (newY + currentContour.Last().Y) * 0.5,
true,
false));
1674 currentContour.Add(
new TrueTypePoint(newX, newY, onCurve,
true));
1679 currentContour.Add(
new TrueTypePoint(size * absoluteX / emSize, size * absoluteY / emSize, onCurve,
true));
1683 if (this.EndPtsOfContours.Contains((ushort)i))
1685 if (!currentContour[0].IsOnCurve)
1687 if (currentContour.Last().IsOnCurve)
1689 currentContour.Insert(0,
new TrueTypePoint(currentContour.Last().X, currentContour.Last().Y, currentContour.Last().IsOnCurve,
false));
1693 currentContour.Insert(0,
new TrueTypePoint((currentContour[0].X + currentContour.Last().X) * 0.5, (currentContour[0].Y + currentContour.Last().Y) * 0.5,
true,
false));
1697 if (!currentContour.Last().IsOnCurve)
1699 currentContour.Add(
new TrueTypePoint(currentContour[0].X, currentContour[0].Y, currentContour[0].IsOnCurve,
false));
1702 contours.Add(currentContour.ToArray());
1703 currentContour =
new List<TrueTypePoint>();
1707 return contours.ToArray();
1711 internal class TrueTypeLocaTable : ITrueTypeTable
1713 public ushort[] ShortOffsets {
get; }
1714 public uint[] IntOffsets {
get; }
1715 public uint[] Lengths {
get; }
1717 public uint GetOffset(
int index)
1719 if (IntOffsets ==
null)
1721 return (uint)ShortOffsets[index] * 2;
1725 return IntOffsets[index];
1729 public void SetOffset(
int index, uint value)
1731 if (IntOffsets ==
null)
1733 ShortOffsets[index] = (ushort)(value / 2);
1737 IntOffsets[index] = value;
1741 public TrueTypeLocaTable(
int numGlyphs,
bool isShort)
1743 this.Lengths =
new uint[numGlyphs];
1747 this.ShortOffsets =
new ushort[numGlyphs + 1];
1751 this.IntOffsets =
new uint[numGlyphs + 1];
1755 public TrueTypeLocaTable(Stream sr,
int numGlyphs,
bool isShort)
1757 this.Lengths =
new uint[numGlyphs];
1761 this.ShortOffsets =
new ushort[numGlyphs + 1];
1762 for (
int i = 0; i < numGlyphs + 1; i++)
1764 this.ShortOffsets[i] = sr.ReadUShort();
1767 for (
int i = 0; i < numGlyphs; i++)
1769 this.Lengths[i] = 2 * ((uint)this.ShortOffsets[i + 1] - (uint)this.ShortOffsets[i]);
1774 this.IntOffsets =
new uint[numGlyphs + 1];
1775 for (
int i = 0; i < numGlyphs + 1; i++)
1777 this.IntOffsets[i] = sr.ReadUInt();
1780 for (
int i = 0; i < numGlyphs; i++)
1782 this.Lengths[i] = this.IntOffsets[i + 1] - this.IntOffsets[i];
1788 public byte[] GetBytes()
1790 using (MemoryStream ms =
new MemoryStream())
1793 if (IntOffsets ==
null)
1795 for (
int i = 0; i < ShortOffsets.Length; i++)
1797 ms.WriteUShort(ShortOffsets[i]);
1802 for (
int i = 0; i < IntOffsets.Length; i++)
1804 ms.WriteUInt(IntOffsets[i]);
1808 return ms.ToArray();
1813 internal class TrueTypeRawTable : ITrueTypeTable
1815 public byte[] Data {
get; }
1817 public TrueTypeRawTable(Stream sr, uint length)
1819 this.Data =
new byte[length];
1820 sr.Read(this.Data, 0, (
int)length);
1823 public byte[] GetBytes()
1829 internal bool HasCmap4Table()
1831 foreach (ICmapTable cmap
in ((TrueTypeCmapTable)Tables[
"cmap"]).ActualCmapTables)
1833 if (cmap is CmapTable4)
1848 TrueTypeNameTable name = (TrueTypeNameTable)this.Tables[
"name"];
1850 for (
int i = 0; i < name.Count; i++)
1852 if (name.NameRecords[i].NameID == 16)
1854 return name.Name[i];
1858 for (
int i = 0; i < name.Count; i++)
1860 if (name.NameRecords[i].NameID == 1)
1862 return name.Name[i];
1875 TrueTypeNameTable name = (TrueTypeNameTable)this.Tables[
"name"];
1877 for (
int i = 0; i < name.Count; i++)
1879 if (name.NameRecords[i].NameID == 4)
1881 return name.Name[i];
1885 for (
int i = 0; i < name.Count; i++)
1887 if (name.NameRecords[i].NameID == 6)
1889 return name.Name[i];
1903 TrueTypeNameTable name = (TrueTypeNameTable)this.Tables[
"name"];
1905 for (
int i = 0; i < name.Count; i++)
1907 if (name.NameRecords[i].NameID == 6)
1909 return name.Name[i];
1922 TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables[
"OS/2"];
1924 return os2.FsFirstCharIndex;
1933 TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables[
"OS/2"];
1935 return os2.FsLastCharIndex;
1945 TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables[
"OS/2"];
1947 return (os2.FsSelection & 1) == 1;
1956 TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables[
"OS/2"];
1958 return (os2.FsSelection & 512) != 0;
1967 TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables[
"OS/2"];
1969 return (os2.FsSelection & 32) != 0;
1978 TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables[
"OS/2"];
1980 return os2.Panose.BProportion == 9;
1989 TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables[
"OS/2"];
1991 return os2.SFamilyClass == 1 || os2.SFamilyClass == 2 || os2.SFamilyClass == 3 || os2.SFamilyClass == 4 || os2.SFamilyClass == 5 || os2.SFamilyClass == 7;
2000 TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables[
"OS/2"];
2002 return os2.SFamilyClass == 10;
2012 foreach (ICmapTable cmap
in ((TrueTypeCmapTable)Tables[
"cmap"]).ActualCmapTables)
2014 if (cmap is CmapTable4)
2016 return cmap.GetGlyphIndex(glyph);
2020 foreach (ICmapTable cmap
in ((TrueTypeCmapTable)Tables[
"cmap"]).ActualCmapTables)
2022 if (cmap is CmapTable0)
2024 return cmap.GetGlyphIndex(glyph);
2031 internal int GetGlyphWidth(
int glyphIndex)
2033 if (((TrueTypeHmtxTable)this.Tables[
"hmtx"]).HMetrics.Length > glyphIndex)
2035 return ((TrueTypeHmtxTable)this.Tables[
"hmtx"]).HMetrics[glyphIndex].AdvanceWidth;
2039 return ((TrueTypeHmtxTable)this.Tables[
"hmtx"]).HMetrics.Last().AdvanceWidth;
2043 internal LongHorFixed GetGlyphMetrics(
int glyphIndex)
2045 if (((TrueTypeHmtxTable)this.Tables[
"hmtx"]).HMetrics.Length > glyphIndex)
2047 return ((TrueTypeHmtxTable)this.Tables[
"hmtx"]).HMetrics[glyphIndex];
2051 return new LongHorFixed(((TrueTypeHmtxTable)this.Tables[
"hmtx"]).HMetrics.Last().AdvanceWidth, ((TrueTypeHmtxTable)
this.Tables[
"hmtx"]).LeftSideBearing[glyphIndex - ((TrueTypeHmtxTable)
this.Tables[
"hmtx"]).HMetrics.Length]);
2063 return ((TrueTypeGlyfTable)this.Tables[
"glyf"]).Glyphs[glyphIndex].GetGlyphPath(size, ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm, ((TrueTypeGlyfTable)this.Tables[
"glyf"]).Glyphs);
2074 return ((TrueTypeGlyfTable)this.Tables[
"glyf"]).Glyphs[
GetGlyphIndex(glyph)].GetGlyphPath(size, ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm, ((TrueTypeGlyfTable)this.Tables[
"glyf"]).Glyphs);
2084 int num = (int)glyph;
2087 return GlyphWidthsCache[num];
2102 int w = GetGlyphWidth(glyphIndex);
2104 return w * 1000 / ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm;
2113 TrueTypeOS2Table os2 = ((TrueTypeOS2Table)this.Tables[
"OS/2"]);
2115 bool useTypoMetrics = (os2.FsSelection & 128) != 0;
2117 if (!useTypoMetrics)
2119 return ((TrueTypeOS2Table)this.Tables[
"OS/2"]).UsWinAscent * 1000.0 / ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm;
2123 return ((TrueTypeOS2Table)this.Tables[
"OS/2"]).STypoAscender * 1000.0 / ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm;
2133 return ((TrueTypeHHeaTable)this.Tables[
"hhea"]).Ascent * 1000.0 / ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm;
2143 return ((TrueTypeHHeaTable)this.Tables[
"hhea"]).Descent * 1000.0 / ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm;
2152 return ((TrueTypeHeadTable)this.Tables[
"head"]).YMax * 1000.0 / ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm;
2161 return ((TrueTypeHeadTable)this.Tables[
"head"]).YMin * 1000.0 / ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm;
2170 return ((TrueTypeHeadTable)this.Tables[
"head"]).XMax * 1000.0 / ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm;
2179 return ((TrueTypeHeadTable)this.Tables[
"head"]).XMin * 1000.0 / ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm;
2197 internal Bearings(
int lsb,
int rsb)
2204 internal Bearings Get1000EmGlyphBearings(
int glyphIndex)
2206 LongHorFixed metrics = GetGlyphMetrics(glyphIndex);
2208 int lsb = metrics.LeftSideBearing;
2210 Glyph glyph = ((TrueTypeGlyfTable)this.Tables[
"glyf"]).Glyphs[glyphIndex];
2212 int rsb = metrics.AdvanceWidth - (lsb + glyph.XMax - glyph.XMin);
2214 return new Bearings(lsb * 1000 / ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm, rsb * 1000 / ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm);
2225 int num = (int)glyph;
2229 return BearingsCache[num];
2259 internal VerticalMetrics Get1000EmGlyphVerticalMetrics(
int glyphIndex)
2261 Glyph glyph = ((TrueTypeGlyfTable)this.Tables[
"glyf"]).Glyphs[glyphIndex];
2263 return new VerticalMetrics(glyph.YMin * 1000 / ((TrueTypeHeadTable)
this.Tables[
"head"]).UnitsPerEm, glyph.YMax * 1000 / ((TrueTypeHeadTable)
this.Tables[
"head"]).UnitsPerEm);
2273 int num = (int)glyph;
2276 return VerticalMetricsCache[num];
2290 if (this.Tables.TryGetValue(
"post", out ITrueTypeTable table) && table is TrueTypePostTable post)
2292 return post.UnderlinePosition * 1000.0 / ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm;
2306 if (this.Tables.TryGetValue(
"post", out ITrueTypeTable table) && table is TrueTypePostTable post)
2308 return post.UnderlineThickness * 1000.0 / ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm;
2322 if (this.Tables.TryGetValue(
"post", out ITrueTypeTable table) && table is TrueTypePostTable post)
2324 return post.ItalicAngle.Bits / Math.Pow(2, post.ItalicAngle.BitShifts);
2342 List<double> intersections =
new List<double>();
2346 for (
int j = 0; j < glyphPaths.Length; j++)
2348 double[] currPoint =
new double[] { glyphPaths[j][0].
X, -glyphPaths[j][0].
Y };
2350 for (
int k = 1; k < glyphPaths[j].Length; k++)
2352 if (glyphPaths[j][k].IsOnCurve)
2354 if (-glyphPaths[j][k].Y - currPoint[1] != 0)
2356 double t = (position - currPoint[1]) / (-glyphPaths[j][k].Y - currPoint[1]);
2357 if (t >= 0 && t <= 1)
2359 intersections.Add(currPoint[0] + t * (glyphPaths[j][k].X - currPoint[0]));
2362 t = (position + thickness - currPoint[1]) / (-glyphPaths[j][k].Y - currPoint[1]);
2363 if (t >= 0 && t <= 1)
2365 intersections.Add(currPoint[0] + t * (glyphPaths[j][k].X - currPoint[0]));
2370 if (position <= currPoint[1] && position + thickness >= currPoint[1])
2372 intersections.Add(currPoint[0]);
2373 intersections.Add(glyphPaths[j][k].X);
2377 currPoint =
new double[] { glyphPaths[j][k].
X, -glyphPaths[j][k].
Y };
2381 double[] ctrlPoint =
new double[] { glyphPaths[j][k].
X, -glyphPaths[j][k].
Y };
2382 double[] endPoint =
new double[] { glyphPaths[j][k + 1].
X, -glyphPaths[j][k + 1].
Y };
2384 double a = currPoint[1] - 2 * ctrlPoint[1] + endPoint[1];
2386 if (Math.Abs(a) < 1e-7)
2390 if (-glyphPaths[j][k].Y - currPoint[1] != 0)
2392 double t = (position - currPoint[1]) / (-glyphPaths[j][k].Y - currPoint[1]);
2393 if (t >= 0 && t <= 1)
2395 intersections.Add(currPoint[0] + t * (glyphPaths[j][k].X - currPoint[0]));
2398 t = (position + thickness - currPoint[1]) / (-glyphPaths[j][k].Y - currPoint[1]);
2399 if (t >= 0 && t <= 1)
2401 intersections.Add(currPoint[0] + t * (glyphPaths[j][k].X - currPoint[0]));
2406 if (position <= currPoint[1] && position + thickness >= currPoint[1])
2408 intersections.Add(currPoint[0]);
2409 intersections.Add(glyphPaths[j][k].X);
2415 double[] ts =
new double[4];
2417 ts[0] = (currPoint[1] - ctrlPoint[1] - Math.Sqrt(position * a + ctrlPoint[1] * ctrlPoint[1] - currPoint[1] * endPoint[1])) / a;
2418 ts[1] = (currPoint[1] - ctrlPoint[1] + Math.Sqrt(position * a + ctrlPoint[1] * ctrlPoint[1] - currPoint[1] * endPoint[1])) / a;
2420 ts[2] = (currPoint[1] - ctrlPoint[1] - Math.Sqrt((position + thickness) * a + ctrlPoint[1] * ctrlPoint[1] - currPoint[1] * endPoint[1])) / a;
2421 ts[3] = (currPoint[1] - ctrlPoint[1] + Math.Sqrt((position + thickness) * a + ctrlPoint[1] * ctrlPoint[1] - currPoint[1] * endPoint[1])) / a;
2423 double minT =
double.MaxValue;
2424 double maxT =
double.MinValue;
2427 for (
int i = 0; i < 4; i++)
2429 if (!
double.IsNaN(ts[i]) && ts[i] >= 0 && ts[i] <= 1)
2431 minT = Math.Min(minT, ts[i]);
2432 maxT = Math.Max(maxT, ts[i]);
2440 double critT = (currPoint[0] - ctrlPoint[0]) / (currPoint[0] - 2 * ctrlPoint[0] + endPoint[0]);
2442 if (critT >= minT && critT <= maxT)
2444 intersections.Add((1 - critT) * (1 - critT) * currPoint[0] + 2 * critT * (1 - critT) * ctrlPoint[0] + critT * critT * endPoint[0]);
2447 intersections.Add((1 - minT) * (1 - minT) * currPoint[0] + 2 * minT * (1 - minT) * ctrlPoint[0] + minT * minT * endPoint[0]);
2448 intersections.Add((1 - maxT) * (1 - maxT) * currPoint[0] + 2 * maxT * (1 - maxT) * ctrlPoint[0] + maxT * maxT * endPoint[0]);
2454 currPoint = endPoint;
2459 if (intersections.Count > 1)
2461 double[] tbr =
new double[2] {
double.MaxValue,
double.MinValue };
2463 for (
int i = 0; i < intersections.Count; i++)
2465 if (intersections[i] <= tbr[0])
2467 tbr[0] = intersections[i];
2470 if (intersections[i] >= tbr[1])
2472 tbr[1] = intersections[i];
2506 if (this.Tables.TryGetValue(
"GPOS", out ITrueTypeTable table) && table is TrueTypeGPOSTable gpos)
2508 PairKerning kerning = gpos.GetKerning(glyph1Index, glyph2Index);
2510 if (kerning !=
null)
2512 double units = ((TrueTypeHeadTable)this.Tables[
"head"]).UnitsPerEm;
2530 internal interface ITrueTypeTable
2535 internal interface ICmapTable
2537 ushort Format {
get; }
2538 ushort Length {
get; }
2539 ushort Language {
get; }
2546 internal class CmapTable4 : ICmapTable
2548 public ushort Format {
get;
set; }
2549 public ushort Length {
get;
set; }
2550 public ushort Language {
get;
set; }
2551 public ushort SegCountX2 {
get;
set; }
2552 public ushort SearchRange {
get;
set; }
2553 public ushort EntrySelector {
get;
set; }
2554 public ushort RangeShift {
get;
set; }
2555 public ushort[] EndCode {
get;
set; }
2556 public ushort ReservedPad {
get;
set; }
2557 public ushort[] StartCode {
get;
set; }
2558 public ushort[] IdDelta {
get;
set; }
2559 public ushort[] IdRangeOffset {
get;
set; }
2560 public ushort[] GlyphIndexArray {
get;
set; }
2562 public CmapTable4() { }
2564 public CmapTable4(ushort format, ushort length, ushort language, Stream sr)
2566 this.Format = format;
2567 this.Length = length;
2568 this.Language = language;
2569 this.SegCountX2 = sr.ReadUShort();
2571 int segCount = this.SegCountX2 / 2;
2573 this.SearchRange = sr.ReadUShort();
2574 this.EntrySelector = sr.ReadUShort();
2575 this.RangeShift = sr.ReadUShort();
2577 this.EndCode =
new ushort[segCount];
2578 for (
int i = 0; i < segCount; i++)
2580 this.EndCode[i] = sr.ReadUShort();
2583 this.ReservedPad = sr.ReadUShort();
2585 this.StartCode =
new ushort[segCount];
2586 for (
int i = 0; i < segCount; i++)
2588 this.StartCode[i] = sr.ReadUShort();
2591 this.IdDelta =
new ushort[segCount];
2592 for (
int i = 0; i < segCount; i++)
2594 this.IdDelta[i] = sr.ReadUShort();
2597 this.IdRangeOffset =
new ushort[segCount];
2598 for (
int i = 0; i < segCount; i++)
2600 this.IdRangeOffset[i] = sr.ReadUShort();
2603 int numGlyphIndices = (this.Length - 16 + 8 * segCount) / 2;
2605 this.GlyphIndexArray =
new ushort[numGlyphIndices];
2607 for (
int i = 0; i < numGlyphIndices; i++)
2609 this.GlyphIndexArray[i] = sr.ReadUShort();
2615 int code = (int)glyph;
2617 int endCodeInd = -1;
2619 for (
int i = 0; i < EndCode.Length; i++)
2621 if (EndCode[i] >= code)
2628 if (StartCode[endCodeInd] <= code)
2630 if (IdRangeOffset[endCodeInd] != 0)
2632 int glyphIndexIndex = IdRangeOffset[endCodeInd] / 2 + (code - StartCode[endCodeInd]) - (IdRangeOffset.Length - endCodeInd);
2634 if (GlyphIndexArray[glyphIndexIndex] != 0)
2636 return (IdDelta[endCodeInd] + GlyphIndexArray[glyphIndexIndex]) % 65536;
2645 return (code + IdDelta[endCodeInd]) % 65536;
2654 public byte[] GetBytes()
2656 using (MemoryStream ms =
new MemoryStream())
2658 ms.WriteUShort(this.Format);
2659 ms.WriteUShort(this.Length);
2660 ms.WriteUShort(this.Language);
2661 ms.WriteUShort(this.SegCountX2);
2662 ms.WriteUShort(this.SearchRange);
2663 ms.WriteUShort(this.EntrySelector);
2664 ms.WriteUShort(this.RangeShift);
2665 for (
int i = 0; i < this.EndCode.Length; i++)
2667 ms.WriteUShort(this.EndCode[i]);
2669 ms.WriteUShort(this.ReservedPad);
2671 for (
int i = 0; i < this.StartCode.Length; i++)
2673 ms.WriteUShort(this.StartCode[i]);
2675 for (
int i = 0; i < this.IdDelta.Length; i++)
2677 ms.WriteUShort(this.IdDelta[i]);
2679 for (
int i = 0; i < this.IdRangeOffset.Length; i++)
2681 ms.WriteUShort(this.IdRangeOffset[i]);
2683 for (
int i = 0; i < this.GlyphIndexArray.Length; i++)
2685 ms.WriteUShort(this.GlyphIndexArray[i]);
2688 return ms.ToArray();
2693 internal class CmapTable0 : ICmapTable
2695 public ushort Format {
get; }
2696 public ushort Length {
get; }
2697 public ushort Language {
get; }
2699 public byte[] GlyphIndexArray {
get; }
2701 public CmapTable0(ushort format, ushort length, ushort language,
byte[] glyphIndexArray)
2703 this.Format = format;
2704 this.Length = length;
2705 this.Language = language;
2706 this.GlyphIndexArray = glyphIndexArray;
2711 return GlyphIndexArray[(byte)glyph];
2714 public byte[] GetBytes()
2716 using (MemoryStream ms =
new MemoryStream())
2718 ms.WriteUShort(this.Format);
2719 ms.WriteUShort(this.Length);
2720 ms.WriteUShort(this.Language);
2721 ms.Write(this.GlyphIndexArray, 0, this.GlyphIndexArray.Length);
2722 return ms.ToArray();
2727 internal struct CmapSubTable
2729 public ushort PlatformID;
2730 public ushort PlatformSpecificID;
2733 public CmapSubTable(ushort platformID, ushort platformSpecificID, uint offset)
2735 this.PlatformID = platformID;
2736 this.PlatformSpecificID = platformSpecificID;
2737 this.Offset = offset;
2741 internal class TrueTypeCmapTable : ITrueTypeTable
2743 public ushort Version;
2744 public ushort NumberSubTables;
2745 public CmapSubTable[] SubTables;
2746 public ICmapTable[] ActualCmapTables;
2748 public byte[] GetBytes()
2750 using (MemoryStream ms =
new MemoryStream())
2752 ms.WriteUShort(this.Version);
2753 ms.WriteUShort(this.NumberSubTables);
2755 for (
int i = 0; i < this.SubTables.Length; i++)
2757 ms.WriteUShort(this.SubTables[i].PlatformID);
2758 ms.WriteUShort(this.SubTables[i].PlatformSpecificID);
2759 ms.WriteUInt(this.SubTables[i].Offset);
2762 for (
int i = 0; i < this.ActualCmapTables.Length; i++)
2764 byte[] bytes = this.ActualCmapTables[i].GetBytes();
2765 ms.Write(bytes, 0, bytes.Length);
2768 return ms.ToArray();
2773 internal class TrueTypeNameTable : ITrueTypeTable
2775 public byte[] GetBytes()
2777 using (MemoryStream ms =
new MemoryStream())
2779 ms.WriteUShort(this.Format);
2780 ms.WriteUShort(this.Count);
2781 ms.WriteUShort(this.StringOffset);
2783 for (
int i = 0; i < this.NameRecords.Length; i++)
2785 byte[] bytes = this.NameRecords[i].GetBytes();
2786 ms.Write(bytes, 0, bytes.Length);
2789 ms.Write(this.RawBytes, 0, this.RawBytes.Length);
2791 return ms.ToArray();
2795 public ushort Format;
2796 public ushort Count;
2797 public ushort StringOffset;
2798 public NameRecord[] NameRecords;
2799 public string[] Name;
2800 private readonly
byte[] RawBytes;
2802 internal struct NameRecord
2804 public ushort PlatformID;
2805 public ushort PlatformSpecificID;
2806 public ushort LanguageID;
2807 public ushort NameID;
2808 public ushort Length;
2809 public ushort Offset;
2811 public NameRecord(ushort platformID, ushort platformSpecificID, ushort languageID, ushort nameID, ushort length, ushort offset)
2813 this.PlatformID = platformID;
2814 this.PlatformSpecificID = platformSpecificID;
2815 this.LanguageID = languageID;
2816 this.NameID = nameID;
2817 this.Length = length;
2818 this.Offset = offset;
2821 public byte[] GetBytes()
2823 using (MemoryStream ms =
new MemoryStream())
2825 ms.WriteUShort(this.PlatformID);
2826 ms.WriteUShort(this.PlatformSpecificID);
2827 ms.WriteUShort(this.LanguageID);
2828 ms.WriteUShort(this.NameID);
2829 ms.WriteUShort(this.Length);
2830 ms.WriteUShort(this.Offset);
2831 return ms.ToArray();
2836 internal TrueTypeNameTable(uint tableOffset, Stream sr)
2838 this.Format = sr.ReadUShort();
2839 this.Count = sr.ReadUShort();
2840 this.StringOffset = sr.ReadUShort();
2841 this.NameRecords =
new NameRecord[this.Count];
2842 this.Name =
new string[this.Count];
2844 int maxOffsetItem = -1;
2846 for (
int i = 0; i < this.Count; i++)
2848 this.NameRecords[i] =
new NameRecord(sr.ReadUShort(), sr.ReadUShort(), sr.ReadUShort(), sr.ReadUShort(), sr.ReadUShort(), sr.ReadUShort());
2849 if (maxOffsetItem < 0 || this.NameRecords[i].Offset > this.NameRecords[maxOffsetItem].Offset || (this.NameRecords[i].Offset == this.NameRecords[maxOffsetItem].Offset && this.NameRecords[i].Length > this.NameRecords[maxOffsetItem].Length))
2855 this.RawBytes =
new byte[this.NameRecords[maxOffsetItem].Offset + this.NameRecords[maxOffsetItem].Length];
2856 sr.Seek(tableOffset + this.StringOffset, SeekOrigin.Begin);
2857 sr.Read(this.RawBytes, 0, this.RawBytes.Length);
2859 for (
int i = 0; i < this.Count; i++)
2861 sr.Seek(tableOffset + this.NameRecords[i].Offset + this.StringOffset, SeekOrigin.Begin);
2862 byte[] stringBytes =
new byte[this.NameRecords[i].Length];
2863 sr.Read(stringBytes, 0, this.NameRecords[i].Length);
2865 if (this.NameRecords[i].PlatformID == 0)
2867 this.Name[i] = Encoding.BigEndianUnicode.GetString(stringBytes);
2869 else if (this.NameRecords[i].PlatformID == 1 && this.NameRecords[i].PlatformSpecificID == 0)
2871 this.Name[i] = GetMacRomanString(stringBytes);
2873 else if (this.NameRecords[i].PlatformID == 3 && (this.NameRecords[i].PlatformSpecificID == 1 || this.NameRecords[i].PlatformSpecificID == 0))
2875 this.Name[i] = Encoding.BigEndianUnicode.GetString(stringBytes);
2879 this.Name[i] =
"Unsupported encoding: " + this.NameRecords[i].PlatformID.ToString() +
"/" + this.NameRecords[i].PlatformSpecificID.ToString();
2885 private static readonly
char[] MacRomanChars =
new char[] {
'\u0020',
'\u0021',
'\u0022',
'\u0023',
'\u0024',
'\u0025',
'\u0026',
'\u0027',
'\u0028',
'\u0029',
'\u002a',
'\u002b',
'\u002c',
'\u002d',
'\u002e',
'\u002f',
'\u0030',
'\u0031',
'\u0032',
'\u0033',
'\u0034',
'\u0035',
'\u0036',
'\u0037',
'\u0038',
'\u0039',
'\u003a',
'\u003b',
'\u003c',
'\u003d',
'\u003e',
'\u003f',
'\u0040',
'\u0041',
'\u0042',
'\u0043',
'\u0044',
'\u0045',
'\u0046',
'\u0047',
'\u0048',
'\u0049',
'\u004a',
'\u004b',
'\u004c',
'\u004d',
'\u004e',
'\u004f',
'\u0050',
'\u0051',
'\u0052',
'\u0053',
'\u0054',
'\u0055',
'\u0056',
'\u0057',
'\u0058',
'\u0059',
'\u005a',
'\u005b',
'\u005c',
'\u005d',
'\u005e',
'\u005f',
'\u0060',
'\u0061',
'\u0062',
'\u0063',
'\u0064',
'\u0065',
'\u0066',
'\u0067',
'\u0068',
'\u0069',
'\u006a',
'\u006b',
'\u006c',
'\u006d',
'\u006e',
'\u006f',
'\u0070',
'\u0071',
'\u0072',
'\u0073',
'\u0074',
'\u0075',
'\u0076',
'\u0077',
'\u0078',
'\u0079',
'\u007a',
'\u007b',
'\u007c',
'\u007d',
'\u007e',
'\u007f',
'\u00c4',
'\u00c5',
'\u00c7',
'\u00c9',
'\u00d1',
'\u00d6',
'\u00dc',
'\u00e1',
'\u00e0',
'\u00e2',
'\u00e4',
'\u00e3',
'\u00e5',
'\u00e7',
'\u00e9',
'\u00e8',
'\u00ea',
'\u00eb',
'\u00ed',
'\u00ec',
'\u00ee',
'\u00ef',
'\u00f1',
'\u00f3',
'\u00f2',
'\u00f4',
'\u00f6',
'\u00f5',
'\u00fa',
'\u00f9',
'\u00fb',
'\u00fc',
'\u2020',
'\u00b0',
'\u00a2',
'\u00a3',
'\u00a7',
'\u2022',
'\u00b6',
'\u00df',
'\u00ae',
'\u00a9',
'\u2122',
'\u00b4',
'\u00a8',
'\u2260',
'\u00c6',
'\u00d8',
'\u221e',
'\u00b1',
'\u2264',
'\u2265',
'\u00a5',
'\u00b5',
'\u2202',
'\u2211',
'\u220f',
'\u03c0',
'\u222b',
'\u00aa',
'\u00ba',
'\u03a9',
'\u00e6',
'\u00f8',
'\u00bf',
'\u00a1',
'\u00ac',
'\u221a',
'\u0192',
'\u2248',
'\u2206',
'\u00ab',
'\u00bb',
'\u2026',
'\u00a0',
'\u00c0',
'\u00c3',
'\u00d5',
'\u0152',
'\u0153',
'\u2013',
'\u2014',
'\u201c',
'\u201d',
'\u2018',
'\u2019',
'\u00f7',
'\u25ca',
'\u00ff',
'\u0178',
'\u2044',
'\u20ac',
'\u2039',
'\u203a',
'\ufb01',
'\ufb02',
'\u2021',
'\u00b7',
'\u201a',
'\u201e',
'\u2030',
'\u00c2',
'\u00ca',
'\u00c1',
'\u00cb',
'\u00c8',
'\u00cd',
'\u00ce',
'\u00cf',
'\u00cc',
'\u00d3',
'\u00d4',
'\uf8ff',
'\u00d2',
'\u00da',
'\u00db',
'\u00d9',
'\u0131',
'\u02c6',
'\u02dc',
'\u00af',
'\u02d8',
'\u02d9',
'\u02da',
'\u00b8',
'\u02dd',
'\u02db',
'\u02c7' };
2887 private static string GetMacRomanString(
byte[] bytes)
2889 StringBuilder bld =
new StringBuilder(bytes.Length);
2891 for (
int i = 0; i < bytes.Length; i++)
2895 bld.Append(MacRomanChars[bytes[i] - 32]);
2899 bld.Append((
char)bytes[i]);
2903 return bld.ToString();
2908 internal class TrueTypeOS2Table : ITrueTypeTable
2910 public ushort Version;
2911 public short XAvgCharWidth;
2912 public ushort UsWeightClass;
2913 public ushort UsWidthClass;
2914 public short FsType;
2915 public short YSubscriptXSize;
2916 public short YSubscriptYSize;
2917 public short YSubscriptXOffset;
2918 public short YSubscriptYOffset;
2919 public short YSuperscriptXSize;
2920 public short YSuperscriptYSize;
2921 public short YSuperscriptXOffset;
2922 public short YSuperscriptYOffset;
2923 public short YStrikeoutSize;
2924 public short YStrikeoutPosition;
2925 public byte SFamilyClass;
2926 public byte SFamilySubClass;
2927 public PANOSE Panose;
2928 public uint[] UlUnicodeRange;
2929 public byte[] AchVendID;
2930 public ushort FsSelection;
2931 public ushort FsFirstCharIndex;
2932 public ushort FsLastCharIndex;
2933 public short STypoAscender;
2934 public short STypoDescender;
2935 public short STypoLineGap;
2936 public ushort UsWinAscent;
2937 public ushort UsWinDescent;
2939 public uint[] UlCodePageRange;
2941 public short SxHeight;
2942 public short SCapHeight;
2943 public ushort UsDefaultChar;
2944 public ushort UsBreakChar;
2945 public ushort UsMaxContext;
2947 public ushort UsLowerOpticalPointSize;
2948 public ushort UsUpperOpticalPointSize;
2950 public byte[] GetBytes()
2952 using (MemoryStream ms =
new MemoryStream())
2954 ms.WriteUShort(this.Version);
2955 ms.WriteShort(this.XAvgCharWidth);
2956 ms.WriteUShort(this.UsWeightClass);
2957 ms.WriteUShort(this.UsWidthClass);
2958 ms.WriteShort(this.FsType);
2959 ms.WriteShort(this.YSubscriptXSize);
2960 ms.WriteShort(this.YSubscriptYSize);
2961 ms.WriteShort(this.YSubscriptXOffset);
2962 ms.WriteShort(this.YSubscriptYOffset);
2963 ms.WriteShort(this.YSuperscriptXSize);
2964 ms.WriteShort(this.YSuperscriptYSize);
2965 ms.WriteShort(this.YSuperscriptXOffset);
2966 ms.WriteShort(this.YSuperscriptYOffset);
2967 ms.WriteShort(this.YStrikeoutSize);
2968 ms.WriteShort(this.YStrikeoutPosition);
2969 ms.WriteByte(this.SFamilyClass);
2970 ms.WriteByte(this.SFamilySubClass);
2971 ms.Write(this.Panose.GetBytes(), 0, 10);
2972 for (
int i = 0; i < this.UlUnicodeRange.Length; i++)
2974 ms.WriteUInt(this.UlUnicodeRange[i]);
2976 ms.Write(this.AchVendID, 0, this.AchVendID.Length);
2977 ms.WriteUShort(this.FsSelection);
2978 ms.WriteUShort(this.FsFirstCharIndex);
2979 ms.WriteUShort(this.FsLastCharIndex);
2980 ms.WriteShort(this.STypoAscender);
2981 ms.WriteShort(this.STypoDescender);
2982 ms.WriteShort(this.STypoLineGap);
2983 ms.WriteUShort(this.UsWinAscent);
2984 ms.WriteUShort(this.UsWinDescent);
2986 if (this.Version >= 1)
2988 for (
int i = 0; i < this.UlCodePageRange.Length; i++)
2990 ms.WriteUInt(this.UlCodePageRange[i]);
2993 if (this.Version >= 2)
2995 ms.WriteShort(this.SxHeight);
2996 ms.WriteShort(this.SCapHeight);
2997 ms.WriteUShort(this.UsDefaultChar);
2998 ms.WriteUShort(this.UsBreakChar);
2999 ms.WriteUShort(this.UsMaxContext);
3001 if (this.Version >= 5)
3003 ms.WriteUShort(this.UsLowerOpticalPointSize);
3004 ms.WriteUShort(this.UsUpperOpticalPointSize);
3009 return ms.ToArray();
3013 internal struct PANOSE
3015 public byte BFamilyType;
3016 public byte BSerifStyle;
3017 public byte BWeight;
3018 public byte BProportion;
3019 public byte BContrast;
3020 public byte BStrokeVariation;
3021 public byte BArmStyle;
3022 public byte BLetterform;
3023 public byte BMidline;
3024 public byte BXHeight;
3026 public PANOSE(
byte bFamilyType,
byte bSerifStyle,
byte bWeight,
byte bProportion,
byte bContrast,
byte bStrokeVariation,
byte bArmStyle,
byte bLetterform,
byte bMidline,
byte bXHeight)
3028 this.BFamilyType = bFamilyType;
3029 this.BSerifStyle = bSerifStyle;
3030 this.BWeight = bWeight;
3031 this.BProportion = bProportion;
3032 this.BContrast = bContrast;
3033 this.BStrokeVariation = bStrokeVariation;
3034 this.BArmStyle = bArmStyle;
3035 this.BLetterform = bLetterform;
3036 this.BMidline = bMidline;
3037 this.BXHeight = bXHeight;
3040 public byte[] GetBytes()
3042 return new byte[] { BFamilyType, BSerifStyle, BWeight, BProportion, BContrast, BStrokeVariation, BArmStyle, BLetterform, BMidline, BXHeight };
3047 internal class TrueTypePostTable : ITrueTypeTable
3049 public Fixed Version;
3050 public Fixed ItalicAngle;
3051 public short UnderlinePosition;
3052 public short UnderlineThickness;
3054 public uint MinMemType42;
3055 public uint MaxMemType42;
3056 public uint MinMemType1;
3057 public uint MaxMemType1;
3059 public ushort NumGlyphs;
3060 public ushort[] GlyphNameIndex;
3061 public string[] Names;
3063 public byte[] GetBytes()
3065 using (MemoryStream ms =
new MemoryStream())
3067 ms.WriteFixed(this.Version);
3068 ms.WriteFixed(this.ItalicAngle);
3069 ms.WriteShort(this.UnderlinePosition);
3070 ms.WriteShort(this.UnderlineThickness);
3071 ms.WriteUInt(this.IsFixedPitch);
3072 ms.WriteUInt(this.MinMemType42);
3073 ms.WriteUInt(this.MaxMemType42);
3074 ms.WriteUInt(this.MinMemType1);
3075 ms.WriteUInt(this.MaxMemType1);
3077 if (this.Version.Bits == 0x00020000)
3079 ms.WriteUShort(NumGlyphs);
3080 for (
int i = 0; i < GlyphNameIndex.Length; i++)
3082 ms.WriteUShort(GlyphNameIndex[i]);
3085 for (
int i = 0; i < Names.Length; i++)
3087 ms.WritePascalString(Names[i]);
3091 return ms.ToArray();
3096 internal class TrueTypeHmtxTable : ITrueTypeTable
3098 public LongHorFixed[] HMetrics;
3099 public short[] LeftSideBearing;
3101 public byte[] GetBytes()
3103 using (MemoryStream ms =
new MemoryStream())
3105 for (
int i = 0; i < HMetrics.Length; i++)
3107 ms.WriteUShort(HMetrics[i].AdvanceWidth);
3108 ms.WriteShort(HMetrics[i].LeftSideBearing);
3110 for (
int i = 0; i < LeftSideBearing.Length; i++)
3112 ms.WriteShort(LeftSideBearing[i]);
3115 return ms.ToArray();
3120 internal class TrueTypeMaxpTable : ITrueTypeTable
3122 public Fixed Version;
3123 public ushort NumGlyphs;
3124 public ushort MaxPoints;
3125 public ushort MaxContours;
3126 public ushort MaxComponentPoints;
3127 public ushort MaxComponentContours;
3128 public ushort MaxZones;
3129 public ushort MaxTwilightPoints;
3130 public ushort MaxStorage;
3131 public ushort MaxFunctionDefs;
3132 public ushort MaxInstructionDefs;
3133 public ushort MaxStackElements;
3134 public ushort MaxSizeOfInstructions;
3135 public ushort MaxComponentElements;
3136 public ushort MaxComponentDepth;
3138 public byte[] GetBytes()
3140 using (MemoryStream ms =
new MemoryStream())
3142 ms.WriteFixed(this.Version);
3143 ms.WriteUShort(this.NumGlyphs);
3144 ms.WriteUShort(this.MaxPoints);
3145 ms.WriteUShort(this.MaxContours);
3146 ms.WriteUShort(this.MaxComponentPoints);
3147 ms.WriteUShort(this.MaxComponentContours);
3148 ms.WriteUShort(this.MaxZones);
3149 ms.WriteUShort(this.MaxTwilightPoints);
3150 ms.WriteUShort(this.MaxStorage);
3151 ms.WriteUShort(this.MaxFunctionDefs);
3152 ms.WriteUShort(this.MaxInstructionDefs);
3153 ms.WriteUShort(this.MaxStackElements);
3154 ms.WriteUShort(this.MaxSizeOfInstructions);
3155 ms.WriteUShort(this.MaxComponentElements);
3156 ms.WriteUShort(this.MaxComponentDepth);
3157 return ms.ToArray();
3162 internal class TrueTypeHeadTable : ITrueTypeTable
3164 public Fixed Version;
3165 public Fixed FontRevision;
3166 public uint ChecksumAdjustment;
3167 public uint MagicNumber;
3168 public ushort Flags;
3169 public ushort UnitsPerEm;
3170 public DateTime Created;
3171 public DateTime Modified;
3176 public ushort MacStyle;
3177 public ushort LowestRecPPEM;
3178 public short FontDirectionInt;
3179 public short IndexToLocFormat;
3180 public short GlyphDataFormat;
3182 public byte[] GetBytes()
3184 using (MemoryStream ms =
new MemoryStream())
3186 ms.WriteFixed(this.Version);
3187 ms.WriteFixed(this.FontRevision);
3188 ms.WriteUInt(this.ChecksumAdjustment);
3189 ms.WriteUInt(this.MagicNumber);
3190 ms.WriteUShort(this.Flags);
3191 ms.WriteUShort(this.UnitsPerEm);
3192 ms.WriteDate(this.Created);
3193 ms.WriteDate(this.Modified);
3194 ms.WriteShort(this.XMin);
3195 ms.WriteShort(this.YMin);
3196 ms.WriteShort(this.XMax);
3197 ms.WriteShort(this.YMax);
3198 ms.WriteUShort(this.MacStyle);
3199 ms.WriteUShort(this.LowestRecPPEM);
3200 ms.WriteShort(this.FontDirectionInt);
3201 ms.WriteShort(this.IndexToLocFormat);
3202 ms.WriteShort(this.GlyphDataFormat);
3203 return ms.ToArray();
3209 internal class TrueTypeHHeaTable : ITrueTypeTable
3211 public Fixed Version;
3212 public short Ascent;
3213 public short Descent;
3214 public short LineGap;
3215 public ushort AdvanceWidthMax;
3216 public short MinLeftSideBearing;
3217 public short MinRightSideBearing;
3218 public short XMaxExtent;
3219 public short CaretSlopeRise;
3220 public short CaretSlopeRun;
3221 public short CaretOffset;
3222 public short MetricDataFormat;
3223 public ushort NumOfLongHorMetrics;
3225 public byte[] GetBytes()
3227 using (MemoryStream ms =
new MemoryStream())
3229 ms.WriteFixed(this.Version);
3230 ms.WriteShort(this.Ascent);
3231 ms.WriteShort(this.Descent);
3232 ms.WriteShort(this.LineGap);
3233 ms.WriteUShort(this.AdvanceWidthMax);
3234 ms.WriteShort(this.MinLeftSideBearing);
3235 ms.WriteShort(this.MinRightSideBearing);
3236 ms.WriteShort(this.XMaxExtent);
3237 ms.WriteShort(this.CaretSlopeRise);
3238 ms.WriteShort(this.CaretSlopeRun);
3239 ms.WriteShort(this.CaretOffset);
3244 ms.WriteShort(this.MetricDataFormat);
3245 ms.WriteUShort(this.NumOfLongHorMetrics);
3246 return ms.ToArray();
3252 internal class LangSysTable
3255 public ushort LookupOrderOffset;
3256 public ushort RequiredFeatureIndex;
3257 public ushort[] FeatureIndices;
3259 public LangSysTable(
string tag, Stream fs)
3262 this.LookupOrderOffset = fs.ReadUShort();
3263 this.RequiredFeatureIndex = fs.ReadUShort();
3264 ushort featureIndexCount = fs.ReadUShort();
3266 this.FeatureIndices =
new ushort[featureIndexCount];
3268 for (
int i = 0; i < featureIndexCount; i++)
3270 this.FeatureIndices[i] = fs.ReadUShort();
3274 public byte[] GetBytes()
3276 using (MemoryStream ms =
new MemoryStream(6 + this.FeatureIndices.Length * 2))
3278 ms.WriteUShort(this.LookupOrderOffset);
3279 ms.WriteUShort(this.RequiredFeatureIndex);
3280 ms.WriteUShort((ushort)this.FeatureIndices.Length);
3281 for (
int i = 0; i < this.FeatureIndices.Length; i++)
3283 ms.WriteUShort(this.FeatureIndices[i]);
3286 return ms.ToArray();
3291 internal class ScriptTable
3294 public LangSysTable DefaultLangSys;
3295 public LangSysTable[] LangSysRecords;
3297 public ScriptTable(
string tag, Stream fs)
3301 long startPosition = fs.Position;
3303 ushort defaultLangSysOffset = fs.ReadUShort();
3304 ushort langSysCount = fs.ReadUShort();
3306 (string, ushort)[] langSysRecords =
new (
string, ushort)[langSysCount];
3308 for (
int i = 0; i < langSysCount; i++)
3310 StringBuilder langSysTag =
new StringBuilder(4);
3311 langSysTag.Append((
char)fs.ReadByte());
3312 langSysTag.Append((
char)fs.ReadByte());
3313 langSysTag.Append((
char)fs.ReadByte());
3314 langSysTag.Append((
char)fs.ReadByte());
3316 langSysRecords[i] = (langSysTag.ToString(), fs.ReadUShort());
3319 if (defaultLangSysOffset != 0)
3321 fs.Seek(startPosition + defaultLangSysOffset, SeekOrigin.Begin);
3322 this.DefaultLangSys =
new LangSysTable(
null, fs);
3326 this.DefaultLangSys =
null;
3329 this.LangSysRecords =
new LangSysTable[langSysCount];
3331 for (
int i = 0; i < langSysCount; i++)
3333 fs.Seek(startPosition + langSysRecords[i].Item2, SeekOrigin.Begin);
3334 this.LangSysRecords[i] =
new LangSysTable(langSysRecords[i].Item1, fs);
3338 public byte[] GetBytes()
3340 using (MemoryStream ms =
new MemoryStream())
3342 int offset = 6 * this.LangSysRecords.Length + 2 + 2;
3344 if (this.DefaultLangSys !=
null)
3346 ms.WriteUShort((ushort)offset);
3353 ms.WriteUShort((ushort)this.LangSysRecords.Length);
3355 byte[] defaultLangSysTable = DefaultLangSys !=
null ? DefaultLangSys.GetBytes() :
new byte[0];
3357 byte[][] langSysTables =
new byte[this.LangSysRecords.Length][];
3359 offset += defaultLangSysTable.Length;
3361 int[] offsets =
new int[this.LangSysRecords.Length];
3363 for (
int i = 0; i < this.LangSysRecords.Length; i++)
3365 offsets[i] = offset;
3366 langSysTables[i] = this.LangSysRecords[i].GetBytes();
3367 offset += langSysTables[i].Length;
3370 for (
int i = 0; i < this.LangSysRecords.Length; i++)
3372 ms.WriteByte((
byte)this.LangSysRecords[i].Tag[0]);
3373 ms.WriteByte((
byte)this.LangSysRecords[i].Tag[1]);
3374 ms.WriteByte((
byte)this.LangSysRecords[i].Tag[2]);
3375 ms.WriteByte((
byte)this.LangSysRecords[i].Tag[3]);
3377 ms.WriteUShort((ushort)offsets[i]);
3380 ms.Write(defaultLangSysTable, 0, defaultLangSysTable.Length);
3382 for (
int i = 0; i < langSysTables[i].Length; i++)
3384 ms.Write(langSysTables[i], 0, langSysTables[i].Length);
3387 return ms.ToArray();
3393 internal class ScriptList
3395 public ScriptTable[] ScriptRecords;
3396 public ScriptList(Stream fs)
3398 long startPosition = fs.Position;
3400 ushort scriptCount = fs.ReadUShort();
3402 (string, ushort)[] scriptRecords =
new (
string, ushort)[scriptCount];
3404 for (
int i = 0; i < scriptCount; i++)
3406 StringBuilder scriptTag =
new StringBuilder(4);
3407 scriptTag.Append((
char)fs.ReadByte());
3408 scriptTag.Append((
char)fs.ReadByte());
3409 scriptTag.Append((
char)fs.ReadByte());
3410 scriptTag.Append((
char)fs.ReadByte());
3412 scriptRecords[i] = (scriptTag.ToString(), fs.ReadUShort());
3415 this.ScriptRecords =
new ScriptTable[scriptCount];
3417 for (
int i = 0; i < scriptCount; i++)
3419 fs.Seek(startPosition + scriptRecords[i].Item2, SeekOrigin.Begin);
3420 this.ScriptRecords[i] =
new ScriptTable(scriptRecords[i].Item1, fs);
3424 public byte[] GetBytes()
3426 using (MemoryStream ms =
new MemoryStream())
3428 int offset = 2 + 6 * this.ScriptRecords.Length;
3430 ms.WriteUShort((ushort)this.ScriptRecords.Length);
3432 byte[][] scripts =
new byte[this.ScriptRecords.Length][];
3433 int[] offsets =
new int[this.ScriptRecords.Length];
3435 for (
int i = 0; i < this.ScriptRecords.Length; i++)
3437 offsets[i] = offset;
3438 scripts[i] = this.ScriptRecords[i].GetBytes();
3439 offset += scripts[i].Length;
3442 for (
int i = 0; i < this.ScriptRecords.Length; i++)
3444 ms.WriteByte((
byte)this.ScriptRecords[i].Tag[0]);
3445 ms.WriteByte((
byte)this.ScriptRecords[i].Tag[1]);
3446 ms.WriteByte((
byte)this.ScriptRecords[i].Tag[2]);
3447 ms.WriteByte((
byte)this.ScriptRecords[i].Tag[3]);
3449 ms.WriteUShort((ushort)offsets[i]);
3452 for (
int i = 0; i < this.ScriptRecords.Length; i++)
3454 ms.Write(scripts[i], 0, scripts[i].Length);
3457 return ms.ToArray();
3462 internal class LookupTable
3464 public ushort LookupType;
3465 public ushort LookupFlag;
3466 public ITrueTypeTable[] SubTables;
3467 public ushort MarkFilteringSet;
3469 public LookupTable(Stream fs)
3471 long startPosition = fs.Position;
3473 this.LookupType = fs.ReadUShort();
3474 this.LookupFlag = fs.ReadUShort();
3476 ushort subTableCount = fs.ReadUShort();
3477 ushort[] subTableOffsets =
new ushort[subTableCount];
3479 for (
int i = 0; i < subTableCount; i++)
3481 subTableOffsets[i] = fs.ReadUShort();
3484 if ((this.LookupFlag & 0x0010) != 0)
3486 this.MarkFilteringSet = fs.ReadUShort();
3489 if (this.LookupType == 1)
3491 this.SubTables =
new ITrueTypeTable[subTableCount];
3493 for (
int i = 0; i < subTableCount; i++)
3495 fs.Seek(startPosition + subTableOffsets[i], SeekOrigin.Begin);
3496 this.SubTables[i] =
new SinglePosSubtable(fs);
3499 else if (this.LookupType == 2)
3501 this.SubTables =
new ITrueTypeTable[subTableCount];
3503 for (
int i = 0; i < subTableCount; i++)
3505 fs.Seek(startPosition + subTableOffsets[i], SeekOrigin.Begin);
3506 this.SubTables[i] =
new PairPosSubtable(fs);
3511 this.SubTables =
new ITrueTypeTable[0];
3515 public byte[] GetBytes()
3517 using (MemoryStream ms =
new MemoryStream())
3519 ms.WriteUShort(this.LookupType);
3520 ms.WriteUShort(this.LookupFlag);
3521 ms.WriteUShort((ushort)this.SubTables.Length);
3523 int offset = 6 + 2 * this.SubTables.Length;
3525 if ((this.LookupFlag & 0x0010) != 0)
3530 byte[][] subTables =
new byte[this.SubTables.Length][];
3531 int[] offsets =
new int[this.SubTables.Length];
3533 for (
int i = 0; i < this.SubTables.Length; i++)
3535 offsets[i] = offset;
3536 subTables[i] = this.SubTables[i].GetBytes();
3537 offset += subTables[i].Length;
3540 for (
int i = 0; i < this.SubTables.Length; i++)
3542 ms.WriteUShort((ushort)offsets[i]);
3545 if ((this.LookupFlag & 0x0010) != 0)
3547 ms.WriteUShort(this.MarkFilteringSet);
3550 for (
int i = 0; i < this.SubTables.Length; i++)
3552 ms.Write(subTables[i], 0, subTables[i].Length);
3555 return ms.ToArray();
3560 internal class LookupList
3562 public LookupTable[] LookupTables;
3564 public LookupList(Stream fs)
3566 long startPosition = fs.Position;
3568 ushort lookupCount = fs.ReadUShort();
3569 ushort[] lookupOffsets =
new ushort[lookupCount];
3571 for (
int i = 0; i < lookupCount; i++)
3573 lookupOffsets[i] = fs.ReadUShort();
3576 this.LookupTables =
new LookupTable[lookupCount];
3578 for (
int i = 0; i < lookupCount; i++)
3580 fs.Seek(startPosition + lookupOffsets[i], SeekOrigin.Begin);
3581 this.LookupTables[i] =
new LookupTable(fs);
3585 public byte[] GetBytes()
3587 using (MemoryStream ms =
new MemoryStream())
3589 ms.WriteUShort((ushort)this.LookupTables.Length);
3591 int offset = 2 + 2 * this.LookupTables.Length;
3593 int[] offsets =
new int[this.LookupTables.Length];
3594 byte[][] lookupTables =
new byte[this.LookupTables.Length][];
3596 for (
int i = 0; i < this.LookupTables.Length; i++)
3598 offsets[i] = offset;
3599 lookupTables[i] = this.LookupTables[i].GetBytes();
3600 offset += lookupTables[i].Length;
3603 for (
int i = 0; i < this.LookupTables.Length; i++)
3605 ms.WriteUShort((ushort)offsets[i]);
3608 for (
int i = 0; i < this.LookupTables.Length; i++)
3610 ms.Write(lookupTables[i], 0, lookupTables[i].Length);
3613 return ms.ToArray();
3618 internal class CoverageTable
3622 public ushort StartGlyphID;
3623 public ushort EndGlyphID;
3624 public ushort StartCoverageIndex;
3628 this.StartGlyphID = fs.ReadUShort();
3629 this.EndGlyphID = fs.ReadUShort();
3630 this.StartCoverageIndex = fs.ReadUShort();
3634 public ushort CoverageFormat;
3636 public ushort[] GlyphArray;
3639 public int ContainsGlyph(
int glyphIndex)
3641 if (this.CoverageFormat == 1)
3643 for (
int i = 0; i < this.GlyphArray.Length; i++)
3645 if (this.GlyphArray[i] == glyphIndex)
3653 else if (this.CoverageFormat == 2)
3655 for (
int i = 0; i < this.RangeRecords.Length; i++)
3657 if (this.RangeRecords[i].StartGlyphID <= glyphIndex && this.RangeRecords[i].EndGlyphID >= glyphIndex)
3659 return this.RangeRecords[i].StartCoverageIndex + (glyphIndex - this.RangeRecords[i].StartGlyphID);
3671 public CoverageTable(Stream fs)
3673 this.CoverageFormat = fs.ReadUShort();
3675 if (this.CoverageFormat == 1)
3677 ushort glyphCount = fs.ReadUShort();
3678 this.GlyphArray =
new ushort[glyphCount];
3679 for (
int i = 0; i < glyphCount; i++)
3681 this.GlyphArray[i] = fs.ReadUShort();
3684 else if (this.CoverageFormat == 2)
3686 ushort rangeCount = fs.ReadUShort();
3688 this.RangeRecords =
new RangeRecord[rangeCount];
3690 for (
int i = 0; i < rangeCount; i++)
3692 this.RangeRecords[i] =
new RangeRecord(fs);
3697 public byte[] GetBytes()
3699 if (this.CoverageFormat == 1)
3701 using (MemoryStream ms =
new MemoryStream(4 + 2 * this.GlyphArray.Length))
3703 ms.WriteUShort(this.CoverageFormat);
3704 ms.WriteUShort((ushort)this.GlyphArray.Length);
3705 for (
int i = 0; i < this.GlyphArray.Length; i++)
3707 ms.WriteUShort(this.GlyphArray[i]);
3710 return ms.ToArray();
3713 else if (this.CoverageFormat == 2)
3715 using (MemoryStream ms =
new MemoryStream(4 + 6 * this.RangeRecords.Length))
3717 ms.WriteUShort(this.CoverageFormat);
3718 ms.WriteUShort((ushort)this.RangeRecords.Length);
3719 for (
int i = 0; i < this.RangeRecords.Length; i++)
3721 ms.WriteUShort(this.RangeRecords[i].StartGlyphID);
3722 ms.WriteUShort(this.RangeRecords[i].EndGlyphID);
3723 ms.WriteUShort(this.RangeRecords[i].StartCoverageIndex);
3726 return ms.ToArray();
3731 using (MemoryStream ms =
new MemoryStream(4 + 2 * this.GlyphArray.Length))
3733 ms.WriteUShort(this.CoverageFormat);
3735 return ms.ToArray();
3741 internal class ClassDefinitionTable
3745 public ushort StartGlyphID;
3746 public ushort EndGlyphID;
3747 public ushort Class;
3751 this.StartGlyphID = fs.ReadUShort();
3752 this.EndGlyphID = fs.ReadUShort();
3753 this.Class = fs.ReadUShort();
3757 public ushort ClassFormat;
3758 public ushort StartGlyphID;
3759 public ushort[] ClassValueArray;
3763 public ClassDefinitionTable(Stream fs)
3765 this.ClassFormat = fs.ReadUShort();
3767 if (this.ClassFormat == 1)
3769 this.StartGlyphID = fs.ReadUShort();
3770 ushort glyphCount = fs.ReadUShort();
3772 this.ClassValueArray =
new ushort[glyphCount];
3774 for (
int i = 0; i < this.ClassValueArray.Length; i++)
3776 this.ClassValueArray[i] = fs.ReadUShort();
3779 else if (this.ClassFormat == 2)
3781 ushort classRangeCount = fs.ReadUShort();
3783 this.ClassRangeRecords =
new ClassRangeRecord[classRangeCount];
3785 for (
int i = 0; i < classRangeCount; i++)
3787 this.ClassRangeRecords[i] =
new ClassRangeRecord(fs);
3792 public int GetClass(
int glyphIndex)
3794 if (this.ClassFormat == 1)
3796 return this.ClassValueArray[glyphIndex - StartGlyphID];
3798 else if (this.ClassFormat == 2)
3800 for (
int i = 0; i < this.ClassRangeRecords.Length; i++)
3802 if (this.ClassRangeRecords[i].StartGlyphID <= glyphIndex && this.ClassRangeRecords[i].EndGlyphID >= glyphIndex)
3804 return this.ClassRangeRecords[i].Class;
3815 public byte[] GetBytes()
3817 if (this.ClassFormat == 1)
3819 using (MemoryStream ms =
new MemoryStream(6 + 2 * this.ClassValueArray.Length))
3821 ms.WriteUShort(this.ClassFormat);
3822 ms.WriteUShort(this.StartGlyphID);
3823 ms.WriteUShort((ushort)this.ClassValueArray.Length);
3825 for (
int i = 0; i < this.ClassValueArray.Length; i++)
3827 ms.WriteUShort(this.ClassValueArray[i]);
3830 return ms.ToArray();
3833 else if (this.ClassFormat == 2)
3835 using (MemoryStream ms =
new MemoryStream(4 + 6 * this.ClassRangeRecords.Length))
3837 ms.WriteUShort(this.ClassFormat);
3838 ms.WriteUShort((ushort)this.ClassRangeRecords.Length);
3840 for (
int i = 0; i < this.ClassRangeRecords.Length; i++)
3842 ms.WriteUShort(this.ClassRangeRecords[i].StartGlyphID);
3843 ms.WriteUShort(this.ClassRangeRecords[i].EndGlyphID);
3844 ms.WriteUShort(this.ClassRangeRecords[i].Class);
3847 return ms.ToArray();
3852 using (MemoryStream ms =
new MemoryStream(2))
3854 ms.WriteUShort(this.ClassFormat);
3856 return ms.ToArray();
3862 internal class FeatureTable
3865 public byte[] FeatureParams;
3866 public ushort[] LookupListIndices;
3868 public FeatureTable(
string tag, Stream fs)
3872 long startPosition = fs.Position;
3874 ushort featureParamsOffset = fs.ReadUShort();
3876 ushort lookupIndexCount = fs.ReadUShort();
3877 this.LookupListIndices =
new ushort[lookupIndexCount];
3879 for (
int i = 0; i < lookupIndexCount; i++)
3881 this.LookupListIndices[i] = fs.ReadUShort();
3884 if (featureParamsOffset != 0 && tag ==
"size")
3886 fs.Seek(startPosition + featureParamsOffset, SeekOrigin.Begin);
3888 this.FeatureParams =
new byte[10];
3890 for (
int i = 0; i < 10; i++)
3892 this.FeatureParams[i] = (byte)fs.ReadByte();
3897 this.FeatureParams =
null;
3901 public byte[] GetBytes()
3903 using (MemoryStream ms =
new MemoryStream())
3905 if (this.FeatureParams !=
null)
3907 ms.WriteUShort((ushort)(4 + 2 * this.LookupListIndices.Length));
3914 ms.WriteUShort((ushort)this.LookupListIndices.Length);
3916 for (
int i = 0; i < this.LookupListIndices.Length; i++)
3918 ms.WriteUShort(this.LookupListIndices[i]);
3921 if (this.FeatureParams !=
null)
3923 ms.Write(this.FeatureParams, 0, this.FeatureParams.Length);
3926 return ms.ToArray();
3931 internal class FeatureList
3933 public FeatureTable[] FeatureRecords;
3935 public FeatureList(Stream fs)
3937 long startPosition = fs.Position;
3939 ushort featureCount = fs.ReadUShort();
3941 (string, ushort)[] featureRecords =
new (
string, ushort)[featureCount];
3943 for (
int i = 0; i < featureCount; i++)
3945 StringBuilder featureTag =
new StringBuilder(4);
3946 featureTag.Append((
char)fs.ReadByte());
3947 featureTag.Append((
char)fs.ReadByte());
3948 featureTag.Append((
char)fs.ReadByte());
3949 featureTag.Append((
char)fs.ReadByte());
3951 featureRecords[i] = (featureTag.ToString(), fs.ReadUShort());
3954 this.FeatureRecords =
new FeatureTable[featureCount];
3956 for (
int i = 0; i < featureCount; i++)
3958 fs.Seek(startPosition + featureRecords[i].Item2, SeekOrigin.Begin);
3960 this.FeatureRecords[i] =
new FeatureTable(featureRecords[i].Item1, fs);
3964 public byte[] GetBytes()
3966 using (MemoryStream ms =
new MemoryStream())
3968 int offset = 2 + 6 * this.FeatureRecords.Length;
3970 ms.WriteUShort((ushort)this.FeatureRecords.Length);
3972 byte[][] featureRecords =
new byte[this.FeatureRecords.Length][];
3973 int[] offsets =
new int[this.FeatureRecords.Length];
3975 for (
int i = 0; i < this.FeatureRecords.Length; i++)
3977 offsets[i] = offset;
3978 featureRecords[i] = this.FeatureRecords[i].GetBytes();
3979 offset += featureRecords[i].Length;
3982 for (
int i = 0; i < this.FeatureRecords.Length; i++)
3984 ms.WriteByte((
byte)this.FeatureRecords[i].Tag[0]);
3985 ms.WriteByte((
byte)this.FeatureRecords[i].Tag[1]);
3986 ms.WriteByte((
byte)this.FeatureRecords[i].Tag[2]);
3987 ms.WriteByte((
byte)this.FeatureRecords[i].Tag[3]);
3989 ms.WriteUShort((ushort)offsets[i]);
3992 for (
int i = 0; i < this.FeatureRecords.Length; i++)
3994 ms.Write(featureRecords[i], 0, featureRecords[i].Length);
3997 return ms.ToArray();
4002 internal class ValueRecord
4004 public short XPlacement;
4005 public short YPlacement;
4006 public short XAdvance;
4007 public short YAdvance;
4008 public ushort ValueFormat;
4010 public ValueRecord(Stream fs, ushort valueFormat)
4012 this.ValueFormat = valueFormat;
4014 if ((valueFormat & 0x0001) != 0)
4016 this.XPlacement = fs.ReadShort();
4019 if ((valueFormat & 0x0002) != 0)
4021 this.YPlacement = fs.ReadShort();
4024 if ((valueFormat & 0x0004) != 0)
4026 this.XAdvance = fs.ReadShort();
4029 if ((valueFormat & 0x0008) != 0)
4031 this.YAdvance = fs.ReadShort();
4034 if ((valueFormat & 0x0010) != 0)
4039 if ((valueFormat & 0x0020) != 0)
4044 if ((valueFormat & 0x0040) != 0)
4049 if ((valueFormat & 0x0080) != 0)
4055 public byte[] GetBytes()
4057 using (MemoryStream ms =
new MemoryStream())
4059 if ((this.ValueFormat & 0x0001) != 0)
4061 ms.WriteShort(this.XPlacement);
4064 if ((this.ValueFormat & 0x0002) != 0)
4066 ms.WriteShort(this.YPlacement);
4069 if ((this.ValueFormat & 0x0004) != 0)
4071 ms.WriteShort(this.XAdvance);
4074 if ((this.ValueFormat & 0x0008) != 0)
4076 ms.WriteShort(this.YAdvance);
4079 if ((this.ValueFormat & 0x0010) != 0)
4084 if ((this.ValueFormat & 0x0020) != 0)
4089 if ((this.ValueFormat & 0x0040) != 0)
4094 if ((this.ValueFormat & 0x0080) != 0)
4099 return ms.ToArray();
4104 internal class SinglePosSubtable : ITrueTypeTable
4106 public ushort PosFormat;
4107 public CoverageTable CoverageTable;
4108 public ushort ValueFormat;
4109 public ValueRecord ValueRecord;
4110 public ValueRecord[] ValueRecords;
4112 public SinglePosSubtable(Stream fs)
4114 long startPosition = fs.Position;
4116 this.PosFormat = fs.ReadUShort();
4118 if (this.PosFormat == 1)
4120 ushort coverageOffset = fs.ReadUShort();
4121 this.ValueFormat = fs.ReadUShort();
4122 this.ValueRecord =
new ValueRecord(fs, this.ValueFormat);
4124 fs.Seek(startPosition + coverageOffset, SeekOrigin.Begin);
4125 this.CoverageTable =
new CoverageTable(fs);
4127 else if (this.PosFormat == 2)
4129 ushort coverageOffset = fs.ReadUShort();
4130 this.ValueFormat = fs.ReadUShort();
4132 ushort valueCount = fs.ReadUShort();
4134 this.ValueRecords =
new ValueRecord[valueCount];
4136 for (
int i = 0; i < valueCount; i++)
4138 this.ValueRecords[i] =
new ValueRecord(fs, this.ValueFormat);
4141 fs.Seek(startPosition + coverageOffset, SeekOrigin.Begin);
4142 this.CoverageTable =
new CoverageTable(fs);
4146 public byte[] GetBytes()
4148 using (MemoryStream ms =
new MemoryStream())
4150 ms.WriteUShort(this.PosFormat);
4152 if (this.PosFormat == 1)
4156 ms.WriteUShort((ushort)offset);
4157 ms.WriteUShort(this.ValueFormat);
4159 byte[] valueBytes = this.ValueRecord.GetBytes();
4161 ms.Write(valueBytes, 0, valueBytes.Length);
4163 byte[] coverageBytes = this.CoverageTable.GetBytes();
4165 ms.Write(coverageBytes, 0, coverageBytes.Length);
4167 else if (this.PosFormat == 2)
4169 int offset = 8 + 16 * ValueRecords.Length;
4171 ms.WriteUShort((ushort)offset);
4172 ms.WriteUShort(this.ValueFormat);
4173 ms.WriteUShort((ushort)this.ValueRecords.Length);
4175 for (
int i = 0; i < this.ValueRecords.Length; i++)
4177 byte[] valueBytes = this.ValueRecords[i].GetBytes();
4179 ms.Write(valueBytes, 0, valueBytes.Length);
4182 byte[] coverageBytes = this.CoverageTable.GetBytes();
4184 ms.Write(coverageBytes, 0, coverageBytes.Length);
4187 return ms.ToArray();
4192 internal class PairValueRecord
4194 public ushort SecondGlyph;
4195 public ValueRecord ValueRecord1;
4196 public ValueRecord ValueRecord2;
4198 public PairValueRecord(Stream fs, ushort valueFormat1, ushort valueFormat2)
4200 this.SecondGlyph = fs.ReadUShort();
4201 this.ValueRecord1 =
new ValueRecord(fs, valueFormat1);
4202 this.ValueRecord2 =
new ValueRecord(fs, valueFormat2);
4205 public byte[] GetBytes()
4207 using (MemoryStream ms =
new MemoryStream())
4209 ms.WriteUShort(SecondGlyph);
4211 byte[] bytes = this.ValueRecord1.GetBytes();
4212 ms.Write(bytes, 0, bytes.Length);
4214 bytes = this.ValueRecord2.GetBytes();
4215 ms.Write(bytes, 0, bytes.Length);
4217 return ms.ToArray();
4222 internal class Class2Record
4224 public ValueRecord ValueRecord1;
4225 public ValueRecord ValueRecord2;
4227 public Class2Record(Stream fs, ushort valueFormat1, ushort valueFormat2)
4229 this.ValueRecord1 =
new ValueRecord(fs, valueFormat1);
4230 this.ValueRecord2 =
new ValueRecord(fs, valueFormat2);
4233 public byte[] GetBytes()
4235 using (MemoryStream ms =
new MemoryStream())
4237 byte[] bytes = this.ValueRecord1.GetBytes();
4238 ms.Write(bytes, 0, bytes.Length);
4240 bytes = this.ValueRecord2.GetBytes();
4241 ms.Write(bytes, 0, bytes.Length);
4243 return ms.ToArray();
4282 internal class PairPosSubtable : ITrueTypeTable
4284 public ushort PosFormat;
4285 public CoverageTable CoverageTable;
4287 public ushort ValueFormat1;
4288 public ushort ValueFormat2;
4290 public PairValueRecord[][] PairSets;
4292 public ClassDefinitionTable ClassDef1;
4293 public ClassDefinitionTable ClassDef2;
4294 public Class2Record[][] Class1Records;
4296 public PairPosSubtable(Stream fs)
4298 long startPosition = fs.Position;
4300 this.PosFormat = fs.ReadUShort();
4302 if (this.PosFormat == 1)
4304 ushort coverageOffset = fs.ReadUShort();
4306 this.ValueFormat1 = fs.ReadUShort();
4307 this.ValueFormat2 = fs.ReadUShort();
4309 ushort pairSetCount = fs.ReadUShort();
4311 ushort[] pairSetOffsets =
new ushort[pairSetCount];
4313 for (
int i = 0; i < pairSetCount; i++)
4315 pairSetOffsets[i] = fs.ReadUShort();
4318 fs.Seek(startPosition + coverageOffset, SeekOrigin.Begin);
4319 this.CoverageTable =
new CoverageTable(fs);
4321 this.PairSets =
new PairValueRecord[pairSetCount][];
4323 for (
int i = 0; i < pairSetCount; i++)
4325 fs.Seek(startPosition + pairSetOffsets[i], SeekOrigin.Begin);
4327 ushort pairValueCount = fs.ReadUShort();
4329 this.PairSets[i] =
new PairValueRecord[pairValueCount];
4331 for (
int j = 0; j < pairValueCount; j++)
4333 this.PairSets[i][j] =
new PairValueRecord(fs, this.ValueFormat1, this.ValueFormat2);
4337 else if (this.PosFormat == 2)
4339 ushort coverageOffset = fs.ReadUShort();
4341 this.ValueFormat1 = fs.ReadUShort();
4342 this.ValueFormat2 = fs.ReadUShort();
4344 ushort class1DefOffset = fs.ReadUShort();
4345 ushort class2DefOffset = fs.ReadUShort();
4347 ushort class1Count = fs.ReadUShort();
4348 ushort class2Count = fs.ReadUShort();
4350 this.Class1Records =
new Class2Record[class1Count][];
4352 for (
int i = 0; i < class1Count; i++)
4354 this.Class1Records[i] =
new Class2Record[class2Count];
4356 for (
int j = 0; j < class2Count; j++)
4358 this.Class1Records[i][j] =
new Class2Record(fs, this.ValueFormat1, this.ValueFormat2);
4362 fs.Seek(startPosition + coverageOffset, SeekOrigin.Begin);
4363 this.CoverageTable =
new CoverageTable(fs);
4365 fs.Seek(startPosition + class1DefOffset, SeekOrigin.Begin);
4366 this.ClassDef1 =
new ClassDefinitionTable(fs);
4368 fs.Seek(startPosition + class2DefOffset, SeekOrigin.Begin);
4370 this.ClassDef2 =
new ClassDefinitionTable(fs);
4374 public PairKerning GetKerning(
int glyph1CoverageIndex,
int glyph1Index,
int glyph2Index)
4376 if (this.PosFormat == 1)
4378 for (
int i = 0; i < this.PairSets[glyph1CoverageIndex].Length; i++)
4380 if (this.PairSets[glyph1CoverageIndex][i].SecondGlyph == glyph2Index)
4382 return new PairKerning(
new Point(this.PairSets[glyph1CoverageIndex][i].ValueRecord1.XPlacement,
this.PairSets[glyph1CoverageIndex][i].ValueRecord1.YPlacement),
4383 new Point(this.PairSets[glyph1CoverageIndex][i].ValueRecord1.XAdvance,
this.PairSets[glyph1CoverageIndex][i].ValueRecord1.YAdvance),
4384 new Point(this.PairSets[glyph1CoverageIndex][i].ValueRecord2.XPlacement,
this.PairSets[glyph1CoverageIndex][i].ValueRecord2.YPlacement),
4385 new Point(this.PairSets[glyph1CoverageIndex][i].ValueRecord2.XAdvance,
this.PairSets[glyph1CoverageIndex][i].ValueRecord2.YAdvance));
4391 else if (this.PosFormat == 2)
4393 int glyph1Class = this.ClassDef1.GetClass(glyph1Index);
4394 int glyph2Class = this.ClassDef2.GetClass(glyph2Index);
4396 return new PairKerning(
new Point(this.Class1Records[glyph1Class][glyph2Class].ValueRecord1.XPlacement,
this.Class1Records[glyph1Class][glyph2Class].ValueRecord1.YPlacement),
4397 new Point(this.Class1Records[glyph1Class][glyph2Class].ValueRecord1.XAdvance,
this.Class1Records[glyph1Class][glyph2Class].ValueRecord1.YAdvance),
4398 new Point(this.Class1Records[glyph1Class][glyph2Class].ValueRecord2.XPlacement,
this.Class1Records[glyph1Class][glyph2Class].ValueRecord2.YPlacement),
4399 new Point(this.Class1Records[glyph1Class][glyph2Class].ValueRecord2.XAdvance,
this.Class1Records[glyph1Class][glyph2Class].ValueRecord2.YAdvance));
4407 public byte[] GetBytes()
4409 using (MemoryStream ms =
new MemoryStream())
4411 ms.WriteUShort(this.PosFormat);
4413 if (this.PosFormat == 1)
4415 int offset = 10 + 2 * PairSets.Length;
4417 ms.WriteUShort((ushort)offset);
4419 ms.WriteUShort((ushort)ValueFormat1);
4420 ms.WriteUShort((ushort)ValueFormat2);
4422 ms.WriteUShort((ushort)this.PairSets.Length);
4424 byte[] coverageBytes = this.CoverageTable.GetBytes();
4426 offset += coverageBytes.Length;
4428 byte[][][] pairSets =
new byte[this.PairSets.Length][][];
4429 int[] offsets =
new int[this.PairSets.Length];
4431 for (
int i = 0; i < this.PairSets.Length; i++)
4433 offsets[i] = offset;
4435 pairSets[i] =
new byte[this.PairSets[i].Length][];
4437 for (
int j = 0; j < this.PairSets[i].Length; j++)
4439 pairSets[i][j] = this.PairSets[i][j].GetBytes();
4440 offset += pairSets[i][j].Length;
4444 for (
int i = 0; i < offsets.Length; i++)
4446 ms.WriteUShort((ushort)offsets[i]);
4449 ms.Write(coverageBytes, 0, coverageBytes.Length);
4451 for (
int i = 0; i < pairSets.Length; i++)
4453 for (
int j = 0; j < pairSets[i].Length; j++)
4455 ms.Write(pairSets[i][j], 0, pairSets[i][j].Length);
4459 return ms.ToArray();
4461 else if (this.PosFormat == 2)
4465 byte[][][] class1Records =
new byte[this.Class1Records.Length][][];
4467 for (
int i = 0; i < class1Records.Length; i++)
4469 class1Records[i] =
new byte[this.Class1Records[i].Length][];
4471 for (
int j = 0; j < class1Records[i].Length; j++)
4473 class1Records[i][j] = this.Class1Records[i][j].GetBytes();
4474 offset += class1Records[i][j].Length;
4478 ms.WriteUShort((ushort)offset);
4480 ms.WriteUShort(this.ValueFormat1);
4481 ms.WriteUShort(this.ValueFormat2);
4483 byte[] coverageBytes = this.CoverageTable.GetBytes();
4485 offset += coverageBytes.Length;
4487 ms.WriteUShort((ushort)offset);
4489 byte[] classDef1Bytes = this.ClassDef1.GetBytes();
4491 offset += classDef1Bytes.Length;
4493 ms.WriteUShort((ushort)offset);
4495 ms.WriteUShort((ushort)this.Class1Records.Length);
4496 ms.WriteUShort((ushort)this.Class1Records[0].Length);
4498 for (
int i = 0; i < class1Records.Length; i++)
4500 for (
int j = 0; j < class1Records[i].Length; j++)
4502 ms.Write(class1Records[i][j], 0, class1Records[i][j].Length);
4506 ms.Write(coverageBytes, 0, coverageBytes.Length);
4507 ms.Write(classDef1Bytes, 0, classDef1Bytes.Length);
4509 byte[] classDef2Bytes = this.ClassDef2.GetBytes();
4510 ms.Write(classDef2Bytes, 0, classDef2Bytes.Length);
4512 return ms.ToArray();
4516 return ms.ToArray();
4523 internal class TrueTypeGPOSTable : ITrueTypeTable
4525 public ushort MajorVersion;
4526 public ushort MinorVersion;
4528 public ScriptList ScriptList;
4529 public FeatureList FeatureList;
4530 public LookupList LookupList;
4532 private HashSet<int> KernLookups;
4534 public TrueTypeGPOSTable(Stream fs)
4536 long startPosition = fs.Position;
4538 this.MajorVersion = fs.ReadUShort();
4539 this.MinorVersion = fs.ReadUShort();
4541 ushort scriptListOffset = fs.ReadUShort();
4542 ushort featureListOffset = fs.ReadUShort();
4543 ushort lookupListOffset = fs.ReadUShort();
4545 if (this.MinorVersion == 1)
4550 fs.Seek(startPosition + scriptListOffset, SeekOrigin.Begin);
4551 this.ScriptList =
new ScriptList(fs);
4553 fs.Seek(startPosition + featureListOffset, SeekOrigin.Begin);
4554 this.FeatureList =
new FeatureList(fs);
4556 fs.Seek(startPosition + lookupListOffset, SeekOrigin.Begin);
4557 this.LookupList =
new LookupList(fs);
4559 this.KernLookups =
new HashSet<int>();
4561 for (
int i = 0; i < this.FeatureList.FeatureRecords.Length; i++)
4563 if (this.FeatureList.FeatureRecords[i].Tag ==
"kern")
4565 for (
int j = 0; j < this.FeatureList.FeatureRecords[i].LookupListIndices.Length; j++)
4567 if (this.LookupList.LookupTables[
this.FeatureList.FeatureRecords[i].LookupListIndices[j]].LookupType == 2)
4569 this.KernLookups.Add(this.FeatureList.FeatureRecords[i].LookupListIndices[j]);
4577 public byte[] GetBytes()
4579 using (MemoryStream ms =
new MemoryStream())
4581 ms.WriteUShort(this.MajorVersion);
4582 ms.WriteUShort(this.MinorVersion);
4586 if (this.MinorVersion == 1)
4591 ms.WriteUShort((ushort)offset);
4593 byte[] scriptList = this.ScriptList.GetBytes();
4594 offset += scriptList.Length;
4596 ms.WriteUShort((ushort)offset);
4598 byte[] featureList = this.FeatureList.GetBytes();
4599 offset += featureList.Length;
4601 ms.WriteUShort((ushort)offset);
4603 if (this.MinorVersion == 1)
4608 ms.Write(scriptList, 0, scriptList.Length);
4609 ms.Write(featureList, 0, featureList.Length);
4611 byte[] lookupList = this.LookupList.GetBytes();
4613 ms.Write(lookupList, 0, lookupList.Length);
4615 return ms.ToArray();
4620 public PairKerning GetKerning(
int glyph1Index,
int glyph2Index)
4622 foreach (
int index
in this.KernLookups)
4624 for (
int i = 0; i < this.LookupList.LookupTables[index].SubTables.Length; i++)
4626 if (this.LookupList.LookupTables[index].SubTables[i] is PairPosSubtable pairPos)
4628 int coverageIndex = pairPos.CoverageTable.ContainsGlyph(glyph1Index);
4630 if (coverageIndex >= 0)
4632 return pairPos.GetKerning(coverageIndex, glyph1Index, glyph2Index);
4642 internal struct TrueTypeTableOffset
4644 public uint Checksum;
4648 public TrueTypeTableOffset(uint checksum, uint offset, uint length)
4650 this.Checksum = checksum;
4651 this.Offset = offset;
4652 this.Length = length;
4656 internal struct LongHorFixed
4658 public ushort AdvanceWidth;
4659 public short LeftSideBearing;
4661 public LongHorFixed(ushort advanceWidth,
short leftSideBearing)
4663 this.AdvanceWidth = advanceWidth;
4664 this.LeftSideBearing = leftSideBearing;
4668 internal struct Fixed
4671 public int BitShifts;
4673 public Fixed(
int bits,
int bitShifts)
4676 this.BitShifts = bitShifts;
4681 internal static class ReadUtils
4683 public static void WritePascalString(
this Stream sr,
string val)
4685 sr.WriteByte((
byte)val.Length);
4686 for (
int i = 0; i < val.Length; i++)
4688 sr.WriteByte((
byte)(
int)val[i]);
4692 public static string ReadPascalString(
this Stream sr)
4694 byte length = (byte)sr.ReadByte();
4695 StringBuilder tbr =
new StringBuilder(length);
4696 for (
int i = 0; i < length; i++)
4698 tbr.Append((
char)sr.ReadByte());
4700 return tbr.ToString();
4703 public static uint ReadUInt(
this Stream sr)
4705 return ((uint)sr.ReadByte() << 24) | ((uint)sr.ReadByte() << 16) | ((uint)sr.ReadByte() << 8) | (uint)sr.ReadByte();
4708 public static void WriteUInt(
this Stream sr, uint val)
4710 sr.Write(
new byte[] { (byte)(val >> 24), (byte)((val >> 16) & 255), (
byte)((val >> 8) & 255), (byte)(val & 255) }, 0, 4);
4713 public static int ReadInt(
this Stream sr)
4715 return (sr.ReadByte() << 24) | (sr.ReadByte() << 16) | (sr.ReadByte() << 8) | sr.ReadByte();
4718 public static void WriteInt(
this Stream sr,
int val)
4720 sr.WriteUInt((uint)val);
4723 public static ushort ReadUShort(
this Stream sr)
4725 return (ushort)(((uint)sr.ReadByte() << 8) | (uint)sr.ReadByte());
4728 public static void WriteUShort(
this Stream sr, ushort val)
4730 sr.Write(
new byte[] { (byte)(val >> 8), (byte)(val & 255) }, 0, 2);
4733 public static short ReadShort(
this Stream sr)
4735 ushort result = sr.ReadUShort();
4737 if ((result & 0x8000) != 0)
4739 tbr = (short)(result - (1 << 16));
4743 tbr = (short)result;
4748 public static void WriteShort(
this Stream sr,
short val)
4750 sr.WriteUShort((ushort)val);
4753 public static string ReadString(
this Stream sr,
int length)
4755 StringBuilder bld =
new StringBuilder(length);
4756 for (
int i = 0; i < length; i++)
4758 bld.Append((
char)sr.ReadByte());
4760 return bld.ToString();
4763 public static TrueTypeFile.Fixed ReadFixed(
this Stream sr)
4765 return new TrueTypeFile.Fixed(sr.ReadInt(), 16);
4768 public static void WriteFixed(
this Stream sr, TrueTypeFile.Fixed val)
4770 sr.WriteInt(val.Bits);
4773 public static DateTime ReadDate(
this Stream sr)
4775 long macTime = sr.ReadUInt() * 0x100000000 + sr.ReadUInt();
4776 return new DateTime(1904, 1, 1).AddTicks(macTime * 1000 * TimeSpan.TicksPerMillisecond);
4779 public static void WriteDate(
this Stream sr, DateTime date)
4781 long macTime = date.Subtract(
new DateTime(1904, 1, 1)).Ticks / 1000 / TimeSpan.TicksPerMillisecond;
4782 sr.WriteUInt((uint)(macTime >> 32));
4783 sr.WriteUInt((uint)(macTime & 0xFFFFFFFF));