├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── RoyT.TrueType.Tests
├── RoyT.TrueType.Tests.csproj
└── WindowsFontsTest.cs
├── RoyT.TrueType.sln
└── RoyT.TrueType
├── Helpers
├── GlyphHelper.cs
├── KerningHelper.cs
└── NameHelper.cs
├── IO
└── FontReader.cs
├── IsExternal.cs
├── Platform.cs
├── Properties
├── Resources.Designer.cs
└── Resources.resx
├── Resources
├── LCIDMap.txt
└── MacLanguageCodeMap.txt
├── RoyT.TrueType.csproj
├── Tables
├── Cmap
│ ├── ByteEncodingTable.cs
│ ├── CmapSubtableFormat.cs
│ ├── CmapTable.cs
│ ├── EncodingRecord.cs
│ ├── ICmapSubtable.cs
│ ├── SegmentMappingToDeltaValuesTable.cs
│ ├── SegmentedCoverageTable.cs
│ ├── SequentialMapGroup.cs
│ └── TrimmedTableMappingTable.cs
├── HeadTable.cs
├── HheaTable.cs
├── Hmtx
│ ├── HmtxTable.cs
│ └── LongHorMetric.cs
├── Kern
│ ├── Direction.cs
│ ├── Format0.cs
│ ├── KernSubTable.cs
│ ├── KernTable.cs
│ ├── KerningPair.cs
│ └── Values.cs
├── MaxpTable.cs
├── Name
│ ├── LanguageIdConverter.cs
│ ├── NameId.cs
│ ├── NameRecord.cs
│ └── NameTable.cs
├── OffsetTable.cs
├── Os2Table.cs
├── TableRecordEntry.cs
├── TrueTypeTableNames.cs
├── TtcHeader.cs
├── VheaTable.cs
└── Vmtx
│ ├── LongVerMetric.cs
│ └── VmtxTable.cs
├── TrueTypeFont.cs
└── WindowsEncoding.cs
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 R. A. Triesscheijn
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Roy-T.TrueType
2 |
3 | *Current version: 0.2.0*
4 |
5 | A TrueType parser for reading, glyphIds, names, descriptions, and kerning information from TrueType fonts. Supports `.NetStandard 2.0`, `.Net 4.6.1` and higher.
6 |
7 |
8 | You can directly add this library to your project using [NuGet](https://www.nuget.org/packages/RoyT.TrueType/):
9 |
10 | ```
11 | Install-Package RoyT.TrueType
12 | ```
13 |
14 |
15 |
16 | For more information please visit my blog at http://roy-t.nl.
17 |
18 | To learn more about the TrueType font format and terminology used here see the [Open Type specification](https://docs.microsoft.com/en-us/typography/opentype/spec/).
19 |
20 |
21 | ## Why choose this library?
22 | - It can read the following tables of all fonts that exist in a standard Windows 10 installation:
23 | - [CMAP](https://learn.microsoft.com/en-us/typography/opentype/spec/cmap), character to glyph index mapping table
24 | - [KERN](https://learn.microsoft.com/en-us/typography/opentype/spec/kern), inter character spacing (kerning)
25 | - [NAME](https://learn.microsoft.com/en-us/typography/opentype/spec/name), multilingual strings for font names, styles, ...
26 | - [MAXP](https://learn.microsoft.com/en-us/typography/opentype/spec/maxp), memory requirements
27 | - [HMTX](https://learn.microsoft.com/en-us/typography/opentype/spec/hmtx), horizontal metrics table
28 | - [VMTX](https://learn.microsoft.com/en-us/typography/opentype/spec/vmtx), vertical metrics table
29 | - [HHEA](https://learn.microsoft.com/en-us/typography/opentype/spec/hhea), horizontal header table
30 | - [VHEA](https://learn.microsoft.com/en-us/typography/opentype/spec/vhea), vertical header table
31 | - [OS2](https://learn.microsoft.com/en-us/typography/opentype/spec/os2) OS/2 and Windows Metric Table
32 |
33 | - It exposes both an easy to use API to directly get useful information from these tables, but also exposes the tables themselves so you add your own interpretation of the data
34 | - It works on both `.Net Core 1.0` and `.Net 4.6.1` and higher by targetting `.Net standard 1.6`
35 |
36 | ## Limitations
37 | - This library aims to read meta-information from .ttf files, it does not give you enough information to render glyphs yourself. For that you need OpenType and HarfBuzz.
38 |
39 |
40 |
41 | ## Supported Cmap formats
42 | [Reference](https://docs.microsoft.com/en-us/typography/opentype/spec/cmap)
43 |
44 | | Subtable format | supported/unsupported | remarks |
45 | |--- | --- | --- |
46 | | Segmented mapping to delta values | supported | Most common |
47 | | Trimmed table mapping | supported | |
48 | | Segmented coverage | supported | |
49 | | Byte encoding table | supported | Least common |
50 | | High-byte mapping through table | unsupported | I have not seeen a font that uses this yet, samples welcome |
51 | | Mixed 16-bit and 32-bit coverage | unsupported | I have not seeen a font that uses this yet, samples welcome |
52 | | Trimmed array | unsupported | I have not seeen a font that uses this yet, samples welcome |
53 | | Many-to-one range mappings | unsupported | I have not seeen a font that uses this yet, samples welcome |
54 | | Unicode Variation Sequences | unsupported | Specifies variations of a single glyph |
55 |
56 |
57 | ## Usage example
58 |
59 | ```csharp
60 | var font = TrueTypeFont.FromFile(@"C:\Windows\Fonts\arial.ttf");
61 |
62 | // Using the helper functions
63 | var glyphIndex = GlyphHelper.GetGlyphIndex('A', font); // 36
64 | var horizontalKerning = KerningHelper.GetHorizontalKerning('A', 'W', font); // -76
65 | var name = NameHelper.GetName(NameId.FontSubfamilyName, new CultureInfo("nl-NL"), font); // Standaard
66 |
67 | // Diving in deep yourself to get some specific information is also possible
68 | if (font.KernTable.SubtableCount > 0)
69 | {
70 | var leftCode = GlyphHelper.GetGlyphIndex(left, font);
71 | var rightCode = GlyphHelper.GetGlyphIndex(right, font);
72 |
73 | foreach (var subTable in font.KernTable.Subtables)
74 | {
75 | if (subTable.Format0 != null && subTable.Direction == Direction.Vertical
76 | && subTable.Values == Values.Kerning)
77 | {
78 | var pair = new KerningPair((ushort)leftCode, (ushort)rightCode);
79 |
80 | if (subTable.Format0.Map.TryGetValue(pair, out var value))
81 | {
82 | // Do something
83 | }
84 | }
85 | }
86 | }
87 | ```
88 |
--------------------------------------------------------------------------------
/RoyT.TrueType.Tests/RoyT.TrueType.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net6.0
4 | Library
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | all
14 | runtime; build; native; contentfiles; analyzers; buildtransitive
15 |
16 |
17 |
--------------------------------------------------------------------------------
/RoyT.TrueType.Tests/WindowsFontsTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.IO;
5 | using System.Linq;
6 | using RoyT.TrueType.Helpers;
7 | using RoyT.TrueType.Tables.Name;
8 | using Xunit;
9 |
10 | namespace RoyT.TrueType.Tests
11 | {
12 | public class WindowsFontsTest
13 | {
14 | ///
15 | /// Integration tests that parses all fonts installed with Windows
16 | ///
17 | [Fact]
18 | public void ShouldParseTrueTypeFonts()
19 | {
20 | List systemFonts = new();
21 | DirectoryInfo fontdir = new(Environment.GetFolderPath(Environment.SpecialFolder.Fonts));
22 |
23 | var fontFiles = fontdir.EnumerateFiles().
24 | Where(f => f.Extension.ToLowerInvariant() is ".ttf");
25 | foreach (var file in fontFiles)
26 | {
27 | if (!file.Name.Equals("mstmc.ttf", StringComparison.InvariantCultureIgnoreCase))
28 | {
29 | var font = TrueTypeFont.FromFile(file.FullName);
30 | systemFonts.Add(font);
31 | }
32 | }
33 |
34 | Assert.NotEmpty(systemFonts);
35 | }
36 |
37 | ///
38 | /// Integration tests that parses all fonts installed with Windows
39 | ///
40 | [Fact]
41 | public void ShouldParseTrueTypeCollections()
42 | {
43 | List systemFonts = new();
44 | DirectoryInfo fontdir = new(Environment.GetFolderPath(Environment.SpecialFolder.Fonts));
45 |
46 | var fontCollectionFiles = fontdir.EnumerateFiles().
47 | Where(f => f.Extension.ToLowerInvariant() is ".ttc");
48 | foreach (var file in fontCollectionFiles)
49 | {
50 | var fonts = TrueTypeFont.FromCollectionFile(file.FullName);
51 | systemFonts.AddRange(fonts);
52 | }
53 |
54 | Assert.NotEmpty(systemFonts);
55 | }
56 |
57 | ///
58 | /// Smoke test for checking glyph indices
59 | ///
60 | [Fact]
61 | public void ShouldGetGlyph()
62 | {
63 | var font = TrueTypeFont.FromFile(@"C:\Windows\Fonts\arial.ttf");
64 |
65 | var glyphIndex = GlyphHelper.GetGlyphIndex('A', font);
66 | Assert.NotEqual((uint)0, glyphIndex);
67 | }
68 |
69 | ///
70 | /// Smoke test for checking kerning info
71 | ///
72 | [Fact]
73 | public void ShouldGetKerning()
74 | {
75 | var font = TrueTypeFont.FromFile(@"C:\Windows\Fonts\arial.ttf");
76 |
77 | var horizontalKerning = KerningHelper.GetHorizontalKerning('A', 'W', font);
78 | Assert.True(horizontalKerning < 0);
79 |
80 | horizontalKerning = KerningHelper.GetHorizontalKerning('T', 'T', font);
81 | Assert.Equal(0, horizontalKerning);
82 | }
83 |
84 | ///
85 | /// Smoke test for checking name info
86 | ///
87 | [Fact]
88 | public void ShouldGetName()
89 | {
90 | var font = TrueTypeFont.FromFile(@"C:\Windows\Fonts\arial.ttf");
91 |
92 | var name = NameHelper.GetName(NameId.FontSubfamilyName, new CultureInfo("nl-NL"), font);
93 |
94 | Assert.Equal("Standaard", name);
95 | }
96 |
97 | [Fact]
98 | public void ShouldGetHhea()
99 | {
100 | var font = TrueTypeFont.FromFile(@"C:\Windows\Fonts\arial.ttf");
101 |
102 | Assert.Equal(1, font.HheaTable.MajorVersion);
103 | Assert.Equal(0, font.HheaTable.MinorVersion);
104 | }
105 |
106 | [Fact]
107 | public void ShouldGetHmtx()
108 | {
109 | var font = TrueTypeFont.FromFile(@"C:\Windows\Fonts\arial.ttf");
110 |
111 | Assert.Equal(font.HheaTable.NumberOfHMetrics, font.HmtxTable.HMetrics.Count);
112 | }
113 |
114 | [Fact]
115 | public void ShouldGetVhea()
116 | {
117 | var font = TrueTypeFont.FromFile(@"C:\Windows\Fonts\malgun.ttf");
118 |
119 | Assert.Equal(1, font.VheaTable.Version);
120 | }
121 |
122 | [Fact]
123 | public void ShouldGetVmtx()
124 | {
125 | var font = TrueTypeFont.FromFile(@"C:\Windows\Fonts\malgun.ttf");
126 |
127 | Assert.Equal(font.VheaTable.NumberOfVMetrics, font.VmtxTable.VMetrics.Count);
128 | Assert.Equal(font.MaxpTable.NumGlyphs - font.VheaTable.NumberOfVMetrics, font.VmtxTable.TopSideBearings.Count);
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/RoyT.TrueType.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27428.2037
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RoyT.TrueType.Tests", "RoyT.TrueType.Tests\RoyT.TrueType.Tests.csproj", "{4F494AFB-C57A-4CAA-8AD4-9C93BA76267B}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RoyT.TrueType", "RoyT.TrueType\RoyT.TrueType.csproj", "{FBB8C11A-9BA6-4A6A-97BC-76D42571960A}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A8807A6F-A4F7-48A2-A070-C98B5B423286}"
11 | ProjectSection(SolutionItems) = preProject
12 | LICENSE = LICENSE
13 | README.md = README.md
14 | EndProjectSection
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Release|Any CPU = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {4F494AFB-C57A-4CAA-8AD4-9C93BA76267B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {4F494AFB-C57A-4CAA-8AD4-9C93BA76267B}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {4F494AFB-C57A-4CAA-8AD4-9C93BA76267B}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {4F494AFB-C57A-4CAA-8AD4-9C93BA76267B}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {FBB8C11A-9BA6-4A6A-97BC-76D42571960A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {FBB8C11A-9BA6-4A6A-97BC-76D42571960A}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {FBB8C11A-9BA6-4A6A-97BC-76D42571960A}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {FBB8C11A-9BA6-4A6A-97BC-76D42571960A}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {D9F6E833-B6C9-48FA-888D-5979403AAAE2}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Helpers/GlyphHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 |
3 | namespace RoyT.TrueType.Helpers
4 | {
5 | public static class GlyphHelper
6 | {
7 | ///
8 | /// Returns the glyph index for the given character, or 0 if the character is not supported by this font
9 | ///
10 | public static uint GetGlyphIndex(char c, TrueTypeFont font)
11 | {
12 | uint glyphIndex = 0;
13 |
14 | // Prefer Windows platform UCS2 glyphs as they are the recommended default on the Windows platform
15 | var preferred = font.CmapTable.EncodingRecords.FirstOrDefault(
16 | e => e.PlatformId == Platform.Windows
17 | && e.WindowsEncodingId == WindowsEncoding.UnicodeUCS2);
18 |
19 |
20 | if (preferred != null)
21 | {
22 | glyphIndex = preferred.Subtable.GetGlyphIndex(c);
23 | }
24 |
25 | if (glyphIndex != 0)
26 | {
27 | return glyphIndex;
28 | }
29 |
30 | // Fall back to using any table to find the match
31 | foreach (var record in font.CmapTable.EncodingRecords)
32 | {
33 | glyphIndex = record.Subtable.GetGlyphIndex(c);
34 | if (glyphIndex != 0)
35 | {
36 | return glyphIndex;
37 | }
38 | }
39 |
40 | return 0;
41 | }
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Helpers/KerningHelper.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.Tables.Kern;
2 |
3 | namespace RoyT.TrueType.Helpers
4 | {
5 | public static class KerningHelper
6 | {
7 | ///
8 | /// Returns the horizontal kerning between the left and right character scaled by the scale parameter
9 | /// or 0 if no kerning information exists for this pair of characters
10 | ///
11 | public static float GetHorizontalKerning(char left, char right, TrueTypeFont font)
12 | {
13 | if (font.KernTable.SubtableCount > 0)
14 | {
15 | var leftCode = GlyphHelper.GetGlyphIndex(left, font);
16 | var rightCode = GlyphHelper.GetGlyphIndex(right, font);
17 |
18 | foreach (var subTable in font.KernTable.Subtables)
19 | {
20 | if (subTable.Format0 != null && subTable.Direction == Direction.Horizontal
21 | && subTable.Values == Values.Kerning)
22 | {
23 | var pair = new KerningPair((ushort)leftCode, (ushort)rightCode);
24 |
25 | if (subTable.Format0.Map.TryGetValue(pair, out var value))
26 | {
27 | return value;
28 | }
29 | }
30 | }
31 | }
32 |
33 | return 0.0f;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Helpers/NameHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Linq;
3 | using RoyT.TrueType.Tables.Name;
4 |
5 | namespace RoyT.TrueType.Helpers
6 | {
7 | public static class NameHelper
8 | {
9 | public static string GetName(NameId nameId, CultureInfo culture, TrueTypeFont font)
10 | {
11 | var candidates = font.NameTable.NameRecords.Where(r =>
12 | r.NameId == nameId &&
13 | r.Language.Equals(culture))
14 | .ToList();
15 |
16 | if (candidates.Any())
17 | {
18 |
19 | // Prefer the Windows platform as the text has a rich encoding (UTF-16)
20 | if (candidates.Any(x => x.PlatformId == Platform.Windows))
21 | {
22 | return candidates.First(x => x.PlatformId == Platform.Windows).Text;
23 | }
24 |
25 | // Fallback to any platform
26 | return candidates.First().Text;
27 |
28 | }
29 |
30 | return string.Empty;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/RoyT.TrueType/IO/FontReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Data;
4 | using System.IO;
5 | using System.Text;
6 |
7 | namespace RoyT.TrueType.IO
8 | {
9 | ///
10 | /// Extended BinaryReader with helper methods for reading number and text formats
11 | /// common in TrueType files
12 | ///
13 | public sealed class FontReader : BinaryReader
14 | {
15 | public FontReader(Stream stream)
16 | : base(stream) { }
17 |
18 | public long Position => this.BaseStream.Position;
19 |
20 | public short ReadInt16BigEndian()
21 | {
22 | var bytes = ReadBigEndian(2);
23 | return BitConverter.ToInt16(bytes, 0);
24 | }
25 |
26 | public ushort ReadUInt16BigEndian()
27 | {
28 | var bytes = ReadBigEndian(2);
29 | return BitConverter.ToUInt16(bytes, 0);
30 | }
31 |
32 | public uint ReadUInt32BigEndian()
33 | {
34 | var bytes = ReadBigEndian(4);
35 | return BitConverter.ToUInt32(bytes, 0);
36 | }
37 |
38 | public long ReadInt64BigEndian()
39 | {
40 | var bytes = ReadBigEndian(8);
41 | return BitConverter.ToInt64(bytes, 0);
42 | }
43 |
44 | public string ReadAscii(int length)
45 | {
46 | var bytes = this.ReadBytes(length);
47 | return Encoding.ASCII.GetString(bytes);
48 | }
49 |
50 | public string ReadBigEndianUnicode(int length)
51 | {
52 | var bytes = ReadBytes(length);
53 | return Encoding.BigEndianUnicode.GetString(bytes).Replace("\0", string.Empty);
54 | }
55 |
56 | public BitArray ReadBitsBigEndian(int numberOfBytes)
57 | {
58 | var bytes = ReadBigEndian(numberOfBytes);
59 | return new BitArray(bytes);
60 | }
61 |
62 | ///
63 | /// Reads a 32 bit signed fixed point number (16.16)
64 | ///
65 | public float ReadFixedBigEndian()
66 | {
67 | var dec = this.ReadInt16BigEndian();
68 | var frac = this.ReadUInt16BigEndian();
69 | return dec + frac / 65535f;
70 | }
71 |
72 | private byte[] ReadBigEndian(int count)
73 | {
74 | var data = base.ReadBytes(count);
75 |
76 | if (BitConverter.IsLittleEndian)
77 | {
78 | Array.Reverse(data);
79 | }
80 |
81 | return data;
82 | }
83 |
84 | public void Seek(long offset, SeekOrigin seekOrigin = SeekOrigin.Begin)
85 | {
86 | this.BaseStream.Seek(offset, seekOrigin);
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/RoyT.TrueType/IsExternal.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace System.Runtime.CompilerServices
4 | {
5 | [EditorBrowsable(EditorBrowsableState.Never)]
6 | internal static class IsExternalInit { }
7 | }
--------------------------------------------------------------------------------
/RoyT.TrueType/Platform.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace RoyT.TrueType
4 | {
5 | public enum Platform : ushort
6 | {
7 | Unicode = 0,
8 | Macintosh = 1,
9 |
10 | [Obsolete]
11 | ISO = 2,
12 |
13 | Windows = 3,
14 | Custom = 4
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace RoyT.TrueType.Properties {
12 | using System;
13 | using System.Reflection;
14 |
15 |
16 | ///
17 | /// A strongly-typed resource class, for looking up localized strings, etc.
18 | ///
19 | // This class was auto-generated by the StronglyTypedResourceBuilder
20 | // class via a tool like ResGen or Visual Studio.
21 | // To add or remove a member, edit your .ResX file then rerun ResGen
22 | // with the /str option, or rebuild your VS project.
23 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
24 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
25 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
26 | internal class Resources {
27 |
28 | private static global::System.Resources.ResourceManager resourceMan;
29 |
30 | private static global::System.Globalization.CultureInfo resourceCulture;
31 |
32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
33 | internal Resources() {
34 | }
35 |
36 | ///
37 | /// Returns the cached ResourceManager instance used by this class.
38 | ///
39 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
40 | internal static global::System.Resources.ResourceManager ResourceManager {
41 | get {
42 | if (object.ReferenceEquals(resourceMan, null)) {
43 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RoyT.TrueType.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly);
44 | resourceMan = temp;
45 | }
46 | return resourceMan;
47 | }
48 | }
49 |
50 | ///
51 | /// Overrides the current thread's CurrentUICulture property for all
52 | /// resource lookups using this strongly typed resource class.
53 | ///
54 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
55 | internal static global::System.Globalization.CultureInfo Culture {
56 | get {
57 | return resourceCulture;
58 | }
59 | set {
60 | resourceCulture = value;
61 | }
62 | }
63 |
64 | ///
65 | /// Looks up a localized string similar to 0x0001 ar
66 | ///0x0002 bg
67 | ///0x0003 ca
68 | ///0x0004 zh-Hans
69 | ///0x0005 cs
70 | ///0x0006 da
71 | ///0x0007 de
72 | ///0x0008 el
73 | ///0x0009 en
74 | ///0x000A es
75 | ///0x000B fi
76 | ///0x000C fr
77 | ///0x000D he
78 | ///0x000E hu
79 | ///0x000F is
80 | ///0x0010 it
81 | ///0x0011 ja
82 | ///0x0012 ko
83 | ///0x0013 nl
84 | ///0x0014 no
85 | ///0x0015 pl
86 | ///0x0016 pt
87 | ///0x0017 rm
88 | ///0x0018 ro
89 | ///0x0019 ru
90 | ///0x001A bs
91 | ///0x001B sk
92 | ///0x001C sq
93 | ///0x001D sv
94 | ///0x001E th
95 | ///0x001F tr
96 | ///0x0020 ur
97 | ///0x0021 id
98 | ///0x0022 uk
99 | ///0x0023 be
100 | ///0x0024 sl
101 | ///0x0025 et
102 | ///0x0026 lv
103 | ///0x0027 lt
104 | ///0x0028 tg
105 | ///0x0029 fa
106 | ///0x002A vi
107 | ///0x002B hy
108 | ///0x002C az
109 | ///0x002D eu
110 | ///0x002E dsb
111 | /// [rest of string was truncated]";.
112 | ///
113 | internal static string LCIDMap {
114 | get {
115 | return ResourceManager.GetString("LCIDMap", resourceCulture);
116 | }
117 | }
118 |
119 | ///
120 | /// Looks up a localized string similar to 0 en
121 | ///1 fr
122 | ///2 de
123 | ///3 it
124 | ///4 nl
125 | ///5 sv
126 | ///6 es
127 | ///7 da
128 | ///8 pt
129 | ///9 no
130 | ///10 he
131 | ///11 ja
132 | ///12 ar
133 | ///13 fi
134 | ///14 el
135 | ///15 is
136 | ///16 mt
137 | ///17 tr
138 | ///18 hr
139 | ///19 zh
140 | ///20 ur
141 | ///21 hi
142 | ///22 th
143 | ///23 ko
144 | ///24 lt
145 | ///25 pl
146 | ///26 hu
147 | ///27 et
148 | ///28 lv
149 | ///30 fo
150 | ///31 fa
151 | ///32 ru
152 | ///33 zh
153 | ///35 ga
154 | ///36 sq
155 | ///37 ro
156 | ///38 cs
157 | ///39 sk
158 | ///40 sl
159 | ///41 yi
160 | ///42 sr
161 | ///43 mk
162 | ///44 bg
163 | ///45 uk
164 | ///46 be
165 | ///47 uz
166 | ///48 kk
167 | ///49 az
168 | ///50 az
169 | ///51 hy
170 | ///52 ka
171 | ///53 mo
172 | ///54 ky
173 | ///55 tg
174 | ///56 tk
175 | ///57 mn
176 | ///58 mn
177 | ///59 ps
178 | ///60 ku
179 | ///61 ks
180 | ///62 sd
181 | ///63 bo
182 | ///64 ne
183 | ///65 sa
184 | ///66 mr
185 | ///67 bn
186 | ///68 as
187 | ///69 gu
188 | ///70 pa
189 | ///71 or
190 | ///72 ml
191 | ///73 kn
192 | ///74 ta
193 | ///75 te
194 | ///76 s [rest of string was truncated]";.
195 | ///
196 | internal static string MacLanguageCodeMap {
197 | get {
198 | return ResourceManager.GetString("MacLanguageCodeMap", resourceCulture);
199 | }
200 | }
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | ..\resources\lcidmap.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
123 |
124 |
125 | ..\resources\maclanguagecodemap.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
126 |
127 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Resources/LCIDMap.txt:
--------------------------------------------------------------------------------
1 | 0x0001 ar
2 | 0x0002 bg
3 | 0x0003 ca
4 | 0x0004 zh-Hans
5 | 0x0005 cs
6 | 0x0006 da
7 | 0x0007 de
8 | 0x0008 el
9 | 0x0009 en
10 | 0x000A es
11 | 0x000B fi
12 | 0x000C fr
13 | 0x000D he
14 | 0x000E hu
15 | 0x000F is
16 | 0x0010 it
17 | 0x0011 ja
18 | 0x0012 ko
19 | 0x0013 nl
20 | 0x0014 no
21 | 0x0015 pl
22 | 0x0016 pt
23 | 0x0017 rm
24 | 0x0018 ro
25 | 0x0019 ru
26 | 0x001A bs
27 | 0x001B sk
28 | 0x001C sq
29 | 0x001D sv
30 | 0x001E th
31 | 0x001F tr
32 | 0x0020 ur
33 | 0x0021 id
34 | 0x0022 uk
35 | 0x0023 be
36 | 0x0024 sl
37 | 0x0025 et
38 | 0x0026 lv
39 | 0x0027 lt
40 | 0x0028 tg
41 | 0x0029 fa
42 | 0x002A vi
43 | 0x002B hy
44 | 0x002C az
45 | 0x002D eu
46 | 0x002E dsb
47 | 0x002F mk
48 | 0x0030 st
49 | 0x0031 ts
50 | 0x0032 tn
51 | 0x0033 ve
52 | 0x0034 xh
53 | 0x0035 zu
54 | 0x0036 af
55 | 0x0037 ka
56 | 0x0038 fo
57 | 0x0039 hi
58 | 0x003A mt
59 | 0x003B se
60 | 0x003C ga
61 | 0x003D yi
62 | 0x003E ms
63 | 0x003F kk
64 | 0x0040 ky
65 | 0x0041 sw
66 | 0x0042 tk
67 | 0x0043 uz
68 | 0x0044 tt
69 | 0x0045 bn
70 | 0x0046 pa
71 | 0x0047 gu
72 | 0x0048 or
73 | 0x0049 ta
74 | 0x004A te
75 | 0x004B kn
76 | 0x004C ml
77 | 0x004D as
78 | 0x004E mr
79 | 0x004F sa
80 | 0x0050 mn
81 | 0x0051 bo
82 | 0x0052 cy
83 | 0x0053 km
84 | 0x0054 lo
85 | 0x0055 my
86 | 0x0056 gl
87 | 0x0057 kok
88 | 0x0058 mni
89 | 0x0059 sd
90 | 0x005A syr
91 | 0x005B si
92 | 0x005C chr
93 | 0x005D iu
94 | 0x005E am
95 | 0x005F tzm
96 | 0x0060 ks
97 | 0x0061 ne
98 | 0x0062 fy
99 | 0x0063 ps
100 | 0x0064 fil
101 | 0x0065 dv
102 | 0x0066 bin
103 | 0x0067 ff
104 | 0x0068 ha
105 | 0x0069 ibb
106 | 0x006A yo
107 | 0x006B quz
108 | 0x006C nso
109 | 0x006D ba
110 | 0x006E lb
111 | 0x006F kl
112 | 0x0070 ig
113 | 0x0071 kr
114 | 0x0072 om
115 | 0x0073 ti
116 | 0x0074 gn
117 | 0x0075 haw
118 | 0x0076 la
119 | 0x0077 so
120 | 0x0078 ii
121 | 0x0079 pap
122 | 0x007A arn
123 | 0x007C moh
124 | 0x007E br
125 | 0x0080 ug
126 | 0x0081 mi
127 | 0x0082 oc
128 | 0x0083 co
129 | 0x0084 gsw
130 | 0x0085 sah
131 | 0x0086 qut
132 | 0x0087 rw
133 | 0x0088 wo
134 | 0x008C prs
135 | 0x0091 gd
136 | 0x0092 ku
137 | 0x0093 quc
138 | 0x0401 ar-SA
139 | 0x0402 bg-BG
140 | 0x0403 ca-ES
141 | 0x0404 zh-TW
142 | 0x0405 cs-CZ
143 | 0x0406 da-DK
144 | 0x0407 de-DE
145 | 0x0408 el-GR
146 | 0x0409 en-US
147 | 0x040A es-ES_tradnl
148 | 0x040B fi-FI
149 | 0x040C fr-FR
150 | 0x040D he-IL
151 | 0x040E hu-HU
152 | 0x040F is-IS
153 | 0x0410 it-IT
154 | 0x0411 ja-JP
155 | 0x0412 ko-KR
156 | 0x0413 nl-NL
157 | 0x0414 nb-NO
158 | 0x0415 pl-PL
159 | 0x0416 pt-BR
160 | 0x0417 rm-CH
161 | 0x0418 ro-RO
162 | 0x0419 ru-RU
163 | 0x041A hr-HR
164 | 0x041B sk-SK
165 | 0x041C sq-AL
166 | 0x041D sv-SE
167 | 0x041E th-TH
168 | 0x041F tr-TR
169 | 0x0420 ur-PK
170 | 0x0421 id-ID
171 | 0x0422 uk-UA
172 | 0x0423 be-BY
173 | 0x0424 sl-SI
174 | 0x0425 et-EE
175 | 0x0426 lv-LV
176 | 0x0427 lt-LT
177 | 0x0428 tg-Cyrl-TJ
178 | 0x0429 fa-IR
179 | 0x042A vi-VN
180 | 0x042B hy-AM
181 | 0x042C az-Latn-AZ
182 | 0x042D eu-ES
183 | 0x042E hsb-DE
184 | 0x042F mk-MK
185 | 0x0430 st-ZA
186 | 0x0431 ts-ZA
187 | 0x0432 tn-ZA
188 | 0x0433 ve-ZA
189 | 0x0434 xh-ZA
190 | 0x0435 zu-ZA
191 | 0x0436 af-ZA
192 | 0x0437 ka-GE
193 | 0x0438 fo-FO
194 | 0x0439 hi-IN
195 | 0x043A mt-MT
196 | 0x043B se-NO
197 | 0x043D yi-Hebr
198 | 0x043E ms-MY
199 | 0x043F kk-KZ
200 | 0x0440 ky-KG
201 | 0x0441 sw-KE
202 | 0x0442 tk-TM
203 | 0x0443 uz-Latn-UZ
204 | 0x0444 tt-RU
205 | 0x0445 bn-IN
206 | 0x0446 pa-IN
207 | 0x0447 gu-IN
208 | 0x0448 or-IN
209 | 0x0449 ta-IN
210 | 0x044A te-IN
211 | 0x044B kn-IN
212 | 0x044C ml-IN
213 | 0x044D as-IN
214 | 0x044E mr-IN
215 | 0x044F sa-IN
216 | 0x0450 mn-MN
217 | 0x0451 bo-CN
218 | 0x0452 cy-GB
219 | 0x0453 km-KH
220 | 0x0454 lo-LA
221 | 0x0455 my-MM
222 | 0x0456 gl-ES
223 | 0x0457 kok-IN
224 | 0x0458 mni-IN
225 | 0x0459 sd-Deva-IN
226 | 0x045A syr-SY
227 | 0x045B si-LK
228 | 0x045C chr-Cher-US
229 | 0x045D iu-Cans-CA
230 | 0x045E am-ET
231 | 0x045F tzm-Arab-MA
232 | 0x0460 ks-Arab
233 | 0x0461 ne-NP
234 | 0x0462 fy-NL
235 | 0x0463 ps-AF
236 | 0x0464 fil-PH
237 | 0x0465 dv-MV
238 | 0x0466 bin-NG
239 | 0x0467 fuv-NG
240 | 0x0468 ha-Latn-NG
241 | 0x0469 ibb-NG
242 | 0x046A yo-NG
243 | 0x046B quz-BO
244 | 0x046C nso-ZA
245 | 0x046D ba-RU
246 | 0x046E lb-LU
247 | 0x046F kl-GL
248 | 0x0470 ig-NG
249 | 0x0471 kr-NG
250 | 0x0472 om-ET
251 | 0x0473 ti-ET
252 | 0x0474 gn-PY
253 | 0x0475 haw-US
254 | 0x0476 la-Latn
255 | 0x0477 so-SO
256 | 0x0478 ii-CN
257 | 0x0479 pap-029
258 | 0x047A arn-CL
259 | 0x047C moh-CA
260 | 0x047E br-FR
261 | 0x0480 ug-CN
262 | 0x0481 mi-NZ
263 | 0x0482 oc-FR
264 | 0x0483 co-FR
265 | 0x0484 gsw-FR
266 | 0x0485 sah-RU
267 | 0x0486 qut-GT
268 | 0x0487 rw-RW
269 | 0x0488 wo-SN
270 | 0x048C prs-AF
271 | 0x048D plt-MG
272 | 0x048E zh-yue-HK
273 | 0x048F tdd-Tale-CN
274 | 0x0490 khb-Talu-CN
275 | 0x0491 gd-GB
276 | 0x0492 ku-Arab-IQ
277 | 0x0493 quc-CO
278 | 0x0501 qps-ploc
279 | 0x05FE qps-ploca
280 | 0x0801 ar-IQ
281 | 0x0803 ca-ES-valencia
282 | 0x0804 zh-CN
283 | 0x0807 de-CH
284 | 0x0809 en-GB
285 | 0x080A es-MX
286 | 0x080C fr-BE
287 | 0x0810 it-CH
288 | 0x0811 ja-Ploc-JP
289 | 0x0813 nl-BE
290 | 0x0814 nn-NO
291 | 0x0816 pt-PT
292 | 0x0818 ro-MD
293 | 0x0819 ru-MD
294 | 0x081A sr-Latn-CS
295 | 0x081D sv-FI
296 | 0x0820 ur-IN
297 | 0x082C az-Cyrl-AZ
298 | 0x082E dsb-DE
299 | 0x0832 tn-BW
300 | 0x083B se-SE
301 | 0x083C ga-IE
302 | 0x083E ms-BN
303 | 0x0843 uz-Cyrl-UZ
304 | 0x0845 bn-BD
305 | 0x0846 pa-Arab-PK
306 | 0x0849 ta-LK
307 | 0x0850 mn-Mong-CN
308 | 0x0851 bo-BT
309 | 0x0859 sd-Arab-PK
310 | 0x085D iu-Latn-CA
311 | 0x085F tzm-Latn-DZ
312 | 0x0860 ks-Deva
313 | 0x0861 ne-IN
314 | 0x0867 ff-Latn-SN
315 | 0x086B quz-EC
316 | 0x0873 ti-ER
317 | 0x09FF qps-plocm
318 | 0x0C01 ar-EG
319 | 0x0C04 zh-HK
320 | 0x0C07 de-AT
321 | 0x0C09 en-AU
322 | 0x0C0A es-ES
323 | 0x0C0C fr-CA
324 | 0x0C1A sr-Cyrl-CS
325 | 0x0C3B se-FI
326 | 0x0C50 mn-Mong-MN
327 | 0x0C51 dz-BT
328 | 0x0C5F tmz-MA
329 | 0x0C6b quz-PE
330 | 0x1001 ar-LY
331 | 0x1004 zh-SG
332 | 0x1007 de-LU
333 | 0x1009 en-CA
334 | 0x100A es-GT
335 | 0x100C fr-CH
336 | 0x101A hr-BA
337 | 0x103B smj-NO
338 | 0x105F tzm-Tfng-MA
339 | 0x1401 ar-DZ
340 | 0x1404 zh-MO
341 | 0x1407 de-LI
342 | 0x1409 en-NZ
343 | 0x140A es-CR
344 | 0x140C fr-LU
345 | 0x141A bs-Latn-BA
346 | 0x143B smj-SE
347 | 0x1801 ar-MA
348 | 0x1809 en-IE
349 | 0x180A es-PA
350 | 0x180C fr-MC
351 | 0x181A sr-Latn-BA
352 | 0x183B sma-NO
353 | 0x1C01 ar-TN
354 | 0x1C09 en-ZA
355 | 0x1C0A es-DO
356 | 0x1C1A sr-Cyrl-BA
357 | 0x1C3B sma-SE
358 | 0x2001 ar-OM
359 | 0x2009 en-JM
360 | 0x200A es-VE
361 | 0x200C fr-RE
362 | 0x201A bs-Cyrl-BA
363 | 0x203B sms-FI
364 | 0x2401 ar-YE
365 | 0x2409 en-029
366 | 0x240A es-CO
367 | 0x240C fr-CD
368 | 0x241A sr-Latn-RS
369 | 0x243B smn-FI
370 | 0x2801 ar-SY
371 | 0x2809 en-BZ
372 | 0x280A es-PE
373 | 0x280C fr-SN
374 | 0x281A sr-Cyrl-RS
375 | 0x2C01 ar-JO
376 | 0x2C09 en-TT
377 | 0x2C0A es-AR
378 | 0x2C0C fr-CM
379 | 0x2C1A sr-Latn-ME
380 | 0x3001 ar-LB
381 | 0x3009 en-ZW
382 | 0x300A es-EC
383 | 0x300C fr-CI
384 | 0x301A sr-Cyrl-ME
385 | 0x3401 ar-KW
386 | 0x3409 en-PH
387 | 0x340A es-CL
388 | 0x340C fr-ML
389 | 0x3801 ar-AE
390 | 0x3809 en-ID
391 | 0x380A es-UY
392 | 0x380C fr-MA
393 | 0x3c01 ar-BH
394 | 0x3c09 en-HK
395 | 0x3c0A es-PY
396 | 0x3c0C fr-HT
397 | 0x4001 ar-QA
398 | 0x4009 en-IN
399 | 0x400A es-BO
400 | 0x4401 ar-Ploc-SA
401 | 0x4409 en-MY
402 | 0x440A es-SV
403 | 0x4801 ar-145
404 | 0x4809 en-SG
405 | 0x480A es-HN
406 | 0x4C09 en-AE
407 | 0x4C0A es-NI
408 | 0x5009 en-BH
409 | 0x500A es-PR
410 | 0x5409 en-EG
411 | 0x540A es-US
412 | 0x5809 en-JO
413 | 0x580A es-419
414 | 0x5C09 en-KW
415 | 0x5C0A es-CU
416 | 0x6009 en-TR
417 | 0x6409 en-YE
418 | 0x641A bs-Cyrl
419 | 0x681A bs-Latn
420 | 0x6C1A sr-Cyrl
421 | 0x701A sr-Latn
422 | 0x703B smn
423 | 0x742C az-Cyrl
424 | 0x743B sms
425 | 0x7804 zh
426 | 0x7814 nn
427 | 0x781A bs
428 | 0x782C az-Latn
429 | 0x783B sma
430 | 0x7843 uz-Cyrl
431 | 0x7850 mn-Cyrl
432 | 0x785D iu-Cans
433 | 0x785F tzm-Tfng
434 | 0x7C04 zh-Hant
435 | 0x7C14 nb
436 | 0x7C1A sr
437 | 0x7C28 tg-Cyrl
438 | 0x7C2E dsb
439 | 0x7C3B smj
440 | 0x7C43 uz-Latn
441 | 0x7C46 pa-Arab
442 | 0x7C50 mn-Mong
443 | 0x7C59 sd-Arab
444 | 0x7C5C chr-Cher
445 | 0x7C5D iu-Latn
446 | 0x7C5F tzm-Latn
447 | 0x7C67 ff-Latn
448 | 0x7C68 ha-Latn
449 | 0x7C92 ku-Arab
450 | 0x0001007F x-IV-mathan
451 | 0x00010407 de-DE_phoneb
452 | 0x0001040E hu-HU_tchncl
453 | 0x00010437 ka-GE_modern
454 | 0x00020804 zh-CN_stroke
455 | 0x00021004 zh-SG_stroke
456 | 0x00021404 zh-MO_stroke
457 | 0x00030404 zh-TW_pronun
458 | 0x00040404 zh-TW_radstr
459 | 0x00040411 ja-JP_radstr
460 | 0x00040C04 zh-HK_radstr
461 | 0x00041404 zh-MO_radstr
462 | 0x00050804 zh-CN_phoneb
463 | 0x00051004 zh-SG_phoneb
--------------------------------------------------------------------------------
/RoyT.TrueType/Resources/MacLanguageCodeMap.txt:
--------------------------------------------------------------------------------
1 | 0 en
2 | 1 fr
3 | 2 de
4 | 3 it
5 | 4 nl
6 | 5 sv
7 | 6 es
8 | 7 da
9 | 8 pt
10 | 9 no
11 | 10 he
12 | 11 ja
13 | 12 ar
14 | 13 fi
15 | 14 el
16 | 15 is
17 | 16 mt
18 | 17 tr
19 | 18 hr
20 | 19 zh
21 | 20 ur
22 | 21 hi
23 | 22 th
24 | 23 ko
25 | 24 lt
26 | 25 pl
27 | 26 hu
28 | 27 et
29 | 28 lv
30 | 30 fo
31 | 31 fa
32 | 32 ru
33 | 33 zh
34 | 35 ga
35 | 36 sq
36 | 37 ro
37 | 38 cs
38 | 39 sk
39 | 40 sl
40 | 41 yi
41 | 42 sr
42 | 43 mk
43 | 44 bg
44 | 45 uk
45 | 46 be
46 | 47 uz
47 | 48 kk
48 | 49 az
49 | 50 az
50 | 51 hy
51 | 52 ka
52 | 53 mo
53 | 54 ky
54 | 55 tg
55 | 56 tk
56 | 57 mn
57 | 58 mn
58 | 59 ps
59 | 60 ku
60 | 61 ks
61 | 62 sd
62 | 63 bo
63 | 64 ne
64 | 65 sa
65 | 66 mr
66 | 67 bn
67 | 68 as
68 | 69 gu
69 | 70 pa
70 | 71 or
71 | 72 ml
72 | 73 kn
73 | 74 ta
74 | 75 te
75 | 76 si
76 | 77 my
77 | 78 km
78 | 79 lo
79 | 80 vi
80 | 81 id
81 | 82 tl
82 | 83 ms
83 | 84 ms
84 | 85 am
85 | 86 ti
86 | 87 om
87 | 88 so
88 | 89 sw
89 | 90 rw
90 | 91 rn
91 | 93 mg
92 | 94 eo
93 | 128 cy
94 | 129 eu
95 | 130 ca
96 | 131 la
97 | 132 qu
98 | 133 gn
99 | 134 ay
100 | 135 tt
101 | 136 ug
102 | 137 dz
103 | 138 jv
104 | 139 su
105 | 140 gl
106 | 141 af
107 | 142 br
108 | 143 iu
109 | 144 gd
110 | 145 gv
111 | 146 ga
112 | 147 to
113 | 148 el
--------------------------------------------------------------------------------
/RoyT.TrueType/RoyT.TrueType.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | latest
6 | 0.2.0
7 | Roy Triesscheijn, Wouter de Vries
8 |
9 |
10 | A TrueType parser for reading, glyphIds, names, descriptions, and kerning information from TrueType fonts.
11 |
12 | https://github.com/roy-t/TrueType/blob/master/LICENSE
13 | https://github.com/roy-t/TrueType
14 | https://github.com/roy-t/TrueType
15 |
16 | truetype, ttf, opentype, font, fonts, parser
17 | 0.2.0.0
18 | 0.2.0.0
19 | true
20 |
21 |
22 |
23 |
24 | True
25 | True
26 | Resources.resx
27 |
28 |
29 |
30 |
31 |
32 | ResXFileCodeGenerator
33 | Resources.Designer.cs
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Cmap/ByteEncodingTable.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 |
3 | namespace RoyT.TrueType.Tables.Cmap
4 | {
5 | public sealed class ByteEncodingTable : ICmapSubtable
6 | {
7 | public static ByteEncodingTable FromReader(FontReader reader)
8 | {
9 | var format = reader.ReadUInt16BigEndian();
10 | var length = reader.ReadUInt16BigEndian();
11 | var language = reader.ReadUInt16BigEndian();
12 | var ids = new byte[256];
13 |
14 | for (var i = 0; i < ids.Length; i++)
15 | {
16 | ids[i] = reader.ReadByte();
17 | }
18 |
19 | return new ByteEncodingTable(format, length, language, ids);
20 | }
21 |
22 | private ByteEncodingTable(ushort format, ushort length, ushort language, byte[] ids)
23 | {
24 | this.Format = format;
25 | this.Length = length;
26 | this.Language = language;
27 | this.Ids = ids;
28 | }
29 |
30 | public ushort Format { get; }
31 | public ushort Length { get; }
32 | public ushort Language { get; }
33 | public byte[] Ids { get; }
34 |
35 | public uint GetGlyphIndex(char c)
36 | {
37 | var charCode = (int) c;
38 | if (c >= 0 && c < this.Ids.Length)
39 | {
40 | return this.Ids[charCode];
41 | }
42 |
43 | return 0;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Cmap/CmapSubtableFormat.cs:
--------------------------------------------------------------------------------
1 | namespace RoyT.TrueType.Tables.Cmap
2 | {
3 | public enum CMapSubtableFormat : ushort
4 | {
5 | ByteEncodingTable = 0,
6 | HighByteMappingThroughTable = 2,
7 | SegmentMappingToDeltaValues = 4,
8 | TrimmedTableMapping = 6,
9 | MixedCoverage = 8,
10 | TrimmedArray = 10,
11 | SegmentedCoverage = 12,
12 | ManyToOneRangeMappings = 13,
13 | UnicodeVariationSequences = 14
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Cmap/CmapTable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using RoyT.TrueType.IO;
4 |
5 | namespace RoyT.TrueType.Tables.Cmap
6 | {
7 | public sealed class CmapTable
8 | {
9 | ///
10 | /// Contains information to get the glyph that corresponds to each supported character
11 | ///
12 | public static CmapTable FromReader(FontReader reader)
13 | {
14 | var cmapOffset = reader.Position;
15 | var version = reader.ReadUInt16BigEndian();
16 | if (version != 0)
17 | {
18 | throw new Exception($"Unexpected Cmap tab;e version. Expected: 0, actual: {version}");
19 | }
20 |
21 | var tables = reader.ReadUInt16BigEndian();
22 |
23 | var encodingRecords = new EncodingRecord[tables];
24 | for (var i = 0; i < tables; i++)
25 | {
26 | encodingRecords[i] = EncodingRecord.FromReader(reader, cmapOffset);
27 | }
28 |
29 | return new CmapTable(version, encodingRecords);
30 | }
31 |
32 | private CmapTable(ushort version, IReadOnlyList encodingRecords)
33 | {
34 | this.Version = version;
35 | this.EncodingRecords = encodingRecords;
36 | }
37 |
38 | public ushort Version { get; }
39 | public IReadOnlyList EncodingRecords { get; }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Cmap/EncodingRecord.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 |
3 | namespace RoyT.TrueType.Tables.Cmap
4 | {
5 | public sealed class EncodingRecord
6 | {
7 | public static EncodingRecord FromReader(FontReader reader, long cmapOffset)
8 | {
9 | var platformId = (Platform)reader.ReadUInt16BigEndian();
10 | var encodingId = (WindowsEncoding)reader.ReadUInt16BigEndian();
11 | var offset = reader.ReadUInt32BigEndian();
12 |
13 | var lastPosition = reader.Position;
14 | reader.Seek(cmapOffset + offset);
15 | var subTable = ReadSubtable(reader, out var format);
16 | reader.Seek(lastPosition);
17 |
18 | return new EncodingRecord(platformId, encodingId, offset, subTable, format);
19 | }
20 |
21 | private static ICmapSubtable ReadSubtable(FontReader reader, out CMapSubtableFormat format)
22 | {
23 | var start = reader.Position;
24 | format = (CMapSubtableFormat) reader.ReadUInt16BigEndian();
25 | reader.Seek(start);
26 | switch (format)
27 | {
28 | // Ordered from most to least used, on the Windows platform
29 | case CMapSubtableFormat.SegmentMappingToDeltaValues: // Format 4
30 | return SegmentMappingToDeltaValuesTable.FromReader(reader);
31 |
32 | case CMapSubtableFormat.TrimmedTableMapping: // Format 6
33 | return TrimmedTableMappingTable.FromReader(reader);
34 |
35 |
36 | case CMapSubtableFormat.SegmentedCoverage: // Format 12
37 | return SegmentedCoverageTable.FromReader(reader);
38 |
39 | case CMapSubtableFormat.ByteEncodingTable: // Format 0
40 | return ByteEncodingTable.FromReader(reader);
41 |
42 | // Used for specifying variations of the same glyph in a single font
43 | case CMapSubtableFormat.UnicodeVariationSequences: // Format 14
44 | return null;
45 |
46 | // The following formats are not used by any of the default fonts in Windows 10
47 | case CMapSubtableFormat.HighByteMappingThroughTable:
48 | return null;
49 |
50 | case CMapSubtableFormat.MixedCoverage:
51 | return null;
52 |
53 | case CMapSubtableFormat.TrimmedArray:
54 | return null;
55 |
56 | case CMapSubtableFormat.ManyToOneRangeMappings:
57 | return null;
58 |
59 | default:
60 | return null;
61 | }
62 | }
63 |
64 | private EncodingRecord(Platform platformId, WindowsEncoding encodingId, uint offset, ICmapSubtable subtable, CMapSubtableFormat format)
65 | {
66 | this.PlatformId = platformId;
67 | this.WindowsEncodingId = encodingId;
68 | this.Offset = offset;
69 | this.Subtable = subtable;
70 | this.Format = format;
71 | }
72 |
73 | public Platform PlatformId { get; }
74 | ///
75 | /// Encoding, only valid if platform is Windows
76 | ///
77 | public WindowsEncoding WindowsEncodingId { get; }
78 | public uint Offset { get; }
79 | public ICmapSubtable Subtable { get; }
80 | public CMapSubtableFormat Format { get; }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Cmap/ICmapSubtable.cs:
--------------------------------------------------------------------------------
1 | namespace RoyT.TrueType.Tables.Cmap
2 | {
3 | public interface ICmapSubtable
4 | {
5 | uint GetGlyphIndex(char c);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Cmap/SegmentMappingToDeltaValuesTable.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 |
3 | namespace RoyT.TrueType.Tables.Cmap
4 | {
5 | public sealed class SegmentMappingToDeltaValuesTable : ICmapSubtable
6 | {
7 | public static SegmentMappingToDeltaValuesTable FromReader(FontReader reader)
8 | {
9 | var start = reader.Position;
10 |
11 | var format = reader.ReadUInt16BigEndian();
12 | var length = reader.ReadUInt16BigEndian();
13 | var language = reader.ReadUInt16BigEndian();
14 |
15 | var segCountX2 = reader.ReadUInt16BigEndian();
16 | var searchRange = reader.ReadUInt16BigEndian();
17 | var entrySelector = reader.ReadUInt16BigEndian();
18 | var rangeShift = reader.ReadUInt16BigEndian();
19 |
20 |
21 | var segCount = segCountX2 / 2;
22 | var endCount = new ushort[segCount];
23 | for (var i = 0; i < endCount.Length; i++)
24 | {
25 | endCount[i] = reader.ReadUInt16BigEndian();
26 | }
27 |
28 | var reservedPad = reader.ReadUInt16BigEndian();
29 |
30 | var startCount = new ushort[segCount];
31 | for (var i = 0; i < startCount.Length; i++)
32 | {
33 | startCount[i] = reader.ReadUInt16BigEndian();
34 | }
35 |
36 | var idDelta = new short[segCount];
37 | for (var i = 0; i < idDelta.Length; i++)
38 | {
39 | idDelta[i] = reader.ReadInt16BigEndian();
40 | }
41 |
42 | var idRangeOffset = new ushort[segCount];
43 | for (var i = 0; i < idRangeOffset.Length; i++)
44 | {
45 | idRangeOffset[i] = reader.ReadUInt16BigEndian();
46 | }
47 |
48 | var read = reader.Position - start;
49 | var glyphCount = (length - read) / sizeof (ushort);
50 | var glyphIdArray = new ushort[glyphCount];
51 | for (var i = 0; i < glyphIdArray.Length; i++)
52 | {
53 | glyphIdArray[i] = reader.ReadUInt16BigEndian();
54 | }
55 |
56 | return new SegmentMappingToDeltaValuesTable(format, reservedPad, length, language, segCount, searchRange, entrySelector, rangeShift,
57 | endCount, startCount, idDelta, idRangeOffset, glyphCount, glyphIdArray);
58 | }
59 |
60 | public SegmentMappingToDeltaValuesTable(ushort format, ushort reservedPad, ushort length, ushort language, int segCount, ushort searchRange, ushort entrySelector, ushort rangeShift, ushort[] endCount, ushort[] startCount, short[] idDelta, ushort[] idRangeOffset, long glyphCount, ushort[] glyphIdArray)
61 | {
62 | this.Format = format;
63 | this.ReservedPad = reservedPad;
64 | this.Length = length;
65 | this.Language = language;
66 | this.SegCount = segCount;
67 | this.SearchRange = searchRange;
68 | this.EntrySelector = entrySelector;
69 | this.RangeShift = rangeShift;
70 | this.EndCount = endCount;
71 | this.StartCount = startCount;
72 | this.IdDelta = idDelta;
73 | this.IdRangeOffset = idRangeOffset;
74 | this.GlyphCount = glyphCount;
75 | this.GlyphIdArray = glyphIdArray;
76 | }
77 |
78 | public ushort Format { get; }
79 | public ushort ReservedPad { get; }
80 | public ushort Length { get; }
81 | public ushort Language { get; }
82 | public int SegCount { get; }
83 | public ushort SearchRange { get; }
84 | public ushort EntrySelector { get; }
85 | public ushort RangeShift { get; }
86 | public ushort[] EndCount { get; }
87 | public ushort[] StartCount { get; }
88 | public short[] IdDelta { get; }
89 | public ushort[] IdRangeOffset { get; }
90 | public long GlyphCount { get; }
91 | public ushort[] GlyphIdArray { get; }
92 |
93 | public uint GetGlyphIndex(char c)
94 | {
95 | var charCode = (ushort) c;
96 |
97 | // Find the first segment that fits the charcode
98 | for (var i = 0; i < this.SegCount; i++)
99 | {
100 | var start = this.StartCount[i];
101 | var end = this.EndCount[i];
102 |
103 | if (start <= charCode && end >= charCode)
104 | {
105 | var delta = this.IdDelta[i];
106 | var rangeOffset = this.IdRangeOffset[i];
107 |
108 | if (rangeOffset == 0)
109 | {
110 | return (ushort) ((delta + c) % 65536);
111 | }
112 |
113 | // Index depends on the position of rangeOffset in the IdRangeOffset array
114 | var index = rangeOffset + (c - start) + i;
115 | var glyphId = this.GlyphIdArray[index];
116 |
117 | if (glyphId != 0)
118 | {
119 | return (ushort) ((glyphId + c) % 65536);
120 | }
121 | }
122 | }
123 |
124 | return 0;
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Cmap/SegmentedCoverageTable.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using RoyT.TrueType.IO;
3 |
4 | namespace RoyT.TrueType.Tables.Cmap
5 | {
6 | public sealed class SegmentedCoverageTable : ICmapSubtable
7 | {
8 | public static SegmentedCoverageTable FromReader(FontReader reader)
9 | {
10 | var format = reader.ReadUInt16BigEndian();
11 | var reserved = reader.ReadUInt16BigEndian();
12 | var length = reader.ReadUInt32BigEndian();
13 | var language = reader.ReadUInt32BigEndian();
14 | var numGroups = reader.ReadUInt32BigEndian();
15 |
16 | var groups = new SequentialMapGroup[numGroups];
17 |
18 | for (var i = 0; i < groups.Length; i++)
19 | {
20 | groups[i] = SequentialMapGroup.FromReader(reader);
21 | }
22 |
23 | return new SegmentedCoverageTable(format, reserved, length, language, numGroups, groups);
24 | }
25 |
26 | private SegmentedCoverageTable(ushort format, ushort reserved, uint length, uint language, uint numGroups, IReadOnlyList groups)
27 | {
28 | this.Format = format;
29 | this.Reserved = reserved;
30 | this.Length = length;
31 | this.Language = language;
32 | this.NumGroups = numGroups;
33 | this.Groups = groups;
34 | }
35 |
36 | public ushort Format { get; }
37 | public ushort Reserved { get; }
38 | public uint Length { get; }
39 | public uint Language { get; }
40 | public uint NumGroups { get; }
41 | public IReadOnlyList Groups { get; }
42 |
43 |
44 | public uint GetGlyphIndex(char c)
45 | {
46 | var charCode = (uint) c;
47 |
48 | foreach (var group in this.Groups)
49 | {
50 | if (group.StartCharCode <= charCode && charCode <= group.EndCharCode)
51 | {
52 | return charCode - group.StartCharCode + group.StartGlyphId;
53 | }
54 | }
55 |
56 | return 0;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Cmap/SequentialMapGroup.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 |
3 | namespace RoyT.TrueType.Tables.Cmap
4 | {
5 | public sealed class SequentialMapGroup
6 | {
7 | public static SequentialMapGroup FromReader(FontReader reader)
8 | {
9 | var startCharCode = reader.ReadUInt32BigEndian();
10 | var endCharCode = reader.ReadUInt32BigEndian();
11 | var startGlyphId = reader.ReadUInt32BigEndian();
12 |
13 | return new SequentialMapGroup(startCharCode, endCharCode, startGlyphId);
14 | }
15 |
16 | public SequentialMapGroup(uint startCharCode, uint endCharCode, uint startGlyphId)
17 | {
18 | this.StartCharCode = startCharCode;
19 | this.EndCharCode = endCharCode;
20 | this.StartGlyphId = startGlyphId;
21 | }
22 |
23 | public uint StartCharCode { get; }
24 | public uint EndCharCode { get; }
25 | public uint StartGlyphId { get; }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Cmap/TrimmedTableMappingTable.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 |
3 | namespace RoyT.TrueType.Tables.Cmap
4 | {
5 | public sealed class TrimmedTableMappingTable : ICmapSubtable
6 | {
7 | public static TrimmedTableMappingTable FromReader(FontReader reader)
8 | {
9 | var format = reader.ReadUInt16BigEndian();
10 | var length = reader.ReadUInt16BigEndian();
11 | var language = reader.ReadUInt16BigEndian();
12 | var firstCode = reader.ReadUInt16BigEndian();
13 | var entryCount = reader.ReadUInt16BigEndian();
14 |
15 | var glyphIdArray = new ushort[entryCount];
16 | for (var i = 0; i < glyphIdArray.Length; i++)
17 | {
18 | glyphIdArray[i] = reader.ReadUInt16BigEndian();
19 | }
20 |
21 | return new TrimmedTableMappingTable(format, length, language, firstCode, entryCount, glyphIdArray);
22 | }
23 |
24 | public TrimmedTableMappingTable(ushort format, ushort length, ushort language, ushort firstCode, ushort entryCount, ushort[] glyphIdArray)
25 | {
26 | this.Format = format;
27 | this.Length = length;
28 | this.Language = language;
29 | this.FirstCode = firstCode;
30 | this.EntryCount = entryCount;
31 | this.GlyphIdArray = glyphIdArray;
32 | }
33 |
34 |
35 | public ushort Format { get; }
36 | public ushort Length { get; }
37 | public ushort Language { get; }
38 | public ushort FirstCode { get; }
39 | public ushort EntryCount { get; }
40 | public ushort[] GlyphIdArray { get; }
41 |
42 |
43 | public uint GetGlyphIndex(char c)
44 | {
45 | var charCode = (ushort) c;
46 |
47 | if (this.FirstCode <= charCode && charCode < this.FirstCode + this.EntryCount)
48 | {
49 | return this.GlyphIdArray[charCode - this.FirstCode];
50 | }
51 |
52 | return 0;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/HeadTable.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 |
3 | namespace RoyT.TrueType.Tables
4 | {
5 | ///
6 | /// Font Header Table
7 | ///
8 | public record HeadTable
9 | {
10 | public static HeadTable FromReader(FontReader reader, TableRecordEntry entry)
11 | {
12 | reader.Seek(entry.Offset);
13 |
14 | var major = reader.ReadUInt16BigEndian();
15 | var minor = reader.ReadUInt16BigEndian();
16 |
17 | return new()
18 | {
19 | MajorVersion = major,
20 | MinorVersion = minor,
21 | FontRevision = reader.ReadFixedBigEndian(),
22 | ChecksumAdjustment = reader.ReadUInt32BigEndian(),
23 | MagicNumber = reader.ReadUInt32BigEndian(),
24 | Flags = reader.ReadUInt16BigEndian(),
25 | UnitsPerEm = reader.ReadUInt16BigEndian(),
26 | Created = reader.ReadInt64BigEndian(),
27 | Modified = reader.ReadInt64BigEndian(),
28 | XMin = reader.ReadInt16BigEndian(),
29 | XMax = reader.ReadInt16BigEndian(),
30 | YMin = reader.ReadInt16BigEndian(),
31 | YMax = reader.ReadInt16BigEndian(),
32 | MacStyle = reader.ReadUInt16BigEndian(),
33 | LowestRecPPEM = reader.ReadUInt16BigEndian(),
34 | FontDirectionHint = reader.ReadInt16BigEndian(),
35 | IndexToLocFormat = reader.ReadInt16BigEndian(),
36 | GlyphDataFormat = reader.ReadInt16BigEndian(),
37 | };
38 | }
39 |
40 | // Major version number of the font header table — set to 1.
41 | public ushort MajorVersion { get; init; }
42 |
43 | // Minor version number of the font header table — set to 0.
44 | public ushort MinorVersion { get; init; }
45 |
46 | // Set by font manufacturer.
47 | public float FontRevision { get; init; }
48 |
49 | public uint ChecksumAdjustment { get; init; }
50 |
51 | ///
52 | /// Set to 0x5F0F3CF5.
53 | ///
54 | public uint MagicNumber { get; init; }
55 |
56 | public ushort Flags { get; init; }
57 |
58 | ///
59 | /// Set to a value from 16 to 16384.Any value in this range is valid. In fonts that have TrueType outlines, a power of 2 is recommended as this allows performance optimizations in some rasterizers.
60 | ///
61 | public ushort UnitsPerEm { get; init; }
62 |
63 | ///
64 | /// Number of seconds since 12:00 midnight that started January 1st 1904 in GMT / UTC time zone.
65 | ///
66 | public long Created { get; init; }
67 |
68 | ///
69 | /// Number of seconds since 12:00 midnight that started January 1st 1904 in GMT / UTC time zone.
70 | ///
71 | public long Modified { get; init; }
72 |
73 | ///
74 | /// Minimum x coordinate across all glyph bounding boxes.
75 | ///
76 | public short XMin { get; init; }
77 |
78 | ///
79 | /// Minimum y coordinate across all glyph bounding boxes.
80 | ///
81 | public short YMin { get; init; }
82 |
83 | ///
84 | /// Maximum x coordinate across all glyph bounding boxes.
85 | ///
86 | public short XMax { get; init; }
87 |
88 | ///
89 | /// Maximum y coordinate across all glyph bounding boxes.
90 | ///
91 | public short YMax { get; init; }
92 |
93 | public ushort MacStyle { get; init; }
94 |
95 | ///
96 | /// Smallest readable size in pixels.
97 | ///
98 | public ushort LowestRecPPEM { get; init; }
99 |
100 | public short FontDirectionHint { get; init; }
101 | public short IndexToLocFormat { get; init; }
102 | public short GlyphDataFormat { get; init; }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/HheaTable.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 |
3 | namespace RoyT.TrueType.Tables
4 | {
5 | ///
6 | /// The Horizontal Header Table contains information for horizontal layout
7 | ///
8 | public sealed class HheaTable
9 | {
10 | public static HheaTable FromReader(FontReader reader, TableRecordEntry entry)
11 | {
12 | reader.Seek(entry.Offset);
13 |
14 | var majorVersion = reader.ReadUInt16BigEndian();
15 | var minorVersion = reader.ReadUInt16BigEndian();
16 | var ascender = reader.ReadInt16BigEndian();
17 | var descender = reader.ReadInt16BigEndian();
18 | var lineGap = reader.ReadInt16BigEndian();
19 | var advanceWidthMax = reader.ReadUInt16BigEndian();
20 | var minLeftSideBearing = reader.ReadInt16BigEndian();
21 | var minRightSideBearing = reader.ReadInt16BigEndian();
22 | var xMaxExtent = reader.ReadInt16BigEndian();
23 | var caretSlopeRise = reader.ReadInt16BigEndian();
24 | var caretSlopeRun = reader.ReadInt16BigEndian();
25 | var caretOffset = reader.ReadInt16BigEndian();
26 |
27 | // Seek over 4 reserved int16 fields
28 | reader.Seek(8, System.IO.SeekOrigin.Current);
29 |
30 | var metricDataFormat = reader.ReadInt16BigEndian();
31 | var numberOfHMetrics = reader.ReadUInt16BigEndian();
32 |
33 | return new HheaTable()
34 | {
35 | MajorVersion = majorVersion,
36 | MinorVersion = minorVersion,
37 | Ascender = ascender,
38 | Descender = descender,
39 | LineGap = lineGap,
40 | AdvanceWidthMax = advanceWidthMax,
41 | MinLeftSideBearing = minLeftSideBearing,
42 | MinRightSideBearing = minRightSideBearing,
43 | XMaxExtent = xMaxExtent,
44 | CaretSlopeRise = caretSlopeRise,
45 | CaretSlopeRun = caretSlopeRun,
46 | CaretOffset = caretOffset,
47 | MetricDataFormat = metricDataFormat,
48 | NumberOfHMetrics = numberOfHMetrics,
49 | };
50 | }
51 |
52 | public static HheaTable Empty => new HheaTable();
53 |
54 | ///
55 | /// Major version number of the horizontal header table
56 | ///
57 | public ushort MajorVersion { get; init; }
58 |
59 | ///
60 | /// Minor version number of the horizontal header table
61 | ///
62 | public ushort MinorVersion { get; init; }
63 |
64 | ///
65 | /// Typographic ascent—see note below.
66 | ///
67 | public short Ascender { get; init; }
68 |
69 | ///
70 | /// Typographic descent—see note below.
71 | ///
72 | public short Descender { get; init; }
73 |
74 | ///
75 | /// Typographic line gap.
76 | ///
77 | public short LineGap { get; init; }
78 |
79 | ///
80 | /// Maximum advance width value in 'hmtx' table.
81 | ///
82 | public ushort AdvanceWidthMax { get; init; }
83 |
84 | ///
85 | /// Minimum left sidebearing value in 'hmtx' table for glyphs with contours (empty glyphs should be ignored).
86 | ///
87 | public short MinLeftSideBearing { get; init; }
88 |
89 | ///
90 | /// Minimum right sidebearing value; calculated as min(aw - (lsb + xMax - xMin)) for glyphs with contours(empty glyphs should be ignored).
91 | ///
92 | public short MinRightSideBearing { get; init; }
93 |
94 | ///
95 | /// Max(lsb + (xMax - xMin)).
96 | ///
97 | public short XMaxExtent { get; init; }
98 |
99 | ///
100 | /// Used to calculate the slope of the cursor(rise/run); 1 for vertical.
101 | ///
102 | public short CaretSlopeRise { get; init; }
103 |
104 | ///
105 | /// 0 for vertical.
106 | ///
107 | public short CaretSlopeRun { get; init; }
108 |
109 | ///
110 | /// The amount by which a slanted highlight on a glyph needs to be shifted to produce the best appearance.Set to 0 for non-slanted fonts
111 | ///
112 | public short CaretOffset { get; init; }
113 |
114 | ///
115 | /// 0 for current format.
116 | ///
117 | public short MetricDataFormat { get; init; }
118 |
119 | ///
120 | /// Number of hMetric entries in 'hmtx' table
121 | ///
122 | public ushort NumberOfHMetrics { get; init; }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Hmtx/HmtxTable.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace RoyT.TrueType.Tables.Hmtx
6 | {
7 | ///
8 | /// Horizontal Metrics Table
9 | ///
10 | public sealed class HmtxTable
11 | {
12 | public static HmtxTable FromReader(FontReader reader, TableRecordEntry entry, int metricsCount, int glyphCount)
13 | {
14 | reader.Seek(entry.Offset);
15 |
16 | int leftSideBearingCount = glyphCount - metricsCount;
17 | var expected = 4 * metricsCount + 2 * leftSideBearingCount;
18 | if (entry.Length != expected)
19 | {
20 | throw new Exception("unexpected hmtx table length");
21 | }
22 |
23 | List hmetrics = new(metricsCount);
24 | for (int i = 0; i < metricsCount; i++)
25 | {
26 | hmetrics.Add(LongHorMetric.FromReader(reader));
27 | }
28 |
29 | List leftSideBearings = new(leftSideBearingCount);
30 | for (int i = 0; i < leftSideBearingCount; i++)
31 | {
32 | leftSideBearings.Add(reader.ReadInt16BigEndian());
33 | }
34 |
35 | return new()
36 | {
37 | HMetrics = hmetrics,
38 | LeftSideBearings = leftSideBearings,
39 | };
40 | }
41 |
42 | public static HmtxTable Empty => new()
43 | {
44 | HMetrics = new List()
45 | };
46 |
47 | ///
48 | /// Paired advance width and left side bearing values for each glyph. Records are indexed by glyph ID.
49 | ///
50 | public IReadOnlyList HMetrics { get; init; }
51 |
52 | ///
53 | /// Optional array for glyphs not in HMetrics. Their Lsb is equal to the Lsb
54 | /// of the last entry in HMetrics
55 | ///
56 | public IReadOnlyList LeftSideBearings { get; init; }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Hmtx/LongHorMetric.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 |
3 | namespace RoyT.TrueType.Tables.Hmtx
4 | {
5 | ///
6 | /// Horizontal layout metrics for a glyph
7 | ///
8 | public class LongHorMetric
9 | {
10 | public static LongHorMetric FromReader(FontReader reader)
11 | {
12 | return new()
13 | {
14 | AdvanceHeight = reader.ReadUInt16BigEndian(),
15 | Lsb = reader.ReadInt16BigEndian(),
16 | };
17 | }
18 |
19 | ///
20 | /// Advance width, in font design units.
21 | ///
22 | public ushort AdvanceHeight { get; init; }
23 |
24 | ///
25 | /// Glyph left side bearing, in font design units.
26 | ///
27 | public short Lsb { get; init; }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Kern/Direction.cs:
--------------------------------------------------------------------------------
1 | namespace RoyT.TrueType.Tables.Kern
2 | {
3 | public enum Direction
4 | {
5 | Horizontal,
6 | Vertical
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Kern/Format0.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using RoyT.TrueType.IO;
3 |
4 | namespace RoyT.TrueType.Tables.Kern
5 | {
6 | public sealed class Format0
7 | {
8 | public static Format0 FromReader(FontReader reader)
9 | {
10 | var pairs = reader.ReadUInt16BigEndian();
11 | var searchRange = reader.ReadUInt16BigEndian();
12 | var entrySelector = reader.ReadUInt16BigEndian();
13 | var rangeShift = reader.ReadUInt16BigEndian();
14 |
15 | var map = new Dictionary();
16 | for (var i = 0; i < pairs; i++)
17 | {
18 | var left = reader.ReadUInt16BigEndian();
19 | var right = reader.ReadUInt16BigEndian();
20 | var value = reader.ReadInt16BigEndian();
21 |
22 | map.Add(new KerningPair(left, right), value);
23 | }
24 |
25 | return new Format0(pairs, searchRange, entrySelector, rangeShift, map);
26 | }
27 |
28 | private Format0(ushort pairs, ushort searchRange, ushort entrySelector, ushort rangeShift, Dictionary map)
29 | {
30 | this.Pairs = pairs;
31 | this.SearchRange = searchRange;
32 | this.EntrySelector = entrySelector;
33 | this.RangeShift = rangeShift;
34 | this.Map = map;
35 | }
36 |
37 | public ushort Pairs { get; }
38 | public ushort SearchRange { get; }
39 | public ushort EntrySelector { get; }
40 | public ushort RangeShift { get; }
41 | public IReadOnlyDictionary Map { get; }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Kern/KernSubTable.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 |
3 | namespace RoyT.TrueType.Tables.Kern
4 | {
5 | public sealed class KernSubtable
6 | {
7 | public static KernSubtable FromReader(FontReader reader)
8 | {
9 | var version = reader.ReadUInt16BigEndian();
10 | var length = reader.ReadUInt16BigEndian();
11 | var format = reader.ReadByte();
12 | var coverage = reader.ReadBitsBigEndian(1);
13 |
14 |
15 | var direction = coverage.Get(0) ? Direction.Horizontal : Direction.Vertical;
16 | var values = coverage.Get(1) ? Values.Minimum : Values.Kerning;
17 | var isCrossStream = coverage.Get(2);
18 | var isOverride = coverage.Get(3);
19 |
20 | // The only format that is properly interpreted by Windows
21 | Format0 format0 = null;
22 | if (format == 0)
23 | {
24 | format0 = Format0.FromReader(reader);
25 | }
26 |
27 | return new KernSubtable(version, length, format, direction, values, isCrossStream, isOverride, format0);
28 | }
29 |
30 | private KernSubtable(ushort version, ushort length, byte format, Direction direction, Values values, bool isCrossStream, bool isOverride, Format0 format0)
31 | {
32 | this.Version = version;
33 | this.Length = length;
34 | this.Format = format;
35 | this.Direction = direction;
36 | this.Values = values;
37 | this.IsCrossStream = isCrossStream;
38 | this.IsOverride = isOverride;
39 | this.Format0 = format0;
40 | }
41 |
42 | public ushort Version { get; }
43 | public ushort Length { get; }
44 | public byte Format { get; }
45 | public Direction Direction { get; }
46 | public Values Values { get; }
47 | public bool IsCrossStream { get; }
48 | public bool IsOverride { get; }
49 | public Format0 Format0 { get; }
50 |
51 | public short GetKerning(KerningPair pair)
52 | {
53 | if (this.Format0 != null)
54 | {
55 | if (this.Format0.Map.TryGetValue(pair, out var value))
56 | {
57 | return value;
58 | }
59 | }
60 |
61 | return 0;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Kern/KernTable.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using RoyT.TrueType.IO;
3 |
4 | namespace RoyT.TrueType.Tables.Kern
5 | {
6 | ///
7 | /// Contains adjustment to horizontal/vertical positions between glyphs
8 | ///
9 | public sealed class KernTable
10 | {
11 | public static KernTable FromReader(FontReader reader)
12 | {
13 | var version = reader.ReadUInt16BigEndian();
14 | var subtableCount = reader.ReadUInt16BigEndian();
15 |
16 | var subtables = new List();
17 | for (var i = 0; i < subtableCount; i++)
18 | {
19 | var subTable = KernSubtable.FromReader(reader);
20 | subtables.Add(subTable);
21 | }
22 |
23 | return new KernTable(version, subtableCount, subtables);
24 | }
25 |
26 | public static KernTable Empty => new KernTable(0, 0, new List());
27 |
28 | private KernTable(ushort version, ushort subtableCount, IReadOnlyList subtables)
29 | {
30 | this.Version = version;
31 | this.SubtableCount = subtableCount;
32 | this.Subtables = subtables;
33 | }
34 |
35 | public ushort Version { get; }
36 | public ushort SubtableCount { get; }
37 | public IReadOnlyList Subtables { get; }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Kern/KerningPair.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace RoyT.TrueType.Tables.Kern
4 | {
5 | public struct KerningPair : IEquatable
6 | {
7 | public ushort LeftGlyphCode { get; }
8 | public ushort RightGlyphCode { get; }
9 |
10 | public KerningPair(ushort leftGlyphCode, ushort rightGlyphCode)
11 | {
12 | this.LeftGlyphCode = leftGlyphCode;
13 | this.RightGlyphCode = rightGlyphCode;
14 | }
15 |
16 | public bool Equals(KerningPair other)
17 | {
18 | return this.LeftGlyphCode == other.LeftGlyphCode && this.RightGlyphCode == other.RightGlyphCode;
19 | }
20 |
21 | public override bool Equals(object obj)
22 | {
23 | if (obj is KerningPair pair)
24 | {
25 | return Equals(pair);
26 | }
27 |
28 | return false;
29 | }
30 |
31 | public override int GetHashCode()
32 | {
33 | unchecked
34 | {
35 | return (this.LeftGlyphCode.GetHashCode() * 397) ^ this.RightGlyphCode.GetHashCode();
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Kern/Values.cs:
--------------------------------------------------------------------------------
1 | namespace RoyT.TrueType.Tables.Kern
2 | {
3 | public enum Values
4 | {
5 | Minimum,
6 | Kerning
7 | }
8 | }
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/MaxpTable.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 |
3 | namespace RoyT.TrueType.Tables
4 | {
5 | ///
6 | /// Maximum Profile Table
7 | ///
8 | public sealed class MaxpTable
9 | {
10 | public static MaxpTable FromReader(FontReader reader, TableRecordEntry entry)
11 | {
12 | reader.Seek(entry.Offset);
13 |
14 | var version = reader.ReadFixedBigEndian();
15 | ushort numGlyphcs = reader.ReadUInt16BigEndian();
16 |
17 | if (version == 0.5f)
18 | {
19 | return new()
20 | {
21 | Version = version,
22 | NumGlyphs = numGlyphcs,
23 | };
24 | }
25 |
26 | return new()
27 | {
28 | Version = version,
29 | NumGlyphs = numGlyphcs,
30 | MaxPoints = reader.ReadUInt16BigEndian(),
31 | MaxContours = reader.ReadUInt16BigEndian(),
32 | MaxCompositePoints = reader.ReadUInt16BigEndian(),
33 | MaxCompositeContours = reader.ReadUInt16BigEndian(),
34 | MaxZones = reader.ReadUInt16BigEndian(),
35 | MaxTwilightPoints = reader.ReadUInt16BigEndian(),
36 | MaxStorage = reader.ReadUInt16BigEndian(),
37 | MaxFunctionDefs = reader.ReadUInt16BigEndian(),
38 | MaxInstructionDefs = reader.ReadUInt16BigEndian(),
39 | MaxStackElements = reader.ReadUInt16BigEndian(),
40 | MaxSizeOfInstructions = reader.ReadUInt16BigEndian(),
41 | MaxComponentElements = reader.ReadUInt16BigEndian(),
42 | MaxComponentDepth = reader.ReadUInt16BigEndian(),
43 | };
44 | }
45 |
46 | public float Version { get; init; }
47 |
48 | ///
49 | /// The number of glyphs in the font.
50 | ///
51 | public ushort NumGlyphs { get; init; }
52 |
53 | ///
54 | /// Maximum points in a non-composite glyph.
55 | ///
56 | public ushort MaxPoints { get; init; }
57 |
58 | ///
59 | /// Maximum contours in a non-composite glyph.
60 | ///
61 | public ushort MaxContours { get; init; }
62 |
63 | ///
64 | /// Maximum points in a composite glyph.
65 | ///
66 | public ushort MaxCompositePoints { get; init; }
67 |
68 | ///
69 | /// Maximum contours in a composite glyph.
70 | ///
71 | public ushort MaxCompositeContours { get; init; }
72 |
73 | ///
74 | /// 1 if instructions do not use the twilight zone (Z0), or 2 if instructions do use Z0; should be set to 2 in most cases.
75 | ///
76 | public ushort MaxZones { get; init; }
77 |
78 | ///
79 | /// Maximum points used in Z0.
80 | ///
81 | public ushort MaxTwilightPoints { get; init; }
82 |
83 | ///
84 | /// Number of Storage Area locations.
85 | ///
86 | public ushort MaxStorage { get; init; }
87 |
88 | ///
89 | /// Number of FDEFs, equal to the highest function number + 1.
90 | ///
91 | public ushort MaxFunctionDefs { get; init; }
92 |
93 | ///
94 | /// Number of IDEFs.
95 | ///
96 | public ushort MaxInstructionDefs { get; init; }
97 |
98 | ///
99 | /// Maximum stack depth across Font Program ('fpgm' table), CVT Program('prep' table) and all glyph instructions(in the 'glyf' table).
100 | ///
101 | public ushort MaxStackElements { get; init; }
102 |
103 | ///
104 | /// Maximum byte count for glyph instructions.
105 | ///
106 | public ushort MaxSizeOfInstructions { get; init; }
107 |
108 | ///
109 | /// Maximum number of components referenced at “top level” for any composite glyph.
110 | ///
111 | public ushort MaxComponentElements { get; init; }
112 |
113 | ///
114 | /// Maximum levels of recursion; 1 for simple components.
115 | ///
116 | public ushort MaxComponentDepth { get; init; }
117 | }
118 | }
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Name/LanguageIdConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 |
5 | namespace RoyT.TrueType.Tables.Name
6 | {
7 | public static class LanguageIdConverter
8 | {
9 | private static readonly Dictionary LCIDMap = ReadMapFromStringResource(Properties.Resources.LCIDMap);
10 | private static readonly Dictionary MacLanguageCodeMap = ReadMapFromStringResource(Properties.Resources.MacLanguageCodeMap);
11 |
12 |
13 | public static CultureInfo ToCulture(Platform platform, ushort languageId)
14 | {
15 | if (platform == Platform.Windows)
16 | {
17 | if (LCIDMap.TryGetValue(languageId, out string culture))
18 | {
19 | return new CultureInfo(culture);
20 | }
21 | }
22 |
23 | if (platform == Platform.Macintosh)
24 | {
25 | if (MacLanguageCodeMap.TryGetValue(languageId, out string culture))
26 | {
27 | return new CultureInfo(culture);
28 | }
29 | }
30 |
31 | return CultureInfo.InvariantCulture;
32 | }
33 |
34 | private static Dictionary ReadMapFromStringResource(string resouce)
35 | {
36 | var map = new Dictionary();
37 |
38 | var lines = resouce.Split(new []{ "\r\n", "\n"}, StringSplitOptions.RemoveEmptyEntries);
39 | foreach (var line in lines)
40 | {
41 | var parts = line.Split(' ');
42 | int code;
43 |
44 | if (parts[0].StartsWith("0x", StringComparison.OrdinalIgnoreCase))
45 | {
46 | code = Convert.ToInt32(parts[0].Substring(2), 16);
47 | }
48 | else
49 | {
50 | code = int.Parse(parts[0]);
51 | }
52 |
53 | map.Add(code, parts[1]);
54 | }
55 |
56 | return map;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Name/NameId.cs:
--------------------------------------------------------------------------------
1 | namespace RoyT.TrueType.Tables.Name
2 | {
3 | public enum NameId : ushort
4 | {
5 | CopyrightNotice = 0,
6 | FontFamilyName = 1,
7 | FontSubfamilyName = 2,
8 | UniqueFontIdentifier = 3,
9 | FullFontName = 4,
10 | VersionString = 5,
11 | PostScriptName = 6,
12 | Trademark = 7,
13 | ManufacturerName = 8,
14 | DesignerName = 9,
15 | Description = 10,
16 | URLVendor = 11,
17 | URLDesigner = 12,
18 | LicenseDescription = 13,
19 | LicenseInfoURL = 14,
20 | Reserved = 15,
21 | TypographicFamilyName = 16,
22 | TypographicSufamilyName = 17,
23 | CompatibleFull = 18,
24 | SampleText = 19,
25 | PostScriptCIDFindFontName = 20,
26 | WWSFamilyName = 21,
27 | WWSubfamilyName = 22,
28 | LightBackgroundPalette = 23,
29 | DarkBackgroundPalette = 24,
30 | VariationsPostScriptNamePrefix = 25
31 | };
32 | }
33 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Name/NameRecord.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using RoyT.TrueType.IO;
4 |
5 | namespace RoyT.TrueType.Tables.Name
6 | {
7 | public class NameRecord
8 | {
9 | public static NameRecord FromReader(FontReader reader, long storageStart)
10 | {
11 | // Currently always 0
12 | var platformId = (Platform)reader.ReadUInt16BigEndian();
13 |
14 | var encodingId = reader.ReadUInt16BigEndian();
15 |
16 | if (platformId == Platform.Windows)
17 | {
18 | if (encodingId is not (10 or 1 or 0)) // Should be 0 for symbol fonts, 1 for unicode fonts
19 | {
20 | throw new Exception("Unexpected encoding in name record");
21 | }
22 | }
23 |
24 | if (platformId == Platform.Macintosh)
25 | {
26 | if (encodingId != 0)
27 | {
28 | throw new Exception("Unexpected encoding in name record");
29 | }
30 | }
31 |
32 | var languageId = reader.ReadUInt16BigEndian();
33 | var language = LanguageIdConverter.ToCulture(platformId, languageId);
34 | var nameId = (NameId)reader.ReadUInt16BigEndian();
35 | var length = reader.ReadUInt16BigEndian();
36 | var offset = reader.ReadUInt16BigEndian();
37 |
38 | var end = reader.Position;
39 |
40 | reader.Seek(storageStart + offset);
41 | var text = ReadString(reader, platformId, encodingId, length);
42 | reader.Seek(end);
43 |
44 |
45 |
46 | return new NameRecord(platformId, encodingId, language, nameId, length, offset, text);
47 | }
48 |
49 | private static string ReadString(FontReader reader, Platform platform, ushort encodingId, ushort length)
50 | {
51 |
52 | if (platform == Platform.Windows || platform == Platform.Unicode)
53 | {
54 | // All unicode fonts on windows encode their name in the naming table using UTF16 Big Endian unicode.
55 | return reader.ReadBigEndianUnicode(length);
56 | }
57 |
58 | // platform is Mac
59 |
60 | // Mac uses the MacRoman character set for the names in a font. Unfortunately they
61 | // differ slightly from the ANSII character set, but for characters 32-126 they are identical
62 | // so we use ANSII encoding as a best effort to read the names
63 | return reader.ReadAscii(length);
64 | }
65 |
66 | public NameRecord(
67 | Platform platformId,
68 | ushort encodingId,
69 | CultureInfo language,
70 | NameId nameId,
71 | ushort length,
72 | ushort offset,
73 | string text)
74 | {
75 | this.PlatformId = platformId;
76 | this.EncodingId = encodingId;
77 | this.Language = language;
78 | this.NameId = nameId;
79 | this.Length = length;
80 | this.Offset = offset;
81 | this.Text = text;
82 | }
83 |
84 | public Platform PlatformId { get; }
85 | public ushort EncodingId { get; }
86 | public CultureInfo Language { get; }
87 | public NameId NameId { get; }
88 | public ushort Length { get; }
89 | public ushort Offset { get; }
90 | public string Text { get; }
91 |
92 | public override string ToString() => $"{this.NameId}: {this.Text}";
93 | }
94 | }
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Name/NameTable.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 |
3 | namespace RoyT.TrueType.Tables.Name
4 | {
5 | public sealed class NameTable
6 | {
7 | ///
8 | /// Contains the (translated) name of this font, copyright notices, etc...
9 | ///
10 | public static NameTable FromReader(FontReader reader)
11 | {
12 | var start = reader.Position;
13 |
14 | var format = reader.ReadUInt16BigEndian();
15 | var count = reader.ReadUInt16BigEndian();
16 | var stringOffset = reader.ReadUInt16BigEndian();
17 |
18 | var nameRecords = new NameRecord[count];
19 | for (var i = 0; i < nameRecords.Length; i++)
20 | nameRecords[i] = NameRecord.FromReader(reader, start + stringOffset);
21 |
22 | return new NameTable(format, count, stringOffset, nameRecords);
23 | }
24 |
25 | public NameTable(ushort format, ushort count, ushort stringOffset, NameRecord[] nameRecords)
26 | {
27 | this.Format = format;
28 | this.Count = count;
29 | this.StringOffset = stringOffset;
30 | this.NameRecords = nameRecords;
31 | }
32 |
33 | public ushort Format { get; }
34 | public ushort Count { get; }
35 | public ushort StringOffset { get; }
36 | public NameRecord[] NameRecords { get; }
37 | }
38 | }
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/OffsetTable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using RoyT.TrueType.IO;
3 |
4 | namespace RoyT.TrueType.Tables
5 | {
6 | ///
7 | /// Contains the offset in the file to the major tables (like CMAP, NAME, KERN, etc...) in this font
8 | ///
9 | public sealed class OffsetTable
10 | {
11 | private static uint WindowsAdobe = 0x00010000u;
12 | private static uint OTTO = 0x4F54544Fu;
13 |
14 | // The Apple specification for TrueType fonts allows for 'true' and 'typ1' for sfnt version. These version tags should not be used for OpenType fonts.
15 | private static uint Mac = 0x74727565u;
16 | private static uint Typ1 = 0x74797031u;
17 |
18 | public static OffsetTable FromReader(FontReader reader)
19 | {
20 | var scalarType = reader.ReadUInt32BigEndian(); //reader.ReadFixedBigEndian(out short major, out short minor);
21 |
22 | if (scalarType == WindowsAdobe || scalarType == OTTO || scalarType == Typ1 || scalarType == Mac)
23 | {
24 | var tables = reader.ReadUInt16BigEndian();
25 | var searchRange = reader.ReadUInt16BigEndian();
26 | var entrySelector = reader.ReadUInt16BigEndian();
27 | var rangeShift = reader.ReadUInt16BigEndian();
28 |
29 | return new OffsetTable(scalarType, tables, searchRange, entrySelector, rangeShift);
30 | }
31 |
32 | throw new Exception($"Unexpected OffsetTable scalar type. Expected: {WindowsAdobe} or {OTTO}, actual: {scalarType}");
33 | }
34 |
35 | private OffsetTable(uint scalarType, ushort tables, ushort searchRange, ushort entrySelector, ushort rangeShift)
36 | {
37 | this.ScalarType = scalarType;
38 | this.Tables = tables;
39 | this.SearchRange = searchRange;
40 | this.EntrySelector = entrySelector;
41 | this.RangeShift = rangeShift;
42 | }
43 |
44 | public uint ScalarType { get; }
45 | public ushort Tables { get; }
46 | public ushort SearchRange { get; }
47 | public ushort EntrySelector { get; }
48 | public ushort RangeShift { get; }
49 |
50 | public override string ToString() => $"Tables: {this.Tables}";
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Os2Table.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 | using System.Collections.Generic;
3 |
4 | namespace RoyT.TrueType.Tables
5 | {
6 | ///
7 | /// OS/2 and Windows Metrics Table
8 | ///
9 | public record Os2Table
10 | {
11 | public static Os2Table FromReader(FontReader reader, TableRecordEntry tableEntry)
12 | {
13 | reader.Seek(tableEntry.Offset);
14 | Os2Table table = new()
15 | {
16 | Version = reader.ReadUInt16BigEndian(),
17 | XAvgCharWidth = reader.ReadInt16BigEndian(),
18 | UsWeightClass = reader.ReadUInt16BigEndian(),
19 | UsWidthClass = reader.ReadUInt16BigEndian(),
20 | FsType = reader.ReadUInt16BigEndian(),
21 | YSubscriptXSize = reader.ReadInt16BigEndian(),
22 | YSubscriptYSize = reader.ReadInt16BigEndian(),
23 | YSubscriptXOffset = reader.ReadInt16BigEndian(),
24 | YSubscriptYOffset = reader.ReadInt16BigEndian(),
25 | YSuperscriptXSize = reader.ReadInt16BigEndian(),
26 | YSuperscriptYSize = reader.ReadInt16BigEndian(),
27 | YSuperscriptXOffset = reader.ReadInt16BigEndian(),
28 | YSuperscriptYOffset = reader.ReadInt16BigEndian(),
29 | YStrikeoutSize = reader.ReadInt16BigEndian(),
30 | YStrikeoutPosition = reader.ReadInt16BigEndian(),
31 | SFamilyClass = reader.ReadInt16BigEndian(),
32 | Panose = reader.ReadBytes(10),
33 | UlUnicodeRange1 = reader.ReadUInt32BigEndian(),
34 | UlUnicodeRange2 = reader.ReadUInt32BigEndian(),
35 | UlUnicodeRange3 = reader.ReadUInt32BigEndian(),
36 | UlUnicodeRange4 = reader.ReadUInt32BigEndian(),
37 | AchVendID = reader.ReadAscii(4),
38 | FsSelection = reader.ReadUInt16BigEndian(),
39 | UsFirstCharIndex = reader.ReadUInt16BigEndian(),
40 | UsLastCharIndex = reader.ReadUInt16BigEndian(),
41 | };
42 |
43 | if (table.Version == 0 && tableEntry.Length == reader.Position - tableEntry.Offset)
44 | {
45 | return table;
46 | }
47 |
48 | // Some version 0 fonts on Windows include the following extra fields
49 | table = table with
50 | {
51 | STypoAscender = reader.ReadInt16BigEndian(),
52 | STypoDescender = reader.ReadInt16BigEndian(),
53 | STypoLineGap = reader.ReadInt16BigEndian(),
54 | UsWinAscent = reader.ReadUInt16BigEndian(),
55 | UsWinDescent = reader.ReadUInt16BigEndian(),
56 | };
57 |
58 | if (table.Version == 0)
59 | {
60 | return table;
61 | }
62 |
63 | table = table with
64 | {
65 | UlCodePageRange1 = reader.ReadUInt32BigEndian(),
66 | UlCodePageRange2 = reader.ReadUInt32BigEndian(),
67 | };
68 |
69 | if (table.Version == 1)
70 | {
71 | return table;
72 | }
73 |
74 | table = table with
75 | {
76 | SxHeight = reader.ReadInt16BigEndian(),
77 | SCapHeight = reader.ReadInt16BigEndian(),
78 | UsDefaultChar = reader.ReadUInt16BigEndian(),
79 | UsBreakChar = reader.ReadUInt16BigEndian(),
80 | UsMaxContext = reader.ReadUInt16BigEndian(),
81 | };
82 |
83 | if (table.Version < 5)
84 | {
85 | return table;
86 | }
87 |
88 | table = table with
89 | {
90 | UsLowerOpticalPointSize = reader.ReadUInt16BigEndian(),
91 | UsUpperOpticalPointSize = reader.ReadUInt16BigEndian(),
92 | };
93 |
94 | return table;
95 | }
96 |
97 | public ushort Version { get; init; }
98 | public short XAvgCharWidth { get; init; }
99 | public ushort UsWeightClass { get; init; }
100 | public ushort UsWidthClass { get; init; }
101 | public ushort FsType { get; init; }
102 | public short YSubscriptXSize { get; init; }
103 | public short YSubscriptYSize { get; init; }
104 | public short YSubscriptXOffset { get; init; }
105 | public short YSubscriptYOffset { get; init; }
106 | public short YSuperscriptXSize { get; init; }
107 | public short YSuperscriptYSize { get; init; }
108 | public short YSuperscriptXOffset { get; init; }
109 | public short YSuperscriptYOffset { get; init; }
110 | public short YStrikeoutSize { get; init; }
111 | public short YStrikeoutPosition { get; init; }
112 | public short SFamilyClass { get; init; }
113 | public IReadOnlyList Panose { get; init; }
114 | public uint UlUnicodeRange1 { get; init; }
115 | public uint UlUnicodeRange2 { get; init; }
116 | public uint UlUnicodeRange3 { get; init; }
117 | public uint UlUnicodeRange4 { get; init; }
118 | public string AchVendID { get; init; }
119 | public ushort FsSelection { get; init; }
120 | public ushort UsFirstCharIndex { get; init; }
121 | public ushort UsLastCharIndex { get; init; }
122 | public short STypoAscender { get; init; }
123 | public short STypoDescender { get; init; }
124 | public short STypoLineGap { get; init; }
125 | public ushort UsWinAscent { get; init; }
126 | public ushort UsWinDescent { get; init; }
127 | public uint UlCodePageRange1 { get; init; }
128 | public uint UlCodePageRange2 { get; init; }
129 | public short SxHeight { get; init; }
130 | public short SCapHeight { get; init; }
131 | public ushort UsDefaultChar { get; init; }
132 | public ushort UsBreakChar { get; init; }
133 | public ushort UsMaxContext { get; init; }
134 | public ushort UsLowerOpticalPointSize { get; init; }
135 | public ushort UsUpperOpticalPointSize { get; init; }
136 | }
137 | }
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/TableRecordEntry.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 |
3 | namespace RoyT.TrueType.Tables
4 | {
5 | public sealed class TableRecordEntry
6 | {
7 | public static TableRecordEntry FromReader(FontReader reader)
8 | {
9 | var tag = reader.ReadAscii(4);
10 | var checksum = reader.ReadUInt32BigEndian();
11 | var offset = reader.ReadUInt32BigEndian();
12 | var length = reader.ReadUInt32BigEndian();
13 |
14 | return new TableRecordEntry(tag, checksum, offset, length);
15 | }
16 |
17 |
18 | private TableRecordEntry(string tag, uint checksum, uint offset, uint length)
19 | {
20 | this.Tag = tag;
21 | this.Checksum = checksum;
22 | this.Offset = offset;
23 | this.Length = length;
24 | }
25 |
26 | public string Tag { get; }
27 | public uint Checksum { get; }
28 | public uint Offset { get; }
29 | public uint Length { get; }
30 |
31 |
32 | public override string ToString() => this.Tag;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/TrueTypeTableNames.cs:
--------------------------------------------------------------------------------
1 | namespace RoyT.TrueType.Tables
2 | {
3 | public static class TrueTypeTableNames
4 | {
5 | ///
6 | /// Character to Glyph Index Mapping Table
7 | ///
8 | public const string cmap = "cmap";
9 |
10 | ///
11 | /// Kerning Table
12 | ///
13 | public const string kern = "kern";
14 |
15 | ///
16 | /// Maximum Profile Table
17 | ///
18 | public const string maxp = "maxp";
19 |
20 | ///
21 | /// Font Header Table
22 | ///
23 | public const string head = "head";
24 |
25 | ///
26 | /// Naming Table
27 | ///
28 | public const string name = "name";
29 |
30 | ///
31 | /// OS/2 and Windows Metrics Table
32 | ///
33 | public const string os2 = "OS/2";
34 |
35 | ///
36 | /// Horizontal Header Table
37 | ///
38 | public const string hhea = "hhea";
39 |
40 | ///
41 | /// Horizontal Metrics Table
42 | ///
43 | public const string hmtx = "hmtx";
44 |
45 | ///
46 | /// Vertical Header Table
47 | ///
48 | public const string vhea = "vhea";
49 |
50 | ///
51 | /// Vertical Metrics Table
52 | ///
53 | public const string vmtx = "vmtx";
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/TtcHeader.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 | using System.Collections.Generic;
3 |
4 | namespace RoyT.TrueType
5 | {
6 | ///
7 | /// Header for TrueType Collection files
8 | ///
9 | public record TtcHeader()
10 | {
11 | ///
12 | /// Font Collection ID
13 | ///
14 | public string Tag { get; init; }
15 |
16 | ///
17 | /// Major version of the TTC Header
18 | ///
19 | public ushort MajorVersion { get; init; }
20 |
21 | ///
22 | /// Minor version of the TTC Header, = 0.
23 | ///
24 | public ushort MinorVersion { get; init; }
25 |
26 | ///
27 | /// Number of fonts in TTC
28 | ///
29 | public uint NumFonts { get; init; }
30 |
31 | ///
32 | /// Array of offsets to the TableDirectory for each font from the beginning of the file
33 | ///
34 | public IReadOnlyList TableDirectoryOffsets { get; init; }
35 |
36 | ///
37 | /// Tag indicating that a DSIG table exists, 0x44534947 (‘DSIG’) (null if no signature)
38 | ///
39 | public uint DsigTag { get; init; }
40 |
41 | ///
42 | /// The length(in bytes) of the DSIG table(null if no signature)
43 | ///
44 | public uint DsigLength { get; init; }
45 |
46 | ///
47 | /// The offset(in bytes) of the DSIG table from the beginning of the TTC file (null if no signature)
48 | ///
49 | public uint DsigOffset { get; init; }
50 |
51 | public static TtcHeader Parse(FontReader reader)
52 | {
53 | TtcHeader result = new()
54 | {
55 | Tag = reader.ReadAscii(4),
56 | MajorVersion = reader.ReadUInt16BigEndian(),
57 | MinorVersion = reader.ReadUInt16BigEndian(),
58 | NumFonts = reader.ReadUInt32BigEndian(),
59 | };
60 |
61 | List offsets = new();
62 | for (int i = 0; i < result.NumFonts; i++)
63 | {
64 | offsets.Add(reader.ReadUInt32BigEndian());
65 | }
66 |
67 | if (result.MajorVersion == 1)
68 | {
69 | return result with { TableDirectoryOffsets = offsets };
70 | }
71 |
72 | return result with
73 | {
74 | TableDirectoryOffsets = offsets,
75 | DsigTag = reader.ReadUInt32BigEndian(),
76 | DsigLength = reader.ReadUInt32BigEndian(),
77 | DsigOffset = reader.ReadUInt32BigEndian(),
78 | };
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/VheaTable.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 |
3 | namespace RoyT.TrueType.Tables
4 | {
5 | ///
6 | /// The Vertical Header Table contains information for vertical layout
7 | ///
8 | public sealed class VheaTable
9 | {
10 | public static VheaTable FromReader(FontReader reader, TableRecordEntry entry)
11 | {
12 | reader.Seek(entry.Offset);
13 |
14 | var version = reader.ReadFixedBigEndian();
15 |
16 | var ascender = reader.ReadInt16BigEndian();
17 | var descender = reader.ReadInt16BigEndian();
18 | var lineGap = reader.ReadInt16BigEndian();
19 | var advanceHeightMax = reader.ReadUInt16BigEndian();
20 | var minTopSideBearing = reader.ReadInt16BigEndian();
21 | var minBottomSideBearing = reader.ReadInt16BigEndian();
22 | var yMaxExtent = reader.ReadInt16BigEndian();
23 | var caretSlopeRise = reader.ReadInt16BigEndian();
24 | var caretSlopeRun = reader.ReadInt16BigEndian();
25 | var caretOffset = reader.ReadInt16BigEndian();
26 |
27 | // Seek over 4 reserved int16 fields
28 | reader.Seek(8, System.IO.SeekOrigin.Current);
29 |
30 | var metricDataFormat = reader.ReadInt16BigEndian();
31 | var numberOfVMetrics = reader.ReadUInt16BigEndian();
32 |
33 | return new VheaTable()
34 | {
35 | Version = version,
36 | Ascender = ascender,
37 | Descender = descender,
38 | LineGap = lineGap,
39 | AdvanceHeightMax = advanceHeightMax,
40 | MinTopSideBearing = minTopSideBearing,
41 | MinBottomSideBearing = minBottomSideBearing,
42 | YMaxExtent = yMaxExtent,
43 | CaretSlopeRise = caretSlopeRise,
44 | CaretSlopeRun = caretSlopeRun,
45 | CaretOffset = caretOffset,
46 | MetricDataFormat = metricDataFormat,
47 | NumberOfVMetrics = numberOfVMetrics,
48 | };
49 | }
50 |
51 | public static VheaTable Empty => new VheaTable();
52 |
53 | ///
54 | /// Version number of the vertical header table
55 | ///
56 | public float Version { get; init; }
57 |
58 | ///
59 | /// The vertical typographic ascender for this font. It is the distance in FUnits from the vertical
60 | /// center baseline to the right edge of the design space for CJK / ideographic glyphs (or “ideographic em box”).
61 | ///
62 | /// It is usually set to(head.unitsPerEm)/2. For example, a font with an em of 1000 FUnits will set this field
63 | /// to 500. See the Baseline tags section of the OpenType Layout Tag Registry for a description of the ideographic em-box.
64 | ///
65 | ///
66 | /// Named vertTypoAscender in table version 1.1
67 | ///
68 | public short Ascender { get; init; }
69 |
70 | ///
71 | /// The vertical typographic descender for this font. It is the distance in FUnits from the vertical center
72 | /// baseline to the left edge of the design space for CJK / ideographic glyphs.
73 | ///
74 | /// It is usually set to -(head.unitsPerEm/2). For example, a font with an em of 1000 FUnits will set this field to -500.
75 | ///
76 | ///
77 | /// Named vertTypoDescender in table version 1.1
78 | ///
79 | public short Descender { get; init; }
80 |
81 | ///
82 | /// The vertical typographic gap for this font. An application can determine the recommended line spacing for single
83 | /// spaced vertical text for an OpenType font by the following expression: ideo embox width + vhea.vertTypoLineGap
84 | ///
85 | ///
86 | /// Named vertTypoLineGap in table version 1.1
87 | ///
88 | public short LineGap { get; init; }
89 |
90 | ///
91 | /// Maximum advance height measurement in FUnits.
92 | ///
93 | public ushort AdvanceHeightMax { get; init; }
94 |
95 | ///
96 | /// Minimum top sidebearing value in FUnits.
97 | ///
98 | public short MinTopSideBearing { get; init; }
99 |
100 | ///
101 | /// Minimum bottom sidebearing value in FUnits.
102 | ///
103 | public short MinBottomSideBearing { get; init; }
104 |
105 | ///
106 | /// Defined as yMaxExtent = max(tsb + (yMax - yMin)).
107 | ///
108 | public short YMaxExtent { get; init; }
109 |
110 | ///
111 | /// The value of the caretSlopeRise field divided by the value of the caretSlopeRun Field
112 | /// determines the slope of the caret. A value of 0 for the rise and a value of 1 for the
113 | /// run specifies a horizontal caret. A value of 1 for the rise and a value of 0 for the
114 | /// run specifies a vertical caret. Intermediate values are desirable for fonts whose glyphs
115 | /// are oblique or italic. For a vertical font, a horizontal caret is best.
116 | ///
117 | public short CaretSlopeRise { get; init; }
118 |
119 | ///
120 | /// See the caretSlopeRise field. Value=1 for nonslanted vertical fonts.
121 | ///
122 | public short CaretSlopeRun { get; init; }
123 |
124 | ///
125 | /// The amount by which the highlight on a slanted glyph needs to be shifted away from the
126 | /// glyph in order to produce the best appearance. Set value equal to 0 for nonslanted fonts.
127 | ///
128 | public short CaretOffset { get; init; }
129 |
130 | ///
131 | /// 0 for current format.
132 | ///
133 | public short MetricDataFormat { get; init; }
134 |
135 | ///
136 | /// Number of advance heights in the 'vmtx' table
137 | ///
138 | public ushort NumberOfVMetrics { get; init; }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Vmtx/LongVerMetric.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 |
3 | namespace RoyT.TrueType.Tables.Vmtx
4 | {
5 | ///
6 | /// Horizontal layout metrics for a glyph
7 | ///
8 | public class LongVerMetric
9 | {
10 | public static LongVerMetric FromReader(FontReader reader)
11 | {
12 | return new()
13 | {
14 | AdvanceHeight = reader.ReadUInt16BigEndian(),
15 | TopSideBearing = reader.ReadInt16BigEndian(),
16 | };
17 | }
18 |
19 | ///
20 | /// Advance height, in font design units.
21 | ///
22 | public ushort AdvanceHeight { get; init; }
23 |
24 | ///
25 | /// Glyph left side bearing, in font design units.
26 | ///
27 | public short TopSideBearing { get; init; }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/RoyT.TrueType/Tables/Vmtx/VmtxTable.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace RoyT.TrueType.Tables.Vmtx
6 | {
7 | ///
8 | /// Vertical Metrics Table
9 | ///
10 | public sealed class VmtxTable
11 | {
12 | public static VmtxTable FromReader(FontReader reader, TableRecordEntry entry, int metricsCount, int glyphCount)
13 | {
14 | reader.Seek(entry.Offset);
15 |
16 | int topSideBearingCount = glyphCount - metricsCount;
17 | if (entry.Length != metricsCount * 4 + topSideBearingCount * 2)
18 | {
19 | throw new Exception("unexpected vmtx table length");
20 | }
21 |
22 | List vmetrics = new(metricsCount);
23 | for (int i = 0; i < metricsCount; i++)
24 | {
25 | vmetrics.Add(LongVerMetric.FromReader(reader));
26 | }
27 |
28 | List topSideBearings = new(topSideBearingCount);
29 | for (int i = 0; i < topSideBearingCount; i++)
30 | {
31 | topSideBearings.Add(reader.ReadInt16BigEndian());
32 | }
33 |
34 | return new()
35 | {
36 | VMetrics = vmetrics,
37 | TopSideBearings = topSideBearings,
38 | };
39 | }
40 |
41 | public static VmtxTable Empty => new()
42 | {
43 | VMetrics = new List()
44 | };
45 |
46 | ///
47 | /// Paired advance width and left side bearing values for each glyph. Records are indexed by glyph ID.
48 | ///
49 | public IReadOnlyList VMetrics { get; init; }
50 |
51 | ///
52 | /// Optional array for glyphs not in VMetrics. Their TopSideBearing is equal to the TopSideBearing
53 | /// of the last entry in VMetrics
54 | ///
55 | public IReadOnlyList TopSideBearings { get; init; }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/RoyT.TrueType/TrueTypeFont.cs:
--------------------------------------------------------------------------------
1 | using RoyT.TrueType.IO;
2 | using RoyT.TrueType.Tables;
3 | using RoyT.TrueType.Tables.Cmap;
4 | using RoyT.TrueType.Tables.Hmtx;
5 | using RoyT.TrueType.Tables.Kern;
6 | using RoyT.TrueType.Tables.Name;
7 | using RoyT.TrueType.Tables.Vmtx;
8 | using System;
9 | using System.Collections.Generic;
10 | using System.IO;
11 |
12 | namespace RoyT.TrueType
13 | {
14 | public sealed class TrueTypeFont
15 | {
16 | public static TrueTypeFont FromFile(string path)
17 | {
18 | using var fstream = File.OpenRead(path);
19 | using var reader = new FontReader(fstream);
20 |
21 | return FromStream(reader, path);
22 | }
23 |
24 | public static IReadOnlyList FromCollectionFile(string path)
25 | {
26 | using var fstream = File.OpenRead(path);
27 | using var reader = new FontReader(fstream);
28 |
29 | var header = TtcHeader.Parse(reader);
30 |
31 | List fonts = new();
32 | foreach (var offset in header.TableDirectoryOffsets)
33 | {
34 | reader.Seek(offset);
35 | fonts.Add(FromStream(reader, path));
36 | }
37 |
38 | return fonts;
39 | }
40 |
41 | public static TrueTypeFont FromStream(FontReader reader, string source)
42 | {
43 | var offsetTable = OffsetTable.FromReader(reader);
44 | var entries = ReadTableRecords(reader, offsetTable);
45 |
46 | var cmap = ReadCmapTable(source, reader, entries);
47 | var name = ReadNameTable(source, reader, entries);
48 | var head = ReadHeadTable(source, reader, entries);
49 | var maxp = ReadMaxpTable(source, reader, entries);
50 | var os2 = ReadOs2Table(reader, entries);
51 | var kern = ReadKernTable(reader, entries);
52 | var hhea = ReadHheaTable(reader, entries);
53 | var vhea = ReadVheaTable(reader, entries);
54 | var hmtx = ReadHmtxTable(reader, entries, hhea.NumberOfHMetrics, maxp.NumGlyphs);
55 | var vmtx = ReadVmtxTable(reader, entries, vhea.NumberOfVMetrics, maxp.NumGlyphs);
56 |
57 | return new TrueTypeFont(source, offsetTable, entries, cmap, name, kern)
58 | {
59 | Os2Table = os2,
60 | HeadTable = head,
61 | MaxpTable = maxp,
62 | HheaTable = hhea,
63 | HmtxTable = hmtx,
64 | VheaTable = vhea,
65 | VmtxTable = vmtx,
66 | };
67 | }
68 |
69 | public static bool TryGetTablePosition(FontReader reader, string tableName, out long offset)
70 | {
71 | reader.Seek(0);
72 | var offsetTable = OffsetTable.FromReader(reader);
73 | var entries = ReadTableRecords(reader, offsetTable);
74 |
75 | if (entries.TryGetValue(tableName, out var cmapEntry))
76 | {
77 | offset = cmapEntry.Offset;
78 | return true;
79 | }
80 |
81 | offset = 0;
82 | return false;
83 | }
84 |
85 | private static IReadOnlyDictionary ReadTableRecords(FontReader reader, OffsetTable offsetTable)
86 | {
87 | var entries = new Dictionary(offsetTable.Tables);
88 | for (var i = 0; i < offsetTable.Tables; i++)
89 | {
90 | var entry = TableRecordEntry.FromReader(reader);
91 | entries.Add(entry.Tag, entry);
92 | }
93 |
94 | return entries;
95 | }
96 |
97 | private static CmapTable ReadCmapTable(string path, FontReader reader, IReadOnlyDictionary entries)
98 | {
99 | if (entries.TryGetValue(TrueTypeTableNames.cmap, out var cmapEntry))
100 | {
101 | reader.Seek(cmapEntry.Offset);
102 | return CmapTable.FromReader(reader);
103 | }
104 |
105 | throw new Exception(
106 | $"Font {path} does not contain a Character To Glyph Index Mapping Table (cmap)");
107 |
108 | }
109 |
110 | private static NameTable ReadNameTable(string path, FontReader reader, IReadOnlyDictionary entries)
111 | {
112 | if (entries.TryGetValue(TrueTypeTableNames.name, out var cmapEntry))
113 | {
114 | reader.Seek(cmapEntry.Offset);
115 | return NameTable.FromReader(reader);
116 | }
117 |
118 | throw new Exception(
119 | $"Font {path} does not contain a Name Table (name)");
120 | }
121 |
122 | private static HeadTable ReadHeadTable(string path, FontReader reader, IReadOnlyDictionary entries)
123 | {
124 | if (entries.TryGetValue(TrueTypeTableNames.head, out var entry))
125 | {
126 | return HeadTable.FromReader(reader, entry);
127 | }
128 |
129 | throw new Exception(
130 | $"Font {path} does not contain a Font Header Table (head)");
131 | }
132 |
133 | private static MaxpTable ReadMaxpTable(string path, FontReader reader, IReadOnlyDictionary entries)
134 | {
135 | if (entries.TryGetValue(TrueTypeTableNames.maxp, out var entry))
136 | {
137 | return MaxpTable.FromReader(reader, entry);
138 | }
139 |
140 | throw new Exception(
141 | $"Font {path} does not contain a Maximum Profile Table (maxp)");
142 | }
143 |
144 | private static Os2Table ReadOs2Table(FontReader reader, IReadOnlyDictionary entries)
145 | {
146 | if (entries.TryGetValue(TrueTypeTableNames.os2, out var entry))
147 | {
148 | return Os2Table.FromReader(reader, entry);
149 | }
150 |
151 | return default;
152 | }
153 |
154 | private static KernTable ReadKernTable(FontReader reader, IReadOnlyDictionary entries)
155 | {
156 | if (entries.TryGetValue(TrueTypeTableNames.kern, out var kernEntry))
157 | {
158 | reader.Seek(kernEntry.Offset);
159 | return KernTable.FromReader(reader);
160 | }
161 |
162 | return KernTable.Empty;
163 | }
164 |
165 | private static HheaTable ReadHheaTable(FontReader reader, IReadOnlyDictionary entries)
166 | {
167 | if (entries.TryGetValue(TrueTypeTableNames.hhea, out var entry))
168 | {
169 | return HheaTable.FromReader(reader, entry);
170 | }
171 |
172 | return HheaTable.Empty;
173 | }
174 |
175 | private static HmtxTable ReadHmtxTable(FontReader reader, IReadOnlyDictionary entries, ushort metricsCount, ushort glyphCount)
176 | {
177 | if (entries.TryGetValue(TrueTypeTableNames.hmtx, out var entry))
178 | {
179 | return HmtxTable.FromReader(reader, entry, metricsCount, glyphCount);
180 | }
181 |
182 | return HmtxTable.Empty;
183 | }
184 |
185 | private static VheaTable ReadVheaTable(FontReader reader, IReadOnlyDictionary entries)
186 | {
187 | if (entries.TryGetValue(TrueTypeTableNames.vhea, out var entry))
188 | {
189 | return VheaTable.FromReader(reader, entry);
190 | }
191 |
192 | return VheaTable.Empty;
193 | }
194 |
195 | private static VmtxTable ReadVmtxTable(FontReader reader, IReadOnlyDictionary entries, ushort metricsCount, int glyphCount)
196 | {
197 | if (entries.TryGetValue(TrueTypeTableNames.vmtx, out var entry))
198 | {
199 | return VmtxTable.FromReader(reader, entry, metricsCount, glyphCount);
200 | }
201 |
202 | return VmtxTable.Empty;
203 | }
204 |
205 | private TrueTypeFont(string source, OffsetTable offsetTable, IReadOnlyDictionary entries, CmapTable cmapTable, NameTable nameTable, KernTable kernTable)
206 | {
207 | this.Source = source;
208 | this.OffsetTable = offsetTable;
209 | this.TableRecordEntries = entries;
210 | this.CmapTable = cmapTable;
211 | this.NameTable = nameTable;
212 | this.KernTable = kernTable;
213 | }
214 |
215 | public string Source { get; }
216 | public OffsetTable OffsetTable { get; }
217 | public IReadOnlyDictionary TableRecordEntries { get; }
218 |
219 | ///
220 | /// Contains information to get the glyph that corresponds to each supported character
221 | ///
222 | public CmapTable CmapTable { get; }
223 |
224 | ///
225 | /// Contains the (translated) name of this font, copyright notices, etc...
226 | ///
227 | public NameTable NameTable { get; }
228 |
229 | ///
230 | /// Consists of a set of metrics and other data that are required in OpenType fonts
231 | ///
232 | public Os2Table Os2Table { get; init; }
233 |
234 | ///
235 | /// Consists of global information about the font
236 | ///
237 | public HeadTable HeadTable { get; init; }
238 |
239 | ///
240 | /// Contains the memory requirements for this font.
241 | ///
242 | public MaxpTable MaxpTable { get; init; }
243 |
244 | ///
245 | /// Contains adjustment to horizontal/vertical positions between glyphs
246 | ///
247 | public KernTable KernTable { get; }
248 |
249 | ///
250 | /// Contains information for horizontal layout
251 | ///
252 | public HheaTable HheaTable { get; init; }
253 |
254 | ///
255 | /// Horizontal Metrics Table
256 | ///
257 | public HmtxTable HmtxTable { get; init; }
258 |
259 | ///
260 | /// Contains information for vertical layout
261 | ///
262 | public VheaTable VheaTable { get; init; }
263 |
264 | ///
265 | /// Vertical Metrics Table
266 | ///
267 | public VmtxTable VmtxTable { get; init; }
268 |
269 | public override string ToString() => this.Source;
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/RoyT.TrueType/WindowsEncoding.cs:
--------------------------------------------------------------------------------
1 | namespace RoyT.TrueType
2 | {
3 | public enum WindowsEncoding : ushort
4 | {
5 | Symbol = 0,
6 | ///
7 | /// Unicode BMP
8 | ///
9 | UnicodeUCS2 = 1,
10 | ShiftJIS = 2,
11 | PRC = 3,
12 | Big5 = 4,
13 | Wansung = 5,
14 | Johab = 6,
15 | Reserved0 = 7,
16 | Reserved1 = 8,
17 | Reserved2 = 9,
18 | UnicodeUCS4 = 10
19 | }
20 | }
21 |
--------------------------------------------------------------------------------