Skip to content

Commit

Permalink
Finalize TTF to VEC font builder
Browse files Browse the repository at this point in the history
  • Loading branch information
Nenkai committed Feb 25, 2023
1 parent 6b4ae69 commit 75f1879
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Product>nvec_builder</Product>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyName>nvec_builder</AssemblyName>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\PDTools.Files\PDTools.Files.csproj" />
</ItemGroup>

<Import Project="..\Typography\Typography.OpenFont\Typography.OpenFont.projitems" Label="Shared" />
</Project>
46 changes: 46 additions & 0 deletions PDTools.Files.Fonts.NVecBuilder/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using PDTools.Files.Fonts;

using System;
using System.IO;

namespace PDTools.Files.Fonts.NVecBuilder
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("NVecBuilder by Nenkai#9075");

if (args.Length != 2)
{
Console.WriteLine("This tool is intended for GT5/6 (older PS3 GTs uses older versions of vector fonts)");
Console.WriteLine(" Usage: nvec_builder.exe <input_ttf_file> <output_vec_file>");
return;
}

Console.WriteLine();

if (!File.Exists(args[0]))
{
Console.WriteLine("Input TTF file does not exist");
return;
}

args[1] = Path.GetFullPath(args[1]);
Directory.CreateDirectory(Path.GetDirectoryName(args[1]));

try
{
TrueTypeToNVecConverter.Convert(args[0], args[1]);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to convert: {ex}");
return;
}

Console.WriteLine($"TTF -> NVEC -> {args[1]}");

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,15 @@
using TTFGlyph = Typography.OpenFont.Glyph;
using NVecGlyph = PDTools.Files.Fonts.Glyph;

namespace PDTools.Files.Fonts
namespace PDTools.Files.Fonts.NVecBuilder
{
public class TrueTypeToNVecConverter
{
/* Some notes:
* May not be the prettiest conversion code, Y is also swapped around
* "/ 2" is hardcoded for 2048 units per em fonts */

public static int Scale { get; set; } = 2;
public static void Convert(string inputTtf, string outputVec)
{
if (Path.GetExtension(inputTtf) == ".otf")
throw new NotSupportedException("otf (OpenType) is not supported (incompatible curves).");

using var ttf = File.OpenRead(inputTtf);
Typeface ttfFont = new OpenFontReader().Read(ttf);

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

Console.WriteLine($"TTF: {unicodes.Count} unicode characters");
Console.WriteLine($"TTF: {ttfFont.UnitsPerEm} units per em");

int Scale = ttfFont.UnitsPerEm > 1024 ? ttfFont.UnitsPerEm / 1024 : 1;

foreach (var codePoint in unicodes)
{
if (codePoint == 0)
continue;

ushort glyphIndex = ttfFont.GetGlyphIndex((int)codePoint);
TTFGlyph ttfGlyph = ttfFont.GetGlyph(glyphIndex);

NVecGlyph vecGlyph = new NVecGlyph((char)codePoint);
vecGlyph.AdvanceWidth = (ushort)(ttfFont.GetAdvanceWidthFromGlyphIndex((ushort)glyphIndex) / Scale);

Console.WriteLine($"TTF: Processing '{(char)codePoint}' ({codePoint:X4}) with {ttfGlyph.GlyphPoints.Length} points");

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

Span<ushort> endPoints = ttfGlyph.EndPoints.AsSpan();

GlyphPointF lastPoint = default;

if (ttfGlyph.GlyphPoints.Any())
Expand All @@ -64,7 +66,7 @@ public static void Convert(string inputTtf, string outputVec)
var prev = ttfGlyph.GlyphPoints[i - 1];
if (prev.onCurve)
{
IGlyphShapeData shape = CompareAdd(prev, currentOutlineStartPoint);
IGlyphShapeData shape = CompareAdd(prev, currentOutlineStartPoint, Scale);
vecGlyph.Points.Data.Add(shape);
}
}
Expand Down Expand Up @@ -109,51 +111,52 @@ public static void Convert(string inputTtf, string outputVec)
}
}

var curve = GetCurve(start, control, lastPoint);
var curve = GetCurve(start, control, lastPoint, Scale);
vecGlyph.Points.Data.Add(curve);
}
else
{
// Handle non-curves
IGlyphShapeData shape = CompareAdd(lastPoint, point);
IGlyphShapeData shape = CompareAdd(lastPoint, point, Scale);
vecGlyph.Points.Data.Add(shape);
lastPoint = point;
}
}
}

vecGlyph.Points.Data.Add(CompareAdd(ttfGlyph.GlyphPoints[^1], currentOutlineStartPoint));
vecGlyph.Points.Data.Add(CompareAdd(ttfGlyph.GlyphPoints[^1], currentOutlineStartPoint, Scale));
}

vecFont.AddGlyph(vecGlyph);
}

using var outputStream = File.Create(outputVec);
vecFont.Write(outputStream);
}

private static IGlyphShapeData CompareAdd(GlyphPointF prev, GlyphPointF next)
private static IGlyphShapeData CompareAdd(GlyphPointF prev, GlyphPointF next, int scale)
{
if (next.X == prev.X)
{
return new GlyphLine(-(next.Y - prev.Y) / Scale, GlyphAxis.Y);
return new GlyphLine(-(next.Y - prev.Y) / scale, GlyphAxis.Y);
}
else if (next.Y == prev.Y)
{
return new GlyphLine((next.X - prev.X) / Scale, GlyphAxis.X);
return new GlyphLine((next.X - prev.X) / scale, GlyphAxis.X);
}
else
{
return new GlyphPoint((next.X - prev.X) / Scale, -(next.Y - prev.Y) / Scale);
return new GlyphPoint((next.X - prev.X) / scale, -(next.Y - prev.Y) / scale);
}
}

private static GlyphQuadraticBezierCurve GetCurve(GlyphPointF start, GlyphPointF control, GlyphPointF end)
private static GlyphQuadraticBezierCurve GetCurve(GlyphPointF start, GlyphPointF control, GlyphPointF end, int scale)
{
float p1X = (control.X - start.X) / Scale;
float p1Y = -(control.Y - start.Y) / Scale;
float p1X = (control.X - start.X) / scale;
float p1Y = -(control.Y - start.Y) / scale;

float p2X = (end.X - control.X) / Scale;
float p2Y = -(end.Y - control.Y) / Scale;
float p2X = (end.X - control.X) / scale;
float p2Y = -(end.Y - control.Y) / scale;

return new GlyphQuadraticBezierCurve(p1X, p1Y, p2X, p2Y);
}
Expand Down
9 changes: 7 additions & 2 deletions PDTools.Files/Fonts/Glyph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class Glyph

public ushort Flags { get; set; } = 0x8002;
public ushort HeightOffset { get; set; }
public ushort AdvanceWidth { get; set; }
public ushort Width { get; set; }
public GlyphShapes Points { get; set; } = new();

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

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

Expand Down Expand Up @@ -127,5 +127,10 @@ public Image<Rgba32> GetAsImage()

return image;
}

public override string ToString()
{
return Character.ToString();
}
}
}
18 changes: 17 additions & 1 deletion PDTools.Files/Fonts/GlyphShapes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class GlyphShapes
{
public float XMin { get; set; }
public float YMin { get; set; }
public float XMax { get; set; }
public float YMax { get; set; }

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

Expand Down Expand Up @@ -200,15 +202,25 @@ public byte[] Write(BinaryStream bs)
return buffer.ToArray();
}

public void RecalculateMins()
public void RecalculateBounds()
{
float minX = 0, minY = 0;
float maxX = 0, maxY = 0;

float currentX = 0, currentY = 0;
for (int i1 = 0; i1 < Data.Count; i1++)
{
IGlyphShapeData? i = Data[i1];
if (i is GlyphStartPoint startPoint)
{
if (startPoint.Unk == true)
{
minX = startPoint.X;
minY = startPoint.Y;
maxX = startPoint.X;
maxY = startPoint.Y;
}

currentX = startPoint.X;
currentY = startPoint.Y;
}
Expand All @@ -235,10 +247,14 @@ public void RecalculateMins()

if (currentX < minX) minX = currentX;
if (currentY < minY) minY = currentY;
if (currentX > maxX) maxX = currentX;
if (currentY > maxY) maxY = currentY;
}

XMin = minX;
YMin = minY;
XMax = maxX;
YMax = maxY;
}

private static float BitValueToFloat(long value, int bitCount)
Expand Down
10 changes: 8 additions & 2 deletions PDTools.Files/Fonts/NVectorFont.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ private void WriteGlyphs(BinaryStream bs)
bs.WriteInt32(glyphDataSize);

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

/// <summary>
/// Adds the glyph to the font (and recalculates the glyph's bounds/width)
/// </summary>
/// <param name="glyph"></param>
/// <exception cref="Exception"></exception>
public void AddGlyph(Glyph glyph)
{
if (Characters.Contains(glyph.Character))
throw new Exception($"Glyph '{glyph.Character}' already exists");

glyph.Points.RecalculateMins();
glyph.Points.RecalculateBounds();
glyph.Width = (ushort)(glyph.Points.XMax - glyph.Points.XMin);

Characters.Add(glyph.Character);
Glyphs.Add(glyph);
Expand Down
2 changes: 0 additions & 2 deletions PDTools.Files/PDTools.Files.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,4 @@
<ProjectReference Include="..\PDTools.Utils\PDTools.Utils.csproj" />
</ItemGroup>

<Import Project="..\Typography\Typography.OpenFont\Typography.OpenFont.projitems" Label="Shared" />

</Project>
31 changes: 30 additions & 1 deletion PDTools.GTPatcher/GT7AppOpt.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,33 @@ rtext_debug
demo_idx

// >1.00 (seen in 1.25)
emotional
emotional

reward_ce_course
autodemo_pause_time
autodemo_return_time
autodemo_entry_num
autodemo_group
autodemo_course
autodemo_track_weather_id
no_bandwidth
lobby_auto_entry
debug_entry_channel
online_race_host
online_race_join_as_spectator
OnlineGameMode
livery_share_file
scapes_open
scapes1
scene_preview_mode
scene_preview_seq
event2
trade_in
virtual_paddock
virtual_paddock_no_car_share
rtext_develop
birthdaygift
livery_special_decken
skip_online_title_movie
enable_bridge_on_steward
vr_showroom_open
13 changes: 7 additions & 6 deletions PDTools.sln
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PDTools.GTPatcher", "PDTool
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Typography.OpenFont", "Typography\Typography.OpenFont\Typography.OpenFont.shproj", "{235A071B-8D06-40AE-A5C5-B1CE59715EE9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PDTools.Files.Fonts.NVecBuilder", "PDTools.Files.Fonts.NVecBuilder\PDTools.Files.Fonts.NVecBuilder.csproj", "{A5CD16E0-AEE4-4656-AD47-717EA915B909}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -119,10 +121,10 @@ Global
{39FA31E3-6088-437C-9697-C86CABFFF733}.Debug|Any CPU.Build.0 = Debug|Any CPU
{39FA31E3-6088-437C-9697-C86CABFFF733}.Release|Any CPU.ActiveCfg = Release|Any CPU
{39FA31E3-6088-437C-9697-C86CABFFF733}.Release|Any CPU.Build.0 = Release|Any CPU
{D585EDFC-C144-486F-BCB9-2991534D8E9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D585EDFC-C144-486F-BCB9-2991534D8E9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D585EDFC-C144-486F-BCB9-2991534D8E9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D585EDFC-C144-486F-BCB9-2991534D8E9E}.Release|Any CPU.Build.0 = Release|Any CPU
{A5CD16E0-AEE4-4656-AD47-717EA915B909}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5CD16E0-AEE4-4656-AD47-717EA915B909}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5CD16E0-AEE4-4656-AD47-717EA915B909}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5CD16E0-AEE4-4656-AD47-717EA915B909}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -132,7 +134,6 @@ Global
EndGlobalSection
GlobalSection(SharedMSBuildProjectFiles) = preSolution
Typography\Typography.OpenFont\Typography.OpenFont.projitems*{235a071b-8d06-40ae-a5c5-b1ce59715ee9}*SharedItemsImports = 13
Typography\Typography.OpenFont\Typography.OpenFont.projitems*{72d26fba-e068-43ae-b5c1-492e45586c13}*SharedItemsImports = 5
Typography\Typography.OpenFont\Typography.OpenFont.projitems*{d585edfc-c144-486f-bcb9-2991534d8e9e}*SharedItemsImports = 5
Typography\Typography.OpenFont\Typography.OpenFont.projitems*{a5cd16e0-aee4-4656-ad47-717ea915b909}*SharedItemsImports = 5
EndGlobalSection
EndGlobal

0 comments on commit 75f1879

Please sign in to comment.