Skip to content

Commit 75f1879

Browse files
committed
Finalize TTF to VEC font builder
1 parent 6b4ae69 commit 75f1879

File tree

9 files changed

+156
-36
lines changed

9 files changed

+156
-36
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net6.0</TargetFramework>
6+
<Product>nvec_builder</Product>
7+
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
8+
<AssemblyName>nvec_builder</AssemblyName>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\PDTools.Files\PDTools.Files.csproj" />
13+
</ItemGroup>
14+
15+
<Import Project="..\Typography\Typography.OpenFont\Typography.OpenFont.projitems" Label="Shared" />
16+
</Project>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using PDTools.Files.Fonts;
2+
3+
using System;
4+
using System.IO;
5+
6+
namespace PDTools.Files.Fonts.NVecBuilder
7+
{
8+
internal class Program
9+
{
10+
static void Main(string[] args)
11+
{
12+
Console.WriteLine("NVecBuilder by Nenkai#9075");
13+
14+
if (args.Length != 2)
15+
{
16+
Console.WriteLine("This tool is intended for GT5/6 (older PS3 GTs uses older versions of vector fonts)");
17+
Console.WriteLine(" Usage: nvec_builder.exe <input_ttf_file> <output_vec_file>");
18+
return;
19+
}
20+
21+
Console.WriteLine();
22+
23+
if (!File.Exists(args[0]))
24+
{
25+
Console.WriteLine("Input TTF file does not exist");
26+
return;
27+
}
28+
29+
args[1] = Path.GetFullPath(args[1]);
30+
Directory.CreateDirectory(Path.GetDirectoryName(args[1]));
31+
32+
try
33+
{
34+
TrueTypeToNVecConverter.Convert(args[0], args[1]);
35+
}
36+
catch (Exception ex)
37+
{
38+
Console.WriteLine($"Failed to convert: {ex}");
39+
return;
40+
}
41+
42+
Console.WriteLine($"TTF -> NVEC -> {args[1]}");
43+
44+
}
45+
}
46+
}

PDTools.Files/Fonts/TrueTypeToNVecConverter.cs renamed to PDTools.Files.Fonts.NVecBuilder/TrueTypeToNVecConverter.cs

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,15 @@
1010
using TTFGlyph = Typography.OpenFont.Glyph;
1111
using NVecGlyph = PDTools.Files.Fonts.Glyph;
1212

13-
namespace PDTools.Files.Fonts
13+
namespace PDTools.Files.Fonts.NVecBuilder
1414
{
1515
public class TrueTypeToNVecConverter
1616
{
17-
/* Some notes:
18-
* May not be the prettiest conversion code, Y is also swapped around
19-
* "/ 2" is hardcoded for 2048 units per em fonts */
20-
21-
public static int Scale { get; set; } = 2;
2217
public static void Convert(string inputTtf, string outputVec)
2318
{
19+
if (Path.GetExtension(inputTtf) == ".otf")
20+
throw new NotSupportedException("otf (OpenType) is not supported (incompatible curves).");
21+
2422
using var ttf = File.OpenRead(inputTtf);
2523
Typeface ttfFont = new OpenFontReader().Read(ttf);
2624

@@ -30,22 +28,26 @@ public static void Convert(string inputTtf, string outputVec)
3028
ttfFont.CollectUnicode(unicodes);
3129
unicodes = unicodes.Distinct().ToList();
3230

31+
Console.WriteLine($"TTF: {unicodes.Count} unicode characters");
32+
Console.WriteLine($"TTF: {ttfFont.UnitsPerEm} units per em");
33+
34+
int Scale = ttfFont.UnitsPerEm > 1024 ? ttfFont.UnitsPerEm / 1024 : 1;
35+
3336
foreach (var codePoint in unicodes)
3437
{
3538
if (codePoint == 0)
3639
continue;
3740

3841
ushort glyphIndex = ttfFont.GetGlyphIndex((int)codePoint);
3942
TTFGlyph ttfGlyph = ttfFont.GetGlyph(glyphIndex);
40-
4143
NVecGlyph vecGlyph = new NVecGlyph((char)codePoint);
42-
vecGlyph.AdvanceWidth = (ushort)(ttfFont.GetAdvanceWidthFromGlyphIndex((ushort)glyphIndex) / Scale);
44+
45+
Console.WriteLine($"TTF: Processing '{(char)codePoint}' ({codePoint:X4}) with {ttfGlyph.GlyphPoints.Length} points");
4346

4447
int offset = ttfFont.Bounds.YMax - ttfGlyph.Bounds.YMax;
4548
vecGlyph.HeightOffset = (ushort)(offset / Scale);
4649

4750
Span<ushort> endPoints = ttfGlyph.EndPoints.AsSpan();
48-
4951
GlyphPointF lastPoint = default;
5052

5153
if (ttfGlyph.GlyphPoints.Any())
@@ -64,7 +66,7 @@ public static void Convert(string inputTtf, string outputVec)
6466
var prev = ttfGlyph.GlyphPoints[i - 1];
6567
if (prev.onCurve)
6668
{
67-
IGlyphShapeData shape = CompareAdd(prev, currentOutlineStartPoint);
69+
IGlyphShapeData shape = CompareAdd(prev, currentOutlineStartPoint, Scale);
6870
vecGlyph.Points.Data.Add(shape);
6971
}
7072
}
@@ -109,51 +111,52 @@ public static void Convert(string inputTtf, string outputVec)
109111
}
110112
}
111113

112-
var curve = GetCurve(start, control, lastPoint);
114+
var curve = GetCurve(start, control, lastPoint, Scale);
113115
vecGlyph.Points.Data.Add(curve);
114116
}
115117
else
116118
{
117119
// Handle non-curves
118-
IGlyphShapeData shape = CompareAdd(lastPoint, point);
120+
IGlyphShapeData shape = CompareAdd(lastPoint, point, Scale);
119121
vecGlyph.Points.Data.Add(shape);
120122
lastPoint = point;
121123
}
122124
}
123125
}
124126

125-
vecGlyph.Points.Data.Add(CompareAdd(ttfGlyph.GlyphPoints[^1], currentOutlineStartPoint));
127+
vecGlyph.Points.Data.Add(CompareAdd(ttfGlyph.GlyphPoints[^1], currentOutlineStartPoint, Scale));
126128
}
129+
127130
vecFont.AddGlyph(vecGlyph);
128131
}
129132

130133
using var outputStream = File.Create(outputVec);
131134
vecFont.Write(outputStream);
132135
}
133136

134-
private static IGlyphShapeData CompareAdd(GlyphPointF prev, GlyphPointF next)
137+
private static IGlyphShapeData CompareAdd(GlyphPointF prev, GlyphPointF next, int scale)
135138
{
136139
if (next.X == prev.X)
137140
{
138-
return new GlyphLine(-(next.Y - prev.Y) / Scale, GlyphAxis.Y);
141+
return new GlyphLine(-(next.Y - prev.Y) / scale, GlyphAxis.Y);
139142
}
140143
else if (next.Y == prev.Y)
141144
{
142-
return new GlyphLine((next.X - prev.X) / Scale, GlyphAxis.X);
145+
return new GlyphLine((next.X - prev.X) / scale, GlyphAxis.X);
143146
}
144147
else
145148
{
146-
return new GlyphPoint((next.X - prev.X) / Scale, -(next.Y - prev.Y) / Scale);
149+
return new GlyphPoint((next.X - prev.X) / scale, -(next.Y - prev.Y) / scale);
147150
}
148151
}
149152

150-
private static GlyphQuadraticBezierCurve GetCurve(GlyphPointF start, GlyphPointF control, GlyphPointF end)
153+
private static GlyphQuadraticBezierCurve GetCurve(GlyphPointF start, GlyphPointF control, GlyphPointF end, int scale)
151154
{
152-
float p1X = (control.X - start.X) / Scale;
153-
float p1Y = -(control.Y - start.Y) / Scale;
155+
float p1X = (control.X - start.X) / scale;
156+
float p1Y = -(control.Y - start.Y) / scale;
154157

155-
float p2X = (end.X - control.X) / Scale;
156-
float p2Y = -(end.Y - control.Y) / Scale;
158+
float p2X = (end.X - control.X) / scale;
159+
float p2Y = -(end.Y - control.Y) / scale;
157160

158161
return new GlyphQuadraticBezierCurve(p1X, p1Y, p2X, p2Y);
159162
}

PDTools.Files/Fonts/Glyph.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class Glyph
2121

2222
public ushort Flags { get; set; } = 0x8002;
2323
public ushort HeightOffset { get; set; }
24-
public ushort AdvanceWidth { get; set; }
24+
public ushort Width { get; set; }
2525
public GlyphShapes Points { get; set; } = new();
2626

2727
public Glyph(char character)
@@ -43,7 +43,7 @@ public static Glyph Read(BinaryStream bs, int dataOffset)
4343

4444
int dataLength = bs.ReadInt32();
4545
uint bits = bs.ReadUInt32();
46-
glyph.AdvanceWidth = (ushort)(bits >> 20);
46+
glyph.Width = (ushort)(bits >> 20);
4747
glyph.HeightOffset = (ushort)((bits >> 8) & 0b1111_11111111);
4848
byte calculatedRenderStrideCount = (byte)(bits & 0xFF); // Check NVectorFont.WriteGlyphs for how this is calculated
4949

@@ -127,5 +127,10 @@ public Image<Rgba32> GetAsImage()
127127

128128
return image;
129129
}
130+
131+
public override string ToString()
132+
{
133+
return Character.ToString();
134+
}
130135
}
131136
}

PDTools.Files/Fonts/GlyphShapes.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public class GlyphShapes
1414
{
1515
public float XMin { get; set; }
1616
public float YMin { get; set; }
17+
public float XMax { get; set; }
18+
public float YMax { get; set; }
1719

1820
public List<IGlyphShapeData> Data { get; set; } = new();
1921

@@ -200,15 +202,25 @@ public byte[] Write(BinaryStream bs)
200202
return buffer.ToArray();
201203
}
202204

203-
public void RecalculateMins()
205+
public void RecalculateBounds()
204206
{
205207
float minX = 0, minY = 0;
208+
float maxX = 0, maxY = 0;
209+
206210
float currentX = 0, currentY = 0;
207211
for (int i1 = 0; i1 < Data.Count; i1++)
208212
{
209213
IGlyphShapeData? i = Data[i1];
210214
if (i is GlyphStartPoint startPoint)
211215
{
216+
if (startPoint.Unk == true)
217+
{
218+
minX = startPoint.X;
219+
minY = startPoint.Y;
220+
maxX = startPoint.X;
221+
maxY = startPoint.Y;
222+
}
223+
212224
currentX = startPoint.X;
213225
currentY = startPoint.Y;
214226
}
@@ -235,10 +247,14 @@ public void RecalculateMins()
235247

236248
if (currentX < minX) minX = currentX;
237249
if (currentY < minY) minY = currentY;
250+
if (currentX > maxX) maxX = currentX;
251+
if (currentY > maxY) maxY = currentY;
238252
}
239253

240254
XMin = minX;
241255
YMin = minY;
256+
XMax = maxX;
257+
YMax = maxY;
242258
}
243259

244260
private static float BitValueToFloat(long value, int bitCount)

PDTools.Files/Fonts/NVectorFont.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ private void WriteGlyphs(BinaryStream bs)
160160
bs.WriteInt32(glyphDataSize);
161161

162162
uint bits = 0;
163-
bits |= (uint)((glyph.AdvanceWidth & 0b1111_11111111) << 20); // 12 bits
163+
bits |= (uint)((glyph.Width & 0b1111_11111111) << 20); // 12 bits
164164
bits |= (uint)((glyph.HeightOffset & 0b1111_11111111) << 8); // 12 bits
165165
bits |= (uint)((glyphDataSize + 0x1F) / 0x10 & 0b11111111); // 8 bit
166166
bs.WriteUInt32(bits);
@@ -177,12 +177,18 @@ private void WriteGlyphs(BinaryStream bs)
177177
bs.Position = lastGlyphDataOffset;
178178
}
179179

180+
/// <summary>
181+
/// Adds the glyph to the font (and recalculates the glyph's bounds/width)
182+
/// </summary>
183+
/// <param name="glyph"></param>
184+
/// <exception cref="Exception"></exception>
180185
public void AddGlyph(Glyph glyph)
181186
{
182187
if (Characters.Contains(glyph.Character))
183188
throw new Exception($"Glyph '{glyph.Character}' already exists");
184189

185-
glyph.Points.RecalculateMins();
190+
glyph.Points.RecalculateBounds();
191+
glyph.Width = (ushort)(glyph.Points.XMax - glyph.Points.XMin);
186192

187193
Characters.Add(glyph.Character);
188194
Glyphs.Add(glyph);

PDTools.Files/PDTools.Files.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,4 @@
2020
<ProjectReference Include="..\PDTools.Utils\PDTools.Utils.csproj" />
2121
</ItemGroup>
2222

23-
<Import Project="..\Typography\Typography.OpenFont\Typography.OpenFont.projitems" Label="Shared" />
24-
2523
</Project>

PDTools.GTPatcher/GT7AppOpt.txt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,33 @@ rtext_debug
9696
demo_idx
9797

9898
// >1.00 (seen in 1.25)
99-
emotional
99+
emotional
100+
101+
reward_ce_course
102+
autodemo_pause_time
103+
autodemo_return_time
104+
autodemo_entry_num
105+
autodemo_group
106+
autodemo_course
107+
autodemo_track_weather_id
108+
no_bandwidth
109+
lobby_auto_entry
110+
debug_entry_channel
111+
online_race_host
112+
online_race_join_as_spectator
113+
OnlineGameMode
114+
livery_share_file
115+
scapes_open
116+
scapes1
117+
scene_preview_mode
118+
scene_preview_seq
119+
event2
120+
trade_in
121+
virtual_paddock
122+
virtual_paddock_no_car_share
123+
rtext_develop
124+
birthdaygift
125+
livery_special_decken
126+
skip_online_title_movie
127+
enable_bridge_on_steward
128+
vr_showroom_open

PDTools.sln

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PDTools.GTPatcher", "PDTool
4141
EndProject
4242
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Typography.OpenFont", "Typography\Typography.OpenFont\Typography.OpenFont.shproj", "{235A071B-8D06-40AE-A5C5-B1CE59715EE9}"
4343
EndProject
44+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PDTools.Files.Fonts.NVecBuilder", "PDTools.Files.Fonts.NVecBuilder\PDTools.Files.Fonts.NVecBuilder.csproj", "{A5CD16E0-AEE4-4656-AD47-717EA915B909}"
45+
EndProject
4446
Global
4547
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4648
Debug|Any CPU = Debug|Any CPU
@@ -119,10 +121,10 @@ Global
119121
{39FA31E3-6088-437C-9697-C86CABFFF733}.Debug|Any CPU.Build.0 = Debug|Any CPU
120122
{39FA31E3-6088-437C-9697-C86CABFFF733}.Release|Any CPU.ActiveCfg = Release|Any CPU
121123
{39FA31E3-6088-437C-9697-C86CABFFF733}.Release|Any CPU.Build.0 = Release|Any CPU
122-
{D585EDFC-C144-486F-BCB9-2991534D8E9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
123-
{D585EDFC-C144-486F-BCB9-2991534D8E9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
124-
{D585EDFC-C144-486F-BCB9-2991534D8E9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
125-
{D585EDFC-C144-486F-BCB9-2991534D8E9E}.Release|Any CPU.Build.0 = Release|Any CPU
124+
{A5CD16E0-AEE4-4656-AD47-717EA915B909}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
125+
{A5CD16E0-AEE4-4656-AD47-717EA915B909}.Debug|Any CPU.Build.0 = Debug|Any CPU
126+
{A5CD16E0-AEE4-4656-AD47-717EA915B909}.Release|Any CPU.ActiveCfg = Release|Any CPU
127+
{A5CD16E0-AEE4-4656-AD47-717EA915B909}.Release|Any CPU.Build.0 = Release|Any CPU
126128
EndGlobalSection
127129
GlobalSection(SolutionProperties) = preSolution
128130
HideSolutionNode = FALSE
@@ -132,7 +134,6 @@ Global
132134
EndGlobalSection
133135
GlobalSection(SharedMSBuildProjectFiles) = preSolution
134136
Typography\Typography.OpenFont\Typography.OpenFont.projitems*{235a071b-8d06-40ae-a5c5-b1ce59715ee9}*SharedItemsImports = 13
135-
Typography\Typography.OpenFont\Typography.OpenFont.projitems*{72d26fba-e068-43ae-b5c1-492e45586c13}*SharedItemsImports = 5
136-
Typography\Typography.OpenFont\Typography.OpenFont.projitems*{d585edfc-c144-486f-bcb9-2991534d8e9e}*SharedItemsImports = 5
137+
Typography\Typography.OpenFont\Typography.OpenFont.projitems*{a5cd16e0-aee4-4656-ad47-717ea915b909}*SharedItemsImports = 5
137138
EndGlobalSection
138139
EndGlobal

0 commit comments

Comments
 (0)