VectSharp  2.2.1
A light library for C# vector graphics
TrueType.cs
1 /*
2  VectSharp - A light library for C# vector graphics.
3  Copyright (C) 2020-2022 Giorgio Bianchini
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU Lesser General Public License as published by
7  the Free Software Foundation, version 3.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU Lesser General Public License for more details.
13 
14  You should have received a copy of the GNU Lesser General Public License
15  along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17 
18 using System;
19 using System.Collections.Generic;
20 using System.IO;
21 using System.Linq;
22 using System.Runtime.CompilerServices;
23 using System.Text;
24 
25 namespace VectSharp
26 {
27  /// <summary>
28  /// Represents a font file in TrueType format. Reference: http://stevehanov.ca/blog/?id=143, https://developer.apple.com/fonts/TrueType-Reference-Manual/, https://docs.microsoft.com/en-us/typography/opentype/spec/
29  /// </summary>
30  public class TrueTypeFile
31  {
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>();
42 
43 
44  /// <summary>
45  /// A stream pointing to the TrueType file source (either on disk or in memory). Never dispose this stream directly; if you really need to, call <see cref="Destroy"/> instead.
46  /// </summary>
47  public Stream FontStream { get; }
48 
49  /// <summary>
50  /// Remove this TrueType file from the cache, clear the tables and release the <see cref="FontStream"/>.
51  /// Only call this when the actual file that was used to create this object needs to be changed!
52  /// </summary>
53  public void Destroy()
54  {
55  string keyString = null;
56 
57 
58  lock (FontCacheLock)
59  {
60  foreach (KeyValuePair<string, TrueTypeFile> kvp in FontCache)
61  {
62  if (kvp.Value == this)
63  {
64  keyString = kvp.Key;
65  break;
66  }
67  }
68 
69 
70  if (!string.IsNullOrEmpty(keyString))
71  {
72  FontCache.Remove(keyString);
73  }
74 
75  Stream keyStream = null;
76 
77  foreach (KeyValuePair<Stream, TrueTypeFile> kvp in StreamFontCache)
78  {
79  if (kvp.Value == this)
80  {
81  keyStream = kvp.Key;
82  }
83  }
84 
85  if (keyStream != null)
86  {
87  StreamFontCache.Remove(keyStream);
88  }
89  }
90 
91  this.FontStream.Dispose();
92  this.TableOffsets.Clear();
93  this.Tables.Clear();
94  }
95 
96  internal TrueTypeFile(Dictionary<string, ITrueTypeTable> tables)
97  {
98  this.Tables = tables;
99 
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);
105 
106  uint offset = 12 + 16 * (uint)tables.Count;
107 
108  uint fontChecksum = this.ScalarType;
109  fontChecksum += ((uint)this.NumTables << 16) + (uint)this.SearchRange;
110  fontChecksum += ((uint)this.EntrySelector << 16) + (uint)this.RangeShift;
111 
112  //For some reason, OpenType Sanitizer (https://github.com/khaledhosny/ots), integrated into Chromium and Firefox, complains if the tables are ordered differently.
113  List<string> tableOrdering = new List<string>()
114  {
115  "OS/2", "cmap", "glyf", "head", "hhea", "hmtx", "loca", "maxp", "name", "post"
116  };
117 
118  Dictionary<string, byte[]> allBytes = new Dictionary<string, byte[]>();
119 
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)
121  {
122  if (kvp.Key == "head")
123  {
124  ((TrueTypeHeadTable)kvp.Value).ChecksumAdjustment = 0;
125  }
126 
127  byte[] tableBytes = kvp.Value.GetBytes();
128  allBytes.Add(kvp.Key, tableBytes);
129  int length = tableBytes.Length;
130 
131  uint checksum = 0;
132 
133  for (int i = 0; i < tableBytes.Length; i += 4)
134  {
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;
139 
140  checksum += (uint)((b1 << 24) + (b2 << 16) + (b3 << 8) + b4);
141  }
142 
143  TableOffsets.Add(kvp.Key, new TrueTypeTableOffset(checksum, offset, (uint)length));
144 
145  fontChecksum += checksum;
146 
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;
151 
152  offset += (uint)length;
153 
154  switch (length % 4)
155  {
156  case 1:
157  offset += 3;
158  break;
159  case 2:
160  offset += 2;
161  break;
162  case 3:
163  offset++;
164  break;
165  }
166  }
167 
168  ((TrueTypeHeadTable)this.Tables["head"]).ChecksumAdjustment = 0xB1B0AFBA - fontChecksum;
169 
170 
171  FontStream = new MemoryStream();
172  FontStream.WriteUInt(this.ScalarType);
173  FontStream.WriteUShort(this.NumTables);
174  FontStream.WriteUShort(this.SearchRange);
175  FontStream.WriteUShort(this.EntrySelector);
176  FontStream.WriteUShort(this.RangeShift);
177 
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)
179  {
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);
184  }
185 
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)
187  {
188  FontStream.Write(allBytes[kvp.Key], 0, allBytes[kvp.Key].Length);
189  switch (allBytes[kvp.Key].Length % 4)
190  {
191  case 1:
192  FontStream.WriteByte(0);
193  FontStream.WriteByte(0);
194  FontStream.WriteByte(0);
195  break;
196  case 2:
197  FontStream.WriteByte(0);
198  FontStream.WriteByte(0);
199  break;
200  case 3:
201  FontStream.WriteByte(0);
202  break;
203  }
204  }
205 
206  FontStream.Seek(0, SeekOrigin.Begin);
207 
208  BuildGlyphCache();
209  }
210 
211  internal static TrueTypeFile CreateTrueTypeFile(string fileName)
212  {
213  lock (FontCacheLock)
214  {
215  if (!FontCache.ContainsKey(fileName))
216  {
217  FontCache.Add(fileName, new TrueTypeFile(fileName));
218  }
219 
220  return FontCache[fileName];
221  }
222  }
223 
224  internal static TrueTypeFile CreateTrueTypeFile(Stream fontStream)
225  {
226  lock (FontCacheLock)
227  {
228  if (!StreamFontCache.ContainsKey(fontStream))
229  {
230  StreamFontCache.Add(fontStream, new TrueTypeFile(fontStream));
231  }
232 
233  return StreamFontCache[fontStream];
234  }
235  }
236 
237  private TrueTypeFile(string fileName) : this(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
238  {
239 
240  }
241 
242  private TrueTypeFile(Stream fs)
243  {
244  FontStream = 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();
250 
251  for (int i = 0; i < NumTables; i++)
252  {
253  this.TableOffsets.Add(fs.ReadString(4), new TrueTypeTableOffset(fs.ReadUInt(), fs.ReadUInt(), fs.ReadUInt()));
254  }
255 
256  fs.Seek(this.TableOffsets["head"].Offset, SeekOrigin.Begin);
257 
258  Tables.Add("head", new TrueTypeHeadTable()
259  {
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()
277  });
278 
279  fs.Seek(this.TableOffsets["hhea"].Offset, SeekOrigin.Begin);
280 
281  Tables.Add("hhea", new TrueTypeHHeaTable()
282  {
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()
294  });
295 
296  fs.ReadShort();
297  fs.ReadShort();
298  fs.ReadShort();
299  fs.ReadShort();
300 
301  ((TrueTypeHHeaTable)Tables["hhea"]).MetricDataFormat = fs.ReadShort();
302  ((TrueTypeHHeaTable)Tables["hhea"]).NumOfLongHorMetrics = fs.ReadUShort();
303 
304  fs.Seek(this.TableOffsets["maxp"].Offset, SeekOrigin.Begin);
305 
306  Tables.Add("maxp", new TrueTypeMaxpTable()
307  {
308  Version = fs.ReadFixed(),
309  NumGlyphs = fs.ReadUShort()
310  });
311 
312  if (((TrueTypeMaxpTable)Tables["maxp"]).Version.Bits == 65536)
313  {
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();
327  }
328 
329  int totalGlyphs = ((TrueTypeMaxpTable)Tables["maxp"]).NumGlyphs;
330  int numMetrics = ((TrueTypeHHeaTable)Tables["hhea"]).NumOfLongHorMetrics;
331 
332  fs.Seek(this.TableOffsets["hmtx"].Offset, SeekOrigin.Begin);
333  Tables.Add("hmtx", new TrueTypeHmtxTable() { HMetrics = new LongHorFixed[numMetrics], LeftSideBearing = new short[totalGlyphs - numMetrics] });
334 
335  for (int i = 0; i < numMetrics; i++)
336  {
337  ((TrueTypeHmtxTable)Tables["hmtx"]).HMetrics[i] = new TrueTypeFile.LongHorFixed(fs.ReadUShort(), fs.ReadShort());
338  }
339 
340  for (int i = 0; i < totalGlyphs - numMetrics; i++)
341  {
342  ((TrueTypeHmtxTable)Tables["hmtx"]).LeftSideBearing[i] = fs.ReadShort();
343  }
344 
345  fs.Seek(this.TableOffsets["cmap"].Offset, SeekOrigin.Begin);
346  Tables.Add("cmap", new TrueTypeCmapTable() { Version = fs.ReadUShort(), NumberSubTables = fs.ReadUShort() });
347 
348  ((TrueTypeCmapTable)Tables["cmap"]).SubTables = new CmapSubTable[((TrueTypeCmapTable)Tables["cmap"]).NumberSubTables];
349  ((TrueTypeCmapTable)Tables["cmap"]).ActualCmapTables = new ICmapTable[((TrueTypeCmapTable)Tables["cmap"]).NumberSubTables];
350 
351  for (int i = 0; i < ((TrueTypeCmapTable)Tables["cmap"]).NumberSubTables; i++)
352  {
353  ((TrueTypeCmapTable)Tables["cmap"]).SubTables[i] = new TrueTypeFile.CmapSubTable(fs.ReadUShort(), fs.ReadUShort(), fs.ReadUInt());
354  }
355 
356 
357  for (int i = 0; i < ((TrueTypeCmapTable)Tables["cmap"]).NumberSubTables; i++)
358  {
359  fs.Seek(((TrueTypeCmapTable)Tables["cmap"]).SubTables[i].Offset + TableOffsets["cmap"].Offset, SeekOrigin.Begin);
360 
361  ushort format = fs.ReadUShort();
362  ushort length = fs.ReadUShort();
363  ushort language = fs.ReadUShort();
364 
365  if (format == 0)
366  {
367  byte[] glyphIndexArray = new byte[256];
368  fs.Read(glyphIndexArray, 0, 256);
369  ((TrueTypeCmapTable)Tables["cmap"]).ActualCmapTables[i] = new CmapTable0(format, length, language, glyphIndexArray);
370  }
371  else if (format == 4)
372  {
373  ((TrueTypeCmapTable)Tables["cmap"]).ActualCmapTables[i] = new CmapTable4(format, length, language, fs);
374  }
375  }
376 
377  if (this.TableOffsets.ContainsKey("OS/2"))
378  {
379  fs.Seek(this.TableOffsets["OS/2"].Offset, SeekOrigin.Begin);
380 
381  TrueTypeOS2Table os2 = new TrueTypeOS2Table()
382  {
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()
406  };
407 
408  if (os2.Version > 0 || os2.Version == 0 && this.TableOffsets["OS/2"].Length > 68)
409  {
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();
415 
416  if (os2.Version >= 1)
417  {
418  os2.UlCodePageRange = new uint[] { fs.ReadUInt(), fs.ReadUInt() };
419 
420  if (os2.Version >= 2)
421  {
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();
427 
428  if (os2.Version >= 5)
429  {
430  os2.UsLowerOpticalPointSize = fs.ReadUShort();
431  os2.UsUpperOpticalPointSize = fs.ReadUShort();
432  }
433  }
434  }
435  }
436 
437 
438  Tables.Add("OS/2", os2);
439  }
440 
441  if (this.TableOffsets.ContainsKey("post"))
442  {
443  fs.Seek(this.TableOffsets["post"].Offset, SeekOrigin.Begin);
444 
445  TrueTypePostTable post = new TrueTypePostTable()
446  {
447  Version = fs.ReadFixed(),
448  ItalicAngle = fs.ReadFixed(),
449  UnderlinePosition = fs.ReadShort(),
450  UnderlineThickness = fs.ReadShort(),
451  IsFixedPitch = fs.ReadUInt(),
452  MinMemType42 = fs.ReadUInt(),
453  MaxMemType42 = fs.ReadUInt(),
454  MinMemType1 = fs.ReadUInt(),
455  MaxMemType1 = fs.ReadUInt()
456  };
457 
458  if (post.Version.Bits == 0x00020000)
459  {
460  post.NumGlyphs = fs.ReadUShort();
461  post.GlyphNameIndex = new ushort[post.NumGlyphs];
462 
463  int numberNewGlyphs = 0;
464 
465  for (int i = 0; i < post.NumGlyphs; i++)
466  {
467  post.GlyphNameIndex[i] = fs.ReadUShort();
468  if (post.GlyphNameIndex[i] >= 258)
469  {
470  numberNewGlyphs++;
471  }
472  }
473 
474  post.Names = new string[numberNewGlyphs];
475 
476  for (int i = 0; i < numberNewGlyphs; i++)
477  {
478  post.Names[i] = fs.ReadPascalString();
479  }
480  }
481  Tables.Add("post", post);
482  }
483 
484  if (this.TableOffsets.ContainsKey("name"))
485  {
486  fs.Seek(this.TableOffsets["name"].Offset, SeekOrigin.Begin);
487  Tables.Add("name", new TrueTypeNameTable(this.TableOffsets["name"].Offset, fs));
488  }
489 
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));
492 
493  if (this.TableOffsets.ContainsKey("cvt "))
494  {
495  fs.Seek(this.TableOffsets["cvt "].Offset, SeekOrigin.Begin);
496  Tables.Add("cvt ", new TrueTypeRawTable(fs, this.TableOffsets["cvt "].Length));
497  }
498 
499  if (this.TableOffsets.ContainsKey("prep"))
500  {
501  fs.Seek(this.TableOffsets["prep"].Offset, SeekOrigin.Begin);
502  Tables.Add("prep", new TrueTypeRawTable(fs, this.TableOffsets["prep"].Length));
503  }
504 
505  if (this.TableOffsets.ContainsKey("fpgm"))
506  {
507  fs.Seek(this.TableOffsets["fpgm"].Offset, SeekOrigin.Begin);
508  Tables.Add("fpgm", new TrueTypeRawTable(fs, this.TableOffsets["fpgm"].Length));
509  }
510 
511  if (this.TableOffsets.ContainsKey("GPOS"))
512  {
513  fs.Seek(this.TableOffsets["GPOS"].Offset, SeekOrigin.Begin);
514  Tables.Add("GPOS", new TrueTypeGPOSTable(fs));
515  }
516 
517  Glyph[] glyphs = new Glyph[((TrueTypeMaxpTable)this.Tables["maxp"]).NumGlyphs];
518 
519  for (int i = 0; i < ((TrueTypeMaxpTable)this.Tables["maxp"]).NumGlyphs; i++)
520  {
521  uint offset = ((TrueTypeLocaTable)Tables["loca"]).GetOffset(i);
522  uint length = ((TrueTypeLocaTable)Tables["loca"]).Lengths[i];
523 
524  if (length > 0)
525  {
526  fs.Seek(this.TableOffsets["glyf"].Offset + offset, SeekOrigin.Begin);
527  glyphs[i] = Glyph.Parse(fs);
528  }
529  else
530  {
531  glyphs[i] = new EmptyGlyph();
532  }
533 
534  }
535 
536  Tables.Add("glyf", new TrueTypeGlyfTable() { Glyphs = glyphs });
537 
538  BuildGlyphCache();
539  }
540 
541 
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()
546  {
547  for (int i = 0; i < 256; i++)
548  {
549  GlyphWidthsCache[i] = this.Get1000EmGlyphWidth(GetGlyphIndex((char)i));
550  VerticalMetricsCache[i] = this.Get1000EmGlyphVerticalMetrics(GetGlyphIndex((char)i));
551  BearingsCache[i] = this.Get1000EmGlyphBearings(GetGlyphIndex((char)i));
552  }
553  }
554 
555  /// <summary>
556  /// Create a subset of the TrueType file, containing only the glyphs for the specified characters.
557  /// </summary>
558  /// <param name="charactersToInclude">A string containing the characters for which the glyphs should be included.</param>
559  /// <param name="consolidateAt32">If true, the character map is rearranged so that the included glyphs start at the unicode U+0032 control point.</param>
560  /// <param name="outputEncoding">If <paramref name="consolidateAt32"/> is true, entries will be added to this dictionary mapping the original characters to the new map (that starts at U+0033).</param>
561  /// <returns></returns>
562  public TrueTypeFile SubsetFont(string charactersToInclude, bool consolidateAt32 = false, Dictionary<char, char> outputEncoding = null)
563  {
564  if (!this.HasCmap4Table())
565  {
566  return this;
567  }
568  else
569  {
570 
571  TrueTypeHeadTable head = (TrueTypeHeadTable)this.Tables["head"];
572 
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"];
578 
579  CmapTable4 cmap4 = null;
580  int platformId = -1;
581  int platformSpecificID = -1;
582 
583  for (int i = 0; i < originalCmap.ActualCmapTables.Length; i++)
584  {
585  if (originalCmap.ActualCmapTables[i] != null && originalCmap.ActualCmapTables[i].Format == 4 && originalCmap.SubTables[i].PlatformID == 3 && originalCmap.SubTables[i].PlatformSpecificID == 1)
586  {
587  cmap4 = (CmapTable4)originalCmap.ActualCmapTables[i];
588  platformId = originalCmap.SubTables[i].PlatformID;
589  platformSpecificID = originalCmap.SubTables[i].PlatformSpecificID;
590  break;
591  }
592  }
593 
594  if (cmap4 == null)
595  {
596  return this;
597  }
598 
599  List<int> characterCodes = new List<int>();
600 
601  for (int i = 0; i < charactersToInclude.Length; i++)
602  {
603  characterCodes.Add((int)charactersToInclude[i]);
604  }
605 
606  characterCodes.Sort();
607 
608  List<(int, int)> segments = new List<(int, int)>();
609 
610  for (int i = 0; i < characterCodes.Count; i++)
611  {
612  if (segments.Count > 0 && segments.Last().Item1 + segments.Last().Item2 == characterCodes[i])
613  {
614  segments[segments.Count - 1] = (segments[segments.Count - 1].Item1, segments[segments.Count - 1].Item2 + 1);
615  }
616  else
617  {
618  segments.Add((characterCodes[i], 1));
619  }
620  }
621 
622  segments.Add((cmap4.StartCode.Last(), cmap4.EndCode.Last() - cmap4.StartCode.Last() + 1));
623 
624  int totalGlyphIndexCount = characterCodes.Count + 1;
625 
626  segments.Sort((a, b) => a.Item1 + a.Item2 - b.Item1 - b.Item2);
627 
628  ushort entrySelector = (ushort)Math.Floor(Math.Log(segments.Count, 2));
629 
630 
631  CmapTable4 newCmap4 = new CmapTable4()
632  {
633  Format = 4,
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]
646  };
647 
648  List<char> includedGlyphs = new List<char>
649  {
650  (char)0
651  };
652 
653  List<ushort> glyphIndexArray = new List<ushort>();
654 
655  if (!consolidateAt32)
656  {
657  for (int i = 0; i < segments.Count; i++)
658  {
659  newCmap4.EndCode[i] = (ushort)(segments[i].Item1 + segments[i].Item2 - 1);
660  newCmap4.StartCode[i] = (ushort)segments[i].Item1;
661 
662  newCmap4.IdRangeOffset[i] = 0;
663 
664  if (i < segments.Count - 1)
665  {
666  newCmap4.IdDelta[i] = (ushort)(includedGlyphs.Count - newCmap4.StartCode[i]);
667 
668  for (int j = newCmap4.StartCode[i]; j <= newCmap4.EndCode[i]; j++)
669  {
670  includedGlyphs.Add((char)j);
671  }
672  }
673  else
674  {
675  newCmap4.IdDelta[i] = 1;
676  newCmap4.IdRangeOffset[i] = 0;
677  }
678  }
679  }
680  else
681  {
682  for (int i = 0; i < segments.Count - 1; i++)
683  {
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;
687 
688  if (i < segments.Count - 1)
689  {
690  newCmap4.IdDelta[i] = (ushort)(includedGlyphs.Count - newCmap4.StartCode[i]);
691 
692  for (int j = segments[i].Item1; j < segments[i].Item1 + segments[i].Item2; j++)
693  {
694  includedGlyphs.Add((char)j);
695  }
696  }
697  else
698  {
699  newCmap4.IdDelta[i] = 1;
700  newCmap4.IdRangeOffset[i] = 0;
701  }
702  }
703 
704  {
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;
709 
710  if (i < segments.Count - 1)
711  {
712  newCmap4.IdDelta[i] = (ushort)(includedGlyphs.Count - newCmap4.StartCode[i]);
713 
714  for (int j = newCmap4.StartCode[i]; j <= newCmap4.EndCode[i]; j++)
715  {
716  includedGlyphs.Add((char)j);
717  }
718  }
719  else
720  {
721  newCmap4.IdDelta[i] = 1;
722  newCmap4.IdRangeOffset[i] = 0;
723  }
724  }
725 
726  if (outputEncoding != null)
727  {
728  for (int i = 1; i < includedGlyphs.Count; i++)
729  {
730  outputEncoding.Add(includedGlyphs[i], (char)(32 + i));
731  }
732  }
733  }
734 
735 
736  newCmap4.GlyphIndexArray = glyphIndexArray.ToArray();
737  newCmap4.Length += (ushort)(newCmap4.GlyphIndexArray.Length * 2);
738 
739  TrueTypeCmapTable cmap = new TrueTypeCmapTable() { ActualCmapTables = new ICmapTable[] { newCmap4 }, NumberSubTables = 1, Version = 0, SubTables = new CmapSubTable[] { new CmapSubTable((ushort)platformId, (ushort)platformSpecificID, 12) } };
740 
741 
742  List<Glyph> glyphs = new List<Glyph>();
743  List<int> originalGlyphIndices = new List<int>();
744 
745  for (int i = 0; i < includedGlyphs.Count; i++)
746  {
747  int index = this.GetGlyphIndex(includedGlyphs[i]);
748  originalGlyphIndices.Add(index);
749  glyphs.Add(originalGlyf.Glyphs[index].Clone());
750  }
751 
752  for (int i = 0; i < glyphs.Count; i++)
753  {
754  if (glyphs[i] is CompositeGlyph)
755  {
756  for (int j = 0; j < ((CompositeGlyph)glyphs[i]).GlyphIndex.Length; j++)
757  {
758  if (originalGlyphIndices.Contains(((CompositeGlyph)glyphs[i]).GlyphIndex[j]))
759  {
760  ((CompositeGlyph)glyphs[i]).GlyphIndex[j] = (ushort)originalGlyphIndices.IndexOf(((CompositeGlyph)glyphs[i]).GlyphIndex[j]);
761  }
762  else
763  {
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]);
767  }
768  }
769  }
770  }
771 
772  TrueTypeGlyfTable glyf = new TrueTypeGlyfTable() { Glyphs = glyphs.ToArray() };
773 
774  TrueTypeLocaTable loca = new TrueTypeLocaTable(glyphs.Count, head.IndexToLocFormat == 0);
775 
776  for (int i = 0; i < glyphs.Count + 1; i++)
777  {
778  if (i == 0)
779  {
780  loca.SetOffset(0, 0);
781  loca.Lengths[i] = (uint)(glyphs[i].GetBytes()).Length;
782  }
783  else
784  {
785  if (i < glyphs.Count)
786  {
787  loca.Lengths[i] = (uint)(glyphs[i].GetBytes()).Length;
788  }
789 
790  loca.SetOffset(i, loca.GetOffset(i - 1) + loca.Lengths[i - 1]);
791  }
792  }
793 
794  TrueTypeHHeaTable hhea = new TrueTypeHHeaTable()
795  {
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
809  };
810 
811 
812  LongHorFixed[] metrics = new LongHorFixed[glyphs.Count];
813 
814  for (int i = 0; i < glyphs.Count; i++)
815  {
816  metrics[i] = this.GetGlyphMetrics(originalGlyphIndices[i]);
817  }
818 
819  TrueTypeHmtxTable hmtx = new TrueTypeHmtxTable()
820  {
821  LeftSideBearing = new short[0],
822  HMetrics = metrics
823  };
824 
825  TrueTypeMaxpTable maxp = new TrueTypeMaxpTable()
826  {
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
842  };
843 
844  Dictionary<string, ITrueTypeTable> newTables = new Dictionary<string, ITrueTypeTable>() {
845  {"head", head },
846  {"hhea", hhea },
847  {"maxp", maxp },
848  };
849 
850  if (Tables.ContainsKey("OS/2"))
851  {
852  TrueTypeOS2Table originalOS2 = (TrueTypeOS2Table)Tables["OS/2"];
853 
854  TrueTypeOS2Table os2 = new TrueTypeOS2Table()
855  {
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]
892  };
893 
894  newTables.Add("OS/2", os2);
895  }
896 
897  newTables.Add("hmtx", hmtx);
898  newTables.Add("cmap", cmap);
899  if (Tables.ContainsKey("fpgm"))
900  {
901  TrueTypeRawTable fpgm = (TrueTypeRawTable)Tables["fpgm"];
902  newTables.Add("fpgm", fpgm);
903  }
904  if (Tables.ContainsKey("prep"))
905  {
906  TrueTypeRawTable prep = (TrueTypeRawTable)Tables["prep"];
907  newTables.Add("prep", prep);
908  }
909  if (Tables.ContainsKey("cvt "))
910  {
911  TrueTypeRawTable cvt = (TrueTypeRawTable)Tables["cvt "];
912  newTables.Add("cvt ", cvt);
913  }
914  newTables.Add("loca", loca);
915  newTables.Add("glyf", glyf);
916 
917  if (Tables.ContainsKey("name"))
918  {
919  TrueTypeNameTable name = (TrueTypeNameTable)Tables["name"];
920  newTables.Add("name", name);
921  }
922 
923  if (Tables.ContainsKey("post"))
924  {
925  TrueTypePostTable oldPost = (TrueTypePostTable)Tables["post"];
926 
927  TrueTypePostTable post = new TrueTypePostTable()
928  {
929  //We don't really care about PostScript names
930  Version = new Fixed(0x00030000, 16),
931  ItalicAngle = oldPost.ItalicAngle,
932  UnderlinePosition = oldPost.UnderlinePosition,
933  UnderlineThickness = oldPost.UnderlineThickness,
934  IsFixedPitch = oldPost.IsFixedPitch,
935  MinMemType42 = oldPost.MinMemType42,
936  MaxMemType42 = oldPost.MaxMemType42,
937  MinMemType1 = oldPost.MinMemType1,
938  MaxMemType1 = oldPost.MaxMemType1
939  };
940 
941  newTables.Add("post", post);
942  }
943 
944  TrueTypeFile newFile = new TrueTypeFile(newTables);
945 
946  return newFile;
947  }
948 
949  }
950 
951  internal class TrueTypeGlyfTable : ITrueTypeTable
952  {
953  public Glyph[] Glyphs;
954 
955  public byte[] GetBytes()
956  {
957  using (MemoryStream ms = new MemoryStream())
958  {
959  for (int i = 0; i < Glyphs.Length; i++)
960  {
961  byte[] glyph = Glyphs[i].GetBytes();
962  ms.Write(glyph, 0, glyph.Length);
963  }
964  return ms.ToArray();
965  }
966  }
967 
968  }
969 
970  internal abstract class Glyph
971  {
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; }
977 
978  public abstract byte[] GetBytes();
979  public abstract Glyph Clone();
980  public static Glyph Parse(Stream sr)
981  {
982  short numOfContours = sr.ReadShort();
983  if (numOfContours >= 0)
984  {
985  return new SimpleGlyph(sr, numOfContours);
986  }
987  else
988  {
989  return new CompositeGlyph(sr, numOfContours);
990  }
991  }
992 
993  public abstract TrueTypePoint[][] GetGlyphPath(double size, int emSize, Glyph[] glyphCollection);
994  }
995 
996  internal class EmptyGlyph : Glyph
997  {
998  public override byte[] GetBytes()
999  {
1000  return new byte[0];
1001  }
1002 
1003  public override Glyph Clone()
1004  {
1005  return new EmptyGlyph();
1006  }
1007 
1008  public override TrueTypePoint[][] GetGlyphPath(double size, int emSize, Glyph[] glyphCollection)
1009  {
1010  return new TrueTypePoint[0][];
1011  }
1012  }
1013  internal class CompositeGlyph : Glyph
1014  {
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; }
1022 
1023  private CompositeGlyph() { }
1024  public override Glyph Clone()
1025  {
1026  CompositeGlyph tbr = new CompositeGlyph()
1027  {
1028  NumberOfContours = this.NumberOfContours,
1029  XMin = this.XMin,
1030  YMin = this.YMin,
1031  XMax = this.XMax,
1032  YMax = this.YMax,
1033 
1034  Flags = new ushort[this.Flags.Length],
1035 
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
1041  };
1042  this.Flags.CopyTo(tbr.Flags, 0);
1043 
1044  this.GlyphIndex.CopyTo(tbr.GlyphIndex, 0);
1045 
1046  for (int i = 0; i < this.Argument1.Length; i++)
1047  {
1048  tbr.Argument1[i] = (byte[])this.Argument1[i].Clone();
1049  }
1050 
1051 
1052  for (int i = 0; i < this.Argument2.Length; i++)
1053  {
1054  tbr.Argument2[i] = (byte[])this.Argument2[i].Clone();
1055  }
1056 
1057 
1058  for (int i = 0; i < this.TransformationOption.Length; i++)
1059  {
1060  tbr.TransformationOption[i] = (byte[])this.TransformationOption[i].Clone();
1061  }
1062 
1063 
1064 
1065  if (this.Instructions != null)
1066  {
1067  tbr.Instructions = new byte[this.Instructions.Length];
1068  this.Instructions.CopyTo(tbr.Instructions, 0);
1069  }
1070  else
1071  {
1072  tbr.Instructions = null;
1073  }
1074 
1075  return tbr;
1076  }
1077  public CompositeGlyph(Stream sr, short numberOfContours) : base()
1078  {
1079  this.NumberOfContours = numberOfContours;
1080  this.XMin = sr.ReadShort();
1081  this.YMin = sr.ReadShort();
1082  this.XMax = sr.ReadShort();
1083  this.YMax = sr.ReadShort();
1084 
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[]>();
1090 
1091  bool moreComponents = true;
1092 
1093  while (moreComponents)
1094  {
1095  flags.Add(sr.ReadUShort());
1096 
1097  moreComponents = (flags.Last() & 0x0020) != 0;
1098  glyphIndex.Add(sr.ReadUShort());
1099 
1100  if ((flags.Last() & 0x0001) != 0)
1101  {
1102  argument1.Add(new byte[] { (byte)sr.ReadByte(), (byte)sr.ReadByte() });
1103  argument2.Add(new byte[] { (byte)sr.ReadByte(), (byte)sr.ReadByte() });
1104  }
1105  else
1106  {
1107  argument1.Add(new byte[] { (byte)sr.ReadByte() });
1108  argument2.Add(new byte[] { (byte)sr.ReadByte() });
1109  }
1110 
1111  if ((flags.Last() & 0x0008) != 0)
1112  {
1113  transformationOption.Add(new byte[] { (byte)sr.ReadByte(), (byte)sr.ReadByte() });
1114  }
1115  else if ((flags.Last() & 0x0040) != 0)
1116  {
1117  transformationOption.Add(new byte[] { (byte)sr.ReadByte(), (byte)sr.ReadByte(), (byte)sr.ReadByte(), (byte)sr.ReadByte() });
1118  }
1119  else if ((flags.Last() & 0x0080) != 0)
1120  {
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() });
1122  }
1123  else
1124  {
1125  transformationOption.Add(new byte[] { });
1126  }
1127  }
1128 
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();
1134 
1135  if ((flags.Last() & 0x0100) != 0)
1136  {
1137  this.NumInstructions = sr.ReadUShort();
1138  this.Instructions = new byte[this.NumInstructions];
1139  sr.Read(this.Instructions, 0, this.NumInstructions);
1140  }
1141  else
1142  {
1143  this.NumInstructions = 0;
1144  this.Instructions = null;
1145  }
1146  }
1147 
1148  public override byte[] GetBytes()
1149  {
1150  using (MemoryStream ms = new MemoryStream())
1151  {
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);
1157 
1158  for (int i = 0; i < this.Flags.Length; i++)
1159  {
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);
1165  }
1166 
1167  if (this.NumInstructions > 0)
1168  {
1169  ms.WriteUShort(this.NumInstructions);
1170  ms.Write(this.Instructions, 0, this.NumInstructions);
1171  }
1172 
1173  if (ms.Length % 2 != 0)
1174  {
1175  ms.WriteByte(0);
1176  }
1177 
1178  return ms.ToArray();
1179  }
1180  }
1181 
1182  public override TrueTypePoint[][] GetGlyphPath(double size, int emSize, Glyph[] glyphCollection)
1183  {
1184  List<short> argument1 = new List<short>();
1185  List<short> argument2 = new List<short>();
1186 
1187  for (int i = 0; i < this.Argument1.Length; i++)
1188  {
1189  if (this.Argument1[i].Length == 1)
1190  {
1191  argument1.Add(this.Argument1[i][0]);
1192  }
1193  else if (this.Argument1[i].Length == 2)
1194  {
1195  argument1.Add((short)((this.Argument1[i][0] << 8) + this.Argument1[i][1]));
1196  }
1197  }
1198 
1199  for (int i = 0; i < this.Argument2.Length; i++)
1200  {
1201  if (this.Argument2[i].Length == 1)
1202  {
1203  argument2.Add(this.Argument2[i][0]);
1204  }
1205  else if (this.Argument2[i].Length == 2)
1206  {
1207  argument2.Add((short)((this.Argument2[i][0] << 8) + this.Argument2[i][1]));
1208  }
1209  }
1210 
1211  List<double[]> transformationOption = new List<double[]>();
1212 
1213  for (int i = 0; i < this.TransformationOption.Length; i++)
1214  {
1215  if (this.TransformationOption[i].Length == 0)
1216  {
1217  transformationOption.Add(new double[] { });
1218  }
1219  else if (this.TransformationOption[i].Length == 2)
1220  {
1221  double val = (double)((this.TransformationOption[i][0] << 8) + this.TransformationOption[i][1]) / (1 << 14);
1222  transformationOption.Add(new double[] { val });
1223  }
1224  else if (this.TransformationOption[i].Length == 4)
1225  {
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 });
1229  }
1230  else if (this.TransformationOption[i].Length == 8)
1231  {
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 });
1237  }
1238  }
1239 
1240  List<TrueTypePoint[]> tbr = new List<TrueTypePoint[]>();
1241 
1242 
1243  for (int i = 0; i < this.GlyphIndex.Length; i++)
1244  {
1245  TrueTypePoint[][] componentContours = glyphCollection[this.GlyphIndex[i]].GetGlyphPath(size, emSize, glyphCollection);
1246 
1247  double[,] transformMatrix = new double[,] { { 1, 0 }, { 0, 1 } };
1248 
1249  if ((Flags[i] & 0x0008) != 0)
1250  {
1251  transformMatrix[0, 0] = transformationOption[i][0];
1252  transformMatrix[1, 1] = transformationOption[i][0];
1253  }
1254  else if ((Flags[i] & 0x0040) != 0)
1255  {
1256  transformMatrix[0, 0] = transformationOption[i][0];
1257  transformMatrix[1, 1] = transformationOption[i][1];
1258  }
1259  else if ((Flags[i] & 0x0080) != 0)
1260  {
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];
1265  }
1266 
1267  double deltaX = 0;
1268  double deltaY = 0;
1269 
1270  if ((Flags[i] & 0x0002) != 0)
1271  {
1272  deltaX = argument1[i] * size / emSize;
1273  deltaY = argument2[i] * size / emSize;
1274 
1275  if ((Flags[i] & 0x0800) != 0 && (Flags[i] & 0x1000) == 0)
1276  {
1277  deltaX *= Magnitude(Multiply(transformMatrix, new double[] { 1, 0 }));
1278  deltaY *= Magnitude(Multiply(transformMatrix, new double[] { 0, 1 }));
1279  }
1280  }
1281  else
1282  {
1283  TrueTypePoint reference = GetNthElementWhere(tbr, argument1[i], el => el.IsDefinedPoint);
1284  TrueTypePoint destination = GetNthElementWhere(componentContours, argument2[i], el => el.IsDefinedPoint);
1285 
1286  deltaX = reference.X - destination.X;
1287  deltaY = reference.Y - destination.Y;
1288 
1289  //Note: this would be the sensible behaviour (i.e. make sure that reference and destination coincide after the transform), but apparently it is not the correct one.
1290  /*double[] transfDestination = Multiply(transformMatrix, new double[] { destination.X, destination.Y });
1291 
1292  deltaX = reference.X - transfDestination[0];
1293  deltaY = reference.Y - transfDestination[1];*/
1294  }
1295 
1296  for (int j = 0; j < componentContours.Length; j++)
1297  {
1298  for (int k = 0; k < componentContours[j].Length; k++)
1299  {
1300  double[] transformed = Multiply(transformMatrix, new double[] { componentContours[j][k].X, componentContours[j][k].Y });
1301 
1302  componentContours[j][k] = new TrueTypePoint(transformed[0] + deltaX, transformed[1] + deltaY, componentContours[j][k].IsOnCurve, componentContours[j][k].IsDefinedPoint);
1303  }
1304  }
1305 
1306  tbr.AddRange(componentContours);
1307  }
1308 
1309  return tbr.ToArray();
1310  }
1311  }
1312 
1313  private static double Magnitude(double[] vector)
1314  {
1315  double tbr = 0;
1316 
1317  for (int i = 0; i < vector.Length; i++)
1318  {
1319  tbr += vector[i] * vector[i];
1320  }
1321 
1322  return Math.Sqrt(tbr);
1323  }
1324 
1325  private static double[] Multiply(double[,] matrix, double[] vector)
1326  {
1327  double[] tbr = new double[2];
1328 
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];
1331 
1332  return tbr;
1333  }
1334 
1335  private static T GetNthElementWhere<T>(IEnumerable<IEnumerable<T>> array, int n, Func<T, bool> condition)
1336  {
1337  int index = 0;
1338 
1339  foreach (IEnumerable<T> arr1 in array)
1340  {
1341  foreach (T el in arr1)
1342  {
1343  if (condition(el))
1344  {
1345  if (index == n)
1346  {
1347  return el;
1348  }
1349  index++;
1350  }
1351  }
1352  }
1353 
1354  throw new IndexOutOfRangeException();
1355  }
1356 
1357  /// <summary>
1358  /// Represents a point in a TrueType path description.
1359  /// </summary>
1360  public struct TrueTypePoint
1361  {
1362  /// <summary>
1363  /// The horizontal coordinate of the point.
1364  /// </summary>
1365  public double X;
1366 
1367  /// <summary>
1368  /// The vertical coordinate of the point.
1369  /// </summary>
1370  public double Y;
1371 
1372  /// <summary>
1373  /// Whether the point is a point on the curve, or a control point of a quadratic Bezier curve.
1374  /// </summary>
1375  public bool IsOnCurve;
1376 
1377  internal bool IsDefinedPoint;
1378 
1379  internal TrueTypePoint(double x, double y, bool onCurve, bool isDefinedPoint)
1380  {
1381  this.X = x;
1382  this.Y = y;
1383  this.IsOnCurve = onCurve;
1384  this.IsDefinedPoint = isDefinedPoint;
1385  }
1386  }
1387 
1388  internal class SimpleGlyph : Glyph
1389  {
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; }
1396 
1397  private SimpleGlyph() { }
1398  public override Glyph Clone()
1399  {
1400  SimpleGlyph tbr = new SimpleGlyph
1401  {
1402  NumberOfContours = this.NumberOfContours,
1403  XMin = this.XMin,
1404  YMin = this.YMin,
1405  XMax = this.XMax,
1406  YMax = this.YMax,
1407 
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]
1414  };
1415 
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);
1421 
1422  return tbr;
1423  }
1424 
1425  public SimpleGlyph(Stream sr, short numberOfContours) : base()
1426  {
1427  this.NumberOfContours = numberOfContours;
1428  this.XMin = sr.ReadShort();
1429  this.YMin = sr.ReadShort();
1430  this.XMax = sr.ReadShort();
1431  this.YMax = sr.ReadShort();
1432 
1433  this.EndPtsOfContours = new ushort[this.NumberOfContours];
1434  for (int i = 0; i < this.NumberOfContours; i++)
1435  {
1436  this.EndPtsOfContours[i] = sr.ReadUShort();
1437  }
1438 
1439  this.InstructionLength = sr.ReadUShort();
1440  this.Instructions = new byte[this.InstructionLength];
1441  for (int i = 0; i < this.InstructionLength; i++)
1442  {
1443  this.Instructions[i] = (byte)sr.ReadByte();
1444  }
1445 
1446  List<byte> logicalFlags = new List<byte>();
1447  List<byte> flags = new List<byte>();
1448 
1449  int totalPoints = this.EndPtsOfContours[this.NumberOfContours - 1] + 1;
1450 
1451  int countedPoints = 0;
1452 
1453  while (countedPoints < totalPoints)
1454  {
1455  flags.Add((byte)sr.ReadByte());
1456  logicalFlags.Add(flags.Last());
1457  countedPoints++;
1458  if ((flags.Last() & 0x08) != 0)
1459  {
1460  byte repeats = (byte)sr.ReadByte();
1461  for (int i = 0; i < repeats; i++)
1462  {
1463  logicalFlags.Add(flags.Last());
1464  countedPoints++;
1465  }
1466  flags.Add(repeats);
1467  }
1468  }
1469 
1470  this.Flags = flags.ToArray();
1471 
1472  List<byte> xCoordinates = new List<byte>();
1473 
1474  for (int i = 0; i < totalPoints; i++)
1475  {
1476  bool isByte = (logicalFlags[i] & 0x02) != 0;
1477 
1478  if (isByte)
1479  {
1480  xCoordinates.Add((byte)sr.ReadByte());
1481  }
1482  else if ((logicalFlags[i] & 0x10) == 0)
1483  {
1484  xCoordinates.Add((byte)sr.ReadByte());
1485  xCoordinates.Add((byte)sr.ReadByte());
1486  }
1487  }
1488 
1489  this.XCoordinates = xCoordinates.ToArray();
1490 
1491  List<byte> yCoordinates = new List<byte>();
1492 
1493  List<int> yCoordinateLengths = new List<int>();
1494 
1495  for (int i = 0; i < totalPoints; i++)
1496  {
1497  bool isByte = (logicalFlags[i] & 0x04) != 0;
1498 
1499  if (isByte)
1500  {
1501  yCoordinates.Add((byte)sr.ReadByte());
1502  yCoordinateLengths.Add(1);
1503  }
1504  else if ((logicalFlags[i] & 0x20) == 0)
1505  {
1506  yCoordinates.Add((byte)sr.ReadByte());
1507  yCoordinates.Add((byte)sr.ReadByte());
1508  yCoordinateLengths.Add(2);
1509  }
1510  else
1511  {
1512  yCoordinateLengths.Add(0);
1513  }
1514  }
1515 
1516  this.YCoordinates = yCoordinates.ToArray();
1517  }
1518 
1519  public override byte[] GetBytes()
1520  {
1521  using (MemoryStream ms = new MemoryStream())
1522  {
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);
1528 
1529  for (int i = 0; i < this.EndPtsOfContours.Length; i++)
1530  {
1531  ms.WriteUShort(this.EndPtsOfContours[i]);
1532  }
1533 
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);
1539 
1540  if (ms.Length % 2 != 0)
1541  {
1542  ms.WriteByte(0);
1543  }
1544 
1545  return ms.ToArray();
1546  }
1547  }
1548 
1549  public override TrueTypePoint[][] GetGlyphPath(double size, int emSize, Glyph[] glyphCollection)
1550  {
1551  List<TrueTypePoint[]> contours = new List<TrueTypePoint[]>();
1552 
1553  List<TrueTypePoint> currentContour = new List<TrueTypePoint>();
1554 
1555 
1556  List<byte> logicalFlags = new List<byte>();
1557 
1558  int totalPoints = this.EndPtsOfContours[this.NumberOfContours - 1] + 1;
1559 
1560  int countedPoints = 0;
1561 
1562  int index = 0;
1563 
1564  while (countedPoints < totalPoints)
1565  {
1566  logicalFlags.Add(this.Flags[index]);
1567  index++;
1568  countedPoints++;
1569  if ((logicalFlags.Last() & 0x08) != 0)
1570  {
1571  byte repeats = this.Flags[index];
1572  index++;
1573  for (int i = 0; i < repeats; i++)
1574  {
1575  logicalFlags.Add(logicalFlags.Last());
1576  countedPoints++;
1577  }
1578  }
1579  }
1580 
1581  List<short> xCoordinates = new List<short>();
1582 
1583  index = 0;
1584 
1585  for (int i = 0; i < totalPoints; i++)
1586  {
1587  bool isByte = (logicalFlags[i] & 0x02) != 0;
1588 
1589  if (isByte)
1590  {
1591  if ((logicalFlags[i] & 0x10) != 0)
1592  {
1593  xCoordinates.Add(this.XCoordinates[index]);
1594  }
1595  else
1596  {
1597  xCoordinates.Add((short)(-this.XCoordinates[index]));
1598  }
1599 
1600  index++;
1601  }
1602  else if ((logicalFlags[i] & 0x10) == 0)
1603  {
1604  xCoordinates.Add((short)((this.XCoordinates[index] << 8) + this.XCoordinates[index + 1]));
1605  index += 2;
1606  }
1607  else
1608  {
1609  xCoordinates.Add(0);
1610  }
1611  }
1612 
1613  List<short> yCoordinates = new List<short>();
1614 
1615  index = 0;
1616 
1617  for (int i = 0; i < totalPoints; i++)
1618  {
1619  bool isByte = (logicalFlags[i] & 0x04) != 0;
1620 
1621  if (isByte)
1622  {
1623  if ((logicalFlags[i] & 0x20) != 0)
1624  {
1625  yCoordinates.Add(this.YCoordinates[index]);
1626  }
1627  else
1628  {
1629  yCoordinates.Add((short)(-this.YCoordinates[index]));
1630  }
1631  index++;
1632  }
1633  else if ((logicalFlags[i] & 0x20) == 0)
1634  {
1635  yCoordinates.Add((short)((this.YCoordinates[index] << 8) + this.YCoordinates[index + 1]));
1636  index += 2;
1637  }
1638  else
1639  {
1640  yCoordinates.Add(0);
1641  }
1642  }
1643 
1644  int[] previousPoint = new int[2] { 0, 0 };
1645 
1646  for (int i = 0; i < totalPoints; i++)
1647  {
1648  int absoluteX = xCoordinates[i] + previousPoint[0];
1649  int absoluteY = yCoordinates[i] + previousPoint[1];
1650 
1651  previousPoint[0] = absoluteX;
1652  previousPoint[1] = absoluteY;
1653 
1654  bool onCurve = (logicalFlags[i] & 0x01) != 0;
1655 
1656  if (onCurve)
1657  {
1658  currentContour.Add(new TrueTypePoint(size * absoluteX / emSize, size * absoluteY / emSize, onCurve, true));
1659  }
1660  else
1661  {
1662  if (currentContour.Count > 0)
1663  {
1664  if (currentContour.Last().IsOnCurve)
1665  {
1666  currentContour.Add(new TrueTypePoint(size * absoluteX / emSize, size * absoluteY / emSize, onCurve, true));
1667  }
1668  else
1669  {
1670  double newX = size * absoluteX / emSize;
1671  double newY = size * absoluteY / emSize;
1672 
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));
1675  }
1676  }
1677  else
1678  {
1679  currentContour.Add(new TrueTypePoint(size * absoluteX / emSize, size * absoluteY / emSize, onCurve, true));
1680  }
1681  }
1682 
1683  if (this.EndPtsOfContours.Contains((ushort)i))
1684  {
1685  if (!currentContour[0].IsOnCurve)
1686  {
1687  if (currentContour.Last().IsOnCurve)
1688  {
1689  currentContour.Insert(0, new TrueTypePoint(currentContour.Last().X, currentContour.Last().Y, currentContour.Last().IsOnCurve, false));
1690  }
1691  else
1692  {
1693  currentContour.Insert(0, new TrueTypePoint((currentContour[0].X + currentContour.Last().X) * 0.5, (currentContour[0].Y + currentContour.Last().Y) * 0.5, true, false));
1694  }
1695  }
1696 
1697  if (!currentContour.Last().IsOnCurve)
1698  {
1699  currentContour.Add(new TrueTypePoint(currentContour[0].X, currentContour[0].Y, currentContour[0].IsOnCurve, false));
1700  }
1701 
1702  contours.Add(currentContour.ToArray());
1703  currentContour = new List<TrueTypePoint>();
1704  }
1705  }
1706 
1707  return contours.ToArray();
1708  }
1709  }
1710 
1711  internal class TrueTypeLocaTable : ITrueTypeTable
1712  {
1713  public ushort[] ShortOffsets { get; }
1714  public uint[] IntOffsets { get; }
1715  public uint[] Lengths { get; }
1716 
1717  public uint GetOffset(int index)
1718  {
1719  if (IntOffsets == null)
1720  {
1721  return (uint)ShortOffsets[index] * 2;
1722  }
1723  else
1724  {
1725  return IntOffsets[index];
1726  }
1727  }
1728 
1729  public void SetOffset(int index, uint value)
1730  {
1731  if (IntOffsets == null)
1732  {
1733  ShortOffsets[index] = (ushort)(value / 2);
1734  }
1735  else
1736  {
1737  IntOffsets[index] = value;
1738  }
1739  }
1740 
1741  public TrueTypeLocaTable(int numGlyphs, bool isShort)
1742  {
1743  this.Lengths = new uint[numGlyphs];
1744 
1745  if (isShort)
1746  {
1747  this.ShortOffsets = new ushort[numGlyphs + 1];
1748  }
1749  else
1750  {
1751  this.IntOffsets = new uint[numGlyphs + 1];
1752  }
1753  }
1754 
1755  public TrueTypeLocaTable(Stream sr, int numGlyphs, bool isShort)
1756  {
1757  this.Lengths = new uint[numGlyphs];
1758 
1759  if (isShort)
1760  {
1761  this.ShortOffsets = new ushort[numGlyphs + 1];
1762  for (int i = 0; i < numGlyphs + 1; i++)
1763  {
1764  this.ShortOffsets[i] = sr.ReadUShort();
1765  }
1766 
1767  for (int i = 0; i < numGlyphs; i++)
1768  {
1769  this.Lengths[i] = 2 * ((uint)this.ShortOffsets[i + 1] - (uint)this.ShortOffsets[i]);
1770  }
1771  }
1772  else
1773  {
1774  this.IntOffsets = new uint[numGlyphs + 1];
1775  for (int i = 0; i < numGlyphs + 1; i++)
1776  {
1777  this.IntOffsets[i] = sr.ReadUInt();
1778  }
1779 
1780  for (int i = 0; i < numGlyphs; i++)
1781  {
1782  this.Lengths[i] = this.IntOffsets[i + 1] - this.IntOffsets[i];
1783  }
1784  }
1785 
1786  }
1787 
1788  public byte[] GetBytes()
1789  {
1790  using (MemoryStream ms = new MemoryStream())
1791  {
1792 
1793  if (IntOffsets == null)
1794  {
1795  for (int i = 0; i < ShortOffsets.Length; i++)
1796  {
1797  ms.WriteUShort(ShortOffsets[i]);
1798  }
1799  }
1800  else
1801  {
1802  for (int i = 0; i < IntOffsets.Length; i++)
1803  {
1804  ms.WriteUInt(IntOffsets[i]);
1805  }
1806  }
1807 
1808  return ms.ToArray();
1809  }
1810  }
1811  }
1812 
1813  internal class TrueTypeRawTable : ITrueTypeTable
1814  {
1815  public byte[] Data { get; }
1816 
1817  public TrueTypeRawTable(Stream sr, uint length)
1818  {
1819  this.Data = new byte[length];
1820  sr.Read(this.Data, 0, (int)length);
1821  }
1822 
1823  public byte[] GetBytes()
1824  {
1825  return Data;
1826  }
1827  }
1828 
1829  internal bool HasCmap4Table()
1830  {
1831  foreach (ICmapTable cmap in ((TrueTypeCmapTable)Tables["cmap"]).ActualCmapTables)
1832  {
1833  if (cmap is CmapTable4)
1834  {
1835  return true;
1836  }
1837 
1838  }
1839  return false;
1840  }
1841 
1842  /// <summary>
1843  /// Obtains the font family name from the TrueType file.
1844  /// </summary>
1845  /// <returns>The font family name, if available; <see langword="null"/> otherwise.</returns>
1846  public string GetFontFamilyName()
1847  {
1848  TrueTypeNameTable name = (TrueTypeNameTable)this.Tables["name"];
1849 
1850  for (int i = 0; i < name.Count; i++)
1851  {
1852  if (name.NameRecords[i].NameID == 16)
1853  {
1854  return name.Name[i];
1855  }
1856  }
1857 
1858  for (int i = 0; i < name.Count; i++)
1859  {
1860  if (name.NameRecords[i].NameID == 1)
1861  {
1862  return name.Name[i];
1863  }
1864  }
1865 
1866  return null;
1867  }
1868 
1869  /// <summary>
1870  /// Obtains the full font family name from the TrueType file.
1871  /// </summary>
1872  /// <returns>The full font family name, if available; <see langword="null"/> otherwise.</returns>
1873  public string GetFullFontFamilyName()
1874  {
1875  TrueTypeNameTable name = (TrueTypeNameTable)this.Tables["name"];
1876 
1877  for (int i = 0; i < name.Count; i++)
1878  {
1879  if (name.NameRecords[i].NameID == 4)
1880  {
1881  return name.Name[i];
1882  }
1883  }
1884 
1885  for (int i = 0; i < name.Count; i++)
1886  {
1887  if (name.NameRecords[i].NameID == 6)
1888  {
1889  return name.Name[i];
1890  }
1891  }
1892 
1893  return null;
1894  }
1895 
1896 
1897  /// <summary>
1898  /// Obtains the PostScript font name from the TrueType file.
1899  /// </summary>
1900  /// <returns>The PostScript font name, if available; <see langword="null"/> otherwise.</returns>
1901  public string GetFontName()
1902  {
1903  TrueTypeNameTable name = (TrueTypeNameTable)this.Tables["name"];
1904 
1905  for (int i = 0; i < name.Count; i++)
1906  {
1907  if (name.NameRecords[i].NameID == 6)
1908  {
1909  return name.Name[i];
1910  }
1911  }
1912 
1913  return null;
1914  }
1915 
1916  /// <summary>
1917  /// Returns the index of the first character glyph represented by the font.
1918  /// </summary>
1919  /// <returns>The index of the first character glyph represented by the font.</returns>
1920  public ushort GetFirstCharIndex()
1921  {
1922  TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables["OS/2"];
1923 
1924  return os2.FsFirstCharIndex;
1925  }
1926 
1927  /// <summary>
1928  /// Returns the index of the last character glyph represented by the font.
1929  /// </summary>
1930  /// <returns>The index of the last character glyph represented by the font.</returns>
1931  public ushort GetLastCharIndex()
1932  {
1933  TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables["OS/2"];
1934 
1935  return os2.FsLastCharIndex;
1936  }
1937 
1938 
1939  /// <summary>
1940  /// Determines whether the typeface is Italic or Oblique or not.
1941  /// </summary>
1942  /// <returns>A <see cref="bool"/> indicating whether the typeface is Italic or Oblique or not.</returns>
1943  public bool IsItalic()
1944  {
1945  TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables["OS/2"];
1946 
1947  return (os2.FsSelection & 1) == 1;
1948  }
1949 
1950  /// <summary>
1951  /// Determines whether the typeface is Oblique or not.
1952  /// </summary>
1953  /// <returns>A <see cref="bool"/> indicating whether the typeface is Oblique or not.</returns>
1954  public bool IsOblique()
1955  {
1956  TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables["OS/2"];
1957 
1958  return (os2.FsSelection & 512) != 0;
1959  }
1960 
1961  /// <summary>
1962  /// Determines whether the typeface is Bold or not.
1963  /// </summary>
1964  /// <returns>A <see cref="bool"/> indicating whether the typeface is Bold or not</returns>
1965  public bool IsBold()
1966  {
1967  TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables["OS/2"];
1968 
1969  return (os2.FsSelection & 32) != 0;
1970  }
1971 
1972  /// <summary>
1973  /// Determines whether the typeface is fixed-pitch (aka monospaces) or not.
1974  /// </summary>
1975  /// <returns>A <see cref="bool"/> indicating whether the typeface is fixed-pitch (aka monospaces) or not.</returns>
1976  public bool IsFixedPitch()
1977  {
1978  TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables["OS/2"];
1979 
1980  return os2.Panose.BProportion == 9;
1981  }
1982 
1983  /// <summary>
1984  /// Determines whether the typeface is serifed or not.
1985  /// </summary>
1986  /// <returns>A <see cref="bool"/> indicating whether the typeface is serifed or not.</returns>
1987  public bool IsSerif()
1988  {
1989  TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables["OS/2"];
1990 
1991  return os2.SFamilyClass == 1 || os2.SFamilyClass == 2 || os2.SFamilyClass == 3 || os2.SFamilyClass == 4 || os2.SFamilyClass == 5 || os2.SFamilyClass == 7;
1992  }
1993 
1994  /// <summary>
1995  /// Determines whether the typeface is a script typeface or not.
1996  /// </summary>
1997  /// <returns>A <see cref="bool"/> indicating whether the typeface is a script typeface or not.</returns>
1998  public bool IsScript()
1999  {
2000  TrueTypeOS2Table os2 = (TrueTypeOS2Table)this.Tables["OS/2"];
2001 
2002  return os2.SFamilyClass == 10;
2003  }
2004 
2005  /// <summary>
2006  /// Determines the index of the glyph corresponding to a certain character.
2007  /// </summary>
2008  /// <param name="glyph">The character whose glyph is sought.</param>
2009  /// <returns>The index of the glyph in the TrueType file.</returns>
2010  public int GetGlyphIndex(char glyph)
2011  {
2012  foreach (ICmapTable cmap in ((TrueTypeCmapTable)Tables["cmap"]).ActualCmapTables)
2013  {
2014  if (cmap is CmapTable4)
2015  {
2016  return cmap.GetGlyphIndex(glyph);
2017  }
2018  }
2019 
2020  foreach (ICmapTable cmap in ((TrueTypeCmapTable)Tables["cmap"]).ActualCmapTables)
2021  {
2022  if (cmap is CmapTable0)
2023  {
2024  return cmap.GetGlyphIndex(glyph);
2025  }
2026  }
2027 
2028  return -1;
2029  }
2030 
2031  internal int GetGlyphWidth(int glyphIndex)
2032  {
2033  if (((TrueTypeHmtxTable)this.Tables["hmtx"]).HMetrics.Length > glyphIndex)
2034  {
2035  return ((TrueTypeHmtxTable)this.Tables["hmtx"]).HMetrics[glyphIndex].AdvanceWidth;
2036  }
2037  else
2038  {
2039  return ((TrueTypeHmtxTable)this.Tables["hmtx"]).HMetrics.Last().AdvanceWidth;
2040  }
2041  }
2042 
2043  internal LongHorFixed GetGlyphMetrics(int glyphIndex)
2044  {
2045  if (((TrueTypeHmtxTable)this.Tables["hmtx"]).HMetrics.Length > glyphIndex)
2046  {
2047  return ((TrueTypeHmtxTable)this.Tables["hmtx"]).HMetrics[glyphIndex];
2048  }
2049  else
2050  {
2051  return new LongHorFixed(((TrueTypeHmtxTable)this.Tables["hmtx"]).HMetrics.Last().AdvanceWidth, ((TrueTypeHmtxTable)this.Tables["hmtx"]).LeftSideBearing[glyphIndex - ((TrueTypeHmtxTable)this.Tables["hmtx"]).HMetrics.Length]);
2052  }
2053  }
2054 
2055  /// <summary>
2056  /// Get the path that describes the shape of a glyph.
2057  /// </summary>
2058  /// <param name="glyphIndex">The index of the glyph whose path is sought.</param>
2059  /// <param name="size">The font size to be used for the font coordinates.</param>
2060  /// <returns>An array of contours, each of which is itself an array of TrueType points.</returns>
2061  public TrueTypePoint[][] GetGlyphPath(int glyphIndex, double size)
2062  {
2063  return ((TrueTypeGlyfTable)this.Tables["glyf"]).Glyphs[glyphIndex].GetGlyphPath(size, ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm, ((TrueTypeGlyfTable)this.Tables["glyf"]).Glyphs);
2064  }
2065 
2066  /// <summary>
2067  /// Get the path that describes the shape of a glyph.
2068  /// </summary>
2069  /// <param name="glyph">The glyph whose path is sought.</param>
2070  /// <param name="size">The font size to be used for the font coordinates.</param>
2071  /// <returns>An array of contours, each of which is itself an array of TrueType points.</returns>
2072  public TrueTypePoint[][] GetGlyphPath(char glyph, double size)
2073  {
2074  return ((TrueTypeGlyfTable)this.Tables["glyf"]).Glyphs[GetGlyphIndex(glyph)].GetGlyphPath(size, ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm, ((TrueTypeGlyfTable)this.Tables["glyf"]).Glyphs);
2075  }
2076 
2077  /// <summary>
2078  /// Computes the advance width of a glyph, in thousandths of em unit.
2079  /// </summary>
2080  /// <param name="glyph">The glyph whose advance width is to be computed.</param>
2081  /// <returns>The advance width of the glyph in thousandths of em unit.</returns>
2082  public double Get1000EmGlyphWidth(char glyph)
2083  {
2084  int num = (int)glyph;
2085  if (num < 256)
2086  {
2087  return GlyphWidthsCache[num];
2088  }
2089  else
2090  {
2091  return Get1000EmGlyphWidth(GetGlyphIndex(glyph));
2092  }
2093  }
2094 
2095  /// <summary>
2096  /// Computes the advance width of a glyph, in thousandths of em unit.
2097  /// </summary>
2098  /// <param name="glyphIndex">The index of the glyph whose advance width is to be computed.</param>
2099  /// <returns>The advance width of the glyph in thousandths of em unit.</returns>
2100  public double Get1000EmGlyphWidth(int glyphIndex)
2101  {
2102  int w = GetGlyphWidth(glyphIndex);
2103 
2104  return w * 1000 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm;
2105  }
2106 
2107  /// <summary>
2108  /// Computes the font's Win ascent, in thousandths of em unit.
2109  /// </summary>
2110  /// <returns>The font's Win ascent in thousandths of em unit.</returns>
2111  public double Get1000EmWinAscent()
2112  {
2113  TrueTypeOS2Table os2 = ((TrueTypeOS2Table)this.Tables["OS/2"]);
2114 
2115  bool useTypoMetrics = (os2.FsSelection & 128) != 0;
2116 
2117  if (!useTypoMetrics)
2118  {
2119  return ((TrueTypeOS2Table)this.Tables["OS/2"]).UsWinAscent * 1000.0 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm;
2120  }
2121  else
2122  {
2123  return ((TrueTypeOS2Table)this.Tables["OS/2"]).STypoAscender * 1000.0 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm;
2124  }
2125  }
2126 
2127  /// <summary>
2128  /// Computes the font ascent, in thousandths of em unit.
2129  /// </summary>
2130  /// <returns>The font ascent in thousandths of em unit.</returns>
2131  public double Get1000EmAscent()
2132  {
2133  return ((TrueTypeHHeaTable)this.Tables["hhea"]).Ascent * 1000.0 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm;
2134  }
2135 
2136 
2137  /// <summary>
2138  /// Computes the font descent, in thousandths of em unit.
2139  /// </summary>
2140  /// <returns>The font descent in thousandths of em unit.</returns>
2141  public double Get1000EmDescent()
2142  {
2143  return ((TrueTypeHHeaTable)this.Tables["hhea"]).Descent * 1000.0 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm;
2144  }
2145 
2146  /// <summary>
2147  /// Computes the maximum height over the baseline of the font, in thousandths of em unit.
2148  /// </summary>
2149  /// <returns>The maximum height over the baseline of the font in thousandths of em unit.</returns>
2150  public double Get1000EmYMax()
2151  {
2152  return ((TrueTypeHeadTable)this.Tables["head"]).YMax * 1000.0 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm;
2153  }
2154 
2155  /// <summary>
2156  /// Computes the maximum depth below the baseline of the font, in thousandths of em unit.
2157  /// </summary>
2158  /// <returns>The maximum depth below the baseline of the font in thousandths of em unit.</returns>
2159  public double Get1000EmYMin()
2160  {
2161  return ((TrueTypeHeadTable)this.Tables["head"]).YMin * 1000.0 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm;
2162  }
2163 
2164  /// <summary>
2165  /// Computes the maximum distance to the right of the glyph origin of the font, in thousandths of em unit.
2166  /// </summary>
2167  /// <returns>The maximum distance to the right of the glyph origin of the font in thousandths of em unit.</returns>
2168  public double Get1000EmXMax()
2169  {
2170  return ((TrueTypeHeadTable)this.Tables["head"]).XMax * 1000.0 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm;
2171  }
2172 
2173  /// <summary>
2174  /// Computes the maximum distance to the left of the glyph origin of the font, in thousandths of em unit.
2175  /// </summary>
2176  /// <returns>The maximum distance to the left of the glyph origin of the font in thousandths of em unit.</returns>
2177  public double Get1000EmXMin()
2178  {
2179  return ((TrueTypeHeadTable)this.Tables["head"]).XMin * 1000.0 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm;
2180  }
2181 
2182  /// <summary>
2183  /// Represents the left- and right-side bearings of a glyph.
2184  /// </summary>
2185  public struct Bearings
2186  {
2187  /// <summary>
2188  /// The left-side bearing of the glyph.
2189  /// </summary>
2190  public int LeftSideBearing;
2191 
2192  /// <summary>
2193  /// The right-side bearing of the glyph.
2194  /// </summary>
2195  public int RightSideBearing;
2196 
2197  internal Bearings(int lsb, int rsb)
2198  {
2199  LeftSideBearing = lsb;
2200  RightSideBearing = rsb;
2201  }
2202  }
2203 
2204  internal Bearings Get1000EmGlyphBearings(int glyphIndex)
2205  {
2206  LongHorFixed metrics = GetGlyphMetrics(glyphIndex);
2207 
2208  int lsb = metrics.LeftSideBearing;
2209 
2210  Glyph glyph = ((TrueTypeGlyfTable)this.Tables["glyf"]).Glyphs[glyphIndex];
2211 
2212  int rsb = metrics.AdvanceWidth - (lsb + glyph.XMax - glyph.XMin);
2213 
2214  return new Bearings(lsb * 1000 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm, rsb * 1000 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm);
2215  }
2216 
2217 
2218  /// <summary>
2219  /// Computes the left- and right- side bearings of a glyph, in thousandths of em unit.
2220  /// </summary>
2221  /// <param name="glyph">The glyph whose bearings are to be computed.</param>
2222  /// <returns>The left- and right- side bearings of the glyph in thousandths of em unit</returns>
2224  {
2225  int num = (int)glyph;
2226 
2227  if (num < 256)
2228  {
2229  return BearingsCache[num];
2230  }
2231  else
2232  {
2233  return Get1000EmGlyphBearings(GetGlyphIndex(glyph));
2234  }
2235  }
2236 
2237  /// <summary>
2238  /// Represents the maximum heigth above and depth below the baseline of a glyph.
2239  /// </summary>
2240  public struct VerticalMetrics
2241  {
2242  /// <summary>
2243  /// The maximum depth below the baseline of the glyph.
2244  /// </summary>
2245  public int YMin;
2246 
2247  /// <summary>
2248  /// The maximum height above the baseline of the glyph.
2249  /// </summary>
2250  public int YMax;
2251 
2252  internal VerticalMetrics(int yMin, int yMax)
2253  {
2254  this.YMin = yMin;
2255  this.YMax = yMax;
2256  }
2257  }
2258 
2259  internal VerticalMetrics Get1000EmGlyphVerticalMetrics(int glyphIndex)
2260  {
2261  Glyph glyph = ((TrueTypeGlyfTable)this.Tables["glyf"]).Glyphs[glyphIndex];
2262 
2263  return new VerticalMetrics(glyph.YMin * 1000 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm, glyph.YMax * 1000 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm);
2264  }
2265 
2266  /// <summary>
2267  /// Computes the vertical metrics of a glyph, in thousandths of em unit.
2268  /// </summary>
2269  /// <param name="glyph">The glyph whose vertical metrics are to be computed.</param>
2270  /// <returns>The vertical metrics of a glyph, in thousandths of em unit.</returns>
2272  {
2273  int num = (int)glyph;
2274  if (num < 256)
2275  {
2276  return VerticalMetricsCache[num];
2277  }
2278  else
2279  {
2280  return Get1000EmGlyphVerticalMetrics(GetGlyphIndex(glyph));
2281  }
2282  }
2283 
2284  /// <summary>
2285  /// Computes the distance of the top of the underline from the baseline, in thousandths of em unit.
2286  /// </summary>
2287  /// <returns>The distance of the top of the underline from the baseline, in thousandths of em unit.</returns>
2289  {
2290  if (this.Tables.TryGetValue("post", out ITrueTypeTable table) && table is TrueTypePostTable post)
2291  {
2292  return post.UnderlinePosition * 1000.0 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm;
2293  }
2294  else
2295  {
2296  return double.NaN;
2297  }
2298  }
2299 
2300  /// <summary>
2301  /// Computes the thickness of the underline, in thousandths of em unit.
2302  /// </summary>
2303  /// <returns>The thickness of the underline, in thousandths of em unit.</returns>
2305  {
2306  if (this.Tables.TryGetValue("post", out ITrueTypeTable table) && table is TrueTypePostTable post)
2307  {
2308  return post.UnderlineThickness * 1000.0 / ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm;
2309  }
2310  else
2311  {
2312  return double.NaN;
2313  }
2314  }
2315 
2316  /// <summary>
2317  /// Computes the italic angle for the current font, in thousandths of em unit. This is computed from the vertical and is negative for text that leans forwards.
2318  /// </summary>
2319  /// <returns></returns>
2320  public double GetItalicAngle()
2321  {
2322  if (this.Tables.TryGetValue("post", out ITrueTypeTable table) && table is TrueTypePostTable post)
2323  {
2324  return post.ItalicAngle.Bits / Math.Pow(2, post.ItalicAngle.BitShifts);
2325  }
2326  else
2327  {
2328  return double.NaN;
2329  }
2330  }
2331 
2332  /// <summary>
2333  /// Computes the intersections between an underline at the specified position and thickness and a glyph, in thousandths of em units.
2334  /// </summary>
2335  /// <param name="glyph">The glyph whose intersections with the underline will be computed.</param>
2336  /// <param name="position">The distance of the top of the underline from the baseline, in thousandths of em unit.</param>
2337  /// <param name="thickness">The thickness of the underline, in thousandths of em unit.</param>
2338  /// <returns>If the underline does not intersect the glyph, this method returns <see langword="null" />. Otherwise, it returns an array
2339  /// containing two elements, representing the horizontal coordinates of the leftmost and rightmost intersection points.</returns>
2340  public double[] Get1000EmUnderlineIntersections(char glyph, double position, double thickness)
2341  {
2342  List<double> intersections = new List<double>();
2343 
2344  TrueTypePoint[][] glyphPaths = GetGlyphPath(glyph, 1000);
2345 
2346  for (int j = 0; j < glyphPaths.Length; j++)
2347  {
2348  double[] currPoint = new double[] { glyphPaths[j][0].X, -glyphPaths[j][0].Y };
2349 
2350  for (int k = 1; k < glyphPaths[j].Length; k++)
2351  {
2352  if (glyphPaths[j][k].IsOnCurve)
2353  {
2354  if (-glyphPaths[j][k].Y - currPoint[1] != 0)
2355  {
2356  double t = (position - currPoint[1]) / (-glyphPaths[j][k].Y - currPoint[1]);
2357  if (t >= 0 && t <= 1)
2358  {
2359  intersections.Add(currPoint[0] + t * (glyphPaths[j][k].X - currPoint[0]));
2360  }
2361 
2362  t = (position + thickness - currPoint[1]) / (-glyphPaths[j][k].Y - currPoint[1]);
2363  if (t >= 0 && t <= 1)
2364  {
2365  intersections.Add(currPoint[0] + t * (glyphPaths[j][k].X - currPoint[0]));
2366  }
2367  }
2368  else
2369  {
2370  if (position <= currPoint[1] && position + thickness >= currPoint[1])
2371  {
2372  intersections.Add(currPoint[0]);
2373  intersections.Add(glyphPaths[j][k].X);
2374  }
2375  }
2376 
2377  currPoint = new double[] { glyphPaths[j][k].X, -glyphPaths[j][k].Y };
2378  }
2379  else
2380  {
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 };
2383 
2384  double a = currPoint[1] - 2 * ctrlPoint[1] + endPoint[1];
2385 
2386  if (Math.Abs(a) < 1e-7)
2387  {
2388  k++;
2389 
2390  if (-glyphPaths[j][k].Y - currPoint[1] != 0)
2391  {
2392  double t = (position - currPoint[1]) / (-glyphPaths[j][k].Y - currPoint[1]);
2393  if (t >= 0 && t <= 1)
2394  {
2395  intersections.Add(currPoint[0] + t * (glyphPaths[j][k].X - currPoint[0]));
2396  }
2397 
2398  t = (position + thickness - currPoint[1]) / (-glyphPaths[j][k].Y - currPoint[1]);
2399  if (t >= 0 && t <= 1)
2400  {
2401  intersections.Add(currPoint[0] + t * (glyphPaths[j][k].X - currPoint[0]));
2402  }
2403  }
2404  else
2405  {
2406  if (position <= currPoint[1] && position + thickness >= currPoint[1])
2407  {
2408  intersections.Add(currPoint[0]);
2409  intersections.Add(glyphPaths[j][k].X);
2410  }
2411  }
2412  }
2413  else
2414  {
2415  double[] ts = new double[4];
2416 
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;
2419 
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;
2422 
2423  double minT = double.MaxValue;
2424  double maxT = double.MinValue;
2425  bool found = false;
2426 
2427  for (int i = 0; i < 4; i++)
2428  {
2429  if (!double.IsNaN(ts[i]) && ts[i] >= 0 && ts[i] <= 1)
2430  {
2431  minT = Math.Min(minT, ts[i]);
2432  maxT = Math.Max(maxT, ts[i]);
2433 
2434  found = true;
2435  }
2436  }
2437 
2438  if (found)
2439  {
2440  double critT = (currPoint[0] - ctrlPoint[0]) / (currPoint[0] - 2 * ctrlPoint[0] + endPoint[0]);
2441 
2442  if (critT >= minT && critT <= maxT)
2443  {
2444  intersections.Add((1 - critT) * (1 - critT) * currPoint[0] + 2 * critT * (1 - critT) * ctrlPoint[0] + critT * critT * endPoint[0]);
2445  }
2446 
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]);
2449  }
2450 
2451  k++;
2452  }
2453 
2454  currPoint = endPoint;
2455  }
2456  }
2457  }
2458 
2459  if (intersections.Count > 1)
2460  {
2461  double[] tbr = new double[2] { double.MaxValue, double.MinValue };
2462 
2463  for (int i = 0; i < intersections.Count; i++)
2464  {
2465  if (intersections[i] <= tbr[0])
2466  {
2467  tbr[0] = intersections[i];
2468  }
2469 
2470  if (intersections[i] >= tbr[1])
2471  {
2472  tbr[1] = intersections[i];
2473  }
2474  }
2475 
2476  return tbr;
2477  }
2478  else
2479  {
2480  return null;
2481  }
2482  }
2483 
2484  /// <summary>
2485  /// Gets the kerning between two glyphs.
2486  /// </summary>
2487  /// <param name="glyph1">The first glyph of the kerning pair.</param>
2488  /// <param name="glyph2">The second glyph of the kerning pair.</param>
2489  /// <returns>A <see cref="PairKerning"/> object containing information about how the position of each glyphs should be altered.</returns>
2490  public PairKerning Get1000EmKerning(char glyph1, char glyph2)
2491  {
2492  int glyph1Index = this.GetGlyphIndex(glyph1);
2493  int glyph2Index = this.GetGlyphIndex(glyph2);
2494 
2495  return Get1000EmKerning(glyph1Index, glyph2Index);
2496  }
2497 
2498  /// <summary>
2499  /// Gets the kerning between two glyphs.
2500  /// </summary>
2501  /// <param name="glyph1Index">The index of the first glyph of the kerning pair.</param>
2502  /// <param name="glyph2Index">The index of the second glyph of the kerning pair.</param>
2503  /// <returns>A <see cref="PairKerning"/> object containing information about how the position of each glyphs should be altered.</returns>
2504  public PairKerning Get1000EmKerning(int glyph1Index, int glyph2Index)
2505  {
2506  if (this.Tables.TryGetValue("GPOS", out ITrueTypeTable table) && table is TrueTypeGPOSTable gpos)
2507  {
2508  PairKerning kerning = gpos.GetKerning(glyph1Index, glyph2Index);
2509 
2510  if (kerning != null)
2511  {
2512  double units = ((TrueTypeHeadTable)this.Tables["head"]).UnitsPerEm;
2513 
2514  return new PairKerning(new Point(kerning.Glyph1Placement.X * 1000 / units, kerning.Glyph1Placement.Y * 1000 / units),
2515  new Point(kerning.Glyph1Advance.X * 1000 / units, kerning.Glyph1Advance.Y * 1000 / units),
2516  new Point(kerning.Glyph2Placement.X * 1000 / units, kerning.Glyph2Placement.Y * 1000 / units),
2517  new Point(kerning.Glyph2Advance.X * 1000 / units, kerning.Glyph2Advance.Y * 1000 / units));
2518  }
2519  else
2520  {
2521  return null;
2522  }
2523  }
2524  else
2525  {
2526  return null;
2527  }
2528  }
2529 
2530  internal interface ITrueTypeTable
2531  {
2532  byte[] GetBytes();
2533  }
2534 
2535  internal interface ICmapTable
2536  {
2537  ushort Format { get; }
2538  ushort Length { get; }
2539  ushort Language { get; }
2540 
2541  int GetGlyphIndex(char glyph);
2542 
2543  byte[] GetBytes();
2544  }
2545 
2546  internal class CmapTable4 : ICmapTable
2547  {
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; }
2561 
2562  public CmapTable4() { }
2563 
2564  public CmapTable4(ushort format, ushort length, ushort language, Stream sr)
2565  {
2566  this.Format = format;
2567  this.Length = length;
2568  this.Language = language;
2569  this.SegCountX2 = sr.ReadUShort();
2570 
2571  int segCount = this.SegCountX2 / 2;
2572 
2573  this.SearchRange = sr.ReadUShort();
2574  this.EntrySelector = sr.ReadUShort();
2575  this.RangeShift = sr.ReadUShort();
2576 
2577  this.EndCode = new ushort[segCount];
2578  for (int i = 0; i < segCount; i++)
2579  {
2580  this.EndCode[i] = sr.ReadUShort();
2581  }
2582 
2583  this.ReservedPad = sr.ReadUShort();
2584 
2585  this.StartCode = new ushort[segCount];
2586  for (int i = 0; i < segCount; i++)
2587  {
2588  this.StartCode[i] = sr.ReadUShort();
2589  }
2590 
2591  this.IdDelta = new ushort[segCount];
2592  for (int i = 0; i < segCount; i++)
2593  {
2594  this.IdDelta[i] = sr.ReadUShort();
2595  }
2596 
2597  this.IdRangeOffset = new ushort[segCount];
2598  for (int i = 0; i < segCount; i++)
2599  {
2600  this.IdRangeOffset[i] = sr.ReadUShort();
2601  }
2602 
2603  int numGlyphIndices = (this.Length - 16 + 8 * segCount) / 2;
2604 
2605  this.GlyphIndexArray = new ushort[numGlyphIndices];
2606 
2607  for (int i = 0; i < numGlyphIndices; i++)
2608  {
2609  this.GlyphIndexArray[i] = sr.ReadUShort();
2610  }
2611  }
2612 
2613  public int GetGlyphIndex(char glyph)
2614  {
2615  int code = (int)glyph;
2616 
2617  int endCodeInd = -1;
2618 
2619  for (int i = 0; i < EndCode.Length; i++)
2620  {
2621  if (EndCode[i] >= code)
2622  {
2623  endCodeInd = i;
2624  break;
2625  }
2626  }
2627 
2628  if (StartCode[endCodeInd] <= code)
2629  {
2630  if (IdRangeOffset[endCodeInd] != 0)
2631  {
2632  int glyphIndexIndex = IdRangeOffset[endCodeInd] / 2 + (code - StartCode[endCodeInd]) - (IdRangeOffset.Length - endCodeInd);
2633 
2634  if (GlyphIndexArray[glyphIndexIndex] != 0)
2635  {
2636  return (IdDelta[endCodeInd] + GlyphIndexArray[glyphIndexIndex]) % 65536;
2637  }
2638  else
2639  {
2640  return 0;
2641  }
2642  }
2643  else
2644  {
2645  return (code + IdDelta[endCodeInd]) % 65536;
2646  }
2647  }
2648  else
2649  {
2650  return 0;
2651  }
2652  }
2653 
2654  public byte[] GetBytes()
2655  {
2656  using (MemoryStream ms = new MemoryStream())
2657  {
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++)
2666  {
2667  ms.WriteUShort(this.EndCode[i]);
2668  }
2669  ms.WriteUShort(this.ReservedPad);
2670 
2671  for (int i = 0; i < this.StartCode.Length; i++)
2672  {
2673  ms.WriteUShort(this.StartCode[i]);
2674  }
2675  for (int i = 0; i < this.IdDelta.Length; i++)
2676  {
2677  ms.WriteUShort(this.IdDelta[i]);
2678  }
2679  for (int i = 0; i < this.IdRangeOffset.Length; i++)
2680  {
2681  ms.WriteUShort(this.IdRangeOffset[i]);
2682  }
2683  for (int i = 0; i < this.GlyphIndexArray.Length; i++)
2684  {
2685  ms.WriteUShort(this.GlyphIndexArray[i]);
2686  }
2687 
2688  return ms.ToArray();
2689  }
2690  }
2691  }
2692 
2693  internal class CmapTable0 : ICmapTable
2694  {
2695  public ushort Format { get; }
2696  public ushort Length { get; }
2697  public ushort Language { get; }
2698 
2699  public byte[] GlyphIndexArray { get; }
2700 
2701  public CmapTable0(ushort format, ushort length, ushort language, byte[] glyphIndexArray)
2702  {
2703  this.Format = format;
2704  this.Length = length;
2705  this.Language = language;
2706  this.GlyphIndexArray = glyphIndexArray;
2707  }
2708 
2709  public int GetGlyphIndex(char glyph)
2710  {
2711  return GlyphIndexArray[(byte)glyph];
2712  }
2713 
2714  public byte[] GetBytes()
2715  {
2716  using (MemoryStream ms = new MemoryStream())
2717  {
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();
2723  }
2724  }
2725  }
2726 
2727  internal struct CmapSubTable
2728  {
2729  public ushort PlatformID;
2730  public ushort PlatformSpecificID;
2731  public uint Offset;
2732 
2733  public CmapSubTable(ushort platformID, ushort platformSpecificID, uint offset)
2734  {
2735  this.PlatformID = platformID;
2736  this.PlatformSpecificID = platformSpecificID;
2737  this.Offset = offset;
2738  }
2739  }
2740 
2741  internal class TrueTypeCmapTable : ITrueTypeTable
2742  {
2743  public ushort Version;
2744  public ushort NumberSubTables;
2745  public CmapSubTable[] SubTables;
2746  public ICmapTable[] ActualCmapTables;
2747 
2748  public byte[] GetBytes()
2749  {
2750  using (MemoryStream ms = new MemoryStream())
2751  {
2752  ms.WriteUShort(this.Version);
2753  ms.WriteUShort(this.NumberSubTables);
2754 
2755  for (int i = 0; i < this.SubTables.Length; i++)
2756  {
2757  ms.WriteUShort(this.SubTables[i].PlatformID);
2758  ms.WriteUShort(this.SubTables[i].PlatformSpecificID);
2759  ms.WriteUInt(this.SubTables[i].Offset);
2760  }
2761 
2762  for (int i = 0; i < this.ActualCmapTables.Length; i++)
2763  {
2764  byte[] bytes = this.ActualCmapTables[i].GetBytes();
2765  ms.Write(bytes, 0, bytes.Length);
2766  }
2767 
2768  return ms.ToArray();
2769  }
2770  }
2771  }
2772 
2773  internal class TrueTypeNameTable : ITrueTypeTable
2774  {
2775  public byte[] GetBytes()
2776  {
2777  using (MemoryStream ms = new MemoryStream())
2778  {
2779  ms.WriteUShort(this.Format);
2780  ms.WriteUShort(this.Count);
2781  ms.WriteUShort(this.StringOffset);
2782 
2783  for (int i = 0; i < this.NameRecords.Length; i++)
2784  {
2785  byte[] bytes = this.NameRecords[i].GetBytes();
2786  ms.Write(bytes, 0, bytes.Length);
2787  }
2788 
2789  ms.Write(this.RawBytes, 0, this.RawBytes.Length);
2790 
2791  return ms.ToArray();
2792  }
2793  }
2794 
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;
2801 
2802  internal struct NameRecord
2803  {
2804  public ushort PlatformID;
2805  public ushort PlatformSpecificID;
2806  public ushort LanguageID;
2807  public ushort NameID;
2808  public ushort Length;
2809  public ushort Offset;
2810 
2811  public NameRecord(ushort platformID, ushort platformSpecificID, ushort languageID, ushort nameID, ushort length, ushort offset)
2812  {
2813  this.PlatformID = platformID;
2814  this.PlatformSpecificID = platformSpecificID;
2815  this.LanguageID = languageID;
2816  this.NameID = nameID;
2817  this.Length = length;
2818  this.Offset = offset;
2819  }
2820 
2821  public byte[] GetBytes()
2822  {
2823  using (MemoryStream ms = new MemoryStream())
2824  {
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();
2832  }
2833  }
2834  }
2835 
2836  internal TrueTypeNameTable(uint tableOffset, Stream sr)
2837  {
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];
2843 
2844  int maxOffsetItem = -1;
2845 
2846  for (int i = 0; i < this.Count; i++)
2847  {
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))
2850  {
2851  maxOffsetItem = i;
2852  }
2853  }
2854 
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);
2858 
2859  for (int i = 0; i < this.Count; i++)
2860  {
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);
2864 
2865  if (this.NameRecords[i].PlatformID == 0)
2866  {
2867  this.Name[i] = Encoding.BigEndianUnicode.GetString(stringBytes);
2868  }
2869  else if (this.NameRecords[i].PlatformID == 1 && this.NameRecords[i].PlatformSpecificID == 0)
2870  {
2871  this.Name[i] = GetMacRomanString(stringBytes);
2872  }
2873  else if (this.NameRecords[i].PlatformID == 3 && (this.NameRecords[i].PlatformSpecificID == 1 || this.NameRecords[i].PlatformSpecificID == 0))
2874  {
2875  this.Name[i] = Encoding.BigEndianUnicode.GetString(stringBytes);
2876  }
2877  else
2878  {
2879  this.Name[i] = "Unsupported encoding: " + this.NameRecords[i].PlatformID.ToString() + "/" + this.NameRecords[i].PlatformSpecificID.ToString();
2880  }
2881 
2882  }
2883  }
2884 
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' };
2886 
2887  private static string GetMacRomanString(byte[] bytes)
2888  {
2889  StringBuilder bld = new StringBuilder(bytes.Length);
2890 
2891  for (int i = 0; i < bytes.Length; i++)
2892  {
2893  if (bytes[i] >= 32)
2894  {
2895  bld.Append(MacRomanChars[bytes[i] - 32]);
2896  }
2897  else
2898  {
2899  bld.Append((char)bytes[i]);
2900  }
2901  }
2902 
2903  return bld.ToString();
2904 
2905  }
2906  }
2907 
2908  internal class TrueTypeOS2Table : ITrueTypeTable
2909  {
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;
2938 
2939  public uint[] UlCodePageRange;
2940 
2941  public short SxHeight;
2942  public short SCapHeight;
2943  public ushort UsDefaultChar;
2944  public ushort UsBreakChar;
2945  public ushort UsMaxContext;
2946 
2947  public ushort UsLowerOpticalPointSize;
2948  public ushort UsUpperOpticalPointSize;
2949 
2950  public byte[] GetBytes()
2951  {
2952  using (MemoryStream ms = new MemoryStream())
2953  {
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++)
2973  {
2974  ms.WriteUInt(this.UlUnicodeRange[i]);
2975  }
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);
2985 
2986  if (this.Version >= 1)
2987  {
2988  for (int i = 0; i < this.UlCodePageRange.Length; i++)
2989  {
2990  ms.WriteUInt(this.UlCodePageRange[i]);
2991  }
2992 
2993  if (this.Version >= 2)
2994  {
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);
3000 
3001  if (this.Version >= 5)
3002  {
3003  ms.WriteUShort(this.UsLowerOpticalPointSize);
3004  ms.WriteUShort(this.UsUpperOpticalPointSize);
3005  }
3006  }
3007  }
3008 
3009  return ms.ToArray();
3010  }
3011  }
3012 
3013  internal struct PANOSE
3014  {
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;
3025 
3026  public PANOSE(byte bFamilyType, byte bSerifStyle, byte bWeight, byte bProportion, byte bContrast, byte bStrokeVariation, byte bArmStyle, byte bLetterform, byte bMidline, byte bXHeight)
3027  {
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;
3038  }
3039 
3040  public byte[] GetBytes()
3041  {
3042  return new byte[] { BFamilyType, BSerifStyle, BWeight, BProportion, BContrast, BStrokeVariation, BArmStyle, BLetterform, BMidline, BXHeight };
3043  }
3044  }
3045  }
3046 
3047  internal class TrueTypePostTable : ITrueTypeTable
3048  {
3049  public Fixed Version;
3050  public Fixed ItalicAngle;
3051  public short UnderlinePosition;
3052  public short UnderlineThickness;
3053  public uint IsFixedPitch;
3054  public uint MinMemType42;
3055  public uint MaxMemType42;
3056  public uint MinMemType1;
3057  public uint MaxMemType1;
3058 
3059  public ushort NumGlyphs;
3060  public ushort[] GlyphNameIndex;
3061  public string[] Names;
3062 
3063  public byte[] GetBytes()
3064  {
3065  using (MemoryStream ms = new MemoryStream())
3066  {
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);
3076 
3077  if (this.Version.Bits == 0x00020000)
3078  {
3079  ms.WriteUShort(NumGlyphs);
3080  for (int i = 0; i < GlyphNameIndex.Length; i++)
3081  {
3082  ms.WriteUShort(GlyphNameIndex[i]);
3083  }
3084 
3085  for (int i = 0; i < Names.Length; i++)
3086  {
3087  ms.WritePascalString(Names[i]);
3088  }
3089  }
3090 
3091  return ms.ToArray();
3092  }
3093  }
3094  }
3095 
3096  internal class TrueTypeHmtxTable : ITrueTypeTable
3097  {
3098  public LongHorFixed[] HMetrics;
3099  public short[] LeftSideBearing;
3100 
3101  public byte[] GetBytes()
3102  {
3103  using (MemoryStream ms = new MemoryStream())
3104  {
3105  for (int i = 0; i < HMetrics.Length; i++)
3106  {
3107  ms.WriteUShort(HMetrics[i].AdvanceWidth);
3108  ms.WriteShort(HMetrics[i].LeftSideBearing);
3109  }
3110  for (int i = 0; i < LeftSideBearing.Length; i++)
3111  {
3112  ms.WriteShort(LeftSideBearing[i]);
3113  }
3114 
3115  return ms.ToArray();
3116  }
3117  }
3118  }
3119 
3120  internal class TrueTypeMaxpTable : ITrueTypeTable
3121  {
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;
3137 
3138  public byte[] GetBytes()
3139  {
3140  using (MemoryStream ms = new MemoryStream())
3141  {
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();
3158  }
3159  }
3160  }
3161 
3162  internal class TrueTypeHeadTable : ITrueTypeTable
3163  {
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;
3172  public short XMin;
3173  public short YMin;
3174  public short XMax;
3175  public short YMax;
3176  public ushort MacStyle;
3177  public ushort LowestRecPPEM;
3178  public short FontDirectionInt;
3179  public short IndexToLocFormat;
3180  public short GlyphDataFormat;
3181 
3182  public byte[] GetBytes()
3183  {
3184  using (MemoryStream ms = new MemoryStream())
3185  {
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();
3204  }
3205  }
3206 
3207  }
3208 
3209  internal class TrueTypeHHeaTable : ITrueTypeTable
3210  {
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;
3224 
3225  public byte[] GetBytes()
3226  {
3227  using (MemoryStream ms = new MemoryStream())
3228  {
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);
3240  ms.WriteShort(0);
3241  ms.WriteShort(0);
3242  ms.WriteShort(0);
3243  ms.WriteShort(0);
3244  ms.WriteShort(this.MetricDataFormat);
3245  ms.WriteUShort(this.NumOfLongHorMetrics);
3246  return ms.ToArray();
3247  }
3248  }
3249  }
3250 
3251 
3252  internal class LangSysTable
3253  {
3254  public string Tag;
3255  public ushort LookupOrderOffset;
3256  public ushort RequiredFeatureIndex;
3257  public ushort[] FeatureIndices;
3258 
3259  public LangSysTable(string tag, Stream fs)
3260  {
3261  this.Tag = tag;
3262  this.LookupOrderOffset = fs.ReadUShort();
3263  this.RequiredFeatureIndex = fs.ReadUShort();
3264  ushort featureIndexCount = fs.ReadUShort();
3265 
3266  this.FeatureIndices = new ushort[featureIndexCount];
3267 
3268  for (int i = 0; i < featureIndexCount; i++)
3269  {
3270  this.FeatureIndices[i] = fs.ReadUShort();
3271  }
3272  }
3273 
3274  public byte[] GetBytes()
3275  {
3276  using (MemoryStream ms = new MemoryStream(6 + this.FeatureIndices.Length * 2))
3277  {
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++)
3282  {
3283  ms.WriteUShort(this.FeatureIndices[i]);
3284  }
3285 
3286  return ms.ToArray();
3287  }
3288  }
3289  }
3290 
3291  internal class ScriptTable
3292  {
3293  public string Tag;
3294  public LangSysTable DefaultLangSys;
3295  public LangSysTable[] LangSysRecords;
3296 
3297  public ScriptTable(string tag, Stream fs)
3298  {
3299  this.Tag = tag;
3300 
3301  long startPosition = fs.Position;
3302 
3303  ushort defaultLangSysOffset = fs.ReadUShort();
3304  ushort langSysCount = fs.ReadUShort();
3305 
3306  (string, ushort)[] langSysRecords = new (string, ushort)[langSysCount];
3307 
3308  for (int i = 0; i < langSysCount; i++)
3309  {
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());
3315 
3316  langSysRecords[i] = (langSysTag.ToString(), fs.ReadUShort());
3317  }
3318 
3319  if (defaultLangSysOffset != 0)
3320  {
3321  fs.Seek(startPosition + defaultLangSysOffset, SeekOrigin.Begin);
3322  this.DefaultLangSys = new LangSysTable(null, fs);
3323  }
3324  else
3325  {
3326  this.DefaultLangSys = null;
3327  }
3328 
3329  this.LangSysRecords = new LangSysTable[langSysCount];
3330 
3331  for (int i = 0; i < langSysCount; i++)
3332  {
3333  fs.Seek(startPosition + langSysRecords[i].Item2, SeekOrigin.Begin);
3334  this.LangSysRecords[i] = new LangSysTable(langSysRecords[i].Item1, fs);
3335  }
3336  }
3337 
3338  public byte[] GetBytes()
3339  {
3340  using (MemoryStream ms = new MemoryStream())
3341  {
3342  int offset = 6 * this.LangSysRecords.Length + 2 + 2;
3343 
3344  if (this.DefaultLangSys != null)
3345  {
3346  ms.WriteUShort((ushort)offset);
3347  }
3348  else
3349  {
3350  ms.WriteUShort(0);
3351  }
3352 
3353  ms.WriteUShort((ushort)this.LangSysRecords.Length);
3354 
3355  byte[] defaultLangSysTable = DefaultLangSys != null ? DefaultLangSys.GetBytes() : new byte[0];
3356 
3357  byte[][] langSysTables = new byte[this.LangSysRecords.Length][];
3358 
3359  offset += defaultLangSysTable.Length;
3360 
3361  int[] offsets = new int[this.LangSysRecords.Length];
3362 
3363  for (int i = 0; i < this.LangSysRecords.Length; i++)
3364  {
3365  offsets[i] = offset;
3366  langSysTables[i] = this.LangSysRecords[i].GetBytes();
3367  offset += langSysTables[i].Length;
3368  }
3369 
3370  for (int i = 0; i < this.LangSysRecords.Length; i++)
3371  {
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]);
3376 
3377  ms.WriteUShort((ushort)offsets[i]);
3378  }
3379 
3380  ms.Write(defaultLangSysTable, 0, defaultLangSysTable.Length);
3381 
3382  for (int i = 0; i < langSysTables[i].Length; i++)
3383  {
3384  ms.Write(langSysTables[i], 0, langSysTables[i].Length);
3385  }
3386 
3387  return ms.ToArray();
3388  }
3389  }
3390  }
3391 
3392 
3393  internal class ScriptList
3394  {
3395  public ScriptTable[] ScriptRecords;
3396  public ScriptList(Stream fs)
3397  {
3398  long startPosition = fs.Position;
3399 
3400  ushort scriptCount = fs.ReadUShort();
3401 
3402  (string, ushort)[] scriptRecords = new (string, ushort)[scriptCount];
3403 
3404  for (int i = 0; i < scriptCount; i++)
3405  {
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());
3411 
3412  scriptRecords[i] = (scriptTag.ToString(), fs.ReadUShort());
3413  }
3414 
3415  this.ScriptRecords = new ScriptTable[scriptCount];
3416 
3417  for (int i = 0; i < scriptCount; i++)
3418  {
3419  fs.Seek(startPosition + scriptRecords[i].Item2, SeekOrigin.Begin);
3420  this.ScriptRecords[i] = new ScriptTable(scriptRecords[i].Item1, fs);
3421  }
3422  }
3423 
3424  public byte[] GetBytes()
3425  {
3426  using (MemoryStream ms = new MemoryStream())
3427  {
3428  int offset = 2 + 6 * this.ScriptRecords.Length;
3429 
3430  ms.WriteUShort((ushort)this.ScriptRecords.Length);
3431 
3432  byte[][] scripts = new byte[this.ScriptRecords.Length][];
3433  int[] offsets = new int[this.ScriptRecords.Length];
3434 
3435  for (int i = 0; i < this.ScriptRecords.Length; i++)
3436  {
3437  offsets[i] = offset;
3438  scripts[i] = this.ScriptRecords[i].GetBytes();
3439  offset += scripts[i].Length;
3440  }
3441 
3442  for (int i = 0; i < this.ScriptRecords.Length; i++)
3443  {
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]);
3448 
3449  ms.WriteUShort((ushort)offsets[i]);
3450  }
3451 
3452  for (int i = 0; i < this.ScriptRecords.Length; i++)
3453  {
3454  ms.Write(scripts[i], 0, scripts[i].Length);
3455  }
3456 
3457  return ms.ToArray();
3458  }
3459  }
3460  }
3461 
3462  internal class LookupTable
3463  {
3464  public ushort LookupType;
3465  public ushort LookupFlag;
3466  public ITrueTypeTable[] SubTables;
3467  public ushort MarkFilteringSet;
3468 
3469  public LookupTable(Stream fs)
3470  {
3471  long startPosition = fs.Position;
3472 
3473  this.LookupType = fs.ReadUShort();
3474  this.LookupFlag = fs.ReadUShort();
3475 
3476  ushort subTableCount = fs.ReadUShort();
3477  ushort[] subTableOffsets = new ushort[subTableCount];
3478 
3479  for (int i = 0; i < subTableCount; i++)
3480  {
3481  subTableOffsets[i] = fs.ReadUShort();
3482  }
3483 
3484  if ((this.LookupFlag & 0x0010) != 0)
3485  {
3486  this.MarkFilteringSet = fs.ReadUShort();
3487  }
3488 
3489  if (this.LookupType == 1)
3490  {
3491  this.SubTables = new ITrueTypeTable[subTableCount];
3492 
3493  for (int i = 0; i < subTableCount; i++)
3494  {
3495  fs.Seek(startPosition + subTableOffsets[i], SeekOrigin.Begin);
3496  this.SubTables[i] = new SinglePosSubtable(fs);
3497  }
3498  }
3499  else if (this.LookupType == 2)
3500  {
3501  this.SubTables = new ITrueTypeTable[subTableCount];
3502 
3503  for (int i = 0; i < subTableCount; i++)
3504  {
3505  fs.Seek(startPosition + subTableOffsets[i], SeekOrigin.Begin);
3506  this.SubTables[i] = new PairPosSubtable(fs);
3507  }
3508  }
3509  else
3510  {
3511  this.SubTables = new ITrueTypeTable[0];
3512  }
3513  }
3514 
3515  public byte[] GetBytes()
3516  {
3517  using (MemoryStream ms = new MemoryStream())
3518  {
3519  ms.WriteUShort(this.LookupType);
3520  ms.WriteUShort(this.LookupFlag);
3521  ms.WriteUShort((ushort)this.SubTables.Length);
3522 
3523  int offset = 6 + 2 * this.SubTables.Length;
3524 
3525  if ((this.LookupFlag & 0x0010) != 0)
3526  {
3527  offset += 2;
3528  }
3529 
3530  byte[][] subTables = new byte[this.SubTables.Length][];
3531  int[] offsets = new int[this.SubTables.Length];
3532 
3533  for (int i = 0; i < this.SubTables.Length; i++)
3534  {
3535  offsets[i] = offset;
3536  subTables[i] = this.SubTables[i].GetBytes();
3537  offset += subTables[i].Length;
3538  }
3539 
3540  for (int i = 0; i < this.SubTables.Length; i++)
3541  {
3542  ms.WriteUShort((ushort)offsets[i]);
3543  }
3544 
3545  if ((this.LookupFlag & 0x0010) != 0)
3546  {
3547  ms.WriteUShort(this.MarkFilteringSet);
3548  }
3549 
3550  for (int i = 0; i < this.SubTables.Length; i++)
3551  {
3552  ms.Write(subTables[i], 0, subTables[i].Length);
3553  }
3554 
3555  return ms.ToArray();
3556  }
3557  }
3558  }
3559 
3560  internal class LookupList
3561  {
3562  public LookupTable[] LookupTables;
3563 
3564  public LookupList(Stream fs)
3565  {
3566  long startPosition = fs.Position;
3567 
3568  ushort lookupCount = fs.ReadUShort();
3569  ushort[] lookupOffsets = new ushort[lookupCount];
3570 
3571  for (int i = 0; i < lookupCount; i++)
3572  {
3573  lookupOffsets[i] = fs.ReadUShort();
3574  }
3575 
3576  this.LookupTables = new LookupTable[lookupCount];
3577 
3578  for (int i = 0; i < lookupCount; i++)
3579  {
3580  fs.Seek(startPosition + lookupOffsets[i], SeekOrigin.Begin);
3581  this.LookupTables[i] = new LookupTable(fs);
3582  }
3583  }
3584 
3585  public byte[] GetBytes()
3586  {
3587  using (MemoryStream ms = new MemoryStream())
3588  {
3589  ms.WriteUShort((ushort)this.LookupTables.Length);
3590 
3591  int offset = 2 + 2 * this.LookupTables.Length;
3592 
3593  int[] offsets = new int[this.LookupTables.Length];
3594  byte[][] lookupTables = new byte[this.LookupTables.Length][];
3595 
3596  for (int i = 0; i < this.LookupTables.Length; i++)
3597  {
3598  offsets[i] = offset;
3599  lookupTables[i] = this.LookupTables[i].GetBytes();
3600  offset += lookupTables[i].Length;
3601  }
3602 
3603  for (int i = 0; i < this.LookupTables.Length; i++)
3604  {
3605  ms.WriteUShort((ushort)offsets[i]);
3606  }
3607 
3608  for (int i = 0; i < this.LookupTables.Length; i++)
3609  {
3610  ms.Write(lookupTables[i], 0, lookupTables[i].Length);
3611  }
3612 
3613  return ms.ToArray();
3614  }
3615  }
3616  }
3617 
3618  internal class CoverageTable
3619  {
3620  public struct RangeRecord
3621  {
3622  public ushort StartGlyphID;
3623  public ushort EndGlyphID;
3624  public ushort StartCoverageIndex;
3625 
3626  public RangeRecord(Stream fs)
3627  {
3628  this.StartGlyphID = fs.ReadUShort();
3629  this.EndGlyphID = fs.ReadUShort();
3630  this.StartCoverageIndex = fs.ReadUShort();
3631  }
3632  }
3633 
3634  public ushort CoverageFormat;
3635 
3636  public ushort[] GlyphArray;
3637  public RangeRecord[] RangeRecords;
3638 
3639  public int ContainsGlyph(int glyphIndex)
3640  {
3641  if (this.CoverageFormat == 1)
3642  {
3643  for (int i = 0; i < this.GlyphArray.Length; i++)
3644  {
3645  if (this.GlyphArray[i] == glyphIndex)
3646  {
3647  return i;
3648  }
3649  }
3650 
3651  return -1;
3652  }
3653  else if (this.CoverageFormat == 2)
3654  {
3655  for (int i = 0; i < this.RangeRecords.Length; i++)
3656  {
3657  if (this.RangeRecords[i].StartGlyphID <= glyphIndex && this.RangeRecords[i].EndGlyphID >= glyphIndex)
3658  {
3659  return this.RangeRecords[i].StartCoverageIndex + (glyphIndex - this.RangeRecords[i].StartGlyphID);
3660  }
3661  }
3662 
3663  return -1;
3664  }
3665  else
3666  {
3667  return -1;
3668  }
3669  }
3670 
3671  public CoverageTable(Stream fs)
3672  {
3673  this.CoverageFormat = fs.ReadUShort();
3674 
3675  if (this.CoverageFormat == 1)
3676  {
3677  ushort glyphCount = fs.ReadUShort();
3678  this.GlyphArray = new ushort[glyphCount];
3679  for (int i = 0; i < glyphCount; i++)
3680  {
3681  this.GlyphArray[i] = fs.ReadUShort();
3682  }
3683  }
3684  else if (this.CoverageFormat == 2)
3685  {
3686  ushort rangeCount = fs.ReadUShort();
3687 
3688  this.RangeRecords = new RangeRecord[rangeCount];
3689 
3690  for (int i = 0; i < rangeCount; i++)
3691  {
3692  this.RangeRecords[i] = new RangeRecord(fs);
3693  }
3694  }
3695  }
3696 
3697  public byte[] GetBytes()
3698  {
3699  if (this.CoverageFormat == 1)
3700  {
3701  using (MemoryStream ms = new MemoryStream(4 + 2 * this.GlyphArray.Length))
3702  {
3703  ms.WriteUShort(this.CoverageFormat);
3704  ms.WriteUShort((ushort)this.GlyphArray.Length);
3705  for (int i = 0; i < this.GlyphArray.Length; i++)
3706  {
3707  ms.WriteUShort(this.GlyphArray[i]);
3708  }
3709 
3710  return ms.ToArray();
3711  }
3712  }
3713  else if (this.CoverageFormat == 2)
3714  {
3715  using (MemoryStream ms = new MemoryStream(4 + 6 * this.RangeRecords.Length))
3716  {
3717  ms.WriteUShort(this.CoverageFormat);
3718  ms.WriteUShort((ushort)this.RangeRecords.Length);
3719  for (int i = 0; i < this.RangeRecords.Length; i++)
3720  {
3721  ms.WriteUShort(this.RangeRecords[i].StartGlyphID);
3722  ms.WriteUShort(this.RangeRecords[i].EndGlyphID);
3723  ms.WriteUShort(this.RangeRecords[i].StartCoverageIndex);
3724  }
3725 
3726  return ms.ToArray();
3727  }
3728  }
3729  else
3730  {
3731  using (MemoryStream ms = new MemoryStream(4 + 2 * this.GlyphArray.Length))
3732  {
3733  ms.WriteUShort(this.CoverageFormat);
3734 
3735  return ms.ToArray();
3736  }
3737  }
3738  }
3739  }
3740 
3741  internal class ClassDefinitionTable
3742  {
3743  public struct ClassRangeRecord
3744  {
3745  public ushort StartGlyphID;
3746  public ushort EndGlyphID;
3747  public ushort Class;
3748 
3749  public ClassRangeRecord(Stream fs)
3750  {
3751  this.StartGlyphID = fs.ReadUShort();
3752  this.EndGlyphID = fs.ReadUShort();
3753  this.Class = fs.ReadUShort();
3754  }
3755  }
3756 
3757  public ushort ClassFormat;
3758  public ushort StartGlyphID;
3759  public ushort[] ClassValueArray;
3760 
3761  public ClassRangeRecord[] ClassRangeRecords;
3762 
3763  public ClassDefinitionTable(Stream fs)
3764  {
3765  this.ClassFormat = fs.ReadUShort();
3766 
3767  if (this.ClassFormat == 1)
3768  {
3769  this.StartGlyphID = fs.ReadUShort();
3770  ushort glyphCount = fs.ReadUShort();
3771 
3772  this.ClassValueArray = new ushort[glyphCount];
3773 
3774  for (int i = 0; i < this.ClassValueArray.Length; i++)
3775  {
3776  this.ClassValueArray[i] = fs.ReadUShort();
3777  }
3778  }
3779  else if (this.ClassFormat == 2)
3780  {
3781  ushort classRangeCount = fs.ReadUShort();
3782 
3783  this.ClassRangeRecords = new ClassRangeRecord[classRangeCount];
3784 
3785  for (int i = 0; i < classRangeCount; i++)
3786  {
3787  this.ClassRangeRecords[i] = new ClassRangeRecord(fs);
3788  }
3789  }
3790  }
3791 
3792  public int GetClass(int glyphIndex)
3793  {
3794  if (this.ClassFormat == 1)
3795  {
3796  return this.ClassValueArray[glyphIndex - StartGlyphID];
3797  }
3798  else if (this.ClassFormat == 2)
3799  {
3800  for (int i = 0; i < this.ClassRangeRecords.Length; i++)
3801  {
3802  if (this.ClassRangeRecords[i].StartGlyphID <= glyphIndex && this.ClassRangeRecords[i].EndGlyphID >= glyphIndex)
3803  {
3804  return this.ClassRangeRecords[i].Class;
3805  }
3806  }
3807  return -1;
3808  }
3809  else
3810  {
3811  return -1;
3812  }
3813  }
3814 
3815  public byte[] GetBytes()
3816  {
3817  if (this.ClassFormat == 1)
3818  {
3819  using (MemoryStream ms = new MemoryStream(6 + 2 * this.ClassValueArray.Length))
3820  {
3821  ms.WriteUShort(this.ClassFormat);
3822  ms.WriteUShort(this.StartGlyphID);
3823  ms.WriteUShort((ushort)this.ClassValueArray.Length);
3824 
3825  for (int i = 0; i < this.ClassValueArray.Length; i++)
3826  {
3827  ms.WriteUShort(this.ClassValueArray[i]);
3828  }
3829 
3830  return ms.ToArray();
3831  }
3832  }
3833  else if (this.ClassFormat == 2)
3834  {
3835  using (MemoryStream ms = new MemoryStream(4 + 6 * this.ClassRangeRecords.Length))
3836  {
3837  ms.WriteUShort(this.ClassFormat);
3838  ms.WriteUShort((ushort)this.ClassRangeRecords.Length);
3839 
3840  for (int i = 0; i < this.ClassRangeRecords.Length; i++)
3841  {
3842  ms.WriteUShort(this.ClassRangeRecords[i].StartGlyphID);
3843  ms.WriteUShort(this.ClassRangeRecords[i].EndGlyphID);
3844  ms.WriteUShort(this.ClassRangeRecords[i].Class);
3845  }
3846 
3847  return ms.ToArray();
3848  }
3849  }
3850  else
3851  {
3852  using (MemoryStream ms = new MemoryStream(2))
3853  {
3854  ms.WriteUShort(this.ClassFormat);
3855 
3856  return ms.ToArray();
3857  }
3858  }
3859  }
3860  }
3861 
3862  internal class FeatureTable
3863  {
3864  public string Tag;
3865  public byte[] FeatureParams;
3866  public ushort[] LookupListIndices;
3867 
3868  public FeatureTable(string tag, Stream fs)
3869  {
3870  this.Tag = tag;
3871 
3872  long startPosition = fs.Position;
3873 
3874  ushort featureParamsOffset = fs.ReadUShort();
3875 
3876  ushort lookupIndexCount = fs.ReadUShort();
3877  this.LookupListIndices = new ushort[lookupIndexCount];
3878 
3879  for (int i = 0; i < lookupIndexCount; i++)
3880  {
3881  this.LookupListIndices[i] = fs.ReadUShort();
3882  }
3883 
3884  if (featureParamsOffset != 0 && tag == "size")
3885  {
3886  fs.Seek(startPosition + featureParamsOffset, SeekOrigin.Begin);
3887 
3888  this.FeatureParams = new byte[10];
3889 
3890  for (int i = 0; i < 10; i++)
3891  {
3892  this.FeatureParams[i] = (byte)fs.ReadByte();
3893  }
3894  }
3895  else
3896  {
3897  this.FeatureParams = null;
3898  }
3899  }
3900 
3901  public byte[] GetBytes()
3902  {
3903  using (MemoryStream ms = new MemoryStream())
3904  {
3905  if (this.FeatureParams != null)
3906  {
3907  ms.WriteUShort((ushort)(4 + 2 * this.LookupListIndices.Length));
3908  }
3909  else
3910  {
3911  ms.WriteUShort(0);
3912  }
3913 
3914  ms.WriteUShort((ushort)this.LookupListIndices.Length);
3915 
3916  for (int i = 0; i < this.LookupListIndices.Length; i++)
3917  {
3918  ms.WriteUShort(this.LookupListIndices[i]);
3919  }
3920 
3921  if (this.FeatureParams != null)
3922  {
3923  ms.Write(this.FeatureParams, 0, this.FeatureParams.Length);
3924  }
3925 
3926  return ms.ToArray();
3927  }
3928  }
3929  }
3930 
3931  internal class FeatureList
3932  {
3933  public FeatureTable[] FeatureRecords;
3934 
3935  public FeatureList(Stream fs)
3936  {
3937  long startPosition = fs.Position;
3938 
3939  ushort featureCount = fs.ReadUShort();
3940 
3941  (string, ushort)[] featureRecords = new (string, ushort)[featureCount];
3942 
3943  for (int i = 0; i < featureCount; i++)
3944  {
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());
3950 
3951  featureRecords[i] = (featureTag.ToString(), fs.ReadUShort());
3952  }
3953 
3954  this.FeatureRecords = new FeatureTable[featureCount];
3955 
3956  for (int i = 0; i < featureCount; i++)
3957  {
3958  fs.Seek(startPosition + featureRecords[i].Item2, SeekOrigin.Begin);
3959 
3960  this.FeatureRecords[i] = new FeatureTable(featureRecords[i].Item1, fs);
3961  }
3962  }
3963 
3964  public byte[] GetBytes()
3965  {
3966  using (MemoryStream ms = new MemoryStream())
3967  {
3968  int offset = 2 + 6 * this.FeatureRecords.Length;
3969 
3970  ms.WriteUShort((ushort)this.FeatureRecords.Length);
3971 
3972  byte[][] featureRecords = new byte[this.FeatureRecords.Length][];
3973  int[] offsets = new int[this.FeatureRecords.Length];
3974 
3975  for (int i = 0; i < this.FeatureRecords.Length; i++)
3976  {
3977  offsets[i] = offset;
3978  featureRecords[i] = this.FeatureRecords[i].GetBytes();
3979  offset += featureRecords[i].Length;
3980  }
3981 
3982  for (int i = 0; i < this.FeatureRecords.Length; i++)
3983  {
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]);
3988 
3989  ms.WriteUShort((ushort)offsets[i]);
3990  }
3991 
3992  for (int i = 0; i < this.FeatureRecords.Length; i++)
3993  {
3994  ms.Write(featureRecords[i], 0, featureRecords[i].Length);
3995  }
3996 
3997  return ms.ToArray();
3998  }
3999  }
4000  }
4001 
4002  internal class ValueRecord
4003  {
4004  public short XPlacement;
4005  public short YPlacement;
4006  public short XAdvance;
4007  public short YAdvance;
4008  public ushort ValueFormat;
4009 
4010  public ValueRecord(Stream fs, ushort valueFormat)
4011  {
4012  this.ValueFormat = valueFormat;
4013 
4014  if ((valueFormat & 0x0001) != 0)
4015  {
4016  this.XPlacement = fs.ReadShort();
4017  }
4018 
4019  if ((valueFormat & 0x0002) != 0)
4020  {
4021  this.YPlacement = fs.ReadShort();
4022  }
4023 
4024  if ((valueFormat & 0x0004) != 0)
4025  {
4026  this.XAdvance = fs.ReadShort();
4027  }
4028 
4029  if ((valueFormat & 0x0008) != 0)
4030  {
4031  this.YAdvance = fs.ReadShort();
4032  }
4033 
4034  if ((valueFormat & 0x0010) != 0)
4035  {
4036  fs.ReadUShort();
4037  }
4038 
4039  if ((valueFormat & 0x0020) != 0)
4040  {
4041  fs.ReadUShort();
4042  }
4043 
4044  if ((valueFormat & 0x0040) != 0)
4045  {
4046  fs.ReadUShort();
4047  }
4048 
4049  if ((valueFormat & 0x0080) != 0)
4050  {
4051  fs.ReadUShort();
4052  }
4053  }
4054 
4055  public byte[] GetBytes()
4056  {
4057  using (MemoryStream ms = new MemoryStream())
4058  {
4059  if ((this.ValueFormat & 0x0001) != 0)
4060  {
4061  ms.WriteShort(this.XPlacement);
4062  }
4063 
4064  if ((this.ValueFormat & 0x0002) != 0)
4065  {
4066  ms.WriteShort(this.YPlacement);
4067  }
4068 
4069  if ((this.ValueFormat & 0x0004) != 0)
4070  {
4071  ms.WriteShort(this.XAdvance);
4072  }
4073 
4074  if ((this.ValueFormat & 0x0008) != 0)
4075  {
4076  ms.WriteShort(this.YAdvance);
4077  }
4078 
4079  if ((this.ValueFormat & 0x0010) != 0)
4080  {
4081  ms.WriteUShort(0);
4082  }
4083 
4084  if ((this.ValueFormat & 0x0020) != 0)
4085  {
4086  ms.WriteUShort(0);
4087  }
4088 
4089  if ((this.ValueFormat & 0x0040) != 0)
4090  {
4091  ms.WriteUShort(0);
4092  }
4093 
4094  if ((this.ValueFormat & 0x0080) != 0)
4095  {
4096  ms.WriteUShort(0);
4097  }
4098 
4099  return ms.ToArray();
4100  }
4101  }
4102  }
4103 
4104  internal class SinglePosSubtable : ITrueTypeTable
4105  {
4106  public ushort PosFormat;
4107  public CoverageTable CoverageTable;
4108  public ushort ValueFormat;
4109  public ValueRecord ValueRecord;
4110  public ValueRecord[] ValueRecords;
4111 
4112  public SinglePosSubtable(Stream fs)
4113  {
4114  long startPosition = fs.Position;
4115 
4116  this.PosFormat = fs.ReadUShort();
4117 
4118  if (this.PosFormat == 1)
4119  {
4120  ushort coverageOffset = fs.ReadUShort();
4121  this.ValueFormat = fs.ReadUShort();
4122  this.ValueRecord = new ValueRecord(fs, this.ValueFormat);
4123 
4124  fs.Seek(startPosition + coverageOffset, SeekOrigin.Begin);
4125  this.CoverageTable = new CoverageTable(fs);
4126  }
4127  else if (this.PosFormat == 2)
4128  {
4129  ushort coverageOffset = fs.ReadUShort();
4130  this.ValueFormat = fs.ReadUShort();
4131 
4132  ushort valueCount = fs.ReadUShort();
4133 
4134  this.ValueRecords = new ValueRecord[valueCount];
4135 
4136  for (int i = 0; i < valueCount; i++)
4137  {
4138  this.ValueRecords[i] = new ValueRecord(fs, this.ValueFormat);
4139  }
4140 
4141  fs.Seek(startPosition + coverageOffset, SeekOrigin.Begin);
4142  this.CoverageTable = new CoverageTable(fs);
4143  }
4144  }
4145 
4146  public byte[] GetBytes()
4147  {
4148  using (MemoryStream ms = new MemoryStream())
4149  {
4150  ms.WriteUShort(this.PosFormat);
4151 
4152  if (this.PosFormat == 1)
4153  {
4154  int offset = 22;
4155 
4156  ms.WriteUShort((ushort)offset);
4157  ms.WriteUShort(this.ValueFormat);
4158 
4159  byte[] valueBytes = this.ValueRecord.GetBytes();
4160 
4161  ms.Write(valueBytes, 0, valueBytes.Length);
4162 
4163  byte[] coverageBytes = this.CoverageTable.GetBytes();
4164 
4165  ms.Write(coverageBytes, 0, coverageBytes.Length);
4166  }
4167  else if (this.PosFormat == 2)
4168  {
4169  int offset = 8 + 16 * ValueRecords.Length;
4170 
4171  ms.WriteUShort((ushort)offset);
4172  ms.WriteUShort(this.ValueFormat);
4173  ms.WriteUShort((ushort)this.ValueRecords.Length);
4174 
4175  for (int i = 0; i < this.ValueRecords.Length; i++)
4176  {
4177  byte[] valueBytes = this.ValueRecords[i].GetBytes();
4178 
4179  ms.Write(valueBytes, 0, valueBytes.Length);
4180  }
4181 
4182  byte[] coverageBytes = this.CoverageTable.GetBytes();
4183 
4184  ms.Write(coverageBytes, 0, coverageBytes.Length);
4185  }
4186 
4187  return ms.ToArray();
4188  }
4189  }
4190  }
4191 
4192  internal class PairValueRecord
4193  {
4194  public ushort SecondGlyph;
4195  public ValueRecord ValueRecord1;
4196  public ValueRecord ValueRecord2;
4197 
4198  public PairValueRecord(Stream fs, ushort valueFormat1, ushort valueFormat2)
4199  {
4200  this.SecondGlyph = fs.ReadUShort();
4201  this.ValueRecord1 = new ValueRecord(fs, valueFormat1);
4202  this.ValueRecord2 = new ValueRecord(fs, valueFormat2);
4203  }
4204 
4205  public byte[] GetBytes()
4206  {
4207  using (MemoryStream ms = new MemoryStream())
4208  {
4209  ms.WriteUShort(SecondGlyph);
4210 
4211  byte[] bytes = this.ValueRecord1.GetBytes();
4212  ms.Write(bytes, 0, bytes.Length);
4213 
4214  bytes = this.ValueRecord2.GetBytes();
4215  ms.Write(bytes, 0, bytes.Length);
4216 
4217  return ms.ToArray();
4218  }
4219  }
4220  }
4221 
4222  internal class Class2Record
4223  {
4224  public ValueRecord ValueRecord1;
4225  public ValueRecord ValueRecord2;
4226 
4227  public Class2Record(Stream fs, ushort valueFormat1, ushort valueFormat2)
4228  {
4229  this.ValueRecord1 = new ValueRecord(fs, valueFormat1);
4230  this.ValueRecord2 = new ValueRecord(fs, valueFormat2);
4231  }
4232 
4233  public byte[] GetBytes()
4234  {
4235  using (MemoryStream ms = new MemoryStream())
4236  {
4237  byte[] bytes = this.ValueRecord1.GetBytes();
4238  ms.Write(bytes, 0, bytes.Length);
4239 
4240  bytes = this.ValueRecord2.GetBytes();
4241  ms.Write(bytes, 0, bytes.Length);
4242 
4243  return ms.ToArray();
4244  }
4245  }
4246  }
4247 
4248  /// <summary>
4249  /// Contains information describing how the position of two glyphs in a kerning pair should be altered.
4250  /// </summary>
4251  public class PairKerning
4252  {
4253  /// <summary>
4254  /// This vector contains the displacement that should be applied to the first glyph of the pair.
4255  /// </summary>
4256  public Point Glyph1Placement { get; }
4257 
4258  /// <summary>
4259  /// This vector describes how the advance width of the first glyph should be altered.
4260  /// </summary>
4261  public Point Glyph1Advance { get; }
4262 
4263  /// <summary>
4264  /// This vector contains the displacement that should be applied to the second glyph of the pair.
4265  /// </summary>
4266  public Point Glyph2Placement { get; }
4267 
4268  /// <summary>
4269  /// This vector describes how the advance width of the second glyph should be altered.
4270  /// </summary>
4271  public Point Glyph2Advance { get; }
4272 
4273  internal PairKerning(Point glyph1Placement, Point glyph1Advance, Point glyph2Placement, Point glyph2Advance)
4274  {
4275  this.Glyph1Placement = glyph1Placement;
4276  this.Glyph1Advance = glyph1Advance;
4277  this.Glyph2Placement = glyph2Placement;
4278  this.Glyph2Advance = glyph2Advance;
4279  }
4280  }
4281 
4282  internal class PairPosSubtable : ITrueTypeTable
4283  {
4284  public ushort PosFormat;
4285  public CoverageTable CoverageTable;
4286 
4287  public ushort ValueFormat1;
4288  public ushort ValueFormat2;
4289 
4290  public PairValueRecord[][] PairSets;
4291 
4292  public ClassDefinitionTable ClassDef1;
4293  public ClassDefinitionTable ClassDef2;
4294  public Class2Record[][] Class1Records;
4295 
4296  public PairPosSubtable(Stream fs)
4297  {
4298  long startPosition = fs.Position;
4299 
4300  this.PosFormat = fs.ReadUShort();
4301 
4302  if (this.PosFormat == 1)
4303  {
4304  ushort coverageOffset = fs.ReadUShort();
4305 
4306  this.ValueFormat1 = fs.ReadUShort();
4307  this.ValueFormat2 = fs.ReadUShort();
4308 
4309  ushort pairSetCount = fs.ReadUShort();
4310 
4311  ushort[] pairSetOffsets = new ushort[pairSetCount];
4312 
4313  for (int i = 0; i < pairSetCount; i++)
4314  {
4315  pairSetOffsets[i] = fs.ReadUShort();
4316  }
4317 
4318  fs.Seek(startPosition + coverageOffset, SeekOrigin.Begin);
4319  this.CoverageTable = new CoverageTable(fs);
4320 
4321  this.PairSets = new PairValueRecord[pairSetCount][];
4322 
4323  for (int i = 0; i < pairSetCount; i++)
4324  {
4325  fs.Seek(startPosition + pairSetOffsets[i], SeekOrigin.Begin);
4326 
4327  ushort pairValueCount = fs.ReadUShort();
4328 
4329  this.PairSets[i] = new PairValueRecord[pairValueCount];
4330 
4331  for (int j = 0; j < pairValueCount; j++)
4332  {
4333  this.PairSets[i][j] = new PairValueRecord(fs, this.ValueFormat1, this.ValueFormat2);
4334  }
4335  }
4336  }
4337  else if (this.PosFormat == 2)
4338  {
4339  ushort coverageOffset = fs.ReadUShort();
4340 
4341  this.ValueFormat1 = fs.ReadUShort();
4342  this.ValueFormat2 = fs.ReadUShort();
4343 
4344  ushort class1DefOffset = fs.ReadUShort();
4345  ushort class2DefOffset = fs.ReadUShort();
4346 
4347  ushort class1Count = fs.ReadUShort();
4348  ushort class2Count = fs.ReadUShort();
4349 
4350  this.Class1Records = new Class2Record[class1Count][];
4351 
4352  for (int i = 0; i < class1Count; i++)
4353  {
4354  this.Class1Records[i] = new Class2Record[class2Count];
4355 
4356  for (int j = 0; j < class2Count; j++)
4357  {
4358  this.Class1Records[i][j] = new Class2Record(fs, this.ValueFormat1, this.ValueFormat2);
4359  }
4360  }
4361 
4362  fs.Seek(startPosition + coverageOffset, SeekOrigin.Begin);
4363  this.CoverageTable = new CoverageTable(fs);
4364 
4365  fs.Seek(startPosition + class1DefOffset, SeekOrigin.Begin);
4366  this.ClassDef1 = new ClassDefinitionTable(fs);
4367 
4368  fs.Seek(startPosition + class2DefOffset, SeekOrigin.Begin);
4369 
4370  this.ClassDef2 = new ClassDefinitionTable(fs);
4371  }
4372  }
4373 
4374  public PairKerning GetKerning(int glyph1CoverageIndex, int glyph1Index, int glyph2Index)
4375  {
4376  if (this.PosFormat == 1)
4377  {
4378  for (int i = 0; i < this.PairSets[glyph1CoverageIndex].Length; i++)
4379  {
4380  if (this.PairSets[glyph1CoverageIndex][i].SecondGlyph == glyph2Index)
4381  {
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));
4386  }
4387  }
4388 
4389  return null;
4390  }
4391  else if (this.PosFormat == 2)
4392  {
4393  int glyph1Class = this.ClassDef1.GetClass(glyph1Index);
4394  int glyph2Class = this.ClassDef2.GetClass(glyph2Index);
4395 
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));
4400  }
4401  else
4402  {
4403  return null;
4404  }
4405  }
4406 
4407  public byte[] GetBytes()
4408  {
4409  using (MemoryStream ms = new MemoryStream())
4410  {
4411  ms.WriteUShort(this.PosFormat);
4412 
4413  if (this.PosFormat == 1)
4414  {
4415  int offset = 10 + 2 * PairSets.Length;
4416 
4417  ms.WriteUShort((ushort)offset);
4418 
4419  ms.WriteUShort((ushort)ValueFormat1);
4420  ms.WriteUShort((ushort)ValueFormat2);
4421 
4422  ms.WriteUShort((ushort)this.PairSets.Length);
4423 
4424  byte[] coverageBytes = this.CoverageTable.GetBytes();
4425 
4426  offset += coverageBytes.Length;
4427 
4428  byte[][][] pairSets = new byte[this.PairSets.Length][][];
4429  int[] offsets = new int[this.PairSets.Length];
4430 
4431  for (int i = 0; i < this.PairSets.Length; i++)
4432  {
4433  offsets[i] = offset;
4434 
4435  pairSets[i] = new byte[this.PairSets[i].Length][];
4436 
4437  for (int j = 0; j < this.PairSets[i].Length; j++)
4438  {
4439  pairSets[i][j] = this.PairSets[i][j].GetBytes();
4440  offset += pairSets[i][j].Length;
4441  }
4442  }
4443 
4444  for (int i = 0; i < offsets.Length; i++)
4445  {
4446  ms.WriteUShort((ushort)offsets[i]);
4447  }
4448 
4449  ms.Write(coverageBytes, 0, coverageBytes.Length);
4450 
4451  for (int i = 0; i < pairSets.Length; i++)
4452  {
4453  for (int j = 0; j < pairSets[i].Length; j++)
4454  {
4455  ms.Write(pairSets[i][j], 0, pairSets[i][j].Length);
4456  }
4457  }
4458 
4459  return ms.ToArray();
4460  }
4461  else if (this.PosFormat == 2)
4462  {
4463  int offset = 16;
4464 
4465  byte[][][] class1Records = new byte[this.Class1Records.Length][][];
4466 
4467  for (int i = 0; i < class1Records.Length; i++)
4468  {
4469  class1Records[i] = new byte[this.Class1Records[i].Length][];
4470 
4471  for (int j = 0; j < class1Records[i].Length; j++)
4472  {
4473  class1Records[i][j] = this.Class1Records[i][j].GetBytes();
4474  offset += class1Records[i][j].Length;
4475  }
4476  }
4477 
4478  ms.WriteUShort((ushort)offset);
4479 
4480  ms.WriteUShort(this.ValueFormat1);
4481  ms.WriteUShort(this.ValueFormat2);
4482 
4483  byte[] coverageBytes = this.CoverageTable.GetBytes();
4484 
4485  offset += coverageBytes.Length;
4486 
4487  ms.WriteUShort((ushort)offset);
4488 
4489  byte[] classDef1Bytes = this.ClassDef1.GetBytes();
4490 
4491  offset += classDef1Bytes.Length;
4492 
4493  ms.WriteUShort((ushort)offset);
4494 
4495  ms.WriteUShort((ushort)this.Class1Records.Length);
4496  ms.WriteUShort((ushort)this.Class1Records[0].Length);
4497 
4498  for (int i = 0; i < class1Records.Length; i++)
4499  {
4500  for (int j = 0; j < class1Records[i].Length; j++)
4501  {
4502  ms.Write(class1Records[i][j], 0, class1Records[i][j].Length);
4503  }
4504  }
4505 
4506  ms.Write(coverageBytes, 0, coverageBytes.Length);
4507  ms.Write(classDef1Bytes, 0, classDef1Bytes.Length);
4508 
4509  byte[] classDef2Bytes = this.ClassDef2.GetBytes();
4510  ms.Write(classDef2Bytes, 0, classDef2Bytes.Length);
4511 
4512  return ms.ToArray();
4513  }
4514  else
4515  {
4516  return ms.ToArray();
4517  }
4518  }
4519  }
4520 
4521  }
4522 
4523  internal class TrueTypeGPOSTable : ITrueTypeTable
4524  {
4525  public ushort MajorVersion;
4526  public ushort MinorVersion;
4527 
4528  public ScriptList ScriptList;
4529  public FeatureList FeatureList;
4530  public LookupList LookupList;
4531 
4532  private HashSet<int> KernLookups;
4533 
4534  public TrueTypeGPOSTable(Stream fs)
4535  {
4536  long startPosition = fs.Position;
4537 
4538  this.MajorVersion = fs.ReadUShort();
4539  this.MinorVersion = fs.ReadUShort();
4540 
4541  ushort scriptListOffset = fs.ReadUShort();
4542  ushort featureListOffset = fs.ReadUShort();
4543  ushort lookupListOffset = fs.ReadUShort();
4544 
4545  if (this.MinorVersion == 1)
4546  {
4547  fs.ReadUInt();
4548  }
4549 
4550  fs.Seek(startPosition + scriptListOffset, SeekOrigin.Begin);
4551  this.ScriptList = new ScriptList(fs);
4552 
4553  fs.Seek(startPosition + featureListOffset, SeekOrigin.Begin);
4554  this.FeatureList = new FeatureList(fs);
4555 
4556  fs.Seek(startPosition + lookupListOffset, SeekOrigin.Begin);
4557  this.LookupList = new LookupList(fs);
4558 
4559  this.KernLookups = new HashSet<int>();
4560 
4561  for (int i = 0; i < this.FeatureList.FeatureRecords.Length; i++)
4562  {
4563  if (this.FeatureList.FeatureRecords[i].Tag == "kern")
4564  {
4565  for (int j = 0; j < this.FeatureList.FeatureRecords[i].LookupListIndices.Length; j++)
4566  {
4567  if (this.LookupList.LookupTables[this.FeatureList.FeatureRecords[i].LookupListIndices[j]].LookupType == 2)
4568  {
4569  this.KernLookups.Add(this.FeatureList.FeatureRecords[i].LookupListIndices[j]);
4570  }
4571  }
4572  }
4573  }
4574 
4575  }
4576 
4577  public byte[] GetBytes()
4578  {
4579  using (MemoryStream ms = new MemoryStream())
4580  {
4581  ms.WriteUShort(this.MajorVersion);
4582  ms.WriteUShort(this.MinorVersion);
4583 
4584  int offset = 10;
4585 
4586  if (this.MinorVersion == 1)
4587  {
4588  offset += 2;
4589  }
4590 
4591  ms.WriteUShort((ushort)offset);
4592 
4593  byte[] scriptList = this.ScriptList.GetBytes();
4594  offset += scriptList.Length;
4595 
4596  ms.WriteUShort((ushort)offset);
4597 
4598  byte[] featureList = this.FeatureList.GetBytes();
4599  offset += featureList.Length;
4600 
4601  ms.WriteUShort((ushort)offset);
4602 
4603  if (this.MinorVersion == 1)
4604  {
4605  ms.WriteUInt(0);
4606  }
4607 
4608  ms.Write(scriptList, 0, scriptList.Length);
4609  ms.Write(featureList, 0, featureList.Length);
4610 
4611  byte[] lookupList = this.LookupList.GetBytes();
4612 
4613  ms.Write(lookupList, 0, lookupList.Length);
4614 
4615  return ms.ToArray();
4616  }
4617  }
4618 
4619 
4620  public PairKerning GetKerning(int glyph1Index, int glyph2Index)
4621  {
4622  foreach (int index in this.KernLookups)
4623  {
4624  for (int i = 0; i < this.LookupList.LookupTables[index].SubTables.Length; i++)
4625  {
4626  if (this.LookupList.LookupTables[index].SubTables[i] is PairPosSubtable pairPos)
4627  {
4628  int coverageIndex = pairPos.CoverageTable.ContainsGlyph(glyph1Index);
4629 
4630  if (coverageIndex >= 0)
4631  {
4632  return pairPos.GetKerning(coverageIndex, glyph1Index, glyph2Index);
4633  }
4634  }
4635  }
4636  }
4637 
4638  return null;
4639  }
4640  }
4641 
4642  internal struct TrueTypeTableOffset
4643  {
4644  public uint Checksum;
4645  public uint Offset;
4646  public uint Length;
4647 
4648  public TrueTypeTableOffset(uint checksum, uint offset, uint length)
4649  {
4650  this.Checksum = checksum;
4651  this.Offset = offset;
4652  this.Length = length;
4653  }
4654  }
4655 
4656  internal struct LongHorFixed
4657  {
4658  public ushort AdvanceWidth;
4659  public short LeftSideBearing;
4660 
4661  public LongHorFixed(ushort advanceWidth, short leftSideBearing)
4662  {
4663  this.AdvanceWidth = advanceWidth;
4664  this.LeftSideBearing = leftSideBearing;
4665  }
4666  }
4667 
4668  internal struct Fixed
4669  {
4670  public int Bits;
4671  public int BitShifts;
4672 
4673  public Fixed(int bits, int bitShifts)
4674  {
4675  this.Bits = bits;
4676  this.BitShifts = bitShifts;
4677  }
4678  }
4679  }
4680 
4681  internal static class ReadUtils
4682  {
4683  public static void WritePascalString(this Stream sr, string val)
4684  {
4685  sr.WriteByte((byte)val.Length);
4686  for (int i = 0; i < val.Length; i++)
4687  {
4688  sr.WriteByte((byte)(int)val[i]);
4689  }
4690  }
4691 
4692  public static string ReadPascalString(this Stream sr)
4693  {
4694  byte length = (byte)sr.ReadByte();
4695  StringBuilder tbr = new StringBuilder(length);
4696  for (int i = 0; i < length; i++)
4697  {
4698  tbr.Append((char)sr.ReadByte());
4699  }
4700  return tbr.ToString();
4701  }
4702 
4703  public static uint ReadUInt(this Stream sr)
4704  {
4705  return ((uint)sr.ReadByte() << 24) | ((uint)sr.ReadByte() << 16) | ((uint)sr.ReadByte() << 8) | (uint)sr.ReadByte();
4706  }
4707 
4708  public static void WriteUInt(this Stream sr, uint val)
4709  {
4710  sr.Write(new byte[] { (byte)(val >> 24), (byte)((val >> 16) & 255), (byte)((val >> 8) & 255), (byte)(val & 255) }, 0, 4);
4711  }
4712 
4713  public static int ReadInt(this Stream sr)
4714  {
4715  return (sr.ReadByte() << 24) | (sr.ReadByte() << 16) | (sr.ReadByte() << 8) | sr.ReadByte();
4716  }
4717 
4718  public static void WriteInt(this Stream sr, int val)
4719  {
4720  sr.WriteUInt((uint)val);
4721  }
4722 
4723  public static ushort ReadUShort(this Stream sr)
4724  {
4725  return (ushort)(((uint)sr.ReadByte() << 8) | (uint)sr.ReadByte());
4726  }
4727 
4728  public static void WriteUShort(this Stream sr, ushort val)
4729  {
4730  sr.Write(new byte[] { (byte)(val >> 8), (byte)(val & 255) }, 0, 2);
4731  }
4732 
4733  public static short ReadShort(this Stream sr)
4734  {
4735  ushort result = sr.ReadUShort();
4736  short tbr;
4737  if ((result & 0x8000) != 0)
4738  {
4739  tbr = (short)(result - (1 << 16));
4740  }
4741  else
4742  {
4743  tbr = (short)result;
4744  }
4745  return tbr;
4746  }
4747 
4748  public static void WriteShort(this Stream sr, short val)
4749  {
4750  sr.WriteUShort((ushort)val);
4751  }
4752 
4753  public static string ReadString(this Stream sr, int length)
4754  {
4755  StringBuilder bld = new StringBuilder(length);
4756  for (int i = 0; i < length; i++)
4757  {
4758  bld.Append((char)sr.ReadByte());
4759  }
4760  return bld.ToString();
4761  }
4762 
4763  public static TrueTypeFile.Fixed ReadFixed(this Stream sr)
4764  {
4765  return new TrueTypeFile.Fixed(sr.ReadInt(), 16);
4766  }
4767 
4768  public static void WriteFixed(this Stream sr, TrueTypeFile.Fixed val)
4769  {
4770  sr.WriteInt(val.Bits);
4771  }
4772 
4773  public static DateTime ReadDate(this Stream sr)
4774  {
4775  long macTime = sr.ReadUInt() * 0x100000000 + sr.ReadUInt();
4776  return new DateTime(1904, 1, 1).AddTicks(macTime * 1000 * TimeSpan.TicksPerMillisecond);
4777  }
4778 
4779  public static void WriteDate(this Stream sr, DateTime date)
4780  {
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));
4784  }
4785  }
4786 
4787 }
VectSharp.TrueTypeFile.TrueTypePoint.Y
double Y
The vertical coordinate of the point.
Definition: TrueType.cs:1370
VectSharp.TrueTypeFile
Represents a font file in TrueType format. Reference: http://stevehanov.ca/blog/?id=143,...
Definition: TrueType.cs:31
VectSharp.TrueTypeFile.GetGlyphPath
TrueTypePoint[][] GetGlyphPath(char glyph, double size)
Get the path that describes the shape of a glyph.
Definition: TrueType.cs:2072
VectSharp.TrueTypeFile.Get1000EmKerning
PairKerning Get1000EmKerning(char glyph1, char glyph2)
Gets the kerning between two glyphs.
Definition: TrueType.cs:2490
VectSharp.TrueTypeFile.Get1000EmUnderlinePosition
double Get1000EmUnderlinePosition()
Computes the distance of the top of the underline from the baseline, in thousandths of em unit.
Definition: TrueType.cs:2288
VectSharp.TrueTypeFile.Destroy
void Destroy()
Remove this TrueType file from the cache, clear the tables and release the FontStream....
Definition: TrueType.cs:53
VectSharp.TrueTypeFile.Get1000EmUnderlineIntersections
double[] Get1000EmUnderlineIntersections(char glyph, double position, double thickness)
Computes the intersections between an underline at the specified position and thickness and a glyph,...
Definition: TrueType.cs:2340
VectSharp.TrueTypeFile.PairKerning.Glyph2Placement
Point Glyph2Placement
This vector contains the displacement that should be applied to the second glyph of the pair.
Definition: TrueType.cs:4266
VectSharp.TrueTypeFile.Get1000EmYMax
double Get1000EmYMax()
Computes the maximum height over the baseline of the font, in thousandths of em unit.
Definition: TrueType.cs:2150
VectSharp.TrueTypeFile.TrueTypePoint
Represents a point in a TrueType path description.
Definition: TrueType.cs:1361
VectSharp.TrueTypeFile.VerticalMetrics.YMax
int YMax
The maximum height above the baseline of the glyph.
Definition: TrueType.cs:2250
VectSharp.TrueTypeFile.Get1000EmGlyphVerticalMetrics
VerticalMetrics Get1000EmGlyphVerticalMetrics(char glyph)
Computes the vertical metrics of a glyph, in thousandths of em unit.
Definition: TrueType.cs:2271
VectSharp
Definition: Brush.cs:26
VectSharp.TrueTypeFile.GetGlyphIndex
int GetGlyphIndex(char glyph)
Determines the index of the glyph corresponding to a certain character.
Definition: TrueType.cs:2010
VectSharp.TrueTypeFile.TrueTypePoint.IsOnCurve
bool IsOnCurve
Whether the point is a point on the curve, or a control point of a quadratic Bezier curve.
Definition: TrueType.cs:1375
VectSharp.TrueTypeFile.Bearings.RightSideBearing
int RightSideBearing
The right-side bearing of the glyph.
Definition: TrueType.cs:2195
VectSharp.TrueTypeFile.IsFixedPitch
bool IsFixedPitch()
Determines whether the typeface is fixed-pitch (aka monospaces) or not.
Definition: TrueType.cs:1976
VectSharp.TrueTypeFile.Get1000EmXMin
double Get1000EmXMin()
Computes the maximum distance to the left of the glyph origin of the font, in thousandths of em unit.
Definition: TrueType.cs:2177
VectSharp.TrueTypeFile.Get1000EmXMax
double Get1000EmXMax()
Computes the maximum distance to the right of the glyph origin of the font, in thousandths of em unit...
Definition: TrueType.cs:2168
VectSharp.TrueTypeFile.TrueTypePoint.X
double X
The horizontal coordinate of the point.
Definition: TrueType.cs:1365
VectSharp.TrueTypeFile.IsItalic
bool IsItalic()
Determines whether the typeface is Italic or Oblique or not.
Definition: TrueType.cs:1943
VectSharp.TrueTypeFile.PairKerning.Glyph2Advance
Point Glyph2Advance
This vector describes how the advance width of the second glyph should be altered.
Definition: TrueType.cs:4271
VectSharp.TrueTypeFile.IsSerif
bool IsSerif()
Determines whether the typeface is serifed or not.
Definition: TrueType.cs:1987
VectSharp.TrueTypeFile.Get1000EmYMin
double Get1000EmYMin()
Computes the maximum depth below the baseline of the font, in thousandths of em unit.
Definition: TrueType.cs:2159
VectSharp.TrueTypeFile.Get1000EmAscent
double Get1000EmAscent()
Computes the font ascent, in thousandths of em unit.
Definition: TrueType.cs:2131
VectSharp.TrueTypeFile.CoverageTable.RangeRecord
Definition: TrueType.cs:3621
VectSharp.TrueTypeFile.GetFullFontFamilyName
string GetFullFontFamilyName()
Obtains the full font family name from the TrueType file.
Definition: TrueType.cs:1873
VectSharp.TrueTypeFile.Bearings.LeftSideBearing
int LeftSideBearing
The left-side bearing of the glyph.
Definition: TrueType.cs:2190
VectSharp.Point.X
double X
Horizontal (x) coordinate, measured to the right of the origin.
Definition: Point.cs:32
VectSharp.TrueTypeFile.Get1000EmGlyphWidth
double Get1000EmGlyphWidth(char glyph)
Computes the advance width of a glyph, in thousandths of em unit.
Definition: TrueType.cs:2082
VectSharp.TrueTypeFile.Get1000EmWinAscent
double Get1000EmWinAscent()
Computes the font's Win ascent, in thousandths of em unit.
Definition: TrueType.cs:2111
VectSharp.TrueTypeFile.VerticalMetrics
Represents the maximum heigth above and depth below the baseline of a glyph.
Definition: TrueType.cs:2241
VectSharp.TrueTypeFile.IsScript
bool IsScript()
Determines whether the typeface is a script typeface or not.
Definition: TrueType.cs:1998
VectSharp.TrueTypeFile.IsOblique
bool IsOblique()
Determines whether the typeface is Oblique or not.
Definition: TrueType.cs:1954
VectSharp.TrueTypeFile.FontStream
Stream FontStream
A stream pointing to the TrueType file source (either on disk or in memory). Never dispose this strea...
Definition: TrueType.cs:47
VectSharp.TrueTypeFile.GetFirstCharIndex
ushort GetFirstCharIndex()
Returns the index of the first character glyph represented by the font.
Definition: TrueType.cs:1920
VectSharp.TrueTypeFile.GetFontName
string GetFontName()
Obtains the PostScript font name from the TrueType file.
Definition: TrueType.cs:1901
VectSharp.TrueTypeFile.Bearings
Represents the left- and right-side bearings of a glyph.
Definition: TrueType.cs:2186
VectSharp.TrueTypeFile.GetItalicAngle
double GetItalicAngle()
Computes the italic angle for the current font, in thousandths of em unit. This is computed from the ...
Definition: TrueType.cs:2320
VectSharp.TrueTypeFile.Get1000EmGlyphBearings
Bearings Get1000EmGlyphBearings(char glyph)
Computes the left- and right- side bearings of a glyph, in thousandths of em unit.
Definition: TrueType.cs:2223
VectSharp.TrueTypeFile.GetLastCharIndex
ushort GetLastCharIndex()
Returns the index of the last character glyph represented by the font.
Definition: TrueType.cs:1931
VectSharp.TrueTypeFile.PairKerning
Contains information describing how the position of two glyphs in a kerning pair should be altered.
Definition: TrueType.cs:4252
VectSharp.TrueTypeFile.VerticalMetrics.YMin
int YMin
The maximum depth below the baseline of the glyph.
Definition: TrueType.cs:2245
VectSharp.TrueTypeFile.Get1000EmGlyphWidth
double Get1000EmGlyphWidth(int glyphIndex)
Computes the advance width of a glyph, in thousandths of em unit.
Definition: TrueType.cs:2100
VectSharp.TrueTypeFile.Get1000EmUnderlineThickness
double Get1000EmUnderlineThickness()
Computes the thickness of the underline, in thousandths of em unit.
Definition: TrueType.cs:2304
VectSharp.Point
Represents a point relative to an origin in the top-left corner.
Definition: Point.cs:28
VectSharp.TrueTypeFile.IsBold
bool IsBold()
Determines whether the typeface is Bold or not.
Definition: TrueType.cs:1965
VectSharp.TrueTypeFile.Get1000EmKerning
PairKerning Get1000EmKerning(int glyph1Index, int glyph2Index)
Gets the kerning between two glyphs.
Definition: TrueType.cs:2504
VectSharp.TrueTypeFile.Get1000EmDescent
double Get1000EmDescent()
Computes the font descent, in thousandths of em unit.
Definition: TrueType.cs:2141
VectSharp.TrueTypeFile.PairKerning.Glyph1Advance
Point Glyph1Advance
This vector describes how the advance width of the first glyph should be altered.
Definition: TrueType.cs:4261
VectSharp.TrueTypeFile.GetGlyphPath
TrueTypePoint[][] GetGlyphPath(int glyphIndex, double size)
Get the path that describes the shape of a glyph.
Definition: TrueType.cs:2061
VectSharp.TrueTypeFile.PairKerning.Glyph1Placement
Point Glyph1Placement
This vector contains the displacement that should be applied to the first glyph of the pair.
Definition: TrueType.cs:4256
VectSharp.TrueTypeFile.SubsetFont
TrueTypeFile SubsetFont(string charactersToInclude, bool consolidateAt32=false, Dictionary< char, char > outputEncoding=null)
Create a subset of the TrueType file, containing only the glyphs for the specified characters.
Definition: TrueType.cs:562
VectSharp.TrueTypeFile.GetFontFamilyName
string GetFontFamilyName()
Obtains the font family name from the TrueType file.
Definition: TrueType.cs:1846
VectSharp.Point.Y
double Y
Vertical (y) coordinate, measured to the bottom of the origin.
Definition: Point.cs:37
VectSharp.TrueTypeFile.ClassDefinitionTable.ClassRangeRecord
Definition: TrueType.cs:3744